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.
Files changed (108) hide show
  1. package/.vitepress/config.js +166 -0
  2. package/CHANGELOG.md +153 -0
  3. package/components.js +2 -1
  4. package/dist/native-document.components.min.js +495 -228
  5. package/dist/native-document.dev.js +7 -0
  6. package/dist/native-document.dev.js.map +1 -1
  7. package/dist/native-document.min.js +1 -1
  8. package/docs/advanced-components.md +213 -608
  9. package/docs/anchor.md +173 -312
  10. package/docs/cache.md +95 -803
  11. package/docs/cli.md +179 -0
  12. package/docs/components/accordion.md +172 -0
  13. package/docs/components/alert.md +99 -0
  14. package/docs/components/avatar.md +160 -0
  15. package/docs/components/badge.md +102 -0
  16. package/docs/components/breadcrumb.md +89 -0
  17. package/docs/components/button.md +183 -0
  18. package/docs/components/card.md +69 -0
  19. package/docs/components/context-menu.md +118 -0
  20. package/docs/components/data-table.md +345 -0
  21. package/docs/components/dropdown.md +214 -0
  22. package/docs/components/form/autocomplete-field.md +81 -0
  23. package/docs/components/form/checkbox-field.md +41 -0
  24. package/docs/components/form/checkbox-group-field.md +54 -0
  25. package/docs/components/form/color-field.md +64 -0
  26. package/docs/components/form/date-field.md +92 -0
  27. package/docs/components/form/field-collection.md +63 -0
  28. package/docs/components/form/file-field.md +203 -0
  29. package/docs/components/form/form-control.md +87 -0
  30. package/docs/components/form/image-field.md +90 -0
  31. package/docs/components/form/index.md +115 -0
  32. package/docs/components/form/number-field.md +65 -0
  33. package/docs/components/form/radio-field.md +51 -0
  34. package/docs/components/form/select-field.md +123 -0
  35. package/docs/components/form/slider.md +136 -0
  36. package/docs/components/form/string-field.md +134 -0
  37. package/docs/components/form/textarea-field.md +65 -0
  38. package/docs/components/form-fields.md +372 -0
  39. package/docs/components/getting-started.md +264 -0
  40. package/docs/components/index.md +337 -0
  41. package/docs/components/layout.md +279 -0
  42. package/docs/components/list.md +73 -0
  43. package/docs/components/menu.md +215 -0
  44. package/docs/components/modal.md +156 -0
  45. package/docs/components/pagination.md +95 -0
  46. package/docs/components/popover.md +131 -0
  47. package/docs/components/progress.md +111 -0
  48. package/docs/components/shortcut-manager.md +221 -0
  49. package/docs/components/simple-table.md +107 -0
  50. package/docs/components/skeleton.md +155 -0
  51. package/docs/components/spinner.md +100 -0
  52. package/docs/components/splitter.md +133 -0
  53. package/docs/components/stepper.md +163 -0
  54. package/docs/components/switch.md +113 -0
  55. package/docs/components/tabs.md +153 -0
  56. package/docs/components/toast.md +119 -0
  57. package/docs/components/tooltip.md +151 -0
  58. package/docs/components/traits.md +261 -0
  59. package/docs/conditional-rendering.md +170 -588
  60. package/docs/contributing.md +300 -25
  61. package/docs/core-concepts.md +205 -374
  62. package/docs/elements.md +251 -367
  63. package/docs/extending-native-document-element.md +192 -207
  64. package/docs/filters.md +153 -1122
  65. package/docs/getting-started.md +193 -267
  66. package/docs/i18n.md +241 -0
  67. package/docs/index.md +76 -0
  68. package/docs/lifecycle-events.md +143 -75
  69. package/docs/list-rendering.md +227 -852
  70. package/docs/memory-management.md +134 -47
  71. package/docs/native-document-element.md +337 -186
  72. package/docs/native-fetch.md +99 -630
  73. package/docs/observable-resource.md +364 -0
  74. package/docs/observables.md +592 -526
  75. package/docs/routing.md +244 -653
  76. package/docs/state-management.md +134 -241
  77. package/docs/svg-elements.md +231 -0
  78. package/docs/theming.md +409 -0
  79. package/docs/tutorials/.gitkeep +0 -0
  80. package/docs/validation.md +95 -97
  81. package/docs/vitepress-conventions.md +219 -0
  82. package/package.json +34 -13
  83. package/readme.md +269 -89
  84. package/src/components/card/Card.js +93 -39
  85. package/src/components/card/index.js +1 -1
  86. package/src/components/list/HasListItem.js +171 -0
  87. package/src/components/list/List.js +41 -107
  88. package/src/components/list/ListDivider.js +39 -0
  89. package/src/components/list/ListGroup.js +76 -59
  90. package/src/components/list/ListItem.js +117 -69
  91. package/src/components/list/index.js +3 -1
  92. package/src/components/list/types/ListItem.d.ts +45 -34
  93. package/src/components/spacer/Spacer.js +1 -1
  94. package/src/core/data/ObservableResource.js +5 -0
  95. package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
  96. package/src/ui/components/card/CardRender.js +133 -0
  97. package/src/ui/components/card/card.css +169 -0
  98. package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
  99. package/src/ui/components/list/ListRender.js +18 -0
  100. package/src/ui/components/list/divider/ListDividerRender.js +10 -0
  101. package/src/ui/components/list/divider/list-divider.css +12 -0
  102. package/src/ui/components/list/group/ListGroupRender.js +61 -0
  103. package/src/ui/components/list/group/list-group.css +62 -0
  104. package/src/ui/components/list/item/ListItemRender.js +238 -0
  105. package/src/ui/components/list/item/list-item.css +191 -0
  106. package/src/ui/components/list/list.css +24 -0
  107. package/src/ui/components/spacer/SpacerRender.js +10 -0
  108. package/src/ui/index.js +8 -0
