@leavepulse/ui 0.29.3 → 0.29.4

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.
Files changed (34) hide show
  1. package/dist/assets/{LpTable-B9Iko3XD.css → LpTable-Bz3uJNa3.css} +8 -8
  2. package/dist/chunks/{LpAppShell-CGX0DlIW.js → LpAppShell-BJk3b-u4.js} +1 -1
  3. package/dist/chunks/{LpAvatar-CzCE5cXg.js → LpAvatar-D3l4nabg.js} +1 -1
  4. package/dist/chunks/{LpCard-DSefTGEH.js → LpCard-D0M2ZCnB.js} +1 -1
  5. package/dist/chunks/{LpChart-UiU4RhXP.js → LpChart-Bmw7y34Z.js} +1 -1
  6. package/dist/chunks/{LpContextMenu-D0mzXqA7.js → LpContextMenu-B6WBLleS.js} +55 -8
  7. package/dist/chunks/{LpFileTree-BaV7f0O7.js → LpFileTree-DfFqY__1.js} +1 -1
  8. package/dist/chunks/{LpFileTreeNode-BzpBa7R7.js → LpFileTreeNode-COchUMNn.js} +1 -1
  9. package/dist/chunks/{LpLogViewer-CKmEQYnm.js → LpLogViewer-CVaGfTF_.js} +1 -1
  10. package/dist/chunks/{LpNotificationBell-Dvu-b6Vn.js → LpNotificationBell-CTsIcPqX.js} +1 -1
  11. package/dist/chunks/{LpPie-BkjWlfSw.js → LpPie-BL1vTDSM.js} +1 -1
  12. package/dist/chunks/{LpSidebar-CWehDCxN.js → LpSidebar-C_Mr-JPI.js} +1 -1
  13. package/dist/chunks/{LpSidebarNav-5RGNr2D3.js → LpSidebarNav-D6v12-36.js} +1 -1
  14. package/dist/chunks/{LpThemeSwitcher-Dj4VcTu7.js → LpThemeSwitcher-r1te9nB2.js} +1 -1
  15. package/dist/components/LpAppShell.vue.js +1 -1
  16. package/dist/components/LpAvatar.vue.js +1 -1
  17. package/dist/components/LpCard.vue.js +1 -1
  18. package/dist/components/LpChart.vue.js +1 -1
  19. package/dist/components/LpContextMenu.vue.d.ts +7 -0
  20. package/dist/components/LpContextMenu.vue.js +1 -1
  21. package/dist/components/LpFileTree.vue.js +1 -1
  22. package/dist/components/LpFileTreeNode.vue.js +1 -1
  23. package/dist/components/LpLogViewer.vue.js +1 -1
  24. package/dist/components/LpNotificationBell.vue.js +1 -1
  25. package/dist/components/LpPie.vue.js +1 -1
  26. package/dist/components/LpSidebar.vue.js +1 -1
  27. package/dist/components/LpSidebarNav.vue.js +1 -1
  28. package/dist/components/LpTable.vue.d.ts +19 -0
  29. package/dist/components/LpTable.vue.js +233 -143
  30. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  31. package/dist/index.js +12 -12
  32. package/package.json +1 -1
  33. package/src/components/LpContextMenu.vue +49 -0
  34. package/src/components/LpTable.vue +166 -23
@@ -1,11 +1,11 @@
1
- import { defineComponent, ref, computed, watch, onBeforeUnmount, openBlock, createBlock, withCtx, createElementVNode, normalizeClass, createElementBlock, createVNode, createCommentVNode, Fragment, renderList, normalizeStyle, createTextVNode, toDisplayString, TransitionGroup, unref, mergeProps, withModifiers, renderSlot } from "vue";
2
- import { _ as _sfc_main$2 } from "../chunks/LpCheckbox-BO0zuuPh.js";
3
- import { _ as _sfc_main$3 } from "../chunks/LpContextMenu-D0mzXqA7.js";
1
+ import { defineComponent, ref, computed, watch, onBeforeUnmount, openBlock, createBlock, withCtx, createVNode, createElementVNode, normalizeClass, createElementBlock, createCommentVNode, Fragment, renderList, normalizeStyle, createTextVNode, toDisplayString, TransitionGroup, unref, mergeProps, withModifiers, renderSlot } from "vue";
2
+ import { _ as _sfc_main$3 } from "../chunks/LpCheckbox-BO0zuuPh.js";
3
+ import { _ as _sfc_main$2 } from "../chunks/LpContextMenu-B6WBLleS.js";
4
4
  import { _ as _sfc_main$4 } from "../chunks/LpIcon-CCnX5_2j.js";
