wenay-react2 1.0.37 → 1.0.38

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.
@@ -111,6 +111,114 @@ export function useReplaySubscribe(remote, cb, options = {}) {
111
111
  }, []);
112
112
  return useMemo(() => ({ ready, error, stale, seq, lastTs, restart }), [ready, error, stale, seq, lastTs, restart]);
113
113
  }
114
+ /**
115
+ * Route-replaceable replay subscription. Use this when the app can promote/demote between
116
+ * transports (relay <-> direct) and must keep one logical replay fold with no gaps or dups.
117
+ * Changing the `remote` prop is still a fresh subscription boundary; no-gap hand-off is explicit
118
+ * through `controller.switchRoute(...)`.
119
+ */
120
+ export function useReplayRouteSubscribe(remote, cb, options = {}) {
121
+ const { since, keepSeq = true, enabled = true, label, onSeq, onError, onRoute, policy, hint } = options;
122
+ const cbRef = useLatestRef(cb);
123
+ const hooksRef = useLatestRef({ onSeq, onError, onRoute });
124
+ const hintRef = useLatestRef(hint);
125
+ const seqRef = useRef(since);
126
+ const labelRef = useRef(label);
127
+ const activeRef = useRef(false);
128
+ const subRef = useRef(null);
129
+ const lastRemoteRef = useRef(undefined);
130
+ const [ready, setReady] = useState(false);
131
+ const [error, setError] = useState(null);
132
+ const [route, setRoute] = useState(null);
133
+ const [switching, setSwitching] = useState(false);
134
+ useEffect(() => {
135
+ if (!remote || !enabled)
136
+ return;
137
+ if (lastRemoteRef.current !== undefined && lastRemoteRef.current !== remote)
138
+ seqRef.current = undefined;
139
+ lastRemoteRef.current = remote;
140
+ let alive = true;
141
+ setReady(false);
142
+ setError(null);
143
+ setRoute(null);
144
+ setSwitching(false);
145
+ labelRef.current = label;
146
+ activeRef.current = false;
147
+ const off = Replay.replayRouteSubscribe(remote, (...event) => cbRef.current(...event), {
148
+ since: seqRef.current,
149
+ label,
150
+ policy,
151
+ hint: hintRef.current,
152
+ onSeq: seq => {
153
+ seqRef.current = seq;
154
+ hooksRef.current.onSeq?.(seq);
155
+ },
156
+ onError: e => {
157
+ if (alive)
158
+ setError(e);
159
+ hooksRef.current.onError?.(e);
160
+ },
161
+ onRoute: ev => {
162
+ if (alive) {
163
+ setRoute(ev);
164
+ setSwitching(ev.phase == 'switching');
165
+ if (ev.phase == 'switching')
166
+ setReady(false);
167
+ if (ev.phase == 'ready') {
168
+ labelRef.current = ev.to;
169
+ activeRef.current = true;
170
+ setReady(true);
171
+ }
172
+ if (ev.phase == 'closed') {
173
+ activeRef.current = false;
174
+ setReady(false);
175
+ }
176
+ if (ev.phase == 'error') {
177
+ activeRef.current = subRef.current?.active() ?? false;
178
+ setReady(activeRef.current);
179
+ }
180
+ }
181
+ hooksRef.current.onRoute?.(ev);
182
+ },
183
+ });
184
+ subRef.current = off;
185
+ off.ready.then(() => {
186
+ if (!alive)
187
+ return;
188
+ activeRef.current = off.active();
189
+ labelRef.current = off.label();
190
+ setReady(true);
191
+ setSwitching(false);
192
+ }, e => {
193
+ if (!alive)
194
+ return;
195
+ setError(e);
196
+ setReady(false);
197
+ setSwitching(false);
198
+ });
199
+ return () => {
200
+ alive = false;
201
+ subRef.current = null;
202
+ activeRef.current = false;
203
+ off();
204
+ if (!keepSeq)
205
+ seqRef.current = since;
206
+ };
207
+ // label/since/keepSeq are start-position metadata; route changes go through switchRoute(). hint rides a ref.
208
+ // eslint-disable-next-line react-hooks/exhaustive-deps
209
+ }, [remote, enabled, policy]);
210
+ const seq = useCallback(() => subRef.current?.seq() ?? seqRef.current ?? -1, []);
211
+ const currentLabel = useCallback(() => subRef.current?.label() ?? labelRef.current, []);
212
+ const active = useCallback(() => subRef.current?.active() ?? activeRef.current, []);
213
+ const switchRoute = useCallback((nextRemote, switchOptions) => {
214
+ const sub = subRef.current;
215
+ if (!sub)
216
+ return Promise.reject(new Error("useReplayRouteSubscribe: no active route subscription"));
217
+ setError(null);
218
+ return sub.switch(nextRemote, switchOptions);
219
+ }, []);
220
+ return useMemo(() => ({ ready, error, route, switching, seq, label: currentLabel, active, switchRoute }), [ready, error, route, switching, seq, currentLabel, active, switchRoute]);
221
+ }
114
222
  /**
115
223
  * Sync an existing mirror store from a store replay line (`Observe.exposeStoreReplay(...).api.replay`).
116
224
  * Thin lifecycle wrapper over `Observe.syncStoreReplay`: the keyframe (root patch) and the
@@ -188,6 +296,120 @@ export function useStoreReplaySync(store, remote, options = {}) {
188
296
  }, []);
189
297
  return useMemo(() => ({ ready, error, stale, seq, lastTs, restart }), [ready, error, stale, seq, lastTs, restart]);
190
298
  }
299
+ /**
300
+ * Route-replaceable store replay sync. The supplied store remains the fold target while
301
+ * switchRoute() promotes/demotes the underlying replay route.
302
+ */
303
+ export function useStoreReplayRouteSync(store, remote, options = {}) {
304
+ const { since, keepSeq = true, enabled = true, label, onSeq, onError, onRoute, policy, hint } = options;
305
+ const hooksRef = useLatestRef({ onSeq, onError, onRoute });
306
+ const hintRef = useLatestRef(hint);
307
+ const seqRef = useRef(since);
308
+ const labelRef = useRef(label);
309
+ const activeRef = useRef(false);
310
+ const subRef = useRef(null);
311
+ const lastRemoteRef = useRef(undefined);
312
+ const [ready, setReady] = useState(false);
313
+ const [error, setError] = useState(null);
314
+ const [route, setRoute] = useState(null);
315
+ const [switching, setSwitching] = useState(false);
316
+ useEffect(() => {
317
+ if (!store || !remote || !enabled)
318
+ return;
319
+ if (lastRemoteRef.current !== undefined && lastRemoteRef.current !== remote)
320
+ seqRef.current = undefined;
321
+ lastRemoteRef.current = remote;
322
+ let alive = true;
323
+ setReady(false);
324
+ setError(null);
325
+ setRoute(null);
326
+ setSwitching(false);
327
+ labelRef.current = label;
328
+ activeRef.current = false;
329
+ const off = Observe.syncStoreReplayRoute(store, remote, {
330
+ since: seqRef.current,
331
+ label,
332
+ policy,
333
+ hint: hintRef.current,
334
+ onSeq: seq => {
335
+ seqRef.current = seq;
336
+ hooksRef.current.onSeq?.(seq);
337
+ },
338
+ onError: e => {
339
+ if (alive)
340
+ setError(e);
341
+ hooksRef.current.onError?.(e);
342
+ },
343
+ onRoute: ev => {
344
+ if (alive) {
345
+ setRoute(ev);
346
+ setSwitching(ev.phase == 'switching');
347
+ if (ev.phase == 'switching')
348
+ setReady(false);
349
+ if (ev.phase == 'ready') {
350
+ labelRef.current = ev.to;
351
+ activeRef.current = true;
352
+ setReady(true);
353
+ }
354
+ if (ev.phase == 'closed') {
355
+ activeRef.current = false;
356
+ setReady(false);
357
+ }
358
+ if (ev.phase == 'error') {
359
+ activeRef.current = subRef.current?.active() ?? false;
360
+ setReady(activeRef.current);
361
+ }
362
+ }
363
+ hooksRef.current.onRoute?.(ev);
364
+ },
365
+ });
366
+ subRef.current = off;
367
+ off.ready.then(() => {
368
+ if (!alive)
369
+ return;
370
+ activeRef.current = off.active();
371
+ labelRef.current = off.label();
372
+ setReady(true);
373
+ setSwitching(false);
374
+ }, e => {
375
+ if (!alive)
376
+ return;
377
+ setError(e);
378
+ setReady(false);
379
+ setSwitching(false);
380
+ });
381
+ return () => {
382
+ alive = false;
383
+ subRef.current = null;
384
+ activeRef.current = false;
385
+ off();
386
+ if (!keepSeq)
387
+ seqRef.current = since;
388
+ };
389
+ // eslint-disable-next-line react-hooks/exhaustive-deps
390
+ }, [store, remote, enabled, policy]);
391
+ const seq = useCallback(() => subRef.current?.seq() ?? seqRef.current ?? -1, []);
392
+ const currentLabel = useCallback(() => subRef.current?.label() ?? labelRef.current, []);
393
+ const active = useCallback(() => subRef.current?.active() ?? activeRef.current, []);
394
+ const switchRoute = useCallback((nextRemote, switchOptions) => {
395
+ const sub = subRef.current;
396
+ if (!sub)
397
+ return Promise.reject(new Error("useStoreReplayRouteSync: no active route subscription"));
398
+ setError(null);
399
+ return sub.switch(nextRemote, switchOptions);
400
+ }, []);
401
+ return useMemo(() => ({ ready, error, route, switching, seq, label: currentLabel, active, switchRoute }), [ready, error, route, switching, seq, currentLabel, active, switchRoute]);
402
+ }
403
+ /** Create a local mirror store and keep it synced through a route-replaceable replay remote. */
404
+ export function useStoreReplayRouteMirror(remote, initial, options = {}) {
405
+ const storeRef = useRef(null);
406
+ if (!storeRef.current || storeRef.current.remote !== remote) {
407
+ storeRef.current = { remote, store: Observe.createStore(initial) };
408
+ }
409
+ const store = storeRef.current.store;
410
+ const sync = useStoreReplayRouteSync(store, remote, options);
411
+ return useMemo(() => ({ ...sync, store }), [sync, store]);
412
+ }
191
413
  /**
192
414
  * Convenience: create a local mirror store and keep it synced from a store replay line.
193
415
  * The store lives in a ref — a component remount keeps both the state and the seq,
@@ -0,0 +1,13 @@
1
+ export declare const logStyleTokens: {
2
+ readonly text: "var(--logs-notification-text, #fff)";
3
+ readonly accent: "var(--logs-notification-accent, #5D9FFA)";
4
+ readonly toggleBg: "var(--logs-toggle-bg, rgb(58, 58, 58))";
5
+ readonly toggleOffBg: "var(--logs-toggle-off-bg, rgb(144, 60, 60))";
6
+ readonly divider: "var(--logs-divider, rgba(255, 255, 255, 1))";
7
+ readonly tabNavBg: "var(--logs-tab-nav-bg, #333)";
8
+ readonly tabBg: "var(--logs-tab-bg, #444)";
9
+ readonly tabActiveBg: "var(--logs-tab-active-bg, #666)";
10
+ readonly tabText: "var(--logs-tab-text, #fff)";
11
+ };
12
+ export declare function logSeverityBackground(importance?: number): string;
13
+ export declare function logDividerGradient(): string;
@@ -0,0 +1,18 @@
1
+ export const logStyleTokens = {
2
+ text: 'var(--logs-notification-text, #fff)',
3
+ accent: 'var(--logs-notification-accent, #5D9FFA)',
4
+ toggleBg: 'var(--logs-toggle-bg, rgb(58, 58, 58))',
5
+ toggleOffBg: 'var(--logs-toggle-off-bg, rgb(144, 60, 60))',
6
+ divider: 'var(--logs-divider, rgba(255, 255, 255, 1))',
7
+ tabNavBg: 'var(--logs-tab-nav-bg, #333)',
8
+ tabBg: 'var(--logs-tab-bg, #444)',
9
+ tabActiveBg: 'var(--logs-tab-active-bg, #666)',
10
+ tabText: 'var(--logs-tab-text, #fff)',
11
+ };
12
+ export function logSeverityBackground(importance = 0) {
13
+ const level = Number.isFinite(importance) ? Math.max(0, importance) : 0;
14
+ return `rgb(${Math.min(255, level * 10)}, 73, 35)`;
15
+ }
16
+ export function logDividerGradient() {
17
+ return `linear-gradient(to right, transparent, ${logStyleTokens.divider}, transparent)`;
18
+ }
@@ -6,6 +6,7 @@ import { renderBy, updateBy } from "../../updateBy";
6
6
  import { contextMenu } from "../menu/menuMouse";
7
7
  import { memoryGetOrCreate } from "../utils/memoryStore";
8
8
  import { ParamsEditor } from "../components/ParamsEditor";
9
+ import { logDividerGradient, logSeverityBackground, logStyleTokens } from "./logStyles";
9
10
  const cashLogs = new Map();
10
11
  const datumConst = {
11
12
  map: cashLogs,
@@ -172,11 +173,8 @@ export function PageLogs({ update }) {
172
173
  return _jsx(Main, {});
173
174
  }
174
175
  function Message({ logs }) {
175
- let red = (logs.var ?? 0) * 10;
176
- if (red > 255)
177
- red = 255;
178
- return _jsxs("div", { className: "testAnime", style: { width: "200px", color: "rgb(255,255,255)", height: "auto", marginTop: "10px", borderRight: "5px solid #5D9FFA", background: `rgb(${red},73,35)` }, children: [_jsx("p", { style: { textAlign: "center", fontSize: "10px", marginBottom: "1px" }, children: "notification" }), _jsx("hr", { style: {
179
- backgroundImage: "linear-gradient(to right, transparent, rgba(255, 255, 255, 1), transparent)",
176
+ return _jsxs("div", { className: "testAnime", style: { width: "200px", color: logStyleTokens.text, height: "auto", marginTop: "10px", borderRight: `5px solid ${logStyleTokens.accent}`, background: logSeverityBackground(logs.var) }, children: [_jsx("p", { style: { textAlign: "center", fontSize: "10px", marginBottom: "1px" }, children: "notification" }), _jsx("hr", { style: {
177
+ backgroundImage: logDividerGradient(),
180
178
  border: 0,
181
179
  height: "1px",
182
180
  margin: "0 0 0 0",
@@ -208,8 +206,8 @@ export function MessageEventLogs({ zIndex }) {
208
206
  const tr = [...Object.values(tt)].reverse().slice(0, 10);
209
207
  return _jsxs("div", { style: { maxHeight: "50vh", position: "absolute", right: "1px", zIndex }, children: [_jsx("div", { onClick: () => { setting.params.show = !setting.params.show; renderBy(tt); }, style: { margin: 3, padding: 3, right: 0, position: "absolute", zIndex: 120,
210
208
  ...setting.params.show ?
211
- { background: "rgb(58,58,58)", fontSize: "25px" } :
212
- { background: "rgb(144,60,60)" }
209
+ { background: logStyleTokens.toggleBg, fontSize: "25px" } :
210
+ { background: logStyleTokens.toggleOffBg }
213
211
  }, children: setting.params.show ? "X" : "log" }), _jsx("div", { children: setting.params.show ? tr : null })] });
214
212
  }
215
213
  const defPageBase = {
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { AgGridReact } from 'ag-grid-react';
4
+ import { logDividerGradient, logSeverityBackground, logStyleTokens } from './logStyles';
4
5
  /** -----------------------------
5
6
  * 2. memoryGetOrCreate function -
6
7
  * loads and saves data in localStorage.
@@ -185,36 +186,31 @@ export function LogsNotifications() {
185
186
  if (!showMessages) {
186
187
  // If popups are hidden, show only "log"
187
188
  return (_jsx("div", { style: { position: 'absolute', right: 10, top: 10, zIndex: 999 }, children: _jsx("div", { style: {
188
- background: 'rgb(144,60,60)',
189
+ background: logStyleTokens.toggleOffBg,
189
190
  padding: '6px 10px',
190
191
  cursor: 'pointer'
191
192
  }, onClick: () => setShowMessages(true), children: "log" }) }));
192
193
  }
193
194
  // Otherwise render the current notification list
194
195
  return (_jsxs("div", { style: { position: 'absolute', right: 10, top: 10, zIndex: 999 }, children: [_jsx("div", { style: {
195
- background: 'rgb(58,58,58)',
196
+ background: logStyleTokens.toggleBg,
196
197
  fontSize: '20px',
197
198
  padding: '6px 10px',
198
199
  cursor: 'pointer'
199
- }, onClick: () => setShowMessages(false), children: "X" }), _jsx("div", { children: notifications.slice(0, 10).map(({ id, log }) => {
200
- let red = (log.var ?? 0) * 10;
201
- if (red > 255)
202
- red = 255;
203
- return (_jsxs("div", { className: "testAnime example-exit", style: {
204
- width: 200,
205
- color: 'white',
206
- marginTop: 10,
207
- borderRight: '5px solid #5D9FFA',
208
- backgroundColor: `rgb(${red},73,35)`,
209
- padding: 8,
210
- wordWrap: 'break-word'
211
- }, children: [_jsx("p", { style: { textAlign: 'center', fontSize: 10, marginBottom: 1 }, children: "notification" }), _jsx("hr", { style: {
212
- backgroundImage: 'linear-gradient(to right, transparent, rgba(255, 255, 255, 1), transparent)',
213
- border: 0,
214
- height: 1,
215
- margin: 0
216
- } }), _jsx("div", { style: { textAlign: 'right', marginRight: 10 }, children: typeof log.txt === 'object' ? JSON.stringify(log.txt) : log.txt }), _jsx("p", { style: { textAlign: 'right', marginRight: 10 }, children: new Date(log.time).toLocaleDateString() })] }, id));
217
- }) })] }));
200
+ }, onClick: () => setShowMessages(false), children: "X" }), _jsx("div", { children: notifications.slice(0, 10).map(({ id, log }) => (_jsxs("div", { className: "testAnime example-exit", style: {
201
+ width: 200,
202
+ color: logStyleTokens.text,
203
+ marginTop: 10,
204
+ borderRight: `5px solid ${logStyleTokens.accent}`,
205
+ backgroundColor: logSeverityBackground(log.var),
206
+ padding: 8,
207
+ wordWrap: 'break-word'
208
+ }, children: [_jsx("p", { style: { textAlign: 'center', fontSize: 10, marginBottom: 1 }, children: "notification" }), _jsx("hr", { style: {
209
+ backgroundImage: logDividerGradient(),
210
+ border: 0,
211
+ height: 1,
212
+ margin: 0
213
+ } }), _jsx("div", { style: { textAlign: 'right', marginRight: 10 }, children: typeof log.txt === 'object' ? JSON.stringify(log.txt) : log.txt }), _jsx("p", { style: { textAlign: 'right', marginRight: 10 }, children: new Date(log.time).toLocaleDateString() })] }, id))) })] }));
218
214
  }
219
215
  /** -----------------------------
220
216
  * 8. LogsSettings component
@@ -231,15 +227,15 @@ export function LogsSettings() {
231
227
  */
