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,274 +1,259 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Extending NDElement
|
|
3
|
+
description: Add custom methods to NDElement to create reusable, domain-specific APIs across your application
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# Extending NDElement
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
NDElement is designed to be extensible. You can add custom methods to make your code more expressive, reduce boilerplate, and create a consistent API across your application.
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
- **Encapsulate common patterns** into reusable methods
|
|
9
|
-
- **Improve code readability** with domain-specific method names
|
|
10
|
-
- **Reduce boilerplate** by abstracting complex event handling
|
|
11
|
-
- **Create a consistent API** across your application
|
|
10
|
+
## Two Ways to Extend
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
### `NDElement.extend()` - App-wide methods
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
Use `NDElement.extend()` to add methods to **all NDElement instances**. This is the recommended public API:
|
|
16
15
|
|
|
17
16
|
```javascript
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
import { NDElement } from 'native-document';
|
|
18
|
+
|
|
19
|
+
NDElement.extend({
|
|
20
|
+
onEnter(callback) {
|
|
21
|
+
this.$element.addEventListener('keyup', e => {
|
|
22
|
+
if (e.key === 'Enter') callback(e);
|
|
23
|
+
});
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
// Now available on every element
|
|
29
|
+
Input({ type: 'text' }).nd.onEnter(e => console.log('Enter pressed'));
|
|
30
|
+
```
|
|
26
31
|
|
|
27
|
-
###
|
|
32
|
+
### `.nd.with()` - Instance-level methods
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
Use `.nd.with()` to add methods to a **single element instance** only. See [NDElement](./native-document-element.md) for the full explanation and the `Counter` use case.
|
|
30
35
|
|
|
31
36
|
```javascript
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const card = Div({ class: 'card' })
|
|
38
|
+
.nd.with({
|
|
39
|
+
highlight() {
|
|
40
|
+
this.$element.style.outline = '2px solid blue';
|
|
41
|
+
return this;
|
|
37
42
|
}
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
})
|
|
44
|
+
.highlight();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> Always `return this` at the end of every method to enable chaining.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Common Extension Examples
|
|
52
|
+
|
|
53
|
+
### Keyboard shortcuts
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
NDElement.extend({
|
|
57
|
+
onEnter(callback) {
|
|
58
|
+
this.$element.addEventListener('keyup', e => {
|
|
59
|
+
if (e.key === 'Enter') callback(e);
|
|
60
|
+
});
|
|
61
|
+
return this;
|
|
62
|
+
},
|
|
63
|
+
onEscape(callback) {
|
|
64
|
+
this.$element.addEventListener('keyup', e => {
|
|
65
|
+
if (e.key === 'Escape') callback(e);
|
|
66
|
+
});
|
|
67
|
+
return this;
|
|
68
|
+
},
|
|
69
|
+
onArrowKey(callback) {
|
|
70
|
+
this.$element.addEventListener('keydown', e => {
|
|
71
|
+
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
|
72
|
+
callback(e, e.key);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
61
78
|
|
|
62
79
|
// Usage
|
|
63
80
|
Input({ type: 'text' })
|
|
64
|
-
.nd
|
|
81
|
+
.nd
|
|
82
|
+
.onEnter(e => console.log('Submitted'))
|
|
65
83
|
.onEscape(e => e.target.blur())
|
|
66
|
-
.onArrowKey((e, direction) => console.log('Arrow
|
|
84
|
+
.onArrowKey((e, direction) => console.log('Arrow:', direction));
|
|
67
85
|
```
|
|
68
86
|
|
|
69
|
-
###
|
|
70
|
-
|
|
71
|
-
Create semantic validation methods:
|
|
87
|
+
### Form validation
|
|
72
88
|
|
|
73
89
|
```javascript
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.showError(message);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
return this;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Error display helpers
|
|
115
|
-
NDElement.prototype.showError = function(message) {
|
|
116
|
-
this.clearError();
|
|
117
|
-
const errorElement = Span({
|
|
118
|
-
class: 'error-message',
|
|
119
|
-
style: 'color: red; font-size: 0.8rem'
|
|
120
|
-
}, message);
|
|
121
|
-
|
|
122
|
-
this.$element.parentNode.appendChild(errorElement);
|
|
123
|
-
this.$element.classList.add('error');
|
|
124
|
-
return this;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
NDElement.prototype.clearError = function() {
|
|
128
|
-
const parent = this.$element.parentNode;
|
|
129
|
-
const existingError = parent.querySelector('.error-message');
|
|
130
|
-
if (existingError) {
|
|
131
|
-
existingError.remove();
|
|
90
|
+
NDElement.extend({
|
|
91
|
+
required(message = 'This field is required') {
|
|
92
|
+
this.$element.addEventListener('blur', e => {
|
|
93
|
+
e.target.value.trim() ? this.clearError() : this.showError(message);
|
|
94
|
+
});
|
|
95
|
+
return this;
|
|
96
|
+
},
|
|
97
|
+
email(message = 'Please enter a valid email') {
|
|
98
|
+
this.$element.addEventListener('blur', e => {
|
|
99
|
+
const value = e.target.value.trim();
|
|
100
|
+
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
101
|
+
value && !valid ? this.showError(message) : this.clearError();
|
|
102
|
+
});
|
|
103
|
+
return this;
|
|
104
|
+
},
|
|
105
|
+
minLength(length, message) {
|
|
106
|
+
message = message || `Minimum ${length} characters required`;
|
|
107
|
+
this.$element.addEventListener('input', e => {
|
|
108
|
+
const len = e.target.value.length;
|
|
109
|
+
len > 0 && len < length ? this.showError(message) : this.clearError();
|
|
110
|
+
});
|
|
111
|
+
return this;
|
|
112
|
+
},
|
|
113
|
+
showError(message) {
|
|
114
|
+
this.clearError();
|
|
115
|
+
this.$element.parentNode.appendChild(
|
|
116
|
+
Span({ class: 'error-message', style: 'color: red; font-size: 0.8rem' }, message)
|
|
117
|
+
);
|
|
118
|
+
this.$element.classList.add('error');
|
|
119
|
+
return this;
|
|
120
|
+
},
|
|
121
|
+
clearError() {
|
|
122
|
+
this.$element.parentNode.querySelector('.error-message')?.remove();
|
|
123
|
+
this.$element.classList.remove('error');
|
|
124
|
+
return this;
|
|
132
125
|
}
|
|
133
|
-
|
|
134
|
-
return this;
|
|
135
|
-
};
|
|
126
|
+
});
|
|
136
127
|
|
|
137
128
|
// Usage
|
|
138
129
|
Input({ type: 'email', placeholder: 'Email' })
|
|
139
|
-
.nd
|
|
130
|
+
.nd
|
|
131
|
+
.required()
|
|
140
132
|
.email();
|
|
141
133
|
|
|
142
134
|
Input({ type: 'password', placeholder: 'Password' })
|
|
143
|
-
.nd
|
|
135
|
+
.nd
|
|
136
|
+
.required()
|
|
144
137
|
.minLength(8, 'Password must be at least 8 characters');
|
|
145
138
|
```
|
|
146
139
|
|
|
147
|
-
###
|
|
148
|
-
|
|
149
|
-
Create smooth animation helpers:
|
|
140
|
+
### Animations
|
|
150
141
|
|
|
151
142
|
```javascript
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
element.style.overflow = 'hidden';
|
|
181
|
-
element.style.transition = `max-height ${duration}ms ease-in-out`;
|
|
182
|
-
|
|
183
|
-
requestAnimationFrame(() => {
|
|
184
|
-
element.style.maxHeight = element.scrollHeight + 'px';
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
return this;
|
|
188
|
-
};
|
|
143
|
+
NDElement.extend({
|
|
144
|
+
fadeIn(duration = 300) {
|
|
145
|
+
duration = Math.max(0, parseInt(duration) || 300);
|
|
146
|
+
this.$element.style.opacity = '0';
|
|
147
|
+
this.$element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
this.$element.style.opacity = '1';
|
|
150
|
+
});
|
|
151
|
+
return this;
|
|
152
|
+
},
|
|
153
|
+
fadeOut(duration = 300, callback) {
|
|
154
|
+
duration = Math.max(0, parseInt(duration) || 300);
|
|
155
|
+
this.$element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
156
|
+
this.$element.style.opacity = '0';
|
|
157
|
+
setTimeout(() => { if (callback) callback(); }, duration);
|
|
158
|
+
return this;
|
|
159
|
+
},
|
|
160
|
+
slideDown(duration = 300) {
|
|
161
|
+
const el = this.$element;
|
|
162
|
+
el.style.maxHeight = '0';
|
|
163
|
+
el.style.overflow = 'hidden';
|
|
164
|
+
el.style.transition = `max-height ${duration}ms ease-in-out`;
|
|
165
|
+
requestAnimationFrame(() => {
|
|
166
|
+
el.style.maxHeight = el.scrollHeight + 'px';
|
|
167
|
+
});
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
189
171
|
|
|
190
172
|
// Usage
|
|
191
|
-
Div(
|
|
192
|
-
.nd.
|
|
193
|
-
|
|
194
|
-
});
|
|
173
|
+
Div('Animated content').nd.onClick(function() {
|
|
174
|
+
this.nd.fadeOut(300, () => this.nd.remove());
|
|
175
|
+
});
|
|
195
176
|
```
|
|
196
177
|
|
|
178
|
+
---
|
|
179
|
+
|
|
197
180
|
## Best Practices
|
|
198
181
|
|
|
199
|
-
###
|
|
200
|
-
|
|
182
|
+
### Always return `this`
|
|
183
|
+
|
|
184
|
+
Every method must return `this` to keep the chain alive:
|
|
201
185
|
|
|
202
186
|
```javascript
|
|
203
|
-
NDElement.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
187
|
+
NDElement.extend({
|
|
188
|
+
myMethod() {
|
|
189
|
+
// your logic
|
|
190
|
+
return this; // required
|
|
191
|
+
}
|
|
192
|
+
});
|
|
207
193
|
```
|
|
208
194
|
|
|
209
|
-
###
|
|
210
|
-
Choose names that clearly describe what the method does:
|
|
195
|
+
### Use descriptive names
|
|
211
196
|
|
|
212
197
|
```javascript
|
|
213
198
|
// Good
|
|
214
|
-
NDElement.
|
|
215
|
-
NDElement.
|
|
199
|
+
NDElement.extend({ onEnter(cb) { ... } });
|
|
200
|
+
NDElement.extend({ fadeIn(duration) { ... } });
|
|
216
201
|
|
|
217
202
|
// Avoid
|
|
218
|
-
NDElement.
|
|
219
|
-
NDElement.
|
|
203
|
+
NDElement.extend({ ke(cb) { ... } }); // unclear
|
|
204
|
+
NDElement.extend({ doStuff() { ... } }); // too vague
|
|
220
205
|
```
|
|
221
206
|
|
|
222
|
-
###
|
|
223
|
-
Always consider edge cases and provide sensible defaults:
|
|
207
|
+
### Guard against edge cases
|
|
224
208
|
|
|
225
209
|
```javascript
|
|
226
|
-
NDElement.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return this;
|
|
235
|
-
};
|
|
210
|
+
NDElement.extend({
|
|
211
|
+
fadeIn(duration = 300) {
|
|
212
|
+
if (!this.$element) return this;
|
|
213
|
+
duration = Math.max(0, parseInt(duration) || 300);
|
|
214
|
+
// animation logic
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
236
218
|
```
|
|
237
219
|
|
|
238
|
-
###
|
|
239
|
-
|
|
240
|
-
Always document your custom methods:
|
|
220
|
+
### Document your extensions
|
|
241
221
|
|
|
242
222
|
```javascript
|
|
243
223
|
/**
|
|
244
|
-
*
|
|
245
|
-
* @param {Function} callback -
|
|
246
|
-
* @returns {NDElement}
|
|
224
|
+
* Fires callback when the Enter key is pressed
|
|
225
|
+
* @param {Function} callback - receives the KeyboardEvent
|
|
226
|
+
* @returns {NDElement} this - for chaining
|
|
247
227
|
* @example
|
|
248
|
-
* Input().nd.onEnter(e =>
|
|
228
|
+
* Input().nd.onEnter(e => submitForm());
|
|
249
229
|
*/
|
|
250
|
-
NDElement.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
callback(e);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
};
|
|
230
|
+
NDElement.extend({
|
|
231
|
+
onEnter(callback) {
|
|
232
|
+
this.$element.addEventListener('keyup', e => {
|
|
233
|
+
if (e.key === 'Enter') callback(e);
|
|
234
|
+
});
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
258
238
|
```
|
|
259
239
|
|
|
260
|
-
|
|
240
|
+
### Respect protected methods
|
|
261
241
|
|
|
262
|
-
|
|
242
|
+
The following names cannot be used - attempting to extend with them throws a `NativeDocumentError`:
|
|
243
|
+
|
|
244
|
+
`constructor`, `valueOf`, `$element`, `$observer`, `ref`, `remove`, `cleanup`, `with`, `extend`, `attach`, `lifecycle`, `mounted`, `unmounted`, `unmountChildren`
|
|
263
245
|
|
|
264
|
-
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Next Steps
|
|
265
249
|
|
|
266
|
-
- **[
|
|
267
|
-
- **[
|
|
268
|
-
- **[
|
|
250
|
+
- **[NDElement](./native-document-element.md)** - Full NDElement API reference
|
|
251
|
+
- **[Lifecycle Events](./lifecycle-events.md)** - Lifecycle in depth
|
|
252
|
+
- **[Args Validation](./validation.md)** - Function argument validation
|
|
253
|
+
- **[Memory Management](./memory-management.md)** - Memory management
|
|
269
254
|
|
|
270
255
|
## Utilities
|
|
271
256
|
|
|
272
|
-
- **[Cache](
|
|
273
|
-
- **[NativeFetch](
|
|
274
|
-
- **[Filters](
|
|
257
|
+
- **[Cache](./cache.md)** - Lazy initialization and singleton patterns
|
|
258
|
+
- **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
|
|
259
|
+
- **[Filters](./filters.md)** - Data filtering helpers
|