wenay-react2 1.0.34 → 1.0.36

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 (95) hide show
  1. package/README.md +12 -56
  2. package/lib/common/api.d.ts +28 -28
  3. package/lib/common/api.js +31 -31
  4. package/lib/common/src/components/Buttons/MiniButton.d.ts +1 -3
  5. package/lib/common/src/components/Buttons/MiniButton.js +1 -3
  6. package/lib/common/src/components/Dnd/{RNDFunc.d.ts → DragArea.d.ts} +1 -1
  7. package/lib/common/src/components/Dnd/{RNDFunc.js → DragArea.js} +1 -1
  8. package/lib/common/src/components/Dnd/{RNDFunc3.d.ts → FloatingWindow.d.ts} +7 -7
  9. package/lib/common/src/components/Dnd/{RNDFunc3.js → FloatingWindow.js} +10 -10
  10. package/lib/common/src/components/Dnd/OutlineDragDemo.d.ts +1 -0
  11. package/lib/common/src/components/Dnd/{DraggableOutlineDiv.js → OutlineDragDemo.js} +1 -2
  12. package/lib/common/src/components/Dnd/Resizable.js +1 -1
  13. package/lib/common/src/components/Dnd/index.d.ts +3 -3
  14. package/lib/common/src/components/Dnd/index.js +3 -3
  15. package/lib/common/src/components/Input.d.ts +5 -5
  16. package/lib/common/src/components/Input.js +10 -10
  17. package/lib/common/src/components/Menu/RightMenu.d.ts +25 -5
  18. package/lib/common/src/components/Menu/RightMenu.js +27 -19
  19. package/lib/common/src/components/Menu/RightMenuStore.d.ts +2 -2
  20. package/lib/common/src/components/Menu/RightMenuStore.js +1 -1
  21. package/lib/common/src/components/Modal/LeftModal.js +4 -4
  22. package/lib/common/src/components/Modal/Modal.d.ts +11 -17
  23. package/lib/common/src/components/Modal/Modal.js +15 -21
  24. package/lib/common/src/components/Modal/ModalContextProvider.d.ts +0 -6
  25. package/lib/common/src/components/Modal/ModalContextProvider.js +2 -9
  26. package/lib/common/src/components/Other.d.ts +2 -2
  27. package/lib/common/src/components/Other.js +5 -5
  28. package/lib/common/src/components/Parameters.d.ts +3 -3
  29. package/lib/common/src/components/Parameters.js +3 -3
  30. package/lib/common/src/components/{ParametersEngine.d.ts → ParamsEditor.d.ts} +1 -1
  31. package/lib/common/src/components/{ParametersEngine.js → ParamsEditor.js} +10 -10
  32. package/lib/common/src/components/Settings/SettingsDialog.d.ts +4 -4
  33. package/lib/common/src/components/Toolbar/Toolbar.d.ts +50 -37
  34. package/lib/common/src/components/Toolbar/Toolbar.js +60 -23
  35. package/lib/common/src/components/UiSlot/UiSlot.d.ts +3 -3
  36. package/lib/common/src/components/UiSlot/UiSlot.js +6 -6
  37. package/lib/common/src/grid/agGrid4/agGrid4.d.ts +4 -4
  38. package/lib/common/src/grid/agGrid4/agGrid4.js +6 -6
  39. package/lib/common/src/grid/agGrid4/index.d.ts +3 -3
  40. package/lib/common/src/grid/agGrid4/index.js +1 -1
  41. package/lib/common/src/grid/agGrid4/theme.d.ts +3 -3
  42. package/lib/common/src/grid/columnState/CardList.d.ts +11 -0
  43. package/lib/common/src/grid/columnState/CardList.js +28 -0
  44. package/lib/common/src/grid/columnState/ColumnDots.d.ts +9 -0
  45. package/lib/common/src/grid/columnState/ColumnDots.js +121 -0
  46. package/lib/common/src/grid/columnState/ColumnsMenu.d.ts +65 -0
  47. package/lib/common/src/grid/columnState/ColumnsMenu.js +123 -0
  48. package/lib/common/src/grid/columnState/columnState.d.ts +113 -0
  49. package/lib/common/src/grid/columnState/columnState.js +284 -0
  50. package/lib/common/src/grid/columnState/index.d.ts +4 -0
  51. package/lib/common/src/grid/columnState/index.js +4 -0
  52. package/lib/common/src/hooks/index.d.ts +1 -1
  53. package/lib/common/src/hooks/index.js +1 -1
  54. package/lib/common/src/hooks/useKeyboard.d.ts +18 -0
  55. package/lib/common/src/hooks/{useAddDownAnyKey.js → useKeyboard.js} +16 -24
  56. package/lib/common/src/hooks/useObserveStore.d.ts +26 -11
  57. package/lib/common/src/hooks/useObserveStore.js +21 -2
  58. package/lib/common/src/hooks/useOutside.d.ts +12 -19
  59. package/lib/common/src/hooks/useOutside.js +9 -16
  60. package/lib/common/src/hooks/useReorder.d.ts +4 -4
  61. package/lib/common/src/hooks/useReorderBoard.d.ts +15 -15
  62. package/lib/common/src/hooks/useReplay.d.ts +33 -6
  63. package/lib/common/src/hooks/useReplay.js +32 -5
  64. package/lib/common/src/logs/logs.d.ts +3 -3
  65. package/lib/common/src/logs/logs.js +11 -13
  66. package/lib/common/src/logs/{logs3.d.ts → logsContext.d.ts} +4 -4
  67. package/lib/common/src/logs/{logs3.js → logsContext.js} +4 -4
  68. package/lib/common/src/menu/menu.d.ts +16 -16
  69. package/lib/common/src/menu/menu.js +12 -11
  70. package/lib/common/src/menu/menuMouse.d.ts +71 -13
  71. package/lib/common/src/menu/menuMouse.js +189 -21
  72. package/lib/common/src/menu/menuR.d.ts +3 -3
  73. package/lib/common/src/menu/menuR.js +5 -5
  74. package/lib/common/src/styles/styleGrid.d.ts +1 -1
  75. package/lib/common/src/styles/tokens.d.ts +1 -1
  76. package/lib/common/src/styles/tokens.js +1 -1
  77. package/lib/common/src/utils/cache.d.ts +11 -11
  78. package/lib/common/src/utils/cache.js +0 -0
  79. package/lib/common/src/utils/gridRows.d.ts +37 -0
  80. package/lib/common/src/utils/{applyTransactionAsyncUpdate.js → gridRows.js} +6 -38
  81. package/lib/common/src/utils/index.d.ts +2 -2
  82. package/lib/common/src/utils/index.js +2 -2
  83. package/lib/common/src/utils/inputAutoStep.d.ts +2 -2
  84. package/lib/common/src/utils/inputAutoStep.js +26 -37
  85. package/lib/common/src/utils/{mapMemory.d.ts → memoryStore.d.ts} +13 -13
  86. package/lib/common/src/utils/{mapMemory.js → memoryStore.js} +32 -32
  87. package/lib/common/src/utils/observableMap.d.ts +5 -5
  88. package/lib/common/src/utils/observableMap.js +3 -3
  89. package/lib/common/updateBy.js +2 -2
  90. package/lib/style/style.css +3 -3
  91. package/lib/style/tokens.css +1 -1
  92. package/package.json +49 -49
  93. package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +0 -2
  94. package/lib/common/src/hooks/useAddDownAnyKey.d.ts +0 -24
  95. package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +0 -53