232
228
  export function MainPage() {
233
229
  const [currentTab, setCurrentTab] = useState('table');
234
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: { display: 'flex', gap: 8, padding: 10, background: '#333' }, children: [_jsx("button", { onClick: () => setCurrentTab('table'), style: {
235
- backgroundColor: currentTab === 'table' ? '#666' : '#444',
236
- color: 'white',
230
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: { display: 'flex', gap: 8, padding: 10, background: logStyleTokens.tabNavBg }, children: [_jsx("button", { onClick: () => setCurrentTab('table'), style: {
231
+ backgroundColor: currentTab === 'table' ? logStyleTokens.tabActiveBg : logStyleTokens.tabBg,
232
+ color: logStyleTokens.tabText,
237
233
  border: 'none',
238
234
  padding: '8px',
239
235
  cursor: 'pointer'
240
236
  }, children: "Log table" }), _jsx("button", { onClick: () => setCurrentTab('settings'), style: {
241
- backgroundColor: currentTab === 'settings' ? '#666' : '#444',
242
- color: 'white',
237
+ backgroundColor: currentTab === 'settings' ? logStyleTokens.tabActiveBg : logStyleTokens.tabBg,
238
+ color: logStyleTokens.tabText,
243
239
  border: 'none',
244
240
  padding: '8px',
245
241
  cursor: 'pointer'
@@ -31,11 +31,12 @@ declare function MenuProgress({ data }: {
31
31
  /*******************************************************
32
32
  * Main menu element with onClick and counters
33
33
  *******************************************************/
34
- declare function MenuElement({ data: item, toLeft, className, update, }: {
34
+ declare function MenuElement({ data: item, toLeft, className, update, open, }: {
35
35
  data: Pick<MenuItemStrict, "onClick" | "active" | "name" | "getStatus">;
36
36
  toLeft: boolean;
37
37
  className?: (active?: boolean) => string;
38
38
  update: () => void;
39
+ open?: boolean;
39
40
  }): ReactElement;
40
41
  /*******************************************************
41
42
  * Menu renders the popup menu with support for
@@ -29,7 +29,7 @@ function MenuProgress({ data }) {
29
29
  /*******************************************************
30
30
  * Main menu element with onClick and counters
31
31
  *******************************************************/
32
- function MenuElement({ data: item, toLeft, className, update, }) {
32
+ function MenuElement({ data: item, toLeft, className, update, open, }) {
33
33
  const unsubOk = useRef(null);
34
34
  const unsubErr = useRef(null);
35
35
  useEffect(() => {
@@ -42,8 +42,9 @@ function MenuElement({ data: item, toLeft, className, update, }) {
42
42
  };
43
43
  }, []);
44
44
  const [progress, setProgress] = useState(null);
45
- return (_jsx("div", { className: className?.(item.active?.()) ||
46
- "MenuR " + (item.active?.() ? "toButtonA" : "toButton"), style: { float: toLeft ? "left" : "right" }, onClick: () => {
45
+ const active = open || item.active?.();
46
+ return (_jsx("div", { className: className?.(active) ||
47
+ "MenuR " + (active ? "toButtonA" : "toButton"), style: { float: toLeft ? "left" : "right" }, onClick: () => {
47
48
  if (!item.onClick)
48
49
  return;
49
50
  const result = item?.onClick?.(item);
@@ -111,12 +112,12 @@ function MenuElement({ data: item, toLeft, className, update, }) {
111
112
  ? item.name
112
113
  : item.name(item.getStatus?.()), progress && _jsx(MenuProgress, { data: progress })] }) }));
113
114
  }
114
- const MenuItemWrapper = ({ item, index, update, className, isLeftAligned, leftPos, menuElement, fullArray, }) => {
115
+ const MenuItemWrapper = ({ item, index, update, className, isLeftAligned, leftPos, menuElement, open, setOpenIndex, }) => {
115
116
  const [childMenu, setChildMenu] = useState([]);
116
117
  const [asyncFuncElement, setAsyncFuncElement] = useState(null);
117
118
  const [onFocusMenu, setOnFocusMenu] = useState([]);
118
119
  useEffect(() => {
119
- if (item.status && item.next) {
120
+ if (open && item.next) {
120
121
  let alive = true; // guard: do not set state after unmount or item change
121
122
  const result = item.next();
122
123
  if (result instanceof Promise) {
@@ -133,9 +134,9 @@ const MenuItemWrapper = ({ item, index, update, className, isLeftAligned, leftPo
133
134
  else {
134
135
  setChildMenu([]);
135
136
  }
136
- }, [item.status, item.next]);
137
+ }, [open, item.next]);
137
138
  useEffect(() => {
138
- if (item.status && item.func) {
139
+ if (open && item.func) {
139
140
  let alive = true;
140
141
  const result = item.func();
141
142
  if (result instanceof Promise) {
@@ -152,9 +153,9 @@ const MenuItemWrapper = ({ item, index, update, className, isLeftAligned, leftPo
152
153
  else {
153
154
  setAsyncFuncElement(null);
154
155
  }
155
- }, [item.status, item.func]);
156
+ }, [open, item.func]);
156
157
  useEffect(() => {
157
- if (item.status && item.onFocus) {
158
+ if (open && item.onFocus) {
158
159
  let alive = true;
159
160
  const result = item.onFocus();
160
161
  if (result instanceof Promise) {
@@ -171,33 +172,31 @@ const MenuItemWrapper = ({ item, index, update, className, isLeftAligned, leftPo
171
172
  else {
172
173
  setOnFocusMenu([]);
173
174
  }
174
- }, [item.status, item.onFocus]);
175
+ }, [open, item.onFocus]);
175
176
  const onMouseEnter = () => {
176
- if (item.status)
177
+ if (open)
177
178
  return;
178
- fullArray.forEach((it, j) => {
179
- it.status = j === index;
180
- });
181
- update();
179
+ setOpenIndex(index);
182
180
  };
181
+ const viewItem = open == !!item.status ? item : { ...item, status: open };
183
182
  return (_jsxs("div", { className: "toLine", onMouseEnter: onMouseEnter, children: [menuElement
184
- ? menuElement(item)
183
+ ? menuElement(viewItem)
185
184
  : item.menuElement?.({
186
185
  toLeft: isLeftAligned,
187
- data: item,
186
+ data: viewItem,
188
187
  className,
189
188
  update,
190
- }) ?? (_jsx(MenuElement, { toLeft: isLeftAligned, data: item, className: className, update: update })), _jsxs("div", { children: [item.status && childMenu.length > 0 && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { data: childMenu, coordinate: {
189
+ }) ?? (_jsx(MenuElement, { toLeft: isLeftAligned, data: viewItem, className: className, update: update, open: open })), _jsxs("div", { children: [open && childMenu.length > 0 && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { data: childMenu, coordinate: {
191
190
  x: 3,
192
191
  y: 0,
193
192
  toLeft: isLeftAligned,
194
193
  left: leftPos,
195
- } }) })), item.status && asyncFuncElement && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { menu: () => asyncFuncElement, data: [], coordinate: {
194
+ } }) })), open && asyncFuncElement && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { menu: () => asyncFuncElement, data: [], coordinate: {
196
195
  x: 3,
197
196
  y: 0,
198
197
  toLeft: isLeftAligned,
199
198
  left: leftPos,
200
- } }) })), item.status && onFocusMenu.length > 0 && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { data: onFocusMenu, coordinate: {
199
+ } }) })), open && onFocusMenu.length > 0 && (_jsx("div", { style: { position: "relative" }, children: _jsx(Menu, { data: onFocusMenu, coordinate: {
201
200
  x: 3,
202
201
  y: 0,
203
202
  toLeft: isLeftAligned,
@@ -209,6 +208,14 @@ export function Menu({ coordinate = { x: 0, y: 0, toLeft: false, left: 0 }, data
209
208
  const update = () => forceUpdate((p) => !p);
210
209
  const refMenu = useRef(null);
211
210
  const dataMemo = useMemo(() => data.filter(Boolean), [data, data.length]);
211
+ const initialActiveIndex = () => {
212
+ const i = dataMemo.findIndex(item => item.status);
213
+ return i == -1 ? null : i;
214
+ };
215
+ const [activeIndex, setActiveIndex] = useState(initialActiveIndex);
216
+ useEffect(() => {
217
+ setActiveIndex(prev => prev != null && dataMemo[prev] ? prev : initialActiveIndex());
218
+ }, [dataMemo]);
212
219
  const [top, setTop] = useState(coordinate.y);
213
220
  const [leftPos, setLeftPos] = useState(coordinate.x);
214
221
  const [menuWidth, setMenuWidth] = useState(0);
@@ -250,6 +257,6 @@ export function Menu({ coordinate = { x: 0, y: 0, toLeft: false, left: 0 }, data
250
257
  ...alignStyle,
251
258
  }, children: menu
252
259
  ? menu(dataMemo)
253
- : dataMemo.map((item, i, arr) => (_jsx(MenuItemWrapper, { item: item, index: i, update: update, className: className, isLeftAligned: isLeftAligned, leftPos: leftPos, menuElement: menuElement, fullArray: dataMemo }, typeof item.name === "string" ? item.name : i))) }));
260
+ : dataMemo.map((item, i, arr) => (_jsx(MenuItemWrapper, { item: item, index: i, update: update, className: className, isLeftAligned: isLeftAligned, leftPos: leftPos, menuElement: menuElement, open: activeIndex === i, setOpenIndex: setActiveIndex }, typeof item.name === "string" ? item.name : i))) }));
254
261
  }
255
262
  export { MenuProgress, MenuElement };
@@ -17,6 +17,7 @@ export declare const tokens: {
17
17
  readonly itemColor: "#fff";
18
18
  readonly itemHoverColor: "#101010";
19
19
  readonly itemHoverBgColor: "#fff";
20
+ readonly outlineColor: "#007bff";
20
21
  readonly shadow: "0 0 20px 14px rgba(34, 60, 80, 0.2)";
21
22
  };
22
23
  /** FloatingWindow window chrome (--wnd-*). Defaults = legacy look; apps re-skin via :root[data-theme].
@@ -55,7 +56,7 @@ export declare const tokens: {
55
56
  readonly shadow: "0 12px 40px rgba(0, 0, 0, 0.5)";
56
57
  /** var(--color-bg-light) in CSS */
57
58
  readonly navBg: "#17202e";
58
- readonly navWidth: "168px";
59
+ readonly navWidth: "220px";
59
60
  };
60
61
  /** Toolbar (createToolbar) chrome (--tb-*). Dark defaults; apps re-skin via :root[data-theme]. */
61
62
  readonly tb: {
@@ -74,6 +75,18 @@ export declare const tokens: {
74
75
  readonly popRadius: "8px";
75
76
  readonly popShadow: "0 8px 28px rgba(0, 0, 0, 0.5)";
76
77
  };
78
+ /** Logs chrome (--logs-*). Defaults preserve the old logger look; apps re-skin via CSS vars. */
79
+ readonly logs: {
80
+ readonly notificationText: "#fff";
81
+ readonly notificationAccent: "#5D9FFA";
82
+ readonly toggleBg: "rgb(58, 58, 58)";
83
+ readonly toggleOffBg: "rgb(144, 60, 60)";
84
+ readonly divider: "rgba(255, 255, 255, 1)";
85
+ readonly tabNavBg: "#333";
86
+ readonly tabBg: "#444";
87
+ readonly tabActiveBg: "#666";
88
+ readonly tabText: "#fff";
89
+ };
77
90
  readonly font: {
78
91
  readonly family: "Roboto";
79
92
  readonly sizeBase: "12px";
@@ -19,6 +19,7 @@ export const tokens = {
19
19
  itemColor: '#fff',
20
20
  itemHoverColor: '#101010',
21
21
  itemHoverBgColor: '#fff',
22
+ outlineColor: '#007bff',
22
23
  shadow: '0 0 20px 14px rgba(34, 60, 80, 0.2)',
23
24
  },
24
25
  /** FloatingWindow window chrome (--wnd-*). Defaults = legacy look; apps re-skin via :root[data-theme].
@@ -57,7 +58,7 @@ export const tokens = {
57
58
  shadow: '0 12px 40px rgba(0, 0, 0, 0.5)',
58
59
  /** var(--color-bg-light) in CSS */
59
60
  navBg: '#17202e',
60
- navWidth: '168px',
61
+ navWidth: '220px',
61
62
  },
62
63
  /** Toolbar (createToolbar) chrome (--tb-*). Dark defaults; apps re-skin via :root[data-theme]. */
63
64
  tb: {
@@ -76,6 +77,18 @@ export const tokens = {
76
77
  popRadius: '8px',
77
78
  popShadow: '0 8px 28px rgba(0, 0, 0, 0.5)',
78
79
  },
80
+ /** Logs chrome (--logs-*). Defaults preserve the old logger look; apps re-skin via CSS vars. */
81
+ logs: {
82
+ notificationText: '#fff',
83
+ notificationAccent: '#5D9FFA',
84
+ toggleBg: 'rgb(58, 58, 58)',
85
+ toggleOffBg: 'rgb(144, 60, 60)',
86
+ divider: 'rgba(255, 255, 255, 1)',
87
+ tabNavBg: '#333',
88
+ tabBg: '#444',
89
+ tabActiveBg: '#666',
90
+ tabText: '#fff',
91
+ },
79
92
  font: {
80
93
  family: 'Roboto',
81
94
  sizeBase: '12px',
@@ -6,3 +6,4 @@ export * from './inputAutoStep';
6
6
  export * from './memoryStore';
7
7
  export * from './observableMap';
8
8
  export * from './pageVisibilityContext';
9
+ export * from './searchHistory';
@@ -6,3 +6,4 @@ export * from './inputAutoStep';
6
6
  export * from './memoryStore';
7
7
  export * from './observableMap';
8
8
  export * from './pageVisibilityContext';
9
+ export * from './searchHistory';
@@ -0,0 +1,14 @@
1
+ export type SearchHistoryState = {
2
+ items: string[];
3
+ };
4
+ export type SearchHistoryApi = ReturnType<typeof createSearchHistory>;
5
+ export declare function createSearchHistory(opts: {
6
+ key: string;
7
+ max?: number;
8
+ }): {
9
+ readonly items: string[];
10
+ use(): string[];
11
+ add(value: string): void;
12
+ remove(value: string): void;
13
+ clear(): void;
14
+ };