@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.
- package/dist/chunks/{LpAppShell-BEGjSqFl.js → LpAppShell-Bl1Ch68N.js} +1 -1
- package/dist/chunks/{LpDateRangePicker-Drd7EbIz.js → LpDateRangePicker-CxV5jW7p.js} +4 -13
- package/dist/chunks/{LpDrawer-CBFVD624.js → LpDrawer-D9lLOC0C.js} +4 -6
- package/dist/chunks/LpHintLayer-CI1oZ9Pd.js +154 -0
- package/dist/chunks/{LpModalNav-DXBilXr0.js → LpModalNav-DXyAyhQl.js} +1 -1
- package/dist/chunks/{LpPie-B7ti5qCH.js → LpPie-CXka8Jvx.js} +1 -1
- package/dist/chunks/{LpSidebar-C2oolgwy.js → LpSidebar-hZ8D1cVr.js} +1 -1
- package/dist/chunks/{LpTableOfContents-BUcAfGPQ.js → LpTableOfContents-DYuqkByy.js} +2 -8
- package/dist/chunks/{LpTooltip-CXVKcOXT.js → LpTooltip-suFCM34F.js} +2 -2
- package/dist/chunks/LpTour-C1TC2GyY.js +315 -0
- package/dist/chunks/{LpUptimeBar-ugcv25xj.js → LpUptimeBar-B4ltCfno.js} +1 -1
- package/dist/chunks/TourCard-BQS3ztqj.js +100 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.d.ts +33 -0
- package/dist/components/LpHintLayer.vue.js +4 -0
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTour.vue.d.ts +98 -0
- package/dist/components/LpTour.vue.js +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/TourCard.vue.d.ts +28 -0
- package/dist/components/TourCard.vue.js +4 -0
- package/dist/components/tour.d.ts +21 -0
- package/dist/components/tour.js +38 -0
- package/dist/composables/scroll.d.ts +17 -0
- package/dist/composables/scroll.js +29 -0
- package/dist/composables/useGamepadNav.js +2 -7
- package/dist/composables/useHints.d.ts +18 -0
- package/dist/composables/useHints.js +120 -0
- package/dist/composables/useMediaQuery.d.ts +2 -0
- package/dist/composables/useMediaQuery.js +18 -0
- package/dist/composables/usePaintSelect.js +1 -7
- package/dist/index.d.ts +10 -0
- package/dist/index.js +32 -16
- package/package.json +1 -1
- package/src/component-names.ts +2 -0
- package/src/components/LpDateRangePicker.vue +3 -12
- package/src/components/LpDrawer.vue +4 -7
- package/src/components/LpHintLayer.vue +148 -0
- package/src/components/LpTableOfContents.vue +2 -14
- package/src/components/LpTooltip.vue +2 -4
- package/src/components/LpTour.vue +338 -0
- package/src/components/TourCard.vue +41 -0
- package/src/components/tour.ts +57 -0
- package/src/composables/scroll.ts +54 -0
- package/src/composables/useGamepadNav.ts +2 -7
- package/src/composables/useHints.ts +151 -0
- package/src/composables/useMediaQuery.ts +20 -0
- package/src/composables/usePaintSelect.ts +1 -8
- package/src/index.ts +10 -0
- package/src/tokens/tokens.css +1 -0
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { ref, toValue, onMounted, onBeforeUnmount, readonly } from "vue";
|
|
2
2
|
import { useHotkeys } from "./useHotkeys.js";
|
|
3
|
+
import { scrollParent } from "./scroll.js";
|
|
3
4
|
const HOLD_MS = 320;
|
|
4
5
|
const SLOP_PX = 8;
|
|
5
6
|
const EDGE_PX = 40;
|
|
6
7
|
const SCROLL_STEP = 14;
|
|
7
|
-
function scrollParent(el) {
|
|
8
|
-
for (let node = el?.parentElement; node; node = node.parentElement) {
|
|
9
|
-
const { overflowY } = getComputedStyle(node);
|
|
10
|
-
if ((overflowY === "auto" || overflowY === "scroll") && node.scrollHeight > node.clientHeight) return node;
|
|
11
|
-
}
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
8
|
function usePaintSelect(options) {
|
|
15
9
|
const resolve = options.resolve ?? ((raw) => raw);
|
|
16
10
|
let stroke = null;
|
package/dist/index.d.ts
CHANGED
|
@@ -157,6 +157,11 @@ export { default as LpThemeSwitcher } from "./components/LpThemeSwitcher.vue";
|
|
|
157
157
|
export { default as LpTilt } from "./components/LpTilt.vue";
|
|
158
158
|
export { default as LpToaster } from "./components/LpToaster.vue";
|
|
159
159
|
export { default as LpTooltip } from "./components/LpTooltip.vue";
|
|
160
|
+
export { default as LpTour } from "./components/LpTour.vue";
|
|
161
|
+
export type { TourContext, TourStep } from "./components/LpTour.vue";
|
|
162
|
+
export { holeOf, openContextMenu, panelsAround } from "./components/tour";
|
|
163
|
+
export { default as LpHintLayer } from "./components/LpHintLayer.vue";
|
|
164
|
+
export type { HintNote } from "./components/LpHintLayer.vue";
|
|
160
165
|
export { default as LpTooltipProvider } from "./components/LpTooltipProvider.vue";
|
|
161
166
|
export { default as LpTopologyCanvas } from "./components/LpTopologyCanvas.vue";
|
|
162
167
|
export type { CanvasViewport, EdgeCategory, EdgeObserved, TopologyEdge, TopologyLane, TopologyNode, } from "./components/LpTopologyCanvas.vue";
|
|
@@ -205,6 +210,11 @@ export type { FieldContext, FieldControlState } from "./composables/useField";
|
|
|
205
210
|
export type { UseInputFilterOptions } from "./composables/useInputFilter";
|
|
206
211
|
export { easeOut, easeTravel, easingToken, prefersReducedMotion } from "./composables/easing";
|
|
207
212
|
export { useReveal } from "./composables/useReveal";
|
|
213
|
+
export { hints, vHint } from "./composables/useHints";
|
|
214
|
+
export type { HintEvent } from "./composables/useHints";
|
|
215
|
+
export { scrollParent, scrollToElement } from "./composables/scroll";
|
|
216
|
+
export type { ScrollParentOptions, ScrollToElementOptions } from "./composables/scroll";
|
|
217
|
+
export { useMediaQuery } from "./composables/useMediaQuery";
|
|
208
218
|
export type { UseReveal, UseRevealOptions } from "./composables/useReveal";
|
|
209
219
|
export { useAsyncTask } from "./composables/useAsyncTask";
|
|
210
220
|
export { useRovingFocus } from "./composables/useRovingFocus";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { _ as _3 } from "./chunks/LpMapPicker-Cc84LaM3.js";
|
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpActionBar-C3hkVsFz.js";
|
|
6
6
|
import { _ as _5 } from "./chunks/LpAlert-B6ilrM7T.js";
|
|
7
|
-
import { _ as _6 } from "./chunks/LpAppShell-
|
|
7
|
+
import { _ as _6 } from "./chunks/LpAppShell-Bl1Ch68N.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAutocomplete-C8IgSMsi.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpAvatar-LTvy0OkE.js";
|
|
10
10
|
import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
|
|
@@ -35,12 +35,12 @@ import { _ as _19 } from "./chunks/LpConfirmHost-BgMIV8r9.js";
|
|
|
35
35
|
import { confirm, useConfirm } from "./components/confirm.js";
|
|
36
36
|
import { _ as _20 } from "./chunks/LpContextMenu-EF_90u3g.js";
|
|
37
37
|
import { _ as _21 } from "./chunks/LpDatePicker-BrdFtSTy.js";
|
|
38
|
-
import { _ as _22 } from "./chunks/LpDateRangePicker-
|
|
38
|
+
import { _ as _22 } from "./chunks/LpDateRangePicker-CxV5jW7p.js";
|
|
39
39
|
import { _ as _23 } from "./chunks/LpRangeCalendar-DZuaRV0W.js";
|
|
40
40
|
import { clampRange, defaultRangePresets, formatRange, normalizeRange, sameRange } from "./components/dateRange.js";
|
|
41
41
|
import { _ as _24 } from "./chunks/LpDisclosure-Cc23-JOT.js";
|
|
42
42
|
import { _ as _25 } from "./chunks/LpDivider-CUX46KR0.js";
|
|
43
|
-
import { _ as _26 } from "./chunks/LpDrawer-
|
|
43
|
+
import { _ as _26 } from "./chunks/LpDrawer-D9lLOC0C.js";
|
|
44
44
|
import { _ as _27 } from "./chunks/LpDropdownMenu-DrpJJBp0.js";
|
|
45
45
|
import { default as default9 } from "./components/LpDropzone.vue.js";
|
|
46
46
|
import { _ as _28 } from "./chunks/LpEmptyState-BJbZXC8D.js";
|
|
@@ -60,7 +60,7 @@ import { _ as _37 } from "./chunks/LpLogViewer-BfgtXVKm.js";
|
|
|
60
60
|
import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
|
|
61
61
|
import { _ as _38 } from "./chunks/LpMediaGallery-BnlkN_1f.js";
|
|
62
62
|
import { _ as _39 } from "./chunks/LpModal-JNa3B_Lr.js";
|
|
63
|
-
import { _ as _40 } from "./chunks/LpModalNav-
|
|
63
|
+
import { _ as _40 } from "./chunks/LpModalNav-DXyAyhQl.js";
|
|
64
64
|
import { _ as _41 } from "./chunks/LpNotificationBell-DEL-RoHS.js";
|
|
65
65
|
import { _ as _42 } from "./chunks/LpNumberField-B77O-z28.js";
|
|
66
66
|
import { _ as _43 } from "./chunks/LpTimeField-Cqe7fXnh.js";
|
|
@@ -68,7 +68,7 @@ import { _ as _44 } from "./chunks/LpNumberFlow-z4WPZcZu.js";
|
|
|
68
68
|
import { _ as _45 } from "./chunks/LpOtpInput-PbeNJ5In.js";
|
|
69
69
|
import { _ as _46 } from "./chunks/LpPagination-BcDlo3r_.js";
|
|
70
70
|
import { _ as _47 } from "./chunks/LpPasswordInput-eKzQhbQP.js";
|
|
71
|
-
import { _ as _48 } from "./chunks/LpPie-
|
|
71
|
+
import { _ as _48 } from "./chunks/LpPie-CXka8Jvx.js";
|
|
72
72
|
import { _ as _49 } from "./chunks/LpPhoneInput-BCbrrhgE.js";
|
|
73
73
|
import { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
|
|
74
74
|
import { _ as _50 } from "./chunks/LpPopover-DCKeqiVn.js";
|
|
@@ -83,7 +83,7 @@ import { _ as _58 } from "./chunks/LpSelect-DNIL6MB9.js";
|
|
|
83
83
|
import { _ as _59 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
|
|
84
84
|
import { _ as _60 } from "./chunks/LpShift-DXNsauQZ.js";
|
|
85
85
|
import { default as default11 } from "./components/LpShortcutsDialog.vue.js";
|
|
86
|
-
import { _ as _61 } from "./chunks/LpSidebar-
|
|
86
|
+
import { _ as _61 } from "./chunks/LpSidebar-hZ8D1cVr.js";
|
|
87
87
|
import { _ as _62 } from "./chunks/LpSkeleton-BB6XIV-w.js";
|
|
88
88
|
import { _ as _63 } from "./chunks/LpSlider-Cuzmrd7p.js";
|
|
89
89
|
import { _ as _64 } from "./chunks/LpRangeSlider-C1yF9-nP.js";
|
|
@@ -95,7 +95,7 @@ import { _ as _68 } from "./chunks/LpStepper-C7XiVKZj.js";
|
|
|
95
95
|
import { _ as _69 } from "./chunks/LpSwitch-DG2AhXIL.js";
|
|
96
96
|
import { default as default12 } from "./components/LpTable.vue.js";
|
|
97
97
|
import { compareValues } from "./components/tableSort.js";
|
|
98
|
-
import { _ as _70 } from "./chunks/LpTableOfContents-
|
|
98
|
+
import { _ as _70 } from "./chunks/LpTableOfContents-DYuqkByy.js";
|
|
99
99
|
import { _ as _71 } from "./chunks/LpTabs-BMSBB78h.js";
|
|
100
100
|
import { _ as _72 } from "./chunks/LpTagsInput-D1SuqbhB.js";
|
|
101
101
|
import { addTags, splitTags } from "./components/tags.js";
|
|
@@ -103,10 +103,13 @@ import { _ as _73 } from "./chunks/LpTextarea-uuScWo6-.js";
|
|
|
103
103
|
import { _ as _74 } from "./chunks/LpThemeSwitcher-Fz85BCRS.js";
|
|
104
104
|
import { _ as _75 } from "./chunks/LpTilt-B7KHX1zH.js";
|
|
105
105
|
import { _ as _76 } from "./chunks/LpToaster-DTtWuKvq.js";
|
|
106
|
-
import { _ as _77 } from "./chunks/LpTooltip-
|
|
107
|
-
import { _ as _78 } from "./chunks/
|
|
108
|
-
import {
|
|
109
|
-
import { _ as
|
|
106
|
+
import { _ as _77 } from "./chunks/LpTooltip-suFCM34F.js";
|
|
107
|
+
import { _ as _78 } from "./chunks/LpTour-C1TC2GyY.js";
|
|
108
|
+
import { holeOf, openContextMenu, panelsAround } from "./components/tour.js";
|
|
109
|
+
import { _ as _79 } from "./chunks/LpHintLayer-CI1oZ9Pd.js";
|
|
110
|
+
import { _ as _80 } from "./chunks/LpTooltipProvider-CftjV7Xc.js";
|
|
111
|
+
import { _ as _81 } from "./chunks/LpTopologyCanvas-6ozNrQKh.js";
|
|
112
|
+
import { _ as _82 } from "./chunks/LpUptimeBar-B4ltCfno.js";
|
|
110
113
|
import { deserializeLayout, serializeLayout } from "./layout/tree.js";
|
|
111
114
|
import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
|
|
112
115
|
import { blockTitle, defineBlocks } from "./layout/registry.js";
|
|
@@ -127,6 +130,9 @@ import { LP_FIELD, fieldMessageId, provideField, useField, useFieldControl } fro
|
|
|
127
130
|
import { FIELD_NOTICE_MS, useFieldNotice } from "./composables/useFieldNotice.js";
|
|
128
131
|
import { easeOut, easeTravel, easingToken, prefersReducedMotion } from "./composables/easing.js";
|
|
129
132
|
import { useReveal } from "./composables/useReveal.js";
|
|
133
|
+
import { hints, vHint } from "./composables/useHints.js";
|
|
134
|
+
import { scrollParent, scrollToElement } from "./composables/scroll.js";
|
|
135
|
+
import { useMediaQuery } from "./composables/useMediaQuery.js";
|
|
130
136
|
import { useAsyncTask } from "./composables/useAsyncTask.js";
|
|
131
137
|
import { useRovingFocus } from "./composables/useRovingFocus.js";
|
|
132
138
|
import { useFormDraft } from "./composables/useFormDraft.js";
|
|
@@ -137,7 +143,7 @@ import { useShift } from "./composables/useShift.js";
|
|
|
137
143
|
import { useTilt } from "./composables/useTilt.js";
|
|
138
144
|
import { useZoomPan } from "./composables/useZoomPan.js";
|
|
139
145
|
import { useToast } from "./composables/useToast.js";
|
|
140
|
-
import { _ as
|
|
146
|
+
import { _ as _83 } from "./chunks/LpThemeProvider-CQ2jC9TA.js";
|
|
141
147
|
import { isInternalHref, resolveLink } from "./components/link.js";
|
|
142
148
|
function defineTheme(base, overrides = {}) {
|
|
143
149
|
const src = typeof base === "string" ? presets[base] : base;
|
|
@@ -209,6 +215,7 @@ export {
|
|
|
209
215
|
_30 as LpFieldNotice,
|
|
210
216
|
_29 as LpFileTree,
|
|
211
217
|
_31 as LpFormField,
|
|
218
|
+
_79 as LpHintLayer,
|
|
212
219
|
_32 as LpIcon,
|
|
213
220
|
default10 as LpImageEditor,
|
|
214
221
|
_33 as LpInfraNode,
|
|
@@ -260,15 +267,16 @@ export {
|
|
|
260
267
|
_71 as LpTabs,
|
|
261
268
|
_72 as LpTagsInput,
|
|
262
269
|
_73 as LpTextarea,
|
|
263
|
-
|
|
270
|
+
_83 as LpThemeProvider,
|
|
264
271
|
_74 as LpThemeSwitcher,
|
|
265
272
|
_75 as LpTilt,
|
|
266
273
|
_43 as LpTimeField,
|
|
267
274
|
_76 as LpToaster,
|
|
268
275
|
_77 as LpTooltip,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
_80 as LpTooltipProvider,
|
|
277
|
+
_81 as LpTopologyCanvas,
|
|
278
|
+
_78 as LpTour,
|
|
279
|
+
_82 as LpUptimeBar,
|
|
272
280
|
_65 as LpVideoBackdrop,
|
|
273
281
|
N as NO_BACKDROP_ATTR,
|
|
274
282
|
OSM_TILES,
|
|
@@ -336,6 +344,8 @@ export {
|
|
|
336
344
|
formatSize,
|
|
337
345
|
formatTick,
|
|
338
346
|
highlightParts,
|
|
347
|
+
hints,
|
|
348
|
+
holeOf,
|
|
339
349
|
hotkeyConflicts,
|
|
340
350
|
hotkeySteps,
|
|
341
351
|
hsvToHsl,
|
|
@@ -370,6 +380,8 @@ export {
|
|
|
370
380
|
normalizeRange,
|
|
371
381
|
k as normalizeTheme,
|
|
372
382
|
opaqueCss,
|
|
383
|
+
openContextMenu,
|
|
384
|
+
panelsAround,
|
|
373
385
|
parseColor,
|
|
374
386
|
parseCommandQuery,
|
|
375
387
|
parseConfig,
|
|
@@ -405,6 +417,8 @@ export {
|
|
|
405
417
|
ringWindow,
|
|
406
418
|
o as rose,
|
|
407
419
|
sameRange,
|
|
420
|
+
scrollParent,
|
|
421
|
+
scrollToElement,
|
|
408
422
|
seamProgress,
|
|
409
423
|
seamReached,
|
|
410
424
|
sequenceProgress,
|
|
@@ -448,6 +462,7 @@ export {
|
|
|
448
462
|
useLayout,
|
|
449
463
|
useLinkComponent,
|
|
450
464
|
useLpConfig,
|
|
465
|
+
useMediaQuery,
|
|
451
466
|
useMergedAttrs,
|
|
452
467
|
useMergedClass,
|
|
453
468
|
useModalLayer,
|
|
@@ -460,5 +475,6 @@ export {
|
|
|
460
475
|
useTilt,
|
|
461
476
|
useToast,
|
|
462
477
|
useZoomPan,
|
|
478
|
+
vHint,
|
|
463
479
|
y as violet
|
|
464
480
|
};
|
package/package.json
CHANGED
package/src/component-names.ts
CHANGED
|
@@ -33,6 +33,7 @@ export const COMPONENT_NAMES = [
|
|
|
33
33
|
"LpFieldNotice",
|
|
34
34
|
"LpFileTree",
|
|
35
35
|
"LpFormField",
|
|
36
|
+
"LpHintLayer",
|
|
36
37
|
"LpIcon",
|
|
37
38
|
"LpImageEditor",
|
|
38
39
|
"LpInfraNode",
|
|
@@ -92,6 +93,7 @@ export const COMPONENT_NAMES = [
|
|
|
92
93
|
"LpTooltip",
|
|
93
94
|
"LpTooltipProvider",
|
|
94
95
|
"LpTopologyCanvas",
|
|
96
|
+
"LpTour",
|
|
95
97
|
"LpUptimeBar",
|
|
96
98
|
"LpVideoBackdrop"
|
|
97
99
|
] as const
|
|
@@ -10,11 +10,12 @@ export default { inheritAttrs: false }
|
|
|
10
10
|
* Two months sit side by side where there is room, one on a phone.
|
|
11
11
|
*/
|
|
12
12
|
import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from "reka-ui"
|
|
13
|
-
import { computed,
|
|
13
|
+
import { computed, ref, useAttrs } from "vue"
|
|
14
14
|
import { useFieldControl } from "../composables/useField"
|
|
15
15
|
import { useLocale } from "../composables/useLocale"
|
|
16
16
|
import { useLabels } from "../composables/useLpConfig"
|
|
17
17
|
import { mergeClass } from "../composables/useMergedClass"
|
|
18
|
+
import { useMediaQuery } from "../composables/useMediaQuery"
|
|
18
19
|
import {
|
|
19
20
|
clampRange,
|
|
20
21
|
type DateRange,
|
|
@@ -113,17 +114,7 @@ function presetFits(preset: DateRangePreset): boolean {
|
|
|
113
114
|
return !!clampRange(preset.range(), props.min, props.max)
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
const wide =
|
|
117
|
-
let query: MediaQueryList | undefined
|
|
118
|
-
const onQuery = () => {
|
|
119
|
-
wide.value = !!query?.matches
|
|
120
|
-
}
|
|
121
|
-
onMounted(() => {
|
|
122
|
-
query = window.matchMedia("(min-width: 640px)")
|
|
123
|
-
onQuery()
|
|
124
|
-
query.addEventListener("change", onQuery)
|
|
125
|
-
})
|
|
126
|
-
onBeforeUnmount(() => query?.removeEventListener("change", onQuery))
|
|
117
|
+
const wide = useMediaQuery("(min-width: 640px)")
|
|
127
118
|
|
|
128
119
|
const shellSize = {
|
|
129
120
|
sm: "h-(--size-control-sm) text-xs",
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "vaul-vue"
|
|
35
35
|
import { computed, onBeforeUnmount, ref, useSlots, watch } from "vue"
|
|
36
36
|
import { CLOSE_ICON } from "./dropdown"
|
|
37
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
37
38
|
import { useGlass } from "../composables/useGlass"
|
|
38
39
|
import { useLabels } from "../composables/useLpConfig"
|
|
39
40
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -328,10 +329,6 @@ function edgeEnabled(): boolean {
|
|
|
328
329
|
let track: { id: number; x: number; y: number; t: number; lastX: number; lastT: number; speed: number } | null = null
|
|
329
330
|
const owner = Symbol("edge-pull")
|
|
330
331
|
|
|
331
|
-
function reducedMotion(): boolean {
|
|
332
|
-
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
333
|
-
}
|
|
334
|
-
|
|
335
332
|
// A horizontally scrollable ancestor that can still move the way the finger
|
|
336
333
|
// pulls (a carousel, a wide table) keeps the gesture: the pull only claims
|
|
337
334
|
// what nothing under the finger wants.
|
|
@@ -414,7 +411,7 @@ function settle() {
|
|
|
414
411
|
peek.value = 0
|
|
415
412
|
}, 60),
|
|
416
413
|
)
|
|
417
|
-
},
|
|
414
|
+
}, prefersReducedMotion() ? 0 : 180),
|
|
418
415
|
)
|
|
419
416
|
} else {
|
|
420
417
|
peek.value = 0
|
|
@@ -462,7 +459,7 @@ const previewStyle = computed(() => {
|
|
|
462
459
|
const axis = dir.value === "right" ? off : -off
|
|
463
460
|
return {
|
|
464
461
|
transform: `translateX(${axis}%)`,
|
|
465
|
-
transition: peeking.value ||
|
|
462
|
+
transition: peeking.value || prefersReducedMotion() ? "none" : "transform 0.18s cubic-bezier(0.32, 0.72, 0, 1)",
|
|
466
463
|
pointerEvents: "none" as const,
|
|
467
464
|
}
|
|
468
465
|
})
|
|
@@ -477,7 +474,7 @@ const previewStyle = computed(() => {
|
|
|
477
474
|
v-show="peeking || peek > 0"
|
|
478
475
|
class="lp-scrim pointer-events-none fixed inset-0"
|
|
479
476
|
:class="{ 'lp-scrim--light': frosted, 'lp-scrim--nested': layer.nested }"
|
|
480
|
-
:style="{ zIndex: layer.scrim, opacity: peek, transition: peeking ||
|
|
477
|
+
:style="{ zIndex: layer.scrim, opacity: peek, transition: peeking || prefersReducedMotion() ? 'none' : 'opacity 0.18s ease-out' }"
|
|
481
478
|
/>
|
|
482
479
|
<div
|
|
483
480
|
v-show="peeking || peek > 0"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface HintNote {
|
|
3
|
+
title: string
|
|
4
|
+
text?: string
|
|
5
|
+
}
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
/*
|
|
10
|
+
* Quiet "new" markers over `v-hint` elements, with a short note when the user
|
|
11
|
+
* reaches for one. Nothing pops up by itself: a dot sits on the control, and
|
|
12
|
+
* the note opens only when the pointer or focus lands on it. Reading the note
|
|
13
|
+
* (or simply using the control) is the app's cue to stop marking it.
|
|
14
|
+
*
|
|
15
|
+
* The layer only draws. Which ids are new, and when a read counts, is decided
|
|
16
|
+
* by the app from the registry's seen/used events.
|
|
17
|
+
*/
|
|
18
|
+
import { PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot } from "reka-ui"
|
|
19
|
+
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue"
|
|
20
|
+
import { hints } from "../composables/useHints"
|
|
21
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
22
|
+
import { PANEL_SURFACE, POPOVER_ANIM } from "./dropdown"
|
|
23
|
+
import LpButton from "./LpButton.vue"
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(
|
|
26
|
+
defineProps<{
|
|
27
|
+
/** Ids to mark, most important first. */
|
|
28
|
+
fresh: string[]
|
|
29
|
+
notes: Record<string, HintNote>
|
|
30
|
+
/** At most this many dots at once, so a page full of news stays calm. */
|
|
31
|
+
limit?: number
|
|
32
|
+
labels?: Partial<typeof DEFAULT_LABELS>
|
|
33
|
+
}>(),
|
|
34
|
+
{ limit: 3 },
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{ (e: "read", id: string): void }>()
|
|
38
|
+
|
|
39
|
+
const DEFAULT_LABELS = { gotIt: "Got it", badge: "New" }
|
|
40
|
+
const labels = useLabels("hintLayer", DEFAULT_LABELS, () => props.labels)
|
|
41
|
+
|
|
42
|
+
const OPEN_DELAY_MS = 350
|
|
43
|
+
|
|
44
|
+
const shown = computed(() => props.fresh.filter((id) => hints.present(id) && props.notes[id]).slice(0, props.limit))
|
|
45
|
+
const spots = ref<{ id: string; x: number; y: number }[]>([])
|
|
46
|
+
const openId = ref<string | null>(null)
|
|
47
|
+
const anchor = shallowRef<Element | null>(null)
|
|
48
|
+
let frame = 0
|
|
49
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
50
|
+
|
|
51
|
+
function place() {
|
|
52
|
+
spots.value = shown.value.flatMap((id) => {
|
|
53
|
+
const el = hints.element(id)
|
|
54
|
+
if (!el) return []
|
|
55
|
+
const r = el.getBoundingClientRect()
|
|
56
|
+
return r.width || r.height ? [{ id, x: r.right, y: r.top }] : []
|
|
57
|
+
})
|
|
58
|
+
frame = shown.value.length ? requestAnimationFrame(place) : 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
watch(
|
|
62
|
+
shown,
|
|
63
|
+
(ids) => {
|
|
64
|
+
if (ids.length && !frame) frame = requestAnimationFrame(place)
|
|
65
|
+
if (openId.value && !ids.includes(openId.value)) openId.value = null
|
|
66
|
+
},
|
|
67
|
+
{ immediate: true },
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
function reveal(id: string) {
|
|
71
|
+
clearTimeout(timer)
|
|
72
|
+
timer = setTimeout(() => {
|
|
73
|
+
if (!shown.value.includes(id)) return
|
|
74
|
+
anchor.value = hints.element(id)
|
|
75
|
+
openId.value = anchor.value ? id : null
|
|
76
|
+
}, OPEN_DELAY_MS)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function read() {
|
|
80
|
+
const id = openId.value
|
|
81
|
+
openId.value = null
|
|
82
|
+
if (id) emit("read", id)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let off: (() => void)[] = []
|
|
86
|
+
onMounted(() => {
|
|
87
|
+
off = [
|
|
88
|
+
hints.on("hover", (id) => {
|
|
89
|
+
if (shown.value.includes(id)) reveal(id)
|
|
90
|
+
}),
|
|
91
|
+
hints.on("used", (id) => {
|
|
92
|
+
if (!shown.value.includes(id)) return
|
|
93
|
+
clearTimeout(timer)
|
|
94
|
+
if (openId.value === id) openId.value = null
|
|
95
|
+
emit("read", id)
|
|
96
|
+
}),
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
onBeforeUnmount(() => {
|
|
101
|
+
cancelAnimationFrame(frame)
|
|
102
|
+
clearTimeout(timer)
|
|
103
|
+
for (const stop of off) stop()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const note = computed(() => (openId.value ? props.notes[openId.value] : undefined))
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<template>
|
|
110
|
+
<Teleport to="body">
|
|
111
|
+
<button
|
|
112
|
+
v-for="spot in spots"
|
|
113
|
+
:key="spot.id"
|
|
114
|
+
type="button"
|
|
115
|
+
class="fixed z-(--z-popover) -translate-x-1/2 -translate-y-1/2 rounded-full p-1"
|
|
116
|
+
:style="{ left: `${spot.x}px`, top: `${spot.y}px` }"
|
|
117
|
+
:aria-label="`${labels.badge}: ${notes[spot.id]?.title ?? ''}`"
|
|
118
|
+
@pointerenter="reveal(spot.id)"
|
|
119
|
+
@focus="reveal(spot.id)"
|
|
120
|
+
@click="reveal(spot.id)"
|
|
121
|
+
>
|
|
122
|
+
<span class="relative flex size-2">
|
|
123
|
+
<span class="absolute inline-flex size-full animate-ping rounded-full bg-brand opacity-60 motion-reduce:animate-none" />
|
|
124
|
+
<span class="relative inline-flex size-2 rounded-full bg-brand ring-2 ring-surface" />
|
|
125
|
+
</span>
|
|
126
|
+
</button>
|
|
127
|
+
<PopoverRoot :open="!!openId" @update:open="(value) => !value && (openId = null)">
|
|
128
|
+
<PopoverAnchor v-if="anchor" :reference="anchor" />
|
|
129
|
+
<PopoverPortal>
|
|
130
|
+
<PopoverContent
|
|
131
|
+
side="bottom"
|
|
132
|
+
:side-offset="8"
|
|
133
|
+
:collision-padding="8"
|
|
134
|
+
:class="[PANEL_SURFACE, POPOVER_ANIM, 'z-(--z-popover) w-72 rounded-card p-3 text-sm text-ink outline-none']"
|
|
135
|
+
>
|
|
136
|
+
<p class="flex items-center gap-2 font-medium">
|
|
137
|
+
<span class="rounded-full bg-brand/15 px-1.5 text-[11px] text-brand">{{ labels.badge }}</span>
|
|
138
|
+
{{ note?.title }}
|
|
139
|
+
</p>
|
|
140
|
+
<p v-if="note?.text" class="mt-1.5 text-muted">{{ note.text }}</p>
|
|
141
|
+
<div class="mt-3 flex justify-end">
|
|
142
|
+
<LpButton size="sm" @click="read">{{ labels.gotIt }}</LpButton>
|
|
143
|
+
</div>
|
|
144
|
+
</PopoverContent>
|
|
145
|
+
</PopoverPortal>
|
|
146
|
+
</PopoverRoot>
|
|
147
|
+
</Teleport>
|
|
148
|
+
</template>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { Motion } from "motion-v"
|
|
13
13
|
import { computed, onBeforeUnmount, onMounted, ref, useId, watch } from "vue"
|
|
14
14
|
import { useLabels } from "../composables/useLpConfig"
|
|
15
|
+
import { scrollParent } from "../composables/scroll"
|
|
15
16
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
16
17
|
|
|
17
18
|
export interface TocLink {
|
|
@@ -113,24 +114,11 @@ function unfreeze() {
|
|
|
113
114
|
safetyTimer = null
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
/*
|
|
117
|
-
* The element that actually scrolls to the heading. Inside an app shell that is
|
|
118
|
-
* a scroll region, not the window, and watching window.scrollY there saw no
|
|
119
|
-
* movement and unfroze on the first frame.
|
|
120
|
-
*/
|
|
121
|
-
function scrollerOf(el: HTMLElement): HTMLElement | null {
|
|
122
|
-
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
123
|
-
const { overflowY } = getComputedStyle(node)
|
|
124
|
-
if (/(auto|scroll|overlay)/.test(overflowY) && node.scrollHeight > node.clientHeight) return node
|
|
125
|
-
}
|
|
126
|
-
return null
|
|
127
|
-
}
|
|
128
|
-
|
|
129
117
|
function suppressSpyUntilScrollEnds(target: HTMLElement) {
|
|
130
118
|
spySuppressed = true
|
|
131
119
|
if (rafId != null) cancelAnimationFrame(rafId)
|
|
132
120
|
if (safetyTimer != null) clearTimeout(safetyTimer)
|
|
133
|
-
const scroller =
|
|
121
|
+
const scroller = scrollParent(target)
|
|
134
122
|
const position = () => (scroller ? scroller.scrollTop : window.scrollY)
|
|
135
123
|
let last = position()
|
|
136
124
|
const tick = () => {
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
type SpringState,
|
|
43
43
|
} from "../composables/usePillTransition"
|
|
44
44
|
import { PANEL_SURFACE, TOOLTIP_ANIM } from "./dropdown"
|
|
45
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
45
46
|
|
|
46
47
|
const props = withDefaults(
|
|
47
48
|
defineProps<{
|
|
@@ -116,9 +117,6 @@ const shared = injectTooltipProviderContext(null)
|
|
|
116
117
|
*/
|
|
117
118
|
const PassThrough: FunctionalComponent = (_, { slots }) => slots.default?.()
|
|
118
119
|
|
|
119
|
-
const reducedMotion = () =>
|
|
120
|
-
typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
121
|
-
|
|
122
120
|
const target = { x: 0, y: 0 }
|
|
123
121
|
const offset = ref({ x: 0, y: 0 })
|
|
124
122
|
|
|
@@ -174,7 +172,7 @@ const onPointerMove = (e: PointerEvent) => {
|
|
|
174
172
|
const dir = props.side === "bottom" ? -1 : 1
|
|
175
173
|
target.y = e.clientY - (rect.top + rect.height / 2) + half * dir
|
|
176
174
|
// Without motion the tip goes straight to the reading instead of trailing.
|
|
177
|
-
if (
|
|
175
|
+
if (prefersReducedMotion()) {
|
|
178
176
|
offset.value = { x: target.x, y: target.y }
|
|
179
177
|
return
|
|
180
178
|
}
|