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.

2628 lines
78 KiB

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