@leavepulse/ui 0.56.0 → 0.57.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.
Files changed (57) hide show
  1. package/dist/chunks/{LpAppShell-BEGjSqFl.js → LpAppShell-Bl1Ch68N.js} +1 -1
  2. package/dist/chunks/{LpDateRangePicker-Drd7EbIz.js → LpDateRangePicker-CxV5jW7p.js} +4 -13
  3. package/dist/chunks/{LpDrawer-CBFVD624.js → LpDrawer-D9lLOC0C.js} +4 -6
  4. package/dist/chunks/LpHintLayer-CI1oZ9Pd.js +154 -0
  5. package/dist/chunks/{LpModalNav-DXBilXr0.js → LpModalNav-DXyAyhQl.js} +1 -1
  6. package/dist/chunks/{LpPie-B7ti5qCH.js → LpPie-CXka8Jvx.js} +1 -1
  7. package/dist/chunks/{LpSidebar-C2oolgwy.js → LpSidebar-hZ8D1cVr.js} +1 -1
  8. package/dist/chunks/{LpTableOfContents-BUcAfGPQ.js → LpTableOfContents-DYuqkByy.js} +2 -8
  9. package/dist/chunks/{LpTooltip-CXVKcOXT.js → LpTooltip-suFCM34F.js} +2 -2
  10. package/dist/chunks/LpTour-C1TC2GyY.js +315 -0
  11. package/dist/chunks/{LpUptimeBar-ugcv25xj.js → LpUptimeBar-B4ltCfno.js} +1 -1
  12. package/dist/chunks/TourCard-BQS3ztqj.js +100 -0
  13. package/dist/component-names.d.ts +1 -1
  14. package/dist/components/LpAppShell.vue.js +1 -1
  15. package/dist/components/LpDateRangePicker.vue.js +1 -1
  16. package/dist/components/LpDrawer.vue.js +1 -1
  17. package/dist/components/LpHintLayer.vue.d.ts +33 -0
  18. package/dist/components/LpHintLayer.vue.js +4 -0
  19. package/dist/components/LpModalNav.vue.js +1 -1
  20. package/dist/components/LpPie.vue.js +1 -1
  21. package/dist/components/LpSidebar.vue.js +1 -1
  22. package/dist/components/LpTableOfContents.vue.js +1 -1
  23. package/dist/components/LpTooltip.vue.js +1 -1
  24. package/dist/components/LpTour.vue.d.ts +98 -0
  25. package/dist/components/LpTour.vue.js +4 -0
  26. package/dist/components/LpUptimeBar.vue.js +1 -1
  27. package/dist/components/TourCard.vue.d.ts +28 -0
  28. package/dist/components/TourCard.vue.js +4 -0
  29. package/dist/components/tour.d.ts +21 -0
  30. package/dist/components/tour.js +38 -0
  31. package/dist/composables/scroll.d.ts +17 -0
  32. package/dist/composables/scroll.js +29 -0
  33. package/dist/composables/useGamepadNav.js +2 -7
  34. package/dist/composables/useHints.d.ts +18 -0
  35. package/dist/composables/useHints.js +120 -0
  36. package/dist/composables/useMediaQuery.d.ts +2 -0
  37. package/dist/composables/useMediaQuery.js +18 -0
  38. package/dist/composables/usePaintSelect.js +1 -7
  39. package/dist/index.d.ts +10 -0
  40. package/dist/index.js +32 -16
  41. package/package.json +1 -1
  42. package/src/component-names.ts +2 -0
  43. package/src/components/LpDateRangePicker.vue +3 -12
  44. package/src/components/LpDrawer.vue +4 -7
  45. package/src/components/LpHintLayer.vue +148 -0
  46. package/src/components/LpTableOfContents.vue +2 -14
  47. package/src/components/LpTooltip.vue +2 -4
  48. package/src/components/LpTour.vue +338 -0
  49. package/src/components/TourCard.vue +41 -0
  50. package/src/components/tour.ts +57 -0
  51. package/src/composables/scroll.ts +54 -0
  52. package/src/composables/useGamepadNav.ts +2 -7
  53. package/src/composables/useHints.ts +151 -0
  54. package/src/composables/useMediaQuery.ts +20 -0
  55. package/src/composables/usePaintSelect.ts +1 -8
  56. package/src/index.ts +10 -0
  57. package/src/tokens/tokens.css +1 -0
@@ -4,7 +4,7 @@ import { _ as _sfc_main$2 } from "./LpButton-BdfZFNqi.js";
4
4
  import { _ as _sfc_main$3 } from "./LpIcon-DYtaid0K.js";
5
5
  import { _ as _sfc_main$5 } from "./LpScrollArea-Hz3-JWUG.js";
6
6
  import { _ as _sfc_main$4 } from "./LpShift-DXNsauQZ.js";