@@ -0,0 +1,284 @@
1
+ import { listen as createListen } from 'wenay-common2';
2
+ import { renderBy, updateBy } from '../../../updateBy';
3
+ import { memoryGetOrCreate, memoryMarkDirty } from '../../utils/memoryStore';
4
+ const SCHEMA_V = 1;
5
+ /** Grid events that mean "the user changed the column layout / sort / filter". */
6
+ const GRID_EVENTS = ['columnMoved', 'columnResized', 'columnVisible', 'sortChanged', 'filterChanged'];
7
+ export function createColumnState(opts) {
8
+ const groupMembers = (g) => opts.columns.filter(c => c.group == g).map(c => c.key);
9
+ const groupKeys = [...new Set(opts.columns.map(c => c.group).filter((g) => !!g))];
10
+ const defConfig = () => ({
11
+ v: SCHEMA_V,
12
+ order: opts.def?.order?.slice() ?? opts.columns.map(c => c.key),
13
+ visible: opts.def?.visible ? { ...opts.def.visible } : Object.fromEntries(opts.columns.map(c => [c.key, c.defaultVisible != false])),
14
+ width: opts.def?.width ? { ...opts.def.width } : {},
15
+ sort: opts.def?.sort ? { ...opts.def.sort } : null,
16
+ filter: opts.def?.filter ? { ...opts.def.filter } : {},
17
+ groups: opts.def?.groups ? { ...opts.def.groups } : Object.fromEntries(groupKeys.map(g => [g, groupMembers(g)])),
18
+ });
19
+ const st = memoryGetOrCreate(opts.key, defConfig());
20
+ const [emitChange, onChange] = createListen();
21
+ /** Runtime-only, never persisted: which column keys the attached grid
22
+ * actually HAS right now. null = unknown / no grid = treat all as present.
23
+ * A column removed from the live grid (dynamic defs, "drop empty columns"
24
+ * standards) keeps its config entry and its menu button - the button just
25
+ * goes inert. The grid adapter maintains this; grid-less consumers may
26
+ * call setPresent themselves. */
27
+ const rt = { present: null };
28
+ function setPresent(keys) {
29
+ const next = keys ? Object.fromEntries(keys.map(k => [k, true])) : null;
30
+ if (JSON.stringify(next) == JSON.stringify(rt.present))
31
+ return;
32
+ rt.present = next;
33
+ renderBy(rt);
34
+ }
35
+ const getPresent = () => rt.present;
36
+ const isPresent = (key) => !rt.present || rt.present[key] == true;
37
+ function usePresent() {
38
+ updateBy(rt);
39
+ return rt.present;
40
+ }
41
+ /** The persisted state may be stale or partial (older app version, columns
42
+ * added/removed) - never crash, never drop user data that still applies:
43
+ * unknown keys are filtered out, missing columns are appended
44
+ * (default-visible), fixed columns are pinned back to their descriptor
45
+ * index. This IS the soft migration; v covers incompatible shape changes. */
46
+ function normalize() {
47
+ const known = new Set(opts.columns.map(c => c.key));
48
+ const byKey = new Map(opts.columns.map(c => [c.key, c]));
49
+ const rawOrder = Array.isArray(st.order) ? st.order : [];
50
+ const order = rawOrder.filter(k => known.has(k) && !byKey.get(k)?.fixed);
51
+ for (const c of opts.columns)
52
+ if (!c.fixed && order.indexOf(c.key) == -1)
53
+ order.push(c.key);
54
+ opts.columns.forEach(function pinFixed(c, i) {
55
+ if (c.fixed)
56
+ order.splice(Math.min(i, order.length), 0, c.key);
57
+ });
58
+ const rawVisible = st.visible && typeof st.visible == 'object' ? st.visible : {};
59
+ const visible = {};
60
+ for (const c of opts.columns)
61
+ visible[c.key] = c.fixed ? true : (rawVisible[c.key] ?? c.defaultVisible != false);
62
+ const rawWidth = st.width && typeof st.width == 'object' ? st.width : {};
63
+ const width = {};
64
+ for (const [k, w] of Object.entries(rawWidth))
65
+ if (known.has(k) && typeof w == 'number' && isFinite(w) && w > 0)
66
+ width[k] = w;
67
+ const sort = st.sort && known.has(st.sort.key) && (st.sort.dir == 'asc' || st.sort.dir == 'desc')
68
+ ? { key: st.sort.key, dir: st.sort.dir } : null;
69
+ const rawFilter = st.filter && typeof st.filter == 'object' ? st.filter : {};
70
+ const filter = {};
71
+ for (const [k, f] of Object.entries(rawFilter))
72
+ if (known.has(k))
73
+ filter[k] = f;
74
+ const rawGroups = st.groups && typeof st.groups == 'object' ? st.groups : {};
75
+ const groups = {};
76
+ for (const g of groupKeys) {
77
+ const members = groupMembers(g);
78
+ groups[g] = Array.isArray(rawGroups[g]) ? rawGroups[g].filter(k => members.indexOf(k) != -1) : members;
79
+ }
80
+ return { v: SCHEMA_V, order, visible, width, sort, filter, groups };
81
+ }
82
+ /** Every edit funnels through here: mutate the persisted object in place
83
+ * (identity is the updateBy/renderBy subscription key), announce, mark the
84
+ * cache dirty, push to the attached grid (unless the grid IS the source),
85
+ * emit outward. */
86
+ function commit(next, fromGrid) {
87
+ st.v = SCHEMA_V;
88
+ st.order = next.order.slice();
89
+ st.visible = { ...next.visible };
90
+ st.width = { ...next.width };
91
+ st.sort = next.sort ? { ...next.sort } : null;
92
+ st.filter = { ...next.filter };
93
+ st.groups = Object.fromEntries(Object.entries(next.groups).map(([g, keys]) => [g, keys.slice()]));
94
+ renderBy(st);
95
+ memoryMarkDirty(opts.key);
96
+ if (!fromGrid)
97
+ applyToGrid();
98
+ emitChange(normalize());
99
+ }
100
+ const getConfig = () => normalize();
101
+ const setConfig = (next) => commit(next, false);
102
+ const reset = () => commit(defConfig(), false);
103
+ function useConfig() {
104
+ updateBy(st);
105
+ return normalize();
106
+ }
107
+ function show(key, on) {
108
+ const cfg = normalize();
109
+ commit({ ...cfg, visible: { ...cfg.visible, [key]: on } }, false);
110
+ }
111
+ function move(order) {
112
+ commit({ ...normalize(), order }, false);
113
+ }
114
+ function setSort(sort) {
115
+ commit({ ...normalize(), sort }, false);
116
+ }
117
+ /** The mobile "sort button" cycle over one column: asc -> desc -> off.
118
+ * A different column starts its own cycle at asc; the sticky sort of
119
+ * another column is simply replaced. */
120
+ function toggleSort(key) {
121
+ const cur = normalize().sort;
122
+ setSort(cur?.key != key ? { key, dir: 'asc' } : cur.dir == 'asc' ? { key, dir: 'desc' } : null);
123
+ }
124
+ /** The order/visibility slice of THIS config as an external list source
125
+ * (structurally = Toolbar's UiListSource): plug it into
126
+ * createToolbar({source}) or any order/visibility editor - the editor,
127
+ * the menu and the attached grid then all mirror one another, because
128
+ * they edit the SAME config. */
129
+ const listSource = {
130
+ useConfig() {
131
+ const c = useConfig();
132
+ return { order: c.order, visible: c.visible };
133
+ },
134
+ getConfig() {
135
+ const c = normalize();
136
+ return { order: c.order, visible: c.visible };
137
+ },
138
+ setConfig(next) {
139
+ const cfg = normalize();
140
+ // an editor over a SUBSET of columns must not lose the rest:
141
+ // unknown incoming keys are dropped by normalize, missing ones re-appended
142
+ commit({ ...cfg, order: next.order, visible: { ...cfg.visible, ...next.visible } }, false);
143
+ },
144
+ onChange: (cb) => onChange.on(c => cb({ order: c.order, visible: c.visible })),
145
+ };
146
+ /** Keys to render, in order. Grouped columns are additionally gated by their
147
+ * group's enabled set. */
148
+ function visibleKeys() {
149
+ const cfg = normalize();
150
+ const byKey = new Map(opts.columns.map(c => [c.key, c]));
151
+ return cfg.order.filter(k => {
152
+ if (cfg.visible[k] == false)
153
+ return false;
154
+ const g = byKey.get(k)?.group;
155
+ return !g || cfg.groups[g]?.indexOf(k) != -1;
156
+ });
157
+ }
158
+ /* ----- grid adapter (two-way) ----- */
159
+ let gridApi = null;
160
+ let applying = false;
161
+ let saveTimer;
162
+ function toAgState(cfg) {
163
+ return cfg.order.map(k => ({
164
+ colId: k,
165
+ hide: cfg.visible[k] == false,
166
+ width: cfg.width[k], // undefined = leave the grid's current width
167
+ sort: cfg.sort?.key == k ? cfg.sort.dir : null,
168
+ }));
169
+ }
170
+ /** Store -> grid. The applying flag (plus source=='api' on events) keeps the
171
+ * restore from bouncing back as a save. */
172
+ function applyToGrid() {
173
+ if (!gridApi)
174
+ return;
175
+ applying = true;
176
+ try {
177
+ const cfg = normalize();
178
+ gridApi.applyColumnState({ state: toAgState(cfg), applyOrder: true });
179
+ gridApi.setFilterModel(Object.keys(cfg.filter).length ? cfg.filter : null);
180
+ }
181
+ finally {
182
+ applying = false;
183
+ }
184
+ }
185
+ /** Grid -> store: fold the live column state back into the config. Columns
186
+ * the grid does not know keep their stored values (a grid may mount a
187
+ * subset). Skipped when nothing actually changed - the residual guard
188
+ * against apply/save loops. */
189
+ function readFromGrid() {
190
+ if (!gridApi || gridApi.isDestroyed?.())
191
+ return;
192
+ const cfg = normalize();
193
+ const known = new Set(opts.columns.map(c => c.key));
194
+ const order = [];
195
+ const visible = { ...cfg.visible };
196
+ const width = { ...cfg.width };
197
+ let sort = null;
198
+ const gridIds = new Set();
199
+ for (const s of gridApi.getColumnState()) {
200
+ if (!s.colId)
201
+ continue;
202
+ gridIds.add(s.colId);
203
+ if (!known.has(s.colId))
204
+ continue;
205
+ order.push(s.colId);
206
+ visible[s.colId] = !s.hide;
207
+ if (typeof s.width == 'number' && s.width > 0)
208
+ width[s.colId] = s.width;
209
+ if (s.sort == 'asc' || s.sort == 'desc')
210
+ sort = { key: s.colId, dir: s.sort };
211
+ }
212
+ // the grid cannot express a sort by a column it does not currently have:
213
+ // keep the STICKY sort instead of folding its absence in as "off"
214
+ if (!sort && cfg.sort && !gridIds.has(cfg.sort.key))
215
+ sort = cfg.sort;
216
+ for (const k of cfg.order)
217
+ if (order.indexOf(k) == -1)
218
+ order.push(k);
219
+ const filter = (gridApi.getFilterModel() ?? {});
220
+ const next = { ...cfg, order, visible, width, sort, filter };
221
+ if (JSON.stringify(next) == JSON.stringify(cfg))
222
+ return;
223
+ commit(next, true);
224
+ // normalize() may have corrected the grid's move (a fixed column dragged
225
+ // away from its pinned index): push the corrected order back so the grid
226
+ // and the config never disagree
227
+ if (JSON.stringify(normalize().order) != JSON.stringify(next.order))
228
+ applyToGrid();
229
+ }
230
+ function onGridEvent(e) {
231
+ if (applying || e?.source == 'api')
232
+ return;
233
+ // resize/move fire per animation frame during a drag; persist the final shape only
234
+ if (e?.finished === false)
235
+ return;
236
+ clearTimeout(saveTimer);
237
+ saveTimer = setTimeout(readFromGrid, opts.saveMs ?? 300);
238
+ }
239
+ /** The grid's column SET changed (dynamic columnDefs, "drop empty columns"
240
+ * standards): refresh presence; when the set really changed, re-impose the
241
+ * config - a column that came BACK gets its stored order/width/visibility
242
+ * again (setting columnDefs resets order to the defs' order). No loop:
243
+ * applyColumnState never adds/removes columns, so it cannot re-fire this. */
244
+ function onGridColumns() {
245
+ if (!gridApi || gridApi.isDestroyed?.())
246
+ return;
247
+ const before = rt.present;
248
+ setPresent((gridApi.getColumns() ?? []).map(c => c.getColId()));
249
+ if (rt.present != before && !applying)
250
+ applyToGrid();
251
+ }
252
+ /** Call from onGridReady (AgGridTable forwards it on top of its own wiring) or
253
+ * via controller.withApi. Restores the saved state, then watches the grid. */
254
+ function attach(api) {
255
+ detach();
256
+ gridApi = api;
257
+ applyToGrid();
258
+ setPresent((api.getColumns() ?? []).map(c => c.getColId()));
259
+ for (const t of GRID_EVENTS)
260
+ api.addEventListener(t, onGridEvent);
261
+ api.addEventListener('gridColumnsChanged', onGridColumns);
262
+ }
263
+ /** Call from onGridPreDestroyed. The config survives the grid (columnBuffer
264
+ * pattern): remount + attach restores the same layout. */
265
+ function detach() {
266
+ clearTimeout(saveTimer);
267
+ saveTimer = undefined;
268
+ if (gridApi && !gridApi.isDestroyed?.()) {
269
+ for (const t of GRID_EVENTS)
270
+ gridApi.removeEventListener(t, onGridEvent);
271
+ gridApi.removeEventListener('gridColumnsChanged', onGridColumns);
272
+ }
273
+ gridApi = null;
274
+ setPresent(null);
275
+ }
276
+ return {
277
+ /** The column descriptors this state was created over - UI components
278
+ * (dots, cards, icon menus) render from these + the config. */
279
+ columns: opts.columns,
280
+ api: { getConfig, setConfig, useConfig, onChange, reset, show, move, setSort, toggleSort, visibleKeys,
281
+ getPresent, usePresent, isPresent, setPresent, listSource },
282
+ grid: { attach, detach },
283
+ };
284
+ }
@@ -0,0 +1,4 @@
1
+ export * from './columnState';
2
+ export * from './ColumnsMenu';
3
+ export * from './ColumnDots';
4
+ export * from './CardList';
@@ -0,0 +1,4 @@
1
+ export * from './columnState';
2
+ export * from './ColumnsMenu';
3
+ export * from './ColumnDots';
4
+ export * from './CardList';
@@ -1,4 +1,4 @@
1
- export * from './useAddDownAnyKey';
1
+ export * from './useKeyboard';
2
2
  export * from './useDraggable';
