@columenlabs/lumen-ui 0.1.52 → 0.1.53

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.
@@ -1,7 +1,7 @@
1
- import { type CardStyle, type MeterKind, type ViewTile } from '@columenlabs/lumen-ui-types/views';
1
+ import { type CardStyle, type MeterKind, type ViewAgg, type ViewTile } from '@columenlabs/lumen-ui-types/views';
2
2
  import { type ReactNode } from 'react';
3
3
  import { type FilterField } from './lumen-filter.js';
4
- import { type ViewDomain } from './view-model.js';
4
+ import { type ViewDomain, type ViewField } from './view-model.js';
5
5
  /**
6
6
  * A LumenCard's number and lines: its frame shows the label, so the body is
7
7
  * the main number and its lines; a domain's own card keeps the app's drawing without its frame.
@@ -36,6 +36,12 @@ export declare function SummaryTiles<T>({ domain, tiles, rows, fields, tile, loa
36
36
  /** While the view's rows load, each card holds its place. */
37
37
  loading?: boolean;
38
38
  }): import("react").JSX.Element | null;
39
+ /**
40
+ * The roll-up a card over `f` starts with, so any field makes a sensible number: text counts its
41
+ * different values, a percent or weight takes its highest (shares never add up), the rest sum.
42
+ * Given the card's `agg`, keeps it when it still fits the field.
43
+ */
44
+ export declare function cardAgg<T>(f: ViewField<T> | undefined, agg?: ViewAgg): ViewAgg;
39
45
  /** What is wrong with each of the cards: a key, a label or a field; null where nothing. */
40
46
  export declare function summaryCardIssues(tiles: ViewTile[], fields: string[], domainTiles: string[]): (string | null)[];
41
47
  /**
@@ -134,14 +134,29 @@ export function SummaryTiles({ domain, tiles, rows, fields, tile, loading, }) {
134
134
  const shown = tiles.filter((t) => isOwnTile(t) || tile);
135
135
  if (!shown.length)
136
136
  return null;
137
- const grid = 'grid grid-cols-2 gap-(--shell-section-gap) md:auto-cols-fr md:grid-flow-col';
137
+ // Each card fills its column, as a header's metric cards do (`TileRow fill`), not the fixed tile width.
138
+ const grid = 'grid grid-cols-2 gap-(--shell-section-gap) md:auto-cols-fr md:grid-flow-col lg:*:*:w-auto';
138
139
  if (loading)
139
140
  return (_jsx("div", { "data-slot": "view-tiles", "aria-busy": "true", className: grid, children: shown.map((t) => (_jsx(Skeleton, { className: "h-[4.5rem] w-full rounded-xl" }, t.key))) }));
140
141
  return (_jsx("div", { "data-slot": "view-tiles", className: cn(grid, '[&>*:last-child:nth-child(odd)]:col-span-2 md:[&>*:last-child:nth-child(odd)]:col-span-1'), children: shown.map((t) => (_jsx("div", { className: "contents", children: isOwnTile(t) ? (_jsx(OwnTile, { domain: domain, tile: t, rows: rows, fields: fields })) : (tile?.(t.key, rows)) }, t.key))) }));
141
142
  }
142
143
  // ── Edit ───────────────────────────────────────────────────────────────────────────────────────
143
144
  const dragOptions = { activationConstraint: { distance: 4 } };
144
- const isNumber = (f) => f.format !== 'text' && !f.render;
145
+ // A number the page draws its own way (a meter, a unit) still reads its raw value, so it rolls up.
146
+ const isNumber = (f) => f.format !== 'text';
147
+ /**
148
+ * The roll-up a card over `f` starts with, so any field makes a sensible number: text counts its
149
+ * different values, a percent or weight takes its highest (shares never add up), the rest sum.
150
+ * Given the card's `agg`, keeps it when it still fits the field.
151
+ */
152
+ export function cardAgg(f, agg) {
153
+ const share = f?.format === 'percent' || f?.format === 'weight';
154
+ if (agg && (isCountAgg(agg) || (f && isNumber(f) && !(share && agg === 'sum'))))
155
+ return agg;
156
+ if (!f || !isNumber(f))
157
+ return 'distinct';
158
+ return share ? 'max' : 'sum';
159
+ }
145
160
  /** What is wrong with each of the cards: a key, a label or a field; null where nothing. */
