@gears-frontx/react 0.2.0-alpha.0 → 0.2.0-alpha.15

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/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  // src/FrontXProvider.tsx
2
- import { useMemo as useMemo2, useEffect as useEffect2, useState as useState2 } from "react";
2
+ import { useEffect as useEffect3 } from "react";
3
3
  import { Provider as ReduxProvider } from "react-redux";
4
- import {
5
- createFrontXApp
6
- } from "@gears-frontx/framework";
7
4
 
8
5
  // src/FrontXContext.tsx
9
6
  import { createContext, useContext } from "react";
@@ -41,7 +38,7 @@ var MfeProvider = ({ value, children }) => {
41
38
  import React, {
42
39
  createContext as createContext3,
43
40
  useContext as useContext3,
44
- useEffect,
41
+ useEffect as useEffect2,
45
42
  useLayoutEffect,
46
43
  useMemo,
47
44
  useState
@@ -56,6 +53,8 @@ import {
56
53
  useSuspenseQuery
57
54
  } from "@tanstack/react-query";
58
55
  import {
56
+ readQueryCacheScopeGeneration,
57
+ recordQueryCacheScopeWriteDropped,
59
58
  subscribeQueryCacheRuntimeChanged
60
59
  } from "@gears-frontx/framework";
61
60
 
@@ -164,13 +163,23 @@ function createQueryCache(queryClient) {
164
163
  };
165
164
  }
166
165
 
166
+ // src/hooks/useLatest.ts
167
+ import { useEffect, useRef } from "react";
168
+ function useLatest(value) {
169
+ const ref = useRef(value);
170
+ useEffect(() => {
171
+ ref.current = value;
172
+ });
173
+ return ref;
174
+ }
175
+
167
176
  // src/queryClient.tsx
168
177
  import { jsx as jsx2 } from "react/jsx-runtime";
169
178
  var APP_QUERY_CLIENT_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client");
170
179
  var APP_QUERY_CLIENT_RESOLVER_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-resolver");
171
180
  var APP_QUERY_CLIENT_ACTIVATOR_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-activator");
172
181
  var FrontXQueryClientContext = createContext3(void 0);
173
- var useCommitEffect = typeof globalThis.window === "undefined" ? useEffect : useLayoutEffect;
182
+ var useCommitEffect = typeof globalThis.window === "undefined" ? useEffect2 : useLayoutEffect;
174
183
  function resolveFrontXQueryClient(app) {
175
184
  const clientApp = app;
176
185
  return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();
@@ -261,24 +270,45 @@ function normalizeFetchStaleTime(staleTime) {
261
270
  if (staleTime === "static") {
262
271
  return Number.POSITIVE_INFINITY;
263
272
  }
273
+ if (typeof staleTime === "function") {
274
+ return 0;
275
+ }
264
276
  return typeof staleTime === "number" ? staleTime : void 0;
265
277
  }
266
- function resolveFetchStaleTime(queryClient, descriptor, overrideStaleTime) {
278
+ function resolveFetchStaleTime(queryClient, queryKey, descriptor, overrideStaleTime) {
279
+ const clientDefaults = {
280
+ ...queryClient.getDefaultOptions().queries,
281
+ ...queryClient.getQueryDefaults(queryKey)
282
+ };
267
283
  return normalizeFetchStaleTime(
268
- overrideStaleTime ?? descriptor.staleTime ?? queryClient.getDefaultOptions().queries?.staleTime
284
+ overrideStaleTime ?? descriptor.staleTime ?? clientDefaults.staleTime
269
285
  );
270
286
  }
287
+ function cacheTimeOptions(staleTime, gcTime) {
288
+ return {
289
+ ...staleTime === void 0 ? {} : { staleTime },
290
+ ...gcTime === void 0 ? {} : { gcTime }
291
+ };
292
+ }
271
293
  function fetchDescriptor(descriptor, options) {
272
294
  return descriptor.fetch(options);
273
295
  }
274
296
  function useFrontXQuery(descriptor, overrides) {
275
297
  const queryClient = useRequiredFrontXQueryClient();
276
- const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);
298
+ const staleTime = resolveFetchStaleTime(
299
+ queryClient,
300
+ descriptor.key,
301
+ descriptor,
302
+ overrides?.staleTime
303
+ );
277
304
  const result = useQuery({
278
305
  queryKey: descriptor.key,
279
306
  queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),
280
- staleTime: overrides?.staleTime ?? descriptor.staleTime,
281
- gcTime: overrides?.gcTime ?? descriptor.gcTime
307
+ ...cacheTimeOptions(
308
+ overrides?.staleTime ?? descriptor.staleTime,
309
+ overrides?.gcTime ?? descriptor.gcTime
310
+ ),
311
+ ...overrides?.enabled === void 0 ? {} : { enabled: overrides.enabled }
282
312
  });
283
313
  return {
284
314
  data: result.data,
@@ -291,12 +321,19 @@ function useFrontXQuery(descriptor, overrides) {
291
321
  }
292
322
  function useFrontXSuspenseQuery(descriptor, overrides) {
293
323
  const queryClient = useRequiredFrontXQueryClient();
294
- const staleTime = resolveFetchStaleTime(queryClient, descriptor, overrides?.staleTime);
324
+ const staleTime = resolveFetchStaleTime(
325
+ queryClient,
326
+ descriptor.key,
327
+ descriptor,
328
+ overrides?.staleTime
329
+ );
295
330
  const result = useSuspenseQuery({
296
331
  queryKey: descriptor.key,
297
332
  queryFn: ({ signal }) => fetchDescriptor(descriptor, { signal, staleTime }),
298
- staleTime: overrides?.staleTime ?? descriptor.staleTime,
299
- gcTime: overrides?.gcTime ?? descriptor.gcTime
333
+ ...cacheTimeOptions(
334
+ overrides?.staleTime ?? descriptor.staleTime,
335
+ overrides?.gcTime ?? descriptor.gcTime
336
+ )
300
337
  });
301
338
  return {
302
339
  data: result.data,
@@ -314,7 +351,12 @@ function useFrontXInfiniteQuery(options) {
314
351
  initialPageParam: options.initialPage,
315
352
  queryFn: ({ pageParam, signal }) => fetchDescriptor(pageParam, {
316
353
  signal,
317
- staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime)
354
+ staleTime: resolveFetchStaleTime(
355
+ queryClient,
356
+ options.initialPage.key,
357
+ pageParam,
358
+ options.staleTime
359
+ )
318
360
  }),
319
361
  getNextPageParam: (lastPage, allPages, lastPageDescriptor, allPageDescriptors) => options.getNextPage(
320
362
  buildPageContext(
@@ -333,9 +375,12 @@ function useFrontXInfiniteQuery(options) {
333
375
  )
334
376
  ) : void 0,
335
377
  select: (data) => data.pages,
336
- staleTime: options.staleTime ?? options.initialPage.staleTime,
337
- gcTime: options.gcTime ?? options.initialPage.gcTime,
338
- maxPages: options.maxPages
378
+ ...cacheTimeOptions(
379
+ options.staleTime ?? options.initialPage.staleTime,
380
+ options.gcTime ?? options.initialPage.gcTime
381
+ ),
382
+ maxPages: options.maxPages,
383
+ ...options.enabled === void 0 ? {} : { enabled: options.enabled }
339
384
  });
340
385
  return {
341
386
  data: result.data,
@@ -365,7 +410,12 @@ function useFrontXSuspenseInfiniteQuery(options) {
365
410
  initialPageParam: options.initialPage,
366
411
  queryFn: ({ pageParam, signal }) => fetchDescriptor(pageParam, {
367
412
  signal,
368
- staleTime: resolveFetchStaleTime(queryClient, pageParam, options.staleTime)
413
+ staleTime: resolveFetchStaleTime(
414
+ queryClient,
415
+ options.initialPage.key,
416
+ pageParam,
417
+ options.staleTime
418
+ )
369
419
  }),
370
420
  getNextPageParam: (lastPage, allPages, lastPageDescriptor, allPageDescriptors) => options.getNextPage(
371
421
  buildPageContext(
@@ -384,8 +434,10 @@ function useFrontXSuspenseInfiniteQuery(options) {
384
434
  )
385
435
  ) : void 0,
386
436
  select: (data) => data.pages,
387
- staleTime: options.staleTime ?? options.initialPage.staleTime,
388
- gcTime: options.gcTime ?? options.initialPage.gcTime,
437
+ ...cacheTimeOptions(
438
+ options.staleTime ?? options.initialPage.staleTime,
439
+ options.gcTime ?? options.initialPage.gcTime
440
+ ),
389
441
  maxPages: options.maxPages
390
442
  });
391
443
  return {
@@ -407,20 +459,46 @@ function useFrontXSuspenseInfiniteQuery(options) {
407
459
  }
408
460
  };
409
461
  }
462
+ var mutationScopes = /* @__PURE__ */ new WeakMap();
463
+ function scopeBoundQueryCache(queryCache2, scope) {
464
+ const scopeChanged = () => readQueryCacheScopeGeneration(scope.queryClient) !== scope.generation;
465
+ const dropped = () => {
466
+ if (!scopeChanged()) return false;
467
+ recordQueryCacheScopeWriteDropped(scope.queryClient, scope.generation);
468
+ return true;
469
+ };
470
+ return {
471
+ get: queryCache2.get,
472
+ getState: queryCache2.getState,
473
+ set: (key, dataOrUpdater) => {
474
+ if (!dropped()) queryCache2.set(key, dataOrUpdater);
475
+ },
476
+ cancel: (key) => scopeChanged() ? Promise.resolve() : queryCache2.cancel(key),
477
+ invalidate: (key) => dropped() ? Promise.resolve() : queryCache2.invalidate(key),
478
+ invalidateMany: (filters) => dropped() ? Promise.resolve() : queryCache2.invalidateMany(filters),
479
+ remove: (key) => {
480
+ if (!dropped()) queryCache2.remove(key);
481
+ }
482
+ };
483
+ }
484
+ function scopeBoundCallbackCtx(callbackCtx, scope) {
485
+ return scope ? { queryCache: scopeBoundQueryCache(createQueryCache(scope.queryClient), scope) } : callbackCtx;
486
+ }
410
487
  function useFrontXMutation(options, callbackCtx) {
411
- useRequiredFrontXQueryClient();
412
- const latestRef = React.useRef({ options, callbackCtx });
413
- latestRef.current = { options, callbackCtx };
488
+ const queryClient = useRequiredFrontXQueryClient();
489
+ const latestRef = useLatest({ options, callbackCtx, queryClient });
414
490
  const fetchAbortControllersRef = React.useRef(/* @__PURE__ */ new Set());
491
+ const abortFetchesOnUnmount = React.useEffectEvent(() => {
492
+ if (options.abortOnUnmount) {
493
+ for (const controller of fetchAbortControllersRef.current) {
494
+ controller.abort();
495
+ }
496
+ fetchAbortControllersRef.current.clear();
497
+ }
498
+ });
415
499
  React.useEffect(() => {
416
- const fetchAbortControllers = fetchAbortControllersRef.current;
417
500
  return () => {
418
- if (latestRef.current.options.abortOnUnmount) {
419
- for (const controller of fetchAbortControllers) {
420
- controller.abort();
421
- }
422
- fetchAbortControllers.clear();
423
- }
501
+ abortFetchesOnUnmount();
424
502
  };
425
503
  }, []);
426
504
  const mutationFn = React.useCallback((variables, context) => {
@@ -444,35 +522,50 @@ function useFrontXMutation(options, callbackCtx) {
444
522
  return latestRef.current.options.endpoint.fetch(variables, { signal: controller.signal }).finally(() => {
445
523
  fetchAbortControllersRef.current.delete(controller);
446
524
  });
447
- }, []);
448
- const onMutate = React.useCallback((variables, _context) => {
525
+ }, [latestRef]);
526
+ const onMutate = React.useCallback(async (variables, mutationFnContext) => {
449
527
  const current = latestRef.current;
450
- if (!current.options.onMutate) {
451
- return void 0;
452
- }
453
- return current.options.onMutate(variables, current.callbackCtx);
454
- }, []);
455
- const onSuccess = React.useCallback((data, variables, context) => {
528
+ const scope = {
529
+ queryClient: current.queryClient,
530
+ generation: readQueryCacheScopeGeneration(current.queryClient)
531
+ };
532
+ mutationScopes.set(mutationFnContext, scope);
533
+ return current.options.onMutate?.(
534
+ variables,
535
+ scopeBoundCallbackCtx(current.callbackCtx, scope)
536
+ );
537
+ }, [latestRef]);
538
+ const onSuccess = React.useCallback((data, variables, context, mutationFnContext) => {
456
539
  const current = latestRef.current;
457
- return current.options.onSuccess?.(data, variables, context, current.callbackCtx);
458
- }, []);
459
- const onError = React.useCallback((error, variables, context) => {
540
+ return current.options.onSuccess?.(
541
+ data,
542
+ variables,
543
+ context,
544
+ scopeBoundCallbackCtx(current.callbackCtx, mutationScopes.get(mutationFnContext))
545
+ );
546
+ }, [latestRef]);
547
+ const onError = React.useCallback((error, variables, context, mutationFnContext) => {
460
548
  const current = latestRef.current;
461
- return current.options.onError?.(error, variables, context, current.callbackCtx);
462
- }, []);
463
- const onSettled = React.useCallback((data, error, variables, context) => {
549
+ return current.options.onError?.(
550
+ error,
551
+ variables,
552
+ context,
553
+ scopeBoundCallbackCtx(current.callbackCtx, mutationScopes.get(mutationFnContext))
554
+ );
555
+ }, [latestRef]);
556
+ const onSettled = React.useCallback((data, error, variables, context, mutationFnContext) => {
464
557
  const current = latestRef.current;
465
558
  return current.options.onSettled?.(
466
559
  data,
467
560
  error,
468
561
  variables,
469
562
  context,
470
- current.callbackCtx
563
+ scopeBoundCallbackCtx(current.callbackCtx, mutationScopes.get(mutationFnContext))
471
564
  );
472
- }, []);
565
+ }, [latestRef]);
473
566
  const mutation = useMutation({
474
567
  mutationFn,
475
- onMutate: options.onMutate ? onMutate : void 0,
568
+ onMutate,
476
569
  onSuccess: options.onSuccess ? onSuccess : void 0,
477
570
  onError: options.onError ? onError : void 0,
478
571
  onSettled: options.onSettled ? onSettled : void 0
@@ -489,38 +582,14 @@ function useFrontXMutation(options, callbackCtx) {
489
582
 
490
583
  // src/FrontXProvider.tsx
491
584
  import { jsx as jsx3 } from "react/jsx-runtime";
492
- function shallowEqual(a, b) {
493
- if (a === b) return true;
494
- if (!a || !b) return false;
495
- const keysA = Object.keys(a);
496
- if (keysA.length !== Object.keys(b).length) return false;
497
- return keysA.every((k) => Object.is(a[k], b[k]));
498
- }
499
- function createProviderOwnedApp(config) {
500
- return createFrontXApp(config);
501
- }
502
585
  var FrontXProvider = ({
503
586
  children,
504
- config,
505
- app: providedApp,
587
+ app,
506
588
  mfeBridge
507
589
  }) => {
508
- const [stableConfig, setStableConfig] = useState2(config);
509
- if (!shallowEqual(
510
- stableConfig,
511
- config
512
- )) {
513
- setStableConfig(config);
514
- }
515
- const app = useMemo2(() => {
516
- if (providedApp) {
517
- return providedApp;
518
- }
519
- return createProviderOwnedApp(stableConfig);
520
- }, [providedApp, stableConfig]);
521
590
  const queryClient = useBootstrappedFrontXQueryClient(app);
522
591
  const deferQuerySubtree = hasFrontXQueryClientActivator(app) && queryClient === void 0;
523
- useEffect2(() => {
592
+ useEffect3(() => {
524
593
  if (queryClient || hasFrontXQueryClientActivator(app) || process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test" || process.env.VITEST === "true") {
525
594
  return;
526
595
  }
@@ -528,13 +597,6 @@ var FrontXProvider = ({
528
597
  "[FrontXProvider] No query cache available. Add queryCache() or queryCacheShared() to your plugin composition. useApiQuery/useApiMutation will fail without it."
529
598
  );
530
599
  }, [app, queryClient]);
531
- useEffect2(() => {
532
- return () => {
533
- if (!providedApp) {
534
- app.destroy();
535
- }
536
- };
537
- }, [app, providedApp]);
538
600
  const content = /* @__PURE__ */ jsx3(FrontXContext.Provider, { value: app, children: /* @__PURE__ */ jsx3(ReduxProvider, { store: app.store, children: /* @__PURE__ */ jsx3(FrontXQueryClientProvider, { queryClient, children: deferQuerySubtree ? null : children }) }) });
539
601
  if (mfeBridge) {
540
602
  return /* @__PURE__ */ jsx3(MfeProvider, { value: mfeBridge, children: content });
@@ -543,7 +605,7 @@ var FrontXProvider = ({
543
605
  };
544
606
 
545
607
  // src/hooks/useCanAccess.ts
546
- import { useEffect as useEffect3, useState as useState3 } from "react";
608
+ import { useEffect as useEffect4, useState as useState2 } from "react";
547
609
  function serializeRecordValue(value) {
548
610
  if (value === null) return "n:";
549
611
  if (typeof value === "string") return `s:${value}`;
@@ -560,24 +622,23 @@ function accessQueryKey(query) {
560
622
  function getAuthRuntime(app) {
561
623
  return app.auth ?? null;
562
624
  }
625
+ var DENIED_NO_AUTH = { allow: false, isResolving: false };
563
626
  function useCanAccess(query) {
564
627
  const app = useFrontX();
628
+ const auth2 = getAuthRuntime(app);
565
629
  const stableKey = accessQueryKey(query);
566
- const [stableQuery, setStableQuery] = useState3(() => query);
567
- const [storedKey, setStoredKey] = useState3(stableKey);
568
- const [result, setResult] = useState3({ allow: false, isResolving: true });
569
- if (storedKey !== stableKey) {
570
- setStoredKey(stableKey);
630
+ const [stableQuery, setStableQuery] = useState2(() => query);
631
+ const [runState, setRunState] = useState2({ auth: auth2, stableKey });
632
+ const [result, setResult] = useState2({ allow: false, isResolving: true });
633
+ if (runState.auth !== auth2 || runState.stableKey !== stableKey) {
634
+ setRunState({ auth: auth2, stableKey });
571
635
  setStableQuery(query);
572
636
  setResult({ allow: false, isResolving: true });
573
637
  }
574
- useEffect3(() => {
575
- const auth2 = getAuthRuntime(app);
638
+ useEffect4(() => {
576
639
  if (!auth2) {
577
- setResult({ allow: false, isResolving: false });
578
640
  return;
579
641
  }
580
- setResult({ allow: false, isResolving: true });
581
642
  let alive = true;
582
643
  const controller = new AbortController();
583
644
  void auth2.canAccess(stableQuery, { signal: controller.signal }).then((decision) => {
@@ -593,8 +654,8 @@ function useCanAccess(query) {
593
654
  alive = false;
594
655
  controller.abort();
595
656
  };
596
- }, [app, stableQuery]);
597
- return result;
657
+ }, [auth2, stableQuery]);
658
+ return auth2 ? result : DENIED_NO_AUTH;
598
659
  }
599
660
 
600
661
  // src/components/CanAccess.tsx
@@ -622,7 +683,7 @@ import { useSelector } from "react-redux";
622
683
  var useAppSelector = useSelector;
623
684
 
624
685
  // src/hooks/useTranslation.ts
625
- import { useMemo as useMemo3, useCallback, useSyncExternalStore } from "react";
686
+ import { useMemo as useMemo2, useCallback, useSyncExternalStore } from "react";
626
687
  function useTranslation() {
627
688
  const app = useFrontX();
628
689
  const { i18nRegistry: i18nRegistry2 } = app;
@@ -636,7 +697,7 @@ function useTranslation() {
636
697
  () => i18nRegistry2.getVersion(),
637
698
  () => i18nRegistry2.getVersion()
638
699
  );
639
- const language = useMemo3(() => {
700
+ const language = useMemo2(() => {
640
701
  void version;
641
702
  return i18nRegistry2.getLanguage();
642
703
  }, [i18nRegistry2, version]);
@@ -654,7 +715,7 @@ function useTranslation() {
654
715
  },
655
716
  [app.actions]
656
717
  );
657
- const isRTL = useMemo3(() => {
718
+ const isRTL = useMemo2(() => {
658
719
  void language;
659
720
  return i18nRegistry2.isRTL();
660
721
  }, [i18nRegistry2, language]);
@@ -667,16 +728,15 @@ function useTranslation() {
667
728
  }
668
729
 
669
730
  // src/hooks/useScreenTranslations.ts
670
- import { useState as useState4, useEffect as useEffect4, useMemo as useMemo4, useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
731
+ import { useState as useState3, useEffect as useEffect5, useMemo as useMemo3, useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
671
732
  function isTranslationLoader(input) {
672
733
  return typeof input === "function";
673
734
  }
674
735
  function useScreenTranslations(screensetId, screenId, translations) {
675
736
  const app = useFrontX();
676
737
  const { i18nRegistry: i18nRegistry2 } = app;
677
- const [loadedLanguage, setLoadedLanguage] = useState4(null);
678
- const [isLoading, setIsLoading] = useState4(false);
679
- const [error, setError] = useState4(null);
738
+ const [loadedLanguage, setLoadedLanguage] = useState3(null);
739
+ const [error, setError] = useState3(null);
680
740
  const version = useSyncExternalStore2(
681
741
  useCallback2(
682
742
  (callback) => i18nRegistry2.subscribe(callback),
@@ -685,11 +745,11 @@ function useScreenTranslations(screensetId, screenId, translations) {
685
745
  () => i18nRegistry2.getVersion(),
686
746
  () => i18nRegistry2.getVersion()
687
747
  );
688
- const currentLanguage = useMemo4(() => {
748
+ const currentLanguage = useMemo3(() => {
689
749
  void version;
690
750
  return i18nRegistry2.getLanguage();
691
751
  }, [i18nRegistry2, version]);
692
- const loader = useMemo4(() => {
752
+ const loader = useMemo3(() => {
693
753
  if (isTranslationLoader(translations)) {
694
754
  return translations;
695
755
  }
@@ -702,12 +762,11 @@ function useScreenTranslations(screensetId, screenId, translations) {
702
762
  return module.default;
703
763
  };
704
764
  }, [translations]);
705
- useEffect4(() => {
765
+ useEffect5(() => {
706
766
  if (!currentLanguage || currentLanguage === loadedLanguage) {
707
767
  return;
708
768
  }
709
769
  let cancelled = false;
710
- setIsLoading(true);
711
770
  const loadTranslations = async () => {
712
771
  try {
713
772
  const namespace = `screen.${screensetId}.${screenId}`;
@@ -716,13 +775,11 @@ function useScreenTranslations(screensetId, screenId, translations) {
716
775
  i18nRegistry2.register(namespace, currentLanguage, loadedTranslations);
717
776
  if (!cancelled) {
718
777
  setLoadedLanguage(currentLanguage);
719
- setIsLoading(false);
720
778
  setError(null);
721
779
  }
722
780
  } catch (err) {
723
781
  if (!cancelled) {
724
782
  setError(err instanceof Error ? err : new Error(String(err)));
725
- setIsLoading(false);
726
783
  }
727
784
  }
728
785
  };
@@ -731,12 +788,12 @@ function useScreenTranslations(screensetId, screenId, translations) {
731
788
  cancelled = true;
732
789
  };
733
790
  }, [screensetId, screenId, loader, i18nRegistry2, currentLanguage, loadedLanguage]);
734
- const isLoaded = currentLanguage !== null && currentLanguage === loadedLanguage && !isLoading;
791
+ const isLoaded = currentLanguage !== null && currentLanguage === loadedLanguage;
735
792
  return { isLoaded, error };
736
793
  }
737
794
 
738
795
  // src/hooks/useFormatters.ts
739
- import { useMemo as useMemo5 } from "react";
796
+ import { useMemo as useMemo4 } from "react";
740
797
  import {
741
798
  formatDate as formatDateFn,
742
799
  formatTime as formatTimeFn,
@@ -751,7 +808,7 @@ import {
751
808
  } from "@gears-frontx/framework";
752
809
  function useFormatters() {
753
810
  const { language } = useTranslation();
754
- return useMemo5(
811
+ return useMemo4(
755
812
  () => {
756
813
  void language;
757
814
  return {
@@ -772,7 +829,7 @@ function useFormatters() {
772
829
  }
773
830
 
774
831
  // src/hooks/useTheme.ts
775
- import { useCallback as useCallback3, useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore3 } from "react";
832
+ import { useCallback as useCallback3, useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore3 } from "react";
776
833
  function useTheme() {
777
834
  const app = useFrontX();
778
835
  const { themeRegistry } = app;
@@ -786,12 +843,12 @@ function useTheme() {
786
843
  () => themeRegistry.getVersion(),
787
844
  () => themeRegistry.getVersion()
788
845
  );
789
- const currentTheme = useMemo6(() => {
846
+ const currentTheme = useMemo5(() => {
790
847
  void version;
791
848
  const theme = themeRegistry.getCurrent();
792
849
  return theme?.id;
793
850
  }, [themeRegistry, version]);
794
- const themes2 = useMemo6(() => {
851
+ const themes2 = useMemo5(() => {
795
852
  return themeRegistry.getAll().map((theme) => ({
796
853
  id: theme.id,
797
854
  name: theme.name
@@ -833,34 +890,47 @@ function useApiSuspenseInfiniteQuery(options) {
833
890
  }
834
891
 
835
892
  // src/hooks/useApiMutation.ts
836
- import { useMemo as useMemo7 } from "react";
893
+ import { useMemo as useMemo6 } from "react";
837
894
  function useApiMutation(options) {
838
895
  const queryClient = useRequiredFrontXQueryClient();
839
- const queryCache2 = useMemo7(() => createQueryCache(queryClient), [queryClient]);
840
- const callbackCtx = useMemo7(() => ({ queryCache: queryCache2 }), [queryCache2]);
896
+ const queryCache2 = useMemo6(() => createQueryCache(queryClient), [queryClient]);
897
+ const callbackCtx = useMemo6(() => ({ queryCache: queryCache2 }), [queryCache2]);
841
898
  return useFrontXMutation(options, callbackCtx);
842
899
  }
843
900
 
844
901
  // src/hooks/useApiStream.ts
845
- import { useEffect as useEffect5, useMemo as useMemo8, useRef, useState as useState5, useCallback as useCallback4 } from "react";
902
+ import { useEffect as useEffect6, useMemo as useMemo7, useRef as useRef2, useState as useState4, useCallback as useCallback4 } from "react";
846
903
  function useApiStream(descriptor, options) {
847
904
  const mode = options?.mode ?? "latest";
848
905
  const enabled = options?.enabled ?? true;
849
- const [data, setData] = useState5(void 0);
850
- const [events, setEvents] = useState5([]);
851
- const [status, setStatus] = useState5("idle");
852
- const [error, setError] = useState5(null);
853
- const connectPromiseRef = useRef(null);
854
- const connectionIdRef = useRef(null);
855
- const disconnectRequestedRef = useRef(false);
856
- const descriptorRef = useRef(descriptor);
857
- descriptorRef.current = descriptor;
858
- const descriptorKey = useMemo8(() => JSON.stringify(descriptor.key), [descriptor.key]);
859
- useEffect5(() => {
906
+ const [data, setData] = useState4(void 0);
907
+ const [events, setEvents] = useState4([]);
908
+ const [status, setStatus] = useState4(() => enabled ? "connecting" : "idle");
909
+ const [error, setError] = useState4(null);
910
+ const connectPromiseRef = useRef2(null);
911
+ const connectionIdRef = useRef2(null);
912
+ const disconnectRequestedRef = useRef2(false);
913
+ const descriptorRef = useLatest(descriptor);
914
+ const descriptorKey = useMemo7(() => JSON.stringify(descriptor.key), [descriptor.key]);
915
+ const resetKey = `${descriptorKey}:${mode}`;
916
+ const [prevResetKey, setPrevResetKey] = useState4(resetKey);
917
+ if (resetKey !== prevResetKey) {
918
+ setPrevResetKey(resetKey);
860
919
  setData(void 0);
861
920
  setEvents([]);
862
921
  setError(null);
863
- }, [descriptorKey, mode]);
922
+ }
923
+ const connectKey = `${resetKey}:${enabled}`;
924
+ const [prevConnectKey, setPrevConnectKey] = useState4(connectKey);
925
+ if (connectKey !== prevConnectKey) {
926
+ setPrevConnectKey(connectKey);
927
+ if (enabled) {
928
+ setData(void 0);
929
+ setEvents([]);
930
+ setStatus("connecting");
931
+ setError(null);
932
+ }
933
+ }
864
934
  const disconnect = useCallback4(() => {
865
935
  disconnectRequestedRef.current = true;
866
936
  if (connectionIdRef.current) {
@@ -869,19 +939,14 @@ function useApiStream(descriptor, options) {
869
939
  disconnectRequestedRef.current = false;
870
940
  }
871
941
  setStatus("disconnected");
872
- }, []);
873
- useEffect5(() => {
942
+ }, [descriptorRef]);
943
+ useEffect6(() => {
874
944
  if (!enabled) {
875
- setStatus("idle");
876
945
  return;
877
946
  }
878
947
  let cancelled = false;
879
948
  disconnectRequestedRef.current = false;
880
949
  const d = descriptorRef.current;
881
- setData(void 0);
882
- setEvents([]);
883
- setStatus("connecting");
884
- setError(null);
885
950
  const connectPromise = d.connect(
886
951
  (event) => {
887
952
  if (cancelled) return;
@@ -927,12 +992,12 @@ function useApiStream(descriptor, options) {
927
992
  connectPromiseRef.current = null;
928
993
  connectionIdRef.current = null;
929
994
  };
930
- }, [descriptorKey, enabled, mode]);
931
- return { data, events, status, error, disconnect };
995
+ }, [descriptorKey, enabled, mode, descriptorRef]);
996
+ return { data, events, status: enabled ? status : "idle", error, disconnect };
932
997
  }
933
998
 
934
999
  // src/hooks/useQueryCache.ts
935
- import { useMemo as useMemo9 } from "react";
1000
+ import { useMemo as useMemo8 } from "react";
936
1001
  function useQueryCache() {
937
1002
  const queryClient = useOptionalFrontXQueryClient();
938
1003
  if (!queryClient) {
@@ -940,7 +1005,7 @@ function useQueryCache() {
940
1005
  "[FrontXProvider] No query cache available. Add queryCache() or queryCacheShared() to your plugin composition."
941
1006
  );
942
1007
  }
943
- return useMemo9(() => createQueryCache(queryClient), [queryClient]);
1008
+ return useMemo8(() => createQueryCache(queryClient), [queryClient]);
944
1009
  }
945
1010
 
946
1011
  // src/mfe/hooks/useMfeBridge.ts
@@ -950,20 +1015,27 @@ function useMfeBridge() {
950
1015
  }
951
1016
 
952
1017
  // src/mfe/hooks/useSharedProperty.ts
953
- import { useSyncExternalStore as useSyncExternalStore4, useCallback as useCallback5 } from "react";
954
- function useSharedProperty(propertyTypeId) {
955
- const { bridge } = useMfeContext();
956
- const subscribe = useCallback5((callback) => {
957
- return bridge.subscribeToProperty(propertyTypeId, () => {
958
- callback();
959
- });
960
- }, [bridge, propertyTypeId]);
1018
+ import { useCallback as useCallback5, useContext as useContext4, useSyncExternalStore as useSyncExternalStore4 } from "react";
1019
+ function useSharedProperty(propertyTypeId, options) {
1020
+ const contextValue = useContext4(MfeContext);
1021
+ const bridge = options?.bridge ?? contextValue?.bridge;
1022
+ const fallback = options?.fallback;
1023
+ if (!bridge) {
1024
+ throw new Error(
1025
+ "useSharedProperty must be used within a MfeProvider, or given an explicit `bridge` option, for components that never mount MfeProvider."
1026
+ );
1027
+ }
1028
+ const subscribe = useCallback5(
1029
+ (onStoreChange) => bridge.subscribeToProperty(propertyTypeId, () => {
1030
+ onStoreChange();
1031
+ }),
1032
+ [bridge, propertyTypeId]
1033
+ );
961
1034
  const getSnapshot = useCallback5(() => {
962
- const property = bridge.getProperty(propertyTypeId);
963
- return property?.value;
964
- }, [bridge, propertyTypeId]);
965
- const value = useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
966
- return value;
1035
+ const raw = bridge.getProperty(propertyTypeId)?.value;
1036
+ return raw === void 0 ? fallback : raw;
1037
+ }, [bridge, propertyTypeId, fallback]);
1038
+ return useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
967
1039
  }
968
1040
 
969
1041
  // src/mfe/hooks/useHostAction.ts
@@ -974,36 +1046,41 @@ function useHostAction(actionTypeId) {
974
1046
  const chain = {
975
1047
  action: {
976
1048
  type: actionTypeId,
977
- target: bridge.domainId,
1049
+ target: bridge.extDomainId,
978
1050
  payload
979
1051
  }
980
1052
  };
981
- bridge.executeActionsChain(chain).catch((error) => {
982
- console.error(
983
- `[useHostAction] Failed to send action '${actionTypeId}':`,
984
- error
985
- );
986
- });
1053
+ bridge.executeActionsChain(chain);
987
1054
  }, [actionTypeId, bridge]);
988
1055
  }
989
1056
 
990
- // src/mfe/hooks/useDomainExtensions.ts
991
- import { useSyncExternalStore as useSyncExternalStore5, useCallback as useCallback7, useRef as useRef2 } from "react";
992
- function useDomainExtensions(domainId) {
993
- const app = useFrontX();
1057
+ // src/mfe/hooks/useMfeRegistry.ts
1058
+ function resolveMfeRegistry(app, callerName) {
994
1059
  const registry = app.mfeRegistry;
995
1060
  if (!registry) {
996
1061
  throw new Error(
997
- "useDomainExtensions requires the microfrontends plugin. Add microfrontends() to your Gears FrontX app configuration."
1062
+ `${callerName} requires the microfrontends plugin. Add microfrontends() to your Gears FrontX app configuration.`
998
1063
  );
999
1064
  }
1065
+ return registry;
1066
+ }
1067
+ function useMfeRegistry() {
1068
+ const app = useFrontX();
1069
+ return resolveMfeRegistry(app, "useMfeRegistry");
1070
+ }
1071
+
1072
+ // src/mfe/hooks/useDomainExtensions.ts
1073
+ import { useSyncExternalStore as useSyncExternalStore5, useCallback as useCallback7, useRef as useRef3 } from "react";
1074
+ function useDomainExtensions(domainId) {
1075
+ const app = useFrontX();
1076
+ const registry = resolveMfeRegistry(app, "useDomainExtensions");
1000
1077
  const subscribe = useCallback7(
1001
1078
  (onStoreChange) => {
1002
1079
  return app.store.subscribe(onStoreChange);
1003
1080
  },
1004
1081
  [app.store]
1005
1082
  );
1006
- const cacheRef = useRef2({ ids: "", extensions: [] });
1083
+ const cacheRef = useRef3({ ids: "", extensions: [] });
1007
1084
  const getSnapshot = useCallback7(() => {
1008
1085
  const extensions = registry.getExtensionsForDomain(domainId);
1009
1086
  const ids = extensions.map((e) => e.id).join(",");
@@ -1015,53 +1092,19 @@ function useDomainExtensions(domainId) {
1015
1092
  return useSyncExternalStore5(subscribe, getSnapshot, getSnapshot);
1016
1093
  }
1017
1094
 
1018
- // src/mfe/hooks/useMountedExtensions.ts
1019
- import { useSyncExternalStore as useSyncExternalStore6, useCallback as useCallback8, useRef as useRef3 } from "react";
1020
- function useMountedExtensions(domainId) {
1021
- const app = useFrontX();
1022
- const registry = app.mfeRegistry;
1023
- if (!registry) {
1024
- throw new Error(
1025
- "useMountedExtensions requires the microfrontends plugin. Add microfrontends() to your Gears FrontX app configuration."
1026
- );
1027
- }
1028
- const subscribe = useCallback8(
1029
- (onStoreChange) => {
1030
- return app.store.subscribe(onStoreChange);
1031
- },
1032
- [app.store]
1033
- );
1034
- const cacheRef = useRef3({ key: "", extensions: [] });
1035
- const getSnapshot = useCallback8(() => {
1036
- const ids = registry.getMountedExtensions(domainId);
1037
- const resolved = ids.map((id) => registry.getExtension(id)).filter((ext) => ext !== void 0);
1038
- const key = ids.join(",");
1039
- if (key !== cacheRef.current.key) {
1040
- cacheRef.current = { key, extensions: resolved };
1041
- }
1042
- return cacheRef.current.extensions;
1043
- }, [registry, domainId]);
1044
- return useSyncExternalStore6(subscribe, getSnapshot, getSnapshot);
1045
- }
1046
-
1047
1095
  // src/mfe/hooks/useRegisteredPackages.ts
1048
- import { useSyncExternalStore as useSyncExternalStore7, useCallback as useCallback9, useRef as useRef4 } from "react";
1096
+ import { useSyncExternalStore as useSyncExternalStore6, useCallback as useCallback8, useRef as useRef4 } from "react";
1049
1097
  function useRegisteredPackages() {
1050
1098
  const app = useFrontX();
1051
- const registry = app.mfeRegistry;
1052
- if (!registry) {
1053
- throw new Error(
1054
- "useRegisteredPackages requires the microfrontends plugin. Add microfrontends() to your Gears FrontX app configuration."
1055
- );
1056
- }
1057
- const subscribe = useCallback9(
1099
+ const registry = resolveMfeRegistry(app, "useRegisteredPackages");
1100
+ const subscribe = useCallback8(
1058
1101
  (onStoreChange) => {
1059
1102
  return app.store.subscribe(onStoreChange);
1060
1103
  },
1061
1104
  [app.store]
1062
1105
  );
1063
1106
  const cacheRef = useRef4({ packages: "", list: [] });
1064
- const getSnapshot = useCallback9(() => {
1107
+ const getSnapshot = useCallback8(() => {
1065
1108
  const list = registry.getRegisteredPackages();
1066
1109
  const packages = list.join(",");
1067
1110
  if (packages !== cacheRef.current.packages) {
@@ -1069,12 +1112,40 @@ function useRegisteredPackages() {
1069
1112
  }
1070
1113
  return cacheRef.current.list;
1071
1114
  }, [registry]);
1072
- return useSyncExternalStore7(subscribe, getSnapshot, getSnapshot);
1115
+ return useSyncExternalStore6(subscribe, getSnapshot, getSnapshot);
1073
1116
  }
1074
1117
 
1075
1118
  // src/mfe/ThemeAwareReactLifecycle.tsx
1076
1119
  import { createRoot } from "react-dom/client";
1120
+
1121
+ // src/mfe/domainTeardownCollector.ts
1122
+ var activeCollector;
1123
+ function collectDomainTeardowns(fn) {
1124
+ const previousCollector = activeCollector;
1125
+ const collector = [];
1126
+ activeCollector = collector;
1127
+ try {
1128
+ fn();
1129
+ } finally {
1130
+ activeCollector = previousCollector;
1131
+ }
1132
+ return collector;
1133
+ }
1134
+ function registerDomainTeardown(promise) {
1135
+ activeCollector?.push(promise);
1136
+ }
1137
+
1138
+ // src/mfe/ThemeAwareReactLifecycle.tsx
1077
1139
  import { jsx as jsx4 } from "react/jsx-runtime";
1140
+ var ADOPTED_HOST_STYLE_ATTR = "data-frontx-adopted-host-style";
1141
+ var BASE_RESETS_STYLE_ID = "__frontx-base-resets__";
1142
+ var SHELL_OWNED_SHADOW_STYLE_IDS = ["__frontx-shadow-isolation__", "__frontx-css-variables__"];
1143
+ function cssToCopy(style) {
1144
+ const text = style.textContent ?? "";
1145
+ const sheet = style.sheet;
1146
+ if (text.trim() || !sheet) return text;
1147
+ return Array.from(sheet.cssRules, (rule) => rule.cssText).join("\n");
1148
+ }
1078
1149
  function resolveProviderMountOptions(app, bridge, mountContext) {
1079
1150
  const extensionId = mountContext?.extensionId;
1080
1151
  const domainId = mountContext?.domainId;
@@ -1102,6 +1173,7 @@ var ThemeAwareReactLifecycle = class {
1102
1173
  app;
1103
1174
  root = null;
1104
1175
  mount(container, bridge, mountContext) {
1176
+ void this.app.mfeRegistry;
1105
1177
  if (container instanceof ShadowRoot) {
1106
1178
  this.adoptHostStylesIntoShadowRoot(container);
1107
1179
  }
@@ -1120,36 +1192,149 @@ var ThemeAwareReactLifecycle = class {
1120
1192
  )
1121
1193
  );
1122
1194
  }
1123
- unmount(_container) {
1124
- if (this.root) {
1125
- this.root.unmount();
1126
- this.root = null;
1127
- }
1195
+ /**
1196
+ * `Root.unmount()` itself is synchronous, but a nested `ExtensionDomainSlot`
1197
+ * mounted anywhere in this root's own tree cannot make ITS cleanup
1198
+ * synchronous too — its mounter's own `detach()` awaits per-extension
1199
+ * unmounts. Collecting whatever that cleanup fire-and-forgets (through
1200
+ * `domainTeardownCollector`, scoped to this one `Root.unmount()` call) and
1201
+ * awaiting it here is what makes this method's own returned promise settle
1202
+ * only once every nested domain this root owns has actually finished
1203
+ * tearing down — never before. A root with no routed nested domain
1204
+ * collects nothing, and this resolves exactly as before (a microtask after
1205
+ * the synchronous unmount, same observable timing `void | Promise<void>`
1206
+ * already allows).
1207
+ */
1208
+ async unmount(_container) {
1209
+ if (!this.root) return;
1210
+ const root = this.root;
1211
+ this.root = null;
1212
+ const pendingTeardowns = collectDomainTeardowns(() => root.unmount());
1213
+ const settled = await Promise.allSettled(pendingTeardowns);
1214
+ const failed = settled.find((result) => result.status === "rejected");
1215
+ if (failed) throw failed.reason;
1128
1216
  }
1129
1217
  /**
1130
1218
  * Copy all inline <style> and <link rel="stylesheet"> from the host document
1131
1219
  * into the shadow root so that Tailwind and component styles apply inside the MFE.
1220
+ *
1221
+ * The clones are inserted ahead of everything already in the shadow root, never
1222
+ * appended, because adopted host styles are context rather than authority: where
1223
+ * they and the MFE's own CSS declare the same property at equal specificity, the
1224
+ * MFE has to win. Appending inverted that. `MfeHandlerMF` injects the MFE's
1225
+ * compiled stylesheet into the shadow root before it calls mount, so an appended
1226
+ * clone of the shell's Tailwind preflight - whose
1227
+ * `button, [type='button'], [type='reset'], [type='submit']` rule sets
1228
+ * `background-color: transparent` at specificity (0,1,0) - tied with
1229
+ * `@gears-frontx/ui-kit`'s single-class button rule and won on document order,
1230
+ * leaving every kit Button transparent until it was hovered.
1231
+ *
1232
+ * Inserting at the front rather than pushing MFE styles to the back is what makes
1233
+ * the invariant hold for stylesheets that appear after mount too - a lazily
1234
+ * imported component's CSS module, or whatever initializeStyles() adds - since
1235
+ * those land behind the adopted block by construction.
1236
+ *
1237
+ * A cascade layer around the adopted CSS was the alternative and is rejected:
1238
+ * layered rules lose to unlayered ones at *any* specificity, so a host utility
1239
+ * class would stop overriding an MFE element selector. Document order keeps
1240
+ * specificity in charge and only settles the ties.
1241
+ *
1242
+ * The block replaces its predecessor instead of stacking on it, the same
1243
+ * upsert shape `injectStylesheet` and `injectCssVariables` use in
1244
+ * `@gears-frontx/mfes`' shadow utilities, widened from one id-keyed node to a
1245
+ * marked block. Replacement is required rather than tidy: `createShadowRoot`
1246
+ * hands back an existing `element.shadowRoot` instead of attaching a new one,
1247
+ * so remounting the same container runs this method again on a shadow root
1248
+ * that already holds a full adopted block, and appending would grow it once
1249
+ * per mount with no bound. It does not save re-resolving the cloned `<link>`s -
1250
+ * every mount clones them afresh and the browser resolves each clone; what
1251
+ * replacement buys is that the count stays at one block instead of one per
1252
+ * mount.
1253
+ *
1254
+ * The block is rebuilt from the host head rather than left in place, because
1255
+ * the head is not fixed: a lazily imported chunk adds stylesheets to it after
1256
+ * the first mount, and the second mount has to pick those up.
1257
+ *
1258
+ * A `<style>` is copied into a new element that holds the host's CSS - see
1259
+ * `cssToCopy` - and takes three things from the host element: `media`,
1260
+ * without which a print-only host style applies on screen inside the MFE;
1261
+ * `type`, which keeps the copy of a non-CSS element as inert as the original;
1262
+ * and the nonce, without which a nonce-based CSP blocks the copy. The nonce is read from the `nonce` property, because
1263
+ * browsers blank the attribute in a document served with a CSP header.
1264
+ * Nothing else is carried over, because libraries look for their own
1265
+ * attributes inside the shadow root: styled-components, rendering into it,
1266
+ * puts its tag right after the last `style[data-styled]` there, so a copy of
1267
+ * the host's tag would pull the MFE's rules ahead of the base resets and the
1268
+ * MFE's own CSS. A `<link>` is a deep clone and is loaded from its `href`
1269
+ * again.
1270
+ *
1271
+ * The fresh block goes in before the previous one comes out. Removing first
1272
+ * leaves the shadow root with no host CSS at all, and everything between that
1273
+ * removal and the insertion is synchronous work in this same task - so the gap
1274
+ * never reaches a paint, but any code that reads computed styles from the tree
1275
+ * in between sees an unstyled one. Inserting first costs one moment with two
1276
+ * blocks present, which changes nothing: the old block is a clone of the same
1277
+ * host head and loses every tie to the new one on document order anyway.
1132
1278
  */
1133
1279
  adoptHostStylesIntoShadowRoot(shadowRoot) {
1134
- const styleElements = document.head.querySelectorAll("style");
1135
- styleElements.forEach((el) => {
1136
- const clone = document.createElement("style");
1137
- clone.textContent = el.textContent ?? "";
1138
- shadowRoot.appendChild(clone);
1139
- });
1140
- const linkElements = document.head.querySelectorAll('link[rel="stylesheet"]');
1141
- linkElements.forEach((el) => {
1142
- const clone = el.cloneNode(true);
1143
- shadowRoot.appendChild(clone);
1280
+ const supersededStyles = Array.from(shadowRoot.querySelectorAll(`[${ADOPTED_HOST_STYLE_ATTR}]`));
1281
+ const adoptedStyles = document.createDocumentFragment();
1282
+ const hostStyleNodes = document.head.querySelectorAll('style, link[rel="stylesheet"]');
1283
+ hostStyleNodes.forEach((el) => {
1284
+ if (el instanceof HTMLStyleElement) {
1285
+ const copy = document.createElement("style");
1286
+ if (el.media) copy.media = el.media;
1287
+ if (el.type) copy.type = el.type;
1288
+ if (el.nonce) copy.nonce = el.nonce;
1289
+ copy.textContent = cssToCopy(el);
1290
+ copy.setAttribute(ADOPTED_HOST_STYLE_ATTR, "");
1291
+ adoptedStyles.appendChild(copy);
1292
+ return;
1293
+ }
1294
+ const linkClone = el.cloneNode(true);
1295
+ linkClone.setAttribute(ADOPTED_HOST_STYLE_ATTR, "");
1296
+ adoptedStyles.appendChild(linkClone);
1144
1297
  });
1298
+ shadowRoot.insertBefore(adoptedStyles, shadowRoot.firstChild);
1299
+ supersededStyles.forEach((el) => el.remove());
1145
1300
  }
1146
1301
  /**
1147
1302
  * Box-model resets and :host defaults needed inside every shadow root.
1148
1303
  * These aren't part of Tailwind's compiled output but are required for
1149
1304
  * consistent rendering across browsers.
1305
+ *
1306
+ * Reuses the node a previous mount left behind, exactly as `injectStylesheet`
1307
+ * does in `@gears-frontx/mfes`' shadow utilities: a remount runs against a
1308
+ * shadow root that `createShadowRoot` handed back intact, and appending a
1309
+ * second copy would add one node per mount forever.
1310
+ *
1311
+ * Reuse alone is not enough, which is why the node is also positioned on every
1312
+ * mount rather than left where the previous one sat. These resets are context
1313
+ * like the adopted block is - `border-color: currentColor` on `*` is a 0-0-0
1314
+ * declaration that beats an MFE preflight's own 0-0-0 reset purely on document
1315
+ * order - so they have to stay ahead of the MFE's CSS, and where they land is
1316
+ * not stable on its own. `MfeHandlerMF.wrapLifecycleWithStylesheets` removes the
1317
+ * MFE's stylesheet on unmount and appends a fresh one before the next mount, so
1318
+ * a node merely left in place ends up ahead of that stylesheet on a remount and
1319
+ * behind it on the first mount: two different cascades for one component.
1320
+ * Anchoring it to the end of the shell-owned head gives every mount the same
1321
+ * order - adopted block, isolation, resets, MFE - which is also the order the
1322
+ * adoption method's invariant is written against.
1323
+ *
1324
+ * With no shell-owned node to anchor to, the resets go to the front. That is the
1325
+ * plain-`Element` container: no adopted block, no isolation style, and the MFE
1326
+ * stylesheet already appended by the handler, so the front is again the only
1327
+ * position that leaves the MFE winning ties.
1328
+ *
1329
+ * Looked up by attribute-selector form rather than `getElementById`, which
1330
+ * `ShadowRoot` has but `Element` does not, and this hook takes either.
1150
1331
  */
1151
1332
  injectBaseResets(container) {
1152
- const style = document.createElement("style");
1333
+ const existing = container.querySelector(
1334
+ `style[id="${BASE_RESETS_STYLE_ID}"]`
1335
+ );
1336
+ const style = existing ?? document.createElement("style");
1337
+ style.id = BASE_RESETS_STYLE_ID;
1153
1338
  style.textContent = `
1154
1339
  *, *::before, *::after {
1155
1340
  box-sizing: border-box;
@@ -1163,11 +1348,21 @@ var ThemeAwareReactLifecycle = class {
1163
1348
  line-height: 1.5;
1164
1349
  -webkit-font-smoothing: antialiased;
1165
1350
  -moz-osx-font-smoothing: grayscale;
1166
- color: hsl(var(--foreground));
1167
- background-color: hsl(var(--background));
1351
+ color: var(--foreground);
1352
+ background-color: var(--background);
1168
1353
  }
1169
1354
  `;
1170
- container.appendChild(style);
1355
+ const shellOwnedHead = container.querySelectorAll(
1356
+ [
1357
+ `[${ADOPTED_HOST_STYLE_ATTR}]`,
1358
+ ...SHELL_OWNED_SHADOW_STYLE_IDS.map((id) => `[id="${id}"]`)
1359
+ ].join(", ")
1360
+ );
1361
+ const lastShellOwnedNode = shellOwnedHead[shellOwnedHead.length - 1];
1362
+ container.insertBefore(
1363
+ style,
1364
+ lastShellOwnedNode ? lastShellOwnedNode.nextSibling : container.firstChild
1365
+ );
1171
1366
  }
1172
1367
  /**
1173
1368
  * Hook for subclasses to inject additional CSS not covered by the adopted host
@@ -1180,7 +1375,7 @@ var ThemeAwareReactLifecycle = class {
1180
1375
  };
1181
1376
 
1182
1377
  // src/mfe/components/ExtensionDomainSlot.tsx
1183
- import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
1378
+ import { useEffect as useEffect7, useEffectEvent, useRef as useRef5, useState as useState5 } from "react";
1184
1379
  import { jsx as jsx5 } from "react/jsx-runtime";
1185
1380
  function ExtensionDomainSlot(props) {
1186
1381
  const {
@@ -1192,12 +1387,14 @@ function ExtensionDomainSlot(props) {
1192
1387
  loadingComponent = null
1193
1388
  } = props;
1194
1389
  const rootRef = useRef5(null);
1195
- const onAttachedRef = useRef5(onAttached);
1196
- const onDetachedRef = useRef5(onDetached);
1197
- onAttachedRef.current = onAttached;
1198
- onDetachedRef.current = onDetached;
1199
- const [attached, setAttached] = useState6(false);
1200
- useEffect6(() => {
1390
+ const emitAttached = useEffectEvent((root) => {
1391
+ onAttached?.(root);
1392
+ });
1393
+ const emitDetached = useEffectEvent(() => {
1394
+ onDetached?.();
1395
+ });
1396
+ const [attached, setAttached] = useState5(false);
1397
+ useEffect7(() => {
1201
1398
  const root = rootRef.current;
1202
1399
  if (!root) {
1203
1400
  return;
@@ -1205,11 +1402,12 @@ function ExtensionDomainSlot(props) {
1205
1402
  const mounter = registry.getMounter(domainId);
1206
1403
  mounter.attach(root);
1207
1404
  setAttached(true);
1208
- onAttachedRef.current?.(root);
1405
+ emitAttached(root);
1209
1406
  return () => {
1210
- void mounter.detach();
1407
+ const detachPromise = mounter.detach();
1408
+ registerDomainTeardown(detachPromise);
1211
1409
  setAttached(false);
1212
- onDetachedRef.current?.();
1410
+ emitDetached();
1213
1411
  };
1214
1412
  }, [registry, domainId]);
1215
1413
  return /* @__PURE__ */ jsx5(
@@ -1226,7 +1424,6 @@ function ExtensionDomainSlot(props) {
1226
1424
  // src/index.ts
1227
1425
  import {
1228
1426
  createFrontX,
1229
- createFrontXApp as createFrontXApp2,
1230
1427
  presets,
1231
1428
  ACCOUNTS_DOMAIN,
1232
1429
  I18nRegistry,
@@ -1243,7 +1440,6 @@ import {
1243
1440
  registerSlice,
1244
1441
  hasSlice,
1245
1442
  createSlice,
1246
- LayoutDomain,
1247
1443
  layoutReducer,
1248
1444
  layoutDomainReducers,
1249
1445
  LAYOUT_SLICE_NAME,
@@ -1303,6 +1499,7 @@ import {
1303
1499
  toggleMockMode,
1304
1500
  MockEvents,
1305
1501
  auth,
1502
+ AuthEvents,
1306
1503
  frontxApiTransport,
1307
1504
  apiRegistry,
1308
1505
  BaseApiService,
@@ -1310,9 +1507,6 @@ import {
1310
1507
  RestEndpointProtocol,
1311
1508
  SseProtocol,
1312
1509
  SseStreamProtocol,
1313
- RestMockPlugin,
1314
- SseMockPlugin,
1315
- MockEventSource,
1316
1510
  ApiPluginBase,
1317
1511
  ApiPlugin,
1318
1512
  ApiProtocol,
@@ -1333,7 +1527,7 @@ import {
1333
1527
  } from "@gears-frontx/framework";
1334
1528
  import { Language, TextDirection, LanguageDisplayMode } from "@gears-frontx/framework";
1335
1529
  import {
1336
- microfrontends as microfrontends2,
1530
+ microfrontends,
1337
1531
  mock
1338
1532
  } from "@gears-frontx/framework";
1339
1533
  import {
@@ -1346,8 +1540,7 @@ import {
1346
1540
  import {
1347
1541
  selectExtensionState,
1348
1542
  selectRegisteredExtensions,
1349
- selectExtensionError,
1350
- selectMountedExtensions
1543
+ selectExtensionError
1351
1544
  } from "@gears-frontx/framework";
1352
1545
  import {
1353
1546
  FRONTX_POPUP_DOMAIN,
@@ -1390,7 +1583,6 @@ import {
1390
1583
  ExclusiveMountStrategy
1391
1584
  } from "@gears-frontx/framework";
1392
1585
  import { MfeHandlerMF, gtsPlugin } from "@gears-frontx/framework";
1393
- import { themeSchema, languageSchema, extensionScreenSchema } from "@gears-frontx/framework";
1394
1586
  import {
1395
1587
  createShadowRoot,
1396
1588
  injectCssVariables,
@@ -1404,6 +1596,7 @@ export {
1404
1596
  ApiPlugin,
1405
1597
  ApiPluginBase,
1406
1598
  ApiProtocol,
1599
+ AuthEvents,
1407
1600
  BaseApiService,
1408
1601
  CanAccess,
1409
1602
  ChildMfeBridge,
@@ -1431,7 +1624,6 @@ export {
1431
1624
  LAYOUT_SLICE_NAME,
1432
1625
  Language,
1433
1626
  LanguageDisplayMode,
1434
- LayoutDomain,
1435
1627
  MOCK_PLUGIN,
1436
1628
  MfeBridgeFactory,
1437
1629
  MfeContext,
@@ -1440,18 +1632,15 @@ export {
1440
1632
  MfeProvider,
1441
1633
  MfeRegistry,
1442
1634
  MfeRegistryFactory,
1443
- MockEventSource,
1444
1635
  MockEvents,
1445
1636
  MountStrategy,
1446
1637
  OptionalMountStrategy,
1447
1638
  ParentMfeBridge,
1448
1639
  RestEndpointProtocol,
1449
- RestMockPlugin,
1450
1640
  RestPlugin,
1451
1641
  RestPluginWithConfig,
1452
1642
  RestProtocol,
1453
1643
  SUPPORTED_LANGUAGES,
1454
- SseMockPlugin,
1455
1644
  SsePlugin,
1456
1645
  SsePluginWithConfig,
1457
1646
  SseProtocol,
@@ -1470,7 +1659,6 @@ export {
1470
1659
  closePopup,
1471
1660
  closeTopPopup,
1472
1661
  createFrontX,
1473
- createFrontXApp2 as createFrontXApp,
1474
1662
  createI18nRegistry,
1475
1663
  createShadowRoot,
1476
1664
  createSlice,
@@ -1478,7 +1666,6 @@ export {
1478
1666
  createThemeRegistry,
1479
1667
  effects,
1480
1668
  eventBus2 as eventBus,
1481
- extensionScreenSchema,
1482
1669
  extractGtsPackage,
1483
1670
  footerActions,
1484
1671
  footerSlice,
@@ -1500,7 +1687,6 @@ export {
1500
1687
  isRestShortCircuit,
1501
1688
  isShortCircuit,
1502
1689
  isSseShortCircuit,
1503
- languageSchema,
1504
1690
  layout,
1505
1691
  layoutDomainReducers,
1506
1692
  layoutReducer,
@@ -1508,7 +1694,7 @@ export {
1508
1694
  menuActions,
1509
1695
  menuSlice,
1510
1696
  mfeRegistryFactory,
1511
- microfrontends2 as microfrontends,
1697
+ microfrontends,
1512
1698
  mock,
1513
1699
  mockActions,
1514
1700
  mockSlice,
@@ -1528,7 +1714,6 @@ export {
1528
1714
  screenDomain,
1529
1715
  selectExtensionError,
1530
1716
  selectExtensionState,
1531
- selectMountedExtensions,
1532
1717
  selectRegisteredExtensions,
1533
1718
  setFooterConfig,
1534
1719
  setFooterVisible,
@@ -1558,7 +1743,6 @@ export {
1558
1743
  tenantActions,
1559
1744
  tenantReducer,
1560
1745
  tenantSlice,
1561
- themeSchema,
1562
1746
  themes,
1563
1747
  toggleMenu,
1564
1748
  toggleMockMode,
@@ -1580,7 +1764,7 @@ export {
1580
1764
  useHostAction,
1581
1765
  useMfeBridge,
1582
1766
  useMfeContext,
1583
- useMountedExtensions,
1767
+ useMfeRegistry,
1584
1768
  useQueryCache,
1585
1769
  useRegisteredPackages,
1586
1770
  useScreenTranslations,