@flipdish/ui-library 0.38.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +9 -9
  2. package/dist/components/templates/WidgetLayout/index.cjs +36 -0
  3. package/dist/components/templates/WidgetLayout/index.cjs.map +1 -0
  4. package/dist/components/templates/WidgetLayout/index.d.ts +54 -0
  5. package/dist/components/templates/WidgetLayout/index.d.ts.map +1 -0
  6. package/dist/components/templates/WidgetLayout/index.js +34 -0
  7. package/dist/components/templates/WidgetLayout/index.js.map +1 -0
  8. package/dist/native/components/templates/WidgetLayout/index.d.ts +12 -0
  9. package/dist/providers/PortalContainer/index.d.ts +1 -1
  10. package/dist/providers/ThemeProvider/index.cjs +36 -14
  11. package/dist/providers/ThemeProvider/index.cjs.map +1 -1
  12. package/dist/providers/ThemeProvider/index.d.ts +19 -5
  13. package/dist/providers/ThemeProvider/index.d.ts.map +1 -1
  14. package/dist/providers/ThemeProvider/index.js +36 -14
  15. package/dist/providers/ThemeProvider/index.js.map +1 -1
  16. package/dist/providers/UIProvider/hooks/usePortalAnchor.cjs +137 -0
  17. package/dist/providers/UIProvider/hooks/usePortalAnchor.cjs.map +1 -0
  18. package/dist/providers/UIProvider/hooks/usePortalAnchor.d.ts +60 -0
  19. package/dist/providers/UIProvider/hooks/usePortalAnchor.d.ts.map +1 -0
  20. package/dist/providers/UIProvider/hooks/usePortalAnchor.js +131 -0
  21. package/dist/providers/UIProvider/hooks/usePortalAnchor.js.map +1 -0
  22. package/dist/providers/UIProvider/index.cjs +7 -64
  23. package/dist/providers/UIProvider/index.cjs.map +1 -1
  24. package/dist/providers/UIProvider/index.d.ts +16 -11
  25. package/dist/providers/UIProvider/index.d.ts.map +1 -1
  26. package/dist/providers/UIProvider/index.js +7 -64
  27. package/dist/providers/UIProvider/index.js.map +1 -1
  28. package/dist/utilities/cssScopeUtils/index.cjs +22 -11
  29. package/dist/utilities/cssScopeUtils/index.cjs.map +1 -1
  30. package/dist/utilities/cssScopeUtils/index.d.ts +20 -9
  31. package/dist/utilities/cssScopeUtils/index.d.ts.map +1 -1
  32. package/dist/utilities/cssScopeUtils/index.js +22 -11
  33. package/dist/utilities/cssScopeUtils/index.js.map +1 -1
  34. package/dist/utilities/renderUtils/index.cjs +36 -7
  35. package/dist/utilities/renderUtils/index.cjs.map +1 -1
  36. package/dist/utilities/renderUtils/index.d.ts +23 -2
  37. package/dist/utilities/renderUtils/index.d.ts.map +1 -1
  38. package/dist/utilities/renderUtils/index.js +35 -8
  39. package/dist/utilities/renderUtils/index.js.map +1 -1
  40. package/package.json +1 -1
@@ -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 [portalAnchor, setPortalAnchor] = React.useState(null);
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; pass `portalContainer`
76
- * only when overlays must land in a specific container.
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","useState","useRef","useLayoutEffect","useCallback","_jsx","BrowserRouter","RouteProvider","ErrorBoundary","PortalContainer","ThemeProvider"],"mappings":"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"}
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 { type PropsWithChildren } from 'react';
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. Passed to `PortalContainer`
26
- * internally — see its docs for full details.
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
- * Use this in micro-frontends or embedded apps whose CSS is scoped to a mount
29
- * subtree. Omit to portal into ThemeProvider's themed root element.
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; pass `portalContainer`
39
- * only when overlays must land in a specific container.
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,KAAK,iBAAiB,EAAkD,MAAM,OAAO,CAAC;AAG/F,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;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CACrD;AAoFD;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,0CAA0C,iBAAiB,CAAC,eAAe,CAAC,gCAUtG,CAAC"}
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 [portalAnchor, setPortalAnchor] = useState(null);
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; pass `portalContainer`
74
- * only when overlays must land in a specific container.
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":"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"}
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, and
16
- * optionally scoping `body` resets or letting the scope root element match class selectors.
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
- * const SCOPE = '#flipdish-micro-frontend';
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
- * `'#flipdish-micro-frontend'`; must match the `prefix` passed to `postcss-prefix-selector`.
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 = false } = options;
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: optionally also match the scope root element itself via a compound
97
- // selector (`#scope.flex-1, #scope .flex-1`), so a utility on the mount node applies too.
98
- if (matchRootElement && selector.startsWith('.')) {
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`, `[hidden]`, element selectors, …) gets the
102
- // standard descendant-scoped selector.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCG;AA4BH;AACA,MAAM,aAAa,GAAG,0BAA0B;AAEhD;AACA,MAAM,YAAY,GAAG,CAAC,KAAa,KAAa,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC;AAE5F;;;;;;;;;AASG;SACa,2BAA2B,CACzC,aAAqB,EACrB,UAAwC,EAAE,EAAA;AAE1C,IAAA,MAAM,EAAE,WAAW,EAAE,SAAS,GAAG,KAAK,EAAE,gBAAgB,GAAG,KAAK,EAAE,GAAG,OAAO;AAE5E,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;;;QAIA,IAAI,gBAAgB,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAChD,YAAA,OAAO,GAAG,MAAM,CAAA,EAAG,QAAQ,CAAA,EAAA,EAAK,gBAAgB,EAAE;QACpD;;;AAIA,QAAA,OAAO,gBAAgB;AACzB,IAAA,CAAC;AACH;;"}
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, and
14
- * optionally scoping `body` resets or letting the scope root element match class selectors.
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
- * const SCOPE = '#flipdish-micro-frontend';
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 selectors
59
- * (e.g. `<div data-scope class="flex-1">` — where the utility must apply to the mount node,
60
- * not just its descendants).
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 false
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
- * `'#flipdish-micro-frontend'`; must match the `prefix` passed to `postcss-prefix-selector`.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;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;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAQD;;;;;;;;;GASG;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,CA0D3F"}
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"}