@leavepulse/ui 0.55.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/{LpActionBar-CnfFVIwC.js → LpActionBar-C3hkVsFz.js} +1 -1
- package/dist/chunks/{LpAddressInput-iOJhmRzy.js → LpAddressInput-NUHmplCd.js} +1 -1
- package/dist/chunks/{LpAppShell-DcSXik4a.js → LpAppShell-Bl1Ch68N.js} +2 -2
- package/dist/chunks/{LpButton-CKSwC6Mu.js → LpButton-BdfZFNqi.js} +31 -7
- package/dist/chunks/{LpConfirmDialog-Ca-CTlxT.js → LpConfirmDialog-CrbUZgJE.js} +1 -1
- package/dist/chunks/{LpConfirmHost-BAaSaT2b.js → LpConfirmHost-BgMIV8r9.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-aWwc_5CD.js → LpModalNav-DXyAyhQl.js} +2 -2
- package/dist/chunks/{LpNotificationBell-wZ3meeim.js → LpNotificationBell-DEL-RoHS.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/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +10 -0
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.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/LpImageEditor.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpTable.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 +38 -22
- package/package.json +1 -1
- package/src/component-names.ts +2 -0
- package/src/components/LpButton.vue +28 -1
- 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
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { _ } from "./chunks/LayoutCanvas-BkaGXTjx.js";
|
|
2
|
-
import { _ as _2 } from "./chunks/LpAddressInput-
|
|
2
|
+
import { _ as _2 } from "./chunks/LpAddressInput-NUHmplCd.js";
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-Cc84LaM3.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
|
-
import { _ as _4 } from "./chunks/LpActionBar-
|
|
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";
|
|
11
11
|
import { _ as _10 } from "./chunks/LpBreadcrumbs-D4TsoFju.js";
|
|
12
|
-
import { _ as _11 } from "./chunks/LpButton-
|
|
12
|
+
import { _ as _11 } from "./chunks/LpButton-BdfZFNqi.js";
|
|
13
13
|
import { _ as _12 } from "./chunks/LpCalendar-DrA2McTU.js";
|
|
14
14
|
import { _ as _13 } from "./chunks/LpCard-DbjO5yaF.js";
|
|
15
15
|
import { default as default2 } from "./components/LpChart.vue.js";
|
|
@@ -30,17 +30,17 @@ import { default as default6 } from "./components/LpPaletteStats.vue.js";
|
|
|
30
30
|
import { default as default7 } from "./components/LpPaletteSwatches.vue.js";
|
|
31
31
|
import { default as default8 } from "./components/LpKeys.vue.js";
|
|
32
32
|
import { comboKeys, comboSteps } from "./components/keys.js";
|
|
33
|
-
import { _ as _18 } from "./chunks/LpConfirmDialog-
|
|
34
|
-
import { _ as _19 } from "./chunks/LpConfirmHost-
|
|
33
|
+
import { _ as _18 } from "./chunks/LpConfirmDialog-CrbUZgJE.js";
|
|
34
|
+
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,15 +60,15 @@ 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-
|
|
64
|
-
import { _ as _41 } from "./chunks/LpNotificationBell-
|
|
63
|
+
import { _ as _40 } from "./chunks/LpModalNav-DXyAyhQl.js";
|
|
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";
|
|
67
67
|
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
|
|
@@ -9,6 +9,7 @@ import { tv, type VariantProps } from "tailwind-variants"
|
|
|
9
9
|
import { computed, type Component } from "vue"
|
|
10
10
|
import { useLinkComponent } from "../composables/useLpConfig"
|
|
11
11
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
12
|
+
import LpIcon from "./LpIcon.vue"
|
|
12
13
|
import { resolveLink, type LinkTarget } from "./link"
|
|
13
14
|
|
|
14
15
|
const button = tv({
|
|
@@ -47,6 +48,8 @@ const button = tv({
|
|
|
47
48
|
square: { true: "aspect-square p-0" },
|
|
48
49
|
// soft elevation glow keyed to the variant's colour
|
|
49
50
|
glow: { true: "" },
|
|
51
|
+
// busy: the label fades out in place (no width jump) and a spinner sits on top
|
|
52
|
+
loading: { true: "relative cursor-progress !text-transparent hover:!text-transparent" },
|
|
50
53
|
},
|
|
51
54
|
compoundVariants: [
|
|
52
55
|
{ square: true, size: "xs", class: "size-8" },
|
|
@@ -96,6 +99,11 @@ const props = withDefaults(
|
|
|
96
99
|
href?: string
|
|
97
100
|
type?: "button" | "submit" | "reset"
|
|
98
101
|
disabled?: boolean
|
|
102
|
+
/**
|
|
103
|
+
* Busy state: blocks clicks like `disabled` without dimming, fades the label out
|
|
104
|
+
* in place and shows a spinner over it, so the width never jumps. Sets `aria-busy`.
|
|
105
|
+
*/
|
|
106
|
+
loading?: boolean
|
|
99
107
|
}>(),
|
|
100
108
|
{ type: "button" },
|
|
101
109
|
)
|
|
@@ -119,11 +127,20 @@ const isNativeButton = computed(() => resolved.value.is === "button")
|
|
|
119
127
|
* own handler, which skips navigation once the event is default-prevented.
|
|
120
128
|
*/
|
|
121
129
|
function blockDisabled(event: MouseEvent) {
|
|
122
|
-
if (!props.disabled || isNativeButton.value) return
|
|
130
|
+
if (!props.loading && (!props.disabled || isNativeButton.value)) return
|
|
123
131
|
event.preventDefault()
|
|
124
132
|
event.stopImmediatePropagation()
|
|
125
133
|
}
|
|
126
134
|
|
|
135
|
+
const SPINNER_TONE: Record<string, string> = {
|
|
136
|
+
solid: "text-ink-inverse",
|
|
137
|
+
action: "text-ink-inverse",
|
|
138
|
+
danger: "text-ink-inverse",
|
|
139
|
+
light: "text-[#0b1210]",
|
|
140
|
+
muted: "text-muted",
|
|
141
|
+
}
|
|
142
|
+
const spinnerTone = computed(() => SPINNER_TONE[props.variant ?? "solid"] ?? "text-ink")
|
|
143
|
+
|
|
127
144
|
const linkProps = computed(() => {
|
|
128
145
|
if (isNativeButton.value) return {}
|
|
129
146
|
if (!props.disabled) return resolved.value.props
|
|
@@ -137,6 +154,7 @@ const { class: classes, attrs: rest } = useMergedAttrs(() =>
|
|
|
137
154
|
block: props.block,
|
|
138
155
|
square: props.square,
|
|
139
156
|
glow: props.glow,
|
|
157
|
+
loading: props.loading,
|
|
140
158
|
}),
|
|
141
159
|
)
|
|
142
160
|
</script>
|
|
@@ -149,9 +167,18 @@ const { class: classes, attrs: rest } = useMergedAttrs(() =>
|
|
|
149
167
|
:disabled="isNativeButton ? disabled : undefined"
|
|
150
168
|
:aria-disabled="!isNativeButton && disabled ? 'true' : undefined"
|
|
151
169
|
:tabindex="!isNativeButton && disabled ? -1 : undefined"
|
|
170
|
+
:aria-busy="loading ? 'true' : undefined"
|
|
152
171
|
:class="classes"
|
|
153
172
|
@click.capture="blockDisabled"
|
|
154
173
|
>
|
|
155
174
|
<slot />
|
|
175
|
+
<span
|
|
176
|
+
v-if="loading"
|
|
177
|
+
class="pointer-events-none absolute inset-0 flex items-center justify-center animate-[fade-in_var(--duration-fast)_var(--ease-emphasized)] motion-reduce:animate-none"
|
|
178
|
+
:class="spinnerTone"
|
|
179
|
+
aria-hidden="true"
|
|
180
|
+
>
|
|
181
|
+
<LpIcon name="lucide:loader-circle" class="size-[1.15em] animate-spin motion-reduce:animate-none" />
|
|
182
|
+
</span>
|
|
156
183
|
</component>
|
|
157
184
|
</template>
|
|
@@ -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
|
}
|