@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
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, useContext, useLayoutEffect, useMemo, useRef } from "react";
|
|
3
|
+
function devWarnings() {
|
|
4
|
+
return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
|
|
5
|
+
}
|
|
6
|
+
function createGateRegistry() {
|
|
7
|
+
let active = false;
|
|
8
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
9
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
10
|
+
const signals = /* @__PURE__ */ new Set();
|
|
11
|
+
const warned = /* @__PURE__ */ new Set();
|
|
12
|
+
let version = 0;
|
|
13
|
+
const notify = (key) => {
|
|
14
|
+
version++;
|
|
15
|
+
const stack = stacks.get(key);
|
|
16
|
+
if (stack && stack.length > 1 && active && devWarnings() && !warned.has(key)) {
|
|
17
|
+
const values = new Set(stack.map((e) => e.satisfied));
|
|
18
|
+
if (values.size > 1) {
|
|
19
|
+
warned.add(key);
|
|
20
|
+
console.warn(
|
|
21
|
+
`[ui-kit] Two components publish the tour gate "${key}" with different values; the one mounted last wins. Qualify gate keys by form (\`txn.amount\`, \`account.name\`).`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const listener of listeners) listener();
|
|
26
|
+
};
|
|
27
|
+
return {
|
|
28
|
+
get(key) {
|
|
29
|
+
const stack = stacks.get(key);
|
|
30
|
+
return stack && stack.length > 0 ? stack[stack.length - 1].satisfied : void 0;
|
|
31
|
+
},
|
|
32
|
+
has(key) {
|
|
33
|
+
return (stacks.get(key)?.length ?? 0) > 0;
|
|
34
|
+
},
|
|
35
|
+
subscribe(listener) {
|
|
36
|
+
listeners.add(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
listeners.delete(listener);
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
version: () => version,
|
|
42
|
+
push(key, entry) {
|
|
43
|
+
const stack = stacks.get(key) ?? [];
|
|
44
|
+
stack.push(entry);
|
|
45
|
+
stacks.set(key, stack);
|
|
46
|
+
notify(key);
|
|
47
|
+
},
|
|
48
|
+
pop(key, entry) {
|
|
49
|
+
const stack = stacks.get(key);
|
|
50
|
+
if (!stack) return;
|
|
51
|
+
const at = stack.indexOf(entry);
|
|
52
|
+
if (at >= 0) stack.splice(at, 1);
|
|
53
|
+
if (stack.length === 0) stacks.delete(key);
|
|
54
|
+
notify(key);
|
|
55
|
+
},
|
|
56
|
+
changed: notify,
|
|
57
|
+
setActive(value) {
|
|
58
|
+
active = value;
|
|
59
|
+
},
|
|
60
|
+
listen(listener) {
|
|
61
|
+
signals.add(listener);
|
|
62
|
+
return () => {
|
|
63
|
+
signals.delete(listener);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
emit(signal) {
|
|
67
|
+
for (const listener of signals) listener(signal);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const TourInternalContext = createContext(null);
|
|
72
|
+
function useTourGate(key, satisfied) {
|
|
73
|
+
const gates = useContext(TourInternalContext)?.gates ?? null;
|
|
74
|
+
const entryRef = useRef(null);
|
|
75
|
+
const satisfiedRef = useRef(satisfied);
|
|
76
|
+
useLayoutEffect(() => {
|
|
77
|
+
satisfiedRef.current = satisfied;
|
|
78
|
+
const entry = entryRef.current;
|
|
79
|
+
if (!gates || key == null || !entry || entry.satisfied === satisfied) return;
|
|
80
|
+
entry.satisfied = satisfied;
|
|
81
|
+
gates.changed(key);
|
|
82
|
+
}, [gates, key, satisfied]);
|
|
83
|
+
useLayoutEffect(() => {
|
|
84
|
+
if (!gates || key == null) return;
|
|
85
|
+
const entry = { satisfied: satisfiedRef.current };
|
|
86
|
+
entryRef.current = entry;
|
|
87
|
+
gates.push(key, entry);
|
|
88
|
+
return () => {
|
|
89
|
+
entryRef.current = null;
|
|
90
|
+
gates.pop(key, entry);
|
|
91
|
+
};
|
|
92
|
+
}, [gates, key]);
|
|
93
|
+
}
|
|
94
|
+
const NO_RELEASE = { release: () => {
|
|
95
|
+
}, refuse: () => {
|
|
96
|
+
} };
|
|
97
|
+
function useTourGateRelease() {
|
|
98
|
+
const gates = useContext(TourInternalContext)?.gates ?? null;
|
|
99
|
+
return useMemo(
|
|
100
|
+
() => gates ? {
|
|
101
|
+
release: (key) => gates.emit({ kind: "release", key }),
|
|
102
|
+
refuse: (key, reason) => gates.emit({ kind: "refuse", key, reason })
|
|
103
|
+
} : NO_RELEASE,
|
|
104
|
+
[gates]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
TourInternalContext,
|
|
109
|
+
createGateRegistry,
|
|
110
|
+
devWarnings,
|
|
111
|
+
useTourGate,
|
|
112
|
+
useTourGateRelease
|
|
113
|
+
};
|
|
114
|
+
//# sourceMappingURL=tour-gates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tour/tour-gates.ts"],"sourcesContent":["import { createContext, useContext, useLayoutEffect, useMemo, useRef } from \"react\";\n\n/*\n * # Value gates (docs/tours-harmonization.md §11.1)\n *\n * A form publishes whether a field holds what a tour waits for (`useTourGate`), a step\n * names the key (`TourStep.gate`). The form knows nothing about steps, the step nothing\n * about the form's state: the registry between them is a `Map` held by the provider.\n *\n * - Each key holds a STACK of publishers in mount order; the most recently mounted one\n * is current. When it unmounts, the one below it is current again. StrictMode's\n * double mount is push, pop, push.\n * - Writes happen in a layout effect, only when `key` or `satisfied` changes, and never\n * re-render the provider: only the overlay subscribes (`useSyncExternalStore`), and it\n * is mounted only while a tour runs.\n * - The same object carries the release bus of `awaitGate` steps (§11.4): `release` and\n * `refuse` are EVENTS, not states, and are not buffered.\n */\n\n/** One publisher of a key. */\ninterface GateEntry {\n satisfied: boolean;\n}\n\n/** `release(key)` / `refuse(key, reason)` from the app, as the provider hears them. */\nexport type GateSignal = { kind: \"release\"; key: string } | { kind: \"refuse\"; key: string; reason?: string };\n\nexport interface GateRegistry {\n /** The current publisher's value; `undefined` while nobody publishes `key`. */\n get(key: string): boolean | undefined;\n /** Is anyone publishing `key`? */\n has(key: string): boolean;\n /** For `useSyncExternalStore`: bumped on every change. */\n subscribe(listener: () => void): () => void;\n version(): number;\n push(key: string, entry: GateEntry): void;\n pop(key: string, entry: GateEntry): void;\n changed(key: string): void;\n /** Whether a tour runs (for the two-publishers warning). */\n setActive(active: boolean): void;\n /** The release bus. */\n listen(listener: (signal: GateSignal) => void): () => void;\n emit(signal: GateSignal): void;\n}\n\n/** The kit's dev switch (as number-input's): a development build, never under vitest. */\nexport function devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\nexport function createGateRegistry(): GateRegistry {\n let active = false;\n const stacks = new Map<string, GateEntry[]>();\n const listeners = new Set<() => void>();\n const signals = new Set<(signal: GateSignal) => void>();\n const warned = new Set<string>();\n let version = 0;\n const notify = (key: string) => {\n version++;\n const stack = stacks.get(key);\n // Two publishers of one key that disagree while a tour runs: the most recent wins,\n // which is right, but an app keying fields `amount` in two forms wants to hear it.\n if (stack && stack.length > 1 && active && devWarnings() && !warned.has(key)) {\n const values = new Set(stack.map((e) => e.satisfied));\n if (values.size > 1) {\n warned.add(key);\n console.warn(\n `[ui-kit] Two components publish the tour gate \"${key}\" with different values; the one mounted last ` +\n \"wins. Qualify gate keys by form (`txn.amount`, `account.name`).\",\n );\n }\n }\n for (const listener of listeners) listener();\n };\n return {\n get(key) {\n const stack = stacks.get(key);\n return stack && stack.length > 0 ? stack[stack.length - 1].satisfied : undefined;\n },\n has(key) {\n return (stacks.get(key)?.length ?? 0) > 0;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n version: () => version,\n push(key, entry) {\n const stack = stacks.get(key) ?? [];\n stack.push(entry);\n stacks.set(key, stack);\n notify(key);\n },\n pop(key, entry) {\n const stack = stacks.get(key);\n if (!stack) return;\n const at = stack.indexOf(entry);\n if (at >= 0) stack.splice(at, 1);\n if (stack.length === 0) stacks.delete(key);\n notify(key);\n },\n changed: notify,\n setActive(value) {\n active = value;\n },\n listen(listener) {\n signals.add(listener);\n return () => {\n signals.delete(listener);\n };\n },\n emit(signal) {\n for (const listener of signals) listener(signal);\n },\n };\n}\n\n/** What the provider shares with the gate hooks and `TourGuideButton` — internal. */\nexport interface TourInternals {\n gates: GateRegistry;\n /** Did the provider get a `launch`? */\n canLaunch: boolean;\n}\n\nexport const TourInternalContext = createContext<TourInternals | null>(null);\n\n/**\n * Publish whether this field holds what a tour waits for (§11.1): `useTourGate(\"txn.amount\",\n * amount !== \"\")` beside the field. A step with `gate: \"txn.amount\"` keeps its Next locked\n * until it is `true`. A key no step names as `gate` is a FACT a step's `when` can ask\n * (`ctx.gate(\"txn.transfer\")`).\n *\n * - `key` `null`/`undefined` publishes nothing: one field component used by a create and\n * an edit form takes part only in create (`mode === \"create\" ? \"txn.memo\" : null`).\n * - Publish the value's state. Do not invent a \"touched\" gate: a field already filled\n * when the step arrives (a date prefilled with today) shows Next open at once, which is\n * intended.\n * - Qualify keys by form (`txn.amount`, not `amount`). Two publishers of one key: the one\n * mounted last wins.\n * - A no-op without a `TourProvider`, so a form's tests need none. It keeps publishing\n * while no tour runs, because \"Guide me\" can start inside a form already filled.\n */\nexport function useTourGate(key: string | null | undefined, satisfied: boolean): void {\n const gates = useContext(TourInternalContext)?.gates ?? null;\n const entryRef = useRef<GateEntry | null>(null);\n const satisfiedRef = useRef(satisfied);\n useLayoutEffect(() => {\n satisfiedRef.current = satisfied;\n const entry = entryRef.current;\n if (!gates || key == null || !entry || entry.satisfied === satisfied) return;\n entry.satisfied = satisfied;\n gates.changed(key);\n }, [gates, key, satisfied]);\n useLayoutEffect(() => {\n if (!gates || key == null) return;\n const entry: GateEntry = { satisfied: satisfiedRef.current };\n entryRef.current = entry;\n gates.push(key, entry);\n return () => {\n entryRef.current = null;\n gates.pop(key, entry);\n };\n }, [gates, key]);\n}\n\n/** What {@link useTourGateRelease} hands back. */\nexport interface TourGateRelease {\n /** The awaited thing happened (a save succeeded): the tour moves past the step that awaits `key`. */\n release(key: string): void;\n /** It was refused (a save failed): the step awaiting `key` stays and shows `reason`. */\n refuse(key: string, reason?: string): void;\n}\n\nconst NO_RELEASE: TourGateRelease = { release: () => {}, refuse: () => {} };\n\n/**\n * The signals of `awaitGate` steps (§11.4), usually from a mutation's `onSuccess` /\n * `onError`. Stable identities. No-ops without a provider, and while no tour runs or no\n * step of the run awaits the key: a release is an event and is never buffered.\n */\nexport function useTourGateRelease(): TourGateRelease {\n const gates = useContext(TourInternalContext)?.gates ?? null;\n return useMemo(\n () =>\n gates\n ? {\n release: (key: string) => gates.emit({ kind: \"release\", key }),\n refuse: (key: string, reason?: string) => gates.emit({ kind: \"refuse\", key, reason }),\n }\n : NO_RELEASE,\n [gates],\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
3
|
+
import { type TourAt } from "./tour.js";
|
|
4
|
+
import { DEFAULT_TOUR_GUIDE_LABELS, type TourGuideLabels } from "./tour-guide-labels.js";
|
|
5
|
+
export { DEFAULT_TOUR_GUIDE_LABELS };
|
|
6
|
+
export type { TourGuideLabels };
|
|
7
|
+
export interface TourGuideButtonProps {
|
|
8
|
+
/** The tour id handed to the provider's `launch`. */
|
|
9
|
+
tour: string;
|
|
10
|
+
/** Usually the form's first in-form anchor (`start(tour, { at })`). */
|
|
11
|
+
at?: TourAt;
|
|
12
|
+
/** Default `"ghost"`. */
|
|
13
|
+
variant?: "ghost" | "secondary";
|
|
14
|
+
/** Default `"sm"`. */
|
|
15
|
+
size?: "sm" | "md";
|
|
16
|
+
labels?: LabelOverride<TourGuideLabels>;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* "Guide me" inside a form (docs/tours-harmonization.md §11.5): starts the tour `tour` by id,
|
|
21
|
+
* at `at`, through `TourProvider`'s `launch` — the run starts inside the open form, with no
|
|
22
|
+
* intro and no Back past `at`, and counts as completing the tour (Marcel, Q2).
|
|
23
|
+
*
|
|
24
|
+
* A decoration: it renders nothing without a provider, without `launch`, or while a tour
|
|
25
|
+
* runs. Place it in the form's header or in `FormActions`' `start` slot.
|
|
26
|
+
*/
|
|
27
|
+
export declare function TourGuideButton({ tour, at, variant, size, labels, className, }: TourGuideButtonProps): ReactElement | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useContext, useId } from "react";
|
|
4
|
+
import { Compass } from "lucide-react";
|
|
5
|
+
import { Button } from "../components/button.js";
|
|
6
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
7
|
+
import { useTourOptional } from "./tour.js";
|
|
8
|
+
import { TourInternalContext } from "./tour-gates.js";
|
|
9
|
+
import { DEFAULT_TOUR_GUIDE_LABELS } from "./tour-guide-labels.js";
|
|
10
|
+
function TourGuideButton({
|
|
11
|
+
tour,
|
|
12
|
+
at,
|
|
13
|
+
variant = "ghost",
|
|
14
|
+
size = "sm",
|
|
15
|
+
labels,
|
|
16
|
+
className
|
|
17
|
+
}) {
|
|
18
|
+
const engine = useTourOptional();
|
|
19
|
+
const internals = useContext(TourInternalContext);
|
|
20
|
+
const words = useKitLabels("tourGuide", DEFAULT_TOUR_GUIDE_LABELS, labels);
|
|
21
|
+
const descriptionId = useId();
|
|
22
|
+
if (!engine || !internals?.canLaunch || engine.active) return null;
|
|
23
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(
|
|
25
|
+
Button,
|
|
26
|
+
{
|
|
27
|
+
variant,
|
|
28
|
+
size,
|
|
29
|
+
className,
|
|
30
|
+
"aria-describedby": descriptionId,
|
|
31
|
+
onClick: () => engine.start(tour, { at }),
|
|
32
|
+
children: [
|
|
33
|
+
/* @__PURE__ */ jsx(Compass, { className: "size-4", "aria-hidden": "true" }),
|
|
34
|
+
words.label
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
/* @__PURE__ */ jsx("span", { id: descriptionId, hidden: true, children: words.description })
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
DEFAULT_TOUR_GUIDE_LABELS,
|
|
43
|
+
TourGuideButton
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=tour-guide-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tour/tour-guide-button.tsx"],"sourcesContent":["import { useContext, useId } from \"react\";\nimport type { ReactElement } from \"react\";\nimport { Compass } from \"lucide-react\";\nimport { Button } from \"../components/button\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { useTourOptional, type TourAt } from \"./tour\";\nimport { TourInternalContext } from \"./tour-gates\";\nimport { DEFAULT_TOUR_GUIDE_LABELS, type TourGuideLabels } from \"./tour-guide-labels\";\n\nexport { DEFAULT_TOUR_GUIDE_LABELS };\nexport type { TourGuideLabels };\n\nexport interface TourGuideButtonProps {\n /** The tour id handed to the provider's `launch`. */\n tour: string;\n /** Usually the form's first in-form anchor (`start(tour, { at })`). */\n at?: TourAt;\n /** Default `\"ghost\"`. */\n variant?: \"ghost\" | \"secondary\";\n /** Default `\"sm\"`. */\n size?: \"sm\" | \"md\";\n labels?: LabelOverride<TourGuideLabels>;\n className?: string;\n}\n\n/**\n * \"Guide me\" inside a form (docs/tours-harmonization.md §11.5): starts the tour `tour` by id,\n * at `at`, through `TourProvider`'s `launch` — the run starts inside the open form, with no\n * intro and no Back past `at`, and counts as completing the tour (Marcel, Q2).\n *\n * A decoration: it renders nothing without a provider, without `launch`, or while a tour\n * runs. Place it in the form's header or in `FormActions`' `start` slot.\n */\nexport function TourGuideButton({\n tour,\n at,\n variant = \"ghost\",\n size = \"sm\",\n labels,\n className,\n}: TourGuideButtonProps): ReactElement | null {\n const engine = useTourOptional();\n const internals = useContext(TourInternalContext);\n const words = useKitLabels(\"tourGuide\", DEFAULT_TOUR_GUIDE_LABELS, labels);\n const descriptionId = useId();\n if (!engine || !internals?.canLaunch || engine.active) return null;\n return (\n <>\n <Button\n variant={variant}\n size={size}\n className={className}\n aria-describedby={descriptionId}\n onClick={() => engine.start(tour, { at })}\n >\n <Compass className=\"size-4\" aria-hidden=\"true\" />\n {words.label}\n </Button>\n <span id={descriptionId} hidden>\n {words.description}\n </span>\n </>\n );\n}\n"],"mappings":";AA+CI,mBAQI,KAPF,YADF;AA/CJ,SAAS,YAAY,aAAa;AAElC,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,oBAAwC;AACjD,SAAS,uBAAoC;AAC7C,SAAS,2BAA2B;AACpC,SAAS,iCAAuD;AA0BzD,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAA8C;AAC5C,QAAM,SAAS,gBAAgB;AAC/B,QAAM,YAAY,WAAW,mBAAmB;AAChD,QAAM,QAAQ,aAAa,aAAa,2BAA2B,MAAM;AACzE,QAAM,gBAAgB,MAAM;AAC5B,MAAI,CAAC,UAAU,CAAC,WAAW,aAAa,OAAO,OAAQ,QAAO;AAC9D,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAkB;AAAA,QAClB,SAAS,MAAM,OAAO,MAAM,MAAM,EAAE,GAAG,CAAC;AAAA,QAExC;AAAA,8BAAC,WAAQ,WAAU,UAAS,eAAY,QAAO;AAAA,UAC9C,MAAM;AAAA;AAAA;AAAA,IACT;AAAA,IACA,oBAAC,UAAK,IAAI,eAAe,QAAM,MAC5B,gBAAM,aACT;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The `tourGuide` namespace: the form's "Guide me". */
|
|
2
|
+
export interface TourGuideLabels {
|
|
3
|
+
/** The button's text. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Its accessible description. */
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const DEFAULT_TOUR_GUIDE_LABELS: TourGuideLabels;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tour/tour-guide-labels.ts"],"sourcesContent":["// The English words of `TourGuideButton` (docs/tours-harmonization.md §11), in a module with\n// no component code, like every `*-labels.ts` (0.34, KS U8).\n\n/** The `tourGuide` namespace: the form's \"Guide me\". */\nexport interface TourGuideLabels {\n /** The button's text. */\n label: string;\n /** Its accessible description. */\n description: string;\n}\n\nexport const DEFAULT_TOUR_GUIDE_LABELS: TourGuideLabels = {\n label: \"Guide me\",\n description: \"Walks you through this form, field by field\",\n};\n"],"mappings":";AAWO,MAAM,4BAA6C;AAAA,EACxD,OAAO;AAAA,EACP,aAAa;AACf;","names":[]}
|
package/dist/tour/tour-labels.js
CHANGED
|
@@ -6,7 +6,15 @@ const DEFAULT_TOUR_LABELS = {
|
|
|
6
6
|
done: "Done",
|
|
7
7
|
awaitClickHint: "Click the highlighted element to continue",
|
|
8
8
|
awaitInputHint: "Enter a value in the highlighted field to continue",
|
|
9
|
-
step: (c, t) => `${c} / ${t}
|
|
9
|
+
step: (c, t) => `${c} / ${t}`,
|
|
10
|
+
// 0.50: guided form tours (docs/tours-harmonization.md §11).
|
|
11
|
+
leaveEmpty: "Leave empty",
|
|
12
|
+
optional: "Optional",
|
|
13
|
+
gateHint: "Fill in the highlighted field to continue",
|
|
14
|
+
gateReady: "Ready. Press Next or Enter to continue.",
|
|
15
|
+
awaitGateHint: "Save to continue",
|
|
16
|
+
awaitGateRefused: "That wasn\u2019t saved yet. Check the form and try again.",
|
|
17
|
+
backToStep: (title) => `Back to \u201C${title}\u201D`
|
|
10
18
|
};
|
|
11
19
|
export {
|
|
12
20
|
DEFAULT_TOUR_LABELS
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tour/tour-labels.ts"],"sourcesContent":["// The English words of ./tour, in a module with no component code: the English catalogue\n// (src/i18n/defaults.ts) imports them without the component, so `en` loads as one chunk\n// like the other languages (0.34, KS U8). ./tour re-exports them.\n\nimport type { TourLabels } from \"./tour\";\n\nexport const DEFAULT_TOUR_LABELS: TourLabels = {\n next: \"Next\",\n back: \"Back\",\n skip: \"Skip\",\n done: \"Done\",\n awaitClickHint: \"Click the highlighted element to continue\",\n awaitInputHint: \"Enter a value in the highlighted field to continue\",\n step: (c, t) => `${c} / ${t}`,\n};\n"],"mappings":";AAMO,MAAM,sBAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/tour/tour-labels.ts"],"sourcesContent":["// The English words of ./tour, in a module with no component code: the English catalogue\n// (src/i18n/defaults.ts) imports them without the component, so `en` loads as one chunk\n// like the other languages (0.34, KS U8). ./tour re-exports them.\n\nimport type { TourLabels } from \"./tour\";\n\nexport const DEFAULT_TOUR_LABELS: TourLabels = {\n next: \"Next\",\n back: \"Back\",\n skip: \"Skip\",\n done: \"Done\",\n awaitClickHint: \"Click the highlighted element to continue\",\n awaitInputHint: \"Enter a value in the highlighted field to continue\",\n step: (c, t) => `${c} / ${t}`,\n // 0.50: guided form tours (docs/tours-harmonization.md §11).\n leaveEmpty: \"Leave empty\",\n optional: \"Optional\",\n gateHint: \"Fill in the highlighted field to continue\",\n gateReady: \"Ready. Press Next or Enter to continue.\",\n awaitGateHint: \"Save to continue\",\n awaitGateRefused: \"That wasn’t saved yet. Check the form and try again.\",\n backToStep: (title) => `Back to “${title}”`,\n};\n"],"mappings":";AAMO,MAAM,sBAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;AAAA;AAAA,EAE3B,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,YAAY,CAAC,UAAU,iBAAY,KAAK;AAC1C;","names":[]}
|
package/dist/tour/tour.d.ts
CHANGED
|
@@ -3,12 +3,14 @@ import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
|
3
3
|
import { DEFAULT_TOUR_LABELS } from "./tour-labels.js";
|
|
4
4
|
import type { TourInfo } from "./tour-info.js";
|
|
5
5
|
import { type UseTourProgressOptions } from "./use-tour-progress.js";
|
|
6
|
+
export { useTourGate, useTourGateRelease } from "./tour-gates.js";
|
|
7
|
+
export type { TourGateRelease } from "./tour-gates.js";
|
|
6
8
|
/**
|
|
7
9
|
* `start`/`end` follow the reading direction (`end` is the right side in LTR and the
|
|
8
10
|
* left side in RTL) and are what a translated app wants. `left`/`right` stay physical
|
|
9
11
|
* for the steps that genuinely mean a screen side.
|
|
10
12
|
*/
|
|
11
|
-
export type TourPlacement = "top" | "right" | "bottom" | "left" | "start" | "end" | "center";
|
|
13
|
+
export type TourPlacement = "top" | "right" | "bottom" | "left" | "start" | "end" | "center" | "dock";
|
|
12
14
|
export interface TourStep {
|
|
13
15
|
/** CSS selector for the element to spotlight. Omit for a centered step
|
|
14
16
|
* (welcome/finish). If the target isn't found/visible, the step centers. */
|
|
@@ -17,13 +19,22 @@ export interface TourStep {
|
|
|
17
19
|
body: ReactNode;
|
|
18
20
|
/** Extra footer content, e.g. a "load sample data" shortcut. */
|
|
19
21
|
action?: ReactNode;
|
|
20
|
-
/** Spotlight padding around the target, px (default 8).
|
|
22
|
+
/** Spotlight padding around the target, px (default 8). On a field step the spot also
|
|
23
|
+
* takes in the lines the field's `aria-describedby` names when they sit within the
|
|
24
|
+
* ring's reach of it (a hint or warning line under the field, keksdose S1, §11.6). */
|
|
21
25
|
padding?: number;
|
|
22
26
|
/** Where the card sits relative to the target. Omit for auto placement
|
|
23
27
|
* (below → above → centered). "center" ignores the target and centers on
|
|
24
28
|
* screen. "start"/"end" are the logical sides and flip in RTL; "left"/"right"
|
|
25
29
|
* are physical. An explicit side flips to the opposite side if it would
|
|
26
|
-
* overflow, then clamps into the viewport.
|
|
30
|
+
* overflow, then clamps into the viewport.
|
|
31
|
+
*
|
|
32
|
+
* `"dock"` (0.50, §11.7): below `sm` (640 px) the card is pinned full-width to the top
|
|
33
|
+
* or bottom edge of the usable band — the visual viewport less the keyboard and any
|
|
34
|
+
* `[data-tour-obstruction]` (the number pad) — on the edge away from the field, and the
|
|
35
|
+
* field's scroll container brings it into the other half. From `sm` up it is auto
|
|
36
|
+
* placement. On a phone a field step (`interactive`, `awaitInput`, `gate`, `awaitGate`)
|
|
37
|
+
* with a target and no `placement` docks by default (`TourProvider dockOnPhones`). */
|
|
27
38
|
placement?: TourPlacement;
|
|
28
39
|
/** Advance only when the user clicks the spotlighted target itself (not the
|
|
29
40
|
* card's Next button). Requires `target`: the real control's own handler runs
|
|
@@ -58,9 +69,58 @@ export interface TourStep {
|
|
|
58
69
|
* button) has nothing to compare: call `useTour().next()` from its `onChange`.
|
|
59
70
|
*/
|
|
60
71
|
awaitInput?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* 0.50 (docs/tours-harmonization.md §11.1): Next stays locked until the gate `key` is
|
|
74
|
+
* satisfied — the form publishes it with `useTourGate(key, satisfied)` beside the field.
|
|
75
|
+
* Implies `interactive`. The locked Next is `aria-disabled` with {@link gateHint} under
|
|
76
|
+
* it; pressing it focuses the field. Once satisfied the tour does NOT advance by itself:
|
|
77
|
+
* Next, Enter in the field, or focus leaving the field to nowhere or to the card (the
|
|
78
|
+
* number pad's Done) moves on. A key nobody publishes is not enforced (the form closed
|
|
79
|
+
* under the tour): Next opens. A step may have a `gate` and no `target`.
|
|
80
|
+
*/
|
|
81
|
+
gate?: string;
|
|
82
|
+
/** Overrides `labels.gateHint` for this step. */
|
|
83
|
+
gateHint?: string;
|
|
84
|
+
/**
|
|
85
|
+
* 0.50 (§11.4): wait for the app to RELEASE this key — `useTourGateRelease().release(key)`
|
|
86
|
+
* from the save's `onSuccess`. Implies `interactive`; usually it targets the Save button,
|
|
87
|
+
* whose hole stays click-through. Next is replaced by {@link awaitGateHint}, and a click
|
|
88
|
+
* does not advance: only `release` does. A release that arrives on an earlier step (Enter
|
|
89
|
+
* in the last field submitted the form) jumps past the first later step awaiting the key.
|
|
90
|
+
* `refuse(key, reason)` keeps the step and shows the reason, with a "Back to …" link to the
|
|
91
|
+
* first earlier field whose gate is now unsatisfied; the kit never jumps there itself.
|
|
92
|
+
* A viewer who cannot save gets `awaitGate: undefined`, so a plain Next.
|
|
93
|
+
*/
|
|
94
|
+
awaitGate?: string;
|
|
95
|
+
/** Overrides `labels.awaitGateHint` for this step. */
|
|
96
|
+
awaitGateHint?: string;
|
|
97
|
+
/**
|
|
98
|
+
* 0.50 (§11.2): the field may stay empty. While the `gate` is unsatisfied the card
|
|
99
|
+
* offers "Leave empty" beside the locked Next, which moves on without touching the field
|
|
100
|
+
* (the kit never writes app values); the title carries an "Optional" chip. Not a skip:
|
|
101
|
+
* the run continues and can complete. Without a `gate`, only the chip.
|
|
102
|
+
*/
|
|
103
|
+
optional?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* 0.50 (§11.3): shown only if this returns true on ARRIVAL (evaluated before
|
|
106
|
+
* `beforeStep`, so a skipped step never navigates). Next walks forward to the first step
|
|
107
|
+
* shown, Back to the nearest earlier one shown now. Branch facts are gates:
|
|
108
|
+
* `when: (c) => c.gate("txn.transfer") === true`. Keep it pure and cheap: the counter
|
|
109
|
+
* evaluates every step's `when` on each arrival. A throw counts as skipped.
|
|
110
|
+
*/
|
|
111
|
+
when?: (ctx: TourStepContext) => boolean;
|
|
61
112
|
/** Runs before the step is shown — e.g. navigate to a route, open a panel.
|
|
62
|
-
* May be async; the step waits for it, then locates the target.
|
|
63
|
-
|
|
113
|
+
* May be async; the step waits for it, then locates the target. Receives the
|
|
114
|
+
* {@link TourStepContext} (0.50); a step entered by "Guide me" sees `via: "at"` and
|
|
115
|
+
* should leave an open form alone. */
|
|
116
|
+
beforeStep?: (ctx: TourStepContext) => void | Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
/** What a step's `when` and `beforeStep` may ask on arrival (0.50, §11.3). */
|
|
119
|
+
export interface TourStepContext {
|
|
120
|
+
/** The gate's current value: true/false, or undefined while nobody publishes it. */
|
|
121
|
+
gate(key: string): boolean | undefined;
|
|
122
|
+
/** How this step is being entered. */
|
|
123
|
+
via: "start" | "at" | "next" | "back" | "goto";
|
|
64
124
|
}
|
|
65
125
|
export interface TourLabels {
|
|
66
126
|
next: string;
|
|
@@ -71,10 +131,42 @@ export interface TourLabels {
|
|
|
71
131
|
awaitClickHint: string;
|
|
72
132
|
/** Shown in place of the Next button on an `awaitInput` step (0.46). */
|
|
73
133
|
awaitInputHint: string;
|
|
74
|
-
/** Step counter, e.g. (2, 7) => "2 / 7". */
|
|
134
|
+
/** Step counter, e.g. (2, 7) => "2 / 7". Counts the steps the run shows (0.50). */
|
|
75
135
|
step: (current: number, total: number) => string;
|
|
136
|
+
/** 0.50: an `optional` gate step's button while the field is empty. */
|
|
137
|
+
leaveEmpty: string;
|
|
138
|
+
/** 0.50: the chip beside an `optional` step's title. */
|
|
139
|
+
optional: string;
|
|
140
|
+
/** 0.50: under a `gate` step's locked Next (`TourStep.gateHint` overrides it). */
|
|
141
|
+
gateHint: string;
|
|
142
|
+
/** 0.50: announced (screen readers only) when a gate step's field is filled. */
|
|
143
|
+
gateReady: string;
|
|
144
|
+
/** 0.50: in place of Next on an `awaitGate` step (`TourStep.awaitGateHint` overrides it). */
|
|
145
|
+
awaitGateHint: string;
|
|
146
|
+
/** 0.50: the warning when the app `refuse`s an `awaitGate` step without a reason. */
|
|
147
|
+
awaitGateRefused: string;
|
|
148
|
+
/** 0.50: the link to an earlier step whose field was emptied, after a refused save. */
|
|
149
|
+
backToStep: (title: string) => string;
|
|
76
150
|
}
|
|
77
151
|
export { DEFAULT_TOUR_LABELS };
|
|
152
|
+
/** 0.50 (§11.5): a step index, or the `data-tour` value of the step's anchor. */
|
|
153
|
+
export type TourAt = number | string;
|
|
154
|
+
/** `useTour().start`, both forms: what `TourProvider` hands its `launch` as the third argument. */
|
|
155
|
+
export type TourStart = TourContextValue["start"];
|
|
156
|
+
/**
|
|
157
|
+
* 0.50 (§11.5): the app's launcher — the shape `useTourUrlSync` already takes, plus `at`.
|
|
158
|
+
* It builds the steps (`prepare()`, `build()`, `tour: tourInfo(def)`) and passes `at` on to
|
|
159
|
+
* `start(steps, { …, at })`. Give the same function to `TourProvider launch` and to
|
|
160
|
+
* `useTourUrlSync`.
|
|
161
|
+
*
|
|
162
|
+
* `start` is the provider's own `start` (keksdose N4), so the launcher needs no ref to a
|
|
163
|
+
* component below the provider and may be defined above it. `useTourUrlSync` passes it too
|
|
164
|
+
* (under a provider). A two-argument launcher that reaches `start` its own way still works.
|
|
165
|
+
*/
|
|
166
|
+
export type TourLauncher = (id: string, opts: {
|
|
167
|
+
startIndex?: number;
|
|
168
|
+
at?: TourAt;
|
|
169
|
+
}, start: TourStart) => void | Promise<unknown>;
|
|
78
170
|
interface StartOptions {
|
|
79
171
|
/** Stable id of the tour, surfaced as `activeId` so the app can mirror it to
|
|
80
172
|
* the URL (deep-link / evaluation tracking) and record completion. Defaults to
|
|
@@ -86,6 +178,15 @@ interface StartOptions {
|
|
|
86
178
|
tour?: TourInfo;
|
|
87
179
|
/** Start at this step index instead of 0 (clamped) — for URL/deep-link resume. */
|
|
88
180
|
startIndex?: number;
|
|
181
|
+
/**
|
|
182
|
+
* 0.50 (§11.5): start here AND make it the floor — earlier steps never run (nor their
|
|
183
|
+
* `beforeStep`), Back stops here, `position`/`count` count from here, and the finish
|
|
184
|
+
* records the completion as a full run does (Marcel, Q2). A number is a step index
|
|
185
|
+
* (clamped); a string is the first step whose `target` contains `data-tour="<at>"`
|
|
186
|
+
* verbatim (unknown: 0, with a development warning). The `at` step is entered with
|
|
187
|
+
* `via: "at"`. With `startIndex` too, `startIndex` picks the step and `at` the floor.
|
|
188
|
+
*/
|
|
189
|
+
at?: TourAt;
|
|
89
190
|
/** Fires synchronously when the tour starts (e.g. enable a mock interceptor). */
|
|
90
191
|
onStart?: () => void;
|
|
91
192
|
/** After the last step: runs after the provider's progress write and `onComplete`. */
|
|
@@ -107,8 +208,17 @@ interface TourContextValue {
|
|
|
107
208
|
active: boolean;
|
|
108
209
|
/** Id of the running tour (from {@link StartOptions.id}), else null. */
|
|
109
210
|
activeId: string | null;
|
|
110
|
-
/**
|
|
111
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Start a tour with the given steps — or, by id (0.50), through the provider's `launch`:
|
|
213
|
+
* `start("create-transaction", { at: "txn-entry-mode" })`. Without a `launch` the id form
|
|
214
|
+
* does nothing (a development build warns).
|
|
215
|
+
*/
|
|
216
|
+
start: {
|
|
217
|
+
(id: string, opts?: {
|
|
218
|
+
at?: TourAt;
|
|
219
|
+
}): void;
|
|
220
|
+
(steps: TourStep[], opts?: StartOptions): void;
|
|
221
|
+
};
|
|
112
222
|
/** End the current tour (no callbacks fire). */
|
|
113
223
|
stop: () => void;
|
|
114
224
|
/** Advance one step; past the last step it finishes the tour (fires onFinish). */
|
|
@@ -121,6 +231,10 @@ interface TourContextValue {
|
|
|
121
231
|
index: number;
|
|
122
232
|
/** Total steps (0 while inactive). */
|
|
123
233
|
total: number;
|
|
234
|
+
/** 0.50: 1-based position among the steps this run shows, for "3 of 9"; 0 while inactive. */
|
|
235
|
+
position: number;
|
|
236
|
+
/** 0.50: how many steps this run shows, from its first (the `at` floor) to the end; 0 while inactive. */
|
|
237
|
+
count: number;
|
|
124
238
|
}
|
|
125
239
|
export declare function useTour(): TourContextValue;
|
|
126
240
|
/**
|
|
@@ -165,11 +279,21 @@ export declare function useTourOptional(): TourContextValue | null;
|
|
|
165
279
|
* new `storageKey` (an account switch on a shared PC) is followed on the next
|
|
166
280
|
* completion, without a remount.
|
|
167
281
|
*/
|
|
168
|
-
export declare function TourProvider({ children, labels, progress, onComplete, }: {
|
|
282
|
+
export declare function TourProvider({ children, labels, progress, onComplete, launch, dockOnPhones, }: {
|
|
169
283
|
children: ReactNode;
|
|
170
284
|
labels?: LabelOverride<TourLabels>;
|
|
285
|
+
/**
|
|
286
|
+
* 0.50 (§11.5): lets `start(id)` and `<TourGuideButton>` start a tour by id. Usually the
|
|
287
|
+
* same function the app gives `useTourUrlSync`.
|
|
288
|
+
*/
|
|
289
|
+
launch?: TourLauncher;
|
|
171
290
|
/** The app's tour progress: the same options its `useTourProgress` readers use. */
|
|
172
291
|
progress?: UseTourProgressOptions;
|
|
173
292
|
/** A tour's last step was finished; see {@link TourCompletion}. */
|
|
174
293
|
onComplete?: (tour: TourInfo | undefined, completion: TourCompletion) => void;
|
|
294
|
+
/**
|
|
295
|
+
* 0.50 (§11.7, Marcel's Q1): below `sm`, a field step with a target and no `placement`
|
|
296
|
+
* docks. Default `true`; `false` makes docking strictly opt-in (`placement: "dock"`).
|
|
297
|
+
*/
|
|
298
|
+
dockOnPhones?: boolean;
|
|
175
299
|
}): import("react").JSX.Element;
|