@columenlabs/lumen-ui 0.1.43 → 0.1.44

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type CustomViewConfig, LumenTable, type LumenTableProps, LumenView, type LumenViewProps, type ServerPaging, type ViewCellValue, type ViewChipTone, type ViewDomain, type ViewField, type ViewFieldGroup, type ViewSource, CHIP_TONE, RUN_STATE_TONE, FORMULAS_GROUP, ViewValueCell, rollUp, } from './lumen-view.js';
1
+ export { type CustomViewConfig, LumenTable, type LumenTableProps, LumenView, type LumenViewProps, type ServerGroup, type ServerGroups, type ServerPaging, type ViewCellValue, type ViewChipTone, type ViewDomain, type ViewField, type ViewFieldGroup, type ViewSource, CHIP_TONE, RUN_STATE_TONE, FORMULAS_GROUP, ViewValueCell, rollUp, } from './lumen-view.js';
2
2
  export { LumenLayout, type LumenLayoutCard, type LumenLayoutProps, type LumenLayoutViewProps, domainKeys, followRows, } from './lumen-layout.js';
3
3
  export { LumenViewCard, type LumenViewCardProps } from './lumen-view-card.js';
4
4
  export { LumenSelector, LumenSelectorList, type LumenSelectorListHandle, type LumenSelectorProps, type LumenOption, type LumenOptionGroup, SelectorRowContent, } from './lumen-selector.js';
@@ -496,8 +496,9 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
496
496
  const viewCard = mainTableOf(cards);
497
497
  const viewOn = (k) => !viewCard || controlOn(viewCard, k);
498
498
  const showBox = !hideFilter && viewOn('filter');
