Skip to content

Commit

Permalink
fix popup
Browse files Browse the repository at this point in the history
  • Loading branch information
claviska committed Aug 10, 2022
1 parent 4b9e353 commit a8e8325
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 80 deletions.
50 changes: 27 additions & 23 deletions docs/components/popup.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Popup is a utility that lets you declaratively anchor "popup" containers to anot

This component's name is inspired by [`<popup>`](https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md). It uses [Floating UI](https://floating-ui.com/) under the hood to provide a well-tested, lightweight, and fully declarative positioning utility for tooltips, dropdowns, and more.

The popup's preferred placement, distance, and skidding (offset) can be configured using attributes. An arrow that points to the anchor can be shown and customized to your liking. Additional positioning options are available and described in more detail below.
Popup doesn't provide any styles — just positioning! The popup's preferred placement, distance, and skidding (offset) can be configured using attributes. An arrow that points to the anchor can be shown and customized to your liking. Additional positioning options are available and described in more detail below.

```html preview
<div class="popup-overview">
Expand Down Expand Up @@ -215,7 +215,7 @@ const App = () => {
};
```

?> A popup's anchor should never be styled with `display: contents` since the coordinates will not be eligible for calculation. However, if the anchor is a `<slot>` element, popup will use the first assigned element as the anchor. This behavior allows other components to pass anchors through more easily via composition.
?> A popup's anchor should not be styled with `display: contents` since the coordinates will not be eligible for calculation. However, if the anchor is a `<slot>` element, popup will use the first assigned element as the anchor. This behavior allows other components to pass anchors through more easily via composition.

## Examples

