@columenlabs/lumen-ui 0.1.45 → 0.1.47
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/README.md +10 -0
- package/dist/color-dot.d.ts +11 -2
- package/dist/color-dot.js +22 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lumen-echart.js +24 -2
- package/dist/lumen-filter.js +70 -18
- package/dist/lumen-links.js +3 -1
- package/dist/lumen-selector.js +2 -1
- package/dist/lumen-view.js +2 -2
- package/dist/lumen-widget-charts.js +17 -1
- package/dist/lumen-widget.d.ts +18 -17
- package/dist/nav.d.ts +5 -1
- package/dist/nav.js +50 -9
- package/dist/share-meter.d.ts +12 -0
- package/dist/share-meter.js +6 -0
- package/dist/tile.d.ts +22 -4
- package/dist/tile.js +24 -9
- package/dist/unit-value.d.ts +11 -0
- package/dist/unit-value.js +5 -0
- package/dist/view-edit-cell.d.ts +6 -2
- package/dist/view-edit-cell.js +9 -4
- package/dist/view-editor.js +7 -1
- package/dist/view-model.d.ts +23 -3
- package/dist/view-model.js +10 -2
- package/dist/whats-new.js +7 -5
- package/dist/widget-settings.d.ts +15 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -74,6 +74,16 @@ Every export, its props and an example are on the [docs](https://lumen.columenla
|
|
|
74
74
|
- Pickers are `LumenSelector`, side panels `LumenDrawer`, tabs `LumenTabs`, destructive confirms `LumenConfirm`; reach for the raw `ui/*` part only when no Lumen part fits.
|
|
75
75
|
- Every card body sits in `LumenBoundary`, so one crash never blanks a page.
|
|
76
76
|
- Numbers use tabular figures and a true minus `−`; a gain or loss always shows a sign or arrow.
|
|
77
|
+
- A page's header numbers are metric cards: `TileRow` of `Tile` (or `TileToggleGroup` when they pick what the page shows), with `fill` so they share the row, each with `spark` set to its daily history where it has one. Don't build a card of your own for this.
|
|
78
|
+
- A metered column (use against an allowance) shows one number per cell with `UnitValue` (right-aligned number, fixed muted unit slot, status dot), never `value [pct]`. Offer both readings through the field's `showAs` (Amount / % of allowance, picked per column in Edit view, saved as `shows`); default amounts for sizes (storage) and shares for capped counts. Put the other reading, the allowance and the cost formula in a hover card. Totals use `rollRender`: sum amounts with their unit; a share is the rows' total over their allowance, never a sum of percents.
|
|
79
|
+
- A floating bulk-action bar over a table's selection: Apply clears the selection and the bar goes away; the toast's Undo restores the change, not the selection. Cancel keeps the selection.
|
|
80
|
+
- Bars in a column line up: use `ShareMeter` (fixed track, fixed right-aligned number), never a bar sized by its label.
|
|
81
|
+
- Table rows are one line. A cell shows one value (with a small bar or tag at most); anything more (other metrics, the allowance, a breakdown) goes in a hover tooltip, never in stacked text or sub-rows.
|
|
82
|
+
- Chart colours are theme tokens (`var(--chart-1)`, `useChartTokens()`), never hex picked for one theme; `LumenEChart` resolves `var(--…)` in its option so sparklines read in light and dark.
|
|
83
|
+
- "No label" filters come built in: `LumenTokenizedFilter` pins a "No <field>" row on any text field where some rows have no value (an `empty` token); don't add your own toggle for it.
|
|
84
|
+
- Fewer tab rows: one row of tabs at most per card; narrower cuts (a product, a type) are fields in `LumenTokenizedFilter`, and a link to one opens the tab with that filter chip set.
|
|
85
|
+
- One selected sidebar row: give each nav item its own route, and let an open parent look neutral.
|
|
86
|
+
- What's new is on in every app: pass `releases="/api/releases"` to `LumenShell`, served by lumen-ship's `listReleases`.
|
|
77
87
|
- A Save blocked by its form's checks is a `SaveButton` that lists each issue, never a bare `disabled`.
|
|
78
88
|
|
|
79
89
|
## Notices
|
package/dist/color-dot.d.ts
CHANGED
|
@@ -7,8 +7,17 @@ export declare function ColorDot({ color, className }: {
|
|
|
7
7
|
export declare function colorIcon(color: string): ComponentType<{
|
|
8
8
|
className?: string;
|
|
9
9
|
}>;
|
|
10
|
-
/**
|
|
11
|
-
export declare function
|
|
10
|
+
/** `icon` drawn in `color` (a label's icon in the label's colour), kept per pair so it keeps its identity. */
|
|
11
|
+
export declare function tintedIcon(icon: ComponentType<{
|
|
12
|
+
className?: string;
|
|
13
|
+
}>, color: string | undefined): ComponentType<{
|
|
14
|
+
className?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** A value as a chip in its colour (a label): a tint behind, the colour as text, its icon first. */
|
|
17
|
+
export declare function ColorChip({ color, icon: Icon, children, }: {
|
|
12
18
|
color?: string;
|
|
19
|
+
icon?: ComponentType<{
|
|
20
|
+
className?: string;
|
|
21
|
+
}>;
|
|
13
22
|
children: ReactNode;
|
|
14
23
|
}): import("react").JSX.Element;
|
package/dist/color-dot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from './lib/utils.js';
|
|
3
3
|
export function ColorDot({ color, className }) {
|
|
4
4
|
return (_jsx("span", { "aria-hidden": true, className: cn('flex size-4 shrink-0 items-center justify-center', className), children: _jsx("span", { className: "size-2.5 rounded-full", style: { background: color } }) }));
|
|
@@ -13,7 +13,25 @@ export function colorIcon(color) {
|
|
|
13
13
|
}
|
|
14
14
|
return icon;
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const tinted = new WeakMap();
|
|
17
|
+
/** `icon` drawn in `color` (a label's icon in the label's colour), kept per pair so it keeps its identity. */
|
|
18
|
+
export function tintedIcon(icon, color) {
|
|
19
|
+
if (!color)
|
|
20
|
+
return icon;
|
|
21
|
+
let byColor = tinted.get(icon);
|
|
22
|
+
if (!byColor) {
|
|
23
|
+
byColor = new Map();
|
|
24
|
+
tinted.set(icon, byColor);
|
|
25
|
+
}
|
|
26
|
+
let out = byColor.get(color);
|
|
27
|
+
if (!out) {
|
|
28
|
+
const Icon = icon;
|
|
29
|
+
out = ({ className }) => (_jsx("span", { "aria-hidden": true, className: "flex shrink-0", style: { color }, children: _jsx(Icon, { className: cn('size-4', className) }) }));
|
|
30
|
+
byColor.set(color, out);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
/** A value as a chip in its colour (a label): a tint behind, the colour as text, its icon first. */
|
|
35
|
+
export function ColorChip({ color, icon: Icon, children, }) {
|
|
36
|
+
return (_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-sm bg-secondary px-1.5 text-xs font-medium whitespace-nowrap text-secondary-foreground", style: color ? { background: `color-mix(in oklab, ${color} 18%, transparent)`, color } : undefined, children: [Icon && _jsx(Icon, { className: "size-3 shrink-0" }), children] }));
|
|
19
37
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export { LumenDateInput, type LumenDateInputProps, stepDate, } from './lumen-dat
|
|
|
21
21
|
export { LumenChoice, type LumenChoiceOption } from './lumen-choice.js';
|
|
22
22
|
export { LumenSecret } from './lumen-secret.js';
|
|
23
23
|
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
24
|
+
export { ShareMeter } from './share-meter.js';
|
|
25
|
+
export { UnitValue } from './unit-value.js';
|
|
24
26
|
export { COVERAGE_LEVELS, CoverageCell, type CoverageJobState, type CoverageLevel, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
25
27
|
export { type CoverageBlock, type CoverageData, type CoverageFact, type CoveragePick, type CoverageRow, type CoverageSummary, LumenCoverageMap, type LumenCoverageMapProps, blockName, blockOf, } from './coverage-map.js';
|
|
26
28
|
export { type CalendarFact, LumenCoverageCalendarMap, type LumenCoverageCalendarMapProps, } from './coverage-calendar-map.js';
|
|
@@ -66,7 +68,7 @@ export { OriginLegend, originsOf } from './origin-legend.js';
|
|
|
66
68
|
export { type ConnectedDomain, ConnectOffer, connectionFields, linkGroupId } from './connections.js';
|
|
67
69
|
export { FRESHNESS, type Freshness, FreshnessIcon, FreshnessMark, freshnessLine, freshnessOf, } from './freshness.js';
|
|
68
70
|
export { SourcesLine, useSourceHealth } from './source-health.js';
|
|
69
|
-
export { TILE_WIDTH, Tile, TileChip, type TileOption, TileRow, TileToggleGroup, } from './tile.js';
|
|
71
|
+
export { TILE_WIDTH, Tile, TileChip, type TileOption, TileRow, TileSpark, TileToggleGroup, } from './tile.js';
|
|
70
72
|
export { type FilterField, type FilterToken, LumenTokenizedFilter, type LumenFilterState, useLumenFilter, } from './lumen-filter.js';
|
|
71
73
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
72
74
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,8 @@ export { LumenDateInput, stepDate, } from './lumen-date-input.js';
|
|
|
28
28
|
export { LumenChoice } from './lumen-choice.js';
|
|
29
29
|
export { LumenSecret } from './lumen-secret.js';
|
|
30
30
|
export { RankedBars, StackedBars, StackedColumns } from './lumen-widget-charts.js';
|
|
31
|
+
export { ShareMeter } from './share-meter.js';
|
|
32
|
+
export { UnitValue } from './unit-value.js';
|
|
31
33
|
export { COVERAGE_LEVELS, CoverageCell, CoverageLegend, coverageClass, coverageLabel, coverageLevel, } from './coverage-cells.js';
|
|
32
34
|
export { LumenCoverageMap, blockName, blockOf, } from './coverage-map.js';
|
|
33
35
|
export { LumenCoverageCalendarMap, } from './coverage-calendar-map.js';
|
|
@@ -73,7 +75,7 @@ export { OriginLegend, originsOf } from './origin-legend.js';
|
|
|
73
75
|
export { ConnectOffer, connectionFields, linkGroupId } from './connections.js';
|
|
74
76
|
export { FRESHNESS, FreshnessIcon, FreshnessMark, freshnessLine, freshnessOf, } from './freshness.js';
|
|
75
77
|
export { SourcesLine, useSourceHealth } from './source-health.js';
|
|
76
|
-
export { TILE_WIDTH, Tile, TileChip, TileRow, TileToggleGroup, } from './tile.js';
|
|
78
|
+
export { TILE_WIDTH, Tile, TileChip, TileRow, TileSpark, TileToggleGroup, } from './tile.js';
|
|
77
79
|
export { LumenTokenizedFilter, useLumenFilter, } from './lumen-filter.js';
|
|
78
80
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
79
81
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
package/dist/lumen-echart.js
CHANGED
|
@@ -71,6 +71,24 @@ export function baseOption(t) {
|
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
/** `option` with every `var(--name)` string (also inside `color-mix()`) replaced by its value. */
|
|
75
|
+
function resolveVars(option) {
|
|
76
|
+
const css = getComputedStyle(document.documentElement);
|
|
77
|
+
const walk = (v) => {
|
|
78
|
+
if (typeof v === 'string')
|
|
79
|
+
return v.includes('var(--')
|
|
80
|
+
? v.replace(/var\((--[\w-]+)(?:,\s*([^)]+))?\)/g, (_, name, fb) => {
|
|
81
|
+
return css.getPropertyValue(name).trim() || fb?.trim() || '';
|
|
82
|
+
})
|
|
83
|
+
: v;
|
|
84
|
+
if (Array.isArray(v))
|
|
85
|
+
return v.map(walk);
|
|
86
|
+
if (v && typeof v === 'object' && Object.getPrototypeOf(v) === Object.prototype)
|
|
87
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
|
|
88
|
+
return v;
|
|
89
|
+
};
|
|
90
|
+
return walk(option);
|
|
91
|
+
}
|
|
74
92
|
/** An axis in the theme's colours (merge into xAxis or yAxis). */
|
|
75
93
|
export function axisStyle(t) {
|
|
76
94
|
return {
|
|
@@ -106,10 +124,14 @@ export function LumenEChart({ option, height = 260, on, className, label, }) {
|
|
|
106
124
|
chart.current = null;
|
|
107
125
|
};
|
|
108
126
|
}, []);
|
|
127
|
+
// Canvas can't read CSS variables: `var(--chart-1)` in an option would draw black. Each one is
|
|
128
|
+
// swapped for its value, again whenever the theme flips.
|
|
129
|
+
const tokens = useChartTokens();
|
|
130
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: tokens change when the theme flips.
|
|
109
131
|
useEffect(() => {
|
|
110
132
|
if (ready)
|
|
111
|
-
chart.current?.setOption(option, { notMerge: true });
|
|
112
|
-
}, [ready, option]);
|
|
133
|
+
chart.current?.setOption(resolveVars(option), { notMerge: true });
|
|
134
|
+
}, [ready, option, tokens]);
|
|
113
135
|
const names = Object.keys(on ?? {}).join();
|
|
114
136
|
useEffect(() => {
|
|
115
137
|
const c = chart.current;
|
package/dist/lumen-filter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { CONTAINS_KEY, meetsNumber } from '@columenlabs/lumen-ui-types/views';
|
|
2
|
+
import { CONTAINS_KEY, EMPTY_VALUE, meetsNumber, } from '@columenlabs/lumen-ui-types/views';
|
|
3
3
|
// The one filter box every view uses, redone after p2e's:
|
|
4
4
|
// one chip per field in the sans face, `Account IRA, Roth ×`, included or excluded as a whole
|
|
5
5
|
// (`Symbol not AAPL`). A chip's tap opens its field: Include / Exclude, then its values ticked,
|
|
@@ -8,7 +8,9 @@ import { CONTAINS_KEY, meetsNumber } from '@columenlabs/lumen-ui-types/views';
|
|
|
8
8
|
// fields: all must match (AND). Free text matches the row's text and any field value; picking
|
|
9
9
|
// "Text contains" keeps it as a chip. It filters the rows already loaded, on the client. The box
|
|
10
10
|
// is one row that never grows and always keeps room to type; crowded chips fold to `Sector: 3`,
|
|
11
|
-
// then behind +N. Phones get a bottom sheet.
|
|
11
|
+
// then behind +N. Phones get a bottom sheet. A text field where some rows have no value pins
|
|
12
|
+
// "No <field>" above its values: Include keeps the rows without one, Exclude the rows with any
|
|
13
|
+
// (an `empty` token); typing "no" or "empty" offers it too.
|
|
12
14
|
import { Command as CommandPrimitive } from 'cmdk';
|
|
13
15
|
import { ChevronLeftIcon, ChevronRightIcon, SaveIcon, SearchIcon, TextSearchIcon, XIcon, } from 'lucide-react';
|
|
14
16
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
@@ -24,14 +26,21 @@ import { Popover, PopoverAnchor, PopoverContent } from './ui/popover.js';
|
|
|
24
26
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from './ui/sheet.js';
|
|
25
27
|
import { ToggleGroup, ToggleGroupItem } from './ui/toggle-group.js';
|
|
26
28
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
27
|
-
const OP_SIGN = { gt: '>', lt: '<', eq: '=', between: '' };
|
|
29
|
+
const OP_SIGN = { gt: '>', lt: '<', eq: '=', between: '', empty: '' };
|
|
30
|
+
/** The rows with no value on a field (`not`: with any). */
|
|
31
|
+
const isEmptyToken = (t) => t.op === 'empty';
|
|
32
|
+
const emptyToken = (key, not) => not ? { key, value: EMPTY_VALUE, op: 'empty', not } : { key, value: EMPTY_VALUE, op: 'empty' };
|
|
33
|
+
/** "No label": the empty row's words for a field. */
|
|
34
|
+
const noneOf = (label) => `No ${label.toLowerCase()}`;
|
|
28
35
|
export const tokenText = (t) => `${t.not ? '-' : ''}${t.key}:${t.op ? OP_SIGN[t.op] : ''}${t.value}`;
|
|
29
36
|
/** A number condition as people read it: `> 100`, `0 – 500`. */
|
|
30
37
|
const conditionText = (t) => t.op === 'between' ? t.value.replace('..', ' – ') : `${OP_SIGN[t.op ?? 'eq']} ${t.value}`;
|
|
31
38
|
/** `field:>100`, `field:<0`, `field:=5`, `field:0..500` typed out for a number field. */
|
|
32
39
|
const NUMBER_TYPED = /^(\w+):(>|<|=)?(-?\d+(?:\.\d+)?)(?:\.\.(-?\d+(?:\.\d+)?))?$/;
|
|
33
40
|
/** Same field and value, included or not: a value sits in one chip only. */
|
|
34
|
-
const same = (a, b) => a.key === b.key &&
|
|
41
|
+
const same = (a, b) => a.key === b.key &&
|
|
42
|
+
isEmptyToken(a) === isEmptyToken(b) &&
|
|
43
|
+
a.value.toLowerCase() === b.value.toLowerCase();
|
|
35
44
|
const token = (key, value, not) => not ? { key, value, not } : { key, value };
|
|
36
45
|
/** The `contains` chip's field: free text over the row's own text. */
|
|
37
46
|
const CONTAINS = {
|
|
@@ -55,9 +64,14 @@ function passes(values, nums, tokens, fields, except) {
|
|
|
55
64
|
continue;
|
|
56
65
|
}
|
|
57
66
|
const have = values[f.key] ?? [];
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
67
|
+
// `empty`: no value (included, alongside any values: none or one of these) or any value (excluded).
|
|
68
|
+
const none = of.find((t) => isEmptyToken(t) && !t.not);
|
|
69
|
+
if (of.some((t) => isEmptyToken(t) && t.not) && !have.length)
|
|
70
|
+
return false;
|
|
71
|
+
const plain = of.filter((t) => !isEmptyToken(t));
|
|
72
|
+
const inc = plain.filter((t) => !t.not).map((t) => t.value.toLowerCase());
|
|
73
|
+
const exc = plain.filter((t) => t.not).map((t) => t.value.toLowerCase());
|
|
74
|
+
if ((inc.length || none) && !(none && !have.length) && !have.some((v) => inc.includes(v)))
|
|
61
75
|
return false;
|
|
62
76
|
if (exc.length && have.some((v) => exc.includes(v)))
|
|
63
77
|
return false;
|
|
@@ -92,7 +106,7 @@ text) {
|
|
|
92
106
|
/** A field is included or excluded as a whole: where a field holds both, as
|
|
93
107
|
* filters saved before that do, its included values stay. */
|
|
94
108
|
function onePerField(tokens) {
|
|
95
|
-
const inc = new Set(tokens.filter((t) => !t.not && !t.op).map((t) => t.key));
|
|
109
|
+
const inc = new Set(tokens.filter((t) => !t.not && (!t.op || isEmptyToken(t))).map((t) => t.key));
|
|
96
110
|
return tokens.some((t) => t.not && inc.has(t.key))
|
|
97
111
|
? tokens.filter((t) => !(t.not && inc.has(t.key)))
|
|
98
112
|
: tokens;
|
|
@@ -179,7 +193,12 @@ function chipWords(chip, field, form) {
|
|
|
179
193
|
if (condition)
|
|
180
194
|
return { name, verb: '', shown: conditionText(condition), all: '' };
|
|
181
195
|
const quote = chip.key === CONTAINS_KEY;
|
|
182
|
-
|
|
196
|
+
// An excluded `empty` reads "Label any"; an included one "none" among the values.
|
|
197
|
+
if (chip.not && chip.tokens.length === 1 && chip.tokens[0] && isEmptyToken(chip.tokens[0]))
|
|
198
|
+
return { name, verb: '', shown: 'any', all: 'any' };
|
|
199
|
+
const all = chip.tokens
|
|
200
|
+
.map((t) => (isEmptyToken(t) ? 'none' : quote ? `“${t.value}”` : t.value))
|
|
201
|
+
.join(', ');
|
|
183
202
|
const counted = form === 'count' && chip.tokens.length > 1;
|
|
184
203
|
const verb = chip.not ? (counted ? 'not in:' : 'not') : counted ? ':' : '';
|
|
185
204
|
return { name, verb, shown: counted ? String(chip.tokens.length) : all, all };
|
|
@@ -294,9 +313,20 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
294
313
|
const only = (key, value) => {
|
|
295
314
|
setTokens([...tokens.filter((x) => x.key !== key), token(key, value, notFor(key))]);
|
|
296
315
|
};
|
|
316
|
+
const hasEmpty = (key) => tokens.some((t) => t.key === key && isEmptyToken(t));
|
|
317
|
+
const toggleEmpty = (key, not = notFor(key)) => {
|
|
318
|
+
if (hasEmpty(key))
|
|
319
|
+
setTokens(tokens.filter((t) => !(t.key === key && isEmptyToken(t))));
|
|
320
|
+
else
|
|
321
|
+
setTokens([...tokens, emptyToken(key, not)]);
|
|
322
|
+
if (!field)
|
|
323
|
+
setText('');
|
|
324
|
+
else
|
|
325
|
+
setSearch('');
|
|
326
|
+
};
|
|
297
327
|
const setNot = (key, not) => {
|
|
298
328
|
setPendingNot(not);
|
|
299
|
-
setTokens(tokens.map((t) =>
|
|
329
|
+
setTokens(tokens.map((t) => t.key !== key ? t : isEmptyToken(t) ? emptyToken(key, not) : token(key, t.value, not)));
|
|
300
330
|
};
|
|
301
331
|
// A number field holds one condition: a new one replaces it.
|
|
302
332
|
const setCondition = (key, op, value) => setTokens([...tokens.filter((x) => x.key !== key), { key, value, op }]);
|
|
@@ -335,21 +365,28 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
335
365
|
const dirty = !!onSave && (!!changed || !sameTokens(tokens, saved ?? []));
|
|
336
366
|
// Each field's values with counts. A field counts the rows the other fields' tokens keep, so a
|
|
337
367
|
// chip can take more values of its own field.
|
|
338
|
-
const counts = useMemo(() => {
|
|
339
|
-
const
|
|
368
|
+
const { counts, empties } = useMemo(() => {
|
|
369
|
+
const counts = new Map();
|
|
370
|
+
// Rows with no value on each field, for its "No <field>" row.
|
|
371
|
+
const empties = new Map();
|
|
340
372
|
for (const f of fields) {
|
|
341
373
|
if (f.number)
|
|
342
374
|
continue;
|
|
343
375
|
const m = new Map();
|
|
376
|
+
let none = 0;
|
|
344
377
|
for (const { row, values, nums } of indexed) {
|
|
345
378
|
if (!passes(values, nums, tokens, fields, f.key))
|
|
346
379
|
continue;
|
|
347
|
-
|
|
380
|
+
const vs = new Set(f.values(row));
|
|
381
|
+
if (!vs.size)
|
|
382
|
+
none++;
|
|
383
|
+
for (const v of vs)
|
|
348
384
|
m.set(v, (m.get(v) ?? 0) + 1);
|
|
349
385
|
}
|
|
350
|
-
|
|
386
|
+
counts.set(f.key, m);
|
|
387
|
+
empties.set(f.key, none);
|
|
351
388
|
}
|
|
352
|
-
return
|
|
389
|
+
return { counts, empties };
|
|
353
390
|
}, [fields, indexed, tokens]);
|
|
354
391
|
const picked = (key, value) => tokens.some((t) => same(t, { key, value }));
|
|
355
392
|
/** A field's values matching `n`: picked ones first, then the most common. In the open field
|
|
@@ -358,7 +395,7 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
358
395
|
const first = (v) => (key === field ? pinned.has(v) : picked(key, v));
|
|
359
396
|
const m = new Map(counts.get(key) ?? []);
|
|
360
397
|
for (const t of tokens)
|
|
361
|
-
if (t.key === key && !m.has(t.value))
|
|
398
|
+
if (t.key === key && !isEmptyToken(t) && !m.has(t.value))
|
|
362
399
|
m.set(t.value, 0);
|
|
363
400
|
return [...m]
|
|
364
401
|
.filter(([v]) => !n || v.toLowerCase().includes(n))
|
|
@@ -458,6 +495,13 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
458
495
|
only(key, value);
|
|
459
496
|
}, children: "Only" }), _jsx(CommandShortcut, { className: cn('ml-0 w-8 text-right', shortcut), children: count })] }, `${key}:${value}`));
|
|
460
497
|
};
|
|
498
|
+
// A field's "No <field>" row, pinned above its values: the rows with no value (Exclude: any).
|
|
499
|
+
const emptyRow = (f, include) => {
|
|
500
|
+
const n = empties.get(f.key) ?? 0;
|
|
501
|
+
if (!n && !hasEmpty(f.key))
|
|
502
|
+
return null;
|
|
503
|
+
return (_jsxs(CommandItem, { value: `${f.key}:${EMPTY_VALUE}`, onSelect: () => toggleEmpty(f.key, include ? false : undefined), className: tall, children: [_jsx(Checkbox, { checked: hasEmpty(f.key), tabIndex: -1, "aria-hidden": true, className: "pointer-events-none" }), _jsx("span", { className: "min-w-0 truncate text-muted-foreground italic", children: noneOf(f.label) }), _jsx(CommandShortcut, { className: cn('ml-auto w-8 text-right', shortcut), children: n })] }, `${f.key}:${EMPTY_VALUE}`));
|
|
504
|
+
};
|
|
461
505
|
const summary = (c) => {
|
|
462
506
|
const w = chipWords(c, fieldOf(c.key), 'full');
|
|
463
507
|
return (_jsxs("span", { className: "ml-auto min-w-0 truncate pl-3 text-right", children: [c.not && _jsx("span", { className: "text-destructive", children: "not " }), _jsx("span", { className: "tabular-nums", children: w.shown })] }));
|
|
@@ -470,7 +514,8 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
470
514
|
focus();
|
|
471
515
|
}, children: _jsx(ChevronLeftIcon, {}) }), _jsx(activeField.icon, { className: "size-4 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: activeField.label }), !activeField.number && (_jsxs(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, className: "ml-auto", value: notFor(activeField.key) ? 'exclude' : 'include', onValueChange: (v) => v && setNot(activeField.key, v === 'exclude'), "aria-label": `${activeField.label}: include or exclude`, children: [_jsx(ToggleGroupItem, { value: "include", className: "h-7 px-2 text-xs font-normal", children: "Include" }), _jsx(ToggleGroupItem, { value: "exclude", className: "h-7 px-2 text-xs font-normal", children: "Exclude" })] }))] }), activeField.number && (_jsx(NumberCondition, { label: activeField.label, current: tokens.find((t) => t.key === activeField.key), tall: mobile, count: (op, value) => numberCount(activeField.key, op, value), onSet: (op, value) => setCondition(activeField.key, op, value) }, activeField.key))] }), active === CONTAINS_KEY ? (_jsxs(CommandGroup, { className: "px-1 py-0.5", children: [containsWord && !picked(CONTAINS_KEY, containsWord) && (_jsxs(CommandItem, { value: `${CONTAINS_KEY}:${containsWord}`, onSelect: () => toggle(CONTAINS_KEY, containsWord), className: tall, children: [_jsx(TextSearchIcon, { className: "text-muted-foreground" }), _jsxs("span", { className: "min-w-0 truncate", children: ["\u201C", containsWord, "\u201D"] }), _jsx(CommandShortcut, { className: shortcut, children: wordOf(containsWord, notFor(CONTAINS_KEY)) })] })), tokens
|
|
472
516
|
.filter((t) => t.key === CONTAINS_KEY)
|
|
473
|
-
.map((t) => valueRow(CONTAINS_KEY, t.value, wordOf(t.value, !!t.not)))] })) : (!activeField.number && (
|
|
517
|
+
.map((t) => valueRow(CONTAINS_KEY, t.value, wordOf(t.value, !!t.not)))] })) : (!activeField.number && (_jsxs(CommandGroup, { className: "px-1 py-0.5", children: [(!needle || noneOf(activeField.label).toLowerCase().includes(needle)) &&
|
|
518
|
+
emptyRow(activeField), valuesOf(activeField.key, needle, 200).map(([v, n]) => valueRow(activeField.key, v, n))] }))), _jsx(CommandEmpty, { children: "No matching values." })] })) : q ? (_jsxs(_Fragment, { children: [_jsx(CommandEmpty, { children: "No matching values." }), word && (_jsx(CommandGroup, { className: "px-1 py-0.5", children: _jsxs(CommandItem, { value: `${CONTAINS_KEY}:${word}`, onSelect: () => {
|
|
474
519
|
setTokens([
|
|
475
520
|
...tokens.filter((t) => !same(t, { key: CONTAINS_KEY, value: word })),
|
|
476
521
|
token(CONTAINS_KEY, word, chipOf(CONTAINS_KEY)?.not ?? negate),
|
|
@@ -478,7 +523,14 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
478
523
|
setText('');
|
|
479
524
|
}, className: tall, children: [_jsx(TextSearchIcon, { className: "text-muted-foreground" }), _jsxs("span", { className: "min-w-0 truncate", children: [_jsxs("span", { className: "text-muted-foreground", children: ["Text ", negate ? 'not contains' : 'contains', ' '] }), "\u201C", word, "\u201D"] }), _jsx(CommandShortcut, { className: shortcut, children: wordOf(word, negate) })] }) })), fields.some((f) => f.label.toLowerCase().startsWith(q) || f.key.startsWith(q)) && (_jsx(CommandGroup, { heading: "Fields", className: "px-1 py-0.5", children: fields
|
|
480
525
|
.filter((f) => f.label.toLowerCase().startsWith(q) || f.key.startsWith(q))
|
|
481
|
-
.map((f) => (_jsxs(CommandItem, { value: `field:${f.key}`, onSelect: () => openField(f.key), className: tall, children: [_jsx(f.icon, { className: "text-muted-foreground" }), f.label, _jsx(ChevronRightIcon, { className: "ml-auto text-muted-foreground" })] }, f.key))) })),
|
|
526
|
+
.map((f) => (_jsxs(CommandItem, { value: `field:${f.key}`, onSelect: () => openField(f.key), className: tall, children: [_jsx(f.icon, { className: "text-muted-foreground" }), f.label, _jsx(ChevronRightIcon, { className: "ml-auto text-muted-foreground" })] }, f.key))) })), (q.startsWith('no') || (q.length > 1 && 'empty'.startsWith(q))) &&
|
|
527
|
+
fields.some((f) => !f.number &&
|
|
528
|
+
(empties.get(f.key) ?? 0) > 0 &&
|
|
529
|
+
(q.startsWith('no') ? noneOf(f.label).toLowerCase().startsWith(q) : true)) && (_jsx(CommandGroup, { heading: "Quick filters", className: "px-1 py-0.5", children: fields
|
|
530
|
+
.filter((f) => !f.number &&
|
|
531
|
+
(empties.get(f.key) ?? 0) > 0 &&
|
|
532
|
+
(q.startsWith('no') ? noneOf(f.label).toLowerCase().startsWith(q) : true))
|
|
533
|
+
.map((f) => emptyRow(f, true)) })), fields
|
|
482
534
|
.filter((f) => !f.number)
|
|
483
535
|
.map((f) => {
|
|
484
536
|
const vs = valuesOf(f.key, q, PER_FIELD);
|
package/dist/lumen-links.js
CHANGED
|
@@ -156,7 +156,9 @@ export function followedBy(tokens, fields, keys) {
|
|
|
156
156
|
/** What a card follows, as its title says it: `IRA, Roth`. */
|
|
157
157
|
export function followsText(tokens) {
|
|
158
158
|
return tokens
|
|
159
|
-
.map((t) =>
|
|
159
|
+
.map((t) => t.op === 'empty'
|
|
160
|
+
? `${t.key} ${t.not ? 'any' : 'none'}`
|
|
161
|
+
: `${t.not ? 'not ' : ''}${t.op ? `${t.key} ${t.value.replace('..', '–')}` : (t.text ?? t.value)}`)
|
|
160
162
|
.join(', ');
|
|
161
163
|
}
|
|
162
164
|
/** The square itself: filled in its colour, or an outline for none. */
|
package/dist/lumen-selector.js
CHANGED
|
@@ -11,6 +11,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
11
11
|
// - Triggers: `field` (a form select), `chip` (a compact header pill), `icon` (an icon button).
|
|
12
12
|
// `LumenSelectorList` is the fourth, `none`: the list anchored to a text box (the assistant @ and /).
|
|
13
13
|
// - Phone: a bottom sheet with the search on top and, for multiple, Done.
|
|
14
|
+
// - Desktop, multiple: a footer with the count, Clear all and Done (picks save as they're made).
|
|
14
15
|
// - An option with a `target` (a card or a sidebar page) lights it on the page while hovered.
|
|
15
16
|
import { Combobox as Base } from '@base-ui/react';
|
|
16
17
|
import { CheckIcon, ChevronDownIcon, PlusIcon, SearchIcon, XIcon } from 'lucide-react';
|
|
@@ -255,7 +256,7 @@ export function LumenSelector(props) {
|
|
|
255
256
|
// No ring on the first button: the person taps what they want.
|
|
256
257
|
onOpenAutoFocus: (e) => e.preventDefault(), className: "group/selector flex max-h-[85dvh] flex-col gap-2 rounded-t-xl pt-2", children: [_jsx("span", { "aria-hidden": true, className: "mx-auto h-1 w-9 rounded-full bg-border" }), _jsxs("div", { className: "flex items-center justify-between px-4", children: [_jsx(SheetTitle, { id: sheetTitle, children: label ?? placeholder }), multiple && (_jsx(Button, { size: "sm", onClick: () => setOpen(false), children: "Done" }))] }), _jsx(SheetDescription, { className: "sr-only", children: multiple ? 'Pick any, then Done' : 'Pick one' }), _jsxs(Base.Root, { ...rootProps, inline: true, open: true, onOpenChange: () => undefined, multiple: multiple, value: multiple ? [...values] : (values[0] ?? null), onValueChange: (v) => pick(v), children: [multiple && values.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5 px-4", children: values.map((v) => (_jsx("span", { className: "rounded-sm bg-muted px-2 py-1 text-[13px]", children: labelOf(v) }, v))) })), searchable && searchBox, list] }), multiple && (_jsxs("div", { className: "flex items-center justify-between border-t px-4 pt-3 pb-5", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [values.length, " picked"] }), clearAll] }))] }) })] }));
|
|
257
258
|
}
|
|
258
|
-
const popup = (_jsx(Base.Portal, { container: host ?? undefined, children: _jsx(Base.Positioner, { side: "bottom", sideOffset: 4, align: align, anchor: inChips ? chipsRef : undefined, className: "pointer-events-auto isolate z-[60]", children: _jsxs(Base.Popup, { "data-slot": "lumen-selector", className: "group/selector max-w-(--available-width) min-w-[max(var(--anchor-width),14rem)] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", children: [!inChips && searchBox, list,
|
|
259
|
+
const popup = (_jsx(Base.Portal, { container: host ?? undefined, children: _jsx(Base.Positioner, { side: "bottom", sideOffset: 4, align: align, anchor: inChips ? chipsRef : undefined, className: "pointer-events-auto isolate z-[60]", children: _jsxs(Base.Popup, { "data-slot": "lumen-selector", className: "group/selector max-w-(--available-width) min-w-[max(var(--anchor-width),14rem)] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95", children: [!inChips && searchBox, list, multiple && (_jsxs("div", { className: "flex items-center gap-3 border-t px-2 py-1.5", children: [_jsxs("span", { className: "text-xs text-muted-foreground", children: [values.length, " picked", max !== undefined ? ` of ${max}` : ''] }), clearAll, _jsx(Button, { size: "xs", className: "ml-auto", onClick: () => setOpen(false), children: "Done" })] }))] }) }) }));
|
|
259
260
|
return (_jsxs(Base.Root, { ...rootProps, multiple: multiple, value: multiple ? [...values] : (values[0] ?? null), onValueChange: (v) => pick(v), children: [inChips ? (_jsxs(Base.Chips, { ref: chipsRef, className: cn('flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent px-1 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 dark:bg-input/30', className), children: [chips, _jsx(Base.Input, { id: id, "aria-label": label, disabled: disabled, readOnly: !searchable, placeholder: values.length ? '' : placeholder, className: "min-w-16 flex-1 bg-transparent px-1 outline-none placeholder:text-muted-foreground" })] })) : trigger === 'icon' ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Base.Trigger, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": label, className: className }), children: LeadIcon && _jsx(LeadIcon, {}) }) }), _jsx(TooltipContent, { children: label })] })) : (_jsx(Base.Trigger, { id: id, "data-size": size, "aria-label": label, className: cn(FIELD, trigger === 'chip' && 'rounded-full', className), children: fieldInside })), _jsx("span", { ref: markRef, hidden: true }), popup] }));
|
|
260
261
|
}
|
|
261
262
|
/**
|
package/dist/lumen-view.js
CHANGED
|
@@ -445,7 +445,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
445
445
|
const edit = f.edit;
|
|
446
446
|
const many = edit.many;
|
|
447
447
|
if (many)
|
|
448
|
-
return (_jsx(EditManyCell, { label: f.label, values: f.values?.(row) ?? (typeof v === 'string' && v !== '' ? [v] : []), options: options.get(f.key)?.inUse ?? [], unused: options.get(f.key)?.unused, note: edit.note?.(row), colorOf: f.color, onSave: (values) => many(row, values) }));
|
|
448
|
+
return (_jsx(EditManyCell, { label: f.label, values: f.values?.(row) ?? (typeof v === 'string' && v !== '' ? [v] : []), options: options.get(f.key)?.inUse ?? [], unused: options.get(f.key)?.unused, note: edit.note?.(row), colorOf: f.color, iconOf: f.iconOf, onSave: (values) => many(row, values) }));
|
|
449
449
|
const save = edit.save;
|
|
450
450
|
if (!save)
|
|
451
451
|
return _jsx(ViewValueCell, { value: v, format: f.format, chip: chip });
|
|
@@ -555,7 +555,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
555
555
|
const lead = Math.max(1, shown.findIndex((f, i) => i > 0 && rolls(f)) < 0
|
|
556
556
|
? shown.length
|
|
557
557
|
: shown.findIndex((f, i) => i > 0 && rolls(f)));
|
|
558
|
-
const rollCell = (f, 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;
|
|
558
|
+
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;
|
|
559
559
|
const rollCells = (members) => shown.map((f) => (_jsx(TableCell, { className: colOf(f), children: rollCell(f, members) }, f.key)));
|
|
560
560
|
// A row's chevron opens one level under it, closed by default: its panel, else the view's
|
|
561
561
|
// sub-group ("Each row opens"; None opens nothing).
|
|
@@ -42,7 +42,8 @@ export function StackedColumns({ rows, x, parts, label, height = 220, tick = Str
|
|
|
42
42
|
const total = (r) => parts.reduce((n, p) => n + (Number(r[p.key]) || 0), 0);
|
|
43
43
|
// The highest part with a value carries the column's rounded cap.
|
|
44
44
|
const top = (r) => parts.findLast((p) => Number(r[p.key]) > 0)?.key;
|
|
45
|
-
|
|
45
|
+
const ticks = niceTicks(Math.max(0, ...rows.map(total)));
|
|
46
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ChartContainer, { config: config, role: "img", "aria-label": label, className: "aspect-auto w-full", style: { height }, children: _jsxs(BarChart, { data: rows, margin: { left: 0, right: 0, top: 8, bottom: 0 }, children: [_jsx(CartesianGrid, { vertical: false, strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: x, tickLine: false, axisLine: false, tickMargin: 8, minTickGap: 12, tickFormatter: (v) => tick(String(v)) }), _jsx(YAxis, { tickLine: false, axisLine: false, domain: [0, ticks.at(-1) ?? 'auto'], ticks: ticks, width: axisWidth(ticks.map(fmt)), tickFormatter: (v) => fmt(Number(v)) }), _jsx(ChartTooltip, { cursor: { fill: 'var(--muted)', opacity: 0.5 }, content: _jsx(ChartTooltipContent, { labelFormatter: (_, payload) => {
|
|
46
47
|
const r = payload?.[0]?.payload;
|
|
47
48
|
return r ? (_jsxs("span", { className: "flex w-full gap-3", children: [_jsx("span", { children: tick(String(r[x])) }), _jsx("span", { className: "num ml-auto", children: fmt(total(r)) })] })) : null;
|
|
48
49
|
}, formatter: (v, name) => (_jsxs("span", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "size-2 shrink-0 rounded-sm", style: { background: config[String(name)]?.color } }), _jsx("span", { children: config[String(name)]?.label }), _jsx("span", { className: "num ml-auto pl-3", children: fmt(Number(v)) })] })) }) }), parts.map((p) => (_jsx(Bar, { dataKey: p.key, stackId: "a", fill: p.fill, isAnimationActive: false, shape: (b) => (_jsx(Rectangle, { ...b, radius: top(b.payload) === p.key ? CAP : 0 })) }, p.key)))] }) }), legend && (_jsx("ul", { className: "flex flex-wrap justify-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: parts.map((p) => (_jsxs("li", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "size-2 rounded-sm", style: { background: p.fill } }), p.name] }, p.key))) }))] }));
|
|
@@ -84,3 +85,18 @@ export function AllocationBar({ items, total, config, tooltip, share, }) {
|
|
|
84
85
|
return (_jsxs("text", { x: Number(p.x) + 8, y: Number(p.y) + Number(p.height) / 2, dy: "0.35em", fontSize: 11, fontWeight: 600, style: { fill: 'var(--background)' }, children: [name, _jsx("tspan", { fontWeight: 400, dx: 6, className: "num", children: share(s.v) })] }));
|
|
85
86
|
} }) }, s.key)))] }) }));
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Round axis steps (1, 2, 2.5 or 5 times a power of ten) from 0 to just above `max`, about four
|
|
90
|
+
* of them, so a value axis reads 0, 100k, 200k, 300k rather than 95k, 190k, 285k.
|
|
91
|
+
*/
|
|
92
|
+
function niceTicks(max, count = 4) {
|
|
93
|
+
if (!(max > 0))
|
|
94
|
+
return [0, 1];
|
|
95
|
+
const raw = max / count;
|
|
96
|
+
const pow = 10 ** Math.floor(Math.log10(raw));
|
|
97
|
+
const step = ([1, 2, 2.5, 5, 10].find((m) => m * pow >= raw) ?? 10) * pow;
|
|
98
|
+
const n = Math.ceil(max / step - 1e-9);
|
|
99
|
+
return Array.from({ length: n + 1 }, (_, i) => Math.round(i * step * 1e6) / 1e6);
|
|
100
|
+
}
|
|
101
|
+
/** Room for the widest tick label (about 7px a character at text-xs), so none is clipped. */
|
|
102
|
+
const axisWidth = (labels) => Math.max(24, Math.max(0, ...labels.map((l) => l.length)) * 7 + 8);
|
package/dist/lumen-widget.d.ts
CHANGED
|
@@ -174,7 +174,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
174
174
|
key: string;
|
|
175
175
|
value: string;
|
|
176
176
|
not?: boolean | undefined;
|
|
177
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
177
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
178
178
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
179
179
|
}[];
|
|
180
180
|
source?: string | undefined;
|
|
@@ -199,6 +199,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
199
199
|
}[];
|
|
200
200
|
formats?: Record<string, "compact" | "integer" | "number" | "percent" | "price" | "price_rounded" | "price_signed" | "rounded" | "signed" | "weight"> | undefined;
|
|
201
201
|
aggs?: Record<string, "count" | "distinct" | "first" | "last" | "max" | "mean" | "min" | "none" | "sum"> | undefined;
|
|
202
|
+
shows?: Record<string, string> | undefined;
|
|
202
203
|
group: {
|
|
203
204
|
fields: string[];
|
|
204
205
|
default: string | null;
|
|
@@ -237,7 +238,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
237
238
|
key: string;
|
|
238
239
|
value: string;
|
|
239
240
|
not?: boolean | undefined;
|
|
240
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
241
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
241
242
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
242
243
|
}[];
|
|
243
244
|
source?: string | undefined;
|
|
@@ -272,7 +273,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
272
273
|
key: string;
|
|
273
274
|
value: string;
|
|
274
275
|
not?: boolean | undefined;
|
|
275
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
276
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
276
277
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
277
278
|
}[];
|
|
278
279
|
source?: string | undefined;
|
|
@@ -297,7 +298,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
297
298
|
key: string;
|
|
298
299
|
value: string;
|
|
299
300
|
not?: boolean | undefined;
|
|
300
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
301
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
301
302
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
302
303
|
}[];
|
|
303
304
|
sign: boolean;
|
|
@@ -310,7 +311,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
310
311
|
key: string;
|
|
311
312
|
value: string;
|
|
312
313
|
not?: boolean | undefined;
|
|
313
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
314
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
314
315
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
315
316
|
}[];
|
|
316
317
|
suffix: string;
|
|
@@ -348,7 +349,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
348
349
|
key: string;
|
|
349
350
|
value: string;
|
|
350
351
|
not?: boolean | undefined;
|
|
351
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
352
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
352
353
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
353
354
|
}[];
|
|
354
355
|
source?: string | undefined;
|
|
@@ -372,7 +373,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
372
373
|
key: string;
|
|
373
374
|
value: string;
|
|
374
375
|
not?: boolean | undefined;
|
|
375
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
376
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
376
377
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
377
378
|
}[];
|
|
378
379
|
sign: boolean;
|
|
@@ -385,7 +386,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
385
386
|
key: string;
|
|
386
387
|
value: string;
|
|
387
388
|
not?: boolean | undefined;
|
|
388
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
389
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
389
390
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
390
391
|
}[];
|
|
391
392
|
suffix: string;
|
|
@@ -418,7 +419,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
418
419
|
key: string;
|
|
419
420
|
value: string;
|
|
420
421
|
not?: boolean | undefined;
|
|
421
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
422
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
422
423
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
423
424
|
}[];
|
|
424
425
|
source?: string | undefined;
|
|
@@ -455,7 +456,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
455
456
|
key: string;
|
|
456
457
|
value: string;
|
|
457
458
|
not?: boolean | undefined;
|
|
458
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
459
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
459
460
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
460
461
|
}[];
|
|
461
462
|
source?: string | undefined;
|
|
@@ -490,7 +491,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
490
491
|
key: string;
|
|
491
492
|
value: string;
|
|
492
493
|
not?: boolean | undefined;
|
|
493
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
494
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
494
495
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
495
496
|
}[];
|
|
496
497
|
source?: string | undefined;
|
|
@@ -525,7 +526,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
525
526
|
key: string;
|
|
526
527
|
value: string;
|
|
527
528
|
not?: boolean | undefined;
|
|
528
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
529
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
529
530
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
530
531
|
}[];
|
|
531
532
|
source?: string | undefined;
|
|
@@ -563,7 +564,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
563
564
|
key: string;
|
|
564
565
|
value: string;
|
|
565
566
|
not?: boolean | undefined;
|
|
566
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
567
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
567
568
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
568
569
|
}[];
|
|
569
570
|
source?: string | undefined;
|
|
@@ -598,7 +599,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
598
599
|
key: string;
|
|
599
600
|
value: string;
|
|
600
601
|
not?: boolean | undefined;
|
|
601
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
602
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
602
603
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
603
604
|
}[];
|
|
604
605
|
source?: string | undefined;
|
|
@@ -631,7 +632,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
631
632
|
key: string;
|
|
632
633
|
value: string;
|
|
633
634
|
not?: boolean | undefined;
|
|
634
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
635
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
635
636
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
636
637
|
}[];
|
|
637
638
|
source?: string | undefined;
|
|
@@ -664,7 +665,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
664
665
|
key: string;
|
|
665
666
|
value: string;
|
|
666
667
|
not?: boolean | undefined;
|
|
667
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
668
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
668
669
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
669
670
|
}[];
|
|
670
671
|
source?: string | undefined;
|
|
@@ -701,7 +702,7 @@ export declare function cardsAfter(cards: ViewCard[], i: number, action: CardAct
|
|
|
701
702
|
key: string;
|
|
702
703
|
value: string;
|
|
703
704
|
not?: boolean | undefined;
|
|
704
|
-
op?: "between" | "eq" | "gt" | "lt" | undefined;
|
|
705
|
+
op?: "between" | "empty" | "eq" | "gt" | "lt" | undefined;
|
|
705
706
|
link?: "amber" | "blue" | "lime" | "pink" | "teal" | undefined;
|
|
706
707
|
}[];
|
|
707
708
|
source?: string | undefined;
|
package/dist/nav.d.ts
CHANGED
|
@@ -50,8 +50,12 @@ export interface SectionSubItem {
|
|
|
50
50
|
id: string;
|
|
51
51
|
title: string;
|
|
52
52
|
to: string;
|
|
53
|
-
/**
|
|
53
|
+
/** Its icon, drawn in `color` (a label's icon in the label's colour). */
|
|
54
|
+
icon?: NavIcon;
|
|
55
|
+
/** The icon's colour; without an icon, a colour dot before the title (a chart line's colour). */
|
|
54
56
|
color?: string;
|
|
57
|
+
/** A count on the right, lined up with the section items' badges. */
|
|
58
|
+
badge?: ReactNode;
|
|
55
59
|
}
|
|
56
60
|
/**
|
|
57
61
|
* A section of things a person keeps (views, trades, a watchlist): `+` adds one, the
|