5
5
  import { _ as _sfc_main$1 } from "../chunks/LpScrollArea-D8xyKXcF.js";
6
6
  import { useRovingFocus } from "../composables/useRovingFocus.js";
7
7
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
8
- import '../assets/LpTable-B9Iko3XD.css';const _hoisted_1 = { class: "w-full border-collapse text-sm" };
8
+ import '../assets/LpTable-Bz3uJNa3.css';const _hoisted_1 = { class: "w-full border-collapse text-sm" };
9
9
  const _hoisted_2 = { class: "border-b border-line bg-surface-soft" };
10
10
  const _hoisted_3 = {
11
11
  key: 0,
@@ -31,7 +31,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
31
31
  rowMenu: {},
32
32
  sortAscLabel: { default: "Sort ascending" },
33
33
  sortDescLabel: { default: "Sort descending" },
34
- sortClearLabel: { default: "Clear sorting" }
34
+ sortClearLabel: { default: "Clear sorting" },
35
+ filterLabel: { default: "Show only" },
36
+ showAllLabel: { default: "Show all" },
37
+ columnsLabel: { default: "Columns" }
35
38
  },
36
39
  emits: ["update:selected", "update:sort", "rowClick"],
37
40
  setup(__props, { emit: __emit }) {
@@ -52,13 +55,51 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
52
55
  }
53
56
  const ownSort = ref(null);
54
57
  const activeSort = computed(() => props.sort ?? ownSort.value);
58
+ const hiddenValues = ref({});
59
+ const hiddenColumns = ref(/* @__PURE__ */ new Set());
60
+ const visibleColumns = computed(
61
+ () => props.columns.filter((c) => !hiddenColumns.value.has(c.key))
62
+ );
63
+ function valuesOf(col) {
64
+ const seen = [];
65
+ for (const row of props.rows) {
66
+ const v = row[col.key];
67
+ if (!seen.includes(v)) seen.push(v);
68
+ }
69
+ return seen;
70
+ }
71
+ function isValueHidden(key, value) {
72
+ return hiddenValues.value[key]?.has(value) ?? false;
73
+ }
74
+ function toggleValue(key, value) {
75
+ const next = new Set(hiddenValues.value[key] ?? []);
76
+ if (next.has(value)) next.delete(value);
77
+ else next.add(value);
78
+ hiddenValues.value = { ...hiddenValues.value, [key]: next };
79
+ }
80
+ function showAllValues(key) {
81
+ hiddenValues.value = { ...hiddenValues.value, [key]: /* @__PURE__ */ new Set() };
82
+ }
83
+ function toggleColumn(key) {
84
+ const next = new Set(hiddenColumns.value);
85
+ if (next.has(key)) next.delete(key);
86
+ else next.add(key);
87
+ hiddenColumns.value = next;
88
+ }
89
+ const filteredRows = computed(() => {
90
+ const active = Object.entries(hiddenValues.value).filter(([, set]) => set.size);
91
+ if (!active.length) return props.rows;
92
+ return props.rows.filter(
93
+ (row) => active.every(([key, hidden]) => !hidden.has(row[key]))
94
+ );
95
+ });
55
96
  const displayRows = computed(() => {
56
97
  const s = activeSort.value;
57
- if (!s) return props.rows;
98
+ if (!s) return filteredRows.value;
58
99
  const col = props.columns.find((c) => c.key === s.key);
59
- if (!col?.sortable) return props.rows;
100
+ if (!col?.sortable) return filteredRows.value;
60
101
  const factor = s.dir === "asc" ? 1 : -1;
61
- return [...props.rows].sort((a, b) => compare(a[s.key], b[s.key]) * factor);
102
+ return [...filteredRows.value].sort((a, b) => compare(a[s.key], b[s.key]) * factor);
62
103
  });
63
104
  function compare(a, b) {
64
105
  if (a == null && b == null) return 0;
@@ -83,31 +124,73 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
83
124
  return s.dir === "asc" ? "lucide:arrow-up" : "lucide:arrow-down";
84
125
  }
