@leavepulse/ui 0.93.3 → 0.94.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 (76) hide show
  1. package/README.md +53 -3
  2. package/dist/assets/LpTabs-D7EyG21l.css +18 -0
  3. package/dist/assets/LpVideoTrim-D8JDg9Qn.css +8 -0
  4. package/dist/chunks/AppShellContextNav-BoH4ffnC.js +54 -0
  5. package/dist/chunks/{LpAppShell-BWaVRsDB.js → LpAppShell-xEBmGV78.js} +12 -5
  6. package/dist/chunks/{LpDateRangePicker-CAn2yGbn.js → LpDateRangePicker-DRBB8z6t.js} +1 -1
  7. package/dist/chunks/{LpDrawer-iRHwJOGn.js → LpDrawer-BkWjFdV0.js} +81 -42
  8. package/dist/chunks/{LpLightbox-a24V_0Wk.js → LpLightbox-DPgz7IsT.js} +62 -49
  9. package/dist/chunks/{LpMapPicker-DVfiy3rR.js → LpMapPicker-V0Lybi10.js} +19 -52
  10. package/dist/chunks/{LpMediaGallery-CtWA5Uab.js → LpMediaGallery-CTejKF2-.js} +1 -1
  11. package/dist/chunks/{LpModalNav-DivqT1NV.js → LpModalNav-DVXAF-DG.js} +1 -1
  12. package/dist/chunks/{LpRangeCalendar-C9gkNMYw.js → LpRangeCalendar-9BvaLZji.js} +3 -4
  13. package/dist/chunks/LpScrollStrip-D4tsOfAQ.js +78 -0
  14. package/dist/chunks/{LpSidebar-DQtCYlT4.js → LpSidebar-BIhusppV.js} +1 -1
  15. package/dist/component-names.d.ts +1 -1
  16. package/dist/components/AppShellContextNav.vue.js +1 -1
  17. package/dist/components/LpAppShell.vue.js +1 -1
  18. package/dist/components/LpChart.vue.js +3 -3
  19. package/dist/components/LpDateRangePicker.vue.js +1 -1
  20. package/dist/components/LpDrawer.vue.d.ts +10 -2
  21. package/dist/components/LpDrawer.vue.js +1 -1
  22. package/dist/components/LpDropzone.vue.d.ts +1 -1
  23. package/dist/components/LpLightbox.vue.js +1 -1
  24. package/dist/components/LpMapPicker.vue.js +1 -1
  25. package/dist/components/LpMediaGallery.vue.js +1 -1
  26. package/dist/components/LpModalNav.vue.js +1 -1
  27. package/dist/components/LpRangeCalendar.vue.js +1 -1
  28. package/dist/components/LpRevealOnScroll.vue.d.ts +1 -1
  29. package/dist/components/LpScrollStrip.vue.d.ts +48 -0
  30. package/dist/components/LpScrollStrip.vue.js +4 -0
  31. package/dist/components/LpSidebar.vue.js +1 -1
  32. package/dist/components/LpTabs.vue.d.ts +4 -4
  33. package/dist/components/LpTabs.vue.js +104 -90
  34. package/dist/components/LpVideoTrim.vue.js +41 -54
  35. package/dist/composables/gesture.d.ts +141 -0
  36. package/dist/composables/gesture.js +176 -0
  37. package/dist/composables/scroll.d.ts +5 -1
  38. package/dist/composables/scroll.js +16 -9
  39. package/dist/composables/useAppUpdate.d.ts +26 -0
  40. package/dist/composables/useAppUpdate.js +91 -0
  41. package/dist/composables/useGesture.d.ts +107 -0
  42. package/dist/composables/useGesture.js +281 -0
  43. package/dist/composables/usePaintSelect.js +3 -4
  44. package/dist/composables/useScrollStrip.d.ts +27 -0
  45. package/dist/composables/useScrollStrip.js +194 -0
  46. package/dist/composables/useTouchHold.d.ts +3 -1
  47. package/dist/composables/useTouchHold.js +9 -51
  48. package/dist/composables/useZoomPan.js +4 -6
  49. package/dist/index.d.ts +9 -0
  50. package/dist/index.js +180 -152
  51. package/package.json +1 -1
  52. package/src/component-names.ts +1 -0
  53. package/src/components/AppShellContextNav.vue +3 -2
  54. package/src/components/LpAppShell.vue +10 -1
  55. package/src/components/LpChart.vue +2 -2
  56. package/src/components/LpDrawer.vue +121 -54
  57. package/src/components/LpLightbox.vue +33 -20
  58. package/src/components/LpMapPicker.vue +22 -64
  59. package/src/components/LpRangeCalendar.vue +4 -5
  60. package/src/components/LpScrollStrip.vue +96 -0
  61. package/src/components/LpSidebar.vue +2 -2
  62. package/src/components/LpTabs.vue +36 -17
  63. package/src/components/LpVideoTrim.vue +35 -46
  64. package/src/composables/gesture.ts +314 -0
  65. package/src/composables/scroll.ts +20 -9
  66. package/src/composables/useAppUpdate.ts +156 -0
  67. package/src/composables/useGesture.ts +451 -0
  68. package/src/composables/usePaintSelect.ts +4 -5
  69. package/src/composables/useScrollStrip.ts +249 -0
  70. package/src/composables/useTouchHold.ts +15 -73
  71. package/src/composables/useZoomPan.ts +6 -9
  72. package/src/index.ts +27 -0
  73. package/src/tokens/effects.css +40 -0
  74. package/dist/assets/LpTabs-Bh7a-Bdl.css +0 -18
  75. package/dist/assets/LpVideoTrim-CMoUw3dJ.css +0 -8
  76. package/dist/chunks/AppShellContextNav-C6Ho2mof.js +0 -49
