wenay-react2 1.0.44 → 1.0.46

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 (46) hide show
  1. package/README.md +4 -0
  2. package/doc/changes/v1.0.44.md +1 -1
  3. package/doc/changes/v1.0.45.md +9 -0
  4. package/doc/changes/v1.0.46.md +16 -0
  5. package/doc/examples/README.md +6 -0
  6. package/doc/examples/peer-call-media.tsx +8 -0
  7. package/doc/examples/stand.tsx +6 -0
  8. package/doc/progress/column-state-present-gate.md +1 -1
  9. package/doc/progress/common2-adoption-1.0.73.md +30 -0
  10. package/doc/progress/common2-adoption-1.0.74.md +24 -0
  11. package/doc/progress/stand-as-examples-audit.md +22 -0
  12. package/doc/target/my.md +23 -9
  13. package/doc/wenay-react2.md +2 -2
  14. package/lib/common/demo/peerMedia.d.ts +6 -0
  15. package/lib/common/demo/peerMedia.js +128 -0
  16. package/lib/common/src/components/Toolbar/Toolbar.d.ts +4 -0
  17. package/lib/common/src/components/Toolbar/Toolbar.js +16 -7
  18. package/lib/common/src/components/index.d.ts +12 -0
  19. package/lib/common/src/components/index.js +12 -0
  20. package/lib/common/src/grid/columnState/CardList.d.ts +2 -0
  21. package/lib/common/src/grid/columnState/CardList.js +1 -1
  22. package/lib/common/src/grid/columnState/ColumnsMenu.d.ts +2 -0
  23. package/lib/common/src/grid/columnState/ColumnsMenu.js +3 -2
  24. package/lib/common/src/grid/columnState/columnState.d.ts +15 -0
  25. package/lib/common/src/grid/columnState/columnState.js +53 -5
  26. package/lib/common/src/hooks/index.d.ts +6 -3
  27. package/lib/common/src/hooks/index.js +6 -3
  28. package/lib/common/src/hooks/useMedia.d.ts +14 -0
  29. package/lib/common/src/hooks/useMedia.js +39 -0
  30. package/lib/common/src/hooks/usePeer.d.ts +16 -0
  31. package/lib/common/src/hooks/usePeer.js +29 -0
  32. package/lib/common/src/hooks/usePeerCall.d.ts +68 -0
  33. package/lib/common/src/hooks/usePeerCall.js +79 -0
  34. package/lib/common/src/hooks/useReorder.d.ts +2 -0
  35. package/lib/common/src/hooks/useReorder.js +3 -1
  36. package/lib/common/src/utils/memoryStore.d.ts +2 -2
  37. package/lib/common/testUseReact/qa.d.ts +1 -0
  38. package/lib/common/testUseReact/qa.js +1148 -0
  39. package/lib/common/testUseReact/replayVideo.d.ts +14 -0
  40. package/lib/common/testUseReact/replayVideo.js +311 -0
  41. package/lib/common/testUseReact/testParams.d.ts +1 -0
  42. package/lib/common/testUseReact/testParams.js +15 -0
  43. package/lib/common/testUseReact/useGrid.d.ts +2 -0
  44. package/lib/common/testUseReact/useGrid.js +62 -0
  45. package/lib/style/style.css +24 -0
  46. package/package.json +52 -50