85
126
  function headerMenu(col) {
86
- if (!col.sortable) return [];
87
127
  const s = activeSort.value;
88
128
  const set = (next) => () => {
89
129
  ownSort.value = next;
90
130
  emit("update:sort", next);
91
131
  };
132
+ const items = [];
133
+ if (col.sortable) {
134
+ items.push(
135
+ {
136
+ label: props.sortAscLabel,
137
+ icon: "lucide:arrow-up",
138
+ disabled: s?.key === col.key && s.dir === "asc",
139
+ onSelect: set({ key: col.key, dir: "asc" })
140
+ },
141
+ {
142
+ label: props.sortDescLabel,
143
+ icon: "lucide:arrow-down",
144
+ disabled: s?.key === col.key && s.dir === "desc",
145
+ onSelect: set({ key: col.key, dir: "desc" })
146
+ },
147
+ {
148
+ label: props.sortClearLabel,
149
+ icon: "lucide:x",
150
+ disabled: !s,
151
+ onSelect: set(null)
152
+ }
153
+ );
154
+ }
155
+ if (col.filterable) {
156
+ const hidden = hiddenValues.value[col.key];
157
+ items.push({
158
+ label: props.filterLabel,
159
+ icon: "lucide:filter",
160
+ separatorBefore: items.length > 0,
161
+ children: [
162
+ ...valuesOf(col).map((value) => ({
163
+ label: col.valueLabel?.(value) ?? String(value ?? "—"),
164
+ checked: !isValueHidden(col.key, value),
165
+ onSelect: () => toggleValue(col.key, value)
166
+ })),
167
+ {
168
+ label: props.showAllLabel,
169
+ icon: "lucide:list",
170
+ separatorBefore: true,
171
+ disabled: !hidden?.size,
172
+ onSelect: () => showAllValues(col.key)
173
+ }
174
+ ]
175
+ });
176
+ }
177
+ return items;
178
+ }
179
+ function tableMenu() {
180
+ const offered = props.columns.filter((c) => !c.alwaysVisible && c.label);
181
+ if (!offered.length) return [];
92
182
  return [
93
183
  {
94
- label: props.sortAscLabel,
95
- icon: "lucide:arrow-up",
96
- disabled: s?.key === col.key && s.dir === "asc",
97
- onSelect: set({ key: col.key, dir: "asc" })
98
- },
99
- {
100
- label: props.sortDescLabel,
101
- icon: "lucide:arrow-down",
102
- disabled: s?.key === col.key && s.dir === "desc",
103
- onSelect: set({ key: col.key, dir: "desc" })
104
- },
105
- {
106
- label: props.sortClearLabel,
107
- icon: "lucide:x",
108
- separatorBefore: true,
109
- disabled: !s,
110
- onSelect: set(null)
184
+ label: props.columnsLabel,
185
+ icon: "lucide:columns-3",
186
+ children: offered.map((col) => ({
187
+ label: col.label,
188
+ checked: !hiddenColumns.value.has(col.key),
189
+ // The last visible column cannot be hidden: a table with no columns is
190
+ // a blank card the operator has no way back from.
191
+ disabled: !hiddenColumns.value.has(col.key) && visibleColumns.value.length <= 1,
192
+ onSelect: () => toggleColumn(col.key)
193
+ }))
111
194
  }
112
195
  ];
113
196
  }
@@ -190,130 +273,137 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
190
273
  "bar-inset-top": barInsetTop.value
