@xaendar/core 0.5.7 → 0.6.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.
@@ -2,10 +2,11 @@ import { AccessorDecorator } from '@xaendar/types';
2
2
  import { Beautify } from '@xaendar/types';
3
3
  import { ClassDecorator as ClassDecorator_2 } from '@xaendar/types';
4
4
  import { Constructor } from '@xaendar/types';
5
+ import { EffectOptions } from '@xaendar/signals';
5
6
  import { NoArgsVoidFunction } from '@xaendar/types';
7
+ import { NoArgsVoidFunction as NoArgsVoidFunction_2 } from '@xaendar/types';
6
8
  import { RequireOne } from '@xaendar/types';
7
9
  import { SignalOptions } from '@xaendar/signals';
8
- import { SignalOptions as SignalOptions_2 } from '@xaendar/signals';
9
10
  import { VoidFunction as VoidFunction_2 } from '@xaendar/types';
10
11
 
11
12
  /**
@@ -62,6 +63,73 @@ export declare type BaseWebComponentConstructor = Constructor<BaseWebComponent,
62
63
  observedAttributes: string[];
63
64
  }>;
64
65
 
66
+ /**
67
+ * A read-only reactive value derived from other signals.
68
+ *
69
+ * Wraps {@link Signal.Computed} and is also callable as a function to read the
70
+ * current computed value.
71
+ *
72
+ * @template Value - The type of the computed value. Defaults to `any`.
73
+ */
74
+ export declare type Computed<Value = any> = Signal.Computed<Value> & {
75
+ /**
76
+ * Reads the current computed value, recomputing it if any dependency changed.
77
+ *
78
+ * @returns The current value of type `Value`.
79
+ */
80
+ (): Value;
81
+ };
82
+
83
+ /**
84
+ * Creates a read-only computed signal.
85
+ *
86
+ * Returns a callable getter that reads the current value, augmented with the
87
+ * underlying {@link Signal.State} API.
88
+ *
89
+ * @template Value - The type of the computed value. Defaults to `any`.
90
+ * @param value - The initial value.
91
+ * @param options - Configuration options for the signal.
92
+ * @returns A {@link Computed} instance.
93
+ */
94
+ export declare function computed<Value = any>(value: Value, options?: SignalOptions<Value>): Computed<Value>;
95
+
96
+ /**
97
+ * Runs a side-effectful function and automatically re-runs it whenever any
98
+ * Signal read during its execution changes.
99
+ *
100
+ * Internally, `effect` wraps the user callback inside a `Computed` node
101
+ * (for dependency tracking) and observes it with a `Watcher` (for push
102
+ * notifications). When any tracked dependency changes, the `Watcher`
103
+ * schedules a microtask that re-evaluates the `Computed`, which in turn
104
+ * re-runs the user callback and re-registers the new set of dependencies.
105
+ *
106
+ * The returned disposer function stops the effect: it unwatches the internal
107
+ * `Computed` from the `Watcher`, severing all dependency subscriptions so
108
+ * the callback is never called again and the graph nodes can be
109
+ * garbage-collected.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const count = new State(0);
114
+ *
115
+ * const stop = effect(() => {
116
+ * console.log('count is', count.get());
117
+ * });
118
+ * // logs: "count is 0"
119
+ *
120
+ * count.set(1); // logs: "count is 1"
121
+ * count.set(2); // logs: "count is 2"
122
+ *
123
+ * stop(); // no more logs
124
+ * count.set(3); // silent
125
+ * ```
126
+ *
127
+ * @param fn - The side-effectful function to run. Any Signal read inside it
128
+ * is tracked as a dependency.
129
+ * @returns A disposer function that, when called, permanently stops the effect.
130
+ */
131
+ export declare function effect(fn: NoArgsVoidFunction_2, options?: EffectOptions): NoArgsVoidFunction_2;
132
+
65
133
  declare function Event_2<Class extends BaseWebComponent, Data = void>(options?: EventOptions): AccessorDecorator<Class, Output<Data>>;
66
134
  export { Event_2 as Event }
67
135
 
