@happyvertical/smrt-ui 0.52.0 → 0.53.1
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/AGENTS.md +11 -1
- package/README.md +300 -2
- package/dist/components/data/DataTable.svelte +60 -15
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableResponsive.d.ts +4 -0
- package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
- package/dist/components/data/DataTableResponsive.js +22 -0
- package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
- package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
- package/dist/components/data/types.d.ts +8 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/display/DateDisplay.svelte +76 -14
- package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
- package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
- package/dist/components/display/Icon.svelte +3 -16
- package/dist/components/display/Icon.svelte.d.ts +0 -9
- package/dist/components/display/Icon.svelte.d.ts.map +1 -1
- package/dist/components/display/StatusBadge.svelte +29 -2
- package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
- package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
- package/dist/components/display/__tests__/Icon.test.js +132 -2
- package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
- package/dist/components/display/icons.svelte.d.ts +11 -0
- package/dist/components/display/icons.svelte.d.ts.map +1 -0
- package/dist/components/display/icons.svelte.js +60 -0
- package/dist/components/display/index.d.ts +3 -1
- package/dist/components/display/index.d.ts.map +1 -1
- package/dist/components/display/index.js +1 -0
- package/dist/components/display/types.d.ts +2 -0
- package/dist/components/display/types.d.ts.map +1 -1
- package/dist/components/feedback/ConfirmDialog.svelte +59 -48
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
- package/dist/components/feedback/ToastViewport.svelte +156 -8
- package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
- package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
- package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/toast.test.js +142 -1
- package/dist/components/feedback/index.d.ts +1 -1
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/toast.d.ts +13 -1
- package/dist/components/feedback/toast.d.ts.map +1 -1
- package/dist/components/feedback/toast.js +51 -10
- package/dist/components/forms/CameraCapture.svelte +619 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
- package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
- package/dist/components/forms/Checkbox.svelte +8 -1
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Combobox.svelte +40 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +5 -0
- package/dist/components/forms/Form.svelte.d.ts +1 -0
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/Input.svelte +8 -1
- package/dist/components/forms/Input.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte.d.ts.map +1 -1
- package/dist/components/forms/Listbox.svelte +39 -1
- package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
- package/dist/components/forms/MultiSelect.svelte +41 -1
- package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
- package/dist/components/forms/Radio.svelte +1 -0
- package/dist/components/forms/RadioGroup.svelte +7 -1
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
- package/dist/components/forms/SegmentedControl.svelte +152 -11
- package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
- package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
- package/dist/components/forms/Select.svelte +8 -1
- package/dist/components/forms/Select.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte.d.ts.map +1 -1
- package/dist/components/forms/SignaturePad.svelte +436 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
- package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
- package/dist/components/forms/Switch.svelte +8 -1
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
- package/dist/components/forms/Textarea.svelte +8 -1
- package/dist/components/forms/Textarea.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
- package/dist/components/forms/__tests__/Combobox.test.js +279 -12
- package/dist/components/forms/__tests__/Form.test.js +46 -0
- package/dist/components/forms/__tests__/Listbox.test.js +303 -0
- package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
- package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
- package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
- package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
- package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
- package/dist/components/forms/__tests__/capture-test-env.js +122 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
- package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
- package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/touch-density.test.js +66 -0
- package/dist/components/forms/camera-capture-session.d.ts +81 -0
- package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
- package/dist/components/forms/camera-capture-session.js +130 -0
- package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
- package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
- package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
- package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
- package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
- package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
- package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
- package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
- package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
- package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
- package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
- package/dist/components/forms/form-retry/content-digest.js +169 -0
- package/dist/components/forms/form-retry/controller.d.ts +214 -0
- package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
- package/dist/components/forms/form-retry/controller.js +404 -0
- package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
- package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/form-draft.js +161 -0
- package/dist/components/forms/form-retry/index.d.ts +19 -0
- package/dist/components/forms/form-retry/index.d.ts.map +1 -0
- package/dist/components/forms/form-retry/index.js +16 -0
- package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
- package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submission-key.js +342 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
- package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
- package/dist/components/forms/form-retry/submitted-draft.js +270 -0
- package/dist/components/forms/form-retry/types.d.ts +84 -0
- package/dist/components/forms/form-retry/types.d.ts.map +1 -0
- package/dist/components/forms/form-retry/types.js +13 -0
- package/dist/components/forms/index.d.ts +7 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +8 -0
- package/dist/components/forms/native-file-field.d.ts +94 -0
- package/dist/components/forms/native-file-field.d.ts.map +1 -0
- package/dist/components/forms/native-file-field.js +147 -0
- package/dist/components/forms/signature-pad-logic.d.ts +50 -0
- package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
- package/dist/components/forms/signature-pad-logic.js +48 -0
- package/dist/components/forms/types.d.ts +41 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/nav/FilterChips.svelte +8 -1
- package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
- package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +9 -2
- package/dist/components/ui/Button.svelte.d.ts +2 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -1
- package/dist/i18n/strings.ui.d.ts +24 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +26 -0
- package/dist/styles/tokens.css +2 -0
- package/dist/svelte/__tests__/playground.test.js +2 -1
- package/dist/svelte/playground/CapturePreview.svelte +56 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/themes/ThemeProvider.svelte +9 -0
- package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
- package/dist/themes/css-generator.d.ts.map +1 -1
- package/dist/themes/css-generator.js +3 -0
- package/dist/themes/styles/glass.css +4 -0
- package/dist/themes/styles/happyvertical.css +4 -0
- package/dist/themes/styles/material.css +4 -0
- package/dist/themes/styles/smrt.css +4 -0
- package/dist/themes/styles/studio.css +4 -0
- package/package.json +8 -2
package/AGENTS.md
CHANGED
|
@@ -52,7 +52,8 @@ components are exempt — they *are* the primitives.
|
|
|
52
52
|
| `./layout` | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, … |
|
|
53
53
|
| `./calendar` | `CalendarView` (time-zone-aware month grid + phone agenda) and its Svelte-free date model (`toEntries`, `layoutMonth`, `dateKeyInZone`, …); deprecated `Calendar`, `DayView` |
|
|
54
54
|
| `./chat` | `MessageBubble`, `ReactionPicker`, `TypingIndicator` |
|
|
55
|
-
| `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, plus the transport-neutral control interaction registry |
|
|
55
|
+
| `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, `CameraCapture`/`SignaturePad` capture, plus the transport-neutral control interaction registry |
|
|
56
|
+
| `./form-retry` | **Svelte-free** form retry (#3291): submission key, in-flight refusal, conditional reset, opt-in restore-after-reload, browser `runOnce()` digest. Also re-exported from `./forms`. Framework-free TS typed structurally against SvelteKit's `SubmitFunction` — never import `@sveltejs/kit` here. Guide: `docs/content/form-retry.md` |
|
|
56
57
|
| `./i18n` | i18n **client**: `useI18n`, `<Trans>`, `defineMessages`, `renderTemplate` (no `smrt-languages` import — the server resolver stays in `smrt-svelte/i18n/server`) |
|
|
57
58
|
| `./registry` | `ModuleUIRegistry` for cross-package component discovery |
|
|
58
59
|
| `./theme` | deprecated compatibility path forwarding to the canonical theme system |
|
|
@@ -153,6 +154,15 @@ other text pairing clears WCAG AA in both schemes.
|
|
|
153
154
|
instants into the zone once (`dateKeyInZone`) and do all grid/span math on
|
|
154
155
|
keys; never `new Date(y, m, d)` / `getDay()` in the browser zone. Months are
|
|
155
156
|
1-12 in the public API (`CalendarMonth`), unlike `Date#getMonth`.
|
|
157
|
+
- **Capture controls post natively.** `CameraCapture` and `SignaturePad` fill
|
|
158
|
+
a hidden named file input via `DataTransfer`, falling back to a `formdata`
|
|
159
|
+
listener (`native-file-field.ts`). Keep camera lifecycle in the
|
|
160
|
+
framework-free `camera-capture-session.ts` and the pointer gate in
|
|
161
|
+
`signature-pad-logic.ts`; their effects must not read rendered state. The
|
|
162
|
+
`<input capture>` path stays opt-in (`fileInputFallback`), off by default.
|
|
163
|
+
A form `reset` empties a native file input, so both clear their capture on
|
|
164
|
+
their form's reset (`attachFormResetListener`), even while `disabled`;
|
|
165
|
+
otherwise the UI shows a file the next submit no longer posts.
|
|
156
166
|
|
|
157
167
|
- **i18n split**: the client (here) is dependency-free; the Node-only server
|
|
158
168
|
resolver (`buildI18nSnapshot`, → `@happyvertical/smrt-languages`) stays in
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ pnpm add @happyvertical/smrt-ui
|
|
|
33
33
|
| Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput`, `SearchInput` |
|
|
34
34
|
| Choices | `Checkbox`, `RadioGroup`/`Radio`, `Switch`, `Toggle`, `ToggleButton`, `SegmentedControl` |
|
|
35
35
|
| Values and files | `Slider`, `RangeSlider`, `DatePicker`, `TimePicker`, `FilePicker` |
|
|
36
|
+
| Capture | `CameraCapture`, `SignaturePad` |
|
|
36
37
|
| Actions and display | `Button`, `Dropdown`/`Menu`, `Badge`, `Chip`, `Avatar`, `Card`, `Skeleton`, `Tooltip`, `Tree` |
|
|
37
38
|
| Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `PhoneSheet`, `ConfirmDialog` |
|
|
38
39
|
| Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay`, `WorkingStrip` |
|
|
@@ -91,12 +92,65 @@ kept in view. It carries `data-shell-tabs`, so AdminShell keeps it sticky
|
|
|
91
92
|
under the phone top bar. Pair it with `useLinkSurface` (smrt-svelte) so
|
|
92
93
|
agents can switch tabs too.
|
|
93
94
|
|
|
94
|
-
Use the focused subpaths (`/forms`, `/ui`, `/feedback`, `/data`,
|
|
95
|
+
Use the focused subpaths (`/forms`, `/form-retry`, `/ui`, `/feedback`, `/data`,
|
|
95
96
|
`/data-surface`, `/layout`, `/themes`) to keep imports explicit. The
|
|
96
97
|
Svelte-free `/data-surface` entry exposes the registry contracts and shared
|
|
97
|
-
protocol limits for server adapters
|
|
98
|
+
protocol limits for server adapters; the Svelte-free `/form-retry` entry
|
|
99
|
+
exposes the form-retry helper. The package root remains a compatibility
|
|
98
100
|
barrel.
|
|
99
101
|
|
|
102
|
+
### Calendar dates and shop time
|
|
103
|
+
|
|
104
|
+
`DateDisplay` accepts `timeZone` (an IANA id such as `America/Edmonton`) for
|
|
105
|
+
instant inputs (`Date`, numeric timestamps, and timestamp strings). Omit it to
|
|
106
|
+
keep browser-local absolute formatting and the existing duration-based relative
|
|
107
|
+
format. With an explicit zone, relative today/yesterday/tomorrow boundaries use
|
|
108
|
+
calendar days in that zone, including daylight saving transitions.
|
|
109
|
+
|
|
110
|
+
A bare `YYYY-MM-DD` string always denotes that calendar date, independent of the
|
|
111
|
+
viewer or shop zone. It formats in UTC, retains a date-only `datetime` attribute,
|
|
112
|
+
and ignores `showTime` because it contains no time. Relative calendar-date
|
|
113
|
+
labels compare it with today's date in the supplied zone (or the browser zone).
|
|
114
|
+
Impossible calendar dates render `fallback`. Invalid explicit zones used for
|
|
115
|
+
instant or relative formatting also render `fallback` rather than throwing.
|
|
116
|
+
|
|
117
|
+
### Combobox form submission
|
|
118
|
+
|
|
119
|
+
A named `Combobox` submits its selected option value through a hidden native
|
|
120
|
+
input. Its visible search text and option label are display-only. With
|
|
121
|
+
`allowCustom`, typed text becomes the submitted value; otherwise searching keeps
|
|
122
|
+
the last committed selection. A named empty selection submits an empty string;
|
|
123
|
+
controls with no name or an empty name are omitted. Disabled controls, including
|
|
124
|
+
those inside disabled fieldsets, are omitted by native `FormData`. Native form
|
|
125
|
+
reset restores the initial selection and its label, unless reset is canceled.
|
|
126
|
+
|
|
127
|
+
### MultiSelect form submission
|
|
128
|
+
|
|
129
|
+
A named `MultiSelect` submits one hidden native input per selected option value,
|
|
130
|
+
using repeated field names in selection order. Read them with `FormData.getAll`.
|
|
131
|
+
Option labels are display-only, and numeric option values submit as strings.
|
|
132
|
+
Empty selections, missing or empty names, and disabled controls (including
|
|
133
|
+
ancestor fieldsets) contribute no entries. Native reset restores the initial
|
|
134
|
+
selection unless reset is canceled.
|
|
135
|
+
|
|
136
|
+
### Listbox form submission
|
|
137
|
+
|
|
138
|
+
A named `Listbox` submits one hidden native input containing its selected option
|
|
139
|
+
value, with numeric values encoded as strings. A named listbox without a
|
|
140
|
+
selection submits an empty string. Missing or empty names and disabled controls
|
|
141
|
+
(including ancestor fieldsets) are omitted. Native reset restores the initial
|
|
142
|
+
selection unless reset is canceled.
|
|
143
|
+
|
|
144
|
+
### Status badge tones
|
|
145
|
+
|
|
146
|
+
`StatusBadge` accepts `tone="success"`, `"warning"`, `"danger"`, `"info"`, or
|
|
147
|
+
`"neutral"` for custom status vocabulary such as `awaiting_cert`. An explicit
|
|
148
|
+
tone overrides the built-in domain scheme while label, size, and outline variant
|
|
149
|
+
continue to work independently. Tones use the active theme's paired container
|
|
150
|
+
and text tokens. Without a tone, known domain statuses retain their existing
|
|
151
|
+
colors and unknown statuses retain the neutral fallback. `StatusTone` is exported
|
|
152
|
+
as a TypeScript type from the package root.
|
|
153
|
+
|
|
100
154
|
### Touch targets for Checkbox, Radio and Switch
|
|
101
155
|
|
|
102
156
|
`Checkbox`, `Radio` and `Switch` have an invisible 44x44px hit area around the
|
|
@@ -229,6 +283,34 @@ Application-specific editors, maps, charts, media workbenches, and domain
|
|
|
229
283
|
records remain composites built from this foundation rather than generic base
|
|
230
284
|
components.
|
|
231
285
|
|
|
286
|
+
## Native and enhanced forms
|
|
287
|
+
|
|
288
|
+
`Form` forwards native form attributes and Svelte attachments through its rest
|
|
289
|
+
props to the underlying `<form>`. For a SvelteKit action, adapt `enhance` with
|
|
290
|
+
`fromAction` and set `preventDefault={false}` so the enhancement handles submission:
|
|
291
|
+
|
|
292
|
+
```svelte
|
|
293
|
+
<script lang="ts">
|
|
294
|
+
import { enhance } from '$app/forms';
|
|
295
|
+
import { fromAction } from 'svelte/attachments';
|
|
296
|
+
import { Form, Input } from '@happyvertical/smrt-ui/forms';
|
|
297
|
+
</script>
|
|
298
|
+
|
|
299
|
+
<Form method="POST" action="?/save" preventDefault={false} {@attach fromAction(enhance)}>
|
|
300
|
+
<Input name="displayName" />
|
|
301
|
+
<button type="submit">Save</button>
|
|
302
|
+
</Form>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
For a customized enhancement, pass its callback as a getter:
|
|
306
|
+
`{@attach fromAction(enhance, () => submitFunction)}`. The attachment runs on the
|
|
307
|
+
native element and cleans up when it unmounts; the interaction registry and
|
|
308
|
+
staged-review surface remain available. A component reference obtained with
|
|
309
|
+
`bind:this` exposes `getFormElement(): HTMLFormElement | null`, which returns
|
|
310
|
+
`null` before mount and after unmount. For ordinary browser GET/POST forms, use
|
|
311
|
+
`preventDefault={false}` without an attachment. The default remains `true` for
|
|
312
|
+
existing handler-driven forms.
|
|
313
|
+
|
|
232
314
|
## Agent-addressable forms
|
|
233
315
|
|
|
234
316
|
`Form` can expose its controls to a chat, voice, tutorial, or test adapter
|
|
@@ -362,6 +444,130 @@ disabled, read-only, file, or password controls), and staging only ever
|
|
|
362
444
|
creates reviewable proposals. smrt-svelte's `<Form webmcp>` and `FormScope`
|
|
363
445
|
build their `*_stage_changes` tool from these.
|
|
364
446
|
|
|
447
|
+
## Form retry
|
|
448
|
+
|
|
449
|
+
`createFormRetry()` is the browser half of `runOnce()` (`@happyvertical/smrt-core`)
|
|
450
|
+
for a SvelteKit enhanced form: a per-tab, per-form submission key, a refused
|
|
451
|
+
second submit while one is in flight, typed values kept across a validation or
|
|
452
|
+
transport failure, a reset after success only when the fields are unchanged
|
|
453
|
+
since submit, and opt-in restore after a reload (a file that cannot be restored
|
|
454
|
+
is reported, never silently dropped). It is framework-free and imports neither
|
|
455
|
+
Svelte nor SvelteKit; the submit function is typed against SvelteKit's
|
|
456
|
+
`SubmitFunction` shape structurally.
|
|
457
|
+
|
|
458
|
+
```svelte
|
|
459
|
+
<script lang="ts">
|
|
460
|
+
import { enhance } from '$app/forms';
|
|
461
|
+
import { createFormRetry } from '@happyvertical/smrt-ui/form-retry';
|
|
462
|
+
|
|
463
|
+
const retry = createFormRetry({ form: 'report' });
|
|
464
|
+
</script>
|
|
465
|
+
|
|
466
|
+
<form method="POST" use:enhance={retry.enhance()} {@attach retry.attach}>
|
|
467
|
+
<input name="title" />
|
|
468
|
+
<button type="submit">Send</button>
|
|
469
|
+
{#if $retry.status === 'transport-error'}<p role="alert">Send it again unchanged.</p>{/if}
|
|
470
|
+
</form>
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
With smrt-ui's own `Form`, attach the same two pieces to it (see
|
|
474
|
+
[Native and enhanced forms](#native-and-enhanced-forms)):
|
|
475
|
+
`<Form method="POST" preventDefault={false} {@attach fromAction(enhance, () => retry.enhance())} {@attach retry.attach}>`.
|
|
476
|
+
|
|
477
|
+
`/forms` re-exports the same API as the Svelte-free `/form-retry` entry. See the
|
|
478
|
+
[form retry guide](../../docs/content/form-retry.md) for the server half, the
|
|
479
|
+
per-result table, storage and private-window behaviour, and restore.
|
|
480
|
+
|
|
481
|
+
## Camera and signature capture
|
|
482
|
+
|
|
483
|
+
`CameraCapture` takes a photo from the device camera and `SignaturePad` takes a
|
|
484
|
+
signature, each inside an ordinary form. Both live in `/forms`, need only
|
|
485
|
+
browser APIs, and post their file through a plain multipart form when given a
|
|
486
|
+
`name`; the page wires no hidden input and no submit handler.
|
|
487
|
+
|
|
488
|
+
```svelte
|
|
489
|
+
<script lang="ts">
|
|
490
|
+
import { CameraCapture, SignaturePad } from '@happyvertical/smrt-ui/forms';
|
|
491
|
+
</script>
|
|
492
|
+
|
|
493
|
+
<form method="POST" enctype="multipart/form-data">
|
|
494
|
+
<CameraCapture name="photo" facingMode="environment" />
|
|
495
|
+
<SignaturePad name="signature" stylusOnly={settings.signatureStylusOnly} />
|
|
496
|
+
<button type="submit">Send</button>
|
|
497
|
+
</form>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**CameraCapture** uses `getUserMedia` with a live preview. The user takes a
|
|
501
|
+
photo, reviews it, and can retake it before "Use photo" commits it. Committing
|
|
502
|
+
calls `onCapture({ blob, dataUrl })` and fills the named field (`photo.jpg` by
|
|
503
|
+
default; set `fileName`, `imageType`, and `quality` to change it). "Retake"
|
|
504
|
+
after a commit empties the field and calls `onClear`. The root's `data-state`
|
|
505
|
+
is one of `starting`, `streaming`, `reviewing`, `committed`, `off` (disabled),
|
|
506
|
+
`permission-denied`, `no-camera`, `unsupported`, `error`, or `fallback`. Each
|
|
507
|
+
problem state has its own copy and, where it can help, a "Try again" action.
|
|
508
|
+
`getUserMedia` exists only in a secure context, so a page served over plain
|
|
509
|
+
HTTP renders `unsupported`.
|
|
510
|
+
|
|
511
|
+
The stream's tracks are stopped on unmount, when `disabled` turns on, when
|
|
512
|
+
`facingMode` changes (the camera is then requested again), and as soon as a
|
|
513
|
+
frame is captured. A permission prompt answered after unmount or disable is
|
|
514
|
+
discarded and its stream released. The lifecycle lives in the framework-free
|
|
515
|
+
`createCameraSession()` (exported with `classifyGetUserMediaError()` and
|
|
516
|
+
`isCameraApiSupported()`), so it can be tested against a fake `MediaDevices`.
|
|
517
|
+
|
|
518
|
+
`fileInputFallback` is opt-in and off by default. With it on, browsers without
|
|
519
|
+
`getUserMedia` render an `<input type="file" accept="image/*" capture>` that
|
|
520
|
+
carries `name` itself (`disabled` blocks it with `aria-disabled` rather than
|
|
521
|
+
the native attribute, so a committed photo keeps posting). It opens the
|
|
522
|
+
operating system's picker, which has no
|
|
523
|
+
live preview and can offer the gallery, so it is not a substitute for the
|
|
524
|
+
camera flow. With it off, those browsers render the `unsupported` state.
|
|
525
|
+
|
|
526
|
+
**SignaturePad** draws on its own canvas. "Use signature" stays disabled until
|
|
527
|
+
an accepted stroke exists, then returns a PNG through `onCapture({ blob,
|
|
528
|
+
dataUrl })` and fills the named field (`signature.png` by default). A committed
|
|
529
|
+
pad is locked until "Clear", which empties the field and calls `onClear`.
|
|
530
|
+
`stylusOnly` is a prop the caller resolves; with it on, only `pointerType ===
|
|
531
|
+
'pen'` draws. `isAcceptedPointerType()` and `mapPointerToCanvasPoint()` are
|
|
532
|
+
exported as pure functions. The pad is dark ink on white paper in every theme
|
|
533
|
+
and colour scheme, and the exported PNG is opaque white, so it reads the same
|
|
534
|
+
wherever it is shown.
|
|
535
|
+
|
|
536
|
+
**Native form posting.** The named field is a hidden `<input type="file">`
|
|
537
|
+
filled through `DataTransfer` (Chrome 60+, Firefox 62+, Safari 14.1+). Where
|
|
538
|
+
`DataTransfer` cannot be constructed or assigned, the field drops its `name`
|
|
539
|
+
and a capture-phase `formdata` listener appends the file while the browser
|
|
540
|
+
builds the request, which covers native navigation submits as well as
|
|
541
|
+
`new FormData(form)`. Where neither API exists, nothing is posted and
|
|
542
|
+
`onCapture` is the only channel. The root's `data-smrt-file-field` attribute
|
|
543
|
+
reports `data-transfer`, `formdata-event`, `file-input`, or `none`. Before a
|
|
544
|
+
commit the field posts what an empty native file input posts. `disabled`
|
|
545
|
+
freezes the controls but keeps a committed file in the submission; unmount the
|
|
546
|
+
component to drop it, or put it in a disabled `<fieldset>`, which leaves the
|
|
547
|
+
field out of the submission (committed or empty) in every posting strategy.
|
|
548
|
+
|
|
549
|
+
A reset of the owning form (`form.reset()`, a reset button, or SvelteKit
|
|
550
|
+
`enhance`'s `update()` after a success, including `createFormRetry()`'s
|
|
551
|
+
conditional reset) empties the field as it empties a native file input, in
|
|
552
|
+
every posting strategy. A committed photo or signature is discarded as
|
|
553
|
+
"Retake" or "Clear" would and `onClear` is called. A photo under review or
|
|
554
|
+
ink not yet committed is discarded too, without `onClear`, so the next entry
|
|
555
|
+
cannot attach the previous one's capture. `CameraCapture` returns to the live
|
|
556
|
+
camera (`off` while `disabled`, the emptied picker with `fileInputFallback`)
|
|
557
|
+
and supersedes a frame still encoding; with nothing held, a reset changes
|
|
558
|
+
nothing and never re-prompts for the camera. `SignaturePad` returns to blank,
|
|
559
|
+
unlocked paper. A reset clears both even while `disabled`, as it clears a
|
|
560
|
+
disabled native input, and a reset a later listener cancels still leaves the
|
|
561
|
+
component and the posted field both empty.
|
|
562
|
+
|
|
563
|
+
Text comes from the `ui.camera_capture.*` and `ui.signature_pad.*` i18n keys
|
|
564
|
+
and can be overridden per instance with `labels`. Capture is a touch flow, so
|
|
565
|
+
its actions (and the fallback picker) are touch targets at every density: at
|
|
566
|
+
least `--smrt-touch-target-min` (48px by default; see
|
|
567
|
+
[Touch density](#touch-density)), which ThemeProvider `overrides` can raise.
|
|
568
|
+
Both register with an enclosing `Form`'s interaction registry as non-readable,
|
|
569
|
+
non-writable `file` controls.
|
|
570
|
+
|
|
365
571
|
## DataTable controller
|
|
366
572
|
|
|
367
573
|
`DataTable` can share one headless `DataTableController` between rendered
|
|
@@ -789,3 +995,95 @@ pnpm test
|
|
|
789
995
|
pnpm build
|
|
790
996
|
pnpm verify:pack
|
|
791
997
|
```
|
|
998
|
+
|
|
999
|
+
### Touch density
|
|
1000
|
+
|
|
1001
|
+
Set `<ThemeProvider density="touch">` for floor tablets and phones, or set
|
|
1002
|
+
`density="touch"` on Input, Select, Textarea, Checkbox, Switch, RadioGroup,
|
|
1003
|
+
Button (including links), FilterChips, SegmentedControl, or DataTable. DataTable
|
|
1004
|
+
applies density to its sort buttons. Density is independent of existing `size`
|
|
1005
|
+
props, including native Input/Select sizes. Omitted density inherits; explicitly
|
|
1006
|
+
setting `density="comfortable"` opts that control out and keeps its usual size.
|
|
1007
|
+
RadioGroup expands each option's clickable label, and Checkbox/Switch expand
|
|
1008
|
+
the label hit area while preserving the visual mark.
|
|
1009
|
+
|
|
1010
|
+
`--smrt-touch-target-min` defaults to `48px` across all presets and works without
|
|
1011
|
+
a provider. Customize it globally with ThemeProvider's `overrides`, for example
|
|
1012
|
+
`overrides={{ '--smrt-touch-target-min': '56px' }}`. Touch targets grow with larger
|
|
1013
|
+
content; normal density keeps existing component sizing. `CameraCapture` and
|
|
1014
|
+
`SignaturePad` actions use `--smrt-touch-target-min` at every density, since
|
|
1015
|
+
capture is a touch flow. TenantNav sizing is
|
|
1016
|
+
tracked separately in [#3246](https://github.com/happyvertical/smrt/issues/3246).
|
|
1017
|
+
|
|
1018
|
+
### Narrow DataTable
|
|
1019
|
+
|
|
1020
|
+
Set `responsiveMode="hide-columns"` to adapt to the table's container width.
|
|
1021
|
+
The default `responsiveBreakpoint={800}` includes both 768px tablets and 390px
|
|
1022
|
+
phones. Narrow mode budgets one column per `responsiveColumnMinWidth={160}`
|
|
1023
|
+
pixels, after reserving space for selection and expansion controls. Higher
|
|
1024
|
+
`column.responsive.priority` values survive first (missing/nonfinite values are
|
|
1025
|
+
zero); ties preserve declared display order. `responsive.keepVisible` columns
|
|
1026
|
+
always survive responsive collapse, while explicitly hidden columns stay hidden.
|
|
1027
|
+
|
|
1028
|
+
Retained cells wrap, and their desktop width and pinning settings resume when
|
|
1029
|
+
the container widens. Responsive presentation never changes controller state,
|
|
1030
|
+
sorting, selection, or persisted column visibility. Header groups and structural
|
|
1031
|
+
row colspans follow the retained columns. If keepVisible columns exceed the
|
|
1032
|
+
budget, all remain visible and share the available width. Custom cell/header
|
|
1033
|
+
snippets should fit their cells. Default `responsiveMode="scroll"` preserves
|
|
1034
|
+
existing horizontal scrolling.
|
|
1035
|
+
|
|
1036
|
+
ConfirmDialog opens a native modal dialog above existing Modal and Drawer surfaces.
|
|
1037
|
+
Its `message` accepts plain text or a Svelte snippet (including lists and emphasis);
|
|
1038
|
+
each instance owns its accessible title and description ids. Escape and backdrop
|
|
1039
|
+
clicks request `oncancel`; the parent controls `open`. The opener regains focus
|
|
1040
|
+
on close. Buttons remain disabled while `loading`, and Escape stays available.
|
|
1041
|
+
The native top layer replaces the previous fixed div; confirmation now stacks
|
|
1042
|
+
above an already open modal and makes its background inert. Escape is scoped to
|
|
1043
|
+
the active dialog rather than handled globally. Existing `open`, `loading` and
|
|
1044
|
+
action callbacks retain their controlled-state contracts.
|
|
1045
|
+
|
|
1046
|
+
Browser feedback contracts run with `pnpm --filter @happyvertical/smrt-ui test:e2e`
|
|
1047
|
+
(after installing Playwright Chromium, or setting
|
|
1048
|
+
`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` to a local Chromium executable).
|
|
1049
|
+
|
|
1050
|
+
ToastViewport follows the topmost native modal dialog and restores its original
|
|
1051
|
+
host when all modals close. Its live region, dismiss buttons and actions stay
|
|
1052
|
+
interactive inside Modal, Drawer and ConfirmDialog. Hover and focus independently
|
|
1053
|
+
pause auto-dismiss, then resume the remaining duration. Configure
|
|
1054
|
+
`createToaster({ successDuration: 0 })` to keep success records until dismissal;
|
|
1055
|
+
per-toast `duration: 0` remains supported. `inset` accepts a CSS length, `anchor`
|
|
1056
|
+
accepts a content-region element, and `top-center`/`bottom-center` positions center
|
|
1057
|
+
the viewport within that region (or the window). Anchor geometry follows resize
|
|
1058
|
+
and scroll. Custom injected Toaster implementations may optionally implement
|
|
1059
|
+
`pause(id, reason)` and `resume(id, reason)` to support interaction pausing.
|
|
1060
|
+
|
|
1061
|
+
### SegmentedControl forms
|
|
1062
|
+
|
|
1063
|
+
SegmentedControl renders native radio inputs. Set `name` to post the selected
|
|
1064
|
+
option value with ordinary form submission or FormData; numeric values post as
|
|
1065
|
+
strings while bound values retain their declared type. Unselected, unknown,
|
|
1066
|
+
disabled-option, disabled-control and disabled-fieldset values are omitted.
|
|
1067
|
+
`required` uses native form validation and accepts numeric zero. Nameless controls
|
|
1068
|
+
validate without posting a generated field. Arrow keys cycle enabled options;
|
|
1069
|
+
Home/End select the first/last enabled option. Touch density applies to the full
|
|
1070
|
+
clickable segment.
|
|
1071
|
+
|
|
1072
|
+
Native form reset restores the initial bound value if that option is still
|
|
1073
|
+
available and enabled; otherwise it clears the selection. Canceling the reset
|
|
1074
|
+
event preserves the current selection. Reset does not fire `onvaluechange`,
|
|
1075
|
+
matching native controls; bind:value reflects it.
|
|
1076
|
+
|
|
1077
|
+
### Application icons
|
|
1078
|
+
|
|
1079
|
+
Icon includes the original menu/search/chevron/action glyphs plus `alert`,
|
|
1080
|
+
`warning`, `info`, `home`, `user`, `settings`, `trash`, `edit`, `calendar`,
|
|
1081
|
+
`clock`, `camera`, `upload`, and `download`. Import `registerIcons` from
|
|
1082
|
+
`@happyvertical/smrt-ui` to install an application SVG path map at startup.
|
|
1083
|
+
Registrations update mounted icons; the returned cleanup function removes that
|
|
1084
|
+
registration and restores the previous active set. Last active registration wins,
|
|
1085
|
+
and an explicit Icon `path` prop wins over every named set. Unknown names retain
|
|
1086
|
+
their empty shape. Names and paths must be nonempty strings; invalid mixed sets
|
|
1087
|
+
are rejected atomically. Sets are snapshotted, so later caller mutations do not
|
|
1088
|
+
change glyphs. Register static application assets, never request or identity data,
|
|
1089
|
+
and install the same application set for SSR and client hydration.
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type DataTableResolvedColumn,
|
|
36
36
|
resolveDataTableLayout,
|
|
37
37
|
} from './DataTableLayout.js';
|
|
38
|
+
import { responsiveColumns } from './DataTableResponsive.js';
|
|
38
39
|
import {
|
|
39
40
|
maximumDataTableVirtualScrollTop,
|
|
40
41
|
resolveDataTableVirtualWindow,
|
|
@@ -103,6 +104,10 @@ let {
|
|
|
103
104
|
empty,
|
|
104
105
|
rowClass,
|
|
105
106
|
size = 'md',
|
|
107
|
+
density,
|
|
108
|
+
responsiveMode = 'scroll',
|
|
109
|
+
responsiveBreakpoint = 800,
|
|
110
|
+
responsiveColumnMinWidth = 160,
|
|
106
111
|
striped = false,
|
|
107
112
|
hoverable = true,
|
|
108
113
|
stickyHeader = false,
|
|
@@ -775,6 +780,7 @@ function updateOverflowState() {
|
|
|
775
780
|
const container = tableContainer;
|
|
776
781
|
if (!container) return;
|
|
777
782
|
|
|
783
|
+
containerWidth = container.clientWidth;
|
|
778
784
|
const maxScrollLeft = Math.max(
|
|
779
785
|
0,
|
|
780
786
|
container.scrollWidth - container.clientWidth,
|
|
@@ -836,9 +842,30 @@ function setIndeterminate(node: HTMLInputElement, value: boolean) {
|
|
|
836
842
|
};
|
|
837
843
|
}
|
|
838
844
|
|
|
839
|
-
|
|
845
|
+
let containerWidth = $state(0);
|
|
846
|
+
const isNarrow = $derived(
|
|
847
|
+
responsiveMode === 'hide-columns' &&
|
|
848
|
+
containerWidth > 0 &&
|
|
849
|
+
containerWidth <=
|
|
850
|
+
(Number.isFinite(responsiveBreakpoint) && responsiveBreakpoint > 0
|
|
851
|
+
? responsiveBreakpoint
|
|
852
|
+
: 800),
|
|
853
|
+
);
|
|
854
|
+
const declaredLayout = $derived(
|
|
840
855
|
resolveDataTableLayout(columns, constrainedLayoutState, measuredColumnWidths),
|
|
841
856
|
);
|
|
857
|
+
const dataTableLayout = $derived(
|
|
858
|
+
isNarrow
|
|
859
|
+
? resolveDataTableLayout(
|
|
860
|
+
responsiveColumns(
|
|
861
|
+
declaredLayout.columns.map((entry) => entry.column),
|
|
862
|
+
containerWidth - (selectable ? 48 : 0) - (expandedContent ? 48 : 0),
|
|
863
|
+
responsiveColumnMinWidth,
|
|
864
|
+
),
|
|
865
|
+
{ ...constrainedLayoutState, columnWidths: [], columnPinning: [] },
|
|
866
|
+
)
|
|
867
|
+
: declaredLayout,
|
|
868
|
+
);
|
|
842
869
|
const visibleColumns = $derived(dataTableLayout.columns);
|
|
843
870
|
const dataBodyStructuralRows = $derived(
|
|
844
871
|
structuralRows.filter((row) => row.kind !== 'footer'),
|
|
@@ -1222,6 +1249,7 @@ function getCellValue(row: T, column: DataTableColumn<T>): unknown {
|
|
|
1222
1249
|
function widthForColumn(
|
|
1223
1250
|
column: DataTableResolvedColumn<T>,
|
|
1224
1251
|
): string | undefined {
|
|
1252
|
+
if (isNarrow) return undefined;
|
|
1225
1253
|
return column.width === undefined ? column.column.width : `${column.width}px`;
|
|
1226
1254
|
}
|
|
1227
1255
|
|
|
@@ -1390,10 +1418,18 @@ $effect(() => {
|
|
|
1390
1418
|
void displayRows.length;
|
|
1391
1419
|
if (!container) return;
|
|
1392
1420
|
|
|
1421
|
+
let resizeFrame: number | undefined;
|
|
1422
|
+
const scheduleOverflowUpdate = () => {
|
|
1423
|
+
if (resizeFrame !== undefined) return;
|
|
1424
|
+
resizeFrame = requestAnimationFrame(() => {
|
|
1425
|
+
resizeFrame = undefined;
|
|
1426
|
+
updateOverflowState();
|
|
1427
|
+
});
|
|
1428
|
+
};
|
|
1393
1429
|
const resizeObserver =
|
|
1394
1430
|
typeof ResizeObserver === 'undefined'
|
|
1395
1431
|
? undefined
|
|
1396
|
-
: new ResizeObserver(
|
|
1432
|
+
: new ResizeObserver(scheduleOverflowUpdate);
|
|
1397
1433
|
resizeObserver?.observe(container);
|
|
1398
1434
|
const table = container.querySelector('table');
|
|
1399
1435
|
if (table) resizeObserver?.observe(table);
|
|
@@ -1402,6 +1438,7 @@ $effect(() => {
|
|
|
1402
1438
|
|
|
1403
1439
|
return () => {
|
|
1404
1440
|
resizeObserver?.disconnect();
|
|
1441
|
+
if (resizeFrame !== undefined) cancelAnimationFrame(resizeFrame);
|
|
1405
1442
|
window.removeEventListener('resize', updateOverflowState);
|
|
1406
1443
|
};
|
|
1407
1444
|
});
|
|
@@ -1411,7 +1448,9 @@ $effect(() => {
|
|
|
1411
1448
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex: the region provides horizontal or virtual keyboard scrolling. -->
|
|
1412
1449
|
<div
|
|
1413
1450
|
bind:this={tableContainer}
|
|
1451
|
+
data-density={density}
|
|
1414
1452
|
class="data-table-container"
|
|
1453
|
+
class:data-table-container--narrow={isNarrow}
|
|
1415
1454
|
class:data-table-container--sticky={stickyHeader || virtualizedBody}
|
|
1416
1455
|
class:data-table-container--overflowing={hasHorizontalOverflow}
|
|
1417
1456
|
class:data-table-container--virtualized={virtualizationWindow.enabled}
|
|
@@ -1503,8 +1542,8 @@ $effect(() => {
|
|
|
1503
1542
|
data-keep-visible={column.responsive?.keepVisible ? 'true' : undefined}
|
|
1504
1543
|
use:observeColumnWidth={column.id}
|
|
1505
1544
|
style:width={widthForColumn(resolvedColumn)}
|
|
1506
|
-
style:min-width={column.minWidth}
|
|
1507
|
-
style:max-width={column.maxWidth}
|
|
1545
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1546
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1508
1547
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1509
1548
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1510
1549
|
style:text-align={column.align}
|
|
@@ -1560,7 +1599,7 @@ $effect(() => {
|
|
|
1560
1599
|
{:else}
|
|
1561
1600
|
{column.label}
|
|
1562
1601
|
{/if}
|
|
1563
|
-
{#if column.resizable}
|
|
1602
|
+
{#if column.resizable && !isNarrow}
|
|
1564
1603
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions: a focusable ARIA separator is the resize control. -->
|
|
1565
1604
|
<span
|
|
1566
1605
|
class="data-table__resize-handle"
|
|
@@ -1686,8 +1725,8 @@ $effect(() => {
|
|
|
1686
1725
|
data-responsive-priority={column.responsive?.priority}
|
|
1687
1726
|
data-keep-visible={column.responsive?.keepVisible ? 'true' : undefined}
|
|
1688
1727
|
style:width={widthForColumn(resolvedColumn)}
|
|
1689
|
-
style:min-width={column.minWidth}
|
|
1690
|
-
style:max-width={column.maxWidth}
|
|
1728
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1729
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1691
1730
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1692
1731
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1693
1732
|
style:text-align={column.align}
|
|
@@ -1743,8 +1782,8 @@ $effect(() => {
|
|
|
1743
1782
|
scope="row"
|
|
1744
1783
|
data-column-id={column.id}
|
|
1745
1784
|
style:width={widthForColumn(resolvedColumn)}
|
|
1746
|
-
style:min-width={column.minWidth}
|
|
1747
|
-
style:max-width={column.maxWidth}
|
|
1785
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1786
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1748
1787
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1749
1788
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1750
1789
|
style:text-align={column.align}
|
|
@@ -1764,8 +1803,8 @@ $effect(() => {
|
|
|
1764
1803
|
class:data-table__cell--pinned-end={resolvedColumn.pin === 'end'}
|
|
1765
1804
|
data-column-id={column.id}
|
|
1766
1805
|
style:width={widthForColumn(resolvedColumn)}
|
|
1767
|
-
style:min-width={column.minWidth}
|
|
1768
|
-
style:max-width={column.maxWidth}
|
|
1806
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1807
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1769
1808
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1770
1809
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1771
1810
|
style:text-align={column.align}
|
|
@@ -1807,8 +1846,8 @@ $effect(() => {
|
|
|
1807
1846
|
scope="row"
|
|
1808
1847
|
data-column-id={column.id}
|
|
1809
1848
|
style:width={widthForColumn(resolvedColumn)}
|
|
1810
|
-
style:min-width={column.minWidth}
|
|
1811
|
-
style:max-width={column.maxWidth}
|
|
1849
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1850
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1812
1851
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1813
1852
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1814
1853
|
style:text-align={column.align}
|
|
@@ -1828,8 +1867,8 @@ $effect(() => {
|
|
|
1828
1867
|
class:data-table__cell--pinned-end={resolvedColumn.pin === 'end'}
|
|
1829
1868
|
data-column-id={column.id}
|
|
1830
1869
|
style:width={widthForColumn(resolvedColumn)}
|
|
1831
|
-
style:min-width={column.minWidth}
|
|
1832
|
-
style:max-width={column.maxWidth}
|
|
1870
|
+
style:min-width={isNarrow ? undefined : column.minWidth}
|
|
1871
|
+
style:max-width={isNarrow ? undefined : column.maxWidth}
|
|
1833
1872
|
style:left={resolvedColumn.pin === 'start' ? resolvedColumn.stickyOffset : undefined}
|
|
1834
1873
|
style:right={resolvedColumn.pin === 'end' ? resolvedColumn.stickyOffset : undefined}
|
|
1835
1874
|
style:text-align={column.align}
|
|
@@ -2292,4 +2331,10 @@ $effect(() => {
|
|
|
2292
2331
|
animation: none;
|
|
2293
2332
|
}
|
|
2294
2333
|
}
|
|
2334
|
+
.data-table-container[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
|
|
2335
|
+
.data-table-container[data-density='comfortable'] { --smrt-control-target-min: 0px; }
|
|
2336
|
+
.data-table__sort-button { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
|
|
2337
|
+
.data-table-container--narrow .data-table { table-layout: fixed; }
|
|
2338
|
+
.data-table-container--narrow .data-table__cell { white-space: normal; overflow-wrap: anywhere; }
|
|
2339
|
+
.data-table-container--narrow .data-table__sort-button { max-width: 100%; text-align: left; white-space: normal; overflow-wrap: anywhere; }
|
|
2295
2340
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTable.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,OAAO,EAAa,KAAK,OAAO,EAAiB,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"DataTable.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTable.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,OAAO,EAAa,KAAK,OAAO,EAAiB,MAAM,QAAQ,CAAC;AAoChE,OAAO,KAAK,EACV,eAAe,EAEf,cAAc,EAGf,MAAM,YAAY,CAAC;AAIpB,UAAU,aAAa,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC;IAClD,+DAA+D;IAC/D,IAAI,CAAC,EAAE,OAAO,CACZ;QAAC;YAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;YAAC,GAAG,EAAE,CAAC,CAAC;YAAC,KAAK,EAAE,OAAO,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE;KAAC,CACxE,CAAC;CACH;AAYC,iBAAS,QAAQ,CAAC,CAAC;WA6oDQ,aAAa,CAAC,CAAC,CAAC;;;;;EAAgH;AAC7J,cAAM,iBAAiB,CAAC,CAAC;IACrB,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5X,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3H,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,SAAS,EAAE,qBAAmC,CAAC;AACnC,KAAK,SAAS,CAAC,CAAC,IAAI,YAAY,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DataTableColumn } from './types.js';
|
|
2
|
+
/** Choose narrow-mode columns without changing their declared display order. */
|
|
3
|
+
export declare function responsiveColumns<T>(columns: readonly DataTableColumn<T>[], width: number, minimumWidth: number): DataTableColumn<T>[];
|
|
4
|
+
//# sourceMappingURL=DataTableResponsive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTableResponsive.d.ts","sourceRoot":"","sources":["../../../src/components/data/DataTableResponsive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,OAAO,EAAE,SAAS,eAAe,CAAC,CAAC,CAAC,EAAE,EACtC,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GACnB,eAAe,CAAC,CAAC,CAAC,EAAE,CAuBtB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Choose narrow-mode columns without changing their declared display order. */
|
|
2
|
+
export function responsiveColumns(columns, width, minimumWidth) {
|
|
3
|
+
const target = Number.isFinite(minimumWidth) && minimumWidth > 0 ? minimumWidth : 160;
|
|
4
|
+
const budget = Math.max(1, Math.floor(Math.max(0, width) / target));
|
|
5
|
+
const retained = new Set(columns
|
|
6
|
+
.filter((column) => column.responsive?.keepVisible)
|
|
7
|
+
.map((column) => column.id));
|
|
8
|
+
const ranked = columns
|
|
9
|
+
.map((column, index) => ({ column, index }))
|
|
10
|
+
.sort((a, b) => {
|
|
11
|
+
const priority = (column) => Number.isFinite(column.responsive?.priority)
|
|
12
|
+
? (column.responsive?.priority ?? 0)
|
|
13
|
+
: 0;
|
|
14
|
+
return priority(b.column) - priority(a.column) || a.index - b.index;
|
|
15
|
+
});
|
|
16
|
+
for (const { column } of ranked) {
|
|
17
|
+
if (retained.size >= budget)
|
|
18
|
+
break;
|
|
19
|
+
retained.add(column.id);
|
|
20
|
+
}
|
|
21
|
+
return columns.filter((column) => retained.has(column.id));
|
|
22
|
+
}
|