(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) {
diff --git a/src/components/tooltip/tooltip.styles.ts b/src/components/tooltip/tooltip.styles.ts
index cea114330..a78017fc4 100644
--- a/src/components/tooltip/tooltip.styles.ts
+++ b/src/components/tooltip/tooltip.styles.ts
@@ -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;
}
`;
diff --git a/src/components/tooltip/tooltip.ts b/src/components/tooltip/tooltip.ts
index 0827775a7..c34bca35e 100644
--- a/src/components/tooltip/tooltip.ts
+++ b/src/components/tooltip/tooltip.ts
@@ -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
| 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`
-
+
@@ -322,7 +330,10 @@ export default class SlTooltip extends LitElement {
role="tooltip"
aria-hidden=${this.open ? 'false' : 'true'}
>
-
${this.content}
+
+
+ ${this.content}
+
`;
diff --git a/src/declaration.d.ts b/src/declaration.d.ts
index 08227969d..d16b948d5 100644
--- a/src/declaration.d.ts
+++ b/src/declaration.d.ts
@@ -1,7 +1,3 @@
-declare module '@popperjs/core/dist/esm' {
- export * from '@popperjs/core/lib';
-}
-
declare module '*.css' {
const styles: string;
export default styles;
diff --git a/src/themes/dark.css b/src/themes/dark.css
index cc3a07b6b..a45f4b08d 100644
--- a/src/themes/dark.css
+++ b/src/themes/dark.css
@@ -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
diff --git a/src/themes/light.css b/src/themes/light.css
index acbfc3eb7..874670ffd 100644
--- a/src/themes/light.css
+++ b/src/themes/light.css
@@ -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