@marianmeres/stuic 2.3.2 → 2.5.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/README.md +0 -1
- package/dist/README.md +17 -11
- package/dist/actions/autogrow.svelte.js +4 -4
- package/dist/actions/autoscroll.d.ts +2 -2
- package/dist/actions/autoscroll.js +17 -8
- package/dist/actions/file-dropzone.svelte.d.ts +1 -1
- package/dist/actions/file-dropzone.svelte.js +1 -1
- package/dist/actions/focus-trap.js +33 -24
- package/dist/actions/highlight-dragover.svelte.js +6 -5
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/on-submit-validity-check.svelte.js +2 -2
- package/dist/actions/popover/PopoverContent.svelte +15 -0
- package/dist/actions/popover/PopoverContent.svelte.d.ts +7 -0
- package/dist/actions/popover/index.css +78 -0
- package/dist/actions/popover/index.d.ts +1 -0
- package/dist/actions/popover/index.js +1 -0
- package/dist/actions/popover/popover.svelte.d.ts +112 -0
- package/dist/actions/popover/popover.svelte.js +449 -0
- package/dist/actions/resizable-width.svelte.d.ts +1 -1
- package/dist/actions/resizable-width.svelte.js +7 -5
- package/dist/actions/tooltip/index.css +2 -7
- package/dist/actions/tooltip/tooltip.svelte.js +5 -4
- package/dist/actions/trim.svelte.d.ts +1 -1
- package/dist/actions/trim.svelte.js +2 -2
- package/dist/actions/validate.svelte.d.ts +4 -4
- package/dist/actions/validate.svelte.js +9 -9
- package/dist/components/AlertConfirmPrompt/alert-confirm-prompt-stack.svelte.d.ts +7 -6
- package/dist/components/AlertConfirmPrompt/alert-confirm-prompt-stack.svelte.js +1 -2
- package/dist/components/AlertConfirmPrompt/index.d.ts +1 -1
- package/dist/components/AlertConfirmPrompt/index.js +1 -1
- package/dist/components/AnimatedElipsis/index.d.ts +1 -1
- package/dist/components/AnimatedElipsis/index.js +1 -1
- package/dist/components/Button/index.d.ts +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/ButtonGroupRadio/ButtonGroupRadio.svelte +4 -1
- package/dist/components/ButtonGroupRadio/index.d.ts +1 -1
- package/dist/components/ButtonGroupRadio/index.js +1 -1
- package/dist/components/ColorScheme/index.d.ts +2 -2
- package/dist/components/ColorScheme/index.js +2 -2
- package/dist/components/CommandMenu/CommandMenu.svelte +1 -1
- package/dist/components/CommandMenu/index.d.ts +1 -1
- package/dist/components/CommandMenu/index.js +1 -1
- package/dist/components/DismissibleMessage/index.d.ts +1 -1
- package/dist/components/DismissibleMessage/index.js +1 -1
- package/dist/components/HoverExpandableWidth/index.d.ts +1 -1
- package/dist/components/HoverExpandableWidth/index.js +1 -1
- package/dist/components/Input/FieldAssets.svelte +7 -3
- package/dist/components/Input/FieldLikeButton.svelte +1 -1
- package/dist/components/Input/FieldOptions.svelte +1 -1
- package/dist/components/Input/index.d.ts +7 -7
- package/dist/components/Input/index.js +7 -7
- package/dist/components/KbdShortcut/index.d.ts +1 -1
- package/dist/components/KbdShortcut/index.js +1 -1
- package/dist/components/ModalDialog/index.d.ts +1 -1
- package/dist/components/ModalDialog/index.js +1 -1
- package/dist/components/Notifications/index.d.ts +1 -1
- package/dist/components/Notifications/index.js +1 -1
- package/dist/components/Notifications/notifications-stack.svelte.d.ts +5 -5
- package/dist/components/Notifications/notifications-stack.svelte.js +8 -7
- package/dist/components/SlidingPanels/index.d.ts +1 -1
- package/dist/components/SlidingPanels/index.js +1 -1
- package/dist/components/Spinner/index.d.ts +1 -1
- package/dist/components/Spinner/index.js +1 -1
- package/dist/components/Switch/Switch.svelte +5 -2
- package/dist/components/Switch/index.d.ts +1 -1
- package/dist/components/Switch/index.js +1 -1
- package/dist/components/TypeaheadInput/index.d.ts +1 -1
- package/dist/components/TypeaheadInput/index.js +1 -1
- package/dist/utils/body-scroll-locker.js +4 -3
- package/dist/utils/breakpoint.svelte.js +0 -2
- package/dist/utils/colors.js +3 -3
- package/dist/utils/debounce.d.ts +1 -1
- package/dist/utils/debounce.js +1 -2
- package/dist/utils/escape-regex.js +1 -1
- package/dist/utils/event-emitter.d.ts +2 -3
- package/dist/utils/event-emitter.js +1 -2
- package/dist/utils/event-modifiers.d.ts +4 -4
- package/dist/utils/event-modifiers.js +4 -6
- package/dist/utils/get-file-type-label.js +1 -1
- package/dist/utils/is-image.js +2 -2
- package/dist/utils/is-nullish.d.ts +1 -1
- package/dist/utils/is-plain-object.d.ts +1 -1
- package/dist/utils/is-plain-object.js +4 -1
- package/dist/utils/maybe-json-parse.d.ts +1 -1
- package/dist/utils/maybe-json-parse.js +1 -1
- package/dist/utils/maybe-json-stringify.d.ts +1 -1
- package/dist/utils/move-array-item.d.ts +1 -1
- package/dist/utils/preload-img.js +2 -1
- package/dist/utils/sleep.d.ts +1 -1
- package/dist/utils/storage-abstraction.d.ts +13 -13
- package/dist/utils/storage-abstraction.js +2 -0
- package/dist/utils/svg-circle.js +2 -1
- package/dist/utils/switch.svelte.d.ts +1 -1
- package/dist/utils/switch.svelte.js +1 -1
- package/dist/utils/throttle.d.ts +1 -1
- package/dist/utils/throttle.js +7 -8
- package/dist/utils/to-integer.d.ts +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
package/dist/README.md
CHANGED
|
@@ -12,21 +12,22 @@ npm install @marianmeres/stuic
|
|
|
12
12
|
|
|
13
13
|
```svelte
|
|
14
14
|
<script>
|
|
15
|
-
|
|
15
|
+
import { Button, Modal } from "@marianmeres/stuic";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
let modal;
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<Button onclick={() => modal.open()}>Open Modal</Button>
|
|
21
21
|
|
|
22
22
|
<Modal bind:this={modal}>
|
|
23
|
-
|
|
23
|
+
<p>Hello from Modal!</p>
|
|
24
24
|
</Modal>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Components
|
|
28
28
|
|
|
29
29
|
### Layout & Overlays
|
|
30
|
+
|
|
30
31
|
- **AppShell** - Application layout wrapper with header, sidebar, main content
|
|
31
32
|
- **Backdrop** - Fullscreen overlay with transitions
|
|
32
33
|
- **Modal** - Dialog overlay with focus trap and scroll lock
|
|
@@ -34,6 +35,7 @@ npm install @marianmeres/stuic
|
|
|
34
35
|
- **Drawer** - Slide-in panel from screen edges
|
|
35
36
|
|
|
36
37
|
### Forms & Inputs
|
|
38
|
+
|
|
37
39
|
- **FieldInput** - Text input with label, validation, description
|
|
38
40
|
- **FieldTextarea** - Multiline text input with autogrow support
|
|
39
41
|
- **FieldSelect** - Native select dropdown
|
|
@@ -46,12 +48,14 @@ npm install @marianmeres/stuic
|
|
|
46
48
|
- **Fieldset** - Group of form fields with legend
|
|
47
49
|
|
|
48
50
|
### Buttons & Controls
|
|
51
|
+
|
|
49
52
|
- **Button** - Styled button with variants (primary, secondary, ghost)
|
|
50
53
|
- **ButtonGroupRadio** - Radio-style button group
|
|
51
54
|
- **Switch** - Toggle switch component
|
|
52
55
|
- **X** - Close/dismiss button icon
|
|
53
56
|
|
|
54
57
|
### Feedback & Notifications
|
|
58
|
+
|
|
55
59
|
- **Notifications** - Toast notification system
|
|
56
60
|
- **AlertConfirmPrompt** - Modal dialogs for alerts, confirms, and prompts
|
|
57
61
|
- **DismissibleMessage** - Closable message banner
|
|
@@ -59,11 +63,13 @@ npm install @marianmeres/stuic
|
|
|
59
63
|
- **Spinner** - Loading spinner animation
|
|
60
64
|
|
|
61
65
|
### Navigation & Interaction
|
|
66
|
+
|
|
62
67
|
- **CommandMenu** - Keyboard-driven command palette
|
|
63
68
|
- **TypeaheadInput** - Autocomplete text input
|
|
64
69
|
- **KbdShortcut** - Keyboard shortcut display
|
|
65
70
|
|
|
66
71
|
### Utilities
|
|
72
|
+
|
|
67
73
|
- **ColorScheme** - Dark/light mode management
|
|
68
74
|
- **Thc** - Render text, HTML, or component dynamically
|
|
69
75
|
- **SlidingPanels** - Animated panel transitions
|
|
@@ -86,7 +92,7 @@ npm install @marianmeres/stuic
|
|
|
86
92
|
## Utilities
|
|
87
93
|
|
|
88
94
|
```ts
|
|
89
|
-
import { debounce, throttle, twMerge, localStorageState } from
|
|
95
|
+
import { debounce, throttle, twMerge, localStorageState } from "@marianmeres/stuic";
|
|
90
96
|
```
|
|
91
97
|
|
|
92
98
|
- **debounce/throttle** - Function rate limiting
|
|
@@ -103,10 +109,10 @@ Components use CSS custom properties for theming. Override in your CSS:
|
|
|
103
109
|
|
|
104
110
|
```css
|
|
105
111
|
:root {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
--color-button-bg: theme("colors.blue.500");
|
|
113
|
+
--color-button-text: white;
|
|
114
|
+
--color-input-accent: theme("colors.blue.500");
|
|
115
|
+
/* ... */
|
|
110
116
|
}
|
|
111
117
|
```
|
|
112
118
|
|
|
@@ -117,10 +123,10 @@ See `src/lib/theme.css` for all available custom properties.
|
|
|
117
123
|
All components export their Props types:
|
|
118
124
|
|
|
119
125
|
```ts
|
|
120
|
-
import type { ButtonProps, ModalProps } from
|
|
126
|
+
import type { ButtonProps, ModalProps } from "@marianmeres/stuic";
|
|
121
127
|
|
|
122
128
|
const buttonConfig: Partial<ButtonProps> = {
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
variant: "primary",
|
|
130
|
+
size: "lg",
|
|
125
131
|
};
|
|
126
132
|
```
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
export function autogrow(el, fn) {
|
|
26
26
|
let lastValue = el.value;
|
|
27
27
|
$effect(() => {
|
|
28
|
-
|
|
28
|
+
const { enabled = true, max = 250, immediate = true, value } = fn?.() || {};
|
|
29
29
|
if (!enabled)
|
|
30
30
|
return;
|
|
31
|
-
function set_height(
|
|
31
|
+
function set_height() {
|
|
32
32
|
// console.log(123, el.value);
|
|
33
33
|
if (enabled) {
|
|
34
34
|
el.style.height = "auto"; // Reset height to auto to correctly calculate scrollHeight
|
|
@@ -38,14 +38,14 @@ export function autogrow(el, fn) {
|
|
|
38
38
|
// eventlistener strategy (we're not passing value)
|
|
39
39
|
if (value === undefined) {
|
|
40
40
|
if (immediate)
|
|
41
|
-
set_height(
|
|
41
|
+
set_height();
|
|
42
42
|
el.addEventListener("input", set_height);
|
|
43
43
|
el.addEventListener("blur", set_height);
|
|
44
44
|
}
|
|
45
45
|
// strategy with provided value
|
|
46
46
|
else {
|
|
47
47
|
if (value !== lastValue) {
|
|
48
|
-
set_height(
|
|
48
|
+
set_height();
|
|
49
49
|
lastValue = value;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -12,8 +12,8 @@ interface StoreLike<T> extends StoreReadable<T> {
|
|
|
12
12
|
* Options for the autoscroll action.
|
|
13
13
|
*/
|
|
14
14
|
export type AutoscrollOptions = ScrollOptions & {
|
|
15
|
-
dependencies?: StoreReadable<
|
|
16
|
-
logger?: (...args:
|
|
15
|
+
dependencies?: StoreReadable<unknown>[];
|
|
16
|
+
logger?: (...args: unknown[]) => void;
|
|
17
17
|
newScrollableContentSignal?: StoreLike<boolean>;
|
|
18
18
|
shouldScrollThresholdPx?: number;
|
|
19
19
|
startScrollTimeout?: number;
|
|
@@ -43,16 +43,19 @@ export function autoscroll(node, options = {
|
|
|
43
43
|
startScrollTimeout: DEFAULTS.startScrollTimeout,
|
|
44
44
|
}) {
|
|
45
45
|
// use "smooth" by default
|
|
46
|
-
options.behavior ??=
|
|
46
|
+
options.behavior ??= "smooth";
|
|
47
47
|
options.shouldScrollThresholdPx ??= DEFAULTS.shouldScrollThresholdPx;
|
|
48
48
|
options.startScrollTimeout ??= DEFAULTS.startScrollTimeout;
|
|
49
49
|
const { behavior, shouldScrollThresholdPx, dependencies, logger, newScrollableContentSignal, startScrollTimeout, } = options || {};
|
|
50
50
|
let origScrollHeight = 0;
|
|
51
|
-
const log = (...args) =>
|
|
51
|
+
const log = (...args) => {
|
|
52
|
+
if (typeof logger === "function")
|
|
53
|
+
logger.apply(null, [...args]);
|
|
54
|
+
};
|
|
52
55
|
const shouldScroll = () => {
|
|
53
56
|
const { scrollTop, clientHeight } = node;
|
|
54
57
|
const result = origScrollHeight - scrollTop - clientHeight < shouldScrollThresholdPx;
|
|
55
|
-
log(
|
|
58
|
+
log("shouldScroll?", result, { scrollTop, origScrollHeight, clientHeight });
|
|
56
59
|
return result;
|
|
57
60
|
};
|
|
58
61
|
const scroll = () => {
|
|
@@ -62,17 +65,23 @@ export function autoscroll(node, options = {
|
|
|
62
65
|
};
|
|
63
66
|
// for when children change sizes
|
|
64
67
|
const resizeObserver = new ResizeObserver(() => {
|
|
65
|
-
log(
|
|
66
|
-
shouldScroll()
|
|
68
|
+
log("observed resize...");
|
|
69
|
+
if (shouldScroll())
|
|
70
|
+
scroll();
|
|
67
71
|
});
|
|
68
72
|
// for when children
|
|
69
73
|
const mutationObserver = new MutationObserver(() => {
|
|
70
|
-
log(
|
|
71
|
-
shouldScroll()
|
|
74
|
+
log("observed mutation...");
|
|
75
|
+
if (shouldScroll()) {
|
|
76
|
+
scroll();
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
newScrollableContentSignal?.set(true);
|
|
80
|
+
}
|
|
72
81
|
origScrollHeight = node.scrollHeight;
|
|
73
82
|
});
|
|
74
83
|
const unsubs = dependencies?.map((dep) => dep.subscribe((v) => {
|
|
75
|
-
log(
|
|
84
|
+
log("dependency update...", v);
|
|
76
85
|
setTimeout(scroll, startScrollTimeout);
|
|
77
86
|
})) ?? [];
|
|
78
87
|
// observe size of all children
|
|
@@ -5,7 +5,7 @@ interface FileDropzoneOptions {
|
|
|
5
5
|
enabled?: boolean;
|
|
6
6
|
inputEl: HTMLInputElement;
|
|
7
7
|
allowClick?: boolean;
|
|
8
|
-
processFiles?: (files: FileList | null, wasDrop?: boolean) =>
|
|
8
|
+
processFiles?: (files: FileList | null, wasDrop?: boolean) => void | Promise<void>;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* A Svelte action that turns any element into a file drop zone.
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
export function fileDropzone(el, fn) {
|
|
42
42
|
$effect(() => {
|
|
43
|
-
|
|
43
|
+
const { enabled = true, allowClick = true, inputEl, processFiles, } = fn?.() || {};
|
|
44
44
|
if (!enabled)
|
|
45
45
|
return;
|
|
46
46
|
if (!inputEl) {
|
|
@@ -32,34 +32,36 @@ const defaults = { enabled: true, autoFocusFirst: true };
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
export function focusTrap(node, options = {}) {
|
|
35
|
-
let
|
|
35
|
+
let enabled;
|
|
36
|
+
const { enabled: _enabled, autoFocusFirst } = { ...defaults, ...(options || {}) };
|
|
37
|
+
enabled = _enabled ?? true;
|
|
36
38
|
const focusableSelectors = [
|
|
37
|
-
|
|
39
|
+
"[contentEditable=true]",
|
|
38
40
|
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
"button:not([disabled])",
|
|
42
|
+
"input:not([disabled])",
|
|
43
|
+
"select:not([disabled])",
|
|
44
|
+
"textarea:not([disabled])",
|
|
43
45
|
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
"a[href]",
|
|
47
|
+
"area[href]",
|
|
48
|
+
"details",
|
|
49
|
+
"iframe",
|
|
48
50
|
// see more below on tabindexes
|
|
49
51
|
'[tabindex]:not([tabindex^="-"])',
|
|
50
|
-
].join(
|
|
52
|
+
].join(",");
|
|
51
53
|
let first;
|
|
52
54
|
let last;
|
|
53
55
|
// When the first element is selected, shift+tab pressed, jump to the last selectable item.
|
|
54
56
|
function onFirstElemKeydown(e) {
|
|
55
|
-
if (e.shiftKey && e.code ===
|
|
57
|
+
if (e.shiftKey && e.code === "Tab") {
|
|
56
58
|
e.preventDefault();
|
|
57
59
|
last.focus();
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
// When the last item selected, tab pressed, jump to the first selectable item.
|
|
61
63
|
function onLastElemKeydown(e) {
|
|
62
|
-
if (!e.shiftKey && e.code ===
|
|
64
|
+
if (!e.shiftKey && e.code === "Tab") {
|
|
63
65
|
e.preventDefault();
|
|
64
66
|
first.focus();
|
|
65
67
|
}
|
|
@@ -68,15 +70,15 @@ export function focusTrap(node, options = {}) {
|
|
|
68
70
|
if (enabled === false)
|
|
69
71
|
return;
|
|
70
72
|
let maxTabindex = 0;
|
|
71
|
-
|
|
73
|
+
const focusable = [...node.querySelectorAll(focusableSelectors)]
|
|
72
74
|
// filter negative tabindexes (afaik there is no :not([disabled] OR [tabindex^="-"]))
|
|
73
75
|
.filter((e) => {
|
|
74
76
|
// reusing loop for a side job here... see sort below
|
|
75
|
-
maxTabindex = Math.max(maxTabindex, parseInt(e.getAttribute(
|
|
77
|
+
maxTabindex = Math.max(maxTabindex, parseInt(e.getAttribute("tabindex") || "0"));
|
|
76
78
|
//
|
|
77
|
-
if (e.getAttribute(
|
|
79
|
+
if (e.getAttribute("disabled") === "")
|
|
78
80
|
return false;
|
|
79
|
-
if ((e.getAttribute(
|
|
81
|
+
if ((e.getAttribute("tabindex") || "").startsWith("-"))
|
|
80
82
|
return false;
|
|
81
83
|
return true;
|
|
82
84
|
})
|
|
@@ -84,8 +86,8 @@ export function focusTrap(node, options = {}) {
|
|
|
84
86
|
// but must increase zero to max + 1 first, because browsers focus zeros as last...
|
|
85
87
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
|
|
86
88
|
.sort((e1, e2) => {
|
|
87
|
-
const a = parseInt(e1.getAttribute(
|
|
88
|
-
const b = parseInt(e2.getAttribute(
|
|
89
|
+
const a = parseInt(e1.getAttribute("tabindex") || "0") || maxTabindex + 1;
|
|
90
|
+
const b = parseInt(e2.getAttribute("tabindex") || "0") || maxTabindex + 1;
|
|
89
91
|
return a - b;
|
|
90
92
|
});
|
|
91
93
|
if (focusable.length) {
|
|
@@ -95,14 +97,16 @@ export function focusTrap(node, options = {}) {
|
|
|
95
97
|
if (!fromObserver && autoFocusFirst)
|
|
96
98
|
first.focus();
|
|
97
99
|
// Listen for keydown on first & last element
|
|
98
|
-
first.addEventListener(
|
|
99
|
-
last.addEventListener(
|
|
100
|
+
first.addEventListener("keydown", onFirstElemKeydown);
|
|
101
|
+
last.addEventListener("keydown", onLastElemKeydown);
|
|
100
102
|
}
|
|
101
103
|
};
|
|
102
104
|
queryElements(false);
|
|
103
105
|
function cleanup() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
if (first)
|
|
107
|
+
first.removeEventListener("keydown", onFirstElemKeydown);
|
|
108
|
+
if (last)
|
|
109
|
+
last.removeEventListener("keydown", onLastElemKeydown);
|
|
106
110
|
}
|
|
107
111
|
// When children of node are changed (added or removed)
|
|
108
112
|
const observer = new MutationObserver((mutations, observer) => {
|
|
@@ -117,7 +121,12 @@ export function focusTrap(node, options = {}) {
|
|
|
117
121
|
return {
|
|
118
122
|
update(options = {}) {
|
|
119
123
|
enabled = !!options?.enabled;
|
|
120
|
-
|
|
124
|
+
if (enabled) {
|
|
125
|
+
queryElements(false);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
cleanup();
|
|
129
|
+
}
|
|
121
130
|
},
|
|
122
131
|
destroy() {
|
|
123
132
|
cleanup();
|
|
@@ -32,15 +32,16 @@ export function highlightDragover(el, fn) {
|
|
|
32
32
|
// // Trigger change event for any listeners
|
|
33
33
|
// el.dispatchEvent(new Event("change"));
|
|
34
34
|
// }
|
|
35
|
-
function prevent(e) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
35
|
+
// function prevent(e: DragEvent) {
|
|
36
|
+
// e.preventDefault();
|
|
37
|
+
// // e.stopPropagation();
|
|
38
|
+
// }
|
|
39
39
|
const HIGH = ["dragenter", "dragover"];
|
|
40
40
|
const UNHIGH = ["dragleave", "drop"];
|
|
41
41
|
// const ALL = [...HIGH, ...UNHIGH];
|
|
42
42
|
$effect(() => {
|
|
43
|
-
|
|
43
|
+
const { enabled = true } = fn?.() || {};
|
|
44
|
+
let { classes = ["dragover"] } = fn?.() || {};
|
|
44
45
|
if (!enabled)
|
|
45
46
|
return;
|
|
46
47
|
if (!Array.isArray(classes))
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./file-dropzone.svelte.js";
|
|
|
4
4
|
export * from "./focus-trap.js";
|
|
5
5
|
export * from "./highlight-dragover.svelte.js";
|
|
6
6
|
export * from "./on-submit-validity-check.svelte.js";
|
|
7
|
+
export * from "./popover/popover.svelte.js";
|
|
7
8
|
export * from "./resizable-width.svelte.js";
|
|
8
9
|
export * from "./tooltip/tooltip.svelte.js";
|
|
9
10
|
export * from "./trim.svelte.js";
|
package/dist/actions/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export * from "./file-dropzone.svelte.js";
|
|
|
4
4
|
export * from "./focus-trap.js";
|
|
5
5
|
export * from "./highlight-dragover.svelte.js";
|
|
6
6
|
export * from "./on-submit-validity-check.svelte.js";
|
|
7
|
+
export * from "./popover/popover.svelte.js";
|
|
7
8
|
export * from "./resizable-width.svelte.js";
|
|
8
9
|
export * from "./tooltip/tooltip.svelte.js";
|
|
9
10
|
export * from "./trim.svelte.js";
|
|
@@ -47,9 +47,9 @@ export function onSubmitValidityCheck(node) {
|
|
|
47
47
|
e.preventDefault();
|
|
48
48
|
// this will disable all other onsubmit listeners...
|
|
49
49
|
e.stopImmediatePropagation();
|
|
50
|
-
|
|
50
|
+
const invalid = [];
|
|
51
51
|
for (let i = 0; i < node.elements?.length; i++) {
|
|
52
|
-
|
|
52
|
+
const el = node.elements[i];
|
|
53
53
|
if (typeof el.checkValidity === "function") {
|
|
54
54
|
// hm... radios are tricky, as triggering change automatically checks the last
|
|
55
55
|
// input (last radio input), which is not desired
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { THC } from "../../components/Thc/Thc.svelte";
|
|
3
|
+
|
|
4
|
+
export interface Props {
|
|
5
|
+
thc: THC;
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import Thc from "../../components/Thc/Thc.svelte";
|
|
11
|
+
|
|
12
|
+
let { thc }: Props = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Thc {thc} />
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { THC } from "../../components/Thc/Thc.svelte";
|
|
2
|
+
export interface Props {
|
|
3
|
+
thc: THC;
|
|
4
|
+
}
|
|
5
|
+
declare const PopoverContent: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type PopoverContent = ReturnType<typeof PopoverContent>;
|
|
7
|
+
export default PopoverContent;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* prettier-ignore */
|
|
2
|
+
@theme inline {
|
|
3
|
+
/* style props will not work as with regular components, because popovers are created outside of the anchor dom tree */
|
|
4
|
+
--color-popover-bg: var(--color-popover-bg, var(--color-white));
|
|
5
|
+
--color-popover-bg-dark: var(--color-popover-bg-dark, var(--color-neutral-800));
|
|
6
|
+
--color-popover-text: var(--color-popover-text, var(--color-neutral-900));
|
|
7
|
+
--color-popover-text-dark: var(--color-popover-text-dark, var(--color-neutral-100));
|
|
8
|
+
--color-popover-border: var(--color-popover-border, var(--color-neutral-200));
|
|
9
|
+
--color-popover-border-dark: var(--color-popover-border-dark, var(--color-neutral-700));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Base popover styles */
|
|
13
|
+
.stuic-popover,
|
|
14
|
+
.stuic-popover-fallback {
|
|
15
|
+
display: none;
|
|
16
|
+
opacity: 0;
|
|
17
|
+
transition-property: opacity;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Backdrop for fallback mode */
|
|
21
|
+
.stuic-popover-backdrop {
|
|
22
|
+
opacity: 0;
|
|
23
|
+
transition-property: opacity;
|
|
24
|
+
&.pop-visible {
|
|
25
|
+
opacity: 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Define fallback positions for auto-repositioning when popover overflows viewport */
|
|
30
|
+
/* span-right/span-left keep popover "above/below" but shift horizontally */
|
|
31
|
+
@position-try --pop-top-span-right {
|
|
32
|
+
position-area: top span-right; /* above, aligned to anchor's left edge */
|
|
33
|
+
}
|
|
34
|
+
@position-try --pop-top-span-left {
|
|
35
|
+
position-area: top span-left; /* above, aligned to anchor's right edge */
|
|
36
|
+
}
|
|
37
|
+
@position-try --pop-bottom-span-right {
|
|
38
|
+
position-area: bottom span-right;
|
|
39
|
+
}
|
|
40
|
+
@position-try --pop-bottom-span-left {
|
|
41
|
+
position-area: bottom span-left;
|
|
42
|
+
}
|
|
43
|
+
@position-try --pop-left {
|
|
44
|
+
position-area: left;
|
|
45
|
+
}
|
|
46
|
+
@position-try --pop-right {
|
|
47
|
+
position-area: right;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* CSS Anchor Positioning supported mode */
|
|
51
|
+
@supports (anchor-name: --anchor) {
|
|
52
|
+
.stuic-popover {
|
|
53
|
+
/* position-area is set via inline style based on position param */
|
|
54
|
+
/* fallbacks ensure popover stays within viewport */
|
|
55
|
+
position-try-fallbacks:
|
|
56
|
+
--pop-top-span-right, --pop-top-span-left, flip-block, --pop-bottom-span-right,
|
|
57
|
+
--pop-bottom-span-left, --pop-left, --pop-right;
|
|
58
|
+
|
|
59
|
+
&.pop-block {
|
|
60
|
+
display: block;
|
|
61
|
+
opacity: 0;
|
|
62
|
+
&.pop-visible {
|
|
63
|
+
opacity: 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Fallback mode (centered modal) */
|
|
70
|
+
.stuic-popover-fallback {
|
|
71
|
+
&.pop-block {
|
|
72
|
+
display: block;
|
|
73
|
+
opacity: 0;
|
|
74
|
+
&.pop-visible {
|
|
75
|
+
opacity: 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./popover.svelte.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./popover.svelte.js";
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { THC } from "../../components/Thc/Thc.svelte";
|
|
2
|
+
import "./index.css";
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the browser supports CSS Anchor Positioning for popovers.
|
|
5
|
+
*
|
|
6
|
+
* Tests for support of `anchor-name`, `position-area`, `position-try`,
|
|
7
|
+
* and `position-try-fallbacks` CSS properties.
|
|
8
|
+
*
|
|
9
|
+
* @returns `true` if CSS Anchor Positioning is fully supported
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* if (isPopoverSupported()) {
|
|
14
|
+
* // Use native anchor positioning
|
|
15
|
+
* } else {
|
|
16
|
+
* // Fall back to centered modal
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function isPopoverSupported(): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Valid positions for popover placement relative to the anchor element.
|
|
23
|
+
*/
|
|
24
|
+
export type PopoverPosition = "top" | "top-left" | "top-right" | "top-span-left" | "top-span-right" | "bottom" | "bottom-left" | "bottom-right" | "bottom-span-left" | "bottom-span-right" | "left" | "right";
|
|
25
|
+
/**
|
|
26
|
+
* Trigger mode for the popover.
|
|
27
|
+
*/
|
|
28
|
+
export type PopoverTrigger = "click" | "hover";
|
|
29
|
+
/**
|
|
30
|
+
* Options for the popover action.
|
|
31
|
+
*/
|
|
32
|
+
export interface PopoverOptions {
|
|
33
|
+
/** Whether the popover is enabled */
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
/** Content to display (THC format: string, {text}, {html}, {component, props}, {snippet}, or Snippet) */
|
|
36
|
+
content?: THC | null;
|
|
37
|
+
/** Preferred position relative to anchor */
|
|
38
|
+
position?: PopoverPosition;
|
|
39
|
+
/** Trigger mode: "click" (default) or "hover" */
|
|
40
|
+
trigger?: PopoverTrigger;
|
|
41
|
+
/** Delay before showing (ms), mainly for hover mode */
|
|
42
|
+
showDelay?: number;
|
|
43
|
+
/** Delay before hiding (ms), mainly for hover mode */
|
|
44
|
+
hideDelay?: number;
|
|
45
|
+
/** Custom class for the popover container */
|
|
46
|
+
class?: string;
|
|
47
|
+
/** Offset/margin from the anchor element (CSS value, e.g., "0.5rem", "8px") */
|
|
48
|
+
offset?: string;
|
|
49
|
+
/** Close all other open popovers when this one opens */
|
|
50
|
+
closeOthers?: boolean;
|
|
51
|
+
/** Close on click outside (for click trigger) */
|
|
52
|
+
closeOnClickOutside?: boolean;
|
|
53
|
+
/** Close on Escape key */
|
|
54
|
+
closeOnEscape?: boolean;
|
|
55
|
+
/** Show backdrop in fallback mode */
|
|
56
|
+
showBackdrop?: boolean;
|
|
57
|
+
/** Force fallback mode (centered modal) even if CSS Anchor Positioning is supported */
|
|
58
|
+
forceFallback?: boolean;
|
|
59
|
+
/** Callback when popover opens */
|
|
60
|
+
onShow?: () => void;
|
|
61
|
+
/** Callback when popover hides */
|
|
62
|
+
onHide?: () => void;
|
|
63
|
+
/** Debug mode */
|
|
64
|
+
debug?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A Svelte action that displays a popover anchored to an element using CSS Anchor Positioning.
|
|
68
|
+
*
|
|
69
|
+
* The popover appears on click or hover (configurable) and supports multiple positions
|
|
70
|
+
* with automatic fallback. When CSS Anchor Positioning is not supported, falls back
|
|
71
|
+
* to a centered modal overlay.
|
|
72
|
+
*
|
|
73
|
+
* @param anchorEl - The element to attach the popover to
|
|
74
|
+
* @param fn - Function returning popover options (reactive)
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```svelte
|
|
78
|
+
* <!-- Click trigger (default) -->
|
|
79
|
+
* <button use:popover={() => ({ content: "Hello World" })}>
|
|
80
|
+
* Open Popover
|
|
81
|
+
* </button>
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```svelte
|
|
86
|
+
* <!-- Hover trigger -->
|
|
87
|
+
* <button use:popover={() => ({
|
|
88
|
+
* content: "Hover content",
|
|
89
|
+
* trigger: "hover",
|
|
90
|
+
* position: "top"
|
|
91
|
+
* })}>
|
|
92
|
+
* Hover Me
|
|
93
|
+
* </button>
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```svelte
|
|
98
|
+
* <!-- With component content -->
|
|
99
|
+
* <button use:popover={() => ({
|
|
100
|
+
* content: { component: MyComponent, props: { foo: "bar" } }
|
|
101
|
+
* })}>
|
|
102
|
+
* With Component
|
|
103
|
+
* </button>
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* - Falls back to centered modal when CSS Anchor Positioning is not supported
|
|
108
|
+
* - Closes on click outside (for click trigger) and Escape key by default
|
|
109
|
+
* - For hover trigger, popover persists when hovering over it
|
|
110
|
+
* - Automatically cleans up DOM elements on unmount
|
|
111
|
+
*/
|
|
112
|
+
export declare function popover(anchorEl: HTMLElement, fn?: () => PopoverOptions): void;
|