@backstage/frontend-plugin-api 0.11.0-next.1 → 0.11.0-next.2

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/dist/analytics/useAnalytics.esm.js +1 -1
  3. package/dist/apis/definitions/AppTreeApi.esm.js.map +1 -1
  4. package/dist/apis/definitions/DialogApi.esm.js.map +1 -1
  5. package/dist/apis/definitions/SwappableComponentsApi.esm.js +8 -0
  6. package/dist/apis/definitions/SwappableComponentsApi.esm.js.map +1 -0
  7. package/dist/blueprints/PageBlueprint.esm.js +10 -0
  8. package/dist/blueprints/PageBlueprint.esm.js.map +1 -1
  9. package/dist/blueprints/SignInPageBlueprint.esm.js +8 -0
  10. package/dist/blueprints/SignInPageBlueprint.esm.js.map +1 -1
  11. package/dist/blueprints/SwappableComponentBlueprint.esm.js +27 -0
  12. package/dist/blueprints/SwappableComponentBlueprint.esm.js.map +1 -0
  13. package/dist/components/DefaultSwappableComponents.esm.js +16 -0
  14. package/dist/components/DefaultSwappableComponents.esm.js.map +1 -0
  15. package/dist/components/ErrorBoundary.esm.js +3 -2
  16. package/dist/components/ErrorBoundary.esm.js.map +1 -1
  17. package/dist/components/ExtensionBoundary.esm.js +2 -10
  18. package/dist/components/ExtensionBoundary.esm.js.map +1 -1
  19. package/dist/components/createSwappableComponent.esm.js +52 -0
  20. package/dist/components/createSwappableComponent.esm.js.map +1 -0
  21. package/dist/frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js +9 -0
  22. package/dist/frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js.map +1 -0
  23. package/dist/frontend-internal/src/wiring/createExtensionDataContainer.esm.js +4 -1
  24. package/dist/frontend-internal/src/wiring/createExtensionDataContainer.esm.js.map +1 -1
  25. package/dist/index.d.ts +244 -86
  26. package/dist/index.esm.js +4 -4
  27. package/dist/routing/useRouteRef.esm.js +1 -1
  28. package/dist/wiring/createExtension.esm.js +7 -0
  29. package/dist/wiring/createExtension.esm.js.map +1 -1
  30. package/dist/wiring/createExtensionBlueprint.esm.js +1 -0
  31. package/dist/wiring/createExtensionBlueprint.esm.js.map +1 -1
  32. package/dist/wiring/createFrontendModule.esm.js.map +1 -1
  33. package/dist/wiring/createFrontendPlugin.esm.js +2 -6
  34. package/dist/wiring/createFrontendPlugin.esm.js.map +1 -1
  35. package/dist/wiring/resolveInputOverrides.esm.js +3 -0
  36. package/dist/wiring/resolveInputOverrides.esm.js.map +1 -1
  37. package/package.json +4 -4
  38. package/dist/apis/definitions/ComponentsApi.esm.js +0 -12
  39. package/dist/apis/definitions/ComponentsApi.esm.js.map +0 -1
  40. package/dist/components/coreComponentRefs.esm.js +0 -19
  41. package/dist/components/coreComponentRefs.esm.js.map +0 -1
  42. package/dist/components/createComponentRef.esm.js +0 -12
  43. package/dist/components/createComponentRef.esm.js.map +0 -1
  44. package/dist/extensions/createComponentExtension.esm.js +0 -43
  45. package/dist/extensions/createComponentExtension.esm.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,113 @@
1
1
  # @backstage/frontend-plugin-api
2
2
 
3
+ ## 0.11.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - fda1bbc: **BREAKING**: The component system has been overhauled to use `SwappableComponent` instead of `ComponentRef`. Several APIs have been removed and replaced:
8
+
9
+ - Removed: `createComponentRef`, `createComponentExtension`, `ComponentRef`, `ComponentsApi`, `componentsApiRef`, `useComponentRef`, `coreComponentRefs`
10
+ - Added: `createSwappableComponent`, `SwappableComponentBlueprint`, `SwappableComponentRef`, `SwappableComponentsApi`, `swappableComponentsApiRef`
11
+
12
+ **BREAKING**: The default `componentRefs` and exported `Core*Props` have been removed and have replacement `SwappableComponents` and revised type names instead.
13
+
14
+ - The `errorBoundaryFallback` component and `CoreErrorBoundaryFallbackProps` type have been replaced with `ErrorDisplay` swappable component and `CoreErrorDisplayProps` respectively.
15
+ - The `progress` component and `CoreProgressProps` type have been replaced with `Progress` swappable component and `ProgressProps` respectively.
16
+ - The `notFoundErrorPage` component and `CoreNotFoundErrorPageProps` type have been replaced with `NotFoundErrorPage` swappable component and `NotFoundErrorPageProps` respectively.
17
+
18
+ **Migration for creating swappable components:**
19
+
20
+ ```tsx
21
+ // OLD: Using createComponentRef and createComponentExtension
22
+ import {
23
+ createComponentRef,
24
+ createComponentExtension,
25
+ } from '@backstage/frontend-plugin-api';
26
+
27
+ const myComponentRef = createComponentRef<{ title: string }>({
28
+ id: 'my-plugin.my-component',
29
+ });
30
+
31
+ const myComponentExtension = createComponentExtension({
32
+ ref: myComponentRef,
33
+ loader: {
34
+ lazy: () => import('./MyComponent').then(m => m.MyComponent),
35
+ },
36
+ });
37
+
38
+ // NEW: Using createSwappableComponent and SwappableComponentBlueprint
39
+ import {
40
+ createSwappableComponent,
41
+ SwappableComponentBlueprint,
42
+ } from '@backstage/frontend-plugin-api';
43
+
44
+ const MySwappableComponent = createSwappableComponent({
45
+ id: 'my-plugin.my-component',
46
+ loader: () => import('./MyComponent').then(m => m.MyComponent),
47
+ });
48
+
49
+ const myComponentExtension = SwappableComponentBlueprint.make({
50
+ name: 'my-component',
51
+ params: {
52
+ component: MySwappableComponent,
53
+ loader: () => import('./MyComponent').then(m => m.MyComponent),
54
+ },
55
+ });
56
+ ```
57
+
58
+ **Migration for using components:**
59
+
60
+ ```tsx
61
+ // OLD: Using ComponentsApi and useComponentRef
62
+ import {
63
+ useComponentRef,
64
+ componentsApiRef,
65
+ useApi,
66
+ coreComponentRefs,
67
+ } from '@backstage/frontend-plugin-api';
68
+
69
+ const MyComponent = useComponentRef(myComponentRef);
70
+ const ProgressComponent = useComponentRef(coreComponentRefs.progress);
71
+
72
+
73
+ // NEW: Direct component usage
74
+ import { Progress } from '@backstage/frontend-plugin-api';
75
+
76
+ // Use directly as React Component
77
+ <Progress />
78
+ <MySwappableComponent title="Hello World" />
79
+ ```
80
+
81
+ **Migration for core component references:**
82
+
83
+ ```tsx
84
+ // OLD: Core component refs
85
+ import { coreComponentRefs } from '@backstage/frontend-plugin-api';
86
+
87
+ coreComponentRefs.progress
88
+ coreComponentRefs.notFoundErrorPage
89
+ coreComponentRefs.errorBoundaryFallback
90
+
91
+ // NEW: Direct swappable component imports
92
+ import { Progress, NotFoundErrorPage, ErrorDisplay } from '@backstage/frontend-plugin-api';
93
+
94
+ // Use directly as React components
95
+ <Progress />
96
+ <NotFoundErrorPage />
97
+ <ErrorDisplay plugin={plugin} error={error} resetError={resetError} />
98
+ ```
99
+
100
+ - 6a75e00: **BREAKING**: Removed the deprecated `createFrontendPlugin` variant where the plugin ID is passed via an `id` option. To update existing code, switch to using the `pluginId` option instead.
101
+ - 1e6410b: **BREAKING**: The `ResolveInputValueOverrides` type is no longer exported.
102
+
103
+ ### Patch Changes
104
+
105
+ - 9831f4e: Adjusted the dialog API types to have more sensible defaults
106
+ - 1c2cc37: Improved runtime error message clarity when extension factories don't return an iterable object.
107
+ - 24558f0: Added inline documentation for `createExtension`, `createExtensionBlueprint`, `createFrontendPlugin`, and `createFrontendModule`.
108
+ - Updated dependencies
109
+ - @backstage/core-components@0.17.5-next.2
110
+
3
111
  ## 0.11.0-next.1
4
112
 
5
113
  ### Minor Changes
@@ -1,7 +1,7 @@
1
1
  import { useApi } from '@backstage/core-plugin-api';
2
2
  import { useAnalyticsContext } from './AnalyticsContext.esm.js';
3
3
  import '../apis/definitions/AppTreeApi.esm.js';
4
- import '../apis/definitions/ComponentsApi.esm.js';
4
+ import '../apis/definitions/SwappableComponentsApi.esm.js';
5
5
  import '../apis/definitions/IconsApi.esm.js';
6
6
  import '../apis/definitions/DialogApi.esm.js';
7
7
  import '../apis/definitions/RouteResolutionApi.esm.js';
