wenay-react2 1.0.41 → 1.0.42

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.
Files changed (58) hide show
  1. package/doc/EXAMPLE_USAGE.md +58 -2
  2. package/doc/PROJECT_FUNCTIONALITY.md +5 -1
  3. package/doc/changes/v1.0.41.md +23 -0
  4. package/doc/changes/v1.0.42.md +26 -0
  5. package/doc/progress/architecture-fix-queue.md +74 -0
  6. package/doc/progress/style-system-normalization.md +5 -5
  7. package/doc/target/my.md +39 -67
  8. package/doc/wenay-react2-rare.md +118 -13
  9. package/doc/wenay-react2.md +75 -11
  10. package/lib/common/api.d.ts +1 -0
  11. package/lib/common/api.js +8 -4
  12. package/lib/common/src/components/Dnd/DragArea.d.ts +5 -0
  13. package/lib/common/src/components/Dnd/DragArea.js +5 -1
  14. package/lib/common/src/components/Dnd/FloatingWindow.d.ts +9 -5
  15. package/lib/common/src/components/Dnd/FloatingWindow.js +39 -97
  16. package/lib/common/src/components/Dnd/Resizable.d.ts +3 -7
  17. package/lib/common/src/components/Dnd/Resizable.js +4 -3
  18. package/lib/common/src/components/Menu/RightMenu.js +8 -4
  19. package/lib/common/src/components/Menu/RightMenuStore.d.ts +2 -2
  20. package/lib/common/src/components/Menu/RightMenuStore.js +5 -3
  21. package/lib/common/src/components/Modal/LeftModal.js +4 -2
  22. package/lib/common/src/components/Modal/ModalContextProvider.js +5 -16
  23. package/lib/common/src/components/MyResizeObserver.d.ts +24 -0
  24. package/lib/common/src/components/MyResizeObserver.js +49 -0
  25. package/lib/common/src/components/Overlay.d.ts +24 -0
  26. package/lib/common/src/components/Overlay.js +28 -0
  27. package/lib/common/src/components/Settings/SettingsDialog.js +21 -22
  28. package/lib/common/src/components/Toolbar/Toolbar.d.ts +1 -0
  29. package/lib/common/src/components/Toolbar/Toolbar.js +16 -23
  30. package/lib/common/src/grid/columnState/ColumnsMenu.js +4 -13
  31. package/lib/common/src/grid/columnState/columnGrid.d.ts +103 -0
  32. package/lib/common/src/grid/columnState/columnGrid.js +231 -0
  33. package/lib/common/src/grid/columnState/columnState.js +10 -9
  34. package/lib/common/src/grid/columnState/index.js +3 -0
  35. package/lib/common/src/hooks/useDraggable.d.ts +8 -0
  36. package/lib/common/src/hooks/useDraggable.js +13 -4
  37. package/lib/common/src/hooks/useOutside.d.ts +4 -1
  38. package/lib/common/src/hooks/useOutside.js +2 -1
  39. package/lib/common/src/logs/logs.d.ts +96 -5
  40. package/lib/common/src/logs/logs.js +147 -108
  41. package/lib/common/src/logs/logsController.d.ts +3 -0
  42. package/lib/common/src/utils/cache.d.ts +12 -0
  43. package/lib/common/src/utils/cache.js +0 -0
  44. package/lib/common/src/utils/fixedOrder.d.ts +15 -0
  45. package/lib/common/src/utils/fixedOrder.js +30 -0
  46. package/lib/common/src/utils/index.d.ts +2 -0
  47. package/lib/common/src/utils/index.js +2 -0
  48. package/lib/common/src/utils/memoryStore.d.ts +3 -6
  49. package/lib/common/src/utils/memoryStore.js +1 -3
  50. package/lib/common/src/utils/persistedMaps.d.ts +16 -0
  51. package/lib/common/src/utils/persistedMaps.js +5 -0
  52. package/lib/common/src/utils/searchHistory.js +7 -6
  53. package/lib/common/src/utils/structEqual.d.ts +12 -0
  54. package/lib/common/src/utils/structEqual.js +40 -0
  55. package/lib/common/updateBy.d.ts +3 -0
  56. package/lib/common/updateBy.js +57 -22
  57. package/lib/style/style.css +58 -1
  58. package/package.json +2 -2
@@ -22,6 +22,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
22
22
  // comes back to life when the column returns. No ag-grid, no storage here.
23
23
  import { useRef } from 'react';
24
24
  import { useReorder } from '../../hooks/useReorder';
25
+ import { movedOrderWithFixed } from '../../utils/fixedOrder';
25
26
  function colsMenuClass(parts) {
26
27
  return parts.filter(Boolean).join(' ');
27
28
  }
@@ -96,20 +97,10 @@ export function ColumnsMenu(p) {
96
97
  p.state.api.show(key, cfg.visible[key] == false);
97
98
  }