7
- import { _ as _sfc_main$1 } from "./LpSidebar-C2oolgwy.js";
7
+ import { _ as _sfc_main$1 } from "./LpSidebar-hZ8D1cVr.js";
8
8
  import { useMergedAttrs } from "../composables/useMergedClass.js";
9
9
  const _hoisted_1 = ["href"];
10
10
  const _hoisted_2 = { class: "flex min-w-0 flex-1 flex-col" };
@@ -1,9 +1,10 @@
1
- import { defineComponent, ref, computed, onMounted, onBeforeUnmount, useAttrs, openBlock, createElementBlock, normalizeStyle, unref, normalizeClass, createVNode, withCtx, createElementVNode, mergeProps, createCommentVNode, Fragment, renderList, toDisplayString } from "vue";
1
+ import { defineComponent, ref, computed, useAttrs, openBlock, createElementBlock, normalizeStyle, unref, normalizeClass, createVNode, withCtx, createElementVNode, mergeProps, createCommentVNode, Fragment, renderList, toDisplayString } from "vue";
2
2
  import { PopoverRoot, PopoverTrigger, PopoverPortal, PopoverContent } from "reka-ui";
3
3
  import { useFieldControl } from "../composables/useField.js";
4
4
  import { useLocale } from "../composables/useLocale.js";
5
5
  import { useLabels } from "../composables/useLpConfig.js";
6
6
  import { mergeClass } from "../composables/useMergedClass.js";
7
+ import { useMediaQuery } from "../composables/useMediaQuery.js";
7
8
  import { normalizeRange, formatRange, defaultRangePresets, sameRange, clampRange } from "../components/dateRange.js";
8
9
  import { POPOVER_PANEL } from "../components/dropdown.js";
9
10
  import { _ as _sfc_main$1 } from "./LpIcon-DYtaid0K.js";
@@ -75,17 +76,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
75
76
  function presetFits(preset) {
76
77
  return !!clampRange(preset.range(), props.min, props.max);
77
78
  }
78
- const wide = ref(false);
79
- let query;
80
- const onQuery = () => {
81
- wide.value = !!query?.matches;
82
- };
83
- onMounted(() => {
84
- query = window.matchMedia("(min-width: 640px)");
85
- onQuery();
86
- query.addEventListener("change", onQuery);
87
- });
88
- onBeforeUnmount(() => query?.removeEventListener("change", onQuery));
79
+ const wide = useMediaQuery("(min-width: 640px)");
89
80
  const shellSize = {
90
81
  sm: "h-(--size-control-sm) text-xs",
91
82
  md: "h-(--size-control-md) text-sm",
@@ -175,7 +166,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
175
166
  "is-disabled": __props.isDisabled,
176
167
  "is-date-unavailable": __props.isDateUnavailable,
177
168
  "maximum-days": __props.maximumDays,
178
- months: wide.value ? 2 : 1,
169
+ months: unref(wide) ? 2 : 1,
179
170
  locale: unref(locale),
180
171
  class: "border-0 bg-transparent",
181
172
  "onUpdate:modelValue": pick
@@ -1,6 +1,7 @@
1
1
  import { ref, defineComponent, computed, watch, onBeforeUnmount, useSlots, openBlock, createElementBlock, Fragment, withDirectives, createElementVNode, normalizeStyle, unref, normalizeClass, vShow, toDisplayString, createCommentVNode, renderSlot, createVNode, withCtx, mergeProps, createBlock, createTextVNode } from "vue";
2
2
  import { DrawerRoot, DrawerPortal, DrawerOverlay, DrawerContent, DrawerTitle, DrawerHandle, DrawerDescription, DrawerClose } from "vaul-vue";
3
3
  import { CLOSE_ICON } from "../components/dropdown.js";
4
+ import { prefersReducedMotion } from "../composables/easing.js";
4
5
  import { useGlass } from "../composables/useGlass.js";
5
6
  import { useLabels } from "../composables/useLpConfig.js";
6
7
  import { _ as _sfc_main$1 } from "./LpIcon-DYtaid0K.js";
@@ -169,9 +170,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
169
170
  }
170
171
  let track = null;
171
172
  const owner = /* @__PURE__ */ Symbol("edge-pull");
172
- function reducedMotion() {
173
- return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
174
- }
175
173
  function scrollsWithPull(target) {
176
174
  let el = target instanceof Element ? target : null;
177
175
  const pullsRight = dir.value === "left";
@@ -246,7 +244,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
246
244
  peek.value = 0;
247
245
  }, 60)
248
246
  );
249
- }, reducedMotion() ? 0 : 180)
247
+ }, prefersReducedMotion() ? 0 : 180)
250
248
  );
251
249
  } else {
252
250
  peek.value = 0;
@@ -287,7 +285,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
287
285
  const axis = dir.value === "right" ? off : -off;
288
286
  return {
289
287
  transform: `translateX(${axis}%)`,
290
- transition: peeking.value || reducedMotion() ? "none" : "transform 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
288
+ transition: peeking.value || prefersReducedMotion() ? "none" : "transform 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
291
289
  pointerEvents: "none"
292
290
  };
293
291
  });
