|
1 | 1 | <script lang="ts"> |
| 2 | + import { tick } from 'svelte'; |
2 | 3 | import type { Placement } from '@floating-ui/dom'; |
3 | 4 | import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'; |
4 | | - import { onMount } from 'svelte'; |
5 | 5 |
|
6 | 6 | export let placement: Placement | undefined = undefined; |
7 | 7 | export let padding: 'none' | 'm' = 'm'; |
|
25 | 25 | } |
26 | 26 |
|
27 | 27 | async function update() { |
| 28 | + if (!referenceElement || !tooltipElement) return; |
| 29 | +
|
28 | 30 | const firstChild = referenceElement.firstElementChild; |
29 | 31 | if (!(firstChild instanceof HTMLElement)) { |
30 | 32 | return; |
31 | 33 | } |
| 34 | +
|
32 | 35 | const { x, y } = await computePosition(firstChild, tooltipElement, { |
33 | 36 | placement, |
34 | 37 | middleware: [offset(offsetAmount), flip(), shift()] |
|
40 | 43 | }); |
41 | 44 | } |
42 | 45 |
|
43 | | - onMount(() => autoUpdate(referenceElement, tooltipElement, update)); |
| 46 | + function fadeSlide(_: Node, { y = 8, duration = 200 } = {}) { |
| 47 | + return { |
| 48 | + duration, |
| 49 | + css: (time: number) => ` |
| 50 | + opacity: ${time}; |
| 51 | + transform: translateY(${(1 - time) * y}px); |
| 52 | + ` |
| 53 | + }; |
| 54 | + } |
| 55 | +
|
| 56 | + function autoUpdateAction(_: HTMLDivElement) { |
| 57 | + tick().then(() => { |
| 58 | + if (!referenceElement || !tooltipElement) return; |
| 59 | +
|
| 60 | + const cleanup = autoUpdate(referenceElement, tooltipElement, update); |
| 61 | + return { destroy: cleanup }; |
| 62 | + }); |
| 63 | + } |
44 | 64 | </script> |
45 | 65 |
|
46 | 66 | <svelte:window on:resize={update} /> |
|
57 | 77 | > |
58 | 78 | <slot {showing} {update} /> |
59 | 79 | </span> |
60 | | -<div |
61 | | - {id} |
62 | | - on:transitionend={() => (showing = false)} |
63 | | - bind:this={tooltipElement} |
64 | | - aria-hidden={!show} |
65 | | - class:padding-none={padding === 'none'} |
66 | | - class:padding-m={padding === 'm'} |
67 | | - role="tooltip" |
68 | | - style:max-inline-size={maxWidth} |
69 | | - data-state={!show ? 'closed' : 'open'} |
70 | | -> |
71 | | - <slot {showing} {update} name="tooltip" /> |
72 | | -</div> |
| 80 | + |
| 81 | +{#if show} |
| 82 | + <div |
| 83 | + {id} |
| 84 | + transition:fadeSlide |
| 85 | + use:autoUpdateAction |
| 86 | + on:transitionend={() => (showing = false)} |
| 87 | + bind:this={tooltipElement} |
| 88 | + aria-hidden={!show} |
| 89 | + class:padding-none={padding === 'none'} |
| 90 | + class:padding-m={padding === 'm'} |
| 91 | + role="tooltip" |
| 92 | + style:max-inline-size={maxWidth} |
| 93 | + data-state={!show ? 'closed' : 'open'} |
| 94 | + > |
| 95 | + <slot {showing} {update} name="tooltip" /> |
| 96 | + </div> |
| 97 | +{/if} |
73 | 98 |
|
74 | 99 | <style lang="scss"> |
75 | 100 | [role='note'] { |
|
88 | 113 | color: var(--fgcolor-on-invert); |
89 | 114 | visibility: hidden; |
90 | 115 | opacity: 0; |
91 | | - transition: visibility 0s linear 0.2s; |
92 | 116 | z-index: 9002; |
93 | 117 |
|
94 | 118 | &[aria-hidden='false'] { |
|
104 | 128 | padding: var(--space-2) var(--space-4); |
105 | 129 | } |
106 | 130 | } |
107 | | -
|
108 | | - &[data-state='open'] { |
109 | | - animation: pink-tooltip-enter 0.2s ease-out; |
110 | | - } |
111 | | -
|
112 | | - &[data-state='closed'] { |
113 | | - animation: pink-tooltip-exit 0.2s ease-out; |
114 | | - } |
115 | | - } |
116 | | - @keyframes pink-tooltip-enter { |
117 | | - from { |
118 | | - opacity: 0; |
119 | | - transform: translateY(0.5rem); |
120 | | - } |
121 | | - to { |
122 | | - opacity: 1; |
123 | | - transform: translateY(0); |
124 | | - } |
125 | | - } |
126 | | -
|
127 | | - @keyframes pink-tooltip-exit { |
128 | | - from { |
129 | | - opacity: 1; |
130 | | - transform: translateY(0); |
131 | | - } |
132 | | - to { |
133 | | - opacity: 0; |
134 | | - transform: translateY(0.5rem); |
135 | | - } |
136 | 131 | } |
137 | 132 | </style> |
0 commit comments