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.

2366 lines
65 KiB

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