3
3
  export * from './useReorder';
4
4
  export * from './useReorderBoard';
@@ -1,4 +1,4 @@
1
- export * from './useAddDownAnyKey';
1
+ export * from './useKeyboard';
2
2
  export * from './useDraggable';
3
3
  export * from './useReorder';
4
4
  export * from './useReorderBoard';
@@ -0,0 +1,18 @@
1
+ export declare const keyboardState: {
2
+ key: string;
3
+ };
4
+ export type KeyboardApi = {
5
+ readonly key: string;
6
+ getKey(): string;
7
+ get(): string;
8
+ clear(): void;
9
+ reset(): void;
10
+ subscribe(listener: (key: string, event?: KeyboardEvent) => void): () => void;
11
+ on(listener: (key: string, event?: KeyboardEvent) => void): () => void;
12
+ };
13
+ export declare const keyboard: KeyboardApi;
14
+ export declare function useKeyboard(options?: {
15
+ enabled?: boolean;
16
+ target?: Document | HTMLElement;
17
+ onKeyDown?: (key: string, event: KeyboardEvent) => void;
18
+ }): KeyboardApi;
@@ -1,30 +1,30 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import { renderBy } from "../../updateBy";
3
- import { UseListen } from "wenay-common2";
4
- export const KeyDown = {
3
+ import { listen as createListen } from "wenay-common2";
4
+ export const keyboardState = {
5
5
  key: ""
6
6
  };
7
- const [emitKeyDown, keyDownListen] = UseListen();
8
- export const keyDownApi = {
9
- get key() { return KeyDown.key; },
10
- getKey() { return KeyDown.key; },
11
- get() { return KeyDown.key; },
7
+ const [emitKeyDown, keyboardListen] = createListen();
8
+ export const keyboard = {
9
+ get key() { return keyboardState.key; },
10
+ getKey() { return keyboardState.key; },
11
+ get() { return keyboardState.key; },
12
12
  clear() {
13
- KeyDown.key = "";
14
- renderBy(KeyDown);
13
+ keyboardState.key = "";
14
+ renderBy(keyboardState);
15
15
  emitKeyDown("", undefined);
16
16
  },
17
17
  reset() {
18
- keyDownApi.clear();
18
+ keyboard.clear();
19
19
  },
20
20
  subscribe(listener) {
21
- return keyDownListen.on(listener);
21
+ return keyboardListen.on(listener);
22
22
  },
23
23
  on(listener) {
24
- return keyDownListen.on(listener);
24
+ return keyboardListen.on(listener);
25
25
  }
26
26
  };
27
- export function useAddDownAnyKey(options = {}) {
27
+ export function useKeyboard(options = {}) {
28
28
  const { enabled = true, target } = options;
29
29
  const onKeyDownRef = useRef(options.onKeyDown);
30
30
  onKeyDownRef.current = options.onKeyDown;
@@ -37,8 +37,8 @@ export function useAddDownAnyKey(options = {}) {
37
37
  const func = (event) => {
38
38
  if (!(event instanceof KeyboardEvent))
39
39
  return;
40
- KeyDown.key = event.key;
41
- renderBy(KeyDown);
40
+ keyboardState.key = event.key;
41
+ renderBy(keyboardState);
42
42
  emitKeyDown(event.key, event);
43
43
  onKeyDownRef.current?.(event.key, event);
44
44
  };
@@ -47,13 +47,5 @@ export function useAddDownAnyKey(options = {}) {
47
47
  currentTarget.removeEventListener("keydown", func);
48
48
  };
49
49
  }, [enabled, target]);
50
- return keyDownApi;
50
+ return keyboard;
51
51
  }
52
- export const useKeyDown = useAddDownAnyKey;
53
- export const useAnyKey = useAddDownAnyKey;
54
- /** @deprecated Use `useKeyDown(options)` or `useAnyKey(options)`. */
55
- export function addDownAnyKey() {
56
- return useAddDownAnyKey();
57
- }
58
- /** @deprecated Use `useKeyDown(options)` or `useAnyKey(options)`. */
59
- export const useAddDownAnyKeyOld = useAddDownAnyKey;
@@ -1,12 +1,13 @@
1
- import { ObserveAll2 } from "wenay-common2";
2
- type StoreChange = ObserveAll2.StoreChange;
3
- type StoreSubOpts = ObserveAll2.StoreSubOpts;
4
- type StoreSyncOpts = ObserveAll2.StoreSyncOpts;
5
- type StoreDrain = ObserveAll2.StoreDrain;
6
- type StoreMask<T> = ObserveAll2.StoreMask<T>;
7
- type StorePick<T, M> = ObserveAll2.StorePick<T, M>;
8
- type StoreNode<T> = ObserveAll2.StoreNode<T>;
9
- type StoreSelection<T, M> = ObserveAll2.StoreSelection<T, M>;
1
+ import { Observe } from "wenay-common2";
2
+ type StoreChange = Observe.StoreChange;
3
+ type StoreEachCtx = Observe.StoreEachCtx;
4
+ type StoreSubOpts = Observe.StoreSubOpts;
5
+ type StoreSyncOpts = Observe.StoreSyncOpts;
6
+ type StoreDrain = Observe.StoreDrain;
7
+ type StoreMask<T> = Observe.StoreMask<T>;
8
+ type StorePick<T, M> = Observe.StorePick<T, M>;
9
+ type StoreNode<T> = Observe.StoreNode<T>;
10
+ type StoreSelection<T, M> = Observe.StoreSelection<T, M>;
10
11
  export type ListenLike<TArgs extends readonly unknown[] = readonly unknown[]> = {
11
12
  on(cb: (...args: TArgs) => void, opts?: {
12
13
  key?: string | symbol;
@@ -55,6 +56,20 @@ export type StoreKeysController<T extends object = any> = {
55
56
  };
56
57
  export declare function useStoreKeys<T extends object>(node: StoreNode<T>, options?: Omit<UseStoreNodeOptions<T>, "mode">): StoreKeysController<T>;
57
58
  export declare function useStoreSelect<T, M extends StoreMask<T>>(selection: StoreSelection<T, M>, options?: UseStoreSelectOptions<StorePick<T, M>>): StoreSelectionController<T, M>;
59
+ export type UseStoreEachOptions = {
60
+ /** false = do not subscribe (and drop the current subscription). Default true. */
61
+ enabled?: boolean;
62
+ };
63
+ /**
64
+ * Per-changed-top-level-key feed of a store (`store.each()` of wenay-common2): cb fires once per
65
+ * CHANGED key per drain window with the current `store.state[key]` (`undefined` = key deleted);
66
+ * a root replace (store.replace / mirror keyframe) EXPANDS into one call per key, so cold start
67
+ * is not a special case for per-key consumers (grid rows, canvas layers, ...). The fold target
68
+ * should live outside React state (ref/store/grid api) — this hook renders nothing by itself.
69
+ * cb goes through a ref — a new identity does not resubscribe.
70
+ * The remote (wire) counterpart is useStoreReplayEach in useReplay.ts.
71
+ */
72
+ export declare function useStoreEach<T extends object>(store: Observe.Store<T> | null | undefined, cb: (key: string, value: T[keyof T] | undefined, ctx: StoreEachCtx) => void, options?: UseStoreEachOptions): void;
58
73
  export type RemoteStoreLike<T extends object> = {
59
74
  get(mask?: any): T | Promise<T>;
60
75
  changed: ListenLike<readonly []>;
@@ -65,7 +80,7 @@ export type UseStoreMirrorOptions<T extends object, M extends StoreMask<T>> = St
65
80
  auto?: boolean;
66
81
  };
67
82
  export type StoreMirrorController<T extends object, M extends StoreMask<T>> = {
68
- readonly store: ObserveAll2.Store<T> & {
83
+ readonly store: Observe.Store<T> & {
69
84
  sync(mask: M, subOpts?: StoreSyncOpts): Promise<() => void>;
70
85
  };
71
86
  readonly value: StorePick<T, M>;
@@ -102,4 +117,4 @@ export declare function useListenValue<T, TArgs extends readonly unknown[] = rea
102
117
  current?: boolean | (() => TArgs | undefined);
103
118
  map?: (...args: TArgs) => T;
104
119
  }): T | undefined;
105
- export type { StoreChange, StoreDrain, StoreMask, StoreNode, StorePick, StoreSelection, StoreSubOpts, StoreSyncOpts };
120
+ export type { StoreChange, StoreDrain, StoreEachCtx, StoreMask, StoreNode, StorePick, StoreSelection, StoreSubOpts, StoreSyncOpts };
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
- import { ObserveAll2 } from "wenay-common2";
2
+ import { Observe } from "wenay-common2";
3
3
  function readNode(node, options) {
4
4
  if (!node.has())
5
5
  return options?.fallback;
@@ -96,6 +96,25 @@ export function useStoreSelect(selection, options = {}) {
96
96
  get: () => selection.get(),
97
97
  }), [selection, value, refresh]);
98
98
  }
99
+ /**
100
+ * Per-changed-top-level-key feed of a store (`store.each()` of wenay-common2): cb fires once per
101
+ * CHANGED key per drain window with the current `store.state[key]` (`undefined` = key deleted);
102
+ * a root replace (store.replace / mirror keyframe) EXPANDS into one call per key, so cold start
103
+ * is not a special case for per-key consumers (grid rows, canvas layers, ...). The fold target
104
+ * should live outside React state (ref/store/grid api) — this hook renders nothing by itself.
105
+ * cb goes through a ref — a new identity does not resubscribe.
106
+ * The remote (wire) counterpart is useStoreReplayEach in useReplay.ts.
107
+ */
108
+ export function useStoreEach(store, cb, options = {}) {
109
+ const { enabled = true } = options;
110
+ const cbRef = useLatestRef(cb);
111
+ useEffect(() => {
112
+ if (!store || !enabled)
113
+ return;
114
+ // each() is created lazily per subscription: it runs while it has subscribers and closes on off()
115
+ return store.each().on((key, value, ctx) => cbRef.current(key, value, ctx));
116
+ }, [store, enabled, cbRef]);
117
+ }
99
118
  export function useStoreMirror(remote, initial, options) {
100
119
  const { mask, auto = true, current = true, drain, key, partial, onError } = options;
101
120
  const stableMask = useStableStoreMask(mask);
@@ -109,7 +128,7 @@ export function useStoreMirror(remote, initial, options) {
109
128
  if (!storeRef.current || storeRef.current.remote !== remote) {
110
129
  storeRef.current = {
111
130
  remote,
112
- store: ObserveAll2.createStoreMirror(remote, initial),
131
+ store: Observe.createStoreMirror(remote, initial),
113
132
  };
114
133
  }
115
134
  const store = storeRef.current.store;
@@ -1,6 +1,6 @@
1
1
  import React, { ReactElement } from "react";
2
2
  export declare const StyleOtherRow: React.CSSProperties;
3
- export declare const StyleOtherColum: React.CSSProperties;
3
+ export declare const StyleOtherColumn: React.CSSProperties;
4
4
  export type UseOutsideOptions<T extends HTMLElement = HTMLDivElement> = {
5
5
  ref?: React.RefObject<T | null>;
6
6
  outsideClick?: () => void;
@@ -24,37 +24,30 @@ export type UseOutsideApi<T extends HTMLElement = HTMLDivElement> = {
24
24
  };
25
25
  export declare function useOutsideApi<T extends HTMLElement = HTMLDivElement>(options: UseOutsideOptions<T>): UseOutsideApi<T>;
26
26
  export declare function useOutside<T extends HTMLElement = HTMLDivElement>(options: UseOutsideOptions<T>): UseOutsideApi<T>;
27
- /** @deprecated Use `useOutside(options)` and read `api.ref`, `api.props`, or `api.current`. */
28
- export declare function useOutsideOld<T extends HTMLElement = HTMLDivElement>(options: UseOutsideOptions<T>): React.RefObject<T | null>;
29
- type tChildrenFunc = (api: {
27
+ export declare function useOutsideRef<T extends HTMLElement = HTMLDivElement>(options: UseOutsideOptions<T>): React.RefObject<T | null>;
28
+ type ChildrenFunc = (api: {
30
29
  onClose: () => void;
31
30
  }) => ReactElement | React.JSX.Element;
32
- type tChildrenEl = ReactElement | React.ReactNode;
33
- type tChildren = tChildrenEl | tChildrenFunc;
34
- type tButtonBase = {
35
- children: tChildren;
31
+ type ButtonChildren = ReactElement | React.ReactNode | ChildrenFunc;
32
+ type ButtonBaseProps = {
33
+ children: ButtonChildren;
36
34
  button: ReactElement | ((status: boolean) => ReactElement);
37
35
  style?: React.CSSProperties;
38
36
  className?: string;
39
37
  };
40
- type tButton = tButtonBase & {
38
+ type ButtonProps = ButtonBaseProps & {
41
39
  statusDef?: boolean;
42
40
  keySave?: string;
43
41
  outClick?: boolean | (() => void);
44
42
  zIndex?: number;
45
43
  };
46
- export declare const DivOutsideClick: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
44
+ export declare const OutsideClickArea: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
47
45
  outsideClick: () => void;
48
46
  status?: boolean;
49
47
  zIndex?: number;
50
48
  } & React.RefAttributes<HTMLDivElement>>;
51
- export declare const DivOutsideClick2: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
52
- outsideClick: () => void;
53
- status?: boolean;
54
- zIndex?: number;
55
- } & React.RefAttributes<HTMLDivElement>>;
56
- export declare function Button({ keySave, statusDef, outClick, ...data }: tButton): import("react/jsx-runtime").JSX.Element;
57
- export declare function ButtonHover(props: tButtonBase): import("react/jsx-runtime").JSX.Element;
58
- export declare const ButtonOutClick: typeof Button;
59
- export declare function ButtonAbs(props: Parameters<typeof Button>[0]): import("react/jsx-runtime").JSX.Element;
49
+ export declare function Button({ keySave, statusDef, outClick, ...data }: ButtonProps): import("react/jsx-runtime").JSX.Element;
50
+ export declare function HoverButton(props: ButtonBaseProps): import("react/jsx-runtime").JSX.Element;
51
+ export declare const OutsideButton: typeof Button;
52
+ export declare function AbsoluteButton(props: Parameters<typeof Button>[0]): import("react/jsx-runtime").JSX.Element;
60
53
  export {};
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useEffect, useMemo, useRef, useState } from "react";
3
3
  export const StyleOtherRow = { display: "flex", flexDirection: "row", flex: "auto 1 1" };
4
- export const StyleOtherColum = { display: "flex", flexDirection: "column", flex: "auto 0 1" };
4
+ export const StyleOtherColumn = { display: "flex", flexDirection: "column", flex: "auto 0 1" };
5
5
  export function useOutsideApi(options) {
6
6
  const { outsideClick, onOutside, ref, status = options.enabled ?? true } = options;
7
- const internalRef = useRef(null); // useRef is always inside the body, not in a default parameter - otherwise it breaks the Rules of Hooks
7
+ const internalRef = useRef(null);
8
8
  const r = ref ?? internalRef;
9
9
  const outsideClickRef = useRef(outsideClick ?? onOutside);
10
10
  const [enabled, setEnabled] = useState(status);
@@ -52,15 +52,12 @@ export function useOutsideApi(options) {
52
52
  export function useOutside(options) {
53
53
  return useOutsideApi(options);
54
54
  }
55
- /** @deprecated Use `useOutside(options)` and read `api.ref`, `api.props`, or `api.current`. */
56
- export function useOutsideOld(options) {
55
+ export function useOutsideRef(options) {
57
56
  return useOutsideApi(options).ref;
58
57
  }
59
- export const DivOutsideClick = React.forwardRef(({ children, outsideClick, zIndex, style = {}, status = true, ...other }, forwardedRef) => {
58
+ export const OutsideClickArea = React.forwardRef(({ children, outsideClick, zIndex, style = {}, status = true, ...other }, forwardedRef) => {
60
59
  const style2 = zIndex ? { ...style, zIndex } : style;
61
- const internalRef = useOutside({ outsideClick: outsideClick, status });
62
- // Combine refs if forwardedRef is provided; stable identity, otherwise React
63
- // re-runs the ref (null + node) on every render
60
+ const internalRef = useOutside({ outsideClick, status });
64
61
  const combinedRef = React.useCallback((node) => {
65
62
  internalRef.current = node;
66
63
  if (typeof forwardedRef === 'function') {
@@ -72,15 +69,11 @@ export const DivOutsideClick = React.forwardRef(({ children, outsideClick, zInde
72
69
  }, [forwardedRef, internalRef]);
73
70
  return _jsx("div", { ref: forwardedRef ? combinedRef : internalRef, style: style2, ...other, children: children });
74
71
  });
75
- // Deprecated: Use DivOutsideClick instead
76
- export const DivOutsideClick2 = DivOutsideClick;
77
72
  function ButtonBase({ children, button, style = {}, className = "", state: [a, setA] }) {
78
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)] });
79
74
  }
80
75
  const saveStatus = {};
81
76
  export function Button({ keySave, statusDef, outClick, ...data }) {
82
- // The keySave feature was dead: saveStatus was read but not written. Now status
83
- // persists in memory for the session on each toggle.
84
77
  if (keySave && saveStatus[keySave] != null)
85
78
  statusDef = saveStatus[keySave];
86
79
  const [status, setStatusRaw] = useState(statusDef ?? false);
@@ -98,15 +91,15 @@ export function Button({ keySave, statusDef, outClick, ...data }) {
98
91
  if (typeof outClick == "function")
99
92
  outClick();
100
93
  };
101
- return outClick ? (_jsx(DivOutsideClick, { status: state[0], outsideClick: handleOutsideClick, children: _jsx(ButtonBase, { ...data, state: state }) })) : (_jsx(ButtonBase, { ...data, state: state }));
94
+ return outClick ? (_jsx(OutsideClickArea, { status: state[0], outsideClick: handleOutsideClick, children: _jsx(ButtonBase, { ...data, state: state }) })) : (_jsx(ButtonBase, { ...data, state: state }));
102
95
  }
103
- export function ButtonHover(props) {
96
+ export function HoverButton(props) {
104
97
  const [hover, setHover] = useState(false);
105
98
  return _jsxs("div", { onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), style: { position: "relative", width: "min-content" }, children: [typeof props.button == "function" ? props.button(hover) : props.button, hover &&
106
99
  _jsx("div", { style: { position: "absolute" }, children: typeof props.children == "function" ? props.children({ onClose: () => setHover(false) }) : props.children })] });
107
100
  }
108
- export const ButtonOutClick = ({ outClick = true, ...a }) => Button({ ...a, outClick });
109
- export function ButtonAbs(props) {
101
+ export const OutsideButton = ({ outClick = true, ...a }) => Button({ ...a, outClick });
102
+ export function AbsoluteButton(props) {
110
103
  const children = (api) => _jsx("div", { style: { position: "relative" }, children: _jsx("div", { style: {
111
104
  position: "absolute",
112
105
  zIndex: props.zIndex ?? 9
@@ -7,7 +7,7 @@ import React from 'react';
7
7
  * the container's children.
8
8
  * NOT a dnd framework: no nesting, no cross-container moves, no spans or
9
9
  * collision packing - when that day comes, take a ready-made library. */
10
- export type tReorderOptions = {
10
+ export type ReorderOptions = {
11
11
  /** keys in render order; the container's children must correspond 1:1 */
12
12
  order: string[];
13
13
  /** ONE commit on drop; skipped when nothing moved */
@@ -28,7 +28,7 @@ export type tReorderOptions = {
28
28
  /** hold before the drag starts (touch-friendly fields); default 0 */
29
29
  holdMs?: number;
30
30
  };
31
- export type tReorderItem = {
31
+ export type ReorderItem = {
32
32
  /** spread on the block element */
33
33
  props: {
34
34
  onMouseDown: React.MouseEventHandler<HTMLElement>;
@@ -41,9 +41,9 @@ export type tReorderItem = {
41
41
  /** some drag is active - the consumer adds its transition class/style on non-dragged blocks */
42
42
  active: boolean;
43
43
  };
44
- export declare function useReorder<E extends HTMLElement = HTMLDivElement>(o: tReorderOptions): {
44
+ export declare function useReorder<E extends HTMLElement = HTMLDivElement>(o: ReorderOptions): {
45
45
  listRef: React.RefObject<E | null>;
46
- item: (key: string) => tReorderItem;
46
+ item: (key: string) => ReorderItem;
47
47
  dragKey: string | null;
48
48
  preview: string[] | null;
49
49
  };