@@ -296,7 +294,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
296
294
  __props.edgeOpen && isHorizontal.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
297
295
  withDirectives(createElementVNode("div", {
298
296
  class: normalizeClass(["lp-scrim pointer-events-none fixed inset-0", { "lp-scrim--light": unref(frosted), "lp-scrim--nested": unref(layer).nested }]),
299
- style: normalizeStyle({ zIndex: unref(layer).scrim, opacity: peek.value, transition: peeking.value || reducedMotion() ? "none" : "opacity 0.18s ease-out" })
297
+ style: normalizeStyle({ zIndex: unref(layer).scrim, opacity: peek.value, transition: peeking.value || unref(prefersReducedMotion)() ? "none" : "opacity 0.18s ease-out" })
300
298
  }, null, 6), [
301
299
  [vShow, peeking.value || peek.value > 0]
302
300
  ]),
@@ -0,0 +1,154 @@
1
+ import { defineComponent, computed, ref, shallowRef, watch, onMounted, onBeforeUnmount, openBlock, createBlock, Teleport, createElementBlock, Fragment, renderList, unref, normalizeStyle, createElementVNode, createVNode, withCtx, createCommentVNode, normalizeClass, toDisplayString, createTextVNode } from "vue";
2
+ import { PopoverRoot, PopoverAnchor, PopoverPortal, PopoverContent } from "reka-ui";
3
+ import { hints } from "../composables/useHints.js";
4
+ import { useLabels } from "../composables/useLpConfig.js";
5
+ import { PANEL_SURFACE, POPOVER_ANIM } from "../components/dropdown.js";
6
+ import { _ as _sfc_main$1 } from "./LpButton-BdfZFNqi.js";
7
+ const _hoisted_1 = ["aria-label", "onPointerenter", "onFocus", "onClick"];
8
+ const _hoisted_2 = { class: "flex items-center gap-2 font-medium" };
9
+ const _hoisted_3 = { class: "rounded-full bg-brand/15 px-1.5 text-[11px] text-brand" };
10
+ const _hoisted_4 = {
11
+ key: 0,
12
+ class: "mt-1.5 text-muted"
13
+ };
14
+ const _hoisted_5 = { class: "mt-3 flex justify-end" };
15
+ const _sfc_main = /* @__PURE__ */ defineComponent({
16
+ __name: "LpHintLayer",
17
+ props: {
18
+ fresh: {},
19
+ notes: {},
20
+ limit: { default: 3 },
21
+ labels: {}
22
+ },
23
+ emits: ["read"],
24
+ setup(__props, { emit: __emit }) {
25
+ const props = __props;
26
+ const emit = __emit;
27
+ const DEFAULT_LABELS = { gotIt: "Got it", badge: "New" };
28
+ const labels = useLabels("hintLayer", DEFAULT_LABELS, () => props.labels);
29
+ const OPEN_DELAY_MS = 350;
30
+ const shown = computed(() => props.fresh.filter((id) => hints.present(id) && props.notes[id]).slice(0, props.limit));
31
+ const spots = ref([]);
32
+ const openId = ref(null);
33
+ const anchor = shallowRef(null);
34
+ let frame = 0;
35
+ let timer;
36
+ function place() {
37
+ spots.value = shown.value.flatMap((id) => {
38
+ const el = hints.element(id);
39
+ if (!el) return [];
40
+ const r = el.getBoundingClientRect();
41
+ return r.width || r.height ? [{ id, x: r.right, y: r.top }] : [];
42
+ });
43
+ frame = shown.value.length ? requestAnimationFrame(place) : 0;
44
+ }
45
+ watch(
46
+ shown,
47
+ (ids) => {
48
+ if (ids.length && !frame) frame = requestAnimationFrame(place);
49
+ if (openId.value && !ids.includes(openId.value)) openId.value = null;
50
+ },
51
+ { immediate: true }
52
+ );
53
+ function reveal(id) {
54
+ clearTimeout(timer);
55
+ timer = setTimeout(() => {
56
+ if (!shown.value.includes(id)) return;
57
+ anchor.value = hints.element(id);
58
+ openId.value = anchor.value ? id : null;
59
+ }, OPEN_DELAY_MS);
60
+ }
61
+ function read() {
62
+ const id = openId.value;
63
+ openId.value = null;
64
+ if (id) emit("read", id);
65
+ }
66
+ let off = [];
67
+ onMounted(() => {
68
+ off = [
69
+ hints.on("hover", (id) => {
70
+ if (shown.value.includes(id)) reveal(id);
71
+ }),
72
+ hints.on("used", (id) => {
73
+ if (!shown.value.includes(id)) return;
74
+ clearTimeout(timer);
75
+ if (openId.value === id) openId.value = null;
76
+ emit("read", id);
77
+ })
78
+ ];
79
+ });
80
+ onBeforeUnmount(() => {
81
+ cancelAnimationFrame(frame);
82
+ clearTimeout(timer);
83
+ for (const stop of off) stop();
84
+ });
85
+ const note = computed(() => openId.value ? props.notes[openId.value] : void 0);
86
+ return (_ctx, _cache) => {
87
+ return openBlock(), createBlock(Teleport, { to: "body" }, [
88
+ (openBlock(true), createElementBlock(Fragment, null, renderList(spots.value, (spot) => {
89
+ return openBlock(), createElementBlock("button", {
90
+ key: spot.id,
91
+ type: "button",
92
+ class: "fixed z-(--z-popover) -translate-x-1/2 -translate-y-1/2 rounded-full p-1",
93
+ style: normalizeStyle({ left: `${spot.x}px`, top: `${spot.y}px` }),
94
+ "aria-label": `${unref(labels).badge}: ${__props.notes[spot.id]?.title ?? ""}`,
95
+ onPointerenter: ($event) => reveal(spot.id),
96
+ onFocus: ($event) => reveal(spot.id),
97
+ onClick: ($event) => reveal(spot.id)
98
+ }, [..._cache[1] || (_cache[1] = [
99
+ createElementVNode("span", { class: "relative flex size-2" }, [
100
+ createElementVNode("span", { class: "absolute inline-flex size-full animate-ping rounded-full bg-brand opacity-60 motion-reduce:animate-none" }),
101
+ createElementVNode("span", { class: "relative inline-flex size-2 rounded-full bg-brand ring-2 ring-surface" })
102
+ ], -1)
103
+ ])], 44, _hoisted_1);
104
+ }), 128)),
105
+ createVNode(unref(PopoverRoot), {
106
+ open: !!openId.value,
107
+ "onUpdate:open": _cache[0] || (_cache[0] = (value) => !value && (openId.value = null))
108
+ }, {
109
+ default: withCtx(() => [
110
+ anchor.value ? (openBlock(), createBlock(unref(PopoverAnchor), {
111
+ key: 0,
112
+ reference: anchor.value
113
+ }, null, 8, ["reference"])) : createCommentVNode("", true),
114
+ createVNode(unref(PopoverPortal), null, {
115
+ default: withCtx(() => [
116
+ createVNode(unref(PopoverContent), {
117
+ side: "bottom",
118
+ "side-offset": 8,
119
+ "collision-padding": 8,
120
+ class: normalizeClass([unref(PANEL_SURFACE), unref(POPOVER_ANIM), "z-(--z-popover) w-72 rounded-card p-3 text-sm text-ink outline-none"])
121
+ }, {
122
+ default: withCtx(() => [
123
+ createElementVNode("p", _hoisted_2, [
124
+ createElementVNode("span", _hoisted_3, toDisplayString(unref(labels).badge), 1),
125
+ createTextVNode(" " + toDisplayString(note.value?.title), 1)
126
+ ]),
127
+ note.value?.text ? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString(note.value.text), 1)) : createCommentVNode("", true),
128
+ createElementVNode("div", _hoisted_5, [
129
+ createVNode(_sfc_main$1, {
130
+ size: "sm",
131
+ onClick: read
132
+ }, {
133
+ default: withCtx(() => [
134
+ createTextVNode(toDisplayString(unref(labels).gotIt), 1)
135
+ ]),
136
+ _: 1
137
+ })
138
+ ])
139
+ ]),
140
+ _: 1
141
+ }, 8, ["class"])
142
+ ]),
143
+ _: 1
144
+ })
145
+ ]),
146
+ _: 1
147
+ }, 8, ["open"])
148
+ ]);
149
+ };
150
+ }
151
+ });
152
+ export {
153
+ _sfc_main as _
154
+ };
@@ -4,7 +4,7 @@ import { useLabels } from "../composables/useLpConfig.js";
4
4
  import { _ as _sfc_main$2 } from "./LpButton-BdfZFNqi.js";
