subay 0.0.13 → 0.1.1
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.
- package/README.md +14 -18
- package/README.zh.md +677 -682
- package/examples/todo-app.html +5 -20
- package/lib/arr.d.ts +2 -0
- package/lib/arr.d.ts.map +1 -0
- package/lib/arr.js +122 -0
- package/lib/arr.js.map +1 -0
- package/lib/h.d.ts.map +1 -1
- package/lib/h.js +136 -318
- package/lib/h.js.map +1 -1
- package/package.json +1 -1
- package/skills/subay-application/SKILL.md +122 -0
- package/skills/subay-component/SKILL.md +53 -0
- package/skills/subay-expert/SKILL.md +37 -0
- package/skills/subay-state/SKILL.md +162 -0
- package/skills/subay-template/SKILL.md +39 -0
- package/src/arr.ts +428 -0
- package/src/h.ts +144 -365
package/src/arr.ts
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// fully adopt from https://github.com/localvoid/ivi/blob/fe3b159fac6cddb7521dccc47d0a041b60d0947e/packages/ivi/src/lib/core.ts#L1210
|
|
2
|
+
|
|
3
|
+
const enum MagicValues {
|
|
4
|
+
/**
|
|
5
|
+
* One of the children nodes were moved.
|
|
6
|
+
*/
|
|
7
|
+
RearrangeNodes = 1073741823, // Max SMI Value
|
|
8
|
+
/**
|
|
9
|
+
* New node marker.
|
|
10
|
+
*/
|
|
11
|
+
NewNodeMark = -1,
|
|
12
|
+
/**
|
|
13
|
+
* LIS marker.
|
|
14
|
+
*/
|
|
15
|
+
LISMark = -2,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Update children list with track by key algorithm.
|
|
20
|
+
*
|
|
21
|
+
* High-level overview of the algorithm that is implemented in this function:
|
|
22
|
+
*
|
|
23
|
+
* This algorithm finds a minimum number of DOM operations. It works in
|
|
24
|
+
* several steps:
|
|
25
|
+
*
|
|
26
|
+
* 1. Common prefix and suffix optimization.
|
|
27
|
+
*
|
|
28
|
+
* Look for nodes with identical keys by simultaneously iterating through nodes
|
|
29
|
+
* in the old children list `A` and new children list `B` from both sides.
|
|
30
|
+
*
|
|
31
|
+
* A: -> [a b c d] <-
|
|
32
|
+
* B: -> [a b d] <-
|
|
33
|
+
*
|
|
34
|
+
* Skip nodes "a" and "b" at the start, and node "d" at the end.
|
|
35
|
+
*
|
|
36
|
+
* A: -> [c] <-
|
|
37
|
+
* B: -> [] <-
|
|
38
|
+
*
|
|
39
|
+
* 2. Zero length optimizations.
|
|
40
|
+
*
|
|
41
|
+
* Check if the size of one of the list is equal to zero. When length of the
|
|
42
|
+
* old children list is zero, insert remaining nodes from the new list. When
|
|
43
|
+
* length of the new children list is zero, remove remaining nodes from the old
|
|
44
|
+
* list.
|
|
45
|
+
*
|
|
46
|
+
* A: -> [a b c g] <-
|
|
47
|
+
* B: -> [a g] <-
|
|
48
|
+
*
|
|
49
|
+
* Skip nodes "a" and "g" (prefix and suffix optimization).
|
|
50
|
+
*
|
|
51
|
+
* A: [b c]
|
|
52
|
+
* B: []
|
|
53
|
+
*
|
|
54
|
+
* Remove nodes "b" and "c".
|
|
55
|
+
*
|
|
56
|
+
* 3. Index and unmount removed nodes.
|
|
57
|
+
*
|
|
58
|
+
* A: [b c d e f]
|
|
59
|
+
* B: [c b h f e]
|
|
60
|
+
* P: [. . . . .] // . == -1
|
|
61
|
+
*
|
|
62
|
+
* Create array `P` (`sources`) with the length of the new children list and
|
|
63
|
+
* fills it with `NewNodeMark` values. This mark indicates that node at this
|
|
64
|
+
* position should be mounted. Later we will assign node positions in the old
|
|
65
|
+
* children list to this array.
|
|
66
|
+
*
|
|
67
|
+
* A: [b c d e f]
|
|
68
|
+
* B: [c b h f e]
|
|
69
|
+
* P: [. . . . .] // . == -1
|
|
70
|
+
* I: {
|
|
71
|
+
* c: 0, // B[0] == c
|
|
72
|
+
* b: 1, // B[1] == b
|
|
73
|
+
* h: 2,
|
|
74
|
+
* f: 3,
|
|
75
|
+
* e: 4,
|
|
76
|
+
* }
|
|
77
|
+
* last = 0
|
|
78
|
+
*
|
|
79
|
+
* Create reverse index `I` that maps keys to node positions in the new
|
|
80
|
+
* children list.
|
|
81
|
+
*
|
|
82
|
+
* A: [b c d e f]
|
|
83
|
+
* ^
|
|
84
|
+
* B: [c b h f e]
|
|
85
|
+
* P: [. 0 . . .] // . == -1
|
|
86
|
+
* I: {
|
|
87
|
+
* c: 0,
|
|
88
|
+
* b: 1, <-
|
|
89
|
+
* h: 2,
|
|
90
|
+
* f: 3,
|
|
91
|
+
* e: 4,
|
|
92
|
+
* }
|
|
93
|
+
* last = 1
|
|
94
|
+
*
|
|
95
|
+
* Assign original positions of the nodes from the old children list to the
|
|
96
|
+
* array `P`.
|
|
97
|
+
*
|
|
98
|
+
* Iterate through nodes in the old children list and gets their new positions
|
|
99
|
+
* from the index `I`. Assign old node position to the array `P`. When index
|
|
100
|
+
* `I` doesn't have a key for the old node, it means that it should be
|
|
101
|
+
* unmounted.
|
|
102
|
+
*
|
|
103
|
+
* When we assigning positions to the array `P`, we also store position of the
|
|
104
|
+
* last seen node in the new children list `pos`, if the last seen position is
|
|
105
|
+
* greater than the current position of the node at the new list, then we are
|
|
106
|
+
* switching `rearrangeNodes` flag to `true` (`pos === RearrangeNodes`).
|
|
107
|
+
*
|
|
108
|
+
* A: [b c d e f]
|
|
109
|
+
* ^
|
|
110
|
+
* B: [c b h f e]
|
|
111
|
+
* P: [1 0 . . .] // . == -1
|
|
112
|
+
* I: {
|
|
113
|
+
* c: 0, <-
|
|
114
|
+
* b: 1,
|
|
115
|
+
* h: 2,
|
|
116
|
+
* f: 3,
|
|
117
|
+
* e: 4,
|
|
118
|
+
* }
|
|
119
|
+
* last = 1 // last > 0; rearrangeNodes = true
|
|
120
|
+
*
|
|
121
|
+
* The last position `1` is greater than the current position of the node at the
|
|
122
|
+
* new list `0`, switch `rearrangeNodes` flag to `true`.
|
|
123
|
+
*
|
|
124
|
+
* A: [b c d e f]
|
|
125
|
+
* ^
|
|
126
|
+
* B: [c b h f e]
|
|
127
|
+
* P: [1 0 . . .] // . == -1
|
|
128
|
+
* I: {
|
|
129
|
+
* c: 0,
|
|
130
|
+
* b: 1,
|
|
131
|
+
* h: 2,
|
|
132
|
+
* f: 3,
|
|
133
|
+
* e: 4,
|
|
134
|
+
* }
|
|
135
|
+
* rearrangeNodes = true
|
|
136
|
+
*
|
|
137
|
+
* Node with key "d" doesn't exist in the index `I`, unmounts node `d`.
|
|
138
|
+
*
|
|
139
|
+
* A: [b c d e f]
|
|
140
|
+
* ^
|
|
141
|
+
* B: [c b h f e]
|
|
142
|
+
* P: [1 0 . . 3] // . == -1
|
|
143
|
+
* I: {
|
|
144
|
+
* c: 0,
|
|
145
|
+
* b: 1,
|
|
146
|
+
* h: 2,
|
|
147
|
+
* f: 3,
|
|
148
|
+
* e: 4, <-
|
|
149
|
+
* }
|
|
150
|
+
* rearrangeNodes = true
|
|
151
|
+
*
|
|
152
|
+
* Assign position `3` for `e` node.
|
|
153
|
+
*
|
|
154
|
+
* A: [b c d e f]
|
|
155
|
+
* ^
|
|
156
|
+
* B: [c b h f e]
|
|
157
|
+
* P: [1 0 . 4 3] // . == -1
|
|
158
|
+
* I: {
|
|
159
|
+
* c: 0,
|
|
160
|
+
* b: 1,
|
|
161
|
+
* h: 2,
|
|
162
|
+
* f: 3, <-
|
|
163
|
+
* e: 4,
|
|
164
|
+
* }
|
|
165
|
+
* rearrangeNodes = true
|
|
166
|
+
*
|
|
167
|
+
* Assign position `4` for 'f' node.
|
|
168
|
+
*
|
|
169
|
+
* 4. Find minimum number of moves when `rearrangeNodes` flag is on and mount
|
|
170
|
+
* new nodes.
|
|
171
|
+
*
|
|
172
|
+
* A: [b c d e f]
|
|
173
|
+
* B: [c b h f e]
|
|
174
|
+
* P: [1 * . 4 *] // . == -1 * == -2
|
|
175
|
+
*
|
|
176
|
+
* When `rearrangeNodes` is on, mark all nodes in the array `P` that belong to
|
|
177
|
+
* the [longest increasing subsequence](http://en.wikipedia.org/wiki/Longest_increasing_subsequence)
|
|
178
|
+
* and move all nodes that doesn't belong to this subsequence.
|
|
179
|
+
*
|
|
180
|
+
* Iterate over the new children list and the `P` array simultaneously. When
|
|
181
|
+
* value from `P` array is equal to `NewNodeMark`, mount a new node. When it
|
|
182
|
+
* isn't equal to `LisMark`, move it to a new position.
|
|
183
|
+
*
|
|
184
|
+
* A: [b c d e f]
|
|
185
|
+
* B: [c b h f e]
|
|
186
|
+
* ^ // new_pos == 4
|
|
187
|
+
* P: [1 * . 4 *] // . == NewNodeMark * == LisMark
|
|
188
|
+
* ^
|
|
189
|
+
*
|
|
190
|
+
* Node "e" has `LisMark` value in the array `P`, nothing changes.
|
|
191
|
+
*
|
|
192
|
+
* A: [b c d e f]
|
|
193
|
+
* B: [c b h f e]
|
|
194
|
+
* ^ // new_pos == 3
|
|
195
|
+
* P: [1 * . 4 *] // . == NewNodeMark * == LisMark
|
|
196
|
+
* ^
|
|
197
|
+
*
|
|
198
|
+
* Node "f" has `4` value in the array `P`, move it before the next node "e".
|
|
199
|
+
*
|
|
200
|
+
* A: [b c d e f]
|
|
201
|
+
* B: [c b h f e]
|
|
202
|
+
* ^ // new_pos == 2
|
|
203
|
+
* P: [1 * . 4 *] // . == NewNodeMark * == LisMark
|
|
204
|
+
* ^
|
|
205
|
+
*
|
|
206
|
+
* Node "h" has `NewNodeMark` value in the array `P`, mount new node "h".
|
|
207
|
+
*
|
|
208
|
+
* A: [b c d e f]
|
|
209
|
+
* B: [c b h f e]
|
|
210
|
+
* ^ // new_pos == 1
|
|
211
|
+
* P: [1 * . 4 *] // . == NewNodeMark * == LisMark
|
|
212
|
+
* ^
|
|
213
|
+
*
|
|
214
|
+
* Node "b" has `LisMark` value in the array `P`, nothing changes.
|
|
215
|
+
*
|
|
216
|
+
* A: [b c d e f]
|
|
217
|
+
* B: [c b h f e]
|
|
218
|
+
* ^ // new_pos == 0
|
|
219
|
+
* P: [1 * . 4 *] // . == NewNodeMark * == LisMark
|
|
220
|
+
*
|
|
221
|
+
* Node "c" has `1` value in the array `P`, move it before the next node "b".
|
|
222
|
+
*
|
|
223
|
+
* When `rearrangeNodes` flag is off, skip LIS algorithm and mount nodes that
|
|
224
|
+
* have `NewNodeMark` value in the array `P`.
|
|
225
|
+
*
|
|
226
|
+
* NOTE: There are many variations of this algorithm that are used by many UI
|
|
227
|
+
* libraries and many implementations are still using an old optimization
|
|
228
|
+
* technique that were removed several years ago from this implementation. This
|
|
229
|
+
* optimization were used to improve performance of simple moves/swaps. E.g.
|
|
230
|
+
*
|
|
231
|
+
* A: -> [a b c] <-
|
|
232
|
+
* B: -> [c b a] <-
|
|
233
|
+
*
|
|
234
|
+
* Move "a" and "c" nodes to the other edge.
|
|
235
|
+
*
|
|
236
|
+
* A: -> [b] <-
|
|
237
|
+
* B: -> [b] <-
|
|
238
|
+
*
|
|
239
|
+
* Skip node "b".
|
|
240
|
+
*
|
|
241
|
+
* This optimization were removed because it breaks invariant that insert and
|
|
242
|
+
* remove operations shouldn't trigger a move operation. E.g.
|
|
243
|
+
*
|
|
244
|
+
* A: -> [a b]
|
|
245
|
+
* B: [c a] <-
|
|
246
|
+
*
|
|
247
|
+
* Move node "a" to the end.
|
|
248
|
+
*
|
|
249
|
+
* A: [b]
|
|
250
|
+
* B: [c a]
|
|
251
|
+
*
|
|
252
|
+
* Remove node "b" and insert node "c".
|
|
253
|
+
*
|
|
254
|
+
* In this use case, this optimization performs one unnecessary operation.
|
|
255
|
+
* Instead of removing node "b" and inserting node "c", it also moves node "a".
|
|
256
|
+
*
|
|
257
|
+
*/
|
|
258
|
+
export const arr = <T>(
|
|
259
|
+
aKeys: readonly T[],
|
|
260
|
+
bKeys: readonly T[],
|
|
261
|
+
onDelete: (item: T) => void,
|
|
262
|
+
onCreate: (item: T) => void,
|
|
263
|
+
onRemain: (item: T) => void,
|
|
264
|
+
onLocate: (item: T) => void,
|
|
265
|
+
): void => {
|
|
266
|
+
const bLength = bKeys.length;
|
|
267
|
+
const aLength = aKeys.length;
|
|
268
|
+
|
|
269
|
+
if (bLength === 0) {
|
|
270
|
+
// New children list is empty.
|
|
271
|
+
if (aLength > 0) {
|
|
272
|
+
// Unmount nodes from the old children list.
|
|
273
|
+
aKeys.forEach(onDelete);
|
|
274
|
+
}
|
|
275
|
+
} else if (aLength === 0) {
|
|
276
|
+
// Old children list is empty.
|
|
277
|
+
bKeys.forEach(onCreate);
|
|
278
|
+
} else {
|
|
279
|
+
let aEnd = aLength - 1;
|
|
280
|
+
let bEnd = bLength - 1;
|
|
281
|
+
let start = 0;
|
|
282
|
+
|
|
283
|
+
// Step 1
|
|
284
|
+
// Align common elements from the start
|
|
285
|
+
while (start <= aEnd && start <= bEnd && aKeys[start] === bKeys[start]) {
|
|
286
|
+
onRemain(aKeys[start++]!);
|
|
287
|
+
}
|
|
288
|
+
// Align common elements from the end
|
|
289
|
+
while (start <= aEnd && start <= bEnd && aKeys[aEnd] === bKeys[bEnd]) {
|
|
290
|
+
aEnd--;
|
|
291
|
+
bEnd--;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Step 2
|
|
295
|
+
if (start > aEnd) {
|
|
296
|
+
// All nodes from `a` are updated, insert the rest from `b`.
|
|
297
|
+
while (start <= bEnd) {
|
|
298
|
+
onCreate(bKeys[start++]!);
|
|
299
|
+
}
|
|
300
|
+
} else if (start > bEnd) {
|
|
301
|
+
// All nodes from `b` are updated, remove the rest from `a`.
|
|
302
|
+
while (start <= aEnd) {
|
|
303
|
+
onDelete(aKeys[start++]!);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
// Step 3
|
|
307
|
+
const bLength = bEnd - start + 1;
|
|
308
|
+
const sources = new Int32Array(bLength); // Maps positions in the new children list to positions in the old list.
|
|
309
|
+
const keyIndex = new Map<T, number>(); // Maps keys to their positions in the new children list.
|
|
310
|
+
for (let i = 0, j = start; i < bLength; i++, j++) {
|
|
311
|
+
// `NewNodeMark` value indicates that node doesn't exist in the old children list.
|
|
312
|
+
sources[i] = MagicValues.NewNodeMark;
|
|
313
|
+
keyIndex.set(bKeys[j]!, j);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// When `nodePosition === RearrangeNodes`, it means that one of the nodes is in the wrong position and we should
|
|
317
|
+
// rearrange nodes with LIS-based algorithm `markLIS()`.
|
|
318
|
+
let nodePosition = 0;
|
|
319
|
+
for (let i = start; i <= aEnd; i++) {
|
|
320
|
+
const nextPosition = keyIndex.get(aKeys[i]!);
|
|
321
|
+
if (nextPosition !== void 0) {
|
|
322
|
+
nodePosition = nodePosition < nextPosition ? nextPosition : MagicValues.RearrangeNodes;
|
|
323
|
+
sources[nextPosition - start] = i;
|
|
324
|
+
} else {
|
|
325
|
+
onDelete(aKeys[i]!);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Step 4
|
|
330
|
+
|
|
331
|
+
// Mark LIS nodes only when this node weren't moved `moveNode === false` and we've detected that one of the
|
|
332
|
+
// children nodes were moved `pos === MagicValues.MovedChildren`.
|
|
333
|
+
if (nodePosition === MagicValues.RearrangeNodes) {
|
|
334
|
+
markLIS(sources);
|
|
335
|
+
}
|
|
336
|
+
for (let i = 0; i < bLength; i++) {
|
|
337
|
+
const bNode = bKeys[start + i]!;
|
|
338
|
+
const lisValue = sources[i]!;
|
|
339
|
+
if (lisValue === -1) {
|
|
340
|
+
onCreate(bNode);
|
|
341
|
+
} else if (nodePosition === MagicValues.RearrangeNodes && lisValue !== MagicValues.LISMark) {
|
|
342
|
+
onLocate(bNode);
|
|
343
|
+
} else {
|
|
344
|
+
onRemain(bNode);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Delayed update for nodes from Step 1 (prefix only). Reconciliation algorithm always updates nodes from left to
|
|
350
|
+
// right.
|
|
351
|
+
for (let i = 1; i + aEnd < aLength /*&& i + bEnd < bLength*/; i++) {
|
|
352
|
+
onRemain(aKeys[i + aEnd]!);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Modified Longest Increased Subsequence algorithm.
|
|
359
|
+
*
|
|
360
|
+
* Mutates input array `a` and replaces all values that are part of LIS with -2 value.
|
|
361
|
+
*
|
|
362
|
+
* Constraints:
|
|
363
|
+
* - Doesn't work with negative numbers. -1 values are ignored.
|
|
364
|
+
* - Input array `a` should contain at least one value that is greater than -1.
|
|
365
|
+
*
|
|
366
|
+
* {@link http://en.wikipedia.org/wiki/Longest_increasing_subsequence}
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
*
|
|
370
|
+
* const A = Int32Array.from([-1, 0, 2, 1]);
|
|
371
|
+
* markLIS(A);
|
|
372
|
+
* // A => [-1, -2, 2, -2]
|
|
373
|
+
*
|
|
374
|
+
* @param a Array of numbers.
|
|
375
|
+
*/
|
|
376
|
+
const markLIS = (a: Int32Array): void => {
|
|
377
|
+
const length = a.length;
|
|
378
|
+
const parent = new Int32Array(length);
|
|
379
|
+
const index = new Int32Array(length);
|
|
380
|
+
let indexLength = 0;
|
|
381
|
+
let i = 0;
|
|
382
|
+
let j: number;
|
|
383
|
+
let k: number;
|
|
384
|
+
let lo: number;
|
|
385
|
+
let hi: number;
|
|
386
|
+
|
|
387
|
+
// Skip -1 values at the start of the input array `a`.
|
|
388
|
+
while (a[i] === MagicValues.NewNodeMark) i++;
|
|
389
|
+
|
|
390
|
+
index[0] = i++;
|
|
391
|
+
for (; i < length; i++) {
|
|
392
|
+
k = a[i]!;
|
|
393
|
+
if (k !== MagicValues.NewNodeMark) {
|
|
394
|
+
// Ignore -1 values.
|
|
395
|
+
j = index[indexLength]!;
|
|
396
|
+
if (a[j]! < k) {
|
|
397
|
+
parent[i] = j;
|
|
398
|
+
index[++indexLength] = i;
|
|
399
|
+
} else {
|
|
400
|
+
lo = 0;
|
|
401
|
+
hi = indexLength;
|
|
402
|
+
|
|
403
|
+
while (lo < hi) {
|
|
404
|
+
j = (lo + hi) >> 1;
|
|
405
|
+
if (a[index[j]!]! < k) {
|
|
406
|
+
lo = j + 1;
|
|
407
|
+
} else {
|
|
408
|
+
hi = j;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (k < a[index[lo]!]!) {
|
|
413
|
+
if (lo > 0) {
|
|
414
|
+
parent[i] = index[lo - 1]!;
|
|
415
|
+
}
|
|
416
|
+
index[lo] = i;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Mutate input array `a` and assign -2 value to all nodes that are part of LIS.
|
|
423
|
+
j = index[indexLength]!;
|
|
424
|
+
while (indexLength-- >= 0) {
|
|
425
|
+
a[j] = MagicValues.LISMark;
|
|
426
|
+
j = parent[j]!;
|
|
427
|
+
}
|
|
428
|
+
};
|