@sveltia/ui 0.25.2 → 0.25.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/checkbox/checkbox.svelte +4 -5
- package/dist/components/menu/menu-item.svelte +46 -6
- package/dist/components/util/popup.svelte +72 -33
- package/dist/components/util/popup.svelte.d.ts +23 -5
- package/dist/services/popup.svelte.js +1 -3
- package/dist/typedefs.d.ts +12 -0
- package/dist/typedefs.js +3 -0
- package/package.json +8 -8
|
@@ -104,8 +104,10 @@
|
|
|
104
104
|
{#snippet startIcon()}
|
|
105
105
|
{#if checkIcon}
|
|
106
106
|
{@render checkIcon()}
|
|
107
|
-
{:else}
|
|
108
|
-
<Icon name=
|
|
107
|
+
{:else if indeterminate}
|
|
108
|
+
<Icon name="remove" />
|
|
109
|
+
{:else if checked}
|
|
110
|
+
<Icon name="check" />
|
|
109
111
|
{/if}
|
|
110
112
|
{/snippet}
|
|
111
113
|
</Button>
|
|
@@ -170,9 +172,6 @@
|
|
|
170
172
|
color: var(--sui-checkbox-foreground-color-checked, var(--sui-primary-accent-color-inverted));
|
|
171
173
|
background-color: var(--sui-checkbox-background-color-checked, var(--sui-primary-accent-color));
|
|
172
174
|
}
|
|
173
|
-
.checkbox :global(button[aria-checked="false"]) {
|
|
174
|
-
color: transparent;
|
|
175
|
-
}
|
|
176
175
|
.checkbox :global(button[aria-invalid="true"]) {
|
|
177
176
|
border-color: var(--sui-error-border-color);
|
|
178
177
|
color: var(--sui-error-foreground-color);
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
@see https://w3c.github.io/aria/#menuitem
|
|
5
5
|
-->
|
|
6
6
|
<script>
|
|
7
|
+
import { onMount } from 'svelte';
|
|
7
8
|
import Button from '../button/button.svelte';
|
|
8
9
|
import Icon from '../icon/icon.svelte';
|
|
9
10
|
import Popup from '../util/popup.svelte';
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
endIcon: _endIcon,
|
|
26
27
|
chevronIcon,
|
|
27
28
|
items,
|
|
29
|
+
onmouseenter,
|
|
30
|
+
onmouseleave,
|
|
31
|
+
onclick,
|
|
28
32
|
onChange,
|
|
29
33
|
onSelect,
|
|
30
34
|
...restProps
|
|
@@ -32,6 +36,7 @@
|
|
|
32
36
|
} = $props();
|
|
33
37
|
|
|
34
38
|
let isPopupOpen = $state(false);
|
|
39
|
+
let isPopupHovered = $state(false);
|
|
35
40
|
|
|
36
41
|
/**
|
|
37
42
|
* Reference to the `<button>` element.
|
|
@@ -39,7 +44,17 @@
|
|
|
39
44
|
*/
|
|
40
45
|
let buttonElement = $state();
|
|
41
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Reference to the `<button>` element.
|
|
49
|
+
* @type {HTMLDialogElement | undefined}
|
|
50
|
+
*/
|
|
51
|
+
let dialogElement = $state();
|
|
52
|
+
|
|
42
53
|
const hasItems = $derived(role === 'menuitem' && !!items);
|
|
54
|
+
|
|
55
|
+
onMount(() => {
|
|
56
|
+
dialogElement = buttonElement?.closest('dialog') ?? undefined;
|
|
57
|
+
});
|
|
43
58
|
</script>
|
|
44
59
|
|
|
45
60
|
<div role="none" class="sui menuitem {className}" {hidden}>
|
|
@@ -51,15 +66,34 @@
|
|
|
51
66
|
{disabled}
|
|
52
67
|
aria-haspopup={hasItems ? 'menu' : undefined}
|
|
53
68
|
aria-expanded={hasItems ? isPopupOpen : undefined}
|
|
54
|
-
onmouseenter={() => {
|
|
69
|
+
onmouseenter={(event) => {
|
|
55
70
|
if (hasItems) {
|
|
56
|
-
|
|
71
|
+
window.setTimeout(() => {
|
|
72
|
+
isPopupOpen = true;
|
|
73
|
+
}, 200);
|
|
57
74
|
}
|
|
75
|
+
|
|
76
|
+
onmouseenter?.(event);
|
|
58
77
|
}}
|
|
59
|
-
onmouseleave={() => {
|
|
78
|
+
onmouseleave={(event) => {
|
|
60
79
|
if (hasItems) {
|
|
61
|
-
|
|
80
|
+
window.setTimeout(() => {
|
|
81
|
+
if (!isPopupHovered) {
|
|
82
|
+
isPopupOpen = false;
|
|
83
|
+
}
|
|
84
|
+
}, 200);
|
|
62
85
|
}
|
|
86
|
+
|
|
87
|
+
onmouseleave?.(event);
|
|
88
|
+
}}
|
|
89
|
+
onclick={(event) => {
|
|
90
|
+
if (hasItems) {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
event.stopPropagation();
|
|
93
|
+
isPopupOpen = !isPopupOpen;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onclick?.(event);
|
|
63
97
|
}}
|
|
64
98
|
{onChange}
|
|
65
99
|
{onSelect}
|
|
@@ -89,8 +123,14 @@
|
|
|
89
123
|
{@render _endIcon?.()}
|
|
90
124
|
{/snippet}
|
|
91
125
|
</Button>
|
|
92
|
-
{#if hasItems}
|
|
93
|
-
<Popup
|
|
126
|
+
{#if hasItems && buttonElement && dialogElement}
|
|
127
|
+
<Popup
|
|
128
|
+
anchor={buttonElement}
|
|
129
|
+
parentDialogElement={dialogElement}
|
|
130
|
+
position="right-top"
|
|
131
|
+
bind:open={isPopupOpen}
|
|
132
|
+
bind:hovered={isPopupHovered}
|
|
133
|
+
>
|
|
94
134
|
<Menu>
|
|
95
135
|
{@render items?.()}
|
|
96
136
|
</Menu>
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
* @typedef {object} Props
|
|
14
14
|
* @property {string} [class] - The `class` attribute on the content element.
|
|
15
15
|
* @property {boolean} [open] - Whether to open the popup.
|
|
16
|
+
* @property {boolean} [hovered] - Whether the content is hovered.
|
|
16
17
|
* @property {HTMLElement} [anchor] - A reference to the anchor element that opens the popup.
|
|
17
18
|
* Typically a `<button>`.
|
|
18
19
|
* @property {HTMLElement} [content] - A reference to the content element.
|
|
19
20
|
* @property {import('../../typedefs').PopupPosition} [position] - Where to show the popup.
|
|
20
|
-
* @property {HTMLElement} [positionBaseElement] - The base element of
|
|
21
|
-
*
|
|
21
|
+
* @property {HTMLElement} [positionBaseElement] - The base element of {@link position}. If
|
|
22
|
+
* omitted, this will be {@link anchor}.
|
|
23
|
+
* @property {HTMLDialogElement} [parentDialogElement] - A reference to a dialog element that is
|
|
24
|
+
* already displayed. This should be provided for a nested popup.
|
|
22
25
|
* @property {boolean} [touchOptimized] - Whether to show the popup at the center of the screen on
|
|
23
26
|
* mobile/tablet and ignore the defined dropdown `position`.
|
|
24
27
|
* @property {import('svelte').Snippet} [children] - Primary slot content.
|
|
@@ -32,12 +35,14 @@
|
|
|
32
35
|
let {
|
|
33
36
|
/* eslint-disable prefer-const */
|
|
34
37
|
open = $bindable(false),
|
|
38
|
+
hovered = $bindable(false),
|
|
35
39
|
content = $bindable(undefined),
|
|
36
40
|
class: className,
|
|
37
41
|
showBackdrop = false,
|
|
38
42
|
anchor,
|
|
39
43
|
position = 'bottom-left',
|
|
40
44
|
positionBaseElement = undefined,
|
|
45
|
+
parentDialogElement = undefined,
|
|
41
46
|
touchOptimized = false,
|
|
42
47
|
children,
|
|
43
48
|
onOpen,
|
|
@@ -77,6 +82,8 @@
|
|
|
77
82
|
height: undefined,
|
|
78
83
|
});
|
|
79
84
|
|
|
85
|
+
let hoveredTimeout = 0;
|
|
86
|
+
|
|
80
87
|
/**
|
|
81
88
|
* Initialize the popup.
|
|
82
89
|
*/
|
|
@@ -98,6 +105,13 @@
|
|
|
98
105
|
initialized = true;
|
|
99
106
|
};
|
|
100
107
|
|
|
108
|
+
$effect(() => {
|
|
109
|
+
if (parentDialogElement && !dialogElement && content) {
|
|
110
|
+
dialogElement = parentDialogElement;
|
|
111
|
+
dialogElement.append(content);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
101
115
|
$effect(() => {
|
|
102
116
|
if (anchor && dialogElement && !initialized) {
|
|
103
117
|
init();
|
|
@@ -111,38 +125,10 @@
|
|
|
111
125
|
});
|
|
112
126
|
</script>
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
{...restProps}
|
|
116
|
-
bind:dialog={dialogElement}
|
|
117
|
-
role="none"
|
|
118
|
-
class="popup"
|
|
119
|
-
bind:open
|
|
120
|
-
showBackdrop={showBackdrop ?? touch}
|
|
121
|
-
lightDismiss={true}
|
|
122
|
-
keepContent={true}
|
|
123
|
-
onOpen={async (event) => {
|
|
124
|
-
onOpen?.(event);
|
|
125
|
-
|
|
126
|
-
await sleep(100);
|
|
127
|
-
|
|
128
|
-
if (!content) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const target = /** @type {HTMLElement} */ (
|
|
133
|
-
content.querySelector('[tabindex]:not([aria-disabled="true"])')
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (target) {
|
|
137
|
-
target.focus();
|
|
138
|
-
} else {
|
|
139
|
-
content.tabIndex = -1;
|
|
140
|
-
content.focus();
|
|
141
|
-
}
|
|
142
|
-
}}
|
|
143
|
-
>
|
|
128
|
+
{#snippet contentWrapper()}
|
|
144
129
|
<div
|
|
145
130
|
bind:this={content}
|
|
131
|
+
hidden={!open}
|
|
146
132
|
role="none"
|
|
147
133
|
class="content {className} {contentType}"
|
|
148
134
|
class:touch
|
|
@@ -152,10 +138,63 @@
|
|
|
152
138
|
style:max-width={$style.maxWidth}
|
|
153
139
|
style:max-height={$style.height}
|
|
154
140
|
style:visibility={$style.inset ? undefined : 'hidden'}
|
|
141
|
+
onmouseenter={() => {
|
|
142
|
+
hovered = true;
|
|
143
|
+
|
|
144
|
+
if (parentDialogElement) {
|
|
145
|
+
window.clearTimeout(hoveredTimeout);
|
|
146
|
+
}
|
|
147
|
+
}}
|
|
148
|
+
onmouseleave={() => {
|
|
149
|
+
hovered = false;
|
|
150
|
+
|
|
151
|
+
if (parentDialogElement) {
|
|
152
|
+
hoveredTimeout = window.setTimeout(() => {
|
|
153
|
+
open = false;
|
|
154
|
+
}, 200);
|
|
155
|
+
}
|
|
156
|
+
}}
|
|
155
157
|
>
|
|
156
158
|
{@render children?.()}
|
|
157
159
|
</div>
|
|
158
|
-
|
|
160
|
+
{/snippet}
|
|
161
|
+
|
|
162
|
+
{#if parentDialogElement}
|
|
163
|
+
{@render contentWrapper()}
|
|
164
|
+
{:else}
|
|
165
|
+
<Modal
|
|
166
|
+
{...restProps}
|
|
167
|
+
bind:dialog={dialogElement}
|
|
168
|
+
role="none"
|
|
169
|
+
class="popup"
|
|
170
|
+
bind:open
|
|
171
|
+
showBackdrop={showBackdrop ?? touch}
|
|
172
|
+
lightDismiss={true}
|
|
173
|
+
keepContent={true}
|
|
174
|
+
onOpen={async (event) => {
|
|
175
|
+
onOpen?.(event);
|
|
176
|
+
|
|
177
|
+
await sleep(100);
|
|
178
|
+
|
|
179
|
+
if (!content) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const target = /** @type {HTMLElement} */ (
|
|
184
|
+
content.querySelector('[tabindex]:not([aria-disabled="true"])')
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (target) {
|
|
188
|
+
target.focus();
|
|
189
|
+
} else {
|
|
190
|
+
content.tabIndex = -1;
|
|
191
|
+
content.focus();
|
|
192
|
+
}
|
|
193
|
+
}}
|
|
194
|
+
>
|
|
195
|
+
{@render contentWrapper()}
|
|
196
|
+
</Modal>
|
|
197
|
+
{/if}
|
|
159
198
|
|
|
160
199
|
<style>.content {
|
|
161
200
|
position: absolute;
|
|
@@ -13,6 +13,10 @@ declare const Popup: import("svelte").Component<import("../../typedefs").ModalPr
|
|
|
13
13
|
* - Whether to open the popup.
|
|
14
14
|
*/
|
|
15
15
|
open?: boolean | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* - Whether the content is hovered.
|
|
18
|
+
*/
|
|
19
|
+
hovered?: boolean | undefined;
|
|
16
20
|
/**
|
|
17
21
|
* - A reference to the anchor element that opens the popup.
|
|
18
22
|
* Typically a `<button>`.
|
|
@@ -27,10 +31,15 @@ declare const Popup: import("svelte").Component<import("../../typedefs").ModalPr
|
|
|
27
31
|
*/
|
|
28
32
|
position?: import("../../typedefs").PopupPosition | undefined;
|
|
29
33
|
/**
|
|
30
|
-
* - The base element of
|
|
31
|
-
*
|
|
34
|
+
* - The base element of {@link position}. If
|
|
35
|
+
* omitted, this will be {@link anchor}.
|
|
32
36
|
*/
|
|
33
37
|
positionBaseElement?: HTMLElement | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* - A reference to a dialog element that is
|
|
40
|
+
* already displayed. This should be provided for a nested popup.
|
|
41
|
+
*/
|
|
42
|
+
parentDialogElement?: HTMLDialogElement | undefined;
|
|
34
43
|
/**
|
|
35
44
|
* - Whether to show the popup at the center of the screen on
|
|
36
45
|
* mobile/tablet and ignore the defined dropdown `position`.
|
|
@@ -48,7 +57,7 @@ declare const Popup: import("svelte").Component<import("../../typedefs").ModalPr
|
|
|
48
57
|
* - Custom `Open` event handler.
|
|
49
58
|
*/
|
|
50
59
|
onOpen?: ((event: CustomEvent) => void) | undefined;
|
|
51
|
-
} & Record<string, any>, {}, "open" | "content">;
|
|
60
|
+
} & Record<string, any>, {}, "open" | "hovered" | "content">;
|
|
52
61
|
type Props = {
|
|
53
62
|
/**
|
|
54
63
|
* - The `class` attribute on the content element.
|
|
@@ -58,6 +67,10 @@ type Props = {
|
|
|
58
67
|
* - Whether to open the popup.
|
|
59
68
|
*/
|
|
60
69
|
open?: boolean | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* - Whether the content is hovered.
|
|
72
|
+
*/
|
|
73
|
+
hovered?: boolean | undefined;
|
|
61
74
|
/**
|
|
62
75
|
* - A reference to the anchor element that opens the popup.
|
|
63
76
|
* Typically a `<button>`.
|
|
@@ -72,10 +85,15 @@ type Props = {
|
|
|
72
85
|
*/
|
|
73
86
|
position?: import("../../typedefs").PopupPosition | undefined;
|
|
74
87
|
/**
|
|
75
|
-
* - The base element of
|
|
76
|
-
*
|
|
88
|
+
* - The base element of {@link position}. If
|
|
89
|
+
* omitted, this will be {@link anchor}.
|
|
77
90
|
*/
|
|
78
91
|
positionBaseElement?: HTMLElement | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* - A reference to a dialog element that is
|
|
94
|
+
* already displayed. This should be provided for a nested popup.
|
|
95
|
+
*/
|
|
96
|
+
parentDialogElement?: HTMLDialogElement | undefined;
|
|
79
97
|
/**
|
|
80
98
|
* - Whether to show the popup at the center of the screen on
|
|
81
99
|
* mobile/tablet and ignore the defined dropdown `position`.
|
|
@@ -89,11 +89,9 @@ class Popup {
|
|
|
89
89
|
? `${Math.round(intersectionRect.left)}px`
|
|
90
90
|
: 'auto';
|
|
91
91
|
|
|
92
|
-
const anchorPopup = /** @type {HTMLElement} */ (this.anchorElement.closest('.popup'));
|
|
93
|
-
|
|
94
92
|
const style = {
|
|
95
93
|
inset: [top, right, bottom, left].join(' '),
|
|
96
|
-
zIndex:
|
|
94
|
+
zIndex: 1000,
|
|
97
95
|
minWidth: `${Math.round(intersectionRect.width)}px`,
|
|
98
96
|
maxWidth: position.endsWith('-left')
|
|
99
97
|
? `${Math.round(rootBounds.width - intersectionRect.left - 8)}px`
|
package/dist/typedefs.d.ts
CHANGED
|
@@ -467,6 +467,18 @@ export type KeyboardEventHandlers = {
|
|
|
467
467
|
onkeypress?: ((event: KeyboardEvent) => void) | undefined;
|
|
468
468
|
};
|
|
469
469
|
export type MouseEventHandlers = {
|
|
470
|
+
/**
|
|
471
|
+
* - `mouseenter` event handler.
|
|
472
|
+
*/
|
|
473
|
+
onmouseenter?: ((event: MouseEvent) => void) | undefined;
|
|
474
|
+
/**
|
|
475
|
+
* - `mouseleave` event handler.
|
|
476
|
+
*/
|
|
477
|
+
onmouseleave?: ((event: MouseEvent) => void) | undefined;
|
|
478
|
+
/**
|
|
479
|
+
* - `mouseover` event handler.
|
|
480
|
+
*/
|
|
481
|
+
onmouseover?: ((event: MouseEvent) => void) | undefined;
|
|
470
482
|
/**
|
|
471
483
|
* - `mousedown` event handler.
|
|
472
484
|
*/
|
package/dist/typedefs.js
CHANGED
|
@@ -164,6 +164,9 @@
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* @typedef {object} MouseEventHandlers
|
|
167
|
+
* @property {(event: MouseEvent) => void} [onmouseenter] - `mouseenter` event handler.
|
|
168
|
+
* @property {(event: MouseEvent) => void} [onmouseleave] - `mouseleave` event handler.
|
|
169
|
+
* @property {(event: MouseEvent) => void} [onmouseover] - `mouseover` event handler.
|
|
167
170
|
* @property {(event: MouseEvent) => void} [onmousedown] - `mousedown` event handler.
|
|
168
171
|
* @property {(event: MouseEvent) => void} [onmouseup] - `mouseup` event handler.
|
|
169
172
|
* @property {(event: MouseEvent) => void} [onclick] - `click` event handler.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltia/ui",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -46,15 +46,15 @@
|
|
|
46
46
|
"svelte": "^5.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@playwright/test": "^1.
|
|
49
|
+
"@playwright/test": "^1.51.0",
|
|
50
50
|
"@sveltejs/adapter-auto": "^4.0.0",
|
|
51
|
-
"@sveltejs/kit": "^2.
|
|
51
|
+
"@sveltejs/kit": "^2.19.0",
|
|
52
52
|
"@sveltejs/package": "^2.3.10",
|
|
53
53
|
"@sveltejs/vite-plugin-svelte": "5.0.3",
|
|
54
54
|
"cspell": "^8.17.5",
|
|
55
55
|
"eslint": "^8.57.1",
|
|
56
56
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
57
|
-
"eslint-config-prettier": "^10.
|
|
57
|
+
"eslint-config-prettier": "^10.1.1",
|
|
58
58
|
"eslint-plugin-import": "^2.31.0",
|
|
59
59
|
"eslint-plugin-jsdoc": "^50.6.3",
|
|
60
60
|
"eslint-plugin-svelte": "^2.46.1",
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"stylelint": "^16.15.0",
|
|
67
67
|
"stylelint-config-recommended-scss": "^14.1.0",
|
|
68
68
|
"stylelint-scss": "^6.11.1",
|
|
69
|
-
"svelte": "5.22.
|
|
70
|
-
"svelte-check": "^4.1.
|
|
69
|
+
"svelte": "5.22.6",
|
|
70
|
+
"svelte-check": "^4.1.5",
|
|
71
71
|
"svelte-i18n": "^4.0.1",
|
|
72
72
|
"svelte-preprocess": "^6.0.3",
|
|
73
73
|
"tslib": "^2.8.1",
|
|
74
|
-
"vite": "^6.2.
|
|
75
|
-
"vitest": "^3.0.
|
|
74
|
+
"vite": "^6.2.1",
|
|
75
|
+
"vitest": "^3.0.8"
|
|
76
76
|
},
|
|
77
77
|
"exports": {
|
|
78
78
|
".": {
|