@mantou/gem 2.1.1 → 2.2.0

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 (115) hide show
  1. package/dist/gem.es.js +1832 -1890
  2. package/dist/gem.umd.js +34 -191
  3. package/elements/base/dialog.d.ts +0 -4
  4. package/elements/base/dialog.d.ts.map +1 -1
  5. package/elements/base/dialog.js +0 -4
  6. package/elements/base/dialog.js.map +1 -1
  7. package/elements/base/gesture.d.ts +5 -14
  8. package/elements/base/gesture.d.ts.map +1 -1
  9. package/elements/base/gesture.js +35 -31
  10. package/elements/base/gesture.js.map +1 -1
  11. package/elements/base/link.d.ts +1 -17
  12. package/elements/base/link.d.ts.map +1 -1
  13. package/elements/base/link.js +12 -32
  14. package/elements/base/link.js.map +1 -1
  15. package/elements/base/modal-factory.d.ts +30 -31
  16. package/elements/base/modal-factory.d.ts.map +1 -1
  17. package/elements/base/reflect.d.ts +0 -3
  18. package/elements/base/reflect.d.ts.map +1 -1
  19. package/elements/base/reflect.js +0 -3
  20. package/elements/base/reflect.js.map +1 -1
  21. package/elements/base/route.d.ts +0 -10
  22. package/elements/base/route.d.ts.map +1 -1
  23. package/elements/base/route.js +10 -22
  24. package/elements/base/route.js.map +1 -1
  25. package/elements/base/title.d.ts +0 -5
  26. package/elements/base/title.d.ts.map +1 -1
  27. package/elements/base/title.js +0 -5
  28. package/elements/base/title.js.map +1 -1
  29. package/elements/base/unsafe.d.ts +0 -5
  30. package/elements/base/unsafe.d.ts.map +1 -1
  31. package/elements/base/unsafe.js +2 -8
  32. package/elements/base/unsafe.js.map +1 -1
  33. package/elements/base/use.d.ts +0 -3
  34. package/elements/base/use.d.ts.map +1 -1
  35. package/elements/base/use.js +4 -11
  36. package/elements/base/use.js.map +1 -1
  37. package/helper/react-shim.d.ts +15 -0
  38. package/helper/react-shim.d.ts.map +1 -0
  39. package/helper/react-shim.js +56 -0
  40. package/helper/react-shim.js.map +1 -0
  41. package/helper/theme.d.ts +3 -3
  42. package/helper/theme.d.ts.map +1 -1
  43. package/helper/theme.js +3 -3
  44. package/helper/theme.js.map +1 -1
  45. package/index.d.ts +0 -1
  46. package/index.d.ts.map +1 -1
  47. package/index.js +0 -1
  48. package/index.js.map +1 -1
  49. package/lib/decorators.d.ts +7 -6
  50. package/lib/decorators.d.ts.map +1 -1
  51. package/lib/decorators.js +45 -30
  52. package/lib/decorators.js.map +1 -1
  53. package/lib/directive.d.ts +83 -0
  54. package/lib/directive.d.ts.map +1 -0
  55. package/lib/directive.js +59 -0
  56. package/lib/directive.js.map +1 -0
  57. package/lib/element.d.ts +7 -21
  58. package/lib/element.d.ts.map +1 -1
  59. package/lib/element.js +53 -49
  60. package/lib/element.js.map +1 -1
  61. package/lib/lit-html.d.ts +331 -0
  62. package/lib/lit-html.d.ts.map +1 -0
  63. package/lib/lit-html.js +941 -0
  64. package/lib/lit-html.js.map +1 -0
  65. package/lib/ref.d.ts +1 -0
  66. package/lib/ref.d.ts.map +1 -0
  67. package/lib/ref.js +2 -0
  68. package/lib/ref.js.map +1 -0
  69. package/lib/repeat.d.ts +126 -0
  70. package/lib/repeat.d.ts.map +1 -0
  71. package/lib/repeat.js +533 -0
  72. package/lib/repeat.js.map +1 -0
  73. package/lib/store.d.ts +1 -1
  74. package/lib/store.d.ts.map +1 -1
  75. package/lib/store.js +4 -4
  76. package/lib/store.js.map +1 -1
  77. package/lib/utils.d.ts +1 -4
  78. package/lib/utils.d.ts.map +1 -1
  79. package/lib/utils.js +5 -15
  80. package/lib/utils.js.map +1 -1
  81. package/lib/version.d.ts +1 -1
  82. package/lib/version.d.ts.map +1 -1
  83. package/lib/version.js +2 -1
  84. package/lib/version.js.map +1 -1
  85. package/package.json +6 -7
  86. package/src/elements/base/dialog.ts +0 -4
  87. package/src/elements/base/gesture.ts +31 -32
  88. package/src/elements/base/link.ts +12 -32
  89. package/src/elements/base/reflect.ts +0 -3
  90. package/src/elements/base/route.ts +11 -21
  91. package/src/elements/base/title.ts +0 -5
  92. package/src/elements/base/unsafe.ts +3 -8
  93. package/src/elements/base/use.ts +4 -11
  94. package/src/helper/theme.ts +4 -4
  95. package/src/index.ts +0 -1
  96. package/src/lib/decorators.ts +50 -37
  97. package/src/lib/directive.ts +139 -0
  98. package/src/lib/element.ts +62 -72
  99. package/src/lib/lit-html.ts +1217 -0
  100. package/src/lib/repeat.ts +580 -0
  101. package/src/lib/store.ts +4 -4
  102. package/src/lib/utils.ts +5 -16
  103. package/src/lib/version.ts +2 -1
  104. package/src/test/elements/link.test.ts +1 -2
  105. package/src/test/elements/reflect.test.ts +13 -3
  106. package/src/test/elements/title.test.ts +1 -2
  107. package/src/test/gem-element/advance.test.ts +15 -8
  108. package/src/test/gem-element/basic.test.ts +22 -10
  109. package/src/test/gem-element/decorators.test.ts +6 -9
  110. package/src/test/gem-element/multi.test.ts +20 -11
  111. package/src/test/history.test.ts +2 -2
  112. package/src/test/store.test.ts +3 -3
  113. package/src/test/utils.test.ts +9 -10
  114. package/src/test/utils.ts +22 -0
  115. package/src/lib/directives.ts +0 -6