@@ -75,32 +143,34 @@ export declare type EventOptions = Beautify<RequireOne<Omit<CustomEventInit, 'de
75
143
  * It extends the base `State` signal with additional functionality to handle incoming values, such as those from HTML attributes or external sources,
76
144
  * and allows for optional transformation of these values before they are stored in the signal.
77
145
  */
78
- export declare class InputSignal<ActualValue = unknown, IncomingValue = ActualValue> extends Signal.State<ActualValue> {
79
- /**
80
- * Optional transform function to convert incoming values to the actual type stored in the signal.
81
- */
82
- private _transform?;
146
+ export declare function input<ActualValue = unknown, IncomingValue = ActualValue>(value?: ActualValue, options?: InputSignalOptions<ActualValue, IncomingValue>): InputSignal<ActualValue, IncomingValue>;
147
+
148
+ /**
149
+ * A reactive value sourced from outside the component (e.g. an attribute).
150
+ *
151
+ * Wraps {@link Signal.State} without its `set` method (inputs are not set
152
+ * directly) and is callable as a function to read the current value. An
153
+ * optional {@link InputSignalOptions.transform} can convert the incoming value
154
+ * into the actual stored value.
155
+ *
156
+ * @template ActualValue - The internal type stored by the signal. Defaults to `unknown`.
157
+ * @template IncomingValue - The raw type received from outside. Defaults to `ActualValue`.
158
+ */
159
+ export declare type InputSignal<ActualValue = unknown, IncomingValue = ActualValue> = Omit<Signal.State<ActualValue>, 'set'> & {
83
160
  /**
84
- * Creates a new `InputSignal` instance.
161
+ * Reads the current value of the input signal.
85
162
  *
86
- * @param initialValue - The initial value of the signal.
87
- * @param options - Optional configuration:
88
- * - `equals` — custom equality function; defaults to `Object.is`.
89
- * - `watched` — called when the signal gains its first sink.
90
- * - `unwatched` — called when the signal loses its last sink.
91
- * - `transform` — function to transform incoming values before setting the signal's value.
163
+ * @returns The current value of type `ActualValue`.
92
164
  */
93
- constructor(value?: ActualValue, options?: SignalOptions<ActualValue> & {
94
- transform?: (value: IncomingValue) => ActualValue;
95
- });
165
+ (): ActualValue;
96
166
  /**
97
- * Set a new value to the signal, applying the transform function if provided.
98
- * @param newValue - The new value to set.
99
- * @throws If `frozen` is `true` writes are forbidden while a protected
100
- * callback is executing.
167
+ * Creates a new input signal.
168
+ *
169
+ * @param value - The initial incoming value.
170
+ * @param options - Optional configuration, including a `transform` function.
101
171
  */
102
- set(newValue: IncomingValue, symbol: symbol): void;
103
- }
172
+ new (value: IncomingValue, options?: InputSignalOptions<ActualValue, IncomingValue>): InputSignal<ActualValue, IncomingValue>;
173
+ };
104
174
 
105
175
  /**
106
176
  * Options used to configure an `InputSignal`.
@@ -112,7 +182,7 @@ export declare class InputSignal<ActualValue = unknown, IncomingValue = ActualVa
112
182
  * @template ActualValue - The internal type stored by the signal. Defaults to `unknown`.
113
183
  * @template IncomingValue - The raw type received from outside (e.g. from an attribute). Defaults to `ActualValue`.
114
184
  */
