@base-framework/base 3.5.8 → 3.5.9

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.
Files changed (2) hide show
  1. package/copilot.md +355 -28
  2. package/package.json +1 -1
package/copilot.md CHANGED
@@ -3,57 +3,114 @@
3
3
  Purpose: concise, project-specific guidance so AI agents are instantly productive in this codebase.
4
4
 
5
5
  ## Architecture (what to know first)
6
- - Render UI from plain JS objects ("layouts")—no templates. Parser turns objects into DOM (browser) or HTML strings (server).
7
- - Public API re-exported by `src/base.js` (e.g., `Builder`, `Component`, `Atom`, `Data`, `SimpleData`, `Model`, `Import`, `NavLink`, `router`, `Html`, `Directives`).
8
- - Runtime renderer switch: `modules/layout/render/*` via `RenderController.setup()` → `BrowserRender` in browser, `ServerRender` otherwise.
9
- - Component stack: `Unit` → `Component`. `Unit` handles lifecycle/context; `Component` adds state (`StateTracker`) and events.
10
- - Reactive data: `modules/data` provides `Data` (deep), `SimpleData` (shallow), `Model` (server-backed). `WatcherHelper` powers `[[prop.path]]` in strings/arrays.
6
+ - **Render UI from plain JS objects** ("layouts")—no templates, no JSX. Parser turns objects into DOM (browser) or HTML strings (server).
7
+ - **Public API** re-exported by `src/base.js`: `Builder`, `Component`, `Unit`, `Jot`, `Pod`, `Atom`, `Data`, `SimpleData`, `Model`, `StateTracker`, `Import`, `NavLink`, `router`, `Html`, `Directives`, `Ajax`.
8
+ - **Runtime renderer switch**: `modules/layout/render/*` via `RenderController.setup()` → `BrowserRender` in browser, `ServerRender` for SSR.
9
+ - **Component hierarchy**: `Unit` (lifecycle/context) `Component` (adds state via `StateTracker` and events).
10
+ - **Shorthand APIs**: `Jot` (functional components), `Pod` (stateful functional), `Atom` (reusable layouts).
11
+ - **Reactive data**: `Data` (deep proxy), `SimpleData` (shallow proxy), `Model` (server-backed). `WatcherHelper` powers `[[prop.path]]` bindings.
12
+ - **State management**: `StateTracker` for global state, component-level states via `setupStates()`.
13
+ - **Routing**: Client-side with History API, reactive `router.data.path`, automatic route parameters.
14
+ - **HTTP requests**: `Ajax` module with shorthand methods (`Ajax.get()`, `Ajax.post()`).
15
+ - **Dynamic imports**: `Import` wrapper for lazy loading with dependencies.
11
16
 
12
17
  ## CRITICAL: Common Mistakes to Avoid
13
18
  1. **DON'T use templates or JSX** - Base uses plain JavaScript objects for layouts
14
19
  2. **DON'T call `render()` directly** - Components call it internally; you return layout objects from `render()`
15
20
  3. **DON'T mutate props** - Props are read-only; use `this.data` or `this.state` for mutable values
16
21
  4. **DON'T use `this.setState()`** - Use `this.state.set('key', value)` or `this.state.increment('key')`
17
- 5. **DON'T forget `new` with Components** - Always: `new MyComponent()`, never just `MyComponent()`
18
- 6. **DON'T use `new` with Atoms** - Always: `Button()`, never `new Button()`
19
- 7. **DON'T mix data initialization locations** - Use `setData()` for initial setup, not `beforeSetup` or constructor
22
+ 5. **DON'T forget `new` with Components** - Always: `new MyComponent()`, `new Component()`, never just `MyComponent()`
23
+ 6. **DON'T use `new` with Atoms** - Always: `Button()`, never `new Button()`. Atoms are functions, not classes
24
+ 7. **DON'T use `new` with Jot/Pod** - Call the returned class: `const MyJot = Jot({...}); new MyJot()` not `new Jot({})`
25
+ 8. **DON'T mix data initialization locations** - Use `setData()` for initial setup, not `beforeSetup` or constructor
26
+ 9. **DON'T bind without data** - `bind` directive requires `this.data` to be initialized via `setData()`
27
+ 10. **DON'T use wrong state methods** - State keys must be defined in `setupStates()` before using `increment`, `decrement`, `toggle`
28
+ 11. **DON'T forget Import function form** - Use `Import(() => import('./file.js'))` not `Import('./file.js')` for bundler support
29
+ 12. **DON'T use element.remove()** - Use `Html.removeElement(element)` or `Builder.removeNode(element)` for proper cleanup
30
+ 13. **DON'T access DOM before afterSetup** - `this.panel` and `this.elem` are only available after `afterSetup()` lifecycle hook
31
+ 14. **DON'T return arrays from render()** - Wrap multiple elements: `return { children: [elem1, elem2] }` not `return [elem1, elem2]`
32
+ 15. **DON'T use `await` in render()** - Load data in lifecycle hooks, render() must be synchronous
20
33
 
