@symbo.ls/mcp 1.0.11 → 1.0.14

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.
@@ -1,84 +1,69 @@
1
- # DOMQL v3 Syntax — Complete Language Reference
1
+ # DOMQL v3 Syntax Reference
2
2
 
3
- Every pattern here is derived from real working code. Use this as the authoritative reference for writing correct DOMQL v3.
3
+ Authoritative reference for generating correct DOMQL v3 code. Every pattern is derived from real working code.
4
4
 
5
5
  ---
6
6
 
7
- ## 1. Element Anatomy
7
+ ## Element Anatomy
8
8
 
9
- A DOMQL element is a plain JS object. Every key has a specific meaning:
9
+ Produce DOMQL elements as plain JS objects. Every key has a specific role:
10
10
 
11
11
  ```js
12
12
  export const MyCard = {
13
- // ── Composition ────────────────────────────────────────────────────
14
- extends: 'Flex', // extend from registered component(s)
15
-
16
- // ── DOM ────────────────────────────────────────────────────────────
17
13
  tag: 'section', // HTML tag (default: div)
18
14
 
19
- // ── CSS props (top-level promoted to props via propertizeElement)
20
- padding: 'B C', // design-token value
15
+ // CSS props (top-level, promoted via propertizeElement)
16
+ padding: 'B C',
21
17
  gap: 'A',
22
18
  flow: 'column',
23
19
  theme: 'dialog',
24
20
  round: 'C',
25
21
 
26
- // ── HTML attributes ────────────────────────────────────────────────
27
- attr: {
28
- role: 'region',
29
- 'aria-label': ({ props }) => props.label
30
- },
22
+ // HTML attributes
23
+ attr: { role: 'region', 'aria-label': ({ props }) => props.label },
31
24
 
32
- // ── State ──────────────────────────────────────────────────────────
25
+ // State
33
26
  state: { open: false },
34
27
 
35
- // ── Events (v3 style) ──────────────────────────────────────────────
28
+ // Events (v3 top-level)
36
29
  onClick: (event, el, state) => { state.update({ open: !state.open }) },
37
30
  onRender: (el, state) => { console.log('rendered') },
38
31
 
39
- // ── Child elements ─────────────────────────────────────────────────
40
- Header: {
41
- extends: 'Flex',
42
- text: ({ props }) => props.title
43
- },
44
- Body: {
45
- html: ({ props }) => props.content
46
- }
32
+ // Children (PascalCase keys)
33
+ Header: { text: ({ props }) => props.title },
34
+ Body: { html: ({ props }) => props.content }
47
35
  }
48
36
  ```
49
37
 
50
38
  ---
51
39
 
52
- ## 2. Element Lifecycle (internal flow)
40
+ ## Element Lifecycle
53
41
 
54
42
  ```
55
43
  create(props, parent, key, options)
56
- ├── createElement() creates bare element
57
- ├── applyExtends() merges extends stack (element wins over extends)
58
- ├── propertizeElement() routes onXxx events, promotes CSS props
59
- ├── addMethods() attaches el.lookup / el.update / etc.
60
- ├── initProps() builds props from propsStack
61
- └── createNode()
62
- ├── throughInitialExec() executes function props
63
- ├── applyEventsOnNode() binds element.on.* DOM addEventListener
64
- └── iterates children create() recursively
44
+ |- createElement() creates bare element
45
+ |- applyExtends() merges extends stack (element wins)
46
+ |- propertizeElement() routes onXxx events, promotes CSS props
47
+ |- addMethods() attaches el.lookup / el.update / etc.
48
+ |- initProps() builds props from propsStack
49
+ +- createNode()
50
+ |- throughInitialExec() executes function props
51
+ |- applyEventsOnNode() binds element.on.* to DOM
52
+ +- iterates children -> create() recursively
65
53
  ```
66
54
 
67
- **Critical ordering**: `propertizeElement` runs BEFORE `addMethods`. Do not rely on prototype methods in `propertizeElement`.
55
+ `propertizeElement` runs BEFORE `addMethods`. Do not rely on prototype methods during propertization.
68
56
 
69
- ### Propertization and the Define System
57
+ ### Propertization and Define System
70
58
 
71
- `propertizeElement()` classifies keys between the element root and `props`. Keys starting with `$` overlap between css-in-props conditionals (`$isActive`) and define handlers (built-in `$router`, plus deprecated v2 handlers like `$propsCollection`, `$collection` in older projects).
59
+ `propertizeElement()` classifies keys between root and `props`. Keys starting with `$` overlap between css-in-props conditionals (`$isActive`) and define handlers (`$router`, deprecated v2 handlers like `$propsCollection`, `$collection`).
72
60
 
73
- **Rule:** Keys with a matching define handler (`element.define[key]` or `context.define[key]`) must stay at the element root so `throughInitialDefine` can process them. This is critical for `$router` and for backwards compatibility with older projects. The propertization checks for define handlers BEFORE applying `CSS_SELECTOR_PREFIXES`:
61
+ Define handlers (`element.define[key]` or `context.define[key]`) must stay at the element root so `throughInitialDefine` can process them. Propertization checks for define handlers BEFORE applying `CSS_SELECTOR_PREFIXES`:
74
62
 
