@xaendar/core 0.6.4 → 0.6.6

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.
@@ -3,6 +3,7 @@ import { Beautify } from '@xaendar/types';
3
3
  import { ClassDecorator as ClassDecorator_2 } from '@xaendar/types';
4
4
  import { Constructor } from '@xaendar/types';
5
5
  import { EffectOptions } from '@xaendar/signals';
6
+ import { NoArgsFunction } from '@xaendar/types';
6
7
  import { NoArgsVoidFunction } from '@xaendar/types';
7
8
  import { NoArgsVoidFunction as NoArgsVoidFunction_2 } from '@xaendar/types';
8
9
  import { RequireOne } from '@xaendar/types';
@@ -63,6 +64,19 @@ export declare type BaseWebComponentConstructor = Constructor<BaseWebComponent,
63
64
  observedAttributes: string[];
64
65
  }>;
65
66
 
67
+ /**
68
+ * Represents a single branch of a conditional structure (`if` / `else if` / `else`).
69
+ *
70
+ * @property condition - Function that evaluates the branch condition and returns a boolean.
71
+ * If absent, the branch is always considered valid (represents the `else` branch).
72
+ * @property block - Function that, when executed, applies the branch's side effects and
73
+ * returns the list of cleanup (unwatch) functions generated within it.
74
+ */
75
+ declare type Block = {
76
+ condition?: NoArgsFunction<boolean>;
77
+ block: NoArgsFunction<NoArgsVoidFunction[]>;
78
+ };
79
+
66
80
  /**
67
81
  * A read-only reactive value derived from other signals.
68
82
  *
@@ -138,6 +152,37 @@ export { Event_2 as Event }
138
152
  */
139
153
  export declare type EventOptions = Beautify<RequireOne<Omit<CustomEventInit, 'detail'>>>;
140
154
 
