@fayz-ai/ui 0.21.0 → 0.22.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/dist/{chunk-P3YTL3MF.js → chunk-7G3O54CC.js} +3 -3
- package/dist/{chunk-P3YTL3MF.js.map → chunk-7G3O54CC.js.map} +1 -1
- package/dist/{chunk-AIUHVTVK.js → chunk-DHNUZF2X.js} +4 -3
- package/dist/chunk-DHNUZF2X.js.map +1 -0
- package/dist/{chunk-EGHEU3U5.js → chunk-PQUS22JE.js} +19 -5
- package/dist/chunk-PQUS22JE.js.map +1 -0
- package/dist/{chunk-MBGTFL4I.js → chunk-SUSW4LSW.js} +37 -6
- package/dist/chunk-SUSW4LSW.js.map +1 -0
- package/dist/{chunk-OYH7CEMB.js → chunk-ZSOT7KTE.js} +12 -8
- package/dist/chunk-ZSOT7KTE.js.map +1 -0
- package/dist/dashboard/OnboardingWidget.d.ts +4 -1
- package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
- package/dist/dashboard/SidebarOnboarding.d.ts +11 -1
- package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
- package/dist/dashboard/index.js +2 -2
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +445 -87
- package/dist/index.js.map +1 -1
- package/dist/layout/ModulePage.d.ts.map +1 -1
- package/dist/layout/SaveBar.d.ts.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/index.js +3 -3
- package/dist/primitives/header-tabs.d.ts +7 -1
- package/dist/primitives/header-tabs.d.ts.map +1 -1
- package/dist/primitives/index.js +3 -3
- package/dist/primitives/search-select.d.ts +3 -1
- package/dist/primitives/search-select.d.ts.map +1 -1
- package/dist/primitives/stock-entry-form.d.ts +4 -1
- package/dist/primitives/stock-entry-form.d.ts.map +1 -1
- package/dist/styles.css +75 -0
- package/dist/tour/PracticeOverlay.d.ts +12 -0
- package/dist/tour/PracticeOverlay.d.ts.map +1 -0
- package/dist/tour/TourSpotlight.d.ts.map +1 -1
- package/dist/tour/confetti.d.ts +17 -0
- package/dist/tour/confetti.d.ts.map +1 -0
- package/dist/tour/index.d.ts +3 -0
- package/dist/tour/index.d.ts.map +1 -1
- package/dist/tour/practice.store.d.ts +28 -0
- package/dist/tour/practice.store.d.ts.map +1 -0
- package/dist/tour/tour.store.d.ts +4 -0
- package/dist/tour/tour.store.d.ts.map +1 -1
- package/dist/tour/types.d.ts +17 -0
- package/dist/tour/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-AIUHVTVK.js.map +0 -1
- package/dist/chunk-EGHEU3U5.js.map +0 -1
- package/dist/chunk-MBGTFL4I.js.map +0 -1
- package/dist/chunk-OYH7CEMB.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Popover, PopoverTrigger, PopoverContent, Input, Hint, DatePicker } from './chunk-
|
|
2
|
-
export { Badge, Breadcrumb, BulkActionBar, ChevronPager, CurrencyInput, DatePicker, DotBadge, DraftCommitBar, FilterCheckBox, FilterCheckList, FilterCheckRow, FilterChip, FilterSection, Hint, Input, ListView, Markdown, MarkdownEditor, MiniCalendar, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, MultiSelect, PageActionBar, PhoneField, PhoneLink, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, RefreshButton, RowActions, SearchCombobox, SearchField, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, StatTiles, StockEntryForm, Tabs, TabsContent, TabsList, TabsTrigger, Thumbnail, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WHATSAPP_BRAND_COLOR, WhatsAppIcon, badgeVariants, formatPhone, normalizePhone, renderMarkdown, setRowActionsLabel, toLocalISODate, toast, useRowSelection } from './chunk-
|
|
3
|
-
export { AppShellSkeleton, ContentSplit, ContentSplitTrigger, PageTrail, Takeover, navigateToCrumb, usePageTrail, usePageTrailParent, useTakeoverClose } from './chunk-
|
|
4
|
-
export { AppShell, BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeader, PageHeaderActions, PageHeaderCrumb, PageRail, PageTabs, PageTransition, RailNavItem, SaveBar, SaveBarProvider, SegmentedControl, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SubpageHeader, Topbar, bottomNavReachesAll, useBackHandler, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar } from './chunk-
|
|
1
|
+
import { Popover, PopoverTrigger, PopoverContent, Input, Hint, DatePicker } from './chunk-ZSOT7KTE.js';
|
|
2
|
+
export { Badge, Breadcrumb, BulkActionBar, ChevronPager, CurrencyInput, DatePicker, DotBadge, DraftCommitBar, FilterCheckBox, FilterCheckList, FilterCheckRow, FilterChip, FilterSection, Hint, Input, ListView, Markdown, MarkdownEditor, MiniCalendar, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, MultiSelect, PageActionBar, PhoneField, PhoneLink, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, RefreshButton, RowActions, SearchCombobox, SearchField, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, StatTiles, StockEntryForm, Tabs, TabsContent, TabsList, TabsTrigger, Thumbnail, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WHATSAPP_BRAND_COLOR, WhatsAppIcon, badgeVariants, formatPhone, normalizePhone, renderMarkdown, setRowActionsLabel, toLocalISODate, toast, useRowSelection } from './chunk-ZSOT7KTE.js';
|
|
3
|
+
export { AppShellSkeleton, ContentSplit, ContentSplitTrigger, PageTrail, Takeover, navigateToCrumb, usePageTrail, usePageTrailParent, useTakeoverClose } from './chunk-7G3O54CC.js';
|
|
4
|
+
export { AppShell, BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeader, PageHeaderActions, PageHeaderCrumb, PageRail, PageTabs, PageTransition, RailNavItem, SaveBar, SaveBarProvider, SegmentedControl, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SubpageHeader, Topbar, bottomNavReachesAll, useBackHandler, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar } from './chunk-PQUS22JE.js';
|
|
5
5
|
export { ThemeProvider, createFayzTheme, createTheme, darkTheme, fayzThemePresets, lightTheme, useTheme, useThemeStore } from './chunk-VDAYIQX4.js';
|
|
6
|
-
import { ChartWidget, Sparkline, DashboardGrid } from './chunk-
|
|
7
|
-
export { ChartWidget, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, GoldCard, KpiCard, OnboardingWidget, SidebarOnboarding, TableWidget, WidgetGrid, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, formatKpiValue, renderIcon, resolveDashboardLayout, useDashboardNavigate, useDashboardPreferences, useDashboardRange, useOnboardingProgress } from './chunk-
|
|
6
|
+
import { ChartWidget, Sparkline, DashboardGrid } from './chunk-SUSW4LSW.js';
|
|
7
|
+
export { ChartWidget, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, GoldCard, KpiCard, OnboardingWidget, SidebarOnboarding, TableWidget, WidgetGrid, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, formatKpiValue, renderIcon, resolveDashboardLayout, useDashboardNavigate, useDashboardPreferences, useDashboardRange, useOnboardingProgress } from './chunk-SUSW4LSW.js';
|
|
8
8
|
import { Skeleton, ToolbarButton, PageToolbar, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Card, CardContent, RangeControl, DropdownSeparator } from './chunk-E44WOPCR.js';
|
|
9
9
|
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DataTable, Dropdown, DropdownCheckboxItem, DropdownContent, DropdownGroup, DropdownItem, DropdownLabel, DropdownPortal, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownShortcut, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, PageToolbar, RangeControl, Skeleton, TableSkeleton, ToolbarAction, ToolbarButton, WIDE_QUERY, useIsWide } from './chunk-E44WOPCR.js';
|
|
10
10
|
export { PageHeaderOutlet, PageTitlePortal, useHasPageActions, useHasPageHeaderBack, useHasPageHeaderHost, useHasPageHeaderOutlet, useLayoutStore, usePageHeader, usePageHeaderClaimed, usePageHeaderHost, useRegisterPageActions } from './chunk-HKGTF2YA.js';
|
|
@@ -13,7 +13,7 @@ export { Wizard, WizardField, errorMessage, useWizard } from './chunk-OB2ULDBD.j
|
|
|
13
13
|
export { ConfirmDialog, ImageDrop, OptionCards, OptionChips, StepProgress, Switch } from './chunk-GLRLWBBF.js';
|
|
14
14
|
import { cn, Button } from './chunk-OKZ6WQB3.js';
|
|
15
15
|
export { Button, buttonVariants, cn } from './chunk-OKZ6WQB3.js';
|
|
16
|
-
export { BRAND_MARKS, ChefControlMark, ICON_MAP, getLucideIcon, registerIcons } from './chunk-
|
|
16
|
+
export { BRAND_MARKS, ChefControlMark, ICON_MAP, getLucideIcon, registerIcons } from './chunk-DHNUZF2X.js';
|
|
17
17
|
import * as React2 from 'react';
|
|
18
18
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
19
19
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -1105,6 +1105,27 @@ function ActiveFilterChip({ label, value, filterKey }) {
|
|
|
1105
1105
|
)
|
|
1106
1106
|
] });
|
|
1107
1107
|
}
|
|
1108
|
+
var usePracticeStore = create((set, get) => ({
|
|
1109
|
+
title: null,
|
|
1110
|
+
steps: [],
|
|
1111
|
+
done: [],
|
|
1112
|
+
start: (title, steps) => {
|
|
1113
|
+
if (!steps.length) return;
|
|
1114
|
+
set({ title, steps, done: [] });
|
|
1115
|
+
},
|
|
1116
|
+
markDone: (anchor) => {
|
|
1117
|
+
const { steps, done } = get();
|
|
1118
|
+
if (!steps.some((s) => s.anchor === anchor)) return;
|
|
1119
|
+
if (done.includes(anchor)) return;
|
|
1120
|
+
set({ done: [...done, anchor] });
|
|
1121
|
+
},
|
|
1122
|
+
stop: () => set({ title: null, steps: [], done: [] })
|
|
1123
|
+
}));
|
|
1124
|
+
function nextPracticeStep(steps, done) {
|
|
1125
|
+
return steps.find((s) => !done.includes(s.anchor));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/tour/tour.store.ts
|
|
1108
1129
|
var navigator;
|
|
1109
1130
|
function setTourNavigator(fn) {
|
|
1110
1131
|
navigator = fn;
|
|
@@ -1116,14 +1137,19 @@ var useTourStore = create((set, get) => ({
|
|
|
1116
1137
|
tour: null,
|
|
1117
1138
|
index: 0,
|
|
1118
1139
|
lastOutcome: null,
|
|
1140
|
+
lastTour: null,
|
|
1119
1141
|
start: (tour) => {
|
|
1120
1142
|
if (!tour.stops.length) return;
|
|
1121
|
-
|
|
1143
|
+
usePracticeStore.getState().stop();
|
|
1144
|
+
set({ tour, index: 0, lastOutcome: null, lastTour: tour });
|
|
1122
1145
|
},
|
|
1123
1146
|
next: () => {
|
|
1124
1147
|
const { tour, index } = get();
|
|
1125
1148
|
if (!tour) return;
|
|
1126
1149
|
if (index >= tour.stops.length - 1) {
|
|
1150
|
+
if (tour.practice?.length) {
|
|
1151
|
+
usePracticeStore.getState().start(tour.title ?? "", tour.practice);
|
|
1152
|
+
}
|
|
1127
1153
|
get().stop(true);
|
|
1128
1154
|
return;
|
|
1129
1155
|
}
|
|
@@ -1135,11 +1161,105 @@ var useTourStore = create((set, get) => ({
|
|
|
1135
1161
|
tour: null,
|
|
1136
1162
|
index: 0,
|
|
1137
1163
|
lastOutcome: s.tour ? { tourId: s.tour.id, completed } : s.lastOutcome
|
|
1138
|
-
}))
|
|
1164
|
+
})),
|
|
1165
|
+
restart: () => {
|
|
1166
|
+
const t = get().lastTour;
|
|
1167
|
+
if (t) get().start(t);
|
|
1168
|
+
}
|
|
1139
1169
|
}));
|
|
1140
1170
|
function currentStop(state) {
|
|
1141
1171
|
return state.tour?.stops[state.index];
|
|
1142
1172
|
}
|
|
1173
|
+
|
|
1174
|
+
// src/tour/confetti.ts
|
|
1175
|
+
function fireConfetti(opts) {
|
|
1176
|
+
if (typeof document === "undefined") return;
|
|
1177
|
+
if (typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
1178
|
+
const canvas = document.createElement("canvas");
|
|
1179
|
+
const dpr = window.devicePixelRatio || 1;
|
|
1180
|
+
const w = window.innerWidth;
|
|
1181
|
+
const h = window.innerHeight;
|
|
1182
|
+
canvas.width = w * dpr;
|
|
1183
|
+
canvas.height = h * dpr;
|
|
1184
|
+
canvas.style.cssText = `position:fixed;inset:0;width:${w}px;height:${h}px;pointer-events:none;z-index:200`;
|
|
1185
|
+
document.body.appendChild(canvas);
|
|
1186
|
+
const ctx = canvas.getContext("2d");
|
|
1187
|
+
if (!ctx) {
|
|
1188
|
+
canvas.remove();
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
ctx.scale(dpr, dpr);
|
|
1192
|
+
const css = getComputedStyle(document.documentElement);
|
|
1193
|
+
const prim = (css.getPropertyValue("--primary") || "35 92% 50%").trim();
|
|
1194
|
+
const colors = [`hsl(${prim})`, "#22c55e", "#f59e0b", "#ec4899", "#3b82f6", "#ffffff"];
|
|
1195
|
+
const n = opts?.count ?? 130;
|
|
1196
|
+
const rnd = (a, b) => a + Math.random() * (b - a);
|
|
1197
|
+
const fromBottom = !!opts?.fromBottom;
|
|
1198
|
+
const ox = opts?.origin?.x ?? w / 2;
|
|
1199
|
+
const oy = opts?.origin?.y ?? h * 0.32;
|
|
1200
|
+
const parts = Array.from({ length: fromBottom ? Math.max(n, 180) : n }, () => {
|
|
1201
|
+
if (fromBottom) {
|
|
1202
|
+
return {
|
|
1203
|
+
x: rnd(0, w),
|
|
1204
|
+
y: h + rnd(0, 40),
|
|
1205
|
+
vx: rnd(-3, 3),
|
|
1206
|
+
vy: -rnd(13, 22),
|
|
1207
|
+
// straight up, hard
|
|
1208
|
+
rot: rnd(0, Math.PI * 2),
|
|
1209
|
+
vr: rnd(-0.3, 0.3),
|
|
1210
|
+
size: rnd(6, 12),
|
|
1211
|
+
color: colors[Math.random() * colors.length | 0],
|
|
1212
|
+
life: 0
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
const angle = rnd(-Math.PI, 0);
|
|
1216
|
+
const speed = rnd(6, 15);
|
|
1217
|
+
return {
|
|
1218
|
+
x: ox,
|
|
1219
|
+
y: oy,
|
|
1220
|
+
vx: Math.cos(angle) * speed * rnd(0.6, 1.4),
|
|
1221
|
+
vy: Math.sin(angle) * speed - rnd(2, 6),
|
|
1222
|
+
rot: rnd(0, Math.PI * 2),
|
|
1223
|
+
vr: rnd(-0.3, 0.3),
|
|
1224
|
+
size: rnd(6, 11),
|
|
1225
|
+
color: colors[Math.random() * colors.length | 0],
|
|
1226
|
+
life: 0
|
|
1227
|
+
};
|
|
1228
|
+
});
|
|
1229
|
+
const GRAVITY = 0.32;
|
|
1230
|
+
const DRAG = 0.99;
|
|
1231
|
+
const MAX_LIFE = 140;
|
|
1232
|
+
let raf = 0;
|
|
1233
|
+
const tick = () => {
|
|
1234
|
+
ctx.clearRect(0, 0, w, h);
|
|
1235
|
+
let alive = 0;
|
|
1236
|
+
for (const p of parts) {
|
|
1237
|
+
if (p.life > MAX_LIFE) continue;
|
|
1238
|
+
alive++;
|
|
1239
|
+
p.life++;
|
|
1240
|
+
p.vx *= DRAG;
|
|
1241
|
+
p.vy = p.vy * DRAG + GRAVITY;
|
|
1242
|
+
p.x += p.vx;
|
|
1243
|
+
p.y += p.vy;
|
|
1244
|
+
p.rot += p.vr;
|
|
1245
|
+
const fade = Math.max(0, 1 - p.life / MAX_LIFE);
|
|
1246
|
+
ctx.save();
|
|
1247
|
+
ctx.translate(p.x, p.y);
|
|
1248
|
+
ctx.rotate(p.rot);
|
|
1249
|
+
ctx.globalAlpha = fade;
|
|
1250
|
+
ctx.fillStyle = p.color;
|
|
1251
|
+
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
|
|
1252
|
+
ctx.restore();
|
|
1253
|
+
}
|
|
1254
|
+
if (alive > 0) {
|
|
1255
|
+
raf = requestAnimationFrame(tick);
|
|
1256
|
+
} else {
|
|
1257
|
+
cancelAnimationFrame(raf);
|
|
1258
|
+
canvas.remove();
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
raf = requestAnimationFrame(tick);
|
|
1262
|
+
}
|
|
1143
1263
|
var DEFAULT_LABELS = {
|
|
1144
1264
|
next: "Pr\xF3ximo",
|
|
1145
1265
|
waiting: "Fa\xE7a agora \u2014 eu espero",
|
|
@@ -1157,6 +1277,27 @@ function readRect(el) {
|
|
|
1157
1277
|
const r = el.getBoundingClientRect();
|
|
1158
1278
|
return { top: r.top - PAD, left: r.left - PAD, width: r.width + PAD * 2, height: r.height + PAD * 2 };
|
|
1159
1279
|
}
|
|
1280
|
+
var CURSOR_TRAVEL_MS = 650;
|
|
1281
|
+
function pointFor(rect) {
|
|
1282
|
+
return { x: rect.left + rect.width * 0.62, y: rect.top + rect.height * 0.62 };
|
|
1283
|
+
}
|
|
1284
|
+
function prefersReducedMotion() {
|
|
1285
|
+
if (typeof window === "undefined" || !window.matchMedia) return false;
|
|
1286
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1287
|
+
}
|
|
1288
|
+
function exemptRightInset() {
|
|
1289
|
+
if (typeof document === "undefined") return 0;
|
|
1290
|
+
let inset = 0;
|
|
1291
|
+
for (const el of Array.from(document.querySelectorAll("[data-tour-exempt]"))) {
|
|
1292
|
+
const r = el.getBoundingClientRect();
|
|
1293
|
+
if (r.width === 0 || r.height === 0) continue;
|
|
1294
|
+
if (r.right < window.innerWidth - 8) continue;
|
|
1295
|
+
if (r.width > window.innerWidth * 0.7) continue;
|
|
1296
|
+
if (r.height < window.innerHeight * 0.5) continue;
|
|
1297
|
+
inset = Math.max(inset, window.innerWidth - r.left);
|
|
1298
|
+
}
|
|
1299
|
+
return Math.round(inset);
|
|
1300
|
+
}
|
|
1160
1301
|
function TourSpotlight({ labels, className }) {
|
|
1161
1302
|
const l = { ...DEFAULT_LABELS, ...labels };
|
|
1162
1303
|
const tour = useTourStore((s) => s.tour);
|
|
@@ -1164,21 +1305,65 @@ function TourSpotlight({ labels, className }) {
|
|
|
1164
1305
|
const next = useTourStore((s) => s.next);
|
|
1165
1306
|
const prev = useTourStore((s) => s.prev);
|
|
1166
1307
|
const stop = useTourStore((s) => s.stop);
|
|
1308
|
+
const lastOutcome = useTourStore((s) => s.lastOutcome);
|
|
1309
|
+
const firedFor = React2.useRef(null);
|
|
1310
|
+
React2.useEffect(() => {
|
|
1311
|
+
if (!lastOutcome?.completed) return;
|
|
1312
|
+
if (firedFor.current === lastOutcome.tourId) return;
|
|
1313
|
+
firedFor.current = lastOutcome.tourId;
|
|
1314
|
+
if (usePracticeStore.getState().steps.length > 0) return;
|
|
1315
|
+
const origin = rect ? { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 } : void 0;
|
|
1316
|
+
fireConfetti({ origin });
|
|
1317
|
+
}, [lastOutcome]);
|
|
1167
1318
|
const stopDef = tour?.stops[index];
|
|
1168
1319
|
const [rect, setRect] = React2.useState(null);
|
|
1169
1320
|
const [missing, setMissing] = React2.useState(false);
|
|
1321
|
+
const [cursor, setCursor] = React2.useState(null);
|
|
1322
|
+
const [arrived, setArrived] = React2.useState(false);
|
|
1323
|
+
const [engaged, setEngaged] = React2.useState(false);
|
|
1324
|
+
const [rightInset, setRightInset] = React2.useState(0);
|
|
1325
|
+
React2.useEffect(() => {
|
|
1326
|
+
if (!tour) return;
|
|
1327
|
+
const sync = () => setRightInset(exemptRightInset());
|
|
1328
|
+
sync();
|
|
1329
|
+
const interval = window.setInterval(sync, 500);
|
|
1330
|
+
window.addEventListener("resize", sync);
|
|
1331
|
+
return () => {
|
|
1332
|
+
window.clearInterval(interval);
|
|
1333
|
+
window.removeEventListener("resize", sync);
|
|
1334
|
+
};
|
|
1335
|
+
}, [tour?.id]);
|
|
1170
1336
|
React2.useEffect(() => {
|
|
1171
1337
|
if (!stopDef) return;
|
|
1172
1338
|
setRect(null);
|
|
1173
1339
|
setMissing(false);
|
|
1340
|
+
setArrived(false);
|
|
1341
|
+
setEngaged(false);
|
|
1174
1342
|
if (stopDef.route) getTourNavigator()?.(stopDef.route);
|
|
1175
1343
|
let raf = 0;
|
|
1344
|
+
let travel = 0;
|
|
1176
1345
|
const deadline = Date.now() + ANCHOR_TIMEOUT_MS;
|
|
1177
1346
|
const hunt = () => {
|
|
1178
1347
|
const el = document.querySelector(`[data-tour~="${CSS.escape(stopDef.anchor)}"]`);
|
|
1179
1348
|
if (el) {
|
|
1180
1349
|
el.scrollIntoView({ block: "center", inline: "nearest", behavior: "smooth" });
|
|
1181
|
-
|
|
1350
|
+
const found = readRect(el);
|
|
1351
|
+
const land = () => {
|
|
1352
|
+
const at = document.querySelector(`[data-tour~="${CSS.escape(stopDef.anchor)}"]`);
|
|
1353
|
+
const settled = at ? readRect(at) : found;
|
|
1354
|
+
setRect(settled);
|
|
1355
|
+
setCursor(pointFor(settled));
|
|
1356
|
+
setArrived(true);
|
|
1357
|
+
};
|
|
1358
|
+
if (prefersReducedMotion()) {
|
|
1359
|
+
land();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
setCursor((prev2) => prev2 ?? { x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
|
1363
|
+
raf = requestAnimationFrame(() => {
|
|
1364
|
+
raf = requestAnimationFrame(() => setCursor(pointFor(found)));
|
|
1365
|
+
});
|
|
1366
|
+
travel = window.setTimeout(land, CURSOR_TRAVEL_MS + 40);
|
|
1182
1367
|
return;
|
|
1183
1368
|
}
|
|
1184
1369
|
if (Date.now() > deadline) {
|
|
@@ -1188,10 +1373,13 @@ function TourSpotlight({ labels, className }) {
|
|
|
1188
1373
|
raf = requestAnimationFrame(hunt);
|
|
1189
1374
|
};
|
|
1190
1375
|
raf = requestAnimationFrame(hunt);
|
|
1191
|
-
return () =>
|
|
1376
|
+
return () => {
|
|
1377
|
+
cancelAnimationFrame(raf);
|
|
1378
|
+
window.clearTimeout(travel);
|
|
1379
|
+
};
|
|
1192
1380
|
}, [stopDef?.anchor, stopDef?.route, tour?.id]);
|
|
1193
1381
|
React2.useEffect(() => {
|
|
1194
|
-
if (!stopDef || missing) return;
|
|
1382
|
+
if (!stopDef || missing || !arrived) return;
|
|
1195
1383
|
const anchor = stopDef.anchor;
|
|
1196
1384
|
const handover = Boolean(stopDef.waitForUser);
|
|
1197
1385
|
let frame = 0;
|
|
@@ -1200,7 +1388,9 @@ function TourSpotlight({ labels, className }) {
|
|
|
1200
1388
|
const el = document.querySelector(`[data-tour~="${CSS.escape(anchor)}"]`);
|
|
1201
1389
|
if (el) {
|
|
1202
1390
|
absent = 0;
|
|
1203
|
-
|
|
1391
|
+
const r = readRect(el);
|
|
1392
|
+
setRect(r);
|
|
1393
|
+
setCursor(pointFor(r));
|
|
1204
1394
|
return;
|
|
1205
1395
|
}
|
|
1206
1396
|
if (!handover) return;
|
|
@@ -1220,7 +1410,49 @@ function TourSpotlight({ labels, className }) {
|
|
|
1220
1410
|
window.removeEventListener("scroll", onFrame, true);
|
|
1221
1411
|
window.removeEventListener("resize", onFrame);
|
|
1222
1412
|
};
|
|
1223
|
-
}, [stopDef?.anchor, stopDef?.waitForUser, missing, stop]);
|
|
1413
|
+
}, [stopDef?.anchor, stopDef?.waitForUser, missing, arrived, stop]);
|
|
1414
|
+
React2.useEffect(() => {
|
|
1415
|
+
if (tour) return;
|
|
1416
|
+
setCursor(null);
|
|
1417
|
+
setArrived(false);
|
|
1418
|
+
setRect(null);
|
|
1419
|
+
setMissing(false);
|
|
1420
|
+
}, [tour]);
|
|
1421
|
+
React2.useEffect(() => {
|
|
1422
|
+
if (!stopDef || missing) return;
|
|
1423
|
+
const anchor = stopDef.anchor;
|
|
1424
|
+
const within = (node) => {
|
|
1425
|
+
const el = document.querySelector(`[data-tour~="${CSS.escape(anchor)}"]`);
|
|
1426
|
+
return !!el && node instanceof Node && el.contains(node);
|
|
1427
|
+
};
|
|
1428
|
+
const editable = (node) => node instanceof Element && !!node.closest('input, textarea, select, [contenteditable="true"]');
|
|
1429
|
+
const onDown = (e) => {
|
|
1430
|
+
if (!within(e.target)) return;
|
|
1431
|
+
if (editable(e.target)) setEngaged(true);
|
|
1432
|
+
};
|
|
1433
|
+
const onFocus = (e) => {
|
|
1434
|
+
if (within(e.target) && editable(e.target)) setEngaged(true);
|
|
1435
|
+
};
|
|
1436
|
+
const onClick = (e) => {
|
|
1437
|
+
if (!within(e.target) || editable(e.target)) return;
|
|
1438
|
+
useTourStore.getState().next();
|
|
1439
|
+
};
|
|
1440
|
+
const onOut = () => {
|
|
1441
|
+
window.setTimeout(() => {
|
|
1442
|
+
if (!within(document.activeElement)) setEngaged(false);
|
|
1443
|
+
}, 0);
|
|
1444
|
+
};
|
|
1445
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1446
|
+
document.addEventListener("focusin", onFocus, true);
|
|
1447
|
+
document.addEventListener("click", onClick, true);
|
|
1448
|
+
document.addEventListener("focusout", onOut, true);
|
|
1449
|
+
return () => {
|
|
1450
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1451
|
+
document.removeEventListener("focusin", onFocus, true);
|
|
1452
|
+
document.removeEventListener("click", onClick, true);
|
|
1453
|
+
document.removeEventListener("focusout", onOut, true);
|
|
1454
|
+
};
|
|
1455
|
+
}, [stopDef?.anchor, missing]);
|
|
1224
1456
|
React2.useEffect(() => {
|
|
1225
1457
|
if (!tour) return;
|
|
1226
1458
|
const onKey = (e) => {
|
|
@@ -1234,99 +1466,125 @@ function TourSpotlight({ labels, className }) {
|
|
|
1234
1466
|
if (!tour || !stopDef || typeof document === "undefined") return null;
|
|
1235
1467
|
const total = tour.stops.length;
|
|
1236
1468
|
const isLast = index === total - 1;
|
|
1237
|
-
const bubble = placeBubble(rect, stopDef.placement);
|
|
1238
|
-
const body =
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
/* @__PURE__ */ jsx(Dim, { style: { top: rect.top, left: 0, width: Math.max(0, rect.left), height: rect.height }, onClick: () => stop(false) }),
|
|
1242
|
-
/* @__PURE__ */ jsx(
|
|
1243
|
-
Dim,
|
|
1244
|
-
{
|
|
1245
|
-
style: { top: rect.top, left: rect.left + rect.width, right: 0, height: rect.height },
|
|
1246
|
-
onClick: () => stop(false)
|
|
1247
|
-
}
|
|
1248
|
-
),
|
|
1249
|
-
/* @__PURE__ */ jsx(Dim, { style: { top: rect.top + rect.height, left: 0, width: "100%", bottom: 0 }, onClick: () => stop(false) }),
|
|
1250
|
-
/* @__PURE__ */ jsx(
|
|
1251
|
-
"div",
|
|
1252
|
-
{
|
|
1253
|
-
"aria-hidden": true,
|
|
1254
|
-
className: "fz-spotlight-ring pointer-events-none absolute motion-safe:transition-[top,left,width,height] motion-safe:duration-300",
|
|
1255
|
-
style: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
|
|
1256
|
-
}
|
|
1257
|
-
)
|
|
1258
|
-
] }) : /* @__PURE__ */ jsx(Dim, { style: { inset: 0 }, onClick: () => stop(false) }),
|
|
1469
|
+
const bubble = placeBubble(rect, stopDef.placement, rightInset);
|
|
1470
|
+
const body = (
|
|
1471
|
+
// `right` leaves the exempt column (the assistant narrating this tour)
|
|
1472
|
+
// outside the overlay entirely — bright, readable, clickable.
|
|
1259
1473
|
/* @__PURE__ */ jsxs(
|
|
1260
1474
|
"div",
|
|
1261
1475
|
{
|
|
1262
|
-
className: cn(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
style: bubble,
|
|
1476
|
+
className: cn("pointer-events-none fixed inset-y-0 left-0 z-[120]", className),
|
|
1477
|
+
style: { right: rightInset },
|
|
1478
|
+
role: "dialog",
|
|
1479
|
+
"aria-modal": "false",
|
|
1267
1480
|
children: [
|
|
1268
|
-
/* @__PURE__ */
|
|
1269
|
-
"
|
|
1270
|
-
{
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
1276
|
-
}
|
|
1277
|
-
),
|
|
1278
|
-
/* @__PURE__ */ jsx("p", { className: "pr-6 text-sm font-semibold leading-snug", children: stopDef.title }),
|
|
1279
|
-
(missing ? l.notFound : stopDef.body) && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[13px] leading-relaxed text-muted-foreground", children: missing ? l.notFound : stopDef.body }),
|
|
1280
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-2", children: [
|
|
1281
|
-
/* @__PURE__ */ jsx("span", { className: "text-[11px] tabular-nums text-muted-foreground", children: l.stepOf.replace("{current}", String(index + 1)).replace("{total}", String(total)) }),
|
|
1282
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1283
|
-
stopDef.waitForUser && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-[12px] font-medium text-primary", children: [
|
|
1284
|
-
/* @__PURE__ */ jsxs("span", { className: "relative flex h-1.5 w-1.5", children: [
|
|
1285
|
-
/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary/70" }),
|
|
1286
|
-
/* @__PURE__ */ jsx("span", { className: "relative inline-flex h-1.5 w-1.5 rounded-full bg-primary" })
|
|
1287
|
-
] }),
|
|
1288
|
-
l.waiting
|
|
1289
|
-
] }),
|
|
1290
|
-
index > 0 && !stopDef.waitForUser && /* @__PURE__ */ jsx(
|
|
1291
|
-
"button",
|
|
1292
|
-
{
|
|
1293
|
-
type: "button",
|
|
1294
|
-
onClick: prev,
|
|
1295
|
-
className: "rounded-button px-2.5 py-1.5 text-[12.5px] font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1296
|
-
children: l.back
|
|
1297
|
-
}
|
|
1298
|
-
),
|
|
1299
|
-
!stopDef.waitForUser && /* @__PURE__ */ jsx(
|
|
1300
|
-
"button",
|
|
1481
|
+
rect && !missing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1482
|
+
/* @__PURE__ */ jsx(Dim, { soft: engaged, style: { top: 0, left: 0, width: "100%", height: Math.max(0, rect.top) } }),
|
|
1483
|
+
/* @__PURE__ */ jsx(Dim, { soft: engaged, style: { top: rect.top, left: 0, width: Math.max(0, rect.left), height: rect.height } }),
|
|
1484
|
+
/* @__PURE__ */ jsx(Dim, { soft: engaged, style: { top: rect.top, left: rect.left + rect.width, right: 0, height: rect.height } }),
|
|
1485
|
+
/* @__PURE__ */ jsx(Dim, { soft: engaged, style: { top: rect.top + rect.height, left: 0, width: "100%", bottom: 0 } }),
|
|
1486
|
+
/* @__PURE__ */ jsx(
|
|
1487
|
+
"div",
|
|
1301
1488
|
{
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
children: isLast ? l.done : l.next
|
|
1489
|
+
"aria-hidden": true,
|
|
1490
|
+
className: "fz-spotlight-ring pointer-events-none absolute motion-safe:transition-[top,left,width,height] motion-safe:duration-300",
|
|
1491
|
+
style: { top: rect.top, left: rect.left, width: rect.width, height: rect.height }
|
|
1306
1492
|
}
|
|
1307
1493
|
)
|
|
1308
|
-
] })
|
|
1494
|
+
] }) : /* @__PURE__ */ jsx(Dim, { style: { inset: 0 } }),
|
|
1495
|
+
cursor && !missing && !engaged && /* @__PURE__ */ jsx(
|
|
1496
|
+
"div",
|
|
1497
|
+
{
|
|
1498
|
+
"aria-hidden": true,
|
|
1499
|
+
className: cn(
|
|
1500
|
+
"fz-tour-cursor",
|
|
1501
|
+
!arrived && "fz-tour-cursor-travel",
|
|
1502
|
+
arrived && "fz-tour-cursor-arrived",
|
|
1503
|
+
arrived && stopDef.waitForUser && "fz-tour-cursor-nudge"
|
|
1504
|
+
),
|
|
1505
|
+
style: { transform: `translate3d(${cursor.x}px, ${cursor.y}px, 0)` },
|
|
1506
|
+
children: /* @__PURE__ */ jsx("svg", { width: "34", height: "34", viewBox: "0 0 24 24", fill: "hsl(var(--primary))", stroke: "white", strokeWidth: "1.4", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M5.5 3.2v17.6c0 .45.54.67.85.35l4.4-4.4a.5.5 0 0 1 .36-.15h6.24c.45 0 .67-.54.35-.85L6.35 2.85a.5.5 0 0 0-.85.35Z" }) })
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
(arrived || missing) && !engaged && /* @__PURE__ */ jsxs(
|
|
1510
|
+
"div",
|
|
1511
|
+
{
|
|
1512
|
+
className: cn(
|
|
1513
|
+
"pointer-events-auto absolute w-[300px] max-w-[calc(100vw-24px)] rounded-xl border border-border bg-popover p-4 text-popover-foreground shadow-2xl",
|
|
1514
|
+
"motion-safe:animate-in motion-safe:fade-in motion-safe:zoom-in-95 motion-safe:duration-200"
|
|
1515
|
+
),
|
|
1516
|
+
style: bubble,
|
|
1517
|
+
children: [
|
|
1518
|
+
/* @__PURE__ */ jsx(
|
|
1519
|
+
"button",
|
|
1520
|
+
{
|
|
1521
|
+
type: "button",
|
|
1522
|
+
onClick: () => stop(false),
|
|
1523
|
+
"aria-label": l.skip,
|
|
1524
|
+
className: "absolute right-2 top-2 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1525
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
1526
|
+
}
|
|
1527
|
+
),
|
|
1528
|
+
/* @__PURE__ */ jsx("p", { className: "pr-6 text-sm font-semibold leading-snug", children: stopDef.title }),
|
|
1529
|
+
(missing ? l.notFound : stopDef.body) && /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-[13px] leading-relaxed text-muted-foreground", children: missing ? l.notFound : stopDef.body }),
|
|
1530
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center gap-2", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] tabular-nums text-muted-foreground", children: l.stepOf.replace("{current}", String(index + 1)).replace("{total}", String(total)) }),
|
|
1532
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1533
|
+
stopDef.waitForUser && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-[12px] font-medium text-primary", children: [
|
|
1534
|
+
/* @__PURE__ */ jsxs("span", { className: "relative flex h-1.5 w-1.5", children: [
|
|
1535
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary/70" }),
|
|
1536
|
+
/* @__PURE__ */ jsx("span", { className: "relative inline-flex h-1.5 w-1.5 rounded-full bg-primary" })
|
|
1537
|
+
] }),
|
|
1538
|
+
l.waiting
|
|
1539
|
+
] }),
|
|
1540
|
+
index > 0 && !stopDef.waitForUser && /* @__PURE__ */ jsx(
|
|
1541
|
+
"button",
|
|
1542
|
+
{
|
|
1543
|
+
type: "button",
|
|
1544
|
+
onClick: prev,
|
|
1545
|
+
className: "rounded-button px-2.5 py-1.5 text-[12.5px] font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1546
|
+
children: l.back
|
|
1547
|
+
}
|
|
1548
|
+
),
|
|
1549
|
+
!stopDef.waitForUser && /* @__PURE__ */ jsx(
|
|
1550
|
+
"button",
|
|
1551
|
+
{
|
|
1552
|
+
type: "button",
|
|
1553
|
+
onClick: next,
|
|
1554
|
+
className: "rounded-button bg-primary px-3 py-1.5 text-[12.5px] font-semibold text-primary-foreground shadow-button-primary transition-colors hover:bg-primary/90",
|
|
1555
|
+
children: isLast ? l.done : l.next
|
|
1556
|
+
}
|
|
1557
|
+
)
|
|
1558
|
+
] })
|
|
1559
|
+
]
|
|
1560
|
+
}
|
|
1561
|
+
)
|
|
1309
1562
|
]
|
|
1310
1563
|
}
|
|
1311
1564
|
)
|
|
1312
|
-
|
|
1565
|
+
);
|
|
1313
1566
|
return createPortal(body, document.body);
|
|
1314
1567
|
}
|
|
1315
|
-
function Dim({ style,
|
|
1568
|
+
function Dim({ style, soft }) {
|
|
1316
1569
|
return /* @__PURE__ */ jsx(
|
|
1317
1570
|
"div",
|
|
1318
1571
|
{
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1572
|
+
className: cn(
|
|
1573
|
+
"absolute motion-safe:transition-all motion-safe:duration-300",
|
|
1574
|
+
// Hard dim still swallows the click (so a stray click cannot press a
|
|
1575
|
+
// covered button) — it just no longer STOPS the tour. Soft is fully
|
|
1576
|
+
// click-through: the person is working inside the hole's overflow.
|
|
1577
|
+
soft ? "pointer-events-none" : "pointer-events-auto"
|
|
1578
|
+
),
|
|
1579
|
+
style: { backgroundColor: soft ? "rgb(0 0 0 / 0.12)" : "rgb(0 0 0 / 0.55)", ...style }
|
|
1322
1580
|
}
|
|
1323
1581
|
);
|
|
1324
1582
|
}
|
|
1325
|
-
function placeBubble(rect, placement) {
|
|
1583
|
+
function placeBubble(rect, placement, rightInset = 0) {
|
|
1326
1584
|
if (typeof window === "undefined" || !rect) {
|
|
1327
1585
|
return { top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
|
|
1328
1586
|
}
|
|
1329
|
-
const vw = window.innerWidth;
|
|
1587
|
+
const vw = window.innerWidth - rightInset;
|
|
1330
1588
|
const vh = window.innerHeight;
|
|
1331
1589
|
const belowRoom = vh - (rect.top + rect.height);
|
|
1332
1590
|
const rightRoom = vw - (rect.left + rect.width);
|
|
@@ -1351,7 +1609,107 @@ function placeBubble(rect, placement) {
|
|
|
1351
1609
|
function tourAnchor(anchor) {
|
|
1352
1610
|
return { "data-tour": anchor };
|
|
1353
1611
|
}
|
|
1612
|
+
function PracticeOverlay() {
|
|
1613
|
+
const title = usePracticeStore((s) => s.title);
|
|
1614
|
+
const steps = usePracticeStore((s) => s.steps);
|
|
1615
|
+
const done = usePracticeStore((s) => s.done);
|
|
1616
|
+
const markDone = usePracticeStore((s) => s.markDone);
|
|
1617
|
+
const stop = usePracticeStore((s) => s.stop);
|
|
1618
|
+
const active = !!title && steps.length > 0;
|
|
1619
|
+
const next = active ? nextPracticeStep(steps, done) : void 0;
|
|
1620
|
+
const [rects, setRects] = React2.useState({});
|
|
1621
|
+
React2.useEffect(() => {
|
|
1622
|
+
if (!active) return;
|
|
1623
|
+
const measure = () => {
|
|
1624
|
+
const out = {};
|
|
1625
|
+
for (const s of steps) {
|
|
1626
|
+
const el = document.querySelector(`[data-tour~="${CSS.escape(s.anchor)}"]`);
|
|
1627
|
+
if (el) {
|
|
1628
|
+
const r = el.getBoundingClientRect();
|
|
1629
|
+
out[s.anchor] = { top: r.top, left: r.left };
|
|
1630
|
+
} else {
|
|
1631
|
+
out[s.anchor] = null;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
setRects(out);
|
|
1635
|
+
};
|
|
1636
|
+
measure();
|
|
1637
|
+
const interval = window.setInterval(measure, 300);
|
|
1638
|
+
window.addEventListener("scroll", measure, true);
|
|
1639
|
+
window.addEventListener("resize", measure);
|
|
1640
|
+
return () => {
|
|
1641
|
+
window.clearInterval(interval);
|
|
1642
|
+
window.removeEventListener("scroll", measure, true);
|
|
1643
|
+
window.removeEventListener("resize", measure);
|
|
1644
|
+
};
|
|
1645
|
+
}, [active, steps]);
|
|
1646
|
+
React2.useEffect(() => {
|
|
1647
|
+
if (!active) return;
|
|
1648
|
+
const within = (anchor, node) => {
|
|
1649
|
+
const el = document.querySelector(`[data-tour~="${CSS.escape(anchor)}"]`);
|
|
1650
|
+
return !!el && node instanceof Node && el.contains(node);
|
|
1651
|
+
};
|
|
1652
|
+
const hit = (e) => {
|
|
1653
|
+
for (const s of steps) {
|
|
1654
|
+
if (done.includes(s.anchor)) continue;
|
|
1655
|
+
if (within(s.anchor, e.target)) {
|
|
1656
|
+
markDone(s.anchor);
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
};
|
|
1661
|
+
document.addEventListener("pointerdown", hit, true);
|
|
1662
|
+
document.addEventListener("input", hit, true);
|
|
1663
|
+
document.addEventListener("change", hit, true);
|
|
1664
|
+
return () => {
|
|
1665
|
+
document.removeEventListener("pointerdown", hit, true);
|
|
1666
|
+
document.removeEventListener("input", hit, true);
|
|
1667
|
+
document.removeEventListener("change", hit, true);
|
|
1668
|
+
};
|
|
1669
|
+
}, [active, steps, done, markDone]);
|
|
1670
|
+
const finaleFired = React2.useRef(false);
|
|
1671
|
+
React2.useEffect(() => {
|
|
1672
|
+
if (!active) {
|
|
1673
|
+
finaleFired.current = false;
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
const finalStep = steps.find((s) => s.final) ?? steps[steps.length - 1];
|
|
1677
|
+
if (finalStep && done.includes(finalStep.anchor) && !finaleFired.current) {
|
|
1678
|
+
finaleFired.current = true;
|
|
1679
|
+
fireConfetti({ fromBottom: true });
|
|
1680
|
+
window.setTimeout(() => stop(), 1400);
|
|
1681
|
+
}
|
|
1682
|
+
}, [active, done, steps, stop]);
|
|
1683
|
+
if (!active || typeof document === "undefined") return null;
|
|
1684
|
+
const body = /* @__PURE__ */ jsx("div", { className: "pointer-events-none fixed inset-0 z-[118]", children: steps.map((s, i) => {
|
|
1685
|
+
const rect = rects[s.anchor];
|
|
1686
|
+
if (!rect) return null;
|
|
1687
|
+
const isDone = done.includes(s.anchor);
|
|
1688
|
+
const isNext = next?.anchor === s.anchor;
|
|
1689
|
+
return /* @__PURE__ */ jsx(Footprint, { n: i + 1, label: s.label, top: rect.top, left: rect.left, done: isDone, next: isNext }, s.anchor);
|
|
1690
|
+
}) });
|
|
1691
|
+
return createPortal(body, document.body);
|
|
1692
|
+
}
|
|
1693
|
+
function Footprint({ n, label, top, left, done, next }) {
|
|
1694
|
+
return /* @__PURE__ */ jsxs(
|
|
1695
|
+
"div",
|
|
1696
|
+
{
|
|
1697
|
+
className: "absolute flex items-center gap-1.5 transition-all duration-300",
|
|
1698
|
+
style: { top: Math.max(4, top - 12), left: Math.max(4, left - 12) },
|
|
1699
|
+
children: [
|
|
1700
|
+
/* @__PURE__ */ jsx(
|
|
1701
|
+
"span",
|
|
1702
|
+
{
|
|
1703
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[12px] font-bold shadow-lg ring-2 ring-background " + (done ? "bg-primary text-primary-foreground" : next ? "fz-practice-pulse bg-primary text-primary-foreground" : "bg-card text-foreground"),
|
|
1704
|
+
children: done ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : n
|
|
1705
|
+
}
|
|
1706
|
+
),
|
|
1707
|
+
next && !done && /* @__PURE__ */ jsx("span", { className: "rounded-md bg-primary px-2 py-1 text-[11.5px] font-medium text-primary-foreground shadow-lg", children: label })
|
|
1708
|
+
]
|
|
1709
|
+
}
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1354
1712
|
|
|
1355
|
-
export { AnalyticsCard, AnalyticsContextProvider, AnalyticsControls, AnalyticsFilterPicker, AnalyticsGrid, Avatar, AvatarFallback, AvatarImage, BATCH_WINDOW_MS, TourSpotlight, autoGrain, collectFilters, comparisonRange, createBatchingProvider, currentStop, deltaPercent, formatAnalyticsValue, formatBucket, formatCompact, formatDuration, getTourNavigator, isEngineMissing, num, parseDay, rangeLengthDays, resolveRangePreset, resolveRatios, resolveRowRatios, setTourNavigator, toBreakdown, toChartData, toDayString, toSpark, tourAnchor, trendOf, useAnalyticsCard, useAnalyticsCardRows, useAnalyticsContext, useAnalyticsContextOptional, useTourStore, vizCategory, vizMeasures };
|
|
1713
|
+
export { AnalyticsCard, AnalyticsContextProvider, AnalyticsControls, AnalyticsFilterPicker, AnalyticsGrid, Avatar, AvatarFallback, AvatarImage, BATCH_WINDOW_MS, PracticeOverlay, TourSpotlight, autoGrain, collectFilters, comparisonRange, createBatchingProvider, currentStop, deltaPercent, fireConfetti, formatAnalyticsValue, formatBucket, formatCompact, formatDuration, getTourNavigator, isEngineMissing, nextPracticeStep, num, parseDay, rangeLengthDays, resolveRangePreset, resolveRatios, resolveRowRatios, setTourNavigator, toBreakdown, toChartData, toDayString, toSpark, tourAnchor, trendOf, useAnalyticsCard, useAnalyticsCardRows, useAnalyticsContext, useAnalyticsContextOptional, usePracticeStore, useTourStore, vizCategory, vizMeasures };
|
|
1356
1714
|
//# sourceMappingURL=index.js.map
|
|
1357
1715
|
//# sourceMappingURL=index.js.map
|