@xaendar/core 0.6.3 → 0.6.5

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,27 @@ export { Event_2 as Event }
138
152
  */
139
153
  export declare type EventOptions = Beautify<RequireOne<Omit<CustomEventInit, 'detail'>>>;
140
154
 
155
+ /**
156
+ * Creates a reactive conditional structure from a list of branches.
157
+ *
158
+ * Depending on the number of branches provided, the appropriate evaluation strategy
159
+ * is selected:
160
+ * - 1 branch → simple `if` (see {@link handleIf});
161
+ * - 2 branches → `if` / `else` (see {@link handleIfElse});
162
+ * - 3+ branches → `if` / `else if` / ... / `else` (see {@link handleIfElseIf}).
163
+ *
164
+ * The evaluation is wrapped in an {@link effect}, so it is automatically re-executed
165
+ * whenever any signal read by the conditions changes. On each re-execution, the current
166
+ * state (which branch is active) and the related cleanup functions are only updated if
167
+ * the active branch has actually changed.
168
+ *
169
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
170
+ * Mutated in place: the created effect and branch functions are added to and
171
+ * removed from this array.
172
+ * @param blocks - Ordered list of conditional branches to evaluate.
173
+ */
174
+ export declare function _if(unwatchFns: NoArgsVoidFunction[], blocks: Block[]): void;
175
+
141
176
  /**
142
177
  * An `InputSignal` is a specialized `Signal.State` designed for use as a property signal in web components.
143
178
  * It extends the base `State` signal with additional functionality to handle incoming values, such as those from HTML attributes or external sources,
@@ -293,6 +328,45 @@ declare type Signal_2<Value = any> = Signal.State<Value> & {
293
328
  };
294
329
  export { Signal_2 as Signal }
295
330
 
331
+ /**
332
+ * Creates a reactive switch/case structure by converting it into an if/else-if chain
333
+ * and delegating to {@link _if}.
334
+ *
335
+ * Each case block's condition values are compared against the result of the
336
+ * `expression` function using strict equality (`===`). A block with
337
+ * `condition: null` acts as the `default` branch (mapped to the `else` branch
338
+ * in the underlying if/else-if chain). Multiple values per case are supported
339
+ * (e.g. fall-through cases) and matched with `Array.some`.
340
+ *
341
+ * Because it delegates to `_if`, the switch benefits from the same optimisation:
342
+ * the active branch is only re-rendered when it actually changes.
343
+ *
344
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
345
+ * Mutated in place by the underlying {@link _if} call.
346
+ * @param expression - Function that evaluates the switch expression. Called
347
+ * reactively inside an effect so that signal reads are tracked.
348
+ * @param blocks - Ordered list of case branches. Each entry contains:
349
+ * - `condition`: array of values to match against the expression result,
350
+ * or `null` for the default branch.
351
+ * - `block`: function that applies the branch's side effects and returns
352
+ * its cleanup functions.
353
+ */
354
+ export declare function _switch(unwatchFns: NoArgsVoidFunction[], expression: NoArgsFunction<unknown>, blocks: Array<{
355
+ condition: unknown[] | null;
356
+ block: NoArgsFunction<NoArgsVoidFunction[]>;
357
+ }>): void;
358
+
359
+ /**
360
+ * Executes and removes the cleanup functions of the previously active branch.
361
+ *
362
+ * For each local function: removes it from the shared `unwatchFns` array (if present)
363
+ * and then invokes it to release resources / tear down associated subscriptions.
364
+ *
365
+ * @param unwatchFns - Shared array of active cleanup functions, mutated in place.
366
+ * @param localUnwatchFns - Cleanup functions of the branch to deactivate.
367
+ */
368
+ export declare function unwatch(unwatchFns: NoArgsVoidFunction[], localUnwatchFns: NoArgsVoidFunction[]): void;
369
+
296
370
  /**
297
371
  * Decorator to define a web component
298
372
  * @param selector Name or names of the custom element
@@ -94,7 +94,7 @@ function input(value, options) {
94
94
  const transformedValue = transform ? transform(newValue) : newValue;
95
95
  originalSet.call(signal, transformedValue);
96
96
  },
97
- get: signal.get
97
+ get: signal.get.bind(signal)
98
98
  });
99
99
  getter[INPUT_SIGNAL_INSTANCE_SYMBOL] = true;
100
100
  return getter;
@@ -291,7 +291,7 @@ function computed(value, options) {
291
291
  const getter = function() {
292
292
  return signal.get();
293
293
  };
294
- Object.assign(getter, { get: signal.get });
294
+ Object.assign(getter, { get: signal.get.bind(signal) });
295
295
  return getter;
296
296
  }
297
297
  //#endregion
@@ -394,11 +394,187 @@ function signal(value, options) {
394
394
  return signal.get();
395
395
  };
396
396
  Object.assign(getter, {
397
- set: signal.set,
398
- get: signal.get,
397
+ set: signal.set.bind(signal),
398
+ get: signal.get.bind(signal),
399
399
  update: (updater) => signal.set(updater(signal.get()))
400
400
  });
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/switch.utils.ts
550
+ /**
551
+ * Creates a reactive switch/case structure by converting it into an if/else-if chain
552
+ * and delegating to {@link _if}.
553
+ *
554
+ * Each case block's condition values are compared against the result of the
555
+ * `expression` function using strict equality (`===`). A block with
556
+ * `condition: null` acts as the `default` branch (mapped to the `else` branch
557
+ * in the underlying if/else-if chain). Multiple values per case are supported
558
+ * (e.g. fall-through cases) and matched with `Array.some`.
559
+ *
560
+ * Because it delegates to `_if`, the switch benefits from the same optimisation:
561
+ * the active branch is only re-rendered when it actually changes.
562
+ *
563
+ * @param unwatchFns - Shared array that collects all active cleanup functions.
564
+ * Mutated in place by the underlying {@link _if} call.
565
+ * @param expression - Function that evaluates the switch expression. Called
566
+ * reactively inside an effect so that signal reads are tracked.
567
+ * @param blocks - Ordered list of case branches. Each entry contains:
568
+ * - `condition`: array of values to match against the expression result,
569
+ * or `null` for the default branch.
570
+ * - `block`: function that applies the branch's side effects and returns
571
+ * its cleanup functions.
572
+ */
573
+ function _switch(unwatchFns, expression, blocks) {
574
+ _if(unwatchFns, blocks.map(({ condition, block }) => ({
575
+ condition: condition ? () => condition.some((condition) => condition === expression()) : void 0,
576
+ block
577
+ })));
578
+ }
579
+ //#endregion
580
+ export { BaseWebComponent, Event, Property, WebComponent, _if, _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.3",
3
+ "version": "0.6.5",
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.3",
20
- "@xaendar/types": "0.6.3"
19
+ "@xaendar/signals": "0.6.5",
20
+ "@xaendar/types": "0.6.5"
21
21
  }
22
22
  }