@@ -21,6 +21,8 @@ export function createColumnState(opts) {
21
21
  const st = memoryGetOrCreate(opts.key, defConfig());
22
22
  const stApi = createUpdateApi(st);
23
23
  const [emitChange, onChange] = createListen();
24
+ const previewRt = { order: null };
25
+ const previewApi = createUpdateApi(previewRt);
24
26
  /** Runtime-only, never persisted: actual = which keys the attached grid has;
25
27
  * gate = optional app-level availability over a stable grid schema (standards,
26
28
  * mode blocks). Consumers see actual AND gate as one presence map. */
@@ -126,6 +128,33 @@ export function createColumnState(opts) {
126
128
  const getConfig = () => normalize();
127
129
  const setConfig = (next) => commit(next, false);
128
130
  const reset = () => commit(defConfig(), false);
131
+ function displayConfig() {
132
+ const cfg = normalize();
133
+ return previewRt.order ? { ...cfg, order: previewRt.order.slice() } : cfg;
134
+ }
135
+ function useDisplayConfig() {
136
+ stApi.use();
137
+ previewApi.use();
138
+ return displayConfig();
139
+ }
140
+ function setPreviewOrder(order) {
141
+ const cfg = normalize();
142
+ const known = new Set(cfg.order);
143
+ const next = order ? pinFixedOrder(order.filter(k => known.has(k)), opts.columns) : null;
144
+ if (JSON.stringify(next) == JSON.stringify(previewRt.order))
145
+ return;
146
+ previewRt.order = next;
147
+ previewApi.render();
148
+ if (!gridApi || gridApi.isDestroyed?.())
149
+ return;
150
+ applying = true;
151
+ try {
152
+ gridApi.applyColumnState({ state: (next ?? cfg.order).map(colId => ({ colId })), applyOrder: true });
153
+ }
154
+ finally {
155
+ applying = false;
156
+ }
157
+ }
129
158
  function useConfig() {
130
159
  stApi.use();
131
160
  return normalize();
@@ -154,14 +183,22 @@ export function createColumnState(opts) {
154
183
  * they edit the SAME config. */
155
184
  const listSource = {
156
185
  useConfig() {
157
- const c = useConfig();
186
+ const c = useDisplayConfig();
158
187
  return { order: c.order, visible: c.visible };
159
188
  },
160
189
  getConfig() {
190
+ const c = displayConfig();
191
+ return { order: c.order, visible: c.visible };
192
+ },
193
+ useBaseConfig() {
194
+ const c = useConfig();
195
+ return { order: c.order, visible: c.visible };
196
+ },
197
+ getBaseConfig() {
161
198
  const c = normalize();
162
199
  return { order: c.order, visible: c.visible };
163
200
  },
164
- setConfig(next) {
201
+ setPreview: setPreviewOrder, setConfig(next) {
165
202
  const cfg = normalize();
166
203
  // an editor over a SUBSET of columns must not lose the rest:
167
204
  // unknown incoming keys are dropped by normalize, missing ones re-appended
@@ -262,9 +299,20 @@ export function createColumnState(opts) {
262
299
  function onGridEvent(e) {
263
300
  if (applying || e?.source == 'api')
264
301
  return;
265
- // resize/move fire per animation frame during a drag; persist the final shape only
266
- if (e?.finished === false)
302
+ if (e?.type == 'columnMoved' && e.finished === false && gridApi) {
303
+ const known = new Set(opts.columns.map(c => c.key));
304
+ const order = gridApi.getColumnState().map(c => c.colId).filter((k) => !!k && known.has(k));
305
+ for (const k of normalize().order)
306
+ if (!order.includes(k))
307
+ order.push(k);
308
+ setPreviewOrder(order);
267
309
  return;
310
+ }
311
+ if (e?.type == 'columnMoved' && previewRt.order) {
312
+ readFromGrid();
313
+ setPreviewOrder(null);
314
+ return;
315
+ }
268
316
  clearTimeout(saveTimer);
269
317
  saveTimer = setTimeout(readFromGrid, opts.saveMs ?? 300);
270
318
  }
@@ -310,7 +358,7 @@ export function createColumnState(opts) {
310
358
  * (dots, cards, icon menus) render from these + the config. */
311
359
  columns: opts.columns,
312
360
  api: { getConfig, setConfig, useConfig, onChange, reset, show, move, setSort, toggleSort, visibleKeys,
313
- getPresent, usePresent, isPresent, setPresent, getPresentGate, setPresentGate, listSource },
361
+ getPresent, usePresent, isPresent, setPresent, getPresentGate, setPresentGate, useDisplayConfig, setPreviewOrder, listSource },
314
362
  grid: { attach, detach },
315
363
  };
316
364
  }
@@ -1,7 +1,10 @@
1
+ export * from './useReplay';
2
+ export * from './useObserveStore';
3
+ export * from './useOutside';
1
4
  export * from './useKeyboard';
2
5
  export * from './useDraggable';
3
6
  export * from './useReorder';
4
7
  export * from './useReorderBoard';
5
- export * from './useObserveStore';
6
- export * from './useReplay';
7
- export * from './useOutside';
8
+ export * from './useMedia';
9
+ export * from './usePeer';
10
+ export * from './usePeerCall';
@@ -1,7 +1,10 @@
1
+ export * from './useReplay';
2
+ export * from './useObserveStore';
3
+ export * from './useOutside';
1
4
  export * from './useKeyboard';
2
5
  export * from './useDraggable';
3
6
  export * from './useReorder';
4
7
  export * from './useReorderBoard';
5
- export * from './useObserveStore';
6
- export * from './useReplay';
7
- export * from './useOutside';
8
+ export * from './useMedia';
9
+ export * from './usePeer';
10
+ export * from './usePeerCall';
@@ -0,0 +1,14 @@
1
+ import { Media } from "wenay-common2";
2
+ export type UseMediaSourceController = {
3
+ source: Media.MediaSource;
4
+ listen: Media.MediaAnyListen;
5
+ state: Media.MediaSourceState;
6
+ start(): Promise<Media.MediaSourceState>;
7
+ stop(): void;
8
+ setDevice(id: string): Promise<Media.MediaSourceState>;
9
+ listDevices(): Promise<Media.MediaSourceDevice[]>;
10
+ stats(): Media.MediaStats;
11
+ };
12
+ /** React lifecycle adapter for a common2 Media source. Frames stay on `listen`:
13
+ * draw/play them through a canvas or AudioContext, never through React state. */
14
+ export declare function useMediaSource(kind: "audio" | "video", options?: Media.AudioSourceOpts | Media.VideoSourceOpts): UseMediaSourceController;
@@ -0,0 +1,39 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Media } from "wenay-common2";
3
+ /** React lifecycle adapter for a common2 Media source. Frames stay on `listen`:
4
+ * draw/play them through a canvas or AudioContext, never through React state. */
5
+ export function useMediaSource(kind, options = {}) {
6
+ const sourceRef = useRef(null);
7
+ if (!sourceRef.current)
8
+ sourceRef.current = kind == "video"
9
+ ? Media.createVideoSource(options)
10
+ : Media.createAudioSource(options);
11
+ const source = sourceRef.current;
12
+ const [state, setState] = useState(source.state);
13
+ const sync = useCallback(() => setState(source.state), [source]);
14
+ const start = useCallback(async () => {
15
+ const next = await source.start();
16
+ setState(next);
17
+ return next;
18
+ }, [source]);
19
+ const stop = useCallback(() => {
20
+ source.stop();
21
+ sync();
22
+ }, [source, sync]);
23
+ const setDevice = useCallback(async (id) => {
24
+ const next = await source.setDevice(id);
25
+ setState(next);
26
+ return next;
27
+ }, [source]);
28
+ useEffect(() => () => source.stop(), [source]);
29
+ return useMemo(() => ({
30
+ source,
31
+ listen: source[1],
32
+ state,
33
+ start,
34
+ stop,
35
+ setDevice,
36
+ listDevices: () => source.listDevices(),
37
+ stats: () => source.getStats(),
38
+ }), [source, state, start, stop, setDevice]);
39
+ }
@@ -0,0 +1,16 @@
1
+ import { Peer } from "wenay-common2";
2
+ /** Thin React view over common2 Peer SDK. The SDK keeps route/repair ownership;
3
+ * consumers use `store` with useStoreNode/useStoreKeys as usual. */
4
+ export declare function usePeer<T extends object>(client: Peer.PeerClient<T>, account: string): {
5
+ store: import("wenay-common2/lib/Common/Observe").Store<T>;
6
+ ready: boolean;
7
+ route: string;
8
+ state: import("wenay-common2/lib/Common/events/route-coordinator").tRouteState;
9
+ seq: () => number;
10
+ promoteDirect: (opts?: import("wenay-common2/lib/Common/events/route-coordinator").PromoteDirectOpts) => Promise<import("wenay-common2/lib/Common/events/route-coordinator").RouteOpResult>;
11
+ reinterposeRelay: (reason?: unknown) => Promise<import("wenay-common2/lib/Common/events/route-coordinator").RouteOpResult>;
12
+ fallback: (reason?: unknown) => Promise<import("wenay-common2/lib/Common/events/route-coordinator").RouteOpResult>;
13
+ block: (reason?: unknown) => Promise<import("wenay-common2/lib/Common/events/route-coordinator").RouteOpResult>;
14
+ resync: () => Promise<void>;
15
+ close: () => void;
16
+ };
@@ -0,0 +1,29 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+ /** Thin React view over common2 Peer SDK. The SDK keeps route/repair ownership;
3
+ * consumers use `store` with useStoreNode/useStoreKeys as usual. */
4
+ export function usePeer(client, account) {
5
+ const peer = useMemo(() => client.peer(account), [client, account]);
6
+ const [epoch, setEpoch] = useState(0);
7
+ const [ready, setReady] = useState(false);
8
+ useEffect(() => {
9
+ let alive = true;
10
+ setReady(false);
11
+ peer.ready.then(() => { if (alive)
12
+ setReady(true); });
13
+ const off = client.onRoute(() => setEpoch(v => v + 1));
14
+ return () => { alive = false; off(); };
15
+ }, [client, peer]);
16
+ return useMemo(() => ({
17
+ store: peer.store,
18
+ ready,
19
+ route: peer.route(),
20
+ state: peer.state(),
21
+ seq: peer.seq,
22
+ promoteDirect: peer.promoteDirect,
23
+ reinterposeRelay: peer.reinterposeRelay,
24
+ fallback: peer.fallback,
25
+ block: peer.block,
26
+ resync: client.resync,
27
+ close: peer.close,
28
+ }), [client, peer, ready, epoch]);
29
+ }
@@ -0,0 +1,68 @@
1
+ import { Peer } from "wenay-common2";
2
+ export type PeerPresence = {
3
+ account: string;
4
+ online: boolean;
5
+ };
6
+ /** React binding for common2 host presence. Subscribe before list(): `changes` is the
7
+ * authoritative edge stream and React does not create its own presence transport. */
8
+ export declare function usePeerPresence(presence: Peer.PeerRemote["presence"] | undefined): {
9
+ accounts: string[];
10
+ online: (account: string) => boolean;
11
+ };
12
+ /** Thin UI binding over common2 CallManager. Signaling, busy/glare policy, timeout,
13
+ * offline verdict and lifecycle ownership stay in common2; callers own manager.close(). */
14
+ export declare function usePeerCalls(manager: Peer.CallManager): {
15
+ ready: boolean;
16
+ active: {
17
+ id: string;
18
+ peer: string;
19
+ direction: "out" | "in";
20
+ meta: unknown;
21
+ state: () => Peer.tCallState;
22
+ reason: () => Peer.tCallEnd | null;
23
+ changed: import("wenay-common2").ListenApi<[Peer.tCallState]>;
24
+ ended: Promise<Peer.tCallEnd>;
25
+ accept(): void;
26
+ decline(why?: Peer.tCallEnd): void;
27
+ hangup(): void;
28
+ } | null;
29
+ rings: {
30
+ id: string;
31
+ peer: string;
32
+ direction: "out" | "in";
33
+ meta: unknown;
34
+ state: () => Peer.tCallState;
35
+ reason: () => Peer.tCallEnd | null;
36
+ changed: import("wenay-common2").ListenApi<[Peer.tCallState]>;
37
+ ended: Promise<Peer.tCallEnd>;
38
+ accept(): void;
39
+ decline(why?: Peer.tCallEnd): void;
40
+ hangup(): void;
41
+ }[];
42
+ calls: {
43
+ id: string;
44
+ peer: string;
45
+ direction: "out" | "in";
46
+ meta: unknown;
47
+ state: () => Peer.tCallState;
48
+ reason: () => Peer.tCallEnd | null;
49
+ changed: import("wenay-common2").ListenApi<[Peer.tCallState]>;
50
+ ended: Promise<Peer.tCallEnd>;
51
+ accept(): void;
52
+ decline(why?: Peer.tCallEnd): void;
53
+ hangup(): void;
54
+ }[];
55
+ call: (account: string, meta?: unknown) => {
56
+ id: string;
57
+ peer: string;
58
+ direction: "out" | "in";
59
+ meta: unknown;
60
+ state: () => Peer.tCallState;
61
+ reason: () => Peer.tCallEnd | null;
62
+ changed: import("wenay-common2").ListenApi<[Peer.tCallState]>;
63
+ ended: Promise<Peer.tCallEnd>;
64
+ accept(): void;
65
+ decline(why?: Peer.tCallEnd): void;
66
+ hangup(): void;
67
+ };
68
+ };
@@ -0,0 +1,79 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ /** React binding for common2 host presence. Subscribe before list(): `changes` is the
3
+ * authoritative edge stream and React does not create its own presence transport. */
4
+ export function usePeerPresence(presence) {
5
+ const [accounts, setAccounts] = useState([]);
6
+ useEffect(() => {
7
+ if (!presence) {
8
+ setAccounts([]);
9
+ return;
10
+ }
11
+ let alive = true;
12
+ const off = presence.changes.on(({ account, online }) => {
13
+ if (!alive)
14
+ return;
15
+ setAccounts(current => online
16
+ ? current.includes(account) ? current : [...current, account].sort()
17
+ : current.filter(value => value !== account));
18
+ });
19
+ Promise.resolve(presence.list()).then(list => {
20
+ if (alive)
21
+ setAccounts([...new Set(list)].sort());
22
+ });
23
+ return () => { alive = false; off(); };
24
+ }, [presence]);
25
+ return useMemo(() => ({ accounts, online: (account) => accounts.includes(account) }), [accounts]);
26
+ }
27
+ /** Thin UI binding over common2 CallManager. Signaling, busy/glare policy, timeout,
28
+ * offline verdict and lifecycle ownership stay in common2; callers own manager.close(). */
29
+ export function usePeerCalls(manager) {
30
+ const calls = useRef(new Map());
31
+ const [version, setVersion] = useState(0);
32
+ const touch = useCallback(() => setVersion(value => value + 1), []);
33
+ useEffect(() => {
34
+ let alive = true;
35
+ const offs = new Map();
36
+ const watch = (call) => {
37
+ if (calls.current.has(call.id))
38
+ return;
39
+ calls.current.set(call.id, call);
40
+ offs.set(call.id, call.changed.on(touch));
41
+ void call.ended.then(() => {
42
+ offs.get(call.id)?.();
43
+ offs.delete(call.id);
44
+ calls.current.delete(call.id);
45
+ if (alive)
46
+ touch();
47
+ });
48
+ touch();
49
+ };
50
+ const offRings = manager.rings.on(watch);
51
+ void manager.ready.then(() => { if (alive)
52
+ touch(); });
53
+ return () => {
54
+ alive = false;
55
+ offRings();
56
+ offs.forEach(off => off());
57
+ calls.current.clear();
58
+ };
59
+ }, [manager, touch]);
60
+ const call = useCallback((account, meta) => {
61
+ const handle = manager.call(account, meta);
62
+ // Outgoing calls do not arrive through `rings`; register them immediately.
63
+ calls.current.set(handle.id, handle);
64
+ const off = handle.changed.on(touch);
65
+ void handle.ended.then(() => { off(); calls.current.delete(handle.id); touch(); });
66
+ touch();
67
+ return handle;
68
+ }, [manager, touch]);
69
+ return useMemo(() => {
70
+ const known = [...calls.current.values()];
71
+ return {
72
+ ready: version > 0,
73
+ active: manager.active(),
74
+ rings: known.filter(handle => handle.state() === "ringing"),
75
+ calls: known,
76
+ call,
77
+ };
78
+ }, [manager, call, version]);
79
+ }
@@ -27,6 +27,8 @@ export type ReorderOptions = {
27
27
  preview?: 'slots' | 'measure';
28
28
  /** hold before the drag starts (touch-friendly fields); default 0 */
29
29
  holdMs?: number;
30
+ /** transient simulated order while dragging; null on drop/cancel */
31
+ onPreviewChange?: (next: string[] | null) => void;
30
32
  };
31
33
  export type ReorderItem = {
32
34
  /** spread on the block element */
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import { useDraggableApi } from './useDraggable';
3
3
  export function useReorder(o) {
4
4
  const listRef = useRef(null);
@@ -96,6 +96,8 @@ export function useReorder(o) {
96
96
  const from = dragKey != null ? o.order.indexOf(dragKey) : -1;
97
97
  const target = from != -1 ? dragTarget(from, local(drag.position.x), local(drag.position.y)) : -1;
98
98
  const preview = from != -1 && dragKey != null ? move(o.order, dragKey, target) : null;
99
+ const previewKey = preview?.join("\u0000") ?? "";
100
+ useEffect(() => o.onPreviewChange?.(preview), [previewKey]);
99
101
  function item(key) {
100
102
  const active = preview != null;
101
103
  const dragging = key == dragKey;
@@ -27,8 +27,8 @@ export declare const memoryCache: {
27
27
  getArr: [k: string, v: Map<string, unknown>][];
28
28
  };
29
29
  export declare const memoryMaps: {
30
- rnd: ObservableMap<string, import("../components/Dnd").FloatingWindowSavedGeometry>;
30
+ rnd: ObservableMap<string, import("../components").FloatingWindowSavedGeometry>;
31
31
  resize: ObservableMap<string, import("./persistedMaps").ResizableSavedSize>;
32
- rightMenu: ObservableMap<string, import("../components/Menu").MenuRightSavedState>;
32
+ rightMenu: ObservableMap<string, import("../components").MenuRightSavedState>;
33
33
  other: ObservableMap<string, object>;
34
34
  };
@@ -0,0 +1 @@
1
+ export declare function QABoard(): import("react/jsx-runtime").JSX.Element;