@eifi1/ui-kit 0.48.2 → 0.50.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 +34 -16
- package/dist/admin/admin-action-confirm.js +1 -1
- package/dist/admin/admin-action-confirm.js.map +1 -1
- package/dist/admin/invitations-panel.js +5 -1
- package/dist/admin/invitations-panel.js.map +1 -1
- package/dist/admin/user-roster.d.ts +24 -12
- package/dist/admin/user-roster.js +21 -6
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/auth/accept-invitation.js +2 -2
- package/dist/auth/accept-invitation.js.map +1 -1
- package/dist/auth/not-found-page.js +1 -1
- package/dist/auth/not-found-page.js.map +1 -1
- package/dist/auth/reset-password-form.js +2 -2
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/verify-email.d.ts +9 -1
- package/dist/auth/verify-email.js +5 -3
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/billing/plan-card.js +2 -2
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/components/amount-input.d.ts +2 -1
- package/dist/components/amount-input.js +3 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.js +1 -0
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/breadcrumbs.d.ts +19 -1
- package/dist/components/breadcrumbs.js +11 -4
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-look.d.ts +37 -7
- package/dist/components/button-look.js +33 -6
- package/dist/components/button-look.js.map +1 -1
- package/dist/components/button.d.ts +4 -1
- package/dist/components/button.js +2 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar-heatmap.js +2 -1
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chart-edge.d.ts +10 -0
- package/dist/components/chart-edge.js +57 -0
- package/dist/components/chart-edge.js.map +1 -0
- package/dist/components/chart.js +2 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +8 -1
- package/dist/components/chip.js +4 -1
- package/dist/components/chip.js.map +1 -1
- package/dist/components/combobox-core.js +1 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox-suggestions.js +1 -0
- package/dist/components/combobox-suggestions.js.map +1 -1
- package/dist/components/confirm-context.d.ts +5 -0
- package/dist/components/confirm-context.js +7 -0
- package/dist/components/confirm-context.js.map +1 -0
- package/dist/components/confirm-dialog.js +3 -3
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/currency-select.d.ts +28 -2
- package/dist/components/currency-select.js +56 -10
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +2 -2
- package/dist/components/danger-confirm.js +1 -1
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-fit.js +21 -3
- package/dist/components/data-table-fit.js.map +1 -1
- package/dist/components/data-table-mobile.d.ts +58 -3
- package/dist/components/data-table-mobile.js +147 -16
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +2 -1
- package/dist/components/data-table-row-actions.js +7 -0
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-row.d.ts +1 -1
- package/dist/components/data-table-row.js.map +1 -1
- package/dist/components/data-table.d.ts +34 -1
- package/dist/components/data-table.js +37 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dropdown.js +1 -0
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/floating-panel.d.ts +36 -13
- package/dist/components/floating-panel.js +27 -7
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +1 -1
- package/dist/components/form-actions.js +1 -1
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +1 -0
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/icon-button.d.ts +11 -0
- package/dist/components/icon-button.js +7 -5
- package/dist/components/icon-button.js.map +1 -1
- package/dist/components/modal.js +1 -0
- package/dist/components/modal.js.map +1 -1
- package/dist/components/nav-pills.d.ts +8 -1
- package/dist/components/nav-pills.js +61 -38
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-input.d.ts +2 -1
- package/dist/components/number-input.js +6 -2
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.js +2 -0
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-header-context.d.ts +8 -0
- package/dist/components/page-header-context.js +7 -0
- package/dist/components/page-header-context.js.map +1 -0
- package/dist/components/page-header.d.ts +14 -2
- package/dist/components/page-header.js +10 -1
- package/dist/components/page-header.js.map +1 -1
- package/dist/components/picker-sheet.js +2 -0
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart.js +2 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.js +1 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress-bar.js +5 -4
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/reauth-dialog.js +1 -1
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/row-actions-labels.js +3 -1
- package/dist/components/row-actions-labels.js.map +1 -1
- package/dist/components/row-actions.d.ts +43 -9
- package/dist/components/row-actions.js +78 -15
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/section-nav.d.ts +37 -0
- package/dist/components/section-nav.js +33 -0
- package/dist/components/section-nav.js.map +1 -0
- package/dist/components/series-chart.js +8 -3
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/share-card.js +1 -1
- package/dist/components/share-card.js.map +1 -1
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/signature-pad.js.map +1 -1
- package/dist/components/sparkline.js +2 -1
- package/dist/components/sparkline.js.map +1 -1
- package/dist/components/stat-tile.js +14 -7
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/status-dot.d.ts +5 -1
- package/dist/components/status-dot.js +4 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review-editor.js +2 -2
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review.js +2 -2
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/treemap.d.ts +5 -2
- package/dist/components/treemap.js +26 -6
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/use-table-state.js +37 -6
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-banner.js +1 -1
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/demo/demo-ended.js +1 -1
- package/dist/demo/demo-ended.js.map +1 -1
- package/dist/demo/demo-start.js +1 -1
- package/dist/demo/demo-start.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-inbox.js +1 -1
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +9 -1
- package/dist/hooks/use-dismiss.js +25 -5
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/hooks/use-document-title.d.ts +14 -0
- package/dist/hooks/use-document-title.js +22 -0
- package/dist/hooks/use-document-title.js.map +1 -0
- package/dist/i18n/defaults.js +2 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +20 -3
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +34 -1
- package/dist/i18n/kit-labels.js +43 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +22 -3
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +20 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +20 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +20 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +20 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +20 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +3 -0
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +1 -0
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/landing/landing-actions.js +2 -2
- package/dist/landing/landing-actions.js.map +1 -1
- package/dist/landing/public-header.js +2 -2
- package/dist/landing/public-header.js.map +1 -1
- package/dist/lib/clipboard-minus.d.ts +27 -0
- package/dist/lib/clipboard-minus.js +63 -0
- package/dist/lib/clipboard-minus.js.map +1 -0
- package/dist/lib/data-attributes.d.ts +11 -0
- package/dist/lib/data-attributes.js +10 -0
- package/dist/lib/data-attributes.js.map +1 -0
- package/dist/lib/document-title.d.ts +17 -0
- package/dist/lib/document-title.js +29 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/float-clearance.js +8 -2
- package/dist/lib/float-clearance.js.map +1 -1
- package/dist/lib/format.d.ts +36 -10
- package/dist/lib/format.js +57 -11
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/minus.d.ts +32 -0
- package/dist/lib/minus.js +14 -0
- package/dist/lib/minus.js.map +1 -0
- package/dist/lib/status-tone.d.ts +30 -0
- package/dist/lib/status-tone.js +17 -0
- package/dist/lib/status-tone.js.map +1 -0
- package/dist/settings/settings-layout.js +3 -2
- package/dist/settings/settings-layout.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +1 -1
- package/dist/shell/account-menu-language.js +9 -1
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/app-shell-labels.js +2 -1
- package/dist/shell/app-shell-labels.js.map +1 -1
- package/dist/shell/app-shell.d.ts +58 -16
- package/dist/shell/app-shell.js +151 -43
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/language-code.d.ts +3 -0
- package/dist/shell/language-code.js +8 -0
- package/dist/shell/language-code.js.map +1 -0
- package/dist/shell/topbar-controls.d.ts +14 -6
- package/dist/shell/topbar-controls.js +14 -17
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/css-variables.generated.d.ts +1 -1
- package/dist/testing/css-variables.generated.js +1 -1
- package/dist/testing/css-variables.generated.js.map +1 -1
- package/dist/testing/react.d.ts +25 -0
- package/dist/testing/react.js +19 -0
- package/dist/testing/react.js.map +1 -0
- package/dist/theme/chart-palette.d.ts +82 -15
- package/dist/theme/chart-palette.js +51 -24
- package/dist/theme/chart-palette.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +9 -0
- package/dist/theme/palette-presets.js +77 -62
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/tour/tour-dock.d.ts +42 -0
- package/dist/tour/tour-dock.js +80 -0
- package/dist/tour/tour-dock.js.map +1 -0
- package/dist/tour/tour-gates.d.ts +71 -0
- package/dist/tour/tour-gates.js +114 -0
- package/dist/tour/tour-gates.js.map +1 -0
- package/dist/tour/tour-guide-button.d.ts +27 -0
- package/dist/tour/tour-guide-button.js +45 -0
- package/dist/tour/tour-guide-button.js.map +1 -0
- package/dist/tour/tour-guide-labels.d.ts +8 -0
- package/dist/tour/tour-guide-labels.js +9 -0
- package/dist/tour/tour-guide-labels.js.map +1 -0
- package/dist/tour/tour-labels.js +9 -1
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +133 -9
- package/dist/tour/tour.js +594 -70
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-url-sync.d.ts +5 -2
- package/dist/tour/use-tour-url-sync.js +7 -1
- package/dist/tour/use-tour-url-sync.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +3 -0
- package/dist/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +1 -1
- package/dist/wizard/stepper-nav.js +18 -5
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +11 -0
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +11 -5
- package/dist/wizard/use-wizard.js.map +1 -1
- package/package.json +9 -1
- package/src/admin/admin-action-confirm.tsx +1 -1
- package/src/admin/invitations-panel.tsx +7 -3
- package/src/admin/user-roster.tsx +41 -14
- package/src/auth/accept-invitation.tsx +3 -3
- package/src/auth/not-found-page.tsx +1 -1
- package/src/auth/reset-password-form.tsx +3 -3
- package/src/auth/verify-email.tsx +13 -3
- package/src/billing/plan-card.tsx +2 -2
- package/src/components/amount-input.tsx +5 -4
- package/src/components/autocomplete.tsx +2 -0
- package/src/components/breadcrumbs.tsx +32 -4
- package/src/components/button-look.ts +86 -15
- package/src/components/button.tsx +6 -3
- package/src/components/calendar-heatmap.tsx +2 -1
- package/src/components/chart-edge.ts +73 -0
- package/src/components/chart.tsx +3 -1
- package/src/components/chip.tsx +10 -1
- package/src/components/combobox-core.tsx +2 -0
- package/src/components/combobox-suggestions.tsx +2 -0
- package/src/components/confirm-context.ts +7 -0
- package/src/components/confirm-dialog.tsx +3 -3
- package/src/components/currency-select.tsx +95 -6
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-fit.ts +41 -6
- package/src/components/data-table-mobile.tsx +259 -17
- package/src/components/data-table-row-actions.tsx +9 -1
- package/src/components/data-table-row.tsx +3 -0
- package/src/components/data-table.tsx +91 -19
- package/src/components/dropdown.tsx +2 -0
- package/src/components/error-boundary.tsx +2 -2
- package/src/components/floating-panel.tsx +67 -16
- package/src/components/form-actions.tsx +2 -2
- package/src/components/full-bleed-dialog.tsx +3 -0
- package/src/components/icon-button.tsx +21 -8
- package/src/components/modal.tsx +3 -0
- package/src/components/nav-pills.tsx +29 -2
- package/src/components/number-input.tsx +8 -3
- package/src/components/numpad-sheet.tsx +4 -0
- package/src/components/page-header-context.ts +8 -0
- package/src/components/page-header.tsx +34 -2
- package/src/components/picker-sheet.tsx +4 -0
- package/src/components/pie-chart.tsx +2 -1
- package/src/components/popover.tsx +2 -0
- package/src/components/progress-bar.tsx +6 -3
- package/src/components/reauth-dialog.tsx +1 -1
- package/src/components/row-actions-labels.ts +2 -0
- package/src/components/row-actions.tsx +154 -33
- package/src/components/section-nav.tsx +65 -0
- package/src/components/series-chart.tsx +12 -3
- package/src/components/share-card.tsx +1 -1
- package/src/components/signature-pad.tsx +1 -1
- package/src/components/sparkline.tsx +2 -1
- package/src/components/stat-tile.tsx +19 -7
- package/src/components/status-dot.tsx +7 -1
- package/src/components/tooltip.tsx +3 -1
- package/src/components/translation-review-editor.tsx +2 -2
- package/src/components/translation-review.tsx +2 -2
- package/src/components/treemap.tsx +34 -8
- package/src/components/use-table-state.ts +63 -8
- package/src/data-table.ts +3 -3
- package/src/demo/demo-banner.tsx +1 -1
- package/src/demo/demo-ended.tsx +1 -1
- package/src/demo/demo-start.tsx +1 -1
- package/src/feedback/feedback-inbox.tsx +1 -1
- package/src/hooks/use-dismiss.ts +43 -4
- package/src/hooks/use-document-title.ts +31 -0
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +17 -0
- package/src/i18n/kit-labels.tsx +79 -1
- package/src/i18n/locales/es.ts +17 -0
- package/src/i18n/locales/fr.ts +17 -0
- package/src/i18n/locales/hu.ts +17 -0
- package/src/i18n/locales/it.ts +17 -0
- package/src/i18n/locales/zh.ts +17 -0
- package/src/i18n/review.ts +3 -0
- package/src/i18n/shared-labels.ts +1 -0
- package/src/index.ts +13 -0
- package/src/landing/landing-actions.tsx +2 -2
- package/src/landing/public-header.tsx +2 -2
- package/src/lib/clipboard-minus.ts +94 -0
- package/src/lib/data-attributes.ts +14 -0
- package/src/lib/document-title.ts +41 -0
- package/src/lib/float-clearance.ts +19 -3
- package/src/lib/format.ts +117 -30
- package/src/lib/minus.ts +50 -0
- package/src/lib/status-tone.ts +43 -0
- package/src/settings/settings-layout.tsx +3 -2
- package/src/shell/account-menu-language.tsx +14 -2
- package/src/shell/app-shell-labels.ts +1 -0
- package/src/shell/app-shell.tsx +291 -68
- package/src/shell/language-code.ts +5 -0
- package/src/shell/topbar-controls.tsx +35 -15
- package/src/testing/css-variables.generated.ts +6 -6
- package/src/testing/react.ts +59 -0
- package/src/theme/chart-palette.ts +102 -20
- package/src/theme/palette-presets.ts +121 -84
- package/src/tour/tour-dock.ts +132 -0
- package/src/tour/tour-gates.ts +195 -0
- package/src/tour/tour-guide-button.tsx +64 -0
- package/src/tour/tour-guide-labels.ts +15 -0
- package/src/tour/tour-labels.ts +8 -0
- package/src/tour/tour.tsx +949 -86
- package/src/tour/use-tour-url-sync.ts +14 -4
- package/src/tour.ts +3 -0
- package/src/wizard/stepper-nav.tsx +27 -7
- package/src/wizard/types.ts +12 -0
- package/src/wizard/use-wizard.ts +16 -9
- package/tokens.css +28 -18
package/dist/tour/tour.js
CHANGED
|
@@ -5,19 +5,27 @@ import {
|
|
|
5
5
|
useCallback,
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
|
+
useId,
|
|
8
9
|
useLayoutEffect,
|
|
10
|
+
useMemo,
|
|
9
11
|
useRef,
|
|
10
|
-
useState
|
|
12
|
+
useState,
|
|
13
|
+
useSyncExternalStore
|
|
11
14
|
} from "react";
|
|
12
15
|
import { createPortal } from "react-dom";
|
|
13
16
|
import { cn } from "../lib/cn.js";
|
|
14
17
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
15
18
|
import { Button } from "../components/button.js";
|
|
16
19
|
import { useFocusTrap } from "../hooks/use-focus-trap.js";
|
|
20
|
+
import { useDismissLayer } from "../hooks/use-dismiss.js";
|
|
17
21
|
import { sameRect } from "../hooks/use-anchored-rect.js";
|
|
18
22
|
import { dirOf, horizontalStep } from "../lib/direction.js";
|
|
23
|
+
import { isComposing } from "../lib/ime.js";
|
|
19
24
|
import { DEFAULT_TOUR_LABELS } from "./tour-labels.js";
|
|
20
25
|
import { completeStored, rememberAccount, rememberMigrate } from "./use-tour-progress.js";
|
|
26
|
+
import { createGateRegistry, devWarnings, TourInternalContext } from "./tour-gates.js";
|
|
27
|
+
import { DOCK_MAX_WIDTH, dockEdge, dockStyle, readBand, scrollIntoFreeHalf } from "./tour-dock.js";
|
|
28
|
+
import { useTourGate, useTourGateRelease } from "./tour-gates.js";
|
|
21
29
|
const TourContext = createContext(null);
|
|
22
30
|
function useTour() {
|
|
23
31
|
const ctx = useContext(TourContext);
|
|
@@ -27,25 +35,28 @@ function useTour() {
|
|
|
27
35
|
function useTourOptional() {
|
|
28
36
|
return useContext(TourContext);
|
|
29
37
|
}
|
|
30
|
-
function devWarnings() {
|
|
31
|
-
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
32
|
-
}
|
|
33
38
|
function TourProvider({
|
|
34
39
|
children,
|
|
35
40
|
labels,
|
|
36
41
|
progress,
|
|
37
|
-
onComplete
|
|
42
|
+
onComplete,
|
|
43
|
+
launch,
|
|
44
|
+
dockOnPhones = true
|
|
38
45
|
}) {
|
|
39
46
|
const merged = useKitLabels("tour", DEFAULT_TOUR_LABELS, labels);
|
|
40
47
|
const [steps, setSteps] = useState(null);
|
|
41
|
-
const [
|
|
48
|
+
const [nav, setNav] = useState(NO_NAV);
|
|
49
|
+
const index = nav.index;
|
|
50
|
+
const [refusal, setRefusal] = useState(null);
|
|
42
51
|
const [activeId, setActiveId] = useState(null);
|
|
43
52
|
const optsRef = useRef({});
|
|
44
53
|
const progressRef = useRef(progress);
|
|
45
54
|
const onCompleteRef = useRef(onComplete);
|
|
55
|
+
const launchRef = useRef(launch);
|
|
46
56
|
useLayoutEffect(() => {
|
|
47
57
|
progressRef.current = progress;
|
|
48
58
|
onCompleteRef.current = onComplete;
|
|
59
|
+
launchRef.current = launch;
|
|
49
60
|
});
|
|
50
61
|
const storageKey = progress?.storageKey;
|
|
51
62
|
const migrate = progress?.migrate;
|
|
@@ -61,15 +72,87 @@ function TourProvider({
|
|
|
61
72
|
stepsRef.current = steps;
|
|
62
73
|
indexRef.current = index;
|
|
63
74
|
}, [steps, index]);
|
|
75
|
+
const floorRef = useRef(0);
|
|
64
76
|
const active = !!steps && steps.length > 0;
|
|
65
77
|
const total = steps?.length ?? 0;
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
const [gates] = useState(createGateRegistry);
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
gates.setActive(active);
|
|
81
|
+
}, [gates, active]);
|
|
82
|
+
const canLaunch = !!launch;
|
|
83
|
+
const internals = useMemo(() => ({ gates, canLaunch }), [gates, canLaunch]);
|
|
84
|
+
const shown = useCallback(
|
|
85
|
+
(s, i, via) => isShown(s[i], stepContext(gates, via)),
|
|
86
|
+
[gates]
|
|
87
|
+
);
|
|
88
|
+
const arrive = useCallback(
|
|
89
|
+
(s, i, via) => {
|
|
90
|
+
const ctx = stepContext(gates, via);
|
|
91
|
+
let position = 0;
|
|
92
|
+
let count = 0;
|
|
93
|
+
for (let j = floorRef.current; j < s.length; j++) {
|
|
94
|
+
if (j !== i && !isShown(s[j], ctx)) continue;
|
|
95
|
+
count++;
|
|
96
|
+
if (j <= i) position++;
|
|
97
|
+
}
|
|
98
|
+
indexRef.current = i;
|
|
99
|
+
setRefusal(null);
|
|
100
|
+
setNav({ index: i, floor: floorRef.current, via, position, count });
|
|
101
|
+
},
|
|
102
|
+
[gates]
|
|
103
|
+
);
|
|
104
|
+
const forward = useCallback(
|
|
105
|
+
(s, from, via) => {
|
|
106
|
+
for (let j = Math.max(from, floorRef.current); j < s.length; j++) if (shown(s, j, via)) return j;
|
|
107
|
+
return -1;
|
|
108
|
+
},
|
|
109
|
+
[shown]
|
|
110
|
+
);
|
|
111
|
+
const backward = useCallback(
|
|
112
|
+
(s, from, via) => {
|
|
113
|
+
for (let j = Math.min(from, s.length - 1); j >= floorRef.current; j--) if (shown(s, j, via)) return j;
|
|
114
|
+
return -1;
|
|
115
|
+
},
|
|
116
|
+
[shown]
|
|
117
|
+
);
|
|
118
|
+
const startSteps = useCallback(
|
|
119
|
+
(s, opts) => {
|
|
120
|
+
optsRef.current = opts ?? {};
|
|
121
|
+
optsRef.current.onStart?.();
|
|
122
|
+
const last = Math.max(0, s.length - 1);
|
|
123
|
+
floorRef.current = opts?.at === void 0 ? 0 : resolveAt(s, opts.at);
|
|
124
|
+
const via = opts?.at === void 0 ? "start" : "at";
|
|
125
|
+
const wanted = Math.min(Math.max(floorRef.current, opts?.startIndex ?? floorRef.current), last);
|
|
126
|
+
let to = forward(s, wanted, via);
|
|
127
|
+
if (to < 0) to = backward(s, wanted, via);
|
|
128
|
+
if (to < 0) return;
|
|
129
|
+
arrive(s, to, via);
|
|
130
|
+
setActiveId(opts?.id ?? opts?.tour?.id ?? null);
|
|
131
|
+
setSteps(s);
|
|
132
|
+
},
|
|
133
|
+
[arrive, forward, backward]
|
|
134
|
+
);
|
|
135
|
+
const ownStartRef = useRef(null);
|
|
136
|
+
const start = useCallback(
|
|
137
|
+
(what, opts) => {
|
|
138
|
+
if (typeof what !== "string") {
|
|
139
|
+
startSteps(what, opts);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const run = launchRef.current;
|
|
143
|
+
if (!run) {
|
|
144
|
+
if (devWarnings()) console.warn(`[ui-kit] start("${what}") needs TourProvider's \`launch\` prop.`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
void Promise.resolve(run(what, { at: opts?.at }, ownStartRef.current)).catch(
|
|
148
|
+
(error) => console.error(error)
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
[startSteps]
|
|
152
|
+
);
|
|
153
|
+
useLayoutEffect(() => {
|
|
154
|
+
ownStartRef.current = start;
|
|
155
|
+
}, [start]);
|
|
73
156
|
const stop = useCallback(() => {
|
|
74
157
|
setSteps(null);
|
|
75
158
|
setActiveId(null);
|
|
@@ -103,34 +186,119 @@ function TourProvider({
|
|
|
103
186
|
setSteps(null);
|
|
104
187
|
setActiveId(null);
|
|
105
188
|
}, []);
|
|
106
|
-
const goToStep = useCallback(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
189
|
+
const goToStep = useCallback(
|
|
190
|
+
(i) => {
|
|
191
|
+
const s = stepsRef.current;
|
|
192
|
+
if (!s || s.length === 0) return;
|
|
193
|
+
const wanted = Math.min(Math.max(floorRef.current, i), s.length - 1);
|
|
194
|
+
let to = forward(s, wanted, "goto");
|
|
195
|
+
if (to < 0) to = backward(s, wanted, "goto");
|
|
196
|
+
if (to >= 0) arrive(s, to, "goto");
|
|
197
|
+
},
|
|
198
|
+
[arrive, forward, backward]
|
|
199
|
+
);
|
|
111
200
|
const next = useCallback(() => {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
201
|
+
const s = stepsRef.current;
|
|
202
|
+
if (!s || s.length === 0) return;
|
|
203
|
+
const to = forward(s, indexRef.current + 1, "next");
|
|
204
|
+
if (to < 0) finish();
|
|
205
|
+
else arrive(s, to, "next");
|
|
206
|
+
}, [arrive, forward, finish]);
|
|
117
207
|
const prev = useCallback(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
208
|
+
const s = stepsRef.current;
|
|
209
|
+
if (!s) return;
|
|
210
|
+
const to = backward(s, indexRef.current - 1, "back");
|
|
211
|
+
if (to >= 0) arrive(s, to, "back");
|
|
212
|
+
}, [arrive, backward]);
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
if (!active) return;
|
|
215
|
+
let seq = 0;
|
|
216
|
+
return gates.listen((signal) => {
|
|
217
|
+
const s = stepsRef.current;
|
|
218
|
+
if (!s) return;
|
|
219
|
+
const i = indexRef.current;
|
|
220
|
+
if (signal.kind === "refuse") {
|
|
221
|
+
if (s[i]?.awaitGate === signal.key) setRefusal({ step: s[i], reason: signal.reason, seq: ++seq });
|
|
222
|
+
return;
|
|
131
223
|
}
|
|
132
|
-
|
|
133
|
-
|
|
224
|
+
const awaiting = s[i]?.awaitGate === signal.key ? i : s.findIndex((st, k) => k > i && st.awaitGate === signal.key);
|
|
225
|
+
if (awaiting < 0) return;
|
|
226
|
+
const to = forward(s, awaiting + 1, "next");
|
|
227
|
+
if (to < 0) finish();
|
|
228
|
+
else arrive(s, to, "next");
|
|
229
|
+
});
|
|
230
|
+
}, [active, gates, arrive, forward, finish]);
|
|
231
|
+
return /* @__PURE__ */ jsx(TourInternalContext.Provider, { value: internals, children: /* @__PURE__ */ jsxs(
|
|
232
|
+
TourContext.Provider,
|
|
233
|
+
{
|
|
234
|
+
value: {
|
|
235
|
+
active,
|
|
236
|
+
activeId,
|
|
237
|
+
start,
|
|
238
|
+
stop,
|
|
239
|
+
next,
|
|
240
|
+
prev,
|
|
241
|
+
goToStep,
|
|
242
|
+
index,
|
|
243
|
+
total,
|
|
244
|
+
position: active ? nav.position : 0,
|
|
245
|
+
count: active ? nav.count : 0
|
|
246
|
+
},
|
|
247
|
+
children: [
|
|
248
|
+
children,
|
|
249
|
+
active && /* @__PURE__ */ jsx(
|
|
250
|
+
TourOverlay,
|
|
251
|
+
{
|
|
252
|
+
steps,
|
|
253
|
+
index,
|
|
254
|
+
via: nav.via,
|
|
255
|
+
floor: nav.floor,
|
|
256
|
+
position: nav.position,
|
|
257
|
+
count: nav.count,
|
|
258
|
+
refusal: refusal?.step === steps[index] ? refusal : null,
|
|
259
|
+
onGoTo: goToStep,
|
|
260
|
+
labels: merged,
|
|
261
|
+
gates,
|
|
262
|
+
dockOnPhones,
|
|
263
|
+
onNext: next,
|
|
264
|
+
onPrev: prev,
|
|
265
|
+
onSkip: skip
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
) });
|
|
271
|
+
}
|
|
272
|
+
const NO_NAV = { index: 0, floor: 0, via: "start", position: 0, count: 0 };
|
|
273
|
+
const NOOP = () => {
|
|
274
|
+
};
|
|
275
|
+
function stepContext(gates, via) {
|
|
276
|
+
return { gate: (key) => gates.get(key), via };
|
|
277
|
+
}
|
|
278
|
+
function resolveAt(steps, at) {
|
|
279
|
+
if (typeof at === "number") return Math.min(Math.max(0, Math.floor(at)), Math.max(0, steps.length - 1));
|
|
280
|
+
const anchor = `data-tour="${at}"`;
|
|
281
|
+
const found = steps.findIndex((step) => step.target?.includes(anchor));
|
|
282
|
+
if (found >= 0) return found;
|
|
283
|
+
if (devWarnings()) console.warn(`[ui-kit] A tour was started at "${at}", but no step targets [${anchor}]: it starts at 0.`);
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
function firstEmptied(steps, floor, index, gates) {
|
|
287
|
+
const ctx = stepContext(gates, "goto");
|
|
288
|
+
for (let j = floor; j < index; j++) {
|
|
289
|
+
const key = steps[j].gate;
|
|
290
|
+
if (key && gates.get(key) === false && isShown(steps[j], ctx)) return j;
|
|
291
|
+
}
|
|
292
|
+
return -1;
|
|
293
|
+
}
|
|
294
|
+
function isShown(step, ctx) {
|
|
295
|
+
if (!step.when) return true;
|
|
296
|
+
try {
|
|
297
|
+
return step.when(ctx) === true;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error(error);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
134
302
|
}
|
|
135
303
|
function isVisible(el) {
|
|
136
304
|
const r = el.getBoundingClientRect();
|
|
@@ -149,29 +317,142 @@ function rectOf(el) {
|
|
|
149
317
|
const r = el.getBoundingClientRect();
|
|
150
318
|
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
151
319
|
}
|
|
320
|
+
const RING_BAND = 6;
|
|
321
|
+
function spotRectOf(el, step) {
|
|
322
|
+
const own = rectOf(el);
|
|
323
|
+
if (!isFieldStep(step)) return own;
|
|
324
|
+
const ids = /* @__PURE__ */ new Set();
|
|
325
|
+
const holders = [el, ...Array.from(el.querySelectorAll("[aria-describedby]"))];
|
|
326
|
+
for (const holder of holders) {
|
|
327
|
+
for (const id of (holder.getAttribute("aria-describedby") ?? "").split(/\s+/)) if (id) ids.add(id);
|
|
328
|
+
}
|
|
329
|
+
if (ids.size === 0) return own;
|
|
330
|
+
const lines = [];
|
|
331
|
+
for (const id of ids) {
|
|
332
|
+
const line = document.getElementById(id);
|
|
333
|
+
if (!line || el.contains(line) || line.closest('[role="tooltip"]') || !isVisible(line)) continue;
|
|
334
|
+
const r = line.getBoundingClientRect();
|
|
335
|
+
if (r.width > 1 && r.height > 1) lines.push(r);
|
|
336
|
+
}
|
|
337
|
+
let top = own.top;
|
|
338
|
+
let left = own.left;
|
|
339
|
+
let right = own.left + own.width;
|
|
340
|
+
let bottom = own.top + own.height;
|
|
341
|
+
const reach = (step.padding ?? 8) + RING_BAND;
|
|
342
|
+
for (let grew = true; grew; ) {
|
|
343
|
+
grew = false;
|
|
344
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
345
|
+
const r = lines[i];
|
|
346
|
+
const across = r.left < right && r.right > left;
|
|
347
|
+
const gap = Math.max(r.top - bottom, top - r.bottom);
|
|
348
|
+
if (!across || gap > reach) continue;
|
|
349
|
+
top = Math.min(top, r.top);
|
|
350
|
+
left = Math.min(left, r.left);
|
|
351
|
+
right = Math.max(right, r.right);
|
|
352
|
+
bottom = Math.max(bottom, r.bottom);
|
|
353
|
+
lines.splice(i, 1);
|
|
354
|
+
grew = true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return { top, left, width: right - left, height: bottom - top };
|
|
358
|
+
}
|
|
359
|
+
function layerHost(el) {
|
|
360
|
+
return el.closest("[data-kit-layer]");
|
|
361
|
+
}
|
|
362
|
+
function coversViewport(host, el) {
|
|
363
|
+
let surface = host;
|
|
364
|
+
if (host.getAttribute("data-kit-layer") === "backdrop") {
|
|
365
|
+
for (const child of Array.from(host.children)) {
|
|
366
|
+
if (child.contains(el)) {
|
|
367
|
+
surface = child;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const r = surface.getBoundingClientRect();
|
|
373
|
+
const vv = window.visualViewport;
|
|
374
|
+
const top = vv?.offsetTop ?? 0;
|
|
375
|
+
const bottom = top + (vv?.height ?? window.innerHeight);
|
|
376
|
+
const width = vv?.width ?? window.innerWidth;
|
|
377
|
+
return r.width > 0 && r.left <= 1 && r.top <= top + 1 && r.right >= width - 1 && r.bottom >= bottom - 1;
|
|
378
|
+
}
|
|
379
|
+
function docks(step, dockOnPhones) {
|
|
380
|
+
if (typeof window === "undefined" || window.innerWidth >= DOCK_MAX_WIDTH || !step.target) return false;
|
|
381
|
+
return step.placement === "dock" || step.placement === void 0 && dockOnPhones && isFieldStep(step);
|
|
382
|
+
}
|
|
383
|
+
function isFieldStep(step) {
|
|
384
|
+
return !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
|
|
385
|
+
}
|
|
386
|
+
function firstFocusable(el) {
|
|
387
|
+
if (el instanceof HTMLElement && el.matches(FOCUSABLE_CONTROL)) return el;
|
|
388
|
+
return el.querySelector(FOCUSABLE_CONTROL);
|
|
389
|
+
}
|
|
390
|
+
function tabStops(el) {
|
|
391
|
+
const all = el.matches(FOCUSABLE_CONTROL) ? [el] : [];
|
|
392
|
+
return all.concat(
|
|
393
|
+
Array.from(el.querySelectorAll(FOCUSABLE_CONTROL)).filter(
|
|
394
|
+
(x) => x.tabIndex >= 0 && !x.closest("[hidden],[inert]")
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const FOCUSABLE_CONTROL = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
399
|
+
function coarsePointer() {
|
|
400
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : false;
|
|
401
|
+
}
|
|
152
402
|
function TourOverlay({
|
|
153
403
|
steps,
|
|
154
404
|
index,
|
|
405
|
+
via,
|
|
406
|
+
floor,
|
|
407
|
+
position,
|
|
408
|
+
count,
|
|
409
|
+
refusal,
|
|
410
|
+
onGoTo,
|
|
155
411
|
labels,
|
|
412
|
+
gates,
|
|
413
|
+
dockOnPhones,
|
|
156
414
|
onNext,
|
|
157
415
|
onPrev,
|
|
158
416
|
onSkip
|
|
159
417
|
}) {
|
|
160
418
|
const step = steps[index];
|
|
161
|
-
const isFirst =
|
|
162
|
-
const isLast =
|
|
419
|
+
const isFirst = position <= 1;
|
|
420
|
+
const isLast = position >= count;
|
|
163
421
|
const [located, setLocated] = useState(null);
|
|
164
422
|
const current = located !== null && located.index === index && located.step === step ? located : null;
|
|
165
423
|
const ready = current !== null;
|
|
166
424
|
const rect = current?.rect ?? null;
|
|
167
425
|
const dir = current?.dir ?? "ltr";
|
|
168
426
|
const rootRef = useRef(null);
|
|
427
|
+
const cardRef = useRef(null);
|
|
428
|
+
const bodyId = useId();
|
|
429
|
+
const hintId = useId();
|
|
430
|
+
const gateVersion = useSyncExternalStore(gates.subscribe, gates.version, gates.version);
|
|
431
|
+
const gateKey = step.gate;
|
|
432
|
+
const enforced = !!gateKey && gates.has(gateKey);
|
|
433
|
+
const locked = enforced && gates.get(gateKey) !== true;
|
|
434
|
+
const [polite, setPolite] = useState("");
|
|
435
|
+
const announce = useCallback((text) => {
|
|
436
|
+
setPolite((prev) => prev === text ? `${text}\u200B` : text);
|
|
437
|
+
}, []);
|
|
169
438
|
useEffect(() => {
|
|
170
439
|
let cancelled = false;
|
|
171
440
|
let raf = 0;
|
|
172
|
-
const settle = (el) =>
|
|
441
|
+
const settle = (el) => {
|
|
442
|
+
let layer2 = null;
|
|
443
|
+
let fullScreen = false;
|
|
444
|
+
if (el && isFieldStep(step)) {
|
|
445
|
+
const host = layerHost(el);
|
|
446
|
+
layer2 = document.createElement("div");
|
|
447
|
+
layer2.setAttribute("data-tour-layer", "");
|
|
448
|
+
if (host) host.after(layer2);
|
|
449
|
+
else document.body.appendChild(layer2);
|
|
450
|
+
fullScreen = !!host && coversViewport(host, el);
|
|
451
|
+
}
|
|
452
|
+
setLocated({ index, step, rect: el ? spotRectOf(el, step) : null, dir: dirOf(el), layer: layer2, fullScreen });
|
|
453
|
+
};
|
|
173
454
|
void (async () => {
|
|
174
|
-
await step.beforeStep?.();
|
|
455
|
+
await step.beforeStep?.(stepContext(gates, via));
|
|
175
456
|
if (cancelled) return;
|
|
176
457
|
if (!step.target) {
|
|
177
458
|
settle(null);
|
|
@@ -182,7 +463,8 @@ function TourOverlay({
|
|
|
182
463
|
if (cancelled) return;
|
|
183
464
|
const el = queryVisibleTarget(step.target);
|
|
184
465
|
if (el) {
|
|
185
|
-
|
|
466
|
+
if (docks(step, dockOnPhones)) scrollIntoFreeHalf(el, null, null);
|
|
467
|
+
else el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
186
468
|
settle(el);
|
|
187
469
|
return;
|
|
188
470
|
}
|
|
@@ -201,10 +483,15 @@ function TourOverlay({
|
|
|
201
483
|
const update = () => {
|
|
202
484
|
const el = queryVisibleTarget(step.target);
|
|
203
485
|
if (el) {
|
|
204
|
-
const next =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
486
|
+
const next = spotRectOf(el, step);
|
|
487
|
+
const host = layerHost(el);
|
|
488
|
+
const full = !!host && coversViewport(host, el);
|
|
489
|
+
setLocated((prev) => {
|
|
490
|
+
if (!prev || prev.index !== index || prev.step !== step) return prev;
|
|
491
|
+
const fullScreen = prev.layer ? full : prev.fullScreen;
|
|
492
|
+
if (sameRect(prev.rect, next) && fullScreen === prev.fullScreen) return prev;
|
|
493
|
+
return { ...prev, rect: next, fullScreen };
|
|
494
|
+
});
|
|
208
495
|
}
|
|
209
496
|
};
|
|
210
497
|
window.addEventListener("scroll", update, true);
|
|
@@ -216,6 +503,23 @@ function TourOverlay({
|
|
|
216
503
|
clearInterval(id);
|
|
217
504
|
};
|
|
218
505
|
}, [index, step]);
|
|
506
|
+
const layer = located?.layer ?? null;
|
|
507
|
+
useEffect(() => {
|
|
508
|
+
if (!layer) return;
|
|
509
|
+
return () => layer.remove();
|
|
510
|
+
}, [layer]);
|
|
511
|
+
useDismissLayer(
|
|
512
|
+
ready,
|
|
513
|
+
(e) => {
|
|
514
|
+
if (fieldStepNow && step.target && e.target instanceof Node) {
|
|
515
|
+
const el = queryVisibleTarget(step.target);
|
|
516
|
+
if (el?.contains(e.target)) return;
|
|
517
|
+
}
|
|
518
|
+
onSkip();
|
|
519
|
+
},
|
|
520
|
+
// Not an overlay to `useOverlayOpen`: a floating launcher a tour spotlights stays.
|
|
521
|
+
{ overlay: false }
|
|
522
|
+
);
|
|
219
523
|
const spotted = rect !== null;
|
|
220
524
|
useEffect(() => {
|
|
221
525
|
if (!ready || !spotted || !step.awaitClick || !step.target) return;
|
|
@@ -225,7 +529,112 @@ function TourOverlay({
|
|
|
225
529
|
el.addEventListener("click", onClick, { once: true });
|
|
226
530
|
return () => el.removeEventListener("click", onClick);
|
|
227
531
|
}, [ready, spotted, index, step, onNext]);
|
|
228
|
-
const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput);
|
|
532
|
+
const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
|
|
533
|
+
const guided = interactive && !!(step.gate || step.awaitInput || step.awaitGate);
|
|
534
|
+
const fieldStepNow = interactive;
|
|
535
|
+
const awaitsRelease = spotted && !!step.target && !!step.awaitGate;
|
|
536
|
+
const warnedGateRef = useRef(null);
|
|
537
|
+
useEffect(() => {
|
|
538
|
+
if (!ready || !gateKey || gates.has(gateKey) || !devWarnings() || warnedGateRef.current === step) return;
|
|
539
|
+
warnedGateRef.current = step;
|
|
540
|
+
console.warn(
|
|
541
|
+
`[ui-kit] Tour step "${step.title}" waits for the gate "${gateKey}", which nothing publishes: its Next is open. Publish it with useTourGate beside the field, or the form has closed under the tour.`
|
|
542
|
+
);
|
|
543
|
+
}, [ready, gateKey, gates, step, gateVersion]);
|
|
544
|
+
useEffect(() => {
|
|
545
|
+
if (!ready || !step.optional || step.gate || !devWarnings()) return;
|
|
546
|
+
console.warn(
|
|
547
|
+
`[ui-kit] Tour step "${step.title}" is \`optional\` without a \`gate\`: it shows the Optional chip and a plain Next. "Leave empty" needs a gate to compare.`
|
|
548
|
+
);
|
|
549
|
+
}, [ready, step]);
|
|
550
|
+
const wasLockedRef = useRef(null);
|
|
551
|
+
useEffect(() => {
|
|
552
|
+
if (!ready || !gateKey) return;
|
|
553
|
+
const before = wasLockedRef.current;
|
|
554
|
+
if (before && before.step === step && before.locked && !locked && enforced) announce(labels.gateReady);
|
|
555
|
+
wasLockedRef.current = { step, locked };
|
|
556
|
+
}, [ready, gateKey, step, locked, enforced, announce, labels.gateReady]);
|
|
557
|
+
useEffect(() => {
|
|
558
|
+
if (!ready || !guided || !step.target) return;
|
|
559
|
+
const el = queryVisibleTarget(step.target);
|
|
560
|
+
const control = el ? firstFocusable(el) : null;
|
|
561
|
+
if (coarsePointer() || !control) cardRef.current?.focus();
|
|
562
|
+
else if (!el.contains(document.activeElement)) control.focus();
|
|
563
|
+
if (!control) return;
|
|
564
|
+
const previous = control.getAttribute("aria-describedby");
|
|
565
|
+
control.setAttribute("aria-describedby", previous ? `${previous} ${bodyId}` : bodyId);
|
|
566
|
+
return () => {
|
|
567
|
+
if (previous === null) control.removeAttribute("aria-describedby");
|
|
568
|
+
else control.setAttribute("aria-describedby", previous);
|
|
569
|
+
};
|
|
570
|
+
}, [ready, guided, index, step, bodyId]);
|
|
571
|
+
useEffect(() => {
|
|
572
|
+
if (!ready || !guided || !step.target) return;
|
|
573
|
+
const onTab = (e) => {
|
|
574
|
+
if (e.key !== "Tab") return;
|
|
575
|
+
const el = queryVisibleTarget(step.target);
|
|
576
|
+
const card = cardRef.current;
|
|
577
|
+
if (!el || !card) return;
|
|
578
|
+
const active = document.activeElement;
|
|
579
|
+
const fieldStops = tabStops(el);
|
|
580
|
+
const cardStops = tabStops(card);
|
|
581
|
+
if (fieldStops.length === 0 || cardStops.length === 0) return;
|
|
582
|
+
let to = null;
|
|
583
|
+
if (active instanceof Node && el.contains(active)) {
|
|
584
|
+
if (!e.shiftKey && active === fieldStops[fieldStops.length - 1]) to = cardStops[0];
|
|
585
|
+
else if (e.shiftKey && active === fieldStops[0]) to = cardStops[cardStops.length - 1];
|
|
586
|
+
} else if (active instanceof Node && card.contains(active)) {
|
|
587
|
+
if (!e.shiftKey && active === cardStops[cardStops.length - 1]) to = fieldStops[0];
|
|
588
|
+
else if (e.shiftKey && (active === cardStops[0] || active === card)) to = fieldStops[fieldStops.length - 1];
|
|
589
|
+
}
|
|
590
|
+
if (!to) return;
|
|
591
|
+
e.preventDefault();
|
|
592
|
+
e.stopPropagation();
|
|
593
|
+
to.focus();
|
|
594
|
+
};
|
|
595
|
+
document.addEventListener("keydown", onTab, true);
|
|
596
|
+
return () => document.removeEventListener("keydown", onTab, true);
|
|
597
|
+
}, [ready, guided, index, step]);
|
|
598
|
+
useEffect(() => {
|
|
599
|
+
if (!ready || !spotted || !gateKey || !step.target) return;
|
|
600
|
+
const el = queryVisibleTarget(step.target);
|
|
601
|
+
if (!el) return;
|
|
602
|
+
const satisfied = () => !gates.has(gateKey) || gates.get(gateKey) === true;
|
|
603
|
+
let timer = 0;
|
|
604
|
+
let armed = el.contains(document.activeElement);
|
|
605
|
+
const check = () => {
|
|
606
|
+
window.clearTimeout(timer);
|
|
607
|
+
timer = window.setTimeout(() => {
|
|
608
|
+
if (satisfied()) onNext();
|
|
609
|
+
}, 0);
|
|
610
|
+
};
|
|
611
|
+
const onFocusIn = () => {
|
|
612
|
+
armed = true;
|
|
613
|
+
};
|
|
614
|
+
const onFocusOut = (e) => {
|
|
615
|
+
const to = e.relatedTarget;
|
|
616
|
+
if (to instanceof Node && el.contains(to)) return;
|
|
617
|
+
const wasArmed = armed;
|
|
618
|
+
armed = false;
|
|
619
|
+
if (!wasArmed) return;
|
|
620
|
+
if (to === null || to instanceof Node && rootRef.current?.contains(to)) check();
|
|
621
|
+
};
|
|
622
|
+
const onKeyDown = (e) => {
|
|
623
|
+
if (e.key !== "Enter" || !(e.target instanceof Node) || !el.contains(e.target)) return;
|
|
624
|
+
if (e.defaultPrevented || isComposing(e)) return;
|
|
625
|
+
if (e.target instanceof HTMLTextAreaElement && !(e.ctrlKey || e.metaKey)) return;
|
|
626
|
+
check();
|
|
627
|
+
};
|
|
628
|
+
el.addEventListener("focusin", onFocusIn);
|
|
629
|
+
el.addEventListener("focusout", onFocusOut);
|
|
630
|
+
document.addEventListener("keydown", onKeyDown);
|
|
631
|
+
return () => {
|
|
632
|
+
window.clearTimeout(timer);
|
|
633
|
+
el.removeEventListener("focusin", onFocusIn);
|
|
634
|
+
el.removeEventListener("focusout", onFocusOut);
|
|
635
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
636
|
+
};
|
|
637
|
+
}, [ready, spotted, gateKey, gates, index, step, onNext]);
|
|
229
638
|
useEffect(() => {
|
|
230
639
|
if (!ready || !spotted || !step.awaitInput || !step.target) return;
|
|
231
640
|
const el = queryVisibleTarget(step.target);
|
|
@@ -259,34 +668,44 @@ function TourOverlay({
|
|
|
259
668
|
el.removeEventListener("change", check);
|
|
260
669
|
};
|
|
261
670
|
}, [ready, spotted, index, step, onNext]);
|
|
671
|
+
const gateHint = step.gateHint ?? labels.gateHint;
|
|
672
|
+
const lockedNext = useCallback(() => {
|
|
673
|
+
const el = step.target ? queryVisibleTarget(step.target) : null;
|
|
674
|
+
const control = el ? firstFocusable(el) : null;
|
|
675
|
+
control?.focus();
|
|
676
|
+
announce(gateHint);
|
|
677
|
+
}, [step.target, announce, gateHint]);
|
|
678
|
+
const tryNext = awaitsRelease ? NOOP : locked ? lockedNext : onNext;
|
|
679
|
+
const backTo = refusal ? firstEmptied(steps, floor, index, gates) : -1;
|
|
262
680
|
useEffect(() => {
|
|
263
681
|
const onKey = (e) => {
|
|
264
682
|
if (interactive && e.target instanceof Node && e.target !== document.body && !rootRef.current?.contains(e.target)) {
|
|
265
683
|
return;
|
|
266
684
|
}
|
|
267
685
|
if (e.key === "Escape") {
|
|
268
|
-
onSkip();
|
|
686
|
+
if (!ready) onSkip();
|
|
269
687
|
return;
|
|
270
688
|
}
|
|
271
689
|
if (e.key === "Enter") {
|
|
272
690
|
if (step.awaitClick || step.awaitInput || ownsKey(e.target, "Enter")) return;
|
|
273
691
|
e.preventDefault();
|
|
274
|
-
|
|
692
|
+
tryNext();
|
|
275
693
|
return;
|
|
276
694
|
}
|
|
277
695
|
if (ownsKey(e.target, e.key)) return;
|
|
278
696
|
const delta = horizontalStep(e.key, rootRef.current);
|
|
279
697
|
if (delta === 1) {
|
|
280
698
|
e.preventDefault();
|
|
281
|
-
|
|
699
|
+
tryNext();
|
|
282
700
|
} else if (delta === -1) {
|
|
283
701
|
if (!isFirst) onPrev();
|
|
284
702
|
}
|
|
285
703
|
};
|
|
286
704
|
document.addEventListener("keydown", onKey);
|
|
287
705
|
return () => document.removeEventListener("keydown", onKey);
|
|
288
|
-
}, [isFirst, step, interactive,
|
|
706
|
+
}, [isFirst, step, interactive, tryNext, onPrev, onSkip, ready]);
|
|
289
707
|
if (!ready) return null;
|
|
708
|
+
const bare = interactive && !!current?.fullScreen;
|
|
290
709
|
const pad = step.padding ?? 8;
|
|
291
710
|
const spot = rect ? { top: rect.top - pad, left: rect.left - pad, width: rect.width + pad * 2, height: rect.height + pad * 2 } : null;
|
|
292
711
|
return createPortal(
|
|
@@ -294,15 +713,16 @@ function TourOverlay({
|
|
|
294
713
|
"div",
|
|
295
714
|
{
|
|
296
715
|
ref: rootRef,
|
|
297
|
-
className: cn("pointer-events-none fixed inset-0", interactive ? "z-
|
|
716
|
+
className: cn("pointer-events-none fixed inset-0", interactive ? "z-50" : "z-[60]"),
|
|
298
717
|
role: "dialog",
|
|
299
718
|
"aria-modal": interactive ? void 0 : "true",
|
|
300
719
|
"data-tour-overlay": "",
|
|
301
720
|
"data-tour-interactive": interactive ? "" : void 0,
|
|
721
|
+
"data-tour-bare": bare ? "" : void 0,
|
|
302
722
|
"aria-label": step.title,
|
|
303
723
|
dir,
|
|
304
724
|
children: [
|
|
305
|
-
spot && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
|
|
725
|
+
spot && !bare && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
|
|
306
726
|
spot ? /* @__PURE__ */ jsx(
|
|
307
727
|
"div",
|
|
308
728
|
{
|
|
@@ -330,7 +750,7 @@ function TourOverlay({
|
|
|
330
750
|
"0 0 0 3px rgba(255,255,255,0.98)",
|
|
331
751
|
"0 0 0 5px rgba(15,23,42,0.92)",
|
|
332
752
|
"0 0 18px 6px rgba(15,23,42,0.45)",
|
|
333
|
-
"0 0 0 9999px rgba(15,23,42,0.70)"
|
|
753
|
+
...bare ? [] : ["0 0 0 9999px rgba(15,23,42,0.70)"]
|
|
334
754
|
].join(", ")
|
|
335
755
|
}
|
|
336
756
|
}
|
|
@@ -338,23 +758,36 @@ function TourOverlay({
|
|
|
338
758
|
/* @__PURE__ */ jsx(
|
|
339
759
|
TourCard,
|
|
340
760
|
{
|
|
761
|
+
cardRef,
|
|
341
762
|
step,
|
|
342
763
|
index,
|
|
343
|
-
|
|
764
|
+
position,
|
|
765
|
+
total: count,
|
|
344
766
|
labels,
|
|
345
767
|
isFirst,
|
|
346
768
|
isLast,
|
|
347
769
|
rect: spot,
|
|
348
770
|
dir,
|
|
771
|
+
trap: !guided,
|
|
772
|
+
bodyId,
|
|
773
|
+
hintId,
|
|
774
|
+
locked,
|
|
775
|
+
gateHint,
|
|
349
776
|
onBack: onPrev,
|
|
350
|
-
onNext,
|
|
351
|
-
|
|
777
|
+
onNext: tryNext,
|
|
778
|
+
onLeaveEmpty: onNext,
|
|
779
|
+
onSkip,
|
|
780
|
+
awaitsRelease,
|
|
781
|
+
docked: spot !== null && docks(step, dockOnPhones),
|
|
782
|
+
refusal,
|
|
783
|
+
backTo: backTo >= 0 ? { title: steps[backTo].title, go: () => onGoTo(backTo) } : null
|
|
352
784
|
}
|
|
353
|
-
)
|
|
785
|
+
),
|
|
786
|
+
/* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: polite })
|
|
354
787
|
]
|
|
355
788
|
}
|
|
356
789
|
),
|
|
357
|
-
document.body
|
|
790
|
+
interactive && current?.layer || document.body
|
|
358
791
|
);
|
|
359
792
|
}
|
|
360
793
|
function fieldValues(el) {
|
|
@@ -433,38 +866,108 @@ function placeCard(rect, cw, ch, vw, vh, m, placement, dir = "ltr") {
|
|
|
433
866
|
}
|
|
434
867
|
}
|
|
435
868
|
function TourCard({
|
|
869
|
+
cardRef: ref,
|
|
436
870
|
step,
|
|
437
871
|
index,
|
|
872
|
+
position,
|
|
438
873
|
total,
|
|
439
874
|
labels,
|
|
440
875
|
isFirst,
|
|
441
876
|
isLast,
|
|
442
877
|
rect,
|
|
443
878
|
dir,
|
|
879
|
+
trap,
|
|
880
|
+
bodyId,
|
|
881
|
+
hintId,
|
|
882
|
+
locked,
|
|
883
|
+
gateHint,
|
|
444
884
|
onBack,
|
|
445
885
|
onNext,
|
|
446
|
-
|
|
886
|
+
onLeaveEmpty,
|
|
887
|
+
onSkip,
|
|
888
|
+
awaitsRelease,
|
|
889
|
+
docked,
|
|
890
|
+
refusal,
|
|
891
|
+
backTo
|
|
447
892
|
}) {
|
|
448
|
-
const ref = useRef(null);
|
|
449
893
|
const [style, setStyle] = useState({ opacity: 0 });
|
|
450
|
-
useFocusTrap(ref, { active:
|
|
894
|
+
useFocusTrap(ref, { active: trap });
|
|
895
|
+
const edgeRef = useRef(null);
|
|
896
|
+
const [bandTick, setBandTick] = useState(0);
|
|
897
|
+
const [edge, setEdge] = useState(null);
|
|
898
|
+
useEffect(() => {
|
|
899
|
+
if (!docked) return;
|
|
900
|
+
let last = "";
|
|
901
|
+
const look = () => {
|
|
902
|
+
const target = step.target ? queryVisibleTarget(step.target) : null;
|
|
903
|
+
const band = readBand(target, ref.current);
|
|
904
|
+
const key = `${band.top}:${band.bottom}`;
|
|
905
|
+
if (key === last) return;
|
|
906
|
+
last = key;
|
|
907
|
+
if (target) {
|
|
908
|
+
const r = target.getBoundingClientRect();
|
|
909
|
+
if (r.top < band.top || r.bottom > band.bottom) {
|
|
910
|
+
edgeRef.current = scrollIntoFreeHalf(target, ref.current, edgeRef.current);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
setBandTick((t) => t + 1);
|
|
914
|
+
};
|
|
915
|
+
const vv = window.visualViewport;
|
|
916
|
+
vv?.addEventListener("resize", look);
|
|
917
|
+
vv?.addEventListener("scroll", look);
|
|
918
|
+
window.addEventListener("resize", look);
|
|
919
|
+
const id = window.setInterval(look, 250);
|
|
920
|
+
return () => {
|
|
921
|
+
vv?.removeEventListener("resize", look);
|
|
922
|
+
vv?.removeEventListener("scroll", look);
|
|
923
|
+
window.removeEventListener("resize", look);
|
|
924
|
+
clearInterval(id);
|
|
925
|
+
};
|
|
926
|
+
}, [docked, step.target, ref]);
|
|
451
927
|
useLayoutEffect(() => {
|
|
452
928
|
const card = ref.current;
|
|
453
929
|
if (!card) return;
|
|
930
|
+
if (docked) {
|
|
931
|
+
const target = step.target ? queryVisibleTarget(step.target) : null;
|
|
932
|
+
const band = readBand(target, card);
|
|
933
|
+
const next = rect ? dockEdge(rect, band, edgeRef.current, card.offsetHeight) : "bottom";
|
|
934
|
+
edgeRef.current = next;
|
|
935
|
+
setEdge(next);
|
|
936
|
+
setStyle(dockStyle(next, rect, band));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
setEdge(null);
|
|
454
940
|
setStyle(
|
|
455
941
|
placeCard(rect, card.offsetWidth, card.offsetHeight, window.innerWidth, window.innerHeight, 12, step.placement, dir)
|
|
456
942
|
);
|
|
457
|
-
}, [rect, index, step.placement, dir]);
|
|
943
|
+
}, [ref, rect, index, step.placement, step.target, dir, docked, bandTick]);
|
|
944
|
+
const awaitsTarget = (step.awaitClick || step.awaitInput) && step.target && rect || awaitsRelease;
|
|
458
945
|
return /* @__PURE__ */ jsxs(
|
|
459
946
|
"div",
|
|
460
947
|
{
|
|
461
948
|
ref,
|
|
462
949
|
tabIndex: -1,
|
|
463
950
|
style,
|
|
464
|
-
|
|
951
|
+
"data-tour-card": "",
|
|
952
|
+
"data-tour-docked": edge ?? void 0,
|
|
953
|
+
className: cn(
|
|
954
|
+
"pointer-events-auto fixed z-[61] w-80 max-w-[calc(100vw-1.5rem)] rounded-lg border border-subtle bg-surface p-4 shadow-xl outline-none transition-all duration-200 ease-out",
|
|
955
|
+
// Docked, the height is capped: the body scrolls, the title and buttons stay.
|
|
956
|
+
docked && "flex flex-col"
|
|
957
|
+
),
|
|
465
958
|
children: [
|
|
466
|
-
/* @__PURE__ */
|
|
467
|
-
|
|
959
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-sm font-semibold text-primary", children: [
|
|
960
|
+
/* @__PURE__ */ jsx("span", { children: step.title }),
|
|
961
|
+
step.optional && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-subtle px-1.5 py-px text-xs font-normal text-muted", children: labels.optional })
|
|
962
|
+
] }),
|
|
963
|
+
/* @__PURE__ */ jsx(
|
|
964
|
+
"div",
|
|
965
|
+
{
|
|
966
|
+
id: bodyId,
|
|
967
|
+
className: cn("mt-1 text-sm leading-relaxed text-secondary", docked && "min-h-0 shrink overflow-y-auto"),
|
|
968
|
+
children: step.body
|
|
969
|
+
}
|
|
970
|
+
),
|
|
468
971
|
step.action && /* @__PURE__ */ jsx("div", { className: "mt-3", children: step.action }),
|
|
469
972
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2", children: [
|
|
470
973
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
@@ -478,12 +981,31 @@ function TourCard({
|
|
|
478
981
|
}
|
|
479
982
|
),
|
|
480
983
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
481
|
-
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(
|
|
984
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(position, total) }),
|
|
482
985
|
!isFirst && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onBack, className: "px-2.5 py-1 text-xs", children: labels.back }),
|
|
483
|
-
|
|
986
|
+
step.optional && locked && !awaitsTarget && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onLeaveEmpty, className: "px-2.5 py-1 text-xs", children: labels.leaveEmpty }),
|
|
987
|
+
!awaitsTarget && /* @__PURE__ */ jsx(
|
|
988
|
+
Button,
|
|
989
|
+
{
|
|
990
|
+
variant: "accent",
|
|
991
|
+
onClick: onNext,
|
|
992
|
+
"aria-disabled": locked || void 0,
|
|
993
|
+
"aria-describedby": locked ? hintId : void 0,
|
|
994
|
+
className: cn("px-2.5 py-1 text-xs", locked && "opacity-60"),
|
|
995
|
+
children: isLast ? labels.done : labels.next
|
|
996
|
+
}
|
|
997
|
+
)
|
|
484
998
|
] })
|
|
485
999
|
] }),
|
|
486
|
-
|
|
1000
|
+
awaitsTarget && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: awaitsRelease ? step.awaitGateHint ?? labels.awaitGateHint : step.awaitInput && !step.awaitClick ? labels.awaitInputHint : labels.awaitClickHint }),
|
|
1001
|
+
awaitsRelease && /* @__PURE__ */ jsxs("div", { "aria-live": "assertive", "aria-atomic": "true", className: "space-y-1", children: [
|
|
1002
|
+
refusal && /* @__PURE__ */ jsxs("p", { "data-tour-refused": "", className: "text-xs text-danger", children: [
|
|
1003
|
+
refusal.reason ?? labels.awaitGateRefused,
|
|
1004
|
+
refusal.seq % 2 === 0 ? "\u200B" : ""
|
|
1005
|
+
] }),
|
|
1006
|
+
refusal && backTo && /* @__PURE__ */ jsx(Button, { variant: "link", onClick: backTo.go, className: "text-xs", children: labels.backToStep(backTo.title) })
|
|
1007
|
+
] }),
|
|
1008
|
+
locked && !awaitsTarget && /* @__PURE__ */ jsx("div", { id: hintId, className: "text-center text-xs italic text-placeholder", children: gateHint })
|
|
487
1009
|
] })
|
|
488
1010
|
]
|
|
489
1011
|
}
|
|
@@ -493,6 +1015,8 @@ export {
|
|
|
493
1015
|
DEFAULT_TOUR_LABELS,
|
|
494
1016
|
TourProvider,
|
|
495
1017
|
useTour,
|
|
1018
|
+
useTourGate,
|
|
1019
|
+
useTourGateRelease,
|
|
496
1020
|
useTourOptional
|
|
497
1021
|
};
|
|
498
1022
|
//# sourceMappingURL=tour.js.map
|