Skip to content

Commit

Permalink
Merge pull request #1363 from szhsin/fix/position
Browse files Browse the repository at this point in the history
fix: reposition
  • Loading branch information
szhsin authored Aug 1, 2024
2 parents 441fc5b + 0e2bbd6 commit 59ee65e
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 165 deletions.
71 changes: 23 additions & 48 deletions dist/es/components/MenuList.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { jsxs, jsx } from 'react/jsx-runtime';
import { createSubmenuCtx } from '../utils/submenuCtx.js';
import { SettingsContext, MenuListContext, HoverActionTypes, menuClass, menuArrowClass, positionAbsolute, dummyItemProps, MenuListItemContext, HoverItemContext, Keys, CloseReason, FocusPositions } from '../utils/constants.js';
import { useItems } from '../hooks/useItems.js';
import { getScrollAncestor, floatEqual, commonProps, mergeProps, safeCall, isMenuOpen, getTransition, batchedUpdates } from '../utils/utils.js';
import { getScrollAncestor, commonProps, mergeProps, safeCall, isMenuOpen, getTransition, batchedUpdates } from '../utils/utils.js';
import { getPositionHelpers } from '../positionUtils/getPositionHelpers.js';
import { positionMenu } from '../positionUtils/positionMenu.js';
import { useLayoutEffect as useIsomorphicLayoutEffect } from '../hooks/useIsomorphicLayoutEffect.js';
import { getNormalizedClientRect } from '../positionUtils/getNormalizedClientRect.js';
import { useBEM } from '../hooks/useBEM.js';
import { useCombinedRef } from '../hooks/useCombinedRef.js';

