@finsweet/webflow-apps-utils 1.0.14 → 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 +225 -283
- 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 : 100;
|
|
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,129 +303,102 @@
|
|
|
316
303
|
}
|
|
317
304
|
});
|
|
318
305
|
|
|
319
|
-
$
|
|
320
|
-
if (tooltipElement) {
|
|
321
|
-
const handleTooltipInteraction = (evt: Event) => {
|
|
322
|
-
evt.stopPropagation();
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
tooltipElement.addEventListener('click', handleTooltipInteraction, true);
|
|
326
|
-
tooltipElement.addEventListener('mousedown', handleTooltipInteraction, true);
|
|
327
|
-
tooltipElement.addEventListener('mouseup', handleTooltipInteraction, true);
|
|
306
|
+
const formattedMessage = $derived(cleanupTooltipMessage(message));
|
|
328
307
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
tooltipElement.removeEventListener('mouseup', handleTooltipInteraction, true);
|
|
333
|
-
};
|
|
334
|
-
}
|
|
308
|
+
onMount(async () => {
|
|
309
|
+
await tick();
|
|
310
|
+
tooltipInstance = await initTooltip();
|
|
335
311
|
});
|
|
336
312
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (tooltipElement) {
|
|
344
|
-
observer = new MutationObserver((mutations) => {
|
|
345
|
-
mutations.forEach((mutation) => {
|
|
346
|
-
if (mutation.type === 'attributes' && tooltipElement?.style) {
|
|
347
|
-
const isVisible = tooltipElement.style.display !== 'none';
|
|
348
|
-
dispatchEvent(isVisible ? 'show' : 'close', true);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
});
|
|
313
|
+
onDestroy(() => {
|
|
314
|
+
// Disconnect observer first to prevent any new callbacks
|
|
315
|
+
if (observer) {
|
|
316
|
+
observer.disconnect();
|
|
317
|
+
observer = null;
|
|
318
|
+
}
|
|
352
319
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
320
|
+
// Clean up document click listener
|
|
321
|
+
if (documentClickListener) {
|
|
322
|
+
document.removeEventListener('click', documentClickListener);
|
|
323
|
+
documentClickListener = null;
|
|
356
324
|
}
|
|
357
325
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
if (observer) {
|
|
365
|
-
observer.disconnect();
|
|
366
|
-
observer = null;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
326
|
+
if (tooltipInstance) {
|
|
327
|
+
tooltipInstance.cleanup();
|
|
328
|
+
activeTooltips.update((ids) => ids.filter((id) => id !== tooltipId));
|
|
329
|
+
tooltipInstance = null;
|
|
330
|
+
}
|
|
369
331
|
});
|
|
370
332
|
</script>
|
|
371
333
|
|
|
372
|
-
<div class="target
|
|
334
|
+
<div class="target" bind:this={targetElement} aria-describedby={tooltipId}>
|
|
373
335
|
{#if target}
|
|
374
336
|
{@render target()}
|
|
375
|
-
{:else}
|
|
376
|
-
<button
|
|
377
|
-
style="
|
|
378
|
-
padding: 4px 8px;
|
|
379
|
-
background: #007bff;
|
|
380
|
-
color: white;
|
|
381
|
-
border: none;
|
|
382
|
-
border-radius: 4px;
|
|
383
|
-
cursor: pointer;
|
|
384
|
-
"
|
|
385
|
-
>
|
|
386
|
-
{defaultTargetText}
|
|
387
|
-
</button>
|
|
388
337
|
{/if}
|
|
389
338
|
|
|
390
339
|
<div
|
|
391
|
-
class="tooltip"
|
|
340
|
+
class="tooltip {className}"
|
|
392
341
|
bind:this={tooltipElement}
|
|
393
342
|
role="tooltip"
|
|
394
343
|
id={tooltipId}
|
|
395
|
-
|
|
396
|
-
|
|
344
|
+
style="position:{position}; width:{width}; padding:{padding}; {bgColor
|
|
345
|
+
? `background-color: ${bgColor};`
|
|
346
|
+
: ''}"
|
|
397
347
|
>
|
|
398
348
|
{#if tooltipIcon}
|
|
399
|
-
{@const
|
|
349
|
+
{@const Icon = tooltipIcon}
|
|
400
350
|
<div class="icon" style="color:{tooltipIconColor};">
|
|
401
|
-
<
|
|
351
|
+
<Icon />
|
|
402
352
|
</div>
|
|
403
353
|
{/if}
|
|
404
354
|
|
|
405
|
-
|
|
406
|
-
{
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{/if}
|
|
417
|
-
</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}
|
|
418
366
|
|
|
419
367
|
{#if showArrow}
|
|
420
|
-
<div
|
|
368
|
+
<div
|
|
369
|
+
class="arrow"
|
|
370
|
+
id="arrow_{tooltipId}"
|
|
371
|
+
bind:this={arrowElement}
|
|
372
|
+
style={bgColor ? `background-color: ${bgColor};` : ''}
|
|
373
|
+
></div>
|
|
421
374
|
{/if}
|
|
422
375
|
</div>
|
|
423
376
|
</div>
|
|
424
377
|
|
|
425
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
|
+
}
|
|
426
387
|
.tooltip {
|
|
427
388
|
display: none;
|
|
428
389
|
top: 0;
|
|
429
390
|
left: 0;
|
|
430
|
-
border-radius: 4px;
|
|
431
391
|
z-index: 99999999999;
|
|
432
|
-
border-radius:
|
|
433
|
-
background: var(--background3);
|
|
392
|
+
border-radius: 4px;
|
|
393
|
+
background: var(--background3, #383838);
|
|
434
394
|
box-shadow:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
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);
|
|
442
402
|
font-family: Inter;
|
|
443
403
|
line-height: 120%;
|
|
444
404
|
width: 150px;
|
|
@@ -448,38 +408,20 @@
|
|
|
448
408
|
align-items: start;
|
|
449
409
|
justify-content: start;
|
|
450
410
|
}
|
|
451
|
-
|
|
452
|
-
.content-wrapper {
|
|
453
|
-
flex: 1;
|
|
454
|
-
min-width: 0;
|
|
455
|
-
border-radius: var(--border-radius, 4px);
|
|
456
|
-
z-index: 3;
|
|
457
|
-
background: inherit;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
.message {
|
|
461
|
-
text-wrap: normal;
|
|
462
|
-
white-space: normal;
|
|
463
|
-
color: var(--text2);
|
|
464
|
-
font-weight: 500;
|
|
465
|
-
font-size: 11px;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
411
|
.arrow {
|
|
412
|
+
z-index: 1;
|
|
413
|
+
background: var(--background3, #383838);
|
|
469
414
|
position: absolute;
|
|
470
|
-
width:
|
|
471
|
-
height:
|
|
415
|
+
width: 8px;
|
|
416
|
+
height: 8px;
|
|
472
417
|
transform: rotate(45deg);
|
|
473
418
|
}
|
|
474
|
-
|
|
475
|
-
|
|
419
|
+
.target :global(div[slot='tooltip']) {
|
|
420
|
+
width: 100%;
|
|
421
|
+
}
|
|
422
|
+
.target :global(div[slot='target']) {
|
|
476
423
|
display: flex;
|
|
477
424
|
align-items: center;
|
|
478
425
|
justify-content: center;
|
|
479
426
|
}
|
|
480
|
-
|
|
481
|
-
.icon {
|
|
482
|
-
position: relative;
|
|
483
|
-
z-index: 1;
|
|
484
|
-
}
|
|
485
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;
|