fusee-framework 1.7.0 → 1.9.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 (77) hide show
  1. package/README.md +206 -139
  2. package/app/benchmark/benchmark.js +2 -2
  3. package/app/benchmark/vite.config.js +23 -0
  4. package/app/main.js +24 -3
  5. package/app/main.ts +7 -1
  6. package/app/pages/_layout.js +27 -0
  7. package/app/pages/async-work.js +65 -0
  8. package/app/pages/index.js +25 -5
  9. package/app/pages/index.ts +9 -0
  10. package/app/pages/router-example.js +24 -1
  11. package/app/pages/scroll-demo.ts +102 -0
  12. package/app/providers/ChildComponent.js +8 -2
  13. package/app/providers/ChildComponent.ts +47 -47
  14. package/app/providers/GrandchildComponent.js +19 -0
  15. package/app/providers/ParentComponent.js +5 -1
  16. package/app/routes/RouterExample.js +23 -1
  17. package/app/routes/TestResource.js +47 -0
  18. package/app/services.js +15 -0
  19. package/auto-imports.d.ts +12 -0
  20. package/framework/auto-import-preset.js +15 -9
  21. package/framework/core/async-context.js +27 -0
  22. package/framework/core/compiler/ast.js +167 -0
  23. package/framework/core/compiler/errors-list.js +47 -0
  24. package/framework/core/compiler/errors.js +107 -0
  25. package/framework/core/compiler/generator.js +363 -0
  26. package/framework/core/compiler/lexer.js +347 -0
  27. package/framework/core/compiler/main-compiler.js +115 -0
  28. package/framework/core/compiler/parser.js +414 -0
  29. package/framework/core/compiler/plugins/actions-plugin.js +263 -0
  30. package/framework/core/compiler/plugins/compiler-plugin.d.ts +5 -0
  31. package/framework/core/compiler/plugins/compiler-plugin.js +45 -0
  32. package/framework/core/compiler/plugins/file-router-plugin.js +301 -0
  33. package/framework/core/compiler/plugins/router-plugin.js +196 -0
  34. package/framework/core/compiler/transformer.js +398 -0
  35. package/framework/core/component.js +61 -23
  36. package/framework/core/di.js +187 -0
  37. package/framework/core/directives.js +40 -96
  38. package/framework/core/evaluator.js +1 -1
  39. package/framework/core/h.js +430 -0
  40. package/framework/core/hydration.js +83 -0
  41. package/framework/core/reconcile.js +151 -0
  42. package/framework/core/signal.js +269 -0
  43. package/framework/core/store.js +322 -81
  44. package/framework/index.js +5 -3
  45. package/framework/router/file-router.js +1 -1
  46. package/framework/router/router.js +113 -21
  47. package/framework/server/actions.js +173 -0
  48. package/framework/server/actions.server.js +88 -0
  49. package/framework/server/index.js +18 -0
  50. package/framework/types/actions.d.ts +50 -0
  51. package/framework/types/actions.server.d.ts +36 -0
  52. package/framework/types/compiler/ast.d.ts +166 -0
  53. package/framework/types/compiler/errors-codes.d.ts +23 -0
  54. package/framework/types/compiler/errors.d.ts +43 -0
  55. package/framework/types/compiler/generator.d.ts +8 -0
  56. package/framework/types/compiler/h.d.ts +46 -0
  57. package/framework/types/compiler/lexer.d.ts +27 -0
  58. package/framework/types/compiler/main-compiler.d.ts +60 -0
  59. package/framework/types/compiler/parser.d.ts +4 -0
  60. package/framework/types/compiler/plugins/actions.d.ts +30 -0
  61. package/framework/types/compiler/plugins/compiler-plugin.d.ts +5 -0
  62. package/framework/types/compiler/plugins/file-router-plugins.d.ts +32 -0
  63. package/framework/types/compiler/plugins/router-plugin.d.ts +24 -0
  64. package/framework/types/compiler/transformer.d.ts +28 -0
  65. package/framework/types/component.d.ts +89 -88
  66. package/framework/types/di.d.ts +43 -0
  67. package/framework/types/hydration.d.ts +30 -0
  68. package/framework/types/index.d.ts +38 -22
  69. package/framework/types/reconcile.d.ts +7 -0
  70. package/framework/types/router.d.ts +11 -1
  71. package/framework/types/server.d.ts +11 -0
  72. package/framework/types/signal.d.ts +142 -79
  73. package/framework/types/store.d.ts +66 -8
  74. package/ghid_go_fusee_integrat_go1_26.txt +276 -0
  75. package/ghid_rust_fusee_rust_1_95_0.txt +1118 -0
  76. package/package.json +44 -36
  77. package/vite.config.js +65 -47