98
99
  /** Simulated drop with the same fixed pinning as the config's normalize():
99
- * the drag preview never disagrees with where the button actually lands. */
100
+ * the drag preview never disagrees with where the button actually lands -
101
+ * both sides now share utils/fixedOrder. */
100
102
  function movedOrder(order, key, to) {
101
- const next = order.slice();
102
- const from = next.indexOf(key);
103
- if (from == -1)
104
- return next;
105
- next.splice(from, 1);
106
- next.splice(Math.max(0, Math.min(next.length, to)), 0, key);
107
- const res = next.filter(k => !byKey.get(k)?.fixed);
108
- p.state.columns.forEach(function pinFixed(c, i) {
109
- if (c.fixed)
110
- res.splice(Math.min(i, res.length), 0, c.key);
111
- });
112
- return res;
103
+ return movedOrderWithFixed(order, key, to, p.state.columns);
113
104
  }
114
105
  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 });
115
106
  }
@@ -0,0 +1,103 @@
1
+ import React from 'react';
2
+ import type { ColDef, ColGroupDef, GridPreDestroyedEvent, GridReadyEvent } from 'ag-grid-community';
3
+ import type { AgGridReactProps } from 'ag-grid-react';
4
+ import { type AgGridTableProps } from '../agGrid4';
5
+ import { createToolbar, type ToolbarConfig, type ToolbarItem, type ToolbarSourceMode } from '../../components/Toolbar';
6
+ import { CardList } from './CardList';
7
+ import { ColumnDots } from './ColumnDots';
8
+ import { ColumnsMenu } from './ColumnsMenu';
9
+ import { type ColumnMeta, type ColumnsConfig, type ColumnStateController } from './columnState';
10
+ export type ColumnGridColumnDef<T extends object> = ColDef<T> | ColGroupDef<T>;
11
+ export type ColumnGridColumn = Partial<Omit<ColumnMeta, 'key'>> & {
12
+ key: string;
13
+ };
14
+ export type ColumnGridToolbarOptions = {
15
+ key?: string;
16
+ items?: ToolbarItem[];
17
+ def?: Partial<ToolbarConfig>;
18
+ settingsItem?: {
19
+ title?: string;
20
+ icon?: React.ReactNode;
21
+ };
22
+ resetItem?: false | {
23
+ title?: string;
24
+ icon?: React.ReactNode;
25
+ defaultVisible?: boolean;
26
+ };
27
+ sourceMode?: ToolbarSourceMode;
28
+ };
29
+ export type ColumnGridOptions<T extends object> = {
30
+ /** Shared persistence key for the column config. */
31
+ key: string;
32
+ /** ag-grid defs. Leaf defs become ColumnMeta automatically by colId/field/headerName. */
33
+ columnDefs?: readonly ColumnGridColumnDef<T>[];
34
+ /** Optional metadata overrides, keyed by colId/field. Missing fields are inferred from columnDefs. */
35
+ columns?: readonly ColumnGridColumn[];
36
+ def?: Partial<ColumnsConfig>;
37
+ /** Optional default data for View; per-render props still override it. */
38
+ data?: readonly T[];
39
+ /** Optional default row id getter for View/CardList and simple table wiring. */
40
+ getId?: (row: T, index: number) => string;
41
+ /** Optional sizeColumnsToFit when the visible column count changes. */
42
+ autoSizeOnColumnCountChange?: boolean;
43
+ saveMs?: number;
44
+ /** Built by default over the same columnState list source; pass false to skip it. */
45
+ toolbar?: false | ColumnGridToolbarOptions;
46
+ };
47
+ export type ColumnGridTableProps<T extends object> = Omit<AgGridTableProps<T>, 'columnDefs' | 'autoSizeColumns' | 'onGridReady' | 'onGridPreDestroyed'> & {
48
+ columnDefs?: AgGridReactProps<T>['columnDefs'];
49
+ /** Defaults to false because columnState restores/persists widths. */
50
+ autoSizeColumns?: boolean;
51
+ onGridReady?: (event: GridReadyEvent<T>) => void;
52
+ onGridPreDestroyed?: (event: GridPreDestroyedEvent<T>) => void;
53
+ /** Defaults from createColumnGrid(opts); fits once when visible column count changes. */
54
+ autoSizeOnColumnCountChange?: boolean;
55
+ };
56
+ export type ColumnGridMenuProps = Omit<React.ComponentProps<typeof ColumnsMenu>, 'state'>;
57
+ export type ColumnGridDotsProps = Omit<React.ComponentProps<typeof ColumnDots>, 'state'>;
58
+ export type ColumnGridCardsProps<T extends object> = Omit<React.ComponentProps<typeof CardList<T>>, 'state'>;
59
+ export type ColumnGridViewMode = 'table' | 'cards';
60
+ export type ColumnGridControls = false | 'auto' | 'toolbar' | 'menu' | 'dots';
61
+ export type ColumnGridToolbar = ReturnType<typeof createToolbar>;
62
+ export type ColumnGridToolbarBarProps = React.ComponentProps<ColumnGridToolbar['Bar']>;
63
+ export type ColumnGridToolbarSettingsProps = React.ComponentProps<ColumnGridToolbar['Settings']>;
64
+ export type ColumnGridViewProps<T extends object> = {
65
+ mode?: ColumnGridViewMode;
66
+ data?: readonly T[];
67
+ getId?: (row: T, index: number) => string;
68
+ controls?: ColumnGridControls;
69
+ table?: ColumnGridTableProps<T>;
70
+ cards?: Omit<ColumnGridCardsProps<T>, 'data'>;
71
+ menu?: ColumnGridMenuProps;
72
+ dots?: ColumnGridDotsProps;
73
+ toolbar?: ColumnGridToolbarBarProps;
74
+ className?: string;
75
+ style?: React.CSSProperties;
76
+ bodyClassName?: string;
77
+ bodyStyle?: React.CSSProperties;
78
+ tableHeight?: React.CSSProperties['height'];
79
+ };
80
+ export type ColumnGridController<T extends object> = {
81
+ state: ColumnStateController;
82
+ toolbar: ColumnGridToolbar | null;
83
+ columns: readonly ColumnMeta[];
84
+ columnDefs: AgGridReactProps<T>['columnDefs'];
85
+ api: ColumnStateController['api'] & {
86
+ tableProps: (props?: ColumnGridTableProps<T>) => AgGridTableProps<T>;
87
+ };
88
+ grid: ColumnStateController['grid'];
89
+ tableProps: (props?: ColumnGridTableProps<T>) => AgGridTableProps<T>;
90
+ Table: (props: ColumnGridTableProps<T>) => React.JSX.Element;
91
+ Menu: (props?: ColumnGridMenuProps) => React.JSX.Element;
92
+ Dots: (props?: ColumnGridDotsProps) => React.JSX.Element;
93
+ Cards: (props: ColumnGridCardsProps<T>) => React.JSX.Element;
94
+ Toolbar: (props?: ColumnGridToolbarBarProps) => React.JSX.Element | null;
95
+ Settings: (props?: ColumnGridToolbarSettingsProps) => React.JSX.Element | null;
96
+ View: (props: ColumnGridViewProps<T>) => React.JSX.Element;
97
+ /** Release factory-lifetime subscriptions; persisted config is untouched. */
98
+ dispose: () => void;
99
+ };
100
+ /** High-level column kit for the common case: one keyed config drives a grid,
101
+ * compact menu, toolbar settings, mobile dots, and card/table views. */
102
+ export declare function createColumnGrid<T extends object>(opts: ColumnGridOptions<T>): ColumnGridController<T>;
103
+ export declare function useColumnGrid<T extends object>(opts: ColumnGridOptions<T>): ColumnGridController<T>;
@@ -0,0 +1,231 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { AgGridTable } from '../agGrid4';
4
+ import { createToolbar } from '../../components/Toolbar';
5
+ import { CardList } from './CardList';
6
+ import { ColumnDots } from './ColumnDots';
7
+ import { ColumnsMenu } from './ColumnsMenu';
8
+ import { createColumnState } from './columnState';
9
+ function isGroupDef(def) {
10
+ return Array.isArray(def.children);
11
+ }
12
+ function textOrUndefined(v) {
13
+ if (v == null || v === '')
14
+ return undefined;
15
+ return String(v);
16
+ }
17
+ function titleFromKey(key) {
18
+ return key
19
+ .replace(/[_-]+/g, ' ')
20
+ .replace(/\b\w/g, s => s.toUpperCase());
21
+ }
22
+ function collectColumnMetas(defs, parentGroup) {
23
+ const res = [];
24
+ for (const def of defs ?? []) {
25
+ if (isGroupDef(def)) {
26
+ const group = textOrUndefined(def.groupId) ?? textOrUndefined(def.headerName) ?? parentGroup;
27
+ res.push(...collectColumnMetas(def.children, group));
28
+ continue;
29
+ }
30
+ const key = textOrUndefined(def.colId) ?? textOrUndefined(def.field);
31
+ if (!key)
32
+ continue;
33
+ res.push({
34
+ key,
35
+ title: textOrUndefined(def.headerName) ?? titleFromKey(key),
36
+ group: parentGroup,
37
+ });
38
+ }
39
+ return res;
40
+ }
41
+ function resolveColumns(opts) {
42
+ const auto = collectColumnMetas(opts.columnDefs);
43
+ const overrides = new Map((opts.columns ?? []).map(c => [c.key, c]));
44
+ const used = new Set();
45
+ const res = [];
46
+ for (const base of auto) {
47
+ if (used.has(base.key))
48
+ continue;
49
+ const patch = overrides.get(base.key);
50
+ used.add(base.key);
51
+ res.push({ ...base, ...patch, key: base.key, title: patch?.title ?? base.title });
52
+ }
53
+ for (const patch of opts.columns ?? []) {
54
+ if (used.has(patch.key))
55
+ continue;
56
+ used.add(patch.key);
57
+ res.push({ ...patch, key: patch.key, title: patch.title ?? titleFromKey(patch.key) });
58
+ }
59
+ return res;
60
+ }
61
+ function cx(parts) {
62
+ return parts.filter(Boolean).join(' ');
63
+ }
64
+ function mergeClass(a, b) {
65
+ return a ? b + ' ' + a : b;
66
+ }
67
+ /** High-level column kit for the common case: one keyed config drives a grid,
68
+ * compact menu, toolbar settings, mobile dots, and card/table views. */
69
+ export function createColumnGrid(opts) {
70
+ const columnDefs = (opts.columnDefs ?? []);
71
+ const state = createColumnState({
72
+ key: opts.key,
73
+ columns: resolveColumns(opts),
74
+ def: opts.def,
75
+ saveMs: opts.saveMs,
76
+ });
77
+ let gridApiForFit = null;
78
+ let fitOnCountChange = opts.autoSizeOnColumnCountChange === true;
79
+ let visibleCount = state.api.visibleKeys().length;
80
+ let fitRaf = 0;
81
+ function scheduleFit() {
82
+ if (!gridApiForFit)
83
+ return;
84
+ cancelAnimationFrame(fitRaf);
85
+ fitRaf = requestAnimationFrame(() => gridApiForFit?.sizeColumnsToFit());
86
+ }
87
+ const offConfigChange = state.api.onChange.on(() => {
88
+ const next = state.api.visibleKeys().length;
89
+ if (next == visibleCount)
90
+ return;
91
+ visibleCount = next;
92
+ if (fitOnCountChange)
93
+ scheduleFit();
94
+ });
95
+ const toolbar = opts.toolbar === false ? null : createToolbar({
96
+ key: opts.toolbar?.key ?? `${opts.key}.toolbar`,
97
+ items: opts.toolbar?.items ?? state.columns.map(c => ({
98
+ key: c.key,
99
+ title: c.title,
100
+ short: c.short,
101
+ icon: c.icon,
102
+ fixed: c.fixed,
103
+ defaultVisible: c.defaultVisible,
104
+ onClick: () => {
105
+ const present = state.api.getPresent();
106
+ if (c.fixed || (present && !present[c.key]))
107
+ return;
108
+ const cfg = state.api.getConfig();
109
+ state.api.show(c.key, cfg.visible[c.key] == false);
110
+ },
111
+ })),
112
+ def: opts.toolbar?.def,
113
+ settingsItem: opts.toolbar?.settingsItem,
114
+ resetItem: opts.toolbar?.resetItem,
115
+ source: state.api.listSource,
116
+ sourceMode: opts.toolbar?.sourceMode,
117
+ });
118
+ function tableProps(props = {}) {
119
+ const { onGridReady, onGridPreDestroyed, autoSizeColumns = false, autoSizeOnColumnCountChange = opts.autoSizeOnColumnCountChange === true, columnDefs: localDefs = columnDefs, ...rest } = props;
120
+ return {
121
+ ...rest,
122
+ columnDefs: localDefs,
123
+ autoSizeColumns,
124
+ onGridReady(event) {
125
+ gridApiForFit = event.api;
126
+ fitOnCountChange = autoSizeOnColumnCountChange;
127
+ state.grid.attach(event.api);
128
+ // re-seed: the column count may have changed while no grid was attached,
129
+ // and the first post-remount onChange must not mis-skip the auto-fit
130
+ visibleCount = state.api.visibleKeys().length;
131
+ if (fitOnCountChange)
132
+ scheduleFit();
133
+ onGridReady?.(event);
134
+ },
135
+ onGridPreDestroyed(event) {
136
+ if (gridApiForFit === event.api)
137
+ gridApiForFit = null;
138
+ cancelAnimationFrame(fitRaf);
139
+ fitRaf = 0;
140
+ state.grid.detach();
141
+ onGridPreDestroyed?.(event);
142
+ },
143
+ };
144
+ }
145
+ function Table(props) {
146
+ return _jsx(AgGridTable, { ...tableProps(props) });
147
+ }
148
+ function Menu(props = {}) {
149
+ return _jsx(ColumnsMenu, { ...props, state: state });
150
+ }
151
+ function Dots(props = {}) {
152
+ return _jsx(ColumnDots, { ...props, max: props.max ?? state.columns.length, className: mergeClass(props.className, 'wenayColumnGridDots'), state: state });
153
+ }
154
+ function Cards(props) {
155
+ return _jsx(CardList, { ...props, state: state });
156
+ }
157
+ function Toolbar(props = {}) {
158
+ if (!toolbar)
159
+ return null;
160
+ const Bar = toolbar.Bar;
161
+ return _jsx(Bar, { ...props });
162
+ }
163
+ function Settings(props = {}) {
164
+ if (!toolbar)
165
+ return null;
166
+ const ToolbarSettings = toolbar.Settings;
167
+ return _jsx(ToolbarSettings, { ...props });
168
+ }
169
+ function controlKind(mode, controls) {
170
+ if (controls === false)
171
+ return null;
172
+ if (!controls || controls == 'auto')
173
+ return 'dots';
174
+ return controls;
175
+ }
176
+ function renderControls(kind, p) {
177
+ if (!kind)
178
+ return null;
179
+ if (kind == 'dots')
180
+ return _jsx(Dots, { ...p.dots });
181
+ if (kind == 'toolbar')
182
+ return _jsx(Toolbar, { settings: true, popAlign: "left", ...p.toolbar });
183
+ return _jsx(Menu, { compact: true, ...p.menu });
184
+ }
185
+ function View(p) {
186
+ const mode = p.mode ?? 'table';
187
+ const kind = controlKind(mode, p.controls);
188
+ const controls = renderControls(kind, p);
189
+ const data = p.data ?? opts.data ?? [];
190
+ const getId = p.getId ?? opts.getId;
191
+ const bodyStyle = mode == 'table'
192
+ ? { height: p.tableHeight ?? 240, minHeight: 0, ...p.bodyStyle }
193
+ : { ...p.bodyStyle };
194
+ const tableGetRowId = p.table?.getRowId ?? (getId
195
+ ? ((params) => getId(params.data, 0))
196
+ : undefined);
197
+ return _jsxs("div", { className: cx(['wenayColumnGrid', `wenayColumnGrid_${mode}`, kind && `wenayColumnGrid_controls_${kind}`, p.className]), style: p.style, children: [_jsx("div", { className: cx(['wenayColumnGridBody', p.bodyClassName]), style: bodyStyle, children: mode == 'cards'
198
+ ? _jsx(Cards, { ...p.cards, data: data, getId: p.cards?.getId ?? getId })
199
+ : _jsx(Table, { ...p.table, rowData: (p.table?.rowData ?? data), getRowId: tableGetRowId }) }), controls && _jsx("div", { className: cx(['wenayColumnGridControls', kind && `wenayColumnGridControls_${kind}`]), children: controls })] });
200
+ }
201
+ /** Release factory-lifetime subscriptions (config onChange, toolbar source, pending fit).
202
+ * Persisted config stays; this is listener lifetime only (HMR, repeated factories). */
203
+ function dispose() {
204
+ offConfigChange();
205
+ toolbar?.api.dispose();
206
+ cancelAnimationFrame(fitRaf);
207
+ fitRaf = 0;
208
+ gridApiForFit = null;
209
+ }
210
+ return {
211
+ state,
212
+ toolbar,
213
+ columns: state.columns,
214
+ columnDefs,
215
+ api: { ...state.api, tableProps },
216
+ grid: state.grid,
217
+ tableProps,
218
+ Table,
219
+ Menu,
220
+ Dots,
221
+ Cards,
222
+ Toolbar,
223
+ Settings,
224
+ View,
225
+ dispose,
226
+ };
227
+ }
228
+ export function useColumnGrid(opts) {
229
+ const [grid] = useState(() => createColumnGrid(opts));
230
+ return grid;
231
+ }
@@ -1,6 +1,8 @@
1
1
  import { listen as createListen } from 'wenay-common2';