75
63
  ```js
76
- // Check define handlers first — these stay at root
77
64
  const defineValue = this.define?.[key]
78
65
  const globalDefineValue = this.context?.define?.[key]
79
66
  if (isFunction(defineValue) || isFunction(globalDefineValue)) continue
80
-
81
- // Only then apply prefix-based classification
82
67
  if (CSS_SELECTOR_PREFIXES.has(firstChar)) {
83
68
  obj.props[key] = value // move to props for css-in-props
84
69
  }
@@ -86,7 +71,9 @@ if (CSS_SELECTOR_PREFIXES.has(firstChar)) {
86
71
 
87
72
  ---
88
73
 
89
- ## 3. REGISTRY Keys (handled by DOMQL, NOT promoted to CSS props)
74
+ ## REGISTRY Keys
75
+
76
+ These keys are handled by DOMQL internally and are NOT promoted to CSS props:
90
77
 
91
78
  ```
92
79
  attr, style, text, html, data, classlist, state, scope, deps,
@@ -96,64 +83,58 @@ props, if, define, __name, __ref, __hash, __text,
96
83
  key, tag, query, parent, node, variables, on, component, context
97
84
  ```
98
85
 
99
- Any key NOT in this list and not a component name (uppercase first) promoted to `element.props` as a CSS prop.
86
+ Any key NOT in this list and not PascalCase (component) is promoted to `element.props` as a CSS prop.
100
87
 
101
- > **v3 note:** Use `childExtends` (plural) `childExtend` (singular) is deprecated v2 syntax. The singular forms still exist in REGISTRY for backwards compatibility with older projects, but new code should always use the plural form.
88
+ Always use `childExtends` (plural). The singular `childExtend` is deprecated v2 syntax kept for backwards compatibility.
102
89
 
103
90
  ---
104
91
 
105
- ## 4. Extending & Composing
92
+ ## Extending and Composing
106
93
 
107
- ### Single extend
108
- ```js
109
- export const PrimaryButton = { extends: 'Button' }
110
- ```
94
+ | Pattern | Syntax |
95
+ |---|---|
96
+ | Single extend | `extends: 'Button'` |
97
+ | Multiple (first = highest priority) | `extends: [Link, RouterLink]` |
98
+ | String reference (from `context.components`) | `extends: 'Hoverable'` |
99
+ | Multiple strings | `extends: ['IconText', 'FocusableComponent']` |
111
100
 
112
- ### Multiple extends — order matters (first = highest priority)
113
- ```js
114
- export const RouteLink = { extends: [Link, RouterLink] }
115
- export const Button = { extends: ['IconText', 'FocusableComponent'] }
116
- ```
117
-
118
- ### String reference (resolved from `context.components`)
119
- ```js
120
- export const MyItem = { extends: 'Hoverable' }
121
- ```
101
+ ### Merge Semantics
122
102
 
123
- ### Merge semantics
124
- - Element's own properties **always win** over extends properties
125
- - Objects are deep-merged (both sides preserved)
126
- - Functions are NOT merged element's function replaces extend's
127
- - Arrays are concatenated
103
+ | Type | Rule |
104
+ |---|---|
105
+ | Own properties | Always win over extends |
106
+ | Objects | Deep-merged (both sides preserved) |
107
+ | Functions | NOT merged; element's function replaces extend's |
108
+ | Arrays | Concatenated |
128
109
 
129
110
  ---
130
111
 
131
- ## 5. CSS Props Top-Level Promotion
112
+ ## CSS Props (Top-Level Promotion)
132
113
 
133
- Top-level non-registry, non-component keys become `element.props`:
114
+ Place CSS props at the element root. Non-registry, non-PascalCase keys become `element.props`:
134
115
 
135
116
  ```js
136
117
  export const Card = {
137
- padding: 'B C', // props.padding
138
- gap: 'Z', // props.gap
139
- flow: 'column', // → props.flow (shorthand for flexDirection)
140
- align: 'center', // → props.align (NOT flexAlign — see RULES.md)
141
- fontSize: 'A', // → props.fontSize
142
- fontWeight: '500', // → props.fontWeight
143
- color: 'currentColor', // → props.color
144
- background: 'codGray', // → props.background
145
- round: 'C', // → props.round (border-radius token)
118
+ padding: 'B C', // -> props.padding
119
+ gap: 'Z', // -> props.gap
120
+ flow: 'column', // shorthand for flexDirection
121
+ align: 'center', // NOT flexAlign
122
+ fontSize: 'A',
123
+ fontWeight: '500',
124
+ color: 'currentColor',
125
+ background: 'codGray',
126
+ round: 'C', // border-radius token
146
127
  opacity: '0.85',
147
128
  overflow: 'hidden',
148
129
  transition: 'B defaultBezier',
149
130
  transitionProperty: 'opacity, transform',
150
131
  zIndex: 10,
151
- tag: 'section', // stays at root (in REGISTRY)
152
- attr: { href: ... } // stays at root (in REGISTRY)
132
+ tag: 'section', // stays at root (REGISTRY)
133
+ attr: { href: '...' } // stays at root (REGISTRY)
153
134
  }
154
135
  ```
155
136
 
156
- ### Pseudo-classes and pseudo-elements
137
+ ### Pseudo-Classes and Pseudo-Elements
157
138
 
158
139
  ```js
