@leavepulse/ui 0.52.0 → 0.53.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.
@@ -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-602c1271],
15
- .lp-row-leave-active[data-v-602c1271] {
14
+ .lp-row-enter-active[data-v-a6bdc221],
15
+ .lp-row-leave-active[data-v-a6bdc221] {
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-602c1271] {
20
+ .lp-row-move[data-v-a6bdc221] {
21
21
  transition: transform var(--duration-medium) var(--ease-emphasized);
22
22
  }
23
- .lp-row-enter-from[data-v-602c1271],
24
- .lp-row-leave-to[data-v-602c1271] {
23
+ .lp-row-enter-from[data-v-a6bdc221],
24
+ .lp-row-leave-to[data-v-a6bdc221] {
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-602c1271] {
38
+ .lp-row-empty.lp-row-leave-active[data-v-a6bdc221] {
39
39
  display: none;
40
40
  }
41
41
 
@@ -54,7 +54,7 @@
54
54
  * once anything is selected. An earlier version collapsed the column to nothing
55
55
  * and floated the hover box over the first cell, where it sat on the text.
56
56
  */
57
- .lp-tick[data-v-602c1271] {
57
+ .lp-tick[data-v-a6bdc221] {
58
58
  display: flex;
59
59
  align-items: center;
60
60
  max-width: 0;
@@ -66,17 +66,17 @@
66
66
  padding var(--duration-medium) var(--ease-emphasized),
67
67
  opacity var(--duration-fast) ease;
68
68
  }
69
- .lp-tick-open[data-v-602c1271] {
69
+ .lp-tick-open[data-v-a6bdc221] {
70
70
  max-width: 3.25rem;
71
71
  padding-inline: 1rem;
72
72
  opacity: 1;
73
73
  }
74
- .lp-tick-hidden[data-v-602c1271],
75
- .lp-tick-cue[data-v-602c1271] {
74
+ .lp-tick-hidden[data-v-a6bdc221],
75
+ .lp-tick-cue[data-v-a6bdc221] {
76
76
  opacity: 0;
77
77
  }
78
- tr:hover .lp-tick-cue[data-v-602c1271],
79
- .lp-tick-cue[data-v-602c1271]:focus-within {
78
+ tr:hover .lp-tick-cue[data-v-a6bdc221],
79
+ .lp-tick-cue[data-v-a6bdc221]:focus-within {
80
80
  opacity: 1;
81
81
  }
82
82
 
@@ -84,24 +84,24 @@ tr:hover .lp-tick-cue[data-v-602c1271],
84
84
  turned down the rows change without animating rather than not changing. */
85
85
  /* Only where there is a hover to reveal it; a finger long-presses instead. */
86
86
  @media (hover: none) {
87
- tr:hover .lp-tick-cue[data-v-602c1271] {
87
+ tr:hover .lp-tick-cue[data-v-a6bdc221] {
88
88
  opacity: 0;
89
89
  }
90
90
 
91
91
  /* Nor is there anything for the column's width to hold until then: on a
92
92
  touch screen it stays closed until a long press starts selecting, and
93
93
  eases open like the rest of the column. */
94
- .lp-tick-hidden[data-v-602c1271],
95
- .lp-tick-cue[data-v-602c1271] {
94
+ .lp-tick-hidden[data-v-a6bdc221],
95
+ .lp-tick-cue[data-v-a6bdc221] {
96
96
  max-width: 0;
97
97
  padding-inline: 0;
98
98
  }
99
99
  }
100
100
  @media (prefers-reduced-motion: reduce) {
101
- .lp-row-enter-active[data-v-602c1271],
102
- .lp-row-leave-active[data-v-602c1271],
103
- .lp-row-move[data-v-602c1271],
104
- .lp-tick[data-v-602c1271] {
101
+ .lp-row-enter-active[data-v-a6bdc221],
102
+ .lp-row-leave-active[data-v-a6bdc221],
103
+ .lp-row-move[data-v-a6bdc221],
104
+ .lp-tick[data-v-a6bdc221] {
105
105
  transition: none;
106
106
  }
107
107
  }
@@ -1,10 +1,12 @@
1
- import { defineComponent, computed, openBlock, createElementBlock, mergeProps, unref, createElementVNode, normalizeClass, Fragment, renderList, createBlock, createCommentVNode, resolveDynamicComponent, withCtx, createTextVNode, toDisplayString } from "vue";
1
+ import { defineComponent, computed, openBlock, createElementBlock, mergeProps, unref, createElementVNode, normalizeClass, Fragment, renderList, createBlock, createCommentVNode, resolveDynamicComponent, withCtx, toDisplayString } from "vue";
2
2
  import { _ as _sfc_main$1 } from "./LpIcon-DYtaid0K.js";
3
3
  import { useLabels, useLinkComponent } from "../composables/useLpConfig.js";
4
4
  import { useMergedAttrs } from "../composables/useMergedClass.js";
5
5
  import { resolveLink } from "../components/link.js";
6
6
  const _hoisted_1 = ["aria-label"];
7
- const _hoisted_2 = ["aria-current"];
7
+ const _hoisted_2 = { class: "truncate" };
8
+ const _hoisted_3 = ["aria-current"];
9
+ const _hoisted_4 = { class: "truncate" };
8
10
  const __default__ = { inheritAttrs: false };
9
11
  const _sfc_main = /* @__PURE__ */ defineComponent({
10
12
  ...__default__,
@@ -34,11 +36,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
34
36
  }))
35
37
  );
36
38
  const { class: rootClass, attrs: rest } = useMergedAttrs(
37
- "flex items-center gap-1.5 text-sm"
39
+ "flex min-w-0 items-center gap-1.5 text-sm"
38
40
  );
39
41
  return (_ctx, _cache) => {
40
42
  return openBlock(), createElementBlock("nav", mergeProps({
41
- "aria-label": unref(text).nav
43
+ "aria-label": unref(text).nav,
44
+ class: "min-w-0"
42
45
  }, unref(rest)), [
43
46
  createElementVNode("ol", {
44
47
  class: normalizeClass(unref(rootClass))
@@ -46,20 +49,20 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
46
49
  (openBlock(true), createElementBlock(Fragment, null, renderList(crumbs.value, ({ item, last, link }, i) => {
47
50
  return openBlock(), createElementBlock("li", {
48
51
  key: i,
49
- class: "inline-flex items-center gap-1.5"
52
+ class: normalizeClass(["inline-flex min-w-0 items-center gap-1.5", last ? "shrink" : "shrink-0"])
50
53
  }, [
51
54
  i > 0 ? (openBlock(), createBlock(_sfc_main$1, {
52
55
  key: 0,
53
56
  name: "lucide:chevron-right",
54
57
  size: 14,
55
- class: "text-muted",
58
+ class: "shrink-0 text-muted",
56
59
  "aria-hidden": "true"
57
60
  })) : createCommentVNode("", true),
58
61
  link ? (openBlock(), createBlock(resolveDynamicComponent(link.is), mergeProps({
59
62
  key: 1,
60
63
  ref_for: true
61
64
  }, link.props, {
62
- class: "inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring",
65
+ class: "inline-flex min-w-0 items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring",
63
66
  onClick: ($event) => _ctx.$emit("navigate", item, i)
64
67
  }), {
65
68
  default: withCtx(() => [
@@ -68,12 +71,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
68
71
  name: item.icon,
69
72
  size: 14
70
73
  }, null, 8, ["name"])) : createCommentVNode("", true),
71
- createTextVNode(" " + toDisplayString(item.label), 1)
74
+ createElementVNode("span", _hoisted_2, toDisplayString(item.label), 1)
72
75
  ]),
73
76
  _: 2
74
77
  }, 1040, ["onClick"])) : (openBlock(), createElementBlock("span", {
75
78
  key: 2,
76
- class: normalizeClass(["inline-flex items-center gap-1.5", last ? "text-ink" : "text-muted"]),
79
+ class: normalizeClass(["inline-flex min-w-0 items-center gap-1.5", last ? "text-ink" : "text-muted"]),
77
80
  "aria-current": last ? "page" : void 0
78
81
  }, [
79
82
  item.icon ? (openBlock(), createBlock(_sfc_main$1, {
@@ -81,9 +84,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
81
84
  name: item.icon,
82
85
  size: 14
83
86
  }, null, 8, ["name"])) : createCommentVNode("", true),
84
- createTextVNode(" " + toDisplayString(item.label), 1)
85
- ], 10, _hoisted_2))
86
- ]);
87
+ createElementVNode("span", _hoisted_4, toDisplayString(item.label), 1)
88
+ ], 10, _hoisted_3))
89
+ ], 2);
87
90
  }), 128))
88
91
  ], 2)
89
92
  ], 16, _hoisted_1);
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpBreadcrumbs-CQJd73Gm.js";
1
+ import { _ as _sfc_main } from "../chunks/LpBreadcrumbs-D4TsoFju.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -211,6 +211,7 @@ declare const __VLS_export: <T extends Record<string, unknown>>(__VLS_props: Non
211
211
  [x: `cell-${string}`]: ((props: {
212
212
  row: T;
213
213
  value: unknown;
214
+ openMenu: (event: Event) => void;
214
215
  }) => any) | undefined;
215
216
  } & {
216
217
  bulk?: (props: {
@@ -18,7 +18,7 @@ import { _ as _sfc_main$1 } from "../chunks/LpButton-CKSwC6Mu.js";
18
18
  import { toCsv, downloadFile, fileStem } from "./exportData.js";
19
19
  import { summarizeShown, parseTableState, serializeTableState, countValues } from "./tableFilter.js";
20
20
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
21
- import '../assets/LpTable-D8jvowDb.css';const _hoisted_1 = {
21
+ import '../assets/LpTable-BNzavdlv.css';const _hoisted_1 = {
22
22
  key: 0,
23
23
  class: "flex justify-end"
24
24
  };
@@ -164,6 +164,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
164
164
  function onRowContextMenu(row) {
165
165
  menuRow.value = row;
166
166
  }
167
+ function openRowMenu(event) {
168
+ const button = event.currentTarget;
169
+ if (!(button instanceof HTMLElement)) return;
170
+ const rect = button.getBoundingClientRect();
171
+ button.dispatchEvent(
172
+ new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom })
173
+ );
174
+ }
167
175
  const contextMenu = computed(() => {
168
176
  if (paint.painting.value) return [];
169
177
  const table = tableMenu();
@@ -917,7 +925,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
917
925
  }, [
918
926
  renderSlot(_ctx.$slots, `cell-${col.key}`, {
919
927
  row,
920
- value: row[col.key]
928
+ value: row[col.key],
929
+ openMenu: openRowMenu
921
930
  }, () => [
922
931
  createTextVNode(toDisplayString(row[col.key]), 1)
923
932
  ], true)
@@ -947,7 +956,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
947
956
  };
948
957
  }
949
958
  });
950
- const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-602c1271"]]);
959
+ const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-a6bdc221"]]);
951
960
  export {
952
961
  LpTable as default
953
962
  };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import { _ as _6 } from "./chunks/LpAppShell-BmMYcEVx.js";
8
8
  import { _ as _7 } from "./chunks/LpAutocomplete-C8IgSMsi.js";
9
9
  import { _ as _8 } from "./chunks/LpAvatar-Cb1jtOVv.js";
10
10
  import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
11
- import { _ as _10 } from "./chunks/LpBreadcrumbs-CQJd73Gm.js";
11
+ import { _ as _10 } from "./chunks/LpBreadcrumbs-D4TsoFju.js";
12
12
  import { _ as _11 } from "./chunks/LpButton-CKSwC6Mu.js";
13
13
  import { _ as _12 } from "./chunks/LpCalendar-DrA2McTU.js";
14
14
  import { _ as _13 } from "./chunks/LpCard-BdxkMXjo.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -53,43 +53,47 @@ const crumbs = computed(() =>
53
53
 
54
54
  // The trail sets its own gap, which a dense header bar needs to reduce.
55
55
  const { class: rootClass, attrs: rest } = useMergedAttrs(
56
- "flex items-center gap-1.5 text-sm",
56
+ "flex min-w-0 items-center gap-1.5 text-sm",
57
57
  )
58
58
  </script>
59
59
 
60
60
  <template>
61
- <nav :aria-label="text.nav" v-bind="rest">
61
+ <!-- One line however long the labels: the earlier crumbs keep their size
62
+ and the current one gives way with an ellipsis. Wrapping, a long name
63
+ pushed the trail onto three lines over whatever bar held it. -->
64
+ <nav :aria-label="text.nav" class="min-w-0" v-bind="rest">
62
65
  <ol :class="rootClass">
63
66
  <li
64
67
  v-for="({ item, last, link }, i) in crumbs"
65
68
  :key="i"
66
- class="inline-flex items-center gap-1.5"
69
+ class="inline-flex min-w-0 items-center gap-1.5"
70
+ :class="last ? 'shrink' : 'shrink-0'"
67
71
  >
68
72
  <LpIcon
69
73
  v-if="i > 0"
70
74
  name="lucide:chevron-right"
71
75
  :size="14"
72
- class="text-muted"
76
+ class="shrink-0 text-muted"
73
77
  aria-hidden="true"
74
78
  />
75
79
  <component
76
80
  :is="link.is"
77
81
  v-if="link"
78
82
  v-bind="link.props"
79
- class="inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
83
+ class="inline-flex min-w-0 items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
80
84
  @click="$emit('navigate', item, i)"
81
85
  >
82
86
  <LpIcon v-if="item.icon" :name="item.icon" :size="14" />
83
- {{ item.label }}
87
+ <span class="truncate">{{ item.label }}</span>
84
88
  </component>
85
89
  <span
86
90
  v-else
87
- class="inline-flex items-center gap-1.5"
91
+ class="inline-flex min-w-0 items-center gap-1.5"
88
92
  :class="last ? 'text-ink' : 'text-muted'"
89
93
  :aria-current="last ? 'page' : undefined"
90
94
  >
91
95
  <LpIcon v-if="item.icon" :name="item.icon" :size="14" />
92
- {{ item.label }}
96
+ <span class="truncate">{{ item.label }}</span>
93
97
  </span>
94
98
  </li>
95
99
  </ol>
@@ -355,6 +355,23 @@ function onRowContextMenu(row: T) {
355
355
  menuRow.value = row
356
356
  }
357
357
 
358
+ /*
359
+ * The same menu on a button of the row's own, a "more" button in an actions
360
+ * cell: cell slots get `openMenu` for its click. A dropdown per row stood up
361
+ * the full reka menu stack in every row, closed — some twenty components a
362
+ * row, which on a phone made a list of a few hundred rows take most of a
363
+ * second to appear. Here the click becomes a right-click at the button's
364
+ * corner, which the row remembers on its way up and the shared menu opens at.
365
+ */
366
+ function openRowMenu(event: Event) {
367
+ const button = event.currentTarget
368
+ if (!(button instanceof HTMLElement)) return
369
+ const rect = button.getBoundingClientRect()
370
+ button.dispatchEvent(
371
+ new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom }),
372
+ )
373
+ }
374
+
358
375
  // Row items first — they are what the pointer is actually on — then the
359
376
  // table-wide ones under a rule, when the row has any of its own.
360
377
  const contextMenu = computed<ContextMenuItemDef[]>(() => {
@@ -1300,7 +1317,7 @@ const barInsetTop = computed(() =>
1300
1317
  class="px-4 text-ink"
1301
1318
  :class="[alignClass(col.align), cellY, col.nowrap ? 'whitespace-nowrap' : '']"
1302
1319
  >
1303
- <slot :name="`cell-${col.key}`" :row="row" :value="row[col.key]">
1320
+ <slot :name="`cell-${col.key}`" :row="row" :value="row[col.key]" :open-menu="openRowMenu">
1304
1321
  {{ row[col.key] }}
1305
1322
  </slot>
1306
1323
  </td>