146
161
  export function summaryCardIssues(tiles, fields, domainTiles) {
147
162
  return tiles.map((t, i) => {
@@ -211,7 +226,7 @@ export function SummaryCardsEditor({ domain, tiles, onChange, rows, fields, prob
211
226
  const builtIns = domain.tiles ?? [];
212
227
  const [current, setCurrent] = useState(tiles.find(isOwnTile)?.key ?? tiles[0]?.key ?? null);
213
228
  const nums = domain.fields.filter(isNumber);
214
- const pickable = domain.fields.filter((f) => !f.render);
229
+ const pickable = domain.fields;
215
230
  const fieldOf = (k) => domain.fields.find((f) => f.key === k);
216
231
  const i = tiles.findIndex((t) => t.key === current);
217
232
  const picked = tiles[i];
@@ -10,7 +10,7 @@ import { ALLOCATION_MAX, isOwnTile, isRowCard, isTableCard, trendBuckets, widget
10
10
  import { ArrowDownIcon, ArrowUpIcon } from 'lucide-react';
11
11
  import { useState } from 'react';
12
12
  import { LumenSelector } from './lumen-selector.js';
13
- import { SummaryCardsEditor, summaryCardIssues } from './summary-cards.js';
13
+ import { SummaryCardsEditor, cardAgg, summaryCardIssues } from './summary-cards.js';
14
14
  import { Input } from './ui/input.js';
15
15
  import { Label } from './ui/label.js';
16
16
  import { Switch } from './ui/switch.js';
@@ -21,7 +21,8 @@ export function EditorRow({ label, children }) {
21
21
  return (_jsxs("div", { className: "grid grid-cols-[5rem_minmax(0,1fr)] items-center gap-2", children: [_jsx("span", { className: "text-sm text-muted-foreground", children: label }), _jsx("div", { className: "min-w-0", children: children })] }));
22
22
  }
23
23
  export const isTextField = (f) => f.format === 'text' && !f.render;
24
- export const isNumberField = (f) => f.format !== 'text' && !f.render;
24
+ // A number the page draws its own way (a meter, a unit) still reads its raw value, so it rolls up.
25
+ export const isNumberField = (f) => f.format !== 'text';
25
26
  const DATE_LIKE = /(^|_)(date|day|at|time|month|week|year)($|_)/i;
26
27
  /** A trend's date candidates: text fields, the date-like ones first. */
27
28
  export const dateFields = (domain) => {
@@ -45,7 +46,8 @@ const freeTileKey = (tiles, taken) => {
45
46
  };
46
47
  /** A widget's slots, in the order the editor lists them; none for a chart of symbols. */
47
48
  export function widgetSlots(card, domain) {
48
- const label = (k) => domain.fields.find((f) => f.key === k)?.label ?? k;
49
+ const fieldOf = (k) => domain.fields.find((f) => f.key === k);
50
+ const label = (k) => fieldOf(k)?.label ?? k;
49
51
  const text = { accepts: isTextField, why: 'Takes a text field' };
50
52
  const number = { accepts: isNumberField, why: 'Takes a number' };
51
53
  if ('by' in card)
@@ -118,18 +120,19 @@ export function widgetSlots(card, domain) {
118
120
  id: 'number',
119
121
  label: 'Number',
120
122
  max: 1,
121
- ...number,
123
+ accepts: () => true,
124
+ why: '',
122
125
  keys: isOwnTile(t) ? [t.field] : [],
123
126
  set: ([field]) => field
124
127
  ? {
125
128
  ...card,
126
129
  tile: isOwnTile(t)
127
- ? { ...t, field }
130
+ ? { ...t, field, agg: cardAgg(fieldOf(field), t.agg) }
128
131
  : {
129
132
  key: t.key,
130
133
  label: label(field),
131
134
  field,
132
- agg: 'sum',
135
+ agg: cardAgg(fieldOf(field)),
133
136
  filter: [],
134
137
  sign: false,
135
138
  lines: [],
@@ -147,9 +150,10 @@ export function widgetSlots(card, domain) {
147
150
  id: 'cards',
148
151
  label: 'Cards',
149
152
  max: MAX_TILES,
150
- ...number,
153
+ accepts: () => true,
154
+ why: '',
151
155
  keys: tiles.filter(isOwnTile).map((t) => t.field),
152
- // A field ticked adds a card of its sum; one unticked takes its cards out.
156
+ // A field ticked adds a card of its roll-up (`cardAgg`); one unticked takes its cards out.
153
157
  set: (keys) => {
154
158
  const kept = tiles.filter((t) => !isOwnTile(t) || keys.includes(t.field));
155
159
  const next = [...kept];
@@ -160,7 +164,7 @@ export function widgetSlots(card, domain) {
160
164
  key: freeTileKey(next, builtIns),
161
165
  label: label(field),
162
166
  field,
163
- agg: 'sum',
167
+ agg: cardAgg(fieldOf(field)),
164
168
  filter: [],
165
169
  sign: false,
166
170
  lines: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
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.52",
44
+ "@columenlabs/lumen-ui-types": "0.1.53",
45
45
  "@dnd-kit/core": "6.3.1",
46
46
  "@dnd-kit/sortable": "10.0.0",
47
47
  "@dnd-kit/utilities": "3.2.2",