@eifi1/ui-kit 0.34.0 → 0.36.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 +115 -41
- package/dist/admin/role-select.d.ts +3 -2
- package/dist/admin/role-select.js +2 -0
- package/dist/admin/role-select.js.map +1 -1
- package/dist/billing/billing-banner.d.ts +5 -2
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +5 -2
- package/dist/billing/plan-limit-notice.js.map +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -5
- package/dist/components/amount-input.js +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +2 -2
- package/dist/components/autocomplete.js +2 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-look.d.ts +1 -1
- package/dist/components/button-look.js +9 -4
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -4
- package/dist/components/calculator.js +1 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +0 -37
- package/dist/components/chart-zoom.js +1 -28
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/checkbox-group.d.ts +5 -2
- package/dist/components/checkbox-group.js +15 -4
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/chip-input.d.ts +0 -4
- package/dist/components/chip-input.js +1 -5
- package/dist/components/chip-input.js.map +1 -1
- package/dist/components/column-mapper.d.ts +5 -3
- package/dist/components/column-mapper.js +2 -0
- package/dist/components/column-mapper.js.map +1 -1
- package/dist/components/combobox.d.ts +3 -3
- package/dist/components/combobox.js +2 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/country-select.d.ts +3 -3
- package/dist/components/country-select.js +3 -0
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +3 -8
- package/dist/components/currency-option-list.js +1 -29
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/currency-select.d.ts +3 -2
- package/dist/components/currency-select.js +10 -1
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +3 -3
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filter-popover.js +1 -1
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +1 -42
- package/dist/components/data-table-labels.js +2 -31
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +17 -1
- package/dist/components/data-table-shared.js +22 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +12 -2
- package/dist/components/data-table.js +18 -14
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-field.d.ts +2 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +3 -0
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +3 -0
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.d.ts +18 -15
- package/dist/components/dropdown.js +11 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +3 -3
- package/dist/components/entity-combobox.js +2 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-base.d.ts +0 -5
- package/dist/components/field-base.js +0 -5
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-parts.d.ts +69 -22
- package/dist/components/field-parts.js +33 -19
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +9 -23
- package/dist/components/field-strip.js +26 -23
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field-sync.d.ts +0 -4
- package/dist/components/field-sync.js +0 -4
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/field.d.ts +7 -6
- package/dist/components/field.js +24 -15
- package/dist/components/field.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +2 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/help-popover.d.ts +9 -7
- package/dist/components/help-popover.js.map +1 -1
- package/dist/components/hover-menu.d.ts +6 -13
- package/dist/components/hover-menu.js +3 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-picker.d.ts +3 -2
- package/dist/components/icon-picker.js +3 -0
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +3 -3
- package/dist/components/inline-entity-combobox.js +2 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.d.ts +2 -2
- package/dist/components/input.js +2 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/modal.d.ts +9 -1
- package/dist/components/modal.js +7 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +2 -1
- package/dist/components/month-picker.js +8 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +2 -2
- 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 +7 -18
- package/dist/components/multi-select.js +8 -10
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -5
- package/dist/components/number-input.js +2 -4
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numeric-field.d.ts +5 -2
- package/dist/components/numeric-field.js +4 -1
- package/dist/components/numeric-field.js.map +1 -1
- package/dist/components/one-time-code-input.d.ts +2 -1
- package/dist/components/one-time-code-input.js +2 -1
- package/dist/components/one-time-code-input.js.map +1 -1
- package/dist/components/phone-input.d.ts +1 -1
- package/dist/components/phone-input.js +4 -0
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/popover.js +3 -3
- package/dist/components/popover.js.map +1 -1
- package/dist/components/select.d.ts +4 -3
- package/dist/components/select.js +2 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/server-wake.d.ts +0 -6
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -2
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/slider.d.ts +3 -2
- package/dist/components/slider.js +7 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/stat-tile.d.ts +8 -2
- package/dist/components/stat-tile.js +2 -11
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swatch-picker.d.ts +3 -2
- package/dist/components/swatch-picker.js +3 -0
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +2 -17
- package/dist/components/swipeable-row.js +11 -16
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/text-link.js +3 -1
- package/dist/components/text-link.js.map +1 -1
- package/dist/components/textarea.d.ts +2 -1
- package/dist/components/textarea.js +9 -3
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +7 -3
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/tooltip.d.ts +9 -1
- package/dist/components/tooltip.js +20 -5
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review.d.ts +10 -2
- package/dist/components/translation-review.js +11 -3
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/use-table-state.d.ts +31 -3
- package/dist/components/use-table-state.js +53 -8
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +2 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.d.ts +1 -4
- package/dist/components/wizard-stepper.js +2 -3
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/currencies.d.ts +23 -0
- package/dist/currencies.js +38 -0
- package/dist/currencies.js.map +1 -0
- package/dist/demo/demo-banner.d.ts +7 -2
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/eslint/index.d.ts +83 -0
- package/dist/eslint/index.js +69 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/role-utilities.d.ts +48 -0
- package/dist/eslint/role-utilities.generated.d.ts +2 -0
- package/dist/eslint/role-utilities.generated.js +143 -0
- package/dist/eslint/role-utilities.generated.js.map +1 -0
- package/dist/eslint/role-utilities.js +43 -0
- package/dist/eslint/role-utilities.js.map +1 -0
- package/dist/feedback/feedback-menu.d.ts +2 -2
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +3 -8
- package/dist/feedback/feedback-thread.js +0 -2
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +16 -3
- package/dist/hooks/use-dismiss.js +14 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/defaults.js +5 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +97 -1
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +29 -4
- package/dist/i18n/kit-labels.js +21 -1
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +104 -3
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +95 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +99 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +95 -1
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +99 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +95 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +13 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +3 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +40 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/device-settings.d.ts +72 -0
- package/dist/lib/device-settings.js +64 -0
- package/dist/lib/device-settings.js.map +1 -0
- package/dist/lib/plain-text.d.ts +11 -0
- package/dist/lib/plain-text.js +16 -0
- package/dist/lib/plain-text.js.map +1 -0
- package/dist/lib/refs.d.ts +13 -0
- package/dist/lib/refs.js +15 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/lib/safe-storage.d.ts +6 -0
- package/dist/lib/safe-storage.js +11 -0
- package/dist/lib/safe-storage.js.map +1 -1
- package/dist/notifications/notification-parts.d.ts +39 -0
- package/dist/notifications/notification-parts.js +47 -0
- package/dist/notifications/notification-parts.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +62 -0
- package/dist/notifications/notification-types-setting.js +192 -0
- package/dist/notifications/notification-types-setting.js.map +1 -0
- package/dist/notifications/notification-types.d.ts +75 -0
- package/dist/notifications/notification-types.js +2 -0
- package/dist/notifications/notification-types.js.map +1 -0
- package/dist/notifications/notifications-labels.d.ts +131 -0
- package/dist/notifications/notifications-labels.js +83 -0
- package/dist/notifications/notifications-labels.js.map +1 -0
- package/dist/notifications/push-device-setting.d.ts +37 -0
- package/dist/notifications/push-device-setting.js +164 -0
- package/dist/notifications/push-device-setting.js.map +1 -0
- package/dist/notifications/push-devices-setting.d.ts +44 -0
- package/dist/notifications/push-devices-setting.js +203 -0
- package/dist/notifications/push-devices-setting.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +47 -0
- package/dist/notifications/quiet-hours-setting.js +131 -0
- package/dist/notifications/quiet-hours-setting.js.map +1 -0
- package/dist/notifications/time-zone-select.d.ts +50 -0
- package/dist/notifications/time-zone-select.js +76 -0
- package/dist/notifications/time-zone-select.js.map +1 -0
- package/dist/notifications/unsubscribe-panel.d.ts +53 -0
- package/dist/notifications/unsubscribe-panel.js +139 -0
- package/dist/notifications/unsubscribe-panel.js.map +1 -0
- package/dist/notifications/use-push-device.d.ts +122 -0
- package/dist/notifications/use-push-device.js +230 -0
- package/dist/notifications/use-push-device.js.map +1 -0
- package/dist/rhf/fields.d.ts +36 -29
- package/dist/rhf/fields.js +51 -22
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form-core.d.ts +5 -4
- package/dist/rhf/form-core.js.map +1 -1
- package/dist/rhf/form.d.ts +7 -36
- package/dist/rhf/form.js +1 -16
- package/dist/rhf/form.js.map +1 -1
- package/dist/rhf/line-items.d.ts +1 -1
- package/dist/rhf/line-items.js.map +1 -1
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
- package/dist/rhf.js.map +1 -1
- package/dist/settings/device-reset-setting-labels.d.ts +3 -0
- package/dist/settings/device-reset-setting-labels.js +15 -0
- package/dist/settings/device-reset-setting-labels.js.map +1 -0
- package/dist/settings/device-reset-setting.d.ts +58 -0
- package/dist/settings/device-reset-setting.js +51 -0
- package/dist/settings/device-reset-setting.js.map +1 -0
- package/dist/shell/app-shell.js +5 -0
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +5 -17
- package/dist/shell/option-switcher-menu.js +1 -3
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +1 -4
- package/dist/shell/role-switcher.js +2 -3
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +7 -19
- package/dist/shell/topbar-action-menu.js +1 -3
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +3 -12
- package/dist/shell/topbar-controls.js +6 -9
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/viewport.d.ts +25 -2
- package/dist/testing/viewport.js +61 -10
- package/dist/testing/viewport.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +42 -4
- package/dist/theme/contrast-tokens.js +56 -9
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -2
- package/dist/theme/contrast.js +13 -0
- package/dist/theme/contrast.js.map +1 -1
- package/dist/theme/palette-derive.d.ts +15 -1
- package/dist/theme/palette-derive.js +37 -8
- package/dist/theme/palette-derive.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +2 -8
- package/dist/theme/palette-presets.js +5 -284
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/theme/palette-store.js +6 -0
- package/dist/theme/palette-store.js.map +1 -1
- package/dist/theme/text-size.js +6 -0
- package/dist/theme/text-size.js.map +1 -1
- package/dist/theme/theme-store.js +6 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/dist/wizard/types.d.ts +0 -5
- package/dist/wizard/types.js +1 -5
- package/dist/wizard/types.js.map +1 -1
- package/package.json +9 -1
- package/src/admin/role-select.tsx +4 -1
- package/src/billing/billing-banner.tsx +5 -2
- package/src/billing/plan-limit-notice.tsx +5 -2
- package/src/chart.ts +5 -3
- package/src/components/amount-input.tsx +4 -7
- package/src/components/autocomplete.tsx +5 -2
- package/src/components/button-look.ts +15 -5
- package/src/components/calculator.tsx +2 -7
- package/src/components/chart-zoom.tsx +4 -47
- package/src/components/checkbox-group.tsx +22 -4
- package/src/components/chip-input.tsx +0 -7
- package/src/components/column-mapper.tsx +6 -2
- package/src/components/combobox.tsx +6 -3
- package/src/components/copy-button.tsx +5 -1
- package/src/components/country-select.tsx +6 -1
- package/src/components/currency-option-list.tsx +8 -42
- package/src/components/currency-select.tsx +12 -3
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-filter-popover.tsx +2 -1
- package/src/components/data-table-labels.ts +2 -84
- package/src/components/data-table-shared.tsx +55 -2
- package/src/components/data-table.tsx +29 -17
- package/src/components/date-field.tsx +2 -2
- package/src/components/date-picker.tsx +3 -0
- package/src/components/date-range-picker.tsx +3 -0
- package/src/components/dialog-frame.tsx +3 -1
- package/src/components/dropdown.tsx +42 -18
- package/src/components/entity-combobox.tsx +6 -3
- package/src/components/field-base.tsx +0 -11
- package/src/components/field-parts.tsx +112 -38
- package/src/components/field-strip.tsx +12 -27
- package/src/components/field-sync.tsx +0 -7
- package/src/components/field.tsx +32 -19
- package/src/components/full-bleed-dialog.tsx +4 -3
- package/src/components/help-popover.tsx +9 -7
- package/src/components/hover-menu.tsx +12 -16
- package/src/components/icon-picker.tsx +8 -3
- package/src/components/inline-entity-combobox.tsx +6 -3
- package/src/components/input.tsx +4 -2
- package/src/components/modal.tsx +20 -4
- package/src/components/month-picker.tsx +10 -3
- package/src/components/multi-entity-combobox.tsx +5 -2
- package/src/components/multi-select.tsx +19 -31
- package/src/components/number-input.tsx +5 -10
- package/src/components/numeric-field.ts +10 -2
- package/src/components/one-time-code-input.tsx +5 -3
- package/src/components/phone-input.tsx +4 -0
- package/src/components/popover.tsx +8 -4
- package/src/components/select.tsx +6 -3
- package/src/components/series-chart.tsx +1 -1
- package/src/components/server-wake.tsx +0 -6
- package/src/components/settings-fields.tsx +4 -2
- package/src/components/slider.tsx +10 -3
- package/src/components/stat-tile.tsx +11 -20
- package/src/components/swatch-picker.tsx +9 -4
- package/src/components/swipeable-row.tsx +15 -37
- package/src/components/text-link.tsx +3 -1
- package/src/components/textarea.tsx +11 -4
- package/src/components/toggle-group.tsx +21 -5
- package/src/components/tooltip.tsx +37 -6
- package/src/components/translation-review.tsx +25 -7
- package/src/components/use-table-state.ts +91 -10
- package/src/components/user-avatar.tsx +2 -2
- package/src/components/wizard-stepper.tsx +2 -6
- package/src/currencies.ts +70 -0
- package/src/demo/demo-banner.tsx +7 -2
- package/src/eslint/index.ts +161 -0
- package/src/eslint/role-utilities.generated.ts +146 -0
- package/src/eslint/role-utilities.ts +99 -0
- package/src/feedback/feedback-menu.tsx +2 -2
- package/src/feedback/feedback-thread.tsx +3 -8
- package/src/hooks/use-dismiss.ts +39 -4
- package/src/i18n/defaults.ts +4 -0
- package/src/i18n/german.ts +113 -0
- package/src/i18n/kit-labels.tsx +66 -6
- package/src/i18n/locales/en.ts +6 -0
- package/src/i18n/locales/es.ts +106 -0
- package/src/i18n/locales/fr.ts +109 -2
- package/src/i18n/locales/hu.ts +104 -0
- package/src/i18n/locales/it.ts +108 -2
- package/src/i18n/locales/zh.ts +96 -0
- package/src/i18n/review.ts +13 -0
- package/src/i18n/shared-labels.ts +2 -0
- package/src/index.ts +53 -1
- package/src/lib/device-settings.ts +146 -0
- package/src/lib/plain-text.ts +22 -0
- package/src/lib/refs.ts +22 -0
- package/src/lib/safe-storage.ts +16 -0
- package/src/notifications/notification-parts.tsx +83 -0
- package/src/notifications/notification-types-setting.tsx +309 -0
- package/src/notifications/notification-types.ts +80 -0
- package/src/notifications/notifications-labels.ts +238 -0
- package/src/notifications/push-device-setting.tsx +224 -0
- package/src/notifications/push-devices-setting.tsx +306 -0
- package/src/notifications/quiet-hours-setting.tsx +185 -0
- package/src/notifications/time-zone-select.tsx +126 -0
- package/src/notifications/unsubscribe-panel.tsx +203 -0
- package/src/notifications/use-push-device.ts +392 -0
- package/src/rhf/fields.tsx +87 -39
- package/src/rhf/form-core.tsx +5 -4
- package/src/rhf/form.tsx +7 -43
- package/src/rhf/line-items.tsx +1 -1
- package/src/rhf/use-rhf-wizard-step.ts +4 -4
- package/src/rhf.ts +3 -2
- package/src/settings/device-reset-setting-labels.ts +20 -0
- package/src/settings/device-reset-setting.tsx +119 -0
- package/src/shell/app-shell.tsx +8 -0
- package/src/shell/option-switcher-menu.tsx +5 -22
- package/src/shell/role-switcher.tsx +2 -6
- package/src/shell/topbar-action-menu.tsx +7 -24
- package/src/shell/topbar-controls.tsx +6 -18
- package/src/testing/index.ts +3 -1
- package/src/testing/viewport.ts +125 -19
- package/src/theme/contrast-tokens.ts +106 -12
- package/src/theme/contrast.ts +30 -4
- package/src/theme/palette-derive.ts +68 -7
- package/src/theme/palette-presets.ts +16 -299
- package/src/theme/palette-store.ts +12 -0
- package/src/theme/text-size.ts +8 -0
- package/src/theme/theme-store.ts +10 -0
- package/src/wizard/types.ts +0 -8
- package/tokens.css +38 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
4
|
+
import { resetDeviceSettings } from "../lib/device-settings";
|
|
5
|
+
import { Button } from "../components/button";
|
|
6
|
+
import { useConfirm } from "../components/confirm-dialog";
|
|
7
|
+
import { toast } from "../components/toast";
|
|
8
|
+
import { SettingsSection, type SettingsSectionProps } from "./settings-section";
|
|
9
|
+
import { DEFAULT_DEVICE_RESET_LABELS } from "./device-reset-setting-labels";
|
|
10
|
+
|
|
11
|
+
/* ── Labels ──────────────────────────────────────────────────────────────── */
|
|
12
|
+
|
|
13
|
+
/** Every string {@link DeviceResetSetting} renders — the `deviceReset` namespace of
|
|
14
|
+
* `<UiKitProvider labels>`, overridable per instance through `labels`
|
|
15
|
+
* (docs/settings-harmonization.md §11.3). The confirm's Cancel is `confirmDialog.cancel`. */
|
|
16
|
+
export interface DeviceResetLabels {
|
|
17
|
+
/** The card's title, and the app's catalogue entry's. */
|
|
18
|
+
title: string;
|
|
19
|
+
/** The line under it: what goes back, and that the account is not touched. */
|
|
20
|
+
description: string;
|
|
21
|
+
/** The button that asks first. */
|
|
22
|
+
action: string;
|
|
23
|
+
/** The question it asks. */
|
|
24
|
+
confirmTitle: string;
|
|
25
|
+
/** What goes, what follows the account again, and what stays. */
|
|
26
|
+
confirmBody: string;
|
|
27
|
+
/** The button that answers it. */
|
|
28
|
+
confirmAction: string;
|
|
29
|
+
/** The toast once it is done. */
|
|
30
|
+
done: string;
|
|
31
|
+
/** For the app's catalogue entry (`keywords`); the card never reads it. */
|
|
32
|
+
keywords: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Defined in ./device-reset-setting-labels, which holds no component code.
|
|
36
|
+
export { DEFAULT_DEVICE_RESET_LABELS };
|
|
37
|
+
|
|
38
|
+
/* ── The card ────────────────────────────────────────────────────────────── */
|
|
39
|
+
|
|
40
|
+
/** The card's DOM id: its catalogue entry's `anchor`, the `?focus=` target. */
|
|
41
|
+
export const DEVICE_RESET_ANCHOR = "settings-device-reset";
|
|
42
|
+
|
|
43
|
+
// `onReset` is the card's own: the DOM's form-reset handler never fires on a div.
|
|
44
|
+
export interface DeviceResetSettingProps
|
|
45
|
+
extends Omit<SettingsSectionProps, "anchor" | "title" | "description" | "children" | "onReset"> {
|
|
46
|
+
/** The app's sentence after the kit's description: what of its own goes back, and what
|
|
47
|
+
* stays (keksdose: the date format, the feature set and the gestures go; presentation
|
|
48
|
+
* mode and the home position stay). */
|
|
49
|
+
description?: ReactNode;
|
|
50
|
+
labels?: LabelOverride<DeviceResetLabels>;
|
|
51
|
+
/** Runs after the reset and before the toast, with the keys reset or removed. */
|
|
52
|
+
onReset?: (keys: string[]) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* "Reset this device's settings" (docs/settings-harmonization.md §11): the LAST card of
|
|
57
|
+
* the appearance group. It puts this device's appearance — theme, palette, language, text
|
|
58
|
+
* size, contrast and the app's own appearance choices — and its views — table views, the
|
|
59
|
+
* sidebar, folded groups, saved filters — back to what a new device gets, through
|
|
60
|
+
* {@link resetDeviceSettings}. The account, the sign-in, the session and anything waiting
|
|
61
|
+
* to be sent are never touched: they never join the registry.
|
|
62
|
+
*
|
|
63
|
+
* - **A confirm, not an undo** (§11.2 decision 9): `useConfirm` in the neutral tone,
|
|
64
|
+
* then a toast. An app mounts a `ConfirmProvider` and a `Toaster` already.
|
|
65
|
+
* - **Not a commit**: nothing is written to the account, so a demo or a lapsed plan's
|
|
66
|
+
* write lock does not disable it (decision 3).
|
|
67
|
+
* - **Live, no reload** (decision 10): each registered store resets through its own
|
|
68
|
+
* setter; the card stays where it is.
|
|
69
|
+
*
|
|
70
|
+
* The app adds the catalogue entry, `{ id: "device_reset", group: "appearance", anchor:
|
|
71
|
+
* DEVICE_RESET_ANCHOR, title, keywords }` (`title` and `keywords` from these labels), last
|
|
72
|
+
* in its appearance group: inside a `SettingsLayout`, `SettingsSection` throws in
|
|
73
|
+
* development without it.
|
|
74
|
+
*/
|
|
75
|
+
export function DeviceResetSetting({
|
|
76
|
+
description,
|
|
77
|
+
labels: labelsProp,
|
|
78
|
+
onReset,
|
|
79
|
+
...rest
|
|
80
|
+
}: DeviceResetSettingProps) {
|
|
81
|
+
const labels = useKitLabels("deviceReset", DEFAULT_DEVICE_RESET_LABELS, labelsProp);
|
|
82
|
+
const confirm = useConfirm();
|
|
83
|
+
|
|
84
|
+
const reset = async () => {
|
|
85
|
+
const ok = await confirm({
|
|
86
|
+
title: labels.confirmTitle,
|
|
87
|
+
body: labels.confirmBody,
|
|
88
|
+
confirmLabel: labels.confirmAction,
|
|
89
|
+
tone: "neutral",
|
|
90
|
+
});
|
|
91
|
+
if (!ok) return;
|
|
92
|
+
const keys = resetDeviceSettings();
|
|
93
|
+
onReset?.(keys);
|
|
94
|
+
toast.success(labels.done);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<SettingsSection
|
|
99
|
+
{...rest}
|
|
100
|
+
anchor={DEVICE_RESET_ANCHOR}
|
|
101
|
+
title={labels.title}
|
|
102
|
+
description={
|
|
103
|
+
description != null ? (
|
|
104
|
+
<>
|
|
105
|
+
{labels.description} {description}
|
|
106
|
+
</>
|
|
107
|
+
) : (
|
|
108
|
+
labels.description
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
<div>
|
|
113
|
+
<Button variant="secondary" onClick={() => void reset()}>
|
|
114
|
+
{labels.action}
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
</SettingsSection>
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/shell/app-shell.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { cn } from "../lib/cn";
|
|
|
8
8
|
import { scrollIntoStrip, useStripFade } from "../lib/strip-fade";
|
|
9
9
|
import { dirOf, type Direction } from "../lib/direction";
|
|
10
10
|
import { readStored, writeStored } from "../lib/safe-storage";
|
|
11
|
+
import { registerDeviceSetting } from "../lib/device-settings";
|
|
11
12
|
import { Tooltip } from "../components/tooltip";
|
|
12
13
|
import { pickLinkRenderer } from "../components/text-link";
|
|
13
14
|
import { DialogFrame } from "../components/dialog-frame";
|
|
@@ -394,6 +395,13 @@ export function AppShell({
|
|
|
394
395
|
useEffect(() => {
|
|
395
396
|
if (collapseStorageKey !== null) writeStored(collapseStorageKey, collapsed ? "1" : "0");
|
|
396
397
|
}, [collapsed, collapseStorageKey]);
|
|
398
|
+
// Reset this device's settings (docs/settings-harmonization.md §11.4): the sidebar
|
|
399
|
+
// unfolds, live, and the effect above writes "0". An embedded shell without a key of
|
|
400
|
+
// its own stores nothing and joins nothing.
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (collapseStorageKey === null) return;
|
|
403
|
+
return registerDeviceSetting({ key: collapseStorageKey, kind: "view", reset: () => setCollapsed(false) });
|
|
404
|
+
}, [collapseStorageKey]);
|
|
397
405
|
// §4: at Large and Extra large the sidebar never collapses to icons only — a glyph
|
|
398
406
|
// with its name in a tooltip is the very thing a reader who asked for bigger type
|
|
399
407
|
// cannot use. The persisted preference is left alone, so Normal gets it back, and the
|
|
@@ -9,23 +9,6 @@ export interface OptionSwitcherOption<T extends string> {
|
|
|
9
9
|
label: ReactNode;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Names the trigger and the menu: one of the two spellings is required (API-1, refactor
|
|
14
|
-
* audit 2026-10). `aria-label` wins when both are given. TopBarActionMenu has the same
|
|
15
|
-
* type; both become a plain required `aria-label` when `ariaLabel` goes in 0.35.
|
|
16
|
-
*/
|
|
17
|
-
type RequiredMenuName =
|
|
18
|
-
| {
|
|
19
|
-
"aria-label": string;
|
|
20
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
21
|
-
ariaLabel?: string;
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
"aria-label"?: string;
|
|
25
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
26
|
-
ariaLabel: string;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
12
|
/**
|
|
30
13
|
* A top-bar hover menu that switches between a small set of options, with a
|
|
31
14
|
* check on the active one and an optional heading. Generic glue over
|
|
@@ -34,8 +17,7 @@ type RequiredMenuName =
|
|
|
34
17
|
*/
|
|
35
18
|
export function OptionSwitcherMenu<T extends string>({
|
|
36
19
|
icon,
|
|
37
|
-
"aria-label":
|
|
38
|
-
ariaLabel: ariaLabelProp,
|
|
20
|
+
"aria-label": ariaLabel,
|
|
39
21
|
title,
|
|
40
22
|
heading,
|
|
41
23
|
options,
|
|
@@ -43,14 +25,15 @@ export function OptionSwitcherMenu<T extends string>({
|
|
|
43
25
|
onSelect,
|
|
44
26
|
}: {
|
|
45
27
|
icon: ReactNode;
|
|
28
|
+
/** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
|
|
29
|
+
* 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
|
|
30
|
+
"aria-label": string;
|
|
46
31
|
title?: string;
|
|
47
32
|
heading?: string;
|
|
48
33
|
options: OptionSwitcherOption<T>[];
|
|
49
34
|
value: T;
|
|
50
35
|
onSelect: (value: T) => void;
|
|
51
|
-
}
|
|
52
|
-
// The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
|
|
53
|
-
const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
|
|
36
|
+
}) {
|
|
54
37
|
return (
|
|
55
38
|
<HoverMenu
|
|
56
39
|
aria-label={ariaLabel}
|
|
@@ -8,9 +8,6 @@ export interface RoleSwitcherProps<R extends string> {
|
|
|
8
8
|
onChange: (role: R) => void;
|
|
9
9
|
/** Default: `topBar.switchRole` from the {@link UiKitProvider}, else English. */
|
|
10
10
|
"aria-label"?: string;
|
|
11
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
12
|
-
* Names the switcher only when `aria-label` is absent. */
|
|
13
|
-
ariaLabel?: string;
|
|
14
11
|
heading?: string;
|
|
15
12
|
/** The trigger's tooltip. Default: `topBar.role(value)`. */
|
|
16
13
|
title?: string;
|
|
@@ -30,12 +27,11 @@ export function RoleSwitcher<R extends string>({
|
|
|
30
27
|
roles,
|
|
31
28
|
value,
|
|
32
29
|
onChange,
|
|
33
|
-
"aria-label":
|
|
34
|
-
ariaLabel,
|
|
30
|
+
"aria-label": ariaLabel,
|
|
35
31
|
heading,
|
|
36
32
|
title,
|
|
37
33
|
}: RoleSwitcherProps<R>) {
|
|
38
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { switchRole:
|
|
34
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { switchRole: ariaLabel });
|
|
39
35
|
return (
|
|
40
36
|
<OptionSwitcherMenu<R>
|
|
41
37
|
aria-label={labels.switchRole}
|
|
@@ -179,23 +179,6 @@ function keepTabInsidePanel(e: ReactKeyboardEvent<HTMLUListElement>) {
|
|
|
179
179
|
if (stops.some((el) => el !== from && from.compareDocumentPosition(el) & way)) e.stopPropagation();
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
/**
|
|
183
|
-
* Names the trigger and the menu: one of the two spellings is required (API-1, refactor
|
|
184
|
-
* audit 2026-10). `aria-label` wins when both are given. OptionSwitcherMenu has the same
|
|
185
|
-
* type; both become a plain required `aria-label` when `ariaLabel` goes in 0.35.
|
|
186
|
-
*/
|
|
187
|
-
type RequiredMenuName =
|
|
188
|
-
| {
|
|
189
|
-
"aria-label": string;
|
|
190
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
191
|
-
ariaLabel?: string;
|
|
192
|
-
}
|
|
193
|
-
| {
|
|
194
|
-
"aria-label"?: string;
|
|
195
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35. */
|
|
196
|
-
ariaLabel: string;
|
|
197
|
-
};
|
|
198
|
-
|
|
199
182
|
/**
|
|
200
183
|
* A top-bar icon button that opens a {@link HoverMenu} of actions and/or links,
|
|
201
184
|
* with an optional uppercase heading and divider rules. Generic glue over the
|
|
@@ -219,8 +202,7 @@ export function TopBarActionMenu({
|
|
|
219
202
|
trigger,
|
|
220
203
|
triggerClassName,
|
|
221
204
|
href,
|
|
222
|
-
"aria-label":
|
|
223
|
-
ariaLabel: ariaLabelProp,
|
|
205
|
+
"aria-label": ariaLabel,
|
|
224
206
|
heading,
|
|
225
207
|
header,
|
|
226
208
|
entries,
|
|
@@ -265,6 +247,9 @@ export function TopBarActionMenu({
|
|
|
265
247
|
* inside interactive content.
|
|
266
248
|
*/
|
|
267
249
|
href?: string;
|
|
250
|
+
/** Names the trigger and the menu. Required: the trigger is an icon (refactor audit
|
|
251
|
+
* 2026-10, API-1 — the `ariaLabel` spelling went in 0.35). */
|
|
252
|
+
"aria-label": string;
|
|
268
253
|
heading?: string;
|
|
269
254
|
/** A non-interactive identity block above the rows. */
|
|
270
255
|
header?: TopBarMenuHeader;
|
|
@@ -283,13 +268,11 @@ export function TopBarActionMenu({
|
|
|
283
268
|
footer?: ReactNode | ((close: () => void) => ReactNode);
|
|
284
269
|
/** Wraps the footer in a `<nav>` with this name ("Legal"). Without it, a plain block. */
|
|
285
270
|
footerLabel?: string;
|
|
286
|
-
/** Which edge of the trigger the panel lines up with
|
|
287
|
-
*
|
|
271
|
+
/** Which edge of the trigger the panel lines up with, `start` or `end` in the reading
|
|
272
|
+
* direction (see `HoverMenu`, whose type this is). Default `end`. */
|
|
288
273
|
align?: HoverMenuProps["align"];
|
|
289
274
|
panelClassName?: string;
|
|
290
|
-
}
|
|
291
|
-
// The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
|
|
292
|
-
const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
|
|
275
|
+
}) {
|
|
293
276
|
// Names the groups' headings for `aria-labelledby`. Positional rather than from the
|
|
294
277
|
// entry's `key`, which is the caller's text and may hold a space — and a space in
|
|
295
278
|
// `aria-labelledby` is a list separator.
|
|
@@ -29,8 +29,7 @@ function MenuHeading({ children }: { children: string }) {
|
|
|
29
29
|
export function ThemeToggle({
|
|
30
30
|
mode,
|
|
31
31
|
onToggle,
|
|
32
|
-
"aria-label":
|
|
33
|
-
ariaLabel,
|
|
32
|
+
"aria-label": ariaLabel,
|
|
34
33
|
title,
|
|
35
34
|
className,
|
|
36
35
|
}: {
|
|
@@ -38,15 +37,12 @@ export function ThemeToggle({
|
|
|
38
37
|
onToggle: () => void;
|
|
39
38
|
/** Default: `topBar.theme` from the {@link UiKitProvider}, else English. */
|
|
40
39
|
"aria-label"?: string;
|
|
41
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
42
|
-
* Names the control only when `aria-label` is absent. */
|
|
43
|
-
ariaLabel?: string;
|
|
44
40
|
title?: string;
|
|
45
41
|
className?: string;
|
|
46
42
|
}) {
|
|
47
43
|
// This had no default at all, so a toggle rendered without `aria-label` was an
|
|
48
44
|
// icon button with no name — a reader heard "button" and nothing else.
|
|
49
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme:
|
|
45
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { theme: ariaLabel });
|
|
50
46
|
const Icon = mode === "dark" ? Moon : Sun;
|
|
51
47
|
return (
|
|
52
48
|
<Tooltip label={title} portal>
|
|
@@ -69,8 +65,7 @@ export function PaletteMenu({
|
|
|
69
65
|
activeId,
|
|
70
66
|
mode,
|
|
71
67
|
onSelect,
|
|
72
|
-
"aria-label":
|
|
73
|
-
ariaLabel,
|
|
68
|
+
"aria-label": ariaLabel,
|
|
74
69
|
heading,
|
|
75
70
|
}: {
|
|
76
71
|
palettes: PalettePreset[];
|
|
@@ -79,13 +74,10 @@ export function PaletteMenu({
|
|
|
79
74
|
onSelect: (id: string) => void;
|
|
80
75
|
/** Default: `topBar.palette` from the {@link UiKitProvider}, else English. */
|
|
81
76
|
"aria-label"?: string;
|
|
82
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
83
|
-
* Names the control only when `aria-label` is absent. */
|
|
84
|
-
ariaLabel?: string;
|
|
85
77
|
/** Optional small heading row above the list. */
|
|
86
78
|
heading?: string;
|
|
87
79
|
}) {
|
|
88
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { palette:
|
|
80
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { palette: ariaLabel });
|
|
89
81
|
const activeName = palettes.find((p) => p.id === activeId)?.name ?? activeId;
|
|
90
82
|
return (
|
|
91
83
|
<HoverMenu
|
|
@@ -177,8 +169,7 @@ export function LanguageMenu({
|
|
|
177
169
|
options,
|
|
178
170
|
current,
|
|
179
171
|
onChange,
|
|
180
|
-
"aria-label":
|
|
181
|
-
ariaLabel,
|
|
172
|
+
"aria-label": ariaLabel,
|
|
182
173
|
heading,
|
|
183
174
|
}: {
|
|
184
175
|
options: LanguageOption[];
|
|
@@ -186,14 +177,11 @@ export function LanguageMenu({
|
|
|
186
177
|
onChange: (code: string) => void;
|
|
187
178
|
/** Default: `topBar.language` from the {@link UiKitProvider}, else English. */
|
|
188
179
|
"aria-label"?: string;
|
|
189
|
-
/** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
|
|
190
|
-
* Names the control only when `aria-label` is absent. */
|
|
191
|
-
ariaLabel?: string;
|
|
192
180
|
/** Optional small heading row above the list — as on `TopBarActionMenu`,
|
|
193
181
|
* `OptionSwitcherMenu` and `PaletteMenu`. */
|
|
194
182
|
heading?: string;
|
|
195
183
|
}) {
|
|
196
|
-
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { language:
|
|
184
|
+
const labels = useKitLabels("topBar", DEFAULT_TOP_BAR_LABELS, { language: ariaLabel });
|
|
197
185
|
const active = options.find((o) => o.code === current);
|
|
198
186
|
return (
|
|
199
187
|
<HoverMenu
|
package/src/testing/index.ts
CHANGED
|
@@ -13,8 +13,10 @@ export {
|
|
|
13
13
|
} from "./css-variables";
|
|
14
14
|
// 0.34 (refactor audit 2026-10, TST-10): the environment jsdom lacks — a `matchMedia`
|
|
15
15
|
// that answers from a viewport width (right at every text size) and a `ResizeObserver`
|
|
16
|
-
// the test drives. Framework-free: each returns its own undo.
|
|
16
|
+
// the test drives. Framework-free: each returns its own undo. 0.35: `setViewportWidth`,
|
|
17
|
+
// a live resize of the stub in force — its lists flip and fire `change` (keksdose).
|
|
17
18
|
export {
|
|
19
|
+
setViewportWidth,
|
|
18
20
|
stubResizeObserver,
|
|
19
21
|
stubViewportWidth,
|
|
20
22
|
viewportMatchMedia,
|
package/src/testing/viewport.ts
CHANGED
|
@@ -85,14 +85,91 @@ function condition(part: string, width: number, options: ViewportOptions): boole
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** A whole query: comma lists are alternatives, `and` joins conditions, as in CSS. */
|
|
89
|
+
function answer(query: string, width: number, options: ViewportOptions): boolean {
|
|
90
|
+
return query
|
|
91
|
+
.split(",")
|
|
92
|
+
.some((alternative) =>
|
|
93
|
+
alternative.split(/\s+and\s+/).every((part) => condition(part, width, options)),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** A list {@link viewportMatchMedia} handed out: what {@link setViewportWidth} flips. */
|
|
98
|
+
interface StubMediaQueryList {
|
|
99
|
+
matches: boolean;
|
|
100
|
+
readonly media: string;
|
|
101
|
+
onchange: ((this: MediaQueryList, event: MediaQueryListEvent) => unknown) | null;
|
|
102
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null): void;
|
|
103
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject | null): void;
|
|
104
|
+
addListener(listener: EventListenerOrEventListenerObject | null): void;
|
|
105
|
+
removeListener(listener: EventListenerOrEventListenerObject | null): void;
|
|
106
|
+
dispatchEvent(event: Event): boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One stub's viewport: its width now, and every list it has handed out. */
|
|
110
|
+
interface StubViewport {
|
|
111
|
+
width: number;
|
|
112
|
+
readonly options: ViewportOptions;
|
|
113
|
+
readonly lists: Set<StubMediaQueryList>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Each `matchMedia` {@link viewportMatchMedia} made → its viewport. Keyed by the function,
|
|
117
|
+
* so {@link setViewportWidth} finds whichever stub is in force however it was put in. */
|
|
118
|
+
const VIEWPORTS = new WeakMap<object, StubViewport>();
|
|
119
|
+
|
|
120
|
+
function mediaQueryList(query: string, viewport: StubViewport): StubMediaQueryList {
|
|
121
|
+
// One set for both APIs: in a browser `addListener(fn)` IS `addEventListener("change",
|
|
122
|
+
// fn)`, so adding the same function through both registers it once.
|
|
123
|
+
const listeners = new Set<EventListenerOrEventListenerObject>();
|
|
124
|
+
const list: StubMediaQueryList = {
|
|
125
|
+
matches: answer(query, viewport.width, viewport.options),
|
|
126
|
+
media: query,
|
|
127
|
+
onchange: null,
|
|
128
|
+
addEventListener(type, listener) {
|
|
129
|
+
if (type === "change" && listener) listeners.add(listener);
|
|
130
|
+
},
|
|
131
|
+
removeEventListener(type, listener) {
|
|
132
|
+
if (type === "change" && listener) listeners.delete(listener);
|
|
133
|
+
},
|
|
134
|
+
addListener(listener) {
|
|
135
|
+
if (listener) listeners.add(listener);
|
|
136
|
+
},
|
|
137
|
+
removeListener(listener) {
|
|
138
|
+
if (listener) listeners.delete(listener);
|
|
139
|
+
},
|
|
140
|
+
dispatchEvent(event) {
|
|
141
|
+
if (event.type !== "change") return true;
|
|
142
|
+
const self = list as unknown as MediaQueryList;
|
|
143
|
+
list.onchange?.call(self, event as MediaQueryListEvent);
|
|
144
|
+
for (const listener of [...listeners]) {
|
|
145
|
+
if (typeof listener === "function") listener.call(self, event);
|
|
146
|
+
else listener.handleEvent(event);
|
|
147
|
+
}
|
|
148
|
+
return !event.defaultPrevented;
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
return list;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The `change` a browser fires at a list whose answer flipped: a `MediaQueryListEvent`'s
|
|
155
|
+
* `matches` and `media`, on an `Event` (jsdom has no `MediaQueryListEvent`). */
|
|
156
|
+
function changeEvent(list: StubMediaQueryList): Event {
|
|
157
|
+
const event = new Event("change");
|
|
158
|
+
for (const [key, value] of Object.entries({ matches: list.matches, media: list.media, target: list, currentTarget: list })) {
|
|
159
|
+
Object.defineProperty(event, key, { value, enumerable: true });
|
|
160
|
+
}
|
|
161
|
+
return event;
|
|
162
|
+
}
|
|
163
|
+
|
|
88
164
|
/**
|
|
89
165
|
* A `window.matchMedia` for a viewport `width` CSS px wide.
|
|
90
166
|
*
|
|
91
167
|
* Width queries answer from `width`: the range syntax the kit writes for `max-*`
|
|
92
168
|
* (`(width < 768px)`) and every other comparison, `(min-width: …)` and `(max-width: …)`,
|
|
93
169
|
* in px or rem; `and` and comma lists combine as in CSS. The features in `options` answer
|
|
94
|
-
* as set there; every other query is false. The lists
|
|
95
|
-
*
|
|
170
|
+
* as set there; every other query is false. The lists keep their listeners, and
|
|
171
|
+
* {@link setViewportWidth} resizes the viewport under them the way a browser window
|
|
172
|
+
* does: each list whose answer flips fires `change`.
|
|
96
173
|
*
|
|
97
174
|
* Hand it to the runner's own global stub where the suite undoes stubs that way —
|
|
98
175
|
* `vi.stubGlobal("matchMedia", viewportMatchMedia(375))` — or use
|
|
@@ -102,23 +179,52 @@ export function viewportMatchMedia(
|
|
|
102
179
|
width: number,
|
|
103
180
|
options: ViewportOptions = {},
|
|
104
181
|
): (query: string) => MediaQueryList {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
182
|
+
const viewport: StubViewport = { width, options, lists: new Set() };
|
|
183
|
+
const matchMedia = (query: string) => {
|
|
184
|
+
const list = mediaQueryList(query, viewport);
|
|
185
|
+
viewport.lists.add(list);
|
|
186
|
+
return list as unknown as MediaQueryList;
|
|
187
|
+
};
|
|
188
|
+
VIEWPORTS.set(matchMedia, viewport);
|
|
189
|
+
return matchMedia;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Resize the stubbed viewport to `width` CSS px, live — the window being dragged narrower,
|
|
194
|
+
* a phone turned (0.35, keksdose's 0.34 report). Every list the stub in force has handed
|
|
195
|
+
* out answers from the new width, and each one whose answer flipped fires `change` to its
|
|
196
|
+
* `addEventListener` listeners, its legacy `addListener` ones and its `onchange`, as in a
|
|
197
|
+
* browser; a list whose answer stayed the same tells nobody. So a mounted component that
|
|
198
|
+
* listens to a breakpoint (`usePhoneLayout()`, `useBreakpoint()`) switches without being
|
|
199
|
+
* rendered again.
|
|
200
|
+
*
|
|
201
|
+
* The stub in force is whatever `window.matchMedia` is now — one {@link stubViewportWidth}
|
|
202
|
+
* or `vi.stubGlobal("matchMedia", viewportMatchMedia(…))` put in; its features
|
|
203
|
+
* (`options`) stay as they were. Throws when `matchMedia` is not one of these stubs, rather
|
|
204
|
+
* than resize nothing. Synchronous: wrap it in `act()` when a listener sets state.
|
|
205
|
+
*
|
|
206
|
+
* ```ts
|
|
207
|
+
* undo = stubViewportWidth(1280);
|
|
208
|
+
* render(<Page />);
|
|
209
|
+
* act(() => setViewportWidth(375));
|
|
210
|
+
* expect(screen.getByRole("list")).toBeInTheDocument(); // the cards
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function setViewportWidth(width: number): void {
|
|
214
|
+
const viewport = typeof window === "undefined" ? undefined : VIEWPORTS.get(window.matchMedia);
|
|
215
|
+
if (!viewport) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
"setViewportWidth: no viewport stub is in force — put one in first with " +
|
|
218
|
+
"stubViewportWidth(width), or vi.stubGlobal(\"matchMedia\", viewportMatchMedia(width)).",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
viewport.width = width;
|
|
222
|
+
for (const list of [...viewport.lists]) {
|
|
223
|
+
const matches = answer(list.media, width, viewport.options);
|
|
224
|
+
if (matches === list.matches) continue;
|
|
225
|
+
list.matches = matches;
|
|
226
|
+
list.dispatchEvent(changeEvent(list));
|
|
227
|
+
}
|
|
122
228
|
}
|
|
123
229
|
|
|
124
230
|
/**
|