@finsweet/webflow-apps-utils 1.0.14 → 1.0.16
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 +2 -2
- 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 +5 -2
- package/dist/ui/components/layout/examples/ExampleLayout.svelte +41 -0
- package/dist/ui/components/section/Section.svelte +3 -2
- package/dist/ui/components/text/Text.stories.svelte +2 -1
- package/dist/ui/components/text/Text.svelte +3 -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 +230 -281
- 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');
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
</script>
|
|
149
149
|
|
|
150
150
|
{#if shouldShowTooltip}
|
|
151
|
-
<Tooltip {...tooltip}>
|
|
151
|
+
<Tooltip {...tooltip} stopPropagation={false}>
|
|
152
152
|
{#snippet target()}
|
|
153
153
|
<button
|
|
154
154
|
bind:this={buttonElement}
|
|
@@ -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>
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
listener={button.popupTrigger || 'click'}
|
|
24
24
|
listenerout={button.popupTrigger || 'click'}
|
|
25
25
|
width="210px"
|
|
26
|
+
stopPropagation={false}
|
|
26
27
|
>
|
|
27
28
|
{#snippet target()}
|
|
28
29
|
<Button
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
class="popup-button-chevron"
|
|
35
36
|
/>
|
|
36
37
|
{/snippet}
|
|
37
|
-
{#snippet
|
|
38
|
+
{#snippet tooltip()}
|
|
38
39
|
<div role="button" tabindex="0" class="popup-content">
|
|
39
40
|
<div class="popup-content-list">
|
|
40
41
|
{#each button.popupButtons || [] as popupButton, popupIndex (popupIndex)}
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
placement={button.tooltip?.placement || 'top'}
|
|
85
86
|
offsetVal={10}
|
|
86
87
|
showArrow={button.tooltip?.showArrow !== false}
|
|
88
|
+
stopPropagation={false}
|
|
87
89
|
>
|
|
88
90
|
{#snippet target()}
|
|
89
91
|
<Button
|
|
@@ -94,7 +96,7 @@
|
|
|
94
96
|
onclick={button.onClick || (() => {})}
|
|
95
97
|
/>
|
|
96
98
|
{/snippet}
|
|
97
|
-
{#snippet
|
|
99
|
+
{#snippet tooltip()}
|
|
98
100
|
<div
|
|
99
101
|
class="tooltip-content {button.tooltip?.className || ''}"
|
|
100
102
|
style="display: {button.tooltip?.icon ? 'grid' : 'inline-block'}"
|
|
@@ -186,6 +188,7 @@
|
|
|
186
188
|
min-width: 150px;
|
|
187
189
|
background: var(--background);
|
|
188
190
|
border-radius: 4px;
|
|
191
|
+
width: 100%;
|
|
189
192
|
}
|
|
190
193
|
.popup-content-icon {
|
|
191
194
|
display: flex;
|
|
@@ -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
|
|
|
@@ -187,13 +187,14 @@
|
|
|
187
187
|
message={defaultDisabledMessage}
|
|
188
188
|
width={disabledTooltipWidth}
|
|
189
189
|
class="not-allowed"
|
|
190
|
+
stopPropagation={false}
|
|
190
191
|
>
|
|
191
192
|
{#snippet target()}
|
|
192
193
|
{@render sectionContent()}
|
|
193
194
|
{/snippet}
|
|
194
195
|
</Tooltip>
|
|
195
196
|
{:else if shouldShowTooltip}
|
|
196
|
-
<Tooltip {...tooltip}>
|
|
197
|
+
<Tooltip {...tooltip} stopPropagation={false}>
|
|
197
198
|
{#snippet target()}
|
|
198
199
|
{@render sectionContent()}
|
|
199
200
|
{/snippet}
|
|
@@ -227,7 +227,8 @@
|
|
|
227
227
|
active: true,
|
|
228
228
|
title: 'Remove',
|
|
229
229
|
subtitle: 'Alt + click',
|
|
230
|
-
description: 'This will remove the current selection.'
|
|
230
|
+
description: 'This will remove the current selection.',
|
|
231
|
+
onclick: () => console.log('Popup action clicked')
|
|
231
232
|
}
|
|
232
233
|
}}
|
|
233
234
|
/>
|
|
@@ -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
|
);
|
|
@@ -550,6 +550,7 @@
|
|
|
550
550
|
bind:hidden={isPopupHidden}
|
|
551
551
|
listener="click"
|
|
552
552
|
listenerout="click"
|
|
553
|
+
stopPropagation={false}
|
|
553
554
|
>
|
|
554
555
|
{#snippet target()}
|
|
555
556
|
<span class="dropdown-label item {popupConfig.active ? 'active' : ''}">
|
|
@@ -784,7 +785,7 @@
|
|
|
784
785
|
{/if}
|
|
785
786
|
</span>
|
|
786
787
|
{/snippet}
|
|
787
|
-
{#snippet
|
|
788
|
+
{#snippet tooltip()}
|
|
788
789
|
<div
|
|
789
790
|
class="popup-wrapper"
|
|
790
791
|
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,53 @@
|
|
|
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 {
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
import { TooltipInfoCircleFilled } from '../../icons';
|
|
16
|
+
import ToolTipInfoCircleIcon from '../../icons/ToolTipInfoCircleIcon.svelte';
|
|
17
|
+
import { cleanupTooltipMessage } from '../../../utils';
|
|
18
|
+
|
|
19
|
+
import Text from '../text/Text.svelte';
|
|
20
|
+
|
|
21
|
+
const activeTooltips = writable<string[]>([]);
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
message?: string;
|
|
25
|
+
listener?: 'click' | 'hover';
|
|
26
|
+
listenerout?: 'click' | 'hover';
|
|
27
|
+
placement?: Placement;
|
|
28
|
+
position?: string;
|
|
29
|
+
showArrow?: boolean;
|
|
30
|
+
offsetVal?: number;
|
|
31
|
+
hidden?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
tooltipIcon?: Component | null;
|
|
34
|
+
tooltipIconColor?: string;
|
|
35
|
+
width?: string;
|
|
36
|
+
padding?: string;
|
|
37
|
+
raw?: boolean;
|
|
38
|
+
isActive?: boolean;
|
|
39
|
+
fallbackPlacements?: Placement[];
|
|
40
|
+
stopPropagation?: boolean;
|
|
41
|
+
fontColor?: string;
|
|
42
|
+
bgColor?: string;
|
|
43
|
+
class?: string;
|
|
44
|
+
/** Target element snippet */
|
|
45
|
+
target?: Snippet;
|
|
46
|
+
/** Tooltip content snippet */
|
|
47
|
+
tooltip?: Snippet;
|
|
48
|
+
onshow?: (event: boolean) => void;
|
|
49
|
+
onclose?: (event: boolean) => void;
|
|
50
|
+
}
|
|
19
51
|
|
|
20
52
|
let {
|
|
21
|
-
bgColor = 'var(--background3)',
|
|
22
53
|
message = '',
|
|
23
54
|
listener = 'hover',
|
|
24
55
|
listenerout = 'hover',
|
|
25
56
|
placement = 'right',
|
|
26
57
|
position = 'absolute',
|
|
27
|
-
class: classNames = '',
|
|
28
58
|
showArrow = true,
|
|
29
59
|
offsetVal = 10,
|
|
30
60
|
hidden = $bindable(false),
|
|
@@ -38,51 +68,30 @@
|
|
|
38
68
|
fallbackPlacements = [],
|
|
39
69
|
stopPropagation = true,
|
|
40
70
|
fontColor = 'var(--text2)',
|
|
71
|
+
bgColor = '',
|
|
72
|
+
class: className = '',
|
|
41
73
|
target,
|
|
42
|
-
|
|
43
|
-
|
|
74
|
+
tooltip,
|
|
75
|
+
onshow,
|
|
76
|
+
onclose
|
|
44
77
|
}: Props = $props();
|
|
45
78
|
|
|
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));
|
|
79
|
+
type TooltipInstance = {
|
|
80
|
+
toggle: HTMLElement;
|
|
81
|
+
tooltip: HTMLElement;
|
|
82
|
+
arrowElement?: HTMLElement;
|
|
83
|
+
cleanup: () => void;
|
|
84
|
+
showTooltip: () => void;
|
|
85
|
+
hideTooltip: () => void;
|
|
86
|
+
};
|
|
70
87
|
|
|
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
|
-
}
|
|
88
|
+
let tooltipInstance: TooltipInstance | null = $state(null);
|
|
89
|
+
let targetElement: HTMLDivElement | undefined = $state();
|
|
90
|
+
let tooltipElement: HTMLDivElement | undefined = $state();
|
|
91
|
+
let arrowElement: HTMLDivElement | undefined = $state();
|
|
92
|
+
let observer: MutationObserver | null = null;
|
|
93
|
+
let documentClickListener: ((event: MouseEvent) => void) | null = null;
|
|
94
|
+
const tooltipId = `tooltip-${uuidv4()}`;
|
|
86
95
|
|
|
87
96
|
/**
|
|
88
97
|
* Dismisses other tooltips based on trigger compatibility.
|
|
@@ -92,217 +101,197 @@
|
|
|
92
101
|
* - hover tooltips should NOT dismiss click tooltips (they're "sticky")
|
|
93
102
|
* - click tooltips CAN dismiss hover tooltips (higher priority)
|
|
94
103
|
*/
|
|
95
|
-
|
|
104
|
+
const dismissOtherTooltips = () => {
|
|
96
105
|
const currentTriggerType = listener === 'click' && listenerout === 'click' ? 'click' : 'hover';
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
107
|
+
activeTooltips.update(() => {
|
|
108
|
+
document.querySelectorAll<HTMLDivElement>('.tooltip[role="tooltip"]').forEach((item) => {
|
|
109
|
+
if (item.id !== tooltipId) {
|
|
110
|
+
const existingTriggerType = item.getAttribute('data-trigger-type');
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
const shouldDismiss =
|
|
113
|
+
currentTriggerType === 'click' ||
|
|
114
|
+
(currentTriggerType === 'hover' && existingTriggerType === 'hover'); // Hover only dismisses other hover
|
|
105
115
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
if (shouldDismiss) {
|
|
117
|
+
item.style.display = 'none';
|
|
118
|
+
item.setAttribute('aria-hidden', 'true');
|
|
119
|
+
}
|
|
109
120
|
}
|
|
110
|
-
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return [tooltipId];
|
|
111
124
|
});
|
|
125
|
+
};
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Inits the tooltip.
|
|
129
|
+
*/
|
|
130
|
+
const initTooltip = async (): Promise<TooltipInstance | null> => {
|
|
131
|
+
if (!tooltipElement || (showArrow && !arrowElement) || !targetElement) return null;
|
|
132
|
+
return setupTooltip(targetElement, tooltipElement, arrowElement);
|
|
133
|
+
};
|
|
115
134
|
|
|
116
135
|
/**
|
|
117
|
-
* Sets up the tooltip
|
|
136
|
+
* Sets up the tooltip.
|
|
137
|
+
* @param toggle
|
|
138
|
+
* @param tooltip
|
|
139
|
+
* @param arrowElement
|
|
118
140
|
*/
|
|
119
|
-
|
|
141
|
+
const setupTooltip = (
|
|
120
142
|
toggle: HTMLElement,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
): TooltipInstance {
|
|
143
|
+
tooltip: HTMLElement,
|
|
144
|
+
arrowElement?: HTMLElement
|
|
145
|
+
): TooltipInstance => {
|
|
124
146
|
/**
|
|
125
|
-
* Updates the tooltip position
|
|
147
|
+
* Updates the tooltip position.
|
|
126
148
|
*/
|
|
127
149
|
const update = () => {
|
|
128
|
-
computePosition(toggle,
|
|
150
|
+
computePosition(toggle, tooltip, {
|
|
129
151
|
placement: placement,
|
|
130
152
|
middleware: [
|
|
131
153
|
offset(offsetVal),
|
|
132
154
|
flip(
|
|
133
155
|
fallbackPlacements?.length > 0
|
|
134
|
-
? {
|
|
156
|
+
? {
|
|
157
|
+
fallbackPlacements
|
|
158
|
+
}
|
|
135
159
|
: {
|
|
136
160
|
fallbackAxisSideDirection: 'start',
|
|
137
161
|
fallbackStrategy: 'bestFit'
|
|
138
162
|
}
|
|
139
163
|
),
|
|
140
164
|
shift({ padding: 5 }),
|
|
141
|
-
showArrow &&
|
|
165
|
+
showArrow && arrowElement ? arrow({ element: arrowElement }) : undefined
|
|
142
166
|
].filter(Boolean)
|
|
143
|
-
}).then(({ x, y, placement
|
|
144
|
-
|
|
145
|
-
Object.assign(tooltipEl.style, {
|
|
167
|
+
}).then(({ x, y, placement, middlewareData }) => {
|
|
168
|
+
Object.assign(tooltip.style, {
|
|
146
169
|
left: `${x}px`,
|
|
147
170
|
top: `${y}px`
|
|
148
171
|
});
|
|
149
172
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
// Accessing the data
|
|
174
|
+
const staticSide = {
|
|
175
|
+
top: 'bottom',
|
|
176
|
+
right: 'left',
|
|
177
|
+
bottom: 'top',
|
|
178
|
+
left: 'right'
|
|
179
|
+
}[placement.split('-')[0]];
|
|
154
180
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
bottom: 'top',
|
|
159
|
-
left: 'right'
|
|
160
|
-
}[side];
|
|
181
|
+
if (showArrow && arrowElement) {
|
|
182
|
+
if (!middlewareData?.arrow) return;
|
|
183
|
+
const { x: arrowX, y: arrowY } = middlewareData.arrow;
|
|
161
184
|
|
|
162
|
-
Object.assign(
|
|
185
|
+
Object.assign(arrowElement.style, {
|
|
163
186
|
left: arrowX != null ? `${arrowX}px` : '',
|
|
164
187
|
top: arrowY != null ? `${arrowY}px` : '',
|
|
165
188
|
right: '',
|
|
166
189
|
bottom: '',
|
|
167
|
-
[staticSide
|
|
190
|
+
[`${staticSide}`]: '-4px'
|
|
168
191
|
});
|
|
169
192
|
}
|
|
170
193
|
});
|
|
171
194
|
};
|
|
172
195
|
|
|
173
196
|
/**
|
|
174
|
-
* Shows the tooltip
|
|
197
|
+
* Shows the tooltip.
|
|
175
198
|
*/
|
|
176
199
|
const showTooltip = () => {
|
|
177
|
-
if (disabled) return;
|
|
200
|
+
if (disabled || !tooltipElement) return;
|
|
178
201
|
|
|
179
202
|
dismissOtherTooltips();
|
|
180
203
|
|
|
181
|
-
|
|
182
|
-
|
|
204
|
+
tooltipElement.style.display = 'flex';
|
|
205
|
+
tooltipElement.setAttribute('aria-hidden', 'false');
|
|
183
206
|
|
|
184
207
|
// Set trigger type for dismissal logic
|
|
185
208
|
const triggerType = listener === 'click' && listenerout === 'click' ? 'click' : 'hover';
|
|
186
|
-
|
|
209
|
+
tooltipElement.setAttribute('data-trigger-type', triggerType);
|
|
187
210
|
|
|
188
211
|
isActive = true;
|
|
189
|
-
hidden = false;
|
|
190
212
|
|
|
191
|
-
|
|
192
|
-
activeTooltips = [...activeTooltips.filter((id) => id !== tooltipId), tooltipId];
|
|
193
|
-
}
|
|
213
|
+
activeTooltips.update((ids) => [...ids.filter((id) => id !== tooltipId), tooltipId]);
|
|
194
214
|
|
|
195
215
|
update();
|
|
196
|
-
|
|
216
|
+
onshow?.(true);
|
|
197
217
|
};
|
|
198
218
|
|
|
199
219
|
/**
|
|
200
|
-
* Hides the tooltip
|
|
220
|
+
* Hides the tooltip.
|
|
201
221
|
*/
|
|
202
222
|
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
223
|
setTimeout(() => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
if (!tooltipElement) return;
|
|
225
|
+
|
|
226
|
+
tooltipElement.style.display = 'none';
|
|
227
|
+
tooltipElement.setAttribute('aria-hidden', 'true');
|
|
228
|
+
tooltipElement.removeAttribute('data-trigger-type');
|
|
213
229
|
isActive = false;
|
|
214
|
-
hidden = true;
|
|
215
|
-
activeTooltips = activeTooltips.filter((id) => id !== tooltipId);
|
|
216
230
|
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
activeTooltips.update((ids) => ids.filter((id) => id !== tooltipId));
|
|
232
|
+
|
|
233
|
+
onclose?.(true);
|
|
234
|
+
}, 50);
|
|
219
235
|
};
|
|
220
236
|
|
|
221
|
-
|
|
222
|
-
let eventOptions: Array<[string, () => void]> = [];
|
|
237
|
+
let opts;
|
|
223
238
|
|
|
224
239
|
if (listener === 'click' && listenerout === 'click') {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
if (
|
|
240
|
+
opts = [['click', showTooltip]];
|
|
241
|
+
|
|
242
|
+
// Store reference to the click handler for cleanup
|
|
243
|
+
documentClickListener = (event: MouseEvent) => {
|
|
244
|
+
if (
|
|
245
|
+
tooltipElement &&
|
|
246
|
+
toggle &&
|
|
247
|
+
!tooltipElement.contains(event.target as Node) &&
|
|
248
|
+
!toggle.contains(event.target as Node)
|
|
249
|
+
) {
|
|
230
250
|
hideTooltip();
|
|
231
251
|
}
|
|
232
252
|
};
|
|
233
253
|
|
|
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
|
-
};
|
|
254
|
+
document.addEventListener('click', documentClickListener);
|
|
262
255
|
} else {
|
|
263
|
-
|
|
256
|
+
opts = [
|
|
264
257
|
listener === 'click' ? ['click', showTooltip] : undefined,
|
|
265
258
|
listener === 'hover' ? ['mouseenter', showTooltip] : undefined,
|
|
266
259
|
listener === 'hover' ? ['mouseleave', hideTooltip] : undefined,
|
|
267
260
|
listener === 'hover' ? ['focus', showTooltip] : undefined,
|
|
268
261
|
['blur', hideTooltip]
|
|
269
|
-
]
|
|
262
|
+
];
|
|
270
263
|
}
|
|
271
264
|
|
|
272
|
-
|
|
273
|
-
|
|
265
|
+
type EventOption = [string, () => void];
|
|
266
|
+
const options: EventOption[] = opts.filter(Boolean) as EventOption[];
|
|
267
|
+
|
|
268
|
+
options.forEach(([event, listener]) => {
|
|
274
269
|
toggle.addEventListener(event, (evt) => {
|
|
275
270
|
if (stopPropagation) {
|
|
276
271
|
evt.stopPropagation();
|
|
277
272
|
evt.preventDefault();
|
|
278
273
|
}
|
|
279
|
-
|
|
274
|
+
listener();
|
|
280
275
|
});
|
|
281
276
|
});
|
|
282
277
|
|
|
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
|
-
}
|
|
278
|
+
const cleanup = () => {
|
|
279
|
+
if (tooltipElement) {
|
|
280
|
+
autoUpdate(toggle, tooltipElement, update)();
|
|
281
|
+
}
|
|
282
|
+
if (documentClickListener) {
|
|
283
|
+
document.removeEventListener('click', documentClickListener);
|
|
284
|
+
documentClickListener = null;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
295
287
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
tooltipInstance?.showTooltip();
|
|
299
|
-
}
|
|
288
|
+
return { toggle, tooltip: tooltipElement!, arrowElement, cleanup, showTooltip, hideTooltip };
|
|
289
|
+
};
|
|
300
290
|
|
|
301
|
-
export
|
|
302
|
-
|
|
303
|
-
}
|
|
291
|
+
export const show = () => tooltipInstance?.showTooltip();
|
|
292
|
+
export const hide = () => tooltipInstance?.hideTooltip();
|
|
304
293
|
|
|
305
|
-
//
|
|
294
|
+
// Svelte 5 effect for hidden prop
|
|
306
295
|
$effect(() => {
|
|
307
296
|
if (hidden) {
|
|
308
297
|
setTimeout(() => {
|
|
@@ -316,129 +305,107 @@
|
|
|
316
305
|
}
|
|
317
306
|
});
|
|
318
307
|
|
|
319
|
-
$
|
|
320
|
-
if (tooltipElement) {
|
|
321
|
-
const handleTooltipInteraction = (evt: Event) => {
|
|
322
|
-
evt.stopPropagation();
|
|
323
|
-
};
|
|
308
|
+
let isClickTarget = $state(listener === 'click' && listenerout === 'click');
|
|
324
309
|
|
|
325
|
-
|
|
326
|
-
tooltipElement.addEventListener('mousedown', handleTooltipInteraction, true);
|
|
327
|
-
tooltipElement.addEventListener('mouseup', handleTooltipInteraction, true);
|
|
310
|
+
const formattedMessage = $derived(cleanupTooltipMessage(message));
|
|
328
311
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
tooltipElement.removeEventListener('mouseup', handleTooltipInteraction, true);
|
|
333
|
-
};
|
|
334
|
-
}
|
|
312
|
+
onMount(async () => {
|
|
313
|
+
await tick();
|
|
314
|
+
tooltipInstance = await initTooltip();
|
|
335
315
|
});
|
|
336
316
|
|
|
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
|
-
});
|
|
317
|
+
onDestroy(() => {
|
|
318
|
+
// Disconnect observer first to prevent any new callbacks
|
|
319
|
+
if (observer) {
|
|
320
|
+
observer.disconnect();
|
|
321
|
+
observer = null;
|
|
322
|
+
}
|
|
352
323
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
324
|
+
// Clean up document click listener
|
|
325
|
+
if (documentClickListener) {
|
|
326
|
+
document.removeEventListener('click', documentClickListener);
|
|
327
|
+
documentClickListener = null;
|
|
356
328
|
}
|
|
357
329
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
if (observer) {
|
|
365
|
-
observer.disconnect();
|
|
366
|
-
observer = null;
|
|
367
|
-
}
|
|
368
|
-
};
|
|
330
|
+
if (tooltipInstance) {
|
|
331
|
+
tooltipInstance.cleanup();
|
|
332
|
+
activeTooltips.update((ids) => ids.filter((id) => id !== tooltipId));
|
|
333
|
+
tooltipInstance = null;
|
|
334
|
+
}
|
|
369
335
|
});
|
|
370
336
|
</script>
|
|
371
337
|
|
|
372
|
-
<div class="target
|
|
338
|
+
<div class="target" bind:this={targetElement} aria-describedby={tooltipId}>
|
|
373
339
|
{#if target}
|
|
374
340
|
{@render target()}
|
|
375
341
|
{:else}
|
|
376
|
-
|
|
377
|
-
|
|
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>
|
|
342
|
+
<!-- Default target for Demo only -->
|
|
343
|
+
<Text link label={isClickTarget ? 'Click me' : 'Hover me'} />
|
|
388
344
|
{/if}
|
|
389
345
|
|
|
390
346
|
<div
|
|
391
|
-
class="tooltip"
|
|
347
|
+
class="tooltip {className}"
|
|
392
348
|
bind:this={tooltipElement}
|
|
393
349
|
role="tooltip"
|
|
394
350
|
id={tooltipId}
|
|
395
|
-
|
|
396
|
-
|
|
351
|
+
style="position:{position}; width:{width}; padding:{padding}; {bgColor
|
|
352
|
+
? `background-color: ${bgColor};`
|
|
353
|
+
: ''}"
|
|
397
354
|
>
|
|
398
355
|
{#if tooltipIcon}
|
|
399
|
-
{@const
|
|
356
|
+
{@const Icon = tooltipIcon}
|
|
400
357
|
<div class="icon" style="color:{tooltipIconColor};">
|
|
401
|
-
<
|
|
358
|
+
<Icon />
|
|
402
359
|
</div>
|
|
403
360
|
{/if}
|
|
404
361
|
|
|
405
|
-
|
|
406
|
-
{
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{/if}
|
|
417
|
-
</div>
|
|
362
|
+
{#if tooltip}
|
|
363
|
+
{@render tooltip()}
|
|
364
|
+
{:else if message && raw}
|
|
365
|
+
<div class="message" style="color:{fontColor};">
|
|
366
|
+
{@html message}
|
|
367
|
+
</div>
|
|
368
|
+
{:else if message}
|
|
369
|
+
<div class="message">
|
|
370
|
+
<Text label={formattedMessage} fontSize="11px" fontWeight="500" {fontColor} />
|
|
371
|
+
</div>
|
|
372
|
+
{/if}
|
|
418
373
|
|
|
419
374
|
{#if showArrow}
|
|
420
|
-
<div
|
|
375
|
+
<div
|
|
376
|
+
class="arrow"
|
|
377
|
+
id="arrow_{tooltipId}"
|
|
378
|
+
bind:this={arrowElement}
|
|
379
|
+
style={bgColor ? `background-color: ${bgColor};` : ''}
|
|
380
|
+
></div>
|
|
421
381
|
{/if}
|
|
422
382
|
</div>
|
|
423
383
|
</div>
|
|
424
384
|
|
|
425
385
|
<style>
|
|
386
|
+
.message {
|
|
387
|
+
background: var(--background3, #383838);
|
|
388
|
+
text-wrap: normal;
|
|
389
|
+
white-space: normal;
|
|
390
|
+
color: var(--text2, #b9b9b9);
|
|
391
|
+
font-weight: 500;
|
|
392
|
+
font-size: 11px;
|
|
393
|
+
}
|
|
426
394
|
.tooltip {
|
|
427
395
|
display: none;
|
|
428
396
|
top: 0;
|
|
429
397
|
left: 0;
|
|
430
|
-
border-radius: 4px;
|
|
431
398
|
z-index: 99999999999;
|
|
432
|
-
border-radius:
|
|
433
|
-
background: var(--background3);
|
|
399
|
+
border-radius: 4px;
|
|
400
|
+
background: var(--background3, #383838);
|
|
434
401
|
box-shadow:
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
color: var(--text2);
|
|
402
|
+
0px 12px 24px 8px rgb(0 0 0 / 30%),
|
|
403
|
+
0px 8px 16px 4px rgb(0 0 0 / 30%),
|
|
404
|
+
0px 4px 8px 2px rgb(0 0 0 / 0%),
|
|
405
|
+
0px 2px 6px 0px rgba(0, 0, 0, 0.08),
|
|
406
|
+
0px -0.5px 0.5px 0px rgb(0 0 0 / 0%) inset,
|
|
407
|
+
0px 0.5px 0.5px 0px rgb(255 255 255 / 34%) inset;
|
|
408
|
+
color: var(--text2, #b9b9b9);
|
|
442
409
|
font-family: Inter;
|
|
443
410
|
line-height: 120%;
|
|
444
411
|
width: 150px;
|
|
@@ -448,38 +415,20 @@
|
|
|
448
415
|
align-items: start;
|
|
449
416
|
justify-content: start;
|
|
450
417
|
}
|
|
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
418
|
.arrow {
|
|
419
|
+
z-index: 1;
|
|
420
|
+
background: var(--background3, #383838);
|
|
469
421
|
position: absolute;
|
|
470
|
-
width:
|
|
471
|
-
height:
|
|
422
|
+
width: 8px;
|
|
423
|
+
height: 8px;
|
|
472
424
|
transform: rotate(45deg);
|
|
473
425
|
}
|
|
474
|
-
|
|
475
|
-
|
|
426
|
+
.target :global(div[slot='tooltip']) {
|
|
427
|
+
width: 100%;
|
|
428
|
+
}
|
|
429
|
+
.target :global(div[slot='target']) {
|
|
476
430
|
display: flex;
|
|
477
431
|
align-items: center;
|
|
478
432
|
justify-content: center;
|
|
479
433
|
}
|
|
480
|
-
|
|
481
|
-
.icon {
|
|
482
|
-
position: relative;
|
|
483
|
-
z-index: 1;
|
|
484
|
-
}
|
|
485
434
|
</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;
|