@leavepulse/ui 0.56.0 → 0.58.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 (182) hide show
  1. package/dist/chunks/{ContextMenuItems-BtKU7074.js → ContextMenuItems-BY5sTVQF.js} +1 -1
  2. package/dist/chunks/{LayoutCanvas-BkaGXTjx.js → LayoutCanvas-BcGLJVBq.js} +1 -1
  3. package/dist/chunks/{LayoutNode-B3_9E1Xa.js → LayoutNode-BkBn6dvM.js} +1 -1
  4. package/dist/chunks/{LpActionBar-C3hkVsFz.js → LpActionBar-DCN-_pZP.js} +2 -2
  5. package/dist/chunks/{LpAddressInput-NUHmplCd.js → LpAddressInput-Dio6Sqed.js} +7 -7
  6. package/dist/chunks/{LpAlert-B6ilrM7T.js → LpAlert-CXMn1GYE.js} +1 -1
  7. package/dist/chunks/{LpAppShell-BEGjSqFl.js → LpAppShell-Be6T96td.js} +3 -3
  8. package/dist/chunks/{LpAutocomplete-C8IgSMsi.js → LpAutocomplete-hJ_6CHlh.js} +1 -1
  9. package/dist/chunks/{LpAvatar-LTvy0OkE.js → LpAvatar-BxNs23Xu.js} +2 -2
  10. package/dist/chunks/{LpBreadcrumbs-D4TsoFju.js → LpBreadcrumbs-BLzOEgAF.js} +1 -1
  11. package/dist/chunks/{LpButton-BdfZFNqi.js → LpButton-vhlwmpBy.js} +1 -1
  12. package/dist/chunks/{LpCalendar-DrA2McTU.js → LpCalendar-BLoE8Qm7.js} +1 -1
  13. package/dist/chunks/{LpCard-DbjO5yaF.js → LpCard-DhR7u0d8.js} +1 -1
  14. package/dist/chunks/{LpCheckbox-DvVYeOD-.js → LpCheckbox-B_C8sOoT.js} +1 -1
  15. package/dist/chunks/{LpCodeBlock-ca7ANFyW.js → LpCodeBlock-CXsqJwDy.js} +1 -1
  16. package/dist/chunks/{LpColorPicker-D1sQqtXS.js → LpColorPicker-DQ88niXq.js} +2 -2
  17. package/dist/chunks/{LpConfirmDialog-CrbUZgJE.js → LpConfirmDialog-DxlSdjmk.js} +3 -3
  18. package/dist/chunks/{LpConfirmHost-BgMIV8r9.js → LpConfirmHost-BcrmTNu_.js} +1 -1
  19. package/dist/chunks/{LpContextMenu-EF_90u3g.js → LpContextMenu-B1NGfpp1.js} +2 -2
  20. package/dist/chunks/{LpDatePicker-BrdFtSTy.js → LpDatePicker-B-VMHMgy.js} +3 -3
  21. package/dist/chunks/{LpDateRangePicker-Drd7EbIz.js → LpDateRangePicker-B68KPKPl.js} +6 -15
  22. package/dist/chunks/{LpDisclosure-Cc23-JOT.js → LpDisclosure-Ct-VBHxt.js} +1 -1
  23. package/dist/chunks/{LpDrawer-CBFVD624.js → LpDrawer-DSknigxH.js} +5 -7
  24. package/dist/chunks/{LpDropdownMenu-DrpJJBp0.js → LpDropdownMenu-C7oHVlKY.js} +2 -2
  25. package/dist/chunks/{LpEmptyState-BJbZXC8D.js → LpEmptyState-CBwwkkdA.js} +1 -1
  26. package/dist/chunks/{LpFieldNotice-wtieLUTj.js → LpFieldNotice-9hH4ITZa.js} +1 -1
  27. package/dist/chunks/{LpFileTree-3E4w4Rjr.js → LpFileTree-CMA0_rsB.js} +2 -2
  28. package/dist/chunks/{LpFileTreeNode-B4uB8Ks_.js → LpFileTreeNode-B4T258hF.js} +3 -3
  29. package/dist/chunks/{LpFormField-CtVYj6tO.js → LpFormField-BpQ6WJPe.js} +1 -1
  30. package/dist/chunks/LpHintLayer-B5-6FyEf.js +154 -0
  31. package/dist/chunks/LpHints-BqoZXHZk.js +109 -0
  32. package/dist/chunks/{LpIcon-DYtaid0K.js → LpIcon-B5guGKrW.js} +1 -1
  33. package/dist/chunks/{LpInput-D-YGQbEv.js → LpInput-WvyP02ID.js} +2 -2
  34. package/dist/chunks/{LpLightbox-EVXzAq62.js → LpLightbox-Dq-e36uT.js} +1 -1
  35. package/dist/chunks/{LpLogViewer-BfgtXVKm.js → LpLogViewer-DwiYE32z.js} +2 -2
  36. package/dist/chunks/{LpMapPicker-Cc84LaM3.js → LpMapPicker-BH5csaBt.js} +1 -1
  37. package/dist/chunks/{LpMediaGallery-BnlkN_1f.js → LpMediaGallery-B1kDypJ-.js} +2 -2
  38. package/dist/chunks/{LpModal-JNa3B_Lr.js → LpModal-BxYssnXg.js} +1 -1
  39. package/dist/chunks/{LpModalNav-DXBilXr0.js → LpModalNav-Bn2ws64b.js} +4 -4
  40. package/dist/chunks/{LpNotificationBell-DEL-RoHS.js → LpNotificationBell-DJfs4JjQ.js} +4 -4
  41. package/dist/chunks/{LpNumberField-B77O-z28.js → LpNumberField-CJPjmoDz.js} +1 -1
  42. package/dist/chunks/{LpPagination-BcDlo3r_.js → LpPagination-DehAaJLY.js} +1 -1
  43. package/dist/chunks/{LpPasswordInput-eKzQhbQP.js → LpPasswordInput-Bg5uTZbx.js} +2 -2
  44. package/dist/chunks/{LpPhoneInput-BCbrrhgE.js → LpPhoneInput-DStwcbnQ.js} +2 -2
  45. package/dist/chunks/{LpPie-B7ti5qCH.js → LpPie-DeTkbbOK.js} +3 -3
  46. package/dist/chunks/{LpRangeCalendar-DZuaRV0W.js → LpRangeCalendar-D5sDID9W.js} +1 -1
  47. package/dist/chunks/{LpSection-tKEH0jYf.js → LpSection-B19VPYmr.js} +1 -1
  48. package/dist/chunks/{LpSegmented-C09EEam2.js → LpSegmented-D3Eam8sa.js} +1 -1
  49. package/dist/chunks/{LpSelect-DNIL6MB9.js → LpSelect-DynjWK1A.js} +1 -1
  50. package/dist/chunks/{LpSidebar-C2oolgwy.js → LpSidebar-CshW0fqC.js} +3 -3
  51. package/dist/chunks/{LpSidebarNav-C9dHu-2b.js → LpSidebarNav-C9b3i9G2.js} +2 -2
  52. package/dist/chunks/{LpStat-B-7udBRP.js → LpStat-BS2TEXYK.js} +1 -1
  53. package/dist/chunks/{LpStepper-C7XiVKZj.js → LpStepper-BSglWF45.js} +1 -1
  54. package/dist/chunks/{LpTableFilter-CrW7_Rj4.js → LpTableFilter-CwHXVxJt.js} +2 -2
  55. package/dist/chunks/{LpTableOfContents-BUcAfGPQ.js → LpTableOfContents-DYuqkByy.js} +2 -8
  56. package/dist/chunks/{LpTabs-BMSBB78h.js → LpTabs-BBGRHY38.js} +1 -1
  57. package/dist/chunks/{LpTagsInput-D1SuqbhB.js → LpTagsInput-CHbQoAAA.js} +3 -3
  58. package/dist/chunks/{LpTextarea-uuScWo6-.js → LpTextarea-Bz2IWhTH.js} +1 -1
  59. package/dist/chunks/{LpThemeSwitcher-Fz85BCRS.js → LpThemeSwitcher-B_qhSl7q.js} +2 -2
  60. package/dist/chunks/{LpTimeField-Cqe7fXnh.js → LpTimeField-DvGXgdES.js} +1 -1
  61. package/dist/chunks/{LpToaster-DTtWuKvq.js → LpToaster-Bo9Q4QBP.js} +1 -1
  62. package/dist/chunks/{LpTooltip-CXVKcOXT.js → LpTooltip-suFCM34F.js} +2 -2
  63. package/dist/chunks/LpTour-D7m9ZxNk.js +334 -0
  64. package/dist/chunks/{LpUptimeBar-ugcv25xj.js → LpUptimeBar-B4ltCfno.js} +1 -1
  65. package/dist/chunks/{MenuTitle-J3ZDYjpV.js → MenuTitle-DQP6hc2N.js} +1 -1
  66. package/dist/chunks/TourCard-DCHOjr6e.js +100 -0
  67. package/dist/chunks/icons-QqBscG5I.js +43 -0
  68. package/dist/component-names.d.ts +1 -1
  69. package/dist/components/ContextMenuItems.vue.js +1 -1
  70. package/dist/components/LayoutCanvas.vue.js +1 -1
  71. package/dist/components/LayoutNode.vue.js +1 -1
  72. package/dist/components/LpActionBar.vue.js +1 -1
  73. package/dist/components/LpAddressInput.vue.js +1 -1
  74. package/dist/components/LpAlert.vue.js +1 -1
  75. package/dist/components/LpAppShell.vue.js +1 -1
  76. package/dist/components/LpAutocomplete.vue.js +1 -1
  77. package/dist/components/LpAvatar.vue.js +1 -1
  78. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  79. package/dist/components/LpButton.vue.js +1 -1
  80. package/dist/components/LpCalendar.vue.js +1 -1
  81. package/dist/components/LpCard.vue.js +1 -1
  82. package/dist/components/LpChart.vue.js +2 -2
  83. package/dist/components/LpCheckbox.vue.js +1 -1
  84. package/dist/components/LpCodeBlock.vue.js +1 -1
  85. package/dist/components/LpColorPicker.vue.js +1 -1
  86. package/dist/components/LpCommandPalette.vue.js +2 -2
  87. package/dist/components/LpConfirmDialog.vue.js +1 -1
  88. package/dist/components/LpConfirmHost.vue.js +1 -1
  89. package/dist/components/LpContextMenu.vue.js +1 -1
  90. package/dist/components/LpDatePicker.vue.js +1 -1
  91. package/dist/components/LpDateRangePicker.vue.js +1 -1
  92. package/dist/components/LpDisclosure.vue.js +1 -1
  93. package/dist/components/LpDrawer.vue.js +1 -1
  94. package/dist/components/LpDropdownMenu.vue.js +1 -1
  95. package/dist/components/LpDropzone.vue.js +1 -1
  96. package/dist/components/LpEmptyState.vue.js +1 -1
  97. package/dist/components/LpFieldNotice.vue.js +1 -1
  98. package/dist/components/LpFileTree.vue.js +1 -1
  99. package/dist/components/LpFileTreeNode.vue.js +1 -1
  100. package/dist/components/LpFormField.vue.js +1 -1
  101. package/dist/components/LpHintLayer.vue.d.ts +33 -0
  102. package/dist/components/LpHintLayer.vue.js +4 -0
  103. package/dist/components/LpHints.vue.d.ts +20 -0
  104. package/dist/components/LpHints.vue.js +4 -0
  105. package/dist/components/LpIcon.vue.js +1 -1
  106. package/dist/components/LpImageEditor.vue.js +2 -2
  107. package/dist/components/LpInput.vue.js +1 -1
  108. package/dist/components/LpLightbox.vue.js +1 -1
  109. package/dist/components/LpLogViewer.vue.js +1 -1
  110. package/dist/components/LpMapPicker.vue.js +1 -1
  111. package/dist/components/LpMediaGallery.vue.js +1 -1
  112. package/dist/components/LpModal.vue.js +1 -1
  113. package/dist/components/LpModalNav.vue.js +1 -1
  114. package/dist/components/LpNotificationBell.vue.js +1 -1
  115. package/dist/components/LpNumberField.vue.js +1 -1
  116. package/dist/components/LpPagination.vue.js +1 -1
  117. package/dist/components/LpPaletteFacts.vue.js +1 -1
  118. package/dist/components/LpPaletteHeader.vue.js +2 -2
  119. package/dist/components/LpPasswordInput.vue.js +1 -1
  120. package/dist/components/LpPhoneInput.vue.js +1 -1
  121. package/dist/components/LpPie.vue.js +1 -1
  122. package/dist/components/LpRangeCalendar.vue.js +1 -1
  123. package/dist/components/LpSection.vue.js +1 -1
  124. package/dist/components/LpSegmented.vue.js +1 -1
  125. package/dist/components/LpSelect.vue.js +1 -1
  126. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  127. package/dist/components/LpSidebar.vue.js +1 -1
  128. package/dist/components/LpSidebarNav.vue.js +1 -1
  129. package/dist/components/LpStat.vue.js +1 -1
  130. package/dist/components/LpStepper.vue.js +1 -1
  131. package/dist/components/LpTable.vue.js +6 -6
  132. package/dist/components/LpTableFilter.vue.js +1 -1
  133. package/dist/components/LpTableOfContents.vue.js +1 -1
  134. package/dist/components/LpTabs.vue.js +1 -1
  135. package/dist/components/LpTagsInput.vue.js +1 -1
  136. package/dist/components/LpTextarea.vue.js +1 -1
  137. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  138. package/dist/components/LpTimeField.vue.js +1 -1
  139. package/dist/components/LpToaster.vue.js +1 -1
  140. package/dist/components/LpTooltip.vue.js +1 -1
  141. package/dist/components/LpTour.vue.d.ts +98 -0
  142. package/dist/components/LpTour.vue.js +4 -0
  143. package/dist/components/LpUptimeBar.vue.js +1 -1
  144. package/dist/components/MenuTitle.vue.js +1 -1
  145. package/dist/components/TourCard.vue.d.ts +28 -0
  146. package/dist/components/TourCard.vue.js +4 -0
  147. package/dist/components/tour.d.ts +21 -0
  148. package/dist/components/tour.js +38 -0
  149. package/dist/composables/scroll.d.ts +17 -0
  150. package/dist/composables/scroll.js +29 -0
  151. package/dist/composables/useGamepadNav.js +2 -7
  152. package/dist/composables/useHintSystem.d.ts +195 -0
  153. package/dist/composables/useHintSystem.js +334 -0
  154. package/dist/composables/useHints.d.ts +18 -0
  155. package/dist/composables/useHints.js +120 -0
  156. package/dist/composables/useMediaQuery.d.ts +2 -0
  157. package/dist/composables/useMediaQuery.js +18 -0
  158. package/dist/composables/usePaintSelect.js +1 -7
  159. package/dist/icons-all.js +1 -1
  160. package/dist/index.d.ts +13 -0
  161. package/dist/index.js +89 -64
  162. package/package.json +1 -1
  163. package/src/component-names.ts +3 -0
  164. package/src/components/LpDateRangePicker.vue +3 -12
  165. package/src/components/LpDrawer.vue +4 -7
  166. package/src/components/LpHintLayer.vue +148 -0
  167. package/src/components/LpHints.vue +116 -0
  168. package/src/components/LpTableOfContents.vue +2 -14
  169. package/src/components/LpTooltip.vue +2 -4
  170. package/src/components/LpTour.vue +358 -0
  171. package/src/components/TourCard.vue +41 -0
  172. package/src/components/tour.ts +57 -0
  173. package/src/composables/scroll.ts +54 -0
  174. package/src/composables/useGamepadNav.ts +2 -7
  175. package/src/composables/useHintSystem.ts +512 -0
  176. package/src/composables/useHints.ts +151 -0
  177. package/src/composables/useMediaQuery.ts +20 -0
  178. package/src/composables/usePaintSelect.ts +1 -8
  179. package/src/index.ts +27 -0
  180. package/src/kit-icons.ts +1 -1
  181. package/src/tokens/tokens.css +1 -0
  182. package/dist/chunks/icons-CkA0njzm.js +0 -43
