@sveltia/ui 0.2.4 → 0.3.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/package/components/{core → button}/button.svelte +2 -1
- package/package/components/{core → button}/button.svelte.d.ts +6 -5
- package/package/components/{composite → button}/select-button-group.svelte +8 -5
- package/package/components/{composite → button}/select-button-group.svelte.d.ts +6 -6
- package/package/components/{core → button}/select-button.svelte +2 -2
- package/package/components/{core → button}/select-button.svelte.d.ts +2 -2
- package/package/components/{composite → calendar}/calendar.svelte +9 -5
- package/package/components/{composite → calendar}/calendar.svelte.d.ts +1 -0
- package/package/components/{composite → checkbox}/checkbox-group.svelte +3 -2
- package/package/components/{composite → checkbox}/checkbox-group.svelte.d.ts +2 -2
- package/package/components/{core → checkbox}/checkbox.svelte +38 -17
- package/package/components/{core → checkbox}/checkbox.svelte.d.ts +7 -3
- package/package/components/{core → dialog}/dialog.svelte +5 -4
- package/package/components/{core → dialog}/dialog.svelte.d.ts +2 -1
- package/package/components/{composite → disclosure}/disclosure.svelte +5 -4
- package/package/components/{composite → disclosure}/disclosure.svelte.d.ts +2 -1
- package/package/components/{core/separator.svelte → divider/divider.svelte} +5 -4
- package/package/components/divider/divider.svelte.d.ts +29 -0
- package/package/components/{core → divider}/spacer.svelte +4 -0
- package/package/components/{core → divider}/spacer.svelte.d.ts +1 -0
- package/package/components/{core → drawer}/drawer.svelte +5 -4
- package/package/components/{core → drawer}/drawer.svelte.d.ts +2 -1
- package/package/components/{core → icon}/icon.svelte +5 -0
- package/package/components/{core → icon}/icon.svelte.d.ts +6 -2
- package/package/components/listbox/listbox.svelte +74 -0
- package/package/components/{composite → listbox}/listbox.svelte.d.ts +3 -1
- package/package/components/listbox/option-group.svelte +47 -0
- package/package/components/listbox/option-group.svelte.d.ts +38 -0
- package/package/components/{core → listbox}/option.svelte +34 -2
- package/package/components/{core → listbox}/option.svelte.d.ts +7 -3
- package/package/components/{core → menu}/menu-button.svelte +3 -17
- package/package/components/{core → menu}/menu-button.svelte.d.ts +4 -1
- package/package/components/{core → menu}/menu-item-checkbox.svelte +1 -0
- package/package/components/{core → menu}/menu-item-checkbox.svelte.d.ts +4 -1
- package/package/components/{composite → menu}/menu-item-group.svelte +5 -1
- package/package/components/{composite → menu}/menu-item-group.svelte.d.ts +1 -0
- package/package/components/{core → menu}/menu-item-radio.svelte +2 -0
- package/package/components/{core → menu}/menu-item-radio.svelte.d.ts +5 -1
- package/package/components/{core → menu}/menu-item.svelte +6 -6
- package/package/components/{core → menu}/menu-item.svelte.d.ts +4 -1
- package/package/components/{composite → menu}/menu.svelte +3 -2
- package/package/components/{composite → menu}/menu.svelte.d.ts +2 -1
- package/package/components/{composite/radio-button-group.svelte → radio/radio-group.svelte} +15 -10
- package/package/components/radio/radio-group.svelte.d.ts +40 -0
- package/package/components/{core/radio-button.svelte → radio/radio.svelte} +45 -18
- package/package/components/radio/radio.svelte.d.ts +43 -0
- package/package/components/{composite → select}/combobox.svelte +7 -6
- package/package/components/{composite → select}/combobox.svelte.d.ts +4 -3
- package/package/components/{composite → select}/select.svelte +3 -1
- package/package/components/{composite → select}/select.svelte.d.ts +7 -3
- package/package/components/{core → slider}/slider.svelte +82 -57
- package/package/components/{core → slider}/slider.svelte.d.ts +12 -10
- package/package/components/{core → switch}/switch.svelte +36 -19
- package/package/components/{core → switch}/switch.svelte.d.ts +4 -3
- package/package/components/table/table-body.svelte +23 -0
- package/package/components/table/table-body.svelte.d.ts +34 -0
- package/package/components/table/table-cell.svelte +23 -0
- package/package/components/table/table-cell.svelte.d.ts +34 -0
- package/package/components/table/table-col-header.svelte +23 -0
- package/package/components/table/table-col-header.svelte.d.ts +34 -0
- package/package/components/table/table-foot.svelte +23 -0
- package/package/components/table/table-foot.svelte.d.ts +34 -0
- package/package/components/table/table-head.svelte +23 -0
- package/package/components/table/table-head.svelte.d.ts +34 -0
- package/package/components/table/table-row-header.svelte +23 -0
- package/package/components/table/table-row-header.svelte.d.ts +34 -0
- package/package/components/table/table-row.svelte +23 -0
- package/package/components/table/table-row.svelte.d.ts +38 -0
- package/package/components/table/table.svelte +44 -0
- package/package/components/table/table.svelte.d.ts +36 -0
- package/package/components/{composite → tabs}/tab-list.svelte +3 -2
- package/package/components/{composite → tabs}/tab-list.svelte.d.ts +7 -6
- package/package/components/{core → tabs}/tab-panel.svelte +2 -1
- package/package/components/{core → tabs}/tab-panel.svelte.d.ts +2 -1
- package/package/components/{core → tabs}/tab.svelte +3 -2
- package/package/components/{core → tabs}/tab.svelte.d.ts +2 -1
- package/package/components/{editor/markdown.svelte → text-field/markdown-editor.svelte} +10 -6
- package/package/components/text-field/markdown-editor.svelte.d.ts +26 -0
- package/package/components/{core → text-field}/number-input.svelte +22 -12
- package/package/components/{core → text-field}/number-input.svelte.d.ts +7 -3
- package/package/components/{core → text-field}/password-input.svelte +5 -2
- package/package/components/{core → text-field}/password-input.svelte.d.ts +8 -3
- package/package/components/{core → text-field}/search-bar.svelte +5 -2
- package/package/components/{core → text-field}/search-bar.svelte.d.ts +8 -3
- package/package/components/{core → text-field}/text-area.svelte +2 -0
- package/package/components/{core → text-field}/text-area.svelte.d.ts +9 -5
- package/package/components/{core → text-field}/text-input.svelte +3 -1
- package/package/components/{core → text-field}/text-input.svelte.d.ts +11 -7
- package/package/components/{core → toolbar}/toolbar.svelte +2 -1
- package/package/components/{core → toolbar}/toolbar.svelte.d.ts +3 -2
- package/package/components/util/app-shell.svelte +10 -36
- package/package/components/util/group.js +305 -0
- package/package/components/{core → util}/group.svelte +5 -11
- package/package/components/{core → util}/group.svelte.d.ts +4 -2
- package/package/components/util/popup.d.ts +30 -0
- package/package/components/{helpers → util}/popup.js +36 -26
- package/package/components/util/popup.svelte +14 -5
- package/package/components/util/{misc.d.ts → util.d.ts} +1 -0
- package/package/components/util/{misc.js → util.js} +15 -0
- package/package/index.d.ts +46 -41
- package/package/index.js +48 -83
- package/package/styles/core.scss +5 -34
- package/package/styles/variables.scss +5 -4
- package/package.json +362 -328
- package/package/components/composite/grid.svelte +0 -24
- package/package/components/composite/grid.svelte.d.ts +0 -31
- package/package/components/composite/listbox.svelte +0 -63
- package/package/components/composite/radio-button-group.svelte.d.ts +0 -36
- package/package/components/core/grid-cell.svelte +0 -13
- package/package/components/core/grid-cell.svelte.d.ts +0 -29
- package/package/components/core/radio-button.svelte.d.ts +0 -37
- package/package/components/core/row-group.svelte +0 -13
- package/package/components/core/row-group.svelte.d.ts +0 -29
- package/package/components/core/row.svelte +0 -13
- package/package/components/core/row.svelte.d.ts +0 -33
- package/package/components/core/separator.svelte.d.ts +0 -26
- package/package/components/editor/markdown.svelte.d.ts +0 -25
- package/package/components/helpers/group.js +0 -251
- package/package/components/helpers/popup.d.ts +0 -30
- package/package/components/helpers/util.d.ts +0 -1
- package/package/components/helpers/util.js +0 -14
- /package/package/components/{helpers → util}/group.d.ts +0 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { getRandomId, sleep } from './util';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {{ [role: string]: {
|
|
5
|
+
* orientation: string,
|
|
6
|
+
* childRoles: string[],
|
|
7
|
+
* childSelectedAttr: string,
|
|
8
|
+
* focusChild: boolean
|
|
9
|
+
* } }}
|
|
10
|
+
*/
|
|
11
|
+
const config = {
|
|
12
|
+
grid: {
|
|
13
|
+
orientation: 'vertical',
|
|
14
|
+
childRoles: ['row'],
|
|
15
|
+
childSelectedAttr: 'aria-selected',
|
|
16
|
+
focusChild: true,
|
|
17
|
+
},
|
|
18
|
+
listbox: {
|
|
19
|
+
orientation: 'vertical',
|
|
20
|
+
childRoles: ['option'],
|
|
21
|
+
childSelectedAttr: 'aria-selected',
|
|
22
|
+
focusChild: false,
|
|
23
|
+
},
|
|
24
|
+
menu: {
|
|
25
|
+
orientation: 'vertical',
|
|
26
|
+
childRoles: ['menuitem', 'menuitemcheckbox', 'menuitemradio'],
|
|
27
|
+
childSelectedAttr: 'aria-checked',
|
|
28
|
+
focusChild: true,
|
|
29
|
+
},
|
|
30
|
+
menubar: {
|
|
31
|
+
orientation: 'horizontal',
|
|
32
|
+
childRoles: ['menuitem', 'menuitemcheckbox', 'menuitemradio'],
|
|
33
|
+
childSelectedAttr: 'aria-checked',
|
|
34
|
+
focusChild: true,
|
|
35
|
+
},
|
|
36
|
+
radiogroup: {
|
|
37
|
+
orientation: 'horizontal',
|
|
38
|
+
childRoles: ['radio'],
|
|
39
|
+
childSelectedAttr: 'aria-checked',
|
|
40
|
+
focusChild: true,
|
|
41
|
+
},
|
|
42
|
+
tablist: {
|
|
43
|
+
orientation: 'horizontal',
|
|
44
|
+
childRoles: ['tab'],
|
|
45
|
+
childSelectedAttr: 'aria-selected',
|
|
46
|
+
focusChild: true,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Implement keyboard and mouse interactions for a grouping composite widget.
|
|
52
|
+
*/
|
|
53
|
+
class Group {
|
|
54
|
+
/**
|
|
55
|
+
* Initialize a new `Group` instance.
|
|
56
|
+
* @param {HTMLElement} parent Parent element.
|
|
57
|
+
* @todo Check for added elements probably with `MutationObserver`.
|
|
58
|
+
*/
|
|
59
|
+
constructor(parent) {
|
|
60
|
+
this.parent = parent;
|
|
61
|
+
this.role = parent.getAttribute('role');
|
|
62
|
+
this.grid = this.role === 'listbox' && parent.matches('.grid');
|
|
63
|
+
this.multi = this.parent.getAttribute('aria-multiselectable') === 'true';
|
|
64
|
+
this.id = getRandomId(this.role);
|
|
65
|
+
this.parentGroupSelector = `[role="group"], [role="${this.role}"]`;
|
|
66
|
+
|
|
67
|
+
const { orientation, childRoles, childSelectedAttr, focusChild } = config[this.role];
|
|
68
|
+
|
|
69
|
+
this.orientation = this.grid
|
|
70
|
+
? 'horizontal'
|
|
71
|
+
: this.parent.getAttribute('aria-orientation') || orientation;
|
|
72
|
+
this.childRoles = childRoles;
|
|
73
|
+
this.childSelectedAttr = childSelectedAttr;
|
|
74
|
+
this.focusChild = focusChild;
|
|
75
|
+
|
|
76
|
+
const { allMembers } = this;
|
|
77
|
+
|
|
78
|
+
const hasSelected = allMembers.some((element) =>
|
|
79
|
+
element.matches(`[${childSelectedAttr}="true"]`),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
allMembers.forEach((element, index) => {
|
|
83
|
+
const isSelected = element.matches(`[${childSelectedAttr}="true"]`);
|
|
84
|
+
const controls = document.querySelector(`#${element.getAttribute('aria-controls')}`);
|
|
85
|
+
|
|
86
|
+
element.id ||= `${this.id}-item-${index}`;
|
|
87
|
+
element.tabIndex ||= isSelected || (!hasSelected && index === 0) ? 0 : -1;
|
|
88
|
+
element.setAttribute(this.childSelectedAttr, String(isSelected));
|
|
89
|
+
controls?.setAttribute('aria-labelledby', element.id);
|
|
90
|
+
controls?.setAttribute('aria-hidden', String(!isSelected));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
parent.addEventListener('click', (event) => {
|
|
94
|
+
if (/** @type {HTMLElement} */ (event.target).matches(this.selector)) {
|
|
95
|
+
this.onClick(event);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
parent.addEventListener('keydown', (event) => {
|
|
100
|
+
this.onKeyDown(event);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @type {string} */
|
|
105
|
+
get selector() {
|
|
106
|
+
return this.childRoles.map((role) => `[role="${role}"]`).join(',');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** @type {HTMLElement[]} */
|
|
110
|
+
get allMembers() {
|
|
111
|
+
// @ts-ignore
|
|
112
|
+
return [...this.parent.querySelectorAll(this.selector)];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @type {HTMLElement[]} */
|
|
116
|
+
get activeMembers() {
|
|
117
|
+
return this.allMembers.filter((element) => !element.matches('[aria-disabled="true"]'));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Select (and move focus to) the given target.
|
|
122
|
+
* @param {(MouseEvent | KeyboardEvent)} event Triggered event.
|
|
123
|
+
* @param {HTMLElement} newTarget Target element.
|
|
124
|
+
*/
|
|
125
|
+
selectTarget(event, newTarget) {
|
|
126
|
+
const targetParentGroup = newTarget.closest(this.parentGroupSelector);
|
|
127
|
+
|
|
128
|
+
this.activeMembers.forEach((element) => {
|
|
129
|
+
const isTarget = element === newTarget;
|
|
130
|
+
const isSelected = element.matches('[aria-selected="true"]');
|
|
131
|
+
const controls = element.getAttribute('aria-controls');
|
|
132
|
+
|
|
133
|
+
if (this.multi && isTarget && event.type === 'click') {
|
|
134
|
+
element.setAttribute(this.childSelectedAttr, String(!isSelected));
|
|
135
|
+
element.dispatchEvent(new CustomEvent(isSelected ? 'unselect' : 'select'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
(element.matches('[role="menuitemradio"]') &&
|
|
140
|
+
element.closest(this.parentGroupSelector) === targetParentGroup) ||
|
|
141
|
+
!this.multi
|
|
142
|
+
) {
|
|
143
|
+
element.setAttribute(this.childSelectedAttr, String(isTarget));
|
|
144
|
+
element.dispatchEvent(new CustomEvent(isTarget ? 'select' : 'unselect'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (this.focusChild) {
|
|
148
|
+
element.tabIndex = isTarget ? 0 : -1;
|
|
149
|
+
|
|
150
|
+
if (isTarget) {
|
|
151
|
+
element.focus();
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
element.classList.toggle('focused', isTarget);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (controls) {
|
|
158
|
+
document.getElementById(controls)?.setAttribute('aria-hidden', String(!isTarget));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
this.parent.dispatchEvent(
|
|
163
|
+
new CustomEvent('select', {
|
|
164
|
+
detail: {
|
|
165
|
+
// @ts-ignore
|
|
166
|
+
value: newTarget.value,
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
name: newTarget.name,
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle the `click` event on the widget.
|
|
176
|
+
* @param {MouseEvent} event `click` event.
|
|
177
|
+
*/
|
|
178
|
+
onClick(event) {
|
|
179
|
+
// eslint-disable-next-line prefer-destructuring
|
|
180
|
+
const target = /** @type {HTMLElement} */ (event.target);
|
|
181
|
+
const newTarget = target.matches(this.selector) ? target : undefined;
|
|
182
|
+
|
|
183
|
+
if (!newTarget || event.button !== 0) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.selectTarget(event, newTarget);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Handle the `keydown` event on the widget.
|
|
192
|
+
* @param {KeyboardEvent} event `keydown` event.
|
|
193
|
+
*/
|
|
194
|
+
onKeyDown(event) {
|
|
195
|
+
const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
|
|
196
|
+
const hasModifier = shiftKey || altKey || ctrlKey || metaKey;
|
|
197
|
+
|
|
198
|
+
if (hasModifier) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// eslint-disable-next-line prefer-destructuring
|
|
203
|
+
const target = /** @type {HTMLElement} */ (event.target);
|
|
204
|
+
const { allMembers, activeMembers } = this;
|
|
205
|
+
|
|
206
|
+
const currentTarget = (() => {
|
|
207
|
+
if (!this.focusChild) {
|
|
208
|
+
return activeMembers.find((member) => member.matches('.focused')) || activeMembers[0];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (target.matches(this.selector)) {
|
|
212
|
+
return target;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return undefined;
|
|
216
|
+
})();
|
|
217
|
+
|
|
218
|
+
if (!currentTarget) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (['Enter', ' ', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(key)) {
|
|
223
|
+
event.preventDefault();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (['Enter', ' '].includes(key)) {
|
|
227
|
+
currentTarget.click();
|
|
228
|
+
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let index;
|
|
233
|
+
let newTarget;
|
|
234
|
+
|
|
235
|
+
if (this.grid) {
|
|
236
|
+
const colCount = Math.floor(this.parent.clientWidth / currentTarget.clientWidth);
|
|
237
|
+
|
|
238
|
+
index = allMembers.indexOf(currentTarget);
|
|
239
|
+
|
|
240
|
+
if (key === 'ArrowUp' && index > 0) {
|
|
241
|
+
newTarget = allMembers[index - colCount];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (key === 'ArrowDown' && index < allMembers.length - 1) {
|
|
245
|
+
newTarget = allMembers[index + colCount];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (key === 'ArrowLeft' && index > 0) {
|
|
249
|
+
newTarget = allMembers[index - 1];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (key === 'ArrowRight' && index < allMembers.length - 1) {
|
|
253
|
+
newTarget = allMembers[index + 1];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (newTarget?.getAttribute('aria-disabled') === 'true') {
|
|
257
|
+
newTarget = undefined;
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
index = activeMembers.indexOf(currentTarget);
|
|
261
|
+
|
|
262
|
+
if (key === (this.orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp')) {
|
|
263
|
+
if (index > 0) {
|
|
264
|
+
// Previous member
|
|
265
|
+
newTarget = activeMembers[index - 1];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (index === 0) {
|
|
269
|
+
// Last member
|
|
270
|
+
newTarget = activeMembers[activeMembers.length - 1];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (key === (this.orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown')) {
|
|
275
|
+
if (index < activeMembers.length - 1) {
|
|
276
|
+
// Next member
|
|
277
|
+
newTarget = activeMembers[index + 1];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (index === activeMembers.length - 1) {
|
|
281
|
+
// First member
|
|
282
|
+
[newTarget] = activeMembers;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (newTarget && newTarget !== currentTarget) {
|
|
288
|
+
this.selectTarget(event, newTarget);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Activate a new group.
|
|
295
|
+
* @param {...any} args Arguments.
|
|
296
|
+
*/
|
|
297
|
+
export const activateGroup = (...args) => {
|
|
298
|
+
(async () => {
|
|
299
|
+
// Wait a bit before the relevant components, including the `aria-controls` target are mounted
|
|
300
|
+
await sleep(100);
|
|
301
|
+
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
return new Group(...args);
|
|
304
|
+
})();
|
|
305
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
A generic group layout.
|
|
4
|
+
@see https://w3c.github.io/aria/#group
|
|
5
|
+
-->
|
|
1
6
|
<script>
|
|
2
|
-
import { getRandomId } from '../helpers/util';
|
|
3
|
-
|
|
4
7
|
/**
|
|
5
8
|
* CSS class name on the button.
|
|
6
9
|
* @type {string}
|
|
@@ -9,23 +12,14 @@
|
|
|
9
12
|
|
|
10
13
|
export { className as class };
|
|
11
14
|
|
|
12
|
-
export let title = '';
|
|
13
|
-
|
|
14
15
|
export let ariaLabel = '';
|
|
15
|
-
|
|
16
|
-
const id = getRandomId('group');
|
|
17
16
|
</script>
|
|
18
17
|
|
|
19
18
|
<div
|
|
20
19
|
class="sui group {className}"
|
|
21
20
|
role="group"
|
|
22
|
-
{id}
|
|
23
21
|
aria-label={ariaLabel || undefined}
|
|
24
|
-
aria-labelledby={title ? '{id}-title' : undefined}
|
|
25
22
|
{...$$restProps}
|
|
26
23
|
>
|
|
27
|
-
{#if title}
|
|
28
|
-
<div class="title" id="{id}-title">{title}</div>
|
|
29
|
-
{/if}
|
|
30
24
|
<slot />
|
|
31
25
|
</div>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/** @typedef {typeof __propDef.props} GroupProps */
|
|
2
2
|
/** @typedef {typeof __propDef.events} GroupEvents */
|
|
3
3
|
/** @typedef {typeof __propDef.slots} GroupSlots */
|
|
4
|
+
/**
|
|
5
|
+
* A generic group layout.
|
|
6
|
+
* @see https://w3c.github.io/aria/#group
|
|
7
|
+
*/
|
|
4
8
|
export default class Group extends SvelteComponentTyped<{
|
|
5
9
|
[x: string]: any;
|
|
6
|
-
title?: string;
|
|
7
10
|
class?: string;
|
|
8
11
|
ariaLabel?: string;
|
|
9
12
|
}, {
|
|
@@ -19,7 +22,6 @@ import { SvelteComponentTyped } from "svelte";
|
|
|
19
22
|
declare const __propDef: {
|
|
20
23
|
props: {
|
|
21
24
|
[x: string]: any;
|
|
22
|
-
title?: string;
|
|
23
25
|
class?: string;
|
|
24
26
|
ariaLabel?: string;
|
|
25
27
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function activatePopup(...args: any[]): Popup;
|
|
2
|
+
/**
|
|
3
|
+
* Implement the popup handler.
|
|
4
|
+
*/
|
|
5
|
+
declare class Popup {
|
|
6
|
+
/**
|
|
7
|
+
* Initialize a new `Popup` instance.
|
|
8
|
+
* @param {HTMLButtonElement} anchorElement `<button>` element that triggers the popup.
|
|
9
|
+
* @param {HTMLDialogElement} popupElement `<dialog>` element to be used for the popup.
|
|
10
|
+
* @param {PopupPosition} position Where to show the popup content.
|
|
11
|
+
*/
|
|
12
|
+
constructor(anchorElement: HTMLButtonElement, popupElement: HTMLDialogElement, position: PopupPosition);
|
|
13
|
+
open: import("svelte/store").Writable<boolean>;
|
|
14
|
+
style: import("svelte/store").Writable<{
|
|
15
|
+
inset: any;
|
|
16
|
+
zIndex: any;
|
|
17
|
+
width: any;
|
|
18
|
+
height: any;
|
|
19
|
+
}>;
|
|
20
|
+
observer: IntersectionObserver;
|
|
21
|
+
anchorElement: HTMLButtonElement;
|
|
22
|
+
popupElement: HTMLDialogElement;
|
|
23
|
+
position: PopupPosition;
|
|
24
|
+
id: string;
|
|
25
|
+
/**
|
|
26
|
+
* Continue checking the position in case the window or parent element resizes.
|
|
27
|
+
*/
|
|
28
|
+
checkPosition(): void;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -4,7 +4,7 @@ import { get, writable } from 'svelte/store';
|
|
|
4
4
|
import { getRandomId } from './util';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Implement the popup handler.
|
|
8
8
|
*/
|
|
9
9
|
class Popup {
|
|
10
10
|
open = writable(false);
|
|
@@ -76,12 +76,14 @@ class Popup {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
80
|
-
* @param {
|
|
81
|
-
* @param {
|
|
82
|
-
* @param {PopupPosition} position
|
|
79
|
+
* Initialize a new `Popup` instance.
|
|
80
|
+
* @param {HTMLButtonElement} anchorElement `<button>` element that triggers the popup.
|
|
81
|
+
* @param {HTMLDialogElement} popupElement `<dialog>` element to be used for the popup.
|
|
82
|
+
* @param {PopupPosition} position Where to show the popup content.
|
|
83
83
|
*/
|
|
84
84
|
constructor(anchorElement, popupElement, position) {
|
|
85
|
+
console.info({ anchorElement, popupElement, position });
|
|
86
|
+
|
|
85
87
|
this.anchorElement = anchorElement;
|
|
86
88
|
this.popupElement = popupElement; // = backdrop
|
|
87
89
|
this.position = position;
|
|
@@ -97,37 +99,44 @@ class Popup {
|
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
this.anchorElement.addEventListener('keydown', (event) => {
|
|
102
|
+
console.info(event);
|
|
103
|
+
|
|
100
104
|
const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
|
|
105
|
+
const hasModifier = shiftKey || altKey || ctrlKey || metaKey;
|
|
101
106
|
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
+
if (['Enter', ' '].includes(key) && !hasModifier) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.stopPropagation();
|
|
110
|
+
this.open.set(!get(this.open));
|
|
107
111
|
}
|
|
108
112
|
});
|
|
109
113
|
|
|
114
|
+
// Close the popup when the backdrop, a menu item or an option is clicked
|
|
110
115
|
this.popupElement.addEventListener('click', (event) => {
|
|
111
|
-
|
|
116
|
+
event.stopPropagation();
|
|
117
|
+
|
|
118
|
+
// eslint-disable-next-line prefer-destructuring
|
|
119
|
+
const target = /** @type {HTMLElement} */ (event.target);
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
get(this.open) &&
|
|
123
|
+
(target === this.popupElement || target.matches('[role^="menuitem"], [role="option"]'))
|
|
124
|
+
) {
|
|
112
125
|
this.open.set(false);
|
|
113
126
|
}
|
|
114
127
|
});
|
|
115
128
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
) {
|
|
128
|
-
this.open.set(false);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
129
|
+
this.popupElement.addEventListener('keydown', (event) => {
|
|
130
|
+
console.info(event);
|
|
131
|
+
|
|
132
|
+
const { key, ctrlKey, metaKey, shiftKey, altKey } = event;
|
|
133
|
+
const hasModifier = shiftKey || altKey || ctrlKey || metaKey;
|
|
134
|
+
|
|
135
|
+
if (key === 'Escape' && !hasModifier) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
event.stopPropagation();
|
|
138
|
+
this.open.set(false);
|
|
139
|
+
}
|
|
131
140
|
});
|
|
132
141
|
|
|
133
142
|
this.open.subscribe((open) => {
|
|
@@ -135,6 +144,7 @@ class Popup {
|
|
|
135
144
|
this.checkPosition();
|
|
136
145
|
} else if (this.anchorElement.getAttribute('aria-expanded') === 'true') {
|
|
137
146
|
this.anchorElement.focus();
|
|
147
|
+
this.anchorElement.removeAttribute('aria-controls');
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
this.anchorElement.setAttribute('aria-expanded', String(open));
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
<script>
|
|
8
8
|
import { onMount } from 'svelte';
|
|
9
9
|
import { writable } from 'svelte/store';
|
|
10
|
-
import { activatePopup } from '
|
|
10
|
+
import { activatePopup } from './popup';
|
|
11
|
+
import { sleep } from './util';
|
|
11
12
|
|
|
12
13
|
/** @type {HTMLElement?} */
|
|
13
14
|
export let anchor = undefined;
|
|
@@ -58,12 +59,20 @@
|
|
|
58
59
|
showContent = true;
|
|
59
60
|
dialog.showModal();
|
|
60
61
|
|
|
61
|
-
window.requestAnimationFrame(() => {
|
|
62
|
+
window.requestAnimationFrame(async () => {
|
|
62
63
|
showDialog = true;
|
|
64
|
+
await sleep(100);
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const target = /** @type {HTMLElement} */ (
|
|
67
|
+
content.querySelector('[tabindex]:not([aria-disabled="true"])')
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (target) {
|
|
71
|
+
target.focus();
|
|
72
|
+
} else {
|
|
73
|
+
content.tabIndex = -1;
|
|
74
|
+
content.focus();
|
|
75
|
+
}
|
|
67
76
|
});
|
|
68
77
|
};
|
|
69
78
|
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get a random ID that can be used for elements.
|
|
3
|
+
* @param {string} [prefix] Prefix to be added to the ID, e.g. `popup`.
|
|
4
|
+
* @param {number} [length] Number of characters to be used in the ID.
|
|
5
|
+
* @returns {string} Generated ID.
|
|
6
|
+
*/
|
|
7
|
+
export const getRandomId = (prefix = '', length = 7) =>
|
|
8
|
+
[
|
|
9
|
+
prefix,
|
|
10
|
+
new Array(length)
|
|
11
|
+
.fill()
|
|
12
|
+
.map(() => '0123456789abcdef'[Math.floor(Math.random() * 12)])
|
|
13
|
+
.join(''),
|
|
14
|
+
].join('-');
|
|
15
|
+
|
|
1
16
|
/**
|
|
2
17
|
* Check if the given input is a simple object.
|
|
3
18
|
* @param {*} input Input, probably an object.
|
package/package/index.d.ts
CHANGED
|
@@ -2,45 +2,50 @@ export function initLocales({ fallbackLocale, initialLocale }?: {
|
|
|
2
2
|
fallbackLocale?: string;
|
|
3
3
|
initialLocale?: string;
|
|
4
4
|
}): void;
|
|
5
|
-
export { default as
|
|
6
|
-
export { default as
|
|
7
|
-
export { default as
|
|
8
|
-
export { default as
|
|
9
|
-
export { default as
|
|
10
|
-
export { default as
|
|
11
|
-
export { default as
|
|
12
|
-
export { default as
|
|
13
|
-
export { default as
|
|
14
|
-
export { default as
|
|
15
|
-
export { default as
|
|
16
|
-
export { default as
|
|
17
|
-
export { default as
|
|
18
|
-
export { default as
|
|
19
|
-
export { default as
|
|
20
|
-
export { default as
|
|
21
|
-
export { default as
|
|
22
|
-
export { default as
|
|
23
|
-
export { default as
|
|
24
|
-
export { default as
|
|
25
|
-
export { default as
|
|
26
|
-
export { default as
|
|
27
|
-
export { default as
|
|
28
|
-
export { default as
|
|
29
|
-
export { default as
|
|
30
|
-
export { default as
|
|
31
|
-
export { default as
|
|
32
|
-
export { default as
|
|
33
|
-
export { default as
|
|
34
|
-
export { default as
|
|
35
|
-
export { default as
|
|
36
|
-
export { default as
|
|
37
|
-
export { default as
|
|
38
|
-
export { default as
|
|
39
|
-
export { default as
|
|
40
|
-
export { default as
|
|
41
|
-
export { default as
|
|
42
|
-
export { default as
|
|
43
|
-
export { default as
|
|
44
|
-
export { default as
|
|
45
|
-
export { default as
|
|
5
|
+
export { default as Button } from "./components/button/button.svelte";
|
|
6
|
+
export { default as SelectButtonGroup } from "./components/button/select-button-group.svelte";
|
|
7
|
+
export { default as SelectButton } from "./components/button/select-button.svelte";
|
|
8
|
+
export { default as Calendar } from "./components/calendar/calendar.svelte";
|
|
9
|
+
export { default as CheckboxGroup } from "./components/checkbox/checkbox-group.svelte";
|
|
10
|
+
export { default as Checkbox } from "./components/checkbox/checkbox.svelte";
|
|
11
|
+
export { default as Dialog } from "./components/dialog/dialog.svelte";
|
|
12
|
+
export { default as Disclosure } from "./components/disclosure/disclosure.svelte";
|
|
13
|
+
export { default as Divider } from "./components/divider/divider.svelte";
|
|
14
|
+
export { default as Spacer } from "./components/divider/spacer.svelte";
|
|
15
|
+
export { default as Drawer } from "./components/drawer/drawer.svelte";
|
|
16
|
+
export { default as Icon } from "./components/icon/icon.svelte";
|
|
17
|
+
export { default as Listbox } from "./components/listbox/listbox.svelte";
|
|
18
|
+
export { default as OptionGroup } from "./components/listbox/option-group.svelte";
|
|
19
|
+
export { default as Option } from "./components/listbox/option.svelte";
|
|
20
|
+
export { default as MenuButton } from "./components/menu/menu-button.svelte";
|
|
21
|
+
export { default as MenuItemCheckbox } from "./components/menu/menu-item-checkbox.svelte";
|
|
22
|
+
export { default as MenuItemGroup } from "./components/menu/menu-item-group.svelte";
|
|
23
|
+
export { default as MenuItemRadio } from "./components/menu/menu-item-radio.svelte";
|
|
24
|
+
export { default as MenuItem } from "./components/menu/menu-item.svelte";
|
|
25
|
+
export { default as Menu } from "./components/menu/menu.svelte";
|
|
26
|
+
export { default as RadioGroup } from "./components/radio/radio-group.svelte";
|
|
27
|
+
export { default as Radio } from "./components/radio/radio.svelte";
|
|
28
|
+
export { default as Combobox } from "./components/select/combobox.svelte";
|
|
29
|
+
export { default as Select } from "./components/select/select.svelte";
|
|
30
|
+
export { default as Slider } from "./components/slider/slider.svelte";
|
|
31
|
+
export { default as Switch } from "./components/switch/switch.svelte";
|
|
32
|
+
export { default as TableBody } from "./components/table/table-body.svelte";
|
|
33
|
+
export { default as TableCell } from "./components/table/table-cell.svelte";
|
|
34
|
+
export { default as TableColHeader } from "./components/table/table-col-header.svelte";
|
|
35
|
+
export { default as TableFoot } from "./components/table/table-foot.svelte";
|
|
36
|
+
export { default as TableHead } from "./components/table/table-head.svelte";
|
|
37
|
+
export { default as TableRowHeader } from "./components/table/table-row-header.svelte";
|
|
38
|
+
export { default as TableRow } from "./components/table/table-row.svelte";
|
|
39
|
+
export { default as Table } from "./components/table/table.svelte";
|
|
40
|
+
export { default as TabList } from "./components/tabs/tab-list.svelte";
|
|
41
|
+
export { default as TabPanel } from "./components/tabs/tab-panel.svelte";
|
|
42
|
+
export { default as Tab } from "./components/tabs/tab.svelte";
|
|
43
|
+
export { default as MarkdownEditor } from "./components/text-field/markdown-editor.svelte";
|
|
44
|
+
export { default as NumberInput } from "./components/text-field/number-input.svelte";
|
|
45
|
+
export { default as PasswordInput } from "./components/text-field/password-input.svelte";
|
|
46
|
+
export { default as SearchBar } from "./components/text-field/search-bar.svelte";
|
|
47
|
+
export { default as TextArea } from "./components/text-field/text-area.svelte";
|
|
48
|
+
export { default as TextInput } from "./components/text-field/text-input.svelte";
|
|
49
|
+
export { default as Toolbar } from "./components/toolbar/toolbar.svelte";
|
|
46
50
|
export { default as AppShell } from "./components/util/app-shell.svelte";
|
|
51
|
+
export { default as Group } from "./components/util/group.svelte";
|