@@ -0,0 +1,580 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2017 Google LLC
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+
7
+ import { noChange, ChildPart } from './lit-html';
8
+ import type { Part, DirectiveParent } from './lit-html';
9
+ import { directive, Directive, PartType } from './directive';
10
+ import type { PartInfo } from './directive';
11
+
12
+ export type KeyFn<T> = (item: T, index: number) => unknown;
13
+ export type ItemTemplate<T> = (item: T, index: number) => unknown;
14
+
15
+ const createMarker = () => document.createComment('');
16
+
17
+ /**
18
+ * Inserts a ChildPart into the given container ChildPart's DOM, either at the
19
+ * end of the container ChildPart, or before the optional `refPart`.
20
+ *
21
+ * This does not add the part to the containerPart's committed value. That must
22
+ * be done by callers.
23
+ *
24
+ * @param containerPart Part within which to add the new ChildPart
25
+ * @param refPart Part before which to add the new ChildPart; when omitted the
26
+ * part added to the end of the `containerPart`
27
+ * @param part Part to insert, or undefined to create a new part
28
+ */
29
+ export const insertPart = (containerPart: ChildPart, refPart?: ChildPart, part?: ChildPart): ChildPart => {
30
+ const container = containerPart._$startNode.parentNode!;
31
+
32
+ const refNode = refPart === undefined ? containerPart._$endNode : refPart._$startNode;
33
+
34
+ if (part === undefined) {
35
+ const startNode = container.insertBefore(createMarker(), refNode);
36
+ const endNode = container.insertBefore(createMarker(), refNode);
37
+ part = new ChildPart(startNode, endNode, containerPart, containerPart.options);
38
+ } else {
39
+ const endNode = part._$endNode!.nextSibling;
40
+ const oldParent = part._$parent;
41
+ const parentChanged = oldParent !== containerPart;
42
+ if (parentChanged) {
43
+ part._$reparentDisconnectables?.(containerPart);
44
+ // Note that although `_$reparentDisconnectables` updates the part's
45
+ // `_$parent` reference after unlinking from its current parent, that
46
+ // method only exists if Disconnectables are present, so we need to
47
+ // unconditionally set it here
48
+ part._$parent = containerPart;
49
+ // Since the _$isConnected getter is somewhat costly, only
50
+ // read it once we know the subtree has directives that need
51
+ // to be notified
52
+ let newConnectionState;
53
+ if (
54
+ part._$notifyConnectionChanged !== undefined &&
55
+ (newConnectionState = containerPart._$isConnected) !== oldParent!._$isConnected
56
+ ) {
57
+ part._$notifyConnectionChanged(newConnectionState);
58
+ }
59
+ }
60
+ if (endNode !== refNode || parentChanged) {
61
+ let start: Node | null = part._$startNode;
62
+ while (start !== endNode) {
63
+ const n: Node | null = start!.nextSibling;
64
+ container.insertBefore(start!, refNode);
65
+ start = n;
66
+ }
67
+ }
68
+ }
69
+
70
+ return part;
71
+ };
72
+
73
+ /**
74
+ * Sets the value of a Part.
75
+ *
76
+ * Note that this should only be used to set/update the value of user-created
77
+ * parts (i.e. those created using `insertPart`); it should not be used
78
+ * by directives to set the value of the directive's container part. Directives
79
+ * should return a value from `update`/`render` to update their part state.
80
+ *
81
+ * For directives that require setting their part value asynchronously, they
82
+ * should extend `AsyncDirective` and call `this.setValue()`.
83
+ *
84
+ * @param part Part to set
85
+ * @param value Value to set
86
+ * @param index For `AttributePart`s, the index to set
87
+ * @param directiveParent Used internally; should not be set by user
88
+ */
89
+ export const setChildPartValue = <T extends ChildPart>(
90
+ part: T,
91
+ value: unknown,
92
+ directiveParent: DirectiveParent = part,
93
+ ): T => {
94
+ part._$setValue(value, directiveParent);
95
+ return part;
96
+ };
97
+
98
+ // A sentinel value that can never appear as a part value except when set by
99
+ // live(). Used to force a dirty-check to fail and cause a re-render.
100
+ const RESET_VALUE = {};
101
+
102
+ /**
103
+ * Sets the committed value of a ChildPart directly without triggering the
104
+ * commit stage of the part.
105
+ *
106
+ * This is useful in cases where a directive needs to update the part such
107
+ * that the next update detects a value change or not. When value is omitted,
108
+ * the next update will be guaranteed to be detected as a change.
109
+ *
110
+ * @param part
111
+ * @param value
112
+ */
113
+ export const setCommittedValue = (part: Part, value: unknown = RESET_VALUE) => (part._$committedValue = value);
114
+
115
+ /**
116
+ * Returns the committed value of a ChildPart.
117
+ *
118
+ * The committed value is used for change detection and efficient updates of
119
+ * the part. It can differ from the value set by the template or directive in
120
+ * cases where the template value is transformed before being committed.
121
+ *
122
+ * - `TemplateResult`s are committed as a `TemplateInstance`
123
+ * - Iterables are committed as `Array<ChildPart>`
124
+ * - All other types are committed as the template value or value returned or
125
+ * set by a directive.
126
+ *
127
+ * @param part
128
+ */
129
+ export const getCommittedValue = (part: ChildPart) => part._$committedValue;
130
+
131
+ /**
132
+ * Removes a ChildPart from the DOM, including any of its content.
133
+ *
134
+ * @param part The Part to remove
135
+ */
136
+ export const removePart = (part: ChildPart) => {
137
+ part._$notifyConnectionChanged?.(false, true);
138
+ let start: ChildNode | null = part._$startNode;
139
+ const end: ChildNode | null = part._$endNode!.nextSibling;
140
+ while (start !== end) {
141
+ const n: ChildNode | null = start!.nextSibling;
142
+ (start! as ChildNode).remove();
143
+ start = n;
144
+ }
145
+ };
146
+
147
+ export const clearPart = (part: ChildPart) => {
148
+ part._$clear();
149
+ };
150
+
151
+ // Helper for generating a map of array item to its index over a subset
152
+ // of an array (used to lazily generate `newKeyToIndexMap` and
153
+ // `oldKeyToIndexMap`)
154
+ const generateMap = (list: unknown[], start: number, end: number) => {
155
+ const map = new Map<unknown, number>();
156
+ for (let i = start; i <= end; i++) {
157
+ map.set(list[i], i);
158
+ }
159
+ return map;
160
+ };
161
+
162
+ class RepeatDirective extends Directive {
163
+ private _itemKeys?: unknown[];
164
+
165
+ constructor(partInfo: PartInfo) {
166
+ super(partInfo);
167
+ if (partInfo.type !== PartType.CHILD) {
168
+ throw new Error('repeat() can only be used in text expressions');
169
+ }
170
+ }
171
+
172
+ private _getValuesAndKeys<T>(
173
+ items: Iterable<T>,
174
+ keyFnOrTemplate: KeyFn<T> | ItemTemplate<T>,
175
+ template?: ItemTemplate<T>,
176
+ ) {
177
+ let keyFn: KeyFn<T> | undefined;
178
+ if (template === undefined) {
179
+ template = keyFnOrTemplate;
180
+ } else if (keyFnOrTemplate !== undefined) {
181
+ keyFn = keyFnOrTemplate as KeyFn<T>;
182
+ }
183
+ const keys = [];
184
+ const values = [];
185
+ let index = 0;
186
+ for (const item of items) {
187
+ keys[index] = keyFn ? keyFn(item, index) : index;
188
+ values[index] = template!(item, index);
189
+ index++;
190
+ }
191
+ return {
192
+ values,
193
+ keys,
194
+ };
195
+ }
196
+
197
+ render<T>(items: Iterable<T>, template: ItemTemplate<T>): Array<unknown>;
198
+ render<T>(items: Iterable<T>, keyFn: KeyFn<T> | ItemTemplate<T>, template: ItemTemplate<T>): Array<unknown>;
199
+ render<T>(items: Iterable<T>, keyFnOrTemplate: KeyFn<T> | ItemTemplate<T>, template?: ItemTemplate<T>) {
200
+ return this._getValuesAndKeys(items, keyFnOrTemplate, template).values;
201
+ }
202
+
203
+ override update<T>(
204
+ containerPart: ChildPart,
205
+ [items, keyFnOrTemplate, template]: [Iterable<T>, KeyFn<T> | ItemTemplate<T>, ItemTemplate<T>],
206
+ ) {
207
+ // Old part & key lists are retrieved from the last update (which may
208
+ // be primed by hydration)
209
+ const oldParts = getCommittedValue(containerPart) as Array<ChildPart | null>;
210
+ const { values: newValues, keys: newKeys } = this._getValuesAndKeys(items, keyFnOrTemplate, template);
211
+
212
+ // We check that oldParts, the committed value, is an Array as an
213
+ // indicator that the previous value came from a repeat() call. If
214
+ // oldParts is not an Array then this is the first render and we return
215
+ // an array for lit-html's array handling to render, and remember the
216
+ // keys.
217
+ if (!Array.isArray(oldParts)) {
218
+ this._itemKeys = newKeys;
219
+ return newValues;
220
+ }
221
+
222
+ // In SSR hydration it's possible for oldParts to be an array but for us
223
+ // to not have item keys because the update() hasn't run yet. We set the
224
+ // keys to an empty array. This will cause all oldKey/newKey comparisons
225
+ // to fail and execution to fall to the last nested brach below which
226
+ // reuses the oldPart.
227
+ const oldKeys = (this._itemKeys ??= []);
228
+
229
+ // New part list will be built up as we go (either reused from
230
+ // old parts or created for new keys in this update). This is
231
+ // saved in the above cache at the end of the update.
232
+ const newParts: ChildPart[] = [];
233
+
234
+ // Maps from key to index for current and previous update; these
235
+ // are generated lazily only when needed as a performance
236
+ // optimization, since they are only required for multiple
237
+ // non-contiguous changes in the list, which are less common.
238
+ let newKeyToIndexMap!: Map<unknown, number>;
239
+ let oldKeyToIndexMap!: Map<unknown, number>;
240
+
241
+ // Head and tail pointers to old parts and new values
242
+ let oldHead = 0;
243
+ let oldTail = oldParts.length - 1;
244
+ let newHead = 0;
245
+ let newTail = newValues.length - 1;
246
+
247
+ // Overview of O(n) reconciliation algorithm (general approach
248
+ // based on ideas found in ivi, vue, snabbdom, etc.):
249
+ //
250
+ // * We start with the list of old parts and new values (and
251
+ // arrays of their respective keys), head/tail pointers into
252
+ // each, and we build up the new list of parts by updating
253
+ // (and when needed, moving) old parts or creating new ones.
254
+ // The initial scenario might look like this (for brevity of
255
+ // the diagrams, the numbers in the array reflect keys
256
+ // associated with the old parts or new values, although keys
257
+ // and parts/values are actually stored in parallel arrays
258
+ // indexed using the same head/tail pointers):
259
+ //
260
+ // oldHead v v oldTail
261
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
262
+ // newParts: [ , , , , , , ]
263
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
264
+ // item order
265
+ // newHead ^ ^ newTail
266
+ //
267
+ // * Iterate old & new lists from both sides, updating,
268
+ // swapping, or removing parts at the head/tail locations
269
+ // until neither head nor tail can move.
270
+ //
271
+ // * Example below: keys at head pointers match, so update old
272
+ // part 0 in-place (no need to move it) and record part 0 in
273
+ // the `newParts` list. The last thing we do is advance the
274
+ // `oldHead` and `newHead` pointers (will be reflected in the
275
+ // next diagram).
276
+ //
277
+ // oldHead v v oldTail
278
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
279
+ // newParts: [0, , , , , , ] <- heads matched: update 0
280
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
281
+ // & newHead
282
+ // newHead ^ ^ newTail
283
+ //
284
+ // * Example below: head pointers don't match, but tail
285
+ // pointers do, so update part 6 in place (no need to move
286
+ // it), and record part 6 in the `newParts` list. Last,
287
+ // advance the `oldTail` and `oldHead` pointers.
288
+ //
289
+ // oldHead v v oldTail
290
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
291
+ // newParts: [0, , , , , , 6] <- tails matched: update 6
292
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldTail
293
+ // & newTail
294
+ // newHead ^ ^ newTail
295
+ //
296
+ // * If neither head nor tail match; next check if one of the
297
+ // old head/tail items was removed. We first need to generate
298
+ // the reverse map of new keys to index (`newKeyToIndexMap`),
299
+ // which is done once lazily as a performance optimization,
300
+ // since we only hit this case if multiple non-contiguous
301
+ // changes were made. Note that for contiguous removal
302
+ // anywhere in the list, the head and tails would advance
303
+ // from either end and pass each other before we get to this
304
+ // case and removals would be handled in the final while loop
305
+ // without needing to generate the map.
306
+ //
307
+ // * Example below: The key at `oldTail` was removed (no longer
308
+ // in the `newKeyToIndexMap`), so remove that part from the
309
+ // DOM and advance just the `oldTail` pointer.
310
+ //
311
+ // oldHead v v oldTail
312
+ // oldKeys: [0, 1, 2, 3, 4, 5, 6]
313
+ // newParts: [0, , , , , , 6] <- 5 not in new map: remove
314
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] 5 and advance oldTail
315
+ // newHead ^ ^ newTail
316
+ //
317
+ // * Once head and tail cannot move, any mismatches are due to
318
+ // either new or moved items; if a new key is in the previous
319
+ // "old key to old index" map, move the old part to the new
320
+ // location, otherwise create and insert a new part. Note
321
+ // that when moving an old part we null its position in the
322
+ // oldParts array if it lies between the head and tail so we
323
+ // know to skip it when the pointers get there.
324
+ //
325
+ // * Example below: neither head nor tail match, and neither
326
+ // were removed; so find the `newHead` key in the
327
+ // `oldKeyToIndexMap`, and move that old part's DOM into the
328
+ // next head position (before `oldParts[oldHead]`). Last,
329
+ // null the part in the `oldPart` array since it was
330
+ // somewhere in the remaining oldParts still to be scanned
331
+ // (between the head and tail pointers) so that we know to
332
+ // skip that old part on future iterations.
333
+ //
334
+ // oldHead v v oldTail
335
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
336
+ // newParts: [0, 2, , , , , 6] <- stuck: update & move 2
337
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] into place and advance
338
+ // newHead
339
+ // newHead ^ ^ newTail
340
+ //
341
+ // * Note that for moves/insertions like the one above, a part
342
+ // inserted at the head pointer is inserted before the
343
+ // current `oldParts[oldHead]`, and a part inserted at the
344
+ // tail pointer is inserted before `newParts[newTail+1]`. The
345
+ // seeming asymmetry lies in the fact that new parts are
346
+ // moved into place outside in, so to the right of the head
347
+ // pointer are old parts, and to the right of the tail
348
+ // pointer are new parts.
349
+ //
350
+ // * We always restart back from the top of the algorithm,
351
+ // allowing matching and simple updates in place to
352
+ // continue...
353
+ //
354
+ // * Example below: the head pointers once again match, so
355
+ // simply update part 1 and record it in the `newParts`
356
+ // array. Last, advance both head pointers.
357
+ //
358
+ // oldHead v v oldTail
359
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
360
+ // newParts: [0, 2, 1, , , , 6] <- heads matched: update 1
361
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
362
+ // & newHead
363
+ // newHead ^ ^ newTail
364
+ //
365
+ // * As mentioned above, items that were moved as a result of
366
+ // being stuck (the final else clause in the code below) are
367
+ // marked with null, so we always advance old pointers over
368
+ // these so we're comparing the next actual old value on
369
+ // either end.
370
+ //
371
+ // * Example below: `oldHead` is null (already placed in
372
+ // newParts), so advance `oldHead`.
373
+ //
374
+ // oldHead v v oldTail
375
+ // oldKeys: [0, 1, -, 3, 4, 5, 6] <- old head already used:
376
+ // newParts: [0, 2, 1, , , , 6] advance oldHead
377
+ // newKeys: [0, 2, 1, 4, 3, 7, 6]
378
+ // newHead ^ ^ newTail
379
+ //
380
+ // * Note it's not critical to mark old parts as null when they
381
+ // are moved from head to tail or tail to head, since they
382
+ // will be outside the pointer range and never visited again.
383
+ //
384
+ // * Example below: Here the old tail key matches the new head
385
+ // key, so the part at the `oldTail` position and move its
386
+ // DOM to the new head position (before `oldParts[oldHead]`).
387
+ // Last, advance `oldTail` and `newHead` pointers.
388
+ //
389
+ // oldHead v v oldTail
390
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
391
+ // newParts: [0, 2, 1, 4, , , 6] <- old tail matches new
392
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] head: update & move 4,
393
+ // advance oldTail & newHead
394
+ // newHead ^ ^ newTail
395
+ //
396
+ // * Example below: Old and new head keys match, so update the
397
+ // old head part in place, and advance the `oldHead` and
398
+ // `newHead` pointers.
399
+ //
400
+ // oldHead v oldTail
401
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
402
+ // newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3
403
+ // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance oldHead &
404
+ // newHead
405
+ // newHead ^ ^ newTail
406
+ //
407
+ // * Once the new or old pointers move past each other then all
408
+ // we have left is additions (if old list exhausted) or
409
+ // removals (if new list exhausted). Those are handled in the
410
+ // final while loops at the end.
411
+ //
412
+ // * Example below: `oldHead` exceeded `oldTail`, so we're done
413
+ // with the main loop. Create the remaining part and insert
414
+ // it at the new head position, and the update is complete.
415
+ //
416
+ // (oldHead > oldTail)
417
+ // oldKeys: [0, 1, -, 3, 4, 5, 6]
418
+ // newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
419
+ // newKeys: [0, 2, 1, 4, 3, 7, 6]
420
+ // newHead ^ newTail
421
+ //
422
+ // * Note that the order of the if/else clauses is not
423
+ // important to the algorithm, as long as the null checks
424
+ // come first (to ensure we're always working on valid old
425
+ // parts) and that the final else clause comes last (since
426
+ // that's where the expensive moves occur). The order of
427
+ // remaining clauses is just a simple guess at which cases
428
+ // will be most common.
429
+ //
430
+ // * Note, we could calculate the longest
431
+ // increasing subsequence (LIS) of old items in new position,
432
+ // and only move those not in the LIS set. However that costs
433
+ // O(nlogn) time and adds a bit more code, and only helps
434
+ // make rare types of mutations require fewer moves. The
435
+ // above handles removes, adds, reversal, swaps, and single
436
+ // moves of contiguous items in linear time, in the minimum
437
+ // number of moves. As the number of multiple moves where LIS
438
+ // might help approaches a random shuffle, the LIS
439
+ // optimization becomes less helpful, so it seems not worth
440
+ // the code at this point. Could reconsider if a compelling
441
+ // case arises.
442
+
443
+ while (oldHead <= oldTail && newHead <= newTail) {
444
+ if (oldParts[oldHead] === null) {
445
+ // `null` means old part at head has already been used
446
+ // below; skip
447
+ oldHead++;
448
+ } else if (oldParts[oldTail] === null) {
449
+ // `null` means old part at tail has already been used
450
+ // below; skip
451
+ oldTail--;
452
+ } else if (oldKeys[oldHead] === newKeys[newHead]) {
453
+ // Old head matches new head; update in place
454
+ newParts[newHead] = setChildPartValue(oldParts[oldHead]!, newValues[newHead]);
455
+ oldHead++;
456
+ newHead++;
457
+ } else if (oldKeys[oldTail] === newKeys[newTail]) {
458
+ // Old tail matches new tail; update in place
459
+ newParts[newTail] = setChildPartValue(oldParts[oldTail]!, newValues[newTail]);
460
+ oldTail--;
461
+ newTail--;
462
+ } else if (oldKeys[oldHead] === newKeys[newTail]) {
463
+ // Old head matches new tail; update and move to new tail
464
+ newParts[newTail] = setChildPartValue(oldParts[oldHead]!, newValues[newTail]);
465
+ insertPart(containerPart, newParts[newTail + 1], oldParts[oldHead]!);
466
+ oldHead++;
467
+ newTail--;
468
+ } else if (oldKeys[oldTail] === newKeys[newHead]) {
469
+ // Old tail matches new head; update and move to new head
470
+ newParts[newHead] = setChildPartValue(oldParts[oldTail]!, newValues[newHead]);
471
+ insertPart(containerPart, oldParts[oldHead]!, oldParts[oldTail]!);
472
+ oldTail--;
473
+ newHead++;
474
+ } else {
475
+ if (newKeyToIndexMap === undefined) {
476
+ // Lazily generate key-to-index maps, used for removals &
477
+ // moves below
478
+ newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
479
+ oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
480
+ }
481
+ if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
482
+ // Old head is no longer in new list; remove
483
+ removePart(oldParts[oldHead]!);
484
+ oldHead++;
485
+ } else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
486
+ // Old tail is no longer in new list; remove
487
+ removePart(oldParts[oldTail]!);
488
+ oldTail--;
489
+ } else {
490
+ // Any mismatches at this point are due to additions or
491
+ // moves; see if we have an old part we can reuse and move
492
+ // into place
493
+ const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
494
+ const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
495
+ if (oldPart === null) {
496
+ // No old part for this value; create a new one and
497
+ // insert it
498
+ const newPart = insertPart(containerPart, oldParts[oldHead]!);
499
+ setChildPartValue(newPart, newValues[newHead]);
500
+ newParts[newHead] = newPart;
501
+ } else {
502
+ // Reuse old part
503
+ newParts[newHead] = setChildPartValue(oldPart, newValues[newHead]);
504
+ insertPart(containerPart, oldParts[oldHead]!, oldPart);
505
+ // This marks the old part as having been used, so that
506
+ // it will be skipped in the first two checks above
507
+ oldParts[oldIndex as number] = null;
508
+ }
509
+ newHead++;
510
+ }
511
+ }
512
+ }
513
+ // Add parts for any remaining new values
514
+ while (newHead <= newTail) {
515
+ // For all remaining additions, we insert before last new
516
+ // tail, since old pointers are no longer valid
517
+ const newPart = insertPart(containerPart, newParts[newTail + 1]);
518
+ setChildPartValue(newPart, newValues[newHead]);
519
+ newParts[newHead++] = newPart;
520
+ }
521
+ // Remove any remaining unused old parts
522
+ while (oldHead <= oldTail) {
523
+ const oldPart = oldParts[oldHead++];
524
+ if (oldPart !== null) {
525
+ removePart(oldPart);
526
+ }
527
+ }
528
+
529
+ // Save order of new parts for next round
530
+ this._itemKeys = newKeys;
531
+ // Directly set part value, bypassing it's dirty-checking
532
+ setCommittedValue(containerPart, newParts);
533
+ return noChange;
534
+ }
535
+ }
536
+
537
+ export interface RepeatDirectiveFn {
538
+ <T>(items: Iterable<T>, keyFnOrTemplate: KeyFn<T> | ItemTemplate<T>, template?: ItemTemplate<T>): unknown;
539
+ <T>(items: Iterable<T>, template: ItemTemplate<T>): unknown;
540
+ <T>(items: Iterable<T>, keyFn: KeyFn<T> | ItemTemplate<T>, template: ItemTemplate<T>): unknown;
541
+ }
542
+
543
+ /**
544
+ * A directive that repeats a series of values (usually `TemplateResults`)
545
+ * generated from an iterable, and updates those items efficiently when the
546
+ * iterable changes based on user-provided `keys` associated with each item.
547
+ *
548
+ * Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
549
+ * meaning previous DOM for a given key is moved into the new position if
550
+ * needed, and DOM will never be reused with values for different keys (new DOM
551
+ * will always be created for new keys). This is generally the most efficient
552
+ * way to use `repeat` since it performs minimum unnecessary work for insertions
553
+ * and removals.
554
+ *
555
+ * The `keyFn` takes two parameters, the item and its index, and returns a unique key value.
556
+ *
557
+ * ```js
558
+ * html`
559
+ * <ol>
560
+ * ${repeat(this.items, (item) => item.id, (item, index) => {
561
+ * return html`<li>${index}: ${item.name}</li>`;
562
+ * })}
563
+ * </ol>
564
+ * `
565
+ * ```
566
+ *
567
+ * **Important**: If providing a `keyFn`, keys *must* be unique for all items in a
568
+ * given call to `repeat`. The behavior when two or more items have the same key
569
+ * is undefined.
570
+ *
571
+ * If no `keyFn` is provided, this directive will perform similar to mapping
572
+ * items to values, and DOM will be reused against potentially different items.
573
+ */
574
+ export const repeat = directive(RepeatDirective) as RepeatDirectiveFn;
575
+
576
+ /**
577
+ * The type of the class that powers this directive. Necessary for naming the
578
+ * directive's return type.
579
+ */
580
+ export type { RepeatDirective };
package/src/lib/store.ts CHANGED
@@ -2,19 +2,19 @@ import { addMicrotask, createUpdater } from './utils';
2
2
 
