@happyvertical/smrt-ui 0.51.38 → 0.52.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/AGENTS.md +14 -2
- package/README.md +257 -5
- package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
- package/dist/actions/swipe-dismiss.d.ts +42 -0
- package/dist/actions/swipe-dismiss.d.ts.map +1 -0
- package/dist/actions/swipe-dismiss.js +69 -0
- package/dist/components/calendar/Calendar.svelte +5 -0
- package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
- package/dist/components/calendar/CalendarView.svelte +1250 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
- package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
- package/dist/components/calendar/DayView.svelte +5 -0
- package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
- package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
- package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
- package/dist/components/calendar/calendar-model.d.ts +178 -0
- package/dist/components/calendar/calendar-model.d.ts.map +1 -0
- package/dist/components/calendar/calendar-model.js +362 -0
- package/dist/components/calendar/index.d.ts +12 -1
- package/dist/components/calendar/index.d.ts.map +1 -1
- package/dist/components/calendar/index.js +11 -1
- package/dist/components/data/CollectionList.svelte +23 -5
- package/dist/components/data/CollectionList.svelte.d.ts +8 -2
- package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTable.svelte +118 -40
- package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
- package/dist/components/data/DataTableController.d.ts +4 -0
- package/dist/components/data/DataTableController.d.ts.map +1 -1
- package/dist/components/data/DataTableController.js +11 -4
- package/dist/components/data/ListSortSelect.svelte +121 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
- package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
- package/dist/components/data/SortableHeader.svelte +169 -0
- package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
- package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
- package/dist/components/data/__tests__/CollectionList.test.js +37 -0
- package/dist/components/data/__tests__/DataTable.test.js +76 -0
- package/dist/components/data/__tests__/DataTableController.test.js +35 -0
- package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
- package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
- package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
- package/dist/components/data/__tests__/list-sort.test.js +91 -0
- package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
- package/dist/components/data/data-surface.d.ts +1 -0
- package/dist/components/data/data-surface.d.ts.map +1 -1
- package/dist/components/data/data-surface.js +1 -0
- package/dist/components/data/index.d.ts +6 -0
- package/dist/components/data/index.d.ts.map +1 -1
- package/dist/components/data/index.js +6 -0
- package/dist/components/data/link-surface.d.ts +79 -0
- package/dist/components/data/link-surface.d.ts.map +1 -0
- package/dist/components/data/link-surface.js +155 -0
- package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
- package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
- package/dist/components/data/list-sort-state.svelte.js +47 -0
- package/dist/components/data/list-sort.d.ts +94 -0
- package/dist/components/data/list-sort.d.ts.map +1 -0
- package/dist/components/data/list-sort.js +182 -0
- package/dist/components/data/step-surface.d.ts +73 -0
- package/dist/components/data/step-surface.d.ts.map +1 -0
- package/dist/components/data/step-surface.js +177 -0
- package/dist/components/data/surface-navigation.d.ts +49 -0
- package/dist/components/data/surface-navigation.d.ts.map +1 -0
- package/dist/components/data/surface-navigation.js +100 -0
- package/dist/components/data/types.d.ts +19 -0
- package/dist/components/data/types.d.ts.map +1 -1
- package/dist/components/feedback/PhoneSheet.svelte +224 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
- package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
- package/dist/components/feedback/WorkingStrip.svelte +426 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
- package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
- package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
- package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
- package/dist/components/feedback/index.d.ts +4 -0
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/index.js +5 -0
- package/dist/components/feedback/working-status.d.ts +34 -0
- package/dist/components/feedback/working-status.d.ts.map +1 -0
- package/dist/components/feedback/working-status.js +5 -0
- package/dist/components/forms/Checkbox.svelte +26 -2
- package/dist/components/forms/Combobox.svelte +68 -12
- package/dist/components/forms/Combobox.svelte.d.ts +6 -0
- package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
- package/dist/components/forms/DictationButton.svelte +128 -0
- package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
- package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
- package/dist/components/forms/DictationStatus.svelte +164 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
- package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
- package/dist/components/forms/Fieldset.svelte +5 -1
- package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
- package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormActionBar.svelte +84 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
- package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
- package/dist/components/forms/Input.svelte +9 -0
- package/dist/components/forms/Radio.svelte +15 -4
- package/dist/components/forms/SearchInput.svelte +207 -0
- package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
- package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/Select.svelte +9 -0
- package/dist/components/forms/StagedControlReview.svelte +21 -12
- package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
- package/dist/components/forms/Switch.svelte +11 -5
- package/dist/components/forms/TagsInput.svelte +8 -1
- package/dist/components/forms/Textarea.svelte +8 -0
- package/dist/components/forms/__tests__/Combobox.test.js +87 -0
- package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
- package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
- package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
- package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
- package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
- package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
- package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
- package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
- package/dist/components/forms/__tests__/dictation.test.js +486 -0
- package/dist/components/forms/__tests__/long-press.test.js +175 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
- package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.d.ts +69 -0
- package/dist/components/forms/audio-capture.d.ts.map +1 -0
- package/dist/components/forms/audio-capture.js +193 -0
- package/dist/components/forms/control-interaction.d.ts +6 -0
- package/dist/components/forms/control-interaction.d.ts.map +1 -1
- package/dist/components/forms/control-interaction.js +12 -1
- package/dist/components/forms/control-proposals.d.ts +55 -0
- package/dist/components/forms/control-proposals.d.ts.map +1 -0
- package/dist/components/forms/control-proposals.js +188 -0
- package/dist/components/forms/dictation-transcribe.d.ts +36 -0
- package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
- package/dist/components/forms/dictation-transcribe.js +65 -0
- package/dist/components/forms/dictation.svelte.d.ts +200 -0
- package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
- package/dist/components/forms/dictation.svelte.js +686 -0
- package/dist/components/forms/index.d.ts +14 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +14 -1
- package/dist/components/forms/insert-text.d.ts +12 -0
- package/dist/components/forms/insert-text.d.ts.map +1 -0
- package/dist/components/forms/insert-text.js +38 -0
- package/dist/components/forms/long-press.d.ts +88 -0
- package/dist/components/forms/long-press.d.ts.map +1 -0
- package/dist/components/forms/long-press.js +213 -0
- package/dist/components/forms/ready-beep.d.ts +24 -0
- package/dist/components/forms/ready-beep.d.ts.map +1 -0
- package/dist/components/forms/ready-beep.js +82 -0
- package/dist/components/layout/PageHeader.svelte +151 -17
- package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
- package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
- package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/layout/index.js +1 -0
- package/dist/components/layout/page-header-context.d.ts +25 -0
- package/dist/components/layout/page-header-context.d.ts.map +1 -0
- package/dist/components/layout/page-header-context.js +16 -0
- package/dist/components/nav/Tabs.svelte +241 -0
- package/dist/components/nav/Tabs.svelte.d.ts +15 -0
- package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
- package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
- package/dist/components/nav/index.d.ts +1 -0
- package/dist/components/nav/index.d.ts.map +1 -1
- package/dist/components/nav/index.js +1 -0
- package/dist/components/nav/tabs-overflow.d.ts +12 -0
- package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
- package/dist/components/nav/tabs-overflow.js +17 -0
- package/dist/components/nav/types.d.ts +8 -0
- package/dist/components/nav/types.d.ts.map +1 -1
- package/dist/components/ui/Button.svelte +7 -0
- package/dist/components/ui/Card.svelte +14 -5
- package/dist/components/ui/Dropdown.svelte +55 -1
- package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
- package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
- package/dist/components/ui/IconToggle.svelte +139 -0
- package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
- package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
- package/dist/components/ui/IconToggleGroup.svelte +56 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
- package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
- package/dist/components/ui/Pagination.svelte +1 -1
- package/dist/components/ui/Popover.svelte +17 -1
- package/dist/components/ui/Popover.svelte.d.ts +8 -0
- package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
- package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
- package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
- package/dist/components/ui/__tests__/Popover.test.js +44 -0
- package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
- package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +2 -0
- package/dist/i18n/strings.ui.d.ts +41 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +46 -0
- package/dist/svelte/__tests__/playground.test.js +1 -0
- package/dist/svelte/playground/CalendarPreview.svelte +140 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +10 -0
- package/dist/types-generic.d.ts +2 -0
- package/dist/types-generic.d.ts.map +1 -1
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -46,11 +46,11 @@ components are exempt — they *are* the primitives.
|
|
|
46
46
|
| `.` | barrel re-exporting the primitives, registry, and theme system |
|
|
47
47
|
| `./playground` | shared-host previews for the complete foundation catalog; rendered inside the active preset and color scheme |
|
|
48
48
|
| `./ui` | `Button`, `Card`, `Badge`, `Avatar`, `Chip`, `Skeleton`, `Tooltip`, `Dropdown`/`Menu`, `Popover`, `Disclosure`, `Accordion`, `Tree`, `Pagination`, … |
|
|
49
|
-
| `./feedback` | `Alert`, `ToastViewport`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog`, `LoadingOverlay`, `Progress`, `Meter`, `Spinner` |
|
|
49
|
+
| `./feedback` | `Alert`, `ToastViewport`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog`, `LoadingOverlay`, `Progress`, `Meter`, `Spinner`, phone surfaces `PhoneSheet` / `WorkingStrip` and the `swipeDismiss` action |
|
|
50
50
|
| `./data` | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable` and their types — a **component barrel**: it re-exports `.svelte` files, so a plain Node `import()` of it throws `ERR_UNKNOWN_FILE_EXTENSION` |
|
|
51
51
|
| `./data-surface` | **Svelte-free**: registry contracts and shared protocol limits (`DATA_SURFACE_MAX_REQUEST_BYTES`, `DATA_SURFACE_IDENTIFIER_MAX_LENGTH`, …). Server adapters must import these from here, not from `./data` — a server entry that reaches the barrel breaks the `smrt` CLI's plain-Node `.smrt/register.js` load (issue #2924) |
|
|
52
52
|
| `./layout` | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, … |
|
|
53
|
-
| `./calendar` | `Calendar`, `DayView` |
|
|
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
55
|
| `./forms` | Provider-free fields, choice controls, sliders/ranges, combobox/listbox/multiselect/tags, date/time/file controls, plus the transport-neutral control interaction registry |
|
|
56
56
|
| `./i18n` | i18n **client**: `useI18n`, `<Trans>`, `defineMessages`, `renderTemplate` (no `smrt-languages` import — the server resolver stays in `smrt-svelte/i18n/server`) |
|
|
@@ -149,7 +149,19 @@ other text pairing clears WCAG AA in both schemes.
|
|
|
149
149
|
|
|
150
150
|
## Gotchas
|
|
151
151
|
|
|
152
|
+
- **Calendar dates are `YYYY-MM-DD` keys in the view's `timeZone`.** Project
|
|
153
|
+
instants into the zone once (`dateKeyInZone`) and do all grid/span math on
|
|
154
|
+
keys; never `new Date(y, m, d)` / `getDay()` in the browser zone. Months are
|
|
155
|
+
1-12 in the public API (`CalendarMonth`), unlike `Date#getMonth`.
|
|
156
|
+
|
|
152
157
|
- **i18n split**: the client (here) is dependency-free; the Node-only server
|
|
153
158
|
resolver (`buildI18nSnapshot`, → `@happyvertical/smrt-languages`) stays in
|
|
154
159
|
`@happyvertical/smrt-svelte/i18n/server`. Package string catalogs register via
|
|
155
160
|
`defineMessages` imported from `@happyvertical/smrt-ui/i18n`.
|
|
161
|
+
|
|
162
|
+
- **Consumers type-checking a local checkout** hit "two different types with
|
|
163
|
+
this name" on `Snippet` when two Svelte copies meet (svelte2tsx's
|
|
164
|
+
`/// <reference types="svelte" />` in compiled sources ignores `paths`). Keep
|
|
165
|
+
every Svelte export's `types` condition first and pointing at built
|
|
166
|
+
`*.svelte.d.ts`; the consumer recipe is
|
|
167
|
+
[docs/development/svelte-consumer-linking.md](../../docs/development/svelte-consumer-linking.md).
|
package/README.md
CHANGED
|
@@ -29,15 +29,67 @@ pnpm add @happyvertical/smrt-ui
|
|
|
29
29
|
|
|
30
30
|
| Area | Components |
|
|
31
31
|
| --- | --- |
|
|
32
|
-
| Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary` |
|
|
33
|
-
| Text and structured input | `Input`, `Textarea`, `Select`, `Combobox`, `Listbox`, `MultiSelect`, `TagsInput` |
|
|
32
|
+
| Fields | `Form`, `Field`/`FormGroup`, `Fieldset`, `InputGroup`, `ErrorSummary`, `FormActionBar` |
|
|
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
36
|
| Actions and display | `Button`, `Dropdown`/`Menu`, `Badge`, `Chip`, `Avatar`, `Card`, `Skeleton`, `Tooltip`, `Tree` |
|
|
37
|
-
| Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `ConfirmDialog` |
|
|
38
|
-
| Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay` |
|
|
37
|
+
| Disclosure and overlays | `Popover`, `Disclosure`, `Accordion`/`AccordionItem`, `Modal`, `Drawer`/`Sheet`, `PhoneSheet`, `ConfirmDialog` |
|
|
38
|
+
| Feedback | `Alert`, `ToastViewport`, `Progress`, `Meter`, `Spinner`, `LoadingOverlay`, `WorkingStrip` |
|
|
39
39
|
| Collections | `CollectionToolbar`, `CollectionList`/`ContentList`, `DataTable`, `Pagination` |
|
|
40
40
|
| Layout and navigation | `Container`, `Grid`, `Header`, `Footer`, `PageHeader`, `EmptyState`, `Tabs`, `FilterChips` |
|
|
41
|
+
| Calendar | `CalendarView` (deprecated: `Calendar`, `DayView`) |
|
|
42
|
+
|
|
43
|
+
### Phone surfaces
|
|
44
|
+
|
|
45
|
+
- `PhoneSheet` (`/feedback`) is the phone replacement for drawers and centered
|
|
46
|
+
modals: `variant="page"` covers the area, `variant="sheet"` rises from the
|
|
47
|
+
bottom and closes on a swipe down its header. It is non-modal, closes on its
|
|
48
|
+
button and Escape, returns focus to the opener, and can stay mounted while
|
|
49
|
+
closed (hidden and inert) so its content keeps state. It is
|
|
50
|
+
`position: absolute; inset: 0` — render it in AdminShell's `overlays`.
|
|
51
|
+
- `WorkingStrip` (`/feedback`) shows a transport-neutral `WorkingStatus`
|
|
52
|
+
(`idle` · `working` · `paused` · `waiting` · `done` · `failed` ·
|
|
53
|
+
`cancelled`, a `label` for the current step and an optional `goal`) as a
|
|
54
|
+
strip above a phone bottom bar, a floating pill (`variant="floating"`, the
|
|
55
|
+
host positions it), or a line at the top of a pane. Optional controls:
|
|
56
|
+
reopen (`onopen`), Pause/Continue (`onpause`/`onresume`; Escape pauses),
|
|
57
|
+
Review while waiting (`onreview`), Stop, and Close once finished
|
|
58
|
+
(`ondismiss`); all 44px. Done uses the success colors, failed the error
|
|
59
|
+
colors, always with an icon and text. Step changes are announced politely
|
|
60
|
+
(throttled by `announceIntervalMs`), `waiting` and `failed` assertively; it
|
|
61
|
+
never takes focus.
|
|
62
|
+
- `FormActionBar` (`/forms`) groups a form's actions (primary last). On phones
|
|
63
|
+
it is fixed to the bottom, carries `data-form-action-bar` so AdminShell hides
|
|
64
|
+
its phone bottom bar, and hides while `:root[data-keyboard-open]`.
|
|
65
|
+
- `swipeDismiss` / `swipeDismisses` (`/feedback`) are the touch action and pure
|
|
66
|
+
decision behind swipe-to-close.
|
|
67
|
+
- `Dictation` / `DictationButton` / `DictationStatus` (`/forms`) let people
|
|
68
|
+
speak into a text field (tap the microphone, or press and hold the field
|
|
69
|
+
with `longPress`). The speech `source` is normally smrt-svelte's
|
|
70
|
+
`createSttDictationSource()` (the browser's own speech recognition). Give
|
|
71
|
+
it a `transcribe` function too (`createHttpTranscriber('/your/route')`)
|
|
72
|
+
and, where the browser has no speech recognition (Firefox) or no speech
|
|
73
|
+
service behind it (Brave: `network` / `service-not-allowed`), it records
|
|
74
|
+
the message with `MediaRecorder` instead (WebM/Opus, MP4 on Safari; at most
|
|
75
|
+
`maxDurationMs` 2 minutes and `maxBytes` 10 MB), shows "Writing it down…"
|
|
76
|
+
while the route turns it into text, and puts the text at the cursor.
|
|
77
|
+
Keep the speech service's key on the server: the route takes the raw audio
|
|
78
|
+
body (`Content-Type` is the recording's type, `?language=&durationMs=`)
|
|
79
|
+
and answers `{ text }`; 413, 503 and 401/403 become "too long", "not set
|
|
80
|
+
up" and "not allowed" messages.
|
|
81
|
+
|
|
82
|
+
### Link tabs
|
|
83
|
+
|
|
84
|
+
`Tabs` with an `href` on every tab renders a page's sections as navigation —
|
|
85
|
+
a `<nav>` of links, `aria-current="page"` on the active one, each tab its own
|
|
86
|
+
URL — instead of an ARIA tablist. `maxVisible` moves extra tabs into a "More"
|
|
87
|
+
menu while the active tab always stays in the row (`splitTabs` is the pure
|
|
88
|
+
rule), `badge` marks a tab needing attention (and dots "More" when a hidden
|
|
89
|
+
tab has one), and the row scrolls sideways on phones with the active tab
|
|
90
|
+
kept in view. It carries `data-shell-tabs`, so AdminShell keeps it sticky
|
|
91
|
+
under the phone top bar. Pair it with `useLinkSurface` (smrt-svelte) so
|
|
92
|
+
agents can switch tabs too.
|
|
41
93
|
|
|
42
94
|
Use the focused subpaths (`/forms`, `/ui`, `/feedback`, `/data`,
|
|
43
95
|
`/data-surface`, `/layout`, `/themes`) to keep imports explicit. The
|
|
@@ -45,6 +97,92 @@ Svelte-free `/data-surface` entry exposes the registry contracts and shared
|
|
|
45
97
|
protocol limits for server adapters. The package root remains a compatibility
|
|
46
98
|
barrel.
|
|
47
99
|
|
|
100
|
+
### Touch targets for Checkbox, Radio and Switch
|
|
101
|
+
|
|
102
|
+
`Checkbox`, `Radio` and `Switch` have an invisible 44x44px hit area around the
|
|
103
|
+
visible control (about 18px for a box or mark, the 2.75rem track for a switch).
|
|
104
|
+
A transparent `::before` on the wrapping `<label>` makes it, so a click anywhere
|
|
105
|
+
in the area toggles the input, no layout shifts, and the focus ring stays on the
|
|
106
|
+
visible box. The box and the label text sit above every hit area, so adjacent
|
|
107
|
+
controls never steal each other's clicks.
|
|
108
|
+
|
|
109
|
+
The hit area paints above plain (unpositioned) content around it, so it is
|
|
110
|
+
constrained to where that cannot cost a neighbour a click:
|
|
111
|
+
|
|
112
|
+
- **Beside other content.** A `Checkbox` or `Radio` that is not the only element
|
|
113
|
+
in its parent (a row title or link next to it, a group's other options) keeps
|
|
114
|
+
its hit area in the box's own column: it grows vertically only, never over the
|
|
115
|
+
text or link beside it. Only a control standing alone gets the full 44x44.
|
|
116
|
+
- **Table cells.** Inside a `td`/`th` the area never leaves the cell. A
|
|
117
|
+
`Checkbox` that is the cell's only content uses the whole cell (clipped to its
|
|
118
|
+
edges). The cell gets `position: relative` at zero specificity (`:where()`), so
|
|
119
|
+
a sticky header or a pinned column keeps its own positioning.
|
|
120
|
+
- **Stacked controls.** Hit areas of tightly stacked controls overlap in the
|
|
121
|
+
gaps; the later control wins a gap, never a neighbour's box or text. Content
|
|
122
|
+
directly above or below a box, closer than 13px, is covered by its hit area:
|
|
123
|
+
give it room, or shrink the area with `--smrt-control-hit-size`.
|
|
124
|
+
- **Resize or turn off.** `--smrt-control-hit-size` (default `2.75rem`); `0px`
|
|
125
|
+
gives only the visible control. Pages never add their own padding hacks.
|
|
126
|
+
- `DataTable`'s built-in selection checkboxes and `CollectionList`'s select box
|
|
127
|
+
are still native inputs and do not use this yet.
|
|
128
|
+
|
|
129
|
+
## Calendar
|
|
130
|
+
|
|
131
|
+
`CalendarView` (`@happyvertical/smrt-ui/calendar`) is a generic month grid
|
|
132
|
+
with a phone agenda:
|
|
133
|
+
|
|
134
|
+
```svelte
|
|
135
|
+
<script lang="ts">
|
|
136
|
+
import { CalendarView } from '@happyvertical/smrt-ui/calendar';
|
|
137
|
+
import { goto } from '$app/navigation';
|
|
138
|
+
let { data } = $props(); // { year, month, items }
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<CalendarView
|
|
142
|
+
items={data.items}
|
|
143
|
+
year={data.year}
|
|
144
|
+
month={data.month}
|
|
145
|
+
timeZone="America/Edmonton"
|
|
146
|
+
onNavigate={({ year, month }) => goto(`?y=${year}&m=${month}`, { keepFocus: true })}
|
|
147
|
+
/>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- **Items** are generic `CalendarItem`s: `{ id, title, start, end?, allDay?,
|
|
151
|
+
tone?, color?, label?, group?, href? }`. `start`/`end` take a `Date`, an ISO
|
|
152
|
+
date-time, or a `YYYY-MM-DD` date (all-day). Timed `end` is exclusive; an
|
|
153
|
+
all-day `YYYY-MM-DD` end is the inclusive last day, and an all-day end
|
|
154
|
+
instant at local midnight is exclusive. `tone` maps to the theme's color
|
|
155
|
+
roles; without it, `group` picks a stable tone. Items with `href` render as
|
|
156
|
+
links, others as buttons; both call `onItemSelect`.
|
|
157
|
+
- **Time zone and locale**: every day is computed in `timeZone` (IANA;
|
|
158
|
+
defaults to the browser's — pass it explicitly so server and client
|
|
159
|
+
agree). Month, weekday, date, and time text come from `Intl` in `locale`
|
|
160
|
+
(defaults to the i18n locale); `weekStartsOn` defaults to the locale's.
|
|
161
|
+
- **Modes**: `month` is a `role="grid"` month with roving focus (arrows,
|
|
162
|
+
Home/End, PageUp/PageDown cross months). All-day and multi-day items are
|
|
163
|
+
bands across the days they cover; a day with more than `maxPerDay` rows
|
|
164
|
+
shows "+N more", which opens the day in a panel under the grid, or follows
|
|
165
|
+
`dayHref` when given. `agenda` is a horizontally scrolling strip of the
|
|
166
|
+
month's days (44px targets, arrow keys) above a day-by-day list. `auto`
|
|
167
|
+
(default) uses the agenda below 48rem, matching AdminShell's phone
|
|
168
|
+
breakpoint. No modals.
|
|
169
|
+
- **URL state**: `year` + `month` (1-12) and `selectedDate` (`YYYY-MM-DD`) are
|
|
170
|
+
controlled when passed; changes are reported via `onNavigate` and
|
|
171
|
+
`onSelectDate`, so a page can keep them in its URL.
|
|
172
|
+
- The Svelte-free date model (`toEntries`, `layoutMonth`, `dateKeyInZone`,
|
|
173
|
+
`monthWeeks`, `shiftMonth`, …) is exported from the same subpath for
|
|
174
|
+
server-side range queries and tests.
|
|
175
|
+
|
|
176
|
+
**Migrating from `Calendar` / `DayView` (deprecated).** They compute days in
|
|
177
|
+
the browser zone, print English-only names, and hard-code game/meeting/event
|
|
178
|
+
emoji and routes. Map each `DayEventDetail` to a `CalendarItem` (`name` →
|
|
179
|
+
`title`, `type` → `group`/`label`, your own route → `href`), replace
|
|
180
|
+
`Calendar`'s `year`/`month` (0-indexed) with `CalendarView`'s `year`/`month`
|
|
181
|
+
(1-12) and `onMonthNavigate` with `onNavigate`, `baseUrl` with a `dayHref`
|
|
182
|
+
function that builds your day route from the `YYYY-MM-DD` key, and a `DayView`
|
|
183
|
+
page with `CalendarView` in agenda mode (`selectedDate` set to the day) or the
|
|
184
|
+
month grid's day panel. They will be removed in a future minor release.
|
|
185
|
+
|
|
48
186
|
### Currency display
|
|
49
187
|
|
|
50
188
|
`CurrencyDisplay` accepts ISO 4217 codes as a public `string` prop so persisted
|
|
@@ -128,7 +266,8 @@ without coupling controls to a transport:
|
|
|
128
266
|
|
|
129
267
|
Controls publish serializable metadata, constraints, options, sensitivity, and
|
|
130
268
|
capabilities. Adapters can focus, reveal, highlight, explain, validate, and
|
|
131
|
-
stage reviewable proposals.
|
|
269
|
+
stage reviewable proposals. An agent never moves keyboard focus: its `focus`
|
|
270
|
+
reveals and highlights the control instead. Agents cannot apply, discard, clear, or undo;
|
|
132
271
|
those value-changing actions require a trusted local gesture handled by the
|
|
133
272
|
framework review surface. Secret/read-only controls reject agent mutations.
|
|
134
273
|
Staging remains separate so proposals never change user state before review.
|
|
@@ -178,6 +317,51 @@ not await a same-control mutation through a captured registry reference: that
|
|
|
178
317
|
call is indistinguishable from an independent caller in browser runtimes and,
|
|
179
318
|
like any hook that never settles, can hold the ordered queue indefinitely.
|
|
180
319
|
|
|
320
|
+
### Composite controls and form proposals
|
|
321
|
+
|
|
322
|
+
A composite field — a place picker, a slug field with its own preview, a
|
|
323
|
+
record picker — registers as ONE control with `useControlRegistration` (call
|
|
324
|
+
it during component init inside any `Form`, rich `Form`, or `FormScope`). Give
|
|
325
|
+
it a stable `controlId`, a plain `label`, and, when its value is not a plain
|
|
326
|
+
string/number/boolean, a `valueSchema` so adapters can describe it:
|
|
327
|
+
|
|
328
|
+
```svelte
|
|
329
|
+
<script lang="ts">
|
|
330
|
+
import { useControlRegistration } from '@happyvertical/smrt-ui/forms';
|
|
331
|
+
|
|
332
|
+
let { value = $bindable(null) } = $props();
|
|
333
|
+
|
|
334
|
+
useControlRegistration(() => ({
|
|
335
|
+
controlId: 'place',
|
|
336
|
+
metadata: {
|
|
337
|
+
kind: 'custom',
|
|
338
|
+
label: 'Town location',
|
|
339
|
+
valueSchema: {
|
|
340
|
+
type: 'object',
|
|
341
|
+
properties: { name: { type: 'string' }, latitude: { type: 'number' }, longitude: { type: 'number' } },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
getValue: () => value,
|
|
345
|
+
setValue: (next) => { value = next; },
|
|
346
|
+
}));
|
|
347
|
+
</script>
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Call `recordControlUserEdit(context, controlId, subject)` from the
|
|
351
|
+
composite's own user-event handlers so a staged proposal goes stale when the
|
|
352
|
+
person edits over it (native inputs bubble their events to the Form instead).
|
|
353
|
+
`focusControl`, `revealControl`, `highlightControl`, and `emitControlChange`
|
|
354
|
+
are the same DOM helpers the built-in primitives use for their `focus`,
|
|
355
|
+
`reveal`, and `highlight` handles.
|
|
356
|
+
|
|
357
|
+
`controlProposalInputSchema(registry, formId)` and
|
|
358
|
+
`stageControlProposals(registry, formId, values)` are the transport-neutral
|
|
359
|
+
halves of a "propose values for this form" tool: the schema has one optional
|
|
360
|
+
property per proposable control (never secret/sensitive, unwritable,
|
|
361
|
+
disabled, read-only, file, or password controls), and staging only ever
|
|
362
|
+
creates reviewable proposals. smrt-svelte's `<Form webmcp>` and `FormScope`
|
|
363
|
+
build their `*_stage_changes` tool from these.
|
|
364
|
+
|
|
181
365
|
## DataTable controller
|
|
182
366
|
|
|
183
367
|
`DataTable` can share one headless `DataTableController` between rendered
|
|
@@ -437,6 +621,34 @@ registry.register({
|
|
|
437
621
|
});
|
|
438
622
|
```
|
|
439
623
|
|
|
624
|
+
### Navigation and step surfaces
|
|
625
|
+
|
|
626
|
+
Two ready-made surfaces cover interactions a view intent cannot reach on its
|
|
627
|
+
own (an intent only dispatches a registry command):
|
|
628
|
+
|
|
629
|
+
- `registerLinkSurface({ registry, surfaceId, label, description, links,
|
|
630
|
+
navigate })` mounts a menu, tab row, or section list. `state.links`
|
|
631
|
+
publishes each link's id, label, description, and group — never its href —
|
|
632
|
+
and the default `open` control resolves `{ target }` (an id, a label, or a
|
|
633
|
+
unique label prefix) to one of those links and calls `navigate(href)`.
|
|
634
|
+
Nothing an agent sends can become a URL.
|
|
635
|
+
Return the navigation's promise from `navigate`: the command answers at
|
|
636
|
+
once, and `whenSurfaceNavigationSettled(registry)` resolves once it
|
|
637
|
+
finished and the new page's surfaces registered (a bespoke surface that
|
|
638
|
+
navigates calls `trackSurfaceNavigation(registry, promise)`).
|
|
639
|
+
- `registerStepSurface({ registry, surfaceId, label, description, steps,
|
|
640
|
+
current, next, back, goTo, nextWrites, showNext })` mounts a wizard.
|
|
641
|
+
`state` carries the steps with their status, the current step, and
|
|
642
|
+
`nextWrites`. `next`/`back`/`go-to` run the page's own handlers (return
|
|
643
|
+
`false` to refuse, e.g. when validation fails). When the current step's
|
|
644
|
+
forward button saves or creates something (`nextWrites`), `next` never
|
|
645
|
+
presses it: it calls `showNext` to reveal and highlight the button and
|
|
646
|
+
publishes `awaitingPerson: true` until the flow moves on.
|
|
647
|
+
|
|
648
|
+
smrt-svelte's `useLinkSurface` / `useStepSurface` / `useListSurface`
|
|
649
|
+
(`@happyvertical/smrt-svelte/web`) bind these to a component's lifetime on
|
|
650
|
+
the Provider's registry.
|
|
651
|
+
|
|
440
652
|
`inspect()` and command results are deterministic `{ version, descriptor,
|
|
441
653
|
revision, state, selection }` envelopes; neither includes a timestamp, rows,
|
|
442
654
|
functions, authority fields, tenant/principal data, SQL, or a transport handle.
|
|
@@ -529,6 +741,46 @@ hand-authored, and every text pairing clears WCAG AA.
|
|
|
529
741
|
Run the shared playground to inspect the full catalog under every preset and
|
|
530
742
|
light/dark scheme.
|
|
531
743
|
|
|
744
|
+
### Card look tokens
|
|
745
|
+
|
|
746
|
+
`Card` and `CollectionList` rows read these custom properties, so an app can
|
|
747
|
+
change the card look once in its own theme layer instead of per page. Unset,
|
|
748
|
+
they keep the stock look.
|
|
749
|
+
|
|
750
|
+
| Property | Default | Used for |
|
|
751
|
+
| --- | --- | --- |
|
|
752
|
+
| `--smrt-card-border` | `1px solid var(--smrt-color-outline-variant)` | edge of `default` and `elevated` cards, CollectionList rows |
|
|
753
|
+
| `--smrt-card-background` | `var(--smrt-color-surface)` | card and row fill |
|
|
754
|
+
| `--smrt-card-shadow` | `none` | shadow of `default` cards (`elevated` keeps its elevation) |
|
|
755
|
+
| `--smrt-card-divider` | `1px solid var(--smrt-color-outline-variant)` | rule under a card header / above its footer |
|
|
756
|
+
|
|
757
|
+
The `outlined` variant always draws its outline. For borderless cards on a
|
|
758
|
+
`surface` page, pair `--smrt-card-border: none` with a tinted background such
|
|
759
|
+
as `var(--smrt-color-surface-variant)` so cards stay distinct in both schemes:
|
|
760
|
+
|
|
761
|
+
```css
|
|
762
|
+
:root {
|
|
763
|
+
--smrt-card-border: none;
|
|
764
|
+
--smrt-card-background: var(--smrt-color-surface-variant);
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Component override hooks
|
|
769
|
+
|
|
770
|
+
Hooks an app sets once in its own theme layer instead of restyling a component's
|
|
771
|
+
classes. Unset, each keeps its stock look.
|
|
772
|
+
|
|
773
|
+
| Property | Component | Used for |
|
|
774
|
+
| --- | --- | --- |
|
|
775
|
+
| `--smrt-popover-panel-width` | `Popover` | the panel's width (never wider than the viewport minus 2rem) |
|
|
776
|
+
| `--smrt-popover-panel-padding` | `Popover` | the panel's padding |
|
|
777
|
+
| `--smrt-admin-shell-background` | `AdminShell` (smrt-svelte) | the page background behind the cards |
|
|
778
|
+
| `--smrt-admin-shell-scrollbar-track` / `-thumb` / `-thumb-hover` | `AdminShell` (smrt-svelte) | the themed thin scrollbars inside the shell |
|
|
779
|
+
| `--smrt-shell-title-font-family` | `ShellTitle` (smrt-svelte) | the workspace name's font family |
|
|
780
|
+
|
|
781
|
+
`IconToggle` takes a `tone` prop (a CSS colour such as `var(--status-draft)`) for
|
|
782
|
+
a toggle that is neutral until pressed and then wears its colour.
|
|
783
|
+
|
|
532
784
|
## Development
|
|
533
785
|
|
|
534
786
|
```bash
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { swipeDismiss, swipeDismisses } from '../swipe-dismiss.js';
|
|
3
|
+
describe('swipeDismisses', () => {
|
|
4
|
+
it('dismisses a drawer on a clear swipe left only', () => {
|
|
5
|
+
expect(swipeDismisses(-80, 10, 'left')).toBe(true);
|
|
6
|
+
expect(swipeDismisses(-40, 0, 'left')).toBe(false);
|
|
7
|
+
expect(swipeDismisses(-80, 70, 'left')).toBe(false);
|
|
8
|
+
expect(swipeDismisses(80, 0, 'left')).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
it('dismisses a right-hand drawer on a clear swipe right only', () => {
|
|
11
|
+
expect(swipeDismisses(80, 5, 'right')).toBe(true);
|
|
12
|
+
expect(swipeDismisses(-80, 5, 'right')).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
it('dismisses a sheet on a clear swipe down only', () => {
|
|
15
|
+
expect(swipeDismisses(5, 90, 'down')).toBe(true);
|
|
16
|
+
expect(swipeDismisses(0, -90, 'down')).toBe(false);
|
|
17
|
+
expect(swipeDismisses(80, 90, 'down')).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
it('honours a custom threshold', () => {
|
|
20
|
+
expect(swipeDismisses(0, 40, 'down', 32)).toBe(true);
|
|
21
|
+
expect(swipeDismisses(0, 40, 'down')).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
function touch(type, x, y) {
|
|
25
|
+
const point = { clientX: x, clientY: y };
|
|
26
|
+
const event = new Event(type);
|
|
27
|
+
Object.defineProperty(event, 'touches', {
|
|
28
|
+
value: type === 'touchend' ? [] : [point],
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(event, 'changedTouches', { value: [point] });
|
|
31
|
+
return event;
|
|
32
|
+
}
|
|
33
|
+
describe('swipeDismiss action', () => {
|
|
34
|
+
it('calls onDismiss after a swipe in its direction', () => {
|
|
35
|
+
const node = document.createElement('div');
|
|
36
|
+
const onDismiss = vi.fn();
|
|
37
|
+
const action = swipeDismiss(node, { direction: 'down', onDismiss });
|
|
38
|
+
node.dispatchEvent(touch('touchstart', 10, 10));
|
|
39
|
+
node.dispatchEvent(touch('touchend', 12, 100));
|
|
40
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
41
|
+
action.destroy();
|
|
42
|
+
});
|
|
43
|
+
it('ignores swipes while disabled and after update re-enables them', () => {
|
|
44
|
+
const node = document.createElement('div');
|
|
45
|
+
const onDismiss = vi.fn();
|
|
46
|
+
const action = swipeDismiss(node, {
|
|
47
|
+
direction: 'left',
|
|
48
|
+
onDismiss,
|
|
49
|
+
enabled: false,
|
|
50
|
+
});
|
|
51
|
+
node.dispatchEvent(touch('touchstart', 100, 10));
|
|
52
|
+
node.dispatchEvent(touch('touchend', 10, 10));
|
|
53
|
+
expect(onDismiss).not.toHaveBeenCalled();
|
|
54
|
+
action.update({ direction: 'left', onDismiss, enabled: true });
|
|
55
|
+
node.dispatchEvent(touch('touchstart', 100, 10));
|
|
56
|
+
node.dispatchEvent(touch('touchend', 10, 10));
|
|
57
|
+
expect(onDismiss).toHaveBeenCalledTimes(1);
|
|
58
|
+
action.destroy();
|
|
59
|
+
});
|
|
60
|
+
it('forgets a cancelled touch', () => {
|
|
61
|
+
const node = document.createElement('div');
|
|
62
|
+
const onDismiss = vi.fn();
|
|
63
|
+
swipeDismiss(node, { direction: 'down', onDismiss });
|
|
64
|
+
node.dispatchEvent(touch('touchstart', 10, 10));
|
|
65
|
+
node.dispatchEvent(new Event('touchcancel'));
|
|
66
|
+
node.dispatchEvent(touch('touchend', 10, 200));
|
|
67
|
+
expect(onDismiss).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe-to-dismiss for phone surfaces: a drawer closes on a swipe left, a
|
|
3
|
+
* bottom sheet on a swipe down.
|
|
4
|
+
*
|
|
5
|
+
* `swipeDismisses` is the pure decision (unit-testable, DOM-free);
|
|
6
|
+
* `swipeDismiss` is the Svelte action that feeds it touch events. Touch only:
|
|
7
|
+
* mouse drags keep selecting text. Never bind a left-edge swipe to anything —
|
|
8
|
+
* mobile browsers use it for Back.
|
|
9
|
+
*/
|
|
10
|
+
/** The direction a surface is swiped to dismiss it. */
|
|
11
|
+
export type SwipeDirection = 'left' | 'right' | 'down';
|
|
12
|
+
/** Minimum travel (px) that counts as a dismissing swipe. */
|
|
13
|
+
export declare const SWIPE_DISMISS_THRESHOLD = 64;
|
|
14
|
+
/**
|
|
15
|
+
* Whether a drag of (`dx`, `dy`) px dismisses a surface swiped in `direction`.
|
|
16
|
+
* The drag must travel at least `threshold` px and be clearly more along the
|
|
17
|
+
* dismiss axis than across it (1.5x), so a vertical scroll never closes a
|
|
18
|
+
* drawer and a sideways pan never closes a sheet.
|
|
19
|
+
*/
|
|
20
|
+
export declare function swipeDismisses(dx: number, dy: number, direction: SwipeDirection, threshold?: number): boolean;
|
|
21
|
+
/** Options for the {@link swipeDismiss} action. */
|
|
22
|
+
export interface SwipeDismissOptions {
|
|
23
|
+
/** Direction that dismisses the surface. */
|
|
24
|
+
direction: SwipeDirection;
|
|
25
|
+
/** Called once a swipe in `direction` passes the threshold. */
|
|
26
|
+
onDismiss: () => void;
|
|
27
|
+
/** Set `false` to ignore swipes (e.g. only enable on phones). */
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
/** Minimum travel in px (default {@link SWIPE_DISMISS_THRESHOLD}). */
|
|
30
|
+
threshold?: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Svelte action: dismiss a drawer or sheet with a touch swipe.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <div use:swipeDismiss={{ direction: 'down', onDismiss: close }}>…</div>
|
|
37
|
+
*/
|
|
38
|
+
export declare function swipeDismiss(node: HTMLElement, initial: SwipeDismissOptions): {
|
|
39
|
+
update: (next: SwipeDismissOptions) => void;
|
|
40
|
+
destroy: () => void;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=swipe-dismiss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swipe-dismiss.d.ts","sourceRoot":"","sources":["../../src/actions/swipe-dismiss.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uDAAuD;AACvD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvD,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,cAAc,EACzB,SAAS,SAA0B,GAClC,OAAO,CAQT;AAED,mDAAmD;AACnD,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,SAAS,EAAE,cAAc,CAAC;IAC1B,+DAA+D;IAC/D,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,mBAAmB,GAC3B;IACD,MAAM,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAsCA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swipe-to-dismiss for phone surfaces: a drawer closes on a swipe left, a
|
|
3
|
+
* bottom sheet on a swipe down.
|
|
4
|
+
*
|
|
5
|
+
* `swipeDismisses` is the pure decision (unit-testable, DOM-free);
|
|
6
|
+
* `swipeDismiss` is the Svelte action that feeds it touch events. Touch only:
|
|
7
|
+
* mouse drags keep selecting text. Never bind a left-edge swipe to anything —
|
|
8
|
+
* mobile browsers use it for Back.
|
|
9
|
+
*/
|
|
10
|
+
/** Minimum travel (px) that counts as a dismissing swipe. */
|
|
11
|
+
export const SWIPE_DISMISS_THRESHOLD = 64;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a drag of (`dx`, `dy`) px dismisses a surface swiped in `direction`.
|
|
14
|
+
* The drag must travel at least `threshold` px and be clearly more along the
|
|
15
|
+
* dismiss axis than across it (1.5x), so a vertical scroll never closes a
|
|
16
|
+
* drawer and a sideways pan never closes a sheet.
|
|
17
|
+
*/
|
|
18
|
+
export function swipeDismisses(dx, dy, direction, threshold = SWIPE_DISMISS_THRESHOLD) {
|
|
19
|
+
if (direction === 'left') {
|
|
20
|
+
return -dx >= threshold && Math.abs(dx) > Math.abs(dy) * 1.5;
|
|
21
|
+
}
|
|
22
|
+
if (direction === 'right') {
|
|
23
|
+
return dx >= threshold && Math.abs(dx) > Math.abs(dy) * 1.5;
|
|
24
|
+
}
|
|
25
|
+
return dy >= threshold && Math.abs(dy) > Math.abs(dx) * 1.5;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Svelte action: dismiss a drawer or sheet with a touch swipe.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* <div use:swipeDismiss={{ direction: 'down', onDismiss: close }}>…</div>
|
|
32
|
+
*/
|
|
33
|
+
export function swipeDismiss(node, initial) {
|
|
34
|
+
let options = initial;
|
|
35
|
+
let start = null;
|
|
36
|
+
function onStart(event) {
|
|
37
|
+
if (options.enabled === false || event.touches.length !== 1)
|
|
38
|
+
return;
|
|
39
|
+
const touch = event.touches[0];
|
|
40
|
+
start = { x: touch.clientX, y: touch.clientY };
|
|
41
|
+
}
|
|
42
|
+
function onEnd(event) {
|
|
43
|
+
if (!start)
|
|
44
|
+
return;
|
|
45
|
+
const touch = event.changedTouches[0];
|
|
46
|
+
const dx = touch.clientX - start.x;
|
|
47
|
+
const dy = touch.clientY - start.y;
|
|
48
|
+
start = null;
|
|
49
|
+
if (swipeDismisses(dx, dy, options.direction, options.threshold)) {
|
|
50
|
+
options.onDismiss();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function onCancel() {
|
|
54
|
+
start = null;
|
|
55
|
+
}
|
|
56
|
+
node.addEventListener('touchstart', onStart, { passive: true });
|
|
57
|
+
node.addEventListener('touchend', onEnd);
|
|
58
|
+
node.addEventListener('touchcancel', onCancel);
|
|
59
|
+
return {
|
|
60
|
+
update(next) {
|
|
61
|
+
options = next;
|
|
62
|
+
},
|
|
63
|
+
destroy() {
|
|
64
|
+
node.removeEventListener('touchstart', onStart);
|
|
65
|
+
node.removeEventListener('touchend', onEnd);
|
|
66
|
+
node.removeEventListener('touchcancel', onCancel);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Calendar Component
|
|
4
4
|
* Month view calendar with event indicators and day navigation
|
|
5
|
+
*
|
|
6
|
+
* @deprecated Use `CalendarView`. This component computes days in the
|
|
7
|
+
* browser's zone, prints English-only names, hard-codes game/meeting/event
|
|
8
|
+
* emoji and `/events/y/m/d` links, and has no phone agenda. See the
|
|
9
|
+
* smrt-ui README, "Calendar", for the migration.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { M } from '../../i18n/strings.ui.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/Calendar.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Calendar.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/calendar/Calendar.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzE,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAChC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,eAAe,CAAC,EAAE,CAChB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;IACV,2CAA2C;IAC3C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1D,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAqQD,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|