@flipdish/ui-library 0.38.0 → 0.39.1
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/README.md +9 -9
- package/dist/components/templates/WidgetLayout/index.cjs +36 -0
- package/dist/components/templates/WidgetLayout/index.cjs.map +1 -0
- package/dist/components/templates/WidgetLayout/index.d.ts +54 -0
- package/dist/components/templates/WidgetLayout/index.d.ts.map +1 -0
- package/dist/components/templates/WidgetLayout/index.js +34 -0
- package/dist/components/templates/WidgetLayout/index.js.map +1 -0
- package/dist/native/components/templates/WidgetLayout/index.d.ts +12 -0
- package/dist/providers/PortalContainer/index.d.ts +1 -1
- package/dist/providers/ThemeProvider/index.cjs +36 -14
- package/dist/providers/ThemeProvider/index.cjs.map +1 -1
- package/dist/providers/ThemeProvider/index.d.ts +19 -5
- package/dist/providers/ThemeProvider/index.d.ts.map +1 -1
- package/dist/providers/ThemeProvider/index.js +36 -14
- package/dist/providers/ThemeProvider/index.js.map +1 -1
- package/dist/providers/UIProvider/hooks/usePortalAnchor.cjs +137 -0
- package/dist/providers/UIProvider/hooks/usePortalAnchor.cjs.map +1 -0
- package/dist/providers/UIProvider/hooks/usePortalAnchor.d.ts +60 -0
- package/dist/providers/UIProvider/hooks/usePortalAnchor.d.ts.map +1 -0
- package/dist/providers/UIProvider/hooks/usePortalAnchor.js +131 -0
- package/dist/providers/UIProvider/hooks/usePortalAnchor.js.map +1 -0
- package/dist/providers/UIProvider/index.cjs +7 -64
- package/dist/providers/UIProvider/index.cjs.map +1 -1
- package/dist/providers/UIProvider/index.d.ts +16 -11
- package/dist/providers/UIProvider/index.d.ts.map +1 -1
- package/dist/providers/UIProvider/index.js +7 -64
- package/dist/providers/UIProvider/index.js.map +1 -1
- package/dist/utilities/cssScopeUtils/index.cjs +22 -11
- package/dist/utilities/cssScopeUtils/index.cjs.map +1 -1
- package/dist/utilities/cssScopeUtils/index.d.ts +20 -9
- package/dist/utilities/cssScopeUtils/index.d.ts.map +1 -1
- package/dist/utilities/cssScopeUtils/index.js +22 -11
- package/dist/utilities/cssScopeUtils/index.js.map +1 -1
- package/dist/utilities/renderUtils/index.cjs +36 -7
- package/dist/utilities/renderUtils/index.cjs.map +1 -1
- package/dist/utilities/renderUtils/index.d.ts +23 -2
- package/dist/utilities/renderUtils/index.d.ts.map +1 -1
- package/dist/utilities/renderUtils/index.js +35 -8
- package/dist/utilities/renderUtils/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** The `portalContainer` prop as consumers pass it: a resolver, `null` for the themed root, or absent. */
|
|
2
|
+
export type PortalContainerProp = (() => HTMLElement | null) | null | undefined;
|
|
3
|
+
/** Fallback for the `body` anchor's stacking level when the `--z-index-overlay-root` token is not in scope. */
|
|
4
|
+
export declare const OVERLAY_ROOT_Z_INDEX_FALLBACK = 1300;
|
|
5
|
+
/**
|
|
6
|
+
* Reads the design system's overlay stacking tier. Library overlays are `z-50`, which loses to host chrome
|
|
7
|
+
* such as Portal's sidebar at 200, so the `body` anchor lifts them as a whole to this tier. The token lives
|
|
8
|
+
* on the themed root because a scoped CSS build rewrites `:root` to the scope, so `body` never carries it.
|
|
9
|
+
*
|
|
10
|
+
* @param themeRoot The element the app's tokens cascade from.
|
|
11
|
+
* @returns The `--z-index-overlay-root` token as a number, or the 1300 fallback.
|
|
12
|
+
*/
|
|
13
|
+
export declare const readOverlayZIndex: (themeRoot: HTMLElement | null) => number;
|
|
14
|
+
/**
|
|
15
|
+
* Decides where overlays portal. An embedded app (one with a `scopeId`) defaults to `document.body` so
|
|
16
|
+
* its overlays escape the host's `isolation` and `z-index` layers; `null` opts back into the themed root.
|
|
17
|
+
*
|
|
18
|
+
* @param portalContainer The consumer's `portalContainer` prop, as passed.
|
|
19
|
+
* @param scopeId The consumer's `scopeId` prop, as passed.
|
|
20
|
+
* @returns A resolver for the container, or `null` for the themed root.
|
|
21
|
+
*/
|
|
22
|
+
export declare const resolvePortalContainer: (portalContainer: PortalContainerProp, scopeId: string | undefined) => (() => HTMLElement | null) | null;
|
|
23
|
+
/**
|
|
24
|
+
* Builds the anchor overlays portal into. On `body` it is a zero-height positioned block: no layout
|
|
25
|
+
* footprint (beyond a `gap` on a flex or grid body), but a stacking context the host's `z-index` layers cannot beat. Anywhere else it is
|
|
26
|
+
* `display: contents`, so it cannot disturb a flex or grid host. It carries `data-theme` and, with a
|
|
27
|
+
* scope, `data-mfe-scope`, which is what lets the app's scoped CSS reach an overlay rendered outside its tree.
|
|
28
|
+
*
|
|
29
|
+
* @param container The element the anchor is appended to.
|
|
30
|
+
* @param options The scope to stamp, if any, the resolved theme mode, and the stacking tier for a `body` anchor.
|
|
31
|
+
* @returns The appended anchor element.
|
|
32
|
+
*/
|
|
33
|
+
export declare const createPortalAnchor: (container: HTMLElement, { scopeId, resolvedMode, zIndex }: {
|
|
34
|
+
scopeId?: string;
|
|
35
|
+
resolvedMode: "light" | "dark";
|
|
36
|
+
zIndex?: number;
|
|
37
|
+
}) => HTMLElement;
|
|
38
|
+
interface UsePortalAnchorOptions {
|
|
39
|
+
/** The consumer's `portalContainer` prop, as passed. */
|
|
40
|
+
portalContainer: PortalContainerProp;
|
|
41
|
+
/** The consumer's `scopeId` prop, as passed. */
|
|
42
|
+
scopeId: string | undefined;
|
|
43
|
+
/** `ThemeProvider`'s themed root, the fallback container. */
|
|
44
|
+
themeRoot: HTMLElement | null;
|
|
45
|
+
/** The resolved theme, mirrored onto the anchor so overlays inherit it. */
|
|
46
|
+
resolvedMode: 'light' | 'dark';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Keeps one themed, optionally scoped anchor inside the resolved portal container and hands React Aria
|
|
50
|
+
* a `getContainer` that only ever returns an attached anchor or the themed root. Compares resolved DOM
|
|
51
|
+
* nodes rather than callback identity, so an inline `() => getElementById(...)` does not rebuild the
|
|
52
|
+
* anchor on every render, and returns `null` for the commit in which the container is still changing so
|
|
53
|
+
* no overlay lands on `document.body` unstyled and then relocates.
|
|
54
|
+
*
|
|
55
|
+
* @param options The container choice, scope, themed root and theme mode.
|
|
56
|
+
* @returns The `getContainer` callback for `PortalContainer`.
|
|
57
|
+
*/
|
|
58
|
+
export declare const usePortalAnchor: ({ portalContainer, scopeId, themeRoot, resolvedMode }: UsePortalAnchorOptions) => (() => HTMLElement | null);
|
|
59
|
+
export {};
|
|
60
|
+
//# sourceMappingURL=usePortalAnchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePortalAnchor.d.ts","sourceRoot":"","sources":["../../../../src/providers/UIProvider/hooks/usePortalAnchor.ts"],"names":[],"mappings":"AAIA,0GAA0G;AAC1G,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAEhF,+GAA+G;AAC/G,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,WAAW,GAAG,IAAI,KAAG,MAGjE,CAAC;AAIF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,iBAAiB,mBAAmB,EAAE,SAAS,MAAM,GAAG,SAAS,KAAG,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,IACnD,CAAC;AAEtF;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,GAC7B,WAAW,WAAW,EACtB,mCAAmE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KACvI,WAiBF,CAAC;AAEF,UAAU,sBAAsB;IAC9B,wDAAwD;IACxD,eAAe,EAAE,mBAAmB,CAAC;IACrC,gDAAgD;IAChD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,6DAA6D;IAC7D,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,uDAAuD,sBAAsB,KAAG,CAAC,MAAM,WAAW,GAAG,IAAI,CAqExI,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { useMemo, useState, useRef, useLayoutEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
/** Fallback for the `body` anchor's stacking level when the `--z-index-overlay-root` token is not in scope. */
|
|
4
|
+
const OVERLAY_ROOT_Z_INDEX_FALLBACK = 1300;
|
|
5
|
+
/**
|
|
6
|
+
* Reads the design system's overlay stacking tier. Library overlays are `z-50`, which loses to host chrome
|
|
7
|
+
* such as Portal's sidebar at 200, so the `body` anchor lifts them as a whole to this tier. The token lives
|
|
8
|
+
* on the themed root because a scoped CSS build rewrites `:root` to the scope, so `body` never carries it.
|
|
9
|
+
*
|
|
10
|
+
* @param themeRoot The element the app's tokens cascade from.
|
|
11
|
+
* @returns The `--z-index-overlay-root` token as a number, or the 1300 fallback.
|
|
12
|
+
*/
|
|
13
|
+
const readOverlayZIndex = (themeRoot) => {
|
|
14
|
+
const value = themeRoot ? Number.parseInt(getComputedStyle(themeRoot).getPropertyValue('--z-index-overlay-root'), 10) : Number.NaN;
|
|
15
|
+
return Number.isFinite(value) ? value : OVERLAY_ROOT_Z_INDEX_FALLBACK;
|
|
16
|
+
};
|
|
17
|
+
const portalToBody = () => document.body;
|
|
18
|
+
/**
|
|
19
|
+
* Decides where overlays portal. An embedded app (one with a `scopeId`) defaults to `document.body` so
|
|
20
|
+
* its overlays escape the host's `isolation` and `z-index` layers; `null` opts back into the themed root.
|
|
21
|
+
*
|
|
22
|
+
* @param portalContainer The consumer's `portalContainer` prop, as passed.
|
|
23
|
+
* @param scopeId The consumer's `scopeId` prop, as passed.
|
|
24
|
+
* @returns A resolver for the container, or `null` for the themed root.
|
|
25
|
+
*/
|
|
26
|
+
const resolvePortalContainer = (portalContainer, scopeId) => portalContainer === undefined && scopeId ? portalToBody : (portalContainer ?? null);
|
|
27
|
+
/**
|
|
28
|
+
* Builds the anchor overlays portal into. On `body` it is a zero-height positioned block: no layout
|
|
29
|
+
* footprint (beyond a `gap` on a flex or grid body), but a stacking context the host's `z-index` layers cannot beat. Anywhere else it is
|
|
30
|
+
* `display: contents`, so it cannot disturb a flex or grid host. It carries `data-theme` and, with a
|
|
31
|
+
* scope, `data-mfe-scope`, which is what lets the app's scoped CSS reach an overlay rendered outside its tree.
|
|
32
|
+
*
|
|
33
|
+
* @param container The element the anchor is appended to.
|
|
34
|
+
* @param options The scope to stamp, if any, the resolved theme mode, and the stacking tier for a `body` anchor.
|
|
35
|
+
* @returns The appended anchor element.
|
|
36
|
+
*/
|
|
37
|
+
const createPortalAnchor = (container, { scopeId, resolvedMode, zIndex = OVERLAY_ROOT_Z_INDEX_FALLBACK }) => {
|
|
38
|
+
const anchor = document.createElement('div');
|
|
39
|
+
if (container === document.body) {
|
|
40
|
+
anchor.style.position = 'relative';
|
|
41
|
+
anchor.style.height = '0';
|
|
42
|
+
anchor.style.zIndex = String(zIndex);
|
|
43
|
+
// A flex or grid body must not stretch the block or give it a share of the space; a `gap` still applies.
|
|
44
|
+
anchor.style.flex = '0 0 auto';
|
|
45
|
+
anchor.style.alignSelf = 'flex-start';
|
|
46
|
+
anchor.style.margin = '0';
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
anchor.style.display = 'contents';
|
|
50
|
+
}
|
|
51
|
+
anchor.setAttribute('data-theme', resolvedMode);
|
|
52
|
+
if (scopeId)
|
|
53
|
+
anchor.setAttribute('data-mfe-scope', scopeId);
|
|
54
|
+
container.appendChild(anchor);
|
|
55
|
+
return anchor;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Keeps one themed, optionally scoped anchor inside the resolved portal container and hands React Aria
|
|
59
|
+
* a `getContainer` that only ever returns an attached anchor or the themed root. Compares resolved DOM
|
|
60
|
+
* nodes rather than callback identity, so an inline `() => getElementById(...)` does not rebuild the
|
|
61
|
+
* anchor on every render, and returns `null` for the commit in which the container is still changing so
|
|
62
|
+
* no overlay lands on `document.body` unstyled and then relocates.
|
|
63
|
+
*
|
|
64
|
+
* @param options The container choice, scope, themed root and theme mode.
|
|
65
|
+
* @returns The `getContainer` callback for `PortalContainer`.
|
|
66
|
+
*/
|
|
67
|
+
const usePortalAnchor = ({ portalContainer, scopeId, themeRoot, resolvedMode }) => {
|
|
68
|
+
const resolveContainer = useMemo(() => resolvePortalContainer(portalContainer, scopeId), [portalContainer, scopeId]);
|
|
69
|
+
const [portalAnchor, setPortalAnchor] = useState(null);
|
|
70
|
+
const portalAnchorNode = useRef(null);
|
|
71
|
+
const lastExplicitContainer = useRef(null);
|
|
72
|
+
// useLayoutEffect + useState so the anchor exists before paint and its creation re-renders
|
|
73
|
+
// PortalContainer with a real container; a ref-only write left the first commit portalling to body.
|
|
74
|
+
useLayoutEffect(() => {
|
|
75
|
+
const explicit = resolveContainer?.() ?? null;
|
|
76
|
+
if (explicit === lastExplicitContainer.current) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
lastExplicitContainer.current = explicit;
|
|
80
|
+
portalAnchorNode.current?.remove();
|
|
81
|
+
portalAnchorNode.current = null;
|
|
82
|
+
if (!explicit) {
|
|
83
|
+
setPortalAnchor(null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const anchor = createPortalAnchor(explicit, { scopeId, resolvedMode, zIndex: readOverlayZIndex(themeRoot) });
|
|
87
|
+
portalAnchorNode.current = anchor;
|
|
88
|
+
setPortalAnchor(anchor);
|
|
89
|
+
});
|
|
90
|
+
useLayoutEffect(() => () => {
|
|
91
|
+
portalAnchorNode.current?.remove();
|
|
92
|
+
portalAnchorNode.current = null;
|
|
93
|
+
// Reset alongside the node so StrictMode's dev-only mount → cleanup → remount does not see a stale
|
|
94
|
+
// "unchanged" match, skip recreating the anchor, and keep state pointing at the removed node.
|
|
95
|
+
lastExplicitContainer.current = null;
|
|
96
|
+
}, []);
|
|
97
|
+
useLayoutEffect(() => {
|
|
98
|
+
portalAnchorNode.current?.setAttribute('data-theme', resolvedMode);
|
|
99
|
+
}, [resolvedMode]);
|
|
100
|
+
useLayoutEffect(() => {
|
|
101
|
+
const anchor = portalAnchorNode.current;
|
|
102
|
+
if (!anchor)
|
|
103
|
+
return;
|
|
104
|
+
if (scopeId)
|
|
105
|
+
anchor.setAttribute('data-mfe-scope', scopeId);
|
|
106
|
+
else
|
|
107
|
+
anchor.removeAttribute('data-mfe-scope');
|
|
108
|
+
}, [scopeId]);
|
|
109
|
+
// `themeRoot` is state filled by a ref callback after the first commit, so the anchor is created before the
|
|
110
|
+
// token can be read; re-read it once the root exists (and whenever it changes) so a scoped override applies.
|
|
111
|
+
useLayoutEffect(() => {
|
|
112
|
+
if (!portalAnchor || portalAnchor.parentElement !== document.body)
|
|
113
|
+
return;
|
|
114
|
+
portalAnchor.style.zIndex = String(readOverlayZIndex(themeRoot));
|
|
115
|
+
}, [themeRoot, portalAnchor]);
|
|
116
|
+
// Re-resolving here (rather than trusting state alone) covers a host swap: this render's anchor still
|
|
117
|
+
// belongs to the old host until the layout effect rebuilds it, so it must not be handed out.
|
|
118
|
+
return useCallback(() => {
|
|
119
|
+
const explicit = resolveContainer?.() ?? null;
|
|
120
|
+
if (explicit) {
|
|
121
|
+
return explicit === lastExplicitContainer.current ? portalAnchor : null;
|
|
122
|
+
}
|
|
123
|
+
if (resolveContainer) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return themeRoot;
|
|
127
|
+
}, [portalAnchor, resolveContainer, themeRoot]);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export { OVERLAY_ROOT_Z_INDEX_FALLBACK, createPortalAnchor, readOverlayZIndex, resolvePortalContainer, usePortalAnchor };
|
|
131
|
+
//# sourceMappingURL=usePortalAnchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePortalAnchor.js","sources":["../../../../src/providers/UIProvider/hooks/usePortalAnchor.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -5,84 +5,27 @@ var ErrorBoundary = require('@flipdish/ui-library/providers/ErrorBoundary');
|
|
|
5
5
|
var PortalContainer = require('@flipdish/ui-library/providers/PortalContainer');
|
|
6
6
|
var RouteProvider = require('@flipdish/ui-library/providers/RouteProvider');
|
|
7
7
|
var ThemeProvider = require('@flipdish/ui-library/providers/ThemeProvider');
|
|
8
|
-
var React = require('react');
|
|
9
8
|
var reactRouter = require('react-router');
|
|
9
|
+
var usePortalAnchor = require('./hooks/usePortalAnchor.cjs');
|
|
10
10
|
|
|
11
|
-
const UIProviderInner = ({ children, baseUrl, errorBoundary, portalContainer }) => {
|
|
11
|
+
const UIProviderInner = ({ children, baseUrl, errorBoundary, scopeId, portalContainer, }) => {
|
|
12
12
|
const { themeRoot, resolvedMode } = ThemeProvider.useTheme();
|
|
13
|
-
const
|
|
14
|
-
const portalAnchorNode = React.useRef(null);
|
|
15
|
-
const lastExplicitContainer = React.useRef(null);
|
|
16
|
-
// Create a themed, layout-invisible anchor inside the portalContainer so
|
|
17
|
-
// React Aria overlays inherit scoped CSS and the active data-theme.
|
|
18
|
-
// Compares resolved DOM nodes (not callback identity) to avoid tearing
|
|
19
|
-
// down the anchor when an inline callback like `() => getElementById(...)` is passed.
|
|
20
|
-
// useLayoutEffect + useState so the anchor exists before paint and its
|
|
21
|
-
// creation re-renders PortalContainer with a real container — a ref-only
|
|
22
|
-
// write left the first commit portalling to document.body.
|
|
23
|
-
React.useLayoutEffect(() => {
|
|
24
|
-
const explicit = portalContainer?.() ?? null;
|
|
25
|
-
if (explicit === lastExplicitContainer.current) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
lastExplicitContainer.current = explicit;
|
|
29
|
-
portalAnchorNode.current?.remove();
|
|
30
|
-
portalAnchorNode.current = null;
|
|
31
|
-
if (!explicit) {
|
|
32
|
-
setPortalAnchor(null);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
const anchor = document.createElement('div');
|
|
36
|
-
anchor.style.display = 'contents';
|
|
37
|
-
anchor.setAttribute('data-theme', resolvedMode);
|
|
38
|
-
explicit.appendChild(anchor);
|
|
39
|
-
portalAnchorNode.current = anchor;
|
|
40
|
-
setPortalAnchor(anchor);
|
|
41
|
-
});
|
|
42
|
-
React.useLayoutEffect(() => () => {
|
|
43
|
-
portalAnchorNode.current?.remove();
|
|
44
|
-
portalAnchorNode.current = null;
|
|
45
|
-
// Reset alongside portalAnchorNode so StrictMode's dev-only mount → cleanup →
|
|
46
|
-
// remount doesn't see a stale "unchanged" match on remount: without this, the
|
|
47
|
-
// remount's effect compares against the pre-cleanup container, skips recreating
|
|
48
|
-
// the anchor, and leaves `portalAnchor` state pointing at the node just removed
|
|
49
|
-
// above — permanently detached from the document.
|
|
50
|
-
lastExplicitContainer.current = null;
|
|
51
|
-
}, []);
|
|
52
|
-
React.useLayoutEffect(() => {
|
|
53
|
-
portalAnchorNode.current?.setAttribute('data-theme', resolvedMode);
|
|
54
|
-
}, [resolvedMode]);
|
|
55
|
-
// While the explicit container's anchor (or ThemeProvider's themed root) is
|
|
56
|
-
// unresolved, return null so overlays render nothing for that commit instead
|
|
57
|
-
// of landing on document.body and then relocating. Re-resolving `portalContainer`
|
|
58
|
-
// here (rather than trusting `portalAnchor` alone) also covers a host swap: if the
|
|
59
|
-
// host changes from A to B, this render's anchor still belongs to host A until the
|
|
60
|
-
// layout effect rebuilds it, so we must not hand out the stale host-A anchor.
|
|
61
|
-
const getContainer = React.useCallback(() => {
|
|
62
|
-
const explicit = portalContainer?.() ?? null;
|
|
63
|
-
if (explicit) {
|
|
64
|
-
return explicit === lastExplicitContainer.current ? portalAnchor : null;
|
|
65
|
-
}
|
|
66
|
-
if (portalContainer) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
return themeRoot;
|
|
70
|
-
}, [portalAnchor, portalContainer, themeRoot]);
|
|
13
|
+
const getContainer = usePortalAnchor.usePortalAnchor({ portalContainer, scopeId, themeRoot, resolvedMode });
|
|
71
14
|
return (jsxRuntime.jsx(reactRouter.BrowserRouter, { basename: baseUrl, children: jsxRuntime.jsx(RouteProvider.RouteProvider, { children: jsxRuntime.jsx(ErrorBoundary.ErrorBoundary, { ...errorBoundary, children: jsxRuntime.jsx(PortalContainer.PortalContainer, { getContainer: getContainer, children: children }) }) }) }));
|
|
72
15
|
};
|
|
73
16
|
/**
|
|
74
17
|
* Single entry-point provider that composes `ThemeProvider`, `RouteProvider`, `ErrorBoundary`
|
|
75
|
-
* and overlay portalling for a consuming app. Wire it once at the root
|
|
76
|
-
*
|
|
18
|
+
* and overlay portalling for a consuming app. Wire it once at the root and pass `scopeId` when the
|
|
19
|
+
* app ships CSS scoped to `[data-mfe-scope]`; overlays then portal into a scoped anchor on `body`.
|
|
77
20
|
*
|
|
78
21
|
* @summary root provider composing theme, routing, errors and portals
|
|
79
22
|
*/
|
|
80
|
-
const UIProvider = ({ children, themeModeOverride, ...rest }) => {
|
|
23
|
+
const UIProvider = ({ children, themeModeOverride, scopeId, ...rest }) => {
|
|
81
24
|
// TODO(i18n): add an internal library translation context so ui-library components can
|
|
82
25
|
// source their own copy (e.g. ErrorBoundary's "Something went wrong") without the
|
|
83
26
|
// consuming app needing to pass text props. This is purely internal — consuming apps
|
|
84
27
|
// own their own translation layer and wrap their content inside UIProvider as usual.
|
|
85
|
-
return (jsxRuntime.jsx(ThemeProvider.ThemeProvider, { themeModeOverride: themeModeOverride, children: jsxRuntime.jsx(UIProviderInner, { ...rest, children: children }) }));
|
|
28
|
+
return (jsxRuntime.jsx(ThemeProvider.ThemeProvider, { themeModeOverride: themeModeOverride, scopeId: scopeId, children: jsxRuntime.jsx(UIProviderInner, { scopeId: scopeId, ...rest, children: children }) }));
|
|
86
29
|
};
|
|
87
30
|
|
|
88
31
|
exports.UIProvider = UIProvider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../src/providers/UIProvider/index.tsx"],"sourcesContent":[null],"names":["useTheme","
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../src/providers/UIProvider/index.tsx"],"sourcesContent":[null],"names":["useTheme","usePortalAnchor","_jsx","BrowserRouter","RouteProvider","ErrorBoundary","PortalContainer","ThemeProvider"],"mappings":";;;;;;;;;;AAiDA,MAAM,eAAe,GAAG,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,OAAO,EACP,eAAe,GAC+C,KAAI;IAClE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAGA,sBAAQ,EAAE;AAC9C,IAAA,MAAM,YAAY,GAAGC,+BAAe,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAE3F,IAAA,QACEC,cAAA,CAACC,yBAAa,EAAA,EAAC,QAAQ,EAAE,OAAO,EAAA,QAAA,EAC9BD,cAAA,CAACE,2BAAa,EAAA,EAAA,QAAA,EACZF,cAAA,CAACG,2BAAa,EAAA,EAAA,GAAK,aAAa,EAAA,QAAA,EAC9BH,cAAA,CAACI,+BAAe,EAAA,EAAC,YAAY,EAAE,YAAY,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAmB,EAAA,CAC3D,EAAA,CACF,EAAA,CACF;AAEpB,CAAC;AAED;;;;;;AAMG;AACI,MAAM,UAAU,GAAG,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,GAAG,IAAI,EAAsC,KAAI;;;;;IAKlH,QACEJ,cAAA,CAACK,2BAAa,EAAA,EAAC,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAA,QAAA,EACnEL,eAAC,eAAe,EAAA,EAAC,OAAO,EAAE,OAAO,EAAA,GAAM,IAAI,EAAA,QAAA,EACxC,QAAQ,EAAA,CACO,EAAA,CACJ;AAEpB;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ErrorBoundaryProps } from '@flipdish/ui-library/providers/ErrorBoundary';
|
|
2
|
-
import {
|
|
2
|
+
import type { PropsWithChildren } from 'react';
|
|
3
3
|
export interface UIProviderProps {
|
|
4
4
|
/**
|
|
5
5
|
* Controlled theme override. When set, the tree renders in this mode and theme
|
|
@@ -20,25 +20,30 @@ export interface UIProviderProps {
|
|
|
20
20
|
* errorBoundary={{ identifier: 'app', onError: datadogReporter }}
|
|
21
21
|
*/
|
|
22
22
|
errorBoundary: Omit<ErrorBoundaryProps, 'children'>;
|
|
23
|
+
/**
|
|
24
|
+
* Stamps `data-mfe-scope={scopeId}` on the themed root so the app's scoped CSS build
|
|
25
|
+
* (`createScopedPrefixTransform('[data-mfe-scope="sale-service"]')`) matches this tree wherever the
|
|
26
|
+
* host mounts it. Overlays then portal into a scoped, themed anchor on `document.body`, above the
|
|
27
|
+
* host's stacking contexts, and `ThemeProvider` stops mirroring `data-theme` onto the host `<html>`.
|
|
28
|
+
*/
|
|
29
|
+
scopeId?: string;
|
|
23
30
|
/**
|
|
24
31
|
* Callback that returns the DOM element where React Aria overlays (Modal, Popover,
|
|
25
|
-
* Select, Tooltip, Toast) should portal into.
|
|
26
|
-
*
|
|
32
|
+
* Select, Tooltip, Toast) should portal into. A layout-invisible anchor carrying `data-theme`
|
|
33
|
+
* and, with `scopeId`, `data-mfe-scope` is created inside it so overlays keep their styles.
|
|
27
34
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* portalContainer={() => document.getElementById('flipdish-micro-frontend')}
|
|
35
|
+
* Defaults to `document.body` when `scopeId` is set, where the anchor is a stacking context at the
|
|
36
|
+
* `--z-index-overlay-root` token (1300) so overlays paint above the host's chrome and isolated content
|
|
37
|
+
* areas, and to the themed root otherwise. Pass `null` to portal into the themed root even with `scopeId`.
|
|
33
38
|
*/
|
|
34
39
|
portalContainer?: (() => HTMLElement | null) | null;
|
|
35
40
|
}
|
|
36
41
|
/**
|
|
37
42
|
* Single entry-point provider that composes `ThemeProvider`, `RouteProvider`, `ErrorBoundary`
|
|
38
|
-
* and overlay portalling for a consuming app. Wire it once at the root
|
|
39
|
-
*
|
|
43
|
+
* and overlay portalling for a consuming app. Wire it once at the root and pass `scopeId` when the
|
|
44
|
+
* app ships CSS scoped to `[data-mfe-scope]`; overlays then portal into a scoped anchor on `body`.
|
|
40
45
|
*
|
|
41
46
|
* @summary root provider composing theme, routing, errors and portals
|
|
42
47
|
*/
|
|
43
|
-
export declare const UIProvider: ({ children, themeModeOverride, ...rest }: PropsWithChildren<UIProviderProps>) => import("react").JSX.Element;
|
|
48
|
+
export declare const UIProvider: ({ children, themeModeOverride, scopeId, ...rest }: PropsWithChildren<UIProviderProps>) => import("react").JSX.Element;
|
|
44
49
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/UIProvider/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAItG,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/UIProvider/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAItG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAI/C,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,aAAa,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;IACpD;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CACrD;AAuBD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,mDAAmD,iBAAiB,CAAC,eAAe,CAAC,gCAY/G,CAAC"}
|
|
@@ -3,84 +3,27 @@ import { ErrorBoundary } from '@flipdish/ui-library/providers/ErrorBoundary';
|
|
|
3
3
|
import { PortalContainer } from '@flipdish/ui-library/providers/PortalContainer';
|
|
4
4
|
import { RouteProvider } from '@flipdish/ui-library/providers/RouteProvider';
|
|
5
5
|
import { ThemeProvider, useTheme } from '@flipdish/ui-library/providers/ThemeProvider';
|
|
6
|
-
import { useState, useRef, useLayoutEffect, useCallback } from 'react';
|
|
7
6
|
import { BrowserRouter } from 'react-router';
|
|
7
|
+
import { usePortalAnchor } from './hooks/usePortalAnchor.js';
|
|
8
8
|
|
|
9
|
-
const UIProviderInner = ({ children, baseUrl, errorBoundary, portalContainer }) => {
|
|
9
|
+
const UIProviderInner = ({ children, baseUrl, errorBoundary, scopeId, portalContainer, }) => {
|
|
10
10
|
const { themeRoot, resolvedMode } = useTheme();
|
|
11
|
-
const
|
|
12
|
-
const portalAnchorNode = useRef(null);
|
|
13
|
-
const lastExplicitContainer = useRef(null);
|
|
14
|
-
// Create a themed, layout-invisible anchor inside the portalContainer so
|
|
15
|
-
// React Aria overlays inherit scoped CSS and the active data-theme.
|
|
16
|
-
// Compares resolved DOM nodes (not callback identity) to avoid tearing
|
|
17
|
-
// down the anchor when an inline callback like `() => getElementById(...)` is passed.
|
|
18
|
-
// useLayoutEffect + useState so the anchor exists before paint and its
|
|
19
|
-
// creation re-renders PortalContainer with a real container — a ref-only
|
|
20
|
-
// write left the first commit portalling to document.body.
|
|
21
|
-
useLayoutEffect(() => {
|
|
22
|
-
const explicit = portalContainer?.() ?? null;
|
|
23
|
-
if (explicit === lastExplicitContainer.current) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
lastExplicitContainer.current = explicit;
|
|
27
|
-
portalAnchorNode.current?.remove();
|
|
28
|
-
portalAnchorNode.current = null;
|
|
29
|
-
if (!explicit) {
|
|
30
|
-
setPortalAnchor(null);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const anchor = document.createElement('div');
|
|
34
|
-
anchor.style.display = 'contents';
|
|
35
|
-
anchor.setAttribute('data-theme', resolvedMode);
|
|
36
|
-
explicit.appendChild(anchor);
|
|
37
|
-
portalAnchorNode.current = anchor;
|
|
38
|
-
setPortalAnchor(anchor);
|
|
39
|
-
});
|
|
40
|
-
useLayoutEffect(() => () => {
|
|
41
|
-
portalAnchorNode.current?.remove();
|
|
42
|
-
portalAnchorNode.current = null;
|
|
43
|
-
// Reset alongside portalAnchorNode so StrictMode's dev-only mount → cleanup →
|
|
44
|
-
// remount doesn't see a stale "unchanged" match on remount: without this, the
|
|
45
|
-
// remount's effect compares against the pre-cleanup container, skips recreating
|
|
46
|
-
// the anchor, and leaves `portalAnchor` state pointing at the node just removed
|
|
47
|
-
// above — permanently detached from the document.
|
|
48
|
-
lastExplicitContainer.current = null;
|
|
49
|
-
}, []);
|
|
50
|
-
useLayoutEffect(() => {
|
|
51
|
-
portalAnchorNode.current?.setAttribute('data-theme', resolvedMode);
|
|
52
|
-
}, [resolvedMode]);
|
|
53
|
-
// While the explicit container's anchor (or ThemeProvider's themed root) is
|
|
54
|
-
// unresolved, return null so overlays render nothing for that commit instead
|
|
55
|
-
// of landing on document.body and then relocating. Re-resolving `portalContainer`
|
|
56
|
-
// here (rather than trusting `portalAnchor` alone) also covers a host swap: if the
|
|
57
|
-
// host changes from A to B, this render's anchor still belongs to host A until the
|
|
58
|
-
// layout effect rebuilds it, so we must not hand out the stale host-A anchor.
|
|
59
|
-
const getContainer = useCallback(() => {
|
|
60
|
-
const explicit = portalContainer?.() ?? null;
|
|
61
|
-
if (explicit) {
|
|
62
|
-
return explicit === lastExplicitContainer.current ? portalAnchor : null;
|
|
63
|
-
}
|
|
64
|
-
if (portalContainer) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
return themeRoot;
|
|
68
|
-
}, [portalAnchor, portalContainer, themeRoot]);
|
|
11
|
+
const getContainer = usePortalAnchor({ portalContainer, scopeId, themeRoot, resolvedMode });
|
|
69
12
|
return (jsx(BrowserRouter, { basename: baseUrl, children: jsx(RouteProvider, { children: jsx(ErrorBoundary, { ...errorBoundary, children: jsx(PortalContainer, { getContainer: getContainer, children: children }) }) }) }));
|
|
70
13
|
};
|
|
71
14
|
/**
|
|
72
15
|
* Single entry-point provider that composes `ThemeProvider`, `RouteProvider`, `ErrorBoundary`
|
|
73
|
-
* and overlay portalling for a consuming app. Wire it once at the root
|
|
74
|
-
*
|
|
16
|
+
* and overlay portalling for a consuming app. Wire it once at the root and pass `scopeId` when the
|
|
17
|
+
* app ships CSS scoped to `[data-mfe-scope]`; overlays then portal into a scoped anchor on `body`.
|
|
75
18
|
*
|
|
76
19
|
* @summary root provider composing theme, routing, errors and portals
|
|
77
20
|
*/
|
|
78
|
-
const UIProvider = ({ children, themeModeOverride, ...rest }) => {
|
|
21
|
+
const UIProvider = ({ children, themeModeOverride, scopeId, ...rest }) => {
|
|
79
22
|
// TODO(i18n): add an internal library translation context so ui-library components can
|
|
80
23
|
// source their own copy (e.g. ErrorBoundary's "Something went wrong") without the
|
|
81
24
|
// consuming app needing to pass text props. This is purely internal — consuming apps
|
|
82
25
|
// own their own translation layer and wrap their content inside UIProvider as usual.
|
|
83
|
-
return (jsx(ThemeProvider, { themeModeOverride: themeModeOverride, children: jsx(UIProviderInner, { ...rest, children: children }) }));
|
|
26
|
+
return (jsx(ThemeProvider, { themeModeOverride: themeModeOverride, scopeId: scopeId, children: jsx(UIProviderInner, { scopeId: scopeId, ...rest, children: children }) }));
|
|
84
27
|
};
|
|
85
28
|
|
|
86
29
|
export { UIProvider };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/providers/UIProvider/index.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/providers/UIProvider/index.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;;;;;;AAiDA,MAAM,eAAe,GAAG,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,aAAa,EACb,OAAO,EACP,eAAe,GAC+C,KAAI;IAClE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE;AAC9C,IAAA,MAAM,YAAY,GAAG,eAAe,CAAC,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAE3F,IAAA,QACEA,GAAA,CAAC,aAAa,EAAA,EAAC,QAAQ,EAAE,OAAO,EAAA,QAAA,EAC9BA,GAAA,CAAC,aAAa,EAAA,EAAA,QAAA,EACZA,GAAA,CAAC,aAAa,EAAA,EAAA,GAAK,aAAa,EAAA,QAAA,EAC9BA,GAAA,CAAC,eAAe,EAAA,EAAC,YAAY,EAAE,YAAY,EAAA,QAAA,EAAG,QAAQ,EAAA,CAAmB,EAAA,CAC3D,EAAA,CACF,EAAA,CACF;AAEpB,CAAC;AAED;;;;;;AAMG;AACI,MAAM,UAAU,GAAG,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,OAAO,EAAE,GAAG,IAAI,EAAsC,KAAI;;;;;IAKlH,QACEA,GAAA,CAAC,aAAa,EAAA,EAAC,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,OAAO,EAAA,QAAA,EACnEA,IAAC,eAAe,EAAA,EAAC,OAAO,EAAE,OAAO,EAAA,GAAM,IAAI,EAAA,QAAA,EACxC,QAAQ,EAAA,CACO,EAAA,CACJ;AAEpB;;"}
|
|
@@ -12,12 +12,20 @@
|
|
|
12
12
|
* [`postcss-prefix-selector`](https://www.npmjs.com/package/postcss-prefix-selector),
|
|
13
13
|
* consolidating the scoping rules every consumer would otherwise hand-copy (and drift on):
|
|
14
14
|
* scoping `:root`/`html` so `@theme` tokens and base typography cascade only within the
|
|
15
|
-
* subtree, leaving `:host` and `@keyframes` steps alone, never double-prefixing,
|
|
16
|
-
*
|
|
15
|
+
* subtree, leaving `:host` and `@keyframes` steps alone, never double-prefixing, letting the scope
|
|
16
|
+
* root element itself match class and attribute selectors (`matchRootElement`, on by default), and
|
|
17
|
+
* optionally scoping `body` resets.
|
|
17
18
|
*
|
|
18
19
|
* This is a pure function with no Node or DOM dependency, so it runs cleanly inside a
|
|
19
20
|
* `postcss.config.mjs`.
|
|
20
21
|
*
|
|
22
|
+
* **The scope must be exactly one selector that is unique to this app**, and the app must
|
|
23
|
+
* render it itself — `UIProvider`'s `scopeId` stamps `data-mfe-scope` on the themed root for
|
|
24
|
+
* exactly this. Never scope to a host container id (`#flipdish-micro-frontend`) or a union of
|
|
25
|
+
* ids (`:is(#a, #b)`): the host mounts other apps in those containers, so every rule leaks
|
|
26
|
+
* into them and the last-loaded bundle wins (`.text-xl` from one app beat `lg:text-display-xs`
|
|
27
|
+
* in another).
|
|
28
|
+
*
|
|
21
29
|
* Why PostCSS (not Tailwind's own `important(#selector)`): Tailwind v4's `important(...)`
|
|
22
30
|
* syntax emits non-standard `@media important(...)` blocks that lightningcss can't parse
|
|
23
31
|
* during minification, so scoping is done as a PostCSS pass over Tailwind's output instead.
|
|
@@ -28,7 +36,8 @@
|
|
|
28
36
|
* import prefixSelector from 'postcss-prefix-selector';
|
|
29
37
|
* import { createScopedPrefixTransform } from '@flipdish/ui-library/utilities/cssScopeUtils';
|
|
30
38
|
*
|
|
31
|
-
*
|
|
39
|
+
* // Your Portal microFrontendWindowName; must match <UIProvider scopeId="sale-service">.
|
|
40
|
+
* const SCOPE = '[data-mfe-scope="sale-service"]';
|
|
32
41
|
*
|
|
33
42
|
* export default {
|
|
34
43
|
* plugins: [
|
|
@@ -48,14 +57,15 @@ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
|
48
57
|
* Builds the `transform` callback for `postcss-prefix-selector` that scopes Tailwind v4 output
|
|
49
58
|
* under `scopeSelector`.
|
|
50
59
|
*
|
|
51
|
-
* @param scopeSelector The selector every rule is scoped under, such as
|
|
52
|
-
* `'
|
|
60
|
+
* @param scopeSelector The single selector every rule is scoped under, such as
|
|
61
|
+
* `'[data-mfe-scope="sale-service"]'`; must match the `prefix` passed to `postcss-prefix-selector`
|
|
62
|
+
* and must be unique to this app on the page (never a host container id or a union of ids).
|
|
53
63
|
* @param options Behavioural toggles; see `ScopedPrefixTransformOptions`.
|
|
54
64
|
* @returns The `(prefix, selector, prefixedSelector, filePath?) => string` transform to pass
|
|
55
65
|
* to the plugin.
|
|
56
66
|
*/
|
|
57
67
|
function createScopedPrefixTransform(scopeSelector, options = {}) {
|
|
58
|
-
const { shouldScope, scopeBody = false, matchRootElement =
|
|
68
|
+
const { shouldScope, scopeBody = false, matchRootElement = true } = options;
|
|
59
69
|
const normalisedScopeSelector = scopeSelector.trim();
|
|
60
70
|
if (!normalisedScopeSelector) {
|
|
61
71
|
throw new Error('createScopedPrefixTransform: `scopeSelector` must be a non-empty selector.');
|
|
@@ -93,13 +103,14 @@ function createScopedPrefixTransform(scopeSelector, options = {}) {
|
|
|
93
103
|
if (KEYFRAME_STEP.test(selector.trim())) {
|
|
94
104
|
return selector;
|
|
95
105
|
}
|
|
96
|
-
// Class selectors:
|
|
97
|
-
//
|
|
98
|
-
|
|
106
|
+
// Class and attribute selectors: also match the scope root itself via a compound selector
|
|
107
|
+
// (`[data-mfe-scope="x"][data-theme="dark"], [data-mfe-scope="x"] [data-theme="dark"]`); the
|
|
108
|
+
// scope root is the themed root, so the dark-token rule only reaches it this way.
|
|
109
|
+
if (matchRootElement && (selector.startsWith('.') || selector.startsWith('['))) {
|
|
99
110
|
return `${prefix}${selector}, ${prefixedSelector}`;
|
|
100
111
|
}
|
|
101
|
-
// Everything else (`*`, `::before`, `::after`,
|
|
102
|
-
//
|
|
112
|
+
// Everything else (`*`, `::before`, `::after`, element selectors, …) gets the standard
|
|
113
|
+
// descendant-scoped selector.
|
|
103
114
|
return prefixedSelector;
|
|
104
115
|
};
|
|
105
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../../src/utilities/cssScopeUtils/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../src/utilities/cssScopeUtils/index.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDG;AA6BH;AACA,MAAM,aAAa,GAAG,0BAA0B;AAEhD;AACA,MAAM,YAAY,GAAG,CAAC,KAAa,KAAa,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;AAE5F;;;;;;;;;;AAUG;SACa,2BAA2B,CACzC,aAAqB,EACrB,UAAwC,EAAE,EAAA;AAE1C,IAAA,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,KAAK,EAAE,gBAAgB,GAAG,IAAI,EAAE,GAAG,OAAO;AAE3E,IAAA,MAAM,uBAAuB,GAAG,aAAa,CAAC,IAAI,EAAE;IACpD,IAAI,CAAC,uBAAuB,EAAE;AAC5B,QAAA,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC;IAC/F;;;;;AAMA,IAAA,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,CAAA,CAAA,EAAI,YAAY,CAAC,uBAAuB,CAAC,CAAA,qBAAA,CAAuB,CAAC;IAElG,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,QAAQ,KAAI;;QAEtD,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE;AACzC,YAAA,OAAO,QAAQ;QACjB;;QAGA,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE;AAC5C,YAAA,OAAO,QAAQ;QACjB;;;;QAKA,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,MAAM,EAAE;AAC/C,YAAA,OAAO,MAAM;QACf;;AAGA,QAAA,IAAI,QAAQ,KAAK,OAAO,EAAE;AACxB,YAAA,OAAO,QAAQ;QACjB;;;AAIA,QAAA,IAAI,QAAQ,KAAK,MAAM,EAAE;YACvB,OAAO,SAAS,GAAG,MAAM,GAAG,QAAQ;QACtC;;QAGA,IAAI,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE;AACvC,YAAA,OAAO,QAAQ;QACjB;;;;AAKA,QAAA,IAAI,gBAAgB,KAAK,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE;AAC9E,YAAA,OAAO,GAAG,MAAM,CAAA,EAAG,QAAQ,CAAA,EAAA,EAAK,gBAAgB,EAAE;QACpD;;;AAIA,QAAA,OAAO,gBAAgB;AACzB,IAAA,CAAC;AACH;;"}
|
|
@@ -10,12 +10,20 @@
|
|
|
10
10
|
* [`postcss-prefix-selector`](https://www.npmjs.com/package/postcss-prefix-selector),
|
|
11
11
|
* consolidating the scoping rules every consumer would otherwise hand-copy (and drift on):
|
|
12
12
|
* scoping `:root`/`html` so `@theme` tokens and base typography cascade only within the
|
|
13
|
-
* subtree, leaving `:host` and `@keyframes` steps alone, never double-prefixing,
|
|
14
|
-
*
|
|
13
|
+
* subtree, leaving `:host` and `@keyframes` steps alone, never double-prefixing, letting the scope
|
|
14
|
+
* root element itself match class and attribute selectors (`matchRootElement`, on by default), and
|
|
15
|
+
* optionally scoping `body` resets.
|
|
15
16
|
*
|
|
16
17
|
* This is a pure function with no Node or DOM dependency, so it runs cleanly inside a
|
|
17
18
|
* `postcss.config.mjs`.
|
|
18
19
|
*
|
|
20
|
+
* **The scope must be exactly one selector that is unique to this app**, and the app must
|
|
21
|
+
* render it itself — `UIProvider`'s `scopeId` stamps `data-mfe-scope` on the themed root for
|
|
22
|
+
* exactly this. Never scope to a host container id (`#flipdish-micro-frontend`) or a union of
|
|
23
|
+
* ids (`:is(#a, #b)`): the host mounts other apps in those containers, so every rule leaks
|
|
24
|
+
* into them and the last-loaded bundle wins (`.text-xl` from one app beat `lg:text-display-xs`
|
|
25
|
+
* in another).
|
|
26
|
+
*
|
|
19
27
|
* Why PostCSS (not Tailwind's own `important(#selector)`): Tailwind v4's `important(...)`
|
|
20
28
|
* syntax emits non-standard `@media important(...)` blocks that lightningcss can't parse
|
|
21
29
|
* during minification, so scoping is done as a PostCSS pass over Tailwind's output instead.
|
|
@@ -26,7 +34,8 @@
|
|
|
26
34
|
* import prefixSelector from 'postcss-prefix-selector';
|
|
27
35
|
* import { createScopedPrefixTransform } from '@flipdish/ui-library/utilities/cssScopeUtils';
|
|
28
36
|
*
|
|
29
|
-
*
|
|
37
|
+
* // Your Portal microFrontendWindowName; must match <UIProvider scopeId="sale-service">.
|
|
38
|
+
* const SCOPE = '[data-mfe-scope="sale-service"]';
|
|
30
39
|
*
|
|
31
40
|
* export default {
|
|
32
41
|
* plugins: [
|
|
@@ -55,11 +64,12 @@ export interface ScopedPrefixTransformOptions {
|
|
|
55
64
|
*/
|
|
56
65
|
scopeBody?: boolean;
|
|
57
66
|
/**
|
|
58
|
-
* Also emit a compound selector so the scope root element itself matches class
|
|
59
|
-
* (
|
|
60
|
-
*
|
|
67
|
+
* Also emit a compound selector so the scope root element itself matches class and attribute
|
|
68
|
+
* selectors (`[data-mfe-scope="x"].flex, [data-mfe-scope="x"] .flex`). The scope root is the
|
|
69
|
+
* themed root, which carries `data-theme`, so the dark-token rule `[data-theme="dark"]` only
|
|
70
|
+
* reaches it in this form; set `false` only when the scope root is a bare host container.
|
|
61
71
|
*
|
|
62
|
-
* @default
|
|
72
|
+
* @default true
|
|
63
73
|
*/
|
|
64
74
|
matchRootElement?: boolean;
|
|
65
75
|
}
|
|
@@ -67,8 +77,9 @@ export interface ScopedPrefixTransformOptions {
|
|
|
67
77
|
* Builds the `transform` callback for `postcss-prefix-selector` that scopes Tailwind v4 output
|
|
68
78
|
* under `scopeSelector`.
|
|
69
79
|
*
|
|
70
|
-
* @param scopeSelector The selector every rule is scoped under, such as
|
|
71
|
-
* `'
|
|
80
|
+
* @param scopeSelector The single selector every rule is scoped under, such as
|
|
81
|
+
* `'[data-mfe-scope="sale-service"]'`; must match the `prefix` passed to `postcss-prefix-selector`
|
|
82
|
+
* and must be unique to this app on the page (never a host container id or a union of ids).
|
|
72
83
|
* @param options Behavioural toggles; see `ScopedPrefixTransformOptions`.
|
|
73
84
|
* @returns The `(prefix, selector, prefixedSelector, filePath?) => string` transform to pass
|
|
74
85
|
* to the plugin.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utilities/cssScopeUtils/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utilities/cssScopeUtils/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC;IACxD;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,4BAAiC,GACzC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CA2D3F"}
|