@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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A navigation list (a menu, a tab row, a section list) as a mounted data
|
|
3
|
+
* surface, so an agent can move a person between pages without a REST path.
|
|
4
|
+
*
|
|
5
|
+
* A view intent can only dispatch a registry command, so "go to page X" needs
|
|
6
|
+
* a mounted surface whose visible control performs the navigation. This
|
|
7
|
+
* registers one: the links are published in the snapshot state
|
|
8
|
+
* (`state.links`: id, label, description, group — never an href an agent
|
|
9
|
+
* could steer), and each declared control resolves its payload to one of
|
|
10
|
+
* those links (or to a caller-built href) and hands it to `navigate`.
|
|
11
|
+
*
|
|
12
|
+
* Navigation is a read-only view-state change: nothing is created or changed.
|
|
13
|
+
*/
|
|
14
|
+
import type { DataSurfaceDescriptor, DataSurfaceJsonValue, DataSurfaceKind, DataSurfaceRegistry, DataSurfaceSubject } from '@happyvertical/smrt-types';
|
|
15
|
+
export interface LinkSurfaceItem {
|
|
16
|
+
/** Stable id, e.g. the nav item or tab id. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Plain label a person sees in the menu. */
|
|
19
|
+
label: string;
|
|
20
|
+
href: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
/** Group or section heading the link sits under, if any. */
|
|
23
|
+
group?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface LinkSurfaceControl {
|
|
26
|
+
id: string;
|
|
27
|
+
label: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a command payload to an href, or `null` to refuse. Defaults to
|
|
31
|
+
* {@link resolveLinkTarget} over the published links.
|
|
32
|
+
*/
|
|
33
|
+
resolve?: (payload: DataSurfaceJsonValue | undefined, links: readonly LinkSurfaceItem[]) => string | null;
|
|
34
|
+
}
|
|
35
|
+
export interface LinkSurfaceOptions {
|
|
36
|
+
registry: DataSurfaceRegistry;
|
|
37
|
+
surfaceId: string;
|
|
38
|
+
kind?: DataSurfaceKind;
|
|
39
|
+
subject?: DataSurfaceSubject;
|
|
40
|
+
label: string;
|
|
41
|
+
description: string;
|
|
42
|
+
links: readonly LinkSurfaceItem[];
|
|
43
|
+
/** Defaults to one `open` control over the published links. */
|
|
44
|
+
controls?: readonly LinkSurfaceControl[];
|
|
45
|
+
/**
|
|
46
|
+
* Perform the navigation. Return its promise (e.g. SvelteKit's `goto`):
|
|
47
|
+
* the command does not wait for it, since the new page may unmount this
|
|
48
|
+
* surface, but it is tracked on the registry, so
|
|
49
|
+
* `whenSurfaceNavigationSettled(registry)` resolves only once it finished
|
|
50
|
+
* and the new page's surfaces registered.
|
|
51
|
+
*/
|
|
52
|
+
navigate: (href: string) => unknown;
|
|
53
|
+
/** Extra JSON state published alongside the links (e.g. the active id). */
|
|
54
|
+
state?: Record<string, DataSurfaceJsonValue>;
|
|
55
|
+
}
|
|
56
|
+
export interface LinkSurfaceHandle {
|
|
57
|
+
update(next: {
|
|
58
|
+
links?: readonly LinkSurfaceItem[];
|
|
59
|
+
state?: Record<string, DataSurfaceJsonValue>;
|
|
60
|
+
}): void;
|
|
61
|
+
destroy(): void;
|
|
62
|
+
}
|
|
63
|
+
/** Cap on published links (and the surface's query limit). */
|
|
64
|
+
export declare const LINK_SURFACE_MAX_LINKS = 200;
|
|
65
|
+
/**
|
|
66
|
+
* The string an agent passed: `"Events"` or `{ target: "Events" }` (also
|
|
67
|
+
* `id` / `label` / `section` / `tab` / `page`).
|
|
68
|
+
*/
|
|
69
|
+
export declare function linkTargetFromPayload(payload: DataSurfaceJsonValue | undefined): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a payload to one link's href: by id, then exact label, then a unique
|
|
72
|
+
* label prefix, all case-insensitive. Ambiguity resolves to `null`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveLinkTarget(payload: DataSurfaceJsonValue | undefined, links: readonly LinkSurfaceItem[]): string | null;
|
|
75
|
+
/** The descriptor {@link registerLinkSurface} mounts. */
|
|
76
|
+
export declare function linkSurfaceDescriptor(options: Pick<LinkSurfaceOptions, 'surfaceId' | 'kind' | 'subject' | 'label' | 'description' | 'controls'>): DataSurfaceDescriptor;
|
|
77
|
+
/** Mount a navigation list on a data-surface registry. */
|
|
78
|
+
export declare function registerLinkSurface(options: LinkSurfaceOptions): LinkSurfaceHandle;
|
|
79
|
+
//# sourceMappingURL=link-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-surface.d.ts","sourceRoot":"","sources":["../../../src/components/data/link-surface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,KAAK,EAAE,SAAS,eAAe,EAAE,KAC9B,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACpC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,IAAI,EAAE;QACX,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;QACnC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;KAC9C,GAAG,IAAI,CAAC;IACT,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,8DAA8D;AAC9D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAe1C;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,GAAG,SAAS,GACxC,MAAM,GAAG,IAAI,CASf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,KAAK,EAAE,SAAS,eAAe,EAAE,GAChC,MAAM,GAAG,IAAI,CAaf;AAaD,yDAAyD;AACzD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,IAAI,CACX,kBAAkB,EAClB,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,GAAG,UAAU,CACxE,GACA,qBAAqB,CAqCvB;AAED,0DAA0D;AAC1D,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,kBAAkB,GAC1B,iBAAiB,CAiDnB"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A navigation list (a menu, a tab row, a section list) as a mounted data
|
|
3
|
+
* surface, so an agent can move a person between pages without a REST path.
|
|
4
|
+
*
|
|
5
|
+
* A view intent can only dispatch a registry command, so "go to page X" needs
|
|
6
|
+
* a mounted surface whose visible control performs the navigation. This
|
|
7
|
+
* registers one: the links are published in the snapshot state
|
|
8
|
+
* (`state.links`: id, label, description, group — never an href an agent
|
|
9
|
+
* could steer), and each declared control resolves its payload to one of
|
|
10
|
+
* those links (or to a caller-built href) and hands it to `navigate`.
|
|
11
|
+
*
|
|
12
|
+
* Navigation is a read-only view-state change: nothing is created or changed.
|
|
13
|
+
*/
|
|
14
|
+
import { trackSurfaceNavigation } from './surface-navigation.js';
|
|
15
|
+
/** Cap on published links (and the surface's query limit). */
|
|
16
|
+
export const LINK_SURFACE_MAX_LINKS = 200;
|
|
17
|
+
const DEFAULT_CONTROLS = [
|
|
18
|
+
{
|
|
19
|
+
id: 'open',
|
|
20
|
+
label: 'Open',
|
|
21
|
+
description: 'Go to one of the listed pages. Payload: { target } — a link id or its label.',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
function normalize(value) {
|
|
25
|
+
return value.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The string an agent passed: `"Events"` or `{ target: "Events" }` (also
|
|
29
|
+
* `id` / `label` / `section` / `tab` / `page`).
|
|
30
|
+
*/
|
|
31
|
+
export function linkTargetFromPayload(payload) {
|
|
32
|
+
if (typeof payload === 'string')
|
|
33
|
+
return payload.trim() ? payload : null;
|
|
34
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
35
|
+
for (const key of ['target', 'id', 'label', 'section', 'tab', 'page']) {
|
|
36
|
+
const value = payload[key];
|
|
37
|
+
if (typeof value === 'string' && value.trim())
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a payload to one link's href: by id, then exact label, then a unique
|
|
45
|
+
* label prefix, all case-insensitive. Ambiguity resolves to `null`.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveLinkTarget(payload, links) {
|
|
48
|
+
const target = linkTargetFromPayload(payload);
|
|
49
|
+
if (!target)
|
|
50
|
+
return null;
|
|
51
|
+
const wanted = normalize(target);
|
|
52
|
+
const byId = links.find((link) => normalize(link.id) === wanted);
|
|
53
|
+
if (byId)
|
|
54
|
+
return byId.href;
|
|
55
|
+
const byLabel = links.filter((link) => normalize(link.label) === wanted);
|
|
56
|
+
if (byLabel.length === 1)
|
|
57
|
+
return byLabel[0].href;
|
|
58
|
+
if (byLabel.length > 1)
|
|
59
|
+
return null;
|
|
60
|
+
const byPrefix = links.filter((link) => normalize(link.label).startsWith(wanted));
|
|
61
|
+
return byPrefix.length === 1 ? byPrefix[0].href : null;
|
|
62
|
+
}
|
|
63
|
+
function publishedLinks(links) {
|
|
64
|
+
return links.slice(0, LINK_SURFACE_MAX_LINKS).map((link) => ({
|
|
65
|
+
id: link.id,
|
|
66
|
+
label: link.label,
|
|
67
|
+
...(link.description ? { description: link.description } : {}),
|
|
68
|
+
...(link.group ? { group: link.group } : {}),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
/** The descriptor {@link registerLinkSurface} mounts. */
|
|
72
|
+
export function linkSurfaceDescriptor(options) {
|
|
73
|
+
const columns = [
|
|
74
|
+
{ id: 'id', label: 'ID', capabilities: ['read'], role: 'row-key' },
|
|
75
|
+
{ id: 'label', label: 'Name', capabilities: ['read'] },
|
|
76
|
+
{ id: 'description', label: 'About', capabilities: ['read'] },
|
|
77
|
+
{ id: 'group', label: 'Group', capabilities: ['read'] },
|
|
78
|
+
];
|
|
79
|
+
return {
|
|
80
|
+
version: 1,
|
|
81
|
+
identity: {
|
|
82
|
+
kind: options.kind ?? 'list',
|
|
83
|
+
surfaceId: options.surfaceId,
|
|
84
|
+
...(options.subject ? { subject: options.subject } : {}),
|
|
85
|
+
},
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
label: options.label,
|
|
88
|
+
description: options.description,
|
|
89
|
+
rowKey: 'id',
|
|
90
|
+
columns,
|
|
91
|
+
query: {
|
|
92
|
+
modes: ['rows'],
|
|
93
|
+
projectableColumnIds: columns.map((column) => column.id),
|
|
94
|
+
filterableColumnIds: [],
|
|
95
|
+
sortableColumnIds: [],
|
|
96
|
+
},
|
|
97
|
+
controls: (options.controls ?? DEFAULT_CONTROLS).map((control) => ({
|
|
98
|
+
id: control.id,
|
|
99
|
+
label: control.label,
|
|
100
|
+
...(control.description ? { description: control.description } : {}),
|
|
101
|
+
})),
|
|
102
|
+
actions: [],
|
|
103
|
+
limits: {
|
|
104
|
+
maxQueryRows: LINK_SURFACE_MAX_LINKS,
|
|
105
|
+
maxQueryBytes: 200_000,
|
|
106
|
+
maxSelectionSize: 1,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Mount a navigation list on a data-surface registry. */
|
|
111
|
+
export function registerLinkSurface(options) {
|
|
112
|
+
let links = [...options.links];
|
|
113
|
+
let extraState = { ...(options.state ?? {}) };
|
|
114
|
+
let revision = 1;
|
|
115
|
+
const controls = new Map((options.controls ?? DEFAULT_CONTROLS).map((control) => [
|
|
116
|
+
control.id,
|
|
117
|
+
control,
|
|
118
|
+
]));
|
|
119
|
+
const dispose = options.registry.register({
|
|
120
|
+
descriptor: linkSurfaceDescriptor(options),
|
|
121
|
+
getSnapshot: () => ({
|
|
122
|
+
revision,
|
|
123
|
+
state: { ...extraState, links: publishedLinks(links) },
|
|
124
|
+
selection: null,
|
|
125
|
+
}),
|
|
126
|
+
execute: (command) => {
|
|
127
|
+
const control = controls.get(command.controlId);
|
|
128
|
+
if (!control)
|
|
129
|
+
return { ok: false };
|
|
130
|
+
const href = (control.resolve ?? resolveLinkTarget)(command.payload, links);
|
|
131
|
+
if (!href)
|
|
132
|
+
return { ok: false };
|
|
133
|
+
void trackSurfaceNavigation(options.registry, options.navigate(href));
|
|
134
|
+
return undefined;
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
update(next) {
|
|
139
|
+
const nextLinks = next.links ? [...next.links] : links;
|
|
140
|
+
const nextState = next.state ? { ...next.state } : extraState;
|
|
141
|
+
const changed = JSON.stringify(publishedLinks(nextLinks)) !==
|
|
142
|
+
JSON.stringify(publishedLinks(links)) ||
|
|
143
|
+
JSON.stringify(nextState) !== JSON.stringify(extraState) ||
|
|
144
|
+
nextLinks.length !== links.length ||
|
|
145
|
+
nextLinks.some((link, index) => link.href !== links[index]?.href);
|
|
146
|
+
links = nextLinks;
|
|
147
|
+
extraState = nextState;
|
|
148
|
+
if (changed)
|
|
149
|
+
revision += 1;
|
|
150
|
+
},
|
|
151
|
+
destroy() {
|
|
152
|
+
dispose();
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive list-sort state for pages — the one object a list's headers
|
|
3
|
+
* (`SortableHeader list={...}`), phone picker (`ListSortSelect list={...}`),
|
|
4
|
+
* rows (`sortListRows(rows, list.sort, ...)`), and data surface (`sort` /
|
|
5
|
+
* `onSort`) share. Call during component initialisation.
|
|
6
|
+
*
|
|
7
|
+
* - `createUrlListSort(spec, { url, navigate })` — server-paginated lists:
|
|
8
|
+
* the sort lives in the URL (`?sort=&dir=`); headers are links, and `set`
|
|
9
|
+
* navigates. Framework-agnostic: pass the router's current URL and its
|
|
10
|
+
* navigate function (SvelteKit: `() => page.url` and `goto`).
|
|
11
|
+
* - `createLocalListSort(spec)` — lists that load every row: page state.
|
|
12
|
+
*/
|
|
13
|
+
import { type ListSort, type ListSortSpec } from './list-sort.js';
|
|
14
|
+
export interface ListSortState {
|
|
15
|
+
readonly sort: ListSort;
|
|
16
|
+
readonly spec: ListSortSpec;
|
|
17
|
+
/** Link to the list after activating `columnId`'s header (URL lists only). */
|
|
18
|
+
href(columnId: string): string | undefined;
|
|
19
|
+
/** Activate a header: toggle `columnId` (URL lists: the link navigates). */
|
|
20
|
+
toggle(columnId: string): void | Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Apply a sort (headers, the "Sort by" picker, agents). A sort naming an
|
|
23
|
+
* undeclared column or an unknown direction is ignored.
|
|
24
|
+
*/
|
|
25
|
+
set(sort: ListSort): void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface UrlListSortOptions {
|
|
28
|
+
/** The current URL (reactive, e.g. SvelteKit's `page.url`). */
|
|
29
|
+
url: () => URL | {
|
|
30
|
+
pathname: string;
|
|
31
|
+
search: string;
|
|
32
|
+
searchParams: URLSearchParams;
|
|
33
|
+
};
|
|
34
|
+
/** Navigate to a same-page href (keep focus and scroll, replace history). */
|
|
35
|
+
navigate: (href: string) => void | Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createUrlListSort(spec: ListSortSpec, options: UrlListSortOptions): ListSortState;
|
|
38
|
+
export declare function createLocalListSort(spec: ListSortSpec, initial?: ListSort): ListSortState;
|
|
39
|
+
//# sourceMappingURL=list-sort-state.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-sort-state.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/data/list-sort-state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,YAAY,EAIlB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,8EAA8E;IAC9E,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C,4EAA4E;IAC5E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,GAAG,EAAE,MACD,GAAG,GACH;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,eAAe,CAAA;KAAE,CAAC;IACxE,6EAA6E;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,kBAAkB,GAC1B,aAAa,CAef;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,YAAY,EAClB,OAAO,CAAC,EAAE,QAAQ,GACjB,aAAa,CAkBf"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive list-sort state for pages — the one object a list's headers
|
|
3
|
+
* (`SortableHeader list={...}`), phone picker (`ListSortSelect list={...}`),
|
|
4
|
+
* rows (`sortListRows(rows, list.sort, ...)`), and data surface (`sort` /
|
|
5
|
+
* `onSort`) share. Call during component initialisation.
|
|
6
|
+
*
|
|
7
|
+
* - `createUrlListSort(spec, { url, navigate })` — server-paginated lists:
|
|
8
|
+
* the sort lives in the URL (`?sort=&dir=`); headers are links, and `set`
|
|
9
|
+
* navigates. Framework-agnostic: pass the router's current URL and its
|
|
10
|
+
* navigate function (SvelteKit: `() => page.url` and `goto`).
|
|
11
|
+
* - `createLocalListSort(spec)` — lists that load every row: page state.
|
|
12
|
+
*/
|
|
13
|
+
import { isValidListSort, listSortHref, parseListSort, toggleListSort, } from './list-sort.js';
|
|
14
|
+
export function createUrlListSort(spec, options) {
|
|
15
|
+
const sort = $derived(parseListSort(options.url().searchParams, spec));
|
|
16
|
+
return {
|
|
17
|
+
get sort() {
|
|
18
|
+
return sort;
|
|
19
|
+
},
|
|
20
|
+
spec,
|
|
21
|
+
href: (columnId) => listSortHref(options.url(), toggleListSort(sort, columnId, spec), spec),
|
|
22
|
+
toggle: () => { },
|
|
23
|
+
set: (next) => {
|
|
24
|
+
if (!isValidListSort(spec, next))
|
|
25
|
+
return;
|
|
26
|
+
return options.navigate(listSortHref(options.url(), next, spec));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function createLocalListSort(spec, initial) {
|
|
31
|
+
let sort = $state(isValidListSort(spec, initial) ? { ...initial } : { ...spec.default });
|
|
32
|
+
return {
|
|
33
|
+
get sort() {
|
|
34
|
+
return sort;
|
|
35
|
+
},
|
|
36
|
+
spec,
|
|
37
|
+
href: () => undefined,
|
|
38
|
+
toggle: (columnId) => {
|
|
39
|
+
sort = toggleListSort(sort, columnId, spec);
|
|
40
|
+
},
|
|
41
|
+
set: (next) => {
|
|
42
|
+
if (!isValidListSort(spec, next))
|
|
43
|
+
return;
|
|
44
|
+
sort = { columnId: next.columnId, direction: next.direction };
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List sorting — one contract for every list, client or server paginated.
|
|
3
|
+
*
|
|
4
|
+
* - A list declares its sortable columns and a default sort (usually its main
|
|
5
|
+
* date column, newest first).
|
|
6
|
+
* - Clicking a header toggles that column between ascending and descending;
|
|
7
|
+
* a column not yet sorted starts at its `firstDirection` (dates and numbers
|
|
8
|
+
* usually `desc`, text `asc`). Sorting never "clears" back to unsorted: the
|
|
9
|
+
* list always has one well-defined order.
|
|
10
|
+
* - Server-paginated lists keep the sort in the URL (`?sort=<column>&dir=<asc|desc>`)
|
|
11
|
+
* so the database orders the WHOLE result set, not just the current page.
|
|
12
|
+
* The default sort is omitted from the URL, and changing the sort drops the
|
|
13
|
+
* page parameter (back to page 1).
|
|
14
|
+
* - Values are validated against the declared columns, so a URL or an agent
|
|
15
|
+
* command can only pick a declared column and a known direction.
|
|
16
|
+
*
|
|
17
|
+
* Pure functions, no Svelte or DOM: usable in `+page.server.ts` loads,
|
|
18
|
+
* components, and data-surface handlers alike.
|
|
19
|
+
*/
|
|
20
|
+
export type ListSortDirection = 'asc' | 'desc';
|
|
21
|
+
export interface ListSort {
|
|
22
|
+
columnId: string;
|
|
23
|
+
direction: ListSortDirection;
|
|
24
|
+
}
|
|
25
|
+
export interface ListSortColumnSpec {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Direction on the first click of this column (default `asc`; use `desc` for dates and counts). */
|
|
28
|
+
firstDirection?: ListSortDirection;
|
|
29
|
+
}
|
|
30
|
+
/** A column offered by `ListSortSelect` (the phone / card-list sort picker). */
|
|
31
|
+
export interface ListSortSelectColumn {
|
|
32
|
+
id: string;
|
|
33
|
+
label: string;
|
|
34
|
+
/** Picks the plain words: dates "newest first", numbers "highest first", text "A–Z". */
|
|
35
|
+
kind?: 'date' | 'number' | 'text';
|
|
36
|
+
}
|
|
37
|
+
export interface ListSortSpec {
|
|
38
|
+
/** Sortable columns. Ids are what the URL and agents use. */
|
|
39
|
+
columns: readonly (ListSortColumnSpec | string)[];
|
|
40
|
+
/** The list's order with no `sort` parameter. Must name a declared column. */
|
|
41
|
+
default: ListSort;
|
|
42
|
+
/** Query parameter names (default `sort` and `dir`). */
|
|
43
|
+
sortParam?: string;
|
|
44
|
+
dirParam?: string;
|
|
45
|
+
/** Page parameter to drop when the sort changes (default `page`; `null` keeps it). */
|
|
46
|
+
pageParam?: string | null;
|
|
47
|
+
}
|
|
48
|
+
/** The ids of the sortable columns. */
|
|
49
|
+
export declare function listSortColumnIds(spec: ListSortSpec): string[];
|
|
50
|
+
/** Whether `sort` names a declared column and a known direction. */
|
|
51
|
+
export declare function isValidListSort(spec: ListSortSpec, sort: Partial<ListSort> | null | undefined): sort is ListSort;
|
|
52
|
+
/**
|
|
53
|
+
* Read the sort from URL parameters. An unknown column falls back to the
|
|
54
|
+
* default; a missing or unknown direction falls back to the column's first
|
|
55
|
+
* direction (or the default's direction for the default column).
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseListSort(params: URLSearchParams | {
|
|
58
|
+
get(name: string): string | null;
|
|
59
|
+
}, spec: ListSortSpec): ListSort;
|
|
60
|
+
/** The sort after activating a column's header (toggle; never cleared). */
|
|
61
|
+
export declare function toggleListSort(current: ListSort, columnId: string, spec: ListSortSpec): ListSort;
|
|
62
|
+
/**
|
|
63
|
+
* URL parameters for `sort`: sets (or, for the default, removes) the sort
|
|
64
|
+
* parameters and drops the page parameter when the sort changed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function listSortSearchParams(params: URLSearchParams | string, sort: ListSort, spec: ListSortSpec): URLSearchParams;
|
|
67
|
+
/** `?query` string (with the leading `?`, or `''`) for a sort link. */
|
|
68
|
+
export declare function listSortHref(url: URL | {
|
|
69
|
+
pathname: string;
|
|
70
|
+
search: string;
|
|
71
|
+
}, sort: ListSort, spec: ListSortSpec): string;
|
|
72
|
+
/** `aria-sort` for a header cell. */
|
|
73
|
+
export declare function listSortAria(current: ListSort | null | undefined, columnId: string): 'ascending' | 'descending' | 'none';
|
|
74
|
+
/** A plain-language label for the header's action, e.g. "Sort by Date, newest first". */
|
|
75
|
+
export declare function listSortActionLabel(current: ListSort | null | undefined, columnId: string, label: string, spec?: ListSortSpec): string;
|
|
76
|
+
export type ListSortValue = string | number | boolean | Date | null | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Compare two values for `direction`. Empty values (null, undefined, '',
|
|
79
|
+
* invalid dates) always sort last. Strings compare with `localeCompare`
|
|
80
|
+
* (numeric-aware, case-insensitive).
|
|
81
|
+
*/
|
|
82
|
+
export declare function compareListValues(left: ListSortValue, right: ListSortValue, direction: ListSortDirection): number;
|
|
83
|
+
/** A sorted copy of `rows` (stable; empties last). */
|
|
84
|
+
export declare function sortListRows<T>(rows: readonly T[], sort: ListSort | null | undefined, getValue: (row: T, columnId: string) => ListSortValue): T[];
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a sort to an allow-listed ORDER BY term for a server query. `map`
|
|
87
|
+
* names the SQL expression (or ORM field) for each sortable column; the
|
|
88
|
+
* result is only ever built from `map` values, never from the input.
|
|
89
|
+
* Returns e.g. `COALESCE(c.publish_date, c.created_at) DESC NULLS LAST`.
|
|
90
|
+
*/
|
|
91
|
+
export declare function listSortOrderBy(sort: ListSort, map: Readonly<Record<string, string>>, options?: {
|
|
92
|
+
nulls?: 'last' | 'first' | false;
|
|
93
|
+
}): string;
|
|
94
|
+
//# sourceMappingURL=list-sort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-sort.d.ts","sourceRoot":"","sources":["../../../src/components/data/list-sort.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/C,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,oGAAoG;IACpG,cAAc,CAAC,EAAE,iBAAiB,CAAC;CACpC;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,OAAO,EAAE,SAAS,CAAC,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAC;IAClD,8EAA8E;IAC9E,OAAO,EAAE,QAAQ,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAmBD,uCAAuC;AACvC,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAE9D;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAC7B,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,GAAG,SAAS,GACzC,IAAI,IAAI,QAAQ,CAOlB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,GAAG;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,EAC9D,IAAI,EAAE,YAAY,GACjB,QAAQ,CAiBV;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAC5B,OAAO,EAAE,QAAQ,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GACjB,QAAQ,CAUV;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,GAAG,MAAM,EAChC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,YAAY,GACjB,eAAe,CAwBjB;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAC1B,GAAG,EAAE,GAAG,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/C,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,YAAY,GACjB,MAAM,CAGR;AAED,qCAAqC;AACrC,wBAAgB,YAAY,CAC1B,OAAO,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,GACf,WAAW,GAAG,YAAY,GAAG,MAAM,CAGrC;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,YAAY,GAClB,MAAM,CAOR;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;AAahF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,aAAa,EACpB,SAAS,EAAE,iBAAiB,GAC3B,MAAM,CAcR;AAED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACjC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,aAAa,GACpD,CAAC,EAAE,CAaL;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACrC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAA;CAAO,GACjD,MAAM,CAWR"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List sorting — one contract for every list, client or server paginated.
|
|
3
|
+
*
|
|
4
|
+
* - A list declares its sortable columns and a default sort (usually its main
|
|
5
|
+
* date column, newest first).
|
|
6
|
+
* - Clicking a header toggles that column between ascending and descending;
|
|
7
|
+
* a column not yet sorted starts at its `firstDirection` (dates and numbers
|
|
8
|
+
* usually `desc`, text `asc`). Sorting never "clears" back to unsorted: the
|
|
9
|
+
* list always has one well-defined order.
|
|
10
|
+
* - Server-paginated lists keep the sort in the URL (`?sort=<column>&dir=<asc|desc>`)
|
|
11
|
+
* so the database orders the WHOLE result set, not just the current page.
|
|
12
|
+
* The default sort is omitted from the URL, and changing the sort drops the
|
|
13
|
+
* page parameter (back to page 1).
|
|
14
|
+
* - Values are validated against the declared columns, so a URL or an agent
|
|
15
|
+
* command can only pick a declared column and a known direction.
|
|
16
|
+
*
|
|
17
|
+
* Pure functions, no Svelte or DOM: usable in `+page.server.ts` loads,
|
|
18
|
+
* components, and data-surface handlers alike.
|
|
19
|
+
*/
|
|
20
|
+
function columnSpecs(spec) {
|
|
21
|
+
return spec.columns.map((column) => typeof column === 'string' ? { id: column } : column);
|
|
22
|
+
}
|
|
23
|
+
function columnSpec(spec, columnId) {
|
|
24
|
+
return columnSpecs(spec).find((column) => column.id === columnId);
|
|
25
|
+
}
|
|
26
|
+
function isDirection(value) {
|
|
27
|
+
return value === 'asc' || value === 'desc';
|
|
28
|
+
}
|
|
29
|
+
/** The ids of the sortable columns. */
|
|
30
|
+
export function listSortColumnIds(spec) {
|
|
31
|
+
return columnSpecs(spec).map((column) => column.id);
|
|
32
|
+
}
|
|
33
|
+
/** Whether `sort` names a declared column and a known direction. */
|
|
34
|
+
export function isValidListSort(spec, sort) {
|
|
35
|
+
return (!!sort &&
|
|
36
|
+
typeof sort.columnId === 'string' &&
|
|
37
|
+
columnSpec(spec, sort.columnId) !== undefined &&
|
|
38
|
+
isDirection(sort.direction));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the sort from URL parameters. An unknown column falls back to the
|
|
42
|
+
* default; a missing or unknown direction falls back to the column's first
|
|
43
|
+
* direction (or the default's direction for the default column).
|
|
44
|
+
*/
|
|
45
|
+
export function parseListSort(params, spec) {
|
|
46
|
+
const columnId = params.get(spec.sortParam ?? 'sort');
|
|
47
|
+
const direction = params.get(spec.dirParam ?? 'dir');
|
|
48
|
+
const column = columnId ? columnSpec(spec, columnId) : undefined;
|
|
49
|
+
if (!column) {
|
|
50
|
+
return isDirection(direction) && !columnId
|
|
51
|
+
? { columnId: spec.default.columnId, direction }
|
|
52
|
+
: { ...spec.default };
|
|
53
|
+
}
|
|
54
|
+
if (isDirection(direction))
|
|
55
|
+
return { columnId: column.id, direction };
|
|
56
|
+
return {
|
|
57
|
+
columnId: column.id,
|
|
58
|
+
direction: column.id === spec.default.columnId
|
|
59
|
+
? spec.default.direction
|
|
60
|
+
: (column.firstDirection ?? 'asc'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The sort after activating a column's header (toggle; never cleared). */
|
|
64
|
+
export function toggleListSort(current, columnId, spec) {
|
|
65
|
+
const column = columnSpec(spec, columnId);
|
|
66
|
+
if (!column)
|
|
67
|
+
return current;
|
|
68
|
+
if (current.columnId === columnId) {
|
|
69
|
+
return {
|
|
70
|
+
columnId,
|
|
71
|
+
direction: current.direction === 'asc' ? 'desc' : 'asc',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { columnId, direction: column.firstDirection ?? 'asc' };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* URL parameters for `sort`: sets (or, for the default, removes) the sort
|
|
78
|
+
* parameters and drops the page parameter when the sort changed.
|
|
79
|
+
*/
|
|
80
|
+
export function listSortSearchParams(params, sort, spec) {
|
|
81
|
+
const next = new URLSearchParams(params);
|
|
82
|
+
const sortParam = spec.sortParam ?? 'sort';
|
|
83
|
+
const dirParam = spec.dirParam ?? 'dir';
|
|
84
|
+
const previous = parseListSort(next, spec);
|
|
85
|
+
const isDefault = sort.columnId === spec.default.columnId &&
|
|
86
|
+
sort.direction === spec.default.direction;
|
|
87
|
+
if (isDefault) {
|
|
88
|
+
next.delete(sortParam);
|
|
89
|
+
next.delete(dirParam);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
next.set(sortParam, sort.columnId);
|
|
93
|
+
next.set(dirParam, sort.direction);
|
|
94
|
+
}
|
|
95
|
+
const pageParam = spec.pageParam === undefined ? 'page' : spec.pageParam;
|
|
96
|
+
if (pageParam &&
|
|
97
|
+
(previous.columnId !== sort.columnId ||
|
|
98
|
+
previous.direction !== sort.direction)) {
|
|
99
|
+
next.delete(pageParam);
|
|
100
|
+
}
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
/** `?query` string (with the leading `?`, or `''`) for a sort link. */
|
|
104
|
+
export function listSortHref(url, sort, spec) {
|
|
105
|
+
const query = listSortSearchParams(url.search, sort, spec).toString();
|
|
106
|
+
return `${url.pathname}${query ? `?${query}` : ''}`;
|
|
107
|
+
}
|
|
108
|
+
/** `aria-sort` for a header cell. */
|
|
109
|
+
export function listSortAria(current, columnId) {
|
|
110
|
+
if (!current || current.columnId !== columnId)
|
|
111
|
+
return 'none';
|
|
112
|
+
return current.direction === 'asc' ? 'ascending' : 'descending';
|
|
113
|
+
}
|
|
114
|
+
/** A plain-language label for the header's action, e.g. "Sort by Date, newest first". */
|
|
115
|
+
export function listSortActionLabel(current, columnId, label, spec) {
|
|
116
|
+
const next = spec
|
|
117
|
+
? toggleListSort(current ?? spec.default, columnId, spec).direction
|
|
118
|
+
: current?.columnId === columnId && current.direction === 'asc'
|
|
119
|
+
? 'desc'
|
|
120
|
+
: 'asc';
|
|
121
|
+
return `Sort by ${label}, ${next === 'asc' ? 'ascending' : 'descending'}`;
|
|
122
|
+
}
|
|
123
|
+
function comparable(value) {
|
|
124
|
+
if (value === null || value === undefined || value === '')
|
|
125
|
+
return null;
|
|
126
|
+
if (value instanceof Date) {
|
|
127
|
+
const time = value.getTime();
|
|
128
|
+
return Number.isNaN(time) ? null : time;
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === 'boolean')
|
|
131
|
+
return value ? 1 : 0;
|
|
132
|
+
if (typeof value === 'number')
|
|
133
|
+
return Number.isFinite(value) ? value : null;
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Compare two values for `direction`. Empty values (null, undefined, '',
|
|
138
|
+
* invalid dates) always sort last. Strings compare with `localeCompare`
|
|
139
|
+
* (numeric-aware, case-insensitive).
|
|
140
|
+
*/
|
|
141
|
+
export function compareListValues(left, right, direction) {
|
|
142
|
+
const a = comparable(left);
|
|
143
|
+
const b = comparable(right);
|
|
144
|
+
if (a === null && b === null)
|
|
145
|
+
return 0;
|
|
146
|
+
if (a === null)
|
|
147
|
+
return 1;
|
|
148
|
+
if (b === null)
|
|
149
|
+
return -1;
|
|
150
|
+
const sign = direction === 'asc' ? 1 : -1;
|
|
151
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
152
|
+
return (a - b) * sign;
|
|
153
|
+
return (String(a).localeCompare(String(b), undefined, {
|
|
154
|
+
numeric: true,
|
|
155
|
+
sensitivity: 'base',
|
|
156
|
+
}) * sign);
|
|
157
|
+
}
|
|
158
|
+
/** A sorted copy of `rows` (stable; empties last). */
|
|
159
|
+
export function sortListRows(rows, sort, getValue) {
|
|
160
|
+
if (!sort)
|
|
161
|
+
return [...rows];
|
|
162
|
+
return rows
|
|
163
|
+
.map((row, index) => ({ row, index }))
|
|
164
|
+
.sort((left, right) => compareListValues(getValue(left.row, sort.columnId), getValue(right.row, sort.columnId), sort.direction) || left.index - right.index)
|
|
165
|
+
.map((entry) => entry.row);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a sort to an allow-listed ORDER BY term for a server query. `map`
|
|
169
|
+
* names the SQL expression (or ORM field) for each sortable column; the
|
|
170
|
+
* result is only ever built from `map` values, never from the input.
|
|
171
|
+
* Returns e.g. `COALESCE(c.publish_date, c.created_at) DESC NULLS LAST`.
|
|
172
|
+
*/
|
|
173
|
+
export function listSortOrderBy(sort, map, options = {}) {
|
|
174
|
+
const expression = Object.hasOwn(map, sort.columnId)
|
|
175
|
+
? map[sort.columnId]
|
|
176
|
+
: undefined;
|
|
177
|
+
if (!expression) {
|
|
178
|
+
throw new Error(`Unknown sort column: ${sort.columnId}`);
|
|
179
|
+
}
|
|
180
|
+
const nulls = options.nulls === undefined ? 'last' : options.nulls;
|
|
181
|
+
return `${expression} ${sort.direction === 'asc' ? 'ASC' : 'DESC'}${nulls ? ` NULLS ${nulls === 'last' ? 'LAST' : 'FIRST'}` : ''}`;
|
|
182
|
+
}
|