@htmlplus/element 0.3.3 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/client/decorators/attributes.js +3 -3
  2. package/client/decorators/element.js +11 -4
  3. package/client/decorators/event.js +4 -4
  4. package/client/decorators/listen.js +2 -1
  5. package/client/decorators/method.js +4 -5
  6. package/client/decorators/property.d.ts +0 -4
  7. package/client/decorators/property.js +38 -43
  8. package/client/decorators/state.js +17 -26
  9. package/client/decorators/watch.d.ts +7 -1
  10. package/client/decorators/watch.js +24 -10
  11. package/client/helpers/classes.d.ts +1 -1
  12. package/client/helpers/index.d.ts +3 -3
  13. package/client/helpers/index.js +3 -3
  14. package/client/helpers/{is-rtl.d.ts → isRTL.d.ts} +0 -0
  15. package/client/helpers/{is-rtl.js → isRTL.js} +0 -0
  16. package/client/helpers/{query-all.d.ts → queryAll.d.ts} +0 -0
  17. package/client/helpers/{query-all.js → queryAll.js} +0 -0
  18. package/client/helpers/slots.js +1 -1
  19. package/client/helpers/styles.js +2 -1
  20. package/client/helpers/{to-unit.d.ts → toUnit.d.ts} +0 -0
  21. package/client/helpers/{to-unit.js → toUnit.js} +0 -0
  22. package/client/utils/{append-to-method.d.ts → appendToMethod.d.ts} +0 -0
  23. package/client/utils/{append-to-method.js → appendToMethod.js} +0 -0
  24. package/client/utils/config.d.ts +10 -0
  25. package/client/utils/config.js +8 -0
  26. package/client/utils/{define-property.d.ts → defineProperty.d.ts} +0 -0
  27. package/client/utils/{define-property.js → defineProperty.js} +0 -0
  28. package/client/utils/event.d.ts +5 -2
  29. package/client/utils/event.js +9 -4
  30. package/client/utils/{get-members.d.ts → getMembers.d.ts} +0 -0
  31. package/client/utils/{get-members.js → getMembers.js} +0 -0
  32. package/client/utils/{get-styles.d.ts → getStyles.d.ts} +0 -0
  33. package/client/utils/{get-styles.js → getStyles.js} +0 -0
  34. package/client/utils/index.d.ts +12 -14
  35. package/client/utils/index.js +12 -14
  36. package/client/utils/{is-event.d.ts → isEvent.d.ts} +0 -0
  37. package/client/utils/{is-event.js → isEvent.js} +0 -0
  38. package/client/utils/{is-server.d.ts → isServer.d.ts} +0 -0
  39. package/client/utils/{is-server.js → isServer.js} +0 -0
  40. package/client/utils/{parse-value.d.ts → parseValue.d.ts} +0 -0
  41. package/client/utils/{parse-value.js → parseValue.js} +2 -2
  42. package/client/utils/request.d.ts +3 -1
  43. package/client/utils/request.js +15 -5
  44. package/client/utils/sync.js +3 -3
  45. package/client/utils/{to-boolean.d.ts → toBoolean.d.ts} +0 -0
  46. package/client/utils/{to-boolean.js → toBoolean.js} +0 -0
  47. package/client/utils/{to-event.d.ts → toEvent.d.ts} +0 -0
  48. package/client/utils/{to-event.js → toEvent.js} +0 -0
  49. package/client/utils/{type-of.d.ts → typeOf.d.ts} +0 -0
  50. package/client/utils/{type-of.js → typeOf.js} +0 -0
  51. package/client/utils/{update-attribute.d.ts → updateAttribute.d.ts} +0 -0
  52. package/client/utils/{update-attribute.js → updateAttribute.js} +0 -0
  53. package/client/vendor/uhtml.d.ts +22 -0
  54. package/client/vendor/uhtml.js +700 -0
  55. package/compiler/compiler.js +42 -22
  56. package/compiler/plugins/customElement.d.ts +4 -1
  57. package/compiler/plugins/customElement.js +96 -77
  58. package/compiler/plugins/customElementReact/customElementReact.d.ts +1 -1
  59. package/compiler/plugins/customElementReact/customElementReact.js +13 -9
  60. package/compiler/plugins/customElementReact/templates/package.json.hbs +2 -2
  61. package/compiler/plugins/customElementReact/templates/src/components/{{fileName}}.compact.ts.hbs +4 -4
  62. package/compiler/plugins/customElementReact/templates/src/components/{{fileName}}.ts.hbs +1 -1
  63. package/compiler/plugins/{docs.d.ts → document.d.ts} +3 -4
  64. package/compiler/plugins/document.js +254 -0
  65. package/compiler/plugins/external.d.ts +9 -0
  66. package/compiler/plugins/external.js +25 -0
  67. package/compiler/plugins/extract.d.ts +1 -1
  68. package/compiler/plugins/extract.js +14 -12
  69. package/compiler/plugins/index.d.ts +2 -0
  70. package/compiler/plugins/index.js +2 -0
  71. package/compiler/plugins/style.js +1 -1
  72. package/compiler/utils/{get-initializer.d.ts → getInitializer.d.ts} +0 -0
  73. package/compiler/utils/{get-initializer.js → getInitializer.js} +0 -0
  74. package/compiler/utils/{get-type.d.ts → getType.d.ts} +0 -0
  75. package/compiler/utils/{get-type.js → getType.js} +0 -0
  76. package/compiler/utils/getTypeReference.d.ts +2 -0
  77. package/compiler/utils/getTypeReference.js +33 -0
  78. package/compiler/utils/{has-decorator.d.ts → hasDecorator.d.ts} +0 -0
  79. package/compiler/utils/{has-decorator.js → hasDecorator.js} +0 -0
  80. package/compiler/utils/index.d.ts +8 -7
  81. package/compiler/utils/index.js +8 -7
  82. package/compiler/utils/{is-directory-empty.d.ts → isDirectoryEmpty.d.ts} +0 -0
  83. package/compiler/utils/{is-directory-empty.js → isDirectoryEmpty.js} +0 -0
  84. package/compiler/utils/{print-type.d.ts → printType.d.ts} +0 -0
  85. package/compiler/utils/{print-type.js → printType.js} +0 -0
  86. package/compiler/utils/{render-template.d.ts → renderTemplate.d.ts} +0 -0
  87. package/compiler/utils/{render-template.js → renderTemplate.js} +0 -0
  88. package/compiler/utils/tags.d.ts +13 -0
  89. package/compiler/utils/tags.js +53 -0
  90. package/constants/index.d.ts +1 -2
  91. package/constants/index.js +2 -2
  92. package/package.json +22 -27
  93. package/types/context.d.ts +7 -0
  94. package/types/index.d.ts +1 -1
  95. package/types/index.js +1 -1
  96. package/types/plugin.d.ts +4 -3
  97. package/types/{plus-element.d.ts → plusElement.d.ts} +0 -0
  98. package/types/{plus-element.js → plusElement.js} +0 -0
  99. package/client/utils/is-ready.d.ts +0 -2
  100. package/client/utils/is-ready.js +0 -4
  101. package/client/utils/on-ready.d.ts +0 -2
  102. package/client/utils/on-ready.js +0 -6
  103. package/client/utils/render.d.ts +0 -2
  104. package/client/utils/render.js +0 -19
  105. package/compiler/plugins/componentDependencyResolver.d.ts +0 -5
  106. package/compiler/plugins/componentDependencyResolver.js +0 -40
  107. package/compiler/plugins/docs.js +0 -320
  108. package/compiler/utils/get-tags.d.ts +0 -6
  109. package/compiler/utils/get-tags.js +0 -34
  110. package/runtime/index.d.ts +0 -1
  111. package/runtime/index.js +0 -4