Expand Down Expand Up @@ -1029,16 +1029,16 @@ const App = () => {

### Auto-size

Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from overflowing. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option.
Use the `auto-size` attribute to tell the popup to resize when necessary to prevent it from getting clipped. You can use `autoSizeBoundary` and `auto-size-padding` to customize the behavior of this option. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.

For best results, set a preferred width/height on the `popup` part and set `width: 100%; height: 100%;` on your content's wrapper. Auto-size works well with `flip`, but if you're using `auto-size-padding` make sure `flip-padding` is the same value.
When using auto-size, two read-only custom properties called `--auto-size-available-width` and `--auto-size-available-height` will be applied to the host element. These values determine the available space the popover has before clipping will occur. Since they cascade, you can use them to set a max-width/height on your popup's content and easily control its overflow.

Scroll the container to see auto-size in action.
Scroll the container to see the popup resize as its available space changes.

```html preview
<div class="popup-auto-size">
<div class="overflow">
<sl-popup placement="bottom" auto-size active>
<sl-popup placement="top" auto-size active>
<span slot="anchor"></span>
<div class="box"></div>
</sl-popup>
Expand All @@ -1061,19 +1061,21 @@ Scroll the container to see auto-size in action.
width: 150px;
height: 150px;
border: dashed 2px var(--sl-color-neutral-600);
margin: 100px 50px 250px 50px;
}
.popup-auto-size sl-popup::part(popup) {
width: 100px;
height: 200px;
margin: 250px 50px 100px 50px;
}
.popup-auto-size .box {
width: 100%;
height: 100%;
background: var(--sl-color-primary-600);
border-radius: var(--sl-border-radius-medium);
/* This sets the preferred size of the popup's content */
width: 100px;
height: 200px;
/* This sets the maximum dimensions and allows scrolling when auto-size kicks in */
max-width: var(--auto-size-available-width);
max-height: var(--auto-size-available-height);
overflow: auto;
}
</style>

Expand Down Expand Up @@ -1103,19 +1105,21 @@ const css = `
width: 150px;
height: 150px;
border: dashed 2px var(--sl-color-neutral-600);
margin: 100px 50px 250px 50px;
}
.popup-auto-size sl-popup::part(popup) {
width: 100px;
height: 200px;
margin: 250px 50px 100px 50px;
}
.popup-auto-size .box {
width: 100%;
height: 100%;
background: var(--sl-color-primary-600);
border-radius: var(--sl-border-radius-medium);
/* This sets the preferred size of the popup's content */
width: 100px;
height: 200px;
/* This sets the maximum dimensions and allows scrolling when auto-size kicks in */
max-width: var(--auto-size-available-width);
max-height: var(--auto-size-available-height);
overflow: auto;
}
`;

Expand All @@ -1126,7 +1130,7 @@ const App = () => {
<>
<div className="popup-auto-size">
<div className="overflow">
<SlPopup placement="bottom" auto-size={autoSize || null} auto-size-padding="10" active>
<SlPopup placement="top" auto-size={autoSize || null} auto-size-padding="10" active>
<span slot="anchor" />
<div className="box" />
</SlPopup>
Expand Down
7 changes: 7 additions & 0 deletions docs/resources/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ New versions of Shoelace are released as-needed and generally occur when a criti

_During the beta period, these restrictions may be relaxed in the event of a mission-critical bug._ 🐛

## Next

- 🚨 BREAKING: removed the `base` part from `<sl-menu>` and removed an unnecessary `<div>` that made styling more difficult
- Added read-only custom properties `--auto-size-available-width` and `--auto-size-available-height` to `<sl-popup>` to improve support for overflowing popup content
- Fixed a bug where auto-size wasn't being applied to `<sl-dropdown>` and `<sl-select>`
- Fixed a bug in `<sl-popup>` that caused auto-size to kick in before flip

## 2.0.0-beta.80

This release breaks radio buttons, which is something that needed to happen to solve a longstanding accessibility issue where screen readers announced an incorrect number of radios, e.g. "1 of 1" instead of "1 of 3." Many attempts to solve this without breaking the existing API were made, but none worked across the board. The new implementation upgrades `<sl-radio-group>` to serve as the "form control" while `<sl-radio>` and `<sl-radio-button>` serve as options within the form control.
Expand Down
8 changes: 6 additions & 2 deletions src/components/dropdown/dropdown.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ export default css`
font-weight: var(--sl-font-weight-normal);
color: var(--color);
box-shadow: var(--sl-shadow-large);
overflow: auto;
overscroll-behavior: none;
pointer-events: none;
}
.dropdown--open .dropdown__panel {
pointer-events: all;
}
/* When users slot a menu, make sure it conforms to the popup's auto-size */
::slotted(sl-menu) {
max-width: var(--auto-size-available-width) !important;
max-height: var(--auto-size-available-height) !important;
}
`;
2 changes: 2 additions & 0 deletions src/components/dropdown/dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ export default class SlDropdown extends LitElement {
strategy=${this.hoist ? 'fixed' : 'absolute'}
flip
shift
auto-size
auto-size-padding="10"
class=${classMap({
dropdown: true,
'dropdown--open': this.open
Expand Down
6 changes: 3 additions & 3 deletions src/components/menu/menu.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ export default css`
:host {
display: block;
}
.menu {
position: relative;
background: var(--sl-panel-background-color);
border: solid var(--sl-panel-border-width) var(--sl-panel-border-color);
border-radius: var(--sl-border-radius-medium);
padding: var(--sl-spacing-x-small) 0;
overflow: auto;
overscroll-behavior: none;
}
::slotted(sl-divider) {
Expand Down
12 changes: 3 additions & 9 deletions src/components/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@ export interface MenuSelectEventDetail {
* @slot - The menu's content, including menu items, menu labels, and dividers.
*
* @event {{ item: SlMenuItem }} sl-select - Emitted when a menu item is selected.
*
* @csspart base - The component's internal wrapper.
*/
@customElement('sl-menu')
export default class SlMenu extends LitElement {
static styles: CSSResultGroup = styles;

@query('.menu') menu: HTMLElement;
@query('slot') defaultSlot: HTMLSlotElement;

private typeToSelectString = '';
Expand Down Expand Up @@ -183,15 +180,12 @@ export default class SlMenu extends LitElement {

render() {
return html`
<div
part="base"
class="menu"
<slot
@slotchange=${this.handleSlotChange}
@click=${this.handleClick}
@keydown=${this.handleKeyDown}
@mousedown=${this.handleMouseDown}
>
<slot @slotchange=${this.handleSlotChange}></slot>
</div>
></slot>
`;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/popup/popup.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export default css`
.popup {
position: absolute;
isolation: isolate;
max-width: var(--auto-size-available-width, none);
max-height: var(--auto-size-available-height, none);
}
.popup--fixed {
Expand Down
71 changes: 33 additions & 38 deletions src/components/popup/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ import type { CSSResultGroup } from 'lit';
* maybe a border or box shadow.
* @csspart popup - The popup's container. Useful for setting a background color, box shadow, etc.
*
* @cssproperty [--arrow-size=4px] - The size of the arrow. Note that an arrow won't be shown unless the `arrow` attribute is used.
* @cssproperty [--arrow-size=4px] - The size of the arrow. Note that an arrow won't be shown unless the `arrow`
* attribute is used.
* @cssproperty [--arrow-color=var(--sl-color-neutral-0)] - The color of the arrow.
* @cssproperty [--auto-size-available-width] - A read-only custom property that determines the amount of width the
* popup can be before overflowing. Useful for positioning child elements that need to overflow. This property is only
* available when using `auto-size`.
* @cssproperty [--auto-size-available-height] - A read-only custom property that determines the amount of height the
* popup can be before overflowing. Useful for positioning child elements that need to overflow. This property is only
* available when using `auto-size`.
*/
@customElement('sl-popup')
export default class SlPopup extends LitElement {
Expand Down Expand Up @@ -127,11 +134,7 @@ export default class SlPopup extends LitElement {
@property({ type: Object }) flipBoundary: Element | Element[];

/** The amount of padding, in pixels, to exceed before the flip behavior will occur. */
@property({
attribute: 'flip-padding',
type: Number
})
flipPadding = 0;
@property({ attribute: 'flip-padding', type: Number }) flipPadding = 0;

/** Moves the popup along the axis to keep it in view when clipped. */
@property({ type: Boolean }) shift = false;
Expand All @@ -144,11 +147,7 @@ export default class SlPopup extends LitElement {
@property({ type: Object }) shiftBoundary: Element | Element[];

/** The amount of padding, in pixels, to exceed before the shift behavior will occur. */
@property({
attribute: 'shift-padding',
type: Number
})
shiftPadding = 0;
@property({ attribute: 'shift-padding', type: Number }) shiftPadding = 0;

/** When set, this will cause the popup to automatically resize itself to prevent it from overflowing. */
@property({ attribute: 'auto-size', type: Boolean }) autoSize = false;
Expand All @@ -161,11 +160,7 @@ export default class SlPopup extends LitElement {
@property({ type: Object }) autoSizeBoundary: Element | Element[];

/** The amount of padding, in pixels, to exceed before the auto-size behavior will occur. */
@property({
attribute: 'auto-size-padding',
type: Number
})
autoSizePadding = 0;
@property({ attribute: 'auto-size-padding', type: Number }) autoSizePadding = 0;

async connectedCallback() {
super.connectedCallback();
Expand Down Expand Up @@ -214,6 +209,8 @@ export default class SlPopup extends LitElement {
this.cleanup();
this.cleanup = undefined;
this.removeAttribute('data-current-placement');
this.style.removeProperty('--auto-size-available-width');
this.style.removeProperty('--auto-size-available-height');
requestAnimationFrame(() => resolve());
} else {
resolve();
Expand Down Expand Up @@ -255,27 +252,7 @@ export default class SlPopup extends LitElement {
offset({ mainAxis: this.distance, crossAxis: this.skidding })
];

// First, we adjust the size as needed
if (this.autoSize) {
middleware.push(
size({
boundary: this.autoSizeBoundary,
padding: this.autoSizePadding,
apply: ({ availableWidth, availableHeight }) => {
// Ensure the panel stays within the viewport when we have lots of menu items
Object.assign(this.popup.style, {
maxWidth: `${availableWidth}px`,
maxHeight: `${availableHeight}px`
});
}
})
);
} else {
// Unset max-width/max-height when we're no longer using this middleware
Object.assign(this.popup.style, { maxWidth: '', maxHeight: '' });
}

// Then we flip, as needed
// First we flip
if (this.flip) {
middleware.push(
flip({
Expand All @@ -288,7 +265,7 @@ export default class SlPopup extends LitElement {
);
}

// Then we shift, as needed
// Then we shift
if (this.shift) {
middleware.push(
shift({
Expand All @@ -298,6 +275,24 @@ export default class SlPopup extends LitElement {
);
}

// Now we adjust the size as needed
if (this.autoSize) {
middleware.push(
size({
boundary: this.autoSizeBoundary,
padding: this.autoSizePadding,
apply: ({ availableWidth, availableHeight }) => {
this.style.setProperty('--auto-size-available-width', `${availableWidth}px`);
this.style.setProperty('--auto-size-available-height', `${availableHeight}px`);
}
})
);
} else {
// Cleanup styles if we're no longer using auto-size
this.style.removeProperty('--auto-size-available-width');
this.style.removeProperty('--auto-size-available-height');
}

// Finally, we add an arrow
if (this.arrow) {
middleware.push(
Expand Down
5 changes: 0 additions & 5 deletions src/components/select/select.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ export default css`
cursor: pointer;
}
.select__menu {
max-height: 50vh;
overflow: auto;
}
.select__menu::part(base) {
border: none;
}
Expand Down

0 comments on commit a8e8325

Please sign in to comment.