@moldable-ai/ui 0.2.20 → 0.2.22
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/catalog/catalog-coverage.d.ts +15 -0
- package/dist/catalog/catalog-coverage.d.ts.map +1 -0
- package/dist/catalog/catalog-coverage.js +44 -0
- package/dist/catalog/catalog-manifest.d.ts.map +1 -1
- package/dist/catalog/catalog-manifest.js +35 -23
- package/dist/catalog/catalog-types.d.ts +17 -0
- package/dist/catalog/catalog-types.d.ts.map +1 -1
- package/dist/catalog/catalog-types.js +7 -0
- package/dist/catalog/component-catalog.d.ts.map +1 -1
- package/dist/catalog/component-catalog.js +16 -6
- package/dist/catalog/core-catalog-exclusions.d.ts +8 -0
- package/dist/catalog/core-catalog-exclusions.d.ts.map +1 -0
- package/dist/catalog/core-catalog-exclusions.js +155 -0
- package/dist/catalog/core-catalog.d.ts +0 -4
- package/dist/catalog/core-catalog.d.ts.map +1 -1
- package/dist/catalog/core-catalog.js +12 -98
- package/dist/catalog/core-collection-stories.d.ts +3 -0
- package/dist/catalog/core-collection-stories.d.ts.map +1 -0
- package/dist/catalog/core-collection-stories.js +42 -0
- package/dist/catalog/core-control-stories.d.ts +3 -0
- package/dist/catalog/core-control-stories.d.ts.map +1 -0
- package/dist/catalog/core-control-stories.js +97 -0
- package/dist/catalog/core-feedback-stories.d.ts +3 -0
- package/dist/catalog/core-feedback-stories.d.ts.map +1 -0
- package/dist/catalog/core-feedback-stories.js +50 -0
- package/dist/catalog/core-foundation-stories.d.ts +3 -0
- package/dist/catalog/core-foundation-stories.d.ts.map +1 -0
- package/dist/catalog/core-foundation-stories.js +53 -0
- package/dist/catalog/core-navigation-stories.d.ts +3 -0
- package/dist/catalog/core-navigation-stories.d.ts.map +1 -0
- package/dist/catalog/core-navigation-stories.js +25 -0
- package/dist/catalog/core-overlay-stories.d.ts +3 -0
- package/dist/catalog/core-overlay-stories.d.ts.map +1 -0
- package/dist/catalog/core-overlay-stories.js +20 -0
- package/dist/catalog/index.d.ts +3 -1
- package/dist/catalog/index.d.ts.map +1 -1
- package/dist/catalog/index.js +3 -1
- package/dist/components/app-error-boundary.d.ts.map +1 -1
- package/dist/components/app-error-boundary.js +3 -2
- package/dist/components/chat/chat-panel.d.ts +0 -1
- package/dist/components/chat/chat-panel.d.ts.map +1 -1
- package/dist/components/chat/chat-panel.js +7 -11
- package/dist/components/ui/alert-dialog.d.ts +10 -4
- package/dist/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/components/ui/alert-dialog.js +14 -6
- package/dist/components/ui/alert.d.ts +19 -3
- package/dist/components/ui/alert.d.ts.map +1 -1
- package/dist/components/ui/alert.js +25 -8
- package/dist/components/ui/app-frame.d.ts +9 -3
- package/dist/components/ui/app-frame.d.ts.map +1 -1
- package/dist/components/ui/app-frame.js +45 -6
- package/dist/components/ui/button.d.ts +6 -4
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.js +40 -8
- package/dist/components/ui/collapsible.d.ts +9 -1
- package/dist/components/ui/collapsible.d.ts.map +1 -1
- package/dist/components/ui/collapsible.js +13 -2
- package/dist/components/ui/collection-navigation.d.ts +8 -0
- package/dist/components/ui/collection-navigation.d.ts.map +1 -0
- package/dist/components/ui/collection-navigation.js +19 -0
- package/dist/components/ui/command.d.ts +5 -1
- package/dist/components/ui/command.d.ts.map +1 -1
- package/dist/components/ui/command.js +13 -1
- package/dist/components/ui/confirm-dialog.d.ts +19 -0
- package/dist/components/ui/confirm-dialog.d.ts.map +1 -0
- package/dist/components/ui/confirm-dialog.js +45 -0
- package/dist/components/ui/date-picker.d.ts +24 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +92 -0
- package/dist/components/ui/dialog-primary-action.d.ts +4 -0
- package/dist/components/ui/dialog-primary-action.d.ts.map +1 -0
- package/dist/components/ui/dialog-primary-action.js +37 -0
- package/dist/components/ui/dialog.d.ts +8 -2
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +15 -5
- package/dist/components/ui/empty.d.ts +7 -2
- package/dist/components/ui/empty.d.ts.map +1 -1
- package/dist/components/ui/empty.js +20 -7
- package/dist/components/ui/error-boundary-view.d.ts +12 -0
- package/dist/components/ui/error-boundary-view.d.ts.map +1 -0
- package/dist/components/ui/error-boundary-view.js +8 -0
- package/dist/components/ui/grid.d.ts.map +1 -1
- package/dist/components/ui/grid.js +70 -46
- package/dist/components/ui/icon-button.d.ts.map +1 -1
- package/dist/components/ui/icon-button.js +1 -2
- package/dist/components/ui/index.d.ts +5 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -0
- package/dist/components/ui/input-group.d.ts +1 -1
- package/dist/components/ui/list.d.ts +5 -1
- package/dist/components/ui/list.d.ts.map +1 -1
- package/dist/components/ui/list.js +27 -16
- package/dist/components/ui/material.d.ts +34 -0
- package/dist/components/ui/material.d.ts.map +1 -0
- package/dist/components/ui/material.js +34 -0
- package/dist/components/ui/message-scroller-controller.d.ts +162 -0
- package/dist/components/ui/message-scroller-controller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-controller.js +888 -0
- package/dist/components/ui/message-scroller-policy.d.ts +74 -0
- package/dist/components/ui/message-scroller-policy.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-policy.js +129 -0
- package/dist/components/ui/message-scroller-primitive.d.ts +55 -0
- package/dist/components/ui/message-scroller-primitive.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-primitive.js +226 -0
- package/dist/components/ui/message-scroller.d.ts +2 -2
- package/dist/components/ui/message-scroller.d.ts.map +1 -1
- package/dist/components/ui/message-scroller.js +3 -15
- package/dist/components/ui/navigation-button-group.d.ts +16 -0
- package/dist/components/ui/navigation-button-group.d.ts.map +1 -0
- package/dist/components/ui/navigation-button-group.js +9 -0
- package/dist/components/ui/popover.d.ts +9 -2
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +13 -3
- package/dist/components/ui/scroll-area.d.ts +8 -2
- package/dist/components/ui/scroll-area.d.ts.map +1 -1
- package/dist/components/ui/scroll-area.js +6 -3
- package/dist/components/ui/search-field.d.ts.map +1 -1
- package/dist/components/ui/segmented-control.d.ts.map +1 -1
- package/dist/components/ui/segmented-control.js +6 -5
- package/dist/components/ui/sheet.d.ts +4 -2
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +12 -9
- package/dist/components/ui/sonner.d.ts +1 -1
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/spinner.d.ts.map +1 -1
- package/dist/components/ui/spinner.js +1 -1
- package/dist/components/ui/table.d.ts.map +1 -1
- package/dist/components/ui/table.js +7 -2
- package/dist/components/ui/toggle.d.ts +12 -4
- package/dist/components/ui/toggle.d.ts.map +1 -1
- package/dist/components/ui/toggle.js +13 -2
- package/dist/components/ui/toolbar.d.ts +9 -2
- package/dist/components/ui/toolbar.d.ts.map +1 -1
- package/dist/components/ui/toolbar.js +23 -4
- package/dist/components/ui/tooltip.d.ts +8 -3
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +11 -6
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/lib/frame-lifecycle.d.ts +23 -0
- package/dist/lib/frame-lifecycle.d.ts.map +1 -1
- package/dist/lib/frame-lifecycle.js +30 -3
- package/dist/lib/host-date-time-picker.d.ts +36 -0
- package/dist/lib/host-date-time-picker.d.ts.map +1 -0
- package/dist/lib/host-date-time-picker.js +162 -0
- package/dist/lib/host-file-dialog.d.ts +66 -0
- package/dist/lib/host-file-dialog.d.ts.map +1 -0
- package/dist/lib/host-file-dialog.js +221 -0
- package/dist/lib/host-native-menu.d.ts +75 -0
- package/dist/lib/host-native-menu.d.ts.map +1 -0
- package/dist/lib/host-native-menu.js +187 -0
- package/dist/lib/host-services.d.ts +13 -0
- package/dist/lib/host-services.d.ts.map +1 -0
- package/dist/lib/host-services.js +161 -0
- package/dist/lib/host-share.d.ts +39 -0
- package/dist/lib/host-share.d.ts.map +1 -0
- package/dist/lib/host-share.js +161 -0
- package/dist/lib/theme.d.ts.map +1 -1
- package/dist/lib/theme.js +2 -1
- package/docs/README.md +4 -1
- package/docs/adaptive-materials.md +79 -0
- package/docs/app-anatomy.md +3 -1
- package/docs/component-catalog.md +26 -3
- package/docs/design-tokens.md +17 -4
- package/docs/foundations.md +5 -3
- package/docs/standalone-app-windows.md +8 -0
- package/docs/visual-regression.md +78 -0
- package/package.json +12 -4
- package/src/components/ui/alert-dialog.md +58 -0
- package/src/components/ui/alert.md +28 -6
- package/src/components/ui/app-frame.md +5 -0
- package/src/components/ui/button.md +10 -3
- package/src/components/ui/collapsible.md +52 -0
- package/src/components/ui/combobox.md +0 -1
- package/src/components/ui/command.md +54 -0
- package/src/components/ui/date-field.md +1 -1
- package/src/components/ui/date-picker.md +55 -0
- package/src/components/ui/dialog.md +59 -0
- package/src/components/ui/empty.md +8 -3
- package/src/components/ui/error-boundary-view.md +98 -0
- package/src/components/ui/grid.md +8 -4
- package/src/components/ui/icon-button.md +2 -2
- package/src/components/ui/list.md +35 -2
- package/src/components/ui/material.md +51 -0
- package/src/components/ui/navigation-button-group.md +46 -0
- package/src/components/ui/popover.md +31 -0
- package/src/components/ui/scroll-area.md +11 -7
- package/src/components/ui/search-field.md +1 -1
- package/src/components/ui/segmented-control.md +2 -5
- package/src/components/ui/sheet.md +30 -0
- package/src/components/ui/table.md +4 -2
- package/src/components/ui/toggle-button.md +48 -0
- package/src/components/ui/toolbar.md +5 -0
- package/src/components/ui/tooltip.md +31 -0
- package/src/styles/index.css +213 -0
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
let frameLifecycleInstalled = false;
|
|
2
|
+
let advertisedHostServices;
|
|
3
|
+
function normalizeHostServices(value) {
|
|
4
|
+
if (typeof value !== 'object' ||
|
|
5
|
+
value === null ||
|
|
6
|
+
!('version' in value) ||
|
|
7
|
+
value.version !== 1 ||
|
|
8
|
+
!('services' in value) ||
|
|
9
|
+
typeof value.services !== 'object' ||
|
|
10
|
+
value.services === null ||
|
|
11
|
+
Array.isArray(value.services)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const services = {};
|
|
15
|
+
for (const [name, details] of Object.entries(value.services)) {
|
|
16
|
+
if (typeof details === 'object' &&
|
|
17
|
+
details !== null &&
|
|
18
|
+
!Array.isArray(details)) {
|
|
19
|
+
services[name] = { ...details };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { version: 1, services };
|
|
23
|
+
}
|
|
24
|
+
export function getMoldableHostServices() {
|
|
25
|
+
return advertisedHostServices;
|
|
26
|
+
}
|
|
2
27
|
export function applyMoldableChatSafePadding(safePadding) {
|
|
3
28
|
if (typeof document === 'undefined')
|
|
4
29
|
return false;
|
|
@@ -25,9 +50,11 @@ function applyMoldableChatState(message) {
|
|
|
25
50
|
: 'docked';
|
|
26
51
|
document.documentElement.dataset.moldableChat = presentation;
|
|
27
52
|
}
|
|
28
|
-
function applyMoldableHostContext(message) {
|
|
53
|
+
export function applyMoldableHostContext(message) {
|
|
29
54
|
const root = document.documentElement;
|
|
55
|
+
advertisedHostServices = normalizeHostServices(message.hostServices);
|
|
30
56
|
root.dataset.moldableDisplayMode = message.displayMode;
|
|
57
|
+
root.dataset.moldableWindowTitlebar = message.titlebar ?? 'host';
|
|
31
58
|
if (message.platform)
|
|
32
59
|
root.dataset.moldablePlatform = message.platform;
|
|
33
60
|
if (message.windowId)
|
|
@@ -55,6 +82,7 @@ function installHostStateLifecycle() {
|
|
|
55
82
|
const root = document.documentElement;
|
|
56
83
|
root.dataset.moldableDisplayMode =
|
|
57
84
|
window.parent === window ? 'window' : 'embedded';
|
|
85
|
+
root.dataset.moldableWindowTitlebar = 'host';
|
|
58
86
|
// Standalone windows do not reserve space for the desktop chat dock.
|
|
59
87
|
if (window.parent === window) {
|
|
60
88
|
applyMoldableChatSafePadding(0);
|
|
@@ -75,8 +103,7 @@ function installHostStateLifecycle() {
|
|
|
75
103
|
else if (event.data.type === 'moldable:host-context') {
|
|
76
104
|
const message = event.data;
|
|
77
105
|
if (message.version === 1 &&
|
|
78
|
-
(message.displayMode === 'embedded' ||
|
|
79
|
-
message.displayMode === 'window')) {
|
|
106
|
+
(message.displayMode === 'embedded' || message.displayMode === 'window')) {
|
|
80
107
|
applyMoldableHostContext(message);
|
|
81
108
|
}
|
|
82
109
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type HostServiceRequestOptions } from './host-services';
|
|
2
|
+
export type HostDateTimePickerMode = 'date' | 'time' | 'datetime';
|
|
3
|
+
export interface HostDateTimePickerAnchorRect {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
export interface HostDateTimePickerOptions extends HostServiceRequestOptions {
|
|
10
|
+
value?: string;
|
|
11
|
+
min?: string;
|
|
12
|
+
max?: string;
|
|
13
|
+
anchor?: Element | HostDateTimePickerAnchorRect;
|
|
14
|
+
}
|
|
15
|
+
export type HostDateTimePickerResult = {
|
|
16
|
+
status: 'completed';
|
|
17
|
+
method: 'native';
|
|
18
|
+
value: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: 'cancelled';
|
|
21
|
+
method: 'native';
|
|
22
|
+
} | {
|
|
23
|
+
status: 'fallback';
|
|
24
|
+
method: 'portable';
|
|
25
|
+
mode: HostDateTimePickerMode;
|
|
26
|
+
value?: string;
|
|
27
|
+
min?: string;
|
|
28
|
+
max?: string;
|
|
29
|
+
};
|
|
30
|
+
export interface HostDateTimePicker {
|
|
31
|
+
available: boolean;
|
|
32
|
+
pick: (mode: HostDateTimePickerMode, options?: HostDateTimePickerOptions) => Promise<HostDateTimePickerResult>;
|
|
33
|
+
}
|
|
34
|
+
export declare function pickDateTimeWithHost(mode: HostDateTimePickerMode, options?: HostDateTimePickerOptions): Promise<HostDateTimePickerResult>;
|
|
35
|
+
export declare function useHostDateTimePicker(): HostDateTimePicker;
|
|
36
|
+
//# sourceMappingURL=host-date-time-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-date-time-picker.d.ts","sourceRoot":"","sources":["../../src/lib/host-date-time-picker.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,iBAAiB,CAAA;AAMxB,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAA;AAEjE,MAAM,WAAW,4BAA4B;IAC3C,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,yBAA0B,SAAQ,yBAAyB;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,GAAG,4BAA4B,CAAA;CAChD;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,QAAQ,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,GACD;IACE,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,QAAQ,CAAA;CACjB,GACD;IACE,MAAM,EAAE,UAAU,CAAA;IAClB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,sBAAsB,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAEL,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,CACJ,IAAI,EAAE,sBAAsB,EAC5B,OAAO,CAAC,EAAE,yBAAyB,KAChC,OAAO,CAAC,wBAAwB,CAAC,CAAA;CACvC;AAgJD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,sBAAsB,EAC5B,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAgDnC;AAED,wBAAgB,qBAAqB,IAAI,kBAAkB,CAoB1D"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { HostServiceRequestError, isHostServiceAvailable, requestHostService, } from './host-services';
|
|
3
|
+
const HOST_CONTEXT_CHANGE_EVENT = 'moldable:host-context-change';
|
|
4
|
+
const DATE_TIME_PICKER_SERVICE_NAME = 'dateTimePicker';
|
|
5
|
+
const MAX_ISO_VALUE_LENGTH = 128;
|
|
6
|
+
function validateMode(mode) {
|
|
7
|
+
if (mode !== 'date' && mode !== 'time' && mode !== 'datetime') {
|
|
8
|
+
throw new TypeError('Date/time picker mode must be date, time, or datetime.');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isoValueNumber(mode, value) {
|
|
12
|
+
if (typeof value !== 'string' ||
|
|
13
|
+
value.length === 0 ||
|
|
14
|
+
value.length > MAX_ISO_VALUE_LENGTH) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const [datePart, timePart, extraPart] = mode === 'time' ? [undefined, value, undefined] : value.split('T');
|
|
18
|
+
if (extraPart !== undefined)
|
|
19
|
+
return null;
|
|
20
|
+
let date;
|
|
21
|
+
if (mode !== 'time') {
|
|
22
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datePart ?? '');
|
|
23
|
+
if (!match)
|
|
24
|
+
return null;
|
|
25
|
+
const year = Number(match[1]);
|
|
26
|
+
const month = Number(match[2]);
|
|
27
|
+
const day = Number(match[3]);
|
|
28
|
+
date = new Date(0);
|
|
29
|
+
date.setHours(0, 0, 0, 0);
|
|
30
|
+
date.setFullYear(year, month - 1, day);
|
|
31
|
+
if (date.getFullYear() !== year ||
|
|
32
|
+
date.getMonth() !== month - 1 ||
|
|
33
|
+
date.getDate() !== day) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (mode === 'date')
|
|
37
|
+
return date.getTime();
|
|
38
|
+
}
|
|
39
|
+
const match = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?$/.exec(timePart ?? '');
|
|
40
|
+
if (!match)
|
|
41
|
+
return null;
|
|
42
|
+
const hour = Number(match[1]);
|
|
43
|
+
const minute = Number(match[2]);
|
|
44
|
+
const second = Number(match[3] ?? 0);
|
|
45
|
+
const millisecond = Math.floor(Number(`0.${match[4] ?? 0}`) * 1000);
|
|
46
|
+
if (hour > 23 || minute > 59 || second > 59)
|
|
47
|
+
return null;
|
|
48
|
+
if (mode === 'time') {
|
|
49
|
+
return ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;
|
|
50
|
+
}
|
|
51
|
+
if (!date)
|
|
52
|
+
return null;
|
|
53
|
+
date.setHours(hour, minute, second, millisecond);
|
|
54
|
+
return date.getHours() === hour &&
|
|
55
|
+
date.getMinutes() === minute &&
|
|
56
|
+
date.getSeconds() === second
|
|
57
|
+
? date.getTime()
|
|
58
|
+
: null;
|
|
59
|
+
}
|
|
60
|
+
function validateIsoValue(mode, name, value) {
|
|
61
|
+
if (value !== undefined && isoValueNumber(mode, value) === null) {
|
|
62
|
+
throw new TypeError(`${name} must be a valid ISO ${mode} value.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateRange(mode, options) {
|
|
66
|
+
validateIsoValue(mode, 'value', options.value);
|
|
67
|
+
validateIsoValue(mode, 'min', options.min);
|
|
68
|
+
validateIsoValue(mode, 'max', options.max);
|
|
69
|
+
if (options.min !== undefined &&
|
|
70
|
+
options.max !== undefined &&
|
|
71
|
+
isoValueNumber(mode, options.min) >
|
|
72
|
+
isoValueNumber(mode, options.max)) {
|
|
73
|
+
throw new TypeError('Date/time picker min must not be after max.');
|
|
74
|
+
}
|
|
75
|
+
if (options.value !== undefined) {
|
|
76
|
+
const value = isoValueNumber(mode, options.value);
|
|
77
|
+
if ((options.min !== undefined &&
|
|
78
|
+
value < isoValueNumber(mode, options.min)) ||
|
|
79
|
+
(options.max !== undefined &&
|
|
80
|
+
value > isoValueNumber(mode, options.max))) {
|
|
81
|
+
throw new TypeError('Date/time picker value must be inside the min/max range.');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function anchorRect(anchor) {
|
|
86
|
+
if (!anchor)
|
|
87
|
+
return undefined;
|
|
88
|
+
const rect = typeof Element !== 'undefined' && anchor instanceof Element
|
|
89
|
+
? anchor.getBoundingClientRect()
|
|
90
|
+
: anchor;
|
|
91
|
+
if (!Number.isFinite(rect.x) ||
|
|
92
|
+
!Number.isFinite(rect.y) ||
|
|
93
|
+
!Number.isFinite(rect.width) ||
|
|
94
|
+
!Number.isFinite(rect.height) ||
|
|
95
|
+
rect.width < 0 ||
|
|
96
|
+
rect.height < 0) {
|
|
97
|
+
throw new TypeError('Date/time picker anchors require a finite, non-negative rect.');
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
x: rect.x,
|
|
101
|
+
y: rect.y,
|
|
102
|
+
width: rect.width,
|
|
103
|
+
height: rect.height,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function abortError() {
|
|
107
|
+
return new DOMException('The date/time picker request was aborted.', 'AbortError');
|
|
108
|
+
}
|
|
109
|
+
export async function pickDateTimeWithHost(mode, options = {}) {
|
|
110
|
+
validateMode(mode);
|
|
111
|
+
validateRange(mode, options);
|
|
112
|
+
if (options.signal?.aborted)
|
|
113
|
+
throw abortError();
|
|
114
|
+
if (!isHostServiceAvailable(DATE_TIME_PICKER_SERVICE_NAME)) {
|
|
115
|
+
return {
|
|
116
|
+
status: 'fallback',
|
|
117
|
+
method: 'portable',
|
|
118
|
+
mode,
|
|
119
|
+
...(options.value === undefined ? {} : { value: options.value }),
|
|
120
|
+
...(options.min === undefined ? {} : { min: options.min }),
|
|
121
|
+
...(options.max === undefined ? {} : { max: options.max }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const result = await requestHostService(DATE_TIME_PICKER_SERVICE_NAME, {
|
|
126
|
+
mode,
|
|
127
|
+
...(options.value === undefined ? {} : { value: options.value }),
|
|
128
|
+
...(options.min === undefined ? {} : { min: options.min }),
|
|
129
|
+
...(options.max === undefined ? {} : { max: options.max }),
|
|
130
|
+
...(options.anchor ? { anchor: anchorRect(options.anchor) } : {}),
|
|
131
|
+
}, {
|
|
132
|
+
signal: options.signal,
|
|
133
|
+
timeoutMs: options.timeoutMs,
|
|
134
|
+
});
|
|
135
|
+
if (typeof result !== 'string' || isoValueNumber(mode, result) === null) {
|
|
136
|
+
throw new Error('The native date/time picker returned an invalid result.');
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
status: 'completed',
|
|
140
|
+
method: 'native',
|
|
141
|
+
value: result,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error instanceof HostServiceRequestError &&
|
|
146
|
+
error.status === 'cancelled') {
|
|
147
|
+
return { status: 'cancelled', method: 'native' };
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function useHostDateTimePicker() {
|
|
153
|
+
const [available, setAvailable] = useState(() => isHostServiceAvailable(DATE_TIME_PICKER_SERVICE_NAME));
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const update = () => setAvailable(isHostServiceAvailable(DATE_TIME_PICKER_SERVICE_NAME));
|
|
156
|
+
update();
|
|
157
|
+
window.addEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
|
|
158
|
+
return () => window.removeEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
|
|
159
|
+
}, []);
|
|
160
|
+
const pick = useCallback((mode, options) => pickDateTimeWithHost(mode, options), []);
|
|
161
|
+
return { available, pick };
|
|
162
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type HostServiceRequestOptions } from './host-services';
|
|
2
|
+
export type HostFileDialogDefaultLocation = 'documents' | 'downloads' | 'desktop' | 'home' | 'appData';
|
|
3
|
+
export interface HostFileDialogFilter {
|
|
4
|
+
name: string;
|
|
5
|
+
extensions: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface HostOpenFileDialogProperties {
|
|
8
|
+
/**
|
|
9
|
+
* Allow more than one selection. Defaults to false.
|
|
10
|
+
*/
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Choose directories instead of files. Defaults to false.
|
|
14
|
+
*/
|
|
15
|
+
directories?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Show the New Folder control. Defaults to false.
|
|
18
|
+
*/
|
|
19
|
+
createDirectories?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Initially reveal hidden entries. Defaults to false.
|
|
22
|
+
*/
|
|
23
|
+
showHidden?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface HostFileDialogCommonOptions extends HostServiceRequestOptions {
|
|
26
|
+
title?: string;
|
|
27
|
+
buttonLabel?: string;
|
|
28
|
+
filters?: HostFileDialogFilter[];
|
|
29
|
+
defaultLocation?: HostFileDialogDefaultLocation;
|
|
30
|
+
}
|
|
31
|
+
export interface HostOpenFileDialogOptions extends HostFileDialogCommonOptions {
|
|
32
|
+
properties?: HostOpenFileDialogProperties;
|
|
33
|
+
}
|
|
34
|
+
export interface HostSaveFileDialogOptions extends HostFileDialogCommonOptions {
|
|
35
|
+
defaultName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Show the New Folder control. Defaults to false.
|
|
38
|
+
*/
|
|
39
|
+
createDirectories?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export type HostOpenFileDialogResult = {
|
|
42
|
+
status: 'completed';
|
|
43
|
+
paths: string[];
|
|
44
|
+
} | {
|
|
45
|
+
status: 'cancelled';
|
|
46
|
+
} | {
|
|
47
|
+
status: 'fallback';
|
|
48
|
+
};
|
|
49
|
+
export type HostSaveFileDialogResult = {
|
|
50
|
+
status: 'completed';
|
|
51
|
+
path: string;
|
|
52
|
+
} | {
|
|
53
|
+
status: 'cancelled';
|
|
54
|
+
} | {
|
|
55
|
+
status: 'fallback';
|
|
56
|
+
};
|
|
57
|
+
export interface HostFileDialog {
|
|
58
|
+
available: boolean;
|
|
59
|
+
open: (options?: HostOpenFileDialogOptions) => Promise<HostOpenFileDialogResult>;
|
|
60
|
+
save: (options?: HostSaveFileDialogOptions) => Promise<HostSaveFileDialogResult>;
|
|
61
|
+
}
|
|
62
|
+
export declare function openFileDialogWithHost(options?: HostOpenFileDialogOptions): Promise<HostOpenFileDialogResult>;
|
|
63
|
+
export declare function saveFileDialogWithHost(options?: HostSaveFileDialogOptions): Promise<HostSaveFileDialogResult>;
|
|
64
|
+
export declare function useHostFileDialog(): HostFileDialog;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=host-file-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-file-dialog.d.ts","sourceRoot":"","sources":["../../src/lib/host-file-dialog.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,yBAAyB,EAG/B,MAAM,iBAAiB,CAAA;AAWxB,MAAM,MAAM,6BAA6B,GACrC,WAAW,GACX,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,CAAA;AAEb,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,UAAU,2BAA4B,SAAQ,yBAAyB;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAA;IAChC,eAAe,CAAC,EAAE,6BAA6B,CAAA;CAChD;AAED,MAAM,WAAW,yBAA0B,SAAQ,2BAA2B;IAC5E,UAAU,CAAC,EAAE,4BAA4B,CAAA;CAC1C;AAED,MAAM,WAAW,yBAA0B,SAAQ,2BAA2B;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACxC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAA;AAE1B,MAAM,MAAM,wBAAwB,GAChC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,CAAA;AAE1B,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,IAAI,EAAE,CACJ,OAAO,CAAC,EAAE,yBAAyB,KAChC,OAAO,CAAC,wBAAwB,CAAC,CAAA;IACtC,IAAI,EAAE,CACJ,OAAO,CAAC,EAAE,yBAAyB,KAChC,OAAO,CAAC,wBAAwB,CAAC,CAAA;CACvC;AAmLD,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CA4CnC;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CA0CnC;AAED,wBAAgB,iBAAiB,IAAI,cAAc,CAuBlD"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { HostServiceRequestError, isHostServiceAvailable, requestHostService, } from './host-services';
|
|
3
|
+
const HOST_CONTEXT_CHANGE_EVENT = 'moldable:host-context-change';
|
|
4
|
+
const FILE_DIALOG_SERVICE_NAME = 'fileDialog';
|
|
5
|
+
const MAX_TEXT_LENGTH = 512;
|
|
6
|
+
const MAX_DEFAULT_NAME_LENGTH = 255;
|
|
7
|
+
const MAX_FILTERS = 32;
|
|
8
|
+
const MAX_FILTER_NAME_LENGTH = 128;
|
|
9
|
+
const MAX_EXTENSIONS_PER_FILTER = 64;
|
|
10
|
+
const MAX_EXTENSION_LENGTH = 64;
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function assertOnlyKeys(value, allowed, label) {
|
|
15
|
+
if (!Object.keys(value).every((key) => allowed.has(key))) {
|
|
16
|
+
throw new TypeError(`${label} contain an unknown property.`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function validateOptionalText(label, value) {
|
|
20
|
+
if (value !== undefined &&
|
|
21
|
+
(typeof value !== 'string' ||
|
|
22
|
+
value.trim().length === 0 ||
|
|
23
|
+
value.length > MAX_TEXT_LENGTH ||
|
|
24
|
+
value.includes('\0'))) {
|
|
25
|
+
throw new TypeError(`${label} must contain 1–${MAX_TEXT_LENGTH} characters.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function validateDefaultLocation(value) {
|
|
29
|
+
if (value !== undefined &&
|
|
30
|
+
value !== 'documents' &&
|
|
31
|
+
value !== 'downloads' &&
|
|
32
|
+
value !== 'desktop' &&
|
|
33
|
+
value !== 'home' &&
|
|
34
|
+
value !== 'appData') {
|
|
35
|
+
throw new TypeError('File-dialog defaultLocation is not supported.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function validateFilters(filters) {
|
|
39
|
+
if (filters === undefined)
|
|
40
|
+
return;
|
|
41
|
+
if (!Array.isArray(filters) || filters.length > MAX_FILTERS) {
|
|
42
|
+
throw new TypeError(`File dialogs support at most ${MAX_FILTERS} filters.`);
|
|
43
|
+
}
|
|
44
|
+
for (const filter of filters) {
|
|
45
|
+
if (!isRecord(filter)) {
|
|
46
|
+
throw new TypeError('File-dialog filters must be objects.');
|
|
47
|
+
}
|
|
48
|
+
assertOnlyKeys(filter, new Set(['name', 'extensions']), 'File-dialog filters');
|
|
49
|
+
if (typeof filter.name !== 'string' ||
|
|
50
|
+
filter.name.trim().length === 0 ||
|
|
51
|
+
filter.name.length > MAX_FILTER_NAME_LENGTH ||
|
|
52
|
+
filter.name.includes('\0') ||
|
|
53
|
+
!Array.isArray(filter.extensions) ||
|
|
54
|
+
filter.extensions.length === 0 ||
|
|
55
|
+
filter.extensions.length > MAX_EXTENSIONS_PER_FILTER) {
|
|
56
|
+
throw new TypeError('Each file-dialog filter requires a bounded name and extension list.');
|
|
57
|
+
}
|
|
58
|
+
if (!filter.extensions.every((extension) => typeof extension === 'string' &&
|
|
59
|
+
extension.length > 0 &&
|
|
60
|
+
extension.length <= MAX_EXTENSION_LENGTH &&
|
|
61
|
+
!extension.startsWith('.') &&
|
|
62
|
+
!/[\s/\\:*\0]/u.test(extension))) {
|
|
63
|
+
throw new TypeError('File-dialog extensions must omit dots, wildcards, whitespace, and path separators.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const COMMON_KEYS = [
|
|
68
|
+
'title',
|
|
69
|
+
'buttonLabel',
|
|
70
|
+
'filters',
|
|
71
|
+
'defaultLocation',
|
|
72
|
+
'signal',
|
|
73
|
+
'timeoutMs',
|
|
74
|
+
];
|
|
75
|
+
const OPEN_KEYS = new Set([...COMMON_KEYS, 'properties']);
|
|
76
|
+
const SAVE_KEYS = new Set([...COMMON_KEYS, 'defaultName', 'createDirectories']);
|
|
77
|
+
const OPEN_PROPERTY_KEYS = new Set([
|
|
78
|
+
'multiple',
|
|
79
|
+
'directories',
|
|
80
|
+
'createDirectories',
|
|
81
|
+
'showHidden',
|
|
82
|
+
]);
|
|
83
|
+
function validateCommonOptions(options, keys) {
|
|
84
|
+
assertOnlyKeys(options, keys, 'File-dialog options');
|
|
85
|
+
validateOptionalText('File-dialog title', options.title);
|
|
86
|
+
validateOptionalText('File-dialog buttonLabel', options.buttonLabel);
|
|
87
|
+
validateDefaultLocation(options.defaultLocation);
|
|
88
|
+
validateFilters(options.filters);
|
|
89
|
+
}
|
|
90
|
+
function validateOpenOptions(options) {
|
|
91
|
+
if (!isRecord(options)) {
|
|
92
|
+
throw new TypeError('Open file-dialog options must be an object.');
|
|
93
|
+
}
|
|
94
|
+
validateCommonOptions(options, OPEN_KEYS);
|
|
95
|
+
if (options.properties === undefined)
|
|
96
|
+
return;
|
|
97
|
+
if (!isRecord(options.properties)) {
|
|
98
|
+
throw new TypeError('Open file-dialog properties must be an object.');
|
|
99
|
+
}
|
|
100
|
+
assertOnlyKeys(options.properties, OPEN_PROPERTY_KEYS, 'Open file-dialog properties');
|
|
101
|
+
if (!Object.values(options.properties).every((value) => typeof value === 'boolean')) {
|
|
102
|
+
throw new TypeError('Open file-dialog properties must be booleans.');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateSaveOptions(options) {
|
|
106
|
+
if (!isRecord(options)) {
|
|
107
|
+
throw new TypeError('Save file-dialog options must be an object.');
|
|
108
|
+
}
|
|
109
|
+
validateCommonOptions(options, SAVE_KEYS);
|
|
110
|
+
if (options.defaultName !== undefined &&
|
|
111
|
+
(typeof options.defaultName !== 'string' ||
|
|
112
|
+
options.defaultName.trim().length === 0 ||
|
|
113
|
+
options.defaultName.length > MAX_DEFAULT_NAME_LENGTH ||
|
|
114
|
+
/[/\\\0]/u.test(options.defaultName))) {
|
|
115
|
+
throw new TypeError(`Save-dialog defaultName must be a filename of 1–${MAX_DEFAULT_NAME_LENGTH} characters.`);
|
|
116
|
+
}
|
|
117
|
+
if (options.createDirectories !== undefined &&
|
|
118
|
+
typeof options.createDirectories !== 'boolean') {
|
|
119
|
+
throw new TypeError('Save-dialog createDirectories must be a boolean.');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function requestOptions(options) {
|
|
123
|
+
return {
|
|
124
|
+
signal: options.signal,
|
|
125
|
+
timeoutMs: options.timeoutMs,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function abortError() {
|
|
129
|
+
return new DOMException('The file-dialog request was aborted.', 'AbortError');
|
|
130
|
+
}
|
|
131
|
+
function isPath(value) {
|
|
132
|
+
return typeof value === 'string' && value.length > 0;
|
|
133
|
+
}
|
|
134
|
+
export async function openFileDialogWithHost(options = {}) {
|
|
135
|
+
validateOpenOptions(options);
|
|
136
|
+
if (options.signal?.aborted)
|
|
137
|
+
throw abortError();
|
|
138
|
+
if (!isHostServiceAvailable(FILE_DIALOG_SERVICE_NAME)) {
|
|
139
|
+
return { status: 'fallback' };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const result = await requestHostService(FILE_DIALOG_SERVICE_NAME, {
|
|
143
|
+
mode: 'open',
|
|
144
|
+
...(options.title === undefined ? {} : { title: options.title }),
|
|
145
|
+
...(options.buttonLabel === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { buttonLabel: options.buttonLabel }),
|
|
148
|
+
...(options.filters === undefined ? {} : { filters: options.filters }),
|
|
149
|
+
...(options.defaultLocation === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { defaultLocation: options.defaultLocation }),
|
|
152
|
+
...(options.properties === undefined
|
|
153
|
+
? {}
|
|
154
|
+
: { properties: options.properties }),
|
|
155
|
+
}, requestOptions(options));
|
|
156
|
+
if (!isRecord(result) ||
|
|
157
|
+
!Array.isArray(result.paths) ||
|
|
158
|
+
result.paths.length === 0 ||
|
|
159
|
+
!result.paths.every(isPath)) {
|
|
160
|
+
throw new Error('The native open dialog returned an invalid result.');
|
|
161
|
+
}
|
|
162
|
+
return { status: 'completed', paths: result.paths };
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof HostServiceRequestError &&
|
|
166
|
+
error.status === 'cancelled') {
|
|
167
|
+
return { status: 'cancelled' };
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export async function saveFileDialogWithHost(options = {}) {
|
|
173
|
+
validateSaveOptions(options);
|
|
174
|
+
if (options.signal?.aborted)
|
|
175
|
+
throw abortError();
|
|
176
|
+
if (!isHostServiceAvailable(FILE_DIALOG_SERVICE_NAME)) {
|
|
177
|
+
return { status: 'fallback' };
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const result = await requestHostService(FILE_DIALOG_SERVICE_NAME, {
|
|
181
|
+
mode: 'save',
|
|
182
|
+
...(options.title === undefined ? {} : { title: options.title }),
|
|
183
|
+
...(options.buttonLabel === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { buttonLabel: options.buttonLabel }),
|
|
186
|
+
...(options.filters === undefined ? {} : { filters: options.filters }),
|
|
187
|
+
...(options.defaultLocation === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { defaultLocation: options.defaultLocation }),
|
|
190
|
+
...(options.defaultName === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { defaultName: options.defaultName }),
|
|
193
|
+
...(options.createDirectories === undefined
|
|
194
|
+
? {}
|
|
195
|
+
: { createDirectories: options.createDirectories }),
|
|
196
|
+
}, requestOptions(options));
|
|
197
|
+
if (!isRecord(result) || !isPath(result.path)) {
|
|
198
|
+
throw new Error('The native save dialog returned an invalid result.');
|
|
199
|
+
}
|
|
200
|
+
return { status: 'completed', path: result.path };
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (error instanceof HostServiceRequestError &&
|
|
204
|
+
error.status === 'cancelled') {
|
|
205
|
+
return { status: 'cancelled' };
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export function useHostFileDialog() {
|
|
211
|
+
const [available, setAvailable] = useState(() => isHostServiceAvailable(FILE_DIALOG_SERVICE_NAME));
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const update = () => setAvailable(isHostServiceAvailable(FILE_DIALOG_SERVICE_NAME));
|
|
214
|
+
update();
|
|
215
|
+
window.addEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
|
|
216
|
+
return () => window.removeEventListener(HOST_CONTEXT_CHANGE_EVENT, update);
|
|
217
|
+
}, []);
|
|
218
|
+
const open = useCallback((options) => openFileDialogWithHost(options), []);
|
|
219
|
+
const save = useCallback((options) => saveFileDialogWithHost(options), []);
|
|
220
|
+
return { available, open, save };
|
|
221
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type HostServiceRequestOptions } from './host-services';
|
|
2
|
+
export declare const HOST_NATIVE_MENU_MAX_DEPTH = 3;
|
|
3
|
+
export declare const HOST_NATIVE_MENU_MAX_ITEMS = 200;
|
|
4
|
+
export declare const HOST_NATIVE_MENU_MAX_TITLE_LENGTH = 256;
|
|
5
|
+
export declare const HOST_NATIVE_MENU_MAX_SUBLABEL_LENGTH = 512;
|
|
6
|
+
export type NativeMenuItemKind = 'normal' | 'separator' | 'checkbox' | 'radio';
|
|
7
|
+
export interface NativeMenuShortcut {
|
|
8
|
+
key: string;
|
|
9
|
+
modifiers?: Array<'cmd' | 'shift' | 'alt' | 'ctrl'>;
|
|
10
|
+
}
|
|
11
|
+
interface NativeMenuItemBase {
|
|
12
|
+
sublabel?: string;
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
shortcut?: NativeMenuShortcut;
|
|
15
|
+
/**
|
|
16
|
+
* Reserved for a future native destructive appearance. It is validated and
|
|
17
|
+
* carried through the v1 service but intentionally not rendered by AppKit.
|
|
18
|
+
*/
|
|
19
|
+
destructive?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type NativeMenuItem = (NativeMenuItemBase & {
|
|
22
|
+
id: string;
|
|
23
|
+
kind?: 'normal';
|
|
24
|
+
title: string;
|
|
25
|
+
checked?: never;
|
|
26
|
+
submenu?: NativeMenuItem[];
|
|
27
|
+
}) | (NativeMenuItemBase & {
|
|
28
|
+
id?: string;
|
|
29
|
+
kind: 'separator';
|
|
30
|
+
title?: string;
|
|
31
|
+
checked?: never;
|
|
32
|
+
submenu?: never;
|
|
33
|
+
}) | (NativeMenuItemBase & {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: 'checkbox';
|
|
36
|
+
title: string;
|
|
37
|
+
checked?: boolean | 'mixed';
|
|
38
|
+
submenu?: never;
|
|
39
|
+
}) | (NativeMenuItemBase & {
|
|
40
|
+
id: string;
|
|
41
|
+
kind: 'radio';
|
|
42
|
+
title: string;
|
|
43
|
+
checked?: boolean;
|
|
44
|
+
submenu?: never;
|
|
45
|
+
});
|
|
46
|
+
export interface HostNativeMenuAnchorRect {
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
}
|
|
52
|
+
export interface HostNativeMenuPoint {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
}
|
|
56
|
+
export interface HostNativeMenuOptions extends HostServiceRequestOptions {
|
|
57
|
+
anchor?: Element | HostNativeMenuAnchorRect;
|
|
58
|
+
position?: HostNativeMenuPoint;
|
|
59
|
+
}
|
|
60
|
+
export type HostNativeMenuResult = {
|
|
61
|
+
status: 'completed';
|
|
62
|
+
selectedId: string;
|
|
63
|
+
} | {
|
|
64
|
+
status: 'cancelled';
|
|
65
|
+
} | {
|
|
66
|
+
status: 'fallback';
|
|
67
|
+
};
|
|
68
|
+
export interface HostNativeMenu {
|
|
69
|
+
available: boolean;
|
|
70
|
+
open: (items: NativeMenuItem[], options: HostNativeMenuOptions) => Promise<HostNativeMenuResult>;
|
|
71
|
+
}
|
|
72
|
+
export declare function openNativeMenuWithHost(items: NativeMenuItem[], options: HostNativeMenuOptions): Promise<HostNativeMenuResult>;
|
|
73
|
+
export declare function useHostNativeMenu(): HostNativeMenu;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=host-native-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-native-menu.d.ts","sourceRoot":"","sources":["../../src/lib/host-native-menu.ts"],"names":[],"mappings":"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"}
|