diff --git a/docs/methods.md b/docs/methods.md index 6e21d71..81b3095 100644 --- a/docs/methods.md +++ b/docs/methods.md @@ -5,6 +5,7 @@ - [reset()](#reset) - [setOptions()](#setoptions) - [setDisabledOptions()](#setdisabledoptions) +- [setEnabledOptions()](#setenabledoptions) - [toggleSelectAll()](#toggleselectall) - [isAllSelected()](#isallselected) - [addOption()](#addoption) @@ -82,6 +83,22 @@ document.querySelector('#sample-select').setDisabledOptions(disabledOptions); document.querySelector('#sample-select').setDisabledOptions(true); ``` +### setEnabledOptions() + +**Arguments:** + +- enabledOptions - list of enabled option's values or `true` to enable all options +- keepValue - set true to keep selected value + +```js +var enabledOptions = [2, 6, 9]; + +document.querySelector('#sample-select').setEnabledOptions(enabledOptions); + +/** to enable all options */ +document.querySelector('#sample-select').setEnabledOptions(true); +``` + ### toggleSelectAll() **Arguments:** diff --git a/docs/properties.md b/docs/properties.md index 6577cd3..de31f41 100644 --- a/docs/properties.md +++ b/docs/properties.md @@ -8,6 +8,7 @@ | options[].alias | String \| Array | | Alternative labels to use on search.
Array of string or comma separated string. | | options[].options | Array | | List of options for option group | | options[].description | String | | Text to show along with label | +| options[].classNames | String | | Additional class names to customize specific option | | options[].customData | Any | | Any custom data to store with the options and it would be available with getSelectedOptions() result. | | valueKey | String | value | Object key to use to get value from options array | | labelKey | String | label | Object key to use to get label from options array | @@ -43,6 +44,7 @@ | name | String | | Name attribute for hidden input
It would be useful for form submit to server | | keepAlwaysOpen | Boolean | false | Keep dropbox always open with fixed height | | maxValues | Number | 0 | Maximum no.of options allowed to choose in multiple select
0 - for no limit | +| minValues | Number | | Minimum no.of options should be selected to succeed required validation | | additionalClasses | String | | Additional classes for wrapper element | | showDropboxAsPopup | Boolean | true | Show dropbox as popup on small screen like mobile | | popupDropboxBreakpoint | String | 576px | Maximum screen width that allowed to show dropbox as popup | diff --git a/package-lock.json b/package-lock.json index 64f9f2f..b0b7d9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "virtual-select-plugin", - "version": "1.0.31", + "version": "1.0.32", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "virtual-select-plugin", - "version": "1.0.31", + "version": "1.0.32", "license": "ISC", "dependencies": { "tooltip-plugin": "^1.0.16" @@ -24,7 +24,7 @@ "eslint-plugin-import": "^2.26.0", "filemanager-webpack-plugin": "^7.0.0", "mini-css-extract-plugin": "^2.6.1", - "popover-plugin": "^1.0.10", + "popover-plugin": "^1.0.11", "postcss-loader": "^7.0.1", "sass": "^1.53.0", "sass-loader": "^13.0.2", @@ -6662,9 +6662,9 @@ } }, "node_modules/popover-plugin": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/popover-plugin/-/popover-plugin-1.0.10.tgz", - "integrity": "sha512-5CCVqUv3QxFot8c3UQ57mF7B+o1J7puCALf0IhBCbcSLOEtWTvQ/yWs3JfgbN8CI0N8PKZkdR97tEvxtRaV7YA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/popover-plugin/-/popover-plugin-1.0.11.tgz", + "integrity": "sha512-4Aau+frrJ32gI3O+b4g42Z32qAEZoKJ0FmkKG121M0vE1pQs0jfY72d3mo/K9+BTN7eATzrkmr0bqFpilDOK6w==", "dev": true }, "node_modules/postcss": { @@ -14386,9 +14386,9 @@ } }, "popover-plugin": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/popover-plugin/-/popover-plugin-1.0.10.tgz", - "integrity": "sha512-5CCVqUv3QxFot8c3UQ57mF7B+o1J7puCALf0IhBCbcSLOEtWTvQ/yWs3JfgbN8CI0N8PKZkdR97tEvxtRaV7YA==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/popover-plugin/-/popover-plugin-1.0.11.tgz", + "integrity": "sha512-4Aau+frrJ32gI3O+b4g42Z32qAEZoKJ0FmkKG121M0vE1pQs0jfY72d3mo/K9+BTN7eATzrkmr0bqFpilDOK6w==", "dev": true }, "postcss": { diff --git a/package.json b/package.json index 5c8bc70..372838c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "virtual-select-plugin", - "version": "1.0.31", + "version": "1.0.32", "description": "A javascript plugin for dropdown with virtual scroll", "scripts": { "start": "webpack --mode development --watch", @@ -34,7 +34,7 @@ "eslint-plugin-import": "^2.26.0", "filemanager-webpack-plugin": "^7.0.0", "mini-css-extract-plugin": "^2.6.1", - "popover-plugin": "^1.0.10", + "popover-plugin": "^1.0.11", "postcss-loader": "^7.0.1", "sass": "^1.53.0", "sass-loader": "^13.0.2", diff --git a/src/virtual-select.js b/src/virtual-select.js index 1820e51..2509be0 100755 --- a/src/virtual-select.js +++ b/src/virtual-select.js @@ -1,3 +1,4 @@ +/** cSpell:ignore nocheck, Labelledby, vscomp, tabindex, combobox, haspopup, listbox, activedescendant */ /* eslint-disable class-methods-use-this */ // @ts-nocheck import { Utils, DomUtils } from './utils'; @@ -41,6 +42,7 @@ const dataProps = [ 'markSearchResults', 'maxValues', 'maxWidth', + 'minValues', 'moreText', 'noOfDisplayValues', 'noOptionsText', @@ -277,6 +279,10 @@ export class VirtualSelect { const isSelected = convertToBoolean(d.isSelected); let ariaDisabledText = ''; + if (d.classNames) { + optionClasses += ` ${d.classNames}`; + } + if (d.isFocused) { optionClasses += ' focused'; } @@ -583,7 +589,6 @@ export class VirtualSelect { /** using setTimeout to fix the issue of dropbox getting closed on select */ setTimeout(() => { - this.isClearingSearchValue = true; this.setSearchValue(''); this.focusSearchInput(); }, 0); @@ -651,13 +656,11 @@ export class VirtualSelect { } afterSetSearchValue() { - if (this.hasServerSearch && !this.isClearingSearchValue) { + if (this.hasServerSearch) { this.serverSearch(); } else { this.setVisibleOptionsCount(); } - - this.isClearingSearchValue = false; if (this.selectAllOnlyVisible) { this.toggleAllOptionsClass(); @@ -763,6 +766,7 @@ export class VirtualSelect { this.noOfDisplayValues = parseInt(options.noOfDisplayValues); this.zIndex = parseInt(options.zIndex); this.maxValues = parseInt(options.maxValues); + this.minValues = parseInt(options.minValues); this.name = this.secureText(options.name); this.additionalClasses = options.additionalClasses; this.popupDropboxBreakpoint = options.popupDropboxBreakpoint; @@ -783,7 +787,6 @@ export class VirtualSelect { this.tooltipEnterDelay = 200; this.searchValue = ''; this.searchValueOriginal = ''; - this.isClearingSearchValue = false; this.isAllSelected = false; if ((options.search === undefined && this.multiple) || this.allowNewOption || this.showOptionsOnlyOnSearch) { @@ -889,6 +892,7 @@ export class VirtualSelect { $ele.setValue = VirtualSelect.setValueMethod; $ele.setOptions = VirtualSelect.setOptionsMethod; $ele.setDisabledOptions = VirtualSelect.setDisabledOptionsMethod; + $ele.setEnabledOptions = VirtualSelect.setEnabledOptionsMethod; $ele.toggleSelectAll = VirtualSelect.toggleSelectAll; $ele.isAllSelected = VirtualSelect.isAllSelected; $ele.addOption = VirtualSelect.addOptionMethod; @@ -1072,6 +1076,55 @@ export class VirtualSelect { this.disabledOptions = disabledOptionsArr; } + setEnabledOptionsMethod(disabledOptions, keepValue = false) { + this.setEnabledOptions(disabledOptions); + + if (!keepValue) { + this.setValueMethod(null); + this.toggleAllOptionsClass(); + } + + this.setVisibleOptions(); + } + + setEnabledOptions(enabledOptions) { + if (enabledOptions === undefined) { + return; + } + + const disabledOptionsArr = []; + + if (enabledOptions === true) { + this.options.forEach((d) => { + // eslint-disable-next-line no-param-reassign + d.isDisabled = false; + + return d; + }); + } else { + const enabledOptionsMapping = {}; + + enabledOptions.forEach((d) => { + enabledOptionsMapping[d] = true; + }); + + this.options.forEach((d) => { + const isDisabled = enabledOptionsMapping[d.value] !== true; + + // eslint-disable-next-line no-param-reassign + d.isDisabled = isDisabled; + + if (isDisabled) { + disabledOptionsArr.push(d.value); + } + + return d; + }); + } + + this.disabledOptions = disabledOptionsArr; + } + setOptions(options = []) { const preparedOptions = []; const hasDisabledOptions = this.disabledOptions.length; @@ -1105,6 +1158,7 @@ export class VirtualSelect { isVisible: convertToBoolean(d.isVisible, true), isNew: d.isNew || false, isGroupTitle, + classNames: d.classNames, }; if (!hasEmptyValueOption && value === '') { @@ -1168,7 +1222,7 @@ export class VirtualSelect { const newOptions = this.options; let optionsUpdated = false; - /** merging already seleted options details with new options */ + /** merging already selected options details with new options */ if (selectedOptions.length) { const newOptionsValueMapping = {}; optionsUpdated = true; @@ -1178,7 +1232,7 @@ export class VirtualSelect { }); selectedOptions.forEach((d) => { - if (newOptionsValueMapping[d.value] === false) { + if (newOptionsValueMapping[d.value] !== true) { // eslint-disable-next-line no-param-reassign d.isVisible = false; newOptions.push(d); @@ -1398,7 +1452,7 @@ export class VirtualSelect { this.setValueTagAttr(); } else { - /** replace comma delimitted list of selections with shorter text indicating selection count */ + /** replace comma separated list of selections with shorter text indicating selection count */ const optionsSelectedText = selectedLength === 1 ? this.optionSelectedText : this.optionsSelectedText; $valueText.innerHTML = `${countText} ${optionsSelectedText}`; } @@ -2263,6 +2317,7 @@ export class VirtualSelect { } this.closeDropbox(); + this.setSearchValue(''); } this.lastSelectedOptionIndex = selectedIndex; @@ -2740,8 +2795,13 @@ export class VirtualSelect { } let hasError = false; + const { selectedValues, minValues } = this; - if (this.required && Utils.isEmpty(this.selectedValues)) { + if (this.required && + (Utils.isEmpty(selectedValues) || + /** required minium options not selected */ + (this.multiple && minValues && selectedValues.length < minValues)) + ) { hasError = true; } @@ -2984,6 +3044,10 @@ export class VirtualSelect { this.virtualSelect.setDisabledOptionsMethod(...params); } + static setEnabledOptionsMethod(...params) { + this.virtualSelect.setEnabledOptionsMethod(...params); + } + static toggleSelectAll(isSelected) { this.virtualSelect.toggleAllOptions(isSelected); } diff --git a/src/virtual-select.types.js b/src/virtual-select.types.js index 9521d34..c8c9ca3 100644 --- a/src/virtual-select.types.js +++ b/src/virtual-select.types.js @@ -1,99 +1,102 @@ +/** cSpell:ignore Labelledby */ /** * @typedef {object} virtualSelectOptions - * @property {(HTMLElement|string)} ele - Parent element to render VirtualSelect - * @property {string} [dropboxWrapper=self] - Parent element to render dropbox. (self, body, or any css selectror) - * Use this when container of dropdown has overflow scroll or hiddden value. - * @property {virtualSelectOption[]} options - Array of object to show as options - * @property {string} [valueKey=value] - Object key to use to get value from options array - * @property {string} [labelKey=label] - Object key to use to get label from options array - * @property {string} [descriptionKey=description] - Object key to use to get description from options array - * @property {string} [aliasKey=alias] - Key name to get alias from options object - * @property {boolean} [multiple=false] - Enable multiselect - * @property {boolean} [search=false] - Enable search - * @property {boolean} [searchByStartsWith=false] - Search options by startsWith() method - * @property {boolean} [searchGroup=false] - Include group title for searching - * @property {boolean} [disabled=false] - Disable dropdown - * @property {boolean} [required=false] - Enable required validation - * @property {boolean} [autofocus=false] - Autofocus dropdown on load - * @property {boolean} [hideClearButton=false] - Hide clear button - * @property {boolean} [autoSelectFirstOption=false] - Select first option by default on load - * @property {boolean} [hasOptionDescription=false] - Has description to show along with label - * @property {boolean} [disableSelectAll=false] - Disable select all feature of multiple select - * @property {string} [optionsCount=5|4] - No.of options to show on viewport - * @property {string} [optionHeight='40px|50px'] - Height of each dropdown options - * @property {string} [position='bottom left'] - Position of dropbox (auto, top, bottom, top left, top right, + * @property {(HTMLElement|string)} ele Parent element to render VirtualSelect + * @property {string} [dropboxWrapper=self] Parent element to render dropbox. (self, body, or any css selector) + * Use this when container of dropdown has overflow scroll or hidden value. + * @property {virtualSelectOption[]} options Array of object to show as options + * @property {string} [valueKey=value] Object key to use to get value from options array + * @property {string} [labelKey=label] Object key to use to get label from options array + * @property {string} [descriptionKey=description] Object key to use to get description from options array + * @property {string} [aliasKey=alias] Key name to get alias from options object + * @property {boolean} [multiple=false] Enable multiselect + * @property {boolean} [search=false] Enable search + * @property {boolean} [searchByStartsWith=false] Search options by startsWith() method + * @property {boolean} [searchGroup=false] Include group title for searching + * @property {boolean} [disabled=false] Disable dropdown + * @property {boolean} [required=false] Enable required validation + * @property {boolean} [autofocus=false] Autofocus dropdown on load + * @property {boolean} [hideClearButton=false] Hide clear button + * @property {boolean} [autoSelectFirstOption=false] Select first option by default on load + * @property {boolean} [hasOptionDescription=false] Has description to show along with label + * @property {boolean} [disableSelectAll=false] Disable select all feature of multiple select + * @property {string} [optionsCount=5|4] No.of options to show on viewport + * @property {string} [optionHeight='40px|50px'] Height of each dropdown options + * @property {string} [position='bottom left'] Position of dropbox (auto, top, bottom, top left, top right, * bottom left, bottom right) - * @property {string} [textDirection=ltr] - Direction of text (ltr or rtl) - * @property {(string[]|number[])} [disabledOptions] - Options to disable (array of values) - * @property {(string|string[]|number[])} [selectedValue] - Single value or array of values to select on init - * @property {boolean} [silentInitialValueSet=false] - To avoid "change event" trigger on setting initial value - * @property {string} [dropboxWidth] - Custom width for dropbox - * @property {number} [zIndex=1] - CSS z-index value for dropbox - * @property {number} [noOfDisplayValues=50] - Maximum no.of values to show in the tooltip for multi-select - * @property {boolean} [allowNewOption=false] - Allow to add new option by searching - * @property {boolean} [markSearchResults=false] - Mark matched term in label - * @property {string} [tooltipFontSize='14px'] - Font size for tooltip - * @property {string} [tooltipAlignment=center] - CSS Text alignment for tooltip - * @property {string} [tooltipMaxWidth='300px'] - CSS max width for tooltip - * @property {boolean} [showSelectedOptionsFirst=false] - Show selected options at the top of the dropbox - * @property {string} [name] - Name attribute for hidden input - * @property {boolean} [keepAlwaysOpen] - Keep dropbox always open with fixed height - * @property {number} [maxValues=0] - Maximum no.of options allowed to choose in multiple select - * @property {string} [additionalClasses] - Additional classes for wrapper element - * @property {boolean} [showDropboxAsPopup=true] - Show dropbox as popup on small screen like mobile - * @property {string} [popupDropboxBreakpoint='576px'] - Maximum screen width that allowed to show dropbox as popup - * @property {string} [popupPosition=center] - Position of the popup (left, center, or right) - * @property {function} [onServerSearch] - Callback function to integrate server search - * @property {function} [labelRenderer] - Callback function to render label, which could be used to add image, + * @property {string} [textDirection=ltr] Direction of text (ltr or rtl) + * @property {(string[]|number[])} [disabledOptions] Options to disable (array of values) + * @property {(string|string[]|number[])} [selectedValue] Single value or array of values to select on init + * @property {boolean} [silentInitialValueSet=false] To avoid "change event" trigger on setting initial value + * @property {string} [dropboxWidth] Custom width for dropbox + * @property {number} [zIndex=1] CSS z-index value for dropbox + * @property {number} [noOfDisplayValues=50] Maximum no.of values to show in the tooltip for multi-select + * @property {boolean} [allowNewOption=false] Allow to add new option by searching + * @property {boolean} [markSearchResults=false] Mark matched term in label + * @property {string} [tooltipFontSize='14px'] Font size for tooltip + * @property {string} [tooltipAlignment=center] CSS Text alignment for tooltip + * @property {string} [tooltipMaxWidth='300px'] CSS max width for tooltip + * @property {boolean} [showSelectedOptionsFirst=false] Show selected options at the top of the dropbox + * @property {string} [name] Name attribute for hidden input + * @property {boolean} [keepAlwaysOpen] Keep dropbox always open with fixed height + * @property {number} [maxValues=0] Maximum no.of options allowed to choose in multiple select + * @property {number} [minValues] Minimum no.of options should be selected to succeed required validation + * @property {string} [additionalClasses] Additional classes for wrapper element + * @property {boolean} [showDropboxAsPopup=true] Show dropbox as popup on small screen like mobile + * @property {string} [popupDropboxBreakpoint='576px'] Maximum screen width that allowed to show dropbox as popup + * @property {string} [popupPosition=center] Position of the popup (left, center, or right) + * @property {function} [onServerSearch] Callback function to integrate server search + * @property {function} [labelRenderer] Callback function to render label, which could be used to add image, * icon, or custom content - * @property {string} [ariaLabelledby] - ID of the label element to use as a11y attribute aria-labelledby - * @property {boolean} [hideValueTooltipOnSelectAll=true] - Hide value tooltip if all options selected - * @property {boolean} [showOptionsOnlyOnSearch=false] - Show options to select only if search value is not empty - * @property {boolean} [selectAllOnlyVisible=false] - Select only visible options on clicking select all checkbox when + * @property {string} [ariaLabelledby] ID of the label element to use as a11y attribute aria-labelledby + * @property {boolean} [hideValueTooltipOnSelectAll=true] Hide value tooltip if all options selected + * @property {boolean} [showOptionsOnlyOnSearch=false] Show options to select only if search value is not empty + * @property {boolean} [selectAllOnlyVisible=false] Select only visible options on clicking select all checkbox when * options filtered by search - * @property {boolean} [alwaysShowSelectedOptionsCount=false] - By default, no.of options selected text would be shown + * @property {boolean} [alwaysShowSelectedOptionsCount=false] By default, no.of options selected text would be shown * when there is no enough space to show all selected values. Set true to override this. - * @property {boolean} [disableAllOptionsSelectedText=false] - By default, when all values selected "All (10)"value + * @property {boolean} [disableAllOptionsSelectedText=false] By default, when all values selected "All (10)"value * text would be shown. * Set true to show value text as "10 options selected". - * @property {boolean} [showValueAsTags=false] - Show each selected values as tags with remove icon - * @property {boolean} [disableOptionGroupCheckbox=false] - Disable option group title checkbox - * @property {boolean} [enableSecureText=false] - Set true to replace HTML tags from option's text (value and label) + * @property {boolean} [showValueAsTags=false] Show each selected values as tags with remove icon + * @property {boolean} [disableOptionGroupCheckbox=false] Disable option group title checkbox + * @property {boolean} [enableSecureText=false] Set true to replace HTML tags from option's text (value and label) * to prevent XSS attack. * This feature is not enabled by default to avoid performance issue. - * @property {boolean} [setValueAsArray=false] - Set value for hidden input in array format (e.g. '["1", "2"]') - * @property {string} [emptyValue=''] - Empty value to use for hidden input when no value is selected + * @property {boolean} [setValueAsArray=false] Set value for hidden input in array format (e.g. '["1", "2"]') + * @property {string} [emptyValue=''] Empty value to use for hidden input when no value is selected * (e.g. 'null' or '[]' or 'none') - * @property {boolean} [disableValidation=false] - Disable required validation - * @property {boolean} [useGroupValue=false] - Group's value would be returned when all of its child options selected - * @property {string} [maxWidth='250px'] - Maximum width for the select element - * @property {number} [updatePositionThrottle=100] - Throttle time for updating dropbox position + * @property {boolean} [disableValidation=false] Disable required validation + * @property {boolean} [useGroupValue=false] Group's value would be returned when all of its child options selected + * @property {string} [maxWidth='250px'] Maximum width for the select element + * @property {number} [updatePositionThrottle=100] Throttle time for updating dropbox position * on scroll event (in milliseconds) * - * @property {string} [placeholder=Select] - Text to show when no options selected - * @property {string} [noOptionsText='No options found'] - Text to show when no options to show - * @property {string} [noSearchResultsText='No results found'] - Text to show when no results on search - * @property {string} [selectAllText='Select all'] - Text to show near select all checkbox when search is disabled - * @property {string} [searchPlaceholderText='Search...'] - Text to show as placeholder for search input - * @property {string} [optionsSelectedText='options selected'] - Text to use when displaying no.of values selected text + * @property {string} [placeholder=Select] Text to show when no options selected + * @property {string} [noOptionsText='No options found'] Text to show when no options to show + * @property {string} [noSearchResultsText='No results found'] Text to show when no results on search + * @property {string} [selectAllText='Select all'] Text to show near select all checkbox when search is disabled + * @property {string} [searchPlaceholderText='Search...'] Text to show as placeholder for search input + * @property {string} [optionsSelectedText='options selected'] Text to use when displaying no.of values selected text * (i.e. 3 options selected) - * @property {string} [optionSelectedText='option selected'] - Text to use when displaying no.of values selected text + * @property {string} [optionSelectedText='option selected'] Text to use when displaying no.of values selected text * and only one value is selected (i.e. 1 option selected) - * @property {string} [allOptionsSelectedText=All] - Text to use when displaying all values selected text + * @property {string} [allOptionsSelectedText=All] Text to use when displaying all values selected text * (i.e. All (10)) - * @property {string} [clearButtonText=Clear] - Tooltip text for clear button - * @property {string} [moreText='more...'] - Text to show when more than noOfDisplayValues options selected + * @property {string} [clearButtonText=Clear] Tooltip text for clear button + * @property {string} [moreText='more...'] Text to show when more than noOfDisplayValues options selected * (i.e + 10 more...) */ /** * @typedef {object} virtualSelectOption - * @property {(string|number)} value - Value of the option - * @property {(string|number)} label - Display text of the option - * @property {(string|number)} [description] - Text to show along with label - * @property {(string|string[])} [alias] - Alternative labels to use on search. Array of string or + * @property {(string|number)} value Value of the option + * @property {(string|number)} label Display text of the option + * @property {(string|number)} [description] Text to show along with label + * @property {(string|number)} [classNames] Additional class names to customize specific option + * @property {(string|string[])} [alias] Alternative labels to use on search. Array of string or * comma separated string. - * @property {any} [customData] - Any custom data to store with the options and it would be available + * @property {any} [customData] Any custom data to store with the options and it would be available * with getSelectedOptions() result. - * @property {virtualSelectOption[]} [options] - List of options for option group + * @property {virtualSelectOption[]} [options] List of options for option group */ diff --git a/tsconfig.json b/tsconfig.json index 6f08fb8..18a6761 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,9 @@ "outDir": "ts-types", "allowJs": true, "checkJs": true, - "strict": true + "strict": true, + "declaration": true, + "emitDeclarationOnly": true }, "include": ["src/**/*"]