@gears-frontx/react 0.2.0-alpha.0 → 0.2.0-alpha.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.
- package/CLAUDE.md +30 -10
- package/dist/index.cjs +254 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +169 -10
- package/dist/index.d.ts +169 -10
- package/dist/index.js +192 -82
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +7 -4
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +5 -2
- package/dist/testing.js.map +1 -1
- package/package.json +3 -4
- package/commands/frontx-duplicate-screenset.md +0 -58
- package/commands/frontx-new-component.md +0 -57
- package/commands/frontx-new-mfe.md +0 -153
- package/commands/frontx-new-screen.md +0 -45
- package/commands/frontx-new-screenset.md +0 -57
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/FrontXProvider.tsx
|
|
2
|
-
import { useMemo as useMemo2, useEffect as
|
|
2
|
+
import { useMemo as useMemo2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
3
3
|
import { Provider as ReduxProvider } from "react-redux";
|
|
4
4
|
import {
|
|
5
5
|
createFrontXApp
|
|
@@ -41,7 +41,7 @@ var MfeProvider = ({ value, children }) => {
|
|
|
41
41
|
import React, {
|
|
42
42
|
createContext as createContext3,
|
|
43
43
|
useContext as useContext3,
|
|
44
|
-
useEffect,
|
|
44
|
+
useEffect as useEffect2,
|
|
45
45
|
useLayoutEffect,
|
|
46
46
|
useMemo,
|
|
47
47
|
useState
|
|
@@ -164,13 +164,23 @@ function createQueryCache(queryClient) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// src/hooks/useLatest.ts
|
|
168
|
+
import { useEffect, useRef } from "react";
|
|
169
|
+
function useLatest(value) {
|
|
170
|
+
const ref = useRef(value);
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
ref.current = value;
|
|
173
|
+
});
|
|
174
|
+
return ref;
|
|
175
|
+
}
|
|
176
|
+
|
|
167
177
|
// src/queryClient.tsx
|
|
168
178
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
169
179
|
var APP_QUERY_CLIENT_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client");
|
|
170
180
|
var APP_QUERY_CLIENT_RESOLVER_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-resolver");
|
|
171
181
|
var APP_QUERY_CLIENT_ACTIVATOR_SYMBOL = /* @__PURE__ */ Symbol.for("frontx:query-cache:app-client-activator");
|
|
172
182
|
var FrontXQueryClientContext = createContext3(void 0);
|
|
173
|
-
var useCommitEffect = typeof globalThis.window === "undefined" ?
|
|
183
|
+
var useCommitEffect = typeof globalThis.window === "undefined" ? useEffect2 : useLayoutEffect;
|
|
174
184
|
function resolveFrontXQueryClient(app) {
|
|
175
185
|
const clientApp = app;
|
|
176
186
|
return clientApp[APP_QUERY_CLIENT_SYMBOL] ?? clientApp[APP_QUERY_CLIENT_RESOLVER_SYMBOL]?.();
|
|
@@ -409,18 +419,19 @@ function useFrontXSuspenseInfiniteQuery(options) {
|
|
|
409
419
|
}
|
|
410
420
|
function useFrontXMutation(options, callbackCtx) {
|
|
411
421
|
useRequiredFrontXQueryClient();
|
|
412
|
-
const latestRef =
|
|
413
|
-
latestRef.current = { options, callbackCtx };
|
|
422
|
+
const latestRef = useLatest({ options, callbackCtx });
|
|
414
423
|
const fetchAbortControllersRef = React.useRef(/* @__PURE__ */ new Set());
|
|
424
|
+
const abortFetchesOnUnmount = React.useEffectEvent(() => {
|
|
425
|
+
if (options.abortOnUnmount) {
|
|
426
|
+
for (const controller of fetchAbortControllersRef.current) {
|
|
427
|
+
controller.abort();
|
|
428
|
+
}
|
|
429
|
+
fetchAbortControllersRef.current.clear();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
415
432
|
React.useEffect(() => {
|
|
416
|
-
const fetchAbortControllers = fetchAbortControllersRef.current;
|
|
417
433
|
return () => {
|
|
418
|
-
|
|
419
|
-
for (const controller of fetchAbortControllers) {
|
|
420
|
-
controller.abort();
|
|
421
|
-
}
|
|
422
|
-
fetchAbortControllers.clear();
|
|
423
|
-
}
|
|
434
|
+
abortFetchesOnUnmount();
|
|
424
435
|
};
|
|
425
436
|
}, []);
|
|
426
437
|
const mutationFn = React.useCallback((variables, context) => {
|
|
@@ -444,22 +455,22 @@ function useFrontXMutation(options, callbackCtx) {
|
|
|
444
455
|
return latestRef.current.options.endpoint.fetch(variables, { signal: controller.signal }).finally(() => {
|
|
445
456
|
fetchAbortControllersRef.current.delete(controller);
|
|
446
457
|
});
|
|
447
|
-
}, []);
|
|
458
|
+
}, [latestRef]);
|
|
448
459
|
const onMutate = React.useCallback((variables, _context) => {
|
|
449
460
|
const current = latestRef.current;
|
|
450
461
|
if (!current.options.onMutate) {
|
|
451
462
|
return void 0;
|
|
452
463
|
}
|
|
453
464
|
return current.options.onMutate(variables, current.callbackCtx);
|
|
454
|
-
}, []);
|
|
465
|
+
}, [latestRef]);
|
|
455
466
|
const onSuccess = React.useCallback((data, variables, context) => {
|
|
456
467
|
const current = latestRef.current;
|
|
457
468
|
return current.options.onSuccess?.(data, variables, context, current.callbackCtx);
|
|
458
|
-
}, []);
|
|
469
|
+
}, [latestRef]);
|
|
459
470
|
const onError = React.useCallback((error, variables, context) => {
|
|
460
471
|
const current = latestRef.current;
|
|
461
472
|
return current.options.onError?.(error, variables, context, current.callbackCtx);
|
|
462
|
-
}, []);
|
|
473
|
+
}, [latestRef]);
|
|
463
474
|
const onSettled = React.useCallback((data, error, variables, context) => {
|
|
464
475
|
const current = latestRef.current;
|
|
465
476
|
return current.options.onSettled?.(
|
|
@@ -469,7 +480,7 @@ function useFrontXMutation(options, callbackCtx) {
|
|
|
469
480
|
context,
|
|
470
481
|
current.callbackCtx
|
|
471
482
|
);
|
|
472
|
-
}, []);
|
|
483
|
+
}, [latestRef]);
|
|
473
484
|
const mutation = useMutation({
|
|
474
485
|
mutationFn,
|
|
475
486
|
onMutate: options.onMutate ? onMutate : void 0,
|
|
@@ -520,7 +531,7 @@ var FrontXProvider = ({
|
|
|
520
531
|
}, [providedApp, stableConfig]);
|
|
521
532
|
const queryClient = useBootstrappedFrontXQueryClient(app);
|
|
522
533
|
const deferQuerySubtree = hasFrontXQueryClientActivator(app) && queryClient === void 0;
|
|
523
|
-
|
|
534
|
+
useEffect3(() => {
|
|
524
535
|
if (queryClient || hasFrontXQueryClientActivator(app) || process.env.NODE_ENV === "production" || process.env.NODE_ENV === "test" || process.env.VITEST === "true") {
|
|
525
536
|
return;
|
|
526
537
|
}
|
|
@@ -528,7 +539,7 @@ var FrontXProvider = ({
|
|
|
528
539
|
"[FrontXProvider] No query cache available. Add queryCache() or queryCacheShared() to your plugin composition. useApiQuery/useApiMutation will fail without it."
|
|
529
540
|
);
|
|
530
541
|
}, [app, queryClient]);
|
|
531
|
-
|
|
542
|
+
useEffect3(() => {
|
|
532
543
|
return () => {
|
|
533
544
|
if (!providedApp) {
|
|
534
545
|
app.destroy();
|
|
@@ -543,7 +554,7 @@ var FrontXProvider = ({
|
|
|
543
554
|
};
|
|
544
555
|
|
|
545
556
|
// src/hooks/useCanAccess.ts
|
|
546
|
-
import { useEffect as
|
|
557
|
+
import { useEffect as useEffect4, useState as useState3 } from "react";
|
|
547
558
|
function serializeRecordValue(value) {
|
|
548
559
|
if (value === null) return "n:";
|
|
549
560
|
if (typeof value === "string") return `s:${value}`;
|
|
@@ -560,24 +571,23 @@ function accessQueryKey(query) {
|
|
|
560
571
|
function getAuthRuntime(app) {
|
|
561
572
|
return app.auth ?? null;
|
|
562
573
|
}
|
|
574
|
+
var DENIED_NO_AUTH = { allow: false, isResolving: false };
|
|
563
575
|
function useCanAccess(query) {
|
|
564
576
|
const app = useFrontX();
|
|
577
|
+
const auth2 = getAuthRuntime(app);
|
|
565
578
|
const stableKey = accessQueryKey(query);
|
|
566
579
|
const [stableQuery, setStableQuery] = useState3(() => query);
|
|
567
|
-
const [
|
|
580
|
+
const [runState, setRunState] = useState3({ auth: auth2, stableKey });
|
|
568
581
|
const [result, setResult] = useState3({ allow: false, isResolving: true });
|
|
569
|
-
if (
|
|
570
|
-
|
|
582
|
+
if (runState.auth !== auth2 || runState.stableKey !== stableKey) {
|
|
583
|
+
setRunState({ auth: auth2, stableKey });
|
|
571
584
|
setStableQuery(query);
|
|
572
585
|
setResult({ allow: false, isResolving: true });
|
|
573
586
|
}
|
|
574
|
-
|
|
575
|
-
const auth2 = getAuthRuntime(app);
|
|
587
|
+
useEffect4(() => {
|
|
576
588
|
if (!auth2) {
|
|
577
|
-
setResult({ allow: false, isResolving: false });
|
|
578
589
|
return;
|
|
579
590
|
}
|
|
580
|
-
setResult({ allow: false, isResolving: true });
|
|
581
591
|
let alive = true;
|
|
582
592
|
const controller = new AbortController();
|
|
583
593
|
void auth2.canAccess(stableQuery, { signal: controller.signal }).then((decision) => {
|
|
@@ -593,8 +603,8 @@ function useCanAccess(query) {
|
|
|
593
603
|
alive = false;
|
|
594
604
|
controller.abort();
|
|
595
605
|
};
|
|
596
|
-
}, [
|
|
597
|
-
return result;
|
|
606
|
+
}, [auth2, stableQuery]);
|
|
607
|
+
return auth2 ? result : DENIED_NO_AUTH;
|
|
598
608
|
}
|
|
599
609
|
|
|
600
610
|
// src/components/CanAccess.tsx
|
|
@@ -667,7 +677,7 @@ function useTranslation() {
|
|
|
667
677
|
}
|
|
668
678
|
|
|
669
679
|
// src/hooks/useScreenTranslations.ts
|
|
670
|
-
import { useState as useState4, useEffect as
|
|
680
|
+
import { useState as useState4, useEffect as useEffect5, useMemo as useMemo4, useCallback as useCallback2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
671
681
|
function isTranslationLoader(input) {
|
|
672
682
|
return typeof input === "function";
|
|
673
683
|
}
|
|
@@ -675,7 +685,6 @@ function useScreenTranslations(screensetId, screenId, translations) {
|
|
|
675
685
|
const app = useFrontX();
|
|
676
686
|
const { i18nRegistry: i18nRegistry2 } = app;
|
|
677
687
|
const [loadedLanguage, setLoadedLanguage] = useState4(null);
|
|
678
|
-
const [isLoading, setIsLoading] = useState4(false);
|
|
679
688
|
const [error, setError] = useState4(null);
|
|
680
689
|
const version = useSyncExternalStore2(
|
|
681
690
|
useCallback2(
|
|
@@ -702,12 +711,11 @@ function useScreenTranslations(screensetId, screenId, translations) {
|
|
|
702
711
|
return module.default;
|
|
703
712
|
};
|
|
704
713
|
}, [translations]);
|
|
705
|
-
|
|
714
|
+
useEffect5(() => {
|
|
706
715
|
if (!currentLanguage || currentLanguage === loadedLanguage) {
|
|
707
716
|
return;
|
|
708
717
|
}
|
|
709
718
|
let cancelled = false;
|
|
710
|
-
setIsLoading(true);
|
|
711
719
|
const loadTranslations = async () => {
|
|
712
720
|
try {
|
|
713
721
|
const namespace = `screen.${screensetId}.${screenId}`;
|
|
@@ -716,13 +724,11 @@ function useScreenTranslations(screensetId, screenId, translations) {
|
|
|
716
724
|
i18nRegistry2.register(namespace, currentLanguage, loadedTranslations);
|
|
717
725
|
if (!cancelled) {
|
|
718
726
|
setLoadedLanguage(currentLanguage);
|
|
719
|
-
setIsLoading(false);
|
|
720
727
|
setError(null);
|
|
721
728
|
}
|
|
722
729
|
} catch (err) {
|
|
723
730
|
if (!cancelled) {
|
|
724
731
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
725
|
-
setIsLoading(false);
|
|
726
732
|
}
|
|
727
733
|
}
|
|
728
734
|
};
|
|
@@ -731,7 +737,7 @@ function useScreenTranslations(screensetId, screenId, translations) {
|
|
|
731
737
|
cancelled = true;
|
|
732
738
|
};
|
|
733
739
|
}, [screensetId, screenId, loader, i18nRegistry2, currentLanguage, loadedLanguage]);
|
|
734
|
-
const isLoaded = currentLanguage !== null && currentLanguage === loadedLanguage
|
|
740
|
+
const isLoaded = currentLanguage !== null && currentLanguage === loadedLanguage;
|
|
735
741
|
return { isLoaded, error };
|
|
736
742
|
}
|
|
737
743
|
|
|
@@ -842,25 +848,38 @@ function useApiMutation(options) {
|
|
|
842
848
|
}
|
|
843
849
|
|
|
844
850
|
// src/hooks/useApiStream.ts
|
|
845
|
-
import { useEffect as
|
|
851
|
+
import { useEffect as useEffect6, useMemo as useMemo8, useRef as useRef2, useState as useState5, useCallback as useCallback4 } from "react";
|
|
846
852
|
function useApiStream(descriptor, options) {
|
|
847
853
|
const mode = options?.mode ?? "latest";
|
|
848
854
|
const enabled = options?.enabled ?? true;
|
|
849
855
|
const [data, setData] = useState5(void 0);
|
|
850
856
|
const [events, setEvents] = useState5([]);
|
|
851
|
-
const [status, setStatus] = useState5("idle");
|
|
857
|
+
const [status, setStatus] = useState5(() => enabled ? "connecting" : "idle");
|
|
852
858
|
const [error, setError] = useState5(null);
|
|
853
|
-
const connectPromiseRef =
|
|
854
|
-
const connectionIdRef =
|
|
855
|
-
const disconnectRequestedRef =
|
|
856
|
-
const descriptorRef =
|
|
857
|
-
descriptorRef.current = descriptor;
|
|
859
|
+
const connectPromiseRef = useRef2(null);
|
|
860
|
+
const connectionIdRef = useRef2(null);
|
|
861
|
+
const disconnectRequestedRef = useRef2(false);
|
|
862
|
+
const descriptorRef = useLatest(descriptor);
|
|
858
863
|
const descriptorKey = useMemo8(() => JSON.stringify(descriptor.key), [descriptor.key]);
|
|
859
|
-
|
|
864
|
+
const resetKey = `${descriptorKey}:${mode}`;
|
|
865
|
+
const [prevResetKey, setPrevResetKey] = useState5(resetKey);
|
|
866
|
+
if (resetKey !== prevResetKey) {
|
|
867
|
+
setPrevResetKey(resetKey);
|
|
860
868
|
setData(void 0);
|
|
861
869
|
setEvents([]);
|
|
862
870
|
setError(null);
|
|
863
|
-
}
|
|
871
|
+
}
|
|
872
|
+
const connectKey = `${resetKey}:${enabled}`;
|
|
873
|
+
const [prevConnectKey, setPrevConnectKey] = useState5(connectKey);
|
|
874
|
+
if (connectKey !== prevConnectKey) {
|
|
875
|
+
setPrevConnectKey(connectKey);
|
|
876
|
+
if (enabled) {
|
|
877
|
+
setData(void 0);
|
|
878
|
+
setEvents([]);
|
|
879
|
+
setStatus("connecting");
|
|
880
|
+
setError(null);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
864
883
|
const disconnect = useCallback4(() => {
|
|
865
884
|
disconnectRequestedRef.current = true;
|
|
866
885
|
if (connectionIdRef.current) {
|
|
@@ -869,19 +888,14 @@ function useApiStream(descriptor, options) {
|
|
|
869
888
|
disconnectRequestedRef.current = false;
|
|
870
889
|
}
|
|
871
890
|
setStatus("disconnected");
|
|
872
|
-
}, []);
|
|
873
|
-
|
|
891
|
+
}, [descriptorRef]);
|
|
892
|
+
useEffect6(() => {
|
|
874
893
|
if (!enabled) {
|
|
875
|
-
setStatus("idle");
|
|
876
894
|
return;
|
|
877
895
|
}
|
|
878
896
|
let cancelled = false;
|
|
879
897
|
disconnectRequestedRef.current = false;
|
|
880
898
|
const d = descriptorRef.current;
|
|
881
|
-
setData(void 0);
|
|
882
|
-
setEvents([]);
|
|
883
|
-
setStatus("connecting");
|
|
884
|
-
setError(null);
|
|
885
899
|
const connectPromise = d.connect(
|
|
886
900
|
(event) => {
|
|
887
901
|
if (cancelled) return;
|
|
@@ -927,8 +941,8 @@ function useApiStream(descriptor, options) {
|
|
|
927
941
|
connectPromiseRef.current = null;
|
|
928
942
|
connectionIdRef.current = null;
|
|
929
943
|
};
|
|
930
|
-
}, [descriptorKey, enabled, mode]);
|
|
931
|
-
return { data, events, status, error, disconnect };
|
|
944
|
+
}, [descriptorKey, enabled, mode, descriptorRef]);
|
|
945
|
+
return { data, events, status: enabled ? status : "idle", error, disconnect };
|
|
932
946
|
}
|
|
933
947
|
|
|
934
948
|
// src/hooks/useQueryCache.ts
|
|
@@ -974,7 +988,7 @@ function useHostAction(actionTypeId) {
|
|
|
974
988
|
const chain = {
|
|
975
989
|
action: {
|
|
976
990
|
type: actionTypeId,
|
|
977
|
-
target: bridge.
|
|
991
|
+
target: bridge.extDomainId,
|
|
978
992
|
payload
|
|
979
993
|
}
|
|
980
994
|
};
|
|
@@ -988,7 +1002,7 @@ function useHostAction(actionTypeId) {
|
|
|
988
1002
|
}
|
|
989
1003
|
|
|
990
1004
|
// src/mfe/hooks/useDomainExtensions.ts
|
|
991
|
-
import { useSyncExternalStore as useSyncExternalStore5, useCallback as useCallback7, useRef as
|
|
1005
|
+
import { useSyncExternalStore as useSyncExternalStore5, useCallback as useCallback7, useRef as useRef3 } from "react";
|
|
992
1006
|
function useDomainExtensions(domainId) {
|
|
993
1007
|
const app = useFrontX();
|
|
994
1008
|
const registry = app.mfeRegistry;
|
|
@@ -1003,7 +1017,7 @@ function useDomainExtensions(domainId) {
|
|
|
1003
1017
|
},
|
|
1004
1018
|
[app.store]
|
|
1005
1019
|
);
|
|
1006
|
-
const cacheRef =
|
|
1020
|
+
const cacheRef = useRef3({ ids: "", extensions: [] });
|
|
1007
1021
|
const getSnapshot = useCallback7(() => {
|
|
1008
1022
|
const extensions = registry.getExtensionsForDomain(domainId);
|
|
1009
1023
|
const ids = extensions.map((e) => e.id).join(",");
|
|
@@ -1016,7 +1030,7 @@ function useDomainExtensions(domainId) {
|
|
|
1016
1030
|
}
|
|
1017
1031
|
|
|
1018
1032
|
// src/mfe/hooks/useMountedExtensions.ts
|
|
1019
|
-
import { useSyncExternalStore as useSyncExternalStore6, useCallback as useCallback8, useRef as
|
|
1033
|
+
import { useSyncExternalStore as useSyncExternalStore6, useCallback as useCallback8, useRef as useRef4 } from "react";
|
|
1020
1034
|
function useMountedExtensions(domainId) {
|
|
1021
1035
|
const app = useFrontX();
|
|
1022
1036
|
const registry = app.mfeRegistry;
|
|
@@ -1031,7 +1045,7 @@ function useMountedExtensions(domainId) {
|
|
|
1031
1045
|
},
|
|
1032
1046
|
[app.store]
|
|
1033
1047
|
);
|
|
1034
|
-
const cacheRef =
|
|
1048
|
+
const cacheRef = useRef4({ key: "", extensions: [] });
|
|
1035
1049
|
const getSnapshot = useCallback8(() => {
|
|
1036
1050
|
const ids = registry.getMountedExtensions(domainId);
|
|
1037
1051
|
const resolved = ids.map((id) => registry.getExtension(id)).filter((ext) => ext !== void 0);
|
|
@@ -1045,7 +1059,7 @@ function useMountedExtensions(domainId) {
|
|
|
1045
1059
|
}
|
|
1046
1060
|
|
|
1047
1061
|
// src/mfe/hooks/useRegisteredPackages.ts
|
|
1048
|
-
import { useSyncExternalStore as useSyncExternalStore7, useCallback as useCallback9, useRef as
|
|
1062
|
+
import { useSyncExternalStore as useSyncExternalStore7, useCallback as useCallback9, useRef as useRef5 } from "react";
|
|
1049
1063
|
function useRegisteredPackages() {
|
|
1050
1064
|
const app = useFrontX();
|
|
1051
1065
|
const registry = app.mfeRegistry;
|
|
@@ -1060,7 +1074,7 @@ function useRegisteredPackages() {
|
|
|
1060
1074
|
},
|
|
1061
1075
|
[app.store]
|
|
1062
1076
|
);
|
|
1063
|
-
const cacheRef =
|
|
1077
|
+
const cacheRef = useRef5({ packages: "", list: [] });
|
|
1064
1078
|
const getSnapshot = useCallback9(() => {
|
|
1065
1079
|
const list = registry.getRegisteredPackages();
|
|
1066
1080
|
const packages = list.join(",");
|
|
@@ -1075,6 +1089,9 @@ function useRegisteredPackages() {
|
|
|
1075
1089
|
// src/mfe/ThemeAwareReactLifecycle.tsx
|
|
1076
1090
|
import { createRoot } from "react-dom/client";
|
|
1077
1091
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1092
|
+
var ADOPTED_HOST_STYLE_ATTR = "data-frontx-adopted-host-style";
|
|
1093
|
+
var BASE_RESETS_STYLE_ID = "__frontx-base-resets__";
|
|
1094
|
+
var SHELL_OWNED_SHADOW_STYLE_IDS = ["__frontx-shadow-isolation__", "__frontx-css-variables__"];
|
|
1078
1095
|
function resolveProviderMountOptions(app, bridge, mountContext) {
|
|
1079
1096
|
const extensionId = mountContext?.extensionId;
|
|
1080
1097
|
const domainId = mountContext?.domainId;
|
|
@@ -1129,27 +1146,108 @@ var ThemeAwareReactLifecycle = class {
|
|
|
1129
1146
|
/**
|
|
1130
1147
|
* Copy all inline <style> and <link rel="stylesheet"> from the host document
|
|
1131
1148
|
* into the shadow root so that Tailwind and component styles apply inside the MFE.
|
|
1149
|
+
*
|
|
1150
|
+
* The clones are inserted ahead of everything already in the shadow root, never
|
|
1151
|
+
* appended, because adopted host styles are context rather than authority: where
|
|
1152
|
+
* they and the MFE's own CSS declare the same property at equal specificity, the
|
|
1153
|
+
* MFE has to win. Appending inverted that. `MfeHandlerMF` injects the MFE's
|
|
1154
|
+
* compiled stylesheet into the shadow root before it calls mount, so an appended
|
|
1155
|
+
* clone of the shell's Tailwind preflight - whose
|
|
1156
|
+
* `button, [type='button'], [type='reset'], [type='submit']` rule sets
|
|
1157
|
+
* `background-color: transparent` at specificity (0,1,0) - tied with
|
|
1158
|
+
* `@gears-frontx/ui-kit`'s single-class button rule and won on document order,
|
|
1159
|
+
* leaving every kit Button transparent until it was hovered.
|
|
1160
|
+
*
|
|
1161
|
+
* Inserting at the front rather than pushing MFE styles to the back is what makes
|
|
1162
|
+
* the invariant hold for stylesheets that appear after mount too - a lazily
|
|
1163
|
+
* imported component's CSS module, or whatever initializeStyles() adds - since
|
|
1164
|
+
* those land behind the adopted block by construction.
|
|
1165
|
+
*
|
|
1166
|
+
* A cascade layer around the adopted CSS was the alternative and is rejected:
|
|
1167
|
+
* layered rules lose to unlayered ones at *any* specificity, so a host utility
|
|
1168
|
+
* class would stop overriding an MFE element selector. Document order keeps
|
|
1169
|
+
* specificity in charge and only settles the ties.
|
|
1170
|
+
*
|
|
1171
|
+
* The block replaces its predecessor instead of stacking on it, the same
|
|
1172
|
+
* upsert shape `injectStylesheet` and `injectCssVariables` use in
|
|
1173
|
+
* `@gears-frontx/mfes`' shadow utilities, widened from one id-keyed node to a
|
|
1174
|
+
* marked block. Replacement is required rather than tidy: `createShadowRoot`
|
|
1175
|
+
* hands back an existing `element.shadowRoot` instead of attaching a new one,
|
|
1176
|
+
* so remounting the same container runs this method again on a shadow root
|
|
1177
|
+
* that already holds a full adopted block, and appending would grow it once
|
|
1178
|
+
* per mount with no bound. It does not save re-resolving the cloned `<link>`s -
|
|
1179
|
+
* every mount clones them afresh and the browser resolves each clone; what
|
|
1180
|
+
* replacement buys is that the count stays at one block instead of one per
|
|
1181
|
+
* mount.
|
|
1182
|
+
*
|
|
1183
|
+
* The block is rebuilt from the host head rather than left in place, because
|
|
1184
|
+
* the head is not fixed: a lazily imported chunk adds stylesheets to it after
|
|
1185
|
+
* the first mount, and the second mount has to pick those up.
|
|
1186
|
+
*
|
|
1187
|
+
* The fresh block goes in before the previous one comes out. Removing first
|
|
1188
|
+
* leaves the shadow root with no host CSS at all, and everything between that
|
|
1189
|
+
* removal and the insertion is synchronous work in this same task - so the gap
|
|
1190
|
+
* never reaches a paint, but any code that reads computed styles from the tree
|
|
1191
|
+
* in between sees an unstyled one. Inserting first costs one moment with two
|
|
1192
|
+
* blocks present, which changes nothing: the old block is a clone of the same
|
|
1193
|
+
* host head and loses every tie to the new one on document order anyway.
|
|
1132
1194
|
*/
|
|
1133
1195
|
adoptHostStylesIntoShadowRoot(shadowRoot) {
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1196
|
+
const supersededStyles = Array.from(shadowRoot.querySelectorAll(`[${ADOPTED_HOST_STYLE_ATTR}]`));
|
|
1197
|
+
const adoptedStyles = document.createDocumentFragment();
|
|
1198
|
+
const hostStyleNodes = document.head.querySelectorAll('style, link[rel="stylesheet"]');
|
|
1199
|
+
hostStyleNodes.forEach((el) => {
|
|
1200
|
+
if (el instanceof HTMLStyleElement) {
|
|
1201
|
+
const clone = document.createElement("style");
|
|
1202
|
+
clone.textContent = el.textContent ?? "";
|
|
1203
|
+
clone.setAttribute(ADOPTED_HOST_STYLE_ATTR, "");
|
|
1204
|
+
adoptedStyles.appendChild(clone);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
const linkClone = el.cloneNode(true);
|
|
1208
|
+
linkClone.setAttribute(ADOPTED_HOST_STYLE_ATTR, "");
|
|
1209
|
+
adoptedStyles.appendChild(linkClone);
|
|
1144
1210
|
});
|
|
1211
|
+
shadowRoot.insertBefore(adoptedStyles, shadowRoot.firstChild);
|
|
1212
|
+
supersededStyles.forEach((el) => el.remove());
|
|
1145
1213
|
}
|
|
1146
1214
|
/**
|
|
1147
1215
|
* Box-model resets and :host defaults needed inside every shadow root.
|
|
1148
1216
|
* These aren't part of Tailwind's compiled output but are required for
|
|
1149
1217
|
* consistent rendering across browsers.
|
|
1218
|
+
*
|
|
1219
|
+
* Reuses the node a previous mount left behind, exactly as `injectStylesheet`
|
|
1220
|
+
* does in `@gears-frontx/mfes`' shadow utilities: a remount runs against a
|
|
1221
|
+
* shadow root that `createShadowRoot` handed back intact, and appending a
|
|
1222
|
+
* second copy would add one node per mount forever.
|
|
1223
|
+
*
|
|
1224
|
+
* Reuse alone is not enough, which is why the node is also positioned on every
|
|
1225
|
+
* mount rather than left where the previous one sat. These resets are context
|
|
1226
|
+
* like the adopted block is - `border-color: currentColor` on `*` is a 0-0-0
|
|
1227
|
+
* declaration that beats an MFE preflight's own 0-0-0 reset purely on document
|
|
1228
|
+
* order - so they have to stay ahead of the MFE's CSS, and where they land is
|
|
1229
|
+
* not stable on its own. `MfeHandlerMF.wrapLifecycleWithStylesheets` removes the
|
|
1230
|
+
* MFE's stylesheet on unmount and appends a fresh one before the next mount, so
|
|
1231
|
+
* a node merely left in place ends up ahead of that stylesheet on a remount and
|
|
1232
|
+
* behind it on the first mount: two different cascades for one component.
|
|
1233
|
+
* Anchoring it to the end of the shell-owned head gives every mount the same
|
|
1234
|
+
* order - adopted block, isolation, resets, MFE - which is also the order the
|
|
1235
|
+
* adoption method's invariant is written against.
|
|
1236
|
+
*
|
|
1237
|
+
* With no shell-owned node to anchor to, the resets go to the front. That is the
|
|
1238
|
+
* plain-`Element` container: no adopted block, no isolation style, and the MFE
|
|
1239
|
+
* stylesheet already appended by the handler, so the front is again the only
|
|
1240
|
+
* position that leaves the MFE winning ties.
|
|
1241
|
+
*
|
|
1242
|
+
* Looked up by attribute-selector form rather than `getElementById`, which
|
|
1243
|
+
* `ShadowRoot` has but `Element` does not, and this hook takes either.
|
|
1150
1244
|
*/
|
|
1151
1245
|
injectBaseResets(container) {
|
|
1152
|
-
const
|
|
1246
|
+
const existing = container.querySelector(
|
|
1247
|
+
`style[id="${BASE_RESETS_STYLE_ID}"]`
|
|
1248
|
+
);
|
|
1249
|
+
const style = existing ?? document.createElement("style");
|
|
1250
|
+
style.id = BASE_RESETS_STYLE_ID;
|
|
1153
1251
|
style.textContent = `
|
|
1154
1252
|
*, *::before, *::after {
|
|
1155
1253
|
box-sizing: border-box;
|
|
@@ -1167,7 +1265,17 @@ var ThemeAwareReactLifecycle = class {
|
|
|
1167
1265
|
background-color: hsl(var(--background));
|
|
1168
1266
|
}
|
|
1169
1267
|
`;
|
|
1170
|
-
container.
|
|
1268
|
+
const shellOwnedHead = container.querySelectorAll(
|
|
1269
|
+
[
|
|
1270
|
+
`[${ADOPTED_HOST_STYLE_ATTR}]`,
|
|
1271
|
+
...SHELL_OWNED_SHADOW_STYLE_IDS.map((id) => `[id="${id}"]`)
|
|
1272
|
+
].join(", ")
|
|
1273
|
+
);
|
|
1274
|
+
const lastShellOwnedNode = shellOwnedHead[shellOwnedHead.length - 1];
|
|
1275
|
+
container.insertBefore(
|
|
1276
|
+
style,
|
|
1277
|
+
lastShellOwnedNode ? lastShellOwnedNode.nextSibling : container.firstChild
|
|
1278
|
+
);
|
|
1171
1279
|
}
|
|
1172
1280
|
/**
|
|
1173
1281
|
* Hook for subclasses to inject additional CSS not covered by the adopted host
|
|
@@ -1180,7 +1288,7 @@ var ThemeAwareReactLifecycle = class {
|
|
|
1180
1288
|
};
|
|
1181
1289
|
|
|
1182
1290
|
// src/mfe/components/ExtensionDomainSlot.tsx
|
|
1183
|
-
import { useEffect as
|
|
1291
|
+
import { useEffect as useEffect7, useEffectEvent, useRef as useRef6, useState as useState6 } from "react";
|
|
1184
1292
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
1185
1293
|
function ExtensionDomainSlot(props) {
|
|
1186
1294
|
const {
|
|
@@ -1191,13 +1299,15 @@ function ExtensionDomainSlot(props) {
|
|
|
1191
1299
|
onDetached,
|
|
1192
1300
|
loadingComponent = null
|
|
1193
1301
|
} = props;
|
|
1194
|
-
const rootRef =
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1302
|
+
const rootRef = useRef6(null);
|
|
1303
|
+
const emitAttached = useEffectEvent((root) => {
|
|
1304
|
+
onAttached?.(root);
|
|
1305
|
+
});
|
|
1306
|
+
const emitDetached = useEffectEvent(() => {
|
|
1307
|
+
onDetached?.();
|
|
1308
|
+
});
|
|
1199
1309
|
const [attached, setAttached] = useState6(false);
|
|
1200
|
-
|
|
1310
|
+
useEffect7(() => {
|
|
1201
1311
|
const root = rootRef.current;
|
|
1202
1312
|
if (!root) {
|
|
1203
1313
|
return;
|
|
@@ -1205,11 +1315,11 @@ function ExtensionDomainSlot(props) {
|
|
|
1205
1315
|
const mounter = registry.getMounter(domainId);
|
|
1206
1316
|
mounter.attach(root);
|
|
1207
1317
|
setAttached(true);
|
|
1208
|
-
|
|
1318
|
+
emitAttached(root);
|
|
1209
1319
|
return () => {
|
|
1210
1320
|
void mounter.detach();
|
|
1211
1321
|
setAttached(false);
|
|
1212
|
-
|
|
1322
|
+
emitDetached();
|
|
1213
1323
|
};
|
|
1214
1324
|
}, [registry, domainId]);
|
|
1215
1325
|
return /* @__PURE__ */ jsx5(
|