@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.
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 +2 -2
package/README.md CHANGED
@@ -185,7 +185,7 @@ Unable to resolve module .../dist/native/components/atoms/Button/index.js
185
185
 
186
186
  ## Scoping Tailwind CSS to a subtree
187
187
 
188
- When your app doesn't own the whole page — a widget, an embed, a Storybook preview, a multi-tenant host, or a micro-frontend — its Tailwind v4 output (utilities **and** Preflight resets) can leak into or clash with the surrounding CSS. The fix is to scope every emitted selector under a single mount element.
188
+ When your app doesn't own the whole page — a widget, an embed, a Storybook preview, a multi-tenant host, or a micro-frontend — its Tailwind v4 output (utilities **and** Preflight resets) can leak into or clash with the surrounding CSS. The fix is to scope every emitted selector under a single element the app renders itself: `UIProvider`'s `scopeId` stamps `data-mfe-scope` on the themed root for this. Never scope to a host container id (`#flipdish-micro-frontend`) or a union of ids; several apps share those containers, so the rules leak across them.
189
189
 
190
190
  `@flipdish/ui-library/utilities/cssScopeUtils` ships `createScopedPrefixTransform`, a small factory for [`postcss-prefix-selector`](https://www.npmjs.com/package/postcss-prefix-selector) that consolidates the scoping rules (scope `:root`/`html` so `@theme` tokens and base typography cascade only within the subtree, leave `:host` and `@keyframes` steps alone, never double-prefix, etc.) so you write one line instead of copy-pasting a bespoke transform.
191
191
 
@@ -202,7 +202,8 @@ Then add it to your PostCSS config:
202
202
  import prefixSelector from "postcss-prefix-selector";
203
203
  import { createScopedPrefixTransform } from "@flipdish/ui-library/utilities/cssScopeUtils";
204
204
 
205
- const SCOPE = "#flipdish-micro-frontend"; // the id/attribute on your mount node
205
+ // Your Portal microFrontendWindowName; must match <UIProvider scopeId="sale-service">.
206
+ const SCOPE = '[data-mfe-scope="sale-service"]';
206
207
 
207
208
  export default {
208
209
  plugins: [
@@ -214,23 +215,22 @@ export default {
214
215
  };
215
216
  ```
216
217
 
217
- Tailwind's output becomes plain `#flipdish-micro-frontend .px-4 { … }`, which every downstream tool (Vite, lightningcss) understands.
218
+ Tailwind's output becomes plain `[data-mfe-scope="sale-service"] .px-4 { … }`, which every downstream tool (Vite, lightningcss) understands.
218
219
 
219
220
  ### Options
220
221
 
221
222
  `createScopedPrefixTransform(scopeSelector, options?)`:
222
223
 
223
- | Option | Type | Default | Description |
224
- | ------------------ | -------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
225
- | `shouldScope` | `(filePath: string \| undefined) => boolean` | `() => true` | Return `false` to leave a file's selectors untouched (e.g. third-party CSS you don't want prefixed). |
226
- | `scopeBody` | `boolean` | `false` | Scope Preflight `body` resets to the mount selector instead of leaving `body` global. Enable when the host page must not inherit the reset. |
227
- | `matchRootElement` | `boolean` | `false` | Also emit a compound selector so the scope root element itself matches class selectors (e.g. `<div data-scope class="flex-1">`). |
224
+ | Option | Type | Default | Description |
225
+ | ------------------ | -------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
226
+ | `shouldScope` | `(filePath: string \| undefined) => boolean` | `() => true` | Return `false` to leave a file's selectors untouched (e.g. third-party CSS you don't want prefixed). |
227
+ | `scopeBody` | `boolean` | `false` | Scope Preflight `body` resets to the mount selector instead of leaving `body` global. Enable when the host page must not inherit the reset. |
228
+ | `matchRootElement` | `boolean` | `true` | Also emit a compound selector so the scope root itself matches class and attribute selectors; `[data-theme="dark"]` on the themed root needs it. |
228
229
 
229
230
  ```js
230
231
  createScopedPrefixTransform("[data-tailwind]", {
231
232
  shouldScope: (file) => !file || !file.includes("node_modules"),
232
233
  scopeBody: true,
233
- matchRootElement: true,
234
234
  });
235
235
  ```
236
236
 
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var ErrorBoundary = require('@flipdish/ui-library/providers/ErrorBoundary');
5
+ var cx = require('@flipdish/ui-library/utilities/cx');
6
+ var testIdUtils = require('@flipdish/ui-library/utilities/testIdUtils');
7
+
8
+ /** English fallback copy — consumers localise via `renderError`. TODO(i18n): source from ui-library's translation layer once one exists. */
9
+ const DefaultWidgetError = ({ testId }) => (jsxRuntime.jsxs("div", { role: "alert", "data-testid": testId, className: "flex w-full flex-col items-center gap-2 py-xl text-center", children: [jsxRuntime.jsx("h2", { className: "font-semibold text-md text-primary", children: "Something went wrong" }), jsxRuntime.jsx("p", { className: "text-sm text-tertiary", children: "An unexpected error occurred in this widget." })] }));
10
+ /**
11
+ * Widget-shaped template for a micro-frontend mounted beside a page: a named `region` that fills
12
+ * the height its host gives it, with pinned `header` and `footer` slots around a content area
13
+ * that scrolls internally, and an error boundary keyed by `widgetName`. Reach for it instead of
14
+ * `PageLayout` whenever the host page already owns `<main>` and the document must not scroll.
15
+ *
16
+ * @summary bounded-height widget template with pinned header and footer
17
+ */
18
+ const WidgetLayout = ({ widgetName, label, header, footer, children, renderError, onError, scrollAreaProps, className, 'data-testid': dataTestId, }) => {
19
+ const widgetLayoutTestId = testIdUtils.useResolvedTestId({ testId: dataTestId, segment: 'widget-layout', rootName: 'widget-layout' });
20
+ const scrollAreaTestId = scrollAreaProps?.['data-testid'] ?? testIdUtils.joinTestId(widgetLayoutTestId, 'scroll-area');
21
+ // A `<section>` with an accessible name is a `region` landmark; without a name it is a plain `div`, so a
22
+ // host that already provides the landmark does not get a nested, same-named one.
23
+ const Root = label ? 'section' : 'div';
24
+ return (
25
+ // `h-full`, never a viewport unit: the host supplies the bounded height, and WebView viewport units can
26
+ // disagree with the visible area. `min-h-0` lets the scroll region shrink below its content's height.
27
+ jsxRuntime.jsxs(Root, { "aria-label": label, "data-testid": widgetLayoutTestId, className: cx.cx('flex h-full min-h-0 w-full flex-col gap-md p-md pb-[max(var(--spacing-md),env(safe-area-inset-bottom))]', className), children: [header, jsxRuntime.jsx("div", { id: scrollAreaProps?.id, ref: scrollAreaProps?.ref, "data-testid": scrollAreaTestId,
28
+ // Focusable so keyboard users can scroll content that has no focusable element of its own (axe: scrollable-region-focusable).
29
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: a scrollable region must be reachable by keyboard
30
+ tabIndex: 0,
31
+ // `overscroll-contain` keeps a scroll that reaches the end from chaining to the host page.
32
+ className: cx.cx('min-h-0 flex-1 overflow-y-auto overscroll-contain outline-focus-ring focus-visible:outline-2 focus-visible:-outline-offset-2', scrollAreaProps?.className), children: jsxRuntime.jsx(ErrorBoundary.ErrorBoundary, { identifier: widgetName, onError: onError, renderFallback: (error) => renderError?.({ error, widgetName }) ?? jsxRuntime.jsx(DefaultWidgetError, { testId: testIdUtils.joinTestId(widgetLayoutTestId, 'error') }), children: children }) }), footer] }));
33
+ };
34
+
35
+ exports.WidgetLayout = WidgetLayout;
36
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../../../../src/components/templates/WidgetLayout/index.tsx"],"sourcesContent":[null],"names":["_jsxs","_jsx","useResolvedTestId","joinTestId","cx","ErrorBoundary"],"mappings":";;;;;;;AAiDA;AACA,MAAM,kBAAkB,GAAG,CAAC,EAAE,MAAM,EAAuB,MACzDA,eAAA,CAAA,KAAA,EAAA,EAAK,IAAI,EAAC,OAAO,iBAAc,MAAM,EAAE,SAAS,EAAC,2DAA2D,EAAA,QAAA,EAAA,CAC1GC,cAAA,CAAA,IAAA,EAAA,EAAI,SAAS,EAAC,oCAAoC,EAAA,QAAA,EAAA,sBAAA,EAAA,CAA0B,EAC5EA,cAAA,CAAA,GAAA,EAAA,EAAG,SAAS,EAAC,uBAAuB,EAAA,QAAA,EAAA,8CAAA,EAAA,CAAiD,CAAA,EAAA,CACjF,CACP;AAED;;;;;;;AAOG;AACI,MAAM,YAAY,GAAG,CAAC,EAC3B,UAAU,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,SAAS,EACT,aAAa,EAAE,UAAU,GACP,KAAe;AACjC,IAAA,MAAM,kBAAkB,GAAGC,6BAAiB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;AACzH,IAAA,MAAM,gBAAgB,GAAG,eAAe,GAAG,aAAa,CAAC,IAAIC,sBAAU,CAAC,kBAAkB,EAAE,aAAa,CAAC;;;IAG1G,MAAM,IAAI,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK;IAEtC;;;AAGE,IAAAH,eAAA,CAAC,IAAI,EAAA,EAAA,YAAA,EACS,KAAK,EAAA,aAAA,EACJ,kBAAkB,EAC/B,SAAS,EAAEI,KAAE,CAAC,yGAAyG,EAAE,SAAS,CAAC,EAAA,QAAA,EAAA,CAElI,MAAM,EAEPH,cAAA,CAAA,KAAA,EAAA,EACE,EAAE,EAAE,eAAe,EAAE,EAAE,EACvB,GAAG,EAAE,eAAe,EAAE,GAAG,iBACZ,gBAAgB;;;AAG7B,gBAAA,QAAQ,EAAE,CAAC;;gBAEX,SAAS,EAAEG,KAAE,CACX,8HAA8H,EAC9H,eAAe,EAAE,SAAS,CAC3B,EAAA,QAAA,EAEDH,cAAA,CAACI,2BAAa,EAAA,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,CAAC,KAAK,KAAK,WAAW,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,IAAIJ,cAAA,CAAC,kBAAkB,EAAA,EAAC,MAAM,EAAEE,sBAAU,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAA,CAAI,EAAA,QAAA,EAEzI,QAAQ,EAAA,CACK,EAAA,CACZ,EAEL,MAAM,CAAA,EAAA,CACF;AAEX;;"}
@@ -0,0 +1,54 @@
1
+ import type { ErrorInfo, ReactNode, Ref } from 'react';
2
+ export interface WidgetLayoutScrollAreaProps {
3
+ /** `id` for the scrolling element, e.g. for a scroll-to-bottom helper that looks it up. */
4
+ id?: string;
5
+ /** Ref to the scrolling element, e.g. to keep a chat list pinned to its newest message. */
6
+ ref?: Ref<HTMLDivElement>;
7
+ /** Extra classes merged onto the scrolling element, e.g. a gap between messages. */
8
+ className?: string;
9
+ /** Overrides the scrolling element's `data-testid` (defaults to `<root>--scroll-area`). */
10
+ 'data-testid'?: string;
11
+ }
12
+ export interface WidgetLayoutProps {
13
+ /**
14
+ * Stable, human-readable name of the widget (e.g. "ChatWidget"). Tags the widget-scoped error
15
+ * boundary so a thrown error can be attributed in logs/RUM, and is forwarded to `renderError`
16
+ * and `onError`.
17
+ */
18
+ widgetName: string;
19
+ /**
20
+ * Accessible name that makes the root a `region` landmark. Set it when the widget is the landmark,
21
+ * since the host page already owns `<main>`; omit it when the host already wraps the widget in a named
22
+ * landmark such as `SidePanel`, so the root is a plain `div` and no nested region repeats the name.
23
+ */
24
+ label?: string;
25
+ /** Pinned above the scroll region and rendered OUTSIDE the error boundary, so a title or close control stays usable when content throws. */
26
+ header?: ReactNode;
27
+ /** Pinned below the scroll region and rendered OUTSIDE the error boundary; the composer slot. Keeps clear of the device's bottom safe area. */
28
+ footer?: ReactNode;
29
+ /** Widget content, rendered INSIDE the error boundary and the scroll region. */
30
+ children: ReactNode;
31
+ /** Replaces the default fallback shown when a child throws. Receives the thrown error and `widgetName` so consumers can render localised copy. */
32
+ renderError?: (props: {
33
+ error: Error;
34
+ widgetName: string;
35
+ }) => ReactNode;
36
+ /** Called when a child throws — wire app-level reporting (e.g. Datadog RUM) here. */
37
+ onError?: (error: Error, info: ErrorInfo) => void;
38
+ /** Hooks for the element that scrolls the content, since the host's bounded height means the widget never scrolls the document. */
39
+ scrollAreaProps?: WidgetLayoutScrollAreaProps;
40
+ /** Extra classes merged onto the root `<section>`. */
41
+ className?: string;
42
+ /** Overrides the `data-testid` (defaults to `fd-widget-layout`). */
43
+ 'data-testid'?: string;
44
+ }
45
+ /**
46
+ * Widget-shaped template for a micro-frontend mounted beside a page: a named `region` that fills
47
+ * the height its host gives it, with pinned `header` and `footer` slots around a content area
48
+ * that scrolls internally, and an error boundary keyed by `widgetName`. Reach for it instead of
49
+ * `PageLayout` whenever the host page already owns `<main>` and the document must not scroll.
50
+ *
51
+ * @summary bounded-height widget template with pinned header and footer
52
+ */
53
+ export declare const WidgetLayout: ({ widgetName, label, header, footer, children, renderError, onError, scrollAreaProps, className, "data-testid": dataTestId, }: WidgetLayoutProps) => ReactNode;
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/templates/WidgetLayout/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C,2FAA2F;IAC3F,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2FAA2F;IAC3F,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1B,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4IAA4I;IAC5I,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,+IAA+I;IAC/I,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gFAAgF;IAChF,QAAQ,EAAE,SAAS,CAAC;IACpB,kJAAkJ;IAClJ,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC;IACzE,qFAAqF;IACrF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAClD,mIAAmI;IACnI,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAUD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,+HAW1B,iBAAiB,KAAG,SA0CtB,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { ErrorBoundary } from '@flipdish/ui-library/providers/ErrorBoundary';
3
+ import { cx } from '@flipdish/ui-library/utilities/cx';
4
+ import { useResolvedTestId, joinTestId } from '@flipdish/ui-library/utilities/testIdUtils';
5
+
6
+ /** English fallback copy — consumers localise via `renderError`. TODO(i18n): source from ui-library's translation layer once one exists. */
7
+ const DefaultWidgetError = ({ testId }) => (jsxs("div", { role: "alert", "data-testid": testId, className: "flex w-full flex-col items-center gap-2 py-xl text-center", children: [jsx("h2", { className: "font-semibold text-md text-primary", children: "Something went wrong" }), jsx("p", { className: "text-sm text-tertiary", children: "An unexpected error occurred in this widget." })] }));
8
+ /**
9
+ * Widget-shaped template for a micro-frontend mounted beside a page: a named `region` that fills
10
+ * the height its host gives it, with pinned `header` and `footer` slots around a content area
11
+ * that scrolls internally, and an error boundary keyed by `widgetName`. Reach for it instead of
12
+ * `PageLayout` whenever the host page already owns `<main>` and the document must not scroll.
13
+ *
14
+ * @summary bounded-height widget template with pinned header and footer
15
+ */
16
+ const WidgetLayout = ({ widgetName, label, header, footer, children, renderError, onError, scrollAreaProps, className, 'data-testid': dataTestId, }) => {
17
+ const widgetLayoutTestId = useResolvedTestId({ testId: dataTestId, segment: 'widget-layout', rootName: 'widget-layout' });
18
+ const scrollAreaTestId = scrollAreaProps?.['data-testid'] ?? joinTestId(widgetLayoutTestId, 'scroll-area');
19
+ // A `<section>` with an accessible name is a `region` landmark; without a name it is a plain `div`, so a
20
+ // host that already provides the landmark does not get a nested, same-named one.
21
+ const Root = label ? 'section' : 'div';
22
+ return (
23
+ // `h-full`, never a viewport unit: the host supplies the bounded height, and WebView viewport units can
24
+ // disagree with the visible area. `min-h-0` lets the scroll region shrink below its content's height.
25
+ jsxs(Root, { "aria-label": label, "data-testid": widgetLayoutTestId, className: cx('flex h-full min-h-0 w-full flex-col gap-md p-md pb-[max(var(--spacing-md),env(safe-area-inset-bottom))]', className), children: [header, jsx("div", { id: scrollAreaProps?.id, ref: scrollAreaProps?.ref, "data-testid": scrollAreaTestId,
26
+ // Focusable so keyboard users can scroll content that has no focusable element of its own (axe: scrollable-region-focusable).
27
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: a scrollable region must be reachable by keyboard
28
+ tabIndex: 0,
29
+ // `overscroll-contain` keeps a scroll that reaches the end from chaining to the host page.
30
+ className: cx('min-h-0 flex-1 overflow-y-auto overscroll-contain outline-focus-ring focus-visible:outline-2 focus-visible:-outline-offset-2', scrollAreaProps?.className), children: jsx(ErrorBoundary, { identifier: widgetName, onError: onError, renderFallback: (error) => renderError?.({ error, widgetName }) ?? jsx(DefaultWidgetError, { testId: joinTestId(widgetLayoutTestId, 'error') }), children: children }) }), footer] }));
31
+ };
32
+
33
+ export { WidgetLayout };
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/templates/WidgetLayout/index.tsx"],"sourcesContent":[null],"names":["_jsxs","_jsx"],"mappings":";;;;;AAiDA;AACA,MAAM,kBAAkB,GAAG,CAAC,EAAE,MAAM,EAAuB,MACzDA,IAAA,CAAA,KAAA,EAAA,EAAK,IAAI,EAAC,OAAO,iBAAc,MAAM,EAAE,SAAS,EAAC,2DAA2D,EAAA,QAAA,EAAA,CAC1GC,GAAA,CAAA,IAAA,EAAA,EAAI,SAAS,EAAC,oCAAoC,EAAA,QAAA,EAAA,sBAAA,EAAA,CAA0B,EAC5EA,GAAA,CAAA,GAAA,EAAA,EAAG,SAAS,EAAC,uBAAuB,EAAA,QAAA,EAAA,8CAAA,EAAA,CAAiD,CAAA,EAAA,CACjF,CACP;AAED;;;;;;;AAOG;AACI,MAAM,YAAY,GAAG,CAAC,EAC3B,UAAU,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,EACR,WAAW,EACX,OAAO,EACP,eAAe,EACf,SAAS,EACT,aAAa,EAAE,UAAU,GACP,KAAe;AACjC,IAAA,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;AACzH,IAAA,MAAM,gBAAgB,GAAG,eAAe,GAAG,aAAa,CAAC,IAAI,UAAU,CAAC,kBAAkB,EAAE,aAAa,CAAC;;;IAG1G,MAAM,IAAI,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK;IAEtC;;;AAGE,IAAAD,IAAA,CAAC,IAAI,EAAA,EAAA,YAAA,EACS,KAAK,EAAA,aAAA,EACJ,kBAAkB,EAC/B,SAAS,EAAE,EAAE,CAAC,yGAAyG,EAAE,SAAS,CAAC,EAAA,QAAA,EAAA,CAElI,MAAM,EAEPC,GAAA,CAAA,KAAA,EAAA,EACE,EAAE,EAAE,eAAe,EAAE,EAAE,EACvB,GAAG,EAAE,eAAe,EAAE,GAAG,iBACZ,gBAAgB;;;AAG7B,gBAAA,QAAQ,EAAE,CAAC;;gBAEX,SAAS,EAAE,EAAE,CACX,8HAA8H,EAC9H,eAAe,EAAE,SAAS,CAC3B,EAAA,QAAA,EAEDA,GAAA,CAAC,aAAa,EAAA,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,CAAC,KAAK,KAAK,WAAW,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,IAAIA,GAAA,CAAC,kBAAkB,EAAA,EAAC,MAAM,EAAE,UAAU,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAA,CAAI,EAAA,QAAA,EAEzI,QAAQ,EAAA,CACK,EAAA,CACZ,EAEL,MAAM,CAAA,EAAA,CACF;AAEX;;"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `WidgetLayout` has no React Native implementation.
3
+ *
4
+ * This is a generated types-only stub (scripts/emit-native-stubs.mjs). It exists so
5
+ * that importing `@flipdish/ui-library/components/templates/WidgetLayout` from React Native fails at
6
+ * compile time rather than resolving to the web build's types. There is no
7
+ * matching `index.js`, so Metro cannot bundle this module either.
8
+ *
9
+ * To make `WidgetLayout` available on React Native, add an `index.native.tsx` beside
10
+ * its `index.tsx` — the build picks it up automatically and this stub disappears.
11
+ */
12
+ export declare const __NO_REACT_NATIVE_IMPLEMENTATION__: never;
@@ -19,7 +19,7 @@ export interface PortalContainerProps {
19
19
  * and is out of scope.
20
20
  *
21
21
  * @example
22
- * <PortalContainer getContainer={() => document.getElementById('flipdish-micro-frontend')}>
22
+ * <PortalContainer getContainer={() => document.getElementById('my-overlay-host')}>
23
23
  * <App />
24
24
  * </PortalContainer>
25
25
  */
@@ -17,6 +17,9 @@ const useTheme = () => {
17
17
  }
18
18
  return context;
19
19
  };
20
+ // Providers currently mirroring onto <html>, so a page with several (a Storybook docs page mounts one per
21
+ // story) only loses the attribute when the last one unmounts.
22
+ let documentThemeWriters = 0;
20
23
  const resolveSystemPreference = () => {
21
24
  if (typeof window === 'undefined')
22
25
  return 'light';
@@ -24,15 +27,20 @@ const resolveSystemPreference = () => {
24
27
  };
25
28
  /**
26
29
  * Provides light/dark/system theme context, persisted to `localStorage` and following the OS
27
- * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` so tokens
28
- * and the Tailwind `dark:` variant cascade, including inside CSS-scoped micro-frontends.
30
+ * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` (and
31
+ * `data-mfe-scope` when `scopeId` is set) so tokens and the Tailwind `dark:` variant cascade,
32
+ * including inside CSS-scoped micro-frontends.
29
33
  *
30
34
  * @summary light/dark/system theme provider carrying data-theme
31
35
  */
32
- const ThemeProvider = ({ children, defaultTheme = 'system', storageKey = 'ui-theme', themeModeOverride }) => {
36
+ const ThemeProvider = ({ children, defaultTheme = 'system', storageKey, themeModeOverride, scopeId: scopeIdProp, syncDocumentTheme: syncDocumentThemeProp, }) => {
37
+ // An empty scopeId is unset: it would stamp `data-mfe-scope=""` and switch the <html> mirror off for nothing.
38
+ const scopeId = scopeIdProp || undefined;
39
+ const syncDocumentTheme = syncDocumentThemeProp ?? (themeModeOverride === undefined && scopeId === undefined);
40
+ const resolvedStorageKey = storageKey ?? (scopeId ? `ui-theme:${scopeId}` : 'ui-theme');
33
41
  const [theme, setTheme] = React.useState(() => {
34
42
  if (typeof window !== 'undefined') {
35
- const savedTheme = localStorage.getItem(storageKey);
43
+ const savedTheme = localStorage.getItem(resolvedStorageKey);
36
44
  return savedTheme || defaultTheme;
37
45
  }
38
46
  return defaultTheme;
@@ -41,24 +49,38 @@ const ThemeProvider = ({ children, defaultTheme = 'system', storageKey = 'ui-the
41
49
  const [themeRoot, setThemeRoot] = React.useState(null);
42
50
  const activeTheme = themeModeOverride ?? theme;
43
51
  const resolvedMode = activeTheme === 'system' ? systemPreference : activeTheme;
52
+ // The cleanup removes the attribute, so a host document is left as it was found when the mirror
53
+ // turns off or the provider unmounts.
44
54
  React.useEffect(() => {
55
+ if (!syncDocumentTheme)
56
+ return;
57
+ documentThemeWriters += 1;
45
58
  document.documentElement.setAttribute('data-theme', resolvedMode);
46
- // Only persist the theme if it's not overridden
47
- if (!themeModeOverride) {
48
- if (activeTheme === 'system') {
49
- localStorage.removeItem(storageKey);
50
- }
51
- else {
52
- localStorage.setItem(storageKey, activeTheme);
53
- }
59
+ return () => {
60
+ documentThemeWriters -= 1;
61
+ if (documentThemeWriters === 0)
62
+ document.documentElement.removeAttribute('data-theme');
63
+ };
64
+ }, [syncDocumentTheme, resolvedMode]);
65
+ // Only persist the theme if it's not overridden
66
+ React.useEffect(() => {
67
+ if (themeModeOverride)
68
+ return;
69
+ if (activeTheme === 'system') {
70
+ localStorage.removeItem(resolvedStorageKey);
71
+ }
72
+ else {
73
+ localStorage.setItem(resolvedStorageKey, activeTheme);
54
74
  }
75
+ }, [activeTheme, themeModeOverride, resolvedStorageKey]);
76
+ React.useEffect(() => {
55
77
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
56
78
  const handleChange = () => setSystemPreference(mediaQuery.matches ? 'dark' : 'light');
57
79
  mediaQuery.addEventListener('change', handleChange);
58
80
  return () => mediaQuery.removeEventListener('change', handleChange);
59
- }, [resolvedMode, activeTheme, themeModeOverride, storageKey]);
81
+ }, []);
60
82
  const contextValue = React.useMemo(() => ({ theme: activeTheme, setTheme, themeRoot, resolvedMode }), [activeTheme, themeRoot, resolvedMode]);
61
- return (jsxRuntime.jsx(ThemeContext.Provider, { value: contextValue, children: jsxRuntime.jsx("div", { ref: setThemeRoot, "data-theme": resolvedMode, style: { display: 'contents' }, children: children }) }));
83
+ return (jsxRuntime.jsx(ThemeContext.Provider, { value: contextValue, children: jsxRuntime.jsx("div", { ref: setThemeRoot, "data-theme": resolvedMode, "data-mfe-scope": scopeId, style: { display: 'contents' }, children: children }) }));
62
84
  };
63
85
 
64
86
  exports.ThemeProvider = ThemeProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../../../src/providers/ThemeProvider/index.tsx"],"sourcesContent":[null],"names":["createContext","useContext","useState","useEffect","useMemo","_jsx"],"mappings":";;;;;AAcA,MAAM,YAAY,GAAGA,mBAAa,CAA+B,SAAS,CAAC;AAE3E;;;;;AAKG;AACI,MAAM,QAAQ,GAAG,MAAuB;AAC7C,IAAA,MAAM,OAAO,GAAGC,gBAAU,CAAC,YAAY,CAAC;AAExC,IAAA,IAAI,OAAO,KAAK,SAAS,EAAE;AACzB,QAAA,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC;IACjE;AAEA,IAAA,OAAO,OAAO;AAChB;AAEA,MAAM,uBAAuB,GAAG,MAAuB;IACrD,IAAI,OAAO,MAAM,KAAK,WAAW;AAAE,QAAA,OAAO,OAAO;AACjD,IAAA,OAAO,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO;AACrF,CAAC;AAqBD;;;;;;AAMG;AACI,MAAM,aAAa,GAAG,CAAC,EAAE,QAAQ,EAAE,YAAY,GAAG,QAAQ,EAAE,UAAU,GAAG,UAAU,EAAE,iBAAiB,EAAsB,KAAI;IACrI,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAGC,cAAQ,CAAQ,MAAK;AAC7C,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;YACjC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,CAAiB;YACnE,OAAO,UAAU,IAAI,YAAY;QACnC;AACA,QAAA,OAAO,YAAY;AACrB,IAAA,CAAC,CAAC;IAEF,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAGA,cAAQ,CAAmB,uBAAuB,CAAC;IACnG,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAGA,cAAQ,CAAqB,IAAI,CAAC;AAEpE,IAAA,MAAM,WAAW,GAAU,iBAAiB,IAAI,KAAK;AACrD,IAAA,MAAM,YAAY,GAAqB,WAAW,KAAK,QAAQ,GAAG,gBAAgB,GAAG,WAAW;IAEhGC,eAAS,CAAC,MAAK;QACb,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,YAAY,EAAE,YAAY,CAAC;;QAGjE,IAAI,CAAC,iBAAiB,EAAE;AACtB,YAAA,IAAI,WAAW,KAAK,QAAQ,EAAE;AAC5B,gBAAA,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC;YACrC;iBAAO;AACL,gBAAA,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC;YAC/C;QACF;QAEA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC;AACpE,QAAA,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AACrF,QAAA,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC;QACnD,OAAO,MAAM,UAAU,CAAC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC;IACrE,CAAC,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,CAAC,CAAC;AAE9D,IAAA,MAAM,YAAY,GAAGC,aAAO,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAEvI,IAAA,QACEC,cAAA,CAAC,YAAY,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,QAAA,EACxCA,cAAA,CAAA,KAAA,EAAA,EAAK,GAAG,EAAE,YAAY,EAAA,YAAA,EAAc,YAAY,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAA,QAAA,EAC7E,QAAQ,EAAA,CACL,EAAA,CACgB;AAE5B;;;"}
1
+ {"version":3,"file":"index.cjs","sources":["../../../src/providers/ThemeProvider/index.tsx"],"sourcesContent":[null],"names":["createContext","useContext","useState","useEffect","useMemo","_jsx"],"mappings":"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"}
@@ -21,8 +21,9 @@ interface ThemeProviderProps {
21
21
  */
22
22
  defaultTheme?: Theme;
23
23
  /**
24
- * The key to use to store the theme in localStorage
25
- * @default "ui-theme"
24
+ * The key to use to store the theme in localStorage. With `scopeId` the default is namespaced so
25
+ * sibling MFEs on one host do not share a key; an explicit key is used as given.
26
+ * @default "ui-theme", or "ui-theme:<scopeId>" when `scopeId` is set
26
27
  */
27
28
  storageKey?: string;
28
29
  /**
@@ -30,14 +31,27 @@ interface ThemeProviderProps {
30
31
  * internal state and skips localStorage persistence — useful for controlled MFE's or tests.
31
32
  */
32
33
  themeModeOverride?: 'light' | 'dark';
34
+ /**
35
+ * Stamps `data-mfe-scope={scopeId}` on the themed root so a scoped CSS build
36
+ * (`[data-mfe-scope="sale-service"]`) matches this tree and nothing else on the page.
37
+ */
38
+ scopeId?: string;
39
+ /**
40
+ * Mirrors `data-theme` onto `<html>` as well as the themed root, and removes it again on unmount or
41
+ * when turned off. An embedded app must not mutate the host document, so this is off whenever
42
+ * `themeModeOverride` or `scopeId` is set.
43
+ * @default true when neither `themeModeOverride` nor `scopeId` is set
44
+ */
45
+ syncDocumentTheme?: boolean;
33
46
  }
34
47
  /**
35
48
  * Provides light/dark/system theme context, persisted to `localStorage` and following the OS
36
- * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` so tokens
37
- * and the Tailwind `dark:` variant cascade, including inside CSS-scoped micro-frontends.
49
+ * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` (and
50
+ * `data-mfe-scope` when `scopeId` is set) so tokens and the Tailwind `dark:` variant cascade,
51
+ * including inside CSS-scoped micro-frontends.
38
52
  *
39
53
  * @summary light/dark/system theme provider carrying data-theme
40
54
  */
41
- export declare const ThemeProvider: ({ children, defaultTheme, storageKey, themeModeOverride }: ThemeProviderProps) => import("react").JSX.Element;
55
+ export declare const ThemeProvider: ({ children, defaultTheme, storageKey, themeModeOverride, scopeId: scopeIdProp, syncDocumentTheme: syncDocumentThemeProp, }: ThemeProviderProps) => import("react").JSX.Element;
42
56
  export {};
43
57
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/ThemeProvider/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,KAAK,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEzC,UAAU,gBAAgB;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC;AAID;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QAAO,gBAQ3B,CAAC;AAOF,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACtC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GAAI,2DAAmF,kBAAkB,gCA0ClI,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/ThemeProvider/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,KAAK,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEzC,UAAU,gBAAgB;IACxB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,OAAO,GAAG,MAAM,CAAC;CAChC;AAID;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QAAO,gBAQ3B,CAAC;AAWF,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GAAI,4HAO3B,kBAAkB,gCA0DpB,CAAC"}
@@ -15,6 +15,9 @@ const useTheme = () => {
15
15
  }
16
16
  return context;
17
17
  };
18
+ // Providers currently mirroring onto <html>, so a page with several (a Storybook docs page mounts one per
19
+ // story) only loses the attribute when the last one unmounts.
20
+ let documentThemeWriters = 0;
18
21
  const resolveSystemPreference = () => {
19
22
  if (typeof window === 'undefined')
20
23
  return 'light';
@@ -22,15 +25,20 @@ const resolveSystemPreference = () => {
22
25
  };
23
26
  /**
24
27
  * Provides light/dark/system theme context, persisted to `localStorage` and following the OS
25
- * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` so tokens
26
- * and the Tailwind `dark:` variant cascade, including inside CSS-scoped micro-frontends.
28
+ * when set to `system`. Renders a `display: contents` wrapper carrying `data-theme` (and
29
+ * `data-mfe-scope` when `scopeId` is set) so tokens and the Tailwind `dark:` variant cascade,
30
+ * including inside CSS-scoped micro-frontends.
27
31
  *
28
32
  * @summary light/dark/system theme provider carrying data-theme
29
33
  */
30
- const ThemeProvider = ({ children, defaultTheme = 'system', storageKey = 'ui-theme', themeModeOverride }) => {
34
+ const ThemeProvider = ({ children, defaultTheme = 'system', storageKey, themeModeOverride, scopeId: scopeIdProp, syncDocumentTheme: syncDocumentThemeProp, }) => {
35
+ // An empty scopeId is unset: it would stamp `data-mfe-scope=""` and switch the <html> mirror off for nothing.
36
+ const scopeId = scopeIdProp || undefined;
37
+ const syncDocumentTheme = syncDocumentThemeProp ?? (themeModeOverride === undefined && scopeId === undefined);
38
+ const resolvedStorageKey = storageKey ?? (scopeId ? `ui-theme:${scopeId}` : 'ui-theme');
31
39
  const [theme, setTheme] = useState(() => {
32
40
  if (typeof window !== 'undefined') {
33
- const savedTheme = localStorage.getItem(storageKey);
41
+ const savedTheme = localStorage.getItem(resolvedStorageKey);
34
42
  return savedTheme || defaultTheme;
35
43
  }
36
44
  return defaultTheme;
@@ -39,24 +47,38 @@ const ThemeProvider = ({ children, defaultTheme = 'system', storageKey = 'ui-the
39
47
  const [themeRoot, setThemeRoot] = useState(null);
40
48
  const activeTheme = themeModeOverride ?? theme;
41
49
  const resolvedMode = activeTheme === 'system' ? systemPreference : activeTheme;
50
+ // The cleanup removes the attribute, so a host document is left as it was found when the mirror
51
+ // turns off or the provider unmounts.
42
52
  useEffect(() => {
53
+ if (!syncDocumentTheme)
54
+ return;
55
+ documentThemeWriters += 1;
43
56
  document.documentElement.setAttribute('data-theme', resolvedMode);
44
- // Only persist the theme if it's not overridden
45
- if (!themeModeOverride) {
46
- if (activeTheme === 'system') {
47
- localStorage.removeItem(storageKey);
48
- }
49
- else {
50
- localStorage.setItem(storageKey, activeTheme);
51
- }
57
+ return () => {
58
+ documentThemeWriters -= 1;
59
+ if (documentThemeWriters === 0)
60
+ document.documentElement.removeAttribute('data-theme');
61
+ };
62
+ }, [syncDocumentTheme, resolvedMode]);
63
+ // Only persist the theme if it's not overridden
64
+ useEffect(() => {
65
+ if (themeModeOverride)
66
+ return;
67
+ if (activeTheme === 'system') {
68
+ localStorage.removeItem(resolvedStorageKey);
69
+ }
70
+ else {
71
+ localStorage.setItem(resolvedStorageKey, activeTheme);
52
72
  }
73
+ }, [activeTheme, themeModeOverride, resolvedStorageKey]);
74
+ useEffect(() => {
53
75
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
54
76
  const handleChange = () => setSystemPreference(mediaQuery.matches ? 'dark' : 'light');
55
77
  mediaQuery.addEventListener('change', handleChange);
56
78
  return () => mediaQuery.removeEventListener('change', handleChange);
57
- }, [resolvedMode, activeTheme, themeModeOverride, storageKey]);
79
+ }, []);
58
80
  const contextValue = useMemo(() => ({ theme: activeTheme, setTheme, themeRoot, resolvedMode }), [activeTheme, themeRoot, resolvedMode]);
59
- return (jsx(ThemeContext.Provider, { value: contextValue, children: jsx("div", { ref: setThemeRoot, "data-theme": resolvedMode, style: { display: 'contents' }, children: children }) }));
81
+ return (jsx(ThemeContext.Provider, { value: contextValue, children: jsx("div", { ref: setThemeRoot, "data-theme": resolvedMode, "data-mfe-scope": scopeId, style: { display: 'contents' }, children: children }) }));
60
82
  };
61
83
 
62
84
  export { ThemeProvider, useTheme };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../src/providers/ThemeProvider/index.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":";;;AAcA,MAAM,YAAY,GAAG,aAAa,CAA+B,SAAS,CAAC;AAE3E;;;;;AAKG;AACI,MAAM,QAAQ,GAAG,MAAuB;AAC7C,IAAA,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC;AAExC,IAAA,IAAI,OAAO,KAAK,SAAS,EAAE;AACzB,QAAA,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC;IACjE;AAEA,IAAA,OAAO,OAAO;AAChB;AAEA,MAAM,uBAAuB,GAAG,MAAuB;IACrD,IAAI,OAAO,MAAM,KAAK,WAAW;AAAE,QAAA,OAAO,OAAO;AACjD,IAAA,OAAO,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO;AACrF,CAAC;AAqBD;;;;;;AAMG;AACI,MAAM,aAAa,GAAG,CAAC,EAAE,QAAQ,EAAE,YAAY,GAAG,QAAQ,EAAE,UAAU,GAAG,UAAU,EAAE,iBAAiB,EAAsB,KAAI;IACrI,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAQ,MAAK;AAC7C,QAAA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;YACjC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,UAAU,CAAiB;YACnE,OAAO,UAAU,IAAI,YAAY;QACnC;AACA,QAAA,OAAO,YAAY;AACrB,IAAA,CAAC,CAAC;IAEF,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAmB,uBAAuB,CAAC;IACnG,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC;AAEpE,IAAA,MAAM,WAAW,GAAU,iBAAiB,IAAI,KAAK;AACrD,IAAA,MAAM,YAAY,GAAqB,WAAW,KAAK,QAAQ,GAAG,gBAAgB,GAAG,WAAW;IAEhG,SAAS,CAAC,MAAK;QACb,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,YAAY,EAAE,YAAY,CAAC;;QAGjE,IAAI,CAAC,iBAAiB,EAAE;AACtB,YAAA,IAAI,WAAW,KAAK,QAAQ,EAAE;AAC5B,gBAAA,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC;YACrC;iBAAO;AACL,gBAAA,YAAY,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC;YAC/C;QACF;QAEA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,8BAA8B,CAAC;AACpE,QAAA,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AACrF,QAAA,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC;QACnD,OAAO,MAAM,UAAU,CAAC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC;IACrE,CAAC,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,CAAC,CAAC;AAE9D,IAAA,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAEvI,IAAA,QACEA,GAAA,CAAC,YAAY,CAAC,QAAQ,EAAA,EAAC,KAAK,EAAE,YAAY,EAAA,QAAA,EACxCA,GAAA,CAAA,KAAA,EAAA,EAAK,GAAG,EAAE,YAAY,EAAA,YAAA,EAAc,YAAY,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAA,QAAA,EAC7E,QAAQ,EAAA,CACL,EAAA,CACgB;AAE5B;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../src/providers/ThemeProvider/index.tsx"],"sourcesContent":[null],"names":["_jsx"],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+
5
+ /** Fallback for the `body` anchor's stacking level when the `--z-index-overlay-root` token is not in scope. */
6
+ const OVERLAY_ROOT_Z_INDEX_FALLBACK = 1300;
7
+ /**
8
+ * Reads the design system's overlay stacking tier. Library overlays are `z-50`, which loses to host chrome
9
+ * such as Portal's sidebar at 200, so the `body` anchor lifts them as a whole to this tier. The token lives
10
+ * on the themed root because a scoped CSS build rewrites `:root` to the scope, so `body` never carries it.
11
+ *
12
+ * @param themeRoot The element the app's tokens cascade from.
13
+ * @returns The `--z-index-overlay-root` token as a number, or the 1300 fallback.
14
+ */
15
+ const readOverlayZIndex = (themeRoot) => {
16
+ const value = themeRoot ? Number.parseInt(getComputedStyle(themeRoot).getPropertyValue('--z-index-overlay-root'), 10) : Number.NaN;
17
+ return Number.isFinite(value) ? value : OVERLAY_ROOT_Z_INDEX_FALLBACK;
18
+ };
19
+ const portalToBody = () => document.body;
20
+ /**
21
+ * Decides where overlays portal. An embedded app (one with a `scopeId`) defaults to `document.body` so
22
+ * its overlays escape the host's `isolation` and `z-index` layers; `null` opts back into the themed root.
23
+ *
24
+ * @param portalContainer The consumer's `portalContainer` prop, as passed.
25
+ * @param scopeId The consumer's `scopeId` prop, as passed.
26
+ * @returns A resolver for the container, or `null` for the themed root.
27
+ */
28
+ const resolvePortalContainer = (portalContainer, scopeId) => portalContainer === undefined && scopeId ? portalToBody : (portalContainer ?? null);
29
+ /**
30
+ * Builds the anchor overlays portal into. On `body` it is a zero-height positioned block: no layout
31
+ * 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
32
+ * `display: contents`, so it cannot disturb a flex or grid host. It carries `data-theme` and, with a
33
+ * scope, `data-mfe-scope`, which is what lets the app's scoped CSS reach an overlay rendered outside its tree.
34
+ *
35
+ * @param container The element the anchor is appended to.
36
+ * @param options The scope to stamp, if any, the resolved theme mode, and the stacking tier for a `body` anchor.
37
+ * @returns The appended anchor element.
38
+ */
39
+ const createPortalAnchor = (container, { scopeId, resolvedMode, zIndex = OVERLAY_ROOT_Z_INDEX_FALLBACK }) => {
40
+ const anchor = document.createElement('div');
41
+ if (container === document.body) {
42
+ anchor.style.position = 'relative';
43
+ anchor.style.height = '0';
44
+ anchor.style.zIndex = String(zIndex);
45
+ // A flex or grid body must not stretch the block or give it a share of the space; a `gap` still applies.
46
+ anchor.style.flex = '0 0 auto';
47
+ anchor.style.alignSelf = 'flex-start';
48
+ anchor.style.margin = '0';
49
+ }
50
+ else {
51
+ anchor.style.display = 'contents';
52
+ }
53
+ anchor.setAttribute('data-theme', resolvedMode);
54
+ if (scopeId)
55
+ anchor.setAttribute('data-mfe-scope', scopeId);
56
+ container.appendChild(anchor);
57
+ return anchor;
58
+ };
59
+ /**
60
+ * Keeps one themed, optionally scoped anchor inside the resolved portal container and hands React Aria
61
+ * a `getContainer` that only ever returns an attached anchor or the themed root. Compares resolved DOM
62
+ * nodes rather than callback identity, so an inline `() => getElementById(...)` does not rebuild the
63
+ * anchor on every render, and returns `null` for the commit in which the container is still changing so
64
+ * no overlay lands on `document.body` unstyled and then relocates.
65
+ *
66
+ * @param options The container choice, scope, themed root and theme mode.
67
+ * @returns The `getContainer` callback for `PortalContainer`.
68
+ */
69
+ const usePortalAnchor = ({ portalContainer, scopeId, themeRoot, resolvedMode }) => {
70
+ const resolveContainer = React.useMemo(() => resolvePortalContainer(portalContainer, scopeId), [portalContainer, scopeId]);
71
+ const [portalAnchor, setPortalAnchor] = React.useState(null);
72
+ const portalAnchorNode = React.useRef(null);
73
+ const lastExplicitContainer = React.useRef(null);
74
+ // useLayoutEffect + useState so the anchor exists before paint and its creation re-renders
75
+ // PortalContainer with a real container; a ref-only write left the first commit portalling to body.
76
+ React.useLayoutEffect(() => {
77
+ const explicit = resolveContainer?.() ?? null;
78
+ if (explicit === lastExplicitContainer.current) {
79
+ return;
80
+ }
81
+ lastExplicitContainer.current = explicit;
82
+ portalAnchorNode.current?.remove();
83
+ portalAnchorNode.current = null;
84
+ if (!explicit) {
85
+ setPortalAnchor(null);
86
+ return;
87
+ }
88
+ const anchor = createPortalAnchor(explicit, { scopeId, resolvedMode, zIndex: readOverlayZIndex(themeRoot) });
89
+ portalAnchorNode.current = anchor;
90
+ setPortalAnchor(anchor);
91
+ });
92
+ React.useLayoutEffect(() => () => {
93
+ portalAnchorNode.current?.remove();
94
+ portalAnchorNode.current = null;
95
+ // Reset alongside the node so StrictMode's dev-only mount → cleanup → remount does not see a stale
96
+ // "unchanged" match, skip recreating the anchor, and keep state pointing at the removed node.
97
+ lastExplicitContainer.current = null;
98
+ }, []);
99
+ React.useLayoutEffect(() => {
100
+ portalAnchorNode.current?.setAttribute('data-theme', resolvedMode);
101
+ }, [resolvedMode]);
102
+ React.useLayoutEffect(() => {
103
+ const anchor = portalAnchorNode.current;
104
+ if (!anchor)
105
+ return;
106
+ if (scopeId)
107
+ anchor.setAttribute('data-mfe-scope', scopeId);
108
+ else
109
+ anchor.removeAttribute('data-mfe-scope');
110
+ }, [scopeId]);
111
+ // `themeRoot` is state filled by a ref callback after the first commit, so the anchor is created before the
112
+ // token can be read; re-read it once the root exists (and whenever it changes) so a scoped override applies.
113
+ React.useLayoutEffect(() => {
114
+ if (!portalAnchor || portalAnchor.parentElement !== document.body)
115
+ return;
116
+ portalAnchor.style.zIndex = String(readOverlayZIndex(themeRoot));
117
+ }, [themeRoot, portalAnchor]);
118
+ // Re-resolving here (rather than trusting state alone) covers a host swap: this render's anchor still
119
+ // belongs to the old host until the layout effect rebuilds it, so it must not be handed out.
120
+ return React.useCallback(() => {
121
+ const explicit = resolveContainer?.() ?? null;
122
+ if (explicit) {
123
+ return explicit === lastExplicitContainer.current ? portalAnchor : null;
124
+ }
125
+ if (resolveContainer) {
126
+ return null;
127
+ }
128
+ return themeRoot;
129
+ }, [portalAnchor, resolveContainer, themeRoot]);
130
+ };
131
+
132
+ exports.OVERLAY_ROOT_Z_INDEX_FALLBACK = OVERLAY_ROOT_Z_INDEX_FALLBACK;
133
+ exports.createPortalAnchor = createPortalAnchor;
134
+ exports.readOverlayZIndex = readOverlayZIndex;
135
+ exports.resolvePortalContainer = resolvePortalContainer;
136
+ exports.usePortalAnchor = usePortalAnchor;
137
+ //# sourceMappingURL=usePortalAnchor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePortalAnchor.cjs","sources":["../../../../src/providers/UIProvider/hooks/usePortalAnchor.ts"],"sourcesContent":[null],"names":["useMemo","useState","useRef","useLayoutEffect","useCallback"],"mappings":"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"}