snice 7.5.0 → 7.7.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/adapters/react/SniceProvider.js +1 -1
- package/adapters/react/SniceRouter.js +1 -1
- package/adapters/react/avatar.d.ts +1 -0
- package/adapters/react/avatar.d.ts.map +1 -1
- package/adapters/react/avatar.js +1 -1
- package/adapters/react/avatar.js.map +1 -1
- package/adapters/react/avatar.tsx +2 -1
- package/adapters/react/calendar.d.ts +4 -0
- package/adapters/react/calendar.d.ts.map +1 -1
- package/adapters/react/calendar.js +1 -1
- package/adapters/react/calendar.js.map +1 -1
- package/adapters/react/calendar.tsx +5 -1
- package/adapters/react/matchRoute.js +68 -2
- package/adapters/react/matchRoute.js.map +1 -1
- package/adapters/react/route-match.d.ts +7 -0
- package/adapters/react/useRequestHandler.js +1 -1
- package/bin/analyzer-contracts.js +60 -2
- package/bin/project-analyzer.js +1262 -118
- package/bin/snice.js +95 -5
- package/custom-elements.json +112 -0
- package/dist/cdn/accordion/snice-accordion.js +1 -1
- package/dist/cdn/action-bar/snice-action-bar.js +1 -1
- package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
- package/dist/cdn/alert/snice-alert.js +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
- package/dist/cdn/availability/snice-availability.js +1 -1
- package/dist/cdn/avatar/README.md +1 -1
- package/dist/cdn/avatar/snice-avatar.js +9 -3
- package/dist/cdn/avatar/snice-avatar.js.map +1 -1
- package/dist/cdn/avatar/snice-avatar.min.js +2 -2
- package/dist/cdn/avatar/snice-avatar.min.js.map +1 -1
- package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
- package/dist/cdn/badge/snice-badge.js +1 -1
- package/dist/cdn/banner/snice-banner.js +1 -1
- package/dist/cdn/binpack/snice-binpack.js +1 -1
- package/dist/cdn/book/snice-book.js +1 -1
- package/dist/cdn/booking/snice-booking.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
- package/dist/cdn/button/snice-button.js +2 -2
- package/dist/cdn/button/snice-button.js.map +1 -1
- package/dist/cdn/button/snice-button.min.js +1 -1
- package/dist/cdn/button/snice-button.min.js.map +1 -1
- package/dist/cdn/calendar/README.md +2 -2
- package/dist/cdn/calendar/snice-calendar.js +562 -40
- package/dist/cdn/calendar/snice-calendar.js.map +1 -1
- package/dist/cdn/calendar/snice-calendar.min.js +28 -1
- package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
- package/dist/cdn/camera/snice-camera.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.js +1 -1
- package/dist/cdn/card/snice-card.js +1 -1
- package/dist/cdn/carousel/snice-carousel.js +1 -1
- package/dist/cdn/cart/snice-cart.js +1 -1
- package/dist/cdn/chart/snice-chart.js +1 -1
- package/dist/cdn/chat/snice-chat.js +1 -1
- package/dist/cdn/checkbox/snice-checkbox.js +1 -1
- package/dist/cdn/chip/snice-chip.js +1 -1
- package/dist/cdn/code-block/snice-code-block.js +1 -1
- package/dist/cdn/color-display/snice-color-display.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.js +1 -1
- package/dist/cdn/command-palette/snice-command-palette.js +1 -1
- package/dist/cdn/comments/snice-comments.js +1 -1
- package/dist/cdn/countdown/snice-countdown.js +1 -1
- package/dist/cdn/cropper/snice-cropper.js +1 -1
- package/dist/cdn/data-card/snice-data-card.js +1 -1
- package/dist/cdn/date-picker/snice-date-picker.js +1 -1
- package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
- package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
- package/dist/cdn/diff/snice-diff.js +1 -1
- package/dist/cdn/divider/snice-divider.js +1 -1
- package/dist/cdn/doc/snice-doc.js +1 -1
- package/dist/cdn/draw/snice-draw.js +1 -1
- package/dist/cdn/drawer/snice-drawer.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.js +1 -1
- package/dist/cdn/estimate/snice-estimate.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.js +1 -1
- package/dist/cdn/flow/snice-flow.js +1 -1
- package/dist/cdn/form-layout/snice-form-layout.js +1 -1
- package/dist/cdn/funnel/snice-funnel.js +1 -1
- package/dist/cdn/gantt/snice-gantt.js +1 -1
- package/dist/cdn/gauge/snice-gauge.js +1 -1
- package/dist/cdn/grid/snice-grid.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.js +1 -1
- package/dist/cdn/image/snice-image.js +1 -1
- package/dist/cdn/input/snice-input.js +1 -1
- package/dist/cdn/invoice/snice-invoice.js +1 -1
- package/dist/cdn/kanban/snice-kanban.js +1 -1
- package/dist/cdn/key-value/snice-key-value.js +1 -1
- package/dist/cdn/kpi/snice-kpi.js +1 -1
- package/dist/cdn/layout/snice-layout.js +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
- package/dist/cdn/link/snice-link.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.js +1 -1
- package/dist/cdn/list/snice-list.js +1 -1
- package/dist/cdn/location/snice-location.js +1 -1
- package/dist/cdn/login/README.md +1 -1
- package/dist/cdn/login/snice-login.js +2 -2
- package/dist/cdn/login/snice-login.js.map +1 -1
- package/dist/cdn/login/snice-login.min.js +1 -1
- package/dist/cdn/login/snice-login.min.js.map +1 -1
- package/dist/cdn/map/snice-map.js +1 -1
- package/dist/cdn/markdown/snice-markdown.js +1 -1
- package/dist/cdn/masonry/snice-masonry.js +1 -1
- package/dist/cdn/menu/snice-menu.js +1 -1
- package/dist/cdn/message-strip/snice-message-strip.js +1 -1
- package/dist/cdn/modal/snice-modal.js +1 -1
- package/dist/cdn/music-player/snice-music-player.js +1 -1
- package/dist/cdn/nav/snice-nav.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.js +1 -1
- package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.js +1 -1
- package/dist/cdn/pagination/snice-pagination.js +1 -1
- package/dist/cdn/paint/snice-paint.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
- package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
- package/dist/cdn/popover/snice-popover.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
- package/dist/cdn/product-card/snice-product-card.js +1 -1
- package/dist/cdn/progress/snice-progress.js +1 -1
- package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
- package/dist/cdn/qr-code/snice-qr-code.js +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
- package/dist/cdn/radio/snice-radio.js +1 -1
- package/dist/cdn/range-slider/snice-range-slider.js +1 -1
- package/dist/cdn/rating/snice-rating.js +1 -1
- package/dist/cdn/receipt/snice-receipt.js +1 -1
- package/dist/cdn/recipe/snice-recipe.js +1 -1
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +706 -267
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +706 -267
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +1 -1
- package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
- package/dist/cdn/select/snice-select.js +3 -1
- package/dist/cdn/select/snice-select.js.map +1 -1
- package/dist/cdn/select/snice-select.min.js +2 -2
- package/dist/cdn/select/snice-select.min.js.map +1 -1
- package/dist/cdn/skeleton/snice-skeleton.js +1 -1
- package/dist/cdn/slider/snice-slider.js +1 -1
- package/dist/cdn/sortable/snice-sortable.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.js +1 -1
- package/dist/cdn/spinner/snice-spinner.js +1 -1
- package/dist/cdn/split-button/snice-split-button.js +1 -1
- package/dist/cdn/split-pane/snice-split-pane.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.js +1 -1
- package/dist/cdn/stat-group/snice-stat-group.js +1 -1
- package/dist/cdn/step-input/snice-step-input.js +1 -1
- package/dist/cdn/stepper/snice-stepper.js +1 -1
- package/dist/cdn/switch/README.md +1 -1
- package/dist/cdn/switch/snice-switch.js +8 -4
- package/dist/cdn/switch/snice-switch.js.map +1 -1
- package/dist/cdn/switch/snice-switch.min.js +8 -4
- package/dist/cdn/switch/snice-switch.min.js.map +1 -1
- package/dist/cdn/table/README.md +2 -2
- package/dist/cdn/table/snice-table.esm.js +77 -5
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +68 -47
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +76 -4
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +53 -32
- package/dist/cdn/table/snice-table.min.js.map +1 -1
- package/dist/cdn/tabs/snice-tabs.js +1 -1
- package/dist/cdn/tag/snice-tag.js +1 -1
- package/dist/cdn/tag-input/snice-tag-input.js +1 -1
- package/dist/cdn/terminal/snice-terminal.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.js +1 -1
- package/dist/cdn/textarea/snice-textarea.js +1 -1
- package/dist/cdn/time-picker/snice-time-picker.js +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
- package/dist/cdn/timeline/snice-timeline.js +1 -1
- package/dist/cdn/timer/snice-timer.js +1 -1
- package/dist/cdn/toast/snice-toast.js +1 -1
- package/dist/cdn/tooltip/snice-tooltip.js +1 -1
- package/dist/cdn/tree/snice-tree.js +1 -1
- package/dist/cdn/treemap/snice-treemap.js +1 -1
- package/dist/cdn/user-card/snice-user-card.js +1 -1
- package/dist/cdn/video-player/snice-video-player.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.js +1 -1
- package/dist/cdn/weather/snice-weather.js +1 -1
- package/dist/cdn/work-order/snice-work-order.js +1 -1
- package/dist/components/avatar/snice-avatar.d.ts +1 -0
- package/dist/components/avatar/snice-avatar.js +8 -2
- package/dist/components/avatar/snice-avatar.js.map +1 -1
- package/dist/components/avatar/snice-avatar.types.d.ts +2 -0
- package/dist/components/button/snice-button.js +1 -1
- package/dist/components/button/snice-button.js.map +1 -1
- package/dist/components/calendar/snice-calendar.d.ts +38 -1
- package/dist/components/calendar/snice-calendar.js +390 -40
- package/dist/components/calendar/snice-calendar.js.map +1 -1
- package/dist/components/calendar/snice-calendar.types.d.ts +63 -0
- package/dist/components/select/snice-select.js +2 -0
- package/dist/components/select/snice-select.js.map +1 -1
- package/dist/components/switch/snice-switch.js +7 -3
- package/dist/components/switch/snice-switch.js.map +1 -1
- package/dist/components/table/snice-table.d.ts +9 -0
- package/dist/components/table/snice-table.js +72 -2
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/table/table-keyboard.js +1 -1
- package/dist/components/table/table-keyboard.js.map +1 -1
- package/dist/index.cjs +703 -264
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +703 -264
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +703 -264
- package/dist/index.iife.js.map +1 -1
- package/dist/parts.d.ts +14 -4
- package/dist/react/SniceProvider.js +1 -1
- package/dist/react/SniceRouter.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/matchRoute.js +68 -2
- package/dist/react/matchRoute.js.map +1 -1
- package/dist/react/route-match.d.ts +7 -0
- package/dist/react/useRequestHandler.js +1 -1
- package/dist/render-errors.d.ts +9 -0
- package/dist/route-match.d.ts +7 -0
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/template-marker.d.ts +5 -0
- package/dist/testing-dom.cjs +1 -1
- package/dist/testing-dom.esm.js +1 -1
- package/dist/testing.d.ts +3 -0
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/dist/types/dispatch-options.d.ts +5 -4
- package/dist/types/event-timing.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/on-options.d.ts +21 -6
- package/dist/utils.d.ts +3 -0
- package/docs/ai/DEVELOPMENT.md +1 -1
- package/docs/ai/api.md +7 -2
- package/docs/ai/cli.md +14 -0
- package/docs/ai/components/avatar.md +1 -0
- package/docs/ai/components/calendar.md +24 -1
- package/docs/ai/components/switch.md +3 -0
- package/docs/ai/components/table.md +1 -1
- package/docs/ai/decorators.md +3 -2
- package/docs/ai/events.md +45 -8
- package/docs/ai/rendering.md +7 -0
- package/docs/ai/routing.md +6 -1
- package/docs/ai/testing.md +17 -0
- package/docs/cli.md +19 -0
- package/docs/components/avatar.md +1 -0
- package/docs/components/calendar.md +112 -2
- package/docs/components/switch.md +7 -1
- package/docs/components/table.md +2 -1
- package/docs/events.md +90 -10
- package/docs/rendering.md +10 -0
- package/docs/routing.md +22 -1
- package/docs/testing.md +19 -0
- package/package.json +2 -2
- package/vscode.html-custom-data.json +28 -0
package/docs/ai/events.md
CHANGED
|
@@ -142,7 +142,8 @@ handleTextInput(event: Event) {}
|
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
- `currentTarget` is the listener's HOST, not the matched element — derive the match with `event.target.closest(selector)`.
|
|
145
|
-
-
|
|
145
|
+
- Delegation matches in both the shadow tree and the light DOM by default; narrow with `light`/`shadow` (see OnOptions). Clicks on content slotted into a matching shadow wrapper match that wrapper.
|
|
146
|
+
- Shadow retargeting: an event crossing a shadow boundary retargets to the shadow host, so a selector never matches a child component's internals. Listen on the container; carry identity in `detail`.
|
|
146
147
|
|
|
147
148
|
Keyboard events with `@on` (`:` notation):
|
|
148
149
|
|
|
@@ -176,7 +177,7 @@ class MyButton extends HTMLElement {
|
|
|
176
177
|
interface OnOptions {
|
|
177
178
|
// Standard event listener options
|
|
178
179
|
capture?: boolean; // Use capture phase instead of bubble phase
|
|
179
|
-
once?: boolean; //
|
|
180
|
+
once?: boolean; // Handler runs exactly once; non-matching selector/key events don't consume it
|
|
180
181
|
passive?: boolean; // Passive listener (can't preventDefault)
|
|
181
182
|
|
|
182
183
|
// Automatic event handling
|
|
@@ -184,17 +185,32 @@ interface OnOptions {
|
|
|
184
185
|
stopPropagation?: boolean; // Automatically call stopPropagation on the event
|
|
185
186
|
|
|
186
187
|
// Timing controls
|
|
187
|
-
debounce?:
|
|
188
|
-
throttle?:
|
|
188
|
+
debounce?: EventTiming;
|
|
189
|
+
throttle?: EventTiming;
|
|
189
190
|
|
|
190
|
-
//
|
|
191
|
-
target?: string; // CSS selector
|
|
191
|
+
// Delegation
|
|
192
|
+
target?: string; // CSS selector for delegation; same as the positional selector argument
|
|
193
|
+
|
|
194
|
+
// Tree toggles — same light/shadow pair as @query; both default to true.
|
|
195
|
+
// Direct: shadow → shadow-root listener, light → host listener.
|
|
196
|
+
// Delegated: which tree(s) the selector matches in.
|
|
197
|
+
// Both false → warn + skip. Ignored (warned) with scope/daemon.
|
|
198
|
+
light?: boolean;
|
|
199
|
+
shadow?: boolean;
|
|
192
200
|
|
|
193
201
|
// Where to attach the listener (see scope below)
|
|
194
202
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
195
203
|
}
|
|
204
|
+
type EventTiming = number | ((this: any) => number);
|
|
196
205
|
```
|
|
197
206
|
|
|
207
|
+
- Number intervals remain supported.
|
|
208
|
+
- Resolver: called with the decorated element/controller as `this` when the
|
|
209
|
+
listener is set up (and set up again after reconnect).
|
|
210
|
+
- Result must be finite and non-negative. `0` disables; invalid/negative/`NaN`
|
|
211
|
+
throws `TypeError`.
|
|
212
|
+
- Use method/function syntax, not an arrow, to read `this`.
|
|
213
|
+
|
|
198
214
|
### scope — listener attachment target
|
|
199
215
|
|
|
200
216
|
Default: host element. `scope` redirects attachment to another target — how Snice expresses cross-cutting events.
|
|
@@ -240,6 +256,11 @@ handleScroll(event: Event) {}
|
|
|
240
256
|
// Debounce input events by 300ms
|
|
241
257
|
@on('input', 'input[type="search"]', { debounce: 300 })
|
|
242
258
|
handleSearch(event: Event) {}
|
|
259
|
+
|
|
260
|
+
@on('input', 'input[type="search"]', {
|
|
261
|
+
debounce() { return this.searchDebounce; }
|
|
262
|
+
})
|
|
263
|
+
handleAdaptiveSearch(event: Event) {}
|
|
243
264
|
```
|
|
244
265
|
|
|
245
266
|
## @dispatch decorator
|
|
@@ -276,13 +297,26 @@ updateStatus(status: string) {
|
|
|
276
297
|
```typescript
|
|
277
298
|
interface DispatchOptions extends EventInit {
|
|
278
299
|
dispatchOnUndefined?: boolean; // Undefined return still dispatches unless false (default: true)
|
|
279
|
-
debounce?:
|
|
280
|
-
throttle?:
|
|
300
|
+
debounce?: EventTiming;
|
|
301
|
+
throttle?: EventTiming;
|
|
281
302
|
// Where to dispatch the event (see scope below)
|
|
282
303
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
283
304
|
}
|
|
284
305
|
```
|
|
285
306
|
|
|
307
|
+
`EventTiming = number | ((this: any) => number)`. A resolver runs against the
|
|
308
|
+
decorated element/controller on every method invocation. Result validation is
|
|
309
|
+
the same as `@on`. Async methods dispatch only after resolution. Disconnect
|
|
310
|
+
drops work queued before disconnect and decorated async invocations that began
|
|
311
|
+
before disconnect. Teardown hooks keep the real element as `this`; calls they
|
|
312
|
+
make after cancellation are new invocations, using current timing state after
|
|
313
|
+
reconnect.
|
|
314
|
+
|
|
315
|
+
Each invocation supersedes that method's pending timed work. Resolved `0`
|
|
316
|
+
cancels an older timer and dispatches the new result immediately. Throttle
|
|
317
|
+
trailing detail is latest-wins; its deadline is last actual dispatch + the
|
|
318
|
+
newly resolved interval.
|
|
319
|
+
|
|
286
320
|
### scope — dispatch target
|
|
287
321
|
|
|
288
322
|
Default: `this.dispatchEvent(event)` — event originates from the host element. `scope` redirects the dispatch to another target so the event behaves as if it originated there. Use with `@on({ scope })` to express cross-cutting events without bubbling.
|
|
@@ -321,6 +355,9 @@ If `scope` cannot resolve (selector matches no ancestor, resolver returns `null`
|
|
|
321
355
|
```typescript
|
|
322
356
|
@dispatch('search-query', { debounce: 300 })
|
|
323
357
|
emitSearch(query: string) { return { query }; }
|
|
358
|
+
|
|
359
|
+
@dispatch('search-query', { debounce() { return this.searchDebounce; } })
|
|
360
|
+
emitAdaptiveSearch(query: string) { return { query }; }
|
|
324
361
|
```
|
|
325
362
|
|
|
326
363
|
### Async methods
|
package/docs/ai/rendering.md
CHANGED
|
@@ -73,6 +73,13 @@ html`${asyncIterable}`
|
|
|
73
73
|
- Promise/AsyncIterable values work directly in node expressions; stale replaced sources are ignored.
|
|
74
74
|
- Async iterators receive a best-effort `return()` on replacement or disconnect. Promise cancellation stays caller-owned.
|
|
75
75
|
|
|
76
|
+
## Authoring diagnostics
|
|
77
|
+
|
|
78
|
+
- Template parse/authoring failures rendered by a Snice element identify the authoritative registered `<tag>` and, when safely available, the class, plus a nearby static-template excerpt when available. Minified CDN constructors may have no class name, so tag-only attribution is expected. Identity is attached to the exact constructor/immediate prototype only after successful `@element`, `@layout`, or Router registration (or an exact existing registration); undecorated subclasses stay generic, while document adoption preserves that identity.
|
|
79
|
+
- Context follows nested/iterable/async `TemplateResult` values in open/closed shadow roots and light DOM.
|
|
80
|
+
- Host-free preparation stays generic; runtime diagnostics do not invent a source filename or component.
|
|
81
|
+
- Contextual errors retain the original error as `cause` (and therefore its stack).
|
|
82
|
+
|
|
76
83
|
## Control flow
|
|
77
84
|
|
|
78
85
|
```typescript
|
package/docs/ai/routing.md
CHANGED
|
@@ -192,7 +192,12 @@ class OverridePage extends HTMLElement {}
|
|
|
192
192
|
|
|
193
193
|
## Route parameters
|
|
194
194
|
|
|
195
|
-
- `:param` segments
|
|
195
|
+
- Named `:param` segments and named splats (`*path`, including optional splats) in string routes and `{ path, order }` routes bind through attributes before `@ready()` fires; normally declare a plain `@property()` field of the same name.
|
|
196
|
+
- The route-param spelling must match the observed attribute after HTML lowercasing. `HTMLElement`: `:articleId` -> plain `articleId` (`articleid`). `SniceElement`: plain `articleId` observes `article-id`, so use `:article-id` or `@property({ attribute: 'articleId' }) articleId`.
|
|
197
|
+
- Explicit alias: `@property({ attribute: 'article-id' }) articleId` binds from `:article-id`, not `:articleId`.
|
|
198
|
+
- `@property({ attribute: false })` opts OUT — Router cannot set it, so it silently keeps its initializer.
|
|
199
|
+
- A reflected native HTMLElement attribute such as `id` is already a binding target when no Snice property overrides it. An explicit `@property({ attribute: false }) id` or differently aliased `id` overrides the native channel and does not bind from `:id`. A custom element can also consume a statically declared `observedAttributes` entry in `attributeChangedCallback`; do not redeclare native IDL properties merely to satisfy the analyzer.
|
|
200
|
+
- A subclass `@state()` member disables an inherited `@property()` channel of the same name. With Snice's stage-3 field transformation, a plain subclass field initializer or authored accessor still runs through the inherited transformed property accessor and remains bindable.
|
|
196
201
|
- Multiple `:param`s all populate independently, e.g. `/posts/:postId/comments/:commentId` → `postId`, `commentId` properties.
|
|
197
202
|
- Query params: declare directly in the route pattern, e.g. `routes: ['/search?q=:query']` — extracted as a route param (`ctx.navigation.params.query`), not parsed from `location.search`.
|
|
198
203
|
|
package/docs/ai/testing.md
CHANGED
|
@@ -42,6 +42,23 @@ Gotcha: awaiting `ready` on an element that never connects hangs — append it t
|
|
|
42
42
|
- `ready` rejects with the first thrown/rejected `@ready()` failure; always await it.
|
|
43
43
|
- jsdom's partial `ElementInternals` is supported; form controls keep native-input/proxy fallback behavior when `setFormValue`/`setValidity` are absent.
|
|
44
44
|
|
|
45
|
+
## Strict render errors
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
try {
|
|
49
|
+
setStrictRenderErrors(true);
|
|
50
|
+
expect(() => { element.invalid = true; }).toThrow(/<my-element> \(MyElement\)/);
|
|
51
|
+
} finally {
|
|
52
|
+
setStrictRenderErrors(false);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Default: log render failure, retain previous DOM. Strict: synchronously rethrow synchronous render failures.
|
|
57
|
+
- Template authoring errors include the authoritative registered tag, an optional safely available class name, and a nearby static-template excerpt when a host exists. Minified CDN output may correctly report only the tag. Attribution is exact-constructor/immediate-prototype metadata recorded after successful Snice registration; undecorated subclasses are generic, and adopted instances retain the registered identity.
|
|
58
|
+
- Host-free preparation stays generic. Runtime does not claim a source filename it cannot know.
|
|
59
|
+
- Original error is `cause`; inspect `cause.stack` for the underlying stack.
|
|
60
|
+
- Promise/AsyncIterable failures happen after the render call and remain `console.error` reports in strict mode; await settlement and assert the `Error` argument.
|
|
61
|
+
|
|
45
62
|
## Partial DOM compatibility
|
|
46
63
|
|
|
47
64
|
In a simulated DOM, opt into Snice's standards compatibility layer from the
|
package/docs/cli.md
CHANGED
|
@@ -52,6 +52,25 @@ npx snice validate # source analyzer only
|
|
|
52
52
|
- deep imports that were never released package paths, such as `snice/decorators`
|
|
53
53
|
- a Router without `target`, `type`, or a project-wide `initialize()` call
|
|
54
54
|
- a routed class combining `@page` with redundant `@element`
|
|
55
|
+
- a path/query `:param` or named `*splat` whose page has no reachable attribute
|
|
56
|
+
target (`snice/route-param-has-no-binding-target`, warning), including
|
|
57
|
+
`attribute: false` and mismatched explicit aliases
|
|
58
|
+
- a controller `@context()` handler that starts load/reload/refresh/fetch work
|
|
59
|
+
without `{ once: true }`, a first-delivery guard, or an update diff
|
|
60
|
+
(`snice/unguarded-context-load`, warning)
|
|
61
|
+
|
|
62
|
+
The route-param check follows proven local declarations, direct relative
|
|
63
|
+
re-exports, and named or namespace Snice imports, so an inherited bindable
|
|
64
|
+
property satisfies it. It deliberately defers a missing-target warning for an
|
|
65
|
+
unresolved/ambiguous base or dynamic route/attribute contract; locally visible
|
|
66
|
+
disabled or mismatched properties are still diagnosed. Native reflected IDL
|
|
67
|
+
attributes such as an unmodified `id`, and statically known custom
|
|
68
|
+
`observedAttributes` handlers, also satisfy the attribute target.
|
|
69
|
+
|
|
70
|
+
The route-param rule is non-blocking because its dependency-free static lexer
|
|
71
|
+
cannot model every valid JavaScript/TypeScript grammar edge. Treat the warning
|
|
72
|
+
as a strong prompt to verify the Router attribute channel; `validate` and
|
|
73
|
+
`check` fail only when another error-level diagnostic is present.
|
|
55
74
|
|
|
56
75
|
It also gives non-blocking architecture suggestions: keep `@page`, `@element`,
|
|
57
76
|
`@controller`, and `@daemon` classes under `src/pages`, `src/components`,
|
|
@@ -22,6 +22,7 @@ Displays a user profile image with automatic fallback to name-based initials or
|
|
|
22
22
|
| `name` | `string` | `''` | User's name (used for initials and color generation) |
|
|
23
23
|
| `size` | `'xs' \| 'small' \| 'medium' \| 'large' \| 'xl' \| 'xxl'` | `'medium'` | Avatar size |
|
|
24
24
|
| `shape` | `'circle' \| 'square' \| 'rounded'` | `'circle'` | Avatar shape |
|
|
25
|
+
| `loading` | `'lazy' \| 'eager'` | `'lazy'` | Image loading strategy; use `eager` for above-the-fold or tiny thumbnail avatars |
|
|
25
26
|
| `fallbackColor` (attr: `fallback-color`) | `string` | `'#ffffff'` | Text color for initials fallback |
|
|
26
27
|
| `fallbackBackground` (attr: `fallback-background`) | `string` | `''` | Background color for initials fallback (overrides auto-color) |
|
|
27
28
|
|
|
@@ -25,6 +25,8 @@ Display and select dates with event support, date restrictions, and locale-aware
|
|
|
25
25
|
| `maxDate` (attr: `max-date`) | `Date \| string` | `''` | Maximum selectable date |
|
|
26
26
|
| `disabledDates` | `(Date \| string)[]` | `[]` | Disabled dates (JS-only; no attribute) |
|
|
27
27
|
| `highlightToday` (attr: `highlight-today`) | `boolean` | `true` | Highlight today's date |
|
|
28
|
+
| `noDaySelect` (attr: `no-day-select`) | `boolean` | `false` | Display-only mode: day clicks neither select/highlight nor fire `calendar-change`; event bars stay interactive |
|
|
29
|
+
| `cellSizing` (attr: `cell-sizing`) | `'square' \| 'stretch'` | `'square'` | `square` keeps day cells as tall as the column is wide; `stretch` lets rows collapse to their content and event-lane reservation |
|
|
28
30
|
| `showWeekNumbers` (attr: `show-week-numbers`) | `boolean` | `false` | Show week numbers |
|
|
29
31
|
| `firstDayOfWeek` (attr: `first-day-of-week`) | `number` | `0` | First day of week (0=Sun, 1=Mon) |
|
|
30
32
|
| `locale` | `string` | `'en-US'` | Locale for formatting |
|
|
@@ -37,9 +39,19 @@ interface CalendarEvent {
|
|
|
37
39
|
title: string;
|
|
38
40
|
start: Date | string;
|
|
39
41
|
end?: Date | string;
|
|
40
|
-
color?: string;
|
|
42
|
+
color?: string; // bar background
|
|
43
|
+
className?: string; // extra class(es) on the bars, also exposed as ::part names
|
|
44
|
+
avatar?: string | CalendarEventAvatar; // string is shorthand for { src }
|
|
45
|
+
tooltip?: string; // static tooltip text for the bars
|
|
46
|
+
popover?: boolean | string | Node | (() => Node); // click-to-open details card (see Event Popovers)
|
|
41
47
|
data?: any;
|
|
42
48
|
}
|
|
49
|
+
|
|
50
|
+
interface CalendarEventAvatar {
|
|
51
|
+
src?: string; // image URL
|
|
52
|
+
name?: string; // initials fallback (rendered with <snice-avatar>)
|
|
53
|
+
alt?: string;
|
|
54
|
+
}
|
|
43
55
|
```
|
|
44
56
|
|
|
45
57
|
## Methods
|
|
@@ -109,10 +121,108 @@ Use the `events` property to display events on the calendar.
|
|
|
109
121
|
```typescript
|
|
110
122
|
calendar.events = [
|
|
111
123
|
{ id: 1, title: 'Team Meeting', start: new Date(2024, 5, 15, 10, 0), color: '#2196f3' },
|
|
112
|
-
{ id: 2, title: 'Project Deadline', start: new Date(2024, 5, 20), color: '#f44336' }
|
|
124
|
+
{ id: 2, title: 'Project Deadline', start: new Date(2024, 5, 20), color: '#f44336' },
|
|
125
|
+
{ id: 3, title: 'Conference', start: new Date(2024, 5, 18), end: new Date(2024, 5, 24), color: '#16a34a' }
|
|
113
126
|
];
|
|
114
127
|
```
|
|
115
128
|
|
|
129
|
+
Events render as continuous stripes, the way professional calendars draw them:
|
|
130
|
+
an event with an `end` date spans all of its days as one bar per week row,
|
|
131
|
+
chopped at week boundaries with squared corners so consecutive rows read as a
|
|
132
|
+
single bar (the title repeats on each row). Concurrent events stack into
|
|
133
|
+
lanes — earlier start first, longer event first on ties. Up to three lanes are
|
|
134
|
+
shown; days with deeper stacks get a `+N more` chip. Each bar exposes a
|
|
135
|
+
`part="event-bar"` for styling and dispatches `calendar-event-click` when
|
|
136
|
+
clicked.
|
|
137
|
+
|
|
138
|
+
Bars are styleable per event: `color` sets the background, `avatar` renders a
|
|
139
|
+
small `<snice-avatar>` at the start of each bar (`part="event-avatar"`) — an
|
|
140
|
+
image when `src` is given, initials from `name` otherwise — and `className` is
|
|
141
|
+
added to the bar's classes **and** its part list, so a specific kind of event
|
|
142
|
+
can be themed from outside the component:
|
|
143
|
+
|
|
144
|
+
```css
|
|
145
|
+
snice-calendar::part(event-bar) { font-weight: 500; }
|
|
146
|
+
snice-calendar::part(urgent) { background: crimson; }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
calendar.events = [
|
|
151
|
+
{ id: 1, title: 'Incident review', start: '2024-06-18', className: 'urgent',
|
|
152
|
+
avatar: { src: '/avatars/sre-lead.png', name: 'Robin Kim' } }
|
|
153
|
+
];
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Event Tooltips
|
|
157
|
+
|
|
158
|
+
A per-event `tooltip` string shows on hover. For rich or lazily-loaded
|
|
159
|
+
content, set the `eventTooltip` provider on the calendar — it runs when the
|
|
160
|
+
pointer enters a bar and may return text, a DOM node, or a promise of either;
|
|
161
|
+
results that resolve after the pointer left are discarded. The provider wins
|
|
162
|
+
over `event.tooltip`. The overlay exposes `part="event-tooltip"`.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
calendar.eventTooltip = async (event) => {
|
|
166
|
+
const details = await fetchEventDetails(event.id);
|
|
167
|
+
const node = document.createElement('div');
|
|
168
|
+
node.innerHTML = `<strong>${event.title}</strong><p>${details.attendees} attendees</p>`;
|
|
169
|
+
return node;
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Event Popovers
|
|
174
|
+
|
|
175
|
+
Tooltips are for glancing; popovers are for interacting. An event that sets
|
|
176
|
+
`popover` opens a click-anchored details card (`role="dialog"`,
|
|
177
|
+
`part="event-popover"`) that holds focus, and closes on Escape (focus returns
|
|
178
|
+
to the bar) or an outside click. `calendar-event-click` still dispatches.
|
|
179
|
+
Popovers are **strictly per-event opt-in** — events without `popover` never
|
|
180
|
+
open a card and never issue a request.
|
|
181
|
+
|
|
182
|
+
Content resolves in this order:
|
|
183
|
+
|
|
184
|
+
1. **Inline** — `popover` is a string, a Node, or a Node factory.
|
|
185
|
+
2. **Provider** — `popover: true` and the element's `eventPopover` callback:
|
|
186
|
+
`(event) => string | Node | Promise<string | Node>`. A loading state shows
|
|
187
|
+
while a promise is pending; results arriving after close are discarded.
|
|
188
|
+
3. **Request channel** — `popover: true` with no provider issues
|
|
189
|
+
`@request('calendar/event-popover')` with `{ event }`; any
|
|
190
|
+
`@respond('calendar/event-popover')` controller can return the content.
|
|
191
|
+
With no responder either, the card closes and a dev warning is logged.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Inline
|
|
195
|
+
calendar.events = [
|
|
196
|
+
{ id: 1, title: 'Standup', start: '2024-06-18', popover: 'Room 4, 10:00' }
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
// Lazy provider
|
|
200
|
+
calendar.events = [{ id: 2, title: 'Conf', start: '2024-06-20', popover: true }];
|
|
201
|
+
calendar.eventPopover = async (event) => detailsCard(await fetchEvent(event.id));
|
|
202
|
+
|
|
203
|
+
// Request channel — the calendar stays generic, a controller owns the data
|
|
204
|
+
@controller('agenda-controller')
|
|
205
|
+
class AgendaController implements IController {
|
|
206
|
+
element: HTMLElement | null = null;
|
|
207
|
+
private ctx!: Context;
|
|
208
|
+
async attach() {}
|
|
209
|
+
async detach() {}
|
|
210
|
+
|
|
211
|
+
@context()
|
|
212
|
+
receiveContext(ctx: Context) { this.ctx = ctx; }
|
|
213
|
+
|
|
214
|
+
@respond('calendar/event-popover')
|
|
215
|
+
async details({ event }: { event: CalendarEvent }) {
|
|
216
|
+
const data = await this.ctx.fetch(`/api/events/${event.id}`).then(r => r.json());
|
|
217
|
+
return renderDetailsCard(data);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Bars with a popover are keyboard-operable (`role="button"`, `tabindex="0"`,
|
|
223
|
+
Enter/Space opens). Close programmatically with
|
|
224
|
+
`calendar.closeEventPopover()`.
|
|
225
|
+
|
|
116
226
|
### Date Restrictions
|
|
117
227
|
|
|
118
228
|
Use `min-date` and `max-date` to constrain the selectable date range.
|
|
@@ -64,6 +64,8 @@ A toggle switch input for boolean selections. Supports on/off labels, form integ
|
|
|
64
64
|
| `thumb` | Switch thumb |
|
|
65
65
|
| `spinner` | Loading spinner |
|
|
66
66
|
| `label` | Label text |
|
|
67
|
+
| `label-on` | On-state text inside the track (renders when `label-on` is set) |
|
|
68
|
+
| `label-off` | Off-state text inside the track (renders when `label-off` is set) |
|
|
67
69
|
|
|
68
70
|
## Basic Usage
|
|
69
71
|
|
|
@@ -102,10 +104,14 @@ Use `size` to change the switch dimensions.
|
|
|
102
104
|
|
|
103
105
|
### With On/Off Labels
|
|
104
106
|
|
|
105
|
-
Use `label-on` and `label-off` for text inside the switch track.
|
|
107
|
+
Use `label-on` and `label-off` for text inside the switch track. The track
|
|
108
|
+
sizes itself to the widest label (medium and large sizes; small hides state
|
|
109
|
+
labels), and the thumb's travel follows the actual track width — so long
|
|
110
|
+
labels and `::part(track)` width overrides both stay aligned.
|
|
106
111
|
|
|
107
112
|
```html
|
|
108
113
|
<snice-switch label="Dark mode" label-on="ON" label-off="OFF"></snice-switch>
|
|
114
|
+
<snice-switch label="Door" label-on="Open" label-off="Closed"></snice-switch>
|
|
109
115
|
```
|
|
110
116
|
|
|
111
117
|
### Form Integration
|
package/docs/components/table.md
CHANGED
|
@@ -153,7 +153,8 @@ Column capability flags default to enabled unless explicitly set to `false` when
|
|
|
153
153
|
`formatter` is the row-aware display override for every built-in cell.
|
|
154
154
|
`valueFormatter` is the fallback display formatter and is also used by the
|
|
155
155
|
editing pipeline, aggregate output, and formatted clipboard export.
|
|
156
|
-
`valueGetter`
|
|
156
|
+
`valueGetter` derives the working value for cell display, local sorting, and
|
|
157
|
+
aggregation. `valueParser` and
|
|
157
158
|
`valueSetter` run during editing; a setter may return either the final field
|
|
158
159
|
value or an updated row object.
|
|
159
160
|
|
package/docs/events.md
CHANGED
|
@@ -354,17 +354,23 @@ class ListController implements IController {
|
|
|
354
354
|
}
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
Three delegation rules worth knowing:
|
|
358
358
|
|
|
359
359
|
- **`currentTarget` is the listener's host, not the matched element.** Inside
|
|
360
360
|
`@on('click', '.delete-button')` the handler receives the raw event, so
|
|
361
361
|
derive the match with `event.target.closest('.delete-button')` when you need
|
|
362
362
|
the element itself (as above).
|
|
363
|
+
- **Delegation matches in both trees by default.** The selector matches
|
|
364
|
+
elements in the component's shadow tree and its light-DOM children. A click
|
|
365
|
+
on content slotted into a matching shadow wrapper matches that wrapper.
|
|
366
|
+
Narrow the search with the `light`/`shadow` options — the same tree toggles
|
|
367
|
+
`@query` uses (see [@on Options](#on-options)).
|
|
363
368
|
- **Shadow DOM retargeting changes what the selector matches.** An event
|
|
364
369
|
crossing a shadow boundary is retargeted to the shadow host, so
|
|
365
370
|
`@on('row-clicked', 'my-row')` stops matching when rows move into a list
|
|
366
|
-
component — from outside, the event's target is the list host.
|
|
367
|
-
|
|
371
|
+
component — from outside, the event's target is the list host. A selector
|
|
372
|
+
never matches a child component's internals. Listen on the container and
|
|
373
|
+
carry the row identity in the event `detail` instead.
|
|
368
374
|
|
|
369
375
|
### Keyboard Events with @on
|
|
370
376
|
|
|
@@ -410,7 +416,7 @@ class EditorController implements IController {
|
|
|
410
416
|
interface OnOptions {
|
|
411
417
|
// Standard event listener options
|
|
412
418
|
capture?: boolean; // Use capture phase instead of bubble phase
|
|
413
|
-
once?: boolean; //
|
|
419
|
+
once?: boolean; // Handler runs exactly once; non-matching selector/key events don't consume it
|
|
414
420
|
passive?: boolean; // Passive listener (can't preventDefault)
|
|
415
421
|
|
|
416
422
|
// Automatic event handling
|
|
@@ -418,11 +424,15 @@ interface OnOptions {
|
|
|
418
424
|
stopPropagation?: boolean; // Automatically call stopPropagation on the event
|
|
419
425
|
|
|
420
426
|
// Timing controls
|
|
421
|
-
debounce?:
|
|
422
|
-
throttle?:
|
|
427
|
+
debounce?: EventTiming; // Debounce the handler
|
|
428
|
+
throttle?: EventTiming; // Throttle the handler
|
|
423
429
|
|
|
424
|
-
//
|
|
425
|
-
target?: string; // CSS selector
|
|
430
|
+
// Delegation
|
|
431
|
+
target?: string; // CSS selector for delegation; same as the positional selector argument
|
|
432
|
+
|
|
433
|
+
// Tree toggles — the same light/shadow pair @query uses; both default to true
|
|
434
|
+
light?: boolean; // Listen in the light DOM (host element + light children)
|
|
435
|
+
shadow?: boolean; // Listen in the shadow tree (the component's shadow root)
|
|
426
436
|
|
|
427
437
|
// Where to attach the listener (see scope section below)
|
|
428
438
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
@@ -430,8 +440,39 @@ interface OnOptions {
|
|
|
430
440
|
// Named daemon from the nearest provided app context
|
|
431
441
|
daemon?: string;
|
|
432
442
|
}
|
|
443
|
+
|
|
444
|
+
type EventTiming = number | ((this: any) => number);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Numeric intervals remain supported. A resolver can instead read a value from
|
|
448
|
+
the decorated element or controller instance. For `@on`, it runs when the
|
|
449
|
+
managed listener is set up and again when it is set up after reconnect. The
|
|
450
|
+
resolved value must be a finite, non-negative number; `0` disables that timing
|
|
451
|
+
option, while invalid, negative, and `NaN` values throw `TypeError`.
|
|
452
|
+
|
|
453
|
+
#### light / shadow — choosing the tree
|
|
454
|
+
|
|
455
|
+
`light` and `shadow` are the same tree toggles `@query` uses. Both default to
|
|
456
|
+
`true`, so `@on` hears events from the shadow tree and the light DOM alike.
|
|
457
|
+
Set one to `false` to narrow the listener:
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
// Only the component's shadow tree
|
|
461
|
+
@on('click', '.item', { light: false })
|
|
462
|
+
onShadowItem(e: MouseEvent) { /* ... */ }
|
|
463
|
+
|
|
464
|
+
// Only light-DOM children (and the host itself)
|
|
465
|
+
@on('click', '.item', { shadow: false })
|
|
466
|
+
onLightItem(e: MouseEvent) { /* ... */ }
|
|
433
467
|
```
|
|
434
468
|
|
|
469
|
+
For direct handlers the flags choose where the listener attaches: `shadow`
|
|
470
|
+
controls the shadow-root listener, `light` controls the host listener. For
|
|
471
|
+
delegated handlers they choose which tree(s) the selector matches in. Setting
|
|
472
|
+
both to `false` warns and skips the listener, and the flags are ignored (with
|
|
473
|
+
a warning) when `scope` or `daemon` is set — those own the listener target
|
|
474
|
+
outright.
|
|
475
|
+
|
|
435
476
|
#### scope — controlling the listener target
|
|
436
477
|
|
|
437
478
|
By default, `@on` attaches the listener to the host element. The `scope` option redirects
|
|
@@ -522,6 +563,19 @@ class SearchController implements IController {
|
|
|
522
563
|
}
|
|
523
564
|
```
|
|
524
565
|
|
|
566
|
+
Per-instance interval:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
@on('input', 'input[type="search"]', {
|
|
570
|
+
debounce() { return this.searchDebounce; }
|
|
571
|
+
})
|
|
572
|
+
handleSearch(event: Event) { /* ... */ }
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Use method syntax (or a normal function), not an arrow, when reading `this`.
|
|
576
|
+
Controllers receive the controller instance as `this`; elements receive the
|
|
577
|
+
element instance.
|
|
578
|
+
|
|
525
579
|
### Using @on in Elements (Alternative)
|
|
526
580
|
|
|
527
581
|
While template syntax is preferred, `@on` can also be used in elements:
|
|
@@ -623,8 +677,8 @@ class StatusIndicator extends HTMLElement {
|
|
|
623
677
|
```typescript
|
|
624
678
|
interface DispatchOptions extends EventInit {
|
|
625
679
|
dispatchOnUndefined?: boolean; // Undefined return still dispatches unless false (default: true)
|
|
626
|
-
debounce?:
|
|
627
|
-
throttle?:
|
|
680
|
+
debounce?: EventTiming; // Debounce dispatch
|
|
681
|
+
throttle?: EventTiming; // Throttle dispatch
|
|
628
682
|
// Where to dispatch the event (see scope section below)
|
|
629
683
|
scope?: 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null);
|
|
630
684
|
// Named daemon from the nearest provided app context
|
|
@@ -632,6 +686,23 @@ interface DispatchOptions extends EventInit {
|
|
|
632
686
|
}
|
|
633
687
|
```
|
|
634
688
|
|
|
689
|
+
`EventTiming` is the same `number | ((this: any) => number)` type used by
|
|
690
|
+
`@on`. `@dispatch` resolves it against the decorated instance on every method
|
|
691
|
+
invocation, so an element or controller can change its interval at runtime.
|
|
692
|
+
The same finite, non-negative validation applies. Debounced async methods still
|
|
693
|
+
dispatch only after the method resolves. Disconnect synchronously drops timed
|
|
694
|
+
dispatches already queued and decorated async invocations that started before
|
|
695
|
+
disconnect. Lifecycle hooks always run with the real element as `this`; an
|
|
696
|
+
`@dispatch` method called by teardown code after that cancellation is a new,
|
|
697
|
+
ordinary invocation (and, after reconnect, participates in the current timing
|
|
698
|
+
state).
|
|
699
|
+
|
|
700
|
+
Each invocation supersedes pending timed work for that decorated method. A
|
|
701
|
+
resolved `0` therefore cancels an older debounce/throttle timer and dispatches
|
|
702
|
+
the new result without delay. For throttle, a suppressed invocation replaces
|
|
703
|
+
the trailing detail and recalculates its deadline as the last actual dispatch
|
|
704
|
+
plus the newly resolved interval.
|
|
705
|
+
|
|
635
706
|
#### scope — controlling the dispatch target
|
|
636
707
|
|
|
637
708
|
By default, `@dispatch` originates from the element or a controller's host; on a
|
|
@@ -696,6 +767,15 @@ class SearchBox extends HTMLElement {
|
|
|
696
767
|
}
|
|
697
768
|
```
|
|
698
769
|
|
|
770
|
+
```typescript
|
|
771
|
+
@dispatch('search-query', {
|
|
772
|
+
debounce() { return this.searchDebounce; }
|
|
773
|
+
})
|
|
774
|
+
emitSearch(query: string) {
|
|
775
|
+
return { query };
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
699
779
|
### Async Methods
|
|
700
780
|
|
|
701
781
|
`@dispatch` works with async methods — the event dispatches after the promise resolves:
|
package/docs/rendering.md
CHANGED
|
@@ -51,6 +51,16 @@ isSafeUrl(objectUrl, { allowed: ['blob:'] });
|
|
|
51
51
|
|
|
52
52
|
By default, relative references and absolute `http:`, `https:`, `mailto:`, and `tel:` URLs are accepted. Network-path references must resolve to an allowed protocol. Malformed URLs, raw ASCII control characters, and every other explicit scheme are rejected. Passing `allowed` replaces the absolute-protocol list but does not disable relative references. `snice-button` applies this policy automatically to its `href` property.
|
|
53
53
|
|
|
54
|
+
### Authoring diagnostics
|
|
55
|
+
|
|
56
|
+
Malformed declarative syntax fails when its `TemplateResult` is prepared for rendering. When the template belongs to a Snice element, the error identifies the owning host by its authoritative registered tag and, when safely available, its JavaScript class, then includes a nearby static-template excerpt where possible:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
snice: render failed for <user-editor> (UserEditor): ... Near "<button ${…}>...".
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Minified CDN builds commonly strip constructor names, so a tag-only identity such as `<user-editor>` is normal and intentional. Identity is recorded only after a successful Snice `@element`, `@layout`, or Router page registration (or when that exact constructor is already registered). It belongs to that exact constructor and immediate prototype: an undecorated subclass stays generic, while an instance adopted into another document keeps its original registered identity. This context follows nested templates, keyed/iterable templates, promises, and async iterables, including templates rendered into open or closed shadow roots and light DOM. A template prepared without a component render host keeps the generic authoring error and nearby excerpt; Snice does not invent a component, source filename, or callsite it cannot know at runtime. Contextual errors retain the original error as `cause`, so its stack remains available for debugging.
|
|
63
|
+
|
|
54
64
|
## Bindings
|
|
55
65
|
|
|
56
66
|
This section is the quick syntax overview. See [Binding Channels](./bindings.md) for the complete value, cleanup, event, spread, sentinel, and form-control semantics.
|
package/docs/routing.md
CHANGED
|
@@ -447,11 +447,32 @@ window.history.go(-2);
|
|
|
447
447
|
### Accessing Parameters
|
|
448
448
|
|
|
449
449
|
Route parameters are automatically mapped to element properties — a `:param`
|
|
450
|
-
segment
|
|
450
|
+
segment or named splat such as `*path` (including an optional splat) normally
|
|
451
|
+
binds to a **plain `@property()`** of the same name. A field declared
|
|
451
452
|
`@property({ attribute: false })` is opted OUT of route-param binding: the
|
|
452
453
|
Router never sets it and it silently keeps its initializer. (The Router sets
|
|
453
454
|
params through attributes; `attribute: false` fields have none.)
|
|
454
455
|
|
|
456
|
+
The parameter spelling must reach the property's observed attribute. On an
|
|
457
|
+
`HTMLElement` page, `:articleId` reaches a plain `articleId` property through
|
|
458
|
+
the lowercased `articleid` attribute. A `SniceElement` page uses kebab-case
|
|
459
|
+
implicit attributes, so its plain `articleId` property needs `:article-id` (or
|
|
460
|
+
an explicit `@property({ attribute: 'articleId' })`). Explicit aliases follow
|
|
461
|
+
the same rule: `@property({ attribute: 'article-id' }) articleId` binds from
|
|
462
|
+
`:article-id`, not `:articleId`.
|
|
463
|
+
|
|
464
|
+
A reflected native HTMLElement attribute such as `id` is already reachable
|
|
465
|
+
when no Snice property overrides it. An explicit
|
|
466
|
+
`@property({ attribute: false }) id` or differently aliased `id` overrides that
|
|
467
|
+
native channel, so `:id` no longer populates the property. A statically declared
|
|
468
|
+
`observedAttributes` entry paired with `attributeChangedCallback` can also
|
|
469
|
+
consume a route attribute.
|
|
470
|
+
|
|
471
|
+
Inheritance follows Snice's decorator transformation: a subclass `@state()`
|
|
472
|
+
member disables an inherited `@property()` channel of the same name, while a
|
|
473
|
+
plain field initializer or authored accessor still uses the inherited
|
|
474
|
+
transformed property accessor and remains bindable.
|
|
475
|
+
|
|
455
476
|
```typescript
|
|
456
477
|
@page({
|
|
457
478
|
tag: 'article-page',
|
package/docs/testing.md
CHANGED
|
@@ -47,6 +47,25 @@ element. Snice form controls also retain their native-input/proxy fallback in
|
|
|
47
47
|
DOM runners such as jsdom that expose only the ARIA subset of
|
|
48
48
|
`ElementInternals`.
|
|
49
49
|
|
|
50
|
+
## Strict Render Errors
|
|
51
|
+
|
|
52
|
+
Rendering logs failures by default and retains the previous DOM. Tests that need a synchronous render failure to fail the assertion directly can temporarily enable strict mode:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { setStrictRenderErrors } from 'snice';
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
setStrictRenderErrors(true);
|
|
59
|
+
expect(() => { element.invalid = true; }).toThrow(/<my-element> \(MyElement\)/);
|
|
60
|
+
} finally {
|
|
61
|
+
setStrictRenderErrors(false);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Template parse and authoring errors identify the owning component by its authoritative registered tag and, when safely available, its class, and include a nearby static-template excerpt. Minified CDN constructors may have no class name, so tests should accept tag-only attribution. Attribution requires the exact constructor and immediate prototype successfully registered by `@element`, `@layout`, or Router (or that exact constructor already present in the registry): undecorated subclasses stay generic, and document adoption does not change an instance's captured registration identity. The original error is retained as `cause`, so assertions and debugging can inspect its stack. Snice cannot recover a source filename from a runtime tagged-template value and does not fabricate one. A template prepared outside a component render therefore keeps the generic nearby-template diagnostic.
|
|
66
|
+
|
|
67
|
+
Promise and async-iterable values settle after the synchronous render call has returned, so their failures are reported through `console.error` even while strict mode is enabled. Spy on `console.error`, wait for the deferred value to settle, and assert against the `Error` argument; it carries the same owning-component context.
|
|
68
|
+
|
|
50
69
|
## Partial DOM Compatibility
|
|
51
70
|
|
|
52
71
|
In a simulated DOM, opt into Snice's standards compatibility layer from your
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snice",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
"build:distribution": "rm -rf dist && npm run generate:metadata && rollup -c && npm run build:types && node tooling/build/embed-sourcemap-sources.mjs dist",
|
|
118
118
|
"build:types": "tsc --project packages/components/tsconfig.json --emitDeclarationOnly && cp packages/components/custom-elements.d.ts dist/components/custom-elements.d.ts",
|
|
119
119
|
"prebuild:cdn": "test -f dist/index.esm.js || npm run build:distribution",
|
|
120
|
-
"build:cdn": "
|
|
120
|
+
"build:cdn": "node tooling/build/parallel-cdn-build.mjs && node tooling/website/sync-component-assets.js && node tooling/website/update-cdn-sizes.js",
|
|
121
121
|
"prebuild:react": "test -f dist/index.d.ts || npm run build:distribution",
|
|
122
122
|
"build:react": "npm run generate:react-adapters && tsc --project adapters/react/tsconfig.json && node tooling/build/embed-sourcemap-sources.mjs adapters/react",
|
|
123
123
|
"build:testing": "rollup -c rollup.config.test.js && node tooling/build/embed-sourcemap-sources.mjs dist",
|