@@ -0,0 +1,700 @@
1
+ class MapSet extends Map {
2
+ set(key, value) {
3
+ super.set(key, value);
4
+ return value;
5
+ }
6
+ }
7
+ class WeakMapSet extends WeakMap {
8
+ set(key, value) {
9
+ super.set(key, value);
10
+ return value;
11
+ }
12
+ }
13
+ /*! (c) Andrea Giammarchi - ISC */
14
+ const empty = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
15
+ const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
16
+ const attributes = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
17
+ const holes = /[\x01\x02]/g;
18
+ // \x01 Node.ELEMENT_NODE
19
+ // \x02 Node.ATTRIBUTE_NODE
20
+ /**
21
+ * Given a template, find holes as both nodes and attributes and
22
+ * return a string with holes as either comment nodes or named attributes.
23
+ * @param {string[]} template a template literal tag array
24
+ * @param {string} prefix prefix to use per each comment/attribute
25
+ * @param {boolean} svg enforces self-closing tags
26
+ * @returns {string} X/HTML with prefixed comments or attributes
27
+ */
28
+ var instrument = (template, prefix, svg) => {
29
+ let i = 0;
30
+ return template
31
+ .join('\x01')
32
+ .trim()
33
+ .replace(elements, (_, name, attrs, selfClosing) => {
34
+ let ml = name + attrs.replace(attributes, '\x02=$2$1').trimEnd();
35
+ if (selfClosing.length)
36
+ ml += svg || empty.test(name) ? ' /' : '></' + name;
37
+ return '<' + ml + '>';
38
+ })
39
+ .replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
40
+ };
41
+ const ELEMENT_NODE = 1;
42
+ const nodeType = 111;
43
+ const remove = ({ firstChild, lastChild }) => {
44
+ const range = document.createRange();
45
+ range.setStartAfter(firstChild);
46
+ range.setEndAfter(lastChild);
47
+ range.deleteContents();
48
+ return firstChild;
49
+ };
50
+ const diffable = (node, operation) => node.nodeType === nodeType
51
+ ? 1 / operation < 0
52
+ ? operation
53
+ ? remove(node)
54
+ : node.lastChild
55
+ : operation
56
+ ? node.valueOf()
57
+ : node.firstChild
58
+ : node;
59
+ const persistent = (fragment) => {
60
+ const { firstChild, lastChild } = fragment;
61
+ if (firstChild === lastChild)
62
+ return lastChild || fragment;
63
+ const { childNodes } = fragment;
64
+ const nodes = [...childNodes];
65
+ return {
66
+ ELEMENT_NODE,
67
+ nodeType,
68
+ firstChild,
69
+ lastChild,
70
+ valueOf() {
71
+ if (childNodes.length !== nodes.length)
72
+ fragment.append(...nodes);
73
+ return fragment;
74
+ }
75
+ };
76
+ };
77
+ const { isArray: isArray$1 } = Array;
78
+ const aria = (node) => (values) => {
79
+ for (const key in values) {
80
+ const name = key === 'role' ? key : `aria-${key}`;
81
+ const value = values[key];
82
+ if (value == null)
83
+ node.removeAttribute(name);
84
+ else
85
+ node.setAttribute(name, value);
86
+ }
87
+ };
88
+ const attribute = (node, name) => {
89
+ let oldValue, orphan = true;
90
+ const attributeNode = document.createAttributeNS(null, name);
91
+ return (newValue) => {
92
+ if (oldValue !== newValue) {
93
+ oldValue = newValue;
94
+ if (oldValue == null) {
95
+ if (!orphan) {
96
+ node.removeAttributeNode(attributeNode);
97
+ orphan = true;
98
+ }
99
+ }
100
+ else {
101
+ const value = newValue;
102
+ if (value == null) {
103
+ if (!orphan)
104
+ node.removeAttributeNode(attributeNode);
105
+ orphan = true;
106
+ }
107
+ else {
108
+ attributeNode.value = value;
109
+ if (orphan) {
110
+ node.setAttributeNodeNS(attributeNode);
111
+ orphan = false;
112
+ }
113
+ }
114
+ }
115
+ }
116
+ };
117
+ };
118
+ const boolean = (node, key, oldValue) => (newValue) => {
119
+ if (oldValue !== !!newValue) {
120
+ // when IE won't be around anymore ...
121
+ // node.toggleAttribute(key, oldValue = !!newValue);
122
+ if ((oldValue = !!newValue))
123
+ node.setAttribute(key, '');
124
+ else
125
+ node.removeAttribute(key);
126
+ }
127
+ };
128
+ const data = ({ dataset }) => (values) => {
129
+ for (const key in values) {
130
+ const value = values[key];
131
+ if (value == null)
132
+ delete dataset[key];
133
+ else
134
+ dataset[key] = value;
135
+ }
136
+ };
137
+ const event = (node, name) => {
138
+ let oldValue, lower, type = name.slice(2);
139
+ if (!(name in node) && (lower = name.toLowerCase()) in node)
140
+ type = lower.slice(2);
141
+ return (newValue) => {
142
+ const info = isArray$1(newValue) ? newValue : [newValue, false];
143
+ if (oldValue !== info[0]) {
144
+ if (oldValue)
145
+ node.removeEventListener(type, oldValue, info[1]);
146
+ if ((oldValue = info[0]))
147
+ node.addEventListener(type, oldValue, info[1]);
148
+ }
149
+ };
150
+ };
151
+ const ref = (node) => {
152
+ let oldValue;
153
+ return (value) => {
154
+ if (oldValue !== value) {
155
+ oldValue = value;
156
+ if (typeof value === 'function')
157
+ value(node);
158
+ else
159
+ value.current = node;
160
+ }
161
+ };
162
+ };
163
+ const setter = (node, key) => key === 'dataset'
164
+ ? data(node)
165
+ : (value) => {
166
+ node[key] = value;
167
+ };
168
+ const text = (node) => {
169
+ let oldValue;
170
+ return (newValue) => {
171
+ if (oldValue != newValue) {
172
+ oldValue = newValue;
173
+ node.textContent = newValue == null ? '' : newValue;
174
+ }
175
+ };
176
+ };
177
+ /**
178
+ * ISC License
179
+ *
180
+ * Copyright (c) 2020, Andrea Giammarchi, @WebReflection
181
+ *
182
+ * Permission to use, copy, modify, and/or distribute this software for any
183
+ * purpose with or without fee is hereby granted, provided that the above
184
+ * copyright notice and this permission notice appear in all copies.
185
+ *
186
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
187
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
188
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
189
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
190
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
191
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
192
+ * PERFORMANCE OF THIS SOFTWARE.
193
+ */
194
+ /**
195
+ * @param {Node} parentNode The container where children live
196
+ * @param {Node[]} a The list of current/live children
197
+ * @param {Node[]} b The list of future children
198
+ * @param {(entry: Node, action: number) => Node} get
199
+ * The callback invoked per each entry related DOM operation.
200
+ * @param {Node} [before] The optional node used as anchor to insert before.
201
+ * @returns {Node[]} The same list of future children.
202
+ */
203
+ var udomdiff = (parentNode, a, b, get, before) => {
204
+ const bLength = b.length;
205
+ let aEnd = a.length;
206
+ let bEnd = bLength;
207
+ let aStart = 0;
208
+ let bStart = 0;
209
+ let map = null;
210
+ while (aStart < aEnd || bStart < bEnd) {
211
+ // append head, tail, or nodes in between: fast path
212
+ if (aEnd === aStart) {
213
+ // we could be in a situation where the rest of nodes that
214
+ // need to be added are not at the end, and in such case
215
+ // the node to `insertBefore`, if the index is more than 0
216
+ // must be retrieved, otherwise it's gonna be the first item.
217
+ const node = bEnd < bLength ? (bStart ? get(b[bStart - 1], -0).nextSibling : get(b[bEnd - bStart], 0)) : before;
218
+ while (bStart < bEnd)
219
+ parentNode.insertBefore(get(b[bStart++], 1), node);
220
+ }
221
+ // remove head or tail: fast path
222
+ else if (bEnd === bStart) {
223
+ while (aStart < aEnd) {
224
+ // remove the node only if it's unknown or not live
225
+ if (!map || !map.has(a[aStart]))
226
+ parentNode.removeChild(get(a[aStart], -1));
227
+ aStart++;
228
+ }
229
+ }
230
+ // same node: fast path
231
+ else if (a[aStart] === b[bStart]) {
232
+ aStart++;
233
+ bStart++;
234
+ }
235
+ // same tail: fast path
236
+ else if (a[aEnd - 1] === b[bEnd - 1]) {
237
+ aEnd--;
238
+ bEnd--;
239
+ }
240
+ // The once here single last swap "fast path" has been removed in v1.1.0
241
+ // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
242
+ // reverse swap: also fast path
243
+ else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
244
+ // this is a "shrink" operation that could happen in these cases:
245
+ // [1, 2, 3, 4, 5]
246
+ // [1, 4, 3, 2, 5]
247
+ // or asymmetric too
248
+ // [1, 2, 3, 4, 5]
249
+ // [1, 2, 3, 5, 6, 4]
250
+ const node = get(a[--aEnd], -1).nextSibling;
251
+ parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
252
+ parentNode.insertBefore(get(b[--bEnd], 1), node);
253
+ // mark the future index as identical (yeah, it's dirty, but cheap 👍)
254
+ // The main reason to do this, is that when a[aEnd] will be reached,
255
+ // the loop will likely be on the fast path, as identical to b[bEnd].
256
+ // In the best case scenario, the next loop will skip the tail,
257
+ // but in the worst one, this node will be considered as already
258
+ // processed, bailing out pretty quickly from the map index check
259
+ a[aEnd] = b[bEnd];
260
+ }
261
+ // map based fallback, "slow" path
262
+ else {
263
+ // the map requires an O(bEnd - bStart) operation once
264
+ // to store all future nodes indexes for later purposes.
265
+ // In the worst case scenario, this is a full O(N) cost,
266
+ // and such scenario happens at least when all nodes are different,
267
+ // but also if both first and last items of the lists are different
268
+ if (!map) {
269
+ map = new Map();
270
+ let i = bStart;
271
+ while (i < bEnd)
272
+ map.set(b[i], i++);
273
+ }
274
+ // if it's a future node, hence it needs some handling
275
+ if (map.has(a[aStart])) {
276
+ // grab the index of such node, 'cause it might have been processed
277
+ const index = map.get(a[aStart]);
278
+ // if it's not already processed, look on demand for the next LCS
279
+ if (bStart < index && index < bEnd) {
280
+ let i = aStart;
281
+ // counts the amount of nodes that are the same in the future
282
+ let sequence = 1;
283
+ while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence)
284
+ sequence++;
285
+ // effort decision here: if the sequence is longer than replaces
286
+ // needed to reach such sequence, which would brings again this loop
287
+ // to the fast path, prepend the difference before a sequence,
288
+ // and move only the future list index forward, so that aStart
289
+ // and bStart will be aligned again, hence on the fast path.
290
+ // An example considering aStart and bStart are both 0:
291
+ // a: [1, 2, 3, 4]
292
+ // b: [7, 1, 2, 3, 6]
293
+ // this would place 7 before 1 and, from that time on, 1, 2, and 3
294
+ // will be processed at zero cost
295
+ if (sequence > index - bStart) {
296
+ const node = get(a[aStart], 0);
297
+ while (bStart < index)
298
+ parentNode.insertBefore(get(b[bStart++], 1), node);
299
+ }
300
+ // if the effort wasn't good enough, fallback to a replace,
301
+ // moving both source and target indexes forward, hoping that some
302
+ // similar node will be found later on, to go back to the fast path
303
+ else {
304
+ parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
305
+ }
306
+ }
307
+ // otherwise move the source forward, 'cause there's nothing to do
308
+ else
309
+ aStart++;
310
+ }
311
+ // this node has no meaning in the future list, so it's more than safe
312
+ // to remove it, and check the next live node out instead, meaning
313
+ // that only the live list index should be forwarded
314
+ else
315
+ parentNode.removeChild(get(a[aStart++], -1));
316
+ }
317
+ }
318
+ return b;
319
+ };
320
+ const { isArray, prototype } = Array;
321
+ const { indexOf } = prototype;
322
+ const document = typeof window == 'undefined' ? {} : window.document;
323
+ const { createDocumentFragment, createElement, createElementNS, createTextNode, createTreeWalker, importNode } = new Proxy(document, {
324
+ get: (target, method) => (target[method] || function () { }).bind(target)
325
+ });
326
+ const createHTML = (html) => {
327
+ const template = createElement('template');
328
+ template.innerHTML = html;
329
+ return template.content;
330
+ };
331
+ let xml;
332
+ const createSVG = (svg) => {
333
+ if (!xml)
334
+ xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
335
+ xml.innerHTML = svg;
336
+ const content = createDocumentFragment();
337
+ content.append(...xml.childNodes);
338
+ return content;
339
+ };
340
+ const createContent = (text, svg) => (svg ? createSVG(text) : createHTML(text));
341
+ // from a generic path, retrieves the exact targeted node
342
+ const reducePath = ({ childNodes }, i) => childNodes[i];
343
+ // this helper avoid code bloat around handleAnything() callback
344
+ const diff = (comment, oldNodes, newNodes) => udomdiff(comment.parentNode,
345
+ // TODO: there is a possible edge case where a node has been
346
+ // removed manually, or it was a keyed one, attached
347
+ // to a shared reference between renders.
348
+ // In this case udomdiff might fail at removing such node
349
+ // as its parent won't be the expected one.
350
+ // The best way to avoid this issue is to filter oldNodes
351
+ // in search of those not live, or not in the current parent
352
+ // anymore, but this would require both a change to uwire,
353
+ // exposing a parentNode from the firstChild, as example,
354
+ // but also a filter per each diff that should exclude nodes
355
+ // that are not in there, penalizing performance quite a lot.
356
+ // As this has been also a potential issue with domdiff,
357
+ // and both lighterhtml and hyperHTML might fail with this
358
+ // very specific edge case, I might as well document this possible
359
+ // "diffing shenanigan" and call it a day.
360
+ oldNodes, newNodes, diffable, comment);
361
+ // if an interpolation represents a comment, the whole
362
+ // diffing will be related to such comment.
363
+ // This helper is in charge of understanding how the new
364
+ // content for such interpolation/hole should be updated
365
+ const handleAnything = (comment) => {
366
+ let oldValue, text, nodes = [];
367
+ const anyContent = (newValue) => {
368
+ switch (typeof newValue) {
369
+ // primitives are handled as text content
370
+ case 'string':
371
+ case 'number':
372
+ case 'boolean':
373
+ if (oldValue !== newValue) {
374
+ oldValue = newValue;
375
+ if (!text)
376
+ text = createTextNode('');
377
+ text.data = newValue;
378
+ nodes = diff(comment, nodes, [text]);
379
+ }
380
+ break;
381
+ // null, and undefined are used to cleanup previous content
382
+ case 'object':
383
+ case 'undefined':
384
+ if (newValue == null) {
385
+ if (oldValue != newValue) {
386
+ oldValue = newValue;
387
+ nodes = diff(comment, nodes, []);
388
+ }
389
+ break;
390
+ }
391
+ // arrays and nodes have a special treatment
392
+ if (isArray(newValue)) {
393
+ oldValue = newValue;
394
+ // arrays can be used to cleanup, if empty
395
+ if (newValue.length === 0)
396
+ nodes = diff(comment, nodes, []);
397
+ // or diffed, if these contains nodes or "wires"
398
+ else if (typeof newValue[0] === 'object')
399
+ nodes = diff(comment, nodes, newValue);
400
+ // in all other cases the content is stringified as is
401
+ else
402
+ anyContent(String(newValue));
403
+ break;
404
+ }
405
+ // if the new value is a DOM node, or a wire, and it's
406
+ // different from the one already live, then it's diffed.
407
+ // if the node is a fragment, it's appended once via its childNodes
408
+ // There is no `else` here, meaning if the content
409
+ // is not expected one, nothing happens, as easy as that.
410
+ if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
411
+ oldValue = newValue;
412
+ nodes = diff(comment, nodes, newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]);
413
+ }
414
+ break;
415
+ case 'function':
416
+ anyContent(newValue(comment));
417
+ break;
418
+ }
419
+ };
420
+ return anyContent;
421
+ };
422
+ // attributes can be:
423
+ // * ref=${...} for hooks and other purposes
424
+ // * aria=${...} for aria attributes
425
+ // * ?boolean=${...} for boolean attributes
426
+ // * .dataset=${...} for dataset related attributes
427
+ // * .setter=${...} for Custom Elements setters or nodes with setters
428
+ // such as buttons, details, options, select, etc
429
+ // * @event=${...} to explicitly handle event listeners
430
+ // * onevent=${...} to automatically handle event listeners
431
+ // * generic=${...} to handle an attribute just like an attribute
432
+ const handleAttribute = (node, name /*, svg*/) => {
433
+ switch (name[0]) {
434
+ case '?':
435
+ return boolean(node, name.slice(1), false);
436
+ case '.':
437
+ return setter(node, name.slice(1));
438
+ case '@':
439
+ return event(node, 'on' + name.slice(1));
440
+ case 'o':
441
+ if (name[1] === 'n')
442
+ return event(node, name);
443
+ }
444
+ switch (name) {
445
+ case 'ref':
446
+ return ref(node);
447
+ case 'aria':
448
+ return aria(node);
449
+ }
450
+ return attribute(node, name /*, svg*/);
451
+ };
452
+ // each mapped update carries the update type and its path
453
+ // the type is either node, attribute, or text, while
454
+ // the path is how to retrieve the related node to update.
455
+ // In the attribute case, the attribute name is also carried along.
456
+ function handlers(options) {
457
+ const { type, path } = options;
458
+ const node = path.reduceRight(reducePath, this);
459
+ return type === 'node'
460
+ ? handleAnything(node)
461
+ : type === 'attr'
462
+ ? handleAttribute(node, options.name /*, options.svg*/)
463
+ : text(node);
464
+ }
465
+ // from a fragment container, create an array of indexes
466
+ // related to its child nodes, so that it's possible
467
+ // to retrieve later on exact node via reducePath
468
+ const createPath = (node) => {
469
+ const path = [];
470
+ let { parentNode } = node;
471
+ while (parentNode) {
472
+ path.push(indexOf.call(parentNode.childNodes, node));
473
+ node = parentNode;
474
+ ({ parentNode } = node);
475
+ }
476
+ return path;
477
+ };
478
+ // the prefix is used to identify either comments, attributes, or nodes
479
+ // that contain the related unique id. In the attribute cases
480
+ // isµX="attribute-name" will be used to map current X update to that
481
+ // attribute name, while comments will be like <!--isµX-->, to map
482
+ // the update to that specific comment node, hence its parent.
483
+ // style and textarea will have <!--isµX--> text content, and are handled
484
+ // directly through text-only updates.
485
+ const prefix = 'isµ';
486
+ // Template Literals are unique per scope and static, meaning a template
487
+ // should be parsed once, and once only, as it will always represent the same
488
+ // content, within the exact same amount of updates each time.
489
+ // This cache relates each template to its unique content and updates.
490
+ const cache$1 = new WeakMapSet();
491
+ // a RegExp that helps checking nodes that cannot contain comments
492
+ const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
493
+ const createCache = () => ({
494
+ stack: [],
495
+ entry: null,
496
+ // * the template that is representing
497
+ // * the type of node it represents (html or svg)
498
+ // * the content fragment with all nodes
499
+ // * the list of updates per each node (template holes)
500
+ // * the "wired" node or fragment that will get updates
501
+ // if the template or type are different from the previous one
502
+ // the entry gets re-created each time
503
+ wire: null // each rendered node represent some wired content and
504
+ // this reference to the latest one. If different, the node
505
+ // will be cleaned up and the new "wire" will be appended
506
+ });
507
+ // the entry stored in the rendered node cache, and per each "hole"
508
+ const createEntry = (type, template) => {
509
+ const { content, updates } = mapUpdates(type, template);
510
+ return { type, template, content, updates, wire: null };
511
+ };
512
+ // a template is instrumented to be able to retrieve where updates are needed.
513
+ // Each unique template becomes a fragment, cloned once per each other
514
+ // operation based on the same template, i.e. data => html`<p>${data}</p>`
515
+ const mapTemplate = (type, template) => {
516
+ const svg = type === 'svg';
517
+ const text = instrument(template, prefix, svg);
518
+ const content = createContent(text, svg);
519
+ // once instrumented and reproduced as fragment, it's crawled
520
+ // to find out where each update is in the fragment tree
521
+ const tw = createTreeWalker(content, 1 | 128);
522
+ const nodes = [];
523
+ const length = template.length - 1;
524
+ let i = 0;
525
+ // updates are searched via unique names, linearly increased across the tree
526
+ // <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
527
+ let search = `${prefix}${i}`;
528
+ while (i < length) {
529
+ const node = tw.nextNode();
530
+ // if not all updates are bound but there's nothing else to crawl
531
+ // it means that there is something wrong with the template.
532
+ if (!node)
533
+ throw `bad template: ${text}`;
534
+ // if the current node is a comment, and it contains isµX
535
+ // it means the update should take care of any content
536
+ if (node.nodeType === 8) {
537
+ // The only comments to be considered are those
538
+ // which content is exactly the same as the searched one.
539
+ if (node.data === search) {
540
+ nodes.push({ type: 'node', path: createPath(node) });
541
+ search = `${prefix}${++i}`;
542
+ }
543
+ }
544
+ else {
545
+ // if the node is not a comment, loop through all its attributes
546
+ // named isµX and relate attribute updates to this node and the
547
+ // attribute name, retrieved through node.getAttribute("isµX")
548
+ // the isµX attribute will be removed as irrelevant for the layout
549
+ // let svg = -1;
550
+ while (node.hasAttribute(search)) {
551
+ nodes.push({
552
+ type: 'attr',
553
+ path: createPath(node),
554
+ name: node.getAttribute(search)
555
+ });
556
+ node.removeAttribute(search);
557
+ search = `${prefix}${++i}`;
558
+ }
559
+ // if the node was a style, textarea, or others, check its content
560
+ // and if it is <!--isµX--> then update tex-only this node
561
+ if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
562
+ node.textContent = '';
563
+ nodes.push({ type: 'text', path: createPath(node) });
564
+ search = `${prefix}${++i}`;
565
+ }
566
+ }
567
+ }
568
+ // once all nodes to update, or their attributes, are known, the content
569
+ // will be cloned in the future to represent the template, and all updates
570
+ // related to such content retrieved right away without needing to re-crawl
571
+ // the exact same template, and its content, more than once.
572
+ return { content, nodes };
573
+ };
574
+ // if a template is unknown, perform the previous mapping, otherwise grab
575
+ // its details such as the fragment with all nodes, and updates info.
576
+ const mapUpdates = (type, template) => {
577
+ const { content, nodes } = cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
578
+ // clone deeply the fragment
579
+ const fragment = importNode(content, true);
580
+ // and relate an update handler per each node that needs one
581
+ const updates = nodes.map(handlers, fragment);
582
+ // return the fragment and all updates to use within its nodes
583
+ return { content: fragment, updates };
584
+ };
585
+ // as html and svg can be nested calls, but no parent node is known
586
+ // until rendered somewhere, the unroll operation is needed to
587
+ // discover what to do with each interpolation, which will result
588
+ // into an update operation.
589
+ const unroll = (info, { type, template, values }) => {
590
+ // interpolations can contain holes and arrays, so these need
591
+ // to be recursively discovered
592
+ const length = unrollValues(info, values);
593
+ let { entry } = info;
594
+ // if the cache entry is either null or different from the template
595
+ // and the type this unroll should resolve, create a new entry
596
+ // assigning a new content fragment and the list of updates.
597
+ if (!entry || entry.template !== template || entry.type !== type)
598
+ info.entry = entry = createEntry(type, template);
599
+ const { content, updates, wire } = entry;
600
+ // even if the fragment and its nodes is not live yet,
601
+ // it is already possible to update via interpolations values.
602
+ for (let i = 0; i < length; i++)
603
+ updates[i](values[i]);
604
+ // if the entry was new, or representing a different template or type,
605
+ // create a new persistent entity to use during diffing.
606
+ // This is simply a DOM node, when the template has a single container,
607
+ // as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
608
+ return wire || (entry.wire = persistent(content));
609
+ };
610
+ // the stack retains, per each interpolation value, the cache
611
+ // related to each interpolation value, or null, if the render
612
+ // was conditional and the value is not special (Array or Hole)
613
+ const unrollValues = ({ stack }, values) => {
614
+ const { length } = values;
615
+ for (let i = 0; i < length; i++) {
616
+ const hole = values[i];
617
+ // each Hole gets unrolled and re-assigned as value
618
+ // so that domdiff will deal with a node/wire, not with a hole
619
+ if (hole instanceof Hole)
620
+ values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
621
+ // arrays are recursively resolved so that each entry will contain
622
+ // also a DOM node or a wire, hence it can be diffed if/when needed
623
+ else if (isArray(hole))
624
+ unrollValues(stack[i] || (stack[i] = createCache()), hole);
625
+ // if the value is nothing special, the stack doesn't need to retain data
626
+ // this is useful also to cleanup previously retained data, if the value
627
+ // was a Hole, or an Array, but not anymore, i.e.:
628
+ // const update = content => html`<div>${content}</div>`;
629
+ // update(listOfItems); update(null); update(html`hole`)
630
+ else
631
+ stack[i] = null;
632
+ }
633
+ if (length < stack.length)
634
+ stack.splice(length);
635
+ return length;
636
+ };
637
+ /**
638
+ * Holds all details wrappers needed to render the content further on.
639
+ * @constructor
640
+ * @param {string} type The hole type, either `html` or `svg`.
641
+ * @param {string[]} template The template literals used to the define the content.
642
+ * @param {Array} values Zero, one, or more interpolated values to render.
643
+ */
644
+ class Hole {
645
+ constructor(type, template, values) {
646
+ this.type = type;
647
+ this.template = template;
648
+ this.values = values;
649
+ }
650
+ }
651
+ // both `html` and `svg` template literal tags are polluted
652
+ // with a `for(ref[, id])` and a `node` tag too
653
+ const tag = (type) => {
654
+ // both `html` and `svg` tags have their own cache
655
+ const keyed = new WeakMapSet();
656
+ // keyed operations always re-use the same cache and unroll
657
+ // the template and its interpolations right away
658
+ const fixed = (cache) => (template, ...values) => unroll(cache, { type, template, values });
659
+ return Object.assign(
660
+ // non keyed operations are recognized as instance of Hole
661
+ // during the "unroll", recursively resolved and updated
662
+ (template, ...values) => new Hole(type, template, values), {
663
+ // keyed operations need a reference object, usually the parent node
664
+ // which is showing keyed results, and optionally a unique id per each
665
+ // related node, handy with JSON results and mutable list of objects
666
+ // that usually carry a unique identifier
667
+ for(ref, id) {
668
+ const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
669
+ return memo.get(id) || memo.set(id, fixed(createCache()));
670
+ },
671
+ // it is possible to create one-off content out of the box via node tag
672
+ // this might return the single created node, or a fragment with all
673
+ // nodes present at the root level and, of course, their child nodes
674
+ node: (template, ...values) => unroll(createCache(), new Hole(type, template, values)).valueOf()
675
+ });
676
+ };
677
+ // each rendered node gets its own cache
678
+ const cache = new WeakMapSet();
679
+ // rendering means understanding what `html` or `svg` tags returned
680
+ // and it relates a specific node to its own unique cache.
681
+ // Each time the content to render changes, the node is cleaned up
682
+ // and the new new content is appended, and if such content is a Hole
683
+ // then it's "unrolled" to resolve all its inner nodes.
684
+ const render = (where, what) => {
685
+ const hole = typeof what === 'function' ? what() : what;
686
+ const info = cache.get(where) || cache.set(where, createCache());
687
+ const wire = hole instanceof Hole ? unroll(info, hole) : hole;
688
+ if (wire !== info.wire) {
689
+ info.wire = wire;
690
+ // valueOf() simply returns the node itself, but in case it was a "wire"
691
+ // it will eventually re-append all nodes to its fragment so that such
692
+ // fragment can be re-appended many times in a meaningful way
693
+ // (wires are basically persistent fragments facades with special behavior)
694
+ where.replaceChildren(wire.valueOf());
695
+ }
696
+ return where;
697
+ };
698
+ const html = tag('html');
699
+ const svg = tag('svg');
700
+ export { Hole, html, render, svg };