@@ -0,0 +1,98 @@
1
+ export interface TourContext {
2
+ /** Undo something when this step is left, in either direction (close the menu it opened). */
3
+ onLeave(cleanup: () => void): void;
4
+ /** Undo something when the tour ends, for what several steps share (a dialog they all live in). */
5
+ onEnd(cleanup: () => void): void;
6
+ }
7
+ export interface TourStep {
8
+ /**
9
+ * What to point at: a `v-hint` id, a CSS selector, an element or a getter.
10
+ * Left out, the step is a centred card with nothing highlighted (an intro).
11
+ * A target that never appears is skipped, so a step for a control the user
12
+ * cannot see costs nothing.
13
+ */
14
+ target?: string | Element | (() => Element | null);
15
+ title: string;
16
+ text?: string;
17
+ /** A shortcut for the same action, shown as keycaps ("Ctrl+K"). */
18
+ keys?: string;
19
+ side?: "top" | "right" | "bottom" | "left";
20
+ /**
21
+ * Ask the user to press the highlighted control themselves; the tour moves
22
+ * on when they do. Doing it beats reading about it.
23
+ */
24
+ try?: boolean;
25
+ /** With `try`, a "do it for me" button runs this instead. */
26
+ perform?: () => void | Promise<void>;
27
+ /** Runs before the step shows: open the dialog or tab its target lives in. */
28
+ enter?: (context: TourContext) => void | Promise<void>;
29
+ }
30
+ declare const _default: typeof __VLS_export;
31
+ export default _default;
32
+ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
33
+ steps: TourStep[];
34
+ /** Room around the target inside the lit hole, px. */
35
+ padding?: number;
36
+ labels?: Partial<{
37
+ next: string;
38
+ back: string;
39
+ done: string;
40
+ close: string;
41
+ progress: string;
42
+ perform: string;
43
+ tryIt: string;
44
+ }>;
45
+ } & {
46
+ open?: boolean;
47
+ step?: number;
48
+ }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
49
+ "update:open": (value: boolean) => any;
50
+ "update:step": (value: number) => any;
51
+ } & {
52
+ dismiss: (index: number) => any;
53
+ finish: () => any;
54
+ }, string, import("vue").PublicProps, Readonly<{
55
+ steps: TourStep[];
56
+ /** Room around the target inside the lit hole, px. */
57
+ padding?: number;
58
+ labels?: Partial<{
59
+ next: string;
60
+ back: string;
61
+ done: string;
62
+ close: string;
63
+ progress: string;
64
+ perform: string;
65
+ tryIt: string;
66
+ }>;
67
+ } & {
68
+ open?: boolean;
69
+ step?: number;
70
+ }> & Readonly<{
71
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
72
+ onDismiss?: ((index: number) => any) | undefined;
73
+ "onUpdate:step"?: ((value: number) => any) | undefined;
74
+ onFinish?: (() => any) | undefined;
75
+ }>, {
76
+ padding: number;
77
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
78
+ card?: (props: {
79
+ step: TourStep | undefined;
80
+ index: number;
81
+ next: () => void;
82
+ back: () => void;
83
+ dismiss: () => void;
84
+ }) => any;
85
+ } & {
86
+ card?: (props: {
87
+ step: TourStep | undefined;
88
+ index: number;
89
+ next: () => void;
90
+ back: () => void;
91
+ dismiss: () => void;
92
+ }) => any;
93
+ }>;
94
+ type __VLS_WithSlots<T, S> = T & {
95
+ new (): {
96
+ $slots: S;
97
+ };
98
+ };
@@ -0,0 +1,4 @@
1
+ import { _ as _sfc_main } from "../chunks/LpTour-D7m9ZxNk.js";
2
+ export {
3
+ _sfc_main as default
4
+ };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpUptimeBar-ugcv25xj.js";
1
+ import { _ as _sfc_main } from "../chunks/LpUptimeBar-B4ltCfno.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/MenuTitle-J3ZDYjpV.js";
1
+ import { _ as _sfc_main } from "../chunks/MenuTitle-DQP6hc2N.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -0,0 +1,28 @@
1
+ import type { TourStep } from "./LpTour.vue";
2
+ type __VLS_Props = {
3
+ step: TourStep | undefined;
4
+ index: number;
5
+ last: boolean;
6
+ progress: string;
7
+ labels: {
8
+ next: string;
9
+ back: string;
10
+ done: string;
11
+ close: string;
12
+ perform: string;
13
+ tryIt: string;
14
+ };
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
17
+ next: () => any;
18
+ dismiss: () => any;
19
+ back: () => any;
20
+ perform: () => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
22
+ onNext?: (() => any) | undefined;
23
+ onDismiss?: (() => any) | undefined;
24
+ onBack?: (() => any) | undefined;
25
+ onPerform?: (() => any) | undefined;
26
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
+ declare const _default: typeof __VLS_export;
28
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { _ as _sfc_main } from "../chunks/TourCard-DCHOjr6e.js";
2
+ export {
3
+ _sfc_main as default
4
+ };
@@ -0,0 +1,21 @@
1
+ import type { Box } from "../composables/spatialNav";
2
+ type Size = {
3
+ width: number;
4
+ height: number;
5
+ };
6
+ /** The hole around a target: padded, then clipped to the viewport so a tall
7
+ * target never pushes the dim panels off-screen. */
8
+ export declare function holeOf(target: Box, padding: number, viewport: Size): Box;
9
+ /** The four dim panels around a hole: top, bottom, left, right. */
10
+ export declare function panelsAround(hole: Box, viewport: Size): Box[];
11
+ /**
12
+ * Open the context menu of an element as a right-click would, at its centre.
13
+ * Lets a tour step show a row's menu without the user hunting for it; the menu
14
+ * itself is whatever the app already wires to that element.
15
+ *
16
+ * Returns the way to close it again: a press outside the menu, which every
17
+ * dismissable layer honours. Not Escape — that key also closes whatever is on
18
+ * top, a tour included, and would end the walk instead of the menu.
19
+ */
20
+ export declare function openContextMenu(el: Element): () => void;
21
+ export {};
@@ -0,0 +1,38 @@
1
+ function holeOf(target, padding, viewport) {
2
+ const x = Math.max(0, target.x - padding);
3
+ const y = Math.max(0, target.y - padding);
4
+ const right = Math.min(viewport.width, target.x + target.width + padding);
5
+ const bottom = Math.min(viewport.height, target.y + target.height + padding);
6
+ return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) };
7
+ }
8
+ function panelsAround(hole, viewport) {
9
+ const bottom = hole.y + hole.height;
10
+ return [
11
+ { x: 0, y: 0, width: viewport.width, height: hole.y },
12
+ { x: 0, y: bottom, width: viewport.width, height: Math.max(0, viewport.height - bottom) },
13
+ { x: 0, y: hole.y, width: hole.x, height: hole.height },
14
+ { x: hole.x + hole.width, y: hole.y, width: Math.max(0, viewport.width - hole.x - hole.width), height: hole.height }
15
+ ];
16
+ }
17
+ function openContextMenu(el) {
18
+ const box = el.getBoundingClientRect();
19
+ el.dispatchEvent(
20
+ new MouseEvent("contextmenu", {
21
+ bubbles: true,
22
+ cancelable: true,
23
+ button: 2,
24
+ clientX: box.left + box.width / 2,
25
+ clientY: box.top + box.height / 2
26
+ })
27
+ );
28
+ return () => {
29
+ const outside = { bubbles: true, cancelable: true, clientX: 0, clientY: 0 };
30
+ document.body.dispatchEvent(new PointerEvent("pointerdown", outside));
31
+ document.body.dispatchEvent(new PointerEvent("pointerup", outside));
32
+ };
33
+ }
34
+ export {
35
+ holeOf,
36
+ openContextMenu,
37
+ panelsAround
38
+ };
@@ -0,0 +1,17 @@
1
+ export interface ScrollParentOptions {
2
+ /** Which overflow counts: vertical (default), horizontal, or either. */
3
+ axis?: "y" | "x" | "both";
4
+ /** Start the search at the element itself rather than its parent. */
5
+ self?: boolean;
6
+ }
7
+ /** The nearest ancestor that scrolls along `axis` and has something to scroll, or null. */
8
+ export declare function scrollParent(el: Element | null | undefined, { axis, self }?: ScrollParentOptions): HTMLElement | null;
9
+ export interface ScrollToElementOptions {
10
+ /** `nearest` scrolls only when the element is out of view. */
11
+ block?: "nearest" | "center" | "start";
12
+ behavior?: ScrollBehavior;
13
+ /** Room kept between the element and the scroller's edge. */
14
+ margin?: number;
15
+ }
16
+ /** Scroll the nearest scroller, and only it, so that `el` is in view. */
17
+ export declare function scrollToElement(el: Element | null | undefined, { block, behavior, margin }?: ScrollToElementOptions): void;
@@ -0,0 +1,29 @@
1
+ const SCROLLS = /(auto|scroll|overlay)/;
2
+ function scrollParent(el, { axis = "y", self = false } = {}) {
3
+ for (let node = self ? el : el?.parentElement; node; node = node.parentElement) {
4
+ if (node === document.body || node === document.documentElement) return null;
5
+ const style = getComputedStyle(node);
6
+ const y = axis !== "x" && SCROLLS.test(style.overflowY) && node.scrollHeight > node.clientHeight;
7
+ const x = axis !== "y" && SCROLLS.test(style.overflowX) && node.scrollWidth > node.clientWidth;
8
+ if (y || x) return node;
9
+ }
10
+ return null;
11
+ }
12
+ function scrollToElement(el, { block = "nearest", behavior = "auto", margin = 0 } = {}) {
13
+ if (!el) return;
14
+ const box = scrollParent(el) ?? document.scrollingElement;
15
+ if (!box) return;
16
+ const outer = box === document.scrollingElement ? new DOMRect(0, 0, window.innerWidth, window.innerHeight) : box.getBoundingClientRect();
17
+ const inner = el.getBoundingClientRect();
18
+ const top = inner.top - outer.top + box.scrollTop;
19
+ let target = box.scrollTop;
20
+ if (block === "start") target = top - margin;
21
+ else if (block === "center") target = top - (box.clientHeight - inner.height) / 2;
22
+ else if (inner.top < outer.top + margin) target = top - margin;
23
+ else if (inner.bottom > outer.bottom - margin) target = top - box.clientHeight + inner.height + margin;
24
+ if (target !== box.scrollTop) box.scrollTo({ top: target, behavior });
25
+ }
26
+ export {
27
+ scrollParent,
28
+ scrollToElement
29
+ };
@@ -1,5 +1,6 @@
1
1
  import { ref, onMounted, onBeforeUnmount, toValue } from "vue";