499
- // A server's page cannot be grouped: a group would bucket the one page only.
500
- const groupButton = viewOn('group') && !paging ? group.button : null;
499
+ // A server's page cannot be grouped (a group would bucket the one page only), unless the
500
+ // server groups it.
501
+ const groupButton = viewOn('group') && (!paging || paging.groups) ? group.button : null;
501
502
  const viewFilter = !showBox && !groupButton ? undefined : (_jsxs(_Fragment, { children: [showBox && (_jsx(LumenTokenizedFilter, { filter: filter, label: `Filter ${config.name}`, className: "min-w-0 flex-1", saved: config.filters.default, link: {
502
503
  color: viewColor,
503
504
  onPick: (color) => links.setColor(viewCardId, color),
@@ -57,11 +57,37 @@ export interface ServerPaging {
57
57
  onPage: (page: number) => void;
58
58
  onSize?: (size: number) => void;
59
59
  sizes?: readonly number[];
60
- /** The columns the server sorts by; the rest take no header sort, and the view groups nothing,
61
- * since either would order or bucket one page only. */
60
+ /** The columns the server sorts by; the rest take no header sort, and the view groups nothing
61
+ * (unless `groups`), since either would order or bucket one page only. */
62
62
  sorts?: readonly string[];
63
63
  /** Told the sort held (a header click, a revert), to fetch the rows in that order. */
64
64
  onSort?: (sort: CustomViewConfig['sort']) => void;
65
+ /** The server groups the rows: the Group button offers the domain's groupable fields, and a
66
+ * group's rows load when it opens. Without it a server's page groups nothing. */
67
+ groups?: ServerGroups;
68
+ }
69
+ /** One group a server counted: its key (the group field's value as the view shows it), its row
70
+ * count (null: not counted), and, once open, the rows loaded so far. */
71
+ export interface ServerGroup {
72
+ key: string;
73
+ n: number | null;
74
+ open: boolean;
75
+ /** The rows loaded for an open group, in the server's order. */
76
+ rows: unknown[];
77
+ /** The group has rows past those loaded: its Show more row loads the next. */
78
+ more?: () => void;
79
+ /** Its rows are loading. */
80
+ loading?: boolean;
81
+ }
82
+ export interface ServerGroups {
83
+ /** Told the field the person groups by (null: none), to fetch that field's groups. */
84
+ onGroup: (key: string | null) => void;
85
+ /** The picked field's groups, in order; null while they load. */
86
+ list: ServerGroup[] | null;
87
+ /** Groups past those listed: a More groups row asks for them. */
88
+ more?: () => void;
89
+ /** A group header's click: open or close it. */
90
+ onToggle: (key: string) => void;
65
91
  }
66
92
  export interface LumenViewProps<T> {
67
93
  /** The view's domain, its formula columns included (`withViewFields`). */
@@ -195,4 +221,7 @@ export interface LumenTableProps<T> {
195
221
  total?: boolean;
196
222
  /** What each row opens under it (`subgroupOf`); null: rows open nothing but their panel. */
197
223
  subgroup?: ViewSubgroup<T> | null;
224
+ /** The groups come from a server (`ServerPaging.groups`): their order, counts and open state,
225
+ * and each open group's rows. */
226
+ serverGroups?: ServerGroups | null;
198
227
  }
@@ -234,8 +234,12 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
234
234
  // scrolls with the page.
235
235
  const inLayout = inScroller && !cell;
236
236
  const columns = pick(domain.fields, config.columns);
237
- // A server's page groups and pivots nothing: either would cover the one page only.
238
- const groupBy = server ? null : pickedGroup;
237
+ // A server's page groups and pivots nothing (either would cover the one page only), unless the
238
+ // server groups it.
239
+ const groupBy = server && !server.groups ? null : pickedGroup;
240
+ const served = server?.groups && groupBy ? server.groups : null;
241
+ const onGroup = server?.groups?.onGroup;
242
+ useEffect(() => onGroup?.(groupBy?.key ?? null), [onGroup, groupBy?.key]);
239
243
  const pivot = server ? false : pickedPivot;
240
244
  // Live columns on screen keep their source reading while the market trades (#861).
241
245
  const load = useLiveLoad(sourceLoad, columns.some((c) => {
@@ -243,7 +247,9 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
243
247
  return r === 'live' || r === 'calc';
244
248
  }));
245
249
  const chips = chipsOf(config);
246
- const tableRows = useMemo(() => mergeRows(domain, rows, groupBy), [domain, rows, groupBy]);
250
+ // Server groups: the open groups' rows, each object once (a row in two groups shows in both).
251
+ const groupRows = useMemo(() => served ? [...new Set((served.list ?? []).flatMap((g) => (g.open ? g.rows : [])))] : null, [served]);
252
+ const tableRows = useMemo(() => groupRows ?? mergeRows(domain, rows, groupBy), [groupRows, domain, rows, groupBy]);
247
253
  const amountsHidden = useAmountsHidden();
248
254
  // Flat tables page their rows, 25 a page unless the view says otherwise; the size picker and
249
255
  // the page buttons sit in the card's header. Views saved before then
@@ -281,9 +287,10 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
281
287
  return [...out].slice(0, MAX_COMPARE);
282
288
  }, [selection, chartField, rows, domain]);
283
289
  const compare = openChart && picked.length > 1 ? (_jsx(IconButton, { icon: ChartSpline, label: `Compare ${picked.join(', ')}`, variant: "outline", onClick: () => openChart(picked) })) : null;
284
- // A server's page: the pager counts from the server's total, or the rows seen so far.
285
- const pager = server ? (_jsx(LumenPager, { total: server.total ?? server.page * server.size + rows.length, page: server.page, size: server.size, onPage: server.onPage, onSize: server.onSize ?? (() => { }), sizes: server.sizes ?? [server.size], more: server.total === null && server.more })) : paging ? (_jsx(LumenPager, { total: tableRows.length, page: page, size: pageSize, onPage: setPageIndex, onSize: setPageSize, noun: rows.length === total ? 'rows' : `of ${total} rows` })) : (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: count }));
286
- const table = pivot && config.pivot ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: tableRows, groupBy: groupBy, subgroup: sub, chips: chips, page: paging ? { index: page, size: pageSize } : null, inView: inLayout, fit: fit, onDetails: onDetails, tapped: tapped, selection: selection, groupActions: groupActions, sort: sort, onSort: onSort, serverSorts: server ? (server.sorts ?? []) : undefined, total: config.total !== false, add: onAddColumn || onColumns
290
+ // A server's page: the pager counts from the server's total, or the rows seen so far. Server
291
+ // groups page themselves: the header counts them.
292
+ const pager = served ? (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: served.list ? `${served.list.length.toLocaleString()}${served.more ? '+' : ''} groups` : '' })) : server ? (_jsx(LumenPager, { total: server.total ?? server.page * server.size + rows.length, page: server.page, size: server.size, onPage: server.onPage, onSize: server.onSize ?? (() => { }), sizes: server.sizes ?? [server.size], more: server.total === null && server.more })) : paging ? (_jsx(LumenPager, { total: tableRows.length, page: page, size: pageSize, onPage: setPageIndex, onSize: setPageSize, noun: rows.length === total ? 'rows' : `of ${total} rows` })) : (_jsx("span", { className: "num px-2 text-xs text-muted-foreground", children: count }));
293
+ const table = pivot && config.pivot ? (_jsx(PivotTable, { domain: domain, rows: rows, pivot: config.pivot })) : (_jsx(LumenTable, { domain: domain, columns: columns, rows: tableRows, groupBy: groupBy, subgroup: sub, chips: chips, page: paging ? { index: page, size: pageSize } : null, inView: inLayout, fit: fit, onDetails: onDetails, tapped: tapped, selection: selection, groupActions: groupActions, sort: sort, onSort: onSort, serverSorts: server ? (server.sorts ?? []) : undefined, serverGroups: served, total: config.total !== false, add: onAddColumn || onColumns
287
294
  ? { ...onAddColumn, field: onColumns && { domain, columns, onColumns } }
288
295
  : undefined }));
289
296
  return (_jsx(LumenWidget, { id: cardId, kind: "table", style: "table", title: title, controls: controls, tools: state ? (tools) : (_jsxs(_Fragment, { children: [tools, compare, pager, onColumns && !onAddColumn && on('columns') && (_jsx(ColumnPicker, { domain: domain, columns: columns, onColumns: onColumns })), !pivot && on('download') && (_jsx(IconButton, { icon: DownloadIcon, label: "Download CSV", onClick: () => downloadCsv({
@@ -359,7 +366,7 @@ function sortingOf(sort, columns, serverSorts) {
359
366
  return [];
360
367
  return columns.some((f) => f.key === sort.key) ? [{ id: sort.key, desc: sort.desc }] : [];
361
368
  }
362
- function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null, chips = [], page = null, inView = false, fit = false, onDetails, tapped, selection, groupActions, sort, onSort, serverSorts, total = true, subgroup = null, add, }) {
369
+ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null, chips = [], page = null, inView = false, fit = false, onDetails, tapped, selection, groupActions, sort, onSort, serverSorts, total = true, subgroup = null, add, serverGroups = null, }) {
363
370
  const [sorting, setSorting] = useState(() => sortingOf(sort, columns, serverSorts));
364
371
  // A saved sort patched while the table is open (the assistant's card move) applies at once.
365
372
  const savedSort = JSON.stringify(sort ?? null);
@@ -507,6 +514,10 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
507
514
  const groups = useMemo(() => {
508
515
  if (!groupBy)
509
516
  return null;
517
+ if (serverGroups) {
518
+ const byRow = new Map(sorted.map((r) => [r.original, r]));
519
+ return (serverGroups.list ?? []).map((g) => [g.key, g.rows.flatMap((o) => byRow.get(o) ?? [])]);
520
+ }
510
521
  const m = new Map();
511
522
  for (const r of sorted) {
512
523
  const k = label(groupBy.value(r.original));
@@ -514,10 +525,11 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
514
525
  b ? b.push(r) : m.set(k, [r]);
515
526
  }
516
527
  return [...m];
517
- }, [groupBy, sorted]);
528
+ }, [groupBy, sorted, serverGroups]);
518
529
  const span = shown.length + (selection ? 1 : 0);
530
+ const served = new Map((serverGroups?.list ?? []).map((g) => [g.key, g]));
519
531
  const startsOpen = (i) => rows.length <= OPEN_ALL || i === 0;
520
- const isOpen = (key, i) => startsOpen(i) !== flipped.has(key);
532
+ const isOpen = (key, i) => serverGroups ? !!served.get(key)?.open : startsOpen(i) !== flipped.has(key);
521
533
  const anyClosed = !!groups?.some(([k], i) => !isOpen(k, i));
522
534
  const setAll = (open) => setFlipped(new Set((groups ?? []).filter((_, i) => startsOpen(i) !== open).map(([k]) => k)));
523
535
  const keyOf = domain.rowKey;
@@ -534,7 +546,8 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
534
546
  const someOn = allKeys.some((k) => selection?.keys.has(k));
535
547
  const selectCell = (key, name) => selection && (_jsx(TableCell, { className: "w-8 pr-0", onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: selection.keys.has(key), onCheckedChange: (on) => toggle([key], on === true), "aria-label": `Select ${name}` }) }));
536
548
  const [openParts, setOpenParts] = useState(new Set());
537
- const hasTotal = total && shown.some(rollsUp);
549
+ // A server group's roll-ups would add up its loaded rows only, so they and the Total stay off.
550
+ const hasTotal = total && !serverGroups && shown.some(rollsUp);
538
551
  // A group row's label spans the leading columns that roll nothing up.
539
552
  const rolls = (f) => !f.noRollup && (!!f.aggregate || !!f.rollFormula || !!f.share);
540
553
  const lead = Math.max(1, shown.findIndex((f, i) => i > 0 && rolls(f)) < 0
@@ -608,6 +621,17 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
608
621
  _jsx(TableRow, { "data-more": true, children: _jsx(TableCell, { colSpan: span, className: "py-1", children: _jsxs(Button, { variant: "ghost", size: "xs", onClick: () => setMore((m) => new Map(m).set(scope, limit + DRAW_STEP)), children: [_jsx(ChevronsDownIcon, {}), "Show ", Math.min(left, DRAW_STEP).toLocaleString(), " more", _jsxs("span", { className: "num font-normal text-muted-foreground", children: ["of ", left.toLocaleString()] })] }) }) }, `${scope}:more`),
609
622
  ];
610
623
  };
624
+ // A server group's rows as loaded, then its Show more (the server's next rows) or loading row.
625
+ const servedRows = (list, key) => {
626
+ const g = served.get(key);
627
+ const out = rowsOf(list, key);
628
+ if (!g?.more && !g?.loading)
629
+ return out;
630
+ return [
631
+ ...out,
632
+ _jsx(TableRow, { "data-more": true, children: _jsx(TableCell, { colSpan: span, className: "py-1", children: g.loading ? (_jsx("span", { className: "px-2 text-xs text-muted-foreground", children: "Loading\u2026" })) : (_jsxs(Button, { variant: "ghost", size: "xs", onClick: g.more, children: [_jsx(ChevronsDownIcon, {}), "Show more", g.n !== null && (_jsxs("span", { className: "num font-normal text-muted-foreground", children: ["of ", (g.n - list.length).toLocaleString()] }))] })) }) }, `${key}:more`),
633
+ ];
634
+ };
611
635
  const box = useRef(null);
612
636
  useFloatingHead(box, !inView);
613
637
  return (_jsx("div", { ref: box, className: cn(inView ? IN_VIEW : 'overflow-x-auto overflow-y-hidden [--pin-top:0px]', fit && FIT), children: _jsxs(Table, { children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((hg) => (_jsxs(TableRow, { children: [selection && (_jsx(TableHead, { className: cn('w-8 pr-0', PINNED_HEAD), children: _jsx(Checkbox, { checked: allOn ? true : someOn ? 'indeterminate' : false, onCheckedChange: (on) => toggle(allKeys, on === true), "aria-label": "Select every row" }) })), hg.headers.map((h, i) => (_jsxs(TableHead, { className: cn(colOf(shown[i]), PINNED_HEAD), style: ruleOf(colorOf(domain, shown[i])), onPointerDown: pressStart, onPointerUp: pressEnd, onPointerLeave: pressEnd, onPointerCancel: pressEnd, onContextMenu: (e) => {
@@ -617,20 +641,25 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
617
641
  ? 'ascending'
618
642
  : h.column.getIsSorted() === 'desc'
619
643
  ? 'descending'
620
- : undefined, children: [i === 0 && groups && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getCanSort() ? h.column.getToggleSortingHandler() : undefined, children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] })] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
644
+ : undefined, children: [i === 0 && groups && !serverGroups && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getCanSort() ? h.column.getToggleSortingHandler() : undefined, children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] })] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
621
645
  ? groups.map(([key, members], gi) => {
622
646
  const open = isOpen(key, gi);
623
647
  return (_jsxs(Fragment, { children: [_jsxs(TableRow, { className: ROLLUP, "data-group": key, children: [selection && (_jsx(TableCell, { className: cn('w-8 pr-0', PINNED_GROUP), children: _jsx(Checkbox, { checked: members.every((m) => selection.keys.has(keyOf(m.original)))
624
648
  ? true
625
649
  : members.some((m) => selection.keys.has(keyOf(m.original)))
626
650
  ? 'indeterminate'
627
- : false, onCheckedChange: (on) => toggle(members.map((m) => keyOf(m.original)), on === true), "aria-label": `Select every row of ${key}` }) })), shown.slice(lead - 1).map((f, i) => i === 0 ? (_jsxs(TableCell, { colSpan: lead, className: PINNED_GROUP, children: [_jsxs(Button, { variant: "ghost", size: "xs", className: "-mx-2 hover:bg-transparent aria-expanded:bg-transparent", "aria-expanded": open, "aria-label": `${open ? 'Collapse' : 'Expand'} ${key}`, onClick: () => setFlipped((c) => {
628
- const n = new Set(c);
629
- n.has(key) ? n.delete(key) : n.add(key);
630
- return n;
631
- }), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(groupBy.icon, {}), key, _jsx("span", { className: "num font-normal text-muted-foreground", children: members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { className: cn(colOf(f), PINNED_GROUP), children: rollCell(f, members.map((m) => m.original)) }, f.key)))] }), open && drawn(members, key)] }, key));
651
+ : false, onCheckedChange: (on) => toggle(members.map((m) => keyOf(m.original)), on === true), "aria-label": `Select every row of ${key}` }) })), shown.slice(lead - 1).map((f, i) => i === 0 ? (_jsxs(TableCell, { colSpan: lead, className: PINNED_GROUP, children: [_jsxs(Button, { variant: "ghost", size: "xs", className: "-mx-2 hover:bg-transparent aria-expanded:bg-transparent", "aria-expanded": open, "aria-label": `${open ? 'Collapse' : 'Expand'} ${key}`, onClick: () => serverGroups
652
+ ? serverGroups.onToggle(key)
653
+ : setFlipped((c) => {
654
+ const n = new Set(c);
655
+ n.has(key) ? n.delete(key) : n.add(key);
656
+ return n;
657
+ }), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(groupBy.icon, {}), key, _jsx("span", { className: "num font-normal text-muted-foreground", children: serverGroups
658
+ ? (served.get(key)?.n?.toLocaleString() ?? '')
659
+ : members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { className: cn(colOf(f), PINNED_GROUP), children: !serverGroups &&
660
+ rollCell(f, members.map((m) => m.original)) }, f.key)))] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
632
661
  })
633
- : drawn(sorted, ''), hasTotal && rows.length > 0 && (_jsxs(TableRow, { className: cn(ROLLUP, 'border-t-2'), "data-total": true, children: [selection && _jsx(TableCell, {}), rollCells(rows).map((cell, i) => i === 0 && !(shown[0] && rollsUp(shown[0])) ? (_jsx(TableCell, { children: "Total" }, "total")) : (cell))] }))] })] }) }));
662
+ : drawn(sorted, ''), serverGroups?.more && (_jsx(TableRow, { "data-more": true, children: _jsx(TableCell, { colSpan: span, className: "py-1", children: _jsxs(Button, { variant: "ghost", size: "xs", onClick: serverGroups.more, children: [_jsx(ChevronsDownIcon, {}), "More groups"] }) }) })), hasTotal && rows.length > 0 && (_jsxs(TableRow, { className: cn(ROLLUP, 'border-t-2'), "data-total": true, children: [selection && _jsx(TableCell, {}), rollCells(rows).map((cell, i) => i === 0 && !(shown[0] && rollsUp(shown[0])) ? (_jsx(TableCell, { children: "Total" }, "total")) : (cell))] }))] })] }) }));
634
663
  }
635
664
  /** One value rolled up into a rows × columns grid, with totals where the roll-up adds. */
636
665
  function PivotTable({ domain, rows, pivot, }) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,7 +41,7 @@
41
41
  "@codemirror/lang-javascript": "6.2.5",
42
42
  "@codemirror/lang-json": "6.0.2",
43
43
  "@codemirror/lang-sql": "6.10.0",
44
- "@columenlabs/lumen-ui-types": "0.1.43",
44
+ "@columenlabs/lumen-ui-types": "0.1.44",
45
45
  "@dnd-kit/core": "6.3.1",
46
46
  "@dnd-kit/sortable": "10.0.0",
47
47
  "@dnd-kit/utilities": "3.2.2",