@columenlabs/lumen-ui 0.1.114 → 0.1.116
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 +1 -1
- package/dist/assistant-panel.d.ts +2 -6
- package/dist/assistant-panel.js +4 -84
- package/dist/lumen-drawer.d.ts +9 -2
- package/dist/lumen-drawer.js +68 -6
- package/dist/lumen-view.js +6 -5
- package/dist/shell.js +3 -1
- package/dist/side-dock.d.ts +26 -0
- package/dist/side-dock.js +94 -0
- package/dist/theme.css +19 -50
- package/dist/ui/overlay-guard.d.ts +2 -0
- package/dist/ui/overlay-guard.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ Every export, its props and an example are on the [docs](https://lumen.columenla
|
|
|
78
78
|
- Numbers use tabular figures and a true minus `−`; a gain or loss always shows a sign or arrow.
|
|
79
79
|
- 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.
|
|
80
80
|
- Column widths: the first column (a row's name) grows only to `--lead-col-max` (18rem, theme.css); past it the cell truncates on one line and a plain text value shows in full on hover (a rendered cell brings its own hover card). Cap any other column with the field's `maxWidth` (`'12rem'`), or lift the cap with `maxWidth: false`. In a rendered name cell, put the name in a `truncate` span so the ellipsis lands on it, not on a chip after it.
|
|
81
|
-
-
|
|
81
|
+
- Rows never wrap. Every row (data, group, sub-group, Total) reads on one line at every width. On a phone each row is a one-line card: its name (ellipsis, full name on hover) and one value on the right, the sorted column, else the first number or date. The other values are in the row's drawer or panel. Pick which value a phone shows by sorting (Sort by) or by marking the others `hideOnPhone`.
|
|
82
82
|
- Column alignment is the table's, not the cell's. Text left, numbers right, and a number column has one right edge for its header, values, dashes and Total. `UnitValue` puts the unit in a fixed slot after the number (`--unit-slot`, 2rem) and its status dot just left of the number. In a LumenView table the first `UnitValue` drawn in a column marks it (`data-units` on every cell of that column, through the `UnitColumns` context and the cell's `data-col`); theme.css then widens that column's right gutter by the slot and hangs each unit in it. So a plain `—`, `Free` or `$0.21` an app draws in the same column sits on the digits' edge, and the header sits over the digits. Don't hand-place units, dots or padding to line a column up; if a column is off, draw its numbers through `UnitValue`. Outside a LumenView table (a hover card, a tile) `UnitValue` keeps its slot inline.
|
|
83
83
|
- 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.
|
|
84
84
|
- 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.
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
write: (next: T) => void;
|
|
5
|
-
use: () => T;
|
|
6
|
-
get: () => T;
|
|
7
|
-
};
|
|
2
|
+
import { tabState } from './side-dock.js';
|
|
3
|
+
export { tabState };
|
|
8
4
|
/** Whether the assistant is open in this tab. */
|
|
9
5
|
export declare const assistantOpen: {
|
|
10
6
|
write: (next: boolean) => void;
|
package/dist/assistant-panel.js
CHANGED
|
@@ -6,44 +6,18 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
6
6
|
// Open or closed and the width are each browser tab's own (sessionStorage). The
|
|
7
7
|
// product fills the body (the product's assistant: its chat, composer, cards and spend).
|
|
8
8
|
import { HistoryIcon, SparklesIcon } from 'lucide-react';
|
|
9
|
-
import { useEffect, useRef, useState
|
|
9
|
+
import { useEffect, useRef, useState } from 'react';
|
|
10
10
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
11
11
|
import { cn } from './lib/utils.js';
|
|
12
12
|
import { LumenBoundary } from './lumen-boundary.js';
|
|
13
13
|
import { LumenSelector } from './lumen-selector.js';
|
|
14
14
|
import { ASSISTANT_WIDTH, usePrefs } from './prefs.js';
|
|
15
|
+
import { ResizeEdge, tabState } from './side-dock.js';
|
|
15
16
|
import { Button } from './ui/button.js';
|
|
16
17
|
import { Kbd } from './ui/kbd.js';
|
|
17
18
|
import { Sheet, SheetContent, SheetDescription, SheetTitle } from './ui/sheet.js';
|
|
18
19
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
19
|
-
|
|
20
|
-
export function tabState(key, fallback, parse) {
|
|
21
|
-
const listeners = new Set();
|
|
22
|
-
const read = () => {
|
|
23
|
-
try {
|
|
24
|
-
const raw = sessionStorage.getItem(key);
|
|
25
|
-
return raw == null ? fallback : (parse(raw) ?? fallback);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return fallback;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
let state = typeof sessionStorage === 'undefined' ? fallback : read();
|
|
32
|
-
const write = (next) => {
|
|
33
|
-
state = next;
|
|
34
|
-
try {
|
|
35
|
-
sessionStorage.setItem(key, String(next));
|
|
36
|
-
}
|
|
37
|
-
catch { }
|
|
38
|
-
for (const l of listeners)
|
|
39
|
-
l();
|
|
40
|
-
};
|
|
41
|
-
const use = () => useSyncExternalStore((l) => {
|
|
42
|
-
listeners.add(l);
|
|
43
|
-
return () => listeners.delete(l);
|
|
44
|
-
}, () => state, () => fallback);
|
|
45
|
-
return { write, use, get: () => state };
|
|
46
|
-
}
|
|
20
|
+
export { tabState };
|
|
47
21
|
/** Whether the assistant is open in this tab. */
|
|
48
22
|
export const assistantOpen = tabState('assistant_open', false, (raw) => raw === '1');
|
|
49
23
|
/** Dragged width in px, within ASSISTANT_WIDTH; 0 is the shell's default. */
|
|
@@ -109,65 +83,11 @@ function Docked({ name, children }) {
|
|
|
109
83
|
}, []);
|
|
110
84
|
return (_jsxs("aside", { ref: ref, "aria-label": name, "data-assistant-docked": "",
|
|
111
85
|
// Its width is the dragged one (or the shell's default), within ASSISTANT_WIDTH and half the window.
|
|
112
|
-
style: width ? { width } : undefined, className: "pointer-events-auto sticky top-0 h-svh w-(--shell-assistant-width) max-w-[50vw] min-w-80 shrink-0 border-l bg-background", children: [_jsx(ResizeEdge, { name: name, width: width || 416, onWidth: (w) => {
|
|
86
|
+
style: width ? { width } : undefined, className: "pointer-events-auto sticky top-0 h-svh w-(--shell-assistant-width) max-w-[50vw] min-w-80 shrink-0 border-l bg-background", children: [_jsx(ResizeEdge, { name: name, width: width || 416, min: ASSISTANT_WIDTH.min, max: Math.min(ASSISTANT_WIDTH.max, Math.floor(window.innerWidth / 2)), slot: "assistant-resize", onWidth: (w) => {
|
|
113
87
|
setDrag(null);
|
|
114
88
|
assistantWidth.write(w);
|
|
115
89
|
}, onDrag: setDrag }), _jsx(LumenBoundary, { where: "assistant", what: "panel", children: children })] }));
|
|
116
90
|
}
|
|
117
|
-
/**
|
|
118
|
-
* The panel's left edge: drag it, or focus it and use the arrow keys, to set the width; a double
|
|
119
|
-
* click goes back to the default. While dragging only `onDrag` hears it.
|
|
120
|
-
*/
|
|
121
|
-
function ResizeEdge({ name, width, onDrag, onWidth, }) {
|
|
122
|
-
const clamp = (w) => Math.round(Math.max(ASSISTANT_WIDTH.min, Math.min(w, ASSISTANT_WIDTH.max, Math.floor(window.innerWidth / 2))));
|
|
123
|
-
// The panel's width on screen (the default one is CSS's).
|
|
124
|
-
const now = (el) => Math.round(el.parentElement?.getBoundingClientRect().width ?? width);
|
|
125
|
-
const [dragging, setDragging] = useState(false);
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
if (!dragging)
|
|
128
|
-
return;
|
|
129
|
-
document.body.style.cursor = 'col-resize';
|
|
130
|
-
document.body.style.userSelect = 'none';
|
|
131
|
-
return () => {
|
|
132
|
-
document.body.style.cursor = '';
|
|
133
|
-
document.body.style.userSelect = '';
|
|
134
|
-
};
|
|
135
|
-
}, [dragging]);
|
|
136
|
-
return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": `Resize ${name}`, "aria-valuemin": ASSISTANT_WIDTH.min, "aria-valuemax": ASSISTANT_WIDTH.max, "aria-valuenow": width, tabIndex: 0, "data-slot": "assistant-resize", "data-dragging": dragging ? '' : undefined, className: "absolute inset-y-0 -left-1 z-20 w-2 cursor-col-resize outline-none after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 after:-translate-x-1/2 after:transition-colors hover:after:bg-ring focus-visible:after:bg-ring data-dragging:after:bg-ring", onPointerDown: (e) => {
|
|
137
|
-
if (e.button !== 0)
|
|
138
|
-
return;
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
const el = e.currentTarget;
|
|
141
|
-
el.setPointerCapture(e.pointerId);
|
|
142
|
-
const x0 = e.clientX;
|
|
143
|
-
const w0 = now(el);
|
|
144
|
-
let last = w0;
|
|
145
|
-
setDragging(true);
|
|
146
|
-
const move = (m) => {
|
|
147
|
-
last = clamp(w0 + x0 - m.clientX);
|
|
148
|
-
onDrag(last);
|
|
149
|
-
};
|
|
150
|
-
const up = () => {
|
|
151
|
-
el.removeEventListener('pointermove', move);
|
|
152
|
-
el.removeEventListener('pointerup', up);
|
|
153
|
-
el.removeEventListener('pointercancel', up);
|
|
154
|
-
setDragging(false);
|
|
155
|
-
onWidth(last);
|
|
156
|
-
};
|
|
157
|
-
el.addEventListener('pointermove', move);
|
|
158
|
-
el.addEventListener('pointerup', up);
|
|
159
|
-
el.addEventListener('pointercancel', up);
|
|
160
|
-
}, onDoubleClick: () => onWidth(0), onKeyDown: (e) => {
|
|
161
|
-
const step = e.shiftKey ? 64 : 16;
|
|
162
|
-
if (e.key === 'ArrowLeft')
|
|
163
|
-
onWidth(clamp(now(e.currentTarget) + step));
|
|
164
|
-
else if (e.key === 'ArrowRight')
|
|
165
|
-
onWidth(clamp(now(e.currentTarget) - step));
|
|
166
|
-
else
|
|
167
|
-
return;
|
|
168
|
-
e.preventDefault();
|
|
169
|
-
} }));
|
|
170
|
-
}
|
|
171
91
|
/** The history groups, newest first. */
|
|
172
92
|
const GROUPS = ['Today', 'Yesterday', 'This week', 'Last week', 'Last month'];
|
|
173
93
|
/** Under this age a chat's time reads relative ("12m ago"), past it the clock, day or date. */
|
package/dist/lumen-drawer.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface LumenDrawerProps {
|
|
|
17
17
|
defaultSize?: Size;
|
|
18
18
|
minSize?: Size;
|
|
19
19
|
maxSize?: Size;
|
|
20
|
+
/**
|
|
21
|
+
* Docks it beside the page on desktop instead of over it, under this name: a click on the page
|
|
22
|
+
* leaves it open (a list's next row swaps what it shows), it folds to a rail, and its width and
|
|
23
|
+
* fold are kept in this browser. A phone still gets the bottom sheet.
|
|
24
|
+
*/
|
|
25
|
+
dock?: string;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* The suite's one pullout drawer (named LumenDrawer), on every app.
|
|
@@ -25,7 +31,8 @@ export interface LumenDrawerProps {
|
|
|
25
31
|
* the handle and the drawer take the pointer, so a click beside it falls through to the overlay
|
|
26
32
|
* and closes it (`!` beats the inline pointer-events Radix sets on a modal's content). Its body
|
|
27
33
|
* holds DrawerSections; its footer sits at the bottom (actions on the right, Close by default; on a
|
|
28
|
-
* phone it shows only when there are actions, as the sheet's X closes it).
|
|
34
|
+
* phone it shows only when there are actions, as the sheet's X closes it). With `dock` it sits beside
|
|
35
|
+
* the page on desktop instead (DockedDrawer), for a list whose rows each open it.
|
|
29
36
|
*/
|
|
30
|
-
export declare function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize, minSize, maxSize, }: LumenDrawerProps): import("react").JSX.Element;
|
|
37
|
+
export declare function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize, minSize, maxSize, dock, }: LumenDrawerProps): import("react").JSX.Element | null;
|
|
31
38
|
export {};
|
package/dist/lumen-drawer.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { XIcon } from 'lucide-react';
|
|
3
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PanelRightCloseIcon, PanelRightOpenIcon, XIcon } from 'lucide-react';
|
|
3
|
+
import { useEffect, useState, useSyncExternalStore } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { useIsMobile } from './hooks/use-mobile.js';
|
|
5
6
|
import { IconButton } from './icon-button.js';
|
|
6
7
|
import { lazyPart } from './lazy-part.js';
|
|
8
|
+
import { ResizeEdge, tabState, useDockSlot } from './side-dock.js';
|
|
7
9
|
import { Button } from './ui/button.js';
|
|
10
|
+
import { pickerOpen } from './ui/overlay-guard.js';
|
|
8
11
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from './ui/sheet.js';
|
|
9
12
|
/** The resizable panels load with the first drawer opened; until then it opens at its default width. */
|
|
10
13
|
const loadPanels = () => import('./lumen-drawer-panels.js');
|
|
@@ -16,10 +19,12 @@ const DrawerPanels = lazyPart(() => loadPanels().then((m) => m.DrawerPanels));
|
|
|
16
19
|
* the handle and the drawer take the pointer, so a click beside it falls through to the overlay
|
|
17
20
|
* and closes it (`!` beats the inline pointer-events Radix sets on a modal's content). Its body
|
|
18
21
|
* holds DrawerSections; its footer sits at the bottom (actions on the right, Close by default; on a
|
|
19
|
-
* phone it shows only when there are actions, as the sheet's X closes it).
|
|
22
|
+
* phone it shows only when there are actions, as the sheet's X closes it). With `dock` it sits beside
|
|
23
|
+
* the page on desktop instead (DockedDrawer), for a list whose rows each open it.
|
|
20
24
|
*/
|
|
21
|
-
export function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', }) {
|
|
25
|
+
export function LumenDrawer({ open, onClose, title, description, children, footer, footerStart, defaultSize = '32%', minSize = '320px', maxSize = '60%', dock, }) {
|
|
22
26
|
const isMobile = useIsMobile();
|
|
27
|
+
const slot = useDockSlot();
|
|
23
28
|
// Fetched once the page is idle, so the first drawer opened on a desktop has its panels ready.
|
|
24
29
|
useEffect(() => {
|
|
25
30
|
if (isMobile)
|
|
@@ -33,9 +38,66 @@ export function LumenDrawer({ open, onClose, title, description, children, foote
|
|
|
33
38
|
// ends on a clear edge. Without actions of its own it offers Close, except on a phone, where the
|
|
34
39
|
// sheet's X already closes it and a second Close only took room.
|
|
35
40
|
const plain = !footer && !footerStart;
|
|
36
|
-
const foot = (_jsxs("footer", { "data-slot": "drawer-footer", className: "flex shrink-0 items-center gap-2 border-t bg-muted/50 px-4 py-2.5", children: [footerStart && (_jsx("div", { className: "min-w-0 flex-1 truncate text-muted-foreground text-xs", children: footerStart })), _jsx("div", { className: "ml-auto flex items-center gap-2", children: footer ?? (_jsx(
|
|
41
|
+
const foot = (_jsxs("footer", { "data-slot": "drawer-footer", className: "flex shrink-0 items-center gap-2 border-t bg-muted/50 px-4 py-2.5", children: [footerStart && (_jsx("div", { className: "min-w-0 flex-1 truncate text-muted-foreground text-xs", children: footerStart })), _jsx("div", { className: "ml-auto flex items-center gap-2", children: footer ?? (_jsx(Button, { type: "button", size: "sm", variant: "outline", onClick: onClose, children: "Close" })) })] }));
|
|
42
|
+
if (dock && !isMobile && slot)
|
|
43
|
+
return open
|
|
44
|
+
? createPortal(_jsx(DockedDrawer, { dock: dock, title: title, description: description, onClose: onClose, defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, body: body, foot: foot }), slot)
|
|
45
|
+
: null;
|
|
37
46
|
return (_jsx(Sheet, { open: open, onOpenChange: (o) => !o && onClose(), modal: isMobile, children: isMobile ? (_jsxs(SheetContent, { side: "bottom", className: "max-h-[85svh] gap-0 overflow-hidden", children: [_jsx(SheetHeader, { className: "order-first shrink-0 border-b pt-3", children: head }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), !plain && foot] })) : (_jsx(SheetContent, { side: "right", showCloseButton: false,
|
|
38
47
|
// The resize handle's grab zone is wider than the handle, so a drag can start on the
|
|
39
48
|
// overlay; the panels library marks that pointerdown handled, and then it doesn't close.
|
|
40
49
|
onPointerDownOutside: (e) => e.detail.originalEvent.defaultPrevented && e.preventDefault(), className: "pointer-events-none! gap-0 border-0 bg-transparent shadow-none data-[side=right]:left-0 data-[side=right]:w-auto data-[side=right]:border-l-0 data-[side=right]:sm:max-w-none", children: _jsx(DrawerPanels, { defaultSize: defaultSize, minSize: minSize, maxSize: maxSize, children: _jsxs("section", { className: "flex h-full flex-col overflow-hidden", children: [_jsxs("header", { className: "flex shrink-0 items-center gap-2 border-b px-4 py-3", children: [head, _jsx(SheetClose, { asChild: true, children: _jsx(IconButton, { icon: XIcon, label: "Close", className: "ml-auto" }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] }) }) })) }));
|
|
41
50
|
}
|
|
51
|
+
/** What each docked drawer keeps, by its `dock` name: its dragged width (0: the default) and its fold. */
|
|
52
|
+
const docks = new Map();
|
|
53
|
+
function dockState(dock) {
|
|
54
|
+
let d = docks.get(dock);
|
|
55
|
+
if (!d) {
|
|
56
|
+
d = {
|
|
57
|
+
width: tabState(`lumen_dock_${dock}_width`, 0, (raw) => {
|
|
58
|
+
const n = Math.round(Number(raw));
|
|
59
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
60
|
+
}, 'local'),
|
|
61
|
+
folded: tabState(`lumen_dock_${dock}_folded`, false, (raw) => raw === 'true', 'local'),
|
|
62
|
+
};
|
|
63
|
+
docks.set(dock, d);
|
|
64
|
+
}
|
|
65
|
+
return d;
|
|
66
|
+
}
|
|
67
|
+
const onResize = (l) => {
|
|
68
|
+
window.addEventListener('resize', l);
|
|
69
|
+
return () => window.removeEventListener('resize', l);
|
|
70
|
+
};
|
|
71
|
+
/** A Size in px, a percentage being of the window's width. */
|
|
72
|
+
const px = (size, vw) => size.endsWith('%') ? (vw * Number.parseFloat(size)) / 100 : Number.parseFloat(size);
|
|
73
|
+
/** Room a docked drawer always leaves the page, px. */
|
|
74
|
+
const PAGE_MIN = 480;
|
|
75
|
+
/**
|
|
76
|
+
* A drawer docked right of the page (LumenShell's dock slot): the page narrows beside it and stays
|
|
77
|
+
* usable. Its left edge drags the width within `minSize` and `maxSize` (never leaving the page under
|
|
78
|
+
* PAGE_MIN); it folds to a rail that shows its title; Esc in it closes it, unless a picker in it is open.
|
|
79
|
+
*/
|
|
80
|
+
function DockedDrawer({ dock, title, description, onClose, defaultSize, minSize, maxSize, body, foot, }) {
|
|
81
|
+
const state = dockState(dock);
|
|
82
|
+
const saved = state.width.use();
|
|
83
|
+
const folded = state.folded.use();
|
|
84
|
+
const [drag, setDrag] = useState(null);
|
|
85
|
+
const vw = useSyncExternalStore(onResize, () => window.innerWidth, () => 1440);
|
|
86
|
+
const min = Math.round(px(minSize, vw));
|
|
87
|
+
const max = Math.max(min, Math.round(Math.min(px(maxSize, vw), vw - PAGE_MIN)));
|
|
88
|
+
const clamp = (w) => Math.max(min, Math.min(w, max));
|
|
89
|
+
const width = drag ?? clamp(saved || px(defaultSize, vw));
|
|
90
|
+
const name = typeof title === 'string' ? title : 'Details';
|
|
91
|
+
return (_jsx("aside", { "aria-label": name, "data-slot": "lumen-drawer", "data-docked": "", "data-folded": folded ? '' : undefined, style: folded ? undefined : { width }, className: "sticky top-0 flex h-svh shrink-0 flex-col border-l bg-background data-folded:w-11", onKeyDown: (e) => {
|
|
92
|
+
if (e.key !== 'Escape' || e.defaultPrevented || pickerOpen())
|
|
93
|
+
return;
|
|
94
|
+
// Only Esc from inside it: one from a dialog it opened (a portal) is that dialog's.
|
|
95
|
+
if (!e.currentTarget.contains(e.target))
|
|
96
|
+
return;
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
onClose();
|
|
99
|
+
}, children: folded ? (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col items-center gap-2 overflow-hidden py-3", children: [_jsx(IconButton, { icon: PanelRightOpenIcon, label: "Expand panel", onClick: () => state.folded.write(false) }), _jsx("span", { className: "min-h-0 truncate text-muted-foreground text-xs [writing-mode:vertical-rl]", title: name, children: title })] })) : (_jsxs(_Fragment, { children: [_jsx(ResizeEdge, { name: name, width: width, min: min, max: max, slot: "lumen-drawer-resize", onDrag: setDrag, onWidth: (w) => {
|
|
100
|
+
setDrag(null);
|
|
101
|
+
state.width.write(w);
|
|
102
|
+
} }), _jsxs("header", { className: "flex shrink-0 items-center gap-1 border-b px-4 py-3", children: [_jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("h2", { className: "cn-font-heading truncate font-medium text-base text-foreground", title: name, children: title }), description && (_jsx("p", { className: "truncate text-muted-foreground text-xs", title: typeof description === 'string' ? description : undefined, children: description }))] }), _jsx(IconButton, { icon: PanelRightCloseIcon, label: "Collapse panel", onClick: () => state.folded.write(true) }), _jsx(IconButton, { icon: XIcon, label: "Close", onClick: onClose })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pt-2", children: body }), foot] })) }));
|
|
103
|
+
}
|
package/dist/lumen-view.js
CHANGED
|
@@ -440,12 +440,13 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
440
440
|
// Inline chips sit on the first column, next to the row's name, not in their own.
|
|
441
441
|
const inline = visible.filter((f, i) => i > 0 && chips.some((c) => c.key === f.key && c.placement === 'inline'));
|
|
442
442
|
const shown = visible.filter((f) => !inline.includes(f));
|
|
443
|
-
// On a narrow phone each row reads as a card (theme.css, `data-card`): its name and
|
|
444
|
-
// (the sorted one, else the first number or date
|
|
445
|
-
//
|
|
443
|
+
// On a narrow phone each row reads as a card on ONE line (theme.css, `data-card`): its name and
|
|
444
|
+
// one value (the sorted one, else the first number or date, else the next column). The rest
|
|
445
|
+
// waits in the row's detail, never on a second line.
|
|
446
446
|
const short = (f) => f.format !== 'text' || (rows[0] !== undefined && isDateText(f.value(rows[0])));
|
|
447
|
-
const
|
|
448
|
-
|
|
447
|
+
const onCard = shown.filter((f, i) => i > 0 && !f.hideOnPhone);
|
|
448
|
+
const meta = (onCard.find((f) => f.key === sorting[0]?.id) ?? onCard.find(short) ?? onCard[0])
|
|
449
|
+
?.key;
|
|
449
450
|
const cardOf = (f) => f === shown[0] ? 'lead' : f.key === meta ? 'meta' : short(f) ? 'num' : 'text';
|
|
450
451
|
const col = (f, ...more) => ({
|
|
451
452
|
...colProps(f, ...more),
|
package/dist/shell.js
CHANGED
|
@@ -13,6 +13,7 @@ import { LumenFinder } from './lumen-finder.js';
|
|
|
13
13
|
import { usePrefs } from './prefs.js';
|
|
14
14
|
import { withRefreshToken } from './refresh.js';
|
|
15
15
|
import { LumenSettings, ThemeToggle, allSettings, } from './settings.js';
|
|
16
|
+
import { DockSlot } from './side-dock.js';
|
|
16
17
|
import { SourceHealthProvider } from './source-health.js';
|
|
17
18
|
import { StatusTimeZone } from './status.js';
|
|
18
19
|
import { Avatar, AvatarFallback } from './ui/avatar.js';
|
|
@@ -96,6 +97,7 @@ const settingsFromUrl = (() => {
|
|
|
96
97
|
})();
|
|
97
98
|
export function LumenShell({ app, appLabel, brand, nav, status, health, statusEnd, releases, releaseApps, settings, shared, sharedLabel, settingsGroups, user = null, signOutUrl, profile, security, onRefresh, hideAmounts = false, onLogs, headerActions = null, panel, finder, wrap = (frame) => frame, children, }) {
|
|
98
99
|
const [statusSlot, setStatusSlot] = useState(null);
|
|
100
|
+
const [dockSlot, setDockSlot] = useState(null);
|
|
99
101
|
const [refreshing, setRefreshing] = useState(false);
|
|
100
102
|
// Refresh reloads the layout too: once the data is fresh the page mounts
|
|
101
103
|
// again, so every card, tab and saved layout starts from what the server holds now.
|
|
@@ -139,7 +141,7 @@ export function LumenShell({ app, appLabel, brand, nav, status, health, statusEn
|
|
|
139
141
|
refreshing,
|
|
140
142
|
hideAmounts,
|
|
141
143
|
headerActions,
|
|
142
|
-
}, children: _jsx(SourceHealthProvider, { children: wrap(_jsx(TooltipProvider, { delayDuration: 200, children: _jsx(SidebarProvider, { defaultOpen: sidebarOpen(), children: _jsxs(LumenFinder, { app: app, settings: findSettings, openSettings: openSettings, config: finder, children: [_jsx(CloseDrawerOnNavigate, {}), _jsxs(Sidebar, { collapsible: "icon", children: [_jsx(SidebarHeader, { children: brand }), _jsx(SidebarContent, { children: nav }), _jsx(SidebarFooter, { children: _jsx(NavUser, { who: user, signOutUrl: signOutUrl, onSettings: () => openSettings(), onLogs: onLogs }) }), _jsx(SidebarRail, {})] }), _jsxs(SidebarInset, { className: "flex min-h-svh min-w-0 flex-col", children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: children }, epoch), _jsxs("footer", { "data-slot": "shell-status", "aria-label": "Status", className: "sticky bottom-0 z-10 flex h-(--shell-status-height) shrink-0 items-center gap-4 overflow-x-auto overflow-y-hidden border-t bg-background px-(--shell-gutter) text-xs whitespace-nowrap text-muted-foreground", children: [status, _jsx("div", { ref: setStatusSlot, className: "flex shrink-0 items-center gap-4" }), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1", children: [health, releases && _jsx(WhatsNew, { api: releases, apps: releaseApps }), statusEnd, _jsx(StatusTimeZone, {})] })] })] }), panel, _jsx(LumenSettings, { open: settingsAt !== null, section: settingsAt ?? lastSection, onOpen: openSettings, onClose: () => setSettingsAt(null), appLabel: appLabel, profile: profile, security: security, sections: settings, shared: shared, sharedLabel: sharedLabel, groups: settingsGroups }), _jsx(Toaster, { position: "bottom-right" })] }) }) })) }) }));
|
|
144
|
+
}, children: _jsx(DockSlot.Provider, { value: dockSlot, children: _jsx(SourceHealthProvider, { children: wrap(_jsx(TooltipProvider, { delayDuration: 200, children: _jsx(SidebarProvider, { defaultOpen: sidebarOpen(), children: _jsxs(LumenFinder, { app: app, settings: findSettings, openSettings: openSettings, config: finder, children: [_jsx(CloseDrawerOnNavigate, {}), _jsxs(Sidebar, { collapsible: "icon", children: [_jsx(SidebarHeader, { children: brand }), _jsx(SidebarContent, { children: nav }), _jsx(SidebarFooter, { children: _jsx(NavUser, { who: user, signOutUrl: signOutUrl, onSettings: () => openSettings(), onLogs: onLogs }) }), _jsx(SidebarRail, {})] }), _jsxs(SidebarInset, { className: "flex min-h-svh min-w-0 flex-col", children: [_jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: children }, epoch), _jsxs("footer", { "data-slot": "shell-status", "aria-label": "Status", className: "sticky bottom-0 z-10 flex h-(--shell-status-height) shrink-0 items-center gap-4 overflow-x-auto overflow-y-hidden border-t bg-background px-(--shell-gutter) text-xs whitespace-nowrap text-muted-foreground", children: [status, _jsx("div", { ref: setStatusSlot, className: "flex shrink-0 items-center gap-4" }), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1", children: [health, releases && _jsx(WhatsNew, { api: releases, apps: releaseApps }), statusEnd, _jsx(StatusTimeZone, {})] })] })] }), _jsx("div", { ref: setDockSlot, "data-slot": "shell-dock", className: "contents" }), panel, _jsx(LumenSettings, { open: settingsAt !== null, section: settingsAt ?? lastSection, onOpen: openSettings, onClose: () => setSettingsAt(null), appLabel: appLabel, profile: profile, security: security, sections: settings, shared: shared, sharedLabel: sharedLabel, groups: settingsGroups }), _jsx(Toaster, { position: "bottom-right" })] }) }) })) }) }) }));
|
|
143
145
|
}
|
|
144
146
|
/** A status bar entry: an optional health dot, a label and its value ("Mail · Up to date"). */
|
|
145
147
|
export function StatusItem({ label, value, ok, className, }) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value kept for this browser tab (sessionStorage), or for this browser with `'local'`
|
|
3
|
+
* (localStorage), read with `use` and set with `write`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function tabState<T>(key: string, fallback: T, parse: (raw: string) => T | null, keep?: 'tab' | 'local'): {
|
|
6
|
+
write: (next: T) => void;
|
|
7
|
+
use: () => T;
|
|
8
|
+
get: () => T;
|
|
9
|
+
};
|
|
10
|
+
/** Where a docked drawer renders on desktop; null outside LumenShell (it then opens over the page). */
|
|
11
|
+
export declare const DockSlot: import("react").Context<HTMLElement | null>;
|
|
12
|
+
export declare const useDockSlot: () => HTMLElement | null;
|
|
13
|
+
/**
|
|
14
|
+
* A docked panel's left edge: drag it, or focus it and use the arrow keys, to set the width between
|
|
15
|
+
* `min` and `max`; a double click goes back to the default (0). While dragging only `onDrag` hears it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ResizeEdge({ name, width, min, max, onDrag, onWidth, slot, }: {
|
|
18
|
+
name: string;
|
|
19
|
+
width: number;
|
|
20
|
+
min: number;
|
|
21
|
+
max: number;
|
|
22
|
+
onDrag: (w: number | null) => void;
|
|
23
|
+
onWidth: (w: number) => void;
|
|
24
|
+
/** Its `data-slot`. */
|
|
25
|
+
slot: string;
|
|
26
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// What a panel docked right of the page needs: a value it keeps (its width, folded or not), the
|
|
3
|
+
// left edge that drags its width, and the shell's slot a docked drawer renders into (LumenShell puts
|
|
4
|
+
// it between the page and the assistant, so the page narrows to make room instead of being covered).
|
|
5
|
+
import { createContext, useContext, useEffect, useState, useSyncExternalStore } from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* A value kept for this browser tab (sessionStorage), or for this browser with `'local'`
|
|
8
|
+
* (localStorage), read with `use` and set with `write`.
|
|
9
|
+
*/
|
|
10
|
+
export function tabState(key, fallback, parse, keep = 'tab') {
|
|
11
|
+
const listeners = new Set();
|
|
12
|
+
const store = () => (keep === 'local' ? localStorage : sessionStorage);
|
|
13
|
+
const read = () => {
|
|
14
|
+
try {
|
|
15
|
+
const raw = store().getItem(key);
|
|
16
|
+
return raw == null ? fallback : (parse(raw) ?? fallback);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
let state = typeof sessionStorage === 'undefined' ? fallback : read();
|
|
23
|
+
const write = (next) => {
|
|
24
|
+
state = next;
|
|
25
|
+
try {
|
|
26
|
+
store().setItem(key, String(next));
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
for (const l of listeners)
|
|
30
|
+
l();
|
|
31
|
+
};
|
|
32
|
+
const use = () => useSyncExternalStore((l) => {
|
|
33
|
+
listeners.add(l);
|
|
34
|
+
return () => listeners.delete(l);
|
|
35
|
+
}, () => state, () => fallback);
|
|
36
|
+
return { write, use, get: () => state };
|
|
37
|
+
}
|
|
38
|
+
/** Where a docked drawer renders on desktop; null outside LumenShell (it then opens over the page). */
|
|
39
|
+
export const DockSlot = createContext(null);
|
|
40
|
+
export const useDockSlot = () => useContext(DockSlot);
|
|
41
|
+
/**
|
|
42
|
+
* A docked panel's left edge: drag it, or focus it and use the arrow keys, to set the width between
|
|
43
|
+
* `min` and `max`; a double click goes back to the default (0). While dragging only `onDrag` hears it.
|
|
44
|
+
*/
|
|
45
|
+
export function ResizeEdge({ name, width, min, max, onDrag, onWidth, slot, }) {
|
|
46
|
+
const clamp = (w) => Math.round(Math.max(min, Math.min(w, max)));
|
|
47
|
+
// The panel's width on screen (the default one is CSS's).
|
|
48
|
+
const now = (el) => Math.round(el.parentElement?.getBoundingClientRect().width ?? width);
|
|
49
|
+
const [dragging, setDragging] = useState(false);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!dragging)
|
|
52
|
+
return;
|
|
53
|
+
document.body.style.cursor = 'col-resize';
|
|
54
|
+
document.body.style.userSelect = 'none';
|
|
55
|
+
return () => {
|
|
56
|
+
document.body.style.cursor = '';
|
|
57
|
+
document.body.style.userSelect = '';
|
|
58
|
+
};
|
|
59
|
+
}, [dragging]);
|
|
60
|
+
return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": `Resize ${name}`, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": width, tabIndex: 0, "data-slot": slot, "data-dragging": dragging ? '' : undefined, className: "absolute inset-y-0 -left-1 z-20 w-2 cursor-col-resize outline-none after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 after:-translate-x-1/2 after:transition-colors hover:after:bg-ring focus-visible:after:bg-ring data-dragging:after:bg-ring", onPointerDown: (e) => {
|
|
61
|
+
if (e.button !== 0)
|
|
62
|
+
return;
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
const el = e.currentTarget;
|
|
65
|
+
el.setPointerCapture(e.pointerId);
|
|
66
|
+
const x0 = e.clientX;
|
|
67
|
+
const w0 = now(el);
|
|
68
|
+
let last = w0;
|
|
69
|
+
setDragging(true);
|
|
70
|
+
const move = (m) => {
|
|
71
|
+
last = clamp(w0 + x0 - m.clientX);
|
|
72
|
+
onDrag(last);
|
|
73
|
+
};
|
|
74
|
+
const up = () => {
|
|
75
|
+
el.removeEventListener('pointermove', move);
|
|
76
|
+
el.removeEventListener('pointerup', up);
|
|
77
|
+
el.removeEventListener('pointercancel', up);
|
|
78
|
+
setDragging(false);
|
|
79
|
+
onWidth(last);
|
|
80
|
+
};
|
|
81
|
+
el.addEventListener('pointermove', move);
|
|
82
|
+
el.addEventListener('pointerup', up);
|
|
83
|
+
el.addEventListener('pointercancel', up);
|
|
84
|
+
}, onDoubleClick: () => onWidth(0), onKeyDown: (e) => {
|
|
85
|
+
const step = e.shiftKey ? 64 : 16;
|
|
86
|
+
if (e.key === 'ArrowLeft')
|
|
87
|
+
onWidth(clamp(now(e.currentTarget) + step));
|
|
88
|
+
else if (e.key === 'ArrowRight')
|
|
89
|
+
onWidth(clamp(now(e.currentTarget) - step));
|
|
90
|
+
else
|
|
91
|
+
return;
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
} }));
|
|
94
|
+
}
|
package/dist/theme.css
CHANGED
|
@@ -443,9 +443,11 @@
|
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
/* A LumenView table on a narrow phone (`data-cards`): each row is a card, so nothing runs off
|
|
446
|
-
the right edge
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
the right edge, and every row is ONE line, whatever an app draws in it: the row's name (shortens
|
|
447
|
+
in an ellipsis), then one figure (`data-card="meta"`, the sorted one, else the first number or
|
|
448
|
+
date) on the right. The other values wait in the row's detail (its drawer or panel), never on a
|
|
449
|
+
second line. Group, fold and Total rows (`data-rollup`) read the same way. More and empty rows
|
|
450
|
+
are one cell across the card. */
|
|
449
451
|
@media (max-width: 639px) {
|
|
450
452
|
table[data-cards],
|
|
451
453
|
table[data-cards] > tbody,
|
|
@@ -462,55 +464,46 @@
|
|
|
462
464
|
}
|
|
463
465
|
table[data-cards] tr {
|
|
464
466
|
display: flex;
|
|
465
|
-
flex-wrap:
|
|
466
|
-
align-items:
|
|
467
|
+
flex-wrap: nowrap;
|
|
468
|
+
align-items: center;
|
|
467
469
|
column-gap: 0.75rem;
|
|
468
|
-
row-gap: 0.125rem;
|
|
469
470
|
padding: 0.5rem var(--cell-x);
|
|
471
|
+
white-space: nowrap;
|
|
470
472
|
}
|
|
471
473
|
table[data-cards] td {
|
|
472
474
|
padding: 0 !important;
|
|
473
475
|
width: auto !important;
|
|
474
476
|
min-width: 0;
|
|
477
|
+
white-space: nowrap;
|
|
475
478
|
}
|
|
476
479
|
table[data-cards] td[colspan] {
|
|
477
480
|
flex: 1 1 100%;
|
|
478
481
|
}
|
|
479
|
-
|
|
482
|
+
/* A row's panel (`row-panel`) is a box of its own under the row, not a row: it keeps its lines. */
|
|
483
|
+
table[data-cards] tr[data-slot="row-panel"],
|
|
484
|
+
table[data-cards] tr[data-slot="row-panel"] > td {
|
|
485
|
+
white-space: normal;
|
|
486
|
+
}
|
|
487
|
+
table[data-cards] td:not([data-card]):not([colspan]),
|
|
488
|
+
table[data-cards] td:is([data-card="num"], [data-card="text"]) {
|
|
480
489
|
display: none;
|
|
481
490
|
}
|
|
482
491
|
table[data-cards] td[data-card="select"] {
|
|
483
492
|
order: 0;
|
|
484
493
|
flex: none;
|
|
485
|
-
align-self: center;
|
|
486
494
|
}
|
|
487
495
|
table[data-cards] td[data-card="lead"] {
|
|
488
496
|
order: 1;
|
|
489
497
|
flex: 1 1 0;
|
|
490
498
|
overflow: hidden;
|
|
499
|
+
text-overflow: ellipsis;
|
|
491
500
|
font-weight: 500;
|
|
492
501
|
}
|
|
493
502
|
table[data-cards] td[data-card="meta"] {
|
|
494
503
|
order: 2;
|
|
495
504
|
flex: none;
|
|
496
|
-
|
|
497
|
-
table[data-cards] tr:has(> td[data-card="lead"])::after {
|
|
498
|
-
content: "";
|
|
499
|
-
order: 3;
|
|
500
|
-
flex-basis: 100%;
|
|
501
|
-
}
|
|
502
|
-
table[data-cards] td:is([data-card="num"], [data-card="text"]) {
|
|
503
|
-
order: 4;
|
|
504
|
-
font-size: 0.8125rem;
|
|
505
|
-
color: var(--muted-foreground);
|
|
506
|
-
}
|
|
507
|
-
table[data-cards] td[data-card="num"] {
|
|
508
|
-
flex: none;
|
|
509
|
-
}
|
|
510
|
-
table[data-cards] td[data-card="text"] {
|
|
511
|
-
flex: 0 1 auto;
|
|
505
|
+
max-width: 50%;
|
|
512
506
|
overflow: hidden;
|
|
513
|
-
text-overflow: ellipsis;
|
|
514
507
|
}
|
|
515
508
|
table[data-cards] [data-slot="unit-value-unit"] {
|
|
516
509
|
position: static;
|
|
@@ -519,31 +512,7 @@
|
|
|
519
512
|
margin-left: 0.25em;
|
|
520
513
|
font-size: inherit;
|
|
521
514
|
}
|
|
522
|
-
/*
|
|
523
|
-
in an ellipsis, the row's one figure keeps its width on the right, and the rest waits in the
|
|
524
|
-
open rows. Whatever an app draws in them (a mark, a roll-up, group actions) never wraps.
|
|
525
|
-
The group band pins as a whole row here: a cell can't pin inside a one-line flex row. */
|
|
526
|
-
table[data-cards] tr[data-rollup] {
|
|
527
|
-
flex-wrap: nowrap;
|
|
528
|
-
align-items: center;
|
|
529
|
-
white-space: nowrap;
|
|
530
|
-
}
|
|
531
|
-
table[data-cards] tr[data-rollup]::after {
|
|
532
|
-
display: none;
|
|
533
|
-
}
|
|
534
|
-
table[data-cards] tr[data-rollup] > td {
|
|
535
|
-
white-space: nowrap;
|
|
536
|
-
}
|
|
537
|
-
table[data-cards] tr[data-rollup] > td[data-card="lead"] {
|
|
538
|
-
flex: 1 1 0;
|
|
539
|
-
min-width: 0;
|
|
540
|
-
overflow: hidden;
|
|
541
|
-
}
|
|
542
|
-
table[data-cards]
|
|
543
|
-
tr[data-rollup]
|
|
544
|
-
> td:is([data-card="num"], [data-card="text"], :not([data-card])) {
|
|
545
|
-
display: none;
|
|
546
|
-
}
|
|
515
|
+
/* The group band pins as a whole row here: a cell can't pin inside a one-line flex row. */
|
|
547
516
|
table[data-cards] tr[data-group] {
|
|
548
517
|
position: sticky;
|
|
549
518
|
top: var(--pin-top, 0px);
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** Whether a picker is open, so a panel that closes on Esc leaves this one to it. */
|
|
2
|
+
export declare const pickerOpen: () => boolean;
|
|
1
3
|
/** The overlay's `onEscapeKeyDown`: ignores Esc while a picker is open, else runs the caller's. */
|
|
2
4
|
export declare function escapeToPicker(own?: (e: KeyboardEvent) => void): (e: KeyboardEvent) => void;
|
package/dist/ui/overlay-guard.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// document before a base-ui picker open inside it (LumenSelector, Combobox), which isn't one of
|
|
3
3
|
// Radix's layers, so one Esc closed both. While such a picker is open the overlay leaves Esc to it.
|
|
4
4
|
const OPEN_PICKER = '[data-slot="lumen-selector"][data-open], [data-slot="combobox-content"][data-open]';
|
|
5
|
+
/** Whether a picker is open, so a panel that closes on Esc leaves this one to it. */
|
|
6
|
+
export const pickerOpen = () => !!document.querySelector(OPEN_PICKER);
|
|
5
7
|
/** The overlay's `onEscapeKeyDown`: ignores Esc while a picker is open, else runs the caller's. */
|
|
6
8
|
export function escapeToPicker(own) {
|
|
7
9
|
return (e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.116",
|
|
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.116",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|