Expand Down Expand Up @@ -72,11 +71,6 @@ const MenuList = ({
const focusRef = useRef();
const arrowRef = useRef();
const prevOpen = useRef(false);
const latestMenuSize = useRef({
width: 0,
height: 0
});
const latestHandlePosition = useRef(() => {});
const {
hoverItem,
dispatch,
Expand Down Expand Up @@ -167,34 +161,30 @@ const MenuList = ({
const {
menuRect
} = positionHelpers;
let menuHeight = menuRect.height;
const menuHeight = menuRect.height;
if (!noOverflowCheck && overflow !== 'visible') {
const {
getTopOverflow,
getBottomOverflow
} = positionHelpers;
let height, overflowAmt;
const prevHeight = latestMenuSize.current.height;
const bottomOverflow = getBottomOverflow(y);
if (bottomOverflow > 0 || floatEqual(bottomOverflow, 0) && floatEqual(menuHeight, prevHeight)) {
if (bottomOverflow > 0) {
height = menuHeight - bottomOverflow;
overflowAmt = bottomOverflow;
} else {
const topOverflow = getTopOverflow(y);
if (topOverflow < 0 || floatEqual(topOverflow, 0) && floatEqual(menuHeight, prevHeight)) {
if (topOverflow < 0) {
height = menuHeight + topOverflow;
overflowAmt = 0 - topOverflow;
if (height >= 0) y -= topOverflow;
}
}
if (height >= 0) {
menuHeight = height;
setOverflowData({
height,
overflowAmt
});
} else {
setOverflowData();
}
}
if (arrow) setArrowPosition({
Expand All @@ -206,18 +196,13 @@ const MenuList = ({
y
});
setExpandedDirection(computedDirection);
latestMenuSize.current = {
width: menuRect.width,
height: menuHeight
};
}, [arrow, align, boundingBoxPadding, direction, gap, shift, position, overflow, anchorPoint, anchorRef, containerRef, boundingBoxRef, rootMenuRef, scrollNodes]);
useIsomorphicLayoutEffect(() => {
if (isOpen) {
handlePosition();
if (prevOpen.current) forceReposSubmenu();
}
prevOpen.current = isOpen;
latestHandlePosition.current = handlePosition;
}, [isOpen, handlePosition, reposFlag]);
useIsomorphicLayoutEffect(() => {
if (overflowData && !setDownOverflow) menuRef.current.scrollTop = 0;
Expand Down Expand Up @@ -262,38 +247,28 @@ const MenuList = ({
return () => parentScroll.removeEventListener('scroll', handleScroll);
}, [isOpen, hasOverflow, parentScrollingRef, handlePosition]);
useEffect(() => {
if (typeof ResizeObserver !== 'function' || reposition === 'initial') return;
const resizeObserver = new ResizeObserver(([entry]) => {
const {
borderBoxSize,
target
} = entry;
let width, height;
if (borderBoxSize) {
const {
inlineSize,
blockSize
} = borderBoxSize[0] || borderBoxSize;
width = inlineSize;
height = blockSize;
if (!isOpen || typeof ResizeObserver !== 'function' || reposition === 'initial') return;
const targetList = [];
const resizeObserver = new ResizeObserver(entries => entries.forEach(({
target
}) => {
if (targetList.indexOf(target) < 0) {
targetList.push(target);
} else {
const borderRect = getNormalizedClientRect(target);
width = borderRect.width;
height = borderRect.height;
flushSync(() => {
handlePosition();
forceReposSubmenu();
});
}
if (width === 0 || height === 0) return;
if (floatEqual(width, latestMenuSize.current.width, 1) && floatEqual(height, latestMenuSize.current.height, 1)) return;
flushSync(() => {
latestHandlePosition.current();
forceReposSubmenu();
});
});
const observeTarget = menuRef.current;
resizeObserver.observe(observeTarget, {
}));
const resizeObserverOptions = {
box: 'border-box'
});
return () => resizeObserver.unobserve(observeTarget);
}, [reposition]);
};
resizeObserver.observe(menuRef.current, resizeObserverOptions);
const anchor = anchorRef == null ? void 0 : anchorRef.current;
anchor && resizeObserver.observe(anchor, resizeObserverOptions);
return () => resizeObserver.disconnect();
}, [isOpen, reposition, anchorRef, handlePosition]);
useEffect(() => {
if (!isOpen) {
dispatch(HoverActionTypes.RESET);
Expand Down
3 changes: 1 addition & 2 deletions dist/es/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { unstable_batchedUpdates } from 'react-dom';
const isMenuOpen = state => !!state && state[0] === 'o';
const batchedUpdates = unstable_batchedUpdates || (callback => callback());
const values = Object.values || (obj => Object.keys(obj).map(key => obj[key]));
const floatEqual = (a, b, diff = 0.0001) => Math.abs(a - b) < diff;
const getTransition = (transition, name) => transition === true || !!(transition && transition[name]);
const safeCall = (fn, arg) => typeof fn === 'function' ? fn(arg) : fn;
const internalKey = '_szhsinMenu';
Expand Down Expand Up @@ -68,4 +67,4 @@ function indexOfNode(nodeList, node) {
return -1;
}

export { batchedUpdates, commonProps, defineName, floatEqual, getName, getScrollAncestor, getTransition, indexOfNode, isMenuOpen, mergeProps, parsePadding, safeCall, values };
export { batchedUpdates, commonProps, defineName, getName, getScrollAncestor, getTransition, indexOfNode, isMenuOpen, mergeProps, parsePadding, safeCall, values };
69 changes: 22 additions & 47 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ const dummyItemProps = {
const isMenuOpen = state => !!state && state[0] === 'o';
const batchedUpdates = reactDom.unstable_batchedUpdates || (callback => callback());
const values = Object.values || (obj => Object.keys(obj).map(key => obj[key]));
const floatEqual = (a, b, diff = 0.0001) => Math.abs(a - b) < diff;
const getTransition = (transition, name) => transition === true || !!(transition && transition[name]);
const safeCall = (fn, arg) => typeof fn === 'function' ? fn(arg) : fn;
const internalKey = '_szhsinMenu';
Expand Down Expand Up @@ -935,11 +934,6 @@ const MenuList = ({
const focusRef = react.useRef();
const arrowRef = react.useRef();
const prevOpen = react.useRef(false);
const latestMenuSize = react.useRef({
width: 0,
height: 0
});
const latestHandlePosition = react.useRef(() => {});
const {
hoverItem,
dispatch,
Expand Down Expand Up @@ -1030,34 +1024,30 @@ const MenuList = ({
const {
menuRect
} = positionHelpers;
let menuHeight = menuRect.height;
const menuHeight = menuRect.height;
if (!noOverflowCheck && overflow !== 'visible') {
const {
getTopOverflow,
getBottomOverflow
} = positionHelpers;
let height, overflowAmt;
const prevHeight = latestMenuSize.current.height;
const bottomOverflow = getBottomOverflow(y);
if (bottomOverflow > 0 || floatEqual(bottomOverflow, 0) && floatEqual(menuHeight, prevHeight)) {
if (bottomOverflow > 0) {
height = menuHeight - bottomOverflow;
overflowAmt = bottomOverflow;
} else {
const topOverflow = getTopOverflow(y);
if (topOverflow < 0 || floatEqual(topOverflow, 0) && floatEqual(menuHeight, prevHeight)) {
if (topOverflow < 0) {
height = menuHeight + topOverflow;
overflowAmt = 0 - topOverflow;
if (height >= 0) y -= topOverflow;
}
}
if (height >= 0) {
menuHeight = height;
setOverflowData({
height,
overflowAmt
});
} else {
setOverflowData();
}
}
if (arrow) setArrowPosition({
Expand All @@ -1069,18 +1059,13 @@ const MenuList = ({
y
});
setExpandedDirection(computedDirection);
latestMenuSize.current = {
width: menuRect.width,
height: menuHeight
};
}, [arrow, align, boundingBoxPadding, direction, gap, shift, position, overflow, anchorPoint, anchorRef, containerRef, boundingBoxRef, rootMenuRef, scrollNodes]);
useIsomorphicLayoutEffect(() => {
if (isOpen) {
handlePosition();
if (prevOpen.current) forceReposSubmenu();
}
prevOpen.current = isOpen;
latestHandlePosition.current = handlePosition;
}, [isOpen, handlePosition, reposFlag]);
useIsomorphicLayoutEffect(() => {
if (overflowData && !setDownOverflow) menuRef.current.scrollTop = 0;
Expand Down Expand Up @@ -1125,38 +1110,28 @@ const MenuList = ({
return () => parentScroll.removeEventListener('scroll', handleScroll);
}, [isOpen, hasOverflow, parentScrollingRef, handlePosition]);
react.useEffect(() => {
if (typeof ResizeObserver !== 'function' || reposition === 'initial') return;
const resizeObserver = new ResizeObserver(([entry]) => {
const {
borderBoxSize,
target
} = entry;
let width, height;
if (borderBoxSize) {
const {
inlineSize,
blockSize
} = borderBoxSize[0] || borderBoxSize;
width = inlineSize;
height = blockSize;
if (!isOpen || typeof ResizeObserver !== 'function' || reposition === 'initial') return;
const targetList = [];
const resizeObserver = new ResizeObserver(entries => entries.forEach(({
target
}) => {
if (targetList.indexOf(target) < 0) {
targetList.push(target);
} else {
const borderRect = getNormalizedClientRect(target);
width = borderRect.width;
height = borderRect.height;
reactDom.flushSync(() => {
handlePosition();
forceReposSubmenu();
});
}
if (width === 0 || height === 0) return;
if (floatEqual(width, latestMenuSize.current.width, 1) && floatEqual(height, latestMenuSize.current.height, 1)) return;
reactDom.flushSync(() => {
latestHandlePosition.current();
forceReposSubmenu();
});
});
const observeTarget = menuRef.current;
resizeObserver.observe(observeTarget, {
}));
const resizeObserverOptions = {
box: 'border-box'
});
return () => resizeObserver.unobserve(observeTarget);
}, [reposition]);
};
resizeObserver.observe(menuRef.current, resizeObserverOptions);
const anchor = anchorRef == null ? void 0 : anchorRef.current;
anchor && resizeObserver.observe(anchor, resizeObserverOptions);
return () => resizeObserver.disconnect();
}, [isOpen, reposition, anchorRef, handlePosition]);
react.useEffect(() => {
if (!isOpen) {
dispatch(HoverActionTypes.RESET);
Expand Down
2 changes: 1 addition & 1 deletion example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion example/src/components/Example.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const Example = React.memo(
</div>

{sourceCode && (
<pre className={bem(blockName, 'source')}>
<pre key={sourceCode.length} className={bem(blockName, 'source')}>
<code className="lang-jsx">{sourceCode}</code>
</pre>
)}
Expand Down
6 changes: 3 additions & 3 deletions example/src/data/codeExamples.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const basicMenu = {

fullSource: `import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
import '@szhsin/react-menu/dist/transitions/slide.css';
import '@szhsin/react-menu/dist/transitions/zoom.css';
export default function Example() {
return (
Expand Down Expand Up @@ -955,7 +955,7 @@ export const menuStateHook = {
fullSource: `import { useRef } from 'react';
import { ControlledMenu, MenuItem, useClick, useMenuState } from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
import '@szhsin/react-menu/dist/transitions/slide.css';
import '@szhsin/react-menu/dist/transitions/zoom.css';
export default function () {
const ref = useRef(null);
Expand Down Expand Up @@ -1005,7 +1005,7 @@ export const hoverMenu = {
fullSource: `import { useRef, useState } from 'react';
import { ControlledMenu, MenuItem, useHover, useMenuState } from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
import '@szhsin/react-menu/dist/transitions/slide.css';
import '@szhsin/react-menu/dist/transitions/zoom.css';
const HoverMenu = () => {
const ref = useRef(null);
Expand Down
4 changes: 2 additions & 2 deletions example/src/data/documentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,8 @@ const rootMenuProps = [
want to explicitly reposition menu using the <code>repositionFlag</code> prop.
</li>
<li>
<code>'auto'</code> Reposition menu whenever its size has changed, using the{' '}
<code>ResizeObserver</code> API.
<code>'auto'</code> Reposition menu whenever itself or the anchor has changed in size,
using the <code>ResizeObserver</code> API.
</li>
</ul>
</>
Expand Down
4 changes: 2 additions & 2 deletions example/src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useEffect, useLayoutEffect } from 'react';
import { useTheme } from '../store';

export const version = '4.2.0';
export const build = '135';
export const version = '4.2.1';
export const build = '136';

export const bem = (block, element, modifiers = {}) => {
let blockElement = element ? `${block}__${element}` : block;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@szhsin/react-menu",
"version": "4.2.0",
"version": "4.2.1",
"description": "React component for building accessible menu, dropdown, submenu, context menu and more.",
"author": "Zheng Song",
"license": "MIT",
Expand Down
Loading

0 comments on commit 59ee65e

Please sign in to comment.