@wildo-ai/presets-components 1.1.3 → 1.1.4
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/esm/components/ai-elements/decision/approval.d.ts +24 -4
- package/dist/esm/components/ai-elements/decision/approval.d.ts.map +1 -1
- package/dist/esm/components/ai-elements/decision/approval.js +35 -7
- package/dist/esm/components/ai-elements/decision/approval.js.map +1 -1
- package/dist/esm/components/app/public-layout-main-container.d.ts.map +1 -1
- package/dist/esm/components/app/public-layout-main-container.js +1 -1
- package/dist/esm/components/app/public-layout-main-container.js.map +1 -1
- package/dist/esm/components/feedback/empty-state.d.ts.map +1 -1
- package/dist/esm/components/feedback/empty-state.js +11 -1
- package/dist/esm/components/feedback/empty-state.js.map +1 -1
- package/dist/esm/components/feedback/notification-center.js +1 -1
- package/dist/esm/components/feedback/notification-center.js.map +1 -1
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.d.ts +52 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.d.ts.map +1 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.js +157 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.js.map +1 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.lazy.d.ts +16 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.lazy.d.ts.map +1 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.lazy.js +20 -0
- package/dist/esm/components/form/controls/file/providers/google-drive-picker.lazy.js.map +1 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.d.ts +37 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.d.ts.map +1 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.js +125 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.js.map +1 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.lazy.d.ts +16 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.lazy.d.ts.map +1 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.lazy.js +20 -0
- package/dist/esm/components/form/controls/file/providers/onedrive-picker.lazy.js.map +1 -0
- package/dist/esm/components/navigation/app-level/utility-panel.d.ts.map +1 -1
- package/dist/esm/components/navigation/app-level/utility-panel.js +9 -2
- package/dist/esm/components/navigation/app-level/utility-panel.js.map +1 -1
- package/dist/esm/components/sections/tabs.d.ts.map +1 -1
- package/dist/esm/components/sections/tabs.js +5 -1
- package/dist/esm/components/sections/tabs.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/registration/register-default-presets.d.ts.map +1 -1
- package/dist/esm/registration/register-default-presets.js +4 -0
- package/dist/esm/registration/register-default-presets.js.map +1 -1
- package/dist/esm/registration/register-provider-components.d.ts +31 -0
- package/dist/esm/registration/register-provider-components.d.ts.map +1 -0
- package/dist/esm/registration/register-provider-components.js +45 -0
- package/dist/esm/registration/register-provider-components.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive-picker.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/google-drive-picker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,OAAO,EAEL,KAAK,uBAAuB,EAE7B,MAAM,sCAAsC,CAAC;AAsD9C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAuGtE"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Drive file picker — the component the `google-drive` provider contributes for
|
|
3
|
+
* `FRONTEND_FILE_PICKER`.
|
|
4
|
+
*
|
|
5
|
+
* ## Why it declares REMOTE_REFERENCE
|
|
6
|
+
*
|
|
7
|
+
* Drive's Picker hands back file METADATA — an id, a name, a mime type — never
|
|
8
|
+
* the bytes. Fetching the bytes is a separate authenticated call to the Drive
|
|
9
|
+
* API, and in a Wildo application the credential that call needs belongs to the
|
|
10
|
+
* backend. So a selection here is an opaque handle the application's executor
|
|
11
|
+
* door resolves, which is exactly `REMOTE_REFERENCE`.
|
|
12
|
+
*
|
|
13
|
+
* Its sibling `onedrive-picker.tsx` declares the OTHER arm,
|
|
14
|
+
* and the contrast is why issue #383 was answered per-vendor rather than
|
|
15
|
+
* globally: two real pickers for one capability genuinely return different
|
|
16
|
+
* things, and a framework insisting on one contract would have had to exclude
|
|
17
|
+
* one of them.
|
|
18
|
+
*
|
|
19
|
+
* ## Why it lives in presets-components, behind a lazy boundary
|
|
20
|
+
*
|
|
21
|
+
* The engine's provider MODULES live in `@wildo-ai/external-connectors-public`,
|
|
22
|
+
* which ships into three browser bundles behind a one-entry allow-list and
|
|
23
|
+
* deliberately has no renderer. A component cannot live there, and that
|
|
24
|
+
* package cannot name a loader that reaches this one either — it would close a
|
|
25
|
+
* package cycle. So the module DECLARES the picker (its delivery, its keys) and
|
|
26
|
+
* this library REGISTERS the loader beside its presets (`register-provider-components.ts`),
|
|
27
|
+
* keyed by capability and provider ref. `google-drive-picker.lazy.tsx` is the
|
|
28
|
+
* only door onto this file, and `check:lazy-boundaries` keeps it that way.
|
|
29
|
+
*
|
|
30
|
+
* This component has no vendor dependency: the Picker arrives as a `<script>`
|
|
31
|
+
* global the provider module declares. A per-vendor package was built for it
|
|
32
|
+
* first and retired for exactly that reason (#394).
|
|
33
|
+
*
|
|
34
|
+
* ## Not proven in a browser
|
|
35
|
+
*
|
|
36
|
+
* Nothing here has been driven against a real Drive account: the Picker
|
|
37
|
+
* authorizes as the SIGNED-IN USER, so it needs a user-consented OAuth token,
|
|
38
|
+
* which is the account-connection surface (#333) and does not exist yet. Without
|
|
39
|
+
* one this picker reports through `onError` and opens nothing — deliberately,
|
|
40
|
+
* rather than reading a token from `publicConfig`, which is served to every
|
|
41
|
+
* browser. Tracked with the rest of the frontend provider surface in #359.
|
|
42
|
+
*/
|
|
43
|
+
import { useEffect, useRef } from 'react';
|
|
44
|
+
import { ProviderFrontendSelectionDelivery, } from '@wildo-ai/saas-models/public-runtime';
|
|
45
|
+
function readString(value) {
|
|
46
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
function readNumber(value) {
|
|
49
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
50
|
+
return value;
|
|
51
|
+
// Drive reports `sizeBytes` as a decimal STRING for large files, so this is a
|
|
52
|
+
// real case rather than defensiveness. A bare `Number()` would turn an empty
|
|
53
|
+
// string into 0 and claim the file is empty.
|
|
54
|
+
const parsed = typeof value === 'string' && value.trim() !== '' ? Number(value) : Number.NaN;
|
|
55
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Opens the Drive picker as soon as it mounts.
|
|
59
|
+
*
|
|
60
|
+
* Mounting IS opening: the framework host mounts a picker only once a user has
|
|
61
|
+
* asked for one, so an extra button inside the component would be a second click
|
|
62
|
+
* for the same intent.
|
|
63
|
+
*/
|
|
64
|
+
export function GoogleDrivePicker(props) {
|
|
65
|
+
// A picker must open exactly once per mount. Without this, any re-render
|
|
66
|
+
// caused by a parent — a resize, a sibling's state — would open a second Drive
|
|
67
|
+
// dialog over the first, and dismissing one would leave the other live.
|
|
68
|
+
const openedRef = useRef(false);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (openedRef.current)
|
|
71
|
+
return;
|
|
72
|
+
openedRef.current = true;
|
|
73
|
+
let cancelled = false;
|
|
74
|
+
const fail = (message) => {
|
|
75
|
+
if (!cancelled)
|
|
76
|
+
props.onError(new Error(message));
|
|
77
|
+
};
|
|
78
|
+
void (async () => {
|
|
79
|
+
const globals = window;
|
|
80
|
+
if (globals.gapi === undefined) {
|
|
81
|
+
fail('The Google API script has not loaded, so the Drive picker cannot open. The `google-drive` provider '
|
|
82
|
+
+ 'declares it in `scripts`; check that the surface injects provider scripts and that the composed '
|
|
83
|
+
+ 'Content-Security-Policy admits https://apis.google.com.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const token = await props.getAccessToken?.();
|
|
87
|
+
if (token === undefined) {
|
|
88
|
+
fail('No Google access token is available for this user, so the Drive picker cannot open. The Picker '
|
|
89
|
+
+ "authorizes as the SIGNED-IN USER, so the application must connect the user's Google account "
|
|
90
|
+
+ 'first. A token must never be read from `publicConfig`, which is served to every browser.');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const appId = readString(props.publicConfig.appId);
|
|
94
|
+
if (appId === undefined) {
|
|
95
|
+
fail("The `google-drive` provider's publicConfig declares no `appId`, which the Drive picker requires.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
globals.gapi.load('picker', () => {
|
|
99
|
+
const picker = window.google?.picker;
|
|
100
|
+
if (picker === undefined) {
|
|
101
|
+
fail('The Google Picker module loaded but installed no `google.picker` global.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const builder = new picker.PickerBuilder()
|
|
105
|
+
.setOAuthToken(token)
|
|
106
|
+
.setAppId(appId)
|
|
107
|
+
.addView(new picker.DocsView(picker.ViewId.DOCS))
|
|
108
|
+
.setCallback((data) => {
|
|
109
|
+
if (cancelled)
|
|
110
|
+
return;
|
|
111
|
+
if (data.action === picker.Action.CANCEL) {
|
|
112
|
+
props.onCancel();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (data.action !== picker.Action.PICKED)
|
|
116
|
+
return;
|
|
117
|
+
const selections = [];
|
|
118
|
+
for (const document of data.docs ?? []) {
|
|
119
|
+
const externalId = readString(document.id);
|
|
120
|
+
// A document with no id is one the backend could never resolve.
|
|
121
|
+
// Reporting beats handing up a selection whose reference is empty,
|
|
122
|
+
// which would fail later and somewhere else.
|
|
123
|
+
if (externalId === undefined) {
|
|
124
|
+
fail('Google Picker returned a selected document with no id, which cannot be resolved.');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
selections.push({
|
|
128
|
+
kind: ProviderFrontendSelectionDelivery.REMOTE_REFERENCE,
|
|
129
|
+
externalId,
|
|
130
|
+
displayName: readString(document.name),
|
|
131
|
+
mimeType: readString(document.mimeType),
|
|
132
|
+
sizeBytes: readNumber(document.sizeBytes),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
props.onSelect(selections);
|
|
136
|
+
});
|
|
137
|
+
if (props.constraints.maxFiles !== undefined && props.constraints.maxFiles > 1) {
|
|
138
|
+
builder.enableFeature('multiselectEnabled');
|
|
139
|
+
}
|
|
140
|
+
const developerKey = readString(props.publicConfig.developerKey);
|
|
141
|
+
if (developerKey !== undefined)
|
|
142
|
+
builder.setDeveloperKey(developerKey);
|
|
143
|
+
builder.build().setVisible(true);
|
|
144
|
+
});
|
|
145
|
+
})();
|
|
146
|
+
return () => {
|
|
147
|
+
cancelled = true;
|
|
148
|
+
};
|
|
149
|
+
// Mount-only: re-running this opens a second dialog, and every prop it reads
|
|
150
|
+
// is fixed for the life of one picker.
|
|
151
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
+
}, []);
|
|
153
|
+
// The picker is a vendor-owned overlay. This component renders nothing of its
|
|
154
|
+
// own and must not put a placeholder behind it.
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=google-drive-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive-picker.js","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/google-drive-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n * Google Drive file picker — the component the `google-drive` provider contributes for\n * `FRONTEND_FILE_PICKER`.\n *\n * ## Why it declares REMOTE_REFERENCE\n *\n * Drive's Picker hands back file METADATA — an id, a name, a mime type — never\n * the bytes. Fetching the bytes is a separate authenticated call to the Drive\n * API, and in a Wildo application the credential that call needs belongs to the\n * backend. So a selection here is an opaque handle the application's executor\n * door resolves, which is exactly `REMOTE_REFERENCE`.\n *\n * Its sibling `onedrive-picker.tsx` declares the OTHER arm,\n * and the contrast is why issue #383 was answered per-vendor rather than\n * globally: two real pickers for one capability genuinely return different\n * things, and a framework insisting on one contract would have had to exclude\n * one of them.\n *\n * ## Why it lives in presets-components, behind a lazy boundary\n *\n * The engine's provider MODULES live in `@wildo-ai/external-connectors-public`,\n * which ships into three browser bundles behind a one-entry allow-list and\n * deliberately has no renderer. A component cannot live there, and that\n * package cannot name a loader that reaches this one either — it would close a\n * package cycle. So the module DECLARES the picker (its delivery, its keys) and\n * this library REGISTERS the loader beside its presets (`register-provider-components.ts`),\n * keyed by capability and provider ref. `google-drive-picker.lazy.tsx` is the\n * only door onto this file, and `check:lazy-boundaries` keeps it that way.\n *\n * This component has no vendor dependency: the Picker arrives as a `<script>`\n * global the provider module declares. A per-vendor package was built for it\n * first and retired for exactly that reason (#394).\n *\n * ## Not proven in a browser\n *\n * Nothing here has been driven against a real Drive account: the Picker\n * authorizes as the SIGNED-IN USER, so it needs a user-consented OAuth token,\n * which is the account-connection surface (#333) and does not exist yet. Without\n * one this picker reports through `onError` and opens nothing — deliberately,\n * rather than reading a token from `publicConfig`, which is served to every\n * browser. Tracked with the rest of the frontend provider surface in #359.\n */\n\nimport { useEffect, useRef } from 'react';\nimport {\n ProviderFrontendSelectionDelivery,\n type ProviderFilePickerProps,\n type ProviderFileSelection,\n} from '@wildo-ai/saas-models/public-runtime';\n\n/** The slice of Drive's picker metadata this component reads. */\ninterface GooglePickerDocument {\n readonly id?: unknown;\n readonly name?: unknown;\n readonly mimeType?: unknown;\n readonly sizeBytes?: unknown;\n}\n\ninterface GooglePickerBuilder {\n setOAuthToken(token: string): GooglePickerBuilder;\n setAppId(appId: string): GooglePickerBuilder;\n setDeveloperKey(key: string): GooglePickerBuilder;\n addView(view: object): GooglePickerBuilder;\n enableFeature(feature: string): GooglePickerBuilder;\n setCallback(\n callback: (data: { action?: unknown; docs?: readonly GooglePickerDocument[] }) => void,\n ): GooglePickerBuilder;\n build(): { setVisible(visible: boolean): void };\n}\n\n/**\n * The `gapi` / `google.picker` globals the vendor's `<script>` installs.\n *\n * Typed structurally and locally rather than through `@types/gapi`: the surface\n * used here is four calls wide, and a `@types` package for a global that may not\n * be present is a dependency bought for a shape this file already states.\n */\ninterface GooglePickerGlobals {\n readonly gapi?: { load(name: string, callback: () => void): void };\n readonly google?: {\n readonly picker?: {\n readonly Action: { readonly PICKED: string; readonly CANCEL: string };\n readonly ViewId: { readonly DOCS: string };\n readonly PickerBuilder: new () => GooglePickerBuilder;\n readonly DocsView: new (viewId: string) => object;\n };\n };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction readNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n // Drive reports `sizeBytes` as a decimal STRING for large files, so this is a\n // real case rather than defensiveness. A bare `Number()` would turn an empty\n // string into 0 and claim the file is empty.\n const parsed = typeof value === 'string' && value.trim() !== '' ? Number(value) : Number.NaN;\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/**\n * Opens the Drive picker as soon as it mounts.\n *\n * Mounting IS opening: the framework host mounts a picker only once a user has\n * asked for one, so an extra button inside the component would be a second click\n * for the same intent.\n */\nexport function GoogleDrivePicker(props: ProviderFilePickerProps): null {\n // A picker must open exactly once per mount. Without this, any re-render\n // caused by a parent — a resize, a sibling's state — would open a second Drive\n // dialog over the first, and dismissing one would leave the other live.\n const openedRef = useRef(false);\n\n useEffect(() => {\n if (openedRef.current) return;\n openedRef.current = true;\n\n let cancelled = false;\n const fail = (message: string): void => {\n if (!cancelled) props.onError(new Error(message));\n };\n\n void (async () => {\n const globals = window as unknown as GooglePickerGlobals;\n if (globals.gapi === undefined) {\n fail(\n 'The Google API script has not loaded, so the Drive picker cannot open. The `google-drive` provider '\n + 'declares it in `scripts`; check that the surface injects provider scripts and that the composed '\n + 'Content-Security-Policy admits https://apis.google.com.',\n );\n return;\n }\n\n const token = await props.getAccessToken?.();\n if (token === undefined) {\n fail(\n 'No Google access token is available for this user, so the Drive picker cannot open. The Picker '\n + \"authorizes as the SIGNED-IN USER, so the application must connect the user's Google account \"\n + 'first. A token must never be read from `publicConfig`, which is served to every browser.',\n );\n return;\n }\n\n const appId = readString(props.publicConfig.appId);\n if (appId === undefined) {\n fail(\"The `google-drive` provider's publicConfig declares no `appId`, which the Drive picker requires.\");\n return;\n }\n\n globals.gapi.load('picker', () => {\n const picker = (window as unknown as GooglePickerGlobals).google?.picker;\n if (picker === undefined) {\n fail('The Google Picker module loaded but installed no `google.picker` global.');\n return;\n }\n\n const builder = new picker.PickerBuilder()\n .setOAuthToken(token)\n .setAppId(appId)\n .addView(new picker.DocsView(picker.ViewId.DOCS))\n .setCallback((data) => {\n if (cancelled) return;\n if (data.action === picker.Action.CANCEL) {\n props.onCancel();\n return;\n }\n if (data.action !== picker.Action.PICKED) return;\n\n const selections: ProviderFileSelection[] = [];\n for (const document of data.docs ?? []) {\n const externalId = readString(document.id);\n // A document with no id is one the backend could never resolve.\n // Reporting beats handing up a selection whose reference is empty,\n // which would fail later and somewhere else.\n if (externalId === undefined) {\n fail('Google Picker returned a selected document with no id, which cannot be resolved.');\n return;\n }\n selections.push({\n kind: ProviderFrontendSelectionDelivery.REMOTE_REFERENCE,\n externalId,\n displayName: readString(document.name),\n mimeType: readString(document.mimeType),\n sizeBytes: readNumber(document.sizeBytes),\n });\n }\n props.onSelect(selections);\n });\n\n if (props.constraints.maxFiles !== undefined && props.constraints.maxFiles > 1) {\n builder.enableFeature('multiselectEnabled');\n }\n const developerKey = readString(props.publicConfig.developerKey);\n if (developerKey !== undefined) builder.setDeveloperKey(developerKey);\n\n builder.build().setVisible(true);\n });\n })();\n\n return () => {\n cancelled = true;\n };\n // Mount-only: re-running this opens a second dialog, and every prop it reads\n // is fixed for the life of one picker.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // The picker is a vendor-owned overlay. This component renders nothing of its\n // own and must not put a placeholder behind it.\n return null;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoogleDrivePickerLazy — the ONLY public door onto the GoogleDrivePicker module.
|
|
3
|
+
*
|
|
4
|
+
* Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the
|
|
5
|
+
* `React.lazy(() => import(...))` split point so the picker's code never lands in the
|
|
6
|
+
* application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of
|
|
7
|
+
* the wrapped module from anywhere else. A provider picker is opened by a user who asked
|
|
8
|
+
* for one; every other visitor pays nothing for it.
|
|
9
|
+
*
|
|
10
|
+
* No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a
|
|
11
|
+
* vendor overlay), so reserving layout would draw a box where there will never be one.
|
|
12
|
+
*/
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import type { ProviderFilePickerProps } from "@wildo-ai/saas-models/public-runtime";
|
|
15
|
+
export declare function GoogleDrivePickerLazy(props: ProviderFilePickerProps): React.JSX.Element;
|
|
16
|
+
//# sourceMappingURL=google-drive-picker.lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive-picker.lazy.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/google-drive-picker.lazy.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAInF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,uBAAuB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAMvF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* GoogleDrivePickerLazy — the ONLY public door onto the GoogleDrivePicker module.
|
|
5
|
+
*
|
|
6
|
+
* Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the
|
|
7
|
+
* `React.lazy(() => import(...))` split point so the picker's code never lands in the
|
|
8
|
+
* application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of
|
|
9
|
+
* the wrapped module from anywhere else. A provider picker is opened by a user who asked
|
|
10
|
+
* for one; every other visitor pays nothing for it.
|
|
11
|
+
*
|
|
12
|
+
* No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a
|
|
13
|
+
* vendor overlay), so reserving layout would draw a box where there will never be one.
|
|
14
|
+
*/
|
|
15
|
+
import * as React from "react";
|
|
16
|
+
const LazyGoogleDrivePicker = React.lazy(async () => ({ default: (await import("./google-drive-picker.js")).GoogleDrivePicker }));
|
|
17
|
+
export function GoogleDrivePickerLazy(props) {
|
|
18
|
+
return (_jsx(React.Suspense, { fallback: null, children: _jsx(LazyGoogleDrivePicker, { ...props }) }));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=google-drive-picker.lazy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive-picker.lazy.js","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/google-drive-picker.lazy.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,qBAAqB,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAA;AAE9H,MAAM,UAAU,qBAAqB,CAAC,KAA8B;IAClE,OAAO,CACL,KAAC,KAAK,CAAC,QAAQ,IAAC,QAAQ,EAAE,IAAI,YAC5B,KAAC,qBAAqB,OAAK,KAAK,GAAI,GACrB,CAClB,CAAA;AACH,CAAC","sourcesContent":["\"use client\"\n\n/**\n * GoogleDrivePickerLazy — the ONLY public door onto the GoogleDrivePicker module.\n *\n * Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the\n * `React.lazy(() => import(...))` split point so the picker's code never lands in the\n * application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of\n * the wrapped module from anywhere else. A provider picker is opened by a user who asked\n * for one; every other visitor pays nothing for it.\n *\n * No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a\n * vendor overlay), so reserving layout would draw a box where there will never be one.\n */\n\nimport * as React from \"react\"\nimport type { ProviderFilePickerProps } from \"@wildo-ai/saas-models/public-runtime\"\n\nconst LazyGoogleDrivePicker = React.lazy(async () => ({ default: (await import(\"./google-drive-picker\")).GoogleDrivePicker }))\n\nexport function GoogleDrivePickerLazy(props: ProviderFilePickerProps): React.JSX.Element {\n return (\n <React.Suspense fallback={null}>\n <LazyGoogleDrivePicker {...props} />\n </React.Suspense>\n )\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneDrive file picker — the component the `microsoft-onedrive` provider contributes for
|
|
3
|
+
* `FRONTEND_FILE_PICKER`.
|
|
4
|
+
*
|
|
5
|
+
* ## Why it declares BROWSER_READABLE_URL, where its Google sibling declares REMOTE_REFERENCE
|
|
6
|
+
*
|
|
7
|
+
* This is the pair that shows why issue #383 was answered PER VENDOR.
|
|
8
|
+
*
|
|
9
|
+
* OneDrive's picker, opened with the `download` action, returns each chosen file
|
|
10
|
+
* carrying a pre-authenticated download URL — a short-lived link that needs no
|
|
11
|
+
* credential to fetch. The browser genuinely can read the bytes, so the honest
|
|
12
|
+
* declaration is `BROWSER_READABLE_URL` and the framework uploads the result the
|
|
13
|
+
* way it uploads any other file.
|
|
14
|
+
*
|
|
15
|
+
* spec-cite: Microsoft Graph `driveItem` reference —
|
|
16
|
+
* https://learn.microsoft.com/en-us/graph/api/resources/driveitem — for the
|
|
17
|
+
* pre-authenticated `@microsoft.graph.downloadUrl` property. Paraphrased rather
|
|
18
|
+
* than quoted; the reading that matters here is that the URL carries its own
|
|
19
|
+
* authorization and expires, which is what makes the browser-readable arm
|
|
20
|
+
* available at all.
|
|
21
|
+
*
|
|
22
|
+
* Drive's picker returns metadata only, so its bytes need a credential the
|
|
23
|
+
* browser must not hold. Two real vendors, two different contracts, and a
|
|
24
|
+
* framework that insisted on one would have had to drop one of them.
|
|
25
|
+
*
|
|
26
|
+
* ## Not proven in a browser
|
|
27
|
+
*
|
|
28
|
+
* Nothing here has been driven against a real OneDrive account. Unlike the Drive
|
|
29
|
+
* picker this one authenticates through the vendor's own popup rather than
|
|
30
|
+
* needing a token from the application, so it does not depend on #333 — but it
|
|
31
|
+
* still needs a real `clientId` and a real user. Tracked with the rest of the
|
|
32
|
+
* frontend provider surface in #359.
|
|
33
|
+
*/
|
|
34
|
+
import { type ProviderFilePickerProps } from '@wildo-ai/saas-models/public-runtime';
|
|
35
|
+
/** Opens the OneDrive picker as soon as it mounts — see the Drive picker for why mounting IS opening. */
|
|
36
|
+
export declare function OneDrivePicker(props: ProviderFilePickerProps): null;
|
|
37
|
+
//# sourceMappingURL=onedrive-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onedrive-picker.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/onedrive-picker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,EAEL,KAAK,uBAAuB,EAE7B,MAAM,sCAAsC,CAAC;AAwC9C,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAyFnE"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneDrive file picker — the component the `microsoft-onedrive` provider contributes for
|
|
3
|
+
* `FRONTEND_FILE_PICKER`.
|
|
4
|
+
*
|
|
5
|
+
* ## Why it declares BROWSER_READABLE_URL, where its Google sibling declares REMOTE_REFERENCE
|
|
6
|
+
*
|
|
7
|
+
* This is the pair that shows why issue #383 was answered PER VENDOR.
|
|
8
|
+
*
|
|
9
|
+
* OneDrive's picker, opened with the `download` action, returns each chosen file
|
|
10
|
+
* carrying a pre-authenticated download URL — a short-lived link that needs no
|
|
11
|
+
* credential to fetch. The browser genuinely can read the bytes, so the honest
|
|
12
|
+
* declaration is `BROWSER_READABLE_URL` and the framework uploads the result the
|
|
13
|
+
* way it uploads any other file.
|
|
14
|
+
*
|
|
15
|
+
* spec-cite: Microsoft Graph `driveItem` reference —
|
|
16
|
+
* https://learn.microsoft.com/en-us/graph/api/resources/driveitem — for the
|
|
17
|
+
* pre-authenticated `@microsoft.graph.downloadUrl` property. Paraphrased rather
|
|
18
|
+
* than quoted; the reading that matters here is that the URL carries its own
|
|
19
|
+
* authorization and expires, which is what makes the browser-readable arm
|
|
20
|
+
* available at all.
|
|
21
|
+
*
|
|
22
|
+
* Drive's picker returns metadata only, so its bytes need a credential the
|
|
23
|
+
* browser must not hold. Two real vendors, two different contracts, and a
|
|
24
|
+
* framework that insisted on one would have had to drop one of them.
|
|
25
|
+
*
|
|
26
|
+
* ## Not proven in a browser
|
|
27
|
+
*
|
|
28
|
+
* Nothing here has been driven against a real OneDrive account. Unlike the Drive
|
|
29
|
+
* picker this one authenticates through the vendor's own popup rather than
|
|
30
|
+
* needing a token from the application, so it does not depend on #333 — but it
|
|
31
|
+
* still needs a real `clientId` and a real user. Tracked with the rest of the
|
|
32
|
+
* frontend provider surface in #359.
|
|
33
|
+
*/
|
|
34
|
+
import { useEffect, useRef } from 'react';
|
|
35
|
+
import { ProviderFrontendSelectionDelivery, } from '@wildo-ai/saas-models/public-runtime';
|
|
36
|
+
function readString(value) {
|
|
37
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
function readNumber(value) {
|
|
40
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Opens the OneDrive picker as soon as it mounts — see the Drive picker for why mounting IS opening. */
|
|
43
|
+
export function OneDrivePicker(props) {
|
|
44
|
+
const openedRef = useRef(false);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (openedRef.current)
|
|
47
|
+
return;
|
|
48
|
+
openedRef.current = true;
|
|
49
|
+
let cancelled = false;
|
|
50
|
+
const fail = (message) => {
|
|
51
|
+
if (!cancelled)
|
|
52
|
+
props.onError(new Error(message));
|
|
53
|
+
};
|
|
54
|
+
const globals = window;
|
|
55
|
+
if (globals.OneDrive === undefined) {
|
|
56
|
+
fail('The OneDrive picker script has not loaded, so the picker cannot open. The `microsoft-onedrive` provider '
|
|
57
|
+
+ 'declares it in `scripts`; check that the surface injects provider scripts and that the composed '
|
|
58
|
+
+ 'Content-Security-Policy admits https://js.live.net.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const clientId = readString(props.publicConfig.clientId);
|
|
62
|
+
if (clientId === undefined) {
|
|
63
|
+
fail("The `microsoft-onedrive` provider's publicConfig declares no `clientId`, which the OneDrive picker requires.");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
globals.OneDrive.open({
|
|
67
|
+
clientId,
|
|
68
|
+
// `download` is what makes this picker's result browser-readable: it is the
|
|
69
|
+
// action that returns a pre-authenticated URL rather than a bare item id.
|
|
70
|
+
// Changing it to `share` or `query` would change what a selection CONTAINS,
|
|
71
|
+
// and the provider's declared `selectionDelivery` would then be a lie the
|
|
72
|
+
// framework refuses — see `receiveProviderFileSelections`.
|
|
73
|
+
action: 'download',
|
|
74
|
+
multiSelect: props.constraints.maxFiles !== undefined && props.constraints.maxFiles > 1,
|
|
75
|
+
advanced: {
|
|
76
|
+
// The vendor filters by extension, not by MIME type, so an allow-list of
|
|
77
|
+
// MIME types cannot be translated faithfully. It is left to the field,
|
|
78
|
+
// which validates every chosen file against `allowedMimeTypes` anyway —
|
|
79
|
+
// a filter that silently narrowed to the wrong set would be worse than
|
|
80
|
+
// no filter, because the user would never see the file they wanted.
|
|
81
|
+
...(props.publicConfig.pickerRedirectUri === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: { redirectUri: String(props.publicConfig.pickerRedirectUri) }),
|
|
84
|
+
},
|
|
85
|
+
success: (response) => {
|
|
86
|
+
if (cancelled)
|
|
87
|
+
return;
|
|
88
|
+
const selections = [];
|
|
89
|
+
for (const item of response.value ?? []) {
|
|
90
|
+
const url = readString(item['@microsoft.graph.downloadUrl']);
|
|
91
|
+
// No download URL means the picker did not run the `download` action —
|
|
92
|
+
// a misconfiguration, and the one case where handing the selection up
|
|
93
|
+
// anyway would break the declaration this provider makes.
|
|
94
|
+
if (url === undefined) {
|
|
95
|
+
fail('OneDrive returned a selected item with no download URL. The picker must be opened with the '
|
|
96
|
+
+ '`download` action for its result to be browser-readable, which is what this provider declares.');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
selections.push({
|
|
100
|
+
kind: ProviderFrontendSelectionDelivery.BROWSER_READABLE_URL,
|
|
101
|
+
url,
|
|
102
|
+
displayName: readString(item.name),
|
|
103
|
+
mimeType: readString(item.file?.mimeType),
|
|
104
|
+
sizeBytes: readNumber(item.size),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
props.onSelect(selections);
|
|
108
|
+
},
|
|
109
|
+
cancel: () => {
|
|
110
|
+
if (!cancelled)
|
|
111
|
+
props.onCancel();
|
|
112
|
+
},
|
|
113
|
+
error: (error) => {
|
|
114
|
+
fail(error instanceof Error ? error.message : `OneDrive picker failed: ${String(error)}`);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return () => {
|
|
118
|
+
cancelled = true;
|
|
119
|
+
};
|
|
120
|
+
// Mount-only — see the Drive picker.
|
|
121
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
122
|
+
}, []);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=onedrive-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onedrive-picker.js","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/onedrive-picker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EACL,iCAAiC,GAGlC,MAAM,sCAAsC,CAAC;AAgC9C,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACjF,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,cAAc,CAAC,KAA8B;IAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO;YAAE,OAAO;QAC9B,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC;QAEzB,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,IAAI,GAAG,CAAC,OAAe,EAAQ,EAAE;YACrC,IAAI,CAAC,SAAS;gBAAE,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,MAAoC,CAAC;QACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACnC,IAAI,CACF,0GAA0G;kBACxG,kGAAkG;kBAClG,qDAAqD,CACxD,CAAC;YACF,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,8GAA8G,CAAC,CAAC;YACrH,OAAO;QACT,CAAC;QAED,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;YACpB,QAAQ;YACR,4EAA4E;YAC5E,0EAA0E;YAC1E,4EAA4E;YAC5E,0EAA0E;YAC1E,2DAA2D;YAC3D,MAAM,EAAE,UAAU;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC;YACvF,QAAQ,EAAE;gBACR,yEAAyE;gBACzE,uEAAuE;gBACvE,wEAAwE;gBACxE,uEAAuE;gBACvE,oEAAoE;gBACpE,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,KAAK,SAAS;oBACpD,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE,CAAC;aACnE;YACD,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;gBACpB,IAAI,SAAS;oBAAE,OAAO;gBAEtB,MAAM,UAAU,GAA4B,EAAE,CAAC;gBAC/C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;oBACxC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC;oBAC7D,uEAAuE;oBACvE,sEAAsE;oBACtE,0DAA0D;oBAC1D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;wBACtB,IAAI,CACF,6FAA6F;8BAC3F,gGAAgG,CACnG,CAAC;wBACF,OAAO;oBACT,CAAC;oBACD,UAAU,CAAC,IAAI,CAAC;wBACd,IAAI,EAAE,iCAAiC,CAAC,oBAAoB;wBAC5D,GAAG;wBACH,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;wBAClC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC;wBACzC,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;qBACjC,CAAC,CAAC;gBACL,CAAC;gBACD,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YAC7B,CAAC;YACD,MAAM,EAAE,GAAG,EAAE;gBACX,IAAI,CAAC,SAAS;oBAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnC,CAAC;YACD,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE;gBACf,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC5F,CAAC;SACF,CAAC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC,CAAC;QACF,qCAAqC;QACrC,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["/**\n * OneDrive file picker — the component the `microsoft-onedrive` provider contributes for\n * `FRONTEND_FILE_PICKER`.\n *\n * ## Why it declares BROWSER_READABLE_URL, where its Google sibling declares REMOTE_REFERENCE\n *\n * This is the pair that shows why issue #383 was answered PER VENDOR.\n *\n * OneDrive's picker, opened with the `download` action, returns each chosen file\n * carrying a pre-authenticated download URL — a short-lived link that needs no\n * credential to fetch. The browser genuinely can read the bytes, so the honest\n * declaration is `BROWSER_READABLE_URL` and the framework uploads the result the\n * way it uploads any other file.\n *\n * spec-cite: Microsoft Graph `driveItem` reference —\n * https://learn.microsoft.com/en-us/graph/api/resources/driveitem — for the\n * pre-authenticated `@microsoft.graph.downloadUrl` property. Paraphrased rather\n * than quoted; the reading that matters here is that the URL carries its own\n * authorization and expires, which is what makes the browser-readable arm\n * available at all.\n *\n * Drive's picker returns metadata only, so its bytes need a credential the\n * browser must not hold. Two real vendors, two different contracts, and a\n * framework that insisted on one would have had to drop one of them.\n *\n * ## Not proven in a browser\n *\n * Nothing here has been driven against a real OneDrive account. Unlike the Drive\n * picker this one authenticates through the vendor's own popup rather than\n * needing a token from the application, so it does not depend on #333 — but it\n * still needs a real `clientId` and a real user. Tracked with the rest of the\n * frontend provider surface in #359.\n */\n\nimport { useEffect, useRef } from 'react';\nimport {\n ProviderFrontendSelectionDelivery,\n type ProviderFilePickerProps,\n type ProviderFileSelection,\n} from '@wildo-ai/saas-models/public-runtime';\n\n/** The slice of a returned OneDrive item this component reads. */\ninterface OneDriveItem {\n readonly id?: unknown;\n readonly name?: unknown;\n readonly size?: unknown;\n readonly file?: { readonly mimeType?: unknown };\n readonly '@microsoft.graph.downloadUrl'?: unknown;\n}\n\ninterface OneDriveOpenOptions {\n clientId: string;\n action: string;\n multiSelect: boolean;\n advanced: { filter?: string; redirectUri?: string };\n success(response: { value?: readonly OneDriveItem[] }): void;\n cancel(): void;\n error(error: unknown): void;\n}\n\n/**\n * The `OneDrive` global the vendor's `<script>` installs.\n *\n * Typed locally for the same reason as the Drive picker's `gapi`: the surface\n * used is one function, and a `@types` package for a global that may not be\n * present buys nothing this file does not already state.\n */\ninterface OneDriveGlobals {\n readonly OneDrive?: { open(options: OneDriveOpenOptions): void };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction readNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** Opens the OneDrive picker as soon as it mounts — see the Drive picker for why mounting IS opening. */\nexport function OneDrivePicker(props: ProviderFilePickerProps): null {\n const openedRef = useRef(false);\n\n useEffect(() => {\n if (openedRef.current) return;\n openedRef.current = true;\n\n let cancelled = false;\n const fail = (message: string): void => {\n if (!cancelled) props.onError(new Error(message));\n };\n\n const globals = window as unknown as OneDriveGlobals;\n if (globals.OneDrive === undefined) {\n fail(\n 'The OneDrive picker script has not loaded, so the picker cannot open. The `microsoft-onedrive` provider '\n + 'declares it in `scripts`; check that the surface injects provider scripts and that the composed '\n + 'Content-Security-Policy admits https://js.live.net.',\n );\n return;\n }\n\n const clientId = readString(props.publicConfig.clientId);\n if (clientId === undefined) {\n fail(\"The `microsoft-onedrive` provider's publicConfig declares no `clientId`, which the OneDrive picker requires.\");\n return;\n }\n\n globals.OneDrive.open({\n clientId,\n // `download` is what makes this picker's result browser-readable: it is the\n // action that returns a pre-authenticated URL rather than a bare item id.\n // Changing it to `share` or `query` would change what a selection CONTAINS,\n // and the provider's declared `selectionDelivery` would then be a lie the\n // framework refuses — see `receiveProviderFileSelections`.\n action: 'download',\n multiSelect: props.constraints.maxFiles !== undefined && props.constraints.maxFiles > 1,\n advanced: {\n // The vendor filters by extension, not by MIME type, so an allow-list of\n // MIME types cannot be translated faithfully. It is left to the field,\n // which validates every chosen file against `allowedMimeTypes` anyway —\n // a filter that silently narrowed to the wrong set would be worse than\n // no filter, because the user would never see the file they wanted.\n ...(props.publicConfig.pickerRedirectUri === undefined\n ? {}\n : { redirectUri: String(props.publicConfig.pickerRedirectUri) }),\n },\n success: (response) => {\n if (cancelled) return;\n\n const selections: ProviderFileSelection[] = [];\n for (const item of response.value ?? []) {\n const url = readString(item['@microsoft.graph.downloadUrl']);\n // No download URL means the picker did not run the `download` action —\n // a misconfiguration, and the one case where handing the selection up\n // anyway would break the declaration this provider makes.\n if (url === undefined) {\n fail(\n 'OneDrive returned a selected item with no download URL. The picker must be opened with the '\n + '`download` action for its result to be browser-readable, which is what this provider declares.',\n );\n return;\n }\n selections.push({\n kind: ProviderFrontendSelectionDelivery.BROWSER_READABLE_URL,\n url,\n displayName: readString(item.name),\n mimeType: readString(item.file?.mimeType),\n sizeBytes: readNumber(item.size),\n });\n }\n props.onSelect(selections);\n },\n cancel: () => {\n if (!cancelled) props.onCancel();\n },\n error: (error) => {\n fail(error instanceof Error ? error.message : `OneDrive picker failed: ${String(error)}`);\n },\n });\n\n return () => {\n cancelled = true;\n };\n // Mount-only — see the Drive picker.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return null;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OneDrivePickerLazy — the ONLY public door onto the OneDrivePicker module.
|
|
3
|
+
*
|
|
4
|
+
* Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the
|
|
5
|
+
* `React.lazy(() => import(...))` split point so the picker's code never lands in the
|
|
6
|
+
* application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of
|
|
7
|
+
* the wrapped module from anywhere else. A provider picker is opened by a user who asked
|
|
8
|
+
* for one; every other visitor pays nothing for it.
|
|
9
|
+
*
|
|
10
|
+
* No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a
|
|
11
|
+
* vendor overlay), so reserving layout would draw a box where there will never be one.
|
|
12
|
+
*/
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import type { ProviderFilePickerProps } from "@wildo-ai/saas-models/public-runtime";
|
|
15
|
+
export declare function OneDrivePickerLazy(props: ProviderFilePickerProps): React.JSX.Element;
|
|
16
|
+
//# sourceMappingURL=onedrive-picker.lazy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onedrive-picker.lazy.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/onedrive-picker.lazy.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAInF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAMpF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* OneDrivePickerLazy — the ONLY public door onto the OneDrivePicker module.
|
|
5
|
+
*
|
|
6
|
+
* Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the
|
|
7
|
+
* `React.lazy(() => import(...))` split point so the picker's code never lands in the
|
|
8
|
+
* application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of
|
|
9
|
+
* the wrapped module from anywhere else. A provider picker is opened by a user who asked
|
|
10
|
+
* for one; every other visitor pays nothing for it.
|
|
11
|
+
*
|
|
12
|
+
* No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a
|
|
13
|
+
* vendor overlay), so reserving layout would draw a box where there will never be one.
|
|
14
|
+
*/
|
|
15
|
+
import * as React from "react";
|
|
16
|
+
const LazyOneDrivePicker = React.lazy(async () => ({ default: (await import("./onedrive-picker.js")).OneDrivePicker }));
|
|
17
|
+
export function OneDrivePickerLazy(props) {
|
|
18
|
+
return (_jsx(React.Suspense, { fallback: null, children: _jsx(LazyOneDrivePicker, { ...props }) }));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=onedrive-picker.lazy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onedrive-picker.lazy.js","sourceRoot":"","sources":["../../../../../../../src/components/form/controls/file/providers/onedrive-picker.lazy.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,kBAAkB,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAA;AAEpH,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAC/D,OAAO,CACL,KAAC,KAAK,CAAC,QAAQ,IAAC,QAAQ,EAAE,IAAI,YAC5B,KAAC,kBAAkB,OAAK,KAAK,GAAI,GAClB,CAClB,CAAA;AACH,CAAC","sourcesContent":["\"use client\"\n\n/**\n * OneDrivePickerLazy — the ONLY public door onto the OneDrivePicker module.\n *\n * Lazy-boundary wrapper (`*.lazy.tsx` convention, PdfViewerLazy precedent): owns the\n * `React.lazy(() => import(...))` split point so the picker's code never lands in the\n * application's entry chunk, and `pnpm check:lazy-boundaries` refuses any static import of\n * the wrapped module from anywhere else. A provider picker is opened by a user who asked\n * for one; every other visitor pays nothing for it.\n *\n * No Suspense fallback with a shape: a picker renders NOTHING of its own (it opens a\n * vendor overlay), so reserving layout would draw a box where there will never be one.\n */\n\nimport * as React from \"react\"\nimport type { ProviderFilePickerProps } from \"@wildo-ai/saas-models/public-runtime\"\n\nconst LazyOneDrivePicker = React.lazy(async () => ({ default: (await import(\"./onedrive-picker\")).OneDrivePicker }))\n\nexport function OneDrivePickerLazy(props: ProviderFilePickerProps): React.JSX.Element {\n return (\n <React.Suspense fallback={null}>\n <LazyOneDrivePicker {...props} />\n </React.Suspense>\n )\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utility-panel.d.ts","sourceRoot":"","sources":["../../../../../src/components/navigation/app-level/utility-panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAiBL,kCAAkC,EAInC,MAAM,6BAA6B,CAAC;AAgBrC,eAAO,MAAM,2CAA2C,EAAE,KAAK,CAAC,EAAE,CAAC,kCAAkC,
|
|
1
|
+
{"version":3,"file":"utility-panel.d.ts","sourceRoot":"","sources":["../../../../../src/components/navigation/app-level/utility-panel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAiBL,kCAAkC,EAInC,MAAM,6BAA6B,CAAC;AAgBrC,eAAO,MAAM,2CAA2C,EAAE,KAAK,CAAC,EAAE,CAAC,kCAAkC,CAkLpG,CAAC"}
|
|
@@ -81,10 +81,17 @@ export const ApplicationLevelPreset_UtilityPanel_Default = ({ position = Horizon
|
|
|
81
81
|
return CustomComponent ? (_jsx(React.Suspense, { fallback: _jsx("div", { className: "h-8 animate-pulse motion-reduce:animate-none bg-muted rounded", "aria-hidden": "true" }), children: _jsx(CustomComponent, {}) })) : renderUnresolved(item.componentRef);
|
|
82
82
|
})();
|
|
83
83
|
const itemRef = item.kind === LauncherItemTargetKind.VIEW ? item.viewRef : item.componentRef;
|
|
84
|
-
return (_jsx(Surface, { asChild: true, emitPolicyVars: false, variant: SurfaceVariant.INSET, children: _jsxs("div", {
|
|
84
|
+
return (_jsx(Surface, { asChild: true, emitPolicyVars: false, variant: SurfaceVariant.INSET, children: _jsxs("div", {
|
|
85
|
+
/*
|
|
86
|
+
* `min-w-0` is load-bearing, not cosmetic. A flex/grid child defaults to `min-width: auto`,
|
|
87
|
+
* so it refuses to shrink below its own content width and overflows its host instead —
|
|
88
|
+
* which is exactly how a 514px view came to be clipped by a 320px sheet rather than
|
|
89
|
+
* laying itself out narrower. Widening the sheet alone would only move the threshold.
|
|
90
|
+
*/
|
|
91
|
+
className: "border rounded-md p-card-sm space-y-item min-w-0", "data-utility-item": itemRef, "data-utility-item-kind": item.kind, children: [_jsxs("div", { className: "flex items-center gap-element-sm", children: [itemIcon, _jsx("span", { className: "text-sm font-medium", children: item.label })] }), body] }) }, `${itemRef}:${index}`));
|
|
85
92
|
};
|
|
86
93
|
if (!isReady)
|
|
87
94
|
return null;
|
|
88
|
-
return (_jsx("div", { "data-component-type": "utility-panel", "data-breakpoint": currentBreakpoint, children: _jsxs(Sheet, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(SheetTrigger, { asChild: true, children: _jsx(Button, { variant: ButtonVariants_Variant.GHOST, size: ButtonSize.SM, "aria-label": toggleLabel, className: "relative", children: panelIcon }) }), _jsxs(SheetContent, { side: sheetSide, className: "w-
|
|
95
|
+
return (_jsx("div", { "data-component-type": "utility-panel", "data-breakpoint": currentBreakpoint, children: _jsxs(Sheet, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(SheetTrigger, { asChild: true, children: _jsx(Button, { variant: ButtonVariants_Variant.GHOST, size: ButtonSize.SM, "aria-label": toggleLabel, className: "relative", children: panelIcon }) }), _jsxs(SheetContent, { side: sheetSide, className: "w-[36rem] sm:max-w-[36rem] max-w-[100vw]", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: titleLabel }) }), _jsx(ScrollArea, { className: "h-[calc(100vh-120px)] mt-field", children: _jsxs("div", { className: "space-y-field pe-card", children: [resolvedItems.length > 0 && (_jsx("div", { className: "space-y-item", children: resolvedItems.map(renderItem) })), resolvedItems.length === 0 && (_jsx("div", { className: "text-sm text-muted-foreground text-center py-section-xl", children: emptyStateLabel }))] }) })] })] }) }));
|
|
89
96
|
};
|
|
90
97
|
//# sourceMappingURL=utility-panel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utility-panel.js","sourceRoot":"","sources":["../../../../../src/components/navigation/app-level/utility-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { ButtonSize, CorePresetNames, FrontendComponentType, HorizontalPlacement, PlacementSide, SurfaceVariant, resolvePlacementSideToRadix } from '@wildo-ai/presets-components-models';\n\nimport {\n Button,\n ButtonVariants_Variant,\n ComponentRegistryService,\n IconSize,\n resolveComponentLabel,\n ScrollArea,\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n StandardIconReference,\n useI18n,\n useIconRegistry,\n useUtilityPanelBehavior,\n Surface,\n ApplicationLevelProps_UtilityPanel,\n UtilityPanelPlacement,\n FlowsActorsView,\n LauncherItemTargetKind,\n} from '@wildo-ai/saas-frontend-lib';\n\n/**\n * Maps horizontal placement to logical placement side.\n * Physical side resolution is delegated to the shared RTL-aware helper.\n */\nfunction getSheetPlacementSideFromPlacement(placement: UtilityPanelPlacement): PlacementSide {\n return placement === HorizontalPlacement.START ? PlacementSide.START : PlacementSide.END;\n}\n\nfunction getUtilityPanelIconReference(placement: UtilityPanelPlacement): StandardIconReference {\n return placement === HorizontalPlacement.START\n ? StandardIconReference.PANEL_LEFT\n : StandardIconReference.CHEVRON_RIGHT;\n}\n\nexport const ApplicationLevelPreset_UtilityPanel_Default: React.FC<ApplicationLevelProps_UtilityPanel> = ({\n position = HorizontalPlacement.END,\n items,\n}) => {\n const behavior = useUtilityPanelBehavior(position, items);\n const { tComponent, isReady } = useI18n({ component: FrontendComponentType.APPLICATION_LEVEL_UTILITY_PANEL });\n const { renderIcon } = useIconRegistry();\n\n const {\n isOpen,\n setIsOpen,\n items: resolvedItems,\n position: resolvedPosition,\n currentBreakpoint,\n } = behavior;\n\n // I-7: no English fallbacks — missing keys surface as `components.<type>.<key>`\n // for translation debugging (intentional, framework-wide i18n discipline).\n const t = (key: string): string => resolveComponentLabel({\n tComponent,\n componentType: FrontendComponentType.APPLICATION_LEVEL_UTILITY_PANEL,\n key,\n });\n const titleLabel = t('title');\n const toggleLabel = t('toggle');\n const emptyStateLabel = t('emptyState');\n\n // Follow the engine convention: keep logical placement until the last moment,\n // then resolve it against the current document direction.\n const sheetPlacementSide = getSheetPlacementSideFromPlacement(resolvedPosition);\n const dir = (typeof document !== 'undefined' ? document.documentElement?.dir : undefined) || 'ltr';\n const sheetSide = resolvePlacementSideToRadix(sheetPlacementSide, dir as 'ltr' | 'rtl');\n\n // Utility panel icon (panel icon for side panel concept)\n const panelIcon = renderIcon(getUtilityPanelIconReference(resolvedPosition), { size: IconSize.MD });\n\n /**\n * Renders a single utility panel item.\n *\n * Custom components render inline, and that is the whole behaviour — nothing here navigates\n * (#504). If a rendered utility view ever pushes a view, it will need a zone of its own, because a\n * push from inside a side sheet otherwise lands in `PRIMARY` and replaces the workspace the panel\n * exists to sit beside. That zone is worth adding then, and not before it has anything to hold.\n */\n /**\n * The shape shown when an item names something this panel cannot host.\n *\n * It prints the offending REF, in destructive colour. It used to print the item's LABEL in a\n * neutral grey box — the shape of a component that rendered fine and had little to say — so a\n * misconfigured panel looked like a working one. Same rule a missing i18n label follows: make the\n * gap visible, never substitute something plausible (#504).\n */\n const renderUnresolved = (ref: string) => (\n <Surface asChild emitPolicyVars={false} variant={SurfaceVariant.INSET}>\n <div className=\"text-xs text-destructive bg-destructive/10 p-card-sm rounded\" role=\"status\">\n {t('unresolvedComponent')} <code>{ref}</code>\n </div>\n </Surface>\n );\n\n /**\n * Renders a single utility panel item.\n *\n * Both kinds render INLINE, and nothing here navigates (#504). If a hosted view ever pushes a\n * view, it will need a zone of its own, because a push from inside a side sheet otherwise lands in\n * `PRIMARY` and replaces the workspace the panel exists to sit beside. That zone is worth adding\n * then, and not before it has anything to hold — and the first question will be whether it IS\n * `COMPANION`, which already describes persistent supplementary content.\n *\n * `VIEW` dispatches to exactly ONE host today, because `FlowsActorsViewDefinition` is the only\n * application-scoped view kind the panel can host. Adding a second means extending this switch and\n * the behaviour hook's validation TOGETHER — the hook decides whether a ref is known, this decides\n * what renders it, and a kind added to one and not the other is a ref that validates and shows\n * nothing.\n */\n const renderItem = (item: typeof resolvedItems[number], index: number) => {\n const itemIcon = renderIcon(item.icon, { size: IconSize.SM });\n\n const body = item.kind === LauncherItemTargetKind.VIEW\n ? (item.resolvedViewRef !== null\n ? <FlowsActorsView viewRef={item.resolvedViewRef} />\n : renderUnresolved(item.viewRef))\n : (() => {\n const CustomComponent = item.componentType\n ? ComponentRegistryService.resolve(item.componentType, CorePresetNames.DEFAULT)\n : null;\n return CustomComponent ? (\n <React.Suspense\n fallback={<div className=\"h-8 animate-pulse motion-reduce:animate-none bg-muted rounded\" aria-hidden=\"true\" />}\n >\n <CustomComponent />\n </React.Suspense>\n ) : renderUnresolved(item.componentRef);\n })();\n\n const itemRef = item.kind === LauncherItemTargetKind.VIEW ? item.viewRef : item.componentRef;\n\n return (\n <Surface key={`${itemRef}:${index}`} asChild emitPolicyVars={false} variant={SurfaceVariant.INSET}>\n <div\n className=\"border rounded-md p-card-sm space-y-item\"\n data-utility-item={itemRef}\n data-utility-item-kind={item.kind}\n >\n <div className=\"flex items-center gap-element-sm\">\n {itemIcon}\n <span className=\"text-sm font-medium\">{item.label}</span>\n </div>\n {body}\n </div>\n </Surface>\n );\n };\n\n if (!isReady) return null;\n\n return (\n <div data-component-type=\"utility-panel\" data-breakpoint={currentBreakpoint}>\n <Sheet open={isOpen} onOpenChange={setIsOpen}>\n <SheetTrigger asChild>\n <Button\n variant={ButtonVariants_Variant.GHOST}\n size={ButtonSize.SM}\n aria-label={toggleLabel}\n className=\"relative\"\n >\n {panelIcon}\n </Button>\n </SheetTrigger>\n\n <SheetContent side={sheetSide} className=\"w-80\">\n <SheetHeader>\n <SheetTitle>{titleLabel}</SheetTitle>\n </SheetHeader>\n\n <ScrollArea className=\"h-[calc(100vh-120px)] mt-field\">\n <div className=\"space-y-field pe-card\">\n {/* Items */}\n {resolvedItems.length > 0 && (\n <div className=\"space-y-item\">\n {resolvedItems.map(renderItem)}\n </div>\n )}\n\n {/* Empty state */}\n {resolvedItems.length === 0 && (\n <div className=\"text-sm text-muted-foreground text-center py-section-xl\">\n {emptyStateLabel}\n </div>\n )}\n </div>\n </ScrollArea>\n </SheetContent>\n </Sheet>\n </div>\n );\n};\n"]}
|
|
1
|
+
{"version":3,"file":"utility-panel.js","sourceRoot":"","sources":["../../../../../src/components/navigation/app-level/utility-panel.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { ButtonSize, CorePresetNames, FrontendComponentType, HorizontalPlacement, PlacementSide, SurfaceVariant, resolvePlacementSideToRadix } from '@wildo-ai/presets-components-models';\n\nimport {\n Button,\n ButtonVariants_Variant,\n ComponentRegistryService,\n IconSize,\n resolveComponentLabel,\n ScrollArea,\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n StandardIconReference,\n useI18n,\n useIconRegistry,\n useUtilityPanelBehavior,\n Surface,\n ApplicationLevelProps_UtilityPanel,\n UtilityPanelPlacement,\n FlowsActorsView,\n LauncherItemTargetKind,\n} from '@wildo-ai/saas-frontend-lib';\n\n/**\n * Maps horizontal placement to logical placement side.\n * Physical side resolution is delegated to the shared RTL-aware helper.\n */\nfunction getSheetPlacementSideFromPlacement(placement: UtilityPanelPlacement): PlacementSide {\n return placement === HorizontalPlacement.START ? PlacementSide.START : PlacementSide.END;\n}\n\nfunction getUtilityPanelIconReference(placement: UtilityPanelPlacement): StandardIconReference {\n return placement === HorizontalPlacement.START\n ? StandardIconReference.PANEL_LEFT\n : StandardIconReference.CHEVRON_RIGHT;\n}\n\nexport const ApplicationLevelPreset_UtilityPanel_Default: React.FC<ApplicationLevelProps_UtilityPanel> = ({\n position = HorizontalPlacement.END,\n items,\n}) => {\n const behavior = useUtilityPanelBehavior(position, items);\n const { tComponent, isReady } = useI18n({ component: FrontendComponentType.APPLICATION_LEVEL_UTILITY_PANEL });\n const { renderIcon } = useIconRegistry();\n\n const {\n isOpen,\n setIsOpen,\n items: resolvedItems,\n position: resolvedPosition,\n currentBreakpoint,\n } = behavior;\n\n // I-7: no English fallbacks — missing keys surface as `components.<type>.<key>`\n // for translation debugging (intentional, framework-wide i18n discipline).\n const t = (key: string): string => resolveComponentLabel({\n tComponent,\n componentType: FrontendComponentType.APPLICATION_LEVEL_UTILITY_PANEL,\n key,\n });\n const titleLabel = t('title');\n const toggleLabel = t('toggle');\n const emptyStateLabel = t('emptyState');\n\n // Follow the engine convention: keep logical placement until the last moment,\n // then resolve it against the current document direction.\n const sheetPlacementSide = getSheetPlacementSideFromPlacement(resolvedPosition);\n const dir = (typeof document !== 'undefined' ? document.documentElement?.dir : undefined) || 'ltr';\n const sheetSide = resolvePlacementSideToRadix(sheetPlacementSide, dir as 'ltr' | 'rtl');\n\n // Utility panel icon (panel icon for side panel concept)\n const panelIcon = renderIcon(getUtilityPanelIconReference(resolvedPosition), { size: IconSize.MD });\n\n /**\n * Renders a single utility panel item.\n *\n * Custom components render inline, and that is the whole behaviour — nothing here navigates\n * (#504). If a rendered utility view ever pushes a view, it will need a zone of its own, because a\n * push from inside a side sheet otherwise lands in `PRIMARY` and replaces the workspace the panel\n * exists to sit beside. That zone is worth adding then, and not before it has anything to hold.\n */\n /**\n * The shape shown when an item names something this panel cannot host.\n *\n * It prints the offending REF, in destructive colour. It used to print the item's LABEL in a\n * neutral grey box — the shape of a component that rendered fine and had little to say — so a\n * misconfigured panel looked like a working one. Same rule a missing i18n label follows: make the\n * gap visible, never substitute something plausible (#504).\n */\n const renderUnresolved = (ref: string) => (\n <Surface asChild emitPolicyVars={false} variant={SurfaceVariant.INSET}>\n <div className=\"text-xs text-destructive bg-destructive/10 p-card-sm rounded\" role=\"status\">\n {t('unresolvedComponent')} <code>{ref}</code>\n </div>\n </Surface>\n );\n\n /**\n * Renders a single utility panel item.\n *\n * Both kinds render INLINE, and nothing here navigates (#504). If a hosted view ever pushes a\n * view, it will need a zone of its own, because a push from inside a side sheet otherwise lands in\n * `PRIMARY` and replaces the workspace the panel exists to sit beside. That zone is worth adding\n * then, and not before it has anything to hold — and the first question will be whether it IS\n * `COMPANION`, which already describes persistent supplementary content.\n *\n * `VIEW` dispatches to exactly ONE host today, because `FlowsActorsViewDefinition` is the only\n * application-scoped view kind the panel can host. Adding a second means extending this switch and\n * the behaviour hook's validation TOGETHER — the hook decides whether a ref is known, this decides\n * what renders it, and a kind added to one and not the other is a ref that validates and shows\n * nothing.\n */\n const renderItem = (item: typeof resolvedItems[number], index: number) => {\n const itemIcon = renderIcon(item.icon, { size: IconSize.SM });\n\n const body = item.kind === LauncherItemTargetKind.VIEW\n ? (item.resolvedViewRef !== null\n ? <FlowsActorsView viewRef={item.resolvedViewRef} />\n : renderUnresolved(item.viewRef))\n : (() => {\n const CustomComponent = item.componentType\n ? ComponentRegistryService.resolve(item.componentType, CorePresetNames.DEFAULT)\n : null;\n return CustomComponent ? (\n <React.Suspense\n fallback={<div className=\"h-8 animate-pulse motion-reduce:animate-none bg-muted rounded\" aria-hidden=\"true\" />}\n >\n <CustomComponent />\n </React.Suspense>\n ) : renderUnresolved(item.componentRef);\n })();\n\n const itemRef = item.kind === LauncherItemTargetKind.VIEW ? item.viewRef : item.componentRef;\n\n return (\n <Surface key={`${itemRef}:${index}`} asChild emitPolicyVars={false} variant={SurfaceVariant.INSET}>\n <div\n /*\n * `min-w-0` is load-bearing, not cosmetic. A flex/grid child defaults to `min-width: auto`,\n * so it refuses to shrink below its own content width and overflows its host instead —\n * which is exactly how a 514px view came to be clipped by a 320px sheet rather than\n * laying itself out narrower. Widening the sheet alone would only move the threshold.\n */\n className=\"border rounded-md p-card-sm space-y-item min-w-0\"\n data-utility-item={itemRef}\n data-utility-item-kind={item.kind}\n >\n <div className=\"flex items-center gap-element-sm\">\n {itemIcon}\n <span className=\"text-sm font-medium\">{item.label}</span>\n </div>\n {body}\n </div>\n </Surface>\n );\n };\n\n if (!isReady) return null;\n\n return (\n <div data-component-type=\"utility-panel\" data-breakpoint={currentBreakpoint}>\n <Sheet open={isOpen} onOpenChange={setIsOpen}>\n <SheetTrigger asChild>\n <Button\n variant={ButtonVariants_Variant.GHOST}\n size={ButtonSize.SM}\n aria-label={toggleLabel}\n className=\"relative\"\n >\n {panelIcon}\n </Button>\n </SheetTrigger>\n\n {/*\n * Sized to the content it HOSTS, not to the rail it began as (#504).\n *\n * This was `w-80` (320px), which is right for a small widget and wrong for a VIEW. Measured\n * in the running app: the Todo Assistant's MIN-CONTENT width is 514px — it cannot lay out\n * narrower, so it overflowed and was clipped by 194px, its prompt input running off one\n * edge and its description off the other. `min-w-0` on the item does not help against a\n * min-content floor; only giving it the width does.\n *\n * `sm:max-w-[36rem]` is not redundant with `w-[36rem]`. `SheetContent`'s own base classes\n * carry `sm:max-w-sm` (384px), which silently CAPPED an earlier attempt at `w-[26rem]` —\n * the computed width came back 384px and the panel still clipped. A width class alone loses\n * to that cap, which is exactly the kind of thing only a browser measurement finds.\n *\n * `max-w-[100vw]` keeps a narrow window whole.\n */}\n <SheetContent side={sheetSide} className=\"w-[36rem] sm:max-w-[36rem] max-w-[100vw]\">\n <SheetHeader>\n <SheetTitle>{titleLabel}</SheetTitle>\n </SheetHeader>\n\n <ScrollArea className=\"h-[calc(100vh-120px)] mt-field\">\n <div className=\"space-y-field pe-card\">\n {/* Items */}\n {resolvedItems.length > 0 && (\n <div className=\"space-y-item\">\n {resolvedItems.map(renderItem)}\n </div>\n )}\n\n {/* Empty state */}\n {resolvedItems.length === 0 && (\n <div className=\"text-sm text-muted-foreground text-center py-section-xl\">\n {emptyStateLabel}\n </div>\n )}\n </div>\n </ScrollArea>\n </SheetContent>\n </Sheet>\n </div>\n );\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/tabs.tsx"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EAKb,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../../src/components/sections/tabs.tsx"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EAKb,MAAM,6BAA6B,CAAC;AAoRrC,eAAO,MAAM,IAAI,cAA+G,CAAA"}
|
|
@@ -166,7 +166,11 @@ function TabsTrigger({ className, ...props }) {
|
|
|
166
166
|
const { getRecipe } = useUI();
|
|
167
167
|
// Vertical rail → FILLED (soft fill on select); horizontal → UNDERLINE.
|
|
168
168
|
const recipe = getRecipe(UIBehavior.SELECTABLE, isVertical ? SelectableVariant.FILLED : SelectableVariant.UNDERLINE);
|
|
169
|
-
return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn(
|
|
169
|
+
return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn(
|
|
170
|
+
// `cursor-pointer` sits here rather than in the recipe, matching the
|
|
171
|
+
// accordion trigger: an element that switches the visible panel must
|
|
172
|
+
// read as clickable whatever the theme dresses it in.
|
|
173
|
+
"inline-flex cursor-pointer items-center gap-1.5 text-sm font-medium whitespace-nowrap [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", isVertical
|
|
170
174
|
// Vertical: full-width, left-aligned rail item (FILLED recipe fills it).
|
|
171
175
|
? "w-full justify-start rounded-md px-card-sm py-item"
|
|
172
176
|
// Horizontal: content-width underline tab (UNDERLINE recipe draws the
|