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.

2384 lines
69 KiB

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