@eifi1/ui-kit 0.51.6 → 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 +146 -26
- 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 +3 -7
- 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.d.ts +3 -2
- package/dist/auth/complete-name-dialog.js +3 -5
- 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-request.d.ts +11 -2
- package/dist/billing/cancel-request.js +15 -3
- package/dist/billing/cancel-request.js.map +1 -1
- package/dist/billing/cancel-subscription.d.ts +22 -10
- package/dist/billing/cancel-subscription.js +8 -11
- 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 +21 -9
- package/dist/billing/plan-switch.js +10 -8
- 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 +22 -4
- package/dist/components/amount-input.js +3 -3
- 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/calculator.d.ts +21 -2
- package/dist/components/calculator.js +53 -14
- package/dist/components/calculator.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 +119 -7
- package/dist/components/combobox-core.js +151 -46
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js +142 -131
- 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 +36 -7
- package/dist/components/data-table-mobile.js +63 -31
- 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 +25 -8
- package/dist/components/data-table.js +154 -25
- 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/dialog-frame.d.ts +2 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/entity-combobox.js +4 -2
- package/dist/components/entity-combobox.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 +150 -141
- 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 +4 -3
- 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.d.ts +24 -0
- package/dist/components/modal.js +12 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/components/money-draft.js +7 -5
- package/dist/components/money-draft.js.map +1 -1
- package/dist/components/money-field.d.ts +2 -2
- package/dist/components/money-field.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-entity-combobox.js +2 -1
- package/dist/components/multi-entity-combobox.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/number-field.d.ts +6 -4
- package/dist/components/number-field.js +3 -2
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +4 -1
- package/dist/components/number-input.js +3 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.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/reauth-dialog.d.ts +4 -5
- package/dist/components/reauth-dialog.js +1 -4
- package/dist/components/reauth-dialog.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 +10 -3
- package/dist/components/series-chart-axes.js +1 -0
- 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 +13 -11
- 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-dismiss.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.d.ts +7 -1
- package/dist/hooks/use-overlay-history.js +35 -5
- 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/calc.d.ts +7 -1
- package/dist/lib/calc.js +3 -1
- package/dist/lib/calc.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +2 -2
- package/dist/lib/clipboard-minus.js +1 -1
- package/dist/lib/clipboard-minus.js.map +1 -1
- 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/decimal-marks.d.ts +7 -1
- package/dist/lib/decimal-marks.js +5 -2
- package/dist/lib/decimal-marks.js.map +1 -1
- 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.d.ts +5 -3
- package/dist/lib/format.js +16 -13
- 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 +38 -0
- package/dist/lib/text-match.js +24 -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 +16 -4
- package/dist/tour/tour.js +62 -778
- 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/tour/use-tour-url-sync.js +2 -2
- package/dist/tour/use-tour-url-sync.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 +12 -8
- 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 +5 -10
- 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 +9 -8
- 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-request.ts +28 -6
- package/src/billing/cancel-subscription.tsx +29 -22
- 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 +31 -20
- 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 +32 -14
- 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/calculator.tsx +98 -17
- 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 +303 -69
- package/src/components/combobox.tsx +184 -155
- 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 +110 -27
- 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 +267 -41
- 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/dialog-frame.tsx +2 -1
- package/src/components/entity-combobox.tsx +4 -1
- 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 +198 -152
- 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 +14 -5
- package/src/components/measured-grid.tsx +8 -4
- package/src/components/mini-calendar.tsx +6 -5
- package/src/components/modal.tsx +45 -4
- package/src/components/money-draft.ts +16 -5
- package/src/components/money-field.tsx +2 -2
- package/src/components/month-grid.tsx +229 -0
- package/src/components/month-picker.tsx +192 -200
- package/src/components/multi-entity-combobox.tsx +1 -0
- package/src/components/multi-select.tsx +21 -9
- package/src/components/number-field.tsx +14 -7
- package/src/components/number-input.tsx +9 -3
- package/src/components/numpad-sheet.tsx +4 -3
- 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/reauth-dialog.tsx +6 -10
- package/src/components/row-actions.tsx +2 -1
- package/src/components/section-nav.tsx +7 -0
- package/src/components/series-chart-axes.ts +11 -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 +35 -19
- 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-dismiss.ts +3 -2
- 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 +70 -5
- 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/calc.ts +13 -2
- package/src/lib/clipboard-minus.ts +14 -4
- package/src/lib/date-format.ts +89 -13
- package/src/lib/day-format.ts +96 -0
- package/src/lib/decimal-marks.ts +12 -3
- 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 +34 -19
- 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 +69 -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 +116 -1193
- package/src/tour/use-tour-progress.ts +1 -1
- package/src/tour/use-tour-url-sync.ts +7 -3
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Fragment as Fragment2, isValidElement, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Fragment as Fragment2, isValidElement, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from "lucide-react";
|
|
5
5
|
import { FOCUS_RING } from "./focus-ring.js";
|
|
6
6
|
import { IconButton } from "./icon-button.js";
|
|
@@ -195,7 +195,9 @@ function MobileRow({
|
|
|
195
195
|
mobileExpandAsDialog,
|
|
196
196
|
compact,
|
|
197
197
|
large,
|
|
198
|
-
select
|
|
198
|
+
select,
|
|
199
|
+
rowIndex,
|
|
200
|
+
onFocusIn
|
|
199
201
|
}) {
|
|
200
202
|
const expanded = isExpanded?.(row) ?? false;
|
|
201
203
|
const selecting = !!select?.active;
|
|
@@ -204,12 +206,15 @@ function MobileRow({
|
|
|
204
206
|
const expansion = expanded ? openedRowContent(row) : null;
|
|
205
207
|
const tint = rowClassName?.(row);
|
|
206
208
|
const interactive = !!onRowClick;
|
|
209
|
+
const rowUrl = rowHref?.(row);
|
|
210
|
+
const linkOnly = !interactive && rowUrl !== void 0;
|
|
207
211
|
const opensDetail = interactive && opensRow;
|
|
208
212
|
const swipeActions = swipeActionsFor?.(row);
|
|
209
213
|
const swipe = swipeActions ? physicalSwipe(swipeActions, mobileDir) : null;
|
|
210
214
|
const swipeEnabled = !!swipe && !selecting;
|
|
211
|
-
const stretched = mobileCardStretched && interactive && !selecting && !mobileCard && !!mobilePrimaryCol;
|
|
212
|
-
const href = mobileCardLinkable || stretched ?
|
|
215
|
+
const stretched = mobileCardStretched && (interactive || linkOnly) && !selecting && !mobileCard && !!mobilePrimaryCol;
|
|
216
|
+
const href = mobileCardLinkable || stretched ? rowUrl : void 0;
|
|
217
|
+
const opens = interactive || linkOnly && href !== void 0;
|
|
213
218
|
const cardClass = cn(
|
|
214
219
|
"w-full text-start flex items-center",
|
|
215
220
|
// §4: more room per row at Large — a taller row is a bigger tap target too.
|
|
@@ -221,14 +226,14 @@ function MobileRow({
|
|
|
221
226
|
// on every row of a long list, so anything larger becomes the list's personality
|
|
222
227
|
// rather than feedback. `transition-transform` alone, so the existing background
|
|
223
228
|
// flip stays instant.
|
|
224
|
-
|
|
229
|
+
opens && "cursor-pointer transition-transform duration-100 active:scale-[0.995] active:bg-active motion-reduce:transition-none motion-reduce:active:scale-100",
|
|
225
230
|
// The keyboard's frame drawn INSIDE the card, as the desktop row's is (keksdose live
|
|
226
231
|
// #394): the browser's own ring and tap highlight sit on the card's outer edge, and
|
|
227
232
|
// three ancestors clip it — SwipeableRow's `overflow-hidden`, the card list, and
|
|
228
233
|
// AppShell main's `overflow-x-clip` — so the frame went missing on one side. A press
|
|
229
234
|
// keeps the fill and the squeeze above; an outline on every tap of a long list would
|
|
230
235
|
// be louder than the acknowledgement it replaces.
|
|
231
|
-
(
|
|
236
|
+
(opens || selecting) && !stretched && cn("[-webkit-tap-highlight-color:transparent] focus-visible:ring-inset", FOCUS_RING),
|
|
232
237
|
// A stretched card (0.50 K4) draws the same frame, inside the card, while its TITLE
|
|
233
238
|
// control has the keyboard: the ring outlines the whole tap target, as on every other
|
|
234
239
|
// card, not the title's line alone. The own-click cells draw their own.
|
|
@@ -247,14 +252,15 @@ function MobileRow({
|
|
|
247
252
|
href,
|
|
248
253
|
"data-card-link": "",
|
|
249
254
|
"data-row-link": "",
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
navigate: linkOnly,
|
|
256
|
+
onActivate: interactive ? () => onRowClick(row) : void 0,
|
|
257
|
+
"aria-expanded": opensRow && interactive ? expanded : void 0,
|
|
252
258
|
className: stretchedClass,
|
|
253
|
-
onKeyDown: (e) => {
|
|
259
|
+
onKeyDown: interactive ? (e) => {
|
|
254
260
|
if (e.key !== " ") return;
|
|
255
261
|
e.preventDefault();
|
|
256
262
|
onRowClick(row);
|
|
257
|
-
},
|
|
263
|
+
} : void 0,
|
|
258
264
|
children: title
|
|
259
265
|
}
|
|
260
266
|
) : /* @__PURE__ */ jsx(
|
|
@@ -417,7 +423,7 @@ function MobileRow({
|
|
|
417
423
|
) : stretched ? (
|
|
418
424
|
// The stretched card (0.50 K4): a plain box; its title is the control (above).
|
|
419
425
|
/* @__PURE__ */ jsx("div", { ...press, "data-card-stretched": "", className: cardClass, children: cardInner })
|
|
420
|
-
) :
|
|
426
|
+
) : opens && href ? (
|
|
421
427
|
// A card with a URL of its own is a LINK, not a div wearing role="button"
|
|
422
428
|
// (feedback #451): the phone's long-press "open in new tab" and a screen
|
|
423
429
|
// reader's link semantics both come free, and the fake role goes away.
|
|
@@ -426,14 +432,15 @@ function MobileRow({
|
|
|
426
432
|
{
|
|
427
433
|
href,
|
|
428
434
|
...press,
|
|
429
|
-
|
|
430
|
-
|
|
435
|
+
navigate: linkOnly,
|
|
436
|
+
onActivate: interactive ? () => onRowClick(row) : void 0,
|
|
437
|
+
"aria-expanded": opensRow && interactive ? expanded : void 0,
|
|
431
438
|
className: cardClass,
|
|
432
|
-
onKeyDown: (e) => {
|
|
439
|
+
onKeyDown: interactive ? (e) => {
|
|
433
440
|
if (e.key !== " ") return;
|
|
434
441
|
e.preventDefault();
|
|
435
442
|
onRowClick(row);
|
|
436
|
-
},
|
|
443
|
+
} : void 0,
|
|
437
444
|
children: cardInner
|
|
438
445
|
}
|
|
439
446
|
)
|
|
@@ -465,24 +472,48 @@ function MobileRow({
|
|
|
465
472
|
}
|
|
466
473
|
)
|
|
467
474
|
);
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
475
|
+
return (
|
|
476
|
+
/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- `onFocus`
|
|
477
|
+
only notes that the focus is in this card (DSP-4); the card inside is the control. */
|
|
478
|
+
/* @__PURE__ */ jsxs(
|
|
479
|
+
"li",
|
|
480
|
+
{
|
|
481
|
+
className: cn(tint),
|
|
482
|
+
...cleanAttrs(rowAttributes?.(row)),
|
|
483
|
+
"data-row-index": rowIndex,
|
|
484
|
+
onFocus: onFocusIn,
|
|
485
|
+
"data-selected": selecting && select.selected ? "" : void 0,
|
|
486
|
+
onClickCapture: select ? (e) => {
|
|
487
|
+
if (!longPress.consumeClick()) return;
|
|
488
|
+
e.preventDefault();
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
} : void 0,
|
|
491
|
+
children: [
|
|
492
|
+
swipeEnabled ? /* @__PURE__ */ jsx(SwipeableRow, { enabled: true, left: swipe.left, right: swipe.right, children: body }) : body,
|
|
493
|
+
expansion && !mobileExpandAsDialog && /* @__PURE__ */ jsx("div", { className: cn("bg-surface-2 border-t border-subtle", compact ? "px-3 py-2" : "px-4 py-3"), children: expansion })
|
|
494
|
+
]
|
|
495
|
+
}
|
|
496
|
+
)
|
|
484
497
|
);
|
|
485
498
|
}
|
|
499
|
+
function MobileLeadingRow({
|
|
500
|
+
sticky,
|
|
501
|
+
onHeight,
|
|
502
|
+
children
|
|
503
|
+
}) {
|
|
504
|
+
const ref = useRef(null);
|
|
505
|
+
useLayoutEffect(() => {
|
|
506
|
+
const el = ref.current;
|
|
507
|
+
if (!sticky || !onHeight || !el) return;
|
|
508
|
+
const measure = () => onHeight(el.getBoundingClientRect().height);
|
|
509
|
+
measure();
|
|
510
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
511
|
+
const observer = new ResizeObserver(measure);
|
|
512
|
+
observer.observe(el);
|
|
513
|
+
return () => observer.disconnect();
|
|
514
|
+
}, [sticky, onHeight]);
|
|
515
|
+
return /* @__PURE__ */ jsx("li", { ref, "data-leading-row": sticky ? "sticky" : "", className: cn(sticky && "sticky top-0 z-[11] bg-surface"), children });
|
|
516
|
+
}
|
|
486
517
|
function MobileSelectBar({
|
|
487
518
|
active,
|
|
488
519
|
allSelected,
|
|
@@ -680,6 +711,7 @@ function MobileFilters({
|
|
|
680
711
|
export {
|
|
681
712
|
MOBILE_CARD_FIELDS,
|
|
682
713
|
MobileFilters,
|
|
714
|
+
MobileLeadingRow,
|
|
683
715
|
MobileRow,
|
|
684
716
|
MobileSelectBar,
|
|
685
717
|
MobileSort,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-mobile.tsx"],"sourcesContent":["import { Fragment, isValidElement, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode } from \"react\";\nimport { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from \"lucide-react\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { IconButton } from \"./icon-button\";\nimport { Select } from \"./select\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { defaultFilterState, isFilterActive, resolveFilter } from \"./data-table-filters\";\nimport type { ColumnFilter, FilterValue } from \"./data-table-filters\";\nimport type { SortCycle, SortState } from \"./data-table-sort\";\nimport { FilterPopover } from \"./data-table-filter-popover\";\nimport { SwipeableRow, type SwipeAction } from \"./swipeable-row\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { Checkbox } from \"./checkbox\";\nimport { Chip } from \"./chip\";\nimport { BooleanMark } from \"./data-table-cells\";\nimport { EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport { useLongPress } from \"../hooks/use-long-press\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { RowLink, cleanAttrs, columnLabel, fromOutside, fromOwnControl } from \"./data-table-shared\";\nimport type {\n DataTableCellProps,\n DataTableColumn,\n DataTableProps,\n DataTableSortLabels,\n FilterState,\n MobileSwipeActions,\n} from \"./data-table\";\n\n// DataTable's phone layout: the card a row becomes, the fields inside it, and the sort\n// and filter controls above the card list. Moved out of data-table.tsx whole (refactor\n// audit 2026-10, MOD-3 step 4); the list itself, its pager and its row dialog stay in\n// DataTable, which owns the state they share with the wide-screen table.\n\n/** Only the `data-*` attributes of a cell's props — what a phone card field takes. */\nfunction dataAttrsOf(props: DataTableCellProps | undefined): Record<string, unknown> | undefined {\n if (!props) return undefined;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(props)) if (k.startsWith(\"data-\") && k !== \"data-col\") out[k] = v;\n return out;\n}\n\n/** The logical sides onto the physical drag directions {@link SwipeableRow} takes. */\nfunction physicalSwipe(actions: MobileSwipeActions, dir: Direction): { left?: SwipeAction[]; right?: SwipeAction[] } {\n const rtl = dir === \"rtl\";\n return {\n left: (rtl ? actions.end : actions.start) ?? actions.left,\n right: (rtl ? actions.start : actions.end) ?? actions.right,\n };\n}\n\n// ---------- Phone card at Large (0.32) ----------\n\n/**\n * §4 \"nothing truncates\" in a phone card at Large and Extra large: a cell's own\n * `truncate` (a kit cell, a chip, the app's span) wraps instead — and breaks inside a\n * word too long for the card rather than running out of it. A descendant rule, so it\n * reaches the cells without the table knowing what they render; `large:`, so Normal\n * keeps every ellipsis it had.\n */\nconst CARD_NO_TRUNCATE = \"large:[&_.truncate]:whitespace-normal large:[&_.truncate]:[overflow-wrap:anywhere]\";\n\n/**\n * Columns as label-above-value pairs: the phone card's fields at Large (§4), and the\n * opened row's `mobileDetailsInRow` at every size. Each pair is a `<div>` in the\n * `<dl>` — the grouping HTML allows — so a pair keeps together and the gap falls\n * BETWEEN facts rather than between a label and its own value. `relative` holds a\n * header's `sr-only` text (an actions column's), as every kit container must.\n */\nexport function StackedFields<T>({\n columns,\n row,\n compact,\n liftOwnClicks = false,\n}: {\n columns: DataTableColumn<T>[];\n row: T;\n compact: boolean;\n /** On a stretched card (0.50 K4): lift the own-click fields above its title link. */\n liftOwnClicks?: boolean;\n}) {\n const fields = filledFields(columns, row);\n if (fields.length === 0) return null;\n return (\n <dl\n data-card-fields=\"stacked\"\n className={cn(\n \"relative flex min-w-0 flex-col\",\n compact ? \"gap-1.5 text-xs\" : \"gap-2.5 text-sm\",\n CARD_NO_TRUNCATE,\n )}\n >\n {fields.map(({ col, value }) => (\n <div key={col.key} className=\"min-w-0\">\n <dt className=\"text-xs uppercase tracking-wide text-muted\">{col.header}</dt>\n <dd\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\n \"mt-0.5 min-w-0 break-words text-secondary\",\n liftOwnClicks && keepsOwnClicks(col) && LIFTED_FIELD,\n )}\n >\n {value}\n </dd>\n </div>\n ))}\n </dl>\n );\n}\n\n/**\n * A stretched card's own-click cell (0.50 K4): above the title control's overlay\n * (`after:z-[1]`), so its link or buttons take their own taps. `w-fit` keeps the lifted\n * box to the cell's content, so the field's empty rest still opens the row.\n */\nconst LIFTED_CELL = \"relative z-[2]\";\nconst LIFTED_FIELD = cn(LIFTED_CELL, \"w-fit max-w-full justify-self-start\");\n\n/**\n * The keyboard frame of a stretched card (0.50 K4): FOCUS_RING's, inside the card,\n * while its title control (`data-card-link`) has the focus.\n */\nconst STRETCHED_RING =\n \"has-[[data-card-link]:focus-visible]:ring-inset has-[[data-card-link]:focus-visible]:ring-focus has-[[data-card-link]:focus-visible]:ring-brand\";\n\n/** The most fields the default phone card shows under its title (0.49 KIT-7). */\nexport const MOBILE_CARD_FIELDS = 3;\n\n/**\n * The default phone card's columns (0.49 KIT-7), out of the phone's columns (visible,\n * not `mobileHidden`): the title — `mobile: \"primary\"` or `mobilePrimary`, else the\n * first that is not a status — the `role: \"status\"` columns for beside it, at most\n * {@link MOBILE_CARD_FIELDS} fields: those marked `mobile: \"secondary\"` when any is,\n * else the first after the title, and the `role: \"actions\"` columns, drawn after the\n * fields. An actions column is never the title and never one of the fields, so the\n * fields and the marks leave it on the card; only `mobileHidden` / `mobile: \"hidden\"`\n * (which keep it out of `columns`) remove it.\n */\nexport function mobileCardColumns<T>(columns: DataTableColumn<T>[]): {\n primary: DataTableColumn<T> | null;\n status: DataTableColumn<T>[];\n fields: DataTableColumn<T>[];\n actions: DataTableColumn<T>[];\n} {\n const titles = columns.filter((c) => c.role !== \"actions\");\n const primary =\n titles.find((c) => c.mobile === \"primary\") ??\n titles.find((c) => c.mobilePrimary) ??\n titles.find((c) => c.role !== \"status\") ??\n titles[0] ??\n null;\n const status = columns.filter((c) => c !== primary && c.role === \"status\");\n const actions = columns.filter((c) => c.role === \"actions\");\n const rest = titles.filter((c) => c !== primary && c.role !== \"status\");\n const marked = rest.filter((c) => c.mobile === \"secondary\");\n return { primary, status, fields: (marked.length > 0 ? marked : rest).slice(0, MOBILE_CARD_FIELDS), actions };\n}\n\n/** A column whose cell keeps its own clicks — `noRowLink`, or `role: \"actions\"`: no row\n * link may wrap it, and a card that draws it is not a link itself. */\nexport function keepsOwnClicks<T>(col: DataTableColumn<T>): boolean {\n return !!col.noRowLink || col.role === \"actions\";\n}\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one, never under vitest. Read per call so a test can switch it on. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\n/**\n * A development build's note on the default phone card (0.49 KIT-7): a `noRowLink`\n * column past its three fields. Such a column holds the cell's own controls — a per-row\n * delete, a link — and the card leaves it out with them, so on a phone the action is\n * gone. Never for a `role: \"actions\"` column, which the card draws after its fields.\n * Read off the declared `columns` (not the ones a person or the width hid), and only\n * while the table lets the card choose: `enabled` is false with a `mobileCard` or\n * `mobileDetailsInRow` (the column is then in the opened row), and a column marked\n * `mobile: \"secondary\"` means the app chose. Once per table and column key; `name`\n * (the table's `storageKey` or `urlSync` prefix) says which table, else its title\n * column does.\n */\nexport function useCardColumnWarnings<T>(columns: DataTableColumn<T>[], enabled: boolean, name?: string): void {\n const warned = useRef<Set<string> | null>(null);\n useEffect(() => {\n if (!enabled || !devWarnings()) return;\n const phone = columns.filter((c) => !c.mobileHidden && c.mobile !== \"hidden\");\n if (phone.some((c) => c.mobile === \"secondary\")) return;\n const { primary, status, fields, actions } = mobileCardColumns(phone);\n const onCard = new Set<DataTableColumn<T> | null>([primary, ...status, ...fields, ...actions]);\n const table = name ? ` \"${name}\"` : primary ? ` (title column \"${primary.key}\")` : \"\";\n warned.current ??= new Set();\n for (const col of phone) {\n if (onCard.has(col) || !col.noRowLink || warned.current.has(col.key)) continue;\n warned.current.add(col.key);\n console.warn(\n `[ui-kit] DataTable${table}: column \"${col.key}\" (noRowLink) is past the phone card's three fields and ` +\n 'leaves the card — give it role: \"actions\" (drawn after the fields), or move its action to rowActions.',\n );\n }\n }, [columns, enabled, name]);\n}\n\n/**\n * Whether a rendered cell shows nothing worth a line (0.49 KIT-7): `null`, `undefined`,\n * a boolean (React draws neither), `\"\"` or blank text, and the formatters' empty value\n * \"—\" ({@link EMPTY_FORMATTED_VALUE}), and `booleanColumn`'s mark for an unset value.\n * Any other element is never empty here — its output is the component's — so a column\n * whose empty value should drop off the card returns `null` (or \"—\") from `cell` for it.\n */\nexport function isEmptyCell(value: ReactNode): boolean {\n if (value === null || value === undefined || typeof value === \"boolean\") return true;\n if (isValidElement<{ value?: unknown }>(value) && value.type === BooleanMark) return value.props.value == null;\n if (typeof value === \"string\") {\n const text = value.trim();\n return text === \"\" || text === EMPTY_FORMATTED_VALUE;\n }\n return false;\n}\n\n/** Each column's rendered cell, without the empty ones. */\nfunction filledFields<T>(columns: DataTableColumn<T>[], row: T): { col: DataTableColumn<T>; value: ReactNode }[] {\n return columns.map((col) => ({ col, value: col.cell(row) })).filter(({ value }) => !isEmptyCell(value));\n}\n\n/** A status column's value beside the card's title: text in a neutral chip, an element\n * (the app's own Chip or StatusDot) as it is. */\nfunction StatusChip({ value }: { value: ReactNode }) {\n return typeof value === \"string\" || typeof value === \"number\" ? <Chip size=\"sm\">{value}</Chip> : <>{value}</>;\n}\n\n// ---------- Phone sort control (keksdose K10) ----------\n\n/**\n * The card list's sort control: a \"Sort by\" select and a direction button.\n *\n * keksdose K10 (inputs audit 2026-10-02 §5; its aggregated report tables, #258): a\n * table is sorted by its header row and the phone card list has none, so the two\n * decisions the header carries — which column, which direction — were unreachable\n * below the breakpoint. keksdose hand-built this row over its DataTable to answer\n * \"who did I pay the most\" on the device it was asked on; this is that row, driven by\n * the SAME sort state as the headers (controlled `sorts` included), so turning the\n * phone sideways keeps the order and a sort chosen here is the one the URL and the\n * storage key hold.\n *\n * The rules are the header's: a newly picked column starts in its `firstSort`, the\n * direction button flips the primary sort (secondary ones stay), and \"Default order\"\n * — the third click of a `\"tri\"` cycle — is offered only under `\"tri\"`, since a\n * `\"toggle\"` table promises it is always ranked by something. The select's label is\n * a real `<label>` beside it, and the button sits outside that label: a click inside\n * a label is forwarded to the control it names, so a nested button would flip AND\n * open the select.\n */\nexport function MobileSort<T>({\n columns,\n sorts,\n sortCycle,\n compact,\n labels,\n onPick,\n onFlip,\n}: {\n columns: DataTableColumn<T>[];\n sorts: SortState[];\n sortCycle: SortCycle;\n compact: boolean;\n labels: DataTableLabels;\n onPick: (key: string | null) => void;\n onFlip: () => void;\n}) {\n const selectId = useId();\n const sortLabels: DataTableSortLabels = labels;\n if (columns.length === 0) return null;\n // A persisted or URL sort can name a column with no `sortBy` (or a hidden one): the\n // sort chain drops it, and so does the control rather than claiming it.\n const primary = sorts[0] && columns.some((c) => c.key === sorts[0].key) ? sorts[0] : null;\n const offerDefault = sortCycle === \"tri\" || primary === null;\n const desc = primary?.dir === \"desc\";\n return (\n <div\n data-table-mobile-sort=\"\"\n className={cn(\n // At Large the direction button shows its words (IconButton's label, §4); the\n // row wraps so the select keeps a usable width on a 240 px card.\n \"flex flex-wrap items-center gap-2 border-b border-subtle\",\n compact ? \"px-3 py-1.5\" : \"px-4 py-2\",\n )}\n >\n <label htmlFor={selectId} className=\"shrink-0 text-xs text-muted\">\n {sortLabels.sortBy}\n </label>\n <Select\n id={selectId}\n size={compact ? \"sm\" : \"md\"}\n className=\"min-w-0 flex-1 large:basis-40\"\n value={primary?.key ?? \"\"}\n onChange={(e) => onPick(e.target.value || null)}\n >\n {offerDefault && <option value=\"\">{sortLabels.sortDefault}</option>}\n {columns.map((col) => (\n <option key={col.key} value={col.key}>\n {columnLabel(col, labels)}\n </option>\n ))}\n </Select>\n <IconButton\n size={compact ? \"xs\" : \"md\"}\n variant=\"secondary\"\n label={desc ? sortLabels.sortDescending : sortLabels.sortAscending}\n disabled={primary === null}\n onClick={onFlip}\n >\n {desc ? <ArrowDownWideNarrow /> : <ArrowUpNarrowWide />}\n </IconButton>\n </div>\n );\n}\n\n// ---------- Phone card ----------\n\n/** One phone card: what DataTable's card list draws for a row. */\nexport interface MobileRowProps<T> {\n row: T;\n isExpanded: DataTableProps<T>[\"isExpanded\"];\n /** What the row shows when opened — DataTable's `openedRowContent`. */\n openedRowContent: (row: T) => ReactNode;\n /** Whether an opened row has anything to show at all. */\n opensRow: boolean;\n rowClassName: DataTableProps<T>[\"rowClassName\"];\n rowAttributes: DataTableProps<T>[\"rowAttributes\"];\n onRowClick: DataTableProps<T>[\"onRowClick\"];\n rowHref: DataTableProps<T>[\"rowHref\"];\n /** `mobileSwipeActions`, or the swipes derived from `rowActions`. */\n swipeActionsFor: ((row: T) => MobileSwipeActions | null) | undefined;\n /** The card list's reading direction, for which physical drag a swipe side means. */\n mobileDir: Direction;\n /** No column the card renders owns a link, so the card itself may be one. */\n mobileCardLinkable: boolean;\n /**\n * The card draws cells that keep their own clicks, so it cannot be one control (0.50\n * K4): its TITLE is the row's link (`rowHref`) or button (`onRowClick`), stretched over\n * the card, and those cells sit above it. Only for the default card, never a\n * `mobileCard`, and never when the title itself keeps its own clicks.\n */\n mobileCardStretched: boolean;\n mobileCard: DataTableProps<T>[\"mobileCard\"];\n mobilePrimaryCol: DataTableColumn<T> | null;\n /** The `role: \"status\"` columns, drawn as chips beside the title (0.49 KIT-7). */\n mobileStatusColumns: DataTableColumn<T>[];\n mobileSecondaryColumns: DataTableColumn<T>[];\n /** The `role: \"actions\"` columns, drawn after the fields at the card's end (0.49 KIT-7). */\n mobileActionColumns: DataTableColumn<T>[];\n mobileExpandAsDialog: boolean;\n compact: boolean;\n /** The text size is Large or Extra large (§4). */\n large: boolean;\n /** Phone selection (0.40, keksdose live #421); null without DataTable's `selection`. */\n select: MobileRowSelect | null;\n}\n\n/** A phone card's part in DataTable's `selection` (0.40, keksdose live #421). */\nexport interface MobileRowSelect {\n /** The card list is in selection mode: the card shows its box and a tap toggles it. */\n active: boolean;\n /** `selection.isSelectable` for this row. */\n selectable: boolean;\n selected: boolean;\n onToggle: (checked: boolean) => void;\n /** A long press outside the mode: enter it with this row selected. */\n onLongPress: () => void;\n}\n\n/** One mobile card. Shared by the flat list and the grouped list, so both draw\n * identical row markup. */\nexport function MobileRow<T>({\n row,\n isExpanded,\n openedRowContent,\n opensRow,\n rowClassName,\n rowAttributes,\n onRowClick,\n rowHref,\n swipeActionsFor,\n mobileDir,\n mobileCardLinkable,\n mobileCardStretched,\n mobileCard,\n mobilePrimaryCol,\n mobileStatusColumns,\n mobileSecondaryColumns,\n mobileActionColumns,\n mobileExpandAsDialog,\n compact,\n large,\n select,\n}: MobileRowProps<T>) {\n const expanded = isExpanded?.(row) ?? false;\n // Selection mode (#421): the whole card is the row's checkbox — a tap toggles it\n // instead of opening the row, and the swipe and the long press stand down, so no\n // gesture means two things at once (keksdose's register list, its feedback #153).\n const selecting = !!select?.active;\n // Not on an open card, as in kk's register: its editor and a selection are two modes\n // fighting over the same row.\n const pressable = !!select && select.selectable && !selecting && !expanded;\n const longPress = useLongPress(() => select?.onLongPress(), { enabled: pressable });\n const expansion = expanded ? openedRowContent(row) : null;\n const tint = rowClassName?.(row);\n // Use div+role=\"button\" rather than a real <button> so cells that\n // contain their own interactive controls (status toggles, action\n // icons) don't end up as illegal nested buttons.\n const interactive = !!onRowClick;\n // Say that the card OPENS something (Keksdose feedback #163). A phone card had\n // only a cursor and a tap-tint to advertise its editor — on the invoice review\n // screen, whose entire purpose is correcting mis-read lines, that read as \"these\n // values are not editable\". A chevron is the affordance every list on a phone\n // uses for exactly this, and it points the way the row actually opens: right into\n // a sheet, or down into an inline panel that flips it when expanded.\n const opensDetail = interactive && opensRow;\n const swipeActions = swipeActionsFor?.(row);\n const swipe = swipeActions ? physicalSwipe(swipeActions, mobileDir) : null;\n // An OPEN card swipes too (0.40, keksdose live #416, Marcel: \"faster than scrolling\n // down to the buttons\"). Only the card's header — the summary line — is inside\n // SwipeableRow; the opened body below is a sibling, so its fields, sliders and\n // scrolling never meet the gesture. Until 0.40 an open card silently lost it.\n // In selection mode a sideways drag would be a second meaning for a selecting touch.\n const swipeEnabled = !!swipe && !selecting;\n // The stretched card (0.50 K4): the title is the control, the card a plain box.\n // Selection mode keeps its own card (the whole card is the toggle there).\n const stretched = mobileCardStretched && interactive && !selecting && !mobileCard && !!mobilePrimaryCol;\n const href = mobileCardLinkable || stretched ? rowHref?.(row) : undefined;\n const cardClass = cn(\n \"w-full text-start flex items-center\",\n // §4: more room per row at Large — a taller row is a bigger tap target too.\n compact ? \"px-3 py-2 gap-2 text-sm large:py-3\" : \"px-4 py-3 gap-3 large:py-4\",\n // Live #320's other half: the card acknowledges the touch before the sheet\n // arrives. On a cold route the data can take a beat, and an unacknowledged tap\n // reads as \"did that register?\" — which is most of what \"abrupt\" means here.\n // `origin-center` + a 0.5% squeeze is deliberately almost subliminal: this fires\n // on every row of a long list, so anything larger becomes the list's personality\n // rather than feedback. `transition-transform` alone, so the existing background\n // flip stays instant.\n interactive &&\n \"cursor-pointer transition-transform duration-100 active:scale-[0.995] active:bg-active motion-reduce:transition-none motion-reduce:active:scale-100\",\n // The keyboard's frame drawn INSIDE the card, as the desktop row's is (keksdose live\n // #394): the browser's own ring and tap highlight sit on the card's outer edge, and\n // three ancestors clip it — SwipeableRow's `overflow-hidden`, the card list, and\n // AppShell main's `overflow-x-clip` — so the frame went missing on one side. A press\n // keeps the fill and the squeeze above; an outline on every tap of a long list would\n // be louder than the acknowledgement it replaces.\n (interactive || selecting) &&\n !stretched &&\n cn(\"[-webkit-tap-highlight-color:transparent] focus-visible:ring-inset\", FOCUS_RING),\n // A stretched card (0.50 K4) draws the same frame, inside the card, while its TITLE\n // control has the keyboard: the ring outlines the whole tap target, as on every other\n // card, not the title's line alone. The own-click cells draw their own.\n stretched && cn(\"relative [-webkit-tap-highlight-color:transparent]\", STRETCHED_RING),\n selecting && \"cursor-pointer\",\n select?.selected && selecting && \"bg-brand-soft\",\n // A 500 ms hold would otherwise start a text selection, and iOS's link callout —\n // which only yields to this CSS. The trade kk made on its register: the card's text\n // is not selectable on a phone where the table offers selection.\n select && \"select-none [-webkit-touch-callout:none]\",\n );\n // The card's content is written once and worn by either tag below. Note that\n // the primary cell is rendered RAW here, never through `linkColumn` — the card\n // itself is the anchor, and routing it through `linkColumn` too would nest an\n // <a> inside an <a>. Desktop and mobile are mutually exclusive (`isMdUp`), so\n // exactly one anchor exists per row.\n // The default card's filled fields and status values (0.49 KIT-7): an empty one\n // gets no line. Not computed for a `mobileCard`, which draws its own.\n // The stretched card's title control (0.50 K4): the row's link, or a real <button>\n // when the table only has `onRowClick` — a plain control, since nothing interactive\n // sits inside it any more. Its `::after` covers the whole card, so a tap anywhere\n // but on an own-click cell (lifted above it) lands on it, and a long press on the\n // card is a long press on the link (\"open in new tab\"). Its accessible name is the\n // title alone; the fields read as text after it. `data-row-link` marks it as the\n // row itself for `fromOwnControl`, so the long press still arms on it.\n const stretchedClass =\n \"block w-full text-start focus-visible:outline-none after:absolute after:inset-0 after:z-[1] after:content-['']\";\n const titleControl = (title: ReactNode) =>\n href ? (\n <RowLink\n href={href}\n data-card-link=\"\"\n data-row-link=\"\"\n onActivate={() => onRowClick!(row)}\n aria-expanded={opensRow ? expanded : undefined}\n className={stretchedClass}\n // Enter follows RowLink's own onClick; Space opens the row too, not the list's scroll.\n onKeyDown={(e) => {\n if (e.key !== \" \") return;\n e.preventDefault();\n onRowClick!(row);\n }}\n >\n {title}\n </RowLink>\n ) : (\n <button\n type=\"button\"\n data-card-link=\"\"\n data-row-link=\"\"\n aria-expanded={opensRow ? expanded : undefined}\n onClick={() => onRowClick!(row)}\n className={cn(stretchedClass, \"font-[inherit]\")}\n >\n {title}\n </button>\n );\n const fields = mobileCard ? [] : filledFields(mobileSecondaryColumns, row);\n const statuses = mobileCard ? [] : filledFields(mobileStatusColumns, row);\n const actionCells = mobileCard ? [] : filledFields(mobileActionColumns, row);\n const cardInner = (\n <>\n {/* The card's own content keeps the column it always had; the chevron sits\n beside it rather than inside, so a caller's `mobileCard` is untouched. */}\n <div\n className={cn(\n \"flex min-w-0 flex-1 flex-col\",\n compact ? \"gap-1 large:gap-2\" : \"gap-2 large:gap-3\",\n // §4 \"nothing truncates\": at Large a cell's own `truncate` wraps instead —\n // the reader who chose bigger type is the one an ellipsis fails. A caller's\n // `mobileCard` is its own layout and keeps what it chose.\n !mobileCard && CARD_NO_TRUNCATE,\n )}\n >\n {mobileCard ? (\n mobileCard(row)\n ) : (\n <>\n {/* Only the `data-*` half of `cellProps` — see its note. The title row\n carries the status chips at its end (0.49 KIT-7). */}\n {(mobilePrimaryCol || statuses.length > 0) && (\n <div data-card-head=\"\" className=\"flex min-w-0 items-start justify-between gap-2\">\n {mobilePrimaryCol && (\n <div\n {...dataAttrsOf(mobilePrimaryCol.cellProps?.(row))}\n data-card-title=\"\"\n className=\"min-w-0 flex-1 break-words font-medium\"\n >\n {stretched ? titleControl(mobilePrimaryCol.cell(row)) : mobilePrimaryCol.cell(row)}\n </div>\n )}\n {statuses.length > 0 && (\n <div className=\"flex shrink-0 flex-wrap items-center justify-end gap-1\">\n {statuses.map(({ col, value }) => (\n <span\n key={col.key}\n {...dataAttrsOf(col.cellProps?.(row))}\n data-card-status=\"\"\n className=\"inline-flex\"\n >\n <StatusChip value={value} />\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n {fields.length > 0 && large ? (\n // §4: each label ABOVE its value at Large. Side by side, a label column\n // sized to the widest label leaves a value a sliver of a 240 px card.\n <StackedFields columns={mobileSecondaryColumns} row={row} compact={compact} liftOwnClicks={stretched} />\n ) : (\n fields.length > 0 && (\n <dl\n className={cn(\"grid grid-cols-[auto_1fr] gap-x-3\", compact ? \"gap-y-0.5 text-xs\" : \"gap-y-1 text-sm\")}\n >\n {fields.map(({ col, value }) => (\n <Fragment key={col.key}>\n <dt className=\"text-xs uppercase tracking-wide text-muted self-center\">{col.header}</dt>\n <dd\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\n \"min-w-0 text-secondary self-center\",\n stretched && keepsOwnClicks(col) && LIFTED_FIELD,\n )}\n >\n {value}\n </dd>\n </Fragment>\n ))}\n </dl>\n )\n )}\n {/* The row's actions after the fields, at the card's end (0.49 KIT-7): its\n buttons keep their own clicks, and RowActions folds into \"⋯\" at Large. */}\n {actionCells.length > 0 && (\n <div data-card-actions=\"\" className=\"flex min-w-0 flex-wrap items-center justify-end gap-2\">\n {actionCells.map(({ col, value }) => (\n <div\n key={col.key}\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\"flex min-w-0 justify-end\", stretched && LIFTED_CELL)}\n >\n {value}\n </div>\n ))}\n </div>\n )}\n </>\n )}\n </div>\n {opensDetail &&\n !selecting &&\n // `aria-hidden`: the row already announces itself as a button with\n // `aria-expanded`, so the icon would only add a second, wordless stop.\n (mobileExpandAsDialog ? (\n // Points INTO the sheet, which is the reading-forward direction.\n <ChevronRight aria-hidden className=\"size-4 shrink-0 text-placeholder rtl:-scale-x-100\" />\n ) : (\n <ChevronDown\n aria-hidden\n className={cn(\"size-4 shrink-0 text-placeholder transition-transform\", expanded && \"rotate-180\")}\n />\n ))}\n </>\n );\n // The long press rides the card itself; the click it leaves behind is swallowed on\n // the `<li>` below, in the capture phase, before the card (or RowLink) sees it.\n // Attached whenever the table selects, the hook's `enabled` deciding whether a press\n // arms: its pointerdown also clears a stale swallow latch, which must happen in the\n // mode too, or a long press whose click never came would eat the first toggle.\n //\n // A press that starts on a control in a cell (or in a portal one opened) is that\n // control's, not a way into the mode (keksdose live #428): it only clears the latch.\n const press = select\n ? {\n ...longPress.handlers,\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => {\n if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) {\n longPress.consumeClick();\n return;\n }\n longPress.handlers.onPointerDown(e);\n },\n onContextMenu: (e: ReactMouseEvent<HTMLElement>) => {\n if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) return;\n longPress.handlers.onContextMenu(e);\n },\n }\n : undefined;\n const body = selecting ? (\n // In the mode a card is a toggle — kk's register row: `role=\"button\"` with\n // `aria-pressed`, the box beside it only a picture of that state (the card is the\n // one control, so the two can never fight or fire twice).\n /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- role=\"button\",\n tabIndex 0 and Enter/Space are all here; the rule cannot follow the conditional role. */\n <div\n role={select!.selectable ? \"button\" : undefined}\n tabIndex={select!.selectable ? 0 : undefined}\n aria-pressed={select!.selectable ? select!.selected : undefined}\n aria-disabled={select!.selectable ? undefined : true}\n data-card-selecting=\"\"\n {...press}\n onClick={(e) => {\n if (!select!.selectable || fromOutside(e.target, e.currentTarget)) return;\n // A tap on a control in a cell is that control's (keksdose live #428).\n if (fromOwnControl(e.target, e.currentTarget)) return;\n select!.onToggle(!select!.selected);\n }}\n onKeyDown={(e) => {\n if (!select!.selectable || e.target !== e.currentTarget) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n select!.onToggle(!select!.selected);\n }\n }}\n className={cardClass}\n >\n <Checkbox\n readOnly\n tabIndex={-1}\n aria-hidden\n checked={select!.selected}\n disabled={!select!.selectable}\n className=\"pointer-events-none shrink-0 self-center\"\n inputClassName=\"pointer-events-none\"\n />\n {cardInner}\n </div>\n ) : stretched ? (\n // The stretched card (0.50 K4): a plain box; its title is the control (above).\n <div {...press} data-card-stretched=\"\" className={cardClass}>\n {cardInner}\n </div>\n ) : interactive && href ? (\n // A card with a URL of its own is a LINK, not a div wearing role=\"button\"\n // (feedback #451): the phone's long-press \"open in new tab\" and a screen\n // reader's link semantics both come free, and the fake role goes away.\n <RowLink\n href={href}\n {...press}\n onActivate={() => onRowClick!(row)}\n aria-expanded={opensRow ? expanded : undefined}\n className={cardClass}\n // Enter already activates a link natively, and that routes through\n // RowLink's own onClick — handling it here too would open the row twice.\n // Space is the only key left, and only so it opens the row instead of\n // scrolling the list.\n onKeyDown={(e) => {\n if (e.key !== \" \") return;\n e.preventDefault();\n onRowClick!(row);\n }}\n >\n {cardInner}\n </RowLink>\n ) : (\n /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- the handlers are\n only attached when `interactive`, and then so are role=\"button\", tabIndex 0 and the\n Enter/Space handler below; the rule cannot follow the conditional role. */\n <div\n {...press}\n role={interactive ? \"button\" : undefined}\n tabIndex={interactive ? 0 : undefined}\n onClick={\n interactive\n ? (e) => {\n if (fromOutside(e.target, e.currentTarget)) return;\n // A tap on a control in a cell (a select, a button) is that\n // control's, as on the desktop `<tr>` (keksdose live #428).\n if (fromOwnControl(e.target, e.currentTarget)) return;\n onRowClick!(row);\n }\n : undefined\n }\n onKeyDown={\n interactive\n ? (e) => {\n // A key press inside a nested control is that control's.\n if (e.target !== e.currentTarget) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick!(row);\n }\n }\n : undefined\n }\n aria-expanded={opensRow ? expanded : undefined}\n className={cardClass}\n >\n {cardInner}\n </div>\n );\n return (\n <li\n className={cn(tint)}\n {...cleanAttrs(rowAttributes?.(row))}\n data-selected={selecting && select!.selected ? \"\" : undefined}\n onClickCapture={\n select\n ? (e) => {\n if (!longPress.consumeClick()) return;\n e.preventDefault();\n e.stopPropagation();\n }\n : undefined\n }\n >\n {swipeEnabled ? (\n <SwipeableRow enabled left={swipe!.left} right={swipe!.right}>\n {body}\n </SwipeableRow>\n ) : (\n body\n )}\n {expansion && !mobileExpandAsDialog && (\n <div className={cn(\"bg-surface-2 border-t border-subtle\", compact ? \"px-3 py-2\" : \"px-4 py-3\")}>\n {expansion}\n </div>\n )}\n </li>\n );\n}\n\n// ---------- Phone selection bar (0.40, keksdose live #421) ----------\n\n/**\n * The card list's way into and out of selection mode for a keyboard and a screen\n * reader — the long press is a touch gesture, and a gesture alone is no path at all.\n *\n * Outside the mode it is one button, \"Select rows\", visually hidden until it takes\n * focus (SwipeableRow's keyboard buttons, the same rule): the card list keeps the look\n * kk's register has, where the long press is the way in, and Tab or a screen reader's\n * cursor still finds it. In the mode it is a visible row: \"Select all rows\" — the\n * desktop header's box — and Cancel, which clears the selection and leaves, as the\n * bulk bar's Clear does. The owner's bulk bar is the owner's, as on desktop: it\n * appears because the selection is no longer empty.\n */\nexport function MobileSelectBar({\n active,\n allSelected,\n someSelected,\n compact,\n labels,\n onEnter,\n onToggleAll,\n onCancel,\n}: {\n active: boolean;\n allSelected: boolean;\n someSelected: boolean;\n compact: boolean;\n labels: DataTableLabels;\n onEnter: () => void;\n onToggleAll: (checked: boolean) => void;\n onCancel: () => void;\n}) {\n // The control that was pressed unmounts with the mode it switched, so focus is\n // handed across: into the bar's box on the way in, back to the button on the way out.\n const handOff = useRef(false);\n const allBox = useRef<HTMLInputElement>(null);\n const enterButton = useRef<HTMLButtonElement>(null);\n useEffect(() => {\n if (!handOff.current) return;\n handOff.current = false;\n (active ? allBox.current : enterButton.current)?.focus();\n }, [active]);\n if (!active)\n return (\n <button\n ref={enterButton}\n type=\"button\"\n data-table-mobile-select=\"\"\n onClick={() => {\n handOff.current = true;\n onEnter();\n }}\n className={cn(\n \"sr-only rounded-md text-sm font-medium text-secondary\",\n // Padding only once shown: unconditional, it beats sr-only's `padding: 0`\n // (SwipeableRow's 0.32.1 note).\n \"focus:not-sr-only focus:m-2 focus:inline-flex focus:px-2 focus:py-1\",\n FOCUS_RING,\n )}\n >\n {labels.selectRows}\n </button>\n );\n return (\n <div\n data-table-mobile-select=\"\"\n className={cn(\n \"flex flex-wrap items-center justify-between gap-2 border-b border-subtle\",\n compact ? \"px-3 py-1.5\" : \"px-4 py-2\",\n )}\n >\n <Checkbox\n ref={allBox}\n label={labels.selectAllRows}\n checked={allSelected}\n indeterminate={someSelected && !allSelected}\n onChange={(e) => onToggleAll(e.target.checked)}\n />\n <button\n type=\"button\"\n onClick={() => {\n handOff.current = true;\n onCancel();\n }}\n className={cn(\"rounded-md px-2 py-1 text-sm text-muted hover:text-secondary\", FOCUS_RING)}\n >\n {labels.cancelSelection}\n </button>\n </div>\n );\n}\n\n// ---------- Mobile filter sheet ----------\n\n/**\n * Filter access for the mobile card list (feedback #299). The desktop table\n * houses per-column filters in its header; the card list has no header, so the\n * same filters were unreachable on phones. This renders a compact \"Filters\" bar\n * that opens a bottom sheet listing each filterable column as a collapsible\n * section, reusing the very same {@link FilterPopover} and filter state the\n * desktop table drives — so it filters identically and stays URL-synced.\n *\n * The sheet is a {@link DialogFrame} (round 0.34 BUG-7b = DUP-6). It was the last\n * hand-rolled modal in the kit: `role=\"dialog\" aria-modal=\"true\"` on the backdrop, with\n * copies of the scroll lock, the backdrop close and the Back entry — and no name, no\n * focus moved in, no trap and no Escape, so a reader was told the page was hidden while\n * focus stayed on it. On a phone `Modal` is a bottom sheet already; it now brings all\n * of that, and the exit motion, from the one place every other dialog gets it.\n */\nexport function MobileFilters<T>({\n columns,\n filters,\n selectOptionsByKey,\n isServer,\n hasFilterCallback,\n onSetFilter,\n onClearFilter,\n onClearAll,\n labels,\n locale,\n}: {\n columns: DataTableColumn<T>[];\n filters: FilterState;\n selectOptionsByKey: Record<string, { value: string; label: string }[]>;\n isServer: boolean;\n hasFilterCallback: boolean;\n onSetFilter: (key: string, value: FilterValue) => void;\n onClearFilter: (key: string) => void;\n onClearAll: () => void;\n labels: DataTableLabels;\n locale?: string;\n}) {\n const [open, setOpen] = useState(false);\n // The sheet is portalled to <body>, out from under whatever `dir` the table sits\n // in, so it carries the trigger's direction with it — read at the moment of opening,\n // from the element the user actually pressed.\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const [expanded, setExpanded] = useState<string | null>(null);\n\n const filterable = useMemo(\n () =>\n columns\n .map((col) => ({ col, filter: !isServer || hasFilterCallback ? resolveFilter(col) : null }))\n .filter((x): x is { col: DataTableColumn<T>; filter: ColumnFilter<T> } => !!x.filter),\n [columns, isServer, hasFilterCallback],\n );\n\n if (filterable.length === 0) return null;\n\n const activeCount = filterable.filter(({ col }) => {\n const s = filters[col.key];\n return s && isFilterActive(s);\n }).length;\n\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-subtle px-4 py-2\">\n <button\n type=\"button\"\n onClick={(e) => {\n setDir(dirOf(e.currentTarget));\n setOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-secondary hover:bg-hover\"\n >\n <Filter className=\"size-4\" />\n <span>{labels.filters}</span>\n {activeCount > 0 && (\n <span className=\"inline-flex min-w-4 items-center justify-center rounded-full bg-info-soft px-1 text-caption font-semibold text-info\">\n {activeCount}\n </span>\n )}\n </button>\n {activeCount > 0 && (\n <button type=\"button\" onClick={onClearAll} className=\"text-xs text-muted hover:text-secondary\">\n {labels.clearAll}\n </button>\n )}\n {/* Escape, the backdrop, Back (Keksdose feedback #172: the filters are already\n in the URL, so closing never loses anything), the scroll lock, focus in and\n back to this bar's button — all `Modal`'s. `fullBleed` lets the sheet meet the\n screen's edges on a phone, as it always did. */}\n <DialogFrame\n open={open}\n onClose={() => setOpen(false)}\n title={labels.filters}\n closeButton\n closeLabel={labels.close}\n headerDivider\n fullBleed\n // Portalled to <body>, so the direction read from the button goes with it.\n dir={dir}\n className=\"max-h-[85vh] rounded-b-none md:rounded-b-lg\"\n // The sections run edge to edge with a rule between them, as before.\n bodyClassName=\"space-y-0 divide-y divide-subtle px-0 pt-0 pb-0\"\n actions={(close) => (\n <>\n <button\n type=\"button\"\n onClick={onClearAll}\n disabled={activeCount === 0}\n // `me-auto` keeps Clear all at the start and Done at the end.\n className=\"me-auto text-sm text-muted hover:text-secondary disabled:opacity-40\"\n >\n {labels.clearAll}\n </button>\n <button\n type=\"button\"\n onClick={close}\n className=\"rounded-md bg-inverse px-4 py-2 text-sm font-medium text-inverse\"\n >\n {labels.done}\n </button>\n </>\n )}\n >\n {filterable.map(({ col, filter }) => {\n const state = filters[col.key];\n const active = !!state && isFilterActive(state);\n const isItemOpen = expanded === col.key;\n return (\n <div key={col.key}>\n <button\n type=\"button\"\n onClick={() => setExpanded(isItemOpen ? null : col.key)}\n aria-expanded={isItemOpen}\n className=\"flex w-full items-center justify-between gap-2 px-4 py-3 text-start\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium text-secondary\">\n {col.header}\n {active && <span className=\"size-1.5 rounded-full bg-brand\" />}\n </span>\n <ChevronDown\n className={cn(\"size-4 shrink-0 text-placeholder transition-transform\", isItemOpen && \"rotate-180\")}\n />\n </button>\n {isItemOpen && (\n <div className=\"px-4 pb-3\">\n <FilterPopover\n column={col}\n state={state ?? defaultFilterState(filter)}\n onChange={(next) => onSetFilter(col.key, next)}\n onClear={() => onClearFilter(col.key)}\n selectOptions={selectOptionsByKey[col.key] ?? []}\n labels={labels}\n locale={locale}\n />\n </div>\n )}\n </div>\n );\n })}\n </DialogFrame>\n </div>\n );\n}\n"],"mappings":"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","names":["Fragment"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-mobile.tsx"],"sourcesContent":["import { Fragment, isValidElement, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport type { MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode } from \"react\";\nimport { ArrowDownWideNarrow, ArrowUpNarrowWide, ChevronDown, ChevronRight, Filter } from \"lucide-react\";\nimport { FOCUS_RING } from \"./focus-ring\";\nimport { IconButton } from \"./icon-button\";\nimport { Select } from \"./select\";\nimport { cn } from \"../lib/cn\";\nimport { dirOf, type Direction } from \"../lib/direction\";\nimport { defaultFilterState, isFilterActive, resolveFilter } from \"./data-table-filters\";\nimport type { ColumnFilter, FilterValue } from \"./data-table-filters\";\nimport type { SortCycle, SortState } from \"./data-table-sort\";\nimport { FilterPopover } from \"./data-table-filter-popover\";\nimport { SwipeableRow, type SwipeAction } from \"./swipeable-row\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { Checkbox } from \"./checkbox\";\nimport { Chip } from \"./chip\";\nimport { BooleanMark } from \"./data-table-cells\";\nimport { EMPTY_FORMATTED_VALUE } from \"../lib/format\";\nimport { useLongPress } from \"../hooks/use-long-press\";\nimport type { DataTableLabels } from \"./data-table-labels\";\nimport { RowLink, cleanAttrs, columnLabel, fromOutside, fromOwnControl } from \"./data-table-shared\";\nimport type {\n DataTableCellProps,\n DataTableColumn,\n DataTableProps,\n DataTableSortLabels,\n FilterState,\n MobileSwipeActions,\n} from \"./data-table\";\n\n// DataTable's phone layout: the card a row becomes, the fields inside it, and the sort\n// and filter controls above the card list. Moved out of data-table.tsx whole (refactor\n// audit 2026-10, MOD-3 step 4); the list itself, its pager and its row dialog stay in\n// DataTable, which owns the state they share with the wide-screen table.\n\n/** Only the `data-*` attributes of a cell's props — what a phone card field takes. */\nfunction dataAttrsOf(props: DataTableCellProps | undefined): Record<string, unknown> | undefined {\n if (!props) return undefined;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(props)) if (k.startsWith(\"data-\") && k !== \"data-col\") out[k] = v;\n return out;\n}\n\n/** The logical sides onto the physical drag directions {@link SwipeableRow} takes. */\nfunction physicalSwipe(actions: MobileSwipeActions, dir: Direction): { left?: SwipeAction[]; right?: SwipeAction[] } {\n const rtl = dir === \"rtl\";\n return {\n left: (rtl ? actions.end : actions.start) ?? actions.left,\n right: (rtl ? actions.start : actions.end) ?? actions.right,\n };\n}\n\n// ---------- Phone card at Large (0.32) ----------\n\n/**\n * §4 \"nothing truncates\" in a phone card at Large and Extra large: a cell's own\n * `truncate` (a kit cell, a chip, the app's span) wraps instead — and breaks inside a\n * word too long for the card rather than running out of it. A descendant rule, so it\n * reaches the cells without the table knowing what they render; `large:`, so Normal\n * keeps every ellipsis it had.\n */\nconst CARD_NO_TRUNCATE = \"large:[&_.truncate]:whitespace-normal large:[&_.truncate]:[overflow-wrap:anywhere]\";\n\n/**\n * Columns as label-above-value pairs: the phone card's fields at Large (§4), and the\n * opened row's `mobileDetailsInRow` at every size. Each pair is a `<div>` in the\n * `<dl>` — the grouping HTML allows — so a pair keeps together and the gap falls\n * BETWEEN facts rather than between a label and its own value. `relative` holds a\n * header's `sr-only` text (an actions column's), as every kit container must.\n */\nexport function StackedFields<T>({\n columns,\n row,\n compact,\n liftOwnClicks = false,\n}: {\n columns: readonly DataTableColumn<T>[];\n row: T;\n compact: boolean;\n /** On a stretched card (0.50 K4): lift the own-click fields above its title link. */\n liftOwnClicks?: boolean;\n}) {\n const fields = filledFields(columns, row);\n if (fields.length === 0) return null;\n return (\n <dl\n data-card-fields=\"stacked\"\n className={cn(\n \"relative flex min-w-0 flex-col\",\n compact ? \"gap-1.5 text-xs\" : \"gap-2.5 text-sm\",\n CARD_NO_TRUNCATE,\n )}\n >\n {fields.map(({ col, value }) => (\n <div key={col.key} className=\"min-w-0\">\n <dt className=\"text-xs uppercase tracking-wide text-muted\">{col.header}</dt>\n <dd\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\n \"mt-0.5 min-w-0 break-words text-secondary\",\n liftOwnClicks && keepsOwnClicks(col) && LIFTED_FIELD,\n )}\n >\n {value}\n </dd>\n </div>\n ))}\n </dl>\n );\n}\n\n/**\n * A stretched card's own-click cell (0.50 K4): above the title control's overlay\n * (`after:z-[1]`), so its link or buttons take their own taps. `w-fit` keeps the lifted\n * box to the cell's content, so the field's empty rest still opens the row.\n */\nconst LIFTED_CELL = \"relative z-[2]\";\nconst LIFTED_FIELD = cn(LIFTED_CELL, \"w-fit max-w-full justify-self-start\");\n\n/**\n * The keyboard frame of a stretched card (0.50 K4): FOCUS_RING's, inside the card,\n * while its title control (`data-card-link`) has the focus.\n */\nconst STRETCHED_RING =\n \"has-[[data-card-link]:focus-visible]:ring-inset has-[[data-card-link]:focus-visible]:ring-focus has-[[data-card-link]:focus-visible]:ring-brand\";\n\n/** The most fields the default phone card shows under its title (0.49 KIT-7). */\nexport const MOBILE_CARD_FIELDS = 3;\n\n/**\n * The default phone card's columns (0.49 KIT-7), out of the phone's columns (visible,\n * not `mobileHidden`): the title — `mobile: \"primary\"` or `mobilePrimary`, else the\n * first that is not a status — the `role: \"status\"` columns for beside it, at most\n * {@link MOBILE_CARD_FIELDS} fields: those marked `mobile: \"secondary\"` when any is,\n * else the first after the title, and the `role: \"actions\"` columns, drawn after the\n * fields. An actions column is never the title and never one of the fields, so the\n * fields and the marks leave it on the card; only `mobileHidden` / `mobile: \"hidden\"`\n * (which keep it out of `columns`) remove it.\n */\nexport function mobileCardColumns<T>(columns: readonly DataTableColumn<T>[]): {\n primary: DataTableColumn<T> | null;\n status: DataTableColumn<T>[];\n fields: DataTableColumn<T>[];\n actions: DataTableColumn<T>[];\n} {\n const titles = columns.filter((c) => c.role !== \"actions\");\n const primary =\n titles.find((c) => c.mobile === \"primary\") ??\n titles.find((c) => c.mobilePrimary) ??\n titles.find((c) => c.role !== \"status\") ??\n titles[0] ??\n null;\n const status = columns.filter((c) => c !== primary && c.role === \"status\");\n const actions = columns.filter((c) => c.role === \"actions\");\n const rest = titles.filter((c) => c !== primary && c.role !== \"status\");\n const marked = rest.filter((c) => c.mobile === \"secondary\");\n return { primary, status, fields: (marked.length > 0 ? marked : rest).slice(0, MOBILE_CARD_FIELDS), actions };\n}\n\n/** A column whose cell keeps its own clicks — `noRowLink`, or `role: \"actions\"`: no row\n * link may wrap it, and a card that draws it is not a link itself. */\nexport function keepsOwnClicks<T>(col: DataTableColumn<T>): boolean {\n return !!col.noRowLink || col.role === \"actions\";\n}\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one, never under vitest. Read per call so a test can switch it on. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\n/**\n * A development build's note on the default phone card (0.49 KIT-7): a `noRowLink`\n * column past its three fields. Such a column holds the cell's own controls — a per-row\n * delete, a link — and the card leaves it out with them, so on a phone the action is\n * gone. Never for a `role: \"actions\"` column, which the card draws after its fields.\n * Read off the declared `columns` (not the ones a person or the width hid), and only\n * while the table lets the card choose: `enabled` is false with a `mobileCard` or\n * `mobileDetailsInRow` (the column is then in the opened row), and a column marked\n * `mobile: \"secondary\"` means the app chose. Once per table and column key; `name`\n * (the table's `storageKey` or `urlSync` prefix) says which table, else its title\n * column does.\n */\nexport function useCardColumnWarnings<T>(\n columns: readonly DataTableColumn<T>[],\n enabled: boolean,\n name?: string,\n): void {\n const warned = useRef<Set<string> | null>(null);\n useEffect(() => {\n if (!enabled || !devWarnings()) return;\n const phone = columns.filter((c) => !c.mobileHidden && c.mobile !== \"hidden\");\n if (phone.some((c) => c.mobile === \"secondary\")) return;\n const { primary, status, fields, actions } = mobileCardColumns(phone);\n const onCard = new Set<DataTableColumn<T> | null>([primary, ...status, ...fields, ...actions]);\n const table = name ? ` \"${name}\"` : primary ? ` (title column \"${primary.key}\")` : \"\";\n warned.current ??= new Set();\n for (const col of phone) {\n if (onCard.has(col) || !col.noRowLink || warned.current.has(col.key)) continue;\n warned.current.add(col.key);\n console.warn(\n `[ui-kit] DataTable${table}: column \"${col.key}\" (noRowLink) is past the phone card's three fields and ` +\n 'leaves the card — give it role: \"actions\" (drawn after the fields), or move its action to rowActions.',\n );\n }\n }, [columns, enabled, name]);\n}\n\n/**\n * Whether a rendered cell shows nothing worth a line (0.49 KIT-7): `null`, `undefined`,\n * a boolean (React draws neither), `\"\"` or blank text, and the formatters' empty value\n * \"—\" ({@link EMPTY_FORMATTED_VALUE}), and `booleanColumn`'s mark for an unset value.\n * Any other element is never empty here — its output is the component's — so a column\n * whose empty value should drop off the card returns `null` (or \"—\") from `cell` for it.\n */\nexport function isEmptyCell(value: ReactNode): boolean {\n if (value === null || value === undefined || typeof value === \"boolean\") return true;\n if (isValidElement<{ value?: unknown }>(value) && value.type === BooleanMark) return value.props.value == null;\n if (typeof value === \"string\") {\n const text = value.trim();\n return text === \"\" || text === EMPTY_FORMATTED_VALUE;\n }\n return false;\n}\n\n/** Each column's rendered cell, without the empty ones. */\nfunction filledFields<T>(\n columns: readonly DataTableColumn<T>[],\n row: T,\n): { col: DataTableColumn<T>; value: ReactNode }[] {\n return columns.map((col) => ({ col, value: col.cell(row) })).filter(({ value }) => !isEmptyCell(value));\n}\n\n/** A status column's value beside the card's title: text in a neutral chip, an element\n * (the app's own Chip or StatusDot) as it is. */\nfunction StatusChip({ value }: { value: ReactNode }) {\n return typeof value === \"string\" || typeof value === \"number\" ? <Chip size=\"sm\">{value}</Chip> : <>{value}</>;\n}\n\n// ---------- Phone sort control (keksdose K10) ----------\n\n/**\n * The card list's sort control: a \"Sort by\" select and a direction button.\n *\n * keksdose K10 (inputs audit 2026-10-02 §5; its aggregated report tables, #258): a\n * table is sorted by its header row and the phone card list has none, so the two\n * decisions the header carries — which column, which direction — were unreachable\n * below the breakpoint. keksdose hand-built this row over its DataTable to answer\n * \"who did I pay the most\" on the device it was asked on; this is that row, driven by\n * the SAME sort state as the headers (controlled `sorts` included), so turning the\n * phone sideways keeps the order and a sort chosen here is the one the URL and the\n * storage key hold.\n *\n * The rules are the header's: a newly picked column starts in its `firstSort`, the\n * direction button flips the primary sort (secondary ones stay), and \"Default order\"\n * — the third click of a `\"tri\"` cycle — is offered only under `\"tri\"`, since a\n * `\"toggle\"` table promises it is always ranked by something. The select's label is\n * a real `<label>` beside it, and the button sits outside that label: a click inside\n * a label is forwarded to the control it names, so a nested button would flip AND\n * open the select.\n */\nexport function MobileSort<T>({\n columns,\n sorts,\n sortCycle,\n compact,\n labels,\n onPick,\n onFlip,\n}: {\n columns: readonly DataTableColumn<T>[];\n sorts: readonly SortState[];\n sortCycle: SortCycle;\n compact: boolean;\n labels: DataTableLabels;\n onPick: (key: string | null) => void;\n onFlip: () => void;\n}) {\n const selectId = useId();\n const sortLabels: DataTableSortLabels = labels;\n if (columns.length === 0) return null;\n // A persisted or URL sort can name a column with no `sortBy` (or a hidden one): the\n // sort chain drops it, and so does the control rather than claiming it.\n const primary = sorts[0] && columns.some((c) => c.key === sorts[0].key) ? sorts[0] : null;\n const offerDefault = sortCycle === \"tri\" || primary === null;\n const desc = primary?.dir === \"desc\";\n return (\n <div\n data-table-mobile-sort=\"\"\n className={cn(\n // At Large the direction button shows its words (IconButton's label, §4); the\n // row wraps so the select keeps a usable width on a 240 px card.\n \"flex flex-wrap items-center gap-2 border-b border-subtle\",\n compact ? \"px-3 py-1.5\" : \"px-4 py-2\",\n )}\n >\n <label htmlFor={selectId} className=\"shrink-0 text-xs text-muted\">\n {sortLabels.sortBy}\n </label>\n <Select\n id={selectId}\n size={compact ? \"sm\" : \"md\"}\n className=\"min-w-0 flex-1 large:basis-40\"\n value={primary?.key ?? \"\"}\n onChange={(e) => onPick(e.target.value || null)}\n >\n {offerDefault && <option value=\"\">{sortLabels.sortDefault}</option>}\n {columns.map((col) => (\n <option key={col.key} value={col.key}>\n {columnLabel(col, labels)}\n </option>\n ))}\n </Select>\n <IconButton\n size={compact ? \"xs\" : \"md\"}\n variant=\"secondary\"\n label={desc ? sortLabels.sortDescending : sortLabels.sortAscending}\n disabled={primary === null}\n onClick={onFlip}\n >\n {desc ? <ArrowDownWideNarrow /> : <ArrowUpNarrowWide />}\n </IconButton>\n </div>\n );\n}\n\n// ---------- Phone card ----------\n\n/** One phone card: what DataTable's card list draws for a row. */\nexport interface MobileRowProps<T> {\n row: T;\n isExpanded: DataTableProps<T>[\"isExpanded\"];\n /** What the row shows when opened — DataTable's `openedRowContent`. */\n openedRowContent: (row: T) => ReactNode;\n /** Whether an opened row has anything to show at all. */\n opensRow: boolean;\n rowClassName: DataTableProps<T>[\"rowClassName\"];\n rowAttributes: DataTableProps<T>[\"rowAttributes\"];\n onRowClick: DataTableProps<T>[\"onRowClick\"];\n rowHref: DataTableProps<T>[\"rowHref\"];\n /** `mobileSwipeActions`, or the swipes derived from `rowActions`. */\n swipeActionsFor: ((row: T) => MobileSwipeActions | null) | undefined;\n /** The card list's reading direction, for which physical drag a swipe side means. */\n mobileDir: Direction;\n /** No column the card renders owns a link, so the card itself may be one. */\n mobileCardLinkable: boolean;\n /**\n * The card draws cells that keep their own clicks, so it cannot be one control (0.50\n * K4): its TITLE is the row's link (`rowHref`) or button (`onRowClick`), stretched over\n * the card, and those cells sit above it. Only for the default card, never a\n * `mobileCard`, and never when the title itself keeps its own clicks.\n */\n mobileCardStretched: boolean;\n mobileCard: DataTableProps<T>[\"mobileCard\"];\n mobilePrimaryCol: DataTableColumn<T> | null;\n /** The `role: \"status\"` columns, drawn as chips beside the title (0.49 KIT-7). */\n mobileStatusColumns: DataTableColumn<T>[];\n mobileSecondaryColumns: DataTableColumn<T>[];\n /** The `role: \"actions\"` columns, drawn after the fields at the card's end (0.49 KIT-7). */\n mobileActionColumns: DataTableColumn<T>[];\n mobileExpandAsDialog: boolean;\n compact: boolean;\n /** The text size is Large or Extra large (§4). */\n large: boolean;\n /** Phone selection (0.40, keksdose live #421); null without DataTable's `selection`. */\n select: MobileRowSelect | null;\n /** The card's place in the list, on its `<li>` — where the table finds the card that\n * takes the focus when a focused one leaves (refactor audit 2026-10-30, DSP-4). */\n rowIndex?: number;\n /** The focus came into the card, or into anything it opened (a portal included). */\n onFocusIn?: () => void;\n}\n\n/** A phone card's part in DataTable's `selection` (0.40, keksdose live #421). */\nexport interface MobileRowSelect {\n /** The card list is in selection mode: the card shows its box and a tap toggles it. */\n active: boolean;\n /** `selection.isSelectable` for this row. */\n selectable: boolean;\n selected: boolean;\n onToggle: (checked: boolean) => void;\n /** A long press outside the mode: enter it with this row selected. */\n onLongPress: () => void;\n}\n\n/** One mobile card. Shared by the flat list and the grouped list, so both draw\n * identical row markup. */\nexport function MobileRow<T>({\n row,\n isExpanded,\n openedRowContent,\n opensRow,\n rowClassName,\n rowAttributes,\n onRowClick,\n rowHref,\n swipeActionsFor,\n mobileDir,\n mobileCardLinkable,\n mobileCardStretched,\n mobileCard,\n mobilePrimaryCol,\n mobileStatusColumns,\n mobileSecondaryColumns,\n mobileActionColumns,\n mobileExpandAsDialog,\n compact,\n large,\n select,\n rowIndex,\n onFocusIn,\n}: MobileRowProps<T>) {\n const expanded = isExpanded?.(row) ?? false;\n // Selection mode (#421): the whole card is the row's checkbox — a tap toggles it\n // instead of opening the row, and the swipe and the long press stand down, so no\n // gesture means two things at once (keksdose's register list, its feedback #153).\n const selecting = !!select?.active;\n // Not on an open card, as in kk's register: its editor and a selection are two modes\n // fighting over the same row.\n const pressable = !!select && select.selectable && !selecting && !expanded;\n const longPress = useLongPress(() => select?.onLongPress(), { enabled: pressable });\n const expansion = expanded ? openedRowContent(row) : null;\n const tint = rowClassName?.(row);\n // Use div+role=\"button\" rather than a real <button> so cells that\n // contain their own interactive controls (status toggles, action\n // icons) don't end up as illegal nested buttons.\n const interactive = !!onRowClick;\n // `rowHref` without `onRowClick` (refactor audit 2026-10-30, DSP-23 b): nothing opens the\n // row in place, so the card — or its stretched title — is a link that is followed. It\n // was neither, and the phone had no way into the row at all.\n const rowUrl = rowHref?.(row);\n const linkOnly = !interactive && rowUrl !== undefined;\n // Say that the card OPENS something (Keksdose feedback #163). A phone card had\n // only a cursor and a tap-tint to advertise its editor — on the invoice review\n // screen, whose entire purpose is correcting mis-read lines, that read as \"these\n // values are not editable\". A chevron is the affordance every list on a phone\n // uses for exactly this, and it points the way the row actually opens: right into\n // a sheet, or down into an inline panel that flips it when expanded.\n const opensDetail = interactive && opensRow;\n const swipeActions = swipeActionsFor?.(row);\n const swipe = swipeActions ? physicalSwipe(swipeActions, mobileDir) : null;\n // An OPEN card swipes too (0.40, keksdose live #416, Marcel: \"faster than scrolling\n // down to the buttons\"). Only the card's header — the summary line — is inside\n // SwipeableRow; the opened body below is a sibling, so its fields, sliders and\n // scrolling never meet the gesture. Until 0.40 an open card silently lost it.\n // In selection mode a sideways drag would be a second meaning for a selecting touch.\n const swipeEnabled = !!swipe && !selecting;\n // The stretched card (0.50 K4): the title is the control, the card a plain box.\n // Selection mode keeps its own card (the whole card is the toggle there).\n const stretched = mobileCardStretched && (interactive || linkOnly) && !selecting && !mobileCard && !!mobilePrimaryCol;\n const href = mobileCardLinkable || stretched ? rowUrl : undefined;\n // The card opens something on a tap: the row (`onRowClick`), or its link.\n const opens = interactive || (linkOnly && href !== undefined);\n const cardClass = cn(\n \"w-full text-start flex items-center\",\n // §4: more room per row at Large — a taller row is a bigger tap target too.\n compact ? \"px-3 py-2 gap-2 text-sm large:py-3\" : \"px-4 py-3 gap-3 large:py-4\",\n // Live #320's other half: the card acknowledges the touch before the sheet\n // arrives. On a cold route the data can take a beat, and an unacknowledged tap\n // reads as \"did that register?\" — which is most of what \"abrupt\" means here.\n // `origin-center` + a 0.5% squeeze is deliberately almost subliminal: this fires\n // on every row of a long list, so anything larger becomes the list's personality\n // rather than feedback. `transition-transform` alone, so the existing background\n // flip stays instant.\n opens &&\n \"cursor-pointer transition-transform duration-100 active:scale-[0.995] active:bg-active motion-reduce:transition-none motion-reduce:active:scale-100\",\n // The keyboard's frame drawn INSIDE the card, as the desktop row's is (keksdose live\n // #394): the browser's own ring and tap highlight sit on the card's outer edge, and\n // three ancestors clip it — SwipeableRow's `overflow-hidden`, the card list, and\n // AppShell main's `overflow-x-clip` — so the frame went missing on one side. A press\n // keeps the fill and the squeeze above; an outline on every tap of a long list would\n // be louder than the acknowledgement it replaces.\n (opens || selecting) &&\n !stretched &&\n cn(\"[-webkit-tap-highlight-color:transparent] focus-visible:ring-inset\", FOCUS_RING),\n // A stretched card (0.50 K4) draws the same frame, inside the card, while its TITLE\n // control has the keyboard: the ring outlines the whole tap target, as on every other\n // card, not the title's line alone. The own-click cells draw their own.\n stretched && cn(\"relative [-webkit-tap-highlight-color:transparent]\", STRETCHED_RING),\n selecting && \"cursor-pointer\",\n select?.selected && selecting && \"bg-brand-soft\",\n // A 500 ms hold would otherwise start a text selection, and iOS's link callout —\n // which only yields to this CSS. The trade kk made on its register: the card's text\n // is not selectable on a phone where the table offers selection.\n select && \"select-none [-webkit-touch-callout:none]\",\n );\n // The card's content is written once and worn by either tag below. Note that\n // the primary cell is rendered RAW here, never through `linkColumn` — the card\n // itself is the anchor, and routing it through `linkColumn` too would nest an\n // <a> inside an <a>. Desktop and mobile are mutually exclusive (`isMdUp`), so\n // exactly one anchor exists per row.\n // The default card's filled fields and status values (0.49 KIT-7): an empty one\n // gets no line. Not computed for a `mobileCard`, which draws its own.\n // The stretched card's title control (0.50 K4): the row's link, or a real <button>\n // when the table only has `onRowClick` — a plain control, since nothing interactive\n // sits inside it any more. Its `::after` covers the whole card, so a tap anywhere\n // but on an own-click cell (lifted above it) lands on it, and a long press on the\n // card is a long press on the link (\"open in new tab\"). Its accessible name is the\n // title alone; the fields read as text after it. `data-row-link` marks it as the\n // row itself for `fromOwnControl`, so the long press still arms on it.\n const stretchedClass =\n \"block w-full text-start focus-visible:outline-none after:absolute after:inset-0 after:z-[1] after:content-['']\";\n const titleControl = (title: ReactNode) =>\n href ? (\n <RowLink\n href={href}\n data-card-link=\"\"\n data-row-link=\"\"\n navigate={linkOnly}\n onActivate={interactive ? () => onRowClick!(row) : undefined}\n aria-expanded={opensRow && interactive ? expanded : undefined}\n className={stretchedClass}\n // Enter follows RowLink's own onClick; Space opens the row too, not the list's scroll.\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key !== \" \") return;\n e.preventDefault();\n onRowClick!(row);\n }\n : undefined\n }\n >\n {title}\n </RowLink>\n ) : (\n <button\n type=\"button\"\n data-card-link=\"\"\n data-row-link=\"\"\n aria-expanded={opensRow ? expanded : undefined}\n onClick={() => onRowClick!(row)}\n className={cn(stretchedClass, \"font-[inherit]\")}\n >\n {title}\n </button>\n );\n const fields = mobileCard ? [] : filledFields(mobileSecondaryColumns, row);\n const statuses = mobileCard ? [] : filledFields(mobileStatusColumns, row);\n const actionCells = mobileCard ? [] : filledFields(mobileActionColumns, row);\n const cardInner = (\n <>\n {/* The card's own content keeps the column it always had; the chevron sits\n beside it rather than inside, so a caller's `mobileCard` is untouched. */}\n <div\n className={cn(\n \"flex min-w-0 flex-1 flex-col\",\n compact ? \"gap-1 large:gap-2\" : \"gap-2 large:gap-3\",\n // §4 \"nothing truncates\": at Large a cell's own `truncate` wraps instead —\n // the reader who chose bigger type is the one an ellipsis fails. A caller's\n // `mobileCard` is its own layout and keeps what it chose.\n !mobileCard && CARD_NO_TRUNCATE,\n )}\n >\n {mobileCard ? (\n mobileCard(row)\n ) : (\n <>\n {/* Only the `data-*` half of `cellProps` — see its note. The title row\n carries the status chips at its end (0.49 KIT-7). */}\n {(mobilePrimaryCol || statuses.length > 0) && (\n <div data-card-head=\"\" className=\"flex min-w-0 items-start justify-between gap-2\">\n {mobilePrimaryCol && (\n <div\n {...dataAttrsOf(mobilePrimaryCol.cellProps?.(row))}\n data-card-title=\"\"\n className=\"min-w-0 flex-1 break-words font-medium\"\n >\n {stretched ? titleControl(mobilePrimaryCol.cell(row)) : mobilePrimaryCol.cell(row)}\n </div>\n )}\n {statuses.length > 0 && (\n <div className=\"flex shrink-0 flex-wrap items-center justify-end gap-1\">\n {statuses.map(({ col, value }) => (\n <span\n key={col.key}\n {...dataAttrsOf(col.cellProps?.(row))}\n data-card-status=\"\"\n className=\"inline-flex\"\n >\n <StatusChip value={value} />\n </span>\n ))}\n </div>\n )}\n </div>\n )}\n {fields.length > 0 && large ? (\n // §4: each label ABOVE its value at Large. Side by side, a label column\n // sized to the widest label leaves a value a sliver of a 240 px card.\n <StackedFields columns={mobileSecondaryColumns} row={row} compact={compact} liftOwnClicks={stretched} />\n ) : (\n fields.length > 0 && (\n <dl\n className={cn(\"grid grid-cols-[auto_1fr] gap-x-3\", compact ? \"gap-y-0.5 text-xs\" : \"gap-y-1 text-sm\")}\n >\n {fields.map(({ col, value }) => (\n <Fragment key={col.key}>\n <dt className=\"text-xs uppercase tracking-wide text-muted self-center\">{col.header}</dt>\n <dd\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\n \"min-w-0 text-secondary self-center\",\n stretched && keepsOwnClicks(col) && LIFTED_FIELD,\n )}\n >\n {value}\n </dd>\n </Fragment>\n ))}\n </dl>\n )\n )}\n {/* The row's actions after the fields, at the card's end (0.49 KIT-7): its\n buttons keep their own clicks, and RowActions folds into \"⋯\" at Large. */}\n {actionCells.length > 0 && (\n <div data-card-actions=\"\" className=\"flex min-w-0 flex-wrap items-center justify-end gap-2\">\n {actionCells.map(({ col, value }) => (\n <div\n key={col.key}\n {...dataAttrsOf(col.cellProps?.(row))}\n className={cn(\"flex min-w-0 justify-end\", stretched && LIFTED_CELL)}\n >\n {value}\n </div>\n ))}\n </div>\n )}\n </>\n )}\n </div>\n {opensDetail &&\n !selecting &&\n // `aria-hidden`: the row already announces itself as a button with\n // `aria-expanded`, so the icon would only add a second, wordless stop.\n (mobileExpandAsDialog ? (\n // Points INTO the sheet, which is the reading-forward direction.\n <ChevronRight aria-hidden className=\"size-4 shrink-0 text-placeholder rtl:-scale-x-100\" />\n ) : (\n <ChevronDown\n aria-hidden\n className={cn(\"size-4 shrink-0 text-placeholder transition-transform\", expanded && \"rotate-180\")}\n />\n ))}\n </>\n );\n // The long press rides the card itself; the click it leaves behind is swallowed on\n // the `<li>` below, in the capture phase, before the card (or RowLink) sees it.\n // Attached whenever the table selects, the hook's `enabled` deciding whether a press\n // arms: its pointerdown also clears a stale swallow latch, which must happen in the\n // mode too, or a long press whose click never came would eat the first toggle.\n //\n // A press that starts on a control in a cell (or in a portal one opened) is that\n // control's, not a way into the mode (keksdose live #428): it only clears the latch.\n const press = select\n ? {\n ...longPress.handlers,\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => {\n if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) {\n longPress.consumeClick();\n return;\n }\n longPress.handlers.onPointerDown(e);\n },\n onContextMenu: (e: ReactMouseEvent<HTMLElement>) => {\n if (fromOutside(e.target, e.currentTarget) || fromOwnControl(e.target, e.currentTarget)) return;\n longPress.handlers.onContextMenu(e);\n },\n }\n : undefined;\n const body = selecting ? (\n // In the mode a card is a toggle — kk's register row: `role=\"button\"` with\n // `aria-pressed`, the box beside it only a picture of that state (the card is the\n // one control, so the two can never fight or fire twice).\n /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- role=\"button\",\n tabIndex 0 and Enter/Space are all here; the rule cannot follow the conditional role. */\n <div\n role={select!.selectable ? \"button\" : undefined}\n tabIndex={select!.selectable ? 0 : undefined}\n aria-pressed={select!.selectable ? select!.selected : undefined}\n aria-disabled={select!.selectable ? undefined : true}\n data-card-selecting=\"\"\n {...press}\n onClick={(e) => {\n if (!select!.selectable || fromOutside(e.target, e.currentTarget)) return;\n // A tap on a control in a cell is that control's (keksdose live #428).\n if (fromOwnControl(e.target, e.currentTarget)) return;\n select!.onToggle(!select!.selected);\n }}\n onKeyDown={(e) => {\n if (!select!.selectable || e.target !== e.currentTarget) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n select!.onToggle(!select!.selected);\n }\n }}\n className={cardClass}\n >\n <Checkbox\n readOnly\n tabIndex={-1}\n aria-hidden\n checked={select!.selected}\n disabled={!select!.selectable}\n className=\"pointer-events-none shrink-0 self-center\"\n inputClassName=\"pointer-events-none\"\n />\n {cardInner}\n </div>\n ) : stretched ? (\n // The stretched card (0.50 K4): a plain box; its title is the control (above).\n <div {...press} data-card-stretched=\"\" className={cardClass}>\n {cardInner}\n </div>\n ) : opens && href ? (\n // A card with a URL of its own is a LINK, not a div wearing role=\"button\"\n // (feedback #451): the phone's long-press \"open in new tab\" and a screen\n // reader's link semantics both come free, and the fake role goes away.\n <RowLink\n href={href}\n {...press}\n navigate={linkOnly}\n onActivate={interactive ? () => onRowClick!(row) : undefined}\n aria-expanded={opensRow && interactive ? expanded : undefined}\n className={cardClass}\n // Enter already activates a link natively, and that routes through\n // RowLink's own onClick — handling it here too would open the row twice.\n // Space is the only key left, and only so it opens the row instead of\n // scrolling the list.\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key !== \" \") return;\n e.preventDefault();\n onRowClick!(row);\n }\n : undefined\n }\n >\n {cardInner}\n </RowLink>\n ) : (\n /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- the handlers are\n only attached when `interactive`, and then so are role=\"button\", tabIndex 0 and the\n Enter/Space handler below; the rule cannot follow the conditional role. */\n <div\n {...press}\n role={interactive ? \"button\" : undefined}\n tabIndex={interactive ? 0 : undefined}\n onClick={\n interactive\n ? (e) => {\n if (fromOutside(e.target, e.currentTarget)) return;\n // A tap on a control in a cell (a select, a button) is that\n // control's, as on the desktop `<tr>` (keksdose live #428).\n if (fromOwnControl(e.target, e.currentTarget)) return;\n onRowClick!(row);\n }\n : undefined\n }\n onKeyDown={\n interactive\n ? (e) => {\n // A key press inside a nested control is that control's.\n if (e.target !== e.currentTarget) return;\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onRowClick!(row);\n }\n }\n : undefined\n }\n aria-expanded={opensRow ? expanded : undefined}\n className={cardClass}\n >\n {cardInner}\n </div>\n );\n return (\n /* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- `onFocus`\n only notes that the focus is in this card (DSP-4); the card inside is the control. */\n <li\n className={cn(tint)}\n {...cleanAttrs(rowAttributes?.(row))}\n data-row-index={rowIndex}\n onFocus={onFocusIn}\n data-selected={selecting && select!.selected ? \"\" : undefined}\n onClickCapture={\n select\n ? (e) => {\n if (!longPress.consumeClick()) return;\n e.preventDefault();\n e.stopPropagation();\n }\n : undefined\n }\n >\n {swipeEnabled ? (\n <SwipeableRow enabled left={swipe!.left} right={swipe!.right}>\n {body}\n </SwipeableRow>\n ) : (\n body\n )}\n {expansion && !mobileExpandAsDialog && (\n <div className={cn(\"bg-surface-2 border-t border-subtle\", compact ? \"px-3 py-2\" : \"px-4 py-3\")}>\n {expansion}\n </div>\n )}\n </li>\n );\n}\n\n// ---------- The card list's leading row (0.51.7, audit 2026-10-30 DSP-2) ----------\n\n/**\n * DataTable's `leadingRow` on a phone: the card list's first item. It was desktop-only\n * and the card list dropped it without a word — kastlan's inline \"Add meter\" editor\n * never appeared (the page's button disabled itself, its only create path gone), and\n * keksdose kept an `md:hidden` copy of its hidden-accounts toggle above the table.\n *\n * A plain `<li>`: the list is a `<ul>`, and axe's `list` rule fails an `<li\n * role=\"none\">` in one (checked against axe 4.13), so it is one more item, as the\n * section headers, the reveal sentinel and the load-more footer are. It is no card — no\n * row link, swipe or long press, no toggle in selection mode, no `rowAttributes` — and\n * selection, the reveal and the row count work on the rows, not on the list's items.\n *\n * `sticky`: pinned at the top of whatever scrolls the list (the list itself under a\n * bounded `fillHeight`, else the page), opaque over the cards passing beneath it, and\n * over the section headers (`z-10`) — which stand under it, at the height `onHeight`\n * reports, measured and followed (an inline editor wraps at Large text). The desktop\n * stacks the same way: its leading row over the group bands.\n */\nexport function MobileLeadingRow({\n sticky,\n onHeight,\n children,\n}: {\n sticky: boolean;\n /** Under `sticky`: the row's height, and each change of it. */\n onHeight?: (height: number) => void;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLLIElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!sticky || !onHeight || !el) return;\n const measure = () => onHeight(el.getBoundingClientRect().height);\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [sticky, onHeight]);\n return (\n <li ref={ref} data-leading-row={sticky ? \"sticky\" : \"\"} className={cn(sticky && \"sticky top-0 z-[11] bg-surface\")}>\n {children}\n </li>\n );\n}\n\n// ---------- Phone selection bar (0.40, keksdose live #421) ----------\n\n/**\n * The card list's way into and out of selection mode for a keyboard and a screen\n * reader — the long press is a touch gesture, and a gesture alone is no path at all.\n *\n * Outside the mode it is one button, \"Select rows\", visually hidden until it takes\n * focus (SwipeableRow's keyboard buttons, the same rule): the card list keeps the look\n * kk's register has, where the long press is the way in, and Tab or a screen reader's\n * cursor still finds it. In the mode it is a visible row: \"Select all rows\" — the\n * desktop header's box — and Cancel, which clears the selection and leaves, as the\n * bulk bar's Clear does. The owner's bulk bar is the owner's, as on desktop: it\n * appears because the selection is no longer empty.\n */\nexport function MobileSelectBar({\n active,\n allSelected,\n someSelected,\n compact,\n labels,\n onEnter,\n onToggleAll,\n onCancel,\n}: {\n active: boolean;\n allSelected: boolean;\n someSelected: boolean;\n compact: boolean;\n labels: DataTableLabels;\n onEnter: () => void;\n onToggleAll: (checked: boolean) => void;\n onCancel: () => void;\n}) {\n // The control that was pressed unmounts with the mode it switched, so focus is\n // handed across: into the bar's box on the way in, back to the button on the way out.\n const handOff = useRef(false);\n const allBox = useRef<HTMLInputElement>(null);\n const enterButton = useRef<HTMLButtonElement>(null);\n useEffect(() => {\n if (!handOff.current) return;\n handOff.current = false;\n (active ? allBox.current : enterButton.current)?.focus();\n }, [active]);\n if (!active)\n return (\n <button\n ref={enterButton}\n type=\"button\"\n data-table-mobile-select=\"\"\n onClick={() => {\n handOff.current = true;\n onEnter();\n }}\n className={cn(\n \"sr-only rounded-md text-sm font-medium text-secondary\",\n // Padding only once shown: unconditional, it beats sr-only's `padding: 0`\n // (SwipeableRow's 0.32.1 note).\n \"focus:not-sr-only focus:m-2 focus:inline-flex focus:px-2 focus:py-1\",\n FOCUS_RING,\n )}\n >\n {labels.selectRows}\n </button>\n );\n return (\n <div\n data-table-mobile-select=\"\"\n className={cn(\n \"flex flex-wrap items-center justify-between gap-2 border-b border-subtle\",\n compact ? \"px-3 py-1.5\" : \"px-4 py-2\",\n )}\n >\n <Checkbox\n ref={allBox}\n label={labels.selectAllRows}\n checked={allSelected}\n indeterminate={someSelected && !allSelected}\n onChange={(e) => onToggleAll(e.target.checked)}\n />\n <button\n type=\"button\"\n onClick={() => {\n handOff.current = true;\n onCancel();\n }}\n className={cn(\"rounded-md px-2 py-1 text-sm text-muted hover:text-secondary\", FOCUS_RING)}\n >\n {labels.cancelSelection}\n </button>\n </div>\n );\n}\n\n// ---------- Mobile filter sheet ----------\n\n/**\n * Filter access for the mobile card list (feedback #299). The desktop table\n * houses per-column filters in its header; the card list has no header, so the\n * same filters were unreachable on phones. This renders a compact \"Filters\" bar\n * that opens a bottom sheet listing each filterable column as a collapsible\n * section, reusing the very same {@link FilterPopover} and filter state the\n * desktop table drives — so it filters identically and stays URL-synced.\n *\n * The sheet is a {@link DialogFrame} (round 0.34 BUG-7b = DUP-6). It was the last\n * hand-rolled modal in the kit: `role=\"dialog\" aria-modal=\"true\"` on the backdrop, with\n * copies of the scroll lock, the backdrop close and the Back entry — and no name, no\n * focus moved in, no trap and no Escape, so a reader was told the page was hidden while\n * focus stayed on it. On a phone `Modal` is a bottom sheet already; it now brings all\n * of that, and the exit motion, from the one place every other dialog gets it.\n */\nexport function MobileFilters<T>({\n columns,\n filters,\n selectOptionsByKey,\n isServer,\n hasFilterCallback,\n onSetFilter,\n onClearFilter,\n onClearAll,\n labels,\n locale,\n}: {\n columns: readonly DataTableColumn<T>[];\n filters: FilterState;\n selectOptionsByKey: Record<string, { value: string; label: string }[]>;\n isServer: boolean;\n hasFilterCallback: boolean;\n onSetFilter: (key: string, value: FilterValue) => void;\n onClearFilter: (key: string) => void;\n onClearAll: () => void;\n labels: DataTableLabels;\n locale?: string;\n}) {\n const [open, setOpen] = useState(false);\n // The sheet is portalled to <body>, out from under whatever `dir` the table sits\n // in, so it carries the trigger's direction with it — read at the moment of opening,\n // from the element the user actually pressed.\n const [dir, setDir] = useState<Direction>(\"ltr\");\n const [expanded, setExpanded] = useState<string | null>(null);\n\n const filterable = useMemo(\n () =>\n columns\n .map((col) => ({ col, filter: !isServer || hasFilterCallback ? resolveFilter(col) : null }))\n .filter((x): x is { col: DataTableColumn<T>; filter: ColumnFilter<T> } => !!x.filter),\n [columns, isServer, hasFilterCallback],\n );\n\n if (filterable.length === 0) return null;\n\n const activeCount = filterable.filter(({ col }) => {\n const s = filters[col.key];\n return s && isFilterActive(s);\n }).length;\n\n return (\n <div className=\"flex items-center justify-between gap-2 border-b border-subtle px-4 py-2\">\n <button\n type=\"button\"\n onClick={(e) => {\n setDir(dirOf(e.currentTarget));\n setOpen(true);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm font-medium text-secondary hover:bg-hover\"\n >\n <Filter className=\"size-4\" />\n <span>{labels.filters}</span>\n {activeCount > 0 && (\n <span className=\"inline-flex min-w-4 items-center justify-center rounded-full bg-info-soft px-1 text-caption font-semibold text-info\">\n {activeCount}\n </span>\n )}\n </button>\n {activeCount > 0 && (\n <button type=\"button\" onClick={onClearAll} className=\"text-xs text-muted hover:text-secondary\">\n {labels.clearAll}\n </button>\n )}\n {/* Escape, the backdrop, Back (Keksdose feedback #172: the filters are already\n in the URL, so closing never loses anything), the scroll lock, focus in and\n back to this bar's button — all `Modal`'s. `fullBleed` lets the sheet meet the\n screen's edges on a phone, as it always did. */}\n <DialogFrame\n open={open}\n onClose={() => setOpen(false)}\n title={labels.filters}\n closeButton\n closeLabel={labels.close}\n headerDivider\n fullBleed\n // Portalled to <body>, so the direction read from the button goes with it.\n dir={dir}\n className=\"max-h-[85vh] rounded-b-none md:rounded-b-lg\"\n // The sections run edge to edge with a rule between them, as before.\n bodyClassName=\"space-y-0 divide-y divide-subtle px-0 pt-0 pb-0\"\n actions={(close) => (\n <>\n <button\n type=\"button\"\n onClick={onClearAll}\n disabled={activeCount === 0}\n // `me-auto` keeps Clear all at the start and Done at the end.\n className=\"me-auto text-sm text-muted hover:text-secondary disabled:opacity-40\"\n >\n {labels.clearAll}\n </button>\n <button\n type=\"button\"\n onClick={close}\n className=\"rounded-md bg-inverse px-4 py-2 text-sm font-medium text-inverse\"\n >\n {labels.done}\n </button>\n </>\n )}\n >\n {filterable.map(({ col, filter }) => {\n const state = filters[col.key];\n const active = !!state && isFilterActive(state);\n const isItemOpen = expanded === col.key;\n return (\n <div key={col.key}>\n <button\n type=\"button\"\n onClick={() => setExpanded(isItemOpen ? null : col.key)}\n aria-expanded={isItemOpen}\n className=\"flex w-full items-center justify-between gap-2 px-4 py-3 text-start\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium text-secondary\">\n {col.header}\n {active && <span className=\"size-1.5 rounded-full bg-brand\" />}\n </span>\n <ChevronDown\n className={cn(\"size-4 shrink-0 text-placeholder transition-transform\", isItemOpen && \"rotate-180\")}\n />\n </button>\n {isItemOpen && (\n <div className=\"px-4 pb-3\">\n <FilterPopover\n column={col}\n state={state ?? defaultFilterState(filter)}\n onChange={(next) => onSetFilter(col.key, next)}\n onClear={() => onClearFilter(col.key)}\n selectOptions={selectOptionsByKey[col.key] ?? []}\n labels={labels}\n locale={locale}\n />\n </div>\n )}\n </div>\n );\n })}\n </DialogFrame>\n </div>\n );\n}\n"],"mappings":"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","names":["Fragment"]}
|
|
@@ -17,6 +17,7 @@ const PAGE_WINDOW = 2;
|
|
|
17
17
|
const PAGE_WINDOW_LARGE_PHONE = 1;
|
|
18
18
|
const PAGE_STRIP = "flex min-w-0 max-w-full items-center gap-1";
|
|
19
19
|
const PAGE_NUMBERS = "flex min-w-0 flex-wrap items-center justify-center gap-1";
|
|
20
|
+
const ARROW = "rounded hover:bg-hover aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent";
|
|
20
21
|
function Pagination({
|
|
21
22
|
page,
|
|
22
23
|
totalPages,
|
|
@@ -79,9 +80,11 @@ function Pagination({
|
|
|
79
80
|
"button",
|
|
80
81
|
{
|
|
81
82
|
type: "button",
|
|
82
|
-
onClick: () =>
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
onClick: () => {
|
|
84
|
+
if (page > 0) onPage(page - 1);
|
|
85
|
+
},
|
|
86
|
+
"aria-disabled": page === 0 || void 0,
|
|
87
|
+
className: cn(ARROW, compact ? "p-0.5" : "p-1"),
|
|
85
88
|
"aria-label": labels.prevPage,
|
|
86
89
|
children: /* @__PURE__ */ jsx(ChevronLeft, { className: cn("rtl:-scale-x-100", compact ? "size-3.5" : "size-4") })
|
|
87
90
|
}
|
|
@@ -107,9 +110,11 @@ function Pagination({
|
|
|
107
110
|
"button",
|
|
108
111
|
{
|
|
109
112
|
type: "button",
|
|
110
|
-
onClick: () =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
onClick: () => {
|
|
114
|
+
if (page < totalPages - 1) onPage(page + 1);
|
|
115
|
+
},
|
|
116
|
+
"aria-disabled": page >= totalPages - 1 || void 0,
|
|
117
|
+
className: cn(ARROW, compact ? "p-0.5" : "p-1"),
|
|
113
118
|
"aria-label": labels.nextPage,
|
|
114
119
|
children: /* @__PURE__ */ jsx(ChevronRight, { className: cn("rtl:-scale-x-100", compact ? "size-3.5" : "size-4") })
|
|
115
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/data-table-pagination.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { usePhoneLayout } from \"../hooks/use-breakpoint\";\nimport { useLargeText } from \"../hooks/use-large-text\";\n\nexport const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;\n\n/** The page-size steps, with the current size slotted in when it is not one of them. */\nfunction sizeOptions(pageSize: number): number[] {\n const steps: number[] = [...PAGE_SIZE_OPTIONS];\n if (!Number.isFinite(pageSize) || steps.includes(pageSize)) return steps;\n return [...steps, pageSize].sort((a, b) => a - b);\n}\n\ninterface PaginationProps {\n page: number;\n totalPages: number;\n pageSize: number;\n total: number;\n onPage: (p: number) => void;\n /** Omit when the page size is not the user's to change (a server that fixes it):\n * the select is then not rendered at all, rather than rendered and inert. */\n onPageSize?: (n: number) => void;\n /** Over `dataTable` from `<UiKitProvider labels>`, over the English defaults. */\n labels?: LabelOverride<DataTableLabels>;\n /** For the page numbers and page-size options; falls back to the provider's. */\n locale?: string;\n /** `\"compact\"` for a pager under a compact {@link DataTable} (lenkbank's in-card\n * tables): a slimmer footer and page buttons, so the pager is not the tallest row\n * of a table that was made compact on purpose. */\n density?: \"comfortable\" | \"compact\";\n}\n\n/**\n * How many page numbers flank the current one on the strip: ±2, and ±1 on a phone at\n * Large and Extra large (0.32.1, the 360 px Extra-large sweep; docs/text-size-\n * harmonization.md §4). At 150 % a number button is 42 px, and \"‹ 1 2 3 … 494 ›\" was\n * 312 px on a 360 px phone's 262; one neighbour fewer on each side saves two buttons.\n */\nconst PAGE_WINDOW = 2;\nconst PAGE_WINDOW_LARGE_PHONE = 1;\n\n/**\n * The strip's two parts (0.32.1, same sweep, §4 \"nothing overflows\"). The arrows keep\n * the ends; the numbers between them wrap, centred, when even the narrower window does\n * not fit — a middle page of a long list (\"1 … 249 250 251 … 494\") at Extra large, or at\n * Normal on a narrow phone, where the one-row strip ran past the screen. Where the\n * strip fits it is one row, as before.\n */\nconst PAGE_STRIP = \"flex min-w-0 max-w-full items-center gap-1\";\nconst PAGE_NUMBERS = \"flex min-w-0 flex-wrap items-center justify-center gap-1\";\n\n/** Footer for {@link DataTable}: range summary, page-size select, and a\n * windowed page-number strip (first/last + ±2 around the current page, ±1 on a phone\n * at Large — see {@link PAGE_WINDOW}). */\nexport function Pagination({\n page,\n totalPages,\n pageSize,\n total,\n onPage,\n onPageSize,\n labels: labelsProp,\n locale: localeProp,\n density = \"comfortable\",\n}: PaginationProps) {\n const compact = density === \"compact\";\n // prop > provider > English, like every other kit component. This defaulted straight\n // to the English object, so a standalone pager under a translated provider still\n // said \"Rows per page\" — only the pager INSIDE a DataTable was translated, because\n // the table resolved the provider's labels and handed them down.\n const labels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n // The bare numbers on the strip and in the select are text too: `String(n)` is\n // always ASCII digits, which is wrong for a locale that writes its own. The range\n // summary is not formatted here — `pageRange` / `rowCount` take NUMBERS so the\n // translation can format them itself, alongside the words around them.\n const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const large = useLargeText();\n const phone = usePhoneLayout();\n const radius = large && phone ? PAGE_WINDOW_LARGE_PHONE : PAGE_WINDOW;\n const visible = useMemo(() => {\n // Every page when that is no longer than the window would be (first, last, the\n // current one, its neighbours and two ellipses).\n if (totalPages <= 2 * radius + 3) return Array.from({ length: totalPages }, (_, i) => i);\n const set = new Set<number>([0, totalPages - 1, page]);\n for (let d = 1; d <= radius; d++) {\n if (page - d >= 0) set.add(page - d);\n if (page + d <= totalPages - 1) set.add(page + d);\n }\n return Array.from(set).sort((a, b) => a - b);\n }, [page, totalPages, radius]);\n\n const items: (number | \"ellipsis\")[] = [];\n visible.forEach((p, idx) => {\n if (idx > 0 && p - visible[idx - 1] > 1) items.push(\"ellipsis\");\n items.push(p);\n });\n\n return (\n <div\n data-density={density}\n className={cn(\n \"flex flex-wrap items-center justify-between gap-2 border-t border-subtle text-xs text-secondary\",\n compact ? \"px-2 py-1\" : \"px-3 py-2\",\n )}\n >\n <div className=\"flex items-center gap-2\">\n <span>\n {/* No rows is a count, not a range: the range read \"1–0 / 0\" (round 0.34 BUG-14). */}\n {pageSize === Infinity || total === 0\n ? labels.rowCount(total)\n : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total)}\n </span>\n {onPageSize && (\n <select\n value={pageSize === Infinity ? \"all\" : pageSize}\n onChange={(e) => onPageSize(e.target.value === \"all\" ? Infinity : Number(e.target.value))}\n className=\"rounded border border-subtle bg-surface px-1 py-0.5 text-xs\"\n aria-label={labels.pageSize}\n >\n {/* A `defaultPageSize` that is not one of the steps (keksdose's 5) is offered\n too — a select whose value has no option shows its first one (\"10\"). */}\n {sizeOptions(pageSize).map((n) => (\n <option key={n} value={n}>\n {num.format(n)}\n </option>\n ))}\n <option value=\"all\">{labels.pageSizeAll}</option>\n </select>\n )}\n </div>\n {pageSize !== Infinity && totalPages > 1 && (\n <div data-slot=\"page-strip\" className={PAGE_STRIP}>\n <button\n type=\"button\"\n onClick={() => onPage(Math.max(0, page - 1))}\n disabled={page === 0}\n className={cn(\"rounded hover:bg-hover disabled:opacity-40\", compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.prevPage}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n <div data-slot=\"page-numbers\" className={PAGE_NUMBERS}>\n {items.map((it, i) =>\n it === \"ellipsis\" ? (\n <span key={`e${i}`} className=\"px-1 text-placeholder\">\n …\n </span>\n ) : (\n <button\n key={it}\n type=\"button\"\n onClick={() => onPage(it)}\n // Which page you are on is carried only by a background colour\n // otherwise, and the strip is a row of bare numbers with nothing\n // else to tell them apart.\n aria-current={it === page ? \"page\" : undefined}\n className={cn(\n \"rounded\",\n compact ? \"min-w-6 px-1.5 py-0\" : \"min-w-7 px-2 py-0.5\",\n it === page ? \"bg-inverse text-inverse\" : \"hover:bg-hover\",\n )}\n >\n {num.format(it + 1)}\n </button>\n ),\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => onPage(Math.min(totalPages - 1, page + 1))}\n disabled={page >= totalPages - 1}\n className={cn(\"rounded hover:bg-hover disabled:opacity-40\", compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.nextPage}\n >\n <ChevronRight className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/data-table-pagination.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { cn } from \"../lib/cn\";\nimport { DEFAULT_DATA_TABLE_LABELS, type DataTableLabels } from \"./data-table-labels\";\nimport { useKitLabels, useKitLocale, type LabelOverride } from \"../i18n/kit-labels\";\nimport { usePhoneLayout } from \"../hooks/use-breakpoint\";\nimport { useLargeText } from \"../hooks/use-large-text\";\n\nexport const PAGE_SIZE_OPTIONS = [10, 25, 50, 100, 200] as const;\n\n/** The page-size steps, with the current size slotted in when it is not one of them. */\nfunction sizeOptions(pageSize: number): number[] {\n const steps: number[] = [...PAGE_SIZE_OPTIONS];\n if (!Number.isFinite(pageSize) || steps.includes(pageSize)) return steps;\n return [...steps, pageSize].sort((a, b) => a - b);\n}\n\ninterface PaginationProps {\n page: number;\n totalPages: number;\n pageSize: number;\n total: number;\n onPage: (p: number) => void;\n /** Omit when the page size is not the user's to change (a server that fixes it):\n * the select is then not rendered at all, rather than rendered and inert. */\n onPageSize?: (n: number) => void;\n /** Over `dataTable` from `<UiKitProvider labels>`, over the English defaults. */\n labels?: LabelOverride<DataTableLabels>;\n /** For the page numbers and page-size options; falls back to the provider's. */\n locale?: string;\n /** `\"compact\"` for a pager under a compact {@link DataTable} (lenkbank's in-card\n * tables): a slimmer footer and page buttons, so the pager is not the tallest row\n * of a table that was made compact on purpose. */\n density?: \"comfortable\" | \"compact\";\n}\n\n/**\n * How many page numbers flank the current one on the strip: ±2, and ±1 on a phone at\n * Large and Extra large (0.32.1, the 360 px Extra-large sweep; docs/text-size-\n * harmonization.md §4). At 150 % a number button is 42 px, and \"‹ 1 2 3 … 494 ›\" was\n * 312 px on a 360 px phone's 262; one neighbour fewer on each side saves two buttons.\n */\nconst PAGE_WINDOW = 2;\nconst PAGE_WINDOW_LARGE_PHONE = 1;\n\n/**\n * The strip's two parts (0.32.1, same sweep, §4 \"nothing overflows\"). The arrows keep\n * the ends; the numbers between them wrap, centred, when even the narrower window does\n * not fit — a middle page of a long list (\"1 … 249 250 251 … 494\") at Extra large, or at\n * Normal on a narrow phone, where the one-row strip ran past the screen. Where the\n * strip fits it is one row, as before.\n */\nconst PAGE_STRIP = \"flex min-w-0 max-w-full items-center gap-1\";\nconst PAGE_NUMBERS = \"flex min-w-0 flex-wrap items-center justify-center gap-1\";\n\n/**\n * The arrows at the ends of the strip are `aria-disabled`, not `disabled` (refactor audit\n * 2026-10-30, DSP-12): Next pressed onto the last page — or Previous onto the first —\n * is still the focused button, and a button that turns `disabled` drops the focus it\n * holds to `<body>`. JumpCalendar's arrows keep the same rule, and the table's\n * `isLoading` pager the same reason (`ServerPagination.isLoading`). Dimmed as before; a\n * press there does nothing.\n */\nconst ARROW =\n \"rounded hover:bg-hover aria-disabled:cursor-not-allowed aria-disabled:opacity-40 aria-disabled:hover:bg-transparent\";\n\n/** Footer for {@link DataTable}: range summary, page-size select, and a\n * windowed page-number strip (first/last + ±2 around the current page, ±1 on a phone\n * at Large — see {@link PAGE_WINDOW}). */\nexport function Pagination({\n page,\n totalPages,\n pageSize,\n total,\n onPage,\n onPageSize,\n labels: labelsProp,\n locale: localeProp,\n density = \"comfortable\",\n}: PaginationProps) {\n const compact = density === \"compact\";\n // prop > provider > English, like every other kit component. This defaulted straight\n // to the English object, so a standalone pager under a translated provider still\n // said \"Rows per page\" — only the pager INSIDE a DataTable was translated, because\n // the table resolved the provider's labels and handed them down.\n const labels = useKitLabels(\"dataTable\", DEFAULT_DATA_TABLE_LABELS, labelsProp);\n const locale = useKitLocale(localeProp);\n // The bare numbers on the strip and in the select are text too: `String(n)` is\n // always ASCII digits, which is wrong for a locale that writes its own. The range\n // summary is not formatted here — `pageRange` / `rowCount` take NUMBERS so the\n // translation can format them itself, alongside the words around them.\n const num = useMemo(() => new Intl.NumberFormat(locale), [locale]);\n const large = useLargeText();\n const phone = usePhoneLayout();\n const radius = large && phone ? PAGE_WINDOW_LARGE_PHONE : PAGE_WINDOW;\n const visible = useMemo(() => {\n // Every page when that is no longer than the window would be (first, last, the\n // current one, its neighbours and two ellipses).\n if (totalPages <= 2 * radius + 3) return Array.from({ length: totalPages }, (_, i) => i);\n const set = new Set<number>([0, totalPages - 1, page]);\n for (let d = 1; d <= radius; d++) {\n if (page - d >= 0) set.add(page - d);\n if (page + d <= totalPages - 1) set.add(page + d);\n }\n return Array.from(set).sort((a, b) => a - b);\n }, [page, totalPages, radius]);\n\n const items: (number | \"ellipsis\")[] = [];\n visible.forEach((p, idx) => {\n if (idx > 0 && p - visible[idx - 1] > 1) items.push(\"ellipsis\");\n items.push(p);\n });\n\n return (\n <div\n data-density={density}\n className={cn(\n \"flex flex-wrap items-center justify-between gap-2 border-t border-subtle text-xs text-secondary\",\n compact ? \"px-2 py-1\" : \"px-3 py-2\",\n )}\n >\n <div className=\"flex items-center gap-2\">\n <span>\n {/* No rows is a count, not a range: the range read \"1–0 / 0\" (round 0.34 BUG-14). */}\n {pageSize === Infinity || total === 0\n ? labels.rowCount(total)\n : labels.pageRange(page * pageSize + 1, Math.min(total, (page + 1) * pageSize), total)}\n </span>\n {onPageSize && (\n <select\n value={pageSize === Infinity ? \"all\" : pageSize}\n onChange={(e) => onPageSize(e.target.value === \"all\" ? Infinity : Number(e.target.value))}\n className=\"rounded border border-subtle bg-surface px-1 py-0.5 text-xs\"\n aria-label={labels.pageSize}\n >\n {/* A `defaultPageSize` that is not one of the steps (keksdose's 5) is offered\n too — a select whose value has no option shows its first one (\"10\"). */}\n {sizeOptions(pageSize).map((n) => (\n <option key={n} value={n}>\n {num.format(n)}\n </option>\n ))}\n <option value=\"all\">{labels.pageSizeAll}</option>\n </select>\n )}\n </div>\n {pageSize !== Infinity && totalPages > 1 && (\n <div data-slot=\"page-strip\" className={PAGE_STRIP}>\n <button\n type=\"button\"\n onClick={() => {\n if (page > 0) onPage(page - 1);\n }}\n aria-disabled={page === 0 || undefined}\n className={cn(ARROW, compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.prevPage}\n >\n {/* \"Previous\" points back along the line — left in LTR, right in RTL. */}\n <ChevronLeft className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n <div data-slot=\"page-numbers\" className={PAGE_NUMBERS}>\n {items.map((it, i) =>\n it === \"ellipsis\" ? (\n <span key={`e${i}`} className=\"px-1 text-placeholder\">\n …\n </span>\n ) : (\n <button\n key={it}\n type=\"button\"\n onClick={() => onPage(it)}\n // Which page you are on is carried only by a background colour\n // otherwise, and the strip is a row of bare numbers with nothing\n // else to tell them apart.\n aria-current={it === page ? \"page\" : undefined}\n className={cn(\n \"rounded\",\n compact ? \"min-w-6 px-1.5 py-0\" : \"min-w-7 px-2 py-0.5\",\n it === page ? \"bg-inverse text-inverse\" : \"hover:bg-hover\",\n )}\n >\n {num.format(it + 1)}\n </button>\n ),\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => {\n if (page < totalPages - 1) onPage(page + 1);\n }}\n aria-disabled={page >= totalPages - 1 || undefined}\n className={cn(ARROW, compact ? \"p-0.5\" : \"p-1\")}\n aria-label={labels.nextPage}\n >\n <ChevronRight className={cn(\"rtl:-scale-x-100\", compact ? \"size-3.5\" : \"size-4\")} />\n </button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -27,7 +27,7 @@ export declare function useColumnResize<T>({ headRefs, widths, setWidths, column
|
|
|
27
27
|
/** The widths React renders the columns at. */
|
|
28
28
|
widths: Record<string, number>;
|
|
29
29
|
setWidths: Dispatch<SetStateAction<Record<string, number>>>;
|
|
30
|
-
columns: DataTableColumn<T>[];
|
|
30
|
+
columns: readonly DataTableColumn<T>[];
|
|
31
31
|
hiddenCols: Set<string>;
|
|
32
32
|
}): {
|
|
33
33
|
startResize: (e: React.PointerEvent<HTMLElement>, key: string) => void;
|