@vanilla-bean/components 1.1.0 → 2.0.0
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/Component/Component.js +226 -40
- package/Component/Component.scenarios.js +2 -2
- package/Component/Component.test.js +427 -40
- package/Component/README.md +480 -0
- package/Component/observeElementConnection.js +1 -1
- package/Elem/README.md +373 -0
- package/README.md +2 -2
- package/components/BottomSheet/BottomSheet.js +8 -16
- package/components/BottomSheet/README.md +1 -1
- package/components/Button/Button.js +12 -18
- package/components/Button/Button.lld.md +1 -1
- package/components/Calendar/Calendar.js +139 -59
- package/components/Calendar/CalendarEvent.js +9 -4
- package/components/Calendar/Toolbar.js +28 -20
- package/components/Calendar/index.js +1 -0
- package/components/Code/Code.js +26 -30
- package/components/Code/Code.lld.md +1 -1
- package/components/ColorPicker/ColorPicker.js +58 -58
- package/components/Dialog/Dialog.js +79 -73
- package/components/Dialog/Dialog.lld.md +1 -1
- package/components/Dialog/README.md +10 -10
- package/components/Form/Form.js +21 -17
- package/components/Form/Form.lld.md +2 -2
- package/components/Form/README.md +3 -3
- package/components/Icon/Icon.js +19 -11
- package/components/Input/Input.js +85 -67
- package/components/Input/README.md +7 -9
- package/components/Keyboard/Key.js +7 -10
- package/components/Keyboard/Keyboard.js +38 -46
- package/components/Keyboard/Keyboard.lld.md +1 -1
- package/components/Label/Label.js +52 -50
- package/components/Link/Link.js +14 -20
- package/components/List/List.js +28 -30
- package/components/Menu/Menu.js +19 -10
- package/components/Menu/Menu.lld.md +2 -2
- package/components/Notify/Notify.js +29 -19
- package/components/Notify/Notify.lld.md +2 -2
- package/components/Page/Page.lld.md +1 -1
- package/components/Popover/Popover.js +49 -44
- package/components/RadioButton/RadioButton.js +33 -29
- package/components/RadioButton/RadioButton.lld.md +1 -1
- package/components/Router/README.md +12 -12
- package/components/Router/Router.js +19 -21
- package/components/Select/README.md +5 -5
- package/components/Select/Select.js +25 -31
- package/components/Table/README.md +4 -4
- package/components/Table/Table.js +55 -36
- package/components/Table/Table.lld.md +1 -1
- package/components/TagList/Tag.js +16 -14
- package/components/TagList/TagList.js +15 -8
- package/components/TagList/TagList.lld.md +1 -1
- package/components/Tooltip/Tooltip.js +12 -25
- package/components/TooltipWrapper/TooltipWrapper.js +55 -49
- package/components/TooltipWrapper/TooltipWrapper.lld.md +2 -2
- package/components/Whiteboard/Whiteboard.js +45 -43
- package/components/Whiteboard/Whiteboard.lld.md +1 -1
- package/devTools/build.js +43 -0
- package/devTools/buildTypes.js +322 -0
- package/devTools/createComponent.js +155 -0
- package/devTools/extractJSDoc.js +395 -0
- package/devTools/processTemplate.js +500 -0
- package/devTools/updateComponentIndex.js +16 -0
- package/devTools/updateDemoViewIndex.js +90 -0
- package/eslint.config.cjs +5 -2
- package/index.d.ts +120 -62
- package/package.json +23 -22
- package/spellcheck.config.cjs +3 -0
- package/styled/README.md +329 -0
- package/theme/.test.js +2 -2
- package/theme/README.md +8 -8
- package/theme/colors.js +16 -13
- package/theme/colors.test.js +28 -0
- package/utils/browser.js +1 -1
- package/utils/element.js +2 -2
- package/FontWithASyntaxHighlighter-Regular.woff2 +0 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# Component
|
|
2
|
+
|
|
3
|
+
Reactive component class extending Elem with Oxject-backed reactive options, lifecycle management, and automatic cleanup.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Reactive options system** - Property changes backed by Oxject emit events and update DOM automatically
|
|
8
|
+
- **Automatic memory management** - Element-level listeners persist through DOM moves, cleaned up on destroy. External resources (timers, subscriptions) cleaned up on disconnect
|
|
9
|
+
- **Flexible render timing** - Immediate, onload, or animationFrame rendering modes
|
|
10
|
+
- **Enhanced event handling** - Input events include `.value` property, DOM connection detection
|
|
11
|
+
- **Integrated styling** - Inline objects or scoped CSS with theme system integration
|
|
12
|
+
- **Lifecycle hooks** - Built-in hover and press handlers with automatic cleanup
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { Component } from '@vanilla-bean/components';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
### Simple Components
|
|
21
|
+
|
|
22
|
+
Every Component extends EventTarget and wraps an HTMLElement with reactive options:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { Component } from '@vanilla-bean/components';
|
|
26
|
+
|
|
27
|
+
const button = new Component({
|
|
28
|
+
tag: 'button',
|
|
29
|
+
textContent: 'Click me',
|
|
30
|
+
className: 'primary-btn',
|
|
31
|
+
onPointerPress: () => alert('Hello!'),
|
|
32
|
+
appendTo: document.body,
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Reactive Property Updates
|
|
37
|
+
|
|
38
|
+
Options are backed by Oxject. Property changes trigger DOM updates automatically:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const input = new Component({
|
|
42
|
+
tag: 'input',
|
|
43
|
+
value: 'Initial text',
|
|
44
|
+
onChange: ({ value }) => {
|
|
45
|
+
input.options.value = value; // Updates DOM reactively
|
|
46
|
+
},
|
|
47
|
+
appendTo: document.body,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Display that updates automatically
|
|
51
|
+
new Component({
|
|
52
|
+
tag: 'p',
|
|
53
|
+
textContent: input.options.subscriber('value', value => `Current: ${value}`),
|
|
54
|
+
appendTo: document.body,
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Extended Component Classes
|
|
59
|
+
|
|
60
|
+
Create reusable component classes with custom behavior:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
class Button extends Component {
|
|
64
|
+
static schema = {
|
|
65
|
+
tag: { default: 'button' },
|
|
66
|
+
mode: {
|
|
67
|
+
default: 'primary',
|
|
68
|
+
enum: ['primary', 'secondary'],
|
|
69
|
+
set(value) {
|
|
70
|
+
this.removeClass(/\bmode-\S+/g).addClass(`mode-${value}`);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With Styled Components
|
|
78
|
+
|
|
79
|
+
Components integrate seamlessly with the styled system:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
import { styled } from '@vanilla-bean/components';
|
|
83
|
+
|
|
84
|
+
const StyledComponent = styled(
|
|
85
|
+
Component,
|
|
86
|
+
({ colors }) => `
|
|
87
|
+
background: ${colors.blue};
|
|
88
|
+
color: ${colors.white};
|
|
89
|
+
padding: 16px;
|
|
90
|
+
border-radius: 8px;
|
|
91
|
+
`,
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Reactive Options System
|
|
96
|
+
|
|
97
|
+
Component options are stored in an Oxject instance (not a plain object like Elem), enabling automatic reactivity:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
const button = new Component({ textContent: 'Click me' });
|
|
101
|
+
|
|
102
|
+
// Property changes trigger DOM updates
|
|
103
|
+
button.options.textContent = 'Updated'; // Updates DOM reactively
|
|
104
|
+
button.options.addClass = 'active'; // Adds CSS class reactively
|
|
105
|
+
|
|
106
|
+
// Full Oxject features available
|
|
107
|
+
const subscription = button.options.subscribe({
|
|
108
|
+
key: 'textContent',
|
|
109
|
+
callback: value => console.log('Text changed:', value),
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Option Processing Pipeline
|
|
114
|
+
|
|
115
|
+
Options are routed through `_setOption` based on key and value type:
|
|
116
|
+
|
|
117
|
+
| Condition | Routing | Example |
|
|
118
|
+
| ------------------------------ | -------------------------- | ------------------------- |
|
|
119
|
+
| Schema `set` / `data` / `enum` | Schema chain (see below) | `variant: 'button'` |
|
|
120
|
+
| Key starts with 'on' | Event handler registration | `onPointerPress: handler` |
|
|
121
|
+
| Schema `attribute: true` | `elem.setAttribute()` | `colspan: '2'` |
|
|
122
|
+
| Component has method | Method call with value | `addClass: 'active'` |
|
|
123
|
+
| HTMLElement has property | Direct elem property | `textContent: 'hello'` |
|
|
124
|
+
| Value is function | Component property | `customMethod: fn` |
|
|
125
|
+
| Default | Elem property assignment | `customProp: value` |
|
|
126
|
+
|
|
127
|
+
## Event Handling
|
|
128
|
+
|
|
129
|
+
### Automatic Event Registration
|
|
130
|
+
|
|
131
|
+
Event handlers are registered automatically when option keys start with 'on':
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
new Component({
|
|
135
|
+
onPointerPress: event => console.log('pressed'),
|
|
136
|
+
onChange: ({ value }) => console.log('value:', value), // Input events include .value
|
|
137
|
+
onConnected: () => console.log('added to DOM'),
|
|
138
|
+
onDisconnected: () => console.log('removed from DOM'),
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
> **Note:** `click` is not in the supported event set. Use `onPointerPress` for press interactions.
|
|
143
|
+
|
|
144
|
+
### Enhanced Input Events
|
|
145
|
+
|
|
146
|
+
Input-related events (`keydown`, `keyup`, `change`, `blur`, `input`, `search`) receive enhanced event objects:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
new Component({
|
|
150
|
+
tag: 'input',
|
|
151
|
+
type: 'text',
|
|
152
|
+
onChange: ({ value, event }) => {
|
|
153
|
+
console.log('New value:', value); // Extracted from input
|
|
154
|
+
console.log('Original event:', event);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Connection Events
|
|
160
|
+
|
|
161
|
+
DOM connection/disconnection detected via MutationObserver:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
new Component({
|
|
165
|
+
onConnected: () => console.log('Component added to DOM'),
|
|
166
|
+
onDisconnected: () => {
|
|
167
|
+
console.log('Component removed - cleanup triggered');
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Specialized Event Handlers
|
|
173
|
+
|
|
174
|
+
#### Hover with Movement Tracking
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
component.onHover(event => {
|
|
178
|
+
console.log('Mouse position:', event.clientX, event.clientY);
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Press Detection
|
|
183
|
+
|
|
184
|
+
Fires on `pointerdown` for immediate, reliable response in all contexts including scroll containers and modal dialogs:
|
|
185
|
+
|
|
186
|
+
```js
|
|
187
|
+
component.onPointerPress(event => {
|
|
188
|
+
console.log('Pressed');
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Styling
|
|
193
|
+
|
|
194
|
+
### Inline Style Objects
|
|
195
|
+
|
|
196
|
+
Apply styles as JavaScript objects:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
component.styles({
|
|
200
|
+
color: 'red',
|
|
201
|
+
padding: '10px',
|
|
202
|
+
backgroundColor: '#f0f0f0',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Or as option
|
|
206
|
+
new Component({
|
|
207
|
+
styles: { color: 'red', padding: '10px' },
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Scoped CSS with Theme Integration
|
|
212
|
+
|
|
213
|
+
Use theme functions for scoped, processed CSS:
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
// Theme function with automatic scoping
|
|
217
|
+
component.styles(
|
|
218
|
+
({ colors, fonts }) => `
|
|
219
|
+
background: ${colors.blue};
|
|
220
|
+
${fonts.kodeMono}
|
|
221
|
+
padding: 16px;
|
|
222
|
+
border-radius: 8px;
|
|
223
|
+
|
|
224
|
+
&:hover {
|
|
225
|
+
background: ${colors.darker(colors.blue)};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@media (max-width: 768px) {
|
|
229
|
+
padding: 8px;
|
|
230
|
+
}
|
|
231
|
+
`,
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
CSS is scope-wrapped and injected as a scoped <style> tag. No preprocessing.
|
|
236
|
+
|
|
237
|
+
## Lifecycle Management
|
|
238
|
+
|
|
239
|
+
### Render Lifecycle
|
|
240
|
+
|
|
241
|
+
`render()` orchestrates a fixed sequence every time it is called:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
render()
|
|
245
|
+
├─ if (this.rendered) ← re-render only: clears children, runs descendant cleanup
|
|
246
|
+
│ empty()
|
|
247
|
+
│ this.rendered = false
|
|
248
|
+
├─ build() ← subclass structural hook, runs before options are applied
|
|
249
|
+
├─ _processOptions() ← routes every option through _setOption()
|
|
250
|
+
│ priority keys first (onConnected, textContent, content, appendTo, prependTo, value)
|
|
251
|
+
│ then all remaining keys
|
|
252
|
+
└─ this.rendered = true
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**`build()` is the subclass structural hook.** Override `build()`, never `render()`, to create child elements and internal structure. Because `build()` runs before `_processOptions()`, all structure exists by the time `_setOption` receives values.
|
|
256
|
+
|
|
257
|
+
The preferred way to declare options is `static schema` - a per-class schema defining each key once: what can exist, its default, and how it routes. Schemas compose across the constructor chain, with child classes overriding parents per-key:
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
class Card extends Component {
|
|
261
|
+
build() {
|
|
262
|
+
this.header = new Component({ tag: 'header', appendTo: this });
|
|
263
|
+
this.body = new Component({ tag: 'section', appendTo: this });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static schema = {
|
|
267
|
+
title: {
|
|
268
|
+
default: 'Untitled',
|
|
269
|
+
set(value) {
|
|
270
|
+
this.header.options.textContent = value;
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
variant: {
|
|
274
|
+
set(value) {
|
|
275
|
+
this.removeClass(/variant-\S+/).addClass(`variant-${value}`);
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
// Component data only - stored in this.options, never routed to the elem
|
|
279
|
+
records: {},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Descriptor fields:
|
|
285
|
+
|
|
286
|
+
- `default` - initial value, merged automatically at construction (no manual `{ ...defaultOptions, ...options }` spread needed). Use a getter (`get default() { return document.body; }`) for values that must evaluate fresh per instance.
|
|
287
|
+
- `set(value, next)` - change handler, called on initial render and every reactive update. Not calling `next(value)` fully owns the key; calling it continues down the chain to parent handlers, then standard routing.
|
|
288
|
+
- A bare `{}` declares a data-only key: declared keys never fall through to fallback guessing, so with no DOM match they live in `this.options` - stored, subscribable, silent. Keys with a real DOM match (Input's `type`, an `onChange` default) still route to it. Reserve bare `{}` for keys whose initial state is genuinely dynamic (computed from other state at use time), optional callbacks where absence is meaningful, and required inputs - everything else declares its `default`, so the read contract lives in the schema instead of scattered `||` fallbacks at consumers. Use a getter default (`get default() { return []; }`) for mutable containers so instances never share one.
|
|
289
|
+
- `data: true` - force store-only routing. Only needed when a data key's name collides with a real DOM property or method that standard routing would otherwise hit (e.g. a data key named `title`).
|
|
290
|
+
- `enum: ['small', 'large']` - the closed set of valid values. Assigning anything else (other than null/undefined) throws, listing the valid values. Readable via `component.optionEnum(key)` - the demo's options editor uses this to render value pickers.
|
|
291
|
+
|
|
292
|
+
Inheritance semantics: `set` functions chain across the class hierarchy (deepest first), while scalar fields (`default`, `enum`, `data`, `attribute`, `priority`) are decided by the nearest class whose descriptor declares them - so a subclass can override a parent default, opt out of a flag with `data: false`, or lift a constraint with `enum: null`, and a subclass that only adds a `set` leaves the parent's scalars intact.
|
|
293
|
+
|
|
294
|
+
Two keys are construction-only: `tag` and `autoRender` accept schema defaults but are consumed before the reactive store is created, so they never appear in `this.options` and cannot change after construction.
|
|
295
|
+
|
|
296
|
+
Unhandled keys fall through to standard routing automatically - no `super._setOption` required.
|
|
297
|
+
|
|
298
|
+
Two more statics complete the declaration surface, and together they eliminate the need for component constructors in almost every case:
|
|
299
|
+
|
|
300
|
+
- `static events = ['select']` - custom event names usable with `on()`/`emit()`. Unioned up the class chain, so a subclass adds to its parent's events instead of replacing them.
|
|
301
|
+
- `static prepareOptions(options, children)` - a pure transform run on the merged (schema defaults + user) options before processing, leaf-first up the class chain. This is the home for computed defaults: Input derives `type` from the value's type here, Code decides its tag from `multiline`, Table normalizes string columns. Because the hook sees merged options, ancestor hooks see subclass schema defaults (Input's hook sees Select's `tag: 'select'`).
|
|
302
|
+
|
|
303
|
+
A fully declared component needs no constructor at all: `static schema` + `static events` + `prepareOptions` + `build()` cover declaration, wiring, and structure. Constructors remain only for genuine construction-time work (Page's eager stylesheet loading, Label's string-shorthand normalization).
|
|
304
|
+
|
|
305
|
+
`_setOption` override still exists for cases that must intercept before the routing chain.
|
|
306
|
+
|
|
307
|
+
### Derived option patterns
|
|
308
|
+
|
|
309
|
+
There is deliberately no `derive` descriptor. When one option's value depends on another, pick the pattern that matches when the derivation must hold:
|
|
310
|
+
|
|
311
|
+
- **Construction-time** - compute in `prepareOptions`. Right when the result feeds construction itself and never changes after (Code decides its `tag` from `multiline`; Notify picks `icon` and `role` from `type`).
|
|
312
|
+
- **Read-time** - normalize where the value is consumed. Right when the option accepts shorthand and can change reactively; construction and reactive assignment then share one path (Table normalizes string columns inside `_renderTable`).
|
|
313
|
+
- **Change-time** - recompute inside the source key's `set`. Right when a change to one option must immediately update dependent state (Table's `sortDirection` re-sorts `data`).
|
|
314
|
+
|
|
315
|
+
**`empty()` only runs on re-render.** On the initial render it is skipped. On subsequent `render()` calls it removes all child elements and runs cleanup on all descendant components before the DOM is cleared.
|
|
316
|
+
|
|
317
|
+
**`async build()` is not supported.** `render()` does not await `build()`'s return value. Asynchronous initialization belongs in `onConnected`.
|
|
318
|
+
|
|
319
|
+
**Deep hierarchies must chain `super.build()` manually.** If both a parent class and its subclass define `build()`, the subclass must call `super.build()` explicitly. It is not called automatically.
|
|
320
|
+
|
|
321
|
+
### Rendering Process (summary)
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
component.render(); // Re-render: empty → build → _processOptions → rendered = true
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Rendering behavior:
|
|
328
|
+
|
|
329
|
+
1. **Content clearing** - `empty()` removes existing children on re-render only (skipped on first render)
|
|
330
|
+
2. **Structure creation** - `build()` creates sub-elements before options are applied
|
|
331
|
+
3. **Priority processing** - `_processOptions()` applies `priority: true` schema keys (and framework priority keys) first
|
|
332
|
+
4. **Option routing** - Each option processed via `_setOption`
|
|
333
|
+
5. **Completion flag** - Sets `rendered = true`
|
|
334
|
+
|
|
335
|
+
### Automatic Cleanup System
|
|
336
|
+
|
|
337
|
+
Components automatically clean up resources when removed from DOM:
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
// Manual cleanup registration
|
|
341
|
+
component.addCleanup('unique-id', () => {
|
|
342
|
+
console.log('Custom cleanup executed');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Manual cleanup execution (automatic on disconnect)
|
|
346
|
+
component.processCleanup();
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
**Automatic cleanup includes:**
|
|
350
|
+
|
|
351
|
+
- Event listener removal
|
|
352
|
+
- Oxject subscription cleanup
|
|
353
|
+
- Style element removal from DOM
|
|
354
|
+
- Recursive child component cleanup
|
|
355
|
+
|
|
356
|
+
### Render Timing Control
|
|
357
|
+
|
|
358
|
+
Control when components render with `autoRender` option:
|
|
359
|
+
|
|
360
|
+
```js
|
|
361
|
+
// Immediate rendering (default)
|
|
362
|
+
new Component({ autoRender: true });
|
|
363
|
+
|
|
364
|
+
// Render on window load event
|
|
365
|
+
new Component({ autoRender: 'onload' });
|
|
366
|
+
|
|
367
|
+
// Render on next animation frame
|
|
368
|
+
new Component({ autoRender: 'animationFrame' });
|
|
369
|
+
|
|
370
|
+
// Manual rendering only
|
|
371
|
+
new Component({ autoRender: false });
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## API Reference
|
|
375
|
+
|
|
376
|
+
### Constructor
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
new Component(options?, ...children)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Parameters:**
|
|
383
|
+
|
|
384
|
+
- `options` (Object) - Configuration options
|
|
385
|
+
- `children` (...Component|Elem) - Child elements appended to `append` option
|
|
386
|
+
|
|
387
|
+
### Configuration Options
|
|
388
|
+
|
|
389
|
+
#### Component-Specific Options
|
|
390
|
+
|
|
391
|
+
| Option | Type | Description |
|
|
392
|
+
| ------------ | ------------------------------------- | ------------------------------------------- |
|
|
393
|
+
| `tag` | `string` | HTML tag name (default: 'div') |
|
|
394
|
+
| `autoRender` | `boolean\|'onload'\|'animationFrame'` | Render timing control |
|
|
395
|
+
| `styles` | `string\|object\|Function` | CSS string, style object, or theme function |
|
|
396
|
+
| `uniqueId` | `string` | Override auto-generated unique ID |
|
|
397
|
+
|
|
398
|
+
Custom events (`static events`), attribute routing (`attribute: true`), and processing priority (`priority: true`) are declared in the class, not passed as options.
|
|
399
|
+
|
|
400
|
+
#### Event Handler Options
|
|
401
|
+
|
|
402
|
+
| Option | Type | Description |
|
|
403
|
+
| ---------------- | ---------- | -------------------------------------------- |
|
|
404
|
+
| `onHover` | `Function` | Hover handler with move tracking |
|
|
405
|
+
| `onPointerPress` | `Function` | Fires on `pointerdown` |
|
|
406
|
+
| ~~`onClick`~~ | - | Not supported. Use `onPointerPress` instead. |
|
|
407
|
+
| `onChange` | `Function` | Change event handler (input .value included) |
|
|
408
|
+
| `onConnected` | `Function` | DOM connection detection |
|
|
409
|
+
| `onDisconnected` | `Function` | DOM disconnection detection |
|
|
410
|
+
|
|
411
|
+
All standard Elem options work as reactive Component options.
|
|
412
|
+
|
|
413
|
+
### Methods
|
|
414
|
+
|
|
415
|
+
#### Options-Compatible Methods
|
|
416
|
+
|
|
417
|
+
Can be called directly or used as reactive options:
|
|
418
|
+
|
|
419
|
+
```js
|
|
420
|
+
component.styles(styleFunction); // Direct call
|
|
421
|
+
component.onHover(callback); // Direct call
|
|
422
|
+
component.addClass('active'); // Direct call
|
|
423
|
+
component.append(child); // Direct call
|
|
424
|
+
|
|
425
|
+
// Or as reactive options:
|
|
426
|
+
new Component({
|
|
427
|
+
styles: styleFunction,
|
|
428
|
+
onHover: callback,
|
|
429
|
+
addClass: 'active',
|
|
430
|
+
append: [child],
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Lifecycle Methods
|
|
435
|
+
|
|
436
|
+
```js
|
|
437
|
+
component.build(); // Subclass structural hook - override to create child elements
|
|
438
|
+
component.render(); // Re-render: empty → build → _processOptions → rendered = true
|
|
439
|
+
component.addCleanup(id, fn); // Register cleanup function (chains with existing for same id)
|
|
440
|
+
component.replaceCleanup(id, fn); // Replace cleanup, running the previous one immediately
|
|
441
|
+
component.replaceDestroyCleanup(id, fn); // Destroy-only cleanup: survives disconnect, runs on destroy()
|
|
442
|
+
component.processCleanup(); // Execute all cleanup functions
|
|
443
|
+
component.destroy(); // Disconnect observer, run all cleanup, remove from DOM
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### Event Methods
|
|
447
|
+
|
|
448
|
+
```js
|
|
449
|
+
component.on({ targetEvent, callback, id? }); // Event registration
|
|
450
|
+
component.emit(eventType, detail); // Event dispatch
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Utility Methods
|
|
454
|
+
|
|
455
|
+
```js
|
|
456
|
+
component.ancestry(); // Prototype chain inspection
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Properties
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
component.parent; // Parent Component/Elem instance
|
|
463
|
+
component.children; // Child Component/Elem instances
|
|
464
|
+
component.uniqueId; // Frozen unique identifier
|
|
465
|
+
component.options; // Reactive Oxject instance
|
|
466
|
+
component.elem; // Underlying HTMLElement
|
|
467
|
+
component.rendered; // Boolean render status flag
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Memory Management
|
|
471
|
+
|
|
472
|
+
Components provide comprehensive automatic memory management:
|
|
473
|
+
|
|
474
|
+
- **Event listeners** - Automatically removed on DOM disconnect
|
|
475
|
+
- **Oxject subscriptions** - Cleaned up when component destroyed
|
|
476
|
+
- **Style elements** - Removed from DOM head on cleanup
|
|
477
|
+
- **Child components** - Recursive cleanup propagation
|
|
478
|
+
- **Custom cleanup** - Manual cleanup function registration
|
|
479
|
+
|
|
480
|
+
No manual cleanup required for typical usage - components handle resource management automatically.
|
|
@@ -33,7 +33,7 @@ function ensureObserver() {
|
|
|
33
33
|
* @param {Node} config.target - Target element to watch for add/remove
|
|
34
34
|
* @param {Function} config.onConnected - Called when target is added to the document
|
|
35
35
|
* @param {Function} config.onDisconnected - Called when target is removed from the document
|
|
36
|
-
* @returns {{ disconnect: Function }} Handle
|
|
36
|
+
* @returns {{ disconnect: Function }} Handle - call disconnect() to deregister
|
|
37
37
|
*/
|
|
38
38
|
export const observeElementConnection = ({ target, onConnected, onDisconnected }) => {
|
|
39
39
|
ensureObserver();
|