canvasengine 2.0.0-beta.36 → 2.0.0-beta.38
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/dist/{DebugRenderer-DDfZuvTR.js → DebugRenderer-DxJSMb9B.js} +2 -2
- package/dist/{DebugRenderer-DDfZuvTR.js.map → DebugRenderer-DxJSMb9B.js.map} +1 -1
- package/dist/components/Canvas.d.ts.map +1 -1
- package/dist/components/Sprite.d.ts +2 -0
- package/dist/components/Sprite.d.ts.map +1 -1
- package/dist/components/types/Spritesheet.d.ts +0 -118
- package/dist/components/types/Spritesheet.d.ts.map +1 -1
- package/dist/directives/GamepadControls.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/engine/signal.d.ts.map +1 -1
- package/dist/{index--faZajmD.js → index-BgNWflRE.js} +2763 -2537
- package/dist/index-BgNWflRE.js.map +1 -0
- package/dist/index.global.js +6 -6
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/utils/GlobalAssetLoader.d.ts +141 -0
- package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Canvas.ts +3 -0
- package/src/components/Sprite.ts +82 -16
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/GamepadControls.ts +0 -7
- package/src/engine/reactive.ts +343 -242
- package/src/engine/signal.ts +8 -2
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/dist/index--faZajmD.js.map +0 -1
package/src/engine/reactive.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
bufferTime,
|
|
15
15
|
filter,
|
|
16
16
|
throttleTime,
|
|
17
|
+
combineLatest,
|
|
17
18
|
} from "rxjs";
|
|
18
19
|
import { ComponentInstance } from "../components/DisplayObject";
|
|
19
20
|
import { Directive, applyDirective } from "./directive";
|
|
@@ -101,7 +102,15 @@ function destroyElement(element: Element | Element[]) {
|
|
|
101
102
|
element.componentInstance.onDestroy(element.parent as any, () => {
|
|
102
103
|
element.propSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
103
104
|
element.effectSubscriptions?.forEach((sub) => sub.unsubscribe());
|
|
104
|
-
element.effectUnmounts?.forEach((fn) =>
|
|
105
|
+
element.effectUnmounts?.forEach((fn) => {
|
|
106
|
+
if (isPromise(fn)) {
|
|
107
|
+
(fn as unknown as Promise<any>).then((retFn) => {
|
|
108
|
+
retFn?.();
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
fn?.();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
105
114
|
});
|
|
106
115
|
} else {
|
|
107
116
|
// If componentInstance is undefined or doesn't have onDestroy, still clean up subscriptions
|
|
@@ -173,8 +182,8 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
173
182
|
instance.onUpdate?.(
|
|
174
183
|
path == ""
|
|
175
184
|
? {
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
[key]: value,
|
|
186
|
+
}
|
|
178
187
|
: set({}, path + "." + key, value)
|
|
179
188
|
);
|
|
180
189
|
})
|
|
@@ -209,9 +218,79 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Checks if all dependencies are ready (not undefined).
|
|
223
|
+
* Handles signals synchronously and promises asynchronously.
|
|
224
|
+
* For reactive signals, sets up subscriptions to mount when all become ready.
|
|
225
|
+
*
|
|
226
|
+
* @param deps - Array of signals, promises, or direct values
|
|
227
|
+
* @returns Promise<boolean> - true if all dependencies are ready
|
|
228
|
+
*/
|
|
229
|
+
async function checkDependencies(
|
|
230
|
+
deps: any[]
|
|
231
|
+
): Promise<boolean> {
|
|
232
|
+
const values = await Promise.all(
|
|
233
|
+
deps.map(async (dep) => {
|
|
234
|
+
if (isSignal(dep)) {
|
|
235
|
+
return dep(); // Read current signal value
|
|
236
|
+
} else if (isPromise(dep)) {
|
|
237
|
+
return await dep; // Await promise resolution
|
|
238
|
+
}
|
|
239
|
+
return dep; // Direct value
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
return values.every((v) => v !== undefined);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Sets up subscriptions to reactive signal dependencies.
|
|
247
|
+
* When all signals become defined, mounts the component.
|
|
248
|
+
*/
|
|
249
|
+
function setupDependencySubscriptions(
|
|
250
|
+
parent: Element,
|
|
251
|
+
element: Element,
|
|
252
|
+
deps: any[],
|
|
253
|
+
index?: number
|
|
254
|
+
) {
|
|
255
|
+
const signalDeps = deps.filter((dep) => isSignal(dep));
|
|
256
|
+
const promiseDeps = deps.filter((dep) => isPromise(dep));
|
|
257
|
+
|
|
258
|
+
if (signalDeps.length === 0) {
|
|
259
|
+
// No reactive signals, nothing to subscribe to
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Create observables from signals
|
|
264
|
+
const signalObservables = signalDeps.map((sig) => sig.observable);
|
|
265
|
+
|
|
266
|
+
// Combine all signal observables
|
|
267
|
+
const subscription = combineLatest(signalObservables).subscribe(
|
|
268
|
+
async () => {
|
|
269
|
+
// Check if all dependencies are now ready
|
|
270
|
+
const allReady = await checkDependencies(deps);
|
|
271
|
+
if (allReady) {
|
|
272
|
+
// Unsubscribe - we only need to mount once
|
|
273
|
+
subscription.unsubscribe();
|
|
274
|
+
// Remove from subscriptions
|
|
275
|
+
const idx = element.propSubscriptions.indexOf(subscription);
|
|
276
|
+
if (idx > -1) {
|
|
277
|
+
element.propSubscriptions.splice(idx, 1);
|
|
278
|
+
}
|
|
279
|
+
// Now mount the component
|
|
280
|
+
performMount(parent, element, index);
|
|
281
|
+
propagateContext(element);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Store subscription for cleanup
|
|
287
|
+
element.propSubscriptions.push(subscription);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Performs the actual mounting of the component.
|
|
292
|
+
*/
|
|
293
|
+
function performMount(parent: Element, element: Element, index?: number) {
|
|
215
294
|
element.componentInstance.onMount?.(element, index);
|
|
216
295
|
for (let name in element.directives) {
|
|
217
296
|
element.directives[name].onMount?.(element);
|
|
@@ -219,6 +298,24 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
219
298
|
element.effectMounts.forEach((fn: any) => {
|
|
220
299
|
element.effectUnmounts.push(fn(element));
|
|
221
300
|
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function onMount(parent: Element, element: Element, index?: number) {
|
|
304
|
+
element.props.context = parent.props.context;
|
|
305
|
+
element.parent = parent;
|
|
306
|
+
|
|
307
|
+
// Check dependencies before mounting
|
|
308
|
+
if (element.props.dependencies && Array.isArray(element.props.dependencies)) {
|
|
309
|
+
const deps = element.props.dependencies;
|
|
310
|
+
const ready = await checkDependencies(deps);
|
|
311
|
+
if (!ready) {
|
|
312
|
+
// Set up subscriptions for reactive signals to trigger mount later
|
|
313
|
+
setupDependencySubscriptions(parent, element, deps, index);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
performMount(parent, element, index);
|
|
222
319
|
};
|
|
223
320
|
|
|
224
321
|
async function propagateContext(element) {
|
|
@@ -229,19 +326,19 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
229
326
|
}
|
|
230
327
|
else {
|
|
231
328
|
await new Promise((resolve) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
329
|
+
let lastElement = null
|
|
330
|
+
element.propSubscriptions.push(element.propObservables.attach.observable.subscribe(async (args) => {
|
|
331
|
+
const value = args?.value ?? args
|
|
332
|
+
if (!value) {
|
|
333
|
+
throw new Error(`attach in ${element.tag} is undefined or null, add a component`)
|
|
334
|
+
}
|
|
335
|
+
if (lastElement) {
|
|
336
|
+
destroyElement(lastElement)
|
|
337
|
+
}
|
|
338
|
+
lastElement = value
|
|
339
|
+
await createElement(element, value)
|
|
340
|
+
resolve(undefined)
|
|
341
|
+
}))
|
|
245
342
|
})
|
|
246
343
|
}
|
|
247
344
|
}
|
|
@@ -254,39 +351,39 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
254
351
|
}
|
|
255
352
|
};
|
|
256
353
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Creates and mounts a child element to a parent element.
|
|
356
|
+
* Handles different types of children: Elements, Promises resolving to Elements, and Observables.
|
|
357
|
+
*
|
|
358
|
+
* @description This function is designed to handle reactive child components that can be:
|
|
359
|
+
* - Direct Element instances
|
|
360
|
+
* - Promises that resolve to Elements (for async components)
|
|
361
|
+
* - Observables that emit Elements, arrays of Elements, or FlowObservable results
|
|
362
|
+
* - Nested observables within arrays or FlowObservable results (handled recursively)
|
|
363
|
+
*
|
|
364
|
+
* For Observables, it subscribes to the stream and automatically mounts/unmounts elements
|
|
365
|
+
* as they are emitted. The function handles nested observables recursively, ensuring that
|
|
366
|
+
* observables within arrays or FlowObservable results are also properly subscribed to.
|
|
367
|
+
* All subscriptions are stored in the parent's effectSubscriptions for automatic cleanup.
|
|
368
|
+
*
|
|
369
|
+
* @param {Element} parent - The parent element to mount the child to
|
|
370
|
+
* @param {Element | Observable<any> | Promise<Element>} child - The child to create and mount
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* // Direct element
|
|
375
|
+
* await createElement(parent, childElement);
|
|
376
|
+
*
|
|
377
|
+
* // Observable of elements (from cond, loop, etc.)
|
|
378
|
+
* await createElement(parent, cond(signal(visible), () => h(Container)));
|
|
379
|
+
*
|
|
380
|
+
* // Observable that emits arrays containing other observables
|
|
381
|
+
* await createElement(parent, observableOfObservables);
|
|
382
|
+
*
|
|
383
|
+
* // Promise resolving to element
|
|
384
|
+
* await createElement(parent, import('./MyComponent').then(mod => h(mod.default)));
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
290
387
|
async function createElement(parent: Element, child: Element | Observable<any> | Promise<Element>) {
|
|
291
388
|
if (isPromise(child)) {
|
|
292
389
|
child = await child;
|
|
@@ -305,62 +402,62 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
305
402
|
elements: Element[];
|
|
306
403
|
prev?: Element;
|
|
307
404
|
} = value;
|
|
308
|
-
|
|
405
|
+
|
|
309
406
|
const components = comp.filter((c) => c !== null);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
407
|
+
if (prev) {
|
|
408
|
+
components.forEach(async (c) => {
|
|
409
|
+
const index = parent.props.children.indexOf(prev.props.key);
|
|
410
|
+
if (c instanceof Observable) {
|
|
411
|
+
// Handle observable component recursively
|
|
412
|
+
await createElement(parent, c);
|
|
413
|
+
} else if (isElement(c)) {
|
|
414
|
+
onMount(parent, c, index + 1);
|
|
415
|
+
propagateContext(c);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
components.forEach(async (component) => {
|
|
421
|
+
if (!Array.isArray(component)) {
|
|
422
|
+
if (component instanceof Observable) {
|
|
423
|
+
// Handle observable component recursively
|
|
424
|
+
await createElement(parent, component);
|
|
425
|
+
} else if (isElement(component)) {
|
|
426
|
+
onMount(parent, component);
|
|
427
|
+
propagateContext(component);
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
component.forEach(async (comp) => {
|
|
431
|
+
if (comp instanceof Observable) {
|
|
432
|
+
// Handle observable component recursively
|
|
433
|
+
await createElement(parent, comp);
|
|
434
|
+
} else if (isElement(comp)) {
|
|
435
|
+
onMount(parent, comp);
|
|
436
|
+
propagateContext(comp);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
});
|
|
344
441
|
} else if (isElement(value)) {
|
|
345
442
|
// Handle direct Element emission
|
|
346
443
|
onMount(parent, value);
|
|
347
444
|
propagateContext(value);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
445
|
+
} else if (Array.isArray(value)) {
|
|
446
|
+
// Handle array of elements (which can also be observables)
|
|
447
|
+
value.forEach(async (element) => {
|
|
448
|
+
if (element instanceof Observable) {
|
|
449
|
+
// Handle observable element recursively
|
|
450
|
+
await createElement(parent, element);
|
|
451
|
+
} else if (isElement(element)) {
|
|
452
|
+
onMount(parent, element);
|
|
453
|
+
propagateContext(element);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
360
457
|
elementsListen.next(undefined);
|
|
361
458
|
}
|
|
362
459
|
);
|
|
363
|
-
|
|
460
|
+
|
|
364
461
|
// Store subscription for cleanup
|
|
365
462
|
parent.effectSubscriptions.push(subscription);
|
|
366
463
|
} else if (isElement(child)) {
|
|
@@ -397,170 +494,174 @@ export function loop<T>(
|
|
|
397
494
|
let elementMap = new Map<string | number, Element>();
|
|
398
495
|
let isFirstSubscription = true;
|
|
399
496
|
|
|
400
|
-
const isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
|
|
497
|
+
const isArraySignal = (signal: any): signal is WritableArraySignal<T[]> =>
|
|
401
498
|
Array.isArray(signal());
|
|
402
499
|
|
|
403
500
|
return new Observable<FlowResult>(subscriber => {
|
|
404
501
|
const subscription = isArraySignal(itemsSubject)
|
|
405
502
|
? itemsSubject.observable.subscribe(change => {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
subscriber.next({
|
|
423
|
-
elements: [...elements]
|
|
503
|
+
if (isFirstSubscription) {
|
|
504
|
+
isFirstSubscription = false;
|
|
505
|
+
elements.forEach(el => el.destroy());
|
|
506
|
+
elements = [];
|
|
507
|
+
elementMap.clear();
|
|
508
|
+
|
|
509
|
+
const items = itemsSubject();
|
|
510
|
+
if (items) {
|
|
511
|
+
items.forEach((item, index) => {
|
|
512
|
+
const element = createElementFn(item, index);
|
|
513
|
+
if (element) {
|
|
514
|
+
elements.push(element);
|
|
515
|
+
elementMap.set(index, element);
|
|
516
|
+
}
|
|
424
517
|
});
|
|
425
|
-
return;
|
|
426
518
|
}
|
|
519
|
+
subscriber.next({
|
|
520
|
+
elements: [...elements]
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
427
524
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
} else if (change.type === 'add' && change.index !== undefined) {
|
|
444
|
-
const newElements = change.items.map((item, i) => {
|
|
445
|
-
const element = createElementFn(item as T, change.index! + i);
|
|
525
|
+
// Handle computed signals that emit array values directly (not ArrayChange objects)
|
|
526
|
+
// When a computed emits, `change` is the array itself, not an object with `type`
|
|
527
|
+
const isDirectArrayChange = Array.isArray(change) || (change && typeof change === 'object' && !('type' in change));
|
|
528
|
+
|
|
529
|
+
if (change.type === 'init' || change.type === 'reset' || isDirectArrayChange) {
|
|
530
|
+
elements.forEach(el => destroyElement(el));
|
|
531
|
+
elements = [];
|
|
532
|
+
elementMap.clear();
|
|
533
|
+
|
|
534
|
+
const items = itemsSubject();
|
|
535
|
+
if (items) {
|
|
536
|
+
items.forEach((item, index) => {
|
|
537
|
+
const element = createElementFn(item, index);
|
|
446
538
|
if (element) {
|
|
447
|
-
|
|
539
|
+
elements.push(element);
|
|
540
|
+
elementMap.set(index, element);
|
|
448
541
|
}
|
|
449
|
-
return element;
|
|
450
|
-
}).filter((el): el is Element => el !== null);
|
|
451
|
-
|
|
452
|
-
elements.splice(change.index, 0, ...newElements);
|
|
453
|
-
} else if (change.type === 'remove' && change.index !== undefined) {
|
|
454
|
-
const removed = elements.splice(change.index, 1);
|
|
455
|
-
removed.forEach(el => {
|
|
456
|
-
destroyElement(el)
|
|
457
|
-
elementMap.delete(change.index!);
|
|
458
542
|
});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
543
|
+
}
|
|
544
|
+
} else if (change.type === 'add' && change.index !== undefined) {
|
|
545
|
+
const newElements = change.items.map((item, i) => {
|
|
546
|
+
const element = createElementFn(item as T, change.index! + i);
|
|
547
|
+
if (element) {
|
|
548
|
+
elementMap.set(change.index! + i, element);
|
|
549
|
+
}
|
|
550
|
+
return element;
|
|
551
|
+
}).filter((el): el is Element => el !== null);
|
|
552
|
+
|
|
553
|
+
elements.splice(change.index, 0, ...newElements);
|
|
554
|
+
} else if (change.type === 'remove' && change.index !== undefined) {
|
|
555
|
+
const removed = elements.splice(change.index, 1);
|
|
556
|
+
removed.forEach(el => {
|
|
557
|
+
destroyElement(el)
|
|
558
|
+
elementMap.delete(change.index!);
|
|
559
|
+
});
|
|
560
|
+
} else if (change.type === 'update' && change.index !== undefined && change.items.length === 1) {
|
|
561
|
+
const index = change.index;
|
|
562
|
+
const newItem = change.items[0];
|
|
563
|
+
|
|
564
|
+
// Check if the previous item at this index was effectively undefined or non-existent
|
|
565
|
+
if (index >= elements.length || elements[index] === undefined || !elementMap.has(index)) {
|
|
566
|
+
// Treat as add operation
|
|
567
|
+
const newElement = createElementFn(newItem as T, index);
|
|
568
|
+
if (newElement) {
|
|
569
|
+
elements.splice(index, 0, newElement); // Insert at the correct index
|
|
570
|
+
elementMap.set(index, newElement);
|
|
571
|
+
// Adjust indices in elementMap for subsequent elements might be needed if map relied on exact indices
|
|
572
|
+
// This simple implementation assumes keys are stable or createElementFn handles context correctly
|
|
475
573
|
} else {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
574
|
+
console.warn(`Element creation returned null for index ${index} during add-like update.`);
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
// Treat as a standard update operation
|
|
578
|
+
const oldElement = elements[index];
|
|
579
|
+
destroyElement(oldElement)
|
|
580
|
+
const newElement = createElementFn(newItem as T, index);
|
|
581
|
+
if (newElement) {
|
|
582
|
+
elements[index] = newElement;
|
|
583
|
+
elementMap.set(index, newElement);
|
|
584
|
+
} else {
|
|
585
|
+
// Handle case where new element creation returns null
|
|
586
|
+
elements.splice(index, 1);
|
|
587
|
+
elementMap.delete(index);
|
|
488
588
|
}
|
|
489
589
|
}
|
|
590
|
+
}
|
|
490
591
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
592
|
+
subscriber.next({
|
|
593
|
+
elements: [...elements] // Create a new array to ensure change detection
|
|
594
|
+
});
|
|
595
|
+
})
|
|
495
596
|
: (itemsSubject as WritableObjectSignal<T>).observable.subscribe(change => {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
subscriber.next({
|
|
514
|
-
elements: [...elements]
|
|
597
|
+
const key = change.key as string | number
|
|
598
|
+
if (isFirstSubscription) {
|
|
599
|
+
isFirstSubscription = false;
|
|
600
|
+
elements.forEach(el => destroyElement(el));
|
|
601
|
+
elements = [];
|
|
602
|
+
elementMap.clear();
|
|
603
|
+
|
|
604
|
+
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
605
|
+
if (items) {
|
|
606
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
607
|
+
const element = createElementFn(value, key);
|
|
608
|
+
if (element) {
|
|
609
|
+
elements.push(element);
|
|
610
|
+
elementMap.set(key, element);
|
|
611
|
+
}
|
|
515
612
|
});
|
|
516
|
-
return;
|
|
517
613
|
}
|
|
614
|
+
subscriber.next({
|
|
615
|
+
elements: [...elements]
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
518
619
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
} else if (change.type === 'add' && change.key && change.value !== undefined) {
|
|
535
|
-
const element = createElementFn(change.value as T, key);
|
|
536
|
-
if (element) {
|
|
537
|
-
elements.push(element);
|
|
538
|
-
elementMap.set(key, element);
|
|
539
|
-
}
|
|
540
|
-
} else if (change.type === 'remove' && change.key) {
|
|
541
|
-
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
542
|
-
if (index !== -1) {
|
|
543
|
-
const [removed] = elements.splice(index, 1);
|
|
544
|
-
destroyElement(removed)
|
|
545
|
-
elementMap.delete(key);
|
|
546
|
-
}
|
|
547
|
-
} else if (change.type === 'update' && change.key && change.value !== undefined) {
|
|
548
|
-
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
549
|
-
if (index !== -1) {
|
|
550
|
-
const oldElement = elements[index];
|
|
551
|
-
destroyElement(oldElement)
|
|
552
|
-
const newElement = createElementFn(change.value as T, key);
|
|
553
|
-
if (newElement) {
|
|
554
|
-
elements[index] = newElement;
|
|
555
|
-
elementMap.set(key, newElement);
|
|
620
|
+
if (change.type === 'init' || change.type === 'reset') {
|
|
621
|
+
elements.forEach(el => destroyElement(el));
|
|
622
|
+
elements = [];
|
|
623
|
+
elementMap.clear();
|
|
624
|
+
|
|
625
|
+
const items = (itemsSubject as WritableObjectSignal<T>)();
|
|
626
|
+
if (items) {
|
|
627
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
628
|
+
const element = createElementFn(value, key);
|
|
629
|
+
if (element) {
|
|
630
|
+
elements.push(element);
|
|
631
|
+
elementMap.set(key, element);
|
|
556
632
|
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
} else if (change.type === 'add' && change.key && change.value !== undefined) {
|
|
636
|
+
const element = createElementFn(change.value as T, key);
|
|
637
|
+
if (element) {
|
|
638
|
+
elements.push(element);
|
|
639
|
+
elementMap.set(key, element);
|
|
640
|
+
}
|
|
641
|
+
} else if (change.type === 'remove' && change.key) {
|
|
642
|
+
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
643
|
+
if (index !== -1) {
|
|
644
|
+
const [removed] = elements.splice(index, 1);
|
|
645
|
+
destroyElement(removed)
|
|
646
|
+
elementMap.delete(key);
|
|
647
|
+
}
|
|
648
|
+
} else if (change.type === 'update' && change.key && change.value !== undefined) {
|
|
649
|
+
const index = elements.findIndex(el => elementMap.get(key) === el);
|
|
650
|
+
if (index !== -1) {
|
|
651
|
+
const oldElement = elements[index];
|
|
652
|
+
destroyElement(oldElement)
|
|
653
|
+
const newElement = createElementFn(change.value as T, key);
|
|
654
|
+
if (newElement) {
|
|
655
|
+
elements[index] = newElement;
|
|
656
|
+
elementMap.set(key, newElement);
|
|
557
657
|
}
|
|
558
658
|
}
|
|
659
|
+
}
|
|
559
660
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
});
|
|
661
|
+
subscriber.next({
|
|
662
|
+
elements: [...elements] // Create a new array to ensure change detection
|
|
563
663
|
});
|
|
664
|
+
});
|
|
564
665
|
|
|
565
666
|
return subscription;
|
|
566
667
|
});
|
|
@@ -652,7 +753,7 @@ export function cond(
|
|
|
652
753
|
];
|
|
653
754
|
|
|
654
755
|
// All conditions are now signals, so we always use the reactive path
|
|
655
|
-
return new Observable<{elements: Element[], type?: "init" | "remove"}>(subscriber => {
|
|
756
|
+
return new Observable<{ elements: Element[], type?: "init" | "remove" }>(subscriber => {
|
|
656
757
|
const subscriptions: Subscription[] = [];
|
|
657
758
|
|
|
658
759
|
const evaluateConditions = () => {
|
|
@@ -661,7 +762,7 @@ export function cond(
|
|
|
661
762
|
for (let i = 0; i < allConditions.length; i++) {
|
|
662
763
|
const condition = allConditions[i].condition;
|
|
663
764
|
const conditionValue = condition();
|
|
664
|
-
|
|
765
|
+
|
|
665
766
|
if (conditionValue) {
|
|
666
767
|
matchingIndex = i;
|
|
667
768
|
break;
|