Skip to content

Commit

Permalink
fix: options reset on add / remove
Browse files Browse the repository at this point in the history
  • Loading branch information
luwes committed Jul 28, 2023
1 parent e3df300 commit 2396a7d
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 16 deletions.
17 changes: 8 additions & 9 deletions src/js/experimental/media-chrome-listbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class MediaChromeListbox extends globalThis.HTMLElement {

#keysSoFar = '';
#clearKeysTimeout = null;
#slot;
#metaPressed = false;

constructor(options = {}) {
Expand Down Expand Up @@ -137,7 +136,7 @@ class MediaChromeListbox extends globalThis.HTMLElement {
return indicator;
}

get #options() {
get options() {
// First query the light dom children for any options.

/** @type NodeListOf<HTMLOptionElement> */
Expand All @@ -152,15 +151,15 @@ class MediaChromeListbox extends globalThis.HTMLElement {
}

get selectedOptions() {
return this.#options.filter(el => el.selected);
return this.options.filter(option => option.selected);
}

get value() {
return this.selectedOptions[0]?.value || this.selectedOptions[0]?.textContent;
return this.selectedOptions[0]?.value ?? '';
}

set value(newValue) {
const option = this.#options.find(el => el.value === newValue || el.textContent === newValue);
const option = this.options.find(option => option.value === newValue);

if (!option) return;

Expand Down Expand Up @@ -314,7 +313,7 @@ class MediaChromeListbox extends globalThis.HTMLElement {
const oldSelectedOptions = [...this.selectedOptions];

if (!this.hasAttribute('aria-multiselectable') || this.getAttribute('aria-multiselectable') !== 'true') {
this.#options.forEach(el => (el.selected = false));
this.options.forEach(el => (el.selected = false));
}

if (toggle) {
Expand All @@ -330,7 +329,7 @@ class MediaChromeListbox extends globalThis.HTMLElement {

handleMovement(e) {
const { key } = e;
const els = this.#options;
const els = this.options;

let currentOption = this.#getOption(e);
if (!currentOption) {
Expand Down Expand Up @@ -379,7 +378,7 @@ class MediaChromeListbox extends globalThis.HTMLElement {

if (!option || option.hasAttribute('disabled')) return;

this.#options.forEach(el => el.setAttribute('tabindex', '-1'));
this.options.forEach(el => el.setAttribute('tabindex', '-1'));
option.setAttribute('tabindex', '0');

this.handleSelection(e, this.hasAttribute('aria-multiselectable') && this.getAttribute('aria-multiselectable') === 'true');
Expand All @@ -388,7 +387,7 @@ class MediaChromeListbox extends globalThis.HTMLElement {
#searchOption(key) {
this.#clearKeysOnDelay();

const els = this.#options;
const els = this.options;
const activeIndex = els.findIndex(el => el.getAttribute('tabindex') === '0');

// always accumulate the key
Expand Down
54 changes: 47 additions & 7 deletions src/js/experimental/media-chrome-option.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class MediaChromeOption extends globalThis.HTMLElement {

/** @see https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-dirtiness */
#dirty = false;
#ownerElement;

constructor() {
super();
Expand All @@ -75,23 +76,27 @@ class MediaChromeOption extends globalThis.HTMLElement {
}
}

set value(value) {
this.setAttribute(Attributes.VALUE, value);
get value() {
return this.getAttribute(Attributes.VALUE) ?? this.text;
}

get value() {
return this.getAttribute(Attributes.VALUE);
set value(val) {
this.setAttribute(Attributes.VALUE, val);
}

set selected(value) {
this.#dirty = true;
this.ariaSelected = value ? 'true' : 'false';
get text() {
return (this.textContent ?? '').trim();
}

get selected() {
return this.ariaSelected === 'true';
}

set selected(value) {
this.#dirty = true;
this.ariaSelected = value ? 'true' : 'false';
}

enable() {
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', -1);
Expand Down Expand Up @@ -126,15 +131,50 @@ class MediaChromeOption extends globalThis.HTMLElement {
}

this.setAttribute('role', 'option');

this.#ownerElement = closestOptionsContainer(this, this.parentNode);
this.#reset();
}

disconnectedCallback() {
this.disable();

this.#reset();
this.#ownerElement = null;
}

#reset() {
const options = this.#ownerElement?.options;
if (!options) return;

const hasActiveOption = options.some(option => option.getAttribute('tabindex') === '0');
// If the user set an element as active, we should use that rather than assume a default.
if (hasActiveOption) return;

// Default to the aria-selected element if there isn't an active element already.
let selectedOption = options.find(option => option.getAttribute('aria-selected') === 'true');

// If there isn't an active element or a selected element, default to the first element.
if (!selectedOption) selectedOption = options[0];

selectedOption?.setAttribute('tabindex', '0');
selectedOption?.setAttribute('aria-selected', 'true');
}

handleClick() {}
}

function closestOptionsContainer(childNode, parentNode) {
if (!childNode) return null;

const { host } = childNode.getRootNode();
if (!parentNode && host) return closestOptionsContainer(childNode, host);

if (parentNode?.options) return parentNode;

return closestOptionsContainer(parentNode, parentNode?.parentNode);
}

if (!globalThis.customElements.get('media-chrome-option')) {
globalThis.customElements.define('media-chrome-option', MediaChromeOption);
}
Expand Down

0 comments on commit 2396a7d

Please sign in to comment.