tempest-react-sdk 0.30.0 → 0.31.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/components/FilterBar/FilterBar.cjs +2 -0
- package/dist/components/FilterBar/FilterBar.cjs.map +1 -0
- package/dist/components/FilterBar/FilterBar.js +238 -0
- package/dist/components/FilterBar/FilterBar.js.map +1 -0
- package/dist/components/FilterBar/FilterBar.module.cjs +2 -0
- package/dist/components/FilterBar/FilterBar.module.cjs.map +1 -0
- package/dist/components/FilterBar/FilterBar.module.js +20 -0
- package/dist/components/FilterBar/FilterBar.module.js.map +1 -0
- package/dist/components/FilterBar/filter-model.cjs +2 -0
- package/dist/components/FilterBar/filter-model.cjs.map +1 -0
- package/dist/components/FilterBar/filter-model.js +167 -0
- package/dist/components/FilterBar/filter-model.js.map +1 -0
- package/dist/components/Tour/Tour.cjs +2 -0
- package/dist/components/Tour/Tour.cjs.map +1 -0
- package/dist/components/Tour/Tour.js +186 -0
- package/dist/components/Tour/Tour.js.map +1 -0
- package/dist/components/Tour/Tour.module.cjs +2 -0
- package/dist/components/Tour/Tour.module.cjs.map +1 -0
- package/dist/components/Tour/Tour.module.js +21 -0
- package/dist/components/Tour/Tour.module.js.map +1 -0
- package/dist/components/Tour/tour-position.cjs +2 -0
- package/dist/components/Tour/tour-position.cjs.map +1 -0
- package/dist/components/Tour/tour-position.js +105 -0
- package/dist/components/Tour/tour-position.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +181 -0
- package/dist/tempest-react-sdk.js +108 -105
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tour.cjs","names":[],"sources":["../../../src/components/Tour/Tour.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { cn } from \"@/utils/cn\";\n\nimport { backdropRects, placeCard, type TourPlacement, type TourRect } from \"./tour-position\";\nimport styles from \"./Tour.module.css\";\n\n/** One stop of the tour. */\nexport interface TourStep {\n /**\n * CSS selector of the element to point at.\n *\n * A selector rather than a ref, so a tour can be declared as data — in a config\n * file, from the backend, next to the copy — without every screen having to\n * thread refs up to whoever renders the tour.\n */\n target?: string;\n /** Heading of the card. */\n title?: ReactNode;\n /** The explanation. */\n body: ReactNode;\n /** Preferred side. Default `\"bottom\"`, flipped when it does not fit. */\n placement?: TourPlacement;\n}\n\n/** Labels, per locale. */\ninterface TourStrings {\n next: string;\n back: string;\n finish: string;\n skip: string;\n close: string;\n progress: (current: number, total: number) => string;\n}\n\nconst PT_BR: TourStrings = {\n next: \"Próximo\",\n back: \"Voltar\",\n finish: \"Concluir\",\n skip: \"Pular\",\n close: \"Fechar tour\",\n progress: (current, total) => `Passo ${current} de ${total}`,\n};\n\nconst EN: TourStrings = {\n next: \"Next\",\n back: \"Back\",\n finish: \"Done\",\n skip: \"Skip\",\n close: \"Close tour\",\n progress: (current, total) => `Step ${current} of ${total}`,\n};\n\nexport interface TourProps {\n /** The stops, in order. */\n steps: readonly TourStep[];\n /** Whether the tour is showing. Controlled. */\n open: boolean;\n /** Closed by `Esc`, by the close button or by \"skip\". */\n onClose: () => void;\n /** Called after the last step is confirmed. `onClose` follows it. */\n onFinish?: () => void;\n /** Current step index, when the app wants to drive it. */\n index?: number;\n /** Step changed — required only when `index` is given. */\n onIndexChange?: (index: number) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Space kept clear around the highlighted element, in px. Default 4. */\n spotlightPadding?: number;\n /** Extra class on the card. */\n className?: string;\n}\n\n/**\n * A guided tour: dim the page, highlight one element at a time, explain it.\n *\n * The highlighted element stays **clickable** while everything else is blocked,\n * because the useful kind of coachmark says \"press this\" and lets you press it. That\n * is why the backdrop is four rectangles around the target rather than one overlay\n * with a `box-shadow` hole — a shadow is not hit-testable, so a hole made that way\n * would not block anything.\n *\n * Whether a user has seen the tour is the app's business: this component takes\n * `open` and emits `onClose`/`onFinish`. Persisting a flag in `localStorage` is one\n * line in the app and a wrong default here.\n *\n * @example\n * const [open, setOpen] = useState(!storage.get(\"tour-v1\"));\n *\n * <Tour\n * open={open}\n * steps={[\n * { target: \"#novo-pedido\", title: \"Comece aqui\", body: \"Todo pedido nasce deste botão.\" },\n * { target: \"[data-tour='filtros']\", body: \"E filtre por período aqui.\" },\n * ]}\n * onClose={() => setOpen(false)}\n * onFinish={() => storage.set(\"tour-v1\", true)}\n * />\n */\nexport function Tour({\n steps,\n open,\n onClose,\n onFinish,\n index,\n onIndexChange,\n locale = \"pt-BR\",\n spotlightPadding = 4,\n className,\n}: TourProps) {\n const strings = locale === \"en\" ? EN : PT_BR;\n const [internal, setInternal] = useState(0);\n const current = Math.min(index ?? internal, Math.max(0, steps.length - 1));\n const step = steps[current];\n\n const card = useRef<HTMLDivElement | null>(null);\n const [target, setTarget] = useState<TourRect | null>(null);\n const [position, setPosition] = useState<{\n placement: TourPlacement;\n top: number;\n left: number;\n }>({ placement: \"center\", top: 0, left: 0 });\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n const titleId = useId();\n const bodyId = useId();\n\n useFocusTrap(card, open);\n\n const goTo = useCallback(\n (next: number) => {\n if (index === undefined) setInternal(next);\n onIndexChange?.(next);\n },\n [index, onIndexChange],\n );\n\n /** Reset to the first step whenever the tour opens again. */\n useEffect(() => {\n if (open && index === undefined) setInternal(0);\n }, [open, index]);\n\n /**\n * Find the step's element, bring it into view, and measure everything.\n *\n * The scroll happens before measuring, and the measurement is deferred to the\n * next frame: reading a rect in the same tick as `scrollIntoView` gives the\n * position the element had *before* the scroll, which lands the card over the\n * wrong part of the page.\n */\n const measure = useCallback(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n const rect = element?.getBoundingClientRect() ?? null;\n setTarget(\n rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n );\n\n const size = {\n width: card.current?.offsetWidth ?? 320,\n height: card.current?.offsetHeight ?? 160,\n };\n const view = { width: window.innerWidth, height: window.innerHeight };\n setViewport(view);\n setPosition(\n placeCard({\n target: rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n card: size,\n viewport: view,\n preferred: step.placement,\n }),\n );\n }, [open, step]);\n\n useLayoutEffect(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n element?.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior: \"smooth\" });\n const frame = requestAnimationFrame(measure);\n return () => cancelAnimationFrame(frame);\n }, [open, step, measure]);\n\n useEffect(() => {\n if (!open) return;\n window.addEventListener(\"resize\", measure);\n window.addEventListener(\"scroll\", measure, true);\n return () => {\n window.removeEventListener(\"resize\", measure);\n window.removeEventListener(\"scroll\", measure, true);\n };\n }, [open, measure]);\n\n useEffect(() => {\n if (open) card.current?.focus();\n }, [open, current]);\n\n if (!open || !step) return null;\n\n const last = current === steps.length - 1;\n\n const finish = (): void => {\n onFinish?.();\n onClose();\n };\n\n /**\n * Arrow keys walk the tour and `Esc` leaves it.\n *\n * `Esc` is handled here rather than on `window` so a tour opened over a modal\n * does not close both: the innermost handler that sees the key wins, and the\n * card has focus.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n return;\n }\n if (event.key === \"ArrowRight\" && !last) {\n event.preventDefault();\n goTo(current + 1);\n }\n if (event.key === \"ArrowLeft\" && current > 0) {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div className={styles.root} data-placement={position.placement}>\n {backdropRects(target, viewport, spotlightPadding).map((rect, i) => (\n <div\n key={i}\n className={styles.backdrop}\n style={{\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n }}\n onClick={onClose}\n />\n ))}\n\n {target && (\n <div\n className={styles.spotlight}\n style={{\n top: target.top - spotlightPadding,\n left: target.left - spotlightPadding,\n width: target.width + spotlightPadding * 2,\n height: target.height + spotlightPadding * 2,\n }}\n />\n )}\n\n <div\n ref={card}\n className={cn(styles.card, className)}\n style={{ top: position.top, left: position.left }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={step.title ? titleId : undefined}\n aria-describedby={bodyId}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n >\n <div className={styles.header}>\n <span className={styles.progress}>\n {strings.progress(current + 1, steps.length)}\n </span>\n <button\n type=\"button\"\n className={styles.close}\n aria-label={strings.close}\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {step.title && (\n <h2 className={styles.title} id={titleId}>\n {step.title}\n </h2>\n )}\n <div className={styles.body} id={bodyId}>\n {step.body}\n </div>\n\n <div className={styles.footer}>\n {!last && (\n <button type=\"button\" className={styles.skip} onClick={onClose}>\n {strings.skip}\n </button>\n )}\n <div className={styles.actions}>\n {current > 0 && (\n <button\n type=\"button\"\n className={styles.secondary}\n onClick={() => goTo(current - 1)}\n >\n {strings.back}\n </button>\n )}\n <button\n type=\"button\"\n className={styles.primary}\n onClick={() => (last ? finish() : goTo(current + 1))}\n >\n {last ? strings.finish : strings.next}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useFocusTrap as t } from "../../hooks/use-focus-trap.js";
|
|
3
|
+
import { backdropRects as n, placeCard as r } from "./tour-position.js";
|
|
4
|
+
import i from "./Tour.module.js";
|
|
5
|
+
import { useCallback as a, useEffect as o, useId as s, useLayoutEffect as c, useRef as l, useState as u } from "react";
|
|
6
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/Tour/Tour.tsx
|
|
8
|
+
var p = {
|
|
9
|
+
next: "Próximo",
|
|
10
|
+
back: "Voltar",
|
|
11
|
+
finish: "Concluir",
|
|
12
|
+
skip: "Pular",
|
|
13
|
+
close: "Fechar tour",
|
|
14
|
+
progress: (e, t) => `Passo ${e} de ${t}`
|
|
15
|
+
}, m = {
|
|
16
|
+
next: "Next",
|
|
17
|
+
back: "Back",
|
|
18
|
+
finish: "Done",
|
|
19
|
+
skip: "Skip",
|
|
20
|
+
close: "Close tour",
|
|
21
|
+
progress: (e, t) => `Step ${e} of ${t}`
|
|
22
|
+
};
|
|
23
|
+
function h({ steps: h, open: g, onClose: _, onFinish: v, index: y, onIndexChange: b, locale: x = "pt-BR", spotlightPadding: S = 4, className: C }) {
|
|
24
|
+
let w = x === "en" ? m : p, [T, E] = u(0), D = Math.min(y ?? T, Math.max(0, h.length - 1)), O = h[D], k = l(null), [A, j] = u(null), [M, N] = u({
|
|
25
|
+
placement: "center",
|
|
26
|
+
top: 0,
|
|
27
|
+
left: 0
|
|
28
|
+
}), [P, F] = u({
|
|
29
|
+
width: 0,
|
|
30
|
+
height: 0
|
|
31
|
+
}), I = s(), L = s();
|
|
32
|
+
t(k, g);
|
|
33
|
+
let R = a((e) => {
|
|
34
|
+
y === void 0 && E(e), b?.(e);
|
|
35
|
+
}, [y, b]);
|
|
36
|
+
o(() => {
|
|
37
|
+
g && y === void 0 && E(0);
|
|
38
|
+
}, [g, y]);
|
|
39
|
+
let z = a(() => {
|
|
40
|
+
if (!g || !O) return;
|
|
41
|
+
let e = (O.target ? document.querySelector(O.target) : null)?.getBoundingClientRect() ?? null;
|
|
42
|
+
j(e ? {
|
|
43
|
+
top: e.top,
|
|
44
|
+
left: e.left,
|
|
45
|
+
width: e.width,
|
|
46
|
+
height: e.height
|
|
47
|
+
} : null);
|
|
48
|
+
let t = {
|
|
49
|
+
width: k.current?.offsetWidth ?? 320,
|
|
50
|
+
height: k.current?.offsetHeight ?? 160
|
|
51
|
+
}, n = {
|
|
52
|
+
width: window.innerWidth,
|
|
53
|
+
height: window.innerHeight
|
|
54
|
+
};
|
|
55
|
+
F(n), N(r({
|
|
56
|
+
target: e ? {
|
|
57
|
+
top: e.top,
|
|
58
|
+
left: e.left,
|
|
59
|
+
width: e.width,
|
|
60
|
+
height: e.height
|
|
61
|
+
} : null,
|
|
62
|
+
card: t,
|
|
63
|
+
viewport: n,
|
|
64
|
+
preferred: O.placement
|
|
65
|
+
}));
|
|
66
|
+
}, [g, O]);
|
|
67
|
+
if (c(() => {
|
|
68
|
+
if (!g || !O) return;
|
|
69
|
+
(O.target ? document.querySelector(O.target) : null)?.scrollIntoView({
|
|
70
|
+
block: "center",
|
|
71
|
+
inline: "nearest",
|
|
72
|
+
behavior: "smooth"
|
|
73
|
+
});
|
|
74
|
+
let e = requestAnimationFrame(z);
|
|
75
|
+
return () => cancelAnimationFrame(e);
|
|
76
|
+
}, [
|
|
77
|
+
g,
|
|
78
|
+
O,
|
|
79
|
+
z
|
|
80
|
+
]), o(() => {
|
|
81
|
+
if (g) return window.addEventListener("resize", z), window.addEventListener("scroll", z, !0), () => {
|
|
82
|
+
window.removeEventListener("resize", z), window.removeEventListener("scroll", z, !0);
|
|
83
|
+
};
|
|
84
|
+
}, [g, z]), o(() => {
|
|
85
|
+
g && k.current?.focus();
|
|
86
|
+
}, [g, D]), !g || !O) return null;
|
|
87
|
+
let B = D === h.length - 1, V = () => {
|
|
88
|
+
v?.(), _();
|
|
89
|
+
};
|
|
90
|
+
return /* @__PURE__ */ f("div", {
|
|
91
|
+
className: i.root,
|
|
92
|
+
"data-placement": M.placement,
|
|
93
|
+
children: [
|
|
94
|
+
n(A, P, S).map((e, t) => /* @__PURE__ */ d("div", {
|
|
95
|
+
className: i.backdrop,
|
|
96
|
+
style: {
|
|
97
|
+
top: e.top,
|
|
98
|
+
left: e.left,
|
|
99
|
+
width: e.width,
|
|
100
|
+
height: e.height
|
|
101
|
+
},
|
|
102
|
+
onClick: _
|
|
103
|
+
}, t)),
|
|
104
|
+
A && /* @__PURE__ */ d("div", {
|
|
105
|
+
className: i.spotlight,
|
|
106
|
+
style: {
|
|
107
|
+
top: A.top - S,
|
|
108
|
+
left: A.left - S,
|
|
109
|
+
width: A.width + S * 2,
|
|
110
|
+
height: A.height + S * 2
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
/* @__PURE__ */ f("div", {
|
|
114
|
+
ref: k,
|
|
115
|
+
className: e(i.card, C),
|
|
116
|
+
style: {
|
|
117
|
+
top: M.top,
|
|
118
|
+
left: M.left
|
|
119
|
+
},
|
|
120
|
+
role: "dialog",
|
|
121
|
+
"aria-modal": "true",
|
|
122
|
+
"aria-labelledby": O.title ? I : void 0,
|
|
123
|
+
"aria-describedby": L,
|
|
124
|
+
tabIndex: -1,
|
|
125
|
+
onKeyDown: (e) => {
|
|
126
|
+
if (e.key === "Escape") {
|
|
127
|
+
e.stopPropagation(), _();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
e.key === "ArrowRight" && !B && (e.preventDefault(), R(D + 1)), e.key === "ArrowLeft" && D > 0 && (e.preventDefault(), R(D - 1));
|
|
131
|
+
},
|
|
132
|
+
children: [
|
|
133
|
+
/* @__PURE__ */ f("div", {
|
|
134
|
+
className: i.header,
|
|
135
|
+
children: [/* @__PURE__ */ d("span", {
|
|
136
|
+
className: i.progress,
|
|
137
|
+
children: w.progress(D + 1, h.length)
|
|
138
|
+
}), /* @__PURE__ */ d("button", {
|
|
139
|
+
type: "button",
|
|
140
|
+
className: i.close,
|
|
141
|
+
"aria-label": w.close,
|
|
142
|
+
onClick: _,
|
|
143
|
+
children: "×"
|
|
144
|
+
})]
|
|
145
|
+
}),
|
|
146
|
+
O.title && /* @__PURE__ */ d("h2", {
|
|
147
|
+
className: i.title,
|
|
148
|
+
id: I,
|
|
149
|
+
children: O.title
|
|
150
|
+
}),
|
|
151
|
+
/* @__PURE__ */ d("div", {
|
|
152
|
+
className: i.body,
|
|
153
|
+
id: L,
|
|
154
|
+
children: O.body
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ f("div", {
|
|
157
|
+
className: i.footer,
|
|
158
|
+
children: [!B && /* @__PURE__ */ d("button", {
|
|
159
|
+
type: "button",
|
|
160
|
+
className: i.skip,
|
|
161
|
+
onClick: _,
|
|
162
|
+
children: w.skip
|
|
163
|
+
}), /* @__PURE__ */ f("div", {
|
|
164
|
+
className: i.actions,
|
|
165
|
+
children: [D > 0 && /* @__PURE__ */ d("button", {
|
|
166
|
+
type: "button",
|
|
167
|
+
className: i.secondary,
|
|
168
|
+
onClick: () => R(D - 1),
|
|
169
|
+
children: w.back
|
|
170
|
+
}), /* @__PURE__ */ d("button", {
|
|
171
|
+
type: "button",
|
|
172
|
+
className: i.primary,
|
|
173
|
+
onClick: () => B ? V() : R(D + 1),
|
|
174
|
+
children: B ? w.finish : w.next
|
|
175
|
+
})]
|
|
176
|
+
})]
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
})
|
|
180
|
+
]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
export { h as Tour };
|
|
185
|
+
|
|
186
|
+
//# sourceMappingURL=Tour.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tour.js","names":[],"sources":["../../../src/components/Tour/Tour.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { cn } from \"@/utils/cn\";\n\nimport { backdropRects, placeCard, type TourPlacement, type TourRect } from \"./tour-position\";\nimport styles from \"./Tour.module.css\";\n\n/** One stop of the tour. */\nexport interface TourStep {\n /**\n * CSS selector of the element to point at.\n *\n * A selector rather than a ref, so a tour can be declared as data — in a config\n * file, from the backend, next to the copy — without every screen having to\n * thread refs up to whoever renders the tour.\n */\n target?: string;\n /** Heading of the card. */\n title?: ReactNode;\n /** The explanation. */\n body: ReactNode;\n /** Preferred side. Default `\"bottom\"`, flipped when it does not fit. */\n placement?: TourPlacement;\n}\n\n/** Labels, per locale. */\ninterface TourStrings {\n next: string;\n back: string;\n finish: string;\n skip: string;\n close: string;\n progress: (current: number, total: number) => string;\n}\n\nconst PT_BR: TourStrings = {\n next: \"Próximo\",\n back: \"Voltar\",\n finish: \"Concluir\",\n skip: \"Pular\",\n close: \"Fechar tour\",\n progress: (current, total) => `Passo ${current} de ${total}`,\n};\n\nconst EN: TourStrings = {\n next: \"Next\",\n back: \"Back\",\n finish: \"Done\",\n skip: \"Skip\",\n close: \"Close tour\",\n progress: (current, total) => `Step ${current} of ${total}`,\n};\n\nexport interface TourProps {\n /** The stops, in order. */\n steps: readonly TourStep[];\n /** Whether the tour is showing. Controlled. */\n open: boolean;\n /** Closed by `Esc`, by the close button or by \"skip\". */\n onClose: () => void;\n /** Called after the last step is confirmed. `onClose` follows it. */\n onFinish?: () => void;\n /** Current step index, when the app wants to drive it. */\n index?: number;\n /** Step changed — required only when `index` is given. */\n onIndexChange?: (index: number) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Space kept clear around the highlighted element, in px. Default 4. */\n spotlightPadding?: number;\n /** Extra class on the card. */\n className?: string;\n}\n\n/**\n * A guided tour: dim the page, highlight one element at a time, explain it.\n *\n * The highlighted element stays **clickable** while everything else is blocked,\n * because the useful kind of coachmark says \"press this\" and lets you press it. That\n * is why the backdrop is four rectangles around the target rather than one overlay\n * with a `box-shadow` hole — a shadow is not hit-testable, so a hole made that way\n * would not block anything.\n *\n * Whether a user has seen the tour is the app's business: this component takes\n * `open` and emits `onClose`/`onFinish`. Persisting a flag in `localStorage` is one\n * line in the app and a wrong default here.\n *\n * @example\n * const [open, setOpen] = useState(!storage.get(\"tour-v1\"));\n *\n * <Tour\n * open={open}\n * steps={[\n * { target: \"#novo-pedido\", title: \"Comece aqui\", body: \"Todo pedido nasce deste botão.\" },\n * { target: \"[data-tour='filtros']\", body: \"E filtre por período aqui.\" },\n * ]}\n * onClose={() => setOpen(false)}\n * onFinish={() => storage.set(\"tour-v1\", true)}\n * />\n */\nexport function Tour({\n steps,\n open,\n onClose,\n onFinish,\n index,\n onIndexChange,\n locale = \"pt-BR\",\n spotlightPadding = 4,\n className,\n}: TourProps) {\n const strings = locale === \"en\" ? EN : PT_BR;\n const [internal, setInternal] = useState(0);\n const current = Math.min(index ?? internal, Math.max(0, steps.length - 1));\n const step = steps[current];\n\n const card = useRef<HTMLDivElement | null>(null);\n const [target, setTarget] = useState<TourRect | null>(null);\n const [position, setPosition] = useState<{\n placement: TourPlacement;\n top: number;\n left: number;\n }>({ placement: \"center\", top: 0, left: 0 });\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n const titleId = useId();\n const bodyId = useId();\n\n useFocusTrap(card, open);\n\n const goTo = useCallback(\n (next: number) => {\n if (index === undefined) setInternal(next);\n onIndexChange?.(next);\n },\n [index, onIndexChange],\n );\n\n /** Reset to the first step whenever the tour opens again. */\n useEffect(() => {\n if (open && index === undefined) setInternal(0);\n }, [open, index]);\n\n /**\n * Find the step's element, bring it into view, and measure everything.\n *\n * The scroll happens before measuring, and the measurement is deferred to the\n * next frame: reading a rect in the same tick as `scrollIntoView` gives the\n * position the element had *before* the scroll, which lands the card over the\n * wrong part of the page.\n */\n const measure = useCallback(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n const rect = element?.getBoundingClientRect() ?? null;\n setTarget(\n rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n );\n\n const size = {\n width: card.current?.offsetWidth ?? 320,\n height: card.current?.offsetHeight ?? 160,\n };\n const view = { width: window.innerWidth, height: window.innerHeight };\n setViewport(view);\n setPosition(\n placeCard({\n target: rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n card: size,\n viewport: view,\n preferred: step.placement,\n }),\n );\n }, [open, step]);\n\n useLayoutEffect(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n element?.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior: \"smooth\" });\n const frame = requestAnimationFrame(measure);\n return () => cancelAnimationFrame(frame);\n }, [open, step, measure]);\n\n useEffect(() => {\n if (!open) return;\n window.addEventListener(\"resize\", measure);\n window.addEventListener(\"scroll\", measure, true);\n return () => {\n window.removeEventListener(\"resize\", measure);\n window.removeEventListener(\"scroll\", measure, true);\n };\n }, [open, measure]);\n\n useEffect(() => {\n if (open) card.current?.focus();\n }, [open, current]);\n\n if (!open || !step) return null;\n\n const last = current === steps.length - 1;\n\n const finish = (): void => {\n onFinish?.();\n onClose();\n };\n\n /**\n * Arrow keys walk the tour and `Esc` leaves it.\n *\n * `Esc` is handled here rather than on `window` so a tour opened over a modal\n * does not close both: the innermost handler that sees the key wins, and the\n * card has focus.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n return;\n }\n if (event.key === \"ArrowRight\" && !last) {\n event.preventDefault();\n goTo(current + 1);\n }\n if (event.key === \"ArrowLeft\" && current > 0) {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div className={styles.root} data-placement={position.placement}>\n {backdropRects(target, viewport, spotlightPadding).map((rect, i) => (\n <div\n key={i}\n className={styles.backdrop}\n style={{\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n }}\n onClick={onClose}\n />\n ))}\n\n {target && (\n <div\n className={styles.spotlight}\n style={{\n top: target.top - spotlightPadding,\n left: target.left - spotlightPadding,\n width: target.width + spotlightPadding * 2,\n height: target.height + spotlightPadding * 2,\n }}\n />\n )}\n\n <div\n ref={card}\n className={cn(styles.card, className)}\n style={{ top: position.top, left: position.left }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={step.title ? titleId : undefined}\n aria-describedby={bodyId}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n >\n <div className={styles.header}>\n <span className={styles.progress}>\n {strings.progress(current + 1, steps.length)}\n </span>\n <button\n type=\"button\"\n className={styles.close}\n aria-label={strings.close}\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {step.title && (\n <h2 className={styles.title} id={titleId}>\n {step.title}\n </h2>\n )}\n <div className={styles.body} id={bodyId}>\n {step.body}\n </div>\n\n <div className={styles.footer}>\n {!last && (\n <button type=\"button\" className={styles.skip} onClick={onClose}>\n {strings.skip}\n </button>\n )}\n <div className={styles.actions}>\n {current > 0 && (\n <button\n type=\"button\"\n className={styles.secondary}\n onClick={() => goTo(current - 1)}\n >\n {strings.back}\n </button>\n )}\n <button\n type=\"button\"\n className={styles.primary}\n onClick={() => (last ? finish() : goTo(current + 1))}\n >\n {last ? strings.finish : strings.next}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`tempest_root_o2Dqb`,t=`tempest_backdrop_TVqRI`,n=`tempest_spotlight_0N62-`,r=`tempest_card_eurpw`,i=`tempest_header_PTKGS`,a=`tempest_progress_WzQ2f`,o=`tempest_close_z43ed`,s=`tempest_title_an3mi`,c=`tempest_body_RUlwz`,l=`tempest_footer_i-Mo6`,u=`tempest_actions_n-L-Q`,d=`tempest_skip_IdWQY`,f=`tempest_primary_ICXJm`,p=`tempest_secondary_kde2L`,m={root:e,backdrop:t,spotlight:n,card:r,header:i,progress:a,close:o,title:s,body:c,footer:l,actions:u,skip:d,primary:f,secondary:p};exports.actions=u,exports.backdrop=t,exports.body=c,exports.card=r,exports.close=o,exports.default=m,exports.footer=l,exports.header=i,exports.primary=f,exports.progress=a,exports.root=e,exports.secondary=p,exports.skip=d,exports.spotlight=n,exports.title=s;
|
|
2
|
+
//# sourceMappingURL=Tour.module.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tour.module.cjs","names":[],"sources":["../../../src/components/Tour/Tour.module.css"],"sourcesContent":[".root {\n position: fixed;\n inset: 0;\n z-index: var(--tempest-z-modal, 1000);\n font-family: var(--tempest-font-sans);\n pointer-events: none;\n}\n\n/*\n * The dimmed area is four rectangles, each one hit-testable, so the page behind is\n * blocked while the highlighted element stays clickable. A single overlay with a\n * `box-shadow` hole looks the same and blocks nothing: a shadow is not hit-tested.\n */\n.backdrop {\n position: fixed;\n background-color: var(--tempest-tour-backdrop, rgb(0 0 0 / 55%));\n pointer-events: auto;\n}\n\n.spotlight {\n position: fixed;\n border-radius: var(--tempest-radius-md);\n box-shadow: 0 0 0 2px var(--tempest-primary);\n pointer-events: none;\n}\n\n.card {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n width: min(22rem, calc(100vw - 2rem));\n padding: var(--tempest-space-4);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n box-shadow: var(--tempest-shadow-lg);\n color: var(--tempest-text);\n pointer-events: auto;\n}\n\n.card:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n}\n\n.progress {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: none;\n border-radius: var(--tempest-radius-sm);\n background: none;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-lg);\n line-height: 1;\n cursor: pointer;\n}\n\n.close:hover {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.body {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n margin-block-start: var(--tempest-space-2);\n}\n\n.actions {\n display: flex;\n gap: var(--tempest-space-2);\n margin-inline-start: auto;\n}\n\n.skip {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-text-muted);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.primary,\n.secondary {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border-radius: var(--tempest-radius-md);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.primary {\n border: none;\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.secondary {\n border: 1px solid var(--tempest-border);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.primary:focus-visible,\n.secondary:focus-visible,\n.skip:focus-visible,\n.close:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n/* The spotlight ring is decoration; a reader who asked for less motion gets none. */\n@media (prefers-reduced-motion: no-preference) {\n .spotlight {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out),\n width var(--tempest-duration-base) var(--tempest-ease-out),\n height var(--tempest-duration-base) var(--tempest-ease-out);\n }\n\n .card {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out);\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/components/Tour/Tour.module.css
|
|
2
|
+
var e = "tempest_root_o2Dqb", t = "tempest_backdrop_TVqRI", n = "tempest_spotlight_0N62-", r = "tempest_card_eurpw", i = "tempest_header_PTKGS", a = "tempest_progress_WzQ2f", o = "tempest_close_z43ed", s = "tempest_title_an3mi", c = "tempest_body_RUlwz", l = "tempest_footer_i-Mo6", u = "tempest_actions_n-L-Q", d = "tempest_skip_IdWQY", f = "tempest_primary_ICXJm", p = "tempest_secondary_kde2L", m = {
|
|
3
|
+
root: e,
|
|
4
|
+
backdrop: t,
|
|
5
|
+
spotlight: n,
|
|
6
|
+
card: r,
|
|
7
|
+
header: i,
|
|
8
|
+
progress: a,
|
|
9
|
+
close: o,
|
|
10
|
+
title: s,
|
|
11
|
+
body: c,
|
|
12
|
+
footer: l,
|
|
13
|
+
actions: u,
|
|
14
|
+
skip: d,
|
|
15
|
+
primary: f,
|
|
16
|
+
secondary: p
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { u as actions, t as backdrop, c as body, r as card, o as close, m as default, l as footer, i as header, f as primary, a as progress, e as root, p as secondary, d as skip, n as spotlight, s as title };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=Tour.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tour.module.js","names":[],"sources":["../../../src/components/Tour/Tour.module.css"],"sourcesContent":[".root {\n position: fixed;\n inset: 0;\n z-index: var(--tempest-z-modal, 1000);\n font-family: var(--tempest-font-sans);\n pointer-events: none;\n}\n\n/*\n * The dimmed area is four rectangles, each one hit-testable, so the page behind is\n * blocked while the highlighted element stays clickable. A single overlay with a\n * `box-shadow` hole looks the same and blocks nothing: a shadow is not hit-tested.\n */\n.backdrop {\n position: fixed;\n background-color: var(--tempest-tour-backdrop, rgb(0 0 0 / 55%));\n pointer-events: auto;\n}\n\n.spotlight {\n position: fixed;\n border-radius: var(--tempest-radius-md);\n box-shadow: 0 0 0 2px var(--tempest-primary);\n pointer-events: none;\n}\n\n.card {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n width: min(22rem, calc(100vw - 2rem));\n padding: var(--tempest-space-4);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n box-shadow: var(--tempest-shadow-lg);\n color: var(--tempest-text);\n pointer-events: auto;\n}\n\n.card:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n}\n\n.progress {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: none;\n border-radius: var(--tempest-radius-sm);\n background: none;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-lg);\n line-height: 1;\n cursor: pointer;\n}\n\n.close:hover {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.body {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n margin-block-start: var(--tempest-space-2);\n}\n\n.actions {\n display: flex;\n gap: var(--tempest-space-2);\n margin-inline-start: auto;\n}\n\n.skip {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-text-muted);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.primary,\n.secondary {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border-radius: var(--tempest-radius-md);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.primary {\n border: none;\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.secondary {\n border: 1px solid var(--tempest-border);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.primary:focus-visible,\n.secondary:focus-visible,\n.skip:focus-visible,\n.close:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n/* The spotlight ring is decoration; a reader who asked for less motion gets none. */\n@media (prefers-reduced-motion: no-preference) {\n .spotlight {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out),\n width var(--tempest-duration-base) var(--tempest-ease-out),\n height var(--tempest-duration-base) var(--tempest-ease-out);\n }\n\n .card {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out);\n }\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e,t,n){return Math.max(t,Math.min(e,Math.max(t,n)))}function t(e,t,n,r){switch(e){case`top`:return t.top-12-n.height>=8;case`bottom`:return t.top+t.height+12+n.height<=r.height-8;case`left`:return t.left-12-n.width>=8;case`right`:return t.left+t.width+12+n.width<=r.width-8;default:return!0}}var n={bottom:[`bottom`,`top`,`right`,`left`],top:[`top`,`bottom`,`right`,`left`],right:[`right`,`left`,`bottom`,`top`],left:[`left`,`right`,`bottom`,`top`]};function r({target:r,card:i,viewport:a,preferred:o=`bottom`}){let s={placement:`center`,top:Math.max(8,(a.height-i.height)/2),left:Math.max(8,(a.width-i.width)/2)};if(!r||o===`center`)return s;let c=(n[o]??n.bottom).find(e=>t(e,r,i,a));if(!c)return s;let l=r.left+r.width/2-i.width/2,u=r.top+r.height/2-i.height/2,d=c===`top`?{top:r.top-12-i.height,left:l}:c===`bottom`?{top:r.top+r.height+12,left:l}:c===`left`?{top:u,left:r.left-12-i.width}:{top:u,left:r.left+r.width+12};return{placement:c,top:e(d.top,8,a.height-i.height-8),left:e(d.left,8,a.width-i.width-8)}}function i(e,t,n=4){if(!e)return[{top:0,left:0,width:t.width,height:t.height}];let r=Math.max(0,e.top-n),i=Math.max(0,e.left-n),a=Math.min(t.width,e.left+e.width+n),o=Math.min(t.height,e.top+e.height+n);return[{top:0,left:0,width:t.width,height:r},{top:o,left:0,width:t.width,height:t.height-o},{top:r,left:0,width:i,height:o-r},{top:r,left:a,width:t.width-a,height:o-r}].filter(e=>e.width>0&&e.height>0)}exports.backdropRects=i,exports.placeCard=r;
|
|
2
|
+
//# sourceMappingURL=tour-position.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-position.cjs","names":[],"sources":["../../../src/components/Tour/tour-position.ts"],"sourcesContent":["/** Where the card sits relative to the highlighted element. */\nexport type TourPlacement = \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\";\n\n/** A rectangle in viewport coordinates. */\nexport interface TourRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Viewport size the placement is solved against. */\nexport interface TourViewport {\n width: number;\n height: number;\n}\n\n/** Gap between the highlighted element and the card. */\nconst OFFSET = 12;\n\n/** Smallest margin the card keeps from the viewport edge. */\nconst MARGIN = 8;\n\n/** Clamp a value into a range, tolerating a range narrower than the value. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, Math.max(min, max)));\n}\n\n/** Does the card fit on this side of the target? */\nfunction fits(\n placement: TourPlacement,\n target: TourRect,\n card: { width: number; height: number },\n viewport: TourViewport,\n): boolean {\n switch (placement) {\n case \"top\":\n return target.top - OFFSET - card.height >= MARGIN;\n case \"bottom\":\n return target.top + target.height + OFFSET + card.height <= viewport.height - MARGIN;\n case \"left\":\n return target.left - OFFSET - card.width >= MARGIN;\n case \"right\":\n return target.left + target.width + OFFSET + card.width <= viewport.width - MARGIN;\n default:\n return true;\n }\n}\n\n/** Sides tried, in order, when the preferred one does not fit. */\nconst FALLBACKS: Record<Exclude<TourPlacement, \"center\">, TourPlacement[]> = {\n bottom: [\"bottom\", \"top\", \"right\", \"left\"],\n top: [\"top\", \"bottom\", \"right\", \"left\"],\n right: [\"right\", \"left\", \"bottom\", \"top\"],\n left: [\"left\", \"right\", \"bottom\", \"top\"],\n};\n\n/**\n * Place the tour card next to the highlighted element.\n *\n * The preferred side is tried first and the opposite one second, because flipping\n * a \"below\" card to \"above\" keeps the reading relationship with the target;\n * jumping to a side would move the card across the screen for no reason the reader\n * can see.\n *\n * Nothing fitting anywhere ends at `center`: a card pinned half off-screen is worse\n * than a card in the middle, and this happens for real whenever the target is\n * taller than the viewport.\n *\n * The result is always clamped inside the viewport, so the card cannot be pushed\n * off the edge by a target near a corner.\n *\n * @param params.target - The highlighted element's rect, viewport coordinates.\n * @param params.card - Measured card size.\n * @param params.viewport - Viewport size.\n * @param params.preferred - Placement the step asked for. Default `\"bottom\"`.\n * @returns The chosen placement and the card's top/left.\n */\nexport function placeCard({\n target,\n card,\n viewport,\n preferred = \"bottom\",\n}: {\n target: TourRect | null;\n card: { width: number; height: number };\n viewport: TourViewport;\n preferred?: TourPlacement;\n}): { placement: TourPlacement; top: number; left: number } {\n const centered = {\n placement: \"center\" as TourPlacement,\n top: Math.max(MARGIN, (viewport.height - card.height) / 2),\n left: Math.max(MARGIN, (viewport.width - card.width) / 2),\n };\n\n // No target — a step whose element is not on the page still has to show its\n // message, so it is shown centered rather than dropped.\n if (!target || preferred === \"center\") return centered;\n\n const order = FALLBACKS[preferred as Exclude<TourPlacement, \"center\">] ?? FALLBACKS.bottom;\n const placement = order.find((candidate) => fits(candidate, target, card, viewport));\n if (!placement) return centered;\n\n const horizontalCenter = target.left + target.width / 2 - card.width / 2;\n const verticalCenter = target.top + target.height / 2 - card.height / 2;\n\n const raw =\n placement === \"top\"\n ? { top: target.top - OFFSET - card.height, left: horizontalCenter }\n : placement === \"bottom\"\n ? { top: target.top + target.height + OFFSET, left: horizontalCenter }\n : placement === \"left\"\n ? { top: verticalCenter, left: target.left - OFFSET - card.width }\n : { top: verticalCenter, left: target.left + target.width + OFFSET };\n\n return {\n placement,\n top: clamp(raw.top, MARGIN, viewport.height - card.height - MARGIN),\n left: clamp(raw.left, MARGIN, viewport.width - card.width - MARGIN),\n };\n}\n\n/**\n * The four backdrop rectangles that surround the highlighted element.\n *\n * Four rects rather than one overlay with a `box-shadow` hole: a box-shadow is not\n * hit-testable, so the \"dimmed\" area would not block clicks — and blocking the rest\n * of the page while leaving the highlighted control usable is the whole behaviour a\n * coachmark needs. Any rect that comes out empty is dropped, which is what happens\n * when the target touches an edge.\n *\n * @param target - Highlighted rect, or `null` for a full-screen backdrop.\n * @param viewport - Viewport size.\n * @param padding - Extra space kept clear around the target.\n */\nexport function backdropRects(\n target: TourRect | null,\n viewport: TourViewport,\n padding = 4,\n): TourRect[] {\n if (!target) {\n return [{ top: 0, left: 0, width: viewport.width, height: viewport.height }];\n }\n const top = Math.max(0, target.top - padding);\n const left = Math.max(0, target.left - padding);\n const right = Math.min(viewport.width, target.left + target.width + padding);\n const bottom = Math.min(viewport.height, target.top + target.height + padding);\n\n return [\n { top: 0, left: 0, width: viewport.width, height: top },\n { top: bottom, left: 0, width: viewport.width, height: viewport.height - bottom },\n { top, left: 0, width: left, height: bottom - top },\n { top, left: right, width: viewport.width - right, height: bottom - top },\n ].filter((rect) => rect.width > 0 && rect.height > 0);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function e(e, t, n) {
|
|
2
|
+
return Math.max(t, Math.min(e, Math.max(t, n)));
|
|
3
|
+
}
|
|
4
|
+
function t(e, t, n, r) {
|
|
5
|
+
switch (e) {
|
|
6
|
+
case "top": return t.top - 12 - n.height >= 8;
|
|
7
|
+
case "bottom": return t.top + t.height + 12 + n.height <= r.height - 8;
|
|
8
|
+
case "left": return t.left - 12 - n.width >= 8;
|
|
9
|
+
case "right": return t.left + t.width + 12 + n.width <= r.width - 8;
|
|
10
|
+
default: return !0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
var n = {
|
|
14
|
+
bottom: [
|
|
15
|
+
"bottom",
|
|
16
|
+
"top",
|
|
17
|
+
"right",
|
|
18
|
+
"left"
|
|
19
|
+
],
|
|
20
|
+
top: [
|
|
21
|
+
"top",
|
|
22
|
+
"bottom",
|
|
23
|
+
"right",
|
|
24
|
+
"left"
|
|
25
|
+
],
|
|
26
|
+
right: [
|
|
27
|
+
"right",
|
|
28
|
+
"left",
|
|
29
|
+
"bottom",
|
|
30
|
+
"top"
|
|
31
|
+
],
|
|
32
|
+
left: [
|
|
33
|
+
"left",
|
|
34
|
+
"right",
|
|
35
|
+
"bottom",
|
|
36
|
+
"top"
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
function r({ target: r, card: i, viewport: a, preferred: o = "bottom" }) {
|
|
40
|
+
let s = {
|
|
41
|
+
placement: "center",
|
|
42
|
+
top: Math.max(8, (a.height - i.height) / 2),
|
|
43
|
+
left: Math.max(8, (a.width - i.width) / 2)
|
|
44
|
+
};
|
|
45
|
+
if (!r || o === "center") return s;
|
|
46
|
+
let c = (n[o] ?? n.bottom).find((e) => t(e, r, i, a));
|
|
47
|
+
if (!c) return s;
|
|
48
|
+
let l = r.left + r.width / 2 - i.width / 2, u = r.top + r.height / 2 - i.height / 2, d = c === "top" ? {
|
|
49
|
+
top: r.top - 12 - i.height,
|
|
50
|
+
left: l
|
|
51
|
+
} : c === "bottom" ? {
|
|
52
|
+
top: r.top + r.height + 12,
|
|
53
|
+
left: l
|
|
54
|
+
} : c === "left" ? {
|
|
55
|
+
top: u,
|
|
56
|
+
left: r.left - 12 - i.width
|
|
57
|
+
} : {
|
|
58
|
+
top: u,
|
|
59
|
+
left: r.left + r.width + 12
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
placement: c,
|
|
63
|
+
top: e(d.top, 8, a.height - i.height - 8),
|
|
64
|
+
left: e(d.left, 8, a.width - i.width - 8)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function i(e, t, n = 4) {
|
|
68
|
+
if (!e) return [{
|
|
69
|
+
top: 0,
|
|
70
|
+
left: 0,
|
|
71
|
+
width: t.width,
|
|
72
|
+
height: t.height
|
|
73
|
+
}];
|
|
74
|
+
let r = Math.max(0, e.top - n), i = Math.max(0, e.left - n), a = Math.min(t.width, e.left + e.width + n), o = Math.min(t.height, e.top + e.height + n);
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
top: 0,
|
|
78
|
+
left: 0,
|
|
79
|
+
width: t.width,
|
|
80
|
+
height: r
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
top: o,
|
|
84
|
+
left: 0,
|
|
85
|
+
width: t.width,
|
|
86
|
+
height: t.height - o
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
top: r,
|
|
90
|
+
left: 0,
|
|
91
|
+
width: i,
|
|
92
|
+
height: o - r
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
top: r,
|
|
96
|
+
left: a,
|
|
97
|
+
width: t.width - a,
|
|
98
|
+
height: o - r
|
|
99
|
+
}
|
|
100
|
+
].filter((e) => e.width > 0 && e.height > 0);
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { i as backdropRects, r as placeCard };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=tour-position.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-position.js","names":[],"sources":["../../../src/components/Tour/tour-position.ts"],"sourcesContent":["/** Where the card sits relative to the highlighted element. */\nexport type TourPlacement = \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\";\n\n/** A rectangle in viewport coordinates. */\nexport interface TourRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Viewport size the placement is solved against. */\nexport interface TourViewport {\n width: number;\n height: number;\n}\n\n/** Gap between the highlighted element and the card. */\nconst OFFSET = 12;\n\n/** Smallest margin the card keeps from the viewport edge. */\nconst MARGIN = 8;\n\n/** Clamp a value into a range, tolerating a range narrower than the value. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, Math.max(min, max)));\n}\n\n/** Does the card fit on this side of the target? */\nfunction fits(\n placement: TourPlacement,\n target: TourRect,\n card: { width: number; height: number },\n viewport: TourViewport,\n): boolean {\n switch (placement) {\n case \"top\":\n return target.top - OFFSET - card.height >= MARGIN;\n case \"bottom\":\n return target.top + target.height + OFFSET + card.height <= viewport.height - MARGIN;\n case \"left\":\n return target.left - OFFSET - card.width >= MARGIN;\n case \"right\":\n return target.left + target.width + OFFSET + card.width <= viewport.width - MARGIN;\n default:\n return true;\n }\n}\n\n/** Sides tried, in order, when the preferred one does not fit. */\nconst FALLBACKS: Record<Exclude<TourPlacement, \"center\">, TourPlacement[]> = {\n bottom: [\"bottom\", \"top\", \"right\", \"left\"],\n top: [\"top\", \"bottom\", \"right\", \"left\"],\n right: [\"right\", \"left\", \"bottom\", \"top\"],\n left: [\"left\", \"right\", \"bottom\", \"top\"],\n};\n\n/**\n * Place the tour card next to the highlighted element.\n *\n * The preferred side is tried first and the opposite one second, because flipping\n * a \"below\" card to \"above\" keeps the reading relationship with the target;\n * jumping to a side would move the card across the screen for no reason the reader\n * can see.\n *\n * Nothing fitting anywhere ends at `center`: a card pinned half off-screen is worse\n * than a card in the middle, and this happens for real whenever the target is\n * taller than the viewport.\n *\n * The result is always clamped inside the viewport, so the card cannot be pushed\n * off the edge by a target near a corner.\n *\n * @param params.target - The highlighted element's rect, viewport coordinates.\n * @param params.card - Measured card size.\n * @param params.viewport - Viewport size.\n * @param params.preferred - Placement the step asked for. Default `\"bottom\"`.\n * @returns The chosen placement and the card's top/left.\n */\nexport function placeCard({\n target,\n card,\n viewport,\n preferred = \"bottom\",\n}: {\n target: TourRect | null;\n card: { width: number; height: number };\n viewport: TourViewport;\n preferred?: TourPlacement;\n}): { placement: TourPlacement; top: number; left: number } {\n const centered = {\n placement: \"center\" as TourPlacement,\n top: Math.max(MARGIN, (viewport.height - card.height) / 2),\n left: Math.max(MARGIN, (viewport.width - card.width) / 2),\n };\n\n // No target — a step whose element is not on the page still has to show its\n // message, so it is shown centered rather than dropped.\n if (!target || preferred === \"center\") return centered;\n\n const order = FALLBACKS[preferred as Exclude<TourPlacement, \"center\">] ?? FALLBACKS.bottom;\n const placement = order.find((candidate) => fits(candidate, target, card, viewport));\n if (!placement) return centered;\n\n const horizontalCenter = target.left + target.width / 2 - card.width / 2;\n const verticalCenter = target.top + target.height / 2 - card.height / 2;\n\n const raw =\n placement === \"top\"\n ? { top: target.top - OFFSET - card.height, left: horizontalCenter }\n : placement === \"bottom\"\n ? { top: target.top + target.height + OFFSET, left: horizontalCenter }\n : placement === \"left\"\n ? { top: verticalCenter, left: target.left - OFFSET - card.width }\n : { top: verticalCenter, left: target.left + target.width + OFFSET };\n\n return {\n placement,\n top: clamp(raw.top, MARGIN, viewport.height - card.height - MARGIN),\n left: clamp(raw.left, MARGIN, viewport.width - card.width - MARGIN),\n };\n}\n\n/**\n * The four backdrop rectangles that surround the highlighted element.\n *\n * Four rects rather than one overlay with a `box-shadow` hole: a box-shadow is not\n * hit-testable, so the \"dimmed\" area would not block clicks — and blocking the rest\n * of the page while leaving the highlighted control usable is the whole behaviour a\n * coachmark needs. Any rect that comes out empty is dropped, which is what happens\n * when the target touches an edge.\n *\n * @param target - Highlighted rect, or `null` for a full-screen backdrop.\n * @param viewport - Viewport size.\n * @param padding - Extra space kept clear around the target.\n */\nexport function backdropRects(\n target: TourRect | null,\n viewport: TourViewport,\n padding = 4,\n): TourRect[] {\n if (!target) {\n return [{ top: 0, left: 0, width: viewport.width, height: viewport.height }];\n }\n const top = Math.max(0, target.top - padding);\n const left = Math.max(0, target.left - padding);\n const right = Math.min(viewport.width, target.left + target.width + padding);\n const bottom = Math.min(viewport.height, target.top + target.height + padding);\n\n return [\n { top: 0, left: 0, width: viewport.width, height: top },\n { top: bottom, left: 0, width: viewport.width, height: viewport.height - bottom },\n { top, left: 0, width: left, height: bottom - top },\n { top, left: right, width: viewport.width - right, height: bottom - top },\n ].filter((rect) => rect.width > 0 && rect.height > 0);\n}\n"],"mappings":"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"}
|