@spaethtech/svelte-ui 0.1.10 → 0.3.1-dev.10.ff03a8d
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/.claude/skills/svelte-ui/SKILL.md +118 -0
- package/.claude/skills/themes/SKILL.md +303 -0
- package/CLAUDE.md +248 -0
- package/README.md +180 -42
- package/dist/components/Alert.svelte +119 -0
- package/dist/components/Alert.svelte.d.ts +29 -0
- package/dist/components/Badge/Badge.svelte +142 -69
- package/dist/components/Badge/Badge.svelte.d.ts +26 -6
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Banner.svelte +133 -0
- package/dist/components/Banner.svelte.d.ts +31 -0
- package/dist/components/Button.svelte +240 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/ButtonDropdown.svelte +145 -0
- package/dist/components/ButtonDropdown.svelte.d.ts +31 -0
- package/dist/components/Calendar.svelte +259 -0
- package/dist/components/Calendar.svelte.d.ts +27 -0
- package/dist/components/Card.svelte +79 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CardBody.svelte +41 -0
- package/dist/components/CardBody.svelte.d.ts +17 -0
- package/dist/components/CardFooter.svelte +51 -0
- package/dist/components/CardFooter.svelte.d.ts +16 -0
- package/dist/components/CardHeader.svelte +51 -0
- package/dist/components/CardHeader.svelte.d.ts +16 -0
- package/dist/components/Checkbox.svelte +131 -0
- package/dist/components/Checkbox.svelte.d.ts +16 -0
- package/dist/components/ConfirmDialog.svelte +103 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +33 -0
- package/dist/components/DataTable.svelte +518 -0
- package/dist/components/DataTable.svelte.d.ts +74 -0
- package/dist/components/DatePicker.svelte +312 -0
- package/dist/components/DatePicker.svelte.d.ts +32 -0
- package/dist/components/DateTimeInput.svelte +93 -0
- package/dist/components/DateTimeInput.svelte.d.ts +20 -0
- package/dist/components/Dialog.svelte +151 -0
- package/dist/components/Dialog.svelte.d.ts +30 -0
- package/dist/components/{EmailInput/EmailInput.svelte → EmailInput.svelte} +17 -8
- package/dist/components/EmailInput.svelte.d.ts +21 -0
- package/dist/components/Input.svelte +369 -0
- package/dist/components/Input.svelte.d.ts +35 -0
- package/dist/components/{List/List.svelte → List.svelte} +194 -133
- package/dist/components/{List/List.svelte.d.ts → List.svelte.d.ts} +9 -1
- package/dist/components/Menu.svelte +117 -0
- package/dist/components/Menu.svelte.d.ts +20 -0
- package/dist/components/NotesEditor.svelte +127 -0
- package/dist/components/NotesEditor.svelte.d.ts +17 -0
- package/dist/components/{NumberInput/NumberInput.svelte → NumberInput.svelte} +146 -105
- package/dist/components/{NumberInput/NumberInput.svelte.d.ts → NumberInput.svelte.d.ts} +11 -4
- package/dist/components/PasswordInput.svelte +52 -0
- package/dist/components/PasswordInput.svelte.d.ts +20 -0
- package/dist/components/Popup.svelte +140 -0
- package/dist/components/Popup.svelte.d.ts +31 -0
- package/dist/components/Query.svelte +284 -0
- package/dist/components/Query.svelte.d.ts +15 -0
- package/dist/components/{Rating/Rating.svelte → Rating.svelte} +19 -10
- package/dist/components/Rating.svelte.d.ts +13 -0
- package/dist/components/{SearchInput/SearchInput.svelte → SearchInput.svelte} +19 -8
- package/dist/components/{SearchInput/SearchInput.svelte.d.ts → SearchInput.svelte.d.ts} +9 -2
- package/dist/components/{Select/Select.svelte → Select.svelte} +48 -76
- package/dist/components/{Select/Select.svelte.d.ts → Select.svelte.d.ts} +11 -1
- package/dist/components/SideBarMenu/SideBarMenu.svelte +724 -0
- package/dist/components/SideBarMenu/SideBarMenu.svelte.d.ts +109 -0
- package/dist/components/SideBarMenu/index.d.ts +2 -0
- package/dist/components/SideBarMenu/index.js +1 -0
- package/dist/components/TabStrip/TabStrip.svelte +384 -0
- package/dist/components/TabStrip/TabStrip.svelte.d.ts +50 -0
- package/dist/components/TabStrip/index.d.ts +3 -0
- package/dist/components/TabStrip/index.js +2 -0
- package/dist/components/{TextArea/TextArea.svelte → TextArea.svelte} +143 -112
- package/dist/components/TextArea.svelte.d.ts +38 -0
- package/dist/components/ThemeSelector.svelte +81 -0
- package/dist/components/ThemeSelector.svelte.d.ts +10 -0
- package/dist/components/TimePicker.svelte +148 -0
- package/dist/components/TimePicker.svelte.d.ts +28 -0
- package/dist/components/TimeRangeInput.svelte +203 -0
- package/dist/components/TimeRangeInput.svelte.d.ts +29 -0
- package/dist/components/TimeSpinner.svelte +202 -0
- package/dist/components/TimeSpinner.svelte.d.ts +26 -0
- package/dist/components/Toast/Toaster.svelte +51 -0
- package/dist/components/Toast/Toaster.svelte.d.ts +12 -0
- package/dist/components/Toast/toast.svelte.d.ts +29 -0
- package/dist/components/Toast/toast.svelte.js +27 -0
- package/dist/components/Toggle.svelte +93 -0
- package/dist/components/Toggle.svelte.d.ts +15 -0
- package/dist/data/dataset.d.ts +42 -0
- package/dist/data/dataset.js +3 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.js +3 -0
- package/dist/data/query/ast.d.ts +62 -0
- package/dist/data/query/ast.js +12 -0
- package/dist/data/query/index.d.ts +3 -0
- package/dist/data/query/index.js +3 -0
- package/dist/data/query/lexer.d.ts +33 -0
- package/dist/data/query/lexer.js +225 -0
- package/dist/data/query/parser.d.ts +11 -0
- package/dist/data/query/parser.js +252 -0
- package/dist/data/table/grid.svelte.d.ts +57 -0
- package/dist/data/table/grid.svelte.js +139 -0
- package/dist/data/table/index.d.ts +2 -0
- package/dist/data/table/index.js +2 -0
- package/dist/data/table/types.d.ts +79 -0
- package/dist/index.d.ts +49 -22
- package/dist/index.js +46 -21
- package/dist/positioning/anchored.d.ts +61 -0
- package/dist/positioning/anchored.js +122 -0
- package/dist/positioning/tooltip.d.ts +41 -0
- package/dist/positioning/tooltip.js +147 -0
- package/dist/theme.css +205 -0
- package/dist/types/breakpoints.d.ts +24 -0
- package/dist/types/breakpoints.js +13 -0
- package/dist/types/responsive.d.ts +32 -0
- package/dist/types/responsive.js +54 -0
- package/dist/types/sizes.d.ts +10 -3
- package/dist/types/time.d.ts +19 -0
- package/dist/types/time.js +10 -0
- package/dist/types/variants.d.ts +8 -1
- package/dist/types/variants.js +16 -1
- package/docs/components.md +255 -0
- package/docs/examples.md +323 -0
- package/docs/paradigm.md +240 -0
- package/docs/themes.md +170 -0
- package/docs/usage.md +450 -0
- package/package.json +97 -63
- package/dist/components/Button/Button.svelte +0 -172
- package/dist/components/Button/Button.svelte.d.ts +0 -32
- package/dist/components/Button/index.d.ts +0 -1
- package/dist/components/Button/index.js +0 -1
- package/dist/components/Dialog/Dialog.svelte +0 -101
- package/dist/components/Dialog/Dialog.svelte.d.ts +0 -18
- package/dist/components/Dialog/index.d.ts +0 -1
- package/dist/components/Dialog/index.js +0 -1
- package/dist/components/ElementManager/ElementManager.svelte +0 -397
- package/dist/components/ElementManager/ElementManager.svelte.d.ts +0 -43
- package/dist/components/ElementManager/index.d.ts +0 -1
- package/dist/components/ElementManager/index.js +0 -1
- package/dist/components/EmailInput/EmailInput.svelte.d.ts +0 -14
- package/dist/components/EmailInput/index.d.ts +0 -1
- package/dist/components/EmailInput/index.js +0 -1
- package/dist/components/Icon/Icon.svelte +0 -74
- package/dist/components/Icon/Icon.svelte.d.ts +0 -13
- package/dist/components/Icon/index.d.ts +0 -1
- package/dist/components/Icon/index.js +0 -1
- package/dist/components/Input/Input.svelte +0 -268
- package/dist/components/Input/Input.svelte.d.ts +0 -18
- package/dist/components/Input/index.d.ts +0 -1
- package/dist/components/Input/index.js +0 -1
- package/dist/components/List/index.d.ts +0 -1
- package/dist/components/List/index.js +0 -1
- package/dist/components/ListItem/ListItem.svelte +0 -175
- package/dist/components/ListItem/ListItem.svelte.d.ts +0 -24
- package/dist/components/ListItem/index.d.ts +0 -2
- package/dist/components/ListItem/index.js +0 -2
- package/dist/components/ListView/ListView.svelte +0 -463
- package/dist/components/ListView/ListView.svelte.d.ts +0 -37
- package/dist/components/ListView/index.d.ts +0 -2
- package/dist/components/ListView/index.js +0 -2
- package/dist/components/NodeGraph/BaseNode.d.ts +0 -57
- package/dist/components/NodeGraph/BaseNode.js +0 -30
- package/dist/components/NodeGraph/Edge.svelte +0 -60
- package/dist/components/NodeGraph/Edge.svelte.d.ts +0 -16
- package/dist/components/NodeGraph/ExpressionEvaluator.d.ts +0 -82
- package/dist/components/NodeGraph/ExpressionEvaluator.js +0 -152
- package/dist/components/NodeGraph/Node.svelte +0 -100
- package/dist/components/NodeGraph/Node.svelte.d.ts +0 -10
- package/dist/components/NodeGraph/NodeGraph.svelte +0 -52
- package/dist/components/NodeGraph/NodeGraph.svelte.d.ts +0 -14
- package/dist/components/NodeGraph/NodeInstance.svelte.d.ts +0 -80
- package/dist/components/NodeGraph/NodeInstance.svelte.js +0 -241
- package/dist/components/NodeGraph/NodePort.svelte +0 -75
- package/dist/components/NodeGraph/NodePort.svelte.d.ts +0 -11
- package/dist/components/NodeGraph/decorators.d.ts +0 -81
- package/dist/components/NodeGraph/decorators.js +0 -148
- package/dist/components/NodeGraph/index.d.ts +0 -9
- package/dist/components/NodeGraph/index.js +0 -9
- package/dist/components/NodeGraph/types.d.ts +0 -94
- package/dist/components/NodeGraph/types.js +0 -33
- package/dist/components/NotesEditor/NotesEditor.svelte +0 -203
- package/dist/components/NotesEditor/NotesEditor.svelte.d.ts +0 -9
- package/dist/components/NotesEditor/index.d.ts +0 -1
- package/dist/components/NotesEditor/index.js +0 -1
- package/dist/components/NumberInput/index.d.ts +0 -1
- package/dist/components/NumberInput/index.js +0 -1
- package/dist/components/PasswordInput/PasswordInput.svelte +0 -41
- package/dist/components/PasswordInput/PasswordInput.svelte.d.ts +0 -13
- package/dist/components/PasswordInput/index.d.ts +0 -1
- package/dist/components/PasswordInput/index.js +0 -1
- package/dist/components/Popup/index.d.ts +0 -1
- package/dist/components/Popup/index.js +0 -1
- package/dist/components/Rating/Rating.svelte.d.ts +0 -7
- package/dist/components/Rating/index.d.ts +0 -1
- package/dist/components/Rating/index.js +0 -1
- package/dist/components/ScrollView/ScrollView.svelte +0 -285
- package/dist/components/ScrollView/ScrollView.svelte.d.ts +0 -19
- package/dist/components/ScrollView/index.d.ts +0 -1
- package/dist/components/ScrollView/index.js +0 -1
- package/dist/components/SearchInput/index.d.ts +0 -1
- package/dist/components/SearchInput/index.js +0 -1
- package/dist/components/Select/index.d.ts +0 -1
- package/dist/components/Select/index.js +0 -1
- package/dist/components/TextArea/TextArea.svelte.d.ts +0 -19
- package/dist/components/TextArea/index.d.ts +0 -1
- package/dist/components/TextArea/index.js +0 -1
- package/dist/components/ThemeSelector/ThemeSelector.svelte +0 -64
- package/dist/components/ThemeSelector/ThemeSelector.svelte.d.ts +0 -3
- package/dist/components/ThemeSelector/index.d.ts +0 -1
- package/dist/components/ThemeSelector/index.js +0 -1
- package/dist/components/TooltipHandler/TooltipHandler.svelte +0 -593
- package/dist/components/TooltipHandler/TooltipHandler.svelte.d.ts +0 -10
- package/dist/components/TooltipHandler/index.d.ts +0 -1
- package/dist/components/TooltipHandler/index.js +0 -1
- package/dist/styles/sizes.d.ts +0 -78
- package/dist/styles/sizes.js +0 -120
- package/dist/styles/variants.d.ts +0 -106
- package/dist/styles/variants.js +0 -194
- package/dist/types/ManagerTypes.d.ts +0 -42
- /package/dist/{types/ManagerTypes.js → data/table/types.js} +0 -0
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import Select from '../Select/Select.svelte';
|
|
3
|
-
|
|
4
|
-
const options = [
|
|
5
|
-
{ label: 'Auto (System)', value: 'auto' },
|
|
6
|
-
{ label: 'Light', value: 'light' },
|
|
7
|
-
{ label: 'Dark', value: 'dark' }
|
|
8
|
-
];
|
|
9
|
-
|
|
10
|
-
const themeClasses = {
|
|
11
|
-
'light': 'theme-light',
|
|
12
|
-
'dark': 'theme-dark'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
let selectedTheme = $state('auto');
|
|
16
|
-
|
|
17
|
-
// Load saved theme on mount
|
|
18
|
-
$effect(() => {
|
|
19
|
-
if (typeof window !== 'undefined') {
|
|
20
|
-
const savedTheme = localStorage.getItem('svelte-ui-theme');
|
|
21
|
-
if (savedTheme && options.some(opt => opt.value === savedTheme)) {
|
|
22
|
-
selectedTheme = savedTheme;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Apply theme to document body
|
|
28
|
-
function applyTheme(themeValue: string) {
|
|
29
|
-
const body = document.body;
|
|
30
|
-
const html = document.documentElement;
|
|
31
|
-
|
|
32
|
-
// Remove all existing theme classes from both body and html
|
|
33
|
-
Object.values(themeClasses).forEach(className => {
|
|
34
|
-
body.classList.remove(className);
|
|
35
|
-
html.classList.remove(className);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Add the selected theme class to body
|
|
39
|
-
const themeClass = themeClasses[themeValue as keyof typeof themeClasses];
|
|
40
|
-
if (themeClass) {
|
|
41
|
-
body.classList.add(themeClass);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Save theme to localStorage
|
|
45
|
-
if (typeof window !== 'undefined') {
|
|
46
|
-
localStorage.setItem('svelte-ui-theme', themeValue);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Apply theme when selection changes
|
|
51
|
-
$effect(() => {
|
|
52
|
-
applyTheme(selectedTheme);
|
|
53
|
-
});
|
|
54
|
-
</script>
|
|
55
|
-
|
|
56
|
-
<div class="flex items-center gap-2">
|
|
57
|
-
<label for="theme-selector" class="text-sm font-medium text-[var(--color-text)]">Theme:</label>
|
|
58
|
-
<Select
|
|
59
|
-
bind:value={selectedTheme}
|
|
60
|
-
{options}
|
|
61
|
-
placeholder="Select theme..."
|
|
62
|
-
class="w-40"
|
|
63
|
-
/>
|
|
64
|
-
</div>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './ThemeSelector.svelte';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './ThemeSelector.svelte';
|
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
/**
|
|
3
|
-
* TooltipHandler - Automatic tooltip handling using elements from ElementManager
|
|
4
|
-
*
|
|
5
|
-
* Receives elements from an external ElementManager and creates tooltips for any element
|
|
6
|
-
* with data-tooltip attributes. Supports both text content and custom snippets.
|
|
7
|
-
*
|
|
8
|
-
* @example Basic Usage
|
|
9
|
-
* ```svelte
|
|
10
|
-
* <script>
|
|
11
|
-
* import { ElementManager, TooltipHandler } from 'svelte-ui';
|
|
12
|
-
* let tooltipElements = $state(new Map());
|
|
13
|
-
* </script>
|
|
14
|
-
*
|
|
15
|
-
* <ElementManager
|
|
16
|
-
* selector="[data-tooltip], [data-tooltip-text]"
|
|
17
|
-
* bind:elements={tooltipElements}
|
|
18
|
-
* >
|
|
19
|
-
* <TooltipHandler elements={tooltipElements}>
|
|
20
|
-
* <button data-tooltip-text="Hello World!">Hover me</button>
|
|
21
|
-
* </TooltipHandler>
|
|
22
|
-
* </ElementManager>
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* @example With Custom Snippets
|
|
26
|
-
* ```svelte
|
|
27
|
-
* <ElementManager
|
|
28
|
-
* selector="[data-tooltip], [data-tooltip-text]"
|
|
29
|
-
* bind:elements={tooltipElements}
|
|
30
|
-
* >
|
|
31
|
-
* <TooltipHandler
|
|
32
|
-
* elements={tooltipElements}
|
|
33
|
-
* warning={warningSnippet}
|
|
34
|
-
* info={infoSnippet}
|
|
35
|
-
* >
|
|
36
|
-
* {#snippet warningSnippet()}
|
|
37
|
-
* <div class="text-center">
|
|
38
|
-
* <div class="font-semibold text-yellow-300">⚠️ Warning</div>
|
|
39
|
-
* <div class="text-xs mt-1">Action cannot be undone!</div>
|
|
40
|
-
* </div>
|
|
41
|
-
* {/snippet}
|
|
42
|
-
*
|
|
43
|
-
* <button data-tooltip="warning">Delete</button>
|
|
44
|
-
* <button data-tooltip-text="Simple text">Save</button>
|
|
45
|
-
* </TooltipHandler>
|
|
46
|
-
* </ElementManager>
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
-->
|
|
50
|
-
<script lang="ts">
|
|
51
|
-
import { tick } from 'svelte';
|
|
52
|
-
import type { Snippet } from 'svelte';
|
|
53
|
-
import type { HandlerComponentProps } from '../../types/ManagerTypes.js';
|
|
54
|
-
|
|
55
|
-
interface Props extends HandlerComponentProps {
|
|
56
|
-
/** Container for tooltip positioning bounds. Can be HTMLElement or CSS selector. Default: viewport */
|
|
57
|
-
container?: HTMLElement | string;
|
|
58
|
-
/** Named snippets for tooltip content */
|
|
59
|
-
[key: string]: any;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let { elements = new Map(), children, container, ...snippets }: Props = $props();
|
|
63
|
-
|
|
64
|
-
// Set CSS custom property for tooltip max-width when in browser
|
|
65
|
-
$effect(() => {
|
|
66
|
-
if (typeof window !== 'undefined') {
|
|
67
|
-
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
68
|
-
const maxWidth = window.innerWidth - scrollbarWidth - 16; // viewport width - margins
|
|
69
|
-
document.documentElement.style.setProperty('--tooltip-max-width', `${maxWidth}px`);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Filter out non-snippet props to get only actual snippets
|
|
74
|
-
const snippetProps = $derived(Object.fromEntries(
|
|
75
|
-
Object.entries(snippets).filter(([_, value]) =>
|
|
76
|
-
typeof value === 'function'
|
|
77
|
-
)
|
|
78
|
-
));
|
|
79
|
-
|
|
80
|
-
interface TooltipState {
|
|
81
|
-
element: HTMLElement;
|
|
82
|
-
text?: string;
|
|
83
|
-
snippet?: Snippet;
|
|
84
|
-
side: 'top' | 'left' | 'right' | 'bottom';
|
|
85
|
-
wait: number;
|
|
86
|
-
isVisible: boolean;
|
|
87
|
-
tooltipElement?: HTMLDivElement;
|
|
88
|
-
hoverTimeout?: ReturnType<typeof setTimeout>;
|
|
89
|
-
tooltipStyle: string;
|
|
90
|
-
arrowStyle: string;
|
|
91
|
-
effectiveSide: 'top' | 'left' | 'right' | 'bottom';
|
|
92
|
-
showArrow: boolean;
|
|
93
|
-
originalTitle?: string;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let tooltipStates = $state<TooltipState[]>([]);
|
|
97
|
-
|
|
98
|
-
function updatePosition(state: TooltipState) {
|
|
99
|
-
if (!state.tooltipElement || !state.element) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const targetRect = state.element.getBoundingClientRect();
|
|
104
|
-
|
|
105
|
-
// Get tooltip dimensions - ensure they're fully calculated
|
|
106
|
-
const tooltipRect = state.tooltipElement.getBoundingClientRect();
|
|
107
|
-
const actualWidth = tooltipRect.width;
|
|
108
|
-
const actualHeight = tooltipRect.height;
|
|
109
|
-
|
|
110
|
-
// If dimensions are not yet available, wait for next frame
|
|
111
|
-
if (actualWidth === 0 || actualHeight === 0) {
|
|
112
|
-
requestAnimationFrame(() => updatePosition(state));
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Determine boundary constraints
|
|
118
|
-
let boundaryRect: DOMRect;
|
|
119
|
-
let viewportWidth: number;
|
|
120
|
-
let viewportHeight: number;
|
|
121
|
-
|
|
122
|
-
if (container) {
|
|
123
|
-
// Resolve container from prop (HTMLElement or CSS selector)
|
|
124
|
-
let containerElement: HTMLElement;
|
|
125
|
-
|
|
126
|
-
if (typeof container === 'string') {
|
|
127
|
-
// Query selector
|
|
128
|
-
const found = document.querySelector(container);
|
|
129
|
-
if (found instanceof HTMLElement) {
|
|
130
|
-
containerElement = found;
|
|
131
|
-
} else {
|
|
132
|
-
containerElement = document.body;
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
// Direct element reference
|
|
136
|
-
containerElement = container;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
boundaryRect = containerElement.getBoundingClientRect();
|
|
140
|
-
viewportWidth = boundaryRect.width;
|
|
141
|
-
viewportHeight = boundaryRect.height;
|
|
142
|
-
} else {
|
|
143
|
-
// Use viewport bounds (default) - account for scrollbars
|
|
144
|
-
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
145
|
-
const scrollbarHeight = window.innerHeight - document.documentElement.clientHeight;
|
|
146
|
-
|
|
147
|
-
boundaryRect = new DOMRect(0, 0,
|
|
148
|
-
window.innerWidth - scrollbarWidth,
|
|
149
|
-
window.innerHeight - scrollbarHeight
|
|
150
|
-
);
|
|
151
|
-
viewportWidth = window.innerWidth - scrollbarWidth;
|
|
152
|
-
viewportHeight = window.innerHeight - scrollbarHeight;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const offset = 16; // 1rem offset
|
|
156
|
-
const margin = 8; // minimum margin from boundary edges
|
|
157
|
-
|
|
158
|
-
// Helper function to check if a side has enough room
|
|
159
|
-
function hasRoomForSide(side: 'top' | 'right' | 'bottom' | 'left'): boolean {
|
|
160
|
-
switch (side) {
|
|
161
|
-
case 'top':
|
|
162
|
-
return targetRect.top - actualHeight - offset >= boundaryRect.top + margin;
|
|
163
|
-
case 'bottom':
|
|
164
|
-
return targetRect.bottom + actualHeight + offset <= boundaryRect.bottom - margin;
|
|
165
|
-
case 'left':
|
|
166
|
-
return targetRect.left - actualWidth - offset >= boundaryRect.left + margin;
|
|
167
|
-
case 'right':
|
|
168
|
-
return targetRect.right + actualWidth + offset <= boundaryRect.right - margin;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Helper function to get opposite side
|
|
173
|
-
function getOppositeSide(side: 'top' | 'right' | 'bottom' | 'left'): 'top' | 'right' | 'bottom' | 'left' {
|
|
174
|
-
const opposites = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' } as const;
|
|
175
|
-
return opposites[side];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Smart positioning logic
|
|
179
|
-
let useSide = state.side;
|
|
180
|
-
let showArrow = true;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// 1. Check if requested side has room
|
|
184
|
-
if (!hasRoomForSide(state.side)) {
|
|
185
|
-
// 2. Try opposite side first
|
|
186
|
-
const oppositeSide = getOppositeSide(state.side);
|
|
187
|
-
if (hasRoomForSide(oppositeSide)) {
|
|
188
|
-
useSide = oppositeSide;
|
|
189
|
-
} else {
|
|
190
|
-
// 3. Try all sides in order: top, right, bottom, left
|
|
191
|
-
const fallbackOrder: Array<'top' | 'right' | 'bottom' | 'left'> = ['top', 'right', 'bottom', 'left'];
|
|
192
|
-
let foundSide = false;
|
|
193
|
-
|
|
194
|
-
for (const side of fallbackOrder) {
|
|
195
|
-
if (hasRoomForSide(side)) {
|
|
196
|
-
useSide = side;
|
|
197
|
-
foundSide = true;
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 4. If no side fits, center the tooltip and hide arrow
|
|
203
|
-
if (!foundSide) {
|
|
204
|
-
useSide = 'top'; // fallback, but we'll position it centered
|
|
205
|
-
showArrow = false;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
state.effectiveSide = useSide;
|
|
211
|
-
|
|
212
|
-
let x = 0;
|
|
213
|
-
let y = 0;
|
|
214
|
-
|
|
215
|
-
if (!showArrow) {
|
|
216
|
-
// Center the tooltip on the target element
|
|
217
|
-
x = targetRect.left + targetRect.width / 2 - actualWidth / 2;
|
|
218
|
-
y = targetRect.top + targetRect.height / 2 - actualHeight / 2;
|
|
219
|
-
|
|
220
|
-
// Ensure it stays within boundary bounds
|
|
221
|
-
if (x < boundaryRect.left + margin) x = boundaryRect.left + margin;
|
|
222
|
-
if (x + actualWidth > boundaryRect.right - margin) x = boundaryRect.right - actualWidth - margin;
|
|
223
|
-
if (y < boundaryRect.top + margin) y = boundaryRect.top + margin;
|
|
224
|
-
if (y + actualHeight > boundaryRect.bottom - margin) y = boundaryRect.bottom - actualHeight - margin;
|
|
225
|
-
} else {
|
|
226
|
-
// Position according to the chosen side
|
|
227
|
-
switch (useSide) {
|
|
228
|
-
case 'top':
|
|
229
|
-
x = targetRect.left + targetRect.width / 2 - actualWidth / 2;
|
|
230
|
-
y = targetRect.top - actualHeight - offset;
|
|
231
|
-
break;
|
|
232
|
-
|
|
233
|
-
case 'bottom':
|
|
234
|
-
x = targetRect.left + targetRect.width / 2 - actualWidth / 2;
|
|
235
|
-
y = targetRect.bottom + offset;
|
|
236
|
-
break;
|
|
237
|
-
|
|
238
|
-
case 'left':
|
|
239
|
-
x = targetRect.left - actualWidth - offset;
|
|
240
|
-
y = targetRect.top + targetRect.height / 2 - actualHeight / 2;
|
|
241
|
-
break;
|
|
242
|
-
|
|
243
|
-
case 'right':
|
|
244
|
-
x = targetRect.right + offset;
|
|
245
|
-
y = targetRect.top + targetRect.height / 2 - actualHeight / 2;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Store original calculated position for triangle adjustment
|
|
250
|
-
const originalX = x;
|
|
251
|
-
const originalY = y;
|
|
252
|
-
|
|
253
|
-
// Boundary collision detection with special handling for oversized tooltips
|
|
254
|
-
const availableWidth = boundaryRect.width - (2 * margin);
|
|
255
|
-
const availableHeight = boundaryRect.height - (2 * margin);
|
|
256
|
-
|
|
257
|
-
// Handle horizontal positioning
|
|
258
|
-
if (actualWidth > availableWidth) {
|
|
259
|
-
// Tooltip wider than available space - center it within bounds
|
|
260
|
-
x = boundaryRect.left + boundaryRect.width / 2 - actualWidth / 2;
|
|
261
|
-
} else {
|
|
262
|
-
// Normal collision detection for width
|
|
263
|
-
if (x < boundaryRect.left + margin) {
|
|
264
|
-
x = boundaryRect.left + margin;
|
|
265
|
-
} else if (x + actualWidth > boundaryRect.right - margin) {
|
|
266
|
-
x = boundaryRect.right - actualWidth - margin;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Handle vertical positioning
|
|
271
|
-
if (actualHeight > availableHeight) {
|
|
272
|
-
// Tooltip taller than available space - center it within margin bounds
|
|
273
|
-
y = boundaryRect.top + margin + availableHeight / 2 - actualHeight / 2;
|
|
274
|
-
} else {
|
|
275
|
-
// Normal collision detection for height
|
|
276
|
-
if (y < boundaryRect.top + margin) {
|
|
277
|
-
y = boundaryRect.top + margin;
|
|
278
|
-
} else if (y + actualHeight > boundaryRect.bottom - margin) {
|
|
279
|
-
y = boundaryRect.bottom - actualHeight - margin;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Calculate arrow offset based on position adjustments
|
|
284
|
-
let arrowOffsetX = 0;
|
|
285
|
-
let arrowOffsetY = 0;
|
|
286
|
-
|
|
287
|
-
if (useSide === 'top' || useSide === 'bottom') {
|
|
288
|
-
// For top/bottom tooltips, adjust triangle horizontal position
|
|
289
|
-
arrowOffsetX = originalX - x;
|
|
290
|
-
} else if (useSide === 'left' || useSide === 'right') {
|
|
291
|
-
// For left/right tooltips, adjust triangle vertical position
|
|
292
|
-
arrowOffsetY = originalY - y;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Update arrow position if needed
|
|
296
|
-
if (arrowOffsetX !== 0 || arrowOffsetY !== 0) {
|
|
297
|
-
if (useSide === 'top' || useSide === 'bottom') {
|
|
298
|
-
state.arrowStyle = `transform: translateX(calc(-50% + ${arrowOffsetX}px));`;
|
|
299
|
-
} else {
|
|
300
|
-
state.arrowStyle = `transform: translateY(calc(-50% + ${arrowOffsetY}px));`;
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
state.arrowStyle = '';
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Apply tooltip position and arrow visibility
|
|
308
|
-
state.tooltipStyle = `left: ${x}px; top: ${y}px;`;
|
|
309
|
-
state.showArrow = showArrow;
|
|
310
|
-
|
|
311
|
-
// Trigger reactivity
|
|
312
|
-
tooltipStates = tooltipStates;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function handleMouseEnter(state: TooltipState) {
|
|
316
|
-
if (state.hoverTimeout) clearTimeout(state.hoverTimeout);
|
|
317
|
-
|
|
318
|
-
state.hoverTimeout = setTimeout(() => {
|
|
319
|
-
// Force reactivity by creating a new array with new objects
|
|
320
|
-
tooltipStates = tooltipStates.map(s => {
|
|
321
|
-
if (s.element === state.element) {
|
|
322
|
-
return { ...s, isVisible: true };
|
|
323
|
-
}
|
|
324
|
-
return s;
|
|
325
|
-
});
|
|
326
|
-
// Update position after making visible - use multiple timing strategies
|
|
327
|
-
tick().then(() => {
|
|
328
|
-
const currentState = tooltipStates.find(s => s.element === state.element);
|
|
329
|
-
if (currentState) {
|
|
330
|
-
// Try positioning immediately
|
|
331
|
-
updatePosition(currentState);
|
|
332
|
-
// Also try again after a short delay to handle timing issues
|
|
333
|
-
setTimeout(() => {
|
|
334
|
-
updatePosition(currentState);
|
|
335
|
-
}, 1);
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}, state.wait);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function handleMouseLeave(state: TooltipState) {
|
|
342
|
-
if (state.hoverTimeout) clearTimeout(state.hoverTimeout);
|
|
343
|
-
// Force reactivity by creating a new array with new objects
|
|
344
|
-
tooltipStates = tooltipStates.map(s => {
|
|
345
|
-
if (s.element === state.element) {
|
|
346
|
-
return { ...s, isVisible: false };
|
|
347
|
-
}
|
|
348
|
-
return s;
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function handleFocus(state: TooltipState) {
|
|
353
|
-
tooltipStates = tooltipStates.map(s => {
|
|
354
|
-
if (s.element === state.element) {
|
|
355
|
-
return { ...s, isVisible: true };
|
|
356
|
-
}
|
|
357
|
-
return s;
|
|
358
|
-
});
|
|
359
|
-
tick().then(() => {
|
|
360
|
-
const currentState = tooltipStates.find(s => s.element === state.element);
|
|
361
|
-
if (currentState) {
|
|
362
|
-
updatePosition(currentState);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function handleBlur(state: TooltipState) {
|
|
368
|
-
tooltipStates = tooltipStates.map(s => {
|
|
369
|
-
if (s.element === state.element) {
|
|
370
|
-
return { ...s, isVisible: false };
|
|
371
|
-
}
|
|
372
|
-
return s;
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function handleScroll() {
|
|
377
|
-
tooltipStates.forEach((state) => {
|
|
378
|
-
if (state.isVisible && state.tooltipElement) {
|
|
379
|
-
updatePosition(state);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function setupTooltip(element: HTMLElement, data: any) {
|
|
385
|
-
// Skip if already set up
|
|
386
|
-
if (data.tooltipState) {
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Determine tooltip content (text or snippet)
|
|
391
|
-
let text: string | undefined;
|
|
392
|
-
let snippet: Snippet | undefined;
|
|
393
|
-
|
|
394
|
-
// Check for data-tooltip-text first
|
|
395
|
-
if (element.dataset.tooltipText) {
|
|
396
|
-
text = element.dataset.tooltipText;
|
|
397
|
-
}
|
|
398
|
-
// Check for data-tooltip (could be text or snippet name)
|
|
399
|
-
else if (element.dataset.tooltip !== undefined) {
|
|
400
|
-
const value = element.dataset.tooltip;
|
|
401
|
-
if (!value) {
|
|
402
|
-
// No value - use title attribute
|
|
403
|
-
text = element.getAttribute('title') || '';
|
|
404
|
-
} else if (snippetProps[value]) {
|
|
405
|
-
// Value is a snippet name
|
|
406
|
-
snippet = snippetProps[value];
|
|
407
|
-
} else {
|
|
408
|
-
// Value is plain text
|
|
409
|
-
text = value;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Skip if no content
|
|
414
|
-
if (!text && !snippet) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const state: TooltipState = {
|
|
419
|
-
element,
|
|
420
|
-
text,
|
|
421
|
-
snippet,
|
|
422
|
-
side: (element.dataset.tooltipSide as any) || 'top',
|
|
423
|
-
wait: element.dataset.tooltipWait ? parseInt(element.dataset.tooltipWait) : 500,
|
|
424
|
-
isVisible: false,
|
|
425
|
-
tooltipStyle: 'left: 0px; top: 0px;', // Start with basic positioning
|
|
426
|
-
arrowStyle: '',
|
|
427
|
-
effectiveSide: (element.dataset.tooltipSide as any) || 'top',
|
|
428
|
-
showArrow: true // Will be updated during positioning
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Store and remove original title to prevent native tooltip
|
|
432
|
-
const originalTitle = element.getAttribute('title');
|
|
433
|
-
if (originalTitle) {
|
|
434
|
-
state.originalTitle = originalTitle;
|
|
435
|
-
element.removeAttribute('title');
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Create event handlers
|
|
439
|
-
const enterHandler = () => handleMouseEnter(state);
|
|
440
|
-
const leaveHandler = () => handleMouseLeave(state);
|
|
441
|
-
const focusHandler = () => handleFocus(state);
|
|
442
|
-
const blurHandler = () => handleBlur(state);
|
|
443
|
-
|
|
444
|
-
element.addEventListener('mouseenter', enterHandler);
|
|
445
|
-
element.addEventListener('mouseleave', leaveHandler);
|
|
446
|
-
element.addEventListener('focus', focusHandler);
|
|
447
|
-
element.addEventListener('blur', blurHandler);
|
|
448
|
-
|
|
449
|
-
// Store cleanup function on the data object
|
|
450
|
-
data.cleanup = () => {
|
|
451
|
-
element.removeEventListener('mouseenter', enterHandler);
|
|
452
|
-
element.removeEventListener('mouseleave', leaveHandler);
|
|
453
|
-
element.removeEventListener('focus', focusHandler);
|
|
454
|
-
element.removeEventListener('blur', blurHandler);
|
|
455
|
-
|
|
456
|
-
if (state.hoverTimeout) clearTimeout(state.hoverTimeout);
|
|
457
|
-
if (state.originalTitle) element.setAttribute('title', state.originalTitle);
|
|
458
|
-
|
|
459
|
-
// Remove from tooltipStates array
|
|
460
|
-
const index = tooltipStates.indexOf(state);
|
|
461
|
-
if (index > -1) {
|
|
462
|
-
tooltipStates.splice(index, 1);
|
|
463
|
-
tooltipStates = tooltipStates;
|
|
464
|
-
}
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
// Store state on the data object and add to array
|
|
468
|
-
data.tooltipState = state;
|
|
469
|
-
tooltipStates.push(state);
|
|
470
|
-
tooltipStates = tooltipStates;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// React to changes in elements Map from ElementManager
|
|
474
|
-
$effect(() => {
|
|
475
|
-
// Process all elements in the current Map
|
|
476
|
-
elements.forEach((data, element) => {
|
|
477
|
-
setupTooltip(element, data);
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// Global scroll/resize listeners
|
|
482
|
-
$effect(() => {
|
|
483
|
-
function handleWindowEvents() {
|
|
484
|
-
handleScroll();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
window.addEventListener('scroll', handleWindowEvents, { passive: true });
|
|
488
|
-
window.addEventListener('resize', handleWindowEvents, { passive: true });
|
|
489
|
-
|
|
490
|
-
return () => {
|
|
491
|
-
window.removeEventListener('scroll', handleWindowEvents);
|
|
492
|
-
window.removeEventListener('resize', handleWindowEvents);
|
|
493
|
-
};
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
// Action to attach tooltip element reference
|
|
497
|
-
function tooltipRef(node: HTMLDivElement, state: TooltipState) {
|
|
498
|
-
state.tooltipElement = node;
|
|
499
|
-
|
|
500
|
-
// Update position immediately if tooltip is visible
|
|
501
|
-
if (state.isVisible) {
|
|
502
|
-
updatePosition(state);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return {
|
|
506
|
-
destroy() {
|
|
507
|
-
// Clean up reference
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
</script>
|
|
512
|
-
|
|
513
|
-
{#if children}
|
|
514
|
-
{@render children()}
|
|
515
|
-
{/if}
|
|
516
|
-
|
|
517
|
-
<!-- Render tooltips -->
|
|
518
|
-
{#each tooltipStates as state (state.element)}
|
|
519
|
-
<div
|
|
520
|
-
use:tooltipRef={state}
|
|
521
|
-
class="tooltip fixed z-[10000] px-3 py-2 text-sm rounded-md shadow-lg pointer-events-none transition-opacity duration-200 ease-out"
|
|
522
|
-
style="{state.tooltipStyle}; background-color: var(--color-text); color: var(--color-background); opacity: {state.isVisible ? '1' : '0'};"
|
|
523
|
-
role="tooltip"
|
|
524
|
-
aria-hidden={!state.isVisible}
|
|
525
|
-
>
|
|
526
|
-
{#if state.snippet}
|
|
527
|
-
{@render state.snippet()}
|
|
528
|
-
{:else if state.text}
|
|
529
|
-
{state.text}
|
|
530
|
-
{/if}
|
|
531
|
-
|
|
532
|
-
<!-- Triangle connector (only show if showArrow is true) -->
|
|
533
|
-
{#if state.showArrow}
|
|
534
|
-
<div
|
|
535
|
-
class="tooltip-arrow absolute"
|
|
536
|
-
class:top={state.effectiveSide === 'bottom'}
|
|
537
|
-
class:bottom={state.effectiveSide === 'top'}
|
|
538
|
-
class:left={state.effectiveSide === 'right'}
|
|
539
|
-
class:right={state.effectiveSide === 'left'}
|
|
540
|
-
style={state.arrowStyle}
|
|
541
|
-
></div>
|
|
542
|
-
{/if}
|
|
543
|
-
</div>
|
|
544
|
-
{/each}
|
|
545
|
-
|
|
546
|
-
<style>
|
|
547
|
-
.tooltip-arrow {
|
|
548
|
-
width: 0;
|
|
549
|
-
height: 0;
|
|
550
|
-
position: absolute;
|
|
551
|
-
z-index: 1;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
.tooltip-arrow.top {
|
|
555
|
-
bottom: 100%;
|
|
556
|
-
left: 50%;
|
|
557
|
-
transform: translateX(-50%);
|
|
558
|
-
border-left: 6px solid transparent;
|
|
559
|
-
border-right: 6px solid transparent;
|
|
560
|
-
border-bottom: 6px solid var(--color-text);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
.tooltip-arrow.bottom {
|
|
564
|
-
top: 100%;
|
|
565
|
-
left: 50%;
|
|
566
|
-
transform: translateX(-50%);
|
|
567
|
-
border-left: 6px solid transparent;
|
|
568
|
-
border-right: 6px solid transparent;
|
|
569
|
-
border-top: 6px solid var(--color-text);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
.tooltip-arrow.left {
|
|
573
|
-
right: 100%;
|
|
574
|
-
top: 50%;
|
|
575
|
-
transform: translateY(-50%);
|
|
576
|
-
border-top: 6px solid transparent;
|
|
577
|
-
border-bottom: 6px solid transparent;
|
|
578
|
-
border-right: 6px solid var(--color-text);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
.tooltip-arrow.right {
|
|
582
|
-
left: 100%;
|
|
583
|
-
top: 50%;
|
|
584
|
-
transform: translateY(-50%);
|
|
585
|
-
border-top: 6px solid transparent;
|
|
586
|
-
border-bottom: 6px solid transparent;
|
|
587
|
-
border-left: 6px solid var(--color-text);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
.tooltip {
|
|
591
|
-
max-width: var(--tooltip-max-width, calc(100vw - 1rem));
|
|
592
|
-
}
|
|
593
|
-
</style>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { HandlerComponentProps } from '../../types/ManagerTypes.js';
|
|
2
|
-
interface Props extends HandlerComponentProps {
|
|
3
|
-
/** Container for tooltip positioning bounds. Can be HTMLElement or CSS selector. Default: viewport */
|
|
4
|
-
container?: HTMLElement | string;
|
|
5
|
-
/** Named snippets for tooltip content */
|
|
6
|
-
[key: string]: any;
|
|
7
|
-
}
|
|
8
|
-
declare const TooltipHandler: import("svelte").Component<Props, {}, "">;
|
|
9
|
-
type TooltipHandler = ReturnType<typeof TooltipHandler>;
|
|
10
|
-
export default TooltipHandler;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './TooltipHandler.svelte';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './TooltipHandler.svelte';
|