@react-aria/overlays 3.23.4 → 3.25.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.
- package/dist/Overlay.main.js.map +1 -1
- package/dist/Overlay.module.js.map +1 -1
- package/dist/PortalProvider.main.js +1 -1
- package/dist/PortalProvider.main.js.map +1 -1
- package/dist/PortalProvider.mjs +1 -1
- package/dist/PortalProvider.module.js +1 -1
- package/dist/PortalProvider.module.js.map +1 -1
- package/dist/ariaHideOutside.main.js +2 -1
- package/dist/ariaHideOutside.main.js.map +1 -1
- package/dist/ariaHideOutside.mjs +2 -1
- package/dist/ariaHideOutside.module.js +2 -1
- package/dist/ariaHideOutside.module.js.map +1 -1
- package/dist/calculatePosition.main.js +32 -23
- package/dist/calculatePosition.main.js.map +1 -1
- package/dist/calculatePosition.mjs +32 -23
- package/dist/calculatePosition.module.js +32 -23
- package/dist/calculatePosition.module.js.map +1 -1
- package/dist/types.d.ts +5 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/useCloseOnScroll.main.js.map +1 -1
- package/dist/useCloseOnScroll.module.js.map +1 -1
- package/dist/useModal.main.js +1 -1
- package/dist/useModal.main.js.map +1 -1
- package/dist/useModal.mjs +1 -1
- package/dist/useModal.module.js +1 -1
- package/dist/useModal.module.js.map +1 -1
- package/dist/useModalOverlay.main.js +1 -1
- package/dist/useModalOverlay.main.js.map +1 -1
- package/dist/useModalOverlay.mjs +1 -1
- package/dist/useModalOverlay.module.js +1 -1
- package/dist/useModalOverlay.module.js.map +1 -1
- package/dist/useOverlay.main.js +2 -2
- package/dist/useOverlay.main.js.map +1 -1
- package/dist/useOverlay.mjs +2 -2
- package/dist/useOverlay.module.js +2 -2
- package/dist/useOverlay.module.js.map +1 -1
- package/dist/useOverlayPosition.main.js +18 -21
- package/dist/useOverlayPosition.main.js.map +1 -1
- package/dist/useOverlayPosition.mjs +18 -21
- package/dist/useOverlayPosition.module.js +18 -21
- package/dist/useOverlayPosition.module.js.map +1 -1
- package/dist/useOverlayTrigger.main.js +1 -1
- package/dist/useOverlayTrigger.main.js.map +1 -1
- package/dist/useOverlayTrigger.mjs +1 -1
- package/dist/useOverlayTrigger.module.js +1 -1
- package/dist/useOverlayTrigger.module.js.map +1 -1
- package/dist/usePopover.main.js +2 -1
- package/dist/usePopover.main.js.map +1 -1
- package/dist/usePopover.mjs +2 -1
- package/dist/usePopover.module.js +2 -1
- package/dist/usePopover.module.js.map +1 -1
- package/dist/usePreventScroll.main.js +13 -26
- package/dist/usePreventScroll.main.js.map +1 -1
- package/dist/usePreventScroll.mjs +13 -26
- package/dist/usePreventScroll.module.js +13 -26
- package/dist/usePreventScroll.module.js.map +1 -1
- package/package.json +14 -14
- package/src/Overlay.tsx +1 -1
- package/src/PortalProvider.tsx +1 -1
- package/src/ariaHideOutside.ts +10 -3
- package/src/calculatePosition.ts +27 -30
- package/src/useCloseOnScroll.ts +2 -2
- package/src/useModal.tsx +2 -2
- package/src/useModalOverlay.ts +1 -1
- package/src/useOverlay.ts +2 -2
- package/src/useOverlayPosition.ts +20 -23
- package/src/useOverlayTrigger.ts +2 -2
- package/src/usePopover.ts +3 -2
- package/src/usePreventScroll.ts +16 -32
|
@@ -11,7 +11,6 @@ import {useLayoutEffect as $7mMvr$useLayoutEffect, isIOS as $7mMvr$isIOS, chain
|
|
|
11
11
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
12
12
|
* governing permissions and limitations under the License.
|
|
13
13
|
*/
|
|
14
|
-
// @ts-ignore
|
|
15
14
|
const $49c51c25361d4cd2$var$visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
|
16
15
|
// HTML input types that do not cause the software keyboard to appear.
|
|
17
16
|
const $49c51c25361d4cd2$var$nonTextInputTypes = new Set([
|
|
@@ -103,31 +102,15 @@ function $49c51c25361d4cd2$var$preventScrollMobileSafari() {
|
|
|
103
102
|
// because it must be set before the touchstart event.
|
|
104
103
|
if (scrollable.scrollHeight === scrollable.clientHeight && scrollable.scrollWidth === scrollable.clientWidth) e.preventDefault();
|
|
105
104
|
};
|
|
106
|
-
let onTouchEnd = (
|
|
107
|
-
let target = e.target;
|
|
108
|
-
// Apply this change if we're not already focused on the target element
|
|
109
|
-
if ($49c51c25361d4cd2$var$willOpenKeyboard(target) && target !== document.activeElement) {
|
|
110
|
-
e.preventDefault();
|
|
111
|
-
setupStyles();
|
|
112
|
-
// Apply a transform to trick Safari into thinking the input is at the top of the page
|
|
113
|
-
// so it doesn't try to scroll it into view. When tapping on an input, this needs to
|
|
114
|
-
// be done before the "focus" event, so we have to focus the element ourselves.
|
|
115
|
-
target.style.transform = 'translateY(-2000px)';
|
|
116
|
-
target.focus();
|
|
117
|
-
requestAnimationFrame(()=>{
|
|
118
|
-
target.style.transform = '';
|
|
119
|
-
});
|
|
120
|
-
}
|
|
105
|
+
let onTouchEnd = ()=>{
|
|
121
106
|
if (restoreScrollableStyles) restoreScrollableStyles();
|
|
122
107
|
};
|
|
123
108
|
let onFocus = (e)=>{
|
|
124
109
|
let target = e.target;
|
|
125
110
|
if ($49c51c25361d4cd2$var$willOpenKeyboard(target)) {
|
|
126
111
|
setupStyles();
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// software keyboard. In these cases, it seems applying the transform in the focus event
|
|
130
|
-
// is good enough, whereas when tapping an input, it must be done before the focus event. 🤷♂️
|
|
112
|
+
// Apply a transform to trick Safari into thinking the input is at the top of the page
|
|
113
|
+
// so it doesn't try to scroll it into view.
|
|
131
114
|
target.style.transform = 'translateY(-2000px)';
|
|
132
115
|
requestAnimationFrame(()=>{
|
|
133
116
|
target.style.transform = '';
|
|
@@ -194,22 +177,26 @@ function $49c51c25361d4cd2$var$setStyle(element, style, value) {
|
|
|
194
177
|
}
|
|
195
178
|
// Adds an event listener to an element, and returns a function to remove it.
|
|
196
179
|
function $49c51c25361d4cd2$var$addEvent(target, event, handler, options) {
|
|
180
|
+
// internal function, so it's ok to ignore the difficult to fix type error
|
|
181
|
+
// @ts-ignore
|
|
197
182
|
target.addEventListener(event, handler, options);
|
|
198
183
|
return ()=>{
|
|
184
|
+
// @ts-ignore
|
|
199
185
|
target.removeEventListener(event, handler, options);
|
|
200
186
|
};
|
|
201
187
|
}
|
|
202
188
|
function $49c51c25361d4cd2$var$scrollIntoView(target) {
|
|
203
189
|
let root = document.scrollingElement || document.documentElement;
|
|
204
|
-
|
|
190
|
+
let nextTarget = target;
|
|
191
|
+
while(nextTarget && nextTarget !== root){
|
|
205
192
|
// Find the parent scrollable element and adjust the scroll position if the target is not already in view.
|
|
206
|
-
let scrollable = (0, $7mMvr$getScrollParent)(
|
|
207
|
-
if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !==
|
|
193
|
+
let scrollable = (0, $7mMvr$getScrollParent)(nextTarget);
|
|
194
|
+
if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== nextTarget) {
|
|
208
195
|
let scrollableTop = scrollable.getBoundingClientRect().top;
|
|
209
|
-
let targetTop =
|
|
210
|
-
if (targetTop > scrollableTop +
|
|
196
|
+
let targetTop = nextTarget.getBoundingClientRect().top;
|
|
197
|
+
if (targetTop > scrollableTop + nextTarget.clientHeight) scrollable.scrollTop += targetTop - scrollableTop;
|
|
211
198
|
}
|
|
212
|
-
|
|
199
|
+
nextTarget = scrollable.parentElement;
|
|
213
200
|
}
|
|
214
201
|
}
|
|
215
202
|
function $49c51c25361d4cd2$var$willOpenKeyboard(target) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;AAAA;;;;;;;;;;CAUC;AASD,aAAa;AACb,MAAM,uCAAiB,OAAO,aAAa,eAAe,OAAO,cAAc;AAE/E,sEAAsE;AACtE,MAAM,0CAAoB,IAAI,IAAI;IAChC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,mIAAmI;AACnI,IAAI,2CAAqB;AACzB,IAAI;AAOG,SAAS,0CAAiB,UAAgC,CAAC,CAAC;IACjE,IAAI,cAAC,UAAU,EAAC,GAAG;IAEnB,CAAA,GAAA,sBAAc,EAAE;QACd,IAAI,YACF;QAGF;QACA,IAAI,6CAAuB;YACzB,IAAI,CAAA,GAAA,YAAI,KACN,gCAAU;iBAEV,gCAAU;;QAId,OAAO;YACL;YACA,IAAI,6CAAuB,GACzB;QAEJ;IACF,GAAG;QAAC;KAAW;AACjB;AAEA,0FAA0F;AAC1F,mFAAmF;AACnF,SAAS;IACP,OAAO,CAAA,GAAA,YAAI,EACT,+BAAS,SAAS,eAAe,EAAE,gBAAgB,CAAC,EAAE,OAAO,UAAU,GAAG,SAAS,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,GAClH,+BAAS,SAAS,eAAe,EAAE,YAAY;AAEnD;AAEA,wEAAwE;AACxE,gDAAgD;AAChD,EAAE;AACF,8FAA8F;AAC9F,sGAAsG;AACtG,mCAAmC;AACnC,6GAA6G;AAC7G,2EAA2E;AAC3E,4GAA4G;AAC5G,sGAAsG;AACtG,EAAE;AACF,oGAAoG;AACpG,EAAE;AACF,+GAA+G;AAC/G,oBAAoB;AACpB,4GAA4G;AAC5G,+GAA+G;AAC/G,mDAAmD;AACnD,uGAAuG;AACvG,qGAAqG;AACrG,4GAA4G;AAC5G,4DAA4D;AAC5D,kHAAkH;AAClH,0GAA0G;AAC1G,oFAAoF;AACpF,gHAAgH;AAChH,oFAAoF;AACpF,SAAS;IACP,IAAI;IACJ,IAAI;IACJ,IAAI,eAAe,CAAC;QAClB,sFAAsF;QACtF,aAAa,CAAA,GAAA,sBAAc,EAAE,EAAE,MAAM,EAAa;QAClD,IAAI,eAAe,SAAS,eAAe,IAAI,eAAe,SAAS,IAAI,EACzE;QAGF,6EAA6E;QAC7E,4EAA4E;QAC5E,sBAAsB;QACtB,IAAI,sBAAsB,eAAe,OAAO,gBAAgB,CAAC,YAAY,kBAAkB,KAAK,QAClG,0BAA0B,+BAAS,YAAY,sBAAsB;IAEzE;IAEA,IAAI,cAAc,CAAC;QACjB,gCAAgC;QAChC,IAAI,CAAC,cAAc,eAAe,SAAS,eAAe,IAAI,eAAe,SAAS,IAAI,EAAE;YAC1F,EAAE,cAAc;YAChB;QACF;QAEA,6EAA6E;QAC7E,2FAA2F;QAC3F,iFAAiF;QACjF,gFAAgF;QAChF,oFAAoF;QACpF,sDAAsD;QACtD,IAAI,WAAW,YAAY,KAAK,WAAW,YAAY,IAAI,WAAW,WAAW,KAAK,WAAW,WAAW,EAC1G,EAAE,cAAc;IAEpB;IAEA,IAAI,aAAa,CAAC;QAChB,IAAI,SAAS,EAAE,MAAM;QAErB,uEAAuE;QACvE,IAAI,uCAAiB,WAAW,WAAW,SAAS,aAAa,EAAE;YACjE,EAAE,cAAc;YAChB;YAEA,sFAAsF;YACtF,oFAAoF;YACpF,+EAA+E;YAC/E,OAAO,KAAK,CAAC,SAAS,GAAG;YACzB,OAAO,KAAK;YACZ,sBAAsB;gBACpB,OAAO,KAAK,CAAC,SAAS,GAAG;YAC3B;QACF;QAEA,IAAI,yBACF;IAEJ;IAEA,IAAI,UAAU,CAAC;QACb,IAAI,SAAS,EAAE,MAAM;QACrB,IAAI,uCAAiB,SAAS;YAC5B;YAEA,mFAAmF;YACnF,iFAAiF;YACjF,wFAAwF;YACxF,+FAA+F;YAC/F,OAAO,KAAK,CAAC,SAAS,GAAG;YACzB,sBAAsB;gBACpB,OAAO,KAAK,CAAC,SAAS,GAAG;gBAEzB,qFAAqF;gBACrF,wFAAwF;gBACxF,IAAI;oBACF,IAAI,qCAAe,MAAM,GAAG,OAAO,WAAW,EAC5C,yEAAyE;oBACzE,2CAA2C;oBAC3C,sBAAsB;wBACpB,qCAAe;oBACjB;yBAEA,+EAA+E;oBAC/E,6CAA6C;oBAC7C,qCAAe,gBAAgB,CAAC,UAAU,IAAM,qCAAe,SAAS;wBAAC,MAAM;oBAAI;;YAGzF;QACF;IACF;IAEA,IAAI,gBAAgB;IACpB,IAAI,cAAc;QAChB,IAAI,eACF;QAGF,IAAI,iBAAiB;YACnB,kEAAkE;YAClE,2FAA2F;YAC3F,OAAO,QAAQ,CAAC,GAAG;QACrB;QAEA,4DAA4D;QAC5D,0FAA0F;QAC1F,6FAA6F;QAC7F,IAAI,UAAU,OAAO,WAAW;QAChC,IAAI,UAAU,OAAO,WAAW;QAEhC,gBAAgB,CAAA,GAAA,YAAI,EAClB,+BAAS,QAAQ,UAAU,iBAC3B,+BAAS,SAAS,eAAe,EAAE,gBAAgB,CAAC,EAAE,OAAO,UAAU,GAAG,SAAS,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,GAClH,+BAAS,SAAS,eAAe,EAAE,YAAY,WAC/C,+BAAS,SAAS,IAAI,EAAE,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GACpD;YACE,OAAO,QAAQ,CAAC,SAAS;QAC3B;QAGF,qFAAqF;QACrF,OAAO,QAAQ,CAAC,GAAG;IACrB;IAEA,IAAI,eAAe,CAAA,GAAA,YAAI,EACrB,+BAAS,UAAU,cAAc,cAAc;QAAC,SAAS;QAAO,SAAS;IAAI,IAC7E,+BAAS,UAAU,aAAa,aAAa;QAAC,SAAS;QAAO,SAAS;IAAI,IAC3E,+BAAS,UAAU,YAAY,YAAY;QAAC,SAAS;QAAO,SAAS;IAAI,IACzE,+BAAS,UAAU,SAAS,SAAS;IAGvC,OAAO;QACL,2DAA2D;QAC3D,oCAAA,8CAAA;QACA,0BAAA,oCAAA;QACA;IACF;AACF;AAEA,gGAAgG;AAChG,SAAS,+BAAS,OAAoB,EAAE,KAAa,EAAE,KAAa;IAClE,IAAI,MAAM,QAAQ,KAAK,CAAC,MAAM;IAC9B,QAAQ,KAAK,CAAC,MAAM,GAAG;IAEvB,OAAO;QACL,QAAQ,KAAK,CAAC,MAAM,GAAG;IACzB;AACF;AAEA,6EAA6E;AAC7E,SAAS,+BACP,MAAmB,EACnB,KAAQ,EACR,OAAoE,EACpE,OAA2C;IAE3C,OAAO,gBAAgB,CAAC,OAAO,SAAS;IACxC,OAAO;QACL,OAAO,mBAAmB,CAAC,OAAO,SAAS;IAC7C;AACF;AAEA,SAAS,qCAAe,MAAe;IACrC,IAAI,OAAO,SAAS,gBAAgB,IAAI,SAAS,eAAe;IAChE,MAAO,UAAU,WAAW,KAAM;QAChC,0GAA0G;QAC1G,IAAI,aAAa,CAAA,GAAA,sBAAc,EAAE;QACjC,IAAI,eAAe,SAAS,eAAe,IAAI,eAAe,SAAS,IAAI,IAAI,eAAe,QAAQ;YACpG,IAAI,gBAAgB,WAAW,qBAAqB,GAAG,GAAG;YAC1D,IAAI,YAAY,OAAO,qBAAqB,GAAG,GAAG;YAClD,IAAI,YAAY,gBAAgB,OAAO,YAAY,EACjD,WAAW,SAAS,IAAI,YAAY;QAExC;QAEA,SAAS,WAAW,aAAa;IACnC;AACF;AAEA,SAAS,uCAAiB,MAAe;IACvC,OACE,AAAC,kBAAkB,oBAAoB,CAAC,wCAAkB,GAAG,CAAC,OAAO,IAAI,KACzE,kBAAkB,uBACjB,kBAAkB,eAAe,OAAO,iBAAiB;AAE9D","sources":["packages/@react-aria/overlays/src/usePreventScroll.ts"],"sourcesContent":["/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {chain, getScrollParent, isIOS, useLayoutEffect} from '@react-aria/utils';\n\ninterface PreventScrollOptions {\n /** Whether the scroll lock is disabled. */\n isDisabled?: boolean\n}\n\n// @ts-ignore\nconst visualViewport = typeof document !== 'undefined' && window.visualViewport;\n\n// HTML input types that do not cause the software keyboard to appear.\nconst nonTextInputTypes = new Set([\n 'checkbox',\n 'radio',\n 'range',\n 'color',\n 'file',\n 'image',\n 'button',\n 'submit',\n 'reset'\n]);\n\n// The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position\nlet preventScrollCount = 0;\nlet restore;\n\n/**\n * Prevents scrolling on the document body on mount, and\n * restores it on unmount. Also ensures that content does not\n * shift due to the scrollbars disappearing.\n */\nexport function usePreventScroll(options: PreventScrollOptions = {}) {\n let {isDisabled} = options;\n\n useLayoutEffect(() => {\n if (isDisabled) {\n return;\n }\n\n preventScrollCount++;\n if (preventScrollCount === 1) {\n if (isIOS()) {\n restore = preventScrollMobileSafari();\n } else {\n restore = preventScrollStandard();\n }\n }\n\n return () => {\n preventScrollCount--;\n if (preventScrollCount === 0) {\n restore();\n }\n };\n }, [isDisabled]);\n}\n\n// For most browsers, all we need to do is set `overflow: hidden` on the root element, and\n// add some padding to prevent the page from shifting when the scrollbar is hidden.\nfunction preventScrollStandard() {\n return chain(\n setStyle(document.documentElement, 'paddingRight', `${window.innerWidth - document.documentElement.clientWidth}px`),\n setStyle(document.documentElement, 'overflow', 'hidden')\n );\n}\n\n// Mobile Safari is a whole different beast. Even with overflow: hidden,\n// it still scrolls the page in many situations:\n//\n// 1. When the bottom toolbar and address bar are collapsed, page scrolling is always allowed.\n// 2. When the keyboard is visible, the viewport does not resize. Instead, the keyboard covers part of\n// it, so it becomes scrollable.\n// 3. When tapping on an input, the page always scrolls so that the input is centered in the visual viewport.\n// This may cause even fixed position elements to scroll off the screen.\n// 4. When using the next/previous buttons in the keyboard to navigate between inputs, the whole page always\n// scrolls, even if the input is inside a nested scrollable element that could be scrolled instead.\n//\n// In order to work around these cases, and prevent scrolling without jankiness, we do a few things:\n//\n// 1. Prevent default on `touchmove` events that are not in a scrollable element. This prevents touch scrolling\n// on the window.\n// 2. Set `overscroll-behavior: contain` on nested scrollable regions so they do not scroll the page when at\n// the top or bottom. Work around a bug where this does not work when the element does not actually overflow\n// by preventing default in a `touchmove` event.\n// 3. Prevent default on `touchend` events on input elements and handle focusing the element ourselves.\n// 4. When focusing an input, apply a transform to trick Safari into thinking the input is at the top\n// of the page, which prevents it from scrolling the page. After the input is focused, scroll the element\n// into view ourselves, without scrolling the whole page.\n// 5. Offset the body by the scroll position using a negative margin and scroll to the top. This should appear the\n// same visually, but makes the actual scroll position always zero. This is required to make all of the\n// above work or Safari will still try to scroll the page when focusing an input.\n// 6. As a last resort, handle window scroll events, and scroll back to the top. This can happen when attempting\n// to navigate to an input with the next/previous buttons that's outside a modal.\nfunction preventScrollMobileSafari() {\n let scrollable: Element;\n let restoreScrollableStyles;\n let onTouchStart = (e: TouchEvent) => {\n // Store the nearest scrollable parent element from the element that the user touched.\n scrollable = getScrollParent(e.target as Element, true);\n if (scrollable === document.documentElement && scrollable === document.body) {\n return;\n }\n\n // Prevent scrolling up when at the top and scrolling down when at the bottom\n // of a nested scrollable area, otherwise mobile Safari will start scrolling\n // the window instead.\n if (scrollable instanceof HTMLElement && window.getComputedStyle(scrollable).overscrollBehavior === 'auto') {\n restoreScrollableStyles = setStyle(scrollable, 'overscrollBehavior', 'contain');\n }\n };\n\n let onTouchMove = (e: TouchEvent) => {\n // Prevent scrolling the window.\n if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {\n e.preventDefault();\n return;\n }\n\n // overscroll-behavior should prevent scroll chaining, but currently does not\n // if the element doesn't actually overflow. https://bugs.webkit.org/show_bug.cgi?id=243452\n // This checks that both the width and height do not overflow, otherwise we might\n // block horizontal scrolling too. In that case, adding `touch-action: pan-x` to\n // the element will prevent vertical page scrolling. We can't add that automatically\n // because it must be set before the touchstart event.\n if (scrollable.scrollHeight === scrollable.clientHeight && scrollable.scrollWidth === scrollable.clientWidth) {\n e.preventDefault();\n }\n };\n\n let onTouchEnd = (e: TouchEvent) => {\n let target = e.target as HTMLElement;\n\n // Apply this change if we're not already focused on the target element\n if (willOpenKeyboard(target) && target !== document.activeElement) {\n e.preventDefault();\n setupStyles();\n\n // Apply a transform to trick Safari into thinking the input is at the top of the page\n // so it doesn't try to scroll it into view. When tapping on an input, this needs to\n // be done before the \"focus\" event, so we have to focus the element ourselves.\n target.style.transform = 'translateY(-2000px)';\n target.focus();\n requestAnimationFrame(() => {\n target.style.transform = '';\n });\n }\n\n if (restoreScrollableStyles) {\n restoreScrollableStyles();\n }\n };\n\n let onFocus = (e: FocusEvent) => {\n let target = e.target as HTMLElement;\n if (willOpenKeyboard(target)) {\n setupStyles();\n\n // Transform also needs to be applied in the focus event in cases where focus moves\n // other than tapping on an input directly, e.g. the next/previous buttons in the\n // software keyboard. In these cases, it seems applying the transform in the focus event\n // is good enough, whereas when tapping an input, it must be done before the focus event. 🤷♂️\n target.style.transform = 'translateY(-2000px)';\n requestAnimationFrame(() => {\n target.style.transform = '';\n\n // This will have prevented the browser from scrolling the focused element into view,\n // so we need to do this ourselves in a way that doesn't cause the whole page to scroll.\n if (visualViewport) {\n if (visualViewport.height < window.innerHeight) {\n // If the keyboard is already visible, do this after one additional frame\n // to wait for the transform to be removed.\n requestAnimationFrame(() => {\n scrollIntoView(target);\n });\n } else {\n // Otherwise, wait for the visual viewport to resize before scrolling so we can\n // measure the correct position to scroll to.\n visualViewport.addEventListener('resize', () => scrollIntoView(target), {once: true});\n }\n }\n });\n }\n };\n\n let restoreStyles = null;\n let setupStyles = () => {\n if (restoreStyles) {\n return;\n }\n\n let onWindowScroll = () => {\n // Last resort. If the window scrolled, scroll it back to the top.\n // It should always be at the top because the body will have a negative margin (see below).\n window.scrollTo(0, 0);\n };\n\n // Record the original scroll position so we can restore it.\n // Then apply a negative margin to the body to offset it by the scroll position. This will\n // enable us to scroll the window to the top, which is required for the rest of this to work.\n let scrollX = window.pageXOffset;\n let scrollY = window.pageYOffset;\n\n restoreStyles = chain(\n addEvent(window, 'scroll', onWindowScroll),\n setStyle(document.documentElement, 'paddingRight', `${window.innerWidth - document.documentElement.clientWidth}px`),\n setStyle(document.documentElement, 'overflow', 'hidden'),\n setStyle(document.body, 'marginTop', `-${scrollY}px`),\n () => {\n window.scrollTo(scrollX, scrollY);\n }\n );\n\n // Scroll to the top. The negative margin on the body will make this appear the same.\n window.scrollTo(0, 0);\n };\n\n let removeEvents = chain(\n addEvent(document, 'touchstart', onTouchStart, {passive: false, capture: true}),\n addEvent(document, 'touchmove', onTouchMove, {passive: false, capture: true}),\n addEvent(document, 'touchend', onTouchEnd, {passive: false, capture: true}),\n addEvent(document, 'focus', onFocus, true)\n );\n\n return () => {\n // Restore styles and scroll the page back to where it was.\n restoreScrollableStyles?.();\n restoreStyles?.();\n removeEvents();\n };\n}\n\n// Sets a CSS property on an element, and returns a function to revert it to the previous value.\nfunction setStyle(element: HTMLElement, style: string, value: string) {\n let cur = element.style[style];\n element.style[style] = value;\n\n return () => {\n element.style[style] = cur;\n };\n}\n\n// Adds an event listener to an element, and returns a function to remove it.\nfunction addEvent<K extends keyof GlobalEventHandlersEventMap>(\n target: EventTarget,\n event: K,\n handler: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n) {\n target.addEventListener(event, handler, options);\n return () => {\n target.removeEventListener(event, handler, options);\n };\n}\n\nfunction scrollIntoView(target: Element) {\n let root = document.scrollingElement || document.documentElement;\n while (target && target !== root) {\n // Find the parent scrollable element and adjust the scroll position if the target is not already in view.\n let scrollable = getScrollParent(target);\n if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {\n let scrollableTop = scrollable.getBoundingClientRect().top;\n let targetTop = target.getBoundingClientRect().top;\n if (targetTop > scrollableTop + target.clientHeight) {\n scrollable.scrollTop += targetTop - scrollableTop;\n }\n }\n\n target = scrollable.parentElement;\n }\n}\n\nfunction willOpenKeyboard(target: Element) {\n return (\n (target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) ||\n target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLElement && target.isContentEditable)\n );\n}\n"],"names":[],"version":3,"file":"usePreventScroll.module.js.map"}
|
|
1
|
+
{"mappings":"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","sources":["packages/@react-aria/overlays/src/usePreventScroll.ts"],"sourcesContent":["/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {chain, getScrollParent, isIOS, useLayoutEffect} from '@react-aria/utils';\n\ninterface PreventScrollOptions {\n /** Whether the scroll lock is disabled. */\n isDisabled?: boolean\n}\n\nconst visualViewport = typeof document !== 'undefined' && window.visualViewport;\n\n// HTML input types that do not cause the software keyboard to appear.\nconst nonTextInputTypes = new Set([\n 'checkbox',\n 'radio',\n 'range',\n 'color',\n 'file',\n 'image',\n 'button',\n 'submit',\n 'reset'\n]);\n\n// The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position\nlet preventScrollCount = 0;\nlet restore;\n\n/**\n * Prevents scrolling on the document body on mount, and\n * restores it on unmount. Also ensures that content does not\n * shift due to the scrollbars disappearing.\n */\nexport function usePreventScroll(options: PreventScrollOptions = {}) {\n let {isDisabled} = options;\n\n useLayoutEffect(() => {\n if (isDisabled) {\n return;\n }\n\n preventScrollCount++;\n if (preventScrollCount === 1) {\n if (isIOS()) {\n restore = preventScrollMobileSafari();\n } else {\n restore = preventScrollStandard();\n }\n }\n\n return () => {\n preventScrollCount--;\n if (preventScrollCount === 0) {\n restore();\n }\n };\n }, [isDisabled]);\n}\n\n// For most browsers, all we need to do is set `overflow: hidden` on the root element, and\n// add some padding to prevent the page from shifting when the scrollbar is hidden.\nfunction preventScrollStandard() {\n return chain(\n setStyle(document.documentElement, 'paddingRight', `${window.innerWidth - document.documentElement.clientWidth}px`),\n setStyle(document.documentElement, 'overflow', 'hidden')\n );\n}\n\n// Mobile Safari is a whole different beast. Even with overflow: hidden,\n// it still scrolls the page in many situations:\n//\n// 1. When the bottom toolbar and address bar are collapsed, page scrolling is always allowed.\n// 2. When the keyboard is visible, the viewport does not resize. Instead, the keyboard covers part of\n// it, so it becomes scrollable.\n// 3. When tapping on an input, the page always scrolls so that the input is centered in the visual viewport.\n// This may cause even fixed position elements to scroll off the screen.\n// 4. When using the next/previous buttons in the keyboard to navigate between inputs, the whole page always\n// scrolls, even if the input is inside a nested scrollable element that could be scrolled instead.\n//\n// In order to work around these cases, and prevent scrolling without jankiness, we do a few things:\n//\n// 1. Prevent default on `touchmove` events that are not in a scrollable element. This prevents touch scrolling\n// on the window.\n// 2. Set `overscroll-behavior: contain` on nested scrollable regions so they do not scroll the page when at\n// the top or bottom. Work around a bug where this does not work when the element does not actually overflow\n// by preventing default in a `touchmove` event.\n// 3. Prevent default on `touchend` events on input elements and handle focusing the element ourselves.\n// 4. When focusing an input, apply a transform to trick Safari into thinking the input is at the top\n// of the page, which prevents it from scrolling the page. After the input is focused, scroll the element\n// into view ourselves, without scrolling the whole page.\n// 5. Offset the body by the scroll position using a negative margin and scroll to the top. This should appear the\n// same visually, but makes the actual scroll position always zero. This is required to make all of the\n// above work or Safari will still try to scroll the page when focusing an input.\n// 6. As a last resort, handle window scroll events, and scroll back to the top. This can happen when attempting\n// to navigate to an input with the next/previous buttons that's outside a modal.\nfunction preventScrollMobileSafari() {\n let scrollable: Element;\n let restoreScrollableStyles;\n let onTouchStart = (e: TouchEvent) => {\n // Store the nearest scrollable parent element from the element that the user touched.\n scrollable = getScrollParent(e.target as Element, true);\n if (scrollable === document.documentElement && scrollable === document.body) {\n return;\n }\n\n // Prevent scrolling up when at the top and scrolling down when at the bottom\n // of a nested scrollable area, otherwise mobile Safari will start scrolling\n // the window instead.\n if (scrollable instanceof HTMLElement && window.getComputedStyle(scrollable).overscrollBehavior === 'auto') {\n restoreScrollableStyles = setStyle(scrollable, 'overscrollBehavior', 'contain');\n }\n };\n\n let onTouchMove = (e: TouchEvent) => {\n // Prevent scrolling the window.\n if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {\n e.preventDefault();\n return;\n }\n\n // overscroll-behavior should prevent scroll chaining, but currently does not\n // if the element doesn't actually overflow. https://bugs.webkit.org/show_bug.cgi?id=243452\n // This checks that both the width and height do not overflow, otherwise we might\n // block horizontal scrolling too. In that case, adding `touch-action: pan-x` to\n // the element will prevent vertical page scrolling. We can't add that automatically\n // because it must be set before the touchstart event.\n if (scrollable.scrollHeight === scrollable.clientHeight && scrollable.scrollWidth === scrollable.clientWidth) {\n e.preventDefault();\n }\n };\n\n let onTouchEnd = () => {\n if (restoreScrollableStyles) {\n restoreScrollableStyles();\n }\n };\n\n let onFocus = (e: FocusEvent) => {\n let target = e.target as HTMLElement;\n if (willOpenKeyboard(target)) {\n setupStyles();\n\n // Apply a transform to trick Safari into thinking the input is at the top of the page\n // so it doesn't try to scroll it into view.\n target.style.transform = 'translateY(-2000px)';\n requestAnimationFrame(() => {\n target.style.transform = '';\n\n // This will have prevented the browser from scrolling the focused element into view,\n // so we need to do this ourselves in a way that doesn't cause the whole page to scroll.\n if (visualViewport) {\n if (visualViewport.height < window.innerHeight) {\n // If the keyboard is already visible, do this after one additional frame\n // to wait for the transform to be removed.\n requestAnimationFrame(() => {\n scrollIntoView(target);\n });\n } else {\n // Otherwise, wait for the visual viewport to resize before scrolling so we can\n // measure the correct position to scroll to.\n visualViewport.addEventListener('resize', () => scrollIntoView(target), {once: true});\n }\n }\n });\n }\n };\n\n let restoreStyles: null | (() => void) = null;\n let setupStyles = () => {\n if (restoreStyles) {\n return;\n }\n\n let onWindowScroll = () => {\n // Last resort. If the window scrolled, scroll it back to the top.\n // It should always be at the top because the body will have a negative margin (see below).\n window.scrollTo(0, 0);\n };\n\n // Record the original scroll position so we can restore it.\n // Then apply a negative margin to the body to offset it by the scroll position. This will\n // enable us to scroll the window to the top, which is required for the rest of this to work.\n let scrollX = window.pageXOffset;\n let scrollY = window.pageYOffset;\n\n restoreStyles = chain(\n addEvent(window, 'scroll', onWindowScroll),\n setStyle(document.documentElement, 'paddingRight', `${window.innerWidth - document.documentElement.clientWidth}px`),\n setStyle(document.documentElement, 'overflow', 'hidden'),\n setStyle(document.body, 'marginTop', `-${scrollY}px`),\n () => {\n window.scrollTo(scrollX, scrollY);\n }\n );\n\n // Scroll to the top. The negative margin on the body will make this appear the same.\n window.scrollTo(0, 0);\n };\n\n let removeEvents = chain(\n addEvent(document, 'touchstart', onTouchStart, {passive: false, capture: true}),\n addEvent(document, 'touchmove', onTouchMove, {passive: false, capture: true}),\n addEvent(document, 'touchend', onTouchEnd, {passive: false, capture: true}),\n addEvent(document, 'focus', onFocus, true)\n );\n\n return () => {\n // Restore styles and scroll the page back to where it was.\n restoreScrollableStyles?.();\n restoreStyles?.();\n removeEvents();\n };\n}\n\n// Sets a CSS property on an element, and returns a function to revert it to the previous value.\nfunction setStyle(element: HTMLElement, style: string, value: string) {\n let cur = element.style[style];\n element.style[style] = value;\n\n return () => {\n element.style[style] = cur;\n };\n}\n\n// Adds an event listener to an element, and returns a function to remove it.\nfunction addEvent<K extends keyof GlobalEventHandlersEventMap>(\n target: Document | Window,\n event: K,\n handler: (this: Document | Window, ev: GlobalEventHandlersEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n) {\n // internal function, so it's ok to ignore the difficult to fix type error\n // @ts-ignore\n target.addEventListener(event, handler, options);\n return () => {\n // @ts-ignore\n target.removeEventListener(event, handler, options);\n };\n}\n\nfunction scrollIntoView(target: Element) {\n let root = document.scrollingElement || document.documentElement;\n let nextTarget: Element | null = target;\n while (nextTarget && nextTarget !== root) {\n // Find the parent scrollable element and adjust the scroll position if the target is not already in view.\n let scrollable = getScrollParent(nextTarget);\n if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== nextTarget) {\n let scrollableTop = scrollable.getBoundingClientRect().top;\n let targetTop = nextTarget.getBoundingClientRect().top;\n if (targetTop > scrollableTop + nextTarget.clientHeight) {\n scrollable.scrollTop += targetTop - scrollableTop;\n }\n }\n\n nextTarget = scrollable.parentElement;\n }\n}\n\nfunction willOpenKeyboard(target: Element) {\n return (\n (target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) ||\n target instanceof HTMLTextAreaElement ||\n (target instanceof HTMLElement && target.isContentEditable)\n );\n}\n"],"names":[],"version":3,"file":"usePreventScroll.module.js.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/overlays",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -22,24 +22,24 @@
|
|
|
22
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@react-aria/focus": "^3.
|
|
26
|
-
"@react-aria/i18n": "^3.12.
|
|
27
|
-
"@react-aria/interactions": "^3.
|
|
28
|
-
"@react-aria/ssr": "^3.9.
|
|
29
|
-
"@react-aria/utils": "^3.
|
|
30
|
-
"@react-aria/visually-hidden": "^3.8.
|
|
31
|
-
"@react-stately/overlays": "^3.6.
|
|
32
|
-
"@react-types/button": "^3.10.
|
|
33
|
-
"@react-types/overlays": "^3.8.
|
|
34
|
-
"@react-types/shared": "^3.
|
|
25
|
+
"@react-aria/focus": "^3.19.1",
|
|
26
|
+
"@react-aria/i18n": "^3.12.5",
|
|
27
|
+
"@react-aria/interactions": "^3.23.0",
|
|
28
|
+
"@react-aria/ssr": "^3.9.7",
|
|
29
|
+
"@react-aria/utils": "^3.27.0",
|
|
30
|
+
"@react-aria/visually-hidden": "^3.8.19",
|
|
31
|
+
"@react-stately/overlays": "^3.6.13",
|
|
32
|
+
"@react-types/button": "^3.10.2",
|
|
33
|
+
"@react-types/overlays": "^3.8.12",
|
|
34
|
+
"@react-types/shared": "^3.27.0",
|
|
35
35
|
"@swc/helpers": "^0.5.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0",
|
|
39
|
-
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0"
|
|
38
|
+
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
|
|
39
|
+
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "09e7f44bebdc9d89122926b2b439a0a38a2814ea"
|
|
45
45
|
}
|
package/src/Overlay.tsx
CHANGED
|
@@ -39,7 +39,7 @@ export interface OverlayProps {
|
|
|
39
39
|
isExiting?: boolean
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export const OverlayContext = React.createContext(null);
|
|
42
|
+
export const OverlayContext = React.createContext<{contain: boolean, setContain: React.Dispatch<React.SetStateAction<boolean>>} | null>(null);
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* A container which renders an overlay such as a popover or modal in a portal,
|
package/src/PortalProvider.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export function UNSTABLE_PortalProvider(props: PortalProviderProps & {children:
|
|
|
23
23
|
let {getContainer} = props;
|
|
24
24
|
let {getContainer: ctxGetContainer} = useUNSTABLE_PortalContext();
|
|
25
25
|
return (
|
|
26
|
-
<PortalContext.Provider value={{getContainer: getContainer === null ?
|
|
26
|
+
<PortalContext.Provider value={{getContainer: getContainer === null ? undefined : getContainer ?? ctxGetContainer}}>
|
|
27
27
|
{props.children}
|
|
28
28
|
</PortalContext.Provider>
|
|
29
29
|
);
|
package/src/ariaHideOutside.ts
CHANGED
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
// Keeps a ref count of all hidden elements. Added to when hiding an element, and
|
|
14
14
|
// subtracted from when showing it again. When it reaches zero, aria-hidden is removed.
|
|
15
15
|
let refCountMap = new WeakMap<Element, number>();
|
|
16
|
-
|
|
16
|
+
interface ObserverWrapper {
|
|
17
|
+
observe: () => void,
|
|
18
|
+
disconnect: () => void
|
|
19
|
+
}
|
|
20
|
+
let observerStack: Array<ObserverWrapper> = [];
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Hides all elements in the DOM outside the given targets from screen readers using aria-hidden,
|
|
@@ -40,7 +44,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
40
44
|
// For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
|
|
41
45
|
if (
|
|
42
46
|
visibleNodes.has(node) ||
|
|
43
|
-
(hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
47
|
+
(node.parentElement && hiddenNodes.has(node.parentElement) && node.parentElement.getAttribute('role') !== 'row')
|
|
44
48
|
) {
|
|
45
49
|
return NodeFilter.FILTER_REJECT;
|
|
46
50
|
}
|
|
@@ -133,7 +137,7 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
133
137
|
|
|
134
138
|
observer.observe(root, {childList: true, subtree: true});
|
|
135
139
|
|
|
136
|
-
let observerWrapper = {
|
|
140
|
+
let observerWrapper: ObserverWrapper = {
|
|
137
141
|
observe() {
|
|
138
142
|
observer.observe(root, {childList: true, subtree: true});
|
|
139
143
|
},
|
|
@@ -149,6 +153,9 @@ export function ariaHideOutside(targets: Element[], root = document.body) {
|
|
|
149
153
|
|
|
150
154
|
for (let node of hiddenNodes) {
|
|
151
155
|
let count = refCountMap.get(node);
|
|
156
|
+
if (count == null) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
152
159
|
if (count === 1) {
|
|
153
160
|
node.removeAttribute('aria-hidden');
|
|
154
161
|
refCountMap.delete(node);
|
package/src/calculatePosition.ts
CHANGED
|
@@ -64,10 +64,10 @@ interface PositionOpts {
|
|
|
64
64
|
type HeightGrowthDirection = 'top' | 'bottom';
|
|
65
65
|
|
|
66
66
|
export interface PositionResult {
|
|
67
|
-
position
|
|
67
|
+
position: Position,
|
|
68
68
|
arrowOffsetLeft?: number,
|
|
69
69
|
arrowOffsetTop?: number,
|
|
70
|
-
maxHeight
|
|
70
|
+
maxHeight: number,
|
|
71
71
|
placement: PlacementAxis
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -102,13 +102,12 @@ const TOTAL_SIZE = {
|
|
|
102
102
|
|
|
103
103
|
const PARSED_PLACEMENT_CACHE = {};
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
let visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
|
105
|
+
let visualViewport = typeof document !== 'undefined' ? window.visualViewport : null;
|
|
107
106
|
|
|
108
107
|
function getContainerDimensions(containerNode: Element): Dimensions {
|
|
109
108
|
let width = 0, height = 0, totalWidth = 0, totalHeight = 0, top = 0, left = 0;
|
|
110
109
|
let scroll: Position = {};
|
|
111
|
-
let isPinchZoomedIn = visualViewport?.scale > 1;
|
|
110
|
+
let isPinchZoomedIn = (visualViewport?.scale ?? 1) > 1;
|
|
112
111
|
|
|
113
112
|
if (containerNode.tagName === 'BODY') {
|
|
114
113
|
let documentElement = document.documentElement;
|
|
@@ -141,8 +140,8 @@ function getContainerDimensions(containerNode: Element): Dimensions {
|
|
|
141
140
|
// before pinch zoom happens
|
|
142
141
|
scroll.top = 0;
|
|
143
142
|
scroll.left = 0;
|
|
144
|
-
top = visualViewport
|
|
145
|
-
left = visualViewport
|
|
143
|
+
top = visualViewport?.pageTop ?? 0;
|
|
144
|
+
left = visualViewport?.pageLeft ?? 0;
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
return {width, height, totalWidth, totalHeight, scroll, top, left};
|
|
@@ -174,7 +173,7 @@ function getDelta(
|
|
|
174
173
|
padding: number,
|
|
175
174
|
containerOffsetWithBoundary: Offset
|
|
176
175
|
) {
|
|
177
|
-
let containerScroll = containerDimensions.scroll[axis];
|
|
176
|
+
let containerScroll = containerDimensions.scroll[axis] ?? 0;
|
|
178
177
|
// The height/width of the boundary. Matches the axis along which we are adjusting the overlay position
|
|
179
178
|
let boundarySize = boundaryDimensions[AXIS_SIZE[axis]];
|
|
180
179
|
// Calculate the edges of the boundary (accomodating for the boundary padding) and the edges of the overlay.
|
|
@@ -240,26 +239,26 @@ function computePosition(
|
|
|
240
239
|
let position: Position = {};
|
|
241
240
|
|
|
242
241
|
// button position
|
|
243
|
-
position[crossAxis] = childOffset[crossAxis];
|
|
242
|
+
position[crossAxis] = childOffset[crossAxis] ?? 0;
|
|
244
243
|
if (crossPlacement === 'center') {
|
|
245
244
|
// + (button size / 2) - (overlay size / 2)
|
|
246
245
|
// at this point the overlay center should match the button center
|
|
247
|
-
position[crossAxis] += (childOffset[crossSize] - overlaySize[crossSize]) / 2;
|
|
246
|
+
position[crossAxis]! += ((childOffset[crossSize] ?? 0) - (overlaySize[crossSize] ?? 0)) / 2;
|
|
248
247
|
} else if (crossPlacement !== crossAxis) {
|
|
249
248
|
// + (button size) - (overlay size)
|
|
250
249
|
// at this point the overlay bottom should match the button bottom
|
|
251
|
-
position[crossAxis] += (childOffset[crossSize] - overlaySize[crossSize]);
|
|
250
|
+
position[crossAxis]! += (childOffset[crossSize] ?? 0) - (overlaySize[crossSize] ?? 0);
|
|
252
251
|
}/* else {
|
|
253
252
|
the overlay top should match the button top
|
|
254
253
|
} */
|
|
255
254
|
|
|
256
|
-
position[crossAxis] += crossOffset;
|
|
255
|
+
position[crossAxis]! += crossOffset;
|
|
257
256
|
|
|
258
257
|
// overlay top overlapping arrow with button bottom
|
|
259
258
|
const minPosition = childOffset[crossAxis] - overlaySize[crossSize] + arrowSize + arrowBoundaryOffset;
|
|
260
259
|
// overlay bottom overlapping arrow with button top
|
|
261
260
|
const maxPosition = childOffset[crossAxis] + childOffset[crossSize] - arrowSize - arrowBoundaryOffset;
|
|
262
|
-
position[crossAxis] = clamp(position[crossAxis]
|
|
261
|
+
position[crossAxis] = clamp(position[crossAxis]!, minPosition, maxPosition);
|
|
263
262
|
|
|
264
263
|
// Floor these so the position isn't placed on a partial pixel, only whole pixels. Shouldn't matter if it was floored or ceiled, so chose one.
|
|
265
264
|
if (placement === axis) {
|
|
@@ -288,19 +287,19 @@ function getMaxHeight(
|
|
|
288
287
|
const containerHeight = (isContainerPositioned ? containerOffsetWithBoundary.height : boundaryDimensions[TOTAL_SIZE.height]);
|
|
289
288
|
// For cases where position is set via "bottom" instead of "top", we need to calculate the true overlay top with respect to the boundary. Reverse calculate this with the same method
|
|
290
289
|
// used in computePosition.
|
|
291
|
-
let overlayTop = position.top != null ? containerOffsetWithBoundary.top + position.top : containerOffsetWithBoundary.top + (containerHeight - position.bottom - overlayHeight);
|
|
290
|
+
let overlayTop = position.top != null ? containerOffsetWithBoundary.top + position.top : containerOffsetWithBoundary.top + (containerHeight - (position.bottom ?? 0) - overlayHeight);
|
|
292
291
|
let maxHeight = heightGrowthDirection !== 'top' ?
|
|
293
292
|
// We want the distance between the top of the overlay to the bottom of the boundary
|
|
294
293
|
Math.max(0,
|
|
295
|
-
(boundaryDimensions.height + boundaryDimensions.top + boundaryDimensions.scroll.top) // this is the bottom of the boundary
|
|
294
|
+
(boundaryDimensions.height + boundaryDimensions.top + (boundaryDimensions.scroll.top ?? 0)) // this is the bottom of the boundary
|
|
296
295
|
- overlayTop // this is the top of the overlay
|
|
297
|
-
- (margins.top + margins.bottom + padding) // save additional space for margin and padding
|
|
296
|
+
- ((margins.top ?? 0) + (margins.bottom ?? 0) + padding) // save additional space for margin and padding
|
|
298
297
|
)
|
|
299
298
|
// We want the distance between the bottom of the overlay to the top of the boundary
|
|
300
299
|
: Math.max(0,
|
|
301
300
|
(overlayTop + overlayHeight) // this is the bottom of the overlay
|
|
302
|
-
- (boundaryDimensions.top + boundaryDimensions.scroll.top) // this is the top of the boundary
|
|
303
|
-
- (margins.top + margins.bottom + padding) // save additional space for margin and padding
|
|
301
|
+
- (boundaryDimensions.top + (boundaryDimensions.scroll.top ?? 0)) // this is the top of the boundary
|
|
302
|
+
- ((margins.top ?? 0) + (margins.bottom ?? 0) + padding) // save additional space for margin and padding
|
|
304
303
|
);
|
|
305
304
|
return Math.min(boundaryDimensions.height - (padding * 2), maxHeight);
|
|
306
305
|
}
|
|
@@ -315,10 +314,10 @@ function getAvailableSpace(
|
|
|
315
314
|
) {
|
|
316
315
|
let {placement, axis, size} = placementInfo;
|
|
317
316
|
if (placement === axis) {
|
|
318
|
-
return Math.max(0, childOffset[axis] - boundaryDimensions[axis] - boundaryDimensions.scroll[axis] + containerOffsetWithBoundary[axis] - margins[axis] - margins[FLIPPED_DIRECTION[axis]] - padding);
|
|
317
|
+
return Math.max(0, childOffset[axis] - boundaryDimensions[axis] - (boundaryDimensions.scroll[axis] ?? 0) + containerOffsetWithBoundary[axis] - (margins[axis] ?? 0) - margins[FLIPPED_DIRECTION[axis]] - padding);
|
|
319
318
|
}
|
|
320
319
|
|
|
321
|
-
return Math.max(0, boundaryDimensions[size] + boundaryDimensions[axis] + boundaryDimensions.scroll[axis] - containerOffsetWithBoundary[axis] - childOffset[axis] - childOffset[size] - margins[axis] - margins[FLIPPED_DIRECTION[axis]] - padding);
|
|
320
|
+
return Math.max(0, boundaryDimensions[size] + boundaryDimensions[axis] + boundaryDimensions.scroll[axis] - containerOffsetWithBoundary[axis] - childOffset[axis] - childOffset[size] - (margins[axis] ?? 0) - margins[FLIPPED_DIRECTION[axis]] - padding);
|
|
322
321
|
}
|
|
323
322
|
|
|
324
323
|
export function calculatePositionInternal(
|
|
@@ -389,8 +388,8 @@ export function calculatePositionInternal(
|
|
|
389
388
|
}
|
|
390
389
|
}
|
|
391
390
|
|
|
392
|
-
let delta = getDelta(crossAxis, position[crossAxis]
|
|
393
|
-
position[crossAxis] += delta;
|
|
391
|
+
let delta = getDelta(crossAxis, position[crossAxis]!, overlaySize[crossSize], boundaryDimensions, containerDimensions, padding, containerOffsetWithBoundary);
|
|
392
|
+
position[crossAxis]! += delta;
|
|
394
393
|
|
|
395
394
|
let maxHeight = getMaxHeight(
|
|
396
395
|
position,
|
|
@@ -410,8 +409,8 @@ export function calculatePositionInternal(
|
|
|
410
409
|
overlaySize.height = Math.min(overlaySize.height, maxHeight);
|
|
411
410
|
|
|
412
411
|
position = computePosition(childOffset, boundaryDimensions, overlaySize, placementInfo, normalizedOffset, crossOffset, containerOffsetWithBoundary, isContainerPositioned, arrowSize, arrowBoundaryOffset);
|
|
413
|
-
delta = getDelta(crossAxis, position[crossAxis]
|
|
414
|
-
position[crossAxis] += delta;
|
|
412
|
+
delta = getDelta(crossAxis, position[crossAxis]!, overlaySize[crossSize], boundaryDimensions, containerDimensions, padding, containerOffsetWithBoundary);
|
|
413
|
+
position[crossAxis]! += delta;
|
|
415
414
|
|
|
416
415
|
let arrowPosition: Position = {};
|
|
417
416
|
|
|
@@ -420,12 +419,12 @@ export function calculatePositionInternal(
|
|
|
420
419
|
// childOffset[crossAxis] + .5 * childOffset[crossSize] = absolute position with respect to the trigger's coordinate system that would place the arrow in the center of the trigger
|
|
421
420
|
// position[crossAxis] - margins[AXIS[crossAxis]] = value use to transform the position to a value with respect to the overlay's coordinate system. A child element's (aka arrow) position absolute's "0"
|
|
422
421
|
// is positioned after the margin of its parent (aka overlay) so we need to subtract it to get the proper coordinate transform
|
|
423
|
-
let preferredArrowPosition = childOffset[crossAxis] + .5 * childOffset[crossSize] - position[crossAxis] - margins[AXIS[crossAxis]];
|
|
422
|
+
let preferredArrowPosition = childOffset[crossAxis] + .5 * childOffset[crossSize] - position[crossAxis]! - margins[AXIS[crossAxis]];
|
|
424
423
|
|
|
425
424
|
// Min/Max position limits for the arrow with respect to the overlay
|
|
426
425
|
const arrowMinPosition = arrowSize / 2 + arrowBoundaryOffset;
|
|
427
426
|
// overlaySize[crossSize] - margins = true size of the overlay
|
|
428
|
-
const overlayMargin = AXIS[crossAxis] === 'left' ? margins.left + margins.right : margins.top + margins.bottom;
|
|
427
|
+
const overlayMargin = AXIS[crossAxis] === 'left' ? (margins.left ?? 0) + (margins.right ?? 0) : (margins.top ?? 0) + (margins.bottom ?? 0);
|
|
429
428
|
const arrowMaxPosition = overlaySize[crossSize] - overlayMargin - (arrowSize / 2) - arrowBoundaryOffset;
|
|
430
429
|
|
|
431
430
|
// Min/Max position limits for the arrow with respect to the trigger/overlay anchor element
|
|
@@ -479,8 +478,8 @@ export function calculatePosition(opts: PositionOpts): PositionResult {
|
|
|
479
478
|
|
|
480
479
|
let overlaySize: Offset = getOffset(overlayNode);
|
|
481
480
|
let margins = getMargins(overlayNode);
|
|
482
|
-
overlaySize.width += margins.left + margins.right;
|
|
483
|
-
overlaySize.height += margins.top + margins.bottom;
|
|
481
|
+
overlaySize.width += (margins.left ?? 0) + (margins.right ?? 0);
|
|
482
|
+
overlaySize.height += (margins.top ?? 0) + (margins.bottom ?? 0);
|
|
484
483
|
|
|
485
484
|
let scrollSize = getScroll(scrollNode);
|
|
486
485
|
let boundaryDimensions = getContainerDimensions(boundaryElement);
|
|
@@ -590,9 +589,7 @@ function isContainingBlock(node: Element): boolean {
|
|
|
590
589
|
/transform|perspective/.test(style.willChange) ||
|
|
591
590
|
style.filter !== 'none' ||
|
|
592
591
|
style.contain === 'paint' ||
|
|
593
|
-
// @ts-ignore
|
|
594
592
|
('backdropFilter' in style && style.backdropFilter !== 'none') ||
|
|
595
|
-
// @ts-ignore
|
|
596
593
|
('WebkitBackdropFilter' in style && style.WebkitBackdropFilter !== 'none')
|
|
597
594
|
);
|
|
598
595
|
}
|
package/src/useCloseOnScroll.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const onCloseMap: WeakMap<Element, () => void> = new WeakMap();
|
|
|
23
23
|
interface CloseOnScrollOptions {
|
|
24
24
|
triggerRef: RefObject<Element | null>,
|
|
25
25
|
isOpen?: boolean,
|
|
26
|
-
onClose?: () => void
|
|
26
|
+
onClose?: (() => void) | null
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** @private */
|
|
@@ -35,7 +35,7 @@ export function useCloseOnScroll(opts: CloseOnScrollOptions) {
|
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
let onScroll = (e:
|
|
38
|
+
let onScroll = (e: Event) => {
|
|
39
39
|
// Ignore if scrolling an scrollable region outside the trigger's tree.
|
|
40
40
|
let target = e.target;
|
|
41
41
|
// window is not a Node and doesn't have contain, but window contains everything
|
package/src/useModal.tsx
CHANGED
|
@@ -79,7 +79,7 @@ export function useModalProvider(): ModalProviderAria {
|
|
|
79
79
|
let context = useContext(Context);
|
|
80
80
|
return {
|
|
81
81
|
modalProviderProps: {
|
|
82
|
-
'aria-hidden': context && context.modalCount > 0 ? true :
|
|
82
|
+
'aria-hidden': context && context.modalCount > 0 ? true : undefined
|
|
83
83
|
}
|
|
84
84
|
};
|
|
85
85
|
}
|
|
@@ -123,7 +123,7 @@ export interface OverlayContainerProps extends ModalProviderProps {
|
|
|
123
123
|
* nested modal is opened. Only the top-most modal or overlay should
|
|
124
124
|
* be accessible at once.
|
|
125
125
|
*/
|
|
126
|
-
export function OverlayContainer(props: OverlayContainerProps): React.ReactPortal {
|
|
126
|
+
export function OverlayContainer(props: OverlayContainerProps): React.ReactPortal | null {
|
|
127
127
|
let isSSR = useIsSSR();
|
|
128
128
|
let {portalContainer = isSSR ? null : document.body, ...rest} = props;
|
|
129
129
|
|
package/src/useModalOverlay.ts
CHANGED
|
@@ -57,7 +57,7 @@ export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTrig
|
|
|
57
57
|
useOverlayFocusContain();
|
|
58
58
|
|
|
59
59
|
useEffect(() => {
|
|
60
|
-
if (state.isOpen) {
|
|
60
|
+
if (state.isOpen && ref.current) {
|
|
61
61
|
return ariaHideOutside([ref.current]);
|
|
62
62
|
}
|
|
63
63
|
}, [state.isOpen, ref]);
|
package/src/useOverlay.ts
CHANGED
|
@@ -120,7 +120,7 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | nul
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
// Handle clicking outside the overlay to close it
|
|
123
|
-
useInteractOutside({ref, onInteractOutside: isDismissable && isOpen ? onInteractOutside :
|
|
123
|
+
useInteractOutside({ref, onInteractOutside: isDismissable && isOpen ? onInteractOutside : undefined, onInteractOutsideStart});
|
|
124
124
|
|
|
125
125
|
let {focusWithinProps} = useFocusWithin({
|
|
126
126
|
isDisabled: !shouldCloseOnBlur,
|
|
@@ -139,7 +139,7 @@ export function useOverlay(props: AriaOverlayProps, ref: RefObject<Element | nul
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (!shouldCloseOnInteractOutside || shouldCloseOnInteractOutside(e.relatedTarget as Element)) {
|
|
142
|
-
onClose();
|
|
142
|
+
onClose?.();
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
});
|
|
@@ -48,7 +48,7 @@ export interface AriaPositionProps extends PositionProps {
|
|
|
48
48
|
*/
|
|
49
49
|
shouldUpdatePosition?: boolean,
|
|
50
50
|
/** Handler that is called when the overlay should close. */
|
|
51
|
-
onClose?: () => void,
|
|
51
|
+
onClose?: (() => void) | null,
|
|
52
52
|
/**
|
|
53
53
|
* The maxHeight specified for the overlay element.
|
|
54
54
|
* By default, it will take all space up to the current viewport height.
|
|
@@ -67,7 +67,7 @@ export interface PositionAria {
|
|
|
67
67
|
/** Props for the overlay tip arrow if any. */
|
|
68
68
|
arrowProps: DOMAttributes,
|
|
69
69
|
/** Placement of the overlay with respect to the overlay trigger. */
|
|
70
|
-
placement: PlacementAxis,
|
|
70
|
+
placement: PlacementAxis | null,
|
|
71
71
|
/** Updates the position of the overlay. */
|
|
72
72
|
updatePosition(): void
|
|
73
73
|
}
|
|
@@ -77,8 +77,7 @@ interface ScrollAnchor {
|
|
|
77
77
|
offset: number
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
let visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
|
80
|
+
let visualViewport = typeof document !== 'undefined' ? window.visualViewport : null;
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* Handles positioning overlays like popovers and menus relative to a trigger
|
|
@@ -103,13 +102,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
103
102
|
maxHeight,
|
|
104
103
|
arrowBoundaryOffset = 0
|
|
105
104
|
} = props;
|
|
106
|
-
let [position, setPosition] = useState<PositionResult>(
|
|
107
|
-
position: {},
|
|
108
|
-
arrowOffsetLeft: undefined,
|
|
109
|
-
arrowOffsetTop: undefined,
|
|
110
|
-
maxHeight: undefined,
|
|
111
|
-
placement: undefined
|
|
112
|
-
});
|
|
105
|
+
let [position, setPosition] = useState<PositionResult | null>(null);
|
|
113
106
|
|
|
114
107
|
let deps = [
|
|
115
108
|
shouldUpdatePosition,
|
|
@@ -154,17 +147,17 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
154
147
|
// changes, the focused element appears to stay in the same position.
|
|
155
148
|
let anchor: ScrollAnchor | null = null;
|
|
156
149
|
if (scrollRef.current && scrollRef.current.contains(document.activeElement)) {
|
|
157
|
-
let anchorRect = document.activeElement
|
|
150
|
+
let anchorRect = document.activeElement?.getBoundingClientRect();
|
|
158
151
|
let scrollRect = scrollRef.current.getBoundingClientRect();
|
|
159
152
|
// Anchor from the top if the offset is in the top half of the scrollable element,
|
|
160
153
|
// otherwise anchor from the bottom.
|
|
161
154
|
anchor = {
|
|
162
155
|
type: 'top',
|
|
163
|
-
offset: anchorRect
|
|
156
|
+
offset: (anchorRect?.top ?? 0) - scrollRect.top
|
|
164
157
|
};
|
|
165
158
|
if (anchor.offset > scrollRect.height / 2) {
|
|
166
159
|
anchor.type = 'bottom';
|
|
167
|
-
anchor.offset = anchorRect
|
|
160
|
+
anchor.offset = (anchorRect?.bottom ?? 0) - scrollRect.bottom;
|
|
168
161
|
}
|
|
169
162
|
}
|
|
170
163
|
|
|
@@ -192,6 +185,10 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
192
185
|
arrowBoundaryOffset
|
|
193
186
|
});
|
|
194
187
|
|
|
188
|
+
if (!position.position) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
195
192
|
// Modify overlay styles directly so positioning happens immediately without the need of a second render
|
|
196
193
|
// This is so we don't have to delay autoFocus scrolling or delay applying preventScroll for popovers
|
|
197
194
|
overlay.style.top = '';
|
|
@@ -199,11 +196,11 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
199
196
|
overlay.style.left = '';
|
|
200
197
|
overlay.style.right = '';
|
|
201
198
|
|
|
202
|
-
Object.keys(position.position).forEach(key => overlay.style[key] = position.position[key] + 'px');
|
|
203
|
-
overlay.style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' :
|
|
199
|
+
Object.keys(position.position).forEach(key => overlay.style[key] = (position.position!)[key] + 'px');
|
|
200
|
+
overlay.style.maxHeight = position.maxHeight != null ? position.maxHeight + 'px' : '';
|
|
204
201
|
|
|
205
202
|
// Restore scroll position relative to anchor element.
|
|
206
|
-
if (anchor) {
|
|
203
|
+
if (anchor && document.activeElement && scrollRef.current) {
|
|
207
204
|
let anchorRect = document.activeElement.getBoundingClientRect();
|
|
208
205
|
let scrollRect = scrollRef.current.getBoundingClientRect();
|
|
209
206
|
let newOffset = anchorRect[anchor.type] - scrollRect[anchor.type];
|
|
@@ -268,7 +265,7 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
268
265
|
|
|
269
266
|
let close = useCallback(() => {
|
|
270
267
|
if (!isResizing.current) {
|
|
271
|
-
onClose();
|
|
268
|
+
onClose?.();
|
|
272
269
|
}
|
|
273
270
|
}, [onClose, isResizing]);
|
|
274
271
|
|
|
@@ -285,17 +282,17 @@ export function useOverlayPosition(props: AriaPositionProps): PositionAria {
|
|
|
285
282
|
style: {
|
|
286
283
|
position: 'absolute',
|
|
287
284
|
zIndex: 100000, // should match the z-index in ModalTrigger
|
|
288
|
-
...position
|
|
289
|
-
maxHeight: position
|
|
285
|
+
...position?.position,
|
|
286
|
+
maxHeight: position?.maxHeight ?? '100vh'
|
|
290
287
|
}
|
|
291
288
|
},
|
|
292
|
-
placement: position
|
|
289
|
+
placement: position?.placement ?? null,
|
|
293
290
|
arrowProps: {
|
|
294
291
|
'aria-hidden': 'true',
|
|
295
292
|
role: 'presentation',
|
|
296
293
|
style: {
|
|
297
|
-
left: position
|
|
298
|
-
top: position
|
|
294
|
+
left: position?.arrowOffsetLeft,
|
|
295
|
+
top: position?.arrowOffsetTop
|
|
299
296
|
}
|
|
300
297
|
},
|
|
301
298
|
updatePosition
|