@eifi1/ui-kit 0.51.7 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -23
- package/dist/account/account-parts.d.ts +10 -8
- package/dist/account/account-parts.js +1 -18
- package/dist/account/account-parts.js.map +1 -1
- package/dist/account/data-export-setting-labels.js +1 -1
- package/dist/account/data-export-setting-labels.js.map +1 -1
- package/dist/account/delete-account-setting-labels.js +1 -1
- package/dist/account/delete-account-setting-labels.js.map +1 -1
- package/dist/account/email-change-setting-labels.js +1 -1
- package/dist/account/email-change-setting-labels.js.map +1 -1
- package/dist/account/passkeys-setting.d.ts +9 -2
- package/dist/account/passkeys-setting.js +41 -29
- package/dist/account/passkeys-setting.js.map +1 -1
- package/dist/account/sessions-setting.d.ts +3 -2
- package/dist/account/sessions-setting.js +10 -8
- package/dist/account/sessions-setting.js.map +1 -1
- package/dist/admin/admin-action-confirm.js +1 -3
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/admin-parts.d.ts +6 -3
- package/dist/admin/admin-parts.js +6 -21
- package/dist/admin/admin-parts.js.map +1 -1
- package/dist/admin/mail-test-card-labels.js +1 -1
- package/dist/admin/mail-test-card-labels.js.map +1 -1
- package/dist/admin/plan-change-labels.d.ts +2 -2
- package/dist/admin/plan-change-labels.js +1 -17
- package/dist/admin/plan-change-labels.js.map +1 -1
- package/dist/admin/plan-change.d.ts +3 -42
- package/dist/admin/plan-change.js +1 -1
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/reviewer-scope-editor.js +3 -3
- package/dist/admin/reviewer-scope-editor.js.map +1 -1
- package/dist/auth/auth-errors-labels.d.ts +11 -0
- package/dist/auth/auth-errors-labels.js +13 -0
- package/dist/auth/auth-errors-labels.js.map +1 -0
- package/dist/auth/auth-errors.d.ts +7 -23
- package/dist/auth/auth-errors.js +2 -15
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/complete-name-dialog.js +2 -1
- package/dist/auth/complete-name-dialog.js.map +1 -1
- package/dist/auth/forgot-password-form-labels.js +1 -1
- package/dist/auth/forgot-password-form-labels.js.map +1 -1
- package/dist/auth/forgot-password-form.js +7 -2
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form-labels.js +1 -1
- package/dist/auth/register-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form-labels.js +1 -1
- package/dist/auth/reset-password-form-labels.js.map +1 -1
- package/dist/auth/reset-password-form.js +5 -1
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form-labels.js +1 -1
- package/dist/auth/sign-in-form-labels.js.map +1 -1
- package/dist/auth/status-parts.d.ts +0 -3
- package/dist/auth/status-parts.js +0 -4
- package/dist/auth/status-parts.js.map +1 -1
- package/dist/auth/verify-email-labels.js +1 -1
- package/dist/auth/verify-email-labels.js.map +1 -1
- package/dist/auth/verify-email.js +7 -2
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +20 -8
- package/dist/billing/cancel-subscription.js +7 -5
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/dialog-close.d.ts +29 -0
- package/dist/billing/dialog-close.js +8 -0
- package/dist/billing/dialog-close.js.map +1 -0
- package/dist/billing/plan-card.d.ts +14 -4
- package/dist/billing/plan-card.js +15 -8
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-change-labels.d.ts +42 -0
- package/dist/billing/plan-change-labels.js +22 -0
- package/dist/billing/plan-change-labels.js.map +1 -0
- package/dist/billing/plan-switch.d.ts +19 -7
- package/dist/billing/plan-switch.js +8 -4
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +12 -0
- package/dist/chart.js +16 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/account-chips.d.ts +5 -5
- package/dist/components/account-chips.js +11 -13
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/alert-banner.d.ts +16 -5
- package/dist/components/alert-banner.js +46 -1
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +13 -0
- package/dist/components/amount-input.js +1 -0
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +57 -65
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.d.ts +12 -2
- package/dist/components/bulk-action-bar.js +65 -21
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-look.d.ts +5 -5
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/chart-edge.d.ts +16 -0
- package/dist/components/chart-edge.js +14 -1
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-figure.d.ts +13 -0
- package/dist/components/chart-figure.js +15 -0
- package/dist/components/chart-figure.js.map +1 -0
- package/dist/components/chart-zoom-core.js +1 -4
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/chart.d.ts +8 -1
- package/dist/components/chart.js +19 -10
- package/dist/components/chart.js.map +1 -1
- package/dist/components/combobox-core.d.ts +111 -2
- package/dist/components/combobox-core.js +148 -42
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +141 -130
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +6 -5
- package/dist/components/copy-button.js +5 -3
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.js +2 -2
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/data-table-column-settings.d.ts +1 -1
- package/dist/components/data-table-column-settings.js +33 -7
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-filters.d.ts +4 -2
- package/dist/components/data-table-filters.js +2 -2
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-fit.d.ts +6 -4
- package/dist/components/data-table-fit.js +4 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-groups.d.ts +1 -1
- package/dist/components/data-table-groups.js.map +1 -1
- package/dist/components/data-table-head.d.ts +1 -1
- package/dist/components/data-table-head.js +15 -12
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +12 -7
- package/dist/components/data-table-mobile.js +43 -30
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-pagination.js +11 -6
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-resize.d.ts +1 -1
- package/dist/components/data-table-resize.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +6 -2
- package/dist/components/data-table-row.js +17 -10
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table-selection.d.ts +1 -1
- package/dist/components/data-table-selection.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +10 -2
- package/dist/components/data-table-shared.js +18 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +21 -2
- package/dist/components/data-table-sort.js +24 -0
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table-virtual.d.ts +1 -1
- package/dist/components/data-table-virtual.js +18 -6
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/data-table.d.ts +13 -4
- package/dist/components/data-table.js +139 -36
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-field.d.ts +24 -2
- package/dist/components/date-field.js +33 -8
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +15 -2
- package/dist/components/date-picker.js +55 -26
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +47 -20
- package/dist/components/date-range-picker.js +23 -7
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/error-boundary.js +37 -30
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
- package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
- package/dist/components/field-base-labels.js.map +1 -0
- package/dist/components/field-base.d.ts +4 -3
- package/dist/components/field-base.js +3 -3
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-sync-labels.d.ts +1 -0
- package/dist/components/field-sync-labels.js.map +1 -1
- package/dist/components/field-sync.d.ts +23 -1
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/form-actions.js +11 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.d.ts +3 -0
- package/dist/components/hover-menu.js +5 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.js +14 -9
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.d.ts +43 -0
- package/dist/components/inline-confirm.js +67 -0
- package/dist/components/inline-confirm.js.map +1 -0
- package/dist/components/inline-entity-combobox.d.ts +4 -3
- package/dist/components/inline-entity-combobox.js +148 -140
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +28 -112
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal-page.d.ts +3 -1
- package/dist/components/legal-page.js +3 -2
- package/dist/components/legal-page.js.map +1 -1
- package/dist/components/legal.d.ts +4 -2
- package/dist/components/legal.js.map +1 -1
- package/dist/components/lightbox.d.ts +2 -2
- package/dist/components/lightbox.js +17 -6
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/list.d.ts +6 -2
- package/dist/components/list.js +3 -2
- package/dist/components/list.js.map +1 -1
- package/dist/components/measured-grid.d.ts +1 -1
- package/dist/components/measured-grid.js +2 -1
- package/dist/components/measured-grid.js.map +1 -1
- package/dist/components/mini-calendar.js +3 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.js +3 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-grid.d.ts +55 -0
- package/dist/components/month-grid.js +148 -0
- package/dist/components/month-grid.js.map +1 -0
- package/dist/components/month-picker.d.ts +36 -4
- package/dist/components/month-picker.js +123 -146
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-select.d.ts +2 -2
- package/dist/components/multi-select.js +11 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +7 -0
- package/dist/components/pie-chart-labels.js +1 -0
- package/dist/components/pie-chart-labels.js.map +1 -1
- package/dist/components/pie-chart.d.ts +5 -3
- package/dist/components/pie-chart.js +7 -22
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +3 -0
- package/dist/components/popover.js +7 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/row-actions.js +2 -1
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +7 -0
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +3 -3
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +7 -4
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +31 -7
- package/dist/components/series-chart.js +72 -29
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +3 -3
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/sign-chip-labels.d.ts +1 -0
- package/dist/components/sign-chip-labels.js.map +1 -1
- package/dist/components/sign-chip.d.ts +10 -1
- package/dist/components/sign-chip.js.map +1 -1
- package/dist/components/tabs-labels.d.ts +2 -0
- package/dist/components/tabs-labels.js +11 -0
- package/dist/components/tabs-labels.js.map +1 -0
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +2 -2
- package/dist/components/toggle-legend.js +6 -6
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/translation-review-editor.d.ts +4 -2
- package/dist/components/translation-review-editor.js +2 -3
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.d.ts +2 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +26 -0
- package/dist/components/treemap-labels.js +10 -0
- package/dist/components/treemap-labels.js.map +1 -0
- package/dist/components/treemap-layout.d.ts +62 -0
- package/dist/components/treemap-layout.js +126 -0
- package/dist/components/treemap-layout.js.map +1 -0
- package/dist/components/treemap.d.ts +140 -17
- package/dist/components/treemap.js +324 -97
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.d.ts +5 -5
- package/dist/components/use-table-state.js +4 -1
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.d.ts +6 -2
- package/dist/data-table.js +7 -2
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +36 -7
- package/dist/eslint/role-utilities.js +42 -9
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +25 -16
- package/dist/feedback/feedback-dialog.js +6 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-mutation.d.ts +30 -0
- package/dist/feedback/feedback-mutation.js +16 -0
- package/dist/feedback/feedback-mutation.js.map +1 -0
- package/dist/feedback/feedback-page-link.d.ts +53 -0
- package/dist/feedback/feedback-page-link.js +37 -0
- package/dist/feedback/feedback-page-link.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +12 -3
- package/dist/feedback/feedback-public-list.js +2 -1
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-replies.d.ts +3 -1
- package/dist/feedback/feedback-replies.js +5 -4
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +5 -0
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +64 -28
- package/dist/feedback/feedback-row-detail.js +30 -28
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +30 -11
- package/dist/feedback/feedback-status-undo.js +10 -15
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
- package/dist/feedback/feedback-submit-dialog.js +1 -1
- package/dist/feedback/feedback-submit-dialog.js.map +1 -1
- package/dist/feedback/feedback-submit.js +9 -3
- package/dist/feedback/feedback-submit.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +1 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/feedback/feedback-table.d.ts +21 -9
- package/dist/feedback/feedback-table.js +45 -25
- package/dist/feedback/feedback-table.js.map +1 -1
- package/dist/feedback/feedback-thread-labels.d.ts +2 -2
- package/dist/feedback/feedback-thread-labels.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +24 -23
- package/dist/feedback/feedback-thread.js +2 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +34 -0
- package/dist/feedback.js +42 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +35 -0
- package/dist/hooks/use-focus-trap.js +6 -4
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-hotkey.d.ts +5 -2
- package/dist/hooks/use-hotkey.js +5 -4
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-long-press.d.ts +7 -4
- package/dist/hooks/use-long-press.js +3 -0
- package/dist/hooks/use-long-press.js.map +1 -1
- package/dist/hooks/use-media-query.js +12 -4
- package/dist/hooks/use-media-query.js.map +1 -1
- package/dist/hooks/use-mounted.d.ts +22 -0
- package/dist/hooks/use-mounted.js +24 -0
- package/dist/hooks/use-mounted.js.map +1 -0
- package/dist/hooks/use-overlay-history.js +19 -3
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +4 -5
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +7 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +9 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +20 -41
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +7 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -10
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +9 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +7 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +8 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +5 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +64 -17
- package/dist/index.js +77 -12
- package/dist/index.js.map +1 -1
- package/dist/landing/routing.d.ts +3 -1
- package/dist/landing/routing.js +4 -7
- package/dist/landing/routing.js.map +1 -1
- package/dist/lib/broadcast.d.ts +17 -0
- package/dist/lib/broadcast.js +30 -0
- package/dist/lib/broadcast.js.map +1 -0
- package/dist/lib/date-format.d.ts +37 -2
- package/dist/lib/date-format.js +34 -8
- package/dist/lib/date-format.js.map +1 -1
- package/dist/lib/day-format.d.ts +46 -0
- package/dist/lib/day-format.js +46 -0
- package/dist/lib/day-format.js.map +1 -0
- package/dist/lib/describe-failure.d.ts +14 -0
- package/dist/lib/describe-failure.js +9 -0
- package/dist/lib/describe-failure.js.map +1 -0
- package/dist/lib/direction.d.ts +16 -0
- package/dist/lib/direction.js +9 -1
- package/dist/lib/direction.js.map +1 -1
- package/dist/lib/focus-left.d.ts +63 -0
- package/dist/lib/focus-left.js +67 -0
- package/dist/lib/focus-left.js.map +1 -0
- package/dist/lib/format.js +5 -14
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/http-status.d.ts +24 -0
- package/dist/lib/http-status.js +14 -0
- package/dist/lib/http-status.js.map +1 -0
- package/dist/lib/intl-cache.d.ts +14 -0
- package/dist/lib/intl-cache.js +15 -0
- package/dist/lib/intl-cache.js.map +1 -0
- package/dist/lib/isin.d.ts +10 -0
- package/dist/lib/isin.js.map +1 -1
- package/dist/lib/iso-day.d.ts +11 -0
- package/dist/lib/iso-day.js +8 -0
- package/dist/lib/iso-day.js.map +1 -0
- package/dist/lib/lazy-with-fallback.d.ts +31 -0
- package/dist/lib/lazy-with-fallback.js +40 -0
- package/dist/lib/lazy-with-fallback.js.map +1 -0
- package/dist/lib/path-pattern.d.ts +34 -0
- package/dist/lib/path-pattern.js +24 -0
- package/dist/lib/path-pattern.js.map +1 -0
- package/dist/lib/reduced-motion.d.ts +2 -1
- package/dist/lib/reduced-motion.js.map +1 -1
- package/dist/lib/round.d.ts +2 -1
- package/dist/lib/round.js +2 -1
- package/dist/lib/round.js.map +1 -1
- package/dist/lib/safe-storage.d.ts +24 -5
- package/dist/lib/safe-storage.js +27 -2
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/lib/session-refresh.js +1 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/lib/share-format.d.ts +13 -0
- package/dist/lib/share-format.js +10 -0
- package/dist/lib/share-format.js.map +1 -0
- package/dist/lib/text-case.d.ts +17 -0
- package/dist/lib/text-case.js +8 -0
- package/dist/lib/text-case.js.map +1 -0
- package/dist/lib/text-match.d.ts +29 -0
- package/dist/lib/text-match.js +20 -2
- package/dist/lib/text-match.js.map +1 -1
- package/dist/lib/thenable.d.ts +13 -0
- package/dist/lib/thenable.js +8 -0
- package/dist/lib/thenable.js.map +1 -0
- package/dist/lib/translation-review.d.ts +7 -2
- package/dist/lib/translation-review.js +4 -4
- package/dist/lib/translation-review.js.map +1 -1
- package/dist/notifications/device-zone-report.js +1 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +2 -1
- package/dist/notifications/notification-bell.js +3 -3
- package/dist/notifications/notification-bell.js.map +1 -1
- package/dist/notifications/notification-inbox.d.ts +10 -6
- package/dist/notifications/notification-inbox.js +26 -48
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +3 -2
- package/dist/notifications/notification-parts.js +1 -3
- package/dist/notifications/notification-parts.js.map +1 -1
- package/dist/notifications/notification-types-setting.js +1 -1
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notifications-labels.d.ts +6 -0
- package/dist/notifications/notifications-labels.js +1 -0
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-device-setting.js +1 -1
- package/dist/notifications/push-device-setting.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +2 -1
- package/dist/notifications/push-devices-setting.js +27 -49
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +10 -3
- package/dist/notifications/quiet-hours-setting.js +65 -26
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/unsubscribe-panel.js +4 -7
- package/dist/notifications/unsubscribe-panel.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +39 -1
- package/dist/notifications/use-push-device.js +28 -7
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/query-persistence.d.ts +3 -1
- package/dist/offline/query-persistence.js +19 -2
- package/dist/offline/query-persistence.js.map +1 -1
- package/dist/rhf/fields.d.ts +11 -4
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/command-palette.js +1 -1
- package/dist/search/command-palette.js.map +1 -1
- package/dist/search/global-search.js +19 -9
- package/dist/search/global-search.js.map +1 -1
- package/dist/search/use-command-key.d.ts +12 -1
- package/dist/search/use-command-key.js +3 -12
- package/dist/search/use-command-key.js.map +1 -1
- package/dist/search.d.ts +7 -0
- package/dist/search.js +10 -0
- package/dist/search.js.map +1 -1
- package/dist/settings/use-settings-focus.js +1 -0
- package/dist/settings/use-settings-focus.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +21 -5
- package/dist/shell/account-menu-language.js +3 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +26 -9
- package/dist/shell/account-menu-text-size.js +3 -2
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/dev-menu.js +5 -18
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +7 -0
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/top-bar-actions.d.ts +1 -1
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +0 -2
- package/dist/shell/top-bar-parts-labels.js +0 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +27 -8
- package/dist/shell.js +28 -7
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +30 -4
- package/dist/sync/engine.js +172 -52
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync.d.ts +6 -2
- package/dist/sync.js +6 -1
- package/dist/sync.js.map +1 -1
- package/dist/testing/css-variables.js +3 -8
- package/dist/testing/css-variables.js.map +1 -1
- package/dist/theme/contrast.d.ts +2 -1
- package/dist/theme/contrast.js +3 -8
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-store.js +6 -6
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +3 -8
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +3 -6
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/tour/tour-catalogue.d.ts +7 -4
- package/dist/tour/tour-catalogue.js +12 -10
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +0 -2
- package/dist/tour/tour-dock.js +0 -2
- package/dist/tour/tour-dock.js.map +1 -1
- package/dist/tour/tour-gates.d.ts +4 -0
- package/dist/tour/tour-gates.js +14 -0
- package/dist/tour/tour-gates.js.map +1 -1
- package/dist/tour/tour-overlay.d.ts +27 -0
- package/dist/tour/tour-overlay.js +741 -0
- package/dist/tour/tour-overlay.js.map +1 -0
- package/dist/tour/tour.d.ts +13 -3
- package/dist/tour/tour.js +59 -781
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.js +1 -1
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +5 -1
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +6 -2
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.d.ts +3 -1
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/validation.d.ts +3 -1
- package/dist/wizard/validation.js.map +1 -1
- package/package.json +10 -6
- package/src/account/account-parts.ts +5 -25
- package/src/account/data-export-setting-labels.ts +1 -1
- package/src/account/delete-account-setting-labels.ts +1 -1
- package/src/account/email-change-setting-labels.ts +1 -1
- package/src/account/passkeys-setting.tsx +60 -38
- package/src/account/sessions-setting.tsx +18 -12
- package/src/admin/admin-action-confirm.tsx +1 -5
- package/src/admin/admin-parts.ts +11 -25
- package/src/admin/mail-test-card-labels.ts +1 -1
- package/src/admin/plan-change-labels.ts +5 -23
- package/src/admin/plan-change.tsx +7 -44
- package/src/admin/reviewer-scope-editor.tsx +3 -3
- package/src/auth/auth-errors-labels.ts +23 -0
- package/src/auth/auth-errors.ts +11 -38
- package/src/auth/complete-name-dialog.tsx +4 -1
- package/src/auth/forgot-password-form-labels.ts +1 -1
- package/src/auth/forgot-password-form.tsx +11 -6
- package/src/auth/register-form-labels.ts +1 -1
- package/src/auth/reset-password-form-labels.ts +1 -1
- package/src/auth/reset-password-form.tsx +8 -4
- package/src/auth/sign-in-form-labels.ts +1 -1
- package/src/auth/status-parts.tsx +0 -6
- package/src/auth/verify-email-labels.ts +1 -1
- package/src/auth/verify-email.tsx +11 -6
- package/src/billing/cancel-subscription.tsx +26 -12
- package/src/billing/dialog-close.ts +34 -0
- package/src/billing/plan-card.tsx +36 -11
- package/src/billing/plan-change-labels.ts +66 -0
- package/src/billing/plan-switch.tsx +26 -12
- package/src/chart.ts +21 -3
- package/src/components/account-chips.tsx +20 -23
- package/src/components/alert-banner.tsx +82 -5
- package/src/components/amount-input.tsx +18 -7
- package/src/components/autocomplete.tsx +64 -70
- package/src/components/bulk-action-bar.tsx +70 -2
- package/src/components/button-look.ts +5 -5
- package/src/components/button.tsx +2 -2
- package/src/components/chart-edge.ts +34 -1
- package/src/components/chart-figure.tsx +35 -0
- package/src/components/chart-zoom-core.tsx +6 -5
- package/src/components/chart.tsx +26 -5
- package/src/components/combobox-core.tsx +293 -62
- package/src/components/combobox.tsx +183 -153
- package/src/components/copy-button.tsx +34 -23
- package/src/components/country-select.tsx +16 -11
- package/src/components/data-table-column-settings.tsx +52 -10
- package/src/components/data-table-filters.ts +6 -4
- package/src/components/data-table-fit.ts +19 -5
- package/src/components/data-table-groups.tsx +1 -1
- package/src/components/data-table-head.tsx +32 -22
- package/src/components/data-table-mobile.tsx +61 -26
- package/src/components/data-table-pagination.tsx +21 -6
- package/src/components/data-table-resize.ts +1 -1
- package/src/components/data-table-row-actions.tsx +3 -3
- package/src/components/data-table-row.tsx +25 -14
- package/src/components/data-table-selection.ts +1 -1
- package/src/components/data-table-shared.tsx +33 -1
- package/src/components/data-table-sort.ts +57 -2
- package/src/components/data-table-virtual.ts +32 -8
- package/src/components/data-table.tsx +239 -68
- package/src/components/date-field.tsx +65 -8
- package/src/components/date-picker.tsx +125 -48
- package/src/components/date-range-picker.tsx +82 -31
- package/src/components/error-boundary.tsx +53 -39
- package/src/components/field-base-labels.ts +20 -0
- package/src/components/field-base.tsx +13 -7
- package/src/components/field-sync-labels.ts +1 -0
- package/src/components/field-sync.tsx +23 -1
- package/src/components/form-actions.tsx +20 -5
- package/src/components/hover-menu.tsx +18 -8
- package/src/components/icon-button.tsx +14 -9
- package/src/components/inline-confirm.tsx +116 -0
- package/src/components/inline-entity-combobox.tsx +197 -153
- package/src/components/input.tsx +1 -1
- package/src/components/jump-calendar.tsx +30 -146
- package/src/components/legal-page.tsx +10 -7
- package/src/components/legal.tsx +4 -2
- package/src/components/lightbox.tsx +21 -8
- package/src/components/list.tsx +10 -4
- package/src/components/measured-grid.tsx +8 -4
- package/src/components/mini-calendar.tsx +6 -5
- package/src/components/modal.tsx +6 -1
- package/src/components/month-grid.tsx +229 -0
- package/src/components/month-picker.tsx +192 -200
- package/src/components/multi-select.tsx +21 -9
- package/src/components/picker-sheet.tsx +12 -9
- package/src/components/pie-chart-labels.ts +8 -0
- package/src/components/pie-chart.tsx +22 -28
- package/src/components/popover.tsx +18 -1
- package/src/components/row-actions.tsx +2 -1
- package/src/components/section-nav.tsx +7 -0
- package/src/components/series-chart-axes.ts +3 -3
- package/src/components/series-chart-labels.ts +7 -4
- package/src/components/series-chart.tsx +178 -69
- package/src/components/share-card.tsx +6 -6
- package/src/components/sign-chip-labels.ts +1 -0
- package/src/components/sign-chip.tsx +10 -1
- package/src/components/tabs-labels.ts +13 -0
- package/src/components/tabs.tsx +2 -2
- package/src/components/toggle-legend.tsx +16 -11
- package/src/components/translation-review-editor.tsx +6 -5
- package/src/components/translation-review.tsx +4 -3
- package/src/components/treemap-labels.ts +39 -0
- package/src/components/treemap-layout.ts +210 -0
- package/src/components/treemap.tsx +722 -133
- package/src/components/use-table-state.ts +12 -9
- package/src/data-table.ts +9 -2
- package/src/eslint/role-utilities.ts +97 -19
- package/src/feedback/feedback-dialog.tsx +30 -19
- package/src/feedback/feedback-mutation.ts +41 -0
- package/src/feedback/feedback-page-link.ts +90 -0
- package/src/feedback/feedback-public-list.tsx +17 -5
- package/src/feedback/feedback-replies.tsx +13 -6
- package/src/feedback/feedback-row-detail-labels.ts +5 -0
- package/src/feedback/feedback-row-detail.tsx +103 -67
- package/src/feedback/feedback-status-undo.ts +41 -27
- package/src/feedback/feedback-submit-dialog.tsx +2 -2
- package/src/feedback/feedback-submit.tsx +21 -7
- package/src/feedback/feedback-swipe.tsx +1 -1
- package/src/feedback/feedback-table.tsx +86 -38
- package/src/feedback/feedback-thread-labels.ts +2 -2
- package/src/feedback/feedback-thread.tsx +27 -25
- package/src/feedback.ts +42 -0
- package/src/hooks/use-focus-trap.ts +8 -24
- package/src/hooks/use-hotkey.ts +24 -7
- package/src/hooks/use-long-press.ts +16 -2
- package/src/hooks/use-media-query.ts +20 -4
- package/src/hooks/use-mounted.ts +39 -0
- package/src/hooks/use-overlay-history.ts +32 -3
- package/src/i18n/defaults.ts +4 -5
- package/src/i18n/german.ts +8 -2
- package/src/i18n/kit-labels.tsx +9 -2
- package/src/i18n/locales/es.ts +8 -2
- package/src/i18n/locales/fr.ts +21 -13
- package/src/i18n/locales/hu.ts +10 -2
- package/src/i18n/locales/it.ts +8 -2
- package/src/i18n/locales/zh.ts +9 -2
- package/src/i18n/review.ts +5 -0
- package/src/index.ts +103 -13
- package/src/landing/routing.tsx +14 -10
- package/src/lib/broadcast.ts +52 -0
- package/src/lib/date-format.ts +89 -13
- package/src/lib/day-format.ts +96 -0
- package/src/lib/describe-failure.ts +18 -0
- package/src/lib/direction.ts +39 -0
- package/src/lib/focus-left.ts +156 -0
- package/src/lib/format.ts +5 -17
- package/src/lib/http-status.ts +38 -0
- package/src/lib/intl-cache.ts +25 -0
- package/src/lib/isin.ts +10 -0
- package/src/lib/iso-day.ts +13 -0
- package/src/lib/lazy-with-fallback.ts +82 -0
- package/src/lib/path-pattern.ts +55 -0
- package/src/lib/reduced-motion.ts +2 -1
- package/src/lib/round.ts +7 -2
- package/src/lib/safe-storage.ts +48 -6
- package/src/lib/session-refresh.ts +1 -1
- package/src/lib/share-format.ts +18 -0
- package/src/lib/text-case.ts +19 -0
- package/src/lib/text-match.ts +57 -2
- package/src/lib/thenable.ts +19 -0
- package/src/lib/translation-review.ts +13 -5
- package/src/notifications/device-zone-report.ts +1 -1
- package/src/notifications/notification-bell.tsx +7 -4
- package/src/notifications/notification-inbox.tsx +40 -58
- package/src/notifications/notification-parts.tsx +3 -4
- package/src/notifications/notification-types-setting.tsx +1 -1
- package/src/notifications/notifications-labels.ts +7 -0
- package/src/notifications/push-device-setting.tsx +1 -1
- package/src/notifications/push-devices-setting.tsx +30 -55
- package/src/notifications/quiet-hours-setting.tsx +93 -31
- package/src/notifications/unsubscribe-panel.tsx +4 -7
- package/src/notifications/use-push-device.ts +76 -10
- package/src/offline/query-persistence.ts +35 -3
- package/src/rhf/fields.tsx +11 -4
- package/src/search/command-palette.tsx +2 -4
- package/src/search/global-search.tsx +36 -15
- package/src/search/use-command-key.ts +13 -12
- package/src/search.ts +9 -0
- package/src/settings/use-settings-focus.ts +3 -0
- package/src/shell/account-menu-language.tsx +27 -5
- package/src/shell/account-menu-text-size.tsx +32 -10
- package/src/shell/dev-menu.tsx +7 -21
- package/src/shell/role-switcher.tsx +7 -0
- package/src/shell/top-bar-actions.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +0 -3
- package/src/shell.ts +40 -9
- package/src/sync/engine.ts +339 -61
- package/src/sync.ts +8 -1
- package/src/testing/css-variables.ts +8 -9
- package/src/theme/contrast.ts +8 -9
- package/src/theme/palette-store.ts +9 -9
- package/src/theme/text-size.ts +6 -8
- package/src/theme/theme-store.ts +7 -9
- package/src/tour/tour-catalogue.tsx +26 -15
- package/src/tour/tour-dock.ts +0 -2
- package/src/tour/tour-gates.ts +23 -0
- package/src/tour/tour-overlay.tsx +1170 -0
- package/src/tour/tour.tsx +108 -1202
- package/src/tour/use-tour-progress.ts +1 -1
- package/src/wizard/stepper-nav.tsx +3 -1
- package/src/wizard/types.ts +11 -2
- package/src/wizard/use-wizard.ts +2 -1
- package/src/wizard/validation.ts +8 -2
- package/tokens.css +2 -1
- package/dist/components/ui-labels.js.map +0 -1
- package/dist/shell/dev-switchers.d.ts +0 -6
- package/dist/shell/dev-switchers.js +0 -12
- package/dist/shell/dev-switchers.js.map +0 -1
- package/src/components/ui-labels.ts +0 -24
- package/src/shell/dev-switchers.tsx +0 -18
|
@@ -7,7 +7,6 @@ import { useAnnounce } from "../hooks/use-announce.js";
|
|
|
7
7
|
import { useCopyToClipboard } from "../hooks/use-copy-to-clipboard.js";
|
|
8
8
|
import { Button } from "./button.js";
|
|
9
9
|
import { IconButton } from "./icon-button.js";
|
|
10
|
-
import { Tooltip } from "./tooltip.js";
|
|
11
10
|
import { DEFAULT_COPY_BUTTON_LABELS } from "./copy-button-labels.js";
|
|
12
11
|
const ICONS = { idle: Copy, copied: Check, failed: X };
|
|
13
12
|
function CopyButton({
|
|
@@ -76,13 +75,16 @@ function CopyButton({
|
|
|
76
75
|
] });
|
|
77
76
|
}
|
|
78
77
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
79
|
-
/* @__PURE__ */ jsx(
|
|
78
|
+
/* @__PURE__ */ jsx(
|
|
80
79
|
IconButton,
|
|
81
80
|
{
|
|
82
81
|
type: "button",
|
|
83
82
|
...rest,
|
|
84
83
|
disabled,
|
|
84
|
+
label: idleText,
|
|
85
85
|
"aria-label": idleText,
|
|
86
|
+
tooltipSide,
|
|
87
|
+
tooltipPortal,
|
|
86
88
|
variant: buttonVariant ?? "ghost",
|
|
87
89
|
size: size ?? "sm",
|
|
88
90
|
tone,
|
|
@@ -92,7 +94,7 @@ function CopyButton({
|
|
|
92
94
|
className: cn(iconTone, className),
|
|
93
95
|
children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": true })
|
|
94
96
|
}
|
|
95
|
-
)
|
|
97
|
+
),
|
|
96
98
|
regions
|
|
97
99
|
] });
|
|
98
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/copy-button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes, Ref } from \"react\";\nimport { Check, Copy, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { useCopyToClipboard } from \"../hooks/use-copy-to-clipboard\";\nimport type { CopyState } from \"../hooks/use-copy-to-clipboard\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./icon-button\";\nimport type { ButtonSize, ButtonTone, ButtonVariant } from \"./button-look\";\nimport type { IconButtonSize, IconButtonTone } from \"./icon-button\";\nimport { Tooltip } from \"./tooltip\";\nimport type { TooltipSide } from \"./tooltip\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"./copy-button-labels\";\n\n/** The words a copy button says. The visible three are short on purpose (they\n * replace the button's own text in the `label` variant); the two announcements\n * are the sentences a screen reader hears, since the button's focus never moves. */\nexport interface CopyButtonLabels {\n copy: string;\n copied: string;\n failed: string;\n copiedAnnouncement: string;\n failedAnnouncement: string;\n}\n\n// Defined in ./copy-button-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_COPY_BUTTON_LABELS };\n\nexport interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\" | \"onClick\"> {\n /** What to copy. A function is read at click time — for a value that is expensive\n * to build (a CSV of the table) or that changes under the button. */\n text: string | (() => string);\n /** `icon` (default): a square icon button whose tooltip states the result.\n * `label`: a text button whose words change to the result. */\n variant?: \"icon\" | \"label\";\n /** The idle accessible name — and, in the `label` variant, the visible text:\n * \"Copy IBAN\" says WHAT is copied, which a row of three copy icons needs. Default\n * `copyButton.copy`. */\n label?: string;\n /** The button's own look. Default: `ghost` for the icon, `secondary` for the label. */\n buttonVariant?: ButtonVariant;\n /** The icon variant's IconButton size (default `sm`). The `label` variant takes a\n * Button size, `sm` or `md` — passed only when given, so an existing label button\n * keeps its look (keksdose budget-share-card and jobs-panel wanted the compact one). */\n size?: IconButtonSize | ButtonSize;\n /** The icon variant's IconButton tone (e.g. `muted`, so a row of copy icons is no\n * heavier than the row's other actions); in the `label` variant a Button tone,\n * which applies to `link` / `ghost`. The copied / failed colour still wins. */\n tone?: IconButtonTone | ButtonTone;\n /** Keep the click and Enter/Space from reaching a clickable row around the button,\n * as IconButton's own `stopPropagation` does (keksdose feedback-page, users-panel). */\n stopPropagation?: boolean;\n /** Icon variant only: where the result tooltip opens. */\n tooltipSide?: TooltipSide;\n /** Pass through to the tooltip. Left unset, the Tooltip decides on its own — it\n * portals inside a clipping container (a DataTable's `data-clips` scroller). */\n tooltipPortal?: boolean;\n /** ms until the button returns to idle. Default 2000. */\n resetAfter?: number;\n /** Called with the real outcome once the copy settles. */\n onCopied?: (ok: boolean) => void;\n labels?: LabelOverride<CopyButtonLabels>;\n /** The `<button>` in either variant — the {@link IconButton}'s or the {@link Button}'s,\n * never the tooltip's wrapper round it (0.35, round 0.35 R4, the follow-up to API-2).\n * It rides the rest of the props on to that kit button, whose own ref it is. */\n ref?: Ref<HTMLButtonElement>;\n}\n\nconst ICONS: Record<CopyState, typeof Copy> = { idle: Copy, copied: Check, failed: X };\n\n/**\n * A button that copies a value and tells the truth about whether it did.\n *\n * keksdose asked for it after two of its own copy buttons said \"Copied\" on a build\n * where the copy had failed (no Clipboard API over plain http). This one takes its\n * state from {@link useCopyToClipboard}, which reads the result, and has a `failed`\n * state that looks and sounds different from success.\n *\n * The result is spoken through {@link useAnnounce}: activating the button moves no\n * focus, so without a live region a screen-reader user hears nothing at all. A\n * failure goes through an ASSERTIVE region and a success through a polite one — the\n * failure is the news that changes what they do next.\n *\n * The icon variant keeps the SAME accessible name in every state. A button renaming\n * itself under the focus is announced inconsistently across readers; the live region\n * is the channel that is actually heard.\n */\nexport function CopyButton({\n text,\n variant = \"icon\",\n label,\n buttonVariant,\n size,\n tone,\n stopPropagation,\n tooltipSide = \"top\",\n tooltipPortal,\n resetAfter = 2000,\n onCopied,\n labels,\n className,\n disabled,\n ...rest\n}: CopyButtonProps) {\n const words = useKitLabels(\"copyButton\", DEFAULT_COPY_BUTTON_LABELS, labels);\n const { state, copy } = useCopyToClipboard({ resetAfter });\n const polite = useAnnounce();\n const assertive = useAnnounce({ politeness: \"assertive\" });\n\n const idleText = label ?? words.copy;\n const stateText = state === \"copied\" ? words.copied : state === \"failed\" ? words.failed : idleText;\n const Icon = ICONS[state];\n const iconTone = state === \"copied\" ? \"text-success\" : state === \"failed\" ? \"text-danger\" : undefined;\n\n const onClick = async () => {\n const ok = await copy(typeof text === \"function\" ? text() : text);\n if (ok) polite.announce(words.copiedAnnouncement);\n else assertive.announce(words.failedAnnouncement);\n onCopied?.(ok);\n };\n\n const regions = (\n <>\n <span {...polite.regionProps} />\n <span {...assertive.regionProps} />\n </>\n );\n\n if (variant === \"label\") {\n return (\n <>\n <Button\n type=\"button\"\n {...rest}\n disabled={disabled}\n variant={buttonVariant ?? \"secondary\"}\n size={size === \"sm\" || size === \"md\" ? size : undefined}\n tone={tone === \"default\" || tone === \"muted\" || tone === \"danger\" ? tone : undefined}\n data-state={state}\n onClick={(e) => {\n if (stopPropagation) e.stopPropagation();\n void onClick();\n }}\n onKeyDown={(e) => {\n if (stopPropagation && (e.key === \"Enter\" || e.key === \" \")) e.stopPropagation();\n rest.onKeyDown?.(e);\n }}\n className={className}\n >\n <Icon aria-hidden className={cn(\"size-4 shrink-0\", iconTone)} />\n {stateText}\n </Button>\n {regions}\n </>\n );\n }\n\n return (\n <>\n <Tooltip label={stateText} side={tooltipSide} portal={tooltipPortal}>\n <IconButton\n type=\"button\"\n {...rest}\n disabled={disabled}\n aria-label={idleText}\n variant={buttonVariant ?? \"ghost\"}\n size={(size ?? \"sm\") as IconButtonSize}\n tone={tone as IconButtonTone | undefined}\n stopPropagation={stopPropagation}\n data-state={state}\n onClick={() => void onClick()}\n className={cn(iconTone, className)}\n >\n <Icon aria-hidden />\n </IconButton>\n </Tooltip>\n {regions}\n </>\n );\n}\n"],"mappings":";AA2HI,mBACE,KADF;AA1HJ,SAAS,OAAO,MAAM,SAAS;AAC/B,SAAS,UAAU;AACnB,SAAS,oBAAwC;AACjD,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAEnC,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAG3B,SAAS,eAAe;AAExB,SAAS,kCAAkC;AAwD3C,MAAM,QAAwC,EAAE,MAAM,MAAM,QAAQ,OAAO,QAAQ,EAAE;AAmB9E,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,QAAQ,aAAa,cAAc,4BAA4B,MAAM;AAC3E,QAAM,EAAE,OAAO,KAAK,IAAI,mBAAmB,EAAE,WAAW,CAAC;AACzD,QAAM,SAAS,YAAY;AAC3B,QAAM,YAAY,YAAY,EAAE,YAAY,YAAY,CAAC;AAEzD,QAAM,WAAW,SAAS,MAAM;AAChC,QAAM,YAAY,UAAU,WAAW,MAAM,SAAS,UAAU,WAAW,MAAM,SAAS;AAC1F,QAAM,OAAO,MAAM,KAAK;AACxB,QAAM,WAAW,UAAU,WAAW,iBAAiB,UAAU,WAAW,gBAAgB;AAE5F,QAAM,UAAU,YAAY;AAC1B,UAAM,KAAK,MAAM,KAAK,OAAO,SAAS,aAAa,KAAK,IAAI,IAAI;AAChE,QAAI,GAAI,QAAO,SAAS,MAAM,kBAAkB;AAAA,QAC3C,WAAU,SAAS,MAAM,kBAAkB;AAChD,eAAW,EAAE;AAAA,EACf;AAEA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAM,GAAG,OAAO,aAAa;AAAA,IAC9B,oBAAC,UAAM,GAAG,UAAU,aAAa;AAAA,KACnC;AAGF,MAAI,YAAY,SAAS;AACvB,WACE,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACJ,GAAG;AAAA,UACJ;AAAA,UACA,SAAS,iBAAiB;AAAA,UAC1B,MAAM,SAAS,QAAQ,SAAS,OAAO,OAAO;AAAA,UAC9C,MAAM,SAAS,aAAa,SAAS,WAAW,SAAS,WAAW,OAAO;AAAA,UAC3E,cAAY;AAAA,UACZ,SAAS,CAAC,MAAM;AACd,gBAAI,gBAAiB,GAAE,gBAAgB;AACvC,iBAAK,QAAQ;AAAA,UACf;AAAA,UACA,WAAW,CAAC,MAAM;AAChB,gBAAI,oBAAoB,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAM,GAAE,gBAAgB;AAC/E,iBAAK,YAAY,CAAC;AAAA,UACpB;AAAA,UACA;AAAA,UAEA;AAAA,gCAAC,QAAK,eAAW,MAAC,WAAW,GAAG,mBAAmB,QAAQ,GAAG;AAAA,YAC7D;AAAA;AAAA;AAAA,MACH;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAEA,SACE,iCACE;AAAA,wBAAC,WAAQ,OAAO,WAAW,MAAM,aAAa,QAAQ,eACpD;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA,cAAY;AAAA,QACZ,SAAS,iBAAiB;AAAA,QAC1B,MAAO,QAAQ;AAAA,QACf;AAAA,QACA;AAAA,QACA,cAAY;AAAA,QACZ,SAAS,MAAM,KAAK,QAAQ;AAAA,QAC5B,WAAW,GAAG,UAAU,SAAS;AAAA,QAEjC,8BAAC,QAAK,eAAW,MAAC;AAAA;AAAA,IACpB,GACF;AAAA,IACC;AAAA,KACH;AAEJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/copy-button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes, Ref } from \"react\";\nimport { Check, Copy, X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useAnnounce } from \"../hooks/use-announce\";\nimport { useCopyToClipboard } from \"../hooks/use-copy-to-clipboard\";\nimport type { CopyState } from \"../hooks/use-copy-to-clipboard\";\nimport { Button } from \"./button\";\nimport { IconButton } from \"./icon-button\";\nimport type { ButtonSize, ButtonTone, ButtonVariant } from \"./button-look\";\nimport type { IconButtonSize, IconButtonTone } from \"./icon-button\";\nimport type { TooltipSide } from \"./tooltip\";\nimport { DEFAULT_COPY_BUTTON_LABELS } from \"./copy-button-labels\";\n\n/** The words a copy button says. The visible three are short on purpose (they\n * replace the button's own text in the `label` variant); the two announcements\n * are the sentences a screen reader hears, since the button's focus never moves. */\nexport interface CopyButtonLabels {\n copy: string;\n copied: string;\n failed: string;\n copiedAnnouncement: string;\n failedAnnouncement: string;\n}\n\n// Defined in ./copy-button-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_COPY_BUTTON_LABELS };\n\nexport interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\" | \"onClick\"> {\n /** What to copy. A function is read at click time — for a value that is expensive\n * to build (a CSV of the table) or that changes under the button. */\n text: string | (() => string);\n /** `icon` (default): a square icon button whose glyph and colour show the result — its\n * words in a bubble, and from Large up beside the glyph, as every labelled IconButton\n * shows them. `label`: a text button whose words change to the result. */\n variant?: \"icon\" | \"label\";\n /** The idle accessible name — and, in the `label` variant, the visible text:\n * \"Copy IBAN\" says WHAT is copied, which a row of three copy icons needs. Default\n * `copyButton.copy`. */\n label?: string;\n /** The button's own look. Default: `ghost` for the icon, `secondary` for the label. */\n buttonVariant?: ButtonVariant;\n /** The icon variant's IconButton size (default `sm`). The `label` variant takes a\n * Button size, `sm` or `md` — passed only when given, so an existing label button\n * keeps its look (keksdose budget-share-card and jobs-panel wanted the compact one). */\n size?: IconButtonSize | ButtonSize;\n /** The icon variant's IconButton tone (e.g. `muted`, so a row of copy icons is no\n * heavier than the row's other actions); in the `label` variant a Button tone,\n * which applies to `link` / `ghost`. The copied / failed colour still wins. */\n tone?: IconButtonTone | ButtonTone;\n /** Keep the click and Enter/Space from reaching a clickable row around the button,\n * as IconButton's own `stopPropagation` does (keksdose feedback-page, users-panel). */\n stopPropagation?: boolean;\n /** Icon variant only: where the result tooltip opens. */\n tooltipSide?: TooltipSide;\n /** Pass through to the tooltip. Left unset, the Tooltip decides on its own — it\n * portals inside a clipping container (a DataTable's `data-clips` scroller). */\n tooltipPortal?: boolean;\n /** ms until the button returns to idle. Default 2000. */\n resetAfter?: number;\n /** Called with the real outcome once the copy settles. */\n onCopied?: (ok: boolean) => void;\n labels?: LabelOverride<CopyButtonLabels>;\n /** The `<button>` in either variant — the {@link IconButton}'s or the {@link Button}'s,\n * never the tooltip's wrapper round it (0.35, round 0.35 R4, the follow-up to API-2).\n * It rides the rest of the props on to that kit button, whose own ref it is. */\n ref?: Ref<HTMLButtonElement>;\n}\n\nconst ICONS: Record<CopyState, typeof Copy> = { idle: Copy, copied: Check, failed: X };\n\n/**\n * A button that copies a value and tells the truth about whether it did.\n *\n * keksdose asked for it after two of its own copy buttons said \"Copied\" on a build\n * where the copy had failed (no Clipboard API over plain http). This one takes its\n * state from {@link useCopyToClipboard}, which reads the result, and has a `failed`\n * state that looks and sounds different from success.\n *\n * The result is spoken through {@link useAnnounce}: activating the button moves no\n * focus, so without a live region a screen-reader user hears nothing at all. A\n * failure goes through an ASSERTIVE region and a success through a polite one — the\n * failure is the news that changes what they do next.\n *\n * The icon variant keeps the SAME accessible name in every state, and the same words on\n * screen. A button renaming itself under the focus is announced inconsistently across\n * readers; the live region is the channel that is actually heard.\n */\nexport function CopyButton({\n text,\n variant = \"icon\",\n label,\n buttonVariant,\n size,\n tone,\n stopPropagation,\n tooltipSide = \"top\",\n tooltipPortal,\n resetAfter = 2000,\n onCopied,\n labels,\n className,\n disabled,\n ...rest\n}: CopyButtonProps) {\n const words = useKitLabels(\"copyButton\", DEFAULT_COPY_BUTTON_LABELS, labels);\n const { state, copy } = useCopyToClipboard({ resetAfter });\n const polite = useAnnounce();\n const assertive = useAnnounce({ politeness: \"assertive\" });\n\n const idleText = label ?? words.copy;\n const stateText = state === \"copied\" ? words.copied : state === \"failed\" ? words.failed : idleText;\n const Icon = ICONS[state];\n const iconTone = state === \"copied\" ? \"text-success\" : state === \"failed\" ? \"text-danger\" : undefined;\n\n const onClick = async () => {\n const ok = await copy(typeof text === \"function\" ? text() : text);\n if (ok) polite.announce(words.copiedAnnouncement);\n else assertive.announce(words.failedAnnouncement);\n onCopied?.(ok);\n };\n\n const regions = (\n <>\n <span {...polite.regionProps} />\n <span {...assertive.regionProps} />\n </>\n );\n\n if (variant === \"label\") {\n return (\n <>\n <Button\n type=\"button\"\n {...rest}\n disabled={disabled}\n variant={buttonVariant ?? \"secondary\"}\n size={size === \"sm\" || size === \"md\" ? size : undefined}\n tone={tone === \"default\" || tone === \"muted\" || tone === \"danger\" ? tone : undefined}\n data-state={state}\n onClick={(e) => {\n if (stopPropagation) e.stopPropagation();\n void onClick();\n }}\n onKeyDown={(e) => {\n if (stopPropagation && (e.key === \"Enter\" || e.key === \" \")) e.stopPropagation();\n rest.onKeyDown?.(e);\n }}\n className={className}\n >\n <Icon aria-hidden className={cn(\"size-4 shrink-0\", iconTone)} />\n {stateText}\n </Button>\n {regions}\n </>\n );\n }\n\n // IconButton names itself (0.52, refactor audit 2026-10-30, DSP-21): `label` is the\n // bubble — and the text beside the glyph at Large, as on every labelled IconButton —\n // and `aria-label` keeps the name steady. A Tooltip of our own round it cloned\n // `aria-describedby` onto the button, which then read \"Copy, button, Copy\".\n //\n // The idle words, in every state (0.52 code review, deep, charts, finding 8). As the\n // state, \"Copied\" stood at Large beside the glyph of a button named \"Copy IBAN\": the pill\n // changed width on every copy, and the words on screen were not in the name a voice\n // user says to press it (WCAG 2.5.3, label in name). The glyph and its colour show the\n // result, and the live regions say it.\n return (\n <>\n <IconButton\n type=\"button\"\n {...rest}\n disabled={disabled}\n label={idleText}\n aria-label={idleText}\n tooltipSide={tooltipSide}\n tooltipPortal={tooltipPortal}\n variant={buttonVariant ?? \"ghost\"}\n size={(size ?? \"sm\") as IconButtonSize}\n tone={tone as IconButtonTone | undefined}\n stopPropagation={stopPropagation}\n data-state={state}\n onClick={() => void onClick()}\n className={cn(iconTone, className)}\n >\n <Icon aria-hidden />\n </IconButton>\n {regions}\n </>\n );\n}\n"],"mappings":";AA2HI,mBACE,KADF;AA1HJ,SAAS,OAAO,MAAM,SAAS;AAC/B,SAAS,UAAU;AACnB,SAAS,oBAAwC;AACjD,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAEnC,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAI3B,SAAS,kCAAkC;AAyD3C,MAAM,QAAwC,EAAE,MAAM,MAAM,QAAQ,OAAO,QAAQ,EAAE;AAmB9E,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAoB;AAClB,QAAM,QAAQ,aAAa,cAAc,4BAA4B,MAAM;AAC3E,QAAM,EAAE,OAAO,KAAK,IAAI,mBAAmB,EAAE,WAAW,CAAC;AACzD,QAAM,SAAS,YAAY;AAC3B,QAAM,YAAY,YAAY,EAAE,YAAY,YAAY,CAAC;AAEzD,QAAM,WAAW,SAAS,MAAM;AAChC,QAAM,YAAY,UAAU,WAAW,MAAM,SAAS,UAAU,WAAW,MAAM,SAAS;AAC1F,QAAM,OAAO,MAAM,KAAK;AACxB,QAAM,WAAW,UAAU,WAAW,iBAAiB,UAAU,WAAW,gBAAgB;AAE5F,QAAM,UAAU,YAAY;AAC1B,UAAM,KAAK,MAAM,KAAK,OAAO,SAAS,aAAa,KAAK,IAAI,IAAI;AAChE,QAAI,GAAI,QAAO,SAAS,MAAM,kBAAkB;AAAA,QAC3C,WAAU,SAAS,MAAM,kBAAkB;AAChD,eAAW,EAAE;AAAA,EACf;AAEA,QAAM,UACJ,iCACE;AAAA,wBAAC,UAAM,GAAG,OAAO,aAAa;AAAA,IAC9B,oBAAC,UAAM,GAAG,UAAU,aAAa;AAAA,KACnC;AAGF,MAAI,YAAY,SAAS;AACvB,WACE,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACJ,GAAG;AAAA,UACJ;AAAA,UACA,SAAS,iBAAiB;AAAA,UAC1B,MAAM,SAAS,QAAQ,SAAS,OAAO,OAAO;AAAA,UAC9C,MAAM,SAAS,aAAa,SAAS,WAAW,SAAS,WAAW,OAAO;AAAA,UAC3E,cAAY;AAAA,UACZ,SAAS,CAAC,MAAM;AACd,gBAAI,gBAAiB,GAAE,gBAAgB;AACvC,iBAAK,QAAQ;AAAA,UACf;AAAA,UACA,WAAW,CAAC,MAAM;AAChB,gBAAI,oBAAoB,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAM,GAAE,gBAAgB;AAC/E,iBAAK,YAAY,CAAC;AAAA,UACpB;AAAA,UACA;AAAA,UAEA;AAAA,gCAAC,QAAK,eAAW,MAAC,WAAW,GAAG,mBAAmB,QAAQ,GAAG;AAAA,YAC7D;AAAA;AAAA;AAAA,MACH;AAAA,MACC;AAAA,OACH;AAAA,EAEJ;AAYA,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACJ,GAAG;AAAA,QACJ;AAAA,QACA,OAAO;AAAA,QACP,cAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,SAAS,iBAAiB;AAAA,QAC1B,MAAO,QAAQ;AAAA,QACf;AAAA,QACA;AAAA,QACA,cAAY;AAAA,QACZ,SAAS,MAAM,KAAK,QAAQ;AAAA,QAC5B,WAAW,GAAG,UAAU,SAAS;AAAA,QAEjC,8BAAC,QAAK,eAAW,MAAC;AAAA;AAAA,IACpB;AAAA,IACC;AAAA,KACH;AAEJ;","names":[]}
|
|
@@ -4,7 +4,7 @@ import { useCallback, useId, useMemo } from "react";
|
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
6
|
import { COUNTRY_CODES, countryName, normalizeCountryCode } from "../lib/countries.js";
|
|
7
|
-
import {
|
|
7
|
+
import { searchRanked } from "../lib/text-match.js";
|
|
8
8
|
import { FieldChevron, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FloatingField } from "./field-base.js";
|
|
9
9
|
import {
|
|
10
10
|
assignRef,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "../i18n/kit-labels.js";
|
|
25
25
|
import { DEFAULT_COUNTRY_SELECT_LABELS } from "./country-select-labels.js";
|
|
26
26
|
function search(entries, q, locale) {
|
|
27
|
-
return entries
|
|
27
|
+
return searchRanked(entries, q, (e) => [e.name], { code: (e) => e.code, locale }).sort((a, b) => a.rank - b.rank).map((x) => x.row);
|
|
28
28
|
}
|
|
29
29
|
function toOption(e, flags, group) {
|
|
30
30
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/country-select.tsx"],"sourcesContent":["import { useCallback, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode, Ref } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { COUNTRY_CODES, countryName, normalizeCountryCode } from \"../lib/countries\";\nimport { rankMatch } from \"../lib/text-match\";\nimport { FieldChevron, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FloatingField } from \"./field-base\";\nimport {\n assignRef,\n EndHintRow,\n LockedReason,\n mergeDescribedBy,\n useFieldMessages,\n useLockReason,\n type DisabledReasonDisplay,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport { CurrencyFlag } from \"./currency-select\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport type { CommitScope } from \"./write-lock\";\nimport {\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n useKitLabels,\n useKitLocale,\n type LabelOverride,\n} from \"../i18n/kit-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"./country-select-labels\";\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\n/** The words {@link CountrySelect} renders on its own behalf. The list's \"no results\",\n * \"loading\" and result-count announcements are the shared `combobox` namespace's,\n * as on every other searchable picker. */\nexport interface CountrySelectLabels {\n /** What the field is, when the caller gives no `label`: the first half of the\n * trigger's accessible name, the phone sheet's title, and the empty trigger's\n * text. */\n country: string;\n /** The search box in the open list. */\n search: string;\n /** The heading between the `preferred` countries and all the others. Shown only\n * when `preferred` puts something above it. */\n others: string;\n}\n\n// Defined in ./country-select-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_COUNTRY_SELECT_LABELS };\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\n/**\n * `onChange` is the kit's — \"a country was picked\", carrying its code — rather than\n * the div's form event. `id` and the `aria-*` wiring below go to the TRIGGER, which is\n * the control a reader meets; everything else in `rest` dresses the wrapper.\n *\n * `Clearable` follows `clearable`, the way {@link EntityCombobox}'s `C` follows its\n * `clearValue`: it is inferred from the prop, so `onChange` is `(code: string) => void`\n * for the field as it always was (`onChange={setCountry}` over a `string` state still\n * type-checks), and `(code: string | null) => void` once `clearable` is set — the\n * `null` of a clear cannot reach a handler that was never told about it.\n */\nexport interface CountrySelectProps<Clearable extends boolean = false>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\" | \"defaultValue\">, FieldHelpProps {\n /** The chosen country as an ISO 3166-1 alpha-2 code (\"CH\"), in either case;\n * `null`, `undefined` or `\"\"` when none is. A code outside the list still shows\n * its name — the list restricts what can be PICKED, not what can be shown. */\n value: string | null | undefined;\n /** A country was picked: its alpha-2 code, always upper-case. With `clearable`,\n * also `null`: the \"×\" (or Delete on the trigger) emptied the field. */\n onChange: (code: Clearable extends true ? string | null : string) => void;\n /**\n * Offer a clear \"×\" in place of the chevron while a country is chosen; it hands\n * `onChange` `null` (the kit's later list: an optional country — a contact's\n * nationality, a bank filter's \"any country\" — could be set but never unset again).\n *\n * The combobox family's clear, as {@link EntityCombobox} draws it: a pointer\n * shortcut inside the trigger, out of the tab order so it is not a second stop\n * between this field and the next, named by `clearLabel` (default: the\n * `combobox.clear` label every picker in the family shares). The keyboard way is\n * Delete or Backspace on the closed trigger. Not offered while `disabled` or locked\n * (`commit` / `disabledReason`): a clear is a change like any other.\n */\n clearable?: Clearable;\n /** The clear \"×\"'s accessible name. Wins over `combobox.clear` from the\n * {@link UiKitProvider}, as {@link EntityCombobox}'s `clearLabel` does. */\n clearLabel?: string;\n /**\n * The TRIGGER — the focusable `<button>` a reader meets — as a React 19 ref prop,\n * the way {@link MoneyField} takes its input's (kastlan: react-hook-form's\n * focus-on-error calls `focus()` on whatever `field.ref` was handed, and without a\n * ref the first field in error on an address form was never focused). See\n * `RhfCountrySelect`, which passes it.\n */\n ref?: Ref<HTMLButtonElement>;\n /**\n * Offer only these codes (any case; duplicates and malformed entries dropped).\n * Default: every ISO 3166-1 country, {@link COUNTRY_CODES}. Sorted by name either\n * way — the order given is not kept, because the reader looks a country up by its\n * name in their own language, and any other order is one they cannot predict.\n *\n * keksdose's bank picker passes the countries its configured bank-connection provider\n * serves, read at runtime: its connections are provider-neutral since 2026-10-29, so\n * the list is no longer a fixed fifteen. A code that is not in ISO's list is allowed\n * here on purpose: `XK` (Kosovo) is the app's decision, not the kit's.\n */\n countries?: readonly string[];\n /**\n * A short group shown FIRST, in the order given, above the alphabetical rest —\n * kastlan's CH, LI, DE, AT, FR, IT, the six countries most of its customers live\n * in, out of a list of 249. Each code appears once: a preferred country is not\n * repeated in the rest. The rest gets a heading (`labels.others`) so the boundary\n * is visible. Search keeps the split: matching preferred countries stay on top.\n * Codes not in `countries` are ignored — `countries` decides what can be picked.\n */\n preferred?: readonly string[];\n /**\n * Draw each country's flag as a flag-icons span (`fi fi-ch`), in the rows and on\n * the trigger. Default `false`, because the kit ships no flag stylesheet — the same\n * convention as `LanguageMenu`'s `country` and `CurrencySelect`: the host imports\n * `flag-icons/css/flag-icons.min.css`, and without it a flag is an empty box.\n */\n flags?: boolean;\n /** The floating label, as on every field. A string also names the trigger\n * (\"Country: Switzerland\"). */\n label?: ReactNode;\n /** As {@link Select}'s `hint`: plain TEXT is a caption under the field, attached\n * through `aria-describedby`; a {@link FieldHint} rides the label line (or the\n * field's end, when there is no label). */\n hint?: ReactNode;\n /** As {@link Input}'s `error`: rendered under the field, on the trigger's\n * `aria-describedby`, and implies `invalid`. */\n error?: ReactNode;\n /** Required and unanswered — {@link FIELD_INVALID}, as on every field. */\n invalid?: boolean;\n /** The empty trigger's text. Default: nothing when the field has a `label` or an\n * `aria-labelledby` (the label already says what goes here), else `labels.country`. */\n placeholder?: string;\n /** As on the other pickers: no focus, no list, the settled look. */\n disabled?: boolean;\n /**\n * Why the country cannot be changed — {@link Button}'s `disabledReason`, for a field\n * that saves itself. The trigger stays focusable and `aria-disabled`, the list does\n * not open and `onChange` never fires; the reason is a line under the field at Large\n * and on a touch screen, else in the kit {@link Tooltip} (`disabledReasonDisplay`), and\n * on the trigger's `aria-describedby`. Wins over `disabled`.\n */\n disabledReason?: ReactNode;\n /** Where the reason shows — {@link Button}'s `disabledReasonDisplay`: a line under the\n * field at Large and on a touch screen by default, the tooltip otherwise (DUP-3). */\n disabledReasonDisplay?: DisabledReasonDisplay;\n /**\n * This select SAVES on change (a settings row with no Save button). Under a locked\n * {@link WriteLockProvider} it is locked the `disabledReason` way, with the lock's\n * reason. A country field inside a form that has its own Save stays editable under\n * the lock — leave this off there, and put `commit` on the Save.\n */\n commit?: CommitScope;\n /** The language the names are in. Default: the `<UiKitProvider locale>`, else\n * English (see {@link countryName}). */\n locale?: string;\n /** Per-instance overrides of {@link CountrySelectLabels}, over the provider's\n * `countrySelect` namespace, over English. */\n labels?: LabelOverride<CountrySelectLabels>;\n /**\n * Name the trigger by an element of the caller's — a form's own label above the\n * field, which is how `RhfCountrySelect` names it. The trigger is then named by\n * that element AND the chosen country (two references, as {@link DatePicker}'s\n * trigger is named, so \"Country Switzerland\" needs no punctuation to translate),\n * instead of the composed \"label: country\". An `aria-label` still wins. Before\n * 0.23 this landed on the wrapper `<div>`, which has no role to be named.\n */\n \"aria-labelledby\"?: string;\n}\n\ninterface Entry {\n code: string;\n name: string;\n}\n\n/** Best rank first; a stable sort, so equal ranks keep the collated (or preferred)\n * order they came in. The rank is the kit's one (`rankMatch`, DUP-5 — written here\n * first): the code itself (a two-letter query \"ch\" means Switzerland before it means\n * Chad), then the start of the name, then anywhere in it, with accents and case folded\n * — \"osterreich\" finds Österreich, \"cote\" Côte d'Ivoire. */\nfunction search(entries: Entry[], q: string, locale: string): Entry[] {\n return entries\n .map((e) => ({ e, r: rankMatch(e.name, q, { code: e.code, locale }) }))\n .filter((x) => x.r >= 0)\n .sort((a, b) => a.r - b.r)\n .map((x) => x.e);\n}\n\nfunction toOption(e: Entry, flags: boolean, group?: string): ComboOption<string> {\n return {\n value: e.code,\n label: e.name,\n icon: flags ? <CurrencyFlag country={e.code.toLowerCase()} /> : undefined,\n group,\n };\n}\n\nfunction collator(locale: string): Intl.Collator {\n try {\n return new Intl.Collator(locale);\n } catch {\n return new Intl.Collator();\n }\n}\n\n/**\n * A country picker: the value is the ISO 3166-1 alpha-2 code (\"CH\"), the names are the\n * runtime's own (`Intl.DisplayNames`) in the provider's locale, sorted by that\n * locale's collation. Requested by kastlan (customer and handover addresses, with its\n * six neighbours on top) and keksdose (its bank picker's countries) — both\n * apps carried a hand-written list of names in two languages.\n *\n * ## Built on the kit's searchable combobox\n *\n * 249 countries is not a menu, it is a lookup, and the list has to work on a phone. So\n * this is the entity pickers' machinery — `useComboboxCore` and `ComboboxPanel`, which\n * {@link EntityCombobox} is also made of — not a native `<select>`: the anchored panel\n * with a search box on a pointer device, the full-screen sheet with its own search box\n * on a phone (live #200), the same keyboard (APG listbox), the same announcements, the\n * same group headings. Only the trigger is this component's own, because the trigger\n * is where the write lock lives: `EntityCombobox`'s is a native `disabled` button,\n * and a locked field has to stay focusable to say why.\n *\n * The ranking is this component's own too: it folds accents and case (\"osterreich\"\n * finds Österreich), matches the code (\"ch\" ranks Switzerland first), and ranks\n * name-starts above name-contains — the kit's `rankMatch`, which began here. The panel\n * is handed those rows in place of the core's filter, which does not rank.\n *\n * Field anatomy as on every field: floating `label`, `hint`, `error`, `invalid`; the\n * trigger is named \"label: value\" as the other trigger pickers are.\n *\n * Since 0.23 it takes a `ref` to the trigger (react-hook-form's focus-on-error, kastlan)\n * and `clearable`, which hands `onChange` `null`.\n */\nexport function CountrySelect<Clearable extends boolean = false>({\n value,\n onChange,\n countries,\n preferred,\n flags = false,\n label,\n hint,\n help,\n error,\n invalid,\n placeholder,\n disabled,\n disabledReason: ownReason,\n disabledReasonDisplay,\n commit,\n clearable,\n clearLabel,\n locale: localeProp,\n labels: labelsProp,\n className,\n id,\n ref,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n ...rest\n}: CountrySelectProps<Clearable>) {\n const text = useKitLabels(\"countrySelect\", DEFAULT_COUNTRY_SELECT_LABELS, labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n // The clear's name is the family's (`combobox.clear`), as on EntityCombobox; a prop\n // left `undefined` falls through to the provider rather than masking it.\n const comboText = useKitLabels(\"combobox\", DEFAULT_COMBOBOX_LABELS, { clear: clearLabel });\n // English without a locale — see `countryName`.\n const locale = useKitLocale(localeProp) ?? \"en\";\n\n // ── The list ──────────────────────────────────────────────────────────────\n // Keyed on the arrays' CONTENTS: `countries={[\"DE\", \"AT\"]}` written inline is a new\n // array every render, and resorting 249 names on every keystroke elsewhere in the\n // form is work for nothing.\n const countriesKey = countries?.join(\",\");\n const preferredKey = preferred?.join(\",\");\n const { top, rest: others } = useMemo(() => {\n const seen = new Set<string>();\n const pool: string[] = [];\n for (const raw of countriesKey !== undefined ? countriesKey.split(\",\") : COUNTRY_CODES) {\n const code = normalizeCountryCode(raw);\n if (code && !seen.has(code)) {\n seen.add(code);\n pool.push(code);\n }\n }\n const entry = (code: string): Entry => ({ code, name: countryName(code, locale) });\n const firsts: Entry[] = [];\n const picked = new Set<string>();\n for (const raw of preferredKey ? preferredKey.split(\",\") : []) {\n const code = normalizeCountryCode(raw);\n if (code && seen.has(code) && !picked.has(code)) {\n picked.add(code);\n firsts.push(entry(code));\n }\n }\n const compare = collator(locale).compare;\n const sorted = pool\n .filter((c) => !picked.has(c))\n .map(entry)\n .sort((a, b) => compare(a.name, b.name));\n return { top: firsts, rest: sorted };\n }, [countriesKey, preferredKey, locale]);\n\n // `filter: false` — the panel is handed the rows `search` ranks below instead of\n // the core's substring filter; the core still owns open, query, the highlighted row,\n // anchoring and dismissal.\n const allOptions = useMemo(() => [...top, ...others].map((e) => toOption(e, flags)), [top, others, flags]);\n const core = useComboboxCore<string>({ options: allOptions, filter: false });\n const { open, setOpen, query, triggerRef, closeToTrigger } = core;\n // The core anchors the panel to the trigger and hands focus back to it; the caller's\n // `ref` wants the same element. One callback feeds both.\n const setTrigger = useCallback(\n (el: HTMLButtonElement | null) => {\n triggerRef.current = el;\n assignRef(ref, el);\n },\n [ref, triggerRef],\n );\n\n const q = query.trim();\n const results = useMemo(() => {\n const firsts = q ? search(top, q, locale) : top;\n const below = q ? search(others, q, locale) : others;\n // The heading only where it separates something: with no preferred country in\n // view it would head the whole list and divide nothing.\n const heading = firsts.length > 0 ? text.others : undefined;\n return [...firsts.map((e) => toOption(e, flags)), ...below.map((e) => toOption(e, flags, heading))];\n }, [top, others, q, text.others, flags, locale]);\n\n // ── The lock ──────────────────────────────────────────────────────────────\n const lock = useLockReason(commit, ownReason);\n const locked = lock.locked;\n const inert = locked || Boolean(disabled);\n // A lock (or `disabled`) arriving while the list is up closes it — adjusted while\n // rendering, as the core adjusts its own query, so no frame shows a list that can\n // no longer be used.\n if (inert && open) setOpen(false);\n\n // ── The field ─────────────────────────────────────────────────────────────\n const generated = useId();\n const triggerId = id ?? generated;\n const listboxId = `${generated}-listbox`;\n // Text is a caption under the field; a FieldHint rides the label line, and is that\n // caption at Large and on touch (§4). The caller's ids, the caption, a lock's reason,\n // the error — Select's order. Through useFieldMessages since 0.34 (DUP-2, DUP-8).\n const field = useFieldMessages({\n hint,\n help,\n name: label ?? ariaLabel,\n error,\n invalid,\n describedBy: ariaDescribedBy,\n ariaInvalid,\n reasonId: locked && lock.reasonId,\n });\n\n const code = normalizeCountryCode(value);\n const selectedName = code ? countryName(code, locale) : undefined;\n // A field named by a label of the caller's (`aria-labelledby`) is a labelled field too:\n // the label already says what goes here.\n const emptyText = placeholder ?? (label === undefined && !ariaLabelledBy ? text.country : \"\");\n const fieldName = typeof label === \"string\" ? label : text.country;\n const valueId = `${generated}-value`;\n // Named by the caller's element (a form's label) — then the shown text is the second\n // reference, as the composed name below has the value second; but only when it IS a\n // value or the caller's placeholder, never the `country` fallback word.\n const labelledBy =\n ariaLabel === undefined && ariaLabelledBy\n ? mergeDescribedBy(ariaLabelledBy, (selectedName !== undefined || Boolean(placeholder)) && valueId)\n : undefined;\n const name =\n labelledBy !== undefined\n ? undefined\n : (ariaLabel ??\n (selectedName !== undefined\n ? common.fieldValue(fieldName, selectedName)\n : placeholder\n ? common.fieldValue(fieldName, placeholder)\n : fieldName));\n\n const showClear = Boolean(clearable && code && !inert);\n // `onChange` widened to take the clear's `null` — its type says so once `clearable`\n // is set, and the clear is only ever offered then.\n const emit = onChange as (code: string | null) => void;\n const clear = () => {\n if (!showClear) return;\n emit(null);\n };\n\n const choose = (o: ComboOption<string>) => {\n if (inert) return;\n emit(o.value);\n // Back to the trigger: the panel holding focus is about to unmount, and a keyboard\n // user who has answered this field should stand on it, ready to Tab on.\n closeToTrigger();\n };\n\n const trigger = (\n <button\n ref={setTrigger}\n id={triggerId}\n type=\"button\"\n // A closed choice that expands into the list below — the role every trigger\n // picker in the kit wears (see EntityCombobox), and the one that supports\n // `aria-invalid`.\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={name}\n aria-labelledby={labelledBy}\n aria-invalid={field.isInvalid || undefined}\n aria-required={ariaRequired}\n aria-describedby={field.describedBy}\n // The focusable kind of disabled while locked (Button's rule): native `disabled`\n // would take the field out of the tab order, and the reason with it.\n disabled={locked ? undefined : disabled}\n aria-disabled={locked || undefined}\n onClick={(e) => {\n if (inert) {\n e.preventDefault();\n return;\n }\n setOpen((o) => !o);\n }}\n // Down/Up opens the list from the closed trigger, per the APG; Enter and Space\n // already do through the button's own click.\n onKeyDown={(e) => {\n if (inert) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n } else if (showClear && !open && (e.key === \"Delete\" || e.key === \"Backspace\")) {\n // The keyboard's clear: the \"×\" is out of the tab order (see `clearable`).\n e.preventDefault();\n clear();\n }\n }}\n className={cn(\n FIELD_TRIGGER,\n // `peer` dims the floating label with a natively disabled trigger, as Select's.\n \"peer pe-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n inert && \"cursor-not-allowed opacity-50\",\n field.isInvalid && FIELD_INVALID,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {flags && code && <CurrencyFlag country={code.toLowerCase()} />}\n {/* A long name (\"Saint Vincent and the Grenadines\") truncates rather than\n widening the field: the kit is checked at 390px. From Large up it wraps and\n the trigger grows instead (text size §10.17, round 0.34 T1). */}\n <span id={valueId} className={cn(\"truncate-until-large\", selectedName === undefined && \"text-placeholder\")}>\n {selectedName ?? emptyText}\n </span>\n </span>\n {showClear ? (\n /* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- tabIndex -1 inside\n the trigger <button>, so it never holds focus and a key handler here could never\n fire; the keys go to the trigger, where Delete and Backspace clear. A pointer\n shortcut, as on EntityCombobox. */\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={comboText.clear}\n onClick={(e) => {\n e.stopPropagation();\n clear();\n }}\n className=\"absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-placeholder hover:text-secondary\"\n >\n <X aria-hidden className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n );\n\n const control = locked ? (\n // Button's frame: the line under the trigger, or the Tooltip with a hidden copy for\n // `aria-describedby`. `items-stretch`: in the line's column the trigger keeps the\n // field's full width.\n <LockedReason lock={lock} display={disabledReasonDisplay} className=\"flex w-full items-stretch\">\n {trigger}\n </LockedReason>\n ) : (\n trigger\n );\n\n const box =\n label !== undefined ? (\n <FloatingField htmlFor={triggerId} label={label} staticLabel hint={field.labelHint}>\n {control}\n </FloatingField>\n ) : (\n // No label line to ride: a \"?\" goes at the field's end, outside the box, as on an\n // unlabelled Select.\n <EndHintRow hint={field.labelHint}>\n <div className=\"relative\">{control}</div>\n </EndHintRow>\n );\n\n return (\n // `rest` dresses the wrapper — a `data-tour` anchor, a test id. The name, the\n // description and the invalid state are on the trigger above.\n <div {...rest} className={className}>\n {box}\n {field.below}\n <ComboboxPanel\n // The core with the rows ranked above in place of its own filter's.\n core={{ ...core, results }}\n listboxId={listboxId}\n // On a phone the list is a full-screen sheet, which needs the field's own\n // name to say what it is asking for (live #200).\n sheetTitle={label ?? text.country}\n searchPlaceholder={text.search}\n isSelected={(v) => v === code}\n onChoose={choose}\n showCreate={false}\n onCreate={() => {}}\n createContent={null}\n />\n </div>\n );\n}\n"],"mappings":"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","names":["code"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/country-select.tsx"],"sourcesContent":["import { useCallback, useId, useMemo } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode, Ref } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { COUNTRY_CODES, countryName, normalizeCountryCode } from \"../lib/countries\";\nimport { searchRanked } from \"../lib/text-match\";\nimport { FieldChevron, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FloatingField } from \"./field-base\";\nimport {\n assignRef,\n EndHintRow,\n LockedReason,\n mergeDescribedBy,\n useFieldMessages,\n useLockReason,\n type DisabledReasonDisplay,\n type FieldHelpProps,\n} from \"./field-parts\";\nimport { CurrencyFlag } from \"./currency-select\";\nimport { ComboboxPanel, useComboboxCore, type ComboOption } from \"./combobox-core\";\nimport type { CommitScope } from \"./write-lock\";\nimport {\n DEFAULT_COMBOBOX_LABELS,\n DEFAULT_COMMON_LABELS,\n useKitLabels,\n useKitLocale,\n type LabelOverride,\n} from \"../i18n/kit-labels\";\nimport { DEFAULT_COUNTRY_SELECT_LABELS } from \"./country-select-labels\";\n\n// ── Labels ────────────────────────────────────────────────────────────────────\n\n/** The words {@link CountrySelect} renders on its own behalf. The list's \"no results\",\n * \"loading\" and result-count announcements are the shared `combobox` namespace's,\n * as on every other searchable picker. */\nexport interface CountrySelectLabels {\n /** What the field is, when the caller gives no `label`: the first half of the\n * trigger's accessible name, the phone sheet's title, and the empty trigger's\n * text. */\n country: string;\n /** The search box in the open list. */\n search: string;\n /** The heading between the `preferred` countries and all the others. Shown only\n * when `preferred` puts something above it. */\n others: string;\n}\n\n// Defined in ./country-select-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_COUNTRY_SELECT_LABELS };\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\n/**\n * `onChange` is the kit's — \"a country was picked\", carrying its code — rather than\n * the div's form event. `id` and the `aria-*` wiring below go to the TRIGGER, which is\n * the control a reader meets; everything else in `rest` dresses the wrapper.\n *\n * `Clearable` follows `clearable`, the way {@link EntityCombobox}'s `C` follows its\n * `clearValue`: it is inferred from the prop, so `onChange` is `(code: string) => void`\n * for the field as it always was (`onChange={setCountry}` over a `string` state still\n * type-checks), and `(code: string | null) => void` once `clearable` is set — the\n * `null` of a clear cannot reach a handler that was never told about it.\n */\nexport interface CountrySelectProps<Clearable extends boolean = false>\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\" | \"defaultValue\">, FieldHelpProps {\n /** The chosen country as an ISO 3166-1 alpha-2 code (\"CH\"), in either case;\n * `null`, `undefined` or `\"\"` when none is. A code outside the list still shows\n * its name — the list restricts what can be PICKED, not what can be shown. */\n value: string | null | undefined;\n /** A country was picked: its alpha-2 code, always upper-case. With `clearable`,\n * also `null`: the \"×\" (or Delete on the trigger) emptied the field. */\n onChange: (code: Clearable extends true ? string | null : string) => void;\n /**\n * Offer a clear \"×\" in place of the chevron while a country is chosen; it hands\n * `onChange` `null` (the kit's later list: an optional country — a contact's\n * nationality, a bank filter's \"any country\" — could be set but never unset again).\n *\n * The combobox family's clear, as {@link EntityCombobox} draws it: a pointer\n * shortcut inside the trigger, out of the tab order so it is not a second stop\n * between this field and the next, named by `clearLabel` (default: the\n * `combobox.clear` label every picker in the family shares). The keyboard way is\n * Delete or Backspace on the closed trigger. Not offered while `disabled` or locked\n * (`commit` / `disabledReason`): a clear is a change like any other.\n */\n clearable?: Clearable;\n /** The clear \"×\"'s accessible name. Wins over `combobox.clear` from the\n * {@link UiKitProvider}, as {@link EntityCombobox}'s `clearLabel` does. */\n clearLabel?: string;\n /**\n * The TRIGGER — the focusable `<button>` a reader meets — as a React 19 ref prop,\n * the way {@link MoneyField} takes its input's (kastlan: react-hook-form's\n * focus-on-error calls `focus()` on whatever `field.ref` was handed, and without a\n * ref the first field in error on an address form was never focused). See\n * `RhfCountrySelect`, which passes it.\n */\n ref?: Ref<HTMLButtonElement>;\n /**\n * Offer only these codes (any case; duplicates and malformed entries dropped).\n * Default: every ISO 3166-1 country, {@link COUNTRY_CODES}. Sorted by name either\n * way — the order given is not kept, because the reader looks a country up by its\n * name in their own language, and any other order is one they cannot predict.\n *\n * keksdose's bank picker passes the countries its configured bank-connection provider\n * serves, read at runtime: its connections are provider-neutral since 2026-10-29, so\n * the list is no longer a fixed fifteen. A code that is not in ISO's list is allowed\n * here on purpose: `XK` (Kosovo) is the app's decision, not the kit's.\n */\n countries?: readonly string[];\n /**\n * A short group shown FIRST, in the order given, above the alphabetical rest —\n * kastlan's CH, LI, DE, AT, FR, IT, the six countries most of its customers live\n * in, out of a list of 249. Each code appears once: a preferred country is not\n * repeated in the rest. The rest gets a heading (`labels.others`) so the boundary\n * is visible. Search keeps the split: matching preferred countries stay on top.\n * Codes not in `countries` are ignored — `countries` decides what can be picked.\n */\n preferred?: readonly string[];\n /**\n * Draw each country's flag as a flag-icons span (`fi fi-ch`), in the rows and on\n * the trigger. Default `false`, because the kit ships no flag stylesheet — the same\n * convention as `LanguageMenu`'s `country` and `CurrencySelect`: the host imports\n * `flag-icons/css/flag-icons.min.css`, and without it a flag is an empty box.\n */\n flags?: boolean;\n /** The floating label, as on every field. A string also names the trigger\n * (\"Country: Switzerland\"). */\n label?: ReactNode;\n /** As {@link Select}'s `hint`: plain TEXT is a caption under the field, attached\n * through `aria-describedby`; a {@link FieldHint} rides the label line (or the\n * field's end, when there is no label). */\n hint?: ReactNode;\n /** As {@link Input}'s `error`: rendered under the field, on the trigger's\n * `aria-describedby`, and implies `invalid`. */\n error?: ReactNode;\n /** Required and unanswered — {@link FIELD_INVALID}, as on every field. */\n invalid?: boolean;\n /** The empty trigger's text. Default: nothing when the field has a `label` or an\n * `aria-labelledby` (the label already says what goes here), else `labels.country`. */\n placeholder?: string;\n /** As on the other pickers: no focus, no list, the settled look. */\n disabled?: boolean;\n /**\n * Why the country cannot be changed — {@link Button}'s `disabledReason`, for a field\n * that saves itself. The trigger stays focusable and `aria-disabled`, the list does\n * not open and `onChange` never fires; the reason is a line under the field at Large\n * and on a touch screen, else in the kit {@link Tooltip} (`disabledReasonDisplay`), and\n * on the trigger's `aria-describedby`. Wins over `disabled`.\n */\n disabledReason?: ReactNode;\n /** Where the reason shows — {@link Button}'s `disabledReasonDisplay`: a line under the\n * field at Large and on a touch screen by default, the tooltip otherwise (DUP-3). */\n disabledReasonDisplay?: DisabledReasonDisplay;\n /**\n * This select SAVES on change (a settings row with no Save button). Under a locked\n * {@link WriteLockProvider} it is locked the `disabledReason` way, with the lock's\n * reason. A country field inside a form that has its own Save stays editable under\n * the lock — leave this off there, and put `commit` on the Save.\n */\n commit?: CommitScope;\n /** The language the names are in. Default: the `<UiKitProvider locale>`, else\n * English (see {@link countryName}). */\n locale?: string;\n /** Per-instance overrides of {@link CountrySelectLabels}, over the provider's\n * `countrySelect` namespace, over English. */\n labels?: LabelOverride<CountrySelectLabels>;\n /**\n * Name the trigger by an element of the caller's — a form's own label above the\n * field, which is how `RhfCountrySelect` names it. The trigger is then named by\n * that element AND the chosen country (two references, as {@link DatePicker}'s\n * trigger is named, so \"Country Switzerland\" needs no punctuation to translate),\n * instead of the composed \"label: country\". An `aria-label` still wins. Before\n * 0.23 this landed on the wrapper `<div>`, which has no role to be named.\n */\n \"aria-labelledby\"?: string;\n}\n\ninterface Entry {\n code: string;\n name: string;\n}\n\n/** Every word of the query in the name, or the whole code (0.51.4's rule: \"kinshasa\n * congo\" finds Congo - Kinshasa; refactor audit 2026-10-30, DUP-4), best rank first; a\n * stable sort, so equal ranks keep the collated (or preferred) order they came in. The\n * rank is the kit's one, for the WHOLE query (`rankMatch`, DUP-5 — written here first):\n * the code itself (a two-letter query \"ch\" means Switzerland before it means Chad), then\n * the start of the name, then anywhere in it, then the words apart (`searchRanked`, each\n * name and code folded once), with accents and case folded — \"osterreich\" finds\n * Österreich, \"cote\" Côte d'Ivoire.\n *\n * A word is the code or it is not: searched INSIDE the code, as one more text, \"w\"\n * listed Aruba (AW) and Samoa (WS) and \"d\" Germany (DE), names with nothing of the\n * query in them (0.52 code review, country-select.tsx). */\nfunction search(entries: Entry[], q: string, locale: string): Entry[] {\n return searchRanked(entries, q, (e) => [e.name], { code: (e) => e.code, locale })\n .sort((a, b) => a.rank - b.rank)\n .map((x) => x.row);\n}\n\nfunction toOption(e: Entry, flags: boolean, group?: string): ComboOption<string> {\n return {\n value: e.code,\n label: e.name,\n icon: flags ? <CurrencyFlag country={e.code.toLowerCase()} /> : undefined,\n group,\n };\n}\n\nfunction collator(locale: string): Intl.Collator {\n try {\n return new Intl.Collator(locale);\n } catch {\n return new Intl.Collator();\n }\n}\n\n/**\n * A country picker: the value is the ISO 3166-1 alpha-2 code (\"CH\"), the names are the\n * runtime's own (`Intl.DisplayNames`) in the provider's locale, sorted by that\n * locale's collation. Requested by kastlan (customer and handover addresses, with its\n * six neighbours on top) and keksdose (its bank picker's countries) — both\n * apps carried a hand-written list of names in two languages.\n *\n * ## Built on the kit's searchable combobox\n *\n * 249 countries is not a menu, it is a lookup, and the list has to work on a phone. So\n * this is the entity pickers' machinery — `useComboboxCore` and `ComboboxPanel`, which\n * {@link EntityCombobox} is also made of — not a native `<select>`: the anchored panel\n * with a search box on a pointer device, the full-screen sheet with its own search box\n * on a phone (live #200), the same keyboard (APG listbox), the same announcements, the\n * same group headings. Only the trigger is this component's own, because the trigger\n * is where the write lock lives: `EntityCombobox`'s is a native `disabled` button,\n * and a locked field has to stay focusable to say why.\n *\n * The ranking is this component's own too: it folds accents and case (\"osterreich\"\n * finds Österreich), matches the code (\"ch\" ranks Switzerland first), and ranks\n * name-starts above name-contains — the kit's `rankMatch`, which began here. The panel\n * is handed those rows in place of the core's filter, which does not rank.\n *\n * Field anatomy as on every field: floating `label`, `hint`, `error`, `invalid`; the\n * trigger is named \"label: value\" as the other trigger pickers are.\n *\n * Since 0.23 it takes a `ref` to the trigger (react-hook-form's focus-on-error, kastlan)\n * and `clearable`, which hands `onChange` `null`.\n */\nexport function CountrySelect<Clearable extends boolean = false>({\n value,\n onChange,\n countries,\n preferred,\n flags = false,\n label,\n hint,\n help,\n error,\n invalid,\n placeholder,\n disabled,\n disabledReason: ownReason,\n disabledReasonDisplay,\n commit,\n clearable,\n clearLabel,\n locale: localeProp,\n labels: labelsProp,\n className,\n id,\n ref,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n \"aria-required\": ariaRequired,\n ...rest\n}: CountrySelectProps<Clearable>) {\n const text = useKitLabels(\"countrySelect\", DEFAULT_COUNTRY_SELECT_LABELS, labelsProp);\n const common = useKitLabels(\"common\", DEFAULT_COMMON_LABELS);\n // The clear's name is the family's (`combobox.clear`), as on EntityCombobox; a prop\n // left `undefined` falls through to the provider rather than masking it.\n const comboText = useKitLabels(\"combobox\", DEFAULT_COMBOBOX_LABELS, { clear: clearLabel });\n // English without a locale — see `countryName`.\n const locale = useKitLocale(localeProp) ?? \"en\";\n\n // ── The list ──────────────────────────────────────────────────────────────\n // Keyed on the arrays' CONTENTS: `countries={[\"DE\", \"AT\"]}` written inline is a new\n // array every render, and resorting 249 names on every keystroke elsewhere in the\n // form is work for nothing.\n const countriesKey = countries?.join(\",\");\n const preferredKey = preferred?.join(\",\");\n const { top, rest: others } = useMemo(() => {\n const seen = new Set<string>();\n const pool: string[] = [];\n for (const raw of countriesKey !== undefined ? countriesKey.split(\",\") : COUNTRY_CODES) {\n const code = normalizeCountryCode(raw);\n if (code && !seen.has(code)) {\n seen.add(code);\n pool.push(code);\n }\n }\n const entry = (code: string): Entry => ({ code, name: countryName(code, locale) });\n const firsts: Entry[] = [];\n const picked = new Set<string>();\n for (const raw of preferredKey ? preferredKey.split(\",\") : []) {\n const code = normalizeCountryCode(raw);\n if (code && seen.has(code) && !picked.has(code)) {\n picked.add(code);\n firsts.push(entry(code));\n }\n }\n const compare = collator(locale).compare;\n const sorted = pool\n .filter((c) => !picked.has(c))\n .map(entry)\n .sort((a, b) => compare(a.name, b.name));\n return { top: firsts, rest: sorted };\n }, [countriesKey, preferredKey, locale]);\n\n // `filter: false` — the panel is handed the rows `search` ranks below instead of\n // the core's substring filter; the core still owns open, query, the highlighted row,\n // anchoring and dismissal.\n const allOptions = useMemo(() => [...top, ...others].map((e) => toOption(e, flags)), [top, others, flags]);\n const core = useComboboxCore<string>({ options: allOptions, filter: false });\n const { open, setOpen, query, triggerRef, closeToTrigger } = core;\n // The core anchors the panel to the trigger and hands focus back to it; the caller's\n // `ref` wants the same element. One callback feeds both.\n const setTrigger = useCallback(\n (el: HTMLButtonElement | null) => {\n triggerRef.current = el;\n assignRef(ref, el);\n },\n [ref, triggerRef],\n );\n\n const q = query.trim();\n const results = useMemo(() => {\n const firsts = q ? search(top, q, locale) : top;\n const below = q ? search(others, q, locale) : others;\n // The heading only where it separates something: with no preferred country in\n // view it would head the whole list and divide nothing.\n const heading = firsts.length > 0 ? text.others : undefined;\n return [...firsts.map((e) => toOption(e, flags)), ...below.map((e) => toOption(e, flags, heading))];\n }, [top, others, q, text.others, flags, locale]);\n\n // ── The lock ──────────────────────────────────────────────────────────────\n const lock = useLockReason(commit, ownReason);\n const locked = lock.locked;\n const inert = locked || Boolean(disabled);\n // A lock (or `disabled`) arriving while the list is up closes it — adjusted while\n // rendering, as the core adjusts its own query, so no frame shows a list that can\n // no longer be used.\n if (inert && open) setOpen(false);\n\n // ── The field ─────────────────────────────────────────────────────────────\n const generated = useId();\n const triggerId = id ?? generated;\n const listboxId = `${generated}-listbox`;\n // Text is a caption under the field; a FieldHint rides the label line, and is that\n // caption at Large and on touch (§4). The caller's ids, the caption, a lock's reason,\n // the error — Select's order. Through useFieldMessages since 0.34 (DUP-2, DUP-8).\n const field = useFieldMessages({\n hint,\n help,\n name: label ?? ariaLabel,\n error,\n invalid,\n describedBy: ariaDescribedBy,\n ariaInvalid,\n reasonId: locked && lock.reasonId,\n });\n\n const code = normalizeCountryCode(value);\n const selectedName = code ? countryName(code, locale) : undefined;\n // A field named by a label of the caller's (`aria-labelledby`) is a labelled field too:\n // the label already says what goes here.\n const emptyText = placeholder ?? (label === undefined && !ariaLabelledBy ? text.country : \"\");\n const fieldName = typeof label === \"string\" ? label : text.country;\n const valueId = `${generated}-value`;\n // Named by the caller's element (a form's label) — then the shown text is the second\n // reference, as the composed name below has the value second; but only when it IS a\n // value or the caller's placeholder, never the `country` fallback word.\n const labelledBy =\n ariaLabel === undefined && ariaLabelledBy\n ? mergeDescribedBy(ariaLabelledBy, (selectedName !== undefined || Boolean(placeholder)) && valueId)\n : undefined;\n const name =\n labelledBy !== undefined\n ? undefined\n : (ariaLabel ??\n (selectedName !== undefined\n ? common.fieldValue(fieldName, selectedName)\n : placeholder\n ? common.fieldValue(fieldName, placeholder)\n : fieldName));\n\n const showClear = Boolean(clearable && code && !inert);\n // `onChange` widened to take the clear's `null` — its type says so once `clearable`\n // is set, and the clear is only ever offered then.\n const emit = onChange as (code: string | null) => void;\n const clear = () => {\n if (!showClear) return;\n emit(null);\n };\n\n const choose = (o: ComboOption<string>) => {\n if (inert) return;\n emit(o.value);\n // Back to the trigger: the panel holding focus is about to unmount, and a keyboard\n // user who has answered this field should stand on it, ready to Tab on.\n closeToTrigger();\n };\n\n const trigger = (\n <button\n ref={setTrigger}\n id={triggerId}\n type=\"button\"\n // A closed choice that expands into the list below — the role every trigger\n // picker in the kit wears (see EntityCombobox), and the one that supports\n // `aria-invalid`.\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-controls={listboxId}\n aria-expanded={open}\n aria-label={name}\n aria-labelledby={labelledBy}\n aria-invalid={field.isInvalid || undefined}\n aria-required={ariaRequired}\n aria-describedby={field.describedBy}\n // The focusable kind of disabled while locked (Button's rule): native `disabled`\n // would take the field out of the tab order, and the reason with it.\n disabled={locked ? undefined : disabled}\n aria-disabled={locked || undefined}\n onClick={(e) => {\n if (inert) {\n e.preventDefault();\n return;\n }\n setOpen((o) => !o);\n }}\n // Down/Up opens the list from the closed trigger, per the APG; Enter and Space\n // already do through the button's own click.\n onKeyDown={(e) => {\n if (inert) return;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") {\n e.preventDefault();\n setOpen(true);\n } else if (showClear && !open && (e.key === \"Delete\" || e.key === \"Backspace\")) {\n // The keyboard's clear: the \"×\" is out of the tab order (see `clearable`).\n e.preventDefault();\n clear();\n }\n }}\n className={cn(\n FIELD_TRIGGER,\n // `peer` dims the floating label with a natively disabled trigger, as Select's.\n \"peer pe-9\",\n label !== undefined && FIELD_FLOATING_PAD,\n inert && \"cursor-not-allowed opacity-50\",\n field.isInvalid && FIELD_INVALID,\n )}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {flags && code && <CurrencyFlag country={code.toLowerCase()} />}\n {/* A long name (\"Saint Vincent and the Grenadines\") truncates rather than\n widening the field: the kit is checked at 390px. From Large up it wraps and\n the trigger grows instead (text size §10.17, round 0.34 T1). */}\n <span id={valueId} className={cn(\"truncate-until-large\", selectedName === undefined && \"text-placeholder\")}>\n {selectedName ?? emptyText}\n </span>\n </span>\n {showClear ? (\n /* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- tabIndex -1 inside\n the trigger <button>, so it never holds focus and a key handler here could never\n fire; the keys go to the trigger, where Delete and Backspace clear. A pointer\n shortcut, as on EntityCombobox. */\n <span\n role=\"button\"\n tabIndex={-1}\n aria-label={comboText.clear}\n onClick={(e) => {\n e.stopPropagation();\n clear();\n }}\n className=\"absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-placeholder hover:text-secondary\"\n >\n <X aria-hidden className=\"size-4\" />\n </span>\n ) : (\n <FieldChevron />\n )}\n </button>\n );\n\n const control = locked ? (\n // Button's frame: the line under the trigger, or the Tooltip with a hidden copy for\n // `aria-describedby`. `items-stretch`: in the line's column the trigger keeps the\n // field's full width.\n <LockedReason lock={lock} display={disabledReasonDisplay} className=\"flex w-full items-stretch\">\n {trigger}\n </LockedReason>\n ) : (\n trigger\n );\n\n const box =\n label !== undefined ? (\n <FloatingField htmlFor={triggerId} label={label} staticLabel hint={field.labelHint}>\n {control}\n </FloatingField>\n ) : (\n // No label line to ride: a \"?\" goes at the field's end, outside the box, as on an\n // unlabelled Select.\n <EndHintRow hint={field.labelHint}>\n <div className=\"relative\">{control}</div>\n </EndHintRow>\n );\n\n return (\n // `rest` dresses the wrapper — a `data-tour` anchor, a test id. The name, the\n // description and the invalid state are on the trigger above.\n <div {...rest} className={className}>\n {box}\n {field.below}\n <ComboboxPanel\n // The core with the rows ranked above in place of its own filter's.\n core={{ ...core, results }}\n listboxId={listboxId}\n // On a phone the list is a full-screen sheet, which needs the field's own\n // name to say what it is asking for (live #200).\n sheetTitle={label ?? text.country}\n searchPlaceholder={text.search}\n isSelected={(v) => v === code}\n onChoose={choose}\n showCreate={false}\n onCreate={() => {}}\n createContent={null}\n />\n </div>\n );\n}\n"],"mappings":"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","names":["code"]}
|
|
@@ -4,7 +4,7 @@ import type { DataTableColumn } from "./data-table.js";
|
|
|
4
4
|
/** The "Columns" rail and its drawer (`columnSettings`). */
|
|
5
5
|
export declare function ColumnSettingsPanel<T>({ columns, hiddenCols, setHiddenCols, autoHidden, keepColumn, showSettings, setShowSettings, columnsCountLabel, labels, autoSizeAll, }: {
|
|
6
6
|
/** Every column, hidden ones included; the actions column is left out here. */
|
|
7
|
-
columns: DataTableColumn<T>[];
|
|
7
|
+
columns: readonly DataTableColumn<T>[];
|
|
8
8
|
hiddenCols: Set<string>;
|
|
9
9
|
setHiddenCols: Dispatch<SetStateAction<Set<string>>>;
|
|
10
10
|
/** The columns the table's width leaves out (`fitPriority`, 0.48): unticked, and
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef } from "react";
|
|
3
|
+
import { useId, useLayoutEffect, useRef } from "react";
|
|
4
4
|
import { X } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/cn.js";
|
|
6
|
+
import { claimEscape } from "../hooks/use-dismiss.js";
|
|
7
|
+
import { firstTabStop } from "../lib/focus-left.js";
|
|
6
8
|
import { Tooltip } from "./tooltip.js";
|
|
7
9
|
import { isTableOwnColumn } from "./data-table-row-actions.js";
|
|
8
10
|
import { columnLabel } from "./data-table-shared.js";
|
|
@@ -19,15 +21,33 @@ function ColumnSettingsPanel({
|
|
|
19
21
|
autoSizeAll
|
|
20
22
|
}) {
|
|
21
23
|
const columnsRail = useRef(null);
|
|
24
|
+
const drawerRef = useRef(null);
|
|
25
|
+
const drawerId = useId();
|
|
26
|
+
const focusOnOpen = useRef(false);
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!showSettings || !focusOnOpen.current) return;
|
|
29
|
+
focusOnOpen.current = false;
|
|
30
|
+
const drawer = drawerRef.current;
|
|
31
|
+
if (!drawer) return;
|
|
32
|
+
(drawer.querySelector('input[type="checkbox"]') ?? firstTabStop(drawer, { includeSelf: false }))?.focus();
|
|
33
|
+
}, [showSettings]);
|
|
34
|
+
const close = () => {
|
|
35
|
+
setShowSettings(false);
|
|
36
|
+
columnsRail.current?.focus();
|
|
37
|
+
};
|
|
22
38
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23
39
|
/* @__PURE__ */ jsx(Tooltip, { label: columnsCountLabel, portal: true, children: /* @__PURE__ */ jsx(
|
|
24
40
|
"button",
|
|
25
41
|
{
|
|
26
42
|
ref: columnsRail,
|
|
27
43
|
type: "button",
|
|
28
|
-
onClick: () =>
|
|
44
|
+
onClick: () => {
|
|
45
|
+
focusOnOpen.current = true;
|
|
46
|
+
setShowSettings(true);
|
|
47
|
+
},
|
|
29
48
|
"aria-label": columnsCountLabel,
|
|
30
|
-
"aria-
|
|
49
|
+
"aria-expanded": showSettings,
|
|
50
|
+
"aria-controls": drawerId,
|
|
31
51
|
tabIndex: showSettings ? -1 : 0,
|
|
32
52
|
className: cn(
|
|
33
53
|
"group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out",
|
|
@@ -48,7 +68,16 @@ function ColumnSettingsPanel({
|
|
|
48
68
|
/* @__PURE__ */ jsx(
|
|
49
69
|
"div",
|
|
50
70
|
{
|
|
71
|
+
ref: drawerRef,
|
|
72
|
+
id: drawerId,
|
|
73
|
+
role: "group",
|
|
74
|
+
"aria-label": columnsCountLabel,
|
|
51
75
|
inert: !showSettings || void 0,
|
|
76
|
+
onKeyDown: (e) => {
|
|
77
|
+
if (e.key !== "Escape" || !showSettings) return;
|
|
78
|
+
claimEscape(e);
|
|
79
|
+
close();
|
|
80
|
+
},
|
|
52
81
|
className: cn(
|
|
53
82
|
"shrink-0 overflow-hidden transition-[width] duration-200 ease-out",
|
|
54
83
|
showSettings ? "w-56" : "w-0"
|
|
@@ -60,10 +89,7 @@ function ColumnSettingsPanel({
|
|
|
60
89
|
"button",
|
|
61
90
|
{
|
|
62
91
|
type: "button",
|
|
63
|
-
onClick:
|
|
64
|
-
setShowSettings(false);
|
|
65
|
-
columnsRail.current?.focus();
|
|
66
|
-
},
|
|
92
|
+
onClick: close,
|
|
67
93
|
"aria-label": labels.close,
|
|
68
94
|
className: "rounded p-0.5 text-placeholder hover:bg-hover hover:text-secondary",
|
|
69
95
|
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-column-settings.tsx"],"sourcesContent":["import { useRef } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { Tooltip } from \"./tooltip\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { isTableOwnColumn } from \"./data-table-row-actions\";\nimport { columnLabel } from \"./data-table-shared\";\nimport type { DataTableColumn } from \"./data-table\";\n\n// DataTable's column settings on a wide screen: the vertical \"Columns (n/m)\" rail and\n// the drawer it opens, with \"Auto-size\" and a show/hide box per column. Moved out of\n// data-table.tsx whole (refactor audit 2026-10, MOD-3 step 5).\n\n/** The \"Columns\" rail and its drawer (`columnSettings`). */\nexport function ColumnSettingsPanel<T>({\n columns,\n hiddenCols,\n setHiddenCols,\n autoHidden,\n keepColumn,\n showSettings,\n setShowSettings,\n columnsCountLabel,\n labels,\n autoSizeAll,\n}: {\n /** Every column, hidden ones included; the actions column is left out here. */\n columns: DataTableColumn<T>[];\n hiddenCols: Set<string>;\n setHiddenCols: Dispatch<SetStateAction<Set<string>>>;\n /** The columns the table's width leaves out (`fitPriority`, 0.48): unticked, and\n * marked \"hidden for width\". */\n autoHidden: Set<string>;\n /** A tick keeps a column in whatever the width; an untick drops that (data-table-fit.ts). */\n keepColumn: (key: string, on: boolean) => void;\n showSettings: boolean;\n setShowSettings: (open: boolean) => void;\n /** \"Columns (n/m)\", the rail's text and name. */\n columnsCountLabel: string;\n labels: DataTableLabels;\n autoSizeAll: () => void;\n}) {\n // The \"Columns\" rail button — where the drawer's X hands focus back (BUG-7a).\n const columnsRail = useRef<HTMLButtonElement | null>(null);\n return (\n <>\n <Tooltip label={columnsCountLabel} portal>\n <button\n ref={columnsRail}\n type=\"button\"\n onClick={() => setShowSettings(true)}\n aria-label={columnsCountLabel}\n aria-hidden={showSettings}\n tabIndex={showSettings ? -1 : 0}\n className={cn(\n \"group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out\",\n \"border-s border-subtle\",\n \"hover:bg-hover\",\n showSettings ? \"w-0 border-s-0\" : \"w-8 cursor-pointer\",\n )}\n >\n <span\n className=\"whitespace-nowrap text-xs font-medium tracking-wide text-muted group-hover:text-secondary\"\n style={{ writingMode: \"vertical-rl\", transform: \"rotate(180deg)\" }}\n >\n {columnsCountLabel}\n </span>\n </button>\n </Tooltip>\n <div\n // Closed, the drawer is only `w-0`: without `inert` its X, Auto-size and one\n // checkbox per column stayed tab stops and in the accessibility tree, and Tab\n // after the last cell walked into controls nobody could see (BUG-7a).\n inert={!showSettings || undefined}\n className={cn(\n \"shrink-0 overflow-hidden transition-[width] duration-200 ease-out\",\n showSettings ? \"w-56\" : \"w-0\",\n )}\n >\n <div className=\"flex w-56 flex-col border-s border-subtle p-2\">\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted\">{columnsCountLabel}</div>\n <Tooltip label={labels.close} portal>\n <button\n type=\"button\"\n onClick={() => {\n setShowSettings(false);\n // Back to the rail that opened it: the X is about to go inert, and\n // focus would fall to <body> with it (BUG-7a).\n columnsRail.current?.focus();\n }}\n aria-label={labels.close}\n className=\"rounded p-0.5 text-placeholder hover:bg-hover hover:text-secondary\"\n >\n <X className=\"size-4\" />\n </button>\n </Tooltip>\n </div>\n <button\n type=\"button\"\n onClick={autoSizeAll}\n className=\"mb-2 rounded border border-subtle px-2 py-1 text-xs font-medium text-secondary hover:bg-hover\"\n >\n {labels.autoSize}\n </button>\n <div className=\"flex flex-col gap-0.5\">\n {columns.map((col) => {\n if (isTableOwnColumn(col.key)) return null;\n // Ticked = drawn: a column left out for width is unticked, so the boxes\n // and \"Columns (8/10)\" say the same (keksdose live #451).\n const forWidth = !hiddenCols.has(col.key) && autoHidden.has(col.key);\n const checked = !hiddenCols.has(col.key) && !forWidth;\n const headerLabel = columnLabel(col);\n return (\n <label\n key={col.key}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-hover\"\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 shrink-0\"\n checked={checked}\n onChange={(e) => {\n keepColumn(col.key, e.target.checked);\n setHiddenCols((s) => {\n const next = new Set(s);\n if (e.target.checked) next.delete(col.key);\n else next.add(col.key);\n return next;\n });\n }}\n />\n {/* The panel is `w-56`: a long column name wraps from Large up rather\n than reading \"Accoun…\" beside its box (text size §10.17, round\n 0.34 T1). */}\n {forWidth ? (\n // Under the name, as part of the box's name: \"Email, hidden for width\".\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate-until-large\">{headerLabel}</span>\n <span className=\"text-micro text-muted\" data-hidden-for-width=\"\">\n {labels.hiddenForWidth}\n </span>\n </span>\n ) : (\n <span className=\"min-w-0 truncate-until-large\">{headerLabel}</span>\n )}\n </label>\n );\n })}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-column-settings.tsx"],"sourcesContent":["import { useId, useLayoutEffect, useRef } from \"react\";\nimport type { Dispatch, SetStateAction } from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { claimEscape } from \"../hooks/use-dismiss\";\nimport { firstTabStop } from \"../lib/focus-left\";\nimport { Tooltip } from \"./tooltip\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { isTableOwnColumn } from \"./data-table-row-actions\";\nimport { columnLabel } from \"./data-table-shared\";\nimport type { DataTableColumn } from \"./data-table\";\n\n// DataTable's column settings on a wide screen: the vertical \"Columns (n/m)\" rail and\n// the drawer it opens, with \"Auto-size\" and a show/hide box per column. Moved out of\n// data-table.tsx whole (refactor audit 2026-10, MOD-3 step 5).\n\n/** The \"Columns\" rail and its drawer (`columnSettings`). */\nexport function ColumnSettingsPanel<T>({\n columns,\n hiddenCols,\n setHiddenCols,\n autoHidden,\n keepColumn,\n showSettings,\n setShowSettings,\n columnsCountLabel,\n labels,\n autoSizeAll,\n}: {\n /** Every column, hidden ones included; the actions column is left out here. */\n columns: readonly DataTableColumn<T>[];\n hiddenCols: Set<string>;\n setHiddenCols: Dispatch<SetStateAction<Set<string>>>;\n /** The columns the table's width leaves out (`fitPriority`, 0.48): unticked, and\n * marked \"hidden for width\". */\n autoHidden: Set<string>;\n /** A tick keeps a column in whatever the width; an untick drops that (data-table-fit.ts). */\n keepColumn: (key: string, on: boolean) => void;\n showSettings: boolean;\n setShowSettings: (open: boolean) => void;\n /** \"Columns (n/m)\", the rail's text and name. */\n columnsCountLabel: string;\n labels: DataTableLabels;\n autoSizeAll: () => void;\n}) {\n // The \"Columns\" rail button — where the drawer's X hands focus back (BUG-7a).\n const columnsRail = useRef<HTMLButtonElement | null>(null);\n const drawerRef = useRef<HTMLDivElement | null>(null);\n const drawerId = useId();\n // The opening half (refactor audit 2026-10-30, DSP-13). Opened from the rail, the focus\n // goes into the drawer: the rail it was on turns zero wide and leaves the tab order in\n // the same render, and the focus sat in a control nobody could see. To the first column\n // box — what the drawer is opened for — or, with none, its first control. Only when the\n // rail opened it: a drawer that starts open (its state is the table's) takes nothing.\n const focusOnOpen = useRef(false);\n useLayoutEffect(() => {\n if (!showSettings || !focusOnOpen.current) return;\n focusOnOpen.current = false;\n const drawer = drawerRef.current;\n if (!drawer) return;\n (\n drawer.querySelector<HTMLElement>('input[type=\"checkbox\"]') ?? firstTabStop(drawer, { includeSelf: false })\n )?.focus();\n }, [showSettings]);\n const close = () => {\n setShowSettings(false);\n // Back to the rail that opened it: the drawer is about to go inert, and focus would\n // fall to <body> with it (BUG-7a).\n columnsRail.current?.focus();\n };\n return (\n <>\n <Tooltip label={columnsCountLabel} portal>\n <button\n ref={columnsRail}\n type=\"button\"\n onClick={() => {\n focusOnOpen.current = true;\n setShowSettings(true);\n }}\n aria-label={columnsCountLabel}\n // A disclosure: it says whether the drawer is open, and never hides itself from\n // a screen reader — `aria-hidden` on it while it held the focus left the reader\n // with nothing to say (DSP-13). Zero wide and out of the tab order while open.\n aria-expanded={showSettings}\n aria-controls={drawerId}\n tabIndex={showSettings ? -1 : 0}\n className={cn(\n \"group flex shrink-0 items-start justify-center overflow-hidden pt-3 transition-[width] duration-200 ease-out\",\n \"border-s border-subtle\",\n \"hover:bg-hover\",\n showSettings ? \"w-0 border-s-0\" : \"w-8 cursor-pointer\",\n )}\n >\n <span\n className=\"whitespace-nowrap text-xs font-medium tracking-wide text-muted group-hover:text-secondary\"\n style={{ writingMode: \"vertical-rl\", transform: \"rotate(180deg)\" }}\n >\n {columnsCountLabel}\n </span>\n </button>\n </Tooltip>\n {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- Escape\n for whatever control in the drawer holds the focus; the group itself is no control. */}\n <div\n ref={drawerRef}\n id={drawerId}\n // Named by its own heading's words, so a reader entering it hears \"Columns (4/9),\n // group\" before the first box (DSP-13).\n role=\"group\"\n aria-label={columnsCountLabel}\n // Closed, the drawer is only `w-0`: without `inert` its X, Auto-size and one\n // checkbox per column stayed tab stops and in the accessibility tree, and Tab\n // after the last cell walked into controls nobody could see (BUG-7a).\n inert={!showSettings || undefined}\n // Escape closes it onto the rail, as its X does. Claimed, not handed to the Escape\n // stack: inside a dialog the drawer is what the key closes, not the dialog.\n onKeyDown={(e) => {\n if (e.key !== \"Escape\" || !showSettings) return;\n claimEscape(e);\n close();\n }}\n className={cn(\n \"shrink-0 overflow-hidden transition-[width] duration-200 ease-out\",\n showSettings ? \"w-56\" : \"w-0\",\n )}\n >\n <div className=\"flex w-56 flex-col border-s border-subtle p-2\">\n <div className=\"mb-2 flex items-center justify-between gap-1\">\n <div className=\"text-xs font-medium uppercase tracking-wide text-muted\">{columnsCountLabel}</div>\n <Tooltip label={labels.close} portal>\n <button\n type=\"button\"\n onClick={close}\n aria-label={labels.close}\n className=\"rounded p-0.5 text-placeholder hover:bg-hover hover:text-secondary\"\n >\n <X className=\"size-4\" />\n </button>\n </Tooltip>\n </div>\n <button\n type=\"button\"\n onClick={autoSizeAll}\n className=\"mb-2 rounded border border-subtle px-2 py-1 text-xs font-medium text-secondary hover:bg-hover\"\n >\n {labels.autoSize}\n </button>\n <div className=\"flex flex-col gap-0.5\">\n {columns.map((col) => {\n if (isTableOwnColumn(col.key)) return null;\n // Ticked = drawn: a column left out for width is unticked, so the boxes\n // and \"Columns (8/10)\" say the same (keksdose live #451).\n const forWidth = !hiddenCols.has(col.key) && autoHidden.has(col.key);\n const checked = !hiddenCols.has(col.key) && !forWidth;\n const headerLabel = columnLabel(col);\n return (\n <label\n key={col.key}\n className=\"inline-flex cursor-pointer items-center gap-1.5 rounded px-1 py-0.5 text-xs hover:bg-hover\"\n >\n <input\n type=\"checkbox\"\n className=\"size-3.5 shrink-0\"\n checked={checked}\n onChange={(e) => {\n keepColumn(col.key, e.target.checked);\n setHiddenCols((s) => {\n const next = new Set(s);\n if (e.target.checked) next.delete(col.key);\n else next.add(col.key);\n return next;\n });\n }}\n />\n {/* The panel is `w-56`: a long column name wraps from Large up rather\n than reading \"Accoun…\" beside its box (text size §10.17, round\n 0.34 T1). */}\n {forWidth ? (\n // Under the name, as part of the box's name: \"Email, hidden for width\".\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate-until-large\">{headerLabel}</span>\n <span className=\"text-micro text-muted\" data-hidden-for-width=\"\">\n {labels.hiddenForWidth}\n </span>\n </span>\n ) : (\n <span className=\"min-w-0 truncate-until-large\">{headerLabel}</span>\n )}\n </label>\n );\n })}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -45,8 +45,10 @@ export declare function isFilterActive(v: FilterValue | undefined): boolean;
|
|
|
45
45
|
* `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the
|
|
46
46
|
* root rules, under which a Turkish user typing "ırmak" does not find "IRMAK";
|
|
47
47
|
* `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.
|
|
48
|
-
* The text filter folds as every kit list does (
|
|
49
|
-
*
|
|
48
|
+
* The text filter folds as every kit list does (`foldForSearch`, DUP-5): accents off
|
|
49
|
+
* too, so "zurich" finds Zürich. And it matches as the pickers do since 0.51.4 (refactor
|
|
50
|
+
* audit 2026-10-30, DUP-4): every word of the query, anywhere in the value, in any order —
|
|
51
|
+
* "zurich migros" finds "Migros · 8004 Zürich". One word matches as before.
|
|
50
52
|
*/
|
|
51
53
|
export declare function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue, locale?: string): boolean;
|
|
52
54
|
/** The empty filter state for a column's filter type. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { toLocalIso } from "../lib/dates.js";
|
|
3
|
-
import {
|
|
3
|
+
import { matchesSearch, searchWords } from "../lib/text-match.js";
|
|
4
4
|
function resolveFilter(col) {
|
|
5
5
|
if (col.filter) return col.filter;
|
|
6
6
|
if (col.filterBy) return { type: "text", getValue: col.filterBy };
|
|
@@ -35,7 +35,7 @@ function rowMatches(col, row, state, locale) {
|
|
|
35
35
|
switch (filter.type) {
|
|
36
36
|
case "text": {
|
|
37
37
|
if (state.type !== "text" || !state.q.trim()) return true;
|
|
38
|
-
return
|
|
38
|
+
return matchesSearch(searchWords(state.q, locale), [filter.getValue(row)], locale);
|
|
39
39
|
}
|
|
40
40
|
case "select": {
|
|
41
41
|
if (state.type !== "select" || state.values.length === 0) return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\nimport { toLocalIso } from \"../lib/dates\";\nimport { foldForSearch } from \"../lib/text-match\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return numberBound(v.min) !== null || numberBound(v.max) !== null || v.abs;\n }\n}\n\n/**\n * A number filter's bound as a number, or `null` for none: blank, or not a finite\n * number (BUG-10, refactor audit 2026-10). `Number(\"abc\")` is NaN, and every comparison\n * with NaN is false, so a broken bound — `f.amount=abc..` in a shared link — used to\n * match every row while the column showed its filter as active; `Number(\" \")` is 0, and\n * a blank bound kept every negative row out. The popover hands in the dot-decimal text\n * its locale's marks read (`readTyped`); anything else is no bound.\n */\nfunction numberBound(raw: string): number | null {\n if (raw.trim() === \"\") return null;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * The calendar day a date filter compares, as \"YYYY-MM-DD\" — the LOCAL day, the one\n * the cell, the DatePicker and the presets show (round 0.34 BUG-2).\n *\n * A bare \"YYYY-MM-DD\" is a calendar day already and is kept as it is. Anything with a\n * time part is an instant: its first ten characters were the UTC day for a server\n * timestamp (\"…Z\", \"+00:00\"), so in Berlin a report filed at 00:30 showed as the 12th\n * and was filtered as the 11th. It is read as the instant it names and put on the\n * local calendar. A string `Date` cannot read keeps the old first-ten-characters\n * answer rather than matching nothing.\n */\nfunction localDay(raw: string): string {\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(raw)) return raw;\n const d = new Date(raw);\n return Number.isNaN(d.getTime()) ? raw.slice(0, 10) : toLocalIso(d);\n}\n\n/**\n * Whether `row` passes column `col`'s filter given the user's `state`.\n *\n * `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the\n * root rules, under which a Turkish user typing \"ırmak\" does not find \"IRMAK\";\n * `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.\n * The text filter folds as every kit list does ({@link foldForSearch}, DUP-5): accents\n * off too, so \"zurich\" finds Zürich.\n */\nexport function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue, locale?: string): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return foldForSearch(filter.getValue(row), locale).includes(foldForSearch(state.q.trim(), locale));\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = localDay(raw);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n const min = numberBound(state.min);\n const max = numberBound(state.max);\n if (raw == null || Number.isNaN(raw)) return min === null && max === null;\n const x = state.abs ? Math.abs(raw) : raw;\n if (min !== null && x < min) return false;\n if (max !== null && x > max) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n // A bound that is not a finite number is dropped here, so a broken link leaves the\n // filter off rather than active and matching everything (BUG-10).\n const kept = (s: string) => (numberBound(s) === null ? \"\" : s);\n return { type: \"number\", min: kept(min), max: kept(max), abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n\n// ---------- Filter builders ----------\n//\n// Terse builders for `DataTableColumn.filter`, lifted from kastlan's column-filters.ts,\n// which every converted list page imports. The one that earns its keep is `selectFilter`:\n// in `serverPagination` mode a select filter MUST declare its options (the table would\n// otherwise derive them from the rows of the page on screen), and the options an app has\n// to hand are usually a translated `{ value: label }` map.\n\n/** A free-text filter over `getValue`. */\nexport function textFilter<T>(getValue: (row: T) => string): ColumnFilter<T> {\n return { type: \"text\", getValue };\n}\n\n/**\n * A multi-select filter. `options` is an options array, or a `{ value: label }` record\n * (a translated status map) — kept in its key order. Left out, the table derives the\n * options from the rows it holds.\n */\nexport function selectFilter<T>(\n getValue: (row: T) => string,\n options?: { value: string; label?: string }[] | Record<string, string>,\n): ColumnFilter<T> {\n const opts = Array.isArray(options)\n ? options\n : options\n ? Object.entries(options).map(([value, label]) => ({ value, label }))\n : undefined;\n return { type: \"select\", getValue, options: opts };\n}\n\n/** A from/to date filter over an ISO date (or date-time) string. */\nexport function dateFilter<T>(getValue: (row: T) => string | null | undefined): ColumnFilter<T> {\n return { type: \"date\", getValue };\n}\n\n/** A min/max (and absolute-value) filter over a number. */\nexport function numberFilter<T>(getValue: (row: T) => number | null | undefined): ColumnFilter<T> {\n return { type: \"number\", getValue };\n}\n\n/**\n * A link to a list page with one column filter already applied — in the table's own URL\n * scheme (`f.<column>=<encoded>`), which a table with `urlSync` (or\n * {@link useTableUrlState}) reads on mount. kastlan's `buildListUrl`, which had to\n * restate the `f.` prefix to do it.\n *\n * `value` is a string for a text filter, an array for a select filter, or any\n * {@link FilterValue}. A filter that narrows nothing returns `path` unchanged. A `path`\n * that already carries a query keeps it; a `#fragment` stays at the end.\n *\n * filterHref(\"/units\", \"status\", [\"vacant\"]) // \"/units?f.status=vacant\"\n */\nexport function filterHref(\n path: string,\n column: string,\n value: string | string[] | FilterValue,\n /** The target table's `urlSync` prefix, if it has one (`{ prefix: \"inv.\" }` →\n * `inv.f.<column>`). */\n options?: { prefix?: string },\n): string {\n const state: FilterValue =\n typeof value === \"string\"\n ? { type: \"text\", q: value }\n : Array.isArray(value)\n ? { type: \"select\", values: value }\n : value;\n const encoded = encodeFilterValue(state);\n if (encoded == null) return path;\n const hashAt = path.indexOf(\"#\");\n const base = hashAt >= 0 ? path.slice(0, hashAt) : path;\n const hash = hashAt >= 0 ? path.slice(hashAt) : \"\";\n const queryAt = base.indexOf(\"?\");\n const params = new URLSearchParams(queryAt >= 0 ? base.slice(queryAt + 1) : \"\");\n params.set(`${options?.prefix ?? \"\"}f.${column}`, encoded);\n return `${queryAt >= 0 ? base.slice(0, queryAt) : base}?${params.toString()}${hash}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-filters.ts"],"sourcesContent":["import type { DataTableColumn } from \"./data-table\";\nimport { toLocalIso } from \"../lib/dates\";\nimport { matchesSearch, searchWords } from \"../lib/text-match\";\n\n/** How a column is filtered. `text`/`select`/`date`/`number` each read a value\n * off the row and compare it against the user's {@link FilterValue}. */\nexport type ColumnFilter<T> =\n | { type: \"text\"; getValue: (row: T) => string }\n | { type: \"select\"; getValue: (row: T) => string; options?: { value: string; label?: string }[] }\n | { type: \"date\"; getValue: (row: T) => string | null | undefined }\n | { type: \"number\"; getValue: (row: T) => number | null | undefined };\n\n/** The current state of a column's filter (what the user has entered). */\nexport type FilterValue =\n | { type: \"text\"; q: string }\n | { type: \"select\"; values: string[] }\n | { type: \"date\"; from: string; to: string }\n | { type: \"number\"; min: string; max: string; abs: boolean };\n\n/** A column's filter config, falling back to a plain text filter over `filterBy`. */\nexport function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null {\n if (col.filter) return col.filter;\n if (col.filterBy) return { type: \"text\", getValue: col.filterBy };\n return null;\n}\n\n/** Whether a filter value is actually narrowing anything (vs its empty default). */\nexport function isFilterActive(v: FilterValue | undefined): boolean {\n if (!v) return false;\n switch (v.type) {\n case \"text\":\n return v.q.trim().length > 0;\n case \"select\":\n return v.values.length > 0;\n case \"date\":\n return !!v.from || !!v.to;\n case \"number\":\n return numberBound(v.min) !== null || numberBound(v.max) !== null || v.abs;\n }\n}\n\n/**\n * A number filter's bound as a number, or `null` for none: blank, or not a finite\n * number (BUG-10, refactor audit 2026-10). `Number(\"abc\")` is NaN, and every comparison\n * with NaN is false, so a broken bound — `f.amount=abc..` in a shared link — used to\n * match every row while the column showed its filter as active; `Number(\" \")` is 0, and\n * a blank bound kept every negative row out. The popover hands in the dot-decimal text\n * its locale's marks read (`readTyped`); anything else is no bound.\n */\nfunction numberBound(raw: string): number | null {\n if (raw.trim() === \"\") return null;\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n}\n\n/**\n * The calendar day a date filter compares, as \"YYYY-MM-DD\" — the LOCAL day, the one\n * the cell, the DatePicker and the presets show (round 0.34 BUG-2).\n *\n * A bare \"YYYY-MM-DD\" is a calendar day already and is kept as it is. Anything with a\n * time part is an instant: its first ten characters were the UTC day for a server\n * timestamp (\"…Z\", \"+00:00\"), so in Berlin a report filed at 00:30 showed as the 12th\n * and was filtered as the 11th. It is read as the instant it names and put on the\n * local calendar. A string `Date` cannot read keeps the old first-ten-characters\n * answer rather than matching nothing.\n */\nfunction localDay(raw: string): string {\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(raw)) return raw;\n const d = new Date(raw);\n return Number.isNaN(d.getTime()) ? raw.slice(0, 10) : toLocalIso(d);\n}\n\n/**\n * Whether `row` passes column `col`'s filter given the user's `state`.\n *\n * `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the\n * root rules, under which a Turkish user typing \"ırmak\" does not find \"IRMAK\";\n * `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.\n * The text filter folds as every kit list does (`foldForSearch`, DUP-5): accents off\n * too, so \"zurich\" finds Zürich. And it matches as the pickers do since 0.51.4 (refactor\n * audit 2026-10-30, DUP-4): every word of the query, anywhere in the value, in any order —\n * \"zurich migros\" finds \"Migros · 8004 Zürich\". One word matches as before.\n */\nexport function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue, locale?: string): boolean {\n const filter = resolveFilter(col);\n if (!filter) return true;\n switch (filter.type) {\n case \"text\": {\n if (state.type !== \"text\" || !state.q.trim()) return true;\n return matchesSearch(searchWords(state.q, locale), [filter.getValue(row)], locale);\n }\n case \"select\": {\n if (state.type !== \"select\" || state.values.length === 0) return true;\n return state.values.includes(filter.getValue(row));\n }\n case \"date\": {\n if (state.type !== \"date\") return true;\n const raw = filter.getValue(row);\n if (!raw) return !state.from && !state.to;\n const v = localDay(raw);\n if (state.from && v < state.from) return false;\n if (state.to && v > state.to) return false;\n return true;\n }\n case \"number\": {\n if (state.type !== \"number\") return true;\n const raw = filter.getValue(row);\n const min = numberBound(state.min);\n const max = numberBound(state.max);\n if (raw == null || Number.isNaN(raw)) return min === null && max === null;\n const x = state.abs ? Math.abs(raw) : raw;\n if (min !== null && x < min) return false;\n if (max !== null && x > max) return false;\n return true;\n }\n }\n}\n\n/** The empty filter state for a column's filter type. */\nexport function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue {\n switch (filter.type) {\n case \"text\":\n return { type: \"text\", q: \"\" };\n case \"select\":\n return { type: \"select\", values: [] };\n case \"date\":\n return { type: \"date\", from: \"\", to: \"\" };\n case \"number\":\n return { type: \"number\", min: \"\", max: \"\", abs: false };\n }\n}\n\n/**\n * Escape the ONE character the select codec uses as its separator, plus the escape\n * marker itself so the transform is reversible.\n *\n * A minimal escape rather than `encodeURIComponent`, deliberately: these strings sit\n * inside a query-parameter value that `URLSearchParams` already encodes, so a full\n * percent-encode would double-encode every space into `%2520` and make shared links\n * unreadable for no gain. Only `,` can be misread here.\n *\n * Backwards compatible with every link written before this existed: a value with\n * neither `,` nor `%` passes through both directions untouched, and `%` on its own\n * never matched `%25` either.\n */\nfunction escapeSelectValue(v: string): string {\n return v.replace(/%/g, \"%25\").replace(/,/g, \"%2C\");\n}\n\n/** Inverse of {@link escapeSelectValue}. The order matters and is the reverse of the\n * escape: undoing `%2C` first cannot see the `2C` inside an escaped `%252C`. */\nfunction unescapeSelectValue(v: string): string {\n return v.replace(/%2C/g, \",\").replace(/%25/g, \"%\");\n}\n\n/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */\nexport function encodeFilterValue(v: FilterValue): string | null {\n switch (v.type) {\n case \"text\":\n return v.q.trim() ? v.q : null;\n case \"select\":\n // Escaped before joining: an option value may legitimately contain the\n // separator (a free-text title, a payee or category name), and without this it\n // came back as two values and the filter silently matched nothing.\n return v.values.length ? v.values.map(escapeSelectValue).join(\",\") : null;\n case \"date\":\n return v.from || v.to ? `${v.from}..${v.to}` : null;\n case \"number\": {\n const hasRange = v.min !== \"\" || v.max !== \"\";\n if (!hasRange && !v.abs) return null;\n return `${v.min}..${v.max}${v.abs ? \"!\" : \"\"}`;\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue} given just the filter type — for\n * callers that know a column's type without holding its full config. */\nexport function decodeFilterValueOfType(type: FilterValue[\"type\"], raw: string): FilterValue {\n switch (type) {\n case \"text\":\n return { type: \"text\", q: raw };\n case \"select\":\n return {\n type: \"select\",\n values: raw\n .split(\",\")\n .filter((v) => v.length > 0)\n .map(unescapeSelectValue),\n };\n case \"date\": {\n const [from = \"\", to = \"\"] = raw.split(\"..\");\n return { type: \"date\", from, to };\n }\n case \"number\": {\n let body = raw;\n const abs = body.endsWith(\"!\");\n if (abs) body = body.slice(0, -1);\n const [min = \"\", max = \"\"] = body.split(\"..\");\n // A bound that is not a finite number is dropped here, so a broken link leaves the\n // filter off rather than active and matching everything (BUG-10).\n const kept = (s: string) => (numberBound(s) === null ? \"\" : s);\n return { type: \"number\", min: kept(min), max: kept(max), abs };\n }\n }\n}\n\n/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */\nexport function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue {\n return decodeFilterValueOfType(filter.type, raw);\n}\n\n// ---------- Filter builders ----------\n//\n// Terse builders for `DataTableColumn.filter`, lifted from kastlan's column-filters.ts,\n// which every converted list page imports. The one that earns its keep is `selectFilter`:\n// in `serverPagination` mode a select filter MUST declare its options (the table would\n// otherwise derive them from the rows of the page on screen), and the options an app has\n// to hand are usually a translated `{ value: label }` map.\n\n/** A free-text filter over `getValue`. */\nexport function textFilter<T>(getValue: (row: T) => string): ColumnFilter<T> {\n return { type: \"text\", getValue };\n}\n\n/**\n * A multi-select filter. `options` is an options array, or a `{ value: label }` record\n * (a translated status map) — kept in its key order. Left out, the table derives the\n * options from the rows it holds.\n */\nexport function selectFilter<T>(\n getValue: (row: T) => string,\n options?: { value: string; label?: string }[] | Record<string, string>,\n): ColumnFilter<T> {\n const opts = Array.isArray(options)\n ? options\n : options\n ? Object.entries(options).map(([value, label]) => ({ value, label }))\n : undefined;\n return { type: \"select\", getValue, options: opts };\n}\n\n/** A from/to date filter over an ISO date (or date-time) string. */\nexport function dateFilter<T>(getValue: (row: T) => string | null | undefined): ColumnFilter<T> {\n return { type: \"date\", getValue };\n}\n\n/** A min/max (and absolute-value) filter over a number. */\nexport function numberFilter<T>(getValue: (row: T) => number | null | undefined): ColumnFilter<T> {\n return { type: \"number\", getValue };\n}\n\n/**\n * A link to a list page with one column filter already applied — in the table's own URL\n * scheme (`f.<column>=<encoded>`), which a table with `urlSync` (or\n * {@link useTableUrlState}) reads on mount. kastlan's `buildListUrl`, which had to\n * restate the `f.` prefix to do it.\n *\n * `value` is a string for a text filter, an array for a select filter, or any\n * {@link FilterValue}. A filter that narrows nothing returns `path` unchanged. A `path`\n * that already carries a query keeps it; a `#fragment` stays at the end.\n *\n * filterHref(\"/units\", \"status\", [\"vacant\"]) // \"/units?f.status=vacant\"\n */\nexport function filterHref(\n path: string,\n column: string,\n value: string | string[] | FilterValue,\n /** The target table's `urlSync` prefix, if it has one (`{ prefix: \"inv.\" }` →\n * `inv.f.<column>`). */\n options?: { prefix?: string },\n): string {\n const state: FilterValue =\n typeof value === \"string\"\n ? { type: \"text\", q: value }\n : Array.isArray(value)\n ? { type: \"select\", values: value }\n : value;\n const encoded = encodeFilterValue(state);\n if (encoded == null) return path;\n const hashAt = path.indexOf(\"#\");\n const base = hashAt >= 0 ? path.slice(0, hashAt) : path;\n const hash = hashAt >= 0 ? path.slice(hashAt) : \"\";\n const queryAt = base.indexOf(\"?\");\n const params = new URLSearchParams(queryAt >= 0 ? base.slice(queryAt + 1) : \"\");\n params.set(`${options?.prefix ?? \"\"}f.${column}`, encoded);\n return `${queryAt >= 0 ? base.slice(0, queryAt) : base}?${params.toString()}${hash}`;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -18,7 +18,7 @@ export declare const GROW_CELL_CLASS = "w-full max-w-0 truncate-until-large";
|
|
|
18
18
|
* `not-large:whitespace-normal` in its `className`. */
|
|
19
19
|
export declare const GROW_PEER_CELL_CLASS = "not-large:whitespace-nowrap";
|
|
20
20
|
/** The auto-hide order: lowest `fitPriority` first, the later column first on a tie. */
|
|
21
|
-
export declare function fitHideOrder<T>(columns: DataTableColumn<T>[]): DataTableColumn<T>[];
|
|
21
|
+
export declare function fitHideOrder<T>(columns: readonly DataTableColumn<T>[]): DataTableColumn<T>[];
|
|
22
22
|
/**
|
|
23
23
|
* Which columns the table leaves out for width, and the Columns panel's "keep it".
|
|
24
24
|
*
|
|
@@ -28,7 +28,7 @@ export declare function fitHideOrder<T>(columns: DataTableColumn<T>[]): DataTabl
|
|
|
28
28
|
* drops the keep. Keeps are not persisted: on the next visit the table fits again.
|
|
29
29
|
*/
|
|
30
30
|
export declare function useColumnFit<T>({ columns, hiddenCols, headRefs, scroller, enabled, content, }: {
|
|
31
|
-
columns: DataTableColumn<T>[];
|
|
31
|
+
columns: readonly DataTableColumn<T>[];
|
|
32
32
|
/** The user's hidden columns: never candidates (they are out already). */
|
|
33
33
|
hiddenCols: Set<string>;
|
|
34
34
|
headRefs: Map<string, HTMLTableCellElement>;
|
|
@@ -39,8 +39,10 @@ export declare function useColumnFit<T>({ columns, hiddenCols, headRefs, scrolle
|
|
|
39
39
|
/**
|
|
40
40
|
* What can change the columns' widths besides the scroller (0.48.2): the rows on
|
|
41
41
|
* screen, the columns, the stored widths, the text size. Compared item by item after
|
|
42
|
-
* each render, and the table is measured only when one changed — a
|
|
43
|
-
* longer forces a layout of the whole table. Without it, every render
|
|
42
|
+
* each render (`Object.is`), and the table is measured only when one changed — a
|
|
43
|
+
* selection tick no longer forces a layout of the whole table. Without it, every render
|
|
44
|
+
* measures. So each item must keep its identity while it means the same: the page's
|
|
45
|
+
* rows memoised, the columns as their keys (DSP-19).
|
|
44
46
|
*/
|
|
45
47
|
content?: readonly unknown[];
|
|
46
48
|
}): {
|
|
@@ -102,9 +102,12 @@ function useColumnFit({
|
|
|
102
102
|
if (table) ro.observe(table);
|
|
103
103
|
}, []);
|
|
104
104
|
const lastContent = useRef(null);
|
|
105
|
+
const orderSig = order.map((c) => c.key).join("\0");
|
|
106
|
+
const currentSig = current.join("\0");
|
|
105
107
|
useLayoutEffect(() => {
|
|
106
108
|
watchTable();
|
|
107
|
-
const
|
|
109
|
+
const headText = active ? scroller()?.querySelector("thead")?.textContent ?? "" : "";
|
|
110
|
+
const now = content ? [...content, orderSig, currentSig, growKey, growMin, active, headText] : null;
|
|
108
111
|
const prev = lastContent.current;
|
|
109
112
|
lastContent.current = now;
|
|
110
113
|
if (now && prev && prev.length === now.length && prev.every((v, i) => Object.is(v, now[i]))) return;
|