@dxos/app-framework 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.mts +1 -1
- package/.storybook/preview.mts +2 -2
- package/CHANGELOG.md +93 -0
- package/TASKS.md +11 -0
- package/dist/lib/chunk-SurfaceManager.mjs +960 -0
- package/dist/lib/chunk-SurfaceManager.mjs.map +1 -0
- package/dist/lib/chunk-capability.mjs +15 -0
- package/dist/lib/chunk-capability.mjs.map +1 -0
- package/dist/lib/{browser/chunk-FJ4765WW.mjs → chunk-context.mjs} +4 -5
- package/dist/lib/chunk-context.mjs.map +1 -0
- package/dist/lib/chunk-history-tracker.mjs +160 -0
- package/dist/lib/chunk-history-tracker.mjs.map +1 -0
- package/dist/lib/chunk-plugin-manager.mjs +1390 -0
- package/dist/lib/chunk-plugin-manager.mjs.map +1 -0
- package/dist/lib/chunk-plugin-manifest.mjs +125 -0
- package/dist/lib/chunk-plugin-manifest.mjs.map +1 -0
- package/dist/lib/chunk-process-manager-capability.mjs +126 -0
- package/dist/lib/chunk-process-manager-capability.mjs.map +1 -0
- package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
- package/dist/lib/chunk-url-loader.mjs +347 -0
- package/dist/lib/chunk-url-loader.mjs.map +1 -0
- package/dist/lib/cli.mjs +78 -0
- package/dist/lib/cli.mjs.map +1 -0
- package/dist/lib/common/activation-events.mjs +35 -0
- package/dist/lib/common/activation-events.mjs.map +1 -0
- package/dist/lib/common/capabilities.mjs +184 -0
- package/dist/lib/common/capabilities.mjs.map +1 -0
- package/dist/lib/config.mjs +2 -0
- package/dist/lib/core/activation-event.mjs +55 -0
- package/dist/lib/core/activation-event.mjs.map +1 -0
- package/dist/lib/core/capability.mjs +189 -0
- package/dist/lib/core/capability.mjs.map +1 -0
- package/dist/lib/core/plugin-manager.mjs +2 -0
- package/dist/lib/core/plugin.mjs +340 -0
- package/dist/lib/core/plugin.mjs.map +1 -0
- package/dist/lib/core/url-loader.mjs +2 -0
- package/dist/lib/index.mjs +134 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing/react.mjs +70 -0
- package/dist/lib/testing/react.mjs.map +1 -0
- package/dist/lib/testing.mjs +399 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/ui.mjs +636 -0
- package/dist/lib/ui.mjs.map +1 -0
- package/dist/lib/vite-plugin.mjs +986 -0
- package/dist/lib/vite-plugin.mjs.map +1 -0
- package/dist/plugin/node-esm/index.mjs +4 -3
- package/dist/plugin/node-esm/index.mjs.map +3 -3
- package/dist/plugin/node-esm/meta.json +1 -1
- package/dist/types/src/common/Role.d.ts +31 -0
- package/dist/types/src/common/Role.d.ts.map +1 -0
- package/dist/types/src/common/Role.test.d.ts +2 -0
- package/dist/types/src/common/Role.test.d.ts.map +1 -0
- package/dist/types/src/common/capabilities.d.ts +11 -2
- package/dist/types/src/common/capabilities.d.ts.map +1 -1
- package/dist/types/src/common/index.d.ts +1 -0
- package/dist/types/src/common/index.d.ts.map +1 -1
- package/dist/types/src/core/capability-manager.d.ts +1 -1
- package/dist/types/src/core/capability-manager.d.ts.map +1 -1
- package/dist/types/src/core/capability.d.ts +38 -5
- package/dist/types/src/core/capability.d.ts.map +1 -1
- package/dist/types/src/core/plugin-manager.d.ts +1 -1
- package/dist/types/src/core/plugin-manager.d.ts.map +1 -1
- package/dist/types/src/core/plugin.d.ts +14 -1
- package/dist/types/src/core/plugin.d.ts.map +1 -1
- package/dist/types/src/core/registry.d.ts +1 -1
- package/dist/types/src/core/registry.d.ts.map +1 -1
- package/dist/types/src/plugin-process-manager/history/capability.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts +1 -1
- package/dist/types/src/plugin-process-manager/process-manager-capability.d.ts.map +1 -1
- package/dist/types/src/testing/StorybookErrorFallback.d.ts +15 -0
- package/dist/types/src/testing/StorybookErrorFallback.d.ts.map +1 -0
- 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/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/react.d.ts +4 -4
- 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 +6 -0
- package/dist/types/src/testing/withPluginManager.stories.d.ts.map +1 -1
- package/dist/types/src/testing/withSurfaceDebug.d.ts +10 -0
- package/dist/types/src/testing/withSurfaceDebug.d.ts.map +1 -0
- package/dist/types/src/ui/components/App/App.d.ts +0 -32
- package/dist/types/src/ui/components/App/App.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/index.d.ts +1 -0
- package/dist/types/src/ui/components/App/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/App/loader.d.ts +35 -0
- package/dist/types/src/ui/components/App/loader.d.ts.map +1 -0
- package/dist/types/src/ui/components/HomeSection/HomeSection.d.ts +26 -0
- package/dist/types/src/ui/components/HomeSection/HomeSection.d.ts.map +1 -0
- package/dist/types/src/ui/components/HomeSection/HomeSection.stories.d.ts +15 -0
- package/dist/types/src/ui/components/HomeSection/HomeSection.stories.d.ts.map +1 -0
- package/dist/types/src/ui/components/HomeSection/index.d.ts +2 -0
- package/dist/types/src/ui/components/HomeSection/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/NamePopover/NamePopover.d.ts +14 -0
- package/dist/types/src/ui/components/NamePopover/NamePopover.d.ts.map +1 -0
- package/dist/types/src/ui/components/NamePopover/index.d.ts +2 -0
- package/dist/types/src/ui/components/NamePopover/index.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts +21 -9
- package/dist/types/src/ui/components/Surface/SurfaceComponent.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts +4 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.stories.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/SurfaceComponent.test.d.ts +2 -0
- package/dist/types/src/ui/components/Surface/SurfaceComponent.test.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceDebug.d.ts +40 -0
- package/dist/types/src/ui/components/Surface/SurfaceDebug.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts +23 -0
- package/dist/types/src/ui/components/Surface/SurfaceManager.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceManagerContext.d.ts +11 -0
- package/dist/types/src/ui/components/Surface/SurfaceManagerContext.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceMetrics.d.ts +72 -0
- package/dist/types/src/ui/components/Surface/SurfaceMetrics.d.ts.map +1 -0
- package/dist/types/src/ui/components/Surface/SurfaceProfilerContext.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/index.d.ts +20 -25
- package/dist/types/src/ui/components/Surface/index.d.ts.map +1 -1
- package/dist/types/src/ui/components/Surface/types.d.ts +56 -52
- package/dist/types/src/ui/components/Surface/types.d.ts.map +1 -1
- package/dist/types/src/ui/components/index.d.ts +2 -0
- package/dist/types/src/ui/components/index.d.ts.map +1 -1
- package/dist/types/src/ui/hooks/useApp.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/moon.yml +25 -18
- package/package.json +58 -73
- package/src/common/Role.test.ts +41 -0
- package/src/common/Role.ts +54 -0
- package/src/common/capabilities.ts +17 -2
- package/src/common/index.ts +1 -0
- package/src/core/capability-manager.test.ts +1 -1
- package/src/core/capability-manager.ts +1 -1
- package/src/core/capability.ts +45 -6
- package/src/core/plugin-manager.test.ts +38 -3
- package/src/core/plugin-manager.ts +18 -1
- package/src/core/plugin.ts +16 -1
- package/src/core/registry.ts +1 -1
- package/src/plugin-process-manager/process-manager-capability.ts +25 -2
- package/src/testing/StorybookErrorFallback.tsx +40 -0
- package/src/testing/harness.ts +1 -1
- package/src/testing/index.ts +2 -0
- package/src/testing/react.test.tsx +2 -1
- package/src/testing/react.tsx +14 -8
- package/src/testing/withPluginManager.stories.tsx +16 -1
- package/src/testing/withPluginManager.tsx +13 -2
- package/src/testing/withSurfaceDebug.tsx +22 -0
- package/src/ui/components/App/App.tsx +7 -35
- package/src/ui/components/App/index.ts +1 -0
- package/src/ui/components/App/loader.ts +39 -0
- package/src/ui/components/HomeSection/HomeSection.stories.tsx +43 -0
- package/src/ui/components/HomeSection/HomeSection.tsx +69 -0
- package/src/ui/components/HomeSection/index.ts +5 -0
- package/src/ui/components/NamePopover/NamePopover.tsx +70 -0
- package/src/ui/components/NamePopover/index.ts +5 -0
- package/src/ui/components/Surface/DESIGN.md +136 -0
- package/src/ui/components/Surface/SurfaceComponent.stories.tsx +70 -41
- package/src/ui/components/Surface/SurfaceComponent.test.tsx +403 -0
- package/src/ui/components/Surface/SurfaceComponent.tsx +209 -189
- package/src/ui/components/Surface/SurfaceDebug.tsx +435 -0
- package/src/ui/components/Surface/SurfaceManager.ts +95 -0
- package/src/ui/components/Surface/SurfaceManagerContext.ts +23 -0
- package/src/ui/components/Surface/SurfaceMetrics.ts +202 -0
- package/src/ui/components/Surface/SurfaceProfilerContext.tsx +12 -2
- package/src/ui/components/Surface/index.ts +24 -22
- package/src/ui/components/Surface/types.test.ts +47 -54
- package/src/ui/components/Surface/types.ts +66 -91
- package/src/ui/components/index.ts +2 -0
- package/src/ui/hooks/useApp.tsx +7 -3
- package/src/vite-plugin/boot-loader/loader-app/boot-loader.css +1 -1
- package/tsconfig.json +3 -0
- package/vite.config.ts +26 -0
- package/dist/lib/browser/capability-S44TE2H7.mjs +0 -35
- package/dist/lib/browser/capability-S44TE2H7.mjs.map +0 -7
- package/dist/lib/browser/chunk-3NQLTQCP.mjs +0 -47
- package/dist/lib/browser/chunk-3NQLTQCP.mjs.map +0 -7
- package/dist/lib/browser/chunk-53HJHMS5.mjs +0 -7
- package/dist/lib/browser/chunk-53HJHMS5.mjs.map +0 -7
- package/dist/lib/browser/chunk-FJ4765WW.mjs.map +0 -7
- package/dist/lib/browser/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/browser/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/browser/chunk-KRJ4KK2W.mjs +0 -505
- package/dist/lib/browser/chunk-KRJ4KK2W.mjs.map +0 -7
- package/dist/lib/browser/chunk-LUH4Y5F2.mjs +0 -430
- package/dist/lib/browser/chunk-LUH4Y5F2.mjs.map +0 -7
- package/dist/lib/browser/chunk-NKVRSMFN.mjs +0 -95
- package/dist/lib/browser/chunk-NKVRSMFN.mjs.map +0 -7
- package/dist/lib/browser/chunk-OO53M5UK.mjs +0 -260
- package/dist/lib/browser/chunk-OO53M5UK.mjs.map +0 -7
- package/dist/lib/browser/chunk-OPTS3ZSN.mjs +0 -83
- package/dist/lib/browser/chunk-OPTS3ZSN.mjs.map +0 -7
- package/dist/lib/browser/chunk-Q2GLJTVV.mjs +0 -12
- package/dist/lib/browser/chunk-Q2GLJTVV.mjs.map +0 -7
- package/dist/lib/browser/chunk-SYXIYT6T.mjs +0 -143
- package/dist/lib/browser/chunk-SYXIYT6T.mjs.map +0 -7
- package/dist/lib/browser/chunk-U5MESPM5.mjs +0 -603
- package/dist/lib/browser/chunk-U5MESPM5.mjs.map +0 -7
- package/dist/lib/browser/chunk-UGYCLOXE.mjs +0 -1471
- package/dist/lib/browser/chunk-UGYCLOXE.mjs.map +0 -7
- package/dist/lib/browser/chunk-VF37YTXJ.mjs +0 -28
- package/dist/lib/browser/chunk-VF37YTXJ.mjs.map +0 -7
- package/dist/lib/browser/chunk-YUVQIOTB.mjs +0 -60
- package/dist/lib/browser/chunk-YUVQIOTB.mjs.map +0 -7
- package/dist/lib/browser/cli/index.mjs +0 -74
- package/dist/lib/browser/cli/index.mjs.map +0 -7
- package/dist/lib/browser/common/activation-events.mjs +0 -20
- package/dist/lib/browser/common/activation-events.mjs.map +0 -7
- package/dist/lib/browser/common/capabilities.mjs +0 -56
- package/dist/lib/browser/common/capabilities.mjs.map +0 -7
- package/dist/lib/browser/config/index.mjs +0 -8
- package/dist/lib/browser/config/index.mjs.map +0 -7
- package/dist/lib/browser/core/activation-event.mjs +0 -20
- package/dist/lib/browser/core/activation-event.mjs.map +0 -7
- package/dist/lib/browser/core/capability.mjs +0 -32
- package/dist/lib/browser/core/capability.mjs.map +0 -7
- package/dist/lib/browser/core/plugin-manager.mjs +0 -19
- package/dist/lib/browser/core/plugin-manager.mjs.map +0 -7
- package/dist/lib/browser/core/plugin.mjs +0 -51
- package/dist/lib/browser/core/plugin.mjs.map +0 -7
- package/dist/lib/browser/core/url-loader.mjs +0 -24
- package/dist/lib/browser/core/url-loader.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -98
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/process-manager-capability-QK4BMKMN.mjs +0 -132
- package/dist/lib/browser/process-manager-capability-QK4BMKMN.mjs.map +0 -7
- package/dist/lib/browser/testing/index.mjs +0 -359
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/browser/testing/react.mjs +0 -78
- package/dist/lib/browser/testing/react.mjs.map +0 -7
- package/dist/lib/browser/ui/index.mjs +0 -60
- package/dist/lib/browser/ui/index.mjs.map +0 -7
- package/dist/lib/node-esm/capability-3YRF77LV.mjs +0 -36
- package/dist/lib/node-esm/capability-3YRF77LV.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs +0 -9
- package/dist/lib/node-esm/chunk-2UPUZXGU.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-37Z53PXZ.mjs +0 -10
- package/dist/lib/node-esm/chunk-37Z53PXZ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-4SE7SX7N.mjs +0 -261
- package/dist/lib/node-esm/chunk-4SE7SX7N.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-BMW6P6QA.mjs +0 -48
- package/dist/lib/node-esm/chunk-BMW6P6QA.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-COHOWGGJ.mjs +0 -144
- package/dist/lib/node-esm/chunk-COHOWGGJ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-DRT75WGD.mjs +0 -604
- package/dist/lib/node-esm/chunk-DRT75WGD.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FKMVORZN.mjs +0 -431
- package/dist/lib/node-esm/chunk-FKMVORZN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-FPW45EZH.mjs +0 -14
- package/dist/lib/node-esm/chunk-FPW45EZH.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-GLH4LGHZ.mjs +0 -84
- package/dist/lib/node-esm/chunk-GLH4LGHZ.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +0 -11
- package/dist/lib/node-esm/chunk-HSLMI22Q.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-IL7O7IRX.mjs +0 -1472
- package/dist/lib/node-esm/chunk-IL7O7IRX.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ND2HETHD.mjs +0 -506
- package/dist/lib/node-esm/chunk-ND2HETHD.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TLKYZKPW.mjs +0 -29
- package/dist/lib/node-esm/chunk-TLKYZKPW.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-VCPGX7WN.mjs +0 -62
- package/dist/lib/node-esm/chunk-VCPGX7WN.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-XSZMX53L.mjs +0 -96
- package/dist/lib/node-esm/chunk-XSZMX53L.mjs.map +0 -7
- package/dist/lib/node-esm/cli/index.mjs +0 -75
- package/dist/lib/node-esm/cli/index.mjs.map +0 -7
- package/dist/lib/node-esm/common/activation-events.mjs +0 -21
- package/dist/lib/node-esm/common/activation-events.mjs.map +0 -7
- package/dist/lib/node-esm/common/capabilities.mjs +0 -57
- package/dist/lib/node-esm/common/capabilities.mjs.map +0 -7
- package/dist/lib/node-esm/config/index.mjs +0 -9
- package/dist/lib/node-esm/config/index.mjs.map +0 -7
- package/dist/lib/node-esm/core/activation-event.mjs +0 -21
- package/dist/lib/node-esm/core/activation-event.mjs.map +0 -7
- package/dist/lib/node-esm/core/capability.mjs +0 -33
- package/dist/lib/node-esm/core/capability.mjs.map +0 -7
- package/dist/lib/node-esm/core/plugin-manager.mjs +0 -20
- package/dist/lib/node-esm/core/plugin-manager.mjs.map +0 -7
- package/dist/lib/node-esm/core/plugin.mjs +0 -52
- package/dist/lib/node-esm/core/plugin.mjs.map +0 -7
- package/dist/lib/node-esm/core/url-loader.mjs +0 -25
- package/dist/lib/node-esm/core/url-loader.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -99
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/process-manager-capability-5S7WZ2JR.mjs +0 -133
- package/dist/lib/node-esm/process-manager-capability-5S7WZ2JR.mjs.map +0 -7
- package/dist/lib/node-esm/testing/index.mjs +0 -360
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/testing/react.mjs +0 -79
- package/dist/lib/node-esm/testing/react.mjs.map +0 -7
- package/dist/lib/node-esm/ui/index.mjs +0 -61
- package/dist/lib/node-esm/ui/index.mjs.map +0 -7
- package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts +0 -11
- package/dist/types/src/ui/components/Surface/SurfaceInfo.d.ts.map +0 -1
- package/src/ui/components/Surface/SurfaceInfo.tsx +0 -106
- package/vitest.config.ts +0 -14
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
import { ReactSurface } from "./common/capabilities.mjs";
|
|
2
|
+
import { Position } from "@dxos/util";
|
|
3
|
+
import { Atom } from "@effect-atom/atom";
|
|
4
|
+
import { log } from "@dxos/log";
|
|
5
|
+
import { Fragment, Profiler, Suspense, createContext, createElement, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
import { ErrorBoundary } from "@dxos/react-error-boundary";
|
|
7
|
+
import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
|
|
8
|
+
import { raise } from "@dxos/debug";
|
|
9
|
+
import { useDefaultValue } from "@dxos/react-hooks";
|
|
10
|
+
import { RegistryContext, useAtomValue } from "@effect-atom/atom-react";
|
|
11
|
+
import { mx } from "@dxos/ui-theme";
|
|
12
|
+
import { createPortal } from "react-dom";
|
|
13
|
+
import { createRoot } from "react-dom/client";
|
|
14
|
+
import { addEventListener, combine } from "@dxos/async";
|
|
15
|
+
import * as Data from "effect/Data";
|
|
16
|
+
//#region src/helpers.ts
|
|
17
|
+
/**
|
|
18
|
+
* Topologically sorts a list of nodes based on their dependencies.
|
|
19
|
+
*/
|
|
20
|
+
var topologicalSort = (nodes) => {
|
|
21
|
+
const getDependencies = (nodeId, seen = /* @__PURE__ */ new Set(), path = /* @__PURE__ */ new Set()) => {
|
|
22
|
+
if (path.has(nodeId)) throw new Error(`Circular dependency detected involving ${nodeId}`);
|
|
23
|
+
if (seen.has(nodeId)) return [];
|
|
24
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
25
|
+
if (!node) throw new Error(`Node ${nodeId} not found but is listed as a dependency`);
|
|
26
|
+
const newPath = /* @__PURE__ */ new Set([...path, nodeId]);
|
|
27
|
+
const newSeen = /* @__PURE__ */ new Set([...seen, nodeId]);
|
|
28
|
+
return [...(node.dependsOn ?? []).flatMap((depId) => getDependencies(depId, newSeen, newPath)), nodeId];
|
|
29
|
+
};
|
|
30
|
+
return nodes.map((node) => node.id).flatMap((id) => getDependencies(id)).filter((id, index, self) => self.indexOf(id) === index).map((id) => nodes.find((node) => node.id === id)).filter((node) => node !== void 0);
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/ui/components/PluginManager/PluginManagerProvider.ts
|
|
34
|
+
var PluginManagerContext = createContext(void 0);
|
|
35
|
+
/**
|
|
36
|
+
* Get the plugin manager.
|
|
37
|
+
*/
|
|
38
|
+
var usePluginManager = () => useContext(PluginManagerContext) ?? raise(/* @__PURE__ */ new Error("Missing PluginManagerContext"));
|
|
39
|
+
/**
|
|
40
|
+
* Get the plugin manager if present, or `undefined` when rendered outside a {@link PluginManagerProvider}
|
|
41
|
+
* (e.g. a standalone component story). Lets components treat plugin capabilities as a progressive
|
|
42
|
+
* enhancement rather than a hard requirement.
|
|
43
|
+
*/
|
|
44
|
+
var useOptionalPluginManager = () => useContext(PluginManagerContext);
|
|
45
|
+
/**
|
|
46
|
+
* Context provider for a plugin manager.
|
|
47
|
+
*/
|
|
48
|
+
var PluginManagerProvider = PluginManagerContext.Provider;
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/ui/components/Surface/context.ts
|
|
51
|
+
var SurfaceContext = createContext(void 0);
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/ui/components/Surface/SurfaceMetrics.ts
|
|
54
|
+
/** Consecutive unstable renders before `dataUnstable` is flagged. */
|
|
55
|
+
var UNSTABLE_THRESHOLD = 3;
|
|
56
|
+
var surfaceMetricKey = (surfaceId, role) => `surface/${surfaceId}/${role}`;
|
|
57
|
+
/**
|
|
58
|
+
* Shallow (top-level) value equality. Used to distinguish a genuinely new `data`
|
|
59
|
+
* value from a new object/array carrying the same content (the unstable-prop case).
|
|
60
|
+
*/
|
|
61
|
+
var shallowEqual = (a, b) => {
|
|
62
|
+
if (Object.is(a, b)) return true;
|
|
63
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
|
|
64
|
+
const aKeys = Object.keys(a);
|
|
65
|
+
const bKeys = Object.keys(b);
|
|
66
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
67
|
+
return aKeys.every((key) => Object.is(a[key], b[key]));
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Singleton store of surface dev metrics. A module singleton (rather than a React
|
|
71
|
+
* context) so the debug overlay — which renders in its own root outside the app's
|
|
72
|
+
* provider tree — can read the same data as the in-app devtools panel.
|
|
73
|
+
*/
|
|
74
|
+
var SurfaceMetricsStore = class {
|
|
75
|
+
#metrics = /* @__PURE__ */ new Map();
|
|
76
|
+
#snapshot = [];
|
|
77
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
78
|
+
#pendingNotify = false;
|
|
79
|
+
#entry(surfaceId, role) {
|
|
80
|
+
const id = surfaceMetricKey(surfaceId, role);
|
|
81
|
+
let metric = this.#metrics.get(id);
|
|
82
|
+
if (!metric) {
|
|
83
|
+
metric = {
|
|
84
|
+
id,
|
|
85
|
+
surfaceId,
|
|
86
|
+
role,
|
|
87
|
+
dispatches: 0,
|
|
88
|
+
candidates: 0,
|
|
89
|
+
truncated: false,
|
|
90
|
+
dataUnstable: false,
|
|
91
|
+
dataChurn: 0,
|
|
92
|
+
errors: 0,
|
|
93
|
+
mounts: 0,
|
|
94
|
+
unmounts: 0
|
|
95
|
+
};
|
|
96
|
+
this.#metrics.set(id, metric);
|
|
97
|
+
}
|
|
98
|
+
return metric;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Records a dispatch. `previousData` is the consumer's prior `data` reference;
|
|
102
|
+
* the store derives churn/instability from the identity-vs-value comparison.
|
|
103
|
+
*/
|
|
104
|
+
recordDispatch(surfaceId, role, args) {
|
|
105
|
+
const metric = this.#entry(surfaceId, role);
|
|
106
|
+
metric.dispatches += 1;
|
|
107
|
+
metric.candidates = args.candidates;
|
|
108
|
+
metric.truncated = args.truncated;
|
|
109
|
+
metric.dataChurn = args.dataChurn;
|
|
110
|
+
metric.dataUnstable = args.dataChurn >= UNSTABLE_THRESHOLD;
|
|
111
|
+
this.#schedule();
|
|
112
|
+
}
|
|
113
|
+
recordError(surfaceId, role) {
|
|
114
|
+
this.#entry(surfaceId, role).errors += 1;
|
|
115
|
+
this.#schedule();
|
|
116
|
+
}
|
|
117
|
+
recordMount(surfaceId, role) {
|
|
118
|
+
this.#entry(surfaceId, role).mounts += 1;
|
|
119
|
+
this.#schedule();
|
|
120
|
+
}
|
|
121
|
+
recordUnmount(surfaceId, role) {
|
|
122
|
+
this.#entry(surfaceId, role).unmounts += 1;
|
|
123
|
+
this.#schedule();
|
|
124
|
+
}
|
|
125
|
+
clear() {
|
|
126
|
+
this.#metrics.clear();
|
|
127
|
+
this.#snapshot = [];
|
|
128
|
+
this.#notify();
|
|
129
|
+
}
|
|
130
|
+
subscribe = (listener) => {
|
|
131
|
+
this.#listeners.add(listener);
|
|
132
|
+
return () => {
|
|
133
|
+
this.#listeners.delete(listener);
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
getSnapshot = () => this.#snapshot;
|
|
137
|
+
/** Defers notification to the next frame to coalesce bursts of records. */
|
|
138
|
+
#schedule() {
|
|
139
|
+
if (this.#pendingNotify || typeof requestAnimationFrame === "undefined") {
|
|
140
|
+
if (typeof requestAnimationFrame === "undefined") this.#notify();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.#pendingNotify = true;
|
|
144
|
+
requestAnimationFrame(() => {
|
|
145
|
+
this.#pendingNotify = false;
|
|
146
|
+
this.#notify();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
#notify() {
|
|
150
|
+
this.#snapshot = [...this.#metrics.values()];
|
|
151
|
+
for (const listener of this.#listeners) listener();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var surfaceMetrics = new SurfaceMetricsStore();
|
|
155
|
+
/**
|
|
156
|
+
* Updates the running churn count for a consumer's `data` prop.
|
|
157
|
+
*
|
|
158
|
+
* @returns the new churn count (0 when `data` changed value or is unchanged).
|
|
159
|
+
*/
|
|
160
|
+
var nextDataChurn = (previous, next, churn) => {
|
|
161
|
+
if (Object.is(previous, next)) return churn;
|
|
162
|
+
return shallowEqual(previous, next) ? churn + 1 : 0;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Subscribes to surface dev metrics, sorted with the most concerning first
|
|
166
|
+
* (unstable data, then errors, then dispatch count).
|
|
167
|
+
*/
|
|
168
|
+
var useSurfaceMetrics = () => {
|
|
169
|
+
return [...useSyncExternalStore(surfaceMetrics.subscribe, surfaceMetrics.getSnapshot, surfaceMetrics.getSnapshot)].sort((a, b) => Number(b.dataUnstable) - Number(a.dataUnstable) || b.errors - a.errors || b.dispatches - a.dispatches);
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/ui/components/Surface/SurfaceDebug.tsx
|
|
173
|
+
var DEBUG_FLAG = "__DX_DEBUG__";
|
|
174
|
+
/**
|
|
175
|
+
* Custom element tag wrapping each React surface in debug builds.
|
|
176
|
+
*/
|
|
177
|
+
var DX_SURFACE_TAG = "dx-surface";
|
|
178
|
+
/**
|
|
179
|
+
* Installs the `window.__DX__` DevTools helper. `__DX__.surfaces(component?)` returns the mounted
|
|
180
|
+
* `<dx-surface>` elements (optionally filtered by `data-component`) — a console shortcut for the
|
|
181
|
+
* otherwise-verbose `document.querySelectorAll('dx-surface[data-component="…"]')`.
|
|
182
|
+
*/
|
|
183
|
+
var ensureDebugApi = () => {
|
|
184
|
+
if (typeof window === "undefined") return;
|
|
185
|
+
window.__DX__ = {
|
|
186
|
+
...window.__DX__,
|
|
187
|
+
surfaces: (component) => Array.from(document.querySelectorAll(component ? `${DX_SURFACE_TAG}[data-component="${component}"]` : DX_SURFACE_TAG))
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Whether surface debugging is enabled, via the `VITE_DEBUG` build flag or the
|
|
192
|
+
* `__DX_DEBUG__` runtime global. The runtime flag is toggleable without a
|
|
193
|
+
* rebuild and takes effect on the next render.
|
|
194
|
+
*/
|
|
195
|
+
var isSurfaceDebugEnabled = () => Boolean(void 0) || typeof window !== "undefined" && DEBUG_FLAG in window;
|
|
196
|
+
/**
|
|
197
|
+
* Whether the `<dx-surface>` wrapper is rendered. Enabled in any development build (or when
|
|
198
|
+
* `VITE_DEBUG` is set) so the wrapper's `data-*` attributes and `window.__DX__` helpers are available
|
|
199
|
+
* for DOM inspection independent of the runtime highlight flag. Production renders no wrapper.
|
|
200
|
+
*/
|
|
201
|
+
var isSurfaceWrapperEnabled = () => Boolean(false) || Boolean(void 0);
|
|
202
|
+
var flagListeners = /* @__PURE__ */ new Set();
|
|
203
|
+
var subscribeFlag = (listener) => {
|
|
204
|
+
flagListeners.add(listener);
|
|
205
|
+
return () => {
|
|
206
|
+
flagListeners.delete(listener);
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Toggles the `__DX_DEBUG__` runtime flag, which gates the visual highlight overlay (not the
|
|
211
|
+
* `<dx-surface>` wrapper). The overlay redraws immediately; surfaces pick it up on their next render.
|
|
212
|
+
*/
|
|
213
|
+
var setSurfaceDebug = (enabled) => {
|
|
214
|
+
if (typeof window === "undefined") return;
|
|
215
|
+
if (enabled) window[DEBUG_FLAG] = true;
|
|
216
|
+
else delete window[DEBUG_FLAG];
|
|
217
|
+
for (const listener of flagListeners) listener();
|
|
218
|
+
};
|
|
219
|
+
var elementRegistered = false;
|
|
220
|
+
var ensureSurfaceElement = () => {
|
|
221
|
+
if (elementRegistered || typeof customElements === "undefined") return;
|
|
222
|
+
elementRegistered = true;
|
|
223
|
+
if (!customElements.get("dx-surface")) {
|
|
224
|
+
class DxSurfaceElement extends HTMLElement {
|
|
225
|
+
connectedCallback() {
|
|
226
|
+
this.style.display = "contents";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
customElements.define(DX_SURFACE_TAG, DxSurfaceElement);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Singleton registry of mounted debug surfaces. Centralizes overlay rendering so
|
|
234
|
+
* a single set of observers/listeners serves every surface, instead of each
|
|
235
|
+
* surface mounting its own portal and observers.
|
|
236
|
+
*/
|
|
237
|
+
var SurfaceDebugManager = class {
|
|
238
|
+
#entries = /* @__PURE__ */ new Set();
|
|
239
|
+
#snapshot = [];
|
|
240
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
241
|
+
#nextKey = 0;
|
|
242
|
+
register(element, infoRef) {
|
|
243
|
+
const entry = {
|
|
244
|
+
key: this.#nextKey++,
|
|
245
|
+
element,
|
|
246
|
+
infoRef
|
|
247
|
+
};
|
|
248
|
+
this.#entries.add(entry);
|
|
249
|
+
this.#update();
|
|
250
|
+
return () => {
|
|
251
|
+
this.#entries.delete(entry);
|
|
252
|
+
this.#update();
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
subscribe = (listener) => {
|
|
256
|
+
this.#listeners.add(listener);
|
|
257
|
+
return () => {
|
|
258
|
+
this.#listeners.delete(listener);
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
getSnapshot = () => this.#snapshot;
|
|
262
|
+
#update() {
|
|
263
|
+
this.#snapshot = [...this.#entries];
|
|
264
|
+
for (const listener of this.#listeners) listener();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var manager = new SurfaceDebugManager();
|
|
268
|
+
var overlayMounted = false;
|
|
269
|
+
var ensureOverlay = () => {
|
|
270
|
+
if (overlayMounted || typeof document === "undefined") return;
|
|
271
|
+
overlayMounted = true;
|
|
272
|
+
const root = document.createElement("div");
|
|
273
|
+
root.id = "dx-surface-overlay";
|
|
274
|
+
document.body.appendChild(root);
|
|
275
|
+
createRoot(root).render(/* @__PURE__ */ jsx(SurfaceDebugOverlay, {}));
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Measures a `display: contents` element (which has no box of its own) by unioning the border boxes
|
|
279
|
+
* of its rendered children. Deliberately uses each child's own `getBoundingClientRect` rather than a
|
|
280
|
+
* Range over all contents: a Range unions every descendant rect, so content overflowing an inner
|
|
281
|
+
* `overflow: auto` scroll container inflates the width past the surface's visible bounds. A direct
|
|
282
|
+
* child's box is clipped to its own border box, so the highlight stays tight to what is on screen.
|
|
283
|
+
*/
|
|
284
|
+
var measureContents = (element) => {
|
|
285
|
+
if (typeof document === "undefined" || typeof getComputedStyle !== "function") return null;
|
|
286
|
+
const boxes = [];
|
|
287
|
+
const collect = (node) => {
|
|
288
|
+
for (const child of node.children) if (getComputedStyle(child).display === "contents") collect(child);
|
|
289
|
+
else {
|
|
290
|
+
const rect = child.getBoundingClientRect();
|
|
291
|
+
if (rect.width > 0 || rect.height > 0) boxes.push(rect);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
collect(element);
|
|
295
|
+
if (boxes.length === 0) return null;
|
|
296
|
+
let left = Infinity;
|
|
297
|
+
let top = Infinity;
|
|
298
|
+
let right = -Infinity;
|
|
299
|
+
let bottom = -Infinity;
|
|
300
|
+
for (const box of boxes) {
|
|
301
|
+
left = Math.min(left, box.left);
|
|
302
|
+
top = Math.min(top, box.top);
|
|
303
|
+
right = Math.max(right, box.right);
|
|
304
|
+
bottom = Math.max(bottom, box.bottom);
|
|
305
|
+
}
|
|
306
|
+
return new DOMRect(left, top, right - left, bottom - top);
|
|
307
|
+
};
|
|
308
|
+
var EMPTY_RECTS = /* @__PURE__ */ new Map();
|
|
309
|
+
/**
|
|
310
|
+
* Single overlay that draws boundary highlights for every registered surface.
|
|
311
|
+
* One ResizeObserver plus one scroll/resize listener serve all surfaces.
|
|
312
|
+
*/
|
|
313
|
+
var SurfaceDebugOverlay = () => {
|
|
314
|
+
const entries = useSyncExternalStore(manager.subscribe, manager.getSnapshot, manager.getSnapshot);
|
|
315
|
+
const enabled = useSyncExternalStore(subscribeFlag, isSurfaceDebugEnabled, isSurfaceDebugEnabled);
|
|
316
|
+
const [rects, setRects] = useState(EMPTY_RECTS);
|
|
317
|
+
useLayoutEffect(() => {
|
|
318
|
+
if (!enabled || entries.length === 0) {
|
|
319
|
+
setRects(EMPTY_RECTS);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const measure = () => {
|
|
323
|
+
const next = /* @__PURE__ */ new Map();
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
const rect = measureContents(entry.element);
|
|
326
|
+
if (rect) next.set(entry.key, rect);
|
|
327
|
+
}
|
|
328
|
+
setRects(next);
|
|
329
|
+
};
|
|
330
|
+
const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(measure) : void 0;
|
|
331
|
+
for (const entry of entries) if (observer && entry.element.parentElement) observer.observe(entry.element.parentElement);
|
|
332
|
+
measure();
|
|
333
|
+
return combine(addEventListener(window, "scroll", measure, true), addEventListener(window, "resize", measure), () => observer?.disconnect());
|
|
334
|
+
}, [entries, enabled]);
|
|
335
|
+
if (!enabled) return null;
|
|
336
|
+
return createPortal(/* @__PURE__ */ jsx(Fragment$1, { children: entries.map((entry) => {
|
|
337
|
+
const rect = rects.get(entry.key);
|
|
338
|
+
return rect ? /* @__PURE__ */ jsx(SurfaceHighlight, {
|
|
339
|
+
infoRef: entry.infoRef,
|
|
340
|
+
rect
|
|
341
|
+
}, entry.key) : null;
|
|
342
|
+
}) }), document.body);
|
|
343
|
+
};
|
|
344
|
+
/** Subscribes to the metric for a single surface. */
|
|
345
|
+
var useSurfaceMetric = (surfaceId, role) => {
|
|
346
|
+
const all = useSyncExternalStore(surfaceMetrics.subscribe, surfaceMetrics.getSnapshot, surfaceMetrics.getSnapshot);
|
|
347
|
+
const key = surfaceMetricKey(surfaceId, role);
|
|
348
|
+
return all.find((metric) => metric.id === key);
|
|
349
|
+
};
|
|
350
|
+
var SurfaceHighlight = ({ infoRef, rect }) => {
|
|
351
|
+
const [expand, setExpand] = useState(false);
|
|
352
|
+
const info = infoRef.current;
|
|
353
|
+
const metric = useSurfaceMetric(info.id ?? "", info.role);
|
|
354
|
+
const concern = !!metric && (metric.dataUnstable || metric.errors > 0);
|
|
355
|
+
return /* @__PURE__ */ jsx("div", {
|
|
356
|
+
className: "z-[100] fixed flex flex-col-reverse scrollbar-none overflow-auto pointer-events-none",
|
|
357
|
+
style: {
|
|
358
|
+
top: rect.top,
|
|
359
|
+
left: rect.left,
|
|
360
|
+
width: rect.width,
|
|
361
|
+
height: rect.height
|
|
362
|
+
},
|
|
363
|
+
children: expand ? /* @__PURE__ */ jsx("div", {
|
|
364
|
+
className: "absolute inset-0 border-2 border-rose-500 border-dotted overflow-auto pointer-events-auto",
|
|
365
|
+
onPointerDown: (ev) => ev.stopPropagation(),
|
|
366
|
+
onClick: (ev) => {
|
|
367
|
+
ev.stopPropagation();
|
|
368
|
+
setExpand(false);
|
|
369
|
+
},
|
|
370
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
371
|
+
className: "absolute left-1 right-1 bottom-1 max-h-[20rem] overflow-auto dx-card-surface ring-2 ring-separator rounded-sm opacity-90",
|
|
372
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
373
|
+
className: "inline-block p-2 text-xs text-description font-mono font-thin",
|
|
374
|
+
children: JSON.stringify({
|
|
375
|
+
info,
|
|
376
|
+
metric
|
|
377
|
+
}, null, 2)
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
381
|
+
className: mx(concern ? "text-rose-500" : "text-green-500", "absolute right-2 bottom-1 flex items-center p-1 opacity-80 hover:opacity-100 text-sm cursor-pointer pointer-events-auto"),
|
|
382
|
+
title: metricSummary(info.id ?? "", metric),
|
|
383
|
+
onPointerDown: (ev) => ev.stopPropagation(),
|
|
384
|
+
onClick: (ev) => {
|
|
385
|
+
ev.stopPropagation();
|
|
386
|
+
setExpand(true);
|
|
387
|
+
},
|
|
388
|
+
children: concern ? "⚠" : "ⓘ"
|
|
389
|
+
})
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
var metricSummary = (surfaceId, metric) => {
|
|
393
|
+
if (!metric) return surfaceId;
|
|
394
|
+
const parts = [
|
|
395
|
+
surfaceId,
|
|
396
|
+
`dispatches=${metric.dispatches}`,
|
|
397
|
+
`candidates=${metric.candidates}${metric.truncated ? "(+truncated)" : ""}`,
|
|
398
|
+
`mounts=${metric.mounts}/unmounts=${metric.unmounts}`
|
|
399
|
+
];
|
|
400
|
+
if (metric.dataUnstable) parts.push(`UNSTABLE data (churn=${metric.dataChurn})`);
|
|
401
|
+
if (metric.errors > 0) parts.push(`errors=${metric.errors}`);
|
|
402
|
+
return parts.join(" · ");
|
|
403
|
+
};
|
|
404
|
+
/**
|
|
405
|
+
* Recovers the rendered surface component's name from the React fiber. Surfaces are registered as
|
|
406
|
+
* anonymous `component: (props) => <Real .../>` wrappers, so the static component carries no useful
|
|
407
|
+
* name; walking the fiber's child chain to the first component with a display name (as DevTools does)
|
|
408
|
+
* finds the real one. The wrapper's own inferred name is the property key ('component'), so skip it.
|
|
409
|
+
*/
|
|
410
|
+
var renderedComponentName = (element) => {
|
|
411
|
+
const node = element;
|
|
412
|
+
const fiberKey = Object.keys(node).find((key) => key.startsWith("__reactFiber$"));
|
|
413
|
+
let fiber = fiberKey ? node[fiberKey]?.child : void 0;
|
|
414
|
+
while (fiber) {
|
|
415
|
+
const { type } = fiber;
|
|
416
|
+
if (typeof type === "function") {
|
|
417
|
+
const name = type.displayName ?? type.name;
|
|
418
|
+
if (name && name !== "component") return name;
|
|
419
|
+
}
|
|
420
|
+
fiber = fiber.child;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* Debug wrapper that mounts each React surface inside a `<dx-surface>` element
|
|
425
|
+
* and registers it with the centralized overlay. The element is named and
|
|
426
|
+
* inspectable in DevTools and queryable (`document.querySelectorAll('dx-surface')`).
|
|
427
|
+
*/
|
|
428
|
+
var DebugSurface = ({ info, children }) => {
|
|
429
|
+
const infoRef = useRef(info);
|
|
430
|
+
infoRef.current = info;
|
|
431
|
+
const elementRef = useRef(null);
|
|
432
|
+
useEffect(() => {
|
|
433
|
+
ensureSurfaceElement();
|
|
434
|
+
ensureOverlay();
|
|
435
|
+
ensureDebugApi();
|
|
436
|
+
const { id, role } = infoRef.current;
|
|
437
|
+
if (id) surfaceMetrics.recordMount(id, role);
|
|
438
|
+
const element = elementRef.current;
|
|
439
|
+
const unregister = element ? manager.register(element, infoRef) : void 0;
|
|
440
|
+
const component = element ? renderedComponentName(element) : void 0;
|
|
441
|
+
if (element && component) element.setAttribute("data-component", component);
|
|
442
|
+
return () => {
|
|
443
|
+
if (id) surfaceMetrics.recordUnmount(id, role);
|
|
444
|
+
unregister?.();
|
|
445
|
+
};
|
|
446
|
+
}, []);
|
|
447
|
+
return createElement(DX_SURFACE_TAG, {
|
|
448
|
+
"className": "contents",
|
|
449
|
+
"data-id": info.id,
|
|
450
|
+
"data-role": info.role,
|
|
451
|
+
"ref": elementRef
|
|
452
|
+
}, children);
|
|
453
|
+
};
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region src/ui/components/Surface/SurfaceManagerContext.ts
|
|
456
|
+
var SurfaceManagerContext = createContext(void 0);
|
|
457
|
+
/**
|
|
458
|
+
* Get the surface manager. Throws when rendered outside a {@link SurfaceManagerProvider};
|
|
459
|
+
* surfaces already hard-require the plugin-manager context, so there is no optional variant.
|
|
460
|
+
*/
|
|
461
|
+
var useSurfaceManager = () => useContext(SurfaceManagerContext) ?? raise(/* @__PURE__ */ new Error("Missing SurfaceManagerContext"));
|
|
462
|
+
/**
|
|
463
|
+
* Context provider for a surface manager.
|
|
464
|
+
*/
|
|
465
|
+
var SurfaceManagerProvider = SurfaceManagerContext.Provider;
|
|
466
|
+
//#endregion
|
|
467
|
+
//#region src/ui/components/Surface/SurfaceProfilerContext.tsx
|
|
468
|
+
var MAX_ENTRIES = 500;
|
|
469
|
+
/**
|
|
470
|
+
* Store that collects profiler entries and notifies subscribers.
|
|
471
|
+
*/
|
|
472
|
+
var SurfaceProfilerStore = class {
|
|
473
|
+
_entries = [];
|
|
474
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
475
|
+
_snapshot = [];
|
|
476
|
+
_pendingNotify = false;
|
|
477
|
+
/**
|
|
478
|
+
* Records an entry and schedules a deferred notification to avoid re-render loops.
|
|
479
|
+
* `_snapshot` is rebuilt only when the deferred notification actually fires
|
|
480
|
+
* ({@link _notifySync}) — not here — so `getSnapshot` stays referentially stable for any
|
|
481
|
+
* synchronous re-invocation React makes within the same commit (e.g. the tearing check a
|
|
482
|
+
* profiled subscriber's own `useSyncExternalStore` runs right after this Profiler's
|
|
483
|
+
* `onRender` callback). Rebuilding it synchronously here would change what `getSnapshot`
|
|
484
|
+
* returns before listeners are told, which React reads as a torn store and forces an
|
|
485
|
+
* immediate re-render — and if the re-rendering component is itself profiled, that
|
|
486
|
+
* re-render re-triggers `record`, looping forever.
|
|
487
|
+
*/
|
|
488
|
+
record(entry) {
|
|
489
|
+
this._entries.push(entry);
|
|
490
|
+
if (this._entries.length > MAX_ENTRIES) this._entries = this._entries.slice(-500);
|
|
491
|
+
this._scheduleNotify();
|
|
492
|
+
}
|
|
493
|
+
clear() {
|
|
494
|
+
this._entries = [];
|
|
495
|
+
this._snapshot = [];
|
|
496
|
+
this._notifySync();
|
|
497
|
+
}
|
|
498
|
+
subscribe = (listener) => {
|
|
499
|
+
this._listeners.add(listener);
|
|
500
|
+
return () => {
|
|
501
|
+
this._listeners.delete(listener);
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
getSnapshot = () => {
|
|
505
|
+
return this._snapshot;
|
|
506
|
+
};
|
|
507
|
+
/**
|
|
508
|
+
* Defers notification to the next animation frame to break the
|
|
509
|
+
* Profiler onRender → record → notify → re-render → onRender loop.
|
|
510
|
+
*/
|
|
511
|
+
_scheduleNotify() {
|
|
512
|
+
if (!this._pendingNotify) {
|
|
513
|
+
this._pendingNotify = true;
|
|
514
|
+
requestAnimationFrame(() => {
|
|
515
|
+
this._pendingNotify = false;
|
|
516
|
+
this._notifySync();
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
_notifySync() {
|
|
521
|
+
this._snapshot = [...this._entries];
|
|
522
|
+
for (const listener of this._listeners) listener();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
var SurfaceProfilerContext = createContext(void 0);
|
|
526
|
+
/**
|
|
527
|
+
* Provider that collects React Profiler data from Surface components.
|
|
528
|
+
*/
|
|
529
|
+
var SurfaceProfilerProvider = ({ children }) => {
|
|
530
|
+
const storeRef = useRef(null);
|
|
531
|
+
if (!storeRef.current) storeRef.current = new SurfaceProfilerStore();
|
|
532
|
+
return /* @__PURE__ */ jsx(SurfaceProfilerContext.Provider, {
|
|
533
|
+
value: { store: storeRef.current },
|
|
534
|
+
children
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Returns a stable onRender callback for use with React Profiler.
|
|
539
|
+
*/
|
|
540
|
+
var useSurfaceProfilerCallback = () => {
|
|
541
|
+
const store = useContext(SurfaceProfilerContext)?.store;
|
|
542
|
+
return useMemo(() => {
|
|
543
|
+
if (!store) return;
|
|
544
|
+
return (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
|
545
|
+
store.record({
|
|
546
|
+
id,
|
|
547
|
+
phase,
|
|
548
|
+
actualDuration,
|
|
549
|
+
baseDuration,
|
|
550
|
+
startTime,
|
|
551
|
+
commitTime,
|
|
552
|
+
timestamp: Date.now()
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
}, [store]);
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* Returns all profiler entries reactively.
|
|
559
|
+
*/
|
|
560
|
+
var useSurfaceProfilerEntries = () => {
|
|
561
|
+
const context = useContext(SurfaceProfilerContext);
|
|
562
|
+
return useSyncExternalStore(context?.store.subscribe ?? noop, context?.store.getSnapshot ?? emptySnapshot);
|
|
563
|
+
};
|
|
564
|
+
/**
|
|
565
|
+
* Returns aggregated stats grouped by surface id.
|
|
566
|
+
*/
|
|
567
|
+
var useSurfaceProfilerStats = () => {
|
|
568
|
+
const entries = useSurfaceProfilerEntries();
|
|
569
|
+
const statsMap = /* @__PURE__ */ new Map();
|
|
570
|
+
for (const entry of entries) {
|
|
571
|
+
let stats = statsMap.get(entry.id);
|
|
572
|
+
if (!stats) {
|
|
573
|
+
stats = {
|
|
574
|
+
id: entry.id,
|
|
575
|
+
mountCount: 0,
|
|
576
|
+
updateCount: 0,
|
|
577
|
+
totalRenders: 0,
|
|
578
|
+
avgActualDuration: 0,
|
|
579
|
+
maxActualDuration: 0,
|
|
580
|
+
avgBaseDuration: 0,
|
|
581
|
+
lastActualDuration: 0,
|
|
582
|
+
lastCommitTime: 0
|
|
583
|
+
};
|
|
584
|
+
statsMap.set(entry.id, stats);
|
|
585
|
+
}
|
|
586
|
+
if (entry.phase === "mount") stats.mountCount++;
|
|
587
|
+
else stats.updateCount++;
|
|
588
|
+
stats.totalRenders++;
|
|
589
|
+
stats.avgActualDuration = (stats.avgActualDuration * (stats.totalRenders - 1) + entry.actualDuration) / stats.totalRenders;
|
|
590
|
+
stats.avgBaseDuration = (stats.avgBaseDuration * (stats.totalRenders - 1) + entry.baseDuration) / stats.totalRenders;
|
|
591
|
+
stats.maxActualDuration = Math.max(stats.maxActualDuration, entry.actualDuration);
|
|
592
|
+
stats.lastActualDuration = entry.actualDuration;
|
|
593
|
+
stats.lastCommitTime = entry.commitTime;
|
|
594
|
+
}
|
|
595
|
+
return [...statsMap.values()].sort((a, b) => b.maxActualDuration - a.maxActualDuration);
|
|
596
|
+
};
|
|
597
|
+
/**
|
|
598
|
+
* Clears all collected profiler entries.
|
|
599
|
+
*/
|
|
600
|
+
var useSurfaceProfilerClear = () => {
|
|
601
|
+
const store = useContext(SurfaceProfilerContext)?.store;
|
|
602
|
+
return useMemo(() => store ? () => store.clear() : void 0, [store]);
|
|
603
|
+
};
|
|
604
|
+
var noop = () => () => {};
|
|
605
|
+
var EMPTY_SNAPSHOT = [];
|
|
606
|
+
var emptySnapshot = () => EMPTY_SNAPSHOT;
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/ui/components/Surface/SurfaceComponent.tsx
|
|
609
|
+
var DEFAULT_PLACEHOLDER = /* @__PURE__ */ jsx(Fragment, {});
|
|
610
|
+
var WebComponentWrapper = memo(({ id, role, data, limit, definition, ...rest }) => {
|
|
611
|
+
const containerRef = useRef(null);
|
|
612
|
+
const elementRef = useRef(null);
|
|
613
|
+
const propsRef = useRef({
|
|
614
|
+
id,
|
|
615
|
+
role,
|
|
616
|
+
data,
|
|
617
|
+
limit,
|
|
618
|
+
...rest
|
|
619
|
+
});
|
|
620
|
+
propsRef.current = {
|
|
621
|
+
id,
|
|
622
|
+
role,
|
|
623
|
+
data,
|
|
624
|
+
limit,
|
|
625
|
+
...rest
|
|
626
|
+
};
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
if (!containerRef.current || elementRef.current) return;
|
|
629
|
+
const element = document.createElement(definition.tagName);
|
|
630
|
+
elementRef.current = element;
|
|
631
|
+
Object.assign(element, propsRef.current);
|
|
632
|
+
containerRef.current.appendChild(element);
|
|
633
|
+
return () => {
|
|
634
|
+
if (elementRef.current && containerRef.current?.contains(elementRef.current)) containerRef.current.removeChild(elementRef.current);
|
|
635
|
+
elementRef.current = null;
|
|
636
|
+
};
|
|
637
|
+
}, [definition.tagName]);
|
|
638
|
+
const previousPropsRef = useRef({});
|
|
639
|
+
useEffect(() => {
|
|
640
|
+
const element = elementRef.current;
|
|
641
|
+
if (element) {
|
|
642
|
+
for (const key of Object.keys(previousPropsRef.current)) if (!(key in propsRef.current)) Reflect.deleteProperty(element, key);
|
|
643
|
+
Object.assign(element, propsRef.current);
|
|
644
|
+
previousPropsRef.current = propsRef.current;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef });
|
|
648
|
+
});
|
|
649
|
+
WebComponentWrapper.displayName = "WebComponentWrapper";
|
|
650
|
+
/**
|
|
651
|
+
* Wrapper component that provides context for a surface.
|
|
652
|
+
*/
|
|
653
|
+
var SurfaceContextProvider = memo(({ id, role, data, limit, fallback = ErrorFallback$1, definition, ...rest }) => {
|
|
654
|
+
const contextValue = useMemo(() => ({
|
|
655
|
+
id,
|
|
656
|
+
role,
|
|
657
|
+
data
|
|
658
|
+
}), [
|
|
659
|
+
id,
|
|
660
|
+
role,
|
|
661
|
+
data
|
|
662
|
+
]);
|
|
663
|
+
const onProfilerRender = useSurfaceProfilerCallback();
|
|
664
|
+
const profilerId = `surface/${id}/${role}`;
|
|
665
|
+
const onError = isSurfaceDebugEnabled() ? () => surfaceMetrics.recordError(id, role) : void 0;
|
|
666
|
+
if (definition.kind === "web-component") return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
667
|
+
name: "surface",
|
|
668
|
+
resetKeys: [data],
|
|
669
|
+
FallbackComponent: fallback,
|
|
670
|
+
onError,
|
|
671
|
+
children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
|
|
672
|
+
value: contextValue,
|
|
673
|
+
children: /* @__PURE__ */ jsx(WebComponentWrapper, {
|
|
674
|
+
id,
|
|
675
|
+
role,
|
|
676
|
+
data,
|
|
677
|
+
limit,
|
|
678
|
+
definition,
|
|
679
|
+
...rest
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
});
|
|
683
|
+
const Component = definition.component;
|
|
684
|
+
const surfaceProps = {
|
|
685
|
+
id,
|
|
686
|
+
role,
|
|
687
|
+
data,
|
|
688
|
+
limit,
|
|
689
|
+
...rest
|
|
690
|
+
};
|
|
691
|
+
const component = /* @__PURE__ */ jsx(Component, { ...definition.props?.(surfaceProps) ?? surfaceProps });
|
|
692
|
+
const profiled = onProfilerRender && !profilerId.includes("org.dxos.plugin.debug") ? /* @__PURE__ */ jsx(Profiler, {
|
|
693
|
+
id: profilerId,
|
|
694
|
+
onRender: onProfilerRender,
|
|
695
|
+
children: component
|
|
696
|
+
}) : component;
|
|
697
|
+
if (isSurfaceWrapperEnabled()) return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
698
|
+
name: "surface",
|
|
699
|
+
resetKeys: [data],
|
|
700
|
+
FallbackComponent: fallback,
|
|
701
|
+
onError,
|
|
702
|
+
children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
|
|
703
|
+
value: contextValue,
|
|
704
|
+
children: /* @__PURE__ */ jsx(DebugSurface, {
|
|
705
|
+
info: contextValue,
|
|
706
|
+
children: profiled
|
|
707
|
+
})
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
return /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
711
|
+
name: "surface",
|
|
712
|
+
resetKeys: [data],
|
|
713
|
+
FallbackComponent: fallback,
|
|
714
|
+
onError,
|
|
715
|
+
children: /* @__PURE__ */ jsx(SurfaceContext.Provider, {
|
|
716
|
+
value: contextValue,
|
|
717
|
+
children: profiled
|
|
718
|
+
})
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
SurfaceContextProvider.displayName = "SurfaceContextProvider";
|
|
722
|
+
/**
|
|
723
|
+
* A surface is a named region of the screen that can be populated by plugins.
|
|
724
|
+
* The `type` prop is a {@link Role.Role} that defines which region and its
|
|
725
|
+
* associated data contract.
|
|
726
|
+
*
|
|
727
|
+
* A surface is a boundary that may resolve to zero, one, or many components, so
|
|
728
|
+
* it intentionally accepts no `ref`; consumers needing an element should own one
|
|
729
|
+
* inside their contributed component.
|
|
730
|
+
*/
|
|
731
|
+
var SurfaceComponent = memo(({ id: _id, type, data: dataProp, limit, placeholder = DEFAULT_PLACEHOLDER, ...rest }) => {
|
|
732
|
+
const data = useDefaultValue(dataProp, () => ({}));
|
|
733
|
+
const surfaceManager = useSurfaceManager();
|
|
734
|
+
const effectiveRole = type?.role ?? "";
|
|
735
|
+
const definitions = matchCandidates(useAtomValue(surfaceManager.candidatesAtom(effectiveRole)), effectiveRole, data);
|
|
736
|
+
const candidates = limit != null ? definitions.slice(0, limit) : definitions;
|
|
737
|
+
const truncated = limit != null && definitions.length > limit;
|
|
738
|
+
const churnRef = useRef({
|
|
739
|
+
data: void 0,
|
|
740
|
+
churn: 0
|
|
741
|
+
});
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
if (!isSurfaceDebugEnabled() || effectiveRole === "") return;
|
|
744
|
+
const previous = churnRef.current;
|
|
745
|
+
const churn = previous.data === void 0 ? 0 : nextDataChurn(previous.data, data, previous.churn);
|
|
746
|
+
churnRef.current = {
|
|
747
|
+
data,
|
|
748
|
+
churn
|
|
749
|
+
};
|
|
750
|
+
for (const definition of candidates) surfaceMetrics.recordDispatch(definition.id, effectiveRole, {
|
|
751
|
+
candidates: candidates.length,
|
|
752
|
+
truncated,
|
|
753
|
+
dataChurn: churn
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
if (type?.role == null) return null;
|
|
757
|
+
return /* @__PURE__ */ jsx(Suspense, {
|
|
758
|
+
fallback: placeholder,
|
|
759
|
+
children: candidates.map((definition) => /* @__PURE__ */ jsx(SurfaceContextProvider, {
|
|
760
|
+
id: definition.id,
|
|
761
|
+
role: effectiveRole,
|
|
762
|
+
data,
|
|
763
|
+
limit,
|
|
764
|
+
definition,
|
|
765
|
+
...rest
|
|
766
|
+
}, definition.id))
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
SurfaceComponent.displayName = "Surface";
|
|
770
|
+
var ErrorFallback$1 = ({ error }) => {
|
|
771
|
+
const { message } = error instanceof Error ? error : { message: String(error) };
|
|
772
|
+
return /* @__PURE__ */ jsx("div", {
|
|
773
|
+
role: "alert",
|
|
774
|
+
"data-testid": "error-boundary-fallback",
|
|
775
|
+
children: /* @__PURE__ */ jsx("h1", {
|
|
776
|
+
className: "flex p-2 text-sm text-info-text",
|
|
777
|
+
children: message
|
|
778
|
+
})
|
|
779
|
+
});
|
|
780
|
+
};
|
|
781
|
+
/**
|
|
782
|
+
* Filters the pre-indexed candidates for a role through their data guards.
|
|
783
|
+
*/
|
|
784
|
+
var matchCandidates = (definitions, role, data) => {
|
|
785
|
+
if (!definitions) return [];
|
|
786
|
+
return definitions.filter(({ filter }) => filter ? filter(data ?? {}, role) : true);
|
|
787
|
+
};
|
|
788
|
+
/**
|
|
789
|
+
* @returns a stable function that checks whether a contributed surface is available for a
|
|
790
|
+
* role & data. The surface manager is captured via context, so the returned function carries
|
|
791
|
+
* no dependency of its own and is safe to store and invoke later — e.g. from inside another
|
|
792
|
+
* callback such as a render-prop or event handler — since it calls no hooks itself.
|
|
793
|
+
*/
|
|
794
|
+
var useIsSurfaceAvailable = () => {
|
|
795
|
+
const surfaceManager = useSurfaceManager();
|
|
796
|
+
const registry = useContext(RegistryContext);
|
|
797
|
+
return useCallback((args) => {
|
|
798
|
+
const effectiveRole = args.type?.role;
|
|
799
|
+
if (effectiveRole == null) return false;
|
|
800
|
+
return matchCandidates(registry.get(surfaceManager.candidatesAtom(effectiveRole)), effectiveRole, args.data).length > 0;
|
|
801
|
+
}, [surfaceManager, registry]);
|
|
802
|
+
};
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/ui/components/Surface/types.ts
|
|
805
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/sdk/app-framework/src/ui/components/Surface/types.ts";
|
|
806
|
+
/**
|
|
807
|
+
* Runtime guard for {@link Filter}. Distinguishes new-style filter bindings from
|
|
808
|
+
* legacy predicate filters.
|
|
809
|
+
*/
|
|
810
|
+
var isFilter = (value) => typeof value === "object" && value !== null && Array.isArray(value.bindings);
|
|
811
|
+
/**
|
|
812
|
+
* Creates a {@link Filter} from a role token and an optional guard.
|
|
813
|
+
*
|
|
814
|
+
* When `guard` is omitted the filter matches any data at the token's role
|
|
815
|
+
* (role-only dispatch). Pass a guard to add runtime data-shape validation on
|
|
816
|
+
* top of the role match.
|
|
817
|
+
*
|
|
818
|
+
* This is the framework-level primitive; `@dxos/app-toolkit` builds richer
|
|
819
|
+
* domain-aware helpers (ECHO schema checks, literal matching, etc.) on top of it.
|
|
820
|
+
*/
|
|
821
|
+
var makeFilter = (token, guard) => {
|
|
822
|
+
const boundGuard = guard == null ? () => true : (data) => {
|
|
823
|
+
try {
|
|
824
|
+
return guard(data);
|
|
825
|
+
} catch (err) {
|
|
826
|
+
log.catch(err, void 0, {
|
|
827
|
+
"~LogMeta": "~LogMeta",
|
|
828
|
+
F: __dxlog_file$1,
|
|
829
|
+
L: 57,
|
|
830
|
+
S: void 0
|
|
831
|
+
});
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
return { bindings: [{
|
|
836
|
+
role: token.role,
|
|
837
|
+
guard: boundGuard
|
|
838
|
+
}] };
|
|
839
|
+
};
|
|
840
|
+
var expandBindings = (filter) => {
|
|
841
|
+
const bindings = filter.bindings;
|
|
842
|
+
const roles = Array.from(new Set(bindings.map((binding) => binding.role)));
|
|
843
|
+
const guard = (data, role) => {
|
|
844
|
+
if (role != null) return bindings.some((entry) => entry.role === role && entry.guard(data));
|
|
845
|
+
return bindings.some((entry) => entry.guard(data));
|
|
846
|
+
};
|
|
847
|
+
return {
|
|
848
|
+
role: roles.length === 1 ? roles[0] : roles,
|
|
849
|
+
guard
|
|
850
|
+
};
|
|
851
|
+
};
|
|
852
|
+
/**
|
|
853
|
+
* Whether a surface or extension local ID follows NSID conventions: the final
|
|
854
|
+
* dot-separated segment must be camelCase (letters and digits only, starting
|
|
855
|
+
* with a letter — no hyphens or underscores). This mirrors the rule enforced
|
|
856
|
+
* when the id is appended to a plugin's NSID to form a full DXN path.
|
|
857
|
+
*
|
|
858
|
+
* A definition with an invalid id is dropped at dispatch rather than rejected
|
|
859
|
+
* here, so a single malformed contribution cannot crash plugin activation.
|
|
860
|
+
*
|
|
861
|
+
* @example Valid: 'about', 'integrationArticle', 'article.journal'
|
|
862
|
+
* @example Invalid: 'integration-article', 'plugin-spec'
|
|
863
|
+
*/
|
|
864
|
+
var isValidLocalId = (id) => /^[a-zA-Z][a-zA-Z0-9]*$/.test(id.split(".").pop() ?? "");
|
|
865
|
+
function create(definition) {
|
|
866
|
+
const { id, filter, component, props, position } = definition;
|
|
867
|
+
const { role, guard } = expandBindings(filter);
|
|
868
|
+
return {
|
|
869
|
+
kind: "react",
|
|
870
|
+
id,
|
|
871
|
+
role,
|
|
872
|
+
position,
|
|
873
|
+
component,
|
|
874
|
+
props,
|
|
875
|
+
filter: guard
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function createWeb(definition) {
|
|
879
|
+
const { id, filter, tagName, position } = definition;
|
|
880
|
+
const { role, guard } = expandBindings(filter);
|
|
881
|
+
return {
|
|
882
|
+
kind: "web-component",
|
|
883
|
+
id,
|
|
884
|
+
role,
|
|
885
|
+
position,
|
|
886
|
+
tagName,
|
|
887
|
+
filter: guard
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/ui/components/Surface/SurfaceManager.ts
|
|
892
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-framework/src/ui/components/Surface/SurfaceManager.ts";
|
|
893
|
+
var EMPTY_CANDIDATES = Data.array([]);
|
|
894
|
+
/**
|
|
895
|
+
* Groups definitions by role with each bucket pre-sorted by {@link Position}, so
|
|
896
|
+
* dispatch avoids a full scan and re-sort on every render. Pure helper — callers
|
|
897
|
+
* are responsible for filtering out invalid definitions before calling this.
|
|
898
|
+
*/
|
|
899
|
+
var indexByRole = (definitions) => {
|
|
900
|
+
const index = /* @__PURE__ */ new Map();
|
|
901
|
+
for (const definition of definitions) {
|
|
902
|
+
const roles = Array.isArray(definition.role) ? definition.role : [definition.role];
|
|
903
|
+
for (const role of roles) {
|
|
904
|
+
let bucket = index.get(role);
|
|
905
|
+
if (!bucket) {
|
|
906
|
+
bucket = [];
|
|
907
|
+
index.set(role, bucket);
|
|
908
|
+
}
|
|
909
|
+
bucket.push(definition);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
for (const bucket of index.values()) bucket.sort(Position.compare);
|
|
913
|
+
return index;
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Owns the per-manager surface memoization: one derived index atom plus a per-role
|
|
917
|
+
* family of candidate atoms. A single instance is provided via
|
|
918
|
+
* {@link SurfaceManagerProvider}, so instance identity does the per-manager keying
|
|
919
|
+
* (replacing module-level WeakMaps) and `Atom.family` does the per-role keying.
|
|
920
|
+
* Atom lifecycle is tied to the provider rather than module-global state.
|
|
921
|
+
*/
|
|
922
|
+
var SurfaceManager = class {
|
|
923
|
+
#capabilities;
|
|
924
|
+
#index = Atom.make((get) => {
|
|
925
|
+
const definitions = get(this.#capabilities.atom(ReactSurface)).flat();
|
|
926
|
+
return indexByRole(this.#dropInvalid(definitions));
|
|
927
|
+
}).pipe(Atom.keepAlive);
|
|
928
|
+
#candidates = Atom.family((role) => Atom.make((get) => {
|
|
929
|
+
const bucket = get(this.#index).get(role);
|
|
930
|
+
return bucket ? Data.array(bucket) : EMPTY_CANDIDATES;
|
|
931
|
+
}).pipe(Atom.keepAlive));
|
|
932
|
+
#warnedInvalidIds = /* @__PURE__ */ new Set();
|
|
933
|
+
constructor(capabilities) {
|
|
934
|
+
this.#capabilities = capabilities;
|
|
935
|
+
}
|
|
936
|
+
/** Derived atom yielding the (position-sorted) candidates for a single role. */
|
|
937
|
+
candidatesAtom(role) {
|
|
938
|
+
return this.#candidates(role);
|
|
939
|
+
}
|
|
940
|
+
/** Drops definitions with an invalid local id, warning once per id. */
|
|
941
|
+
#dropInvalid(definitions) {
|
|
942
|
+
return definitions.filter((definition) => {
|
|
943
|
+
if (isValidLocalId(definition.id)) return true;
|
|
944
|
+
if (!this.#warnedInvalidIds.has(definition.id)) {
|
|
945
|
+
this.#warnedInvalidIds.add(definition.id);
|
|
946
|
+
log.warn("dropping surface with invalid id; the final segment must be camelCase (no hyphens or underscores)", { id: definition.id }, {
|
|
947
|
+
"~LogMeta": "~LogMeta",
|
|
948
|
+
F: __dxlog_file,
|
|
949
|
+
L: 88,
|
|
950
|
+
S: this
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return false;
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
//#endregion
|
|
958
|
+
export { topologicalSort as C, usePluginManager as S, surfaceMetrics as _, makeFilter as a, PluginManagerProvider as b, SurfaceProfilerProvider as c, useSurfaceProfilerEntries as d, useSurfaceProfilerStats as f, setSurfaceDebug as g, isSurfaceDebugEnabled as h, isFilter as i, useSurfaceProfilerCallback as l, useSurfaceManager as m, create as n, SurfaceComponent as o, SurfaceManagerProvider as p, createWeb as r, useIsSurfaceAvailable as s, SurfaceManager as t, useSurfaceProfilerClear as u, useSurfaceMetrics as v, useOptionalPluginManager as x, SurfaceContext as y };
|
|
959
|
+
|
|
960
|
+
//# sourceMappingURL=chunk-SurfaceManager.mjs.map
|