@xaendar/core 0.6.20 → 0.7.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/dist/xaendar-core.es.d.ts +100 -32
- package/dist/xaendar-core.es.js +197 -82
- package/package.json +3 -3
|
@@ -85,7 +85,7 @@ export declare type BaseWebComponentConstructor = Constructor<BaseWebComponent,
|
|
|
85
85
|
*/
|
|
86
86
|
declare type Block = {
|
|
87
87
|
condition?: NoArgsFunction<boolean>;
|
|
88
|
-
block: Function_2<[HTMLElement, Context], Context>;
|
|
88
|
+
block: Function_2<[HTMLElement, Context, Node | null], Context>;
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -131,7 +131,16 @@ export declare class Context {
|
|
|
131
131
|
*/
|
|
132
132
|
private _children;
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
134
|
+
* Map containing the run time values of variables defines during the run time execution
|
|
135
|
+
* (e.g. {@for} variables: 'index', 'item', etc...)
|
|
136
|
+
*/
|
|
137
|
+
private _variables;
|
|
138
|
+
/**
|
|
139
|
+
* DOM nodes directly mounted into this scope via {@link mountNode} (does not
|
|
140
|
+
* include nodes owned by child contexts). Used to determine, for a given
|
|
141
|
+
* context, which DOM nodes belong to it — for example so `_for`'s keyed
|
|
142
|
+
* diffing can move a reused item's nodes to a new position without
|
|
143
|
+
* recreating them.
|
|
135
144
|
*/
|
|
136
145
|
private _nodes;
|
|
137
146
|
/**
|
|
@@ -144,10 +153,24 @@ export declare class Context {
|
|
|
144
153
|
* Creates a new scope context.
|
|
145
154
|
*
|
|
146
155
|
* @param _root - Web component reference used to resolve property and method bindings.
|
|
147
|
-
* @param _parent - Optional parent context representing the enclosing scope.
|
|
148
|
-
* @param _identifiers - Named identifier bindings declared in this scope.
|
|
149
156
|
*/
|
|
150
157
|
constructor(_root: BaseWebComponent, _parent: Context);
|
|
158
|
+
/**
|
|
159
|
+
* Registers a new identifier name in this scope.
|
|
160
|
+
*
|
|
161
|
+
* @param name - The identifier name to register.
|
|
162
|
+
* @throws When an identifier with the same name is already declared in this scope.
|
|
163
|
+
*/
|
|
164
|
+
addIdentifier(name: string, value: unknown): void;
|
|
165
|
+
removeIdentifier(name: string): void;
|
|
166
|
+
/**
|
|
167
|
+
* Returns `true` if an identifier with the given name is declared in this
|
|
168
|
+
* scope or any of its ancestor scopes.
|
|
169
|
+
*
|
|
170
|
+
* @param name - The identifier name to look up.
|
|
171
|
+
* @returns `true` if the identifier exists in the scope chain, `false` otherwise.
|
|
172
|
+
*/
|
|
173
|
+
get(name: string): unknown;
|
|
151
174
|
/**
|
|
152
175
|
* Returns the event handler method bound to the root component instance.
|
|
153
176
|
*
|
|
@@ -168,7 +191,7 @@ export declare class Context {
|
|
|
168
191
|
*/
|
|
169
192
|
removeChild(context: Context): void;
|
|
170
193
|
/**
|
|
171
|
-
* Registers a DOM node as owned by this context.
|
|
194
|
+
* Registers a DOM node as directly owned by this context.
|
|
172
195
|
*
|
|
173
196
|
* @param node - The DOM node to track.
|
|
174
197
|
*/
|
|
@@ -179,6 +202,13 @@ export declare class Context {
|
|
|
179
202
|
* @param nodeToRemove - The DOM node to untrack.
|
|
180
203
|
*/
|
|
181
204
|
removeNode(nodeToRemove: Node): void;
|
|
205
|
+
/**
|
|
206
|
+
* Returns the DOM nodes directly owned by this context (not recursively
|
|
207
|
+
* including nodes owned by child contexts), in mount order. Used by
|
|
208
|
+
* `_for`'s keyed diffing to locate a reused item's nodes so they can be
|
|
209
|
+
* repositioned with `insertBefore` instead of being destroyed and recreated.
|
|
210
|
+
*/
|
|
211
|
+
getNodes(): ReadonlyArray<Node>;
|
|
182
212
|
/**
|
|
183
213
|
* Registers one or more cleanup functions to be called when this context is destroyed.
|
|
184
214
|
*
|
|
@@ -198,6 +228,25 @@ export declare class Context {
|
|
|
198
228
|
unlisten(): void;
|
|
199
229
|
}
|
|
200
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Creates a Comment node used as a positional placeholder for dynamic content
|
|
233
|
+
* (`_if`, `_for`). It is inserted immediately, in order, and stays in place for
|
|
234
|
+
* the entire lifetime of the given `context`: any future insertion of dynamic
|
|
235
|
+
* content is done via `insertBefore(node, anchor)`, guaranteeing the correct
|
|
236
|
+
* position even when sibling constructs update independently and asynchronously.
|
|
237
|
+
*
|
|
238
|
+
* @param label - Textual label for the comment, useful for debugging to visually
|
|
239
|
+
* distinguish in the DOM which construct (if/for) generated the anchor.
|
|
240
|
+
* @param parentNode - The parent HTML element the anchor is inserted into.
|
|
241
|
+
* @param context - The Context the anchor is bound to: it determines the anchor's
|
|
242
|
+
* lifecycle, since it will be removed from the DOM when this context is destroyed.
|
|
243
|
+
* @param referenceNode - The node to insert the anchor relative to (same semantics
|
|
244
|
+
* as `Node.insertBefore`). If `null`, the anchor is appended at the end of `parentNode`.
|
|
245
|
+
* @returns The created Comment node, to be used as the reference point for future
|
|
246
|
+
* insertions of dynamic content via `insertBefore(node, anchor)`.
|
|
247
|
+
*/
|
|
248
|
+
export declare function createAnchor(label: string, parentNode: HTMLElement, context: Context, referenceNode?: Comment | null): Comment;
|
|
249
|
+
|
|
201
250
|
/**
|
|
202
251
|
* Runs a side-effectful function and automatically re-runs it whenever any
|
|
203
252
|
* Signal read during its execution changes.
|
|
@@ -267,16 +316,27 @@ export { Event_2 as Event }
|
|
|
267
316
|
export declare type EventOptions = Beautify<RequireOne<Omit<CustomEventInit, 'detail'>>>;
|
|
268
317
|
|
|
269
318
|
/**
|
|
270
|
-
* Reactively iterates over a list of items
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
319
|
+
* Reactively iterates over a list of items. Items are matched across re-runs
|
|
320
|
+
* by the key produced by `trackExpression`:
|
|
321
|
+
* - key sopravvive → il Context e l'intero sottoalbero (stato, subscription,
|
|
322
|
+
* richieste pendenti) vengono riusati SEMPRE, indipendentemente da un
|
|
323
|
+
* eventuale cambio di indice: si spostano solo i nodi DOM, e se il body usa
|
|
324
|
+
* $index/$first/$last/$even/$odd questi vengono aggiornati in-place tramite
|
|
325
|
+
* `update`, senza ricreare nulla.
|
|
326
|
+
* - key sparita → il Context viene distrutto.
|
|
327
|
+
* - key nuova → il Context viene creato.
|
|
328
|
+
*
|
|
329
|
+
* @param forFn - Callback invocata per creare un nuovo item. Deve ritornare
|
|
330
|
+
* sia il Context che possiede i nodi, sia (opzionalmente) una funzione
|
|
331
|
+
* `update` per aggiornare le variabili implicite in-place quando l'item
|
|
332
|
+
* viene riusato a un indice diverso.
|
|
278
333
|
*/
|
|
279
|
-
export declare function _for(parentNode: HTMLElement, parentContext: Context, condition: () => unknown[], trackExpression: Function_2<[unknown],
|
|
334
|
+
export declare function _for(parentNode: HTMLElement, parentContext: Context, condition: () => unknown[], trackExpression: Function_2<[unknown], ForKey>, forFn: Function_2<[HTMLElement, Context, unknown[], number, Node | null], {
|
|
335
|
+
context: Context;
|
|
336
|
+
update?: (newIndex: number, items: unknown[]) => void;
|
|
337
|
+
}>): void;
|
|
338
|
+
|
|
339
|
+
declare type ForKey = string | number;
|
|
280
340
|
|
|
281
341
|
/**
|
|
282
342
|
* Creates a reactive conditional structure from a list of branches.
|
|
@@ -294,9 +354,6 @@ export declare function _for(parentNode: HTMLElement, parentContext: Context, co
|
|
|
294
354
|
*
|
|
295
355
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
296
356
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
297
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
298
|
-
* Mutated in place: the created effect and branch functions are added to and
|
|
299
|
-
* removed from this array.
|
|
300
357
|
* @param blocks - Ordered list of conditional branches to evaluate.
|
|
301
358
|
*/
|
|
302
359
|
export declare function _if(parentNode: HTMLElement, parentContext: Context, blocks: Block[]): void;
|
|
@@ -367,34 +424,47 @@ export declare type InputSignalOptions<ActualValue = unknown, IncomingValue = Ac
|
|
|
367
424
|
|
|
368
425
|
/**
|
|
369
426
|
* Builds a record of iteration context variables for a given index in the loop.
|
|
370
|
-
*
|
|
427
|
+
* `item` is a plain value (identity-stable across moves thanks to the key),
|
|
428
|
+
* while `$index`/`$first`/`$last`/`$even`/`$odd` are signals: when an item is
|
|
429
|
+
* moved to a different position in the array, `update()` writes the new
|
|
430
|
+
* values into these signals in place, instead of recreating the item's
|
|
431
|
+
* template output. Anything bound to these variables re-runs reactively;
|
|
432
|
+
* everything else in the item's subtree (state, subscriptions, pending
|
|
433
|
+
* requests) is left completely untouched.
|
|
371
434
|
*
|
|
372
435
|
* @param items - The full array being iterated.
|
|
373
436
|
* @param index - The current iteration index.
|
|
374
|
-
* @param itemName - The identifier to reference the i-th item during iteration
|
|
375
|
-
* @param aliases - Aliases for implicit variables defined in the `
|
|
376
|
-
* @returns A
|
|
437
|
+
* @param itemName - The identifier to reference the i-th item during iteration.
|
|
438
|
+
* @param aliases - Aliases for implicit variables defined in the `@for` loop.
|
|
439
|
+
* @returns A handle exposing the resolved variables and an `update` function.
|
|
377
440
|
*/
|
|
378
|
-
export declare function _iterationVariables(items: unknown[], index: number, itemName: string, aliases: {
|
|
441
|
+
export declare function _iterationVariables(context: Context, items: unknown[], index: number, itemName: string, aliases: {
|
|
379
442
|
$index: string;
|
|
380
443
|
$first: string;
|
|
381
444
|
$last: string;
|
|
382
445
|
$even: string;
|
|
383
446
|
$odd: string;
|
|
384
|
-
}):
|
|
447
|
+
}): IterationVariablesHandle;
|
|
448
|
+
|
|
449
|
+
declare type IterationVariablesHandle = {
|
|
450
|
+
vars: Record<string, unknown>;
|
|
451
|
+
update: (newIndex: number, items: unknown[]) => void;
|
|
452
|
+
};
|
|
385
453
|
|
|
386
454
|
/**
|
|
387
455
|
* Mounts a node into the DOM and binds it to the context lifecycle.
|
|
388
456
|
*
|
|
389
|
-
* Registers the node with the context,
|
|
390
|
-
* schedules a cleanup listener that removes the node
|
|
391
|
-
* and the DOM when the context is destroyed.
|
|
457
|
+
* Registers the node with the context, inserts it into `parentNode` at the
|
|
458
|
+
* given position, and schedules a cleanup listener that removes the node
|
|
459
|
+
* from both the context and the DOM when the context is destroyed.
|
|
392
460
|
*
|
|
393
461
|
* @param node - The node to mount.
|
|
394
|
-
* @param parentNode - The parent HTML element to
|
|
462
|
+
* @param parentNode - The parent HTML element to insert the node into.
|
|
395
463
|
* @param context - The current template execution scope that owns the node's lifecycle.
|
|
464
|
+
* @param referenceNode - The node to insert relative to (same semantics
|
|
465
|
+
* as `Node.insertBefore`). If `null`, the node is appended at the end of `parentNode`.
|
|
396
466
|
*/
|
|
397
|
-
export declare function mountNode(node: Node, parentNode: HTMLElement, context: Context): void;
|
|
467
|
+
export declare function mountNode(node: Node, parentNode: HTMLElement, context: Context, referenceNode?: Comment | null): void;
|
|
398
468
|
|
|
399
469
|
/**
|
|
400
470
|
* Represents the output type returned by an `@Event` decorator in a web component.
|
|
@@ -485,7 +555,7 @@ declare type PropertyDecoratorOptionsWithRequired<ActualValue = unknown, Incomin
|
|
|
485
555
|
* @param events - List of event listener descriptors to attach to the element.
|
|
486
556
|
* @returns The newly created HTML element.
|
|
487
557
|
*/
|
|
488
|
-
export declare function _renderElement(parentNode: HTMLElement, context: Context, tagName: string, attributes: RenderElementAttribute[], events: RenderElementEvent[]): HTMLElement;
|
|
558
|
+
export declare function _renderElement(parentNode: HTMLElement, context: Context, anchor: Comment | null, tagName: string, attributes: RenderElementAttribute[], events: RenderElementEvent[]): HTMLElement;
|
|
489
559
|
|
|
490
560
|
/**
|
|
491
561
|
* Describes a single HTML attribute to be applied to a rendered DOM element.
|
|
@@ -606,8 +676,6 @@ export { Signal_2 as Signal }
|
|
|
606
676
|
*
|
|
607
677
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
608
678
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
609
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
610
|
-
* Mutated in place by the underlying {@link _if} call.
|
|
611
679
|
* @param expression - Function that evaluates the switch expression. Called
|
|
612
680
|
* reactively inside an effect so that signal reads are tracked.
|
|
613
681
|
* @param blocks - Ordered list of case branches. Each entry contains:
|
|
@@ -618,7 +686,7 @@ export { Signal_2 as Signal }
|
|
|
618
686
|
*/
|
|
619
687
|
export declare function _switch(parentNode: HTMLElement, parentContext: Context, expression: NoArgsFunction<unknown>, blocks: Array<{
|
|
620
688
|
condition: unknown[] | null;
|
|
621
|
-
block: Function_2<[HTMLElement, Context], Context>;
|
|
689
|
+
block: Function_2<[HTMLElement, Context, Node | null], Context>;
|
|
622
690
|
}>): void;
|
|
623
691
|
|
|
624
692
|
/**
|
package/dist/xaendar-core.es.js
CHANGED
|
@@ -507,8 +507,17 @@ var Context = class {
|
|
|
507
507
|
* Child contexts representing nested scopes (e.g. `@for` loop iterations, `@if` branches).
|
|
508
508
|
*/
|
|
509
509
|
_children = new Array();
|
|
510
|
-
/**
|
|
511
|
-
*
|
|
510
|
+
/**
|
|
511
|
+
* Map containing the run time values of variables defines during the run time execution
|
|
512
|
+
* (e.g. {@for} variables: 'index', 'item', etc...)
|
|
513
|
+
*/
|
|
514
|
+
_variables = /* @__PURE__ */ new Map();
|
|
515
|
+
/**
|
|
516
|
+
* DOM nodes directly mounted into this scope via {@link mountNode} (does not
|
|
517
|
+
* include nodes owned by child contexts). Used to determine, for a given
|
|
518
|
+
* context, which DOM nodes belong to it — for example so `_for`'s keyed
|
|
519
|
+
* diffing can move a reused item's nodes to a new position without
|
|
520
|
+
* recreating them.
|
|
512
521
|
*/
|
|
513
522
|
_nodes = new Array();
|
|
514
523
|
/**
|
|
@@ -521,14 +530,35 @@ var Context = class {
|
|
|
521
530
|
* Creates a new scope context.
|
|
522
531
|
*
|
|
523
532
|
* @param _root - Web component reference used to resolve property and method bindings.
|
|
524
|
-
* @param _parent - Optional parent context representing the enclosing scope.
|
|
525
|
-
* @param _identifiers - Named identifier bindings declared in this scope.
|
|
526
533
|
*/
|
|
527
534
|
constructor(_root, _parent) {
|
|
528
535
|
this._root = _root;
|
|
529
536
|
this._parent = _parent;
|
|
530
537
|
}
|
|
531
538
|
/**
|
|
539
|
+
* Registers a new identifier name in this scope.
|
|
540
|
+
*
|
|
541
|
+
* @param name - The identifier name to register.
|
|
542
|
+
* @throws When an identifier with the same name is already declared in this scope.
|
|
543
|
+
*/
|
|
544
|
+
addIdentifier(name, value) {
|
|
545
|
+
if (this._variables.has(name)) throw new Error(`Identifier "${name}" is already declared in this scope.`);
|
|
546
|
+
this._variables.set(name, value);
|
|
547
|
+
}
|
|
548
|
+
removeIdentifier(name) {
|
|
549
|
+
this._variables.delete(name);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Returns `true` if an identifier with the given name is declared in this
|
|
553
|
+
* scope or any of its ancestor scopes.
|
|
554
|
+
*
|
|
555
|
+
* @param name - The identifier name to look up.
|
|
556
|
+
* @returns `true` if the identifier exists in the scope chain, `false` otherwise.
|
|
557
|
+
*/
|
|
558
|
+
get(name) {
|
|
559
|
+
return this._variables.has(name) ? this._variables.get(name) : this._parent?.get(name);
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
532
562
|
* Returns the event handler method bound to the root component instance.
|
|
533
563
|
*
|
|
534
564
|
* @param handler - The name of the method on the root component to retrieve.
|
|
@@ -551,10 +581,10 @@ var Context = class {
|
|
|
551
581
|
* @param context - The child context to remove.
|
|
552
582
|
*/
|
|
553
583
|
removeChild(context) {
|
|
554
|
-
this._children = this._children.filter((child) => child
|
|
584
|
+
this._children = this._children.filter((child) => child !== context);
|
|
555
585
|
}
|
|
556
586
|
/**
|
|
557
|
-
* Registers a DOM node as owned by this context.
|
|
587
|
+
* Registers a DOM node as directly owned by this context.
|
|
558
588
|
*
|
|
559
589
|
* @param node - The DOM node to track.
|
|
560
590
|
*/
|
|
@@ -567,7 +597,16 @@ var Context = class {
|
|
|
567
597
|
* @param nodeToRemove - The DOM node to untrack.
|
|
568
598
|
*/
|
|
569
599
|
removeNode(nodeToRemove) {
|
|
570
|
-
this._nodes = this._nodes.filter((node) => node
|
|
600
|
+
this._nodes = this._nodes.filter((node) => node !== nodeToRemove);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Returns the DOM nodes directly owned by this context (not recursively
|
|
604
|
+
* including nodes owned by child contexts), in mount order. Used by
|
|
605
|
+
* `_for`'s keyed diffing to locate a reused item's nodes so they can be
|
|
606
|
+
* repositioned with `insertBefore` instead of being destroyed and recreated.
|
|
607
|
+
*/
|
|
608
|
+
getNodes() {
|
|
609
|
+
return this._nodes;
|
|
571
610
|
}
|
|
572
611
|
/**
|
|
573
612
|
* Registers one or more cleanup functions to be called when this context is destroyed.
|
|
@@ -591,79 +630,149 @@ var Context = class {
|
|
|
591
630
|
this._unwatchFns.forEach((fn) => fn());
|
|
592
631
|
this._children.forEach((child) => child.unlisten());
|
|
593
632
|
this._unwatchFns = [];
|
|
633
|
+
this._children = [];
|
|
594
634
|
this._nodes = [];
|
|
635
|
+
this._variables.clear();
|
|
595
636
|
}
|
|
596
637
|
};
|
|
597
638
|
/**
|
|
598
639
|
* Mounts a node into the DOM and binds it to the context lifecycle.
|
|
599
640
|
*
|
|
600
|
-
* Registers the node with the context,
|
|
601
|
-
* schedules a cleanup listener that removes the node
|
|
602
|
-
* and the DOM when the context is destroyed.
|
|
641
|
+
* Registers the node with the context, inserts it into `parentNode` at the
|
|
642
|
+
* given position, and schedules a cleanup listener that removes the node
|
|
643
|
+
* from both the context and the DOM when the context is destroyed.
|
|
603
644
|
*
|
|
604
645
|
* @param node - The node to mount.
|
|
605
|
-
* @param parentNode - The parent HTML element to
|
|
646
|
+
* @param parentNode - The parent HTML element to insert the node into.
|
|
606
647
|
* @param context - The current template execution scope that owns the node's lifecycle.
|
|
648
|
+
* @param referenceNode - The node to insert relative to (same semantics
|
|
649
|
+
* as `Node.insertBefore`). If `null`, the node is appended at the end of `parentNode`.
|
|
607
650
|
*/
|
|
608
|
-
function mountNode(node, parentNode, context) {
|
|
651
|
+
function mountNode(node, parentNode, context, referenceNode = null) {
|
|
609
652
|
context.addNode(node);
|
|
610
|
-
parentNode.
|
|
653
|
+
parentNode.insertBefore(node, referenceNode);
|
|
611
654
|
context.listen(() => {
|
|
612
655
|
context.removeNode(node);
|
|
613
|
-
parentNode.removeChild(node);
|
|
656
|
+
if (node.parentNode === parentNode) parentNode.removeChild(node);
|
|
614
657
|
});
|
|
615
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Creates a Comment node used as a positional placeholder for dynamic content
|
|
661
|
+
* (`_if`, `_for`). It is inserted immediately, in order, and stays in place for
|
|
662
|
+
* the entire lifetime of the given `context`: any future insertion of dynamic
|
|
663
|
+
* content is done via `insertBefore(node, anchor)`, guaranteeing the correct
|
|
664
|
+
* position even when sibling constructs update independently and asynchronously.
|
|
665
|
+
*
|
|
666
|
+
* @param label - Textual label for the comment, useful for debugging to visually
|
|
667
|
+
* distinguish in the DOM which construct (if/for) generated the anchor.
|
|
668
|
+
* @param parentNode - The parent HTML element the anchor is inserted into.
|
|
669
|
+
* @param context - The Context the anchor is bound to: it determines the anchor's
|
|
670
|
+
* lifecycle, since it will be removed from the DOM when this context is destroyed.
|
|
671
|
+
* @param referenceNode - The node to insert the anchor relative to (same semantics
|
|
672
|
+
* as `Node.insertBefore`). If `null`, the anchor is appended at the end of `parentNode`.
|
|
673
|
+
* @returns The created Comment node, to be used as the reference point for future
|
|
674
|
+
* insertions of dynamic content via `insertBefore(node, anchor)`.
|
|
675
|
+
*/
|
|
676
|
+
function createAnchor(label, parentNode, context, referenceNode = null) {
|
|
677
|
+
const anchor = document.createComment(label);
|
|
678
|
+
mountNode(anchor, parentNode, context, referenceNode);
|
|
679
|
+
return anchor;
|
|
680
|
+
}
|
|
616
681
|
//#endregion
|
|
617
682
|
//#region ../packages/core/src/utils/for.util.ts
|
|
618
683
|
/**
|
|
619
|
-
* Reactively iterates over a list of items
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
684
|
+
* Reactively iterates over a list of items. Items are matched across re-runs
|
|
685
|
+
* by the key produced by `trackExpression`:
|
|
686
|
+
* - key sopravvive → il Context e l'intero sottoalbero (stato, subscription,
|
|
687
|
+
* richieste pendenti) vengono riusati SEMPRE, indipendentemente da un
|
|
688
|
+
* eventuale cambio di indice: si spostano solo i nodi DOM, e se il body usa
|
|
689
|
+
* $index/$first/$last/$even/$odd questi vengono aggiornati in-place tramite
|
|
690
|
+
* `update`, senza ricreare nulla.
|
|
691
|
+
* - key sparita → il Context viene distrutto.
|
|
692
|
+
* - key nuova → il Context viene creato.
|
|
693
|
+
*
|
|
694
|
+
* @param forFn - Callback invocata per creare un nuovo item. Deve ritornare
|
|
695
|
+
* sia il Context che possiede i nodi, sia (opzionalmente) una funzione
|
|
696
|
+
* `update` per aggiornare le variabili implicite in-place quando l'item
|
|
697
|
+
* viene riusato a un indice diverso.
|
|
627
698
|
*/
|
|
628
699
|
function _for(parentNode, parentContext, condition, trackExpression, forFn) {
|
|
629
|
-
|
|
700
|
+
const anchor = createAnchor("for", parentNode, parentContext);
|
|
701
|
+
let entries = /* @__PURE__ */ new Map();
|
|
630
702
|
const unlistener = effect(() => {
|
|
631
|
-
contexts.forEach((context) => {
|
|
632
|
-
context.unlisten();
|
|
633
|
-
parentContext.removeChild(context);
|
|
634
|
-
});
|
|
635
|
-
contexts = [];
|
|
636
703
|
const items = condition();
|
|
704
|
+
const newKeys = items.map((item) => trackExpression(item));
|
|
705
|
+
const newKeySet = new Set(newKeys);
|
|
637
706
|
untracked(() => {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
parentContext.
|
|
707
|
+
const newEntries = /* @__PURE__ */ new Map();
|
|
708
|
+
for (const [key, entry] of entries) if (!newKeySet.has(key)) {
|
|
709
|
+
entry.context.unlisten();
|
|
710
|
+
parentContext.removeChild(entry.context);
|
|
642
711
|
}
|
|
712
|
+
let nextReference = anchor;
|
|
713
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
714
|
+
const key = newKeys[i];
|
|
715
|
+
const existing = entries.get(key);
|
|
716
|
+
let entry;
|
|
717
|
+
if (existing) {
|
|
718
|
+
const nodes = existing.context.getNodes();
|
|
719
|
+
if (nodes[nodes.length - 1]?.nextSibling !== nextReference) nodes.forEach((node) => parentNode.insertBefore(node, nextReference));
|
|
720
|
+
existing.update?.(i, items);
|
|
721
|
+
entry = existing;
|
|
722
|
+
} else {
|
|
723
|
+
const created = forFn(parentNode, parentContext, items, i, nextReference);
|
|
724
|
+
parentContext.addChild(created.context);
|
|
725
|
+
entry = created;
|
|
726
|
+
}
|
|
727
|
+
newEntries.set(key, entry);
|
|
728
|
+
nextReference = entry.context.getNodes()[0] ?? nextReference;
|
|
729
|
+
}
|
|
730
|
+
entries = newEntries;
|
|
643
731
|
});
|
|
644
732
|
});
|
|
645
733
|
parentContext.listen(unlistener);
|
|
646
734
|
}
|
|
647
735
|
/**
|
|
648
736
|
* Builds a record of iteration context variables for a given index in the loop.
|
|
649
|
-
*
|
|
737
|
+
* `item` is a plain value (identity-stable across moves thanks to the key),
|
|
738
|
+
* while `$index`/`$first`/`$last`/`$even`/`$odd` are signals: when an item is
|
|
739
|
+
* moved to a different position in the array, `update()` writes the new
|
|
740
|
+
* values into these signals in place, instead of recreating the item's
|
|
741
|
+
* template output. Anything bound to these variables re-runs reactively;
|
|
742
|
+
* everything else in the item's subtree (state, subscriptions, pending
|
|
743
|
+
* requests) is left completely untouched.
|
|
650
744
|
*
|
|
651
745
|
* @param items - The full array being iterated.
|
|
652
746
|
* @param index - The current iteration index.
|
|
653
|
-
* @param itemName - The identifier to reference the i-th item during iteration
|
|
654
|
-
* @param aliases - Aliases for implicit variables defined in the `
|
|
655
|
-
* @returns A
|
|
747
|
+
* @param itemName - The identifier to reference the i-th item during iteration.
|
|
748
|
+
* @param aliases - Aliases for implicit variables defined in the `@for` loop.
|
|
749
|
+
* @returns A handle exposing the resolved variables and an `update` function.
|
|
656
750
|
*/
|
|
657
|
-
function _iterationVariables(items, index, itemName, aliases) {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
751
|
+
function _iterationVariables(context, items, index, itemName, aliases) {
|
|
752
|
+
const $index = signal(index);
|
|
753
|
+
const $first = signal(index === 0);
|
|
754
|
+
const $last = signal(index === items.length - 1);
|
|
755
|
+
const $even = signal(index % 2 === 0);
|
|
756
|
+
const $odd = signal(index % 2 !== 0);
|
|
757
|
+
const retVal = {
|
|
758
|
+
vars: {
|
|
759
|
+
[itemName]: items[index],
|
|
760
|
+
[aliases.$index]: $index,
|
|
761
|
+
[aliases.$first]: $first,
|
|
762
|
+
[aliases.$last]: $last,
|
|
763
|
+
[aliases.$even]: $even,
|
|
764
|
+
[aliases.$odd]: $odd
|
|
765
|
+
},
|
|
766
|
+
update(newIndex, newItems) {
|
|
767
|
+
$index.set(newIndex);
|
|
768
|
+
$first.set(newIndex === 0);
|
|
769
|
+
$last.set(newIndex === newItems.length - 1);
|
|
770
|
+
$even.set(newIndex % 2 === 0);
|
|
771
|
+
$odd.set(newIndex % 2 !== 0);
|
|
772
|
+
}
|
|
666
773
|
};
|
|
774
|
+
Object.entries(retVal.vars).forEach(([key, value]) => context.addIdentifier(key, value));
|
|
775
|
+
return retVal;
|
|
667
776
|
}
|
|
668
777
|
//#endregion
|
|
669
778
|
//#region ../packages/core/src/utils/if.util.ts
|
|
@@ -683,31 +792,22 @@ function _iterationVariables(items, index, itemName, aliases) {
|
|
|
683
792
|
*
|
|
684
793
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
685
794
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
686
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
687
|
-
* Mutated in place: the created effect and branch functions are added to and
|
|
688
|
-
* removed from this array.
|
|
689
795
|
* @param blocks - Ordered list of conditional branches to evaluate.
|
|
690
796
|
*/
|
|
691
797
|
function _if(parentNode, parentContext, blocks) {
|
|
798
|
+
const anchor = createAnchor("if", parentNode, parentContext);
|
|
692
799
|
let state;
|
|
693
800
|
let fn;
|
|
694
801
|
switch (blocks.length) {
|
|
695
802
|
case 1:
|
|
696
|
-
fn = (state) => handleIf(parentNode, parentContext, blocks[0], state);
|
|
803
|
+
fn = (state) => handleIf(parentNode, parentContext, blocks[0], state, anchor);
|
|
697
804
|
break;
|
|
698
805
|
case 2:
|
|
699
|
-
fn = (state) => handleIfElse(parentNode, parentContext, blocks[0], blocks[1], state);
|
|
806
|
+
fn = (state) => handleIfElse(parentNode, parentContext, blocks[0], blocks[1], state, anchor);
|
|
700
807
|
break;
|
|
701
|
-
default: fn = (state) => handleIfElseIf(parentNode, parentContext, blocks, state);
|
|
808
|
+
default: fn = (state) => handleIfElseIf(parentNode, parentContext, blocks, state, anchor);
|
|
702
809
|
}
|
|
703
|
-
const unlistener = effect(() =>
|
|
704
|
-
if (state) {
|
|
705
|
-
state.context.unlisten();
|
|
706
|
-
parentContext.removeChild(state.context);
|
|
707
|
-
}
|
|
708
|
-
state = fn(state);
|
|
709
|
-
if (state) parentContext.addChild(state.context);
|
|
710
|
-
});
|
|
810
|
+
const unlistener = effect(() => state = fn(state));
|
|
711
811
|
parentContext.listen(unlistener);
|
|
712
812
|
}
|
|
713
813
|
/**
|
|
@@ -724,13 +824,9 @@ function _if(parentNode, parentContext, blocks) {
|
|
|
724
824
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
725
825
|
* otherwise `undefined`.
|
|
726
826
|
*/
|
|
727
|
-
function handleIf(parentNode, parentContext, ifBlock, state) {
|
|
728
|
-
if (ifBlock.condition()) return checkAndUpdateState(parentNode, parentContext, state, 0, ifBlock.block);
|
|
729
|
-
|
|
730
|
-
const context = state.context;
|
|
731
|
-
context.unlisten();
|
|
732
|
-
parentContext.removeChild(context);
|
|
733
|
-
}
|
|
827
|
+
function handleIf(parentNode, parentContext, ifBlock, state, anchor) {
|
|
828
|
+
if (ifBlock.condition()) return checkAndUpdateState(parentNode, parentContext, state, 0, ifBlock.block, anchor);
|
|
829
|
+
teardown(parentContext, state);
|
|
734
830
|
}
|
|
735
831
|
/**
|
|
736
832
|
* Handles the `if` / `else` case (exactly two branches).
|
|
@@ -746,8 +842,8 @@ function handleIf(parentNode, parentContext, ifBlock, state) {
|
|
|
746
842
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
747
843
|
* otherwise `undefined`.
|
|
748
844
|
*/
|
|
749
|
-
function handleIfElse(parentNode, parentContext, ifBlock, elseBlock, state) {
|
|
750
|
-
return ifBlock.condition() ? checkAndUpdateState(parentNode, parentContext, state, 0, ifBlock.block) : checkAndUpdateState(parentNode, parentContext, state, 1, elseBlock.block);
|
|
845
|
+
function handleIfElse(parentNode, parentContext, ifBlock, elseBlock, state, anchor) {
|
|
846
|
+
return ifBlock.condition() ? checkAndUpdateState(parentNode, parentContext, state, 0, ifBlock.block, anchor) : checkAndUpdateState(parentNode, parentContext, state, 1, elseBlock.block, anchor);
|
|
751
847
|
}
|
|
752
848
|
/**
|
|
753
849
|
* Handles the general case of an `if` / `else if` / ... / `else` chain (three or more branches).
|
|
@@ -763,10 +859,10 @@ function handleIfElse(parentNode, parentContext, ifBlock, elseBlock, state) {
|
|
|
763
859
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
764
860
|
* otherwise `undefined`. Also returns `undefined` if no branch matches.
|
|
765
861
|
*/
|
|
766
|
-
function handleIfElseIf(parentNode, parentContext, blocks, state) {
|
|
862
|
+
function handleIfElseIf(parentNode, parentContext, blocks, state, anchor) {
|
|
767
863
|
for (let i = 0; i < blocks.length; i++) {
|
|
768
864
|
const { condition, block } = blocks[i];
|
|
769
|
-
if (!condition || condition()) return checkAndUpdateState(parentNode, parentContext, state, i, block);
|
|
865
|
+
if (!condition || condition()) return checkAndUpdateState(parentNode, parentContext, state, i, block, anchor);
|
|
770
866
|
}
|
|
771
867
|
}
|
|
772
868
|
/**
|
|
@@ -789,13 +885,34 @@ function handleIfElseIf(parentNode, parentContext, blocks, state) {
|
|
|
789
885
|
* @returns The updated state if the active branch changed, or `undefined` if the branch
|
|
790
886
|
* is unchanged.
|
|
791
887
|
*/
|
|
792
|
-
function checkAndUpdateState(parentNode, parentContext, state, newState, conditionalBlockFn) {
|
|
793
|
-
if (state?.activeBranch
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
888
|
+
function checkAndUpdateState(parentNode, parentContext, state, newState, conditionalBlockFn, anchor) {
|
|
889
|
+
if (state?.activeBranch === newState) return state;
|
|
890
|
+
if (state) {
|
|
891
|
+
state.context.unlisten();
|
|
892
|
+
parentContext.removeChild(state.context);
|
|
893
|
+
}
|
|
894
|
+
const context = untracked(() => conditionalBlockFn(parentNode, parentContext, anchor));
|
|
895
|
+
parentContext.addChild(context);
|
|
896
|
+
return {
|
|
897
|
+
activeBranch: newState,
|
|
898
|
+
context
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Handles the case where no branch condition matched: tears down the previously
|
|
903
|
+
* active branch, if one existed, by unlistening its context (running cleanup
|
|
904
|
+
* functions and destroying its subtree) and detaching it from the parent context.
|
|
905
|
+
* A no-op if no branch was previously active.
|
|
906
|
+
*
|
|
907
|
+
* @param parentContext - The parent Context from which the previous branch's
|
|
908
|
+
* context should be detached.
|
|
909
|
+
* @param state - The current state (active branch index and its context), or
|
|
910
|
+
* `undefined` if no branch is currently active.
|
|
911
|
+
*/
|
|
912
|
+
function teardown(parentContext, state) {
|
|
913
|
+
if (state) {
|
|
914
|
+
state.context.unlisten();
|
|
915
|
+
parentContext.removeChild(state.context);
|
|
799
916
|
}
|
|
800
917
|
}
|
|
801
918
|
//#endregion
|
|
@@ -817,9 +934,9 @@ function checkAndUpdateState(parentNode, parentContext, state, newState, conditi
|
|
|
817
934
|
* @param events - List of event listener descriptors to attach to the element.
|
|
818
935
|
* @returns The newly created HTML element.
|
|
819
936
|
*/
|
|
820
|
-
function _renderElement(parentNode, context, tagName, attributes, events) {
|
|
937
|
+
function _renderElement(parentNode, context, anchor, tagName, attributes, events) {
|
|
821
938
|
const element = document.createElement(tagName);
|
|
822
|
-
mountNode(element, parentNode, context);
|
|
939
|
+
mountNode(element, parentNode, context, anchor);
|
|
823
940
|
attributes.forEach(({ name, value, literal }) => {
|
|
824
941
|
literal ? element.setAttribute(name, String(value())) : context.listen(effect(() => element.setAttribute(name, String(value()))));
|
|
825
942
|
});
|
|
@@ -880,8 +997,6 @@ function _renderLiteralText(parentNode, context, text) {
|
|
|
880
997
|
*
|
|
881
998
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
882
999
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
883
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
884
|
-
* Mutated in place by the underlying {@link _if} call.
|
|
885
1000
|
* @param expression - Function that evaluates the switch expression. Called
|
|
886
1001
|
* reactively inside an effect so that signal reads are tracked.
|
|
887
1002
|
* @param blocks - Ordered list of case branches. Each entry contains:
|
|
@@ -897,4 +1012,4 @@ function _switch(parentNode, parentContext, expression, blocks) {
|
|
|
897
1012
|
})));
|
|
898
1013
|
}
|
|
899
1014
|
//#endregion
|
|
900
|
-
export { BaseWebComponent, Context, Event, Property, WebComponent, _for, _if, _iterationVariables, _renderElement, _renderLiteralText, _renderText, _switch, computed, effect, input, mountNode, signal, untracked };
|
|
1015
|
+
export { BaseWebComponent, Context, Event, Property, WebComponent, _for, _if, _iterationVariables, _renderElement, _renderLiteralText, _renderText, _switch, computed, createAnchor, effect, input, mountNode, signal, untracked };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xaendar/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A library containing core utils such as webcomponent base classes and theming support",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@xaendar/signals": "0.
|
|
20
|
-
"@xaendar/types": "0.
|
|
19
|
+
"@xaendar/signals": "0.7.0",
|
|
20
|
+
"@xaendar/types": "0.7.0"
|
|
21
21
|
}
|
|
22
22
|
}
|