selectic 3.1.2 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ function styleInject(css, ref) {
32
32
  }
33
33
  }
34
34
 
35
- var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n top: var(--top-position, 0);\n z-index: 2000;\n height: auto;\n max-height: var(--availableSpace);\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n width: var(--list-width, 200px);\n min-width: 200px;\n display: grid;\n grid-template-rows: minmax(0, max-content) 1fr;\n}\n.selectic__extended-list.selectic-position-top {\n box-shadow: 2px -3px 12px 0px #888888;\n}\n.selectic__extended-list__list-container{\n overflow: auto;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n min-width: max-content;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item__is-group.selectable {\n cursor: pointer;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n.selectic .form-control-feedback.fa.selectic-search-scope {\n width: calc(var(--selectic-input-height) * 0.75);\n height: calc(var(--selectic-input-height) * 0.75);\n line-height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* {{{ icons */\n\n@keyframes selectic-animation-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(359deg);\n }\n}\n\n.selectic__icon {\n height: 1em;\n fill: currentColor;\n}\n\n.selectic-spin {\n animation: selectic-animation-spin 2s infinite linear;\n}\n\n/* }}} */\n";
35
+ var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n top: var(--top-position, 0);\n z-index: 2000;\n height: auto;\n max-height: var(--availableSpace);\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n width: var(--list-width, 200px);\n min-width: 200px;\n display: grid;\n grid-template-rows: minmax(0, max-content) 1fr;\n}\n.selectic__extended-list.selectic-position-top {\n box-shadow: 2px -3px 12px 0px #888888;\n}\n.selectic__extended-list__list-container{\n overflow: auto;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n min-width: max-content;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n.selectic-item__active.selectic-item__disabled:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item__is-group.selectable {\n cursor: pointer;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n.selectic .form-control-feedback.fa.selectic-search-scope {\n width: calc(var(--selectic-input-height) * 0.75);\n height: calc(var(--selectic-input-height) * 0.75);\n line-height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* {{{ icons */\n\n@keyframes selectic-animation-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(359deg);\n }\n}\n\n.selectic__icon {\n height: 1em;\n fill: currentColor;\n}\n\n.selectic-spin {\n animation: selectic-animation-spin 2s infinite linear;\n}\n\n/* }}} */\n";
36
36
  styleInject(css_248z);
37
37
 
38
38
  /**
@@ -558,25 +558,54 @@ class SelecticStore {
558
558
  if (selected === undefined) {
559
559
  selected = !isAlreadySelected;
560
560
  }
561
+ const selectedOptions = Array.isArray(state.selectedOptions)
562
+ ? state.selectedOptions
563
+ : [];
561
564
  if (id === null) {
562
- state.internalValue = [];
563
- hasChanged = internalValue.length > 0;
565
+ /* Keep disabled items: we cannot removed them because they
566
+ * are disabled */
567
+ const newSelection = selectedOptions.reduce((list, item) => {
568
+ if (item.disabled && item.id) {
569
+ list.push(item.id);
570
+ }
571
+ return list;
572
+ }, []);
573
+ state.internalValue = newSelection;
574
+ hasChanged = internalValue.length > newSelection.length;
564
575
  }
565
576
  else if (selected && !isAlreadySelected) {
577
+ let addItem = true;
566
578
  if (item === null || item === void 0 ? void 0 : item.exclusive) {
567
- /* clear the current selection because the item is exclusive */
568
- internalValue.splice(0, Infinity);
579
+ const hasDisabledSelected = selectedOptions.some((opt) => {
580
+ return opt.disabled;
581
+ });
582
+ if (hasDisabledSelected) {
583
+ /* do not remove disabled item from selection */
584
+ addItem = false;
585
+ }
586
+ else {
587
+ /* clear the current selection because the item is exclusive */
588
+ internalValue.splice(0, Infinity);
589
+ }
569
590
  }
570
591
  else if (internalValue.length === 1) {
571
592
  const selectedId = internalValue[0];
572
593
  const selectedItem = state.allOptions.find((opt) => opt.id === selectedId);
573
594
  if (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.exclusive) {
574
- /* clear the current selection because the old item was exclusive */
575
- internalValue.pop();
595
+ if (selectedItem.disabled) {
596
+ /* If selected item is disabled and exclusive do not change the selection */
597
+ addItem = false;
598
+ }
599
+ else {
600
+ /* clear the current selection because the old item was exclusive */
601
+ internalValue.pop();
602
+ }
576
603
  }
577
604
  }
578
- internalValue.push(id);
579
- hasChanged = true;
605
+ if (addItem) {
606
+ internalValue.push(id);
607
+ hasChanged = true;
608
+ }
580
609
  }