21
34
  ## Authoring layouts (house rules)
22
- - Shape: `{ tag: 'div', class: 'name', children: [...] }`. Shorthands: `nest` → `children`; `text` creates text node; `html|innerHTML` sets raw HTML; buttons default `type: 'button'`.
35
+ - **Shape**: `{ tag: 'div', class: 'name', children: [...] }`. Shorthands: `nest` → `children`; `text` creates text node; `html|innerHTML` sets raw HTML.
23
36
  - **Default tag is 'div'** - Omit `tag` for divs: `{ class: 'container' }` renders as `<div class="container"></div>`
24
- - Events on elements receive `(event, parentComponent)` and are bound on the element: `{ click(e, parent) { parent.doThing(); } }`.
25
- - **Event names are lowercase** - Use `click`, `mouseover`, `change`, not `onClick` or `CLICK`
26
- - Watchers become `watch` directives automatically:
27
- - **PREFERRED**: Current component data: `{ class: 'counter-[[count]]' }` (simplest, use this by default)
37
+ - **Button default** - Buttons default to `type: 'button'` (not 'submit')
38
+ - **Events on elements** receive `(event, parentComponent)` and are bound on the element: `{ click(e, parent) { parent.doThing(); } }`
39
+ - **Event names are lowercase** - Use `click`, `mouseover`, `change`, `input`, `submit`, not `onClick` or `CLICK`
40
+ - **Watchers** become `watch` directives automatically:
41
+ - **PREFERRED**: Current component data: `{ class: 'counter-[[count]]' }` (simplest, watches `this.data` or `this.state`)
28
42
  - Specific data source: `{ value: ['[[path]]', data] }` (when you need different data than `this.data`)
29
43
  - Multi-source with callback: `{ class: ['[[a]] [[b]]', [dataA, dataB], ([a,b]) => `${a}-${b}`] }` (advanced use)
30
- - Directives are just attrs mapped in `modules/layout/directives/core/default-directives.js` (e.g., `bind`, `watch`, `map`, `for`, `route`, `switch`, `useState`, `useData`, `onCreated`, `onDestroyed`).
31
44
  - **Null/undefined props are ignored** - Use `{ class: condition ? 'active' : null }` to conditionally add attributes
45
+ - **Arrays in children** - Flatten automatically: `{ children: [elem1, [elem2, elem3], elem4] }` works
46
+ - **Function children** - Return layout objects: `{ children: [() => ({ tag: 'span', text: 'Dynamic' })] }`
47
+ - **Conditional rendering** - Use logical operators: `{ children: [condition && element, other || fallback] }`
32
48
 
33
49
  ## Common directives (quick cookbook)
34
50
  - **bind** (two-way by default, binds to `this.data` in component):
35
51
  - Text input: `{ tag: 'input', type: 'text', bind: 'form.name' }` (binds to `this.data.form.name`)
36
52
  - Checkbox: `{ tag: 'input', type: 'checkbox', bind: 'form.accepted' }` (binds to boolean)
53
+ - Radio: `{ tag: 'input', type: 'radio', name: 'color', value: 'red', bind: 'form.color' }`
37
54
  - Select + options: `{ tag: 'select', bind: 'form.color', children: [{ map: ['[[colors]]', data, (c) => ({ tag:'option', value:c, text:c })] }] }`
