mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
looking good; needs docs
This commit is contained in:
@@ -10,57 +10,58 @@ A description of the component goes here.
|
||||
<span slot="anchor">This is the anchor</span>
|
||||
<div class="box"></div>
|
||||
</sl-popup>
|
||||
</div>
|
||||
|
||||
<div class="popup-overview-options">
|
||||
<sl-select label="Placement" name="placement" value="top" class="popup-overview-select">
|
||||
<sl-menu-item value="top">top</sl-menu-item>
|
||||
<sl-menu-item value="top-start">top-start</sl-menu-item>
|
||||
<sl-menu-item value="top-end">top-end</sl-menu-item>
|
||||
<sl-menu-item value="bottom">bottom</sl-menu-item>
|
||||
<sl-menu-item value="bottom-start">bottom-start</sl-menu-item>
|
||||
<sl-menu-item value="bottom-end">bottom-end</sl-menu-item>
|
||||
<sl-menu-item value="right">right</sl-menu-item>
|
||||
<sl-menu-item value="right-start">right-start</sl-menu-item>
|
||||
<sl-menu-item value="right-end">right-end</sl-menu-item>
|
||||
<sl-menu-item value="left">left</sl-menu-item>
|
||||
<sl-menu-item value="left-start">left-start</sl-menu-item>
|
||||
<sl-menu-item value="left-end">left-end</sl-menu-item>
|
||||
</sl-select>
|
||||
<sl-input type="number" name="distance" label="distance" value="0"></sl-input>
|
||||
<sl-input type="number" name="skidding" label="Skidding" value="0"></sl-input>
|
||||
<sl-switch name="active" checked>Active</sl-switch>
|
||||
<sl-switch name="arrow">Arrow</sl-switch>
|
||||
<sl-switch name="fixed">Fixed</sl-switch>
|
||||
<div class="popup-overview-options">
|
||||
<sl-select label="Placement" name="placement" value="top" class="popup-overview-select">
|
||||
<sl-menu-item value="top">top</sl-menu-item>
|
||||
<sl-menu-item value="top-start">top-start</sl-menu-item>
|
||||
<sl-menu-item value="top-end">top-end</sl-menu-item>
|
||||
<sl-menu-item value="bottom">bottom</sl-menu-item>
|
||||
<sl-menu-item value="bottom-start">bottom-start</sl-menu-item>
|
||||
<sl-menu-item value="bottom-end">bottom-end</sl-menu-item>
|
||||
<sl-menu-item value="right">right</sl-menu-item>
|
||||
<sl-menu-item value="right-start">right-start</sl-menu-item>
|
||||
<sl-menu-item value="right-end">right-end</sl-menu-item>
|
||||
<sl-menu-item value="left">left</sl-menu-item>
|
||||
<sl-menu-item value="left-start">left-start</sl-menu-item>
|
||||
<sl-menu-item value="left-end">left-end</sl-menu-item>
|
||||
</sl-select>
|
||||
<sl-input type="number" name="distance" label="distance" value="0"></sl-input>
|
||||
<sl-input type="number" name="skidding" label="Skidding" value="0"></sl-input>
|
||||
</div>
|
||||
|
||||
<div class="popup-overview-options">
|
||||
<sl-switch name="active" checked>Active</sl-switch>
|
||||
<sl-switch name="flip">Flip</sl-switch>
|
||||
<sl-switch name="arrow">Arrow</sl-switch>
|
||||
<sl-switch name="fixed">Fixed</sl-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.popup-overview');
|
||||
const popup = container.querySelector('sl-popup');
|
||||
const options = document.querySelector('.popup-overview-options');
|
||||
const select = options.querySelector('sl-select[name="placement"]');
|
||||
const distance = options.querySelector('sl-input[name="distance"]');
|
||||
const skidding = options.querySelector('sl-input[name="skidding"]');
|
||||
const active = options.querySelector('sl-switch[name="active"]');
|
||||
const arrow = options.querySelector('sl-switch[name="arrow"]');
|
||||
const fixed = options.querySelector('sl-switch[name="fixed"]');
|
||||
const select = container.querySelector('sl-select[name="placement"]');
|
||||
const distance = container.querySelector('sl-input[name="distance"]');
|
||||
const skidding = container.querySelector('sl-input[name="skidding"]');
|
||||
const active = container.querySelector('sl-switch[name="active"]');
|
||||
const flip = container.querySelector('sl-switch[name="flip"]');
|
||||
const arrow = container.querySelector('sl-switch[name="arrow"]');
|
||||
const fixed = container.querySelector('sl-switch[name="fixed"]');
|
||||
|
||||
select.addEventListener('sl-change', () => (popup.placement = select.value));
|
||||
distance.addEventListener('sl-input', () => (popup.distance = distance.value));
|
||||
skidding.addEventListener('sl-input', () => (popup.skidding = skidding.value));
|
||||
active.addEventListener('sl-change', () => (popup.active = active.checked));
|
||||
flip.addEventListener('sl-change', () => (popup.flip = flip.checked));
|
||||
arrow.addEventListener('sl-change', () => (popup.arrow = arrow.checked));
|
||||
fixed.addEventListener('sl-change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.popup-overview {
|
||||
padding: calc(50px + 1rem);
|
||||
}
|
||||
|
||||
.popup-overview sl-popup {
|
||||
--arrow-color: var(--sl-color-primary-600);
|
||||
--arrow-size: 4px;
|
||||
margin: calc(50px + 1rem);
|
||||
}
|
||||
|
||||
.popup-overview [slot='anchor'] {
|
||||
@@ -71,6 +72,8 @@ A description of the component goes here.
|
||||
.popup-overview .box {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
background: var(--sl-color-primary-600);
|
||||
}
|
||||
|
||||
@@ -99,9 +102,9 @@ A description of the component goes here.
|
||||
|
||||
### Fixed Positioning Strategy
|
||||
|
||||
By default, an absolute positioning strategy is used for maximum performance. However, if your content is within a container that has `overflow: auto|hidden` the popup will be clipped. To work around this, you can switch to the fixed positioning strategy by setting the `strategy` attribute to `fixed`.
|
||||
By default, an absolute positioning strategy is used. However, if your content is fixed or within a container that has `overflow: auto|hidden`, the popup will be clipped. To work around this, you can switch to the fixed positioning strategy by setting the `strategy` attribute to `fixed`.
|
||||
|
||||
The fixed positioning strategy allows the content to break out containers that clip them. When using this strategy, it's important to note that the content will be positioned _relative to its containing block_, which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
The fixed positioning reduces jumpiness when the anchor is fixed and allows the content to break out containers that clip them. When using this strategy, it's important to note that the content will be positioned _relative to its containing block_, which is usually the viewport unless an ancestor uses a `transform`, `perspective`, or `filter`. [Refer to this page](https://developer.mozilla.org/en-US/docs/Web/CSS/position#fixed) for more details.
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ export default css`
|
||||
|
||||
:host {
|
||||
--arrow-size: 4px;
|
||||
--arrow-color: var(--sl-color-neutral-0);
|
||||
--arrow-shadow: none;
|
||||
--arrow-color: var(--sl-color-neutral-1000);
|
||||
|
||||
display: inline;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.popup {
|
||||
@@ -32,7 +31,6 @@ export default css`
|
||||
height: calc(var(--arrow-size) * 2);
|
||||
transform: rotate(45deg);
|
||||
background: var(--arrow-color);
|
||||
box-shadow: var(--arrow-shadow);
|
||||
z-index: -1;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -18,8 +18,8 @@ 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.
|
||||
* @cssproperty [--arrow-color=4px] - The color of the arrow.
|
||||
* @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.
|
||||
*/
|
||||
@customElement('sl-popup')
|
||||
export default class SlPopup extends LitElement {
|
||||
@@ -31,7 +31,10 @@ export default class SlPopup extends LitElement {
|
||||
private anchor: HTMLElement | null;
|
||||
private cleanup: ReturnType<typeof autoUpdate> | undefined;
|
||||
|
||||
/** Activates popup logic and shows the popup. */
|
||||
/**
|
||||
* Activates the positioning logic and shows the popup. When this attribute is removed, the positioning logic is torn
|
||||
* down and the popup will be hidden.
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true }) active = false;
|
||||
|
||||
/**
|
||||
@@ -50,7 +53,7 @@ export default class SlPopup extends LitElement {
|
||||
| 'right-end'
|
||||
| 'left'
|
||||
| 'left-start'
|
||||
| 'left-end' = 'bottom-start';
|
||||
| 'left-end' = 'top';
|
||||
|
||||
/**
|
||||
* Determines how the popup is positioned. The `absolute` strategy works well in most cases, but if
|
||||
@@ -64,12 +67,19 @@ export default class SlPopup extends LitElement {
|
||||
/** The distance in pixels from which to offset the panel along its anchor. */
|
||||
@property({ type: Number }) skidding = 0;
|
||||
|
||||
/** Moves the popup along the axis to keep it in view when clipped. */
|
||||
@property({ type: Boolean }) shift = false;
|
||||
|
||||
/** Attaches an arrow to the popup. */
|
||||
/**
|
||||
* Attaches an arrow to the popup. The arrow's size and color can be customized using the `--arrow-size` and
|
||||
* `--arrow-color` custom properties. For additional customizations, you can also target the arrow using
|
||||
* `::part(arrow) in your stylesheet.
|
||||
*/
|
||||
@property({ type: Boolean }) arrow = false;
|
||||
|
||||
/**
|
||||
* The amount of padding between the arrow and the edges of the popup. If the popup has a border-radius, for example,
|
||||
* this will prevent it from overflowing the corners.
|
||||
*/
|
||||
@property({ type: Number }) arrowPadding = 10;
|
||||
|
||||
/**
|
||||
* When set, placement of the popup will flip to the opposite site to keep it in view. You can use
|
||||
* `flipFallbackPlacement` to further configure how the fallback placement is determined.
|
||||
@@ -81,14 +91,93 @@ export default class SlPopup extends LitElement {
|
||||
* string of any number of placements separated by a space, e.g. "top bottom left". If no placement fits, the flip
|
||||
* fallback strategy will be used instead.
|
||||
* */
|
||||
@property() flipFallbackPlacement: string;
|
||||
@property({
|
||||
attribute: 'flip-fallback-placement',
|
||||
converter: {
|
||||
fromAttribute: (value: string) => {
|
||||
console.log(value);
|
||||
return String(value)
|
||||
.split(' ')
|
||||
.map(p => p.trim());
|
||||
},
|
||||
toAttribute: (value: []) => {
|
||||
console.log(value);
|
||||
return value.join(' ');
|
||||
}
|
||||
}
|
||||
})
|
||||
flipFallbackPlacement = '';
|
||||
|
||||
/**
|
||||
* When neither the preferred placement nor the fallback placements fit, this value will be used to determine whether
|
||||
* the popup should be positioned as it was initially preferred or using the best available fit based on available
|
||||
* space.
|
||||
*/
|
||||
@property() flipFallbackStrategy: 'bestFit' | 'initialPlacement' = 'initialPlacement';
|
||||
@property({ attribute: 'flip-fallback-strategy' }) flipFallbackStrategy: 'bestFit' | 'initialPlacement' =
|
||||
'initialPlacement';
|
||||
|
||||
/**
|
||||
* The flip boundary describes clipping element(s) that overflow will be checked relative to when flipping. By
|
||||
* default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can
|
||||
* change the boundary by passing a reference to one or more elements to this property.
|
||||
*/
|
||||
@property({
|
||||
attribute: 'flip-boundary',
|
||||
type: Object
|
||||
})
|
||||
flipBoundary: Element | Element[];
|
||||
|
||||
/** The amount of padding, in pixels, when the flip behavior will occur. */
|
||||
@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;
|
||||
|
||||
/**
|
||||
* The shift boundary describes clipping element(s) that overflow will be checked relative to when shifting. By
|
||||
* default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can
|
||||
* change the boundary by passing a reference to one or more elements to this property.
|
||||
*/
|
||||
@property({
|
||||
attribute: 'shift-boundary',
|
||||
type: Object
|
||||
})
|
||||
shiftBoundary: Element | Element[];
|
||||
|
||||
/** The amount of padding, in pixels, when the shift behavior will occur. */
|
||||
@property({
|
||||
attribute: 'shift-padding',
|
||||
type: Number
|
||||
})
|
||||
shiftPadding = 0;
|
||||
|
||||
/**
|
||||
* When set, this will cause the popup to be resized to prevent it from overflowing. This is used as a last resort
|
||||
* and will only be attempted after flipping and shifting, if those options are enabled.
|
||||
*/
|
||||
@property({ type: Boolean }) resize = false;
|
||||
|
||||
/**
|
||||
* The resize boundary describes clipping element(s) that overflow will be checked relative to when resizing. By
|
||||
* default, the boundary includes overflow ancestors that will cause the element to be clipped. If needed, you can
|
||||
* change the boundary by passing a reference to one or more elements to this property.
|
||||
*/
|
||||
@property({
|
||||
attribute: 'resize-boundary',
|
||||
type: Object
|
||||
})
|
||||
resizeBoundary: Element | Element[];
|
||||
|
||||
/** The amount of padding, in pixels, when the resize behavior will occur. */
|
||||
@property({
|
||||
attribute: 'resize-padding',
|
||||
type: Number
|
||||
})
|
||||
resizePadding = 0;
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -139,19 +228,21 @@ export default class SlPopup extends LitElement {
|
||||
@watch('active', { waitUntilFirstUpdate: true })
|
||||
handleActiveChange() {
|
||||
if (this.active) {
|
||||
this.reposition();
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@watch('arrow')
|
||||
@watch('boundary')
|
||||
@watch('distance')
|
||||
@watch('flip')
|
||||
@watch('flipFallbackPlacement')
|
||||
@watch('flipFallbackStrategy')
|
||||
@watch('placement')
|
||||
@watch('shift')
|
||||
@watch('resize')
|
||||
@watch('skidding')
|
||||
@watch('strategy')
|
||||
async handlePositionChange() {
|
||||
@@ -184,19 +275,43 @@ export default class SlPopup extends LitElement {
|
||||
if (this.flip) {
|
||||
middleware.push(
|
||||
flip({
|
||||
boundary: this.flipBoundary,
|
||||
// @ts-expect-error - We're converting a string attribute to an array here
|
||||
//
|
||||
// TODO - use a custom adapter for this property
|
||||
//
|
||||
fallbackPlacement: this.flipFallbackPlacement.split(' ').filter(p => p.trim()),
|
||||
fallbackStrategy: this.flipFallbackStrategy
|
||||
fallbackPlacement: this.flipFallbackPlacement,
|
||||
fallbackStrategy: this.flipFallbackStrategy,
|
||||
padding: this.flipPadding
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Then we shift, as needed
|
||||
if (this.shift) {
|
||||
middleware.push(shift());
|
||||
middleware.push(
|
||||
shift({
|
||||
boundary: this.shiftBoundary,
|
||||
padding: this.shiftPadding
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Then, we adjust the size as needed
|
||||
if (this.resize) {
|
||||
middleware.push(
|
||||
size({
|
||||
boundary: this.resizeBoundary,
|
||||
padding: this.resizePadding,
|
||||
apply: ({ availableWidth, availableHeight }) => {
|
||||
// Ensure the panel stays within the viewport when we have lots of menu items
|
||||
Object.assign(this.popupEl.style, {
|
||||
maxWidth: `${availableWidth}px`,
|
||||
maxHeight: `${availableHeight}px`
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Unset max-width/max-height when we're no longer using this middleware
|
||||
Object.assign(this.popupEl.style, { maxWidth: '', maxHeight: '' });
|
||||
}
|
||||
|
||||
// Finally, we add an arrow
|
||||
@@ -204,7 +319,7 @@ export default class SlPopup extends LitElement {
|
||||
middleware.push(
|
||||
arrow({
|
||||
element: this.arrowEl,
|
||||
padding: 10 // min distance from the edge, in pixels
|
||||
padding: this.arrowPadding
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user