@@ -0,0 +1,249 @@
1
+ import { onBeforeUnmount, ref, toValue, watch, type MaybeRefOrGetter, type Ref } from "vue"
2
+ import { prefersReducedMotion } from "./easing"
3
+ import { momentumDistance, wheelPixels } from "./gesture"
4
+ import { isHTMLElement, nodeWindow } from "./realm"
5
+ import { scrollToElement } from "./scroll"
6
+ import { useGesture } from "./useGesture"
7
+
8
+ /*
9
+ * A row that scrolls sideways, made reachable without a touch screen. A
10
+ * hidden scrollbar leaves a mouse with no way to the items off to the right,
11
+ * so:
12
+ *
13
+ * - The wheel scrolls the row while the row can still move that way. At
14
+ * either end it does nothing, and the page scrolls on as usual; a sideways
15
+ * wheel or touchpad is left to the browser. Notches ease on a frame loop of
16
+ * our own, so quick notches add up instead of restarting.
17
+ * - A mouse or pen drags the row. A press that stays within a few pixels is
18
+ * an ordinary click on the item under it; a drag past that swallows the
19
+ * click, and a release while moving coasts on.
20
+ * - The focused item is kept in view, scrolling the row and nothing else
21
+ * (scrollIntoView would move every scrollable ancestor too).
22
+ * - Touch keeps the browser's own scrolling and scroll snap; a snapping row
23
+ * lets the browser snap the wheel and the end of a drag.
24
+ *
25
+ * `atStart`/`atEnd`/`overflowing` drive the edge fades and the buttons.
26
+ */
27
+
28
+ export interface ScrollStripOptions {
29
+ /** Map a vertical wheel to sideways scrolling. Default true. */
30
+ wheel?: MaybeRefOrGetter<boolean | undefined>
31
+ /** Drag to scroll with a mouse or pen. Default true. */
32
+ drag?: MaybeRefOrGetter<boolean | undefined>
33
+ /** Room kept beside a focused item, so it clears the fade. Default 24. */
34
+ focusMargin?: number
35
+ }
36
+
37
+ export interface ScrollStrip {
38
+ overflowing: Ref<boolean>
39
+ atStart: Ref<boolean>
40
+ atEnd: Ref<boolean>
41
+ dragging: Ref<boolean>
42
+ /** Scroll most of a view's width toward the start (-1) or the end (1). */
43
+ page: (direction: -1 | 1) => void
44
+ /** Scroll by `px`, eased unless reduced motion asks otherwise. */
45
+ scrollBy: (px: number) => void
46
+ /** Bring `el` into view within the strip only (`center` puts it in the middle). */
47
+ reveal: (el: Element | null | undefined, options?: { smooth?: boolean; block?: "nearest" | "center" }) => void
48
+ /** Measure again (after content changed in a way nothing observed). */
49
+ measure: () => void
50
+ }
51
+
52
+ const PAGE_SHARE = 0.8
53
+
54
+ export function useScrollStrip(target: MaybeRefOrGetter<HTMLElement | null | undefined>, options: ScrollStripOptions = {}): ScrollStrip {
55
+ const overflowing = ref(false)
56
+ const atStart = ref(true)
57
+ const atEnd = ref(true)
58
+ const el = () => {
59
+ const box = toValue(target)
60
+ return isHTMLElement(box) ? box : null
61
+ }
62
+
63
+ function measure() {
64
+ const box = el()
65
+ if (!box) return
66
+ const max = box.scrollWidth - box.clientWidth
67
+ const left = Math.abs(box.scrollLeft)
68
+ overflowing.value = max > 1
69
+ atStart.value = left <= 1
70
+ atEnd.value = left >= max - 1
71
+ }
72
+
73
+ const maxScroll = (box: HTMLElement) => Math.max(0, box.scrollWidth - box.clientWidth)
74
+ const snaps = (box: HTMLElement) => {
75
+ const type = nodeWindow(box).getComputedStyle(box).scrollSnapType
76
+ return !!type && type !== "none"
77
+ }
78
+
79
+ let frame = 0
80
+ let goal: number | null = null
81
+ let restoreBehavior: (() => void) | null = null
82
+
83
+ function stopEase() {
84
+ const box = el()
85
+ if (frame && box) nodeWindow(box).cancelAnimationFrame(frame)
86
+ frame = 0
87
+ goal = null
88
+ restoreBehavior?.()
89
+ restoreBehavior = null
90
+ }
91
+
92
+ function easeTo(box: HTMLElement, to: number) {
93
+ const win = nodeWindow(box)
94
+ goal = Math.max(0, Math.min(maxScroll(box), to))
95
+ if (prefersReducedMotion(win)) {
96
+ box.scrollLeft = goal
97
+ goal = null
98
+ return
99
+ }
100
+ if (frame) return
101
+ let previous = win.performance.now()
102
+ let position = box.scrollLeft
103
+ const behavior = box.style.scrollBehavior
104
+ box.style.scrollBehavior = "auto"
105
+ restoreBehavior = () => (box.style.scrollBehavior = behavior)
106
+ const done = () => {
107
+ frame = 0
108
+ goal = null
109
+ restoreBehavior?.()
110
+ restoreBehavior = null
111
+ }
112
+ const tick = (now: number) => {
113
+ if (goal === null) {
114
+ done()
115
+ return
116
+ }
117
+ const dt = Math.min(now - previous, 32)
118
+ previous = now
119
+ position += (goal - position) * (1 - Math.exp(-dt / 70))
120
+ if (Math.abs(goal - position) < 0.5) position = goal
121
+ box.scrollLeft = position
122
+ if (position === goal) {
123
+ done()
124
+ return
125
+ }
126
+ frame = win.requestAnimationFrame(tick)
127
+ }
128
+ frame = win.requestAnimationFrame(tick)
129
+ }
130
+
131
+ function scrollBy(px: number) {
132
+ const box = el()
133
+ if (!box) return
134
+ if (snaps(box)) {
135
+ box.scrollBy({ left: px, behavior: prefersReducedMotion(nodeWindow(box)) ? "auto" : "smooth" })
136
+ return
137
+ }
138
+ easeTo(box, (goal ?? box.scrollLeft) + px)
139
+ }
140
+
141
+ function page(direction: -1 | 1) {
142
+ const box = el()
143
+ if (box) scrollBy(direction * box.clientWidth * PAGE_SHARE)
144
+ }
145
+
146
+ function reveal(item: Element | null | undefined, { smooth = true, block = "nearest" }: { smooth?: boolean; block?: "nearest" | "center" } = {}) {
147
+ const box = el()
148
+ if (!box || !item || !box.contains(item)) return
149
+ stopEase()
150
+ const behavior = smooth && !prefersReducedMotion(nodeWindow(box)) ? "smooth" : "auto"
151
+ scrollToElement(item, { axis: "x", container: box, margin: options.focusMargin ?? 24, behavior, block })
152
+ }
153
+
154
+ function onWheel(e: WheelEvent) {
155
+ const box = el()
156
+ if (!box || toValue(options.wheel) === false || e.ctrlKey || e.defaultPrevented) return
157
+ if (Math.abs(e.deltaX) >= Math.abs(e.deltaY)) return
158
+ const max = maxScroll(box)
159
+ if (max <= 1) return
160
+ const px = wheelPixels(e.shiftKey ? e.deltaX || e.deltaY : e.deltaY, e.deltaMode, box.clientWidth)
161
+ const from = goal ?? box.scrollLeft
162
+ if ((px > 0 && from >= max - 1) || (px < 0 && from <= 1)) return
163
+ e.preventDefault()
164
+ const notch = e.deltaMode !== 0 || Math.abs(px) >= 50
165
+ if (notch) scrollBy(px)
166
+ else {
167
+ stopEase()
168
+ box.scrollLeft += px
169
+ }
170
+ }
171
+
172
+ function onFocusin(e: FocusEvent) {
173
+ const item = e.target
174
+ if (!isHTMLElement(item) || gesture.dragging.value) return
175
+ try {
176
+ if (!item.matches(":focus-visible")) return
177
+ } catch {}
178
+ reveal(item)
179
+ }
180
+
181
+ let snapType: string | null = null
182
+ const gesture = useGesture(el, {
183
+ pointerTypes: ["mouse", "pen"],
184
+ enabled: () => toValue(options.drag) !== false && overflowing.value,
185
+ onDragStart() {
186
+ const box = el()
187
+ if (!box) return
188
+ stopEase()
189
+ if (snaps(box)) {
190
+ snapType = box.style.scrollSnapType
191
+ box.style.scrollSnapType = "none"
192
+ }
193
+ box.style.scrollBehavior = "auto"
194
+ },
195
+ onDrag({ delta }) {
196
+ const box = el()
197
+ if (box) box.scrollLeft -= delta.x
198
+ },
199
+ onDragEnd({ velocity, cancelled }) {
200
+ const box = el()
201
+ if (!box) return
202
+ box.style.scrollBehavior = ""
203
+ const travel = cancelled ? 0 : -momentumDistance(velocity.x)
204
+ if (snapType !== null) {
205
+ box.style.scrollSnapType = snapType
206
+ snapType = null
207
+ box.scrollBy({ left: travel, behavior: prefersReducedMotion(nodeWindow(box)) ? "auto" : "smooth" })
208
+ return
209
+ }
210
+ if (Math.abs(travel) > 4) easeTo(box, box.scrollLeft + travel)
211
+ },
212
+ })
213
+
214
+ watch(
215
+ () => el(),
216
+ (box, _, onCleanup) => {
217
+ if (!box) return
218
+ const win = nodeWindow(box)
219
+ const resize = new win.ResizeObserver(measure)
220
+ const observeAll = () => {
221
+ resize.disconnect()
222
+ resize.observe(box)
223
+ for (const child of Array.from(box.children)) resize.observe(child)
224
+ }
225
+ const mutations = new win.MutationObserver(() => {
226
+ observeAll()
227
+ measure()
228
+ })
229
+ observeAll()
230
+ mutations.observe(box, { childList: true })
231
+ box.addEventListener("scroll", measure, { passive: true })
232
+ box.addEventListener("wheel", onWheel, { passive: false })
233
+ box.addEventListener("focusin", onFocusin)
234
+ measure()
235
+ onCleanup(() => {
236
+ resize.disconnect()
237
+ mutations.disconnect()
238
+ box.removeEventListener("scroll", measure)
239
+ box.removeEventListener("wheel", onWheel)
240
+ box.removeEventListener("focusin", onFocusin)
241
+ stopEase()
242
+ })
243
+ },
244
+ { immediate: true, flush: "post" },
245
+ )
246
+ onBeforeUnmount(stopEase)
247
+
248
+ return { overflowing, atStart, atEnd, dragging: gesture.dragging, page, scrollBy, reveal, measure }
249
+ }
@@ -1,19 +1,16 @@
1
1
  import { onBeforeUnmount } from "vue"