159
140
  export const Hoverable = {
@@ -168,7 +149,7 @@ export const Hoverable = {
168
149
  }
169
150
  ```
170
151
 
171
- ### CSS class state modifiers (Emotion `.className`)
152
+ ### CSS Class State Modifiers (Emotion `.className`)
172
153
 
173
154
  ```js
174
155
  export const Item = {
@@ -179,7 +160,7 @@ export const Item = {
179
160
  }
180
161
  ```
181
162
 
182
- ### Raw style object (escape hatch)
163
+ ### Raw Style Object (Escape Hatch)
183
164
 
184
165
  ```js
185
166
  export const DropdownParent = {
@@ -192,7 +173,7 @@ export const DropdownParent = {
192
173
  }
193
174
  ```
194
175
 
195
- ### Media queries (responsive)
176
+ ### Media Queries
196
177
 
197
178
  ```js
198
179
  export const Grid = {
@@ -206,32 +187,58 @@ export const Grid = {
206
187
 
207
188
  ---
208
189
 
209
- ## 6. Events
190
+ ## Events
210
191
 
211
- ### v3 syntax — top-level `onXxx` (preferred)
192
+ ### v3 Syntax (Top-Level `onXxx`)
193
+
194
+ Use top-level `onXxx` handlers. Two signatures exist:
195
+
196
+ **DOM events** -- signature: `(event, el, state)`:
212
197
 
213
198
  ```js
214
- export const MyForm = {
215
- // DOM events — signature: (event, el, state)
216
- onClick: (event, el, state) => { /* ... */ },
217
- onChange: (event, el, state) => { /* ... */ },
218
- onInput: (event, el, state) => { state.update({ value: event.target.value }) },
219
- onSubmit: (event, el, state) => { event.preventDefault(); /* ... */ },
220
- onKeydown: (event, el, state) => { if (event.key === 'Enter') /* ... */ },
221
- onMouseover:(event, el, state) => { /* ... */ },
222
- onBlur: (event, el, state) => { /* ... */ },
223
- onFocus: (event, el, state) => { /* ... */ },
199
+ onClick: (event, el, state) => { /* ... */ }
200
+ onChange: (event, el, state) => { /* ... */ }
201
+ onInput: (event, el, state) => { state.update({ value: event.target.value }) }
202
+ onSubmit: (event, el, state) => { event.preventDefault() }
203
+ onKeydown: (event, el, state) => { if (event.key === 'Enter') /* ... */ }
204
+ onMouseover: (event, el, state) => { /* ... */ }
205
+ onBlur: (event, el, state) => { /* ... */ }
206
+ onFocus: (event, el, state) => { /* ... */ }
207
+ ```
224
208
 
225
- // DOMQL lifecycle events signature: (el, state)
226
- onRender: (el, state) => { /* after element renders */ },
227
- onInit: (el, state) => { /* before render */ },
228
- onUpdate: (el, state) => { /* after state/props update */ },
229
- onStateUpdate: (el, state) => { /* specifically after state update */ },
230
- onCreate: (el, state) => { /* after full creation */ }
231
- }
209
+ **Lifecycle events** -- signature: `(el, state)`:
210
+
211
+ ```js
212
+ onInit: (el, state) => { /* before render */ }
213
+ onRender: (el, state) => { /* after render */ }
214
+ onCreate: (el, state) => { /* after full creation */ }
215
+ onUpdate: (el, state) => { /* after state/props update */ }
216
+ onStateUpdate: (el, state) => { /* after state update */ }
232
217
  ```
233
218
 
234
- ### DOMQL lifecycle events (never bound to DOM)
219
+ ### Element Lifecycle Events (Full Signatures)
220
+
221
+ | Event | Signature | When | Notes |
222
+ |---|---|---|---|
223
+ | `onInit` | `(element, state, context, updateOptions)` | Before init | Return `false` to break |
224
+ | `onAttachNode` | `(element, state, context, updateOptions)` | After DOM node attached | |
225
+ | `onRender` | `(element, state, context, updateOptions)` | After render | |
226
+ | `onComplete` | `(element, state, context, updateOptions)` | After full creation | |
227
+ | `onBeforeUpdate` | `(changes, element, state, context, updateOptions)` | Before update | `changes` is first param |
228
+ | `onUpdate` | `(element, state, context, updateOptions)` | After update | |
229
+
230
+ ### State Events
231
+
232
+ | Event | Signature | When | Notes |
233
+ |---|---|---|---|
234
+ | `onStateInit` | `(element, state, context, updateOptions)` | Before state init | Return `false` to break |
235
+ | `onStateCreated` | `(element, state, context, updateOptions)` | After state created | |
236
+ | `onBeforeStateUpdate` | `(changes, element, state, context, updateOptions)` | Before state update | Return `false` to prevent; `changes` first |
237
+ | `onStateUpdate` | `(changes, element, state, context, updateOptions)` | After state update | `changes` is first param |
238
+
239
+ `onBeforeStateUpdate` and `onStateUpdate` receive `changes` as their FIRST parameter.
240
+
241
+ ### DOMQL Lifecycle Names (Never Bound to DOM)
235
242
 
236
243
  ```
237
244
  init, beforeClassAssign, render, renderRouter, attachNode,
@@ -239,17 +246,18 @@ stateInit, stateCreated, beforeStateUpdate, stateUpdate,
239
246
  beforeUpdate, done, create, complete, frame, update
240
247
  ```
241
248
 
242
- ### Event detection rule (v3)
249
+ ### Event Detection Rule
250
+
251
+ A key is a v3 event handler when:
243
252
 
244
253
  ```js
245
- // A key is a v3 event handler if:
246
254
  key.length > 2 &&
247
255
  key.startsWith('on') &&
248
- key[2] === key[2].toUpperCase() && // onClick, onRender NOT "one", "only"
256
+ key[2] === key[2].toUpperCase() && // onClick, onRender -- NOT "one", "only"
249
257
  isFunction(value)
250
258
  ```
251
259
 
252
- ### Async events
260
+ ### Async Events
253
261
 
254
262
  ```js
255
263
  onRender: async (el, state) => {
@@ -264,29 +272,20 @@ onRender: async (el, state) => {
264
272
 
265
273
  ---
266
274
 
267
- ## 7. State
268
-
269
- ### Defining state
270
-
271
- ```js
272
- export const Counter = {
273
- state: { count: 0, open: false, selected: null },
274
- }
275
- ```
275
+ ## State
276
276
 
277
- ### Reading state in element definitions
277
+ ### Define, Read, Update
278
278
 
279
279
  ```js
280
- export const Item = {
281
- text: ({ state }) => state.label,
282
- opacity: ({ state }) => state.loading ? 0.5 : 1,
283
- isActive: ({ key, state }) => state.active === key
284
- }
285
- ```
280
+ // Define
281
+ state: { count: 0, open: false, selected: null }
286
282
 
287
- ### Updating state from events
283
+ // Read in definitions
284
+ text: ({ state }) => state.label
285
+ opacity: ({ state }) => state.loading ? 0.5 : 1
286
+ isActive: ({ key, state }) => state.active === key
288
287
 
289
- ```js
288
+ // Update from events
290
289
  onClick: (event, el, state) => {
291
290
  state.update({ on: !state.on }) // partial update
292
291
  state.set({ on: false }) // replace
@@ -295,31 +294,78 @@ onClick: (event, el, state) => {
295
294
  }
296
295
  ```
297
296
 
298
- ### Accessing root state
297
+ ### Root State Access
299
298
 
300
299
  ```js
301
- onClick: (event, el) => {
302
- const rootState = el.getRootState()
303
- const user = el.getRootState('user')
304
- }
300
+ // From events
301
+ const rootState = el.getRootState()
302
+ const user = el.getRootState('user')
305
303
 
306
304
  // In definitions
307
305
  text: (el) => el.getRootState('currentPage')
308
306
  ```
309
307
 
310
- ### Targeted state updates (performance)
308
+ ### Targeted Updates (Performance)
311
309
 
312
310
  ```js
313
- state.root.update({
314
- activeModal: true
315
- }, {
311
+ state.root.update({ activeModal: true }, {
316
312
  onlyUpdate: 'ModalCard' // only ModalCard subtree re-renders
317
313
  })
318
314
  ```
319
315
 
320
316
  ---
321
317
 
322
- ## 8. `attr` — HTML Attributes
318
+ ## State Methods
319
+
320
+ | Method | Description |
321
+ |---|---|
322
+ | `state.update(value, options?)` | Deep overwrite, triggers re-render |
323
+ | `state.set(value, options?)` | Replace state entirely (removes old values) |
324
+ | `state.reset(options?)` | Reset to initial values |
325
+ | `state.add(value, options?)` | Add item to array state |
326
+ | `state.toggle(key, options?)` | Toggle boolean property |
327
+ | `state.remove(key, options?)` | Remove property |
328
+ | `state.apply(fn, options?)` | Apply fn that RETURNS new value |
329
+ | `state.applyFunction(fn, options?)` | Apply fn that MUTATES state directly |
330
+ | `state.replace(value, options?)` | SHALLOW replace (nested keys disappear) |
331
+ | `state.clean(options?)` | Empty the state |
332
+ | `state.parse()` | Get purified plain object |
333
+ | `state.quietUpdate(value)` | Update without triggering re-render |
334
+ | `state.quietReplace(value)` | Replace without triggering re-render |
335
+ | `state.destroy(options?)` | Completely remove state |
336
+ | `state.setByPath('a.b.c', value)` | Update nested by dot-path |
337
+
338
+ `apply()` expects the function to RETURN a new value. `applyFunction()` expects direct MUTATION:
339
+
340
+ ```js
341
+ state.apply(s => ({ ...s, count: s.count + 1 })) // return
342
+ state.applyFunction(s => { s.count++ }) // mutate
343
+ ```
344
+
345
+ ### State Update Options
346
+
347
+ | Option | Description |
348
+ |---|---|
349
+ | `isHoisted` | Mark update as hoisted |
350
+ | `preventHoistElementUpdate` | Prevent hoisted element from updating |
351
+
352
+ ### State Navigation
353
+
354
+ ```js
355
+ state.parent // parent element's state
356
+ state.root // application-level root state
357
+ ```
358
+
359
+ State as string inherits from parent:
360
+
361
+ ```js
362
+ // Parent has state: { userProfile: { name: 'John' } }
363
+ state: 'userProfile' // child inherits parent's userProfile key
364
+ ```
365
+
366
+ ---
367
+
368
+ ## `attr` (HTML Attributes)
323
369
 
324
370
  ```js
325
371
  export const Input = {
@@ -329,7 +375,7 @@ export const Input = {
329
375
  autocomplete: 'off',
330
376
  placeholder: ({ props }) => props.placeholder,
331
377
  name: ({ props }) => props.name,
332
- disabled: ({ props }) => props.disabled || null, // null removes the attr
378
+ disabled: ({ props }) => props.disabled || null, // null removes attr
333
379
  value: (el) => el.call('exec', el.props.value, el),
334
380
  required: ({ props }) => props.required,
335
381
  role: 'button',
@@ -339,33 +385,32 @@ export const Input = {
339
385
  }
340
386
  ```
341
387
 
342
- **Rule**: Returning `null` or `undefined` from an attr function removes the attribute.
388
+ Return `null` or `undefined` from an attr function to remove the attribute.
343
389
 
344
390
  ---
345
391
 
346
- ## 9. `text` and `html`
392
+ ## `text` and `html`
347
393
 
348
394
  ```js
349
- // Plain text content
350
- export const Label = { text: ({ props }) => props.label }
351
- export const Badge = { text: 'New' }
352
- export const Price = { text: ({ state }) => `$${state.amount.toFixed(2)}` }
353
-
354
- // Raw HTML (XSS risk — use sparingly)
355
- export const RichText = { html: ({ props }) => props.html }
395
+ export const Label = { text: ({ props }) => props.label }
396
+ export const Badge = { text: 'New' }
397
+ export const Price = { text: ({ state }) => `$${state.amount.toFixed(2)}` }
398
+ export const RichText = { html: ({ props }) => props.html } // XSS risk
356
399
  ```
357
400
 
358
401
  ---
359
402
 
360
- ## 10. Children
403
+ ## Children
361
404
 
362
- ### Named children (most common)
405
+ ### Named Children
406
+
407
+ PascalCase or numeric keys become child elements:
363
408
 
364
409
  ```js
365
410
  export const Card = {
366
- extends: 'Flex',
411
+ flow: 'y',
367
412
  Header: {
368
- extends: 'Flex',
413
+ flow: 'x',
369
414
  Title: { text: ({ props }) => props.title },
370
415
  },
371
416
  Body: { html: ({ props }) => props.content },
@@ -375,27 +420,23 @@ export const Card = {
375
420
  }
376
421
  ```
377
422
 
378
- **Rule**: Child keys that start with uppercase or are numeric → child elements. All others → CSS props or builtins.
379
-
380
- ### `childExtends` — extend all direct children
423
+ ### `childExtends`
381
424
 
382
- Must be a named component string (see RULES.md Rule 10):
425
+ Extend all direct children. Use a named component string:
383
426
 
384
427
  ```js
385
- export const NavList = {
386
- childExtends: 'NavLink' // ✅ named string only
387
- }
428
+ export const NavList = { childExtends: 'NavLink' }
388
429
  ```
389
430
 
390
- ### `childExtendRecursive` — apply to ALL descendants
431
+ ### `childExtendsRecursive`
432
+
433
+ Apply to ALL descendants:
391
434
 
392
435
  ```js
393
- export const Tree = {
394
- childExtendRecursive: { fontSize: 'A' }
395
- }
436
+ export const Tree = { childExtendsRecursive: { fontSize: 'A' } }
396
437
  ```
397
438
 
398
- ### `children` dynamic child list from data
439
+ ### `children` (Dynamic Child List)
399
440
 
400
441
  ```js
401
442
  export const DropdownList = {
@@ -404,108 +445,100 @@ export const DropdownList = {
404
445
  }
405
446
  ```
406
447
 
407
- ### `childrenAs` — control how children data maps to elements
448
+ ### `childrenAs`
408
449
 
409
- By default, each item in `children` becomes `props` on the child element. Use `childrenAs` to change this:
410
-
411
- ```js
412
- // Default (childrenAs: 'props') — each item becomes element props
413
- { children: [{ text: 'Hello' }] }
414
- // → child gets: { props: { text: 'Hello' } }
450
+ Control how children data maps to elements:
415
451
 
416
- // childrenAs: 'state' each item becomes element state
417
- { children: [{ count: 5 }], childrenAs: 'state' }
418
- // child gets: { state: { count: 5 } }
452
+ | Value | Behavior |
453
+ |---|---|
454
+ | `'props'` (default) | Each item becomes child's `props` |
455
+ | `'state'` | Each item becomes child's `state` |
456
+ | `'element'` | Each item is used directly as element definition |
419
457
 
420
- // childrenAs: 'element' — each item is used directly as element definition
421
- { children: [{ tag: 'span', text: 'Hi' }], childrenAs: 'element' }
422
- // → child IS: { tag: 'span', text: 'Hi' }
458
+ ```js
459
+ { children: [{ text: 'Hello' }] } // -> { props: { text: 'Hello' } }
460
+ { children: [{ count: 5 }], childrenAs: 'state' } // -> { state: { count: 5 } }
461
+ { children: [{ tag: 'span', text: 'Hi' }], childrenAs: 'element' } // -> { tag: 'span', text: 'Hi' }
423
462
  ```
424
463
 
425
- ### `state: 'key'` narrow state scope for children
464
+ ### `state: 'key'` (Narrow State Scope)
426
465
 
427
- When a parent has nested state (e.g. `state.data` is an array), use `state: 'data'` on the container to narrow the scope so children receive individual items:
466
+ Narrow parent state for children:
428
467
 
429
468
  ```js
430
- // Parent narrows state to the 'members' key
431
469
  export const TeamList = {
432
470
  state: 'members',
433
471
  childExtends: 'TeamItem',
434
472
  children: ({ state }) => state
435
473
  }
436
474
 
437
- // Child reads individual item state — requires state: true
438
475
  export const TeamItem = {
439
- state: true,
476
+ state: true, // REQUIRED for children to receive individual state
440
477
  Title: { text: ({ state }) => state.name }
441
478
  }
442
479
  ```
443
480
 
444
- **Important**: `state: true` is required on child components that read `({ state }) => state.field` when used with `childExtends`. Without it, children don't receive individual state from the parent's children array.
481
+ `state: true` is required on child components reading `({ state }) => state.field` when used with `childExtends`.
445
482
 
446
- ### `content` single dynamic child
483
+ ### `content` (Single Dynamic Child)
447
484
 
448
485
  ```js
449
- export const Page = {
450
- content: ({ props }) => props.page
451
- }
486
+ export const Page = { content: ({ props }) => props.page }
452
487
  ```
453
488
 
454
- ---
455
-
456
- ## 11. Props
457
-
458
- ### Passing props (consumer side)
489
+ ### Children as Async
459
490
 
460
491
  ```js
461
- const instance = {
462
- extends: 'Button',
463
- props: {
464
- text: 'Submit',
465
- href: '/dashboard',
466
- disabled: false
467
- }
492
+ {
493
+ children: async (element, state, context) => await window.fetch('...endpoint'),
494
+ childrenAs: 'state',
468
495
  }
469
496
  ```
470
497
 
471
- ### Accessing props inside definitions
498
+ ---
499
+
500
+ ## Props
501
+
502
+ ### Pass and Access
472
503
 
473
504
  ```js
474
- export const Input = {
475
- attr: {
476
- placeholder: ({ props }) => props.placeholder,
477
- value: (el) => el.props.value,
478
- disabled: ({ props }) => props.disabled || null
479
- },
480
- text: ({ props }) => props.label
505
+ // Pass (consumer side)
506
+ { extends: 'Button', props: { text: 'Submit', href: '/dashboard', disabled: false } }
507
+
508
+ // Access (definition side)
509
+ attr: {
510
+ placeholder: ({ props }) => props.placeholder,
511
+ value: (el) => el.props.value,
512
+ disabled: ({ props }) => props.disabled || null
481
513
  }
514
+ text: ({ props }) => props.label
482
515
  ```
483
516
 
484
- ### Boolean / computed props
517
+ ### Boolean/Computed Props
518
+
519
+ `is*`, `has*`, `use*` prefixes are treated as boolean flags:
485
520
 
486
521
  ```js
487
- export const MyComponent = {
488
- isActive: ({ key, state }) => state.active === key, // computed boolean
489
- hasIcon: ({ props }) => Boolean(props.icon),
490
- useCache: true
491
- }
522
+ isActive: ({ key, state }) => state.active === key
523
+ hasIcon: ({ props }) => Boolean(props.icon)
524
+ useCache: true
492
525
  ```
493
526
 
494
- `is*`, `has*`, `use*` prefixed props are treated as boolean flags.
527
+ ### `childProps`
495
528
 
496
- ### `childProps` — inject props into named children
529
+ Inject props into all named children:
497
530
 
498
531
  ```js
499
532
  export const Layout = {
500
533
  childProps: {
501
- onClick: (ev) => { ev.stopPropagation() } // all children get this
534
+ onClick: (ev) => { ev.stopPropagation() }
502
535
  }
503
536
  }
504
537
  ```
505
538
 
506
539
  ---
507
540
 
508
- ## 12. `define` Custom Property Transformers
541
+ ## `define` (Custom Property Transformers)
509
542
 
510
543
  ```js
511
544
  define: {
@@ -516,11 +549,13 @@ define: {
516
549
  }
517
550
  ```
518
551
 
519
- ### Built-in defines
520
-
521
- These are registered globally and work on any element:
552
+ ### Built-In Defines
522
553
 
523
- - `metadata` SEO metadata (see SEO-METADATA.md). Values can be static or functions:
554
+ | Define | Purpose |
555
+ |---|---|
556
+ | `metadata` | SEO metadata (see SEO-METADATA.md) |
557
+ | `routes` | Route definitions for the router |
558
+ | `$router` | Render route content into the element |
524
559
 
525
560
  ```js
526
561
  export const aboutPage = {
@@ -532,17 +567,14 @@ export const aboutPage = {
532
567
  }
533
568
  ```
534
569
 
535
- - `routes` — route definitions for the router
536
- - `$router` — renders route content into the element
537
-
538
570
  ---
539
571
 
540
- ## 13. `if` Conditional Rendering
572
+ ## `if` (Conditional Rendering)
541
573
 
542
574
  ```js
543
575
  export const AuthView = {
544
576
  if: (el, state) => state.isAuthenticated,
545
- Dashboard: { /* only renders if condition is true */ }
577
+ Dashboard: { /* renders only when true */ }
546
578
  }
547
579
 
548
580
  export const ErrorMsg = {
@@ -553,16 +585,16 @@ export const ErrorMsg = {
553
585
 
554
586
  ---
555
587
 
556
- ## 14. `scope`, `data`
588
+ ## `scope` and `data`
557
589
 
558
590
  ```js
559
- // scope: 'state' element.scope becomes element.state
591
+ // scope: 'state' -- element.scope becomes element.state
560
592
  export const Form = {
561
593
  scope: 'state',
562
594
  state: { name: '', email: '' }
563
595
  }
564
596
 
565
- // data non-reactive storage (doesn't trigger re-renders)
597
+ // data -- non-reactive storage (no re-renders)
566
598
  export const Chart = {
567
599
  data: { chartInstance: null },
568
600
  onRender: (el) => {
@@ -573,78 +605,67 @@ export const Chart = {
573
605
 
574
606
  ---
575
607
 
576
- ## 15. Element Methods (on every element)
577
-
578
- ```js
579
- // Navigation
580
- el.lookup('key') // find ancestor by key or predicate
581
- el.lookdown('key') // find first descendant by key
582
- el.lookdownAll('key') // find all descendants
583
- el.nextElement() // sibling after
584
- el.previousElement() // sibling before
585
-
586
- // Updates
587
- el.update({ key: value }) // partial update of element properties
588
- el.set({ key: value }) // full replacement
589
- el.setProps({ key: value }) // update props specifically
590
-
591
- // Content
592
- el.updateContent(newContent)
593
- el.removeContent()
594
-
595
- // State
596
- el.getRootState() // app-level root state
597
- el.getRootState('key') // specific key from root state
598
- el.getRoot() // root element
599
- el.getContext('key') // from element's context
600
-
601
- // DOM
602
- el.setNodeStyles({ key: value }) // apply inline styles
603
- el.remove() // remove element from DOM
604
-
605
- // Calling context functions
606
- el.call('fnKey', ...args) // looks up in context.utils → functions → methods → snippets
607
-
608
- // Debug
609
- el.parse() // serialize element to plain object
610
- el.keys() // list element's own keys
611
- el.verbose() // log element in console
612
- ```
608
+ ## Element Methods
609
+
610
+ | Category | Method | Description |
611
+ |---|---|---|
612
+ | **Navigation** | `el.lookup('key')` | Find ancestor by key or predicate |
613
+ | | `el.lookdown('key')` | Find first descendant by key |
614
+ | | `el.lookdownAll('key')` | Find all descendants by key |
615
+ | | `el.spotByPath(['Header', 'Nav', 'Logo'])` | Find by array path |
616
+ | | `el.nextElement()` | Next sibling |
617
+ | | `el.previousElement()` | Previous sibling |
618
+ | | `el.getRoot()` | Root element |
619
+ | **Updates** | `el.update({ key: value })` | Deep overwrite element properties |
620
+ | | `el.set({ key: value })` | Set content element |
621
+ | | `el.setProps({ key: value })` | Update props specifically |
622
+ | **Content** | `el.updateContent(newContent)` | Update content |
623
+ | | `el.removeContent()` | Remove content |
624
+ | **State** | `el.getRootState()` | App-level root state |
625
+ | | `el.getRootState('key')` | Specific key from root state |
626
+ | | `el.getContext('key')` | Value from element's context |
627
+ | **DOM** | `el.setNodeStyles({ key: value })` | Apply inline styles |
628
+ | | `el.remove()` | Remove from tree and DOM |
629
+ | **Context** | `el.call('fnKey', ...args)` | Lookup: `context.utils -> functions -> methods -> snippets` |
630
+ | **Router** | `el.router(path, root)` | SPA navigation |
631
+ | **Dependencies** | `el.require('moduleName')` | Cross-environment dependency loading |
632
+ | **Debug** | `el.parse(exclude)` | One-level purified plain object |
633
+ | | `el.parseDeep(exclude)` | Deep purified plain object |
634
+ | | `el.keys()` | List element's own keys |
635
+ | | `el.verbose()` | Log element in console |
636
+
637
+ ### Element Update Options
638
+
639
+ Pass as second argument to `el.update(value, options)`:
640
+
641
+ | Option | Description |
642
+ |---|---|
643
+ | `onlyUpdate` | Only update specific subtree by key |
644
+ | `preventUpdate` | Prevent element update |
645
+ | `preventStateUpdate` | Prevent state update |
646
+ | `preventUpdateListener` | Skip update event listeners |
647
+ | `preventUpdateAfter` | Skip post-update hooks |
648
+ | `lazyLoad` | Enable lazy loading for the update |
613
649
 
614
650
  ---
615
651
 
616
- ## 16. State Methods
617
-
618
- ```js
619
- state.update({ key: value }) // partial update, triggers re-render
620
- state.set({ key: value }) // replace, triggers re-render
621
- state.reset() // reset to initial value
622
- state.toggle('open') // toggle boolean property
623
- state.remove() // remove state node
624
- state.quietUpdate({ key: value }) // update without re-render
625
- state.add(item) // add to collection
626
- state.setByPath('a.b.c', value) // update nested by path
627
- ```
628
-
629
- ---
652
+ ## `el.call()` (Context Function Lookup)
630
653
 
631
- ## 17. `el.call()` Context Function Lookup
654
+ Lookup order: `context.utils -> context.functions -> context.methods -> context.snippets`
632
655
 
633
656
  ```js
634
657
  el.call('router', href, root, {}, options)
635
- el.call('exec', value, el) // execute a potentially-function prop
658
+ el.call('exec', value, el)
636
659
  el.call('isString', value)
637
660
  el.call('fetchData', id)
638
661
  el.call('replaceLiteralsWithObjectFields', template)
639
662
  ```
640
663
 
641
- Lookup order: `context.utils → context.functions → context.methods → context.snippets`
642
-
643
664
  ---
644
665
 
645
- ## 18. Router (Navigation)
666
+ ## Router
646
667
 
647
- ### Declaring pages
668
+ ### Declare Pages
648
669
 
649
670
  ```js
650
671
  // pages/index.js
@@ -654,7 +675,7 @@ export default {
654
675
  }
655
676
  ```
656
677
 
657
- ### Navigation via Link
678
+ ### Link Navigation
658
679
 
659
680
  ```js
660
681
  export const NavItem = {
@@ -664,11 +685,13 @@ export const NavItem = {
664
685
  }
665
686
  ```
666
687
 
667
- ### Programmatic navigation
688
+ ### Programmatic Navigation
689
+
690
+ Call `event.preventDefault()` BEFORE the router call:
668
691
 
669
692
  ```js
670
693
  onClick: (event, el) => {
671
- event.preventDefault() // MUST come BEFORE router call
694
+ event.preventDefault()
672
695
  el.call('router', '/dashboard', el.__ref.root, {}, {
673
696
  scrollToTop: true,
674
697
  scrollToOptions: { behavior: 'instant' }
@@ -676,9 +699,9 @@ onClick: (event, el) => {
676
699
  }
677
700
  ```
678
701
 
679
- ### Custom router element (persistent layouts)
702
+ ### Custom Router Element (Persistent Layouts)
680
703
 
681
- Configure in `config.js` to render pages inside a specific element in the tree:
704
+ Configure in `config.js` to render pages inside a specific element:
682
705
 
683
706
  ```js
684
707
  // config.js
@@ -689,18 +712,32 @@ export default {
689
712
  }
690
713
  ```
691
714
 
692
- The `/` page defines the persistent layout shell. Sub-pages render inside the target element without destroying the layout. The path is resolved by traversing `root.Folder.Content`.
715
+ The `/` page defines the persistent layout shell. Sub-pages render inside the target element without destroying the layout.
716
+
717
+ ---
718
+
719
+ ## `element.require()` (Cross-Environment Dependency Loading)
720
+
721
+ ```js
722
+ {
723
+ tag: 'canvas',
724
+ onRender: async (element, state) => {
725
+ const Chart = element.require('chartjs')
726
+ const ctx = element.node.getContext('2d')
727
+ new Chart(ctx, { type: 'bar', data: { /* ... */ } })
728
+ }
729
+ }
730
+ ```
693
731
 
694
732
  ---
695
733
 
696
- ## 19. Common Patterns
734
+ ## Common Patterns
697
735
 
698
- ### Loading state
736
+ ### Loading State
699
737
 
700
738
  ```js
701
739
  export const DataList = {
702
740
  state: { items: [], loading: true, error: null },
703
-
704
741
  Loader: { if: ({ state }) => state.loading, extends: 'Spinner' },
705
742
  Error: { if: ({ state }) => Boolean(state.error), text: ({ state }) => state.error },
706
743
  Items: {
@@ -708,7 +745,6 @@ export const DataList = {
708
745
  children: ({ state }) => state.items,
709
746
  childExtends: 'ListItem'
710
747
  },
711
-
712
748
  onRender: async (el, state) => {
713
749
  try {
714
750
  const items = await el.call('fetchItems')
@@ -720,7 +756,7 @@ export const DataList = {
720
756
  }
721
757
  ```
722
758
 
723
- ### Active list item
759
+ ### Active List Item
724
760
 
725
761
  ```js
726
762
  export const Menu = {
@@ -734,11 +770,11 @@ export const Menu = {
734
770
  }
735
771
  ```
736
772
 
737
- ### Modal (v3 complete pattern)
773
+ ### Modal
738
774
 
739
775
  ```js
740
776
  export const ModalCard = {
741
- position: 'absolute', flexAlign: 'center center',
777
+ position: 'absolute', align: 'center center',
742
778
  top: 0, left: 0, boxSize: '100% 100%',
743
779
  transition: 'all C defaultBezier',
744
780
  opacity: '0', visibility: 'hidden', pointerEvents: 'none', zIndex: '-1',
@@ -753,12 +789,34 @@ export const ModalCard = {
753
789
 
754
790
  ---
755
791
 
756
- ## 20. Finding DOMQL Elements in the Browser DOM
792
+ ## Naming Conventions
793
+
794
+ | Category | Convention | Examples |
795
+ |---|---|---|
796
+ | Components | PascalCase | `CustomComponent`, `NavBar`, `UserProfile` |
797
+ | Properties | camelCase | `paddingInlineStart`, `fontSize`, `backgroundColor` |
798
+ | Repeating keys | Snake_Case suffixes | `Li_1`, `Li_2`, `Li_One` |
799
+
800
+ ---
801
+
802
+ ## Reserved Keywords
803
+
804
+ These keys are handled by the DOMQL engine and are NOT CSS props or child components:
805
+
806
+ ```
807
+ key, extends, childExtends, childExtendsRecursive, childProps, props,
808
+ state, tag, query, data, scope, children, childrenAs, context
809
+ ```
810
+
811
+ All other keys: lowercase/camelCase = CSS props, PascalCase = child components.
812
+
813
+ ---
814
+
815
+ ## Finding DOMQL Elements in Browser DOM
757
816
 
758
- DOMQL elements use generated Emotion class names (`smbls-xxx`). Access via `node.ref`:
817
+ Every DOMQL-managed DOM node has `.ref` pointing to its DOMQL element:
759
818
 
760
819
  ```js
761
- // Every DOMQL-managed DOM node has .ref pointing to its DOMQL element
762
820
  const domqlElement = someNode.ref
763
821
  domqlElement.key // element key name
764
822
  domqlElement.props // current props