@columenlabs/lumen-ui 0.1.161 → 0.1.162
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/hooks/use-tell.d.ts +8 -0
- package/dist/hooks/use-tell.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lumen-filter-panel.js +1 -1
- package/dist/lumen-filter.d.ts +7 -3
- package/dist/lumen-filter.js +6 -5
- package/dist/lumen-layout.js +4 -3
- package/dist/lumen-view.js +2 -2
- package/package.json +2 -2
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells `tell` a value each time the value itself changes, never because `tell` is a new function.
|
|
3
|
+
* The view hears its sort, chips and grouping through these: an app's inline callback (a new
|
|
4
|
+
* function every render) used to re-run the effect, set the app's state, render, and so on without
|
|
5
|
+
* end, asking the server for the same page each time. The value is compared by its JSON, so an
|
|
6
|
+
* equal value in a new object is not a change either. A `tell` that appears later hears the value.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useTell<V>(value: V, tell: ((value: V) => void) | undefined): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Tells `tell` a value each time the value itself changes, never because `tell` is a new function.
|
|
4
|
+
* The view hears its sort, chips and grouping through these: an app's inline callback (a new
|
|
5
|
+
* function every render) used to re-run the effect, set the app's state, render, and so on without
|
|
6
|
+
* end, asking the server for the same page each time. The value is compared by its JSON, so an
|
|
7
|
+
* equal value in a new object is not a change either. A `tell` that appears later hears the value.
|
|
8
|
+
*/
|
|
9
|
+
export function useTell(value, tell) {
|
|
10
|
+
const tellRef = React.useRef(tell);
|
|
11
|
+
tellRef.current = tell;
|
|
12
|
+
const valueRef = React.useRef(value);
|
|
13
|
+
valueRef.current = value;
|
|
14
|
+
const sent = React.useRef(null);
|
|
15
|
+
const key = JSON.stringify(value ?? null);
|
|
16
|
+
const has = !!tell;
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const mark = has ? key : null;
|
|
19
|
+
if (mark === null || sent.current === mark)
|
|
20
|
+
return;
|
|
21
|
+
sent.current = mark;
|
|
22
|
+
tellRef.current?.(valueRef.current);
|
|
23
|
+
}, [key, has]);
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,7 @@ export { TILE_WIDTH, Tile, TileChip, type TileOption, TileRow, TileSpark, TileTo
|
|
|
95
95
|
export { type FacetCounts, type FilterField, type FilterToken, LumenTokenizedFilter, type LumenFilterState, countsShown, useFacetCounts, useLumenFilter, } from './lumen-filter.js';
|
|
96
96
|
export { LumenFilterPanel, LumenFilterPanelButton, type LumenFilterPanelProps, useFilterPanel, } from './lumen-filter-panel.js';
|
|
97
97
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
98
|
+
export { useTell } from './hooks/use-tell.js';
|
|
98
99
|
export { useBackClose } from './hooks/use-back-close.js';
|
|
99
100
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
100
101
|
export { type AppViewState, useAppView } from './app-views.js';
|
package/dist/index.js
CHANGED
|
@@ -102,6 +102,7 @@ export { TILE_WIDTH, Tile, TileChip, TileRow, TileSpark, TileToggleGroup, } from
|
|
|
102
102
|
export { LumenTokenizedFilter, countsShown, useFacetCounts, useLumenFilter, } from './lumen-filter.js';
|
|
103
103
|
export { LumenFilterPanel, LumenFilterPanelButton, useFilterPanel, } from './lumen-filter-panel.js';
|
|
104
104
|
export { useIsMobile } from './hooks/use-mobile.js';
|
|
105
|
+
export { useTell } from './hooks/use-tell.js';
|
|
105
106
|
export { useBackClose } from './hooks/use-back-close.js';
|
|
106
107
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
107
108
|
export { useAppView } from './app-views.js';
|
|
@@ -89,7 +89,7 @@ export function LumenFilterPanel({ filter, open, onOpenChange, tips, order, titl
|
|
|
89
89
|
/** One field: its values ticked in place, or a number field's range. */
|
|
90
90
|
function Section({ field, filter, facets: { counts, empties }, tip, }) {
|
|
91
91
|
const { tokens, setTokens } = filter;
|
|
92
|
-
const shown = countsShown(filter);
|
|
92
|
+
const shown = countsShown(filter, field.key);
|
|
93
93
|
const mine = tokens.filter((t) => t.key === field.key);
|
|
94
94
|
const not = mine.some((t) => t.not);
|
|
95
95
|
const [more, setMore] = useState(false);
|
package/dist/lumen-filter.d.ts
CHANGED
|
@@ -36,10 +36,13 @@ export declare function filterByTokens<T>(rows: T[], fields: FilterField<T>[], t
|
|
|
36
36
|
/** The row's text, for `contains` tokens. */
|
|
37
37
|
text?: (row: T) => string[]): T[];
|
|
38
38
|
/** A server's counts per field: each value's rows, and the rows with no value (`none`), over the
|
|
39
|
-
* rows every other field's tokens keep.
|
|
39
|
+
* rows every other field's tokens keep. `counted: false` gives a field's values to pick without
|
|
40
|
+
* counts, for when the server could not count them without reading every row: the box and the
|
|
41
|
+
* panel list the values and show no numbers. */
|
|
40
42
|
export type FacetCounts = Record<string, {
|
|
41
43
|
values: Record<string, number>;
|
|
42
44
|
none?: number;
|
|
45
|
+
counted?: boolean;
|
|
43
46
|
}>;
|
|
44
47
|
/**
|
|
45
48
|
* Each field's values with their counts, shared by the filter box and the filter panel. A field
|
|
@@ -50,11 +53,12 @@ export declare function useFacetCounts<T>(filter: LumenFilterState<T>): {
|
|
|
50
53
|
counts: Map<string, Map<string, number>>;
|
|
51
54
|
empties: Map<string, number>;
|
|
52
55
|
};
|
|
53
|
-
/** Whether a count means something: over a server's page only when the server counted
|
|
56
|
+
/** Whether a count means something: over a server's page only when the server counted (that
|
|
57
|
+
* field, when `key` is given). */
|
|
54
58
|
export declare const countsShown: (filter: {
|
|
55
59
|
partial: boolean;
|
|
56
60
|
facets: FacetCounts | null;
|
|
57
|
-
}) => boolean;
|
|
61
|
+
}, key?: string) => boolean;
|
|
58
62
|
/** Keeps the tokens and text, and filters `rows` by them. */
|
|
59
63
|
export declare function useLumenFilter<T>(rows: T[], fields: FilterField<T>[], { text: textOf, initial, partial, facets, }?: {
|
|
60
64
|
/** Free-text haystack per row (a name, an id), besides the field values. */
|
package/dist/lumen-filter.js
CHANGED
|
@@ -149,8 +149,9 @@ export function useFacetCounts(filter) {
|
|
|
149
149
|
return { counts, empties };
|
|
150
150
|
}, [fields, indexed, tokens, facets]);
|
|
151
151
|
}
|
|
152
|
-
/** Whether a count means something: over a server's page only when the server counted
|
|
153
|
-
|
|
152
|
+
/** Whether a count means something: over a server's page only when the server counted (that
|
|
153
|
+
* field, when `key` is given). */
|
|
154
|
+
export const countsShown = (filter, key) => (filter.facets ? key === undefined || filter.facets[key]?.counted !== false : !filter.partial);
|
|
154
155
|
/** Keeps the tokens and text, and filters `rows` by them. */
|
|
155
156
|
export function useLumenFilter(rows, fields, { text: textOf, initial = [], partial = false, facets, } = {}) {
|
|
156
157
|
const [tokens, setRaw] = useState(() => onePerField(initial));
|
|
@@ -312,7 +313,7 @@ function NumberCondition({ label, current, tall, count, onSet, }) {
|
|
|
312
313
|
export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label = 'Filter', className, saved, onSave, changed, link: given, }) {
|
|
313
314
|
const { fields, indexed, tokens, setTokens, text, setText, hasText } = filter;
|
|
314
315
|
// Over one page of a server's rows a count is not the real one: show none, unless the server counted.
|
|
315
|
-
const tally = (n) => (countsShown(filter) ? n : '');
|
|
316
|
+
const tally = (n, key) => (countsShown(filter, key) ? n : '');
|
|
316
317
|
// Text counts are over the rows loaded: none over a server's page.
|
|
317
318
|
const pageTally = (n) => (filter.partial ? '' : n);
|
|
318
319
|
// A box its card passes no link takes the card's own (every card carries a square).
|
|
@@ -530,14 +531,14 @@ export function LumenTokenizedFilter({ filter, placeholder = 'Filter…', label
|
|
|
530
531
|
return (_jsxs(CommandItem, { value: `${key}:${value}`, onSelect: () => toggle(key, value), className: tall, children: [_jsx(Checkbox, { checked: on, tabIndex: -1, "aria-hidden": true, className: "pointer-events-none" }), prefix && _jsx("span", { className: "shrink-0 text-muted-foreground", children: prefix }), _jsx("span", { className: "min-w-0 truncate", children: value }), _jsx(Button, { variant: "ghost", size: "xs", className: "-my-1 ml-auto h-6 px-1.5 font-normal text-muted-foreground opacity-0 group-data-selected/command-item:opacity-100 max-md:opacity-100", "aria-label": `Only ${value}`, onMouseDown: (e) => e.preventDefault(), onClick: (e) => {
|
|
531
532
|
e.stopPropagation();
|
|
532
533
|
only(key, value);
|
|
533
|
-
}, children: "Only" }), _jsx(CommandShortcut, { className: cn('ml-0 w-8 text-right', shortcut), children: tally(count) })] }, `${key}:${value}`));
|
|
534
|
+
}, children: "Only" }), _jsx(CommandShortcut, { className: cn('ml-0 w-8 text-right', shortcut), children: tally(count, key) })] }, `${key}:${value}`));
|
|
534
535
|
};
|
|
535
536
|
// A field's "No <field>" row, pinned above its values: the rows with no value (Exclude: any).
|
|
536
537
|
const emptyRow = (f, include) => {
|
|
537
538
|
const n = empties.get(f.key) ?? 0;
|
|
538
539
|
if (!n && !hasEmpty(f.key))
|
|
539
540
|
return null;
|
|
540
|
-
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: tally(n) })] }, `${f.key}:${EMPTY_VALUE}`));
|
|
541
|
+
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: tally(n, f.key) })] }, `${f.key}:${EMPTY_VALUE}`));
|
|
541
542
|
};
|
|
542
543
|
const summary = (c) => {
|
|
543
544
|
const w = chipWords(c, fieldOf(c.key), 'full');
|
package/dist/lumen-layout.js
CHANGED
|
@@ -19,6 +19,7 @@ import { ConnectOffer } from './connections.js';
|
|
|
19
19
|
import { usePageKey } from './fold.js';
|
|
20
20
|
import { useGroup } from './group-button.js';
|
|
21
21
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
22
|
+
import { useTell } from './hooks/use-tell.js';
|
|
22
23
|
import { SourceStatusItems, useJoins } from './joins.js';
|
|
23
24
|
import { useYourData } from './joins.js';
|
|
24
25
|
import { lazyPart } from './lazy-part.js';
|
|
@@ -249,7 +250,8 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
249
250
|
savedFiltersRef.current = savedFilters;
|
|
250
251
|
setTokens(config.filters.default);
|
|
251
252
|
}, [savedFilters, config.filters.default, setTokens]);
|
|
252
|
-
|
|
253
|
+
// Told through a ref, so an app's inline callback never loops (useTell).
|
|
254
|
+
useTell(filter.tokens, onTokens);
|
|
253
255
|
const group = useGroup(domain, config);
|
|
254
256
|
// The sort the person set by a header click, held like the chips and grouping (rule C).
|
|
255
257
|
const [heldSort, setHeldSort] = useState(config.sort);
|
|
@@ -262,8 +264,7 @@ function ViewLayout({ crumbs, actions, meta, status, intro, cards: extra, lead,
|
|
|
262
264
|
setHeldSort(config.sort);
|
|
263
265
|
}, [savedSort, config.sort]);
|
|
264
266
|
// A server's page sorts on the server: it hears each sort held.
|
|
265
|
-
|
|
266
|
-
useEffect(() => onServerSort?.(heldSort ?? null), [onServerSort, heldSort]);
|
|
267
|
+
useTell(heldSort ?? null, paging?.onSort);
|
|
267
268
|
const [details, setDetails] = useState(null);
|
|
268
269
|
const onDetails = onOpen ?? (domain.details?.length ? setDetails : undefined);
|
|
269
270
|
const openKey = onOpen ? (openedKey ?? null) : details === null ? null : domain.rowKey(details);
|
package/dist/lumen-view.js
CHANGED
|
@@ -15,6 +15,7 @@ import { DrawerSection } from './drawer-section.js';
|
|
|
15
15
|
import { useFloatingHead } from './floating-head.js';
|
|
16
16
|
import { FreshnessMark, freshnessLine, freshnessOf } from './freshness.js';
|
|
17
17
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
18
|
+
import { useTell } from './hooks/use-tell.js';
|
|
18
19
|
import { IconButton } from './icon-button.js';
|
|
19
20
|
import { lazyPart } from './lazy-part.js';
|
|
20
21
|
import { cn } from './lib/utils.js';
|
|
@@ -236,8 +237,7 @@ export function LumenView({ domain, config, rows, total = rows.length, paging: s
|
|
|
236
237
|
// server groups it.
|
|
237
238
|
const groupBy = server && !server.groups ? null : pickedGroup;
|
|
238
239
|
const served = server?.groups && groupBy ? server.groups : null;
|
|
239
|
-
|
|
240
|
-
useEffect(() => onGroup?.(groupBy?.key ?? null), [onGroup, groupBy?.key]);
|
|
240
|
+
useTell(groupBy?.key ?? null, server?.groups?.onGroup);
|
|
241
241
|
const pivot = server ? false : pickedPivot;
|
|
242
242
|
// Live columns on screen keep their source reading while the market trades (#861).
|
|
243
243
|
const load = useLiveLoad(sourceLoad, columns.some((c) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.162",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.162",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|