5
5
  import { _ as _sfc_main$3 } from "./LpIcon-DYtaid0K.js";
6
6
  import { _ as _sfc_main$1 } from "./LpModal-JNa3B_Lr.js";
7
- import { _ as _sfc_main$4 } from "./LpSidebar-C2oolgwy.js";
7
+ import { _ as _sfc_main$4 } from "./LpSidebar-hZ8D1cVr.js";
8
8
  const _hoisted_1 = { class: "flex items-center gap-2" };
9
9
  const _sfc_main = /* @__PURE__ */ defineComponent({
10
10
  __name: "LpModalNav",
@@ -7,7 +7,7 @@ import { _ as _sfc_main$1 } from "./LpIcon-DYtaid0K.js";
7
7
  import { _ as _sfc_main$4 } from "./LpNumberFlow-z4WPZcZu.js";
8
8
  import { _ as _sfc_main$5 } from "./LpRollingText-DB7upf-5.js";
9
9
  import { _ as _sfc_main$6 } from "./LpShift-DXNsauQZ.js";
10
- import { _ as _sfc_main$3 } from "./LpTooltip-CXVKcOXT.js";
10
+ import { _ as _sfc_main$3 } from "./LpTooltip-suFCM34F.js";
11
11
  import { useMergedAttrs } from "../composables/useMergedClass.js";
12
12
  import { useLabels } from "../composables/useLpConfig.js";
13
13
  const _hoisted_1 = {
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, computed, ref, useId, openBlock, createElementBlock, Fragment, createElementVNode, mergeProps, unref, normalizeClass, renderSlot, createCommentVNode, createBlock, createVNode, createSlots, withCtx, normalizeProps, guardReactiveProps } from "vue";
2
2
  import { useLabels } from "../composables/useLpConfig.js";
3
- import { _ as _sfc_main$3 } from "./LpDrawer-CBFVD624.js";
3
+ import { _ as _sfc_main$3 } from "./LpDrawer-D9lLOC0C.js";
4
4
  import { _ as _sfc_main$1 } from "./LpInput-D-YGQbEv.js";
5
5
  import { _ as _sfc_main$2 } from "./LpSidebarNav-C9dHu-2b.js";
6
6
  const _hoisted_1 = ["aria-label"];
@@ -1,6 +1,7 @@
1
1
  import { defineComponent, computed, ref, useId, onMounted, watch, onBeforeUnmount, openBlock, createElementBlock, createElementVNode, toDisplayString, Fragment, renderList, normalizeStyle, normalizeClass, createBlock, unref, createCommentVNode, createTextVNode } from "vue";
2
2
  import { Motion } from "motion-v";
3
3
  import { useLabels } from "../composables/useLpConfig.js";
4
+ import { scrollParent } from "../composables/scroll.js";
4
5
  import { usePillTransition } from "../composables/usePillTransition.js";
5
6
  const _hoisted_1 = ["aria-label"];
6
7
  const _hoisted_2 = { class: "mb-2.5 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted" };
@@ -63,18 +64,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
63
64
  rafId = null;
64
65
  safetyTimer = null;
65
66
  }
66
- function scrollerOf(el) {
67
- for (let node = el.parentElement; node; node = node.parentElement) {
68
- const { overflowY } = getComputedStyle(node);
69
- if (/(auto|scroll|overlay)/.test(overflowY) && node.scrollHeight > node.clientHeight) return node;
70
- }
71
- return null;
72
- }
73
67
  function suppressSpyUntilScrollEnds(target) {
74
68
  spySuppressed = true;
75
69
  if (rafId != null) cancelAnimationFrame(rafId);
76
70
  if (safetyTimer != null) clearTimeout(safetyTimer);
77
- const scroller = scrollerOf(target);
71
+ const scroller = scrollParent(target);
78
72
  const position = () => scroller ? scroller.scrollTop : window.scrollY;
79
73
  let last = position();
80
74
  const tick = () => {
@@ -2,6 +2,7 @@ import { defineComponent, ref, watch, onBeforeUnmount, openBlock, createBlock, r
2
2
  import { injectTooltipProviderContext, TooltipProvider, TooltipRoot, TooltipTrigger, TooltipPortal, TooltipContent } from "reka-ui";
3
3
  import { springStep, springSettled } from "../composables/usePillTransition.js";
4
4
  import { PANEL_SURFACE, TOOLTIP_ANIM } from "../components/dropdown.js";
5
+ import { prefersReducedMotion } from "../composables/easing.js";
5
6
  import '../assets/LpTooltip-B61SYd4K.css';const _sfc_main = /* @__PURE__ */ defineComponent({
6
7
  __name: "LpTooltip",
7
8
  props: {
@@ -18,7 +19,6 @@ import '../assets/LpTooltip-B61SYd4K.css';const _sfc_main = /* @__PURE__ */ defi
18
19
  const props = __props;
19
20
  const shared = injectTooltipProviderContext(null);
20
21
  const PassThrough = (_, { slots }) => slots.default?.();
21
- const reducedMotion = () => typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
22
22
  const target = { x: 0, y: 0 };
23
23
  const offset = ref({ x: 0, y: 0 });
24
24
  let sx = { value: 0, velocity: 0 };
@@ -52,7 +52,7 @@ import '../assets/LpTooltip-B61SYd4K.css';const _sfc_main = /* @__PURE__ */ defi
52
52
  const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0;
53
53
  const dir = props.side === "bottom" ? -1 : 1;
54
54
  target.y = e.clientY - (rect.top + rect.height / 2) + half * dir;
55
- if (reducedMotion()) {
55
+ if (prefersReducedMotion()) {
56
56
  offset.value = { x: target.x, y: target.y };
57
57
  return;
58
58
  }
@@ -0,0 +1,315 @@
1
+ import { defineComponent, useModel, computed, shallowRef, ref, watch, onBeforeUnmount, openBlock, createBlock, Teleport, createElementBlock, withModifiers, Fragment, renderList, normalizeStyle, normalizeClass, createCommentVNode, unref, withCtx, createVNode, renderSlot, mergeModels, nextTick } from "vue";
2
+ import { PopoverRoot, PopoverAnchor, PopoverPortal, PopoverContent } from "reka-ui";
3
+ import { prefersReducedMotion } from "../composables/easing.js";
4
+ import { hints } from "../composables/useHints.js";
5
+ import { useHotkeys } from "../composables/useHotkeys.js";
6
+ import { useLabels } from "../composables/useLpConfig.js";
7
+ import { useMediaQuery } from "../composables/useMediaQuery.js";
8
+ import { scrollToElement } from "../composables/scroll.js";
9
+ import { PANEL_SURFACE } from "../components/dropdown.js";
10
+ import { _ as _sfc_main$1 } from "./TourCard-BQS3ztqj.js";
11
+ import { holeOf, panelsAround } from "../components/tour.js";
12
+ const _hoisted_1 = ["aria-label"];
13
+ const _sfc_main = /* @__PURE__ */ defineComponent({
14
+ __name: "LpTour",
15
+ props: /* @__PURE__ */ mergeModels({
16
+ steps: {},
17
+ padding: { default: 6 },
18
+ labels: {}
19
+ }, {
20
+ "open": { type: Boolean, ...{ default: false } },
21
+ "openModifiers": {},
22
+ "step": { default: 0 },
23
+ "stepModifiers": {}
24
+ }),
25
+ emits: /* @__PURE__ */ mergeModels(["finish", "dismiss"], ["update:open", "update:step"]),
26
+ setup(__props, { emit: __emit }) {
27
+ const props = __props;
28
+ const emit = __emit;
29
+ const open = useModel(__props, "open");
30
+ const index = useModel(__props, "step");
31
+ const DEFAULT_LABELS = {
32
+ next: "Next",
33
+ back: "Back",
34
+ done: "Done",
35
+ close: "Close tour",
36
+ progress: "{n} of {total}",
37
+ perform: "Do it for me",
38
+ tryIt: "Try it: press the highlighted control."
39
+ };
40
+ const labels = useLabels("tour", DEFAULT_LABELS, () => props.labels);
41
+ const WAIT_MS = 800;
42
+ const TRY_SETTLE_MS = 120;
43
+ const wide = useMediaQuery("(min-width: 640px)");
44
+ const step = computed(() => props.steps[index.value]);
45
+ const target = shallowRef(null);
46
+ const box = ref(null);
47
+ const viewport = ref({ width: 0, height: 0 });
48
+ const ready = ref(false);
49
+ const cleanups = [];
50
+ const leaving = [];
51
+ let frame = 0;
52
+ let token = 0;
53
+ let direction = 1;
54
+ const hole = computed(() => box.value ? holeOf(box.value, props.padding, viewport.value) : null);
55
+ const panels = computed(
56
+ () => hole.value ? panelsAround(hole.value, viewport.value) : [{ x: 0, y: 0, width: viewport.value.width, height: viewport.value.height }]
57
+ );
58
+ const reference = {
59
+ getBoundingClientRect: () => {
60
+ const h = hole.value;
61
+ return h ? new DOMRect(h.x, h.y, h.width, h.height) : new DOMRect(viewport.value.width / 2, viewport.value.height / 2, 0, 0);
62
+ }
63
+ };
64
+ const last = computed(() => index.value >= props.steps.length - 1);
65
+ const progress = computed(() => labels.value.progress.replace("{n}", String(index.value + 1)).replace("{total}", String(props.steps.length)));
66
+ const smooth = () => !prefersReducedMotion();
67
+ function resolve(raw) {
68
+ if (!raw) return null;
69
+ if (typeof raw === "function") return raw();
70
+ if (typeof raw !== "string") return raw.isConnected ? raw : null;
71
+ return hints.element(raw) ?? document.querySelector(raw);
72
+ }
73
+ function waitFor(raw, mine) {
74
+ const started = performance.now();
75
+ return new Promise((done) => {
76
+ const tick = () => {
77
+ if (mine !== token) return done(null);
78
+ const el = resolve(raw);
79
+ if (el && el.getClientRects().length) return done(el);
80
+ if (performance.now() - started > WAIT_MS) return done(null);
81
+ requestAnimationFrame(tick);
82
+ };
83
+ tick();
84
+ });
85
+ }
86
+ function measure() {
87
+ viewport.value = { width: window.innerWidth, height: window.innerHeight };
88
+ const el = target.value;
89
+ if (!el || !el.isConnected) {
90
+ box.value = null;
91
+ return;
92
+ }
93
+ const r = el.getBoundingClientRect();
94
+ const prev = box.value;
95
+ if (!prev || prev.x !== r.x || prev.y !== r.y || prev.width !== r.width || prev.height !== r.height) {
96
+ box.value = { x: r.x, y: r.y, width: r.width, height: r.height };
97
+ }
98
+ }
99
+ function track() {
100
+ measure();
101
+ frame = requestAnimationFrame(track);
102
+ }
103
+ async function show() {
104
+ const mine = ++token;
105
+ ready.value = false;
106
+ leave();
107
+ const current = step.value;
108
+ if (!current) return;
109
+ await current.enter?.({ onLeave: (fn) => leaving.push(fn), onEnd: (fn) => cleanups.push(fn) });
110
+ if (mine !== token) return;
111
+ if (!current.target) {
112
+ target.value = null;
113
+ measure();
114
+ ready.value = true;
115
+ return;
116
+ }
117
+ const el = current.enter ? await waitFor(current.target, mine) : resolve(current.target);
118
+ if (mine !== token) return;
119
+ if (!el || !el.getClientRects().length) return skip();
120
+ target.value = el;
121
+ scrollToElement(el, { block: wide.value ? "nearest" : "center", behavior: smooth() ? "smooth" : "auto", margin: 48 });
122
+ await nextTick();
123
+ measure();
124
+ ready.value = true;
125
+ }
126
+ function skip() {
127
+ const nextIndex = index.value + direction;
128
+ if (nextIndex < 0 || nextIndex >= props.steps.length) return direction > 0 ? finish() : show();
129
+ index.value = nextIndex;
130
+ }
131
+ function next() {
132
+ direction = 1;
133
+ if (last.value) return finish();
134
+ index.value += 1;
135
+ }
136
+ function back() {
137
+ if (index.value === 0) return;
138
+ direction = -1;
139
+ index.value -= 1;
140
+ }
141
+ function forward() {
142
+ if (!step.value?.try) next();
143
+ }
144
+ async function perform() {
145
+ await step.value?.perform?.();
146
+ setTimeout(next, TRY_SETTLE_MS);
147
+ }
148
+ function leave() {
149
+ for (const cleanup of leaving.splice(0).reverse()) cleanup();
150
+ }
151
+ function end() {
152
+ token++;
153
+ leave();
154
+ cancelAnimationFrame(frame);
155
+ frame = 0;
156
+ target.value = null;
157
+ box.value = null;
158
+ for (const cleanup of cleanups.splice(0).reverse()) cleanup();
159
+ }
160
+ function finish() {
161
+ open.value = false;
162
+ emit("finish");
163
+ }
164
+ function dismiss() {
165
+ const at = index.value;
166
+ open.value = false;
167
+ emit("dismiss", at);
168
+ }
169
+ function onTargetClick(event) {
170
+ if (!step.value?.try || !target.value) return;
171
+ if (event.target instanceof Node && target.value.contains(event.target)) setTimeout(next, TRY_SETTLE_MS);
172
+ }
173
+ watch(
174
+ open,
175
+ (value) => {
176
+ if (value) {
177
+ direction = 1;
178
+ document.addEventListener("click", onTargetClick, true);
179
+ frame ||= requestAnimationFrame(track);
180
+ void show();
181
+ } else {
182
+ document.removeEventListener("click", onTargetClick, true);
183
+ end();
184
+ }
185
+ },
186
+ { immediate: true }
187
+ );
188
+ watch(index, () => {
189
+ if (open.value) void show();
190
+ });
191
+ onBeforeUnmount(() => {
192
+ document.removeEventListener("click", onTargetClick, true);
193
+ if (open.value) end();
194
+ });
195
+ useHotkeys(
196
+ () => [
197
+ { key: "Escape", handler: dismiss, overLayers: true, allowInInput: true },
198
+ { key: "ArrowRight", handler: forward, overLayers: true },
199
+ { key: "Enter", handler: forward, overLayers: true },
200
+ { key: "ArrowLeft", handler: back, overLayers: true }
201
+ ],
202
+ { enabled: open, priority: 100 }
203
+ );
204
+ const CARD = `${PANEL_SURFACE} rounded-card w-80 max-w-[calc(100vw-24px)] p-4 text-sm text-ink outline-none`;
205
+ return (_ctx, _cache) => {
206
+ return openBlock(), createBlock(Teleport, { to: "body" }, [
207
+ open.value ? (openBlock(), createElementBlock("div", {
208
+ key: 0,
209
+ class: "lp-tour pointer-events-none fixed inset-0 z-(--z-tour)",
210
+ onPointerdown: _cache[0] || (_cache[0] = withModifiers(() => {
211
+ }, ["stop"]))
212
+ }, [
213
+ (openBlock(true), createElementBlock(Fragment, null, renderList(panels.value, (panel, i) => {
214
+ return openBlock(), createElementBlock("div", {
215
+ key: i,
216
+ 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",
217
+ style: normalizeStyle({ transform: `translate(${panel.x}px, ${panel.y}px)`, width: `${panel.width}px`, height: `${panel.height}px` })
218
+ }, null, 4);
219
+ }), 128)),
220
+ hole.value ? (openBlock(), createElementBlock("div", {
221
+ key: 0,
222
+ 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"]),
223
+ style: normalizeStyle({ transform: `translate(${hole.value.x}px, ${hole.value.y}px)`, width: `${hole.value.width}px`, height: `${hole.value.height}px` })
224
+ }, null, 6)) : createCommentVNode("", true)
225
+ ], 32)) : createCommentVNode("", true),
226
+ open.value && unref(wide) ? (openBlock(), createBlock(unref(PopoverRoot), {
227
+ key: 1,
228
+ open: ready.value
229
+ }, {
230
+ default: withCtx(() => [
231
+ createVNode(unref(PopoverAnchor), { reference }),
232
+ createVNode(unref(PopoverPortal), null, {
233
+ default: withCtx(() => [
234
+ createVNode(unref(PopoverContent), {
235
+ side: step.value?.side ?? "bottom",
236
+ "side-offset": 12,
237
+ "collision-padding": 12,
238
+ "update-position-strategy": "always",
239
+ "trap-focus": false,
240
+ 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"]),
241
+ role: "dialog",
242
+ "aria-label": step.value?.title,
243
+ onPointerdown: _cache[1] || (_cache[1] = withModifiers(() => {
244
+ }, ["stop"])),
245
+ onInteractOutside: _cache[2] || (_cache[2] = withModifiers(() => {
246
+ }, ["prevent"])),
247
+ onEscapeKeyDown: _cache[3] || (_cache[3] = withModifiers(() => {
248
+ }, ["prevent"])),
249
+ onFocusOutside: _cache[4] || (_cache[4] = withModifiers(() => {
250
+ }, ["prevent"]))
251
+ }, {
252
+ default: withCtx(() => [
253
+ renderSlot(_ctx.$slots, "card", {
254
+ step: step.value,
255
+ index: index.value,
256
+ next,
257
+ back,
258
+ dismiss
259
+ }, () => [
260
+ createVNode(_sfc_main$1, {
261
+ step: step.value,
262
+ index: index.value,
263
+ last: last.value,
264
+ progress: progress.value,
265
+ labels: unref(labels),
266
+ onNext: next,
267
+ onBack: back,
268
+ onPerform: perform,
269
+ onDismiss: dismiss
270
+ }, null, 8, ["step", "index", "last", "progress", "labels"])
271
+ ])
272
+ ]),
273
+ _: 3
274
+ }, 8, ["side", "class", "aria-label"])
275
+ ]),
276
+ _: 3
277
+ })
278
+ ]),
279
+ _: 3
280
+ }, 8, ["open"])) : createCommentVNode("", true),
281
+ open.value && !unref(wide) && ready.value ? (openBlock(), createElementBlock("div", {
282
+ key: 2,
283
+ 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"]),
284
+ role: "dialog",
285
+ "aria-label": step.value?.title,
286
+ onPointerdown: _cache[5] || (_cache[5] = withModifiers(() => {
287
+ }, ["stop"]))
288
+ }, [
289
+ renderSlot(_ctx.$slots, "card", {
290
+ step: step.value,
291
+ index: index.value,
292
+ next,
293
+ back,
294
+ dismiss
295
+ }, () => [
296
+ createVNode(_sfc_main$1, {
297
+ step: step.value,
298
+ index: index.value,
299
+ last: last.value,
300
+ progress: progress.value,
301
+ labels: unref(labels),
302
+ onNext: next,
303
+ onBack: back,
304
+ onPerform: perform,
305
+ onDismiss: dismiss
306
+ }, null, 8, ["step", "index", "last", "progress", "labels"])
307
+ ])
308
+ ], 42, _hoisted_1)) : createCommentVNode("", true)
309
+ ]);
310
+ };
311
+ }
312
+ });
313
+ export {
314
+ _sfc_main as _
315
+ };
@@ -1,7 +1,7 @@
1
1
  import { defineComponent, computed, ref, openBlock, createElementBlock, mergeProps, unref, createElementVNode, toDisplayString, createCommentVNode, createVNode, withCtx, Fragment, renderList, normalizeStyle, createBlock, createTextVNode } from "vue";
2
2
  import { _ as _sfc_main$3 } from "./LpNumberFlow-z4WPZcZu.js";
3
3
  import { _ as _sfc_main$2 } from "./LpRollingText-DB7upf-5.js";
4
- import { _ as _sfc_main$1 } from "./LpTooltip-CXVKcOXT.js";
4
+ import { _ as _sfc_main$1 } from "./LpTooltip-suFCM34F.js";
5
5
  import { useMergedAttrs } from "../composables/useMergedClass.js";
6
6
  import { useLabels } from "../composables/useLpConfig.js";
7
7
  import '../assets/LpUptimeBar-B98o9krI.css';const _hoisted_1 = {