fusee-framework 1.7.0 → 1.8.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.
- package/README.md +145 -139
- package/app/benchmark/benchmark.js +2 -2
- package/app/main.js +24 -3
- package/app/main.ts +7 -1
- package/app/pages/_layout.js +27 -0
- package/app/pages/async-work.js +65 -0
- package/app/pages/index.js +25 -5
- package/app/pages/index.ts +9 -0
- package/app/pages/router-example.js +24 -1
- package/app/pages/scroll-demo.ts +102 -0
- package/app/providers/ChildComponent.js +8 -2
- package/app/providers/ChildComponent.ts +47 -47
- package/app/providers/GrandchildComponent.js +19 -0
- package/app/providers/ParentComponent.js +5 -1
- package/app/routes/RouterExample.js +23 -1
- package/app/routes/TestResource.js +47 -0
- package/app/services.js +15 -0
- package/auto-imports.d.ts +8 -0
- package/framework/auto-import-preset.js +13 -9
- package/framework/core/component.js +27 -13
- package/framework/core/di.js +187 -0
- package/framework/core/directives.js +40 -96
- package/framework/core/evaluator.js +1 -1
- package/framework/core/reconcile.js +151 -0
- package/framework/core/signal.js +267 -0
- package/framework/core/store.js +322 -81
- package/framework/index.js +5 -3
- package/framework/router/file-router.js +1 -1
- package/framework/router/router.js +113 -21
- package/framework/types/component.d.ts +89 -88
- package/framework/types/di.d.ts +43 -0
- package/framework/types/index.d.ts +32 -22
- package/framework/types/reconcile.d.ts +7 -0
- package/framework/types/router.d.ts +11 -1
- package/framework/types/signal.d.ts +147 -79
- package/framework/types/store.d.ts +66 -8
- package/package.json +1 -1
- package/vite.config.js +7 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
export class InjectionToken {
|
|
2
|
+
constructor(description) {
|
|
3
|
+
this.description = description;
|
|
4
|
+
}
|
|
5
|
+
toString() {
|
|
6
|
+
return `InjectionToken ${this.description}`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isClass(fn) {
|
|
11
|
+
if (typeof fn !== 'function') return false;
|
|
12
|
+
const str = fn.toString();
|
|
13
|
+
if (/^\s*class\s+/.test(str)) return true;
|
|
14
|
+
return fn.prototype !== undefined &&
|
|
15
|
+
fn.prototype.constructor === fn &&
|
|
16
|
+
Object.getOwnPropertyNames(fn.prototype).length > 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class Injector {
|
|
20
|
+
get(token, options = { optional: false }) {
|
|
21
|
+
throw new Error('Not implemented');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class NullInjector extends Injector {
|
|
26
|
+
get(token, options = { optional: false }) {
|
|
27
|
+
if (options.optional) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const tokenName = token?.name || token?.description || token;
|
|
31
|
+
throw new Error(`NullInjectorError: No provider for ${tokenName}!`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const NULL_INJECTOR = new NullInjector();
|
|
36
|
+
|
|
37
|
+
export class EnvironmentInjector extends Injector {
|
|
38
|
+
constructor(providers = [], parent = NULL_INJECTOR) {
|
|
39
|
+
super();
|
|
40
|
+
this.parent = parent;
|
|
41
|
+
this.records = new Map();
|
|
42
|
+
this.instances = new Map();
|
|
43
|
+
this.resolutionStack = new Set();
|
|
44
|
+
this._normalizeProviders(providers);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_normalizeProviders(providers) {
|
|
48
|
+
for (const provider of providers) {
|
|
49
|
+
if (typeof provider === 'function' && isClass(provider)) {
|
|
50
|
+
this.records.set(provider, { useClass: provider });
|
|
51
|
+
} else if (provider && provider.provide) {
|
|
52
|
+
this.records.set(provider.provide, provider);
|
|
53
|
+
} else {
|
|
54
|
+
throw new Error(`Invalid provider configuration: ${provider}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
provide(provider) {
|
|
60
|
+
this._normalizeProviders([provider]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Bubble-up algorithm for resolving hierarchical dependencies
|
|
64
|
+
get(token, options = { optional: false }) {
|
|
65
|
+
if (this.instances.has(token)) {
|
|
66
|
+
return this.instances.get(token);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.records.has(token)) {
|
|
70
|
+
if (this.resolutionStack.has(token)) {
|
|
71
|
+
const path = [...this.resolutionStack, token]
|
|
72
|
+
.map(t => t?.name || t?.description || t)
|
|
73
|
+
.join(' -> ');
|
|
74
|
+
throw new Error(`Circular dependency detected: ${path}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.resolutionStack.add(token);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const record = this.records.get(token);
|
|
81
|
+
const instance = this._instantiate(record);
|
|
82
|
+
this.instances.set(token, instance);
|
|
83
|
+
return instance;
|
|
84
|
+
} finally {
|
|
85
|
+
this.resolutionStack.delete(token);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (options.self === true) {
|
|
90
|
+
if (options.optional === true) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const tokenName = token?.name || token?.description || token;
|
|
94
|
+
throw new Error(`NullInjectorError: No provider found for ${tokenName} locally (self: true)`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.parent.get(token, options);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_instantiate(record) {
|
|
101
|
+
return runInContext(this, () => {
|
|
102
|
+
if (record.useValue !== undefined) {
|
|
103
|
+
return record.useValue;
|
|
104
|
+
}
|
|
105
|
+
if (record.useFactory) {
|
|
106
|
+
return record.useFactory();
|
|
107
|
+
}
|
|
108
|
+
if (record.useExisting) {
|
|
109
|
+
return inject(record.useExisting);
|
|
110
|
+
}
|
|
111
|
+
if (record.useClass) {
|
|
112
|
+
const ClassDef = record.useClass;
|
|
113
|
+
return new ClassDef();
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Invalid provider record configuration for ${record.provide?.name || record.provide}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
createChild(providers = []) {
|
|
120
|
+
return new EnvironmentInjector(providers, this);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
destroy() {
|
|
124
|
+
for (const instance of this.instances.values()) {
|
|
125
|
+
if (instance && typeof instance.destroy === 'function') {
|
|
126
|
+
try {
|
|
127
|
+
instance.destroy();
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('Error during instance destroy:', e);
|
|
130
|
+
}
|
|
131
|
+
} else if (instance && typeof instance.onDestroy === 'function') {
|
|
132
|
+
try {
|
|
133
|
+
instance.onDestroy();
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error('Error during instance onDestroy:', e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this.instances.clear();
|
|
140
|
+
this.records.clear();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let _activeInjector = null;
|
|
145
|
+
|
|
146
|
+
export function runInContext(injector, fn) {
|
|
147
|
+
const previousInjector = _activeInjector;
|
|
148
|
+
_activeInjector = injector;
|
|
149
|
+
try {
|
|
150
|
+
return fn();
|
|
151
|
+
} finally {
|
|
152
|
+
_activeInjector = previousInjector;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function replaceActiveInjector(injector) {
|
|
157
|
+
_activeInjector = injector;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function inject(token, options = {}) {
|
|
161
|
+
if (_activeInjector === null) {
|
|
162
|
+
throw new Error('inject() called outside of an injection context');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const isOptional = options.optional === true;
|
|
166
|
+
const skipSelf = options.skipSelf === true;
|
|
167
|
+
const self = options.self === true;
|
|
168
|
+
|
|
169
|
+
if (skipSelf && self) {
|
|
170
|
+
throw new Error('Cannot combine both skipSelf and self InjectOptions');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const injectorToUse = skipSelf ? _activeInjector.parent : _activeInjector;
|
|
174
|
+
|
|
175
|
+
if (!injectorToUse) {
|
|
176
|
+
if (isOptional) return null;
|
|
177
|
+
throw new Error(`NullInjectorError: No provider found for ${token?.name || token}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return injectorToUse.get(token, options);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const rootInjector = new EnvironmentInjector();
|
|
184
|
+
|
|
185
|
+
export function provideGlobal(provider) {
|
|
186
|
+
rootInjector.provide(provider);
|
|
187
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { signal, effect, batch } from './signal.js'
|
|
2
2
|
import { evaluateExpression } from './evaluator.js'
|
|
3
3
|
import { compileNode } from './compiler.js'
|
|
4
|
+
import { reconcileArrays } from './reconcile.js'
|
|
4
5
|
import { registerDelegatedEvent, createEventHandler, isDelegatedEvent } from './event-delegation.js'
|
|
5
|
-
import { currentRoute } from '../router/router.js'
|
|
6
|
+
import { currentRoute, navigate } from '../router/router.js'
|
|
6
7
|
|
|
7
8
|
const customDirectives = new Map()
|
|
8
9
|
|
|
@@ -165,8 +166,7 @@ export function processFor(el, context, components, effects) {
|
|
|
165
166
|
const templateNode = el.cloneNode(true)
|
|
166
167
|
parent.removeChild(el)
|
|
167
168
|
|
|
168
|
-
let
|
|
169
|
-
let previousOrder = []
|
|
169
|
+
let oldRenderedNodes = []
|
|
170
170
|
|
|
171
171
|
effects.push(effect(() => {
|
|
172
172
|
const rawItems = evaluateExpression(arrayExpr, context)
|
|
@@ -180,9 +180,6 @@ export function processFor(el, context, components, effects) {
|
|
|
180
180
|
items = Object.entries(rawItems).map(([key, value], index) => ({ value, key, rawIndex: index }))
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
const nextCache = new Map()
|
|
184
|
-
const nextOrder = []
|
|
185
|
-
|
|
186
183
|
const newItemsWithKeys = items.map((item, index) => {
|
|
187
184
|
const itemContext = Object.create(context)
|
|
188
185
|
itemContext[itemName] = item.value
|
|
@@ -190,116 +187,54 @@ export function processFor(el, context, components, effects) {
|
|
|
190
187
|
if (indexName) itemContext[indexName] = index
|
|
191
188
|
|
|
192
189
|
const key = keyExpr ? evaluateExpression(keyExpr, itemContext) : item.rawIndex
|
|
193
|
-
return { item, key }
|
|
190
|
+
return { item, key, index }
|
|
194
191
|
})
|
|
195
192
|
|
|
196
193
|
const parentNode = anchor.parentNode
|
|
197
|
-
let currentCursor = anchor.nextSibling
|
|
198
|
-
|
|
199
|
-
const isClear = items.length === 0 && previousOrder.length > 0
|
|
200
|
-
if (isClear) {
|
|
201
|
-
const nodesToRemove = []
|
|
202
|
-
let node = anchor.nextSibling
|
|
203
|
-
while (node) {
|
|
204
|
-
const next = node.nextSibling
|
|
205
|
-
nodesToRemove.push(node)
|
|
206
|
-
node = next
|
|
207
|
-
}
|
|
208
|
-
if (nodesToRemove.length > 0) {
|
|
209
|
-
const fragment = document.createDocumentFragment()
|
|
210
|
-
nodesToRemove.forEach(n => fragment.appendChild(n))
|
|
211
|
-
}
|
|
212
|
-
for (const entry of cache.values()) {
|
|
213
|
-
for (let i = entry.effects.length - 1; i >= 0; i--) {
|
|
214
|
-
const cleanup = entry.effects[i]
|
|
215
|
-
if (typeof cleanup === 'function') cleanup()
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
cache.clear()
|
|
219
|
-
previousOrder = []
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const fragment = document.createDocumentFragment()
|
|
224
|
-
const nodesToInsert = []
|
|
225
|
-
|
|
226
|
-
newItemsWithKeys.forEach(({ item, key }, index) => {
|
|
227
|
-
let entry = cache.get(key)
|
|
228
|
-
|
|
229
|
-
if (entry) {
|
|
230
|
-
batch(() => {
|
|
231
|
-
if (typeof entry.context[itemName] === 'function' && entry.context[itemName].isSignal) {
|
|
232
|
-
entry.context[itemName](item.value)
|
|
233
|
-
}
|
|
234
|
-
if (keyName && typeof entry.context[keyName] === 'function' && entry.context[keyName].isSignal) {
|
|
235
|
-
entry.context[keyName](item.hasOwnProperty('key') ? item.key : item.rawIndex)
|
|
236
|
-
}
|
|
237
|
-
if (indexName && typeof entry.context[indexName] === 'function' && entry.context[indexName].isSignal) {
|
|
238
|
-
entry.context[indexName](index)
|
|
239
|
-
}
|
|
240
|
-
})
|
|
241
194
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
195
|
+
oldRenderedNodes = reconcileArrays(
|
|
196
|
+
parentNode,
|
|
197
|
+
anchor,
|
|
198
|
+
oldRenderedNodes,
|
|
199
|
+
newItemsWithKeys,
|
|
200
|
+
(obj) => obj.key,
|
|
201
|
+
(newItem, index) => {
|
|
249
202
|
const itemContext = Object.create(context)
|
|
250
|
-
itemContext[itemName] = signal(item.value)
|
|
251
|
-
if (keyName) itemContext[keyName] = signal(item.hasOwnProperty('key') ? item.key : item.rawIndex)
|
|
203
|
+
itemContext[itemName] = signal(newItem.item.value)
|
|
204
|
+
if (keyName) itemContext[keyName] = signal(newItem.item.hasOwnProperty('key') ? newItem.item.key : newItem.item.rawIndex)
|
|
252
205
|
if (indexName) itemContext[indexName] = signal(index)
|
|
253
206
|
|
|
254
207
|
const clone = templateNode.cloneNode(true)
|
|
255
208
|
const childEffects = []
|
|
256
209
|
compileNode(clone, itemContext, components, childEffects)
|
|
257
210
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
211
|
+
return { node: clone, effects: childEffects, context: itemContext, key: newItem.key }
|
|
212
|
+
},
|
|
213
|
+
(oldNode, newItem, index) => {
|
|
214
|
+
batch(() => {
|
|
215
|
+
if (typeof oldNode.context[itemName] === 'function' && oldNode.context[itemName].isSignal) {
|
|
216
|
+
oldNode.context[itemName](newItem.item.value)
|
|
217
|
+
}
|
|
218
|
+
if (keyName && typeof oldNode.context[keyName] === 'function' && oldNode.context[keyName].isSignal) {
|
|
219
|
+
oldNode.context[keyName](newItem.item.hasOwnProperty('key') ? newItem.item.key : newItem.item.rawIndex)
|
|
220
|
+
}
|
|
221
|
+
if (indexName && typeof oldNode.context[indexName] === 'function' && oldNode.context[indexName].isSignal) {
|
|
222
|
+
oldNode.context[indexName](index)
|
|
223
|
+
}
|
|
224
|
+
})
|
|
261
225
|
}
|
|
262
|
-
|
|
263
|
-
nextCache.set(key, entry)
|
|
264
|
-
nextOrder.push(entry.node)
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
if (fragment.childNodes.length > 0) {
|
|
268
|
-
parentNode.insertBefore(fragment, currentCursor)
|
|
269
|
-
nodesToInsert.forEach(({ node, effects, context, key }) => {
|
|
270
|
-
nextCache.set(key, { node, effects, context })
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const removedEntries = []
|
|
275
|
-
for (const [_, entry] of cache.entries()) {
|
|
276
|
-
removedEntries.push(entry)
|
|
277
|
-
}
|
|
278
|
-
if (removedEntries.length > 0) {
|
|
279
|
-
const removeFragment = document.createDocumentFragment()
|
|
280
|
-
removedEntries.forEach(entry => {
|
|
281
|
-
for (let i = entry.effects.length - 1; i >= 0; i--) {
|
|
282
|
-
const cleanup = entry.effects[i]
|
|
283
|
-
if (typeof cleanup === 'function') cleanup()
|
|
284
|
-
}
|
|
285
|
-
if (entry.node.parentNode) {
|
|
286
|
-
removeFragment.appendChild(entry.node)
|
|
287
|
-
}
|
|
288
|
-
})
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
cache = nextCache
|
|
292
|
-
previousOrder = nextOrder
|
|
226
|
+
)
|
|
293
227
|
}))
|
|
294
228
|
|
|
295
229
|
effects.push(() => {
|
|
296
|
-
for (const entry of
|
|
230
|
+
for (const entry of oldRenderedNodes) {
|
|
231
|
+
if (!entry) continue
|
|
297
232
|
for (let i = entry.effects.length - 1; i >= 0; i--) {
|
|
298
233
|
const cleanup = entry.effects[i]
|
|
299
234
|
if (typeof cleanup === 'function') cleanup()
|
|
300
235
|
}
|
|
301
236
|
}
|
|
302
|
-
|
|
237
|
+
oldRenderedNodes = []
|
|
303
238
|
})
|
|
304
239
|
|
|
305
240
|
return true
|
|
@@ -861,6 +796,8 @@ export function processLinks(el, effects) {
|
|
|
861
796
|
const href = link.getAttribute('href')
|
|
862
797
|
if (!href) continue
|
|
863
798
|
|
|
799
|
+
link.removeAttribute('f-link')
|
|
800
|
+
|
|
864
801
|
effects.push(effect(() => {
|
|
865
802
|
const currentPath = currentRoute()
|
|
866
803
|
const isActive = currentPath === href || currentPath.startsWith(href + '/')
|
|
@@ -871,5 +808,12 @@ export function processLinks(el, effects) {
|
|
|
871
808
|
link.classList.remove('active')
|
|
872
809
|
}
|
|
873
810
|
}))
|
|
811
|
+
|
|
812
|
+
const clickHandler = (e) => {
|
|
813
|
+
e.preventDefault()
|
|
814
|
+
navigate(href)
|
|
815
|
+
}
|
|
816
|
+
link.addEventListener('click', clickHandler)
|
|
817
|
+
effects.push(() => link.removeEventListener('click', clickHandler))
|
|
874
818
|
}
|
|
875
819
|
}
|
|
@@ -37,7 +37,7 @@ export function evaluateExpression(expr, context, extraContext = {}, unwrapSigna
|
|
|
37
37
|
).join('\n')
|
|
38
38
|
let wrappedExpr = expr
|
|
39
39
|
for (const id of keysToUnwrap) {
|
|
40
|
-
wrappedExpr = wrappedExpr.replace(new RegExp('\\b' + id + '\\b', 'g'), '__' + id)
|
|
40
|
+
wrappedExpr = wrappedExpr.replace(new RegExp('\\b' + id + '\\b(?![\\.\\(])', 'g'), '__' + id)
|
|
41
41
|
}
|
|
42
42
|
body = (unwrapStatements ? unwrapStatements + '\n' : '') + `return ${wrappedExpr}`
|
|
43
43
|
}
|
|
@@ -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
|
+
}
|