@mittwald/flow-react-components 1.4.21 → 1.4.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/component-index.json +1 -1
- package/dist/assets/doc-properties.json +1 -1
- package/dist/js/packages/components/src/components/Overlay/components/OverlayContent.mjs +6 -1
- package/dist/js/packages/components/src/components/Overlay/components/OverlayContent.mjs.map +1 -1
- package/package.json +9 -9
|
@@ -17,9 +17,14 @@ var overlayContainerAttribute = "data-flow-overlays";
|
|
|
17
17
|
* blurs a focused field inside it (#3268). The same heuristic also spares
|
|
18
18
|
* custom elements and shadow hosts the page itself appends, which then render
|
|
19
19
|
* above open overlays.
|
|
20
|
+
*
|
|
21
|
+
* Hidden nodes don't count either: react-aria appends a `display: none`
|
|
22
|
+
* description node whenever a DatePicker's value changes, and moving the
|
|
23
|
+
* container for it on every keystroke resets the segment being typed into.
|
|
20
24
|
*/
|
|
25
|
+
var isHiddenNode = (node) => node instanceof HTMLElement && (node.hidden === true || node.style.display === "none");
|
|
21
26
|
var isCoveredByPageContent = (container) => {
|
|
22
|
-
for (let node = container.nextElementSibling; node; node = node.nextElementSibling) if (!isBrowserExtensionNode(node)) return true;
|
|
27
|
+
for (let node = container.nextElementSibling; node; node = node.nextElementSibling) if (!isBrowserExtensionNode(node) && !isHiddenNode(node)) return true;
|
|
23
28
|
return false;
|
|
24
29
|
};
|
|
25
30
|
/**
|
package/dist/js/packages/components/src/components/Overlay/components/OverlayContent.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverlayContent.mjs","names":["styles"],"sources":["../../../../../../../../src/components/Overlay/components/OverlayContent.tsx"],"sourcesContent":["import * as Aria from \"react-aria-components\";\nimport { type FC, type PropsWithChildren, type Ref, Suspense } from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria\";\nimport type { PropsWithClassName } from \"@/lib/types/props\";\nimport { OverlaySuspenseFallback } from \"@/components/Overlay/components/OverlaySuspenseFallback\";\nimport styles from \"../Overlay.module.scss\";\nimport DivView from \"@/views/DivView\";\nimport {\n isBrowserExtensionNode,\n useKeepBrowserExtensionsInteractive,\n} from \"@/lib/hooks/dom/useKeepBrowserExtensionsInteractive\";\nimport { useIsActivityActive } from \"@/components/Activity/context\";\n\nconst overlayContainerAttribute = \"data-flow-overlays\";\n\n/**\n * Nodes a browser extension appends (e.g. 1Password's inline menu) don't count\n * as covering: they belong on top anyway, and moving the container for them\n * blurs a focused field inside it (#3268). The same heuristic also spares\n * custom elements and shadow hosts the page itself appends, which then render\n * above open overlays.\n */\nconst isCoveredByPageContent = (container: HTMLElement): boolean => {\n for (\n let node = container.nextElementSibling;\n node;\n node = node.nextElementSibling\n ) {\n if (!isBrowserExtensionNode(node)) {\n return true;\n }\n }\n return false;\n};\n\n/**\n * Only the container moves, never the nodes appended after it: those are\n * foreign, and moving them costs state we cannot restore (an iframe reloads).\n * The container's own state we can. Moving a node blurs the focused element\n * inside it and resets every scroll container inside to the top. `moveBefore`\n * keeps the focus (and, in Chromium, the scroll positions); the rest is\n * restored afterwards.\n */\nconst moveToEnd = (container: HTMLElement): void => {\n const body = document.body;\n const focused = document.activeElement;\n const scrolled = Array.from(container.querySelectorAll<HTMLElement>(\"*\"))\n .filter((element) => element.scrollTop !== 0 || element.scrollLeft !== 0)\n .map(\n (element) => [element, element.scrollTop, element.scrollLeft] as const,\n );\n\n if (typeof body.moveBefore === \"function\") {\n body.moveBefore(container, null);\n } else {\n body.append(container);\n }\n\n for (const [element, scrollTop, scrollLeft] of scrolled) {\n element.scrollTop = scrollTop;\n element.scrollLeft = scrollLeft;\n }\n if (\n focused instanceof HTMLElement &&\n container.contains(focused) &&\n document.activeElement !== focused\n ) {\n focused.focus({ preventScroll: true });\n }\n};\n\n/**\n * Render overlays into a dedicated container instead of `document.body`.\n *\n * React Aria `FocusScope` relies on nearby sibling nodes (sentinels + overlay).\n * Browser extensions (e.g. password managers) inject/reorder `body` children,\n * which can separate those nodes. Then focus scope detection breaks and may\n * cause recursive focus restoration.\n *\n * Keep the overlay container as the last child of body.\n *\n * React Aria normally portals overlays directly into body, so they are appended\n * last and render on top.\n *\n * If we create one persistent container only once, anything appended to body\n * later (toasts, third-party widgets, remounted app root) can render above open\n * overlays. Then overlays can appear behind page content, and the backdrop blur\n * no longer covers the page.\n */\nconst getOverlayContainer = (): HTMLElement | null => {\n if (typeof document === \"undefined\") {\n return null;\n }\n\n const existingContainer = document.querySelector<HTMLElement>(\n `body > [${overlayContainerAttribute}]`,\n );\n\n if (existingContainer) {\n if (isCoveredByPageContent(existingContainer)) {\n moveToEnd(existingContainer);\n }\n return existingContainer;\n }\n\n const container = document.createElement(\"div\");\n container.setAttribute(overlayContainerAttribute, \"\");\n document.body.append(container);\n\n return container;\n};\n\nexport interface OverlayContentProps\n extends\n PropsWithChildren,\n PropsWithClassName,\n Pick<Aria.DialogProps, \"aria-labelledby\"> {\n ref?: Ref<HTMLDivElement>;\n onOpenChange: (isOpen: boolean) => void;\n isDismissable?: boolean;\n isOpen?: boolean;\n}\n\n/** @flr-generate all */\nexport const OverlayContent: FC<OverlayContentProps> = (props) => {\n const {\n children,\n className,\n \"aria-labelledby\": ariaLabelledBy,\n ...restProps\n } = props;\n\n const isActivityActive = useIsActivityActive();\n\n useKeepBrowserExtensionsInteractive(restProps.isOpen ?? false);\n\n const Fallback = () => {\n return (\n <DivView className={styles.suspense}>\n <OverlaySuspenseFallback {...restProps} />\n </DivView>\n );\n };\n\n if (!isActivityActive) {\n return null;\n }\n\n return (\n <UNSAFE_PortalProvider getContainer={getOverlayContainer}>\n <Aria.ModalOverlay {...restProps} className={className}>\n {/* The centering viewport between the backdrop and react-aria's modal\n wrapper. Classed so Modal/OffCanvas/LightBox can position it\n without selecting a bare `div`. */}\n <DivView className={styles.viewport}>\n <Aria.Modal>\n <Suspense fallback={<Fallback />}>\n <Aria.Dialog aria-labelledby={ariaLabelledBy}>\n {children}\n </Aria.Dialog>\n </Suspense>\n </Aria.Modal>\n </DivView>\n </Aria.ModalOverlay>\n </UNSAFE_PortalProvider>\n );\n};\n\nexport default OverlayContent;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,4BAA4B
|
|
1
|
+
{"version":3,"file":"OverlayContent.mjs","names":["styles"],"sources":["../../../../../../../../src/components/Overlay/components/OverlayContent.tsx"],"sourcesContent":["import * as Aria from \"react-aria-components\";\nimport { type FC, type PropsWithChildren, type Ref, Suspense } from \"react\";\nimport { UNSAFE_PortalProvider } from \"react-aria\";\nimport type { PropsWithClassName } from \"@/lib/types/props\";\nimport { OverlaySuspenseFallback } from \"@/components/Overlay/components/OverlaySuspenseFallback\";\nimport styles from \"../Overlay.module.scss\";\nimport DivView from \"@/views/DivView\";\nimport {\n isBrowserExtensionNode,\n useKeepBrowserExtensionsInteractive,\n} from \"@/lib/hooks/dom/useKeepBrowserExtensionsInteractive\";\nimport { useIsActivityActive } from \"@/components/Activity/context\";\n\nconst overlayContainerAttribute = \"data-flow-overlays\";\n\n/**\n * Nodes a browser extension appends (e.g. 1Password's inline menu) don't count\n * as covering: they belong on top anyway, and moving the container for them\n * blurs a focused field inside it (#3268). The same heuristic also spares\n * custom elements and shadow hosts the page itself appends, which then render\n * above open overlays.\n *\n * Hidden nodes don't count either: react-aria appends a `display: none`\n * description node whenever a DatePicker's value changes, and moving the\n * container for it on every keystroke resets the segment being typed into.\n */\nconst isHiddenNode = (node: Element): boolean =>\n node instanceof HTMLElement &&\n (node.hidden === true || node.style.display === \"none\");\n\nconst isCoveredByPageContent = (container: HTMLElement): boolean => {\n for (\n let node = container.nextElementSibling;\n node;\n node = node.nextElementSibling\n ) {\n if (!isBrowserExtensionNode(node) && !isHiddenNode(node)) {\n return true;\n }\n }\n return false;\n};\n\n/**\n * Only the container moves, never the nodes appended after it: those are\n * foreign, and moving them costs state we cannot restore (an iframe reloads).\n * The container's own state we can. Moving a node blurs the focused element\n * inside it and resets every scroll container inside to the top. `moveBefore`\n * keeps the focus (and, in Chromium, the scroll positions); the rest is\n * restored afterwards.\n */\nconst moveToEnd = (container: HTMLElement): void => {\n const body = document.body;\n const focused = document.activeElement;\n const scrolled = Array.from(container.querySelectorAll<HTMLElement>(\"*\"))\n .filter((element) => element.scrollTop !== 0 || element.scrollLeft !== 0)\n .map(\n (element) => [element, element.scrollTop, element.scrollLeft] as const,\n );\n\n if (typeof body.moveBefore === \"function\") {\n body.moveBefore(container, null);\n } else {\n body.append(container);\n }\n\n for (const [element, scrollTop, scrollLeft] of scrolled) {\n element.scrollTop = scrollTop;\n element.scrollLeft = scrollLeft;\n }\n if (\n focused instanceof HTMLElement &&\n container.contains(focused) &&\n document.activeElement !== focused\n ) {\n focused.focus({ preventScroll: true });\n }\n};\n\n/**\n * Render overlays into a dedicated container instead of `document.body`.\n *\n * React Aria `FocusScope` relies on nearby sibling nodes (sentinels + overlay).\n * Browser extensions (e.g. password managers) inject/reorder `body` children,\n * which can separate those nodes. Then focus scope detection breaks and may\n * cause recursive focus restoration.\n *\n * Keep the overlay container as the last child of body.\n *\n * React Aria normally portals overlays directly into body, so they are appended\n * last and render on top.\n *\n * If we create one persistent container only once, anything appended to body\n * later (toasts, third-party widgets, remounted app root) can render above open\n * overlays. Then overlays can appear behind page content, and the backdrop blur\n * no longer covers the page.\n */\nconst getOverlayContainer = (): HTMLElement | null => {\n if (typeof document === \"undefined\") {\n return null;\n }\n\n const existingContainer = document.querySelector<HTMLElement>(\n `body > [${overlayContainerAttribute}]`,\n );\n\n if (existingContainer) {\n if (isCoveredByPageContent(existingContainer)) {\n moveToEnd(existingContainer);\n }\n return existingContainer;\n }\n\n const container = document.createElement(\"div\");\n container.setAttribute(overlayContainerAttribute, \"\");\n document.body.append(container);\n\n return container;\n};\n\nexport interface OverlayContentProps\n extends\n PropsWithChildren,\n PropsWithClassName,\n Pick<Aria.DialogProps, \"aria-labelledby\"> {\n ref?: Ref<HTMLDivElement>;\n onOpenChange: (isOpen: boolean) => void;\n isDismissable?: boolean;\n isOpen?: boolean;\n}\n\n/** @flr-generate all */\nexport const OverlayContent: FC<OverlayContentProps> = (props) => {\n const {\n children,\n className,\n \"aria-labelledby\": ariaLabelledBy,\n ...restProps\n } = props;\n\n const isActivityActive = useIsActivityActive();\n\n useKeepBrowserExtensionsInteractive(restProps.isOpen ?? false);\n\n const Fallback = () => {\n return (\n <DivView className={styles.suspense}>\n <OverlaySuspenseFallback {...restProps} />\n </DivView>\n );\n };\n\n if (!isActivityActive) {\n return null;\n }\n\n return (\n <UNSAFE_PortalProvider getContainer={getOverlayContainer}>\n <Aria.ModalOverlay {...restProps} className={className}>\n {/* The centering viewport between the backdrop and react-aria's modal\n wrapper. Classed so Modal/OffCanvas/LightBox can position it\n without selecting a bare `div`. */}\n <DivView className={styles.viewport}>\n <Aria.Modal>\n <Suspense fallback={<Fallback />}>\n <Aria.Dialog aria-labelledby={ariaLabelledBy}>\n {children}\n </Aria.Dialog>\n </Suspense>\n </Aria.Modal>\n </DivView>\n </Aria.ModalOverlay>\n </UNSAFE_PortalProvider>\n );\n};\n\nexport default OverlayContent;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,4BAA4B;;;;;;;;;;;;AAalC,IAAM,gBAAgB,SACpB,gBAAgB,gBACf,KAAK,WAAW,QAAQ,KAAK,MAAM,YAAY;AAElD,IAAM,0BAA0B,cAAoC;CAClE,KACE,IAAI,OAAO,UAAU,oBACrB,MACA,OAAO,KAAK,oBAEZ,IAAI,CAAC,uBAAuB,IAAI,KAAK,CAAC,aAAa,IAAI,GACrD,OAAO;CAGX,OAAO;AACT;;;;;;;;;AAUA,IAAM,aAAa,cAAiC;CAClD,MAAM,OAAO,SAAS;CACtB,MAAM,UAAU,SAAS;CACzB,MAAM,WAAW,MAAM,KAAK,UAAU,iBAA8B,GAAG,CAAC,CAAC,CACtE,QAAQ,YAAY,QAAQ,cAAc,KAAK,QAAQ,eAAe,CAAC,CAAC,CACxE,KACE,YAAY;EAAC;EAAS,QAAQ;EAAW,QAAQ;CAAU,CAC9D;CAEF,IAAI,OAAO,KAAK,eAAe,YAC7B,KAAK,WAAW,WAAW,IAAI;MAE/B,KAAK,OAAO,SAAS;CAGvB,KAAK,MAAM,CAAC,SAAS,WAAW,eAAe,UAAU;EACvD,QAAQ,YAAY;EACpB,QAAQ,aAAa;CACvB;CACA,IACE,mBAAmB,eACnB,UAAU,SAAS,OAAO,KAC1B,SAAS,kBAAkB,SAE3B,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;AAoBA,IAAM,4BAAgD;CACpD,IAAI,OAAO,aAAa,aACtB,OAAO;CAGT,MAAM,oBAAoB,SAAS,cACjC,WAAW,0BAA0B,EACvC;CAEA,IAAI,mBAAmB;EACrB,IAAI,uBAAuB,iBAAiB,GAC1C,UAAU,iBAAiB;EAE7B,OAAO;CACT;CAEA,MAAM,YAAY,SAAS,cAAc,KAAK;CAC9C,UAAU,aAAa,2BAA2B,EAAE;CACpD,SAAS,KAAK,OAAO,SAAS;CAE9B,OAAO;AACT;;AAcA,IAAa,kBAA2C,UAAU;CAChE,MAAM,EACJ,UACA,WACA,mBAAmB,gBACnB,GAAG,cACD;CAEJ,MAAM,mBAAmB,oBAAoB;CAE7C,oCAAoC,UAAU,UAAU,KAAK;CAE7D,MAAM,iBAAiB;EACrB,OACE,oBAAC,SAAD;GAAS,WAAWA,uBAAO;GACzB,UAAA,oBAAC,yBAAD,EAAyB,GAAI,UAAY,CAAA;EAClC,CAAA;CAEb;CAEA,IAAI,CAAC,kBACH,OAAO;CAGT,OACE,oBAAC,uBAAD;EAAuB,cAAc;EACnC,UAAA,oBAAC,KAAK,cAAN;GAAmB,GAAI;GAAsB;GAI3C,UAAA,oBAAC,SAAD;IAAS,WAAWA,uBAAO;IACzB,UAAA,oBAAC,KAAK,OAAN,EAAA,UACE,oBAAC,UAAD;KAAU,UAAU,oBAAC,UAAD,CAAW,CAAA;KAC7B,UAAA,oBAAC,KAAK,QAAN;MAAa,mBAAiB;MAC3B;KACU,CAAA;IACL,CAAA,EACA,CAAA;GACL,CAAA;EACQ,CAAA;CACE,CAAA;AAE3B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/flow-react-components",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A React implementation of Flow, mittwald’s design system",
|
|
6
6
|
"homepage": "https://flow.mittwald.de",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
"@internationalized/string-compiler": "^3.4.1",
|
|
85
85
|
"@lezer/common": "^1.5.2",
|
|
86
86
|
"@lezer/highlight": "^1.2.3",
|
|
87
|
-
"@mittwald/flow-icons": "^1.4.
|
|
87
|
+
"@mittwald/flow-icons": "^1.4.22",
|
|
88
88
|
"@mittwald/password-tools-js": "^3.0.0",
|
|
89
|
-
"@mittwald/react-tunnel": "^1.4.
|
|
89
|
+
"@mittwald/react-tunnel": "^1.4.22",
|
|
90
90
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
91
91
|
"@react-aria/form": "^3.2.1",
|
|
92
92
|
"@react-aria/i18n": "^3.13.1",
|
|
@@ -139,12 +139,12 @@
|
|
|
139
139
|
"@internationalized/date": "^3.12.3",
|
|
140
140
|
"@lezer/generator": "^1.8.0",
|
|
141
141
|
"@lezer/lr": "^1.4.10",
|
|
142
|
-
"@mittwald/flow-core": "1.4.
|
|
143
|
-
"@mittwald/flow-design-tokens": "1.4.
|
|
144
|
-
"@mittwald/flow-icons-base": "1.4.
|
|
142
|
+
"@mittwald/flow-core": "1.4.22",
|
|
143
|
+
"@mittwald/flow-design-tokens": "1.4.22",
|
|
144
|
+
"@mittwald/flow-icons-base": "1.4.22",
|
|
145
145
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
146
146
|
"@mittwald/remote-dom-react": "1.2.2-mittwald.10",
|
|
147
|
-
"@mittwald/typescript-config": "1.4.
|
|
147
|
+
"@mittwald/typescript-config": "1.4.22",
|
|
148
148
|
"@nx/storybook": "^22.7.6",
|
|
149
149
|
"@storybook/addon-a11y": "^10.5.10",
|
|
150
150
|
"@storybook/addon-actions": "^9.0.8",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
},
|
|
197
197
|
"peerDependencies": {
|
|
198
198
|
"@internationalized/date": "^3.12.2",
|
|
199
|
-
"@mittwald/flow-icons-pro": "1.4.
|
|
199
|
+
"@mittwald/flow-icons-pro": "1.4.22",
|
|
200
200
|
"@mittwald/react-use-promise": "^4.2.2",
|
|
201
201
|
"next": "^16.2.3",
|
|
202
202
|
"react": "^19.2.0",
|
|
@@ -220,5 +220,5 @@
|
|
|
220
220
|
"browserslist": [
|
|
221
221
|
"baseline widely available"
|
|
222
222
|
],
|
|
223
|
-
"gitHead": "
|
|
223
|
+
"gitHead": "35903034549bd872fa6fec6feb0c974ad813a79e"
|
|
224
224
|
}
|