@ibobbyts/svelte-ui-utils 0.4.2 → 0.4.3
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 +6 -1
- package/dist/dropdown/Dropdown.svelte +57 -12
- package/dist/dropdown/Dropdown.svelte.d.ts +1 -0
- package/dist/dropdown/Dropdown.svelte.d.ts.map +1 -1
- package/dist/dropdown/DropdownMultiSelect.svelte +2 -0
- package/dist/dropdown/DropdownMultiSelect.svelte.d.ts +1 -0
- package/dist/dropdown/DropdownMultiSelect.svelte.d.ts.map +1 -1
- package/dist/style.css +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -511,7 +511,12 @@ relational pseudo-class.
|
|
|
511
511
|
The original flat `options` API remains available. New callers may opt into
|
|
512
512
|
grouped options with `optionGroups`; each group can have an
|
|
513
513
|
optional accessible label and its options keep the same keyboard and disabled
|
|
514
|
-
semantics.
|
|
514
|
+
semantics. Set `groupsCollapsedByDefault` to `"true"` to initially collapse
|
|
515
|
+
all groups, or to `"auto"` to initially expand only groups containing a
|
|
516
|
+
selected option; `"false"` is the default. The initial state is evaluated each
|
|
517
|
+
time the menu opens, and selecting options while it remains open does not
|
|
518
|
+
change the current collapsed state. Group headings can always be toggled
|
|
519
|
+
manually. `width`, `minWidth`, `maxWidth`, and `className` are optional styling
|
|
515
520
|
hooks. `onTriggerClick` runs before the dropdown toggles, so a nested control
|
|
516
521
|
can stop event propagation without preventing the dropdown from opening.
|
|
517
522
|
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
export let multiselect = false;
|
|
22
22
|
export let options: DropdownOption[] = [];
|
|
23
23
|
export let optionGroups: DropdownOptionGroup[] | undefined = undefined;
|
|
24
|
+
export let groupsCollapsedByDefault: 'true' | 'false' | 'auto' = 'false';
|
|
24
25
|
export let ariaLabel: string | undefined = undefined;
|
|
25
26
|
export let placement: DropdownPlacement = 'auto';
|
|
26
27
|
export let menuAlign: DropdownMenuAlign = 'left';
|
|
@@ -56,11 +57,15 @@
|
|
|
56
57
|
let typeaheadBuffer = '';
|
|
57
58
|
let typeaheadTimer: ReturnType<typeof setTimeout> | undefined;
|
|
58
59
|
let typeaheadGeneration = 0;
|
|
60
|
+
let collapsedGroupIndexes = new Set<number>();
|
|
59
61
|
// Keep the buffer long enough for users to type multi-character aliases
|
|
60
62
|
// while the menu is rendering a large option list.
|
|
61
63
|
const typeaheadTimeoutMs = 1000;
|
|
62
64
|
|
|
63
65
|
$: resolvedOptions = optionGroups === undefined ? options : optionGroups.flatMap((group) => group.options);
|
|
66
|
+
$: visibleOptions = optionGroups === undefined
|
|
67
|
+
? options
|
|
68
|
+
: optionGroups.flatMap((group, groupIndex) => group.label && collapsedGroupIndexes.has(groupIndex) ? [] : group.options);
|
|
64
69
|
$: selectedValues = normalizeSelectedValues(multiselect && Array.isArray(value) ? value : []);
|
|
65
70
|
$: selectedOption = Array.isArray(value) ? undefined : resolvedOptions.find((option) => option.value === value);
|
|
66
71
|
$: selectedText = multiselect
|
|
@@ -89,7 +94,7 @@
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
function firstEnabledOption(): DropdownOption | undefined {
|
|
92
|
-
return
|
|
97
|
+
return visibleOptions.find((option) => !option.disabled);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function normalizeSelectedValues(nextValues: DropdownMultiValue): DropdownMultiValue {
|
|
@@ -104,7 +109,7 @@
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
function initialActiveValue(): DropdownValue {
|
|
107
|
-
const firstSelectedEnabled =
|
|
112
|
+
const firstSelectedEnabled = visibleOptions.find(
|
|
108
113
|
(option) => selectedValues.includes(option.value) && !option.disabled
|
|
109
114
|
);
|
|
110
115
|
return firstSelectedEnabled?.value ?? selectedOption?.value ?? firstEnabledOption()?.value ?? '';
|
|
@@ -115,21 +120,21 @@
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
function activeOptionIndex(nextActiveValue: DropdownValue): number {
|
|
118
|
-
const index =
|
|
123
|
+
const index = visibleOptions.findIndex((option) => option.value === nextActiveValue && !option.disabled);
|
|
119
124
|
if (index >= 0) {
|
|
120
125
|
return index;
|
|
121
126
|
}
|
|
122
|
-
const fallbackIndex =
|
|
127
|
+
const fallbackIndex = visibleOptions.findIndex(
|
|
123
128
|
(option) => isOptionSelected(option) && !option.disabled
|
|
124
129
|
);
|
|
125
130
|
if (fallbackIndex >= 0) {
|
|
126
131
|
return fallbackIndex;
|
|
127
132
|
}
|
|
128
|
-
return
|
|
133
|
+
return visibleOptions.findIndex((option) => !option.disabled);
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
function moveActiveOption(offset: number) {
|
|
132
|
-
const enabledOptions =
|
|
137
|
+
const enabledOptions = visibleOptions.filter((option) => !option.disabled);
|
|
133
138
|
if (enabledOptions.length === 0) {
|
|
134
139
|
return;
|
|
135
140
|
}
|
|
@@ -180,7 +185,7 @@
|
|
|
180
185
|
: key.toLocaleLowerCase();
|
|
181
186
|
const nextBuffer = `${typeaheadBuffer}${normalizedKey}`;
|
|
182
187
|
const findMatch = (prefix: string) =>
|
|
183
|
-
|
|
188
|
+
visibleOptions.find(
|
|
184
189
|
(option) =>
|
|
185
190
|
!option.disabled &&
|
|
186
191
|
[option.searchText, option.label]
|
|
@@ -233,12 +238,42 @@
|
|
|
233
238
|
}
|
|
234
239
|
if (!open) {
|
|
235
240
|
updateResolvedPlacement();
|
|
241
|
+
initializeCollapsedGroups();
|
|
236
242
|
}
|
|
237
243
|
clearTypeaheadBuffer();
|
|
238
244
|
open = !open;
|
|
239
245
|
activeValue = initialActiveValue();
|
|
240
246
|
}
|
|
241
247
|
|
|
248
|
+
function isGroupCollapsed(groupIndex: number): boolean {
|
|
249
|
+
return collapsedGroupIndexes.has(groupIndex);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function initializeCollapsedGroups() {
|
|
253
|
+
if (optionGroups === undefined || groupsCollapsedByDefault === 'false') {
|
|
254
|
+
collapsedGroupIndexes = new Set();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (groupsCollapsedByDefault === 'true') {
|
|
258
|
+
collapsedGroupIndexes = new Set(
|
|
259
|
+
optionGroups.flatMap((group, index) => group.label ? [index] : [])
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
collapsedGroupIndexes = new Set(
|
|
264
|
+
optionGroups.flatMap((group, index) =>
|
|
265
|
+
group.label && group.options.some((option) => isOptionSelected(option)) ? [] : group.label ? [index] : []
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function toggleGroup(groupIndex: number) {
|
|
271
|
+
const next = new Set(collapsedGroupIndexes);
|
|
272
|
+
if (next.has(groupIndex)) next.delete(groupIndex);
|
|
273
|
+
else next.add(groupIndex);
|
|
274
|
+
collapsedGroupIndexes = next;
|
|
275
|
+
}
|
|
276
|
+
|
|
242
277
|
function handleTriggerClick(event: MouseEvent) {
|
|
243
278
|
onTriggerClick?.(event);
|
|
244
279
|
toggleOpen();
|
|
@@ -284,7 +319,7 @@
|
|
|
284
319
|
if ((event.key === 'Enter' || event.key === ' ') && open) {
|
|
285
320
|
event.preventDefault();
|
|
286
321
|
const index = activeOptionIndex(activeValue);
|
|
287
|
-
const option = index >= 0 ?
|
|
322
|
+
const option = index >= 0 ? visibleOptions[index] : undefined;
|
|
288
323
|
if (option) {
|
|
289
324
|
selectOption(option);
|
|
290
325
|
}
|
|
@@ -558,12 +593,21 @@
|
|
|
558
593
|
</button>
|
|
559
594
|
{/each}
|
|
560
595
|
{:else}
|
|
561
|
-
{#each optionGroups as group}
|
|
596
|
+
{#each optionGroups as group, groupIndex}
|
|
562
597
|
<div class="suu-dropdown__group" role="group" aria-label={group.label}>
|
|
563
598
|
{#if group.label}
|
|
564
|
-
<
|
|
599
|
+
<button
|
|
600
|
+
type="button"
|
|
601
|
+
class="suu-dropdown__group-label"
|
|
602
|
+
aria-expanded={!collapsedGroupIndexes.has(groupIndex)}
|
|
603
|
+
on:click|stopPropagation={() => toggleGroup(groupIndex)}
|
|
604
|
+
>
|
|
605
|
+
<span>{group.label}</span>
|
|
606
|
+
<span class="suu-dropdown__group-chevron" aria-hidden="true"></span>
|
|
607
|
+
</button>
|
|
565
608
|
{/if}
|
|
566
|
-
{#
|
|
609
|
+
{#if !group.label || !collapsedGroupIndexes.has(groupIndex)}
|
|
610
|
+
{#each group.options as option, optionIndex}
|
|
567
611
|
<button
|
|
568
612
|
type="button"
|
|
569
613
|
class="suu-dropdown__option"
|
|
@@ -592,7 +636,8 @@
|
|
|
592
636
|
{/if}
|
|
593
637
|
<span class="suu-dropdown__label">{option.label}</span>
|
|
594
638
|
</button>
|
|
595
|
-
|
|
639
|
+
{/each}
|
|
640
|
+
{/if}
|
|
596
641
|
</div>
|
|
597
642
|
{/each}
|
|
598
643
|
{/if}
|
|
@@ -18,6 +18,7 @@ declare const Dropdown: $$__sveltets_2_IsomorphicComponent<{
|
|
|
18
18
|
multiselect?: boolean;
|
|
19
19
|
options?: DropdownOption[];
|
|
20
20
|
optionGroups?: DropdownOptionGroup[] | undefined;
|
|
21
|
+
groupsCollapsedByDefault?: "true" | "false" | "auto";
|
|
21
22
|
ariaLabel?: string | undefined;
|
|
22
23
|
placement?: DropdownPlacement;
|
|
23
24
|
menuAlign?: DropdownMenuAlign;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAER,iBAAiB,EAGjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,iBAAiB,EACjB,2BAA2B,EAE5B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAER,iBAAiB,EAGjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,iBAAiB,EACjB,2BAA2B,EAE5B,MAAM,YAAY,CAAC;AAuiBtB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,QAAQ;SAV0e,MAAM,GAAG,SAAS;YAAU,iBAAiB;;cAA8C,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;+BAA6B,MAAM,GAAG,OAAO,GAAG,MAAM;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;WAAuG,MAAM,GAAG,SAAS;;YAAsC,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAqC,8BAA8B,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAUpmC,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
export let value: DropdownMultiValue = [];
|
|
17
17
|
export let options: DropdownOption[] = [];
|
|
18
18
|
export let optionGroups: DropdownOptionGroup[] | undefined = undefined;
|
|
19
|
+
export let groupsCollapsedByDefault: 'true' | 'false' | 'auto' = 'false';
|
|
19
20
|
export let ariaLabel: string | undefined = undefined;
|
|
20
21
|
export let placement: DropdownPlacement = 'auto';
|
|
21
22
|
export let menuAlign: DropdownMenuAlign = 'left';
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
multiselect={true}
|
|
38
39
|
{options}
|
|
39
40
|
{optionGroups}
|
|
41
|
+
{groupsCollapsedByDefault}
|
|
40
42
|
{ariaLabel}
|
|
41
43
|
{placement}
|
|
42
44
|
{menuAlign}
|
|
@@ -17,6 +17,7 @@ declare const DropdownMultiSelect: $$__sveltets_2_IsomorphicComponent<{
|
|
|
17
17
|
value?: DropdownMultiValue;
|
|
18
18
|
options?: DropdownOption[];
|
|
19
19
|
optionGroups?: DropdownOptionGroup[] | undefined;
|
|
20
|
+
groupsCollapsedByDefault?: "true" | "false" | "auto";
|
|
20
21
|
ariaLabel?: string | undefined;
|
|
21
22
|
placement?: DropdownPlacement;
|
|
22
23
|
menuAlign?: DropdownMenuAlign;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropdownMultiSelect.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/DropdownMultiSelect.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,iBAAiB,EACjB,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"DropdownMultiSelect.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/DropdownMultiSelect.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,iBAAiB,EACjB,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC5B,MAAM,YAAY,CAAC;AAgCtB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,mBAAmB;SAVka,MAAM,GAAG,SAAS;YAAU,kBAAkB;cAAY,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;+BAA6B,MAAM,GAAG,OAAO,GAAG,MAAM;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;YAAwG,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAqC,0BAA0B,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAUh8B,CAAC;AACvF,KAAK,mBAAmB,GAAG,YAAY,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACtE,eAAe,mBAAmB,CAAC"}
|
package/dist/style.css
CHANGED
|
@@ -1293,12 +1293,33 @@
|
|
|
1293
1293
|
}
|
|
1294
1294
|
|
|
1295
1295
|
.suu-dropdown__group-label {
|
|
1296
|
+
display: flex;
|
|
1297
|
+
width: 100%;
|
|
1298
|
+
border: 0;
|
|
1296
1299
|
padding: 8px 14px 6px;
|
|
1297
1300
|
background: var(--suu-color-surface);
|
|
1298
1301
|
color: var(--suu-color-muted);
|
|
1302
|
+
cursor: pointer;
|
|
1299
1303
|
font-size: 0.75em;
|
|
1300
1304
|
font-weight: 700;
|
|
1301
1305
|
letter-spacing: 0.02em;
|
|
1306
|
+
font: inherit;
|
|
1307
|
+
text-align: left;
|
|
1308
|
+
align-items: center;
|
|
1309
|
+
justify-content: space-between;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.suu-dropdown__group-chevron {
|
|
1313
|
+
width: 7px;
|
|
1314
|
+
height: 7px;
|
|
1315
|
+
border-right: 2px solid currentColor;
|
|
1316
|
+
border-bottom: 2px solid currentColor;
|
|
1317
|
+
transform: rotate(225deg);
|
|
1318
|
+
transition: transform 120ms ease;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.suu-dropdown__group-label[aria-expanded='true'] .suu-dropdown__group-chevron {
|
|
1322
|
+
transform: rotate(45deg);
|
|
1302
1323
|
}
|
|
1303
1324
|
|
|
1304
1325
|
.suu-dropdown__option {
|
package/package.json
CHANGED