From 09773f1fd14362c3341b949e68c2dfc1741acf6d Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 3 Aug 2022 18:40:52 +0200 Subject: [PATCH 1/9] Extract placement utilities --- packages/components/src/popover/index.tsx | 17 ++++++++-------- packages/components/src/popover/utils.ts | 24 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 1e9060b9a42356..70402790569a27 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -57,6 +57,8 @@ import { placementToMotionAnimationProps, getReferenceOwnerDocument, getReferenceElement, + isTopBottomPlacement, + hasBeforePlacement, } from './utils'; import type { WordPressComponentProps } from '../ui/context'; import type { @@ -293,22 +295,19 @@ const UnforwardedPopover = ( return offsetRef.current; } - const isTopBottomPlacement = - currentPlacement.includes( 'top' ) || - currentPlacement.includes( 'bottom' ); - // The main axis should represent the gap between the // floating element and the reference element. The cross // axis is always perpendicular to the main axis. - const mainAxis = isTopBottomPlacement ? 'y' : 'x'; + const mainAxis = isTopBottomPlacement( currentPlacement ) + ? 'y' + : 'x'; const crossAxis = mainAxis === 'x' ? 'y' : 'x'; // When the popover is before the reference, subtract the offset, // of the main axis else add it. - const hasBeforePlacement = - currentPlacement.includes( 'top' ) || - currentPlacement.includes( 'left' ); - const mainAxisModifier = hasBeforePlacement ? -1 : 1; + const mainAxisModifier = hasBeforePlacement( currentPlacement ) + ? -1 + : 1; return { mainAxis: diff --git a/packages/components/src/popover/utils.ts b/packages/components/src/popover/utils.ts index c5b15a076114fc..09951712f48a23 100644 --- a/packages/components/src/popover/utils.ts +++ b/packages/components/src/popover/utils.ts @@ -240,3 +240,27 @@ export const getReferenceElement = ( { // Convert any `undefined` value to `null`. return referenceElement ?? null; }; + +/** + * Checks the placement for a top/bottom value. + * + * @param placement + * @return Whether the placement is top or bottom + */ +export const isTopBottomPlacement = ( + placement: NonNullable< PopoverProps[ 'placement' ] > +) => + placement.trim().startsWith( 'top' ) || + placement.trim().startsWith( 'bottom' ); + +/** + * Checks the placement for a top/left value. + * + * @param placement + * @return Whether the placement is top or left + */ +export const hasBeforePlacement = ( + placement: NonNullable< PopoverProps[ 'placement' ] > +) => + placement.trim().startsWith( 'top' ) || + placement.trim().startsWith( 'left' ); From cf718899a7243b72c48bcb8cfbaa49b649388c03 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 4 Aug 2022 00:03:50 +0200 Subject: [PATCH 2/9] Add offset to limitShift functionality to compensate iframe s offset --- packages/components/src/popover/index.tsx | 37 ++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 70402790569a27..81f3f685478cc9 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -338,7 +338,42 @@ const UnforwardedPopover = ( shouldShift ? shiftMiddleware( { crossAxis: true, - limiter: limitShift(), + limiter: limitShift( { + offset: ( { placement: currentPlacement } ) => { + // The following calculations are aimed at allowing the floating + // element to shift fully below the reference element, when the + // reference element is in a different document (i.e. an iFrame). + if ( + referenceOwnerDocument === document || + frameOffsetRef.current === undefined + ) { + return 0; + } + + // The main axis (according to floating UI's docs) is the "x" axis + // for 'top' and 'bottom' placements, and the "y" axis for 'left' + // and 'right' placements. + const mainAxis = isTopBottomPlacement( + currentPlacement + ) + ? 'x' + : 'y'; + const crossAxis = mainAxis === 'x' ? 'y' : 'x'; + + const crossAxisModifier = hasBeforePlacement( + currentPlacement + ) + ? -1 + : 1; + + return { + mainAxis: -frameOffsetRef.current[ mainAxis ], + crossAxis: + crossAxisModifier * + frameOffsetRef.current[ crossAxis ], + }; + }, + } ), padding: 1, // Necessary to avoid flickering at the edge of the viewport. } ) : undefined, From 61da3a0bd2be4fd2263ac2e9f813b7d3d945fc69 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 4 Aug 2022 10:20:35 +0200 Subject: [PATCH 3/9] CHANGELOG --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 30311411bb979a..d9513724e240df 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,6 +12,7 @@ - `Button`: Remove unexpected `has-text` class when empty children are passed ([#44198](https://github.com/WordPress/gutenberg/pull/44198)). - The `LinkedButton` to unlink sides in `BoxControl`, `BorderBoxControl` and `BorderRadiusControl` have changed from a rectangular primary button to an icon-only button, with a sentence case tooltip, and default-size icon for better legibility. The `Button` component has been fixed so when `isSmall` and `icon` props are set, and no text is present, the button shape is square rather than rectangular. +- `Popover`: fix shift limit when anchor is inside an iframe [#42950](https://github.com/WordPress/gutenberg/pull/42950)). ### Internal From 5f67818ee8910ab5623dd0ef759c1ac6654959f5 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 24 Aug 2022 17:08:15 +0200 Subject: [PATCH 4/9] Custom limitShift implementation --- packages/components/src/popover/index.tsx | 39 +--- .../components/src/popover/limit-shift.ts | 166 ++++++++++++++++++ 2 files changed, 168 insertions(+), 37 deletions(-) create mode 100644 packages/components/src/popover/limit-shift.ts diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index 81f3f685478cc9..ee4aed5b61c1b7 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -10,7 +10,6 @@ import { autoUpdate, arrow, offset as offsetMiddleware, - limitShift, size, Middleware, } from '@floating-ui/react-dom'; @@ -67,6 +66,7 @@ import type { PopoverAnchorRefReference, PopoverAnchorRefTopBottom, } from './types'; +import { limitShift as customLimitShift } from './limit-shift'; /** * Name of slot in which popover should fill. @@ -338,42 +338,7 @@ const UnforwardedPopover = ( shouldShift ? shiftMiddleware( { crossAxis: true, - limiter: limitShift( { - offset: ( { placement: currentPlacement } ) => { - // The following calculations are aimed at allowing the floating - // element to shift fully below the reference element, when the - // reference element is in a different document (i.e. an iFrame). - if ( - referenceOwnerDocument === document || - frameOffsetRef.current === undefined - ) { - return 0; - } - - // The main axis (according to floating UI's docs) is the "x" axis - // for 'top' and 'bottom' placements, and the "y" axis for 'left' - // and 'right' placements. - const mainAxis = isTopBottomPlacement( - currentPlacement - ) - ? 'x' - : 'y'; - const crossAxis = mainAxis === 'x' ? 'y' : 'x'; - - const crossAxisModifier = hasBeforePlacement( - currentPlacement - ) - ? -1 - : 1; - - return { - mainAxis: -frameOffsetRef.current[ mainAxis ], - crossAxis: - crossAxisModifier * - frameOffsetRef.current[ crossAxis ], - }; - }, - } ), + limiter: customLimitShift(), padding: 1, // Necessary to avoid flickering at the edge of the viewport. } ) : undefined, diff --git a/packages/components/src/popover/limit-shift.ts b/packages/components/src/popover/limit-shift.ts new file mode 100644 index 00000000000000..5f7fd9fb4ede53 --- /dev/null +++ b/packages/components/src/popover/limit-shift.ts @@ -0,0 +1,166 @@ +/** + * External dependencies + */ +import type { + Axis, + Coords, + Placement, + Side, + MiddlewareArguments, +} from '@floating-ui/react-dom'; + +/** + * Temporary re-implementation of the `limitShift` function from `@floating-ui` + * which, compared to the original function, also takes into account the offset + * from the offset middleware on the main axis. + * + * All unexported types and functions are also from the `@floating-ui` library, + * and have been copied to this file for convenience. + */ + +type LimitShiftOffset = + | ( ( args: MiddlewareArguments ) => + | number + | { + /** + * Offset the limiting of the axis that runs along the alignment of the + * floating element. + */ + mainAxis?: number; + /** + * Offset the limiting of the axis that runs along the side of the + * floating element. + */ + crossAxis?: number; + } ) + | number + | { + /** + * Offset the limiting of the axis that runs along the alignment of the + * floating element. + */ + mainAxis?: number; + /** + * Offset the limiting of the axis that runs along the side of the + * floating element. + */ + crossAxis?: number; + }; + +type LimitShiftOptions = { + /** + * Offset when limiting starts. `0` will limit when the opposite edges of the + * reference and floating elements are aligned. + * - positive = start limiting earlier + * - negative = start limiting later + */ + offset: LimitShiftOffset; + /** + * Whether to limit the axis that runs along the alignment of the floating + * element. + */ + mainAxis: boolean; + /** + * Whether to limit the axis that runs along the side of the floating element. + */ + crossAxis: boolean; +}; + +function getSide( placement: Placement ): Side { + return placement.split( '-' )[ 0 ] as Side; +} + +function getMainAxisFromPlacement( placement: Placement ): Axis { + return [ 'top', 'bottom' ].includes( getSide( placement ) ) ? 'x' : 'y'; +} + +function getCrossAxis( axis: Axis ): Axis { + return axis === 'x' ? 'y' : 'x'; +} + +export const limitShift = ( + options: Partial< LimitShiftOptions > = {} +): { + options: Partial< LimitShiftOffset >; + fn: ( middlewareArguments: MiddlewareArguments ) => Coords; +} => ( { + options, + fn( middlewareArguments ) { + const { x, y, placement, rects, middlewareData } = middlewareArguments; + const { + offset = 0, + mainAxis: checkMainAxis = true, + crossAxis: checkCrossAxis = true, + } = options; + + const coords = { x, y }; + const mainAxis = getMainAxisFromPlacement( placement ); + const crossAxis = getCrossAxis( mainAxis ); + + let mainAxisCoord = coords[ mainAxis ]; + let crossAxisCoord = coords[ crossAxis ]; + + const rawOffset = + typeof offset === 'function' + ? offset( middlewareArguments ) + : offset; + const computedOffset = + typeof rawOffset === 'number' + ? { mainAxis: rawOffset, crossAxis: 0 } + : { mainAxis: 0, crossAxis: 0, ...rawOffset }; + + if ( checkMainAxis ) { + const len = mainAxis === 'y' ? 'height' : 'width'; + const limitMin = + rects.reference[ mainAxis ] - + rects.floating[ len ] + + computedOffset.mainAxis + + // Note: the original function doesn't add the main axis offset. + ( middlewareData.offset?.[ mainAxis ] ?? 0 ); + const limitMax = + rects.reference[ mainAxis ] + + rects.reference[ len ] - + computedOffset.mainAxis + + // Note: the original function doesn't add the main axis offset. + ( middlewareData.offset?.[ mainAxis ] ?? 0 ); + + if ( mainAxisCoord < limitMin ) { + mainAxisCoord = limitMin; + } else if ( mainAxisCoord > limitMax ) { + mainAxisCoord = limitMax; + } + } + + if ( checkCrossAxis ) { + const len = mainAxis === 'y' ? 'width' : 'height'; + const isOriginSide = [ 'top', 'left' ].includes( + getSide( placement ) + ); + const limitMin = + rects.reference[ crossAxis ] - + rects.floating[ len ] + + // Note: the original function only adds the cross axis offset here + // when `isOriginSide === true`. + ( middlewareData.offset?.[ crossAxis ] ?? 0 ) + + ( isOriginSide ? 0 : computedOffset.crossAxis ); + const limitMax = + rects.reference[ crossAxis ] + + rects.reference[ len ] + + // Note: the original function only adds the cross axis offset here + // when `isOriginSide === false`. + ( middlewareData.offset?.[ crossAxis ] ?? 0 ) - + ( isOriginSide ? computedOffset.crossAxis : 0 ); + + if ( crossAxisCoord < limitMin ) { + crossAxisCoord = limitMin; + } else if ( crossAxisCoord > limitMax ) { + crossAxisCoord = limitMax; + } + } + + return { + [ mainAxis ]: mainAxisCoord, + [ crossAxis ]: crossAxisCoord, + } as Coords; + }, +} ); From fce608e331cc8c44ad441639f2e001cc37d6e964 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 25 Aug 2022 09:15:43 +0200 Subject: [PATCH 5/9] Add offset to the iframe via a custom middleware --- packages/components/src/popover/index.tsx | 52 +++++++++---------- .../components/src/popover/limit-shift.ts | 42 +++++++++------ 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index ee4aed5b61c1b7..f57be975690787 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -12,6 +12,7 @@ import { offset as offsetMiddleware, size, Middleware, + MiddlewareArguments, } from '@floating-ui/react-dom'; // eslint-disable-next-line no-restricted-imports import { @@ -56,8 +57,6 @@ import { placementToMotionAnimationProps, getReferenceOwnerDocument, getReferenceElement, - isTopBottomPlacement, - hasBeforePlacement, } from './utils'; import type { WordPressComponentProps } from '../ui/context'; import type { @@ -290,32 +289,29 @@ const UnforwardedPopover = ( const offsetRef = useRef( offsetProp ); const middleware = [ - offsetMiddleware( ( { placement: currentPlacement } ) => { - if ( ! frameOffsetRef.current ) { - return offsetRef.current; - } - - // The main axis should represent the gap between the - // floating element and the reference element. The cross - // axis is always perpendicular to the main axis. - const mainAxis = isTopBottomPlacement( currentPlacement ) - ? 'y' - : 'x'; - const crossAxis = mainAxis === 'x' ? 'y' : 'x'; - - // When the popover is before the reference, subtract the offset, - // of the main axis else add it. - const mainAxisModifier = hasBeforePlacement( currentPlacement ) - ? -1 - : 1; - - return { - mainAxis: - offsetRef.current + - frameOffsetRef.current[ mainAxis ] * mainAxisModifier, - crossAxis: frameOffsetRef.current[ crossAxis ], - }; - } ), + // Custom middleware which adjusts the popover's position by taking into + // account the offset of the anchor's iframe (if any) compared to the page. + { + name: 'frameOffset', + fn( { x, y }: MiddlewareArguments ) { + if ( ! frameOffsetRef.current ) { + return { + x, + y, + }; + } + + return { + x: x + frameOffsetRef.current.x, + y: y + frameOffsetRef.current.y, + data: { + // This will be used in the customLimitShift() function. + amount: frameOffsetRef.current, + }, + }; + }, + }, + offsetMiddleware( offsetProp ), computedFlipProp ? flipMiddleware() : undefined, computedResizeProp ? size( { diff --git a/packages/components/src/popover/limit-shift.ts b/packages/components/src/popover/limit-shift.ts index 5f7fd9fb4ede53..91cd2be384dcba 100644 --- a/packages/components/src/popover/limit-shift.ts +++ b/packages/components/src/popover/limit-shift.ts @@ -10,9 +10,11 @@ import type { } from '@floating-ui/react-dom'; /** - * Temporary re-implementation of the `limitShift` function from `@floating-ui` - * which, compared to the original function, also takes into account the offset - * from the offset middleware on the main axis. + * Custom limiter function for the `shift` middleware. + * This function is mostly identical default `limitShift` from ``@floating-ui`; + * the only difference is that, when computing the min/max shift limits, it + * also takes into account the iframe offset that is added by the + * custom "frameOffset" middleware. * * All unexported types and functions are also from the `@floating-ui` library, * and have been copied to this file for convenience. @@ -109,20 +111,28 @@ export const limitShift = ( ? { mainAxis: rawOffset, crossAxis: 0 } : { mainAxis: 0, crossAxis: 0, ...rawOffset }; + // At the moment of writing, this is the only difference + // with the `limitShift` function from `@floating-ui`. + // This offset needs to be added to all min/max limits + // in order to make the shift-limiting work as expected. + const additionalFrameOffset = { + x: 0, + y: 0, + ...middlewareData.frameOffset?.amount, + }; + if ( checkMainAxis ) { const len = mainAxis === 'y' ? 'height' : 'width'; const limitMin = rects.reference[ mainAxis ] - rects.floating[ len ] + computedOffset.mainAxis + - // Note: the original function doesn't add the main axis offset. - ( middlewareData.offset?.[ mainAxis ] ?? 0 ); + additionalFrameOffset[ mainAxis ]; const limitMax = rects.reference[ mainAxis ] + rects.reference[ len ] - computedOffset.mainAxis + - // Note: the original function doesn't add the main axis offset. - ( middlewareData.offset?.[ mainAxis ] ?? 0 ); + additionalFrameOffset[ mainAxis ]; if ( mainAxisCoord < limitMin ) { mainAxisCoord = limitMin; @@ -139,17 +149,19 @@ export const limitShift = ( const limitMin = rects.reference[ crossAxis ] - rects.floating[ len ] + - // Note: the original function only adds the cross axis offset here - // when `isOriginSide === true`. - ( middlewareData.offset?.[ crossAxis ] ?? 0 ) + - ( isOriginSide ? 0 : computedOffset.crossAxis ); + ( isOriginSide + ? middlewareData.offset?.[ crossAxis ] ?? 0 + : 0 ) + + ( isOriginSide ? 0 : computedOffset.crossAxis ) + + additionalFrameOffset[ crossAxis ]; const limitMax = rects.reference[ crossAxis ] + rects.reference[ len ] + - // Note: the original function only adds the cross axis offset here - // when `isOriginSide === false`. - ( middlewareData.offset?.[ crossAxis ] ?? 0 ) - - ( isOriginSide ? computedOffset.crossAxis : 0 ); + ( isOriginSide + ? 0 + : middlewareData.offset?.[ crossAxis ] ?? 0 ) - + ( isOriginSide ? computedOffset.crossAxis : 0 ) + + additionalFrameOffset[ crossAxis ]; if ( crossAxisCoord < limitMin ) { crossAxisCoord = limitMin; From d0db89a5ab4791e925e700b969df4f02274d9d6f Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 25 Aug 2022 09:19:37 +0200 Subject: [PATCH 6/9] Remove unused utilities --- packages/components/src/popover/utils.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/packages/components/src/popover/utils.ts b/packages/components/src/popover/utils.ts index 09951712f48a23..c5b15a076114fc 100644 --- a/packages/components/src/popover/utils.ts +++ b/packages/components/src/popover/utils.ts @@ -240,27 +240,3 @@ export const getReferenceElement = ( { // Convert any `undefined` value to `null`. return referenceElement ?? null; }; - -/** - * Checks the placement for a top/bottom value. - * - * @param placement - * @return Whether the placement is top or bottom - */ -export const isTopBottomPlacement = ( - placement: NonNullable< PopoverProps[ 'placement' ] > -) => - placement.trim().startsWith( 'top' ) || - placement.trim().startsWith( 'bottom' ); - -/** - * Checks the placement for a top/left value. - * - * @param placement - * @return Whether the placement is top or left - */ -export const hasBeforePlacement = ( - placement: NonNullable< PopoverProps[ 'placement' ] > -) => - placement.trim().startsWith( 'top' ) || - placement.trim().startsWith( 'left' ); From c12aba44b5a20fd65eb2e337c6d70f5bf17faa6a Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 25 Aug 2022 10:18:28 +0200 Subject: [PATCH 7/9] Remove unnecessary offset ref --- packages/components/src/popover/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/components/src/popover/index.tsx b/packages/components/src/popover/index.tsx index f57be975690787..25bb157247a361 100644 --- a/packages/components/src/popover/index.tsx +++ b/packages/components/src/popover/index.tsx @@ -34,7 +34,6 @@ import { useMemo, useState, useCallback, - useEffect, } from '@wordpress/element'; import { useViewportMatch, @@ -282,11 +281,6 @@ const UnforwardedPopover = ( * https://floating-ui.com/docs/react-dom#variables-inside-middleware-functions. */ const frameOffsetRef = useRef( getFrameOffset( referenceOwnerDocument ) ); - /** - * Store the offset prop in a ref, due to constraints with floating-ui: - * https://floating-ui.com/docs/react-dom#variables-inside-middleware-functions. - */ - const offsetRef = useRef( offsetProp ); const middleware = [ // Custom middleware which adjusts the popover's position by taking into @@ -390,11 +384,6 @@ const UnforwardedPopover = ( } ), } ); - useEffect( () => { - offsetRef.current = offsetProp; - update(); - }, [ offsetProp, update ] ); - const arrowCallbackRef = useCallback( ( node ) => { arrowRef.current = node; From 6dc52d78e96cd46225ef7d619f6b5c9a8cc81e1e Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 25 Aug 2022 10:20:34 +0200 Subject: [PATCH 8/9] Update CHANGELOG --- packages/components/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index d9513724e240df..bfb0e141c69402 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -12,7 +12,7 @@ - `Button`: Remove unexpected `has-text` class when empty children are passed ([#44198](https://github.com/WordPress/gutenberg/pull/44198)). - The `LinkedButton` to unlink sides in `BoxControl`, `BorderBoxControl` and `BorderRadiusControl` have changed from a rectangular primary button to an icon-only button, with a sentence case tooltip, and default-size icon for better legibility. The `Button` component has been fixed so when `isSmall` and `icon` props are set, and no text is present, the button shape is square rather than rectangular. -- `Popover`: fix shift limit when anchor is inside an iframe [#42950](https://github.com/WordPress/gutenberg/pull/42950)). +- `Popover`: fix limitShift logic by adding iframe offset correctly [#42950](https://github.com/WordPress/gutenberg/pull/42950)). ### Internal From 8d01dbf016741f9efa9ebd47757449e9d116ffaf Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 21 Sep 2022 19:16:55 +0200 Subject: [PATCH 9/9] Add reference to floating-ui s MIT license --- .../components/src/popover/limit-shift.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/components/src/popover/limit-shift.ts b/packages/components/src/popover/limit-shift.ts index 91cd2be384dcba..45e65a0b619098 100644 --- a/packages/components/src/popover/limit-shift.ts +++ b/packages/components/src/popover/limit-shift.ts @@ -9,6 +9,33 @@ import type { MiddlewareArguments, } from '@floating-ui/react-dom'; +/** + * Parts of this source were derived and modified from `floating-ui`, + * released under the MIT license. + * + * https://github.com/floating-ui/floating-ui + * + * Copyright (c) 2021 Floating UI contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + /** * Custom limiter function for the `shift` middleware. * This function is mostly identical default `limitShift` from ``@floating-ui`;