@leavepulse/ui 0.61.0 → 0.62.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/chunks/{LpAlert--KLL4NLk.js → LpAlert-BAOujnXy.js} +8 -6
- package/dist/chunks/{LpHints-BTpvisgP.js → LpHints-B_85eSmm.js} +1 -1
- package/dist/chunks/{LpTour-C29-1wha.js → LpTour-ByaL36fN.js} +75 -53
- package/dist/components/LpAlert.vue.d.ts +7 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/tour.d.ts +9 -0
- package/dist/components/tour.js +22 -0
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/components/LpAlert.vue +16 -5
- package/src/components/LpTour.vue +26 -8
- package/src/components/tour.ts +31 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineComponent, computed, openBlock, createElementBlock, normalizeClass, createVNode, createElementVNode, toDisplayString, createCommentVNode, renderSlot } from "vue";
|
|
2
2
|
import { tv } from "tailwind-variants";
|
|
3
3
|
import { _ as _sfc_main$1 } from "./LpIcon-CTuDVTYv.js";
|
|
4
|
-
const _hoisted_1 = ["role"];
|
|
4
|
+
const _hoisted_1 = ["role", "aria-busy"];
|
|
5
5
|
const _hoisted_2 = { class: "min-w-0" };
|
|
6
6
|
const _hoisted_3 = {
|
|
7
7
|
key: 0,
|
|
@@ -13,7 +13,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
13
13
|
variant: { default: "info" },
|
|
14
14
|
title: {},
|
|
15
15
|
icon: {},
|
|
16
|
-
live: { type: [String, Boolean], default: void 0 }
|
|
16
|
+
live: { type: [String, Boolean], default: void 0 },
|
|
17
|
+
busy: { type: Boolean, default: false }
|
|
17
18
|
},
|
|
18
19
|
setup(__props) {
|
|
19
20
|
const alert = tv({
|
|
@@ -42,21 +43,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
42
43
|
};
|
|
43
44
|
const props = __props;
|
|
44
45
|
const role = computed(() => {
|
|
45
|
-
const live = props.live ?? (props.variant === "danger" ? "assertive" : false);
|
|
46
|
+
const live = props.live ?? (props.variant === "danger" ? "assertive" : props.busy ? "polite" : false);
|
|
46
47
|
return live === "assertive" ? "alert" : live === "polite" ? "status" : void 0;
|
|
47
48
|
});
|
|
48
49
|
const classes = computed(() => alert({ variant: props.variant }));
|
|
49
|
-
const resolvedIcon = computed(() => props.icon ?? ICONS[props.variant ?? "info"]);
|
|
50
|
+
const resolvedIcon = computed(() => props.icon ?? (props.busy ? "lucide:loader-circle" : ICONS[props.variant ?? "info"]));
|
|
50
51
|
const iconTone = computed(() => ICON_TONE[props.variant ?? "info"]);
|
|
51
52
|
return (_ctx, _cache) => {
|
|
52
53
|
return openBlock(), createElementBlock("div", {
|
|
53
54
|
class: normalizeClass(classes.value),
|
|
54
|
-
role: role.value
|
|
55
|
+
role: role.value,
|
|
56
|
+
"aria-busy": __props.busy || void 0
|
|
55
57
|
}, [
|
|
56
58
|
createVNode(_sfc_main$1, {
|
|
57
59
|
name: resolvedIcon.value,
|
|
58
60
|
size: 18,
|
|
59
|
-
class: normalizeClass(["mt-0.5 shrink-0", iconTone.value]),
|
|
61
|
+
class: normalizeClass(["mt-0.5 shrink-0", iconTone.value, __props.busy ? "animate-spin motion-reduce:animate-none" : ""]),
|
|
60
62
|
"aria-hidden": "true"
|
|
61
63
|
}, null, 8, ["name", "class"]),
|
|
62
64
|
createElementVNode("div", _hoisted_2, [
|
|
@@ -3,7 +3,7 @@ import { provideHintSystem } from "../composables/useHintSystem.js";
|
|
|
3
3
|
import { useLabels } from "../composables/useLpConfig.js";
|
|
4
4
|
import { _ as _sfc_main$3 } from "./LpActionBar-BsSp66S_.js";
|
|
5
5
|
import { _ as _sfc_main$2 } from "./LpHintLayer-DsclGYcD.js";
|
|
6
|
-
import { _ as _sfc_main$1 } from "./LpTour-
|
|
6
|
+
import { _ as _sfc_main$1 } from "./LpTour-ByaL36fN.js";
|
|
7
7
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
8
8
|
__name: "LpHints",
|
|
9
9
|
props: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, useModel, computed, shallowRef, ref, watch, onBeforeUnmount, openBlock, createBlock, Teleport, createElementBlock, withModifiers, Fragment, renderList,
|
|
1
|
+
import { defineComponent, useModel, computed, shallowRef, ref, watch, onBeforeUnmount, openBlock, createBlock, Teleport, createVNode, Transition, withCtx, createElementBlock, withModifiers, createElementVNode, normalizeStyle, Fragment, renderList, normalizeClass, createCommentVNode, unref, renderSlot, mergeModels, nextTick } from "vue";
|
|
2
2
|
import { PopoverRoot, PopoverAnchor, PopoverPortal, PopoverContent } from "reka-ui";
|
|
3
3
|
import { prefersReducedMotion } from "../composables/easing.js";
|
|
4
4
|
import { hints } from "../composables/useHints.js";
|
|
@@ -8,7 +8,7 @@ import { useMediaQuery } from "../composables/useMediaQuery.js";
|
|
|
8
8
|
import { scrollToElement } from "../composables/scroll.js";
|
|
9
9
|
import { PANEL_SURFACE } from "../components/dropdown.js";
|
|
10
10
|
import { _ as _sfc_main$1 } from "./TourCard-AjN0uhs1.js";
|
|
11
|
-
import { holeOf, panelsAround } from "../components/tour.js";
|
|
11
|
+
import { holeOf, panelsAround, holeClip } from "../components/tour.js";
|
|
12
12
|
const _hoisted_1 = ["aria-label"];
|
|
13
13
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
14
14
|
__name: "LpTour",
|
|
@@ -55,6 +55,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
55
55
|
const panels = computed(
|
|
56
56
|
() => hole.value ? panelsAround(hole.value, viewport.value) : [{ x: 0, y: 0, width: viewport.value.width, height: viewport.value.height }]
|
|
57
57
|
);
|
|
58
|
+
const radius = ref(8);
|
|
59
|
+
const clip = computed(() => holeClip(hole.value, viewport.value, radius.value));
|
|
58
60
|
const reference = {
|
|
59
61
|
getBoundingClientRect: () => {
|
|
60
62
|
const h = hole.value;
|
|
@@ -173,7 +175,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
173
175
|
cancelAnimationFrame(frame);
|
|
174
176
|
frame = 0;
|
|
175
177
|
target.value = null;
|
|
176
|
-
box.value = null;
|
|
177
178
|
runAll(cleanups);
|
|
178
179
|
}
|
|
179
180
|
function finish() {
|
|
@@ -194,6 +195,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
194
195
|
(value) => {
|
|
195
196
|
if (value) {
|
|
196
197
|
direction = 1;
|
|
198
|
+
box.value = null;
|
|
199
|
+
radius.value = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--radius-control")) || 8;
|
|
197
200
|
document.addEventListener("click", onTargetClick, true);
|
|
198
201
|
document.addEventListener("contextmenu", onTargetClick, true);
|
|
199
202
|
frame ||= requestAnimationFrame(track);
|
|
@@ -226,28 +229,42 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
226
229
|
const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 text-sm text-ink outline-none`;
|
|
227
230
|
return (_ctx, _cache) => {
|
|
228
231
|
return openBlock(), createBlock(Teleport, { to: "body" }, [
|
|
229
|
-
|
|
232
|
+
createVNode(Transition, {
|
|
233
|
+
"enter-active-class": "transition-opacity duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
234
|
+
"leave-active-class": "transition-opacity duration-[var(--duration-medium)] ease-out motion-reduce:transition-none",
|
|
235
|
+
"enter-from-class": "opacity-0",
|
|
236
|
+
"leave-to-class": "opacity-0"
|
|
237
|
+
}, {
|
|
238
|
+
default: withCtx(() => [
|
|
239
|
+
open.value ? (openBlock(), createElementBlock("div", {
|
|
240
|
+
key: 0,
|
|
241
|
+
class: "lp-tour pointer-events-none fixed inset-0 z-(--z-tour)",
|
|
242
|
+
onPointerdown: _cache[0] || (_cache[0] = withModifiers(() => {
|
|
243
|
+
}, ["stop"]))
|
|
244
|
+
}, [
|
|
245
|
+
createElementVNode("div", {
|
|
246
|
+
class: "lp-scrim absolute inset-0 transition-[clip-path] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
247
|
+
style: normalizeStyle({ clipPath: clip.value })
|
|
248
|
+
}, null, 4),
|
|
249
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(panels.value, (panel, i) => {
|
|
250
|
+
return openBlock(), createElementBlock("div", {
|
|
251
|
+
key: i,
|
|
252
|
+
class: "pointer-events-auto absolute left-0 top-0",
|
|
253
|
+
style: normalizeStyle({ transform: `translate(${panel.x}px, ${panel.y}px)`, width: `${panel.width}px`, height: `${panel.height}px` })
|
|
254
|
+
}, null, 4);
|
|
255
|
+
}), 128)),
|
|
256
|
+
hole.value ? (openBlock(), createElementBlock("div", {
|
|
257
|
+
key: 0,
|
|
258
|
+
class: normalizeClass(["absolute left-0 top-0 rounded-control ring-2 ring-brand transition-[transform,width,height] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none", step.value?.try ? "pointer-events-none animate-pulse" : "pointer-events-auto"]),
|
|
259
|
+
style: normalizeStyle({ transform: `translate(${hole.value.x}px, ${hole.value.y}px)`, width: `${hole.value.width}px`, height: `${hole.value.height}px` })
|
|
260
|
+
}, null, 6)) : createCommentVNode("", true)
|
|
261
|
+
], 32)) : createCommentVNode("", true)
|
|
262
|
+
]),
|
|
263
|
+
_: 1
|
|
264
|
+
}),
|
|
265
|
+
unref(wide) ? (openBlock(), createBlock(unref(PopoverRoot), {
|
|
230
266
|
key: 0,
|
|
231
|
-
|
|
232
|
-
onPointerdown: _cache[0] || (_cache[0] = withModifiers(() => {
|
|
233
|
-
}, ["stop"]))
|
|
234
|
-
}, [
|
|
235
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(panels.value, (panel, i) => {
|
|
236
|
-
return openBlock(), createElementBlock("div", {
|
|
237
|
-
key: i,
|
|
238
|
-
class: "lp-scrim pointer-events-auto absolute left-0 top-0 transition-[transform,width,height] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
239
|
-
style: normalizeStyle({ transform: `translate(${panel.x}px, ${panel.y}px)`, width: `${panel.width}px`, height: `${panel.height}px` })
|
|
240
|
-
}, null, 4);
|
|
241
|
-
}), 128)),
|
|
242
|
-
hole.value ? (openBlock(), createElementBlock("div", {
|
|
243
|
-
key: 0,
|
|
244
|
-
class: normalizeClass(["absolute left-0 top-0 rounded-control ring-2 ring-brand transition-[transform,width,height] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none", step.value?.try ? "pointer-events-none animate-pulse" : "pointer-events-auto"]),
|
|
245
|
-
style: normalizeStyle({ transform: `translate(${hole.value.x}px, ${hole.value.y}px)`, width: `${hole.value.width}px`, height: `${hole.value.height}px` })
|
|
246
|
-
}, null, 6)) : createCommentVNode("", true)
|
|
247
|
-
], 32)) : createCommentVNode("", true),
|
|
248
|
-
open.value && unref(wide) ? (openBlock(), createBlock(unref(PopoverRoot), {
|
|
249
|
-
key: 1,
|
|
250
|
-
open: ready.value
|
|
267
|
+
open: open.value && ready.value
|
|
251
268
|
}, {
|
|
252
269
|
default: withCtx(() => [
|
|
253
270
|
createVNode(unref(PopoverAnchor), { reference }),
|
|
@@ -259,7 +276,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
259
276
|
"collision-padding": 12,
|
|
260
277
|
"update-position-strategy": "always",
|
|
261
278
|
"trap-focus": false,
|
|
262
|
-
class: normalizeClass([CARD, "z-[calc(var(--z-tour)+1)] data-[state=open]:animate-[popover-in_var(--duration-medium)_var(--ease-emphasized)] motion-reduce:animate-none"]),
|
|
279
|
+
class: normalizeClass([CARD, "z-[calc(var(--z-tour)+1)] data-[state=open]:animate-[popover-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[popover-out_var(--duration-fast)_ease-out] motion-reduce:animate-none"]),
|
|
263
280
|
role: "dialog",
|
|
264
281
|
"aria-label": step.value?.title,
|
|
265
282
|
onPointerdown: _cache[1] || (_cache[1] = withModifiers(() => {
|
|
@@ -300,34 +317,39 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
300
317
|
]),
|
|
301
318
|
_: 3
|
|
302
319
|
}, 8, ["open"])) : createCommentVNode("", true),
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
320
|
+
createVNode(Transition, { "leave-active-class": "animate-[rise-out_var(--duration-fast)_ease-out] motion-reduce:animate-none" }, {
|
|
321
|
+
default: withCtx(() => [
|
|
322
|
+
open.value && !unref(wide) && ready.value ? (openBlock(), createElementBlock("div", {
|
|
323
|
+
key: 0,
|
|
324
|
+
class: normalizeClass([CARD, "pointer-events-auto fixed inset-x-3 bottom-3 z-[calc(var(--z-tour)+1)] w-auto animate-[rise-in_var(--duration-medium)_var(--ease-emphasized)] motion-reduce:animate-none"]),
|
|
325
|
+
role: "dialog",
|
|
326
|
+
"aria-label": step.value?.title,
|
|
327
|
+
onPointerdown: _cache[5] || (_cache[5] = withModifiers(() => {
|
|
328
|
+
}, ["stop"]))
|
|
329
|
+
}, [
|
|
330
|
+
renderSlot(_ctx.$slots, "card", {
|
|
331
|
+
step: step.value,
|
|
332
|
+
index: index.value,
|
|
333
|
+
next,
|
|
334
|
+
back,
|
|
335
|
+
dismiss
|
|
336
|
+
}, () => [
|
|
337
|
+
createVNode(_sfc_main$1, {
|
|
338
|
+
step: step.value,
|
|
339
|
+
index: index.value,
|
|
340
|
+
last: last.value,
|
|
341
|
+
progress: progress.value,
|
|
342
|
+
labels: unref(labels),
|
|
343
|
+
onNext: next,
|
|
344
|
+
onBack: back,
|
|
345
|
+
onPerform: perform,
|
|
346
|
+
onDismiss: dismiss
|
|
347
|
+
}, null, 8, ["step", "index", "last", "progress", "labels"])
|
|
348
|
+
])
|
|
349
|
+
], 42, _hoisted_1)) : createCommentVNode("", true)
|
|
350
|
+
]),
|
|
351
|
+
_: 3
|
|
352
|
+
})
|
|
331
353
|
]);
|
|
332
354
|
};
|
|
333
355
|
}
|
|
@@ -34,6 +34,12 @@ type __VLS_Props = {
|
|
|
34
34
|
* of the page from the start should not be shouted on load.
|
|
35
35
|
*/
|
|
36
36
|
live?: "assertive" | "polite" | false;
|
|
37
|
+
/**
|
|
38
|
+
* Work is under way: the icon becomes a spinning loader (unless `icon` is
|
|
39
|
+
* given, which then spins instead) and the alert is announced politely.
|
|
40
|
+
* A loader drawn as a still icon read as a frozen page.
|
|
41
|
+
*/
|
|
42
|
+
busy?: boolean;
|
|
37
43
|
};
|
|
38
44
|
declare var __VLS_6: {};
|
|
39
45
|
type __VLS_Slots = {} & {
|
|
@@ -42,6 +48,7 @@ type __VLS_Slots = {} & {
|
|
|
42
48
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
43
49
|
variant: "info" | "danger" | "success" | "warning";
|
|
44
50
|
live: "assertive" | "polite" | false;
|
|
51
|
+
busy: boolean;
|
|
45
52
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
46
53
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
47
54
|
declare const _default: typeof __VLS_export;
|
|
@@ -8,6 +8,15 @@ type Size = {
|
|
|
8
8
|
export declare function holeOf(target: Box, padding: number, viewport: Size): Box;
|
|
9
9
|
/** The four dim panels around a hole: top, bottom, left, right. */
|
|
10
10
|
export declare function panelsAround(hole: Box, viewport: Size): Box[];
|
|
11
|
+
/**
|
|
12
|
+
* The dimmed, blurred layer as one clip: the whole viewport with the hole cut
|
|
13
|
+
* out (evenodd), corners rounded like the ring drawn around it. One layer
|
|
14
|
+
* instead of the four panels, because each panel blurred the page on its own
|
|
15
|
+
* and the blur's edges showed as seams where they met. The point count never
|
|
16
|
+
* changes, so the clip eases between steps on every engine; with no target
|
|
17
|
+
* the hole shrinks to a dot in the middle.
|
|
18
|
+
*/
|
|
19
|
+
export declare function holeClip(hole: Box | null, viewport: Size, radius: number): string;
|
|
11
20
|
/**
|
|
12
21
|
* Open the context menu of an element as a right-click would, at its centre.
|
|
13
22
|
* Lets a tour step show a row's menu without the user hunting for it; the menu
|
package/dist/components/tour.js
CHANGED
|
@@ -14,6 +14,27 @@ function panelsAround(hole, viewport) {
|
|
|
14
14
|
{ x: hole.x + hole.width, y: hole.y, width: Math.max(0, viewport.width - hole.x - hole.width), height: hole.height }
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
|
+
const CORNER_STEPS = 6;
|
|
18
|
+
function holeClip(hole, viewport, radius) {
|
|
19
|
+
const h = hole ?? { x: viewport.width / 2, y: viewport.height / 2, width: 0, height: 0 };
|
|
20
|
+
const r = Math.max(0, Math.min(radius, h.width / 2, h.height / 2));
|
|
21
|
+
const px = (n) => `${Math.round(n * 100) / 100}px`;
|
|
22
|
+
const corners = [
|
|
23
|
+
[h.x + h.width - r, h.y + r, -90],
|
|
24
|
+
[h.x + h.width - r, h.y + h.height - r, 0],
|
|
25
|
+
[h.x + r, h.y + h.height - r, 90],
|
|
26
|
+
[h.x + r, h.y + r, 180]
|
|
27
|
+
];
|
|
28
|
+
const points = [];
|
|
29
|
+
for (const [cx, cy, start] of corners) {
|
|
30
|
+
for (let i = 0; i <= CORNER_STEPS; i++) {
|
|
31
|
+
const angle = (start + 90 * i / CORNER_STEPS) * Math.PI / 180;
|
|
32
|
+
points.push(`${px(cx + r * Math.cos(angle))} ${px(cy + r * Math.sin(angle))}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const outer = [`0px 0px`, `${px(viewport.width)} 0px`, `${px(viewport.width)} ${px(viewport.height)}`, `0px ${px(viewport.height)}`, `0px 0px`];
|
|
36
|
+
return `polygon(evenodd, ${[...outer, ...points, points[0]].join(", ")})`;
|
|
37
|
+
}
|
|
17
38
|
function openContextMenu(el) {
|
|
18
39
|
const box = el.getBoundingClientRect();
|
|
19
40
|
el.dispatchEvent(
|
|
@@ -32,6 +53,7 @@ function openContextMenu(el) {
|
|
|
32
53
|
};
|
|
33
54
|
}
|
|
34
55
|
export {
|
|
56
|
+
holeClip,
|
|
35
57
|
holeOf,
|
|
36
58
|
openContextMenu,
|
|
37
59
|
panelsAround
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { _ as _2 } from "./chunks/LpAddressInput-CzdZguBo.js";
|
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-DybIlWnF.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpActionBar-BsSp66S_.js";
|
|
6
|
-
import { _ as _5 } from "./chunks/LpAlert
|
|
6
|
+
import { _ as _5 } from "./chunks/LpAlert-BAOujnXy.js";
|
|
7
7
|
import { _ as _6 } from "./chunks/LpAppShell-BXMNDEpF.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAutocomplete-D9mY5Tqk.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpAvatar-K6MOMfgn.js";
|
|
@@ -105,10 +105,10 @@ import { _ as _75 } from "./chunks/LpThemeSwitcher-ZFFftBQn.js";
|
|
|
105
105
|
import { _ as _76 } from "./chunks/LpTilt-B7KHX1zH.js";
|
|
106
106
|
import { _ as _77 } from "./chunks/LpToaster-D3OisETH.js";
|
|
107
107
|
import { _ as _78 } from "./chunks/LpTooltip-suFCM34F.js";
|
|
108
|
-
import { _ as _79 } from "./chunks/LpTour-
|
|
108
|
+
import { _ as _79 } from "./chunks/LpTour-ByaL36fN.js";
|
|
109
109
|
import { holeOf, openContextMenu, panelsAround } from "./components/tour.js";
|
|
110
110
|
import { _ as _80 } from "./chunks/LpHintLayer-DsclGYcD.js";
|
|
111
|
-
import { _ as _81 } from "./chunks/LpHints-
|
|
111
|
+
import { _ as _81 } from "./chunks/LpHints-B_85eSmm.js";
|
|
112
112
|
import { HINT_SIGNALS, contextMenuTip, createHintSystem, hintKey, hintSummary, localHintStorage, paletteTip, provideHintSystem, runningHintSystem, useHintSystem } from "./composables/useHintSystem.js";
|
|
113
113
|
import { _ as _82 } from "./chunks/LpTooltipProvider-CftjV7Xc.js";
|
|
114
114
|
import { _ as _83 } from "./chunks/LpTopologyCanvas-6ozNrQKh.js";
|
package/package.json
CHANGED
|
@@ -45,23 +45,34 @@ const props = withDefaults(
|
|
|
45
45
|
* of the page from the start should not be shouted on load.
|
|
46
46
|
*/
|
|
47
47
|
live?: "assertive" | "polite" | false
|
|
48
|
+
/**
|
|
49
|
+
* Work is under way: the icon becomes a spinning loader (unless `icon` is
|
|
50
|
+
* given, which then spins instead) and the alert is announced politely.
|
|
51
|
+
* A loader drawn as a still icon read as a frozen page.
|
|
52
|
+
*/
|
|
53
|
+
busy?: boolean
|
|
48
54
|
}>(),
|
|
49
|
-
{ variant: "info", live: undefined },
|
|
55
|
+
{ variant: "info", live: undefined, busy: false },
|
|
50
56
|
)
|
|
51
57
|
|
|
52
58
|
const role = computed(() => {
|
|
53
|
-
const live = props.live ?? (props.variant === "danger" ? "assertive" : false)
|
|
59
|
+
const live = props.live ?? (props.variant === "danger" ? "assertive" : props.busy ? "polite" : false)
|
|
54
60
|
return live === "assertive" ? "alert" : live === "polite" ? "status" : undefined
|
|
55
61
|
})
|
|
56
62
|
|
|
57
63
|
const classes = computed(() => alert({ variant: props.variant }))
|
|
58
|
-
const resolvedIcon = computed(() => props.icon ?? ICONS[props.variant ?? "info"])
|
|
64
|
+
const resolvedIcon = computed(() => props.icon ?? (props.busy ? "lucide:loader-circle" : ICONS[props.variant ?? "info"]))
|
|
59
65
|
const iconTone = computed(() => ICON_TONE[props.variant ?? "info"])
|
|
60
66
|
</script>
|
|
61
67
|
|
|
62
68
|
<template>
|
|
63
|
-
<div :class="classes" :role="role">
|
|
64
|
-
<LpIcon
|
|
69
|
+
<div :class="classes" :role="role" :aria-busy="busy || undefined">
|
|
70
|
+
<LpIcon
|
|
71
|
+
:name="resolvedIcon"
|
|
72
|
+
:size="18"
|
|
73
|
+
:class="['mt-0.5 shrink-0', iconTone, busy ? 'animate-spin motion-reduce:animate-none' : '']"
|
|
74
|
+
aria-hidden="true"
|
|
75
|
+
/>
|
|
65
76
|
<div class="min-w-0">
|
|
66
77
|
<p v-if="title" class="font-semibold text-ink">{{ title }}</p>
|
|
67
78
|
<div :class="['text-muted-strong', title ? 'mt-0.5' : '']">
|
|
@@ -37,9 +37,11 @@ export interface TourStep {
|
|
|
37
37
|
* scrim, a card beside it says what it is, and the user steps through with the
|
|
38
38
|
* buttons or the arrow keys.
|
|
39
39
|
*
|
|
40
|
-
* The
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* The dim is one blurred layer clipped around the hole (`holeClip`), so the
|
|
41
|
+
* blur has no seams; four transparent panels around the hole take the clicks,
|
|
42
|
+
* so the target stays clickable (a `try` step waits for exactly that click).
|
|
43
|
+
* Both glide between targets on any engine, WebKitGTK included. Closing fades
|
|
44
|
+
* the whole layer out instead of dropping it. The card is a reka
|
|
43
45
|
* popover anchored to the hole, so flipping and sliding to stay on screen is
|
|
44
46
|
* floating-ui's job; on a phone it becomes a sheet at the bottom instead.
|
|
45
47
|
*
|
|
@@ -59,7 +61,7 @@ import { scrollToElement } from "../composables/scroll"
|
|
|
59
61
|
import { PANEL_SURFACE } from "./dropdown"
|
|
60
62
|
import TourCard from "./TourCard.vue"
|
|
61
63
|
import type { Box } from "../composables/spatialNav"
|
|
62
|
-
import { holeOf, panelsAround } from "./tour"
|
|
64
|
+
import { holeClip, holeOf, panelsAround } from "./tour"
|
|
63
65
|
|
|
64
66
|
const props = withDefaults(
|
|
65
67
|
defineProps<{
|
|
@@ -113,6 +115,8 @@ const panels = computed(() =>
|
|
|
113
115
|
? panelsAround(hole.value, viewport.value)
|
|
114
116
|
: [{ x: 0, y: 0, width: viewport.value.width, height: viewport.value.height }],
|
|
115
117
|
)
|
|
118
|
+
const radius = ref(8)
|
|
119
|
+
const clip = computed(() => holeClip(hole.value, viewport.value, radius.value))
|
|
116
120
|
const reference = {
|
|
117
121
|
getBoundingClientRect: () => {
|
|
118
122
|
const h = hole.value
|
|
@@ -244,7 +248,6 @@ function end() {
|
|
|
244
248
|
cancelAnimationFrame(frame)
|
|
245
249
|
frame = 0
|
|
246
250
|
target.value = null
|
|
247
|
-
box.value = null
|
|
248
251
|
runAll(cleanups)
|
|
249
252
|
}
|
|
250
253
|
|
|
@@ -269,6 +272,8 @@ watch(
|
|
|
269
272
|
(value) => {
|
|
270
273
|
if (value) {
|
|
271
274
|
direction = 1
|
|
275
|
+
box.value = null
|
|
276
|
+
radius.value = parseFloat(getComputedStyle(document.documentElement).getPropertyValue("--radius-control")) || 8
|
|
272
277
|
document.addEventListener("click", onTargetClick, true)
|
|
273
278
|
document.addEventListener("contextmenu", onTargetClick, true)
|
|
274
279
|
frame ||= requestAnimationFrame(track)
|
|
@@ -307,11 +312,21 @@ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 te
|
|
|
307
312
|
|
|
308
313
|
<template>
|
|
309
314
|
<Teleport to="body">
|
|
315
|
+
<Transition
|
|
316
|
+
enter-active-class="transition-opacity duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none"
|
|
317
|
+
leave-active-class="transition-opacity duration-[var(--duration-medium)] ease-out motion-reduce:transition-none"
|
|
318
|
+
enter-from-class="opacity-0"
|
|
319
|
+
leave-to-class="opacity-0"
|
|
320
|
+
>
|
|
310
321
|
<div v-if="open" class="lp-tour pointer-events-none fixed inset-0 z-(--z-tour)" @pointerdown.stop>
|
|
322
|
+
<div
|
|
323
|
+
class="lp-scrim absolute inset-0 transition-[clip-path] duration-[var(--duration-medium)] ease-[var(--ease-emphasized)] motion-reduce:transition-none"
|
|
324
|
+
:style="{ clipPath: clip }"
|
|
325
|
+
/>
|
|
311
326
|
<div
|
|
312
327
|
v-for="(panel, i) in panels"
|
|
313
328
|
:key="i"
|
|
314
|
-
class="
|
|
329
|
+
class="pointer-events-auto absolute left-0 top-0"
|
|
315
330
|
:style="{ transform: `translate(${panel.x}px, ${panel.y}px)`, width: `${panel.width}px`, height: `${panel.height}px` }"
|
|
316
331
|
/>
|
|
317
332
|
<div
|
|
@@ -321,8 +336,9 @@ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 te
|
|
|
321
336
|
:style="{ transform: `translate(${hole.x}px, ${hole.y}px)`, width: `${hole.width}px`, height: `${hole.height}px` }"
|
|
322
337
|
/>
|
|
323
338
|
</div>
|
|
339
|
+
</Transition>
|
|
324
340
|
|
|
325
|
-
<PopoverRoot v-if="
|
|
341
|
+
<PopoverRoot v-if="wide" :open="open && ready">
|
|
326
342
|
<PopoverAnchor :reference="reference" />
|
|
327
343
|
<PopoverPortal>
|
|
328
344
|
<PopoverContent
|
|
@@ -331,7 +347,7 @@ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 te
|
|
|
331
347
|
:collision-padding="12"
|
|
332
348
|
update-position-strategy="always"
|
|
333
349
|
:trap-focus="false"
|
|
334
|
-
:class="[CARD, 'z-[calc(var(--z-tour)+1)] data-[state=open]:animate-[popover-in_var(--duration-medium)_var(--ease-emphasized)] motion-reduce:animate-none']"
|
|
350
|
+
:class="[CARD, 'z-[calc(var(--z-tour)+1)] data-[state=open]:animate-[popover-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[popover-out_var(--duration-fast)_ease-out] motion-reduce:animate-none']"
|
|
335
351
|
role="dialog"
|
|
336
352
|
:aria-label="step?.title"
|
|
337
353
|
@pointerdown.stop
|
|
@@ -346,6 +362,7 @@ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 te
|
|
|
346
362
|
</PopoverPortal>
|
|
347
363
|
</PopoverRoot>
|
|
348
364
|
|
|
365
|
+
<Transition leave-active-class="animate-[rise-out_var(--duration-fast)_ease-out] motion-reduce:animate-none">
|
|
349
366
|
<div
|
|
350
367
|
v-if="open && !wide && ready"
|
|
351
368
|
:class="[CARD, 'pointer-events-auto fixed inset-x-3 bottom-3 z-[calc(var(--z-tour)+1)] w-auto animate-[rise-in_var(--duration-medium)_var(--ease-emphasized)] motion-reduce:animate-none']"
|
|
@@ -357,5 +374,6 @@ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 te
|
|
|
357
374
|
<TourCard :step="step" :index="index" :last="last" :progress="progress" :labels="labels" @next="next" @back="back" @perform="perform" @dismiss="dismiss" />
|
|
358
375
|
</slot>
|
|
359
376
|
</div>
|
|
377
|
+
</Transition>
|
|
360
378
|
</Teleport>
|
|
361
379
|
</template>
|
package/src/components/tour.ts
CHANGED
|
@@ -29,6 +29,37 @@ export function panelsAround(hole: Box, viewport: Size): Box[] {
|
|
|
29
29
|
]
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
const CORNER_STEPS = 6
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The dimmed, blurred layer as one clip: the whole viewport with the hole cut
|
|
36
|
+
* out (evenodd), corners rounded like the ring drawn around it. One layer
|
|
37
|
+
* instead of the four panels, because each panel blurred the page on its own
|
|
38
|
+
* and the blur's edges showed as seams where they met. The point count never
|
|
39
|
+
* changes, so the clip eases between steps on every engine; with no target
|
|
40
|
+
* the hole shrinks to a dot in the middle.
|
|
41
|
+
*/
|
|
42
|
+
export function holeClip(hole: Box | null, viewport: Size, radius: number): string {
|
|
43
|
+
const h = hole ?? { x: viewport.width / 2, y: viewport.height / 2, width: 0, height: 0 }
|
|
44
|
+
const r = Math.max(0, Math.min(radius, h.width / 2, h.height / 2))
|
|
45
|
+
const px = (n: number) => `${Math.round(n * 100) / 100}px`
|
|
46
|
+
const corners: [number, number, number][] = [
|
|
47
|
+
[h.x + h.width - r, h.y + r, -90],
|
|
48
|
+
[h.x + h.width - r, h.y + h.height - r, 0],
|
|
49
|
+
[h.x + r, h.y + h.height - r, 90],
|
|
50
|
+
[h.x + r, h.y + r, 180],
|
|
51
|
+
]
|
|
52
|
+
const points: string[] = []
|
|
53
|
+
for (const [cx, cy, start] of corners) {
|
|
54
|
+
for (let i = 0; i <= CORNER_STEPS; i++) {
|
|
55
|
+
const angle = ((start + (90 * i) / CORNER_STEPS) * Math.PI) / 180
|
|
56
|
+
points.push(`${px(cx + r * Math.cos(angle))} ${px(cy + r * Math.sin(angle))}`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const outer = [`0px 0px`, `${px(viewport.width)} 0px`, `${px(viewport.width)} ${px(viewport.height)}`, `0px ${px(viewport.height)}`, `0px 0px`]
|
|
60
|
+
return `polygon(evenodd, ${[...outer, ...points, points[0]].join(", ")})`
|
|
61
|
+
}
|
|
62
|
+
|
|
32
63
|
/**
|
|
33
64
|
* Open the context menu of an element as a right-click would, at its centre.
|
|
34
65
|
* Lets a tour step show a row's menu without the user hunting for it; the menu
|