@adobe/uix-host-react 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,170 @@
1
+ /*
2
+ Copyright 2022 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { useCallback, useEffect, useMemo, useState } from "react";
14
+ import type {
15
+ GuestConnection,
16
+ GuestApis,
17
+ RemoteGuestApis,
18
+ VirtualApi,
19
+ } from "@adobe/uix-core";
20
+
21
+ import { Host, HostEvents } from "@adobe/uix-host";
22
+ import type { CapabilitySpec } from "@adobe/uix-host";
23
+ import { useHost } from "./useHost.js";
24
+
25
+ /**
26
+ * @internal
27
+ */
28
+ export interface TypedGuestConnection<T extends GuestApis>
29
+ extends GuestConnection {
30
+ id: GuestConnection["id"];
31
+ apis: RemoteGuestApis<T>;
32
+ }
33
+
34
+ /** @public */
35
+ export interface UseExtensionsConfig<
36
+ Incoming extends GuestApis,
37
+ Outgoing extends VirtualApi
38
+ > {
39
+ /**
40
+ * A {@link @adobe/uix-host#CapabilitySpec} describing the namespaced methods
41
+ * extensions must implement to be used by this component.
42
+ *
43
+ * @remarks
44
+ * This declaration is used to filter the extensions that will be
45
+ * returned; if they don't implement those methods, they will be filtered out.
46
+ */
47
+ requires?: CapabilitySpec<Incoming>;
48
+ /**
49
+ * A namespaced object of methods which extensions will be able to call.
50
+ *
51
+ * @remarks This is the counterpart of `requires`; in `requires`, the you
52
+ * describes methods the extension must implement that your host code will
53
+ * call, and in `provides`, you implement host methods that extensions will be
54
+ * able to call.
55
+ *
56
+ * Most cases for host-side methods will use the state of the component. This
57
+ * can cause unexpected bugs in React if the config callback is run on every
58
+ * render. **useExtensions caches the config callback by default!**
59
+ * So remember to pass a deps array, so that the config callback re-runs under
60
+ * the right conditions.
61
+ */
62
+ provides?: Outgoing;
63
+ /**
64
+ * Sets when re-render is triggered on extension load.
65
+ *
66
+ * @remarks
67
+ * Set to `each` to trigger a component re-render every time an individual
68
+ * extension loads, which may result in multiple UI updates. Set to `all` to
69
+ * wait until all extensions have loaded to re-render the component.
70
+ * @defaultValue "each"
71
+ */
72
+ updateOn?: "each" | "all";
73
+ }
74
+
75
+ /** @public */
76
+ export interface UseExtensionsResult<T extends GuestApis> {
77
+ /**
78
+ * A list of loaded guests which implement the methods specified in
79
+ * `requires`, represented as {@link @adobe/uix-host#Port} objects which
80
+ * present methods to be called.
81
+ */
82
+ extensions: TypedGuestConnection<T>[];
83
+ /**
84
+ * This is `true` until all extensions are loaded. Use for rendering spinners
85
+ * or other intermediate UI.
86
+ */
87
+ loading: boolean;
88
+ /**
89
+ * Populated with an Error if there were any problems during the load process.
90
+ */
91
+ error?: Error;
92
+ }
93
+
94
+ const NO_EXTENSIONS: [] = [];
95
+
96
+ /**
97
+ * Fetch extensions which implement an API, provide them methods, and use them.
98
+ *
99
+ * @remarks `useExtensions` does three things at once:
100
+ * - Gets all extensions which implement the APIs described in the `require` field
101
+ * - Exposes any functions defined in the `provide` field to those extensions
102
+ * - Returns an object whose `extensions` property is a list of `Port` objects representing those extensions
103
+ *
104
+ * useExtensions will trigger a re-render when extensions load. You can choose whether it triggers that rerender as each extension loads, or only after all extensions have loaded.
105
+ * @public
106
+ */
107
+ export function useExtensions<
108
+ Incoming extends GuestApis,
109
+ Outgoing extends VirtualApi
110
+ >(
111
+ configFactory: (host: Host) => UseExtensionsConfig<Incoming, Outgoing>,
112
+ deps: unknown[] = []
113
+ ): UseExtensionsResult<Incoming> {
114
+ const { host, error } = useHost();
115
+ if (error) {
116
+ return {
117
+ extensions: NO_EXTENSIONS,
118
+ loading: false,
119
+ error,
120
+ };
121
+ }
122
+
123
+ const baseDeps = [host, ...deps];
124
+ const {
125
+ requires,
126
+ provides,
127
+ updateOn = "each",
128
+ } = useMemo(() => configFactory(host), baseDeps);
129
+
130
+ const getExtensions = useCallback(() => {
131
+ const newExtensions = [];
132
+ const guests = host.getLoadedGuests(requires);
133
+ for (const guest of guests) {
134
+ if (provides) {
135
+ guest.provide(provides);
136
+ }
137
+ newExtensions.push(guest as unknown as TypedGuestConnection<Incoming>);
138
+ }
139
+ return newExtensions.length === 0 ? NO_EXTENSIONS : newExtensions;
140
+ }, [...baseDeps, requires]);
141
+
142
+ const subscribe = useCallback(
143
+ (handler: EventListener) => {
144
+ const eventName = updateOn === "all" ? "loadallguests" : "guestload";
145
+ host.addEventListener(eventName, handler);
146
+ return () => host.removeEventListener(eventName, handler);
147
+ },
148
+ [...baseDeps, updateOn]
149
+ );
150
+
151
+ const [extensions, setExtensions] = useState(() => getExtensions());
152
+ useEffect(() => {
153
+ return subscribe(() => {
154
+ setExtensions(getExtensions());
155
+ });
156
+ }, [subscribe, getExtensions]);
157
+
158
+ const [hostError, setHostError] = useState<Error>();
159
+ useEffect(
160
+ () =>
161
+ host.addEventListener(
162
+ "error",
163
+ (event: Extract<HostEvents, { detail: { error: Error } }>) =>
164
+ setHostError(event.detail.error)
165
+ ),
166
+ baseDeps
167
+ );
168
+
169
+ return { extensions, loading: !host.loading, error: hostError };
170
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ Copyright 2022 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { useContext } from "react";
14
+ import { Host } from "@adobe/uix-host";
15
+ import { ExtensionContext } from "../extension-context.js";
16
+
17
+ /**
18
+ * @public
19
+ */
20
+ export class OutsideOfExtensionContextError extends Error {
21
+ outsideOfExtensionContext: boolean;
22
+ constructor(msg: string) {
23
+ super(msg);
24
+ this.outsideOfExtensionContext = true;
25
+ Object.setPrototypeOf(this, OutsideOfExtensionContextError.prototype);
26
+ }
27
+ }
28
+
29
+ /** @public */
30
+ type UseHostResponse =
31
+ | { host: undefined; error: Error }
32
+ | { host: Host; error: undefined };
33
+
34
+ /**
35
+ * Retrieve the {@link @adobe/uix-host#Host} object hosting all extensions inside the current parent provider.
36
+ *
37
+ * @remarks Returns a `{ host, error }` tuple, not the host object directly.
38
+ * @beta
39
+ */
40
+ export function useHost(): UseHostResponse {
41
+ const host = useContext(ExtensionContext);
42
+ if (!(host instanceof Host)) {
43
+ const error = new OutsideOfExtensionContextError(
44
+ "Attempt to use extensions outside of ExtensionContext. Wrap extensible part of application with Extensible component."
45
+ );
46
+ return {
47
+ host: undefined,
48
+ error,
49
+ };
50
+ }
51
+ return { error: undefined, host };
52
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /*
2
+ Copyright 2022 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ export * from "@adobe/uix-host";
14
+ export * from "./components/index.js";
15
+ export * from "./hooks/index.js";
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "http://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig-base.json",
4
+ "compilerOptions": {
5
+ "jsx": "react",
6
+ "outDir": "dist",
7
+ "rootDir": "src"
8
+ },
9
+ "include": [
10
+ "src/**/*"
11
+ ],
12
+ "exclude": [
13
+ "src/**/*.test.tsx?"
14
+ ],
15
+ "references": [
16
+ {
17
+ "path": "../uix-core"
18
+ },
19
+ {
20
+ "path": "../uix-host"
21
+ }
22
+ ]
23
+ }
24
+