@leavepulse/ui 0.37.1 → 0.37.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/{LpTable-BbOFmZfc.css → LpTable-BgPLJOXa.css} +9 -9
- package/dist/chunks/{LpAppShell-lVvpENMH.js → LpAppShell-Cs9jDoqJ.js} +1 -1
- package/dist/chunks/{LpDatePicker-6JFXllj5.js → LpDatePicker-BPbvu3cZ.js} +49 -8
- package/dist/chunks/{LpDrawer-DAJN5rGK.js → LpDrawer-CslZtgUm.js} +10 -3
- package/dist/chunks/{LpModalNav-K6N5YpQk.js → LpModalNav-Dkogc3O_.js} +1 -1
- package/dist/chunks/{LpSidebar-DcYavim2.js → LpSidebar-DvGfofv6.js} +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +8 -0
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpTable.vue.js +28 -21
- package/dist/composables/useShift.js +19 -0
- package/dist/index.js +5 -5
- package/package.json +1 -1
- package/src/components/LpDatePicker.vue +61 -5
- package/src/components/LpDrawer.vue +14 -2
- package/src/components/LpTable.vue +19 -4
- package/src/composables/useShift.ts +28 -0
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
* fade before closing the gap and the whole list lurches at the end instead of
|
|
12
12
|
* gliding.
|
|
13
13
|
*/
|
|
14
|
-
.lp-row-enter-active[data-v-
|
|
15
|
-
.lp-row-leave-active[data-v-
|
|
14
|
+
.lp-row-enter-active[data-v-e0bb036b],
|
|
15
|
+
.lp-row-leave-active[data-v-e0bb036b] {
|
|
16
16
|
transition:
|
|
17
17
|
opacity var(--duration-fast) var(--ease-emphasized),
|
|
18
18
|
transform var(--duration-fast) var(--ease-emphasized);
|
|
19
19
|
}
|
|
20
|
-
.lp-row-move[data-v-
|
|
20
|
+
.lp-row-move[data-v-e0bb036b] {
|
|
21
21
|
transition: transform var(--duration-medium) var(--ease-emphasized);
|
|
22
22
|
}
|
|
23
|
-
.lp-row-enter-from[data-v-
|
|
24
|
-
.lp-row-leave-to[data-v-
|
|
23
|
+
.lp-row-enter-from[data-v-e0bb036b],
|
|
24
|
+
.lp-row-leave-to[data-v-e0bb036b] {
|
|
25
25
|
opacity: 0;
|
|
26
26
|
transform: translateX(-0.5rem);
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* drops out of the table layout entirely. Nothing sits below this row to be
|
|
36
36
|
* dragged around, so simply not animating its exit costs nothing.
|
|
37
37
|
*/
|
|
38
|
-
.lp-row-empty.lp-row-leave-active[data-v-
|
|
38
|
+
.lp-row-empty.lp-row-leave-active[data-v-e0bb036b] {
|
|
39
39
|
display: none;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
/* A filter that redraws the list is information, not decoration: with motion
|
|
51
51
|
turned down the rows change without animating rather than not changing. */
|
|
52
52
|
@media (prefers-reduced-motion: reduce) {
|
|
53
|
-
.lp-row-enter-active[data-v-
|
|
54
|
-
.lp-row-leave-active[data-v-
|
|
55
|
-
.lp-row-move[data-v-
|
|
53
|
+
.lp-row-enter-active[data-v-e0bb036b],
|
|
54
|
+
.lp-row-leave-active[data-v-e0bb036b],
|
|
55
|
+
.lp-row-move[data-v-e0bb036b] {
|
|
56
56
|
transition: none;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -3,7 +3,7 @@ import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
|
|
|
3
3
|
import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
|
|
4
4
|
import { _ as _sfc_main$5 } from "./LpScrollArea-D8xyKXcF.js";
|
|
5
5
|
import { _ as _sfc_main$4 } from "./LpShift-DXNsauQZ.js";
|
|
6
|
-
import { _ as _sfc_main$1 } from "./LpSidebar-
|
|
6
|
+
import { _ as _sfc_main$1 } from "./LpSidebar-DvGfofv6.js";
|
|
7
7
|
import { useMergedAttrs } from "../composables/useMergedClass.js";
|
|
8
8
|
const _hoisted_1 = { class: "flex min-w-0 flex-1 flex-col" };
|
|
9
9
|
const _hoisted_2 = { class: "relative flex h-16 shrink-0 items-center gap-3 border-b border-line bg-surface px-4 md:px-6" };
|
|
@@ -5,6 +5,11 @@ import { _ as _sfc_main$2 } from "./LpCalendar-B7Ko5RdR.js";
|
|
|
5
5
|
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
6
6
|
const _hoisted_1 = ["disabled"];
|
|
7
7
|
const _hoisted_2 = ["onKeydown"];
|
|
8
|
+
const _hoisted_3 = {
|
|
9
|
+
key: 0,
|
|
10
|
+
class: "flex items-center gap-2 border-t border-line px-3 py-2"
|
|
11
|
+
};
|
|
12
|
+
const _hoisted_4 = ["value", "disabled"];
|
|
8
13
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
9
14
|
__name: "LpDatePicker",
|
|
10
15
|
props: {
|
|
@@ -17,18 +22,27 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
17
22
|
disabled: { type: Boolean },
|
|
18
23
|
invalid: { type: Boolean },
|
|
19
24
|
size: { default: "md" },
|
|
20
|
-
format: { default: () => ({ year: "numeric", month: "short", day: "numeric" }) }
|
|
25
|
+
format: { default: () => ({ year: "numeric", month: "short", day: "numeric" }) },
|
|
26
|
+
time: { type: Boolean },
|
|
27
|
+
defaultTime: { default: "00:00" },
|
|
28
|
+
doneLabel: { default: "Done" }
|
|
21
29
|
},
|
|
22
30
|
emits: ["update:modelValue"],
|
|
23
31
|
setup(__props, { emit: __emit }) {
|
|
24
32
|
const props = __props;
|
|
25
33
|
const emit = __emit;
|
|
26
34
|
const open = ref(false);
|
|
35
|
+
const day = computed(() => (props.modelValue || "").slice(0, 10));
|
|
36
|
+
const minute = computed(() => {
|
|
37
|
+
const at = (props.modelValue || "").slice(11, 16);
|
|
38
|
+
return /^\d{2}:\d{2}$/.test(at) ? at : "";
|
|
39
|
+
});
|
|
27
40
|
const display = computed(() => {
|
|
28
41
|
if (!props.modelValue) return "";
|
|
29
|
-
const [y, m, d] =
|
|
42
|
+
const [y, m, d] = day.value.split("-").map(Number);
|
|
30
43
|
if (!y || !m || !d) return props.modelValue;
|
|
31
|
-
|
|
44
|
+
const shown = new Intl.DateTimeFormat(void 0, props.format).format(new Date(y, m - 1, d));
|
|
45
|
+
return props.time && minute.value ? `${shown}, ${minute.value}` : shown;
|
|
32
46
|
});
|
|
33
47
|
const shellSize = {
|
|
34
48
|
sm: "h-(--size-control-sm) text-xs",
|
|
@@ -36,8 +50,16 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
36
50
|
lg: "h-(--size-control-lg) text-sm"
|
|
37
51
|
};
|
|
38
52
|
function onPick(v) {
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
if (!props.time) {
|
|
54
|
+
emit("update:modelValue", v);
|
|
55
|
+
open.value = false;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
emit("update:modelValue", v ? `${v}T${minute.value || props.defaultTime}` : void 0);
|
|
59
|
+
}
|
|
60
|
+
function onTime(at) {
|
|
61
|
+
if (!day.value) return;
|
|
62
|
+
emit("update:modelValue", `${day.value}T${at || props.defaultTime}`);
|
|
41
63
|
}
|
|
42
64
|
function clear() {
|
|
43
65
|
emit("update:modelValue", void 0);
|
|
@@ -45,7 +67,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
45
67
|
return (_ctx, _cache) => {
|
|
46
68
|
return openBlock(), createBlock(unref(PopoverRoot), {
|
|
47
69
|
open: open.value,
|
|
48
|
-
"onUpdate:open": _cache[
|
|
70
|
+
"onUpdate:open": _cache[2] || (_cache[2] = ($event) => open.value = $event)
|
|
49
71
|
}, {
|
|
50
72
|
default: withCtx(() => [
|
|
51
73
|
createVNode(unref(PopoverTrigger), { "as-child": "" }, {
|
|
@@ -98,13 +120,32 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
98
120
|
}, {
|
|
99
121
|
default: withCtx(() => [
|
|
100
122
|
createVNode(_sfc_main$2, {
|
|
101
|
-
"model-value":
|
|
123
|
+
"model-value": day.value,
|
|
102
124
|
min: __props.min,
|
|
103
125
|
max: __props.max,
|
|
104
126
|
"is-disabled": __props.isDisabled,
|
|
105
127
|
class: "border-0 bg-transparent",
|
|
106
128
|
"onUpdate:modelValue": onPick
|
|
107
|
-
}, null, 8, ["model-value", "min", "max", "is-disabled"])
|
|
129
|
+
}, null, 8, ["model-value", "min", "max", "is-disabled"]),
|
|
130
|
+
__props.time ? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
131
|
+
createVNode(_sfc_main$1, {
|
|
132
|
+
name: "lucide:clock",
|
|
133
|
+
size: 15,
|
|
134
|
+
class: "shrink-0 text-muted"
|
|
135
|
+
}),
|
|
136
|
+
createElementVNode("input", {
|
|
137
|
+
type: "time",
|
|
138
|
+
value: minute.value,
|
|
139
|
+
disabled: !day.value,
|
|
140
|
+
class: "h-(--size-control-sm) flex-1 rounded-control border border-line bg-surface-soft px-2 text-sm text-ink outline-none transition-colors focus-visible:border-brand focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
|
|
141
|
+
onInput: _cache[0] || (_cache[0] = ($event) => onTime($event.target.value))
|
|
142
|
+
}, null, 40, _hoisted_4),
|
|
143
|
+
createElementVNode("button", {
|
|
144
|
+
type: "button",
|
|
145
|
+
class: "shrink-0 rounded-control px-2 py-1 text-xs text-muted transition-colors hover:text-ink",
|
|
146
|
+
onClick: _cache[1] || (_cache[1] = ($event) => open.value = false)
|
|
147
|
+
}, toDisplayString(__props.doneLabel), 1)
|
|
148
|
+
])) : createCommentVNode("", true)
|
|
108
149
|
]),
|
|
109
150
|
_: 1
|
|
110
151
|
}, 8, ["class"])
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { defineComponent, computed, ref, useSlots, openBlock, createElementBlock, Fragment, createElementVNode, normalizeStyle, normalizeClass, withDirectives, vShow, toDisplayString, createCommentVNode, createVNode, unref, withCtx, createBlock, renderSlot, createTextVNode } from "vue";
|
|
1
|
+
import { defineComponent, computed, watch, onBeforeUnmount, ref, useSlots, openBlock, createElementBlock, Fragment, createElementVNode, normalizeStyle, normalizeClass, withDirectives, vShow, toDisplayString, createCommentVNode, createVNode, unref, withCtx, createBlock, renderSlot, createTextVNode } from "vue";
|
|
2
2
|
import { usePointerSwipe } from "@vueuse/core";
|
|
3
3
|
import { DrawerRoot, DrawerPortal, DrawerOverlay, DrawerContent, DrawerHandle, DrawerTitle, DrawerDescription, DrawerClose } from "vaul-vue";
|
|
4
4
|
import { CLOSE_ICON } from "../components/dropdown.js";
|
|
5
5
|
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
6
6
|
import { _ as _sfc_main$2 } from "./LpScrollArea-D8xyKXcF.js";
|
|
7
|
+
import { useModalLayer } from "../components/modalDepth.js";
|
|
7
8
|
const _hoisted_1 = { class: "text-base font-semibold text-ink" };
|
|
8
9
|
const _hoisted_2 = { class: "flex flex-col gap-1" };
|
|
9
10
|
const _hoisted_3 = ["data-vaul-no-drag"];
|
|
@@ -47,6 +48,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
47
48
|
const isSheet = computed(() => dir.value === "top" || dir.value === "bottom");
|
|
48
49
|
const showHandle = computed(() => props.handle ?? isSheet.value);
|
|
49
50
|
const SIZES = { sm: 22, md: 28, lg: 36, xl: 48 };
|
|
51
|
+
const { claim, release, layer } = useModalLayer();
|
|
52
|
+
watch(() => props.open, (open) => open ? claim() : release(), { immediate: true });
|
|
53
|
+
onBeforeUnmount(release);
|
|
50
54
|
const contentClass = computed(() => {
|
|
51
55
|
const base = "fixed z-(--z-modal) flex flex-col border-line bg-surface-raised shadow-panel outline-none";
|
|
52
56
|
const map = {
|
|
@@ -215,10 +219,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
215
219
|
default: withCtx(() => [
|
|
216
220
|
createVNode(unref(DrawerPortal), null, {
|
|
217
221
|
default: withCtx(() => [
|
|
218
|
-
createVNode(unref(DrawerOverlay), {
|
|
222
|
+
createVNode(unref(DrawerOverlay), {
|
|
223
|
+
style: normalizeStyle({ zIndex: unref(layer).scrim }),
|
|
224
|
+
class: "lp-scrim fixed inset-0 z-(--z-overlay) data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"
|
|
225
|
+
}, null, 8, ["style"]),
|
|
219
226
|
createVNode(unref(DrawerContent), {
|
|
220
227
|
class: normalizeClass(contentClass.value),
|
|
221
|
-
style: normalizeStyle(sizeStyle.value),
|
|
228
|
+
style: normalizeStyle([sizeStyle.value, { zIndex: unref(layer).panel }]),
|
|
222
229
|
"data-vaul-animate": handingOff.value ? "false" : void 0
|
|
223
230
|
}, {
|
|
224
231
|
default: withCtx(() => [
|
|
@@ -2,7 +2,7 @@ import { defineComponent, ref, computed, openBlock, createBlock, createSlots, wi
|
|
|
2
2
|
import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
|
|
3
3
|
import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
|
|
4
4
|
import { _ as _sfc_main$1 } from "./LpModal-D3QxgWaT.js";
|
|
5
|
-
import { _ as _sfc_main$4 } from "./LpSidebar-
|
|
5
|
+
import { _ as _sfc_main$4 } from "./LpSidebar-DvGfofv6.js";
|
|
6
6
|
const _hoisted_1 = { class: "flex items-center gap-2" };
|
|
7
7
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
8
8
|
__name: "LpModalNav",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineComponent, computed, ref, useId, openBlock, createElementBlock, Fragment, createElementVNode, mergeProps, normalizeClass, renderSlot, createCommentVNode, createBlock, createVNode, createSlots, withCtx, normalizeProps, guardReactiveProps } from "vue";
|
|
2
|
-
import { _ as _sfc_main$3 } from "./LpDrawer-
|
|
2
|
+
import { _ as _sfc_main$3 } from "./LpDrawer-CslZtgUm.js";
|
|
3
3
|
import { _ as _sfc_main$1 } from "./LpInput-9EXWUM9B.js";
|
|
4
4
|
import { _ as _sfc_main$2 } from "./LpSidebarNav-CqwvT4H8.js";
|
|
5
5
|
const _hoisted_1 = {
|
|
@@ -11,6 +11,12 @@ type __VLS_Props = {
|
|
|
11
11
|
size?: "sm" | "md" | "lg";
|
|
12
12
|
/** Intl format for the displayed date. */
|
|
13
13
|
format?: Intl.DateTimeFormatOptions;
|
|
14
|
+
/** Also pick a minute; the model becomes "YYYY-MM-DDTHH:mm". */
|
|
15
|
+
time?: boolean;
|
|
16
|
+
/** Time a freshly picked day starts at, "HH:mm". */
|
|
17
|
+
defaultTime?: string;
|
|
18
|
+
/** Label of the button that closes the popover in `time` mode. */
|
|
19
|
+
doneLabel?: string;
|
|
14
20
|
};
|
|
15
21
|
declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
|
16
22
|
"update:modelValue": (value: string | undefined) => any;
|
|
@@ -20,6 +26,8 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {
|
|
|
20
26
|
size: "sm" | "md" | "lg";
|
|
21
27
|
format: Intl.DateTimeFormatOptions;
|
|
22
28
|
placeholder: string;
|
|
29
|
+
defaultTime: string;
|
|
30
|
+
doneLabel: string;
|
|
23
31
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
24
32
|
declare const _default: typeof __VLS_export;
|
|
25
33
|
export default _default;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent,
|
|
1
|
+
import { defineComponent, useAttrs, computed, ref, shallowRef, watch, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, createVNode, mergeProps, withCtx, createElementVNode, createCommentVNode, Fragment, renderList, normalizeStyle, createBlock, createTextVNode, toDisplayString, TransitionGroup, unref, withModifiers, renderSlot, nextTick } from "vue";
|
|
2
2
|
import { _ as _sfc_main$3 } from "../chunks/LpCheckbox-CVccKYaH.js";
|
|
3
3
|
import { _ as _sfc_main$2 } from "../chunks/LpContextMenu-B6WBLleS.js";
|
|
4
4
|
import { _ as _sfc_main$4 } from "../chunks/LpIcon-CCnX5_2j.js";
|
|
@@ -7,20 +7,19 @@ import { _ as _sfc_main$1 } from "../chunks/LpScrollArea-D8xyKXcF.js";
|
|
|
7
7
|
import { useAsyncTask } from "../composables/useAsyncTask.js";
|
|
8
8
|
import { useRovingFocus } from "../composables/useRovingFocus.js";
|
|
9
9
|
import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
|
|
10
|
-
import '../assets/LpTable-
|
|
11
|
-
const _hoisted_2 = {
|
|
12
|
-
const _hoisted_3 = {
|
|
10
|
+
import '../assets/LpTable-BgPLJOXa.css';const _hoisted_1 = { class: "border-b border-line bg-surface-soft" };
|
|
11
|
+
const _hoisted_2 = {
|
|
13
12
|
key: 0,
|
|
14
13
|
class: "w-px px-4 py-3"
|
|
15
14
|
};
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
15
|
+
const _hoisted_3 = ["aria-sort"];
|
|
16
|
+
const _hoisted_4 = ["onClick"];
|
|
17
|
+
const _hoisted_5 = {
|
|
19
18
|
key: "lp-table-empty",
|
|
20
19
|
class: "lp-row-empty"
|
|
21
20
|
};
|
|
22
|
-
const
|
|
23
|
-
const
|
|
21
|
+
const _hoisted_6 = ["colspan"];
|
|
22
|
+
const _hoisted_7 = ["onContextmenu", "onClick"];
|
|
24
23
|
const __default__ = { inheritAttrs: false };
|
|
25
24
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
26
25
|
...__default__,
|
|
@@ -51,6 +50,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
51
50
|
emits: ["update:selected", "update:sort", "rowClick"],
|
|
52
51
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
53
52
|
const props = __props;
|
|
53
|
+
const attrs = useAttrs();
|
|
54
|
+
const rootClass = computed(() => attrs.class);
|
|
55
|
+
const scrollAttrs = computed(() => {
|
|
56
|
+
const { class: _class, ...rest } = attrs;
|
|
57
|
+
return rest;
|
|
58
|
+
});
|
|
54
59
|
const emit = __emit;
|
|
55
60
|
function keyFor(row, index) {
|
|
56
61
|
if (typeof props.rowKey === "function") return props.rowKey(row);
|
|
@@ -351,10 +356,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
351
356
|
() => props.stickyHeader && headHeight.value ? `${headHeight.value}px` : void 0
|
|
352
357
|
);
|
|
353
358
|
return (_ctx, _cache) => {
|
|
354
|
-
return openBlock(), createElementBlock("div",
|
|
355
|
-
|
|
359
|
+
return openBlock(), createElementBlock("div", {
|
|
360
|
+
class: normalizeClass(["flex flex-col gap-3", rootClass.value])
|
|
361
|
+
}, [
|
|
362
|
+
createVNode(_sfc_main$1, mergeProps(scrollAttrs.value, {
|
|
356
363
|
fade: false,
|
|
357
|
-
class: "rounded-card border border-line",
|
|
364
|
+
class: "min-h-0 flex-1 rounded-card border border-line",
|
|
358
365
|
style: __props.load && floor.value ? { minHeight: `${floor.value + 2}px` } : void 0,
|
|
359
366
|
"bar-inset-top": barInsetTop.value
|
|
360
367
|
}), {
|
|
@@ -374,8 +381,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
374
381
|
ref: headEl,
|
|
375
382
|
class: normalizeClass(__props.stickyHeader ? "sticky top-0 z-10 bg-surface-soft" : "")
|
|
376
383
|
}, [
|
|
377
|
-
createElementVNode("tr",
|
|
378
|
-
__props.selectable ? (openBlock(), createElementBlock("th",
|
|
384
|
+
createElementVNode("tr", _hoisted_1, [
|
|
385
|
+
__props.selectable ? (openBlock(), createElementBlock("th", _hoisted_2, [
|
|
379
386
|
createVNode(_sfc_main$3, {
|
|
380
387
|
"model-value": allChecked.value,
|
|
381
388
|
indeterminate: someChecked.value,
|
|
@@ -417,13 +424,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
417
424
|
activeSort.value?.key === col.key ? "opacity-100" : "opacity-40 group-hover/sort:opacity-70"
|
|
418
425
|
])
|
|
419
426
|
}, null, 8, ["name", "class"]))
|
|
420
|
-
], 10,
|
|
427
|
+
], 10, _hoisted_4)
|
|
421
428
|
]),
|
|
422
429
|
_: 2
|
|
423
430
|
}, 1032, ["items"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
424
431
|
createTextVNode(toDisplayString(col.label), 1)
|
|
425
432
|
], 64))
|
|
426
|
-
], 14,
|
|
433
|
+
], 14, _hoisted_3);
|
|
427
434
|
}), 128))
|
|
428
435
|
])
|
|
429
436
|
], 2),
|
|
@@ -435,7 +442,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
435
442
|
ref: (el) => unref(roving).setContainer(el?.$el ?? null)
|
|
436
443
|
}, {
|
|
437
444
|
default: withCtx(() => [
|
|
438
|
-
displayRows.value.length === 0 ? (openBlock(), createElementBlock("tr",
|
|
445
|
+
displayRows.value.length === 0 ? (openBlock(), createElementBlock("tr", _hoisted_5, [
|
|
439
446
|
createElementVNode("td", {
|
|
440
447
|
colspan: colSpan.value,
|
|
441
448
|
class: "px-4 py-10 text-center text-muted"
|
|
@@ -446,7 +453,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
446
453
|
class: normalizeClass(["mx-auto mb-2 opacity-60", unref(loading) ? "animate-spin" : ""])
|
|
447
454
|
}, null, 8, ["name", "class"]),
|
|
448
455
|
createElementVNode("div", null, toDisplayString(unref(loading) ? __props.loadingLabel : failed.value ? __props.errorLabel : __props.emptyLabel), 1)
|
|
449
|
-
], 8,
|
|
456
|
+
], 8, _hoisted_6)
|
|
450
457
|
])) : createCommentVNode("", true),
|
|
451
458
|
(openBlock(true), createElementBlock(Fragment, null, renderList(displayRows.value, (row, index) => {
|
|
452
459
|
return openBlock(), createElementBlock("tr", mergeProps({
|
|
@@ -481,7 +488,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
481
488
|
], true)
|
|
482
489
|
], 2);
|
|
483
490
|
}), 128))
|
|
484
|
-
], 16,
|
|
491
|
+
], 16, _hoisted_7);
|
|
485
492
|
}), 128))
|
|
486
493
|
]),
|
|
487
494
|
_: 3
|
|
@@ -501,11 +508,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
501
508
|
"page-size": __props.pageSize,
|
|
502
509
|
disabled: unref(loading)
|
|
503
510
|
}, null, 8, ["page", "total", "page-size", "disabled"])) : createCommentVNode("", true)
|
|
504
|
-
]);
|
|
511
|
+
], 2);
|
|
505
512
|
};
|
|
506
513
|
}
|
|
507
514
|
});
|
|
508
|
-
const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
515
|
+
const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-e0bb036b"]]);
|
|
509
516
|
export {
|
|
510
517
|
LpTable as default
|
|
511
518
|
};
|
|
@@ -14,6 +14,17 @@ function useShift(options = {}) {
|
|
|
14
14
|
let lastWidth = 0;
|
|
15
15
|
let inFlight = false;
|
|
16
16
|
let tweenStart = 0;
|
|
17
|
+
const scrolled = /* @__PURE__ */ new Map();
|
|
18
|
+
const rememberScroll = (event) => {
|
|
19
|
+
const target = event.target;
|
|
20
|
+
if (target instanceof Element) scrolled.set(target, target.scrollTop);
|
|
21
|
+
};
|
|
22
|
+
function restoreScroll() {
|
|
23
|
+
for (const [node, top] of scrolled) {
|
|
24
|
+
if (!node.isConnected) scrolled.delete(node);
|
|
25
|
+
else if (node.scrollTop !== top) node.scrollTop = top;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
const axisOf = () => toValue(options.axis) ?? "height";
|
|
18
29
|
const wantsHeight = () => axisOf() !== "width";
|
|
19
30
|
const wantsWidth = () => axisOf() !== "height";
|
|
@@ -40,6 +51,7 @@ function useShift(options = {}) {
|
|
|
40
51
|
const toWidth = wantsWidth() ? node.offsetWidth : 0;
|
|
41
52
|
if (wantsHeight()) node.style.maxHeight = "";
|
|
42
53
|
if (wantsWidth()) node.style.maxWidth = "";
|
|
54
|
+
restoreScroll();
|
|
43
55
|
lastHeight = toHeight;
|
|
44
56
|
lastWidth = toWidth;
|
|
45
57
|
const movesHeight = wantsHeight() && fromHeight !== 0 && toHeight !== fromHeight;
|
|
@@ -50,6 +62,7 @@ function useShift(options = {}) {
|
|
|
50
62
|
void node.offsetHeight;
|
|
51
63
|
if (movesHeight) node.style.height = `${toHeight}px`;
|
|
52
64
|
if (movesWidth) node.style.width = `${toWidth}px`;
|
|
65
|
+
restoreScroll();
|
|
53
66
|
inFlight = true;
|
|
54
67
|
tweenStart = performance.now();
|
|
55
68
|
resizing.value = true;
|
|
@@ -64,7 +77,11 @@ function useShift(options = {}) {
|
|
|
64
77
|
lastWidth = node.offsetWidth;
|
|
65
78
|
}, wait);
|
|
66
79
|
}
|
|
80
|
+
let scrollHost = null;
|
|
67
81
|
function teardown() {
|
|
82
|
+
scrollHost?.removeEventListener("scroll", rememberScroll, true);
|
|
83
|
+
scrollHost = null;
|
|
84
|
+
scrolled.clear();
|
|
68
85
|
ro?.disconnect();
|
|
69
86
|
mo?.disconnect();
|
|
70
87
|
ro = void 0;
|
|
@@ -84,6 +101,8 @@ function useShift(options = {}) {
|
|
|
84
101
|
if (!enabled || !node) return;
|
|
85
102
|
lastHeight = node.offsetHeight;
|
|
86
103
|
lastWidth = node.offsetWidth;
|
|
104
|
+
scrollHost = node;
|
|
105
|
+
node.addEventListener("scroll", rememberScroll, { capture: true, passive: true });
|
|
87
106
|
if (wantsHeight()) node.style.height = `${lastHeight}px`;
|
|
88
107
|
if (wantsWidth()) node.style.width = `${lastWidth}px`;
|
|
89
108
|
raf = requestAnimationFrame(() => {
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { _ as _3 } from "./chunks/LpMapPicker-ul4oYno6.js";
|
|
|
4
4
|
import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
|
|
5
5
|
import { _ as _4 } from "./chunks/LpActionBar-BlvtGB2P.js";
|
|
6
6
|
import { _ as _5 } from "./chunks/LpAlert-BAYHDSS1.js";
|
|
7
|
-
import { _ as _6 } from "./chunks/LpAppShell-
|
|
7
|
+
import { _ as _6 } from "./chunks/LpAppShell-Cs9jDoqJ.js";
|
|
8
8
|
import { _ as _7 } from "./chunks/LpAutocomplete-DExlIZe_.js";
|
|
9
9
|
import { _ as _8 } from "./chunks/LpAvatar-D3l4nabg.js";
|
|
10
10
|
import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
|
|
@@ -21,10 +21,10 @@ import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, n
|
|
|
21
21
|
import { default as default3 } from "./components/LpCommandPalette.vue.js";
|
|
22
22
|
import { _ as _17 } from "./chunks/LpConfirmDialog-B6RAvDES.js";
|
|
23
23
|
import { _ as _18 } from "./chunks/LpContextMenu-B6WBLleS.js";
|
|
24
|
-
import { _ as _19 } from "./chunks/LpDatePicker-
|
|
24
|
+
import { _ as _19 } from "./chunks/LpDatePicker-BPbvu3cZ.js";
|
|
25
25
|
import { _ as _20 } from "./chunks/LpDisclosure-ChQYmjlR.js";
|
|
26
26
|
import { _ as _21 } from "./chunks/LpDivider-CUX46KR0.js";
|
|
27
|
-
import { _ as _22 } from "./chunks/LpDrawer-
|
|
27
|
+
import { _ as _22 } from "./chunks/LpDrawer-CslZtgUm.js";
|
|
28
28
|
import { _ as _23 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
|
|
29
29
|
import { default as default4 } from "./components/LpDropzone.vue.js";
|
|
30
30
|
import { _ as _24 } from "./chunks/LpEmptyState-Dsdqz1Wj.js";
|
|
@@ -41,7 +41,7 @@ import { _ as _31 } from "./chunks/LpLink-DCND-zyu.js";
|
|
|
41
41
|
import { _ as _32 } from "./chunks/LpLogViewer-y53ZqSVq.js";
|
|
42
42
|
import { _ as _33 } from "./chunks/LpMediaGallery-Cid7PGYx.js";
|
|
43
43
|
import { _ as _34 } from "./chunks/LpModal-D3QxgWaT.js";
|
|
44
|
-
import { _ as _35 } from "./chunks/LpModalNav-
|
|
44
|
+
import { _ as _35 } from "./chunks/LpModalNav-Dkogc3O_.js";
|
|
45
45
|
import { _ as _36 } from "./chunks/LpNotificationBell-lHePf89W.js";
|
|
46
46
|
import { _ as _37 } from "./chunks/LpNumberField-CWuX08iX.js";
|
|
47
47
|
import { _ as _38 } from "./chunks/LpNumberFlow-amWYaeuA.js";
|
|
@@ -63,7 +63,7 @@ import { _ as _52 } from "./chunks/LpSelect-ANIVtrpM.js";
|
|
|
63
63
|
import { _ as _53 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
|
|
64
64
|
import { _ as _54 } from "./chunks/LpShift-DXNsauQZ.js";
|
|
65
65
|
import { default as default6 } from "./components/LpShortcutsDialog.vue.js";
|
|
66
|
-
import { _ as _55 } from "./chunks/LpSidebar-
|
|
66
|
+
import { _ as _55 } from "./chunks/LpSidebar-DvGfofv6.js";
|
|
67
67
|
import { _ as _56 } from "./chunks/LpSkeleton-CTI-vBgW.js";
|
|
68
68
|
import { _ as _57 } from "./chunks/LpSlider-BwNIBkAK.js";
|
|
69
69
|
import { _ as _58 } from "./chunks/LpSparkline-C6xQ2dwv.js";
|
package/package.json
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
* v-model), same as LpCalendar (which it embeds). The trigger shows a formatted
|
|
5
5
|
* date; picking a day fills the field and closes the popover. Clearable, with
|
|
6
6
|
* min/max + isDisabled forwarded to the calendar. Themed like the other inputs.
|
|
7
|
+
*
|
|
8
|
+
* With `time`, the model carries a minute too ("YYYY-MM-DDTHH:mm") and the
|
|
9
|
+
* popover grows a time row under the calendar. Anything that needs a moment
|
|
10
|
+
* rather than a day — an expiry, a scheduled start — would otherwise fall back
|
|
11
|
+
* to a native `datetime-local`, which the browser paints in its own locale and
|
|
12
|
+
* chrome, breaking the form it sits in. Picking a day keeps the time already
|
|
13
|
+
* chosen (default `defaultTime`), so the popover closes on the first click for
|
|
14
|
+
* the common case.
|
|
7
15
|
*/
|
|
8
16
|
import { PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from "reka-ui"
|
|
9
17
|
import { computed, ref } from "vue"
|
|
@@ -25,11 +33,19 @@ const props = withDefaults(
|
|
|
25
33
|
size?: "sm" | "md" | "lg"
|
|
26
34
|
/** Intl format for the displayed date. */
|
|
27
35
|
format?: Intl.DateTimeFormatOptions
|
|
36
|
+
/** Also pick a minute; the model becomes "YYYY-MM-DDTHH:mm". */
|
|
37
|
+
time?: boolean
|
|
38
|
+
/** Time a freshly picked day starts at, "HH:mm". */
|
|
39
|
+
defaultTime?: string
|
|
40
|
+
/** Label of the button that closes the popover in `time` mode. */
|
|
41
|
+
doneLabel?: string
|
|
28
42
|
}>(),
|
|
29
43
|
{
|
|
30
44
|
placeholder: "Pick a date",
|
|
31
45
|
size: "md",
|
|
32
46
|
format: () => ({ year: "numeric", month: "short", day: "numeric" }),
|
|
47
|
+
defaultTime: "00:00",
|
|
48
|
+
doneLabel: "Done",
|
|
33
49
|
},
|
|
34
50
|
)
|
|
35
51
|
|
|
@@ -39,12 +55,22 @@ const emit = defineEmits<{
|
|
|
39
55
|
|
|
40
56
|
const open = ref(false)
|
|
41
57
|
|
|
58
|
+
/** Day half of the model; the calendar never sees the time. */
|
|
59
|
+
const day = computed(() => (props.modelValue || "").slice(0, 10))
|
|
60
|
+
|
|
61
|
+
/** Minute half, "HH:mm" — empty when the model carries a date only. */
|
|
62
|
+
const minute = computed(() => {
|
|
63
|
+
const at = (props.modelValue || "").slice(11, 16)
|
|
64
|
+
return /^\d{2}:\d{2}$/.test(at) ? at : ""
|
|
65
|
+
})
|
|
66
|
+
|
|
42
67
|
const display = computed(() => {
|
|
43
68
|
if (!props.modelValue) return ""
|
|
44
69
|
// Parse as local midnight so the formatted day can't drift across a timezone.
|
|
45
|
-
const [y, m, d] =
|
|
70
|
+
const [y, m, d] = day.value.split("-").map(Number)
|
|
46
71
|
if (!y || !m || !d) return props.modelValue
|
|
47
|
-
|
|
72
|
+
const shown = new Intl.DateTimeFormat(undefined, props.format).format(new Date(y, m - 1, d))
|
|
73
|
+
return props.time && minute.value ? `${shown}, ${minute.value}` : shown
|
|
48
74
|
})
|
|
49
75
|
|
|
50
76
|
const shellSize = {
|
|
@@ -54,8 +80,21 @@ const shellSize = {
|
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
function onPick(v: string | undefined) {
|
|
57
|
-
|
|
58
|
-
|
|
83
|
+
if (!props.time) {
|
|
84
|
+
emit("update:modelValue", v)
|
|
85
|
+
open.value = false
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
// The popover stays open: the time row below is part of the same choice, and
|
|
89
|
+
// closing on the day would hide it right as it becomes reachable.
|
|
90
|
+
emit("update:modelValue", v ? `${v}T${minute.value || props.defaultTime}` : undefined)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onTime(at: string) {
|
|
94
|
+
// A time without a day would be a model no consumer can use; until a day is
|
|
95
|
+
// picked the row is disabled, so this only guards a stray event.
|
|
96
|
+
if (!day.value) return
|
|
97
|
+
emit("update:modelValue", `${day.value}T${at || props.defaultTime}`)
|
|
59
98
|
}
|
|
60
99
|
|
|
61
100
|
function clear() {
|
|
@@ -104,13 +143,30 @@ function clear() {
|
|
|
104
143
|
:class="[POPOVER_PANEL, 'z-(--z-popover) rounded-card p-0 outline-none']"
|
|
105
144
|
>
|
|
106
145
|
<LpCalendar
|
|
107
|
-
:model-value="
|
|
146
|
+
:model-value="day"
|
|
108
147
|
:min="min"
|
|
109
148
|
:max="max"
|
|
110
149
|
:is-disabled="isDisabled"
|
|
111
150
|
class="border-0 bg-transparent"
|
|
112
151
|
@update:model-value="onPick"
|
|
113
152
|
/>
|
|
153
|
+
<div v-if="time" class="flex items-center gap-2 border-t border-line px-3 py-2">
|
|
154
|
+
<LpIcon name="lucide:clock" :size="15" class="shrink-0 text-muted" />
|
|
155
|
+
<input
|
|
156
|
+
type="time"
|
|
157
|
+
:value="minute"
|
|
158
|
+
:disabled="!day"
|
|
159
|
+
class="h-(--size-control-sm) flex-1 rounded-control border border-line bg-surface-soft px-2 text-sm text-ink outline-none transition-colors focus-visible:border-brand focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-55"
|
|
160
|
+
@input="onTime(($event.target as HTMLInputElement).value)"
|
|
161
|
+
/>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="shrink-0 rounded-control px-2 py-1 text-xs text-muted transition-colors hover:text-ink"
|
|
165
|
+
@click="open = false"
|
|
166
|
+
>
|
|
167
|
+
{{ doneLabel }}
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
114
170
|
</PopoverContent>
|
|
115
171
|
</PopoverPortal>
|
|
116
172
|
</PopoverRoot>
|
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
DrawerRoot,
|
|
24
24
|
DrawerTitle,
|
|
25
25
|
} from "vaul-vue"
|
|
26
|
-
import { computed, ref, useSlots } from "vue"
|
|
26
|
+
import { computed, onBeforeUnmount, ref, useSlots, watch } from "vue"
|
|
27
27
|
import { CLOSE_ICON } from "./dropdown"
|
|
28
28
|
import LpIcon from "./LpIcon.vue"
|
|
29
29
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
30
|
+
import { useModalLayer } from "./modalDepth"
|
|
30
31
|
|
|
31
32
|
type Direction = "top" | "bottom" | "left" | "right"
|
|
32
33
|
|
|
@@ -125,6 +126,16 @@ const SIZES = { sm: 22, md: 28, lg: 36, xl: 48 } // rem along the main axis
|
|
|
125
126
|
// Geometry per edge. The cross-axis is pinned to the viewport; the main axis
|
|
126
127
|
// takes the size preset. vaul drives the open/close transform itself, so we
|
|
127
128
|
// don't add slide keyframes — only the resting box + rounding toward the centre.
|
|
129
|
+
/*
|
|
130
|
+
* The drawer joins the same stack as LpModal. It painted at the fixed
|
|
131
|
+
* --z-overlay/--z-modal rungs, so a dialog opened from it (a picker inside a
|
|
132
|
+
* settings drawer) claimed those same rungs and the tie went to DOM order —
|
|
133
|
+
* the drawer, portalled later on reopen, covered the dialog it had opened.
|
|
134
|
+
*/
|
|
135
|
+
const { claim, release, layer } = useModalLayer()
|
|
136
|
+
watch(() => props.open, (open) => (open ? claim() : release()), { immediate: true })
|
|
137
|
+
onBeforeUnmount(release)
|
|
138
|
+
|
|
128
139
|
const contentClass = computed(() => {
|
|
129
140
|
const base = "fixed z-(--z-modal) flex flex-col border-line bg-surface-raised shadow-panel outline-none"
|
|
130
141
|
const map: Record<Direction, string> = {
|
|
@@ -345,11 +356,12 @@ const edgeStripClass = computed(() =>
|
|
|
345
356
|
>
|
|
346
357
|
<DrawerPortal>
|
|
347
358
|
<DrawerOverlay
|
|
359
|
+
:style="{ zIndex: layer.scrim }"
|
|
348
360
|
class="lp-scrim fixed inset-0 z-(--z-overlay) data-[state=open]:animate-[fade-in_150ms_ease] data-[state=closed]:animate-[fade-out_130ms_ease]"
|
|
349
361
|
/>
|
|
350
362
|
<DrawerContent
|
|
351
363
|
:class="contentClass"
|
|
352
|
-
:style="sizeStyle"
|
|
364
|
+
:style="[sizeStyle, { zIndex: layer.panel }]"
|
|
353
365
|
:data-vaul-animate="handingOff ? 'false' : undefined"
|
|
354
366
|
>
|
|
355
367
|
<!-- Resize grip on the inner edge (horizontal drawers). data-vaul-no-drag
|
|
@@ -5,7 +5,7 @@ export default { inheritAttrs: false }
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts" generic="T extends Record<string, unknown>">
|
|
8
|
-
import { computed, nextTick, onBeforeUnmount, ref, shallowRef, watch } from "vue"
|
|
8
|
+
import { computed, nextTick, onBeforeUnmount, ref, shallowRef, useAttrs, watch } from "vue"
|
|
9
9
|
import LpCheckbox from "./LpCheckbox.vue"
|
|
10
10
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
11
11
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -132,6 +132,21 @@ const props = withDefaults(
|
|
|
132
132
|
},
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
/*
|
|
136
|
+
* Layout classes belong on the root, not on the scroll area. Since the pager
|
|
137
|
+
* wrapper became the root, a caller's `min-h-0 flex-1` landed on the inner
|
|
138
|
+
* scroll area while the wrapper kept sizing to its content — in a flex column
|
|
139
|
+
* the table grew past its container and its bottom edge was clipped away.
|
|
140
|
+
* Everything else (style, aria, listeners) still goes to the scroll area, so a
|
|
141
|
+
* `max-height` keeps capping the scrolling part.
|
|
142
|
+
*/
|
|
143
|
+
const attrs = useAttrs()
|
|
144
|
+
const rootClass = computed(() => attrs.class)
|
|
145
|
+
const scrollAttrs = computed(() => {
|
|
146
|
+
const { class: _class, ...rest } = attrs
|
|
147
|
+
return rest
|
|
148
|
+
})
|
|
149
|
+
|
|
135
150
|
const emit = defineEmits<{
|
|
136
151
|
(e: "update:selected", value: RowKey[]): void
|
|
137
152
|
(e: "update:sort", value: SortState | null): void
|
|
@@ -577,11 +592,11 @@ const barInsetTop = computed(() =>
|
|
|
577
592
|
<template>
|
|
578
593
|
<!-- No top fade: a sticky header sits in the masked band and would be dimmed
|
|
579
594
|
by it. The bottom edge is left crisp with it for symmetry. -->
|
|
580
|
-
<div class="flex flex-col gap-3">
|
|
595
|
+
<div class="flex flex-col gap-3" :class="rootClass">
|
|
581
596
|
<LpScrollArea
|
|
582
|
-
v-bind="
|
|
597
|
+
v-bind="scrollAttrs"
|
|
583
598
|
:fade="false"
|
|
584
|
-
class="rounded-card border border-line"
|
|
599
|
+
class="min-h-0 flex-1 rounded-card border border-line"
|
|
585
600
|
:style="load && floor ? { minHeight: `${floor + 2}px` } : undefined"
|
|
586
601
|
:bar-inset-top="barInsetTop"
|
|
587
602
|
>
|
|
@@ -91,6 +91,25 @@ export function useShift(
|
|
|
91
91
|
let inFlight = false
|
|
92
92
|
/** When the in-flight tween started, to tell a duplicate from a follow-up. */
|
|
93
93
|
let tweenStart = 0
|
|
94
|
+
/**
|
|
95
|
+
* Scroll positions inside the box, kept from the scroll events themselves.
|
|
96
|
+
* Measuring lifts the height cap, and for that one reflow a scrolling list
|
|
97
|
+
* inside (a picker, a span tree) is as tall as its content: it no longer
|
|
98
|
+
* overflows, the browser clamps its scrollTop to 0, and putting the cap back
|
|
99
|
+
* does not bring the position back. Every click that changed the content —
|
|
100
|
+
* ticking an item, picking a span — threw the list back to the top.
|
|
101
|
+
*/
|
|
102
|
+
const scrolled = new Map<Element, number>()
|
|
103
|
+
const rememberScroll = (event: Event) => {
|
|
104
|
+
const target = event.target
|
|
105
|
+
if (target instanceof Element) scrolled.set(target, target.scrollTop)
|
|
106
|
+
}
|
|
107
|
+
function restoreScroll() {
|
|
108
|
+
for (const [node, top] of scrolled) {
|
|
109
|
+
if (!node.isConnected) scrolled.delete(node)
|
|
110
|
+
else if (node.scrollTop !== top) node.scrollTop = top
|
|
111
|
+
}
|
|
112
|
+
}
|
|
94
113
|
|
|
95
114
|
const axisOf = () => toValue(options.axis) ?? "height"
|
|
96
115
|
const wantsHeight = () => axisOf() !== "width"
|
|
@@ -151,6 +170,7 @@ export function useShift(
|
|
|
151
170
|
const toWidth = wantsWidth() ? node.offsetWidth : 0
|
|
152
171
|
if (wantsHeight()) node.style.maxHeight = ""
|
|
153
172
|
if (wantsWidth()) node.style.maxWidth = ""
|
|
173
|
+
restoreScroll()
|
|
154
174
|
lastHeight = toHeight
|
|
155
175
|
lastWidth = toWidth
|
|
156
176
|
|
|
@@ -173,6 +193,7 @@ export function useShift(
|
|
|
173
193
|
void node.offsetHeight
|
|
174
194
|
if (movesHeight) node.style.height = `${toHeight}px`
|
|
175
195
|
if (movesWidth) node.style.width = `${toWidth}px`
|
|
196
|
+
restoreScroll()
|
|
176
197
|
|
|
177
198
|
inFlight = true
|
|
178
199
|
tweenStart = performance.now()
|
|
@@ -194,7 +215,12 @@ export function useShift(
|
|
|
194
215
|
}, wait)
|
|
195
216
|
}
|
|
196
217
|
|
|
218
|
+
let scrollHost: HTMLElement | null = null
|
|
219
|
+
|
|
197
220
|
function teardown() {
|
|
221
|
+
scrollHost?.removeEventListener("scroll", rememberScroll, true)
|
|
222
|
+
scrollHost = null
|
|
223
|
+
scrolled.clear()
|
|
198
224
|
ro?.disconnect()
|
|
199
225
|
mo?.disconnect()
|
|
200
226
|
ro = undefined
|
|
@@ -218,6 +244,8 @@ export function useShift(
|
|
|
218
244
|
|
|
219
245
|
lastHeight = node.offsetHeight
|
|
220
246
|
lastWidth = node.offsetWidth
|
|
247
|
+
scrollHost = node
|
|
248
|
+
node.addEventListener("scroll", rememberScroll, { capture: true, passive: true })
|
|
221
249
|
if (wantsHeight()) node.style.height = `${lastHeight}px`
|
|
222
250
|
if (wantsWidth()) node.style.width = `${lastWidth}px`
|
|
223
251
|
// A box must not animate in from its pre-content size, so the transition
|