38
55
  - **IMPORTANT**: `bind` requires the component to have `this.data` set via `setData()`
39
56
  - Custom attribute: `{ tag: 'a', bind: 'href:link.url' }` (binds to href instead of value)
40
57
  - With filter: `{ bind: ['count', (v) => Math.round(v)] }` (transform displayed value)
58
+ - One-way: `{ oneway: 'propPath' }` (element → data only)
41
59
 
42
60
  - **map** (render lists from arrays, signature: `[watcherString, dataSource, callback]`):
43
61
  - Basic: `{ tag: 'ul', children: [{ map: ['[[items]]', data, (item, i) => ({ tag:'li', text:item.name })] }] }`
44
62
  - Callback receives: `(item, index)` - use both for keyed lists
45
63
  - **IMPORTANT**: The callback must return a layout object, not a string
64
+ - With keys: Use `key: item.id` in returned layout for better performance
65
+
66
+ - **for** (repeat element N times):
67
+ - Basic: `{ for: [5, (i) => ({ tag: 'div', text: `Item ${i}` })] }`
68
+ - With data: `{ for: [['[[count]]', data], (i) => ({ tag: 'span', text: i })] }`
69
+
70
+ - **if** (conditional rendering):
71
+ - Basic: `{ if: [() => condition, { tag: 'div', text: 'Shown' }] }`
72
+ - With data: `{ if: [['[[isVisible]]', data], { tag: 'div', text: 'Visible' }] }`
73
+ - **NOTE**: Use regular JavaScript `condition && layout` for simpler cases
46
74
 
47
75
  - **Watchers** (one-way binding, auto-updates when data changes):
48
- - Simple: `{ class: 'status-[[status]]' }` (watches `this.data.status`)
76
+ - Simple: `{ class: 'status-[[status]]' }` (watches `this.data.status` or `this.state.status`)
49
77
  - Multiple: `{ text: 'User: [[name]] Age: [[age]]' }` (watches multiple props)
50
78
  - Deep paths: `{ text: '[[user.profile.name]]' }` (nested object access)
79
+ - In arrays: `{ class: ['theme-[[theme]]', 'page'] }` (combines static and dynamic)
51
80
 
52
81
  - **Lifecycle hooks** (element-level, different from component lifecycle):
53
82
  - `{ onCreated: (el, parent) => {/* el is DOM node, parent is component */} }`
54
83
  - `{ onDestroyed: (el, parent) => {/* cleanup before removal */} }`
55
84
  - **IMPORTANT**: These fire for each element, not once per component
56
85
 
86
+ - **State/Data hooks** (access parent state/data):
87
+ - `{ useData: data }` - use specific data source for watchers in this subtree
88
+ - `{ useState: state }` - use specific state source for watchers in this subtree
89
+ - `{ useContext: context }` - use specific context for this subtree
90
+ - `{ useParent: component }` - explicitly set parent component reference
91
+ - **NOTE**: These propagate to child elements
92
+
93
+ - **onSet** (reactive callback for data changes):
94
+ - Basic: `{ onSet: ['propPath', (value, oldValue) => ({ tag: 'div', text: value })] }`
95
+ - Multiple props: `{ onSet: [['prop1', 'prop2'], ([val1, val2]) => layout] }`
96
+ - With data source: `{ onSet: ['propPath', data, (value) => layout] }`
97
+
98
+ - **Component integration**:
99
+ - `{ addState: { count: 0 } }` - add state properties to component
100
+ - `{ addEvent: { myEvent: (data) => {} } }` - add event to component
101
+ - `{ addContext: { theme: 'dark' } }` - add context to component
102
+
103
+ - **Animation**:
104
+ - Enter: `{ animateEnter: 'fadeIn' }` or `{ animateEnter: { name: 'slide', duration: 300 } }`
105
+ - Exit: `{ animateExit: 'fadeOut' }`
106
+ - Move: `{ animateMove: 'slide' }`
107
+
108
+ - **Accessibility**:
109
+ - `{ a11yHide: true }` - hide from screen readers
110
+ - `{ a11yLabel: 'descriptive text' }` - set aria-label
111
+ - `{ a11yRole: 'button' }` - set role
112
+ - `{ a11yDescribe: 'longer description' }` - set aria-describedby
113
+
57
114
  - **Cache/persist**:
