Best JavaScript code snippet using best
MultiSelectAutocomplete.jsx
Source:MultiSelectAutocomplete.jsx
1/* eslint-disable jsx-a11y/interactive-supports-focus, jsx-a11y/role-has-required-aria-props */2// Disabled due to the linter being out of date for combobox role: https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/7893import { h, Fragment } from 'preact';4import { useEffect, useRef, useReducer } from 'preact/hooks';5import { Icon, Button } from '@crayons';6import { Close } from '@images/x.svg';7const KEYS = {8 UP: 'ArrowUp',9 DOWN: 'ArrowDown',10 ENTER: 'Enter',11 ESCAPE: 'Escape',12 DELETE: 'Backspace',13 COMMA: ',',14 SPACE: ' ',15};16const ALLOWED_CHARS_REGEX = /([a-zA-Z0-9])/;17const reducer = (state, action) => {18 switch (action.type) {19 case 'setSelectedItems':20 return {21 ...state,22 selectedItems: action.payload,23 suggestions: [],24 activeDescendentIndex: null,25 };26 case 'setSuggestions':27 return {28 ...state,29 suggestions: action.payload,30 activeDescendentIndex: null,31 };32 case 'updateEditState':33 return {34 ...state,35 editValue: action.payload.editValue,36 inputPosition: action.payload.inputPosition,37 };38 case 'setActiveDescendentIndex':39 return { ...state, activeDescendentIndex: action.payload };40 case 'setIgnoreBlur':41 return { ...state, ignoreBlur: action.payload };42 default:43 return state;44 }45};46export const MultiSelectAutocomplete = ({ labelText, fetchSuggestions }) => {47 const [state, dispatch] = useReducer(reducer, {48 suggestions: [],49 selectedItems: [],50 inputPosition: null,51 editValue: '',52 activeDescendentIndex: null,53 ignoreBlur: false,54 });55 const {56 selectedItems,57 suggestions,58 inputPosition,59 editValue,60 activeDescendentIndex,61 ignoreBlur,62 } = state;63 const inputRef = useRef(null);64 const inputSizerRef = useRef(null);65 const selectedItemsRef = useRef(null);66 const handleInputBlur = () => {67 // Since the input is sometimes removed and rendered in a new location on blur, it's possible that inputRef.current may be null when we complete this check.68 const currentValue = inputRef.current ? inputRef.current.value : '';69 // The input will blur when user selects an option from the dropdown via mouse click. The ignoreBlur boolean lets us know we can ignore this event.70 if (!ignoreBlur && currentValue !== '') {71 selectItem({ selectedItem: currentValue, focusInput: false });72 } else {73 dispatch({ type: 'setSuggestions', payload: [] });74 }75 dispatch({ type: 'setIgnoreBlur', payload: false });76 };77 useEffect(() => {78 const { current: input } = inputRef;79 if (inputPosition !== null) {80 resizeInputToContentSize();81 input.value = editValue;82 const { length: cursorPosition } = editValue;83 input.focus();84 input.setSelectionRange(cursorPosition, cursorPosition);85 } else {86 // Remove inline style added to size the input87 input.style.width = '';88 input.focus();89 }90 }, [inputPosition, editValue]);91 const enterEditState = (editItem, editItemIndex) => {92 inputSizerRef.current.innerText = editItem;93 deselectItem(editItem);94 dispatch({95 type: 'updateEditState',96 payload: { editValue: editItem, inputPosition: editItemIndex },97 });98 };99 const exitEditState = (nextInputValue = '') => {100 inputSizerRef.current.innerText = nextInputValue;101 dispatch({102 type: 'updateEditState',103 payload: {104 editValue: nextInputValue,105 inputPosition: nextInputValue === '' ? null : inputPosition + 1,106 },107 });108 };109 const resizeInputToContentSize = () => {110 inputRef.current.style.width = `${inputSizerRef.current.clientWidth}px`;111 };112 const handleInputChange = async ({ target: { value } }) => {113 // When the input appears inline in "edit" mode, we need to dynamically calculate the width to ensure it occupies the right space114 // (an input cannot resize based on its text content). We use a hidden <span> to track the size.115 inputSizerRef.current.innerText = value;116 if (inputPosition !== null) {117 resizeInputToContentSize();118 }119 const results = await fetchSuggestions(value);120 dispatch({121 type: 'setSuggestions',122 payload: results.filter((item) => !selectedItems.includes(item)),123 });124 };125 const clearInput = () => {126 inputRef.current.value = '';127 dispatch({ type: 'setSuggestions', payload: [] });128 };129 const handleKeyDown = (e) => {130 const { selectionStart, value: currentValue } = inputRef.current;131 switch (e.key) {132 case KEYS.DOWN:133 e.preventDefault();134 if (135 activeDescendentIndex !== null &&136 activeDescendentIndex < suggestions.length - 1137 ) {138 dispatch({139 type: 'setActiveDescendentIndex',140 payload: activeDescendentIndex + 1,141 });142 } else {143 dispatch({ type: 'setActiveDescendentIndex', payload: 0 });144 }145 break;146 case KEYS.UP:147 e.preventDefault();148 dispatch({149 type: 'setActiveDescendentIndex',150 payload:151 activeDescendentIndex >= 1152 ? activeDescendentIndex - 1153 : suggestions.length - 1,154 });155 break;156 case KEYS.ENTER:157 e.preventDefault();158 if (activeDescendentIndex !== null) {159 selectItem({ selectedItem: suggestions[activeDescendentIndex] });160 }161 break;162 case KEYS.ESCAPE:163 e.preventDefault();164 // Clear the input and suggestions165 clearInput();166 break;167 case KEYS.COMMA:168 case KEYS.SPACE:169 e.preventDefault();170 // Accept whatever is in the input before the comma or space.171 // If any text remains after the comma or space, the edit will continue separately172 if (currentValue !== '') {173 selectItem({174 selectedItem: currentValue.slice(0, selectionStart),175 nextInputValue: currentValue.slice(selectionStart),176 });177 }178 break;179 case KEYS.DELETE:180 if (currentValue === '') {181 e.preventDefault();182 editPreviousSelectionIfExists();183 }184 break;185 default:186 if (!ALLOWED_CHARS_REGEX.test(e.key)) {187 e.preventDefault();188 }189 }190 };191 // If there is a previous selection, then pop it into edit mode192 const editPreviousSelectionIfExists = () => {193 if (selectedItems.length > 0 && inputPosition !== 0) {194 const nextEditIndex =195 inputPosition !== null ? inputPosition - 1 : selectedItems.length - 1;196 const item = selectedItems[nextEditIndex];197 deselectItem(item);198 enterEditState(item, nextEditIndex);199 }200 };201 const selectItem = ({202 selectedItem,203 nextInputValue = '',204 focusInput = true,205 }) => {206 // If a user has manually typed an item already selected, reset207 if (selectedItems.includes(selectedItem)) {208 clearInput();209 return;210 }211 // If an item was edited, we want to keep it in the same position in the list212 const insertIndex =213 inputPosition !== null ? inputPosition : selectedItems.length;214 const newSelections = [215 ...selectedItems.slice(0, insertIndex),216 selectedItem,217 ...selectedItems.slice(insertIndex),218 ];219 // We update the hidden selected items list, so additions are announced to screen reader users220 const listItem = document.createElement('li');221 listItem.innerText = selectedItem;222 selectedItemsRef.current.appendChild(listItem);223 exitEditState(nextInputValue);224 dispatch({ type: 'setSelectedItems', payload: newSelections });225 // Clear the text input226 const { current: input } = inputRef;227 input.value = nextInputValue;228 focusInput && input.focus();229 };230 const deselectItem = (deselectedItem) => {231 dispatch({232 type: 'setSelectedItems',233 payload: selectedItems.filter((item) => item !== deselectedItem),234 });235 // We also update the hidden selected items list, so removals are announced to screen reader users236 selectedItemsRef.current.querySelectorAll('li').forEach((selectionNode) => {237 if (selectionNode.innerText === deselectedItem) {238 selectionNode.remove();239 }240 });241 };242 const allSelectedItemElements = selectedItems.map((item, index) => (243 <li key={item} className="w-max">244 <div role="group" aria-label={item} className="flex mr-1 mb-1 w-max">245 <Button246 variant="secondary"247 className="c-autocomplete--multi__selected p-1 cursor-text"248 aria-label={`Edit ${item}`}249 onClick={() => enterEditState(item, index)}250 >251 {item}252 </Button>253 <Button254 variant="secondary"255 className="c-autocomplete--multi__selected p-1"256 aria-label={`Remove ${item}`}257 onClick={() => deselectItem(item)}258 >259 <Icon src={Close} />260 </Button>261 </div>262 </li>263 ));264 // When a user edits a tag, we need to move the input inside the selected items265 const splitSelectionsAt =266 inputPosition !== null ? inputPosition : selectedItems.length;267 const input = (268 <li className="self-center">269 <input270 ref={inputRef}271 autocomplete="off"272 className="c-autocomplete--multi__input"273 aria-activedescendant={274 activeDescendentIndex !== null275 ? suggestions[activeDescendentIndex]276 : null277 }278 aria-autocomplete="list"279 aria-labelledby="multi-select-label selected-items-list"280 type="text"281 onChange={handleInputChange}282 onKeyDown={handleKeyDown}283 onBlur={handleInputBlur}284 />285 </li>286 );287 return (288 <Fragment>289 <span290 ref={inputSizerRef}291 aria-hidden="true"292 className="absolute pointer-events-none opacity-0 p-2"293 />294 <label id="multi-select-label">{labelText}</label>295 {/* A visually hidden list provides confirmation messages to screen reader users as an item is selected or removed */}296 <div className="screen-reader-only">297 <p>Selected items:</p>298 <ul299 ref={selectedItemsRef}300 className="screen-reader-only list-none"301 aria-live="assertive"302 aria-atomic="false"303 aria-relevant="additions removals"304 />305 </div>306 <div className="c-autocomplete--multi relative">307 {/* disabled as the inner input forms the tab stop (this click handler ensures _any_ click on the wrapper focuses the input which may be less wide) */}308 {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}309 <div310 role="combobox"311 aria-haspopup="listbox"312 aria-expanded={suggestions.length > 0}313 aria-owns="listbox1"314 className="c-autocomplete--multi__wrapper flex items-center crayons-textfield cursor-text"315 onClick={() => inputRef.current.focus()}316 >317 <ul id="combo-selected" className="list-none flex flex-wrap w-100">318 {allSelectedItemElements.slice(0, splitSelectionsAt)}319 {inputPosition !== null && input}320 {allSelectedItemElements.slice(splitSelectionsAt)}321 {inputPosition === null && input}322 </ul>323 </div>324 {suggestions.length > 0 ? (325 <ul326 className="c-autocomplete--multi__popover"327 aria-labelledby="multi-select-label"328 role="listbox"329 aria-multiselectable="true"330 id="listbox1"331 >332 {suggestions.map((suggestion, index) => (333 // Focus remains in the input during keyboard use, and event handler is attached to that input334 // eslint-disable-next-line jsx-a11y/click-events-have-key-events335 <li336 id={suggestion}337 role="option"338 aria-selected={index === activeDescendentIndex}339 key={suggestion}340 onClick={() => selectItem({ selectedItem: suggestion })}341 onMouseDown={() =>342 dispatch({ type: 'setIgnoreBlue', payload: true })343 }344 >345 {suggestion}346 </li>347 ))}348 </ul>349 ) : null}350 </div>351 </Fragment>352 );...
MultiInput.jsx
Source:MultiInput.jsx
1import { h, Fragment } from 'preact';2import PropTypes from 'prop-types';3import { useRef, useState, useEffect } from 'preact/hooks';4import { DefaultSelectionTemplate } from '../../shared/components/defaultSelectionTemplate';5const KEYS = {6 ENTER: 'Enter',7 COMMA: ',',8 SPACE: ' ',9 DELETE: 'Backspace',10};11/**12 * Component allowing users to add multiple entries for a given input field that get displayed as destructive pills13 *14 * @param {Object} props15 * @param {string} props.labelText The text for the input's label16 * @param {boolean} props.showLabel Whether the label text should be visible or hidden (for assistive tech users only)17 * @param {string} props.placeholder Input placeholder text18 * @param {string} props.inputRegex Optional regular expression used to restrict the input19 * @param {string} props.validationRegex Optional regular expression used to validate the value of the input20 * @param {Function} props.SelectionTemplate Optional Preact component to render selected items21 */22export const MultiInput = ({23 placeholder,24 inputRegex,25 validationRegex,26 showLabel = true,27 labelText,28 SelectionTemplate = DefaultSelectionTemplate,29}) => {30 const inputRef = useRef(null);31 const inputSizerRef = useRef(null);32 const selectedItemsRef = useRef(null);33 const [items, setItems] = useState([]);34 const [editValue, setEditValue] = useState(null);35 const [inputPosition, setInputPosition] = useState(null);36 useEffect(() => {37 // editValue defaults to null when component is first rendered.38 // This ensures we do not autofocus the input before the user has started interacting with the component.39 if (editValue === null) {40 return;41 }42 const { current: input } = inputRef;43 if (input && inputPosition !== null) {44 // Entering 'edit' mode45 resizeInputToContentSize();46 input.value = editValue;47 const { length: cursorPosition } = editValue;48 input.focus();49 // This will set the cursor position at the end of the text.50 input.setSelectionRange(cursorPosition, cursorPosition);51 }52 }, [inputPosition, editValue]);53 const handleInputBlur = ({ target: { value } }) => {54 addItemToList(value);55 clearInput();56 };57 const handleInputChange = async ({ target: { value } }) => {58 // When the input appears inline in "edit" mode, we need to dynamically calculate the width to ensure it occupies the right space59 // (an input cannot resize based on its text content). We use a hidden <span> to track the size.60 inputSizerRef.current.innerText = value;61 if (inputPosition !== null) {62 resizeInputToContentSize();63 }64 };65 const handleKeyDown = (e) => {66 const { value: currentValue } = inputRef.current;67 switch (e.key) {68 case KEYS.SPACE:69 case KEYS.ENTER:70 case KEYS.COMMA:71 e.preventDefault();72 addItemToList(e.target.value);73 clearInput();74 break;75 case KEYS.DELETE:76 if (currentValue === '') {77 e.preventDefault();78 editPreviousSelectionIfExists();79 }80 break;81 default:82 if (inputRegex && !inputRegex.test(e.key)) {83 e.preventDefault();84 }85 }86 };87 const addItemToList = (value) => {88 if (value.trim().length > 0) {89 // If an item was edited, we want to keep it in the same position in the list90 const insertIndex = inputPosition !== null ? inputPosition : items.length;91 // if we do not pass in a validationRegex we can assume that anything is valid92 const valid = validationRegex ? checkValidity(value) : true;93 const newSelections = [94 ...items.slice(0, insertIndex),95 { value, valid },96 ...items.slice(insertIndex),97 ];98 // We update the hidden selected items list, so additions are announced to screen reader users99 const listItem = document.createElement('li');100 listItem.innerText = value;101 selectedItemsRef.current.appendChild(listItem);102 setItems([...newSelections]);103 exitEditState({});104 }105 };106 const checkValidity = (value) => {107 return validationRegex.test(value);108 };109 const clearInput = () => {110 inputRef.current.value = '';111 };112 const resizeInputToContentSize = () => {113 const { current: input } = inputRef;114 if (input) {115 input.style.width = `${inputSizerRef.current.clientWidth}px`;116 }117 };118 const deselectItem = (clickedItem) => {119 const newArr = items.filter((item) => item.value !== clickedItem);120 setItems(newArr);121 // We also update the hidden selected items list, so removals are announced to screen reader users122 selectedItemsRef.current.querySelectorAll('li').forEach((selectionNode) => {123 if (selectionNode.innerText === clickedItem) {124 selectionNode.remove();125 }126 });127 };128 // If there is a previous selection, then pop it into edit mode129 const editPreviousSelectionIfExists = () => {130 if (items.length > 0 && inputPosition !== 0) {131 const nextEditIndex =132 inputPosition !== null ? inputPosition - 1 : items.length - 1;133 const item = items[nextEditIndex];134 enterEditState(item.value, nextEditIndex);135 }136 };137 const enterEditState = (editItem, editItemIndex) => {138 inputSizerRef.current.innerText = editItem;139 deselectItem(editItem);140 setEditValue(editItem);141 setInputPosition(editItemIndex);142 };143 const exitEditState = ({ nextInputValue = '' }) => {144 // Reset 'edit mode' input resizing145 inputRef.current?.style?.removeProperty('width');146 inputSizerRef.current.innerText = nextInputValue;147 setEditValue(nextInputValue);148 setInputPosition(nextInputValue === '' ? null : inputPosition + 1);149 // Blurring away while clearing the input150 if (nextInputValue === '') {151 inputRef.current.value = '';152 }153 };154 const allSelectedItemElements = items.map((item, index) => {155 // When we are in "edit mode" we visually display the input between the other selections156 // If the item being edited appears before the item being rendered then we set its position to157 // the index + 1 which matches the order, however, any items that appear after the item that is158 // being edited will need to increment their position by one to make place for the item being edited.159 // at this point the position is already set160 const defaultPosition = index + 1;161 const appearsBeforeInput = inputPosition === null || index < inputPosition;162 const position = appearsBeforeInput ? defaultPosition : defaultPosition + 1;163 return (164 <li165 key={index}166 className="c-input--multi__selection-list-item w-max"167 style={{ order: position }}168 >169 <SelectionTemplate170 name={item.value}171 className={`c-input--multi__selected ${172 !item.valid ? 'c-input--multi__selected-invalid' : ''173 }`}174 enableValidation={true}175 valid={item.valid}176 onEdit={() => enterEditState(item.value, index)}177 onDeselect={() => deselectItem(item.value)}178 />179 </li>180 );181 });182 return (183 <Fragment>184 <span185 ref={inputSizerRef}186 aria-hidden="true"187 className="absolute pointer-events-none opacity-0 p-2"188 />189 <label190 id="multi-select-label"191 className={showLabel ? '' : 'screen-reader-only'}192 >193 {labelText}194 </label>195 {/* A visually hidden list provides confirmation messages to screen reader users as an item is selected or removed */}196 <div className="screen-reader-only">197 <p>Selected items:</p>198 <ul199 ref={selectedItemsRef}200 className="screen-reader-only list-none"201 aria-live="assertive"202 aria-atomic="false"203 aria-relevant="additions removals"204 />205 </div>206 <div class="c-input--multi relative">207 <div class="c-input--multi__wrapper-border crayons-textfield flex items-center cursor-text pb-9">208 <ul class="list-none flex flex-wrap w-100">209 {allSelectedItemElements}210 <li211 class="self-center"212 style={{213 order:214 inputPosition === null ? items.length + 1 : inputPosition + 1,215 }}216 >217 <input218 autocomplete="off"219 class="c-input--multi__input"220 type="text"221 aria-labelledby="multi-select-label"222 onBlur={handleInputBlur}223 onKeyDown={handleKeyDown}224 placeholder={inputPosition === null ? placeholder : null}225 onChange={handleInputChange}226 ref={inputRef}227 />228 </li>229 </ul>230 </div>231 </div>232 </Fragment>233 );234};235MultiInput.propTypes = {236 labelText: PropTypes.string.isRequired,237 showLabel: PropTypes.bool,238 placeholder: PropTypes.string,239 inputRegex: PropTypes.string,240 validationRegex: PropTypes.string,241 SelectionTemplate: PropTypes.func,...
dropdown.spec.js
Source:dropdown.spec.js
1import { createElement } from 'lwc';2import Dropdown from 'component/dropdown';3describe('component-dropdown', () => {4 afterEach(() => {5 // The jsdom instance is shared across test cases in a single file so reset the DOM6 while (document.body.firstChild) {7 document.body.removeChild(document.body.firstChild);8 }9 });10 it('display correct text and data-index with one selected item', () => {11 const selectedItem = {12 title: 'First Selected Item',13 id: 'item-one',14 };15 const items = [16 selectedItem,17 {18 title: 'Second Item',19 id: 'item-two',20 },21 {22 title: 'Third Item',23 id: 'item-three',24 },25 ];26 const element = createElement('component-dropdown', { is: Dropdown });27 element.options = {28 multiple: false,29 items,30 selectedItems: [selectedItem],31 };32 document.body.appendChild(element);33 return Promise.resolve().then(() => {34 expect(element).toMatchSnapshot();35 const selectedItemElement = element.shadowRoot.querySelector('.selected-item');36 expect(selectedItemElement.textContent).toBe(selectedItem.title);37 expect(parseInt(selectedItemElement.dataset.index, 10)).toBe(0);38 });39 });40 it('displays no selected items when none are provided', () => {41 const items = [42 {43 title: 'First Selected Item',44 id: 'item-one',45 },46 {47 title: 'Second Item',48 id: 'item-two',49 },50 {51 title: 'Third Item',52 id: 'item-three',53 },54 ];55 const element = createElement('component-dropdown', { is: Dropdown });56 element.options = {57 multiple: false,58 items,59 selectedItems: [],60 };61 document.body.appendChild(element);62 return Promise.resolve().then(() => {63 expect(element).toMatchSnapshot();64 const allSelectedItemElements = element.shadowRoot.querySelectorAll('.selected-item');65 expect(allSelectedItemElements).toHaveLength(0);66 });67 });68 it('displays no items when none are provided', () => {69 const element = createElement('component-dropdown', { is: Dropdown });70 element.options = {71 multiple: false,72 items: [],73 selectedItems: [],74 };75 document.body.appendChild(element);76 return Promise.resolve().then(() => {77 expect(element).toMatchSnapshot();78 const allItemElements = element.shadowRoot.querySelectorAll('.item');79 expect(allItemElements).toHaveLength(0);80 });81 });82 it('displays items in correct order', () => {83 const items = [84 {85 title: 'First Selected Item',86 id: 'item-one',87 },88 {89 title: 'Second Item',90 id: 'item-two',91 },92 {93 title: 'Third Item',94 id: 'item-three',95 },96 ];97 const element = createElement('component-dropdown', { is: Dropdown });98 element.options = {99 multiple: false,100 items,101 selectedItems: [],102 };103 document.body.appendChild(element);104 return Promise.resolve().then(() => {105 expect(element).toMatchSnapshot();106 const allItemElements = element.shadowRoot.querySelectorAll('.item');107 expect(allItemElements).toHaveLength(items.length);108 items.forEach((item, index) => {109 const itemElement = allItemElements[index];110 expect(itemElement.textContent).toBe(item.title);111 expect(parseInt(itemElement.dataset.index, 10)).toBe(index);112 });113 });114 });115 it('fires selection event when item is clicked', () => {116 const items = [117 {118 title: 'First Selected Item',119 id: 'item-one',120 },121 {122 title: 'Second Item',123 id: 'item-two',124 },125 {126 title: 'Third Item',127 id: 'item-three',128 },129 ];130 const element = createElement('component-dropdown', { is: Dropdown });131 element.options = {132 multiple: false,133 items,134 selectedItems: [],135 };136 const evtListenerMock = jest.fn();137 element.addEventListener('selection', evtListenerMock);138 document.body.appendChild(element);139 return Promise.resolve().then(() => {140 const itemElement = element.shadowRoot.querySelector('.item');141 itemElement.click();142 expect(evtListenerMock).toHaveBeenCalledTimes(1);143 });144 });...
Using AI Code Generation
1const BestBuy = require("./BestBuy.js");2const bestBuy = new BestBuy();3const allItems = bestBuy.allSelectedItemElements();4for (let i = 0; i < allItems.length; i++) {5 console.log(allItems[i].getText());6}
Using AI Code Generation
1const BestBuy = require('./BestBuy');2const bestBuy = new BestBuy();3const allSelectedItemsElements = bestBuy.allSelectedItemElements();4allSelectedItemsElements.then(function (result) {5 console.log(result);6});
Using AI Code Generation
1const bestBuy = require('../src/bestBuy');2const assert = require('assert');3describe('allSelectedItemElements', () => {4 it('should return an array of all items selected', () => {5 const result = bestBuy.allSelectedItemElements();6 assert.strictEqual(Array.isArray(result), true);7 });8});
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!