Compare commits

..

3 commits

View file

@ -126,6 +126,9 @@ export class BetterSelect extends HTMLElement {
#abortOpen #abortOpen
#value = {} #value = {}
/** @type {number} */
#index = undefined
#internals = this.attachInternals() #internals = this.attachInternals()
#states = new StateAttributeSet(this, this.#internals.states) #states = new StateAttributeSet(this, this.#internals.states)
@ -160,7 +163,7 @@ export class BetterSelect extends HTMLElement {
#text:empty ~ *[name="clear"] { #text:empty ~ *[name="clear"] {
display: none; display: none;
} }
[part="drop-down"], [part="item"] { [part="drop-down"], [part~="item"] {
/* Resets */ /* Resets */
border: unset; border: unset;
outline: unset; outline: unset;
@ -195,10 +198,10 @@ export class BetterSelect extends HTMLElement {
cursor: pointer; cursor: pointer;
} }
} }
[part="item"]:focus { [part~="item"]:focus {
font-weight: bold; font-weight: bold;
} }
[part="item"][hidden] { [part~="item"][hidden] {
display: none; display: none;
} }
slot[name="loading"] { slot[name="loading"] {
@ -313,6 +316,14 @@ export class BetterSelect extends HTMLElement {
this.keyboardSearchAppend(key) this.keyboardSearchAppend(key)
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
} else if (key == "Delete") {
this.clear()
} else if (key == "ArrowDown") {
event.preventDefault()
this.next()
} else if (key == "ArrowUp") {
event.preventDefault()
this.previous()
} }
} }
}) })
@ -421,10 +432,11 @@ export class BetterSelect extends HTMLElement {
/** @param {String} value */ /** @param {String} value */
search(value) { search(value) {
for (const item of this.list.children) { for (const item of this.list.children) {
if (item instanceof HTMLElement) if (item instanceof HTMLElement) {
item.toggleAttribute("hidden", !this.match(value, item)) item.toggleAttribute("hidden", !this.match(value, item))
} }
} }
}
selectDefault() { selectDefault() {
const active = this.shadowRoot.activeElement const active = this.shadowRoot.activeElement
@ -433,18 +445,21 @@ export class BetterSelect extends HTMLElement {
this.close() this.close()
return return
} }
const candidates = /** @type {HTMLElement[]} */(Array.from(this.list.children).filter(child => !child.hasAttribute("hidden"))) const candidates = /** @type {HTMLElement[]} */(Array.from(this.list.children).filter(child => !child.hasAttribute("hidden") && !child.part.contains("disabled")))
if (candidates.length) { if (candidates.length) {
this.setOption(candidates[0]) this.setOption(candidates[0])
this.close() this.close()
} }
} }
static searchHideDisabled = true
/** /**
* @param {string} value * @param {string} value
* @param {HTMLElement} item * @param {HTMLElement} item
*/ */
match(value, item) { match(value, item) {
if (/** @type {Object} */(value && this.constructor).searchHideDisabled && item.part.contains("disabled")) return false
return item.innerText.toLowerCase().match(value.toLowerCase()) return item.innerText.toLowerCase().match(value.toLowerCase())
} }
@ -458,14 +473,17 @@ export class BetterSelect extends HTMLElement {
/** @param {HTMLElement} option */ /** @param {HTMLElement} option */
setOption(option) { setOption(option) {
return this.setValue(option.dataset.value, option.innerText) return this.setValue(Number(option.dataset.index), option.dataset.value, option.innerText)
} }
/** /**
* @param {number} index
* @param {string} value * @param {string} value
* @param {string} state * @param {string} state
*/ */
setValue(value, state=value) { setValue(index, value, state=value) {
this.#index = Number(index)
this.#value = {value, state} this.#value = {value, state}
this.dispatchEvent(new Event("change", {bubbles: true})); this.dispatchEvent(new Event("change", {bubbles: true}));
this.#internals.setFormValue(value, state) this.#internals.setFormValue(value, state)
@ -497,9 +515,10 @@ export class BetterSelect extends HTMLElement {
if (value === undefined) { if (value === undefined) {
this.clear() this.clear()
} else { } else {
let index = 0
for (const option of this.options) { for (const option of this.options) {
if (option.value === String(value)) { if (option.value === String(value)) {
this.setValue(option.value, option.innerText) this.setValue(index++, option.value, option.innerText)
return return
} }
} }
@ -511,8 +530,9 @@ export class BetterSelect extends HTMLElement {
setOptions() { setOptions() {
this.list.replaceChildren() this.list.replaceChildren()
let idx = 0
for (const option of this.options) { for (const option of this.options) {
const fragment = f`<li tabindex="0" part="item" data-value="${option.value}">${option.innerText}</li>` const fragment = f`<li data-index=${idx++} tabindex="0" part="item" data-value="${option.value}">${option.innerText}</li>`
const li = fragment.querySelector("li") const li = fragment.querySelector("li")
if (option.disabled) { if (option.disabled) {
li.part.add("disabled") li.part.add("disabled")
@ -554,7 +574,7 @@ export class BetterSelect extends HTMLElement {
get form() { return this.#internals.form } get form() { return this.#internals.form }
clear() { clear() {
this.setValue(undefined, "") this.setValue(undefined, undefined, "")
} }
/** /**
@ -570,6 +590,21 @@ export class BetterSelect extends HTMLElement {
return "Please select an option." return "Please select an option."
} }
next() {
const index = (this.#index ?? -1) + 1
const item = /** @type {HTMLElement} */(this.list.children[index])
if (item) {
this.setOption(item)
}
}
previous() {
const index = (this.#index ?? this.options.length) - 1
const item = /** @type {HTMLElement} */(this.list.children[index])
if (item) {
this.setOption(item)
}
}
setValidity() { setValidity() {
if (this.required) { if (this.required) {