j-templates 7.0.68 → 7.0.70

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.
@@ -169,9 +169,10 @@ exports.DOMNodeConfig = {
169
169
  target.appendChild(children[x]);
170
170
  },
171
171
  reconcileChild(target, child) {
172
- if (target.childElementCount === 0)
173
- target.appendChild(child);
174
- else if (target.childElementCount > 1 || target.firstChild !== child)
175
- target.replaceChildren(child);
172
+ if (target.firstChild === child)
173
+ return;
174
+ target.appendChild(child);
175
+ while (target.firstChild !== child)
176
+ target.removeChild(target.firstChild);
176
177
  },
177
178
  };
@@ -38,14 +38,6 @@ export declare class Component<D = void, T = void, E = {}> {
38
38
  * Accessor for the component's template collection.
39
39
  */
40
40
  protected get Templates(): T;
41
- /**
42
- * Creates a new Component instance. Not intended to be overriden.
43
- *
44
- * @param data - Initial data or a factory function returning data/promise.
45
- * @param templates - Template definitions for rendering.
46
- * @param vNode - The underlying virtual node instance.
47
- * @param componentEvents - Optional event callbacks.
48
- */
49
41
  constructor(vNode: vNodeType, config: vComponentConfig<D, E>, templates: T);
50
42
  /**
51
43
  * Returns the component's rendered vNode(s).
package/Node/component.js CHANGED
@@ -48,32 +48,9 @@ class Component {
48
48
  get Templates() {
49
49
  return this.templates;
50
50
  }
51
- /**
52
- * Creates a new Component instance. Not intended to be overriden.
53
- *
54
- * @param data - Initial data or a factory function returning data/promise.
55
- * @param templates - Template definitions for rendering.
56
- * @param vNode - The underlying virtual node instance.
57
- * @param componentEvents - Optional event callbacks.
58
- */
59
- /* constructor(
60
- data: D | (() => D | Promise<D>),
61
- templates: T,
62
- private vNode: vNodeType,
63
- private componentEvents: ComponentEvents<E>,
64
- ) {
65
- if (typeof data === "function")
66
- this.scope = new ObservableScope<D>(data as () => D | Promise<D>);
67
- else this.scope = new ObservableScope<D>(() => data);
68
-
69
- this.templates = templates || ({} as T);
70
- } */
71
51
  constructor(vNode, config, templates) {
72
52
  this.vNode = vNode;
73
53
  const { data, on } = config;
74
- /* if (typeof data === "function")
75
- this.scope = new ObservableScope<D>(data as () => D | Promise<D>);
76
- else this.scope = new ObservableScope<D>(() => data); */
77
54
  if (typeof data === "function")
78
55
  this.scope = Store_1.ObservableScope.Create(data);
79
56
  else
@@ -119,30 +96,13 @@ class Component {
119
96
  }
120
97
  }
121
98
  exports.Component = Component;
122
- /* type ComponentConstructor<D, T, E> = {
123
- new (
124
- data: D | (() => D | Promise<D>),
125
- templates: T,
126
- vNode: vNodeType,
127
- componentEvents: ComponentEvents<E>,
128
- ): Component<D, T, E>;
129
- }; */
130
99
  (function (Component) {
131
100
  /**
132
101
  * Function wraps the Component as a function that can be used to create vNode objects
133
102
  * and generate templates.
134
103
  */
135
- function ToFunction(type,
136
- // constructor: ComponentConstructor<D, T, E>,
137
- constructor, namespace) {
104
+ function ToFunction(type, constructor, namespace) {
138
105
  return function (config, templates) {
139
- /* const { data, on, props } = config;
140
-
141
- class ConcreteComponent extends constructor {
142
- constructor(vnode: vNodeType) {
143
- super(data, templates, vnode, on);
144
- }
145
- } */
146
106
  function ComponentFactory(vnode) {
147
107
  return new constructor(vnode, config, templates);
148
108
  }
@@ -151,7 +111,6 @@ exports.Component = Component;
151
111
  namespace: namespace ?? null,
152
112
  props: config.props,
153
113
  componentFactory: ComponentFactory,
154
- // componentConstructor: ConcreteComponent,
155
114
  };
156
115
  return vNode_1.vNode.Create(definition);
157
116
  };
package/Node/vNode.js CHANGED
@@ -14,7 +14,6 @@ var vNode;
14
14
  return {
15
15
  definition,
16
16
  type: definition.type,
17
- // injector: definition.componentConstructor
18
17
  injector: definition.componentFactory
19
18
  ? injector_1.Injector.Scope(injector_1.Injector.Current(), function () {
20
19
  return new injector_1.Injector();
@@ -95,9 +94,7 @@ var vNode;
95
94
  vNode.Attach = Attach;
96
95
  })(vNode || (exports.vNode = vNode = {}));
97
96
  function InitNode(vnode) {
98
- const { type, namespace, props, attrs, on, data,
99
- // componentConstructor,
100
- componentFactory, children, childrenArray, } = vnode.definition;
97
+ const { type, namespace, props, attrs, on, data, componentFactory, children, childrenArray, } = vnode.definition;
101
98
  const node = (vnode.node =
102
99
  vnode.definition.node ?? nodeConfig_1.NodeConfig.createNode(type, namespace));
103
100
  vnode.definition = null;
@@ -105,11 +102,8 @@ function InitNode(vnode) {
105
102
  const assignProperties = nodeConfig_1.NodeConfig.createPropertyAssignment(node);
106
103
  if (typeof props === "function") {
107
104
  const scope = Store_1.ObservableScope.Create(props);
108
- // const [value, scope] = ObservableScope.CreateIf(props as () => any);
109
- // if (scope) {
110
105
  vnode.scopes.push(scope);
111
106
  Store_1.ObservableScope.Watch(scope, ScheduledAssignment(assignProperties));
112
- // }
113
107
  assignProperties(Store_1.ObservableScope.Value(scope));
114
108
  }
115
109
  else
@@ -119,11 +113,8 @@ function InitNode(vnode) {
119
113
  const assignEvents = nodeConfig_1.NodeConfig.createEventAssignment(node);
120
114
  if (typeof on === "function") {
121
115
  const scope = Store_1.ObservableScope.Create(on);
122
- // const [value, scope] = ObservableScope.CreateIf(on);
123
- // if (scope) {
124
116
  vnode.scopes.push(scope);
125
117
  Store_1.ObservableScope.Watch(scope, ScheduledAssignment(assignEvents));
126
- // }
127
118
  assignEvents(Store_1.ObservableScope.Value(scope));
128
119
  }
129
120
  else
@@ -133,19 +124,14 @@ function InitNode(vnode) {
133
124
  const assignAttributes = nodeConfig_1.NodeConfig.createAttributeAssignment(node);
134
125
  if (typeof attrs === "function") {
135
126
  const scope = Store_1.ObservableScope.Create(attrs);
136
- // const [value, scope] = ObservableScope.CreateIf(attrs);
137
- // if (scope) {
138
127
  vnode.scopes.push(scope);
139
128
  Store_1.ObservableScope.Watch(scope, ScheduledAssignment(assignAttributes));
140
- //}
141
129
  assignAttributes(Store_1.ObservableScope.Value(scope));
142
130
  }
143
131
  else
144
132
  assignAttributes(attrs);
145
133
  }
146
- // if (componentConstructor) {
147
134
  if (componentFactory) {
148
- // vnode.component = new componentConstructor(vnode);
149
135
  vnode.component = componentFactory(vnode);
150
136
  vnode.component.Bound();
151
137
  function componentChildren() {
@@ -171,7 +157,6 @@ function Children(vnode, children, data) {
171
157
  return;
172
158
  const startChildren = vnode.children;
173
159
  const newChildren = Store_1.ObservableScope.Value(scope);
174
- // AssignChildren(vnode, scope);
175
160
  if (startChildren !== newChildren) {
176
161
  vnode.children = newChildren;
177
162
  UpdateChildren(vnode);
@@ -179,21 +164,7 @@ function Children(vnode, children, data) {
179
164
  }));
180
165
  }
181
166
  vnode.children = Store_1.ObservableScope.Value(childrenScope);
182
- // AssignChildren(vnode, childrenScope);
183
167
  }
184
- /* function AssignChildren(
185
- vnode: vNodeType,
186
- childrenScope: IObservableScope<
187
- [
188
- any,
189
- vNodeType[],
190
- IObservableScope<string | vNodeType | vNodeType[]> | null,
191
- ][]
192
- >,
193
- ) {
194
- const children = ObservableScope.Peek(childrenScope);
195
- vnode.children = children;
196
- } */
197
168
  const DEFAULT_DATA = [undefined];
198
169
  function DefaultData() {
199
170
  return DEFAULT_DATA;
@@ -302,7 +273,6 @@ function EvaluateNextNodesLarge(injector, getNextChildren, nextData, nodeArray)
302
273
  if (currentChildIndex !== -1) {
303
274
  const currentChild = currentChildren[currentChildIndex];
304
275
  currentChildren[currentChildIndex] = null;
305
- // if (currentChild[2]) {
306
276
  const scope = currentChild[2];
307
277
  const value = scope.value;
308
278
  const updatedValue = Store_1.ObservableScope.Value(scope);
@@ -310,13 +280,7 @@ function EvaluateNextNodesLarge(injector, getNextChildren, nextData, nodeArray)
310
280
  vNode.DestroyAll(currentChild[1]);
311
281
  currentChild[1] = CreateNodeArray(updatedValue);
312
282
  }
313
- // }
314
- /* if (currentChild[2]?.dirty) {
315
- const nextChildren = ObservableScope.Value(currentChild[2]);
316
- currentChild[1] = CreateNodeArray(nextChildren);
317
- } */
318
283
  nextNodes[x] = currentChild;
319
- // currentChildren[currentChildIndex] = null;
320
284
  if (currentChildIndex === 0)
321
285
  dataMap.delete(data);
322
286
  }
@@ -324,9 +288,6 @@ function EvaluateNextNodesLarge(injector, getNextChildren, nextData, nodeArray)
324
288
  const scope = Store_1.ObservableScope.Create(function () {
325
289
  return injector_1.Injector.Scope(injector, getNextChildren, data);
326
290
  });
327
- /* const [nextChildren, scope] = ObservableScope.CreateIf(function () {
328
- return Injector.Scope(injector, getNextChildren, data);
329
- }); */
330
291
  nextNodes[x] = [
331
292
  data,
332
293
  CreateNodeArray(Store_1.ObservableScope.Value(scope)),
@@ -411,7 +372,6 @@ function ScheduledAssignment(assign) {
411
372
  return;
412
373
  scheduled = true;
413
374
  nodeConfig_1.NodeConfig.scheduleUpdate(function () {
414
- // if (scope.destroyed) return;
415
375
  scheduled = false;
416
376
  const value = Store_1.ObservableScope.Peek(scope);
417
377
  assign(value);
@@ -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,15 @@ 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
+ const list_1 = require("../../Utils/list");
9
+ /**
10
+ * Creates a dynamic (reactive) observable scope.
11
+ * @template T The type of value stored in the scope.
12
+ * @param getFunction Function that returns the scope's value. May be async.
13
+ * @param greedy Whether updates should be batched via microtask queue.
14
+ * @param value Initial value for the scope (null for async functions).
15
+ * @returns A new dynamic observable scope.
16
+ */
8
17
  function CreateDynamicScope(getFunction, greedy, value) {
9
18
  const async = (0, functions_1.IsAsync)(getFunction);
10
19
  const scope = {
@@ -25,6 +34,13 @@ function CreateDynamicScope(getFunction, greedy, value) {
25
34
  };
26
35
  return scope;
27
36
  }
37
+ /**
38
+ * Creates a static (non-reactive) observable scope.
39
+ * Static scopes are optimized for values that never change and don't need dependency tracking.
40
+ * @template T The type of value stored in the scope.
41
+ * @param initialValue The immutable value to store in the scope.
42
+ * @returns A new static observable scope.
43
+ */
28
44
  function CreateStaticScope(initialValue) {
29
45
  return {
30
46
  type: "static",
@@ -50,6 +66,11 @@ function OnSetQueued(scope) {
50
66
  queueMicrotask(ProcessScopeQueue);
51
67
  scopeQueue.push(scope);
52
68
  }
69
+ /**
70
+ * Marks a scope as dirty and triggers update behavior based on scope type.
71
+ * @param scope The scope to mark for update.
72
+ * @returns true if the scope is static or destroyed, false if queued or non-greedy scope emitted.
73
+ */
53
74
  function OnSet(scope) {
54
75
  if (scope.type === "static" || scope.destroyed)
55
76
  return true;
@@ -63,59 +84,136 @@ function OnSet(scope) {
63
84
  emitter_1.Emitter.Emit(scope.emitter, scope);
64
85
  return false;
65
86
  }
66
- function RegisterEmitter(emitter) {
67
- if (watchState.emitterIds) {
68
- if (!watchState.emitterIds.has(emitter[0])) {
69
- watchState.emitters.push(emitter);
70
- watchState.emitterIds.add(emitter[0]);
71
- }
87
+ function RegisterSame(state, emitter) {
88
+ if (state.emitterIndex < state.emitters.length &&
89
+ state.emitters[state.emitterIndex] === emitter) {
90
+ state.emitterIndex++;
72
91
  return;
73
92
  }
74
- watchState.emitters.push(emitter);
75
- if (watchState.emitters.length > 50) {
76
- const idSet = (watchState.emitterIds = new Set());
93
+ state.emitters = state.emitters.slice(0, state.emitterIndex);
94
+ state.emitters.push(emitter);
95
+ state.emitterIndex = -1;
96
+ state.strategy = PUSH_STRATEGY;
97
+ }
98
+ function RegisterPush(state, emitter) {
99
+ state.emitters.push(emitter);
100
+ if (state.emitters.length > 50) {
101
+ const idSet = (state.emitterIds = new Set([state.emitters[0][0]]));
77
102
  let writePos = 0;
78
- for (let x = 1; x < watchState.emitters.length; x++) {
79
- if (!idSet.has(watchState.emitters[x][0])) {
80
- watchState.emitters[++writePos] = watchState.emitters[x];
81
- idSet.add(watchState.emitters[x][0]);
103
+ for (let x = 1; x < state.emitters.length; x++) {
104
+ if (!idSet.has(state.emitters[x][0])) {
105
+ state.emitters[++writePos] = state.emitters[x];
106
+ idSet.add(state.emitters[x][0]);
82
107
  }
83
108
  }
84
109
  writePos++;
85
- if (writePos < watchState.emitters.length)
86
- watchState.emitters.splice(writePos);
110
+ if (writePos < state.emitters.length)
111
+ state.emitters.splice(writePos);
112
+ state.strategy = DISTINCT_STRATEGY;
113
+ }
114
+ }
115
+ function RegisterDistinct(state, emitter) {
116
+ if (!state.emitterIds.has(emitter[0])) {
117
+ state.emitters.push(emitter);
118
+ state.emitterIds.add(emitter[0]);
119
+ }
120
+ }
121
+ function RegisterEmitter(emitter) {
122
+ if (watchState === null)
123
+ return;
124
+ switch (watchState.strategy) {
125
+ case SAME_STRATEGY:
126
+ RegisterSame(watchState, emitter);
127
+ break;
128
+ case PUSH_STRATEGY:
129
+ RegisterPush(watchState, emitter);
130
+ break;
131
+ case DISTINCT_STRATEGY:
132
+ RegisterDistinct(watchState, emitter);
133
+ break;
87
134
  }
88
135
  }
136
+ /**
137
+ * Registers a scope as a dependency for the current watch context.
138
+ * @param scope The scope to register as a dependency.
139
+ */
89
140
  function RegisterScope(scope) {
90
141
  if (watchState === null || scope.type === "static")
91
142
  return;
92
143
  RegisterEmitter(scope.emitter);
93
144
  }
145
+ /**
146
+ * Gets the current value of a scope, recomputing if dirty.
147
+ * @template T The type of value stored in the scope.
148
+ * @param scope The scope to get the value from.
149
+ * @returns The scope's current value.
150
+ */
94
151
  function GetScopeValue(scope) {
95
152
  if (scope.type === "static" || !scope.dirty || scope.destroyed)
96
153
  return scope.value;
97
154
  ExecuteScope(scope);
98
155
  return scope.value;
99
156
  }
157
+ const SAME_STRATEGY = 1;
158
+ const PUSH_STRATEGY = 2;
159
+ const DISTINCT_STRATEGY = 3;
160
+ const SHRINK_STRATEGY = 4;
100
161
  let watchState = null;
101
- function WatchFunction(callback, currentCalc = null) {
102
- const parent = watchState;
103
- watchState = {
162
+ const watchPool = list_1.List.Create();
163
+ function CreateWatchState() {
164
+ return (list_1.List.Pop(watchPool) ?? {
165
+ emitterIndex: 0,
104
166
  value: null,
105
- emitters: [],
167
+ emitters: null,
106
168
  emitterIds: null,
107
- currentCalc: currentCalc,
169
+ currentCalc: null,
108
170
  nextCalc: null,
109
- };
171
+ strategy: PUSH_STRATEGY,
172
+ });
173
+ }
174
+ function ReturnWatchState(state) {
175
+ state.emitterIndex = 0;
176
+ state.value = null;
177
+ state.emitters = null;
178
+ state.emitterIds = null;
179
+ state.currentCalc = null;
180
+ state.nextCalc = null;
181
+ state.strategy = PUSH_STRATEGY;
182
+ list_1.List.Push(watchPool, state);
183
+ }
184
+ /**
185
+ * Executes a callback while tracking all scope and emitter dependencies.
186
+ * Creates a watch context that records what was accessed during execution.
187
+ * @param callback The function to execute while tracking dependencies.
188
+ * @param currentCalc Optional map of existing calc scopes to reuse.
189
+ * @returns The watch state containing tracked dependencies and result.
190
+ */
191
+ function WatchFunction(callback, currentCalc, initialEmitters) {
192
+ const parent = watchState;
193
+ watchState = CreateWatchState();
194
+ watchState.emitters = initialEmitters ?? [];
195
+ watchState.currentCalc = currentCalc;
196
+ if (initialEmitters !== null)
197
+ watchState.strategy = SAME_STRATEGY;
110
198
  watchState.value = callback();
111
199
  const resultState = watchState;
112
200
  watchState = parent;
201
+ if (resultState.strategy === SAME_STRATEGY &&
202
+ resultState.emitterIndex < resultState.emitters.length) {
203
+ resultState.emitters = resultState.emitters.slice(0, resultState.emitterIndex);
204
+ resultState.strategy = SHRINK_STRATEGY;
205
+ }
113
206
  return resultState;
114
207
  }
208
+ /**
209
+ * Recomputes a scope's value by re-executing its getFunction.
210
+ * Updates the scope's dependencies and emits if the value changed.
211
+ * @param scope The scope to recompute.
212
+ */
115
213
  function ExecuteScope(scope) {
116
214
  scope.dirty = false;
117
- const state = WatchFunction(scope.getFunction, scope.calcScopes);
118
- UpdateEmitters(scope, state.emitters, !!state.emitterIds);
215
+ const state = WatchFunction(scope.getFunction, scope.calcScopes, scope.emitters);
216
+ UpdateEmitters(scope, state.emitters, state.strategy);
119
217
  const calcScopes = state.currentCalc;
120
218
  scope.calcScopes = state.nextCalc;
121
219
  for (const key in calcScopes)
@@ -127,28 +225,55 @@ function ExecuteScope(scope) {
127
225
  });
128
226
  else
129
227
  scope.value = state.value;
228
+ ReturnWatchState(state);
130
229
  }
230
+ /**
231
+ * Creates a scope from a function, choosing between static and dynamic based on dependencies.
232
+ * @template T The type of value returned by the callback.
233
+ * @param callback The function to compute the scope's value.
234
+ * @param greedy Whether the scope should batch updates.
235
+ * @param allowStatic Whether to return a static scope if no dependencies are found.
236
+ * @returns A new observable scope.
237
+ */
131
238
  function ExecuteFunction(callback, greedy, allowStatic) {
132
239
  const async = (0, functions_1.IsAsync)(callback);
133
- const state = WatchFunction(callback);
134
- if (!allowStatic || async || state.emitters.length > 0) {
240
+ const state = WatchFunction(callback, null, null);
241
+ if (!allowStatic || async || state.emitters !== null) {
135
242
  const scope = CreateDynamicScope(callback, greedy, async ? null : state.value);
136
243
  scope.calcScopes = state.nextCalc;
137
- UpdateEmitters(scope, state.emitters, !!state.emitterIds);
244
+ UpdateEmitters(scope, state.emitters, state.strategy);
138
245
  if (async)
139
246
  state.value.then(function (result) {
140
247
  scope.value = result;
141
248
  emitter_1.Emitter.Emit(scope.emitter, scope);
142
249
  });
250
+ ReturnWatchState(state);
143
251
  return scope;
144
252
  }
145
- return CreateStaticScope(state.value);
253
+ const value = state.value;
254
+ ReturnWatchState(state);
255
+ return CreateStaticScope(value);
146
256
  }
257
+ /**
258
+ * Creates a computed scope that acts as a gatekeeper for parent scope emissions.
259
+ * If this scope's value doesn't change (=== comparison) it won't emit.
260
+ *
261
+ * Useful for optimizing expensive operations driven by key values (e.g., sort keys).
262
+ * Scopes are memoized by ID and reused across multiple reads within a single parent scope evaluation.
263
+ * Defaults to "default" ID - provide custom IDs when using multiple calc scopes.
264
+ *
265
+ * Only works within a watch context (during another scope's execution).
266
+ * Always creates greedy scopes that batch updates via microtask queue.
267
+ * @template T The type of value returned by the callback.
268
+ * @param callback The function to compute the derived value.
269
+ * @param idOverride Optional custom ID for memoization when using multiple calc scopes.
270
+ * @returns The computed value, reusing existing scope if available.
271
+ */
147
272
  function CalcScope(callback, idOverride) {
148
273
  if (watchState === null)
149
274
  return callback();
150
275
  const nextScopes = (watchState.nextCalc ??= {});
151
- const id = idOverride ?? callback.toString();
276
+ const id = idOverride ?? "default";
152
277
  if (nextScopes[id]) {
153
278
  RegisterScope(nextScopes[id]);
154
279
  return GetScopeValue(nextScopes[id]);
@@ -160,33 +285,53 @@ function CalcScope(callback, idOverride) {
160
285
  RegisterScope(nextScopes[id]);
161
286
  return GetScopeValue(nextScopes[id]);
162
287
  }
163
- function UpdateEmitters(scope, right, distinct = false) {
164
- distinct ? emitter_1.Emitter.Sort(right) : emitter_1.Emitter.Distinct(right);
165
- if (scope.emitters === null) {
166
- for (let x = 0; x < right.length; x++)
167
- emitter_1.Emitter.On(right[x], scope.setCallback);
168
- scope.emitters = right;
169
- return;
170
- }
171
- if (right.length === 0) {
172
- if (scope.emitters.length > 0) {
173
- for (let x = 0; x < scope.emitters.length; x++)
288
+ /**
289
+ * Updates a scope's dependency emitters using efficient diffing.
290
+ * Subscribes to new emitters and unsubscribes from removed ones.
291
+ * @param scope The scope to update dependencies for.
292
+ * @param right The new list of emitters to track.
293
+ * @param distinct Whether emitters are already unique (sorted and deduplicated).
294
+ */
295
+ function UpdateEmitters(scope, right, strategy) {
296
+ switch (strategy) {
297
+ case SHRINK_STRATEGY: {
298
+ for (let x = right.length; x < scope.emitters.length; x++)
174
299
  emitter_1.Emitter.Remove(scope.emitters[x], scope.setCallback);
175
- scope.emitters = [];
300
+ break;
176
301
  }
177
- return;
302
+ case PUSH_STRATEGY:
303
+ case DISTINCT_STRATEGY:
304
+ strategy === PUSH_STRATEGY
305
+ ? emitter_1.Emitter.Distinct(right)
306
+ : emitter_1.Emitter.Sort(right);
307
+ if (scope.emitters === null || scope.emitters.length === 0) {
308
+ for (let x = 0; x < right.length; x++)
309
+ emitter_1.Emitter.On(right[x], scope.setCallback);
310
+ }
311
+ else {
312
+ (0, array_1.ReconcileSortedEmitters)(scope.emitters, right, function (emitter) {
313
+ emitter_1.Emitter.On(emitter, scope.setCallback);
314
+ }, function (emitter) {
315
+ emitter_1.Emitter.Remove(emitter, scope.setCallback);
316
+ });
317
+ }
318
+ break;
178
319
  }
179
- (0, array_1.ReconcileSortedEmitters)(scope.emitters, right, function (emitter) {
180
- emitter_1.Emitter.On(emitter, scope.setCallback);
181
- }, function (emitter) {
182
- emitter_1.Emitter.Remove(emitter, scope.setCallback);
183
- });
184
320
  scope.emitters = right;
185
321
  }
322
+ /**
323
+ * Destroys multiple scopes, cleaning up their resources.
324
+ * @param scopes Array of scopes to destroy.
325
+ */
186
326
  function DestroyAllScopes(scopes) {
187
327
  for (let x = 0; x < scopes.length; x++)
188
328
  DestroyScope(scopes[x]);
189
329
  }
330
+ /**
331
+ * Destroys a scope, cleaning up all resources and dependencies.
332
+ * Unsubscribes from emitters, destroys nested scopes, and clears references.
333
+ * @param scope The scope to destroy.
334
+ */
190
335
  function DestroyScope(scope) {
191
336
  if (!scope || scope.type === "static")
192
337
  return;
@@ -207,18 +352,42 @@ function DestroyScope(scope) {
207
352
  }
208
353
  var ObservableScope;
209
354
  (function (ObservableScope) {
355
+ /**
356
+ * Creates a new observable scope from a value function.
357
+ * @template T The type of value returned by the function.
358
+ * @param valueFunction Function that returns the scope's value. Can be async.
359
+ * @param greedy Whether updates should be batched via microtask queue. Defaults to false.
360
+ * @param force If true, always creates a dynamic scope even without dependencies. Defaults to false.
361
+ * @returns A new observable scope.
362
+ */
210
363
  function Create(valueFunction, greedy = false, force = false) {
211
364
  return ExecuteFunction(valueFunction, greedy, !force);
212
365
  }
213
366
  ObservableScope.Create = Create;
367
+ /**
368
+ * Registers an emitter as a dependency for the current watch context.
369
+ * @param emitter The emitter to register as a dependency.
370
+ */
214
371
  function Register(emitter) {
215
372
  RegisterEmitter(emitter);
216
373
  }
217
374
  ObservableScope.Register = Register;
375
+ /**
376
+ * Gets a scope's current value without registering as a dependency.
377
+ * @template T The type of value stored in the scope.
378
+ * @param scope The scope to peek at.
379
+ * @returns The scope's current value.
380
+ */
218
381
  function Peek(scope) {
219
382
  return GetScopeValue(scope);
220
383
  }
221
384
  ObservableScope.Peek = Peek;
385
+ /**
386
+ * Gets a scope's current value and registers as a dependency.
387
+ * @template T The type of value stored in the scope.
388
+ * @param scope The scope to get the value from.
389
+ * @returns The scope's current value.
390
+ */
222
391
  function Value(scope) {
223
392
  if (!scope)
224
393
  return undefined;
@@ -226,24 +395,46 @@ var ObservableScope;
226
395
  return Peek(scope);
227
396
  }
228
397
  ObservableScope.Value = Value;
398
+ /**
399
+ * Registers a scope as a dependency without retrieving its value.
400
+ * @template T The type of value stored in the scope.
401
+ * @param scope The scope to register as a dependency.
402
+ */
229
403
  function Touch(scope) {
230
404
  if (!scope)
231
405
  return;
232
406
  RegisterScope(scope);
233
407
  }
234
408
  ObservableScope.Touch = Touch;
409
+ /**
410
+ * Subscribes to changes on a dynamic scope.
411
+ * @template T The type of value stored in the scope.
412
+ * @param scope The scope to watch for changes.
413
+ * @param callback Function to invoke when the scope's value changes.
414
+ */
235
415
  function Watch(scope, callback) {
236
416
  if (!scope || scope.type === "static")
237
417
  return;
238
418
  emitter_1.Emitter.On(scope.emitter, callback);
239
419
  }
240
420
  ObservableScope.Watch = Watch;
421
+ /**
422
+ * Unsubscribes from changes on a dynamic scope.
423
+ * @template T The type of value stored in the scope.
424
+ * @param scope The scope to stop watching.
425
+ * @param callback The callback function to remove.
426
+ */
241
427
  function Unwatch(scope, callback) {
242
428
  if (!scope || scope.type === "static")
243
429
  return;
244
430
  emitter_1.Emitter.Remove(scope.emitter, callback);
245
431
  }
246
432
  ObservableScope.Unwatch = Unwatch;
433
+ /**
434
+ * Registers a callback to be invoked when the scope is destroyed.
435
+ * @param scope The scope to monitor for destruction.
436
+ * @param callback Function to invoke when the scope is destroyed.
437
+ */
247
438
  function OnDestroyed(scope, callback) {
248
439
  if (scope.type === "static")
249
440
  return;
@@ -251,16 +442,29 @@ var ObservableScope;
251
442
  emitter_1.Emitter.On(scope.onDestroyed, callback);
252
443
  }
253
444
  ObservableScope.OnDestroyed = OnDestroyed;
445
+ /**
446
+ * Marks a scope as dirty, triggering recomputation on next access or batch.
447
+ * @param scope The scope to mark for update.
448
+ */
254
449
  function Update(scope) {
255
450
  if (!scope)
256
451
  return;
257
452
  OnSet(scope);
258
453
  }
259
454
  ObservableScope.Update = Update;
455
+ /**
456
+ * Destroys a scope, cleaning up all resources and dependencies.
457
+ * @template T The type of value stored in the scope.
458
+ * @param scope The scope to destroy.
459
+ */
260
460
  function Destroy(scope) {
261
461
  DestroyScope(scope);
262
462
  }
263
463
  ObservableScope.Destroy = Destroy;
464
+ /**
465
+ * Destroys multiple scopes, cleaning up their resources.
466
+ * @param scopes Array of scopes to destroy.
467
+ */
264
468
  function DestroyAll(scopes) {
265
469
  DestroyAllScopes(scopes);
266
470
  }
@@ -20,9 +20,7 @@ exports.Scope = Scope;
20
20
  exports.Watch = Watch;
21
21
  exports.Inject = Inject;
22
22
  exports.Destroy = Destroy;
23
- // import { Component } from "../Node/component";
24
23
  const observableScope_1 = require("../Store/Tree/observableScope");
25
- // import { ElementNodeRefTypes } from "../Node/nodeRef.types";
26
24
  const observableNode_1 = require("../Store/Tree/observableNode");
27
25
  const Store_1 = require("../Store");
28
26
  /**
@@ -365,14 +363,6 @@ function WatchDecorator(target, propertyKey, descriptor, scopeFunction) {
365
363
  observableScope_1.ObservableScope.Watch(scope, function (scope) {
366
364
  instance[propertyKey](observableScope_1.ObservableScope.Value(scope));
367
365
  });
368
- /* const [value, scope] = ObservableScope.CreateIf(scopeFunctionWrapper, true);
369
- if (scope) {
370
- const propertyMap = GetScopeMapForInstance(this);
371
- propertyMap[propertyKey as string] = [scope, undefined];
372
- ObservableScope.Watch(scope, function (scope) {
373
- (instance as any)[propertyKey](ObservableScope.Value(scope));
374
- });
375
- } */
376
366
  instance[propertyKey](observableScope_1.ObservableScope.Value(scope));
377
367
  return instance;
378
368
  }
package/Utils/emitter.js CHANGED
@@ -42,11 +42,6 @@ var Emitter;
42
42
  function Distinct(emitters) {
43
43
  if (emitters.length < 2)
44
44
  return;
45
- // emitters.length < 51 ? DistinctSmall(emitters) : DistinctLarge(emitters);
46
- DistinctSmall(emitters);
47
- }
48
- Emitter.Distinct = Distinct;
49
- function DistinctSmall(emitters) {
50
45
  Sort(emitters);
51
46
  let writePos = 0;
52
47
  for (let x = 1; x < emitters.length; x++) {
@@ -58,20 +53,7 @@ var Emitter;
58
53
  if (writePos < emitters.length)
59
54
  emitters.splice(writePos);
60
55
  }
61
- function DistinctLarge(emitters) {
62
- let writePos = 0;
63
- const ids = new Set();
64
- for (let x = 0; x < emitters.length; x++) {
65
- const id = emitters[x][0];
66
- if (!ids.has(id)) {
67
- ids.add(id);
68
- emitters[writePos++] = emitters[x];
69
- }
70
- }
71
- if (writePos < emitters.length)
72
- emitters.splice(writePos);
73
- Sort(emitters);
74
- }
56
+ Emitter.Distinct = Distinct;
75
57
  function Sort(emitters) {
76
58
  if (emitters.length < 11)
77
59
  (0, array_1.InsertionSortTuples)(emitters);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "7.0.68",
3
+ "version": "7.0.70",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",