@dxos/app-framework 0.9.0 → 0.9.1-staging.ee54ba693a
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/lib/browser/{capability-BOPAKKWG.mjs → capability-S44TE2H7.mjs} +9 -9
- package/dist/lib/browser/{chunk-66IXTIVK.mjs → chunk-3NQLTQCP.mjs} +6 -7
- package/dist/lib/browser/chunk-3NQLTQCP.mjs.map +7 -0
- package/dist/lib/browser/chunk-53HJHMS5.mjs +7 -0
- package/dist/lib/browser/chunk-53HJHMS5.mjs.map +7 -0
- package/dist/lib/browser/{chunk-Y66ELD4U.mjs → chunk-KRJ4KK2W.mjs} +36 -7
- package/dist/lib/browser/chunk-KRJ4KK2W.mjs.map +7 -0
- package/dist/lib/browser/{chunk-KZUDO43J.mjs → chunk-LUH4Y5F2.mjs} +31 -23
- package/dist/lib/browser/chunk-LUH4Y5F2.mjs.map +7 -0
- package/dist/lib/browser/{chunk-CO3XLNUX.mjs → chunk-NKVRSMFN.mjs} +18 -18
- package/dist/lib/browser/chunk-NKVRSMFN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-DC3WRPBV.mjs → chunk-OO53M5UK.mjs} +45 -12
- package/dist/lib/browser/chunk-OO53M5UK.mjs.map +7 -0
- package/dist/lib/browser/{chunk-IW44C7UL.mjs → chunk-OPTS3ZSN.mjs} +1 -1
- package/dist/lib/browser/chunk-OPTS3ZSN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-HFLKXMT7.mjs → chunk-U5MESPM5.mjs} +61 -45
- package/dist/lib/browser/chunk-U5MESPM5.mjs.map +7 -0
- package/dist/lib/browser/{chunk-XHS5KDNX.mjs → chunk-UGYCLOXE.mjs} +25 -25
- package/dist/lib/browser/chunk-UGYCLOXE.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5GY3YOEL.mjs → chunk-VF37YTXJ.mjs} +5 -5
- package/dist/lib/browser/chunk-VF37YTXJ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-FO3IYSLV.mjs → chunk-YUVQIOTB.mjs} +29 -37
- package/dist/lib/browser/chunk-YUVQIOTB.mjs.map +7 -0
- package/dist/lib/browser/cli/index.mjs +9 -9
- package/dist/lib/browser/cli/index.mjs.map +2 -2
- package/dist/lib/browser/common/activation-events.mjs +7 -7
- package/dist/lib/browser/common/capabilities.mjs +7 -7
- package/dist/lib/browser/config/index.mjs +8 -0
- package/dist/lib/browser/config/index.mjs.map +7 -0
- package/dist/lib/browser/core/activation-event.mjs +1 -1
- package/dist/lib/browser/core/capability.mjs +1 -1
- package/dist/lib/browser/core/plugin-manager.mjs +4 -4
- package/dist/lib/browser/core/plugin.mjs +12 -2
- package/dist/lib/browser/core/url-loader.mjs +3 -3
- package/dist/lib/browser/index.mjs +14 -10
- package/dist/lib/browser/index.mjs.map +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs → process-manager-capability-QK4BMKMN.mjs} +9 -9
- package/dist/lib/browser/testing/index.mjs +15 -15
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/testing/react.mjs +11 -11
- package/dist/lib/browser/testing/react.mjs.map +3 -3
- package/dist/lib/browser/ui/index.mjs +18 -10
- package/dist/lib/node-esm/{capability-KZWRTX7R.mjs → capability-3YRF77LV.mjs} +9 -9
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs +9 -0
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XIGPWXCQ.mjs → chunk-4SE7SX7N.mjs} +45 -12
- package/dist/lib/node-esm/chunk-4SE7SX7N.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XOCUANHO.mjs → chunk-BMW6P6QA.mjs} +6 -7
- package/dist/lib/node-esm/chunk-BMW6P6QA.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-A5CHGAZE.mjs → chunk-DRT75WGD.mjs} +61 -45
- package/dist/lib/node-esm/chunk-DRT75WGD.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-XGBUKRCK.mjs → chunk-FKMVORZN.mjs} +31 -23
- package/dist/lib/node-esm/chunk-FKMVORZN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-CTKEZHKF.mjs → chunk-GLH4LGHZ.mjs} +1 -1
- package/dist/lib/node-esm/chunk-GLH4LGHZ.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-SX5NVETP.mjs → chunk-IL7O7IRX.mjs} +25 -25
- package/dist/lib/node-esm/chunk-IL7O7IRX.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-FSVGKTF6.mjs → chunk-ND2HETHD.mjs} +36 -7
- package/dist/lib/node-esm/chunk-ND2HETHD.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-KFZEB6BV.mjs → chunk-TLKYZKPW.mjs} +5 -5
- package/dist/lib/node-esm/chunk-TLKYZKPW.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-WK7OIQKI.mjs → chunk-VCPGX7WN.mjs} +29 -37
- package/dist/lib/node-esm/chunk-VCPGX7WN.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-NULBHQYS.mjs → chunk-XSZMX53L.mjs} +18 -18
- package/dist/lib/node-esm/chunk-XSZMX53L.mjs.map +7 -0
- package/dist/lib/node-esm/cli/index.mjs +9 -9
- package/dist/lib/node-esm/cli/index.mjs.map +2 -2
- package/dist/lib/node-esm/common/activation-events.mjs +7 -7
- package/dist/lib/node-esm/common/capabilities.mjs +7 -7
- package/dist/lib/node-esm/config/index.mjs +9 -0
- package/dist/lib/node-esm/config/index.mjs.map +7 -0
- package/dist/lib/node-esm/core/activation-event.mjs +1 -1
- package/dist/lib/node-esm/core/capability.mjs +1 -1
- package/dist/lib/node-esm/core/plugin-manager.mjs +4 -4
- package/dist/lib/node-esm/core/plugin.mjs +12 -2
- package/dist/lib/node-esm/core/url-loader.mjs +3 -3
- package/dist/lib/node-esm/index.mjs +14 -10
- package/dist/lib/node-esm/index.mjs.map +1 -1
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs → process-manager-capability-5S7WZ2JR.mjs} +9 -9
- package/dist/lib/node-esm/testing/index.mjs +15 -15
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/testing/react.mjs +11 -11
- package/dist/lib/node-esm/testing/react.mjs.map +3 -3
- package/dist/lib/node-esm/ui/index.mjs +18 -10
- package/dist/plugin/node-esm/index.mjs +414 -81
- package/dist/plugin/node-esm/index.mjs.map +4 -4
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/cli/cli.d.ts +1 -1
- package/dist/types/src/common/activation-events.d.ts.map +1 -1
- package/dist/types/src/common/capabilities.d.ts +2 -2
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/config/index.d.ts +2 -0
- package/dist/types/src/config/index.d.ts.map +1 -0
- package/dist/types/src/core/activation-event.d.ts +9 -6
- package/dist/types/src/core/activation-event.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +9 -3
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts +6 -11
- package/dist/types/src/core/edge-registry-plugin-provider.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manifest.d.ts +25 -7
- package/dist/types/src/core/plugin-manifest.d.ts.map +1 -1
- package/dist/types/src/core/plugin.d.ts +89 -91
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/registry.d.ts +17 -57
- package/dist/types/src/core/registry.d.ts.map +1 -1
- package/dist/types/src/core/url-loader.d.ts +2 -2
- package/dist/types/src/core/url-loader.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/history/capability.d.ts +1 -3
- package/dist/types/src/plugin-process-manager/history/capability.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/meta.d.ts +32 -2
- package/dist/types/src/plugin-process-manager/meta.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts +1 -4
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -1
- package/dist/types/src/testing/harness.d.ts +1 -1
- package/dist/types/src/testing/harness.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +6 -5
- package/dist/types/src/testing/react.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.d.ts.map +1 -1
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/App.stories.d.ts +2 -2
- package/dist/types/src/ui/components/App/App.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts +6 -0
- package/dist/types/src/ui/components/PluginManager/PluginManagerProvider.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +6 -10
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/context.d.ts +6 -2
- package/dist/types/src/ui/components/Surface/context.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/index.d.ts +11 -4
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.d.ts +52 -72
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts +1 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts +17 -1
- package/dist/types/src/ui/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/composer/index.d.ts +11 -4
- package/dist/types/src/vite-plugin/composer/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/import-map/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/index.d.ts +1 -0
- package/dist/types/src/vite-plugin/index.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/load.d.ts +16 -0
- package/dist/types/src/vite-plugin/load.d.ts.map +1 -0
- package/dist/types/src/vite-plugin/manifest.d.ts +16 -4
- package/dist/types/src/vite-plugin/manifest.d.ts.map +1 -1
- package/dist/types/src/vite-plugin/packages.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/docs/registry-spec.md +331 -0
- package/moon.yml +1 -0
- package/package.json +50 -39
- package/src/cli/cli.ts +2 -2
- package/src/common/activation-events.ts +3 -3
- package/src/common/capabilities.ts +18 -18
- package/src/config/index.ts +5 -0
- package/src/core/activation-event.ts +15 -8
- package/src/core/capability.ts +18 -6
- package/src/core/edge-registry-plugin-provider.ts +41 -42
- package/src/core/plugin-manager.test.ts +133 -137
- package/src/core/plugin-manager.ts +25 -19
- package/src/core/plugin-manifest.test.ts +5 -5
- package/src/core/plugin-manifest.ts +13 -10
- package/src/core/plugin.ts +58 -114
- package/src/core/registry.ts +20 -60
- package/src/core/url-loader.test.ts +2 -2
- package/src/core/url-loader.ts +23 -19
- package/src/index.ts +1 -0
- package/src/testing/harness.ts +5 -3
- package/src/testing/react.test.tsx +4 -2
- package/src/testing/react.tsx +10 -9
- package/src/testing/service.ts +2 -2
- package/src/testing/withPluginManager.stories.tsx +4 -2
- package/src/testing/withPluginManager.tsx +3 -1
- package/src/ui/components/App/App.stories.tsx +2 -2
- package/src/ui/components/PluginManager/PluginManagerProvider.ts +7 -0
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +22 -22
- package/src/ui/components/Surface/SurfaceComponent.tsx +90 -79
- package/src/ui/components/Surface/SurfaceProfilerContext.tsx +1 -1
- package/src/ui/components/Surface/context.ts +6 -4
- package/src/ui/components/Surface/index.ts +14 -3
- package/src/ui/components/Surface/types.test.ts +43 -51
- package/src/ui/components/Surface/types.ts +99 -64
- package/src/ui/hooks/useApp.test.tsx +1 -1
- package/src/ui/hooks/useApp.tsx +2 -2
- package/src/ui/hooks/useCapabilities.ts +49 -2
- package/src/vite-plugin/boot-loader/BootLoader.solid-stories.tsx +11 -11
- package/src/vite-plugin/boot-loader/loader.ts +3 -3
- package/src/vite-plugin/composer/index.ts +134 -61
- package/src/vite-plugin/import-map/index.ts +23 -7
- package/src/vite-plugin/index.ts +1 -0
- package/src/vite-plugin/load.ts +57 -0
- package/src/vite-plugin/manifest.test.ts +3 -3
- package/src/vite-plugin/manifest.ts +21 -10
- package/src/vite-plugin/packages.ts +0 -2
- package/tsconfig.json +5 -2
- package/dist/lib/browser/chunk-5GY3YOEL.mjs.map +0 -7
- package/dist/lib/browser/chunk-66IXTIVK.mjs.map +0 -7
- package/dist/lib/browser/chunk-CO3XLNUX.mjs.map +0 -7
- package/dist/lib/browser/chunk-DC3WRPBV.mjs.map +0 -7
- package/dist/lib/browser/chunk-FO3IYSLV.mjs.map +0 -7
- package/dist/lib/browser/chunk-HFLKXMT7.mjs.map +0 -7
- package/dist/lib/browser/chunk-IW44C7UL.mjs.map +0 -7
- package/dist/lib/browser/chunk-KZUDO43J.mjs.map +0 -7
- package/dist/lib/browser/chunk-XHS5KDNX.mjs.map +0 -7
- package/dist/lib/browser/chunk-Y66ELD4U.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-A5CHGAZE.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-CTKEZHKF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FSVGKTF6.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-KFZEB6BV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-NULBHQYS.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-SX5NVETP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WK7OIQKI.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XGBUKRCK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XIGPWXCQ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XOCUANHO.mjs.map +0 -7
- /package/dist/lib/browser/{capability-BOPAKKWG.mjs.map → capability-S44TE2H7.mjs.map} +0 -0
- /package/dist/lib/browser/{process-manager-capability-GXNUO7AR.mjs.map → process-manager-capability-QK4BMKMN.mjs.map} +0 -0
- /package/dist/lib/node-esm/{capability-KZWRTX7R.mjs.map → capability-3YRF77LV.mjs.map} +0 -0
- /package/dist/lib/node-esm/{process-manager-capability-TX5TA6YN.mjs.map → process-manager-capability-5S7WZ2JR.mjs.map} +0 -0
|
@@ -2,16 +2,20 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type { FC, PropsWithChildren, ReactNode,
|
|
5
|
+
import type { FC, PropsWithChildren, ReactNode, Ref } from 'react';
|
|
6
6
|
|
|
7
|
+
import type { DXN } from '@dxos/keys';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
7
9
|
import type { MakeOptional, Position } from '@dxos/util';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
* A typed role token. Carries a role
|
|
12
|
+
* A typed role token. Carries a role NSID for runtime dispatch plus a phantom
|
|
11
13
|
* data type so consumers using `type={SomeToken}` and providers using
|
|
12
14
|
* `AppSurface.object(SomeToken, ...)` share a single type-level contract.
|
|
13
15
|
*
|
|
14
|
-
* Mint via {@link makeType}.
|
|
16
|
+
* Mint via {@link makeType}. The `role` field holds the bare NSID (e.g.
|
|
17
|
+
* `org.dxos.role.article`) — the dispatch key, `data-role` attribute value,
|
|
18
|
+
* and what `.role` comparisons inside matched components see.
|
|
15
19
|
*/
|
|
16
20
|
export type RoleToken<TData> = {
|
|
17
21
|
readonly role: string;
|
|
@@ -20,8 +24,8 @@ export type RoleToken<TData> = {
|
|
|
20
24
|
};
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
|
-
* One entry in a {@link SurfaceFilter} — a role plus the guard that
|
|
24
|
-
* the data shape when the Surface dispatcher is matching that role.
|
|
27
|
+
* One entry in a {@link SurfaceFilter} — a role NSID plus the guard that
|
|
28
|
+
* validates the data shape when the Surface dispatcher is matching that role.
|
|
25
29
|
*/
|
|
26
30
|
export type SurfaceBinding = {
|
|
27
31
|
readonly role: string;
|
|
@@ -52,17 +56,61 @@ export const isSurfaceFilter = (value: unknown): value is SurfaceFilter<any> =>
|
|
|
52
56
|
typeof value === 'object' && value !== null && Array.isArray((value as { bindings?: unknown }).bindings);
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
|
-
* Mints a typed role token.
|
|
56
|
-
*
|
|
59
|
+
* Mints a typed role token. The NSID is validated at compile time via
|
|
60
|
+
* {@link DXN.Name}: the final segment must be camelCase (no hyphens).
|
|
61
|
+
* Dynamic NSIDs (template-literal / runtime strings) are validated at runtime.
|
|
62
|
+
*
|
|
63
|
+
* Identity is structural; all tokens with the same `role` string are
|
|
64
|
+
* interchangeable at runtime.
|
|
57
65
|
*/
|
|
58
|
-
export const makeType
|
|
66
|
+
export const makeType: {
|
|
67
|
+
<TData = unknown, T extends string = string>(
|
|
68
|
+
nsid: [DXN.Name<T>] extends [never] ? `Invalid NSID "${T}": final segment must be camelCase (no hyphens)` : T,
|
|
69
|
+
): RoleToken<TData>;
|
|
70
|
+
} = <TData>(nsid: string): RoleToken<TData> => {
|
|
71
|
+
// Runtime guard for dynamic NSIDs that bypass the compile-time DXN.Name<T> check.
|
|
72
|
+
if (
|
|
73
|
+
typeof nsid !== 'string' ||
|
|
74
|
+
!/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z][a-zA-Z0-9]{0,62})$/.test(
|
|
75
|
+
nsid,
|
|
76
|
+
)
|
|
77
|
+
) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Invalid role NSID: "${nsid}". Must be a dotted name with a camelCase final segment (no hyphens in the last segment).`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return { role: nsid };
|
|
83
|
+
};
|
|
59
84
|
|
|
60
85
|
/**
|
|
61
|
-
*
|
|
86
|
+
* Creates a {@link SurfaceFilter} from a role token and an optional guard.
|
|
87
|
+
*
|
|
88
|
+
* When `guard` is omitted the filter matches any data at the token's role
|
|
89
|
+
* (role-only dispatch). Pass a guard to add runtime data-shape validation on
|
|
90
|
+
* top of the role match.
|
|
62
91
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
|
|
92
|
+
* This is the framework-level primitive; `@dxos/app-toolkit` builds richer
|
|
93
|
+
* domain-aware helpers (ECHO schema checks, literal matching, etc.) on top of it.
|
|
94
|
+
*/
|
|
95
|
+
export const makeFilter = <TData>(token: RoleToken<TData>, guard?: (data: TData) => boolean): SurfaceFilter<TData> => {
|
|
96
|
+
const boundGuard =
|
|
97
|
+
guard == null
|
|
98
|
+
? () => true
|
|
99
|
+
: (data: unknown): boolean => {
|
|
100
|
+
try {
|
|
101
|
+
return guard(data as TData);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
log.catch(err);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return { bindings: [{ role: token.role, guard: boundGuard }] };
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Typed Surface consumer props — carries the role/data-shape contract via a
|
|
112
|
+
* {@link RoleToken}. The `type` prop is required; the string `role` prop has
|
|
113
|
+
* been removed from the consumer-facing API.
|
|
66
114
|
*/
|
|
67
115
|
export type Props<T extends Record<string, any> = Record<string, unknown>> = {
|
|
68
116
|
/**
|
|
@@ -76,22 +124,7 @@ export type Props<T extends Record<string, any> = Record<string, unknown>> = {
|
|
|
76
124
|
* The placeholder component will be rendered while the surface component is loading.
|
|
77
125
|
*/
|
|
78
126
|
placeholder?: ReactNode;
|
|
79
|
-
} & MakeOptional<CoreProps<T>, 'id' | 'data'
|
|
80
|
-
/**
|
|
81
|
-
* Explicitly disallow `type` on the untyped Props overload — if the caller
|
|
82
|
-
* provides `type={AppSurface.X}`, TypeScript must route to the typed
|
|
83
|
-
* overload (where `data` is narrowed by the token). Without this, the
|
|
84
|
-
* catch-all index signature below would accept any `type` value and mask
|
|
85
|
-
* the typed overload entirely.
|
|
86
|
-
*/
|
|
87
|
-
type?: undefined;
|
|
88
|
-
} /**
|
|
89
|
-
* Additional props to pass to the component.
|
|
90
|
-
* These props are not used by Surface itself but may be used by components which resolve the surface.
|
|
91
|
-
* Exclude known prop names to prevent overriding well-defined props.
|
|
92
|
-
*/ & {
|
|
93
|
-
[K in keyof Record<string, any>]: K extends keyof CoreProps<T> | 'fallback' | 'placeholder' | 'type' ? never : any;
|
|
94
|
-
};
|
|
127
|
+
} & MakeOptional<CoreProps<T>, 'id' | 'data'>;
|
|
95
128
|
|
|
96
129
|
/**
|
|
97
130
|
* Typed Surface consumer props — carries the role/data-shape contract via a
|
|
@@ -118,6 +151,8 @@ export type TypedProps<TToken extends RoleToken<any>> = {
|
|
|
118
151
|
|
|
119
152
|
/**
|
|
120
153
|
* NOTE: If `[key: string]: unknown` is included in shared types, when re-used other fields become unknown as well.
|
|
154
|
+
* The `type` prop is the only way to set the role; it is required here and
|
|
155
|
+
* optional in {@link Props} via MakeOptional.
|
|
121
156
|
*/
|
|
122
157
|
export type CoreProps<T extends Record<string, any> = Record<string, unknown>> = PropsWithChildren<{
|
|
123
158
|
/**
|
|
@@ -126,12 +161,9 @@ export type CoreProps<T extends Record<string, any> = Record<string, unknown>> =
|
|
|
126
161
|
id: string;
|
|
127
162
|
|
|
128
163
|
/**
|
|
129
|
-
*
|
|
130
|
-
* typed overload of the Surface component which takes a {@link RoleToken}
|
|
131
|
-
* via the `type` prop instead — it enforces a role/data contract at compile
|
|
132
|
-
* time.
|
|
164
|
+
* The role token defining how the data should be rendered.
|
|
133
165
|
*/
|
|
134
|
-
|
|
166
|
+
type: RoleToken<T>;
|
|
135
167
|
|
|
136
168
|
/**
|
|
137
169
|
* The data to be rendered by the surface.
|
|
@@ -146,9 +178,20 @@ export type CoreProps<T extends Record<string, any> = Record<string, unknown>> =
|
|
|
146
178
|
}>;
|
|
147
179
|
|
|
148
180
|
// TODO(burdon): Remove ref since relying on this would be error prone.
|
|
149
|
-
export type ComponentProps<T extends Record<string, any> = Record<string, any>> =
|
|
150
|
-
|
|
151
|
-
|
|
181
|
+
export type ComponentProps<T extends Record<string, any> = Record<string, any>> = {
|
|
182
|
+
/**
|
|
183
|
+
* The resolved role NSID string (e.g. `org.dxos.role.article`). Present on
|
|
184
|
+
* matched components only — multi-role containers can branch on this value
|
|
185
|
+
* by comparing against a token's `.role` (e.g. `role === AppSurface.Section.role`).
|
|
186
|
+
*/
|
|
187
|
+
role: string;
|
|
188
|
+
ref?: Ref<HTMLElement>;
|
|
189
|
+
} & PropsWithChildren<{
|
|
190
|
+
id: string;
|
|
191
|
+
data: T;
|
|
192
|
+
limit?: number | undefined;
|
|
193
|
+
}> &
|
|
194
|
+
Record<string, any>;
|
|
152
195
|
|
|
153
196
|
/**
|
|
154
197
|
* React component used to render a surface once is has matched.
|
|
@@ -163,24 +206,30 @@ export type ComponentFunction<T extends Record<string, any> = Record<string, any
|
|
|
163
206
|
* The optional `filter` receives the consumer-supplied `role` as its second
|
|
164
207
|
* argument so a single definition can encode role-specific guards (used by the
|
|
165
208
|
* {@link SurfaceFilter}-based `create` overload).
|
|
209
|
+
*
|
|
210
|
+
* @internal The `role` field is populated by the framework from the filter
|
|
211
|
+
* bindings and is not authored directly.
|
|
166
212
|
*/
|
|
167
213
|
export type ReactDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
168
214
|
kind: 'react';
|
|
169
215
|
id: string;
|
|
170
216
|
role: string | string[];
|
|
171
|
-
position?: Position;
|
|
217
|
+
position?: Position.Position;
|
|
172
218
|
component: ComponentFunction<T>;
|
|
173
219
|
filter?: (data: Record<string, unknown>, role?: string) => data is T;
|
|
174
220
|
}>;
|
|
175
221
|
|
|
176
222
|
/**
|
|
177
223
|
* Definition of when a Web Component surface should be rendered.
|
|
224
|
+
*
|
|
225
|
+
* @internal The `role` field is populated by the framework from the filter
|
|
226
|
+
* bindings and is not authored directly.
|
|
178
227
|
*/
|
|
179
228
|
export type WebComponentDefinition<T extends Record<string, any> = any> = Readonly<{
|
|
180
229
|
kind: 'web-component';
|
|
181
230
|
id: string;
|
|
182
231
|
role: string | string[];
|
|
183
|
-
position?: Position;
|
|
232
|
+
position?: Position.Position;
|
|
184
233
|
/**
|
|
185
234
|
* The tag name of the Web Component to render.
|
|
186
235
|
* The Web Component will receive the same props as React surfaces via properties/attributes.
|
|
@@ -201,7 +250,7 @@ export type TypedReactDefinition<T extends Record<string, any> = any> = Readonly
|
|
|
201
250
|
id: string;
|
|
202
251
|
filter: SurfaceFilter<T>;
|
|
203
252
|
component: ComponentFunction<T>;
|
|
204
|
-
position?: Position;
|
|
253
|
+
position?: Position.Position;
|
|
205
254
|
}>;
|
|
206
255
|
|
|
207
256
|
/**
|
|
@@ -211,7 +260,7 @@ export type TypedWebComponentDefinition<T extends Record<string, any> = any> = R
|
|
|
211
260
|
id: string;
|
|
212
261
|
filter: SurfaceFilter<T>;
|
|
213
262
|
tagName: string;
|
|
214
|
-
position?: Position;
|
|
263
|
+
position?: Position.Position;
|
|
215
264
|
}>;
|
|
216
265
|
|
|
217
266
|
const expandBindings = <T extends Record<string, any>>(
|
|
@@ -250,41 +299,27 @@ const validateLocalId = (id: string): void => {
|
|
|
250
299
|
};
|
|
251
300
|
|
|
252
301
|
/**
|
|
253
|
-
* Creates a React surface definition.
|
|
302
|
+
* Creates a React surface definition from a typed filter.
|
|
254
303
|
*/
|
|
255
304
|
export function create<T extends Record<string, any> = any>(definition: TypedReactDefinition<T>): ReactDefinition<T>;
|
|
256
|
-
export function create<T extends Record<string, any> = any>(
|
|
257
|
-
definition: Omit<ReactDefinition<T>, 'kind'>,
|
|
258
|
-
): ReactDefinition<T>;
|
|
259
|
-
export function create<T extends Record<string, any> = any>(
|
|
260
|
-
definition: TypedReactDefinition<T> | Omit<ReactDefinition<T>, 'kind'>,
|
|
261
|
-
): ReactDefinition<T> {
|
|
305
|
+
export function create<T extends Record<string, any> = any>(definition: TypedReactDefinition<T>): ReactDefinition<T> {
|
|
262
306
|
validateLocalId(definition.id);
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return { kind: 'react', id, role, position, component, filter: guard };
|
|
267
|
-
}
|
|
268
|
-
return { ...(definition as Omit<ReactDefinition<T>, 'kind'>), kind: 'react' };
|
|
307
|
+
const { id, filter, component, position } = definition;
|
|
308
|
+
const { role, guard } = expandBindings(filter);
|
|
309
|
+
return { kind: 'react', id, role, position, component, filter: guard };
|
|
269
310
|
}
|
|
270
311
|
|
|
271
312
|
/**
|
|
272
|
-
* Creates a Web Component surface definition.
|
|
313
|
+
* Creates a Web Component surface definition from a typed filter.
|
|
273
314
|
*/
|
|
274
315
|
export function createWeb<T extends Record<string, any> = any>(
|
|
275
316
|
definition: TypedWebComponentDefinition<T>,
|
|
276
317
|
): WebComponentDefinition<T>;
|
|
277
318
|
export function createWeb<T extends Record<string, any> = any>(
|
|
278
|
-
definition:
|
|
279
|
-
): WebComponentDefinition<T>;
|
|
280
|
-
export function createWeb<T extends Record<string, any> = any>(
|
|
281
|
-
definition: TypedWebComponentDefinition<T> | Omit<WebComponentDefinition<T>, 'kind'>,
|
|
319
|
+
definition: TypedWebComponentDefinition<T>,
|
|
282
320
|
): WebComponentDefinition<T> {
|
|
283
321
|
validateLocalId(definition.id);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return { kind: 'web-component', id, role, position, tagName, filter: guard };
|
|
288
|
-
}
|
|
289
|
-
return { ...(definition as Omit<WebComponentDefinition<T>, 'kind'>), kind: 'web-component' };
|
|
322
|
+
const { id, filter, tagName, position } = definition;
|
|
323
|
+
const { role, guard } = expandBindings(filter);
|
|
324
|
+
return { kind: 'web-component', id, role, position, tagName, filter: guard };
|
|
290
325
|
}
|
|
@@ -18,7 +18,7 @@ const testMeta = Plugin.makeMeta({ key: DXN.make('org.dxos.plugin.test'), name:
|
|
|
18
18
|
|
|
19
19
|
const pluginLoader = (plugins: Plugin.Plugin[]) =>
|
|
20
20
|
Effect.fn(function* (id: string) {
|
|
21
|
-
const plugin = plugins.find((plugin) => plugin.meta.
|
|
21
|
+
const plugin = plugins.find((plugin) => plugin.meta.profile.key === id);
|
|
22
22
|
if (!plugin) {
|
|
23
23
|
return yield* Effect.fail(new Error(`Plugin not found: ${id}`));
|
|
24
24
|
}
|
package/src/ui/hooks/useApp.tsx
CHANGED
|
@@ -94,7 +94,7 @@ export type UseAppOptions = {
|
|
|
94
94
|
* );
|
|
95
95
|
*
|
|
96
96
|
* @param params.pluginLoader A function which loads new plugins.
|
|
97
|
-
* @param params.plugins All plugins available to the application. Plugins whose `meta.tags` includes `'system'` are treated as core (force-enabled, not user-toggleable).
|
|
97
|
+
* @param params.plugins All plugins available to the application. Plugins whose `meta.profile.tags` includes `'system'` are treated as core (force-enabled, not user-toggleable).
|
|
98
98
|
* @param params.defaults Default plugins are enabled by default but can be disabled by the user.
|
|
99
99
|
* @param params.cacheEnabled Whether to cache enabled plugins in localStorage.
|
|
100
100
|
* @param params.safeMode Whether to enable safe mode, which disables optional plugins.
|
|
@@ -123,7 +123,7 @@ export const useApp = ({
|
|
|
123
123
|
pluginLoaderProp ??
|
|
124
124
|
((id: string) =>
|
|
125
125
|
Effect.sync(() => {
|
|
126
|
-
const plugin = plugins.find((plugin) => plugin.meta.
|
|
126
|
+
const plugin = plugins.find((plugin) => plugin.meta.profile.key === id);
|
|
127
127
|
invariant(plugin, `Plugin not found: ${id}`);
|
|
128
128
|
return { plugin };
|
|
129
129
|
})),
|
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom';
|
|
6
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
7
|
import { useCallback } from 'react';
|
|
7
8
|
|
|
8
9
|
import { invariant } from '@dxos/invariant';
|
|
9
10
|
|
|
10
11
|
import { Capabilities } from '../../common';
|
|
11
12
|
import { type Capability } from '../../core';
|
|
12
|
-
import { usePluginManager } from '../components';
|
|
13
|
+
import { useOptionalPluginManager, usePluginManager } from '../components';
|
|
14
|
+
|
|
15
|
+
/** Stable empty result for capability lookups made outside a plugin manager. */
|
|
16
|
+
const emptyCapabilities = Atom.make(() => [] as const);
|
|
17
|
+
|
|
18
|
+
/** Stable atom yielding `undefined`, used as the fallback for optional atom-capability lookups. */
|
|
19
|
+
const emptyAtomValue = Atom.make(() => undefined);
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Hook to request capabilities from the plugin context.
|
|
@@ -32,6 +39,24 @@ export const useCapability = <T>(interfaceDef: Capability.InterfaceDef<T>) => {
|
|
|
32
39
|
return capabilities[0];
|
|
33
40
|
};
|
|
34
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Hook to request capabilities without requiring a plugin manager.
|
|
44
|
+
* @returns An array of capabilities, or an empty array when rendered outside a {@link PluginManagerProvider}.
|
|
45
|
+
*/
|
|
46
|
+
export const useOptionalCapabilities = <T>(interfaceDef: Capability.InterfaceDef<T>): readonly T[] => {
|
|
47
|
+
const manager = useOptionalPluginManager();
|
|
48
|
+
return useAtomValue(
|
|
49
|
+
manager ? manager.capabilities.atom(interfaceDef) : (emptyCapabilities as Atom.Atom<readonly T[]>),
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hook to request a single capability without requiring a plugin manager.
|
|
55
|
+
* @returns The first matching capability, or `undefined` when none is registered (or there is no plugin manager).
|
|
56
|
+
*/
|
|
57
|
+
export const useOptionalCapability = <T>(interfaceDef: Capability.InterfaceDef<T>): T | undefined =>
|
|
58
|
+
useOptionalCapabilities(interfaceDef)[0];
|
|
59
|
+
|
|
35
60
|
/**
|
|
36
61
|
* Hook to get the current value of an atom capability.
|
|
37
62
|
* Automatically subscribes to changes.
|
|
@@ -62,6 +87,28 @@ export const useAtomCapabilityState = <T>(
|
|
|
62
87
|
return [value, update];
|
|
63
88
|
};
|
|
64
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Tolerant variant of {@link useAtomCapabilityState}: returns `[undefined, noop]` when the atom
|
|
92
|
+
* capability is not registered (e.g. the contributing plugin is not installed) rather than
|
|
93
|
+
* throwing. The updater is a no-op while the capability is absent.
|
|
94
|
+
*/
|
|
95
|
+
export const useOptionalAtomCapabilityState = <T>(
|
|
96
|
+
atomCapability: Capability.InterfaceDef<Atom.Writable<T>>,
|
|
97
|
+
): [T | undefined, (fn: (current: T) => T) => void] => {
|
|
98
|
+
const registry = useOptionalCapability(Capabilities.AtomRegistry);
|
|
99
|
+
const atom = useOptionalCapability(atomCapability);
|
|
100
|
+
const value = useAtomValue(atom ?? emptyAtomValue);
|
|
101
|
+
const update = useCallback(
|
|
102
|
+
(fn: (current: T) => T) => {
|
|
103
|
+
if (registry && atom) {
|
|
104
|
+
registry.set(atom, fn(registry.get(atom)));
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[registry, atom],
|
|
108
|
+
);
|
|
109
|
+
return [value, update];
|
|
110
|
+
};
|
|
111
|
+
|
|
65
112
|
/**
|
|
66
113
|
* Hook to get the operation invoker capability.
|
|
67
114
|
*/
|
|
@@ -148,12 +148,12 @@ const LoaderHarness = () => {
|
|
|
148
148
|
{/* Float the controls above the loader's `z-index: 10` fixed backdrop. */}
|
|
149
149
|
<div
|
|
150
150
|
style={{
|
|
151
|
-
position: 'fixed',
|
|
152
|
-
top: '12px',
|
|
153
|
-
left: '12px',
|
|
151
|
+
'position': 'fixed',
|
|
152
|
+
'top': '12px',
|
|
153
|
+
'left': '12px',
|
|
154
154
|
'z-index': 9999,
|
|
155
|
-
display: 'flex',
|
|
156
|
-
gap: '12px',
|
|
155
|
+
'display': 'flex',
|
|
156
|
+
'gap': '12px',
|
|
157
157
|
'align-items': 'center',
|
|
158
158
|
}}
|
|
159
159
|
>
|
|
@@ -161,20 +161,20 @@ const LoaderHarness = () => {
|
|
|
161
161
|
type='button'
|
|
162
162
|
onClick={advance}
|
|
163
163
|
style={{
|
|
164
|
-
padding: '6px 14px',
|
|
164
|
+
'padding': '6px 14px',
|
|
165
165
|
'font-size': '13px',
|
|
166
166
|
'font-weight': '600',
|
|
167
|
-
color: '#fff',
|
|
168
|
-
background: '#017ab7',
|
|
169
|
-
border: 'none',
|
|
167
|
+
'color': '#fff',
|
|
168
|
+
'background': '#017ab7',
|
|
169
|
+
'border': 'none',
|
|
170
170
|
'border-radius': '6px',
|
|
171
|
-
cursor: 'pointer',
|
|
171
|
+
'cursor': 'pointer',
|
|
172
172
|
'box-shadow': '0 1px 3px rgba(0,0,0,0.25)',
|
|
173
173
|
}}
|
|
174
174
|
>
|
|
175
175
|
{sim() === 'creep' ? 'Start progress' : 'Finish + reset'}
|
|
176
176
|
</button>
|
|
177
|
-
<span style={{ 'font-family': 'ui-sans-serif, system-ui, sans-serif', 'font-size': '12px', color: '#888' }}>
|
|
177
|
+
<span style={{ 'font-family': 'ui-sans-serif, system-ui, sans-serif', 'font-size': '12px', 'color': '#888' }}>
|
|
178
178
|
{sim()}
|
|
179
179
|
</span>
|
|
180
180
|
</div>
|
|
@@ -8,7 +8,7 @@ import * as babel from '@babel/core';
|
|
|
8
8
|
import babelPresetTypeScript from '@babel/preset-typescript';
|
|
9
9
|
// @ts-ignore — babel-preset-solid ships no .d.ts.
|
|
10
10
|
import babelPresetSolid from 'babel-preset-solid';
|
|
11
|
-
import {
|
|
11
|
+
import { type Plugin as EsbuildPlugin, build } from 'esbuild';
|
|
12
12
|
import { basename, dirname } from 'node:path';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { type Plugin } from 'vite';
|
|
@@ -181,8 +181,8 @@ export const bootLoaderPlugin = ({ status, markSvg, include = ['index.html'] }:
|
|
|
181
181
|
tag: 'div',
|
|
182
182
|
injectTo: 'body-prepend',
|
|
183
183
|
attrs: {
|
|
184
|
-
id: rootId,
|
|
185
|
-
role: 'status',
|
|
184
|
+
'id': rootId,
|
|
185
|
+
'role': 'status',
|
|
186
186
|
'aria-live': 'polite',
|
|
187
187
|
'aria-label': 'Initializing',
|
|
188
188
|
},
|