581
610
  else if (!selected && isAlreadySelected) {
582
611
  internalValue.splice(internalValue.indexOf(id), 1);
@@ -599,6 +628,11 @@ class SelecticStore {
599
628
  if (id !== oldValue) {
600
629
  return hasChanged;
601
630
  }
631
+ const oldOption = state.selectedOptions;
632
+ if (oldOption === null || oldOption === void 0 ? void 0 : oldOption.disabled) {
633
+ /* old selection is disabled so do not unselect it */
634
+ return hasChanged;
635
+ }
602
636
  id = null;
603
637
  }
604
638
  else if (id === oldValue) {
@@ -1368,15 +1402,23 @@ class SelecticStore {
1368
1402
  if (doNotCheck || !hasFetchedAllItems) {
1369
1403
  return;
1370
1404
  }
1405
+ const selectedOptions = state.selectedOptions;
1371
1406
  const enabledOptions = state.allOptions.filter((opt) => !opt.disabled);
1372
- const nb = enabledOptions.length;
1407
+ const nbEnabled = enabledOptions.length;
1373
1408
  const value = state.internalValue;
1374
1409
  const hasValue = Array.isArray(value) ? value.length > 0 : value !== null;
1375
- const hasValidValue = hasValue && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1410
+ const hasDisabledSelected = Array.isArray(selectedOptions)
1411
+ ? selectedOptions.some((opt) => opt.disabled)
1412
+ : false;
1413
+ const hasOnlyValidValue = hasValue && !hasDisabledSelected && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1376
1414
  this.hasValue(value));
1377
- const isEmpty = nb === 0;
1378
- const hasOnlyOneOption = nb === 1 && hasValidValue && !state.allowClearSelection;
1379
- if (hasOnlyOneOption || isEmpty) {
1415
+ const isEmpty = nbEnabled === 0;
1416
+ const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
1417
+ const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1418
+ && selectedOptions.length === 1
1419
+ && selectedOptions[0].exclusive
1420
+ && selectedOptions[0].disabled;
1421
+ if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
1380
1422
  if (state.isOpen) {
1381
1423
  this.setAutomaticClose();
1382
1424
  this.commit('isOpen', false);
@@ -1796,6 +1838,18 @@ let MainInput = class MainInput extends vtyx.Vue {
1796
1838
  ? Array.isArray(value) && value.length > 0
1797
1839
  : value !== null;
1798
1840
  }
1841
+ get disabledList() {
1842
+ const state = this.store.state;
1843
+ const isMultiple = state.multiple;
1844
+ const value = state.selectedOptions;
1845
+ if (!isMultiple || !value) {
1846
+ return [];
1847
+ }
1848
+ const disabledValues = value.filter((option) => {
1849
+ return option.disabled;
1850
+ });
1851
+ return disabledValues;
1852
+ }
1799
1853
  get displayPlaceholder() {
1800
1854
  const placeholder = this.store.state.placeholder;
1801
1855
  const hasValue = this.hasValue;
@@ -1814,10 +1868,12 @@ let MainInput = class MainInput extends vtyx.Vue {
1814
1868
  const state = this.store.state;
1815
1869
  const isMultiple = state.multiple;
1816
1870
  const value = state.internalValue;
1817
- const hasOnlyOneValue = Array.isArray(value) && value.length === 1;
1871
+ const nbSelection = (Array.isArray(value) && value.length) || 0;
1872
+ const hasOnlyOneValue = nbSelection === 1;
1873
+ const hasOnlyDisabled = nbSelection <= this.disabledList.length;
1818
1874
  /* Should not display the clear action if there is only one selected
1819
1875
  * item in multiple (as this item has already its remove icon) */
1820
- return !isMultiple || !hasOnlyOneValue;
1876
+ return !isMultiple || !hasOnlyOneValue || !hasOnlyDisabled;
1821
1877
  }
1822
1878
  get clearedLabel() {
1823
1879
  const isMultiple = this.store.state.multiple;
@@ -1935,6 +1991,9 @@ let MainInput = class MainInput extends vtyx.Vue {
1935
1991
  /* Check if there is enough space to display items like there are
1936
1992
  * currently shown */
1937
1993
  const el = this.$refs.selectedItems;
1994
+ if (!el) {
1995
+ return;
1996
+ }
1938
1997
  const parentEl = el.parentElement;
1939
1998
  if (!document.contains(parentEl)) {
1940
1999
  /* The element is currently not in DOM */
@@ -2033,7 +2092,7 @@ let MainInput = class MainInput extends vtyx.Vue {
2033
2092
  click: () => this.$emit('item:click', item.id),
2034
2093
  } },
2035
2094
  vtyx.h("span", { class: "selectic-input__selected-items__value" }, item.text),
2036
- !this.isDisabled && (vtyx.h(Icon$1, { icon: "times", class: "selectic-input__selected-items__icon", store: this.store, on: {
2095
+ !this.isDisabled && !item.disabled && (vtyx.h(Icon$1, { icon: "times", class: "selectic-input__selected-items__icon", store: this.store, on: {
2037
2096
  'click.prevent.stop': () => this.selectItem(item.id),
2038
2097
  } }))))),
2039
2098
  this.moreSelectedNb && (vtyx.h("div", { class: "single-value more-items", title: this.moreSelectedTitle }, this.moreSelectedNb)))),
@@ -2787,7 +2846,8 @@ let Selectic = class Selectic extends vtyx.Vue {
2787
2846
  const store = this.store;
2788
2847
  const keepOpenWithOtherSelectic = this.params.keepOpenWithOtherSelectic;
2789
2848
  const extendedList = this.$refs.extendedList;
2790
- if (!extendedList) {
2849
+ const extendedListEl = extendedList === null || extendedList === void 0 ? void 0 : extendedList.$el;
2850
+ if (!extendedListEl) {
2791
2851
  /* this component is not focused anymore */
2792
2852
  if (!keepOpenWithOtherSelectic) {
2793
2853
  this.removeListeners();
@@ -2796,7 +2856,7 @@ let Selectic = class Selectic extends vtyx.Vue {
2796
2856
  return;
2797
2857
  }
2798
2858
  const target = evt.target;
2799
- if (!extendedList.$el.contains(target) && !this.$el.contains(target)) {
2859
+ if (!extendedListEl.contains(target) && !this.$el.contains(target)) {
2800
2860
  store.commit('isOpen', false);
2801
2861
  }
2802
2862
  };
@@ -2889,14 +2949,23 @@ let Selectic = class Selectic extends vtyx.Vue {
2889
2949
  /* }}} */
2890
2950
  /* {{{ private methods */
2891
2951
  computeWidth() {
2892
- const el = this.$refs.mainInput.$el;
2893
- this.width = el.offsetWidth;
2952
+ var _a;
2953
+ const mainInput = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.mainInput;
2954
+ const mainEl = mainInput === null || mainInput === void 0 ? void 0 : mainInput.$el;
2955
+ if (!mainEl) {
2956
+ /* This method has been called too soon (before render function)
2957
+ * or too late (after unmount) */
2958
+ return;
2959
+ }
2960
+ this.width = mainEl.offsetWidth;
2894
2961
  }
2895
2962
  computeOffset(doNotAddListener = false) {
2896
- const mainInput = this.$refs.mainInput;
2963
+ var _a;
2964
+ const mainInput = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.mainInput;
2897
2965
  const mainEl = mainInput === null || mainInput === void 0 ? void 0 : mainInput.$el;
2898
2966
  if (!mainEl) {
2899
- /* This method has been called too soon (before render function) */
2967
+ /* This method has been called too soon (before render function)
2968
+ * or too late (after unmount) */
2900
2969
  return;
2901
2970
  }
2902
2971
  const _elementsListeners = this._elementsListeners;
@@ -3030,13 +3099,14 @@ let Selectic = class Selectic extends vtyx.Vue {
3030
3099
  checkFocus() {
3031
3100
  /* Await that focused element becomes active */
3032
3101
  setTimeout(() => {
3102
+ var _a;
3033
3103
  const focusedEl = document.activeElement;
3034
- const extendedList = this.$refs.extendedList;
3104
+ const extendedList = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.extendedList;
3035
3105
  /* check if there is a focused element (if none the body is
3036
3106
  * selected) and if it is inside current Selectic */
3037
3107
  if (focusedEl === document.body
3038
3108
  || this.$el.contains(focusedEl)
3039
- || (extendedList && extendedList.$el.contains(focusedEl))) {
3109
+ || (extendedList === null || extendedList === void 0 ? void 0 : extendedList.$el.contains(focusedEl))) {
3040
3110
  return;
3041
3111
  }
3042
3112
  this.store.commit('isOpen', false);
@@ -28,7 +28,7 @@ function styleInject(css, ref) {
28
28
  }
29
29
  }
30
30
 
31
- var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n top: var(--top-position, 0);\n z-index: 2000;\n height: auto;\n max-height: var(--availableSpace);\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n width: var(--list-width, 200px);\n min-width: 200px;\n display: grid;\n grid-template-rows: minmax(0, max-content) 1fr;\n}\n.selectic__extended-list.selectic-position-top {\n box-shadow: 2px -3px 12px 0px #888888;\n}\n.selectic__extended-list__list-container{\n overflow: auto;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n min-width: max-content;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item__is-group.selectable {\n cursor: pointer;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n.selectic .form-control-feedback.fa.selectic-search-scope {\n width: calc(var(--selectic-input-height) * 0.75);\n height: calc(var(--selectic-input-height) * 0.75);\n line-height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* {{{ icons */\n\n@keyframes selectic-animation-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(359deg);\n }\n}\n\n.selectic__icon {\n height: 1em;\n fill: currentColor;\n}\n\n.selectic-spin {\n animation: selectic-animation-spin 2s infinite linear;\n}\n\n/* }}} */\n";
31
+ var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n top: var(--top-position, 0);\n z-index: 2000;\n height: auto;\n max-height: var(--availableSpace);\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n width: var(--list-width, 200px);\n min-width: 200px;\n display: grid;\n grid-template-rows: minmax(0, max-content) 1fr;\n}\n.selectic__extended-list.selectic-position-top {\n box-shadow: 2px -3px 12px 0px #888888;\n}\n.selectic__extended-list__list-container{\n overflow: auto;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n min-width: max-content;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n.selectic-item__active.selectic-item__disabled:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item__is-group.selectable {\n cursor: pointer;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n.selectic .form-control-feedback.fa.selectic-search-scope {\n width: calc(var(--selectic-input-height) * 0.75);\n height: calc(var(--selectic-input-height) * 0.75);\n line-height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* {{{ icons */\n\n@keyframes selectic-animation-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(359deg);\n }\n}\n\n.selectic__icon {\n height: 1em;\n fill: currentColor;\n}\n\n.selectic-spin {\n animation: selectic-animation-spin 2s infinite linear;\n}\n\n/* }}} */\n";
32
32
  styleInject(css_248z);
33
33
 
34
34
  /**
@@ -554,25 +554,54 @@ class SelecticStore {
554
554
  if (selected === undefined) {
555
555
  selected = !isAlreadySelected;
556
556
  }
557
+ const selectedOptions = Array.isArray(state.selectedOptions)
558
+ ? state.selectedOptions
559
+ : [];
557
560
  if (id === null) {
558
- state.internalValue = [];
559
- hasChanged = internalValue.length > 0;
561
+ /* Keep disabled items: we cannot removed them because they
562
+ * are disabled */
563
+ const newSelection = selectedOptions.reduce((list, item) => {
564
+ if (item.disabled && item.id) {
565
+ list.push(item.id);
566
+ }
567
+ return list;
568
+ }, []);
569
+ state.internalValue = newSelection;
570
+ hasChanged = internalValue.length > newSelection.length;
560
571
  }
561
572
  else if (selected && !isAlreadySelected) {
573
+ let addItem = true;
562
574
  if (item === null || item === void 0 ? void 0 : item.exclusive) {
563
- /* clear the current selection because the item is exclusive */
564
- internalValue.splice(0, Infinity);
575
+ const hasDisabledSelected = selectedOptions.some((opt) => {
576
+ return opt.disabled;
577
+ });
578
+ if (hasDisabledSelected) {
579
+ /* do not remove disabled item from selection */
580
+ addItem = false;
581
+ }
582
+ else {
583
+ /* clear the current selection because the item is exclusive */
584
+ internalValue.splice(0, Infinity);
585
+ }
565
586
  }
566
587
  else if (internalValue.length === 1) {
567
588
  const selectedId = internalValue[0];
568
589
  const selectedItem = state.allOptions.find((opt) => opt.id === selectedId);
569
590
  if (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.exclusive) {
570
- /* clear the current selection because the old item was exclusive */
571
- internalValue.pop();
591
+ if (selectedItem.disabled) {
592
+ /* If selected item is disabled and exclusive do not change the selection */
593
+ addItem = false;
594
+ }
595
+ else {
596
+ /* clear the current selection because the old item was exclusive */
597
+ internalValue.pop();
598
+ }
572
599
  }
573
600
  }
574
- internalValue.push(id);
575
- hasChanged = true;
601
+ if (addItem) {
602
+ internalValue.push(id);
603
+ hasChanged = true;
604
+ }
576
605
  }
577
606
  else if (!selected && isAlreadySelected) {
578
607
  internalValue.splice(internalValue.indexOf(id), 1);
@@ -595,6 +624,11 @@ class SelecticStore {
595
624
  if (id !== oldValue) {
596
625
  return hasChanged;
597
626
  }
627
+ const oldOption = state.selectedOptions;
628
+ if (oldOption === null || oldOption === void 0 ? void 0 : oldOption.disabled) {
629
+ /* old selection is disabled so do not unselect it */
630
+ return hasChanged;
631
+ }
598
632
  id = null;
599
633
  }
600
634
  else if (id === oldValue) {
@@ -1364,15 +1398,23 @@ class SelecticStore {
1364
1398
  if (doNotCheck || !hasFetchedAllItems) {
1365
1399
  return;
1366
1400
  }
1401
+ const selectedOptions = state.selectedOptions;
1367
1402
  const enabledOptions = state.allOptions.filter((opt) => !opt.disabled);
1368
- const nb = enabledOptions.length;
1403
+ const nbEnabled = enabledOptions.length;
1369
1404
  const value = state.internalValue;
1370
1405
  const hasValue = Array.isArray(value) ? value.length > 0 : value !== null;
1371
- const hasValidValue = hasValue && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1406
+ const hasDisabledSelected = Array.isArray(selectedOptions)
1407
+ ? selectedOptions.some((opt) => opt.disabled)
1408
+ : false;
1409
+ const hasOnlyValidValue = hasValue && !hasDisabledSelected && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1372
1410
  this.hasValue(value));
1373
- const isEmpty = nb === 0;
1374
- const hasOnlyOneOption = nb === 1 && hasValidValue && !state.allowClearSelection;
1375
- if (hasOnlyOneOption || isEmpty) {
1411
+ const isEmpty = nbEnabled === 0;
1412
+ const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
1413
+ const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1414
+ && selectedOptions.length === 1
1415
+ && selectedOptions[0].exclusive
1416
+ && selectedOptions[0].disabled;
1417
+ if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
1376
1418
  if (state.isOpen) {
1377
1419
  this.setAutomaticClose();
1378
1420
  this.commit('isOpen', false);
@@ -1792,6 +1834,18 @@ let MainInput = class MainInput extends Vue {
1792
1834
  ? Array.isArray(value) && value.length > 0
1793
1835
  : value !== null;
1794
1836
  }
1837
+ get disabledList() {
1838
+ const state = this.store.state;
1839
+ const isMultiple = state.multiple;
1840
+ const value = state.selectedOptions;
1841
+ if (!isMultiple || !value) {
1842
+ return [];
1843
+ }
1844
+ const disabledValues = value.filter((option) => {
1845
+ return option.disabled;
1846
+ });
1847
+ return disabledValues;
1848
+ }
1795
1849
  get displayPlaceholder() {
1796
1850
  const placeholder = this.store.state.placeholder;
1797
1851
  const hasValue = this.hasValue;
@@ -1810,10 +1864,12 @@ let MainInput = class MainInput extends Vue {
1810
1864
  const state = this.store.state;
1811
1865
  const isMultiple = state.multiple;
1812
1866
  const value = state.internalValue;
1813
- const hasOnlyOneValue = Array.isArray(value) && value.length === 1;
1867
+ const nbSelection = (Array.isArray(value) && value.length) || 0;
1868
+ const hasOnlyOneValue = nbSelection === 1;
1869
+ const hasOnlyDisabled = nbSelection <= this.disabledList.length;
1814
1870
  /* Should not display the clear action if there is only one selected
1815
1871
  * item in multiple (as this item has already its remove icon) */
1816
- return !isMultiple || !hasOnlyOneValue;
1872
+ return !isMultiple || !hasOnlyOneValue || !hasOnlyDisabled;
1817
1873
  }
1818
1874
  get clearedLabel() {
1819
1875
  const isMultiple = this.store.state.multiple;
@@ -1931,6 +1987,9 @@ let MainInput = class MainInput extends Vue {
1931
1987
  /* Check if there is enough space to display items like there are
1932
1988
  * currently shown */
1933
1989
  const el = this.$refs.selectedItems;
1990
+ if (!el) {
1991
+ return;
1992
+ }
1934
1993
  const parentEl = el.parentElement;
1935
1994
  if (!document.contains(parentEl)) {
1936
1995
  /* The element is currently not in DOM */
@@ -2029,7 +2088,7 @@ let MainInput = class MainInput extends Vue {
2029
2088
  click: () => this.$emit('item:click', item.id),
2030
2089
  } },
2031
2090
  h("span", { class: "selectic-input__selected-items__value" }, item.text),
2032
- !this.isDisabled && (h(Icon$1, { icon: "times", class: "selectic-input__selected-items__icon", store: this.store, on: {
2091
+ !this.isDisabled && !item.disabled && (h(Icon$1, { icon: "times", class: "selectic-input__selected-items__icon", store: this.store, on: {
2033
2092
  'click.prevent.stop': () => this.selectItem(item.id),
2034
2093
  } }))))),
2035
2094
  this.moreSelectedNb && (h("div", { class: "single-value more-items", title: this.moreSelectedTitle }, this.moreSelectedNb)))),
@@ -2783,7 +2842,8 @@ let Selectic = class Selectic extends Vue {
2783
2842
  const store = this.store;
2784
2843
  const keepOpenWithOtherSelectic = this.params.keepOpenWithOtherSelectic;
2785
2844
  const extendedList = this.$refs.extendedList;
2786
- if (!extendedList) {
2845
+ const extendedListEl = extendedList === null || extendedList === void 0 ? void 0 : extendedList.$el;
2846
+ if (!extendedListEl) {
2787
2847
  /* this component is not focused anymore */
2788
2848
  if (!keepOpenWithOtherSelectic) {
2789
2849
  this.removeListeners();
@@ -2792,7 +2852,7 @@ let Selectic = class Selectic extends Vue {
2792
2852
  return;
2793
2853
  }
2794
2854
  const target = evt.target;
2795
- if (!extendedList.$el.contains(target) && !this.$el.contains(target)) {
2855
+ if (!extendedListEl.contains(target) && !this.$el.contains(target)) {
2796
2856
  store.commit('isOpen', false);
2797
2857
  }
2798
2858
  };
@@ -2885,14 +2945,23 @@ let Selectic = class Selectic extends Vue {
2885
2945
  /* }}} */
2886
2946
  /* {{{ private methods */
2887
2947
  computeWidth() {
2888
- const el = this.$refs.mainInput.$el;
2889
- this.width = el.offsetWidth;
2948
+ var _a;
2949
+ const mainInput = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.mainInput;
2950
+ const mainEl = mainInput === null || mainInput === void 0 ? void 0 : mainInput.$el;
2951
+ if (!mainEl) {
2952
+ /* This method has been called too soon (before render function)
2953
+ * or too late (after unmount) */
2954
+ return;
2955
+ }
2956
+ this.width = mainEl.offsetWidth;
2890
2957
  }
2891
2958
  computeOffset(doNotAddListener = false) {
2892
- const mainInput = this.$refs.mainInput;
2959
+ var _a;
2960
+ const mainInput = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.mainInput;
2893
2961
  const mainEl = mainInput === null || mainInput === void 0 ? void 0 : mainInput.$el;
2894
2962
  if (!mainEl) {
2895
- /* This method has been called too soon (before render function) */
2963
+ /* This method has been called too soon (before render function)
2964
+ * or too late (after unmount) */
2896
2965
  return;
2897
2966
  }
2898
2967
  const _elementsListeners = this._elementsListeners;
@@ -3026,13 +3095,14 @@ let Selectic = class Selectic extends Vue {
3026
3095
  checkFocus() {
3027
3096
  /* Await that focused element becomes active */
3028
3097
  setTimeout(() => {
3098
+ var _a;
3029
3099
  const focusedEl = document.activeElement;
3030
- const extendedList = this.$refs.extendedList;
3100
+ const extendedList = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.extendedList;
3031
3101
  /* check if there is a focused element (if none the body is
3032
3102
  * selected) and if it is inside current Selectic */
3033
3103
  if (focusedEl === document.body
3034
3104
  || this.$el.contains(focusedEl)
3035
- || (extendedList && extendedList.$el.contains(focusedEl))) {
3105
+ || (extendedList === null || extendedList === void 0 ? void 0 : extendedList.$el.contains(focusedEl))) {
3036
3106
  return;
3037
3107
  }
3038
3108
  this.store.commit('isOpen', false);
package/doc/list.md CHANGED
@@ -28,12 +28,13 @@ It is possible to define the `option` more precisely.
28
28
  * **text** {`string`} _(mandatory)_: The text which is displayed to select the option or when it is selected.
29
29
  * **title** {`string`}: Text displayed in `title` when cursor is over the option (default: `''`).
30
30
  * **disabled** {`boolean`}: if `true`, this option cannot be selected (default: `false`).
31
+ In "multiple" mode, if the option is already selected, it cannot be removed from selection (it can always be removed by changing `value` of the component).
31
32
  * **className** {`string`}: `class` that are applied on the option (default: `''`).
32
33
  * **style** {`string`}: css style which are applied on the option (default: `''`).
33
34
  * **icon** {`string`}: class names which are applied on a `<span>` before text in the option to display an icon (default: `''`).
34
35
  * **options** {`options[]`}: an other list of options. The current option is considered as a group (equivalent of `optgroup`) (default: `undefined`).
35
36
  * **group** {`string | number`}: If set, the option is part of the given group. This property is needed only in dynamic mode if the option is part of an optgroup (default: `null`).
36
- * **exclusive** {`boolean`}: If set to `true`, in multiple mode, this option will be the only one selected. It means that it clears the previous selected options, and if another option is selected, this option is no more selected.
37
+ * **exclusive** {`boolean`}: If set to `true`, in "multiple" mode, this option will be the only one selected. It means that it clears the previous selected options, and if another option is selected, this option is no more selected.
37
38
  * **data** {`any`}: You can store any information here, it will be provided when getting selected options. _It is not used by selectic so it can be anything you want_ (default: `undefined`).
38
39
 
39
40
  ```javascript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selectic",
3
- "version": "3.1.2",
3
+ "version": "3.1.3",
4
4
  "description": "Smart Select for VueJS 3.x",
5
5
  "main": "dist/selectic.common.js",
6
6
  "module": "dist/selectic.esm.js",
@@ -17,6 +17,7 @@
17
17
  "homepage": "https://github.com/intersec/selectic#readme",
18
18
  "keywords": [
19
19
  "select",
20
+ "selectic",
20
21
  "multiselect",
21
22
  "multi-select",
22
23
  "multi-select",
package/src/MainInput.tsx CHANGED
@@ -17,7 +17,7 @@ export interface Props {
17
17
  @Component
18
18
  export default class MainInput extends Vue<Props> {
19
19
  public $refs: {
20
- selectedItems: HTMLDivElement;
20
+ selectedItems?: HTMLDivElement;
21
21
  };
22
22
 
23
23
  /* {{{ props */
@@ -53,6 +53,22 @@ export default class MainInput extends Vue<Props> {
53
53
  : value !== null;
54
54
  }
55
55
 
56
+ get disabledList(): OptionItem[] {
57
+ const state = this.store.state;
58
+ const isMultiple = state.multiple;
59
+ const value = state.selectedOptions;
60
+
61
+ if (!isMultiple || !value) {
62
+ return [];
63
+ }
64
+
65
+ const disabledValues = (value as OptionItem[]).filter((option) => {
66
+ return option.disabled;
67
+ });
68
+
69
+ return disabledValues;
70
+ }
71
+
56
72
  get displayPlaceholder(): boolean {
57
73
  const placeholder = this.store.state.placeholder;
58
74
  const hasValue = this.hasValue;
@@ -76,11 +92,13 @@ export default class MainInput extends Vue<Props> {
76
92
  const state = this.store.state;
77
93
  const isMultiple = state.multiple;
78
94
  const value = state.internalValue;
79
- const hasOnlyOneValue = Array.isArray(value) && value.length === 1;
95
+ const nbSelection = (Array.isArray(value) && value.length) || 0;
96
+ const hasOnlyOneValue = nbSelection === 1;
97
+ const hasOnlyDisabled = nbSelection <= this.disabledList.length;
80
98
 
81
99
  /* Should not display the clear action if there is only one selected
82
100
  * item in multiple (as this item has already its remove icon) */
83
- return !isMultiple || !hasOnlyOneValue;
101
+ return !isMultiple || !hasOnlyOneValue || !hasOnlyDisabled;
84
102
  }
85
103
 
86
104
  get clearedLabel(): string {
@@ -239,6 +257,11 @@ export default class MainInput extends Vue<Props> {
239
257
  /* Check if there is enough space to display items like there are
240
258
  * currently shown */
241
259
  const el = this.$refs.selectedItems;
260
+
261
+ if (!el) {
262
+ return;
263
+ }
264
+
242
265
  const parentEl = el.parentElement as HTMLDivElement;
243
266
 
244
267
  if (!document.contains(parentEl)) {
@@ -401,7 +424,7 @@ export default class MainInput extends Vue<Props> {
401
424
  >
402
425
  { item.text }
403
426
  </span>
404
- {!this.isDisabled && (
427
+ {!this.isDisabled && !item.disabled && (
405
428
  <Icon
406
429
  icon="times"
407
430
  class="selectic-input__selected-items__icon"
package/src/Store.tsx CHANGED
@@ -939,24 +939,58 @@ export default class SelecticStore {
939
939
  selected = !isAlreadySelected;
940
940
  }
941
941
 
942
+ const selectedOptions = Array.isArray(state.selectedOptions)
943
+ ? state.selectedOptions
944
+ : [];
945
+
942
946
  if (id === null) {
943
- state.internalValue = [];
944
- hasChanged = internalValue.length > 0;
947
+ /* Keep disabled items: we cannot removed them because they
948
+ * are disabled */
949
+ const newSelection = selectedOptions.reduce((list, item) => {
950
+ if (item.disabled && item.id) {
951
+ list.push(item.id);
952
+ }
953
+
954
+ return list;
955
+ }, [] as StrictOptionId[]);
956
+
957
+ state.internalValue = newSelection;
958
+ hasChanged = internalValue.length > newSelection.length;
945
959
  } else
946
960
  if (selected && !isAlreadySelected) {
961
+ let addItem = true;
962
+
947
963
  if (item?.exclusive) {
948
- /* clear the current selection because the item is exclusive */
949
- internalValue.splice(0, Infinity);
964
+ const hasDisabledSelected = selectedOptions.some((opt) => {
965
+ return opt.disabled;
966
+ });
967
+
968
+ if (hasDisabledSelected) {
969
+ /* do not remove disabled item from selection */
970
+ addItem = false;
971
+ } else {
972
+ /* clear the current selection because the item is exclusive */
973
+ internalValue.splice(0, Infinity);
974
+ }
950
975
  } else if (internalValue.length === 1) {
951
976
  const selectedId = internalValue[0];
952
977
  const selectedItem = state.allOptions.find((opt) => opt.id === selectedId);
978
+
953
979
  if (selectedItem?.exclusive) {
954
- /* clear the current selection because the old item was exclusive */
955
- internalValue.pop();
980
+ if (selectedItem.disabled) {
981
+ /* If selected item is disabled and exclusive do not change the selection */
982
+ addItem = false;
983
+ } else {
984
+ /* clear the current selection because the old item was exclusive */
985
+ internalValue.pop();
986
+ }
956
987
  }
957
988
  }
958
- internalValue.push(id);
959
- hasChanged = true;
989
+
990
+ if (addItem) {
991
+ internalValue.push(id);
992
+ hasChanged = true;
993
+ }
960
994
  } else
961
995
  if (!selected && isAlreadySelected) {
962
996
  internalValue.splice(internalValue.indexOf(id), 1);
@@ -982,6 +1016,14 @@ export default class SelecticStore {
982
1016
  if (id !== oldValue) {
983
1017
  return hasChanged;
984
1018
  }
1019
+
1020
+ const oldOption = state.selectedOptions as OptionItem | null;
1021
+
1022
+ if (oldOption?.disabled) {
1023
+ /* old selection is disabled so do not unselect it */
1024
+ return hasChanged;
1025
+ }
1026
+
985
1027
  id = null;
986
1028
  } else
987
1029
  if (id === oldValue) {
@@ -1925,18 +1967,27 @@ export default class SelecticStore {
1925
1967
  return;
1926
1968
  }
1927
1969
 
1970
+ const selectedOptions = state.selectedOptions;
1928
1971
  const enabledOptions = state.allOptions.filter((opt) => !opt.disabled);
1929
- const nb = enabledOptions.length;
1972
+ const nbEnabled = enabledOptions.length;
1930
1973
  const value = state.internalValue;
1931
1974
  const hasValue = Array.isArray(value) ? value.length > 0 : value !== null;
1932
- const hasValidValue = hasValue && (
1975
+ const hasDisabledSelected = Array.isArray(selectedOptions)
1976
+ ? selectedOptions.some((opt) => opt.disabled)
1977
+ : false;
1978
+ const hasOnlyValidValue = hasValue && !hasDisabledSelected && (
1933
1979
  Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1934
1980
  this.hasValue(value)
1935
1981
  );
1936
- const isEmpty = nb === 0;
1937
- const hasOnlyOneOption = nb === 1 && hasValidValue && !state.allowClearSelection;
1938
1982
 
1939
- if (hasOnlyOneOption || isEmpty) {
1983
+ const isEmpty = nbEnabled === 0;
1984
+ const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
1985
+ const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1986
+ && selectedOptions.length === 1
1987
+ && selectedOptions[0].exclusive
1988
+ && selectedOptions[0].disabled;
1989
+
1990
+ if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
1940
1991
  if (state.isOpen) {
1941
1992
  this.setAutomaticClose();
1942
1993
  this.commit('isOpen', false);
@@ -244,6 +244,9 @@
244
244
  .selectic-item__active:not(.selected) .selectic-item_icon {
245
245
  opacity: 0.2;
246
246
  }
247
+ .selectic-item__active.selectic-item__disabled:not(.selected) .selectic-item_icon {
248
+ opacity: 0;
249
+ }
247
250
 
248
251
  .selectic-item__disabled {
249
252
  color: var(--selectic-color-disabled);
package/src/index.tsx CHANGED
@@ -243,7 +243,7 @@ export function changeIcons(icons: PartialIcons, iconFamily?: IconFamily) {
243
243
  export default class Selectic extends Vue<Props> {
244
244
  public $refs: {
245
245
  mainInput: MainInput;
246
- extendedList: ExtendedList;
246
+ extendedList?: ExtendedList;
247
247
  };
248
248
 
249
249
  /* {{{ props */
@@ -349,19 +349,21 @@ export default class Selectic extends Vue<Props> {
349
349
  const store = this.store;
350
350
  const keepOpenWithOtherSelectic = this.params.keepOpenWithOtherSelectic;
351
351
  const extendedList = this.$refs.extendedList;
352
+ const extendedListEl: HTMLElement | undefined = extendedList?.$el;
352
353
 
353
- if (!extendedList) {
354
+ if (!extendedListEl) {
354
355
  /* this component is not focused anymore */
355
356
  if (!keepOpenWithOtherSelectic) {
356
357
  this.removeListeners();
357
358
  this.store.commit('isOpen', false);
358
359
  }
360
+
359
361
  return;
360
362
  }
361
363
 
362
364
  const target = evt.target as Node;
363
365
 
364
- if (!extendedList.$el.contains(target) && !this.$el.contains(target)) {
366
+ if (!extendedListEl.contains(target) && !this.$el.contains(target)) {
365
367
  store.commit('isOpen', false);
366
368
  }
367
369
  };
@@ -477,18 +479,27 @@ export default class Selectic extends Vue<Props> {
477
479
  /* {{{ private methods */
478
480
 
479
481
  private computeWidth() {
480
- const el = this.$refs.mainInput.$el as HTMLElement;
482
+ const mainInput = this.$refs?.mainInput;
483
+
484
+ const mainEl: HTMLElement | undefined = mainInput?.$el;
481
485
 
482
- this.width = el.offsetWidth;
486
+ if (!mainEl) {
487
+ /* This method has been called too soon (before render function)
488
+ * or too late (after unmount) */
489
+ return;
490
+ }
491
+
492
+ this.width = mainEl.offsetWidth;
483
493
  }
484
494
 
485
495
  private computeOffset(doNotAddListener = false) {
486
- const mainInput = this.$refs.mainInput;
496
+ const mainInput = this.$refs?.mainInput;
487
497
 
488
- const mainEl = mainInput?.$el as HTMLElement;
498
+ const mainEl: HTMLElement | undefined = mainInput?.$el;
489
499
 
490
500
  if (!mainEl) {
491
- /* This method has been called too soon (before render function) */
501
+ /* This method has been called too soon (before render function)
502
+ * or too late (after unmount) */
492
503
  return;
493
504
  }
494
505
 
@@ -669,13 +680,13 @@ export default class Selectic extends Vue<Props> {
669
680
  /* Await that focused element becomes active */
670
681
  setTimeout(() => {
671
682
  const focusedEl = document.activeElement;
672
- const extendedList = this.$refs.extendedList;
683
+ const extendedList = this.$refs?.extendedList;
673
684
 
674
685
  /* check if there is a focused element (if none the body is
675
686
  * selected) and if it is inside current Selectic */
676
687
  if (focusedEl === document.body
677
688
  || this.$el.contains(focusedEl)
678
- || (extendedList && extendedList.$el.contains(focusedEl)))
689
+ || extendedList?.$el.contains(focusedEl))
679
690
  {
680
691
  return;
681
692
  }
@@ -699,6 +699,80 @@ tape.test('Store creation', (subT) => {
699
699
  t.end();
700
700
  });
701
701
 
702
+ sTest.test('should disable multiple select with the only enabled value selected', async (t) => {
703
+ const options = getOptions(3);
704
+ options[0].disabled = true;
705
+ options[1].disabled = true;
706
+
707
+ const store = new Store({
708
+ options: options,
709
+ disabled: false,
710
+ value: [2],
711
+ params: {
712
+ multiple: true,
713
+ autoDisabled: true,
714
+ allowClearSelection: false,
715
+ },
716
+ });
717
+
718
+ await sleep(0);
719
+
720
+ t.is(store.state.disabled, true);
721
+ t.deepEqual(store.state.internalValue, [2]);
722
+
723
+ t.end();
724
+ });
725
+
726
+ sTest.test('should not disable multiple select when it is possible to remove selected value', async (t) => {
727
+ const options = getOptions(3);
728
+ options[0].disabled = true;
729
+ options[1].disabled = true;
730
+
731
+ const store = new Store({
732
+ options: options,
733
+ disabled: false,
734
+ value: [1, 2],
735
+ params: {
736
+ multiple: true,
737
+ autoDisabled: true,
738
+ allowClearSelection: false,
739
+ },
740
+ });
741
+
742
+ await sleep(0);
743
+
744
+ /* It is possible to remove the item "2" because there is another value */
745
+ t.is(store.state.disabled, false);
746
+ t.deepEqual(store.state.internalValue, [1, 2]);
747
+
748
+ t.end();
749
+ });
750
+
751
+ sTest.test('should disable multiple select when exclusive disabled item is selected', async (t) => {
752
+ const options = getOptions(3);
753
+ options[1].disabled = true;
754
+ options[1].exclusive = true;
755
+
756
+ const store = new Store({
757
+ options: options,
758
+ disabled: false,
759
+ value: [1],
760
+ params: {
761
+ multiple: true,
762
+ autoDisabled: true,
763
+ allowClearSelection: false,
764
+ },
765
+ });
766
+
767
+ await sleep(0);
768
+
769
+ /* It is not possible to change the value */
770
+ t.is(store.state.disabled, true);
771
+ t.deepEqual(store.state.internalValue, [1]);
772
+
773
+ t.end();
774
+ });
775
+
702
776
  sTest.test('should not disable select without autoDisabled', async (t) => {
703
777
  const store = new Store({
704
778
  options: getOptions(1),
@@ -31,7 +31,7 @@ tape.test('selectItem()', (st) => {
31
31
  const result1 = store.selectItem(2, true);
32
32
  t.is(store.state.internalValue, 2);
33
33
  t.is(store.state.status.hasChanged, true);
34
- t.is(result1, true, 'should return if a change occurs');
34
+ t.is(result1, true, 'should return true if a change occurs');
35
35
 
36
36
  /* reset status to check that it is modified */
37
37
  store.state.status.hasChanged = false;
@@ -39,7 +39,17 @@ tape.test('selectItem()', (st) => {
39
39
  const result2 = store.selectItem(5, true);
40
40
  t.is(store.state.internalValue, 5);
41
41
  t.is(store.state.status.hasChanged, true);
42
- t.is(result2, true, 'should return if a change occurs');
42
+ t.is(result2, true, 'should return true if a change occurs');
43
+
44
+ /* Should replace a disabled selected item */
45
+ store.commit('internalValue', 4);
46
+ store.state.status.hasChanged = false;
47
+
48
+ const result3 = store.selectItem(3);
49
+ t.is(store.state.internalValue, 3);
50
+ t.is(store.state.status.hasChanged, true);
51
+ t.is(result3, true, 'should return true if a change occurs');
52
+
43
53
  t.end();
44
54
  });
45
55
 
@@ -50,12 +60,24 @@ tape.test('selectItem()', (st) => {
50
60
  /* reset status to check that it is modified */
51
61
  store.state.status.hasChanged = false;
52
62
 
53
- const result = store.selectItem(2, false);
63
+ const result1 = store.selectItem(2, false);
54
64
  t.is(store.state.internalValue, null);
55
65
  t.is(store.state.status.hasChanged, true);
56
66
 
57
67
  t.is(store.state.selectionIsExcluded, false);
58
- t.is(result, true, 'should return if a change occurs');
68
+ t.is(result1, true, 'should return true if a change occurs');
69
+
70
+ /* Should not deselect a disabled selected item */
71
+ store.commit('internalValue', 4);
72
+ store.state.status.hasChanged = false;
73
+
74
+ const result2 = store.selectItem(4, false);
75
+ t.is(store.state.internalValue, 4);
76
+ t.is(store.state.status.hasChanged, false);
77
+
78
+ t.is(store.state.selectionIsExcluded, false);
79
+ t.is(result2, false, 'should return false if no change occurs');
80
+
59
81
  t.end();
60
82
  });
61
83
 
@@ -173,31 +195,40 @@ tape.test('selectItem()', (st) => {
173
195
  sTest.test('should clear selection', (t) => {
174
196
  const store = getStore();
175
197
  store.commit('isOpen', true);
176
- store.state.internalValue = 1;
198
+ store.commit('internalValue', 1);
177
199
 
178
200
  const result1 = store.selectItem(null);
179
201
  t.is(store.state.isOpen, false);
180
202
  t.is(store.state.internalValue, null);
181
203
  t.is(store.state.status.hasChanged, true);
182
- t.is(result1, true, 'should return if a change occurs');
204
+ t.is(result1, true, 'should return true if a change occurs');
183
205
 
206
+ store.commit('internalValue', 2);
184
207
  store.state.status.hasChanged = false;
185
- store.state.internalValue = 2;
186
208
 
187
209
  /* applied also when selectic is closed */
188
210
  const result2 = store.selectItem(null);
189
211
  t.is(store.state.internalValue, null);
190
212
  t.is(store.state.status.hasChanged, true);
191
- t.is(result2, true, 'should return if a change occurs');
213
+ t.is(result2, true, 'should return true if a change occurs');
192
214
 
215
+ store.commit('internalValue', 3);
193
216
  store.state.status.hasChanged = false;
194
- store.state.internalValue = 3;
195
217
 
196
218
  /* ignore the selected argument */
197
219
  const result3 = store.selectItem(null, false);
198
220
  t.is(store.state.internalValue, null);
199
221
  t.is(store.state.status.hasChanged, true);
200
- t.is(result3, true, 'should return if a change occurs');
222
+ t.is(result3, true, 'should return true if a change occurs');
223
+
224
+ /* Should removed a disabled selected item */
225
+ store.commit('internalValue', 4);
226
+ store.state.status.hasChanged = false;
227
+
228
+ const result4 = store.selectItem(null, false);
229
+ t.is(store.state.internalValue, null);
230
+ t.is(store.state.status.hasChanged, true);
231
+ t.is(result4, true, 'should return true if a change occurs');
201
232
 
202
233
  t.end();
203
234
  });
@@ -225,10 +256,12 @@ tape.test('selectItem()', (st) => {
225
256
 
226
257
  st.test('when "multiple" is true', (sTest) => {
227
258
  function getStore() {
228
- const options = getOptions(8);
259
+ const options = getOptions(9);
229
260
  options[4].disabled = true;
230
261
  options[6].exclusive = true;
231
262
  options[7].exclusive = true;
263
+ options[8].disabled = true;
264
+ options[8].exclusive = true;
232
265
 
233
266
  const store = new Store({
234
267
  options: options,
@@ -410,24 +443,24 @@ tape.test('selectItem()', (st) => {
410
443
 
411
444
  sTest.test('should clear selection', (t) => {
412
445
  const store = getStore();
413
- store.state.internalValue = [1, 4];
446
+ store.commit('internalValue', [1, 3]);
414
447
 
415
448
  const result1 = store.selectItem(null);
416
449
  t.deepEqual(store.state.internalValue, []);
417
450
  t.is(store.state.status.hasChanged, true);
418
- t.is(result1, true, 'should return if a change occurs');
451
+ t.is(result1, true, 'should return true if a change occurs');
419
452
 
453
+ store.commit('internalValue', [2, 3]);
420
454
  store.state.status.hasChanged = false;
421
- store.state.internalValue = [2, 4];
422
455
 
423
456
  /* ignore the selected argument */
424
457
  const result2 = store.selectItem(null, false);
425
458
  t.deepEqual(store.state.internalValue, []);
426
459
  t.is(store.state.status.hasChanged, true);
427
- t.is(result2, true, 'should return if a change occurs');
460
+ t.is(result2, true, 'should return true if a change occurs');
428
461
 
462
+ store.commit('internalValue', [3, 5]);
429
463
  store.state.status.hasChanged = false;
430
- store.state.internalValue = [3, 4];
431
464
 
432
465
  /* applied also when selectic is open */
433
466
  store.commit('isOpen', true);
@@ -435,7 +468,33 @@ tape.test('selectItem()', (st) => {
435
468
  t.is(store.state.isOpen, true);
436
469
  t.deepEqual(store.state.internalValue, []);
437
470
  t.is(store.state.status.hasChanged, true);
438
- t.is(result3, true, 'should return if a change occurs');
471
+ t.is(result3, true, 'should return true if a change occurs');
472
+ t.end();
473
+ });
474
+
475
+ sTest.test('should clear selection with disabled item', (t) => {
476
+ const store = getStore();
477
+ store.commit('internalValue', [1, 4]);
478
+
479
+ const result1 = store.selectItem(null);
480
+ t.deepEqual(store.state.internalValue, [4], 'should keep disabled item in selection');
481
+ t.is(store.state.status.hasChanged, true);
482
+ t.is(result1, true, 'should return true if a change occurs');
483
+
484
+ store.state.status.hasChanged = false;
485
+
486
+ /* With only disabled item in selection */
487
+ const result2 = store.selectItem(null);
488
+ t.deepEqual(store.state.internalValue, [4]);
489
+ t.is(store.state.status.hasChanged, false);
490
+ t.is(result2, false, 'should return false if no change occurs');
491
+
492
+ store.state.status.hasChanged = false;
493
+
494
+ /* Assert disabled items can be removed by changing the selection */
495
+ store.commit('internalValue', [3, 2]);
496
+ t.deepEqual(store.state.internalValue, [3, 2], 'should remove disabled item');
497
+
439
498
  t.end();
440
499
  });
441
500
 
@@ -466,30 +525,57 @@ tape.test('selectItem()', (st) => {
466
525
 
467
526
  sTest.test('should keep only exclusive item', (t) => {
468
527
  const store = getStore();
469
- store.state.internalValue = [1, 4, 5];
528
+ store.commit('internalValue', [1, 3, 5]);
470
529
 
471
530
  const result1 = store.selectItem(6, true);
472
531
  t.deepEqual(store.state.internalValue, [6]);
473
532
  t.is(store.state.status.hasChanged, true);
474
- t.is(result1, true, 'should return if a change occurs');
533
+ t.is(result1, true, 'should return true if a change occurs');
475
534
 
476
535
  const result2 = store.selectItem(7, true);
477
536
  t.deepEqual(store.state.internalValue, [7]);
478
537
  t.is(store.state.status.hasChanged, true);
479
- t.is(result2, true, 'should return if a change occurs');
538
+ t.is(result2, true, 'should return true if a change occurs');
480
539
 
481
540
  const result3 = store.selectItem(1, true);
482
541
  t.deepEqual(store.state.internalValue, [1]);
483
542
  t.is(store.state.status.hasChanged, true);
484
- t.is(result3, true, 'should return if a change occurs');
543
+ t.is(result3, true, 'should return true if a change occurs');
485
544
 
486
545
  const result4 = store.selectItem(5, true);
487
546
  t.deepEqual(store.state.internalValue, [1, 5]);
488
547
  t.is(store.state.status.hasChanged, true);
489
- t.is(result4, true, 'should return if a change occurs');
548
+ t.is(result4, true, 'should return true if a change occurs');
490
549
 
491
550
  t.is(store.state.selectionIsExcluded, false);
492
551
  t.end();
493
552
  });
553
+
554
+ sTest.test('should manage exclusive item with disabled item', (t) => {
555
+ const store = getStore();
556
+ store.commit('internalValue', [1, 4, 5]);
557
+
558
+ const result1 = store.selectItem(6, true);
559
+ t.deepEqual(store.state.internalValue, [1, 4, 5]);
560
+ t.is(store.state.status.hasChanged, false);
561
+ t.is(result1, false, 'should return false if no change occurs');
562
+
563
+ /* With exclusive and disabled item selected */
564
+ store.commit('internalValue', [8]);
565
+
566
+ const result2 = store.selectItem(6, true);
567
+ t.deepEqual(store.state.internalValue, [8]);
568
+ t.is(store.state.status.hasChanged, false);
569
+ t.is(result2, false, 'should return false if no change occurs');
570
+
571
+ const result3 = store.selectItem(1, true);
572
+ t.deepEqual(store.state.internalValue, [8]);
573
+ t.is(store.state.status.hasChanged, false);
574
+ t.is(result3, false, 'should return false if no change occurs');
575
+
576
+ t.is(store.state.selectionIsExcluded, false);
577
+
578
+ t.end();
579
+ });
494
580
  });
495
581
  });
@@ -6,7 +6,7 @@ export interface Props {
6
6
  }
7
7
  export default class MainInput extends Vue<Props> {
8
8
  $refs: {
9
- selectedItems: HTMLDivElement;
9
+ selectedItems?: HTMLDivElement;
10
10
  };
11
11
  private store;
12
12
  private id;
@@ -14,6 +14,7 @@ export default class MainInput extends Vue<Props> {
14
14
  private domObserver;
15
15
  get isDisabled(): boolean;
16
16
  get hasValue(): boolean;
17
+ get disabledList(): OptionItem[];
17
18
  get displayPlaceholder(): boolean;
18
19
  get canBeCleared(): boolean;
19
20
  get showClearAll(): boolean;
package/types/index.d.ts CHANGED
@@ -133,7 +133,7 @@ export declare function changeIcons(icons: PartialIcons, iconFamily?: IconFamily
133
133
  export default class Selectic extends Vue<Props> {
134
134
  $refs: {
135
135
  mainInput: MainInput;
136
- extendedList: ExtendedList;
136
+ extendedList?: ExtendedList;
137
137
  };
138
138
  value?: SelectedValue;
139
139
  selectionIsExcluded: boolean;