@columenlabs/lumen-ui 0.1.43 → 0.1.45

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,39 @@ 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;
91
+ /** Expand all (true) or Collapse all (false): open or close every group listed. */
92
+ onToggleAll?: (open: boolean) => void;
65
93
  }
66
94
  export interface LumenViewProps<T> {
67
95
  /** The view's domain, its formula columns included (`withViewFields`). */
@@ -195,4 +223,7 @@ export interface LumenTableProps<T> {
195
223
  total?: boolean;
196
224
  /** What each row opens under it (`subgroupOf`); null: rows open nothing but their panel. */
197
225
  subgroup?: ViewSubgroup<T> | null;
226
+ /** The groups come from a server (`ServerPaging.groups`): their order, counts and open state,
227
+ * and each open group's rows. */
228
+ serverGroups?: ServerGroups | null;
198
229
  }
@@ -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,12 +525,15 @@ 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
- const setAll = (open) => setFlipped(new Set((groups ?? []).filter((_, i) => startsOpen(i) !== open).map(([k]) => k)));
534
+ const setAll = (open) => serverGroups
535
+ ? serverGroups.onToggleAll?.(open)
536
+ : setFlipped(new Set((groups ?? []).filter((_, i) => startsOpen(i) !== open).map(([k]) => k)));
523
537
  const keyOf = domain.rowKey;
524
538
  const toggle = (keys, on) => {
525
539
  if (!selection)
@@ -534,7 +548,8 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
534
548
  const someOn = allKeys.some((k) => selection?.keys.has(k));
535
549
  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
550
  const [openParts, setOpenParts] = useState(new Set());
537
- const hasTotal = total && shown.some(rollsUp);
551
+ // A server group's roll-ups would add up its loaded rows only, so they and the Total stay off.
552
+ const hasTotal = total && !serverGroups && shown.some(rollsUp);
538
553
  // A group row's label spans the leading columns that roll nothing up.
539
554
  const rolls = (f) => !f.noRollup && (!!f.aggregate || !!f.rollFormula || !!f.share);
540
555
  const lead = Math.max(1, shown.findIndex((f, i) => i > 0 && rolls(f)) < 0
@@ -608,6 +623,21 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
608
623
  _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
624
  ];
610
625
  };
626
+ // A server group's rows as loaded, then its Show more (the server's next rows) or loading row.
627
+ const servedRows = (list, key) => {
628
+ const g = served.get(key);
629
+ const out = rowsOf(list, key);
630
+ if (!g?.more && !g?.loading)
631
+ return out.length
632
+ ? out
633
+ : [
634
+ _jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-2 text-center text-xs text-muted-foreground", children: "No rows match." }) }, `${key}:none`),
635
+ ];
636
+ return [
637
+ ...out,
638
+ _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`),
639
+ ];
640
+ };
611
641
  const box = useRef(null);
612
642
  useFloatingHead(box, !inView);
613
643
  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 +647,25 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
617
647
  ? 'ascending'
618
648
  : h.column.getIsSorted() === 'desc'
619
649
  ? '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
650
+ : undefined, children: [i === 0 && groups && (!serverGroups || serverGroups.onToggleAll) && (_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 && !serverGroups?.list?.length && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center text-muted-foreground", children: "No rows match." }) })), groups && groupBy
621
651
  ? groups.map(([key, members], gi) => {
622
652
  const open = isOpen(key, gi);
623
653
  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
654
  ? true
625
655
  : members.some((m) => selection.keys.has(keyOf(m.original)))
626
656
  ? '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));
657
+ : 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
658
+ ? serverGroups.onToggle(key)
659
+ : setFlipped((c) => {
660
+ const n = new Set(c);
661
+ n.has(key) ? n.delete(key) : n.add(key);
662
+ return n;
663
+ }), 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
664
+ ? (served.get(key)?.n?.toLocaleString() ?? '')
665
+ : members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { className: cn(colOf(f), PINNED_GROUP), children: !serverGroups &&
666
+ rollCell(f, members.map((m) => m.original)) }, f.key)))] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
632
667
  })
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))] }))] })] }) }));
668
+ : 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
669
  }
635
670
  /** One value rolled up into a rows × columns grid, with totals where the roll-up adds. */
636
671
  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.45",
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.45",
45
45
  "@dnd-kit/core": "6.3.1",
46
46
  "@dnd-kit/sortable": "10.0.0",
47
47
  "@dnd-kit/utilities": "3.2.2",