j-templates 8.0.21 → 8.0.24

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.
@@ -56,6 +56,9 @@ function getHTMLNode(from, current) {
56
56
  return from;
57
57
  }
58
58
  export const DOMNodeConfig = {
59
+ isConnected(target) {
60
+ return target.isConnected;
61
+ },
59
62
  createNode(type, namespace) {
60
63
  return namespace
61
64
  ? document.createElementNS(namespace, type)
@@ -14,6 +14,11 @@ export declare class Component<D = void, T = void, E = {}> {
14
14
  private scope;
15
15
  private templates;
16
16
  private componentEvents;
17
+ /**
18
+ * Return if the component's vNode is connected
19
+ * to a parent document
20
+ */
21
+ get IsConnected(): boolean;
17
22
  /**
18
23
  * Returns the component's virtual node injector.
19
24
  */
@@ -51,6 +56,7 @@ export declare class Component<D = void, T = void, E = {}> {
51
56
  * Override to perform post‑binding initialization.
52
57
  */
53
58
  Bound(): void;
59
+ Connected(): void;
54
60
  /**
55
61
  * Fires a component event.
56
62
  *
@@ -85,6 +91,6 @@ export declare namespace Component {
85
91
  * @param vnode - The virtual node to be attached.
86
92
  * @returns The result of the attachment operation.
87
93
  */
88
- function Attach(node: any, vnode: vNodeType): vNodeType;
94
+ function Attach(node: any, vnode: vElementNode): vElementNode;
89
95
  }
90
96
  export {};
package/Node/component.js CHANGED
@@ -9,6 +9,13 @@ import { ObservableScope } from "../Store";
9
9
  * @template E - Event map type for component events.
10
10
  */
11
11
  export class Component {
12
+ /**
13
+ * Return if the component's vNode is connected
14
+ * to a parent document
15
+ */
16
+ get IsConnected() {
17
+ return this.vNode.connected;
18
+ }
12
19
  /**
13
20
  * Returns the component's virtual node injector.
14
21
  */
@@ -76,6 +83,7 @@ export class Component {
76
83
  Bound() {
77
84
  Bound.All(this);
78
85
  }
86
+ Connected() { }
79
87
  /**
80
88
  * Fires a component event.
81
89
  *
@@ -1,4 +1,5 @@
1
1
  export interface INodeConfig {
2
+ isConnected(node: any): boolean;
2
3
  createNode(type: any, namespace: string): any;
3
4
  createTextNode(value?: string): any;
4
5
  scheduleUpdate(callback: () => void): void;
package/Node/vNode.d.ts CHANGED
@@ -6,5 +6,5 @@ export declare namespace vNode {
6
6
  function Destroy(vnode: vNodeType): void;
7
7
  function DestroyAll(vnodes: vNodeType[]): void;
8
8
  function ToFunction<P = HTMLElement, E = HTMLElementEventMap>(type: string | typeof TEXT_NODE | typeof FRAGMENT_NODE, namespace?: string): <T>(config: vNodeConfig<P, E, T>, children?: vNodeType[] | vNodeChildrenFunction<T>) => vElementNode;
9
- function Attach(node: any, vnode: vNodeType): vNodeType;
9
+ function Attach(node: any, vnode: vElementNode): vElementNode;
10
10
  }
package/Node/vNode.js CHANGED
@@ -13,6 +13,7 @@ export var vNode;
13
13
  return {
14
14
  definition,
15
15
  type: definition.type,
16
+ connected: false,
16
17
  injector: definition.componentFactory
17
18
  ? new Injector()
18
19
  : (Injector.Current() ?? new Injector()),
@@ -71,8 +72,11 @@ export var vNode;
71
72
  if (vnode.type === FRAGMENT_NODE)
72
73
  throw new Error("Cannot attach a fragment directly. A fragment has no DOM node — " +
73
74
  "wrap it in a real element (e.g. div) before attaching.");
75
+ // Must create a node + attach before Init so Connected can run correctly
76
+ // on a possible Component instance
77
+ const childNode = vnode.definition.node = CreateNode(vnode);
78
+ NodeConfig.addChild(node, childNode);
74
79
  Init(vnode);
75
- NodeConfig.addChild(node, vnode.node);
76
80
  return vnode;
77
81
  }
78
82
  vNode.Attach = Attach;
@@ -80,19 +84,22 @@ export var vNode;
80
84
  function ComponentChildren() {
81
85
  return this.Template();
82
86
  }
83
- function InitNode(vnode, parentNode) {
84
- const { type, namespace, props, attrs, on, data, componentFactory, children, childrenArray, } = vnode.definition;
85
- let node = null;
87
+ function CreateNode(vnode) {
86
88
  switch (vnode.type) {
87
89
  case FRAGMENT_NODE:
88
- break;
90
+ return null;
89
91
  case TEXT_NODE:
90
- vnode.node = node = vnode.definition.node ?? NodeConfig.createTextNode();
91
- break;
92
+ return vnode.definition.node ?? NodeConfig.createTextNode();
92
93
  default:
93
- vnode.node = node = vnode.definition.node ?? NodeConfig.createNode(type, namespace);
94
- break;
94
+ return vnode.definition.node ?? NodeConfig.createNode(vnode.definition.type, vnode.definition.namespace);
95
95
  }
96
+ }
97
+ function InitNode(vnode, parentNode) {
98
+ const {
99
+ /* type,
100
+ namespace, */
101
+ props, attrs, on, data, componentFactory, children, childrenArray, } = vnode.definition;
102
+ const node = vnode.node = CreateNode(vnode);
96
103
  vnode.definition = null;
97
104
  vnode.parentNode = parentNode;
98
105
  if (props) {
@@ -130,7 +137,7 @@ function InitNode(vnode, parentNode) {
130
137
  }
131
138
  if (componentFactory) {
132
139
  vnode.component = Injector.Scope(vnode.injector, componentFactory, vnode);
133
- vnode.component.Bound();
140
+ // vnode.component.Bound();
134
141
  Children(vnode, ComponentChildren.bind(vnode.component));
135
142
  }
136
143
  else if (childrenArray) {
@@ -173,7 +180,7 @@ function CreateChildrenScope(vnode, children, data) {
173
180
  return ObservableScope.Create(DynamicChildrenFunction.bind(null, vnode.injector, children, data));
174
181
  }
175
182
  function ToVNodeType(children) {
176
- return typeof children === "string" ? { type: STRING_NODE, node: children } : children;
183
+ return typeof children === "string" ? { type: STRING_NODE, connected: false, node: children } : children;
177
184
  }
178
185
  function GetNextNodes(injector, children, data) {
179
186
  return ToVNodeType(Injector.Scope(injector, children, data));
@@ -236,14 +243,18 @@ function UpdateChildren(vnode, init = false, skipInit = false) {
236
243
  if (vnode.destroyed || children !== vnode.children)
237
244
  return;
238
245
  if (init) {
239
- if (vnode.node !== null)
246
+ if (vnode.node !== null) {
240
247
  NodeConfig.reconcileChildren(vnode.node, CleanupChildrenArray(vnode.children));
248
+ vnode.component?.Bound();
249
+ FireConnected(vnode, true);
250
+ }
241
251
  }
242
252
  else if (!async) {
243
253
  let reconcileNode = vnode;
244
254
  while (reconcileNode.node === null)
245
255
  reconcileNode = reconcileNode.parentNode;
246
256
  NodeConfig.reconcileChildren(reconcileNode.node, CleanupChildrenArray(reconcileNode.children));
257
+ FireConnected(vnode, true);
247
258
  }
248
259
  else {
249
260
  NodeConfig.scheduleUpdate(function () {
@@ -253,11 +264,47 @@ function UpdateChildren(vnode, init = false, skipInit = false) {
253
264
  while (reconcileNode.node === null)
254
265
  reconcileNode = reconcileNode.parentNode;
255
266
  NodeConfig.reconcileChildren(reconcileNode.node, CleanupChildrenArray(reconcileNode.children));
267
+ FireConnected(vnode, true);
256
268
  });
257
269
  }
258
270
  });
259
271
  });
260
272
  }
273
+ function FireConnected(vnode, root = false) {
274
+ if ((!root && vnode.connected) ||
275
+ (vnode.type !== FRAGMENT_NODE && vnode.node === null))
276
+ return;
277
+ switch (vnode.type) {
278
+ case FRAGMENT_NODE:
279
+ break;
280
+ default: {
281
+ if (!vnode.connected) {
282
+ vnode.connected = NodeConfig.isConnected(vnode.node);
283
+ switch (vnode.type) {
284
+ case STRING_NODE:
285
+ case TEXT_NODE:
286
+ break;
287
+ default:
288
+ vnode.connected && vnode.component?.Connected();
289
+ }
290
+ }
291
+ break;
292
+ }
293
+ }
294
+ if (vnode.type !== FRAGMENT_NODE && !vnode.connected)
295
+ return;
296
+ switch (vnode.type) {
297
+ case STRING_NODE:
298
+ case TEXT_NODE:
299
+ break;
300
+ default: {
301
+ const children = vnode.children;
302
+ const count = children?.length;
303
+ for (let x = 0; x < count; x++)
304
+ FireConnected(children[x]);
305
+ }
306
+ }
307
+ }
261
308
  function ScheduledAssignment(assign) {
262
309
  let scheduled = false;
263
310
  return function (scope) {
@@ -27,10 +27,12 @@ export type vNodeConfig<P = HTMLElement, E = HTMLElementEventMap, T = never> = {
27
27
  };
28
28
  export type vStringNode = {
29
29
  type: typeof STRING_NODE;
30
+ connected: boolean;
30
31
  node: string;
31
32
  };
32
33
  export type vElementNode = {
33
34
  type: string | typeof TEXT_NODE | typeof FRAGMENT_NODE;
35
+ connected: boolean;
34
36
  definition: vNodeDefinition<any, any, any>;
35
37
  injector: Injector;
36
38
  parentNode: vElementNode | null;
@@ -34,16 +34,16 @@ function FlattenValue(root, value, keyFunc) {
34
34
  * @param path - Dot-separated path to the value (empty string returns source)
35
35
  * @returns The value at the specified path
36
36
  */
37
- function GetPathValue(source, path) {
37
+ function GetPathValue(source, path, keyFunc) {
38
38
  if (path === "")
39
39
  return [source, true];
40
40
  const parts = path.split(".");
41
41
  let curr = source;
42
42
  let x = 0;
43
43
  for (; curr && x < parts.length - 1; x++)
44
- curr = curr[parts[x]];
44
+ curr = ResolveKeyed(source, curr[parts[x]], keyFunc);
45
45
  if (curr && Object.hasOwn(curr, parts[x])) {
46
- curr = curr[parts[x]];
46
+ curr = ResolveKeyed(source, curr[parts[x]], keyFunc);
47
47
  x++;
48
48
  }
49
49
  else
@@ -224,7 +224,7 @@ function RunProjections(source, keyFunc, projections, memo) {
224
224
  let y = 0;
225
225
  for (; allPathsExist && y < reads.length && !reads[y].endsWith("*"); y++) {
226
226
  const read = reads[y];
227
- const [value, found] = GetPathValue(source, read);
227
+ const [value, found] = GetPathValue(source, read, keyFunc);
228
228
  allPathsExist = found;
229
229
  keys.push(read);
230
230
  values.push((found && CreateSnapshot(source, keyFunc, value)) || value);
@@ -233,6 +233,7 @@ function RunProjections(source, keyFunc, projections, memo) {
233
233
  const prefix = reads[y].slice(0, reads[y].length - 1);
234
234
  const matchedKeys = Object.keys(source).filter(key => key.startsWith(prefix));
235
235
  for (let z = 0; z < matchedKeys.length; z++) {
236
+ // No keyFunc passed because dot paths are not supported for wildcard paths
236
237
  const [value, found] = GetPathValue(source, matchedKeys[z]);
237
238
  allPathsExist = allPathsExist && found;
238
239
  keys.push(matchedKeys[z]);
@@ -253,6 +254,12 @@ function RunProjections(source, keyFunc, projections, memo) {
253
254
  }
254
255
  return diffResult;
255
256
  }
257
+ function ResolveKeyed(source, value, keyFunc) {
258
+ if (keyFunc === undefined || JsonType(value) === 'value')
259
+ return value;
260
+ const key = keyFunc(value);
261
+ return (key && source[key]) ?? value;
262
+ }
256
263
  /**
257
264
  * Resolves a path to its keyed root object if applicable.
258
265
  * Searches up the path to find an object with a key from keyFunc.
@@ -402,7 +409,7 @@ export class DiffTree {
402
409
  * @returns The value at the specified path
403
410
  */
404
411
  GetPath(path) {
405
- const [value] = GetPathValue(this.rootState, path);
412
+ const [value] = GetPathValue(this.rootState, path, this.keyFunc);
406
413
  return value;
407
414
  }
408
415
  /**
@@ -411,7 +418,7 @@ export class DiffTree {
411
418
  * @param path - Dot-separated path to the value
412
419
  */
413
420
  Snapshot(path) {
414
- const [value, found] = GetPathValue(this.rootState, path);
421
+ const [value, found] = GetPathValue(this.rootState, path, this.keyFunc);
415
422
  if (!found)
416
423
  throw `Unable to get snapshot for path ${path}`;
417
424
  return CreateSnapshot(this.rootState, this.keyFunc, value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "8.0.21",
3
+ "version": "8.0.24",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",