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
package/readme.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NativeDocument
|
|
3
|
+
description: A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# NativeDocument
|
|
2
7
|
|
|
3
8
|
[](https://opensource.org/licenses/MIT)
|
|
4
9
|
[](#)
|
|
5
|
-
[](#)
|
|
6
11
|
[](#)
|
|
7
12
|
|
|
8
13
|
> **A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features**
|
|
@@ -10,7 +15,8 @@
|
|
|
10
15
|
NativeDocument combines the familiarity of vanilla JavaScript with the power of modern reactivity. No compilation, no virtual DOM, just pure JavaScript with an intuitive API.
|
|
11
16
|
|
|
12
17
|
## Why NativeDocument?
|
|
13
|
-
|
|
18
|
+
|
|
19
|
+
> **Note**: NativeDocument works best with a bundler (Vite, Webpack, Rollup) for tree-shaking and optimal bundle size. The CDN version includes all features.
|
|
14
20
|
|
|
15
21
|
### **Instant Start**
|
|
16
22
|
```html
|
|
@@ -29,41 +35,58 @@ import { Observable } from 'native-document';
|
|
|
29
35
|
const count = Observable(0);
|
|
30
36
|
|
|
31
37
|
const App = Div({ class: 'app' }, [
|
|
32
|
-
Div([
|
|
33
|
-
Button('Increment').nd
|
|
38
|
+
Div(['Count ', count]),
|
|
39
|
+
Button('Increment').nd
|
|
40
|
+
.onClick(() => count.$value++)
|
|
34
41
|
]);
|
|
35
42
|
|
|
36
43
|
document.body.appendChild(App);
|
|
37
44
|
```
|
|
38
45
|
|
|
39
46
|
### **Complete Features**
|
|
40
|
-
- **Native reactivity** with observables
|
|
41
|
-
- **Global store** for state management
|
|
42
|
-
- **Built-in conditional rendering**
|
|
43
|
-
- **Full-featured router** (hash, history, memory modes)
|
|
47
|
+
- **Native reactivity** with observables, computed values, and batched updates
|
|
48
|
+
- **Global store** for state management with groups, persistence, and computed stores
|
|
49
|
+
- **Built-in conditional rendering** (`ShowIf`, `Match`, `Switch`, `When`)
|
|
50
|
+
- **Full-featured router** (hash, history, memory modes) with layouts and middlewares
|
|
51
|
+
- **Headless UI components** with an optional rendering system (50+ components)
|
|
52
|
+
- **Built-in i18n** via `tr()`, locale-aware `Observable.format()`, and `Formatters`
|
|
53
|
+
- **Advanced data filtering** with composable filter helpers
|
|
54
|
+
- **Official CLI** for scaffolding projects, pages, components, and services
|
|
44
55
|
- **Advanced debugging system**
|
|
45
|
-
- **Automatic memory management** via FinalizationRegistry
|
|
56
|
+
- **Automatic memory management** via `FinalizationRegistry`
|
|
57
|
+
- **Tree-shaking** support — only bundle what you use
|
|
46
58
|
|
|
47
59
|
## Quick Installation
|
|
48
60
|
|
|
49
|
-
### Option 1:
|
|
61
|
+
### Option 1: CLI (Recommended)
|
|
62
|
+
|
|
63
|
+
The fastest way to start a complete project:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install -g @native-document/cli
|
|
67
|
+
|
|
68
|
+
nd create MyApp # default structure
|
|
69
|
+
nd create MyApp --feature # feature-based architecture
|
|
70
|
+
|
|
71
|
+
cd MyApp
|
|
72
|
+
npm install
|
|
73
|
+
npm start
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> The CLI source is available at [github.com/afrocodeur/native-document-cli](https://github.com/afrocodeur/native-document-cli).
|
|
77
|
+
|
|
78
|
+
See the **[CLI guide](./cli.md)** for all available commands (`nd create:page`, `nd create:component`, `nd create:service` and more).
|
|
79
|
+
|
|
80
|
+
### Option 2: CDN (No build step)
|
|
50
81
|
```html
|
|
51
|
-
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document/dist/native-document.min.js"></script>
|
|
82
|
+
<script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
|
|
52
83
|
<script>
|
|
53
|
-
const { Div } = NativeDocument.elements
|
|
54
|
-
const { Observable } = NativeDocument
|
|
84
|
+
const { Div } = NativeDocument.elements;
|
|
85
|
+
const { Observable } = NativeDocument;
|
|
55
86
|
// Your code here
|
|
56
87
|
</script>
|
|
57
88
|
```
|
|
58
89
|
|
|
59
|
-
### Option 2: Vite Template (Complete Project)
|
|
60
|
-
```bash
|
|
61
|
-
npx degit afrocodeur/native-document-vite my-app
|
|
62
|
-
cd my-app
|
|
63
|
-
npm install
|
|
64
|
-
npm run start
|
|
65
|
-
```
|
|
66
|
-
|
|
67
90
|
### Option 3: NPM/Yarn
|
|
68
91
|
```bash
|
|
69
92
|
npm install native-document
|
|
@@ -74,16 +97,16 @@ yarn add native-document
|
|
|
74
97
|
## Quick Example
|
|
75
98
|
|
|
76
99
|
```javascript
|
|
77
|
-
import { Div, Input, Button, ShowIf, ForEach } from 'native-document/elements'
|
|
78
|
-
import { Observable } from 'native-document'
|
|
100
|
+
import { Div, Input, Button, ShowIf, ForEach } from 'native-document/elements';
|
|
101
|
+
import { Observable } from 'native-document';
|
|
79
102
|
|
|
80
103
|
// CDN
|
|
81
104
|
// const { Div, Input, Button, ShowIf, ForEach } = NativeDocument.elements;
|
|
82
105
|
// const { Observable } = NativeDocument;
|
|
83
106
|
|
|
84
107
|
// Reactive state
|
|
85
|
-
const todos = Observable.array([])
|
|
86
|
-
const newTodo = Observable('')
|
|
108
|
+
const todos = Observable.array([]);
|
|
109
|
+
const newTodo = Observable('');
|
|
87
110
|
|
|
88
111
|
// Todo Component
|
|
89
112
|
const TodoApp = Div({ class: 'todo-app' }, [
|
|
@@ -92,10 +115,11 @@ const TodoApp = Div({ class: 'todo-app' }, [
|
|
|
92
115
|
Input({ placeholder: 'Add new task...', value: newTodo }),
|
|
93
116
|
|
|
94
117
|
// Add button
|
|
95
|
-
Button('Add Todo').nd
|
|
118
|
+
Button('Add Todo').nd
|
|
119
|
+
.onClick(() => {
|
|
96
120
|
if (newTodo.val().trim()) {
|
|
97
|
-
todos.push({ id: Date.now(), text: newTodo.val(), done: false })
|
|
98
|
-
newTodo.set('')
|
|
121
|
+
todos.push({ id: Date.now(), text: newTodo.val(), done: false });
|
|
122
|
+
newTodo.set('');
|
|
99
123
|
}
|
|
100
124
|
}),
|
|
101
125
|
|
|
@@ -104,17 +128,19 @@ const TodoApp = Div({ class: 'todo-app' }, [
|
|
|
104
128
|
Div({ class: 'todo-item' }, [
|
|
105
129
|
Input({ type: 'checkbox', checked: todo.done }),
|
|
106
130
|
`${todo.text}`,
|
|
107
|
-
Button('Delete').nd
|
|
108
|
-
|
|
131
|
+
Button('Delete').nd
|
|
132
|
+
.onClick(() => todos.splice(index.val(), 1))
|
|
133
|
+
]),
|
|
134
|
+
(item) => item.id // Key function — use unique identifier
|
|
135
|
+
),
|
|
109
136
|
|
|
110
137
|
// Empty state
|
|
111
|
-
ShowIf(
|
|
112
|
-
todos.check(list => list.length === 0),
|
|
138
|
+
ShowIf(todos.isEmpty(),
|
|
113
139
|
Div({ class: 'empty' }, 'No todos yet!')
|
|
114
140
|
)
|
|
115
141
|
]);
|
|
116
142
|
|
|
117
|
-
document.body.appendChild(TodoApp)
|
|
143
|
+
document.body.appendChild(TodoApp);
|
|
118
144
|
```
|
|
119
145
|
|
|
120
146
|
## Core Concepts
|
|
@@ -122,47 +148,77 @@ document.body.appendChild(TodoApp)
|
|
|
122
148
|
### Observables
|
|
123
149
|
Reactive data that automatically updates the DOM:
|
|
124
150
|
```javascript
|
|
125
|
-
import { Div } from 'native-document/elements'
|
|
126
|
-
import { Observable } from 'native-document'
|
|
151
|
+
import { Div } from 'native-document/elements';
|
|
152
|
+
import { Observable } from 'native-document';
|
|
127
153
|
|
|
128
154
|
// CDN
|
|
129
|
-
// const { Div
|
|
155
|
+
// const { Div } = NativeDocument.elements;
|
|
130
156
|
// const { Observable } = NativeDocument;
|
|
131
157
|
|
|
132
158
|
const user = Observable({ name: 'John', age: 25 });
|
|
133
|
-
const greeting = Observable.computed(() => `Hello ${user
|
|
134
|
-
// Or const greeting = Observable.computed(() => `Hello ${user.val().name}!`, [user])
|
|
159
|
+
const greeting = Observable.computed(() => `Hello ${user.val().name}!`, [user]);
|
|
135
160
|
|
|
136
161
|
document.body.appendChild(Div(greeting));
|
|
137
162
|
|
|
138
163
|
// Direct mutation won't trigger updates
|
|
139
|
-
user.name = 'Fausty';
|
|
164
|
+
// user.name = 'Fausty'; // Wrong!
|
|
140
165
|
|
|
141
166
|
// These will trigger updates:
|
|
142
|
-
user.$value = { ...user.$value, name: '
|
|
167
|
+
user.$value = { ...user.$value, name: 'Hermes!' };
|
|
143
168
|
user.set(data => ({ ...data, name: 'Hermes!' }));
|
|
144
169
|
user.set({ ...user.val(), name: 'Hermes!' });
|
|
145
170
|
```
|
|
146
171
|
|
|
172
|
+
### Formatting & i18n
|
|
173
|
+
Format observable values reactively with built-in locale awareness.
|
|
174
|
+
You must set a locale observable before using `Observable.format()`:
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
import { Observable } from 'native-document';
|
|
178
|
+
import { tr } from 'native-document/i18n';
|
|
179
|
+
|
|
180
|
+
// Set the locale first — formats react to it automatically
|
|
181
|
+
const $locale = Observable('fr');
|
|
182
|
+
Observable.setLocale($locale);
|
|
183
|
+
|
|
184
|
+
const price = Observable(4999);
|
|
185
|
+
const date = Observable(Date.now());
|
|
186
|
+
|
|
187
|
+
export function PriceDisplay() {
|
|
188
|
+
return Div([
|
|
189
|
+
Div(price.format('currency', { currency: 'USD' })),
|
|
190
|
+
Div(date.format('date', { dateStyle: 'long' })),
|
|
191
|
+
Button('Switch to English').nd
|
|
192
|
+
.onClick(() => $locale.set('en'))
|
|
193
|
+
]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Built-in format types: currency, number, percent, date, time, datetime, relative, plural
|
|
197
|
+
|
|
198
|
+
// Translation helper
|
|
199
|
+
P(tr('welcome_message'))
|
|
200
|
+
```
|
|
201
|
+
|
|
147
202
|
### Elements
|
|
148
203
|
Familiar HTML element creation with reactive bindings:
|
|
149
204
|
```javascript
|
|
150
|
-
import { Div, Button } from 'native-document/elements'
|
|
151
|
-
import { Observable } from 'native-document'
|
|
205
|
+
import { Div, Button } from 'native-document/elements';
|
|
206
|
+
import { Observable } from 'native-document';
|
|
152
207
|
|
|
153
208
|
// CDN
|
|
154
|
-
// const { Div, Button
|
|
209
|
+
// const { Div, Button } = NativeDocument.elements;
|
|
155
210
|
// const { Observable } = NativeDocument;
|
|
156
211
|
|
|
157
|
-
const App
|
|
158
|
-
const isVisible = Observable(true)
|
|
159
|
-
|
|
212
|
+
const App = function() {
|
|
213
|
+
const isVisible = Observable(true);
|
|
214
|
+
|
|
160
215
|
return Div([
|
|
161
216
|
Div({
|
|
162
|
-
class: { 'hidden': isVisible.
|
|
163
|
-
style: { opacity: isVisible.
|
|
217
|
+
class: { 'hidden': isVisible.isFalsy() },
|
|
218
|
+
style: { opacity: isVisible.format(v => v ? 1 : 0.2) }
|
|
164
219
|
}, 'Content'),
|
|
165
|
-
Button('Toggle').nd
|
|
220
|
+
Button('Toggle').nd
|
|
221
|
+
.onClick(() => isVisible.toggle()),
|
|
166
222
|
]);
|
|
167
223
|
};
|
|
168
224
|
|
|
@@ -172,13 +228,15 @@ document.body.appendChild(App());
|
|
|
172
228
|
### Conditional Rendering
|
|
173
229
|
Built-in components for dynamic content:
|
|
174
230
|
```javascript
|
|
175
|
-
ShowIf
|
|
176
|
-
|
|
231
|
+
import { ShowIf, Match, Switch, When } from 'native-document/elements';
|
|
232
|
+
|
|
233
|
+
ShowIf(user.is(u => u.isLoggedIn),
|
|
234
|
+
Div('Welcome back!')
|
|
177
235
|
)
|
|
178
236
|
|
|
179
237
|
Match(theme, {
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
'dark': Div({ class: 'dark-mode' }),
|
|
239
|
+
'light': Div({ class: 'light-mode' })
|
|
182
240
|
})
|
|
183
241
|
|
|
184
242
|
Switch(condition, onTrue, onFalse)
|
|
@@ -191,51 +249,173 @@ When(condition)
|
|
|
191
249
|
### List Rendering
|
|
192
250
|
Efficient rendering of lists with automatic updates:
|
|
193
251
|
```javascript
|
|
194
|
-
import { ForEach, Div } from 'native-document/elements'
|
|
195
|
-
import { Observable } from 'native-document'
|
|
252
|
+
import { ForEach, Div } from 'native-document/elements';
|
|
253
|
+
import { Observable } from 'native-document';
|
|
196
254
|
|
|
197
|
-
const items = Observable.array(['Apple', 'Banana', 'Cherry'])
|
|
255
|
+
const items = Observable.array(['Apple', 'Banana', 'Cherry']);
|
|
198
256
|
|
|
199
|
-
ForEach(items, (item, index) =>
|
|
257
|
+
ForEach(items, (item, index) =>
|
|
200
258
|
Div([index, '. ', item])
|
|
201
259
|
)
|
|
202
260
|
|
|
203
|
-
// With object arrays
|
|
261
|
+
// With object arrays — use a key function for efficient updates
|
|
204
262
|
const users = Observable.array([
|
|
205
263
|
{ id: 1, name: 'Alice' },
|
|
206
264
|
{ id: 2, name: 'Bob' }
|
|
207
|
-
])
|
|
265
|
+
]);
|
|
208
266
|
|
|
209
|
-
ForEach(users, (user) =>
|
|
267
|
+
ForEach(users, (user) =>
|
|
210
268
|
Div(user.name),
|
|
211
269
|
(user) => user.id // Key for efficient updates
|
|
212
270
|
)
|
|
213
271
|
```
|
|
214
272
|
|
|
273
|
+
### Routing
|
|
274
|
+
Full-featured router with hash, history, and memory modes.
|
|
275
|
+
Use `to` with a route name (string) or `{ name, params }` object. Use `href` for direct path links:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
import { Router, Link } from 'native-document/router';
|
|
279
|
+
|
|
280
|
+
Router.create({ name: 'default', mode: 'history' }, (router) => {
|
|
281
|
+
router.group('', { layout: DefaultLayout }, () => {
|
|
282
|
+
router.add('/', HomePage);
|
|
283
|
+
router.add('/user/{id}', UserPage);
|
|
284
|
+
router.add('{*}', NotFoundPage);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Named route link
|
|
289
|
+
Link({ to: 'home' }, 'Home')
|
|
290
|
+
|
|
291
|
+
// Named route with params
|
|
292
|
+
Link({ to: { name: 'user', params: { id: 42 } } }, 'User Profile')
|
|
293
|
+
|
|
294
|
+
// Direct path link
|
|
295
|
+
Link({ href: '/about' }, 'About')
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### State Management
|
|
299
|
+
Global state with groups, persistence, and computed stores:
|
|
300
|
+
```javascript
|
|
301
|
+
import { Store } from 'native-document';
|
|
302
|
+
|
|
303
|
+
// Simple store
|
|
304
|
+
Store.create('theme', 'light');
|
|
305
|
+
|
|
306
|
+
// Persistent store — survives page reloads
|
|
307
|
+
Store.createPersistent('settings', { lang: 'en', darkMode: false });
|
|
308
|
+
|
|
309
|
+
// Grouped stores — isolated namespaces
|
|
310
|
+
const CartStore = Store.group('cart', (group) => {
|
|
311
|
+
group.create('items', []);
|
|
312
|
+
group.createComposed('total', () => {
|
|
313
|
+
return CartStore.get('items').val()
|
|
314
|
+
.reduce((sum, item) => sum + item.price * item.qty, 0);
|
|
315
|
+
}, ['items']);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Access in components
|
|
319
|
+
const items = CartStore.use('items'); // two-way reactive
|
|
320
|
+
const total = CartStore.follow('total'); // read-only reactive
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Data Filters
|
|
324
|
+
Composable filter helpers for `ObservableArray` only:
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
import { Observable } from 'native-document';
|
|
328
|
+
import { equals, greaterThan, lessThan, and, or, not } from 'native-document/filters';
|
|
329
|
+
|
|
330
|
+
const users = Observable.array([
|
|
331
|
+
{ name: 'Alice', age: 17, role: 'user' },
|
|
332
|
+
{ name: 'Bob', age: 25, role: 'admin' },
|
|
333
|
+
{ name: 'Carol', age: 32, role: 'user' },
|
|
334
|
+
]);
|
|
335
|
+
|
|
336
|
+
// and — field must pass ALL conditions
|
|
337
|
+
const youngAdults = users.where({
|
|
338
|
+
age: and(greaterThan(18), lessThan(30))
|
|
339
|
+
});
|
|
340
|
+
// → Bob (25)
|
|
341
|
+
|
|
342
|
+
// or — field must pass AT LEAST ONE condition
|
|
343
|
+
const adminOrEditor = users.where({
|
|
344
|
+
role: or(equals('admin'), equals('editor'))
|
|
345
|
+
});
|
|
346
|
+
// → Bob
|
|
347
|
+
|
|
348
|
+
// not — inverts a filter
|
|
349
|
+
const nonAdmins = users.where({
|
|
350
|
+
role: not(equals('admin'))
|
|
351
|
+
});
|
|
352
|
+
// → Alice, Carol
|
|
353
|
+
|
|
354
|
+
// Cross-field logic — use the _ key with a plain function
|
|
355
|
+
const adminsOrMinors = users.where({
|
|
356
|
+
_: (item) => item.role === 'admin' || item.age < 18
|
|
357
|
+
});
|
|
358
|
+
// → Alice (minor), Bob (admin)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
> `and`, `or`, and `not` work on **filter result objects** — they operate on a single field.
|
|
362
|
+
> For cross-field logic use the `_` key with a plain function.
|
|
363
|
+
> `.where()` returns a new live `ObservableArray` that re-filters automatically when the source changes.
|
|
364
|
+
|
|
215
365
|
## Documentation
|
|
216
366
|
|
|
217
|
-
- **[Getting Started](
|
|
218
|
-
- **[
|
|
219
|
-
- **[
|
|
220
|
-
- **[
|
|
221
|
-
- **[
|
|
222
|
-
- **[
|
|
223
|
-
- **[
|
|
224
|
-
- **[
|
|
225
|
-
- **[
|
|
226
|
-
- **[
|
|
227
|
-
- **[
|
|
228
|
-
- **[
|
|
229
|
-
- **[
|
|
230
|
-
- **[
|
|
231
|
-
- **[
|
|
367
|
+
- **[Getting Started](./getting-started.md)** — Installation and first steps
|
|
368
|
+
- **[CLI](./cli.md)** — Scaffolding projects, pages, and components
|
|
369
|
+
- **[Core Concepts](./core-concepts.md)** — Understanding the fundamentals
|
|
370
|
+
- **[Observables](./observables.md)** — Reactive state management
|
|
371
|
+
- **[Observable Resource](./observable-resource.md)** — Async data fetching
|
|
372
|
+
- **[Elements](./elements.md)** — Creating and composing UI
|
|
373
|
+
- **[Conditional Rendering](./conditional-rendering.md)** — Dynamic content
|
|
374
|
+
- **[List Rendering](./list-rendering.md)** — ForEach and dynamic lists
|
|
375
|
+
- **[Routing](./routing.md)** — Navigation and URL management
|
|
376
|
+
- **[State Management](./state-management.md)** — Global state patterns
|
|
377
|
+
- **[Lifecycle Events](./lifecycle-events.md)** — Lifecycle events
|
|
378
|
+
- **[NDElement](./native-document-element.md)** — Native Document Element
|
|
379
|
+
- **[Extending NDElement](./extending-native-document-element.md)** — Custom Methods Guide
|
|
380
|
+
- **[Advanced Components](./advanced-components.md)** — Template caching and singleton views
|
|
381
|
+
- **[Args Validation](./validation.md)** — Function Argument Validation
|
|
382
|
+
- **[Memory Management](./memory-management.md)** — Memory management
|
|
383
|
+
- **[Anchor](./anchor.md)** — Anchor
|
|
384
|
+
- **[SVG Elements](./svg-elements.md)** — SVG wrapper functions
|
|
385
|
+
- **[i18n & Formatting](./i18n.md)** — Locale-aware formatting and translations
|
|
386
|
+
|
|
387
|
+
### Components
|
|
388
|
+
|
|
389
|
+
- **[Components Overview](./components/index.md)** — Headless UI component system
|
|
390
|
+
- **[Getting Started](./components/getting-started.md)** — First component and renderer setup
|
|
391
|
+
- **[Traits](./components/traits.md)** — Draggable, Resizable, EventEmitter
|
|
392
|
+
- **[Layout](./components/layout.md)** — Stack, Row, Col, Divider
|
|
393
|
+
- **[Accordion](./components/accordion.md)**
|
|
394
|
+
- **[Alert, Badge, Spinner, Skeleton, Progress](./components/alert.md)**
|
|
395
|
+
- **[Avatar](./components/avatar.md)**
|
|
396
|
+
- **[Breadcrumb](./components/breadcrumb.md)**
|
|
397
|
+
- **[Button](./components/button.md)**
|
|
398
|
+
- **[Context Menu](./components/context-menu.md)**
|
|
399
|
+
- **[Data Table](./components/data-table.md)**
|
|
400
|
+
- **[Dropdown](./components/dropdown.md)**
|
|
401
|
+
- **[File Upload](./components/file.md)**
|
|
402
|
+
- **[Form Fields](./components/form-fields.md)**
|
|
403
|
+
- **[Checkbox & Radio](./components/checkbox-radio.md)**
|
|
404
|
+
- **[Select](./components/select.md)**
|
|
405
|
+
- **[Menu](./components/menu.md)**
|
|
406
|
+
- **[Modal & Popover](./components/modal.md)**
|
|
407
|
+
- **[Slider & Stepper](./components/slider-stepper.md)**
|
|
408
|
+
- **[Splitter](./components/splitter.md)**
|
|
409
|
+
- **[Switch](./components/switch.md)**
|
|
410
|
+
- **[Tabs](./components/tabs.md)**
|
|
411
|
+
- **[Toast](./components/toast.md)**
|
|
412
|
+
- **[Tooltip](./components/tooltip.md)**
|
|
232
413
|
|
|
233
414
|
### Utilities
|
|
234
415
|
|
|
235
|
-
- **[Cache](
|
|
236
|
-
- **[NativeFetch](
|
|
237
|
-
- **[Filters](
|
|
238
|
-
|
|
416
|
+
- **[Cache](./utils/cache.md)** — Lazy initialization and singleton patterns
|
|
417
|
+
- **[NativeFetch](./utils/native-fetch.md)** — HTTP client with interceptors
|
|
418
|
+
- **[Filters](./utils/filters.md)** — Data filtering helpers
|
|
239
419
|
|
|
240
420
|
## Key Features Deep Dive
|
|
241
421
|
|
|
@@ -244,25 +424,26 @@ ForEach(users, (user) =>
|
|
|
244
424
|
- Automatic batching of updates
|
|
245
425
|
- Lazy evaluation of computed values
|
|
246
426
|
- Efficient list rendering with keyed updates
|
|
427
|
+
- Tree-shaking — only bundle what you use
|
|
247
428
|
|
|
248
429
|
### Developer Experience
|
|
249
430
|
```javascript
|
|
250
|
-
import { ArgTypes } from 'native-document'
|
|
431
|
+
import { ArgTypes } from 'native-document';
|
|
251
432
|
|
|
252
433
|
// Built-in debugging
|
|
253
|
-
Observable.debug.enable()
|
|
434
|
+
Observable.debug.enable();
|
|
254
435
|
|
|
255
436
|
// Argument validation
|
|
256
|
-
const createUser = (function
|
|
257
|
-
|
|
258
|
-
}).args(ArgTypes.string('name'), ArgTypes.number('age'))
|
|
259
|
-
|
|
437
|
+
const createUser = (function(name, age) {
|
|
438
|
+
// Auto-validates argument types in development
|
|
439
|
+
}).args(ArgTypes.string('name'), ArgTypes.number('age'));
|
|
260
440
|
|
|
441
|
+
// Error boundaries
|
|
261
442
|
const SafeApp = App.errorBoundary((error, { caller, args }) => {
|
|
262
443
|
return Div({ class: 'error' }, [
|
|
263
444
|
'An error occurred: ',
|
|
264
445
|
error.message
|
|
265
|
-
])
|
|
446
|
+
]);
|
|
266
447
|
});
|
|
267
448
|
|
|
268
449
|
document.body.appendChild(SafeApp());
|
|
@@ -270,7 +451,7 @@ document.body.appendChild(SafeApp());
|
|
|
270
451
|
|
|
271
452
|
## Contributing
|
|
272
453
|
|
|
273
|
-
We welcome contributions! Please see our [Contributing Guide](
|
|
454
|
+
We welcome contributions! Please see our [Contributing Guide](./contributing.md) for details.
|
|
274
455
|
|
|
275
456
|
### Development Setup
|
|
276
457
|
```bash
|
|
@@ -310,11 +491,10 @@ Your support helps me:
|
|
|
310
491
|
|
|
311
492
|
Thanks for your support! 🙏
|
|
312
493
|
|
|
313
|
-
|
|
314
494
|
## Acknowledgments
|
|
315
495
|
|
|
316
496
|
Thanks to all contributors and the JavaScript community for inspiration.
|
|
317
497
|
|
|
318
498
|
---
|
|
319
499
|
|
|
320
|
-
**Ready to build with native simplicity?** [Get Started ->](
|
|
500
|
+
**Ready to build with native simplicity?** [Get Started ->](./getting-started.md)
|