@adobe/uix-host-react 0.6.3

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.
@@ -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
+