mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Refined multi-select
This commit is contained in:
@@ -191,10 +191,10 @@ toc:
|
||||
<dd>When the popover opens, the component dispatches a custom (non-bubbling) <code>basecoat:popover</code> event on <code>document</code>. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.</dd>
|
||||
<dt><code>change</code></dt>
|
||||
<dd>
|
||||
<p>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail</code>:</p>
|
||||
<p>When the selected value changes, the component dispatches a custom (bubbling) <code>change</code> event on itself, with the selected value in <code>event.detail.value</code>:</p>
|
||||
<ul>
|
||||
<li>Single select: <code>{ detail: { value: "something" }}</code></li>
|
||||
<li>Multiple select: <code>{ detail: { value: "last-changed", values: ["item1", "item2"] }}</code></li>
|
||||
<li>Single select: <code>{ detail: { value: "something" }}</code> (string)</li>
|
||||
<li>Multiple select: <code>{ detail: { value: ["item1", "item2"] }}</code> (array)</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
+58
-85
@@ -87,50 +87,57 @@
|
||||
}
|
||||
};
|
||||
|
||||
const updateValue = (option, triggerEvent = true) => {
|
||||
if (option) {
|
||||
const updateValue = (optionOrOptions, triggerEvent = true) => {
|
||||
let value;
|
||||
|
||||
if (isMultiple) {
|
||||
const opts = Array.isArray(optionOrOptions) ? optionOrOptions : [];
|
||||
selectedValues = new Set(opts.map(opt => opt.dataset.value));
|
||||
options.forEach(opt => {
|
||||
if (selectedValues.has(opt.dataset.value)) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
value = Array.from(selectedValues);
|
||||
} else {
|
||||
const option = optionOrOptions;
|
||||
if (!option) return;
|
||||
selectedLabel.innerHTML = option.innerHTML;
|
||||
input.value = option.dataset.value;
|
||||
listbox.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute('aria-selected');
|
||||
option.setAttribute('aria-selected', 'true');
|
||||
options.forEach(opt => {
|
||||
if (opt === option) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
value = option.dataset.value;
|
||||
}
|
||||
|
||||
if (triggerEvent) {
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { value: option.dataset.value },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
}
|
||||
if (triggerEvent) {
|
||||
selectComponent.dispatchEvent(new CustomEvent('change', {
|
||||
detail: { value },
|
||||
bubbles: true
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMultipleValue = (value, triggerEvent = true) => {
|
||||
if (!isMultiple || value == null) return;
|
||||
|
||||
if (selectedValues.has(value)) {
|
||||
selectedValues.delete(value);
|
||||
const newValues = new Set(selectedValues);
|
||||
if (newValues.has(value)) {
|
||||
newValues.delete(value);
|
||||
} else {
|
||||
selectedValues.add(value);
|
||||
newValues.add(value);
|
||||
}
|
||||
|
||||
options.forEach(opt => {
|
||||
if (selectedValues.has(opt.dataset.value)) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
|
||||
if (triggerEvent) {
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { value, values: Array.from(selectedValues) },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
}
|
||||
const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
|
||||
updateValue(selectedOptions, triggerEvent);
|
||||
};
|
||||
|
||||
const closePopover = (focusOnTrigger = true) => {
|
||||
@@ -172,22 +179,12 @@
|
||||
const selectByValue = (value) => {
|
||||
const option = options.find(opt => opt.dataset.value === value);
|
||||
if (isMultiple) {
|
||||
if (value != null && selectedValues.has(value)) return;
|
||||
if (option && value != null) {
|
||||
selectedValues.add(value);
|
||||
options.forEach(opt => {
|
||||
if (selectedValues.has(opt.dataset.value)) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { value, values: Array.from(selectedValues) },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
const newValues = new Set(selectedValues);
|
||||
newValues.add(value);
|
||||
const selectedOptions = options.filter(opt => newValues.has(opt.dataset.value));
|
||||
updateValue(selectedOptions);
|
||||
}
|
||||
} else {
|
||||
selectOption(option);
|
||||
@@ -196,34 +193,12 @@
|
||||
|
||||
const selectAll = () => {
|
||||
if (!isMultiple) return;
|
||||
selectedValues = new Set(options.map(opt => opt.dataset.value).filter(v => v != null));
|
||||
options.forEach(opt => {
|
||||
if (selectedValues.has(opt.dataset.value)) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { values: Array.from(selectedValues) },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
updateValue(options.filter(opt => opt.dataset.value != null));
|
||||
};
|
||||
|
||||
const selectNone = () => {
|
||||
if (!isMultiple) return;
|
||||
selectedValues.clear();
|
||||
options.forEach(opt => opt.removeAttribute('aria-selected'));
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
const event = new CustomEvent('change', {
|
||||
detail: { values: [] },
|
||||
bubbles: true
|
||||
});
|
||||
selectComponent.dispatchEvent(event);
|
||||
updateValue([]);
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
@@ -265,23 +240,15 @@
|
||||
const initialValues = inputs
|
||||
.map(inp => inp.value)
|
||||
.filter(v => v != null && validValues.has(v));
|
||||
|
||||
let initialOptions;
|
||||
if (initialValues.length > 0) {
|
||||
initialValues.forEach(val => selectedValues.add(val));
|
||||
initialOptions = options.filter(opt => initialValues.includes(opt.dataset.value));
|
||||
} else {
|
||||
const preSelected = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
|
||||
if (preSelected.length > 0) {
|
||||
preSelected.forEach(opt => opt.dataset.value != null && selectedValues.add(opt.dataset.value));
|
||||
}
|
||||
initialOptions = options.filter(opt => opt.getAttribute('aria-selected') === 'true');
|
||||
}
|
||||
options.forEach(opt => {
|
||||
if (selectedValues.has(opt.dataset.value)) {
|
||||
opt.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
opt.removeAttribute('aria-selected');
|
||||
}
|
||||
});
|
||||
updateMultipleLabel();
|
||||
syncMultipleInputs();
|
||||
|
||||
updateValue(initialOptions, false);
|
||||
} else {
|
||||
let initialOption = options.find(opt => opt.dataset.value === input.value);
|
||||
|
||||
@@ -423,6 +390,12 @@
|
||||
if (clickedOption) {
|
||||
if (isMultiple) {
|
||||
toggleMultipleValue(clickedOption.dataset.value);
|
||||
setActiveOption(options.indexOf(clickedOption));
|
||||
if (filter) {
|
||||
filter.focus();
|
||||
} else {
|
||||
trigger.focus();
|
||||
}
|
||||
} else {
|
||||
selectOption(clickedOption);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user