3
3
  export type Store<T = any> = ReturnType<typeof createUpdater<T>>;
4
4
 
5
- export const StoreListenerMap = new WeakMap<Store<any>, Set<() => void>>();
5
+ export const _StoreListenerMap = new WeakMap<Store<any>, Set<() => void>>();
6
6
 
7
7
  export function createStore<T = any>(originStore: T) {
8
8
  const store = createUpdater(originStore, (value?: Partial<T>) => {
9
9
  Object.assign(store, value);
10
- StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
10
+ _StoreListenerMap.get(store)?.forEach((func) => addMicrotask(func));
11
11
  });
12
- StoreListenerMap.set(store, new Set<() => void>());
12
+ _StoreListenerMap.set(store, new Set<() => void>());
13
13
  return store;
14
14
  }
15
15
 
16
16
  export function connect(store: Store<any>, func: () => void) {
17
- const listeners = StoreListenerMap.get(store);
17
+ const listeners = _StoreListenerMap.get(store);
18
18
  listeners?.add(func);
19
19
  return () => listeners?.delete(func);
20
20
  }
package/src/lib/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- const { assign, setPrototypeOf, fromEntries, entries, keys } = Object;
1
+ const { assign, fromEntries, entries, keys } = Object;
2
2
 
3
3
  const microtaskSet = new Set<() => void>();
