j-templates 7.0.67 → 7.0.69

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.
@@ -25,11 +25,11 @@ export declare class Component<D = void, T = void, E = {}> {
25
25
  /**
26
26
  * Internal scoped Observable for component state.
27
27
  */
28
- protected get Scope(): IObservableScope<D | Promise<D>>;
28
+ protected get Scope(): IObservableScope<D>;
29
29
  /**
30
30
  * Current data value from the scoped Observable.
31
31
  */
32
- protected get Data(): D | Promise<D>;
32
+ protected get Data(): D;
33
33
  /**
34
34
  * Accessor for the component's virtual node.
35
35
  */
@@ -1,39 +1,138 @@
1
1
  import { Emitter, EmitterCallback } from "../../Utils/emitter";
2
+ /**
3
+ * Represents a static (non-reactive) observable scope.
4
+ * Static scopes are immutable and do not track dependencies or emit updates.
5
+ * @template T The type of value stored in the scope.
6
+ */
2
7
  interface IStaticObservableScope<T> {
3
8
  type: "static";
4
9
  value: T;
5
10
  }
11
+ /**
12
+ * Represents a dynamic (reactive) observable scope.
13
+ * Dynamic scopes track dependencies, detect when values change, and emit updates.
14
+ * @template T The type of value stored in the scope.
15
+ */
6
16
  interface IDynamicObservableScope<T> {
7
17
  type: "dynamic";
18
+ /** Whether the scope's getter function is async */
8
19
  async: boolean;
20
+ /** Whether updates are batched (true) or immediate (false) */
9
21
  greedy: boolean;
22
+ /** Whether the scope needs recomputation */
10
23
  dirty: boolean;
24
+ /** Whether the scope has been destroyed */
11
25
  destroyed: boolean;
26
+ /** Function that returns the scope's current or computed value */
12
27
  getFunction: () => Promise<T> | T;
28
+ /** Callback invoked when dependencies change */
13
29
  setCallback: EmitterCallback;
30
+ /** Current cached value */
14
31
  value: T;
32
+ /** Emitter for notifying listeners of value changes */
15
33
  emitter: Emitter;
34
+ /** Array of emitters this scope listens to for dependency changes */
16
35
  emitters: (Emitter | null)[];
36
+ /** Emitter for notifying when the scope is destroyed */
17
37
  onDestroyed: Emitter | null;
38
+ /** Map of nested calc scopes created during this scope's execution */
18
39
  calcScopes: {
19
40
  [id: string]: IObservableScope<unknown> | null;
20
41
  } | null;
21
42
  }
43
+ /**
44
+ * A reactive or static scope containing a value.
45
+ * @template T The type of value stored in the scope.
46
+ */
22
47
  export type IObservableScope<T> = IStaticObservableScope<T> | IDynamicObservableScope<T>;
48
+ /**
49
+ * Creates a computed scope that acts as a gatekeeper for parent scope emissions.
50
+ * If this scope's value doesn't change (=== comparison) it won't emit.
51
+ *
52
+ * Useful for optimizing expensive operations driven by key values (e.g., sort keys).
53
+ * Scopes are memoized by ID and reused across multiple reads within a single parent scope evaluation.
54
+ * Defaults to "default" ID - provide custom IDs when using multiple calc scopes.
55
+ *
56
+ * Only works within a watch context (during another scope's execution).
57
+ * Always creates greedy scopes that batch updates via microtask queue.
58
+ * @template T The type of value returned by the callback.
59
+ * @param callback The function to compute the derived value.
60
+ * @param idOverride Optional custom ID for memoization when using multiple calc scopes.
61
+ * @returns The computed value, reusing existing scope if available.
62
+ */
23
63
  export declare function CalcScope<T>(callback: () => T, idOverride?: string): T;
24
64
  export declare namespace ObservableScope {
65
+ /**
66
+ * Creates a new observable scope from a value function.
67
+ * @template T The type of value returned by the function.
68
+ * @param valueFunction Function that returns the scope's value. Can be async.
69
+ * @param greedy Whether updates should be batched via microtask queue. Defaults to false.
70
+ * @param force If true, always creates a dynamic scope even without dependencies. Defaults to false.
71
+ * @returns A new observable scope.
72
+ */
25
73
  function Create<T>(valueFunction: {
26
74
  (): T | Promise<T>;
27
75
  }, greedy?: boolean, force?: boolean): IObservableScope<T>;
76
+ /**
77
+ * Registers an emitter as a dependency for the current watch context.
78
+ * @param emitter The emitter to register as a dependency.
79
+ */
28
80
  function Register(emitter: Emitter): void;
81
+ /**
82
+ * Gets a scope's current value without registering as a dependency.
83
+ * @template T The type of value stored in the scope.
84
+ * @param scope The scope to peek at.
85
+ * @returns The scope's current value.
86
+ */
29
87
  function Peek<T>(scope: IObservableScope<T>): T;
88
+ /**
89
+ * Gets a scope's current value and registers as a dependency.
90
+ * @template T The type of value stored in the scope.
91
+ * @param scope The scope to get the value from.
92
+ * @returns The scope's current value.
93
+ */
30
94
  function Value<T>(scope: IObservableScope<T>): T;
95
+ /**
96
+ * Registers a scope as a dependency without retrieving its value.
97
+ * @template T The type of value stored in the scope.
98
+ * @param scope The scope to register as a dependency.
99
+ */
31
100
  function Touch<T>(scope: IObservableScope<T>): void;
101
+ /**
102
+ * Subscribes to changes on a dynamic scope.
103
+ * @template T The type of value stored in the scope.
104
+ * @param scope The scope to watch for changes.
105
+ * @param callback Function to invoke when the scope's value changes.
106
+ */
32
107
  function Watch<T>(scope: IObservableScope<T>, callback: EmitterCallback<[IObservableScope<T>]>): void;
108
+ /**
109
+ * Unsubscribes from changes on a dynamic scope.
110
+ * @template T The type of value stored in the scope.
111
+ * @param scope The scope to stop watching.
112
+ * @param callback The callback function to remove.
113
+ */
33
114
  function Unwatch<T>(scope: IObservableScope<T>, callback: EmitterCallback<[IObservableScope<T>]>): void;
115
+ /**
116
+ * Registers a callback to be invoked when the scope is destroyed.
117
+ * @param scope The scope to monitor for destruction.
118
+ * @param callback Function to invoke when the scope is destroyed.
119
+ */
34
120
  function OnDestroyed(scope: IObservableScope<unknown>, callback: EmitterCallback): void;
121
+ /**
122
+ * Marks a scope as dirty, triggering recomputation on next access or batch.
123
+ * @param scope The scope to mark for update.
124
+ */
35
125
  function Update(scope: IObservableScope<any>): void;
126
+ /**
127
+ * Destroys a scope, cleaning up all resources and dependencies.
128
+ * @template T The type of value stored in the scope.
129
+ * @param scope The scope to destroy.
130
+ */
36
131
  function Destroy<T>(scope: IObservableScope<T>): void;
132
+ /**
133
+ * Destroys multiple scopes, cleaning up their resources.
134
+ * @param scopes Array of scopes to destroy.
135
+ */
37
136
  function DestroyAll(scopes: IObservableScope<unknown>[]): void;
38
137
  }
39
138
  export {};
@@ -5,6 +5,14 @@ exports.CalcScope = CalcScope;
5
5
  const array_1 = require("../../Utils/array");
6
6
  const emitter_1 = require("../../Utils/emitter");
7
7
  const functions_1 = require("../../Utils/functions");
8
+ /**
9
+ * Creates a dynamic (reactive) observable scope.
10
+ * @template T The type of value stored in the scope.
11
+ * @param getFunction Function that returns the scope's value. May be async.
12
+ * @param greedy Whether updates should be batched via microtask queue.
13
+ * @param value Initial value for the scope (null for async functions).
14
+ * @returns A new dynamic observable scope.
15
+ */
8
16
  function CreateDynamicScope(getFunction, greedy, value) {
9
17
  const async = (0, functions_1.IsAsync)(getFunction);
10
18
  const scope = {
@@ -25,6 +33,13 @@ function CreateDynamicScope(getFunction, greedy, value) {
25
33
  };
26
34
  return scope;
27
35
  }
36
+ /**
37
+ * Creates a static (non-reactive) observable scope.
38
+ * Static scopes are optimized for values that never change and don't need dependency tracking.
39
+ * @template T The type of value stored in the scope.
40
+ * @param initialValue The immutable value to store in the scope.
41
+ * @returns A new static observable scope.
42
+ */
28
43
  function CreateStaticScope(initialValue) {
29
44
  return {
30
45
  type: "static",
@@ -50,6 +65,11 @@ function OnSetQueued(scope) {
50
65
  queueMicrotask(ProcessScopeQueue);
51
66
  scopeQueue.push(scope);
52
67
  }
68
+ /**
69
+ * Marks a scope as dirty and triggers update behavior based on scope type.
70
+ * @param scope The scope to mark for update.
71
+ * @returns true if the scope is static or destroyed, false if queued or non-greedy scope emitted.
72
+ */
53
73
  function OnSet(scope) {
54
74
  if (scope.type === "static" || scope.destroyed)
55
75
  return true;
@@ -63,7 +83,24 @@ function OnSet(scope) {
63
83
  emitter_1.Emitter.Emit(scope.emitter, scope);
64
84
  return false;
65
85
  }
86
+ /**
87
+ * Registers an emitter as a dependency for the current watch context.
88
+ * Tracks which emitters are accessed during a scope's execution.
89
+ * @param emitter The emitter to register as a dependency.
90
+ */
66
91
  function RegisterEmitter(emitter) {
92
+ if (watchState === null)
93
+ return;
94
+ if (watchState.emitterIndex !== null &&
95
+ watchState.emitters[watchState.emitterIndex] === emitter) {
96
+ watchState.emitterIndex++;
97
+ return;
98
+ }
99
+ else if (watchState.emitterIndex !== null) {
100
+ const index = watchState.emitterIndex;
101
+ watchState.emitterIndex = null;
102
+ watchState.emitters = watchState.emitters.slice(0, index);
103
+ }
67
104
  if (watchState.emitterIds) {
68
105
  if (!watchState.emitterIds.has(emitter[0])) {
69
106
  watchState.emitters.push(emitter);
@@ -86,11 +123,21 @@ function RegisterEmitter(emitter) {
86
123
  watchState.emitters.splice(writePos);
87
124
  }
88
125
  }
126
+ /**
127
+ * Registers a scope as a dependency for the current watch context.
128
+ * @param scope The scope to register as a dependency.
129
+ */
89
130
  function RegisterScope(scope) {
90
131
  if (watchState === null || scope.type === "static")
91
132
  return;
92
133
  RegisterEmitter(scope.emitter);
93
134
  }
135
+ /**
136
+ * Gets the current value of a scope, recomputing if dirty.
137
+ * @template T The type of value stored in the scope.
138
+ * @param scope The scope to get the value from.
139
+ * @returns The scope's current value.
140
+ */
94
141
  function GetScopeValue(scope) {
95
142
  if (scope.type === "static" || !scope.dirty || scope.destroyed)
96
143
  return scope.value;
@@ -98,11 +145,19 @@ function GetScopeValue(scope) {
98
145
  return scope.value;
99
146
  }
100
147
  let watchState = null;
101
- function WatchFunction(callback, currentCalc = null) {
148
+ /**
149
+ * Executes a callback while tracking all scope and emitter dependencies.
150
+ * Creates a watch context that records what was accessed during execution.
151
+ * @param callback The function to execute while tracking dependencies.
152
+ * @param currentCalc Optional map of existing calc scopes to reuse.
153
+ * @returns The watch state containing tracked dependencies and result.
154
+ */
155
+ function WatchFunction(callback, currentCalc = null, initialEmitters = []) {
102
156
  const parent = watchState;
103
157
  watchState = {
158
+ emitterIndex: initialEmitters.length > 0 ? 0 : null,
104
159
  value: null,
105
- emitters: [],
160
+ emitters: initialEmitters,
106
161
  emitterIds: null,
107
162
  currentCalc: currentCalc,
108
163
  nextCalc: null,
@@ -110,16 +165,25 @@ function WatchFunction(callback, currentCalc = null) {
110
165
  watchState.value = callback();
111
166
  const resultState = watchState;
112
167
  watchState = parent;
168
+ if (resultState.emitterIndex !== null &&
169
+ resultState.emitterIndex < resultState.emitters.length)
170
+ resultState.emitters = resultState.emitters.slice(0, resultState.emitterIndex);
113
171
  return resultState;
114
172
  }
173
+ /**
174
+ * Recomputes a scope's value by re-executing its getFunction.
175
+ * Updates the scope's dependencies and emits if the value changed.
176
+ * @param scope The scope to recompute.
177
+ */
115
178
  function ExecuteScope(scope) {
116
179
  scope.dirty = false;
117
- const state = WatchFunction(scope.getFunction, scope.calcScopes);
118
- UpdateEmitters(scope, state.emitters, !!state.emitterIds);
180
+ const state = WatchFunction(scope.getFunction, scope.calcScopes, scope.emitters);
181
+ if (state.emitters !== scope.emitters)
182
+ UpdateEmitters(scope, state.emitters, !!state.emitterIds);
119
183
  const calcScopes = state.currentCalc;
184
+ scope.calcScopes = state.nextCalc;
120
185
  for (const key in calcScopes)
121
186
  DestroyScope(calcScopes[key]);
122
- scope.calcScopes = state.nextCalc;
123
187
  if (scope.async)
124
188
  state.value.then(function (result) {
125
189
  scope.value = result;
@@ -128,6 +192,14 @@ function ExecuteScope(scope) {
128
192
  else
129
193
  scope.value = state.value;
130
194
  }
195
+ /**
196
+ * Creates a scope from a function, choosing between static and dynamic based on dependencies.
197
+ * @template T The type of value returned by the callback.
198
+ * @param callback The function to compute the scope's value.
199
+ * @param greedy Whether the scope should batch updates.
200
+ * @param allowStatic Whether to return a static scope if no dependencies are found.
201
+ * @returns A new observable scope.
202
+ */
131
203
  function ExecuteFunction(callback, greedy, allowStatic) {
132
204
  const async = (0, functions_1.IsAsync)(callback);
133
205
  const state = WatchFunction(callback);
@@ -144,11 +216,26 @@ function ExecuteFunction(callback, greedy, allowStatic) {
144
216
  }
145
217
  return CreateStaticScope(state.value);
146
218
  }
219
+ /**
220
+ * Creates a computed scope that acts as a gatekeeper for parent scope emissions.
221
+ * If this scope's value doesn't change (=== comparison) it won't emit.
222
+ *
223
+ * Useful for optimizing expensive operations driven by key values (e.g., sort keys).
224
+ * Scopes are memoized by ID and reused across multiple reads within a single parent scope evaluation.
225
+ * Defaults to "default" ID - provide custom IDs when using multiple calc scopes.
226
+ *
227
+ * Only works within a watch context (during another scope's execution).
228
+ * Always creates greedy scopes that batch updates via microtask queue.
229
+ * @template T The type of value returned by the callback.
230
+ * @param callback The function to compute the derived value.
231
+ * @param idOverride Optional custom ID for memoization when using multiple calc scopes.
232
+ * @returns The computed value, reusing existing scope if available.
233
+ */
147
234
  function CalcScope(callback, idOverride) {
148
235
  if (watchState === null)
149
236
  return callback();
150
237
  const nextScopes = (watchState.nextCalc ??= {});
151
- const id = idOverride ?? callback.toString();
238
+ const id = idOverride ?? "default";
152
239
  if (nextScopes[id]) {
153
240
  RegisterScope(nextScopes[id]);
154
241
  return GetScopeValue(nextScopes[id]);
@@ -160,6 +247,13 @@ function CalcScope(callback, idOverride) {
160
247
  RegisterScope(nextScopes[id]);
161
248
  return GetScopeValue(nextScopes[id]);
162
249
  }
250
+ /**
251
+ * Updates a scope's dependency emitters using efficient diffing.
252
+ * Subscribes to new emitters and unsubscribes from removed ones.
253
+ * @param scope The scope to update dependencies for.
254
+ * @param right The new list of emitters to track.
255
+ * @param distinct Whether emitters are already unique (sorted and deduplicated).
256
+ */
163
257
  function UpdateEmitters(scope, right, distinct = false) {
164
258
  distinct ? emitter_1.Emitter.Sort(right) : emitter_1.Emitter.Distinct(right);
165
259
  if (scope.emitters === null) {
@@ -183,10 +277,19 @@ function UpdateEmitters(scope, right, distinct = false) {
183
277
  });
184
278
  scope.emitters = right;
185
279
  }
280
+ /**
281
+ * Destroys multiple scopes, cleaning up their resources.
282
+ * @param scopes Array of scopes to destroy.
283
+ */
186
284
  function DestroyAllScopes(scopes) {
187
285
  for (let x = 0; x < scopes.length; x++)
188
286
  DestroyScope(scopes[x]);
189
287
  }
288
+ /**
289
+ * Destroys a scope, cleaning up all resources and dependencies.
290
+ * Unsubscribes from emitters, destroys nested scopes, and clears references.
291
+ * @param scope The scope to destroy.
292
+ */
190
293
  function DestroyScope(scope) {
191
294
  if (!scope || scope.type === "static")
192
295
  return;
@@ -207,18 +310,42 @@ function DestroyScope(scope) {
207
310
  }
208
311
  var ObservableScope;
209
312
  (function (ObservableScope) {
313
+ /**
314
+ * Creates a new observable scope from a value function.
315
+ * @template T The type of value returned by the function.
316
+ * @param valueFunction Function that returns the scope's value. Can be async.
317
+ * @param greedy Whether updates should be batched via microtask queue. Defaults to false.
318
+ * @param force If true, always creates a dynamic scope even without dependencies. Defaults to false.
319
+ * @returns A new observable scope.
320
+ */
210
321
  function Create(valueFunction, greedy = false, force = false) {
211
322
  return ExecuteFunction(valueFunction, greedy, !force);
212
323
  }
213
324
  ObservableScope.Create = Create;
325
+ /**
326
+ * Registers an emitter as a dependency for the current watch context.
327
+ * @param emitter The emitter to register as a dependency.
328
+ */
214
329
  function Register(emitter) {
215
330
  RegisterEmitter(emitter);
216
331
  }
217
332
  ObservableScope.Register = Register;
333
+ /**
334
+ * Gets a scope's current value without registering as a dependency.
335
+ * @template T The type of value stored in the scope.
336
+ * @param scope The scope to peek at.
337
+ * @returns The scope's current value.
338
+ */
218
339
  function Peek(scope) {
219
340
  return GetScopeValue(scope);
220
341
  }
221
342
  ObservableScope.Peek = Peek;
343
+ /**
344
+ * Gets a scope's current value and registers as a dependency.
345
+ * @template T The type of value stored in the scope.
346
+ * @param scope The scope to get the value from.
347
+ * @returns The scope's current value.
348
+ */
222
349
  function Value(scope) {
223
350
  if (!scope)
224
351
  return undefined;
@@ -226,24 +353,46 @@ var ObservableScope;
226
353
  return Peek(scope);
227
354
  }
228
355
  ObservableScope.Value = Value;
356
+ /**
357
+ * Registers a scope as a dependency without retrieving its value.
358
+ * @template T The type of value stored in the scope.
359
+ * @param scope The scope to register as a dependency.
360
+ */
229
361
  function Touch(scope) {
230
362
  if (!scope)
231
363
  return;
232
364
  RegisterScope(scope);
233
365
  }
234
366
  ObservableScope.Touch = Touch;
367
+ /**
368
+ * Subscribes to changes on a dynamic scope.
369
+ * @template T The type of value stored in the scope.
370
+ * @param scope The scope to watch for changes.
371
+ * @param callback Function to invoke when the scope's value changes.
372
+ */
235
373
  function Watch(scope, callback) {
236
374
  if (!scope || scope.type === "static")
237
375
  return;
238
376
  emitter_1.Emitter.On(scope.emitter, callback);
239
377
  }
240
378
  ObservableScope.Watch = Watch;
379
+ /**
380
+ * Unsubscribes from changes on a dynamic scope.
381
+ * @template T The type of value stored in the scope.
382
+ * @param scope The scope to stop watching.
383
+ * @param callback The callback function to remove.
384
+ */
241
385
  function Unwatch(scope, callback) {
242
386
  if (!scope || scope.type === "static")
243
387
  return;
244
388
  emitter_1.Emitter.Remove(scope.emitter, callback);
245
389
  }
246
390
  ObservableScope.Unwatch = Unwatch;
391
+ /**
392
+ * Registers a callback to be invoked when the scope is destroyed.
393
+ * @param scope The scope to monitor for destruction.
394
+ * @param callback Function to invoke when the scope is destroyed.
395
+ */
247
396
  function OnDestroyed(scope, callback) {
248
397
  if (scope.type === "static")
249
398
  return;
@@ -251,16 +400,29 @@ var ObservableScope;
251
400
  emitter_1.Emitter.On(scope.onDestroyed, callback);
252
401
  }
253
402
  ObservableScope.OnDestroyed = OnDestroyed;
403
+ /**
404
+ * Marks a scope as dirty, triggering recomputation on next access or batch.
405
+ * @param scope The scope to mark for update.
406
+ */
254
407
  function Update(scope) {
255
408
  if (!scope)
256
409
  return;
257
410
  OnSet(scope);
258
411
  }
259
412
  ObservableScope.Update = Update;
413
+ /**
414
+ * Destroys a scope, cleaning up all resources and dependencies.
415
+ * @template T The type of value stored in the scope.
416
+ * @param scope The scope to destroy.
417
+ */
260
418
  function Destroy(scope) {
261
419
  DestroyScope(scope);
262
420
  }
263
421
  ObservableScope.Destroy = Destroy;
422
+ /**
423
+ * Destroys multiple scopes, cleaning up their resources.
424
+ * @param scopes Array of scopes to destroy.
425
+ */
264
426
  function DestroyAll(scopes) {
265
427
  DestroyAllScopes(scopes);
266
428
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.67",
3
+ "version": "7.0.69",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",