@@ -0,0 +1,364 @@
1
+ ---
2
+ title: Observable Resource
3
+ description: Manage async data fetching with built-in states, AbortController cancellation, reactive dependencies, and debouncing
4
+ ---
5
+
6
+ # Observable Resource
7
+
8
+ `Observable.resource()` manages async data fetching with built-in lifecycle states, automatic AbortController cancellation, reactive dependency tracking, and debounce support.
9
+
10
+ ---
11
+
12
+ ## Basic Usage
13
+
14
+ ```javascript
15
+ import { Observable } from 'native-document';
16
+
17
+ const user = Observable.resource(
18
+ async () => {
19
+ const res = await fetch('/api/user');
20
+ return res.json();
21
+ },
22
+ [],
23
+ { auto: true }
24
+ );
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Signature
30
+
31
+ ```javascript
32
+ Observable.resource(fn, deps, options)
33
+ ```
34
+
35
+ | Parameter | Type | Description |
36
+ |---|---|---|
37
+ | `fn` | `async function` | Async function that returns the data. Receives dependency values as arguments. If the function declares more parameters than there are dependencies, the last parameter receives an `AbortSignal` |
38
+ | `deps` | `Observable[]` | Reactive dependencies - resource re-fetches when any of these change |
39
+ | `options` | `object` | Configuration object |
40
+
41
+ ### Options
42
+
43
+ | Option | Type | Default | Description |
44
+ |---|---|---|---|
45
+ | `auto` | `boolean` | `false` | Fetch immediately on creation, and re-fetch when deps change |
46
+ | `lazy` | `boolean` | `false` | With deps, skip the initial fetch - only re-fetch when deps change |
47
+ | `debounce` | `number` | `0` | Debounce re-fetches by N milliseconds |
48
+ | `into` | `Observable` | `Observable(null)` | Observable to use as `data`. Pass `$.array()` for list results, or an existing observable to reuse |
49
+ | `apply` | `function` | `null` | Custom function to apply the fetch result to `data`: `(result, data) => void` |
50
+
51
+ ---
52
+
53
+ ## Resource Properties
54
+
55
+ | Property | Type | Description |
56
+ |---|---|---|
57
+ | `data` | `Observable` | The fetched data. Starts as `null` unless `into` is provided |
58
+ | `error` | `Observable` | The error if the last fetch failed, otherwise `null` |
59
+ | `state` | `Observable<string>` | Current state |
60
+ | `loading` | `Observable<boolean>` | `true` when state is `'pending'` or `'refreshing'` |
61
+
62
+ ### States
63
+
64
+ | State | When |
65
+ |---|---|
66
+ | `unresolved` | Created but not yet fetched |
67
+ | `pending` | First fetch in progress, no data yet |
68
+ | `ready` | Data available |
69
+ | `refreshing` | Re-fetching while previous data is still shown |
70
+ | `errored` | Last fetch failed |
71
+
72
+ ---
73
+
74
+ ## State Checkers
75
+
76
+ All return an `ObservableChecker<boolean>`:
77
+
78
+ ```javascript
79
+ user.isUnresolved()
80
+ user.isPending()
81
+ user.isReady()
82
+ user.isRefreshing()
83
+ user.isErrored()
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Methods
89
+
90
+ ### `fetch()` - Trigger first fetch
91
+
92
+ ```javascript
93
+ const user = Observable.resource(fetchUser, []);
94
+ user.fetch();
95
+ ```
96
+
97
+ ### `refetch()` - Re-fetch (shows `'refreshing'` state)
98
+
99
+ ```javascript
100
+ Button('Refresh').nd.onClick(() => user.refetch())
101
+ ```
102
+
103
+ ### `mutate(value)` - Update data optimistically
104
+
105
+ Updates `data` directly and sets state to `'ready'` without a network request:
106
+
107
+ ```javascript
108
+ user.mutate({ ...user.data.val(), name: 'Alice Updated' });
109
+ ```
110
+
111
+ ### `into($observable)` - Set or change the data observable
112
+
113
+ Can be passed as an option or as a chained method after creation.
114
+
115
+ > **Warning:** When `auto: true`, the fetch starts immediately in the constructor - before any chained method calls. If you call `.into()` or `.apply()` as chained methods, the first fetch may already be in progress and will use the default `data` observable, causing an async conflict. **Always pass `into` and `apply` as options when using `auto: true`.**
116
+ >
117
+ > When `auto: false`, chaining is safe - the fetch has not started yet:
118
+
119
+ ```javascript
120
+ // Safe - auto: false, chain .into() then trigger manually
121
+ Observable.resource(fetchUsers, [])
122
+ .into($.array())
123
+ .fetch();
124
+
125
+ // Safe - into passed as option with auto: true
126
+ const users = Observable.resource(fetchUsers, [], {
127
+ auto: true,
128
+ into: $.array()
129
+ });
130
+
131
+ // Unsafe - fetch may start before .into() is called
132
+ const users = Observable.resource(fetchUsers, [], { auto: true })
133
+ .into($.array()); // race condition!
134
+ ```
135
+
136
+ ### `apply(fn)` - Custom result application
137
+
138
+ By default the result is applied via `data.set(result)`. Use `apply` when you need custom merge logic.
139
+
140
+ > **Warning:** Same async conflict as `into()` - always pass `apply` as an option when `auto: true`. When `auto: false`, chaining is safe:
141
+
142
+ ```javascript
143
+ // Safe - auto: false, chain .apply() then trigger manually
144
+ Observable.resource(fetchUsers, [])
145
+ .into($.array())
146
+ .apply((result, data) => data.merge(result))
147
+ .fetch();
148
+
149
+ // Safe - apply passed as option with auto: true
150
+ const users = Observable.resource(fetchUsers, [], {
151
+ auto: true,
152
+ into: $.array(),
153
+ apply: (result, data) => data.merge(result)
154
+ });
155
+
156
+ // Unsafe - apply may not be set before first fetch completes
157
+ const users = Observable.resource(fetchUsers, [], { auto: true })
158
+ .apply((result, data) => data.merge(result)); // race condition!
159
+ ```
160
+
161
+ ### `onSuccess(callback)`
162
+
163
+ Fires when a fetch completes successfully:
164
+
165
+ ```javascript
166
+ user.onSuccess(data => console.log('Loaded:', data));
167
+ ```
168
+
169
+ ### `onError(callback)`
170
+
171
+ Fires when a fetch fails:
172
+
173
+ ```javascript
174
+ user.onError(err => console.error('Failed:', err.message));
175
+ ```
176
+
177
+ ### `destroy()` - Cleanup
178
+
179
+ Aborts any in-flight request and unsubscribes from all dependencies:
180
+
181
+ ```javascript
182
+ user.destroy();
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Rendering Patterns
188
+
189
+ ### With `Match`
190
+
191
+ ```javascript
192
+ import { Match, Div } from 'native-document/elements';
193
+
194
+ const user = Observable.resource(
195
+ async () => {
196
+ const res = await fetch('/api/user');
197
+ return res.json();
198
+ },
199
+ [],
200
+ { auto: true }
201
+ );
202
+
203
+ Match(user.state, {
204
+ unresolved: Div('Not started'),
205
+ pending: Div('Loading...'),
206
+ refreshing: () => Div([
207
+ Div({ class: 'opacity-50' }, user.data.select(u => u.name)),
208
+ Div('Refreshing...')
209
+ ]),
210
+ ready: () => Div(user.data.select(u => u.name)),
211
+ errored: () => Div(['Error: ', user.error.select(e => e.message)])
212
+ })
213
+ ```
214
+
215
+ ### With `ShowIf`
216
+
217
+ ```javascript
218
+ Div([
219
+ ShowIf(user.loading, () => Div('Loading...')),
220
+ ShowIf(user.isReady(), () => P(user.data.select(u => u.name))),
221
+ ShowIf(user.isErrored(), () => Div(['Error: ', user.error.select(e => e.message)])),
222
+ Button('Refresh').nd.onClick(() => user.refetch())
223
+ ])
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Reactive Dependencies
229
+
230
+ When deps change, the resource re-fetches. Dependency values are passed as arguments to `fn` in order:
231
+
232
+ ```javascript
233
+ const userId = Observable(1);
234
+ const language = Observable('en');
235
+
236
+ const profile = Observable.resource(
237
+ async (id, lang) => {
238
+ const res = await fetch(`/api/users/${id}?lang=${lang}`);
239
+ return res.json();
240
+ },
241
+ [userId, language],
242
+ { auto: true }
243
+ );
244
+
245
+ // Triggers a re-fetch automatically
246
+ userId.set(2);
247
+ language.set('fr');
248
+ ```
249
+
250
+ ---
251
+
252
+ ## AbortController Support
253
+
254
+ If `fn` declares **more parameters than there are dependencies**, the last parameter receives an `AbortSignal`. Previous requests are aborted automatically when a new fetch starts:
255
+
256
+ ```javascript
257
+ const userId = Observable(1);
258
+
259
+ const user = Observable.resource(
260
+ async (id, signal) => {
261
+ // fn.length (2) > deps.length (1) -> AbortController enabled
262
+ const res = await fetch(`/api/users/${id}`, { signal });
263
+ return res.json();
264
+ },
265
+ [userId],
266
+ { auto: true }
267
+ );
268
+
269
+ // Rapid changes abort in-flight requests
270
+ userId.set(2);
271
+ userId.set(3); // request for id=2 is aborted
272
+ ```
273
+
274
+ If `fn.length === deps.length`, no AbortController is used.
275
+
276
+ ---
277
+
278
+ ## Debouncing
279
+
280
+ ```javascript
281
+ const search = Observable('');
282
+
283
+ const results = Observable.resource(
284
+ async (query) => {
285
+ const res = await fetch(`/api/search?q=${query}`);
286
+ return res.json();
287
+ },
288
+ [search],
289
+ { auto: true, debounce: 300 }
290
+ );
291
+
292
+ Input({ value: search, placeholder: 'Search...' })
293
+ ```
294
+
295
+ ---
296
+
297
+ ## Lazy Loading
298
+
299
+ With `lazy: true` and deps, the initial fetch is skipped. The resource only fetches when a dependency changes:
300
+
301
+ ```javascript
302
+ const isOpen = Observable(false);
303
+
304
+ const data = Observable.resource(
305
+ async (open) => {
306
+ if (!open) return null;
307
+ const res = await fetch('/api/data');
308
+ return res.json();
309
+ },
310
+ [isOpen],
311
+ { auto: true, lazy: true } // no fetch on creation
312
+ );
313
+
314
+ // First fetch triggered when isOpen changes
315
+ isOpen.set(true);
316
+ ```
317
+
318
+ ---
319
+
320
+ ## `into` - Control the Data Observable
321
+
322
+ Use `into` as an **option** to control the type of `data` or reuse an existing observable. Always pass it as an option when `auto: true` - not as a chained method call:
323
+
324
+ ```javascript
325
+ import { $ } from 'native-document';
326
+
327
+ // Default - plain Observable(null)
328
+ const user = Observable.resource(fetchUser, [], { auto: true });
329
+
330
+ // ObservableArray - useful for list results
331
+ const users = Observable.resource(fetchUsers, [], {
332
+ auto: true,
333
+ into: $.array()
334
+ });
335
+
336
+ // Existing observable - e.g. a cached Store value shown immediately
337
+ const cachedUser = Store.follow('user');
338
+ const user = Observable.resource(fetchUser, [], {
339
+ auto: true,
340
+ into: cachedUser
341
+ });
342
+ // user.data === cachedUser, shown immediately, updated when fetch completes
343
+ ```
344
+
345
+ ---
346
+
347
+ ## vs `Observable.batch()`
348
+
349
+ | | `Observable.resource()` | `Observable.batch()` |
350
+ |---|---|---|
351
+ | **Purpose** | Async data fetching | Batching multiple state changes |
352
+ | **States** | Built-in (pending, ready, errored...) | None |
353
+ | **AbortController** | Automatic | Manual |
354
+ | **Deps tracking** | Auto re-fetch | Manual call |
355
+ | **Best for** | API calls, remote data | Complex local state updates |
356
+
357
+ ---
358
+
359
+ ## Next Steps
360
+
361
+ - **[Observables](./observables.md)** - Reactive state management
362
+ - **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
363
+ - **[State Management](./state-management.md)** - Global state with Store
364
+ - **[Memory Management](./memory-management.md)** - Cleanup with `destroy()`