@eifi1/ui-kit 0.51.7 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -23
- package/dist/account/account-parts.d.ts +10 -8
- package/dist/account/account-parts.js +1 -18
- package/dist/account/account-parts.js.map +1 -1
- package/dist/account/data-export-setting-labels.js +1 -1
- package/dist/account/data-export-setting-labels.js.map +1 -1
- package/dist/account/delete-account-setting-labels.js +1 -1
- package/dist/account/delete-account-setting-labels.js.map +1 -1
- package/dist/account/email-change-setting-labels.js +1 -1
- package/dist/account/email-change-setting-labels.js.map +1 -1
- package/dist/account/passkeys-setting.d.ts +9 -2
- package/dist/account/passkeys-setting.js +41 -29
- package/dist/account/passkeys-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +3 -2
- package/dist/account/sessions-setting.js +10 -8
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.js +1 -3
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/admin-parts.d.ts +6 -3
- package/dist/admin/admin-parts.js +6 -21
- package/dist/admin/admin-parts.js.map +1 -1
- package/dist/admin/mail-test-card-labels.js +1 -1
- package/dist/admin/mail-test-card-labels.js.map +1 -1
- package/dist/admin/plan-change-labels.d.ts +2 -2
- package/dist/admin/plan-change-labels.js +1 -17
- package/dist/admin/plan-change-labels.js.map +1 -1
- package/dist/admin/plan-change.d.ts +3 -42
- package/dist/admin/plan-change.js +1 -1
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.js +3 -3
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/auth/auth-errors-labels.d.ts +11 -0
- package/dist/auth/auth-errors-labels.js +13 -0
- package/dist/auth/auth-errors-labels.js.map +1 -0
- package/dist/auth/auth-errors.d.ts +7 -23
- package/dist/auth/auth-errors.js +2 -15
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/forgot-password-form-labels.js +1 -1
- package/dist/auth/forgot-password-form-labels.js.map +1 -1
- package/dist/auth/forgot-password-form.js +7 -2
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form-labels.js +1 -1
- package/dist/auth/register-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form-labels.js +1 -1
- package/dist/auth/reset-password-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form.js +5 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form-labels.js +1 -1
- package/dist/auth/sign-in-form-labels.js.map +1 -1
- package/dist/auth/status-parts.d.ts +0 -3
- package/dist/auth/status-parts.js +0 -4
- package/dist/auth/status-parts.js.map +1 -1
- package/dist/auth/verify-email-labels.js +1 -1
- package/dist/auth/verify-email-labels.js.map +1 -1
- package/dist/auth/verify-email.js +7 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +20 -8
- package/dist/billing/cancel-subscription.js +7 -5
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/dialog-close.d.ts +29 -0
- package/dist/billing/dialog-close.js +8 -0
- package/dist/billing/dialog-close.js.map +1 -0
- package/dist/billing/plan-card.d.ts +14 -4
- package/dist/billing/plan-card.js +15 -8
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-change-labels.d.ts +42 -0
- package/dist/billing/plan-change-labels.js +22 -0
- package/dist/billing/plan-change-labels.js.map +1 -0
- package/dist/billing/plan-switch.d.ts +19 -7
- package/dist/billing/plan-switch.js +8 -4
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +12 -0
- package/dist/chart.js +16 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/account-chips.d.ts +5 -5
- package/dist/components/account-chips.js +11 -13
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.d.ts +16 -5
- package/dist/components/alert-banner.js +46 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -0
- package/dist/components/amount-input.js +1 -0
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +57 -65
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +12 -2
- package/dist/components/bulk-action-bar.js +65 -21
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-look.d.ts +5 -5
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/chart-edge.d.ts +16 -0
- package/dist/components/chart-edge.js +14 -1
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-figure.d.ts +13 -0
- package/dist/components/chart-figure.js +15 -0
- package/dist/components/chart-figure.js.map +1 -0
- package/dist/components/chart-zoom-core.js +1 -4
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/chart.d.ts +8 -1
- package/dist/components/chart.js +19 -10
- package/dist/components/chart.js.map +1 -1
- package/dist/components/combobox-core.d.ts +111 -2
- package/dist/components/combobox-core.js +148 -42
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +141 -130
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +6 -5
- package/dist/components/copy-button.js +5 -3
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.js +2 -2
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +1 -1
- package/dist/components/data-table-column-settings.js +33 -7
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +4 -2
- package/dist/components/data-table-filters.js +2 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +6 -4
- package/dist/components/data-table-fit.js +4 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.d.ts +1 -1
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.d.ts +1 -1
- package/dist/components/data-table-head.js +15 -12
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +12 -7
- package/dist/components/data-table-mobile.js +43 -30
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-pagination.js +11 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-resize.d.ts +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +6 -2
- package/dist/components/data-table-row.js +17 -10
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table-selection.d.ts +1 -1
- package/dist/components/data-table-selection.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +10 -2
- package/dist/components/data-table-shared.js +18 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +21 -2
- package/dist/components/data-table-sort.js +24 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table-virtual.d.ts +1 -1
- package/dist/components/data-table-virtual.js +18 -6
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/data-table.d.ts +13 -4
- package/dist/components/data-table.js +139 -36
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-field.d.ts +24 -2
- package/dist/components/date-field.js +33 -8
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +15 -2
- package/dist/components/date-picker.js +55 -26
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +47 -20
- package/dist/components/date-range-picker.js +23 -7
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/error-boundary.js +37 -30
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
- package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
- package/dist/components/field-base-labels.js.map +1 -0
- package/dist/components/field-base.d.ts +4 -3
- package/dist/components/field-base.js +3 -3
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-sync-labels.d.ts +1 -0
- package/dist/components/field-sync-labels.js.map +1 -1
- package/dist/components/field-sync.d.ts +23 -1
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/form-actions.js +11 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +5 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +14 -9
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.d.ts +43 -0
- package/dist/components/inline-confirm.js +67 -0
- package/dist/components/inline-confirm.js.map +1 -0
- package/dist/components/inline-entity-combobox.d.ts +4 -3
- package/dist/components/inline-entity-combobox.js +148 -140
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +28 -112
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal-page.d.ts +3 -1
- package/dist/components/legal-page.js +3 -2
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.d.ts +4 -2
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.d.ts +2 -2
- package/dist/components/lightbox.js +17 -6
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/list.d.ts +6 -2
- package/dist/components/list.js +3 -2
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.d.ts +1 -1
- package/dist/components/measured-grid.js +2 -1
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/mini-calendar.js +3 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.js +3 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-grid.d.ts +55 -0
- package/dist/components/month-grid.js +148 -0
- package/dist/components/month-grid.js.map +1 -0
- package/dist/components/month-picker.d.ts +36 -4
- package/dist/components/month-picker.js +123 -146
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +11 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +7 -0
- package/dist/components/pie-chart-labels.js +1 -0
- package/dist/components/pie-chart-labels.js.map +1 -1
- package/dist/components/pie-chart.d.ts +5 -3
- package/dist/components/pie-chart.js +7 -22
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +3 -0
- package/dist/components/popover.js +7 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.js +2 -1
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +7 -0
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +3 -3
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +7 -4
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +31 -7
- package/dist/components/series-chart.js +72 -29
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +3 -3
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/sign-chip-labels.d.ts +1 -0
- package/dist/components/sign-chip-labels.js.map +1 -1
- package/dist/components/sign-chip.d.ts +10 -1
- package/dist/components/sign-chip.js.map +1 -1
- package/dist/components/tabs-labels.d.ts +2 -0
- package/dist/components/tabs-labels.js +11 -0
- package/dist/components/tabs-labels.js.map +1 -0
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +2 -2
- package/dist/components/toggle-legend.js +6 -6
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -2
- package/dist/components/translation-review-editor.js +2 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.d.ts +2 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +26 -0
- package/dist/components/treemap-labels.js +10 -0
- package/dist/components/treemap-labels.js.map +1 -0
- package/dist/components/treemap-layout.d.ts +62 -0
- package/dist/components/treemap-layout.js +126 -0
- package/dist/components/treemap-layout.js.map +1 -0
- package/dist/components/treemap.d.ts +140 -17
- package/dist/components/treemap.js +324 -97
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.d.ts +5 -5
- package/dist/components/use-table-state.js +4 -1
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +6 -2
- package/dist/data-table.js +7 -2
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +36 -7
- package/dist/eslint/role-utilities.js +42 -9
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +25 -16
- package/dist/feedback/feedback-dialog.js +6 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-mutation.d.ts +30 -0
- package/dist/feedback/feedback-mutation.js +16 -0
- package/dist/feedback/feedback-mutation.js.map +1 -0
- package/dist/feedback/feedback-page-link.d.ts +53 -0
- package/dist/feedback/feedback-page-link.js +37 -0
- package/dist/feedback/feedback-page-link.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +12 -3
- package/dist/feedback/feedback-public-list.js +2 -1
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-replies.d.ts +3 -1
- package/dist/feedback/feedback-replies.js +5 -4
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +5 -0
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +64 -28
- package/dist/feedback/feedback-row-detail.js +30 -28
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +30 -11
- package/dist/feedback/feedback-status-undo.js +10 -15
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
- package/dist/feedback/feedback-submit-dialog.js +1 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.js +9 -3
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +21 -9
- package/dist/feedback/feedback-table.js +45 -25
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread-labels.d.ts +2 -2
- package/dist/feedback/feedback-thread-labels.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +24 -23
- package/dist/feedback/feedback-thread.js +2 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +34 -0
- package/dist/feedback.js +42 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +35 -0
- package/dist/hooks/use-focus-trap.js +6 -4
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +5 -2
- package/dist/hooks/use-hotkey.js +5 -4
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-long-press.d.ts +7 -4
- package/dist/hooks/use-long-press.js +3 -0
- package/dist/hooks/use-long-press.js.map +1 -1
- package/dist/hooks/use-media-query.js +12 -4
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-mounted.d.ts +22 -0
- package/dist/hooks/use-mounted.js +24 -0
- package/dist/hooks/use-mounted.js.map +1 -0
- package/dist/hooks/use-overlay-history.js +19 -3
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +4 -5
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +7 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +9 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +20 -41
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +7 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -10
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +9 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +7 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +8 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +5 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +64 -17
- package/dist/index.js +77 -12
- package/dist/index.js.map +1 -1
- package/dist/landing/routing.d.ts +3 -1
- package/dist/landing/routing.js +4 -7
- package/dist/landing/routing.js.map +1 -1
- package/dist/lib/broadcast.d.ts +17 -0
- package/dist/lib/broadcast.js +30 -0
- package/dist/lib/broadcast.js.map +1 -0
- package/dist/lib/date-format.d.ts +37 -2
- package/dist/lib/date-format.js +34 -8
- package/dist/lib/date-format.js.map +1 -1
- package/dist/lib/day-format.d.ts +46 -0
- package/dist/lib/day-format.js +46 -0
- package/dist/lib/day-format.js.map +1 -0
- package/dist/lib/describe-failure.d.ts +14 -0
- package/dist/lib/describe-failure.js +9 -0
- package/dist/lib/describe-failure.js.map +1 -0
- package/dist/lib/direction.d.ts +16 -0
- package/dist/lib/direction.js +9 -1
- package/dist/lib/direction.js.map +1 -1
- package/dist/lib/focus-left.d.ts +63 -0
- package/dist/lib/focus-left.js +67 -0
- package/dist/lib/focus-left.js.map +1 -0
- package/dist/lib/format.js +5 -14
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/http-status.d.ts +24 -0
- package/dist/lib/http-status.js +14 -0
- package/dist/lib/http-status.js.map +1 -0
- package/dist/lib/intl-cache.d.ts +14 -0
- package/dist/lib/intl-cache.js +15 -0
- package/dist/lib/intl-cache.js.map +1 -0
- package/dist/lib/isin.d.ts +10 -0
- package/dist/lib/isin.js.map +1 -1
- package/dist/lib/iso-day.d.ts +11 -0
- package/dist/lib/iso-day.js +8 -0
- package/dist/lib/iso-day.js.map +1 -0
- package/dist/lib/lazy-with-fallback.d.ts +31 -0
- package/dist/lib/lazy-with-fallback.js +40 -0
- package/dist/lib/lazy-with-fallback.js.map +1 -0
- package/dist/lib/path-pattern.d.ts +34 -0
- package/dist/lib/path-pattern.js +24 -0
- package/dist/lib/path-pattern.js.map +1 -0
- package/dist/lib/reduced-motion.d.ts +2 -1
- package/dist/lib/reduced-motion.js.map +1 -1
- package/dist/lib/round.d.ts +2 -1
- package/dist/lib/round.js +2 -1
- package/dist/lib/round.js.map +1 -1
- package/dist/lib/safe-storage.d.ts +24 -5
- package/dist/lib/safe-storage.js +27 -2
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/lib/session-refresh.js +1 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/share-format.d.ts +13 -0
- package/dist/lib/share-format.js +10 -0
- package/dist/lib/share-format.js.map +1 -0
- package/dist/lib/text-case.d.ts +17 -0
- package/dist/lib/text-case.js +8 -0
- package/dist/lib/text-case.js.map +1 -0
- package/dist/lib/text-match.d.ts +29 -0
- package/dist/lib/text-match.js +20 -2
- package/dist/lib/text-match.js.map +1 -1
- package/dist/lib/thenable.d.ts +13 -0
- package/dist/lib/thenable.js +8 -0
- package/dist/lib/thenable.js.map +1 -0
- package/dist/lib/translation-review.d.ts +7 -2
- package/dist/lib/translation-review.js +4 -4
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/notifications/device-zone-report.js +1 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +2 -1
- package/dist/notifications/notification-bell.js +3 -3
- package/dist/notifications/notification-bell.js.map +1 -1
- package/dist/notifications/notification-inbox.d.ts +10 -6
- package/dist/notifications/notification-inbox.js +26 -48
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +3 -2
- package/dist/notifications/notification-parts.js +1 -3
- package/dist/notifications/notification-parts.js.map +1 -1
- package/dist/notifications/notification-types-setting.js +1 -1
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notifications-labels.d.ts +6 -0
- package/dist/notifications/notifications-labels.js +1 -0
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-device-setting.js +1 -1
- package/dist/notifications/push-device-setting.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +2 -1
- package/dist/notifications/push-devices-setting.js +27 -49
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +10 -3
- package/dist/notifications/quiet-hours-setting.js +65 -26
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/unsubscribe-panel.js +4 -7
- package/dist/notifications/unsubscribe-panel.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +39 -1
- package/dist/notifications/use-push-device.js +28 -7
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/query-persistence.d.ts +3 -1
- package/dist/offline/query-persistence.js +19 -2
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/rhf/fields.d.ts +11 -4
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/command-palette.js +1 -1
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.js +19 -9
- package/dist/search/global-search.js.map +1 -1
- package/dist/search/use-command-key.d.ts +12 -1
- package/dist/search/use-command-key.js +3 -12
- package/dist/search/use-command-key.js.map +1 -1
- package/dist/search.d.ts +7 -0
- package/dist/search.js +10 -0
- package/dist/search.js.map +1 -1
- package/dist/settings/use-settings-focus.js +1 -0
- package/dist/settings/use-settings-focus.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +21 -5
- package/dist/shell/account-menu-language.js +3 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +26 -9
- package/dist/shell/account-menu-text-size.js +3 -2
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/dev-menu.js +5 -18
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +7 -0
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +1 -1
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +0 -2
- package/dist/shell/top-bar-parts-labels.js +0 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +27 -8
- package/dist/shell.js +28 -7
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +30 -4
- package/dist/sync/engine.js +172 -52
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync.d.ts +6 -2
- package/dist/sync.js +6 -1
- package/dist/sync.js.map +1 -1
- package/dist/testing/css-variables.js +3 -8
- package/dist/testing/css-variables.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -1
- package/dist/theme/contrast.js +3 -8
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-store.js +6 -6
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +3 -8
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +3 -6
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour-catalogue.d.ts +7 -4
- package/dist/tour/tour-catalogue.js +12 -10
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +0 -2
- package/dist/tour/tour-dock.js +0 -2
- package/dist/tour/tour-dock.js.map +1 -1
- package/dist/tour/tour-gates.d.ts +4 -0
- package/dist/tour/tour-gates.js +14 -0
- package/dist/tour/tour-gates.js.map +1 -1
- package/dist/tour/tour-overlay.d.ts +27 -0
- package/dist/tour/tour-overlay.js +741 -0
- package/dist/tour/tour-overlay.js.map +1 -0
- package/dist/tour/tour.d.ts +13 -3
- package/dist/tour/tour.js +59 -781
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.js +1 -1
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +5 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +6 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.d.ts +3 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.d.ts +3 -1
- package/dist/wizard/validation.js.map +1 -1
- package/package.json +10 -6
- package/src/account/account-parts.ts +5 -25
- package/src/account/data-export-setting-labels.ts +1 -1
- package/src/account/delete-account-setting-labels.ts +1 -1
- package/src/account/email-change-setting-labels.ts +1 -1
- package/src/account/passkeys-setting.tsx +60 -38
- package/src/account/sessions-setting.tsx +18 -12
- package/src/admin/admin-action-confirm.tsx +1 -5
- package/src/admin/admin-parts.ts +11 -25
- package/src/admin/mail-test-card-labels.ts +1 -1
- package/src/admin/plan-change-labels.ts +5 -23
- package/src/admin/plan-change.tsx +7 -44
- package/src/admin/reviewer-scope-editor.tsx +3 -3
- package/src/auth/auth-errors-labels.ts +23 -0
- package/src/auth/auth-errors.ts +11 -38
- package/src/auth/complete-name-dialog.tsx +4 -1
- package/src/auth/forgot-password-form-labels.ts +1 -1
- package/src/auth/forgot-password-form.tsx +11 -6
- package/src/auth/register-form-labels.ts +1 -1
- package/src/auth/reset-password-form-labels.ts +1 -1
- package/src/auth/reset-password-form.tsx +8 -4
- package/src/auth/sign-in-form-labels.ts +1 -1
- package/src/auth/status-parts.tsx +0 -6
- package/src/auth/verify-email-labels.ts +1 -1
- package/src/auth/verify-email.tsx +11 -6
- package/src/billing/cancel-subscription.tsx +26 -12
- package/src/billing/dialog-close.ts +34 -0
- package/src/billing/plan-card.tsx +36 -11
- package/src/billing/plan-change-labels.ts +66 -0
- package/src/billing/plan-switch.tsx +26 -12
- package/src/chart.ts +21 -3
- package/src/components/account-chips.tsx +20 -23
- package/src/components/alert-banner.tsx +82 -5
- package/src/components/amount-input.tsx +18 -7
- package/src/components/autocomplete.tsx +64 -70
- package/src/components/bulk-action-bar.tsx +70 -2
- package/src/components/button-look.ts +5 -5
- package/src/components/button.tsx +2 -2
- package/src/components/chart-edge.ts +34 -1
- package/src/components/chart-figure.tsx +35 -0
- package/src/components/chart-zoom-core.tsx +6 -5
- package/src/components/chart.tsx +26 -5
- package/src/components/combobox-core.tsx +293 -62
- package/src/components/combobox.tsx +183 -153
- package/src/components/copy-button.tsx +34 -23
- package/src/components/country-select.tsx +16 -11
- package/src/components/data-table-column-settings.tsx +52 -10
- package/src/components/data-table-filters.ts +6 -4
- package/src/components/data-table-fit.ts +19 -5
- package/src/components/data-table-groups.tsx +1 -1
- package/src/components/data-table-head.tsx +32 -22
- package/src/components/data-table-mobile.tsx +61 -26
- package/src/components/data-table-pagination.tsx +21 -6
- package/src/components/data-table-resize.ts +1 -1
- package/src/components/data-table-row-actions.tsx +3 -3
- package/src/components/data-table-row.tsx +25 -14
- package/src/components/data-table-selection.ts +1 -1
- package/src/components/data-table-shared.tsx +33 -1
- package/src/components/data-table-sort.ts +57 -2
- package/src/components/data-table-virtual.ts +32 -8
- package/src/components/data-table.tsx +239 -68
- package/src/components/date-field.tsx +65 -8
- package/src/components/date-picker.tsx +125 -48
- package/src/components/date-range-picker.tsx +82 -31
- package/src/components/error-boundary.tsx +53 -39
- package/src/components/field-base-labels.ts +20 -0
- package/src/components/field-base.tsx +13 -7
- package/src/components/field-sync-labels.ts +1 -0
- package/src/components/field-sync.tsx +23 -1
- package/src/components/form-actions.tsx +20 -5
- package/src/components/hover-menu.tsx +18 -8
- package/src/components/icon-button.tsx +14 -9
- package/src/components/inline-confirm.tsx +116 -0
- package/src/components/inline-entity-combobox.tsx +197 -153
- package/src/components/input.tsx +1 -1
- package/src/components/jump-calendar.tsx +30 -146
- package/src/components/legal-page.tsx +10 -7
- package/src/components/legal.tsx +4 -2
- package/src/components/lightbox.tsx +21 -8
- package/src/components/list.tsx +10 -4
- package/src/components/measured-grid.tsx +8 -4
- package/src/components/mini-calendar.tsx +6 -5
- package/src/components/modal.tsx +6 -1
- package/src/components/month-grid.tsx +229 -0
- package/src/components/month-picker.tsx +192 -200
- package/src/components/multi-select.tsx +21 -9
- package/src/components/picker-sheet.tsx +12 -9
- package/src/components/pie-chart-labels.ts +8 -0
- package/src/components/pie-chart.tsx +22 -28
- package/src/components/popover.tsx +18 -1
- package/src/components/row-actions.tsx +2 -1
- package/src/components/section-nav.tsx +7 -0
- package/src/components/series-chart-axes.ts +3 -3
- package/src/components/series-chart-labels.ts +7 -4
- package/src/components/series-chart.tsx +178 -69
- package/src/components/share-card.tsx +6 -6
- package/src/components/sign-chip-labels.ts +1 -0
- package/src/components/sign-chip.tsx +10 -1
- package/src/components/tabs-labels.ts +13 -0
- package/src/components/tabs.tsx +2 -2
- package/src/components/toggle-legend.tsx +16 -11
- package/src/components/translation-review-editor.tsx +6 -5
- package/src/components/translation-review.tsx +4 -3
- package/src/components/treemap-labels.ts +39 -0
- package/src/components/treemap-layout.ts +210 -0
- package/src/components/treemap.tsx +722 -133
- package/src/components/use-table-state.ts +12 -9
- package/src/data-table.ts +9 -2
- package/src/eslint/role-utilities.ts +97 -19
- package/src/feedback/feedback-dialog.tsx +30 -19
- package/src/feedback/feedback-mutation.ts +41 -0
- package/src/feedback/feedback-page-link.ts +90 -0
- package/src/feedback/feedback-public-list.tsx +17 -5
- package/src/feedback/feedback-replies.tsx +13 -6
- package/src/feedback/feedback-row-detail-labels.ts +5 -0
- package/src/feedback/feedback-row-detail.tsx +103 -67
- package/src/feedback/feedback-status-undo.ts +41 -27
- package/src/feedback/feedback-submit-dialog.tsx +2 -2
- package/src/feedback/feedback-submit.tsx +21 -7
- package/src/feedback/feedback-swipe.tsx +1 -1
- package/src/feedback/feedback-table.tsx +86 -38
- package/src/feedback/feedback-thread-labels.ts +2 -2
- package/src/feedback/feedback-thread.tsx +27 -25
- package/src/feedback.ts +42 -0
- package/src/hooks/use-focus-trap.ts +8 -24
- package/src/hooks/use-hotkey.ts +24 -7
- package/src/hooks/use-long-press.ts +16 -2
- package/src/hooks/use-media-query.ts +20 -4
- package/src/hooks/use-mounted.ts +39 -0
- package/src/hooks/use-overlay-history.ts +32 -3
- package/src/i18n/defaults.ts +4 -5
- package/src/i18n/german.ts +8 -2
- package/src/i18n/kit-labels.tsx +9 -2
- package/src/i18n/locales/es.ts +8 -2
- package/src/i18n/locales/fr.ts +21 -13
- package/src/i18n/locales/hu.ts +10 -2
- package/src/i18n/locales/it.ts +8 -2
- package/src/i18n/locales/zh.ts +9 -2
- package/src/i18n/review.ts +5 -0
- package/src/index.ts +103 -13
- package/src/landing/routing.tsx +14 -10
- package/src/lib/broadcast.ts +52 -0
- package/src/lib/date-format.ts +89 -13
- package/src/lib/day-format.ts +96 -0
- package/src/lib/describe-failure.ts +18 -0
- package/src/lib/direction.ts +39 -0
- package/src/lib/focus-left.ts +156 -0
- package/src/lib/format.ts +5 -17
- package/src/lib/http-status.ts +38 -0
- package/src/lib/intl-cache.ts +25 -0
- package/src/lib/isin.ts +10 -0
- package/src/lib/iso-day.ts +13 -0
- package/src/lib/lazy-with-fallback.ts +82 -0
- package/src/lib/path-pattern.ts +55 -0
- package/src/lib/reduced-motion.ts +2 -1
- package/src/lib/round.ts +7 -2
- package/src/lib/safe-storage.ts +48 -6
- package/src/lib/session-refresh.ts +1 -1
- package/src/lib/share-format.ts +18 -0
- package/src/lib/text-case.ts +19 -0
- package/src/lib/text-match.ts +57 -2
- package/src/lib/thenable.ts +19 -0
- package/src/lib/translation-review.ts +13 -5
- package/src/notifications/device-zone-report.ts +1 -1
- package/src/notifications/notification-bell.tsx +7 -4
- package/src/notifications/notification-inbox.tsx +40 -58
- package/src/notifications/notification-parts.tsx +3 -4
- package/src/notifications/notification-types-setting.tsx +1 -1
- package/src/notifications/notifications-labels.ts +7 -0
- package/src/notifications/push-device-setting.tsx +1 -1
- package/src/notifications/push-devices-setting.tsx +30 -55
- package/src/notifications/quiet-hours-setting.tsx +93 -31
- package/src/notifications/unsubscribe-panel.tsx +4 -7
- package/src/notifications/use-push-device.ts +76 -10
- package/src/offline/query-persistence.ts +35 -3
- package/src/rhf/fields.tsx +11 -4
- package/src/search/command-palette.tsx +2 -4
- package/src/search/global-search.tsx +36 -15
- package/src/search/use-command-key.ts +13 -12
- package/src/search.ts +9 -0
- package/src/settings/use-settings-focus.ts +3 -0
- package/src/shell/account-menu-language.tsx +27 -5
- package/src/shell/account-menu-text-size.tsx +32 -10
- package/src/shell/dev-menu.tsx +7 -21
- package/src/shell/role-switcher.tsx +7 -0
- package/src/shell/top-bar-actions.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +0 -3
- package/src/shell.ts +40 -9
- package/src/sync/engine.ts +339 -61
- package/src/sync.ts +8 -1
- package/src/testing/css-variables.ts +8 -9
- package/src/theme/contrast.ts +8 -9
- package/src/theme/palette-store.ts +9 -9
- package/src/theme/text-size.ts +6 -8
- package/src/theme/theme-store.ts +7 -9
- package/src/tour/tour-catalogue.tsx +26 -15
- package/src/tour/tour-dock.ts +0 -2
- package/src/tour/tour-gates.ts +23 -0
- package/src/tour/tour-overlay.tsx +1170 -0
- package/src/tour/tour.tsx +108 -1202
- package/src/tour/use-tour-progress.ts +1 -1
- package/src/wizard/stepper-nav.tsx +3 -1
- package/src/wizard/types.ts +11 -2
- package/src/wizard/use-wizard.ts +2 -1
- package/src/wizard/validation.ts +8 -2
- package/tokens.css +2 -1
- package/dist/components/ui-labels.js.map +0 -1
- package/dist/shell/dev-switchers.d.ts +0 -6
- package/dist/shell/dev-switchers.js +0 -12
- package/dist/shell/dev-switchers.js.map +0 -1
- package/src/components/ui-labels.ts +0 -24
- package/src/shell/dev-switchers.tsx +0 -18
package/dist/lib/date-format.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { formatLocaleOf } from "../i18n/languages.js";
|
|
3
|
+
import { pad, toLocalIso } from "./dates.js";
|
|
4
|
+
import { dateFormatter } from "./intl-cache.js";
|
|
5
|
+
import { isBareIsoDay } from "./iso-day.js";
|
|
3
6
|
const DATE_FORMAT_PREFERENCES = ["auto", "de-DE", "en-GB", "en-US", "sv-SE"];
|
|
4
7
|
function isDateFormatPreference(value) {
|
|
5
8
|
return typeof value === "string" && DATE_FORMAT_PREFERENCES.includes(value);
|
|
@@ -12,10 +15,9 @@ function resolveDateFormat(preference, language, options = {}) {
|
|
|
12
15
|
}
|
|
13
16
|
return { locale: preference, options: { year: "numeric", month: "2-digit", day: "2-digit" }, ...weekday };
|
|
14
17
|
}
|
|
15
|
-
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
16
18
|
function withWeekday(date, dateText, locale, options) {
|
|
17
19
|
const isDatePart = (p) => p.type === "day" || p.type === "month" || p.type === "year";
|
|
18
|
-
const parts =
|
|
20
|
+
const parts = dateFormatter(locale, { weekday: "short", ...options }).formatToParts(date);
|
|
19
21
|
const first = parts.findIndex(isDatePart);
|
|
20
22
|
if (first === -1) return dateText;
|
|
21
23
|
let last = first;
|
|
@@ -28,16 +30,38 @@ function withWeekday(date, dateText, locale, options) {
|
|
|
28
30
|
function formatDateWithPreference(value, preference, language, options = {}) {
|
|
29
31
|
if (!value) return "";
|
|
30
32
|
const resolved = resolveDateFormat(preference, language, options);
|
|
31
|
-
const dateOnly =
|
|
33
|
+
const dateOnly = isBareIsoDay(value);
|
|
32
34
|
const date = typeof value === "string" ? new Date(value) : value;
|
|
33
35
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const text =
|
|
36
|
+
const render = (timeZone) => {
|
|
37
|
+
const intl = timeZone ? { ...resolved.options, timeZone } : resolved.options;
|
|
38
|
+
const text = dateFormatter(resolved.locale, intl).format(date);
|
|
37
39
|
return resolved.weekday ? withWeekday(date, text, resolved.weekday.locale, intl) : text;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
+
};
|
|
41
|
+
if (dateOnly) return firstAccepted([() => render("UTC")], () => value);
|
|
42
|
+
return firstAccepted([() => render(options.timeZone), () => render(void 0)], () => toLocalIso(date));
|
|
43
|
+
}
|
|
44
|
+
function firstAccepted(renders, last) {
|
|
45
|
+
for (const render of renders) {
|
|
46
|
+
try {
|
|
47
|
+
return render();
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
40
50
|
}
|
|
51
|
+
return last();
|
|
52
|
+
}
|
|
53
|
+
function clockTime(date, language, timeZone) {
|
|
54
|
+
const locale = language ? formatLocaleOf(language) : void 0;
|
|
55
|
+
const clock = (zone) => dateFormatter(locale, { hour: "2-digit", minute: "2-digit", ...zone ? { timeZone: zone } : null }).format(date);
|
|
56
|
+
const reader = () => `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
57
|
+
return firstAccepted([() => clock(timeZone), () => clock(void 0)], reader);
|
|
58
|
+
}
|
|
59
|
+
function formatDateTimeWithPreference(value, preference, language, options = {}) {
|
|
60
|
+
if (!value) return "";
|
|
61
|
+
if (isBareIsoDay(value)) return formatDateWithPreference(value, preference, language, options);
|
|
62
|
+
const date = typeof value === "string" ? new Date(value) : value;
|
|
63
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
64
|
+
return `${formatDateWithPreference(date, preference, language, options)}, ${clockTime(date, language, options.timeZone)}`;
|
|
41
65
|
}
|
|
42
66
|
function dateFormatSample(preference, language) {
|
|
43
67
|
return formatDateWithPreference("2026-08-01", preference, language);
|
|
@@ -47,8 +71,10 @@ function dateFormatterFor(preference, language) {
|
|
|
47
71
|
}
|
|
48
72
|
export {
|
|
49
73
|
DATE_FORMAT_PREFERENCES,
|
|
74
|
+
clockTime,
|
|
50
75
|
dateFormatSample,
|
|
51
76
|
dateFormatterFor,
|
|
77
|
+
formatDateTimeWithPreference,
|
|
52
78
|
formatDateWithPreference,
|
|
53
79
|
isDateFormatPreference,
|
|
54
80
|
resolveDateFormat
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/date-format.ts"],"sourcesContent":["import { formatLocaleOf } from \"../i18n/languages\";\nimport type { KitDateFormatter } from \"../i18n/kit-labels\";\n\n/**\n * The date-format preference (0.46, lifted from keksdose's `date-format-store.ts`,\n * feedback #180): how a DAY is written — the order of day, month and year and the\n * separator between them — as a choice of its own, apart from the interface language.\n * \"Read the app in English and still see German dates\", or ISO everywhere.\n *\n * keksdose's rules, which the kit keeps:\n *\n * 1. **A label that identifies a day follows the preference** — anything that prints\n * day, month and year goes through {@link formatDateWithPreference} (or the\n * provider's `formatDate`, {@link dateFormatterFor}, for the kit's own date fields).\n * 2. **A label without a day follows the language**: \"August 2026\", a month or weekday\n * NAME has no order to arrange. {@link dateFormatterFor} answers `\"\"` for a month or a\n * year, so the kit keeps its own rendering in the language.\n * 3. **A clock time follows the language**: `sv-SE` is a statement about digit order in\n * a date, not about a 12- or 24-hour clock.\n * 4. **A weekday's NAME follows the language** (dev#546): `sv-SE` means \"year first,\n * with dashes\", not \"the reader speaks Swedish\" — so with `weekday` the day's name is\n * the language's, joined to the date the preference renders.\n *\n * The preference is the APP's to keep — keksdose holds it per device (a persisted store,\n * not an account column: nothing server-written formats a date for the reader). The kit\n * owns the vocabulary, the resolver, the renderer and the settings field\n * ({@link DateFormatSetting}).\n */\n\n/** `\"auto\"` follows the interface language (the default — nobody's dates move until\n * they ask); the rest are BCP 47 tags chosen for the SHAPE they produce: German\n * 01.08.2026, British 01/08/2026, US 08/01/2026, ISO 2026-08-01. */\nexport type DateFormatPreference = \"auto\" | \"de-DE\" | \"en-GB\" | \"en-US\" | \"sv-SE\";\n\n/** The options in the order the setting lists them: `auto` first (the default), then\n * German → UK → US → ISO. */\nexport const DATE_FORMAT_PREFERENCES: readonly DateFormatPreference[] = [\"auto\", \"de-DE\", \"en-GB\", \"en-US\", \"sv-SE\"];\n\n/** Whether `value` is one of {@link DATE_FORMAT_PREFERENCES} — for a value read back from\n * storage, where anything else falls back to `\"auto\"`. */\nexport function isDateFormatPreference(value: unknown): value is DateFormatPreference {\n return typeof value === \"string\" && (DATE_FORMAT_PREFERENCES as readonly string[]).includes(value);\n}\n\n/** A resolved preference: the locale and `Intl` options the date itself is rendered\n * with, and — only when asked for — the locale the weekday's NAME comes from. */\nexport interface ResolvedDateFormat {\n locale: string | undefined;\n options: Intl.DateTimeFormatOptions;\n /** The language's format locale, present when `weekday` was asked for. */\n weekday?: { locale: string | undefined };\n}\n\n/** What {@link resolveDateFormat} and the renderers take beyond the preference. */\nexport interface DateFormatOptions {\n /** Name the day of the week too, in the interface language (\"Mo., 01.08.2026\"). A date\n * in a COLUMN carries it; a date in a SENTENCE does not (keksdose dev#546). */\n weekday?: boolean;\n}\n\n/**\n * What a date is rendered with, given the preference and the interface language\n * (`language`: a code or tag — `\"de\"`, `\"en\"`, i18next's `resolvedLanguage`; it is read\n * through `formatLocaleOf`, so English is British and an unshipped tag stays itself).\n *\n * `\"auto\"` keeps the short-month shape (\"01 Aug 2026\", \"01. Aug. 2026\"); every explicit\n * choice is all-numeric, the only way it produces the shape its option promises —\n * `month: \"short\"` under `sv-SE` would print \"2026 aug. 01\", not ISO.\n */\nexport function resolveDateFormat(\n preference: DateFormatPreference,\n language: string | undefined,\n options: DateFormatOptions = {},\n): ResolvedDateFormat {\n const languageLocale = language ? formatLocaleOf(language) : undefined;\n const weekday = options.weekday ? { weekday: { locale: languageLocale } } : {};\n if (preference === \"auto\") {\n return { locale: languageLocale, options: { year: \"numeric\", month: \"short\", day: \"2-digit\" }, ...weekday };\n }\n return { locale: preference, options: { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }, ...weekday };\n}\n\n/** A bare calendar day: no time, no zone. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * `dateText`, already rendered under the preference, with the day's NAME around it in\n * `locale` (the language's). Not \"weekday alone + comma\": German says \"So\" alone and\n * \"So., 06.09.2026\" in company. The full date is formatted in the language and split\n * into parts; what that language puts before the first and after the last day/month/\n * year part (the weekday, its comma) frames the preference's digits — so where the two\n * locales coincide the output is the native string, character for character.\n */\nfunction withWeekday(date: Date, dateText: string, locale: string | undefined, options: Intl.DateTimeFormatOptions) {\n const isDatePart = (p: Intl.DateTimeFormatPart) => p.type === \"day\" || p.type === \"month\" || p.type === \"year\";\n const parts = new Intl.DateTimeFormat(locale, { weekday: \"short\", ...options }).formatToParts(date);\n const first = parts.findIndex(isDatePart);\n if (first === -1) return dateText;\n let last = first;\n parts.forEach((p, i) => {\n if (isDatePart(p)) last = i;\n });\n const join = (ps: Intl.DateTimeFormatPart[]) => ps.map((p) => p.value).join(\"\");\n return join(parts.slice(0, first)) + dateText + join(parts.slice(last + 1));\n}\n\n/**\n * A date under the preference: `\"\"` for no value, the input itself when it does not\n * parse. A bare \"YYYY-MM-DD\" is the calendar day it names in every zone (formatted in\n * UTC, undoing `new Date`'s UTC-midnight parse — west of Greenwich it read a day early);\n * a timestamp is an instant and stays in the reader's zone. The weekday, when asked for,\n * is rendered in the same zone, so a Sunday is never captioned Saturday.\n */\nexport function formatDateWithPreference(\n value: string | Date | null | undefined,\n preference: DateFormatPreference,\n language: string | undefined,\n options: DateFormatOptions = {},\n): string {\n if (!value) return \"\";\n const resolved = resolveDateFormat(preference, language, options);\n const dateOnly = typeof value === \"string\" && DATE_ONLY.test(value);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return String(value);\n const intl: Intl.DateTimeFormatOptions = dateOnly ? { ...resolved.options, timeZone: \"UTC\" } : resolved.options;\n try {\n const text = date.toLocaleDateString(resolved.locale, intl);\n return resolved.weekday ? withWeekday(date, text, resolved.weekday.locale, intl) : text;\n } catch {\n // A language tag `Intl` refuses: the ISO day rather than nothing (keksdose's fallback).\n return date.toISOString().slice(0, 10);\n }\n}\n\n/** The sample an option is labelled with — 1 August 2026 in its shape, the shape being\n * the whole choice (a country name would say nothing about the digit order). */\nexport function dateFormatSample(preference: DateFormatPreference, language: string | undefined): string {\n return formatDateWithPreference(\"2026-08-01\", preference, language);\n}\n\n/**\n * The `<UiKitProvider formatDate>` for a preference — so the kit's own date fields\n * (DatePicker, DateRangePicker, DateMark) write a day as the app's dates do. A day\n * follows the preference, with the weekday where the kit suggests it; a month or a year\n * answers `\"\"` and keeps the kit's rendering in the language (rule 2). Rebuild it when\n * the preference or the language changes (a `useMemo` on both).\n */\nexport function dateFormatterFor(preference: DateFormatPreference, language: string | undefined): KitDateFormatter {\n return (iso, { unit, weekday, locale }) =>\n unit === \"day\" ? formatDateWithPreference(iso, preference, locale ?? language, { weekday }) : \"\";\n}\n"],"mappings":";AAAA,SAAS,sBAAsB;AAoCxB,MAAM,0BAA2D,CAAC,QAAQ,SAAS,SAAS,SAAS,OAAO;AAI5G,SAAS,uBAAuB,OAA+C;AACpF,SAAO,OAAO,UAAU,YAAa,wBAA8C,SAAS,KAAK;AACnG;AA2BO,SAAS,kBACd,YACA,UACA,UAA6B,CAAC,GACV;AACpB,QAAM,iBAAiB,WAAW,eAAe,QAAQ,IAAI;AAC7D,QAAM,UAAU,QAAQ,UAAU,EAAE,SAAS,EAAE,QAAQ,eAAe,EAAE,IAAI,CAAC;AAC7E,MAAI,eAAe,QAAQ;AACzB,WAAO,EAAE,QAAQ,gBAAgB,SAAS,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,UAAU,GAAG,GAAG,QAAQ;AAAA,EAC5G;AACA,SAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,MAAM,WAAW,OAAO,WAAW,KAAK,UAAU,GAAG,GAAG,QAAQ;AAC1G;AAGA,MAAM,YAAY;AAUlB,SAAS,YAAY,MAAY,UAAkB,QAA4B,SAAqC;AAClH,QAAM,aAAa,CAAC,MAA+B,EAAE,SAAS,SAAS,EAAE,SAAS,WAAW,EAAE,SAAS;AACxG,QAAM,QAAQ,IAAI,KAAK,eAAe,QAAQ,EAAE,SAAS,SAAS,GAAG,QAAQ,CAAC,EAAE,cAAc,IAAI;AAClG,QAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,MAAI,UAAU,GAAI,QAAO;AACzB,MAAI,OAAO;AACX,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,QAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC5B,CAAC;AACD,QAAM,OAAO,CAAC,OAAkC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE;AAC9E,SAAO,KAAK,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,WAAW,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;AAC5E;AASO,SAAS,yBACd,OACA,YACA,UACA,UAA6B,CAAC,GACtB;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,WAAW,kBAAkB,YAAY,UAAU,OAAO;AAChE,QAAM,WAAW,OAAO,UAAU,YAAY,UAAU,KAAK,KAAK;AAClE,QAAM,OAAO,OAAO,UAAU,WAAW,IAAI,KAAK,KAAK,IAAI;AAC3D,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO,OAAO,KAAK;AACrD,QAAM,OAAmC,WAAW,EAAE,GAAG,SAAS,SAAS,UAAU,MAAM,IAAI,SAAS;AACxG,MAAI;AACF,UAAM,OAAO,KAAK,mBAAmB,SAAS,QAAQ,IAAI;AAC1D,WAAO,SAAS,UAAU,YAAY,MAAM,MAAM,SAAS,QAAQ,QAAQ,IAAI,IAAI;AAAA,EACrF,QAAQ;AAEN,WAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,EACvC;AACF;AAIO,SAAS,iBAAiB,YAAkC,UAAsC;AACvG,SAAO,yBAAyB,cAAc,YAAY,QAAQ;AACpE;AASO,SAAS,iBAAiB,YAAkC,UAAgD;AACjH,SAAO,CAAC,KAAK,EAAE,MAAM,SAAS,OAAO,MACnC,SAAS,QAAQ,yBAAyB,KAAK,YAAY,UAAU,UAAU,EAAE,QAAQ,CAAC,IAAI;AAClG;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/date-format.ts"],"sourcesContent":["import { formatLocaleOf } from \"../i18n/languages\";\nimport type { KitDateFormatter } from \"../i18n/kit-labels\";\nimport { pad, toLocalIso } from \"./dates\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\n\n/**\n * The date-format preference (0.46, lifted from keksdose's `date-format-store.ts`,\n * feedback #180): how a DAY is written — the order of day, month and year and the\n * separator between them — as a choice of its own, apart from the interface language.\n * \"Read the app in English and still see German dates\", or ISO everywhere.\n *\n * keksdose's rules, which the kit keeps:\n *\n * 1. **A label that identifies a day follows the preference** — anything that prints\n * day, month and year goes through {@link formatDateWithPreference} (or the\n * provider's `formatDate`, {@link dateFormatterFor}, for the kit's own date fields).\n * 2. **A label without a day follows the language**: \"August 2026\", a month or weekday\n * NAME has no order to arrange. {@link dateFormatterFor} answers `\"\"` for a month or a\n * year, so the kit keeps its own rendering in the language.\n * 3. **A clock time follows the language**: `sv-SE` is a statement about digit order in\n * a date, not about a 12- or 24-hour clock.\n * 4. **A weekday's NAME follows the language** (dev#546): `sv-SE` means \"year first,\n * with dashes\", not \"the reader speaks Swedish\" — so with `weekday` the day's name is\n * the language's, joined to the date the preference renders.\n *\n * The preference is the APP's to keep — keksdose holds it per device (a persisted store,\n * not an account column: nothing server-written formats a date for the reader). The kit\n * owns the vocabulary, the resolver, the renderer and the settings field\n * ({@link DateFormatSetting}).\n */\n\n/** `\"auto\"` follows the interface language (the default — nobody's dates move until\n * they ask); the rest are BCP 47 tags chosen for the SHAPE they produce: German\n * 01.08.2026, British 01/08/2026, US 08/01/2026, ISO 2026-08-01. */\nexport type DateFormatPreference = \"auto\" | \"de-DE\" | \"en-GB\" | \"en-US\" | \"sv-SE\";\n\n/** The options in the order the setting lists them: `auto` first (the default), then\n * German → UK → US → ISO. */\nexport const DATE_FORMAT_PREFERENCES: readonly DateFormatPreference[] = [\"auto\", \"de-DE\", \"en-GB\", \"en-US\", \"sv-SE\"];\n\n/** Whether `value` is one of {@link DATE_FORMAT_PREFERENCES} — for a value read back from\n * storage, where anything else falls back to `\"auto\"`. */\nexport function isDateFormatPreference(value: unknown): value is DateFormatPreference {\n return typeof value === \"string\" && (DATE_FORMAT_PREFERENCES as readonly string[]).includes(value);\n}\n\n/** A resolved preference: the locale and `Intl` options the date itself is rendered\n * with, and — only when asked for — the locale the weekday's NAME comes from. */\nexport interface ResolvedDateFormat {\n locale: string | undefined;\n options: Intl.DateTimeFormatOptions;\n /** The language's format locale, present when `weekday` was asked for. */\n weekday?: { locale: string | undefined };\n}\n\n/** What {@link resolveDateFormat} and the renderers take beyond the preference. */\nexport interface DateFormatOptions {\n /** Name the day of the week too, in the interface language (\"Mo., 01.08.2026\"). A date\n * in a COLUMN carries it; a date in a SENTENCE does not (keksdose dev#546). */\n weekday?: boolean;\n /**\n * 0.52: the IANA zone a TIMESTAMP's day and clock are read in (\"Europe/Zurich\") — pass\n * the `<UiKitProvider timeZone>` (`useKitTimeZone()`), so an app's own column says what\n * the kit's components say for the same instant. Left out: the device's. A bare\n * \"YYYY-MM-DD\" ignores it: a calendar day is the day it names in every zone. A zone\n * `Intl` refuses reads as the device's. The renderers read it; {@link resolveDateFormat}\n * does not.\n */\n timeZone?: string;\n}\n\n/**\n * What a date is rendered with, given the preference and the interface language\n * (`language`: a code or tag — `\"de\"`, `\"en\"`, i18next's `resolvedLanguage`; it is read\n * through `formatLocaleOf`, so English is British and an unshipped tag stays itself).\n *\n * `\"auto\"` keeps the short-month shape (\"01 Aug 2026\", \"01. Aug. 2026\"); every explicit\n * choice is all-numeric, the only way it produces the shape its option promises —\n * `month: \"short\"` under `sv-SE` would print \"2026 aug. 01\", not ISO.\n */\nexport function resolveDateFormat(\n preference: DateFormatPreference,\n language: string | undefined,\n options: DateFormatOptions = {},\n): ResolvedDateFormat {\n const languageLocale = language ? formatLocaleOf(language) : undefined;\n const weekday = options.weekday ? { weekday: { locale: languageLocale } } : {};\n if (preference === \"auto\") {\n return { locale: languageLocale, options: { year: \"numeric\", month: \"short\", day: \"2-digit\" }, ...weekday };\n }\n return { locale: preference, options: { year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }, ...weekday };\n}\n\n/**\n * `dateText`, already rendered under the preference, with the day's NAME around it in\n * `locale` (the language's). Not \"weekday alone + comma\": German says \"So\" alone and\n * \"So., 06.09.2026\" in company. The full date is formatted in the language and split\n * into parts; what that language puts before the first and after the last day/month/\n * year part (the weekday, its comma) frames the preference's digits — so where the two\n * locales coincide the output is the native string, character for character.\n */\nfunction withWeekday(date: Date, dateText: string, locale: string | undefined, options: Intl.DateTimeFormatOptions) {\n const isDatePart = (p: Intl.DateTimeFormatPart) => p.type === \"day\" || p.type === \"month\" || p.type === \"year\";\n const parts = dateFormatter(locale, { weekday: \"short\", ...options }).formatToParts(date);\n const first = parts.findIndex(isDatePart);\n if (first === -1) return dateText;\n let last = first;\n parts.forEach((p, i) => {\n if (isDatePart(p)) last = i;\n });\n const join = (ps: Intl.DateTimeFormatPart[]) => ps.map((p) => p.value).join(\"\");\n return join(parts.slice(0, first)) + dateText + join(parts.slice(last + 1));\n}\n\n/**\n * A date under the preference: `\"\"` for no value, the input itself when it does not\n * parse. A bare \"YYYY-MM-DD\" is the calendar day it names in every zone (formatted in\n * UTC, undoing `new Date`'s UTC-midnight parse — west of Greenwich it read a day early);\n * a timestamp is an instant, read in `options.timeZone`, else the reader's zone. The\n * weekday, when asked for, is rendered in the same zone, so a Sunday is never captioned\n * Saturday.\n */\nexport function formatDateWithPreference(\n value: string | Date | null | undefined,\n preference: DateFormatPreference,\n language: string | undefined,\n options: DateFormatOptions = {},\n): string {\n if (!value) return \"\";\n const resolved = resolveDateFormat(preference, language, options);\n const dateOnly = isBareIsoDay(value);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return String(value);\n const render = (timeZone: string | undefined) => {\n const intl: Intl.DateTimeFormatOptions = timeZone ? { ...resolved.options, timeZone } : resolved.options;\n // Through the shared cache (refactor audit 2026-10-30, DUP-2): a `toLocaleDateString`\n // builds a formatter per call, which is why keksdose kept its own copy of this\n // renderer — 392 ms per click on a 270-row register (dev#632).\n const text = dateFormatter(resolved.locale, intl).format(date);\n return resolved.weekday ? withWeekday(date, text, resolved.weekday.locale, intl) : text;\n };\n if (dateOnly) return firstAccepted([() => render(\"UTC\")], () => value);\n // A zone `Intl` refuses: the reader's own day, as `clockTime` reads the reader's own\n // clock — never the UTC day `toISOString` gave, a day off around midnight (0.52 code\n // review, this file).\n return firstAccepted([() => render(options.timeZone), () => render(undefined)], () => toLocalIso(date));\n}\n\n/** The first of `renders` that `Intl` accepts — a language tag or a zone it refuses\n * throws — else `last()`: the ISO day or the clock rather than nothing (keksdose's\n * fallback). */\nfunction firstAccepted(renders: readonly (() => string)[], last: () => string): string {\n for (const render of renders) {\n try {\n return render();\n } catch {\n // Refused: the next.\n }\n }\n return last();\n}\n\n/**\n * The clock time of `date` as the language writes it (\"14:05\"), two digits each: rule 3,\n * a clock follows the language, never the preference — `sv-SE` is a statement about the\n * order of a date's digits, not about a 12- or 24-hour clock. `language` is read through\n * `formatLocaleOf`, so English is the British 24-hour clock, not \"02:05 PM\" in a\n * German household's app. In `timeZone` when given, else the reader's.\n *\n * @internal Shared with the kit's own day formatter; not part of the barrel.\n */\nexport function clockTime(date: Date, language: string | undefined, timeZone?: string): string {\n const locale = language ? formatLocaleOf(language) : undefined;\n const clock = (zone: string | undefined) =>\n dateFormatter(locale, { hour: \"2-digit\", minute: \"2-digit\", ...(zone ? { timeZone: zone } : null) }).format(date);\n // A zone `Intl` refuses: the reader's own clock, as `isoDayIn` reads the reader's own\n // day — the UTC clock `toISOString` gave printed 13:05 for 14:05 in Zurich, an hour or\n // more off with no error (0.52 code review, this file).\n const reader = () => `${pad(date.getHours())}:${pad(date.getMinutes())}`;\n return firstAccepted([() => clock(timeZone), () => clock(undefined)], reader);\n}\n\n/**\n * A timestamp under the preference (0.52, keksdose's `formatDateTimeFor`, refactor audit\n * 2026-10-30, DUP-2): the date as {@link formatDateWithPreference} writes it, weekday\n * when asked for, then the clock time in the LANGUAGE ({@link clockTime}): \"01.08.2026,\n * 14:05\", \"Sa., 01.08.2026, 14:05\". keksdose's notification inbox and admin scheduler\n * were where an explicit ISO choice did nothing until it wrote this. `\"\"` for no value,\n * the input itself when it does not parse; a bare \"YYYY-MM-DD\" names no moment, so it\n * prints as the date alone.\n *\n * Day and clock in `options.timeZone` — pass the provider's (`useKitTimeZone()`): without\n * it they were the device's, and an app on Zurich time read from New York printed 09:05\n * in its own column where the kit's inbox printed 14:05 for the same instant (0.52 code\n * review, this file).\n */\nexport function formatDateTimeWithPreference(\n value: string | Date | null | undefined,\n preference: DateFormatPreference,\n language: string | undefined,\n options: DateFormatOptions = {},\n): string {\n if (!value) return \"\";\n if (isBareIsoDay(value)) return formatDateWithPreference(value, preference, language, options);\n const date = typeof value === \"string\" ? new Date(value) : value;\n if (Number.isNaN(date.getTime())) return String(value);\n return `${formatDateWithPreference(date, preference, language, options)}, ${clockTime(date, language, options.timeZone)}`;\n}\n\n/** The sample an option is labelled with — 1 August 2026 in its shape, the shape being\n * the whole choice (a country name would say nothing about the digit order). */\nexport function dateFormatSample(preference: DateFormatPreference, language: string | undefined): string {\n return formatDateWithPreference(\"2026-08-01\", preference, language);\n}\n\n/**\n * The `<UiKitProvider formatDate>` for a preference — so the kit's own date fields\n * (DatePicker, DateRangePicker, DateMark) write a day as the app's dates do. A day\n * follows the preference, with the weekday where the kit suggests it; a month or a year\n * answers `\"\"` and keeps the kit's rendering in the language (rule 2). Rebuild it when\n * the preference or the language changes (a `useMemo` on both).\n */\nexport function dateFormatterFor(preference: DateFormatPreference, language: string | undefined): KitDateFormatter {\n return (iso, { unit, weekday, locale }) =>\n unit === \"day\" ? formatDateWithPreference(iso, preference, locale ?? language, { weekday }) : \"\";\n}\n"],"mappings":";AAAA,SAAS,sBAAsB;AAE/B,SAAS,KAAK,kBAAkB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAmCtB,MAAM,0BAA2D,CAAC,QAAQ,SAAS,SAAS,SAAS,OAAO;AAI5G,SAAS,uBAAuB,OAA+C;AACpF,SAAO,OAAO,UAAU,YAAa,wBAA8C,SAAS,KAAK;AACnG;AAoCO,SAAS,kBACd,YACA,UACA,UAA6B,CAAC,GACV;AACpB,QAAM,iBAAiB,WAAW,eAAe,QAAQ,IAAI;AAC7D,QAAM,UAAU,QAAQ,UAAU,EAAE,SAAS,EAAE,QAAQ,eAAe,EAAE,IAAI,CAAC;AAC7E,MAAI,eAAe,QAAQ;AACzB,WAAO,EAAE,QAAQ,gBAAgB,SAAS,EAAE,MAAM,WAAW,OAAO,SAAS,KAAK,UAAU,GAAG,GAAG,QAAQ;AAAA,EAC5G;AACA,SAAO,EAAE,QAAQ,YAAY,SAAS,EAAE,MAAM,WAAW,OAAO,WAAW,KAAK,UAAU,GAAG,GAAG,QAAQ;AAC1G;AAUA,SAAS,YAAY,MAAY,UAAkB,QAA4B,SAAqC;AAClH,QAAM,aAAa,CAAC,MAA+B,EAAE,SAAS,SAAS,EAAE,SAAS,WAAW,EAAE,SAAS;AACxG,QAAM,QAAQ,cAAc,QAAQ,EAAE,SAAS,SAAS,GAAG,QAAQ,CAAC,EAAE,cAAc,IAAI;AACxF,QAAM,QAAQ,MAAM,UAAU,UAAU;AACxC,MAAI,UAAU,GAAI,QAAO;AACzB,MAAI,OAAO;AACX,QAAM,QAAQ,CAAC,GAAG,MAAM;AACtB,QAAI,WAAW,CAAC,EAAG,QAAO;AAAA,EAC5B,CAAC;AACD,QAAM,OAAO,CAAC,OAAkC,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE;AAC9E,SAAO,KAAK,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,WAAW,KAAK,MAAM,MAAM,OAAO,CAAC,CAAC;AAC5E;AAUO,SAAS,yBACd,OACA,YACA,UACA,UAA6B,CAAC,GACtB;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,WAAW,kBAAkB,YAAY,UAAU,OAAO;AAChE,QAAM,WAAW,aAAa,KAAK;AACnC,QAAM,OAAO,OAAO,UAAU,WAAW,IAAI,KAAK,KAAK,IAAI;AAC3D,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO,OAAO,KAAK;AACrD,QAAM,SAAS,CAAC,aAAiC;AAC/C,UAAM,OAAmC,WAAW,EAAE,GAAG,SAAS,SAAS,SAAS,IAAI,SAAS;AAIjG,UAAM,OAAO,cAAc,SAAS,QAAQ,IAAI,EAAE,OAAO,IAAI;AAC7D,WAAO,SAAS,UAAU,YAAY,MAAM,MAAM,SAAS,QAAQ,QAAQ,IAAI,IAAI;AAAA,EACrF;AACA,MAAI,SAAU,QAAO,cAAc,CAAC,MAAM,OAAO,KAAK,CAAC,GAAG,MAAM,KAAK;AAIrE,SAAO,cAAc,CAAC,MAAM,OAAO,QAAQ,QAAQ,GAAG,MAAM,OAAO,MAAS,CAAC,GAAG,MAAM,WAAW,IAAI,CAAC;AACxG;AAKA,SAAS,cAAc,SAAoC,MAA4B;AACrF,aAAW,UAAU,SAAS;AAC5B,QAAI;AACF,aAAO,OAAO;AAAA,IAChB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,KAAK;AACd;AAWO,SAAS,UAAU,MAAY,UAA8B,UAA2B;AAC7F,QAAM,SAAS,WAAW,eAAe,QAAQ,IAAI;AACrD,QAAM,QAAQ,CAAC,SACb,cAAc,QAAQ,EAAE,MAAM,WAAW,QAAQ,WAAW,GAAI,OAAO,EAAE,UAAU,KAAK,IAAI,KAAM,CAAC,EAAE,OAAO,IAAI;AAIlH,QAAM,SAAS,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC;AACtE,SAAO,cAAc,CAAC,MAAM,MAAM,QAAQ,GAAG,MAAM,MAAM,MAAS,CAAC,GAAG,MAAM;AAC9E;AAgBO,SAAS,6BACd,OACA,YACA,UACA,UAA6B,CAAC,GACtB;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,aAAa,KAAK,EAAG,QAAO,yBAAyB,OAAO,YAAY,UAAU,OAAO;AAC7F,QAAM,OAAO,OAAO,UAAU,WAAW,IAAI,KAAK,KAAK,IAAI;AAC3D,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO,OAAO,KAAK;AACrD,SAAO,GAAG,yBAAyB,MAAM,YAAY,UAAU,OAAO,CAAC,KAAK,UAAU,MAAM,UAAU,QAAQ,QAAQ,CAAC;AACzH;AAIO,SAAS,iBAAiB,YAAkC,UAAsC;AACvG,SAAO,yBAAyB,cAAc,YAAY,QAAQ;AACpE;AASO,SAAS,iBAAiB,YAAkC,UAAgD;AACjH,SAAO,CAAC,KAAK,EAAE,MAAM,SAAS,OAAO,MACnC,SAAS,QAAQ,yBAAyB,KAAK,YAAY,UAAU,UAAU,EAAE,QAAQ,CAAC,IAAI;AAClG;","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type DateInput, type FormatDateStyle } from "./format.js";
|
|
2
|
+
/**
|
|
3
|
+
* How a kit component prints a day — ONE ladder (refactor audit 2026-10-30, DUP-2):
|
|
4
|
+
*
|
|
5
|
+
* 1. the component's own prop (`formatDate`, `renderDate`), when the app gave one and it
|
|
6
|
+
* answers;
|
|
7
|
+
* 2. else the `<UiKitProvider formatDate>` (keksdose K12: the app's date-format
|
|
8
|
+
* preference), asked for the calendar day with `source: "dateMark"`;
|
|
9
|
+
* 3. else the kit's `formatDate` in the provider's locale AND `timeZone` (0.46).
|
|
10
|
+
*
|
|
11
|
+
* 0.46's rule — "a label that identifies a day follows the preference" — held only where
|
|
12
|
+
* a component wrote that ladder itself: four copies (admin's `useDayText`, AccountStateChip,
|
|
13
|
+
* DateMark, the feedback table), while about twelve components took their own prop or
|
|
14
|
+
* nothing and went straight to `lib/format`. keksdose set the provider's `formatDate` and
|
|
15
|
+
* still passed it into six components by hand; and the 10 that imported the raw
|
|
16
|
+
* `formatDate` ignored the provider's `timeZone`.
|
|
17
|
+
*
|
|
18
|
+
* @internal Not part of the barrel: the components' props are the public way in.
|
|
19
|
+
*/
|
|
20
|
+
/** What a {@link DayFormatter} call may ask for. */
|
|
21
|
+
export interface DayTextOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Name the day of the week — what the provider's formatter is told (its `weekday`).
|
|
24
|
+
* keksdose's rule: a date in a COLUMN carries it, a date in a SENTENCE does not
|
|
25
|
+
* (dev#546). Default `false`. The kit's own rendering follows `style`, not this.
|
|
26
|
+
*/
|
|
27
|
+
weekday?: boolean;
|
|
28
|
+
/** Add the clock time, in the language (date-format rule 3): a timestamp. Default false. */
|
|
29
|
+
time?: boolean;
|
|
30
|
+
/** The kit's own rendering (step 3). Default `"medium"`, `"dateTime"` with `time`. */
|
|
31
|
+
style?: FormatDateStyle | Intl.DateTimeFormatOptions;
|
|
32
|
+
/** `false`: skip the provider (step 2) — a component's explicit style wins over it,
|
|
33
|
+
* as `DateMark`'s `dateStyle` does. Default `true`. */
|
|
34
|
+
preference?: boolean;
|
|
35
|
+
/** Printed for no value. Default "—". */
|
|
36
|
+
empty?: string;
|
|
37
|
+
}
|
|
38
|
+
/** A component's day printer: a value in, its text out. */
|
|
39
|
+
export type DayFormatter<V extends DateInput = DateInput> = (value: V, options?: DayTextOptions) => string;
|
|
40
|
+
/**
|
|
41
|
+
* The component's day printer, bound to its `prop`, the provider's `formatDate`, locale
|
|
42
|
+
* and `timeZone`. `prop` is called with the value as the component passed it, and wins
|
|
43
|
+
* whenever it answers non-empty. A value that does not parse is printed as it came
|
|
44
|
+
* (a bug upstream is better seen than hidden behind a dash).
|
|
45
|
+
*/
|
|
46
|
+
export declare function useDayFormatter<V extends DateInput = DateInput>(prop?: ((value: V) => string) | null, localeProp?: string): DayFormatter<V>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useKitDateFormatter, useKitLocale, useKitTimeZone } from "../i18n/kit-labels.js";
|
|
4
|
+
import { toLocalIso } from "./dates.js";
|
|
5
|
+
import { clockTime } from "./date-format.js";
|
|
6
|
+
import { EMPTY_FORMATTED_VALUE, formatDate, toDate } from "./format.js";
|
|
7
|
+
import { dateFormatter } from "./intl-cache.js";
|
|
8
|
+
import { isBareIsoDay } from "./iso-day.js";
|
|
9
|
+
function isoDayIn(date, timeZone) {
|
|
10
|
+
if (!timeZone) return toLocalIso(date);
|
|
11
|
+
try {
|
|
12
|
+
const parts = dateFormatter("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(
|
|
13
|
+
date
|
|
14
|
+
);
|
|
15
|
+
const part = (type) => parts.find((p) => p.type === type)?.value ?? "";
|
|
16
|
+
return `${part("year")}-${part("month")}-${part("day")}`;
|
|
17
|
+
} catch {
|
|
18
|
+
return toLocalIso(date);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function useDayFormatter(prop, localeProp) {
|
|
22
|
+
const locale = useKitLocale(localeProp);
|
|
23
|
+
const timeZone = useKitTimeZone();
|
|
24
|
+
const fromProvider = useKitDateFormatter();
|
|
25
|
+
return useCallback(
|
|
26
|
+
(value, options = {}) => {
|
|
27
|
+
const own = prop?.(value);
|
|
28
|
+
if (own) return own;
|
|
29
|
+
if (value === null || value === void 0 || value === "") return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
30
|
+
const date = toDate(value);
|
|
31
|
+
if (!date) return String(value);
|
|
32
|
+
const bareDay = isBareIsoDay(value);
|
|
33
|
+
if (fromProvider && options.preference !== false) {
|
|
34
|
+
const iso = bareDay ? value : isoDayIn(date, timeZone);
|
|
35
|
+
const day = fromProvider(iso, { unit: "day", source: "dateMark", locale, weekday: options.weekday ?? false });
|
|
36
|
+
if (day) return options.time && !bareDay ? `${day}, ${clockTime(date, locale, timeZone)}` : day;
|
|
37
|
+
}
|
|
38
|
+
return formatDate(value, options.style ?? (options.time ? "dateTime" : "medium"), { locale, timeZone });
|
|
39
|
+
},
|
|
40
|
+
[prop, fromProvider, locale, timeZone]
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
useDayFormatter
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=day-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/day-format.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useKitDateFormatter, useKitLocale, useKitTimeZone } from \"../i18n/kit-labels\";\nimport { toLocalIso } from \"./dates\";\nimport { clockTime } from \"./date-format\";\nimport { EMPTY_FORMATTED_VALUE, formatDate, toDate, type DateInput, type FormatDateStyle } from \"./format\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\n\n/**\n * How a kit component prints a day — ONE ladder (refactor audit 2026-10-30, DUP-2):\n *\n * 1. the component's own prop (`formatDate`, `renderDate`), when the app gave one and it\n * answers;\n * 2. else the `<UiKitProvider formatDate>` (keksdose K12: the app's date-format\n * preference), asked for the calendar day with `source: \"dateMark\"`;\n * 3. else the kit's `formatDate` in the provider's locale AND `timeZone` (0.46).\n *\n * 0.46's rule — \"a label that identifies a day follows the preference\" — held only where\n * a component wrote that ladder itself: four copies (admin's `useDayText`, AccountStateChip,\n * DateMark, the feedback table), while about twelve components took their own prop or\n * nothing and went straight to `lib/format`. keksdose set the provider's `formatDate` and\n * still passed it into six components by hand; and the 10 that imported the raw\n * `formatDate` ignored the provider's `timeZone`.\n *\n * @internal Not part of the barrel: the components' props are the public way in.\n */\n\n/** What a {@link DayFormatter} call may ask for. */\nexport interface DayTextOptions {\n /**\n * Name the day of the week — what the provider's formatter is told (its `weekday`).\n * keksdose's rule: a date in a COLUMN carries it, a date in a SENTENCE does not\n * (dev#546). Default `false`. The kit's own rendering follows `style`, not this.\n */\n weekday?: boolean;\n /** Add the clock time, in the language (date-format rule 3): a timestamp. Default false. */\n time?: boolean;\n /** The kit's own rendering (step 3). Default `\"medium\"`, `\"dateTime\"` with `time`. */\n style?: FormatDateStyle | Intl.DateTimeFormatOptions;\n /** `false`: skip the provider (step 2) — a component's explicit style wins over it,\n * as `DateMark`'s `dateStyle` does. Default `true`. */\n preference?: boolean;\n /** Printed for no value. Default \"—\". */\n empty?: string;\n}\n\n/** A component's day printer: a value in, its text out. */\nexport type DayFormatter<V extends DateInput = DateInput> = (value: V, options?: DayTextOptions) => string;\n\n/** The calendar day `date` falls on in `timeZone`, as \"YYYY-MM-DD\" — so the day the\n * provider is asked for is the day the kit's own rendering would print. */\nfunction isoDayIn(date: Date, timeZone: string | undefined): string {\n if (!timeZone) return toLocalIso(date);\n try {\n // `en-CA` writes YYYY-MM-DD with Latin digits.\n const parts = dateFormatter(\"en-CA\", { timeZone, year: \"numeric\", month: \"2-digit\", day: \"2-digit\" }).formatToParts(\n date,\n );\n const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value ?? \"\";\n return `${part(\"year\")}-${part(\"month\")}-${part(\"day\")}`;\n } catch {\n return toLocalIso(date);\n }\n}\n\n/**\n * The component's day printer, bound to its `prop`, the provider's `formatDate`, locale\n * and `timeZone`. `prop` is called with the value as the component passed it, and wins\n * whenever it answers non-empty. A value that does not parse is printed as it came\n * (a bug upstream is better seen than hidden behind a dash).\n */\nexport function useDayFormatter<V extends DateInput = DateInput>(\n prop?: ((value: V) => string) | null,\n localeProp?: string,\n): DayFormatter<V> {\n const locale = useKitLocale(localeProp);\n const timeZone = useKitTimeZone();\n const fromProvider = useKitDateFormatter();\n return useCallback<DayFormatter<V>>(\n (value, options = {}) => {\n const own = prop?.(value);\n if (own) return own;\n if (value === null || value === undefined || value === \"\") return options.empty ?? EMPTY_FORMATTED_VALUE;\n const date = toDate(value);\n if (!date) return String(value);\n const bareDay = isBareIsoDay(value);\n if (fromProvider && options.preference !== false) {\n const iso = bareDay ? value : isoDayIn(date, timeZone);\n const day = fromProvider(iso, { unit: \"day\", source: \"dateMark\", locale, weekday: options.weekday ?? false });\n if (day) return options.time && !bareDay ? `${day}, ${clockTime(date, locale, timeZone)}` : day;\n }\n return formatDate(value, options.style ?? (options.time ? \"dateTime\" : \"medium\"), { locale, timeZone });\n },\n [prop, fromProvider, locale, timeZone],\n );\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB,cAAc,sBAAsB;AAClE,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB,YAAY,cAAoD;AAChG,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AA6C7B,SAAS,SAAS,MAAY,UAAsC;AAClE,MAAI,CAAC,SAAU,QAAO,WAAW,IAAI;AACrC,MAAI;AAEF,UAAM,QAAQ,cAAc,SAAS,EAAE,UAAU,MAAM,WAAW,OAAO,WAAW,KAAK,UAAU,CAAC,EAAE;AAAA,MACpG;AAAA,IACF;AACA,UAAM,OAAO,CAAC,SAAuC,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI,GAAG,SAAS;AAClG,WAAO,GAAG,KAAK,MAAM,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC;AAAA,EACxD,QAAQ;AACN,WAAO,WAAW,IAAI;AAAA,EACxB;AACF;AAQO,SAAS,gBACd,MACA,YACiB;AACjB,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,WAAW,eAAe;AAChC,QAAM,eAAe,oBAAoB;AACzC,SAAO;AAAA,IACL,CAAC,OAAO,UAAU,CAAC,MAAM;AACvB,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,IAAK,QAAO;AAChB,UAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,GAAI,QAAO,QAAQ,SAAS;AACnF,YAAM,OAAO,OAAO,KAAK;AACzB,UAAI,CAAC,KAAM,QAAO,OAAO,KAAK;AAC9B,YAAM,UAAU,aAAa,KAAK;AAClC,UAAI,gBAAgB,QAAQ,eAAe,OAAO;AAChD,cAAM,MAAM,UAAU,QAAQ,SAAS,MAAM,QAAQ;AACrD,cAAM,MAAM,aAAa,KAAK,EAAE,MAAM,OAAO,QAAQ,YAAY,QAAQ,SAAS,QAAQ,WAAW,MAAM,CAAC;AAC5G,YAAI,IAAK,QAAO,QAAQ,QAAQ,CAAC,UAAU,GAAG,GAAG,KAAK,UAAU,MAAM,QAAQ,QAAQ,CAAC,KAAK;AAAA,MAC9F;AACA,aAAO,WAAW,OAAO,QAAQ,UAAU,QAAQ,OAAO,aAAa,WAAW,EAAE,QAAQ,SAAS,CAAC;AAAA,IACxG;AAAA,IACA,CAAC,MAAM,cAAc,QAAQ,QAAQ;AAAA,EACvC;AACF;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The failure ladder, once: the app's words for a failure when it has any, else the
|
|
4
|
+
* kit's `fallback`.
|
|
5
|
+
*
|
|
6
|
+
* "Any" is {@link hasContent}, so an app's `describeError` answering `""` (nothing to say
|
|
7
|
+
* about this one) gets the kit's sentence. Written as `own ?? fallback` in the auth forms,
|
|
8
|
+
* where `""` printed an empty alert, and both ways within one file in the reviewer scope
|
|
9
|
+
* editor; it lived in the notification cards' parts only (refactor audit 2026-10-30,
|
|
10
|
+
* DUP-14).
|
|
11
|
+
*
|
|
12
|
+
* @internal Package-private — not re-exported from the barrel.
|
|
13
|
+
*/
|
|
14
|
+
export declare function describeFailure(own: ReactNode | undefined, fallback: ReactNode): ReactNode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/describe-failure.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { hasContent } from \"./presence\";\n\n/**\n * The failure ladder, once: the app's words for a failure when it has any, else the\n * kit's `fallback`.\n *\n * \"Any\" is {@link hasContent}, so an app's `describeError` answering `\"\"` (nothing to say\n * about this one) gets the kit's sentence. Written as `own ?? fallback` in the auth forms,\n * where `\"\"` printed an empty alert, and both ways within one file in the reviewer scope\n * editor; it lived in the notification cards' parts only (refactor audit 2026-10-30,\n * DUP-14).\n *\n * @internal Package-private — not re-exported from the barrel.\n */\nexport function describeFailure(own: ReactNode | undefined, fallback: ReactNode): ReactNode {\n return hasContent(own) ? own : fallback;\n}\n"],"mappings":";AACA,SAAS,kBAAkB;AAcpB,SAAS,gBAAgB,KAA4B,UAAgC;AAC1F,SAAO,WAAW,GAAG,IAAI,MAAM;AACjC;","names":[]}
|
package/dist/lib/direction.d.ts
CHANGED
|
@@ -20,3 +20,19 @@ export declare function isRtl(el: Element | null | undefined): boolean;
|
|
|
20
20
|
* any other key. ArrowRight is forward in LTR and backward in RTL.
|
|
21
21
|
*/
|
|
22
22
|
export declare function horizontalStep(key: string, el: Element | null | undefined): 1 | -1 | 0;
|
|
23
|
+
/**
|
|
24
|
+
* Where a key moves the focus in a list with ONE tab stop (a roving tabindex): the index
|
|
25
|
+
* to focus from `at` among `count` items, or `undefined` for a key that moves nothing.
|
|
26
|
+
* ←/→ along `dir` — → is next in `ltr` and previous in `rtl` — ↓/↑ as next and previous,
|
|
27
|
+
* Home and End to the ends. With `wrap`, past the last is the first and before the first
|
|
28
|
+
* the last (a ring: PieChart's slices); without, the ends hold (a plane: Treemap's tiles).
|
|
29
|
+
*
|
|
30
|
+
* The charts each mapped the keys themselves, and differently at the ends (0.52 code
|
|
31
|
+
* review of treemap.tsx, finding 9); the end rule is now a word at the call.
|
|
32
|
+
*
|
|
33
|
+
* `dir` and not an element to read it off (0.52 code review, deep, charts, findings 1 and
|
|
34
|
+
* 14): a list in the page's flow takes the page's (`dirOf(el)`), but a plot is laid out
|
|
35
|
+
* left to right in any page (see `ChartContainer`) and passes `"ltr"` — read off the
|
|
36
|
+
* element, Treemap's → walked an RTL page's tiles backwards, against the picture.
|
|
37
|
+
*/
|
|
38
|
+
export declare function rovingTarget(key: string, dir: Direction, at: number, count: number, wrap: boolean): number | undefined;
|
package/dist/lib/direction.js
CHANGED
|
@@ -11,9 +11,17 @@ function horizontalStep(key, el) {
|
|
|
11
11
|
const forward = key === "ArrowRight" !== isRtl(el);
|
|
12
12
|
return forward ? 1 : -1;
|
|
13
13
|
}
|
|
14
|
+
function rovingTarget(key, dir, at, count, wrap) {
|
|
15
|
+
const physical = key === "ArrowRight" ? 1 : key === "ArrowLeft" ? -1 : 0;
|
|
16
|
+
const step = dir === "rtl" ? -physical : physical;
|
|
17
|
+
const to = step ? at + step : key === "ArrowDown" ? at + 1 : key === "ArrowUp" ? at - 1 : key === "Home" ? 0 : key === "End" ? count - 1 : void 0;
|
|
18
|
+
if (to === void 0 || count <= 0) return void 0;
|
|
19
|
+
return wrap ? (to % count + count) % count : Math.max(0, Math.min(count - 1, to));
|
|
20
|
+
}
|
|
14
21
|
export {
|
|
15
22
|
dirOf,
|
|
16
23
|
horizontalStep,
|
|
17
|
-
isRtl
|
|
24
|
+
isRtl,
|
|
25
|
+
rovingTarget
|
|
18
26
|
};
|
|
19
27
|
//# sourceMappingURL=direction.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/direction.ts"],"sourcesContent":["/**\n * Reading direction, as the DOM has it — internal, not exported from the barrel.\n *\n * The kit lays itself out with logical classes (`ps-*`, `end-*`, `text-start`), which\n * need no JavaScript. These helpers are for the two things CSS cannot do:\n *\n * - **Arrow keys.** ←/→ mean previous/next in LTR and the reverse in RTL. Resolve the\n * key through {@link horizontalStep} instead of comparing to \"ArrowRight\".\n * - **Portals.** A panel portalled to `document.body` leaves the subtree whose `dir`\n * it inherited. Read the anchor's direction with {@link dirOf} and put it on the\n * portalled root, and position it from the anchor's START edge, not `rect.left`.\n */\n\nexport type Direction = \"ltr\" | \"rtl\";\n\n/** The effective direction at `el`: its nearest `[dir]` ancestor (or itself), else the\n * document's, else LTR. Attribute-based on purpose — jsdom computes no `direction`. */\nexport function dirOf(el: Element | null | undefined): Direction {\n const source = el?.closest(\"[dir]\") ?? (typeof document !== \"undefined\" ? document.documentElement : null);\n return source?.getAttribute(\"dir\") === \"rtl\" ? \"rtl\" : \"ltr\";\n}\n\nexport function isRtl(el: Element | null | undefined): boolean {\n return dirOf(el) === \"rtl\";\n}\n\n/**\n * +1 for a key that moves FORWARD along the reading direction, -1 for backward, 0 for\n * any other key. ArrowRight is forward in LTR and backward in RTL.\n */\nexport function horizontalStep(key: string, el: Element | null | undefined): 1 | -1 | 0 {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = (key === \"ArrowRight\") !== isRtl(el);\n return forward ? 1 : -1;\n}\n"],"mappings":";AAiBO,SAAS,MAAM,IAA2C;AAC/D,QAAM,SAAS,IAAI,QAAQ,OAAO,MAAM,OAAO,aAAa,cAAc,SAAS,kBAAkB;AACrG,SAAO,QAAQ,aAAa,KAAK,MAAM,QAAQ,QAAQ;AACzD;AAEO,SAAS,MAAM,IAAyC;AAC7D,SAAO,MAAM,EAAE,MAAM;AACvB;AAMO,SAAS,eAAe,KAAa,IAA4C;AACtF,MAAI,QAAQ,gBAAgB,QAAQ,YAAa,QAAO;AACxD,QAAM,UAAW,QAAQ,iBAAkB,MAAM,EAAE;AACnD,SAAO,UAAU,IAAI;AACvB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/direction.ts"],"sourcesContent":["/**\n * Reading direction, as the DOM has it — internal, not exported from the barrel.\n *\n * The kit lays itself out with logical classes (`ps-*`, `end-*`, `text-start`), which\n * need no JavaScript. These helpers are for the two things CSS cannot do:\n *\n * - **Arrow keys.** ←/→ mean previous/next in LTR and the reverse in RTL. Resolve the\n * key through {@link horizontalStep} instead of comparing to \"ArrowRight\".\n * - **Portals.** A panel portalled to `document.body` leaves the subtree whose `dir`\n * it inherited. Read the anchor's direction with {@link dirOf} and put it on the\n * portalled root, and position it from the anchor's START edge, not `rect.left`.\n */\n\nexport type Direction = \"ltr\" | \"rtl\";\n\n/** The effective direction at `el`: its nearest `[dir]` ancestor (or itself), else the\n * document's, else LTR. Attribute-based on purpose — jsdom computes no `direction`. */\nexport function dirOf(el: Element | null | undefined): Direction {\n const source = el?.closest(\"[dir]\") ?? (typeof document !== \"undefined\" ? document.documentElement : null);\n return source?.getAttribute(\"dir\") === \"rtl\" ? \"rtl\" : \"ltr\";\n}\n\nexport function isRtl(el: Element | null | undefined): boolean {\n return dirOf(el) === \"rtl\";\n}\n\n/**\n * +1 for a key that moves FORWARD along the reading direction, -1 for backward, 0 for\n * any other key. ArrowRight is forward in LTR and backward in RTL.\n */\nexport function horizontalStep(key: string, el: Element | null | undefined): 1 | -1 | 0 {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = (key === \"ArrowRight\") !== isRtl(el);\n return forward ? 1 : -1;\n}\n\n/**\n * Where a key moves the focus in a list with ONE tab stop (a roving tabindex): the index\n * to focus from `at` among `count` items, or `undefined` for a key that moves nothing.\n * ←/→ along `dir` — → is next in `ltr` and previous in `rtl` — ↓/↑ as next and previous,\n * Home and End to the ends. With `wrap`, past the last is the first and before the first\n * the last (a ring: PieChart's slices); without, the ends hold (a plane: Treemap's tiles).\n *\n * The charts each mapped the keys themselves, and differently at the ends (0.52 code\n * review of treemap.tsx, finding 9); the end rule is now a word at the call.\n *\n * `dir` and not an element to read it off (0.52 code review, deep, charts, findings 1 and\n * 14): a list in the page's flow takes the page's (`dirOf(el)`), but a plot is laid out\n * left to right in any page (see `ChartContainer`) and passes `\"ltr\"` — read off the\n * element, Treemap's → walked an RTL page's tiles backwards, against the picture.\n */\nexport function rovingTarget(\n key: string,\n dir: Direction,\n at: number,\n count: number,\n wrap: boolean,\n): number | undefined {\n const physical = key === \"ArrowRight\" ? 1 : key === \"ArrowLeft\" ? -1 : 0;\n const step = dir === \"rtl\" ? -physical : physical;\n const to = step\n ? at + step\n : key === \"ArrowDown\"\n ? at + 1\n : key === \"ArrowUp\"\n ? at - 1\n : key === \"Home\"\n ? 0\n : key === \"End\"\n ? count - 1\n : undefined;\n if (to === undefined || count <= 0) return undefined;\n return wrap ? ((to % count) + count) % count : Math.max(0, Math.min(count - 1, to));\n}\n"],"mappings":";AAiBO,SAAS,MAAM,IAA2C;AAC/D,QAAM,SAAS,IAAI,QAAQ,OAAO,MAAM,OAAO,aAAa,cAAc,SAAS,kBAAkB;AACrG,SAAO,QAAQ,aAAa,KAAK,MAAM,QAAQ,QAAQ;AACzD;AAEO,SAAS,MAAM,IAAyC;AAC7D,SAAO,MAAM,EAAE,MAAM;AACvB;AAMO,SAAS,eAAe,KAAa,IAA4C;AACtF,MAAI,QAAQ,gBAAgB,QAAQ,YAAa,QAAO;AACxD,QAAM,UAAW,QAAQ,iBAAkB,MAAM,EAAE;AACnD,SAAO,UAAU,IAAI;AACvB;AAiBO,SAAS,aACd,KACA,KACA,IACA,OACA,MACoB;AACpB,QAAM,WAAW,QAAQ,eAAe,IAAI,QAAQ,cAAc,KAAK;AACvE,QAAM,OAAO,QAAQ,QAAQ,CAAC,WAAW;AACzC,QAAM,KAAK,OACP,KAAK,OACL,QAAQ,cACN,KAAK,IACL,QAAQ,YACN,KAAK,IACL,QAAQ,SACN,IACA,QAAQ,QACN,QAAQ,IACR;AACZ,MAAI,OAAO,UAAa,SAAS,EAAG,QAAO;AAC3C,SAAO,QAAS,KAAK,QAAS,SAAS,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,GAAG,EAAE,CAAC;AACpF;","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface TabStopOptions {
|
|
2
|
+
/** Whether `el` itself counts, first, when it is a stop. Default true: the tour's
|
|
3
|
+
* target may BE the field (refactor audit 2026-10-30, DUP-9). False for "a control
|
|
4
|
+
* inside it" — a row, a drawer, a trap's container, a toolbar. */
|
|
5
|
+
includeSelf?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** The tab stops in `el`, in DOM order — `el` itself first when it is one, unless
|
|
8
|
+
* `includeSelf: false`. */
|
|
9
|
+
export declare function tabStops(el: Element, { includeSelf }?: TabStopOptions): HTMLElement[];
|
|
10
|
+
/** The first of {@link tabStops} — the control a person types into or presses — or null. */
|
|
11
|
+
export declare function firstTabStop(el: Element, options?: TabStopOptions): HTMLElement | null;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the focus has fallen out of the page: onto `<body>`, onto nothing, or onto a
|
|
14
|
+
* node no longer in the document. False on the server, where nothing has focus to lose.
|
|
15
|
+
* A recovery asks this first: focus that went somewhere real (a dialog, the next field,
|
|
16
|
+
* an app's own `focus()`) is the user's or the app's, and is left where it is.
|
|
17
|
+
*/
|
|
18
|
+
export declare function focusIsLost(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Calls `then` when a `blur`/`focusout` was the user's own move to nothing — a click on
|
|
21
|
+
* the page's background, or a click Safari answers by focusing nothing (it focuses no
|
|
22
|
+
* button or link on a click, and blurs what had the focus) — after which a note of where
|
|
23
|
+
* the focus was is stale (0.52 code review: DataTable's row, BulkActionBar's and
|
|
24
|
+
* AlertBanner's notes outlived it, and a removal much later pulled the focus, and the
|
|
25
|
+
* scroll, back to them).
|
|
26
|
+
*
|
|
27
|
+
* No element took the focus, the document holds none, and the node that lost it could
|
|
28
|
+
* still hold it. Not when it was removed, hidden, made inert or disabled: that is the page
|
|
29
|
+
* taking the focus away — the very case those notes are kept for. Not a window losing the
|
|
30
|
+
* focus either, which leaves the node the document's active element. Decided a microtask
|
|
31
|
+
* later, once the event's own work is done: a browser that blurs a node it is removing
|
|
32
|
+
* (or hiding) may do it while the node is still in place, and only after it is gone is
|
|
33
|
+
* that told apart from a click.
|
|
34
|
+
*/
|
|
35
|
+
export declare function whenFocusLeftForNothing(e: {
|
|
36
|
+
target: EventTarget | null;
|
|
37
|
+
relatedTarget: EventTarget | null;
|
|
38
|
+
}, then: () => void): void;
|
|
39
|
+
/**
|
|
40
|
+
* The tab stops around `frame`, outside it, nearest first on each side — where focus
|
|
41
|
+
* goes once `frame` is gone. Read BEFORE it goes: a removed node has no place in the
|
|
42
|
+
* document to measure from. `limit` per side keeps a long page's stops out of memory.
|
|
43
|
+
*
|
|
44
|
+
* Walked outwards from the frame, `limit` stops each way (0.52 code review,
|
|
45
|
+
* lib/focus-left.ts): read from every stop in the page, a banner under a 2000-row table
|
|
46
|
+
* read all 2000 to keep the nearest five before it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function tabStopsAround(frame: Element, limit?: number): {
|
|
49
|
+
after: HTMLElement[];
|
|
50
|
+
before: HTMLElement[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Focus the first of `candidates` that is still in the page and takes the focus; true
|
|
54
|
+
* when one did. Each is tried and checked, since `focus()` on a node that cannot hold it
|
|
55
|
+
* (detached, `display: none`, disabled since) fails without a word.
|
|
56
|
+
*/
|
|
57
|
+
export declare function focusFirst(candidates: Iterable<Element | null | undefined>): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Focus a container that is not a tab stop of its own — a table, a list — as the place
|
|
60
|
+
* the user is in, without making it one: `tabindex="-1"` for as long as it holds the
|
|
61
|
+
* focus, unless it already carries a `tabindex` of its own.
|
|
62
|
+
*/
|
|
63
|
+
export declare function focusContainer(el: HTMLElement): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const TAB_STOP = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
3
|
+
function isTabStop(el) {
|
|
4
|
+
return el.tabIndex >= 0 && !el.closest("[hidden],[inert]");
|
|
5
|
+
}
|
|
6
|
+
function tabStops(el, { includeSelf = true } = {}) {
|
|
7
|
+
const inside = Array.from(el.querySelectorAll(TAB_STOP)).filter(isTabStop);
|
|
8
|
+
const own = includeSelf && el instanceof HTMLElement && el.matches(TAB_STOP) && isTabStop(el);
|
|
9
|
+
return own ? [el, ...inside] : inside;
|
|
10
|
+
}
|
|
11
|
+
function firstTabStop(el, options) {
|
|
12
|
+
return tabStops(el, options)[0] ?? null;
|
|
13
|
+
}
|
|
14
|
+
function focusIsLost() {
|
|
15
|
+
if (typeof document === "undefined") return false;
|
|
16
|
+
const el = document.activeElement;
|
|
17
|
+
return !el || el === document.body || !el.isConnected;
|
|
18
|
+
}
|
|
19
|
+
function whenFocusLeftForNothing(e, then) {
|
|
20
|
+
const el = e.target;
|
|
21
|
+
if (e.relatedTarget !== null || !(el instanceof Element)) return;
|
|
22
|
+
queueMicrotask(() => {
|
|
23
|
+
if (!el.isConnected || el.matches(":disabled") || el.closest("[hidden],[inert]")) return;
|
|
24
|
+
if (typeof el.checkVisibility === "function" && !el.checkVisibility()) return;
|
|
25
|
+
if (focusIsLost()) then();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function tabStopsAround(frame, limit = 5) {
|
|
29
|
+
const doc = frame.ownerDocument;
|
|
30
|
+
const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);
|
|
31
|
+
const isStop = (node2) => node2 instanceof HTMLElement && node2.matches(TAB_STOP) && isTabStop(node2);
|
|
32
|
+
const after = [];
|
|
33
|
+
walker.currentNode = frame;
|
|
34
|
+
let node = walker.nextSibling();
|
|
35
|
+
while (!node && walker.parentNode()) node = walker.nextSibling();
|
|
36
|
+
for (; node && after.length < limit; node = walker.nextNode()) if (isStop(node)) after.push(node);
|
|
37
|
+
const before = [];
|
|
38
|
+
walker.currentNode = frame;
|
|
39
|
+
for (node = walker.previousNode(); node && before.length < limit; node = walker.previousNode())
|
|
40
|
+
if (isStop(node)) before.push(node);
|
|
41
|
+
return { after, before };
|
|
42
|
+
}
|
|
43
|
+
function focusFirst(candidates) {
|
|
44
|
+
for (const el of candidates) {
|
|
45
|
+
if (!(el instanceof HTMLElement) || !el.isConnected) continue;
|
|
46
|
+
el.focus();
|
|
47
|
+
if (document.activeElement === el) return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function focusContainer(el) {
|
|
52
|
+
if (!el.hasAttribute("tabindex")) {
|
|
53
|
+
el.setAttribute("tabindex", "-1");
|
|
54
|
+
el.addEventListener("blur", () => el.removeAttribute("tabindex"), { once: true });
|
|
55
|
+
}
|
|
56
|
+
el.focus();
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
firstTabStop,
|
|
60
|
+
focusContainer,
|
|
61
|
+
focusFirst,
|
|
62
|
+
focusIsLost,
|
|
63
|
+
tabStops,
|
|
64
|
+
tabStopsAround,
|
|
65
|
+
whenFocusLeftForNothing
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=focus-left.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/focus-left.ts"],"sourcesContent":["// \"The focused node left\" (refactor audit 2026-10-30, Wave 1: DSP-4, DSP-15). When the\n// element holding the keyboard's focus is removed — a table row deleted, filtered out or\n// approved; a bulk bar gone with its Clear; a banner its × took away — the browser puts\n// focus on <body> and says nothing: no blur in Chromium, none in jsdom. A keyboard or\n// screen-reader user then starts again from the top of the page. The overlays give focus\n// back to their opener (use-focus-trap); these helpers are for the controls that leave\n// the page while the focus is IN them, and decide where it goes instead.\n\n/**\n * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab\n * really reaches. The kit's one copy of the rule (0.52 code review, lib/focus-left.ts):\n * this file and use-focus-trap each had it, with an exported `firstTabStop` apiece that\n * disagreed about the element itself, and a combobox panel's toolbar had a third\n * selector. The trap, the focus a removed node drops (below), the tour's field ring and\n * the toolbar all read it here, so \"the next stop\" is the same stop everywhere.\n *\n * The selector is the one `Modal` used. On its own it was not enough (round 0.34\n * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or\n * a link taken out of the tab order still counted. A panel that ends in a roving grid —\n * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`\n * but the active cell — then had a \"last\" stop no Tab ever reaches: Tab from the real\n * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the\n * first landed on a cell that is not a stop at all.\n */\n// `input[type=hidden]` is no stop: a Tab never lands on it and focus() on it does nothing,\n// but its tabIndex reads 0, so a trap could take a form's hidden field for its first or last\n// stop and Shift+Tab left the dialog (0.52 code review).\nconst TAB_STOP =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type=\"hidden\"]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not\n * inside something `hidden` or `inert`. Removes only elements a native Tab never\n * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are\n * still not walked — they need layout, which this rule has never read.\n */\nfunction isTabStop(el: HTMLElement): boolean {\n return el.tabIndex >= 0 && !el.closest(\"[hidden],[inert]\");\n}\n\nexport interface TabStopOptions {\n /** Whether `el` itself counts, first, when it is a stop. Default true: the tour's\n * target may BE the field (refactor audit 2026-10-30, DUP-9). False for \"a control\n * inside it\" — a row, a drawer, a trap's container, a toolbar. */\n includeSelf?: boolean;\n}\n\n/** The tab stops in `el`, in DOM order — `el` itself first when it is one, unless\n * `includeSelf: false`. */\nexport function tabStops(el: Element, { includeSelf = true }: TabStopOptions = {}): HTMLElement[] {\n const inside = Array.from(el.querySelectorAll<HTMLElement>(TAB_STOP)).filter(isTabStop);\n const own = includeSelf && el instanceof HTMLElement && el.matches(TAB_STOP) && isTabStop(el);\n return own ? [el, ...inside] : inside;\n}\n\n/** The first of {@link tabStops} — the control a person types into or presses — or null. */\nexport function firstTabStop(el: Element, options?: TabStopOptions): HTMLElement | null {\n return tabStops(el, options)[0] ?? null;\n}\n\n/**\n * Whether the focus has fallen out of the page: onto `<body>`, onto nothing, or onto a\n * node no longer in the document. False on the server, where nothing has focus to lose.\n * A recovery asks this first: focus that went somewhere real (a dialog, the next field,\n * an app's own `focus()`) is the user's or the app's, and is left where it is.\n */\nexport function focusIsLost(): boolean {\n if (typeof document === \"undefined\") return false;\n const el = document.activeElement;\n return !el || el === document.body || !el.isConnected;\n}\n\n/**\n * Calls `then` when a `blur`/`focusout` was the user's own move to nothing — a click on\n * the page's background, or a click Safari answers by focusing nothing (it focuses no\n * button or link on a click, and blurs what had the focus) — after which a note of where\n * the focus was is stale (0.52 code review: DataTable's row, BulkActionBar's and\n * AlertBanner's notes outlived it, and a removal much later pulled the focus, and the\n * scroll, back to them).\n *\n * No element took the focus, the document holds none, and the node that lost it could\n * still hold it. Not when it was removed, hidden, made inert or disabled: that is the page\n * taking the focus away — the very case those notes are kept for. Not a window losing the\n * focus either, which leaves the node the document's active element. Decided a microtask\n * later, once the event's own work is done: a browser that blurs a node it is removing\n * (or hiding) may do it while the node is still in place, and only after it is gone is\n * that told apart from a click.\n */\nexport function whenFocusLeftForNothing(\n e: { target: EventTarget | null; relatedTarget: EventTarget | null },\n then: () => void,\n): void {\n const el = e.target;\n if (e.relatedTarget !== null || !(el instanceof Element)) return;\n queueMicrotask(() => {\n if (!el.isConnected || el.matches(\":disabled\") || el.closest(\"[hidden],[inert]\")) return;\n // `display: none`, where the browser can say (jsdom cannot).\n if (typeof el.checkVisibility === \"function\" && !el.checkVisibility()) return;\n if (focusIsLost()) then();\n });\n}\n\n/**\n * The tab stops around `frame`, outside it, nearest first on each side — where focus\n * goes once `frame` is gone. Read BEFORE it goes: a removed node has no place in the\n * document to measure from. `limit` per side keeps a long page's stops out of memory.\n *\n * Walked outwards from the frame, `limit` stops each way (0.52 code review,\n * lib/focus-left.ts): read from every stop in the page, a banner under a 2000-row table\n * read all 2000 to keep the nearest five before it.\n */\nexport function tabStopsAround(frame: Element, limit = 5): { after: HTMLElement[]; before: HTMLElement[] } {\n const doc = frame.ownerDocument;\n const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);\n const isStop = (node: Node): node is HTMLElement =>\n node instanceof HTMLElement && node.matches(TAB_STOP) && isTabStop(node);\n // After: from the first element past the frame's own subtree, in document order.\n const after: HTMLElement[] = [];\n walker.currentNode = frame;\n let node: Node | null = walker.nextSibling();\n while (!node && walker.parentNode()) node = walker.nextSibling();\n for (; node && after.length < limit; node = walker.nextNode()) if (isStop(node)) after.push(node);\n // Before: back from the frame, nearest first (its ancestors among them, as before).\n const before: HTMLElement[] = [];\n walker.currentNode = frame;\n for (node = walker.previousNode(); node && before.length < limit; node = walker.previousNode())\n if (isStop(node)) before.push(node);\n return { after, before };\n}\n\n/**\n * Focus the first of `candidates` that is still in the page and takes the focus; true\n * when one did. Each is tried and checked, since `focus()` on a node that cannot hold it\n * (detached, `display: none`, disabled since) fails without a word.\n */\nexport function focusFirst(candidates: Iterable<Element | null | undefined>): boolean {\n for (const el of candidates) {\n if (!(el instanceof HTMLElement) || !el.isConnected) continue;\n el.focus();\n if (document.activeElement === el) return true;\n }\n return false;\n}\n\n/**\n * Focus a container that is not a tab stop of its own — a table, a list — as the place\n * the user is in, without making it one: `tabindex=\"-1\"` for as long as it holds the\n * focus, unless it already carries a `tabindex` of its own.\n */\nexport function focusContainer(el: HTMLElement): void {\n if (!el.hasAttribute(\"tabindex\")) {\n el.setAttribute(\"tabindex\", \"-1\");\n el.addEventListener(\"blur\", () => el.removeAttribute(\"tabindex\"), { once: true });\n }\n el.focus();\n}\n"],"mappings":";AA2BA,MAAM,WACJ;AAQF,SAAS,UAAU,IAA0B;AAC3C,SAAO,GAAG,YAAY,KAAK,CAAC,GAAG,QAAQ,kBAAkB;AAC3D;AAWO,SAAS,SAAS,IAAa,EAAE,cAAc,KAAK,IAAoB,CAAC,GAAkB;AAChG,QAAM,SAAS,MAAM,KAAK,GAAG,iBAA8B,QAAQ,CAAC,EAAE,OAAO,SAAS;AACtF,QAAM,MAAM,eAAe,cAAc,eAAe,GAAG,QAAQ,QAAQ,KAAK,UAAU,EAAE;AAC5F,SAAO,MAAM,CAAC,IAAI,GAAG,MAAM,IAAI;AACjC;AAGO,SAAS,aAAa,IAAa,SAA8C;AACtF,SAAO,SAAS,IAAI,OAAO,EAAE,CAAC,KAAK;AACrC;AAQO,SAAS,cAAuB;AACrC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,KAAK,SAAS;AACpB,SAAO,CAAC,MAAM,OAAO,SAAS,QAAQ,CAAC,GAAG;AAC5C;AAkBO,SAAS,wBACd,GACA,MACM;AACN,QAAM,KAAK,EAAE;AACb,MAAI,EAAE,kBAAkB,QAAQ,EAAE,cAAc,SAAU;AAC1D,iBAAe,MAAM;AACnB,QAAI,CAAC,GAAG,eAAe,GAAG,QAAQ,WAAW,KAAK,GAAG,QAAQ,kBAAkB,EAAG;AAElF,QAAI,OAAO,GAAG,oBAAoB,cAAc,CAAC,GAAG,gBAAgB,EAAG;AACvE,QAAI,YAAY,EAAG,MAAK;AAAA,EAC1B,CAAC;AACH;AAWO,SAAS,eAAe,OAAgB,QAAQ,GAAoD;AACzG,QAAM,MAAM,MAAM;AAClB,QAAM,SAAS,IAAI,iBAAiB,IAAI,iBAAiB,WAAW,YAAY;AAChF,QAAM,SAAS,CAACA,UACdA,iBAAgB,eAAeA,MAAK,QAAQ,QAAQ,KAAK,UAAUA,KAAI;AAEzE,QAAM,QAAuB,CAAC;AAC9B,SAAO,cAAc;AACrB,MAAI,OAAoB,OAAO,YAAY;AAC3C,SAAO,CAAC,QAAQ,OAAO,WAAW,EAAG,QAAO,OAAO,YAAY;AAC/D,SAAO,QAAQ,MAAM,SAAS,OAAO,OAAO,OAAO,SAAS,EAAG,KAAI,OAAO,IAAI,EAAG,OAAM,KAAK,IAAI;AAEhG,QAAM,SAAwB,CAAC;AAC/B,SAAO,cAAc;AACrB,OAAK,OAAO,OAAO,aAAa,GAAG,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,aAAa;AAC3F,QAAI,OAAO,IAAI,EAAG,QAAO,KAAK,IAAI;AACpC,SAAO,EAAE,OAAO,OAAO;AACzB;AAOO,SAAS,WAAW,YAA2D;AACpF,aAAW,MAAM,YAAY;AAC3B,QAAI,EAAE,cAAc,gBAAgB,CAAC,GAAG,YAAa;AACrD,OAAG,MAAM;AACT,QAAI,SAAS,kBAAkB,GAAI,QAAO;AAAA,EAC5C;AACA,SAAO;AACT;AAOO,SAAS,eAAe,IAAuB;AACpD,MAAI,CAAC,GAAG,aAAa,UAAU,GAAG;AAChC,OAAG,aAAa,YAAY,IAAI;AAChC,OAAG,iBAAiB,QAAQ,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EAClF;AACA,KAAG,MAAM;AACX;","names":["node"]}
|
package/dist/lib/format.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { useKitLocale, useKitTimeZone } from "../i18n/kit-labels.js";
|
|
4
4
|
import { parseIsoDate } from "./dates.js";
|
|
5
|
+
import { dateFormatter } from "./intl-cache.js";
|
|
6
|
+
import { isBareIsoDay } from "./iso-day.js";
|
|
5
7
|
import { formatWithMinus } from "./minus.js";
|
|
6
8
|
import { roundHalfAway } from "./round.js";
|
|
7
9
|
import { formatWithMinus as formatWithMinus2 } from "./minus.js";
|
|
@@ -111,28 +113,17 @@ const DATE_STYLES = {
|
|
|
111
113
|
time: { timeStyle: "short" },
|
|
112
114
|
monthYear: { month: "long", year: "numeric" }
|
|
113
115
|
};
|
|
114
|
-
const BARE_ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
115
116
|
function toDate(value) {
|
|
116
117
|
if (value === null || value === void 0 || value === "") return null;
|
|
117
|
-
if (
|
|
118
|
+
if (isBareIsoDay(value)) return parseIsoDate(value);
|
|
118
119
|
const d = value instanceof Date ? value : new Date(value);
|
|
119
120
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
120
121
|
}
|
|
121
|
-
const dateFormatters = /* @__PURE__ */ new Map();
|
|
122
|
-
function dateFormatter(locale, options) {
|
|
123
|
-
const key = `${locale ?? ""}|${JSON.stringify(options)}`;
|
|
124
|
-
let f = dateFormatters.get(key);
|
|
125
|
-
if (!f) {
|
|
126
|
-
f = new Intl.DateTimeFormat(locale, options);
|
|
127
|
-
dateFormatters.set(key, f);
|
|
128
|
-
}
|
|
129
|
-
return f;
|
|
130
|
-
}
|
|
131
122
|
function formatDate(value, style = "medium", options = {}) {
|
|
132
123
|
const d = toDate(value);
|
|
133
124
|
if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
134
125
|
const base = typeof style === "string" ? DATE_STYLES[style] : style;
|
|
135
|
-
if (
|
|
126
|
+
if (isBareIsoDay(value)) {
|
|
136
127
|
const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
137
128
|
return dateFormatter(options.locale, { ...base, timeZone: "UTC" }).format(day);
|
|
138
129
|
}
|
|
@@ -180,7 +171,7 @@ function formatRelativeTime(value, options = {}) {
|
|
|
180
171
|
const rtf = relativeFormatter(options.locale, options.numeric ?? "auto", options.style ?? "long");
|
|
181
172
|
const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit);
|
|
182
173
|
if (unit === "day") {
|
|
183
|
-
const bareDay =
|
|
174
|
+
const bareDay = isBareIsoDay(value);
|
|
184
175
|
const days = dayNumber(d, bareDay ? void 0 : options.timeZone) - dayNumber(now, options.timeZone);
|
|
185
176
|
return days === 0 ? rtf.format(Math.round(diff / HOUR), "hour") : rtf.format(days, "day");
|
|
186
177
|
}
|