@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.
@@ -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-63738e53],
15
- .lp-row-leave-active[data-v-63738e53] {
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-63738e53] {
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-63738e53],
24
- .lp-row-leave-to[data-v-63738e53] {
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-63738e53] {
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-63738e53],
54
- .lp-row-leave-active[data-v-63738e53],
55
- .lp-row-move[data-v-63738e53] {
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-DcYavim2.js";
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] = props.modelValue.split("-").map(Number);
42
+ const [y, m, d] = day.value.split("-").map(Number);
30
43
  if (!y || !m || !d) return props.modelValue;
31
- return new Intl.DateTimeFormat(void 0, props.format).format(new Date(y, m - 1, d));
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
- emit("update:modelValue", v);
40
- open.value = false;
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[0] || (_cache[0] = ($event) => open.value = $event)
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": __props.modelValue,
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), { 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]" }),
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-DcYavim2.js";
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-DAJN5rGK.js";
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 = {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAppShell-lVvpENMH.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAppShell-Cs9jDoqJ.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -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 { _ as _sfc_main } from "../chunks/LpDatePicker-6JFXllj5.js";
1
+ import { _ as _sfc_main } from "../chunks/LpDatePicker-BPbvu3cZ.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpDrawer-DAJN5rGK.js";
1
+ import { _ as _sfc_main } from "../chunks/LpDrawer-CslZtgUm.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpModalNav-K6N5YpQk.js";
1
+ import { _ as _sfc_main } from "../chunks/LpModalNav-Dkogc3O_.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebar-DcYavim2.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebar-DvGfofv6.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, computed, shallowRef, watch, onBeforeUnmount, openBlock, createElementBlock, createVNode, mergeProps, withCtx, createElementVNode, normalizeClass, createCommentVNode, Fragment, renderList, normalizeStyle, createBlock, createTextVNode, toDisplayString, TransitionGroup, unref, withModifiers, renderSlot, nextTick } from "vue";
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-BbOFmZfc.css';const _hoisted_1 = { class: "flex flex-col gap-3" };
11
- const _hoisted_2 = { class: "border-b border-line bg-surface-soft" };
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 _hoisted_4 = ["aria-sort"];
17
- const _hoisted_5 = ["onClick"];
18
- const _hoisted_6 = {
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 _hoisted_7 = ["colspan"];
23
- const _hoisted_8 = ["onContextmenu", "onClick"];
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", _hoisted_1, [
355
- createVNode(_sfc_main$1, mergeProps(_ctx.$attrs, {
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", _hoisted_2, [
378
- __props.selectable ? (openBlock(), createElementBlock("th", _hoisted_3, [
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, _hoisted_5)
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, _hoisted_4);
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", _hoisted_6, [
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, _hoisted_7)
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, _hoisted_8);
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-63738e53"]]);
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-lVvpENMH.js";
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-6JFXllj5.js";
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-DAJN5rGK.js";
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-K6N5YpQk.js";
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-DcYavim2.js";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.37.1",
3
+ "version": "0.37.3",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -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] = props.modelValue.split("-").map(Number)
70
+ const [y, m, d] = day.value.split("-").map(Number)
46
71
  if (!y || !m || !d) return props.modelValue
47
- return new Intl.DateTimeFormat(undefined, props.format).format(new Date(y, m - 1, d))
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
- emit("update:modelValue", v)
58
- open.value = false
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="modelValue"
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="$attrs"
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