191
274
  }, {
192
275
  default: withCtx(() => [
193
- createElementVNode("table", _hoisted_1, [
194
- createElementVNode("thead", {
195
- ref_key: "headEl",
196
- ref: headEl,
197
- class: normalizeClass(__props.stickyHeader ? "sticky top-0 z-10" : "")
198
- }, [
199
- createElementVNode("tr", _hoisted_2, [
200
- __props.selectable ? (openBlock(), createElementBlock("th", _hoisted_3, [
201
- createVNode(_sfc_main$2, {
202
- "model-value": allChecked.value,
203
- indeterminate: someChecked.value,
204
- "onUpdate:modelValue": toggleAll
205
- }, null, 8, ["model-value", "indeterminate"])
206
- ])) : createCommentVNode("", true),
207
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.columns, (col) => {
208
- return openBlock(), createElementBlock("th", {
209
- key: col.key,
210
- style: normalizeStyle(col.width ? { width: col.width } : void 0),
211
- class: normalizeClass(["px-4 py-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted", alignClass(col.align)]),
212
- "aria-sort": activeSort.value?.key === col.key ? activeSort.value.dir === "asc" ? "ascending" : "descending" : void 0
213
- }, [
214
- col.sortable ? (openBlock(), createBlock(_sfc_main$3, {
215
- key: 0,
216
- items: headerMenu(col)
217
- }, {
218
- default: withCtx(() => [
219
- createElementVNode("button", {
220
- type: "button",
221
- class: normalizeClass(["group/sort -mx-1 inline-flex items-center gap-1 rounded px-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring", [
222
- col.align === "right" ? "flex-row-reverse" : "",
223
- activeSort.value?.key === col.key ? "text-ink" : ""
224
- ]]),
225
- onClick: ($event) => toggleSort(col)
226
- }, [
227
- createTextVNode(toDisplayString(col.label) + " ", 1),
228
- createVNode(_sfc_main$4, {
229
- name: sortIcon(col),
230
- size: 13,
231
- class: normalizeClass([
232
- "shrink-0 transition-[transform,opacity] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)]",
233
- activeSort.value?.key === col.key ? "opacity-100" : "opacity-40 group-hover/sort:opacity-70"
234
- ])
235
- }, null, 8, ["name", "class"])
236
- ], 10, _hoisted_5)
237
- ]),
238
- _: 2
239
- }, 1032, ["items"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
240
- createTextVNode(toDisplayString(col.label), 1)
241
- ], 64))
242
- ], 14, _hoisted_4);
243
- }), 128))
244
- ])
245
- ], 2),
246
- createVNode(TransitionGroup, {
247
- tag: "tbody",
248
- name: "lp-row",
249
- ref: (el) => unref(roving).setContainer(el?.$el ?? null)
250
- }, {
251
- default: withCtx(() => [
252
- displayRows.value.length === 0 ? (openBlock(), createElementBlock("tr", _hoisted_6, [
253
- createElementVNode("td", {
254
- colspan: colSpan.value,
255
- class: "px-4 py-10 text-center text-muted"
256
- }, [
257
- createVNode(_sfc_main$4, {
258
- name: __props.emptyIcon,
259
- size: 22,
260
- class: "mx-auto mb-2 opacity-60"
261
- }, null, 8, ["name"]),
262
- createElementVNode("div", null, toDisplayString(__props.emptyLabel), 1)
263
- ], 8, _hoisted_7)
264
- ])) : createCommentVNode("", true),
265
- (openBlock(true), createElementBlock(Fragment, null, renderList(displayRows.value, (row, index) => {
266
- return openBlock(), createBlock(_sfc_main$3, {
267
- key: keyFor(row, index),
268
- items: menuFor(row)
269
- }, {
270
- default: withCtx(() => [
271
- createElementVNode("tr", mergeProps({
272
- class: ["group border-b border-line/60 outline-none transition-colors last:border-0 hover:bg-surface-soft/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-[active]:bg-surface-soft/40", selectedSet.value.has(keyFor(row, index)) ? "bg-brand-soft/40" : ""]
273
- }, { ref_for: true }, unref(roving).itemProps(String(keyFor(row, index))), {
274
- onClick: ($event) => emit("rowClick", row),
275
- onKeydown: onRowKeydown
276
- }), [
277
- __props.selectable ? (openBlock(), createElementBlock("td", {
276
+ createVNode(_sfc_main$2, {
277
+ items: tableMenu()
278
+ }, {
279
+ default: withCtx(() => [
280
+ createElementVNode("table", _hoisted_1, [
281
+ createElementVNode("thead", {
282
+ ref_key: "headEl",
283
+ ref: headEl,
284
+ class: normalizeClass(__props.stickyHeader ? "sticky top-0 z-10" : "")
285
+ }, [
286
+ createElementVNode("tr", _hoisted_2, [
287
+ __props.selectable ? (openBlock(), createElementBlock("th", _hoisted_3, [
288
+ createVNode(_sfc_main$3, {
289
+ "model-value": allChecked.value,
290
+ indeterminate: someChecked.value,
291
+ "onUpdate:modelValue": toggleAll
292
+ }, null, 8, ["model-value", "indeterminate"])
293
+ ])) : createCommentVNode("", true),
294
+ (openBlock(true), createElementBlock(Fragment, null, renderList(visibleColumns.value, (col) => {
295
+ return openBlock(), createElementBlock("th", {
296
+ key: col.key,
297
+ style: normalizeStyle(col.width ? { width: col.width } : void 0),
298
+ class: normalizeClass(["px-4 py-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted", alignClass(col.align)]),
299
+ "aria-sort": activeSort.value?.key === col.key ? activeSort.value.dir === "asc" ? "ascending" : "descending" : void 0
300
+ }, [
301
+ col.sortable ? (openBlock(), createBlock(_sfc_main$2, {
278
302
  key: 0,
279
- class: "w-px px-4 py-3",
280
- onClick: _cache[0] || (_cache[0] = withModifiers(() => {
281
- }, ["stop"]))
282
- }, [
283
- createVNode(_sfc_main$2, {
284
- "model-value": selectedSet.value.has(keyFor(row, index)),
285
- "onUpdate:modelValue": (v) => toggleRow(keyFor(row, index), v)
286
- }, null, 8, ["model-value", "onUpdate:modelValue"])
287
- ])) : createCommentVNode("", true),
288
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.columns, (col) => {
289
- return openBlock(), createElementBlock("td", {
290
- key: col.key,
291
- class: normalizeClass(["px-4 py-3 text-ink", alignClass(col.align)])
292
- }, [
293
- renderSlot(_ctx.$slots, `cell-${col.key}`, {
294
- row,
295
- value: row[col.key]
296
- }, () => [
297
- createTextVNode(toDisplayString(row[col.key]), 1)
298
- ], true)
299
- ], 2);
300
- }), 128))
301
- ], 16, _hoisted_8)
302
- ]),
303
- _: 2
304
- }, 1032, ["items"]);
305
- }), 128))
306
- ]),
307
- _: 3
308
- }, 512)
309
- ])
303
+ items: headerMenu(col)
304
+ }, {
305
+ default: withCtx(() => [
306
+ createElementVNode("button", {
307
+ type: "button",
308
+ class: normalizeClass(["group/sort -mx-1 inline-flex items-center gap-1 rounded px-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring", [
309
+ col.align === "right" ? "flex-row-reverse" : "",
310
+ activeSort.value?.key === col.key ? "text-ink" : ""
311
+ ]]),
312
+ onClick: ($event) => toggleSort(col)
313
+ }, [
314
+ createTextVNode(toDisplayString(col.label) + " ", 1),
315
+ createVNode(_sfc_main$4, {
316
+ name: sortIcon(col),
317
+ size: 13,
318
+ class: normalizeClass([
319
+ "shrink-0 transition-[transform,opacity] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)]",
320
+ activeSort.value?.key === col.key ? "opacity-100" : "opacity-40 group-hover/sort:opacity-70"
321
+ ])
322
+ }, null, 8, ["name", "class"])
323
+ ], 10, _hoisted_5)
324
+ ]),
325
+ _: 2
326
+ }, 1032, ["items"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
327
+ createTextVNode(toDisplayString(col.label), 1)
328
+ ], 64))
329
+ ], 14, _hoisted_4);
330
+ }), 128))
331
+ ])
332
+ ], 2),
333
+ createVNode(TransitionGroup, {
334
+ tag: "tbody",
335
+ name: "lp-row",
336
+ ref: (el) => unref(roving).setContainer(el?.$el ?? null)
337
+ }, {
338
+ default: withCtx(() => [
339
+ displayRows.value.length === 0 ? (openBlock(), createElementBlock("tr", _hoisted_6, [
340
+ createElementVNode("td", {
341
+ colspan: colSpan.value,
342
+ class: "px-4 py-10 text-center text-muted"
343
+ }, [
344
+ createVNode(_sfc_main$4, {
345
+ name: __props.emptyIcon,
346
+ size: 22,
347
+ class: "mx-auto mb-2 opacity-60"
348
+ }, null, 8, ["name"]),
349
+ createElementVNode("div", null, toDisplayString(__props.emptyLabel), 1)
350
+ ], 8, _hoisted_7)
351
+ ])) : createCommentVNode("", true),
352
+ (openBlock(true), createElementBlock(Fragment, null, renderList(displayRows.value, (row, index) => {
353
+ return openBlock(), createBlock(_sfc_main$2, {
354
+ key: keyFor(row, index),
355
+ items: menuFor(row)
356
+ }, {
357
+ default: withCtx(() => [
358
+ createElementVNode("tr", mergeProps({
359
+ class: ["group border-b border-line/60 outline-none transition-colors last:border-0 hover:bg-surface-soft/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-[active]:bg-surface-soft/40", selectedSet.value.has(keyFor(row, index)) ? "bg-brand-soft/40" : ""]
360
+ }, { ref_for: true }, unref(roving).itemProps(String(keyFor(row, index))), {
361
+ onClick: ($event) => emit("rowClick", row),
362
+ onKeydown: onRowKeydown
363
+ }), [
364
+ __props.selectable ? (openBlock(), createElementBlock("td", {
365
+ key: 0,
366
+ class: "w-px px-4 py-3",
367
+ onClick: _cache[0] || (_cache[0] = withModifiers(() => {
368
+ }, ["stop"]))
369
+ }, [
370
+ createVNode(_sfc_main$3, {
371
+ "model-value": selectedSet.value.has(keyFor(row, index)),
372
+ "onUpdate:modelValue": (v) => toggleRow(keyFor(row, index), v)
373
+ }, null, 8, ["model-value", "onUpdate:modelValue"])
374
+ ])) : createCommentVNode("", true),
375
+ (openBlock(true), createElementBlock(Fragment, null, renderList(visibleColumns.value, (col) => {
376
+ return openBlock(), createElementBlock("td", {
377
+ key: col.key,
378
+ class: normalizeClass(["px-4 py-3 text-ink", alignClass(col.align)])
379
+ }, [
380
+ renderSlot(_ctx.$slots, `cell-${col.key}`, {
381
+ row,
382
+ value: row[col.key]
383
+ }, () => [
384
+ createTextVNode(toDisplayString(row[col.key]), 1)
385
+ ], true)
386
+ ], 2);
387
+ }), 128))
388
+ ], 16, _hoisted_8)
389
+ ]),
390
+ _: 2
391
+ }, 1032, ["items"]);
392
+ }), 128))
393
+ ]),
394
+ _: 3
395
+ }, 512)
396
+ ])
397
+ ]),
398
+ _: 3
399
+ }, 8, ["items"])
310
400
  ]),