115
- export declare type InputSignalOptions<ActualValue = unknown, IncomingValue = ActualValue> = SignalOptions_2<ActualValue> & {
185
+ export declare type InputSignalOptions<ActualValue = unknown, IncomingValue = ActualValue> = SignalOptions<ActualValue> & {
116
186
  /**
117
187
  * Optional function to transform the incoming value before it is stored.
118
188
  *
@@ -185,6 +255,44 @@ declare type PropertyDecoratorOptionsWithRequired<ActualValue = unknown, Incomin
185
255
  required: true;
186
256
  };
187
257
 
258
+ /**
259
+ * Creates a writable reactive signal.
260
+ *
261
+ * Returns a callable getter that reads the current value, augmented with the
262
+ * underlying {@link Signal.State} API and an `update` method to derive the next
263
+ * value from the previous one.
264
+ *
265
+ * @template T - The type of the stored value. Defaults to `any`.
266
+ * @param value - The initial value of the signal.
267
+ * @param options - Configuration options for the signal.
268
+ * @returns A {@link SignalType} instance.
269
+ */
270
+ export declare function signal<T = any>(value: T, options?: SignalOptions<T>): Signal_2<T>;
271
+
272
+ /**
273
+ * A writable reactive value.
274
+ *
275
+ * Wraps {@link Signal.State} and is also callable as a function to set a new
276
+ * value directly.
277
+ *
278
+ * @template Value - The type of the stored value. Defaults to `any`.
279
+ */
280
+ declare type Signal_2<Value = any> = Signal.State<Value> & {
281
+ /**
282
+ * Sets a new value for the signal.
283
+ *
284
+ * @param value - The new value to store.
285
+ */
286
+ (value: Value): void;
287
+ /**
288
+ * Updates the signal value based on its previous value.
289
+ *
290
+ * @param updater - Function receiving the previous value and returning the next one.
291
+ */
292
+ update(updater: (prev: Value) => Value): void;
293
+ };
294
+ export { Signal_2 as Signal }
295
+
188
296
  /**
189
297
  * Decorator to define a web component
190
298
  * @param selector Name or names of the custom element
@@ -33,6 +33,25 @@ function Event(options) {
33
33
  //#region ../packages/core/src/costants.ts
34
34
  var INTERNAL_OBSERVED_ATTRIBUTES = `observedAttributes`;
35
35
  //#endregion
36
+ //#region ../packages/core/src/signals/input/input-instance.symbol.ts
37
+ /**
38
+ * This file defines a unique symbol used to identify instances of input signals within the framework.
39
+ * The symbol serves as a marker to ensure that only valid input signal instances are recognized and processed.
40
+ *
41
+ * @internal
42
+ */
43
+ var INPUT_SIGNAL_INSTANCE_SYMBOL = Symbol(`InputSignalInstance`);
44
+ /**
45
+ * Type guard function to check if a given instance is an InputSignal.
46
+ * This function checks for the presence of the INPUT_SIGNAL_INSTANCE_SYMBOL on the instance,
47
+ * which is set to true for valid InputSignal instances.
48
+ * @param instance - The object to check for being an InputSignal instance.
49
+ * @returns A boolean indicating whether the instance is an InputSignal.
50
+ */
51
+ function isInputSignal(instance) {
52
+ return instance?.[INPUT_SIGNAL_INSTANCE_SYMBOL];
53
+ }
54
+ //#endregion
36
55
  //#region ../packages/core/src/signals/input/input-set.symbol.ts
37
56
  /**
38
57
  * This symbol is used to call the set method of the InputSignal
@@ -55,45 +74,29 @@ function assertPrivateContext(symbol) {
55
74
  if (symbol !== INPUT_SIGNAL_SET_SYMBOL) throw new Error("Invalid symbol for InputSignal set method");
56
75
  }
57
76
  //#endregion
58
- //#region ../packages/core/src/signals/input/input.model.ts
77
+ //#region ../packages/core/src/signals/input/input.ts
59
78
  /**
60
79
  * An `InputSignal` is a specialized `Signal.State` designed for use as a property signal in web components.
61
80
  * It extends the base `State` signal with additional functionality to handle incoming values, such as those from HTML attributes or external sources,
62
81
  * and allows for optional transformation of these values before they are stored in the signal.
63
82
  */
64
- var InputSignal = class extends Signal.State {
65
- /**
66
- * Optional transform function to convert incoming values to the actual type stored in the signal.
67
- */
68
- _transform;
69
- /**
70
- * Creates a new `InputSignal` instance.
71
- *
72
- * @param initialValue - The initial value of the signal.
73
- * @param options - Optional configuration:
74
- * - `equals` — custom equality function; defaults to `Object.is`.
75
- * - `watched` — called when the signal gains its first sink.
76
- * - `unwatched` — called when the signal loses its last sink.
77
- * - `transform` — function to transform incoming values before setting the signal's value.
78
- */
79
- constructor(value, options) {
80
- const transform = options?.transform;
81
- delete options?.transform;
82
- super(value, options);
83
- this._transform = transform;
84
- }
85
- /**
86
- * Set a new value to the signal, applying the transform function if provided.
87
- * @param newValue - The new value to set.
88
- * @throws If `frozen` is `true` — writes are forbidden while a protected
89
- * callback is executing.
90
- */
91
- set(newValue, symbol) {
83
+ function input(value, options) {
84
+ const transform = options?.transform;
85
+ delete options?.transform;
86
+ const signal = new Signal.State(value, options);
87
+ const originalSet = signal.set;
88
+ const getter = function() {
89
+ signal.get();
90
+ };
91
+ Object.assign(getter, signal);
92
+ Object.assign(signal, { set(newValue, symbol) {
92
93
  assertPrivateContext(symbol);
93
- const transformedValue = this._transform ? this._transform(newValue) : newValue;
94
- super.set(transformedValue);
95
- }
96
- };
94
+ const transformedValue = transform ? transform(newValue) : newValue;
95
+ originalSet.call(signal, transformedValue);
96
+ } });
97
+ getter[INPUT_SIGNAL_INSTANCE_SYMBOL] = true;
98
+ return getter;
99
+ }
97
100
  //#endregion
98
101
  //#region ../packages/core/src/decorators/property.decorator.ts
99
102
  var propertyDecoratorOptionsWithRequiredBrand = Symbol("PropertyDecoratorOptionsWithRequiredBrand");
@@ -110,7 +113,7 @@ function createPropertyDecorator(value, options) {
110
113
  actualValue = value;
111
114
  actualOptions = options;
112
115
  } else actualOptions = value;
113
- const signal = new InputSignal(actualValue, {
116
+ const signal = input(actualValue, {
114
117
  equals: actualOptions?.equals,
115
118
  watched: actualOptions?.watched,
116
119
  unwatched: actualOptions?.unwatched,
@@ -241,7 +244,7 @@ var BaseWebComponent = class extends HTMLElement {
241
244
  attributeChangedCallback(name, _oldValue, newValue) {
242
245
  const context = this;
243
246
  if (!(name in context)) throw new Error(`Attribute ${name} is not associated to any property`);
244
- if (!(context[name] instanceof InputSignal)) throw new Error(`Property ${name} is not an InputSignal`);
247
+ if (!isInputSignal(context[name])) throw new Error(`Property ${name} is not an InputSignal`);
245
248
  context[name].set(newValue, INPUT_SIGNAL_SET_SYMBOL);
246
249
  }
247
250
  /**
@@ -269,4 +272,128 @@ var BaseWebComponent = class extends HTMLElement {
269
272
  }
270
273
  };
271
274
  //#endregion
272
- export { BaseWebComponent, Event, InputSignal, Property, WebComponent };
275
+ //#region ../packages/core/src/signals/computed/computed.ts
276
+ /**
277
+ * Creates a read-only computed signal.
278
+ *
279
+ * Returns a callable getter that reads the current value, augmented with the
280
+ * underlying {@link Signal.State} API.
281
+ *
282
+ * @template Value - The type of the computed value. Defaults to `any`.
283
+ * @param value - The initial value.
284
+ * @param options - Configuration options for the signal.
285
+ * @returns A {@link Computed} instance.
286
+ */
287
+ function computed(value, options) {
288
+ const signal = new Signal.State(value, options);
289
+ const getter = function() {
290
+ signal.get();
291
+ };
292
+ Object.assign(getter, signal);
293
+ return getter;
294
+ }
295
+ //#endregion
296
+ //#region ../packages/core/src/signals/effect/effect.ts
297
+ /**
298
+ * Runs a side-effectful function and automatically re-runs it whenever any
299
+ * Signal read during its execution changes.
300
+ *
301
+ * Internally, `effect` wraps the user callback inside a `Computed` node
302
+ * (for dependency tracking) and observes it with a `Watcher` (for push
303
+ * notifications). When any tracked dependency changes, the `Watcher`
304
+ * schedules a microtask that re-evaluates the `Computed`, which in turn
305
+ * re-runs the user callback and re-registers the new set of dependencies.
306
+ *
307
+ * The returned disposer function stops the effect: it unwatches the internal
308
+ * `Computed` from the `Watcher`, severing all dependency subscriptions so
309
+ * the callback is never called again and the graph nodes can be
310
+ * garbage-collected.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * const count = new State(0);
315
+ *
316
+ * const stop = effect(() => {
317
+ * console.log('count is', count.get());
318
+ * });
319
+ * // logs: "count is 0"
320
+ *
321
+ * count.set(1); // logs: "count is 1"
322
+ * count.set(2); // logs: "count is 2"
323
+ *
324
+ * stop(); // no more logs
325
+ * count.set(3); // silent
326
+ * ```
327
+ *
328
+ * @param fn - The side-effectful function to run. Any Signal read inside it
329
+ * is tracked as a dependency.
330
+ * @returns A disposer function that, when called, permanently stops the effect.
331
+ */
332
+ function effect(fn, options) {
333
+ /**
334
+ * Wrap the user callback in a Computed so that automatic dependency
335
+ * tracking (via pushComputed / popComputed) works for free.
336
+ * The Computed always returns `undefined` — we only care about the
337
+ * side-effects and the tracked sources, not the value.
338
+ */
339
+ const computed = new Signal.Computed(() => fn());
340
+ let needsEnqueue = true;
341
+ /**
342
+ * The Watcher is notified synchronously as soon as any tracked dependency
343
+ * changes. Its job is purely to schedule the re-execution; the actual
344
+ * re-evaluation happens asynchronously in a microtask so that multiple
345
+ * synchronous signal updates are batched into a single re-run.
346
+ */
347
+ const watcher = new Signal.subtle.Watcher(() => {
348
+ if (needsEnqueue) {
349
+ needsEnqueue = false;
350
+ queueMicrotask(() => {
351
+ needsEnqueue = true;
352
+ options?.onBeforeRun?.();
353
+ watcher.getPending().forEach((computed) => computed.get());
354
+ options?.onAfterRun?.();
355
+ watcher.watch();
356
+ });
357
+ }
358
+ });
359
+ options?.onBeforeRun?.();
360
+ watcher.watch(computed);
361
+ computed.get();
362
+ options?.onAfterRun?.();
363
+ /**
364
+ * Disposer — call this to permanently stop the effect.
365
+ *
366
+ * Unwatching the Computed tears down the entire live dependency chain
367
+ * (Watcher → Computed → all sources), preventing any further
368
+ * notifications and allowing GC.
369
+ */
370
+ return () => {
371
+ options?.onCleanup?.();
372
+ watcher.unwatch(computed);
373
+ };
374
+ }
375
+ //#endregion
376
+ //#region ../packages/core/src/signals/signal/signal.ts
377
+ /**
378
+ * Creates a writable reactive signal.
379
+ *
380
+ * Returns a callable getter that reads the current value, augmented with the
381
+ * underlying {@link Signal.State} API and an `update` method to derive the next
382
+ * value from the previous one.
383
+ *
384
+ * @template T - The type of the stored value. Defaults to `any`.
385
+ * @param value - The initial value of the signal.
386
+ * @param options - Configuration options for the signal.
387
+ * @returns A {@link SignalType} instance.
388
+ */
389
+ function signal(value, options) {
390
+ const signal = new Signal.State(value, options);
391
+ const getter = function() {
392
+ signal.get();
393
+ };
394
+ Object.assign(getter, signal);
395
+ Object.assign(getter, { update: (updater) => signal.set(updater(signal.get())) });
396
+ return getter;
397
+ }
398
+ //#endregion
399
+ export { BaseWebComponent, Event, Property, WebComponent, computed, effect, input, signal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaendar/core",
3
- "version": "0.5.7",
3
+ "version": "0.6.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.5.7",
20
- "@xaendar/types": "0.5.7"
19
+ "@xaendar/signals": "0.6.0",
20
+ "@xaendar/types": "0.6.0"
21
21
  }
22
22
  }