@@ -0,0 +1,430 @@
1
+ import { effect, batch, signal } from '../core/signal.js'
2
+ import { createEventHandler, registerDelegatedEvent, isDelegatedEvent } from '../core/event-delegation.js'
3
+
4
+ export { effect as _effect, batch as _batch }
5
+
6
+ function isGetter(v) {
7
+ return typeof v === 'function' && !v.isSignal
8
+ }
9
+
10
+ const SENSITIVE_ATTRS = new Set(['href', 'src', 'srcset', 'formaction', 'xlink:href', 'data'])
11
+ const DANGEROUS_SCHEME = /^(javascript|data|vbscript|file):/i
12
+
13
+ function sanitize(name, value) {
14
+ if (SENSITIVE_ATTRS.has(name.toLowerCase())) {
15
+ const trimmed = String(value).trim()
16
+ if (DANGEROUS_SCHEME.test(trimmed)) {
17
+ console.warn(`[fusée] Blocked potential XSS on "${name}": "${value}"`)
18
+ return 'about:blank'
19
+ }
20
+ }
21
+ return value
22
+ }
23
+
24
+ export function h(tag, props = {}, children = [], isStatic = false) {
25
+ const el = document.createElement(tag)
26
+ const effects = []
27
+
28
+ if (!isStatic) {
29
+ _applyProps(el, props, effects)
30
+ } else {
31
+ _applyStaticProps(el, props)
32
+ }
33
+
34
+ const childNodes = _mountChildren(el, children, effects)
35
+
36
+ return {
37
+ node: el,
38
+ effects,
39
+ props,
40
+ children: childNodes,
41
+ _tag: tag,
42
+ }
43
+ }
44
+
45
+ export function hText(value) {
46
+ const node = document.createTextNode('')
47
+ const effects = []
48
+
49
+ if (typeof value === 'function') {
50
+ const cleanup = effect(() => {
51
+ node.textContent = String(value() ?? '')
52
+ })
53
+ effects.push(cleanup)
54
+ } else {
55
+ node.textContent = String(value ?? '')
56
+ }
57
+
58
+ return { node, effects }
59
+ }
60
+
61
+ export function hIf(branches) {
62
+ const anchor = document.createComment('f-if')
63
+ const effects = []
64
+
65
+ let currentNodes = []
66
+ let currentBranch = -1
67
+
68
+ const cleanup = effect(() => {
69
+ let matchIdx = -1
70
+ for (let i = 0; i < branches.length; i++) {
71
+ const [cond] = branches[i]
72
+ if (cond === null || cond()) { matchIdx = i; break }
73
+ }
74
+
75
+ if (matchIdx === currentBranch) return
76
+
77
+ _unmountNodes(currentNodes)
78
+ currentNodes = []
79
+ currentBranch = matchIdx
80
+
81
+ if (matchIdx === -1) return
82
+
83
+ const [, getChildren] = branches[matchIdx]
84
+ const children = getChildren()
85
+ const parent = anchor.parentNode
86
+
87
+ if (parent) {
88
+ for (const fnode of children) {
89
+ parent.insertBefore(fnode.node, anchor)
90
+ currentNodes.push(fnode)
91
+ }
92
+ }
93
+ })
94
+
95
+ effects.push(cleanup)
96
+
97
+ return {
98
+ node: anchor,
99
+ effects,
100
+ _ifBranchNodes: () => currentNodes,
101
+ _isIf: true,
102
+ }
103
+ }
104
+
105
+ export function hFor(sourceGetter, renderItem, keyFn) {
106
+ const anchor = document.createComment('f-for')
107
+ const effects = []
108
+
109
+ let keyedMap = new Map()
110
+ const keyEvaluatorSignal = signal(null)
111
+
112
+ const cleanup = effect(() => {
113
+ const list = sourceGetter() ?? []
114
+ const parent = anchor.parentNode
115
+
116
+ if (!parent) {
117
+ return
118
+ }
119
+
120
+ const newMap = new Map()
121
+ const newKeys = []
122
+
123
+ for (let i = 0; i < list.length; i++) {
124
+ const item = list[i]
125
+
126
+ keyEvaluatorSignal(item)
127
+ const key = keyFn(keyEvaluatorSignal, i)
128
+ newKeys.push(key)
129
+
130
+ const exists = keyedMap.get(key)
131
+ if (exists) {
132
+ if (exists.itemSignal() !== item) {
133
+ exists.itemSignal(item)
134
+ }
135
+ newMap.set(key, exists)
136
+ } else {
137
+ const itemSignal = signal(item)
138
+ const fnode = renderItem(itemSignal, i)
139
+ newMap.set(key, { fnode, key, itemSignal })
140
+ }
141
+ }
142
+
143
+ for (const [key, entry] of keyedMap) {
144
+ if (!newMap.has(key)) {
145
+ _unmountNodes([entry.fnode])
146
+ }
147
+ }
148
+
149
+ for (let i = newKeys.length - 1; i >= 0; i--) {
150
+ const key = newKeys[i]
151
+ const entry = newMap.get(key)
152
+ const ref = i === newKeys.length - 1 ? anchor : newMap.get(newKeys[i + 1])?.fnode?.node
153
+
154
+ if (ref && entry.fnode.node.nextSibling !== ref) {
155
+ parent.insertBefore(entry.fnode.node, ref ?? anchor)
156
+ } else if (!entry.fnode.node.parentNode) {
157
+ parent.insertBefore(entry.fnode.node, anchor)
158
+ }
159
+ }
160
+
161
+ keyedMap = newMap
162
+ })
163
+
164
+ effects.push(cleanup)
165
+
166
+ return {
167
+ node: anchor,
168
+ effects,
169
+ _isFor: true,
170
+ _getNodes: () => [...keyedMap.values()].map(e => e.fnode),
171
+ }
172
+ }
173
+
174
+ export function hSlot(slots, name, fallback = []) {
175
+ const anchor = document.createComment(`slot:${name}`)
176
+ const effects = []
177
+ const slotFn = slots?.[name]
178
+ const nodes = typeof slotFn === 'function' ? slotFn() : fallback
179
+ return {
180
+ node: anchor,
181
+ effects,
182
+ _slotNodes: nodes,
183
+ _isSlot: true,
184
+ }
185
+ }
186
+
187
+ export function createComponent(name, ComponentFn, rawProps = {}, rawSlots = {}) {
188
+ if (!ComponentFn) {
189
+ console.warn(`[fusée] Component "${name}" is not registered`)
190
+ const placeholder = document.createComment(`missing:${name}`)
191
+ return { node: placeholder, effects: [] }
192
+ }
193
+
194
+ const resolvedProps = _resolveComponentProps(rawProps)
195
+ const listeners = {}
196
+ const slots = {}
197
+
198
+ for (const [key, value] of Object.entries(rawProps)) {
199
+ if (key.startsWith('on:')) {
200
+ listeners[key.slice(3)] = value
201
+ }
202
+ }
203
+
204
+ for (const [slotName, slotFn] of Object.entries(rawSlots)) {
205
+ slots[slotName] = slotFn
206
+ }
207
+
208
+ const container = document.createElement('div')
209
+ container.setAttribute('data-fusee-component', name)
210
+
211
+ const api = ComponentFn(resolvedProps, { listeners, slots })
212
+ api.render(container)
213
+ const root = container.firstElementChild ?? container
214
+
215
+ const effects = []
216
+ effects.push(() => api.unmount())
217
+
218
+ return { node: root, effects, _component: api }
219
+ }
220
+
221
+ function _applyProps(el, props, effects) {
222
+ for (const [key, value] of Object.entries(props)) {
223
+ if (key.startsWith('@')) {
224
+ const eventName = key.slice(1)
225
+ const { handler, modifiers = [] } = value
226
+ const handlerState = { timeoutId: null, throttleTimeoutId: null, lastRun: 0 }
227
+ const wrappedHandler = createEventHandler(handler, modifiers, null, null, handlerState)
228
+
229
+ let cleanup
230
+ if (isDelegatedEvent(eventName)) {
231
+ cleanup = registerDelegatedEvent(el, eventName, wrappedHandler, { modifiers, handlerState })
232
+ } else {
233
+ el.addEventListener(eventName, wrappedHandler)
234
+ cleanup = () => el.removeEventListener(eventName, wrappedHandler)
235
+ }
236
+ effects.push(cleanup)
237
+ continue
238
+ }
239
+
240
+ if (key === 'f-show') {
241
+ const cleanup = effect(() => {
242
+ el.style.display = value() ? '' : 'none'
243
+ })
244
+ effects.push(cleanup)
245
+ continue
246
+ }
247
+
248
+ if (key === 'f-html') {
249
+ const cleanup = effect(() => {
250
+ el.innerHTML = String(value() ?? '')
251
+ })
252
+ effects.push(cleanup)
253
+ continue
254
+ }
255
+
256
+ if (key === 'f-model') {
257
+ const sig = value()
258
+ const cleanup = effect(() => {
259
+ const v = typeof sig === 'function' ? sig() : sig
260
+ if (el.value !== String(v ?? '')) el.value = String(v ?? '')
261
+ })
262
+ effects.push(cleanup)
263
+ continue
264
+ }
265
+
266
+ if (key === 'f-ref') {
267
+ el.dispatchEvent(new CustomEvent('fusee:ref', {
268
+ detail: { name: value },
269
+ bubbles: true,
270
+ }))
271
+ continue
272
+ }
273
+
274
+ if (key === 'f-once') continue
275
+
276
+ if (typeof value !== 'function') {
277
+ _applyAttr(el, key, value)
278
+ continue
279
+ }
280
+
281
+ const attrName = key
282
+ const getter = value
283
+ const cleanup = effect(() => {
284
+ _applyAttr(el, attrName, getter())
285
+ })
286
+ effects.push(cleanup)
287
+ }
288
+ }
289
+
290
+ function _applyStaticProps(el, props) {
291
+ for (const [key, value] of Object.entries(props)) {
292
+ if (key.startsWith('@') || key.startsWith('f-'))
293
+ continue
294
+ if (typeof value !== 'function')
295
+ _applyAttr(el, key, value)
296
+ }
297
+ }
298
+
299
+ function _applyAttr(el, name, value) {
300
+ if (name === 'class') {
301
+ const cls = _resolveClass(value)
302
+ if (cls) el.className = cls
303
+ else el.removeAttribute('class')
304
+ return
305
+ }
306
+
307
+ if (name === 'style') {
308
+ if (value !== null && typeof value === 'object') {
309
+ for (const k in value) el.style[k] = value[k]
310
+ } else {
311
+ el.style.cssText = String(value ?? '')
312
+ }
313
+ return
314
+ }
315
+
316
+ if (name in el && typeof el[name] !== 'undefined' && name !== 'type') {
317
+ try {
318
+ el[name] = value
319
+ return
320
+ } catch (_) {
321
+ return
322
+ }
323
+ }
324
+
325
+ if (typeof value === 'boolean') {
326
+ value ? el.setAttribute(name, '') : el.removeAttribute(name)
327
+ return
328
+ }
329
+
330
+ if (value == null) {
331
+ el.removeAttribute(name)
332
+ return
333
+ }
334
+
335
+ el.setAttribute(name, sanitize(name, String(value)))
336
+ }
337
+
338
+ function _resolveClass(value) {
339
+ if (typeof value === 'string')
340
+ return value
341
+ if (Array.isArray(value))
342
+ return value.filter(Boolean).join(' ')
343
+ if (value && typeof value === 'object')
344
+ return Object.entries(value).filter(([, v]) => !!v).map(([k]) => k).join(' ')
345
+ return String(value ?? '')
346
+ }
347
+
348
+ function _mountChildren(parent, children, parentEffects) {
349
+ const mounted = []
350
+
351
+ for (const fnode of children) {
352
+ if (!fnode || !fnode.node) continue
353
+
354
+ parent.appendChild(fnode.node)
355
+
356
+ if (fnode._isSlot && fnode._slotNodes) {
357
+ for (const sn of fnode._slotNodes) {
358
+ if (sn?.node) parent.insertBefore(sn.node, null)
359
+ }
360
+ }
361
+
362
+ if (fnode.effects) parentEffects.push(...fnode.effects)
363
+ mounted.push(fnode)
364
+ }
365
+
366
+ return mounted
367
+ }
368
+
369
+ function _unmountNodes(fnodes) {
370
+ for (const fnode of fnodes) {
371
+ if (!fnode) continue
372
+
373
+ if (fnode.effects) {
374
+ for (let i = fnode.effects.length - 1; i >= 0; i--) {
375
+ const cleanup = fnode.effects[i]
376
+ if (typeof cleanup === 'function') cleanup()
377
+ }
378
+ }
379
+
380
+ if (fnode.node?.parentNode) {
381
+ fnode.node.parentNode.removeChild(fnode.node)
382
+ }
383
+
384
+ if (fnode._ifBranchNodes) {
385
+ _unmountNodes(fnode._ifBranchNodes())
386
+ }
387
+
388
+ if (fnode._getNodes) {
389
+ _unmountNodes(fnode._getNodes())
390
+ }
391
+ }
392
+ }
393
+
394
+ function _resolveComponentProps(rawProps) {
395
+ const resolved = {}
396
+
397
+ for (const [key, value] of Object.entries(rawProps)) {
398
+ if (key.startsWith('on:')) continue
399
+
400
+ if (isGetter(value)) {
401
+ Object.defineProperty(resolved, key, {
402
+ get: value,
403
+ enumerable: true,
404
+ configurable: true,
405
+ })
406
+ } else {
407
+ resolved[key] = value
408
+ }
409
+ }
410
+
411
+ return resolved
412
+ }
413
+
414
+ export function mount(renderFn, ctx, components, container) {
415
+ const fnodes = renderFn(ctx, components)
416
+ const effects = []
417
+
418
+ for (const fnode of fnodes) {
419
+ if (!fnode?.node) continue
420
+ container.appendChild(fnode.node)
421
+ if (fnode.effects) effects.push(...fnode.effects)
422
+ }
423
+
424
+ return {
425
+ unmount() {
426
+ _unmountNodes(fnodes)
427
+ container.innerHTML = ''
428
+ }
429
+ }
430
+ }
@@ -0,0 +1,83 @@
1
+ const _hydrationRegistry = new Map()
2
+ const _registeredCaches = []
3
+
4
+ export function _registerResourceCache(resourceKey, cache) {
5
+ _registeredCaches.push({ resourceKey, cache })
6
+ }
7
+
8
+ export function extractHydrationData() {
9
+ const snapshot = {}
10
+
11
+ for (const { resourceKey, cache } of _registeredCaches) {
12
+ const entries = {}
13
+ for (const [cacheKey, entry] of cache) {
14
+ entries[cacheKey] = { data: entry.data, updatedAt: entry.updatedAt }
15
+ }
16
+ if (Object.keys(entries).length > 0) {
17
+ snapshot[resourceKey] = entries
18
+ }
19
+ }
20
+
21
+ return snapshot
22
+ }
23
+
24
+ export function dehydrate(snapshot = extractHydrationData()) {
25
+ try {
26
+ const json = JSON.stringify(snapshot)
27
+ return json.replace(/<\/script>/gi, '<\\/script>')
28
+ } catch (err) {
29
+ console.error('[fusée] dehydrate() failed to serialize hydration data:', err)
30
+ return '{}'
31
+ }
32
+ }
33
+
34
+ export function loadHydration(snapshot) {
35
+ if (!snapshot || typeof snapshot !== 'object') {
36
+ console.warn('[fusée] loadHydration() received invalid snapshot')
37
+ return
38
+ }
39
+
40
+ for (const [resourceKey, entries] of Object.entries(snapshot)) {
41
+ if (!_hydrationRegistry.has(resourceKey)) {
42
+ _hydrationRegistry.set(resourceKey, new Map())
43
+ }
44
+ const map = _hydrationRegistry.get(resourceKey)
45
+ for (const [cacheKey, entry] of Object.entries(entries)) {
46
+ map.set(cacheKey, { data: entry.data, updatedAt: entry.updatedAt })
47
+ }
48
+ }
49
+ }
50
+
51
+ export function hydrateFromWindow() {
52
+ if (typeof window === 'undefined') return
53
+ const raw = window.__FUSEE_HYDRATION__
54
+ if (raw) loadHydration(raw)
55
+ }
56
+
57
+ export function getHydratedEntries(resourceKey) {
58
+ return _hydrationRegistry.get(resourceKey) ?? null
59
+ }
60
+
61
+ export function isHydrationFresh(resourceKey, cacheKey, staleTime = 0) {
62
+ const entries = _hydrationRegistry.get(resourceKey)
63
+ if (!entries) return false
64
+ const entry = entries.get(cacheKey)
65
+ if (!entry) return false
66
+ return (Date.now() - entry.updatedAt) <= staleTime
67
+ }
68
+
69
+ export function clearHydration() {
70
+ _hydrationRegistry.clear()
71
+ _registeredCaches.length = 0
72
+ }
73
+
74
+ export function getHydrationSnapshot() {
75
+ const out = {}
76
+ for (const [resourceKey, map] of _hydrationRegistry) {
77
+ out[resourceKey] = {}
78
+ for (const [cacheKey, entry] of map) {
79
+ out[resourceKey][cacheKey] = { ...entry }
80
+ }
81
+ }
82
+ return out
83
+ }
@@ -0,0 +1,151 @@
1
+ export function reconcileArrays(parentNode, anchor, oldList, newList, keyFn, createFn, updateFn) {
2
+ let oldEnd = oldList.length - 1;
3
+ let newEnd = newList.length - 1;
4
+ let oldStart = 0;
5
+ let newStart = 0;
6
+
7
+ let oldStartNode = oldList[oldStart];
8
+ let oldEndNode = oldList[oldEnd];
9
+ let newStartItem = newList[newStart];
10
+ let newEndItem = newList[newEnd];
11
+
12
+ const nextOrder = new Array(newList.length);
13
+
14
+ while (oldStart <= oldEnd && newStart <= newEnd) {
15
+ if (oldStartNode === null) {
16
+ oldStartNode = oldList[++oldStart];
17
+ } else if (oldEndNode === null) {
18
+ oldEndNode = oldList[--oldEnd];
19
+ } else if (keyFn(oldStartNode) === keyFn(newStartItem)) {
20
+ updateFn(oldStartNode, newStartItem, newStart);
21
+ nextOrder[newStart] = oldStartNode;
22
+ oldStartNode = oldList[++oldStart];
23
+ newStartItem = newList[++newStart];
24
+ } else if (keyFn(oldEndNode) === keyFn(newEndItem)) {
25
+ updateFn(oldEndNode, newEndItem, newEnd);
26
+ nextOrder[newEnd] = oldEndNode;
27
+ oldEndNode = oldList[--oldEnd];
28
+ newEndItem = newList[--newEnd];
29
+ } else if (keyFn(oldStartNode) === keyFn(newEndItem)) {
30
+ updateFn(oldStartNode, newEndItem, newEnd);
31
+ const nextSibling = oldEndNode.node.nextSibling;
32
+ parentNode.insertBefore(oldStartNode.node, nextSibling === anchor ? anchor : nextSibling);
33
+ nextOrder[newEnd] = oldStartNode;
34
+ oldStartNode = oldList[++oldStart];
35
+ newEndItem = newList[--newEnd];
36
+ } else if (keyFn(oldEndNode) === keyFn(newStartItem)) {
37
+ updateFn(oldEndNode, newStartItem, newStart);
38
+ parentNode.insertBefore(oldEndNode.node, oldStartNode.node);
39
+ nextOrder[newStart] = oldEndNode;
40
+ oldEndNode = oldList[--oldEnd];
41
+ newStartItem = newList[++newStart];
42
+ } else {
43
+ break;
44
+ }
45
+ }
46
+
47
+ if (oldStart > oldEnd) {
48
+ for (; newStart <= newEnd; ++newStart) {
49
+ const nextPos = newStart + 1;
50
+ const refNode = nextPos < newList.length && nextOrder[nextPos] ? nextOrder[nextPos].node : (oldStart < oldList.length && oldList[oldStart] ? oldList[oldStart].node : anchor);
51
+ const newNode = createFn(newList[newStart], newStart);
52
+ parentNode.insertBefore(newNode.node, refNode);
53
+ nextOrder[newStart] = newNode;
54
+ }
55
+ } else if (newStart > newEnd) {
56
+ for (; oldStart <= oldEnd; ++oldStart) {
57
+ if (oldList[oldStart]) {
58
+ const node = oldList[oldStart];
59
+ node.node.remove();
60
+ if (node.effects) {
61
+ node.effects.forEach(cleanup => typeof cleanup === 'function' && cleanup());
62
+ }
63
+ }
64
+ }
65
+ } else {
66
+ const keyToIndex = new Map();
67
+ for (let i = newStart; i <= newEnd; ++i) {
68
+ keyToIndex.set(keyFn(newList[i]), i);
69
+ }
70
+
71
+ let nodesToMove = 0;
72
+ let lastIndex = 0;
73
+ const newIndexToOldIndexMap = new Array(newEnd - newStart + 1).fill(0);
74
+
75
+ for (let i = oldStart; i <= oldEnd; ++i) {
76
+ const oldNode = oldList[i];
77
+ if (!oldNode) continue;
78
+
79
+ const newIndex = keyToIndex.get(keyFn(oldNode));
80
+ if (newIndex === undefined) {
81
+ oldNode.node.remove();
82
+ if (oldNode.effects) {
83
+ oldNode.effects.forEach(cleanup => typeof cleanup === 'function' && cleanup());
84
+ }
85
+ } else {
86
+ updateFn(oldNode, newList[newIndex], newIndex);
87
+ newIndexToOldIndexMap[newIndex - newStart] = i + 1;
88
+ if (newIndex >= lastIndex) {
89
+ lastIndex = newIndex;
90
+ } else {
91
+ nodesToMove++;
92
+ }
93
+ nextOrder[newIndex] = oldNode;
94
+ }
95
+ }
96
+
97
+ for (let i = newEnd - newStart; i >= 0; i--) {
98
+ const newIndex = i + newStart;
99
+ const nextPos = newIndex + 1;
100
+ const refNode = nextPos < nextOrder.length && nextOrder[nextPos] ? nextOrder[nextPos].node : anchor;
101
+
102
+ if (newIndexToOldIndexMap[i] === 0) {
103
+ const newNode = createFn(newList[newIndex], newIndex);
104
+ parentNode.insertBefore(newNode.node, refNode);
105
+ nextOrder[newIndex] = newNode;
106
+ } else if (nodesToMove > 0) {
107
+ let currentPosIndex = i;
108
+ const oldNode = nextOrder[newIndex];
109
+ if (oldNode.node.nextSibling !== refNode) {
110
+ parentNode.insertBefore(oldNode.node, refNode);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ return nextOrder;
117
+ }
118
+
119
+ export function reconcile(container, oldList, newList, keyFn, createFn) {
120
+ if (!container._renderedNodes) {
121
+ container._renderedNodes = [];
122
+ }
123
+
124
+ const mappedOldList = container._renderedNodes;
125
+ const mappedNewList = newList.map((item, index) => ({
126
+ item: item,
127
+ key: keyFn(item),
128
+ rawIndex: index
129
+ }));
130
+
131
+ const nextOrder = reconcileArrays(
132
+ container,
133
+ null,
134
+ mappedOldList,
135
+ mappedNewList,
136
+ (x) => x.key,
137
+ (newItem) => {
138
+ const domNode = createFn(newItem.item);
139
+ return {
140
+ node: domNode,
141
+ item: newItem.item,
142
+ key: newItem.key
143
+ };
144
+ },
145
+ (oldNode, newItem) => {
146
+ oldNode.item = newItem.item;
147
+ }
148
+ );
149
+
150
+ container._renderedNodes = nextOrder;
151
+ }