155
+ /**
156
+ * Reactively iterates over a list of items, re-running the loop whenever the tracked condition changes.
157
+ * Previous iteration side-effects are cleaned up before each re-evaluation.
158
+ *
159
+ * @param unwatchFns - Array collecting cleanup functions for the parent scope.
160
+ * @param condition - A reactive function that returns the array of items to iterate over.
161
+ * @param forFn - A callback invoked for each item, receiving the item and its index. Must return an array of cleanup functions.
162
+ */
163
+ export declare function _for(unwatchFns: NoArgsVoidFunction[], condition: () => unknown[], forFn: (item: unknown, index: number) => NoArgsVoidFunction[]): void;
164
+
165
+ /**
166
+ * Creates a reactive conditional structure from a list of branches.
167
+ *
168
+ * Depending on the number of branches provided, the appropriate evaluation strategy
169
+ * is selected:
170
+ * - 1 branch → simple `if` (see {@link handleIf});
171
+ * - 2 branches → `if` / `else` (see {@link handleIfElse});
172
+ * - 3+ branches → `if` / `else if` / ... / `else` (see {@link handleIfElseIf}).
173
+ *
174
+ * The evaluation is wrapped in an {@link effect}, so it is automatically re-executed
175
+ * whenever any signal read by the conditions changes. On each re-execution, the current
176
+ * state (which branch is active) and the related cleanup functions are only updated if
177
+ * the active branch has actually changed.
178
+ *
179
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
180
+ * Mutated in place: the created effect and branch functions are added to and
181
+ * removed from this array.
182
+ * @param blocks - Ordered list of conditional branches to evaluate.
183
+ */
184
+ export declare function _if(unwatchFns: NoArgsVoidFunction[], blocks: Block[]): void;
185
+
141
186
  /**
142
187
  * An `InputSignal` is a specialized `Signal.State` designed for use as a property signal in web components.
143
188
  * It extends the base `State` signal with additional functionality to handle incoming values, such as those from HTML attributes or external sources,
@@ -192,6 +237,18 @@ export declare type InputSignalOptions<ActualValue = unknown, IncomingValue = Ac
192
237
  transform?: (value: IncomingValue) => ActualValue;
193
238
  };
194
239
 
240
+ /**
241
+ * Builds a record of iteration context variables for a given index in the loop.
242
+ * Provides the current item, index, and convenience flags (`$first`, `$last`, `$even`, `$odd`).
243
+ *
244
+ * @param items - The full array being iterated.
245
+ * @param index - The current iteration index.
246
+ * @param itemAlias - The template alias name for the current item.
247
+ * @param indexAlias - The template alias name for the current index.
248
+ * @returns A record mapping alias names and built-in variables to their values.
249
+ */
250
+ export declare function _iterationVariables(items: unknown[], index: number, itemAlias: string, indexAlias: string): Record<string, unknown>;
251
+
195
252
  /**
196
253
  * Rapresent the output type returned by an @Event Decorator in a Web Component.
197
254
  *
@@ -293,6 +350,45 @@ declare type Signal_2<Value = any> = Signal.State<Value> & {
293
350
  };
294
351
  export { Signal_2 as Signal }
295
352
 
353
+ /**
354
+ * Creates a reactive switch/case structure by converting it into an if/else-if chain
355
+ * and delegating to {@link _if}.
356
+ *
357
+ * Each case block's condition values are compared against the result of the
358
+ * `expression` function using strict equality (`===`). A block with
359
+ * `condition: null` acts as the `default` branch (mapped to the `else` branch
360
+ * in the underlying if/else-if chain). Multiple values per case are supported
361
+ * (e.g. fall-through cases) and matched with `Array.some`.
362
+ *
363
+ * Because it delegates to `_if`, the switch benefits from the same optimisation:
364
+ * the active branch is only re-rendered when it actually changes.
365
+ *
366
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
367
+ * Mutated in place by the underlying {@link _if} call.
368
+ * @param expression - Function that evaluates the switch expression. Called
369
+ * reactively inside an effect so that signal reads are tracked.
370
+ * @param blocks - Ordered list of case branches. Each entry contains:
371
+ * - `condition`: array of values to match against the expression result,
372
+ * or `null` for the default branch.
373
+ * - `block`: function that applies the branch's side effects and returns
374
+ * its cleanup functions.
375
+ */
376
+ export declare function _switch(unwatchFns: NoArgsVoidFunction[], expression: NoArgsFunction<unknown>, blocks: Array<{
377
+ condition: unknown[] | null;
378
+ block: NoArgsFunction<NoArgsVoidFunction[]>;
379
+ }>): void;
380
+
381
+ /**
382
+ * Executes and removes the cleanup functions of the previously active branch.
383
+ *
384
+ * For each local function: removes it from the shared `unwatchFns` array (if present)
385
+ * and then invokes it to release resources / tear down associated subscriptions.
386
+ *
387
+ * @param unwatchFns - Shared array of active cleanup functions, mutated in place.
388
+ * @param localUnwatchFns - Cleanup functions of the branch to deactivate.
389
+ */
390
+ export declare function unwatch(unwatchFns: NoArgsVoidFunction[], localUnwatchFns: NoArgsVoidFunction[]): void;
391
+
296
392
  /**
297
393
  * Decorator to define a web component
298
394
  * @param selector Name or names of the custom element
@@ -401,4 +401,225 @@ function signal(value, options) {
401
401
  return getter;
402
402
  }
403
403
  //#endregion
404
- export { BaseWebComponent, Event, Property, WebComponent, computed, effect, input, signal };
404
+ //#region ../packages/core/src/utils/if.utils.ts
405
+ /**
406
+ * Creates a reactive conditional structure from a list of branches.
407
+ *
408
+ * Depending on the number of branches provided, the appropriate evaluation strategy
409
+ * is selected:
410
+ * - 1 branch → simple `if` (see {@link handleIf});
411
+ * - 2 branches → `if` / `else` (see {@link handleIfElse});
412
+ * - 3+ branches → `if` / `else if` / ... / `else` (see {@link handleIfElseIf}).
413
+ *
414
+ * The evaluation is wrapped in an {@link effect}, so it is automatically re-executed
415
+ * whenever any signal read by the conditions changes. On each re-execution, the current
416
+ * state (which branch is active) and the related cleanup functions are only updated if
417
+ * the active branch has actually changed.
418
+ *
419
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
420
+ * Mutated in place: the created effect and branch functions are added to and
421
+ * removed from this array.
422
+ * @param blocks - Ordered list of conditional branches to evaluate.
423
+ */
424
+ function _if(unwatchFns, blocks) {
425
+ let state = null;
426
+ let localUnwatchFns = new Array();
427
+ let fn;
428
+ switch (blocks.length) {
429
+ case 1:
430
+ fn = () => handleIf(blocks[0], state, unwatchFns, localUnwatchFns);
431
+ break;
432
+ case 2:
433
+ fn = () => handleIfElse(blocks[0], blocks[1], state, unwatchFns, localUnwatchFns);
434
+ break;
435
+ default: fn = () => handleIfElseIf(blocks, state, unwatchFns, localUnwatchFns);
436
+ }
437
+ const unlistener = effect(() => {
438
+ const retVal = fn();
439
+ if (retVal) {
440
+ state = retVal.state;
441
+ localUnwatchFns = retVal.fns;
442
+ }
443
+ });
444
+ unwatchFns.push(unlistener);
445
+ }
446
+ /**
447
+ * Handles the simple `if` case (a single branch, no `else`).
448
+ *
449
+ * If the condition is true, the branch is activated (state `0`); otherwise, any
450
+ * previously active branch is deactivated by resetting the state to `null` and
451
+ * executing an empty block.
452
+ *
453
+ * @param ifBlock - The `if` branch to evaluate.
454
+ * @param state - The current state (index of the active branch, or `null` if none).
455
+ * @param unwatchFns - Shared array of active cleanup functions.
456
+ * @param localUnwatchFns - Cleanup functions generated by the last branch activation.
457
+ * @returns The new state and new cleanup functions if the active branch changed,
458
+ * otherwise `undefined`.
459
+ */
460
+ function handleIf(ifBlock, state, unwatchFns, localUnwatchFns) {
461
+ return ifBlock.condition() ? checkAndUpdateState(state, 0, ifBlock.block, unwatchFns, localUnwatchFns) : checkAndUpdateState(state, null, () => [], unwatchFns, localUnwatchFns);
462
+ }
463
+ /**
464
+ * Handles the `if` / `else` case (exactly two branches).
465
+ *
466
+ * If the `if` condition is true, the first branch is activated (state `0`); otherwise,
467
+ * the `else` branch is activated (state `1`).
468
+ *
469
+ * @param ifBlock - The `if` branch to evaluate.
470
+ * @param elseBlock - The `else` branch used when the `if` condition is false.
471
+ * @param state - The current state (index of the active branch, or `null` if none).
472
+ * @param unwatchFns - Shared array of active cleanup functions.
473
+ * @param localUnwatchFns - Cleanup functions generated by the last branch activation.
474
+ * @returns The new state and new cleanup functions if the active branch changed,
475
+ * otherwise `undefined`.
476
+ */
477
+ function handleIfElse(ifBlock, elseBlock, state, unwatchFns, localUnwatchFns) {
478
+ return ifBlock.condition() ? checkAndUpdateState(state, 0, ifBlock.block, unwatchFns, localUnwatchFns) : checkAndUpdateState(state, 1, elseBlock.block, unwatchFns, localUnwatchFns);
479
+ }
480
+ /**
481
+ * Handles the general case of an `if` / `else if` / ... / `else` chain (three or more branches).
482
+ *
483
+ * Iterates through the branches in order and activates the first one whose condition is
484
+ * true; a branch without a condition is always considered valid and acts as the final
485
+ * `else`. The state is set to the index of the activated branch.
486
+ *
487
+ * @param blocks - Ordered list of conditional branches to evaluate.
488
+ * @param state - The current state (index of the active branch, or `null` if none).
489
+ * @param unwatchFns - Shared array of active cleanup functions.
490
+ * @param localUnwatchFns - Cleanup functions generated by the last branch activation.
491
+ * @returns The new state and new cleanup functions if the active branch changed,
492
+ * otherwise `undefined`. Also returns `undefined` if no branch matches.
493
+ */
494
+ function handleIfElseIf(blocks, state, unwatchFns, localUnwatchFns) {
495
+ for (let i = 0; i < blocks.length; i++) {
496
+ const { condition, block } = blocks[i];
497
+ if (!condition || condition()) return checkAndUpdateState(state, i, block, unwatchFns, localUnwatchFns);
498
+ }
499
+ }
500
+ /**
501
+ * Updates the conditional structure state only when the active branch changes.
502
+ *
503
+ * If `newState` differs from `state`:
504
+ * 1. runs cleanup of the previous branch's functions (see {@link unwatch});
505
+ * 2. executes the new branch's block via {@link Signal.subtle.untrack} to avoid
506
+ * creating unwanted reactive dependencies during block execution;
507
+ * 3. registers the new cleanup functions in the shared `unwatchFns` array.
508
+ *
509
+ * If the branch has not changed, no operation is performed.
510
+ *
511
+ * @param state - The current state (index of the active branch, or `null` if none).
512
+ * @param newState - The new state to set (index of the branch to activate, or `null`).
513
+ * @param conditionalBlockFn - Branch function to execute, which returns its own
514
+ * cleanup functions.
515
+ * @param unwatchFns - Shared array of active cleanup functions.
516
+ * @param localUnwatchFns - Cleanup functions of the currently active branch, to be
517
+ * removed before activating the new branch.
518
+ * @returns Il nuovo stato e le nuove funzioni di cleanup se il ramo è cambiato, altrimenti
519
+ * `undefined`.
520
+ */
521
+ function checkAndUpdateState(state, newState, conditionalBlockFn, unwatchFns, localUnwatchFns) {
522
+ if (state !== newState) {
523
+ unwatch(unwatchFns, localUnwatchFns);
524
+ localUnwatchFns = Signal.subtle.untrack(conditionalBlockFn);
525
+ unwatchFns.push(...localUnwatchFns);
526
+ return {
527
+ state: newState,
528
+ fns: localUnwatchFns
529
+ };
530
+ }
531
+ }
532
+ /**
533
+ * Executes and removes the cleanup functions of the previously active branch.
534
+ *
535
+ * For each local function: removes it from the shared `unwatchFns` array (if present)
536
+ * and then invokes it to release resources / tear down associated subscriptions.
537
+ *
538
+ * @param unwatchFns - Shared array of active cleanup functions, mutated in place.
539
+ * @param localUnwatchFns - Cleanup functions of the branch to deactivate.
540
+ */
541
+ function unwatch(unwatchFns, localUnwatchFns) {
542
+ for (const fn of localUnwatchFns) {
543
+ const index = unwatchFns.indexOf(fn);
544
+ if (index !== -1) unwatchFns.splice(index, 1);
545
+ fn();
546
+ }
547
+ }
548
+ //#endregion
549
+ //#region ../packages/core/src/utils/for.utils.ts
550
+ /**
551
+ * Reactively iterates over a list of items, re-running the loop whenever the tracked condition changes.
552
+ * Previous iteration side-effects are cleaned up before each re-evaluation.
553
+ *
554
+ * @param unwatchFns - Array collecting cleanup functions for the parent scope.
555
+ * @param condition - A reactive function that returns the array of items to iterate over.
556
+ * @param forFn - A callback invoked for each item, receiving the item and its index. Must return an array of cleanup functions.
557
+ */
558
+ function _for(unwatchFns, condition, forFn) {
559
+ const localUnwatchFns = new Array();
560
+ const unlistener = effect(() => {
561
+ unwatch(unwatchFns, localUnwatchFns);
562
+ const items = condition();
563
+ Signal.subtle.untrack(() => {
564
+ for (let i = 0; i < items.length; i++) {
565
+ localUnwatchFns.push(...forFn(items[i], i));
566
+ unwatchFns.push(...localUnwatchFns);
567
+ }
568
+ });
569
+ });
570
+ unwatchFns.push(unlistener);
571
+ }
572
+ /**
573
+ * Builds a record of iteration context variables for a given index in the loop.
574
+ * Provides the current item, index, and convenience flags (`$first`, `$last`, `$even`, `$odd`).
575
+ *
576
+ * @param items - The full array being iterated.
577
+ * @param index - The current iteration index.
578
+ * @param itemAlias - The template alias name for the current item.
579
+ * @param indexAlias - The template alias name for the current index.
580
+ * @returns A record mapping alias names and built-in variables to their values.
581
+ */
582
+ function _iterationVariables(items, index, itemAlias, indexAlias) {
583
+ const even = index % 2 === 0;
584
+ return {
585
+ [itemAlias]: items[index],
586
+ [indexAlias]: index,
587
+ $first: index === 0,
588
+ $last: index === items.length - 1,
589
+ $even: even,
590
+ $odd: !even
591
+ };
592
+ }
593
+ //#endregion
594
+ //#region ../packages/core/src/utils/switch.utils.ts
595
+ /**
596
+ * Creates a reactive switch/case structure by converting it into an if/else-if chain
597
+ * and delegating to {@link _if}.
598
+ *
599
+ * Each case block's condition values are compared against the result of the
600
+ * `expression` function using strict equality (`===`). A block with
601
+ * `condition: null` acts as the `default` branch (mapped to the `else` branch
602
+ * in the underlying if/else-if chain). Multiple values per case are supported
603
+ * (e.g. fall-through cases) and matched with `Array.some`.
604
+ *
605
+ * Because it delegates to `_if`, the switch benefits from the same optimisation:
606
+ * the active branch is only re-rendered when it actually changes.
607
+ *
608
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
609
+ * Mutated in place by the underlying {@link _if} call.
610
+ * @param expression - Function that evaluates the switch expression. Called
611
+ * reactively inside an effect so that signal reads are tracked.
612
+ * @param blocks - Ordered list of case branches. Each entry contains:
613
+ * - `condition`: array of values to match against the expression result,
614
+ * or `null` for the default branch.
615
+ * - `block`: function that applies the branch's side effects and returns
616
+ * its cleanup functions.
617
+ */
618
+ function _switch(unwatchFns, expression, blocks) {
619
+ _if(unwatchFns, blocks.map(({ condition, block }) => ({
620
+ condition: condition ? () => condition.some((condition) => condition === expression()) : void 0,
621
+ block
622
+ })));
623
+ }
624
+ //#endregion
625
+ export { BaseWebComponent, Event, Property, WebComponent, _for, _if, _iterationVariables, _switch, computed, effect, input, signal, unwatch };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaendar/core",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
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.6.4",
20
- "@xaendar/types": "0.6.4"
19
+ "@xaendar/signals": "0.6.6",
20
+ "@xaendar/types": "0.6.6"
21
21
  }
22
22
  }