mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
Drop hoist from public API, use Popover API where supported (#351)
* First stab at using the Popover API for PE * fix popup * First stab at using the Popover API for PE * fix popup * Prettier * Fix TS error * Remove workaround * Default to `strategy = fixed` if Popover API is not supported * Clear out default UA styles for popovers * Kill `hoist` with fire 🔥 * Refactor * Update `@floating-ui/dom` * Fix flipping and shofting * Fix autosize * Use `defaultBoundary` for `flip` too That way we get the previous behavior for it. * Remove `strategy`, just use `SUPPORTS_POPOVER` check where relevant * Remove uses of `strategy` * Use viewport as the default boundary for shifting and autosizing and add `boundary=scroll` to override --------- Co-authored-by: konnorrogers <konnor5456@gmail.com>
This commit is contained in:
@@ -210,12 +210,6 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
/** Disables the color picker. */
|
||||
@property({ type: Boolean }) disabled = false;
|
||||
|
||||
/**
|
||||
* Enable this option to prevent the panel from being clipped when the component is placed inside a container with
|
||||
* `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.
|
||||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
/** Shows the opacity slider. Enabling this will cause the formatted value to be HEXA, RGBA, or HSLA. */
|
||||
@property({ type: Boolean }) opacity = false;
|
||||
|
||||
@@ -1097,7 +1091,6 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
aria-disabled=${this.disabled ? 'true' : 'false'}
|
||||
.containingElement=${this}
|
||||
?disabled=${this.disabled}
|
||||
?hoist=${this.hoist}
|
||||
@wa-after-show=${this.handleAfterShow}
|
||||
@wa-after-hide=${this.handleAfterHide}
|
||||
>
|
||||
|
||||
@@ -97,13 +97,6 @@ export default class WaCopyButton extends WebAwesomeElement {
|
||||
/** The preferred placement of the tooltip. */
|
||||
@property({ attribute: 'tooltip-placement' }) tooltipPlacement: 'top' | 'right' | 'bottom' | 'left' = 'top';
|
||||
|
||||
/**
|
||||
* Enable this option to prevent the tooltip from being clipped when the component is placed inside a container with
|
||||
* `overflow: auto|hidden|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all,
|
||||
* scenarios.
|
||||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
private async handleCopy() {
|
||||
if (this.disabled || this.isCopying) {
|
||||
return;
|
||||
@@ -220,7 +213,6 @@ export default class WaCopyButton extends WebAwesomeElement {
|
||||
for="copy-button"
|
||||
placement=${this.tooltipPlacement}
|
||||
?disabled=${this.disabled}
|
||||
?hoist=${this.hoist}
|
||||
exportparts="
|
||||
base:tooltip__base,
|
||||
base__popup:tooltip__base__popup,
|
||||
|
||||
@@ -95,12 +95,6 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
/** The distance in pixels from which to offset the panel along its trigger. */
|
||||
@property({ type: Number }) skidding = 0;
|
||||
|
||||
/**
|
||||
* Enable this option to prevent the panel from being clipped when the component is placed inside a container with
|
||||
* `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.
|
||||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
/**
|
||||
* Syncs the popup width or height to that of the trigger element.
|
||||
*/
|
||||
@@ -415,7 +409,6 @@ export default class WaDropdown extends WebAwesomeElement {
|
||||
placement=${this.placement}
|
||||
distance=${this.distance}
|
||||
skidding=${this.skidding}
|
||||
strategy=${this.hoist ? 'fixed' : 'absolute'}
|
||||
flip
|
||||
shift
|
||||
auto-size="vertical"
|
||||
|
||||
@@ -275,7 +275,6 @@ export class SubmenuController implements ReactiveController {
|
||||
flip
|
||||
flip-fallback-strategy="best-fit"
|
||||
skidding="${this.skidding}"
|
||||
strategy="fixed"
|
||||
auto-size="vertical"
|
||||
auto-size-padding="10"
|
||||
>
|
||||
|
||||
@@ -19,6 +19,19 @@
|
||||
isolation: isolate;
|
||||
max-width: var(--auto-size-available-width, none);
|
||||
max-height: var(--auto-size-available-height, none);
|
||||
|
||||
/* Clear UA styles for [popover] */
|
||||
:where(&) {
|
||||
inset: unset;
|
||||
padding: unset;
|
||||
margin: unset;
|
||||
width: unset;
|
||||
height: unset;
|
||||
color: unset;
|
||||
background: unset;
|
||||
border: unset;
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.popup--fixed {
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, platform, shift, size } from '@floating-ui/dom';
|
||||
import {
|
||||
arrow,
|
||||
autoUpdate,
|
||||
computePosition,
|
||||
flip,
|
||||
getOverflowAncestors,
|
||||
offset,
|
||||
platform,
|
||||
shift,
|
||||
size,
|
||||
} from '@floating-ui/dom';
|
||||
import { offsetParent } from 'composed-offset-position';
|
||||
import type { PropertyValues } from 'lit';
|
||||
import { html } from 'lit';
|
||||
@@ -23,6 +33,8 @@ function isVirtualElement(e: unknown): e is VirtualElement {
|
||||
);
|
||||
}
|
||||
|
||||
const SUPPORTS_POPOVER = globalThis?.HTMLElement?.prototype.hasOwnProperty('popover');
|
||||
|
||||
/**
|
||||
* @summary Popup is a utility that lets you declaratively anchor "popup" containers to another element.
|
||||
* @documentation https://backers.webawesome.com/docs/components/popup
|
||||
@@ -97,11 +109,8 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
| 'left-start'
|
||||
| 'left-end' = 'top';
|
||||
|
||||
/**
|
||||
* Determines how the popup is positioned. The `absolute` strategy works well in most cases, but if overflow is
|
||||
* clipped, using a `fixed` position strategy can often workaround it.
|
||||
*/
|
||||
@property({ reflect: true }) strategy: 'absolute' | 'fixed' = 'absolute';
|
||||
/** Which bounding box to use for flipping, shifting, and auto-sizing? */
|
||||
@property() boundary: 'viewport' | 'scroll' = 'viewport';
|
||||
|
||||
/** The distance in pixels from which to offset the panel away from its anchor. */
|
||||
@property({ type: Number }) distance = 0;
|
||||
@@ -281,6 +290,8 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this.popup.showPopover?.();
|
||||
|
||||
this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
|
||||
this.reposition();
|
||||
});
|
||||
@@ -288,6 +299,8 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
|
||||
private async stop(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
this.popup.hidePopover?.();
|
||||
|
||||
if (this.cleanup) {
|
||||
this.cleanup();
|
||||
this.cleanup = undefined;
|
||||
@@ -334,11 +347,25 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
this.popup.style.height = '';
|
||||
}
|
||||
|
||||
let overflowBoundary, defaultBoundary;
|
||||
|
||||
if (SUPPORTS_POPOVER && !isVirtualElement(this.anchor)) {
|
||||
// When using the Popover API, the floating element is no longer in the same DOM context
|
||||
// as the overflow ancestors so Floating-UI can't find them.
|
||||
// For flip, `elementContext: 'reference'` gets it to use the anchor element instead,
|
||||
// but the option is not available for shift() or size(), so we basically need to implement it ourselves.
|
||||
overflowBoundary = getOverflowAncestors(this.anchorEl as Element).filter(el => el instanceof Element);
|
||||
}
|
||||
|
||||
if (this.boundary === 'scroll') {
|
||||
defaultBoundary = overflowBoundary;
|
||||
}
|
||||
|
||||
// Then we flip
|
||||
if (this.flip) {
|
||||
middleware.push(
|
||||
flip({
|
||||
boundary: this.flipBoundary,
|
||||
boundary: this.flipBoundary || overflowBoundary,
|
||||
// @ts-expect-error - We're converting a string attribute to an array here
|
||||
fallbackPlacements: this.flipFallbackPlacements,
|
||||
fallbackStrategy: this.flipFallbackStrategy === 'best-fit' ? 'bestFit' : 'initialPlacement',
|
||||
@@ -351,7 +378,7 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
if (this.shift) {
|
||||
middleware.push(
|
||||
shift({
|
||||
boundary: this.shiftBoundary,
|
||||
boundary: this.shiftBoundary || defaultBoundary,
|
||||
padding: this.shiftPadding,
|
||||
}),
|
||||
);
|
||||
@@ -361,7 +388,7 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
if (this.autoSize) {
|
||||
middleware.push(
|
||||
size({
|
||||
boundary: this.autoSizeBoundary,
|
||||
boundary: this.autoSizeBoundary || defaultBoundary,
|
||||
padding: this.autoSizePadding,
|
||||
apply: ({ availableWidth, availableHeight }) => {
|
||||
if (this.autoSize === 'vertical' || this.autoSize === 'both') {
|
||||
@@ -399,15 +426,14 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
//
|
||||
// More info: https://github.com/shoelace-style/shoelace/issues/1135
|
||||
//
|
||||
const getOffsetParent =
|
||||
this.strategy === 'absolute'
|
||||
? (element: Element) => platform.getOffsetParent(element, offsetParent)
|
||||
: platform.getOffsetParent;
|
||||
const getOffsetParent = SUPPORTS_POPOVER
|
||||
? (element: Element) => platform.getOffsetParent(element, offsetParent)
|
||||
: platform.getOffsetParent;
|
||||
|
||||
computePosition(this.anchorEl, this.popup, {
|
||||
placement: this.placement,
|
||||
middleware,
|
||||
strategy: this.strategy,
|
||||
strategy: SUPPORTS_POPOVER ? 'absolute' : 'fixed',
|
||||
platform: {
|
||||
...platform,
|
||||
getOffsetParent,
|
||||
@@ -563,11 +589,12 @@ export default class WaPopup extends WebAwesomeElement {
|
||||
></span>
|
||||
|
||||
<div
|
||||
popover="manual"
|
||||
part="popup"
|
||||
class=${classMap({
|
||||
popup: true,
|
||||
'popup--active': this.active,
|
||||
'popup--fixed': this.strategy === 'fixed',
|
||||
'popup--fixed': !SUPPORTS_POPOVER,
|
||||
'popup--has-arrow': this.arrow,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -229,12 +229,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) open = false;
|
||||
|
||||
/**
|
||||
* Enable this option to prevent the listbox from being clipped when the component is placed inside a container with
|
||||
* `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.
|
||||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
/** The select's visual appearance. */
|
||||
@property({ reflect: true }) appearance: 'filled' | 'outlined' = 'outlined';
|
||||
|
||||
@@ -891,7 +885,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
'placeholder-visible': isPlaceholderVisible,
|
||||
})}
|
||||
placement=${this.placement}
|
||||
strategy=${this.hoist ? 'fixed' : 'absolute'}
|
||||
flip
|
||||
shift
|
||||
sync="width"
|
||||
|
||||
@@ -92,13 +92,6 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
*/
|
||||
@property() trigger = 'hover focus';
|
||||
|
||||
/**
|
||||
* Enable this option to prevent the tooltip from being clipped when the component is placed inside a container with
|
||||
* `overflow: auto|hidden|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all,
|
||||
* scenarios.
|
||||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
@property() for: string | null = null;
|
||||
|
||||
@state() anchor: null | Element = null;
|
||||
@@ -290,7 +283,7 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
this.anchor = newAnchor;
|
||||
}
|
||||
|
||||
@watch(['distance', 'hoist', 'placement', 'skidding'])
|
||||
@watch(['distance', 'placement', 'skidding'])
|
||||
async handleOptionsChange() {
|
||||
if (this.hasUpdated) {
|
||||
await this.updateComplete;
|
||||
@@ -340,7 +333,6 @@ export default class WaTooltip extends WebAwesomeElement {
|
||||
placement=${this.placement}
|
||||
distance=${this.distance}
|
||||
skidding=${this.skidding}
|
||||
strategy=${this.hoist ? 'fixed' : 'absolute'}
|
||||
flip
|
||||
shift
|
||||
arrow
|
||||
|
||||
Reference in New Issue
Block a user