@columenlabs/lumen-ui 0.1.59 → 0.1.61
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 +1 -1
- package/dist/lumen-view.d.ts +1 -1
- package/dist/lumen-view.js +32 -12
- package/dist/view-model.d.ts +14 -3
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
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 RollLevel, 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';
|
package/dist/lumen-view.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type ReactNode } from 'react';
|
|
|
4
4
|
import { type LumenLoad } from './lumen-source.js';
|
|
5
5
|
import { type CardControls, type CardLink } from './lumen-widget.js';
|
|
6
6
|
import { type ViewCellValue, type ViewChipTone, type ViewDomain, type ViewField, type ViewSubgroup } from './view-model.js';
|
|
7
|
-
export type { ViewCellValue, ViewChipTone, ViewDomain, ViewField, ViewFieldGroup, ViewSource, } from './view-model.js';
|
|
7
|
+
export type { ViewCellValue, ViewChipTone, ViewDomain, ViewField, RollLevel, ViewFieldGroup, ViewSource, } from './view-model.js';
|
|
8
8
|
export { CHIP_TONE, FORMULAS_GROUP, RUN_STATE_TONE } from './view-model.js';
|
|
9
9
|
export { rollUp } from './view-model.js';
|
|
10
10
|
export type { ViewEditorSection } from './view-editor.js';
|
package/dist/lumen-view.js
CHANGED
|
@@ -422,10 +422,14 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
422
422
|
const col = colPropsOf(units);
|
|
423
423
|
const [flipped, setFlipped] = useState(new Set());
|
|
424
424
|
const [more, setMore] = useState(new Map());
|
|
425
|
-
//
|
|
425
|
+
// Under a sub-group's `flat` grouping each row names itself by its sub-group key instead of
|
|
426
|
+
// folding; the first column keeps its place to show that name.
|
|
427
|
+
const flat = !!groupBy && !!subgroup?.flat?.includes(groupBy.key);
|
|
428
|
+
// The grouped field is the group row's label, so its column hides (a flat lead stays).
|
|
426
429
|
// Each key once: two columns under one React key can lose a cell on a re-render, and every
|
|
427
430
|
// row after it slides left by one.
|
|
428
|
-
const visible = columns.filter((f, i) => f.key !== groupBy?.key
|
|
431
|
+
const visible = columns.filter((f, i) => (f.key !== groupBy?.key || (flat && i === 0)) &&
|
|
432
|
+
columns.findIndex((x) => x.key === f.key) === i);
|
|
429
433
|
// Inline chips sit on the first column, next to the row's name, not in their own.
|
|
430
434
|
const inline = visible.filter((f, i) => i > 0 && chips.some((c) => c.key === f.key && c.placement === 'inline'));
|
|
431
435
|
const shown = visible.filter((f) => !inline.includes(f));
|
|
@@ -506,7 +510,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
506
510
|
},
|
|
507
511
|
sortUndefined: 'last',
|
|
508
512
|
enableSorting: !serverSorts || serverSorts.includes(f.key),
|
|
509
|
-
header: f.label,
|
|
513
|
+
header: i === 0 && flat && subgroup ? subgroup.label : f.label,
|
|
510
514
|
cell: ({ row }) => {
|
|
511
515
|
const { shown: now, cellOf, inline, chips, computed } = latest.current;
|
|
512
516
|
const field = now[i] ?? f;
|
|
@@ -519,7 +523,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
519
523
|
return [{ key: x.key, node: cellOf(x, row.original, true) }];
|
|
520
524
|
}) })) : (cellOf(field, row.original, chip)) }));
|
|
521
525
|
},
|
|
522
|
-
})), [shownKeys, inlineOn, serverSorts?.join(',')]);
|
|
526
|
+
})), [shownKeys, inlineOn, serverSorts?.join(','), flat]);
|
|
523
527
|
const table = useTable({
|
|
524
528
|
features: SORTING,
|
|
525
529
|
data: rows,
|
|
@@ -601,15 +605,16 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
601
605
|
// On a phone the hideOnPhone columns are gone, so the label spans only the leading ones still
|
|
602
606
|
// drawn; spanning the hidden ones too pushed the group's totals right of their headers.
|
|
603
607
|
const leadSpan = Math.max(1, lead - (isMobile ? shown.slice(0, lead).filter((f) => f.hideOnPhone).length : 0));
|
|
604
|
-
const rollCell = (f, members) => rollsUp(f) && f.rollRender ? (f.rollRender(members)) : rollsUp(f) ? (_jsx(ViewValueCell, { value: computed.roll(f, members), format: f.aggregate && isCountAgg(f.aggregate) ? 'rounded' : f.format, amount: f.aggregate !== 'count' && f.amount, compact: f.compact })) : null;
|
|
605
|
-
const rollCells = (members) => shown.map((f) => (_jsx(TableCell, { ...col(f), children: rollCell(f, members) }, f.key)));
|
|
608
|
+
const rollCell = (f, members, level) => rollsUp(f) && f.rollRender ? (f.rollRender(members, level)) : rollsUp(f) ? (_jsx(ViewValueCell, { value: computed.roll(f, members), format: f.aggregate && isCountAgg(f.aggregate) ? 'rounded' : f.format, amount: f.aggregate !== 'count' && f.amount, compact: f.compact })) : null;
|
|
609
|
+
const rollCells = (members) => shown.map((f) => (_jsx(TableCell, { ...col(f), children: rollCell(f, members, 'total') }, f.key)));
|
|
606
610
|
// A row's chevron opens one level under it, closed by default: its panel, else the view's
|
|
607
611
|
// sub-group ("Each row opens"; None opens nothing).
|
|
608
612
|
const partsOf = (row) => (domain.panel ? [] : (subgroup?.rows?.(row) ?? []));
|
|
609
613
|
const panelOf = (row) => domain.panel?.render(row) ?? null;
|
|
610
614
|
const opens = (row) => (domain.panel ? panelOf(row) !== null : partsOf(row).length > 1);
|
|
611
615
|
const openLabel = () => domain.panel?.label ?? subgroup?.label.toLowerCase() ?? 'parts';
|
|
612
|
-
const folds = !domain.panel && !!subgroup?.under;
|
|
616
|
+
const folds = !domain.panel && !!subgroup?.under && !flat;
|
|
617
|
+
const flatName = (row) => flat && subgroup?.under ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [subgroup.mark?.(subgroup.under(row), [row]), _jsx("span", { className: "truncate", children: subgroup.under(row) })] })) : null;
|
|
613
618
|
const nests = folds || sorted.some((r) => opens(r.original));
|
|
614
619
|
const chevron = (k, name) => (_jsx(Button, { variant: "ghost", size: "icon-xs", className: "-ml-1", "aria-expanded": openParts.has(k), "aria-label": `${openParts.has(k) ? 'Hide' : 'Show'} ${name}`, onClick: (e) => {
|
|
615
620
|
e.stopPropagation();
|
|
@@ -619,7 +624,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
619
624
|
return n;
|
|
620
625
|
});
|
|
621
626
|
}, children: _jsx(ChevronRightIcon, { className: cn('transition-transform', openParts.has(k) && 'rotate-90') }) }));
|
|
622
|
-
const partRow = (p, name) => (_jsxs(TableRow, { className: cn('bg-muted/30 text-muted-foreground hover:bg-muted
|
|
627
|
+
const partRow = (p, name) => (_jsxs(TableRow, { className: cn('bg-muted/30 text-muted-foreground hover:bg-muted', onDetails && 'cursor-pointer'), onClick: onDetails ? () => onDetails(p) : undefined, children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, i === 0 && 'pl-12'), children: i === 0 ? (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx(CornerDownRightIcon, { className: "size-3.5 shrink-0" }), _jsx(CappedCell, { max: capOf(f, 0), children: name
|
|
623
628
|
? name(p)
|
|
624
629
|
: cellOf(f, p, chips.some((c) => c.key === f.key)) })] })) : (cellOf(f, p, chips.some((c) => c.key === f.key))) }, f.key)))] }, `part:${keyOf(p)}`));
|
|
625
630
|
// The whole row opens the details drawer.
|
|
@@ -632,14 +637,18 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
632
637
|
onDetails(r.original);
|
|
633
638
|
}
|
|
634
639
|
}
|
|
635
|
-
: undefined, children: [selectCell(keyOf(r.original), keyOf(r.original)), r.getAllCells().map((c, i) => (_jsx(TableCell, { ...col(shown[i], groupBy && i === 0 && 'pl-8'), style: { background: shown[i]?.tint?.(r.original) }, children: i === 0 && nests ? (_jsxs("span", { className: "flex items-center gap-1", children: [opens(r.original) ? (chevron(keyOf(r.original), openLabel())) : (_jsx("span", { className: "-ml-1 size-6 shrink-0" })), flexRender(c.column.columnDef.cell, c.getContext())] })) : (flexRender(c.column.columnDef.cell, c.getContext())) }, c.id)))] }, domain.rowKey(r.original)), openParts.has(keyOf(r.original)) &&
|
|
640
|
+
: undefined, children: [selectCell(keyOf(r.original), keyOf(r.original)), r.getAllCells().map((c, i) => (_jsx(TableCell, { ...col(shown[i], groupBy && i === 0 && 'pl-8'), style: { background: shown[i]?.tint?.(r.original) }, children: i === 0 && nests ? (_jsxs("span", { className: "flex items-center gap-1", children: [opens(r.original) ? (chevron(keyOf(r.original), openLabel())) : (_jsx("span", { className: "-ml-1 size-6 shrink-0" })), flexRender(c.column.columnDef.cell, c.getContext())] })) : i === 0 && flat ? (flatName(r.original)) : (flexRender(c.column.columnDef.cell, c.getContext())) }, c.id)))] }, domain.rowKey(r.original)), openParts.has(keyOf(r.original)) &&
|
|
636
641
|
partsOf(r.original).map((p) => partRow(p, subgroup?.name)), openParts.has(keyOf(r.original)) && domain.panel ? (_jsx(TableRow, { "data-slot": "row-panel", className: "bg-muted/20 hover:bg-muted/20", children: _jsx(TableCell, { colSpan: shown.length + (selection ? 1 : 0), className: "p-0 whitespace-normal", children: _jsx("div", { className: "sticky left-0 w-[min(56rem,calc(100vw-2rem))] max-w-full px-4 py-3 md:pl-10", children: panelOf(r.original) }) }) }, `panel:${keyOf(r.original)}`)) : null] }, domain.rowKey(r.original)));
|
|
637
642
|
// Rows sharing the sub-group's `under` key fold into one rolled-up line that opens them; a key
|
|
638
643
|
// with one row keeps that row (NVDA opens Shares, then each option).
|
|
639
644
|
const foldRow = (key, members, scope) => {
|
|
640
645
|
const k = `under:${scope}\u0000${key}`;
|
|
641
646
|
const ms = members.map((m) => m.original);
|
|
642
|
-
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k,
|
|
647
|
+
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-row": k, className: "cursor-pointer hover:bg-muted has-aria-expanded:hover:bg-muted", onClick: () => setOpenParts((o) => {
|
|
648
|
+
const n = new Set(o);
|
|
649
|
+
n.has(k) ? n.delete(k) : n.add(k);
|
|
650
|
+
return n;
|
|
651
|
+
}), children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, groupBy && i === 0 && 'pl-8'), children: i === 0 ? (_jsxs("span", { className: "flex items-center gap-1", children: [chevron(k, `${key} ${openLabel()}`), subgroup?.mark?.(key, ms), _jsx("span", { className: "font-medium", children: key }), _jsx("span", { className: "num text-xs text-muted-foreground", children: ms.length })] })) : (rollCell(f, ms, 'fold')) }, f.key)))] }), openParts.has(k) && ms.map((p) => partRow(p, subgroup?.name))] }, k));
|
|
643
652
|
};
|
|
644
653
|
const rowsOf = (list, scope) => {
|
|
645
654
|
const under = folds ? subgroup?.under : undefined;
|
|
@@ -706,10 +715,10 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
706
715
|
const n = new Set(c);
|
|
707
716
|
n.has(key) ? n.delete(key) : n.add(key);
|
|
708
717
|
return n;
|
|
709
|
-
}), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(groupBy
|
|
718
|
+
}), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(GroupMark, { field: groupBy, value: key }), key, _jsx("span", { className: "num font-normal text-muted-foreground", children: serverGroups
|
|
710
719
|
? (served.get(key)?.n?.toLocaleString() ?? '')
|
|
711
720
|
: members.length })] }), groupActions?.(key, members.map((m) => m.original))] }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
|
|
712
|
-
rollCell(f, members.map((m) => m.original)) }, f.key)))] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
|
|
721
|
+
rollCell(f, members.map((m) => m.original), 'group') }, f.key)))] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
|
|
713
722
|
})
|
|
714
723
|
: drawn(sorted, ''), serverGroups?.more && !serverGroups.pages && (_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))] }))] })] }) }) }));
|
|
715
724
|
}
|
|
@@ -738,3 +747,14 @@ function PivotTable({ domain, rows, pivot, }) {
|
|
|
738
747
|
const amount = pivot.agg !== 'count' && vf.amount;
|
|
739
748
|
return (_jsx("div", { className: IN_VIEW, "data-testid": "view-pivot", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: PINNED_HEAD, style: ruleOf(colorOf(domain, rf)), children: _jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(rf.icon, { className: "size-3.5" }), _jsx("span", { style: { color: colorOf(domain, rf) ?? undefined }, children: rf.label }), " \\", ' ', _jsx("span", { style: { color: colorOf(domain, cf) ?? undefined }, children: cf.label }), _jsxs("span", { className: "font-normal text-muted-foreground", children: ["(", _jsx("span", { style: { color: colorOf(domain, vf) ?? undefined }, children: vf.label }), ",", ' ', pivot.agg, ")"] })] }) }), grid.colKeys.map((c) => (_jsx(TableHead, { className: cn('text-right', PINNED_HEAD), style: ruleOf(colorOf(domain, cf)), children: c }, c))), totals && _jsx(TableHead, { className: cn('text-right', PINNED_HEAD), children: "Total" })] }) }), _jsxs(TableBody, { children: [grid.rowKeys.map((r) => (_jsxs(TableRow, { "data-row": r, children: [_jsx(TableCell, { children: r }), grid.colKeys.map((c) => (_jsx(TableCell, { className: "text-right", children: _jsx(ViewValueCell, { value: grid.cell(r, c), format: fmt, amount: amount }) }, c))), totals && (_jsx(TableCell, { className: cn('text-right', ROLLUP), children: _jsx(ViewValueCell, { value: grid.cell(r, null), format: fmt, amount: amount }) }))] }, r))), totals && (_jsxs(TableRow, { className: cn(ROLLUP, 'border-t-2'), "data-total": true, children: [_jsx(TableCell, { children: "Total" }), grid.colKeys.map((c) => (_jsx(TableCell, { className: "text-right", children: _jsx(ViewValueCell, { value: grid.cell(null, c), format: fmt, amount: amount }) }, c))), _jsx(TableCell, { className: "text-right", children: _jsx(ViewValueCell, { value: grid.cell(null, null), format: fmt, amount: amount }) })] }))] })] }) }));
|
|
740
749
|
}
|
|
750
|
+
/** A group heading's mark: its value's icon in its colour, else its colour's dot, else the
|
|
751
|
+
* field's icon. */
|
|
752
|
+
function GroupMark({ field, value }) {
|
|
753
|
+
const color = field.color?.(value);
|
|
754
|
+
const Icon = field.iconOf?.(value);
|
|
755
|
+
if (Icon)
|
|
756
|
+
return (_jsx("span", { className: "inline-flex", style: color ? { color } : undefined, children: _jsx(Icon, { className: "size-3.5" }) }));
|
|
757
|
+
if (color)
|
|
758
|
+
return _jsx(ColorDot, { color: color });
|
|
759
|
+
return _jsx(field.icon, {});
|
|
760
|
+
}
|
package/dist/view-model.d.ts
CHANGED
|
@@ -98,9 +98,10 @@ export interface ViewField<T> {
|
|
|
98
98
|
/**
|
|
99
99
|
* Draws a group or Total row's cell from its rows instead of the rolled-up number: an amount with
|
|
100
100
|
* its unit, or a share worked out again as the rows' total over their allowance (never a sum of
|
|
101
|
-
* percents). Only where the field rolls up (`aggregate`).
|
|
101
|
+
* percents). Only where the field rolls up (`aggregate`). `level` says which line it draws: a
|
|
102
|
+
* group heading, a sub-group's folded line or the Total (a split bar on a fold only).
|
|
102
103
|
*/
|
|
103
|
-
rollRender?: (rows: T[]) => ReactNode;
|
|
104
|
+
rollRender?: (rows: T[], level: RollLevel) => ReactNode;
|
|
104
105
|
/**
|
|
105
106
|
* Ways the column can read, picked per column in Edit view ("Show as"), e.g. an amount or its % of
|
|
106
107
|
* an allowance. The first is the field as defined; each other one's `field` replaces those parts
|
|
@@ -123,7 +124,8 @@ export interface ViewField<T> {
|
|
|
123
124
|
* cell, its chip and the edit picker. A CSS colour; a theme token reads in light and dark.
|
|
124
125
|
*/
|
|
125
126
|
color?: (value: string) => string | undefined;
|
|
126
|
-
/** A text value's icon (a label's), drawn in its `color` on its chip
|
|
127
|
+
/** A text value's icon (a label's, a product's), drawn in its `color` on its chip, in the edit
|
|
128
|
+
* picker and on its group's heading. */
|
|
127
129
|
iconOf?: (value: string) => ComponentType<{
|
|
128
130
|
className?: string;
|
|
129
131
|
}> | undefined;
|
|
@@ -300,6 +302,8 @@ export interface ViewDomain<T> {
|
|
|
300
302
|
export declare const summaryCash: (card: {
|
|
301
303
|
cash?: boolean;
|
|
302
304
|
}, narrowed: boolean) => boolean;
|
|
305
|
+
/** The line a `rollRender` draws: a group heading, a sub-group's folded line, the Total. */
|
|
306
|
+
export type RollLevel = 'group' | 'fold' | 'total';
|
|
303
307
|
/** One choice of a domain's "Each row opens". */
|
|
304
308
|
export interface ViewSubgroup<T> {
|
|
305
309
|
key: string;
|
|
@@ -312,6 +316,13 @@ export interface ViewSubgroup<T> {
|
|
|
312
316
|
always?: boolean;
|
|
313
317
|
/** Rows start open, their parts showing (a meter's labels); a click still closes one. */
|
|
314
318
|
open?: boolean;
|
|
319
|
+
/** Drawn before a folded line's key (a meter's product icon, in its colour). */
|
|
320
|
+
mark?: (key: string, rows: T[]) => ReactNode;
|
|
321
|
+
/**
|
|
322
|
+
* Group fields under which rows do not fold: each row is one line named by its `under` key (and
|
|
323
|
+
* `mark`) instead of its first column (grouped by label, a label's meters).
|
|
324
|
+
*/
|
|
325
|
+
flat?: string[];
|
|
315
326
|
}
|
|
316
327
|
/**
|
|
317
328
|
* What a row can open: the domain's own sub-groups, else every groupable field as a second level
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.61",
|
|
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.
|
|
44
|
+
"@columenlabs/lumen-ui-types": "0.1.61",
|
|
45
45
|
"@dnd-kit/core": "6.3.1",
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@dnd-kit/utilities": "3.2.2",
|