snice 7.8.0 → 7.9.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/README.md +1 -1
- package/adapters/react/SniceProvider.js +1 -1
- package/adapters/react/SniceRouter.js +1 -1
- package/adapters/react/matchRoute.js +1 -1
- package/adapters/react/table.d.ts +1 -0
- package/adapters/react/table.d.ts.map +1 -1
- package/adapters/react/table.js +1 -1
- package/adapters/react/table.js.map +1 -1
- package/adapters/react/table.tsx +2 -1
- package/adapters/react/useRequestHandler.js +1 -1
- package/bin/analyzer-contracts.js +17 -1
- package/custom-elements.json +57 -3
- 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/snice-avatar.js +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 +1 -1
- package/dist/cdn/calendar/README.md +2 -2
- package/dist/cdn/calendar/snice-calendar.js +537 -44
- package/dist/cdn/calendar/snice-calendar.js.map +1 -1
- package/dist/cdn/calendar/snice-calendar.min.js +2 -2
- 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/README.md +2 -2
- package/dist/cdn/code-block/snice-code-block.js +177 -167
- 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-picker/snice-color-picker.js +1 -1
- package/dist/cdn/command-palette/README.md +1 -1
- package/dist/cdn/command-palette/snice-command-palette.js +2 -2
- package/dist/cdn/command-palette/snice-command-palette.js.map +1 -1
- package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
- package/dist/cdn/command-palette/snice-command-palette.min.js.map +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/snice-login.js +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 +2 -2
- package/dist/cdn/progress/snice-progress.js.map +1 -1
- package/dist/cdn/progress/snice-progress.min.js +1 -1
- package/dist/cdn/progress/snice-progress.min.js.map +1 -1
- package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
- package/dist/cdn/qr-code/README.md +2 -2
- package/dist/cdn/qr-code/snice-qr-code.js +19 -3
- package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
- package/dist/cdn/qr-code/snice-qr-code.min.js +2 -2
- 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/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 +5 -2
- package/dist/cdn/receipt/snice-receipt.js.map +1 -1
- package/dist/cdn/receipt/snice-receipt.min.js +1 -1
- package/dist/cdn/receipt/snice-receipt.min.js.map +1 -1
- package/dist/cdn/recipe/snice-recipe.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.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.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/README.md +2 -2
- package/dist/cdn/select/snice-select.js +6 -9
- 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/snice-switch.js +1 -1
- package/dist/cdn/table/README.md +2 -2
- package/dist/cdn/table/snice-table.esm.js +1940 -407
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +242 -53
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +1940 -407
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +234 -45
- 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/calendar/snice-calendar.d.ts +187 -5
- package/dist/components/calendar/snice-calendar.js +537 -44
- package/dist/components/calendar/snice-calendar.js.map +1 -1
- package/dist/components/calendar/snice-calendar.types.d.ts +2 -1
- package/dist/components/code-block/formatter.d.ts +6 -0
- package/dist/components/code-block/formatter.js +174 -164
- package/dist/components/code-block/formatter.js.map +1 -1
- package/dist/components/code-block/grammars/snice.json +1 -1
- package/dist/components/code-block/grammars/typescript.json +1 -1
- package/dist/components/command-palette/snice-command-palette.js +1 -1
- package/dist/components/command-palette/snice-command-palette.js.map +1 -1
- package/dist/components/progress/snice-progress.js +1 -1
- package/dist/components/progress/snice-progress.js.map +1 -1
- package/dist/components/qr-code/qrcode.d.ts +1 -1
- package/dist/components/qr-code/qrcode.js +18 -2
- package/dist/components/qr-code/qrcode.js.map +1 -1
- package/dist/components/receipt/snice-receipt.js +4 -1
- package/dist/components/receipt/snice-receipt.js.map +1 -1
- package/dist/components/select/snice-select.d.ts +1 -1
- package/dist/components/select/snice-select.js +5 -8
- package/dist/components/select/snice-select.js.map +1 -1
- package/dist/components/table/snice-cell-actions.js +1 -1
- package/dist/components/table/snice-cell-actions.js.map +1 -1
- package/dist/components/table/snice-cell-color.js +1 -1
- package/dist/components/table/snice-cell-color.js.map +1 -1
- package/dist/components/table/snice-cell-date.js +20 -8
- package/dist/components/table/snice-cell-date.js.map +1 -1
- package/dist/components/table/snice-cell-email.js +1 -1
- package/dist/components/table/snice-cell-email.js.map +1 -1
- package/dist/components/table/snice-cell-image.js +1 -1
- package/dist/components/table/snice-cell-image.js.map +1 -1
- package/dist/components/table/snice-cell-json.js +1 -1
- package/dist/components/table/snice-cell-json.js.map +1 -1
- package/dist/components/table/snice-cell-link.js +1 -1
- package/dist/components/table/snice-cell-link.js.map +1 -1
- package/dist/components/table/snice-cell-location.js +1 -1
- package/dist/components/table/snice-cell-location.js.map +1 -1
- package/dist/components/table/snice-cell-phone.js +1 -1
- package/dist/components/table/snice-cell-phone.js.map +1 -1
- package/dist/components/table/snice-cell-status.js +1 -1
- package/dist/components/table/snice-cell-status.js.map +1 -1
- package/dist/components/table/snice-cell.d.ts +10 -0
- package/dist/components/table/snice-cell.js +59 -13
- package/dist/components/table/snice-cell.js.map +1 -1
- package/dist/components/table/snice-row.d.ts +0 -1
- package/dist/components/table/snice-row.js +4 -10
- package/dist/components/table/snice-row.js.map +1 -1
- package/dist/components/table/snice-table.d.ts +170 -2
- package/dist/components/table/snice-table.js +868 -189
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/table/snice-table.types.d.ts +17 -0
- package/dist/components/table/table-cell-presentation.d.ts +17 -4
- package/dist/components/table/table-cell-presentation.js +37 -11
- package/dist/components/table/table-cell-presentation.js.map +1 -1
- package/dist/components/table/table-column-manager.d.ts +153 -2
- package/dist/components/table/table-column-manager.js +364 -43
- package/dist/components/table/table-column-manager.js.map +1 -1
- package/dist/components/table/table-date.d.ts +29 -0
- package/dist/components/table/table-date.js +78 -0
- package/dist/components/table/table-date.js.map +1 -0
- package/dist/components/table/table-export.js +6 -2
- package/dist/components/table/table-export.js.map +1 -1
- package/dist/components/table/table-filter-engine.d.ts +9 -0
- package/dist/components/table/table-filter-engine.js +58 -14
- package/dist/components/table/table-filter-engine.js.map +1 -1
- package/dist/components/table/table-keyboard.d.ts +8 -0
- package/dist/components/table/table-keyboard.js +14 -3
- package/dist/components/table/table-keyboard.js.map +1 -1
- package/dist/components/table/table-toolbar.js +1 -0
- package/dist/components/table/table-toolbar.js.map +1 -1
- package/dist/components/table/table-tree-data.d.ts +2 -0
- package/dist/components/table/table-tree-data.js +35 -9
- package/dist/components/table/table-tree-data.js.map +1 -1
- package/dist/components/table/table-virtualizer.d.ts +44 -7
- package/dist/components/table/table-virtualizer.js +101 -54
- package/dist/components/table/table-virtualizer.js.map +1 -1
- package/dist/components/theme/theme.css +9 -0
- package/dist/index.cjs +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.iife.js +1 -1
- 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 +1 -1
- package/dist/react/useRequestHandler.js +1 -1
- package/dist/symbols.cjs +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/testing-dom.cjs +1 -1
- package/dist/testing-dom.esm.js +1 -1
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/docs/ai/DEVELOPMENT.md +93 -2
- package/docs/ai/components/calendar.md +113 -10
- package/docs/ai/components/code-block.md +4 -0
- package/docs/ai/components/doc.md +1 -1
- package/docs/ai/components/progress.md +1 -0
- package/docs/ai/components/receipt.md +1 -1
- package/docs/ai/components/table.md +29 -30
- package/docs/ai/components/theme.md +1 -0
- package/docs/ai/events.md +30 -0
- package/docs/ai/theme.md +1 -0
- package/docs/components/calendar.md +171 -11
- package/docs/components/code-block.md +2 -0
- package/docs/components/doc.md +1 -1
- package/docs/components/progress.md +13 -0
- package/docs/components/receipt.md +1 -0
- package/docs/components/table.md +274 -15
- package/docs/components/theme.md +5 -0
- package/docs/events.md +49 -0
- package/docs/plans/2026-03-06-date-range-picker-plan.md +4 -4
- package/docs/plans/2026-03-07-table-mui-parity-design.md +1 -1
- package/docs/plans/2026-03-09-action-bar-plan.md +11 -11
- package/docs/superpowers/specs/2026-07-09-table-incredible-design.md +1 -1
- package/docs/superpowers/specs/2026-07-09-table-phase0-implementation.md +2 -2
- package/package.json +3 -1
- package/vscode.html-custom-data.json +13 -1
package/docs/components/table.md
CHANGED
|
@@ -51,7 +51,7 @@ The imperative `columns` and `data` properties are the most complete path. Decla
|
|
|
51
51
|
| `searchText` | `string` | `''` | Current legacy/controller search text. This is a plain JS field so typing does not rerender and steal focus |
|
|
52
52
|
| `searchDebounce` (attr: `search-debounce`) | `number` | `500` | Delay in milliseconds before the legacy search control requests data |
|
|
53
53
|
| `currentSort` | `Array<{ column: string; direction: 'asc' \| 'desc' }>` | `[]` | Reactive JS-only sort model. Assignment sorts locally or requests remote data |
|
|
54
|
-
| `selectedRows` | `number[]` | `[]` | Reactive JS-only indices into the raw `data` array, not indices into a filtered or sorted page |
|
|
54
|
+
| `selectedRows` | `number[]` | `[]` | Reactive JS-only indices into the raw `data` array, not indices into a filtered or sorted page. Reordering the same rows (a local sort, or a re-delivery of the same row objects in another order) re-resolves the indices so the selection stays on the rows the user picked |
|
|
55
55
|
| `selectionMode` (attr: `selection-mode`) | `'none' \| 'single' \| 'multiple'` | `'multiple'` | Selection model. `'none'` removes selection controls; `'single'` shows row checkboxes without select-all; `'multiple'` enables row, range, group, and select-all selection |
|
|
56
56
|
| `selector` | `string` | `''` | Comma-joined value from the legacy filter selector and the value sent in remote requests |
|
|
57
57
|
| `selectorOptions` | `Array<{ value: string; label: string }>` | `[]` | JS-only options for the legacy `filterable` selector |
|
|
@@ -69,6 +69,7 @@ The imperative `columns` and `data` properties are the most complete path. Decla
|
|
|
69
69
|
| `editable` | `boolean` | `false` | Enables the inline editing engine |
|
|
70
70
|
| `editMode` (attr: `edit-mode`) | `'cell' \| 'row'` | `'cell'` | Edits one cell or every editable cell in a row |
|
|
71
71
|
| `density` | `'compact' \| 'standard' \| 'comfortable'` | `'standard'` | Changes header and cell padding, rerenders rows, and emits `density-change` after post-mount assignment |
|
|
72
|
+
| `columnFit` (attr: `column-fit`) | `'scroll' \| 'squish'` | `'scroll'` | How columns relate to the frame's width. `scroll` keeps every column at or above its `minWidth` and lets the frame scroll horizontally; `squish` relaxes the minimums so the columns always fit the frame and never scroll sideways. See [How a column gets its width](#how-a-column-gets-its-width) |
|
|
72
73
|
| `columnResize` (attr: `column-resize`) | `boolean` | `false` | Adds draggable resize handles to resizable columns |
|
|
73
74
|
| `headerFilters` (attr: `header-filters`) | `boolean` | `false` | Adds debounced contains inputs below filterable headers |
|
|
74
75
|
| `quickFilter` (attr: `quick-filter`) | `boolean` | `false` | Shows a debounced local/remote quick-filter input backed by the same model as `setQuickFilter(text)` |
|
|
@@ -152,12 +153,45 @@ Column capability flags default to enabled unless explicitly set to `false` when
|
|
|
152
153
|
|
|
153
154
|
`formatter` is the row-aware display override for every built-in cell.
|
|
154
155
|
`valueFormatter` is the fallback display formatter and is also used by the
|
|
155
|
-
editing pipeline, aggregate output, and formatted clipboard export.
|
|
156
|
-
`
|
|
157
|
-
|
|
156
|
+
editing pipeline, aggregate output, and formatted clipboard export. When a
|
|
157
|
+
column declares both, `formatter` wins on every path — rendered cells, group
|
|
158
|
+
and table aggregate footers, declarative rows, and formatted export.
|
|
159
|
+
`valueGetter` derives the working value for cell display, local sorting,
|
|
160
|
+
aggregation, CSV/clipboard export, and declarative `<snice-row>` cells.
|
|
161
|
+
`valueParser` and
|
|
158
162
|
`valueSetter` run during editing; a setter may return either the final field
|
|
159
163
|
value or an updated row object.
|
|
160
164
|
|
|
165
|
+
A column that declares a display formatter renders through
|
|
166
|
+
`<snice-cell-text>`: the formatter output is a string, so the table resolves
|
|
167
|
+
the pipeline once and hands the text cell the result, keeping the column's
|
|
168
|
+
type-based alignment. The rendered cell's `value` (property and attribute) is
|
|
169
|
+
therefore always the DISPLAY value — the formatter output where one is
|
|
170
|
+
declared, the post-`valueGetter` working value otherwise.
|
|
171
|
+
|
|
172
|
+
The one cell that is not a typed cell element is the tree group column, which
|
|
173
|
+
renders its label as plain text beside the indent and chevron. It resolves the
|
|
174
|
+
same pipeline (`renderCell`, then `formatter`, then `valueFormatter`), but the
|
|
175
|
+
resulting text lives directly in the cell rather than in a `<snice-cell-text>`,
|
|
176
|
+
so there is no `value` attribute to read there.
|
|
177
|
+
|
|
178
|
+
When the working value is empty — the row field is `null`, or the row never
|
|
179
|
+
carried the field at all — a table-rendered cell falls back to the empty value
|
|
180
|
+
of its own type rather than to a blanket empty string:
|
|
181
|
+
|
|
182
|
+
| Column type | Empty value |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `boolean` | `false` |
|
|
185
|
+
| `rating`, `progress`, `duration`, `filesize` | `0` |
|
|
186
|
+
| `json` | `null` |
|
|
187
|
+
| everything else | `''` |
|
|
188
|
+
|
|
189
|
+
This is the same per-type value semantics the standalone cells declare, so a
|
|
190
|
+
row that omits a boolean field renders as false — never as true — and a row
|
|
191
|
+
that omits a progress field renders an empty bar rather than an object. A
|
|
192
|
+
declarative `<snice-row>` cell instead receives the empty working value
|
|
193
|
+
unchanged, so it renders its own no-value state.
|
|
194
|
+
|
|
161
195
|
The declared `ColumnType` union is:
|
|
162
196
|
|
|
163
197
|
```typescript
|
|
@@ -503,6 +537,36 @@ Standalone behavior boundaries:
|
|
|
503
537
|
- Links auto-open HTTP(S) values externally. Location builds Google, OpenStreetMap, or Apple URLs. JSON supports collapse, toggle, depth controls, JSON text, and direct object assignment.
|
|
504
538
|
- Date supports relative time, custom tokens, and optional time. Image supports fallback, `variant`, size, lazy loading, and a placeholder/error state.
|
|
505
539
|
|
|
540
|
+
### Dates and timezones
|
|
541
|
+
|
|
542
|
+
A date value that carries no time is a **calendar day**, and the table reads it
|
|
543
|
+
at *local* midnight. `'2026-03-15'` therefore renders as March 15 in every
|
|
544
|
+
timezone — it is never shifted to the 14th by a negative UTC offset, as the
|
|
545
|
+
platform's own `new Date('2026-03-15')` (UTC midnight) would. The rule covers
|
|
546
|
+
the shortened ISO forms too: `'2026-03'` is March 1 and `'2026'` is January 1.
|
|
547
|
+
|
|
548
|
+
A value that *does* carry a time keeps instant semantics and is projected into
|
|
549
|
+
the viewer's local zone: `'2026-03-15T23:30:00Z'` is the evening of the 15th in
|
|
550
|
+
London and the morning of the 16th in Tokyo. Zoneless date-times
|
|
551
|
+
(`'2026-03-15T10:00'`), epoch numbers, and `Date` objects are already local or
|
|
552
|
+
absolute and pass through untouched.
|
|
553
|
+
|
|
554
|
+
One parse serves the whole date layer, so the day a cell paints is the day the
|
|
555
|
+
row editor holds, the day a CSV/clipboard export ships, and the day a `date`
|
|
556
|
+
column filter (`is`, `before`, `onOrBefore`, `after`, `onOrAfter`) compares
|
|
557
|
+
against.
|
|
558
|
+
|
|
559
|
+
A date-only *filter bound* names the whole day, so all five operators agree
|
|
560
|
+
about what falls on it. Filtering `onOrBefore` `'2026-03-15'` keeps a row
|
|
561
|
+
timestamped `'2026-03-15T09:00'`, and `after` `'2026-03-15'` excludes it —
|
|
562
|
+
exactly the rows `is` would report for that day, no more and no less. Give the
|
|
563
|
+
bound its own time (`'2026-03-15T12:00'`) to compare against that instant
|
|
564
|
+
instead.
|
|
565
|
+
|
|
566
|
+
An impossible day is not silently rounded: `'2026-02-30'` and `'2026-13-01'`
|
|
567
|
+
are rejected the way the platform rejects them, and the cell shows the raw
|
|
568
|
+
value rather than inventing March 2 or January 2027.
|
|
569
|
+
|
|
506
570
|
## Methods
|
|
507
571
|
|
|
508
572
|
### Data, Requests, and Rendering
|
|
@@ -527,10 +591,10 @@ Standalone behavior boundaries:
|
|
|
527
591
|
|--------|-----------|-------------|
|
|
528
592
|
| `getSelectedData()` | — | Returns row objects for the raw `selectedRows` indices |
|
|
529
593
|
| `setSelectabilityCheck()` | `(row, index) => boolean` | Disables selection for rows that fail the predicate and removes them from current, range, group, and select-all selections |
|
|
530
|
-
| `updateRowSelectionState()` | — | Synchronizes rendered rows from `selectedRows
|
|
531
|
-
| `updateSelectAllState()` | — | Synchronizes the multiple-mode select-all checkbox |
|
|
594
|
+
| `updateRowSelectionState()` | — | Synchronizes rendered rows from `selectedRows`. Each row is matched by its own data index, so structural rows that share the body — group headers, aggregate footers, virtual spacers, and the bottom filler — are never marked selected, and a paged body cannot mark the wrong row |
|
|
595
|
+
| `updateSelectAllState()` | — | Synchronizes the multiple-mode select-all checkbox. Also runs on every body render, so a delivery or cleared filter that changes the selectable row count re-derives the checked/indeterminate state |
|
|
532
596
|
| `toggleSort()` | `columnKey: string, multiSort = false` | Cycles ascending → descending → none. `multiSort=false` replaces other sorts; header clicks pass `true` and therefore accumulate sorts |
|
|
533
|
-
| `setSortComparator()` | `columnKey, (a, b, direction) => number` | Installs a custom local comparator |
|
|
597
|
+
| `setSortComparator()` | `columnKey, (a, b, direction) => number` | Installs a custom local comparator. It receives the column's working values — the `valueGetter` result where one is declared |
|
|
534
598
|
| `setColumnFilter()` | `column, operator, value` | Adds or replaces one column filter |
|
|
535
599
|
| `removeColumnFilter()` | `column: string` | Removes a column filter |
|
|
536
600
|
| `setQuickFilter()` | `text: string` | Searches all configured columns. Applies synchronously in local mode and requests data in remote mode |
|
|
@@ -557,11 +621,11 @@ Filter operators are type-specific:
|
|
|
557
621
|
| `getColumnVisibility()` | — | Returns `{ [columnKey]: boolean }` |
|
|
558
622
|
| `pinColumn()` | `key, side: 'left' \| 'right'` | Pins a pinnable column to a physical edge |
|
|
559
623
|
| `unpinColumn()` | `key: string` | Removes a column pin |
|
|
560
|
-
| `autoSizeColumn()` | `key: string` |
|
|
561
|
-
| `autoSizeAllColumns()` | — | Auto-sizes
|
|
624
|
+
| `autoSizeColumn()` | `key: string` | Fits one column to the content of its rendered header and body cells — it shrinks as readily as it grows — and repaints header and body with the measured width |
|
|
625
|
+
| `autoSizeAllColumns()` | — | Auto-sizes every managed column in one measuring pass |
|
|
562
626
|
| `moveColumn()` | `key, toIndex: number` | Moves an unpinned, reorderable column and emits `column-order-change` |
|
|
563
627
|
| `setColumnGroups()` | `Array<{ label, children, headerClass? }>` | Adds a multi-level header row. `children` contains column keys |
|
|
564
|
-
| `scrollToRow()` | `index: number` | Scrolls the virtualized display to a raw data row, translating grouped/tree positions when needed |
|
|
628
|
+
| `scrollToRow()` | `index: number` | Scrolls the virtualized display to a raw data row, translating grouped/tree positions when needed. The virtual window is recomputed as part of the call, so the row is in the DOM when it returns — a programmatic scroll fires no `scroll` event to do it later |
|
|
565
629
|
| `scrollToColumn()` | `columnKey: string` | Scrolls the rendered header into view |
|
|
566
630
|
| `getScrollPosition()` | — | Returns the virtualizer's `{ top, left }` position |
|
|
567
631
|
| `toggleFullscreen()` | — | Toggles native fullscreen with a CSS fallback |
|
|
@@ -717,7 +781,7 @@ the corresponding user-action event; density is the explicit exception.
|
|
|
717
781
|
| `columns` | Declarative `<snice-column>` definitions |
|
|
718
782
|
| `rows` | Declarative `<snice-row>` data |
|
|
719
783
|
| `header` | Super-header content above the native column headers |
|
|
720
|
-
| `empty-state` | Custom content cloned into the empty table body |
|
|
784
|
+
| `empty-state` | Custom content cloned into the empty table body, on the virtualized path as well |
|
|
721
785
|
|
|
722
786
|
`<snice-table>` has no default slot.
|
|
723
787
|
|
|
@@ -753,11 +817,16 @@ the corresponding user-action event; density is the explicit exception.
|
|
|
753
817
|
|
|
754
818
|
The internal native body exposes `row` and `cell` parts, so page CSS can style body rows and cells directly (e.g. `snice-table::part(cell) { ... }`). Standalone cell-component parts are not forwarded through `<snice-table>`.
|
|
755
819
|
|
|
820
|
+
When a sized host is taller than its rows, the table appends an inert filler row so the column grid reaches the bottom edge of the frame (this happens on the virtualized path too). The filler is not data: it carries no row identity, no ARIA, no selection state, and no `row`/`cell` part — a `::part(row)` border rule therefore stops at the last real row instead of drawing a phantom one underneath it.
|
|
821
|
+
|
|
822
|
+
**A sized host only.** The filler may only consume space the frame already has. If the host's height is content-driven — `:host { height: 100% }` resolving against an auto or max-content container, such as a stretched grid/flex item or a plain block parent — then the filler would be part of the content the frame is measured from, and it would grow the box it is trying to fill on every resize notification. The table detects this by measurement (apply the filler, confirm the frame kept its height, hand back any growth it caused), so on a content-driven host you get either a smaller filler or none at all, never a growing table. Give the host a real height (`style="height: 24rem"`, a flex/grid track, or a sized ancestor) whenever you want the grid to reach the bottom edge.
|
|
823
|
+
|
|
756
824
|
## CSS Custom Properties
|
|
757
825
|
|
|
758
826
|
| Property | Description | Default |
|
|
759
827
|
|----------|-------------|---------|
|
|
760
828
|
| `--snice-table-body-bg` | Native table body background | `--snice-color-surface` |
|
|
829
|
+
| `--snice-table-stripe-bg` | `striped` row tint | `--snice-color-overlay-stripe` |
|
|
761
830
|
| `--snice-table-group-header-bg` | Group-header background | `--snice-color-surface-container-low` |
|
|
762
831
|
| `--snice-table-group-header-color` | Group-header text | `--snice-color-text` |
|
|
763
832
|
| `--snice-table-group-count-bg` | Group count badge background | `--snice-color-surface-container-high` |
|
|
@@ -769,6 +838,7 @@ The internal native body exposes `row` and `cell` parts, so page CSS can style b
|
|
|
769
838
|
| `--snice-table-cell-padding` | Body/header cell padding | `--snice-spacing-xs` `--snice-spacing-sm` |
|
|
770
839
|
| `--snice-table-cell-border` | Vertical (right) cell border; set to `none` to drop grid lines | `1px solid --snice-color-border` |
|
|
771
840
|
| `--snice-table-row-border` | Horizontal (bottom) row border | `1px solid --snice-color-border` |
|
|
841
|
+
| `--snice-table-cell-min-width` | Width floor of the typed cells (`email`, `status`, `link`, `phone`, `location`, `json`, `color`, `image`, `actions`); `column-fit="squish"` sets it to `0` on a squished cell so the content ellipsises | `6.25rem` |
|
|
772
842
|
|
|
773
843
|
The component also consumes global Snice color, spacing, typography, radius, focus-ring, shadow, and transition tokens.
|
|
774
844
|
|
|
@@ -801,6 +871,51 @@ import 'snice/components/table/snice-table';
|
|
|
801
871
|
|
|
802
872
|
`setColumns()` is equally reactive. `setData()` is deliberately non-eager for bulk loading; pair it with an explicit `renderBody()` or prefer `table.data = rows`.
|
|
803
873
|
|
|
874
|
+
### The assignment is the signal, not the identity
|
|
875
|
+
|
|
876
|
+
`data` and `columns` re-render on **every** assignment, including one that hands
|
|
877
|
+
back the array the table already holds. Mutating rows in place and re-publishing
|
|
878
|
+
them is a supported pattern:
|
|
879
|
+
|
|
880
|
+
```typescript
|
|
881
|
+
rows.push({ name: 'Carol Diaz', email: 'carol@example.com', age: 41 });
|
|
882
|
+
table.data = rows; // same array reference — still re-renders
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
Most Snice properties use identity to decide whether anything changed, which is
|
|
886
|
+
why assigning an unchanged object elsewhere is a no-op. `data` and `columns`
|
|
887
|
+
opt out of that check, because they are bulk payloads applications routinely
|
|
888
|
+
edit in place. The cost is small and bounded: a genuinely redundant assignment
|
|
889
|
+
(`table.data = table.data` with nothing changed) buys a render pass that would
|
|
890
|
+
otherwise have been skipped, and renders are coalesced per microtask, so a burst
|
|
891
|
+
of assignments in one tick still paints once.
|
|
892
|
+
|
|
893
|
+
A `columns` assignment is a **new configuration**, not a patch. The array you
|
|
894
|
+
assign is the source of truth for painted order, `pinned`, `width`, and which
|
|
895
|
+
columns exist at all — a column the new array does not declare is dropped from
|
|
896
|
+
the model entirely, and a column it re-declares comes back visible at the
|
|
897
|
+
position and width it declares. That also means the assignment discards column
|
|
898
|
+
state the user established through the UI (a dragged resize, a manual reorder, a
|
|
899
|
+
column hidden with `setColumnVisible()`) for the columns it re-declares.
|
|
900
|
+
|
|
901
|
+
Because `columns` ignores identity too, this is true of *every* assignment —
|
|
902
|
+
handing back the same array, mutated or untouched, re-applies the declaration
|
|
903
|
+
just as a fresh array would. Mutating the definitions you already published is
|
|
904
|
+
therefore not a way around it. To keep user column state, do not re-assign
|
|
905
|
+
`columns`: drive the change through the column APIs (`setColumnVisible()`,
|
|
906
|
+
`moveColumn()`, `pinColumn()`, `unpinColumn()`), which update the model in place.
|
|
907
|
+
|
|
908
|
+
Rows delivered while a client-side sort is active land in that sorted order —
|
|
909
|
+
`currentSort` and the rendered body can never disagree. In remote mode the
|
|
910
|
+
server owns the ordering, so the delivered order is rendered as-is.
|
|
911
|
+
|
|
912
|
+
Row rendering recycles the `<tr>` of a row object it has already painted, and a
|
|
913
|
+
row is repainted when its own field values change — so patching a row in place
|
|
914
|
+
and re-delivering it (`table.data = [...rows]`, a poll or websocket update)
|
|
915
|
+
repaints that row while untouched rows keep their DOM. The comparison is
|
|
916
|
+
shallow: a mutation buried inside a nested object value is not detected, so
|
|
917
|
+
hand over a new row object for those.
|
|
918
|
+
|
|
804
919
|
## Examples
|
|
805
920
|
|
|
806
921
|
### Density and List Styling
|
|
@@ -881,11 +996,26 @@ Use `selectable` with `selection-mode`. In multiple mode, a plain click or Ctrl/
|
|
|
881
996
|
|
|
882
997
|
// Runtime mode changes rebuild the selection controls.
|
|
883
998
|
table.selectionMode = 'single';
|
|
999
|
+
|
|
1000
|
+
// So do runtime `selectable` changes: the header select-all cell and every
|
|
1001
|
+
// row checkbox are added or removed together.
|
|
1002
|
+
table.selectable = false;
|
|
884
1003
|
</script>
|
|
885
1004
|
```
|
|
886
1005
|
|
|
887
1006
|
In single mode, selecting a row replaces the previous selection; its row checkbox can clear the selection. In none mode, selection controls and row-selection behavior are removed.
|
|
888
1007
|
|
|
1008
|
+
Selection is anchored to the row, not to its position. Sorting locally, or
|
|
1009
|
+
re-delivering the same row objects in a different order, re-resolves
|
|
1010
|
+
`selectedRows` against those objects, so `getSelectedData()` and the highlighted
|
|
1011
|
+
rows stay on the rows the user picked. Delivering a different row set keeps the
|
|
1012
|
+
documented raw-index semantics.
|
|
1013
|
+
|
|
1014
|
+
Every selection path — a row click, a row checkbox, select-all, keyboard
|
|
1015
|
+
selection, or a programmatic `selectedRows` assignment — writes both
|
|
1016
|
+
`data-selected` and `aria-selected="true"` on the affected rows, so assistive
|
|
1017
|
+
technology hears the change immediately rather than at the next full render.
|
|
1018
|
+
|
|
889
1019
|
### Rich Cells and Currency-Looking Columns
|
|
890
1020
|
|
|
891
1021
|
Use specialized runtime types for status, progress, actions, and related cells. Use `numberFormat` for currency-looking values inside the table until the table routes `currency` through its dedicated currency cell.
|
|
@@ -926,6 +1056,31 @@ table.addEventListener('cell-action', (event) => {
|
|
|
926
1056
|
});
|
|
927
1057
|
```
|
|
928
1058
|
|
|
1059
|
+
#### Accounting notation
|
|
1060
|
+
|
|
1061
|
+
`type: 'accounting'` is currency notation, not a bare number: it renders the
|
|
1062
|
+
currency symbol, groups thousands, and puts negatives in parentheses instead of
|
|
1063
|
+
using a minus sign. Positives are padded with one non-breaking space so their
|
|
1064
|
+
last digit stays under the closing paren of the row above in a right-aligned
|
|
1065
|
+
column. An `accounting` cell and a `currency` cell showing the same amount
|
|
1066
|
+
therefore agree.
|
|
1067
|
+
|
|
1068
|
+
```javascript
|
|
1069
|
+
{ key: 'balance', label: 'Balance', type: 'accounting' }
|
|
1070
|
+
// -12840.5 -> ($12,840.50) 95000 -> $95,000.00
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
A generic `<snice-cell>` with no declared `align` keeps its type's alignment
|
|
1074
|
+
(right, for every numeric type) instead of forcing left, so the padding lines the
|
|
1075
|
+
figures up against the currency column beside it.
|
|
1076
|
+
|
|
1077
|
+
Defaults are two decimals, grouping on, and `USD`/`en-US`. Override with
|
|
1078
|
+
`numberFormat.decimals` and `numberFormat.thousandsSeparator`, or with
|
|
1079
|
+
`currencyFormat.currency`, `currencyFormat.locale`, and
|
|
1080
|
+
`currencyFormat.currencyDisplay`. Setting `numberFormat.prefix` or
|
|
1081
|
+
`numberFormat.suffix` opts out of the symbol entirely — the caller is naming the
|
|
1082
|
+
unit — so `{ prefix: 'CR ' }` renders `(CR 12,840.50)`.
|
|
1083
|
+
|
|
929
1084
|
### Custom Cell and Editor Renderers
|
|
930
1085
|
|
|
931
1086
|
Use `renderCell` and `renderEditor` when a built-in type is not enough.
|
|
@@ -1039,6 +1194,8 @@ Use `pagination-mode="client"` to page the local filtered display model.
|
|
|
1039
1194
|
|
|
1040
1195
|
When grouping or tree data is active, a client page contains flattened visible display items, including structural rows, rather than exactly `pageSize` raw data rows.
|
|
1041
1196
|
|
|
1197
|
+
If the data behind the current page shrinks — a filter, a `data` reassignment, or a shorter re-delivery — the table re-clamps `currentPage` to the new last page before slicing, exactly as `goToPage()` would. The body therefore always shows the rows the "Showing a–b of n" summary claims, with no page interaction required. An empty result set is the one exception: the declared page is left alone so a table rendered before its rows arrive keeps the starting page the host asked for.
|
|
1198
|
+
|
|
1042
1199
|
### Virtualization and Lazy Loading
|
|
1043
1200
|
|
|
1044
1201
|
Use `virtualize` on a fixed-height table and append data in response to `lazy-load`.
|
|
@@ -1071,6 +1228,8 @@ Use `virtualize` on a fixed-height table and append data in response to `lazy-lo
|
|
|
1071
1228
|
|
|
1072
1229
|
Guard the handler because continued scrolling can emit more than once.
|
|
1073
1230
|
|
|
1231
|
+
A virtualized body renders the same zero-row states as an ordinary one: the loading spinner while a remote request is in flight, the `⚠️` row for a failed load, and the `empty-state` slot (or the default "No data" placeholder) when the delivery is empty. `scrollToRow()` recomputes the window as part of the call, so the requested row is rendered immediately instead of waiting for the next scroll or delivery.
|
|
1232
|
+
|
|
1074
1233
|
### Master-Detail Rows
|
|
1075
1234
|
|
|
1076
1235
|
Use `setDetailPanel()` to add an expand control and render detail content below a row.
|
|
@@ -1090,7 +1249,7 @@ String detail content is parsed as HTML; use trusted strings or return an `HTMLE
|
|
|
1090
1249
|
|
|
1091
1250
|
### Tree Data
|
|
1092
1251
|
|
|
1093
|
-
Call `setTreeData()` before assigning `data` reactively. Each row's path identifies its place in the hierarchy; missing ancestors become generated gap nodes.
|
|
1252
|
+
Call `setTreeData()` before assigning `data` reactively. Each row's path identifies its place in the hierarchy; missing ancestors become generated gap nodes, at every level a path skips — a row at `['Eng', 'Team', 'Alice']` delivered on its own renders `Eng` and `Team` as gap nodes above it.
|
|
1094
1253
|
|
|
1095
1254
|
```javascript
|
|
1096
1255
|
const table = document.querySelector('#org-tree');
|
|
@@ -1115,7 +1274,9 @@ table.data = [
|
|
|
1115
1274
|
];
|
|
1116
1275
|
```
|
|
1117
1276
|
|
|
1118
|
-
`defaultExpansionDepth: 0` starts collapsed; `1` expands root nodes; `Infinity` expands all levels.
|
|
1277
|
+
`defaultExpansionDepth: 0` starts collapsed; `1` expands root nodes; `Infinity` expands all levels. It describes the *starting* expansion only: it is applied once, when the first rows arrive, so collapsing every node — with `collapseTreeNode()` or `collapseAllTreeNodes()` — sticks across later renders and re-deliveries instead of springing back to the default depth.
|
|
1278
|
+
|
|
1279
|
+
The group column is a normal cell: `renderCell` owns it outright, and otherwise `formatter` (then `valueFormatter` as the fallback) produces its label, rendered next to the indent and chevron. Tree rows also compose with master-detail — with `setDetailPanel()` configured, expanding a tree row renders its panel below the row. Generated gap nodes carry no data row, so they show only the label their path implies and have no detail panel.
|
|
1119
1280
|
|
|
1120
1281
|
### Column Groups and Layout Controls
|
|
1121
1282
|
|
|
@@ -1136,6 +1297,81 @@ table.moveColumn('department', 1);
|
|
|
1136
1297
|
|
|
1137
1298
|
Enable `column-resize`, `column-reorder`, and `column-menu` for equivalent pointer controls. Pinned headers stay at their physical edges and are not draggable.
|
|
1138
1299
|
|
|
1300
|
+
#### How a column gets its width
|
|
1301
|
+
|
|
1302
|
+
A column that declares no `width` **shares the frame**. On every header render,
|
|
1303
|
+
and whenever the frame is resized, the space left over after the fixed tool
|
|
1304
|
+
columns and the explicitly sized columns is split evenly between them. They
|
|
1305
|
+
therefore grow into a wide frame and shrink into a narrow one — a table inside a
|
|
1306
|
+
half-width card fits its card instead of running past the edge.
|
|
1307
|
+
|
|
1308
|
+
The floor is the column's `minWidth` (50px by default). Once the frame is too
|
|
1309
|
+
narrow even for the minimums, the columns stop shrinking and `.table-frame`
|
|
1310
|
+
scrolls horizontally; nothing is ever collapsed away.
|
|
1311
|
+
|
|
1312
|
+
Four things count as an **authored** width and are honoured verbatim, never
|
|
1313
|
+
refitted: `width` on the column definition, a drag-resize on the header edge,
|
|
1314
|
+
`autoSizeColumn()`, and `autoSizeAllColumns()`. Give a column `width` when its
|
|
1315
|
+
content has a natural size (an ID, a status chip); leave it off when the column
|
|
1316
|
+
should absorb whatever room is going.
|
|
1317
|
+
|
|
1318
|
+
#### `column-fit`: scroll or squish
|
|
1319
|
+
|
|
1320
|
+
The paragraphs above describe `column-fit="scroll"`, the default: `minWidth` is
|
|
1321
|
+
inviolable and the frame gives way. `column-fit="squish"` inverts the priority —
|
|
1322
|
+
the frame is inviolable and `minWidth` gives way:
|
|
1323
|
+
|
|
1324
|
+
```html
|
|
1325
|
+
<!-- Default: columns keep their minimums, the frame scrolls sideways. -->
|
|
1326
|
+
<snice-table column-fit="scroll"></snice-table>
|
|
1327
|
+
|
|
1328
|
+
<!-- Every column shares the frame; the table never scrolls horizontally. -->
|
|
1329
|
+
<snice-table column-fit="squish"></snice-table>
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
```javascript
|
|
1333
|
+
table.columnFit = 'squish'; // reactive; re-fits the painted columns
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
In squish mode:
|
|
1337
|
+
|
|
1338
|
+
- **Every visible column shares the available frame width.** `minWidth` is
|
|
1339
|
+
relaxed down to a 24px legibility floor, so eight columns still fit a
|
|
1340
|
+
half-width card.
|
|
1341
|
+
- **Cell content and header labels ellipsise** instead of wrapping the row
|
|
1342
|
+
taller or spilling past the column boundary. This covers the typed cells too
|
|
1343
|
+
(`email`, `status`, `link`, `phone`, `location`, `json`, `color`, `image`,
|
|
1344
|
+
`actions`), whose 100px default width floor squish relaxes through the
|
|
1345
|
+
`--snice-table-cell-min-width` custom property — without that they refuse to shrink
|
|
1346
|
+
and the column clips them mid-glyph instead of ellipsising.
|
|
1347
|
+
- **The table never scrolls horizontally.** The rightmost column edge sits on
|
|
1348
|
+
the frame's inner edge at any width, and `.table-frame` sets
|
|
1349
|
+
`overflow-x: hidden` so a sub-pixel rounding error can never produce a
|
|
1350
|
+
scrollbar. Vertical scrolling is unaffected.
|
|
1351
|
+
- **Resizing still works and rebalances.** A drag is honoured, but the columns
|
|
1352
|
+
beside the dragged one give up (or take back) the difference so the total
|
|
1353
|
+
stays inside the frame — capped so no single column can squeeze the others
|
|
1354
|
+
below the floor.
|
|
1355
|
+
- **Declared widths are still honoured** while there is an unsized column left
|
|
1356
|
+
to absorb the difference. When the authored widths alone overflow the frame,
|
|
1357
|
+
every column is scaled down by the same factor instead: squish has no
|
|
1358
|
+
scrollbar to fall back on.
|
|
1359
|
+
|
|
1360
|
+
Choose `squish` for a table that has to live in a card, a dashboard tile, or a
|
|
1361
|
+
split pane, where a horizontal scrollbar reads as a layout bug. Choose the
|
|
1362
|
+
default `scroll` for a wide dataset the reader is expected to explore sideways —
|
|
1363
|
+
pinned columns, in particular, only pay off when something scrolls past them.
|
|
1364
|
+
|
|
1365
|
+
#### Pinned column affordance
|
|
1366
|
+
|
|
1367
|
+
A pinned cell renders with `pinned-cell` plus `pinned-cell--left` or
|
|
1368
|
+
`pinned-cell--right`. The inner-most pinned column on each side — the boundary
|
|
1369
|
+
between the frozen region and the scrolling one — also gets `pinned-cell--edge`,
|
|
1370
|
+
which paints a 2px divider and a soft shadow cast into the scrolling area. Pinned
|
|
1371
|
+
headers additionally carry a `.pin-indicator` pin glyph and say "pinned left" /
|
|
1372
|
+
"pinned right" in their accessible name, so a frozen column is still legible when
|
|
1373
|
+
the table happens to be wide enough that nothing scrolls.
|
|
1374
|
+
|
|
1139
1375
|
### Row Reordering and Pinning
|
|
1140
1376
|
|
|
1141
1377
|
Use `row-reorder` for local drag-and-drop, and pin independent summary rows with the row-pinning methods.
|
|
@@ -1163,6 +1399,18 @@ template, bind it with `.listRenderer=${fn}`. The imperative
|
|
|
1163
1399
|
`setListViewRenderer()` equivalent remains available. Tool cells such as
|
|
1164
1400
|
selection and detail toggles remain available.
|
|
1165
1401
|
|
|
1402
|
+
An active `listRenderer` also **suppresses the columnar header**: the group
|
|
1403
|
+
header row, the per-column `<th>` cells, and the header-filter row are all
|
|
1404
|
+
dropped, because a card row has no column geometry for them to line up with.
|
|
1405
|
+
The tool-column headers stay, so `selectable` keeps its select-all checkbox
|
|
1406
|
+
above the cards. Clearing the renderer (`listRenderer = null`) or leaving list
|
|
1407
|
+
mode (`list = false`) brings the full header back. `list` on its own — with no
|
|
1408
|
+
renderer — still paints normal columnar cells and keeps its header.
|
|
1409
|
+
|
|
1410
|
+
Because there is no header to click, sorting in list mode runs through the
|
|
1411
|
+
toolbar (`setToolbar({ showSort: true })`) or the `toggleSort()` / `currentSort`
|
|
1412
|
+
API; the rows re-order exactly as they do in table mode.
|
|
1413
|
+
|
|
1166
1414
|
```javascript
|
|
1167
1415
|
table.list = true;
|
|
1168
1416
|
table.listRenderer = (row) => {
|
|
@@ -1182,6 +1430,13 @@ Use `loading` for progress and the `empty-state` slot for a custom zero-row mess
|
|
|
1182
1430
|
</snice-table>
|
|
1183
1431
|
```
|
|
1184
1432
|
|
|
1433
|
+
While `loading` with no rows, the body shows a centred spinner **and the word
|
|
1434
|
+
"Loading…"**, wrapped in a `role="status" aria-live="polite"` block so the state
|
|
1435
|
+
is announced rather than merely drawn. With rows already on screen the body dims
|
|
1436
|
+
and the `loading-overlay` part appears instead, carrying the same announcement
|
|
1437
|
+
as an `aria-label`. Both spinners raise `--progress-track-opacity` so their track
|
|
1438
|
+
ring stays visible on dark surfaces.
|
|
1439
|
+
|
|
1185
1440
|
The slotted empty content is **cloned** into the table body on each zero-row render — the light-DOM original is only a template, so event listeners or state on the slotted node do not carry over, and updating the slotted copy only takes effect on the next zero-row render. Remove `loading` after the request completes so the empty state can appear.
|
|
1186
1441
|
|
|
1187
1442
|
### Remote Data with Request/Response Events
|
|
@@ -1222,6 +1477,8 @@ Use `mode="remote"` with server pagination. The request payload contains `search
|
|
|
1222
1477
|
|
|
1223
1478
|
Only the newest overlapping request may update the table. A rejected latest request emits `table-load-error` and renders its error message.
|
|
1224
1479
|
|
|
1480
|
+
A control change that re-requests (sort, filter, page size, or page in server mode) supersedes any in-flight request **at the moment the change is made**, not 150 ms later when the debounce fires. A response that lands inside that window is therefore discarded rather than painted — no flash of rows belonging to the previous sort under a header that already shows the new one — and a superseded request that fails stays silent, exactly like any other stale response: only the newest request reports `table-load-error`.
|
|
1481
|
+
|
|
1225
1482
|
### Remote Data with a Controller
|
|
1226
1483
|
|
|
1227
1484
|
Set `mode="remote"` when a controller provides `table/config` and `table/data`; local-mode controller attachment intentionally does not fetch.
|
|
@@ -1345,7 +1602,9 @@ table.printTable({ pageStyles: '@page { size: landscape; }' });
|
|
|
1345
1602
|
|
|
1346
1603
|
CSV output uses raw row values and skips columns with `exportable: false`.
|
|
1347
1604
|
Clipboard output uses formatted values by default and copies every filtered row
|
|
1348
|
-
when selection is empty.
|
|
1605
|
+
when selection is empty. Both read the column's working value, so a
|
|
1606
|
+
`valueGetter` column exports what the table displays rather than a row field it
|
|
1607
|
+
may not even have. With a selection, both helpers intersect the correct
|
|
1349
1608
|
raw row identities with the filtered view.
|
|
1350
1609
|
|
|
1351
1610
|
## Limits Worth Knowing
|
|
@@ -1355,7 +1614,7 @@ contract before adopting the table:
|
|
|
1355
1614
|
|
|
1356
1615
|
- **Pagination labels.** The built-in `pagination` renders "Showing 1–25 of 60" with plain prev/next-style buttons; `snice-pagination` renders numbered `aria-label="Page N"` buttons. Neither produces a "Page X of Y" contract.
|
|
1357
1616
|
- **Remote-mode debounce.** `mode="remote"` requests are debounced by a hard-coded 150 ms.
|
|
1358
|
-
- **Failed remote loads keep their rows.** With data present, a failed re-request leaves the previous rows on screen; the `⚠️` warning row and the `empty-state` slot only appear when there is no data at all.
|
|
1617
|
+
- **Failed remote loads keep their rows.** With data present, a failed re-request leaves the previous rows on screen; the `⚠️` warning row and the `empty-state` slot only appear when there is no data at all. The error state (the `⚠️` row and the `table--error` host class) describes the last delivery *attempt*, so it clears as soon as any dataset arrives — a successful re-request, `setData()`, or a plain `table.data = rows` assignment when the page falls back to local data. Assigning an empty array clears it too, and the body then shows the empty state rather than the stale error. Re-assigning the identical array reference counts as an arrival and clears the error too — `data` re-renders on every assignment, not only on a new array. One limit remains: a request that was already in flight when the local rows arrived still reports its own failure when it settles — the error re-appears until the next assignment. Reordering rows by drag republishes the same rows and deliberately leaves the error alone.
|
|
1359
1618
|
- **Remote re-request triggers.** Remote mode re-requests only on `currentPage`, `currentSort`, and `pageSize` changes. A page that owns its own filter set must reset one of those (e.g. `currentPage`) to drive a refetch.
|
|
1360
1619
|
- **Local mode is client-side.** `sortable`/`searchable` in local mode sort and filter the rows already in `data` — on a server-paged list that is one page of results.
|
|
1361
1620
|
|
package/docs/components/theme.md
CHANGED
|
@@ -51,6 +51,11 @@ Provides all design tokens for Snice components via CSS custom properties. Inclu
|
|
|
51
51
|
| `--snice-color-overlay-hover` | Alpha tint layered over a surface on hover |
|
|
52
52
|
| `--snice-color-overlay-selected` | Primary-tinted overlay for selected rows/items |
|
|
53
53
|
| `--snice-color-overlay-selected-hover` | Selected state when also hovered |
|
|
54
|
+
| `--snice-color-overlay-stripe` | Zebra tint for alternating rows (`hsl(0 0% 0% / 0.02)` light, `hsl(0 0% 100% / 0.11)` dark) |
|
|
55
|
+
|
|
56
|
+
Dark surfaces need a far larger step than light ones: the tint that reads as a
|
|
57
|
+
stripe on white is invisible on near-black, which is why the stripe token is an
|
|
58
|
+
alpha overlay tuned per theme rather than a step on the surface scale.
|
|
54
59
|
|
|
55
60
|
### Motion
|
|
56
61
|
|
package/docs/events.md
CHANGED
|
@@ -908,6 +908,55 @@ Manual dispatch remains valid when code needs direct access to the Event
|
|
|
908
908
|
object, a dynamic event name, the cancellation boolean returned by
|
|
909
909
|
`dispatchEvent()`, or a target other than the Snice host.
|
|
910
910
|
|
|
911
|
+
### Cancelable Events (Suppressible Default Actions)
|
|
912
|
+
|
|
913
|
+
`cancelable` is an `EventInit` field, so `@dispatch('my-event', { cancelable:
|
|
914
|
+
true })` really does produce a cancelable event and a listener really can call
|
|
915
|
+
`preventDefault()` on it.
|
|
916
|
+
|
|
917
|
+
What the decorator cannot give you is the answer. `@dispatch` returns the
|
|
918
|
+
method's own return value — the event `detail` — not the boolean
|
|
919
|
+
`dispatchEvent()` produces, so the component has no way to find out that a
|
|
920
|
+
listener cancelled the event. A component whose event carries a **default
|
|
921
|
+
action it must be able to suppress** therefore dispatches by hand:
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
@element('day-chip')
|
|
925
|
+
class DayChip extends HTMLElement {
|
|
926
|
+
// Returns false when a listener called preventDefault().
|
|
927
|
+
private emitMoreClick(date: Date, count: number): boolean {
|
|
928
|
+
return this.dispatchEvent(new CustomEvent('more-click', {
|
|
929
|
+
detail: { date, count },
|
|
930
|
+
bubbles: true,
|
|
931
|
+
composed: true,
|
|
932
|
+
cancelable: true
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
private handleChipClick(date: Date, count: number) {
|
|
937
|
+
// The application gets first refusal on the day.
|
|
938
|
+
if (!this.emitMoreClick(date, count)) return;
|
|
939
|
+
this.openBuiltInPanel(date);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
An application then chooses between the two behaviors with one line:
|
|
945
|
+
|
|
946
|
+
```typescript
|
|
947
|
+
// Keep the built-in behavior, just observe it
|
|
948
|
+
chip.addEventListener('more-click', (e) => log(e.detail));
|
|
949
|
+
|
|
950
|
+
// Replace the built-in behavior entirely
|
|
951
|
+
chip.addEventListener('more-click', (e) => {
|
|
952
|
+
e.preventDefault();
|
|
953
|
+
openMyOwnDayView(e.detail.date);
|
|
954
|
+
});
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
Components in the library that use this pattern: `snice-link`'s `navigate`,
|
|
958
|
+
`snice-stepper`'s `step-change`, and `snice-calendar`'s `calendar-more-click`.
|
|
959
|
+
|
|
911
960
|
### Manual Escape Hatch
|
|
912
961
|
|
|
913
962
|
```typescript
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
- Date-picker types: `components/date-picker/snice-date-picker.types.ts` (type patterns)
|
|
15
15
|
- Date-picker tests: `tests/components/date-picker.test.ts` (test patterns)
|
|
16
16
|
- Test utils: `tests/components/test-utils.ts` (`createComponent`, `queryShadow`, `wait`)
|
|
17
|
-
- Coding standards: `.ai/
|
|
18
|
-
- Component checklist: `.ai/
|
|
17
|
+
- Coding standards: `.ai/style.md` (decorator patterns, CSS token rules)
|
|
18
|
+
- Component checklist: `.ai/workflow.md` (completion criteria)
|
|
19
19
|
- Design doc: `docs/plans/2026-03-06-date-range-picker-design.md`
|
|
20
20
|
|
|
21
21
|
---
|
|
@@ -157,7 +157,7 @@ Base calendar styles on `components/date-picker/snice-date-picker.css`. Key addi
|
|
|
157
157
|
- `.presets` — preset button list
|
|
158
158
|
- `.calendar-body` — flex container for presets + calendar grid(s)
|
|
159
159
|
|
|
160
|
-
All CSS must use `var(--snice-*, fallback)` pattern. Spacing in rem, borders in px. See `.ai/
|
|
160
|
+
All CSS must use `var(--snice-*, fallback)` pattern. Spacing in rem, borders in px. See `.ai/style.md` for full token reference.
|
|
161
161
|
|
|
162
162
|
```css
|
|
163
163
|
/* Date Range Picker Component Styles */
|
|
@@ -987,7 +987,7 @@ If anything failed, fix and commit.
|
|
|
987
987
|
|
|
988
988
|
**Step 1: Write human docs**
|
|
989
989
|
|
|
990
|
-
Follow the format in `.ai/
|
|
990
|
+
Follow the format in `.ai/style.md`. Section order: Title, Description, Basic Usage, Importing, Examples (basic, formats, sizes, variants, dual column, presets, constraints, states), Slots, Properties, Events, Methods, CSS Custom Properties.
|
|
991
991
|
|
|
992
992
|
**Step 2: Write AI docs**
|
|
993
993
|
|
|
@@ -159,7 +159,7 @@ Features: 7, 8, 9, 10, 12, 17, 21, 26, 33, 36, 37, 38
|
|
|
159
159
|
|
|
160
160
|
## Component Checklist
|
|
161
161
|
|
|
162
|
-
Each phase must satisfy `.ai/
|
|
162
|
+
Each phase must satisfy `.ai/workflow.md` before moving on. Key items:
|
|
163
163
|
|
|
164
164
|
- Types in `snice-table.types.ts`
|
|
165
165
|
- CSS with `var(--snice-*, fallback)`, rem for spacing, px for borders
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
**Design doc:** `docs/plans/2026-03-09-action-bar-design.md`
|
|
12
12
|
|
|
13
|
-
**Component checklist:** `.ai/
|
|
13
|
+
**Component checklist:** `.ai/workflow.md` — verify ALL items before marking complete.
|
|
14
14
|
|
|
15
15
|
## REQUIRED READING — Complete ALL before writing any code
|
|
16
16
|
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
3. `docs/ai/DEVELOPMENT.md` — build system, testing, component file structure, adding components checklist, CDN builds, React adapters, test generation, component template
|
|
23
23
|
|
|
24
24
|
### Internal coding standards (read third — these are mandatory)
|
|
25
|
-
4. `.ai/
|
|
26
|
-
5. `.ai/
|
|
27
|
-
6. `.ai/
|
|
28
|
-
7. `.ai/
|
|
29
|
-
8. `.ai/
|
|
25
|
+
4. `.ai/style.md` — REQUIRED decorator patterns (@query, @on, @dispatch, @ready, @dispose, @observe, @watch, @request/@respond), CSS theme integration with fallbacks, units (rem vs px), two-tier variable pattern, container component dual API, dos and don'ts
|
|
26
|
+
5. `.ai/workflow.md` — every item must be satisfied before component is complete
|
|
27
|
+
6. `.ai/style.md` — exact doc format for both human and AI docs, section order, writing rules
|
|
28
|
+
7. `.ai/gotchas.md` — project organization (customer-facing vs internal), file naming, camera/timer rules, scroll spy notes
|
|
29
|
+
8. `.ai/testing.md` — E2E testing rules, .debug/ only for temp files, always headless
|
|
30
30
|
9. `.ai/tasks.md` — has pending tasks list AND required snice decorator patterns reference at the bottom
|
|
31
|
-
10. `.ai/
|
|
31
|
+
10. `.ai/pr-workflow.md` — release process, when to manually update llms.txt
|
|
32
32
|
|
|
33
33
|
### Design spec (read fourth)
|
|
34
34
|
11. `docs/plans/2026-03-09-action-bar-design.md` — the design spec for this component
|
|
@@ -110,7 +110,7 @@ git commit -m "feat(action-bar): add type definitions"
|
|
|
110
110
|
|
|
111
111
|
**Files:**
|
|
112
112
|
- Create: `components/action-bar/snice-action-bar.css`
|
|
113
|
-
- Reference: `.ai/
|
|
113
|
+
- Reference: `.ai/style.md` for theme token patterns, units, fallbacks
|
|
114
114
|
|
|
115
115
|
**Step 1: Read theme.css for available tokens**
|
|
116
116
|
|
|
@@ -511,11 +511,11 @@ git commit -m "feat(action-bar): add full showcase"
|
|
|
511
511
|
**Files:**
|
|
512
512
|
- Create: `docs/components/action-bar.md` (human-friendly)
|
|
513
513
|
- Create: `docs/ai/components/action-bar.md` (token-efficient)
|
|
514
|
-
- Reference: `.ai/
|
|
514
|
+
- Reference: `.ai/style.md` for exact format
|
|
515
515
|
|
|
516
516
|
**Step 1: Write human docs**
|
|
517
517
|
|
|
518
|
-
Follow the section order from
|
|
518
|
+
Follow the section order from `.ai/style.md`:
|
|
519
519
|
1. Title + tag name
|
|
520
520
|
2. One-sentence description
|
|
521
521
|
3. Basic usage
|
|
@@ -646,7 +646,7 @@ Expected: ALL PASS
|
|
|
646
646
|
|
|
647
647
|
**Step 6: Verify against component checklist**
|
|
648
648
|
|
|
649
|
-
Run through every item in `.ai/
|
|
649
|
+
Run through every item in `.ai/workflow.md`:
|
|
650
650
|
- [ ] Pre-implementation items
|
|
651
651
|
- [ ] Implementation items
|
|
652
652
|
- [ ] Testing items
|
|
@@ -41,7 +41,7 @@ select-all), ~4 s full renderBody @10k, ~6.6 s per sort click (render-bound),
|
|
|
41
41
|
snice-cell-actions.css:40 uses var(--snice-color-text, rgb(23 23 23 / .05))
|
|
42
42
|
— theme token as background, tint only as unreachable fallback.
|
|
43
43
|
6. **row-clicked not composed.** snice-table.ts:2191-2193 raw dispatchEvent
|
|
44
|
-
without bubbles/composed, violating .ai/
|
|
44
|
+
without bubbles/composed, violating .ai/style.md; invisible
|
|
45
45
|
outside shadow boundaries.
|
|
46
46
|
7. **Error state unwired.** Failed remote load only console.errors
|
|
47
47
|
(snice-table.ts:252-254) then shows generic "No data". A designed
|