2
+ import { createGesture } from "./useGesture"
2
3
 
3
4
  /*
4
5
  * Press-and-hold on a touch screen, for things a pointer reaches by hovering
5
- * (a hover card, a tooltip). A hold that drifts is a scroll, not a hold, so
6
- * moving past a few pixels cancels it. Once it has fired, the click the finger
7
- * produces on lifting is swallowed: the reader asked for the preview, not for
8
- * the link or button under it. A mouse or pen is left alone.
6
+ * (a hover card, a tooltip). The kit's gesture engine's long press, limited to
7
+ * touch: a hold that drifts past the slop is a scroll, not a hold; once it has
8
+ * fired, the click the finger produces on lifting is swallowed (the reader
9
+ * asked for the preview, not for the link or button under it), and the
10
+ * system's own long-press menu is kept away. A mouse or pen is left alone.
9
11
  *
10
- * The handlers go on the trigger: pointerdown, pointermove, pointerup,
11
- * pointercancel, click (capture) and contextmenu.
12
+ * The handlers go on the trigger.
12
13
  */
13
- const SLOP = 8
14
- /* A long press may produce no click at all; past this the next click is the user's own. */
15
- const CLICK_AFTER_RELEASE_MS = 350
16
-
17
14
  export interface TouchHoldOptions {
18
15
  /** Whether a touch press may start a hold now. */
19
16
  enabled: () => boolean
@@ -23,70 +20,15 @@ export interface TouchHoldOptions {
23
20
  }
24
21
 
25
22
  export function useTouchHold(options: TouchHoldOptions) {
26
- let timer = 0
27
- /* The finger the hold belongs to: a second one must not move or end it. */
28
- let finger: number | null = null
29
- let start = { x: 0, y: 0 }
30
- let held = false
31
-
32
- function stop() {
33
- window.clearTimeout(timer)
34
- timer = 0
35
- }
36
-
37
- function onPointerDown(event: PointerEvent) {
38
- // Any new press starts over: on a hybrid laptop the mouse click after a
39
- // hold must not be taken for that hold's own click.
40
- if (timer && event.pointerId !== finger) return
41
- held = false
42
- if (event.pointerType !== "touch" || !options.enabled()) return
43
- finger = event.pointerId
44
- start = { x: event.clientX, y: event.clientY }
45
- stop()
46
- timer = window.setTimeout(() => {
47
- timer = 0
48
- held = true
49
- options.onHold()
50
- }, options.delay())
51
- }
52
-
53
- function onPointerMove(event: PointerEvent) {
54
- if (!timer || event.pointerId !== finger) return
55
- if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > SLOP) stop()
56
- }
57
-
58
- function onPointerUp(event: PointerEvent) {
59
- if (finger !== null && event.pointerId !== finger) return
60
- finger = null
61
- stop()
62
- if (held) window.setTimeout(() => (held = false), CLICK_AFTER_RELEASE_MS)
63
- }
64
-
65
- // A cancelled press produces no click, so there is nothing left to swallow.
66
- function onPointerCancel(event?: PointerEvent) {
67
- if (event && finger !== null && event.pointerId !== finger) return
68
- finger = null
69
- stop()
70
- held = false
71
- }
72
-
73
- function onClickCapture(event: MouseEvent) {
74
- if (!held) return
75
- held = false
76
- event.preventDefault()
77
- event.stopImmediatePropagation()
78
- }
79
-
80
- // A long press would otherwise also bring up the system's own menu.
81
- function onContextMenu(event: Event) {
82
- if (timer || held) event.preventDefault()
83
- }
84
-
85
- onBeforeUnmount(stop)
86
-
23
+ const gesture = createGesture({
24
+ enabled: options.enabled,
25
+ pointerTypes: ["touch"],
26
+ longPress: { delay: options.delay, handler: () => options.onHold() },
27
+ })
28
+ onBeforeUnmount(gesture.dispose)
87
29
  return {
88
30
  /** Drops a hold in progress, as if the finger had been lifted off the screen. */
89
- cancel: () => onPointerCancel(),
90
- handlers: { onPointerdown: onPointerDown, onPointermove: onPointerMove, onPointerup: onPointerUp, onPointercancel: onPointerCancel, onClickCapture, onContextmenu: onContextMenu },
31
+ cancel: gesture.cancel,
32
+ handlers: gesture.handlers,
91
33
  }
92
34
  }
