@happyvertical/smrt-ui 0.51.39 → 0.53.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 +25 -3
- package/README.md +558 -8
- 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 +178 -55
- 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/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/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__/DataTableResponsive.test.js +80 -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__/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/__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 +27 -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/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/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/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__/ConfirmDialog.test.js +38 -20
- package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
- package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
- 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 +5 -1
- package/dist/components/feedback/index.d.ts.map +1 -1
- package/dist/components/feedback/index.js +5 -0
- 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/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/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 +34 -3
- 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 +108 -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/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/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 +17 -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 +16 -4
- 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/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/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 +17 -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/StagedControlReview.svelte +21 -12
- package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
- package/dist/components/forms/Switch.svelte +18 -5
- 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/TagsInput.svelte +8 -1
- package/dist/components/forms/Textarea.svelte +16 -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 +354 -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__/Form.test.js +46 -0
- package/dist/components/forms/__tests__/FormActionBar.test.js +22 -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__/SearchInput.test.js +92 -0
- package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
- package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
- 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__/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__/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__/long-press.test.js +175 -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__/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/__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/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/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/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/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 +21 -2
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +22 -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/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/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/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/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/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/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 +16 -2
- package/dist/components/ui/Button.svelte.d.ts +2 -0
- package/dist/components/ui/Button.svelte.d.ts.map +1 -1
- 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 +65 -0
- package/dist/i18n/strings.ui.d.ts.map +1 -1
- package/dist/i18n/strings.ui.js +72 -0
- package/dist/styles/tokens.css +2 -0
- package/dist/svelte/__tests__/playground.test.js +3 -1
- 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/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 +20 -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/dist/types-generic.d.ts +2 -0
- package/dist/types-generic.d.ts.map +1 -1
- package/package.json +9 -3
|
@@ -17,6 +17,8 @@ import { useControlRegistration } from './use-control-registration.svelte.js';
|
|
|
17
17
|
|
|
18
18
|
export interface Props
|
|
19
19
|
extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
|
|
20
|
+
/** Target density; omit to inherit the ThemeProvider setting. */
|
|
21
|
+
density?: 'comfortable' | 'touch';
|
|
20
22
|
/** Whether the checkbox is checked (bindable). */
|
|
21
23
|
checked?: boolean;
|
|
22
24
|
/** Whether the checkbox shows indeterminate state. */
|
|
@@ -43,6 +45,7 @@ let {
|
|
|
43
45
|
onchange,
|
|
44
46
|
'aria-label': ariaLabel,
|
|
45
47
|
'aria-describedby': ariaDescribedby,
|
|
48
|
+
density,
|
|
46
49
|
...rest
|
|
47
50
|
}: Props = $props();
|
|
48
51
|
|
|
@@ -121,8 +124,9 @@ useControlRegistration(() => {
|
|
|
121
124
|
</script>
|
|
122
125
|
|
|
123
126
|
<label
|
|
124
|
-
class="checkbox {className}"
|
|
127
|
+
data-density={density} class="checkbox {className}"
|
|
125
128
|
class:checkbox--disabled={disabled}
|
|
129
|
+
data-smrt-hit-target
|
|
126
130
|
data-smrt-control={controlId}
|
|
127
131
|
data-smrt-form={interactionContext?.formId}
|
|
128
132
|
data-smrt-subject-type={resolvedInteraction === false ? undefined : resolvedInteraction.subject?.type}
|
|
@@ -150,14 +154,41 @@ useControlRegistration(() => {
|
|
|
150
154
|
</label>
|
|
151
155
|
|
|
152
156
|
<style>
|
|
153
|
-
.
|
|
157
|
+
/* The visible box stays 18px. A transparent 44x44 hit area (WCAG 2.5.8 / M3 touch target) is
|
|
158
|
+
centred on it with ::before, so clicks nearby still land on the wrapping <label>. It adds no
|
|
159
|
+
layout. The box and the label text sit above every hit area (z-index 1 over 0), so one
|
|
160
|
+
control's hit area never steals a click from another control's box or text.
|
|
161
|
+
The hit area is painted above plain content around it, so it only grows sideways when the
|
|
162
|
+
checkbox stands alone in its parent. Beside anything else (a row title, a link, other text)
|
|
163
|
+
it keeps to the box's own column and grows vertically only. Resize it with
|
|
164
|
+
--smrt-control-hit-size (0px turns it off). */
|
|
165
|
+
.checkbox { --_box: 1.125rem; --_hit: max(var(--smrt-control-hit-size, 2.75rem), var(--_box)); position: relative; display: inline-flex; align-items: center; gap: var(--smrt-spacing-2, .5rem); cursor: pointer; color: var(--smrt-color-on-surface); }
|
|
166
|
+
.checkbox::before { content: ''; position: absolute; z-index: 0; inset-block-start: 50%; inset-inline-start: calc(var(--_box) / 2); inline-size: var(--_hit); block-size: var(--_hit); margin-block-start: calc(var(--_hit) / -2); margin-inline-start: calc(var(--_hit) / -2); }
|
|
154
167
|
.checkbox--disabled { opacity: .5; cursor: not-allowed; }
|
|
155
168
|
input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
|
156
|
-
.checkbox__box { width: 1.125rem; height: 1.125rem; display: grid; place-items: center; border: 2px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-extra-small, 3px); background: var(--smrt-color-surface); transition: background var(--smrt-duration-short2), border-color var(--smrt-duration-short2); }
|
|
169
|
+
.checkbox__box { position: relative; z-index: 1; width: 1.125rem; height: 1.125rem; display: grid; place-items: center; border: 2px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-extra-small, 3px); background: var(--smrt-color-surface); transition: background var(--smrt-duration-short2), border-color var(--smrt-duration-short2); }
|
|
170
|
+
.checkbox__label { position: relative; z-index: 1; }
|
|
157
171
|
input:checked + .checkbox__box, input:indeterminate + .checkbox__box { background: var(--smrt-color-primary); border-color: var(--smrt-color-primary); color: var(--smrt-color-on-primary); }
|
|
158
172
|
input:focus-visible + .checkbox__box { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
|
|
159
173
|
.checkbox__check { font-size: .8rem; font-weight: 800; line-height: 1; }
|
|
160
174
|
.checkbox__dash { width: .6rem; height: 2px; background: currentColor; }
|
|
175
|
+
.checkbox:not(:only-child)::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
|
|
161
176
|
.checkbox[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-small); }
|
|
177
|
+
|
|
178
|
+
/* Table cells: neighbouring cells hold links and other controls, so the hit area never leaves
|
|
179
|
+
the cell's column. It grows vertically only (box wide)... */
|
|
180
|
+
:global(:is(td, th)) .checkbox::before { inline-size: var(--_box); margin-inline-start: calc(var(--_box) / -2); }
|
|
181
|
+
/* ...unless the checkbox is the cell's only content: then the whole cell, clipped to its own
|
|
182
|
+
edges, is the hit area (the 44px target the header and every row share). The cell only needs
|
|
183
|
+
a containing block, so :where() keeps that rule at zero specificity: a sticky header or a
|
|
184
|
+
pinned column's own position always wins (a sticky cell contains the hit area too). */
|
|
185
|
+
:global(:where(td:has(> [data-smrt-hit-target]:only-child), th:has(> [data-smrt-hit-target]:only-child))) { position: relative; }
|
|
186
|
+
:global(:is(td, th):has(> [data-smrt-hit-target]:only-child)) > .checkbox { position: static; }
|
|
187
|
+
:global(:is(td, th):has(> [data-smrt-hit-target]:only-child)) > .checkbox::before { inset: 0; inline-size: auto; block-size: auto; margin: 0; }
|
|
188
|
+
|
|
162
189
|
@media (prefers-reduced-motion: reduce) { .checkbox__box { transition: none; } }
|
|
190
|
+
|
|
191
|
+
.checkbox[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
|
|
192
|
+
.checkbox[data-density='comfortable'] { --smrt-control-target-min: 0px; }
|
|
193
|
+
.checkbox { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
|
|
163
194
|
</style>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
2
|
import type { ControlInteractionOptions } from './control-interaction.js';
|
|
3
3
|
export interface Props extends Omit<HTMLInputAttributes, 'type' | 'class' | 'checked'> {
|
|
4
|
+
/** Target density; omit to inherit the ThemeProvider setting. */
|
|
5
|
+
density?: 'comfortable' | 'touch';
|
|
4
6
|
/** Whether the checkbox is checked (bindable). */
|
|
5
7
|
checked?: boolean;
|
|
6
8
|
/** Whether the checkbox shows indeterminate state. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Checkbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/D,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;
|
|
1
|
+
{"version":3,"file":"Checkbox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Checkbox.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAM3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAW1E,MAAM,WAAW,KACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/D,iEAAiE;IACjE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,kDAAkD;IAClD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;CACjD;AAmHD,QAAA,MAAM,QAAQ,kDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import {
|
|
3
4
|
emitControlChange,
|
|
4
5
|
highlightControl,
|
|
@@ -36,6 +37,12 @@ export interface Props {
|
|
|
36
37
|
allowCustom?: boolean;
|
|
37
38
|
/** Interaction options or false to disable all. */
|
|
38
39
|
interaction?: ControlInteractionOptions | false;
|
|
40
|
+
/**
|
|
41
|
+
* Label for `value` while its option is not in `options` yet (options load
|
|
42
|
+
* asynchronously, or only the current record is known). Without it the
|
|
43
|
+
* field stays empty rather than flashing the raw value (an id).
|
|
44
|
+
*/
|
|
45
|
+
valueLabel?: string;
|
|
39
46
|
/** Callback when the selected value changes. */
|
|
40
47
|
onvaluechange?: (value: string) => void;
|
|
41
48
|
/** CSS class to apply to the combobox container. */
|
|
@@ -51,6 +58,7 @@ let {
|
|
|
51
58
|
required = false,
|
|
52
59
|
allowCustom = false,
|
|
53
60
|
interaction,
|
|
61
|
+
valueLabel,
|
|
54
62
|
onvaluechange,
|
|
55
63
|
class: className = '',
|
|
56
64
|
}: Props = $props();
|
|
@@ -61,12 +69,25 @@ const interactionContext = tryGetControlInteractionContext();
|
|
|
61
69
|
let rootEl = $state<HTMLDivElement | null>(null);
|
|
62
70
|
let inputEl = $state<HTMLInputElement | null>(null);
|
|
63
71
|
let open = $state(false);
|
|
64
|
-
|
|
72
|
+
const initialValue = untrack(() => value);
|
|
73
|
+
/** The text for a value: its option's label, never a raw id (see valueLabel). */
|
|
74
|
+
function labelForValue(candidate: string): string {
|
|
75
|
+
if (!candidate) return '';
|
|
76
|
+
const option = options.find((item) => String(item.value) === candidate);
|
|
77
|
+
if (option) return option.label;
|
|
78
|
+
if (valueLabel) return valueLabel;
|
|
79
|
+
return allowCustom ? candidate : '';
|
|
80
|
+
}
|
|
81
|
+
let query = $state(untrack(() => labelForValue(value)));
|
|
82
|
+
/** The person has typed since opening: only then does the list filter. */
|
|
83
|
+
let typed = $state(false);
|
|
65
84
|
let activeIndex = $state(0);
|
|
66
85
|
const filtered = $derived(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
typed
|
|
87
|
+
? options.filter((option) =>
|
|
88
|
+
option.label.toLowerCase().includes(query.toLowerCase()),
|
|
89
|
+
)
|
|
90
|
+
: options,
|
|
70
91
|
);
|
|
71
92
|
const controlId = $derived(
|
|
72
93
|
interaction === false ? undefined : (interaction?.id ?? name ?? inputId),
|
|
@@ -76,6 +97,7 @@ function commit(option: ControlOption, userEdit = false) {
|
|
|
76
97
|
const changed = String(option.value) !== value;
|
|
77
98
|
value = String(option.value);
|
|
78
99
|
query = option.label;
|
|
100
|
+
typed = false;
|
|
79
101
|
open = false;
|
|
80
102
|
onvaluechange?.(value);
|
|
81
103
|
if (userEdit && changed) {
|
|
@@ -94,6 +116,7 @@ function setValue(next: unknown) {
|
|
|
94
116
|
else if (allowCustom) {
|
|
95
117
|
value = candidate;
|
|
96
118
|
query = candidate;
|
|
119
|
+
typed = false;
|
|
97
120
|
onvaluechange?.(candidate);
|
|
98
121
|
}
|
|
99
122
|
}
|
|
@@ -104,8 +127,24 @@ function prepareValue(next: unknown) {
|
|
|
104
127
|
if (allowCustom) return candidate;
|
|
105
128
|
return candidate;
|
|
106
129
|
}
|
|
130
|
+
function openList() {
|
|
131
|
+
if (open) return;
|
|
132
|
+
open = true;
|
|
133
|
+
// Reopening shows every option, starting at the current choice.
|
|
134
|
+
typed = false;
|
|
135
|
+
const selected = options.findIndex(
|
|
136
|
+
(option) => String(option.value) === value,
|
|
137
|
+
);
|
|
138
|
+
activeIndex = selected >= 0 ? selected : 0;
|
|
139
|
+
}
|
|
140
|
+
function closeList() {
|
|
141
|
+
open = false;
|
|
142
|
+
// Leaving without choosing puts the chosen label back.
|
|
143
|
+
typed = false;
|
|
144
|
+
}
|
|
107
145
|
function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
|
|
108
146
|
query = event.currentTarget.value;
|
|
147
|
+
typed = true;
|
|
109
148
|
open = true;
|
|
110
149
|
activeIndex = 0;
|
|
111
150
|
if (allowCustom) {
|
|
@@ -116,7 +155,10 @@ function handleInput(event: Event & { currentTarget: HTMLInputElement }) {
|
|
|
116
155
|
function handleKeydown(event: KeyboardEvent) {
|
|
117
156
|
if (event.key === 'ArrowDown') {
|
|
118
157
|
event.preventDefault();
|
|
119
|
-
open
|
|
158
|
+
if (!open) {
|
|
159
|
+
openList();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
120
162
|
const enabled = filtered
|
|
121
163
|
.map((option, index) => (option.disabled ? -1 : index))
|
|
122
164
|
.filter((index) => index >= 0);
|
|
@@ -134,16 +176,55 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
134
176
|
event.preventDefault();
|
|
135
177
|
commit(filtered[activeIndex], true);
|
|
136
178
|
} else if (event.key === 'Escape') {
|
|
137
|
-
|
|
179
|
+
closeList();
|
|
138
180
|
}
|
|
139
181
|
}
|
|
182
|
+
$effect(() => {
|
|
183
|
+
const form = inputEl?.form;
|
|
184
|
+
if (!form) return;
|
|
185
|
+
let disposed = false;
|
|
186
|
+
const timers = new Set<ReturnType<typeof setTimeout>>();
|
|
187
|
+
const reset = (event: Event) => {
|
|
188
|
+
const valueAtDispatch = value;
|
|
189
|
+
const queryAtDispatch = query;
|
|
190
|
+
// Native dispatch can checkpoint microtasks between reset listeners.
|
|
191
|
+
// Wait for the next task so later cancellation and edits win.
|
|
192
|
+
const timer = setTimeout(() => {
|
|
193
|
+
timers.delete(timer);
|
|
194
|
+
if (
|
|
195
|
+
disposed ||
|
|
196
|
+
event.defaultPrevented ||
|
|
197
|
+
value !== valueAtDispatch ||
|
|
198
|
+
query !== queryAtDispatch
|
|
199
|
+
)
|
|
200
|
+
return;
|
|
201
|
+
value = initialValue;
|
|
202
|
+
query = labelForValue(initialValue);
|
|
203
|
+
typed = false;
|
|
204
|
+
open = false;
|
|
205
|
+
recordControlUserEdit(
|
|
206
|
+
interactionContext,
|
|
207
|
+
controlId,
|
|
208
|
+
interaction === false ? undefined : interaction?.subject,
|
|
209
|
+
);
|
|
210
|
+
onvaluechange?.(value);
|
|
211
|
+
});
|
|
212
|
+
timers.add(timer);
|
|
213
|
+
};
|
|
214
|
+
form.addEventListener('reset', reset);
|
|
215
|
+
return () => {
|
|
216
|
+
disposed = true;
|
|
217
|
+
form.removeEventListener('reset', reset);
|
|
218
|
+
for (const timer of timers) clearTimeout(timer);
|
|
219
|
+
};
|
|
220
|
+
});
|
|
140
221
|
$effect(() => {
|
|
141
222
|
if (!open) return;
|
|
142
223
|
const dismissPointer = (event: PointerEvent) => {
|
|
143
|
-
if (rootEl && !rootEl.contains(event.target as Node))
|
|
224
|
+
if (rootEl && !rootEl.contains(event.target as Node)) closeList();
|
|
144
225
|
};
|
|
145
226
|
const dismissFocus = (event: FocusEvent) => {
|
|
146
|
-
if (rootEl && !rootEl.contains(event.target as Node))
|
|
227
|
+
if (rootEl && !rootEl.contains(event.target as Node)) closeList();
|
|
147
228
|
};
|
|
148
229
|
document.addEventListener('pointerdown', dismissPointer, true);
|
|
149
230
|
document.addEventListener('focusin', dismissFocus, true);
|
|
@@ -152,9 +233,21 @@ $effect(() => {
|
|
|
152
233
|
document.removeEventListener('focusin', dismissFocus, true);
|
|
153
234
|
};
|
|
154
235
|
});
|
|
236
|
+
// Follow the value (and options that load later) unless the person is typing.
|
|
237
|
+
$effect(() => {
|
|
238
|
+
const label = labelForValue(value);
|
|
239
|
+
if (!typed) query = label;
|
|
240
|
+
});
|
|
241
|
+
// Validity follows the committed `value`, not the visible text: a value whose
|
|
242
|
+
// option has not loaded shows an empty field but is filled in, and text typed
|
|
243
|
+
// without choosing an option is not a value.
|
|
155
244
|
$effect(() => {
|
|
156
|
-
|
|
157
|
-
|
|
245
|
+
if (!inputEl) return;
|
|
246
|
+
inputEl.setCustomValidity(
|
|
247
|
+
required && !disabled && !value && query.trim() !== ''
|
|
248
|
+
? 'Choose an option from the list.'
|
|
249
|
+
: '',
|
|
250
|
+
);
|
|
158
251
|
});
|
|
159
252
|
useControlRegistration(() => {
|
|
160
253
|
const root = rootEl;
|
|
@@ -203,9 +296,12 @@ useControlRegistration(() => {
|
|
|
203
296
|
<div bind:this={rootEl} class="combobox {className}" data-smrt-control={controlId} data-smrt-form={interactionContext?.formId}
|
|
204
297
|
data-smrt-subject-type={interaction === false ? undefined : interaction?.subject?.type}
|
|
205
298
|
data-smrt-subject-id={interaction === false ? undefined : interaction?.subject?.id}>
|
|
206
|
-
<label for={inputId}>{label}</label
|
|
299
|
+
<label for={inputId}>{label}</label>
|
|
300
|
+
<!-- The form posts the committed value (an option id), never the label the person sees. -->
|
|
301
|
+
{#if name}<input type="hidden" {name} {value} {disabled} />{/if}
|
|
302
|
+
<input bind:this={inputEl} id={inputId} role="combobox" autocomplete="off" {placeholder} {disabled} required={required && !value} aria-required={required ? 'true' : undefined} value={query}
|
|
207
303
|
aria-expanded={open} aria-controls={listId} aria-autocomplete="list" aria-activedescendant={open && filtered[activeIndex] ? `${listId}-${activeIndex}` : undefined}
|
|
208
|
-
onfocus={
|
|
304
|
+
onfocus={openList} onclick={openList} oninput={handleInput} onkeydown={handleKeydown} />
|
|
209
305
|
{#if open && filtered.length}<div id={listId} class="options" role="listbox">{#each filtered as option, index (option.value)}<button id={`${listId}-${index}`} type="button" role="option"
|
|
210
306
|
aria-selected={String(option.value) === value} class:active={index === activeIndex} disabled={option.disabled} onpointerdown={(event) => event.preventDefault()} onclick={() => commit(option, true)}>{option.label}</button>{/each}</div>{/if}
|
|
211
307
|
</div>
|
|
@@ -18,6 +18,12 @@ export interface Props {
|
|
|
18
18
|
allowCustom?: boolean;
|
|
19
19
|
/** Interaction options or false to disable all. */
|
|
20
20
|
interaction?: ControlInteractionOptions | false;
|
|
21
|
+
/**
|
|
22
|
+
* Label for `value` while its option is not in `options` yet (options load
|
|
23
|
+
* asynchronously, or only the current record is known). Without it the
|
|
24
|
+
* field stays empty rather than flashing the raw value (an id).
|
|
25
|
+
*/
|
|
26
|
+
valueLabel?: string;
|
|
21
27
|
/** Callback when the selected value changes. */
|
|
22
28
|
onvaluechange?: (value: string) => void;
|
|
23
29
|
/** CSS class to apply to the combobox container. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Combobox.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Combobox.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAWlC,MAAM,WAAW,KAAK;IACpB,uCAAuC;IACvC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,WAAW,CAAC,EAAE,yBAAyB,GAAG,KAAK,CAAC;IAChD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAyQD,QAAA,MAAM,QAAQ,gDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DictationButton — the microphone next to a text field (see `Dictation`).
|
|
4
|
+
*
|
|
5
|
+
* Tap to start listening, tap again to stop. It is always there, so people
|
|
6
|
+
* who cannot (or do not know to) press and hold the field can still speak.
|
|
7
|
+
* 44px target, named by `aria-label`, `aria-pressed` while listening, and a
|
|
8
|
+
* listening dot that does not rely on color alone (the name changes too).
|
|
9
|
+
* Pair it with `DictationStatus` for the words ("Listening…", errors).
|
|
10
|
+
*/
|
|
11
|
+
import { M } from '../../i18n/strings.ui.js';
|
|
12
|
+
import { useI18n } from '../../i18n/use-i18n.js';
|
|
13
|
+
import Button from '../ui/Button.svelte';
|
|
14
|
+
import type { Dictation } from './dictation.svelte.js';
|
|
15
|
+
import { primeReadyBeep } from './ready-beep.js';
|
|
16
|
+
|
|
17
|
+
export interface Props {
|
|
18
|
+
/** The dictation this button starts and stops. */
|
|
19
|
+
dictation: Dictation;
|
|
20
|
+
/** Disable the button (for example while the form sends). */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Accessible name to start (default "Speak instead of typing"). */
|
|
23
|
+
label?: string;
|
|
24
|
+
/** Accessible name to stop (default "Stop listening"). */
|
|
25
|
+
stopLabel?: string;
|
|
26
|
+
/** Tooltip (default mentions press-and-hold on the text box). */
|
|
27
|
+
title?: string;
|
|
28
|
+
/** Extra class names. */
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
dictation,
|
|
34
|
+
disabled = false,
|
|
35
|
+
label,
|
|
36
|
+
stopLabel,
|
|
37
|
+
title,
|
|
38
|
+
class: className = '',
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const { t } = useI18n();
|
|
42
|
+
const active = $derived(dictation.active);
|
|
43
|
+
// While a recording is being written down, a tap would do nothing.
|
|
44
|
+
const busy = $derived(dictation.state === 'transcribing');
|
|
45
|
+
const name = $derived(
|
|
46
|
+
active
|
|
47
|
+
? (stopLabel ?? t(M['ui.dictation.stop']))
|
|
48
|
+
: (label ?? t(M['ui.dictation.start'])),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
function handleClick() {
|
|
52
|
+
// A tap is a user gesture: wake the audio so the ready beep can play.
|
|
53
|
+
primeReadyBeep();
|
|
54
|
+
void dictation.unlock();
|
|
55
|
+
void dictation.toggle();
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<Button
|
|
60
|
+
type="button"
|
|
61
|
+
variant="ghost"
|
|
62
|
+
size="sm"
|
|
63
|
+
class={`smrt-dictation-button${active ? ' smrt-dictation-button--listening' : ''} ${className}`.trim()}
|
|
64
|
+
aria-label={name}
|
|
65
|
+
aria-pressed={active}
|
|
66
|
+
aria-busy={busy || undefined}
|
|
67
|
+
title={active ? name : (title ?? t(M['ui.dictation.start_hint']))}
|
|
68
|
+
disabled={disabled || busy}
|
|
69
|
+
onclick={handleClick}
|
|
70
|
+
data-dictation-state={dictation.state}
|
|
71
|
+
>
|
|
72
|
+
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
73
|
+
<rect x="9" y="2" width="6" height="12" rx="3"></rect>
|
|
74
|
+
<path d="M5 10a7 7 0 0 0 14 0"></path>
|
|
75
|
+
<path d="M12 17v4"></path>
|
|
76
|
+
</svg>
|
|
77
|
+
{#if active}
|
|
78
|
+
<span class="smrt-dictation-dot" aria-hidden="true"></span>
|
|
79
|
+
{/if}
|
|
80
|
+
</Button>
|
|
81
|
+
|
|
82
|
+
<style>
|
|
83
|
+
:global(.smrt-dictation-button) {
|
|
84
|
+
position: relative;
|
|
85
|
+
flex-shrink: 0;
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
justify-content: center;
|
|
89
|
+
inline-size: 44px;
|
|
90
|
+
block-size: 44px;
|
|
91
|
+
min-inline-size: 44px;
|
|
92
|
+
padding: 0;
|
|
93
|
+
border-radius: var(--smrt-radius-full, 9999px);
|
|
94
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
:global(.smrt-dictation-button--listening) {
|
|
98
|
+
background: var(--smrt-color-error-container, #ffdad6);
|
|
99
|
+
color: var(--smrt-color-on-error-container, #410002);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.smrt-dictation-dot {
|
|
103
|
+
position: absolute;
|
|
104
|
+
inset-block-start: 6px;
|
|
105
|
+
inset-inline-end: 6px;
|
|
106
|
+
inline-size: 10px;
|
|
107
|
+
block-size: 10px;
|
|
108
|
+
border-radius: 50%;
|
|
109
|
+
background: var(--smrt-color-error, #ba1a1a);
|
|
110
|
+
animation: smrt-dictation-pulse 1.2s ease-in-out infinite;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@keyframes smrt-dictation-pulse {
|
|
114
|
+
0%,
|
|
115
|
+
100% {
|
|
116
|
+
opacity: 1;
|
|
117
|
+
}
|
|
118
|
+
50% {
|
|
119
|
+
opacity: 0.35;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (prefers-reduced-motion: reduce) {
|
|
124
|
+
.smrt-dictation-dot {
|
|
125
|
+
animation: none;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Dictation } from './dictation.svelte.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** The dictation this button starts and stops. */
|
|
4
|
+
dictation: Dictation;
|
|
5
|
+
/** Disable the button (for example while the form sends). */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Accessible name to start (default "Speak instead of typing"). */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Accessible name to stop (default "Stop listening"). */
|
|
10
|
+
stopLabel?: string;
|
|
11
|
+
/** Tooltip (default mentions press-and-hold on the text box). */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Extra class names. */
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
declare const DictationButton: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type DictationButton = ReturnType<typeof DictationButton>;
|
|
18
|
+
export default DictationButton;
|
|
19
|
+
//# sourceMappingURL=DictationButton.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationButton.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationButton.svelte.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAIvD,MAAM,WAAW,KAAK;IACpB,kDAAkD;IAClD,SAAS,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoDD,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DictationStatus — what dictation is doing, in words (see `Dictation`).
|
|
4
|
+
*
|
|
5
|
+
* "Listening…" (with the words heard so far) while the microphone is on,
|
|
6
|
+
* "Writing it down…" while a recording is turned into text, and
|
|
7
|
+
* a plain explanation when it cannot listen: the browser cannot turn speech
|
|
8
|
+
* into text, the microphone is blocked, or nothing was heard. Nothing while
|
|
9
|
+
* idle. Listening is announced politely; problems are announced as alerts.
|
|
10
|
+
*/
|
|
11
|
+
import { M } from '../../i18n/strings.ui.js';
|
|
12
|
+
import { useI18n } from '../../i18n/use-i18n.js';
|
|
13
|
+
import type { Dictation, DictationErrorKind } from './dictation.svelte.js';
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
/** The dictation session whose listening state and messages this shows. */
|
|
17
|
+
dictation: Dictation;
|
|
18
|
+
/** Extra class names. */
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { dictation, class: className = '' }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const { t } = useI18n();
|
|
25
|
+
const showLine = $derived(
|
|
26
|
+
dictation.state === 'starting' ||
|
|
27
|
+
dictation.state === 'listening' ||
|
|
28
|
+
dictation.state === 'stopping' ||
|
|
29
|
+
dictation.state === 'transcribing',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
function errorText(kind: DictationErrorKind | null): string {
|
|
33
|
+
switch (kind) {
|
|
34
|
+
case 'unsupported':
|
|
35
|
+
return t(M['ui.dictation.unsupported']);
|
|
36
|
+
case 'denied':
|
|
37
|
+
return t(M['ui.dictation.denied']);
|
|
38
|
+
case 'no-speech':
|
|
39
|
+
return t(M['ui.dictation.no_speech']);
|
|
40
|
+
case 'microphone':
|
|
41
|
+
return t(M['ui.dictation.microphone']);
|
|
42
|
+
case 'interrupted':
|
|
43
|
+
return t(M['ui.dictation.interrupted']);
|
|
44
|
+
case 'too-long':
|
|
45
|
+
return t(M['ui.dictation.too_long']);
|
|
46
|
+
case 'not-transcribed':
|
|
47
|
+
return t(M['ui.dictation.not_transcribed']);
|
|
48
|
+
case 'unavailable':
|
|
49
|
+
return t(M['ui.dictation.unavailable']);
|
|
50
|
+
case 'forbidden':
|
|
51
|
+
return t(M['ui.dictation.forbidden']);
|
|
52
|
+
default:
|
|
53
|
+
return t(M['ui.dictation.failed']);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<div class={`smrt-dictation-status ${className}`.trim()}>
|
|
59
|
+
<p
|
|
60
|
+
class="smrt-dictation-status-line"
|
|
61
|
+
class:smrt-dictation-status--empty={!showLine}
|
|
62
|
+
role="status"
|
|
63
|
+
aria-live="polite"
|
|
64
|
+
>
|
|
65
|
+
{#if dictation.state === 'starting'}
|
|
66
|
+
{t(M['ui.dictation.starting'])}
|
|
67
|
+
{:else if dictation.state === 'listening' || dictation.state === 'stopping'}
|
|
68
|
+
<span class="smrt-dictation-status-dot" aria-hidden="true"></span>
|
|
69
|
+
{t(M['ui.dictation.listening'])}
|
|
70
|
+
{#if dictation.interim}
|
|
71
|
+
<span class="smrt-dictation-status-interim">“{dictation.interim}”</span>
|
|
72
|
+
{/if}
|
|
73
|
+
{:else if dictation.state === 'transcribing'}
|
|
74
|
+
<span class="smrt-dictation-status-spinner" aria-hidden="true"></span>
|
|
75
|
+
{t(M['ui.dictation.transcribing'])}
|
|
76
|
+
{/if}
|
|
77
|
+
</p>
|
|
78
|
+
<p
|
|
79
|
+
class="smrt-dictation-status-error"
|
|
80
|
+
class:smrt-dictation-status--empty={dictation.state !== 'error'}
|
|
81
|
+
role="alert"
|
|
82
|
+
data-dictation-error={dictation.state === 'error'
|
|
83
|
+
? (dictation.errorCode ?? dictation.errorKind)
|
|
84
|
+
: undefined}
|
|
85
|
+
>
|
|
86
|
+
{#if dictation.state === 'error'}
|
|
87
|
+
{errorText(dictation.errorKind)}
|
|
88
|
+
{/if}
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
.smrt-dictation-status {
|
|
94
|
+
display: contents;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.smrt-dictation-status-line,
|
|
98
|
+
.smrt-dictation-status-error {
|
|
99
|
+
margin: 0;
|
|
100
|
+
font: var(--smrt-typography-body-small-font, 0.8125rem / 1.4 sans-serif);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
.smrt-dictation-status-line {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-wrap: wrap;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 0.4rem;
|
|
109
|
+
padding: 0.25rem 0;
|
|
110
|
+
color: var(--smrt-color-on-surface, #1a1c1e);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Kept mounted while empty (live regions must exist before they speak),
|
|
114
|
+
but take no room. */
|
|
115
|
+
.smrt-dictation-status--empty {
|
|
116
|
+
position: absolute;
|
|
117
|
+
inline-size: 1px;
|
|
118
|
+
block-size: 1px;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
clip-path: inset(50%);
|
|
121
|
+
padding: 0;
|
|
122
|
+
white-space: nowrap;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.smrt-dictation-status-dot {
|
|
126
|
+
inline-size: 8px;
|
|
127
|
+
block-size: 8px;
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: var(--smrt-color-error, #ba1a1a);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.smrt-dictation-status-spinner {
|
|
133
|
+
inline-size: 12px;
|
|
134
|
+
block-size: 12px;
|
|
135
|
+
border-radius: 50%;
|
|
136
|
+
border: 2px solid var(--smrt-color-outline-variant, #c4c6cf);
|
|
137
|
+
border-block-start-color: var(--smrt-color-primary, #2f5ea8);
|
|
138
|
+
animation: smrt-dictation-spin 0.9s linear infinite;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@keyframes smrt-dictation-spin {
|
|
142
|
+
to {
|
|
143
|
+
transform: rotate(360deg);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@media (prefers-reduced-motion: reduce) {
|
|
148
|
+
.smrt-dictation-status-spinner {
|
|
149
|
+
animation: none;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.smrt-dictation-status-interim {
|
|
154
|
+
color: var(--smrt-color-on-surface-variant, #43474e);
|
|
155
|
+
font-style: italic;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.smrt-dictation-status-error {
|
|
159
|
+
padding: 0.4rem 0.6rem;
|
|
160
|
+
border-radius: var(--smrt-radius-medium, 8px);
|
|
161
|
+
background: var(--smrt-color-error-container, #ffdad6);
|
|
162
|
+
color: var(--smrt-color-on-error-container, #410002);
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Dictation } from './dictation.svelte.js';
|
|
2
|
+
export interface Props {
|
|
3
|
+
/** The dictation session whose listening state and messages this shows. */
|
|
4
|
+
dictation: Dictation;
|
|
5
|
+
/** Extra class names. */
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const DictationStatus: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DictationStatus = ReturnType<typeof DictationStatus>;
|
|
10
|
+
export default DictationStatus;
|
|
11
|
+
//# sourceMappingURL=DictationStatus.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DictationStatus.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/DictationStatus.svelte.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAsB,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,SAAS,EAAE,SAAS,CAAC;IACrB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwED,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|