@@ -1 +1 @@
1
- {"version":3,"file":"AppTreeApi.esm.js","sources":["../../../src/apis/definitions/AppTreeApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport {\n FrontendPlugin,\n Extension,\n ExtensionDataRef,\n ExtensionAttachToSpec,\n} from '../../wiring';\n\n/**\n * The specification for this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The specifications for a collection of app nodes is all the information needed\n * to build the tree and instantiate the nodes.\n */\nexport interface AppNodeSpec {\n readonly id: string;\n readonly attachTo: ExtensionAttachToSpec;\n readonly extension: Extension<unknown, unknown>;\n readonly disabled: boolean;\n readonly config?: unknown;\n readonly plugin?: FrontendPlugin;\n}\n\n/**\n * The connections from this {@link AppNode} to other nodes.\n *\n * @public\n * @remarks\n *\n * The app node edges are resolved based on the app node specs, regardless of whether\n * adjacent nodes are disabled or not. If no parent attachment is present or\n */\nexport interface AppNodeEdges {\n readonly attachedTo?: { node: AppNode; input: string };\n readonly attachments: ReadonlyMap<string, AppNode[]>;\n}\n\n/**\n * The instance of this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The app node instance is created when the `factory` function of an extension is called.\n * Instances will only be present for nodes in the app that are connected to the root\n * node and not disabled\n */\nexport interface AppNodeInstance {\n /** Returns a sequence of all extension data refs that were output by this instance */\n getDataRefs(): Iterable<ExtensionDataRef<unknown>>;\n /** Get the output data for a single extension data ref */\n getData<T>(ref: ExtensionDataRef<T>): T | undefined;\n}\n\n/**\n * A node in the {@link AppTree}.\n *\n * @public\n */\nexport interface AppNode {\n /** The specification for how this node should be instantiated */\n readonly spec: AppNodeSpec;\n /** The edges from this node to other nodes in the app tree */\n readonly edges: AppNodeEdges;\n /** The instance of this node, if it was instantiated */\n readonly instance?: AppNodeInstance;\n}\n\n/**\n * The app tree containing all {@link AppNode}s of the app.\n *\n * @public\n */\nexport interface AppTree {\n /** The root node of the app */\n readonly root: AppNode;\n /** A map of all nodes in the app by ID, including orphaned or disabled nodes */\n readonly nodes: ReadonlyMap<string /* id */, AppNode>;\n /** A sequence of all nodes with a parent that is not reachable from the app root node */\n readonly orphans: Iterable<AppNode>;\n}\n\n/**\n * The API for interacting with the {@link AppTree}.\n *\n * @public\n */\nexport interface AppTreeApi {\n /**\n * Get the {@link AppTree} for the app.\n */\n getTree(): { tree: AppTree };\n\n /**\n * Get all nodes in the app that are mounted at a given route path.\n */\n getNodesByRoutePath(sourcePath: string): { nodes: AppNode[] };\n}\n\n/**\n * The `ApiRef` of {@link AppTreeApi}.\n *\n * @public\n */\nexport const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });\n"],"names":[],"mappings":";;AA2HO,MAAM,aAAgB,GAAA,YAAA,CAAyB,EAAE,EAAA,EAAI,iBAAiB;;;;"}
1
+ {"version":3,"file":"AppTreeApi.esm.js","sources":["../../../src/apis/definitions/AppTreeApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport {\n FrontendPlugin,\n Extension,\n ExtensionDataRef,\n ExtensionAttachToSpec,\n} from '../../wiring';\n\n/**\n * The specification for this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The specifications for a collection of app nodes is all the information needed\n * to build the tree and instantiate the nodes.\n */\nexport interface AppNodeSpec {\n readonly id: string;\n readonly attachTo: ExtensionAttachToSpec;\n readonly extension: Extension<unknown, unknown>;\n readonly disabled: boolean;\n readonly config?: unknown;\n readonly plugin: FrontendPlugin;\n}\n\n/**\n * The connections from this {@link AppNode} to other nodes.\n *\n * @public\n * @remarks\n *\n * The app node edges are resolved based on the app node specs, regardless of whether\n * adjacent nodes are disabled or not. If no parent attachment is present or\n */\nexport interface AppNodeEdges {\n readonly attachedTo?: { node: AppNode; input: string };\n readonly attachments: ReadonlyMap<string, AppNode[]>;\n}\n\n/**\n * The instance of this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The app node instance is created when the `factory` function of an extension is called.\n * Instances will only be present for nodes in the app that are connected to the root\n * node and not disabled\n */\nexport interface AppNodeInstance {\n /** Returns a sequence of all extension data refs that were output by this instance */\n getDataRefs(): Iterable<ExtensionDataRef<unknown>>;\n /** Get the output data for a single extension data ref */\n getData<T>(ref: ExtensionDataRef<T>): T | undefined;\n}\n\n/**\n * A node in the {@link AppTree}.\n *\n * @public\n */\nexport interface AppNode {\n /** The specification for how this node should be instantiated */\n readonly spec: AppNodeSpec;\n /** The edges from this node to other nodes in the app tree */\n readonly edges: AppNodeEdges;\n /** The instance of this node, if it was instantiated */\n readonly instance?: AppNodeInstance;\n}\n\n/**\n * The app tree containing all {@link AppNode}s of the app.\n *\n * @public\n */\nexport interface AppTree {\n /** The root node of the app */\n readonly root: AppNode;\n /** A map of all nodes in the app by ID, including orphaned or disabled nodes */\n readonly nodes: ReadonlyMap<string /* id */, AppNode>;\n /** A sequence of all nodes with a parent that is not reachable from the app root node */\n readonly orphans: Iterable<AppNode>;\n}\n\n/**\n * The API for interacting with the {@link AppTree}.\n *\n * @public\n */\nexport interface AppTreeApi {\n /**\n * Get the {@link AppTree} for the app.\n */\n getTree(): { tree: AppTree };\n\n /**\n * Get all nodes in the app that are mounted at a given route path.\n */\n getNodesByRoutePath(routePath: string): { nodes: AppNode[] };\n}\n\n/**\n * The `ApiRef` of {@link AppTreeApi}.\n *\n * @public\n */\nexport const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });\n"],"names":[],"mappings":";;AA2HO,MAAM,aAAgB,GAAA,YAAA,CAAyB,EAAE,EAAA,EAAI,iBAAiB;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"DialogApi.esm.js","sources":["../../../src/apis/definitions/DialogApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\n\n/**\n * A handle for an open dialog that can be used to interact with it.\n *\n * @remarks\n *\n * Dialogs can be opened using either {@link DialogApi.show} or {@link DialogApi.showModal}.\n *\n * @public\n */\nexport interface DialogApiDialog<TResult = unknown> {\n /**\n * Closes the dialog with that provided result.\n *\n * @remarks\n *\n * If the dialog is a modal dialog a result must always be provided. If it's a regular dialog then passing a result is optional.\n */\n close(\n ...args: undefined extends TResult ? [result?: TResult] : [result: TResult]\n ): void;\n\n /**\n * Replaces the content of the dialog with the provided element or component, causing it to be rerenedered.\n */\n update(\n elementOrComponent:\n | React.JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): void;\n\n /**\n * Wait until the dialog is closed and return the result.\n *\n * @remarks\n *\n * If the dialog is a modal dialog a result will always be returned. If it's a regular dialog then the result may be `undefined`.\n */\n result(): Promise<TResult>;\n}\n\n/**\n * A Utility API for showing dialogs that render in the React tree and return a result.\n *\n * @public\n */\nexport interface DialogApi {\n /**\n * Opens a modal dialog and returns a handle to it.\n *\n * @remarks\n *\n * This dialog can be closed by calling the `close` method on the returned handle, optionally providing a result.\n * The dialog can also be closed by the user by clicking the backdrop or pressing the escape key.\n *\n * If the dialog is closed without a result, the result will be `undefined`.\n *\n * @example\n *\n * ### Example with inline dialog content\n * ```tsx\n * const dialog = dialogApi.show<boolean>(\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * );\n * const result = await dialog.result();\n * ```\n *\n * @example\n *\n * ### Example with separate dialog component\n * ```tsx\n * function CustomDialog({ dialog }: { dialog: DialogApiDialog<boolean | undefined> }) {\n * return (\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * )\n * }\n * const result = await dialogApi.show(CustomDialog).result();\n * ```\n *\n * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle.\n * @public\n */\n show<TResult = {}>(\n elementOrComponent:\n | JSX.Element\n | ((props: {\n dialog: DialogApiDialog<TResult | undefined>;\n }) => JSX.Element),\n ): DialogApiDialog<TResult | undefined>;\n\n /**\n * Opens a modal dialog and returns a handle to it.\n *\n * @remarks\n *\n * This dialog can not be closed in any other way than calling the `close` method on the returned handle and providing a result.\n *\n * @example\n *\n * ### Example with inline dialog content\n * ```tsx\n * const dialog = dialogApi.showModal<boolean>(\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * );\n * const result = await dialog.result();\n * ```\n *\n * @example\n *\n * ### Example with separate dialog component\n * ```tsx\n * function CustomDialog({ dialog }: { dialog: DialogApiDialog<boolean> }) {\n * return (\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * )\n * }\n * const result = await dialogApi.showModal(CustomDialog).result();\n * ```\n *\n * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle.\n * @public\n */\n showModal<TResult = {}>(\n elementOrComponent:\n | JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): DialogApiDialog<TResult>;\n}\n\n/**\n * The `ApiRef` of {@link DialogApi}.\n *\n * @public\n */\nexport const dialogApiRef = createApiRef<DialogApi>({\n id: 'core.dialog',\n});\n"],"names":[],"mappings":";;AA+KO,MAAM,eAAe,YAAwB,CAAA;AAAA,EAClD,EAAI,EAAA;AACN,CAAC;;;;"}
1
+ {"version":3,"file":"DialogApi.esm.js","sources":["../../../src/apis/definitions/DialogApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createApiRef } from '@backstage/core-plugin-api';\n\n/**\n * A handle for an open dialog that can be used to interact with it.\n *\n * @remarks\n *\n * Dialogs can be opened using either {@link DialogApi.show} or {@link DialogApi.showModal}.\n *\n * @public\n */\nexport interface DialogApiDialog<TResult = void> {\n /**\n * Closes the dialog with that provided result.\n *\n * @remarks\n *\n * If the dialog is a modal dialog a result must always be provided. If it's a regular dialog then passing a result is optional.\n */\n close(\n ...args: undefined extends TResult ? [result?: TResult] : [result: TResult]\n ): void;\n\n /**\n * Replaces the content of the dialog with the provided element or component, causing it to be rerenedered.\n */\n update(\n elementOrComponent:\n | React.JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): void;\n\n /**\n * Wait until the dialog is closed and return the result.\n *\n * @remarks\n *\n * If the dialog is a modal dialog a result will always be returned. If it's a regular dialog then the result may be `undefined`.\n */\n result(): Promise<TResult>;\n}\n\n/**\n * A Utility API for showing dialogs that render in the React tree and return a result.\n *\n * @public\n */\nexport interface DialogApi {\n /**\n * Opens a modal dialog and returns a handle to it.\n *\n * @remarks\n *\n * This dialog can be closed by calling the `close` method on the returned handle, optionally providing a result.\n * The dialog can also be closed by the user by clicking the backdrop or pressing the escape key.\n *\n * If the dialog is closed without a result, the result will be `undefined`.\n *\n * @example\n *\n * ### Example with inline dialog content\n * ```tsx\n * const dialog = dialogApi.show<boolean>(\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * );\n * const result = await dialog.result();\n * ```\n *\n * @example\n *\n * ### Example with separate dialog component\n * ```tsx\n * function CustomDialog({ dialog }: { dialog: DialogApiDialog<boolean | undefined> }) {\n * return (\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * )\n * }\n * const result = await dialogApi.show(CustomDialog).result();\n * ```\n *\n * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle.\n * @public\n */\n show<TResult = void>(\n elementOrComponent:\n | JSX.Element\n | ((props: {\n dialog: DialogApiDialog<TResult | undefined>;\n }) => JSX.Element),\n ): DialogApiDialog<TResult | undefined>;\n\n /**\n * Opens a modal dialog and returns a handle to it.\n *\n * @remarks\n *\n * This dialog can not be closed in any other way than calling the `close` method on the returned handle and providing a result.\n *\n * @example\n *\n * ### Example with inline dialog content\n * ```tsx\n * const dialog = dialogApi.showModal<boolean>(\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * );\n * const result = await dialog.result();\n * ```\n *\n * @example\n *\n * ### Example with separate dialog component\n * ```tsx\n * function CustomDialog({ dialog }: { dialog: DialogApiDialog<boolean> }) {\n * return (\n * <DialogContent>\n * <DialogTitle>Are you sure?</DialogTitle>\n * <DialogActions>\n * <Button onClick={() => dialog.close(true)}>Yes</Button>\n * <Button onClick={() => dialog.close(false)}>No</Button>\n * </DialogActions>\n * </DialogContent>\n * )\n * }\n * const result = await dialogApi.showModal(CustomDialog).result();\n * ```\n *\n * @param elementOrComponent - The element or component to render in the dialog. If a component is provided, it will be provided with a `dialog` prop that contains the dialog handle.\n * @public\n */\n showModal<TResult = void>(\n elementOrComponent:\n | JSX.Element\n | ((props: { dialog: DialogApiDialog<TResult> }) => JSX.Element),\n ): DialogApiDialog<TResult>;\n}\n\n/**\n * The `ApiRef` of {@link DialogApi}.\n *\n * @public\n */\nexport const dialogApiRef = createApiRef<DialogApi>({\n id: 'core.dialog',\n});\n"],"names":[],"mappings":";;AA+KO,MAAM,eAAe,YAAwB,CAAA;AAAA,EAClD,EAAI,EAAA;AACN,CAAC;;;;"}
@@ -0,0 +1,8 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+
3
+ const swappableComponentsApiRef = createApiRef({
4
+ id: "core.swappable-components"
5
+ });
6
+
7
+ export { swappableComponentsApiRef };
8
+ //# sourceMappingURL=SwappableComponentsApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwappableComponentsApi.esm.js","sources":["../../../src/apis/definitions/SwappableComponentsApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SwappableComponentRef } from '../../components';\nimport { createApiRef } from '@backstage/core-plugin-api';\n\n/**\n * API for looking up components based on component refs.\n *\n * @public\n */\nexport interface SwappableComponentsApi {\n getComponent<\n TInnerComponentProps extends {},\n TExternalComponentProps extends {} = TInnerComponentProps,\n >(\n ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,\n ): (props: TInnerComponentProps) => JSX.Element | null;\n}\n\n/**\n * The `ApiRef` of {@link SwappableComponentsApi}.\n *\n * @public\n */\nexport const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({\n id: 'core.swappable-components',\n});\n"],"names":[],"mappings":";;AAsCO,MAAM,4BAA4B,YAAqC,CAAA;AAAA,EAC5E,EAAI,EAAA;AACN,CAAC;;;;"}
@@ -3,7 +3,17 @@ import 'zod';
3
3
  import 'zod-to-json-schema';
4
4
  import { createExtensionBlueprint } from '../wiring/createExtensionBlueprint.esm.js';
5
5
  import { ExtensionBoundary } from '../components/ExtensionBoundary.esm.js';
6
+ import 'react/jsx-runtime';
7
+ import '../apis/definitions/AppTreeApi.esm.js';
8
+ import '@backstage/core-plugin-api';
9
+ import '../apis/definitions/SwappableComponentsApi.esm.js';
10
+ import '../apis/definitions/IconsApi.esm.js';
11
+ import '../apis/definitions/DialogApi.esm.js';
12
+ import '../apis/definitions/RouteResolutionApi.esm.js';
13
+ import '../apis/definitions/AnalyticsApi.esm.js';
14
+ import 'react';
6
15
  import '../components/AppNodeProvider.esm.js';
16
+ import '../components/DefaultSwappableComponents.esm.js';
7
17
 
8
18
  const PageBlueprint = createExtensionBlueprint({
9
19
  kind: "page",
@@ -1 +1 @@
1
- {"version":3,"file":"PageBlueprint.esm.js","sources":["../../src/blueprints/PageBlueprint.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RouteRef } from '../routing';\nimport { coreExtensionData, createExtensionBlueprint } from '../wiring';\nimport { ExtensionBoundary } from '../components';\n\n/**\n * Createx extensions that are routable React page components.\n *\n * @public\n */\nexport const PageBlueprint = createExtensionBlueprint({\n kind: 'page',\n attachTo: { id: 'app/routes', input: 'routes' },\n output: [\n coreExtensionData.routePath,\n coreExtensionData.reactElement,\n coreExtensionData.routeRef.optional(),\n ],\n config: {\n schema: {\n path: z => z.string().optional(),\n },\n },\n *factory(\n params: {\n /**\n * @deprecated Use the `path` param instead.\n */\n defaultPath?: [Error: `Use the 'path' param instead`];\n path: string;\n loader: () => Promise<JSX.Element>;\n routeRef?: RouteRef;\n },\n { config, node },\n ) {\n yield coreExtensionData.routePath(config.path ?? params.path);\n yield coreExtensionData.reactElement(\n ExtensionBoundary.lazy(node, params.loader),\n );\n if (params.routeRef) {\n yield coreExtensionData.routeRef(params.routeRef);\n }\n },\n});\n"],"names":[],"mappings":";;;;;;;AAyBO,MAAM,gBAAgB,wBAAyB,CAAA;AAAA,EACpD,IAAM,EAAA,MAAA;AAAA,EACN,QAAU,EAAA,EAAE,EAAI,EAAA,YAAA,EAAc,OAAO,QAAS,EAAA;AAAA,EAC9C,MAAQ,EAAA;AAAA,IACN,iBAAkB,CAAA,SAAA;AAAA,IAClB,iBAAkB,CAAA,YAAA;AAAA,IAClB,iBAAA,CAAkB,SAAS,QAAS;AAAA,GACtC;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,MAAQ,EAAA;AAAA,MACN,IAAM,EAAA,CAAA,CAAA,KAAK,CAAE,CAAA,MAAA,GAAS,QAAS;AAAA;AACjC,GACF;AAAA,EACA,CAAC,OACC,CAAA,MAAA,EASA,EAAE,MAAA,EAAQ,MACV,EAAA;AACA,IAAA,MAAM,iBAAkB,CAAA,SAAA,CAAU,MAAO,CAAA,IAAA,IAAQ,OAAO,IAAI,CAAA;AAC5D,IAAA,MAAM,iBAAkB,CAAA,YAAA;AAAA,MACtB,iBAAkB,CAAA,IAAA,CAAK,IAAM,EAAA,MAAA,CAAO,MAAM;AAAA,KAC5C;AACA,IAAA,IAAI,OAAO,QAAU,EAAA;AACnB,MAAM,MAAA,iBAAA,CAAkB,QAAS,CAAA,MAAA,CAAO,QAAQ,CAAA;AAAA;AAClD;AAEJ,CAAC;;;;"}
1
+ {"version":3,"file":"PageBlueprint.esm.js","sources":["../../src/blueprints/PageBlueprint.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { RouteRef } from '../routing';\nimport { coreExtensionData, createExtensionBlueprint } from '../wiring';\nimport { ExtensionBoundary } from '../components';\n\n/**\n * Createx extensions that are routable React page components.\n *\n * @public\n */\nexport const PageBlueprint = createExtensionBlueprint({\n kind: 'page',\n attachTo: { id: 'app/routes', input: 'routes' },\n output: [\n coreExtensionData.routePath,\n coreExtensionData.reactElement,\n coreExtensionData.routeRef.optional(),\n ],\n config: {\n schema: {\n path: z => z.string().optional(),\n },\n },\n *factory(\n params: {\n /**\n * @deprecated Use the `path` param instead.\n */\n defaultPath?: [Error: `Use the 'path' param instead`];\n path: string;\n loader: () => Promise<JSX.Element>;\n routeRef?: RouteRef;\n },\n { config, node },\n ) {\n yield coreExtensionData.routePath(config.path ?? params.path);\n yield coreExtensionData.reactElement(\n ExtensionBoundary.lazy(node, params.loader),\n );\n if (params.routeRef) {\n yield coreExtensionData.routeRef(params.routeRef);\n }\n },\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAyBO,MAAM,gBAAgB,wBAAyB,CAAA;AAAA,EACpD,IAAM,EAAA,MAAA;AAAA,EACN,QAAU,EAAA,EAAE,EAAI,EAAA,YAAA,EAAc,OAAO,QAAS,EAAA;AAAA,EAC9C,MAAQ,EAAA;AAAA,IACN,iBAAkB,CAAA,SAAA;AAAA,IAClB,iBAAkB,CAAA,YAAA;AAAA,IAClB,iBAAA,CAAkB,SAAS,QAAS;AAAA,GACtC;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,MAAQ,EAAA;AAAA,MACN,IAAM,EAAA,CAAA,CAAA,KAAK,CAAE,CAAA,MAAA,GAAS,QAAS;AAAA;AACjC,GACF;AAAA,EACA,CAAC,OACC,CAAA,MAAA,EASA,EAAE,MAAA,EAAQ,MACV,EAAA;AACA,IAAA,MAAM,iBAAkB,CAAA,SAAA,CAAU,MAAO,CAAA,IAAA,IAAQ,OAAO,IAAI,CAAA;AAC5D,IAAA,MAAM,iBAAkB,CAAA,YAAA;AAAA,MACtB,iBAAkB,CAAA,IAAA,CAAK,IAAM,EAAA,MAAA,CAAO,MAAM;AAAA,KAC5C;AACA,IAAA,IAAI,OAAO,QAAU,EAAA;AACnB,MAAM,MAAA,iBAAA,CAAkB,QAAS,CAAA,MAAA,CAAO,QAAQ,CAAA;AAAA;AAClD;AAEJ,CAAC;;;;"}
@@ -6,7 +6,15 @@ import 'zod-to-json-schema';
6
6
  import { createExtensionDataRef } from '../wiring/createExtensionDataRef.esm.js';
7
7
  import { createExtensionBlueprint } from '../wiring/createExtensionBlueprint.esm.js';
8
8
  import { ExtensionBoundary } from '../components/ExtensionBoundary.esm.js';
9
+ import '../apis/definitions/AppTreeApi.esm.js';
10
+ import '@backstage/core-plugin-api';
11
+ import '../apis/definitions/SwappableComponentsApi.esm.js';
12
+ import '../apis/definitions/IconsApi.esm.js';
13
+ import '../apis/definitions/DialogApi.esm.js';
14
+ import '../apis/definitions/RouteResolutionApi.esm.js';
15
+ import '../apis/definitions/AnalyticsApi.esm.js';
9
16
  import '../components/AppNodeProvider.esm.js';
17
+ import '../components/DefaultSwappableComponents.esm.js';
10
18
 
11
19
  const componentDataRef = createExtensionDataRef().with({ id: "core.sign-in-page.component" });
12
20
  const SignInPageBlueprint = createExtensionBlueprint({
@@ -1 +1 @@
1
- {"version":3,"file":"SignInPageBlueprint.esm.js","sources":["../../src/blueprints/SignInPageBlueprint.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ComponentType, lazy } from 'react';\nimport { createExtensionBlueprint, createExtensionDataRef } from '../wiring';\nimport { SignInPageProps } from '@backstage/core-plugin-api';\nimport { ExtensionBoundary } from '../components';\n\nconst componentDataRef = createExtensionDataRef<\n ComponentType<SignInPageProps>\n>().with({ id: 'core.sign-in-page.component' });\n\n/**\n * Creates an extension that replaces the sign in page.\n *\n * @public\n */\nexport const SignInPageBlueprint = createExtensionBlueprint({\n kind: 'sign-in-page',\n attachTo: { id: 'app/root', input: 'signInPage' },\n output: [componentDataRef],\n dataRefs: {\n component: componentDataRef,\n },\n *factory(\n {\n loader,\n }: {\n loader: () => Promise<ComponentType<SignInPageProps>>;\n },\n { node },\n ) {\n const ExtensionComponent = lazy(() =>\n loader().then(component => ({ default: component })),\n );\n\n yield componentDataRef(props => (\n <ExtensionBoundary node={node}>\n <ExtensionComponent {...props} />\n </ExtensionBoundary>\n ));\n },\n});\n"],"names":[],"mappings":";;;;;;;;;;AAqBA,MAAM,mBAAmB,sBAEvB,EAAA,CAAE,KAAK,EAAE,EAAA,EAAI,+BAA+B,CAAA;AAOvC,MAAM,sBAAsB,wBAAyB,CAAA;AAAA,EAC1D,IAAM,EAAA,cAAA;AAAA,EACN,QAAU,EAAA,EAAE,EAAI,EAAA,UAAA,EAAY,OAAO,YAAa,EAAA;AAAA,EAChD,MAAA,EAAQ,CAAC,gBAAgB,CAAA;AAAA,EACzB,QAAU,EAAA;AAAA,IACR,SAAW,EAAA;AAAA,GACb;AAAA,EACA,CAAC,OACC,CAAA;AAAA,IACE;AAAA,GACF,EAGA,EAAE,IAAA,EACF,EAAA;AACA,IAAA,MAAM,kBAAqB,GAAA,IAAA;AAAA,MAAK,MAC9B,QAAS,CAAA,IAAA,CAAK,gBAAc,EAAE,OAAA,EAAS,WAAY,CAAA;AAAA,KACrD;AAEA,IAAM,MAAA,gBAAA,CAAiB,CACrB,KAAA,qBAAA,GAAA,CAAC,iBAAkB,EAAA,EAAA,IAAA,EACjB,8BAAC,kBAAoB,EAAA,EAAA,GAAG,KAAO,EAAA,CAAA,EACjC,CACD,CAAA;AAAA;AAEL,CAAC;;;;"}
1
+ {"version":3,"file":"SignInPageBlueprint.esm.js","sources":["../../src/blueprints/SignInPageBlueprint.tsx"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ComponentType, lazy } from 'react';\nimport { createExtensionBlueprint, createExtensionDataRef } from '../wiring';\nimport { SignInPageProps } from '@backstage/core-plugin-api';\nimport { ExtensionBoundary } from '../components';\n\nconst componentDataRef = createExtensionDataRef<\n ComponentType<SignInPageProps>\n>().with({ id: 'core.sign-in-page.component' });\n\n/**\n * Creates an extension that replaces the sign in page.\n *\n * @public\n */\nexport const SignInPageBlueprint = createExtensionBlueprint({\n kind: 'sign-in-page',\n attachTo: { id: 'app/root', input: 'signInPage' },\n output: [componentDataRef],\n dataRefs: {\n component: componentDataRef,\n },\n *factory(\n {\n loader,\n }: {\n loader: () => Promise<ComponentType<SignInPageProps>>;\n },\n { node },\n ) {\n const ExtensionComponent = lazy(() =>\n loader().then(component => ({ default: component })),\n );\n\n yield componentDataRef(props => (\n <ExtensionBoundary node={node}>\n <ExtensionComponent {...props} />\n </ExtensionBoundary>\n ));\n },\n});\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAqBA,MAAM,mBAAmB,sBAEvB,EAAA,CAAE,KAAK,EAAE,EAAA,EAAI,+BAA+B,CAAA;AAOvC,MAAM,sBAAsB,wBAAyB,CAAA;AAAA,EAC1D,IAAM,EAAA,cAAA;AAAA,EACN,QAAU,EAAA,EAAE,EAAI,EAAA,UAAA,EAAY,OAAO,YAAa,EAAA;AAAA,EAChD,MAAA,EAAQ,CAAC,gBAAgB,CAAA;AAAA,EACzB,QAAU,EAAA;AAAA,IACR,SAAW,EAAA;AAAA,GACb;AAAA,EACA,CAAC,OACC,CAAA;AAAA,IACE;AAAA,GACF,EAGA,EAAE,IAAA,EACF,EAAA;AACA,IAAA,MAAM,kBAAqB,GAAA,IAAA;AAAA,MAAK,MAC9B,QAAS,CAAA,IAAA,CAAK,gBAAc,EAAE,OAAA,EAAS,WAAY,CAAA;AAAA,KACrD;AAEA,IAAM,MAAA,gBAAA,CAAiB,CACrB,KAAA,qBAAA,GAAA,CAAC,iBAAkB,EAAA,EAAA,IAAA,EACjB,8BAAC,kBAAoB,EAAA,EAAA,GAAG,KAAO,EAAA,CAAA,EACjC,CACD,CAAA;AAAA;AAEL,CAAC;;;;"}
@@ -0,0 +1,27 @@
1
+ import '../wiring/coreExtensionData.esm.js';
2
+ import 'zod';
3
+ import 'zod-to-json-schema';
4
+ import { createExtensionDataRef } from '../wiring/createExtensionDataRef.esm.js';
5
+ import { createExtensionBlueprint, createExtensionBlueprintParams } from '../wiring/createExtensionBlueprint.esm.js';
6
+
7
+ const componentDataRef = createExtensionDataRef().with({ id: "core.swappableComponent" });
8
+ const SwappableComponentBlueprint = createExtensionBlueprint({
9
+ kind: "component",
10
+ attachTo: { id: "api:app/swappable-components", input: "components" },
11
+ output: [componentDataRef],
12
+ dataRefs: {
13
+ component: componentDataRef
14
+ },
15
+ defineParams(params) {
16
+ return createExtensionBlueprintParams(params);
17
+ },
18
+ factory: (params) => [
19
+ componentDataRef({
20
+ ref: params.component.ref,
21
+ loader: params.loader
22
+ })
23
+ ]
24
+ });
25
+
26
+ export { SwappableComponentBlueprint, componentDataRef };
27
+ //# sourceMappingURL=SwappableComponentBlueprint.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwappableComponentBlueprint.esm.js","sources":["../../src/blueprints/SwappableComponentBlueprint.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { SwappableComponentRef } from '../components';\nimport {\n createExtensionBlueprint,\n createExtensionBlueprintParams,\n createExtensionDataRef,\n} from '../wiring';\n\nexport const componentDataRef = createExtensionDataRef<{\n ref: SwappableComponentRef;\n loader:\n | (() => (props: {}) => JSX.Element | null)\n | (() => Promise<(props: {}) => JSX.Element | null>);\n}>().with({ id: 'core.swappableComponent' });\n\n/**\n * Blueprint for creating swappable components from a SwappableComponentRef and a loader\n *\n * @public\n */\nexport const SwappableComponentBlueprint = createExtensionBlueprint({\n kind: 'component',\n attachTo: { id: 'api:app/swappable-components', input: 'components' },\n output: [componentDataRef],\n dataRefs: {\n component: componentDataRef,\n },\n defineParams<Ref extends SwappableComponentRef<any>>(params: {\n component: Ref extends SwappableComponentRef<\n any,\n infer IExternalComponentProps\n >\n ? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null)\n : never;\n loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>\n ?\n | (() => (props: IInnerComponentProps) => JSX.Element | null)\n | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)\n : never;\n }) {\n return createExtensionBlueprintParams(params);\n },\n factory: params => [\n componentDataRef({\n ref: params.component.ref,\n loader: params.loader,\n }),\n ],\n});\n"],"names":[],"mappings":";;;;;;AAsBO,MAAM,mBAAmB,sBAK7B,EAAA,CAAE,KAAK,EAAE,EAAA,EAAI,2BAA2B;AAOpC,MAAM,8BAA8B,wBAAyB,CAAA;AAAA,EAClE,IAAM,EAAA,WAAA;AAAA,EACN,QAAU,EAAA,EAAE,EAAI,EAAA,8BAAA,EAAgC,OAAO,YAAa,EAAA;AAAA,EACpE,MAAA,EAAQ,CAAC,gBAAgB,CAAA;AAAA,EACzB,QAAU,EAAA;AAAA,IACR,SAAW,EAAA;AAAA,GACb;AAAA,EACA,aAAqD,MAYlD,EAAA;AACD,IAAA,OAAO,+BAA+B,MAAM,CAAA;AAAA,GAC9C;AAAA,EACA,SAAS,CAAU,MAAA,KAAA;AAAA,IACjB,gBAAiB,CAAA;AAAA,MACf,GAAA,EAAK,OAAO,SAAU,CAAA,GAAA;AAAA,MACtB,QAAQ,MAAO,CAAA;AAAA,KAChB;AAAA;AAEL,CAAC;;;;"}
@@ -0,0 +1,16 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { createSwappableComponent } from './createSwappableComponent.esm.js';
3
+
4
+ const Progress = createSwappableComponent({
5
+ id: "core-progress"
6
+ });
7
+ const NotFoundErrorPage = createSwappableComponent({
8
+ id: "core-not-found-error-page"
9
+ });
10
+ const ErrorDisplay = createSwappableComponent({
11
+ id: "core-error-display",
12
+ loader: () => (props) => /* @__PURE__ */ jsx("div", { "data-testid": "core-error-display", children: props.error.message })
13
+ });
14
+
15
+ export { ErrorDisplay, NotFoundErrorPage, Progress };
16
+ //# sourceMappingURL=DefaultSwappableComponents.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DefaultSwappableComponents.esm.js","sources":["../../src/components/DefaultSwappableComponents.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ErrorDisplayProps,\n NotFoundErrorPageProps,\n ProgressProps,\n} from '../types';\nimport { createSwappableComponent } from './createSwappableComponent';\n\n/**\n * @public\n */\nexport const Progress = createSwappableComponent<ProgressProps>({\n id: 'core-progress',\n});\n\n/**\n * @public\n */\nexport const NotFoundErrorPage =\n createSwappableComponent<NotFoundErrorPageProps>({\n id: 'core-not-found-error-page',\n });\n\n/**\n * @public\n */\nexport const ErrorDisplay = createSwappableComponent<ErrorDisplayProps>({\n id: 'core-error-display',\n loader: () => props =>\n <div data-testid=\"core-error-display\">{props.error.message}</div>,\n});\n"],"names":[],"mappings":";;;AA0BO,MAAM,WAAW,wBAAwC,CAAA;AAAA,EAC9D,EAAI,EAAA;AACN,CAAC;AAKM,MAAM,oBACX,wBAAiD,CAAA;AAAA,EAC/C,EAAI,EAAA;AACN,CAAC;AAKI,MAAM,eAAe,wBAA4C,CAAA;AAAA,EACtE,EAAI,EAAA,oBAAA;AAAA,EACJ,MAAA,EAAQ,MAAM,CACZ,KAAA,qBAAA,GAAA,CAAC,SAAI,aAAY,EAAA,oBAAA,EAAsB,QAAM,EAAA,KAAA,CAAA,KAAA,CAAM,OAAQ,EAAA;AAC/D,CAAC;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { Component } from 'react';
3
+ import { ErrorDisplay } from './DefaultSwappableComponents.esm.js';
3
4
 
4
5
  class ErrorBoundary extends Component {
5
6
  static getDerivedStateFromError(error) {
@@ -11,10 +12,10 @@ class ErrorBoundary extends Component {
11
12
  };
12
13
  render() {
13
14
  const { error } = this.state;
14
- const { plugin, children, Fallback } = this.props;
15
+ const { plugin, children } = this.props;
15
16
  if (error) {
16
17
  return /* @__PURE__ */ jsx(
17
- Fallback,
18
+ ErrorDisplay,
18
19
  {
19
20
  plugin,
20
21
  error,
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorBoundary.esm.js","sources":["../../src/components/ErrorBoundary.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Component, ComponentType, PropsWithChildren } from 'react';\nimport { FrontendPlugin } from '../wiring';\nimport { CoreErrorBoundaryFallbackProps } from '../types';\n\ntype ErrorBoundaryProps = PropsWithChildren<{\n plugin?: FrontendPlugin;\n Fallback: ComponentType<CoreErrorBoundaryFallbackProps>;\n}>;\ntype ErrorBoundaryState = { error?: Error };\n\n/** @internal */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n state: ErrorBoundaryState = { error: undefined };\n\n handleErrorReset = () => {\n this.setState({ error: undefined });\n };\n\n render() {\n const { error } = this.state;\n const { plugin, children, Fallback } = this.props;\n\n if (error) {\n return (\n <Fallback\n plugin={plugin}\n error={error}\n resetError={this.handleErrorReset}\n />\n );\n }\n\n return children;\n }\n}\n"],"names":[],"mappings":";;;AA2BO,MAAM,sBAAsB,SAGjC,CAAA;AAAA,EACA,OAAO,yBAAyB,KAAc,EAAA;AAC5C,IAAA,OAAO,EAAE,KAAM,EAAA;AAAA;AACjB,EAEA,KAAA,GAA4B,EAAE,KAAA,EAAO,KAAU,CAAA,EAAA;AAAA,EAE/C,mBAAmB,MAAM;AACvB,IAAA,IAAA,CAAK,QAAS,CAAA,EAAE,KAAO,EAAA,KAAA,CAAA,EAAW,CAAA;AAAA,GACpC;AAAA,EAEA,MAAS,GAAA;AACP,IAAM,MAAA,EAAE,KAAM,EAAA,GAAI,IAAK,CAAA,KAAA;AACvB,IAAA,MAAM,EAAE,MAAA,EAAQ,QAAU,EAAA,QAAA,KAAa,IAAK,CAAA,KAAA;AAE5C,IAAA,IAAI,KAAO,EAAA;AACT,MACE,uBAAA,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,MAAA;AAAA,UACA,KAAA;AAAA,UACA,YAAY,IAAK,CAAA;AAAA;AAAA,OACnB;AAAA;AAIJ,IAAO,OAAA,QAAA;AAAA;AAEX;;;;"}
1
+ {"version":3,"file":"ErrorBoundary.esm.js","sources":["../../src/components/ErrorBoundary.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Component, PropsWithChildren } from 'react';\nimport { FrontendPlugin } from '../wiring';\nimport { ErrorDisplay } from './DefaultSwappableComponents';\n\ntype ErrorBoundaryProps = PropsWithChildren<{\n plugin?: FrontendPlugin;\n}>;\ntype ErrorBoundaryState = { error?: Error };\n\n/** @internal */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n state: ErrorBoundaryState = { error: undefined };\n\n handleErrorReset = () => {\n this.setState({ error: undefined });\n };\n\n render() {\n const { error } = this.state;\n const { plugin, children } = this.props;\n\n if (error) {\n return (\n <ErrorDisplay\n // todo: do we want to just use useAppNode hook in the ErrorDisplay instead?\n plugin={plugin}\n error={error}\n // todo: probably change this to onResetError\n resetError={this.handleErrorReset}\n />\n );\n }\n\n return children;\n }\n}\n"],"names":[],"mappings":";;;;AA0BO,MAAM,sBAAsB,SAGjC,CAAA;AAAA,EACA,OAAO,yBAAyB,KAAc,EAAA;AAC5C,IAAA,OAAO,EAAE,KAAM,EAAA;AAAA;AACjB,EAEA,KAAA,GAA4B,EAAE,KAAA,EAAO,KAAU,CAAA,EAAA;AAAA,EAE/C,mBAAmB,MAAM;AACvB,IAAA,IAAA,CAAK,QAAS,CAAA,EAAE,KAAO,EAAA,KAAA,CAAA,EAAW,CAAA;AAAA,GACpC;AAAA,EAEA,MAAS,GAAA;AACP,IAAM,MAAA,EAAE,KAAM,EAAA,GAAI,IAAK,CAAA,KAAA;AACvB,IAAA,MAAM,EAAE,MAAA,EAAQ,QAAS,EAAA,GAAI,IAAK,CAAA,KAAA;AAElC,IAAA,IAAI,KAAO,EAAA;AACT,MACE,uBAAA,GAAA;AAAA,QAAC,YAAA;AAAA,QAAA;AAAA,UAEC,MAAA;AAAA,UACA,KAAA;AAAA,UAEA,YAAY,IAAK,CAAA;AAAA;AAAA,OACnB;AAAA;AAIJ,IAAO,OAAA,QAAA;AAAA;AAEX;;;;"}
@@ -3,18 +3,12 @@ import { Suspense, lazy, useEffect } from 'react';
3
3
  import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';
4
4
  import { ErrorBoundary } from './ErrorBoundary.esm.js';
5
5
  import { routableExtensionRenderedEvent } from '../core-plugin-api/src/analytics/Tracker.esm.js';
6
- import '../apis/definitions/AppTreeApi.esm.js';
7
- import { useComponentRef } from '../apis/definitions/ComponentsApi.esm.js';
8
- import '../apis/definitions/IconsApi.esm.js';
9
- import '../apis/definitions/DialogApi.esm.js';
10
- import '../apis/definitions/RouteResolutionApi.esm.js';
11
- import '../apis/definitions/AnalyticsApi.esm.js';
12
- import { coreComponentRefs } from './coreComponentRefs.esm.js';
13
6
  import { coreExtensionData } from '../wiring/coreExtensionData.esm.js';
14
7
  import 'zod';
15
8
  import 'zod-to-json-schema';
16
9
  import '../wiring/createExtensionBlueprint.esm.js';
17
10
  import { AppNodeProvider } from './AppNodeProvider.esm.js';
11
+ import { Progress } from './DefaultSwappableComponents.esm.js';
18
12
 
19
13
  const RouteTracker = (props) => {
20
14
  const { enabled, children } = props;
@@ -32,13 +26,11 @@ function ExtensionBoundary(props) {
32
26
  node.instance?.getData(coreExtensionData.routePath)
33
27
  );
34
28
  const plugin = node.spec.plugin;
35
- const Progress = useComponentRef(coreComponentRefs.progress);
36
- const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
37
29
  const attributes = {
38
30
  extensionId: node.spec.id,
39
31
  pluginId: node.spec.plugin?.id ?? "app"
40
32
  };
41
- return /* @__PURE__ */ jsx(AppNodeProvider, { node, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Progress, {}), children: /* @__PURE__ */ jsx(ErrorBoundary, { plugin, Fallback: fallback, children: /* @__PURE__ */ jsx(AnalyticsContext, { attributes, children: /* @__PURE__ */ jsx(RouteTracker, { enabled: hasRoutePathOutput, children }) }) }) }) });
33
+ return /* @__PURE__ */ jsx(AppNodeProvider, { node, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Progress, {}), children: /* @__PURE__ */ jsx(ErrorBoundary, { plugin, children: /* @__PURE__ */ jsx(AnalyticsContext, { attributes, children: /* @__PURE__ */ jsx(RouteTracker, { enabled: hasRoutePathOutput, children }) }) }) }) });
42
34
  }
43
35
  ((ExtensionBoundary2) => {
44
36
  function lazy$1(appNode, loader) {
@@ -1 +1 @@
1
- {"version":3,"file":"ExtensionBoundary.esm.js","sources":["../../src/components/ExtensionBoundary.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PropsWithChildren,\n ReactNode,\n Suspense,\n useEffect,\n lazy as reactLazy,\n} from 'react';\nimport { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';\nimport { ErrorBoundary } from './ErrorBoundary';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';\nimport { AppNode, useComponentRef } from '../apis';\nimport { coreComponentRefs } from './coreComponentRefs';\nimport { coreExtensionData } from '../wiring';\nimport { AppNodeProvider } from './AppNodeProvider';\n\ntype RouteTrackerProps = PropsWithChildren<{\n enabled?: boolean;\n}>;\n\nconst RouteTracker = (props: RouteTrackerProps) => {\n const { enabled, children } = props;\n const analytics = useAnalytics();\n\n // This event, never exposed to end-users of the analytics API,\n // helps inform which extension metadata gets associated with a\n // navigation event when the route navigated to is a gathered\n // mountpoint.\n useEffect(() => {\n if (enabled) {\n analytics.captureEvent(routableExtensionRenderedEvent, '');\n }\n }, [analytics, enabled]);\n\n return <>{children}</>;\n};\n\n/** @public */\nexport interface ExtensionBoundaryProps {\n node: AppNode;\n children: ReactNode;\n}\n\n/** @public */\nexport function ExtensionBoundary(props: ExtensionBoundaryProps) {\n const { node, children } = props;\n\n const hasRoutePathOutput = Boolean(\n node.instance?.getData(coreExtensionData.routePath),\n );\n\n const plugin = node.spec.plugin;\n const Progress = useComponentRef(coreComponentRefs.progress);\n const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);\n\n // Skipping \"routeRef\" attribute in the new system, the extension \"id\" should provide more insight\n const attributes = {\n extensionId: node.spec.id,\n pluginId: node.spec.plugin?.id ?? 'app',\n };\n\n return (\n <AppNodeProvider node={node}>\n <Suspense fallback={<Progress />}>\n <ErrorBoundary plugin={plugin} Fallback={fallback}>\n <AnalyticsContext attributes={attributes}>\n <RouteTracker enabled={hasRoutePathOutput}>{children}</RouteTracker>\n </AnalyticsContext>\n </ErrorBoundary>\n </Suspense>\n </AppNodeProvider>\n );\n}\n\n/** @public */\nexport namespace ExtensionBoundary {\n export function lazy(\n appNode: AppNode,\n loader: () => Promise<JSX.Element>,\n ): JSX.Element {\n const ExtensionComponent = reactLazy(() =>\n loader().then(element => ({ default: () => element })),\n );\n return (\n <ExtensionBoundary node={appNode}>\n <ExtensionComponent />\n </ExtensionBoundary>\n );\n }\n\n export function lazyComponent<TProps extends {}>(\n appNode: AppNode,\n loader: () => Promise<(props: TProps) => JSX.Element>,\n ): (props: TProps) => JSX.Element {\n const ExtensionComponent = reactLazy(() =>\n loader().then(Component => ({ default: Component })),\n ) as unknown as React.ComponentType<TProps>;\n\n return (props: TProps) => (\n <ExtensionBoundary node={appNode}>\n <ExtensionComponent {...props} />\n </ExtensionBoundary>\n );\n }\n}\n"],"names":["ExtensionBoundary","lazy","reactLazy"],"mappings":";;;;;;;;;;;;;;;;;;AAoCA,MAAM,YAAA,GAAe,CAAC,KAA6B,KAAA;AACjD,EAAM,MAAA,EAAE,OAAS,EAAA,QAAA,EAAa,GAAA,KAAA;AAC9B,EAAA,MAAM,YAAY,YAAa,EAAA;AAM/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAS,EAAA;AACX,MAAU,SAAA,CAAA,YAAA,CAAa,gCAAgC,EAAE,CAAA;AAAA;AAC3D,GACC,EAAA,CAAC,SAAW,EAAA,OAAO,CAAC,CAAA;AAEvB,EAAA,uCAAU,QAAS,EAAA,CAAA;AACrB,CAAA;AASO,SAAS,kBAAkB,KAA+B,EAAA;AAC/D,EAAM,MAAA,EAAE,IAAM,EAAA,QAAA,EAAa,GAAA,KAAA;AAE3B,EAAA,MAAM,kBAAqB,GAAA,OAAA;AAAA,IACzB,IAAK,CAAA,QAAA,EAAU,OAAQ,CAAA,iBAAA,CAAkB,SAAS;AAAA,GACpD;AAEA,EAAM,MAAA,MAAA,GAAS,KAAK,IAAK,CAAA,MAAA;AACzB,EAAM,MAAA,QAAA,GAAW,eAAgB,CAAA,iBAAA,CAAkB,QAAQ,CAAA;AAC3D,EAAM,MAAA,QAAA,GAAW,eAAgB,CAAA,iBAAA,CAAkB,qBAAqB,CAAA;AAGxE,EAAA,MAAM,UAAa,GAAA;AAAA,IACjB,WAAA,EAAa,KAAK,IAAK,CAAA,EAAA;AAAA,IACvB,QAAU,EAAA,IAAA,CAAK,IAAK,CAAA,MAAA,EAAQ,EAAM,IAAA;AAAA,GACpC;AAEA,EACE,uBAAA,GAAA,CAAC,eAAgB,EAAA,EAAA,IAAA,EACf,QAAC,kBAAA,GAAA,CAAA,QAAA,EAAA,EAAS,0BAAW,GAAA,CAAA,QAAA,EAAA,EAAS,CAC5B,EAAA,QAAA,kBAAA,GAAA,CAAC,aAAc,EAAA,EAAA,MAAA,EAAgB,UAAU,QACvC,EAAA,QAAA,kBAAA,GAAA,CAAC,gBAAiB,EAAA,EAAA,UAAA,EAChB,QAAC,kBAAA,GAAA,CAAA,YAAA,EAAA,EAAa,OAAS,EAAA,kBAAA,EAAqB,QAAS,EAAA,CAAA,EACvD,CACF,EAAA,CAAA,EACF,CACF,EAAA,CAAA;AAEJ;AAAA,CAGO,CAAUA,kBAAV,KAAA;AACE,EAAS,SAAAC,MAAA,CACd,SACA,MACa,EAAA;AACb,IAAA,MAAM,kBAAqB,GAAAC,IAAA;AAAA,MAAU,MACnC,QAAS,CAAA,IAAA,CAAK,cAAY,EAAE,OAAA,EAAS,MAAM,OAAA,EAAU,CAAA;AAAA,KACvD;AACA,IAAA,2BACGF,kBAAA,EAAA,EAAkB,MAAM,OACvB,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CACtB,EAAA,CAAA;AAAA;AAVG,EAAAA,kBAAS,CAAA,IAAA,GAAAC,MAAA;AAcT,EAAS,SAAA,aAAA,CACd,SACA,MACgC,EAAA;AAChC,IAAA,MAAM,kBAAqB,GAAAC,IAAA;AAAA,MAAU,MACnC,QAAS,CAAA,IAAA,CAAK,gBAAc,EAAE,OAAA,EAAS,WAAY,CAAA;AAAA,KACrD;AAEA,IAAO,OAAA,CAAC,KACN,qBAAA,GAAA,CAACF,kBAAA,EAAA,EAAkB,IAAM,EAAA,OAAA,EACvB,QAAC,kBAAA,GAAA,CAAA,kBAAA,EAAA,EAAoB,GAAG,KAAA,EAAO,CACjC,EAAA,CAAA;AAAA;AAXG,EAAAA,kBAAS,CAAA,aAAA,GAAA,aAAA;AAAA,CAfD,EAAA,iBAAA,KAAA,iBAAA,GAAA,EAAA,CAAA,CAAA;;;;"}
1
+ {"version":3,"file":"ExtensionBoundary.esm.js","sources":["../../src/components/ExtensionBoundary.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n PropsWithChildren,\n ReactNode,\n Suspense,\n useEffect,\n lazy as reactLazy,\n} from 'react';\nimport { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';\nimport { ErrorBoundary } from './ErrorBoundary';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';\nimport { AppNode } from '../apis';\nimport { coreExtensionData } from '../wiring';\nimport { AppNodeProvider } from './AppNodeProvider';\nimport { Progress } from './DefaultSwappableComponents';\n\ntype RouteTrackerProps = PropsWithChildren<{\n enabled?: boolean;\n}>;\n\nconst RouteTracker = (props: RouteTrackerProps) => {\n const { enabled, children } = props;\n const analytics = useAnalytics();\n\n // This event, never exposed to end-users of the analytics API,\n // helps inform which extension metadata gets associated with a\n // navigation event when the route navigated to is a gathered\n // mountpoint.\n useEffect(() => {\n if (enabled) {\n analytics.captureEvent(routableExtensionRenderedEvent, '');\n }\n }, [analytics, enabled]);\n\n return <>{children}</>;\n};\n\n/** @public */\nexport interface ExtensionBoundaryProps {\n node: AppNode;\n children: ReactNode;\n}\n\n/** @public */\nexport function ExtensionBoundary(props: ExtensionBoundaryProps) {\n const { node, children } = props;\n\n const hasRoutePathOutput = Boolean(\n node.instance?.getData(coreExtensionData.routePath),\n );\n\n const plugin = node.spec.plugin;\n\n // Skipping \"routeRef\" attribute in the new system, the extension \"id\" should provide more insight\n const attributes = {\n extensionId: node.spec.id,\n pluginId: node.spec.plugin?.id ?? 'app',\n };\n\n return (\n <AppNodeProvider node={node}>\n <Suspense fallback={<Progress />}>\n <ErrorBoundary plugin={plugin}>\n <AnalyticsContext attributes={attributes}>\n <RouteTracker enabled={hasRoutePathOutput}>{children}</RouteTracker>\n </AnalyticsContext>\n </ErrorBoundary>\n </Suspense>\n </AppNodeProvider>\n );\n}\n\n/** @public */\nexport namespace ExtensionBoundary {\n export function lazy(\n appNode: AppNode,\n loader: () => Promise<JSX.Element>,\n ): JSX.Element {\n const ExtensionComponent = reactLazy(() =>\n loader().then(element => ({ default: () => element })),\n );\n return (\n <ExtensionBoundary node={appNode}>\n <ExtensionComponent />\n </ExtensionBoundary>\n );\n }\n\n export function lazyComponent<TProps extends {}>(\n appNode: AppNode,\n loader: () => Promise<(props: TProps) => JSX.Element>,\n ): (props: TProps) => JSX.Element {\n const ExtensionComponent = reactLazy(() =>\n loader().then(Component => ({ default: Component })),\n ) as unknown as React.ComponentType<TProps>;\n\n return (props: TProps) => (\n <ExtensionBoundary node={appNode}>\n <ExtensionComponent {...props} />\n </ExtensionBoundary>\n );\n }\n}\n"],"names":["ExtensionBoundary","lazy","reactLazy"],"mappings":";;;;;;;;;;;;AAoCA,MAAM,YAAA,GAAe,CAAC,KAA6B,KAAA;AACjD,EAAM,MAAA,EAAE,OAAS,EAAA,QAAA,EAAa,GAAA,KAAA;AAC9B,EAAA,MAAM,YAAY,YAAa,EAAA;AAM/B,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAS,EAAA;AACX,MAAU,SAAA,CAAA,YAAA,CAAa,gCAAgC,EAAE,CAAA;AAAA;AAC3D,GACC,EAAA,CAAC,SAAW,EAAA,OAAO,CAAC,CAAA;AAEvB,EAAA,uCAAU,QAAS,EAAA,CAAA;AACrB,CAAA;AASO,SAAS,kBAAkB,KAA+B,EAAA;AAC/D,EAAM,MAAA,EAAE,IAAM,EAAA,QAAA,EAAa,GAAA,KAAA;AAE3B,EAAA,MAAM,kBAAqB,GAAA,OAAA;AAAA,IACzB,IAAK,CAAA,QAAA,EAAU,OAAQ,CAAA,iBAAA,CAAkB,SAAS;AAAA,GACpD;AAEA,EAAM,MAAA,MAAA,GAAS,KAAK,IAAK,CAAA,MAAA;AAGzB,EAAA,MAAM,UAAa,GAAA;AAAA,IACjB,WAAA,EAAa,KAAK,IAAK,CAAA,EAAA;AAAA,IACvB,QAAU,EAAA,IAAA,CAAK,IAAK,CAAA,MAAA,EAAQ,EAAM,IAAA;AAAA,GACpC;AAEA,EACE,uBAAA,GAAA,CAAC,mBAAgB,IACf,EAAA,QAAA,kBAAA,GAAA,CAAC,YAAS,QAAU,kBAAA,GAAA,CAAC,QAAS,EAAA,EAAA,CAAA,EAC5B,QAAC,kBAAA,GAAA,CAAA,aAAA,EAAA,EAAc,QACb,QAAC,kBAAA,GAAA,CAAA,gBAAA,EAAA,EAAiB,UAChB,EAAA,QAAA,kBAAA,GAAA,CAAC,YAAa,EAAA,EAAA,OAAA,EAAS,oBAAqB,QAAS,EAAA,CAAA,EACvD,CACF,EAAA,CAAA,EACF,CACF,EAAA,CAAA;AAEJ;AAAA,CAGO,CAAUA,kBAAV,KAAA;AACE,EAAS,SAAAC,MAAA,CACd,SACA,MACa,EAAA;AACb,IAAA,MAAM,kBAAqB,GAAAC,IAAA;AAAA,MAAU,MACnC,QAAS,CAAA,IAAA,CAAK,cAAY,EAAE,OAAA,EAAS,MAAM,OAAA,EAAU,CAAA;AAAA,KACvD;AACA,IAAA,2BACGF,kBAAA,EAAA,EAAkB,MAAM,OACvB,EAAA,QAAA,kBAAA,GAAA,CAAC,sBAAmB,CACtB,EAAA,CAAA;AAAA;AAVG,EAAAA,kBAAS,CAAA,IAAA,GAAAC,MAAA;AAcT,EAAS,SAAA,aAAA,CACd,SACA,MACgC,EAAA;AAChC,IAAA,MAAM,kBAAqB,GAAAC,IAAA;AAAA,MAAU,MACnC,QAAS,CAAA,IAAA,CAAK,gBAAc,EAAE,OAAA,EAAS,WAAY,CAAA;AAAA,KACrD;AAEA,IAAO,OAAA,CAAC,KACN,qBAAA,GAAA,CAACF,kBAAA,EAAA,EAAkB,IAAM,EAAA,OAAA,EACvB,QAAC,kBAAA,GAAA,CAAA,kBAAA,EAAA,EAAoB,GAAG,KAAA,EAAO,CACjC,EAAA,CAAA;AAAA;AAXG,EAAAA,kBAAS,CAAA,aAAA,GAAA,aAAA;AAAA,CAfD,EAAA,iBAAA,KAAA,iBAAA,GAAA,EAAA,CAAA,CAAA;;;;"}
@@ -0,0 +1,52 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import '../apis/definitions/AppTreeApi.esm.js';
3
+ import { useApi } from '@backstage/core-plugin-api';
4
+ import { swappableComponentsApiRef } from '../apis/definitions/SwappableComponentsApi.esm.js';
5
+ import '../apis/definitions/IconsApi.esm.js';
6
+ import '../apis/definitions/DialogApi.esm.js';
7
+ import '../apis/definitions/RouteResolutionApi.esm.js';
8
+ import '../apis/definitions/AnalyticsApi.esm.js';
9
+ import { lazy } from 'react';
10
+ import { OpaqueSwappableComponentRef } from '../frontend-internal/src/wiring/InternalSwappableComponentRef.esm.js';
11
+ import '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
12
+ import '../frontend-internal/src/wiring/InternalFrontendPlugin.esm.js';
13
+
14
+ const useComponentRefApi = () => {
15
+ try {
16
+ return useApi(swappableComponentsApiRef);
17
+ } catch (e) {
18
+ return void 0;
19
+ }
20
+ };
21
+ function createSwappableComponent(options) {
22
+ const FallbackComponent = (p) => /* @__PURE__ */ jsx("div", { "data-testid": options.id, ...p });
23
+ const ref = OpaqueSwappableComponentRef.createInstance("v1", {
24
+ id: options.id,
25
+ TProps: null,
26
+ TExternalProps: null,
27
+ toString() {
28
+ return `SwappableComponentRef{id=${options.id}}`;
29
+ },
30
+ defaultComponent: lazy(async () => {
31
+ const Component = await options.loader?.() ?? FallbackComponent;
32
+ return { default: Component };
33
+ }),
34
+ transformProps: options.transformProps
35
+ });
36
+ const ComponentRefImpl = (props) => {
37
+ const api = useComponentRefApi();
38
+ if (!api) {
39
+ const internalRef = OpaqueSwappableComponentRef.toInternal(ref);
40
+ const Component2 = internalRef.defaultComponent;
41
+ const innerProps = internalRef.transformProps?.(props) ?? props;
42
+ return /* @__PURE__ */ jsx(Component2, { ...innerProps });
43
+ }
44
+ const Component = api.getComponent(ref);
45
+ return /* @__PURE__ */ jsx(Component, { ...props });
46
+ };
47
+ Object.assign(ComponentRefImpl, { ref });
48
+ return ComponentRefImpl;
49
+ }
50
+
51
+ export { createSwappableComponent };
52
+ //# sourceMappingURL=createSwappableComponent.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSwappableComponent.esm.js","sources":["../../src/components/createSwappableComponent.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { OpaqueSwappableComponentRef } from '@internal/frontend';\nimport { swappableComponentsApiRef, useApi } from '../apis';\nimport { lazy } from 'react';\n\n/** @public */\nexport type SwappableComponentRef<\n TInnerComponentProps extends {} = {},\n TExternalComponentProps extends {} = TInnerComponentProps,\n> = {\n id: string;\n TProps: TInnerComponentProps;\n TExternalProps: TExternalComponentProps;\n $$type: '@backstage/SwappableComponentRef';\n};\n\n/**\n * Options for creating an SwappableComponent.\n *\n * @public\n */\nexport type CreateSwappableComponentOptions<\n TInnerComponentProps extends {},\n TExternalComponentProps extends {} = TInnerComponentProps,\n> = {\n id: string;\n loader?:\n | (() => (props: TInnerComponentProps) => JSX.Element | null)\n | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>);\n transformProps?: (props: TExternalComponentProps) => TInnerComponentProps;\n};\n\nconst useComponentRefApi = () => {\n try {\n return useApi(swappableComponentsApiRef);\n } catch (e) {\n return undefined;\n }\n};\n\n/**\n * Creates a SwappableComponent that can be used to render the component, optionally overridden by the app.\n *\n * @public\n */\nexport function createSwappableComponent<\n TInnerComponentProps extends {},\n TExternalComponentProps extends {} = TInnerComponentProps,\n>(\n options: CreateSwappableComponentOptions<\n TInnerComponentProps,\n TExternalComponentProps\n >,\n): {\n (props: TExternalComponentProps): JSX.Element | null;\n ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;\n} {\n const FallbackComponent = (p: JSX.IntrinsicAttributes) => (\n <div data-testid={options.id} {...p} />\n );\n\n const ref = OpaqueSwappableComponentRef.createInstance('v1', {\n id: options.id,\n TProps: null as unknown as TInnerComponentProps,\n TExternalProps: null as unknown as TExternalComponentProps,\n toString() {\n return `SwappableComponentRef{id=${options.id}}`;\n },\n defaultComponent: lazy(async () => {\n const Component = (await options.loader?.()) ?? FallbackComponent;\n return { default: Component };\n }) as (typeof OpaqueSwappableComponentRef.TInternal)['defaultComponent'],\n transformProps:\n options.transformProps as (typeof OpaqueSwappableComponentRef.TInternal)['transformProps'],\n });\n\n const ComponentRefImpl = (props: TExternalComponentProps) => {\n const api = useComponentRefApi();\n\n if (!api) {\n const internalRef = OpaqueSwappableComponentRef.toInternal(ref);\n const Component = internalRef.defaultComponent;\n const innerProps = internalRef.transformProps?.(props) ?? props;\n return <Component {...innerProps} />;\n }\n\n const Component = api.getComponent<any>(ref);\n return <Component {...props} />;\n };\n\n Object.assign(ComponentRefImpl, { ref });\n\n return ComponentRefImpl as {\n (props: TExternalComponentProps): JSX.Element | null;\n ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;\n };\n}\n"],"names":["Component"],"mappings":";;;;;;;;;;;;;AA+CA,MAAM,qBAAqB,MAAM;AAC/B,EAAI,IAAA;AACF,IAAA,OAAO,OAAO,yBAAyB,CAAA;AAAA,WAChC,CAAG,EAAA;AACV,IAAO,OAAA,KAAA,CAAA;AAAA;AAEX,CAAA;AAOO,SAAS,yBAId,OAOA,EAAA;AACA,EAAM,MAAA,iBAAA,GAAoB,CAAC,CACzB,qBAAA,GAAA,CAAC,SAAI,aAAa,EAAA,OAAA,CAAQ,EAAK,EAAA,GAAG,CAAG,EAAA,CAAA;AAGvC,EAAM,MAAA,GAAA,GAAM,2BAA4B,CAAA,cAAA,CAAe,IAAM,EAAA;AAAA,IAC3D,IAAI,OAAQ,CAAA,EAAA;AAAA,IACZ,MAAQ,EAAA,IAAA;AAAA,IACR,cAAgB,EAAA,IAAA;AAAA,IAChB,QAAW,GAAA;AACT,MAAO,OAAA,CAAA,yBAAA,EAA4B,QAAQ,EAAE,CAAA,CAAA,CAAA;AAAA,KAC/C;AAAA,IACA,gBAAA,EAAkB,KAAK,YAAY;AACjC,MAAA,MAAM,SAAa,GAAA,MAAM,OAAQ,CAAA,MAAA,IAAe,IAAA,iBAAA;AAChD,MAAO,OAAA,EAAE,SAAS,SAAU,EAAA;AAAA,KAC7B,CAAA;AAAA,IACD,gBACE,OAAQ,CAAA;AAAA,GACX,CAAA;AAED,EAAM,MAAA,gBAAA,GAAmB,CAAC,KAAmC,KAAA;AAC3D,IAAA,MAAM,MAAM,kBAAmB,EAAA;AAE/B,IAAA,IAAI,CAAC,GAAK,EAAA;AACR,MAAM,MAAA,WAAA,GAAc,2BAA4B,CAAA,UAAA,CAAW,GAAG,CAAA;AAC9D,MAAA,MAAMA,aAAY,WAAY,CAAA,gBAAA;AAC9B,MAAA,MAAM,UAAa,GAAA,WAAA,CAAY,cAAiB,GAAA,KAAK,CAAK,IAAA,KAAA;AAC1D,MAAA,uBAAQA,GAAAA,CAAAA,UAAAA,EAAA,EAAW,GAAG,UAAY,EAAA,CAAA;AAAA;AAGpC,IAAM,MAAA,SAAA,GAAY,GAAI,CAAA,YAAA,CAAkB,GAAG,CAAA;AAC3C,IAAO,uBAAA,GAAA,CAAC,SAAW,EAAA,EAAA,GAAG,KAAO,EAAA,CAAA;AAAA,GAC/B;AAEA,EAAA,MAAA,CAAO,MAAO,CAAA,gBAAA,EAAkB,EAAE,GAAA,EAAK,CAAA;AAEvC,EAAO,OAAA,gBAAA;AAIT;;;;"}
@@ -0,0 +1,9 @@
1
+ import { OpaqueType } from '../../../opaque-internal/src/OpaqueType.esm.js';
2
+
3
+ const OpaqueSwappableComponentRef = OpaqueType.create({
4
+ versions: ["v1"],
5
+ type: "@backstage/SwappableComponentRef"
6
+ });
7
+
8
+ export { OpaqueSwappableComponentRef };
9
+ //# sourceMappingURL=InternalSwappableComponentRef.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InternalSwappableComponentRef.esm.js","sources":["../../../../../frontend-internal/src/wiring/InternalSwappableComponentRef.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SwappableComponentRef } from '@backstage/frontend-plugin-api';\nimport { OpaqueType } from '@internal/opaque';\n\nexport const OpaqueSwappableComponentRef = OpaqueType.create<{\n public: SwappableComponentRef;\n versions: {\n readonly version: 'v1';\n readonly transformProps?: (props: object) => object;\n readonly defaultComponent: (props: object) => JSX.Element | null;\n };\n}>({\n versions: ['v1'],\n type: '@backstage/SwappableComponentRef',\n});\n"],"names":[],"mappings":";;AAmBa,MAAA,2BAAA,GAA8B,WAAW,MAOnD,CAAA;AAAA,EACD,QAAA,EAAU,CAAC,IAAI,CAAA;AAAA,EACf,IAAM,EAAA;AACR,CAAC;;;;"}
@@ -1,4 +1,7 @@
1
- function createExtensionDataContainer(values, declaredRefs) {
1
+ function createExtensionDataContainer(values, contextName, declaredRefs) {
2
+ if (typeof values !== "object" || !values?.[Symbol.iterator]) {
3
+ throw new Error(`${contextName} did not provide an iterable object`);
4
+ }
2
5
  const container = /* @__PURE__ */ new Map();
3
6
  const verifyRefs = declaredRefs && new Map(declaredRefs.map((ref) => [ref.id, ref]));
4
7
  for (const output of values) {
@@ -1 +1 @@
1
- {"version":3,"file":"createExtensionDataContainer.esm.js","sources":["../../../../../frontend-internal/src/wiring/createExtensionDataContainer.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionDataValue,\n} from '@backstage/frontend-plugin-api';\n\nexport function createExtensionDataContainer<UData extends ExtensionDataRef>(\n values: Iterable<\n UData extends ExtensionDataRef<infer IData, infer IId>\n ? ExtensionDataValue<IData, IId>\n : never\n >,\n declaredRefs?: ExtensionDataRef<any, any, any>[],\n): ExtensionDataContainer<UData> {\n const container = new Map<string, ExtensionDataValue<any, any>>();\n const verifyRefs =\n declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));\n\n for (const output of values) {\n if (verifyRefs) {\n if (!verifyRefs.delete(output.id)) {\n throw new Error(\n `extension data '${output.id}' was provided but not declared`,\n );\n }\n }\n container.set(output.id, output);\n }\n\n const remainingRefs =\n verifyRefs &&\n Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);\n if (remainingRefs && remainingRefs.length > 0) {\n throw new Error(\n `missing required extension data value(s) '${remainingRefs\n .map(ref => ref.id)\n .join(', ')}'`,\n );\n }\n\n return {\n get(ref) {\n return container.get(ref.id)?.value;\n },\n [Symbol.iterator]() {\n return container.values();\n },\n } as ExtensionDataContainer<UData>;\n}\n"],"names":[],"mappings":"AAsBgB,SAAA,4BAAA,CACd,QAKA,YAC+B,EAAA;AAC/B,EAAM,MAAA,SAAA,uBAAgB,GAA0C,EAAA;AAChE,EAAA,MAAM,UACJ,GAAA,YAAA,IAAgB,IAAI,GAAA,CAAI,YAAa,CAAA,GAAA,CAAI,CAAO,GAAA,KAAA,CAAC,GAAI,CAAA,EAAA,EAAI,GAAG,CAAC,CAAC,CAAA;AAEhE,EAAA,KAAA,MAAW,UAAU,MAAQ,EAAA;AAC3B,IAAA,IAAI,UAAY,EAAA;AACd,MAAA,IAAI,CAAC,UAAA,CAAW,MAAO,CAAA,MAAA,CAAO,EAAE,CAAG,EAAA;AACjC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,gBAAA,EAAmB,OAAO,EAAE,CAAA,+BAAA;AAAA,SAC9B;AAAA;AACF;AAEF,IAAU,SAAA,CAAA,GAAA,CAAI,MAAO,CAAA,EAAA,EAAI,MAAM,CAAA;AAAA;AAGjC,EAAA,MAAM,aACJ,GAAA,UAAA,IACA,KAAM,CAAA,IAAA,CAAK,UAAW,CAAA,MAAA,EAAQ,CAAA,CAAE,MAAO,CAAA,CAAA,GAAA,KAAO,CAAC,GAAA,CAAI,OAAO,QAAQ,CAAA;AACpE,EAAI,IAAA,aAAA,IAAiB,aAAc,CAAA,MAAA,GAAS,CAAG,EAAA;AAC7C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,0CAAA,EAA6C,cAC1C,GAAI,CAAA,CAAA,GAAA,KAAO,IAAI,EAAE,CAAA,CACjB,IAAK,CAAA,IAAI,CAAC,CAAA,CAAA;AAAA,KACf;AAAA;AAGF,EAAO,OAAA;AAAA,IACL,IAAI,GAAK,EAAA;AACP,MAAA,OAAO,SAAU,CAAA,GAAA,CAAI,GAAI,CAAA,EAAE,CAAG,EAAA,KAAA;AAAA,KAChC;AAAA,IACA,CAAC,MAAO,CAAA,QAAQ,CAAI,GAAA;AAClB,MAAA,OAAO,UAAU,MAAO,EAAA;AAAA;AAC1B,GACF;AACF;;;;"}
1
+ {"version":3,"file":"createExtensionDataContainer.esm.js","sources":["../../../../../frontend-internal/src/wiring/createExtensionDataContainer.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ExtensionDataContainer,\n ExtensionDataRef,\n ExtensionDataValue,\n} from '@backstage/frontend-plugin-api';\n\nexport function createExtensionDataContainer<UData extends ExtensionDataRef>(\n values: Iterable<\n UData extends ExtensionDataRef<infer IData, infer IId>\n ? ExtensionDataValue<IData, IId>\n : never\n >,\n contextName: string,\n declaredRefs?: ExtensionDataRef<any, any, any>[],\n): ExtensionDataContainer<UData> {\n if (typeof values !== 'object' || !values?.[Symbol.iterator]) {\n throw new Error(`${contextName} did not provide an iterable object`);\n }\n\n const container = new Map<string, ExtensionDataValue<any, any>>();\n const verifyRefs =\n declaredRefs && new Map(declaredRefs.map(ref => [ref.id, ref]));\n\n for (const output of values) {\n if (verifyRefs) {\n if (!verifyRefs.delete(output.id)) {\n throw new Error(\n `extension data '${output.id}' was provided but not declared`,\n );\n }\n }\n container.set(output.id, output);\n }\n\n const remainingRefs =\n verifyRefs &&\n Array.from(verifyRefs.values()).filter(ref => !ref.config.optional);\n if (remainingRefs && remainingRefs.length > 0) {\n throw new Error(\n `missing required extension data value(s) '${remainingRefs\n .map(ref => ref.id)\n .join(', ')}'`,\n );\n }\n\n return {\n get(ref) {\n return container.get(ref.id)?.value;\n },\n [Symbol.iterator]() {\n return container.values();\n },\n } as ExtensionDataContainer<UData>;\n}\n"],"names":[],"mappings":"AAsBgB,SAAA,4BAAA,CACd,MAKA,EAAA,WAAA,EACA,YAC+B,EAAA;AAC/B,EAAA,IAAI,OAAO,MAAW,KAAA,QAAA,IAAY,CAAC,MAAS,GAAA,MAAA,CAAO,QAAQ,CAAG,EAAA;AAC5D,IAAA,MAAM,IAAI,KAAA,CAAM,CAAG,EAAA,WAAW,CAAqC,mCAAA,CAAA,CAAA;AAAA;AAGrE,EAAM,MAAA,SAAA,uBAAgB,GAA0C,EAAA;AAChE,EAAA,MAAM,UACJ,GAAA,YAAA,IAAgB,IAAI,GAAA,CAAI,YAAa,CAAA,GAAA,CAAI,CAAO,GAAA,KAAA,CAAC,GAAI,CAAA,EAAA,EAAI,GAAG,CAAC,CAAC,CAAA;AAEhE,EAAA,KAAA,MAAW,UAAU,MAAQ,EAAA;AAC3B,IAAA,IAAI,UAAY,EAAA;AACd,MAAA,IAAI,CAAC,UAAA,CAAW,MAAO,CAAA,MAAA,CAAO,EAAE,CAAG,EAAA;AACjC,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,gBAAA,EAAmB,OAAO,EAAE,CAAA,+BAAA;AAAA,SAC9B;AAAA;AACF;AAEF,IAAU,SAAA,CAAA,GAAA,CAAI,MAAO,CAAA,EAAA,EAAI,MAAM,CAAA;AAAA;AAGjC,EAAA,MAAM,aACJ,GAAA,UAAA,IACA,KAAM,CAAA,IAAA,CAAK,UAAW,CAAA,MAAA,EAAQ,CAAA,CAAE,MAAO,CAAA,CAAA,GAAA,KAAO,CAAC,GAAA,CAAI,OAAO,QAAQ,CAAA;AACpE,EAAI,IAAA,aAAA,IAAiB,aAAc,CAAA,MAAA,GAAS,CAAG,EAAA;AAC7C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,0CAAA,EAA6C,cAC1C,GAAI,CAAA,CAAA,GAAA,KAAO,IAAI,EAAE,CAAA,CACjB,IAAK,CAAA,IAAI,CAAC,CAAA,CAAA;AAAA,KACf;AAAA;AAGF,EAAO,OAAA;AAAA,IACL,IAAI,GAAK,EAAA;AACP,MAAA,OAAO,SAAU,CAAA,GAAA,CAAI,GAAI,CAAA,EAAE,CAAG,EAAA,KAAA;AAAA,KAChC;AAAA,IACA,CAAC,MAAO,CAAA,QAAQ,CAAI,GAAA;AAClB,MAAA,OAAO,UAAU,MAAO,EAAA;AAAA;AAC1B,GACF;AACF;;;;"}