58
115
  - `cache: 'propName'` - stores DOM element in `this.propName` (use for later access)
59
116
  - `persist: true` on parent preserves child component instances across re-renders
@@ -63,6 +120,10 @@ Purpose: concise, project-specific guidance so AI agents are instantly productiv
63
120
  - Extend `Component` and implement `render()` - return layout object (never call `render()` manually)
64
121
  - Root element auto-cached as `this.panel` - access after `afterSetup()` lifecycle hook
65
122
  - Use `this.getId('child')` for stable DOM IDs across re-renders
123
+ - Helper methods available on Component instances:
124
+ - `this.if(condition, layout)` - conditional rendering helper
125
+ - `this.map(array, callback)` - array mapping helper
126
+ - `this.declareProps(schema)` - prop validation and defaults
66
127
 
67
128
  ### State Management
68
129
  - Override `setupStates()` to define reactive state properties:
@@ -70,44 +131,310 @@ Purpose: concise, project-specific guidance so AI agents are instantly productiv
70
131
  setupStates() {
71
132
  return {
72
133
  count: 0, // Initial value
73
- active: { state: false, callBack: (val) => {/* fires on change */} }
134
+ active: { state: false, callBack: (val) => {/* fires on change */} },
135
+ items: [] // Arrays work too
74
136
  };
75
137
  }
76
138
  ```
77
139
  - Access via `this.state.count` or `this.state.get('count')`
78
- - Update methods: `this.state.set('count', 5)`, `this.state.increment('count')`, `this.state.toggle('active')`
140
+ - Update methods:
141
+ - `this.state.set('count', 5)` or `this.state.set({ count: 5, active: true })`
142
+ - `this.state.increment('count', amount?)` - add to number (default +1)
143
+ - `this.state.decrement('count', amount?)` - subtract from number (default -1)
144
+ - `this.state.toggle('active')` - flip boolean
145
+ - `this.state.push('items', item)` - add to array
146
+ - `this.state.splice('items', index, count)` - remove from array
79
147
  - **NEVER** use `this.setState()` - that method doesn't exist
148
+ - **IMPORTANT**: State keys must be defined in `setupStates()` before using helper methods
149
+
150
+ ### Global State (StateTracker)
151
+ - Create global state with `StateTracker.create(id, initialState)`:
152
+ ```javascript
153
+ import { StateTracker } from '@base-framework/base';
154
+ const appState = StateTracker.create('app', { user: null, theme: 'light' });
155
+ ```
156
+ - Access in components:
157
+ ```javascript
158
+ setupStateTarget() {
159
+ this.state = StateTracker.get('app');
160
+ }
161
+ ```
162
+ - All state methods work on global state: `this.state.set('theme', 'dark')`
163
+ - Use `useState` directive to connect elements to global state
80
164
 
81
165
  ### Data Management
82
166
  - Override `setData()` to attach reactive data (runs during component initialization):
83
167
  ```javascript
84
168
  setData() {
85
169
  this.data = new Data({ name: '', items: [] });
170
+ // Optional: local storage persistence
171
+ this.data.setKey('MY_STORAGE_KEY');
172
+ this.data.resume({ name: '', items: [] }); // Load or use defaults
86
173
  }
