@eifi1/ui-kit 0.4.0 → 0.5.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/LICENSE +32 -0
- package/README.md +498 -57
- package/dist/chart.d.ts +10 -0
- package/dist/chart.js +10 -0
- package/dist/chart.js.map +1 -0
- package/dist/components/account-settings.d.ts +11 -0
- package/dist/components/account-settings.js +19 -12
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/alert-banner.d.ts +10 -6
- package/dist/components/alert-banner.js +20 -16
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +30 -4
- package/dist/components/amount-input.js +36 -14
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +24 -7
- package/dist/components/calculator.js +37 -20
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +156 -0
- package/dist/components/chart-zoom.js +262 -0
- package/dist/components/chart-zoom.js.map +1 -0
- package/dist/components/chart.d.ts +28 -3
- package/dist/components/chart.js +71 -32
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.d.ts +51 -0
- package/dist/components/checkbox.js +123 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +128 -0
- package/dist/components/chip.js +357 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/choice-parts.d.ts +24 -0
- package/dist/components/choice-parts.js +18 -0
- package/dist/components/choice-parts.js.map +1 -0
- package/dist/components/combobox-core.d.ts +52 -9
- package/dist/components/combobox-core.js +91 -20
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +43 -33
- package/dist/components/combobox.js +409 -340
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/currency-select.d.ts +48 -8
- package/dist/components/currency-select.js +107 -65
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +15 -3
- package/dist/components/data-table-filter-popover.js +23 -17
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-filters.js +3 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +53 -2
- package/dist/components/data-table-labels.js +31 -2
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table-pagination.js +16 -10
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.js +1 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +201 -85
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +25 -9
- package/dist/components/date-picker.js +232 -94
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/dropdown.d.ts +96 -20
- package/dist/components/dropdown.js +77 -33
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +5 -4
- package/dist/components/entity-combobox.js +107 -76
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +36 -0
- package/dist/components/facing-pair.js +40 -0
- package/dist/components/facing-pair.js.map +1 -0
- package/dist/components/field-sync.d.ts +164 -0
- package/dist/components/field-sync.js +264 -0
- package/dist/components/field-sync.js.map +1 -0
- package/dist/components/file-dropzone.d.ts +38 -6
- package/dist/components/file-dropzone.js +34 -9
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/full-bleed-dialog.d.ts +37 -26
- package/dist/components/full-bleed-dialog.js +20 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/grouped-picker.d.ts +21 -10
- package/dist/components/grouped-picker.js +66 -52
- package/dist/components/grouped-picker.js.map +1 -1
- package/dist/components/hover-menu.d.ts +20 -4
- package/dist/components/hover-menu.js +15 -4
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +102 -14
- package/dist/components/mini-calendar.js +256 -70
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +9 -3
- package/dist/components/modal.js +34 -45
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +79 -0
- package/dist/components/month-picker.js +342 -0
- package/dist/components/month-picker.js.map +1 -0
- package/dist/components/multi-entity-combobox.d.ts +7 -6
- package/dist/components/multi-entity-combobox.js +112 -72
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.d.ts +13 -11
- package/dist/components/multi-select.js +161 -84
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +92 -0
- package/dist/components/number-field.js +111 -0
- package/dist/components/number-field.js.map +1 -0
- package/dist/components/number-input.d.ts +28 -2
- package/dist/components/number-input.js +12 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +32 -4
- package/dist/components/numpad-sheet.js +30 -13
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-contents.d.ts +90 -0
- package/dist/components/page-contents.js +170 -0
- package/dist/components/page-contents.js.map +1 -0
- package/dist/components/password-strength.d.ts +106 -0
- package/dist/components/password-strength.js +132 -0
- package/dist/components/password-strength.js.map +1 -0
- package/dist/components/picker-sheet.d.ts +23 -15
- package/dist/components/picker-sheet.js +34 -14
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/popover.d.ts +62 -4
- package/dist/components/popover.js +52 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/search-field.d.ts +25 -7
- package/dist/components/search-field.js +7 -5
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +14 -0
- package/dist/components/series-chart-labels.js +11 -0
- package/dist/components/series-chart-labels.js.map +1 -0
- package/dist/components/series-chart.d.ts +207 -0
- package/dist/components/series-chart.js +223 -0
- package/dist/components/series-chart.js.map +1 -0
- package/dist/components/settings-fields.d.ts +14 -4
- package/dist/components/settings-fields.js +19 -8
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/signature-pad.d.ts +119 -0
- package/dist/components/signature-pad.js +405 -0
- package/dist/components/signature-pad.js.map +1 -0
- package/dist/components/slider.d.ts +80 -0
- package/dist/components/slider.js +155 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sparkline.d.ts +93 -0
- package/dist/components/sparkline.js +205 -0
- package/dist/components/sparkline.js.map +1 -0
- package/dist/components/stat-tile.d.ts +151 -0
- package/dist/components/stat-tile.js +236 -0
- package/dist/components/stat-tile.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +25 -2
- package/dist/components/swipeable-row.js +31 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/switch.d.ts +60 -0
- package/dist/components/switch.js +81 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/time-input.d.ts +58 -0
- package/dist/components/time-input.js +50 -0
- package/dist/components/time-input.js.map +1 -0
- package/dist/components/toggle-group.d.ts +18 -3
- package/dist/components/toggle-group.js +10 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +115 -0
- package/dist/components/toggle-legend.js +114 -0
- package/dist/components/toggle-legend.js.map +1 -0
- package/dist/components/tooltip.d.ts +40 -11
- package/dist/components/tooltip.js +77 -20
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/treemap.d.ts +132 -0
- package/dist/components/treemap.js +179 -0
- package/dist/components/treemap.js.map +1 -0
- package/dist/components/ui.d.ts +159 -46
- package/dist/components/ui.js +220 -115
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-mobile-reveal.js +1 -0
- package/dist/components/use-mobile-reveal.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/components/use-table-state.js +38 -17
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +15 -4
- package/dist/components/user-avatar.js +5 -4
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.js +3 -2
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
- package/dist/data-table.d.ts +7 -0
- package/dist/data-table.js +12 -0
- package/dist/data-table.js.map +1 -0
- package/dist/feedback/feedback-attachment.js +12 -11
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.js +2 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +33 -7
- package/dist/feedback/feedback-inbox.js +49 -37
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback.d.ts +5 -0
- package/dist/feedback.js +5 -0
- package/dist/feedback.js.map +1 -0
- package/dist/hooks/use-anchored-panel.js +1 -0
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-anchored-rect.js +1 -0
- package/dist/hooks/use-anchored-rect.js.map +1 -1
- package/dist/hooks/use-announce.d.ts +46 -0
- package/dist/hooks/use-announce.js +37 -0
- package/dist/hooks/use-announce.js.map +1 -0
- package/dist/hooks/use-body-scroll-lock.js +1 -0
- package/dist/hooks/use-body-scroll-lock.js.map +1 -1
- package/dist/hooks/use-close-transition.js +1 -0
- package/dist/hooks/use-close-transition.js.map +1 -1
- package/dist/hooks/use-dismiss.js +1 -0
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +16 -0
- package/dist/hooks/use-focus-trap.js +67 -0
- package/dist/hooks/use-focus-trap.js.map +1 -0
- package/dist/hooks/use-media-query.js +1 -0
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-overlay-history.js +27 -1
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/hooks/use-row-swipe.js +1 -0
- package/dist/hooks/use-row-swipe.js.map +1 -1
- package/dist/i18n/defaults.d.ts +31 -0
- package/dist/i18n/defaults.js +63 -0
- package/dist/i18n/defaults.js.map +1 -0
- package/dist/i18n/kit-labels.d.ts +250 -0
- package/dist/i18n/kit-labels.js +184 -0
- package/dist/i18n/kit-labels.js.map +1 -0
- package/dist/index.d.ts +47 -22
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calc.js +1 -0
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/cn.js +1 -0
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/dates.js +1 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/safe-storage.js +1 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/search/command-palette.d.ts +11 -1
- package/dist/search/command-palette.js +21 -15
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search.d.ts +2 -0
- package/dist/search.js +3 -0
- package/dist/search.js.map +1 -0
- package/dist/shell/app-shell.d.ts +33 -4
- package/dist/shell/app-shell.js +275 -78
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.js +3 -2
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +6 -0
- package/dist/shell/role-switcher.js +7 -4
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar.d.ts +15 -7
- package/dist/shell/top-bar.js +3 -5
- package/dist/shell/top-bar.js.map +1 -1
- package/dist/shell/topbar-action-menu.js +3 -2
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +5 -2
- package/dist/shell/topbar-controls.js +20 -15
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/shell.d.ts +13 -0
- package/dist/shell.js +8 -0
- package/dist/shell.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +19 -12
- package/dist/theme/chart-palette.js +24 -30
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/color.d.ts +78 -0
- package/dist/theme/color.js +148 -0
- package/dist/theme/color.js.map +1 -0
- package/dist/theme/palette-derive.d.ts +140 -0
- package/dist/theme/palette-derive.js +376 -0
- package/dist/theme/palette-derive.js.map +1 -0
- package/dist/theme/palette-presets.d.ts +27 -5
- package/dist/theme/palette-presets.js +231 -33
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +1 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour.d.ts +2 -1
- package/dist/tour/tour.js +15 -9
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -0
- package/dist/wizard/stepper-nav.js +10 -8
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +18 -2
- package/dist/wizard/types.js +4 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +12 -4
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.js +1 -0
- package/dist/wizard/validation.js.map +1 -1
- package/dist/wizard/wizard-context.js +1 -0
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.js +1 -0
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard/wizard-summary.js +5 -3
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +8 -0
- package/dist/wizard.js +9 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +67 -14
- package/src/chart.ts +22 -0
- package/src/components/account-settings.tsx +275 -0
- package/src/components/alert-banner.tsx +75 -0
- package/src/components/amount-input.tsx +471 -0
- package/src/components/calculator.tsx +252 -0
- package/src/components/chart-zoom.tsx +499 -0
- package/src/components/chart.tsx +363 -0
- package/src/components/checkbox.tsx +222 -0
- package/src/components/chip.tsx +570 -0
- package/src/components/choice-parts.ts +33 -0
- package/src/components/combobox-core.tsx +540 -0
- package/src/components/combobox.tsx +1026 -0
- package/src/components/currency-select.tsx +269 -0
- package/src/components/data-table-filter-popover.tsx +252 -0
- package/src/components/data-table-filters.ts +176 -0
- package/src/components/data-table-labels.ts +190 -0
- package/src/components/data-table-pagination.tsx +126 -0
- package/src/components/data-table-sort.ts +70 -0
- package/src/components/data-table.tsx +1740 -0
- package/src/components/date-picker.tsx +631 -0
- package/src/components/dropdown.tsx +341 -0
- package/src/components/entity-combobox.tsx +215 -0
- package/src/components/facing-pair.ts +86 -0
- package/src/components/field-sync.tsx +472 -0
- package/src/components/file-dropzone.tsx +181 -0
- package/src/components/full-bleed-dialog.tsx +179 -0
- package/src/components/grouped-picker.tsx +157 -0
- package/src/components/hover-menu.tsx +183 -0
- package/src/components/mini-calendar.tsx +554 -0
- package/src/components/modal.tsx +287 -0
- package/src/components/month-picker.tsx +549 -0
- package/src/components/multi-entity-combobox.tsx +226 -0
- package/src/components/multi-select.tsx +303 -0
- package/src/components/number-field.tsx +225 -0
- package/src/components/number-input.tsx +211 -0
- package/src/components/numpad-sheet.tsx +264 -0
- package/src/components/page-contents.tsx +289 -0
- package/src/components/password-strength.tsx +258 -0
- package/src/components/picker-sheet.tsx +251 -0
- package/src/components/popover.tsx +195 -0
- package/src/components/search-field.tsx +146 -0
- package/src/components/series-chart-labels.ts +26 -0
- package/src/components/series-chart.tsx +481 -0
- package/src/components/settings-fields.tsx +68 -0
- package/src/components/signature-pad.tsx +630 -0
- package/src/components/slider.tsx +313 -0
- package/src/components/sparkline.tsx +329 -0
- package/src/components/stat-tile.tsx +448 -0
- package/src/components/swipeable-row.tsx +271 -0
- package/src/components/switch.tsx +170 -0
- package/src/components/time-input.tsx +107 -0
- package/src/components/toggle-group.tsx +153 -0
- package/src/components/toggle-legend.tsx +229 -0
- package/src/components/tooltip.tsx +477 -0
- package/src/components/treemap.tsx +390 -0
- package/src/components/ui.tsx +1284 -0
- package/src/components/use-mobile-reveal.ts +54 -0
- package/src/components/use-table-state.ts +295 -0
- package/src/components/user-avatar.tsx +61 -0
- package/src/components/wizard-stepper.tsx +37 -0
- package/src/data-table.ts +19 -0
- package/src/feedback/feedback-attachment.tsx +246 -0
- package/src/feedback/feedback-dialog.tsx +209 -0
- package/src/feedback/feedback-inbox.tsx +615 -0
- package/src/feedback.ts +8 -0
- package/src/hooks/use-anchored-panel.ts +169 -0
- package/src/hooks/use-anchored-rect.ts +78 -0
- package/src/hooks/use-announce.ts +85 -0
- package/src/hooks/use-body-scroll-lock.ts +94 -0
- package/src/hooks/use-close-transition.ts +99 -0
- package/src/hooks/use-dismiss.ts +77 -0
- package/src/hooks/use-focus-trap.ts +160 -0
- package/src/hooks/use-media-query.ts +23 -0
- package/src/hooks/use-overlay-history.ts +345 -0
- package/src/hooks/use-row-swipe.ts +212 -0
- package/src/i18n/defaults.ts +68 -0
- package/src/i18n/kit-labels.tsx +447 -0
- package/src/index.ts +169 -0
- package/src/lib/calc.ts +309 -0
- package/src/lib/cn.ts +5 -0
- package/src/lib/dates.ts +187 -0
- package/src/lib/logger.ts +73 -0
- package/src/lib/safe-storage.ts +37 -0
- package/src/search/command-palette.tsx +342 -0
- package/src/search.ts +3 -0
- package/src/shell/app-shell.tsx +772 -0
- package/src/shell/option-switcher-menu.tsx +72 -0
- package/src/shell/role-switcher.tsx +46 -0
- package/src/shell/top-bar.tsx +39 -0
- package/src/shell/topbar-action-menu.tsx +91 -0
- package/src/shell/topbar-controls.tsx +229 -0
- package/src/shell.ts +10 -0
- package/src/theme/chart-palette.ts +145 -0
- package/src/theme/color.ts +266 -0
- package/src/theme/palette-derive.ts +681 -0
- package/src/theme/palette-presets.ts +760 -0
- package/src/theme/palette-store.ts +94 -0
- package/src/theme/theme-store.ts +145 -0
- package/src/tour/tour.tsx +586 -0
- package/src/tour.ts +3 -0
- package/src/vite-env.d.ts +26 -0
- package/src/wizard/stepper-nav.tsx +221 -0
- package/src/wizard/types.ts +178 -0
- package/src/wizard/use-wizard.ts +345 -0
- package/src/wizard/validation.ts +37 -0
- package/src/wizard/wizard-context.tsx +29 -0
- package/src/wizard/wizard-step.tsx +14 -0
- package/src/wizard/wizard-summary.tsx +69 -0
- package/src/wizard.ts +14 -0
- package/tokens.css +222 -20
package/dist/lib/dates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/dates.ts"],"sourcesContent":["/**\n * ISO date helpers, collected from copies that were scattered across the pages.\n *\n * **Every \"what day is it\" helper here reads the LOCAL calendar** — one family, not\n * two. It used to be two: these all built a local Date and then read it back through\n * `toISOString()`, which is UTC, so east of Greenwich they all returned YESTERDAY\n * between local midnight and the UTC rollover. The old module note called that\n * deliberate and warned against changing it. It was not defensible, and dev#471 is\n * what it cost:\n *\n * > *\"Just entered a tx now from scheduled. But it still shows as upcoming. Is there\n * > a problem with timezones? It is currently 1 o clock middle european summer time.\"*\n *\n * There was: at 01:00 CEST `todayIso()` answered with the 14th, the row the scheduler\n * had just booked was dated the 15th, and `isUpcoming` compares the two strings — so a\n * transaction entered a second ago read as one still to come. The same slip moved\n * `startOfMonthIso()` onto the last day of the PREVIOUS month for an hour every first\n * of the month, which is a report range nobody would have questioned.\n *\n * A calendar day only means anything to the human reading it, and every caller here —\n * the register's today, the date-range presets, the budget cursor, a new\n * transaction's default date — is asking that question. There is no caller for whom\n * \"the UTC day\" is the right answer; the server compares plain dates with no zone at\n * all, so sending it the user's local day is also what it wants.\n */\n\n/** Zero-pad a number to two digits (\"3\" -> \"03\"). */\nexport function pad(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n/** Today as \"YYYY-MM-DD\" on the LOCAL calendar (see the module note). */\nexport function todayIso(): string {\n return toLocalIso(new Date());\n}\n\n/** A Date as a local-time \"YYYY-MM-DD\" string. */\nexport function toLocalIso(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/** A Date as a local-time \"YYYY-MM\" month key. */\nexport function monthKey(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;\n}\n\n// ---------- Date-range presets + calendar helpers ----------\n// Local-calendar like todayIso (see the module note); used by the data-table date\n// filter and its MiniCalendar. Each one mutates a local Date and must therefore READ\n// it back locally — going out through toISOString() is what shifted every one of\n// these by a day near midnight (dev#471).\n\n/** Today shifted by `days`, as \"YYYY-MM-DD\". */\nexport function shiftIso(days: number): string {\n const d = new Date();\n d.setDate(d.getDate() + days);\n return toLocalIso(d);\n}\n\n/** First day (Monday) of the current week, as \"YYYY-MM-DD\". */\nexport function startOfWeekIso(): string {\n const d = new Date();\n const day = d.getDay();\n const diff = (day + 6) % 7; // monday = 0\n d.setDate(d.getDate() - diff);\n return toLocalIso(d);\n}\n\n/** First day of the month `offsetMonths` from now, as \"YYYY-MM-DD\". */\nexport function startOfMonthIso(offsetMonths = 0): string {\n const d = new Date();\n d.setDate(1);\n d.setMonth(d.getMonth() + offsetMonths);\n return toLocalIso(d);\n}\n\n/** Last day of the month `offsetMonths` from now, as \"YYYY-MM-DD\". */\nexport function endOfMonthIso(offsetMonths = 0): string {\n const d = new Date();\n d.setDate(1);\n d.setMonth(d.getMonth() + offsetMonths + 1);\n d.setDate(0);\n return toLocalIso(d);\n}\n\n/** First day of the year `offsetYears` from now, as \"YYYY-MM-DD\". */\nexport function startOfYearIso(offsetYears = 0): string {\n const d = new Date();\n d.setMonth(0);\n d.setDate(1);\n d.setFullYear(d.getFullYear() + offsetYears);\n return toLocalIso(d);\n}\n\n/** Last day of the year `offsetYears` from now, as \"YYYY-MM-DD\". */\nexport function endOfYearIso(offsetYears = 0): string {\n const d = new Date();\n d.setMonth(11);\n d.setDate(31);\n d.setFullYear(d.getFullYear() + offsetYears);\n return toLocalIso(d);\n}\n\n/** Parse a \"YYYY-MM-DD\" string to a local-midnight Date, or null if malformed —\n * where \"malformed\" includes a date that does not exist.\n *\n * The falsy check alone was not enough. `new Date(y, m - 1, d)` ROLLS OVER out-of-range\n * components rather than refusing them, so \"2026-13-45\" came back as 2027-02-14 and\n * \"2026-02-30\" as 2026-03-02: a valid-looking Date for a day that never happened. No\n * caller could tell — {@link formatIsoDate} blanks only on `null`, so the invented\n * date formatted and displayed exactly like a real one.\n *\n * The round-trip is the check: build the Date, then read the components back off it.\n * A value that rolled over cannot answer with the numbers it was given. That is the\n * same shape {@link sameYmd} already expresses, applied to a Date and its source. */\nexport function parseIsoDate(s: string): Date | null {\n if (!s) return null;\n const [y, m, d] = s.split(\"-\").map(Number);\n if (!y || !m || !d) return null;\n if (!Number.isInteger(y) || !Number.isInteger(m) || !Number.isInteger(d)) return null;\n const dt = new Date(y, m - 1, d);\n if (Number.isNaN(dt.getTime())) return null;\n if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) return null;\n return dt;\n}\n\n/** Format a \"YYYY-MM-DD\" string for display in `locale` (empty string when\n * unparseable). Defaults to the locale's short numeric date; pass `options` to\n * override (e.g. `{ dateStyle: \"medium\" }`). */\nexport function formatIsoDate(\n iso: string,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const d = parseIsoDate(iso);\n return d ? d.toLocaleDateString(locale, options) : \"\";\n}\n\n/** A \"YYYY-MM-DD\" string shifted by `days` (negative shifts back), or the input\n * unchanged when it isn't a parseable date.\n *\n * Local-time throughout — {@link parseIsoDate} builds a local-midnight Date and\n * {@link toLocalIso} reads local components back, so this never slides a day the way\n * a `toISOString()` round-trip does west of UTC. */\nexport function addDaysIso(iso: string, days: number): string {\n const d = parseIsoDate(iso);\n if (!d) return iso;\n d.setDate(d.getDate() + days);\n return toLocalIso(d);\n}\n\n/** Whether two Dates fall on the same local calendar day. */\nexport function sameYmd(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nexport interface DateRangePreset {\n key: string;\n from: string;\n to: string;\n}\n\n/**\n * The named ranges offered by the data-table date filter (local-calendar, like\n * {@link todayIso} — see the module note). `key` maps to the consumer's\n * `table.preset_*` labels.\n */\nexport function dateRangePresets(): DateRangePreset[] {\n const mondayOffset = (new Date().getDay() + 6) % 7; // monday = 0\n return [\n { key: \"today\", from: todayIso(), to: todayIso() },\n { key: \"yesterday\", from: shiftIso(-1), to: shiftIso(-1) },\n { key: \"this_week\", from: startOfWeekIso(), to: todayIso() },\n { key: \"last_week\", from: shiftIso(-7 - mondayOffset), to: shiftIso(-1 - mondayOffset) },\n { key: \"last_7_days\", from: shiftIso(-6), to: todayIso() },\n { key: \"last_30_days\", from: shiftIso(-29), to: todayIso() },\n { key: \"this_month\", from: startOfMonthIso(0), to: todayIso() },\n { key: \"last_month\", from: startOfMonthIso(-1), to: endOfMonthIso(-1) },\n { key: \"last_3_months\", from: shiftIso(-90), to: todayIso() },\n { key: \"ytd\", from: startOfYearIso(0), to: todayIso() },\n { key: \"last_year\", from: startOfYearIso(-1), to: endOfYearIso(-1) },\n ];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/dates.ts"],"sourcesContent":["/**\n * ISO date helpers, collected from copies that were scattered across the pages.\n *\n * **Every \"what day is it\" helper here reads the LOCAL calendar** — one family, not\n * two. It used to be two: these all built a local Date and then read it back through\n * `toISOString()`, which is UTC, so east of Greenwich they all returned YESTERDAY\n * between local midnight and the UTC rollover. The old module note called that\n * deliberate and warned against changing it. It was not defensible, and dev#471 is\n * what it cost:\n *\n * > *\"Just entered a tx now from scheduled. But it still shows as upcoming. Is there\n * > a problem with timezones? It is currently 1 o clock middle european summer time.\"*\n *\n * There was: at 01:00 CEST `todayIso()` answered with the 14th, the row the scheduler\n * had just booked was dated the 15th, and `isUpcoming` compares the two strings — so a\n * transaction entered a second ago read as one still to come. The same slip moved\n * `startOfMonthIso()` onto the last day of the PREVIOUS month for an hour every first\n * of the month, which is a report range nobody would have questioned.\n *\n * A calendar day only means anything to the human reading it, and every caller here —\n * the register's today, the date-range presets, the budget cursor, a new\n * transaction's default date — is asking that question. There is no caller for whom\n * \"the UTC day\" is the right answer; the server compares plain dates with no zone at\n * all, so sending it the user's local day is also what it wants.\n */\n\n/** Zero-pad a number to two digits (\"3\" -> \"03\"). */\nexport function pad(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n/** Today as \"YYYY-MM-DD\" on the LOCAL calendar (see the module note). */\nexport function todayIso(): string {\n return toLocalIso(new Date());\n}\n\n/** A Date as a local-time \"YYYY-MM-DD\" string. */\nexport function toLocalIso(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/** A Date as a local-time \"YYYY-MM\" month key. */\nexport function monthKey(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;\n}\n\n// ---------- Date-range presets + calendar helpers ----------\n// Local-calendar like todayIso (see the module note); used by the data-table date\n// filter and its MiniCalendar. Each one mutates a local Date and must therefore READ\n// it back locally — going out through toISOString() is what shifted every one of\n// these by a day near midnight (dev#471).\n\n/** Today shifted by `days`, as \"YYYY-MM-DD\". */\nexport function shiftIso(days: number): string {\n const d = new Date();\n d.setDate(d.getDate() + days);\n return toLocalIso(d);\n}\n\n/** First day (Monday) of the current week, as \"YYYY-MM-DD\". */\nexport function startOfWeekIso(): string {\n const d = new Date();\n const day = d.getDay();\n const diff = (day + 6) % 7; // monday = 0\n d.setDate(d.getDate() - diff);\n return toLocalIso(d);\n}\n\n/** First day of the month `offsetMonths` from now, as \"YYYY-MM-DD\". */\nexport function startOfMonthIso(offsetMonths = 0): string {\n const d = new Date();\n d.setDate(1);\n d.setMonth(d.getMonth() + offsetMonths);\n return toLocalIso(d);\n}\n\n/** Last day of the month `offsetMonths` from now, as \"YYYY-MM-DD\". */\nexport function endOfMonthIso(offsetMonths = 0): string {\n const d = new Date();\n d.setDate(1);\n d.setMonth(d.getMonth() + offsetMonths + 1);\n d.setDate(0);\n return toLocalIso(d);\n}\n\n/** First day of the year `offsetYears` from now, as \"YYYY-MM-DD\". */\nexport function startOfYearIso(offsetYears = 0): string {\n const d = new Date();\n d.setMonth(0);\n d.setDate(1);\n d.setFullYear(d.getFullYear() + offsetYears);\n return toLocalIso(d);\n}\n\n/** Last day of the year `offsetYears` from now, as \"YYYY-MM-DD\". */\nexport function endOfYearIso(offsetYears = 0): string {\n const d = new Date();\n d.setMonth(11);\n d.setDate(31);\n d.setFullYear(d.getFullYear() + offsetYears);\n return toLocalIso(d);\n}\n\n/** Parse a \"YYYY-MM-DD\" string to a local-midnight Date, or null if malformed —\n * where \"malformed\" includes a date that does not exist.\n *\n * The falsy check alone was not enough. `new Date(y, m - 1, d)` ROLLS OVER out-of-range\n * components rather than refusing them, so \"2026-13-45\" came back as 2027-02-14 and\n * \"2026-02-30\" as 2026-03-02: a valid-looking Date for a day that never happened. No\n * caller could tell — {@link formatIsoDate} blanks only on `null`, so the invented\n * date formatted and displayed exactly like a real one.\n *\n * The round-trip is the check: build the Date, then read the components back off it.\n * A value that rolled over cannot answer with the numbers it was given. That is the\n * same shape {@link sameYmd} already expresses, applied to a Date and its source. */\nexport function parseIsoDate(s: string): Date | null {\n if (!s) return null;\n const [y, m, d] = s.split(\"-\").map(Number);\n if (!y || !m || !d) return null;\n if (!Number.isInteger(y) || !Number.isInteger(m) || !Number.isInteger(d)) return null;\n const dt = new Date(y, m - 1, d);\n if (Number.isNaN(dt.getTime())) return null;\n if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) return null;\n return dt;\n}\n\n/** Format a \"YYYY-MM-DD\" string for display in `locale` (empty string when\n * unparseable). Defaults to the locale's short numeric date; pass `options` to\n * override (e.g. `{ dateStyle: \"medium\" }`). */\nexport function formatIsoDate(\n iso: string,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const d = parseIsoDate(iso);\n return d ? d.toLocaleDateString(locale, options) : \"\";\n}\n\n/** A \"YYYY-MM-DD\" string shifted by `days` (negative shifts back), or the input\n * unchanged when it isn't a parseable date.\n *\n * Local-time throughout — {@link parseIsoDate} builds a local-midnight Date and\n * {@link toLocalIso} reads local components back, so this never slides a day the way\n * a `toISOString()` round-trip does west of UTC. */\nexport function addDaysIso(iso: string, days: number): string {\n const d = parseIsoDate(iso);\n if (!d) return iso;\n d.setDate(d.getDate() + days);\n return toLocalIso(d);\n}\n\n/** Whether two Dates fall on the same local calendar day. */\nexport function sameYmd(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nexport interface DateRangePreset {\n key: string;\n from: string;\n to: string;\n}\n\n/**\n * The named ranges offered by the data-table date filter (local-calendar, like\n * {@link todayIso} — see the module note). `key` maps to the consumer's\n * `table.preset_*` labels.\n */\nexport function dateRangePresets(): DateRangePreset[] {\n const mondayOffset = (new Date().getDay() + 6) % 7; // monday = 0\n return [\n { key: \"today\", from: todayIso(), to: todayIso() },\n { key: \"yesterday\", from: shiftIso(-1), to: shiftIso(-1) },\n { key: \"this_week\", from: startOfWeekIso(), to: todayIso() },\n { key: \"last_week\", from: shiftIso(-7 - mondayOffset), to: shiftIso(-1 - mondayOffset) },\n { key: \"last_7_days\", from: shiftIso(-6), to: todayIso() },\n { key: \"last_30_days\", from: shiftIso(-29), to: todayIso() },\n { key: \"this_month\", from: startOfMonthIso(0), to: todayIso() },\n { key: \"last_month\", from: startOfMonthIso(-1), to: endOfMonthIso(-1) },\n { key: \"last_3_months\", from: shiftIso(-90), to: todayIso() },\n { key: \"ytd\", from: startOfYearIso(0), to: todayIso() },\n { key: \"last_year\", from: startOfYearIso(-1), to: endOfYearIso(-1) },\n ];\n}\n"],"mappings":"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","names":[]}
|
package/dist/lib/logger.js
CHANGED
package/dist/lib/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/logger.ts"],"sourcesContent":["import type { StateCreator, StoreMutatorIdentifier } from \"zustand\";\n\nimport { readStored } from \"./safe-storage\";\n\n/**\n * zustand logging middleware.\n *\n * Logs every state transition (a store \"action\") to the DevTools console as a\n * single grouped line showing the prev/next state. Enabled in dev (Vite\n * `import.meta.env.DEV`) by default; override at runtime with\n * `localStorage.setItem(\"store_log\", \"0\")` to silence or `\"1\"` to force-on in a\n * production build.\n */\ntype Logger = <\n T,\n Mps extends [StoreMutatorIdentifier, unknown][] = [],\n Mcs extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n f: StateCreator<T, Mps, Mcs>,\n name?: string,\n) => StateCreator<T, Mps, Mcs>;\n\ntype LoggerImpl = <T>(f: StateCreator<T, [], []>, name?: string) => StateCreator<T, [], []>;\n\n/**\n * Read ONCE, at module scope, not on every `set`.\n *\n * This used to run inside the wrapped `set`, which means a synchronous\n * `localStorage.getItem` on every state transition of every logged store — in\n * production builds too, because the override has to stay readable there by design.\n * The stores this middleware wraps include ones that transition per pointer event.\n *\n * The override cannot change without someone opening devtools and reloading, so\n * once per session is the same behaviour at a fraction of the cost. {@link setStoreLog}\n * keeps the ability to flip it live from a console, which is the only thing the\n * per-call read was actually buying.\n */\nlet storeLog: boolean = (() => {\n const override = readStored(\"store_log\");\n if (override === \"0\") return false;\n if (override === \"1\") return true;\n // NOT under vitest. `DEV` distinguishes a dev build from a production one; a test\n // run is neither, and vitest sets `DEV=true` — so every transition of every logged\n // store printed a prev/next object into the suite output. Measured on the consumer's\n // suite: 822 blocks, several thousand lines, in a log whose whole job is to make one\n // real `console.error` findable. `store_log=1` still forces it on.\n // Optional-chained because `import.meta.env` is a Vite injection: it is\n // undefined under plain Node ESM, and this package is published publicly,\n // so a non-Vite consumer must be able to import it without throwing.\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n})();\n\n/** Turn store logging on or off for the rest of the session (devtools escape hatch). */\nexport function setStoreLog(enabled: boolean): void {\n storeLog = enabled;\n}\n\nconst loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {\n const loggedSet = ((...args: unknown[]) => {\n const prev = get();\n (set as (...a: unknown[]) => void)(...args);\n if (storeLog) {\n console.debug(\n `%c[store${name ? `:${name}` : \"\"}]`,\n \"color:#0d9488\",\n { prev, next: get() },\n );\n }\n }) as typeof set;\n return f(loggedSet, get, store);\n};\n\nexport const logger = loggerImpl as unknown as Logger;\n"],"mappings":"AAEA,SAAS,kBAAkB;AAmC3B,IAAI,YAAqB,MAAM;AAC7B,QAAM,WAAW,WAAW,WAAW;AACvC,MAAI,aAAa,IAAK,QAAO;AAC7B,MAAI,aAAa,IAAK,QAAO;AAS7B,SAAO,QAAQ,YAAY,KAAK,GAAG,KAAK,CAAC,YAAY,KAAK;AAC5D,GAAG;AAGI,SAAS,YAAY,SAAwB;AAClD,aAAW;AACb;AAEA,MAAM,aAAyB,CAAC,GAAG,SAAS,CAAC,KAAK,KAAK,UAAU;AAC/D,QAAM,aAAa,IAAI,SAAoB;AACzC,UAAM,OAAO,IAAI;AACjB,IAAC,IAAkC,GAAG,IAAI;AAC1C,QAAI,UAAU;AACZ,cAAQ;AAAA,QACN,WAAW,OAAO,IAAI,IAAI,KAAK,EAAE;AAAA,QACjC;AAAA,QACA,EAAE,MAAM,MAAM,IAAI,EAAE;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,WAAW,KAAK,KAAK;AAChC;AAEO,MAAM,SAAS;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/logger.ts"],"sourcesContent":["import type { StateCreator, StoreMutatorIdentifier } from \"zustand\";\n\nimport { readStored } from \"./safe-storage\";\n\n/**\n * zustand logging middleware.\n *\n * Logs every state transition (a store \"action\") to the DevTools console as a\n * single grouped line showing the prev/next state. Enabled in dev (Vite\n * `import.meta.env.DEV`) by default; override at runtime with\n * `localStorage.setItem(\"store_log\", \"0\")` to silence or `\"1\"` to force-on in a\n * production build.\n */\ntype Logger = <\n T,\n Mps extends [StoreMutatorIdentifier, unknown][] = [],\n Mcs extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n f: StateCreator<T, Mps, Mcs>,\n name?: string,\n) => StateCreator<T, Mps, Mcs>;\n\ntype LoggerImpl = <T>(f: StateCreator<T, [], []>, name?: string) => StateCreator<T, [], []>;\n\n/**\n * Read ONCE, at module scope, not on every `set`.\n *\n * This used to run inside the wrapped `set`, which means a synchronous\n * `localStorage.getItem` on every state transition of every logged store — in\n * production builds too, because the override has to stay readable there by design.\n * The stores this middleware wraps include ones that transition per pointer event.\n *\n * The override cannot change without someone opening devtools and reloading, so\n * once per session is the same behaviour at a fraction of the cost. {@link setStoreLog}\n * keeps the ability to flip it live from a console, which is the only thing the\n * per-call read was actually buying.\n */\nlet storeLog: boolean = (() => {\n const override = readStored(\"store_log\");\n if (override === \"0\") return false;\n if (override === \"1\") return true;\n // NOT under vitest. `DEV` distinguishes a dev build from a production one; a test\n // run is neither, and vitest sets `DEV=true` — so every transition of every logged\n // store printed a prev/next object into the suite output. Measured on the consumer's\n // suite: 822 blocks, several thousand lines, in a log whose whole job is to make one\n // real `console.error` findable. `store_log=1` still forces it on.\n // Optional-chained because `import.meta.env` is a Vite injection: it is\n // undefined under plain Node ESM, and this package is published publicly,\n // so a non-Vite consumer must be able to import it without throwing.\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n})();\n\n/** Turn store logging on or off for the rest of the session (devtools escape hatch). */\nexport function setStoreLog(enabled: boolean): void {\n storeLog = enabled;\n}\n\nconst loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {\n const loggedSet = ((...args: unknown[]) => {\n const prev = get();\n (set as (...a: unknown[]) => void)(...args);\n if (storeLog) {\n console.debug(\n `%c[store${name ? `:${name}` : \"\"}]`,\n \"color:#0d9488\",\n { prev, next: get() },\n );\n }\n }) as typeof set;\n return f(loggedSet, get, store);\n};\n\nexport const logger = loggerImpl as unknown as Logger;\n"],"mappings":";AAEA,SAAS,kBAAkB;AAmC3B,IAAI,YAAqB,MAAM;AAC7B,QAAM,WAAW,WAAW,WAAW;AACvC,MAAI,aAAa,IAAK,QAAO;AAC7B,MAAI,aAAa,IAAK,QAAO;AAS7B,SAAO,QAAQ,YAAY,KAAK,GAAG,KAAK,CAAC,YAAY,KAAK;AAC5D,GAAG;AAGI,SAAS,YAAY,SAAwB;AAClD,aAAW;AACb;AAEA,MAAM,aAAyB,CAAC,GAAG,SAAS,CAAC,KAAK,KAAK,UAAU;AAC/D,QAAM,aAAa,IAAI,SAAoB;AACzC,UAAM,OAAO,IAAI;AACjB,IAAC,IAAkC,GAAG,IAAI;AAC1C,QAAI,UAAU;AACZ,cAAQ;AAAA,QACN,WAAW,OAAO,IAAI,IAAI,KAAK,EAAE;AAAA,QACjC;AAAA,QACA,EAAE,MAAM,MAAM,IAAI,EAAE;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,WAAW,KAAK,KAAK;AAChC;AAEO,MAAM,SAAS;","names":[]}
|
package/dist/lib/safe-storage.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/safe-storage.ts"],"sourcesContent":["/**\n * `localStorage` that cannot take the page down with it.\n *\n * Accessing `window.localStorage` **throws** — rather than returning null — when a\n * browser has site data blocked: Safari private browsing, Chrome with third-party\n * site data disabled, a partitioned webview. Three places in this package already\n * knew that and wrapped their calls in try/catch (`logger.ts`, and the DataTable's\n * `loadPersisted`/`savePersisted`). One did not: `AppShell` read the sidebar's\n * collapse flag straight out of a `useState` initialiser, so the throw happened\n * during render of the application's top-level shell. That is not a lost preference\n * — nothing mounts, in every consumer of this package at once.\n *\n * So the guard lives here, once, and the next storage-backed preference cannot\n * forget it. A read that cannot happen is indistinguishable from a key that was\n * never set, which is the right answer for every caller: the default.\n */\n\n/** The stored string for `key`, or null when absent, unreadable, or blocked. */\nexport function readStored(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\n/** Store `value` under `key`, silently doing nothing when storage is unavailable\n * or full. A preference that cannot be persisted is not worth an exception. */\nexport function writeStored(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // ignore (private mode, quota, partitioned storage)\n }\n}\n"],"mappings":"AAkBO,SAAS,WAAW,KAA4B;AACrD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,OAAO,aAAa,QAAQ,GAAG;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,SAAS,YAAY,KAAa,OAAqB;AAC5D,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,aAAa,QAAQ,KAAK,KAAK;AAAA,EACxC,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/safe-storage.ts"],"sourcesContent":["/**\n * `localStorage` that cannot take the page down with it.\n *\n * Accessing `window.localStorage` **throws** — rather than returning null — when a\n * browser has site data blocked: Safari private browsing, Chrome with third-party\n * site data disabled, a partitioned webview. Three places in this package already\n * knew that and wrapped their calls in try/catch (`logger.ts`, and the DataTable's\n * `loadPersisted`/`savePersisted`). One did not: `AppShell` read the sidebar's\n * collapse flag straight out of a `useState` initialiser, so the throw happened\n * during render of the application's top-level shell. That is not a lost preference\n * — nothing mounts, in every consumer of this package at once.\n *\n * So the guard lives here, once, and the next storage-backed preference cannot\n * forget it. A read that cannot happen is indistinguishable from a key that was\n * never set, which is the right answer for every caller: the default.\n */\n\n/** The stored string for `key`, or null when absent, unreadable, or blocked. */\nexport function readStored(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\n/** Store `value` under `key`, silently doing nothing when storage is unavailable\n * or full. A preference that cannot be persisted is not worth an exception. */\nexport function writeStored(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // ignore (private mode, quota, partitioned storage)\n }\n}\n"],"mappings":";AAkBO,SAAS,WAAW,KAA4B;AACrD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI;AACF,WAAO,OAAO,aAAa,QAAQ,GAAG;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,SAAS,YAAY,KAAa,OAAqB;AAC5D,MAAI,OAAO,WAAW,YAAa;AACnC,MAAI;AACF,WAAO,aAAa,QAAQ,KAAK,KAAK;AAAA,EACxC,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
@@ -27,7 +27,17 @@ interface CommandPaletteLabels {
|
|
|
27
27
|
loading: string;
|
|
28
28
|
/** Hint shown in the footer, e.g. "↑↓ to navigate · ↵ to select · esc to close". */
|
|
29
29
|
hint?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Accessible name of the dialog itself. It used to borrow `placeholder`, which is
|
|
32
|
+
* written to sit greyed-out in an empty box ("Search…", "Type a command"), not to
|
|
33
|
+
* name a window — and a reader heard the ellipsis read out as part of the name.
|
|
34
|
+
*
|
|
35
|
+
* Optional, unlike `placeholder`, so a translation annotated as
|
|
36
|
+
* `CommandPaletteLabels` keeps compiling; the default always carries it.
|
|
37
|
+
*/
|
|
38
|
+
dialog?: string;
|
|
30
39
|
}
|
|
40
|
+
declare const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels;
|
|
31
41
|
interface CommandPaletteProps {
|
|
32
42
|
open: boolean;
|
|
33
43
|
onClose: () => void;
|
|
@@ -68,4 +78,4 @@ declare function useCommandKey(onOpen: () => void): void;
|
|
|
68
78
|
*/
|
|
69
79
|
declare function CommandPalette({ open, onClose, search, revision, labels, }: CommandPaletteProps): react.ReactPortal | null;
|
|
70
80
|
|
|
71
|
-
export { type CommandItem, CommandPalette, type CommandPaletteLabels, useCommandKey };
|
|
81
|
+
export { type CommandItem, CommandPalette, type CommandPaletteLabels, DEFAULT_COMMAND_PALETTE_LABELS, useCommandKey };
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
4
|
import { createPortal } from "react-dom";
|
|
4
5
|
import { Search } from "lucide-react";
|
|
5
6
|
import { cn } from "../lib/cn.js";
|
|
6
7
|
import { useOverlayHistory } from "../hooks/use-overlay-history.js";
|
|
7
|
-
|
|
8
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
9
|
+
const DEFAULT_COMMAND_PALETTE_LABELS = {
|
|
8
10
|
placeholder: "Search\u2026",
|
|
9
11
|
empty: "No results",
|
|
10
|
-
loading: "Searching\u2026"
|
|
12
|
+
loading: "Searching\u2026",
|
|
13
|
+
dialog: "Search"
|
|
11
14
|
};
|
|
15
|
+
const { dialog: DEFAULT_DIALOG_NAME, ...DEFAULTS_WITHOUT_DIALOG } = DEFAULT_COMMAND_PALETTE_LABELS;
|
|
12
16
|
function useCommandKey(onOpen) {
|
|
13
17
|
useEffect(() => {
|
|
14
18
|
const handler = (e) => {
|
|
@@ -28,7 +32,8 @@ function CommandPalette({
|
|
|
28
32
|
revision,
|
|
29
33
|
labels
|
|
30
34
|
}) {
|
|
31
|
-
const l =
|
|
35
|
+
const l = useKitLabels("commandPalette", DEFAULTS_WITHOUT_DIALOG, labels);
|
|
36
|
+
const dialogName = l.dialog ?? (l.placeholder !== DEFAULT_COMMAND_PALETTE_LABELS.placeholder ? l.placeholder : DEFAULT_DIALOG_NAME);
|
|
32
37
|
const [query, setQuery] = useState("");
|
|
33
38
|
const [results, setResults] = useState([]);
|
|
34
39
|
const [loading, setLoading] = useState(false);
|
|
@@ -118,11 +123,11 @@ function CommandPalette({
|
|
|
118
123
|
{
|
|
119
124
|
role: "dialog",
|
|
120
125
|
"aria-modal": "true",
|
|
121
|
-
"aria-label":
|
|
122
|
-
className: "flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-
|
|
126
|
+
"aria-label": dialogName,
|
|
127
|
+
className: "flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-2xl",
|
|
123
128
|
children: [
|
|
124
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-
|
|
125
|
-
/* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0 text-
|
|
129
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b border-[var(--border)] px-3", children: [
|
|
130
|
+
/* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0 text-[var(--text-placeholder)]" }),
|
|
126
131
|
/* @__PURE__ */ jsx(
|
|
127
132
|
"input",
|
|
128
133
|
{
|
|
@@ -135,15 +140,15 @@ function CommandPalette({
|
|
|
135
140
|
onChange: (e) => setQuery(e.target.value),
|
|
136
141
|
onKeyDown,
|
|
137
142
|
placeholder: l.placeholder,
|
|
138
|
-
className: "w-full bg-transparent py-3 text-sm outline-none placeholder:text-
|
|
143
|
+
className: "w-full bg-transparent py-3 text-sm outline-none placeholder:text-[var(--text-placeholder)] text-[var(--text-primary)]"
|
|
139
144
|
}
|
|
140
145
|
),
|
|
141
|
-
loading && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-
|
|
146
|
+
loading && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-[var(--text-placeholder)]", children: l.loading })
|
|
142
147
|
] }),
|
|
143
148
|
/* @__PURE__ */ jsxs("ul", { id: "command-palette-list", ref: listRef, role: "listbox", className: "min-h-0 flex-1 overflow-y-auto py-1", children: [
|
|
144
|
-
flat.length === 0 && !loading && /* @__PURE__ */ jsx("li", { className: "px-3 py-6 text-center text-sm text-
|
|
149
|
+
flat.length === 0 && !loading && /* @__PURE__ */ jsx("li", { className: "px-3 py-6 text-center text-sm text-[var(--text-muted)]", children: l.empty }),
|
|
145
150
|
groups.map(({ group, items }) => /* @__PURE__ */ jsxs("li", { children: [
|
|
146
|
-
/* @__PURE__ */ jsx("div", { className: "px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-
|
|
151
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--text-placeholder)]", children: group }),
|
|
147
152
|
/* @__PURE__ */ jsx("ul", { children: items.map((item) => {
|
|
148
153
|
flatIndex += 1;
|
|
149
154
|
const idx = flatIndex;
|
|
@@ -156,13 +161,13 @@ function CommandPalette({
|
|
|
156
161
|
onMouseMove: () => setActive(idx),
|
|
157
162
|
className: cn(
|
|
158
163
|
"flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
|
|
159
|
-
isActive ? "bg-
|
|
164
|
+
isActive ? "bg-[var(--bg-active)] text-[var(--text-primary)]" : "text-[var(--text-secondary)]"
|
|
160
165
|
)
|
|
161
166
|
};
|
|
162
167
|
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
163
|
-
item.icon && /* @__PURE__ */ jsx("span", { className: "flex size-4 shrink-0 items-center justify-center text-
|
|
168
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "flex size-4 shrink-0 items-center justify-center text-[var(--text-placeholder)]", children: item.icon }),
|
|
164
169
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
|
|
165
|
-
item.hint && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-
|
|
170
|
+
item.hint && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-[var(--text-placeholder)]", children: item.hint })
|
|
166
171
|
] });
|
|
167
172
|
return /* @__PURE__ */ jsx("li", { children: item.href ? /* @__PURE__ */ jsx(
|
|
168
173
|
"a",
|
|
@@ -182,7 +187,7 @@ function CommandPalette({
|
|
|
182
187
|
}) })
|
|
183
188
|
] }, group))
|
|
184
189
|
] }),
|
|
185
|
-
l.hint && /* @__PURE__ */ jsx("div", { className: "border-t border-
|
|
190
|
+
l.hint && /* @__PURE__ */ jsx("div", { className: "border-t border-[var(--border)] px-3 py-1.5 text-[11px] text-[var(--text-placeholder)]", children: l.hint })
|
|
186
191
|
]
|
|
187
192
|
}
|
|
188
193
|
)
|
|
@@ -193,6 +198,7 @@ function CommandPalette({
|
|
|
193
198
|
}
|
|
194
199
|
export {
|
|
195
200
|
CommandPalette,
|
|
201
|
+
DEFAULT_COMMAND_PALETTE_LABELS,
|
|
196
202
|
useCommandKey
|
|
197
203
|
};
|
|
198
204
|
//# sourceMappingURL=command-palette.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/search/command-palette.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Search } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useOverlayHistory } from \"../hooks/use-overlay-history\";\n\nexport interface CommandItem {\n id: string;\n /** Primary text. */\n label: string;\n /** Secondary text on the right (e.g. an amount, a date, a group hint). */\n hint?: string;\n /** Heading this item is grouped under. */\n group: string;\n /** Leading icon / element. */\n icon?: ReactNode;\n /** Invoked when the item is chosen (navigate, run an action, …). */\n onSelect: () => void;\n /** Where this item leads, when it is a navigation rather than an action. Renders\n * the row as a real anchor so it can be middle-/⌘-clicked into a new tab\n * (Keksdose feedback #451); `onSelect` still runs for a plain click and for ↵, so\n * in-app navigation stays client-side. `href` only adds what the browser can do\n * with a link and JavaScript cannot fake — the rejected alternative, an\n * `onAuxClick` calling `window.open`, gets the middle click and nothing else. */\n href?: string;\n}\n\nexport interface CommandPaletteLabels {\n placeholder: string;\n empty: string;\n loading: string;\n /** Hint shown in the footer, e.g. \"↑↓ to navigate · ↵ to select · esc to close\". */\n hint?: string;\n}\n\nconst DEFAULT_LABELS: CommandPaletteLabels = {\n placeholder: \"Search…\",\n empty: \"No results\",\n loading: \"Searching…\",\n};\n\ninterface CommandPaletteProps {\n open: boolean;\n onClose: () => void;\n /**\n * Returns results for a query (called debounced as the user types; an empty\n * query is allowed — return defaults like pages/recent). Sync or async; stale\n * async responses are ignored.\n *\n * Its IDENTITY is not a re-run signal — it is read through a ref, so the palette\n * never re-searches merely because the caller rebuilt the callback. That is\n * deliberate: a provider closing over a list rendered from a query allocates a new\n * function on most renders, and re-running the search on each would make typing\n * stutter. Use {@link CommandPaletteProps.revision} when the DATA behind the\n * provider changes.\n */\n search: (query: string) => CommandItem[] | Promise<CommandItem[]>;\n /**\n * Anything whose change means the current results are out of date — typically the\n * arrays the provider searches over. Compared by identity (as an effect dependency\n * would be), so pass stable references: a `data = []` default allocates a fresh\n * array on every render while a query is pending and would re-search forever.\n *\n * Without it, a provider whose data arrives AFTER the palette opened never gets\n * asked again: the user's last keystroke settles the debounce, the queries resolve a\n * moment later, and the groups they would have contributed are simply absent until\n * another keystroke. That is the ordinary first open on a cold cache.\n */\n revision?: unknown;\n labels?: Partial<CommandPaletteLabels>;\n}\n\n/** Registers a global ⌘K / Ctrl-K shortcut that calls `onOpen`. */\nexport function useCommandKey(onOpen: () => void): void {\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && (e.key === \"k\" || e.key === \"K\")) {\n e.preventDefault();\n onOpen();\n }\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [onOpen]);\n}\n\n/**\n * A ⌘K-style command palette: a top-centred modal with a search field and a\n * grouped, keyboard-navigable result list. Domain-free — the app supplies\n * results via the `search` provider (pages, actions, records, …); each result\n * carries its own `onSelect`.\n */\nexport function CommandPalette({\n open,\n onClose,\n search,\n revision,\n labels,\n}: CommandPaletteProps) {\n const l = { ...DEFAULT_LABELS, ...labels };\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<CommandItem[]>([]);\n const [loading, setLoading] = useState(false);\n const [active, setActive] = useState(0);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n const searchRef = useRef(search);\n searchRef.current = search;\n const reqId = useRef(0);\n\n // Back dismisses the palette (Keksdose feedback #172). Declared with the other\n // hooks, above the `if (!open) return null` below — a hook past a conditional\n // return changes the hook order between renders. Passing `open` rather than\n // mounting-while-open is what lets it sit here.\n useOverlayHistory(open, onClose);\n\n // Reset + focus when opened.\n useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n setActive(0);\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }, [open]);\n\n // Debounced, race-safe search.\n useEffect(() => {\n if (!open) return;\n const id = ++reqId.current;\n setLoading(true);\n const run = async () => {\n try {\n const r = await Promise.resolve(searchRef.current(query));\n if (reqId.current === id) {\n setResults(r);\n setActive(0);\n }\n } finally {\n if (reqId.current === id) setLoading(false);\n }\n };\n const t = setTimeout(run, query ? 150 : 0);\n return () => clearTimeout(t);\n // `revision`, not `search`: see the prop's docstring. The debounce is re-armed on a\n // revision change, which is right — data arriving is not a keystroke and there is\n // nothing to feel laggy about.\n }, [query, open, revision]);\n\n // Group results, preserving first-seen group order; keep a flat list for nav.\n const { groups, flat } = useMemo(() => {\n const order: string[] = [];\n const byGroup = new Map<string, CommandItem[]>();\n for (const item of results) {\n if (!byGroup.has(item.group)) {\n byGroup.set(item.group, []);\n order.push(item.group);\n }\n byGroup.get(item.group)!.push(item);\n }\n return {\n groups: order.map((g) => ({ group: g, items: byGroup.get(g)! })),\n flat: order.flatMap((g) => byGroup.get(g)!),\n };\n }, [results]);\n\n const choose = (item: CommandItem | undefined) => {\n if (!item) return;\n onClose();\n item.onSelect();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActive((i) => Math.min(i + 1, flat.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((i) => Math.max(i - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n choose(flat[active]);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onClose();\n }\n };\n\n // Keep the active row scrolled into view.\n useEffect(() => {\n const el = listRef.current?.querySelector<HTMLElement>(`[data-index=\"${active}\"]`);\n el?.scrollIntoView({ block: \"nearest\" });\n }, [active]);\n\n if (!open) return null;\n\n let flatIndex = -1;\n return createPortal(\n <div\n className=\"fixed inset-0 z-[70] flex items-start justify-center bg-black/40 p-4 pt-[10vh]\"\n onMouseDown={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={l.placeholder}\n className=\"flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-slate-900\"\n >\n <div className=\"flex items-center gap-2 border-b border-slate-100 px-3 dark:border-slate-800\">\n <Search className=\"size-4 shrink-0 text-slate-400 dark:text-slate-500\" />\n <input\n ref={inputRef}\n role=\"combobox\"\n aria-expanded\n aria-controls=\"command-palette-list\"\n aria-activedescendant={flat[active] ? `command-item-${flat[active].id}` : undefined}\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={l.placeholder}\n className=\"w-full bg-transparent py-3 text-sm outline-none placeholder:text-slate-400 dark:text-slate-100 dark:placeholder:text-slate-500\"\n />\n {loading && <span className=\"shrink-0 text-[11px] text-slate-400 dark:text-slate-500\">{l.loading}</span>}\n </div>\n\n <ul id=\"command-palette-list\" ref={listRef} role=\"listbox\" className=\"min-h-0 flex-1 overflow-y-auto py-1\">\n {flat.length === 0 && !loading && (\n <li className=\"px-3 py-6 text-center text-sm text-slate-500 dark:text-slate-400\">{l.empty}</li>\n )}\n {groups.map(({ group, items }) => (\n <li key={group}>\n <div className=\"px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-slate-400 dark:text-slate-500\">\n {group}\n </div>\n <ul>\n {items.map((item) => {\n flatIndex += 1;\n const idx = flatIndex;\n const isActive = idx === active;\n // Written once and worn by either tag below, so the anchor and the\n // button can never drift apart in looks or in listbox semantics.\n // `role=\"option\"` on an <a href> is fine: the browser's middle-click\n // behaviour keys off the element, not the ARIA role.\n const shared = {\n id: `command-item-${item.id}`,\n role: \"option\" as const,\n \"aria-selected\": isActive,\n \"data-index\": idx,\n onMouseMove: () => setActive(idx),\n className: cn(\n \"flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm\",\n isActive\n ? \"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-100\"\n : \"text-slate-700 dark:text-slate-300\",\n ),\n };\n const inner = (\n <>\n {item.icon && (\n <span className=\"flex size-4 shrink-0 items-center justify-center text-slate-400 dark:text-slate-500\">\n {item.icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.hint && (\n <span className=\"shrink-0 text-xs text-slate-400 dark:text-slate-500\">{item.hint}</span>\n )}\n </>\n );\n return (\n <li key={item.id}>\n {item.href ? (\n <a\n {...shared}\n href={item.href}\n draggable={false}\n onClick={(e) => {\n // A modified click belongs to the browser (feedback\n // #451) — and the palette deliberately stays OPEN for\n // it: ⌘-clicking three results in a row is the reason to\n // want this, and closing after the first would undo it.\n if (\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey\n )\n return;\n e.preventDefault();\n choose(item);\n }}\n >\n {inner}\n </a>\n ) : (\n <button type=\"button\" {...shared} onClick={() => choose(item)}>\n {inner}\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </li>\n ))}\n </ul>\n\n {l.hint && (\n <div className=\"border-t border-slate-100 px-3 py-1.5 text-[11px] text-slate-400 dark:border-slate-800 dark:text-slate-500\">\n {l.hint}\n </div>\n )}\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/search/command-palette.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Search } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useOverlayHistory } from \"../hooks/use-overlay-history\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\n\nexport interface CommandItem {\n id: string;\n /** Primary text. */\n label: string;\n /** Secondary text on the right (e.g. an amount, a date, a group hint). */\n hint?: string;\n /** Heading this item is grouped under. */\n group: string;\n /** Leading icon / element. */\n icon?: ReactNode;\n /** Invoked when the item is chosen (navigate, run an action, …). */\n onSelect: () => void;\n /** Where this item leads, when it is a navigation rather than an action. Renders\n * the row as a real anchor so it can be middle-/⌘-clicked into a new tab\n * (Keksdose feedback #451); `onSelect` still runs for a plain click and for ↵, so\n * in-app navigation stays client-side. `href` only adds what the browser can do\n * with a link and JavaScript cannot fake — the rejected alternative, an\n * `onAuxClick` calling `window.open`, gets the middle click and nothing else. */\n href?: string;\n}\n\nexport interface CommandPaletteLabels {\n placeholder: string;\n empty: string;\n loading: string;\n /** Hint shown in the footer, e.g. \"↑↓ to navigate · ↵ to select · esc to close\". */\n hint?: string;\n /**\n * Accessible name of the dialog itself. It used to borrow `placeholder`, which is\n * written to sit greyed-out in an empty box (\"Search…\", \"Type a command\"), not to\n * name a window — and a reader heard the ellipsis read out as part of the name.\n *\n * Optional, unlike `placeholder`, so a translation annotated as\n * `CommandPaletteLabels` keeps compiling; the default always carries it.\n */\n dialog?: string;\n}\n\nexport const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels = {\n placeholder: \"Search…\",\n empty: \"No results\",\n loading: \"Searching…\",\n dialog: \"Search\",\n};\n\n/** The defaults minus `dialog`, so a resolved `dialog` means someone SUPPLIED one —\n * see the dialog's `aria-label` for why that distinction matters. */\nconst { dialog: DEFAULT_DIALOG_NAME, ...DEFAULTS_WITHOUT_DIALOG } = DEFAULT_COMMAND_PALETTE_LABELS;\n\ninterface CommandPaletteProps {\n open: boolean;\n onClose: () => void;\n /**\n * Returns results for a query (called debounced as the user types; an empty\n * query is allowed — return defaults like pages/recent). Sync or async; stale\n * async responses are ignored.\n *\n * Its IDENTITY is not a re-run signal — it is read through a ref, so the palette\n * never re-searches merely because the caller rebuilt the callback. That is\n * deliberate: a provider closing over a list rendered from a query allocates a new\n * function on most renders, and re-running the search on each would make typing\n * stutter. Use {@link CommandPaletteProps.revision} when the DATA behind the\n * provider changes.\n */\n search: (query: string) => CommandItem[] | Promise<CommandItem[]>;\n /**\n * Anything whose change means the current results are out of date — typically the\n * arrays the provider searches over. Compared by identity (as an effect dependency\n * would be), so pass stable references: a `data = []` default allocates a fresh\n * array on every render while a query is pending and would re-search forever.\n *\n * Without it, a provider whose data arrives AFTER the palette opened never gets\n * asked again: the user's last keystroke settles the debounce, the queries resolve a\n * moment later, and the groups they would have contributed are simply absent until\n * another keystroke. That is the ordinary first open on a cold cache.\n */\n revision?: unknown;\n labels?: Partial<CommandPaletteLabels>;\n}\n\n/** Registers a global ⌘K / Ctrl-K shortcut that calls `onOpen`. */\nexport function useCommandKey(onOpen: () => void): void {\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && (e.key === \"k\" || e.key === \"K\")) {\n e.preventDefault();\n onOpen();\n }\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [onOpen]);\n}\n\n/**\n * A ⌘K-style command palette: a top-centred modal with a search field and a\n * grouped, keyboard-navigable result list. Domain-free — the app supplies\n * results via the `search` provider (pages, actions, records, …); each result\n * carries its own `onSelect`.\n */\nexport function CommandPalette({\n open,\n onClose,\n search,\n revision,\n labels,\n}: CommandPaletteProps) {\n const l = useKitLabels(\"commandPalette\", DEFAULTS_WITHOUT_DIALOG, labels);\n // `dialog` is new, and until it existed the dialog was named by `placeholder`. An\n // app that translated `placeholder` and has not heard of `dialog` would otherwise\n // go from a German name to an English \"Search\" on update — so a translated\n // placeholder keeps doing the job until a `dialog` is supplied.\n const dialogName =\n l.dialog ??\n (l.placeholder !== DEFAULT_COMMAND_PALETTE_LABELS.placeholder ? l.placeholder : DEFAULT_DIALOG_NAME);\n const [query, setQuery] = useState(\"\");\n const [results, setResults] = useState<CommandItem[]>([]);\n const [loading, setLoading] = useState(false);\n const [active, setActive] = useState(0);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n const searchRef = useRef(search);\n searchRef.current = search;\n const reqId = useRef(0);\n\n // Back dismisses the palette (Keksdose feedback #172). Declared with the other\n // hooks, above the `if (!open) return null` below — a hook past a conditional\n // return changes the hook order between renders. Passing `open` rather than\n // mounting-while-open is what lets it sit here.\n useOverlayHistory(open, onClose);\n\n // Reset + focus when opened.\n useEffect(() => {\n if (!open) return;\n setQuery(\"\");\n setActive(0);\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }, [open]);\n\n // Debounced, race-safe search.\n useEffect(() => {\n if (!open) return;\n const id = ++reqId.current;\n setLoading(true);\n const run = async () => {\n try {\n const r = await Promise.resolve(searchRef.current(query));\n if (reqId.current === id) {\n setResults(r);\n setActive(0);\n }\n } finally {\n if (reqId.current === id) setLoading(false);\n }\n };\n const t = setTimeout(run, query ? 150 : 0);\n return () => clearTimeout(t);\n // `revision`, not `search`: see the prop's docstring. The debounce is re-armed on a\n // revision change, which is right — data arriving is not a keystroke and there is\n // nothing to feel laggy about.\n }, [query, open, revision]);\n\n // Group results, preserving first-seen group order; keep a flat list for nav.\n const { groups, flat } = useMemo(() => {\n const order: string[] = [];\n const byGroup = new Map<string, CommandItem[]>();\n for (const item of results) {\n if (!byGroup.has(item.group)) {\n byGroup.set(item.group, []);\n order.push(item.group);\n }\n byGroup.get(item.group)!.push(item);\n }\n return {\n groups: order.map((g) => ({ group: g, items: byGroup.get(g)! })),\n flat: order.flatMap((g) => byGroup.get(g)!),\n };\n }, [results]);\n\n const choose = (item: CommandItem | undefined) => {\n if (!item) return;\n onClose();\n item.onSelect();\n };\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActive((i) => Math.min(i + 1, flat.length - 1));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActive((i) => Math.max(i - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n choose(flat[active]);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n onClose();\n }\n };\n\n // Keep the active row scrolled into view.\n useEffect(() => {\n const el = listRef.current?.querySelector<HTMLElement>(`[data-index=\"${active}\"]`);\n el?.scrollIntoView({ block: \"nearest\" });\n }, [active]);\n\n if (!open) return null;\n\n let flatIndex = -1;\n return createPortal(\n <div\n className=\"fixed inset-0 z-[70] flex items-start justify-center bg-black/40 p-4 pt-[10vh]\"\n onMouseDown={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={dialogName}\n className=\"flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-2xl\"\n >\n <div className=\"flex items-center gap-2 border-b border-[var(--border)] px-3\">\n <Search className=\"size-4 shrink-0 text-[var(--text-placeholder)]\" />\n <input\n ref={inputRef}\n role=\"combobox\"\n aria-expanded\n aria-controls=\"command-palette-list\"\n aria-activedescendant={flat[active] ? `command-item-${flat[active].id}` : undefined}\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={l.placeholder}\n className=\"w-full bg-transparent py-3 text-sm outline-none placeholder:text-[var(--text-placeholder)] text-[var(--text-primary)]\"\n />\n {loading && <span className=\"shrink-0 text-[11px] text-[var(--text-placeholder)]\">{l.loading}</span>}\n </div>\n\n <ul id=\"command-palette-list\" ref={listRef} role=\"listbox\" className=\"min-h-0 flex-1 overflow-y-auto py-1\">\n {flat.length === 0 && !loading && (\n <li className=\"px-3 py-6 text-center text-sm text-[var(--text-muted)]\">{l.empty}</li>\n )}\n {groups.map(({ group, items }) => (\n <li key={group}>\n <div className=\"px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--text-placeholder)]\">\n {group}\n </div>\n <ul>\n {items.map((item) => {\n flatIndex += 1;\n const idx = flatIndex;\n const isActive = idx === active;\n // Written once and worn by either tag below, so the anchor and the\n // button can never drift apart in looks or in listbox semantics.\n // `role=\"option\"` on an <a href> is fine: the browser's middle-click\n // behaviour keys off the element, not the ARIA role.\n const shared = {\n id: `command-item-${item.id}`,\n role: \"option\" as const,\n \"aria-selected\": isActive,\n \"data-index\": idx,\n onMouseMove: () => setActive(idx),\n className: cn(\n \"flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm\",\n isActive\n ? \"bg-[var(--bg-active)] text-[var(--text-primary)]\"\n : \"text-[var(--text-secondary)]\",\n ),\n };\n const inner = (\n <>\n {item.icon && (\n <span className=\"flex size-4 shrink-0 items-center justify-center text-[var(--text-placeholder)]\">\n {item.icon}\n </span>\n )}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.hint && (\n <span className=\"shrink-0 text-xs text-[var(--text-placeholder)]\">{item.hint}</span>\n )}\n </>\n );\n return (\n <li key={item.id}>\n {item.href ? (\n <a\n {...shared}\n href={item.href}\n draggable={false}\n onClick={(e) => {\n // A modified click belongs to the browser (feedback\n // #451) — and the palette deliberately stays OPEN for\n // it: ⌘-clicking three results in a row is the reason to\n // want this, and closing after the first would undo it.\n if (\n e.button !== 0 ||\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n e.altKey\n )\n return;\n e.preventDefault();\n choose(item);\n }}\n >\n {inner}\n </a>\n ) : (\n <button type=\"button\" {...shared} onClick={() => choose(item)}>\n {inner}\n </button>\n )}\n </li>\n );\n })}\n </ul>\n </li>\n ))}\n </ul>\n\n {l.hint && (\n <div className=\"border-t border-[var(--border)] px-3 py-1.5 text-[11px] text-[var(--text-placeholder)]\">\n {l.hint}\n </div>\n )}\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/search.d.ts
ADDED
package/dist/search.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/search.ts"],"sourcesContent":["// `@eifi1/ui-kit/search` — the command palette / global search.\n// A re-slicing of the main barrel, not a new API.\nexport * from \"./search/command-palette\";\n"],"mappings":";AAEA,cAAc;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
3
|
import { LucideIcon } from 'lucide-react';
|
|
4
4
|
|
|
5
5
|
interface AppShellSubItem {
|
|
@@ -35,7 +35,13 @@ interface AppShellNavItem {
|
|
|
35
35
|
* bottom bar ignores sub-items and links to `to`. */
|
|
36
36
|
items?: AppShellSubItem[];
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Exported and `<div>`-shaped. This is the outermost element of every consuming app, so
|
|
40
|
+
* there is nothing above it to hang an id, a landmark label or a `data-tour` anchor on —
|
|
41
|
+
* and nothing a consumer can wrap it in either, since the root owns the `h-dvh` and
|
|
42
|
+
* `overflow-hidden` the whole layout is built on (audit §api-design).
|
|
43
|
+
*/
|
|
44
|
+
interface AppShellProps extends ComponentPropsWithoutRef<"div"> {
|
|
39
45
|
/** Navigation entries — shared by the desktop sidebar and mobile bottom bar. */
|
|
40
46
|
nav: AppShellNavItem[];
|
|
41
47
|
/** The composed top bar (e.g. the shared TopBar with app-owned actions). */
|
|
@@ -49,8 +55,31 @@ interface AppShellProps {
|
|
|
49
55
|
sidebarFooter?: (collapsed: boolean) => ReactNode;
|
|
50
56
|
/** localStorage key for the persisted collapse state. */
|
|
51
57
|
collapseStorageKey?: string;
|
|
58
|
+
/** Default: `appShell.collapse` from the {@link UiKitProvider}, else English. */
|
|
52
59
|
collapseLabel?: string;
|
|
60
|
+
/** Default: `appShell.expand` from the {@link UiKitProvider}, else English. */
|
|
53
61
|
expandLabel?: string;
|
|
62
|
+
/** How an entry's `items` are revealed in the EXPANDED desktop sidebar.
|
|
63
|
+
*
|
|
64
|
+
* - `"flyout"` (default) — a panel beside the sidebar on hover/focus. Compact: the
|
|
65
|
+
* sidebar stays one row per group however many pages a group holds.
|
|
66
|
+
* - `"inline"` — a disclosure under the entry, the pattern of MUI's nested List and
|
|
67
|
+
* most documentation sites. Every page is visible at once, and the group holding
|
|
68
|
+
* the current page opens on its own, so the reader can see where they are without
|
|
69
|
+
* hovering anything.
|
|
70
|
+
*
|
|
71
|
+
* The icon-only (collapsed) sidebar always uses the flyout: an indented list has no
|
|
72
|
+
* room there. The mobile bottom bar ignores `items` in both modes. */
|
|
73
|
+
subNav?: "flyout" | "inline";
|
|
74
|
+
/** Accessible name of the inline disclosure button, given the group's label.
|
|
75
|
+
* Only used with `subNav="inline"`. Its expanded state is carried by
|
|
76
|
+
* `aria-expanded`, so one wording serves both directions. Default:
|
|
77
|
+
* `appShell.toggleGroup` from the {@link UiKitProvider}, else English. */
|
|
78
|
+
toggleGroupLabel?: (groupLabel: string) => string;
|
|
79
|
+
/** Below `md`: show the current group's `items` as a scrollable row above the
|
|
80
|
+
* bottom bar — the phone's counterpart of the sidebar's second level. Default true;
|
|
81
|
+
* `false` keeps the single bar, where a group entry only links to its own `to`. */
|
|
82
|
+
mobileSubNav?: boolean;
|
|
54
83
|
}
|
|
55
84
|
/**
|
|
56
85
|
* The responsive application shell: a top bar, a collapsible desktop sidebar
|
|
@@ -59,6 +88,6 @@ interface AppShellProps {
|
|
|
59
88
|
* `NavLink`; domain-free — nav items, brand, footer and the top bar's actions
|
|
60
89
|
* are all supplied by the app.
|
|
61
90
|
*/
|
|
62
|
-
declare function AppShell({ nav, topBar, children, footer, sidebarFooter, collapseStorageKey, collapseLabel, expandLabel, }: AppShellProps): react.JSX.Element;
|
|
91
|
+
declare function AppShell({ nav, topBar, children, footer, sidebarFooter, collapseStorageKey, collapseLabel, expandLabel, subNav, mobileSubNav, toggleGroupLabel, className, ...rest }: AppShellProps): react.JSX.Element;
|
|
63
92
|
|
|
64
|
-
export { AppShell, type AppShellNavItem, type AppShellSubItem };
|
|
93
|
+
export { AppShell, type AppShellNavItem, type AppShellProps, type AppShellSubItem };
|