sveltacular 1.0.18 → 1.0.21
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/README.md +1 -2
- package/dist/forms/bool-box/bool-box.svelte +392 -13
- package/dist/forms/bool-box/bool-box.svelte.d.ts +2 -0
- package/dist/forms/bool-box/index.d.ts +2 -0
- package/dist/forms/bool-box/index.js +2 -0
- package/dist/forms/date-box/date-box.svelte +104 -53
- package/dist/forms/date-box/date-box.svelte.d.ts +8 -3
- package/dist/forms/dimension-box/dimension-box.svelte +15 -50
- package/dist/forms/file-box/file-box.svelte +7 -25
- package/dist/forms/form-input-wrapper/form-input-wrapper.svelte +203 -0
- package/dist/forms/form-input-wrapper/form-input-wrapper.svelte.d.ts +16 -0
- package/dist/forms/form-input-wrapper/index.d.ts +2 -0
- package/dist/forms/form-input-wrapper/index.js +2 -0
- package/dist/forms/index.d.ts +0 -1
- package/dist/forms/index.js +0 -1
- package/dist/forms/list-box/list-box.svelte +26 -9
- package/dist/forms/list-box/list-box.svelte.d.ts +3 -0
- package/dist/forms/money-box/money-box.svelte +104 -65
- package/dist/forms/money-box/money-box.svelte.d.ts +6 -0
- package/dist/forms/number-box/number-box.svelte +93 -49
- package/dist/forms/number-box/number-box.svelte.d.ts +6 -0
- package/dist/forms/number-range-box/number-range-box.svelte +22 -58
- package/dist/forms/phone-box/phone-box.svelte +101 -38
- package/dist/forms/phone-box/phone-box.svelte.d.ts +6 -1
- package/dist/forms/slider/slider.svelte +13 -6
- package/dist/forms/slider/slider.svelte.d.ts +6 -2
- package/dist/forms/tag-box/tag-box.svelte +5 -3
- package/dist/forms/text-area/text-area.svelte +22 -2
- package/dist/forms/text-area/text-area.svelte.d.ts +4 -0
- package/dist/forms/text-box/text-box.svelte +97 -131
- package/dist/forms/text-box/text-box.svelte.d.ts +7 -2
- package/dist/forms/time-box/time-box.svelte +106 -37
- package/dist/forms/time-box/time-box.svelte.d.ts +10 -3
- package/dist/forms/url-box/url-box.svelte +26 -5
- package/dist/forms/url-box/url-box.svelte.d.ts +7 -1
- package/dist/generic/theme-provider/theme-provider-demo.svelte +7 -2
- package/dist/navigation/dropdown-button/dropdown-button.svelte +102 -3
- package/dist/navigation/dropdown-button/dropdown-manager.svelte.d.ts +47 -0
- package/dist/navigation/dropdown-button/dropdown-manager.svelte.js +47 -0
- package/dist/tables/table-cell.svelte +2 -0
- package/dist/tables/table-row.svelte +1 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Icon } from '../../index.js';
|
|
4
4
|
import { hasContext } from 'svelte';
|
|
5
5
|
import { uniqueId } from '../../helpers/unique-id.js';
|
|
6
|
+
import { dropdownManager } from './dropdown-manager.svelte.js';
|
|
6
7
|
|
|
7
8
|
let {
|
|
8
9
|
open = $bindable(false),
|
|
@@ -22,25 +23,112 @@
|
|
|
22
23
|
const buttonId = `dropdown-button-${id}`;
|
|
23
24
|
const menuId = `dropdown-menu-${id}`;
|
|
24
25
|
|
|
26
|
+
// DOM element refs for bind:this - these don't need $state() as they're not reactive state
|
|
27
|
+
let containerRef: HTMLDivElement | null = $state(null);
|
|
28
|
+
let menuRef: HTMLDivElement | null = $state(null);
|
|
29
|
+
|
|
30
|
+
let openUpward = $state(false);
|
|
31
|
+
|
|
25
32
|
const onClick = () => {
|
|
26
|
-
|
|
33
|
+
// Use the global manager to handle open/close
|
|
34
|
+
const shouldOpen = dropdownManager.open(id);
|
|
35
|
+
open = shouldOpen;
|
|
27
36
|
};
|
|
28
37
|
|
|
38
|
+
// Sync with global manager state
|
|
39
|
+
$effect(() => {
|
|
40
|
+
const isOpen = dropdownManager.isOpen(id);
|
|
41
|
+
if (isOpen !== open) {
|
|
42
|
+
open = isOpen;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Close this dropdown when another one opens
|
|
47
|
+
$effect(() => {
|
|
48
|
+
if (!dropdownManager.isOpen(id) && open) {
|
|
49
|
+
open = false;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
29
53
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
30
54
|
if (e.key === 'Escape' && open) {
|
|
31
55
|
e.preventDefault();
|
|
56
|
+
dropdownManager.close(id);
|
|
32
57
|
open = false;
|
|
33
58
|
// Return focus to button
|
|
34
59
|
document.getElementById(buttonId)?.focus();
|
|
35
60
|
}
|
|
36
61
|
};
|
|
37
62
|
|
|
63
|
+
// Handle clicks outside dropdown
|
|
64
|
+
$effect(() => {
|
|
65
|
+
if (!open) return;
|
|
66
|
+
|
|
67
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
68
|
+
if (containerRef && !containerRef.contains(e.target as Node)) {
|
|
69
|
+
dropdownManager.close(id);
|
|
70
|
+
open = false;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Add listener after a small delay to avoid immediate closure from the opening click
|
|
75
|
+
const timeoutId = setTimeout(() => {
|
|
76
|
+
document.addEventListener('click', handleClickOutside);
|
|
77
|
+
}, 0);
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
document.removeEventListener('click', handleClickOutside);
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Cleanup on unmount
|
|
86
|
+
$effect(() => {
|
|
87
|
+
return () => {
|
|
88
|
+
dropdownManager.close(id);
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Detect if menu should open upward to prevent overflow
|
|
93
|
+
$effect(() => {
|
|
94
|
+
if (open && containerRef && menuRef) {
|
|
95
|
+
const containerRect = containerRef.getBoundingClientRect();
|
|
96
|
+
const menuHeight = menuRef.offsetHeight;
|
|
97
|
+
|
|
98
|
+
// Check both viewport and any scrollable parent container
|
|
99
|
+
let spaceBelow = window.innerHeight - containerRect.bottom;
|
|
100
|
+
let spaceAbove = containerRect.top;
|
|
101
|
+
|
|
102
|
+
// Find the nearest scrollable parent
|
|
103
|
+
let scrollParent = containerRef.parentElement;
|
|
104
|
+
while (scrollParent) {
|
|
105
|
+
const overflowY = window.getComputedStyle(scrollParent).overflowY;
|
|
106
|
+
if (overflowY === 'auto' || overflowY === 'scroll') {
|
|
107
|
+
const parentRect = scrollParent.getBoundingClientRect();
|
|
108
|
+
const parentSpaceBelow = parentRect.bottom - containerRect.bottom;
|
|
109
|
+
const parentSpaceAbove = containerRect.top - parentRect.top;
|
|
110
|
+
|
|
111
|
+
// Use the more restrictive space constraint
|
|
112
|
+
if (parentSpaceBelow < spaceBelow) spaceBelow = parentSpaceBelow;
|
|
113
|
+
if (parentSpaceAbove < spaceAbove) spaceAbove = parentSpaceAbove;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
scrollParent = scrollParent.parentElement;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// If there's not enough space below but more space above, open upward
|
|
120
|
+
openUpward = spaceBelow < menuHeight && spaceAbove > spaceBelow;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
38
124
|
let hasText = $derived(text && text.length > 0);
|
|
39
125
|
</script>
|
|
40
126
|
|
|
41
127
|
<div
|
|
128
|
+
bind:this={containerRef}
|
|
42
129
|
class="dropdown-button {variant} icon-{icon}"
|
|
43
130
|
class:open
|
|
131
|
+
class:open-upward={openUpward}
|
|
44
132
|
role="presentation"
|
|
45
133
|
onkeydown={handleKeyDown}
|
|
46
134
|
>
|
|
@@ -68,7 +156,7 @@
|
|
|
68
156
|
{/if}
|
|
69
157
|
</button>
|
|
70
158
|
{#if open}
|
|
71
|
-
<div id={menuId} class="menu" role="menu">
|
|
159
|
+
<div bind:this={menuRef} id={menuId} class="menu" role="menu">
|
|
72
160
|
{@render children?.()}
|
|
73
161
|
</div>
|
|
74
162
|
{/if}
|
|
@@ -136,13 +224,24 @@
|
|
|
136
224
|
border-style: solid;
|
|
137
225
|
border-width: 1px;
|
|
138
226
|
border-color: var(--button-secondary-border, #aaa);
|
|
139
|
-
z-index:
|
|
227
|
+
z-index: 1000;
|
|
140
228
|
text-align: center;
|
|
229
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
141
230
|
}
|
|
142
231
|
.dropdown-button.open .icon {
|
|
143
232
|
transition: transform 0.3s linear;
|
|
144
233
|
transform: rotate(180deg);
|
|
145
234
|
}
|
|
235
|
+
.dropdown-button.open-upward button {
|
|
236
|
+
border-radius: 0 0 0.5rem 0.5rem;
|
|
237
|
+
}
|
|
238
|
+
.dropdown-button.open-upward .menu {
|
|
239
|
+
top: auto;
|
|
240
|
+
bottom: 100%;
|
|
241
|
+
}
|
|
242
|
+
.dropdown-button.open-upward.open .icon {
|
|
243
|
+
transform: rotate(0deg);
|
|
244
|
+
}
|
|
146
245
|
.dropdown-button.icon-none button .text {
|
|
147
246
|
padding-right: 0.5rem;
|
|
148
247
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global dropdown manager to ensure only one dropdown is open at a time
|
|
3
|
+
*/
|
|
4
|
+
export declare function createDropdownManager(): {
|
|
5
|
+
/**
|
|
6
|
+
* Register a dropdown as open
|
|
7
|
+
* @param id - Unique identifier for the dropdown
|
|
8
|
+
* @returns true if this dropdown was opened, false if it was closed
|
|
9
|
+
*/
|
|
10
|
+
open(id: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Close a specific dropdown
|
|
13
|
+
* @param id - Unique identifier for the dropdown
|
|
14
|
+
*/
|
|
15
|
+
close(id: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a specific dropdown is currently open
|
|
18
|
+
* @param id - Unique identifier for the dropdown
|
|
19
|
+
*/
|
|
20
|
+
isOpen(id: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Close all dropdowns
|
|
23
|
+
*/
|
|
24
|
+
closeAll(): void;
|
|
25
|
+
};
|
|
26
|
+
export declare const dropdownManager: {
|
|
27
|
+
/**
|
|
28
|
+
* Register a dropdown as open
|
|
29
|
+
* @param id - Unique identifier for the dropdown
|
|
30
|
+
* @returns true if this dropdown was opened, false if it was closed
|
|
31
|
+
*/
|
|
32
|
+
open(id: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Close a specific dropdown
|
|
35
|
+
* @param id - Unique identifier for the dropdown
|
|
36
|
+
*/
|
|
37
|
+
close(id: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a specific dropdown is currently open
|
|
40
|
+
* @param id - Unique identifier for the dropdown
|
|
41
|
+
*/
|
|
42
|
+
isOpen(id: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Close all dropdowns
|
|
45
|
+
*/
|
|
46
|
+
closeAll(): void;
|
|
47
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global dropdown manager to ensure only one dropdown is open at a time
|
|
3
|
+
*/
|
|
4
|
+
let openDropdownId = $state(null);
|
|
5
|
+
export function createDropdownManager() {
|
|
6
|
+
return {
|
|
7
|
+
/**
|
|
8
|
+
* Register a dropdown as open
|
|
9
|
+
* @param id - Unique identifier for the dropdown
|
|
10
|
+
* @returns true if this dropdown was opened, false if it was closed
|
|
11
|
+
*/
|
|
12
|
+
open(id) {
|
|
13
|
+
if (openDropdownId === id) {
|
|
14
|
+
// Same dropdown clicked again - close it
|
|
15
|
+
openDropdownId = null;
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
// Close any other open dropdown and open this one
|
|
19
|
+
openDropdownId = id;
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
/**
|
|
23
|
+
* Close a specific dropdown
|
|
24
|
+
* @param id - Unique identifier for the dropdown
|
|
25
|
+
*/
|
|
26
|
+
close(id) {
|
|
27
|
+
if (openDropdownId === id) {
|
|
28
|
+
openDropdownId = null;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Check if a specific dropdown is currently open
|
|
33
|
+
* @param id - Unique identifier for the dropdown
|
|
34
|
+
*/
|
|
35
|
+
isOpen(id) {
|
|
36
|
+
return openDropdownId === id;
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Close all dropdowns
|
|
40
|
+
*/
|
|
41
|
+
closeAll() {
|
|
42
|
+
openDropdownId = null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// Create singleton instance
|
|
47
|
+
export const dropdownManager = createDropdownManager();
|