snice 4.12.0 → 4.14.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/dist/cdn/accordion/snice-accordion.js +1 -1
- package/dist/cdn/accordion/snice-accordion.min.js +1 -1
- package/dist/cdn/alert/snice-alert.js +1 -1
- package/dist/cdn/alert/snice-alert.min.js +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
- package/dist/cdn/avatar/snice-avatar.js +1 -1
- package/dist/cdn/avatar/snice-avatar.min.js +1 -1
- package/dist/cdn/badge/snice-badge.js +1 -1
- package/dist/cdn/badge/snice-badge.min.js +1 -1
- package/dist/cdn/banner/snice-banner.js +1 -1
- package/dist/cdn/banner/snice-banner.min.js +1 -1
- package/dist/cdn/book/snice-book.js +1 -1
- package/dist/cdn/book/snice-book.min.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
- package/dist/cdn/button/snice-button.js +1 -1
- package/dist/cdn/button/snice-button.min.js +1 -1
- package/dist/cdn/calendar/snice-calendar.js +1 -1
- package/dist/cdn/calendar/snice-calendar.min.js +1 -1
- package/dist/cdn/camera/snice-camera.js +1 -1
- package/dist/cdn/camera/snice-camera.min.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
- package/dist/cdn/card/snice-card.js +1 -1
- package/dist/cdn/card/snice-card.min.js +1 -1
- package/dist/cdn/carousel/snice-carousel.js +1 -1
- package/dist/cdn/carousel/snice-carousel.min.js +1 -1
- package/dist/cdn/chart/snice-chart.js +1 -1
- package/dist/cdn/chart/snice-chart.min.js +1 -1
- package/dist/cdn/chat/snice-chat.js +1 -1
- package/dist/cdn/chat/snice-chat.min.js +1 -1
- package/dist/cdn/checkbox/snice-checkbox.js +1 -1
- package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
- package/dist/cdn/chip/snice-chip.js +1 -1
- package/dist/cdn/chip/snice-chip.min.js +1 -1
- package/dist/cdn/code-block/snice-code-block.js +2 -2
- package/dist/cdn/code-block/snice-code-block.js.map +1 -1
- package/dist/cdn/code-block/snice-code-block.min.js +2 -2
- package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
- package/dist/cdn/color-display/snice-color-display.js +1 -1
- package/dist/cdn/color-display/snice-color-display.min.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
- package/dist/cdn/command-palette/snice-command-palette.js +1 -1
- package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
- package/dist/cdn/comments/snice-comments.js +1 -1
- package/dist/cdn/comments/snice-comments.min.js +1 -1
- package/dist/cdn/countdown/snice-countdown.js +1 -1
- package/dist/cdn/countdown/snice-countdown.min.js +1 -1
- package/dist/cdn/cropper/snice-cropper.js +1 -1
- package/dist/cdn/cropper/snice-cropper.min.js +1 -1
- package/dist/cdn/date-picker/snice-date-picker.js +1 -1
- package/dist/cdn/date-picker/snice-date-picker.min.js +1 -1
- package/dist/cdn/diff/snice-diff.js +1 -1
- package/dist/cdn/diff/snice-diff.min.js +1 -1
- package/dist/cdn/divider/snice-divider.js +1 -1
- package/dist/cdn/divider/snice-divider.min.js +1 -1
- package/dist/cdn/doc/snice-doc.js +1 -1
- package/dist/cdn/doc/snice-doc.min.js +1 -1
- package/dist/cdn/draw/snice-draw.js +1 -1
- package/dist/cdn/draw/snice-draw.min.js +1 -1
- package/dist/cdn/drawer/snice-drawer.js +1 -1
- package/dist/cdn/drawer/snice-drawer.min.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
- package/dist/cdn/flow/snice-flow.js +1 -1
- package/dist/cdn/flow/snice-flow.min.js +1 -1
- package/dist/cdn/funnel/snice-funnel.js +1 -1
- package/dist/cdn/funnel/snice-funnel.min.js +1 -1
- package/dist/cdn/gantt/snice-gantt.js +1 -1
- package/dist/cdn/gantt/snice-gantt.min.js +1 -1
- package/dist/cdn/gauge/snice-gauge.js +1 -1
- package/dist/cdn/gauge/snice-gauge.min.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
- package/dist/cdn/image/snice-image.js +1 -1
- package/dist/cdn/image/snice-image.min.js +1 -1
- package/dist/cdn/input/snice-input.js +1 -1
- package/dist/cdn/input/snice-input.min.js +1 -1
- package/dist/cdn/kanban/snice-kanban.js +1 -1
- package/dist/cdn/kanban/snice-kanban.min.js +1 -1
- package/dist/cdn/kpi/snice-kpi.js +1 -1
- package/dist/cdn/kpi/snice-kpi.min.js +1 -1
- package/dist/cdn/layout/snice-layout.js +1 -1
- package/dist/cdn/layout/snice-layout.min.js +1 -1
- package/dist/cdn/link/snice-link.js +1 -1
- package/dist/cdn/link/snice-link.min.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.min.js +1 -1
- package/dist/cdn/list/snice-list.js +1 -1
- package/dist/cdn/list/snice-list.min.js +1 -1
- package/dist/cdn/location/snice-location.js +1 -1
- package/dist/cdn/location/snice-location.min.js +1 -1
- package/dist/cdn/login/snice-login.js +1 -1
- package/dist/cdn/login/snice-login.min.js +1 -1
- package/dist/cdn/map/snice-map.js +1 -1
- package/dist/cdn/map/snice-map.min.js +1 -1
- package/dist/cdn/markdown/snice-markdown.js +1 -1
- package/dist/cdn/markdown/snice-markdown.min.js +1 -1
- package/dist/cdn/masonry/snice-masonry.js +1 -1
- package/dist/cdn/masonry/snice-masonry.min.js +1 -1
- package/dist/cdn/menu/snice-menu.js +1 -1
- package/dist/cdn/menu/snice-menu.min.js +1 -1
- package/dist/cdn/modal/snice-modal.js +1 -1
- package/dist/cdn/modal/snice-modal.min.js +1 -1
- package/dist/cdn/music-player/snice-music-player.js +1 -1
- package/dist/cdn/music-player/snice-music-player.min.js +1 -1
- package/dist/cdn/nav/snice-nav.js +1 -1
- package/dist/cdn/nav/snice-nav.min.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
- package/dist/cdn/pagination/snice-pagination.js +1 -1
- package/dist/cdn/pagination/snice-pagination.min.js +1 -1
- package/dist/cdn/paint/snice-paint.js +1 -1
- package/dist/cdn/paint/snice-paint.min.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
- package/dist/cdn/progress/snice-progress.js +1 -1
- package/dist/cdn/progress/snice-progress.min.js +1 -1
- package/dist/cdn/qr-code/README.md +2 -2
- package/dist/cdn/qr-code/snice-qr-code.js +149 -20
- package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
- package/dist/cdn/qr-code/snice-qr-code.min.js +3 -3
- package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
- package/dist/cdn/radio/snice-radio.js +1 -1
- package/dist/cdn/radio/snice-radio.min.js +1 -1
- package/dist/cdn/rating/snice-rating.js +1 -1
- package/dist/cdn/rating/snice-rating.min.js +1 -1
- package/dist/cdn/recipe/snice-recipe.js +1 -1
- package/dist/cdn/recipe/snice-recipe.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.js +4 -4
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +3 -3
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +4 -4
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +3 -3
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +1 -1
- package/dist/cdn/sankey/snice-sankey.min.js +1 -1
- package/dist/cdn/select/snice-select.js +1 -1
- package/dist/cdn/select/snice-select.min.js +1 -1
- package/dist/cdn/skeleton/snice-skeleton.js +1 -1
- package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
- package/dist/cdn/slider/snice-slider.js +2 -2
- package/dist/cdn/slider/snice-slider.js.map +1 -1
- package/dist/cdn/slider/snice-slider.min.js +5 -5
- package/dist/cdn/slider/snice-slider.min.js.map +1 -1
- package/dist/cdn/sortable/snice-sortable.js +1 -1
- package/dist/cdn/sortable/snice-sortable.min.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
- package/dist/cdn/spinner/snice-spinner.js +1 -1
- package/dist/cdn/spinner/snice-spinner.min.js +1 -1
- package/dist/cdn/split-pane/snice-split-pane.js +1 -1
- package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
- package/dist/cdn/spreadsheet/snice-spreadsheet.js +1 -1
- package/dist/cdn/spreadsheet/snice-spreadsheet.min.js +1 -1
- package/dist/cdn/stepper/snice-stepper.js +1 -1
- package/dist/cdn/stepper/snice-stepper.min.js +1 -1
- package/dist/cdn/switch/snice-switch.js +1 -1
- package/dist/cdn/switch/snice-switch.min.js +1 -1
- package/dist/cdn/table/snice-table.js +1 -1
- package/dist/cdn/table/snice-table.min.js +1 -1
- package/dist/cdn/tabs/snice-tabs.js +1 -1
- package/dist/cdn/tabs/snice-tabs.min.js +1 -1
- package/dist/cdn/tag-input/snice-tag-input.js +1 -1
- package/dist/cdn/tag-input/snice-tag-input.min.js +1 -1
- package/dist/cdn/terminal/snice-terminal.js +1 -1
- package/dist/cdn/terminal/snice-terminal.min.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
- package/dist/cdn/textarea/snice-textarea.js +1 -1
- package/dist/cdn/textarea/snice-textarea.min.js +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
- package/dist/cdn/timeline/snice-timeline.js +1 -1
- package/dist/cdn/timeline/snice-timeline.min.js +1 -1
- package/dist/cdn/timer/snice-timer.js +1 -1
- package/dist/cdn/timer/snice-timer.min.js +1 -1
- package/dist/cdn/toast/snice-toast.js +1 -1
- package/dist/cdn/toast/snice-toast.min.js +1 -1
- package/dist/cdn/tooltip/snice-tooltip.js +1 -1
- package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
- package/dist/cdn/tree/snice-tree.js +1 -1
- package/dist/cdn/tree/snice-tree.min.js +1 -1
- package/dist/cdn/treemap/snice-treemap.js +1 -1
- package/dist/cdn/treemap/snice-treemap.min.js +1 -1
- package/dist/cdn/video-player/snice-video-player.js +1 -1
- package/dist/cdn/video-player/snice-video-player.min.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
- package/dist/cdn/weather/snice-weather.js +1 -1
- package/dist/cdn/weather/snice-weather.min.js +1 -1
- package/dist/components/code-block/snice-code-block.js +1 -1
- package/dist/components/code-block/snice-code-block.js.map +1 -1
- package/dist/components/qr-code/qrcode.d.ts +1 -0
- package/dist/components/qr-code/qrcode.js +16 -8
- package/dist/components/qr-code/qrcode.js.map +1 -1
- package/dist/components/qr-code/snice-qr-code.d.ts +5 -2
- package/dist/components/qr-code/snice-qr-code.js +132 -11
- package/dist/components/qr-code/snice-qr-code.js.map +1 -1
- package/dist/components/qr-code/snice-qr-code.types.d.ts +3 -2
- package/dist/components/slider/snice-slider.js +1 -1
- package/dist/components/slider/snice-slider.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.esm.js +2 -2
- package/dist/index.iife.js +2 -2
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/dist/types/request-options.d.ts +1 -1
- package/docs/ai/api.md +1 -1
- package/docs/ai/components/code-block.md +1 -1
- package/docs/ai/decorators.md +2 -2
- package/docs/ai/patterns.md +17 -6
- package/docs/code-block.md +1 -3
- package/docs/controllers.md +98 -391
- package/docs/elements.md +131 -117
- package/docs/events.md +74 -83
- package/docs/fetcher.md +64 -76
- package/docs/observe.md +13 -33
- package/docs/placards.md +6 -16
- package/docs/request-response.md +171 -693
- package/docs/routing.md +67 -136
- package/package.json +1 -1
- package/docs/migration-v2-to-v3.md +0 -569
package/docs/fetcher.md
CHANGED
|
@@ -22,12 +22,12 @@ import { Router, ContextAwareFetcher } from 'snice';
|
|
|
22
22
|
// Create a fetcher instance
|
|
23
23
|
const fetcher = new ContextAwareFetcher();
|
|
24
24
|
|
|
25
|
-
//
|
|
25
|
+
// Attach JWT to outgoing requests
|
|
26
26
|
fetcher.use('request', function(request, next) {
|
|
27
27
|
// `this` is bound to the Context instance
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
request.headers.set('Authorization', `Bearer ${
|
|
28
|
+
const jwt = this.application.principal?.token;
|
|
29
|
+
if (jwt) {
|
|
30
|
+
request.headers.set('Authorization', `Bearer ${jwt}`);
|
|
31
31
|
}
|
|
32
32
|
return next();
|
|
33
33
|
});
|
|
@@ -43,7 +43,7 @@ fetcher.use('response', async function(response, next) {
|
|
|
43
43
|
// Pass fetcher to Router
|
|
44
44
|
const router = Router({
|
|
45
45
|
target: '#app',
|
|
46
|
-
context: {
|
|
46
|
+
context: { auth: null },
|
|
47
47
|
fetcher
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -101,26 +101,43 @@ type RequestMiddleware = (
|
|
|
101
101
|
- Adding custom headers (CSRF tokens, API keys, etc.)
|
|
102
102
|
- Request validation
|
|
103
103
|
|
|
104
|
-
**Example -
|
|
104
|
+
**Example - JWT Bearer Token:**
|
|
105
|
+
|
|
106
|
+
`this` in middleware is the `Context` instance. Store auth state in `application` (your `AppContext`):
|
|
107
|
+
|
|
105
108
|
```typescript
|
|
109
|
+
// Attach JWT to every request
|
|
106
110
|
fetcher.use('request', function(request, next) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
request.headers.set('Authorization', `Bearer ${
|
|
111
|
+
const jwt = this.application.principal?.token;
|
|
112
|
+
if (jwt) {
|
|
113
|
+
request.headers.set('Authorization', `Bearer ${jwt}`);
|
|
110
114
|
}
|
|
111
115
|
return next();
|
|
112
116
|
});
|
|
113
|
-
```
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
// Handle expired tokens — refresh or redirect to login
|
|
119
|
+
fetcher.use('response', async function(response, next) {
|
|
120
|
+
if (response.status === 401) {
|
|
121
|
+
const refreshToken = this.application.principal?.refreshToken;
|
|
122
|
+
if (refreshToken) {
|
|
123
|
+
const res = await fetch('/api/auth/refresh', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
body: JSON.stringify({ refreshToken })
|
|
127
|
+
});
|
|
128
|
+
if (res.ok) {
|
|
129
|
+
const { token } = await res.json();
|
|
130
|
+
this.application.principal.token = token;
|
|
131
|
+
// Retry original request with new token
|
|
132
|
+
const retry = new Request(response.url, {
|
|
133
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
134
|
+
});
|
|
135
|
+
return fetch(retry);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
this.application.principal = null;
|
|
139
|
+
window.location.hash = '#/login';
|
|
140
|
+
throw new Error('Session expired');
|
|
124
141
|
}
|
|
125
142
|
return next();
|
|
126
143
|
});
|
|
@@ -177,18 +194,19 @@ fetcher.use('response', async function(response, next) {
|
|
|
177
194
|
|
|
178
195
|
**Example - Performance Metrics:**
|
|
179
196
|
```typescript
|
|
180
|
-
const timings = new
|
|
197
|
+
const timings = new Map<string, number>();
|
|
181
198
|
|
|
182
199
|
fetcher.use('request', function(request, next) {
|
|
183
|
-
timings.set(request, Date.now());
|
|
200
|
+
timings.set(request.url, Date.now());
|
|
184
201
|
return next();
|
|
185
202
|
});
|
|
186
203
|
|
|
187
204
|
fetcher.use('response', async function(response, next) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
const start = timings.get(response.url);
|
|
206
|
+
if (start) {
|
|
207
|
+
console.log(`${response.url} took ${Date.now() - start}ms`);
|
|
208
|
+
timings.delete(response.url);
|
|
209
|
+
}
|
|
192
210
|
return next();
|
|
193
211
|
});
|
|
194
212
|
```
|
|
@@ -207,7 +225,7 @@ fetcher.use('response', async function(response, next) {
|
|
|
207
225
|
if (response.status === 401) {
|
|
208
226
|
// Unauthorized - clear user and redirect to login
|
|
209
227
|
this.application.user = null;
|
|
210
|
-
|
|
228
|
+
window.location.hash = '#/login';
|
|
211
229
|
throw new Error('Authentication required');
|
|
212
230
|
}
|
|
213
231
|
|
|
@@ -269,22 +287,20 @@ Here's a complete example with authentication, error handling, and logging:
|
|
|
269
287
|
import { Router, ContextAwareFetcher } from 'snice';
|
|
270
288
|
|
|
271
289
|
interface AppContext {
|
|
272
|
-
|
|
273
|
-
token: string;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
config: {
|
|
277
|
-
apiBaseUrl: string;
|
|
290
|
+
auth?: {
|
|
291
|
+
token: string; // JWT access token
|
|
292
|
+
refreshToken: string;
|
|
293
|
+
userId: string;
|
|
278
294
|
};
|
|
279
295
|
}
|
|
280
296
|
|
|
281
297
|
const fetcher = new ContextAwareFetcher();
|
|
282
298
|
|
|
283
|
-
//
|
|
299
|
+
// Attach JWT to every request
|
|
284
300
|
fetcher.use('request', function(request, next) {
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
287
|
-
request.headers.set('Authorization', `Bearer ${
|
|
301
|
+
const jwt = this.application.principal?.token;
|
|
302
|
+
if (jwt) {
|
|
303
|
+
request.headers.set('Authorization', `Bearer ${jwt}`);
|
|
288
304
|
}
|
|
289
305
|
return next();
|
|
290
306
|
});
|
|
@@ -296,13 +312,12 @@ fetcher.use('request', function(request, next) {
|
|
|
296
312
|
return next();
|
|
297
313
|
});
|
|
298
314
|
|
|
299
|
-
// Handle
|
|
315
|
+
// Handle 401 — attempt token refresh, then redirect on failure
|
|
300
316
|
fetcher.use('response', async function(response, next) {
|
|
301
317
|
if (response.status === 401) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
throw new Error('Authentication required');
|
|
318
|
+
this.application.principal = undefined;
|
|
319
|
+
window.location.hash = '#/login';
|
|
320
|
+
throw new Error('Session expired');
|
|
306
321
|
}
|
|
307
322
|
|
|
308
323
|
if (response.status >= 400) {
|
|
@@ -314,19 +329,9 @@ fetcher.use('response', async function(response, next) {
|
|
|
314
329
|
return next();
|
|
315
330
|
});
|
|
316
331
|
|
|
317
|
-
// Log responses
|
|
318
|
-
fetcher.use('response', async function(response, next) {
|
|
319
|
-
console.log(`[${this.navigation.route}] Response ${response.status}`);
|
|
320
|
-
return next();
|
|
321
|
-
});
|
|
322
|
-
|
|
323
332
|
const router = Router({
|
|
324
333
|
target: '#app',
|
|
325
|
-
context: {
|
|
326
|
-
config: {
|
|
327
|
-
apiBaseUrl: 'https://api.example.com'
|
|
328
|
-
}
|
|
329
|
-
},
|
|
334
|
+
context: {},
|
|
330
335
|
fetcher
|
|
331
336
|
});
|
|
332
337
|
|
|
@@ -343,28 +348,17 @@ The `Context` instance is created once per Router and persists for the entire ap
|
|
|
343
348
|
- `ctx.fetch` is initialized once and reused
|
|
344
349
|
- Middleware can safely reference `this.application` and `this.navigation` as they update in place
|
|
345
350
|
|
|
346
|
-
### Request
|
|
351
|
+
### Modifying Request Headers
|
|
347
352
|
|
|
348
|
-
The `Request` object
|
|
353
|
+
The `Request` object's `headers` property is mutable — you can call `request.headers.set()` directly in middleware:
|
|
349
354
|
|
|
350
355
|
```typescript
|
|
351
356
|
fetcher.use('request', function(request, next) {
|
|
352
|
-
|
|
353
|
-
// request.headers.set('X-Custom', 'value');
|
|
354
|
-
|
|
355
|
-
// Instead, create a new Request
|
|
356
|
-
const newRequest = new Request(request, {
|
|
357
|
-
headers: new Headers(request.headers)
|
|
358
|
-
});
|
|
359
|
-
newRequest.headers.set('X-Custom', 'value');
|
|
360
|
-
|
|
361
|
-
// But this is complex - better to set headers before creating Request
|
|
357
|
+
request.headers.set('X-Custom', 'value');
|
|
362
358
|
return next();
|
|
363
359
|
});
|
|
364
360
|
```
|
|
365
361
|
|
|
366
|
-
In practice, it's better to set headers by modifying the `headers` property if it exists, or reconstructing the request entirely.
|
|
367
|
-
|
|
368
362
|
### No Fetcher Means Native Fetch
|
|
369
363
|
|
|
370
364
|
If no `fetcher` is provided to the Router, `ctx.fetch` defaults to the native `fetch` function bound to the Context instance:
|
|
@@ -424,15 +418,11 @@ interface Fetcher {
|
|
|
424
418
|
}
|
|
425
419
|
```
|
|
426
420
|
|
|
427
|
-
##
|
|
428
|
-
|
|
429
|
-
1. **Configure middleware at startup** - Don't add middleware inside pages or components, as this would add duplicate middleware on each navigation.
|
|
421
|
+
## Notes
|
|
430
422
|
|
|
431
|
-
|
|
423
|
+
- **Configure middleware at startup** — don't add middleware inside pages, as it would duplicate on each navigation.
|
|
432
424
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
4. **Clone responses if needed** - If you need to read the response body in middleware, clone it first as streams can only be read once:
|
|
425
|
+
- **Clone responses before reading** — streams can only be read once:
|
|
436
426
|
```typescript
|
|
437
427
|
fetcher.use('response', async function(response, next) {
|
|
438
428
|
const clone = response.clone();
|
|
@@ -442,6 +432,4 @@ interface Fetcher {
|
|
|
442
432
|
});
|
|
443
433
|
```
|
|
444
434
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
6. **Call `next()`** - Every middleware must call and return `next()` to continue the chain.
|
|
435
|
+
- **Always call `next()`** — every middleware must call and return `next()` to continue the chain.
|
package/docs/observe.md
CHANGED
|
@@ -336,35 +336,24 @@ class ViewportController implements IController {
|
|
|
336
336
|
|
|
337
337
|
## Options
|
|
338
338
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
All observers support these options:
|
|
339
|
+
All observer types share a single options interface. Pass only the fields relevant to the observer type you're using:
|
|
342
340
|
|
|
343
341
|
```typescript
|
|
344
342
|
interface ObserveOptions {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Specific Options
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
// Intersection Observer
|
|
353
|
-
interface IntersectionOptions extends ObserveOptions {
|
|
354
|
-
threshold?: number | number[]; // 0-1 visibility threshold
|
|
343
|
+
// Intersection Observer
|
|
344
|
+
threshold?: number | number[]; // 0-1 visibility percentage
|
|
355
345
|
rootMargin?: string; // Margin around root
|
|
356
346
|
root?: Element | null; // Viewport element
|
|
357
|
-
}
|
|
358
347
|
|
|
359
|
-
// Resize Observer
|
|
360
|
-
interface ResizeOptions extends ObserveOptions {
|
|
348
|
+
// Resize Observer
|
|
361
349
|
box?: 'content-box' | 'border-box'; // Box model to observe
|
|
362
|
-
}
|
|
363
350
|
|
|
364
|
-
// Mutation Observer
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
351
|
+
// Mutation Observer
|
|
352
|
+
subtree?: boolean; // Observe descendants (use with caution)
|
|
353
|
+
maxDepth?: number; // Safety limit for subtree depth
|
|
354
|
+
|
|
355
|
+
// All observers
|
|
356
|
+
throttle?: number; // Throttle callbacks by milliseconds
|
|
368
357
|
}
|
|
369
358
|
```
|
|
370
359
|
|
|
@@ -547,16 +536,11 @@ class Dashboard extends HTMLElement {
|
|
|
547
536
|
}
|
|
548
537
|
```
|
|
549
538
|
|
|
550
|
-
### Form
|
|
539
|
+
### Dynamic Form Fields
|
|
551
540
|
|
|
552
541
|
```typescript
|
|
553
|
-
@element('
|
|
554
|
-
class
|
|
555
|
-
@observe('mutation:attributes:value', 'input, textarea', { throttle: 1000 })
|
|
556
|
-
handleInputChange(mutations: MutationRecord[]) {
|
|
557
|
-
this.saveFormData();
|
|
558
|
-
}
|
|
559
|
-
|
|
542
|
+
@element('dynamic-form')
|
|
543
|
+
class DynamicForm extends HTMLElement {
|
|
560
544
|
@observe('mutation:childList', '.dynamic-fields')
|
|
561
545
|
handleFieldsAdded(mutations: MutationRecord[]) {
|
|
562
546
|
mutations.forEach(mutation => {
|
|
@@ -577,10 +561,6 @@ class AutoSaveForm extends HTMLElement {
|
|
|
577
561
|
`;
|
|
578
562
|
}
|
|
579
563
|
|
|
580
|
-
saveFormData() {
|
|
581
|
-
// Save logic
|
|
582
|
-
}
|
|
583
|
-
|
|
584
564
|
initializeField(field: Element) {
|
|
585
565
|
// Initialize logic
|
|
586
566
|
}
|
package/docs/placards.md
CHANGED
|
@@ -17,7 +17,8 @@ The Placard system allows pages to declare metadata that describes their purpose
|
|
|
17
17
|
Define a placard for your page using the `placard` option in the `@page` decorator:
|
|
18
18
|
|
|
19
19
|
```typescript
|
|
20
|
-
import {
|
|
20
|
+
import { Placard, render, html } from 'snice';
|
|
21
|
+
import { page } from './router'; // page comes from Router(), not from 'snice'
|
|
21
22
|
|
|
22
23
|
const placard: Placard<AppContext> = {
|
|
23
24
|
name: 'dashboard',
|
|
@@ -151,8 +152,9 @@ parent: 'users' // Child of the 'users' page
|
|
|
151
152
|
### Dynamic Visibility
|
|
152
153
|
|
|
153
154
|
**`visibleOn`** (optional)
|
|
154
|
-
- Guard functions
|
|
155
|
-
-
|
|
155
|
+
- Guard functions for layouts to evaluate when building navigation menus
|
|
156
|
+
- The router passes all placards to layouts unfiltered — layouts must check `visibleOn` themselves
|
|
157
|
+
- Reuses the same guard type as route protection
|
|
156
158
|
|
|
157
159
|
```typescript
|
|
158
160
|
visibleOn: [isAuthenticated, hasAdminRole]
|
|
@@ -223,7 +225,7 @@ const settingsPlacard: Placard<AppContext> = {
|
|
|
223
225
|
name: 'user-settings',
|
|
224
226
|
title: 'Settings',
|
|
225
227
|
parent: 'user-profile'
|
|
226
|
-
//
|
|
228
|
+
// Layout can resolve breadcrumbs using parent chain: Users > Profile > Settings
|
|
227
229
|
};
|
|
228
230
|
|
|
229
231
|
// Explicit breadcrumbs
|
|
@@ -387,15 +389,3 @@ class BreadcrumbLayout extends HTMLElement implements Layout {
|
|
|
387
389
|
}
|
|
388
390
|
```
|
|
389
391
|
|
|
390
|
-
## Best Practices
|
|
391
|
-
|
|
392
|
-
1. **Use consistent naming**: Use kebab-case for placard names
|
|
393
|
-
2. **Provide helpful icons**: Visual indicators improve navigation UX
|
|
394
|
-
3. **Set meaningful order**: Lower numbers appear first in navigation
|
|
395
|
-
4. **Use groups wisely**: Group related pages for better organization
|
|
396
|
-
5. **Define search terms**: Help users discover features through search
|
|
397
|
-
6. **Leverage guards**: Use visibleOn to show/hide navigation based on permissions
|
|
398
|
-
7. **Explicit when needed**: Use explicit breadcrumbs for complex hierarchies
|
|
399
|
-
8. **Keep titles short**: Navigation labels should be concise
|
|
400
|
-
9. **Provide tooltips**: Add helpful context for ambiguous page names
|
|
401
|
-
10. **Use parent relationships**: Build hierarchical navigation automatically
|