87
174
  ```
88
175
  - **CRITICAL**: Initialize data in `setData()`, NOT in `beforeSetup()` or constructor
89
176
  - Use `Data` for deep nested objects, `SimpleData` for flat objects, `Model` for server-backed data
90
- - Access/modify: `this.data.name = 'test'` or `this.data.set('name', 'test')`
91
- - Components with `route`/`switch` directives automatically receive `this.route` (a bindable Data object)
177
+ - Access/modify: `this.data.name = 'test'` or `this.data.set('name', 'test')` or `this.data.set({ name: 'test', items: [] })`
178
+ - Components with `route`/`switch` directives automatically receive `this.route` (a bindable Data object with route params)
179
+ - Array methods: `push`, `pop`, `shift`, `unshift`, `splice` (all trigger reactivity)
180
+ - Other methods:
181
+ - `this.data.get('nested.path')` - get nested value
182
+ - `this.data.refresh('key')` - trigger watchers without changing value
183
+ - `this.data.delete('key')` - remove property
184
+ - `this.data.ifNull('key', defaultValue)` - return default if null/undefined
185
+ - `this.data.getIndex('array', predicate)` - find array index
186
+ - `this.data.concat('array', items)` - append to array
187
+ - `this.data.on('change', callback)` - subscribe to changes
188
+ - `this.data.off('change', callback)` - unsubscribe
189
+ - `this.data.store()` - save to local storage (if key set)
190
+ - `this.data.resume(defaults)` - load from local storage
191
+ - `this.data.revert()` - undo changes (Data only, not SimpleData)
192
+ - `this.data.link(otherData, 'prop')` - two-way sync with another data source
92
193
 
93
194
  ### Lifecycle Execution Order
94
195
  1. `onCreated()` - component instance created, props available, NO DOM yet
95
196
  2. `beforeSetup()` - before render, good for computed props
96
- 3. `render()` - return layout object (called automatically)
97
- 4. `afterSetup()` - DOM created but not in document, `this.panel` available
98
- 5. `afterLayout()` - DOM in document, safe for measurements/animations
99
- 6. `beforeDestroy()` - cleanup before removal
197
+ 3. `setData()` - initialize reactive data (runs automatically)
198
+ 4. `setupStates()` - define state properties (runs automatically)
199
+ 5. `setupStateTarget()` - connect to global state (runs automatically if defined)
200
+ 6. `render()` - return layout object (called automatically, NEVER call manually)
201
+ 7. `afterSetup()` - DOM created but not in document, `this.panel` available
202
+ 8. `afterRender()` - alias for afterSetup
203
+ 9. `afterLayout()` - DOM in document, safe for measurements/animations
204
+ 10. `beforeDestroy()` - cleanup before removal
205
+ 11. `onDestroyed()` - final cleanup after removal
206
+
207
+ ### Atoms (Reusable Layouts)
208
+ - Create with `Atom((props, children) => layoutObject)`:
209
+ ```javascript
210
+ const Button = Atom((props, children) => ({
211
+ tag: 'button',
212
+ type: 'button',
213
+ ...props,
214
+ children
215
+ }));
216
+ ```
217
+ - Call without `new`: `Button({ class: 'primary', click: handler }, 'Click Me')`
218
+ - Support flexible argument order: `Button('text')`, `Button({ class: 'btn' })`, `Button({ class: 'btn' }, 'text')`
219
+ - **DON'T** use `new` with Atoms - they are functions, not classes
220
+ - Atoms can merge props, children, and have watchers: `Button({ class: 'btn-[[size]]' }, 'Text')`
221
+
222
+ ### Jot (Functional Components)
223
+ - Create lightweight components: `const MyJot = Jot({ render() { return { tag: 'div' }; } })`
224
+ - Returns a Component class: `new MyJot()` to instantiate
225
+ - Supports all component features: `setData()`, `setupStates()`, lifecycle hooks
226
+ - Auto-wrapped for non-Component objects in `Builder.render()`
227
+ - **DON'T** use `new Jot({})` - call the returned class: `const MyJot = Jot({}); new MyJot()`
228
+
229
+ ### Pod (Stateful Functional Components)
230
+ - Like Jot but with built-in state setup:
231
+ ```javascript
232
+ const Counter = Pod({
233
+ states: { count: 0 },
234
+ render() {
235
+ return { tag: 'div', text: 'Count: [[count]]' };
236
+ }
237
+ });
238
+ ```
239
+ - Use `states` property to define state (equivalent to `setupStates()`)
240
+ - Instantiate: `new Counter()`
241
+
242
+ ### Persistence & Component Reuse
243
+ - Parent `persist: true` keeps child component instances alive during re-renders
244
+ - Child can opt-out: `persist: false`
245
+ - **WARNING**: Data initialized in `beforeSetup` can cause issues with persistence
246
+
247
+ ## HTTP Requests (Ajax Module)
248
+ - **Shorthand methods** (recommended for simple requests):
249
+ ```javascript
250
+ import { Ajax } from '@base-framework/base';
251
+
252
+ // GET request
253
+ Ajax.get('/api/users').then(data => console.log(data));
254
+
255
+ // POST request
256
+ Ajax.post('/api/users', { name: 'John' }).then(data => console.log(data));
257
+
258
+ // PUT request
259
+ Ajax.put('/api/users/123', { name: 'Jane' });
260
+
261
+ // DELETE request
262
+ Ajax.delete('/api/users/123');
263
+
264
+ // HEAD request
265
+ Ajax.head('/api/status');
266
+ ```
267
+
268
+ - **Object syntax** (for advanced options):
269
+ ```javascript
270
+ Ajax({
271
+ url: '/api/users',
272
+ method: 'POST',
273
+ data: { name: 'John' },
274
+ responseType: 'json', // 'json', 'text', 'blob', 'arraybuffer'
275
+ headers: { 'X-Custom': 'value' },
276
+ success: (data) => console.log('Success:', data),
277
+ error: (xhr) => console.error('Error:', xhr.status),
278
+ progress: (e) => console.log('Progress:', e.loaded / e.total)
279
+ });
280
+ ```
281
+
282
+ - **Response types**:
283
+ - `'json'` (default) - auto-parse JSON response
284
+ - `'text'` - get response as string
285
+ - `'blob'` - for binary data (files, images)
286
+ - `'arraybuffer'` - for raw binary data
287
+
288
+ - **Global configuration**:
289
+ ```javascript
290
+ // Add fixed params to all requests
291
+ Ajax.addFixedParams({ apiKey: 'abc123' });
292
+
293
+ // Pre-request hook
294
+ Ajax.beforeSend((xhr) => {
295
+ xhr.setRequestHeader('Authorization', 'Bearer token');
296
+ });
297
+
298
+ // Default settings
299
+ Ajax.ajaxSettings({
300
+ baseURL: '/api',
301
+ timeout: 5000,
302
+ withCredentials: true
303
+ });
304
+ ```
305
+
306
+ - **In components**:
307
+ ```javascript
308
+ class UserList extends Component {
309
+ onCreated() {
310
+ Ajax.get('/api/users').then(users => {
311
+ this.data.set('users', users);
312
+ });
313
+ }
314
+ }
315
+ ```
316
+
317
+ ## Dynamic Module Loading (Import)
318
+ - **Basic usage** (lazy load components/modules):
319
+ ```javascript
320
+ import { Import } from '@base-framework/base';
321
+
322
+ // In layouts (function form - works with bundlers)
323
+ { children: [Import(() => import('./components/heavy.js'))] }
324
+
325
+ // String path (for non-bundler scenarios)
326
+ { children: [Import('./components/simple.js')] }
327
+ ```
328
+
329
+ - **With dependencies** (load CSS/JS before module):
330
+ ```javascript
331
+ Import({
332
+ src: () => import('./components/Chart.js'),
333
+ depends: [
334
+ './styles/chart.css',
335
+ './vendor/chart-lib.js'
336
+ ],
337
+ callback: (module) => console.log('Chart loaded:', module)
338
+ });
339
+ ```
340
+
341
+ - **Route-based lazy loading**:
342
+ ```javascript
343
+ {
344
+ switch: [
345
+ { uri: '/dashboard', component: Import(() => import('./pages/Dashboard.js')) },
346
+ { uri: '/profile', component: Import(() => import('./pages/Profile.js')) },
347
+ { uri: '/settings', component: Import(() => import('./pages/Settings.js')) }
348
+ ]
349
+ }
350
+ ```
351
+
352
+ - **Persistent modules** (keep loaded after parent destroyed):
353
+ ```javascript
354
+ Import({
355
+ src: () => import('./services/Analytics.js'),
356
+ persist: true // Stays loaded globally
357
+ });
358
+ ```
359
+
360
+ - **CRITICAL**: Always use function form `() => import()` for bundler support (Vite, Webpack)
361
+ - **DON'T** use string paths with bundlers: `Import('./file.js')` won't code-split
362
+ - **DO** use function form: `Import(() => import('./file.js'))` enables code-splitting
100
363
 
101
- ### Persistence
364
+ ### Persistence & Component Reuse
102
365
  - Parent `persist: true` keeps child component instances alive during re-renders
103
366
  - Child can opt-out: `persist: false`
104
367
  - **WARNING**: Data initialized in `beforeSetup` can cause issues with persistence
105
368
 
106
- ### Jot (Lightweight Components)
107
- - Wrap objects/functions as components: `Jot({ render() { return {...}; } })`
108
- - Non-Unit inputs to `Builder.render()` auto-wrapped with Jot
369
+ ### Component Helper Methods (Available in all Components)
370
+ ```javascript
371
+ class MyComponent extends Component {
372
+ someMethod() {
373
+ // Conditional rendering helper
374
+ this.if(this.state.show, { tag: 'div', text: 'Visible' });
375
+
376
+ // Array mapping helper
377
+ const items = this.map([1, 2, 3], (num) => ({ tag: 'span', text: num }));
378
+
379
+ // Get stable DOM ID
380
+ const id = this.getId('element-name'); // 'component-123-element-name'
109
381
 
110
- ### Useful state/data ops
382
+ // Prop validation (optional)
383
+ this.declareProps({
384
+ title: { type: 'string', required: true },
385
+ count: { type: 'number', default: 0 }
386
+ });
387
+ }
388
+ }
389
+ ```
390
+
391
+ ### Lifecycle Execution Order
392
+ 1. `onCreated()` - component instance created, props available, NO DOM yet
393
+ 2. `beforeSetup()` - before render, good for computed props
394
+ 3. `setData()` - initialize reactive data (runs automatically)
395
+ 4. `setupStates()` - define state properties (runs automatically)
396
+ 5. `setupStateTarget()` - connect to global state (runs automatically if defined)
397
+ 6. `render()` - return layout object (called automatically, NEVER call manually)
398
+ 7. `afterSetup()` - DOM created but not in document, `this.panel` available
399
+ 8. `afterRender()` - alias for afterSetup
400
+ 9. `afterLayout()` - DOM in document, safe for measurements/animations
401
+ 10. `beforeDestroy()` - cleanup before removal
402
+ 11. `onDestroyed()` - final cleanup after removal
403
+
404
+ ### Atoms (Reusable Layouts)
405
+ - Create with `Atom((props, children) => layoutObject)`:
406
+ ```javascript
407
+ const Button = Atom((props, children) => ({
408
+ tag: 'button',
409
+ type: 'button',
410
+ ...props,
411
+ children
412
+ }));
413
+ ```
414
+ - Call without `new`: `Button({ class: 'primary', click: handler }, 'Click Me')`
415
+ - Support flexible argument order: `Button('text')`, `Button({ class: 'btn' })`, `Button({ class: 'btn' }, 'text')`
416
+ - **DON'T** use `new` with Atoms - they are functions, not classes
417
+ - Atoms can merge props, children, and have watchers: `Button({ class: 'btn-[[size]]' }, 'Text')`
418
+
419
+ ### Jot (Functional Components)
420
+ - Create lightweight components: `const MyJot = Jot({ render() { return { tag: 'div' }; } })`
421
+ - Returns a Component class: `new MyJot()` to instantiate
422
+ - Supports all component features: `setData()`, `setupStates()`, lifecycle hooks
423
+ - Auto-wrapped for non-Component objects in `Builder.render()`
424
+ - **DON'T** use `new Jot({})` - call the returned class: `const MyJot = Jot({}); new MyJot()`
425
+
426
+ ### Pod (Stateful Functional Components)
427
+ - Like Jot but with built-in state setup:
428
+ ```javascript
429
+ const Counter = Pod({
430
+ states: { count: 0 },
431
+ render() {
432
+ return { tag: 'div', text: 'Count: [[count]]' };
433
+ }
434
+ });
435
+ ```
436
+ - Use `states` property to define state (equivalent to `setupStates()`)
437
+ - Instantiate: `new Counter()`
111
438
  - **State helpers** (only work on keys defined in `setupStates()`):
112
439
  - `this.state.set('key', value)` - set any value
113
440
  - `this.state.toggle('key')` - flip boolean
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base-framework/base",
3
- "version": "3.5.8",
3
+ "version": "3.5.9",
4
4
  "description": "This is a javascript framework.",
5
5
  "main": "./dist/base.js",
6
6
  "type": "module",