native-document 1.0.166 → 1.0.168
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/.vitepress/config.js +166 -0
- package/CHANGELOG.md +153 -0
- package/components.js +2 -1
- package/dist/native-document.components.min.js +495 -228
- package/dist/native-document.dev.js +7 -0
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +213 -608
- package/docs/anchor.md +173 -312
- package/docs/cache.md +95 -803
- package/docs/cli.md +179 -0
- package/docs/components/accordion.md +172 -0
- package/docs/components/alert.md +99 -0
- package/docs/components/avatar.md +160 -0
- package/docs/components/badge.md +102 -0
- package/docs/components/breadcrumb.md +89 -0
- package/docs/components/button.md +183 -0
- package/docs/components/card.md +69 -0
- package/docs/components/context-menu.md +118 -0
- package/docs/components/data-table.md +345 -0
- package/docs/components/dropdown.md +214 -0
- package/docs/components/form/autocomplete-field.md +81 -0
- package/docs/components/form/checkbox-field.md +41 -0
- package/docs/components/form/checkbox-group-field.md +54 -0
- package/docs/components/form/color-field.md +64 -0
- package/docs/components/form/date-field.md +92 -0
- package/docs/components/form/field-collection.md +63 -0
- package/docs/components/form/file-field.md +203 -0
- package/docs/components/form/form-control.md +87 -0
- package/docs/components/form/image-field.md +90 -0
- package/docs/components/form/index.md +115 -0
- package/docs/components/form/number-field.md +65 -0
- package/docs/components/form/radio-field.md +51 -0
- package/docs/components/form/select-field.md +123 -0
- package/docs/components/form/slider.md +136 -0
- package/docs/components/form/string-field.md +134 -0
- package/docs/components/form/textarea-field.md +65 -0
- package/docs/components/form-fields.md +372 -0
- package/docs/components/getting-started.md +264 -0
- package/docs/components/index.md +337 -0
- package/docs/components/layout.md +279 -0
- package/docs/components/list.md +73 -0
- package/docs/components/menu.md +215 -0
- package/docs/components/modal.md +156 -0
- package/docs/components/pagination.md +95 -0
- package/docs/components/popover.md +131 -0
- package/docs/components/progress.md +111 -0
- package/docs/components/shortcut-manager.md +221 -0
- package/docs/components/simple-table.md +107 -0
- package/docs/components/skeleton.md +155 -0
- package/docs/components/spinner.md +100 -0
- package/docs/components/splitter.md +133 -0
- package/docs/components/stepper.md +163 -0
- package/docs/components/switch.md +113 -0
- package/docs/components/tabs.md +153 -0
- package/docs/components/toast.md +119 -0
- package/docs/components/tooltip.md +151 -0
- package/docs/components/traits.md +261 -0
- package/docs/conditional-rendering.md +170 -588
- package/docs/contributing.md +300 -25
- package/docs/core-concepts.md +205 -374
- package/docs/elements.md +251 -367
- package/docs/extending-native-document-element.md +192 -207
- package/docs/filters.md +153 -1122
- package/docs/getting-started.md +193 -267
- package/docs/i18n.md +241 -0
- package/docs/index.md +76 -0
- package/docs/lifecycle-events.md +143 -75
- package/docs/list-rendering.md +227 -852
- package/docs/memory-management.md +134 -47
- package/docs/native-document-element.md +337 -186
- package/docs/native-fetch.md +99 -630
- package/docs/observable-resource.md +364 -0
- package/docs/observables.md +592 -526
- package/docs/routing.md +244 -653
- package/docs/state-management.md +134 -241
- package/docs/svg-elements.md +231 -0
- package/docs/theming.md +409 -0
- package/docs/tutorials/.gitkeep +0 -0
- package/docs/validation.md +95 -97
- package/docs/vitepress-conventions.md +219 -0
- package/package.json +34 -13
- package/readme.md +269 -89
- package/src/components/card/Card.js +93 -39
- package/src/components/card/index.js +1 -1
- package/src/components/list/HasListItem.js +171 -0
- package/src/components/list/List.js +41 -107
- package/src/components/list/ListDivider.js +39 -0
- package/src/components/list/ListGroup.js +76 -59
- package/src/components/list/ListItem.js +117 -69
- package/src/components/list/index.js +3 -1
- package/src/components/list/types/ListItem.d.ts +45 -34
- package/src/components/spacer/Spacer.js +1 -1
- package/src/core/data/ObservableResource.js +5 -0
- package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
- package/src/ui/components/card/CardRender.js +133 -0
- package/src/ui/components/card/card.css +169 -0
- package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
- package/src/ui/components/list/ListRender.js +18 -0
- package/src/ui/components/list/divider/ListDividerRender.js +10 -0
- package/src/ui/components/list/divider/list-divider.css +12 -0
- package/src/ui/components/list/group/ListGroupRender.js +61 -0
- package/src/ui/components/list/group/list-group.css +62 -0
- package/src/ui/components/list/item/ListItemRender.js +238 -0
- package/src/ui/components/list/item/list-item.css +191 -0
- package/src/ui/components/list/list.css +24 -0
- package/src/ui/components/spacer/SpacerRender.js +10 -0
- package/src/ui/index.js +8 -0
|
@@ -1,336 +1,487 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NDElement
|
|
3
|
+
description: The NDElement wrapper enhances native HTML elements with fluent event handling, lifecycle hooks, transitions, and DOM utilities
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# NDElement
|
|
2
7
|
|
|
3
|
-
`NDElement` is a wrapper class that enhances native HTML elements with
|
|
8
|
+
`NDElement` is a wrapper class that enhances native HTML elements with fluent event handling, lifecycle hooks, transitions, and DOM utilities - while preserving full access to the underlying native element.
|
|
4
9
|
|
|
5
10
|
## Accessing NDElement
|
|
6
11
|
|
|
7
|
-
Every
|
|
12
|
+
Every element created with NativeDocument automatically has an `nd` property that returns its `NDElement` instance:
|
|
8
13
|
|
|
9
14
|
```javascript
|
|
10
|
-
const element = Div(
|
|
15
|
+
const element = Div('Hello World');
|
|
11
16
|
const ndElement = element.nd; // NDElement instance
|
|
12
17
|
|
|
13
|
-
//
|
|
14
|
-
Div(
|
|
18
|
+
// Chain directly
|
|
19
|
+
Div('Hello').nd.onClick(() => console.log('Clicked!'));
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Once you call `.nd`, subsequent methods chain directly, no need to repeat `.nd`:
|
|
18
23
|
|
|
19
24
|
```javascript
|
|
20
|
-
|
|
25
|
+
Button('Interactive')
|
|
26
|
+
.nd
|
|
27
|
+
.onClick(e => console.log('Clicked'))
|
|
28
|
+
.onMouseEnter(e => e.target.style.background = 'blue')
|
|
29
|
+
.onMouseLeave(e => e.target.style.background = '')
|
|
30
|
+
.mounted(el => console.log('Button mounted'));
|
|
21
31
|
```
|
|
22
32
|
|
|
23
|
-
**Parameters:**
|
|
24
|
-
- `element`: The HTML element to wrap
|
|
25
|
-
|
|
26
33
|
## Properties
|
|
27
34
|
|
|
28
35
|
### `$element`
|
|
29
36
|
The encapsulated native HTML element.
|
|
30
37
|
|
|
31
38
|
```javascript
|
|
32
|
-
const div = Div(
|
|
39
|
+
const div = Div('Content');
|
|
33
40
|
const htmlElement = div.nd.$element; // Native HTMLDivElement
|
|
34
41
|
```
|
|
35
42
|
|
|
36
43
|
### `$observer`
|
|
37
|
-
Lifecycle observer
|
|
44
|
+
Lifecycle observer, used internally for DOM monitoring.
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
---
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
## Event Handling
|
|
42
49
|
|
|
43
|
-
###
|
|
50
|
+
### Auto-generated event methods
|
|
51
|
+
|
|
52
|
+
NDElement generates methods for all standard DOM events in four variants:
|
|
44
53
|
|
|
45
54
|
```javascript
|
|
46
|
-
// Standard
|
|
55
|
+
// Standard
|
|
47
56
|
element.nd.onClick(callback)
|
|
48
|
-
element.nd.onMouseOver(callback)
|
|
49
|
-
element.nd.onKeyDown(callback)
|
|
50
57
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
// Prevents default behavior
|
|
59
|
+
element.nd.onPreventClick(callback)
|
|
60
|
+
|
|
61
|
+
// Stops event propagation
|
|
62
|
+
element.nd.onStopClick(callback)
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
// Both preventDefault() and stopPropagation()
|
|
65
|
+
element.nd.onPreventStopClick(callback)
|
|
66
|
+
```
|
|
57
67
|
|
|
58
68
|
```javascript
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
// Standard usage
|
|
70
|
+
Button('Submit').nd.onClick(e => console.log('Clicked'));
|
|
71
|
+
Input().nd.onInput(e => console.log('Value:', e.target.value));
|
|
72
|
+
|
|
73
|
+
// Prevent default
|
|
74
|
+
Link({ href: '/page' })
|
|
75
|
+
.nd
|
|
76
|
+
.onPreventClick(e => {
|
|
77
|
+
router.push('/page');
|
|
78
|
+
});
|
|
62
79
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
// Stop propagation
|
|
81
|
+
Div([
|
|
82
|
+
Button('Child').nd.onStopClick(e => console.log("Won't bubble"))
|
|
83
|
+
]).nd.onClick(() => console.log('Never called'));
|
|
84
|
+
|
|
85
|
+
// Prevent + stop
|
|
86
|
+
Form().nd.onPreventStopSubmit(handleFormSubmit);
|
|
68
87
|
```
|
|
69
88
|
|
|
70
|
-
###
|
|
89
|
+
### Supported events
|
|
90
|
+
|
|
91
|
+
**Mouse:** `Click`, `DblClick`, `MouseDown`, `MouseEnter`, `MouseLeave`, `MouseMove`, `MouseOut`, `MouseOver`, `MouseUp`, `Wheel`
|
|
92
|
+
|
|
93
|
+
**Keyboard:** `KeyDown`, `KeyPress`, `KeyUp`
|
|
94
|
+
|
|
95
|
+
**Form:** `Blur`, `Change`, `Focus`, `Input`, `Invalid`, `Reset`, `Search`, `Select`, `Submit`
|
|
96
|
+
|
|
97
|
+
**Drag & Drop:** `Drag`, `DragEnd`, `DragEnter`, `DragLeave`, `DragOver`, `DragStart`, `Drop`
|
|
98
|
+
|
|
99
|
+
**Media:** `Abort`, `CanPlay`, `CanPlayThrough`, `DurationChange`, `Emptied`, `Ended`, `LoadedData`, `LoadedMetadata`, `LoadStart`, `Pause`, `Play`, `Playing`, `Progress`, `RateChange`, `Seeked`, `Seeking`, `Stalled`, `Suspend`, `TimeUpdate`, `VolumeChange`, `Waiting`
|
|
100
|
+
|
|
101
|
+
**Window:** `AfterPrint`, `BeforePrint`, `BeforeUnload`, `Error`, `HashChange`, `Load`, `Offline`, `Online`, `PageHide`, `PageShow`, `Resize`, `Scroll`, `Unload`
|
|
102
|
+
|
|
103
|
+
### `.on(name, callback, options)` - Generic event
|
|
104
|
+
|
|
105
|
+
Registers any DOM event. Uses `AbortController` internally - listeners are **automatically removed when the element is unmounted**, no manual cleanup needed:
|
|
71
106
|
|
|
72
107
|
```javascript
|
|
73
|
-
|
|
74
|
-
element.nd.
|
|
75
|
-
|
|
108
|
+
element.nd.on('scroll', callback, { passive: true })
|
|
109
|
+
element.nd.on('customEvent', callback)
|
|
110
|
+
```
|
|
76
111
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
### `.off(name, callback)` - Remove a specific listener
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const handler = e => console.log(e);
|
|
116
|
+
|
|
117
|
+
element.nd.on('click', handler);
|
|
118
|
+
element.nd.off('click', handler);
|
|
83
119
|
```
|
|
84
120
|
|
|
85
|
-
###
|
|
121
|
+
### `.once(name, callback)` - One-time listener
|
|
122
|
+
|
|
123
|
+
Fires once then removes itself automatically:
|
|
86
124
|
|
|
87
125
|
```javascript
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
element.nd.onPreventStopClick(callback)
|
|
126
|
+
element.nd.once('click', () => console.log('First click only'));
|
|
127
|
+
```
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
### `.emit(name, detail?)` - Dispatch a custom event
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
element.nd.emit('my-event', { value: 42 });
|
|
133
|
+
|
|
134
|
+
// Listen for it on a parent
|
|
135
|
+
parent.nd.on('my-event', e => console.log(e.detail.value)); // 42
|
|
97
136
|
```
|
|
98
137
|
|
|
99
|
-
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Attribute Methods
|
|
100
141
|
|
|
101
|
-
|
|
142
|
+
### `.attr(name, value)` - Set a single attribute
|
|
102
143
|
|
|
103
|
-
|
|
144
|
+
Accepts a plain value or an observable:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
element.nd.attr('aria-label', 'Close button');
|
|
148
|
+
element.nd.attr('aria-expanded', isOpen); // reactive
|
|
149
|
+
```
|
|
104
150
|
|
|
105
|
-
|
|
151
|
+
### `.attrs(attrs)` - Set multiple attributes
|
|
106
152
|
|
|
107
|
-
|
|
153
|
+
```javascript
|
|
154
|
+
element.nd.attrs({
|
|
155
|
+
'aria-label': 'Close',
|
|
156
|
+
'aria-expanded': isOpen,
|
|
157
|
+
'data-id': '123'
|
|
158
|
+
});
|
|
159
|
+
```
|
|
108
160
|
|
|
109
|
-
|
|
161
|
+
### `.class(classes)` - Bind class object
|
|
110
162
|
|
|
111
|
-
|
|
163
|
+
```javascript
|
|
164
|
+
element.nd.class({
|
|
165
|
+
'active': isActive,
|
|
166
|
+
'disabled': isDisabled,
|
|
167
|
+
'hidden': isVisible.isFalsy()
|
|
168
|
+
});
|
|
169
|
+
```
|
|
112
170
|
|
|
113
|
-
|
|
171
|
+
### `.style(style)` - Bind style object
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
element.nd.style({
|
|
175
|
+
color: theme.format(t => t === 'dark' ? '#fff' : '#333'),
|
|
176
|
+
opacity: isVisible.format(v => v ? 1 : 0.5)
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
114
181
|
|
|
115
182
|
## Utility Methods
|
|
116
183
|
|
|
117
|
-
### `ref(target, name)`
|
|
118
|
-
|
|
184
|
+
### `ref(target, name)` / `refSelf(target, name)`
|
|
185
|
+
|
|
186
|
+
Both store a reference on a target object but store different things:
|
|
187
|
+
|
|
188
|
+
- **`ref(target, name)`** - stores the **native HTML element** (`this.$element`) - use for direct DOM access
|
|
189
|
+
- **`refSelf(target, name)`** - stores the **`NDElement` instance** (`this`) - use to keep calling `.nd` methods
|
|
119
190
|
|
|
120
191
|
```javascript
|
|
121
192
|
const refs = {};
|
|
122
|
-
|
|
123
|
-
|
|
193
|
+
|
|
194
|
+
Div([
|
|
195
|
+
Input({ type: 'text' }).nd.ref(refs, 'nameInput'), // refs.nameInput -> HTMLInputElement
|
|
196
|
+
Input({ type: 'text' }).nd.refSelf(refs, 'emailInput'), // refs.emailInput -> NDElement instance
|
|
197
|
+
|
|
198
|
+
Button('Actions')
|
|
199
|
+
.nd
|
|
200
|
+
.onClick(() => {
|
|
201
|
+
refs.nameInput.focus(); // native DOM method
|
|
202
|
+
refs.emailInput.onInput(e => console.log(e.target.value)); // nd method
|
|
203
|
+
})
|
|
204
|
+
]);
|
|
124
205
|
```
|
|
125
206
|
|
|
126
207
|
### `htmlElement()` / `node()`
|
|
127
|
-
|
|
208
|
+
|
|
209
|
+
Returns the native HTML element, both are aliases for `$element`:
|
|
128
210
|
|
|
129
211
|
```javascript
|
|
130
|
-
const div = Div(
|
|
131
|
-
|
|
132
|
-
|
|
212
|
+
const div = Div('Hello');
|
|
213
|
+
div.nd.htmlElement(); // HTMLDivElement
|
|
214
|
+
div.nd.node(); // same
|
|
133
215
|
```
|
|
134
216
|
|
|
135
217
|
### `remove()`
|
|
136
|
-
|
|
218
|
+
|
|
219
|
+
Removes the element from the DOM and cleans up its internal references:
|
|
137
220
|
|
|
138
221
|
```javascript
|
|
139
|
-
const element = Div(
|
|
140
|
-
element.nd.remove();
|
|
222
|
+
const element = Div('Temporary');
|
|
223
|
+
element.nd.remove();
|
|
141
224
|
```
|
|
142
225
|
|
|
143
226
|
### `unmountChildren()`
|
|
144
|
-
|
|
227
|
+
|
|
228
|
+
Unmounts all child elements and cleans up their references:
|
|
145
229
|
|
|
146
230
|
```javascript
|
|
147
|
-
const container = Div([
|
|
148
|
-
|
|
149
|
-
Div("Child 2")
|
|
150
|
-
]);
|
|
151
|
-
container.nd.unmountChildren(); // Children cleaned up
|
|
231
|
+
const container = Div([Div('Child 1'), Div('Child 2')]);
|
|
232
|
+
container.nd.unmountChildren();
|
|
152
233
|
```
|
|
153
234
|
|
|
154
|
-
|
|
235
|
+
### `ghostDom(element)`
|
|
236
|
+
|
|
237
|
+
Appends an element to an internal `DocumentFragment` attached to the component. When the component is inserted into the DOM, both the main element and the ghost elements are injected together - but the user only interacts with the main element.
|
|
155
238
|
|
|
156
|
-
|
|
157
|
-
Configures lifecycle callbacks.
|
|
239
|
+
This is primarily useful when building components that need a companion element in the DOM. For example, a `Button` that controls a `Dropdown`: the button is the returned element, the dropdown lives in the ghost DOM. Both are rendered, but only the button is exposed to the parent:
|
|
158
240
|
|
|
159
241
|
```javascript
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
242
|
+
function DropdownButton(label, items) {
|
|
243
|
+
const isOpen = Observable(false);
|
|
244
|
+
|
|
245
|
+
const dropdown = Div({ class: 'dropdown' }, [
|
|
246
|
+
ShowIf(isOpen, () => Ul(items.map(item => Li(item))))
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
return Button(label)
|
|
250
|
+
.nd
|
|
251
|
+
.onClick(() => isOpen.toggle())
|
|
252
|
+
.ghostDom(dropdown); // dropdown is injected into DOM alongside the button
|
|
253
|
+
// but the parent only receives the button
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Usage - the parent only works with the button
|
|
257
|
+
const btn = DropdownButton('Options', ['Edit', 'Delete']);
|
|
258
|
+
document.body.appendChild(btn);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `attach(methodName, bindingHydrator)`
|
|
262
|
+
|
|
263
|
+
Attaches a template binding hydrator to the element. Used internally by the `useCache` and `useSingleton` rendering systems:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
element.nd.attach('onClick', bindingHydrator);
|
|
164
267
|
```
|
|
165
268
|
|
|
269
|
+
See [Advanced Components](./advanced-components.md) for practical usage.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Lifecycle Management
|
|
274
|
+
|
|
166
275
|
### `mounted(callback)`
|
|
167
|
-
|
|
276
|
+
|
|
277
|
+
Fires when the element is added to the DOM:
|
|
168
278
|
|
|
169
279
|
```javascript
|
|
170
|
-
Div(
|
|
171
|
-
|
|
172
|
-
|
|
280
|
+
Div('Content')
|
|
281
|
+
.nd
|
|
282
|
+
.mounted(element => {
|
|
283
|
+
console.log('In the DOM');
|
|
284
|
+
});
|
|
173
285
|
```
|
|
174
286
|
|
|
175
287
|
### `unmounted(callback)`
|
|
176
|
-
|
|
288
|
+
|
|
289
|
+
Fires when the element is removed from the DOM:
|
|
290
|
+
|
|
177
291
|
```javascript
|
|
178
|
-
Div(
|
|
179
|
-
|
|
180
|
-
|
|
292
|
+
Div('Content')
|
|
293
|
+
.nd
|
|
294
|
+
.unmounted(element => {
|
|
295
|
+
console.log('Removed from DOM');
|
|
296
|
+
// Only clean up external resources here (timers, websockets)
|
|
297
|
+
// Do NOT clean up observables unless the element is permanently destroyed
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### `lifecycle({ mounted, unmounted })`
|
|
302
|
+
|
|
303
|
+
Configures both hooks at once:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
Div('Content')
|
|
307
|
+
.nd
|
|
308
|
+
.lifecycle({
|
|
309
|
+
mounted: el => console.log('Mounted'),
|
|
310
|
+
unmounted: el => console.log('Unmounted')
|
|
311
|
+
});
|
|
181
312
|
```
|
|
182
313
|
|
|
183
314
|
### `beforeUnmount(id, callback)`
|
|
184
|
-
|
|
185
|
-
Useful for exit animations or
|
|
315
|
+
|
|
316
|
+
Registers an async callback that runs before the element is removed. Useful for exit animations or data saving:
|
|
317
|
+
|
|
186
318
|
```javascript
|
|
187
|
-
Div(
|
|
188
|
-
|
|
189
|
-
|
|
319
|
+
Div('Content')
|
|
320
|
+
.nd
|
|
321
|
+
.beforeUnmount('save', async () => {
|
|
322
|
+
await saveData();
|
|
323
|
+
});
|
|
190
324
|
```
|
|
191
325
|
|
|
326
|
+
---
|
|
327
|
+
|
|
192
328
|
## Transitions
|
|
193
329
|
|
|
194
330
|
### `transition(name)`
|
|
195
|
-
|
|
331
|
+
|
|
332
|
+
Applies both enter and exit transitions via CSS classes:
|
|
333
|
+
|
|
196
334
|
```javascript
|
|
197
|
-
Div(
|
|
198
|
-
// On mount
|
|
335
|
+
Div('Content').nd.transition('fade');
|
|
336
|
+
// On mount: adds 'fade-enter-from', then 'fade-enter-to'
|
|
199
337
|
// On unmount: adds 'fade-exit'
|
|
200
338
|
```
|
|
201
339
|
|
|
202
|
-
### `transitionIn(name)`
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Div("Content").nd.transitionIn('slide');
|
|
206
|
-
```
|
|
340
|
+
### `transitionIn(name)` / `transitionOut(name)`
|
|
341
|
+
|
|
342
|
+
Apply only the enter or exit transition:
|
|
207
343
|
|
|
208
|
-
### `transitionOut(name)`
|
|
209
|
-
Applies only the exit transition.
|
|
210
344
|
```javascript
|
|
211
|
-
Div(
|
|
345
|
+
Div('Content')
|
|
346
|
+
.nd
|
|
347
|
+
.transitionIn('slide-down')
|
|
348
|
+
.transitionOut('slide-up');
|
|
212
349
|
```
|
|
213
350
|
|
|
214
351
|
### `animate(name)`
|
|
215
|
-
|
|
352
|
+
|
|
353
|
+
Triggers a one-shot CSS animation, adds the class then removes it automatically:
|
|
354
|
+
|
|
216
355
|
```javascript
|
|
217
|
-
Button(
|
|
356
|
+
Button('Click').nd.onClick(function() {
|
|
218
357
|
this.nd.animate('bounce');
|
|
219
358
|
});
|
|
220
359
|
```
|
|
221
360
|
|
|
222
|
-
|
|
361
|
+
---
|
|
223
362
|
|
|
224
|
-
|
|
363
|
+
## Extending NDElement
|
|
364
|
+
|
|
365
|
+
### `.nd.with(methods)` - Exposing child component methods
|
|
366
|
+
|
|
367
|
+
`.with()` is designed for components that need to **expose internal control methods to their parent** without leaking their internal observables. The parent accesses these methods via `refSelf`.
|
|
368
|
+
|
|
369
|
+
A typical use case is a component like a media player or a counter that must expose `play`, `pause`, `reset` - letting the parent control it without knowing its internal state:
|
|
225
370
|
|
|
226
371
|
```javascript
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
372
|
+
function Counter(initialValue = 0) {
|
|
373
|
+
const count = Observable(initialValue); // internal - never exposed
|
|
374
|
+
let interval = null;
|
|
375
|
+
|
|
376
|
+
return Div({ class: 'counter' }, [
|
|
377
|
+
Div(['Count: ', count]),
|
|
378
|
+
])
|
|
379
|
+
.nd
|
|
380
|
+
.with({
|
|
381
|
+
play() {
|
|
382
|
+
if (interval) return this;
|
|
383
|
+
interval = setInterval(() => count.$value++, 1000);
|
|
384
|
+
return this;
|
|
385
|
+
},
|
|
386
|
+
pause() {
|
|
387
|
+
clearInterval(interval);
|
|
388
|
+
interval = null;
|
|
389
|
+
return this;
|
|
390
|
+
},
|
|
391
|
+
reset() {
|
|
392
|
+
this.pause();
|
|
393
|
+
count.set(initialValue);
|
|
394
|
+
return this;
|
|
232
395
|
}
|
|
233
396
|
});
|
|
234
|
-
|
|
235
|
-
};
|
|
397
|
+
}
|
|
236
398
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
.nd.onEnter(e => console.log("Enter pressed!"));
|
|
240
|
-
```
|
|
399
|
+
// Parent controls the counter via refSelf - no internal state exposed
|
|
400
|
+
const refs = {};
|
|
241
401
|
|
|
242
|
-
|
|
402
|
+
Div([
|
|
403
|
+
Counter(0).nd.refSelf(refs, 'counter'),
|
|
404
|
+
Button('Play').nd.onClick(() => refs.counter.play()),
|
|
405
|
+
Button('Pause').nd.onClick(() => refs.counter.pause()),
|
|
406
|
+
Button('Reset').nd.onClick(() => refs.counter.reset())
|
|
407
|
+
]);
|
|
408
|
+
```
|
|
243
409
|
|
|
244
|
-
|
|
245
|
-
const interactiveButton = Button("Interactive")
|
|
246
|
-
.nd.onClick(e => console.log("Clicked"))
|
|
247
|
-
.onMouseEnter(e => e.target.style.background = "blue")
|
|
248
|
-
.onMouseLeave(e => e.target.style.background = "")
|
|
249
|
-
.mounted(el => console.log("Button mounted"));
|
|
410
|
+
> `.with()` only affects the current instance. The methods are not available on other elements.
|
|
250
411
|
|
|
251
|
-
|
|
412
|
+
### `NDElement.extend(methods)` - App-wide methods
|
|
252
413
|
|
|
253
|
-
|
|
414
|
+
Adds methods to **all NDElement instances** via the prototype. Use for app-wide utilities:
|
|
254
415
|
|
|
255
416
|
```javascript
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
417
|
+
NDElement.extend({
|
|
418
|
+
onEnter(callback) {
|
|
419
|
+
this.$element.addEventListener('keyup', e => {
|
|
420
|
+
if (e.key === 'Enter') callback(e);
|
|
421
|
+
});
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Available on every element
|
|
427
|
+
Input().nd.onEnter(e => console.log('Enter pressed'));
|
|
262
428
|
```
|
|
263
429
|
|
|
264
|
-
###
|
|
430
|
+
### Protected methods
|
|
265
431
|
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
element.nd.on('customEvent', callback, options)
|
|
432
|
+
The following method names **cannot be overridden** via `NDElement.extend()` - attempting to do so throws a `NativeDocumentError`:
|
|
269
433
|
|
|
270
|
-
|
|
271
|
-
element.nd.on('scroll', callback, { passive: true })
|
|
272
|
-
```
|
|
434
|
+
`constructor`, `valueOf`, `$element`, `$observer`, `ref`, `remove`, `cleanup`, `with`, `extend`, `attach`, `lifecycle`, `mounted`, `unmounted`, `unmountChildren`
|
|
273
435
|
|
|
274
|
-
|
|
436
|
+
---
|
|
275
437
|
|
|
276
|
-
|
|
277
|
-
const components = {};
|
|
438
|
+
## Shadow DOM
|
|
278
439
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
440
|
+
```javascript
|
|
441
|
+
// Open shadow DOM (inspectable in DevTools)
|
|
442
|
+
Div('Content')
|
|
443
|
+
.nd
|
|
444
|
+
.openShadow(`
|
|
445
|
+
:host { display: block; padding: 20px; }
|
|
446
|
+
p { color: blue; }
|
|
447
|
+
`);
|
|
448
|
+
|
|
449
|
+
// Closed shadow DOM (private)
|
|
450
|
+
Div('Content').nd.closedShadow(`p { color: red; }`);
|
|
451
|
+
|
|
452
|
+
// Manual mode
|
|
453
|
+
Div('Content').nd.shadow('open', `/* scoped styles */`);
|
|
285
454
|
```
|
|
286
455
|
|
|
287
|
-
|
|
456
|
+
---
|
|
288
457
|
|
|
289
|
-
|
|
458
|
+
## Integration with Observables
|
|
290
459
|
|
|
291
460
|
```javascript
|
|
292
461
|
const isVisible = Observable(false);
|
|
293
|
-
const message
|
|
462
|
+
const message = Observable('Hello');
|
|
294
463
|
|
|
295
464
|
Div([
|
|
296
|
-
Button(
|
|
297
|
-
ShowIf(isVisible, () =>
|
|
298
|
-
P(message).nd.onClick(() =>
|
|
299
|
-
message.set("Clicked!")
|
|
300
|
-
)
|
|
465
|
+
Button('Toggle').nd.onClick(() => isVisible.toggle()),
|
|
466
|
+
ShowIf(isVisible, () =>
|
|
467
|
+
P(message).nd.onClick(() => message.set('Clicked!'))
|
|
301
468
|
)
|
|
302
469
|
]);
|
|
303
470
|
```
|
|
304
471
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
1. **Fluent chaining**: Use method chaining for concise syntax
|
|
308
|
-
2. **Cleanup**: Call `remove()` to clean up dynamic elements
|
|
309
|
-
3. **Extensions**: Add your own methods to the prototype for specific needs
|
|
310
|
-
4. **Lifecycle**: Use `mounted`/`unmounted` for initialization/cleanup
|
|
311
|
-
5. **References**: Use `ref()` for direct element access when needed
|
|
312
|
-
|
|
313
|
-
## Limitations
|
|
314
|
-
|
|
315
|
-
- Event handlers added via .nd.onXxx() are not automatically removed.
|
|
316
|
-
Use the native removeEventListener() on .$element if needed,
|
|
317
|
-
or rely on .nd.remove() which cleans up the element entirely.
|
|
318
|
-
- Access to native HTML element is still necessary for advanced APIs
|
|
319
|
-
|
|
320
|
-
NDElement thus provides a practical abstraction layer while preserving the power and performance of native DOM.
|
|
321
|
-
|
|
472
|
+
---
|
|
322
473
|
|
|
323
474
|
## Next Steps
|
|
324
475
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
- **[
|
|
328
|
-
- **[
|
|
329
|
-
- **[
|
|
330
|
-
- **[Anchor](anchor.md)** - Anchor
|
|
476
|
+
- **[Extending NDElement](./extending-native-document-element.md)** - Adding custom methods
|
|
477
|
+
- **[Lifecycle Events](./lifecycle-events.md)** - Mounted, unmounted, beforeUnmount in depth
|
|
478
|
+
- **[Advanced Components](./advanced-components.md)** - Template caching and singleton views
|
|
479
|
+
- **[Memory Management](./memory-management.md)** - Cleanup and auto-cleanup
|
|
480
|
+
- **[Observables](./observables.md)** - Reactive state management
|
|
481
|
+
- **[Anchor](./anchor.md)** - Anchor
|
|
331
482
|
|
|
332
483
|
## Utilities
|
|
333
484
|
|
|
334
|
-
- **[Cache](
|
|
335
|
-
- **[NativeFetch](
|
|
336
|
-
- **[Filters](
|
|
485
|
+
- **[Cache](./cache.md)** - Lazy initialization and singleton patterns
|
|
486
|
+
- **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
|
|
487
|
+
- **[Filters](./filters.md)** - Data filtering helpers
|