diff --git a/docs/components/popup.md b/docs/components/popup.md
index c48a3c0d..0321b70b 100644
--- a/docs/components/popup.md
+++ b/docs/components/popup.md
@@ -681,50 +681,96 @@ const App = () => {
Add an arrow to your popup with the `arrow` attribute. It's usually a good idea to set a `distance` to make room for the arrow. To adjust the arrow's color and size, use the `--arrow-color` and `--arrow-size` custom properties, respectively. You can also target the `arrow` part to add additional styles such as shadows and borders.
+By default, the arrow will be aligned as close to the center of the _anchor_ as possible, considering available space and `arrow-padding`. You can use the `arrow-placement` attribute to force the arrow to align to the start, end, or center of the _popup_ instead.
+
```html preview
-
-
-
-
```
```jsx react
import { useState } from 'react';
-import { SlPopup, SlSwitch } from '@shoelace-style/shoelace/dist/react';
+import { SlPopup, SlSelect, SlMenuItem, SlSwitch } from '@shoelace-style/shoelace/dist/react';
const css = `
.popup-arrow sl-popup {
@@ -745,23 +791,76 @@ const css = `
background: var(--sl-color-primary-600);
border-radius: var(--sl-border-radius-medium);
}
+
+ .popup-arrow-options {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: end;
+ gap: 1rem;
+ }
+
+ .popup-arrow-options sl-select {
+ width: 160px;
+ }
+
+ .popup-arrow-options + .popup-arrow-options {
+ margin-top: 1rem;
+ }
`;
const App = () => {
+ const [placement, setPlacement] = useState('top');
+ const [arrowPlacement, setArrowPlacement] = useState('anchor');
const [arrow, setArrow] = useState(true);
return (
<>
-
+
-
- setArrow(event.target.checked)}>
- Arrow
-
+
+ setPlacement(event.target.value)}
+ >
+ top
+ top-start
+ top-end
+ bottom
+ bottom-start
+ bottom-end
+ right
+ right-start
+ right-end
+ left
+ left-start
+ left-end
+
+
+ setArrowPlacement(event.target.value)}
+ >
+ anchor
+ start
+ end
+ center
+
+
+
+
+ setArrow(event.target.checked)}>
+ Arrow
+
+
diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md
index 22c064f7..a823117f 100644
--- a/docs/resources/changelog.md
+++ b/docs/resources/changelog.md
@@ -10,7 +10,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
-- Added the `sync` attribute to ``
+- Added the `sync` and `arrow-placement` attributes to ``
- Changed the `auto-size` attribute of the experimental `` component so it accepts `horizontal`, `vertical`, and `both` instead of a boolean value
- Changed the `flip-fallback-placement` attribute of the experimental `` component to `flip-fallback-placements`
- Changed the `flip-fallback-strategy` in the experimental `` component to accept `best-fit` and `initial` instead of `bestFit` and `initialPlacement`
diff --git a/src/components/popup/popup.ts b/src/components/popup/popup.ts
index 6737ade1..e97830b4 100644
--- a/src/components/popup/popup.ts
+++ b/src/components/popup/popup.ts
@@ -93,6 +93,13 @@ export default class SlPopup extends ShoelaceElement {
*/
@property({ type: Boolean }) arrow = false;
+ /**
+ * The placement of the arrow. The default is `anchor`, which will align the arrow as close to the center of the
+ * anchor as possible, considering available space and `arrow-padding`. A value of `start`, `end`, or `center` will
+ * align the arrow to the start, end, or center of the popover instead.
+ */
+ @property({ attribute: 'arrow-placement' }) arrowPlacement: 'start' | 'end' | 'center' | 'anchor' = 'anchor';
+
/**
* 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.
@@ -372,14 +379,36 @@ export default class SlPopup extends ShoelaceElement {
});
if (this.arrow) {
- const arrowX = middlewareData.arrow?.x;
- const arrowY = middlewareData.arrow?.y;
+ const arrowX = middlewareData.arrow!.x;
+ const arrowY = middlewareData.arrow!.y;
+ let top = '';
+ let right = '';
+ let bottom = '';
+ let left = '';
+
+ if (this.arrowPlacement === 'start') {
+ // Start
+ left = typeof arrowX === 'number' ? `${this.arrowPadding}px` : '';
+ top = typeof arrowY === 'number' ? `${this.arrowPadding}px` : '';
+ } else if (this.arrowPlacement === 'end') {
+ // End
+ right = typeof arrowX === 'number' ? `${this.arrowPadding}px` : '';
+ bottom = typeof arrowY === 'number' ? `${this.arrowPadding}px` : '';
+ } else if (this.arrowPlacement === 'center') {
+ // Center
+ left = typeof arrowX === 'number' ? `calc(50% - var(--arrow-size))` : '';
+ top = typeof arrowY === 'number' ? `calc(50% - var(--arrow-size))` : '';
+ } else {
+ // Anchor (default)
+ left = typeof arrowX === 'number' ? `${arrowX}px` : '';
+ top = typeof arrowY === 'number' ? `${arrowY}px` : '';
+ }
Object.assign(this.arrowEl.style, {
- left: typeof arrowX === 'number' ? `${arrowX}px` : '',
- top: typeof arrowY === 'number' ? `${arrowY}px` : '',
- right: '',
- bottom: '',
+ top,
+ right,
+ bottom,
+ left,
[staticSide]: 'calc(var(--arrow-size) * -1)'
});
}