4
4
  export function addMicrotask(func: () => void) {
@@ -13,7 +13,8 @@ export function addMicrotask(func: () => void) {
13
13
  // 注意 typeof state === 'function' 但是没有 Function 的方法和属性
14
14
  export function createUpdater<T, Fn = (payload?: Partial<T>) => any>(initState: T, fn: Fn) {
15
15
  const state: any = fn;
16
- setPrototypeOf(state, null);
16
+ // https://github.com/vitejs/vite/issues/18540 现在覆盖 call 也一样会报错
17
+ // setPrototypeOf(state, null);
17
18
  delete state.name;
18
19
  delete state.length;
19
20
  assign(state, initState);
@@ -223,8 +224,8 @@ export function raw(arr: TemplateStringsArray, ...args: any[]) {
223
224
  return arr.reduce((prev, current, index) => prev + (args[index - 1] ?? '') + current);
224
225
  }
225
226
 
226
- // 写 css 文本,在 CSSStyleSheet 中使用
227
- export const css = raw;
227
+ // 在 html 中引用 class 时使用,仅提供高亮功能
228
+ export const styled = raw;
228
229
 
229
230
  export function randomStr(len = 5): string {
230
231
  const str = Math.random()
@@ -234,18 +235,6 @@ export function randomStr(len = 5): string {
234
235
  return str;
235
236
  }
236
237
 
237
- // 写 css 文本,在 CSSStyleSheet 中使用,使用 styed-components 高亮
238
- //
239
- // createCSSSheet({
240
- // red: styled.class`
241
- // background: red;
242
- // &:hover {
243
- // background: blue;
244
- // }
245
- // `,
246
- // });
247
- export const styled = { class: raw };
248
-
249
238
  export function camelToKebabCase(str: string) {
250
239
  return str.replace(/[A-Z]/g, ($1: string) => '-' + $1.toLowerCase());
251
240
  }
@@ -1 +1,2 @@
1
- export const version = '2.1.1';
1
+ // Do not modify manually
2
+ export const version = '0.0.0';
@@ -1,5 +1,4 @@
1
- import { fixture, expect } from '@open-wc/testing';
2
-
1
+ import { fixture, expect } from '../utils';
3
2
  import { html } from '../../lib/element';
4
3
  import type { GemActiveLinkElement } from '../../elements/link';
5
4