@eifi1/ui-kit 0.53.0 → 0.55.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 +96 -82
- package/dist/account/delete-account-setting.d.ts +3 -2
- package/dist/account/delete-account-setting.js.map +1 -1
- package/dist/admin/plan-change.js +2 -2
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/user-roster.d.ts +6 -0
- package/dist/admin/user-roster.js +4 -0
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/auth-errors.d.ts +36 -0
- package/dist/auth/auth-errors.js +20 -0
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +26 -0
- package/dist/billing/billing-labels.js +9 -0
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/subscription-standing.d.ts +61 -0
- package/dist/billing/subscription-standing.js +51 -0
- package/dist/billing/subscription-standing.js.map +1 -0
- package/dist/billing/usage-meter.d.ts +52 -0
- package/dist/billing/usage-meter.js +52 -0
- package/dist/billing/usage-meter.js.map +1 -0
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +4 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +2 -3
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js +3 -15
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +2 -2
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.js +2 -1
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button.d.ts +35 -1
- package/dist/components/button.js +5 -9
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +6 -4
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/camera-button.d.ts +89 -0
- package/dist/components/camera-button.js +122 -0
- package/dist/components/camera-button.js.map +1 -0
- package/dist/components/camera-capture.d.ts +66 -0
- package/dist/components/camera-capture.js +190 -0
- package/dist/components/camera-capture.js.map +1 -0
- package/dist/components/camera-labels.d.ts +20 -0
- package/dist/components/camera-labels.js +15 -0
- package/dist/components/camera-labels.js.map +1 -0
- package/dist/components/chart-edge.d.ts +9 -0
- package/dist/components/chart-edge.js +14 -3
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-zoom-core.js +34 -29
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/checkbox-group.js +1 -1
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/combobox-core.js +3 -2
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.js +2 -2
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/consent-again-notice-labels.d.ts +25 -0
- package/dist/components/consent-again-notice-labels.js +15 -0
- package/dist/components/consent-again-notice-labels.js.map +1 -0
- package/dist/components/consent-again-notice.d.ts +85 -0
- package/dist/components/consent-again-notice.js +94 -0
- package/dist/components/consent-again-notice.js.map +1 -0
- package/dist/components/country-select.js +2 -7
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +2 -1
- package/dist/components/currency-option-list.js +10 -3
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/currency-select.d.ts +3 -1
- package/dist/components/currency-select.js +2 -2
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +22 -1
- package/dist/components/danger-confirm.js +52 -30
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +2 -1
- package/dist/components/data-table-filter-popover.js +3 -2
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +33 -3
- package/dist/components/data-table-filters.js +6 -4
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-virtual.js +13 -3
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/date-field.js +2 -2
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +2 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +2 -6
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +5 -1
- package/dist/components/dialog-frame.js +6 -2
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.js +7 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.js +2 -2
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-parts.d.ts +108 -6
- package/dist/components/field-parts.js +59 -6
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-suggestion-labels.d.ts +2 -0
- package/dist/components/field-suggestion-labels.js +10 -0
- package/dist/components/field-suggestion-labels.js.map +1 -0
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/floating-panel.d.ts +7 -6
- package/dist/components/floating-panel.js +3 -17
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +3 -2
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.js +3 -2
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.d.ts +23 -5
- package/dist/components/icon-button.js +6 -8
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.js +2 -1
- package/dist/components/inline-confirm.js.map +1 -1
- package/dist/components/inline-entity-combobox.js +2 -2
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +3 -3
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal.d.ts +13 -1
- package/dist/components/legal.js +12 -0
- package/dist/components/legal.js.map +1 -1
- package/dist/components/legended-series-chart.d.ts +80 -0
- package/dist/components/legended-series-chart.js +70 -0
- package/dist/components/legended-series-chart.js.map +1 -0
- package/dist/components/lightbox.js +2 -1
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/mini-calendar.js +16 -7
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +48 -0
- package/dist/components/modal.js +26 -6
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +1 -1
- package/dist/components/month-picker.js +6 -6
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.js +2 -2
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.js +2 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.js +2 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/page-header-layout.d.ts +18 -0
- package/dist/components/page-header-layout.js +23 -0
- package/dist/components/page-header-layout.js.map +1 -0
- package/dist/components/page-header-skeleton.d.ts +41 -0
- package/dist/components/page-header-skeleton.js +52 -0
- package/dist/components/page-header-skeleton.js.map +1 -0
- package/dist/components/page-header.js +4 -17
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/popover.js +7 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/release-notes-labels.d.ts +16 -0
- package/dist/components/release-notes-labels.js +18 -0
- package/dist/components/release-notes-labels.js.map +1 -0
- package/dist/components/release-notes.d.ts +65 -0
- package/dist/components/release-notes.js +65 -0
- package/dist/components/release-notes.js.map +1 -0
- package/dist/components/select.js +2 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +9 -0
- package/dist/components/series-chart-labels.js +3 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +5 -0
- package/dist/components/series-chart.js +89 -36
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/stat-tile.d.ts +21 -2
- package/dist/components/stat-tile.js +4 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/toggle-group.js +3 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/treemap-layout.js.map +1 -1
- package/dist/components/treemap.d.ts +17 -15
- package/dist/components/treemap.js +167 -85
- package/dist/components/treemap.js.map +1 -1
- package/dist/currencies.d.ts +16 -2
- package/dist/currencies.js +2 -1
- package/dist/currencies.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +2 -2
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-context-labels.js +2 -1
- package/dist/feedback/feedback-context-labels.js.map +1 -1
- package/dist/feedback/feedback-context.d.ts +19 -3
- package/dist/feedback/feedback-context.js +12 -1
- package/dist/feedback/feedback-context.js.map +1 -1
- package/dist/feedback/feedback-diagnostics.d.ts +17 -0
- package/dist/feedback/feedback-diagnostics.js +27 -0
- package/dist/feedback/feedback-diagnostics.js.map +1 -0
- package/dist/feedback/feedback-dialog.d.ts +10 -0
- package/dist/feedback/feedback-dialog.js +6 -4
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox-page.d.ts +260 -0
- package/dist/feedback/feedback-inbox-page.js +207 -0
- package/dist/feedback/feedback-inbox-page.js.map +1 -0
- package/dist/feedback/feedback-record.d.ts +5 -0
- package/dist/feedback/feedback-record.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +1 -0
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +3 -0
- package/dist/feedback/feedback-row-detail.js +3 -0
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
- package/dist/feedback/feedback-submit-dialog.js +15 -3
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.d.ts +30 -2
- package/dist/feedback/feedback-submit.js +5 -2
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-vocabulary.js +2 -2
- package/dist/feedback/feedback-vocabulary.js.map +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.js +6 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-debounce.d.ts +32 -5
- package/dist/hooks/use-debounce.js +6 -5
- package/dist/hooks/use-debounce.js.map +1 -1
- package/dist/hooks/use-file-picker.d.ts +3 -1
- package/dist/hooks/use-file-picker.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +19 -3
- package/dist/hooks/use-focus-trap.js +15 -5
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-has-camera.d.ts +36 -0
- package/dist/hooks/use-has-camera.js +53 -0
- package/dist/hooks/use-has-camera.js.map +1 -0
- package/dist/hooks/use-hotkey.js +2 -1
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-overlay-history.d.ts +6 -1
- package/dist/hooks/use-overlay-history.js +10 -2
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +11 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +78 -4
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +20 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +95 -5
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +74 -4
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +75 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +77 -4
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +77 -4
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +74 -4
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +22 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-labels.d.ts +8 -0
- package/dist/landing/landing-labels.js +2 -1
- package/dist/landing/landing-labels.js.map +1 -1
- package/dist/lib/changelog.d.ts +130 -0
- package/dist/lib/changelog.js +137 -0
- package/dist/lib/changelog.js.map +1 -0
- package/dist/lib/clipboard-minus.js +2 -2
- package/dist/lib/clipboard-minus.js.map +1 -1
- package/dist/lib/dates.d.ts +36 -4
- package/dist/lib/dates.js +49 -13
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/editable-content.d.ts +33 -0
- package/dist/lib/editable-content.js +15 -0
- package/dist/lib/editable-content.js.map +1 -0
- package/dist/lib/focus-left.d.ts +47 -1
- package/dist/lib/focus-left.js +11 -2
- package/dist/lib/focus-left.js.map +1 -1
- package/dist/lib/format.d.ts +37 -2
- package/dist/lib/format.js +25 -1
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/intl-cache.d.ts +4 -2
- package/dist/lib/intl-cache.js.map +1 -1
- package/dist/lib/server-wake.d.ts +49 -9
- package/dist/lib/server-wake.js +25 -14
- package/dist/lib/server-wake.js.map +1 -1
- package/dist/lib/session-refresh.d.ts +53 -0
- package/dist/lib/session-refresh.js +27 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/translation-review.d.ts +26 -0
- package/dist/lib/translation-review.js +14 -0
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/rhf/fields.js +3 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/shell/app-shell.d.ts +25 -1
- package/dist/shell/app-shell.js +3 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +3 -0
- package/dist/shell/dev-menu.js +4 -2
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/held-changes-banner-labels.d.ts +23 -0
- package/dist/shell/held-changes-banner-labels.js +14 -0
- package/dist/shell/held-changes-banner-labels.js.map +1 -0
- package/dist/shell/held-changes-banner.d.ts +75 -0
- package/dist/shell/held-changes-banner.js +106 -0
- package/dist/shell/held-changes-banner.js.map +1 -0
- package/dist/shell/sync-status-indicator.d.ts +27 -12
- package/dist/shell/sync-status-indicator.js +13 -4
- package/dist/shell/sync-status-indicator.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +5 -0
- package/dist/shell/top-bar-parts-labels.js +3 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +3 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +11 -2
- package/dist/sync/engine.js +13 -5
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/hooks.d.ts +3 -2
- package/dist/sync/hooks.js +4 -2
- package/dist/sync/hooks.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +2 -2
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +18 -0
- package/dist/theme/chart-palette.js +17 -1
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +3 -3
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -4
- package/dist/theme/contrast.js.map +1 -1
- package/dist/tour/tour-overlay.js +2 -1
- package/dist/tour/tour-overlay.js.map +1 -1
- package/package.json +5 -9
- package/tokens.css +19 -54
- package/src/account/account-parts.ts +0 -47
- package/src/account/account-settings-labels.ts +0 -240
- package/src/account/account-settings.tsx +0 -698
- package/src/account/data-export-setting-labels.ts +0 -20
- package/src/account/data-export-setting.tsx +0 -200
- package/src/account/delete-account-setting-labels.ts +0 -33
- package/src/account/delete-account-setting.tsx +0 -341
- package/src/account/email-change-setting-labels.ts +0 -31
- package/src/account/email-change-setting.tsx +0 -443
- package/src/account/passkeys-setting.tsx +0 -445
- package/src/account/sessions-setting-labels.ts +0 -24
- package/src/account/sessions-setting.tsx +0 -283
- package/src/admin/admin-action-confirm-labels.ts +0 -19
- package/src/admin/admin-action-confirm.tsx +0 -509
- package/src/admin/admin-action-log-labels.ts +0 -83
- package/src/admin/admin-action-log.tsx +0 -464
- package/src/admin/admin-area-labels.ts +0 -114
- package/src/admin/admin-forbidden.tsx +0 -75
- package/src/admin/admin-overview.tsx +0 -258
- package/src/admin/admin-parts.ts +0 -68
- package/src/admin/admin-sections.tsx +0 -208
- package/src/admin/admin-tool-links.tsx +0 -90
- package/src/admin/invitations-panel-labels.ts +0 -31
- package/src/admin/invitations-panel.tsx +0 -516
- package/src/admin/mail-test-card-labels.ts +0 -19
- package/src/admin/mail-test-card.tsx +0 -189
- package/src/admin/plan-change-labels.ts +0 -5
- package/src/admin/plan-change.tsx +0 -474
- package/src/admin/reviewer-scope-editor-labels.ts +0 -16
- package/src/admin/reviewer-scope-editor.tsx +0 -224
- package/src/admin/role-select-labels.ts +0 -16
- package/src/admin/role-select.tsx +0 -282
- package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
- package/src/admin/transfer-ownership-dialog.tsx +0 -162
- package/src/admin/user-roster-labels.ts +0 -17
- package/src/admin/user-roster.tsx +0 -559
- package/src/auth/accept-invitation-labels.ts +0 -21
- package/src/auth/accept-invitation.tsx +0 -210
- package/src/auth/auth-errors-labels.ts +0 -23
- package/src/auth/auth-errors.ts +0 -370
- package/src/auth/complete-name-dialog-labels.ts +0 -16
- package/src/auth/complete-name-dialog.tsx +0 -253
- package/src/auth/email-tag.ts +0 -75
- package/src/auth/forgot-password-form-labels.ts +0 -20
- package/src/auth/forgot-password-form.tsx +0 -221
- package/src/auth/form-rules.ts +0 -40
- package/src/auth/new-password-fields.tsx +0 -85
- package/src/auth/not-found-page-labels.ts +0 -12
- package/src/auth/not-found-page.tsx +0 -95
- package/src/auth/register-form-labels.ts +0 -31
- package/src/auth/register-form.tsx +0 -456
- package/src/auth/reset-password-form-labels.ts +0 -27
- package/src/auth/reset-password-form.tsx +0 -341
- package/src/auth/sign-in-form-labels.ts +0 -44
- package/src/auth/sign-in-form.tsx +0 -858
- package/src/auth/status-parts.tsx +0 -132
- package/src/auth/verify-email-labels.ts +0 -26
- package/src/auth/verify-email.tsx +0 -445
- package/src/billing/billing-banner.tsx +0 -258
- package/src/billing/billing-labels.ts +0 -381
- package/src/billing/billing-lock.ts +0 -108
- package/src/billing/billing-overview.ts +0 -157
- package/src/billing/billing-standing.ts +0 -80
- package/src/billing/cancel-request.ts +0 -97
- package/src/billing/cancel-subscription.tsx +0 -98
- package/src/billing/checkout-processing.ts +0 -394
- package/src/billing/checkout-return.ts +0 -90
- package/src/billing/pay-page.ts +0 -330
- package/src/billing/plan-card.tsx +0 -562
- package/src/billing/plan-change-labels.ts +0 -66
- package/src/billing/plan-limit-notice.tsx +0 -148
- package/src/billing/plan-limit-toast.tsx +0 -90
- package/src/billing/plan-limit.ts +0 -52
- package/src/billing/plan-price.ts +0 -98
- package/src/billing/plan-switch.tsx +0 -216
- package/src/billing/provider-page.ts +0 -84
- package/src/billing/subscription-actions.tsx +0 -247
- package/src/billing/subscription-status-chip.tsx +0 -95
- package/src/chart.ts +0 -103
- package/src/components/account-chips-labels.ts +0 -16
- package/src/components/account-chips.tsx +0 -474
- package/src/components/alert-banner.tsx +0 -670
- package/src/components/amount-input.tsx +0 -815
- package/src/components/appearance-labels.ts +0 -63
- package/src/components/authed-image-labels.ts +0 -11
- package/src/components/authed-image.tsx +0 -209
- package/src/components/autocomplete.tsx +0 -645
- package/src/components/breadcrumbs-labels.ts +0 -10
- package/src/components/breadcrumbs.tsx +0 -205
- package/src/components/bulk-action-bar-labels.ts +0 -11
- package/src/components/bulk-action-bar.tsx +0 -443
- package/src/components/button-group.tsx +0 -223
- package/src/components/button-look.ts +0 -256
- package/src/components/button.tsx +0 -339
- package/src/components/calculator.tsx +0 -341
- package/src/components/calendar-heatmap-labels.ts +0 -15
- package/src/components/calendar-heatmap.tsx +0 -594
- package/src/components/card.tsx +0 -377
- package/src/components/chart-edge.ts +0 -106
- package/src/components/chart-figure.tsx +0 -35
- package/src/components/chart-height.ts +0 -30
- package/src/components/chart-zoom-core.tsx +0 -627
- package/src/components/chart-zoom.tsx +0 -22
- package/src/components/chart.tsx +0 -554
- package/src/components/checkbox-group.tsx +0 -247
- package/src/components/checkbox.tsx +0 -346
- package/src/components/chip-input.tsx +0 -370
- package/src/components/chip-labels.ts +0 -13
- package/src/components/chip.tsx +0 -843
- package/src/components/choice-card.tsx +0 -544
- package/src/components/column-mapper-labels.ts +0 -38
- package/src/components/column-mapper.tsx +0 -655
- package/src/components/combobox-core.tsx +0 -1321
- package/src/components/combobox-suggestions.tsx +0 -131
- package/src/components/combobox.tsx +0 -716
- package/src/components/confirm-context.ts +0 -7
- package/src/components/confirm-dialog-labels.ts +0 -13
- package/src/components/confirm-dialog.tsx +0 -341
- package/src/components/copy-button-labels.ts +0 -13
- package/src/components/copy-button.tsx +0 -192
- package/src/components/country-select-labels.ts +0 -11
- package/src/components/country-select.tsx +0 -539
- package/src/components/currency-option-list.tsx +0 -180
- package/src/components/currency-select.tsx +0 -341
- package/src/components/danger-confirm-labels.ts +0 -20
- package/src/components/danger-confirm.tsx +0 -934
- package/src/components/data-table-cells.tsx +0 -118
- package/src/components/data-table-column-settings.tsx +0 -198
- package/src/components/data-table-filter-popover.tsx +0 -281
- package/src/components/data-table-filters.ts +0 -287
- package/src/components/data-table-fit.ts +0 -301
- package/src/components/data-table-groups.tsx +0 -288
- package/src/components/data-table-head.tsx +0 -280
- package/src/components/data-table-labels.ts +0 -198
- package/src/components/data-table-leading-row.tsx +0 -58
- package/src/components/data-table-mobile.tsx +0 -1117
- package/src/components/data-table-pagination.tsx +0 -202
- package/src/components/data-table-resize.ts +0 -188
- package/src/components/data-table-row-actions.tsx +0 -302
- package/src/components/data-table-row.tsx +0 -496
- package/src/components/data-table-selection.ts +0 -71
- package/src/components/data-table-shared.tsx +0 -287
- package/src/components/data-table-sort.ts +0 -163
- package/src/components/data-table-virtual.ts +0 -576
- package/src/components/data-table.tsx +0 -2598
- package/src/components/date-field.tsx +0 -684
- package/src/components/date-format-setting.tsx +0 -64
- package/src/components/date-picker.tsx +0 -404
- package/src/components/date-range-picker.tsx +0 -518
- package/src/components/description-list-labels.ts +0 -9
- package/src/components/description-list.tsx +0 -386
- package/src/components/dialog-frame-labels.ts +0 -7
- package/src/components/dialog-frame.tsx +0 -276
- package/src/components/disclosure.tsx +0 -476
- package/src/components/dropdown.tsx +0 -461
- package/src/components/empty-state.tsx +0 -136
- package/src/components/entity-combobox.tsx +0 -422
- package/src/components/error-boundary-labels.ts +0 -20
- package/src/components/error-boundary.tsx +0 -675
- package/src/components/facing-pair.ts +0 -93
- package/src/components/field-base-labels.ts +0 -20
- package/src/components/field-base.tsx +0 -656
- package/src/components/field-parts.tsx +0 -825
- package/src/components/field-strip.tsx +0 -129
- package/src/components/field.tsx +0 -206
- package/src/components/file-button.tsx +0 -295
- package/src/components/file-dropzone.tsx +0 -559
- package/src/components/floating-panel-labels.ts +0 -10
- package/src/components/floating-panel.tsx +0 -828
- package/src/components/focus-ring.ts +0 -16
- package/src/components/form-actions-labels.ts +0 -11
- package/src/components/form-actions.tsx +0 -578
- package/src/components/full-bleed-dialog.tsx +0 -294
- package/src/components/grouped-picker.tsx +0 -184
- package/src/components/help-popover.tsx +0 -117
- package/src/components/hover-menu.tsx +0 -343
- package/src/components/iban-input-labels.ts +0 -14
- package/src/components/iban-input.tsx +0 -236
- package/src/components/icon-button.tsx +0 -959
- package/src/components/icon-picker-labels.ts +0 -13
- package/src/components/icon-picker.tsx +0 -232
- package/src/components/image-grid-labels.ts +0 -11
- package/src/components/image-grid.tsx +0 -202
- package/src/components/inline-confirm.tsx +0 -116
- package/src/components/inline-edit-field-labels.ts +0 -11
- package/src/components/inline-edit-field.tsx +0 -552
- package/src/components/inline-entity-combobox.tsx +0 -849
- package/src/components/input.tsx +0 -394
- package/src/components/jump-calendar.tsx +0 -265
- package/src/components/language-select.tsx +0 -93
- package/src/components/legal-labels.ts +0 -105
- package/src/components/legal-page.tsx +0 -711
- package/src/components/legal.tsx +0 -668
- package/src/components/lightbox-labels.ts +0 -18
- package/src/components/lightbox.tsx +0 -399
- package/src/components/line-items-labels.ts +0 -14
- package/src/components/line-items.tsx +0 -602
- package/src/components/list-labels.ts +0 -12
- package/src/components/list.tsx +0 -870
- package/src/components/loading-state.tsx +0 -78
- package/src/components/measured-grid-labels.ts +0 -25
- package/src/components/measured-grid.tsx +0 -837
- package/src/components/menu-item.tsx +0 -241
- package/src/components/mini-calendar-labels.ts +0 -17
- package/src/components/mini-calendar.tsx +0 -789
- package/src/components/modal.tsx +0 -505
- package/src/components/money-draft.ts +0 -83
- package/src/components/money-field.tsx +0 -76
- package/src/components/month-grid.tsx +0 -229
- package/src/components/month-picker-labels.ts +0 -22
- package/src/components/month-picker.tsx +0 -1087
- package/src/components/multi-entity-combobox.tsx +0 -369
- package/src/components/multi-select.tsx +0 -376
- package/src/components/nav-pills.tsx +0 -198
- package/src/components/number-field.tsx +0 -327
- package/src/components/number-input.tsx +0 -558
- package/src/components/numeric-field.ts +0 -169
- package/src/components/numpad-sheet.tsx +0 -285
- package/src/components/one-time-code-input.tsx +0 -270
- package/src/components/page-contents-labels.ts +0 -9
- package/src/components/page-contents.tsx +0 -282
- package/src/components/page-header-context.ts +0 -8
- package/src/components/page-header.tsx +0 -298
- package/src/components/password-strength-labels.ts +0 -22
- package/src/components/password-strength.tsx +0 -241
- package/src/components/phone-input-labels.ts +0 -10
- package/src/components/phone-input.tsx +0 -282
- package/src/components/picker-sheet.tsx +0 -330
- package/src/components/pie-chart-labels.ts +0 -40
- package/src/components/pie-chart.tsx +0 -480
- package/src/components/popover-labels.ts +0 -14
- package/src/components/popover.tsx +0 -268
- package/src/components/progress-bar-labels.ts +0 -10
- package/src/components/progress-bar.tsx +0 -479
- package/src/components/qr-code.tsx +0 -76
- package/src/components/reauth-dialog-labels.ts +0 -13
- package/src/components/reauth-dialog.tsx +0 -265
- package/src/components/row-actions-labels.ts +0 -12
- package/src/components/row-actions.tsx +0 -795
- package/src/components/scroll-area.tsx +0 -123
- package/src/components/search-field.tsx +0 -192
- package/src/components/select.tsx +0 -294
- package/src/components/separator.tsx +0 -33
- package/src/components/series-chart-axes.ts +0 -342
- package/src/components/series-chart-budget.ts +0 -150
- package/src/components/series-chart-labels.ts +0 -33
- package/src/components/series-chart-thin.ts +0 -180
- package/src/components/series-chart-ticks.ts +0 -194
- package/src/components/series-chart.tsx +0 -1906
- package/src/components/server-wake-labels.ts +0 -11
- package/src/components/server-wake.tsx +0 -129
- package/src/components/settings-fields.tsx +0 -237
- package/src/components/share-card-labels.ts +0 -30
- package/src/components/share-card.tsx +0 -675
- package/src/components/signature-pad-labels.ts +0 -24
- package/src/components/signature-pad.tsx +0 -747
- package/src/components/signed-amount-labels.ts +0 -10
- package/src/components/signed-amount.tsx +0 -336
- package/src/components/skeleton.tsx +0 -80
- package/src/components/slider.tsx +0 -338
- package/src/components/sparkline-labels.ts +0 -14
- package/src/components/sparkline.tsx +0 -322
- package/src/components/spinner.tsx +0 -81
- package/src/components/stat-tile-labels.ts +0 -15
- package/src/components/stat-tile.tsx +0 -725
- package/src/components/status-dot.tsx +0 -170
- package/src/components/swatch-picker-labels.ts +0 -10
- package/src/components/swatch-picker.tsx +0 -283
- package/src/components/swipeable-row.tsx +0 -386
- package/src/components/switch.tsx +0 -273
- package/src/components/table-skeleton.tsx +0 -136
- package/src/components/table.tsx +0 -647
- package/src/components/tabs-labels.ts +0 -13
- package/src/components/tabs.tsx +0 -693
- package/src/components/text-link.tsx +0 -359
- package/src/components/text.tsx +0 -150
- package/src/components/textarea.tsx +0 -330
- package/src/components/tile-radio.tsx +0 -332
- package/src/components/time-input.tsx +0 -121
- package/src/components/toast-labels.ts +0 -12
- package/src/components/toast.tsx +0 -519
- package/src/components/toggle-group.tsx +0 -752
- package/src/components/toggle-legend.tsx +0 -394
- package/src/components/tooltip.tsx +0 -1149
- package/src/components/translation-review-editor.tsx +0 -290
- package/src/components/translation-review-labels.ts +0 -181
- package/src/components/translation-review-parts.tsx +0 -272
- package/src/components/translation-review.tsx +0 -1240
- package/src/components/tree-view.tsx +0 -589
- package/src/components/treemap-labels.ts +0 -47
- package/src/components/treemap-layout.ts +0 -210
- package/src/components/treemap.tsx +0 -1184
- package/src/components/trigger-aria.ts +0 -44
- package/src/components/use-anchor-dir.ts +0 -20
- package/src/components/use-mobile-reveal.ts +0 -62
- package/src/components/use-table-state.ts +0 -753
- package/src/components/user-avatar.tsx +0 -116
- package/src/components/wizard-stepper.tsx +0 -42
- package/src/components/write-lock-labels.ts +0 -10
- package/src/components/write-lock.tsx +0 -253
- package/src/currencies.ts +0 -70
- package/src/data-table.ts +0 -42
- package/src/demo/demo-banner.tsx +0 -153
- package/src/demo/demo-countdown.ts +0 -136
- package/src/demo/demo-ended.tsx +0 -90
- package/src/demo/demo-labels.ts +0 -78
- package/src/demo/demo-session.ts +0 -32
- package/src/demo/demo-start.tsx +0 -156
- package/src/eslint/index.ts +0 -161
- package/src/eslint/role-utilities.generated.ts +0 -146
- package/src/eslint/role-utilities.ts +0 -233
- package/src/feedback/feedback-attachment-download.tsx +0 -77
- package/src/feedback/feedback-attachment-labels.ts +0 -37
- package/src/feedback/feedback-attachment.tsx +0 -1239
- package/src/feedback/feedback-capture.ts +0 -287
- package/src/feedback/feedback-comment-thread.tsx +0 -353
- package/src/feedback/feedback-context-labels.ts +0 -10
- package/src/feedback/feedback-context.tsx +0 -213
- package/src/feedback/feedback-crash.ts +0 -547
- package/src/feedback/feedback-dialog-labels.ts +0 -29
- package/src/feedback/feedback-dialog.tsx +0 -440
- package/src/feedback/feedback-entry-file.tsx +0 -150
- package/src/feedback/feedback-inbox.tsx +0 -552
- package/src/feedback/feedback-labels.ts +0 -144
- package/src/feedback/feedback-menu-labels.ts +0 -12
- package/src/feedback/feedback-menu.tsx +0 -177
- package/src/feedback/feedback-mutation.ts +0 -20
- package/src/feedback/feedback-page-link.ts +0 -90
- package/src/feedback/feedback-public-labels.ts +0 -81
- package/src/feedback/feedback-public-list.tsx +0 -524
- package/src/feedback/feedback-public-switch.tsx +0 -102
- package/src/feedback/feedback-public-types.ts +0 -101
- package/src/feedback/feedback-record.ts +0 -423
- package/src/feedback/feedback-replies-context.ts +0 -9
- package/src/feedback/feedback-replies.tsx +0 -343
- package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
- package/src/feedback/feedback-row-detail-labels.ts +0 -30
- package/src/feedback/feedback-row-detail.tsx +0 -858
- package/src/feedback/feedback-status-undo.ts +0 -109
- package/src/feedback/feedback-submit-dialog.tsx +0 -118
- package/src/feedback/feedback-submit.tsx +0 -237
- package/src/feedback/feedback-swipe.tsx +0 -192
- package/src/feedback/feedback-table-labels.ts +0 -27
- package/src/feedback/feedback-table.tsx +0 -912
- package/src/feedback/feedback-thread-labels.ts +0 -22
- package/src/feedback/feedback-thread-parts-labels.ts +0 -41
- package/src/feedback/feedback-thread.tsx +0 -905
- package/src/feedback/feedback-vocabulary.ts +0 -263
- package/src/feedback/feedback-vote-button.tsx +0 -124
- package/src/feedback.ts +0 -116
- package/src/hooks/use-anchored-panel.ts +0 -396
- package/src/hooks/use-anchored-rect.ts +0 -77
- package/src/hooks/use-announce.ts +0 -85
- package/src/hooks/use-authed-src.ts +0 -167
- package/src/hooks/use-body-scroll-lock.ts +0 -119
- package/src/hooks/use-breakpoint.ts +0 -133
- package/src/hooks/use-close-transition.ts +0 -107
- package/src/hooks/use-copy-to-clipboard.ts +0 -122
- package/src/hooks/use-debounce.ts +0 -114
- package/src/hooks/use-dismiss.ts +0 -371
- package/src/hooks/use-document-title.ts +0 -33
- package/src/hooks/use-file-drop.ts +0 -121
- package/src/hooks/use-file-picker-labels.ts +0 -28
- package/src/hooks/use-file-picker.tsx +0 -359
- package/src/hooks/use-focus-trap.ts +0 -199
- package/src/hooks/use-hotkey.ts +0 -189
- package/src/hooks/use-large-text.ts +0 -41
- package/src/hooks/use-long-press.ts +0 -140
- package/src/hooks/use-media-query.ts +0 -39
- package/src/hooks/use-mounted.ts +0 -39
- package/src/hooks/use-noindex.ts +0 -37
- package/src/hooks/use-overlay-history.ts +0 -714
- package/src/hooks/use-row-swipe.ts +0 -207
- package/src/hooks/use-search-param-state.ts +0 -434
- package/src/hooks/use-windowed-rows.ts +0 -225
- package/src/i18n/defaults.ts +0 -267
- package/src/i18n/german.ts +0 -2057
- package/src/i18n/kit-labels.tsx +0 -1003
- package/src/i18n/languages.ts +0 -349
- package/src/i18n/locales/de-CH.ts +0 -24
- package/src/i18n/locales/en.ts +0 -267
- package/src/i18n/locales/es.ts +0 -1997
- package/src/i18n/locales/fr.ts +0 -2035
- package/src/i18n/locales/hu.ts +0 -2002
- package/src/i18n/locales/it.ts +0 -2012
- package/src/i18n/locales/zh.ts +0 -1910
- package/src/i18n/plurals.ts +0 -86
- package/src/i18n/review.ts +0 -475
- package/src/i18n/shared-labels.ts +0 -138
- package/src/i18n/swiss.ts +0 -29
- package/src/index.ts +0 -1179
- package/src/landing/access.ts +0 -93
- package/src/landing/landing-actions.tsx +0 -97
- package/src/landing/landing-labels.ts +0 -59
- package/src/landing/landing-sections.tsx +0 -341
- package/src/landing/page-seo.ts +0 -261
- package/src/landing/public-header.tsx +0 -121
- package/src/landing/public-page.tsx +0 -106
- package/src/landing/routing.tsx +0 -225
- package/src/lib/broadcast.ts +0 -52
- package/src/lib/calc.ts +0 -323
- package/src/lib/chip-edge-snap.ts +0 -212
- package/src/lib/clipboard-minus.ts +0 -107
- package/src/lib/clipping.ts +0 -34
- package/src/lib/cn.ts +0 -28
- package/src/lib/column-mapping.ts +0 -234
- package/src/lib/countries.ts +0 -91
- package/src/lib/data-attributes.ts +0 -14
- package/src/lib/date-format.ts +0 -227
- package/src/lib/dates.ts +0 -266
- package/src/lib/day-format.ts +0 -96
- package/src/lib/decimal-marks.ts +0 -108
- package/src/lib/describe-failure.ts +0 -18
- package/src/lib/device-settings.ts +0 -147
- package/src/lib/direction.ts +0 -74
- package/src/lib/document-navigation.ts +0 -17
- package/src/lib/document-title.ts +0 -41
- package/src/lib/download.ts +0 -172
- package/src/lib/float-clearance.ts +0 -70
- package/src/lib/floating-stack.ts +0 -70
- package/src/lib/focus-left.ts +0 -156
- package/src/lib/format.ts +0 -522
- package/src/lib/http-status.ts +0 -38
- package/src/lib/iban.ts +0 -251
- package/src/lib/ime.ts +0 -24
- package/src/lib/intl-cache.ts +0 -25
- package/src/lib/iso-day.ts +0 -13
- package/src/lib/lazy-with-fallback.ts +0 -82
- package/src/lib/logger.ts +0 -69
- package/src/lib/minus.ts +0 -50
- package/src/lib/path-pattern.ts +0 -55
- package/src/lib/person-name.ts +0 -155
- package/src/lib/phone.ts +0 -208
- package/src/lib/plain-text.ts +0 -22
- package/src/lib/presence.ts +0 -18
- package/src/lib/qr-encode.ts +0 -444
- package/src/lib/quiet-focus.ts +0 -32
- package/src/lib/reduced-motion.ts +0 -22
- package/src/lib/refs.ts +0 -22
- package/src/lib/round.ts +0 -43
- package/src/lib/safe-storage.ts +0 -119
- package/src/lib/server-wake.ts +0 -315
- package/src/lib/session-refresh.ts +0 -472
- package/src/lib/share-format.ts +0 -18
- package/src/lib/status-tone.ts +0 -43
- package/src/lib/strip-fade.ts +0 -287
- package/src/lib/table-text.ts +0 -554
- package/src/lib/text-case.ts +0 -19
- package/src/lib/text-match.ts +0 -169
- package/src/lib/thenable.ts +0 -19
- package/src/lib/translation-review.ts +0 -717
- package/src/notifications/device-zone-report.ts +0 -67
- package/src/notifications/feedback-notification-types-labels.ts +0 -33
- package/src/notifications/feedback-notification-types.ts +0 -71
- package/src/notifications/notification-bell.tsx +0 -119
- package/src/notifications/notification-inbox.tsx +0 -309
- package/src/notifications/notification-parts.tsx +0 -81
- package/src/notifications/notification-types-setting.tsx +0 -406
- package/src/notifications/notification-types.ts +0 -89
- package/src/notifications/notifications-labels.ts +0 -287
- package/src/notifications/push-device-setting.tsx +0 -230
- package/src/notifications/push-devices-setting.tsx +0 -374
- package/src/notifications/push-worker.ts +0 -168
- package/src/notifications/quiet-hours-setting.tsx +0 -238
- package/src/notifications/unsubscribe-panel.tsx +0 -200
- package/src/notifications/use-push-device.ts +0 -448
- package/src/offline/local-first.ts +0 -100
- package/src/offline/pwa-update-prompt-labels.ts +0 -9
- package/src/offline/pwa-update-prompt.tsx +0 -78
- package/src/offline/query-persistence.ts +0 -391
- package/src/offline/service-worker-updates.ts +0 -81
- package/src/offline/shared-computer-labels.ts +0 -8
- package/src/offline/shared-computer.tsx +0 -87
- package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
- package/src/offline/sign-out-pending-dialog.tsx +0 -135
- package/src/offline.ts +0 -37
- package/src/push-worker.ts +0 -25
- package/src/rhf/fields.tsx +0 -2187
- package/src/rhf/form-core.tsx +0 -324
- package/src/rhf/form.tsx +0 -12
- package/src/rhf/line-items.tsx +0 -135
- package/src/rhf/use-rhf-wizard-step.ts +0 -104
- package/src/rhf.ts +0 -21
- package/src/search/command-palette-labels.ts +0 -16
- package/src/search/command-palette.tsx +0 -624
- package/src/search/global-search-labels.ts +0 -13
- package/src/search/global-search.tsx +0 -660
- package/src/search/search-index.ts +0 -283
- package/src/search/use-command-key.ts +0 -23
- package/src/search.ts +0 -14
- package/src/settings/device-reset-setting-labels.ts +0 -20
- package/src/settings/device-reset-setting.tsx +0 -116
- package/src/settings/settings-card-classes.ts +0 -11
- package/src/settings/settings-catalogue.ts +0 -150
- package/src/settings/settings-context.ts +0 -33
- package/src/settings/settings-heading.tsx +0 -67
- package/src/settings/settings-labels.ts +0 -87
- package/src/settings/settings-layout.tsx +0 -471
- package/src/settings/settings-section.tsx +0 -108
- package/src/settings/use-account-appearance.ts +0 -148
- package/src/settings/use-account-language.ts +0 -146
- package/src/settings/use-settings-focus.ts +0 -128
- package/src/settings/use-settings-route.ts +0 -258
- package/src/shell/account-menu-language.tsx +0 -130
- package/src/shell/account-menu-text-size.tsx +0 -80
- package/src/shell/admin-menu.tsx +0 -183
- package/src/shell/app-shell-labels.ts +0 -11
- package/src/shell/app-shell.tsx +0 -1560
- package/src/shell/auth-layout.tsx +0 -186
- package/src/shell/company-switcher-labels.ts +0 -13
- package/src/shell/company-switcher.tsx +0 -136
- package/src/shell/dev-menu.tsx +0 -257
- package/src/shell/fill-height.tsx +0 -45
- package/src/shell/language-code.ts +0 -5
- package/src/shell/option-switcher-menu.tsx +0 -84
- package/src/shell/sync-status-indicator.tsx +0 -303
- package/src/shell/top-bar-actions.tsx +0 -87
- package/src/shell/top-bar-brand.tsx +0 -78
- package/src/shell/top-bar-parts-labels.ts +0 -148
- package/src/shell/top-bar.tsx +0 -40
- package/src/shell/topbar-action-menu.tsx +0 -567
- package/src/shell/topbar-controls.tsx +0 -269
- package/src/shell/tour-menu.tsx +0 -187
- package/src/shell.ts +0 -108
- package/src/sync/db.ts +0 -129
- package/src/sync/engine.ts +0 -1011
- package/src/sync/hooks.ts +0 -98
- package/src/sync/testing.ts +0 -73
- package/src/sync/wire.ts +0 -61
- package/src/sync.ts +0 -37
- package/src/table-text.ts +0 -8
- package/src/testing/css-variables.generated.ts +0 -112
- package/src/testing/css-variables.ts +0 -189
- package/src/testing/index.ts +0 -29
- package/src/testing/react.ts +0 -58
- package/src/testing/tour.ts +0 -41
- package/src/testing/viewport.ts +0 -357
- package/src/theme/chart-palette.ts +0 -232
- package/src/theme/color.ts +0 -272
- package/src/theme/contrast-tokens.ts +0 -268
- package/src/theme/contrast.ts +0 -250
- package/src/theme/palette-derive.ts +0 -825
- package/src/theme/palette-presets.ts +0 -544
- package/src/theme/palette-store.ts +0 -108
- package/src/theme/text-size.ts +0 -289
- package/src/theme/theme-store.ts +0 -157
- package/src/tour/tour-catalogue-labels.ts +0 -75
- package/src/tour/tour-catalogue.tsx +0 -335
- package/src/tour/tour-dock.ts +0 -134
- package/src/tour/tour-engine.ts +0 -37
- package/src/tour/tour-gates.ts +0 -218
- package/src/tour/tour-guide-button.tsx +0 -64
- package/src/tour/tour-guide-labels.ts +0 -15
- package/src/tour/tour-info.ts +0 -144
- package/src/tour/tour-labels.ts +0 -23
- package/src/tour/tour-overlay.tsx +0 -1170
- package/src/tour/tour.tsx +0 -750
- package/src/tour/use-tour-progress.ts +0 -671
- package/src/tour/use-tour-url-sync.ts +0 -266
- package/src/tour.ts +0 -21
- package/src/vite-env.d.ts +0 -23
- package/src/wizard/stepper-nav.tsx +0 -360
- package/src/wizard/types.ts +0 -275
- package/src/wizard/use-wizard.ts +0 -560
- package/src/wizard/validation.ts +0 -39
- package/src/wizard/wizard-context.tsx +0 -74
- package/src/wizard/wizard-labels.ts +0 -29
- package/src/wizard/wizard-step.tsx +0 -76
- package/src/wizard/wizard-summary.tsx +0 -84
- package/src/wizard.ts +0 -15
package/dist/lib/focus-left.d.ts
CHANGED
|
@@ -52,9 +52,55 @@ export declare function tabStopsAround(frame: Element, limit?: number): {
|
|
|
52
52
|
/**
|
|
53
53
|
* Focus the first of `candidates` that is still in the page and takes the focus; true
|
|
54
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.
|
|
55
|
+
* (detached, `display: none`, disabled since) fails without a word. An SVG element counts
|
|
56
|
+
* (a chart's tile, a slice); `<body>` and `<html>` never do — "focused" there is the
|
|
57
|
+
* focus lost, not given (0.52 code review 2026-10-31, held to 0.55).
|
|
56
58
|
*/
|
|
57
59
|
export declare function focusFirst(candidates: Iterable<Element | null | undefined>): boolean;
|
|
60
|
+
/** {@link returnFocus}'s three answers to "where was this opened from". */
|
|
61
|
+
export interface FocusReturn {
|
|
62
|
+
/** The control it was opened from: the overlay's trigger when it names one, else what
|
|
63
|
+
* held the focus as it opened. */
|
|
64
|
+
opener?: Element | null;
|
|
65
|
+
/** Where the focus goes when the opener cannot take it, nearest first — the overlay's
|
|
66
|
+
* stated fallback. */
|
|
67
|
+
fallback?: Iterable<Element | null | undefined>;
|
|
68
|
+
/** What is closing, while it is still in the page (a panel on its way out, a list
|
|
69
|
+
* about to unmount): a focus inside it is one to give back. */
|
|
70
|
+
closing?: Element | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Where the focus goes when an overlay closes, or a control holding it leaves the page —
|
|
74
|
+
* the kit's ONE rule for it (0.52 code review 2026-10-31, held to 0.55). The dialogs, the
|
|
75
|
+
* popovers and menus, the drawers, the phone sheets, the tour, AlertBanner's × and the
|
|
76
|
+
* Treemap's tiles each decided it themselves, and they drifted: the trap gave the focus
|
|
77
|
+
* back whenever its opener was still connected — over a focus the app had just placed —
|
|
78
|
+
* and left it on `<body>` when it was not; the date and month pickers and the floating
|
|
79
|
+
* panel moved it only when it was lost; the dropdown, the combobox and the column drawer
|
|
80
|
+
* moved it to their trigger every time, unchecked; and a Treemap tile's note of where to
|
|
81
|
+
* go once outlived the commit it was for and took the focus back, from wherever the
|
|
82
|
+
* reader was, when the map was shown again.
|
|
83
|
+
*
|
|
84
|
+
* 1. **Only a lost focus is moved** — on `<body>`, on nothing or on a removed node
|
|
85
|
+
* ({@link focusIsLost}) — or one still inside what is `closing`. A focus that went
|
|
86
|
+
* somewhere real is the user's (an outside press landed on a field) or the app's (its
|
|
87
|
+
* own `focus()` in `onClose`), and stays.
|
|
88
|
+
* 2. **To the opener**, if it is still in the page and takes the focus: `focus()` is
|
|
89
|
+
* checked, as a disabled, hidden or inert opener refuses it without a word.
|
|
90
|
+
* 3. **Else to the stated fallback**, the first of it that does — each caller names its
|
|
91
|
+
* own: the trap the stops that stood around its opener, a banner the stops around its
|
|
92
|
+
* ×, the map the tile that holds its tab stop.
|
|
93
|
+
* 4. **Never to `<body>`**: no `focus()` on a node that has left, none on `<body>`, no
|
|
94
|
+
* `blur()`. When nothing can take the focus — the page under the overlay was replaced
|
|
95
|
+
* — it is left for the page's own handling of a new page, not guessed at.
|
|
96
|
+
* 5. **Only in the closing commit**: called from the commit that closes or removes (a
|
|
97
|
+
* handler that closes, a ref callback, a layout effect, an effect's cleanup), and
|
|
98
|
+
* nothing handed to it is kept. A note gathered earlier, where a removed node can no
|
|
99
|
+
* longer be measured from, is spent or dropped in that commit.
|
|
100
|
+
*
|
|
101
|
+
* True when it moved the focus.
|
|
102
|
+
*/
|
|
103
|
+
export declare function returnFocus({ opener, fallback, closing }: FocusReturn): boolean;
|
|
58
104
|
/**
|
|
59
105
|
* Focus a container that is not a tab stop of its own — a table, a list — as the place
|
|
60
106
|
* the user is in, without making it one: `tabindex="-1"` for as long as it holds the
|
package/dist/lib/focus-left.js
CHANGED
|
@@ -42,12 +42,20 @@ function tabStopsAround(frame, limit = 5) {
|
|
|
42
42
|
}
|
|
43
43
|
function focusFirst(candidates) {
|
|
44
44
|
for (const el of candidates) {
|
|
45
|
-
if (!(el instanceof HTMLElement) || !el.isConnected) continue;
|
|
45
|
+
if (!(el instanceof HTMLElement || el instanceof SVGElement) || !el.isConnected) continue;
|
|
46
|
+
if (el === el.ownerDocument.body || el === el.ownerDocument.documentElement) continue;
|
|
46
47
|
el.focus();
|
|
47
|
-
if (
|
|
48
|
+
if (el.ownerDocument.activeElement === el) return true;
|
|
48
49
|
}
|
|
49
50
|
return false;
|
|
50
51
|
}
|
|
52
|
+
function returnFocus({ opener, fallback = [], closing }) {
|
|
53
|
+
if (typeof document === "undefined") return false;
|
|
54
|
+
const active = document.activeElement;
|
|
55
|
+
const inside = Boolean(closing?.isConnected && active && closing.contains(active));
|
|
56
|
+
if (!inside && !focusIsLost()) return false;
|
|
57
|
+
return focusFirst([opener, ...fallback]);
|
|
58
|
+
}
|
|
51
59
|
function focusContainer(el) {
|
|
52
60
|
if (!el.hasAttribute("tabindex")) {
|
|
53
61
|
el.setAttribute("tabindex", "-1");
|
|
@@ -60,6 +68,7 @@ export {
|
|
|
60
68
|
focusContainer,
|
|
61
69
|
focusFirst,
|
|
62
70
|
focusIsLost,
|
|
71
|
+
returnFocus,
|
|
63
72
|
tabStops,
|
|
64
73
|
tabStopsAround,
|
|
65
74
|
whenFocusLeftForNothing
|
|
@@ -1 +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"]}
|
|
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. These helpers are for\n// the controls that leave the page while the focus is IN them, and decide where it goes\n// instead; `returnFocus` is the one rule for that and for an overlay giving the focus\n// back as it closes (0.52 code review 2026-10-31, held to 0.55; use-focus-trap reads it).\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. An SVG element counts\n * (a chart's tile, a slice); `<body>` and `<html>` never do — \"focused\" there is the\n * focus lost, not given (0.52 code review 2026-10-31, held to 0.55).\n */\nexport function focusFirst(candidates: Iterable<Element | null | undefined>): boolean {\n for (const el of candidates) {\n if (!(el instanceof HTMLElement || el instanceof SVGElement) || !el.isConnected) continue;\n if (el === el.ownerDocument.body || el === el.ownerDocument.documentElement) continue;\n el.focus();\n if (el.ownerDocument.activeElement === el) return true;\n }\n return false;\n}\n\n/** {@link returnFocus}'s three answers to \"where was this opened from\". */\nexport interface FocusReturn {\n /** The control it was opened from: the overlay's trigger when it names one, else what\n * held the focus as it opened. */\n opener?: Element | null;\n /** Where the focus goes when the opener cannot take it, nearest first — the overlay's\n * stated fallback. */\n fallback?: Iterable<Element | null | undefined>;\n /** What is closing, while it is still in the page (a panel on its way out, a list\n * about to unmount): a focus inside it is one to give back. */\n closing?: Element | null;\n}\n\n/**\n * Where the focus goes when an overlay closes, or a control holding it leaves the page —\n * the kit's ONE rule for it (0.52 code review 2026-10-31, held to 0.55). The dialogs, the\n * popovers and menus, the drawers, the phone sheets, the tour, AlertBanner's × and the\n * Treemap's tiles each decided it themselves, and they drifted: the trap gave the focus\n * back whenever its opener was still connected — over a focus the app had just placed —\n * and left it on `<body>` when it was not; the date and month pickers and the floating\n * panel moved it only when it was lost; the dropdown, the combobox and the column drawer\n * moved it to their trigger every time, unchecked; and a Treemap tile's note of where to\n * go once outlived the commit it was for and took the focus back, from wherever the\n * reader was, when the map was shown again.\n *\n * 1. **Only a lost focus is moved** — on `<body>`, on nothing or on a removed node\n * ({@link focusIsLost}) — or one still inside what is `closing`. A focus that went\n * somewhere real is the user's (an outside press landed on a field) or the app's (its\n * own `focus()` in `onClose`), and stays.\n * 2. **To the opener**, if it is still in the page and takes the focus: `focus()` is\n * checked, as a disabled, hidden or inert opener refuses it without a word.\n * 3. **Else to the stated fallback**, the first of it that does — each caller names its\n * own: the trap the stops that stood around its opener, a banner the stops around its\n * ×, the map the tile that holds its tab stop.\n * 4. **Never to `<body>`**: no `focus()` on a node that has left, none on `<body>`, no\n * `blur()`. When nothing can take the focus — the page under the overlay was replaced\n * — it is left for the page's own handling of a new page, not guessed at.\n * 5. **Only in the closing commit**: called from the commit that closes or removes (a\n * handler that closes, a ref callback, a layout effect, an effect's cleanup), and\n * nothing handed to it is kept. A note gathered earlier, where a removed node can no\n * longer be measured from, is spent or dropped in that commit.\n *\n * True when it moved the focus.\n */\nexport function returnFocus({ opener, fallback = [], closing }: FocusReturn): boolean {\n if (typeof document === \"undefined\") return false;\n const active = document.activeElement;\n const inside = Boolean(closing?.isConnected && active && closing.contains(active));\n if (!inside && !focusIsLost()) return false;\n return focusFirst([opener, ...fallback]);\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":";AA4BA,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;AASO,SAAS,WAAW,YAA2D;AACpF,aAAW,MAAM,YAAY;AAC3B,QAAI,EAAE,cAAc,eAAe,cAAc,eAAe,CAAC,GAAG,YAAa;AACjF,QAAI,OAAO,GAAG,cAAc,QAAQ,OAAO,GAAG,cAAc,gBAAiB;AAC7E,OAAG,MAAM;AACT,QAAI,GAAG,cAAc,kBAAkB,GAAI,QAAO;AAAA,EACpD;AACA,SAAO;AACT;AA8CO,SAAS,YAAY,EAAE,QAAQ,WAAW,CAAC,GAAG,QAAQ,GAAyB;AACpF,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,SAAS,SAAS;AACxB,QAAM,SAAS,QAAQ,SAAS,eAAe,UAAU,QAAQ,SAAS,MAAM,CAAC;AACjF,MAAI,CAAC,UAAU,CAAC,YAAY,EAAG,QAAO;AACtC,SAAO,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;AACzC;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.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { type MinusStyle } from "./minus.js";
|
|
|
2
2
|
export type { MinusStyle } from "./minus.js";
|
|
3
3
|
export { formatWithMinus } from "./minus.js";
|
|
4
4
|
/**
|
|
5
|
-
* Number, money, percentage, date and relative-time formatting — `Intl`,
|
|
6
|
-
* for the figures an app prints OUTSIDE a kit component.
|
|
5
|
+
* Number, money, percentage, duration (0.54), date and relative-time formatting — `Intl`,
|
|
6
|
+
* in one place, for the figures an app prints OUTSIDE a kit component.
|
|
7
7
|
*
|
|
8
8
|
* Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned
|
|
9
9
|
* `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`
|
|
@@ -83,6 +83,18 @@ export interface FormatMoneyOptions extends FormatNumberBaseOptions {
|
|
|
83
83
|
/** `"symbol"` (default), `"narrowSymbol"`, `"code"` ("CHF 12.00") or `"name"`. */
|
|
84
84
|
currencyDisplay?: Intl.NumberFormatOptions["currencyDisplay"];
|
|
85
85
|
}
|
|
86
|
+
export interface FormatDurationOptions extends BaseOptions {
|
|
87
|
+
/**
|
|
88
|
+
* How the units are written — `Intl.NumberFormat`'s `unitDisplay`, in `locale`:
|
|
89
|
+
* - `"narrow"` (default): "3m 12s", "3 Min. 12 Sek." (de-CH), "3min 12s" (fr) —
|
|
90
|
+
* keksdose's scan readout;
|
|
91
|
+
* - `"short"`: "3 min 12 sec", "3 Min. 12 Sek.", "3 min 12 s";
|
|
92
|
+
* - `"long"`: "3 minutes 12 seconds", "3 Minuten 12 Sekunden", "3 minutes 12 secondes".
|
|
93
|
+
*/
|
|
94
|
+
unitDisplay?: "narrow" | "short" | "long";
|
|
95
|
+
/** The minus of a negative duration — {@link FormatNumberBaseOptions.minus}. */
|
|
96
|
+
minus?: MinusStyle;
|
|
97
|
+
}
|
|
86
98
|
export interface FormatPercentOptions extends FormatNumberBaseOptions {
|
|
87
99
|
/**
|
|
88
100
|
* `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read
|
|
@@ -105,6 +117,27 @@ export declare function formatMoney(value: FormattableNumber, currency: string,
|
|
|
105
117
|
/** A percentage in `locale` ("12%", "12 %", "+1.25%"). See {@link FormatPercentOptions.ratio}
|
|
106
118
|
* for what `value` means. Default digits: at most one. */
|
|
107
119
|
export declare function formatPercent(value: FormattableNumber, options?: FormatPercentOptions): string;
|
|
120
|
+
/**
|
|
121
|
+
* A number of SECONDS as an elapsed time in `locale`: "42s", "3m 12s", "1h 0m 5s" — and in
|
|
122
|
+
* German "3 Min. 12 Sek.", in French "3min 12s" (keksdose audit follow-up, 2026-11-01:
|
|
123
|
+
* keksdose's `formatElapsed`, the scan card's readout, moves here). Each unit is
|
|
124
|
+
* `Intl.NumberFormat`'s `style: "unit"`, so the digits and the unit's spelling follow
|
|
125
|
+
* `locale`; `unitDisplay` picks the form, narrow by default.
|
|
126
|
+
*
|
|
127
|
+
* - Seconds alone under a minute ("0s", "59s"); minutes and seconds under an hour ("1m 0s",
|
|
128
|
+
* "59m 59s"); from an hour on, hours, minutes and seconds ("1h 0m 0s"). Hours are the
|
|
129
|
+
* largest unit: a day and a bit is "27h 46m 40s". Every unit below the first is printed,
|
|
130
|
+
* zero too, so a ticking readout keeps its shape from one second to the next.
|
|
131
|
+
* - Whole seconds, cut toward zero: a timer reads "59s" until the minute is full.
|
|
132
|
+
* - The parts are joined by a space, as the readout always was — not `Intl.ListFormat`'s
|
|
133
|
+
* "3 Min. und 12 Sek.", which is a sentence rather than a figure.
|
|
134
|
+
* - Negative: the minus on the first unit, as `Intl.DurationFormat` writes one — "−3m 12s",
|
|
135
|
+
* with the kit's minus (`minus`). An elapsed time from a server's clock can come out a
|
|
136
|
+
* second below zero; clamp it before, as keksdose's card does, when that is not news.
|
|
137
|
+
* - `null` / `undefined` print `empty`; `NaN`, ±`Infinity` and a non-numeric string too,
|
|
138
|
+
* with the formatters' development warning. A numeric string ("185") is a number.
|
|
139
|
+
*/
|
|
140
|
+
export declare function formatDuration(seconds: FormattableNumber, options?: FormatDurationOptions): string;
|
|
108
141
|
/**
|
|
109
142
|
* The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:
|
|
110
143
|
* - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`
|
|
@@ -180,6 +213,8 @@ export interface KitFormat {
|
|
|
180
213
|
formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;
|
|
181
214
|
formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;
|
|
182
215
|
formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;
|
|
216
|
+
/** 0.54: {@link formatDuration}, a number of seconds as an elapsed time. */
|
|
217
|
+
formatDuration: (seconds: FormattableNumber, options?: WithoutLocale<FormatDurationOptions>) => string;
|
|
183
218
|
formatDate: (value: DateInput, style?: FormatDateStyle | Intl.DateTimeFormatOptions, options?: WithoutLocale<FormatDateOptions>) => string;
|
|
184
219
|
formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;
|
|
185
220
|
}
|
package/dist/lib/format.js
CHANGED
|
@@ -40,7 +40,7 @@ function devWarnings() {
|
|
|
40
40
|
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
41
41
|
}
|
|
42
42
|
const warnedSites = /* @__PURE__ */ new Set();
|
|
43
|
-
const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\b/;
|
|
43
|
+
const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|formatDuration|toFormattable|warnInvalid|callSite)\b/;
|
|
44
44
|
function callSite() {
|
|
45
45
|
const frames = (new Error().stack ?? "").split("\n").slice(1);
|
|
46
46
|
return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? "";
|
|
@@ -104,6 +104,28 @@ function formatPercent(value, options = {}) {
|
|
|
104
104
|
});
|
|
105
105
|
return formatWithMinus(formatter, ratio, options.minus);
|
|
106
106
|
}
|
|
107
|
+
const DURATION_UNITS = [
|
|
108
|
+
["hour", 3600],
|
|
109
|
+
["minute", 60],
|
|
110
|
+
["second", 1]
|
|
111
|
+
];
|
|
112
|
+
function formatDuration(seconds, options = {}) {
|
|
113
|
+
const n = toFormattable("formatDuration", seconds);
|
|
114
|
+
if (n === void 0) return options.empty ?? EMPTY_FORMATTED_VALUE;
|
|
115
|
+
const unitDisplay = options.unitDisplay ?? "narrow";
|
|
116
|
+
const negative = n < 0;
|
|
117
|
+
let rest = Math.trunc(Math.abs(n));
|
|
118
|
+
const parts = [];
|
|
119
|
+
for (const [unit, size] of DURATION_UNITS) {
|
|
120
|
+
const count = Math.floor(rest / size);
|
|
121
|
+
rest -= count * size;
|
|
122
|
+
if (parts.length === 0 && count === 0 && unit !== "second") continue;
|
|
123
|
+
const formatter = numberFormatter(options.locale, { style: "unit", unit, unitDisplay });
|
|
124
|
+
const signed = negative && parts.length === 0 && count > 0 ? -count : count;
|
|
125
|
+
parts.push(formatWithMinus(formatter, signed, options.minus));
|
|
126
|
+
}
|
|
127
|
+
return parts.join(" ");
|
|
128
|
+
}
|
|
107
129
|
const DATE_STYLES = {
|
|
108
130
|
short: { dateStyle: "short" },
|
|
109
131
|
medium: { dateStyle: "medium" },
|
|
@@ -187,6 +209,7 @@ function useKitFormat(locale) {
|
|
|
187
209
|
formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),
|
|
188
210
|
formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),
|
|
189
211
|
formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),
|
|
212
|
+
formatDuration: (seconds, options) => formatDuration(seconds, { locale: kitLocale, ...options }),
|
|
190
213
|
formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),
|
|
191
214
|
formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options })
|
|
192
215
|
}),
|
|
@@ -211,6 +234,7 @@ export {
|
|
|
211
234
|
EMPTY_FORMATTED_VALUE,
|
|
212
235
|
currencyMinorDigits,
|
|
213
236
|
formatDate,
|
|
237
|
+
formatDuration,
|
|
214
238
|
formatMoney,
|
|
215
239
|
formatNumber,
|
|
216
240
|
formatPercent,
|
package/dist/lib/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale, useKitTimeZone } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\nimport { formatWithMinus, type MinusStyle } from \"./minus\";\nimport { roundHalfAway } from \"./round\";\n\nexport type { MinusStyle } from \"./minus\";\n// 0.50 (KIT-1 follow-up): the kit's minus for an app's OWN `Intl.NumberFormat` wrapper.\nexport { formatWithMinus } from \"./minus\";\n\n/**\n * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,\n * for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, an unparseable date — formats as `empty`\n * (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or \"Invalid\n * Date\". A number may also arrive as a numeric STRING (0.49, KIT-2) — \"1850.00\", \"-12.5\",\n * \"0\": a Decimal field off a Python API — and is formatted like the number. Anything\n * else (a non-numeric string, an object, `NaN`, ±`Infinity`) prints `empty` too, but is\n * a bug upstream rather than a gap in the data, so a development build says so on the\n * console, once per call site. Zero is a figure: `0` and `\"0\"` print \"CHF 0.00\", never\n * the dash.\n *\n * A negative figure carries the typographic minus, U+2212 (0.49, KIT-1), in every\n * locale — `Intl` writes a hyphen in most. `minus: \"ascii\"` for text a machine reads\n * back (a CSV, the clipboard).\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n /**\n * The minus sign of a negative figure (0.49, KIT-1). `\"typographic\"` (default):\n * U+2212, as wide as \"+\" and the digits, as `SignedAmount` prints it — in every\n * locale, also where `Intl` writes a hyphen. `\"ascii\"`: U+002D, for an export a\n * machine parses back (a CSV download, the clipboard) — there, also pass\n * `options: { useGrouping: false }` or write the raw number.\n */\n minus?: MinusStyle;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\n/**\n * What the number formatters take (0.49, KIT-2): a number, a numeric string (\"1850.00\",\n * \"-12.5\", \"1e3\" — what a Decimal arrives as in JSON), or `null` / `undefined` for no\n * value. A string is read with `Number()`, so a figure with more significant digits than\n * a double holds (about 15–17) is printed as its nearest double.\n */\nexport type FormattableNumber = number | string | null | undefined;\n\n/** A plain decimal: optional sign, digits with an optional point, an optional exponent.\n * No grouping marks, no decimal comma, no currency — those are formatted text, and\n * reading them back is a locale's guess. */\nconst NUMERIC_STRING = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one (the consumer's bundler makes `import.meta.env.DEV` false) and never\n * under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\nconst warnedSites = new Set<string>();\n/** This module's own frames, skipped to find the caller (\"Object.formatMoney\" is a\n * `useKitFormat` wrapper). */\nconst OWN_FRAME = /\\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\\b/;\n\n/** The first stack frame outside the formatters: where the bad value was passed. */\nfunction callSite(): string {\n const frames = (new Error().stack ?? \"\").split(\"\\n\").slice(1);\n return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? \"\";\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") return String(value);\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\n/** A development build's console note about an invalid value — once per call site. */\nfunction warnInvalid(fn: string, value: unknown): void {\n if (!devWarnings()) return;\n const site = callSite();\n const key = `${fn}|${site || typeof value}`;\n if (warnedSites.has(key)) return;\n warnedSites.add(key);\n const type = Array.isArray(value) ? \"array\" : typeof value;\n console.warn(\n `[ui-kit] ${fn} got ${describeValue(value)} (${type}), which is not a number: printed the empty value. ` +\n \"Pass a number, a numeric string, or null/undefined for a missing value.\" +\n (site ? `\\n at ${site.replace(/^at /, \"\")}` : \"\"),\n );\n}\n\n/**\n * `value` as a finite number to format, or `undefined` to print `empty`. `null` and\n * `undefined` are a missing value and pass quietly; anything that is neither a finite\n * number nor a numeric string is an invalid one — ±Infinity included (lenkbank, adopting\n * 0.13: a division by zero upstream; \"∞\" in a money column reads like data) — and a\n * development build warns. `-0` is folded into `0`: zero carries no sign.\n */\nfunction toFormattable(fn: string, value: unknown): number | undefined {\n if (value === null || value === undefined) return undefined;\n let n: number | undefined;\n if (typeof value === \"number\") n = value;\n else if (typeof value === \"string\" && NUMERIC_STRING.test(value.trim())) n = Number(value.trim());\n if (n === undefined || !Number.isFinite(n)) {\n warnInvalid(fn, value);\n return undefined;\n }\n return n + 0;\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: FormattableNumber, options: FormatNumberOptions = {}): string {\n const n = toFormattable(\"formatNumber\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = formatWithMinus(numberFormatter(options.locale, numberOptions(options)), n, options.minus);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(value: FormattableNumber, currency: string, options: FormatMoneyOptions = {}): string {\n const n = toFormattable(\"formatMoney\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const formatter = numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, n, options.minus);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: FormattableNumber, options: FormatPercentOptions = {}): string {\n const n = toFormattable(\"formatPercent\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? n / 100 : n;\n const formatter = numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, ratio, options.minus);\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. A bare\n * \"YYYY-MM-DD\" ignores it: a calendar day is the day it names in every zone. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (isBareIsoDay(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n // A bare \"YYYY-MM-DD\" is a calendar day, which has no zone: it is the day it names\n // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the\n // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).\n if (isBareIsoDay(value)) {\n const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\n return dateFormatter(options.locale, { ...base, timeZone: \"UTC\" }).format(day);\n }\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The zone of the date printed past `absoluteAfterDays` (0.46), and (0.51.7) the zone\n * whose calendar days \"yesterday\" and \"2 days ago\" count. Left out: the device's. */\n timeZone?: string;\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\n/**\n * `d`'s calendar day in `timeZone` (left out: the device's), as a whole number of days:\n * that day's UTC midnight over a day's length. Two of them subtract to whole calendar\n * days — a 23- or 25-hour DST day still counts one.\n */\nfunction dayNumber(d: Date, timeZone: string | undefined): number {\n if (!timeZone) return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY;\n // `en-US`: Gregorian, Latin digits — the parts are read back as numbers.\n const fields: Intl.DateTimeFormatOptions = { timeZone, year: \"numeric\", month: \"numeric\", day: \"numeric\" };\n const parts = dateFormatter(\"en-US\", fields).formatToParts(d);\n const part = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((p) => p.type === type)?.value);\n return Date.UTC(part(\"year\"), part(\"month\") - 1, part(\"day\")) / DAY;\n}\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded; days are\n * CALENDAR days (0.51.7), in `timeZone` when given: \"yesterday\" is the day before today,\n * however few hours ago that was.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n if (unit === \"day\") {\n // By the calendar, not in 24-hour units (refactor audit 2026-10-30, DSP-7): rounded\n // elapsed time made 00:10 seen at 23:50 \"yesterday\" and 01:00 yesterday seen at 23:30\n // \"2 days ago\", beside an exact date (DateMark's second line, the feedback thread's\n // tooltip) that said otherwise. Counted in `timeZone`, the zone that date is printed\n // in; a bare \"YYYY-MM-DD\" is the day it names wherever it is read, as in formatDate.\n // Still the same day (22 hours and more since just after midnight): in hours.\n const bareDay = isBareIsoDay(value);\n const days = dayNumber(d, bareDay ? undefined : options.timeZone) - dayNumber(now, options.timeZone);\n return days === 0 ? rtf.format(Math.round(diff / HOUR), \"hour\") : rtf.format(days, \"day\");\n }\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;\n formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n // 0.46 (§10.3): the provider's `timeZone` for timestamps; a call's own wins, and a bare\n // \"YYYY-MM-DD\" ignores it (formatDate).\n const timeZone = useKitTimeZone();\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),\n }),\n [kitLocale, timeZone],\n );\n}\n\n/* ── Currency minor units ──────────────────────────────────────────────────── */\n\n/**\n * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,\n * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule\n * AmountInput settles a typed figure by, for a host that needs it outside the field.\n */\nexport function currencyMinorDigits(currency: string | null | undefined): number | undefined {\n if (!currency) return undefined;\n try {\n return new Intl.NumberFormat(\"en\", { style: \"currency\", currency }).resolvedOptions().maximumFractionDigits;\n } catch {\n return undefined;\n }\n}\n\n/** The options form of {@link roundToCurrency}'s third argument. */\nexport interface RoundToCurrencyOptions {\n /** Decimals to round to, whatever the currency — the plain `digits` argument. */\n digits?: number;\n /**\n * Decimals for a currency `Intl` cannot resolve — no code yet (`null`, `\"\"`), or a\n * malformed one (\"EURO\", \"€\"). Left out, such a value comes back unchanged, as\n * before. Note that `Intl` resolves ANY well-formed three-letter code, a made-up\n * \"XYZ\" included (to 2), so this is the case of a missing or broken code, not of a\n * rare currency.\n */\n fallbackDigits?: number;\n}\n\n/**\n * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,\n * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *\n * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency\n * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an\n * FX estimate set into an AmountInput showed four decimals) — unless the options form\n * gives `fallbackDigits`: `roundToCurrency(v, code, { fallbackDigits: 2 })` rounds a\n * figure whose currency is not chosen yet to cents rather than leaving 0.1 + 0.2 as is.\n * A finite value never comes back NaN or -0, however small or large (BUG-11):\n * 0.1 + 0.2 - 0.3 → 0, -0.004 → 0.\n */\nexport function roundToCurrency(\n value: number,\n currency: string | null | undefined,\n digits?: number | RoundToCurrencyOptions,\n): number {\n const options = typeof digits === \"object\" && digits !== null ? digits : { digits };\n const places = options.digits ?? currencyMinorDigits(currency) ?? options.fallbackDigits;\n if (places === undefined) return value;\n return roundHalfAway(value, places);\n}\n"],"mappings":"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","names":["formatWithMinus"]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale, useKitTimeZone } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\nimport { formatWithMinus, type MinusStyle } from \"./minus\";\nimport { roundHalfAway } from \"./round\";\n\nexport type { MinusStyle } from \"./minus\";\n// 0.50 (KIT-1 follow-up): the kit's minus for an app's OWN `Intl.NumberFormat` wrapper.\nexport { formatWithMinus } from \"./minus\";\n\n/**\n * Number, money, percentage, duration (0.54), date and relative-time formatting — `Intl`,\n * in one place, for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, an unparseable date — formats as `empty`\n * (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or \"Invalid\n * Date\". A number may also arrive as a numeric STRING (0.49, KIT-2) — \"1850.00\", \"-12.5\",\n * \"0\": a Decimal field off a Python API — and is formatted like the number. Anything\n * else (a non-numeric string, an object, `NaN`, ±`Infinity`) prints `empty` too, but is\n * a bug upstream rather than a gap in the data, so a development build says so on the\n * console, once per call site. Zero is a figure: `0` and `\"0\"` print \"CHF 0.00\", never\n * the dash.\n *\n * A negative figure carries the typographic minus, U+2212 (0.49, KIT-1), in every\n * locale — `Intl` writes a hyphen in most. `minus: \"ascii\"` for text a machine reads\n * back (a CSV, the clipboard).\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n /**\n * The minus sign of a negative figure (0.49, KIT-1). `\"typographic\"` (default):\n * U+2212, as wide as \"+\" and the digits, as `SignedAmount` prints it — in every\n * locale, also where `Intl` writes a hyphen. `\"ascii\"`: U+002D, for an export a\n * machine parses back (a CSV download, the clipboard) — there, also pass\n * `options: { useGrouping: false }` or write the raw number.\n */\n minus?: MinusStyle;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatDurationOptions extends BaseOptions {\n /**\n * How the units are written — `Intl.NumberFormat`'s `unitDisplay`, in `locale`:\n * - `\"narrow\"` (default): \"3m 12s\", \"3 Min. 12 Sek.\" (de-CH), \"3min 12s\" (fr) —\n * keksdose's scan readout;\n * - `\"short\"`: \"3 min 12 sec\", \"3 Min. 12 Sek.\", \"3 min 12 s\";\n * - `\"long\"`: \"3 minutes 12 seconds\", \"3 Minuten 12 Sekunden\", \"3 minutes 12 secondes\".\n */\n unitDisplay?: \"narrow\" | \"short\" | \"long\";\n /** The minus of a negative duration — {@link FormatNumberBaseOptions.minus}. */\n minus?: MinusStyle;\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\n/**\n * What the number formatters take (0.49, KIT-2): a number, a numeric string (\"1850.00\",\n * \"-12.5\", \"1e3\" — what a Decimal arrives as in JSON), or `null` / `undefined` for no\n * value. A string is read with `Number()`, so a figure with more significant digits than\n * a double holds (about 15–17) is printed as its nearest double.\n */\nexport type FormattableNumber = number | string | null | undefined;\n\n/** A plain decimal: optional sign, digits with an optional point, an optional exponent.\n * No grouping marks, no decimal comma, no currency — those are formatted text, and\n * reading them back is a locale's guess. */\nconst NUMERIC_STRING = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one (the consumer's bundler makes `import.meta.env.DEV` false) and never\n * under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\nconst warnedSites = new Set<string>();\n/** This module's own frames, skipped to find the caller (\"Object.formatMoney\" is a\n * `useKitFormat` wrapper). */\nconst OWN_FRAME = /\\b(?:formatNumber|formatMoney|formatPercent|formatDuration|toFormattable|warnInvalid|callSite)\\b/;\n\n/** The first stack frame outside the formatters: where the bad value was passed. */\nfunction callSite(): string {\n const frames = (new Error().stack ?? \"\").split(\"\\n\").slice(1);\n return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? \"\";\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") return String(value);\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\n/** A development build's console note about an invalid value — once per call site. */\nfunction warnInvalid(fn: string, value: unknown): void {\n if (!devWarnings()) return;\n const site = callSite();\n const key = `${fn}|${site || typeof value}`;\n if (warnedSites.has(key)) return;\n warnedSites.add(key);\n const type = Array.isArray(value) ? \"array\" : typeof value;\n console.warn(\n `[ui-kit] ${fn} got ${describeValue(value)} (${type}), which is not a number: printed the empty value. ` +\n \"Pass a number, a numeric string, or null/undefined for a missing value.\" +\n (site ? `\\n at ${site.replace(/^at /, \"\")}` : \"\"),\n );\n}\n\n/**\n * `value` as a finite number to format, or `undefined` to print `empty`. `null` and\n * `undefined` are a missing value and pass quietly; anything that is neither a finite\n * number nor a numeric string is an invalid one — ±Infinity included (lenkbank, adopting\n * 0.13: a division by zero upstream; \"∞\" in a money column reads like data) — and a\n * development build warns. `-0` is folded into `0`: zero carries no sign.\n */\nfunction toFormattable(fn: string, value: unknown): number | undefined {\n if (value === null || value === undefined) return undefined;\n let n: number | undefined;\n if (typeof value === \"number\") n = value;\n else if (typeof value === \"string\" && NUMERIC_STRING.test(value.trim())) n = Number(value.trim());\n if (n === undefined || !Number.isFinite(n)) {\n warnInvalid(fn, value);\n return undefined;\n }\n return n + 0;\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: FormattableNumber, options: FormatNumberOptions = {}): string {\n const n = toFormattable(\"formatNumber\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = formatWithMinus(numberFormatter(options.locale, numberOptions(options)), n, options.minus);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(value: FormattableNumber, currency: string, options: FormatMoneyOptions = {}): string {\n const n = toFormattable(\"formatMoney\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const formatter = numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, n, options.minus);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: FormattableNumber, options: FormatPercentOptions = {}): string {\n const n = toFormattable(\"formatPercent\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? n / 100 : n;\n const formatter = numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, ratio, options.minus);\n}\n\n/** The units a duration is written in, largest first, with their length in seconds. */\nconst DURATION_UNITS = [\n [\"hour\", 3600],\n [\"minute\", 60],\n [\"second\", 1],\n] as const;\n\n/**\n * A number of SECONDS as an elapsed time in `locale`: \"42s\", \"3m 12s\", \"1h 0m 5s\" — and in\n * German \"3 Min. 12 Sek.\", in French \"3min 12s\" (keksdose audit follow-up, 2026-11-01:\n * keksdose's `formatElapsed`, the scan card's readout, moves here). Each unit is\n * `Intl.NumberFormat`'s `style: \"unit\"`, so the digits and the unit's spelling follow\n * `locale`; `unitDisplay` picks the form, narrow by default.\n *\n * - Seconds alone under a minute (\"0s\", \"59s\"); minutes and seconds under an hour (\"1m 0s\",\n * \"59m 59s\"); from an hour on, hours, minutes and seconds (\"1h 0m 0s\"). Hours are the\n * largest unit: a day and a bit is \"27h 46m 40s\". Every unit below the first is printed,\n * zero too, so a ticking readout keeps its shape from one second to the next.\n * - Whole seconds, cut toward zero: a timer reads \"59s\" until the minute is full.\n * - The parts are joined by a space, as the readout always was — not `Intl.ListFormat`'s\n * \"3 Min. und 12 Sek.\", which is a sentence rather than a figure.\n * - Negative: the minus on the first unit, as `Intl.DurationFormat` writes one — \"−3m 12s\",\n * with the kit's minus (`minus`). An elapsed time from a server's clock can come out a\n * second below zero; clamp it before, as keksdose's card does, when that is not news.\n * - `null` / `undefined` print `empty`; `NaN`, ±`Infinity` and a non-numeric string too,\n * with the formatters' development warning. A numeric string (\"185\") is a number.\n */\nexport function formatDuration(seconds: FormattableNumber, options: FormatDurationOptions = {}): string {\n const n = toFormattable(\"formatDuration\", seconds);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const unitDisplay = options.unitDisplay ?? \"narrow\";\n const negative = n < 0;\n let rest = Math.trunc(Math.abs(n));\n const parts: string[] = [];\n for (const [unit, size] of DURATION_UNITS) {\n const count = Math.floor(rest / size);\n rest -= count * size;\n // Units above the first that has a count are left out; seconds always print.\n if (parts.length === 0 && count === 0 && unit !== \"second\") continue;\n const formatter = numberFormatter(options.locale, { style: \"unit\", unit, unitDisplay });\n // `count > 0`: under a second either way is \"0s\", with no sign on it.\n const signed = negative && parts.length === 0 && count > 0 ? -count : count;\n parts.push(formatWithMinus(formatter, signed, options.minus));\n }\n return parts.join(\" \");\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. A bare\n * \"YYYY-MM-DD\" ignores it: a calendar day is the day it names in every zone. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (isBareIsoDay(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n // A bare \"YYYY-MM-DD\" is a calendar day, which has no zone: it is the day it names\n // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the\n // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).\n if (isBareIsoDay(value)) {\n const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\n return dateFormatter(options.locale, { ...base, timeZone: \"UTC\" }).format(day);\n }\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The zone of the date printed past `absoluteAfterDays` (0.46), and (0.51.7) the zone\n * whose calendar days \"yesterday\" and \"2 days ago\" count. Left out: the device's. */\n timeZone?: string;\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\n/**\n * `d`'s calendar day in `timeZone` (left out: the device's), as a whole number of days:\n * that day's UTC midnight over a day's length. Two of them subtract to whole calendar\n * days — a 23- or 25-hour DST day still counts one.\n */\nfunction dayNumber(d: Date, timeZone: string | undefined): number {\n if (!timeZone) return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY;\n // `en-US`: Gregorian, Latin digits — the parts are read back as numbers.\n const fields: Intl.DateTimeFormatOptions = { timeZone, year: \"numeric\", month: \"numeric\", day: \"numeric\" };\n const parts = dateFormatter(\"en-US\", fields).formatToParts(d);\n const part = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((p) => p.type === type)?.value);\n return Date.UTC(part(\"year\"), part(\"month\") - 1, part(\"day\")) / DAY;\n}\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded; days are\n * CALENDAR days (0.51.7), in `timeZone` when given: \"yesterday\" is the day before today,\n * however few hours ago that was.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n if (unit === \"day\") {\n // By the calendar, not in 24-hour units (refactor audit 2026-10-30, DSP-7): rounded\n // elapsed time made 00:10 seen at 23:50 \"yesterday\" and 01:00 yesterday seen at 23:30\n // \"2 days ago\", beside an exact date (DateMark's second line, the feedback thread's\n // tooltip) that said otherwise. Counted in `timeZone`, the zone that date is printed\n // in; a bare \"YYYY-MM-DD\" is the day it names wherever it is read, as in formatDate.\n // Still the same day (22 hours and more since just after midnight): in hours.\n const bareDay = isBareIsoDay(value);\n const days = dayNumber(d, bareDay ? undefined : options.timeZone) - dayNumber(now, options.timeZone);\n return days === 0 ? rtf.format(Math.round(diff / HOUR), \"hour\") : rtf.format(days, \"day\");\n }\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;\n formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;\n /** 0.54: {@link formatDuration}, a number of seconds as an elapsed time. */\n formatDuration: (seconds: FormattableNumber, options?: WithoutLocale<FormatDurationOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n // 0.46 (§10.3): the provider's `timeZone` for timestamps; a call's own wins, and a bare\n // \"YYYY-MM-DD\" ignores it (formatDate).\n const timeZone = useKitTimeZone();\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDuration: (seconds, options) => formatDuration(seconds, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),\n }),\n [kitLocale, timeZone],\n );\n}\n\n/* ── Currency minor units ──────────────────────────────────────────────────── */\n\n/**\n * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,\n * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule\n * AmountInput settles a typed figure by, for a host that needs it outside the field.\n */\nexport function currencyMinorDigits(currency: string | null | undefined): number | undefined {\n if (!currency) return undefined;\n try {\n return new Intl.NumberFormat(\"en\", { style: \"currency\", currency }).resolvedOptions().maximumFractionDigits;\n } catch {\n return undefined;\n }\n}\n\n/** The options form of {@link roundToCurrency}'s third argument. */\nexport interface RoundToCurrencyOptions {\n /** Decimals to round to, whatever the currency — the plain `digits` argument. */\n digits?: number;\n /**\n * Decimals for a currency `Intl` cannot resolve — no code yet (`null`, `\"\"`), or a\n * malformed one (\"EURO\", \"€\"). Left out, such a value comes back unchanged, as\n * before. Note that `Intl` resolves ANY well-formed three-letter code, a made-up\n * \"XYZ\" included (to 2), so this is the case of a missing or broken code, not of a\n * rare currency.\n */\n fallbackDigits?: number;\n}\n\n/**\n * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,\n * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *\n * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency\n * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an\n * FX estimate set into an AmountInput showed four decimals) — unless the options form\n * gives `fallbackDigits`: `roundToCurrency(v, code, { fallbackDigits: 2 })` rounds a\n * figure whose currency is not chosen yet to cents rather than leaving 0.1 + 0.2 as is.\n * A finite value never comes back NaN or -0, however small or large (BUG-11):\n * 0.1 + 0.2 - 0.3 → 0, -0.004 → 0.\n */\nexport function roundToCurrency(\n value: number,\n currency: string | null | undefined,\n digits?: number | RoundToCurrencyOptions,\n): number {\n const options = typeof digits === \"object\" && digits !== null ? digits : { digits };\n const places = options.digits ?? currencyMinorDigits(currency) ?? options.fallbackDigits;\n if (places === undefined) return value;\n return roundHalfAway(value, places);\n}\n"],"mappings":"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","names":["formatWithMinus"]}
|
package/dist/lib/intl-cache.d.ts
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Constructing the formatter is the expensive part: keksdose measured 392 ms per click on
|
|
5
5
|
* a 270-row register when each cell built its own (dev#632), and kept its own renderer of
|
|
6
6
|
* the date-format preference because the kit's built one per call (refactor audit
|
|
7
|
-
* 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one
|
|
7
|
+
* 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one, and
|
|
8
|
+
* since 0.54 `lib/dates`' `formatIsoDate` (keksdose audit follow-up, 2026-11-01).
|
|
8
9
|
*
|
|
9
10
|
* @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache
|
|
10
|
-
* lives apart from it.
|
|
11
|
+
* lives apart from it. It imports nothing, which `@eifi1/ui-kit/dates` (no package at
|
|
12
|
+
* all) relies on.
|
|
11
13
|
*/
|
|
12
14
|
/** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does
|
|
13
15
|
* for a tag it refuses — and caches nothing then. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/intl-cache.ts"],"sourcesContent":["/**\n * One `Intl.DateTimeFormat` per locale and option set, for every date the kit prints.\n *\n * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on\n * a 270-row register when each cell built its own (dev#632), and kept its own renderer of\n * the date-format preference because the kit's built one per call (refactor audit\n * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one.\n *\n * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache\n * lives apart from it.\n */\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\n\n/** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does\n * for a tag it refuses — and caches nothing then. */\nexport function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/lib/intl-cache.ts"],"sourcesContent":["/**\n * One `Intl.DateTimeFormat` per locale and option set, for every date the kit prints.\n *\n * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on\n * a 270-row register when each cell built its own (dev#632), and kept its own renderer of\n * the date-format preference because the kit's built one per call (refactor audit\n * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one, and\n * since 0.54 `lib/dates`' `formatIsoDate` (keksdose audit follow-up, 2026-11-01).\n *\n * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache\n * lives apart from it. It imports nothing, which `@eifi1/ui-kit/dates` (no package at\n * all) relies on.\n */\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\n\n/** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does\n * for a tag it refuses — and caches nothing then. */\nexport function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n"],"mappings":";AAcA,MAAM,iBAAiB,oBAAI,IAAiC;AAIrD,SAAS,cAAc,QAA4B,SAA0D;AAClH,QAAM,MAAM,GAAG,UAAU,EAAE,IAAI,KAAK,UAAU,OAAO,CAAC;AACtD,MAAI,IAAI,eAAe,IAAI,GAAG;AAC9B,MAAI,CAAC,GAAG;AACN,QAAI,IAAI,KAAK,eAAe,QAAQ,OAAO;AAC3C,mBAAe,IAAI,KAAK,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;","names":[]}
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
* It never arms while the browser is offline, whatever `shouldWatch` says: that state
|
|
36
36
|
* has its own, truthful indicator, and a request that never left the device is not
|
|
37
37
|
* evidence of anything about the server.
|
|
38
|
+
*
|
|
39
|
+
* A READ THAT IS HEAVY ON PURPOSE (0.55) — a report the server computes for a few
|
|
40
|
+
* seconds — says so with `slowOk` on its request (the axios config, or `track`'s
|
|
41
|
+
* request): it is counted, but never called slow, so it shows nothing while it is under
|
|
42
|
+
* `wakingMs`. Past `wakingMs` it is the server waking like any other read. A read that
|
|
43
|
+
* can take longer than that on a warm server stays out of the watch (`shouldWatch`).
|
|
38
44
|
*/
|
|
39
45
|
/** `slow` = longer than a warm server ever takes; `waking` = long enough that a cold
|
|
40
46
|
* start is the only sensible explanation. */
|
|
@@ -61,21 +67,53 @@ export interface ServerWakeRequest {
|
|
|
61
67
|
/** axios's `responseType`. `"blob"`, `"arraybuffer"` and `"stream"` mark a download,
|
|
62
68
|
* which the default filter does not count. */
|
|
63
69
|
responseType?: string;
|
|
70
|
+
/**
|
|
71
|
+
* This read is slow on purpose (0.55; the per-request opt-out adopt-0.51 left open): it
|
|
72
|
+
* still counts, but its own age never shows "taking longer than usual" — only past
|
|
73
|
+
* `wakingMs`, when a cold start is the likelier story, does it show the server waking.
|
|
74
|
+
* The stage then follows the other requests out beside it, as ever.
|
|
75
|
+
*
|
|
76
|
+
* ONLY for a read that is heavy but answers under `wakingMs` (7 s) on a warm server: a
|
|
77
|
+
* report computed on request, a large page of rows. A read that can take longer — an
|
|
78
|
+
* export rendered on the server, a slow third party behind the route — would say "the
|
|
79
|
+
* server is waking up" on a warm server; keep it out of the watch through `shouldWatch`
|
|
80
|
+
* instead (kastlan's advice), as a download and an upload are.
|
|
81
|
+
*
|
|
82
|
+
* On axios it rides the request config: `api.get(url, { slowOk: true })` (declare it on
|
|
83
|
+
* `AxiosRequestConfig` once, by module augmentation); through `track`, on its request.
|
|
84
|
+
*/
|
|
85
|
+
slowOk?: boolean;
|
|
64
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* One counted request, as {@link ServerWakeWatcher.start} names it (0.55, keksdose's 0.51
|
|
89
|
+
* review N4): a positive whole number, unique for the watcher's lifetime, which
|
|
90
|
+
* {@link ServerWakeWatcher.end} takes back to end exactly that request. `start` returns
|
|
91
|
+
* `false` where it counts nothing, as before: the token is truthy where `start` returned
|
|
92
|
+
* `true` until 0.55, so code that only tests it reads as it did.
|
|
93
|
+
*/
|
|
94
|
+
export type ServerWakeToken = number;
|
|
65
95
|
export interface ServerWakeWatcher {
|
|
66
96
|
/**
|
|
67
97
|
* Count a request that is going out, and arm the escalation if it is the first.
|
|
68
|
-
* Returns
|
|
69
|
-
*
|
|
70
|
-
*
|
|
98
|
+
* Returns its {@link ServerWakeToken}, or `false` when it was not counted (until 0.55
|
|
99
|
+
* `true` / `false`; the token is truthy where `true` was). The caller MUST end every
|
|
100
|
+
* counted request exactly once, or the counter never returns to zero and the notice
|
|
101
|
+
* sticks: `end(token)`, which ends that request, or a bare `end()`, as before. (The
|
|
102
|
+
* axios adapter stashes the token on the request config for that reason.)
|
|
71
103
|
* `request` reaches the filter as its third argument; default `{ method, url }`.
|
|
72
104
|
*/
|
|
73
|
-
start(method?: string, url?: string, request?: ServerWakeRequest):
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
105
|
+
start(method?: string, url?: string, request?: ServerWakeRequest): ServerWakeToken | false;
|
|
106
|
+
/**
|
|
107
|
+
* A counted request settled — either way: an error still proves the server answered or
|
|
108
|
+
* gave up. The stage then follows the oldest request still out (0.51), so the notice
|
|
109
|
+
* goes away when the last one does, or sooner when the rest are recent.
|
|
110
|
+
*
|
|
111
|
+
* With `token` (0.55), exactly that request is retired, so the age the stage is read
|
|
112
|
+
* from is the real one; a token that was ended already (or reset away), or `false`, is
|
|
113
|
+
* a no-op. A bare `end()` — the call before 0.55 — retires the OLDEST start, which can
|
|
114
|
+
* make a notice come late, never falsely. Never takes the counter below zero.
|
|
115
|
+
*/
|
|
116
|
+
end(token?: ServerWakeToken | false): void;
|
|
79
117
|
/** `start` + `end` around a promise (or a function returning one), for a request
|
|
80
118
|
* that does not go through an adapter. Resolves and rejects as the work does. */
|
|
81
119
|
track<T>(work: PromiseLike<T> | (() => PromiseLike<T>), request?: ServerWakeRequest): Promise<T>;
|
|
@@ -108,6 +146,8 @@ export interface ServerWakeAxiosConfig {
|
|
|
108
146
|
method?: string;
|
|
109
147
|
url?: string;
|
|
110
148
|
responseType?: string;
|
|
149
|
+
/** A read slow on purpose — see {@link ServerWakeRequest.slowOk} (0.55). */
|
|
150
|
+
slowOk?: boolean;
|
|
111
151
|
}
|
|
112
152
|
/** The slice of an interceptor manager the adapter uses. A method (not a function
|
|
113
153
|
* property) so axios's wider `use(onFulfilled?, onRejected?, options?)` fits it. */
|