@adobe/aio-commerce-lib-admin-ui 0.2.0-beta-20260714082406 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -23
- package/dist/cjs/mass-actions/index.cjs +1 -55
- package/dist/cjs/mass-actions/index.d.cts +1 -42
- package/dist/cjs/web/index.cjs +122 -80
- package/dist/cjs/web/index.d.cts +50 -36
- package/dist/es/mass-actions/index.d.mts +1 -42
- package/dist/es/mass-actions/index.mjs +1 -53
- package/dist/es/web/index.d.mts +50 -36
- package/dist/es/web/index.mjs +122 -80
- package/package.json +5 -5
package/dist/es/web/index.mjs
CHANGED
|
@@ -48,6 +48,37 @@ function isControlFrame() {
|
|
|
48
48
|
return isEmbeddedInHost() && !window.name;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region source/web/react/result.ts
|
|
53
|
+
/** Creates a successful data result. */
|
|
54
|
+
function ok(data) {
|
|
55
|
+
return {
|
|
56
|
+
data,
|
|
57
|
+
error: null
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Creates a failed data result. */
|
|
61
|
+
function error(message, options) {
|
|
62
|
+
return {
|
|
63
|
+
data: null,
|
|
64
|
+
error: new Error(message, options)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Creates a successful actions result. */
|
|
68
|
+
function okActions(actions) {
|
|
69
|
+
return {
|
|
70
|
+
actions,
|
|
71
|
+
error: null
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Creates a failed actions result. */
|
|
75
|
+
function actionsError(message, options) {
|
|
76
|
+
return {
|
|
77
|
+
actions: null,
|
|
78
|
+
error: new Error(message, options)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
51
82
|
//#endregion
|
|
52
83
|
//#region source/web/react/auth/context/ims-context.tsx
|
|
53
84
|
const ImsContextValue = createContext(void 0);
|
|
@@ -55,14 +86,13 @@ const ImsContextValue = createContext(void 0);
|
|
|
55
86
|
* Returns the IMS credentials provided by the host. Works inside the Commerce Admin and the
|
|
56
87
|
* Experience Cloud shell.
|
|
57
88
|
*
|
|
58
|
-
*
|
|
59
|
-
* Commerce Admin and the Experience Cloud shell).
|
|
89
|
+
* Returns an error when no host provides credentials.
|
|
60
90
|
*/
|
|
61
91
|
function useIms() {
|
|
62
92
|
const credentials = use(ImsContextValue);
|
|
63
|
-
if (credentials === void 0)
|
|
64
|
-
if (!credentials)
|
|
65
|
-
return credentials;
|
|
93
|
+
if (credentials === void 0) return error("useIms must be used inside an ImsContextProvider.");
|
|
94
|
+
if (!credentials) return error("useIms requires running inside the Commerce Admin or the Experience Cloud shell, which provide the IMS credentials.");
|
|
95
|
+
return ok(credentials);
|
|
66
96
|
}
|
|
67
97
|
/**
|
|
68
98
|
* Provides the IMS credentials for a mounted Admin UI iframe app.
|
|
@@ -84,9 +114,7 @@ const SharedContextValue = createContext(void 0);
|
|
|
84
114
|
* Returns the current Commerce shared context provider state.
|
|
85
115
|
*/
|
|
86
116
|
function useInternalSharedContext() {
|
|
87
|
-
|
|
88
|
-
if (context === void 0) throw new Error("useSharedContext must be used inside a SharedContextProvider, which is only available in the Commerce Admin.");
|
|
89
|
-
return context;
|
|
117
|
+
return use(SharedContextValue);
|
|
90
118
|
}
|
|
91
119
|
/**
|
|
92
120
|
* Returns the current Commerce shared context. The guest connection is already established by
|
|
@@ -96,26 +124,29 @@ function useInternalSharedContext() {
|
|
|
96
124
|
* Prefer a purpose-built hook ({@link useCommerce}, {@link useMassActionContext},
|
|
97
125
|
* {@link useOrderViewButtonContext}) when one covers what you need.
|
|
98
126
|
*
|
|
99
|
-
* @throws If used outside a {@link SharedContextProvider}.
|
|
100
|
-
*
|
|
101
127
|
* @example
|
|
102
128
|
* ```tsx
|
|
103
129
|
* import { useSharedContext } from "@adobe/aio-commerce-lib-admin-ui/web";
|
|
104
130
|
*
|
|
105
131
|
* function ImsTokenLabel() {
|
|
106
|
-
* const {
|
|
107
|
-
*
|
|
132
|
+
* const { data, error } = useSharedContext();
|
|
133
|
+
* if (error) return null;
|
|
134
|
+
* return <span>{data.sharedContext.get("imsToken")}</span>;
|
|
108
135
|
* }
|
|
109
136
|
* ```
|
|
110
137
|
*/
|
|
111
138
|
function useSharedContext() {
|
|
112
|
-
const
|
|
113
|
-
const sharedContext = useLiveSharedContext(guestConnection);
|
|
114
|
-
return {
|
|
115
|
-
|
|
116
|
-
host
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
const context = useInternalSharedContext();
|
|
140
|
+
const sharedContext = useLiveSharedContext(context?.guestConnection);
|
|
141
|
+
return useMemo(() => {
|
|
142
|
+
if (!context) return error("useSharedContext must be used inside a SharedContextProvider, which is only available in the Commerce Admin.");
|
|
143
|
+
if (!sharedContext) return error("The Commerce host did not provide a shared context for this extension.");
|
|
144
|
+
return ok({
|
|
145
|
+
extensionId: context.extensionId,
|
|
146
|
+
host: context.guestConnection.host,
|
|
147
|
+
sharedContext
|
|
148
|
+
});
|
|
149
|
+
}, [context, sharedContext]);
|
|
119
150
|
}
|
|
120
151
|
/**
|
|
121
152
|
* Tracks the live UIX `sharedContext` for a connection.
|
|
@@ -127,7 +158,10 @@ function useSharedContext() {
|
|
|
127
158
|
* @param guestConnection - The established guest connection.
|
|
128
159
|
*/
|
|
129
160
|
function useLiveSharedContext(guestConnection) {
|
|
130
|
-
return useSyncExternalStore(useCallback((onContextChange) =>
|
|
161
|
+
return useSyncExternalStore(useCallback((onContextChange) => {
|
|
162
|
+
if (!guestConnection) return () => void 0;
|
|
163
|
+
return guestConnection.addEventListener("contextchange", onContextChange);
|
|
164
|
+
}, [guestConnection]), () => guestConnection?.sharedContext ?? null);
|
|
131
165
|
}
|
|
132
166
|
/**
|
|
133
167
|
* Provides the Commerce shared context for a mounted Admin UI iframe app.
|
|
@@ -149,28 +183,33 @@ function SharedContextProvider(props) {
|
|
|
149
183
|
//#endregion
|
|
150
184
|
//#region source/web/react/promise-cache.ts
|
|
151
185
|
/**
|
|
152
|
-
* Creates a keyed cache that memoizes in-flight,
|
|
186
|
+
* Creates a keyed cache that memoizes in-flight, successful, and failed promises, giving `use()`
|
|
153
187
|
* the reference-stable promise it needs while suspended and single-flighting side-effecting
|
|
154
188
|
* establishment calls across re-renders, remounts, and StrictMode double-invocation.
|
|
155
189
|
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
190
|
+
* Promise rejections are failures by default. Pass `isFailure` when a fulfilled value, such as an
|
|
191
|
+
* error result, must also be retryable. Failed entries remain cached until `evictIfFailed` is
|
|
192
|
+
* called, so retries never replace a stable promise during render.
|
|
193
|
+
*
|
|
194
|
+
* @param isFailure - Determines whether a fulfilled value represents a failure.
|
|
158
195
|
*/
|
|
159
|
-
function createRetryablePromiseCache() {
|
|
196
|
+
function createRetryablePromiseCache(isFailure = () => false) {
|
|
160
197
|
const cache = /* @__PURE__ */ new Map();
|
|
161
198
|
return {
|
|
162
|
-
|
|
163
|
-
if (cache.get(key)?.
|
|
199
|
+
evictIfFailed(key) {
|
|
200
|
+
if (cache.get(key)?.failed) cache.delete(key);
|
|
164
201
|
},
|
|
165
202
|
get(key, create) {
|
|
166
203
|
const cached = cache.get(key);
|
|
167
204
|
if (cached) return cached.promise;
|
|
168
205
|
const entry = {
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
failed: false,
|
|
207
|
+
promise: create()
|
|
171
208
|
};
|
|
172
|
-
entry.promise.
|
|
173
|
-
entry.
|
|
209
|
+
entry.promise.then((value) => {
|
|
210
|
+
entry.failed = isFailure(value);
|
|
211
|
+
}).catch(() => {
|
|
212
|
+
entry.failed = true;
|
|
174
213
|
});
|
|
175
214
|
cache.set(key, entry);
|
|
176
215
|
return entry.promise;
|
|
@@ -180,7 +219,7 @@ function createRetryablePromiseCache() {
|
|
|
180
219
|
|
|
181
220
|
//#endregion
|
|
182
221
|
//#region source/web/react/commerce/hooks/use-commerce.ts
|
|
183
|
-
const commerceHosts = createRetryablePromiseCache();
|
|
222
|
+
const commerceHosts = createRetryablePromiseCache((result) => result.error !== null);
|
|
184
223
|
/**
|
|
185
224
|
* Returns the cached Commerce Admin host promise for an extension, resolving it once over the
|
|
186
225
|
* given guest connection. The value is static for the lifetime of the connection.
|
|
@@ -189,24 +228,31 @@ const commerceHosts = createRetryablePromiseCache();
|
|
|
189
228
|
* @param connection - The established guest connection.
|
|
190
229
|
*/
|
|
191
230
|
function getCommerceHostPromise(extensionId, connection) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
231
|
+
return commerceHosts.get(extensionId, async () => {
|
|
232
|
+
const { integration } = connection.host;
|
|
233
|
+
if (!integration) return error("The host does not provide the integration API needed to resolve the Commerce host.");
|
|
234
|
+
try {
|
|
235
|
+
return ok({ commerceHost: await integration.getCommerceHost() });
|
|
236
|
+
} catch (cause) {
|
|
237
|
+
return error("Failed to resolve the Commerce host.", { cause });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
195
240
|
}
|
|
196
241
|
/** Drops a failed Commerce host resolution for `extensionId`, so a later render retries it. */
|
|
197
242
|
function retryCommerceHost(extensionId) {
|
|
198
|
-
commerceHosts.
|
|
243
|
+
commerceHosts.evictIfFailed(extensionId);
|
|
199
244
|
}
|
|
200
245
|
/**
|
|
201
246
|
* Returns the host (domain) of the Commerce Admin the extension is embedded in, resolving it over
|
|
202
247
|
* the guest connection.
|
|
203
248
|
*
|
|
204
|
-
*
|
|
205
|
-
* Commerce integration API.
|
|
249
|
+
* Returns an error when used outside a Commerce Admin UI frame, when the host does not expose the
|
|
250
|
+
* Commerce integration API, or when resolving the host fails.
|
|
206
251
|
*/
|
|
207
252
|
function useCommerce() {
|
|
208
|
-
const
|
|
209
|
-
|
|
253
|
+
const context = useInternalSharedContext();
|
|
254
|
+
if (!context) return error("useCommerce requires running inside the Commerce Admin.");
|
|
255
|
+
return use(getCommerceHostPromise(context.extensionId, context.guestConnection));
|
|
210
256
|
}
|
|
211
257
|
|
|
212
258
|
//#endregion
|
|
@@ -215,28 +261,31 @@ function useCommerce() {
|
|
|
215
261
|
* Returns the context for a mass-action extension point: the selected row IDs the action was
|
|
216
262
|
* triggered with. The value is read from the host-provided Commerce context.
|
|
217
263
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
264
|
+
* Returns an error outside the Commerce shared context, or when the mass-action selection is
|
|
265
|
+
* missing, empty, or contains a non-string row ID.
|
|
220
266
|
*/
|
|
221
267
|
function useMassActionContext() {
|
|
222
|
-
const {
|
|
268
|
+
const { data, error: contextError } = useSharedContext();
|
|
223
269
|
return useMemo(() => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
return
|
|
227
|
-
|
|
270
|
+
if (contextError) return error(contextError.message, { cause: contextError });
|
|
271
|
+
const selectedIds = data.sharedContext.get("selectedIds");
|
|
272
|
+
if (!Array.isArray(selectedIds)) return error("Could not find `selectedIds` in the Commerce shared context. Is this frame running as a mass-action extension point?");
|
|
273
|
+
if (selectedIds.length === 0) return error("No rows selected. A mass-action extension point must be triggered with at least one selected row.");
|
|
274
|
+
if (selectedIds.some((id) => typeof id !== "string")) return error("Some of the `selectedIds` in the Commerce shared context are not strings. All selected row IDs must be strings.");
|
|
275
|
+
return ok({ selectedIds });
|
|
276
|
+
}, [data, contextError]);
|
|
228
277
|
}
|
|
229
278
|
/**
|
|
230
279
|
* Returns the context for an order view-button extension point: the order ID the button was
|
|
231
280
|
* triggered from.
|
|
232
281
|
*
|
|
233
|
-
*
|
|
282
|
+
* Returns an error when no order ID is present in the page URL.
|
|
234
283
|
*/
|
|
235
284
|
function useOrderViewButtonContext() {
|
|
236
285
|
return useMemo(() => {
|
|
237
286
|
const orderId = parseOrderId(globalThis.location.href);
|
|
238
|
-
if (orderId === null)
|
|
239
|
-
return { orderId };
|
|
287
|
+
if (orderId === null) return error("Could not find an order ID. Is this frame running as an order view-button extension point?");
|
|
288
|
+
return ok({ orderId });
|
|
240
289
|
}, []);
|
|
241
290
|
}
|
|
242
291
|
|
|
@@ -245,51 +294,39 @@ function useOrderViewButtonContext() {
|
|
|
245
294
|
/**
|
|
246
295
|
* Returns typed helpers for interacting with the Commerce Admin host.
|
|
247
296
|
*
|
|
248
|
-
* @throws If called before the guest connection is established, or when the host frame actions
|
|
249
|
-
* are unavailable.
|
|
250
|
-
*
|
|
251
297
|
* @example
|
|
252
298
|
* ```tsx
|
|
253
299
|
* import { useHostConnection } from "@adobe/aio-commerce-lib-admin-ui/web";
|
|
254
300
|
*
|
|
255
301
|
* function DoneButton() {
|
|
256
|
-
* const {
|
|
257
|
-
*
|
|
302
|
+
* const { actions, error } = useHostConnection();
|
|
303
|
+
* if (error) return null;
|
|
304
|
+
* return <button onClick={actions.close}>Done</button>;
|
|
258
305
|
* }
|
|
259
306
|
* ```
|
|
260
307
|
*/
|
|
261
308
|
function useHostConnection() {
|
|
262
|
-
const {
|
|
309
|
+
const { data, error: contextError } = useSharedContext();
|
|
263
310
|
return useMemo(() => {
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
}, [host]);
|
|
311
|
+
if (contextError) return actionsError(contextError.message, { cause: contextError });
|
|
312
|
+
const { field } = data.host;
|
|
313
|
+
if (!field) return actionsError("Host frame actions are unavailable. They require an established guest connection with a host that exposes frame actions.");
|
|
314
|
+
return okActions({
|
|
315
|
+
close: () => field.close(),
|
|
316
|
+
closeWithError: () => field.onError()
|
|
317
|
+
});
|
|
318
|
+
}, [data, contextError]);
|
|
274
319
|
}
|
|
275
320
|
|
|
276
321
|
//#endregion
|
|
277
322
|
//#region source/web/react/routing/lib.ts
|
|
278
323
|
const HASH_ROUTE_PREFIX_PATTERN = /^[#/]+/u;
|
|
279
324
|
/**
|
|
280
|
-
* Determines if a given route is an index route.
|
|
281
|
-
* @param route The route to check.
|
|
282
|
-
*/
|
|
283
|
-
function isIndexRoute(route) {
|
|
284
|
-
return "index" in route && route.index;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
325
|
* Returns the path for a given route, removing any leading hash or slash characters.
|
|
288
326
|
* @param route The route to get the path for.
|
|
289
327
|
*/
|
|
290
328
|
function getRoutePath(route) {
|
|
291
|
-
|
|
292
|
-
return route.path.replace(HASH_ROUTE_PREFIX_PATTERN, "");
|
|
329
|
+
return route.path.replace(HASH_ROUTE_PREFIX_PATTERN, "") || "/";
|
|
293
330
|
}
|
|
294
331
|
/**
|
|
295
332
|
* Returns the "to" path for a given route, removing any leading hash or slash characters.
|
|
@@ -431,7 +468,7 @@ function useGuestConnection(extensionId) {
|
|
|
431
468
|
}
|
|
432
469
|
/** Drops a failed connection for `extensionId`, so a later render re-attaches. */
|
|
433
470
|
function retryGuestConnection(extensionId) {
|
|
434
|
-
guestConnections.
|
|
471
|
+
guestConnections.evictIfFailed(extensionId);
|
|
435
472
|
}
|
|
436
473
|
|
|
437
474
|
//#endregion
|
|
@@ -622,9 +659,10 @@ function CommerceGuestConnection(props) {
|
|
|
622
659
|
}
|
|
623
660
|
/** Renders IMS-gated route content for a Commerce Admin UI frame. */
|
|
624
661
|
function CommerceExtensionContent() {
|
|
625
|
-
const {
|
|
662
|
+
const { data, error } = useSharedContext();
|
|
663
|
+
if (error) throw error;
|
|
626
664
|
return /* @__PURE__ */ jsx(ImsContextProvider, {
|
|
627
|
-
credentials: resolveCommerceImsCredentials(sharedContext),
|
|
665
|
+
credentials: resolveCommerceImsCredentials(data.sharedContext),
|
|
628
666
|
children: /* @__PURE__ */ jsx(Outlet, {})
|
|
629
667
|
});
|
|
630
668
|
}
|
|
@@ -780,11 +818,12 @@ function Entrypoint(props) {
|
|
|
780
818
|
*
|
|
781
819
|
* createExtensionApp({
|
|
782
820
|
* metadata: { extensionId: "my-extension-id" },
|
|
783
|
-
*
|
|
821
|
+
* menu: <MainPage />,
|
|
784
822
|
* });
|
|
785
823
|
* ```
|
|
786
824
|
*/
|
|
787
|
-
function createExtensionApp({ metadata, routes, root: customRoot }) {
|
|
825
|
+
function createExtensionApp({ menu, metadata, routes = [], root: customRoot }) {
|
|
826
|
+
if (menu !== void 0 && routes.some((route) => getRouteTo(route.path) === "/")) throw new Error("The \"/\" route is reserved for the menu. Pass its element through the menu option instead.");
|
|
788
827
|
const rootElement = customRoot ?? document.getElementById("root");
|
|
789
828
|
if (!rootElement) throw new Error("Could not find an element with id \"root\".");
|
|
790
829
|
const root = createRoot(rootElement);
|
|
@@ -793,7 +832,10 @@ function createExtensionApp({ metadata, routes, root: customRoot }) {
|
|
|
793
832
|
extensionId: metadata.extensionId,
|
|
794
833
|
initialConfigurationPromise,
|
|
795
834
|
runtime
|
|
796
|
-
}), routes
|
|
835
|
+
}), menu === void 0 ? routes : [{
|
|
836
|
+
element: menu,
|
|
837
|
+
path: "/"
|
|
838
|
+
}, ...routes]);
|
|
797
839
|
root.render(/* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(RouterProvider, { router }) }));
|
|
798
840
|
};
|
|
799
841
|
try {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@adobe/aio-commerce-lib-admin-ui",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"author": "Adobe Inc.",
|
|
5
|
-
"version": "0.2.0
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"private": false,
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=22 <=24"
|
|
@@ -113,8 +113,8 @@
|
|
|
113
113
|
"ky": "^1.9.0",
|
|
114
114
|
"react-error-boundary": "^6.1.2",
|
|
115
115
|
"valibot": "^1.1.0",
|
|
116
|
-
"@adobe/aio-commerce-lib-api": "1.3.0
|
|
117
|
-
"@adobe/aio-commerce-lib-core": "1.2.0
|
|
116
|
+
"@adobe/aio-commerce-lib-api": "1.3.0",
|
|
117
|
+
"@adobe/aio-commerce-lib-core": "1.2.0"
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
120
|
"@react-spectrum/s2": "^1.5.1",
|
|
@@ -139,12 +139,12 @@
|
|
|
139
139
|
"react": "^19.2.7",
|
|
140
140
|
"react-dom": "^19.2.7",
|
|
141
141
|
"typescript": "^6.0.0",
|
|
142
|
-
"@aio-commerce-sdk/common-utils": "0.2.6
|
|
142
|
+
"@aio-commerce-sdk/common-utils": "0.2.6",
|
|
143
143
|
"@aio-commerce-sdk/config-tsdown": "1.0.1",
|
|
144
144
|
"@aio-commerce-sdk/config-typedoc": "1.0.0",
|
|
145
145
|
"@aio-commerce-sdk/config-typescript": "1.0.0",
|
|
146
146
|
"@aio-commerce-sdk/config-vitest": "1.0.0",
|
|
147
|
-
"@aio-commerce-sdk/scripting-utils": "0.3.4
|
|
147
|
+
"@aio-commerce-sdk/scripting-utils": "0.3.4",
|
|
148
148
|
"@aio-commerce-sdk/scripts": "0.1.0"
|
|
149
149
|
},
|
|
150
150
|
"sideEffects": false,
|