You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2609 lines
73 KiB

4 years ago
  1. /* Riot v4.8.6, @license MIT */
  2. const COMPONENTS_IMPLEMENTATION_MAP = new Map(),
  3. DOM_COMPONENT_INSTANCE_PROPERTY = Symbol('riot-component'),
  4. PLUGINS_SET = new Set(),
  5. IS_DIRECTIVE = 'is',
  6. VALUE_ATTRIBUTE = 'value',
  7. MOUNT_METHOD_KEY = 'mount',
  8. UPDATE_METHOD_KEY = 'update',
  9. UNMOUNT_METHOD_KEY = 'unmount',
  10. SHOULD_UPDATE_KEY = 'shouldUpdate',
  11. ON_BEFORE_MOUNT_KEY = 'onBeforeMount',
  12. ON_MOUNTED_KEY = 'onMounted',
  13. ON_BEFORE_UPDATE_KEY = 'onBeforeUpdate',
  14. ON_UPDATED_KEY = 'onUpdated',
  15. ON_BEFORE_UNMOUNT_KEY = 'onBeforeUnmount',
  16. ON_UNMOUNTED_KEY = 'onUnmounted',
  17. PROPS_KEY = 'props',
  18. STATE_KEY = 'state',
  19. SLOTS_KEY = 'slots',
  20. ROOT_KEY = 'root',
  21. IS_PURE_SYMBOL = Symbol.for('pure'),
  22. PARENT_KEY_SYMBOL = Symbol('parent'),
  23. ATTRIBUTES_KEY_SYMBOL = Symbol('attributes'),
  24. TEMPLATE_KEY_SYMBOL = Symbol('template');
  25. var globals = /*#__PURE__*/Object.freeze({
  26. __proto__: null,
  27. COMPONENTS_IMPLEMENTATION_MAP: COMPONENTS_IMPLEMENTATION_MAP,
  28. DOM_COMPONENT_INSTANCE_PROPERTY: DOM_COMPONENT_INSTANCE_PROPERTY,
  29. PLUGINS_SET: PLUGINS_SET,
  30. IS_DIRECTIVE: IS_DIRECTIVE,
  31. VALUE_ATTRIBUTE: VALUE_ATTRIBUTE,
  32. MOUNT_METHOD_KEY: MOUNT_METHOD_KEY,
  33. UPDATE_METHOD_KEY: UPDATE_METHOD_KEY,
  34. UNMOUNT_METHOD_KEY: UNMOUNT_METHOD_KEY,
  35. SHOULD_UPDATE_KEY: SHOULD_UPDATE_KEY,
  36. ON_BEFORE_MOUNT_KEY: ON_BEFORE_MOUNT_KEY,
  37. ON_MOUNTED_KEY: ON_MOUNTED_KEY,
  38. ON_BEFORE_UPDATE_KEY: ON_BEFORE_UPDATE_KEY,
  39. ON_UPDATED_KEY: ON_UPDATED_KEY,
  40. ON_BEFORE_UNMOUNT_KEY: ON_BEFORE_UNMOUNT_KEY,
  41. ON_UNMOUNTED_KEY: ON_UNMOUNTED_KEY,
  42. PROPS_KEY: PROPS_KEY,
  43. STATE_KEY: STATE_KEY,
  44. SLOTS_KEY: SLOTS_KEY,
  45. ROOT_KEY: ROOT_KEY,
  46. IS_PURE_SYMBOL: IS_PURE_SYMBOL,
  47. PARENT_KEY_SYMBOL: PARENT_KEY_SYMBOL,
  48. ATTRIBUTES_KEY_SYMBOL: ATTRIBUTES_KEY_SYMBOL,
  49. TEMPLATE_KEY_SYMBOL: TEMPLATE_KEY_SYMBOL
  50. });
  51. /**
  52. * Quick type checking
  53. * @param {*} element - anything
  54. * @param {string} type - type definition
  55. * @returns {boolean} true if the type corresponds
  56. */
  57. function checkType(element, type) {
  58. return typeof element === type;
  59. }
  60. /**
  61. * Check that will be passed if its argument is a function
  62. * @param {*} value - value to check
  63. * @returns {boolean} - true if the value is a function
  64. */
  65. function isFunction(value) {
  66. return checkType(value, 'function');
  67. }
  68. function noop() {
  69. return this;
  70. }
  71. /**
  72. * Autobind the methods of a source object to itself
  73. * @param {Object} source - probably a riot tag instance
  74. * @param {Array<string>} methods - list of the methods to autobind
  75. * @returns {Object} the original object received
  76. */
  77. function autobindMethods(source, methods) {
  78. methods.forEach(method => {
  79. source[method] = source[method].bind(source);
  80. });
  81. return source;
  82. }
  83. /**
  84. * Call the first argument received only if it's a function otherwise return it as it is
  85. * @param {*} source - anything
  86. * @returns {*} anything
  87. */
  88. function callOrAssign(source) {
  89. return isFunction(source) ? source.prototype && source.prototype.constructor ? new source() : source() : source;
  90. }
  91. /**
  92. * Convert a string from camel case to dash-case
  93. * @param {string} string - probably a component tag name
  94. * @returns {string} component name normalized
  95. */
  96. /**
  97. * Convert a string containing dashes to camel case
  98. * @param {string} string - input string
  99. * @returns {string} my-string -> myString
  100. */
  101. function dashToCamelCase(string) {
  102. return string.replace(/-(\w)/g, (_, c) => c.toUpperCase());
  103. }
  104. /**
  105. * Move all the child nodes from a source tag to another
  106. * @param {HTMLElement} source - source node
  107. * @param {HTMLElement} target - target node
  108. * @returns {undefined} it's a void method ¯\_()_/¯
  109. */
  110. // Ignore this helper because it's needed only for svg tags
  111. function moveChildren(source, target) {
  112. if (source.firstChild) {
  113. target.appendChild(source.firstChild);
  114. moveChildren(source, target);
  115. }
  116. }
  117. /**
  118. * Remove the child nodes from any DOM node
  119. * @param {HTMLElement} node - target node
  120. * @returns {undefined}
  121. */
  122. function cleanNode(node) {
  123. clearChildren(node.childNodes);
  124. }
  125. /**
  126. * Clear multiple children in a node
  127. * @param {HTMLElement[]} children - direct children nodes
  128. * @returns {undefined}
  129. */
  130. function clearChildren(children) {
  131. Array.from(children).forEach(removeNode);
  132. }
  133. /**
  134. * Remove a node from the DOM
  135. * @param {HTMLElement} node - target node
  136. * @returns {undefined}
  137. */
  138. function removeNode(node) {
  139. const {
  140. parentNode
  141. } = node;
  142. if (node.remove) node.remove();
  143. /* istanbul ignore else */
  144. else if (parentNode) parentNode.removeChild(node);
  145. }
  146. const EACH = 0;
  147. const IF = 1;
  148. const SIMPLE = 2;
  149. const TAG = 3;
  150. const SLOT = 4;
  151. var bindingTypes = {
  152. EACH,
  153. IF,
  154. SIMPLE,
  155. TAG,
  156. SLOT
  157. };
  158. const ATTRIBUTE = 0;
  159. const EVENT = 1;
  160. const TEXT = 2;
  161. const VALUE = 3;
  162. var expressionTypes = {
  163. ATTRIBUTE,
  164. EVENT,
  165. TEXT,
  166. VALUE
  167. };
  168. /**
  169. * Create the template meta object in case of <template> fragments
  170. * @param {TemplateChunk} componentTemplate - template chunk object
  171. * @returns {Object} the meta property that will be passed to the mount function of the TemplateChunk
  172. */
  173. function createTemplateMeta(componentTemplate) {
  174. const fragment = componentTemplate.dom.cloneNode(true);
  175. return {
  176. avoidDOMInjection: true,
  177. fragment,
  178. children: Array.from(fragment.childNodes)
  179. };
  180. }
  181. const {
  182. indexOf: iOF
  183. } = [];
  184. const append = (get, parent, children, start, end, before) => {
  185. const isSelect = 'selectedIndex' in parent;
  186. let noSelection = isSelect;
  187. while (start < end) {
  188. const child = get(children[start], 1);
  189. parent.insertBefore(child, before);
  190. if (isSelect && noSelection && child.selected) {
  191. noSelection = !noSelection;
  192. let {
  193. selectedIndex
  194. } = parent;
  195. parent.selectedIndex = selectedIndex < 0 ? start : iOF.call(parent.querySelectorAll('option'), child);
  196. }
  197. start++;
  198. }
  199. };
  200. const eqeq = (a, b) => a == b;
  201. const identity = O => O;
  202. const indexOf = (moreNodes, moreStart, moreEnd, lessNodes, lessStart, lessEnd, compare) => {
  203. const length = lessEnd - lessStart;
  204. /* istanbul ignore if */
  205. if (length < 1) return -1;
  206. while (moreEnd - moreStart >= length) {
  207. let m = moreStart;
  208. let l = lessStart;
  209. while (m < moreEnd && l < lessEnd && compare(moreNodes[m], lessNodes[l])) {
  210. m++;
  211. l++;
  212. }
  213. if (l === lessEnd) return moreStart;
  214. moreStart = m + 1;
  215. }
  216. return -1;
  217. };
  218. const isReversed = (futureNodes, futureEnd, currentNodes, currentStart, currentEnd, compare) => {
  219. while (currentStart < currentEnd && compare(currentNodes[currentStart], futureNodes[futureEnd - 1])) {
  220. currentStart++;
  221. futureEnd--;
  222. }
  223. return futureEnd === 0;
  224. };
  225. const next = (get, list, i, length, before) => i < length ? get(list[i], 0) : 0 < i ? get(list[i - 1], -0).nextSibling : before;
  226. const remove = (get, children, start, end) => {
  227. while (start < end) drop(get(children[start++], -1));
  228. }; // - - - - - - - - - - - - - - - - - - -
  229. // diff related constants and utilities
  230. // - - - - - - - - - - - - - - - - - - -
  231. const DELETION = -1;
  232. const INSERTION = 1;
  233. const SKIP = 0;
  234. const SKIP_OND = 50;
  235. const HS = (futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges) => {
  236. let k = 0;
  237. /* istanbul ignore next */
  238. let minLen = futureChanges < currentChanges ? futureChanges : currentChanges;
  239. const link = Array(minLen++);
  240. const tresh = Array(minLen);
  241. tresh[0] = -1;
  242. for (let i = 1; i < minLen; i++) tresh[i] = currentEnd;
  243. const nodes = currentNodes.slice(currentStart, currentEnd);
  244. for (let i = futureStart; i < futureEnd; i++) {
  245. const index = nodes.indexOf(futureNodes[i]);
  246. if (-1 < index) {
  247. const idxInOld = index + currentStart;
  248. k = findK(tresh, minLen, idxInOld);
  249. /* istanbul ignore else */
  250. if (-1 < k) {
  251. tresh[k] = idxInOld;
  252. link[k] = {
  253. newi: i,
  254. oldi: idxInOld,
  255. prev: link[k - 1]
  256. };
  257. }
  258. }
  259. }
  260. k = --minLen;
  261. --currentEnd;
  262. while (tresh[k] > currentEnd) --k;
  263. minLen = currentChanges + futureChanges - k;
  264. const diff = Array(minLen);
  265. let ptr = link[k];
  266. --futureEnd;
  267. while (ptr) {
  268. const {
  269. newi,
  270. oldi
  271. } = ptr;
  272. while (futureEnd > newi) {
  273. diff[--minLen] = INSERTION;
  274. --futureEnd;
  275. }
  276. while (currentEnd > oldi) {
  277. diff[--minLen] = DELETION;
  278. --currentEnd;
  279. }
  280. diff[--minLen] = SKIP;
  281. --futureEnd;
  282. --currentEnd;
  283. ptr = ptr.prev;
  284. }
  285. while (futureEnd >= futureStart) {
  286. diff[--minLen] = INSERTION;
  287. --futureEnd;
  288. }
  289. while (currentEnd >= currentStart) {
  290. diff[--minLen] = DELETION;
  291. --currentEnd;
  292. }
  293. return diff;
  294. }; // this is pretty much the same petit-dom code without the delete map part
  295. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L556-L561
  296. const OND = (futureNodes, futureStart, rows, currentNodes, currentStart, cols, compare) => {
  297. const length = rows + cols;
  298. const v = [];
  299. let d, k, r, c, pv, cv, pd;
  300. outer: for (d = 0; d <= length; d++) {
  301. /* istanbul ignore if */
  302. if (d > SKIP_OND) return null;
  303. pd = d - 1;
  304. /* istanbul ignore next */
  305. pv = d ? v[d - 1] : [0, 0];
  306. cv = v[d] = [];
  307. for (k = -d; k <= d; k += 2) {
  308. if (k === -d || k !== d && pv[pd + k - 1] < pv[pd + k + 1]) {
  309. c = pv[pd + k + 1];
  310. } else {
  311. c = pv[pd + k - 1] + 1;
  312. }
  313. r = c - k;
  314. while (c < cols && r < rows && compare(currentNodes[currentStart + c], futureNodes[futureStart + r])) {
  315. c++;
  316. r++;
  317. }
  318. if (c === cols && r === rows) {
  319. break outer;
  320. }
  321. cv[d + k] = c;
  322. }
  323. }
  324. const diff = Array(d / 2 + length / 2);
  325. let diffIdx = diff.length - 1;
  326. for (d = v.length - 1; d >= 0; d--) {
  327. while (c > 0 && r > 0 && compare(currentNodes[currentStart + c - 1], futureNodes[futureStart + r - 1])) {
  328. // diagonal edge = equality
  329. diff[diffIdx--] = SKIP;
  330. c--;
  331. r--;
  332. }
  333. if (!d) break;
  334. pd = d - 1;
  335. /* istanbul ignore next */
  336. pv = d ? v[d - 1] : [0, 0];
  337. k = c - r;
  338. if (k === -d || k !== d && pv[pd + k - 1] < pv[pd + k + 1]) {
  339. // vertical edge = insertion
  340. r--;
  341. diff[diffIdx--] = INSERTION;
  342. } else {
  343. // horizontal edge = deletion
  344. c--;
  345. diff[diffIdx--] = DELETION;
  346. }
  347. }
  348. return diff;
  349. };
  350. const applyDiff = (diff, get, parentNode, futureNodes, futureStart, currentNodes, currentStart, currentLength, before) => {
  351. const live = [];
  352. const length = diff.length;
  353. let currentIndex = currentStart;
  354. let i = 0;
  355. while (i < length) {
  356. switch (diff[i++]) {
  357. case SKIP:
  358. futureStart++;
  359. currentIndex++;
  360. break;
  361. case INSERTION:
  362. // TODO: bulk appends for sequential nodes
  363. live.push(futureNodes[futureStart]);
  364. append(get, parentNode, futureNodes, futureStart++, futureStart, currentIndex < currentLength ? get(currentNodes[currentIndex], 0) : before);
  365. break;
  366. case DELETION:
  367. currentIndex++;
  368. break;
  369. }
  370. }
  371. i = 0;
  372. while (i < length) {
  373. switch (diff[i++]) {
  374. case SKIP:
  375. currentStart++;
  376. break;
  377. case DELETION:
  378. // TODO: bulk removes for sequential nodes
  379. if (-1 < live.indexOf(currentNodes[currentStart])) currentStart++;else remove(get, currentNodes, currentStart++, currentStart);
  380. break;
  381. }
  382. }
  383. };
  384. const findK = (ktr, length, j) => {
  385. let lo = 1;
  386. let hi = length;
  387. while (lo < hi) {
  388. const mid = (lo + hi) / 2 >>> 0;
  389. if (j < ktr[mid]) hi = mid;else lo = mid + 1;
  390. }
  391. return lo;
  392. };
  393. const smartDiff = (get, parentNode, futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges, currentLength, compare, before) => {
  394. applyDiff(OND(futureNodes, futureStart, futureChanges, currentNodes, currentStart, currentChanges, compare) || HS(futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges), get, parentNode, futureNodes, futureStart, currentNodes, currentStart, currentLength, before);
  395. };
  396. const drop = node => (node.remove || dropChild).call(node);
  397. function dropChild() {
  398. const {
  399. parentNode
  400. } = this;
  401. /* istanbul ignore else */
  402. if (parentNode) parentNode.removeChild(this);
  403. }
  404. /*! (c) 2018 Andrea Giammarchi (ISC) */
  405. const domdiff = (parentNode, // where changes happen
  406. currentNodes, // Array of current items/nodes
  407. futureNodes, // Array of future items/nodes
  408. options // optional object with one of the following properties
  409. // before: domNode
  410. // compare(generic, generic) => true if same generic
  411. // node(generic) => Node
  412. ) => {
  413. if (!options) options = {};
  414. const compare = options.compare || eqeq;
  415. const get = options.node || identity;
  416. const before = options.before == null ? null : get(options.before, 0);
  417. const currentLength = currentNodes.length;
  418. let currentEnd = currentLength;
  419. let currentStart = 0;
  420. let futureEnd = futureNodes.length;
  421. let futureStart = 0; // common prefix
  422. while (currentStart < currentEnd && futureStart < futureEnd && compare(currentNodes[currentStart], futureNodes[futureStart])) {
  423. currentStart++;
  424. futureStart++;
  425. } // common suffix
  426. while (currentStart < currentEnd && futureStart < futureEnd && compare(currentNodes[currentEnd - 1], futureNodes[futureEnd - 1])) {
  427. currentEnd--;
  428. futureEnd--;
  429. }
  430. const currentSame = currentStart === currentEnd;
  431. const futureSame = futureStart === futureEnd; // same list
  432. if (currentSame && futureSame) return futureNodes; // only stuff to add
  433. if (currentSame && futureStart < futureEnd) {
  434. append(get, parentNode, futureNodes, futureStart, futureEnd, next(get, currentNodes, currentStart, currentLength, before));
  435. return futureNodes;
  436. } // only stuff to remove
  437. if (futureSame && currentStart < currentEnd) {
  438. remove(get, currentNodes, currentStart, currentEnd);
  439. return futureNodes;
  440. }
  441. const currentChanges = currentEnd - currentStart;
  442. const futureChanges = futureEnd - futureStart;
  443. let i = -1; // 2 simple indels: the shortest sequence is a subsequence of the longest
  444. if (currentChanges < futureChanges) {
  445. i = indexOf(futureNodes, futureStart, futureEnd, currentNodes, currentStart, currentEnd, compare); // inner diff
  446. if (-1 < i) {
  447. append(get, parentNode, futureNodes, futureStart, i, get(currentNodes[currentStart], 0));
  448. append(get, parentNode, futureNodes, i + currentChanges, futureEnd, next(get, currentNodes, currentEnd, currentLength, before));
  449. return futureNodes;
  450. }
  451. }
  452. /* istanbul ignore else */
  453. else if (futureChanges < currentChanges) {
  454. i = indexOf(currentNodes, currentStart, currentEnd, futureNodes, futureStart, futureEnd, compare); // outer diff
  455. if (-1 < i) {
  456. remove(get, currentNodes, currentStart, i);
  457. remove(get, currentNodes, i + futureChanges, currentEnd);
  458. return futureNodes;
  459. }
  460. } // common case with one replacement for many nodes
  461. // or many nodes replaced for a single one
  462. /* istanbul ignore else */
  463. if (currentChanges < 2 || futureChanges < 2) {
  464. append(get, parentNode, futureNodes, futureStart, futureEnd, get(currentNodes[currentStart], 0));
  465. remove(get, currentNodes, currentStart, currentEnd);
  466. return futureNodes;
  467. } // the half match diff part has been skipped in petit-dom
  468. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397
  469. // accordingly, I think it's safe to skip in here too
  470. // if one day it'll come out like the speediest thing ever to do
  471. // then I might add it in here too
  472. // Extra: before going too fancy, what about reversed lists ?
  473. // This should bail out pretty quickly if that's not the case.
  474. if (currentChanges === futureChanges && isReversed(futureNodes, futureEnd, currentNodes, currentStart, currentEnd, compare)) {
  475. append(get, parentNode, futureNodes, futureStart, futureEnd, next(get, currentNodes, currentEnd, currentLength, before));
  476. return futureNodes;
  477. } // last resort through a smart diff
  478. smartDiff(get, parentNode, futureNodes, futureStart, futureEnd, futureChanges, currentNodes, currentStart, currentEnd, currentChanges, currentLength, compare, before);
  479. return futureNodes;
  480. };
  481. /**
  482. * Quick type checking
  483. * @param {*} element - anything
  484. * @param {string} type - type definition
  485. * @returns {boolean} true if the type corresponds
  486. */
  487. function checkType$1(element, type) {
  488. return typeof element === type;
  489. }
  490. /**
  491. * Check if an element is part of an svg
  492. * @param {HTMLElement} el - element to check
  493. * @returns {boolean} true if we are in an svg context
  494. */
  495. function isSvg(el) {
  496. const owner = el.ownerSVGElement;
  497. return !!owner || owner === null;
  498. }
  499. /**
  500. * Check if an element is a template tag
  501. * @param {HTMLElement} el - element to check
  502. * @returns {boolean} true if it's a <template>
  503. */
  504. function isTemplate(el) {
  505. return !isNil(el.content);
  506. }
  507. /**
  508. * Check that will be passed if its argument is a function
  509. * @param {*} value - value to check
  510. * @returns {boolean} - true if the value is a function
  511. */
  512. function isFunction$1(value) {
  513. return checkType$1(value, 'function');
  514. }
  515. /**
  516. * Check if a value is a Boolean
  517. * @param {*} value - anything
  518. * @returns {boolean} true only for the value is a boolean
  519. */
  520. function isBoolean(value) {
  521. return checkType$1(value, 'boolean');
  522. }
  523. /**
  524. * Check if a value is an Object
  525. * @param {*} value - anything
  526. * @returns {boolean} true only for the value is an object
  527. */
  528. function isObject(value) {
  529. return !isNil(value) && checkType$1(value, 'object');
  530. }
  531. /**
  532. * Check if a value is null or undefined
  533. * @param {*} value - anything
  534. * @returns {boolean} true only for the 'undefined' and 'null' types
  535. */
  536. function isNil(value) {
  537. return value === null || value === undefined;
  538. }
  539. const UNMOUNT_SCOPE = Symbol('unmount');
  540. const EachBinding = Object.seal({
  541. // dynamic binding properties
  542. // childrenMap: null,
  543. // node: null,
  544. // root: null,
  545. // condition: null,
  546. // evaluate: null,
  547. // template: null,
  548. // isTemplateTag: false,
  549. nodes: [],
  550. // getKey: null,
  551. // indexName: null,
  552. // itemName: null,
  553. // afterPlaceholder: null,
  554. // placeholder: null,
  555. // API methods
  556. mount(scope, parentScope) {
  557. return this.update(scope, parentScope);
  558. },
  559. update(scope, parentScope) {
  560. const {
  561. placeholder,
  562. nodes,
  563. childrenMap
  564. } = this;
  565. const collection = scope === UNMOUNT_SCOPE ? null : this.evaluate(scope);
  566. const items = collection ? Array.from(collection) : [];
  567. const parent = placeholder.parentNode; // prepare the diffing
  568. const {
  569. newChildrenMap,
  570. batches,
  571. futureNodes
  572. } = createPatch(items, scope, parentScope, this); // patch the DOM only if there are new nodes
  573. domdiff(parent, nodes, futureNodes, {
  574. before: placeholder,
  575. node: patch(Array.from(childrenMap.values()), parentScope)
  576. }); // trigger the mounts and the updates
  577. batches.forEach(fn => fn()); // update the children map
  578. this.childrenMap = newChildrenMap;
  579. this.nodes = futureNodes;
  580. return this;
  581. },
  582. unmount(scope, parentScope) {
  583. this.update(UNMOUNT_SCOPE, parentScope);
  584. return this;
  585. }
  586. });
  587. /**
  588. * Patch the DOM while diffing
  589. * @param {TemplateChunk[]} redundant - redundant tepmplate chunks
  590. * @param {*} parentScope - scope of the parent template
  591. * @returns {Function} patch function used by domdiff
  592. */
  593. function patch(redundant, parentScope) {
  594. return (item, info) => {
  595. if (info < 0) {
  596. const element = redundant.pop();
  597. if (element) {
  598. const {
  599. template,
  600. context
  601. } = element; // notice that we pass null as last argument because
  602. // the root node and its children will be removed by domdiff
  603. template.unmount(context, parentScope, null);
  604. }
  605. }
  606. return item;
  607. };
  608. }
  609. /**
  610. * Check whether a template must be filtered from a loop
  611. * @param {Function} condition - filter function
  612. * @param {Object} context - argument passed to the filter function
  613. * @returns {boolean} true if this item should be skipped
  614. */
  615. function mustFilterItem(condition, context) {
  616. return condition ? Boolean(condition(context)) === false : false;
  617. }
  618. /**
  619. * Extend the scope of the looped template
  620. * @param {Object} scope - current template scope
  621. * @param {string} options.itemName - key to identify the looped item in the new context
  622. * @param {string} options.indexName - key to identify the index of the looped item
  623. * @param {number} options.index - current index
  624. * @param {*} options.item - collection item looped
  625. * @returns {Object} enhanced scope object
  626. */
  627. function extendScope(scope, _ref) {
  628. let {
  629. itemName,
  630. indexName,
  631. index,
  632. item
  633. } = _ref;
  634. scope[itemName] = item;
  635. if (indexName) scope[indexName] = index;
  636. return scope;
  637. }
  638. /**
  639. * Loop the current template items
  640. * @param {Array} items - expression collection value
  641. * @param {*} scope - template scope
  642. * @param {*} parentScope - scope of the parent template
  643. * @param {EeachBinding} binding - each binding object instance
  644. * @returns {Object} data
  645. * @returns {Map} data.newChildrenMap - a Map containing the new children template structure
  646. * @returns {Array} data.batches - array containing the template lifecycle functions to trigger
  647. * @returns {Array} data.futureNodes - array containing the nodes we need to diff
  648. */
  649. function createPatch(items, scope, parentScope, binding) {
  650. const {
  651. condition,
  652. template,
  653. childrenMap,
  654. itemName,
  655. getKey,
  656. indexName,
  657. root,
  658. isTemplateTag
  659. } = binding;
  660. const newChildrenMap = new Map();
  661. const batches = [];
  662. const futureNodes = [];
  663. items.forEach((item, index) => {
  664. const context = extendScope(Object.create(scope), {
  665. itemName,
  666. indexName,
  667. index,
  668. item
  669. });
  670. const key = getKey ? getKey(context) : index;
  671. const oldItem = childrenMap.get(key);
  672. if (mustFilterItem(condition, context)) {
  673. return;
  674. }
  675. const componentTemplate = oldItem ? oldItem.template : template.clone();
  676. const el = oldItem ? componentTemplate.el : root.cloneNode();
  677. const mustMount = !oldItem;
  678. const meta = isTemplateTag && mustMount ? createTemplateMeta(componentTemplate) : {};
  679. if (mustMount) {
  680. batches.push(() => componentTemplate.mount(el, context, parentScope, meta));
  681. } else {
  682. batches.push(() => componentTemplate.update(context, parentScope));
  683. } // create the collection of nodes to update or to add
  684. // in case of template tags we need to add all its children nodes
  685. if (isTemplateTag) {
  686. const children = meta.children || componentTemplate.children;
  687. futureNodes.push(...children); // add fake children into the childrenMap in order to preserve
  688. // the index in case of unmount calls
  689. children.forEach(child => newChildrenMap.set(child, null));
  690. } else {
  691. futureNodes.push(el);
  692. } // delete the old item from the children map
  693. childrenMap.delete(key); // update the children map
  694. newChildrenMap.set(key, {
  695. template: componentTemplate,
  696. context,
  697. index
  698. });
  699. });
  700. return {
  701. newChildrenMap,
  702. batches,
  703. futureNodes
  704. };
  705. }
  706. function create(node, _ref2) {
  707. let {
  708. evaluate,
  709. condition,
  710. itemName,
  711. indexName,
  712. getKey,
  713. template
  714. } = _ref2;
  715. const placeholder = document.createTextNode('');
  716. const parent = node.parentNode;
  717. const root = node.cloneNode();
  718. parent.insertBefore(placeholder, node);
  719. removeNode(node);
  720. return Object.assign({}, EachBinding, {
  721. childrenMap: new Map(),
  722. node,
  723. root,
  724. condition,
  725. evaluate,
  726. isTemplateTag: isTemplate(root),
  727. template: template.createDOM(node),
  728. getKey,
  729. indexName,
  730. itemName,
  731. placeholder
  732. });
  733. }
  734. /**
  735. * Binding responsible for the `if` directive
  736. */
  737. const IfBinding = Object.seal({
  738. // dynamic binding properties
  739. // node: null,
  740. // evaluate: null,
  741. // isTemplateTag: false,
  742. // placeholder: null,
  743. // template: null,
  744. // API methods
  745. mount(scope, parentScope) {
  746. return this.update(scope, parentScope);
  747. },
  748. update(scope, parentScope) {
  749. const value = !!this.evaluate(scope);
  750. const mustMount = !this.value && value;
  751. const mustUnmount = this.value && !value;
  752. const mount = () => {
  753. const pristine = this.node.cloneNode();
  754. this.placeholder.parentNode.insertBefore(pristine, this.placeholder);
  755. this.template = this.template.clone();
  756. this.template.mount(pristine, scope, parentScope);
  757. };
  758. switch (true) {
  759. case mustMount:
  760. mount();
  761. break;
  762. case mustUnmount:
  763. this.unmount(scope);
  764. break;
  765. default:
  766. if (value) this.template.update(scope, parentScope);
  767. }
  768. this.value = value;
  769. return this;
  770. },
  771. unmount(scope, parentScope) {
  772. this.template.unmount(scope, parentScope, true);
  773. return this;
  774. }
  775. });
  776. function create$1(node, _ref3) {
  777. let {
  778. evaluate,
  779. template
  780. } = _ref3;
  781. const parent = node.parentNode;
  782. const placeholder = document.createTextNode('');
  783. parent.insertBefore(placeholder, node);
  784. removeNode(node);
  785. return Object.assign({}, IfBinding, {
  786. node,
  787. evaluate,
  788. placeholder,
  789. template: template.createDOM(node)
  790. });
  791. }
  792. /**
  793. * Returns the memoized (cached) function.
  794. * // borrowed from https://www.30secondsofcode.org/js/s/memoize
  795. * @param {Function} fn - function to memoize
  796. * @returns {Function} memoize function
  797. */
  798. function memoize(fn) {
  799. const cache = new Map();
  800. const cached = val => {
  801. return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
  802. };
  803. cached.cache = cache;
  804. return cached;
  805. }
  806. /**
  807. * Evaluate a list of attribute expressions
  808. * @param {Array} attributes - attribute expressions generated by the riot compiler
  809. * @returns {Object} key value pairs with the result of the computation
  810. */
  811. function evaluateAttributeExpressions(attributes) {
  812. return attributes.reduce((acc, attribute) => {
  813. const {
  814. value,
  815. type
  816. } = attribute;
  817. switch (true) {
  818. // spread attribute
  819. case !attribute.name && type === ATTRIBUTE:
  820. return Object.assign({}, acc, {}, value);
  821. // value attribute
  822. case type === VALUE:
  823. acc.value = attribute.value;
  824. break;
  825. // normal attributes
  826. default:
  827. acc[dashToCamelCase(attribute.name)] = attribute.value;
  828. }
  829. return acc;
  830. }, {});
  831. }
  832. const REMOVE_ATTRIBUTE = 'removeAttribute';
  833. const SET_ATTIBUTE = 'setAttribute';
  834. const ElementProto = typeof Element === 'undefined' ? {} : Element.prototype;
  835. const isNativeHtmlProperty = memoize(name => ElementProto.hasOwnProperty(name)); // eslint-disable-line
  836. /**
  837. * Add all the attributes provided
  838. * @param {HTMLElement} node - target node
  839. * @param {Object} attributes - object containing the attributes names and values
  840. * @returns {undefined} sorry it's a void function :(
  841. */
  842. function setAllAttributes(node, attributes) {
  843. Object.entries(attributes).forEach((_ref4) => {
  844. let [name, value] = _ref4;
  845. return attributeExpression(node, {
  846. name
  847. }, value);
  848. });
  849. }
  850. /**
  851. * Remove all the attributes provided
  852. * @param {HTMLElement} node - target node
  853. * @param {Object} attributes - object containing all the attribute names
  854. * @returns {undefined} sorry it's a void function :(
  855. */
  856. function removeAllAttributes(node, attributes) {
  857. Object.keys(attributes).forEach(attribute => node.removeAttribute(attribute));
  858. }
  859. /**
  860. * This methods handles the DOM attributes updates
  861. * @param {HTMLElement} node - target node
  862. * @param {Object} expression - expression object
  863. * @param {string} expression.name - attribute name
  864. * @param {*} value - new expression value
  865. * @param {*} oldValue - the old expression cached value
  866. * @returns {undefined}
  867. */
  868. function attributeExpression(node, _ref5, value, oldValue) {
  869. let {
  870. name
  871. } = _ref5;
  872. // is it a spread operator? {...attributes}
  873. if (!name) {
  874. // is the value still truthy?
  875. if (value) {
  876. setAllAttributes(node, value);
  877. } else if (oldValue) {
  878. // otherwise remove all the old attributes
  879. removeAllAttributes(node, oldValue);
  880. }
  881. return;
  882. } // handle boolean attributes
  883. if (!isNativeHtmlProperty(name) && (isBoolean(value) || isObject(value) || isFunction$1(value))) {
  884. node[name] = value;
  885. }
  886. node[getMethod(value)](name, normalizeValue(name, value));
  887. }
  888. /**
  889. * Get the attribute modifier method
  890. * @param {*} value - if truthy we return `setAttribute` othewise `removeAttribute`
  891. * @returns {string} the node attribute modifier method name
  892. */
  893. function getMethod(value) {
  894. return isNil(value) || value === false || value === '' || isObject(value) || isFunction$1(value) ? REMOVE_ATTRIBUTE : SET_ATTIBUTE;
  895. }
  896. /**
  897. * Get the value as string
  898. * @param {string} name - attribute name
  899. * @param {*} value - user input value
  900. * @returns {string} input value as string
  901. */
  902. function normalizeValue(name, value) {
  903. // be sure that expressions like selected={ true } will be always rendered as selected='selected'
  904. if (value === true) return name;
  905. return value;
  906. }
  907. const RE_EVENTS_PREFIX = /^on/;
  908. /**
  909. * Set a new event listener
  910. * @param {HTMLElement} node - target node
  911. * @param {Object} expression - expression object
  912. * @param {string} expression.name - event name
  913. * @param {*} value - new expression value
  914. * @param {*} oldValue - old expression value
  915. * @returns {value} the callback just received
  916. */
  917. function eventExpression(node, _ref6, value, oldValue) {
  918. let {
  919. name
  920. } = _ref6;
  921. const normalizedEventName = name.replace(RE_EVENTS_PREFIX, '');
  922. if (oldValue) {
  923. node.removeEventListener(normalizedEventName, oldValue);
  924. }
  925. if (value) {
  926. node.addEventListener(normalizedEventName, value, false);
  927. }
  928. }
  929. /**
  930. * Normalize the user value in order to render a empty string in case of falsy values
  931. * @param {*} value - user input value
  932. * @returns {string} hopefully a string
  933. */
  934. function normalizeStringValue(value) {
  935. return isNil(value) ? '' : value;
  936. }
  937. /**
  938. * Get the the target text node to update or create one from of a comment node
  939. * @param {HTMLElement} node - any html element containing childNodes
  940. * @param {number} childNodeIndex - index of the text node in the childNodes list
  941. * @returns {HTMLTextNode} the text node to update
  942. */
  943. const getTextNode = (node, childNodeIndex) => {
  944. const target = node.childNodes[childNodeIndex];
  945. if (target.nodeType === Node.COMMENT_NODE) {
  946. const textNode = document.createTextNode('');
  947. node.replaceChild(textNode, target);
  948. return textNode;
  949. }
  950. return target;
  951. };
  952. /**
  953. * This methods handles a simple text expression update
  954. * @param {HTMLElement} node - target node
  955. * @param {Object} data - expression object
  956. * @param {*} value - new expression value
  957. * @returns {undefined}
  958. */
  959. function textExpression(node, data, value) {
  960. node.data = normalizeStringValue(value);
  961. }
  962. /**
  963. * This methods handles the input fileds value updates
  964. * @param {HTMLElement} node - target node
  965. * @param {Object} expression - expression object
  966. * @param {*} value - new expression value
  967. * @returns {undefined}
  968. */
  969. function valueExpression(node, expression, value) {
  970. node.value = normalizeStringValue(value);
  971. }
  972. var expressions = {
  973. [ATTRIBUTE]: attributeExpression,
  974. [EVENT]: eventExpression,
  975. [TEXT]: textExpression,
  976. [VALUE]: valueExpression
  977. };
  978. const Expression = Object.seal({
  979. // Static props
  980. // node: null,
  981. // value: null,
  982. // API methods
  983. /**
  984. * Mount the expression evaluating its initial value
  985. * @param {*} scope - argument passed to the expression to evaluate its current values
  986. * @returns {Expression} self
  987. */
  988. mount(scope) {
  989. // hopefully a pure function
  990. this.value = this.evaluate(scope); // IO() DOM updates
  991. apply(this, this.value);
  992. return this;
  993. },
  994. /**
  995. * Update the expression if its value changed
  996. * @param {*} scope - argument passed to the expression to evaluate its current values
  997. * @returns {Expression} self
  998. */
  999. update(scope) {
  1000. // pure function
  1001. const value = this.evaluate(scope);
  1002. if (this.value !== value) {
  1003. // IO() DOM updates
  1004. apply(this, value);
  1005. this.value = value;
  1006. }
  1007. return this;
  1008. },
  1009. /**
  1010. * Expression teardown method
  1011. * @returns {Expression} self
  1012. */
  1013. unmount() {
  1014. // unmount only the event handling expressions
  1015. if (this.type === EVENT) apply(this, null);
  1016. return this;
  1017. }
  1018. });
  1019. /**
  1020. * IO() function to handle the DOM updates
  1021. * @param {Expression} expression - expression object
  1022. * @param {*} value - current expression value
  1023. * @returns {undefined}
  1024. */
  1025. function apply(expression, value) {
  1026. return expressions[expression.type](expression.node, expression, value, expression.value);
  1027. }
  1028. function create$2(node, data) {
  1029. return Object.assign({}, Expression, {}, data, {
  1030. node: data.type === TEXT ? getTextNode(node, data.childNodeIndex) : node
  1031. });
  1032. }
  1033. /**
  1034. * Create a flat object having as keys a list of methods that if dispatched will propagate
  1035. * on the whole collection
  1036. * @param {Array} collection - collection to iterate
  1037. * @param {Array<string>} methods - methods to execute on each item of the collection
  1038. * @param {*} context - context returned by the new methods created
  1039. * @returns {Object} a new object to simplify the the nested methods dispatching
  1040. */
  1041. function flattenCollectionMethods(collection, methods, context) {
  1042. return methods.reduce((acc, method) => {
  1043. return Object.assign({}, acc, {
  1044. [method]: scope => {
  1045. return collection.map(item => item[method](scope)) && context;
  1046. }
  1047. });
  1048. }, {});
  1049. }
  1050. function create$3(node, _ref7) {
  1051. let {
  1052. expressions
  1053. } = _ref7;
  1054. return Object.assign({}, flattenCollectionMethods(expressions.map(expression => create$2(node, expression)), ['mount', 'update', 'unmount']));
  1055. }
  1056. function extendParentScope(attributes, scope, parentScope) {
  1057. if (!attributes || !attributes.length) return parentScope;
  1058. const expressions = attributes.map(attr => Object.assign({}, attr, {
  1059. value: attr.evaluate(scope)
  1060. }));
  1061. return Object.assign(Object.create(parentScope || null), evaluateAttributeExpressions(expressions));
  1062. }
  1063. const SlotBinding = Object.seal({
  1064. // dynamic binding properties
  1065. // node: null,
  1066. // name: null,
  1067. attributes: [],
  1068. // template: null,
  1069. getTemplateScope(scope, parentScope) {
  1070. return extendParentScope(this.attributes, scope, parentScope);
  1071. },
  1072. // API methods
  1073. mount(scope, parentScope) {
  1074. const templateData = scope.slots ? scope.slots.find((_ref8) => {
  1075. let {
  1076. id
  1077. } = _ref8;
  1078. return id === this.name;
  1079. }) : false;
  1080. const {
  1081. parentNode
  1082. } = this.node;
  1083. this.template = templateData && create$6(templateData.html, templateData.bindings).createDOM(parentNode);
  1084. if (this.template) {
  1085. this.template.mount(this.node, this.getTemplateScope(scope, parentScope));
  1086. this.template.children = moveSlotInnerContent(this.node);
  1087. }
  1088. removeNode(this.node);
  1089. return this;
  1090. },
  1091. update(scope, parentScope) {
  1092. if (this.template) {
  1093. this.template.update(this.getTemplateScope(scope, parentScope));
  1094. }
  1095. return this;
  1096. },
  1097. unmount(scope, parentScope, mustRemoveRoot) {
  1098. if (this.template) {
  1099. this.template.unmount(this.getTemplateScope(scope, parentScope), null, mustRemoveRoot);
  1100. }
  1101. return this;
  1102. }
  1103. });
  1104. /**
  1105. * Move the inner content of the slots outside of them
  1106. * @param {HTMLNode} slot - slot node
  1107. * @param {HTMLElement} children - array to fill with the child nodes detected
  1108. * @returns {HTMLElement[]} list of the node moved
  1109. */
  1110. function moveSlotInnerContent(slot, children) {
  1111. if (children === void 0) {
  1112. children = [];
  1113. }
  1114. const child = slot.firstChild;
  1115. if (child) {
  1116. slot.parentNode.insertBefore(child, slot);
  1117. return [child, ...moveSlotInnerContent(slot)];
  1118. }
  1119. return children;
  1120. }
  1121. /**
  1122. * Create a single slot binding
  1123. * @param {HTMLElement} node - slot node
  1124. * @param {string} options.name - slot id
  1125. * @returns {Object} Slot binding object
  1126. */
  1127. function createSlot(node, _ref9) {
  1128. let {
  1129. name,
  1130. attributes
  1131. } = _ref9;
  1132. return Object.assign({}, SlotBinding, {
  1133. attributes,
  1134. node,
  1135. name
  1136. });
  1137. }
  1138. /**
  1139. * Create a new tag object if it was registered before, otherwise fallback to the simple
  1140. * template chunk
  1141. * @param {Function} component - component factory function
  1142. * @param {Array<Object>} slots - array containing the slots markup
  1143. * @param {Array} attributes - dynamic attributes that will be received by the tag element
  1144. * @returns {TagImplementation|TemplateChunk} a tag implementation or a template chunk as fallback
  1145. */
  1146. function getTag(component, slots, attributes) {
  1147. if (slots === void 0) {
  1148. slots = [];
  1149. }
  1150. if (attributes === void 0) {
  1151. attributes = [];
  1152. }
  1153. // if this tag was registered before we will return its implementation
  1154. if (component) {
  1155. return component({
  1156. slots,
  1157. attributes
  1158. });
  1159. } // otherwise we return a template chunk
  1160. return create$6(slotsToMarkup(slots), [...slotBindings(slots), {
  1161. // the attributes should be registered as binding
  1162. // if we fallback to a normal template chunk
  1163. expressions: attributes.map(attr => {
  1164. return Object.assign({
  1165. type: ATTRIBUTE
  1166. }, attr);
  1167. })
  1168. }]);
  1169. }
  1170. /**
  1171. * Merge all the slots bindings into a single array
  1172. * @param {Array<Object>} slots - slots collection
  1173. * @returns {Array<Bindings>} flatten bindings array
  1174. */
  1175. function slotBindings(slots) {
  1176. return slots.reduce((acc, _ref10) => {
  1177. let {
  1178. bindings
  1179. } = _ref10;
  1180. return acc.concat(bindings);
  1181. }, []);
  1182. }
  1183. /**
  1184. * Merge all the slots together in a single markup string
  1185. * @param {Array<Object>} slots - slots collection
  1186. * @returns {string} markup of all the slots in a single string
  1187. */
  1188. function slotsToMarkup(slots) {
  1189. return slots.reduce((acc, slot) => {
  1190. return acc + slot.html;
  1191. }, '');
  1192. }
  1193. const TagBinding = Object.seal({
  1194. // dynamic binding properties
  1195. // node: null,
  1196. // evaluate: null,
  1197. // name: null,
  1198. // slots: null,
  1199. // tag: null,
  1200. // attributes: null,
  1201. // getComponent: null,
  1202. mount(scope) {
  1203. return this.update(scope);
  1204. },
  1205. update(scope, parentScope) {
  1206. const name = this.evaluate(scope); // simple update
  1207. if (name === this.name) {
  1208. this.tag.update(scope);
  1209. } else {
  1210. // unmount the old tag if it exists
  1211. this.unmount(scope, parentScope, true); // mount the new tag
  1212. this.name = name;
  1213. this.tag = getTag(this.getComponent(name), this.slots, this.attributes);
  1214. this.tag.mount(this.node, scope);
  1215. }
  1216. return this;
  1217. },
  1218. unmount(scope, parentScope, keepRootTag) {
  1219. if (this.tag) {
  1220. // keep the root tag
  1221. this.tag.unmount(keepRootTag);
  1222. }
  1223. return this;
  1224. }
  1225. });
  1226. function create$4(node, _ref11) {
  1227. let {
  1228. evaluate,
  1229. getComponent,
  1230. slots,
  1231. attributes
  1232. } = _ref11;
  1233. return Object.assign({}, TagBinding, {
  1234. node,
  1235. evaluate,
  1236. slots,
  1237. attributes,
  1238. getComponent
  1239. });
  1240. }
  1241. var bindings = {
  1242. [IF]: create$1,
  1243. [SIMPLE]: create$3,
  1244. [EACH]: create,
  1245. [TAG]: create$4,
  1246. [SLOT]: createSlot
  1247. };
  1248. /**
  1249. * Text expressions in a template tag will get childNodeIndex value normalized
  1250. * depending on the position of the <template> tag offset
  1251. * @param {Expression[]} expressions - riot expressions array
  1252. * @param {number} textExpressionsOffset - offset of the <template> tag
  1253. * @returns {Expression[]} expressions containing the text expressions normalized
  1254. */
  1255. function fixTextExpressionsOffset(expressions, textExpressionsOffset) {
  1256. return expressions.map(e => e.type === TEXT ? Object.assign({}, e, {
  1257. childNodeIndex: e.childNodeIndex + textExpressionsOffset
  1258. }) : e);
  1259. }
  1260. /**
  1261. * Bind a new expression object to a DOM node
  1262. * @param {HTMLElement} root - DOM node where to bind the expression
  1263. * @param {Object} binding - binding data
  1264. * @param {number|null} templateTagOffset - if it's defined we need to fix the text expressions childNodeIndex offset
  1265. * @returns {Binding} Binding object
  1266. */
  1267. function create$5(root, binding, templateTagOffset) {
  1268. const {
  1269. selector,
  1270. type,
  1271. redundantAttribute,
  1272. expressions
  1273. } = binding; // find the node to apply the bindings
  1274. const node = selector ? root.querySelector(selector) : root; // remove eventually additional attributes created only to select this node
  1275. if (redundantAttribute) node.removeAttribute(redundantAttribute);
  1276. const bindingExpressions = expressions || []; // init the binding
  1277. return (bindings[type] || bindings[SIMPLE])(node, Object.assign({}, binding, {
  1278. expressions: templateTagOffset && !selector ? fixTextExpressionsOffset(bindingExpressions, templateTagOffset) : bindingExpressions
  1279. }));
  1280. } // in this case a simple innerHTML is enough
  1281. function createHTMLTree(html, root) {
  1282. const template = isTemplate(root) ? root : document.createElement('template');
  1283. template.innerHTML = html;
  1284. return template.content;
  1285. } // for svg nodes we need a bit more work
  1286. function createSVGTree(html, container) {
  1287. // create the SVGNode
  1288. const svgNode = container.ownerDocument.importNode(new window.DOMParser().parseFromString(`<svg xmlns="http://www.w3.org/2000/svg">${html}</svg>`, 'application/xml').documentElement, true);
  1289. return svgNode;
  1290. }
  1291. /**
  1292. * Create the DOM that will be injected
  1293. * @param {Object} root - DOM node to find out the context where the fragment will be created
  1294. * @param {string} html - DOM to create as string
  1295. * @returns {HTMLDocumentFragment|HTMLElement} a new html fragment
  1296. */
  1297. function createDOMTree(root, html) {
  1298. if (isSvg(root)) return createSVGTree(html, root);
  1299. return createHTMLTree(html, root);
  1300. }
  1301. /**
  1302. * Inject the DOM tree into a target node
  1303. * @param {HTMLElement} el - target element
  1304. * @param {HTMLFragment|SVGElement} dom - dom tree to inject
  1305. * @returns {undefined}
  1306. */
  1307. function injectDOM(el, dom) {
  1308. switch (true) {
  1309. case isSvg(el):
  1310. moveChildren(dom, el);
  1311. break;
  1312. case isTemplate(el):
  1313. el.parentNode.replaceChild(dom, el);
  1314. break;
  1315. default:
  1316. el.appendChild(dom);
  1317. }
  1318. }
  1319. /**
  1320. * Create the Template DOM skeleton
  1321. * @param {HTMLElement} el - root node where the DOM will be injected
  1322. * @param {string} html - markup that will be injected into the root node
  1323. * @returns {HTMLFragment} fragment that will be injected into the root node
  1324. */
  1325. function createTemplateDOM(el, html) {
  1326. return html && (typeof html === 'string' ? createDOMTree(el, html) : html);
  1327. }
  1328. /**
  1329. * Template Chunk model
  1330. * @type {Object}
  1331. */
  1332. const TemplateChunk = Object.freeze({
  1333. // Static props
  1334. // bindings: null,
  1335. // bindingsData: null,
  1336. // html: null,
  1337. // isTemplateTag: false,
  1338. // fragment: null,
  1339. // children: null,
  1340. // dom: null,
  1341. // el: null,
  1342. /**
  1343. * Create the template DOM structure that will be cloned on each mount
  1344. * @param {HTMLElement} el - the root node
  1345. * @returns {TemplateChunk} self
  1346. */
  1347. createDOM(el) {
  1348. // make sure that the DOM gets created before cloning the template
  1349. this.dom = this.dom || createTemplateDOM(el, this.html);
  1350. return this;
  1351. },
  1352. // API methods
  1353. /**
  1354. * Attach the template to a DOM node
  1355. * @param {HTMLElement} el - target DOM node
  1356. * @param {*} scope - template data
  1357. * @param {*} parentScope - scope of the parent template tag
  1358. * @param {Object} meta - meta properties needed to handle the <template> tags in loops
  1359. * @returns {TemplateChunk} self
  1360. */
  1361. mount(el, scope, parentScope, meta) {
  1362. if (meta === void 0) {
  1363. meta = {};
  1364. }
  1365. if (!el) throw new Error('Please provide DOM node to mount properly your template');
  1366. if (this.el) this.unmount(scope); // <template> tags require a bit more work
  1367. // the template fragment might be already created via meta outside of this call
  1368. const {
  1369. fragment,
  1370. children,
  1371. avoidDOMInjection
  1372. } = meta; // <template> bindings of course can not have a root element
  1373. // so we check the parent node to set the query selector bindings
  1374. const {
  1375. parentNode
  1376. } = children ? children[0] : el;
  1377. const isTemplateTag = isTemplate(el);
  1378. const templateTagOffset = isTemplateTag ? Math.max(Array.from(parentNode.children).indexOf(el), 0) : null;
  1379. this.isTemplateTag = isTemplateTag; // create the DOM if it wasn't created before
  1380. this.createDOM(el);
  1381. if (this.dom) {
  1382. // create the new template dom fragment if it want already passed in via meta
  1383. this.fragment = fragment || this.dom.cloneNode(true);
  1384. } // store root node
  1385. // notice that for template tags the root note will be the parent tag
  1386. this.el = this.isTemplateTag ? parentNode : el; // create the children array only for the <template> fragments
  1387. this.children = this.isTemplateTag ? children || Array.from(this.fragment.childNodes) : null; // inject the DOM into the el only if a fragment is available
  1388. if (!avoidDOMInjection && this.fragment) injectDOM(el, this.fragment); // create the bindings
  1389. this.bindings = this.bindingsData.map(binding => create$5(this.el, binding, templateTagOffset));
  1390. this.bindings.forEach(b => b.mount(scope, parentScope));
  1391. return this;
  1392. },
  1393. /**
  1394. * Update the template with fresh data
  1395. * @param {*} scope - template data
  1396. * @param {*} parentScope - scope of the parent template tag
  1397. * @returns {TemplateChunk} self
  1398. */
  1399. update(scope, parentScope) {
  1400. this.bindings.forEach(b => b.update(scope, parentScope));
  1401. return this;
  1402. },
  1403. /**
  1404. * Remove the template from the node where it was initially mounted
  1405. * @param {*} scope - template data
  1406. * @param {*} parentScope - scope of the parent template tag
  1407. * @param {boolean|null} mustRemoveRoot - if true remove the root element,
  1408. * if false or undefined clean the root tag content, if null don't touch the DOM
  1409. * @returns {TemplateChunk} self
  1410. */
  1411. unmount(scope, parentScope, mustRemoveRoot) {
  1412. if (this.el) {
  1413. this.bindings.forEach(b => b.unmount(scope, parentScope, mustRemoveRoot));
  1414. switch (true) {
  1415. // <template> tags should be treated a bit differently
  1416. // we need to clear their children only if it's explicitly required by the caller
  1417. // via mustRemoveRoot !== null
  1418. case this.children && mustRemoveRoot !== null:
  1419. clearChildren(this.children);
  1420. break;
  1421. // remove the root node only if the mustRemoveRoot === true
  1422. case mustRemoveRoot === true:
  1423. removeNode(this.el);
  1424. break;
  1425. // otherwise we clean the node children
  1426. case mustRemoveRoot !== null:
  1427. cleanNode(this.el);
  1428. break;
  1429. }
  1430. this.el = null;
  1431. }
  1432. return this;
  1433. },
  1434. /**
  1435. * Clone the template chunk
  1436. * @returns {TemplateChunk} a clone of this object resetting the this.el property
  1437. */
  1438. clone() {
  1439. return Object.assign({}, this, {
  1440. el: null
  1441. });
  1442. }
  1443. });
  1444. /**
  1445. * Create a template chunk wiring also the bindings
  1446. * @param {string|HTMLElement} html - template string
  1447. * @param {Array} bindings - bindings collection
  1448. * @returns {TemplateChunk} a new TemplateChunk copy
  1449. */
  1450. function create$6(html, bindings) {
  1451. if (bindings === void 0) {
  1452. bindings = [];
  1453. }
  1454. return Object.assign({}, TemplateChunk, {
  1455. html,
  1456. bindingsData: bindings
  1457. });
  1458. }
  1459. /**
  1460. * Helper function to set an immutable property
  1461. * @param {Object} source - object where the new property will be set
  1462. * @param {string} key - object key where the new property will be stored
  1463. * @param {*} value - value of the new property
  1464. * @param {Object} options - set the propery overriding the default options
  1465. * @returns {Object} - the original object modified
  1466. */
  1467. function defineProperty(source, key, value, options) {
  1468. if (options === void 0) {
  1469. options = {};
  1470. }
  1471. /* eslint-disable fp/no-mutating-methods */
  1472. Object.defineProperty(source, key, Object.assign({
  1473. value,
  1474. enumerable: false,
  1475. writable: false,
  1476. configurable: true
  1477. }, options));
  1478. /* eslint-enable fp/no-mutating-methods */
  1479. return source;
  1480. }
  1481. /**
  1482. * Define multiple properties on a target object
  1483. * @param {Object} source - object where the new properties will be set
  1484. * @param {Object} properties - object containing as key pair the key + value properties
  1485. * @param {Object} options - set the propery overriding the default options
  1486. * @returns {Object} the original object modified
  1487. */
  1488. function defineProperties(source, properties, options) {
  1489. Object.entries(properties).forEach((_ref) => {
  1490. let [key, value] = _ref;
  1491. defineProperty(source, key, value, options);
  1492. });
  1493. return source;
  1494. }
  1495. /**
  1496. * Define default properties if they don't exist on the source object
  1497. * @param {Object} source - object that will receive the default properties
  1498. * @param {Object} defaults - object containing additional optional keys
  1499. * @returns {Object} the original object received enhanced
  1500. */
  1501. function defineDefaults(source, defaults) {
  1502. Object.entries(defaults).forEach((_ref2) => {
  1503. let [key, value] = _ref2;
  1504. if (!source[key]) source[key] = value;
  1505. });
  1506. return source;
  1507. }
  1508. const ATTRIBUTE$1 = 0;
  1509. const VALUE$1 = 3;
  1510. /**
  1511. * Convert a string from camel case to dash-case
  1512. * @param {string} string - probably a component tag name
  1513. * @returns {string} component name normalized
  1514. */
  1515. function camelToDashCase(string) {
  1516. return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  1517. }
  1518. /**
  1519. * Convert a string containing dashes to camel case
  1520. * @param {string} string - input string
  1521. * @returns {string} my-string -> myString
  1522. */
  1523. function dashToCamelCase$1(string) {
  1524. return string.replace(/-(\w)/g, (_, c) => c.toUpperCase());
  1525. }
  1526. /**
  1527. * Throw an error with a descriptive message
  1528. * @param { string } message - error message
  1529. * @returns { undefined } hoppla.. at this point the program should stop working
  1530. */
  1531. function panic(message) {
  1532. throw new Error(message);
  1533. }
  1534. /**
  1535. * Evaluate a list of attribute expressions
  1536. * @param {Array} attributes - attribute expressions generated by the riot compiler
  1537. * @returns {Object} key value pairs with the result of the computation
  1538. */
  1539. function evaluateAttributeExpressions$1(attributes) {
  1540. return attributes.reduce((acc, attribute) => {
  1541. const {
  1542. value,
  1543. type
  1544. } = attribute;
  1545. switch (true) {
  1546. // spread attribute
  1547. case !attribute.name && type === ATTRIBUTE$1:
  1548. return Object.assign({}, acc, {}, value);
  1549. // value attribute
  1550. case type === VALUE$1:
  1551. acc.value = attribute.value;
  1552. break;
  1553. // normal attributes
  1554. default:
  1555. acc[dashToCamelCase$1(attribute.name)] = attribute.value;
  1556. }
  1557. return acc;
  1558. }, {});
  1559. }
  1560. /**
  1561. * Converts any DOM node/s to a loopable array
  1562. * @param { HTMLElement|NodeList } els - single html element or a node list
  1563. * @returns { Array } always a loopable object
  1564. */
  1565. function domToArray(els) {
  1566. // can this object be already looped?
  1567. if (!Array.isArray(els)) {
  1568. // is it a node list?
  1569. if (/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(els)) && typeof els.length === 'number') return Array.from(els);else // if it's a single node
  1570. // it will be returned as "array" with one single entry
  1571. return [els];
  1572. } // this object could be looped out of the box
  1573. return els;
  1574. }
  1575. /**
  1576. * Simple helper to find DOM nodes returning them as array like loopable object
  1577. * @param { string|DOMNodeList } selector - either the query or the DOM nodes to arraify
  1578. * @param { HTMLElement } ctx - context defining where the query will search for the DOM nodes
  1579. * @returns { Array } DOM nodes found as array
  1580. */
  1581. function $(selector, ctx) {
  1582. return domToArray(typeof selector === 'string' ? (ctx || document).querySelectorAll(selector) : selector);
  1583. }
  1584. /**
  1585. * Normalize the return values, in case of a single value we avoid to return an array
  1586. * @param { Array } values - list of values we want to return
  1587. * @returns { Array|string|boolean } either the whole list of values or the single one found
  1588. * @private
  1589. */
  1590. const normalize = values => values.length === 1 ? values[0] : values;
  1591. /**
  1592. * Parse all the nodes received to get/remove/check their attributes
  1593. * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse
  1594. * @param { string|Array } name - name or list of attributes
  1595. * @param { string } method - method that will be used to parse the attributes
  1596. * @returns { Array|string } result of the parsing in a list or a single value
  1597. * @private
  1598. */
  1599. function parseNodes(els, name, method) {
  1600. const names = typeof name === 'string' ? [name] : name;
  1601. return normalize(domToArray(els).map(el => {
  1602. return normalize(names.map(n => el[method](n)));
  1603. }));
  1604. }
  1605. /**
  1606. * Set any attribute on a single or a list of DOM nodes
  1607. * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse
  1608. * @param { string|Object } name - either the name of the attribute to set
  1609. * or a list of properties as object key - value
  1610. * @param { string } value - the new value of the attribute (optional)
  1611. * @returns { HTMLElement|NodeList|Array } the original array of elements passed to this function
  1612. *
  1613. * @example
  1614. *
  1615. * import { set } from 'bianco.attr'
  1616. *
  1617. * const img = document.createElement('img')
  1618. *
  1619. * set(img, 'width', 100)
  1620. *
  1621. * // or also
  1622. * set(img, {
  1623. * width: 300,
  1624. * height: 300
  1625. * })
  1626. *
  1627. */
  1628. function set(els, name, value) {
  1629. const attrs = typeof name === 'object' ? name : {
  1630. [name]: value
  1631. };
  1632. const props = Object.keys(attrs);
  1633. domToArray(els).forEach(el => {
  1634. props.forEach(prop => el.setAttribute(prop, attrs[prop]));
  1635. });
  1636. return els;
  1637. }
  1638. /**
  1639. * Get any attribute from a single or a list of DOM nodes
  1640. * @param { HTMLElement|NodeList|Array } els - DOM node/s to parse
  1641. * @param { string|Array } name - name or list of attributes to get
  1642. * @returns { Array|string } list of the attributes found
  1643. *
  1644. * @example
  1645. *
  1646. * import { get } from 'bianco.attr'
  1647. *
  1648. * const img = document.createElement('img')
  1649. *
  1650. * get(img, 'width') // => '200'
  1651. *
  1652. * // or also
  1653. * get(img, ['width', 'height']) // => ['200', '300']
  1654. *
  1655. * // or also
  1656. * get([img1, img2], ['width', 'height']) // => [['200', '300'], ['500', '200']]
  1657. */
  1658. function get(els, name) {
  1659. return parseNodes(els, name, 'getAttribute');
  1660. }
  1661. const CSS_BY_NAME = new Map();
  1662. const STYLE_NODE_SELECTOR = 'style[riot]'; // memoized curried function
  1663. const getStyleNode = (style => {
  1664. return () => {
  1665. // lazy evaluation:
  1666. // if this function was already called before
  1667. // we return its cached result
  1668. if (style) return style; // create a new style element or use an existing one
  1669. // and cache it internally
  1670. style = $(STYLE_NODE_SELECTOR)[0] || document.createElement('style');
  1671. set(style, 'type', 'text/css');
  1672. /* istanbul ignore next */
  1673. if (!style.parentNode) document.head.appendChild(style);
  1674. return style;
  1675. };
  1676. })();
  1677. /**
  1678. * Object that will be used to inject and manage the css of every tag instance
  1679. */
  1680. var cssManager = {
  1681. CSS_BY_NAME,
  1682. /**
  1683. * Save a tag style to be later injected into DOM
  1684. * @param { string } name - if it's passed we will map the css to a tagname
  1685. * @param { string } css - css string
  1686. * @returns {Object} self
  1687. */
  1688. add(name, css) {
  1689. if (!CSS_BY_NAME.has(name)) {
  1690. CSS_BY_NAME.set(name, css);
  1691. this.inject();
  1692. }
  1693. return this;
  1694. },
  1695. /**
  1696. * Inject all previously saved tag styles into DOM
  1697. * innerHTML seems slow: http://jsperf.com/riot-insert-style
  1698. * @returns {Object} self
  1699. */
  1700. inject() {
  1701. getStyleNode().innerHTML = [...CSS_BY_NAME.values()].join('\n');
  1702. return this;
  1703. },
  1704. /**
  1705. * Remove a tag style from the DOM
  1706. * @param {string} name a registered tagname
  1707. * @returns {Object} self
  1708. */
  1709. remove(name) {
  1710. if (CSS_BY_NAME.has(name)) {
  1711. CSS_BY_NAME.delete(name);
  1712. this.inject();
  1713. }
  1714. return this;
  1715. }
  1716. };
  1717. /**
  1718. * Function to curry any javascript method
  1719. * @param {Function} fn - the target function we want to curry
  1720. * @param {...[args]} acc - initial arguments
  1721. * @returns {Function|*} it will return a function until the target function
  1722. * will receive all of its arguments
  1723. */
  1724. function curry(fn) {
  1725. for (var _len = arguments.length, acc = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  1726. acc[_key - 1] = arguments[_key];
  1727. }
  1728. return function () {
  1729. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  1730. args[_key2] = arguments[_key2];
  1731. }
  1732. args = [...acc, ...args];
  1733. return args.length < fn.length ? curry(fn, ...args) : fn(...args);
  1734. };
  1735. }
  1736. /**
  1737. * Get the tag name of any DOM node
  1738. * @param {HTMLElement} element - DOM node we want to inspect
  1739. * @returns {string} name to identify this dom node in riot
  1740. */
  1741. function getName(element) {
  1742. return get(element, IS_DIRECTIVE) || element.tagName.toLowerCase();
  1743. }
  1744. const COMPONENT_CORE_HELPERS = Object.freeze({
  1745. // component helpers
  1746. $(selector) {
  1747. return $(selector, this.root)[0];
  1748. },
  1749. $$(selector) {
  1750. return $(selector, this.root);
  1751. }
  1752. });
  1753. const PURE_COMPONENT_API = Object.freeze({
  1754. [MOUNT_METHOD_KEY]: noop,
  1755. [UPDATE_METHOD_KEY]: noop,
  1756. [UNMOUNT_METHOD_KEY]: noop
  1757. });
  1758. const COMPONENT_LIFECYCLE_METHODS = Object.freeze({
  1759. [SHOULD_UPDATE_KEY]: noop,
  1760. [ON_BEFORE_MOUNT_KEY]: noop,
  1761. [ON_MOUNTED_KEY]: noop,
  1762. [ON_BEFORE_UPDATE_KEY]: noop,
  1763. [ON_UPDATED_KEY]: noop,
  1764. [ON_BEFORE_UNMOUNT_KEY]: noop,
  1765. [ON_UNMOUNTED_KEY]: noop
  1766. });
  1767. const MOCKED_TEMPLATE_INTERFACE = Object.assign({}, PURE_COMPONENT_API, {
  1768. clone: noop,
  1769. createDOM: noop
  1770. });
  1771. /**
  1772. * Wrap the Riot.js core API methods using a mapping function
  1773. * @param {Function} mapFunction - lifting function
  1774. * @returns {Object} an object having the { mount, update, unmount } functions
  1775. */
  1776. function createCoreAPIMethods(mapFunction) {
  1777. return [MOUNT_METHOD_KEY, UPDATE_METHOD_KEY, UNMOUNT_METHOD_KEY].reduce((acc, method) => {
  1778. acc[method] = mapFunction(method);
  1779. return acc;
  1780. }, {});
  1781. }
  1782. /**
  1783. * Factory function to create the component templates only once
  1784. * @param {Function} template - component template creation function
  1785. * @param {Object} components - object containing the nested components
  1786. * @returns {TemplateChunk} template chunk object
  1787. */
  1788. function componentTemplateFactory(template, components) {
  1789. return template(create$6, expressionTypes, bindingTypes, name => {
  1790. return components[name] || COMPONENTS_IMPLEMENTATION_MAP.get(name);
  1791. });
  1792. }
  1793. /**
  1794. * Create a pure component
  1795. * @param {Function} pureFactoryFunction - pure component factory function
  1796. * @param {Array} options.slots - component slots
  1797. * @param {Array} options.attributes - component attributes
  1798. * @param {Array} options.template - template factory function
  1799. * @param {Array} options.template - template factory function
  1800. * @param {any} options.props - initial component properties
  1801. * @returns {Object} pure component object
  1802. */
  1803. function createPureComponent(pureFactoryFunction, _ref) {
  1804. let {
  1805. slots,
  1806. attributes,
  1807. props,
  1808. css,
  1809. template
  1810. } = _ref;
  1811. if (template) panic('Pure components can not have html');
  1812. if (css) panic('Pure components do not have css');
  1813. const component = defineDefaults(pureFactoryFunction({
  1814. slots,
  1815. attributes,
  1816. props
  1817. }), PURE_COMPONENT_API);
  1818. return createCoreAPIMethods(method => function () {
  1819. component[method](...arguments);
  1820. return component;
  1821. });
  1822. }
  1823. /**
  1824. * Create the component interface needed for the @riotjs/dom-bindings tag bindings
  1825. * @param {string} options.css - component css
  1826. * @param {Function} options.template - functon that will return the dom-bindings template function
  1827. * @param {Object} options.exports - component interface
  1828. * @param {string} options.name - component name
  1829. * @returns {Object} component like interface
  1830. */
  1831. function createComponent(_ref2) {
  1832. let {
  1833. css,
  1834. template,
  1835. exports,
  1836. name
  1837. } = _ref2;
  1838. const templateFn = template ? componentTemplateFactory(template, exports ? createSubcomponents(exports.components) : {}) : MOCKED_TEMPLATE_INTERFACE;
  1839. return (_ref3) => {
  1840. let {
  1841. slots,
  1842. attributes,
  1843. props
  1844. } = _ref3;
  1845. // pure components rendering will be managed by the end user
  1846. if (exports && exports[IS_PURE_SYMBOL]) return createPureComponent(exports, {
  1847. slots,
  1848. attributes,
  1849. props,
  1850. css,
  1851. template
  1852. });
  1853. const componentAPI = callOrAssign(exports) || {};
  1854. const component = defineComponent({
  1855. css,
  1856. template: templateFn,
  1857. componentAPI,
  1858. name
  1859. })({
  1860. slots,
  1861. attributes,
  1862. props
  1863. }); // notice that for the components create via tag binding
  1864. // we need to invert the mount (state/parentScope) arguments
  1865. // the template bindings will only forward the parentScope updates
  1866. // and never deal with the component state
  1867. return {
  1868. mount(element, parentScope, state) {
  1869. return component.mount(element, state, parentScope);
  1870. },
  1871. update(parentScope, state) {
  1872. return component.update(state, parentScope);
  1873. },
  1874. unmount(preserveRoot) {
  1875. return component.unmount(preserveRoot);
  1876. }
  1877. };
  1878. };
  1879. }
  1880. /**
  1881. * Component definition function
  1882. * @param {Object} implementation - the componen implementation will be generated via compiler
  1883. * @param {Object} component - the component initial properties
  1884. * @returns {Object} a new component implementation object
  1885. */
  1886. function defineComponent(_ref4) {
  1887. let {
  1888. css,
  1889. template,
  1890. componentAPI,
  1891. name
  1892. } = _ref4;
  1893. // add the component css into the DOM
  1894. if (css && name) cssManager.add(name, css);
  1895. return curry(enhanceComponentAPI)(defineProperties( // set the component defaults without overriding the original component API
  1896. defineDefaults(componentAPI, Object.assign({}, COMPONENT_LIFECYCLE_METHODS, {
  1897. [STATE_KEY]: {}
  1898. })), Object.assign({
  1899. // defined during the component creation
  1900. [SLOTS_KEY]: null,
  1901. [ROOT_KEY]: null
  1902. }, COMPONENT_CORE_HELPERS, {
  1903. name,
  1904. css,
  1905. template
  1906. })));
  1907. }
  1908. /**
  1909. * Create the bindings to update the component attributes
  1910. * @param {HTMLElement} node - node where we will bind the expressions
  1911. * @param {Array} attributes - list of attribute bindings
  1912. * @returns {TemplateChunk} - template bindings object
  1913. */
  1914. function createAttributeBindings(node, attributes) {
  1915. if (attributes === void 0) {
  1916. attributes = [];
  1917. }
  1918. const expressions = attributes.map(a => create$2(node, a));
  1919. const binding = {};
  1920. return Object.assign(binding, Object.assign({
  1921. expressions
  1922. }, createCoreAPIMethods(method => scope => {
  1923. expressions.forEach(e => e[method](scope));
  1924. return binding;
  1925. })));
  1926. }
  1927. /**
  1928. * Create the subcomponents that can be included inside a tag in runtime
  1929. * @param {Object} components - components imported in runtime
  1930. * @returns {Object} all the components transformed into Riot.Component factory functions
  1931. */
  1932. function createSubcomponents(components) {
  1933. if (components === void 0) {
  1934. components = {};
  1935. }
  1936. return Object.entries(callOrAssign(components)).reduce((acc, _ref5) => {
  1937. let [key, value] = _ref5;
  1938. acc[camelToDashCase(key)] = createComponent(value);
  1939. return acc;
  1940. }, {});
  1941. }
  1942. /**
  1943. * Run the component instance through all the plugins set by the user
  1944. * @param {Object} component - component instance
  1945. * @returns {Object} the component enhanced by the plugins
  1946. */
  1947. function runPlugins(component) {
  1948. return [...PLUGINS_SET].reduce((c, fn) => fn(c) || c, component);
  1949. }
  1950. /**
  1951. * Compute the component current state merging it with its previous state
  1952. * @param {Object} oldState - previous state object
  1953. * @param {Object} newState - new state givent to the `update` call
  1954. * @returns {Object} new object state
  1955. */
  1956. function computeState(oldState, newState) {
  1957. return Object.assign({}, oldState, {}, callOrAssign(newState));
  1958. }
  1959. /**
  1960. * Add eventually the "is" attribute to link this DOM node to its css
  1961. * @param {HTMLElement} element - target root node
  1962. * @param {string} name - name of the component mounted
  1963. * @returns {undefined} it's a void function
  1964. */
  1965. function addCssHook(element, name) {
  1966. if (getName(element) !== name) {
  1967. set(element, IS_DIRECTIVE, name);
  1968. }
  1969. }
  1970. /**
  1971. * Component creation factory function that will enhance the user provided API
  1972. * @param {Object} component - a component implementation previously defined
  1973. * @param {Array} options.slots - component slots generated via riot compiler
  1974. * @param {Array} options.attributes - attribute expressions generated via riot compiler
  1975. * @returns {Riot.Component} a riot component instance
  1976. */
  1977. function enhanceComponentAPI(component, _ref6) {
  1978. let {
  1979. slots,
  1980. attributes,
  1981. props
  1982. } = _ref6;
  1983. return autobindMethods(runPlugins(defineProperties(Object.create(component), {
  1984. mount(element, state, parentScope) {
  1985. if (state === void 0) {
  1986. state = {};
  1987. }
  1988. this[ATTRIBUTES_KEY_SYMBOL] = createAttributeBindings(element, attributes).mount(parentScope);
  1989. defineProperty(this, PROPS_KEY, Object.freeze(Object.assign({}, props, {}, evaluateAttributeExpressions$1(this[ATTRIBUTES_KEY_SYMBOL].expressions))));
  1990. this[STATE_KEY] = computeState(this[STATE_KEY], state);
  1991. this[TEMPLATE_KEY_SYMBOL] = this.template.createDOM(element).clone(); // link this object to the DOM node
  1992. element[DOM_COMPONENT_INSTANCE_PROPERTY] = this; // add eventually the 'is' attribute
  1993. component.name && addCssHook(element, component.name); // define the root element
  1994. defineProperty(this, ROOT_KEY, element); // define the slots array
  1995. defineProperty(this, SLOTS_KEY, slots); // before mount lifecycle event
  1996. this[ON_BEFORE_MOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]); // mount the template
  1997. this[TEMPLATE_KEY_SYMBOL].mount(element, this, parentScope);
  1998. this[PARENT_KEY_SYMBOL] = parentScope;
  1999. this[ON_MOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
  2000. return this;
  2001. },
  2002. update(state, parentScope) {
  2003. if (state === void 0) {
  2004. state = {};
  2005. }
  2006. if (parentScope) {
  2007. this[ATTRIBUTES_KEY_SYMBOL].update(parentScope);
  2008. }
  2009. const newProps = evaluateAttributeExpressions$1(this[ATTRIBUTES_KEY_SYMBOL].expressions);
  2010. if (this[SHOULD_UPDATE_KEY](newProps, this[PROPS_KEY]) === false) return;
  2011. defineProperty(this, PROPS_KEY, Object.freeze(Object.assign({}, props, {}, newProps)));
  2012. this[STATE_KEY] = computeState(this[STATE_KEY], state);
  2013. this[ON_BEFORE_UPDATE_KEY](this[PROPS_KEY], this[STATE_KEY]);
  2014. this[TEMPLATE_KEY_SYMBOL].update(this, this[PARENT_KEY_SYMBOL]);
  2015. this[ON_UPDATED_KEY](this[PROPS_KEY], this[STATE_KEY]);
  2016. return this;
  2017. },
  2018. unmount(preserveRoot) {
  2019. this[ON_BEFORE_UNMOUNT_KEY](this[PROPS_KEY], this[STATE_KEY]);
  2020. this[ATTRIBUTES_KEY_SYMBOL].unmount(); // if the preserveRoot is null the template html will be left untouched
  2021. // in that case the DOM cleanup will happen differently from a parent node
  2022. this[TEMPLATE_KEY_SYMBOL].unmount(this, this[PARENT_KEY_SYMBOL], preserveRoot === null ? null : !preserveRoot);
  2023. this[ON_UNMOUNTED_KEY](this[PROPS_KEY], this[STATE_KEY]);
  2024. return this;
  2025. }
  2026. })), Object.keys(component).filter(prop => isFunction(component[prop])));
  2027. }
  2028. /**
  2029. * Component initialization function starting from a DOM node
  2030. * @param {HTMLElement} element - element to upgrade
  2031. * @param {Object} initialProps - initial component properties
  2032. * @param {string} componentName - component id
  2033. * @returns {Object} a new component instance bound to a DOM node
  2034. */
  2035. function mountComponent(element, initialProps, componentName) {
  2036. const name = componentName || getName(element);
  2037. if (!COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component named "${name}" was never registered`);
  2038. const component = COMPONENTS_IMPLEMENTATION_MAP.get(name)({
  2039. props: initialProps
  2040. });
  2041. return component.mount(element);
  2042. }
  2043. /**
  2044. * Get all the element attributes as object
  2045. * @param {HTMLElement} element - DOM node we want to parse
  2046. * @returns {Object} all the attributes found as a key value pairs
  2047. */
  2048. function DOMattributesToObject(element) {
  2049. return Array.from(element.attributes).reduce((acc, attribute) => {
  2050. acc[dashToCamelCase$1(attribute.name)] = attribute.value;
  2051. return acc;
  2052. }, {});
  2053. }
  2054. /**
  2055. * Similar to compose but performs from left-to-right function composition.<br/>
  2056. * {@link https://30secondsofcode.org/function#composeright see also}
  2057. * @param {...[function]} fns) - list of unary function
  2058. * @returns {*} result of the computation
  2059. */
  2060. /**
  2061. * Performs right-to-left function composition.<br/>
  2062. * Use Array.prototype.reduce() to perform right-to-left function composition.<br/>
  2063. * The last (rightmost) function can accept one or more arguments; the remaining functions must be unary.<br/>
  2064. * {@link https://30secondsofcode.org/function#compose original source code}
  2065. * @param {...[function]} fns) - list of unary function
  2066. * @returns {*} result of the computation
  2067. */
  2068. function compose() {
  2069. for (var _len2 = arguments.length, fns = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  2070. fns[_key2] = arguments[_key2];
  2071. }
  2072. return fns.reduce((f, g) => function () {
  2073. return f(g(...arguments));
  2074. });
  2075. }
  2076. const {
  2077. DOM_COMPONENT_INSTANCE_PROPERTY: DOM_COMPONENT_INSTANCE_PROPERTY$1,
  2078. COMPONENTS_IMPLEMENTATION_MAP: COMPONENTS_IMPLEMENTATION_MAP$1,
  2079. PLUGINS_SET: PLUGINS_SET$1
  2080. } = globals;
  2081. /**
  2082. * Evaluate the component properties either from its real attributes or from its initial user properties
  2083. * @param {HTMLElement} element - component root
  2084. * @param {Object} initialProps - initial props
  2085. * @returns {Object} component props key value pairs
  2086. */
  2087. function evaluateInitialProps(element, initialProps) {
  2088. if (initialProps === void 0) {
  2089. initialProps = [];
  2090. }
  2091. return Object.assign({}, DOMattributesToObject(element), {}, callOrAssign(initialProps));
  2092. }
  2093. /**
  2094. * Riot public api
  2095. */
  2096. /**
  2097. * Register a custom tag by name
  2098. * @param {string} name - component name
  2099. * @param {Object} implementation - tag implementation
  2100. * @returns {Map} map containing all the components implementations
  2101. */
  2102. function register(name, _ref) {
  2103. let {
  2104. css,
  2105. template,
  2106. exports
  2107. } = _ref;
  2108. if (COMPONENTS_IMPLEMENTATION_MAP$1.has(name)) panic(`The component "${name}" was already registered`);
  2109. COMPONENTS_IMPLEMENTATION_MAP$1.set(name, createComponent({
  2110. name,
  2111. css,
  2112. template,
  2113. exports
  2114. }));
  2115. return COMPONENTS_IMPLEMENTATION_MAP$1;
  2116. }
  2117. /**
  2118. * Unregister a riot web component
  2119. * @param {string} name - component name
  2120. * @returns {Map} map containing all the components implementations
  2121. */
  2122. function unregister(name) {
  2123. if (!COMPONENTS_IMPLEMENTATION_MAP$1.has(name)) panic(`The component "${name}" was never registered`);
  2124. COMPONENTS_IMPLEMENTATION_MAP$1.delete(name);
  2125. cssManager.remove(name);
  2126. return COMPONENTS_IMPLEMENTATION_MAP$1;
  2127. }
  2128. /**
  2129. * Mounting function that will work only for the components that were globally registered
  2130. * @param {string|HTMLElement} selector - query for the selection or a DOM element
  2131. * @param {Object} initialProps - the initial component properties
  2132. * @param {string} name - optional component name
  2133. * @returns {Array} list of nodes upgraded
  2134. */
  2135. function mount(selector, initialProps, name) {
  2136. return $(selector).map(element => mountComponent(element, evaluateInitialProps(element, initialProps), name));
  2137. }
  2138. /**
  2139. * Sweet unmounting helper function for the DOM node mounted manually by the user
  2140. * @param {string|HTMLElement} selector - query for the selection or a DOM element
  2141. * @param {boolean|null} keepRootElement - if true keep the root element
  2142. * @returns {Array} list of nodes unmounted
  2143. */
  2144. function unmount(selector, keepRootElement) {
  2145. return $(selector).map(element => {
  2146. if (element[DOM_COMPONENT_INSTANCE_PROPERTY$1]) {
  2147. element[DOM_COMPONENT_INSTANCE_PROPERTY$1].unmount(keepRootElement);
  2148. }
  2149. return element;
  2150. });
  2151. }
  2152. /**
  2153. * Define a riot plugin
  2154. * @param {Function} plugin - function that will receive all the components created
  2155. * @returns {Set} the set containing all the plugins installed
  2156. */
  2157. function install(plugin) {
  2158. if (!isFunction(plugin)) panic('Plugins must be of type function');
  2159. if (PLUGINS_SET$1.has(plugin)) panic('This plugin was already install');
  2160. PLUGINS_SET$1.add(plugin);
  2161. return PLUGINS_SET$1;
  2162. }
  2163. /**
  2164. * Uninstall a riot plugin
  2165. * @param {Function} plugin - plugin previously installed
  2166. * @returns {Set} the set containing all the plugins installed
  2167. */
  2168. function uninstall(plugin) {
  2169. if (!PLUGINS_SET$1.has(plugin)) panic('This plugin was never installed');
  2170. PLUGINS_SET$1.delete(plugin);
  2171. return PLUGINS_SET$1;
  2172. }
  2173. /**
  2174. * Helpter method to create component without relying on the registered ones
  2175. * @param {Object} implementation - component implementation
  2176. * @returns {Function} function that will allow you to mount a riot component on a DOM node
  2177. */
  2178. function component(implementation) {
  2179. return function (el, props, _temp) {
  2180. let {
  2181. slots,
  2182. attributes
  2183. } = _temp === void 0 ? {} : _temp;
  2184. return compose(c => c.mount(el), c => c({
  2185. props: evaluateInitialProps(el, props),
  2186. slots,
  2187. attributes
  2188. }), createComponent)(implementation);
  2189. };
  2190. }
  2191. /**
  2192. * Lift a riot component Interface into a pure riot object
  2193. * @param {Function} func - RiotPureComponent factory function
  2194. * @returns {Function} the lifted original function received as argument
  2195. */
  2196. function pure(func) {
  2197. if (!isFunction(func)) panic('riot.pure accepts only arguments of type "function"');
  2198. func[IS_PURE_SYMBOL] = true;
  2199. return func;
  2200. }
  2201. /** @type {string} current riot version */
  2202. const version = 'v4.8.6'; // expose some internal stuff that might be used from external tools
  2203. const __ = {
  2204. cssManager,
  2205. createComponent,
  2206. defineComponent,
  2207. globals
  2208. };
  2209. export { __, component, install, mount, pure, register, uninstall, unmount, unregister, version };