2
2
  import { createUpdateApi } from '../../../updateBy';
3
3
  import { memoryGetOrCreate, memoryMarkDirty } from '../../utils/memoryStore';
4
+ import { pinFixedOrder } from '../../utils/fixedOrder';
5
+ import { structEqual } from '../../utils/structEqual';
4
6
  const SCHEMA_V = 1;
5
7
  /** Grid events that mean "the user changed the column layout / sort / filter". */
6
8
  const GRID_EVENTS = ['columnMoved', 'columnResized', 'columnVisible', 'sortChanged', 'filterChanged'];
@@ -74,14 +76,11 @@ export function createColumnState(opts) {
74
76
  const known = new Set(opts.columns.map(c => c.key));
75
77
  const byKey = new Map(opts.columns.map(c => [c.key, c]));
76
78
  const rawOrder = Array.isArray(st.order) ? st.order : [];
77
- const order = rawOrder.filter(k => known.has(k) && !byKey.get(k)?.fixed);
79
+ const prelim = rawOrder.filter(k => known.has(k) && !byKey.get(k)?.fixed);
78
80
  for (const c of opts.columns)
79
- if (!c.fixed && order.indexOf(c.key) == -1)
80
- order.push(c.key);
81
- opts.columns.forEach(function pinFixed(c, i) {
82
- if (c.fixed)
83
- order.splice(Math.min(i, order.length), 0, c.key);
84
- });
81
+ if (!c.fixed && prelim.indexOf(c.key) == -1)
82
+ prelim.push(c.key);
83
+ const order = pinFixedOrder(prelim, opts.columns);
85
84
  const rawVisible = st.visible && typeof st.visible == 'object' ? st.visible : {};
86
85
  const visible = {};
87
86
  for (const c of opts.columns)
@@ -245,13 +244,15 @@ export function createColumnState(opts) {
245
244
  order.push(k);
246
245
  const filter = (gridApi.getFilterModel() ?? {});
247
246
  const next = { ...cfg, order, visible, width, sort, filter };
248
- if (JSON.stringify(next) == JSON.stringify(cfg))
247
+ // structural compare: the grid returns filter models with its own key order,
248
+ // which must not count as a change (stringify used to false-positive here)
249
+ if (structEqual(next, cfg))
249
250
  return;
250
251
  commit(next, true);
251
252
  // normalize() may have corrected the grid's move (a fixed column dragged
252
253
  // away from its pinned index): push the corrected order back so the grid
253
254
  // and the config never disagree
254
- if (JSON.stringify(normalize().order) != JSON.stringify(next.order))
255
+ if (!structEqual(normalize().order, next.order))
255
256
  applyToGrid();
256
257
  }
257
258
  function onGridEvent(e) {
@@ -1,3 +1,6 @@
1
+ // Grid-less barrel: columnState/ColumnsMenu/ColumnDots/CardList must stay importable without
2
+ // pulling ag-grid runtime (mobile/card consumers). createColumnGrid lives in './columnGrid'
3
+ // (it imports AgGridTable + createToolbar) and is exported from the root api, not from here.
1
4
  export * from './columnState';
2
5
  export * from './ColumnsMenu';
3
6
  export * from './ColumnDots';
@@ -8,6 +8,14 @@ export type UseDraggableOptions = {
8
8
  holdMs?: number;
9
9
  onDragEnd?: DragEndCallback;
10
10
  onDragStart?: DragStartCallback;
11
+ /** Imperative per-move-tick callback: fires with the current delta on every
12
+ * mousemove/touchmove of an active drag - NOT on setPosition/resetPosition/cancelDrag
13
+ * and not on release. Goes through a ref, an inline closure does not resubscribe. */
14
+ onMove?: (position: Position) => void;
15
+ /** When false, position lives only in positionRef/onMove and the hook does NOT
16
+ * re-render per move tick (imperative consumers, e.g. the DragBox adapter).
17
+ * isDragging start/end re-renders remain. Default true. */
18
+ trackState?: boolean;
11
19
  };
12
20
  export interface UseDraggableReturn {
13
21
  readonly position: Position;
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo, useRef, useState } from "react";
2
2
  export function useDraggableApi(options = {}) {
3
- const { initialPosition = { x: 0, y: 0 }, holdMs = 500, onDragEnd, onDragStart } = options;
3
+ const { initialPosition = { x: 0, y: 0 }, holdMs = 500, onDragEnd, onDragStart, onMove, trackState = true } = options;
4
4
  const [position, setPositionState] = useState(initialPosition);
5
5
  const positionRef = useRef(position);
6
6
  const holdMsRef = useRef(holdMs);
@@ -11,17 +11,22 @@ export function useDraggableApi(options = {}) {
11
11
  const draggingRef = useRef(false);
12
12
  const onDragEndRef = useRef(onDragEnd);
13
13
  const onDragStartRef = useRef(onDragStart);
14
+ const onMoveRef = useRef(onMove);
15
+ const trackStateRef = useRef(trackState);
14
16
  const holdTimerMouse = useRef(null);
15
17
  const holdTimerTouch = useRef(null);
16
18
  const setPos = (p) => {
17
19
  positionRef.current = p;
18
- setPositionState(p);
20
+ if (trackStateRef.current)
21
+ setPositionState(p);
19
22
  };
20
23
  holdMsRef.current = holdMs;
24
+ trackStateRef.current = trackState;
21
25
  draggingRef.current = draggingMouse || draggingTouch;
22
26
  useEffect(() => {
23
27
  onDragEndRef.current = onDragEnd;
24
28
  onDragStartRef.current = onDragStart;
29
+ onMoveRef.current = onMove;
25
30
  });
26
31
  const cancelMouseHold = useMemo(() => function cancelMouseHold() {
27
32
  if (holdTimerMouse.current != null) {
@@ -77,7 +82,9 @@ export function useDraggableApi(options = {}) {
77
82
  if (!draggingMouse)
78
83
  return;
79
84
  const handleMouseMove = (e) => {
80
- setPos({ x: e.clientX - offsetMouse.current.x, y: e.clientY - offsetMouse.current.y });
85
+ const p = { x: e.clientX - offsetMouse.current.x, y: e.clientY - offsetMouse.current.y };
86
+ setPos(p);
87
+ onMoveRef.current?.(p);
81
88
  };
82
89
  const handleMouseUp = () => {
83
90
  document.removeEventListener("mousemove", handleMouseMove);
@@ -104,7 +111,9 @@ export function useDraggableApi(options = {}) {
104
111
  const theTouch = Array.from(e.changedTouches).find((t) => t.identifier === offsetTouch.current?.id);
105
112
  if (!theTouch)
106
113
  return;
107
- setPos({ x: theTouch.clientX - offsetTouch.current.x, y: theTouch.clientY - offsetTouch.current.y });
114
+ const p = { x: theTouch.clientX - offsetTouch.current.x, y: theTouch.clientY - offsetTouch.current.y };
115
+ setPos(p);
116
+ onMoveRef.current?.(p);
108
117
  };
109
118
  const handleTouchEnd = (e) => {
110
119
  if (!offsetTouch.current)
@@ -37,6 +37,9 @@ type ButtonBaseProps = {
37
37
  };
38
38
  type ButtonProps = ButtonBaseProps & {
39
39
  statusDef?: boolean;
40
+ /** Persist the open/closed status under this key (module-lifetime). Same naming as FloatingWindow/Resizable/RightMenu. */
41
+ keyForSave?: string;
42
+ /** @deprecated alias of {@link keyForSave}; kept for compatibility, `keyForSave` wins when both are set. */
40
43
  keySave?: string;
41
44
  outClick?: boolean | (() => void);
42
45
  zIndex?: number;
@@ -46,7 +49,7 @@ export declare const OutsideClickArea: React.ForwardRefExoticComponent<React.HTM
46
49
  status?: boolean;
47
50
  zIndex?: number;
48
51
  } & React.RefAttributes<HTMLDivElement>>;
49
- export declare function Button({ keySave, statusDef, outClick, ...data }: ButtonProps): import("react/jsx-runtime").JSX.Element;
52
+ export declare function Button({ keyForSave, keySave, statusDef, outClick, ...data }: ButtonProps): import("react/jsx-runtime").JSX.Element;
50
53
  export declare function HoverButton(props: ButtonBaseProps): import("react/jsx-runtime").JSX.Element;
51
54
  export declare const OutsideButton: typeof Button;
52
55
  export declare function AbsoluteButton(props: Parameters<typeof Button>[0]): import("react/jsx-runtime").JSX.Element;
@@ -73,7 +73,8 @@ function ButtonBase({ children, button, style = {}, className = "", state: [a, s
73
73
  return _jsxs("div", { style: { position: "relative", width: "min-content", ...style }, className: className, children: [_jsx("div", { onClick: () => setA(!a), children: typeof button == "function" ? button(a) : button }), a && (typeof children == "function" ? children({ onClose: () => setA(!a) }) : children)] });
74
74
  }
75
75
  const saveStatus = {};
76
- export function Button({ keySave, statusDef, outClick, ...data }) {
76
+ export function Button({ keyForSave, keySave, statusDef, outClick, ...data }) {
77
+ keySave = keyForSave ?? keySave;
77
78
  if (keySave && saveStatus[keySave] != null)
78
79
  statusDef = saveStatus[keySave];
79
80
  const [status, setStatusRaw] = useState(statusDef ?? false);
@@ -1,8 +1,16 @@
1
1
  import React from "react";
2
- import { getSettingLogs, type LogEntry, type LogsApiOptions } from "./logsController";
2
+ import { GridReadyEvent } from "ag-grid-community";
3
+ import { getSettingLogs, type LogEntry, type LogsApiOptions, type LogsControllerState, type LogsFullState, type LogsMiniState, type LogsSettingsState } from "./logsController";
3
4
  export { createLogsController, createLogsControllerState, getSettingLogs, } from "./logsController";
4
5
  export type { CreateLogsControllerOptions, LogsApiOptions, LogsController, LogsControllerState, LogsFullState, LogsMiniState, } from "./logsController";
5
- export declare function getLogsApi<T extends object = {}>(setting: LogsApiOptions): {
6
+ /** Optional external state for the log views/hooks; every omitted part falls back to the
7
+ * legacy module-level state (datumConst/datumMiniConst/"settingLogs"). */
8
+ export type LogsViewState = {
9
+ full?: LogsFullState<any>;
10
+ mini?: LogsMiniState<any>;
11
+ settings?: LogsSettingsState;
12
+ };
13
+ export declare function getLogsApi<T extends object = {}>(setting: LogsApiOptions, sharedState?: LogsControllerState<T>): {
6
14
  addLogs: (input: import("./logsController").LogInput<T>) => LogEntry<T>;
7
15
  params: {
8
16
  def: typeof getSettingLogs;
@@ -33,11 +41,90 @@ export declare const logsApi: {
33
41
  PageLogs: typeof PageLogs;
34
42
  };
35
43
  };
36
- declare function InputSettingLogs({}: {
44
+ declare function InputSettingLogs({ settings }: {
37
45
  update?: number;
46
+ settings?: LogsSettingsState;
38
47
  }): import("react/jsx-runtime").JSX.Element;
39
- export declare function PageLogs({ update }: {
48
+ type LogRow = LogEntry<any>;
49
+ /** Controller for the full-page logs table: owns the ag-grid imperative surface that
50
+ * `PageLogs` used to drive inline. The importance filter lives in ONE method
51
+ * (`applyImportanceFilter`) - it used to be duplicated between the settings effect and
52
+ * `onGridReady`. Rows keep the original identity semantics: the grid receives the mount-time
53
+ * snapshot once, later entries arrive as `applyTransactionAsync` copies of the mini feed. */
54
+ export declare function useLogsPageTable(state?: LogsViewState): {
55
+ getApi: () => GridReadyEvent<any, any> | null;
56
+ fit: () => void;
57
+ applyImportanceFilter: (min?: number) => void;
58
+ appendRow: (row: LogRow) => void;
59
+ onGridReady: (a: GridReadyEvent<LogRow>) => void;
60
+ columnDefs: ({
61
+ field: string;
62
+ sort: "desc";
63
+ width: number;
64
+ valueFormatter: (e: import("ag-grid-community").ValueFormatterParams<any, any, any>) => any;
65
+ wrapText?: undefined;
66
+ autoHeight?: undefined;
67
+ } | {
68
+ field: string;
69
+ width: number;
70
+ sort?: undefined;
71
+ valueFormatter?: undefined;
72
+ wrapText?: undefined;
73
+ autoHeight?: undefined;
74
+ } | {
75
+ field: string;
76
+ wrapText: true;
77
+ autoHeight: true;
78
+ width: number;
79
+ sort?: undefined;
80
+ valueFormatter?: undefined;
81
+ })[];
82
+ gridProps: {
83
+ suppressCellFocus: boolean;
84
+ onGridReady: (a: GridReadyEvent<LogRow>) => void;
85
+ defaultColDef: {
86
+ wrapText: true;
87
+ headerClass: () => string;
88
+ resizable: true;
89
+ cellStyle: {
90
+ textAlign: string;
91
+ };
92
+ sortable: true;
93
+ filter: boolean;
94
+ };
95
+ headerHeight: number;
96
+ rowHeight: number;
97
+ autoSizePadding: number;
98
+ rowData: any[];
99
+ columnDefs: ({
100
+ field: string;
101
+ sort: "desc";
102
+ width: number;
103
+ valueFormatter: (e: import("ag-grid-community").ValueFormatterParams<any, any, any>) => any;
104
+ wrapText?: undefined;
105
+ autoHeight?: undefined;
106
+ } | {
107
+ field: string;
108
+ width: number;
109
+ sort?: undefined;
110
+ valueFormatter?: undefined;
111
+ wrapText?: undefined;
112
+ autoHeight?: undefined;
113
+ } | {
114
+ field: string;
115
+ wrapText: true;
116
+ autoHeight: true;
117
+ width: number;
118
+ sort?: undefined;
119
+ valueFormatter?: undefined;
120
+ })[];
121
+ onCellMouseDown: (e: any) => void;
122
+ };
123
+ };
124
+ export type LogsPageTableController = ReturnType<typeof useLogsPageTable>;
125
+ export declare function PageLogs({ update, state }: {
40
126
  update?: number;
127
+ state?: LogsViewState;
41
128
  }): import("react/jsx-runtime").JSX.Element;
42
129
  export type MessageEventLogsItem = {
43
130
  key: string;
@@ -45,6 +132,8 @@ export type MessageEventLogsItem = {
45
132
  };
46
133
  export type UseMessageEventLogsControllerOptions = {
47
134
  maxVisible?: number;
135
+ settings?: LogsSettingsState;
136
+ mini?: LogsMiniState<any>;
48
137
  };
49
138
  export type MessageEventLogsController = {
50
139
  show: boolean;
@@ -65,8 +154,10 @@ export declare function MessageEventLogCard({ logs }: {
65
154
  }): import("react/jsx-runtime").JSX.Element;
66
155
  export declare function useMessageEventLogsController(options?: UseMessageEventLogsControllerOptions): MessageEventLogsController;
67
156
  export declare function MessageEventLogsView({ controller, zIndex, className, style }: MessageEventLogsViewProps): import("react/jsx-runtime").JSX.Element;
68
- export declare function MessageEventLogs({ zIndex }: {
157
+ export declare function MessageEventLogs({ zIndex, settings, mini }: {
69
158
  zIndex?: number;
159
+ settings?: LogsSettingsState;
160
+ mini?: LogsMiniState<any>;
70
161
  }): import("react/jsx-runtime").JSX.Element;
71
162
  export declare function LogsPage({ update }: {
72
163
  update?: number;