@shenora/react 0.9.1 → 0.10.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/fileDialogs.d.ts +153 -0
- package/dist/fileDialogs.js +90 -0
- package/dist/hooks.d.ts +23 -1
- package/dist/hooks.js +24 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/types.d.ts +13 -0
- package/dist/types.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { ShenoraBridge } from './bridge.js';
|
|
2
|
+
import { BaseModuleService } from './moduleService.js';
|
|
3
|
+
/** One dialog filter row (e.g. `{ name: 'Images', extensions: ['png', 'jpg'] }`). */
|
|
4
|
+
export interface FileDialogFilter {
|
|
5
|
+
name: string;
|
|
6
|
+
/** Extensions WITHOUT the dot or wildcard — `'png'`, not `'*.png'`. */
|
|
7
|
+
extensions: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What EVERY dialog call takes. The per-dialog shapes below add what only that dialog can honour —
|
|
11
|
+
* which is the point: a save-only field on a folder pick will not compile.
|
|
12
|
+
*/
|
|
13
|
+
export interface FileDialogOptions {
|
|
14
|
+
/** Dialog title. Omit for a neutral per-dialog default. */
|
|
15
|
+
title?: string;
|
|
16
|
+
/** Start location when nothing is remembered. */
|
|
17
|
+
defaultPath?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Key under which the host remembers the last-used directory and restores it next time.
|
|
20
|
+
* Remembered PER KEY, so "import" and "export" stay separate. Omit for no memory.
|
|
21
|
+
*/
|
|
22
|
+
rememberPathKey?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Inputs for {@link FileDialogs.openFile}. */
|
|
25
|
+
export interface OpenFileOptions extends FileDialogOptions {
|
|
26
|
+
/** Filter rows; omit/empty = "All Files". */
|
|
27
|
+
filters?: FileDialogFilter[];
|
|
28
|
+
/** Initial file name shown in the dialog. */
|
|
29
|
+
fileName?: string;
|
|
30
|
+
/** Require the picked file to exist. Default true. Desktop hint — a mobile picker ignores it. */
|
|
31
|
+
checkFileExists?: boolean;
|
|
32
|
+
/** Require the path to exist. Default true. Desktop hint. */
|
|
33
|
+
checkPathExists?: boolean;
|
|
34
|
+
/** Validate the host's file-name rules. Default true. Desktop hint. */
|
|
35
|
+
validateNames?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Inputs for {@link FileDialogs.openFolder}. */
|
|
38
|
+
export interface OpenFolderOptions extends FileDialogOptions {
|
|
39
|
+
/** Also allow picking a FILE. The host swaps in a file dialog with relaxed validation. */
|
|
40
|
+
allowFileSelection?: boolean;
|
|
41
|
+
/** Filter rows for the FILE half — ⚠ ignored unless {@link allowFileSelection} is set. */
|
|
42
|
+
filters?: FileDialogFilter[];
|
|
43
|
+
}
|
|
44
|
+
/** Inputs for {@link FileDialogs.saveFile} and {@link FileDialogs.saveText}. */
|
|
45
|
+
export interface SaveFileOptions extends FileDialogOptions {
|
|
46
|
+
/** Filter rows; omit/empty = "All Files". */
|
|
47
|
+
filters?: FileDialogFilter[];
|
|
48
|
+
/** The name being suggested. */
|
|
49
|
+
fileName?: string;
|
|
50
|
+
/** Extension appended when the user omits one (no dot). */
|
|
51
|
+
defaultExtension?: string;
|
|
52
|
+
/** Prompt before overwriting. Default true. Desktop hint. */
|
|
53
|
+
overwritePrompt?: boolean;
|
|
54
|
+
/** Require the path to exist. Default true. Desktop hint. */
|
|
55
|
+
checkPathExists?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A dialog outcome. ⚠ **THREE outcomes, not two** — and the third is the one that surprises people:
|
|
59
|
+
* cancelled (`success: false`); succeeded with an addressable location (`filePath` set); and
|
|
60
|
+
* **succeeded with NO location**, which is what {@link FileDialogs.saveText} returns on a phone,
|
|
61
|
+
* because the bytes went to a revocable grant rather than somewhere the app may reopen.
|
|
62
|
+
*
|
|
63
|
+
* So `success && filePath === undefined` is legitimate, and `result.filePath!` after checking
|
|
64
|
+
* `success` is a null-reference waiting for a mobile shell.
|
|
65
|
+
*/
|
|
66
|
+
export interface FileDialogResult {
|
|
67
|
+
success: boolean;
|
|
68
|
+
filePath?: string;
|
|
69
|
+
}
|
|
70
|
+
interface FileDialogRequests {
|
|
71
|
+
OPEN_FILE: {
|
|
72
|
+
options?: OpenFileOptions;
|
|
73
|
+
};
|
|
74
|
+
OPEN_FOLDER: {
|
|
75
|
+
options?: OpenFolderOptions;
|
|
76
|
+
};
|
|
77
|
+
SAVE_FILE: {
|
|
78
|
+
options?: SaveFileOptions;
|
|
79
|
+
};
|
|
80
|
+
SAVE_TEXT: {
|
|
81
|
+
text: string;
|
|
82
|
+
options?: SaveFileOptions;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Typed client for the host's `FILE_DIALOGS` module (`FileDialogFacade`).
|
|
87
|
+
*
|
|
88
|
+
* ⚠ **Two of these are DESKTOP capabilities.** `openFolder` and `saveFile` have no expression on a
|
|
89
|
+
* phone and reject with `IpcErrorCodes.capabilityNotSupported` there. Do not catch that — ask first,
|
|
90
|
+
* via {@link useFileDialogs}, and do not render the control at all. `openFile` and `saveText` work
|
|
91
|
+
* everywhere.
|
|
92
|
+
*/
|
|
93
|
+
export declare class FileDialogs extends BaseModuleService<FileDialogRequests> {
|
|
94
|
+
constructor(bridge?: ShenoraBridge);
|
|
95
|
+
/** Pick an existing file. Available on every shell. */
|
|
96
|
+
openFile(options?: OpenFileOptions): Promise<FileDialogResult>;
|
|
97
|
+
/**
|
|
98
|
+
* Pick a folder. ⚠ DESKTOP only — gate on {@link FileDialogsHandle.canPickFolder}.
|
|
99
|
+
*
|
|
100
|
+
* On mobile "open folder" means the camera roll, the app's own space, or a scoped grant: the same
|
|
101
|
+
* word with a different guarantee, which is why there is no portable version of it.
|
|
102
|
+
*/
|
|
103
|
+
openFolder(options?: OpenFolderOptions): Promise<FileDialogResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Pick a save destination and get the PATH back. ⚠ DESKTOP only — gate on
|
|
106
|
+
* {@link FileDialogsHandle.canPickSavePath}. "Give me somewhere to write later" has no mobile
|
|
107
|
+
* expression; use {@link saveText} in portable code, which also works on the desktop.
|
|
108
|
+
*/
|
|
109
|
+
saveFile(options?: SaveFileOptions): Promise<FileDialogResult>;
|
|
110
|
+
/**
|
|
111
|
+
* Pick a destination AND write `text` to it, in one call — the PORTABLE save, working everywhere
|
|
112
|
+
* because the HOST does the writing.
|
|
113
|
+
*
|
|
114
|
+
* ⚠ For text a page legitimately holds: an export, a report, a config. The content crosses the IPC
|
|
115
|
+
* envelope as JSON, so anything large or binary should be produced host-side and saved through the
|
|
116
|
+
* host's own `IFileDialogs.SaveAsync`, where it never enters a message.
|
|
117
|
+
*/
|
|
118
|
+
saveText(text: string, options?: SaveFileOptions): Promise<FileDialogResult>;
|
|
119
|
+
}
|
|
120
|
+
/** What {@link useFileDialogs} returns: the client, plus what this shell will actually honour. */
|
|
121
|
+
export interface FileDialogsHandle {
|
|
122
|
+
/** The typed client. Stable across renders. */
|
|
123
|
+
dialogs: FileDialogs;
|
|
124
|
+
/** This shell can pick an existing file. */
|
|
125
|
+
canPickFile: boolean;
|
|
126
|
+
/** This shell can pick a FOLDER — desktop only (see {@link FileDialogs.openFolder}). */
|
|
127
|
+
canPickFolder: boolean;
|
|
128
|
+
/** This shell can return a save PATH — desktop only (see {@link FileDialogs.saveFile}). */
|
|
129
|
+
canPickSavePath: boolean;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The dialogs client together with what the CURRENT shell can honour — read from the ready
|
|
133
|
+
* handshake, not sniffed from the platform (D36).
|
|
134
|
+
*
|
|
135
|
+
* ```tsx
|
|
136
|
+
* const { dialogs, canPickFolder } = useFileDialogs();
|
|
137
|
+
* return (
|
|
138
|
+
* <>
|
|
139
|
+
* <button onClick={() => dialogs.openFile()}>Choose a file</button>
|
|
140
|
+
* {canPickFolder && <button onClick={() => dialogs.openFolder()}>Choose a folder</button>}
|
|
141
|
+
* </>
|
|
142
|
+
* );
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* ⚠ **Use these to decide what to RENDER, not what to catch.** A refused call rejects with
|
|
146
|
+
* `IpcErrorCodes.capabilityNotSupported`, which is the honest answer to a question that should not
|
|
147
|
+
* have been asked — the button should not have been there.
|
|
148
|
+
*
|
|
149
|
+
* ⚠ Every flag is `false` until the handshake has landed, so await `bridge.notifyReady()` before
|
|
150
|
+
* rendering this tree — see {@link useShellInfo} for why the read is synchronous.
|
|
151
|
+
*/
|
|
152
|
+
export declare function useFileDialogs(dialogs?: FileDialogs): FileDialogsHandle;
|
|
153
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's half of the host's `FILE_DIALOGS` module — native file/folder/save dialogs, and the
|
|
3
|
+
* capability gating that lets ONE bundle ship to every shell.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors `Shenora.Ipc.FileDialogFacade`. The wire names here are pinned against the host's own
|
|
6
|
+
* constants by `WireMirrorTests`, not by care.
|
|
7
|
+
*/
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
|
+
import { useShellInfo } from './hooks.js';
|
|
10
|
+
import { BaseModuleService } from './moduleService.js';
|
|
11
|
+
import { ShellCapabilities } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Typed client for the host's `FILE_DIALOGS` module (`FileDialogFacade`).
|
|
14
|
+
*
|
|
15
|
+
* ⚠ **Two of these are DESKTOP capabilities.** `openFolder` and `saveFile` have no expression on a
|
|
16
|
+
* phone and reject with `IpcErrorCodes.capabilityNotSupported` there. Do not catch that — ask first,
|
|
17
|
+
* via {@link useFileDialogs}, and do not render the control at all. `openFile` and `saveText` work
|
|
18
|
+
* everywhere.
|
|
19
|
+
*/
|
|
20
|
+
export class FileDialogs extends BaseModuleService {
|
|
21
|
+
constructor(bridge) {
|
|
22
|
+
super('FILE_DIALOGS', bridge);
|
|
23
|
+
}
|
|
24
|
+
/** Pick an existing file. Available on every shell. */
|
|
25
|
+
openFile(options) {
|
|
26
|
+
return this.send('OPEN_FILE', { payload: { options } });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Pick a folder. ⚠ DESKTOP only — gate on {@link FileDialogsHandle.canPickFolder}.
|
|
30
|
+
*
|
|
31
|
+
* On mobile "open folder" means the camera roll, the app's own space, or a scoped grant: the same
|
|
32
|
+
* word with a different guarantee, which is why there is no portable version of it.
|
|
33
|
+
*/
|
|
34
|
+
openFolder(options) {
|
|
35
|
+
return this.send('OPEN_FOLDER', { payload: { options } });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pick a save destination and get the PATH back. ⚠ DESKTOP only — gate on
|
|
39
|
+
* {@link FileDialogsHandle.canPickSavePath}. "Give me somewhere to write later" has no mobile
|
|
40
|
+
* expression; use {@link saveText} in portable code, which also works on the desktop.
|
|
41
|
+
*/
|
|
42
|
+
saveFile(options) {
|
|
43
|
+
return this.send('SAVE_FILE', { payload: { options } });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pick a destination AND write `text` to it, in one call — the PORTABLE save, working everywhere
|
|
47
|
+
* because the HOST does the writing.
|
|
48
|
+
*
|
|
49
|
+
* ⚠ For text a page legitimately holds: an export, a report, a config. The content crosses the IPC
|
|
50
|
+
* envelope as JSON, so anything large or binary should be produced host-side and saved through the
|
|
51
|
+
* host's own `IFileDialogs.SaveAsync`, where it never enters a message.
|
|
52
|
+
*/
|
|
53
|
+
saveText(text, options) {
|
|
54
|
+
return this.send('SAVE_TEXT', { payload: { text, options } });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The dialogs client together with what the CURRENT shell can honour — read from the ready
|
|
59
|
+
* handshake, not sniffed from the platform (D36).
|
|
60
|
+
*
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const { dialogs, canPickFolder } = useFileDialogs();
|
|
63
|
+
* return (
|
|
64
|
+
* <>
|
|
65
|
+
* <button onClick={() => dialogs.openFile()}>Choose a file</button>
|
|
66
|
+
* {canPickFolder && <button onClick={() => dialogs.openFolder()}>Choose a folder</button>}
|
|
67
|
+
* </>
|
|
68
|
+
* );
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* ⚠ **Use these to decide what to RENDER, not what to catch.** A refused call rejects with
|
|
72
|
+
* `IpcErrorCodes.capabilityNotSupported`, which is the honest answer to a question that should not
|
|
73
|
+
* have been asked — the button should not have been there.
|
|
74
|
+
*
|
|
75
|
+
* ⚠ Every flag is `false` until the handshake has landed, so await `bridge.notifyReady()` before
|
|
76
|
+
* rendering this tree — see {@link useShellInfo} for why the read is synchronous.
|
|
77
|
+
*/
|
|
78
|
+
export function useFileDialogs(dialogs) {
|
|
79
|
+
const shell = useShellInfo();
|
|
80
|
+
// The service is stateless and holds only its module name, but a fresh instance per render would
|
|
81
|
+
// still make it useless as an effect dependency — the same reason useWindowMaximized caches one.
|
|
82
|
+
const client = useMemo(() => dialogs ?? new FileDialogs(), [dialogs]);
|
|
83
|
+
const capabilities = shell?.capabilities;
|
|
84
|
+
return useMemo(() => ({
|
|
85
|
+
dialogs: client,
|
|
86
|
+
canPickFile: capabilities?.includes(ShellCapabilities.filePicker) ?? false,
|
|
87
|
+
canPickFolder: capabilities?.includes(ShellCapabilities.folderPicker) ?? false,
|
|
88
|
+
canPickSavePath: capabilities?.includes(ShellCapabilities.savePicker) ?? false,
|
|
89
|
+
}), [client, capabilities]);
|
|
90
|
+
}
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { type ShenoraBridge } from './bridge.js';
|
|
2
2
|
import { type ShenoraEventBus } from './eventBus.js';
|
|
3
|
-
import type { EventMessage } from './types.js';
|
|
3
|
+
import type { EventMessage, ShellInfo } from './types.js';
|
|
4
4
|
/** Host access for components: the (default) bridge and whether a host transport exists. */
|
|
5
5
|
export declare function useShenora(): {
|
|
6
6
|
isAvailable: boolean;
|
|
7
7
|
bridge: ShenoraBridge;
|
|
8
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* What the host IS and what it can do — the way one bundle renders correctly on every shell.
|
|
11
|
+
* Branch on {@link ShellInfo.capabilities}, never on {@link ShellInfo.name}.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const shell = useShellInfo();
|
|
15
|
+
* return <>{shell?.capabilities.includes(ShellCapabilities.windowChrome) && <TitleBar />}</>;
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* `undefined` means no host said anything — a plain browser tab, a host predating the handshake, or a
|
|
19
|
+
* handshake that has not finished. **Treat absent as "assume nothing", never as "assume desktop".**
|
|
20
|
+
*
|
|
21
|
+
* ⚠ **Await `bridge.notifyReady()` before rendering the tree that depends on this.** The value is read
|
|
22
|
+
* SYNCHRONOUSLY from the bridge's cache, deliberately — a capability learned after layout is a visible
|
|
23
|
+
* flash — which means this hook does not re-render when a handshake lands later. A component mounted
|
|
24
|
+
* mid-handshake sees `undefined` and keeps seeing it, which is the documented "assume nothing" tree
|
|
25
|
+
* rather than a wrong one, but it is not the tree you wanted.
|
|
26
|
+
*
|
|
27
|
+
* ⚠ This hook was referenced by two doc examples in this package for several releases before it
|
|
28
|
+
* existed — `bridge.shell` was the only way to read it. If you wrote that workaround, this replaces it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function useShellInfo(bridge?: ShenoraBridge): ShellInfo | undefined;
|
|
9
31
|
/**
|
|
10
32
|
* Subscribe to one (module, type) event for the component's lifetime, ported from the primary
|
|
11
33
|
* desktop sibling. The handler receives the unwrapped payload (plus the full event). DEVIATION
|
package/dist/hooks.js
CHANGED
|
@@ -6,6 +6,30 @@ export function useShenora() {
|
|
|
6
6
|
const bridge = getBridge();
|
|
7
7
|
return { isAvailable: bridge.isAvailable, bridge };
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* What the host IS and what it can do — the way one bundle renders correctly on every shell.
|
|
11
|
+
* Branch on {@link ShellInfo.capabilities}, never on {@link ShellInfo.name}.
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const shell = useShellInfo();
|
|
15
|
+
* return <>{shell?.capabilities.includes(ShellCapabilities.windowChrome) && <TitleBar />}</>;
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* `undefined` means no host said anything — a plain browser tab, a host predating the handshake, or a
|
|
19
|
+
* handshake that has not finished. **Treat absent as "assume nothing", never as "assume desktop".**
|
|
20
|
+
*
|
|
21
|
+
* ⚠ **Await `bridge.notifyReady()` before rendering the tree that depends on this.** The value is read
|
|
22
|
+
* SYNCHRONOUSLY from the bridge's cache, deliberately — a capability learned after layout is a visible
|
|
23
|
+
* flash — which means this hook does not re-render when a handshake lands later. A component mounted
|
|
24
|
+
* mid-handshake sees `undefined` and keeps seeing it, which is the documented "assume nothing" tree
|
|
25
|
+
* rather than a wrong one, but it is not the tree you wanted.
|
|
26
|
+
*
|
|
27
|
+
* ⚠ This hook was referenced by two doc examples in this package for several releases before it
|
|
28
|
+
* existed — `bridge.shell` was the only way to read it. If you wrote that workaround, this replaces it.
|
|
29
|
+
*/
|
|
30
|
+
export function useShellInfo(bridge) {
|
|
31
|
+
return (bridge ?? getBridge()).shell;
|
|
32
|
+
}
|
|
9
33
|
/**
|
|
10
34
|
* Subscribe to one (module, type) event for the component's lifetime, ported from the primary
|
|
11
35
|
* desktop sibling. The handler receives the unwrapped payload (plus the full event). DEVIATION
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { IpcCategories, IpcErrorCodes, HANDSHAKE_MODULE, HANDSHAKE_TYPE, type IpcRequest, type IpcResponse, type IpcError, type IpcNotification, type IpcNotificationBatch, type EventMessage, ShellCapabilities, type ShellInfo, } from './types.js';
|
|
2
2
|
export { OperationError } from './errors.js';
|
|
3
3
|
export { isShenoraAvailable, createHostTransport, createWebView2Transport, createHybridWebViewTransport, type ShenoraTransport, } from './transport.js';
|
|
4
|
-
export { ShenoraEventBus, eventBus } from './eventBus.js';
|
|
4
|
+
export { ShenoraEventBus, eventBus, type SubscribeOptions, } from './eventBus.js';
|
|
5
5
|
export { ShenoraBridge, getBridge, configureBridge, type ShenoraBridgeOptions, type InvokeOptions, type PostOptions, type PostFailure, } from './bridge.js';
|
|
6
6
|
export { createShenoraStore, type ShenoraStore, type ShenoraStoreOptions, type ShenoraStoreIo, type ShenoraStoreSnapshot, } from './store.js';
|
|
7
7
|
export { BaseModuleService } from './moduleService.js';
|
|
8
8
|
export { OperationStatuses, OperationEventTypes, OperationModuleName, createOperationsStore, useShenoraOperations, type OperationStatus, type OperationLabel, type OperationProgress, type OperationInfo, type OperationsState, type OperationsActions, type OperationsStoreOptions, } from './operations.js';
|
|
9
9
|
export { WindowCommands, useWindowMaximized, type WindowResizeEdge, type CaptionButtonKind, type CaptionButtonRect, } from './windowCommands.js';
|
|
10
10
|
export { useDropZone, DROP_ZONE_MODULE, type DropZoneFileDrop, type UseDropZoneOptions, } from './useDropZone.js';
|
|
11
|
-
export { useShenora, useShenoraEvent, useShenoraQuery, type ShenoraQueryResult } from './hooks.js';
|
|
11
|
+
export { useShenora, useShenoraEvent, useShenoraQuery, useShellInfo, type ShenoraQueryResult, } from './hooks.js';
|
|
12
|
+
export { FileDialogs, useFileDialogs, type FileDialogFilter, type FileDialogOptions, type OpenFileOptions, type OpenFolderOptions, type SaveFileOptions, type FileDialogResult, type FileDialogsHandle, } from './fileDialogs.js';
|
|
12
13
|
export { installDevInterceptor, type DevInterceptorOptions, type DevIpcEntry, type DevEventEntry, } from './devInterceptor.js';
|
|
13
14
|
export { mediaUrl, encodeMediaPayload, decodeMediaPayload } from './media.js';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export { IpcCategories, IpcErrorCodes, HANDSHAKE_MODULE, HANDSHAKE_TYPE, ShellCapabilities, } from './types.js';
|
|
6
6
|
export { OperationError } from './errors.js';
|
|
7
7
|
export { isShenoraAvailable, createHostTransport, createWebView2Transport, createHybridWebViewTransport, } from './transport.js';
|
|
8
|
-
export { ShenoraEventBus, eventBus } from './eventBus.js';
|
|
8
|
+
export { ShenoraEventBus, eventBus, } from './eventBus.js';
|
|
9
9
|
export { ShenoraBridge, getBridge, configureBridge, } from './bridge.js';
|
|
10
10
|
export { createShenoraStore, } from './store.js';
|
|
11
11
|
export { BaseModuleService } from './moduleService.js';
|
|
@@ -17,7 +17,9 @@ export { OperationStatuses,
|
|
|
17
17
|
OperationEventTypes, OperationModuleName, createOperationsStore, useShenoraOperations, } from './operations.js';
|
|
18
18
|
export { WindowCommands, useWindowMaximized, } from './windowCommands.js';
|
|
19
19
|
export { useDropZone, DROP_ZONE_MODULE, } from './useDropZone.js';
|
|
20
|
-
export { useShenora, useShenoraEvent, useShenoraQuery } from './hooks.js';
|
|
20
|
+
export { useShenora, useShenoraEvent, useShenoraQuery, useShellInfo, } from './hooks.js';
|
|
21
|
+
// Native dialogs, capability-gated. The client half of the host's FILE_DIALOGS module.
|
|
22
|
+
export { FileDialogs, useFileDialogs, } from './fileDialogs.js';
|
|
21
23
|
export { installDevInterceptor, } from './devInterceptor.js';
|
|
22
24
|
// Addressing local content the page cannot reach itself. A pure function, not a hook — building the URL
|
|
23
25
|
// needs no React, and a `useMediaSource` can follow if an adopter wants load/error state.
|
package/dist/types.d.ts
CHANGED
|
@@ -37,6 +37,19 @@ export declare const IpcErrorCodes: {
|
|
|
37
37
|
* one failure a UI should stay silent about. Previously indistinguishable from `UNKNOWN_ERROR`.
|
|
38
38
|
*/
|
|
39
39
|
readonly operationCancelled: "OPERATION_CANCELLED";
|
|
40
|
+
/**
|
|
41
|
+
* The shell has NO EXPRESSION of what was asked for — not a fault, and not something a retry fixes.
|
|
42
|
+
* Parameters: `capability` (a {@link ShellCapabilities} value).
|
|
43
|
+
*
|
|
44
|
+
* Treat it like `operationCancelled`: do not show a fault. The right response is to hide the control,
|
|
45
|
+
* because the capability is absent by design on that platform (a folder picker on a phone, for
|
|
46
|
+
* instance) rather than broken.
|
|
47
|
+
*
|
|
48
|
+
* ⚠ A page should not normally NEED this. The ready handshake advertises `ShellInfo.capabilities`
|
|
49
|
+
* precisely so one bundle can decide BEFORE it asks — `useFileDialogs().canPickFolder` is the
|
|
50
|
+
* intended path. This is the honest answer when a page asks anyway.
|
|
51
|
+
*/
|
|
52
|
+
readonly capabilityNotSupported: "CAPABILITY_NOT_SUPPORTED";
|
|
40
53
|
/** Client-only: the request timed out waiting for a response. */
|
|
41
54
|
readonly timeout: "TIMEOUT";
|
|
42
55
|
/** Client-only: no transport is available and no fallback was configured. */
|
package/dist/types.js
CHANGED
|
@@ -37,6 +37,19 @@ export const IpcErrorCodes = {
|
|
|
37
37
|
* one failure a UI should stay silent about. Previously indistinguishable from `UNKNOWN_ERROR`.
|
|
38
38
|
*/
|
|
39
39
|
operationCancelled: 'OPERATION_CANCELLED',
|
|
40
|
+
/**
|
|
41
|
+
* The shell has NO EXPRESSION of what was asked for — not a fault, and not something a retry fixes.
|
|
42
|
+
* Parameters: `capability` (a {@link ShellCapabilities} value).
|
|
43
|
+
*
|
|
44
|
+
* Treat it like `operationCancelled`: do not show a fault. The right response is to hide the control,
|
|
45
|
+
* because the capability is absent by design on that platform (a folder picker on a phone, for
|
|
46
|
+
* instance) rather than broken.
|
|
47
|
+
*
|
|
48
|
+
* ⚠ A page should not normally NEED this. The ready handshake advertises `ShellInfo.capabilities`
|
|
49
|
+
* precisely so one bundle can decide BEFORE it asks — `useFileDialogs().canPickFolder` is the
|
|
50
|
+
* intended path. This is the honest answer when a page asks anyway.
|
|
51
|
+
*/
|
|
52
|
+
capabilityNotSupported: 'CAPABILITY_NOT_SUPPORTED',
|
|
40
53
|
/** Client-only: the request timed out waiting for a response. */
|
|
41
54
|
timeout: 'TIMEOUT',
|
|
42
55
|
/** Client-only: no transport is available and no fallback was configured. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shenora/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "React client for Shenora desktop hosts: correlated invoke/send/subscribe over the WebView2 bridge, typed module services, hooks, and a browser fallback for pure-UI development.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jiarong Gu",
|