2
2
  import { focusableIn, nextInDirection } from "./spatialNav.js";
3
+ import { scrollParent } from "./scroll.js";
3
4
  const BUTTON = { a: 0, b: 1, lb: 4, rb: 5, start: 9, up: 12, down: 13, left: 14, right: 15 };
4
5
  const STICK_THRESHOLD = 0.55;
5
6
  const REPEAT_DELAY_MS = 380;
@@ -76,13 +77,7 @@ function useGamepadNav(options = {}) {
76
77
  }
77
78
  }
78
79
  function scrollBy(dx, dy) {
79
- let element = document.activeElement;
80
- while (element && element !== document.body) {
81
- const style = getComputedStyle(element);
82
- if (/(auto|scroll)/.test(style.overflowY + style.overflowX) && element.scrollHeight > element.clientHeight) break;
83
- element = element.parentElement;
84
- }
85
- (element && element !== document.body ? element : document.scrollingElement)?.scrollBy(dx, dy);
80
+ (scrollParent(document.activeElement, { axis: "both", self: true }) ?? document.scrollingElement)?.scrollBy(dx, dy);
86
81
  }
87
82
  function poll(now) {
88
83
  frame = requestAnimationFrame(poll);
@@ -0,0 +1,195 @@
1
+ import { type ComputedRef } from "vue";
2
+ import type { HintNote } from "../components/LpHintLayer.vue";
3
+ import type { TourContext, TourStep } from "../components/LpTour.vue";
4
+ export interface HintFeature {
5
+ page: string;
6
+ title: string;
7
+ text: string;
8
+ order?: number;
9
+ keys?: string;
10
+ try?: boolean;
11
+ side?: TourStep["side"];
12
+ /** Bump to announce a changed feature again: it reads as new to everyone. */
13
+ rev?: number;
14
+ /** Keeps its dot until explained, even after "not now". */
15
+ required?: boolean;
16
+ /**
17
+ * Rendered only in some states (data-dependent, behind a filter). Its first
18
+ * appearance is not news, so it is never marked new for merely showing up;
19
+ * a `rev` bump still announces it.
20
+ */
21
+ conditional?: boolean;
22
+ /** When the `v-hint` id is not the element to light (a column header, an opened menu). */
23
+ target?: () => Element | null;
24
+ enter?: (context: TourContext) => void | Promise<void>;
25
+ }
26
+ export interface HintPage {
27
+ title: string;
28
+ /**
29
+ * A localStorage key prefix that proves the user worked on this page before
30
+ * the system existed (a remembered table layout). Such a page is treated as
31
+ * known on first sight: no tour offer, no dots for what was always there.
32
+ */
33
+ known?: string;
34
+ }
35
+ export interface HintNews {
36
+ id: string;
37
+ date: string;
38
+ page: string;
39
+ text: string;
40
+ feature?: string;
41
+ }
42
+ export interface HintNudge {
43
+ id: string;
44
+ feature: string;
45
+ text: string;
46
+ }
47
+ export type HintLevel = "seen" | "explained" | "used";
48
+ export interface HintRecord {
49
+ state: HintLevel;
50
+ dismissed: boolean;
51
+ count: number;
52
+ assigned?: boolean;
53
+ updatedAt: string;
54
+ }
55
+ export interface HintDelta {
56
+ key: string;
57
+ state?: HintLevel;
58
+ dismissed?: boolean;
59
+ add?: number;
60
+ }
61
+ export interface HintStorage {
62
+ load(): Promise<Record<string, HintRecord>> | Record<string, HintRecord>;
63
+ save(deltas: HintDelta[]): Promise<void> | void;
64
+ /** Last chance on page hide (sendBeacon); falls back to `save`. */
65
+ saveOnLeave?(deltas: HintDelta[]): void;
66
+ }
67
+ export interface HintTiming {
68
+ settle: number;
69
+ offerAgain: number;
70
+ offersMax: number;
71
+ freshLimit: number;
72
+ nudgeAgain: number;
73
+ flush: number;
74
+ }
75
+ export interface HintSystemOptions {
76
+ features: Record<string, HintFeature>;
77
+ pages?: Record<string, HintPage>;
78
+ news?: HintNews[];
79
+ storage?: HintStorage;
80
+ /** The current page id, read reactively (map your route to it). */
81
+ page: () => string;
82
+ sessionKey?: string;
83
+ timing?: Partial<HintTiming>;
84
+ }
85
+ export type HintOffer = {
86
+ kind: "tour" | "assigned";
87
+ page: string;
88
+ } | {
89
+ kind: "news";
90
+ page: string;
91
+ items: HintNews[];
92
+ } | {
93
+ kind: "nudge";
94
+ page: string;
95
+ nudge: HintNudge;
96
+ };
97
+ /** Records in localStorage: for anonymous sessions and apps without a server. */
98
+ export declare function localHintStorage(key?: string): HintStorage;
99
+ /** Record keys, for apps that read or assign records directly (a "who knows what" view). */
100
+ export declare const hintKey: {
101
+ feature: (id: string, rev?: number) => string;
102
+ page: (page: string) => string;
103
+ news: (id: string) => string;
104
+ nudge: (id: string) => string;
105
+ };
106
+ export interface HintSummary {
107
+ tour: "done" | "assigned" | "declined" | "offered" | "never";
108
+ offers: number;
109
+ known: number;
110
+ total: number;
111
+ /** Titles of `required` features this person has not been shown. */
112
+ missing: string[];
113
+ }
114
+ /** What one person knows of a page, from their records: for a manager's view. */
115
+ export declare function hintSummary(records: Map<string, Pick<HintRecord, "state" | "dismissed" | "count" | "assigned">>, features: Record<string, HintFeature>, page: string): HintSummary;
116
+ export declare function createHintSystem(options: HintSystemOptions): {
117
+ state: {
118
+ records: Map<string, {
119
+ state: HintLevel;
120
+ dismissed: boolean;
121
+ count: number;
122
+ assigned?: boolean | undefined;
123
+ updatedAt: string;
124
+ }> & Omit<Map<string, HintRecord>, keyof Map<any, any>>;
125
+ loaded: boolean;
126
+ page: string;
127
+ tour: {
128
+ open: boolean;
129
+ step: number;
130
+ steps: {
131
+ target?: (string | Element | (() => Element | null)) | undefined;
132
+ title: string;
133
+ text?: string | undefined;
134
+ keys?: string | undefined;
135
+ side?: "top" | "right" | "bottom" | "left" | undefined;
136
+ try?: boolean | undefined;
137
+ perform?: (() => void | Promise<void>) | undefined;
138
+ enter?: ((context: TourContext) => void | Promise<void>) | undefined;
139
+ }[];
140
+ ids: string[];
141
+ page: string;
142
+ };
143
+ offer: {
144
+ kind: "tour" | "assigned";
145
+ page: string;
146
+ } | {
147
+ kind: "news";
148
+ page: string;
149
+ items: {
150
+ id: string;
151
+ date: string;
152
+ page: string;
153
+ text: string;
154
+ feature?: string | undefined;
155
+ }[];
156
+ } | {
157
+ kind: "nudge";
158
+ page: string;
159
+ nudge: {
160
+ id: string;
161
+ feature: string;
162
+ text: string;
163
+ };
164
+ } | null;
165
+ candidate: {
166
+ id: string;
167
+ feature: string;
168
+ text: string;
169
+ page: string;
170
+ } | null;
171
+ settledAt: number;
172
+ intent: boolean;
173
+ };
174
+ fresh: ComputedRef<string[]>;
175
+ notes: ComputedRef<Record<string, HintNote>>;
176
+ start: () => void;
177
+ stop: () => void;
178
+ hasTour: (page?: string) => boolean;
179
+ startTour: (page?: string, only?: string[]) => void;
180
+ load(): Promise<void>;
181
+ /** Ask for a nudge; it shows when the budget and the page allow. */
182
+ nudge(n: HintNudge): void;
183
+ /** The user navigated on purpose (palette, notification): do not interrupt this arrival. */
184
+ markIntent(): void;
185
+ unreadNews: (page?: string) => HintNews[];
186
+ readNews: (page: string) => void;
187
+ pageTitle: (page: string) => string;
188
+ readFresh(id: string): void;
189
+ tourShown(index: number): void;
190
+ tourEnded(finished: boolean): void;
191
+ answer(choice: "show" | "later" | "never"): void;
192
+ };
193
+ export type HintSystem = ReturnType<typeof createHintSystem>;
194
+ export declare function provideHintSystem(system: HintSystem): void;
195
+ export declare function useHintSystem(): HintSystem | null;