@placeholderco/placeholder-ui 1.0.11 → 2.0.0
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/LICENSE +1 -1
- package/README.md +134 -130
- package/dist/app.css +178 -0
- package/dist/bootstrap.min.css +3600 -0
- package/dist/cards/GitCard.svelte +115 -0
- package/dist/cards/GitCard.svelte.d.ts +22 -0
- package/dist/display/Avatar.svelte +29 -17
- package/dist/display/Avatar.svelte.d.ts +12 -2
- package/dist/display/LinkCollection.svelte +40 -18
- package/dist/display/LinkCollection.svelte.d.ts +9 -2
- package/dist/display/Paper.svelte +31 -19
- package/dist/display/Paper.svelte.d.ts +12 -3
- package/dist/display/alert/Alert.svelte +129 -0
- package/dist/display/alert/Alert.svelte.d.ts +15 -0
- package/dist/display/alert/AlertVariant.d.ts +1 -0
- package/dist/display/alert/AlertVariant.js +1 -0
- package/dist/display/filetree/FileTree.svelte +259 -0
- package/dist/display/filetree/FileTree.svelte.d.ts +76 -0
- package/dist/display/filetree/FileTreeItem.svelte +396 -0
- package/dist/display/filetree/FileTreeItem.svelte.d.ts +40 -0
- package/dist/display/filetree/FileTreeNode.d.ts +46 -0
- package/dist/display/filetree/FileTreeNode.js +115 -0
- package/dist/display/filetree/index.d.ts +5 -0
- package/dist/display/filetree/index.js +3 -0
- package/dist/editors/CodeMirror.svelte +306 -0
- package/dist/editors/CodeMirror.svelte.d.ts +31 -0
- package/dist/editors/tiptap/LinkActionMenu.svelte +307 -0
- package/dist/editors/tiptap/LinkActionMenu.svelte.d.ts +8 -0
- package/dist/editors/tiptap/TipTap.svelte +856 -0
- package/dist/editors/tiptap/TipTap.svelte.d.ts +54 -0
- package/dist/editors/tiptap/TipTapBubble.svelte +790 -0
- package/dist/editors/tiptap/TipTapBubble.svelte.d.ts +44 -0
- package/dist/editors/tiptap/collaboration.d.ts +55 -0
- package/dist/editors/tiptap/collaboration.js +1 -0
- package/dist/editors/tiptap/extensions/Alert.d.ts +18 -0
- package/dist/editors/tiptap/extensions/Alert.js +72 -0
- package/dist/editors/tiptap/extensions/CodeBlockEditor.d.ts +21 -0
- package/dist/editors/tiptap/extensions/CodeBlockEditor.js +326 -0
- package/dist/editors/tiptap/extensions/Div.d.ts +19 -0
- package/dist/editors/tiptap/extensions/Div.js +89 -0
- package/dist/editors/tiptap/extensions/Span.d.ts +5 -0
- package/dist/editors/tiptap/extensions/Span.js +56 -0
- package/dist/editors/tiptap/extensions/codeBlockEditor.css +236 -0
- package/dist/editors/tiptap/extensions/codeBlockLanguages.d.ts +17 -0
- package/dist/editors/tiptap/extensions/codeBlockLanguages.js +124 -0
- package/dist/editors/tiptap/extensions/index.d.ts +7 -0
- package/dist/editors/tiptap/extensions/index.js +5 -0
- package/dist/editors/tiptap/toolbar/AlertButton.svelte +261 -0
- package/dist/editors/tiptap/toolbar/AlertButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/AlignCenterButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/AlignJustifyButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/AlignLeftButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/AlignRightButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/AlignRightButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/BoldButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/BoldButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/BulletListButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/BulletListButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/CodeBlockButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/CodeButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/CodeButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/ColorButton.svelte +299 -0
- package/dist/editors/tiptap/toolbar/ColorButton.svelte.d.ts +20 -0
- package/dist/editors/tiptap/toolbar/DocumentButton.svelte +58 -0
- package/dist/editors/tiptap/toolbar/DocumentButton.svelte.d.ts +9 -0
- package/dist/editors/tiptap/toolbar/HeadingButton.svelte +30 -0
- package/dist/editors/tiptap/toolbar/HeadingButton.svelte.d.ts +8 -0
- package/dist/editors/tiptap/toolbar/HtmlButton.svelte +68 -0
- package/dist/editors/tiptap/toolbar/HtmlButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/ImageButton.svelte +54 -0
- package/dist/editors/tiptap/toolbar/ImageButton.svelte.d.ts +8 -0
- package/dist/editors/tiptap/toolbar/ItalicButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/ItalicButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/LinkButton.svelte +257 -0
- package/dist/editors/tiptap/toolbar/LinkButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/OrderedListButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/OrderedListButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/RedoButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/RedoButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/StrikethroughButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/SubscriptButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/SubscriptButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/SuperscriptButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/TableButton.svelte +258 -0
- package/dist/editors/tiptap/toolbar/TableButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/ToolbarButton.svelte +84 -0
- package/dist/editors/tiptap/toolbar/ToolbarButton.svelte.d.ts +10 -0
- package/dist/editors/tiptap/toolbar/UnderlineButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/UnderlineButton.svelte.d.ts +7 -0
- package/dist/editors/tiptap/toolbar/UndoButton.svelte +18 -0
- package/dist/editors/tiptap/toolbar/UndoButton.svelte.d.ts +7 -0
- package/dist/form/Autocomplete.svelte +270 -137
- package/dist/form/Autocomplete.svelte.d.ts +58 -10
- package/dist/form/AutocompleteMulti.svelte +385 -227
- package/dist/form/AutocompleteMulti.svelte.d.ts +59 -10
- package/dist/form/Checkbox.svelte +124 -43
- package/dist/form/Checkbox.svelte.d.ts +26 -2
- package/dist/form/Chips.svelte +47 -17
- package/dist/form/Chips.svelte.d.ts +24 -3
- package/dist/form/ComboBox.svelte +30 -20
- package/dist/form/ComboBox.svelte.d.ts +1 -1
- package/dist/form/ComboBoxItemBuilder.svelte +161 -188
- package/dist/form/ComboBoxItemBuilder.svelte.d.ts +19 -2
- package/dist/form/ComboBoxMulti.svelte +38 -24
- package/dist/form/ComboBoxMulti.svelte.d.ts +1 -1
- package/dist/form/CronBuilder.svelte +84 -67
- package/dist/form/CronBuilder.svelte.d.ts +16 -3
- package/dist/form/DatePicker.svelte +177 -191
- package/dist/form/DatePicker.svelte.d.ts +17 -2
- package/dist/form/DateRangePicker.svelte +775 -0
- package/dist/form/DateRangePicker.svelte.d.ts +23 -0
- package/dist/form/DateTimePicker.svelte +168 -209
- package/dist/form/DateTimePicker.svelte.d.ts +10 -2
- package/dist/form/FormGroup.svelte +33 -10
- package/dist/form/FormGroup.svelte.d.ts +12 -2
- package/dist/form/Number.svelte +71 -29
- package/dist/form/Number.svelte.d.ts +39 -6
- package/dist/form/Radio.svelte +174 -0
- package/dist/form/Radio.svelte.d.ts +21 -0
- package/dist/form/RadioGroup.svelte +52 -138
- package/dist/form/RadioGroup.svelte.d.ts +19 -2
- package/dist/form/SegmentedControl.svelte +103 -57
- package/dist/form/SegmentedControl.svelte.d.ts +12 -2
- package/dist/form/Select.svelte +160 -229
- package/dist/form/Select.svelte.d.ts +35 -6
- package/dist/form/SelectMulti.svelte +166 -277
- package/dist/form/SelectMulti.svelte.d.ts +37 -6
- package/dist/form/Slider.svelte +185 -268
- package/dist/form/Slider.svelte.d.ts +33 -16
- package/dist/form/StringArrayBuilder.svelte +249 -0
- package/dist/form/StringArrayBuilder.svelte.d.ts +15 -0
- package/dist/form/Switch.svelte +164 -106
- package/dist/form/Switch.svelte.d.ts +22 -3
- package/dist/form/TextArea.svelte +80 -45
- package/dist/form/TextArea.svelte.d.ts +35 -10
- package/dist/form/Textbox.svelte +80 -47
- package/dist/form/Textbox.svelte.d.ts +41 -8
- package/dist/form/TimePicker.svelte +42 -47
- package/dist/form/TimePicker.svelte.d.ts +12 -2
- package/dist/form/comboBoxCore.svelte.d.ts +99 -0
- package/dist/form/comboBoxCore.svelte.js +243 -0
- package/dist/icon/Icon.svelte +8 -3
- package/dist/icon/Icon.svelte.d.ts +8 -3
- package/dist/icon/cube-send.svg +1 -0
- package/dist/icon/file-type-bmp.svg +1 -0
- package/dist/icon/file-type-csv.svg +1 -0
- package/dist/icon/file-type-doc.svg +1 -0
- package/dist/icon/file-type-docx.svg +1 -0
- package/dist/icon/file-type-jpg.svg +1 -0
- package/dist/icon/file-type-pdf.svg +1 -0
- package/dist/icon/file-type-png.svg +1 -0
- package/dist/icon/file-type-ppt.svg +1 -0
- package/dist/icon/file-type-svg.svg +1 -0
- package/dist/icon/file-type-txt.svg +1 -0
- package/dist/icon/file-type-xls.svg +1 -0
- package/dist/icon/file-type-zip.svg +1 -0
- package/dist/icon/index.d.ts +166 -145
- package/dist/icon/index.js +166 -146
- package/dist/icon/layout-sidebar-left-collapse.svg +1 -0
- package/dist/icon/layout-sidebar-left-expand.svg +1 -0
- package/dist/icon/logo-placeholder.svg +6 -0
- package/dist/icon/paperclip.svg +1 -0
- package/dist/icon/source-code.svg +1 -0
- package/dist/icon/sun-moon.svg +1 -0
- package/dist/icon/table.svg +6 -0
- package/dist/icon/thumb-down.svg +1 -0
- package/dist/icon/thumb-up.svg +1 -0
- package/dist/index.d.ts +93 -22
- package/dist/index.js +28 -27
- package/dist/layout/AppShell.svelte +12 -162
- package/dist/layout/AppShell.svelte.d.ts +6 -27
- package/dist/layout/CustomNavbar.svelte +9 -7
- package/dist/layout/CustomNavbar.svelte.d.ts +4 -2
- package/dist/layout/Navbar.svelte +158 -110
- package/dist/layout/Navbar.svelte.d.ts +26 -12
- package/dist/layout/NavbarItemDisplay.svelte +2 -2
- package/dist/layout/NavbarItemView.svelte +182 -0
- package/dist/layout/NavbarItemView.svelte.d.ts +10 -0
- package/dist/layout/Sidenav.svelte +280 -341
- package/dist/layout/Sidenav.svelte.d.ts +36 -4
- package/dist/models/ComboBoxItem.d.ts +2 -0
- package/dist/models/Employee.d.ts +53 -0
- package/dist/models/Employee.js +23 -0
- package/dist/models/Hyperlink.d.ts +3 -1
- package/dist/models/LinkCollectionItem.d.ts +3 -1
- package/dist/models/NavbarItem.d.ts +6 -2
- package/dist/theme.svelte.d.ts +0 -1
- package/dist/theme.svelte.js +46 -58
- package/dist/ui/Accordion.svelte +81 -242
- package/dist/ui/Accordion.svelte.d.ts +37 -14
- package/dist/ui/AccordionItem.svelte +303 -0
- package/dist/ui/AccordionItem.svelte.d.ts +21 -0
- package/dist/ui/ActionIcon.svelte +25 -21
- package/dist/ui/ActionIcon.svelte.d.ts +15 -2
- package/dist/ui/Badge.svelte +170 -161
- package/dist/ui/Badge.svelte.d.ts +16 -4
- package/dist/ui/Button.svelte +178 -205
- package/dist/ui/Button.svelte.d.ts +19 -2
- package/dist/ui/ButtonVariant.d.ts +15 -1
- package/dist/ui/ContextMenu.svelte +146 -0
- package/dist/ui/ContextMenu.svelte.d.ts +37 -0
- package/dist/ui/ContextMenuList.svelte +188 -0
- package/dist/ui/ContextMenuList.svelte.d.ts +12 -0
- package/dist/ui/Dialog.svelte +183 -104
- package/dist/ui/Dialog.svelte.d.ts +21 -2
- package/dist/ui/Drawer.svelte +237 -184
- package/dist/ui/Drawer.svelte.d.ts +20 -2
- package/dist/ui/Dropdown.svelte +24 -14
- package/dist/ui/Dropdown.svelte.d.ts +11 -2
- package/dist/ui/Dropzone.svelte +159 -57
- package/dist/ui/Dropzone.svelte.d.ts +29 -2
- package/dist/ui/Link.svelte +12 -4
- package/dist/ui/Link.svelte.d.ts +8 -2
- package/dist/ui/Loader.svelte +23 -18
- package/dist/ui/Loader.svelte.d.ts +5 -2
- package/dist/ui/Logo.svelte +37 -0
- package/dist/ui/Logo.svelte.d.ts +16 -0
- package/dist/ui/MultiSortable.svelte +287 -0
- package/dist/ui/MultiSortable.svelte.d.ts +50 -0
- package/dist/ui/Pagination.svelte +12 -6
- package/dist/ui/Pagination.svelte.d.ts +6 -2
- package/dist/ui/Popover.svelte +43 -65
- package/dist/ui/Popover.svelte.d.ts +14 -5
- package/dist/ui/ProgressBar.svelte +169 -0
- package/dist/ui/ProgressBar.svelte.d.ts +30 -0
- package/dist/ui/ProgressBarVariant.d.ts +7 -0
- package/dist/ui/ProgressBarVariant.js +1 -0
- package/dist/ui/SegmentedControl.svelte +0 -0
- package/dist/ui/SegmentedControl.svelte.d.ts +26 -0
- package/dist/ui/Sortable.svelte +163 -0
- package/dist/ui/Sortable.svelte.d.ts +47 -0
- package/dist/ui/Table.svelte +497 -56
- package/dist/ui/Table.svelte.d.ts +64 -2
- package/dist/ui/Tabs.svelte +22 -18
- package/dist/ui/Tabs.svelte.d.ts +6 -2
- package/dist/ui/ThemeSwitcher.svelte +26 -7
- package/dist/ui/ThemeSwitcher.svelte.d.ts +4 -2
- package/dist/ui/Tooltip.svelte +312 -180
- package/dist/ui/Tooltip.svelte.d.ts +4 -3
- package/dist/uiTheme.svelte.d.ts +86 -0
- package/dist/uiTheme.svelte.js +214 -0
- package/dist/util/DateFunctions.js +1 -1
- package/dist/util/DragDrop.d.ts +50 -0
- package/dist/util/DragDrop.js +154 -0
- package/dist/util/NavigateTo.js +2 -2
- package/dist/util/dayjs.d.ts +4 -0
- package/dist/util/dayjs.js +15 -0
- package/dist/util/interceptLinkClick.d.ts +13 -0
- package/dist/util/interceptLinkClick.js +33 -0
- package/package.json +66 -26
- package/dist/display/Alert.svelte +0 -179
- package/dist/display/Alert.svelte.d.ts +0 -13
- package/dist/form/FileInput.svelte +0 -235
- package/dist/form/FileInput.svelte.d.ts +0 -23
- package/dist/form/PasswordInput.svelte +0 -252
- package/dist/form/PasswordInput.svelte.d.ts +0 -33
- package/dist/form/Rating.svelte +0 -235
- package/dist/form/Rating.svelte.d.ts +0 -21
- package/dist/styles/components.css +0 -199
- package/dist/styles/dark.css +0 -146
- package/dist/styles/index.css +0 -116
- package/dist/styles/reset.css +0 -110
- package/dist/styles/semantic.css +0 -86
- package/dist/styles/tokens.css +0 -203
- package/dist/styles/utilities.css +0 -523
- package/dist/ui/Breadcrumbs.svelte +0 -131
- package/dist/ui/Breadcrumbs.svelte.d.ts +0 -16
- package/dist/ui/LoadingOverlay.svelte +0 -53
- package/dist/ui/LoadingOverlay.svelte.d.ts +0 -11
- package/dist/ui/Progress.svelte +0 -191
- package/dist/ui/Progress.svelte.d.ts +0 -15
- package/dist/ui/RingProgress.svelte +0 -141
- package/dist/ui/RingProgress.svelte.d.ts +0 -18
- package/dist/ui/Skeleton.svelte +0 -85
- package/dist/ui/Skeleton.svelte.d.ts +0 -12
- package/dist/ui/Stepper.svelte +0 -355
- package/dist/ui/Stepper.svelte.d.ts +0 -24
- package/dist/ui/Timeline.svelte +0 -225
- package/dist/ui/Timeline.svelte.d.ts +0 -23
- /package/dist/{actions → util}/ClickOutside.d.ts +0 -0
- /package/dist/{actions → util}/ClickOutside.js +0 -0
- /package/dist/{transitions → util}/Transitions.d.ts +0 -0
- /package/dist/{transitions → util}/Transitions.js +0 -0
package/dist/ui/Tooltip.svelte
CHANGED
|
@@ -2,7 +2,23 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { fade } from 'svelte/transition';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
export type TooltipLocation =
|
|
6
|
+
| 'top'
|
|
7
|
+
| 'right'
|
|
8
|
+
| 'bottom'
|
|
9
|
+
| 'left'
|
|
10
|
+
| 'top-start'
|
|
11
|
+
| 'top-end'
|
|
12
|
+
| 'right-start'
|
|
13
|
+
| 'right-end'
|
|
14
|
+
| 'bottom-start'
|
|
15
|
+
| 'bottom-end'
|
|
16
|
+
| 'left-start'
|
|
17
|
+
| 'left-end';
|
|
18
|
+
|
|
19
|
+
type Side = 'top' | 'right' | 'bottom' | 'left';
|
|
20
|
+
|
|
21
|
+
export interface TooltipProps {
|
|
6
22
|
/** The element to attach the tooltip to */
|
|
7
23
|
children: Snippet;
|
|
8
24
|
/** Rich content using a Svelte snippet */
|
|
@@ -12,7 +28,7 @@
|
|
|
12
28
|
/** HTML content (use with caution - sanitize user input) */
|
|
13
29
|
html?: string;
|
|
14
30
|
/** Preferred tooltip position */
|
|
15
|
-
location?:
|
|
31
|
+
location?: TooltipLocation;
|
|
16
32
|
/** Maximum width of tooltip */
|
|
17
33
|
maxWidth?: string;
|
|
18
34
|
/** Offset distance from target element (px) */
|
|
@@ -39,33 +55,50 @@
|
|
|
39
55
|
delay = 0,
|
|
40
56
|
onOpen,
|
|
41
57
|
disabled = false
|
|
42
|
-
}:
|
|
58
|
+
}: TooltipProps = $props();
|
|
59
|
+
|
|
60
|
+
/** Half the arrow's base and its full height — the triangle is 2x wide, 1x tall. */
|
|
61
|
+
const ARROW_SIZE = 6;
|
|
62
|
+
/** Matches .tooltip-v2-content border-radius so the arrow never sits on a rounded corner. */
|
|
63
|
+
const CORNER_RADIUS = 6;
|
|
64
|
+
/** Minimum gap between the tooltip and the viewport edge. */
|
|
65
|
+
const VIEWPORT_PADDING = 5;
|
|
66
|
+
|
|
67
|
+
const OPPOSITE_SIDE: Record<Side, Side> = {
|
|
68
|
+
top: 'bottom',
|
|
69
|
+
bottom: 'top',
|
|
70
|
+
left: 'right',
|
|
71
|
+
right: 'left'
|
|
72
|
+
};
|
|
43
73
|
|
|
44
74
|
// State
|
|
45
75
|
let open = $state(false);
|
|
76
|
+
/** False until the tooltip has been measured and placed; it stays hidden until then. */
|
|
77
|
+
let positioned = $state(false);
|
|
46
78
|
let referenceElement: HTMLElement | null = $state(null);
|
|
47
79
|
let tooltipElement: HTMLElement | null = $state(null);
|
|
48
|
-
let arrowElement: HTMLElement | null = $state(null);
|
|
49
80
|
let timeoutId: number | null = null;
|
|
81
|
+
let rafId: number | null = null;
|
|
82
|
+
/** Ancestor the tooltip's left/top actually resolve against, if not the viewport. */
|
|
83
|
+
let fixedContainer: HTMLElement | null = null;
|
|
50
84
|
|
|
51
85
|
// Position state
|
|
52
|
-
let tooltipStyle = $state('');
|
|
86
|
+
let tooltipStyle = $state('left: 0px; top: 0px;');
|
|
53
87
|
let arrowStyle = $state('');
|
|
54
|
-
let actualPlacement = $state<
|
|
55
|
-
|
|
56
|
-
// Sync actualPlacement with location prop
|
|
57
|
-
$effect(() => {
|
|
58
|
-
actualPlacement = location;
|
|
59
|
-
});
|
|
88
|
+
let actualPlacement = $state<TooltipLocation>('top');
|
|
60
89
|
|
|
61
90
|
// Check if tooltip has any content
|
|
62
91
|
const hasContent = $derived(!!(text || html || tooltipContent));
|
|
92
|
+
const visible = $derived(open && hasContent && !disabled);
|
|
63
93
|
|
|
64
94
|
function handleMouseEnter() {
|
|
65
95
|
if (disabled || !hasContent) return;
|
|
66
96
|
|
|
97
|
+
cancelPending();
|
|
98
|
+
|
|
67
99
|
if (delay > 0) {
|
|
68
100
|
timeoutId = window.setTimeout(() => {
|
|
101
|
+
timeoutId = null;
|
|
69
102
|
showTooltip();
|
|
70
103
|
}, delay);
|
|
71
104
|
} else {
|
|
@@ -74,65 +107,175 @@
|
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
function handleMouseLeave() {
|
|
77
|
-
|
|
110
|
+
cancelPending();
|
|
111
|
+
hideTooltip();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function cancelPending() {
|
|
115
|
+
if (timeoutId !== null) {
|
|
78
116
|
clearTimeout(timeoutId);
|
|
79
117
|
timeoutId = null;
|
|
80
118
|
}
|
|
81
|
-
hideTooltip();
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
function showTooltip() {
|
|
122
|
+
if (open) return;
|
|
123
|
+
|
|
124
|
+
// The floating element is remounted on every open, so it starts unmeasured.
|
|
125
|
+
// Keeping it hidden until the positioning effect has run stops the fade-in
|
|
126
|
+
// from playing at the coordinates left over from the previous open.
|
|
127
|
+
positioned = false;
|
|
85
128
|
open = true;
|
|
86
129
|
onOpen?.(true);
|
|
87
|
-
|
|
88
|
-
// Position tooltip after it's rendered
|
|
89
|
-
// Use multiple RAF calls and verify dimensions to ensure layout is complete
|
|
90
|
-
requestAnimationFrame(() => {
|
|
91
|
-
requestAnimationFrame(() => {
|
|
92
|
-
if (referenceElement && tooltipElement) {
|
|
93
|
-
positionTooltip();
|
|
94
|
-
|
|
95
|
-
// Verify and reposition if dimensions changed
|
|
96
|
-
requestAnimationFrame(() => {
|
|
97
|
-
if (tooltipElement) {
|
|
98
|
-
const rect = tooltipElement.getBoundingClientRect();
|
|
99
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
100
|
-
positionTooltip();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
function hideTooltip() {
|
|
133
|
+
if (!open) return;
|
|
134
|
+
|
|
110
135
|
open = false;
|
|
111
136
|
onOpen?.(false);
|
|
112
137
|
}
|
|
113
138
|
|
|
139
|
+
function clamp(value: number, min: number, max: number): number {
|
|
140
|
+
return Math.max(min, Math.min(value, max));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A transform, filter, perspective, containment or container-type on an ancestor
|
|
145
|
+
* makes that ancestor the containing block for `position: fixed` descendants —
|
|
146
|
+
* left/top stop meaning "from the viewport" while getBoundingClientRect() keeps
|
|
147
|
+
* reporting viewport coordinates. Dialog hits this: its open animation uses a
|
|
148
|
+
* `forwards` fill, which leaves `transform: translateY(0)` applied while open.
|
|
149
|
+
*/
|
|
150
|
+
function findFixedContainingBlock(element: HTMLElement): HTMLElement | null {
|
|
151
|
+
let node = element.parentElement;
|
|
152
|
+
|
|
153
|
+
while (node) {
|
|
154
|
+
const style = getComputedStyle(node);
|
|
155
|
+
// Newer properties go through getPropertyValue so an older DOM lib (or a
|
|
156
|
+
// browser without support) reports '' instead of failing to compile.
|
|
157
|
+
const containerType = style.getPropertyValue('container-type');
|
|
158
|
+
const backdropFilter =
|
|
159
|
+
style.getPropertyValue('backdrop-filter') ||
|
|
160
|
+
style.getPropertyValue('-webkit-backdrop-filter');
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
style.transform !== 'none' ||
|
|
164
|
+
style.perspective !== 'none' ||
|
|
165
|
+
style.filter !== 'none' ||
|
|
166
|
+
(backdropFilter !== '' && backdropFilter !== 'none') ||
|
|
167
|
+
(containerType !== '' && containerType !== 'normal') ||
|
|
168
|
+
/\b(paint|layout|strict|content)\b/.test(style.contain) ||
|
|
169
|
+
/\b(transform|perspective|filter)\b/.test(style.willChange)
|
|
170
|
+
) {
|
|
171
|
+
return node;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
node = node.parentElement;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Viewport coordinates of the origin that left/top are measured from. */
|
|
181
|
+
function containingBlockOrigin(): { x: number; y: number } {
|
|
182
|
+
if (!fixedContainer) return { x: 0, y: 0 };
|
|
183
|
+
|
|
184
|
+
const rect = fixedContainer.getBoundingClientRect();
|
|
185
|
+
const style = getComputedStyle(fixedContainer);
|
|
186
|
+
|
|
187
|
+
// Fixed descendants resolve against the padding box, so drop the border.
|
|
188
|
+
return {
|
|
189
|
+
x: rect.left + (parseFloat(style.borderLeftWidth) || 0),
|
|
190
|
+
y: rect.top + (parseFloat(style.borderTopWidth) || 0)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
114
194
|
function positionTooltip() {
|
|
195
|
+
// Read the geometry props before any early return, so that calling this from
|
|
196
|
+
// an effect always registers them as dependencies and a prop changed while
|
|
197
|
+
// the tooltip is open re-anchors it.
|
|
198
|
+
const preferred = location;
|
|
199
|
+
const offset = offsetDistance + (showArrow ? ARROW_SIZE : 0);
|
|
200
|
+
|
|
115
201
|
if (!referenceElement || !tooltipElement) return;
|
|
116
202
|
|
|
117
203
|
const refRect = referenceElement.getBoundingClientRect();
|
|
118
204
|
const tooltipRect = tooltipElement.getBoundingClientRect();
|
|
119
|
-
const arrowSize = showArrow ? 12 : 0;
|
|
120
|
-
const offset = offsetDistance + arrowSize;
|
|
121
205
|
|
|
122
|
-
|
|
206
|
+
// A zero-sized box means layout has not happened yet; anchoring to it would
|
|
207
|
+
// pin the tooltip to dimensions that are about to change.
|
|
208
|
+
if (tooltipRect.width === 0 || tooltipRect.height === 0) return;
|
|
209
|
+
|
|
210
|
+
const { position, placement } = resolvePlacement(preferred, refRect, tooltipRect, offset);
|
|
123
211
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
212
|
+
// `position` is in viewport space, which is what the arrow maths below wants
|
|
213
|
+
// too. Only the style needs translating into the containing block's space.
|
|
214
|
+
const origin = containingBlockOrigin();
|
|
215
|
+
|
|
216
|
+
actualPlacement = placement;
|
|
217
|
+
tooltipStyle = `left: ${Math.round(position.left - origin.x)}px; top: ${Math.round(position.top - origin.y)}px;`;
|
|
218
|
+
arrowStyle = showArrow ? calculateArrowPosition(placement, refRect, position, tooltipRect) : '';
|
|
219
|
+
positioned = true;
|
|
220
|
+
}
|
|
128
221
|
|
|
129
|
-
|
|
130
|
-
|
|
222
|
+
/** Coalesce bursts of scroll/resize events into one reposition per frame. */
|
|
223
|
+
function schedulePosition() {
|
|
224
|
+
if (rafId !== null) return;
|
|
131
225
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
226
|
+
rafId = requestAnimationFrame(() => {
|
|
227
|
+
rafId = null;
|
|
228
|
+
positionTooltip();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function resolvePlacement(
|
|
233
|
+
preferred: TooltipLocation,
|
|
234
|
+
refRect: DOMRect,
|
|
235
|
+
tooltipRect: DOMRect,
|
|
236
|
+
offset: number
|
|
237
|
+
): { position: { left: number; top: number }; placement: TooltipLocation } {
|
|
238
|
+
// clientWidth/Height exclude the scrollbars, which window.innerWidth includes.
|
|
239
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
240
|
+
const viewportHeight = document.documentElement.clientHeight;
|
|
241
|
+
|
|
242
|
+
const [side, alignment] = preferred.split('-') as [Side, string | undefined];
|
|
243
|
+
|
|
244
|
+
const space = {
|
|
245
|
+
top: refRect.top - VIEWPORT_PADDING,
|
|
246
|
+
bottom: viewportHeight - refRect.bottom - VIEWPORT_PADDING,
|
|
247
|
+
left: refRect.left - VIEWPORT_PADDING,
|
|
248
|
+
right: viewportWidth - refRect.right - VIEWPORT_PADDING
|
|
249
|
+
};
|
|
250
|
+
const required =
|
|
251
|
+
side === 'top' || side === 'bottom'
|
|
252
|
+
? tooltipRect.height + offset
|
|
253
|
+
: tooltipRect.width + offset;
|
|
254
|
+
|
|
255
|
+
// Flip only when the preferred side cannot fit *and* the opposite side is
|
|
256
|
+
// roomier — flipping into an equally cramped side just moves the problem.
|
|
257
|
+
let chosen = side;
|
|
258
|
+
if (space[side] < required && space[OPPOSITE_SIDE[side]] > space[side]) {
|
|
259
|
+
chosen = OPPOSITE_SIDE[side];
|
|
135
260
|
}
|
|
261
|
+
|
|
262
|
+
const placement = (alignment ? `${chosen}-${alignment}` : chosen) as TooltipLocation;
|
|
263
|
+
const position = calculatePosition(placement, refRect, tooltipRect, offset);
|
|
264
|
+
|
|
265
|
+
// Shift back inside the viewport. The arrow is clamped separately so it keeps
|
|
266
|
+
// pointing at the trigger after the box slides away from it.
|
|
267
|
+
position.left = clamp(
|
|
268
|
+
position.left,
|
|
269
|
+
VIEWPORT_PADDING,
|
|
270
|
+
viewportWidth - tooltipRect.width - VIEWPORT_PADDING
|
|
271
|
+
);
|
|
272
|
+
position.top = clamp(
|
|
273
|
+
position.top,
|
|
274
|
+
VIEWPORT_PADDING,
|
|
275
|
+
viewportHeight - tooltipRect.height - VIEWPORT_PADDING
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return { position, placement };
|
|
136
279
|
}
|
|
137
280
|
|
|
138
281
|
function calculatePosition(
|
|
@@ -179,174 +322,150 @@
|
|
|
179
322
|
return { left, top };
|
|
180
323
|
}
|
|
181
324
|
|
|
182
|
-
function checkAndFlipPosition(
|
|
183
|
-
position: { left: number; top: number },
|
|
184
|
-
tooltipRect: DOMRect,
|
|
185
|
-
placement: string,
|
|
186
|
-
refRect: DOMRect,
|
|
187
|
-
offset: number
|
|
188
|
-
): { position: { left: number; top: number }; placement: string } {
|
|
189
|
-
const viewportWidth = window.innerWidth;
|
|
190
|
-
const viewportHeight = window.innerHeight;
|
|
191
|
-
const padding = 5;
|
|
192
|
-
|
|
193
|
-
let newPosition = { ...position };
|
|
194
|
-
let newPlacement = placement;
|
|
195
|
-
const [side] = placement.split('-');
|
|
196
|
-
|
|
197
|
-
// Check vertical overflow
|
|
198
|
-
if (side === 'top' && position.top < padding) {
|
|
199
|
-
// Flip to bottom
|
|
200
|
-
newPlacement = placement.replace('top', 'bottom');
|
|
201
|
-
newPosition = calculatePosition(newPlacement, refRect, tooltipRect, offset);
|
|
202
|
-
} else if (side === 'bottom' && position.top + tooltipRect.height > viewportHeight - padding) {
|
|
203
|
-
// Flip to top
|
|
204
|
-
newPlacement = placement.replace('bottom', 'top');
|
|
205
|
-
newPosition = calculatePosition(newPlacement, refRect, tooltipRect, offset);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Check horizontal overflow
|
|
209
|
-
if (side === 'left' && position.left < padding) {
|
|
210
|
-
// Flip to right
|
|
211
|
-
newPlacement = placement.replace('left', 'right');
|
|
212
|
-
newPosition = calculatePosition(newPlacement, refRect, tooltipRect, offset);
|
|
213
|
-
} else if (side === 'right' && position.left + tooltipRect.width > viewportWidth - padding) {
|
|
214
|
-
// Flip to left
|
|
215
|
-
newPlacement = placement.replace('right', 'left');
|
|
216
|
-
newPosition = calculatePosition(newPlacement, refRect, tooltipRect, offset);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Shift within bounds if still overflowing
|
|
220
|
-
if (newPosition.left < padding) {
|
|
221
|
-
newPosition.left = padding;
|
|
222
|
-
} else if (newPosition.left + tooltipRect.width > viewportWidth - padding) {
|
|
223
|
-
newPosition.left = viewportWidth - tooltipRect.width - padding;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (newPosition.top < padding) {
|
|
227
|
-
newPosition.top = padding;
|
|
228
|
-
} else if (newPosition.top + tooltipRect.height > viewportHeight - padding) {
|
|
229
|
-
newPosition.top = viewportHeight - tooltipRect.height - padding;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { position: newPosition, placement: newPlacement };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
325
|
function calculateArrowPosition(
|
|
236
|
-
placement:
|
|
326
|
+
placement: TooltipLocation,
|
|
237
327
|
refRect: DOMRect,
|
|
238
|
-
tooltipPos: { left: number; top: number }
|
|
328
|
+
tooltipPos: { left: number; top: number },
|
|
329
|
+
tooltipRect: DOMRect
|
|
239
330
|
): string {
|
|
240
331
|
const [side] = placement.split('-');
|
|
241
332
|
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
y: refRect.top + refRect.height / 2
|
|
245
|
-
};
|
|
333
|
+
const refCenterX = refRect.left + refRect.width / 2;
|
|
334
|
+
const refCenterY = refRect.top + refRect.height / 2;
|
|
246
335
|
|
|
247
|
-
|
|
248
|
-
|
|
336
|
+
// Aim at the trigger's centre, but never past the tooltip's rounded corners —
|
|
337
|
+
// an unclamped arrow floats off the box once the tooltip has been shifted.
|
|
338
|
+
const inset = CORNER_RADIUS + ARROW_SIZE;
|
|
339
|
+
const arrowX = clamp(refCenterX - tooltipPos.left, inset, tooltipRect.width - inset);
|
|
340
|
+
const arrowY = clamp(refCenterY - tooltipPos.top, inset, tooltipRect.height - inset);
|
|
249
341
|
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
342
|
+
const transparent = `${ARROW_SIZE}px solid transparent`;
|
|
343
|
+
const filled = `${ARROW_SIZE}px solid var(--tooltip-bg)`;
|
|
344
|
+
// Negative margin centres the triangle on the aim point; the 100% offsets sit
|
|
345
|
+
// it flush against the edge so it spans the whole gap to the trigger.
|
|
346
|
+
const centreX = `margin-left: ${-ARROW_SIZE}px;`;
|
|
347
|
+
const centreY = `margin-top: ${-ARROW_SIZE}px;`;
|
|
253
348
|
|
|
254
349
|
switch (side) {
|
|
255
350
|
case 'top':
|
|
256
|
-
//
|
|
257
|
-
return `
|
|
258
|
-
left: ${arrowX}px;
|
|
259
|
-
top: ${tooltipHeight}px;
|
|
260
|
-
border-left: 12px solid transparent;
|
|
261
|
-
border-right: 12px solid transparent;
|
|
262
|
-
border-top: 12px solid var(--tooltip-bg);
|
|
263
|
-
transform: translateX(-50%) translateY(-50%);
|
|
264
|
-
`;
|
|
351
|
+
// Bottom edge, pointing down
|
|
352
|
+
return `left: ${Math.round(arrowX)}px; top: 100%; ${centreX} border-left: ${transparent}; border-right: ${transparent}; border-top: ${filled};`;
|
|
265
353
|
|
|
266
354
|
case 'bottom':
|
|
267
|
-
//
|
|
268
|
-
return `
|
|
269
|
-
left: ${arrowX}px;
|
|
270
|
-
top: 0px;
|
|
271
|
-
border-left: 12px solid transparent;
|
|
272
|
-
border-right: 12px solid transparent;
|
|
273
|
-
border-bottom: 12px solid var(--tooltip-bg);
|
|
274
|
-
transform: translateX(-50%) translateY(-50%);
|
|
275
|
-
`;
|
|
355
|
+
// Top edge, pointing up
|
|
356
|
+
return `left: ${Math.round(arrowX)}px; bottom: 100%; ${centreX} border-left: ${transparent}; border-right: ${transparent}; border-bottom: ${filled};`;
|
|
276
357
|
|
|
277
358
|
case 'left':
|
|
278
|
-
//
|
|
279
|
-
return `
|
|
280
|
-
left: ${tooltipWidth}px;
|
|
281
|
-
top: ${arrowY}px;
|
|
282
|
-
border-top: 12px solid transparent;
|
|
283
|
-
border-bottom: 12px solid transparent;
|
|
284
|
-
border-left: 12px solid var(--tooltip-bg);
|
|
285
|
-
transform: translateX(-50%) translateY(-50%);
|
|
286
|
-
`;
|
|
359
|
+
// Right edge, pointing right at the trigger
|
|
360
|
+
return `top: ${Math.round(arrowY)}px; left: 100%; ${centreY} border-top: ${transparent}; border-bottom: ${transparent}; border-left: ${filled};`;
|
|
287
361
|
|
|
288
362
|
case 'right':
|
|
289
|
-
//
|
|
290
|
-
return `
|
|
291
|
-
left: 0px;
|
|
292
|
-
top: ${arrowY}px;
|
|
293
|
-
border-top: 12px solid transparent;
|
|
294
|
-
border-bottom: 12px solid transparent;
|
|
295
|
-
border-right: 12px solid var(--tooltip-bg);
|
|
296
|
-
transform: translateX(-50%) translateY(-50%);
|
|
297
|
-
`;
|
|
363
|
+
// Left edge, pointing left at the trigger
|
|
364
|
+
return `top: ${Math.round(arrowY)}px; right: 100%; ${centreY} border-top: ${transparent}; border-bottom: ${transparent}; border-right: ${filled};`;
|
|
298
365
|
|
|
299
366
|
default:
|
|
300
367
|
return '';
|
|
301
368
|
}
|
|
302
369
|
}
|
|
303
370
|
|
|
304
|
-
|
|
305
|
-
|
|
371
|
+
// Keep the tooltip anchored for as long as it is on screen. Runs before paint,
|
|
372
|
+
// so the first frame is already in the right place.
|
|
373
|
+
$effect(() => {
|
|
374
|
+
if (!visible || !tooltipElement || !referenceElement) return;
|
|
375
|
+
|
|
376
|
+
const tooltip = tooltipElement;
|
|
377
|
+
const reference = referenceElement;
|
|
378
|
+
|
|
379
|
+
// Resolved once per open rather than per reposition — walking the ancestor
|
|
380
|
+
// chain with getComputedStyle is too costly to repeat on every scroll frame.
|
|
381
|
+
fixedContainer = findFixedContainingBlock(tooltip);
|
|
382
|
+
|
|
383
|
+
// Reads location/offsetDistance/showArrow, so this effect re-runs when they
|
|
384
|
+
// change. maxWidth is covered by the observer below: it resizes the box.
|
|
385
|
+
positionTooltip();
|
|
386
|
+
|
|
387
|
+
// Content that grows, shrinks or reflows after the fact — async data, images,
|
|
388
|
+
// a font swap, a reactive snippet — invalidates the measurements above.
|
|
389
|
+
const observer = new ResizeObserver(() => positionTooltip());
|
|
390
|
+
observer.observe(tooltip);
|
|
391
|
+
observer.observe(reference);
|
|
392
|
+
// A centred container (a dialog) moves the trigger when it resizes without
|
|
393
|
+
// resizing the trigger itself, so neither observer above would notice.
|
|
394
|
+
if (fixedContainer) observer.observe(fixedContainer);
|
|
395
|
+
|
|
396
|
+
// Capture phase so scrolling any ancestor container counts, not just the page.
|
|
397
|
+
window.addEventListener('scroll', schedulePosition, true);
|
|
398
|
+
window.addEventListener('resize', schedulePosition);
|
|
399
|
+
|
|
400
|
+
return () => {
|
|
401
|
+
observer.disconnect();
|
|
402
|
+
window.removeEventListener('scroll', schedulePosition, true);
|
|
403
|
+
window.removeEventListener('resize', schedulePosition);
|
|
404
|
+
fixedContainer = null;
|
|
405
|
+
|
|
406
|
+
if (rafId !== null) {
|
|
407
|
+
cancelAnimationFrame(rafId);
|
|
408
|
+
rafId = null;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
$effect(() => {
|
|
414
|
+
if (!visible) return;
|
|
415
|
+
|
|
416
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
417
|
+
if (event.key === 'Escape') {
|
|
418
|
+
cancelPending();
|
|
419
|
+
hideTooltip();
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
window.addEventListener('keydown', onKeydown);
|
|
424
|
+
return () => window.removeEventListener('keydown', onKeydown);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Losing the content or being disabled mid-hover has to close the tooltip, not
|
|
428
|
+
// just hide it, or onOpen would be left reporting an open tooltip.
|
|
429
|
+
$effect(() => {
|
|
430
|
+
if (open && (disabled || !hasContent)) {
|
|
431
|
+
cancelPending();
|
|
306
432
|
hideTooltip();
|
|
307
433
|
}
|
|
308
|
-
}
|
|
434
|
+
});
|
|
309
435
|
|
|
436
|
+
// Drop a pending delay timer if the component is destroyed mid-hover.
|
|
310
437
|
$effect(() => {
|
|
311
|
-
|
|
312
|
-
window.addEventListener('keydown', handleKeydown);
|
|
313
|
-
window.addEventListener('scroll', positionTooltip, true);
|
|
314
|
-
window.addEventListener('resize', positionTooltip);
|
|
315
|
-
|
|
316
|
-
return () => {
|
|
317
|
-
window.removeEventListener('keydown', handleKeydown);
|
|
318
|
-
window.removeEventListener('scroll', positionTooltip, true);
|
|
319
|
-
window.removeEventListener('resize', positionTooltip);
|
|
320
|
-
};
|
|
321
|
-
}
|
|
438
|
+
return () => cancelPending();
|
|
322
439
|
});
|
|
323
440
|
</script>
|
|
324
441
|
|
|
325
442
|
<div class="tooltip-v2-container">
|
|
326
|
-
<!-- Reference Element
|
|
443
|
+
<!-- Reference Element: a passive wrapper — the interactive element (e.g. button) is
|
|
444
|
+
provided by children; focusin/focusout bubble up from it, unlike focus/blur -->
|
|
445
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
327
446
|
<div
|
|
328
447
|
bind:this={referenceElement}
|
|
329
448
|
class="tooltip-v2-trigger"
|
|
330
449
|
onmouseenter={handleMouseEnter}
|
|
331
450
|
onmouseleave={handleMouseLeave}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
role="button"
|
|
335
|
-
tabindex="0"
|
|
451
|
+
onfocusin={handleMouseEnter}
|
|
452
|
+
onfocusout={handleMouseLeave}
|
|
336
453
|
>
|
|
337
454
|
{@render children()}
|
|
338
455
|
</div>
|
|
339
456
|
|
|
340
457
|
<!-- Floating Tooltip -->
|
|
341
|
-
{#if
|
|
458
|
+
{#if visible}
|
|
342
459
|
<div
|
|
343
460
|
bind:this={tooltipElement}
|
|
344
461
|
class="tooltip-v2-floating"
|
|
345
|
-
|
|
462
|
+
class:is-positioned={positioned}
|
|
463
|
+
style="{tooltipStyle} max-width: {maxWidth};"
|
|
464
|
+
data-placement={actualPlacement}
|
|
346
465
|
role="tooltip"
|
|
347
466
|
transition:fade={{ duration: 150 }}
|
|
348
467
|
>
|
|
349
|
-
<div class="tooltip-v2-content"
|
|
468
|
+
<div class="tooltip-v2-content">
|
|
350
469
|
{#if text}
|
|
351
470
|
<span>{text}</span>
|
|
352
471
|
{:else if html}
|
|
@@ -357,7 +476,7 @@
|
|
|
357
476
|
</div>
|
|
358
477
|
|
|
359
478
|
{#if showArrow}
|
|
360
|
-
<div
|
|
479
|
+
<div class="tooltip-v2-arrow" style={arrowStyle}></div>
|
|
361
480
|
{/if}
|
|
362
481
|
</div>
|
|
363
482
|
{/if}
|
|
@@ -375,29 +494,42 @@
|
|
|
375
494
|
|
|
376
495
|
.tooltip-v2-floating {
|
|
377
496
|
position: fixed;
|
|
378
|
-
|
|
497
|
+
left: 0;
|
|
498
|
+
top: 0;
|
|
499
|
+
z-index: 9999;
|
|
379
500
|
pointer-events: none;
|
|
380
|
-
|
|
381
|
-
|
|
501
|
+
/* Size the box independently of where it sits. With auto width the available
|
|
502
|
+
space is viewportWidth - left, so measuring and then moving the tooltip
|
|
503
|
+
would change the size it was measured at and retrigger the observer. */
|
|
504
|
+
width: max-content;
|
|
505
|
+
/* Revealed by .is-positioned once measured, so it is never painted at a
|
|
506
|
+
stale position. */
|
|
507
|
+
visibility: hidden;
|
|
508
|
+
--tooltip-bg: #e0e0e0;
|
|
509
|
+
--tooltip-text: #333;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.tooltip-v2-floating.is-positioned {
|
|
513
|
+
visibility: visible;
|
|
382
514
|
}
|
|
383
515
|
|
|
384
516
|
:global(.dark) .tooltip-v2-floating {
|
|
385
|
-
--tooltip-bg:
|
|
386
|
-
--tooltip-text:
|
|
517
|
+
--tooltip-bg: rgb(51, 52, 57);
|
|
518
|
+
--tooltip-text: #e5e5e5;
|
|
387
519
|
}
|
|
388
520
|
|
|
389
521
|
.tooltip-v2-content {
|
|
390
522
|
background: var(--tooltip-bg);
|
|
391
523
|
color: var(--tooltip-text);
|
|
392
|
-
padding:
|
|
393
|
-
border-radius:
|
|
394
|
-
font-size:
|
|
395
|
-
line-height:
|
|
396
|
-
|
|
524
|
+
padding: 0.5rem 0.75rem;
|
|
525
|
+
border-radius: 6px;
|
|
526
|
+
font-size: 0.875rem;
|
|
527
|
+
line-height: 1.4;
|
|
528
|
+
overflow-wrap: break-word;
|
|
397
529
|
}
|
|
398
530
|
|
|
399
531
|
:global(.dark) .tooltip-v2-content {
|
|
400
|
-
box-shadow:
|
|
532
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
401
533
|
}
|
|
402
534
|
|
|
403
535
|
.tooltip-v2-arrow {
|
|
@@ -432,4 +564,4 @@
|
|
|
432
564
|
:global(.dark) .tooltip-v2-content :global(code) {
|
|
433
565
|
background: rgba(255, 255, 255, 0.05);
|
|
434
566
|
}
|
|
435
|
-
</style>
|
|
567
|
+
</style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
|
|
2
|
+
export type TooltipLocation = 'top' | 'right' | 'bottom' | 'left' | 'top-start' | 'top-end' | 'right-start' | 'right-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end';
|
|
3
|
+
export interface TooltipProps {
|
|
3
4
|
/** The element to attach the tooltip to */
|
|
4
5
|
children: Snippet;
|
|
5
6
|
/** Rich content using a Svelte snippet */
|
|
@@ -9,7 +10,7 @@ interface Props {
|
|
|
9
10
|
/** HTML content (use with caution - sanitize user input) */
|
|
10
11
|
html?: string;
|
|
11
12
|
/** Preferred tooltip position */
|
|
12
|
-
location?:
|
|
13
|
+
location?: TooltipLocation;
|
|
13
14
|
/** Maximum width of tooltip */
|
|
14
15
|
maxWidth?: string;
|
|
15
16
|
/** Offset distance from target element (px) */
|
|
@@ -23,6 +24,6 @@ interface Props {
|
|
|
23
24
|
/** Disable the tooltip */
|
|
24
25
|
disabled?: boolean;
|
|
25
26
|
}
|
|
26
|
-
declare const Tooltip: import("svelte").Component<
|
|
27
|
+
declare const Tooltip: import("svelte").Component<TooltipProps, {}, "">;
|
|
27
28
|
type Tooltip = ReturnType<typeof Tooltip>;
|
|
28
29
|
export default Tooltip;
|