@domql/element 3.6.3 → 3.6.6

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.
package/README.md CHANGED
@@ -34,4 +34,60 @@ const MyComponent = {
34
34
  }
35
35
  ```
36
36
 
37
- `el.html` takes priority when both are set. `props.html` is used as a fallback when `el.html` is not defined.
37
+ `el.html` takes priority when both are set. `props.html` is used as a fallback when `el.html` is not defined.
38
+
39
+ ## REGISTRY (`mixins/registry.js`)
40
+
41
+ The `REGISTRY` object defines which keys are recognized as framework properties (rather than child elements). Any key **not** in REGISTRY and starting with an uppercase letter is treated as a child element.
42
+
43
+ **Every framework-level key must be listed here.** If a key like `childExtend` is missing from REGISTRY, it gets interpreted as a child element name, causing silent rendering failures (e.g., cart components not rendering in Archy).
44
+
45
+ Current framework keys that must remain in REGISTRY:
46
+
47
+ ```
48
+ extends, children, content,
49
+ childExtend (deprecated), childExtends,
50
+ childExtendRecursive (deprecated), childExtendsRecursive,
51
+ props, if, define,
52
+ __name, __ref, __hash, __text, key, tag, query, parent, node,
53
+ variables, on, component, context
54
+ ```
55
+
56
+ Plus mixin handlers: `attr`, `style`, `text`, `html`, `data`, `classlist`, `state`, `scope`, `deps`.
57
+
58
+ > **v3 note:** `childExtend` (singular) is deprecated v2 syntax — use `childExtends` (plural) in new code. The singular forms remain in REGISTRY for backwards compatibility with older projects. If a key is missing from REGISTRY, it gets interpreted as a child element name, causing silent rendering failures.
59
+
60
+ ## set.js — Content Setting
61
+
62
+ ### Fragment forwarding
63
+
64
+ When `set()` receives content with `tag: 'fragment'`, the fragment itself doesn't create a DOM node — its children are inserted directly. This means:
65
+
66
+ 1. **childExtends forwarding** — The parent's `childExtends` must be forwarded to the fragment's params so that fragment children inherit the correct extends:
67
+ ```javascript
68
+ if (tag === 'fragment') {
69
+ const elementChildExtends = element.childExtends || element.childExtend
70
+ if (!childExtends && elementChildExtends) {
71
+ params.childExtends = elementChildExtends
72
+ }
73
+ }
74
+ ```
75
+ (Also checks deprecated `childExtend` for backwards compatibility with v2 projects.)
76
+
77
+ 2. **childProps forwarding** — Similarly, `childProps` from the parent must be explicitly passed through the fragment to its children.
78
+
79
+ 3. **ignoreChildProps** — When forwarding `childProps` through a fragment, set `props.ignoreChildProps = true` on the fragment itself to prevent the fragment from inheriting the parent's `childProps` via `inheritParentProps`. The `childProps` are already forwarded explicitly for the fragment's children.
80
+
81
+ ### Infinite loop guard
82
+
83
+ `set()` includes a re-entrancy guard via `ref.__settingContent`. Without this, define handlers (like `$router`) that call `el.set()` can trigger infinite recursion when content-setting triggers another content-set cycle.
84
+
85
+ ```javascript
86
+ if (ref.__settingContent) return element
87
+ ref.__settingContent = true
88
+ try {
89
+ return _setInner(params, options, element)
90
+ } finally {
91
+ ref.__settingContent = false
92
+ }
93
+ ```
package/children.js CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  deepClone,
6
6
  exec,
7
7
  getChildStateInKey,
8
+ getParentStateInKey,
9
+ getRootStateInKey,
8
10
  isArray,
9
11
  isDefined,
10
12
  isNot,
@@ -45,6 +47,32 @@ const deepChildrenEqual = (a, b) => {
45
47
  return a === b
46
48
  }
47
49
 
50
+ const SELF_STATE_PATHS = new Set(['.', '/', './'])
51
+
52
+ const resolveChildrenFromState = (path, state) => {
53
+ if (!isString(path) || !state) return
54
+
55
+ // children: 'state' | '/' | './' → current state
56
+ if (SELF_STATE_PATHS.has(path)) return state.parse ? state.parse() : state
57
+
58
+ // children: '~/path' → from root state
59
+ const rootState = getRootStateInKey(path, state)
60
+ if (rootState) {
61
+ const cleanKey = path.replaceAll('~/', '')
62
+ return getChildStateInKey(cleanKey, rootState)
63
+ }
64
+
65
+ // children: '../path' → from ancestor state
66
+ const parentState = getParentStateInKey(path, state)
67
+ if (parentState) {
68
+ const cleanKey = path.replaceAll('../', '')
69
+ return getChildStateInKey(cleanKey, parentState)
70
+ }
71
+
72
+ // children: 'key' or 'key/nested' → from current state
73
+ return getChildStateInKey(path, state)
74
+ }
75
+
48
76
  /**
49
77
  * Apply data parameters on the DOM nodes
50
78
  * this should only work if `showOnNode: true` is passed
@@ -57,18 +85,20 @@ export function setChildren (param, element, opts) {
57
85
  let execChildren = exec(children, element, state)
58
86
  children = execParam || execChildren
59
87
 
88
+ let childrenStatePath
89
+
60
90
  if (children) {
61
- if (isState(children)) children = children.parse()
91
+ if (isState(children)) {
92
+ childrenAs = childrenAs || 'state'
93
+ children = children.parse()
94
+ }
62
95
  if (isString(children) || isNumber(children)) {
63
- if (children === 'state') children = state.parse()
64
- else {
65
- const pathInState = getChildStateInKey(children, state)
66
- if (pathInState) {
67
- childrenAs = 'state'
68
- children = getChildStateInKey(children, state) || { value: children }
69
- } else {
70
- children = { text: children }
71
- }
96
+ const resolved = resolveChildrenFromState(children, state)
97
+ if (resolved !== undefined) {
98
+ childrenStatePath = SELF_STATE_PATHS.has(children) ? '' : children
99
+ children = isState(resolved) ? resolved.parse() : resolved
100
+ } else {
101
+ children = { text: children }
72
102
  }
73
103
  }
74
104
 
@@ -124,13 +154,19 @@ export function setChildren (param, element, opts) {
124
154
  for (const key in children) {
125
155
  const value = Object.prototype.hasOwnProperty.call(children, key) && children[key]
126
156
  if (isDefined(value) && value !== null && value !== false) {
127
- content[key] = isObjectLike(value)
128
- ? childrenAs
129
- ? { [childrenAs]: value }
130
- : value
131
- : childrenAs
132
- ? { [childrenAs]: childrenAs === 'state' ? { value } : { text: value } }
133
- : { text: value }
157
+ if (childrenStatePath !== undefined && childrenAs !== 'props') {
158
+ // Set inherited state path so DOMQL resolves at runtime with hoisting
159
+ const statePath = childrenStatePath ? `${childrenStatePath}/${key}` : key
160
+ content[key] = { state: statePath }
161
+ } else {
162
+ content[key] = isObjectLike(value)
163
+ ? childrenAs
164
+ ? { [childrenAs]: value }
165
+ : value
166
+ : childrenAs
167
+ ? { [childrenAs]: childrenAs === 'state' ? { value } : { text: value } }
168
+ : { text: value }
169
+ }
134
170
  }
135
171
  }
136
172
 
package/create.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
 
23
23
  import { applyAnimationFrame, triggerEventOn } from './event/index.js'
24
24
  import { assignNode } from './render/index.js'
25
+ import { detectTag } from './render/cache.js'
25
26
  import { createState } from '@domql/state'
26
27
 
27
28
  import { REGISTRY } from './mixins/index.js'
@@ -98,6 +99,8 @@ export const create = (
98
99
  initProps(element, parent, options)
99
100
  // Re-pickup component-named properties that entered props via childProps/inheritParentProps
100
101
  pickupElementFromProps.call(element, element, { cachedKeys: [] })
102
+ // Detect tag early so applyPropsAsAttrs can filter by tag
103
+ if (!element.tag) element.tag = detectTag(element)
101
104
  // Populate element.attr from props matching the tag's HTML spec
102
105
  applyPropsAsAttrs(element)
103
106
  if (element.scope === 'props' || element.scope === true) {
@@ -49,24 +49,41 @@ const deepChildrenEqual = (a, b) => {
49
49
  }
50
50
  return a === b;
51
51
  };
52
+ const SELF_STATE_PATHS = /* @__PURE__ */ new Set([".", "/", "./"]);
53
+ const resolveChildrenFromState = (path, state) => {
54
+ if (!(0, import_utils.isString)(path) || !state) return;
55
+ if (SELF_STATE_PATHS.has(path)) return state.parse ? state.parse() : state;
56
+ const rootState = (0, import_utils.getRootStateInKey)(path, state);
57
+ if (rootState) {
58
+ const cleanKey = path.replaceAll("~/", "");
59
+ return (0, import_utils.getChildStateInKey)(cleanKey, rootState);
60
+ }
61
+ const parentState = (0, import_utils.getParentStateInKey)(path, state);
62
+ if (parentState) {
63
+ const cleanKey = path.replaceAll("../", "");
64
+ return (0, import_utils.getChildStateInKey)(cleanKey, parentState);
65
+ }
66
+ return (0, import_utils.getChildStateInKey)(path, state);
67
+ };
52
68
  function setChildren(param, element, opts) {
53
69
  let { children, __ref: ref, state } = element;
54
70
  let { childrenAs } = element.props || {};
55
71
  let execParam = (0, import_utils.exec)(param, element, state);
56
72
  let execChildren = (0, import_utils.exec)(children, element, state);
57
73
  children = execParam || execChildren;
74
+ let childrenStatePath;
58
75
  if (children) {
59
- if ((0, import_utils.isState)(children)) children = children.parse();
76
+ if ((0, import_utils.isState)(children)) {
77
+ childrenAs = childrenAs || "state";
78
+ children = children.parse();
79
+ }
60
80
  if ((0, import_utils.isString)(children) || (0, import_utils.isNumber)(children)) {
61
- if (children === "state") children = state.parse();
62
- else {
63
- const pathInState = (0, import_utils.getChildStateInKey)(children, state);
64
- if (pathInState) {
65
- childrenAs = "state";
66
- children = (0, import_utils.getChildStateInKey)(children, state) || { value: children };
67
- } else {
68
- children = { text: children };
69
- }
81
+ const resolved = resolveChildrenFromState(children, state);
82
+ if (resolved !== void 0) {
83
+ childrenStatePath = SELF_STATE_PATHS.has(children) ? "" : children;
84
+ children = (0, import_utils.isState)(resolved) ? resolved.parse() : resolved;
85
+ } else {
86
+ children = { text: children };
70
87
  }
71
88
  }
72
89
  if ((0, import_utils.isObject)(children)) {
@@ -113,7 +130,12 @@ function setChildren(param, element, opts) {
113
130
  for (const key in children) {
114
131
  const value = Object.prototype.hasOwnProperty.call(children, key) && children[key];
115
132
  if ((0, import_utils.isDefined)(value) && value !== null && value !== false) {
116
- content[key] = (0, import_utils.isObjectLike)(value) ? childrenAs ? { [childrenAs]: value } : value : childrenAs ? { [childrenAs]: childrenAs === "state" ? { value } : { text: value } } : { text: value };
133
+ if (childrenStatePath !== void 0 && childrenAs !== "props") {
134
+ const statePath = childrenStatePath ? `${childrenStatePath}/${key}` : key;
135
+ content[key] = { state: statePath };
136
+ } else {
137
+ content[key] = (0, import_utils.isObjectLike)(value) ? childrenAs ? { [childrenAs]: value } : value : childrenAs ? { [childrenAs]: childrenAs === "state" ? { value } : { text: value } } : { text: value };
138
+ }
117
139
  }
118
140
  }
119
141
  return content;
@@ -27,6 +27,7 @@ var import_tree = require("./tree.js");
27
27
  var import_utils = require("@domql/utils");
28
28
  var import_event = require("./event/index.js");
29
29
  var import_render = require("./render/index.js");
30
+ var import_cache = require("./render/cache.js");
30
31
  var import_state = require("@domql/state");
31
32
  var import_mixins = require("./mixins/index.js");
32
33
  var import_set = require("./methods/set.js");
@@ -75,6 +76,7 @@ const create = (props, parentEl, passedKey, options = import_utils.OPTIONS.creat
75
76
  (0, import_utils.createIfConditionFlag)(element, parent);
76
77
  (0, import_utils.initProps)(element, parent, options);
77
78
  import_utils.pickupElementFromProps.call(element, element, { cachedKeys: [] });
79
+ if (!element.tag) element.tag = (0, import_cache.detectTag)(element);
78
80
  applyPropsAsAttrs(element);
79
81
  if (element.scope === "props" || element.scope === true) {
80
82
  element.scope = element.props;
@@ -39,7 +39,9 @@ const addMethods = (element, parent, options = {}) => {
39
39
  lookdown: import_methods.lookdown,
40
40
  lookdownAll: import_methods.lookdownAll,
41
41
  getRef: import_methods.getRef,
42
+ getDB: import_methods.getDB,
42
43
  getPath: import_methods.getPath,
44
+ getQuery: import_methods.getQuery,
43
45
  getRootState: import_methods.getRootState,
44
46
  getRoot: import_methods.getRoot,
45
47
  getRootData: import_methods.getRootData,
@@ -49,6 +49,7 @@ const REGISTRY = {
49
49
  classlist: import_classList.classList,
50
50
  state: import_state.default,
51
51
  scope: import_scope.default,
52
+ fetch: {},
52
53
  deps: (param, el) => param || el.parent.deps,
53
54
  extends: {},
54
55
  children: {},
@@ -214,6 +214,9 @@ const update = function(params = {}, opts) {
214
214
  if (!preventUpdateListener && !options.preventListeners) {
215
215
  (0, import_event.triggerEventOn)("update", element, options);
216
216
  }
217
+ if (!options.preventFetch && ref.__fetchOnStateChange) {
218
+ ref.__fetchOnStateChange();
219
+ }
217
220
  };
218
221
  const findSiblingAttachOptions = (element, parent) => {
219
222
  const { __children } = parent.__ref || {};
@@ -302,24 +305,13 @@ const checkIfOnUpdate = (element, parent, options) => {
302
305
  const inheritStateUpdates = (element, options) => {
303
306
  const { __ref: ref } = element;
304
307
  const stateKey = ref.__state;
305
- const { parent, state } = element;
306
- const { preventUpdateTriggerStateUpdate, isHoisted, execStateFunction } = options;
308
+ const { parent } = element;
309
+ const { preventUpdateTriggerStateUpdate } = options;
307
310
  if (preventUpdateTriggerStateUpdate) return;
308
311
  if (!stateKey && !ref.__hasRootState) {
309
312
  element.state = parent?.state || {};
310
313
  return;
311
314
  }
312
- const shouldForceFunctionState = (0, import_utils.isFunction)(stateKey) && !isHoisted && execStateFunction;
313
- if (shouldForceFunctionState) {
314
- const execState = (0, import_utils.exec)(stateKey, element);
315
- state.set(execState, {
316
- ...options,
317
- preventUpdate: true,
318
- preventStateUpdateListener: false,
319
- updatedByStateFunction: true
320
- });
321
- return;
322
- }
323
315
  const keyInParentState = (0, import_utils.findInheritedState)(element, element.parent);
324
316
  if (!keyInParentState || options.preventInheritedStateUpdate) return;
325
317
  if (!options.preventBeforeStateUpdateListener && !options.preventListeners) {
@@ -3,6 +3,8 @@ import {
3
3
  deepClone,
4
4
  exec,
5
5
  getChildStateInKey,
6
+ getParentStateInKey,
7
+ getRootStateInKey,
6
8
  isArray,
7
9
  isDefined,
8
10
  isNot,
@@ -39,24 +41,41 @@ const deepChildrenEqual = (a, b) => {
39
41
  }
40
42
  return a === b;
41
43
  };
44
+ const SELF_STATE_PATHS = /* @__PURE__ */ new Set([".", "/", "./"]);
45
+ const resolveChildrenFromState = (path, state) => {
46
+ if (!isString(path) || !state) return;
47
+ if (SELF_STATE_PATHS.has(path)) return state.parse ? state.parse() : state;
48
+ const rootState = getRootStateInKey(path, state);
49
+ if (rootState) {
50
+ const cleanKey = path.replaceAll("~/", "");
51
+ return getChildStateInKey(cleanKey, rootState);
52
+ }
53
+ const parentState = getParentStateInKey(path, state);
54
+ if (parentState) {
55
+ const cleanKey = path.replaceAll("../", "");
56
+ return getChildStateInKey(cleanKey, parentState);
57
+ }
58
+ return getChildStateInKey(path, state);
59
+ };
42
60
  function setChildren(param, element, opts) {
43
61
  let { children, __ref: ref, state } = element;
44
62
  let { childrenAs } = element.props || {};
45
63
  let execParam = exec(param, element, state);
46
64
  let execChildren = exec(children, element, state);
47
65
  children = execParam || execChildren;
66
+ let childrenStatePath;
48
67
  if (children) {
49
- if (isState(children)) children = children.parse();
68
+ if (isState(children)) {
69
+ childrenAs = childrenAs || "state";
70
+ children = children.parse();
71
+ }
50
72
  if (isString(children) || isNumber(children)) {
51
- if (children === "state") children = state.parse();
52
- else {
53
- const pathInState = getChildStateInKey(children, state);
54
- if (pathInState) {
55
- childrenAs = "state";
56
- children = getChildStateInKey(children, state) || { value: children };
57
- } else {
58
- children = { text: children };
59
- }
73
+ const resolved = resolveChildrenFromState(children, state);
74
+ if (resolved !== void 0) {
75
+ childrenStatePath = SELF_STATE_PATHS.has(children) ? "" : children;
76
+ children = isState(resolved) ? resolved.parse() : resolved;
77
+ } else {
78
+ children = { text: children };
60
79
  }
61
80
  }
62
81
  if (isObject(children)) {
@@ -103,7 +122,12 @@ function setChildren(param, element, opts) {
103
122
  for (const key in children) {
104
123
  const value = Object.prototype.hasOwnProperty.call(children, key) && children[key];
105
124
  if (isDefined(value) && value !== null && value !== false) {
106
- content[key] = isObjectLike(value) ? childrenAs ? { [childrenAs]: value } : value : childrenAs ? { [childrenAs]: childrenAs === "state" ? { value } : { text: value } } : { text: value };
125
+ if (childrenStatePath !== void 0 && childrenAs !== "props") {
126
+ const statePath = childrenStatePath ? `${childrenStatePath}/${key}` : key;
127
+ content[key] = { state: statePath };
128
+ } else {
129
+ content[key] = isObjectLike(value) ? childrenAs ? { [childrenAs]: value } : value : childrenAs ? { [childrenAs]: childrenAs === "state" ? { value } : { text: value } } : { text: value };
130
+ }
107
131
  }
108
132
  }
109
133
  return content;
@@ -18,6 +18,7 @@ import {
18
18
  } from "@domql/utils";
19
19
  import { applyAnimationFrame, triggerEventOn } from "./event/index.js";
20
20
  import { assignNode } from "./render/index.js";
21
+ import { detectTag } from "./render/cache.js";
21
22
  import { createState } from "@domql/state";
22
23
  import { REGISTRY } from "./mixins/index.js";
23
24
  import { addMethods } from "./methods/set.js";
@@ -66,6 +67,7 @@ const create = (props, parentEl, passedKey, options = OPTIONS.create || {}, atta
66
67
  createIfConditionFlag(element, parent);
67
68
  initProps(element, parent, options);
68
69
  pickupElementFromProps.call(element, element, { cachedKeys: [] });
70
+ if (!element.tag) element.tag = detectTag(element);
69
71
  applyPropsAsAttrs(element);
70
72
  if (element.scope === "props" || element.scope === true) {
71
73
  element.scope = element.props;
@@ -5,7 +5,9 @@ import {
5
5
  call,
6
6
  error,
7
7
  getContext,
8
+ getDB,
8
9
  getPath,
10
+ getQuery,
9
11
  getRef,
10
12
  getRoot,
11
13
  getRootContext,
@@ -42,7 +44,9 @@ const addMethods = (element, parent, options = {}) => {
42
44
  lookdown,
43
45
  lookdownAll,
44
46
  getRef,
47
+ getDB,
45
48
  getPath,
49
+ getQuery,
46
50
  getRootState,
47
51
  getRoot,
48
52
  getRootData,
@@ -15,6 +15,7 @@ const REGISTRY = {
15
15
  classlist: classList,
16
16
  state,
17
17
  scope,
18
+ fetch: {},
18
19
  deps: (param, el) => param || el.parent.deps,
19
20
  extends: {},
20
21
  children: {},
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  window,
3
- exec,
4
3
  isArray,
5
4
  isFunction,
6
5
  isNumber,
@@ -201,6 +200,9 @@ const update = function(params = {}, opts) {
201
200
  if (!preventUpdateListener && !options.preventListeners) {
202
201
  triggerEventOn("update", element, options);
203
202
  }
203
+ if (!options.preventFetch && ref.__fetchOnStateChange) {
204
+ ref.__fetchOnStateChange();
205
+ }
204
206
  };
205
207
  const findSiblingAttachOptions = (element, parent) => {
206
208
  const { __children } = parent.__ref || {};
@@ -289,24 +291,13 @@ const checkIfOnUpdate = (element, parent, options) => {
289
291
  const inheritStateUpdates = (element, options) => {
290
292
  const { __ref: ref } = element;
291
293
  const stateKey = ref.__state;
292
- const { parent, state } = element;
293
- const { preventUpdateTriggerStateUpdate, isHoisted, execStateFunction } = options;
294
+ const { parent } = element;
295
+ const { preventUpdateTriggerStateUpdate } = options;
294
296
  if (preventUpdateTriggerStateUpdate) return;
295
297
  if (!stateKey && !ref.__hasRootState) {
296
298
  element.state = parent?.state || {};
297
299
  return;
298
300
  }
299
- const shouldForceFunctionState = isFunction(stateKey) && !isHoisted && execStateFunction;
300
- if (shouldForceFunctionState) {
301
- const execState = exec(stateKey, element);
302
- state.set(execState, {
303
- ...options,
304
- preventUpdate: true,
305
- preventStateUpdateListener: false,
306
- updatedByStateFunction: true
307
- });
308
- return;
309
- }
310
301
  const keyInParentState = findInheritedState(element, element.parent);
311
302
  if (!keyInParentState || options.preventInheritedStateUpdate) return;
312
303
  if (!options.preventBeforeStateUpdateListener && !options.preventListeners) {