@xaendar/core 0.6.19 → 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 +115 -26
- package/dist/xaendar-core.es.js +221 -88
- 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,15 +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
|
-
*
|
|
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.
|
|
277
333
|
*/
|
|
278
|
-
export declare function _for(parentNode: HTMLElement, parentContext: Context, condition: () => 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;
|
|
279
340
|
|
|
280
341
|
/**
|
|
281
342
|
* Creates a reactive conditional structure from a list of branches.
|
|
@@ -293,9 +354,6 @@ export declare function _for(parentNode: HTMLElement, parentContext: Context, co
|
|
|
293
354
|
*
|
|
294
355
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
295
356
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
296
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
297
|
-
* Mutated in place: the created effect and branch functions are added to and
|
|
298
|
-
* removed from this array.
|
|
299
357
|
* @param blocks - Ordered list of conditional branches to evaluate.
|
|
300
358
|
*/
|
|
301
359
|
export declare function _if(parentNode: HTMLElement, parentContext: Context, blocks: Block[]): void;
|
|
@@ -366,21 +424,47 @@ export declare type InputSignalOptions<ActualValue = unknown, IncomingValue = Ac
|
|
|
366
424
|
|
|
367
425
|
/**
|
|
368
426
|
* Builds a record of iteration context variables for a given index in the loop.
|
|
369
|
-
*
|
|
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.
|
|
370
434
|
*
|
|
371
435
|
* @param items - The full array being iterated.
|
|
372
436
|
* @param index - The current iteration index.
|
|
373
|
-
* @param itemName - The identifier to reference the i-th item during iteration
|
|
374
|
-
* @param aliases - Aliases for implicit variables defined in the `
|
|
375
|
-
* @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.
|
|
376
440
|
*/
|
|
377
|
-
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: {
|
|
378
442
|
$index: string;
|
|
379
443
|
$first: string;
|
|
380
444
|
$last: string;
|
|
381
445
|
$even: string;
|
|
382
446
|
$odd: string;
|
|
383
|
-
}):
|
|
447
|
+
}): IterationVariablesHandle;
|
|
448
|
+
|
|
449
|
+
declare type IterationVariablesHandle = {
|
|
450
|
+
vars: Record<string, unknown>;
|
|
451
|
+
update: (newIndex: number, items: unknown[]) => void;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Mounts a node into the DOM and binds it to the context lifecycle.
|
|
456
|
+
*
|
|
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.
|
|
460
|
+
*
|
|
461
|
+
* @param node - The node to mount.
|
|
462
|
+
* @param parentNode - The parent HTML element to insert the node into.
|
|
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`.
|
|
466
|
+
*/
|
|
467
|
+
export declare function mountNode(node: Node, parentNode: HTMLElement, context: Context, referenceNode?: Comment | null): void;
|
|
384
468
|
|
|
385
469
|
/**
|
|
386
470
|
* Represents the output type returned by an `@Event` decorator in a web component.
|
|
@@ -471,7 +555,7 @@ declare type PropertyDecoratorOptionsWithRequired<ActualValue = unknown, Incomin
|
|
|
471
555
|
* @param events - List of event listener descriptors to attach to the element.
|
|
472
556
|
* @returns The newly created HTML element.
|
|
473
557
|
*/
|
|
474
|
-
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;
|
|
475
559
|
|
|
476
560
|
/**
|
|
477
561
|
* Describes a single HTML attribute to be applied to a rendered DOM element.
|
|
@@ -592,8 +676,6 @@ export { Signal_2 as Signal }
|
|
|
592
676
|
*
|
|
593
677
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
594
678
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
595
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
596
|
-
* Mutated in place by the underlying {@link _if} call.
|
|
597
679
|
* @param expression - Function that evaluates the switch expression. Called
|
|
598
680
|
* reactively inside an effect so that signal reads are tracked.
|
|
599
681
|
* @param blocks - Ordered list of case branches. Each entry contains:
|
|
@@ -604,9 +686,16 @@ export { Signal_2 as Signal }
|
|
|
604
686
|
*/
|
|
605
687
|
export declare function _switch(parentNode: HTMLElement, parentContext: Context, expression: NoArgsFunction<unknown>, blocks: Array<{
|
|
606
688
|
condition: unknown[] | null;
|
|
607
|
-
block: Function_2<[HTMLElement, Context], Context>;
|
|
689
|
+
block: Function_2<[HTMLElement, Context, Node | null], Context>;
|
|
608
690
|
}>): void;
|
|
609
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Executes a function without tracking any dependencies.
|
|
694
|
+
* @param fn - The function to execute without tracking.
|
|
695
|
+
* @returns The result of the function execution.
|
|
696
|
+
*/
|
|
697
|
+
export declare const untracked: typeof Signal.subtle.untrack;
|
|
698
|
+
|
|
610
699
|
/**
|
|
611
700
|
* Decorator that registers a class as a custom web component.
|
|
612
701
|
*
|
package/dist/xaendar-core.es.js
CHANGED
|
@@ -486,6 +486,14 @@ function signal(value, options) {
|
|
|
486
486
|
return getter;
|
|
487
487
|
}
|
|
488
488
|
//#endregion
|
|
489
|
+
//#region ../packages/core/src/signals/untracked.ts
|
|
490
|
+
/**
|
|
491
|
+
* Executes a function without tracking any dependencies.
|
|
492
|
+
* @param fn - The function to execute without tracking.
|
|
493
|
+
* @returns The result of the function execution.
|
|
494
|
+
*/
|
|
495
|
+
var untracked = Signal.subtle.untrack;
|
|
496
|
+
//#endregion
|
|
489
497
|
//#region ../packages/core/src/utils/context.util.ts
|
|
490
498
|
/**
|
|
491
499
|
* Tracks identifier scope during run time template function execution
|
|
@@ -499,8 +507,17 @@ var Context = class {
|
|
|
499
507
|
* Child contexts representing nested scopes (e.g. `@for` loop iterations, `@if` branches).
|
|
500
508
|
*/
|
|
501
509
|
_children = new Array();
|
|
502
|
-
/**
|
|
503
|
-
*
|
|
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.
|
|
504
521
|
*/
|
|
505
522
|
_nodes = new Array();
|
|
506
523
|
/**
|
|
@@ -513,14 +530,35 @@ var Context = class {
|
|
|
513
530
|
* Creates a new scope context.
|
|
514
531
|
*
|
|
515
532
|
* @param _root - Web component reference used to resolve property and method bindings.
|
|
516
|
-
* @param _parent - Optional parent context representing the enclosing scope.
|
|
517
|
-
* @param _identifiers - Named identifier bindings declared in this scope.
|
|
518
533
|
*/
|
|
519
534
|
constructor(_root, _parent) {
|
|
520
535
|
this._root = _root;
|
|
521
536
|
this._parent = _parent;
|
|
522
537
|
}
|
|
523
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
|
+
/**
|
|
524
562
|
* Returns the event handler method bound to the root component instance.
|
|
525
563
|
*
|
|
526
564
|
* @param handler - The name of the method on the root component to retrieve.
|
|
@@ -543,10 +581,10 @@ var Context = class {
|
|
|
543
581
|
* @param context - The child context to remove.
|
|
544
582
|
*/
|
|
545
583
|
removeChild(context) {
|
|
546
|
-
this._children = this._children.filter((child) => child
|
|
584
|
+
this._children = this._children.filter((child) => child !== context);
|
|
547
585
|
}
|
|
548
586
|
/**
|
|
549
|
-
* Registers a DOM node as owned by this context.
|
|
587
|
+
* Registers a DOM node as directly owned by this context.
|
|
550
588
|
*
|
|
551
589
|
* @param node - The DOM node to track.
|
|
552
590
|
*/
|
|
@@ -559,7 +597,16 @@ var Context = class {
|
|
|
559
597
|
* @param nodeToRemove - The DOM node to untrack.
|
|
560
598
|
*/
|
|
561
599
|
removeNode(nodeToRemove) {
|
|
562
|
-
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;
|
|
563
610
|
}
|
|
564
611
|
/**
|
|
565
612
|
* Registers one or more cleanup functions to be called when this context is destroyed.
|
|
@@ -583,59 +630,149 @@ var Context = class {
|
|
|
583
630
|
this._unwatchFns.forEach((fn) => fn());
|
|
584
631
|
this._children.forEach((child) => child.unlisten());
|
|
585
632
|
this._unwatchFns = [];
|
|
633
|
+
this._children = [];
|
|
586
634
|
this._nodes = [];
|
|
635
|
+
this._variables.clear();
|
|
587
636
|
}
|
|
588
637
|
};
|
|
638
|
+
/**
|
|
639
|
+
* Mounts a node into the DOM and binds it to the context lifecycle.
|
|
640
|
+
*
|
|
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.
|
|
644
|
+
*
|
|
645
|
+
* @param node - The node to mount.
|
|
646
|
+
* @param parentNode - The parent HTML element to insert the node into.
|
|
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`.
|
|
650
|
+
*/
|
|
651
|
+
function mountNode(node, parentNode, context, referenceNode = null) {
|
|
652
|
+
context.addNode(node);
|
|
653
|
+
parentNode.insertBefore(node, referenceNode);
|
|
654
|
+
context.listen(() => {
|
|
655
|
+
context.removeNode(node);
|
|
656
|
+
if (node.parentNode === parentNode) parentNode.removeChild(node);
|
|
657
|
+
});
|
|
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
|
+
}
|
|
589
681
|
//#endregion
|
|
590
682
|
//#region ../packages/core/src/utils/for.util.ts
|
|
591
683
|
/**
|
|
592
|
-
* Reactively iterates over a list of items
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
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.
|
|
599
698
|
*/
|
|
600
|
-
function _for(parentNode, parentContext, condition, forFn) {
|
|
601
|
-
|
|
699
|
+
function _for(parentNode, parentContext, condition, trackExpression, forFn) {
|
|
700
|
+
const anchor = createAnchor("for", parentNode, parentContext);
|
|
701
|
+
let entries = /* @__PURE__ */ new Map();
|
|
602
702
|
const unlistener = effect(() => {
|
|
603
|
-
contexts.forEach((context) => {
|
|
604
|
-
context.unlisten();
|
|
605
|
-
parentContext.removeChild(context);
|
|
606
|
-
});
|
|
607
|
-
contexts = [];
|
|
608
703
|
const items = condition();
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
704
|
+
const newKeys = items.map((item) => trackExpression(item));
|
|
705
|
+
const newKeySet = new Set(newKeys);
|
|
706
|
+
untracked(() => {
|
|
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);
|
|
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;
|
|
614
729
|
}
|
|
730
|
+
entries = newEntries;
|
|
615
731
|
});
|
|
616
732
|
});
|
|
617
733
|
parentContext.listen(unlistener);
|
|
618
734
|
}
|
|
619
735
|
/**
|
|
620
736
|
* Builds a record of iteration context variables for a given index in the loop.
|
|
621
|
-
*
|
|
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.
|
|
622
744
|
*
|
|
623
745
|
* @param items - The full array being iterated.
|
|
624
746
|
* @param index - The current iteration index.
|
|
625
|
-
* @param itemName - The identifier to reference the i-th item during iteration
|
|
626
|
-
* @param aliases - Aliases for implicit variables defined in the `
|
|
627
|
-
* @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.
|
|
628
750
|
*/
|
|
629
|
-
function _iterationVariables(items, index, itemName, aliases) {
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
+
}
|
|
638
773
|
};
|
|
774
|
+
Object.entries(retVal.vars).forEach(([key, value]) => context.addIdentifier(key, value));
|
|
775
|
+
return retVal;
|
|
639
776
|
}
|
|
640
777
|
//#endregion
|
|
641
778
|
//#region ../packages/core/src/utils/if.util.ts
|
|
@@ -655,31 +792,22 @@ function _iterationVariables(items, index, itemName, aliases) {
|
|
|
655
792
|
*
|
|
656
793
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
657
794
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
658
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
659
|
-
* Mutated in place: the created effect and branch functions are added to and
|
|
660
|
-
* removed from this array.
|
|
661
795
|
* @param blocks - Ordered list of conditional branches to evaluate.
|
|
662
796
|
*/
|
|
663
797
|
function _if(parentNode, parentContext, blocks) {
|
|
798
|
+
const anchor = createAnchor("if", parentNode, parentContext);
|
|
664
799
|
let state;
|
|
665
800
|
let fn;
|
|
666
801
|
switch (blocks.length) {
|
|
667
802
|
case 1:
|
|
668
|
-
fn = (state) => handleIf(parentNode, parentContext, blocks[0], state);
|
|
803
|
+
fn = (state) => handleIf(parentNode, parentContext, blocks[0], state, anchor);
|
|
669
804
|
break;
|
|
670
805
|
case 2:
|
|
671
|
-
fn = (state) => handleIfElse(parentNode, parentContext, blocks[0], blocks[1], state);
|
|
806
|
+
fn = (state) => handleIfElse(parentNode, parentContext, blocks[0], blocks[1], state, anchor);
|
|
672
807
|
break;
|
|
673
|
-
default: fn = (state) => handleIfElseIf(parentNode, parentContext, blocks, state);
|
|
808
|
+
default: fn = (state) => handleIfElseIf(parentNode, parentContext, blocks, state, anchor);
|
|
674
809
|
}
|
|
675
|
-
const unlistener = effect(() =>
|
|
676
|
-
if (state) {
|
|
677
|
-
state.context.unlisten();
|
|
678
|
-
parentContext.removeChild(state.context);
|
|
679
|
-
}
|
|
680
|
-
state = fn(state);
|
|
681
|
-
if (state) parentContext.addChild(state.context);
|
|
682
|
-
});
|
|
810
|
+
const unlistener = effect(() => state = fn(state));
|
|
683
811
|
parentContext.listen(unlistener);
|
|
684
812
|
}
|
|
685
813
|
/**
|
|
@@ -696,13 +824,9 @@ function _if(parentNode, parentContext, blocks) {
|
|
|
696
824
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
697
825
|
* otherwise `undefined`.
|
|
698
826
|
*/
|
|
699
|
-
function handleIf(parentNode, parentContext, ifBlock, state) {
|
|
700
|
-
if (ifBlock.condition()) return checkAndUpdateState(parentNode, parentContext, state, 0, ifBlock.block);
|
|
701
|
-
|
|
702
|
-
const context = state.context;
|
|
703
|
-
context.unlisten();
|
|
704
|
-
parentContext.removeChild(context);
|
|
705
|
-
}
|
|
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);
|
|
706
830
|
}
|
|
707
831
|
/**
|
|
708
832
|
* Handles the `if` / `else` case (exactly two branches).
|
|
@@ -718,8 +842,8 @@ function handleIf(parentNode, parentContext, ifBlock, state) {
|
|
|
718
842
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
719
843
|
* otherwise `undefined`.
|
|
720
844
|
*/
|
|
721
|
-
function handleIfElse(parentNode, parentContext, ifBlock, elseBlock, state) {
|
|
722
|
-
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);
|
|
723
847
|
}
|
|
724
848
|
/**
|
|
725
849
|
* Handles the general case of an `if` / `else if` / ... / `else` chain (three or more branches).
|
|
@@ -735,10 +859,10 @@ function handleIfElse(parentNode, parentContext, ifBlock, elseBlock, state) {
|
|
|
735
859
|
* @returns The new state and new cleanup functions if the active branch changed,
|
|
736
860
|
* otherwise `undefined`. Also returns `undefined` if no branch matches.
|
|
737
861
|
*/
|
|
738
|
-
function handleIfElseIf(parentNode, parentContext, blocks, state) {
|
|
862
|
+
function handleIfElseIf(parentNode, parentContext, blocks, state, anchor) {
|
|
739
863
|
for (let i = 0; i < blocks.length; i++) {
|
|
740
864
|
const { condition, block } = blocks[i];
|
|
741
|
-
if (!condition || condition()) return checkAndUpdateState(parentNode, parentContext, state, i, block);
|
|
865
|
+
if (!condition || condition()) return checkAndUpdateState(parentNode, parentContext, state, i, block, anchor);
|
|
742
866
|
}
|
|
743
867
|
}
|
|
744
868
|
/**
|
|
@@ -761,13 +885,34 @@ function handleIfElseIf(parentNode, parentContext, blocks, state) {
|
|
|
761
885
|
* @returns The updated state if the active branch changed, or `undefined` if the branch
|
|
762
886
|
* is unchanged.
|
|
763
887
|
*/
|
|
764
|
-
function checkAndUpdateState(parentNode, parentContext, state, newState, conditionalBlockFn) {
|
|
765
|
-
if (state?.activeBranch
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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);
|
|
771
916
|
}
|
|
772
917
|
}
|
|
773
918
|
//#endregion
|
|
@@ -789,13 +934,9 @@ function checkAndUpdateState(parentNode, parentContext, state, newState, conditi
|
|
|
789
934
|
* @param events - List of event listener descriptors to attach to the element.
|
|
790
935
|
* @returns The newly created HTML element.
|
|
791
936
|
*/
|
|
792
|
-
function _renderElement(parentNode, context, tagName, attributes, events) {
|
|
937
|
+
function _renderElement(parentNode, context, anchor, tagName, attributes, events) {
|
|
793
938
|
const element = document.createElement(tagName);
|
|
794
|
-
|
|
795
|
-
context.listen(() => {
|
|
796
|
-
context.removeNode(element);
|
|
797
|
-
parentNode.removeChild(element);
|
|
798
|
-
});
|
|
939
|
+
mountNode(element, parentNode, context, anchor);
|
|
799
940
|
attributes.forEach(({ name, value, literal }) => {
|
|
800
941
|
literal ? element.setAttribute(name, String(value())) : context.listen(effect(() => element.setAttribute(name, String(value()))));
|
|
801
942
|
});
|
|
@@ -822,11 +963,7 @@ function _renderElement(parentNode, context, tagName, attributes, events) {
|
|
|
822
963
|
*/
|
|
823
964
|
function _renderText(parentNode, context, textFn) {
|
|
824
965
|
const node = document.createTextNode(textFn());
|
|
825
|
-
|
|
826
|
-
context.listen(() => {
|
|
827
|
-
context.removeNode(node);
|
|
828
|
-
parentNode.removeChild(node);
|
|
829
|
-
});
|
|
966
|
+
mountNode(node, parentNode, context);
|
|
830
967
|
context.listen(effect(() => node.textContent = textFn()));
|
|
831
968
|
}
|
|
832
969
|
/**
|
|
@@ -841,9 +978,7 @@ function _renderText(parentNode, context, textFn) {
|
|
|
841
978
|
* @param text - The literal string to render as text content.
|
|
842
979
|
*/
|
|
843
980
|
function _renderLiteralText(parentNode, context, text) {
|
|
844
|
-
|
|
845
|
-
parentNode.appendChild(node);
|
|
846
|
-
context.listen(() => parentNode.removeChild(node));
|
|
981
|
+
mountNode(document.createTextNode(text), parentNode, context);
|
|
847
982
|
}
|
|
848
983
|
//#endregion
|
|
849
984
|
//#region ../packages/core/src/utils/switch.util.ts
|
|
@@ -862,8 +997,6 @@ function _renderLiteralText(parentNode, context, text) {
|
|
|
862
997
|
*
|
|
863
998
|
* @param parentNode - The parent HTML element where the conditional structure is applied.
|
|
864
999
|
* @param parentContext - The parent Context object containing all the variables definition from the Parent Closure
|
|
865
|
-
* @param unwatchFns - Shared array that collects all active cleanup functions.
|
|
866
|
-
* Mutated in place by the underlying {@link _if} call.
|
|
867
1000
|
* @param expression - Function that evaluates the switch expression. Called
|
|
868
1001
|
* reactively inside an effect so that signal reads are tracked.
|
|
869
1002
|
* @param blocks - Ordered list of case branches. Each entry contains:
|
|
@@ -879,4 +1012,4 @@ function _switch(parentNode, parentContext, expression, blocks) {
|
|
|
879
1012
|
})));
|
|
880
1013
|
}
|
|
881
1014
|
//#endregion
|
|
882
|
-
export { BaseWebComponent, Context, Event, Property, WebComponent, _for, _if, _iterationVariables, _renderElement, _renderLiteralText, _renderText, _switch, computed, effect, input, signal };
|
|
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
|
}
|