@leavepulse/ui 0.28.1 → 0.28.3
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/assets/LpDropzone-DVDC-ABU.css +52 -0
- package/dist/assets/LpImageEditor-DqP5JKCy.css +25 -0
- package/dist/chunks/{LpChart-tNXs1ttr.js → LpChart-UiU4RhXP.js} +5 -4
- package/dist/chunks/{LpLogViewer-CcQ3s3Fp.js → LpLogViewer-CKmEQYnm.js} +66 -8
- package/dist/chunks/{LpProgress-DoIP4jQR.js → LpProgress-Btq5W4Ut.js} +14 -14
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpChart.vue.js +1 -1
- package/dist/components/LpDropzone.vue.d.ts +66 -0
- package/dist/components/LpDropzone.vue.js +163 -0
- package/dist/components/LpImageEditor.vue.d.ts +58 -0
- package/dist/components/LpImageEditor.vue.js +357 -0
- package/dist/components/LpLogViewer.vue.d.ts +4 -0
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/composables/useHotkeys.d.ts +16 -1
- package/dist/composables/useHotkeys.js +8 -1
- package/dist/composables/useZoomPan.d.ts +24 -0
- package/dist/composables/useZoomPan.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -5
- package/package.json +1 -1
- package/src/component-names.ts +2 -0
- package/src/components/LpChart.vue +23 -4
- package/src/components/LpDropzone.vue +299 -0
- package/src/components/LpImageEditor.vue +515 -0
- package/src/components/LpLogViewer.vue +114 -2
- package/src/components/LpProgress.vue +30 -21
- package/src/composables/useHotkeys.ts +30 -1
- package/src/composables/useZoomPan.ts +30 -0
- package/src/index.ts +2 -0
|
@@ -97,17 +97,29 @@ const props = withDefaults(
|
|
|
97
97
|
* gap to the other end. Interpolating here and handing both the same number
|
|
98
98
|
* keeps them exactly level, because there is only one number.
|
|
99
99
|
*/
|
|
100
|
-
|
|
100
|
+
/*
|
|
101
|
+
* `shown` is the position on screen, and it is the thing that is animated —
|
|
102
|
+
* not a phase that something else multiplies by.
|
|
103
|
+
*
|
|
104
|
+
* The earlier shape kept an eased 0..1 and derived the position from it and a
|
|
105
|
+
* `from` captured in the watcher. That could not work: `from` was read off a
|
|
106
|
+
* computed over `props.value`, so by the time the watcher ran it already
|
|
107
|
+
* reported the DESTINATION, and every sweep after the first interpolated from
|
|
108
|
+
* the answer to the answer. The entrance still animated, which is what made
|
|
109
|
+
* the bug look like a decision — the bar filled beautifully once and jumped
|
|
110
|
+
* forever after.
|
|
111
|
+
*
|
|
112
|
+
* Animating the position directly removes the question. Wherever the bar is is
|
|
113
|
+
* where the next sweep starts, including one that interrupts another midway.
|
|
114
|
+
*/
|
|
115
|
+
const shown = ref(props.animateOnMount ? 0 : props.value)
|
|
101
116
|
let raf = 0
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
arc's present position rather than snapping back to empty. */
|
|
105
|
-
let from = props.animateOnMount ? 0 : props.value
|
|
106
|
-
|
|
107
|
-
const runSweep = (ms: number) => {
|
|
118
|
+
const runSweep = (to: number, ms: number) => {
|
|
108
119
|
if (raf) cancelAnimationFrame(raf)
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
const from = shown.value
|
|
121
|
+
if (prefersReducedMotion() || ms <= 0 || from === to) {
|
|
122
|
+
shown.value = to
|
|
111
123
|
return
|
|
112
124
|
}
|
|
113
125
|
const t0 = performance.now()
|
|
@@ -115,7 +127,7 @@ const runSweep = (ms: number) => {
|
|
|
115
127
|
const t = Math.min(1, (now - t0) / ms)
|
|
116
128
|
// The kit's shared curve — the same function LpNumberFlow eases on, so the
|
|
117
129
|
// arc and the counter agree by construction rather than by comment.
|
|
118
|
-
|
|
130
|
+
shown.value = from + (to - from) * easeOut(t)
|
|
119
131
|
raf = t < 1 ? requestAnimationFrame(step) : 0
|
|
120
132
|
}
|
|
121
133
|
raf = requestAnimationFrame(step)
|
|
@@ -140,22 +152,19 @@ const bindReveal = (instance: unknown) => {
|
|
|
140
152
|
revealAnchor.value = (instance as { $el?: Element } | null)?.$el ?? null
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
watch(revealed, (visible) => {
|
|
144
|
-
if (visible && props.animateOnMount) runSweep(props.duration)
|
|
145
|
-
}, { immediate: true })
|
|
146
|
-
|
|
147
|
-
/* A later change re-runs the sweep from where it stands, so the ring eases to
|
|
148
|
-
its new value instead of jumping. */
|
|
149
155
|
watch(
|
|
150
|
-
|
|
151
|
-
() => {
|
|
152
|
-
|
|
153
|
-
runSweep(props.duration)
|
|
156
|
+
revealed,
|
|
157
|
+
(visible) => {
|
|
158
|
+
if (visible && props.animateOnMount) runSweep(props.value, props.duration)
|
|
154
159
|
},
|
|
160
|
+
{ immediate: true },
|
|
155
161
|
)
|
|
156
162
|
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
/* A later change eases to the new value from wherever the bar stands. */
|
|
164
|
+
watch(
|
|
165
|
+
() => [props.value, props.max],
|
|
166
|
+
() => runSweep(props.value, props.duration),
|
|
167
|
+
)
|
|
159
168
|
|
|
160
169
|
const pct = computed(() =>
|
|
161
170
|
Math.max(0, Math.min(100, (shown.value / props.max) * 100)),
|
|
@@ -111,9 +111,38 @@ function matches(event: KeyboardEvent, binding: HotkeyBinding): boolean {
|
|
|
111
111
|
* may come and go with the state that owns them — a getter is the point, not a
|
|
112
112
|
* formality.
|
|
113
113
|
*/
|
|
114
|
-
export
|
|
114
|
+
export interface UseHotkeysOptions {
|
|
115
|
+
/**
|
|
116
|
+
* Only act while the focus is inside this element.
|
|
117
|
+
*
|
|
118
|
+
* The default is the whole window, which is right for a command palette and
|
|
119
|
+
* wrong for a control: two of the same component on one page would both
|
|
120
|
+
* answer the same key, and a control beside a form would answer keys aimed
|
|
121
|
+
* at the form. Scoping it here rather than in each component because every
|
|
122
|
+
* one of them was about to write the same `document.activeElement` check and
|
|
123
|
+
* decline by hand.
|
|
124
|
+
*
|
|
125
|
+
* Undefined or null means the window, so the palette case stays the default.
|
|
126
|
+
*/
|
|
127
|
+
scope?: MaybeRefOrGetter<HTMLElement | null | undefined>
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function useHotkeys(
|
|
131
|
+
bindings: MaybeRefOrGetter<HotkeyBinding[]>,
|
|
132
|
+
options: UseHotkeysOptions = {},
|
|
133
|
+
) {
|
|
134
|
+
function inScope(target: EventTarget | null): boolean {
|
|
135
|
+
const el = toValue(options.scope)
|
|
136
|
+
if (!el) return true
|
|
137
|
+
const node = target instanceof Node ? target : null
|
|
138
|
+
// The element itself counts: a focusable canvas holds the focus without
|
|
139
|
+
// containing anything that could.
|
|
140
|
+
return !!node && (el === node || el.contains(node))
|
|
141
|
+
}
|
|
142
|
+
|
|
115
143
|
function onKeydown(event: KeyboardEvent) {
|
|
116
144
|
if (event.isComposing) return
|
|
145
|
+
if (!inScope(event.target)) return
|
|
117
146
|
const inTextEntry = isTextEntry(event.target)
|
|
118
147
|
for (const binding of toValue(bindings)) {
|
|
119
148
|
if (inTextEntry && !binding.allowInInput) continue
|
|
@@ -25,6 +25,23 @@ export interface UseZoomPanOptions {
|
|
|
25
25
|
|
|
26
26
|
export interface UseZoomPan {
|
|
27
27
|
scale: Ref<number>
|
|
28
|
+
/**
|
|
29
|
+
* Pan offset in CONTENT pixels — what `style` puts inside `translate()`,
|
|
30
|
+
* before the scale magnifies it. Exposed because anything that has to know
|
|
31
|
+
* *which part* is visible needs the number, not the CSS string: an exporter
|
|
32
|
+
* writing the cropped region, a minimap drawing the viewport rectangle, a
|
|
33
|
+
* caller restoring a saved position. Parsing it back out of `transform` is
|
|
34
|
+
* the alternative, and that breaks the moment this composable changes how it
|
|
35
|
+
* composes the transform.
|
|
36
|
+
*/
|
|
37
|
+
offset: ComputedRef<{ x: number; y: number }>
|
|
38
|
+
/**
|
|
39
|
+
* Put the view somewhere — restoring a saved position, or carrying one
|
|
40
|
+
* through a change that re-frames the content. Clamped like any other pan,
|
|
41
|
+
* so a caller cannot place the content off the viewport. Omitting `scale`
|
|
42
|
+
* keeps the current one.
|
|
43
|
+
*/
|
|
44
|
+
setView: (view: { x: number; y: number; scale?: number }) => void
|
|
28
45
|
/** True once zoomed past the minimum — the point at which panning matters. */
|
|
29
46
|
zoomed: ComputedRef<boolean>
|
|
30
47
|
/** True while a drag is in flight, so the caller can swap the cursor. */
|
|
@@ -194,8 +211,21 @@ export function useZoomPan(options: UseZoomPanOptions = {}): UseZoomPan {
|
|
|
194
211
|
transition: panning.value || pinchStartDistance > 0 ? "none" : undefined,
|
|
195
212
|
}))
|
|
196
213
|
|
|
214
|
+
const offset = computed(() => ({ x: x.value, y: y.value }))
|
|
215
|
+
|
|
216
|
+
function setView(view: { x: number; y: number; scale?: number }) {
|
|
217
|
+
if (view.scale !== undefined) {
|
|
218
|
+
scale.value = Math.min(max, Math.max(min, view.scale))
|
|
219
|
+
}
|
|
220
|
+
x.value = view.x
|
|
221
|
+
y.value = view.y
|
|
222
|
+
clampPan()
|
|
223
|
+
}
|
|
224
|
+
|
|
197
225
|
return {
|
|
198
226
|
scale,
|
|
227
|
+
offset,
|
|
228
|
+
setView,
|
|
199
229
|
zoomed,
|
|
200
230
|
panning,
|
|
201
231
|
style,
|
package/src/index.ts
CHANGED
|
@@ -55,6 +55,7 @@ export { default as LpDivider } from "./components/LpDivider.vue"
|
|
|
55
55
|
export { default as LpDrawer } from "./components/LpDrawer.vue"
|
|
56
56
|
export { default as LpDropdownMenu } from "./components/LpDropdownMenu.vue"
|
|
57
57
|
export type { MenuItem } from "./components/LpDropdownMenu.vue"
|
|
58
|
+
export { default as LpDropzone } from "./components/LpDropzone.vue"
|
|
58
59
|
export { default as LpEmptyState } from "./components/LpEmptyState.vue"
|
|
59
60
|
export { default as LpFileTree } from "./components/LpFileTree.vue"
|
|
60
61
|
export type { FileTreeSummary } from "./components/LpFileTree.vue"
|
|
@@ -73,6 +74,7 @@ export {
|
|
|
73
74
|
} from "./components/fileTree"
|
|
74
75
|
export { default as LpFormField } from "./components/LpFormField.vue"
|
|
75
76
|
export { default as LpIcon } from "./components/LpIcon.vue"
|
|
77
|
+
export { default as LpImageEditor } from "./components/LpImageEditor.vue"
|
|
76
78
|
export { default as LpInfraNode } from "./components/LpInfraNode.vue"
|
|
77
79
|
export type { InfraNodeData } from "./components/LpInfraNode.vue"
|
|
78
80
|
export { default as LpInput } from "./components/LpInput.vue"
|