311
401
  _: 3
312
402
  }, 8, ["bar-inset-top"]);
313
403
  };
314
404
  }
315
405
  });
316
- const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-4fedb617"]]);
406
+ const LpTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f9c874a7"]]);
317
407
  export {
318
408
  LpTable as default
319
409
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-Dj4VcTu7.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-r1te9nB2.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
package/dist/index.js CHANGED
@@ -3,21 +3,21 @@ import { _ as _2 } from "./chunks/LpAddressInput-Bk6D8Bcy.js";
3
3
  import { _ as _3 } from "./chunks/LpMapPicker-CXvoTrAe.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
5
  import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
6
- import { _ as _5 } from "./chunks/LpAppShell-CGX0DlIW.js";
6
+ import { _ as _5 } from "./chunks/LpAppShell-BJk3b-u4.js";
7
7
  import { _ as _6 } from "./chunks/LpAutocomplete-DExlIZe_.js";
8
- import { _ as _7 } from "./chunks/LpAvatar-CzCE5cXg.js";
8
+ import { _ as _7 } from "./chunks/LpAvatar-D3l4nabg.js";
9
9
  import { _ as _8 } from "./chunks/LpBadge-BPodVcJa.js";
10
10
  import { _ as _9 } from "./chunks/LpBreadcrumbs-5uvViCXi.js";
11
11
  import { _ as _10 } from "./chunks/LpButton-5VXXCjwF.js";
12
12
  import { _ as _11 } from "./chunks/LpCalendar-B7ZL0ty3.js";
13
- import { _ as _12 } from "./chunks/LpCard-DSefTGEH.js";
14
- import { _ as _13 } from "./chunks/LpChart-UiU4RhXP.js";
13
+ import { _ as _12 } from "./chunks/LpCard-D0M2ZCnB.js";
14
+ import { _ as _13 } from "./chunks/LpChart-Bmw7y34Z.js";
15
15
  import { CHART_PALETTE, axisTicks, formatTick, niceScale, resolveSlices } from "./components/chart.js";
16
16
  import { _ as _14 } from "./chunks/LpCheckbox-BO0zuuPh.js";
17
17
  import { _ as _15 } from "./chunks/LpCodeBlock-rS_6mO3D.js";
18
18
  import { default as default2 } from "./components/LpCommandPalette.vue.js";
19
19
  import { _ as _16 } from "./chunks/LpConfirmDialog-CjOGcj1e.js";
20
- import { _ as _17 } from "./chunks/LpContextMenu-D0mzXqA7.js";
20
+ import { _ as _17 } from "./chunks/LpContextMenu-B6WBLleS.js";
21
21
  import { _ as _18 } from "./chunks/LpDatePicker-dY2q4dr4.js";
22
22
  import { _ as _19 } from "./chunks/LpDisclosure-BnwP4Q-b.js";
23
23
  import { _ as _20 } from "./chunks/LpDivider-CUX46KR0.js";
@@ -25,7 +25,7 @@ import { _ as _21 } from "./chunks/LpDrawer-DAJN5rGK.js";
25
25
  import { _ as _22 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
26
26
  import { default as default3 } from "./components/LpDropzone.vue.js";
27
27
  import { _ as _23 } from "./chunks/LpEmptyState-BV3q7Qyi.js";
28
- import { _ as _24 } from "./chunks/LpFileTree-BaV7f0O7.js";
28
+ import { _ as _24 } from "./chunks/LpFileTree-DfFqY__1.js";
29
29
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
30
30
  import { _ as _25 } from "./chunks/LpFormField-fV_2ZdCk.js";
31
31
  import { _ as _26 } from "./chunks/LpIcon-CCnX5_2j.js";
@@ -35,15 +35,15 @@ import { _ as _28 } from "./chunks/LpInput-CYcTL9gj.js";
35
35
  import { _ as _29 } from "./chunks/LpLightbox-Ci35Y_xP.js";
36
36
  import { fileNameOf } from "./components/lightbox.js";
37
37
  import { _ as _30 } from "./chunks/LpLink-BHZ1CLlF.js";
38
- import { _ as _31 } from "./chunks/LpLogViewer-CKmEQYnm.js";
38
+ import { _ as _31 } from "./chunks/LpLogViewer-CVaGfTF_.js";
39
39
  import { _ as _32 } from "./chunks/LpModal-D_o-56To.js";
40
- import { _ as _33 } from "./chunks/LpNotificationBell-Dvu-b6Vn.js";
40
+ import { _ as _33 } from "./chunks/LpNotificationBell-CTsIcPqX.js";
41
41
  import { _ as _34 } from "./chunks/LpNumberField-NPE8Bumx.js";
42
42
  import { _ as _35 } from "./chunks/LpNumberFlow-amWYaeuA.js";
43
43
  import { _ as _36 } from "./chunks/LpOtpInput-DqaT0Z75.js";
44
44
  import { _ as _37 } from "./chunks/LpPagination-CeGuNe_q.js";
45
45
  import { _ as _38 } from "./chunks/LpPasswordInput-DrYE4J5d.js";
46
- import { _ as _39 } from "./chunks/LpPie-BkjWlfSw.js";
46
+ import { _ as _39 } from "./chunks/LpPie-BL1vTDSM.js";
47
47
  import { _ as _40 } from "./chunks/LpPhoneInput-NXXjciJJ.js";
48
48
  import { flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
49
49
  import { _ as _41 } from "./chunks/LpPopover-8268UmlC.js";
@@ -58,7 +58,7 @@ import { _ as _49 } from "./chunks/LpSelect-ANIVtrpM.js";
58
58
  import { _ as _50 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
59
59
  import { _ as _51 } from "./chunks/LpShift-DXNsauQZ.js";
60
60
  import { default as default5 } from "./components/LpShortcutsDialog.vue.js";
61
- import { _ as _52 } from "./chunks/LpSidebar-CWehDCxN.js";
61
+ import { _ as _52 } from "./chunks/LpSidebar-C_Mr-JPI.js";
62
62
  import { _ as _53 } from "./chunks/LpSkeleton-CTI-vBgW.js";
63
63
  import { _ as _54 } from "./chunks/LpSlider-B1SF3pRV.js";
64
64
  import { _ as _55 } from "./chunks/LpSparkline-C6xQ2dwv.js";
@@ -69,8 +69,8 @@ import { default as default6 } from "./components/LpTable.vue.js";
69
69
  import { _ as _59 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
70
70
  import { _ as _60 } from "./chunks/LpTabs-Cefe_lys.js";
71
71
  import { _ as _61 } from "./chunks/LpTextarea-BMIOfw5l.js";
72
- import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-Dj4VcTu7.js";
73
- import { D, _ as _62, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-Dj4VcTu7.js";
72
+ import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-r1te9nB2.js";
73
+ import { D, _ as _62, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v } from "./chunks/LpThemeSwitcher-r1te9nB2.js";
74
74
  import { _ as _63 } from "./chunks/LpTilt-DuyKbpkA.js";
75
75
  import { _ as _64 } from "./chunks/LpToaster-gDB1EaU3.js";
76
76
  import { _ as _65 } from "./chunks/LpTooltip-hCLPAqMG.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.29.3",
3
+ "version": "0.29.4",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -32,6 +32,13 @@ export interface ContextMenuItemDef {
32
32
  onSelect?: () => void
33
33
  /** Nested submenu. When present, onSelect is ignored. */
34
34
  children?: ContextMenuItemDef[]
35
+ /**
36
+ * Renders as a tick-box rather than a plain item, and the menu stays open on
37
+ * select. For the lists where the point is to set several at once — which
38
+ * columns to show, which values to filter by — and closing after each one
39
+ * would mean reopening the menu for every tick.
40
+ */
41
+ checked?: boolean
35
42
  }
36
43
 
37
44
  const props = defineProps<{
@@ -85,7 +92,28 @@ const ICON =
85
92
  <ContextMenuSubContent :class="PANEL" :side-offset="2" :align-offset="-4">
86
93
  <template v-for="(sub, j) in item.children" :key="j">
87
94
  <ContextMenuSeparator v-if="sub.separatorBefore" class="my-1 h-px bg-line" />
95
+ <!-- The tick-box case, as in the top-level menu: this is
96
+ where most of them live, since a submenu is what a list
97
+ of choices opens into. -->
98
+ <ContextMenuItem
99
+ v-if="sub.checked !== undefined"
100
+ :class="[ITEM, 'justify-between text-ink data-[highlighted]:text-brand']"
101
+ :disabled="sub.disabled"
102
+ @select.prevent="sub.onSelect?.()"
103
+ >
104
+ <span class="flex items-center gap-2">
105
+ <LpIcon
106
+ :name="sub.checked ? 'lucide:check' : 'lucide:minus'"
107
+ :size="15"
108
+ :class="[ICON, sub.checked ? '' : 'opacity-25']"
109
+ />
110
+ {{ sub.label }}
111
+ </span>
112
+ <kbd v-if="sub.shortcut" class="text-xs text-muted">{{ sub.shortcut }}</kbd>
113
+ </ContextMenuItem>
114
+
88
115
  <ContextMenuItem
116
+ v-else
89
117
  :class="[ITEM, 'justify-between', sub.danger ? 'text-danger data-[highlighted]:bg-danger-soft data-[highlighted]:text-danger' : 'text-ink data-[highlighted]:text-brand']"
90
118
  :disabled="sub.disabled"
91
119
  @select="sub.onSelect?.()"
@@ -101,6 +129,27 @@ const ICON =
101
129
  </ContextMenuPortal>
102
130
  </ContextMenuSub>
103
131
 
132
+ <!-- Tick-box item. `@select.prevent` keeps the menu open: these come
133
+ in lists where several are set in one visit, and closing after
134
+ each tick would mean reopening for the next. The tick occupies a
135
+ fixed slot so unticked rows stay aligned with ticked ones. -->
136
+ <ContextMenuItem
137
+ v-else-if="item.checked !== undefined"
138
+ :class="[ITEM, 'justify-between text-ink data-[highlighted]:text-brand']"
139
+ :disabled="item.disabled"
140
+ @select.prevent="item.onSelect?.()"
141
+ >
142
+ <span class="flex items-center gap-2">
143
+ <LpIcon
144
+ :name="item.checked ? 'lucide:check' : 'lucide:minus'"
145
+ :size="15"
146
+ :class="[ICON, item.checked ? '' : 'opacity-25']"
147
+ />
148
+ {{ item.label }}
149
+ </span>
150
+ <kbd v-if="item.shortcut" class="text-xs text-muted">{{ item.shortcut }}</kbd>
151
+ </ContextMenuItem>
152
+
104
153
  <!-- Leaf item -->
105
154
  <ContextMenuItem
106
155
  v-else