From d454f437efed35c13741d96c76f731d6a5ae24c3 Mon Sep 17 00:00:00 2001 From: William Bagayoko Date: Mon, 1 Jun 2020 17:31:53 -0500 Subject: [PATCH 01/18] 3470 prettier / input event / stubs --- spec/unit/combo-box/combo-box.spec.js | 498 +++++++++++++++++--------- 1 file changed, 323 insertions(+), 175 deletions(-) diff --git a/spec/unit/combo-box/combo-box.spec.js b/spec/unit/combo-box/combo-box.spec.js index 92c4666da8..d0550f6d98 100755 --- a/spec/unit/combo-box/combo-box.spec.js +++ b/spec/unit/combo-box/combo-box.spec.js @@ -1,10 +1,10 @@ -const fs = require('fs'); -const path = require('path'); -const sinon = require('sinon'); -const assert = require('assert'); -const ComboBox = require('../../../src/js/components/combo-box'); +const fs = require("fs"); +const path = require("path"); +const sinon = require("sinon"); +const assert = require("assert"); +const ComboBox = require("../../../src/js/components/combo-box"); -const TEMPLATE = fs.readFileSync(path.join(__dirname, '/template.html')); +const TEMPLATE = fs.readFileSync(path.join(__dirname, "/template.html")); const EVENTS = {}; @@ -12,11 +12,11 @@ const EVENTS = {}; * send a click event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.click = (el) => { - const evt = new MouseEvent('click', { +EVENTS.click = el => { + const evt = new MouseEvent("click", { view: el.ownerDocument.defaultView, bubbles: true, - cancelable: true, + cancelable: true }); el.dispatchEvent(evt); }; @@ -25,10 +25,10 @@ EVENTS.click = (el) => { * send a focusout event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.focusout = (el) => { - const evt = new Event('focusout', { +EVENTS.focusout = el => { + const evt = new Event("focusout", { bubbles: true, - cancelable: true, + cancelable: true }); el.dispatchEvent(evt); }; @@ -37,11 +37,11 @@ EVENTS.focusout = (el) => { * send a keyup A event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.keyupA = (el) => { - const evt = new KeyboardEvent('keyup', { +EVENTS.keyupA = el => { + const evt = new KeyboardEvent("keyup", { bubbles: true, - key: 'a', - keyCode: 65, + key: "a", + keyCode: 65 }); el.dispatchEvent(evt); }; @@ -50,11 +50,11 @@ EVENTS.keyupA = (el) => { * send a keyup O event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.keyupO = (el) => { - const evt = new KeyboardEvent('keyup', { +EVENTS.keyupO = el => { + const evt = new KeyboardEvent("keyup", { bubbles: true, - key: 'o', - keyCode: 79, + key: "o", + keyCode: 79 }); el.dispatchEvent(evt); }; @@ -62,15 +62,15 @@ EVENTS.keyupO = (el) => { /** * send a keydown Enter event * @param {HTMLElement} el the element to sent the event to - * @returns {{preventDefaultSpy: sinon.SinonSpy<[], void>}} + * @returns {{preventDefaultSpy: sinon.SinonSpy<[], void>}} */ -EVENTS.keydownEnter = (el) => { - const evt = new KeyboardEvent('keydown', { +EVENTS.keydownEnter = el => { + const evt = new KeyboardEvent("keydown", { bubbles: true, - key: 'Enter', - keyCode: 13, + key: "Enter", + keyCode: 13 }); - const preventDefaultSpy = sinon.spy(evt, 'preventDefault'); + const preventDefaultSpy = sinon.spy(evt, "preventDefault"); el.dispatchEvent(evt); return { preventDefaultSpy }; }; @@ -79,11 +79,11 @@ EVENTS.keydownEnter = (el) => { * send a keydown Escape event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.keydownEscape = (el) => { - const evt = new KeyboardEvent('keydown', { +EVENTS.keydownEscape = el => { + const evt = new KeyboardEvent("keydown", { bubbles: true, - key: 'Escape', - keyCode: 27, + key: "Escape", + keyCode: 27 }); el.dispatchEvent(evt); }; @@ -92,10 +92,10 @@ EVENTS.keydownEscape = (el) => { * send a keydown ArrowDown event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.keydownArrowDown = (el) => { - const evt = new KeyboardEvent('keydown', { +EVENTS.keydownArrowDown = el => { + const evt = new KeyboardEvent("keydown", { bubbles: true, - key: 'ArrowDown', + key: "ArrowDown" }); el.dispatchEvent(evt); }; @@ -104,15 +104,23 @@ EVENTS.keydownArrowDown = (el) => { * send a keydown ArrowUp event * @param {HTMLElement} el the element to sent the event to */ -EVENTS.keydownArrowUp = (el) => { - const evt = new KeyboardEvent('keydown', { +EVENTS.keydownArrowUp = el => { + const evt = new KeyboardEvent("keydown", { bubbles: true, - key: 'ArrowUp', + key: "ArrowUp" }); el.dispatchEvent(evt); }; -describe('combo box component', () => { +/** + * send an input event + * @param {HTMLElement} el the element to sent the event to + */ +EVENTS.input = el => { + el.dispatchEvent(new KeyboardEvent("input", { bubbles: true })); +}; + +describe("combo box component", () => { const { body } = document; let root; @@ -123,279 +131,419 @@ describe('combo box component', () => { beforeEach(() => { body.innerHTML = TEMPLATE; ComboBox.on(); - root = body.querySelector('.usa-combo-box'); - input = root.querySelector('.usa-combo-box__input'); - select = root.querySelector('.usa-combo-box__select'); - list = root.querySelector('.usa-combo-box__list'); + root = body.querySelector(".usa-combo-box"); + input = root.querySelector(".usa-combo-box__input"); + select = root.querySelector(".usa-combo-box__select"); + list = root.querySelector(".usa-combo-box__list"); }); afterEach(() => { - body.textContent = ''; + body.textContent = ""; ComboBox.off(body); }); - it('enhances a select element into a combo box component', () => { - assert.ok(input, 'adds an input element'); - assert.ok(select.classList.contains('usa-sr-only'), 'hides the select element from view'); - assert.ok(list, 'adds an list element'); - assert.ok(list.hidden, 'the list is hidden'); - assert.equal(select.getAttribute('id'), '', 'transfers id attribute to combo box'); - assert.equal(input.getAttribute('id'), 'combo-box', 'transfers id attribute to combo box'); - assert.equal(select.getAttribute('required'), null, 'transfers required attribute to combo box'); - assert.equal(input.getAttribute('required'), '', 'transfers required attribute to combo box'); - assert.equal(select.getAttribute('name'), 'combo-box', 'should not transfer name attribute to combo box'); - assert.equal(input.getAttribute('name'), null, 'should not transfer name attribute to combo box'); - assert.equal(list.getAttribute('role'), 'listbox', 'the list should have a role of `listbox`'); - assert.ok(select.getAttribute('aria-hidden'), 'the select should be hidden from screen readers'); - assert.equal(select.getAttribute('tabindex'), '-1', 'the select should be hidden from keyboard navigation'); - assert.equal(select.value, 'value-JavaScript', 'select the selected select item'); - assert.equal(input.value, 'JavaScript', 'select the selected select item'); + it("enhances a select element into a combo box component", () => { + assert.ok(input, "adds an input element"); + assert.ok( + select.classList.contains("usa-sr-only"), + "hides the select element from view" + ); + assert.ok(list, "adds an list element"); + assert.ok(list.hidden, "the list is hidden"); + assert.equal( + select.getAttribute("id"), + "", + "transfers id attribute to combo box" + ); + assert.equal( + input.getAttribute("id"), + "combo-box", + "transfers id attribute to combo box" + ); + assert.equal( + select.getAttribute("required"), + null, + "transfers required attribute to combo box" + ); + assert.equal( + input.getAttribute("required"), + "", + "transfers required attribute to combo box" + ); + assert.equal( + select.getAttribute("name"), + "combo-box", + "should not transfer name attribute to combo box" + ); + assert.equal( + input.getAttribute("name"), + null, + "should not transfer name attribute to combo box" + ); + assert.equal( + list.getAttribute("role"), + "listbox", + "the list should have a role of `listbox`" + ); + assert.ok( + select.getAttribute("aria-hidden"), + "the select should be hidden from screen readers" + ); + assert.equal( + select.getAttribute("tabindex"), + "-1", + "the select should be hidden from keyboard navigation" + ); + assert.equal( + select.value, + "value-JavaScript", + "select the selected select item" + ); + assert.equal(input.value, "JavaScript", "select the selected select item"); }); - it('should show the list by clicking the input', () => { - input.value = ''; + it("should show the list by clicking the input", () => { + input.value = ""; EVENTS.click(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); assert.equal( list.children.length, select.options.length - 1, - 'should have all of the initial select items in the list except placeholder empty items', + "should have all of the initial select items in the list except placeholder empty items" ); }); - it('should show the list by clicking when clicking the input twice', () => { - input.value = ''; + it("should show the list by clicking when clicking the input twice", () => { + input.value = ""; EVENTS.click(input); EVENTS.click(input); - assert.ok(!list.hidden, 'should keep the option list displayed'); + assert.ok(!list.hidden, "should keep the option list displayed"); assert.equal( list.children.length, select.options.length - 1, - 'should have all of the initial select items in the list except placeholder empty items', + "should have all of the initial select items in the list except placeholder empty items" ); }); - it('should set up the list items for accessibility', () => { + it("should set up the list items for accessibility", () => { EVENTS.click(input); for (let i = 0, len = list.children.length; i < len; i += 1) { assert.equal( - list.children[i].getAttribute('aria-selected'), - 'false', - `item ${i} should not be shown as selected`, + list.children[i].getAttribute("aria-selected"), + "false", + `item ${i} should not be shown as selected` ); assert.equal( - list.children[i].getAttribute('tabindex'), - '-1', - `item ${i} should be hidden from keyboard navigation`, + list.children[i].getAttribute("tabindex"), + "-1", + `item ${i} should be hidden from keyboard navigation` ); assert.equal( - list.children[i].getAttribute('role'), - 'option', - `item ${i} should have a role of 'option'`, + list.children[i].getAttribute("role"), + "option", + `item ${i} should have a role of 'option'` ); } }); - it('should close the list by clicking away', () => { + it("should close the list by clicking away", () => { EVENTS.click(input); EVENTS.focusout(input); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.ok(list.hidden, 'should hide the option list'); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.ok(list.hidden, "should hide the option list"); }); - it('should select an item from the option list when clicking a list option', () => { - input.value = ''; + it("should select an item from the option list when clicking a list option", () => { + input.value = ""; EVENTS.click(input); EVENTS.click(list.children[0]); - assert.equal(select.value, 'value-ActionScript', 'should set that item to being the select option'); - assert.equal(input.value, 'ActionScript', 'should set that item to being the input value'); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); + assert.equal( + select.value, + "value-ActionScript", + "should set that item to being the select option" + ); + assert.equal( + input.value, + "ActionScript", + "should set that item to being the input value" + ); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); }); - it('should display and filter the option list after a character is typed', () => { - input.value = 'a'; + it("should display and filter the option list after a character is typed", () => { + input.value = "a"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); - assert.equal(list.children.length, 10, 'should filter the item by the string being present in the option'); + assert.ok(!list.hidden, "should display the option list"); + assert.equal( + list.children.length, + 10, + "should filter the item by the string being present in the option" + ); }); - it('should clear input values when an incomplete item is remaining on blur', () => { - select.value = 'value-ActionScript'; - input.value = 'a'; + it("should clear input values when an incomplete item is remaining on blur", () => { + select.value = "value-ActionScript"; + input.value = "a"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); EVENTS.focusout(input); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, '', 'should clear the value on the select'); - assert.equal(input.value, '', 'should clear the value on the input'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal(select.value, "", "should clear the value on the select"); + assert.equal(input.value, "", "should clear the value on the input"); }); - it('should clear input values when an incomplete item is submitted through enter', () => { - select.value = 'value-ActionScript'; - input.value = 'a'; + it("should clear input values when an incomplete item is submitted through enter", () => { + select.value = "value-ActionScript"; + input.value = "a"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); const { preventDefaultSpy } = EVENTS.keydownEnter(input); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, '', 'should clear the value on the select'); - assert.equal(input.value, '', 'should clear the value on the input'); - assert.ok(preventDefaultSpy.called, 'should not have allowed enter to perform default action'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal(select.value, "", "should clear the value on the select"); + assert.equal(input.value, "", "should clear the value on the input"); + assert.ok( + preventDefaultSpy.called, + "should not have allowed enter to perform default action" + ); }); - it('should allow enter to perform default action when the list is hidden', () => { - assert.ok(list.hidden, 'the list is hidden'); + it("should allow enter to perform default action when the list is hidden", () => { + assert.ok(list.hidden, "the list is hidden"); const { preventDefaultSpy } = EVENTS.keydownEnter(input); - assert.ok(preventDefaultSpy.notCalled, 'should allow event to perform default action'); + assert.ok( + preventDefaultSpy.notCalled, + "should allow event to perform default action" + ); }); - it('should close the list but not the clear the input value when escape is performed while the list is open', () => { - select.value = 'value-ActionScript'; - input.value = 'a'; + it("should close the list but not the clear the input value when escape is performed while the list is open", () => { + select.value = "value-ActionScript"; + input.value = "a"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); EVENTS.keydownEscape(input); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, 'value-ActionScript', 'should not change the value of the select'); - assert.equal(input.value, 'a', 'should not change the value in the input'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal( + select.value, + "value-ActionScript", + "should not change the value of the select" + ); + assert.equal(input.value, "a", "should not change the value in the input"); }); - it('should set the input value when a complete selection is submitted by clicking away', () => { - select.value = 'value-ActionScript'; - input.value = 'go'; + it("should set the input value when a complete selection is submitted by clicking away", () => { + select.value = "value-ActionScript"; + input.value = "go"; EVENTS.keyupO(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); EVENTS.focusout(input); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, 'value-Go', 'should set that item to being the select option'); - assert.equal(input.value, 'Go', 'should set that item to being the input value'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal( + select.value, + "value-Go", + "should set that item to being the select option" + ); + assert.equal( + input.value, + "Go", + "should set that item to being the input value" + ); }); - it('should set the input value when a complete selection is submitted by pressing enter', () => { - select.value = 'value-ActionScript'; - input.value = 'go'; + it("should set the input value when a complete selection is submitted by pressing enter", () => { + select.value = "value-ActionScript"; + input.value = "go"; EVENTS.keyupO(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); EVENTS.keydownEnter(input); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, 'value-Go', 'should set that item to being the select option'); - assert.equal(input.value, 'Go', 'should set that item to being the input value'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal( + select.value, + "value-Go", + "should set that item to being the select option" + ); + assert.equal( + input.value, + "Go", + "should set that item to being the input value" + ); }); - it('should show the no results item when a nonexistent option is typed', () => { - input.value = 'Bibbidi-Bobbidi-Boo'; + it("should show the no results item when a nonexistent option is typed", () => { + input.value = "Bibbidi-Bobbidi-Boo"; EVENTS.keyupO(input); - assert.ok(!list.hidden, 'should display the option list'); - assert.equal(list.children.length, 1, 'should show no results list item'); - assert.equal(list.children[0].textContent, 'No results found', 'should show the no results list item'); + assert.ok(!list.hidden, "should display the option list"); + assert.equal(list.children.length, 1, "should show no results list item"); + assert.equal( + list.children[0].textContent, + "No results found", + "should show the no results list item" + ); }); - it('should show the list when pressing down from an empty input', () => { - assert.ok(list.hidden, 'the option list is hidden'); + it("should show the list when pressing down from an empty input", () => { + assert.ok(list.hidden, "the option list is hidden"); EVENTS.keydownArrowDown(input); - assert.ok(!list.hidden, 'should display the option list'); + assert.ok(!list.hidden, "should display the option list"); }); - it('should focus the first item in the list when pressing down from the input', () => { - input.value = 'la'; + it("should focus the first item in the list when pressing down from the input", () => { + input.value = "la"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); - assert.equal(list.children.length, 2, 'should filter the item by the string being present in the option'); + assert.ok(!list.hidden, "should display the option list"); + assert.equal( + list.children.length, + 2, + "should filter the item by the string being present in the option" + ); EVENTS.keydownArrowDown(input); const focusedOption = document.activeElement; - assert.ok(focusedOption.classList.contains('usa-combo-box__list-option--focused'), 'should style the focused item in the list'); - assert.equal(focusedOption.textContent, 'Erlang', 'should focus the first item in the list'); + assert.ok( + focusedOption.classList.contains("usa-combo-box__list-option--focused"), + "should style the focused item in the list" + ); + assert.equal( + focusedOption.textContent, + "Erlang", + "should focus the first item in the list" + ); }); - it('should select the focused list item in the list when pressing enter on a focused item', () => { - select.value = 'value-JavaScript'; - input.value = 'la'; + it("should select the focused list item in the list when pressing enter on a focused item", () => { + select.value = "value-JavaScript"; + input.value = "la"; EVENTS.keyupA(input); EVENTS.keydownArrowDown(input); const focusedOption = document.activeElement; - assert.equal(focusedOption.textContent, 'Erlang', 'should focus the first item in the list'); + assert.equal( + focusedOption.textContent, + "Erlang", + "should focus the first item in the list" + ); EVENTS.keydownEnter(focusedOption); - assert.equal(select.value, 'value-Erlang', 'select the first item in the list'); - assert.equal(input.value, 'Erlang', 'should set the value in the input'); + assert.equal( + select.value, + "value-Erlang", + "select the first item in the list" + ); + assert.equal(input.value, "Erlang", "should set the value in the input"); }); - it('should focus the last item in the list when pressing down many times from the input', () => { - input.value = 'la'; + it("should focus the last item in the list when pressing down many times from the input", () => { + input.value = "la"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); - assert.equal(list.children.length, 2, 'should filter the item by the string being present in the option'); + assert.ok(!list.hidden, "should display the option list"); + assert.equal( + list.children.length, + 2, + "should filter the item by the string being present in the option" + ); EVENTS.keydownArrowDown(input); EVENTS.keydownArrowDown(input); EVENTS.keydownArrowDown(input); const focusedOption = document.activeElement; - assert.ok(focusedOption.classList.contains('usa-combo-box__list-option--focused'), 'should style the focused item in the list'); - assert.equal(focusedOption.textContent, 'Scala', 'should focus the last item in the list'); + assert.ok( + focusedOption.classList.contains("usa-combo-box__list-option--focused"), + "should style the focused item in the list" + ); + assert.equal( + focusedOption.textContent, + "Scala", + "should focus the last item in the list" + ); }); - it('should not select the focused item in the list when pressing escape from the focused item', () => { - select.value = 'value-JavaScript'; - input.value = 'la'; + it("should not select the focused item in the list when pressing escape from the focused item", () => { + select.value = "value-JavaScript"; + input.value = "la"; EVENTS.keyupA(input); - assert.ok(!list.hidden && list.children.length, 'should display the option list with options'); + assert.ok( + !list.hidden && list.children.length, + "should display the option list with options" + ); EVENTS.keydownArrowDown(input); const focusedOption = document.activeElement; - assert.equal(focusedOption.textContent, 'Erlang', 'should focus the first item in the list'); + assert.equal( + focusedOption.textContent, + "Erlang", + "should focus the first item in the list" + ); EVENTS.keydownEscape(focusedOption); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(list.children.length, 0, 'should empty the option list'); - assert.equal(select.value, 'value-JavaScript', 'should not change the value of the select'); - assert.equal(input.value, 'la', 'should not change the value in the input'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(list.children.length, 0, "should empty the option list"); + assert.equal( + select.value, + "value-JavaScript", + "should not change the value of the select" + ); + assert.equal(input.value, "la", "should not change the value in the input"); }); - it('should focus the input and hide the list when pressing up from the first item in the list', () => { - input.value = 'la'; + it("should focus the input and hide the list when pressing up from the first item in the list", () => { + input.value = "la"; EVENTS.keyupA(input); - assert.ok(!list.hidden, 'should display the option list'); - assert.equal(list.children.length, 2, 'should filter the item by the string being present in the option'); + assert.ok(!list.hidden, "should display the option list"); + assert.equal( + list.children.length, + 2, + "should filter the item by the string being present in the option" + ); EVENTS.keydownArrowDown(input); const focusedOption = document.activeElement; - assert.equal(focusedOption.textContent, 'Erlang', 'should focus the first item in the list'); + assert.equal( + focusedOption.textContent, + "Erlang", + "should focus the first item in the list" + ); EVENTS.keydownArrowUp(focusedOption); - assert.ok(list.hidden, 'should hide the option list'); - assert.equal(document.activeElement, input, 'should focus the input'); + assert.ok(list.hidden, "should hide the option list"); + assert.equal(document.activeElement, input, "should focus the input"); }); + + it("should display the full list when the input is pristine (fresh selection or empty)", () => {}); + it("should display the filtered list when the input is dirty (characters inputted)", () => {}); + it("should show a clear button when the input has a selected value present", () => {}); + it("should display a clear input button when the combo box has a selected value", () => {}); + it("should clear the input when the clear button is clicked", () => {}); + it("should update the filter when the input is changed", () => {}); }); From 11a22fb85ddb08f2a9c8ac2518e69d0b193d82fc Mon Sep 17 00:00:00 2001 From: William Bagayoko Date: Tue, 2 Jun 2020 00:29:37 -0500 Subject: [PATCH 02/18] 3479 Subsequent Selection --- src/js/components/combo-box.js | 119 ++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/src/js/components/combo-box.js b/src/js/components/combo-box.js index 504392c599..a752b3c02c 100644 --- a/src/js/components/combo-box.js +++ b/src/js/components/combo-box.js @@ -4,42 +4,27 @@ const behavior = require("../utils/behavior"); const { prefix: PREFIX } = require("../config"); const { CLICK } = require("../events"); -const COMBO_BOX = `.${PREFIX}-combo-box`; - -const INPUT_CLASS = `${PREFIX}-combo-box__input`; -const LIST_CLASS = `${PREFIX}-combo-box__list`; -const LIST_OPTION_CLASS = `${PREFIX}-combo-box__list-option`; -const STATUS_CLASS = `${PREFIX}-combo-box__status`; +const COMBO_BOX_CLASS = `${PREFIX}-combo-box`; +const SELECT_CLASS = `${COMBO_BOX_CLASS}__select`; +const INPUT_CLASS = `${COMBO_BOX_CLASS}__input`; +const INPUT_DIRTY_CLASS = `${INPUT_CLASS}--dirty`; +const CLEAR_INPUT_BUTTON_CLASS = `${COMBO_BOX_CLASS}__clear-input`; +const LIST_CLASS = `${COMBO_BOX_CLASS}__list`; +const LIST_OPTION_CLASS = `${COMBO_BOX_CLASS}__list-option`; const LIST_OPTION_FOCUSED_CLASS = `${LIST_OPTION_CLASS}--focused`; +const STATUS_CLASS = `${COMBO_BOX_CLASS}__status`; -const SELECT = `.${PREFIX}-combo-box__select`; +const COMBO_BOX = `.${COMBO_BOX_CLASS}`; +const SELECT = `.${SELECT_CLASS}`; const INPUT = `.${INPUT_CLASS}`; const LIST = `.${LIST_CLASS}`; const LIST_OPTION = `.${LIST_OPTION_CLASS}`; const LIST_OPTION_FOCUSED = `.${LIST_OPTION_FOCUSED_CLASS}`; const STATUS = `.${STATUS_CLASS}`; -/** - * Determine if the key code of an event is printable - * - * @param {number} keyCode The key code of the event - * @returns {boolean} true is the key code is printable - */ -const isPrintableKeyCode = keyCode => { - return ( - (keyCode > 47 && keyCode < 58) || // number keys - keyCode === 32 || // space - keyCode === 8 || // backspace - (keyCode > 64 && keyCode < 91) || // letter keys - (keyCode > 95 && keyCode < 112) || // numpad keys - (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) - (keyCode > 218 && keyCode < 223) // [\]' (in order) - ); -}; - /** * The elements within the combo box. - * @typedef {Object} ComboBoxElements + * @typedef {Object} ComboBoxContext * @property {HTMLElement} comboBoxEl * @property {HTMLSelectElement} selectEl * @property {HTMLInputElement} inputEl @@ -53,9 +38,9 @@ const isPrintableKeyCode = keyCode => { * combo box component. * * @param {HTMLElement} el the element within the combo box - * @returns {ComboBoxElements} elements + * @returns {ComboBoxContext} elements */ -const getComboBoxElements = el => { +const getComboBoxContext = el => { const comboBoxEl = el.closest(COMBO_BOX); if (!comboBoxEl) { @@ -73,7 +58,17 @@ const getComboBoxElements = el => { const statusEl = comboBoxEl.querySelector(STATUS); const focusedOptionEl = comboBoxEl.querySelector(LIST_OPTION_FOCUSED); - return { comboBoxEl, selectEl, inputEl, listEl, statusEl, focusedOptionEl }; + const isPristine = !inputEl.classList.contains(INPUT_DIRTY_CLASS); + + return { + comboBoxEl, + selectEl, + inputEl, + listEl, + statusEl, + focusedOptionEl, + isPristine + }; }; /** @@ -82,7 +77,17 @@ const getComboBoxElements = el => { * @param {HTMLElement} el The initial element within the combo box component */ const enhanceComboBox = el => { - const { comboBoxEl, selectEl } = getComboBoxElements(el); + const comboBoxEl = el.closest(COMBO_BOX); + + if (!comboBoxEl) { + throw new Error(`Element is missing outer ${COMBO_BOX}`); + } + + const selectEl = comboBoxEl.querySelector(SELECT); + + if (!selectEl) { + throw new Error(`${COMBO_BOX} is missing inner ${SELECT}`); + } const selectId = selectEl.id; const listId = `${selectId}--list`; @@ -137,6 +142,7 @@ const enhanceComboBox = el => { role="combobox" ${additionalAttributes.join(" ")} >`, + ``, `