@leavepulse/ui 0.19.1 → 0.20.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/{LpAddressInput-N8O-LtCS.js → LpAddressInput-CCtskgfZ.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-B-amVJsM.js → LpConfirmDialog-Z9hsfm2D.js} +1 -1
- package/dist/chunks/{LpModal-vlrgcEY_.js → LpModal-g2dGpF7l.js} +5 -5
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/fileTree.js +2 -1
- package/dist/composables/useSettled.d.ts +21 -0
- package/dist/composables/useSettled.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/components/LpModal.vue +17 -6
- package/src/components/fileTree.ts +8 -1
- package/src/composables/useSettled.ts +82 -0
- package/src/index.ts +2 -0
|
@@ -6,7 +6,7 @@ import { _ as _sfc_main$3 } from "./LpButton-5VXXCjwF.js";
|
|
|
6
6
|
import { _ as _sfc_main$1 } from "./LpFormField-Bn-ZwC6z.js";
|
|
7
7
|
import { _ as _sfc_main$4 } from "./LpIcon-CCnX5_2j.js";
|
|
8
8
|
import { _ as _sfc_main$5 } from "./LpInput-BauZRyzm.js";
|
|
9
|
-
import { _ as _sfc_main$7 } from "./LpModal-
|
|
9
|
+
import { _ as _sfc_main$7 } from "./LpModal-g2dGpF7l.js";
|
|
10
10
|
import { _ as _sfc_main$6 } from "./LpSelect-D5mU4cZO.js";
|
|
11
11
|
const _hoisted_1 = { class: "flex flex-col gap-3" };
|
|
12
12
|
const _hoisted_2 = { class: "flex items-stretch gap-2" };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent, openBlock, createBlock, withCtx, renderSlot, createVNode, createTextVNode, toDisplayString } from "vue";
|
|
2
2
|
import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
|
|
3
|
-
import { _ as _sfc_main$1 } from "./LpModal-
|
|
3
|
+
import { _ as _sfc_main$1 } from "./LpModal-g2dGpF7l.js";
|
|
4
4
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
5
5
|
__name: "LpConfirmDialog",
|
|
6
6
|
props: {
|
|
@@ -60,12 +60,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
60
60
|
const retune = (el) => {
|
|
61
61
|
const from = el.offsetHeight;
|
|
62
62
|
el.style.height = "";
|
|
63
|
+
el.style.maxHeight = "none";
|
|
63
64
|
const to = el.offsetHeight;
|
|
64
|
-
|
|
65
|
-
if (to === from)
|
|
66
|
-
el.style.height = `${to}px`;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
65
|
+
el.style.maxHeight = "";
|
|
66
|
+
if (to === 0 || to === from) return;
|
|
69
67
|
el.style.height = `${from}px`;
|
|
70
68
|
void el.offsetHeight;
|
|
71
69
|
el.style.height = `${to}px`;
|
|
@@ -73,6 +71,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
73
71
|
clearTimeout(settleTimer);
|
|
74
72
|
settleTimer = setTimeout(() => {
|
|
75
73
|
resizing.value = false;
|
|
74
|
+
el.style.height = "";
|
|
76
75
|
}, TWEEN_MS);
|
|
77
76
|
};
|
|
78
77
|
watch(
|
|
@@ -91,6 +90,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
91
90
|
el.style.height = `${el.offsetHeight}px`;
|
|
92
91
|
raf = requestAnimationFrame(() => {
|
|
93
92
|
tweening.value = true;
|
|
93
|
+
el.style.height = "";
|
|
94
94
|
});
|
|
95
95
|
const watchChildren = () => {
|
|
96
96
|
if (!ro) return;
|
|
@@ -62,10 +62,11 @@ function fileIcon(node, expanded = false) {
|
|
|
62
62
|
const ext = dot > 0 ? lower.slice(dot + 1) : "";
|
|
63
63
|
return EXT_ICONS[ext] ?? "lucide:file";
|
|
64
64
|
}
|
|
65
|
+
const NAME_COLLATOR = new Intl.Collator(void 0, { numeric: true, sensitivity: "base" });
|
|
65
66
|
function sortNodes(nodes) {
|
|
66
67
|
return [...nodes].sort((a, b) => {
|
|
67
68
|
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
68
|
-
return a.name
|
|
69
|
+
return NAME_COLLATOR.compare(a.name, b.name);
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
function formatSize(bytes) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
export interface UseSettledOptions {
|
|
3
|
+
/** Override the wait. Defaults to the kit's overlay animation duration. */
|
|
4
|
+
delay?: MaybeRefOrGetter<number>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Returns a ref that flips true once `open` has been true for the length of the
|
|
8
|
+
* overlay's opening animation. Gate expensive content on it:
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const settled = useSettled(() => isOpen.value)
|
|
12
|
+
* ```
|
|
13
|
+
* ```vue
|
|
14
|
+
* <RowList v-if="settled" :rows="rows" />
|
|
15
|
+
* <RowSkeletons v-else />
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* With reduced motion there is no animation to protect, so it resolves on the
|
|
19
|
+
* next tick rather than making the user wait for a delay that buys nothing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useSettled(open: MaybeRefOrGetter<boolean>, options?: UseSettledOptions): import('vue').Ref<boolean, boolean>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ref, watch, toValue, onScopeDispose } from "vue";
|
|
2
|
+
const SETTLE_MS = 260;
|
|
3
|
+
function useSettled(open, options = {}) {
|
|
4
|
+
const settled = ref(false);
|
|
5
|
+
let timer;
|
|
6
|
+
function clear() {
|
|
7
|
+
if (timer !== void 0) {
|
|
8
|
+
clearTimeout(timer);
|
|
9
|
+
timer = void 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
watch(
|
|
13
|
+
() => toValue(open),
|
|
14
|
+
(isOpen) => {
|
|
15
|
+
clear();
|
|
16
|
+
if (!isOpen) {
|
|
17
|
+
settled.value = false;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
21
|
+
const wait = reduced ? 0 : toValue(options.delay) ?? SETTLE_MS;
|
|
22
|
+
timer = setTimeout(() => {
|
|
23
|
+
settled.value = true;
|
|
24
|
+
timer = void 0;
|
|
25
|
+
}, wait);
|
|
26
|
+
},
|
|
27
|
+
{ immediate: true }
|
|
28
|
+
);
|
|
29
|
+
onScopeDispose(clear);
|
|
30
|
+
return settled;
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
useSettled
|
|
34
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ export { parseConfig, serializeConfig, UI_CONFIG_VERSION } from './theme/config'
|
|
|
109
109
|
export type { UiConfig } from './theme/config';
|
|
110
110
|
export { useInputFilter } from './composables/useInputFilter';
|
|
111
111
|
export type { UseInputFilterOptions } from './composables/useInputFilter';
|
|
112
|
+
export { useSettled } from './composables/useSettled';
|
|
113
|
+
export type { UseSettledOptions } from './composables/useSettled';
|
|
112
114
|
export { useTilt } from './composables/useTilt';
|
|
113
115
|
export type { UseTilt, UseTiltOptions } from './composables/useTilt';
|
|
114
116
|
export { useZoomPan } from './composables/useZoomPan';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
|
|
2
|
-
import { _ as _2 } from "./chunks/LpAddressInput-
|
|
2
|
+
import { _ as _2 } from "./chunks/LpAddressInput-CCtskgfZ.js";
|
|
3
3
|
import { _ as _3 } from "./chunks/LpMapPicker-DFKrd1Ne.js";
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
|
|
@@ -14,7 +14,7 @@ import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
|
|
|
14
14
|
import { _ as _13 } from "./chunks/LpCheckbox-BO0zuuPh.js";
|
|
15
15
|
import { _ as _14 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
|
|
16
16
|
import { default as default2 } from "./components/LpCommandPalette.vue.js";
|
|
17
|
-
import { _ as _15 } from "./chunks/LpConfirmDialog-
|
|
17
|
+
import { _ as _15 } from "./chunks/LpConfirmDialog-Z9hsfm2D.js";
|
|
18
18
|
import { _ as _16 } from "./chunks/LpContextMenu-D0mzXqA7.js";
|
|
19
19
|
import { _ as _17 } from "./chunks/LpDatePicker-dY2q4dr4.js";
|
|
20
20
|
import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
|
|
@@ -32,7 +32,7 @@ import { _ as _28 } from "./chunks/LpLightbox-CzhFe9wZ.js";
|
|
|
32
32
|
import { fileNameOf } from "./components/lightbox.js";
|
|
33
33
|
import { _ as _29 } from "./chunks/LpLink-DHwOEp4e.js";
|
|
34
34
|
import { _ as _30 } from "./chunks/LpLogViewer-tmKPNTuU.js";
|
|
35
|
-
import { _ as _31 } from "./chunks/LpModal-
|
|
35
|
+
import { _ as _31 } from "./chunks/LpModal-g2dGpF7l.js";
|
|
36
36
|
import { _ as _32 } from "./chunks/LpNotificationBell-DHze8wYN.js";
|
|
37
37
|
import { _ as _33 } from "./chunks/LpNumberField-C5ar-OwE.js";
|
|
38
38
|
import { _ as _34 } from "./chunks/LpOtpInput-DqaT0Z75.js";
|
|
@@ -70,6 +70,7 @@ import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, remove
|
|
|
70
70
|
import { blockTitle, defineBlocks } from "./layout/registry.js";
|
|
71
71
|
import { useLayout } from "./layout/useLayout.js";
|
|
72
72
|
import { useInputFilter } from "./composables/useInputFilter.js";
|
|
73
|
+
import { useSettled } from "./composables/useSettled.js";
|
|
73
74
|
import { useTilt } from "./composables/useTilt.js";
|
|
74
75
|
import { useZoomPan } from "./composables/useZoomPan.js";
|
|
75
76
|
import { useToast } from "./composables/useToast.js";
|
|
@@ -229,6 +230,7 @@ export {
|
|
|
229
230
|
unproject,
|
|
230
231
|
useInputFilter,
|
|
231
232
|
useLayout,
|
|
233
|
+
useSettled,
|
|
232
234
|
u as useTheme,
|
|
233
235
|
useTilt,
|
|
234
236
|
useToast,
|
package/package.json
CHANGED
|
@@ -108,12 +108,16 @@ let settleTimer: ReturnType<typeof setTimeout> | undefined
|
|
|
108
108
|
const retune = (el: HTMLElement) => {
|
|
109
109
|
const from = el.offsetHeight
|
|
110
110
|
el.style.height = ""
|
|
111
|
+
// `max-h` comes from a class, so the panel already reports the clipped figure
|
|
112
|
+
// once the cap bites. Pinning that would leave a short panel sitting over
|
|
113
|
+
// content it can no longer grow to fit, so the natural height is measured with
|
|
114
|
+
// the cap lifted and the clamp is left to CSS afterwards.
|
|
115
|
+
el.style.maxHeight = "none"
|
|
111
116
|
const to = el.offsetHeight
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
+
el.style.maxHeight = ""
|
|
118
|
+
// Nothing moved, so there is nothing to ease — and leaving the height unset
|
|
119
|
+
// keeps the panel free to size itself.
|
|
120
|
+
if (to === 0 || to === from) return
|
|
117
121
|
el.style.height = `${from}px`
|
|
118
122
|
void el.offsetHeight
|
|
119
123
|
el.style.height = `${to}px`
|
|
@@ -125,6 +129,10 @@ const retune = (el: HTMLElement) => {
|
|
|
125
129
|
clearTimeout(settleTimer)
|
|
126
130
|
settleTimer = setTimeout(() => {
|
|
127
131
|
resizing.value = false
|
|
132
|
+
// Released once the panel has arrived. A height left pinned in pixels stops
|
|
133
|
+
// being a starting point and becomes a cap: content that grows afterwards
|
|
134
|
+
// is clipped, and `max-h` can no longer size the panel itself.
|
|
135
|
+
el.style.height = ""
|
|
128
136
|
}, TWEEN_MS)
|
|
129
137
|
}
|
|
130
138
|
|
|
@@ -147,9 +155,12 @@ watch(
|
|
|
147
155
|
|
|
148
156
|
el.style.height = `${el.offsetHeight}px`
|
|
149
157
|
// The opening panel must not animate its height in from the pre-content box,
|
|
150
|
-
// so the transition only arms once that first pin has painted.
|
|
158
|
+
// so the transition only arms once that first pin has painted. The pin is
|
|
159
|
+
// dropped in the same frame: it exists to give the first `retune` something
|
|
160
|
+
// to start from, and holding it would cap the panel at its opening size.
|
|
151
161
|
raf = requestAnimationFrame(() => {
|
|
152
162
|
tweening.value = true
|
|
163
|
+
el.style.height = ""
|
|
153
164
|
})
|
|
154
165
|
|
|
155
166
|
// Two triggers, since either can move the height alone: the resize observer
|
|
@@ -104,11 +104,18 @@ export function fileIcon(node: FileNode, expanded = false): string {
|
|
|
104
104
|
return EXT_ICONS[ext] ?? "lucide:file"
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/*
|
|
108
|
+
* Built once. `String.localeCompare` with options constructs a collator on every
|
|
109
|
+
* call, which on a few thousand nodes costs tens of milliseconds — enough to
|
|
110
|
+
* drop frames while a dialog is animating open.
|
|
111
|
+
*/
|
|
112
|
+
const NAME_COLLATOR = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" })
|
|
113
|
+
|
|
107
114
|
/** Directories first, then case-insensitive by name — the file-manager order. */
|
|
108
115
|
export function sortNodes(nodes: FileNode[]): FileNode[] {
|
|
109
116
|
return [...nodes].sort((a, b) => {
|
|
110
117
|
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1
|
|
111
|
-
return a.name
|
|
118
|
+
return NAME_COLLATOR.compare(a.name, b.name)
|
|
112
119
|
})
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { onScopeDispose, ref, watch, type MaybeRefOrGetter, toValue } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* "Has the opening animation had the frame to itself yet?"
|
|
5
|
+
*
|
|
6
|
+
* A panel that mounts something expensive — a few hundred rows, each carrying
|
|
7
|
+
* its own components — spends the opening frame building DOM instead of
|
|
8
|
+
* animating, and the overlay snaps into place instead of easing in. The fix is
|
|
9
|
+
* always the same shape: hold the expensive part back, let the animation run,
|
|
10
|
+
* put the content up right after. That reads as the panel filling in rather
|
|
11
|
+
* than the window stuttering.
|
|
12
|
+
*
|
|
13
|
+
* Consumers were writing this as a bare `setTimeout(260)` with the duration
|
|
14
|
+
* copied from the kit's `--duration-medium` — a number that has to be kept in
|
|
15
|
+
* step by hand, in every caller, forever. This owns it instead.
|
|
16
|
+
*
|
|
17
|
+
* The flag goes false as soon as `open` does, so reopening starts from the top
|
|
18
|
+
* rather than inheriting the previous pass's answer.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Overlay animations run at `--duration-medium`; the margin covers the frame
|
|
22
|
+
* the browser spends scheduling them. Kept here so callers don't guess. */
|
|
23
|
+
const SETTLE_MS = 260
|
|
24
|
+
|
|
25
|
+
export interface UseSettledOptions {
|
|
26
|
+
/** Override the wait. Defaults to the kit's overlay animation duration. */
|
|
27
|
+
delay?: MaybeRefOrGetter<number>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a ref that flips true once `open` has been true for the length of the
|
|
32
|
+
* overlay's opening animation. Gate expensive content on it:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const settled = useSettled(() => isOpen.value)
|
|
36
|
+
* ```
|
|
37
|
+
* ```vue
|
|
38
|
+
* <RowList v-if="settled" :rows="rows" />
|
|
39
|
+
* <RowSkeletons v-else />
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* With reduced motion there is no animation to protect, so it resolves on the
|
|
43
|
+
* next tick rather than making the user wait for a delay that buys nothing.
|
|
44
|
+
*/
|
|
45
|
+
export function useSettled(
|
|
46
|
+
open: MaybeRefOrGetter<boolean>,
|
|
47
|
+
options: UseSettledOptions = {},
|
|
48
|
+
) {
|
|
49
|
+
const settled = ref(false)
|
|
50
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
51
|
+
|
|
52
|
+
function clear() {
|
|
53
|
+
if (timer !== undefined) {
|
|
54
|
+
clearTimeout(timer)
|
|
55
|
+
timer = undefined
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
watch(
|
|
60
|
+
() => toValue(open),
|
|
61
|
+
(isOpen) => {
|
|
62
|
+
clear()
|
|
63
|
+
if (!isOpen) {
|
|
64
|
+
settled.value = false
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
const reduced =
|
|
68
|
+
typeof window !== "undefined" &&
|
|
69
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
70
|
+
const wait = reduced ? 0 : (toValue(options.delay) ?? SETTLE_MS)
|
|
71
|
+
timer = setTimeout(() => {
|
|
72
|
+
settled.value = true
|
|
73
|
+
timer = undefined
|
|
74
|
+
}, wait)
|
|
75
|
+
},
|
|
76
|
+
{ immediate: true },
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
onScopeDispose(clear)
|
|
80
|
+
|
|
81
|
+
return settled
|
|
82
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -184,6 +184,8 @@ export type { UiConfig } from "./theme/config"
|
|
|
184
184
|
|
|
185
185
|
export { useInputFilter } from "./composables/useInputFilter"
|
|
186
186
|
export type { UseInputFilterOptions } from "./composables/useInputFilter"
|
|
187
|
+
export { useSettled } from "./composables/useSettled"
|
|
188
|
+
export type { UseSettledOptions } from "./composables/useSettled"
|
|
187
189
|
export { useTilt } from "./composables/useTilt"
|
|
188
190
|
export type { UseTilt, UseTiltOptions } from "./composables/useTilt"
|
|
189
191
|
export { useZoomPan } from "./composables/useZoomPan"
|