@shenora/react 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.ts +28 -47
- package/dist/bridge.js +37 -70
- package/dist/clipboard.d.ts +5 -10
- package/dist/clipboard.js +11 -18
- package/dist/devInterceptor.d.ts +8 -12
- package/dist/devInterceptor.js +10 -15
- package/dist/errors.d.ts +4 -5
- package/dist/errors.js +4 -5
- package/dist/eventBus.d.ts +13 -28
- package/dist/eventBus.js +19 -40
- package/dist/fileDialogs.d.ts +8 -11
- package/dist/fileDialogs.js +9 -13
- package/dist/hooks.d.ts +15 -24
- package/dist/hooks.js +19 -31
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -14
- package/dist/internal.d.ts +2 -8
- package/dist/internal.js +2 -8
- package/dist/media.d.ts +14 -22
- package/dist/media.js +14 -22
- package/dist/mediaPlayer.d.ts +39 -22
- package/dist/mediaPlayer.js +54 -44
- package/dist/moduleService.d.ts +11 -22
- package/dist/moduleService.js +11 -22
- package/dist/requests.d.ts +34 -65
- package/dist/requests.js +21 -54
- package/dist/segmentBinder.d.ts +13 -26
- package/dist/segmentBinder.js +65 -42
- package/dist/segmentStream.d.ts +43 -54
- package/dist/segmentStream.js +102 -70
- package/dist/store.d.ts +15 -26
- package/dist/store.js +63 -59
- package/dist/transport.d.ts +9 -18
- package/dist/transport.js +9 -18
- package/dist/types.d.ts +23 -57
- package/dist/types.js +19 -40
- package/dist/useDropZone.d.ts +13 -25
- package/dist/useDropZone.js +24 -41
- package/dist/windowCommands.d.ts +14 -18
- package/dist/windowCommands.js +16 -23
- package/package.json +1 -1
package/dist/useDropZone.js
CHANGED
|
@@ -7,21 +7,14 @@ export const DROP_ZONE_MODULE = 'SHENORA.DROPZONE';
|
|
|
7
7
|
const newZoneId = () => randomId('drop-zone-');
|
|
8
8
|
/**
|
|
9
9
|
* **The file-drop API for a Shenora page. Do not use the DOM's own drop event for files —
|
|
10
|
-
* it is not an alternative here, it is the thing this exists to replace.**
|
|
10
|
+
* it is not an alternative here, it is the thing this exists to replace.** A page-side `onDrop` gets a
|
|
11
|
+
* `File` whose only accessor is its CONTENT, so every dropped file is copied into the renderer and
|
|
12
|
+
* across the IPC boundary before the app knows whether it wants any of them. This gives you `string[]`
|
|
13
|
+
* OS paths instead — open lazily, stream, hash incrementally, move or link without copying.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* at drop time, before the app knows whether it wants any of them. Drop 200 files to filter by
|
|
16
|
-
* extension and you pay for all 200; drop a multi-GB asset and you pay that, to reach a file the
|
|
17
|
-
* host could have opened off the same disk. This hook gives you `string[]` paths instead — open
|
|
18
|
-
* lazily, stream, hash incrementally, move or link without copying, watch for changes.
|
|
19
|
-
*
|
|
20
|
-
* Sync a native drop-zone overlay to a page element, ported from the primary desktop sibling
|
|
21
|
-
* (its fix-history comments kept below): the host positions a transparent WinForms overlay
|
|
22
|
-
* over the element to capture REAL OS file paths — including drags started while the app is in
|
|
23
|
-
* the background. Bounds re-sync (debounced) on resize/scroll/intersection changes; the host
|
|
24
|
-
* converts the CSS rect to physical pixels per-monitor.
|
|
15
|
+
* The host positions a transparent native overlay over the element to capture those paths, including
|
|
16
|
+
* for drags started while the app is in the background. Bounds re-sync (debounced) on
|
|
17
|
+
* resize/scroll/intersection changes; the host converts the CSS rect to physical pixels per-monitor.
|
|
25
18
|
*
|
|
26
19
|
* How the visibility dance works: mouse leaves the element → SHOW (overlay up, ready to catch a
|
|
27
20
|
* drag); mouse enters → the host hides the overlay (hover effects keep working); an inactive
|
|
@@ -31,17 +24,12 @@ const newZoneId = () => randomId('drop-zone-');
|
|
|
31
24
|
export function useDropZone(options) {
|
|
32
25
|
const { targetRef, enabled = true } = options;
|
|
33
26
|
// ⚠ LAZY, because `useRef(newZoneId())` evaluates its argument on EVERY render and keeps only the
|
|
34
|
-
// first
|
|
35
|
-
//
|
|
36
|
-
// passes `zoneId: ''` short-circuits the `??` and so still never reaches the generator.
|
|
27
|
+
// first. The empty string is a safe sentinel: a generated id is never empty, and a caller passing
|
|
28
|
+
// `zoneId: ''` short-circuits the `??` and so still never reaches the generator.
|
|
37
29
|
const zoneIdRef = useRef('');
|
|
38
30
|
if (zoneIdRef.current === '')
|
|
39
31
|
zoneIdRef.current = options.zoneId ?? newZoneId();
|
|
40
|
-
//
|
|
41
|
-
// deliberate rather than an oversight: the hover effect captures this class for its cleanup, while
|
|
42
|
-
// the FILE_DROP effect reads it live, so a value that could change mid-hover would let one path add
|
|
43
|
-
// class A and another remove class B — leaving A stuck on the element with no drag in progress. The
|
|
44
|
-
// default is a constant, so unlike the id there is nothing here worth making lazy.
|
|
32
|
+
// Read ONCE, unlike `onDrop`/`bridge` below which track the latest value — see the option's docs.
|
|
45
33
|
const dropClassRef = useRef(options.dropClassName ?? 'shenora-drop-hover');
|
|
46
34
|
const onDropRef = useRef(options.onDrop);
|
|
47
35
|
onDropRef.current = options.onDrop;
|
|
@@ -49,22 +37,19 @@ export function useDropZone(options) {
|
|
|
49
37
|
bridgeRef.current = options.bridge;
|
|
50
38
|
const bus = options.bus ?? defaultEventBus;
|
|
51
39
|
// Tracks the latest handler (like `onDrop`), so a cleanup that runs long after mount still reports
|
|
52
|
-
// through the sink the app has NOW.
|
|
53
|
-
// took `onError` owns its reporting and must not be double-logged, the rule the package's other
|
|
54
|
-
// three sinks follow.
|
|
40
|
+
// through the sink the app has NOW. Logs only when the app supplied none.
|
|
55
41
|
const onErrorRef = useRef(options.onError);
|
|
56
42
|
onErrorRef.current = options.onError;
|
|
57
43
|
const reportRef = useRef(() => { });
|
|
58
44
|
reportRef.current = (error, route) => onErrorRef.current
|
|
59
45
|
? onErrorRef.current(error, route)
|
|
60
46
|
: console.error(`[shenora] drop-zone ${route} failed:`, error);
|
|
61
|
-
// Make the ref's CONTENT reactive
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// so this cannot loop.
|
|
47
|
+
// 🔴 Make the ref's CONTENT reactive. `targetRef` is a stable object, so an effect keyed on it runs
|
|
48
|
+
// exactly once — and if `targetRef.current` is null on that run (a conditionally-rendered target, or
|
|
49
|
+
// any order where the ref is attached after the first commit) the effect bails out and NEVER re-runs:
|
|
50
|
+
// the zone is silently dead for the component's whole life, with no error anywhere. A ref mutation
|
|
51
|
+
// triggers no render, so this effect has NO dependency array; `setElement` with an unchanged value is
|
|
52
|
+
// a React no-op, so it cannot loop.
|
|
68
53
|
const [element, setElement] = useState(null);
|
|
69
54
|
useEffect(() => {
|
|
70
55
|
setElement(targetRef.current ?? null);
|
|
@@ -76,10 +61,9 @@ export function useDropZone(options) {
|
|
|
76
61
|
const attemptedRef = useRef(false);
|
|
77
62
|
// A REGISTER is in flight — guards against sending a duplicate before the first resolves.
|
|
78
63
|
const registeringRef = useRef(false);
|
|
79
|
-
// Teardown epoch:
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// from an older epoch are ignored.
|
|
64
|
+
// Teardown epoch: a REGISTER ack must not apply after its zone was torn down. Under StrictMode's
|
|
65
|
+
// mount-unmount-remount a stale ack marks the DESTROYED zone "registered" and the overlay silently
|
|
66
|
+
// never exists again. Cleanup bumps the epoch; acks from an older epoch are ignored.
|
|
83
67
|
const epochRef = useRef(0);
|
|
84
68
|
const lastBoundsRef = useRef({ x: 0, y: 0, width: 0, height: 0 });
|
|
85
69
|
const syncBoundsRef = useRef(() => { });
|
|
@@ -165,11 +149,10 @@ export function useDropZone(options) {
|
|
|
165
149
|
window.removeEventListener('blur', onWindowBlur);
|
|
166
150
|
element.removeEventListener('mouseleave', onMouseLeave);
|
|
167
151
|
element.removeAttribute('data-drop-zone-id');
|
|
168
|
-
// Unregister whenever this effect tears down — on unmount OR when `enabled` flips false —
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
// no orphan).
|
|
152
|
+
// Unregister whenever this effect tears down — on unmount OR when `enabled` flips false — and
|
|
153
|
+
// never gated on the REGISTER ack, so an in-flight REGISTER is torn down too. The host's
|
|
154
|
+
// UnregisterZone no-ops if the overlay isn't there yet, and the ordered IPC channel processes
|
|
155
|
+
// the earlier REGISTER first, so there is no orphan.
|
|
173
156
|
if (attemptedRef.current) {
|
|
174
157
|
(bridgeRef.current ?? getBridge())
|
|
175
158
|
.invoke(DROP_ZONE_MODULE, 'UNREGISTER', { payload: { zoneId: zoneIdRef.current } })
|
package/dist/windowCommands.d.ts
CHANGED
|
@@ -54,31 +54,27 @@ export declare class WindowCommands extends BaseModuleService<WindowRequests> {
|
|
|
54
54
|
/** Resync the native chrome to the app theme (host `WindowCommandOptions.ApplyTheme`). */
|
|
55
55
|
setTheme(dark: boolean): Promise<void>;
|
|
56
56
|
/**
|
|
57
|
-
* Tell the host where the page drew its caption buttons, so the OS
|
|
58
|
-
*
|
|
59
|
-
* button never gets otherwise.
|
|
57
|
+
* Tell the host where the page drew its caption buttons, so the OS treats them as the real thing —
|
|
58
|
+
* chiefly so Windows 11 offers **Snap Layouts** on the maximize button.
|
|
60
59
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* only way to stay hot while the pointer is over the snap flyout, which is a different window.
|
|
60
|
+
* ⚠ The host then takes over CLICKS in those rects and performs minimize/maximize/close itself, so
|
|
61
|
+
* your `onClick` handlers stop firing there. CSS `:hover` stops firing too — subscribe to the host's
|
|
62
|
+
* caption-button state to render hot/pressed, which is also the only way to stay hot while the
|
|
63
|
+
* pointer is over the snap flyout, a different window.
|
|
66
64
|
*
|
|
67
|
-
* Re-send on every layout change
|
|
68
|
-
*
|
|
69
|
-
* to hand every pixel back to the page.
|
|
65
|
+
* ⚠ Re-send on every layout change: the rectangles are a snapshot, and a stale one moves the
|
|
66
|
+
* hit-test off the button the user can see. Pass an empty array to hand the pixels back to the page.
|
|
70
67
|
*/
|
|
71
68
|
setCaptionButtons(buttons: CaptionButtonRect[]): Promise<void>;
|
|
72
69
|
}
|
|
73
70
|
/**
|
|
74
|
-
* The
|
|
75
|
-
*
|
|
76
|
-
*
|
|
71
|
+
* The authoritative maximize state, re-queried when a resize SETTLES — a maximize/restore always
|
|
72
|
+
* resizes the window, and the DOM has no other signal for the manual work-area maximize. Failures
|
|
73
|
+
* (plain browser, no host) leave it false.
|
|
77
74
|
*
|
|
78
|
-
* Read once immediately, then on the TRAILING edge of a 100 ms debounce
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* not build on intermediate values during a drag; there are none.
|
|
75
|
+
* ⚠ Read once immediately, then only on the TRAILING edge of a 100 ms debounce, so there are no
|
|
76
|
+
* intermediate values during a drag to build on. Maximize/restore is a single step; only the end
|
|
77
|
+
* state exists.
|
|
82
78
|
*/
|
|
83
79
|
export declare function useWindowMaximized(commands?: WindowCommands): boolean;
|
|
84
80
|
export {};
|
package/dist/windowCommands.js
CHANGED
|
@@ -40,33 +40,29 @@ export class WindowCommands extends BaseModuleService {
|
|
|
40
40
|
return this.send('SET_THEME', { payload: { dark } });
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Tell the host where the page drew its caption buttons, so the OS
|
|
44
|
-
*
|
|
45
|
-
* button never gets otherwise.
|
|
43
|
+
* Tell the host where the page drew its caption buttons, so the OS treats them as the real thing —
|
|
44
|
+
* chiefly so Windows 11 offers **Snap Layouts** on the maximize button.
|
|
46
45
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* only way to stay hot while the pointer is over the snap flyout, which is a different window.
|
|
46
|
+
* ⚠ The host then takes over CLICKS in those rects and performs minimize/maximize/close itself, so
|
|
47
|
+
* your `onClick` handlers stop firing there. CSS `:hover` stops firing too — subscribe to the host's
|
|
48
|
+
* caption-button state to render hot/pressed, which is also the only way to stay hot while the
|
|
49
|
+
* pointer is over the snap flyout, a different window.
|
|
52
50
|
*
|
|
53
|
-
* Re-send on every layout change
|
|
54
|
-
*
|
|
55
|
-
* to hand every pixel back to the page.
|
|
51
|
+
* ⚠ Re-send on every layout change: the rectangles are a snapshot, and a stale one moves the
|
|
52
|
+
* hit-test off the button the user can see. Pass an empty array to hand the pixels back to the page.
|
|
56
53
|
*/
|
|
57
54
|
setCaptionButtons(buttons) {
|
|
58
55
|
return this.send('SET_CAPTION_BUTTONS', { payload: { buttons } });
|
|
59
56
|
}
|
|
60
57
|
}
|
|
61
58
|
/**
|
|
62
|
-
* The
|
|
63
|
-
*
|
|
64
|
-
*
|
|
59
|
+
* The authoritative maximize state, re-queried when a resize SETTLES — a maximize/restore always
|
|
60
|
+
* resizes the window, and the DOM has no other signal for the manual work-area maximize. Failures
|
|
61
|
+
* (plain browser, no host) leave it false.
|
|
65
62
|
*
|
|
66
|
-
* Read once immediately, then on the TRAILING edge of a 100 ms debounce
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* not build on intermediate values during a drag; there are none.
|
|
63
|
+
* ⚠ Read once immediately, then only on the TRAILING edge of a 100 ms debounce, so there are no
|
|
64
|
+
* intermediate values during a drag to build on. Maximize/restore is a single step; only the end
|
|
65
|
+
* state exists.
|
|
70
66
|
*/
|
|
71
67
|
export function useWindowMaximized(commands) {
|
|
72
68
|
const [maximized, setMaximized] = useState(false);
|
|
@@ -80,11 +76,8 @@ export function useWindowMaximized(commands) {
|
|
|
80
76
|
setMaximized(value);
|
|
81
77
|
}, () => { });
|
|
82
78
|
};
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// arming a 30-second timeout timer. The state that matters only changes at the END of a resize
|
|
86
|
-
// (maximize/restore is a single step), so the trailing edge is not just cheaper, it is the correct
|
|
87
|
-
// semantics. 100 ms matches the drop-zone bounds sync.
|
|
79
|
+
// `resize` fires continuously while a window is dragged, and each event undebounced is a full IPC
|
|
80
|
+
// round-trip arming its own 30-second timeout timer. 100 ms matches the drop-zone bounds sync.
|
|
88
81
|
const refresh = debounce(query, 100);
|
|
89
82
|
query(); // the initial read is immediate — nothing to coalesce yet
|
|
90
83
|
window.addEventListener('resize', refresh);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shenora/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "React client for Shenora hosts on Windows, Android and iOS: correlated invoke/send/subscribe over the desktop postMessage bridge or the MAUI HybridWebView transport, typed module services, host-backed stores, and hooks for request tracking and media playback. Drop zones and window commands are desktop-only, because the capabilities are. Ships a browser fallback for pure-UI development.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jiarong Gu",
|