kopia lustrzana https://github.com/shoelace-style/shoelace
replace popper with floating ui
rodzic
93cb8a2411
commit
b9770e7e73
docs
scripts
src
components
|
@ -80,7 +80,6 @@
|
|||
"ParamagicDev",
|
||||
"peta",
|
||||
"petabit",
|
||||
"popperjs",
|
||||
"progressbar",
|
||||
"radiogroup",
|
||||
"Railsbyte",
|
||||
|
|
|
@ -219,7 +219,11 @@
|
|||
await searchIndex;
|
||||
|
||||
const hasQuery = query.length > 0;
|
||||
const matches = hasQuery ? searchIndex.search(`t:*${query}*^20 h:*${query}*^10 ${query}~1^5 *${query}*^0`) : [];
|
||||
const searchQuery = query
|
||||
.split(' ')
|
||||
.map((term, index, arr) => `${term}${index === arr.length - 1 ? `* ${term}~1` : '~1'}`)
|
||||
.join(' ');
|
||||
const matches = hasQuery ? searchIndex.search(searchQuery) : [];
|
||||
const hasResults = hasQuery && matches.length > 0;
|
||||
siteSearch.classList.toggle('site-search--has-results', hasQuery && hasResults);
|
||||
siteSearch.classList.toggle('site-search--no-results', hasQuery && !hasResults);
|
||||
|
|
|
@ -124,7 +124,7 @@ Special thanks to the following projects and individuals that help make Shoelace
|
|||
- Color primitives are inspired by [Tailwind](https://tailwindcss.com/)
|
||||
- Icons are courtesy of [Bootstrap Icons](https://icons.getbootstrap.com/)
|
||||
- The homepage illustration is courtesy of [unDraw](https://undraw.co/)
|
||||
- Positioning of menus, tooltips, et al is handled by [Popper.js](https://popper.js.org/)
|
||||
- Positioning of dropdowns, tooltips, et al is handled by [Floating UI](https://floating-ui.com/)
|
||||
- Animations are courtesy of [animate.css](https://animate.style/)
|
||||
- QR codes are generated with [qr-creator](https://github.com/nimiq/qr-creator)
|
||||
- Search is powered by [Lunr](https://lunrjs.com/)
|
||||
|
|
|
@ -13,7 +13,10 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
|
|||
- Fixed a bug that prevented forms from submitting when pressing <kbd>Enter</kbd> inside of an `<sl-input>` [#700](https://github.com/shoelace-style/shoelace/issues/700)
|
||||
- Fixed a bug in `<sl-input>` that prevented the `valueAsDate` and `valueAsNumber` properties from working when set before the component was initialized
|
||||
- Improved `autofocus` behavior in Safari for `<sl-dialog>` and `<sl-drawer>` [#693](https://github.com/shoelace-style/shoelace/issues/693)
|
||||
- Improved type to select logic in `<sl-menu>` so it supports <kbd>Backspace</kbd> and gives users more time before resetting
|
||||
- Removed feature detection for `focus({ preventScroll })` since it no longer works in Safari
|
||||
- Removed the `--sl-tooltip-arrow-start-end-offset` design token
|
||||
- Replaced Popper positioning dependency with Floating UI in `<sl-dropdown>` and `<sl-tooltip>`
|
||||
|
||||
## 2.0.0-beta.70
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"version": "2.0.0-beta.70",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.3.1",
|
||||
"@lit-labs/react": "^1.0.2",
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^2.1.3",
|
||||
"color": "4.2",
|
||||
|
@ -663,6 +663,19 @@
|
|||
"@types/chai": "^4.2.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.5.1.tgz",
|
||||
"integrity": "sha512-LOFg65vdU4o4eZjVKNhu7VXbjdnY0amMw9sydLtKZiCNwj0eMD06rmktbWp9L/f6w6s9Qqe22lkks8PbwYwy4Q=="
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.3.1.tgz",
|
||||
"integrity": "sha512-HJX/2xgGPt01YHQNAya2taqg5HDnY6ipF0B0Bk/Th2s4FD9R89Ecw3TEegLLO+VrQnPEGbuHOGJ6Fr3BPM0DYg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
|
@ -892,15 +905,6 @@
|
|||
"lit-html": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
|
||||
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
|
||||
|
@ -14821,6 +14825,19 @@
|
|||
"@types/chai": "^4.2.12"
|
||||
}
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.5.1.tgz",
|
||||
"integrity": "sha512-LOFg65vdU4o4eZjVKNhu7VXbjdnY0amMw9sydLtKZiCNwj0eMD06rmktbWp9L/f6w6s9Qqe22lkks8PbwYwy4Q=="
|
||||
},
|
||||
"@floating-ui/dom": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.3.1.tgz",
|
||||
"integrity": "sha512-HJX/2xgGPt01YHQNAya2taqg5HDnY6ipF0B0Bk/Th2s4FD9R89Ecw3TEegLLO+VrQnPEGbuHOGJ6Fr3BPM0DYg==",
|
||||
"requires": {
|
||||
"@floating-ui/core": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
|
@ -15031,11 +15048,6 @@
|
|||
"lit-html": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz",
|
||||
"integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA=="
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
|
||||
|
|
|
@ -52,8 +52,8 @@
|
|||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.3.1",
|
||||
"@lit-labs/react": "^1.0.2",
|
||||
"@popperjs/core": "^2.11.2",
|
||||
"@shoelace-style/animations": "^1.1.0",
|
||||
"@shoelace-style/localize": "^2.1.3",
|
||||
"color": "4.2",
|
||||
|
|
|
@ -64,7 +64,7 @@ fs.mkdirSync(outdir, { recursive: true });
|
|||
chunkNames: 'chunks/[name].[hash]',
|
||||
incremental: serve,
|
||||
define: {
|
||||
// Popper.js requires this to be set
|
||||
// Floating UI requires this to be set
|
||||
'process.env.NODE_ENV': '"production"'
|
||||
},
|
||||
bundle: true,
|
||||
|
@ -76,7 +76,7 @@ fs.mkdirSync(outdir, { recursive: true });
|
|||
//
|
||||
external: bundle
|
||||
? alwaysExternal
|
||||
: [...alwaysExternal, '@popperjs/core', '@shoelace-style/animations', 'lit', 'qr-creator'],
|
||||
: [...alwaysExternal, '@floating-ui/dom', '@shoelace-style/animations', 'lit', 'qr-creator'],
|
||||
splitting: true,
|
||||
plugins: []
|
||||
})
|
||||
|
|
|
@ -22,7 +22,6 @@ export default css`
|
|||
}
|
||||
|
||||
.dropdown__panel {
|
||||
max-height: 75vh;
|
||||
font-family: var(--sl-font-sans);
|
||||
font-size: var(--sl-font-size-medium);
|
||||
font-weight: var(--sl-font-weight-normal);
|
||||
|
@ -40,19 +39,19 @@ export default css`
|
|||
pointer-events: all;
|
||||
}
|
||||
|
||||
.dropdown__positioner[data-popper-placement^='top'] .dropdown__panel {
|
||||
.dropdown__positioner[data-placement^='top'] .dropdown__panel {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.dropdown__positioner[data-popper-placement^='bottom'] .dropdown__panel {
|
||||
.dropdown__positioner[data-placement^='bottom'] .dropdown__panel {
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.dropdown__positioner[data-popper-placement^='left'] .dropdown__panel {
|
||||
.dropdown__positioner[data-placement^='left'] .dropdown__panel {
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.dropdown__positioner[data-popper-placement^='right'] .dropdown__panel {
|
||||
.dropdown__positioner[data-placement^='right'] .dropdown__panel {
|
||||
transform-origin: left;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createPopper } from '@popperjs/core/dist/esm';
|
||||
import { autoUpdate, computePosition, flip, offset, shift, size } from '@floating-ui/dom';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
@ -11,7 +11,6 @@ import { getTabbableBoundary } from '~/internal/tabbable';
|
|||
import { watch } from '~/internal/watch';
|
||||
import { getAnimation, setDefaultAnimation } from '~/utilities/animation-registry';
|
||||
import styles from './dropdown.styles';
|
||||
import type { Instance as PopperInstance } from '@popperjs/core/dist/esm';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
|
@ -40,7 +39,7 @@ export default class SlDropdown extends LitElement {
|
|||
@query('.dropdown__panel') panel: HTMLElement;
|
||||
@query('.dropdown__positioner') positioner: HTMLElement;
|
||||
|
||||
private popover?: PopperInstance;
|
||||
private positionerCleanup: ReturnType<typeof autoUpdate> | undefined;
|
||||
|
||||
/** Indicates whether or not the dropdown is open. You can use this in lieu of the show/hide methods. */
|
||||
@property({ type: Boolean, reflect: true }) open = false;
|
||||
|
@ -49,7 +48,7 @@ export default class SlDropdown extends LitElement {
|
|||
* The preferred placement of the dropdown panel. Note that the actual placement may vary as needed to keep the panel
|
||||
* inside of the viewport.
|
||||
*/
|
||||
@property() placement:
|
||||
@property({ reflect: true }) placement:
|
||||
| 'top'
|
||||
| 'top-start'
|
||||
| 'top-end'
|
||||
|
@ -97,39 +96,22 @@ export default class SlDropdown extends LitElement {
|
|||
if (!this.containingElement) {
|
||||
this.containingElement = this;
|
||||
}
|
||||
|
||||
// Create the popover after render
|
||||
this.updateComplete.then(() => {
|
||||
this.popover = createPopper(this.trigger, this.positioner, {
|
||||
placement: this.placement,
|
||||
strategy: this.hoist ? 'fixed' : 'absolute',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
boundary: 'viewport'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [this.skidding, this.distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
async firstUpdated() {
|
||||
this.panel.hidden = !this.open;
|
||||
|
||||
// If the dropdown is visible on init, update its position
|
||||
if (this.open) {
|
||||
await this.updateComplete;
|
||||
this.startPositioner();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.hide();
|
||||
|
||||
this.popover?.destroy();
|
||||
this.stopPositioner();
|
||||
}
|
||||
|
||||
focusOnTrigger() {
|
||||
|
@ -213,24 +195,7 @@ export default class SlDropdown extends LitElement {
|
|||
@watch('placement')
|
||||
@watch('skidding')
|
||||
handlePopoverOptionsChange() {
|
||||
this.popover?.setOptions({
|
||||
placement: this.placement,
|
||||
strategy: this.hoist ? 'fixed' : 'absolute',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
boundary: 'viewport'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [this.skidding, this.distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
this.updatePositioner();
|
||||
}
|
||||
|
||||
handleTriggerClick() {
|
||||
|
@ -351,11 +316,7 @@ export default class SlDropdown extends LitElement {
|
|||
* is activated.
|
||||
*/
|
||||
reposition() {
|
||||
if (!this.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.popover?.update();
|
||||
this.updatePositioner();
|
||||
}
|
||||
|
||||
@watch('open', { waitUntilFirstUpdate: true })
|
||||
|
@ -376,7 +337,7 @@ export default class SlDropdown extends LitElement {
|
|||
document.addEventListener('mousedown', this.handleDocumentMouseDown);
|
||||
|
||||
await stopAnimations(this);
|
||||
this.popover?.update();
|
||||
this.startPositioner();
|
||||
this.panel.hidden = false;
|
||||
const { keyframes, options } = getAnimation(this, 'dropdown.show');
|
||||
await animateTo(this.panel, keyframes, options);
|
||||
|
@ -394,11 +355,60 @@ export default class SlDropdown extends LitElement {
|
|||
const { keyframes, options } = getAnimation(this, 'dropdown.hide');
|
||||
await animateTo(this.panel, keyframes, options);
|
||||
this.panel.hidden = true;
|
||||
this.stopPositioner();
|
||||
|
||||
emit(this, 'sl-after-hide');
|
||||
}
|
||||
}
|
||||
|
||||
private startPositioner() {
|
||||
this.stopPositioner();
|
||||
this.updatePositioner();
|
||||
this.positionerCleanup = autoUpdate(this.trigger, this.positioner, this.updatePositioner.bind(this));
|
||||
}
|
||||
|
||||
private updatePositioner() {
|
||||
if (!this.open || !this.trigger || !this.positioner) {
|
||||
return;
|
||||
}
|
||||
|
||||
computePosition(this.trigger, this.positioner, {
|
||||
placement: this.placement,
|
||||
middleware: [
|
||||
offset({ mainAxis: this.distance, crossAxis: this.skidding }),
|
||||
flip(),
|
||||
shift(),
|
||||
size({
|
||||
apply: ({ width, height }) => {
|
||||
// Ensure the panel stays within the viewport when we have lots of menu items
|
||||
Object.assign(this.panel.style, {
|
||||
maxWidth: `${width}px`,
|
||||
maxHeight: `${height}px`
|
||||
});
|
||||
},
|
||||
padding: 8
|
||||
})
|
||||
],
|
||||
strategy: this.hoist ? 'fixed' : 'absolute'
|
||||
}).then(({ x, y, placement }) => {
|
||||
this.positioner.setAttribute('data-placement', placement);
|
||||
|
||||
Object.assign(this.positioner.style, {
|
||||
position: this.hoist ? 'fixed' : 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private stopPositioner() {
|
||||
if (this.positionerCleanup) {
|
||||
this.positionerCleanup();
|
||||
this.positionerCleanup = undefined;
|
||||
this.positioner.removeAttribute('data-placement');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div
|
||||
|
|
|
@ -78,8 +78,13 @@ export default class SlMenu extends LitElement {
|
|||
typeToSelect(key: string) {
|
||||
const items = this.getAllItems({ includeDisabled: false });
|
||||
clearTimeout(this.typeToSelectTimeout);
|
||||
this.typeToSelectTimeout = window.setTimeout(() => (this.typeToSelectString = ''), 750);
|
||||
this.typeToSelectString += key.toLowerCase();
|
||||
this.typeToSelectTimeout = window.setTimeout(() => (this.typeToSelectString = ''), 1000);
|
||||
|
||||
if (key === 'Backspace') {
|
||||
this.typeToSelectString = this.typeToSelectString.slice(0, -1);
|
||||
} else {
|
||||
this.typeToSelectString += key.toLowerCase();
|
||||
}
|
||||
|
||||
// Restore focus in browsers that don't support :focus-visible when using the keyboard
|
||||
if (!hasFocusVisible) {
|
||||
|
|
|
@ -12,7 +12,7 @@ export default css`
|
|||
display: contents;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
.tooltip-target {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,23 @@ export default css`
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
.tooltip-positioner[data-placement^='top'] .tooltip {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-placement^='bottom'] .tooltip {
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-placement^='left'] .tooltip {
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-placement^='right'] .tooltip {
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
.tooltip__content {
|
||||
max-width: var(--max-width);
|
||||
border-radius: var(--sl-tooltip-border-radius);
|
||||
background-color: var(--sl-tooltip-background-color);
|
||||
|
@ -34,98 +50,12 @@ export default css`
|
|||
padding: var(--sl-tooltip-padding);
|
||||
}
|
||||
|
||||
.tooltip:after {
|
||||
content: '';
|
||||
.tooltip__arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement^='top'] .tooltip {
|
||||
transform-origin: bottom;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement^='bottom'] .tooltip {
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement^='left'] .tooltip {
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement^='right'] .tooltip {
|
||||
transform-origin: left;
|
||||
}
|
||||
|
||||
/* Arrow + bottom */
|
||||
.tooltip-positioner[data-popper-placement^='bottom'] .tooltip:after {
|
||||
bottom: 100%;
|
||||
left: calc(50% - var(--sl-tooltip-arrow-size));
|
||||
border-bottom: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
|
||||
border-left: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
border-right: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='bottom-start'] .tooltip:after {
|
||||
left: var(--sl-tooltip-arrow-start-end-offset);
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='bottom-end'] .tooltip:after {
|
||||
right: var(--sl-tooltip-arrow-start-end-offset);
|
||||
left: auto;
|
||||
}
|
||||
|
||||
/* Arrow + top */
|
||||
.tooltip-positioner[data-popper-placement^='top'] .tooltip:after {
|
||||
top: 100%;
|
||||
left: calc(50% - var(--sl-tooltip-arrow-size));
|
||||
border-top: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
|
||||
border-left: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
border-right: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='top-start'] .tooltip:after {
|
||||
left: var(--sl-tooltip-arrow-start-end-offset);
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='top-end'] .tooltip:after {
|
||||
right: var(--sl-tooltip-arrow-start-end-offset);
|
||||
left: auto;
|
||||
}
|
||||
|
||||
/* Arrow + left */
|
||||
.tooltip-positioner[data-popper-placement^='left'] .tooltip:after {
|
||||
top: calc(50% - var(--sl-tooltip-arrow-size));
|
||||
left: 100%;
|
||||
border-left: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
|
||||
border-top: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
border-bottom: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='left-start'] .tooltip:after {
|
||||
top: var(--sl-tooltip-arrow-start-end-offset);
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='left-end'] .tooltip:after {
|
||||
top: auto;
|
||||
bottom: var(--sl-tooltip-arrow-start-end-offset);
|
||||
}
|
||||
|
||||
/* Arrow + right */
|
||||
.tooltip-positioner[data-popper-placement^='right'] .tooltip:after {
|
||||
top: calc(50% - var(--sl-tooltip-arrow-size));
|
||||
right: 100%;
|
||||
border-right: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
|
||||
border-top: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
border-bottom: var(--sl-tooltip-arrow-size) solid transparent;
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='right-start'] .tooltip:after {
|
||||
top: var(--sl-tooltip-arrow-start-end-offset);
|
||||
}
|
||||
|
||||
.tooltip-positioner[data-popper-placement='right-end'] .tooltip:after {
|
||||
top: auto;
|
||||
bottom: var(--sl-tooltip-arrow-start-end-offset);
|
||||
background: var(--sl-tooltip-background-color);
|
||||
width: calc(var(--sl-tooltip-arrow-size) * 2);
|
||||
height: calc(var(--sl-tooltip-arrow-size) * 2);
|
||||
transform: rotate(45deg);
|
||||
z-index: -1;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createPopper } from '@popperjs/core/dist/esm';
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
import { html, LitElement } from 'lit';
|
||||
import { customElement, property, query } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
|
@ -7,7 +7,6 @@ import { emit, waitForEvent } from '~/internal/event';
|
|||
import { watch } from '~/internal/watch';
|
||||
import { getAnimation, setDefaultAnimation } from '~/utilities/animation-registry';
|
||||
import styles from './tooltip.styles';
|
||||
import type { Instance as PopperInstance } from '@popperjs/core/dist/esm';
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
|
@ -36,10 +35,11 @@ export default class SlTooltip extends LitElement {
|
|||
|
||||
@query('.tooltip-positioner') positioner: HTMLElement;
|
||||
@query('.tooltip') tooltip: HTMLElement;
|
||||
@query('.tooltip__arrow') arrow: HTMLElement;
|
||||
|
||||
private target: HTMLElement;
|
||||
private popover?: PopperInstance;
|
||||
private hoverTimeout: number;
|
||||
private positionerCleanup: ReturnType<typeof autoUpdate> | undefined;
|
||||
|
||||
/** The tooltip's content. Alternatively, you can use the content slot. */
|
||||
@property() content = '';
|
||||
|
@ -103,14 +103,18 @@ export default class SlTooltip extends LitElement {
|
|||
this.addEventListener('keydown', this.handleKeyDown);
|
||||
this.addEventListener('mouseover', this.handleMouseOver);
|
||||
this.addEventListener('mouseout', this.handleMouseOut);
|
||||
|
||||
this.target = this.getTarget();
|
||||
this.syncOptions();
|
||||
});
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
async firstUpdated() {
|
||||
this.tooltip.hidden = !this.open;
|
||||
|
||||
// If the tooltip is visible on init, update its position
|
||||
if (this.open) {
|
||||
await this.updateComplete;
|
||||
this.updatePositioner();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
|
@ -121,8 +125,7 @@ export default class SlTooltip extends LitElement {
|
|||
this.removeEventListener('keydown', this.handleKeyDown);
|
||||
this.removeEventListener('mouseover', this.handleMouseOver);
|
||||
this.removeEventListener('mouseout', this.handleMouseOut);
|
||||
|
||||
this.popover?.destroy();
|
||||
this.stopPositioner();
|
||||
}
|
||||
|
||||
/** Shows the tooltip. */
|
||||
|
@ -215,28 +218,7 @@ export default class SlTooltip extends LitElement {
|
|||
emit(this, 'sl-show');
|
||||
|
||||
await stopAnimations(this.tooltip);
|
||||
|
||||
this.popover?.destroy();
|
||||
|
||||
this.popover = createPopper(this.target, this.positioner, {
|
||||
placement: this.placement,
|
||||
strategy: this.hoist ? 'fixed' : 'absolute',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
boundary: 'viewport'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [this.skidding, this.distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
this.startPositioner();
|
||||
this.tooltip.hidden = false;
|
||||
const { keyframes, options } = getAnimation(this, 'tooltip.show');
|
||||
await animateTo(this.tooltip, keyframes, options);
|
||||
|
@ -250,26 +232,19 @@ export default class SlTooltip extends LitElement {
|
|||
const { keyframes, options } = getAnimation(this, 'tooltip.hide');
|
||||
await animateTo(this.tooltip, keyframes, options);
|
||||
this.tooltip.hidden = true;
|
||||
|
||||
this.popover?.destroy();
|
||||
this.stopPositioner();
|
||||
|
||||
emit(this, 'sl-after-hide');
|
||||
}
|
||||
}
|
||||
|
||||
@watch('placement')
|
||||
@watch('distance')
|
||||
@watch('skidding')
|
||||
@watch('hoist')
|
||||
handleOptionsChange() {
|
||||
this.syncOptions();
|
||||
}
|
||||
|
||||
@watch('content')
|
||||
handleContentChange() {
|
||||
if (this.open) {
|
||||
this.popover?.update();
|
||||
}
|
||||
@watch('distance')
|
||||
@watch('hoist')
|
||||
@watch('placement')
|
||||
@watch('skidding')
|
||||
handleOptionsChange() {
|
||||
this.updatePositioner();
|
||||
}
|
||||
|
||||
@watch('disabled')
|
||||
|
@ -284,30 +259,63 @@ export default class SlTooltip extends LitElement {
|
|||
return triggers.includes(triggerType);
|
||||
}
|
||||
|
||||
syncOptions() {
|
||||
this.popover?.setOptions({
|
||||
private startPositioner() {
|
||||
this.stopPositioner();
|
||||
this.updatePositioner();
|
||||
this.positionerCleanup = autoUpdate(this.target, this.positioner, this.updatePositioner.bind(this));
|
||||
}
|
||||
|
||||
private updatePositioner() {
|
||||
if (!this.open || !this.target || !this.positioner) {
|
||||
return;
|
||||
}
|
||||
|
||||
computePosition(this.target, this.positioner, {
|
||||
placement: this.placement,
|
||||
strategy: this.hoist ? 'fixed' : 'absolute',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
boundary: 'viewport'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [this.skidding, this.distance]
|
||||
}
|
||||
}
|
||||
]
|
||||
middleware: [
|
||||
offset({ mainAxis: this.distance, crossAxis: this.skidding }),
|
||||
flip(),
|
||||
shift(),
|
||||
arrow({
|
||||
element: this.arrow,
|
||||
padding: 10 // min distance from the edge
|
||||
})
|
||||
],
|
||||
strategy: this.hoist ? 'fixed' : 'absolute'
|
||||
}).then(({ x, y, middlewareData, placement }) => {
|
||||
const arrowX = middlewareData.arrow!.x;
|
||||
const arrowY = middlewareData.arrow!.y;
|
||||
const staticSide = { top: 'bottom', right: 'left', bottom: 'top', left: 'right' }[placement.split('-')[0]]!;
|
||||
|
||||
this.positioner.setAttribute('data-placement', placement);
|
||||
|
||||
Object.assign(this.positioner.style, {
|
||||
position: this.hoist ? 'fixed' : 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`
|
||||
});
|
||||
|
||||
Object.assign(this.arrow.style, {
|
||||
left: typeof arrowX === 'number' ? `${arrowX}px` : '',
|
||||
top: typeof arrowY === 'number' ? `${arrowY}px` : '',
|
||||
right: '',
|
||||
bottom: '',
|
||||
[staticSide]: 'calc(var(--sl-tooltip-arrow-size) * -1)'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private stopPositioner() {
|
||||
if (this.positionerCleanup) {
|
||||
this.positionerCleanup();
|
||||
this.positionerCleanup = undefined;
|
||||
this.positioner.removeAttribute('data-placement');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="tooltip-content" aria-describedby="tooltip">
|
||||
<div class="tooltip-target" aria-describedby="tooltip">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
|
@ -322,7 +330,10 @@ export default class SlTooltip extends LitElement {
|
|||
role="tooltip"
|
||||
aria-hidden=${this.open ? 'false' : 'true'}
|
||||
>
|
||||
<slot name="content"> ${this.content} </slot>
|
||||
<div class="tooltip__arrow"></div>
|
||||
<div class="tooltip__content">
|
||||
<slot name="content"> ${this.content} </slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
declare module '@popperjs/core/dist/esm' {
|
||||
export * from '@popperjs/core/lib';
|
||||
}
|
||||
|
||||
declare module '*.css' {
|
||||
const styles: string;
|
||||
export default styles;
|
||||
|
|
|
@ -505,8 +505,7 @@
|
|||
--sl-tooltip-font-size: var(--sl-font-size-small);
|
||||
--sl-tooltip-line-height: var(--sl-line-height-dense);
|
||||
--sl-tooltip-padding: var(--sl-spacing-2x-small) var(--sl-spacing-x-small);
|
||||
--sl-tooltip-arrow-size: 5px;
|
||||
--sl-tooltip-arrow-start-end-offset: 8px;
|
||||
--sl-tooltip-arrow-size: 4px;
|
||||
|
||||
/*
|
||||
* Z-indexes
|
||||
|
|
|
@ -505,8 +505,7 @@
|
|||
--sl-tooltip-font-size: var(--sl-font-size-small);
|
||||
--sl-tooltip-line-height: var(--sl-line-height-dense);
|
||||
--sl-tooltip-padding: var(--sl-spacing-2x-small) var(--sl-spacing-x-small);
|
||||
--sl-tooltip-arrow-size: 5px;
|
||||
--sl-tooltip-arrow-start-end-offset: 8px;
|
||||
--sl-tooltip-arrow-size: 4px;
|
||||
|
||||
/*
|
||||
* Z-indexes
|
||||
|
|
Ładowanie…
Reference in New Issue