diff --git a/sass/components/_chips.scss b/sass/components/_chips.scss index 5104445628..0750b176d5 100644 --- a/sass/components/_chips.scss +++ b/sass/components/_chips.scss @@ -75,7 +75,6 @@ flex-wrap: wrap; border: none; - border-bottom: 1px solid var(--md-sys-color-on-surface-variant); box-shadow: none; margin: 0 0 8px 0; @@ -90,8 +89,12 @@ box-shadow: 0 1px 0 0 var(--md-sys-color-primary); } - &:hover { - cursor: text; + &.input-field { + border-bottom: 1px solid var(--md-sys-color-on-surface-variant); + + &:hover { + cursor: text; + } } input:not([type]):not(.browser-default).input { diff --git a/spec/tests/chips/chipsSpec.js b/spec/tests/chips/chipsSpec.js index 68db00185f..1b65ddc647 100644 --- a/spec/tests/chips/chipsSpec.js +++ b/spec/tests/chips/chipsSpec.js @@ -1,10 +1,12 @@ /* eslint-disable no-undef */ describe('Chips', () => { - const fixture = `
- - -`; + const fixture = ` + + + + +`; beforeEach(() => { XloadHtml(fixture); @@ -21,11 +23,29 @@ describe('Chips', () => { } ] }); - M.Chips.init(document.querySelector('.chips-placeholder'), { + M.Chips.init(document.querySelector('.chips.input-field'), { + allowUserInput: true + }); + M.Chips.init(document.querySelector('.chips-initial.input-field'), { + allowUserInput: true, + data: [ + { id: 12, text: 'Apple' }, + { id: 13, text: 'Microsoft' }, + { + id: 42, + text: 'Google', + image: + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' + } + ] + }); + M.Chips.init(document.querySelector('.chips-placeholder.input-field'), { + allowUserInput: true, placeholder: 'Enter a tag', secondaryPlaceholder: '+Tag' }); - M.Chips.init(document.querySelector('.chips-autocomplete'), { + M.Chips.init(document.querySelector('.chips-autocomplete.input-field'), { + allowUserInput: true, autocompleteOptions: { data: [ { id: 12, text: 'Apple' }, @@ -38,20 +58,21 @@ describe('Chips', () => { afterEach(() => XunloadFixtures()); describe('chips plugin', () => { - let chips, input; + let chips, chipsUserInput, input; it('should work with multiple initializations', () => { chips = document.querySelector('.chips'); M.Chips.init(chips); M.Chips.init(chips); M.Chips.init(chips); - M.Chips.init(chips); - input = chips.querySelectorAll('input'); + chipsUserInput = document.querySelector('.chips.input-field'); + M.Chips.init(chips, {allowUserInput: true}); + input = chipsUserInput.querySelectorAll('input'); expect(input.length).toEqual(1, 'Should dynamically generate chips structure.'); }); it('should be able to add chip', (done) => { - chips = document.querySelector('.chips'); + chips = document.querySelector('.chips.input-field'); input = chips.querySelector('input'); input.value = 'one'; keydown(input, 13); @@ -68,11 +89,12 @@ describe('Chips', () => { }); it('should be able to delete chip', (done) => { - chips = document.querySelector('.chips.chips-initial'); - input = chips.querySelector('input'); + chips = document.querySelector('.chips.chips-initial.input-field'); let numChips = chips.querySelectorAll('.chip').length; expect(numChips).toEqual(3, '3 initial chips should have been added'); - click(chips.querySelector('.chip .close')); + let chipCloseButton = chips.querySelectorAll('.chip .close'); + expect(chipCloseButton.length).toEqual(3, 'expected all chips to have close button'); + click(chipCloseButton[0]); setTimeout(() => { numChips = chips.querySelectorAll('.chip').length; expect(numChips).toEqual(2, 'one chip should have been deleted'); @@ -81,7 +103,7 @@ describe('Chips', () => { }); it('should have working callbacks', (done) => { - chips = document.querySelector('.chips'); + chips = document.querySelector('.chips.input-field'); let chipWasAdded = false; let chipAddedElem = null; let chipSelect = false; @@ -90,6 +112,7 @@ describe('Chips', () => { let chipDeleted = null; M.Chips.init(chips, { + allowUserInput: true, data: [{ id: 'One' }, { id: 'Two' }, { id: 'Three' }], onChipAdd: (chipsEl, chipEl) => { chipAddedElem = chipEl; diff --git a/spec/tests/datepicker/datepickerSpec.js b/spec/tests/datepicker/datepickerSpec.js index d2cc8f613d..63b91596a1 100644 --- a/spec/tests/datepicker/datepickerSpec.js +++ b/spec/tests/datepicker/datepickerSpec.js @@ -7,14 +7,16 @@ describe('Datepicker Plugin', () => { `; - beforeEach(() => { - XloadHtml(fixture); - M.Datepicker.init(document.querySelectorAll('.datepicker')); - }); + beforeEach(() => XloadHtml(fixture)); afterEach(() => XunloadFixtures()); describe('Datepicker', () => { + afterEach(() => { + M.Datepicker.getInstance(document.querySelector('.datepicker')).destroy(); + }); + it('should open and close programmatically', (done) => { + M.Datepicker.init(document.querySelectorAll('.datepicker')); const input = document.querySelector('#datepickerInput'); const modal = document.querySelector('.datepicker-modal'); expect(modal).toBeHidden('Should be hidden before datepicker input is focused.'); @@ -39,17 +41,18 @@ describe('Datepicker Plugin', () => { it('can have a string format', (done) => { const input = document.querySelector('#datepickerInput'); const today = new Date(); - M.Datepicker.init(input, { format: 'mm/dd/yyyy' }).open(); - M.Datepicker.getInstance(input).open(); + M.Datepicker.init(input, { format: 'mm/dd/yyyy' }); + const datepicker = M.Datepicker.getInstance(input); + datepicker.open(); setTimeout(() => { const day1 = document.querySelector('.datepicker-modal button[data-day="1"]'); day1.click(); + document.querySelector('.datepicker-done').click(); setTimeout(() => { const year = today.getFullYear(); - let month = today.getMonth() + 1; - month = month < 10 ? `0${month}` : month; - const value = M.Datepicker.getInstance(input).toString(); - expect(value).toEqual(`${month}/01/${year}`); + const month = today.getMonth() + 1; + const value = datepicker.toString(); + expect(value).toEqual(`${month < 10 ? `0${month}` : month}/01/${year}`); done(); }, 400); }, 400); @@ -58,20 +61,57 @@ describe('Datepicker Plugin', () => { it('can have a format function', (done) => { const input = document.querySelector('#datepickerInput'); const today = new Date(); - const formatFn = (date) => `${date.getFullYear() - 100}-${date.getMonth() + 1}-99`; - M.Datepicker.init(input, { format: formatFn }).open(); - M.Datepicker.getInstance(input).open(); + const formatFn = `${today.getFullYear() - 100}-${today.getMonth() + 1}-99`; + M.Datepicker.init(input, { format: formatFn }); + const datepicker = M.Datepicker.getInstance(input); + datepicker.open(); setTimeout(() => { const day1 = document.querySelector('.datepicker-modal button[data-day="1"]'); day1.click(); + document.querySelector('.datepicker-done').click(); setTimeout(() => { const year = today.getFullYear() - 100; const month = today.getMonth() + 1; - const value = M.Datepicker.getInstance(input).toString(); - expect(value).toEqual(`${year}-${month}-99`); + expect(datepicker.toString()).toEqual(`${year}-${month < 10 ? `0${month}` : month}-99`); done(); }, 400); }, 400); }); + + it('can change the calendar modal selected date by input', (done) => { + const input = document.querySelector('#datepickerInput'); + M.Datepicker.init(input, { format: 'mm/dd/yyyy' }); + const today = new Date(); + let month = today.getMonth(); + const year = today.getFullYear() - 44; + const day = 11; + input.value = `${month < 10 ? `0${month}` : month}/${day}/${year}`; + input.dispatchEvent( + new KeyboardEvent('change', { bubbles: true, cancelable: true }) + ); + keydown(input, 13); + setTimeout(() => { + expect(document.querySelector('.datepicker-modal')).toHaveClass( + 'open', + 'modal should be shown after input is submitted.' + ); + const selectMonthElem = document.querySelector('.datepicker-select.orig-select-month'); + const selectYearElem = document.querySelector('.datepicker-select.orig-select-year'); + const selectedDayElem = document.querySelector(`.datepicker-row td[data-day="${day}"]`); + expect(selectMonthElem.querySelector('option[selected="selected"]').value === (month - 1).toString()).toEqual(true, `selected month should be ${month}, given value ${selectMonthElem.querySelector('option[selected="selected"]').value}`); + expect(selectYearElem.querySelector('option[selected="selected"]').value === year.toString()).toEqual(true, `selected year should be ${year}, given value ${selectYearElem.querySelector('option[selected="selected"]').value}`); + expect(selectedDayElem.classList.contains('is-selected')).toEqual(true, `selected day should be ${day}, given value ${selectedDayElem.classList}`); + done(); + }, 10); + }); + + it('should have a date range input field if date range option is enabled', (done) => { + const input = document.querySelector('#datepickerInput'); + M.Datepicker.init(input, { isDateRange: true }); + setTimeout(() => { + expect(document.querySelector('.datepicker-end-date')).toExist('end date input should exist'); + done(); + }, 10); + }); }); }); diff --git a/src/chips.ts b/src/chips.ts index 9680f59ec8..a63101ed8c 100644 --- a/src/chips.ts +++ b/src/chips.ts @@ -53,6 +53,11 @@ export interface ChipsOptions extends BaseOptions{ * @default 'material-icons' */ closeIconClass: string; + /** + * Specifies option to render user input field + * @default false; + */ + allowUserInput: boolean; /** * Callback for chip add. * @default null @@ -78,6 +83,7 @@ let _defaults: ChipsOptions = { autocompleteOptions: {}, autocompleteOnly: false, limit: Infinity, + allowUserInput: false, onChipAdd: null, onChipSelect: null, onChipDelete: null @@ -109,26 +115,25 @@ export class Chips extends Component