@finsweet/webflow-apps-utils 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ui/components/button/Button.svelte +1 -1
- package/dist/ui/components/button/types.d.ts +1 -1
- package/dist/ui/components/color-picker/ColorPicker.svelte +1 -1
- package/dist/ui/components/controlled-buttons/ControlledButtons.svelte +2 -2
- package/dist/ui/components/layout/examples/ExampleLayout.svelte +41 -0
- package/dist/ui/components/section/Section.svelte +1 -1
- package/dist/ui/components/text/Text.svelte +2 -2
- package/dist/ui/components/tooltip/TestTooltipWrapper.svelte +48 -0
- package/dist/ui/components/tooltip/TestTooltipWrapper.svelte.d.ts +16 -0
- package/dist/ui/components/tooltip/Tooltip.svelte +228 -268
- package/dist/ui/components/tooltip/Tooltip.svelte.d.ts +33 -4
- package/dist/ui/components/tooltip/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
let hasRightIcon = $derived(!!rightIcon);
|
|
123
123
|
let hasText = $derived(!!text);
|
|
124
124
|
let currentText = $derived(loading ? loadingText : text);
|
|
125
|
-
let shouldShowTooltip = $derived(!!tooltip?.message || !!tooltip?.
|
|
125
|
+
let shouldShowTooltip = $derived(!!tooltip?.message || !!tooltip?.tooltip);
|
|
126
126
|
|
|
127
127
|
// Default styling (no size variations)
|
|
128
128
|
let computedPadding = $derived(padding || '4px 8px');
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
<div class="color-swatch" style="background-color: {color || '#000000'}"></div>
|
|
172
172
|
</div>
|
|
173
173
|
{/snippet}
|
|
174
|
-
{#snippet
|
|
174
|
+
{#snippet tooltip()}
|
|
175
175
|
<ColorSelect {color} oncolorchange={handleFullColorChange} />
|
|
176
176
|
{/snippet}
|
|
177
177
|
</Tooltip>
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
class="popup-button-chevron"
|
|
35
35
|
/>
|
|
36
36
|
{/snippet}
|
|
37
|
-
{#snippet
|
|
37
|
+
{#snippet tooltip()}
|
|
38
38
|
<div role="button" tabindex="0" class="popup-content">
|
|
39
39
|
<div class="popup-content-list">
|
|
40
40
|
{#each button.popupButtons || [] as popupButton, popupIndex (popupIndex)}
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
onclick={button.onClick || (() => {})}
|
|
95
95
|
/>
|
|
96
96
|
{/snippet}
|
|
97
|
-
{#snippet
|
|
97
|
+
{#snippet tooltip()}
|
|
98
98
|
<div
|
|
99
99
|
class="tooltip-content {button.tooltip?.className || ''}"
|
|
100
100
|
style="display: {button.tooltip?.icon ? 'grid' : 'inline-block'}"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { Section } from '../../section';
|
|
11
11
|
import { Switch } from '../../switch';
|
|
12
12
|
import Text from '../../text/Text.svelte';
|
|
13
|
+
import { Tooltip } from '../../tooltip';
|
|
13
14
|
import Layout from '../Layout.svelte';
|
|
14
15
|
import type { LayoutNotification, LayoutTab } from '../types';
|
|
15
16
|
|
|
@@ -309,6 +310,40 @@
|
|
|
309
310
|
}}
|
|
310
311
|
width="130px"
|
|
311
312
|
/>
|
|
313
|
+
|
|
314
|
+
<Tooltip
|
|
315
|
+
onshow={(value) => console.log('Tooltip shown:', value)}
|
|
316
|
+
onclose={(value) => console.log('Tooltip closed:', value)}
|
|
317
|
+
>
|
|
318
|
+
{#snippet target()}
|
|
319
|
+
<button>Hover me</button>
|
|
320
|
+
{/snippet}
|
|
321
|
+
|
|
322
|
+
{#snippet tooltip()}
|
|
323
|
+
<div>Custom tooltip content here!</div>
|
|
324
|
+
{/snippet}
|
|
325
|
+
</Tooltip>
|
|
326
|
+
|
|
327
|
+
<Tooltip
|
|
328
|
+
onshow={(value) => console.log('Tooltip shown:', value)}
|
|
329
|
+
onclose={(value) => console.log('Tooltip closed:', value)}
|
|
330
|
+
listener="click"
|
|
331
|
+
listenerout="click"
|
|
332
|
+
stopPropagation={false}
|
|
333
|
+
>
|
|
334
|
+
{#snippet target()}
|
|
335
|
+
<button>Click me</button>
|
|
336
|
+
{/snippet}
|
|
337
|
+
|
|
338
|
+
{#snippet tooltip()}
|
|
339
|
+
<div class="click-tests">
|
|
340
|
+
<Text link onclick={() => console.log('Tooltip clicked')}>Click me</Text>
|
|
341
|
+
<Text link onclick={() => console.log('Tooltip another click')}
|
|
342
|
+
>Another click me</Text
|
|
343
|
+
>
|
|
344
|
+
</div>
|
|
345
|
+
{/snippet}
|
|
346
|
+
</Tooltip>
|
|
312
347
|
</div>
|
|
313
348
|
<Section clickable disabled>
|
|
314
349
|
<Text fontSize="md" fontWeight="normal">Clickable disabled</Text>
|
|
@@ -343,6 +378,12 @@
|
|
|
343
378
|
</div>
|
|
344
379
|
|
|
345
380
|
<style>
|
|
381
|
+
.click-tests {
|
|
382
|
+
display: flex;
|
|
383
|
+
flex-direction: column;
|
|
384
|
+
gap: 8px;
|
|
385
|
+
}
|
|
386
|
+
|
|
346
387
|
/* Main container layout */
|
|
347
388
|
.example-container {
|
|
348
389
|
display: flex;
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
let ariaDisabled = $derived(clickable ? disabled : undefined);
|
|
122
122
|
|
|
123
123
|
// Determine tooltip configuration
|
|
124
|
-
let shouldShowTooltip = $derived(!!tooltip?.message || !!tooltip?.
|
|
124
|
+
let shouldShowTooltip = $derived(!!tooltip?.message || !!tooltip?.tooltip);
|
|
125
125
|
let shouldShowDisabledTooltip = $derived(!!disabledMessage);
|
|
126
126
|
let hasAnyTooltip = $derived(shouldShowTooltip || shouldShowDisabledTooltip);
|
|
127
127
|
|
|
@@ -282,7 +282,7 @@
|
|
|
282
282
|
let shouldShowTooltip = $derived(
|
|
283
283
|
!!tooltip &&
|
|
284
284
|
Object.keys(tooltip).length > 0 &&
|
|
285
|
-
(tooltip.message || tooltip.
|
|
285
|
+
(tooltip.message || tooltip.tooltip) &&
|
|
286
286
|
!disabled &&
|
|
287
287
|
(isPopupHidden || !hasPopup)
|
|
288
288
|
);
|
|
@@ -784,7 +784,7 @@
|
|
|
784
784
|
{/if}
|
|
785
785
|
</span>
|
|
786
786
|
{/snippet}
|
|
787
|
-
{#snippet
|
|
787
|
+
{#snippet tooltip()}
|
|
788
788
|
<div
|
|
789
789
|
class="popup-wrapper"
|
|
790
790
|
style={!popupConfig.active ? 'display:none' : ''}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Placement } from '@floating-ui/dom';
|
|
3
|
+
|
|
4
|
+
import Tooltip from './Tooltip.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
message?: string;
|
|
8
|
+
hidden?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
listener?: 'click' | 'hover';
|
|
11
|
+
listenerout?: 'click' | 'hover';
|
|
12
|
+
placement?: Placement;
|
|
13
|
+
bgColor?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
onshow?: (event: boolean) => void;
|
|
16
|
+
onclose?: (event: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
message = '',
|
|
21
|
+
hidden = false,
|
|
22
|
+
disabled = false,
|
|
23
|
+
listener = 'hover',
|
|
24
|
+
listenerout = 'hover',
|
|
25
|
+
placement = 'right',
|
|
26
|
+
bgColor,
|
|
27
|
+
class: className,
|
|
28
|
+
onshow,
|
|
29
|
+
onclose
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<Tooltip
|
|
34
|
+
{message}
|
|
35
|
+
{hidden}
|
|
36
|
+
{disabled}
|
|
37
|
+
{listener}
|
|
38
|
+
{listenerout}
|
|
39
|
+
{placement}
|
|
40
|
+
{bgColor}
|
|
41
|
+
class={className}
|
|
42
|
+
{onshow}
|
|
43
|
+
{onclose}
|
|
44
|
+
>
|
|
45
|
+
{#snippet target()}
|
|
46
|
+
<button>Hover me</button>
|
|
47
|
+
{/snippet}
|
|
48
|
+
</Tooltip>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Placement } from '@floating-ui/dom';
|
|
2
|
+
interface Props {
|
|
3
|
+
message?: string;
|
|
4
|
+
hidden?: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
listener?: 'click' | 'hover';
|
|
7
|
+
listenerout?: 'click' | 'hover';
|
|
8
|
+
placement?: Placement;
|
|
9
|
+
bgColor?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
onshow?: (event: boolean) => void;
|
|
12
|
+
onclose?: (event: boolean) => void;
|
|
13
|
+
}
|
|
14
|
+
declare const TestTooltipWrapper: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type TestTooltipWrapper = ReturnType<typeof TestTooltipWrapper>;
|
|
16
|
+
export default TestTooltipWrapper;
|
|
@@ -8,23 +8,51 @@
|
|
|
8
8
|
type Placement,
|
|
9
9
|
shift
|
|
10
10
|
} from '@floating-ui/dom';
|
|
11
|
-
import { tick } from 'svelte';
|
|
11
|
+
import { type Component, onDestroy, onMount, type Snippet, tick } from 'svelte';
|
|
12
|
+
import { writable } from 'svelte/store';
|
|
12
13
|
import { v4 as uuidv4 } from 'uuid';
|
|
13
14
|
|
|
14
|
-
import { cleanupTooltipMessage } from '../../../utils
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
import { cleanupTooltipMessage } from '../../../utils';
|
|
16
|
+
|
|
17
|
+
import Text from '../text/Text.svelte';
|
|
18
|
+
|
|
19
|
+
const activeTooltips = writable<string[]>([]);
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
message?: string;
|
|
23
|
+
listener?: 'click' | 'hover';
|
|
24
|
+
listenerout?: 'click' | 'hover';
|
|
25
|
+
placement?: Placement;
|
|
26
|
+
position?: string;
|
|
27
|
+
showArrow?: boolean;
|
|
28
|
+
offsetVal?: number;
|
|
29
|
+
hidden?: boolean;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
tooltipIcon?: Component | null;
|
|
32
|
+
tooltipIconColor?: string;
|
|
33
|
+
width?: string;
|
|
34
|
+
padding?: string;
|
|
35
|
+
raw?: boolean;
|
|
36
|
+
isActive?: boolean;
|
|
37
|
+
fallbackPlacements?: Placement[];
|
|
38
|
+
stopPropagation?: boolean;
|
|
39
|
+
fontColor?: string;
|
|
40
|
+
bgColor?: string;
|
|
41
|
+
class?: string;
|
|
42
|
+
/** Target element snippet */
|
|
43
|
+
target?: Snippet;
|
|
44
|
+
/** Tooltip content snippet */
|
|
45
|
+
tooltip?: Snippet;
|
|
46
|
+
onshow?: (event: boolean) => void;
|
|
47
|
+
onclose?: (event: boolean) => void;
|
|
48
|
+
}
|
|
19
49
|
|
|
20
50
|
let {
|
|
21
|
-
bgColor = 'var(--background3)',
|
|
22
51
|
message = '',
|
|
23
52
|
listener = 'hover',
|
|
24
53
|
listenerout = 'hover',
|
|
25
54
|
placement = 'right',
|
|
26
55
|
position = 'absolute',
|
|
27
|
-
class: classNames = '',
|
|
28
56
|
showArrow = true,
|
|
29
57
|
offsetVal = 10,
|
|
30
58
|
hidden = $bindable(false),
|
|
@@ -38,51 +66,30 @@
|
|
|
38
66
|
fallbackPlacements = [],
|
|
39
67
|
stopPropagation = true,
|
|
40
68
|
fontColor = 'var(--text2)',
|
|
69
|
+
bgColor = '',
|
|
70
|
+
class: className = '',
|
|
41
71
|
target,
|
|
42
|
-
|
|
43
|
-
|
|
72
|
+
tooltip,
|
|
73
|
+
onshow,
|
|
74
|
+
onclose
|
|
44
75
|
}: Props = $props();
|
|
45
76
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? 'Hover to show'
|
|
55
|
-
: 'Hover me!')
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// State management with runes
|
|
59
|
-
let tooltipInstance: TooltipInstance | null = $state(null);
|
|
60
|
-
let targetElement: HTMLDivElement = $state()!;
|
|
61
|
-
let tooltipElement: HTMLDivElement = $state()!;
|
|
62
|
-
let arrowElement: HTMLDivElement = $state()!;
|
|
63
|
-
let observer: MutationObserver | null = $state(null);
|
|
64
|
-
|
|
65
|
-
// Generate unique ID for this tooltip instance
|
|
66
|
-
const tooltipId = $derived(`tooltip-${uuidv4()}`);
|
|
67
|
-
|
|
68
|
-
// Format message with cleanup utility
|
|
69
|
-
let formattedMessage = $derived(cleanupTooltipMessage(message));
|
|
77
|
+
type TooltipInstance = {
|
|
78
|
+
toggle: HTMLElement;
|
|
79
|
+
tooltip: HTMLElement;
|
|
80
|
+
arrowElement?: HTMLElement;
|
|
81
|
+
cleanup: () => void;
|
|
82
|
+
showTooltip: () => void;
|
|
83
|
+
hideTooltip: () => void;
|
|
84
|
+
};
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function dispatchEvent(eventType: keyof TooltipEvents, detail: boolean) {
|
|
79
|
-
const event = new CustomEvent(eventType, {
|
|
80
|
-
detail,
|
|
81
|
-
bubbles: true,
|
|
82
|
-
cancelable: true
|
|
83
|
-
});
|
|
84
|
-
targetElement?.dispatchEvent(event);
|
|
85
|
-
}
|
|
86
|
+
let tooltipInstance: TooltipInstance | null = $state(null);
|
|
87
|
+
let targetElement: HTMLDivElement | undefined = $state();
|
|
88
|
+
let tooltipElement: HTMLDivElement | undefined = $state();
|
|
89
|
+
let arrowElement: HTMLDivElement | undefined = $state();
|
|
90
|
+
let observer: MutationObserver | null = null;
|
|
91
|
+
let documentClickListener: ((event: MouseEvent) => void) | null = null;
|
|
92
|
+
const tooltipId = `tooltip-${uuidv4()}`;
|
|
86
93
|
|
|
87
94
|
/**
|
|
88
95
|
* Dismisses other tooltips based on trigger compatibility.
|
|
@@ -92,217 +99,197 @@
|
|
|
92
99
|
* - hover tooltips should NOT dismiss click tooltips (they're "sticky")
|
|
93
100
|
* - click tooltips CAN dismiss hover tooltips (higher priority)
|
|
94
101
|
*/
|
|
95
|
-
|
|
102
|
+
const dismissOtherTooltips = () => {
|
|
96
103
|
const currentTriggerType = listener === 'click' && listenerout === 'click' ? 'click' : 'hover';
|
|
97
104
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
activeTooltips.update(() => {
|
|
106
|
+
document.querySelectorAll<HTMLDivElement>('.tooltip[role="tooltip"]').forEach((item) => {
|
|
107
|
+
if (item.id !== tooltipId) {
|
|
108
|
+
const existingTriggerType = item.getAttribute('data-trigger-type');
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
110
|
+
const shouldDismiss =
|
|
111
|
+
currentTriggerType === 'click' ||
|
|
112
|
+
(currentTriggerType === 'hover' && existingTriggerType === 'hover'); // Hover only dismisses other hover
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
if (shouldDismiss) {
|
|
115
|
+
item.style.display = 'none';
|
|
116
|
+
item.setAttribute('aria-hidden', 'true');
|
|
117
|
+
}
|
|
109
118
|
}
|
|
110
|
-
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return [tooltipId];
|
|
111
122
|
});
|
|
123
|
+
};
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Inits the tooltip.
|
|
127
|
+
*/
|
|
128
|
+
const initTooltip = async (): Promise<TooltipInstance | null> => {
|
|
129
|
+
if (!tooltipElement || (showArrow && !arrowElement) || !targetElement) return null;
|
|
130
|
+
return setupTooltip(targetElement, tooltipElement, arrowElement);
|
|
131
|
+
};
|
|
115
132
|
|
|
116
133
|
/**
|
|
117
|
-
* Sets up the tooltip
|
|
134
|
+
* Sets up the tooltip.
|
|
135
|
+
* @param toggle
|
|
136
|
+
* @param tooltip
|
|
137
|
+
* @param arrowElement
|
|
118
138
|
*/
|
|
119
|
-
|
|
139
|
+
const setupTooltip = (
|
|
120
140
|
toggle: HTMLElement,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
): TooltipInstance {
|
|
141
|
+
tooltip: HTMLElement,
|
|
142
|
+
arrowElement?: HTMLElement
|
|
143
|
+
): TooltipInstance => {
|
|
124
144
|
/**
|
|
125
|
-
* Updates the tooltip position
|
|
145
|
+
* Updates the tooltip position.
|
|
126
146
|
*/
|
|
127
147
|
const update = () => {
|
|
128
|
-
computePosition(toggle,
|
|
148
|
+
computePosition(toggle, tooltip, {
|
|
129
149
|
placement: placement,
|
|
130
150
|
middleware: [
|
|
131
151
|
offset(offsetVal),
|
|
132
152
|
flip(
|
|
133
153
|
fallbackPlacements?.length > 0
|
|
134
|
-
? {
|
|
154
|
+
? {
|
|
155
|
+
fallbackPlacements
|
|
156
|
+
}
|
|
135
157
|
: {
|
|
136
158
|
fallbackAxisSideDirection: 'start',
|
|
137
159
|
fallbackStrategy: 'bestFit'
|
|
138
160
|
}
|
|
139
161
|
),
|
|
140
162
|
shift({ padding: 5 }),
|
|
141
|
-
showArrow &&
|
|
163
|
+
showArrow && arrowElement ? arrow({ element: arrowElement }) : undefined
|
|
142
164
|
].filter(Boolean)
|
|
143
|
-
}).then(({ x, y, placement
|
|
144
|
-
|
|
145
|
-
Object.assign(tooltipEl.style, {
|
|
165
|
+
}).then(({ x, y, placement, middlewareData }) => {
|
|
166
|
+
Object.assign(tooltip.style, {
|
|
146
167
|
left: `${x}px`,
|
|
147
168
|
top: `${y}px`
|
|
148
169
|
});
|
|
149
170
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
// Accessing the data
|
|
172
|
+
const staticSide = {
|
|
173
|
+
top: 'bottom',
|
|
174
|
+
right: 'left',
|
|
175
|
+
bottom: 'top',
|
|
176
|
+
left: 'right'
|
|
177
|
+
}[placement.split('-')[0]];
|
|
154
178
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
bottom: 'top',
|
|
159
|
-
left: 'right'
|
|
160
|
-
}[side];
|
|
179
|
+
if (showArrow && arrowElement) {
|
|
180
|
+
if (!middlewareData?.arrow) return;
|
|
181
|
+
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
|
161
182
|
|
|
162
|
-
Object.assign(
|
|
183
|
+
Object.assign(arrowElement.style, {
|
|
163
184
|
left: arrowX != null ? `${arrowX}px` : '',
|
|
164
185
|
top: arrowY != null ? `${arrowY}px` : '',
|
|
165
186
|
right: '',
|
|
166
187
|
bottom: '',
|
|
167
|
-
[staticSide
|
|
188
|
+
[`${staticSide}`]: '-4px'
|
|
168
189
|
});
|
|
169
190
|
}
|
|
170
191
|
});
|
|
171
192
|
};
|
|
172
193
|
|
|
173
194
|
/**
|
|
174
|
-
* Shows the tooltip
|
|
195
|
+
* Shows the tooltip.
|
|
175
196
|
*/
|
|
176
197
|
const showTooltip = () => {
|
|
177
|
-
if (disabled) return;
|
|
198
|
+
if (disabled || !tooltipElement) return;
|
|
178
199
|
|
|
179
200
|
dismissOtherTooltips();
|
|
180
201
|
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
tooltipElement.style.display = 'flex';
|
|
203
|
+
tooltipElement.setAttribute('aria-hidden', 'false');
|
|
183
204
|
|
|
184
205
|
// Set trigger type for dismissal logic
|
|
185
206
|
const triggerType = listener === 'click' && listenerout === 'click' ? 'click' : 'hover';
|
|
186
|
-
|
|
207
|
+
tooltipElement.setAttribute('data-trigger-type', triggerType);
|
|
187
208
|
|
|
188
209
|
isActive = true;
|
|
189
|
-
hidden = false;
|
|
190
210
|
|
|
191
|
-
|
|
192
|
-
activeTooltips = [...activeTooltips.filter((id) => id !== tooltipId), tooltipId];
|
|
193
|
-
}
|
|
211
|
+
activeTooltips.update((ids) => [...ids.filter((id) => id !== tooltipId), tooltipId]);
|
|
194
212
|
|
|
195
213
|
update();
|
|
196
|
-
|
|
214
|
+
onshow?.(true);
|
|
197
215
|
};
|
|
198
216
|
|
|
199
217
|
/**
|
|
200
|
-
* Hides the tooltip
|
|
218
|
+
* Hides the tooltip.
|
|
201
219
|
*/
|
|
202
220
|
const hideTooltip = () => {
|
|
203
|
-
// Hide immediately in test environment
|
|
204
|
-
const isTestEnv =
|
|
205
|
-
(typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') ||
|
|
206
|
-
(typeof globalThis !== 'undefined' && '__vitest__' in globalThis);
|
|
207
|
-
const delay = isTestEnv ? 0 : 50;
|
|
208
|
-
|
|
209
221
|
setTimeout(() => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
if (!tooltipElement) return;
|
|
223
|
+
|
|
224
|
+
tooltipElement.style.display = 'none';
|
|
225
|
+
tooltipElement.setAttribute('aria-hidden', 'true');
|
|
226
|
+
tooltipElement.removeAttribute('data-trigger-type');
|
|
213
227
|
isActive = false;
|
|
214
|
-
hidden = true;
|
|
215
|
-
activeTooltips = activeTooltips.filter((id) => id !== tooltipId);
|
|
216
228
|
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
activeTooltips.update((ids) => ids.filter((id) => id !== tooltipId));
|
|
230
|
+
|
|
231
|
+
onclose?.(true);
|
|
232
|
+
}, 50);
|
|
219
233
|
};
|
|
220
234
|
|
|
221
|
-
|
|
222
|
-
let eventOptions: Array<[string, () => void]> = [];
|
|
235
|
+
let opts;
|
|
223
236
|
|
|
224
237
|
if (listener === 'click' && listenerout === 'click') {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
if (
|
|
238
|
+
opts = [['click', showTooltip]];
|
|
239
|
+
|
|
240
|
+
// Store reference to the click handler for cleanup
|
|
241
|
+
documentClickListener = (event: MouseEvent) => {
|
|
242
|
+
if (
|
|
243
|
+
tooltipElement &&
|
|
244
|
+
toggle &&
|
|
245
|
+
!tooltipElement.contains(event.target as Node) &&
|
|
246
|
+
!toggle.contains(event.target as Node)
|
|
247
|
+
) {
|
|
230
248
|
hideTooltip();
|
|
231
249
|
}
|
|
232
250
|
};
|
|
233
251
|
|
|
234
|
-
document.addEventListener('click',
|
|
235
|
-
|
|
236
|
-
// Return cleanup function that removes global listener
|
|
237
|
-
const originalCleanup = autoUpdate(toggle, tooltipEl, update);
|
|
238
|
-
const cleanup = () => {
|
|
239
|
-
originalCleanup();
|
|
240
|
-
document.removeEventListener('click', handleGlobalClick);
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
// Add event listeners to toggle element
|
|
244
|
-
eventOptions.forEach(([event, handler]) => {
|
|
245
|
-
toggle.addEventListener(event, (evt) => {
|
|
246
|
-
if (stopPropagation) {
|
|
247
|
-
evt.stopPropagation();
|
|
248
|
-
evt.preventDefault();
|
|
249
|
-
}
|
|
250
|
-
handler();
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
toggle,
|
|
256
|
-
tooltip: tooltipEl,
|
|
257
|
-
arrowElement: arrowEl,
|
|
258
|
-
cleanup,
|
|
259
|
-
showTooltip,
|
|
260
|
-
hideTooltip
|
|
261
|
-
};
|
|
252
|
+
document.addEventListener('click', documentClickListener);
|
|
262
253
|
} else {
|
|
263
|
-
|
|
254
|
+
opts = [
|
|
264
255
|
listener === 'click' ? ['click', showTooltip] : undefined,
|
|
265
256
|
listener === 'hover' ? ['mouseenter', showTooltip] : undefined,
|
|
266
257
|
listener === 'hover' ? ['mouseleave', hideTooltip] : undefined,
|
|
267
258
|
listener === 'hover' ? ['focus', showTooltip] : undefined,
|
|
268
259
|
['blur', hideTooltip]
|
|
269
|
-
]
|
|
260
|
+
];
|
|
270
261
|
}
|
|
271
262
|
|
|
272
|
-
|
|
273
|
-
|
|
263
|
+
type EventOption = [string, () => void];
|
|
264
|
+
const options: EventOption[] = opts.filter(Boolean) as EventOption[];
|
|
265
|
+
|
|
266
|
+
options.forEach(([event, listener]) => {
|
|
274
267
|
toggle.addEventListener(event, (evt) => {
|
|
275
268
|
if (stopPropagation) {
|
|
276
269
|
evt.stopPropagation();
|
|
277
270
|
evt.preventDefault();
|
|
278
271
|
}
|
|
279
|
-
|
|
272
|
+
listener();
|
|
280
273
|
});
|
|
281
274
|
});
|
|
282
275
|
|
|
283
|
-
const cleanup =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (!tooltipElement || (showArrow && !arrowElement) || !targetElement) return null;
|
|
293
|
-
return setupTooltip(targetElement, tooltipElement, arrowElement);
|
|
294
|
-
}
|
|
276
|
+
const cleanup = () => {
|
|
277
|
+
if (tooltipElement) {
|
|
278
|
+
autoUpdate(toggle, tooltipElement, update)();
|
|
279
|
+
}
|
|
280
|
+
if (documentClickListener) {
|
|
281
|
+
document.removeEventListener('click', documentClickListener);
|
|
282
|
+
documentClickListener = null;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
295
285
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
tooltipInstance?.showTooltip();
|
|
299
|
-
}
|
|
286
|
+
return { toggle, tooltip: tooltipElement!, arrowElement, cleanup, showTooltip, hideTooltip };
|
|
287
|
+
};
|
|
300
288
|
|
|
301
|
-
export
|
|
302
|
-
|
|
303
|
-
}
|
|
289
|
+
export const show = () => tooltipInstance?.showTooltip();
|
|
290
|
+
export const hide = () => tooltipInstance?.hideTooltip();
|
|
304
291
|
|
|
305
|
-
//
|
|
292
|
+
// Svelte 5 effect for hidden prop
|
|
306
293
|
$effect(() => {
|
|
307
294
|
if (hidden) {
|
|
308
295
|
setTimeout(() => {
|
|
@@ -316,111 +303,102 @@
|
|
|
316
303
|
}
|
|
317
304
|
});
|
|
318
305
|
|
|
319
|
-
|
|
320
|
-
$effect(() => {
|
|
321
|
-
if (targetElement && tooltipElement) {
|
|
322
|
-
tick().then(async () => {
|
|
323
|
-
tooltipInstance = await initTooltip();
|
|
324
|
-
|
|
325
|
-
if (tooltipElement) {
|
|
326
|
-
observer = new MutationObserver((mutations) => {
|
|
327
|
-
mutations.forEach((mutation) => {
|
|
328
|
-
if (mutation.type === 'attributes' && tooltipElement?.style) {
|
|
329
|
-
const isVisible = tooltipElement.style.display !== 'none';
|
|
330
|
-
dispatchEvent(isVisible ? 'show' : 'close', true);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
});
|
|
306
|
+
const formattedMessage = $derived(cleanupTooltipMessage(message));
|
|
334
307
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
308
|
+
onMount(async () => {
|
|
309
|
+
await tick();
|
|
310
|
+
tooltipInstance = await initTooltip();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
onDestroy(() => {
|
|
314
|
+
// Disconnect observer first to prevent any new callbacks
|
|
315
|
+
if (observer) {
|
|
316
|
+
observer.disconnect();
|
|
317
|
+
observer = null;
|
|
338
318
|
}
|
|
339
319
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
320
|
+
// Clean up document click listener
|
|
321
|
+
if (documentClickListener) {
|
|
322
|
+
document.removeEventListener('click', documentClickListener);
|
|
323
|
+
documentClickListener = null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (tooltipInstance) {
|
|
327
|
+
tooltipInstance.cleanup();
|
|
328
|
+
activeTooltips.update((ids) => ids.filter((id) => id !== tooltipId));
|
|
329
|
+
tooltipInstance = null;
|
|
330
|
+
}
|
|
351
331
|
});
|
|
352
332
|
</script>
|
|
353
333
|
|
|
354
|
-
<div class="target
|
|
334
|
+
<div class="target" bind:this={targetElement} aria-describedby={tooltipId}>
|
|
355
335
|
{#if target}
|
|
356
336
|
{@render target()}
|
|
357
|
-
{:else}
|
|
358
|
-
<button
|
|
359
|
-
style="
|
|
360
|
-
padding: 4px 8px;
|
|
361
|
-
background: #007bff;
|
|
362
|
-
color: white;
|
|
363
|
-
border: none;
|
|
364
|
-
border-radius: 4px;
|
|
365
|
-
cursor: pointer;
|
|
366
|
-
"
|
|
367
|
-
>
|
|
368
|
-
{defaultTargetText}
|
|
369
|
-
</button>
|
|
370
337
|
{/if}
|
|
371
338
|
|
|
372
339
|
<div
|
|
373
|
-
class="tooltip"
|
|
340
|
+
class="tooltip {className}"
|
|
374
341
|
bind:this={tooltipElement}
|
|
375
342
|
role="tooltip"
|
|
376
343
|
id={tooltipId}
|
|
377
|
-
|
|
378
|
-
|
|
344
|
+
style="position:{position}; width:{width}; padding:{padding}; {bgColor
|
|
345
|
+
? `background-color: ${bgColor};`
|
|
346
|
+
: ''}"
|
|
379
347
|
>
|
|
380
348
|
{#if tooltipIcon}
|
|
381
|
-
{@const
|
|
349
|
+
{@const Icon = tooltipIcon}
|
|
382
350
|
<div class="icon" style="color:{tooltipIconColor};">
|
|
383
|
-
<
|
|
351
|
+
<Icon />
|
|
384
352
|
</div>
|
|
385
353
|
{/if}
|
|
386
354
|
|
|
387
|
-
|
|
388
|
-
{
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{/if}
|
|
399
|
-
</div>
|
|
355
|
+
{#if tooltip}
|
|
356
|
+
{@render tooltip()}
|
|
357
|
+
{:else if message && raw}
|
|
358
|
+
<div class="message" style="color:{fontColor};">
|
|
359
|
+
{@html message}
|
|
360
|
+
</div>
|
|
361
|
+
{:else if message}
|
|
362
|
+
<div class="message">
|
|
363
|
+
<Text label={formattedMessage} fontSize="11px" fontWeight="500" {fontColor} />
|
|
364
|
+
</div>
|
|
365
|
+
{/if}
|
|
400
366
|
|
|
401
367
|
{#if showArrow}
|
|
402
|
-
<div
|
|
368
|
+
<div
|
|
369
|
+
class="arrow"
|
|
370
|
+
id="arrow_{tooltipId}"
|
|
371
|
+
bind:this={arrowElement}
|
|
372
|
+
style={bgColor ? `background-color: ${bgColor};` : ''}
|
|
373
|
+
></div>
|
|
403
374
|
{/if}
|
|
404
375
|
</div>
|
|
405
376
|
</div>
|
|
406
377
|
|
|
407
378
|
<style>
|
|
379
|
+
.message {
|
|
380
|
+
background: var(--background3, #383838);
|
|
381
|
+
text-wrap: normal;
|
|
382
|
+
white-space: normal;
|
|
383
|
+
color: var(--text2, #b9b9b9);
|
|
384
|
+
font-weight: 500;
|
|
385
|
+
font-size: 11px;
|
|
386
|
+
}
|
|
408
387
|
.tooltip {
|
|
409
388
|
display: none;
|
|
410
389
|
top: 0;
|
|
411
390
|
left: 0;
|
|
412
|
-
border-radius: 4px;
|
|
413
391
|
z-index: 99999999999;
|
|
414
|
-
border-radius:
|
|
415
|
-
background: var(--background3);
|
|
392
|
+
border-radius: 4px;
|
|
393
|
+
background: var(--background3, #383838);
|
|
416
394
|
box-shadow:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
color: var(--text2);
|
|
395
|
+
0px 12px 24px 8px rgb(0 0 0 / 30%),
|
|
396
|
+
0px 8px 16px 4px rgb(0 0 0 / 30%),
|
|
397
|
+
0px 4px 8px 2px rgb(0 0 0 / 0%),
|
|
398
|
+
0px 2px 6px 0px rgba(0, 0, 0, 0.08),
|
|
399
|
+
0px -0.5px 0.5px 0px rgb(0 0 0 / 0%) inset,
|
|
400
|
+
0px 0.5px 0.5px 0px rgb(255 255 255 / 34%) inset;
|
|
401
|
+
color: var(--text2, #b9b9b9);
|
|
424
402
|
font-family: Inter;
|
|
425
403
|
line-height: 120%;
|
|
426
404
|
width: 150px;
|
|
@@ -430,38 +408,20 @@
|
|
|
430
408
|
align-items: start;
|
|
431
409
|
justify-content: start;
|
|
432
410
|
}
|
|
433
|
-
|
|
434
|
-
.content-wrapper {
|
|
435
|
-
flex: 1;
|
|
436
|
-
min-width: 0;
|
|
437
|
-
border-radius: var(--border-radius, 4px);
|
|
438
|
-
z-index: 3;
|
|
439
|
-
background: inherit;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
.message {
|
|
443
|
-
text-wrap: normal;
|
|
444
|
-
white-space: normal;
|
|
445
|
-
color: var(--text2);
|
|
446
|
-
font-weight: 500;
|
|
447
|
-
font-size: 11px;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
411
|
.arrow {
|
|
412
|
+
z-index: 1;
|
|
413
|
+
background: var(--background3, #383838);
|
|
451
414
|
position: absolute;
|
|
452
|
-
width:
|
|
453
|
-
height:
|
|
415
|
+
width: 8px;
|
|
416
|
+
height: 8px;
|
|
454
417
|
transform: rotate(45deg);
|
|
455
418
|
}
|
|
456
|
-
|
|
457
|
-
|
|
419
|
+
.target :global(div[slot='tooltip']) {
|
|
420
|
+
width: 100%;
|
|
421
|
+
}
|
|
422
|
+
.target :global(div[slot='target']) {
|
|
458
423
|
display: flex;
|
|
459
424
|
align-items: center;
|
|
460
425
|
justify-content: center;
|
|
461
426
|
}
|
|
462
|
-
|
|
463
|
-
.icon {
|
|
464
|
-
position: relative;
|
|
465
|
-
z-index: 1;
|
|
466
|
-
}
|
|
467
427
|
</style>
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { type Placement } from '@floating-ui/dom';
|
|
2
|
+
import { type Component, type Snippet } from 'svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
message?: string;
|
|
5
|
+
listener?: 'click' | 'hover';
|
|
6
|
+
listenerout?: 'click' | 'hover';
|
|
7
|
+
placement?: Placement;
|
|
8
|
+
position?: string;
|
|
9
|
+
showArrow?: boolean;
|
|
10
|
+
offsetVal?: number;
|
|
11
|
+
hidden?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
tooltipIcon?: Component | null;
|
|
14
|
+
tooltipIconColor?: string;
|
|
15
|
+
width?: string;
|
|
16
|
+
padding?: string;
|
|
17
|
+
raw?: boolean;
|
|
18
|
+
isActive?: boolean;
|
|
19
|
+
fallbackPlacements?: Placement[];
|
|
20
|
+
stopPropagation?: boolean;
|
|
21
|
+
fontColor?: string;
|
|
22
|
+
bgColor?: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
/** Target element snippet */
|
|
25
|
+
target?: Snippet;
|
|
26
|
+
/** Tooltip content snippet */
|
|
27
|
+
tooltip?: Snippet;
|
|
28
|
+
onshow?: (event: boolean) => void;
|
|
29
|
+
onclose?: (event: boolean) => void;
|
|
30
|
+
}
|
|
31
|
+
declare const Tooltip: Component<Props, {
|
|
32
|
+
show: () => void | undefined;
|
|
33
|
+
hide: () => void | undefined;
|
|
5
34
|
}, "hidden" | "isActive">;
|
|
6
35
|
type Tooltip = ReturnType<typeof Tooltip>;
|
|
7
36
|
export default Tooltip;
|