diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7b58b..cc3e217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,26 @@ # CHANGELOG -## 10.3.5 +## 10.4.0 -`zoo-tooltip` - added CSS variable `zoo-tooltip-z-index` to change predefined z-index in case of conflict with some other 3rd party components -`zoo-toast` - added CSS variable `zoo-toast-z-index` to change predefined z-index in case of conflict with some other 3rd party components -`zoo-grid` - added CSS variable `zoo-grid-z-index` to change predefined z-index in case of conflict with some other 3rd party components -`zoo-spinner` - added CSS variable `zoo-spinner-z-index` to change predefined z-index in case of conflict with some other 3rd party components +Semi-Breaking Changes: + - `zoo-input-tag`: + - added `clearSelection()` method to programmatically clear current component selection + - `zoo-tag-options` can contain not only `zoo-tag` elements, but custom markup + - `zoo-tag-options` are now stacked vertically + - `zoo-tag-options` - changed left,right padding to `15px` and removed `5px` gap, `--input-tag-padding-top-bottom` + and `--input-tag-padding-left-right` to control component padding + - selection will be updated when `data-initial-value` attribute changes + - `--input-tag-options-max-height` CSS variable to control max height of options list overlay + - `--input-tag-options-overflow` CSS variable to control options list scrolls + - `zoo-tag` with `type="tag"` uses same border radius as other components + + CSS changes: + - `--item-hovered` - new CSS variable to control color of hovered areas in components + - `--input-disabled` - new CSS variable to control color of disabled form components + - `zoo-tooltip` - new CSS variable `zoo-tooltip-z-index` to change predefined z-index in case of conflict with some other 3rd party components + - `zoo-toast` - new CSS variable `zoo-toast-z-index` to change predefined z-index in case of conflict with some other 3rd party components + - `zoo-grid` - new CSS variable `zoo-grid-z-index` to change predefined z-index in case of conflict with some other 3rd party components + - `zoo-spinner` - new CSS variable `zoo-spinner-z-index` to change predefined z-index in case of conflict with some other 3rd party components ## 10.3.4 diff --git a/docs/index.html b/docs/index.html index 7ee1a6c..0568727 100644 --- a/docs/index.html +++ b/docs/index.html @@ -102,6 +102,7 @@

Input/Textarea

+ @@ -127,6 +128,42 @@

Input/Textarea

