@eifi1/ui-kit 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -76
- package/dist/admin/plan-change.js +2 -2
- package/dist/admin/plan-change.js.map +1 -1
- package/dist/admin/user-roster.d.ts +6 -0
- package/dist/admin/user-roster.js +4 -0
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/billing/billing-labels.d.ts +26 -0
- package/dist/billing/billing-labels.js +9 -0
- package/dist/billing/billing-labels.js.map +1 -1
- package/dist/billing/subscription-standing.d.ts +61 -0
- package/dist/billing/subscription-standing.js +51 -0
- package/dist/billing/subscription-standing.js.map +1 -0
- package/dist/billing/usage-meter.d.ts +52 -0
- package/dist/billing/usage-meter.js +52 -0
- package/dist/billing/usage-meter.js.map +1 -0
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +4 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/alert-banner.js +2 -3
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.js +3 -15
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +2 -2
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/bulk-action-bar.js +2 -1
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button.d.ts +35 -1
- package/dist/components/button.js +5 -9
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -2
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/camera-button.d.ts +89 -0
- package/dist/components/camera-button.js +122 -0
- package/dist/components/camera-button.js.map +1 -0
- package/dist/components/camera-capture.d.ts +66 -0
- package/dist/components/camera-capture.js +190 -0
- package/dist/components/camera-capture.js.map +1 -0
- package/dist/components/camera-labels.d.ts +20 -0
- package/dist/components/camera-labels.js +15 -0
- package/dist/components/camera-labels.js.map +1 -0
- package/dist/components/chart-edge.d.ts +9 -0
- package/dist/components/chart-edge.js +14 -3
- package/dist/components/chart-edge.js.map +1 -1
- package/dist/components/chart-zoom-core.js +34 -29
- package/dist/components/chart-zoom-core.js.map +1 -1
- package/dist/components/checkbox-group.js +1 -1
- package/dist/components/checkbox-group.js.map +1 -1
- package/dist/components/combobox-core.js +3 -2
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.js +2 -2
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/consent-again-notice-labels.d.ts +25 -0
- package/dist/components/consent-again-notice-labels.js +15 -0
- package/dist/components/consent-again-notice-labels.js.map +1 -0
- package/dist/components/consent-again-notice.d.ts +85 -0
- package/dist/components/consent-again-notice.js +94 -0
- package/dist/components/consent-again-notice.js.map +1 -0
- package/dist/components/country-select.js +2 -7
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/currency-select.js +2 -2
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.js +2 -2
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-virtual.js +13 -3
- package/dist/components/data-table-virtual.js.map +1 -1
- package/dist/components/date-field.js +2 -2
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.js +2 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.js +2 -6
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.js +7 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.js +2 -2
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-parts.d.ts +108 -6
- package/dist/components/field-parts.js +59 -6
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-suggestion-labels.d.ts +2 -0
- package/dist/components/field-suggestion-labels.js +10 -0
- package/dist/components/field-suggestion-labels.js.map +1 -0
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +1 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/floating-panel.d.ts +7 -6
- package/dist/components/floating-panel.js +3 -17
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +3 -2
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/hover-menu.js +3 -2
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/icon-button.d.ts +23 -5
- package/dist/components/icon-button.js +6 -8
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/inline-confirm.js +2 -1
- package/dist/components/inline-confirm.js.map +1 -1
- package/dist/components/inline-entity-combobox.js +2 -2
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/input.js.map +1 -1
- package/dist/components/jump-calendar.js +3 -3
- package/dist/components/jump-calendar.js.map +1 -1
- package/dist/components/legal.d.ts +13 -1
- package/dist/components/legal.js +12 -0
- package/dist/components/legal.js.map +1 -1
- package/dist/components/legended-series-chart.d.ts +80 -0
- package/dist/components/legended-series-chart.js +70 -0
- package/dist/components/legended-series-chart.js.map +1 -0
- package/dist/components/lightbox.js +2 -1
- package/dist/components/lightbox.js.map +1 -1
- package/dist/components/mini-calendar.js +13 -5
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +23 -2
- package/dist/components/modal.js +24 -6
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +1 -1
- package/dist/components/month-picker.js +6 -6
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.js +2 -2
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/multi-select.js +2 -6
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.js +2 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/page-header-layout.d.ts +18 -0
- package/dist/components/page-header-layout.js +23 -0
- package/dist/components/page-header-layout.js.map +1 -0
- package/dist/components/page-header-skeleton.d.ts +41 -0
- package/dist/components/page-header-skeleton.js +52 -0
- package/dist/components/page-header-skeleton.js.map +1 -0
- package/dist/components/page-header.js +4 -17
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/popover.js +7 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/release-notes-labels.d.ts +16 -0
- package/dist/components/release-notes-labels.js +18 -0
- package/dist/components/release-notes-labels.js.map +1 -0
- package/dist/components/release-notes.d.ts +65 -0
- package/dist/components/release-notes.js +65 -0
- package/dist/components/release-notes.js.map +1 -0
- package/dist/components/select.js +2 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/series-chart-labels.d.ts +9 -0
- package/dist/components/series-chart-labels.js +3 -1
- package/dist/components/series-chart-labels.js.map +1 -1
- package/dist/components/series-chart.d.ts +5 -0
- package/dist/components/series-chart.js +89 -36
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/toggle-group.js +3 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/treemap-layout.js.map +1 -1
- package/dist/components/treemap.d.ts +17 -15
- package/dist/components/treemap.js +167 -85
- package/dist/components/treemap.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/eslint/role-utilities.d.ts +2 -2
- package/dist/eslint/role-utilities.js.map +1 -1
- package/dist/feedback/feedback-inbox-page.d.ts +260 -0
- package/dist/feedback/feedback-inbox-page.js +207 -0
- package/dist/feedback/feedback-inbox-page.js.map +1 -0
- package/dist/feedback/feedback-vocabulary.js +2 -2
- package/dist/feedback/feedback-vocabulary.js.map +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.js +6 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-debounce.d.ts +32 -5
- package/dist/hooks/use-debounce.js +6 -5
- package/dist/hooks/use-debounce.js.map +1 -1
- package/dist/hooks/use-file-picker.d.ts +3 -1
- package/dist/hooks/use-file-picker.js.map +1 -1
- package/dist/hooks/use-focus-trap.d.ts +19 -3
- package/dist/hooks/use-focus-trap.js +15 -5
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-has-camera.d.ts +36 -0
- package/dist/hooks/use-has-camera.js +53 -0
- package/dist/hooks/use-has-camera.js.map +1 -0
- package/dist/hooks/use-hotkey.js +1 -10
- package/dist/hooks/use-hotkey.js.map +1 -1
- package/dist/hooks/use-overlay-history.d.ts +6 -1
- package/dist/hooks/use-overlay-history.js +10 -2
- package/dist/hooks/use-overlay-history.js.map +1 -1
- package/dist/i18n/defaults.js +11 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +75 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +20 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +92 -4
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +71 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +72 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +74 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +74 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +71 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +22 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +24 -5
- package/dist/index.js +35 -1
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-labels.d.ts +8 -0
- package/dist/landing/landing-labels.js +2 -1
- package/dist/landing/landing-labels.js.map +1 -1
- package/dist/lib/changelog.d.ts +130 -0
- package/dist/lib/changelog.js +137 -0
- package/dist/lib/changelog.js.map +1 -0
- package/dist/lib/clipboard-minus.js +2 -2
- package/dist/lib/clipboard-minus.js.map +1 -1
- package/dist/lib/dates.d.ts +29 -3
- package/dist/lib/dates.js +41 -12
- package/dist/lib/dates.js.map +1 -1
- package/dist/lib/editable-content.d.ts +33 -0
- package/dist/lib/editable-content.js +15 -0
- package/dist/lib/editable-content.js.map +1 -0
- package/dist/lib/focus-left.d.ts +47 -1
- package/dist/lib/focus-left.js +11 -2
- package/dist/lib/focus-left.js.map +1 -1
- package/dist/lib/server-wake.d.ts +49 -9
- package/dist/lib/server-wake.js +25 -14
- package/dist/lib/server-wake.js.map +1 -1
- package/dist/lib/session-refresh.d.ts +53 -0
- package/dist/lib/session-refresh.js +27 -1
- package/dist/lib/session-refresh.js.map +1 -1
- package/dist/rhf/fields.js +3 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/shell/app-shell.d.ts +25 -1
- package/dist/shell/app-shell.js +3 -1
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +3 -0
- package/dist/shell/dev-menu.js +4 -2
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/held-changes-banner-labels.d.ts +23 -0
- package/dist/shell/held-changes-banner-labels.js +14 -0
- package/dist/shell/held-changes-banner-labels.js.map +1 -0
- package/dist/shell/held-changes-banner.d.ts +75 -0
- package/dist/shell/held-changes-banner.js +106 -0
- package/dist/shell/held-changes-banner.js.map +1 -0
- package/dist/shell/sync-status-indicator.d.ts +27 -12
- package/dist/shell/sync-status-indicator.js +13 -4
- package/dist/shell/sync-status-indicator.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +5 -0
- package/dist/shell/top-bar-parts-labels.js +3 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +3 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/engine.d.ts +11 -2
- package/dist/sync/engine.js +13 -5
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/hooks.d.ts +3 -2
- package/dist/sync/hooks.js +4 -2
- package/dist/sync/hooks.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +2 -2
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +18 -0
- package/dist/theme/chart-palette.js +17 -1
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/contrast-tokens.d.ts +3 -3
- package/dist/theme/contrast-tokens.js.map +1 -1
- package/dist/theme/contrast.d.ts +4 -4
- package/dist/theme/contrast.js.map +1 -1
- package/dist/tour/tour-overlay.js +2 -1
- package/dist/tour/tour-overlay.js.map +1 -1
- package/package.json +5 -9
- package/tokens.css +19 -54
- package/src/account/account-parts.ts +0 -47
- package/src/account/account-settings-labels.ts +0 -240
- package/src/account/account-settings.tsx +0 -698
- package/src/account/data-export-setting-labels.ts +0 -20
- package/src/account/data-export-setting.tsx +0 -200
- package/src/account/delete-account-setting-labels.ts +0 -33
- package/src/account/delete-account-setting.tsx +0 -342
- package/src/account/email-change-setting-labels.ts +0 -31
- package/src/account/email-change-setting.tsx +0 -443
- package/src/account/passkeys-setting.tsx +0 -445
- package/src/account/sessions-setting-labels.ts +0 -24
- package/src/account/sessions-setting.tsx +0 -283
- package/src/admin/admin-action-confirm-labels.ts +0 -19
- package/src/admin/admin-action-confirm.tsx +0 -509
- package/src/admin/admin-action-log-labels.ts +0 -83
- package/src/admin/admin-action-log.tsx +0 -464
- package/src/admin/admin-area-labels.ts +0 -114
- package/src/admin/admin-forbidden.tsx +0 -75
- package/src/admin/admin-overview.tsx +0 -258
- package/src/admin/admin-parts.ts +0 -68
- package/src/admin/admin-sections.tsx +0 -208
- package/src/admin/admin-tool-links.tsx +0 -90
- package/src/admin/invitations-panel-labels.ts +0 -31
- package/src/admin/invitations-panel.tsx +0 -516
- package/src/admin/mail-test-card-labels.ts +0 -19
- package/src/admin/mail-test-card.tsx +0 -189
- package/src/admin/plan-change-labels.ts +0 -5
- package/src/admin/plan-change.tsx +0 -474
- package/src/admin/reviewer-scope-editor-labels.ts +0 -16
- package/src/admin/reviewer-scope-editor.tsx +0 -224
- package/src/admin/role-select-labels.ts +0 -16
- package/src/admin/role-select.tsx +0 -282
- package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
- package/src/admin/transfer-ownership-dialog.tsx +0 -162
- package/src/admin/user-roster-labels.ts +0 -17
- package/src/admin/user-roster.tsx +0 -559
- package/src/auth/accept-invitation-labels.ts +0 -21
- package/src/auth/accept-invitation.tsx +0 -210
- package/src/auth/auth-errors-labels.ts +0 -23
- package/src/auth/auth-errors.ts +0 -439
- package/src/auth/complete-name-dialog-labels.ts +0 -16
- package/src/auth/complete-name-dialog.tsx +0 -253
- package/src/auth/email-tag.ts +0 -75
- package/src/auth/forgot-password-form-labels.ts +0 -20
- package/src/auth/forgot-password-form.tsx +0 -221
- package/src/auth/form-rules.ts +0 -40
- package/src/auth/new-password-fields.tsx +0 -85
- package/src/auth/not-found-page-labels.ts +0 -12
- package/src/auth/not-found-page.tsx +0 -95
- package/src/auth/register-form-labels.ts +0 -31
- package/src/auth/register-form.tsx +0 -456
- package/src/auth/reset-password-form-labels.ts +0 -27
- package/src/auth/reset-password-form.tsx +0 -341
- package/src/auth/sign-in-form-labels.ts +0 -44
- package/src/auth/sign-in-form.tsx +0 -858
- package/src/auth/status-parts.tsx +0 -132
- package/src/auth/verify-email-labels.ts +0 -26
- package/src/auth/verify-email.tsx +0 -445
- package/src/billing/billing-banner.tsx +0 -258
- package/src/billing/billing-labels.ts +0 -381
- package/src/billing/billing-lock.ts +0 -108
- package/src/billing/billing-overview.ts +0 -157
- package/src/billing/billing-standing.ts +0 -80
- package/src/billing/cancel-request.ts +0 -97
- package/src/billing/cancel-subscription.tsx +0 -98
- package/src/billing/checkout-processing.ts +0 -394
- package/src/billing/checkout-return.ts +0 -90
- package/src/billing/pay-page.ts +0 -330
- package/src/billing/plan-card.tsx +0 -562
- package/src/billing/plan-change-labels.ts +0 -66
- package/src/billing/plan-limit-notice.tsx +0 -148
- package/src/billing/plan-limit-toast.tsx +0 -90
- package/src/billing/plan-limit.ts +0 -52
- package/src/billing/plan-price.ts +0 -98
- package/src/billing/plan-switch.tsx +0 -216
- package/src/billing/provider-page.ts +0 -84
- package/src/billing/subscription-actions.tsx +0 -247
- package/src/billing/subscription-status-chip.tsx +0 -95
- package/src/chart.ts +0 -103
- package/src/components/account-chips-labels.ts +0 -16
- package/src/components/account-chips.tsx +0 -474
- package/src/components/alert-banner.tsx +0 -670
- package/src/components/amount-input.tsx +0 -816
- package/src/components/appearance-labels.ts +0 -63
- package/src/components/authed-image-labels.ts +0 -11
- package/src/components/authed-image.tsx +0 -209
- package/src/components/autocomplete.tsx +0 -645
- package/src/components/breadcrumbs-labels.ts +0 -10
- package/src/components/breadcrumbs.tsx +0 -205
- package/src/components/bulk-action-bar-labels.ts +0 -11
- package/src/components/bulk-action-bar.tsx +0 -443
- package/src/components/button-group.tsx +0 -223
- package/src/components/button-look.ts +0 -256
- package/src/components/button.tsx +0 -339
- package/src/components/calculator.tsx +0 -341
- package/src/components/calendar-heatmap-labels.ts +0 -15
- package/src/components/calendar-heatmap.tsx +0 -600
- package/src/components/card.tsx +0 -377
- package/src/components/chart-edge.ts +0 -106
- package/src/components/chart-figure.tsx +0 -35
- package/src/components/chart-height.ts +0 -30
- package/src/components/chart-zoom-core.tsx +0 -627
- package/src/components/chart-zoom.tsx +0 -22
- package/src/components/chart.tsx +0 -554
- package/src/components/checkbox-group.tsx +0 -247
- package/src/components/checkbox.tsx +0 -346
- package/src/components/chip-input.tsx +0 -370
- package/src/components/chip-labels.ts +0 -13
- package/src/components/chip.tsx +0 -843
- package/src/components/choice-card.tsx +0 -544
- package/src/components/column-mapper-labels.ts +0 -38
- package/src/components/column-mapper.tsx +0 -655
- package/src/components/combobox-core.tsx +0 -1321
- package/src/components/combobox-suggestions.tsx +0 -131
- package/src/components/combobox.tsx +0 -716
- package/src/components/confirm-context.ts +0 -7
- package/src/components/confirm-dialog-labels.ts +0 -13
- package/src/components/confirm-dialog.tsx +0 -341
- package/src/components/copy-button-labels.ts +0 -13
- package/src/components/copy-button.tsx +0 -192
- package/src/components/country-select-labels.ts +0 -11
- package/src/components/country-select.tsx +0 -539
- package/src/components/currency-option-list.tsx +0 -188
- package/src/components/currency-select.tsx +0 -343
- package/src/components/danger-confirm-labels.ts +0 -20
- package/src/components/danger-confirm.tsx +0 -987
- package/src/components/data-table-cells.tsx +0 -118
- package/src/components/data-table-column-settings.tsx +0 -198
- package/src/components/data-table-filter-popover.tsx +0 -289
- package/src/components/data-table-filters.ts +0 -315
- package/src/components/data-table-fit.ts +0 -301
- package/src/components/data-table-groups.tsx +0 -288
- package/src/components/data-table-head.tsx +0 -280
- package/src/components/data-table-labels.ts +0 -198
- package/src/components/data-table-leading-row.tsx +0 -58
- package/src/components/data-table-mobile.tsx +0 -1117
- package/src/components/data-table-pagination.tsx +0 -202
- package/src/components/data-table-resize.ts +0 -188
- package/src/components/data-table-row-actions.tsx +0 -302
- package/src/components/data-table-row.tsx +0 -496
- package/src/components/data-table-selection.ts +0 -71
- package/src/components/data-table-shared.tsx +0 -287
- package/src/components/data-table-sort.ts +0 -163
- package/src/components/data-table-virtual.ts +0 -576
- package/src/components/data-table.tsx +0 -2598
- package/src/components/date-field.tsx +0 -684
- package/src/components/date-format-setting.tsx +0 -64
- package/src/components/date-picker.tsx +0 -404
- package/src/components/date-range-picker.tsx +0 -518
- package/src/components/description-list-labels.ts +0 -9
- package/src/components/description-list.tsx +0 -386
- package/src/components/dialog-frame-labels.ts +0 -7
- package/src/components/dialog-frame.tsx +0 -284
- package/src/components/disclosure.tsx +0 -476
- package/src/components/dropdown.tsx +0 -461
- package/src/components/empty-state.tsx +0 -136
- package/src/components/entity-combobox.tsx +0 -422
- package/src/components/error-boundary-labels.ts +0 -20
- package/src/components/error-boundary.tsx +0 -675
- package/src/components/facing-pair.ts +0 -93
- package/src/components/field-base-labels.ts +0 -20
- package/src/components/field-base.tsx +0 -656
- package/src/components/field-parts.tsx +0 -825
- package/src/components/field-strip.tsx +0 -129
- package/src/components/field.tsx +0 -206
- package/src/components/file-button.tsx +0 -295
- package/src/components/file-dropzone.tsx +0 -559
- package/src/components/floating-panel-labels.ts +0 -10
- package/src/components/floating-panel.tsx +0 -828
- package/src/components/focus-ring.ts +0 -16
- package/src/components/form-actions-labels.ts +0 -11
- package/src/components/form-actions.tsx +0 -578
- package/src/components/full-bleed-dialog.tsx +0 -294
- package/src/components/grouped-picker.tsx +0 -184
- package/src/components/help-popover.tsx +0 -117
- package/src/components/hover-menu.tsx +0 -343
- package/src/components/iban-input-labels.ts +0 -14
- package/src/components/iban-input.tsx +0 -236
- package/src/components/icon-button.tsx +0 -959
- package/src/components/icon-picker-labels.ts +0 -13
- package/src/components/icon-picker.tsx +0 -232
- package/src/components/image-grid-labels.ts +0 -11
- package/src/components/image-grid.tsx +0 -202
- package/src/components/inline-confirm.tsx +0 -116
- package/src/components/inline-edit-field-labels.ts +0 -11
- package/src/components/inline-edit-field.tsx +0 -552
- package/src/components/inline-entity-combobox.tsx +0 -849
- package/src/components/input.tsx +0 -394
- package/src/components/jump-calendar.tsx +0 -265
- package/src/components/language-select.tsx +0 -93
- package/src/components/legal-labels.ts +0 -105
- package/src/components/legal-page.tsx +0 -711
- package/src/components/legal.tsx +0 -668
- package/src/components/lightbox-labels.ts +0 -18
- package/src/components/lightbox.tsx +0 -399
- package/src/components/line-items-labels.ts +0 -14
- package/src/components/line-items.tsx +0 -602
- package/src/components/list-labels.ts +0 -12
- package/src/components/list.tsx +0 -870
- package/src/components/loading-state.tsx +0 -78
- package/src/components/measured-grid-labels.ts +0 -25
- package/src/components/measured-grid.tsx +0 -837
- package/src/components/menu-item.tsx +0 -241
- package/src/components/mini-calendar-labels.ts +0 -17
- package/src/components/mini-calendar.tsx +0 -792
- package/src/components/modal.tsx +0 -534
- package/src/components/money-draft.ts +0 -83
- package/src/components/money-field.tsx +0 -76
- package/src/components/month-grid.tsx +0 -229
- package/src/components/month-picker-labels.ts +0 -22
- package/src/components/month-picker.tsx +0 -1087
- package/src/components/multi-entity-combobox.tsx +0 -369
- package/src/components/multi-select.tsx +0 -376
- package/src/components/nav-pills.tsx +0 -198
- package/src/components/number-field.tsx +0 -327
- package/src/components/number-input.tsx +0 -559
- package/src/components/numeric-field.ts +0 -169
- package/src/components/numpad-sheet.tsx +0 -285
- package/src/components/one-time-code-input.tsx +0 -270
- package/src/components/page-contents-labels.ts +0 -9
- package/src/components/page-contents.tsx +0 -282
- package/src/components/page-header-context.ts +0 -8
- package/src/components/page-header.tsx +0 -298
- package/src/components/password-strength-labels.ts +0 -22
- package/src/components/password-strength.tsx +0 -241
- package/src/components/phone-input-labels.ts +0 -10
- package/src/components/phone-input.tsx +0 -282
- package/src/components/picker-sheet.tsx +0 -330
- package/src/components/pie-chart-labels.ts +0 -40
- package/src/components/pie-chart.tsx +0 -480
- package/src/components/popover-labels.ts +0 -14
- package/src/components/popover.tsx +0 -268
- package/src/components/progress-bar-labels.ts +0 -10
- package/src/components/progress-bar.tsx +0 -479
- package/src/components/qr-code.tsx +0 -76
- package/src/components/reauth-dialog-labels.ts +0 -13
- package/src/components/reauth-dialog.tsx +0 -265
- package/src/components/row-actions-labels.ts +0 -12
- package/src/components/row-actions.tsx +0 -795
- package/src/components/scroll-area.tsx +0 -123
- package/src/components/search-field.tsx +0 -192
- package/src/components/select.tsx +0 -294
- package/src/components/separator.tsx +0 -33
- package/src/components/series-chart-axes.ts +0 -342
- package/src/components/series-chart-budget.ts +0 -150
- package/src/components/series-chart-labels.ts +0 -33
- package/src/components/series-chart-thin.ts +0 -180
- package/src/components/series-chart-ticks.ts +0 -194
- package/src/components/series-chart.tsx +0 -1906
- package/src/components/server-wake-labels.ts +0 -11
- package/src/components/server-wake.tsx +0 -129
- package/src/components/settings-fields.tsx +0 -237
- package/src/components/share-card-labels.ts +0 -30
- package/src/components/share-card.tsx +0 -675
- package/src/components/signature-pad-labels.ts +0 -24
- package/src/components/signature-pad.tsx +0 -747
- package/src/components/signed-amount-labels.ts +0 -10
- package/src/components/signed-amount.tsx +0 -336
- package/src/components/skeleton.tsx +0 -80
- package/src/components/slider.tsx +0 -338
- package/src/components/sparkline-labels.ts +0 -14
- package/src/components/sparkline.tsx +0 -322
- package/src/components/spinner.tsx +0 -81
- package/src/components/stat-tile-labels.ts +0 -15
- package/src/components/stat-tile.tsx +0 -754
- package/src/components/status-dot.tsx +0 -170
- package/src/components/swatch-picker-labels.ts +0 -10
- package/src/components/swatch-picker.tsx +0 -283
- package/src/components/swipeable-row.tsx +0 -386
- package/src/components/switch.tsx +0 -273
- package/src/components/table-skeleton.tsx +0 -136
- package/src/components/table.tsx +0 -647
- package/src/components/tabs-labels.ts +0 -13
- package/src/components/tabs.tsx +0 -693
- package/src/components/text-link.tsx +0 -359
- package/src/components/text.tsx +0 -150
- package/src/components/textarea.tsx +0 -330
- package/src/components/tile-radio.tsx +0 -332
- package/src/components/time-input.tsx +0 -121
- package/src/components/toast-labels.ts +0 -12
- package/src/components/toast.tsx +0 -519
- package/src/components/toggle-group.tsx +0 -752
- package/src/components/toggle-legend.tsx +0 -394
- package/src/components/tooltip.tsx +0 -1149
- package/src/components/translation-review-editor.tsx +0 -290
- package/src/components/translation-review-labels.ts +0 -181
- package/src/components/translation-review-parts.tsx +0 -272
- package/src/components/translation-review.tsx +0 -1240
- package/src/components/tree-view.tsx +0 -589
- package/src/components/treemap-labels.ts +0 -47
- package/src/components/treemap-layout.ts +0 -210
- package/src/components/treemap.tsx +0 -1184
- package/src/components/trigger-aria.ts +0 -44
- package/src/components/use-anchor-dir.ts +0 -20
- package/src/components/use-mobile-reveal.ts +0 -62
- package/src/components/use-table-state.ts +0 -753
- package/src/components/user-avatar.tsx +0 -116
- package/src/components/wizard-stepper.tsx +0 -42
- package/src/components/write-lock-labels.ts +0 -10
- package/src/components/write-lock.tsx +0 -253
- package/src/currencies.ts +0 -90
- package/src/data-table.ts +0 -42
- package/src/demo/demo-banner.tsx +0 -153
- package/src/demo/demo-countdown.ts +0 -136
- package/src/demo/demo-ended.tsx +0 -90
- package/src/demo/demo-labels.ts +0 -78
- package/src/demo/demo-session.ts +0 -32
- package/src/demo/demo-start.tsx +0 -156
- package/src/eslint/index.ts +0 -161
- package/src/eslint/role-utilities.generated.ts +0 -146
- package/src/eslint/role-utilities.ts +0 -233
- package/src/feedback/feedback-attachment-download.tsx +0 -77
- package/src/feedback/feedback-attachment-labels.ts +0 -37
- package/src/feedback/feedback-attachment.tsx +0 -1239
- package/src/feedback/feedback-capture.ts +0 -287
- package/src/feedback/feedback-comment-thread.tsx +0 -353
- package/src/feedback/feedback-context-labels.ts +0 -11
- package/src/feedback/feedback-context.tsx +0 -245
- package/src/feedback/feedback-crash.ts +0 -547
- package/src/feedback/feedback-diagnostics.tsx +0 -41
- package/src/feedback/feedback-dialog-labels.ts +0 -29
- package/src/feedback/feedback-dialog.tsx +0 -452
- package/src/feedback/feedback-entry-file.tsx +0 -150
- package/src/feedback/feedback-inbox.tsx +0 -552
- package/src/feedback/feedback-labels.ts +0 -144
- package/src/feedback/feedback-menu-labels.ts +0 -12
- package/src/feedback/feedback-menu.tsx +0 -177
- package/src/feedback/feedback-mutation.ts +0 -20
- package/src/feedback/feedback-page-link.ts +0 -90
- package/src/feedback/feedback-public-labels.ts +0 -81
- package/src/feedback/feedback-public-list.tsx +0 -524
- package/src/feedback/feedback-public-switch.tsx +0 -102
- package/src/feedback/feedback-public-types.ts +0 -101
- package/src/feedback/feedback-record.ts +0 -428
- package/src/feedback/feedback-replies-context.ts +0 -9
- package/src/feedback/feedback-replies.tsx +0 -343
- package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
- package/src/feedback/feedback-row-detail-labels.ts +0 -31
- package/src/feedback/feedback-row-detail.tsx +0 -871
- package/src/feedback/feedback-status-undo.ts +0 -109
- package/src/feedback/feedback-submit-dialog.tsx +0 -131
- package/src/feedback/feedback-submit.tsx +0 -270
- package/src/feedback/feedback-swipe.tsx +0 -192
- package/src/feedback/feedback-table-labels.ts +0 -27
- package/src/feedback/feedback-table.tsx +0 -912
- package/src/feedback/feedback-thread-labels.ts +0 -22
- package/src/feedback/feedback-thread-parts-labels.ts +0 -41
- package/src/feedback/feedback-thread.tsx +0 -905
- package/src/feedback/feedback-vocabulary.ts +0 -263
- package/src/feedback/feedback-vote-button.tsx +0 -124
- package/src/feedback.ts +0 -116
- package/src/hooks/use-anchored-panel.ts +0 -396
- package/src/hooks/use-anchored-rect.ts +0 -77
- package/src/hooks/use-announce.ts +0 -85
- package/src/hooks/use-authed-src.ts +0 -167
- package/src/hooks/use-body-scroll-lock.ts +0 -119
- package/src/hooks/use-breakpoint.ts +0 -133
- package/src/hooks/use-close-transition.ts +0 -107
- package/src/hooks/use-copy-to-clipboard.ts +0 -122
- package/src/hooks/use-debounce.ts +0 -114
- package/src/hooks/use-dismiss.ts +0 -371
- package/src/hooks/use-document-title.ts +0 -33
- package/src/hooks/use-file-drop.ts +0 -121
- package/src/hooks/use-file-picker-labels.ts +0 -28
- package/src/hooks/use-file-picker.tsx +0 -359
- package/src/hooks/use-focus-trap.ts +0 -199
- package/src/hooks/use-hotkey.ts +0 -212
- package/src/hooks/use-large-text.ts +0 -41
- package/src/hooks/use-long-press.ts +0 -140
- package/src/hooks/use-media-query.ts +0 -39
- package/src/hooks/use-mounted.ts +0 -39
- package/src/hooks/use-noindex.ts +0 -37
- package/src/hooks/use-overlay-history.ts +0 -714
- package/src/hooks/use-row-swipe.ts +0 -207
- package/src/hooks/use-search-param-state.ts +0 -434
- package/src/hooks/use-windowed-rows.ts +0 -225
- package/src/i18n/defaults.ts +0 -267
- package/src/i18n/german.ts +0 -2059
- package/src/i18n/kit-labels.tsx +0 -1003
- package/src/i18n/languages.ts +0 -349
- package/src/i18n/locales/de-CH.ts +0 -24
- package/src/i18n/locales/en.ts +0 -267
- package/src/i18n/locales/es.ts +0 -1999
- package/src/i18n/locales/fr.ts +0 -2037
- package/src/i18n/locales/hu.ts +0 -2004
- package/src/i18n/locales/it.ts +0 -2014
- package/src/i18n/locales/zh.ts +0 -1912
- package/src/i18n/plurals.ts +0 -86
- package/src/i18n/review.ts +0 -475
- package/src/i18n/shared-labels.ts +0 -138
- package/src/i18n/swiss.ts +0 -29
- package/src/index.ts +0 -1191
- package/src/landing/access.ts +0 -93
- package/src/landing/landing-actions.tsx +0 -97
- package/src/landing/landing-labels.ts +0 -59
- package/src/landing/landing-sections.tsx +0 -341
- package/src/landing/page-seo.ts +0 -261
- package/src/landing/public-header.tsx +0 -121
- package/src/landing/public-page.tsx +0 -106
- package/src/landing/routing.tsx +0 -225
- package/src/lib/broadcast.ts +0 -52
- package/src/lib/calc.ts +0 -323
- package/src/lib/chip-edge-snap.ts +0 -212
- package/src/lib/clipboard-minus.ts +0 -107
- package/src/lib/clipping.ts +0 -34
- package/src/lib/cn.ts +0 -28
- package/src/lib/column-mapping.ts +0 -234
- package/src/lib/countries.ts +0 -91
- package/src/lib/data-attributes.ts +0 -14
- package/src/lib/date-format.ts +0 -227
- package/src/lib/dates.ts +0 -288
- package/src/lib/day-format.ts +0 -96
- package/src/lib/decimal-marks.ts +0 -108
- package/src/lib/describe-failure.ts +0 -18
- package/src/lib/device-settings.ts +0 -147
- package/src/lib/direction.ts +0 -74
- package/src/lib/document-navigation.ts +0 -17
- package/src/lib/document-title.ts +0 -41
- package/src/lib/download.ts +0 -172
- package/src/lib/float-clearance.ts +0 -70
- package/src/lib/floating-stack.ts +0 -70
- package/src/lib/focus-left.ts +0 -156
- package/src/lib/format.ts +0 -585
- package/src/lib/http-status.ts +0 -38
- package/src/lib/iban.ts +0 -251
- package/src/lib/ime.ts +0 -24
- package/src/lib/intl-cache.ts +0 -27
- package/src/lib/iso-day.ts +0 -13
- package/src/lib/lazy-with-fallback.ts +0 -82
- package/src/lib/logger.ts +0 -69
- package/src/lib/minus.ts +0 -50
- package/src/lib/path-pattern.ts +0 -55
- package/src/lib/person-name.ts +0 -155
- package/src/lib/phone.ts +0 -208
- package/src/lib/plain-text.ts +0 -22
- package/src/lib/presence.ts +0 -18
- package/src/lib/qr-encode.ts +0 -444
- package/src/lib/quiet-focus.ts +0 -32
- package/src/lib/reduced-motion.ts +0 -22
- package/src/lib/refs.ts +0 -22
- package/src/lib/round.ts +0 -43
- package/src/lib/safe-storage.ts +0 -119
- package/src/lib/server-wake.ts +0 -315
- package/src/lib/session-refresh.ts +0 -472
- package/src/lib/share-format.ts +0 -18
- package/src/lib/status-tone.ts +0 -43
- package/src/lib/strip-fade.ts +0 -287
- package/src/lib/table-text.ts +0 -554
- package/src/lib/text-case.ts +0 -19
- package/src/lib/text-match.ts +0 -169
- package/src/lib/thenable.ts +0 -19
- package/src/lib/translation-review.ts +0 -769
- package/src/notifications/device-zone-report.ts +0 -67
- package/src/notifications/feedback-notification-types-labels.ts +0 -33
- package/src/notifications/feedback-notification-types.ts +0 -71
- package/src/notifications/notification-bell.tsx +0 -119
- package/src/notifications/notification-inbox.tsx +0 -309
- package/src/notifications/notification-parts.tsx +0 -81
- package/src/notifications/notification-types-setting.tsx +0 -406
- package/src/notifications/notification-types.ts +0 -89
- package/src/notifications/notifications-labels.ts +0 -287
- package/src/notifications/push-device-setting.tsx +0 -230
- package/src/notifications/push-devices-setting.tsx +0 -374
- package/src/notifications/push-worker.ts +0 -168
- package/src/notifications/quiet-hours-setting.tsx +0 -238
- package/src/notifications/unsubscribe-panel.tsx +0 -200
- package/src/notifications/use-push-device.ts +0 -448
- package/src/offline/local-first.ts +0 -100
- package/src/offline/pwa-update-prompt-labels.ts +0 -9
- package/src/offline/pwa-update-prompt.tsx +0 -78
- package/src/offline/query-persistence.ts +0 -391
- package/src/offline/service-worker-updates.ts +0 -81
- package/src/offline/shared-computer-labels.ts +0 -8
- package/src/offline/shared-computer.tsx +0 -87
- package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
- package/src/offline/sign-out-pending-dialog.tsx +0 -135
- package/src/offline.ts +0 -37
- package/src/push-worker.ts +0 -25
- package/src/rhf/fields.tsx +0 -2187
- package/src/rhf/form-core.tsx +0 -324
- package/src/rhf/form.tsx +0 -12
- package/src/rhf/line-items.tsx +0 -135
- package/src/rhf/use-rhf-wizard-step.ts +0 -104
- package/src/rhf.ts +0 -21
- package/src/search/command-palette-labels.ts +0 -16
- package/src/search/command-palette.tsx +0 -624
- package/src/search/global-search-labels.ts +0 -13
- package/src/search/global-search.tsx +0 -660
- package/src/search/search-index.ts +0 -283
- package/src/search/use-command-key.ts +0 -23
- package/src/search.ts +0 -14
- package/src/settings/device-reset-setting-labels.ts +0 -20
- package/src/settings/device-reset-setting.tsx +0 -116
- package/src/settings/settings-card-classes.ts +0 -11
- package/src/settings/settings-catalogue.ts +0 -150
- package/src/settings/settings-context.ts +0 -33
- package/src/settings/settings-heading.tsx +0 -67
- package/src/settings/settings-labels.ts +0 -87
- package/src/settings/settings-layout.tsx +0 -471
- package/src/settings/settings-section.tsx +0 -108
- package/src/settings/use-account-appearance.ts +0 -148
- package/src/settings/use-account-language.ts +0 -146
- package/src/settings/use-settings-focus.ts +0 -128
- package/src/settings/use-settings-route.ts +0 -258
- package/src/shell/account-menu-language.tsx +0 -130
- package/src/shell/account-menu-text-size.tsx +0 -80
- package/src/shell/admin-menu.tsx +0 -183
- package/src/shell/app-shell-labels.ts +0 -11
- package/src/shell/app-shell.tsx +0 -1560
- package/src/shell/auth-layout.tsx +0 -186
- package/src/shell/company-switcher-labels.ts +0 -13
- package/src/shell/company-switcher.tsx +0 -136
- package/src/shell/dev-menu.tsx +0 -257
- package/src/shell/fill-height.tsx +0 -45
- package/src/shell/language-code.ts +0 -5
- package/src/shell/option-switcher-menu.tsx +0 -84
- package/src/shell/sync-status-indicator.tsx +0 -303
- package/src/shell/top-bar-actions.tsx +0 -87
- package/src/shell/top-bar-brand.tsx +0 -78
- package/src/shell/top-bar-parts-labels.ts +0 -148
- package/src/shell/top-bar.tsx +0 -40
- package/src/shell/topbar-action-menu.tsx +0 -567
- package/src/shell/topbar-controls.tsx +0 -269
- package/src/shell/tour-menu.tsx +0 -187
- package/src/shell.ts +0 -108
- package/src/sync/db.ts +0 -129
- package/src/sync/engine.ts +0 -1011
- package/src/sync/hooks.ts +0 -98
- package/src/sync/testing.ts +0 -73
- package/src/sync/wire.ts +0 -61
- package/src/sync.ts +0 -37
- package/src/table-text.ts +0 -8
- package/src/testing/css-variables.generated.ts +0 -112
- package/src/testing/css-variables.ts +0 -189
- package/src/testing/index.ts +0 -29
- package/src/testing/react.ts +0 -58
- package/src/testing/tour.ts +0 -41
- package/src/testing/viewport.ts +0 -357
- package/src/theme/chart-palette.ts +0 -232
- package/src/theme/color.ts +0 -272
- package/src/theme/contrast-tokens.ts +0 -268
- package/src/theme/contrast.ts +0 -250
- package/src/theme/palette-derive.ts +0 -825
- package/src/theme/palette-presets.ts +0 -544
- package/src/theme/palette-store.ts +0 -108
- package/src/theme/text-size.ts +0 -289
- package/src/theme/theme-store.ts +0 -157
- package/src/tour/tour-catalogue-labels.ts +0 -75
- package/src/tour/tour-catalogue.tsx +0 -335
- package/src/tour/tour-dock.ts +0 -134
- package/src/tour/tour-engine.ts +0 -37
- package/src/tour/tour-gates.ts +0 -218
- package/src/tour/tour-guide-button.tsx +0 -64
- package/src/tour/tour-guide-labels.ts +0 -15
- package/src/tour/tour-info.ts +0 -144
- package/src/tour/tour-labels.ts +0 -23
- package/src/tour/tour-overlay.tsx +0 -1170
- package/src/tour/tour.tsx +0 -750
- package/src/tour/use-tour-progress.ts +0 -671
- package/src/tour/use-tour-url-sync.ts +0 -266
- package/src/tour.ts +0 -21
- package/src/vite-env.d.ts +0 -23
- package/src/wizard/stepper-nav.tsx +0 -360
- package/src/wizard/types.ts +0 -275
- package/src/wizard/use-wizard.ts +0 -560
- package/src/wizard/validation.ts +0 -39
- package/src/wizard/wizard-context.tsx +0 -74
- package/src/wizard/wizard-labels.ts +0 -29
- package/src/wizard/wizard-step.tsx +0 -76
- package/src/wizard/wizard-summary.tsx +0 -84
- package/src/wizard.ts +0 -15
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { useMediaQuery } from "../hooks/use-media-query";
|
|
4
|
-
import { httpStatusOf } from "../lib/http-status";
|
|
5
|
-
import { PUSH_SUBSCRIPTION_CHANGED, keyBytes, pushSubscriptionInput as inputOf } from "./push-worker";
|
|
6
|
-
import type { PushSubscriptionInput } from "./push-worker";
|
|
7
|
-
|
|
8
|
-
export { PUSH_SUBSCRIPTION_CHANGED };
|
|
9
|
-
export type { PushSubscriptionInput };
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* This browser's Web Push subscription for the signed-in account
|
|
13
|
-
* (docs/notifications-harmonization.md §2.7, §3.5, §4.2) — keksdose's
|
|
14
|
-
* `shared/push/use-push.ts`, lifted. The service worker with its `push` and
|
|
15
|
-
* `notificationclick` handlers stays the app's; this hook owns the browser's side:
|
|
16
|
-
* permission, subscribe with the server's VAPID key, register the subscription, and
|
|
17
|
-
* unsubscribe. The kit makes no request of its own: the app hands it one function per
|
|
18
|
-
* endpoint.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** The browser's notification permission, or `"unsupported"` where it has no push. */
|
|
22
|
-
export type PushPermission = NotificationPermission | "unsupported";
|
|
23
|
-
|
|
24
|
-
/** Why turning push on or off could not finish — a {@link PushDeviceError}'s `code`. */
|
|
25
|
-
export type PushDeviceErrorCode = "no_service_worker" | "push_timeout";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The two failures the card has words of its own for: no service worker registered (or
|
|
29
|
-
* one not ready in 10 s) — "reload and try again" — and a browser push service that did
|
|
30
|
-
* not answer in 15 s — "check your connection". Anything else is the app's request
|
|
31
|
-
* failing, rejected as it came.
|
|
32
|
-
*/
|
|
33
|
-
export class PushDeviceError extends Error {
|
|
34
|
-
readonly code: PushDeviceErrorCode;
|
|
35
|
-
constructor(code: PushDeviceErrorCode) {
|
|
36
|
-
super(code);
|
|
37
|
-
this.name = "PushDeviceError";
|
|
38
|
-
this.code = code;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface UsePushDeviceOptions {
|
|
43
|
-
/** `GET /notifications/push-key` → its `key`; `""` when push is off on the server. */
|
|
44
|
-
pushKey: () => Promise<string>;
|
|
45
|
-
/** `POST /notifications/devices`. A 409 (another account's endpoint) is recovered
|
|
46
|
-
* once: the hook unsubscribes and registers a fresh endpoint. */
|
|
47
|
-
register: (subscription: PushSubscriptionInput) => Promise<unknown>;
|
|
48
|
-
/** `POST /notifications/devices/unregister {endpoint}` — the endpoint never goes in a
|
|
49
|
-
* URL. */
|
|
50
|
-
unregister: (endpoint: string) => Promise<unknown>;
|
|
51
|
-
/** `GET /notifications/devices`: the account's own list, which says whether THIS
|
|
52
|
-
* account holds this browser's subscription. */
|
|
53
|
-
devices: () => Promise<readonly { endpoint: string }[]>;
|
|
54
|
-
/** `POST /notifications/test` → how many devices the push service accepted it for. */
|
|
55
|
-
test: () => Promise<{ sent: number }>;
|
|
56
|
-
/** Whether `register` refused another account's endpoint. Default: HTTP 409. */
|
|
57
|
-
isEndpointTaken?: (error: unknown) => boolean;
|
|
58
|
-
/** After an enable or a disable: the app refreshes its device queries. */
|
|
59
|
-
onChange?: () => void;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** What {@link usePushDevice} answers. */
|
|
63
|
-
export interface PushDevice {
|
|
64
|
-
/** The browser exposes service workers, `PushManager` and `Notification`. */
|
|
65
|
-
supported: boolean;
|
|
66
|
-
/** The server has VAPID keys; `undefined` until `pushKey` answered (or when it failed). */
|
|
67
|
-
configured: boolean | undefined;
|
|
68
|
-
/** Running as an installed app: `display-mode: standalone`, or iOS's
|
|
69
|
-
* `navigator.standalone`. */
|
|
70
|
-
installed: boolean;
|
|
71
|
-
/** iPhone or iPad — where push reaches only the installed app. */
|
|
72
|
-
ios: boolean;
|
|
73
|
-
permission: PushPermission;
|
|
74
|
-
/** THIS account holds this browser's subscription. An unreachable device list falls
|
|
75
|
-
* back to the browser's own answer. */
|
|
76
|
-
subscribed: boolean;
|
|
77
|
-
/** This browser holds a subscription that belongs to another account (two accounts in
|
|
78
|
-
* one browser profile): `subscribed` is false, and turning on mints a fresh one. */
|
|
79
|
-
foreignSubscription: boolean;
|
|
80
|
-
/** This browser's subscription endpoint, or `null` — marks "This device" in a list. */
|
|
81
|
-
currentEndpoint: string | null;
|
|
82
|
-
/** An enable or a disable is running. */
|
|
83
|
-
busy: boolean;
|
|
84
|
-
/** Ask for permission (the browser's one prompt), subscribe and register. `false` when
|
|
85
|
-
* the permission was not granted or push is off on the server; rejects with a
|
|
86
|
-
* {@link PushDeviceError} or the app's request's error. */
|
|
87
|
-
enable: () => Promise<boolean>;
|
|
88
|
-
/** Tell the server first, then the browser, so a dropped device stops at once. At a
|
|
89
|
-
* sign-out, with no card mounted: {@link releaseThisDevicePush}. */
|
|
90
|
-
disable: () => Promise<void>;
|
|
91
|
-
sendTest: () => Promise<{ sent: number }>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/*
|
|
95
|
-
* PUSH_SUBSCRIPTION_CHANGED (push-worker.ts): the window event every mounted
|
|
96
|
-
* {@link usePushDevice} re-reads its state on. Fired by every enable and disable, and by
|
|
97
|
-
* `PushDevicesSetting` when it removes this device — so an app's own card on the same
|
|
98
|
-
* hook (keksdose's check-up) stays in step. The hook that fires it does not re-read: it
|
|
99
|
-
* knows what it changed. Since 0.46 also the message a service worker posts after a
|
|
100
|
-
* rotated subscription was re-registered (`handlePushSubscriptionChange`). Namespaced
|
|
101
|
-
* for the kit; keksdose's was `"keksdose:push-subscription-changed"`.
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
/** Tell every {@link usePushDevice} that this browser's subscription changed. */
|
|
105
|
-
export function notifyPushSubscriptionChanged(): void {
|
|
106
|
-
if (typeof window !== "undefined") window.dispatchEvent(new Event(PUSH_SUBSCRIPTION_CHANGED));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Whether this browser has the APIs push needs. iOS exposes them only to an INSTALLED
|
|
110
|
-
* app (Add to Home Screen). */
|
|
111
|
-
export function pushSupported(): boolean {
|
|
112
|
-
return (
|
|
113
|
-
typeof window !== "undefined" &&
|
|
114
|
-
typeof navigator !== "undefined" &&
|
|
115
|
-
"serviceWorker" in navigator &&
|
|
116
|
-
"PushManager" in window &&
|
|
117
|
-
"Notification" in window
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** iPhone or iPad. iPadOS 13+ reports itself as a Mac; the touch points tell it apart. */
|
|
122
|
-
function isIos(): boolean {
|
|
123
|
-
if (typeof navigator === "undefined") return false;
|
|
124
|
-
const ua = navigator.userAgent;
|
|
125
|
-
return /iphone|ipad|ipod/i.test(ua) || (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const READY_TIMEOUT_MS = 10_000;
|
|
129
|
-
const PUSH_TIMEOUT_MS = 15_000;
|
|
130
|
-
|
|
131
|
-
/** `promise`, or a {@link PushDeviceError} after `ms`. The timer is cleared either way. */
|
|
132
|
-
function within<T>(promise: Promise<T>, ms: number, code: PushDeviceErrorCode): Promise<T> {
|
|
133
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
134
|
-
return Promise.race([
|
|
135
|
-
promise,
|
|
136
|
-
new Promise<never>((_, reject) => {
|
|
137
|
-
timer = setTimeout(() => reject(new PushDeviceError(code)), ms);
|
|
138
|
-
}),
|
|
139
|
-
]).finally(() => clearTimeout(timer));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* The active registration. `navigator.serviceWorker.ready` never settles while the origin
|
|
144
|
-
* has no registration, so a missing one fails at once, and one stuck installing fails
|
|
145
|
-
* after 10 s (keksdose: enable() hung with the button latched before its worker was
|
|
146
|
-
* served).
|
|
147
|
-
*/
|
|
148
|
-
async function activeRegistration(): Promise<ServiceWorkerRegistration> {
|
|
149
|
-
const existing = await navigator.serviceWorker.getRegistration();
|
|
150
|
-
if (!existing) throw new PushDeviceError("no_service_worker");
|
|
151
|
-
return within(navigator.serviceWorker.ready, READY_TIMEOUT_MS, "no_service_worker");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const defaultEndpointTaken = (error: unknown) => httpStatusOf(error) === 409;
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Drop this browser's subscription when the person turns push off: the server first, so a
|
|
158
|
-
* dropped device stops receiving even if the browser's unsubscribe fails, then the
|
|
159
|
-
* browser, cut at 15 s. Whether there was one. A refused server keeps the browser's
|
|
160
|
-
* subscription: the card says the turn-off failed, still reads on, and the person tries
|
|
161
|
-
* again. {@link usePushDevice}'s `disable`; a sign-out has its own
|
|
162
|
-
* ({@link releaseThisDevicePush}).
|
|
163
|
-
*/
|
|
164
|
-
async function releaseSubscription(unregister: (endpoint: string) => Promise<unknown>): Promise<boolean> {
|
|
165
|
-
const registration = await activeRegistration();
|
|
166
|
-
const subscription = await registration.pushManager.getSubscription();
|
|
167
|
-
if (!subscription) return false;
|
|
168
|
-
await unregister(subscription.endpoint);
|
|
169
|
-
await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Release this browser's push at an EXPLICIT sign-out (0.52, docs/notifications-
|
|
175
|
-
* harmonization.md §10.6; refactor audit 2026-10-30, FLW-2) — without the settings card
|
|
176
|
-
* mounted, which `usePushDevice().disable` needs.
|
|
177
|
-
*
|
|
178
|
-
* A sign-out left the subscription registered to the account, so the device went on
|
|
179
|
-
* showing that account's notifications, title and body (keksdose: invoices, bookings,
|
|
180
|
-
* shares), to whoever used it next — while the same sign-out wiped the mirror, the outbox
|
|
181
|
-
* and the query cache "for the next person on this device".
|
|
182
|
-
*
|
|
183
|
-
* Call it with the app's `unregister` (`POST /notifications/devices/unregister
|
|
184
|
-
* {endpoint}`) BEFORE the session is cleared, while the token is still valid:
|
|
185
|
-
*
|
|
186
|
-
* async function logOut() {
|
|
187
|
-
* await releaseThisDevicePush(api.unregisterDevice).catch(() => undefined);
|
|
188
|
-
* clear();
|
|
189
|
-
* }
|
|
190
|
-
*
|
|
191
|
-
* The server first, then the browser — and the browser EVEN WHEN THE SERVER REFUSED (0.52
|
|
192
|
-
* code review, this file): `disable`'s order left a sign-out on a flaky network, or one
|
|
193
|
-
* that met a 5xx, with the device still subscribed. Unsubscribed, the push service answers
|
|
194
|
-
* the server's next send with 410 and the server prunes the row. The browser's unsubscribe
|
|
195
|
-
* is cut at 15 s (`PushDeviceError("push_timeout")`). Then it tells every mounted
|
|
196
|
-
* {@link usePushDevice} ({@link PUSH_SUBSCRIPTION_CHANGED}).
|
|
197
|
-
*
|
|
198
|
-
* Resolves whether this browser held a subscription; `false` at once where push is
|
|
199
|
-
* unsupported or was never set up — no service worker registration, or none ever active
|
|
200
|
-
* (0.52 code review: most devices, which used to reject with `no_service_worker`, after
|
|
201
|
-
* 10 s for a worker stuck installing). It asks the registration, not `ready`: a
|
|
202
|
-
* subscription belongs to the registration, and a sign-out waits for no worker. Rejects
|
|
203
|
-
* as the app's request did — once the browser has let go — or with `push_timeout`. Best
|
|
204
|
-
* effort at a sign-out: catch it, a sign-out must not stop on push.
|
|
205
|
-
*
|
|
206
|
-
* Only an explicit sign-out (Marcel's decision 2 of the 2026-10-30 audit): a session that
|
|
207
|
-
* merely expired keeps its device, and whoever signs back in turns push on again.
|
|
208
|
-
*/
|
|
209
|
-
export async function releaseThisDevicePush(unregister: (endpoint: string) => Promise<unknown>): Promise<boolean> {
|
|
210
|
-
if (!pushSupported()) return false;
|
|
211
|
-
try {
|
|
212
|
-
const registration = await navigator.serviceWorker.getRegistration();
|
|
213
|
-
// A worker never active holds no subscription: `subscribe` needs an active one.
|
|
214
|
-
if (!registration?.active) return false;
|
|
215
|
-
const subscription = await registration.pushManager.getSubscription();
|
|
216
|
-
if (!subscription) return false;
|
|
217
|
-
try {
|
|
218
|
-
await unregister(subscription.endpoint);
|
|
219
|
-
} finally {
|
|
220
|
-
await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
|
|
221
|
-
}
|
|
222
|
-
return true;
|
|
223
|
-
} finally {
|
|
224
|
-
notifyPushSubscriptionChanged();
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* This browser's push for the signed-in account. Hand it one function per endpoint
|
|
230
|
-
* (§3.8); {@link PushDeviceSetting} draws it, and an app's own card may use it too.
|
|
231
|
-
*
|
|
232
|
-
* **"Subscribed" means this account**, not "this browser holds a subscription": a
|
|
233
|
-
* browser profile's subscription has no account in it, so a second account in the same
|
|
234
|
-
* profile would read the first's as its own (keksdose). The endpoint is checked against
|
|
235
|
-
* the account's own `devices()`; when that list cannot be read (offline), the browser's
|
|
236
|
-
* answer stands — a wrong "on" costs what it did before the check, a wrong "off" invites
|
|
237
|
-
* re-subscribing a device that is fine.
|
|
238
|
-
*
|
|
239
|
-
* **A 409 from `register`** — the endpoint belongs to another account, and the server
|
|
240
|
-
* will not re-home what it cannot prove this browser holds — unsubscribes and registers
|
|
241
|
-
* a fresh endpoint, once.
|
|
242
|
-
*
|
|
243
|
-
* **Timeouts:** no registration fails at once and a worker not ready in 10 s fails, both
|
|
244
|
-
* `PushDeviceError("no_service_worker")`; the browser's subscribe and unsubscribe are cut
|
|
245
|
-
* at 15 s, `PushDeviceError("push_timeout")` — so `busy` can never latch.
|
|
246
|
-
*
|
|
247
|
-
* Every mounted hook re-reads its state on {@link PUSH_SUBSCRIPTION_CHANGED}.
|
|
248
|
-
*/
|
|
249
|
-
export function usePushDevice(options: UsePushDeviceOptions): PushDevice {
|
|
250
|
-
// The app's functions are usually new on every render; the effects read the latest.
|
|
251
|
-
const latest = useRef(options);
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
latest.current = options;
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const supported = pushSupported();
|
|
257
|
-
const ios = isIos();
|
|
258
|
-
const standalone = useMediaQuery("(display-mode: standalone)", false);
|
|
259
|
-
const installed =
|
|
260
|
-
standalone ||
|
|
261
|
-
(typeof navigator !== "undefined" && (navigator as Navigator & { standalone?: boolean }).standalone === true);
|
|
262
|
-
|
|
263
|
-
const [permission, setPermission] = useState<PushPermission>(() =>
|
|
264
|
-
supported ? Notification.permission : "unsupported",
|
|
265
|
-
);
|
|
266
|
-
const [configured, setConfigured] = useState<boolean | undefined>(undefined);
|
|
267
|
-
const [subscribed, setSubscribed] = useState(false);
|
|
268
|
-
const [foreignSubscription, setForeignSubscription] = useState(false);
|
|
269
|
-
const [currentEndpoint, setCurrentEndpoint] = useState<string | null>(null);
|
|
270
|
-
const [busy, setBusy] = useState(false);
|
|
271
|
-
const mounted = useRef(true);
|
|
272
|
-
// Set while this hook fires the event itself: it already knows what changed, and a
|
|
273
|
-
// re-read could only be worse — an app whose `devices()` answers from a cache would
|
|
274
|
-
// hand back the list from before the change.
|
|
275
|
-
const announcing = useRef(false);
|
|
276
|
-
const announce = useCallback(() => {
|
|
277
|
-
announcing.current = true;
|
|
278
|
-
try {
|
|
279
|
-
notifyPushSubscriptionChanged();
|
|
280
|
-
} finally {
|
|
281
|
-
announcing.current = false;
|
|
282
|
-
}
|
|
283
|
-
latest.current.onChange?.();
|
|
284
|
-
}, []);
|
|
285
|
-
useEffect(() => {
|
|
286
|
-
mounted.current = true;
|
|
287
|
-
return () => {
|
|
288
|
-
mounted.current = false;
|
|
289
|
-
};
|
|
290
|
-
}, []);
|
|
291
|
-
|
|
292
|
-
// Whether the server has keys ("" → push is off there). Once per mount.
|
|
293
|
-
useEffect(() => {
|
|
294
|
-
if (!supported) return;
|
|
295
|
-
let cancelled = false;
|
|
296
|
-
latest.current.pushKey().then(
|
|
297
|
-
(key) => {
|
|
298
|
-
if (!cancelled) setConfigured(key !== "");
|
|
299
|
-
},
|
|
300
|
-
() => {
|
|
301
|
-
/* unknown: the card shows the ordinary states */
|
|
302
|
-
},
|
|
303
|
-
);
|
|
304
|
-
return () => {
|
|
305
|
-
cancelled = true;
|
|
306
|
-
};
|
|
307
|
-
}, [supported]);
|
|
308
|
-
|
|
309
|
-
// What this browser holds, and whether it is this account's — at mount, and whenever
|
|
310
|
-
// a subscription changes anywhere in the app.
|
|
311
|
-
useEffect(() => {
|
|
312
|
-
if (!supported) return;
|
|
313
|
-
let cancelled = false;
|
|
314
|
-
const sync = async () => {
|
|
315
|
-
try {
|
|
316
|
-
const registration = await activeRegistration();
|
|
317
|
-
const existing = await registration.pushManager.getSubscription();
|
|
318
|
-
if (cancelled) return;
|
|
319
|
-
// Read again: the person may have changed it in the browser's settings.
|
|
320
|
-
setPermission(Notification.permission);
|
|
321
|
-
if (!existing) {
|
|
322
|
-
setSubscribed(false);
|
|
323
|
-
setForeignSubscription(false);
|
|
324
|
-
setCurrentEndpoint(null);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
let mine: boolean;
|
|
328
|
-
try {
|
|
329
|
-
const devices = await latest.current.devices();
|
|
330
|
-
mine = devices.some((device) => device.endpoint === existing.endpoint);
|
|
331
|
-
} catch {
|
|
332
|
-
mine = true;
|
|
333
|
-
}
|
|
334
|
-
if (cancelled) return;
|
|
335
|
-
setCurrentEndpoint(existing.endpoint);
|
|
336
|
-
setSubscribed(mine);
|
|
337
|
-
setForeignSubscription(!mine);
|
|
338
|
-
} catch {
|
|
339
|
-
/* no active service worker yet: not subscribed */
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
const heard = () => {
|
|
343
|
-
if (!announcing.current) void sync();
|
|
344
|
-
};
|
|
345
|
-
// The worker's word after it re-registered a rotated subscription (0.46).
|
|
346
|
-
const fromWorker = (event: MessageEvent) => {
|
|
347
|
-
if ((event.data as { type?: unknown } | null)?.type === PUSH_SUBSCRIPTION_CHANGED) void sync();
|
|
348
|
-
};
|
|
349
|
-
const worker = navigator.serviceWorker as ServiceWorkerContainer | undefined;
|
|
350
|
-
void sync();
|
|
351
|
-
window.addEventListener(PUSH_SUBSCRIPTION_CHANGED, heard);
|
|
352
|
-
worker?.addEventListener?.("message", fromWorker);
|
|
353
|
-
return () => {
|
|
354
|
-
cancelled = true;
|
|
355
|
-
window.removeEventListener(PUSH_SUBSCRIPTION_CHANGED, heard);
|
|
356
|
-
worker?.removeEventListener?.("message", fromWorker);
|
|
357
|
-
};
|
|
358
|
-
}, [supported]);
|
|
359
|
-
|
|
360
|
-
const enable = useCallback(async (): Promise<boolean> => {
|
|
361
|
-
if (!supported) return false;
|
|
362
|
-
const { pushKey, register, isEndpointTaken = defaultEndpointTaken } = latest.current;
|
|
363
|
-
setBusy(true);
|
|
364
|
-
try {
|
|
365
|
-
const granted = await Notification.requestPermission();
|
|
366
|
-
if (mounted.current) setPermission(granted);
|
|
367
|
-
if (granted !== "granted") return false;
|
|
368
|
-
|
|
369
|
-
const key = await pushKey();
|
|
370
|
-
if (!key) {
|
|
371
|
-
if (mounted.current) setConfigured(false);
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
if (mounted.current) setConfigured(true);
|
|
375
|
-
|
|
376
|
-
const registration = await activeRegistration();
|
|
377
|
-
// The one place the browser is asked for a subscription, so the timeout and the
|
|
378
|
-
// `{userVisibleOnly, applicationServerKey}` literal are on both paths.
|
|
379
|
-
const subscribe = (reuse: boolean) =>
|
|
380
|
-
within(
|
|
381
|
-
(async () =>
|
|
382
|
-
(reuse ? await registration.pushManager.getSubscription() : null) ??
|
|
383
|
-
(await registration.pushManager.subscribe({
|
|
384
|
-
userVisibleOnly: true,
|
|
385
|
-
applicationServerKey: keyBytes(key),
|
|
386
|
-
})))(),
|
|
387
|
-
PUSH_TIMEOUT_MS,
|
|
388
|
-
"push_timeout",
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
let subscription = await subscribe(true);
|
|
392
|
-
let input = inputOf(subscription);
|
|
393
|
-
if (!input) return false;
|
|
394
|
-
try {
|
|
395
|
-
await register(input);
|
|
396
|
-
} catch (error) {
|
|
397
|
-
if (!isEndpointTaken(error)) throw error;
|
|
398
|
-
await within(subscription.unsubscribe(), PUSH_TIMEOUT_MS, "push_timeout");
|
|
399
|
-
subscription = await subscribe(false);
|
|
400
|
-
input = inputOf(subscription);
|
|
401
|
-
if (!input) return false;
|
|
402
|
-
await register(input);
|
|
403
|
-
}
|
|
404
|
-
if (mounted.current) {
|
|
405
|
-
setSubscribed(true);
|
|
406
|
-
setForeignSubscription(false);
|
|
407
|
-
setCurrentEndpoint(input.endpoint);
|
|
408
|
-
}
|
|
409
|
-
announce();
|
|
410
|
-
return true;
|
|
411
|
-
} finally {
|
|
412
|
-
if (mounted.current) setBusy(false);
|
|
413
|
-
}
|
|
414
|
-
}, [supported, announce]);
|
|
415
|
-
|
|
416
|
-
const disable = useCallback(async (): Promise<void> => {
|
|
417
|
-
if (!supported) return;
|
|
418
|
-
setBusy(true);
|
|
419
|
-
try {
|
|
420
|
-
await releaseSubscription((endpoint) => latest.current.unregister(endpoint));
|
|
421
|
-
if (mounted.current) {
|
|
422
|
-
setSubscribed(false);
|
|
423
|
-
setForeignSubscription(false);
|
|
424
|
-
setCurrentEndpoint(null);
|
|
425
|
-
}
|
|
426
|
-
announce();
|
|
427
|
-
} finally {
|
|
428
|
-
if (mounted.current) setBusy(false);
|
|
429
|
-
}
|
|
430
|
-
}, [supported, announce]);
|
|
431
|
-
|
|
432
|
-
const sendTest = useCallback(() => latest.current.test(), []);
|
|
433
|
-
|
|
434
|
-
return {
|
|
435
|
-
supported,
|
|
436
|
-
configured,
|
|
437
|
-
installed,
|
|
438
|
-
ios,
|
|
439
|
-
permission,
|
|
440
|
-
subscribed,
|
|
441
|
-
foreignSubscription,
|
|
442
|
-
currentEndpoint,
|
|
443
|
-
busy,
|
|
444
|
-
enable,
|
|
445
|
-
disable,
|
|
446
|
-
sendTest,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { isTransientRefreshFailure } from "../lib/session-refresh";
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* The leaf utilities of local-first (docs/local-first-harmonization.md §2.3, 0.46): what a
|
|
5
|
-
* page and a sync engine both need, with no store behind them. One module that both
|
|
6
|
-
* `@eifi1/ui-kit/offline` and `@eifi1/ui-kit/sync` export, so `/sync` never loads
|
|
7
|
-
* `/offline`'s TanStack and idb-keyval peers, and `/offline` never loads Dexie. It imports
|
|
8
|
-
* no package at all (packaging-contract.test.ts holds that).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* An `updated_at` that wins Last-Write-Wins over `previous`: now, or one millisecond past
|
|
13
|
-
* `previous` when that is not behind this clock (keksdose's sync-client.ts:639-643).
|
|
14
|
-
*
|
|
15
|
-
* `/sync` keeps the server's row when it is STRICTLY newer, comparing this device's clock
|
|
16
|
-
* with the server's. A row the server just wrote carries the server's time, so an edit
|
|
17
|
-
* stamped by a clock a second slow would lose and come straight back. The millisecond is
|
|
18
|
-
* enough although the server keeps microseconds: `Date.parse` truncates them.
|
|
19
|
-
*/
|
|
20
|
-
export function stampAfter(previous: unknown): string {
|
|
21
|
-
const now = Date.now();
|
|
22
|
-
const prior = typeof previous === "string" ? Date.parse(previous) : Number.NaN;
|
|
23
|
-
return new Date(Number.isFinite(prior) && prior >= now ? prior + 1 : now).toISOString();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A name-based UUID, version 5 (RFC 4122 §4.3: SHA-1 of the namespace's 16 bytes and the
|
|
28
|
-
* name's UTF-8), the same as Python's `uuid.uuid5(uuid.UUID(namespace), name)` — for an id
|
|
29
|
-
* the device and the server derive alike (§2: the per-owner vote id
|
|
30
|
-
* `uuid5(namespace, "<item>:<invitation>")`). Lower case, hyphenated. `namespace` is a UUID,
|
|
31
|
-
* hyphenated or not, in either case; anything else is a `TypeError`. Uses `crypto.subtle`
|
|
32
|
-
* (a secure context: https or localhost).
|
|
33
|
-
*/
|
|
34
|
-
export async function uuid5(namespace: string, name: string): Promise<string> {
|
|
35
|
-
const hex = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i.test(namespace)
|
|
36
|
-
? namespace.replaceAll("-", "")
|
|
37
|
-
: null;
|
|
38
|
-
if (!hex) throw new TypeError(`uuid5: the namespace must be a UUID, not ${JSON.stringify(namespace)}`);
|
|
39
|
-
const nameBytes = new TextEncoder().encode(name);
|
|
40
|
-
const input = new Uint8Array(16 + nameBytes.length);
|
|
41
|
-
for (let i = 0; i < 16; i += 1) input[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
42
|
-
input.set(nameBytes, 16);
|
|
43
|
-
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-1", input)).slice(0, 16);
|
|
44
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x50; // version 5
|
|
45
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80; // the RFC 4122 variant
|
|
46
|
-
const out = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
47
|
-
return `${out.slice(0, 8)}-${out.slice(8, 12)}-${out.slice(12, 16)}-${out.slice(16, 20)}-${out.slice(20)}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The connection, not the server, failed: the request had no answer (an axios-like error
|
|
52
|
-
* without `response`, or `fetch`'s `TypeError`), or the session refresh marked a 401 as
|
|
53
|
-
* passing because the server was out of reach ({@link isTransientRefreshFailure}). A real
|
|
54
|
-
* HTTP answer — a 4xx or a 5xx — is never offline: the caller must see it.
|
|
55
|
-
*/
|
|
56
|
-
export function isOfflineError(err: unknown): boolean {
|
|
57
|
-
if (isTransientRefreshFailure(err)) return true;
|
|
58
|
-
if (typeof err !== "object" || err === null) return false;
|
|
59
|
-
const bag = err as { isAxiosError?: unknown; response?: unknown };
|
|
60
|
-
if (bag.isAxiosError === true || "response" in bag) return bag.response == null;
|
|
61
|
-
return err instanceof TypeError;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Read from the server, and from the device's copy only when the connection failed
|
|
66
|
-
* ({@link isOfflineError}); a 4xx or 5xx still throws. `onOnline` gets the server's answer
|
|
67
|
-
* before it is returned (to refresh the copy, say); a throw from it counts as a failure of
|
|
68
|
-
* the online path. The plain form of keksdose's net.ts:58, without its sealed-household
|
|
69
|
-
* and cold-start branches (those stay keksdose's).
|
|
70
|
-
*/
|
|
71
|
-
export async function readLocalFirst<T>(
|
|
72
|
-
online: () => Promise<T>,
|
|
73
|
-
offline: () => Promise<T>,
|
|
74
|
-
onOnline?: (result: T) => void | Promise<void>,
|
|
75
|
-
): Promise<T> {
|
|
76
|
-
try {
|
|
77
|
-
const result = await online();
|
|
78
|
-
await onOnline?.(result);
|
|
79
|
-
return result;
|
|
80
|
-
} catch (err) {
|
|
81
|
-
if (!isOfflineError(err)) throw err;
|
|
82
|
-
return offline();
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Write through the server first (`rest`), and queue the change on the device (`queue`)
|
|
88
|
-
* only when the connection failed; any other failure throws, nothing queued. Online writes
|
|
89
|
-
* stay server-stamped (D2). A create mints its id BEFORE calling this and sends it on both
|
|
90
|
-
* paths, so a REST create that timed out after the server applied it is not created twice
|
|
91
|
-
* (D16).
|
|
92
|
-
*/
|
|
93
|
-
export async function writeLocalFirst<T>(rest: () => Promise<T>, queue: () => Promise<T>): Promise<T> {
|
|
94
|
-
try {
|
|
95
|
-
return await rest();
|
|
96
|
-
} catch (err) {
|
|
97
|
-
if (!isOfflineError(err)) throw err;
|
|
98
|
-
return queue();
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// The English words of ./pwa-update-prompt, in a module with no component code (0.34, KS U8).
|
|
2
|
-
// keksdose's reviewed words (pwa.updateAvailable, pwa.reload) are the canon.
|
|
3
|
-
|
|
4
|
-
import type { PwaUpdatePromptLabels } from "./pwa-update-prompt";
|
|
5
|
-
|
|
6
|
-
export const DEFAULT_PWA_UPDATE_PROMPT_LABELS: PwaUpdatePromptLabels = {
|
|
7
|
-
updateAvailable: "A new version is available.",
|
|
8
|
-
reload: "Reload",
|
|
9
|
-
};
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from "react";
|
|
2
|
-
import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
3
|
-
import { toast } from "../components/toast";
|
|
4
|
-
import { DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "./pwa-update-prompt-labels";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* "A new version is available. — Reload": keksdose's service-worker toast
|
|
8
|
-
* (app/pwa-update-prompt.tsx) as a kit component (docs/local-first-harmonization.md §1.5
|
|
9
|
-
* D8). The app keeps `useRegisterSW` (vite-plugin-pwa, `registerType: "prompt"`) and hands
|
|
10
|
-
* its state in: `needRefresh` from it, `onReload` = `updateServiceWorker(true)`,
|
|
11
|
-
* `onDismiss` = `setNeedRefresh(false)`.
|
|
12
|
-
*
|
|
13
|
-
* `hold` defers the prompt while a reload would hurt — a handover being signed, a tour
|
|
14
|
-
* running. Nothing shows while it holds; the toast appears once it drops. A hold that
|
|
15
|
-
* starts while the toast is up takes it down again until it drops.
|
|
16
|
-
*
|
|
17
|
-
* Renders nothing itself: the toast needs a mounted `<Toaster>`.
|
|
18
|
-
*/
|
|
19
|
-
export interface PwaUpdatePromptLabels {
|
|
20
|
-
updateAvailable: string;
|
|
21
|
-
reload: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Defined in ./pwa-update-prompt-labels, which holds no component code (0.34, KS U8).
|
|
25
|
-
export { DEFAULT_PWA_UPDATE_PROMPT_LABELS };
|
|
26
|
-
|
|
27
|
-
export interface PwaUpdatePromptProps {
|
|
28
|
-
/** A new service worker is waiting. */
|
|
29
|
-
needRefresh: boolean;
|
|
30
|
-
/** Activates it and reloads — `updateServiceWorker(true)`. */
|
|
31
|
-
onReload: () => void;
|
|
32
|
-
/** The toast was closed without reloading. */
|
|
33
|
-
onDismiss?: () => void;
|
|
34
|
-
/** Wait: a handover is open, a tour runs. */
|
|
35
|
-
hold?: boolean;
|
|
36
|
-
labels?: LabelOverride<PwaUpdatePromptLabels>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The toast's id: one prompt at a time, replaced in place. */
|
|
40
|
-
const PWA_UPDATE_TOAST_ID = "pwa-update";
|
|
41
|
-
|
|
42
|
-
export function PwaUpdatePrompt({
|
|
43
|
-
needRefresh,
|
|
44
|
-
onReload,
|
|
45
|
-
onDismiss,
|
|
46
|
-
hold = false,
|
|
47
|
-
labels: labelsProp,
|
|
48
|
-
}: PwaUpdatePromptProps) {
|
|
49
|
-
const labels = useKitLabels("pwaUpdate", DEFAULT_PWA_UPDATE_PROMPT_LABELS, labelsProp);
|
|
50
|
-
// The callbacks are read at click time, so a new function each render does not
|
|
51
|
-
// re-raise the toast.
|
|
52
|
-
const handlers = useRef({ onReload, onDismiss });
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
handlers.current = { onReload, onDismiss };
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const show = needRefresh && !hold;
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!show) return;
|
|
60
|
-
// sonner calls `onDismiss` for a programmatic dismiss too: the one this effect makes
|
|
61
|
-
// when a hold starts must not tell the app the person waved the update away.
|
|
62
|
-
let ours = false;
|
|
63
|
-
toast(labels.updateAvailable, {
|
|
64
|
-
id: PWA_UPDATE_TOAST_ID,
|
|
65
|
-
duration: Infinity,
|
|
66
|
-
action: { label: labels.reload, onClick: () => handlers.current.onReload() },
|
|
67
|
-
onDismiss: () => {
|
|
68
|
-
if (!ours) handlers.current.onDismiss?.();
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
return () => {
|
|
72
|
-
ours = true;
|
|
73
|
-
toast.dismiss(PWA_UPDATE_TOAST_ID);
|
|
74
|
-
};
|
|
75
|
-
}, [show, labels.updateAvailable, labels.reload]);
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
|
-
}
|