@@ -1,4 +1,5 @@
1
1
  import { clamp } from "./clamp"
2
+ import { distance, pinchFrame } from "./gesture"
2
3
  import { computed, ref, toValue, type ComputedRef, type CSSProperties, type MaybeRefOrGetter, type Ref } from "vue"
3
4
 
4
5
  /*
@@ -130,15 +131,11 @@ export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
130
131
  zoomAbout(scale.value * (e.deltaY < 0 ? step : 1 / step), point.x, point.y)
131
132
  }
132
133
 
133
- function distance(a: { x: number; y: number }, b: { x: number; y: number }) {
134
- return Math.hypot(a.x - b.x, a.y - b.y)
135
- }
136
-
137
134
  function onPointerDown(e: PointerEvent) {
138
135
  pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
139
136
 
140
137
  if (pointers.size === 2) {
141
- const [a, b] = [...pointers.values()]
138
+ const [a, b] = [...pointers.values()] as [{ x: number; y: number }, { x: number; y: number }]
142
139
  pinchStartDistance = distance(a, b)
143
140
  pinchStartScale = scale.value
144
141
  panning.value = false
@@ -159,10 +156,10 @@ export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
159
156
  pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
160
157
 
161
158
  if (pointers.size === 2 && pinchStartDistance > 0) {
162
- const [a, b] = [...pointers.values()]
163
- const mid = { clientX: (a.x + b.x) / 2, clientY: (a.y + b.y) / 2 }
164
- const point = relative(mid)
165
- zoomAbout(pinchStartScale * (distance(a, b) / pinchStartDistance), point.x, point.y)
159
+ const [a, b] = [...pointers.values()] as [{ x: number; y: number }, { x: number; y: number }]
160
+ const pinch = pinchFrame(a, b, pinchStartDistance)
161
+ const point = relative({ clientX: pinch.origin.x, clientY: pinch.origin.y })
162
+ zoomAbout(pinchStartScale * pinch.scale, point.x, point.y)
166
163
  return
167
164
  }
168
165
 
package/src/index.ts CHANGED
@@ -216,6 +216,7 @@ export { default as LpRadioGroup } from "./components/LpRadioGroup.vue"
216
216
  export type { RadioOption } from "./components/LpRadioGroup.vue"
217
217
  export { default as LpRollingText } from "./components/LpRollingText.vue"
218
218
  export { default as LpScrollArea } from "./components/LpScrollArea.vue"
219
+ export { default as LpScrollStrip } from "./components/LpScrollStrip.vue"
219
220
  export { default as LpPageScrollbar } from "./components/LpPageScrollbar.vue"
220
221
  export { default as LpSegmented } from "./components/LpSegmented.vue"
221
222
  export type { SegmentedOption } from "./components/LpSegmented.vue"
@@ -496,6 +497,30 @@ export { useTilt } from "./composables/useTilt"
496
497
  export type { UseTilt, UseTiltOptions } from "./composables/useTilt"
497
498
  export { useZoomPan } from "./composables/useZoomPan"
498
499
  export type { UseZoomPan, UseZoomPanOptions } from "./composables/useZoomPan"
500
+ export { createGesture, glide, useGesture } from "./composables/useGesture"
501
+ export type { Gesture, GestureEnd, GestureOptions, GesturePinch, GestureState, Glide } from "./composables/useGesture"
502
+ export {
503
+ createDragSession,
504
+ createVelocityTracker,
505
+ FLICK_VELOCITY,
506
+ HOLD_TO_DRAG_MS,
507
+ LONG_PRESS_MS,
508
+ lockAxis,
509
+ MOUSE_SLOP,
510
+ momentumDistance,
511
+ pageAfterSwipe,
512
+ pinchFrame,
513
+ rubberBand,
514
+ rubberClamp,
515
+ shouldDismiss,
516
+ slopFor,
517
+ swipeDirection,
518
+ TOUCH_SLOP,
519
+ wheelPixels,
520
+ } from "./composables/gesture"
521
+ export type { DismissOptions, DragFrame, DragSession, DragSessionOptions, GestureAxis, GesturePoint, PageSwipeOptions, PinchFrame, SwipeOptions, VelocityTracker } from "./composables/gesture"
522
+ export { useScrollStrip } from "./composables/useScrollStrip"
523
+ export type { ScrollStrip, ScrollStripOptions } from "./composables/useScrollStrip"
499
524
  export { useToast } from "./composables/useToast"
500
525
  export type {
501
526
  ToastAction,
@@ -572,6 +597,8 @@ export type { PipelineStep, StepStatus } from "./components/pipeline"
572
597
  export { default as LpLoader } from "./components/LpLoader.vue"
573
598
  export type { LoaderLabels, LoaderSize, LoaderTone, LoaderVariant } from "./components/LpLoader.vue"
574
599
  export { useLoadingGate } from "./composables/useLoadingGate"
600
+ export { useAppUpdate } from "./composables/useAppUpdate"
601
+ export type { AppUpdate, AppUpdateOptions } from "./composables/useAppUpdate"
575
602
  export type { LoadingGateOptions } from "./composables/useLoadingGate"
576
603
  export { useNow } from "./composables/useNow"
577
604
  export type { UseNowOptions } from "./composables/useNow"
@@ -149,3 +149,43 @@
149
149
  display: none;
150
150
  }
151
151
  }
152
+
153
+ /*
154
+ * Edge fades for a sideways strip (LpScrollStrip, useScrollStrip): an edge
155
+ * with items beyond it fades out, an edge at the end of the row stays crisp.
156
+ * The widths are registered so a fade grows and leaves rather than switching;
157
+ * without @property it switches.
158
+ */
159
+ @property --lp-strip-fade-start {
160
+ syntax: "<length>";
161
+ inherits: false;
162
+ initial-value: 0px;
163
+ }
164
+
165
+ @property --lp-strip-fade-end {
166
+ syntax: "<length>";
167
+ inherits: false;
168
+ initial-value: 0px;
169
+ }
170
+
171
+ .lp-strip-fade {
172
+ -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--lp-strip-fade-start), #000 calc(100% - var(--lp-strip-fade-end)), transparent 100%);
173
+ mask-image: linear-gradient(to right, transparent 0, #000 var(--lp-strip-fade-start), #000 calc(100% - var(--lp-strip-fade-end)), transparent 100%);
174
+ transition:
175
+ --lp-strip-fade-start var(--duration-fast) var(--ease-emphasized),
176
+ --lp-strip-fade-end var(--duration-fast) var(--ease-emphasized);
177
+ }
178
+
179
+ .lp-strip-fade[data-lp-more-start] {
180
+ --lp-strip-fade-start: var(--lp-strip-fade, 28px);
181
+ }
182
+
183
+ .lp-strip-fade[data-lp-more-end] {
184
+ --lp-strip-fade-end: var(--lp-strip-fade, 28px);
185
+ }
186
+
187
+ @media (prefers-reduced-motion: reduce) {
188
+ .lp-strip-fade {
189
+ transition: none;
190
+ }
191
+ }
@@ -1,18 +0,0 @@
1
-
2
- .lp-tab-badge-enter-active[data-v-ef828291],
3
- .lp-tab-badge-leave-active[data-v-ef828291] {
4
- transition:
5
- opacity var(--duration-fast) var(--ease-emphasized),
6
- transform var(--duration-fast) var(--ease-emphasized);
7
- }
8
- .lp-tab-badge-enter-from[data-v-ef828291],
9
- .lp-tab-badge-leave-to[data-v-ef828291] {
10
- opacity: 0;
11
- transform: scale(0.6);
12
- }
13
- @media (prefers-reduced-motion: reduce) {
14
- .lp-tab-badge-enter-active[data-v-ef828291],
15
- .lp-tab-badge-leave-active[data-v-ef828291] {
16
- transition: none;
17
- }
18
- }
@@ -1,8 +0,0 @@
1
-
2
- .lp-trim-still-in[data-v-ba15829e] > canvas:not([data-lp-stand-in]) {
3
- animation: lp-trim-still-ba15829e var(--duration-medium) var(--ease-emphasized);
4
- }
5
- @keyframes lp-trim-still-ba15829e {
6
- from { opacity: 0;
7
- }
8
- }
@@ -1,49 +0,0 @@
1
- import { defineComponent, openBlock, createElementBlock, Fragment, renderList, createBlock, resolveDynamicComponent, mergeProps, withCtx, createTextVNode, toDisplayString } from "vue";
2
- import { useLinkComponent } from "../composables/useLpConfig.js";
3
- import { resolveLink } from "../components/link.js";
4
- const _hoisted_1 = ["aria-label"];
5
- const _sfc_main = /* @__PURE__ */ defineComponent({
6
- __name: "AppShellContextNav",
7
- props: {
8
- items: {},
9
- isActive: { type: Function },
10
- label: {}
11
- },
12
- emits: ["select"],
13
- setup(__props, { emit: __emit }) {
14
- const props = __props;
15
- const emit = __emit;
16
- const linkComponent = useLinkComponent();
17
- function rowOf(item) {
18
- const link = item.disabled ? null : resolveLink(linkComponent.value, { to: item.to, href: item.href });
19
- return link ?? { is: "button", props: { type: "button", disabled: item.disabled || void 0 } };
20
- }
21
- return (_ctx, _cache) => {
22
- return openBlock(), createElementBlock("nav", {
23
- "aria-label": props.label,
24
- class: "flex shrink-0 gap-1 overflow-x-auto border-b border-line bg-surface px-4 py-1.5 lp-scrollbar-none"
25
- }, [
26
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
27
- return openBlock(), createBlock(resolveDynamicComponent(rowOf(item).is), mergeProps({
28
- key: item.id
29
- }, { ref_for: true }, rowOf(item).props, {
30
- "aria-current": __props.isActive(item) ? "page" : void 0,
31
- class: [
32
- "shrink-0 rounded-control px-2.5 py-1 text-sm font-medium outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring",
33
- item.disabled ? "cursor-not-allowed opacity-45" : __props.isActive(item) ? "bg-surface-soft text-ink" : "text-muted hover:text-ink"
34
- ],
35
- onClick: ($event) => emit("select", item)
36
- }), {
37
- default: withCtx(() => [
38
- createTextVNode(toDisplayString(item.label), 1)
39
- ]),
40
- _: 2
41
- }, 1040, ["aria-current", "class", "onClick"]);
42
- }), 128))
43
- ], 8, _hoisted_1);
44
- };
45
- }
46
- });
47
- export {
48
- _sfc_main as _
49
- };