wenay-react2 1.0.35 → 1.0.37
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 +8 -53
- package/lib/common/api.d.ts +28 -28
- package/lib/common/api.js +31 -31
- package/lib/common/src/components/Buttons/MiniButton.d.ts +1 -3
- package/lib/common/src/components/Buttons/MiniButton.js +1 -3
- package/lib/common/src/components/Dnd/{RNDFunc.d.ts → DragArea.d.ts} +1 -1
- package/lib/common/src/components/Dnd/{RNDFunc.js → DragArea.js} +1 -1
- package/lib/common/src/components/Dnd/{RNDFunc3.d.ts → FloatingWindow.d.ts} +7 -7
- package/lib/common/src/components/Dnd/{RNDFunc3.js → FloatingWindow.js} +10 -10
- package/lib/common/src/components/Dnd/OutlineDragDemo.d.ts +1 -0
- package/lib/common/src/components/Dnd/{DraggableOutlineDiv.js → OutlineDragDemo.js} +1 -2
- package/lib/common/src/components/Dnd/Resizable.js +1 -1
- package/lib/common/src/components/Dnd/index.d.ts +3 -3
- package/lib/common/src/components/Dnd/index.js +3 -3
- package/lib/common/src/components/Input.d.ts +5 -5
- package/lib/common/src/components/Input.js +10 -10
- package/lib/common/src/components/Menu/RightMenu.d.ts +25 -5
- package/lib/common/src/components/Menu/RightMenu.js +27 -19
- package/lib/common/src/components/Menu/RightMenuStore.d.ts +2 -2
- package/lib/common/src/components/Menu/RightMenuStore.js +1 -1
- package/lib/common/src/components/Modal/LeftModal.js +4 -4
- package/lib/common/src/components/Modal/Modal.d.ts +11 -17
- package/lib/common/src/components/Modal/Modal.js +15 -21
- package/lib/common/src/components/Modal/ModalContextProvider.d.ts +0 -6
- package/lib/common/src/components/Modal/ModalContextProvider.js +2 -9
- package/lib/common/src/components/Other.d.ts +2 -2
- package/lib/common/src/components/Other.js +5 -5
- package/lib/common/src/components/Parameters.d.ts +3 -3
- package/lib/common/src/components/Parameters.js +3 -3
- package/lib/common/src/components/{ParametersEngine.d.ts → ParamsEditor.d.ts} +1 -1
- package/lib/common/src/components/{ParametersEngine.js → ParamsEditor.js} +10 -10
- package/lib/common/src/components/Settings/SettingsDialog.d.ts +4 -4
- package/lib/common/src/components/Toolbar/Toolbar.d.ts +50 -37
- package/lib/common/src/components/Toolbar/Toolbar.js +60 -23
- package/lib/common/src/components/UiSlot/UiSlot.d.ts +3 -3
- package/lib/common/src/components/UiSlot/UiSlot.js +6 -6
- package/lib/common/src/grid/agGrid4/agGrid4.d.ts +4 -4
- package/lib/common/src/grid/agGrid4/agGrid4.js +6 -6
- package/lib/common/src/grid/agGrid4/index.d.ts +3 -3
- package/lib/common/src/grid/agGrid4/index.js +1 -1
- package/lib/common/src/grid/agGrid4/theme.d.ts +3 -3
- package/lib/common/src/grid/columnState/CardList.d.ts +11 -0
- package/lib/common/src/grid/columnState/CardList.js +28 -0
- package/lib/common/src/grid/columnState/ColumnDots.d.ts +9 -0
- package/lib/common/src/grid/columnState/ColumnDots.js +121 -0
- package/lib/common/src/grid/columnState/ColumnsMenu.d.ts +65 -0
- package/lib/common/src/grid/columnState/ColumnsMenu.js +123 -0
- package/lib/common/src/grid/columnState/columnState.d.ts +113 -0
- package/lib/common/src/grid/columnState/columnState.js +284 -0
- package/lib/common/src/grid/columnState/index.d.ts +4 -0
- package/lib/common/src/grid/columnState/index.js +4 -0
- package/lib/common/src/hooks/index.d.ts +1 -1
- package/lib/common/src/hooks/index.js +1 -1
- package/lib/common/src/hooks/useKeyboard.d.ts +18 -0
- package/lib/common/src/hooks/{useAddDownAnyKey.js → useKeyboard.js} +16 -24
- package/lib/common/src/hooks/useObserveStore.d.ts +12 -12
- package/lib/common/src/hooks/useObserveStore.js +2 -2
- package/lib/common/src/hooks/useOutside.d.ts +12 -19
- package/lib/common/src/hooks/useOutside.js +9 -16
- package/lib/common/src/hooks/useReorder.d.ts +4 -4
- package/lib/common/src/hooks/useReorderBoard.d.ts +15 -15
- package/lib/common/src/hooks/useReplay.d.ts +9 -9
- package/lib/common/src/hooks/useReplay.js +7 -7
- package/lib/common/src/logs/logs.d.ts +3 -3
- package/lib/common/src/logs/logs.js +11 -13
- package/lib/common/src/logs/{logs3.d.ts → logsContext.d.ts} +4 -4
- package/lib/common/src/logs/{logs3.js → logsContext.js} +4 -4
- package/lib/common/src/menu/menu.d.ts +16 -16
- package/lib/common/src/menu/menu.js +12 -11
- package/lib/common/src/menu/menuMouse.d.ts +71 -13
- package/lib/common/src/menu/menuMouse.js +189 -21
- package/lib/common/src/menu/menuR.d.ts +3 -3
- package/lib/common/src/menu/menuR.js +5 -5
- package/lib/common/src/styles/styleGrid.d.ts +1 -1
- package/lib/common/src/styles/tokens.d.ts +1 -1
- package/lib/common/src/styles/tokens.js +1 -1
- package/lib/common/src/utils/cache.d.ts +11 -11
- package/lib/common/src/utils/cache.js +0 -0
- package/lib/common/src/utils/gridRows.d.ts +37 -0
- package/lib/common/src/utils/{applyTransactionAsyncUpdate.js → gridRows.js} +6 -38
- package/lib/common/src/utils/index.d.ts +2 -2
- package/lib/common/src/utils/index.js +2 -2
- package/lib/common/src/utils/inputAutoStep.d.ts +2 -2
- package/lib/common/src/utils/inputAutoStep.js +26 -37
- package/lib/common/src/utils/{mapMemory.d.ts → memoryStore.d.ts} +13 -13
- package/lib/common/src/utils/{mapMemory.js → memoryStore.js} +32 -32
- package/lib/common/src/utils/observableMap.d.ts +5 -5
- package/lib/common/src/utils/observableMap.js +3 -3
- package/lib/common/updateBy.js +2 -2
- package/lib/style/style.css +3 -3
- package/lib/style/tokens.css +1 -1
- package/package.json +49 -49
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +0 -2
- package/lib/common/src/hooks/useAddDownAnyKey.d.ts +0 -24
- package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +0 -53
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// ColumnDots - the mobile column selector: a track of marks (one per column)
|
|
3
|
+
// with dots on the columns that are currently shown. Placing a dot IS showing
|
|
4
|
+
// the column; the card view (CardList) rebuilds from the same config live.
|
|
5
|
+
// Gestures (pointer events, mouse + touch):
|
|
6
|
+
// drag a dot along the track -> the dot slides to another (empty) mark: that
|
|
7
|
+
// column replaces the old one
|
|
8
|
+
// swipe a dot UP (flick off) -> the dot tears off the track: column hidden
|
|
9
|
+
// tap an empty mark -> a dot appears there: column shown
|
|
10
|
+
// tap a dot (no move) -> select the field (highlight); the sort
|
|
11
|
+
// button cycles asc -> desc -> off on the
|
|
12
|
+
// SELECTED field. Sort is sticky: selecting
|
|
13
|
+
// another dot does not touch it, and it may
|
|
14
|
+
// point at a hidden column.
|
|
15
|
+
// No ag-grid, no storage - only the columnState config.
|
|
16
|
+
import { useRef, useState } from 'react';
|
|
17
|
+
/** Dominant-axis thresholds: a gesture is a REMOVE only when it is clearly
|
|
18
|
+
* vertical and clearly upward - a horizontal drag or a page scroll never is. */
|
|
19
|
+
const REMOVE_DY = 32;
|
|
20
|
+
const MOVE_SLOP = 4;
|
|
21
|
+
export function ColumnDots(p) {
|
|
22
|
+
const cfg = p.state.api.useConfig();
|
|
23
|
+
const cols = p.state.columns;
|
|
24
|
+
const byKey = new Map(cols.map(c => [c.key, c]));
|
|
25
|
+
const max = p.max ?? 4;
|
|
26
|
+
const order = cfg.order;
|
|
27
|
+
const n = order.length;
|
|
28
|
+
const visibleKeys = order.filter(k => cfg.visible[k] != false);
|
|
29
|
+
const trackRef = useRef(null);
|
|
30
|
+
const gestureRef = useRef(null);
|
|
31
|
+
const [drag, setDrag] = useState(null);
|
|
32
|
+
const [selected, setSelected] = useState(null);
|
|
33
|
+
const pct = (i) => n <= 1 ? 50 : (i * 100) / (n - 1);
|
|
34
|
+
const short = (k) => k ? (byKey.get(k)?.short ?? byKey.get(k)?.title ?? k) : '';
|
|
35
|
+
function downDot(key, e) {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
38
|
+
gestureRef.current = { key, startX: e.clientX, startY: e.clientY, moved: false };
|
|
39
|
+
}
|
|
40
|
+
function moveDot(e) {
|
|
41
|
+
const g = gestureRef.current;
|
|
42
|
+
if (!g)
|
|
43
|
+
return;
|
|
44
|
+
const dx = e.clientX - g.startX;
|
|
45
|
+
const dy = e.clientY - g.startY;
|
|
46
|
+
if (!g.moved && Math.hypot(dx, dy) < MOVE_SLOP)
|
|
47
|
+
return;
|
|
48
|
+
g.moved = true;
|
|
49
|
+
const r = trackRef.current?.getBoundingClientRect();
|
|
50
|
+
if (!r)
|
|
51
|
+
return;
|
|
52
|
+
const step = n > 1 ? r.width / (n - 1) : r.width;
|
|
53
|
+
const from = order.indexOf(g.key);
|
|
54
|
+
const off = -dy > REMOVE_DY && -dy > Math.abs(dx);
|
|
55
|
+
const to = Math.max(0, Math.min(n - 1, Math.round(from + dx / step)));
|
|
56
|
+
setDrag({ key: g.key, dx, dy, to, off });
|
|
57
|
+
}
|
|
58
|
+
function upDot() {
|
|
59
|
+
const g = gestureRef.current;
|
|
60
|
+
gestureRef.current = null;
|
|
61
|
+
const st = drag;
|
|
62
|
+
setDrag(null);
|
|
63
|
+
if (!g)
|
|
64
|
+
return;
|
|
65
|
+
if (!g.moved) { // tap = select the field
|
|
66
|
+
setSelected(g.key);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!st)
|
|
70
|
+
return;
|
|
71
|
+
const meta = byKey.get(g.key);
|
|
72
|
+
if (st.off) { // swipe up = tear the dot off (fixed and the last dot stay)
|
|
73
|
+
if (!meta?.fixed && visibleKeys.length > 1) {
|
|
74
|
+
p.state.api.show(g.key, false);
|
|
75
|
+
if (selected == g.key)
|
|
76
|
+
setSelected(null);
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const target = order[st.to];
|
|
81
|
+
if (target && target != g.key && cfg.visible[target] == false && !meta?.fixed) {
|
|
82
|
+
// the dot slides to another mark: that column takes this slot
|
|
83
|
+
p.state.api.setConfig({ ...cfg, visible: { ...cfg.visible, [g.key]: false, [target]: true } });
|
|
84
|
+
if (selected == g.key)
|
|
85
|
+
setSelected(target);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function tapMark(key) {
|
|
89
|
+
if (cfg.visible[key] != false)
|
|
90
|
+
return;
|
|
91
|
+
if (visibleKeys.length >= max)
|
|
92
|
+
return;
|
|
93
|
+
p.state.api.show(key, true);
|
|
94
|
+
setSelected(key);
|
|
95
|
+
}
|
|
96
|
+
const sortLabel = cfg.sort ? `${short(cfg.sort.key)} ${cfg.sort.dir == 'asc' ? '↑' : '↓'}` : 'off';
|
|
97
|
+
return _jsxs("div", { className: p.className, style: { userSelect: 'none', ...p.style }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 6, fontSize: 12 }, children: [_jsxs("span", { style: { color: '#57606a' }, children: [visibleKeys.length, "/", max, " fields"] }), _jsx("span", { style: { flex: 1 } }), _jsxs("span", { style: { color: '#57606a' }, children: ["field: ", _jsx("b", { children: selected ? short(selected) : '—' })] }), _jsxs("button", { disabled: !selected, title: 'Sort by the selected field: asc -> desc -> off', onClick: () => selected && p.state.api.toggleSort(selected), style: { border: '1px solid #6e7781', borderRadius: 6, padding: '2px 8px', fontSize: 12, cursor: selected ? 'pointer' : 'default', opacity: selected ? 1 : 0.5, background: '#fff' }, children: ["\u21C5 sort: ", sortLabel] })] }), _jsxs("div", { ref: trackRef, style: { position: 'relative', height: 56, margin: '0 14px', touchAction: 'none' }, children: [_jsx("div", { style: { position: 'absolute', left: -8, right: -8, top: 27, height: 2, background: '#d0d7de', borderRadius: 1 } }), order.map((k, i) => {
|
|
98
|
+
const vis = cfg.visible[k] != false;
|
|
99
|
+
const isSorted = cfg.sort?.key == k;
|
|
100
|
+
return _jsxs("div", { onPointerUp: () => tapMark(k), style: { position: 'absolute', left: `${pct(i)}%`, top: 0, width: 44, height: 56, marginLeft: -22, cursor: !vis && visibleKeys.length < max ? 'pointer' : 'default' }, children: [isSorted && _jsx("div", { style: { position: 'absolute', left: 0, right: 0, top: 0, textAlign: 'center', fontSize: 11, color: '#0969da', fontWeight: 700 }, children: cfg.sort.dir == 'asc' ? '↑' : '↓' }), _jsx("div", { style: { position: 'absolute', left: '50%', top: 24, width: 8, height: 8, marginLeft: -4, borderRadius: 4, background: vis ? 'transparent' : '#afb8c1' } }), _jsx("div", { style: { position: 'absolute', left: 0, right: 0, top: 40, textAlign: 'center', fontSize: 10, color: vis ? '#24292f' : '#8c959f', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: short(k) })] }, 'm' + k);
|
|
101
|
+
}), order.map((k, i) => {
|
|
102
|
+
if (cfg.visible[k] == false)
|
|
103
|
+
return null;
|
|
104
|
+
const meta = byKey.get(k);
|
|
105
|
+
const d = drag?.key == k ? drag : null;
|
|
106
|
+
const removing = !!d?.off && !meta?.fixed && visibleKeys.length > 1;
|
|
107
|
+
return _jsx("div", { onPointerDown: e => downDot(k, e), onPointerMove: moveDot, onPointerUp: upDot, onPointerCancel: upDot, style: {
|
|
108
|
+
position: 'absolute', left: `${pct(i)}%`, top: 28, width: 44, height: 44,
|
|
109
|
+
margin: '-22px 0 0 -22px', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
110
|
+
cursor: 'grab', zIndex: d ? 3 : 2,
|
|
111
|
+
transform: d ? `translate(${d.dx}px, ${removing ? d.dy : 0}px)` : undefined,
|
|
112
|
+
transition: d ? undefined : 'transform 0.15s ease',
|
|
113
|
+
opacity: removing ? 0.4 : 1,
|
|
114
|
+
}, children: _jsx("div", { style: {
|
|
115
|
+
width: 18, height: 18, borderRadius: 9,
|
|
116
|
+
background: selected == k ? '#0969da' : '#24292f',
|
|
117
|
+
border: meta?.fixed ? '2px solid #afb8c1' : selected == k ? '2px solid #b6d4fe' : '2px solid transparent',
|
|
118
|
+
boxShadow: d ? '0 3px 10px rgba(0,0,0,0.35)' : undefined,
|
|
119
|
+
} }) }, 'd' + k);
|
|
120
|
+
})] })] });
|
|
121
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ColumnStateController, ColumnsConfig } from './columnState';
|
|
3
|
+
export type MenuStripItem = {
|
|
4
|
+
/** stable id; reported back on click / in the reorder commit */
|
|
5
|
+
key: string;
|
|
6
|
+
/** full name - tooltip / accessible label */
|
|
7
|
+
title: string;
|
|
8
|
+
/** caption on the button (falls back to title) */
|
|
9
|
+
short?: string;
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
/** 'disabled' = the slot exists but its target is gone (column removed
|
|
12
|
+
* from the live grid): the button stays in place, inert */
|
|
13
|
+
state: 'on' | 'off' | 'disabled';
|
|
14
|
+
/** adornment for "on with extras": sub-column dots, mode marks... */
|
|
15
|
+
marks?: React.ReactNode;
|
|
16
|
+
/** cannot be dragged (fixed columns) */
|
|
17
|
+
fixed?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** The presentation layer: renders the buttons in the given order, reports
|
|
20
|
+
* clicks (onItem) and drag-reorders (onMove) - never interprets either.
|
|
21
|
+
* Disabled buttons do not report clicks; fixed ones do not drag. */
|
|
22
|
+
export declare function MenuStrip(p: {
|
|
23
|
+
items: MenuStripItem[];
|
|
24
|
+
/** a click happened on this key - the MEANING lives a layer up */
|
|
25
|
+
onItem?: (key: string, e: React.MouseEvent) => void;
|
|
26
|
+
/** present = items are drag-reorderable (one commit per drop) */
|
|
27
|
+
onMove?: (order: string[]) => void;
|
|
28
|
+
/** preview rule for the drop (fixed pinning etc.) - same contract as
|
|
29
|
+
* useReorder.move, so the preview shows exactly what onMove receives */
|
|
30
|
+
move?: (order: string[], key: string, to: number) => string[];
|
|
31
|
+
/** trailing buttons after a divider, OUTSIDE the reorder list (mode
|
|
32
|
+
* cyclers, actions...); their clicks go to onItem like any other key */
|
|
33
|
+
tail?: MenuStripItem[];
|
|
34
|
+
/** hold before a touch drag starts, ms (default 150 - keeps page scroll usable) */
|
|
35
|
+
holdMs?: number;
|
|
36
|
+
/** icon-only buttons (letters stand in for a missing icon) */
|
|
37
|
+
compact?: boolean;
|
|
38
|
+
className?: string;
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
/** The binding layer: MenuStrip fed by a ColumnStateController. Buttons follow
|
|
42
|
+
* config.order (the grid mirror is free: both sides subscribe to the same
|
|
43
|
+
* config); state per button = disabled (column absent from the live grid) /
|
|
44
|
+
* on / off. Default click toggles visibility; pass onItem for richer,
|
|
45
|
+
* multi-state semantics - the strip below stays untouched. */
|
|
46
|
+
export declare function ColumnsMenu(p: {
|
|
47
|
+
state: ColumnStateController;
|
|
48
|
+
/** what a click MEANS - a separate layer by design. Default: toggle the
|
|
49
|
+
* column's visibility (fixed columns ignore the default). */
|
|
50
|
+
onItem?: (key: string, e: React.MouseEvent) => void;
|
|
51
|
+
/** "on with extras" adornment per column (enabled sub-columns, strategies...) */
|
|
52
|
+
marks?: (key: string, cfg: ColumnsConfig) => React.ReactNode;
|
|
53
|
+
/** trailing non-column buttons (table standards cycler etc.) */
|
|
54
|
+
tail?: MenuStripItem[];
|
|
55
|
+
/** clicks on tail buttons */
|
|
56
|
+
onTail?: (key: string, e: React.MouseEvent) => void;
|
|
57
|
+
/** drag reorder of the column buttons, mirrored to the grid (default on) */
|
|
58
|
+
reorder?: boolean;
|
|
59
|
+
holdMs?: number;
|
|
60
|
+
/** icon-only buttons: the column's icon, or the first letters of its
|
|
61
|
+
* short/title as a text pseudo-icon (full title in the tooltip) */
|
|
62
|
+
compact?: boolean;
|
|
63
|
+
className?: string;
|
|
64
|
+
style?: React.CSSProperties;
|
|
65
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ColumnsMenu - the icon menu over a columnState config: one button per
|
|
3
|
+
// column, a 1:1 mirror of the grid. Reorder a column in the grid - the buttons
|
|
4
|
+
// reorder; drag a button - the grid reorders. Two DECOUPLED layers, because
|
|
5
|
+
// "what a click means" is deliberately not this strip's business:
|
|
6
|
+
//
|
|
7
|
+
// MenuStrip - pure presentation. Ordered buttons, each in one of three
|
|
8
|
+
// states ('on' / 'off' / 'disabled') plus an optional
|
|
9
|
+
// adornment (marks) for "on with extras" (sub-columns,
|
|
10
|
+
// naming strategies...). It reports clicks and drag-reorders;
|
|
11
|
+
// it never interprets them. Reusable for ANY button strip
|
|
12
|
+
// with multi-state buttons (table standards cyclers etc.).
|
|
13
|
+
//
|
|
14
|
+
// ColumnsMenu - the binding to a ColumnStateController: derives the items
|
|
15
|
+
// from order/visibility/presence, default click = toggle the
|
|
16
|
+
// column, drag = api.move. The click meaning is overridable
|
|
17
|
+
// (onItem) - multi-state columns plug in without touching the
|
|
18
|
+
// strip.
|
|
19
|
+
//
|
|
20
|
+
// A column REMOVED from the live grid (dynamic defs, "drop empty columns"
|
|
21
|
+
// standards) keeps its button: it renders 'disabled' (dashed, inert) and
|
|
22
|
+
// comes back to life when the column returns. No ag-grid, no storage here.
|
|
23
|
+
import { useRef } from 'react';
|
|
24
|
+
import { useReorder } from '../../hooks/useReorder';
|
|
25
|
+
function MenuButton(p) {
|
|
26
|
+
const it = p.item;
|
|
27
|
+
const disabled = it.state == 'disabled';
|
|
28
|
+
const on = it.state == 'on';
|
|
29
|
+
const abbr = p.compact && it.icon == null
|
|
30
|
+
? _jsx("span", { style: { fontSize: 10, fontWeight: 700, letterSpacing: 0.5, textTransform: 'uppercase' }, children: (it.short ?? it.title).slice(0, 3) }) : null;
|
|
31
|
+
return _jsxs("div", { title: it.title, onClick: disabled ? undefined : e => p.onItem?.(it.key, e), onMouseDown: p.drag?.onMouseDown, onTouchStart: p.drag?.onTouchStart, style: {
|
|
32
|
+
position: 'relative', display: 'flex', alignItems: 'center', gap: 4,
|
|
33
|
+
padding: '3px 9px', borderRadius: 6, fontSize: 12, lineHeight: '16px',
|
|
34
|
+
whiteSpace: 'nowrap', cursor: disabled ? 'default' : 'pointer',
|
|
35
|
+
...(disabled ? { border: '1px dashed #d0d7de', background: '#f6f8fa', color: '#c4ccd4' }
|
|
36
|
+
: on ? { border: '1px solid #24292f', background: '#24292f', color: '#fff' }
|
|
37
|
+
: { border: '1px solid #d0d7de', background: '#fff', color: '#8c959f', textDecoration: 'line-through' }),
|
|
38
|
+
...(it.fixed ? { boxShadow: '0 0 0 2px #eaeef2' } : null),
|
|
39
|
+
...p.style,
|
|
40
|
+
}, children: [it.icon != null && _jsx("span", { style: { display: 'inline-flex', alignItems: 'center' }, children: it.icon }), abbr, !p.compact && _jsx("span", { children: it.short ?? it.title }), it.marks != null && _jsx("span", { style: { fontSize: 9, opacity: 0.9 }, children: it.marks })] });
|
|
41
|
+
}
|
|
42
|
+
/** The presentation layer: renders the buttons in the given order, reports
|
|
43
|
+
* clicks (onItem) and drag-reorders (onMove) - never interprets either.
|
|
44
|
+
* Disabled buttons do not report clicks; fixed ones do not drag. */
|
|
45
|
+
export function MenuStrip(p) {
|
|
46
|
+
const reorder = useReorder({
|
|
47
|
+
order: p.items.map(i => i.key),
|
|
48
|
+
commit: next => p.onMove?.(next),
|
|
49
|
+
move: p.move,
|
|
50
|
+
canDrag: k => !!p.onMove && !p.items.find(i => i.key == k)?.fixed,
|
|
51
|
+
holdMs: p.holdMs ?? 150,
|
|
52
|
+
});
|
|
53
|
+
// A drag that ends over the button it started on still produces a browser
|
|
54
|
+
// click - without this guard every snapped-back drag would ALSO toggle the
|
|
55
|
+
// button. Track the press point; a click that travelled is not a click.
|
|
56
|
+
const down = useRef(null);
|
|
57
|
+
function onItem(key, e) {
|
|
58
|
+
const d = down.current;
|
|
59
|
+
down.current = null;
|
|
60
|
+
if (d && Math.hypot(e.clientX - d.x, e.clientY - d.y) > 4)
|
|
61
|
+
return;
|
|
62
|
+
p.onItem?.(key, e);
|
|
63
|
+
}
|
|
64
|
+
return _jsxs("div", { className: p.className, style: { display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap', userSelect: 'none', ...p.style }, children: [_jsx("div", { ref: reorder.listRef, style: { display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }, children: p.items.map(it => {
|
|
65
|
+
const r = reorder.item(it.key);
|
|
66
|
+
const drag = {
|
|
67
|
+
onMouseDown: (e) => {
|
|
68
|
+
down.current = { x: e.clientX, y: e.clientY };
|
|
69
|
+
r.props.onMouseDown(e);
|
|
70
|
+
},
|
|
71
|
+
onTouchStart: r.props.onTouchStart,
|
|
72
|
+
};
|
|
73
|
+
return _jsx(MenuButton, { item: it, onItem: onItem, drag: drag, compact: p.compact, style: {
|
|
74
|
+
...r.style,
|
|
75
|
+
...(r.dragging ? { zIndex: 3, boxShadow: '0 3px 10px rgba(0,0,0,0.35)' }
|
|
76
|
+
: r.active ? { transition: 'transform 0.15s ease' } : null),
|
|
77
|
+
} }, it.key);
|
|
78
|
+
}) }), !!p.tail?.length && _jsxs(_Fragment, { children: [_jsx("div", { style: { width: 1, alignSelf: 'stretch', margin: '1px 2px', background: '#d0d7de' } }), p.tail.map(it => _jsx(MenuButton, { item: it, onItem: p.onItem, compact: p.compact }, it.key))] })] });
|
|
79
|
+
}
|
|
80
|
+
/** The binding layer: MenuStrip fed by a ColumnStateController. Buttons follow
|
|
81
|
+
* config.order (the grid mirror is free: both sides subscribe to the same
|
|
82
|
+
* config); state per button = disabled (column absent from the live grid) /
|
|
83
|
+
* on / off. Default click toggles visibility; pass onItem for richer,
|
|
84
|
+
* multi-state semantics - the strip below stays untouched. */
|
|
85
|
+
export function ColumnsMenu(p) {
|
|
86
|
+
const cfg = p.state.api.useConfig();
|
|
87
|
+
const present = p.state.api.usePresent();
|
|
88
|
+
const byKey = new Map(p.state.columns.map(c => [c.key, c]));
|
|
89
|
+
const items = cfg.order.filter(k => byKey.has(k)).map(k => {
|
|
90
|
+
const c = byKey.get(k);
|
|
91
|
+
return {
|
|
92
|
+
key: k, title: c.title, short: c.short, icon: c.icon, fixed: c.fixed,
|
|
93
|
+
state: present && !present[k] ? 'disabled' : cfg.visible[k] != false ? 'on' : 'off',
|
|
94
|
+
marks: p.marks?.(k, cfg),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
function onItem(key, e) {
|
|
98
|
+
if (!byKey.has(key))
|
|
99
|
+
return p.onTail?.(key, e);
|
|
100
|
+
if (p.onItem)
|
|
101
|
+
return p.onItem(key, e);
|
|
102
|
+
if (byKey.get(key)?.fixed)
|
|
103
|
+
return;
|
|
104
|
+
p.state.api.show(key, cfg.visible[key] == false);
|
|
105
|
+
}
|
|
106
|
+
/** Simulated drop with the same fixed pinning as the config's normalize():
|
|
107
|
+
* the drag preview never disagrees with where the button actually lands. */
|
|
108
|
+
function movedOrder(order, key, to) {
|
|
109
|
+
const next = order.slice();
|
|
110
|
+
const from = next.indexOf(key);
|
|
111
|
+
if (from == -1)
|
|
112
|
+
return next;
|
|
113
|
+
next.splice(from, 1);
|
|
114
|
+
next.splice(Math.max(0, Math.min(next.length, to)), 0, key);
|
|
115
|
+
const res = next.filter(k => !byKey.get(k)?.fixed);
|
|
116
|
+
p.state.columns.forEach(function pinFixed(c, i) {
|
|
117
|
+
if (c.fixed)
|
|
118
|
+
res.splice(Math.min(i, res.length), 0, c.key);
|
|
119
|
+
});
|
|
120
|
+
return res;
|
|
121
|
+
}
|
|
122
|
+
return _jsx(MenuStrip, { items: items, tail: p.tail, onItem: onItem, onMove: p.reorder == false ? undefined : order => p.state.api.move(order), move: movedOrder, holdMs: p.holdMs, compact: p.compact, className: p.className, style: p.style });
|
|
123
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { GridApi } from 'ag-grid-community';
|
|
3
|
+
export type ColumnMeta = {
|
|
4
|
+
/** stable id (persist key; must equal the grid colId) */
|
|
5
|
+
key: string;
|
|
6
|
+
/** full human name - menus, card labels */
|
|
7
|
+
title: string;
|
|
8
|
+
/** short caption for icons/cards (falls back to title) */
|
|
9
|
+
short?: string;
|
|
10
|
+
/** glyph for icon menus */
|
|
11
|
+
icon?: ReactNode;
|
|
12
|
+
/** group key for columns with sub-columns ("versions" etc.) */
|
|
13
|
+
group?: string;
|
|
14
|
+
/** cannot be hidden or reordered (pinned to its descriptor index) */
|
|
15
|
+
fixed?: boolean;
|
|
16
|
+
/** default true */
|
|
17
|
+
defaultVisible?: boolean;
|
|
18
|
+
/** role in the mobile card view: 'title' = card header, 'accent' = badge */
|
|
19
|
+
cardRole?: 'title' | 'accent';
|
|
20
|
+
};
|
|
21
|
+
export type ColumnsSort = {
|
|
22
|
+
key: string;
|
|
23
|
+
dir: 'asc' | 'desc';
|
|
24
|
+
};
|
|
25
|
+
export type ColumnsConfig = {
|
|
26
|
+
/** schema version of the persisted shape */
|
|
27
|
+
v: number;
|
|
28
|
+
/** column keys, display order */
|
|
29
|
+
order: string[];
|
|
30
|
+
visible: {
|
|
31
|
+
[key: string]: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** column widths - written by the grid adapter only */
|
|
34
|
+
width: {
|
|
35
|
+
[key: string]: number;
|
|
36
|
+
};
|
|
37
|
+
/** STICKY sort: independent of visibility and of any UI selection, may point
|
|
38
|
+
* at a hidden column; changes only by an explicit toggle or a header click */
|
|
39
|
+
sort: ColumnsSort | null;
|
|
40
|
+
/** grid filterModel - written by the grid adapter only */
|
|
41
|
+
filter: {
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
};
|
|
44
|
+
/** group key -> enabled sub-column keys */
|
|
45
|
+
groups: {
|
|
46
|
+
[group: string]: string[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare function createColumnState(opts: {
|
|
50
|
+
/** persistence key (memoryProps -> memoryCache), like createToolbar */
|
|
51
|
+
key: string;
|
|
52
|
+
/** column descriptors; the config only ever references them by key */
|
|
53
|
+
columns: ColumnMeta[];
|
|
54
|
+
/** defaults; missing fields are derived from columns */
|
|
55
|
+
def?: Partial<ColumnsConfig>;
|
|
56
|
+
/** grid->store save debounce, ms (default 300) */
|
|
57
|
+
saveMs?: number;
|
|
58
|
+
}): {
|
|
59
|
+
/** The column descriptors this state was created over - UI components
|
|
60
|
+
* (dots, cards, icon menus) render from these + the config. */
|
|
61
|
+
columns: readonly ColumnMeta[];
|
|
62
|
+
api: {
|
|
63
|
+
getConfig: () => ColumnsConfig;
|
|
64
|
+
setConfig: (next: ColumnsConfig) => void;
|
|
65
|
+
useConfig: () => ColumnsConfig;
|
|
66
|
+
onChange: import("wenay-common2").ListenApi<[ColumnsConfig]>;
|
|
67
|
+
reset: () => void;
|
|
68
|
+
show: (key: string, on: boolean) => void;
|
|
69
|
+
move: (order: string[]) => void;
|
|
70
|
+
setSort: (sort: ColumnsSort | null) => void;
|
|
71
|
+
toggleSort: (key: string) => void;
|
|
72
|
+
visibleKeys: () => string[];
|
|
73
|
+
getPresent: () => {
|
|
74
|
+
[key: string]: true;
|
|
75
|
+
} | null;
|
|
76
|
+
usePresent: () => {
|
|
77
|
+
[key: string]: true;
|
|
78
|
+
} | null;
|
|
79
|
+
isPresent: (key: string) => boolean;
|
|
80
|
+
setPresent: (keys: string[] | null) => void;
|
|
81
|
+
listSource: {
|
|
82
|
+
useConfig(): {
|
|
83
|
+
order: string[];
|
|
84
|
+
visible: {
|
|
85
|
+
[key: string]: boolean;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
getConfig(): {
|
|
89
|
+
order: string[];
|
|
90
|
+
visible: {
|
|
91
|
+
[key: string]: boolean;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
setConfig(next: {
|
|
95
|
+
order: string[];
|
|
96
|
+
visible: {
|
|
97
|
+
[k: string]: boolean;
|
|
98
|
+
};
|
|
99
|
+
}): void;
|
|
100
|
+
onChange: (cb: (cfg: {
|
|
101
|
+
order: string[];
|
|
102
|
+
visible: {
|
|
103
|
+
[k: string]: boolean;
|
|
104
|
+
};
|
|
105
|
+
}) => void) => import("wenay-common2").ListenOff;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
grid: {
|
|
109
|
+
attach: (api: GridApi) => void;
|
|
110
|
+
detach: () => void;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
export type ColumnStateController = ReturnType<typeof createColumnState>;
|