diff --git a/packages/action-button/src/ActionButton.ts b/packages/action-button/src/ActionButton.ts index 95c693dcd7..bf9f750f12 100644 --- a/packages/action-button/src/ActionButton.ts +++ b/packages/action-button/src/ActionButton.ts @@ -116,7 +116,6 @@ export class ActionButton extends SizedMixin(ButtonBase, { constructor() { super(); this.addEventListener('click', this.onClick); - this.addEventListener('pointerdown', this.onPointerdown); } private onClick = (): void => { @@ -134,10 +133,13 @@ export class ActionButton extends SizedMixin(ButtonBase, { } }; - private onPointerdown(event: PointerEvent): void { + private handlePointerdownHoldAffordance(event: PointerEvent): void { if (event.button !== 0) return; - this.addEventListener('pointerup', this.onPointerup); - this.addEventListener('pointercancel', this.onPointerup); + this.addEventListener('pointerup', this.handlePointerupHoldAffordance); + this.addEventListener( + 'pointercancel', + this.handlePointerupHoldAffordance + ); LONGPRESS_TIMEOUT = setTimeout(() => { this.dispatchEvent( new CustomEvent('longpress', { @@ -151,10 +153,16 @@ export class ActionButton extends SizedMixin(ButtonBase, { }, LONGPRESS_DURATION); } - private onPointerup(): void { + private handlePointerupHoldAffordance(): void { clearTimeout(LONGPRESS_TIMEOUT); - this.removeEventListener('pointerup', this.onPointerup); - this.removeEventListener('pointercancel', this.onPointerup); + this.removeEventListener( + 'pointerup', + this.handlePointerupHoldAffordance + ); + this.removeEventListener( + 'pointercancel', + this.handlePointerupHoldAffordance + ); } /** @@ -258,6 +266,20 @@ export class ActionButton extends SizedMixin(ButtonBase, { ); } } + if (changes.has('holdAffordance')) { + if (this.holdAffordance) { + this.addEventListener( + 'pointerdown', + this.handlePointerdownHoldAffordance + ); + } else { + this.removeEventListener( + 'pointerdown', + this.handlePointerdownHoldAffordance + ); + this.handlePointerupHoldAffordance(); + } + } } } diff --git a/packages/button/src/ButtonBase.ts b/packages/button/src/ButtonBase.ts index 4c11842909..eca960a163 100644 --- a/packages/button/src/ButtonBase.ts +++ b/packages/button/src/ButtonBase.ts @@ -30,7 +30,7 @@ import buttonStyles from './button-base.css.js'; * @slot icon - icon element(s) to display at the start of the button */ export class ButtonBase extends ObserveSlotText(LikeAnchor(Focusable), '', [ - 'sp-tooltip', + 'sp-overlay,sp-tooltip', ]) { public static override get styles(): CSSResultArray { return [buttonStyles]; diff --git a/packages/button/src/button-base.css b/packages/button/src/button-base.css index 66dcab4f2d..79d5953573 100644 --- a/packages/button/src/button-base.css +++ b/packages/button/src/button-base.css @@ -35,6 +35,7 @@ governing permissions and limitations under the License. inset: 0; } +::slotted(sp-overlay), ::slotted(sp-tooltip) { position: absolute; } diff --git a/packages/overlay/src/Overlay.ts b/packages/overlay/src/Overlay.ts index b66d2ab46c..09ac260c4c 100644 --- a/packages/overlay/src/Overlay.ts +++ b/packages/overlay/src/Overlay.ts @@ -806,6 +806,7 @@ export class Overlay extends OverlayFeatures { return; } this.open = true; + this.placementController.allowPlacementUpdate = true; this.manageOpen(false); } diff --git a/packages/overlay/src/PlacementController.ts b/packages/overlay/src/PlacementController.ts index 548f8fa78b..c2af9d65c8 100644 --- a/packages/overlay/src/PlacementController.ts +++ b/packages/overlay/src/PlacementController.ts @@ -130,12 +130,19 @@ export class PlacementController implements ReactiveController { }; } + allowPlacementUpdate = false; + updatePlacement = (): void => { - if (this.options.type !== 'modal' && this.cleanup) { + if ( + !this.allowPlacementUpdate && + this.options.type !== 'modal' && + this.cleanup + ) { this.target.dispatchEvent(new Event('close', { bubbles: true })); return; } this.computePlacement(); + this.allowPlacementUpdate = false; }; async computePlacement(): Promise { diff --git a/packages/overlay/src/topLayerOverTransforms.ts b/packages/overlay/src/topLayerOverTransforms.ts index 03799faad1..5e2c763bcc 100644 --- a/packages/overlay/src/topLayerOverTransforms.ts +++ b/packages/overlay/src/topLayerOverTransforms.ts @@ -10,7 +10,12 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ import type { Middleware, MiddlewareState } from '@floating-ui/dom'; -import { getContainingBlock, getWindow } from '@floating-ui/utils/dom'; +import { + getContainingBlock, + getWindow, + isContainingBlock, +} from '@floating-ui/utils/dom'; +import { VirtualTrigger } from './VirtualTrigger.js'; export const topLayerOverTransforms = (): Middleware => ({ name: 'topLayer', @@ -22,6 +27,7 @@ export const topLayerOverTransforms = (): Middleware => ({ } = middlewareArguments; let onTopLayer = false; let topLayerIsFloating = false; + let withinReference = false; const diffCoords = { x: 0, y: 0, @@ -40,45 +46,47 @@ export const topLayerOverTransforms = (): Middleware => ({ /* c8 ignore next 3 */ } catch (error) {} topLayerIsFloating = onTopLayer; - if (!onTopLayer) { - const dialogAncestorQueryEvent = new Event( - 'floating-ui-dialog-test', - { composed: true, bubbles: true } - ); - floating.addEventListener( - 'floating-ui-dialog-test', - (event: Event) => { - (event.composedPath() as unknown as Element[]).forEach( - (el) => { - if (el === floating || el.localName !== 'dialog') - return; - try { - onTopLayer = onTopLayer || el.matches(':modal'); - // eslint-disable-next-line no-empty - /* c8 ignore next */ - } catch (error) {} - } - ); - }, - { once: true } - ); - floating.dispatchEvent(dialogAncestorQueryEvent); - } + const dialogAncestorQueryEvent = new Event('floating-ui-dialog-test', { + composed: true, + bubbles: true, + }); + floating.addEventListener( + 'floating-ui-dialog-test', + (event: Event) => { + (event.composedPath() as unknown as Element[]).forEach((el) => { + withinReference = withinReference || el === reference; + if (el === floating || el.localName !== 'dialog') return; + try { + onTopLayer = onTopLayer || el.matches(':modal'); + // eslint-disable-next-line no-empty + /* c8 ignore next */ + } catch (error) {} + }); + }, + { once: true } + ); + floating.dispatchEvent(dialogAncestorQueryEvent); let overTransforms = false; - const containingBlock = getContainingBlock(reference as Element); - if ( - containingBlock !== null && - getWindow(containingBlock) !== - (containingBlock as unknown as Window) - ) { - const css = getComputedStyle(containingBlock); - overTransforms = css.transform !== 'none'; - } + if (!(reference instanceof VirtualTrigger)) { + const containingBlock = isContainingBlock(reference as Element) + ? (reference as Element) + : getContainingBlock(reference as Element); + if ( + containingBlock !== null && + getWindow(containingBlock) !== + (containingBlock as unknown as Window) + ) { + const css = getComputedStyle(containingBlock); + overTransforms = + withinReference && + (css.transform !== 'none' || css.filter !== 'none'); + } - if (onTopLayer && overTransforms && containingBlock) { - const rect = containingBlock.getBoundingClientRect(); - diffCoords.x = rect.x; - diffCoords.y = rect.y; + if (onTopLayer && overTransforms && containingBlock) { + const rect = containingBlock.getBoundingClientRect(); + diffCoords.x = rect.x; + diffCoords.y = rect.y; + } } if (onTopLayer && topLayerIsFloating) { diff --git a/packages/overlay/stories/overlay-element.stories.ts b/packages/overlay/stories/overlay-element.stories.ts index 1efabea539..ed271a5fc9 100644 --- a/packages/overlay/stories/overlay-element.stories.ts +++ b/packages/overlay/stories/overlay-element.stories.ts @@ -272,3 +272,126 @@ export const actionGroup = ({ delayed }: Properties): TemplateResult => { `; }; + +export const actionGroupWithFilters = ({ + delayed, +}: Properties): TemplateResult => { + const popoverOffset = [6, -13] as [number, number]; + return html` + + + + + + + Hover + + + + + + + + + + + + + + + + + + + + + + + + Hover + + + + + + Hover + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `; +};