Birds are a group of warm-blooded vertebrates constituting the class Aves /ˈeɪviːz/, characterised by feathers, toothless beaked jaws, the laying of hard-shelled eggs, a high metabolic rate, a four-chambered heart, and a strong yet lightweight skeleton.
+ + + Reptile + + Reptiles, in common parlance, are a group of tetrapods with an ectothermic ('cold-blooded') metabolism and amniotic development. + + no results + + + + + + Start typing to see available tags. Tag filtering should be done on client side by listening on 'input' event on slotted input. + At least one tag should be selected! + + + 🐕 Dog + + + 🐈 Cat + + + 🐦‍⬛ Bird + + + 🐟 Aquatic + + + 🦎 Reptile + no results diff --git a/docs/main.js b/docs/main.js index 61e10e0..abc955d 100644 --- a/docs/main.js +++ b/docs/main.js @@ -241,7 +241,7 @@ handleExpandAction('#row-1-actions .expander', '#row-1-content'); handleExpandAction('#row-2-actions .expander', '#row-2-content'); handleExpandAction('#row-3-actions .expander', '#row-3-content'); -const tagInfos = ['dog', 'cat', 'bird', 'aquatic']; +const tagInfos = ['dog', 'cat', 'bird', 'aquatic', 'reptile']; const inputTag = document.querySelector('zoo-input-tag'); document.getElementById('input-tag').addEventListener('input', e => { inputTag.querySelectorAll('zoo-input-tag-option').forEach(o => o.style.display = 'none'); @@ -250,7 +250,6 @@ document.getElementById('input-tag').addEventListener('input', e => { const val = e.target.value; if (!val) return; const matchedTags = tagInfos.filter(i => i.toLowerCase().indexOf(val.toLowerCase()) > -1); - const docFrag = document.createDocumentFragment(); if (matchedTags && matchedTags.length > 0) { matchedTags.forEach(m => { document.getElementById(`${m}-tag`).style.display = 'flex'; @@ -258,4 +257,23 @@ document.getElementById('input-tag').addEventListener('input', e => { } else { noResultsSpan.style.display = 'flex'; } -}); \ No newline at end of file +}); + +const customTagInput = document.getElementById('input-tag-custom-input'); +document.getElementById('input-tag-custom').addEventListener('input', e => { + customTagInput.querySelectorAll('zoo-input-tag-option').forEach(o => o.style.display = 'none'); + const noResultsSpan = document.getElementById('input-tag-custom').querySelector('*[slot="no-results"]'); + if (noResultsSpan) noResultsSpan.style.display = 'none'; + const val = e.target.value; + if (!val) return; + const matchedTags = tagInfos.filter(i => i.toLowerCase().indexOf(val.toLowerCase()) > -1); + if (matchedTags && matchedTags.length > 0) { + matchedTags.forEach(m => { + document.getElementById(`${m}-tagc`).style.display = 'flex'; + }); + } else { + noResultsSpan.style.display = 'flex'; + } + const unMatchedTags = tagInfos.filter(i => i.toLowerCase().indexOf(val.toLowerCase()) === -1); + unMatchedTags.forEach( m => document.getElementById(`${m}-tagc`).style.display = 'none') +}); diff --git a/package-lock.json b/package-lock.json index 980b398..483fa39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@zooplus/zoo-web-components", - "version": "10.3.4", + "version": "10.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@zooplus/zoo-web-components", - "version": "10.3.4", + "version": "10.4.0", "license": "MIT", "devDependencies": { "axe-core": "^4.3.3", @@ -481,9 +481,9 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", - "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", diff --git a/package.json b/package.json index 50330ef..ab71b66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@zooplus/zoo-web-components", - "version": "10.3.4", + "version": "10.4.0", "main": "dist/zoo-web-components.js", "sideEffects": false, "files": [ diff --git a/src/zoo-modules/form/checkbox/checkbox.css b/src/zoo-modules/form/checkbox/checkbox.css index 20cb587..9f0eb13 100644 --- a/src/zoo-modules/form/checkbox/checkbox.css +++ b/src/zoo-modules/form/checkbox/checkbox.css @@ -84,7 +84,7 @@ svg path { } :host([disabled]) svg { - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); } .checkbox { diff --git a/src/zoo-modules/form/input-tag/input-tag-option.css b/src/zoo-modules/form/input-tag/input-tag-option.css index c63e595..3da54a9 100644 --- a/src/zoo-modules/form/input-tag/input-tag-option.css +++ b/src/zoo-modules/form/input-tag/input-tag-option.css @@ -7,7 +7,3 @@ font-size: 12px; gap: 3px; } - -:host(:hover) { - background: var(--primary-ultralight); -} diff --git a/src/zoo-modules/form/input-tag/input-tag.css b/src/zoo-modules/form/input-tag/input-tag.css index 02b3d38..ec1388d 100644 --- a/src/zoo-modules/form/input-tag/input-tag.css +++ b/src/zoo-modules/form/input-tag/input-tag.css @@ -4,6 +4,10 @@ width: 100%; height: max-content; box-sizing: border-box; + + --input-tag-padding-top-bottom-default: 13px; + --input-tag-padding-left-right-default: 15px; + --input-tag-padding-reduced: calc(var(--input-tag-padding-top-bottom, var(--input-tag-padding-top-bottom-default)) - 1px) calc(var(--input-tag-padding-left-right, var(--input-tag-padding-left-right-default)) - 1px); } #input-wrapper { @@ -14,7 +18,7 @@ gap: 5px; font-size: 14px; line-height: 20px; - padding: 13px 15px; + padding: var(--input-tag-padding-top-bottom, var(--input-tag-padding-top-bottom-default)) var(--input-tag-padding-left-right, var(--input-tag-padding-left-right-default)); border: 1px solid #767676; border-radius: 5px; color: #555; @@ -26,7 +30,7 @@ :host(:focus-within) #input-wrapper { border: 2px solid #555; - padding: 12px 14px; + padding: var(--input-tag-padding-reduced); } :host([show-tags]) #input-wrapper { @@ -35,7 +39,7 @@ :host([invalid]) #input-wrapper { border: 2px solid var(--warning-mid); - padding: 12px 14px; + padding: var(--input-tag-padding-reduced); } ::slotted(input) { @@ -57,15 +61,16 @@ zoo-label { position: absolute; flex-wrap: wrap; background: white; - padding: 5px; + padding: 5px var(--input-tag-padding-left-right, var(--input-tag-padding-left-right-default)); border: 1px solid #555; border-radius: 0 0 3px 3px; - gap: 5px; left: -1px; - top: 90%; + top: calc(90% + 2px); border-top: 0; width: calc(100% + 2px); box-sizing: border-box; + max-height: var(--input-tag-options-max-height, fit-content); + overflow: var(--input-tag-options-overflow, auto); } :host(:focus-within) #tag-options, @@ -73,6 +78,8 @@ zoo-label { border-width: 2px; width: calc(100% + 4px); left: -2px; + padding-left: calc(var(--input-tag-padding-left-right, var(--input-tag-padding-left-right-default)) - 1px); + padding-right: calc(var(--input-tag-padding-left-right, var(--input-tag-padding-left-right-default)) - 1px); } :host([invalid]) #tag-options { @@ -98,5 +105,16 @@ zoo-cross-icon { } ::slotted(zoo-input-tag-option) { - flex: 1 0 30%; + box-sizing: border-box; + width: 100%; } + +::slotted(zoo-input-tag-option:hover), +::slotted(zoo-input-tag-option[selected]:hover) { + background: var(--item-hovered, #E6E6E6); +} + +::slotted(zoo-input-tag-option[selected]) { + background: var(--primary-ultralight); +} + diff --git a/src/zoo-modules/form/input-tag/input-tag.js b/src/zoo-modules/form/input-tag/input-tag.js index bcd579d..f8f48af 100644 --- a/src/zoo-modules/form/input-tag/input-tag.js +++ b/src/zoo-modules/form/input-tag/input-tag.js @@ -34,9 +34,10 @@ export class InputTag extends FormElement { })); this.addEventListener('keydown', e => { - if (e.key === ' ' && e.target.tagName === 'ZOO-TAG') { + if ((e.key === ' ' || e.key === 'Enter') + && (e.target.tagName === 'ZOO-TAG' || e.target.tagName === 'ZOO-INPUT-TAG-OPTION')) { e.preventDefault(); - this.handleTagSelect(e); + this.toggleOptionSelect(e); } }); this.shadowRoot.querySelector('slot[name="select"]').addEventListener('slotchange', e => { @@ -44,62 +45,134 @@ export class InputTag extends FormElement { this.select && this.registerElementForValidation(this.select); }); this.shadowRoot.querySelector('slot[name="tag-option"]').addEventListener('click', e => { - this.handleTagSelect(e); + this.toggleOptionSelect(e, true); }); } - handleTagSelect(e) { + static get observedAttributes() { + return [...super.observedAttributes, 'data-initial-value']; + } + + attributeChangedCallback(name, oldValue) { + if (name === 'invalid') { + super.attributeChangedCallback(); + } else if (name === 'data-initial-value' && oldValue != null) { + this.handleInitialValues(); + } + } + + toggleOptionSelect(e, withFocusOnInput = false) { const target = this.getElAsParentBySlotName(e.target, 'tag-option'); - const tag = target.querySelector('zoo-tag'); - const selectedValue = tag.getAttribute('data-value'); + if (target && target.hasAttribute('selected')) { + const dataElem = target.querySelector('[data-value]'); + const tagInInput = this.shadowRoot.querySelector(`zoo-tag[data-value="${dataElem.getAttribute('data-value')}"] zoo-cross-icon`); + tagInInput.dispatchEvent(new Event('click')); + } else if(target) { + this.handleTagSelect(target); + } + if (withFocusOnInput) { + this.input.focus(); + } + } + + handleTagSelect(tagOptionSlot) { + const optionElement = tagOptionSlot.querySelector('zoo-tag, [data-option-content]'); + const selectedValue = optionElement.getAttribute('data-value'); const options = [...this.select.querySelectorAll('option')]; const matchedOptionIndex = options.findIndex(o => o.value === selectedValue); + const hideOptionsAfterSelect = !this.hasAttribute('show-tags-after-select'); + if (matchedOptionIndex > -1 && !this.select.options[matchedOptionIndex].selected) { this.select.options[matchedOptionIndex].selected = true; this.select.options[matchedOptionIndex].setAttribute('selected', ''); this.select.dispatchEvent(new Event('input')); - this.input.value = ''; - const clonedTag = tag.cloneNode(true); - const crossIcon = document.createElement('zoo-cross-icon'); - crossIcon.setAttribute('tabindex', 0); - crossIcon.setAttribute('slot', 'post'); - crossIcon.addEventListener('click', () => this.deselectOption(clonedTag, matchedOptionIndex)); - crossIcon.addEventListener('keydown', e => { - if (e.key === ' ') { - e.preventDefault(); - this.deselectOption(clonedTag, matchedOptionIndex); - } - }); - clonedTag.appendChild(crossIcon); - this.inputSlot.before(clonedTag); + if (hideOptionsAfterSelect) { + this.input.value = ''; + } + optionElement.parentElement.setAttribute('selected', ''); + optionElement.parentElement.setAttribute('aria-selected', 'true'); + let tagElementFromSelection = this.createSelectedTagElement(optionElement, matchedOptionIndex); + this.inputSlot.before(tagElementFromSelection); } - this.removeAttribute('show-tags'); - this.input.focus(); + if (hideOptionsAfterSelect) { + this.removeAttribute('show-tags'); + } + } + + createSelectedTagElement(selectedOptionElement, matchedOptionIndex) { + let tagElementForInput; + const dataValue = selectedOptionElement.getAttribute('data-value'); + if(selectedOptionElement.tagName === 'ZOO-TAG') { + tagElementForInput = selectedOptionElement.cloneNode(true); + } else { + tagElementForInput = document.createElement('ZOO-TAG'); + tagElementForInput.setAttribute('slot', 'tag'); + tagElementForInput.setAttribute('type', 'tag'); + tagElementForInput.setAttribute('data-value', dataValue); + tagElementForInput.setAttribute('tabindex', '0'); + tagElementForInput.insertAdjacentHTML('beforeend', `${dataValue}`); + } + + const crossIcon = document.createElement('zoo-cross-icon'); + crossIcon.setAttribute('tabindex', '0'); + crossIcon.setAttribute('slot', 'post'); + crossIcon.setAttribute('role', 'button'); + crossIcon.setAttribute('aria-label', 'Deselect ' + dataValue); + crossIcon.addEventListener('click', () => this.deselectOption(tagElementForInput, matchedOptionIndex, selectedOptionElement)); + crossIcon.addEventListener('keydown', e => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + this.deselectOption(tagElementForInput, matchedOptionIndex, selectedOptionElement); + } + }); + tagElementForInput.appendChild(crossIcon); + return tagElementForInput; } handleInitialValues() { - const tagOptions = [...this.children].filter(el => el.tagName === 'ZOO-INPUT-TAG-OPTION'); + let tagOptions = []; + [].push.apply(tagOptions, this.children); + tagOptions = tagOptions.filter(el => el.tagName === 'ZOO-INPUT-TAG-OPTION'); const defaultValues = this.hasAttribute('data-initial-value') ? this.getAttribute('data-initial-value') .split(',') .map(value => value.trim()) + .filter(value => !!value) : null; - if (tagOptions && defaultValues) { - [...tagOptions].forEach((tagOption) => { - if (defaultValues.includes([...tagOption.children][0].getAttribute('data-value'))) { - this.handleTagSelect({ - target: tagOption - }); + if (tagOptions && defaultValues && defaultValues.length) { + tagOptions.forEach((tagOption) => { + if (!tagOption.hasAttribute('selected') && defaultValues.includes([...tagOption.children][0].getAttribute('data-value'))) { + this.handleTagSelect(tagOption); } }); } } - deselectOption(clonedTag, matchedOptionIndex) { - clonedTag.remove(); + deselectOption(tagElementForInput, matchedOptionIndex, selectedOptionElement) { + tagElementForInput.remove(); this.select.options[matchedOptionIndex].selected = false; this.select.options[matchedOptionIndex].removeAttribute('selected'); this.select.dispatchEvent(new Event('input')); + if (selectedOptionElement) { + selectedOptionElement.parentElement.removeAttribute('selected'); + selectedOptionElement.parentElement.setAttribute('aria-selected', 'false'); + } + } + + clearSelection() { + this.shadowRoot.querySelectorAll('#input-wrapper > zoo-tag').forEach(el => el.remove()); + this.select.querySelectorAll(':checked').forEach(option => { + option.selected = false; + option.removeAttribute('selected'); + }); + this.shadowRoot.querySelectorAll('slot[name="tag-option"]').forEach(slot => + slot.assignedElements().forEach(tagOption => { + tagOption.removeAttribute('selected'); + tagOption.setAttribute('aria-selected', 'false'); + })); + this.input.value = ''; + this.select.dispatchEvent(new Event('input')); + this.input.dispatchEvent(new Event('input')); this.input.focus(); } diff --git a/src/zoo-modules/form/input-tag/input-tag.spec.mjs b/src/zoo-modules/form/input-tag/input-tag.spec.mjs index 774980e..5b66941 100644 --- a/src/zoo-modules/form/input-tag/input-tag.spec.mjs +++ b/src/zoo-modules/form/input-tag/input-tag.spec.mjs @@ -26,7 +26,7 @@ describe('Zoo input tag', function () { }); it('should render component with initial values', async () => { - const optionsStatus = await page.evaluate(async () => { + const ret = await page.evaluate(async () => { document.body.innerHTML = ` @@ -52,11 +52,79 @@ describe('Zoo input tag', function () { `; const options = [...document.querySelectorAll('option')]; await new Promise(r => setTimeout(r, 10)); - return options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + + const input = document.querySelector('zoo-input-tag'); + let selectedTags = [] + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags.push(el.textContent.trim())); + const optionsStatus = options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + return { + optionsStatus, + selectedTags + } }); + expect(ret.optionsStatus).toEqual(['Dog']); + expect(ret.optionsStatus).not.toContain('Cat'); + expect(ret.selectedTags).toContain('Dog'); + expect(ret.selectedTags).not.toContain('Cat'); + }); + + it('should update initial selection when the attribute changes', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + + Cat + + The cat (Felis catus) is a domestic species of small carnivorous mammal. + + + `; + let options = [...document.querySelectorAll('option')]; + await new Promise(r => setTimeout(r, 10)); + + const input = document.querySelector('zoo-input-tag'); + let selectedTags = [] + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags.push(el.textContent.trim())); + const optionsStatus = options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + + input.setAttribute("data-initial-value", "Dog,Cat"); + await new Promise(r => setTimeout(r, 10)); - expect(optionsStatus).toEqual(['Dog']); - expect(optionsStatus).not.toContain(['Cat']); + let selectedTagsAfter = []; + options = [...document.querySelectorAll('option')]; + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTagsAfter.push(el.textContent.trim())); + const optionsStatusAfter = options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + + return { + optionsStatus, + selectedTags, + selectedTagsAfter, + optionsStatusAfter + } + }); + expect(ret.optionsStatus).toEqual(['Dog']); + expect(ret.optionsStatus).not.toContain('Cat'); + expect(ret.selectedTags).toContain('Dog'); + expect(ret.selectedTags).not.toContain('Cat'); + + expect(ret.optionsStatusAfter).toContain('Dog'); + expect(ret.optionsStatusAfter).toContain('Cat'); + expect(ret.selectedTagsAfter).toContain('Dog'); + expect(ret.selectedTagsAfter).toContain('Cat'); }); it('should not render input error', async () => { @@ -132,6 +200,261 @@ describe('Zoo input tag', function () { expect(ret.tagOptionsDisplayWithoutInputValue).toEqual('none'); }); + it('should remove selected tag after clicking cross icon', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + + Cat + + The cat (Felis catus) is a domestic species of small carnivorous mammal. + + + `; + const input = document.querySelector('zoo-input-tag'); + const options = [...document.querySelectorAll('option')]; + await new Promise(r => setTimeout(r, 10)); + + input.shadowRoot.querySelector('#input-wrapper zoo-tag zoo-cross-icon').click() + await new Promise(r => setTimeout(r, 10)); + + let selectedTags = [] + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags.push(el.textContent.trim())); + const optionsStatus = options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + return { + optionsStatus, + selectedTags + } + }); + expect(ret.optionsStatus.length).toEqual(0); + expect(ret.selectedTags.length).toEqual(0); + }); + + it('should toggle tag selection when clicking on option list', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + + Bird + + Birds are a group of warm-blooded vertebrates constituting the class Aves /ˈeɪviːz/. + + + `; + await new Promise(r => setTimeout(r, 10)); + let input = document.querySelector('zoo-input-tag'); + const slottedInput = input.shadowRoot.querySelector('slot[name="input"]').assignedElements()[0]; + slottedInput.value = 123; + slottedInput.dispatchEvent(new Event('input')); + await new Promise(r => setTimeout(r, 10)); + + const tagOption1 = input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements()[0] + tagOption1.click(); + await new Promise(r => setTimeout(r, 10)); + + let selectedTags1stClick = []; + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags1stClick.push(el.textContent.trim())); + let selectedOptions1stClick = []; + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements().forEach(el=> { + if(el.hasAttribute('selected')) { + selectedOptions1stClick.push(el.querySelector('span').textContent.trim()) + } + }); + + tagOption1.click(); + await new Promise(r => setTimeout(r, 10)); + + let selectedTags2stClick = []; + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags2stClick.push(el.textContent.trim())); + let selectedOptions2stClick = []; + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements().forEach(el=> { + if(el.hasAttribute('selected')) { + selectedOptions2stClick.push(el.querySelector('span').textContent.trim()) + } + }); + return { + selectedTags1stClick, + selectedOptions1stClick, + selectedTags2stClick, + selectedOptions2stClick + } + }); + expect(ret.selectedOptions1stClick).toEqual(['Dog']); + expect(ret.selectedTags1stClick).toEqual(['Dog']); + expect(ret.selectedOptions2stClick.length).toEqual(0); + expect(ret.selectedTags2stClick.length).toEqual(0); + }); + + it('should render clicked option as tag with tag content as text', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog content + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + `; + await new Promise(r => setTimeout(r, 10)); + let input = document.querySelector('zoo-input-tag'); + const slottedInput = input.shadowRoot.querySelector('slot[name="input"]').assignedElements()[0]; + slottedInput.value = 123; + slottedInput.dispatchEvent(new Event('input')); + await new Promise(r => setTimeout(r, 10)); + const tagOptions = input.shadowRoot.getElementById('tag-options'); + const tagOptionsDisplayWithInputValue = window.getComputedStyle(tagOptions).display; + const showTagsAttrPresentWithInputValue = input.hasAttribute('show-tags'); + + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements()[0].click(); + await new Promise(r => setTimeout(r, 10)); + + const selectedTag = input.shadowRoot.querySelector('#input-wrapper zoo-tag'); + const selectedTagContent = selectedTag.textContent.trim() + return { + showTagsAttrPresentWithInputValue, + tagOptionsDisplayWithInputValue, + selectedTagContent + }; + }); + expect(ret.showTagsAttrPresentWithInputValue).toBeTrue(); + expect(ret.tagOptionsDisplayWithInputValue).toEqual('flex'); + expect(ret.selectedTagContent).toEqual('Dog content'); + }); + + it('should render clicked option as tag when using custom markup with data-option-content attribute', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + +
Dog content
+ The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. +
+
+ `; + await new Promise(r => setTimeout(r, 10)); + let input = document.querySelector('zoo-input-tag'); + const slottedInput = input.shadowRoot.querySelector('slot[name="input"]').assignedElements()[0]; + slottedInput.value = 123; + slottedInput.dispatchEvent(new Event('input')); + await new Promise(r => setTimeout(r, 10)); + + const tagOptions = input.shadowRoot.getElementById('tag-options'); + const tagOptionsDisplayWithInputValue = window.getComputedStyle(tagOptions).display; + const showTagsAttrPresentWithInputValue = input.hasAttribute('show-tags'); + + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements()[0].click(); + await new Promise(r => setTimeout(r, 10)); + + const selectedTag = input.shadowRoot.querySelector('#input-wrapper zoo-tag'); + const selectedTagContent = selectedTag.textContent.trim() + return { + showTagsAttrPresentWithInputValue, + tagOptionsDisplayWithInputValue, + selectedTagContent + }; + }); + expect(ret.showTagsAttrPresentWithInputValue).toBeTrue(); + expect(ret.tagOptionsDisplayWithInputValue).toEqual('flex'); + expect(ret.selectedTagContent).toEqual('Dog'); + }); + + it('should show and not hide tags on input when attribute set', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + + Bird + + Birds are a group of warm-blooded vertebrates constituting the class Aves /ˈeɪviːz/. + + + `; + await new Promise(r => setTimeout(r, 10)); + let input = document.querySelector('zoo-input-tag'); + const slottedInput = input.shadowRoot.querySelector('slot[name="input"]').assignedElements()[0]; + slottedInput.value = 123; + slottedInput.dispatchEvent(new Event('input')); + await new Promise(r => setTimeout(r, 10)); + const tagOptions = input.shadowRoot.getElementById('tag-options'); + const tagOptionsDisplayWithInputValue = window.getComputedStyle(tagOptions).display; + const showTagsAttrPresentWithInputValue = input.hasAttribute('show-tags'); + + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements()[0].click(); + + await new Promise(r => setTimeout(r, 10)); + + const showTagsAttrPresentWithoutInputValue = input.hasAttribute('show-tags'); + const tagOptionsDisplayWithoutInputValue = window.getComputedStyle(tagOptions).display; + + return { + showTagsAttrPresentWithInputValue, + tagOptionsDisplayWithInputValue, + showTagsAttrPresentWithoutInputValue, + tagOptionsDisplayWithoutInputValue + }; + }); + expect(ret.showTagsAttrPresentWithInputValue).toBeTrue(); + expect(ret.tagOptionsDisplayWithInputValue).toEqual('flex'); + expect(ret.showTagsAttrPresentWithoutInputValue).toBeTrue(); + expect(ret.tagOptionsDisplayWithoutInputValue).toEqual('flex'); + }); + it('should set and then remove invalid attribute from host while setting the value in the slotted select', async () => { const result = await page.evaluate(async () => { document.body.innerHTML = ` @@ -177,4 +500,50 @@ describe('Zoo input tag', function () { expect(result.invalidAfterCrossClick).toBeTrue(); expect(result.selectValueAfterCrossClick).toEqual(''); }); + + it('should clear selection by invoking function', async () => { + const ret = await page.evaluate(async () => { + document.body.innerHTML = ` + + + + At least one tag should be selected! + + + + Dog + + The domestic dog (Canis familiaris or Canis lupus familiaris)[4] is a domesticated descendant of the wolf. + + + + Cat + + The cat (Felis catus) is a domestic species of small carnivorous mammal. + + + `; + const input = document.querySelector('zoo-input-tag'); + input.shadowRoot.querySelector('slot[name="tag-option"]').assignedElements()[1].click(); + await new Promise(r => setTimeout(r, 10)); + + input.clearSelection(); + + const options = [...document.querySelectorAll('option')]; + await new Promise(r => setTimeout(r, 10)); + + let selectedTags = [] + input.shadowRoot.querySelectorAll('#input-wrapper zoo-tag').forEach(el=> selectedTags.push(el.textContent.trim())); + const optionsStatus = options.filter((option) => option.hasAttribute('selected')).map(option => option.value); + return { + optionsStatus, + selectedTags + } + }); + expect(ret.optionsStatus.length).toEqual(0); + expect(ret.selectedTags.length).toEqual(0); + }); }); \ No newline at end of file diff --git a/src/zoo-modules/form/input/input.css b/src/zoo-modules/form/input/input.css index 55dcb1d..cf33b43 100644 --- a/src/zoo-modules/form/input/input.css +++ b/src/zoo-modules/form/input/input.css @@ -42,7 +42,7 @@ ::slotted(input:disabled), ::slotted(textarea:disabled) { border: 1px solid #E6E6E6; - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); color: #767676; cursor: not-allowed; } diff --git a/src/zoo-modules/form/quantity-control/quantity-control.css b/src/zoo-modules/form/quantity-control/quantity-control.css index 0d8ca5c..60b02e1 100644 --- a/src/zoo-modules/form/quantity-control/quantity-control.css +++ b/src/zoo-modules/form/quantity-control/quantity-control.css @@ -30,7 +30,7 @@ div { } ::slotted(button:disabled) { - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); cursor: not-allowed; } diff --git a/src/zoo-modules/form/select/select.css b/src/zoo-modules/form/select/select.css index fb03971..c556fcd 100644 --- a/src/zoo-modules/form/select/select.css +++ b/src/zoo-modules/form/select/select.css @@ -38,7 +38,7 @@ zoo-arrow-icon { ::slotted(select:disabled) { border: 1px solid #E6E6E6; - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); color: #666; } diff --git a/src/zoo-modules/form/toggle-switch/toggle-switch.css b/src/zoo-modules/form/toggle-switch/toggle-switch.css index 03ca650..20bc12a 100644 --- a/src/zoo-modules/form/toggle-switch/toggle-switch.css +++ b/src/zoo-modules/form/toggle-switch/toggle-switch.css @@ -40,7 +40,7 @@ div { } ::slotted(input:disabled) { - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); cursor: not-allowed; } diff --git a/src/zoo-modules/grid/grid-header/grid-header.css b/src/zoo-modules/grid/grid-header/grid-header.css index c14e918..acbd23c 100644 --- a/src/zoo-modules/grid/grid-header/grid-header.css +++ b/src/zoo-modules/grid/grid-header/grid-header.css @@ -15,7 +15,7 @@ button { border: 0; cursor: pointer; border-radius: 5px; - background: #F2F3F4; + background: var(--input-disabled, #F2F3F4); --icon-color: black; } diff --git a/src/zoo-modules/grid/grid/grid.css b/src/zoo-modules/grid/grid/grid.css index 75e4e2e..42727a1 100644 --- a/src/zoo-modules/grid/grid/grid.css +++ b/src/zoo-modules/grid/grid/grid.css @@ -77,7 +77,7 @@ ::slotted(*[slot="row"]:hover), ::slotted(*[slot="row"]:focus) { - background: #E6E6E6; + background: var(--item-hovered, #E6E6E6); } ::slotted(*[slot="norecords"]) { diff --git a/src/zoo-modules/icon/arrow-icon/arrow-icon.css b/src/zoo-modules/icon/arrow-icon/arrow-icon.css index 1ab21d3..027ac6b 100644 --- a/src/zoo-modules/icon/arrow-icon/arrow-icon.css +++ b/src/zoo-modules/icon/arrow-icon/arrow-icon.css @@ -1,6 +1,6 @@ svg { display: flex; - width: var(--width, 24px); - height: var(--height, 24px); + width: var(--icon-width, 24px); + height: var(--icon-height, 24px); fill: var(--icon-color, var(--primary-mid)); } diff --git a/src/zoo-modules/icon/attention-icon/attention-icon.css b/src/zoo-modules/icon/attention-icon/attention-icon.css index 2f5d16e..c8d8ec8 100644 --- a/src/zoo-modules/icon/attention-icon/attention-icon.css +++ b/src/zoo-modules/icon/attention-icon/attention-icon.css @@ -1,7 +1,7 @@ svg { display: flex; padding-right: 5px; - width: var(--width, 18px); - height: var(--height, 18px); + width: var(--icon-width, 18px); + height: var(--icon-height, 18px); fill: var(--icon-color, var(--info-mid)); } diff --git a/src/zoo-modules/icon/cross-icon/cross-icon.css b/src/zoo-modules/icon/cross-icon/cross-icon.css index 315f1f9..c990ec4 100644 --- a/src/zoo-modules/icon/cross-icon/cross-icon.css +++ b/src/zoo-modules/icon/cross-icon/cross-icon.css @@ -1,6 +1,6 @@ svg { display: flex; - width: var(--width, 24px); - height: var(--height, 24px); + width: var(--icon-width, 18px); + height: var(--icon-height, 18px); fill: var(--icon-color, black); } diff --git a/src/zoo-modules/misc/button/button.css b/src/zoo-modules/misc/button/button.css index 7052eac..dc3e141 100644 --- a/src/zoo-modules/misc/button/button.css +++ b/src/zoo-modules/misc/button/button.css @@ -69,9 +69,9 @@ ::slotted(button:disabled) { cursor: not-allowed; - --background: #F2F3F4; - --color-mid: #F2F3F4; - --color-dark: #F2F3F4; + --background: var(--input-disabled, #F2F3F4); + --color-mid: var(--input-disabled, #F2F3F4); + --color-dark: var(--input-disabled, #F2F3F4); --text-normal: #767676; --text-active: #767676; --border: 1px solid #E6E6E6; diff --git a/src/zoo-modules/misc/tag/tag.css b/src/zoo-modules/misc/tag/tag.css index 62f56b7..ccd7fa2 100644 --- a/src/zoo-modules/misc/tag/tag.css +++ b/src/zoo-modules/misc/tag/tag.css @@ -6,7 +6,8 @@ width: max-content; color: var(--color); border-color: var(--color); - max-width: 100px; + max-width: var(--zoo-tag-max-width, 100px); + border-radius: 3px; } :host(:hover) { @@ -32,7 +33,6 @@ ::slotted(*[slot="content"]) { font-size: 12px; - line-height: 16px; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap;