selectic 3.2.1 → 3.3.1

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.
package/.tool-versions CHANGED
@@ -1,5 +1,5 @@
1
- # ASDF configuration file for mmsx repository.
1
+ # ASDF configuration file for Selectic repository.
2
2
  #
3
3
  # https://asdf-vm.com/manage/configuration.html
4
4
 
5
- nodejs 12.18.4 system
5
+ nodejs 20.11.1 system
@@ -1456,13 +1456,26 @@ class SelecticStore {
1456
1456
  : false;
1457
1457
  const hasOnlyValidValue = hasValue && !hasDisabledSelected && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1458
1458
  this.hasValue(value));
1459
+ /* Whether at least one option, that is not disabled, is not selected. */
1460
+ const hasAvailableNonSelectedOptions = state.allOptions.some((opt) => {
1461
+ const isOptionSelected = (Array.isArray(selectedOptions)
1462
+ ? selectedOptions.some((selectedOpt) => selectedOpt.id === opt.id)
1463
+ : selectedOptions && selectedOptions.id === opt.id);
1464
+ return !isOptionSelected && !opt.disabled;
1465
+ });
1459
1466
  const isEmpty = nbEnabled === 0;
1460
- const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
1467
+ const hasOnlyOneOption = (nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection);
1468
+ /* In most cases if `hasOnlyOneOption` is true, we disable selection.
1469
+ * However, if we are not in multi-select mode and if the only option available is not the selected one then
1470
+ * we do not disable selection (to let the user switch from its current disabled selection,
1471
+ * and a valid one).
1472
+ */
1473
+ const cannotSelectAnyOption = hasOnlyOneOption && (Array.isArray(selectedOptions) || !hasAvailableNonSelectedOptions);
1461
1474
  const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1462
1475
  && selectedOptions.length === 1
1463
1476
  && selectedOptions[0].exclusive
1464
1477
  && selectedOptions[0].disabled;
1465
- if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
1478
+ if (cannotSelectAnyOption || isEmpty || isExclusiveDisabledItem) {
1466
1479
  if (state.isOpen) {
1467
1480
  this.setAutomaticClose();
1468
1481
  this.commit('isOpen', false);
@@ -3317,7 +3330,7 @@ let Selectic = class Selectic extends vtyx.Vue {
3317
3330
  multiple: ((_a = this.multiple) !== null && _a !== void 0 ? _a : false) !== false,
3318
3331
  pageSize: this.params.pageSize || 100,
3319
3332
  hideFilter: (_b = this.params.hideFilter) !== null && _b !== void 0 ? _b : 'auto',
3320
- allowRevert: this.params.allowRevert,
3333
+ allowRevert: this.params.allowRevert, /* it can be undefined */
3321
3334
  forceSelectAll: this.params.forceSelectAll || 'auto',
3322
3335
  allowClearSelection: this.params.allowClearSelection || false,
3323
3336
  autoSelect: this.params.autoSelect === undefined
@@ -3331,7 +3344,7 @@ let Selectic = class Selectic extends vtyx.Vue {
3331
3344
  formatOption: this.params.formatOption,
3332
3345
  formatSelection: this.params.formatSelection,
3333
3346
  listPosition: this.params.listPosition || 'auto',
3334
- optionBehavior: this.params.optionBehavior,
3347
+ optionBehavior: this.params.optionBehavior, /* it can be undefined */
3335
3348
  isOpen: ((_c = this.open) !== null && _c !== void 0 ? _c : false) !== false,
3336
3349
  disableGroupSelection: this.params.disableGroupSelection,
3337
3350
  },
@@ -1452,13 +1452,26 @@ class SelecticStore {
1452
1452
  : false;
1453
1453
  const hasOnlyValidValue = hasValue && !hasDisabledSelected && (Array.isArray(value) ? value.every((val) => this.hasValue(val)) :
1454
1454
  this.hasValue(value));
1455
+ /* Whether at least one option, that is not disabled, is not selected. */
1456
+ const hasAvailableNonSelectedOptions = state.allOptions.some((opt) => {
1457
+ const isOptionSelected = (Array.isArray(selectedOptions)
1458
+ ? selectedOptions.some((selectedOpt) => selectedOpt.id === opt.id)
1459
+ : selectedOptions && selectedOptions.id === opt.id);
1460
+ return !isOptionSelected && !opt.disabled;
1461
+ });
1455
1462
  const isEmpty = nbEnabled === 0;
1456
- const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
1463
+ const hasOnlyOneOption = (nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection);
1464
+ /* In most cases if `hasOnlyOneOption` is true, we disable selection.
1465
+ * However, if we are not in multi-select mode and if the only option available is not the selected one then
1466
+ * we do not disable selection (to let the user switch from its current disabled selection,
1467
+ * and a valid one).
1468
+ */
1469
+ const cannotSelectAnyOption = hasOnlyOneOption && (Array.isArray(selectedOptions) || !hasAvailableNonSelectedOptions);
1457
1470
  const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1458
1471
  && selectedOptions.length === 1
1459
1472
  && selectedOptions[0].exclusive
1460
1473
  && selectedOptions[0].disabled;
1461
- if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
1474
+ if (cannotSelectAnyOption || isEmpty || isExclusiveDisabledItem) {
1462
1475
  if (state.isOpen) {
1463
1476
  this.setAutomaticClose();
1464
1477
  this.commit('isOpen', false);
@@ -3313,7 +3326,7 @@ let Selectic = class Selectic extends Vue {
3313
3326
  multiple: ((_a = this.multiple) !== null && _a !== void 0 ? _a : false) !== false,
3314
3327
  pageSize: this.params.pageSize || 100,
3315
3328
  hideFilter: (_b = this.params.hideFilter) !== null && _b !== void 0 ? _b : 'auto',
3316
- allowRevert: this.params.allowRevert,
3329
+ allowRevert: this.params.allowRevert, /* it can be undefined */
3317
3330
  forceSelectAll: this.params.forceSelectAll || 'auto',
3318
3331
  allowClearSelection: this.params.allowClearSelection || false,
3319
3332
  autoSelect: this.params.autoSelect === undefined
@@ -3327,7 +3340,7 @@ let Selectic = class Selectic extends Vue {
3327
3340
  formatOption: this.params.formatOption,
3328
3341
  formatSelection: this.params.formatSelection,
3329
3342
  listPosition: this.params.listPosition || 'auto',
3330
- optionBehavior: this.params.optionBehavior,
3343
+ optionBehavior: this.params.optionBehavior, /* it can be undefined */
3331
3344
  isOpen: ((_c = this.open) !== null && _c !== void 0 ? _c : false) !== false,
3332
3345
  disableGroupSelection: this.params.disableGroupSelection,
3333
3346
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selectic",
3
- "version": "3.2.1",
3
+ "version": "3.3.1",
4
4
  "description": "Smart Select for VueJS 3.x",
5
5
  "main": "dist/selectic.common.js",
6
6
  "module": "dist/selectic.esm.js",
@@ -39,13 +39,13 @@
39
39
  "test": "npm run build && tape test/**/*.spec.js"
40
40
  },
41
41
  "dependencies": {
42
- "vtyx": "4.2.1"
42
+ "vtyx": "4.4.3"
43
43
  },
44
44
  "devDependencies": {
45
- "@babel/types": "^7.28.1",
46
- "rollup": "^2.79.2",
45
+ "@babel/types": "^7.29.0",
46
+ "rollup": "^2.80.0",
47
47
  "rollup-plugin-postcss": "^4.0.2",
48
48
  "tape": "^4.17.0",
49
- "typescript": "~4.8"
49
+ "typescript": "~5.9"
50
50
  }
51
51
  }
package/src/Store.tsx CHANGED
@@ -1988,14 +1988,37 @@ export default class SelecticStore {
1988
1988
  this.hasValue(value)
1989
1989
  );
1990
1990
 
1991
+ /* Whether at least one option, that is not disabled, is not selected. */
1992
+ const hasAvailableNonSelectedOptions = state.allOptions.some((opt) => {
1993
+ const isOptionSelected = (
1994
+ Array.isArray(selectedOptions)
1995
+ ? selectedOptions.some((selectedOpt) => selectedOpt.id === opt.id)
1996
+ : selectedOptions && selectedOptions.id === opt.id
1997
+ );
1998
+
1999
+ return !isOptionSelected && !opt.disabled;
2000
+ });
2001
+
1991
2002
  const isEmpty = nbEnabled === 0;
1992
- const hasOnlyOneOption = nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection;
2003
+ const hasOnlyOneOption = (
2004
+ nbEnabled === 1 && hasOnlyValidValue && !state.allowClearSelection
2005
+ );
2006
+
2007
+ /* In most cases if `hasOnlyOneOption` is true, we disable selection.
2008
+ * However, if we are not in multi-select mode and if the only option available is not the selected one then
2009
+ * we do not disable selection (to let the user switch from its current disabled selection,
2010
+ * and a valid one).
2011
+ */
2012
+ const cannotSelectAnyOption = hasOnlyOneOption && (
2013
+ Array.isArray(selectedOptions) || !hasAvailableNonSelectedOptions
2014
+ );
2015
+
1993
2016
  const isExclusiveDisabledItem = Array.isArray(selectedOptions) /* which means "multiple" mode */
1994
2017
  && selectedOptions.length === 1
1995
2018
  && selectedOptions[0].exclusive
1996
2019
  && selectedOptions[0].disabled;
1997
2020
 
1998
- if (hasOnlyOneOption || isEmpty || isExclusiveDisabledItem) {
2021
+ if (cannotSelectAnyOption || isEmpty || isExclusiveDisabledItem) {
1999
2022
  if (state.isOpen) {
2000
2023
  this.setAutomaticClose();
2001
2024
  this.commit('isOpen', false);
@@ -229,6 +229,62 @@ tape.test('change props', (subT) => {
229
229
  t.end();
230
230
  });
231
231
 
232
+ sTest.test('should not disable the select, if selected item is disabled but there is another option',
233
+ async (t) => {
234
+ const options = getOptions(2, 'alpha');
235
+ options[1].disabled = true;
236
+
237
+ const store = new Store({
238
+ value: 1,
239
+ options: options,
240
+ disabled: false,
241
+ params: {
242
+ autoDisabled: true,
243
+ },
244
+ });
245
+
246
+ store.commit('isOpen', true);
247
+ await _.nextVueTick(store);
248
+
249
+ t.is(store.state.selectedOptions.text, 'alpha1');
250
+ t.is(store.state.internalValue, 1);
251
+ t.is(store.state.disabled, false);
252
+
253
+ /* Once we choose the only valid option, disable the selection */
254
+ store.props.value = 0;
255
+ await _.nextVueTick(store);
256
+
257
+ t.is(store.state.selectedOptions.text, 'alpha0');
258
+ t.is(store.state.internalValue, 0);
259
+ t.is(store.state.disabled, true);
260
+
261
+ t.end();
262
+ });
263
+
264
+ sTest.test('multi select should not be disabled, even if only disabled options are left',
265
+ async (t) => {
266
+ const options = getOptions(3, 'alpha');
267
+ options[1].disabled = true;
268
+ options[2].disabled = true;
269
+
270
+ const store = new Store({
271
+ value: [0, 1],
272
+ options: options,
273
+ disabled: false,
274
+ params: {
275
+ autoDisabled: true,
276
+ multiple: true,
277
+ },
278
+ });
279
+
280
+ store.commit('isOpen', true);
281
+ await _.nextVueTick(store);
282
+
283
+ t.deepEqual(store.state.internalValue, [0, 1]);
284
+ t.is(store.state.disabled, false);
285
+ t.end();
286
+ });
287
+
232
288
  sTest.test('should not disable the select with an invalid value', async (t) => {
233
289
  const store = new Store({
234
290
  value: 2,
package/types/Store.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { ComputedRef } from 'vue';
2
- declare type MandateProps<T extends {}> = {
2
+ type MandateProps<T extends {}> = {
3
3
  [TK in keyof T]-?: T[TK];
4
4
  };
5
- export declare type StrictOptionId = string | number;
6
- export declare type OptionId = StrictOptionId | null;
7
- export declare type SelectedValue = OptionId | StrictOptionId[];
5
+ export type StrictOptionId = string | number;
6
+ export type OptionId = StrictOptionId | null;
7
+ export type SelectedValue = OptionId | StrictOptionId[];
8
8
  export interface OptionValue {
9
9
  id: OptionId;
10
10
  text: string;
@@ -18,19 +18,19 @@ export interface OptionValue {
18
18
  exclusive?: boolean;
19
19
  data?: any;
20
20
  }
21
- declare type OptionBehaviorOperation = 'sort' | 'force';
22
- declare type OptionBehaviorOrder = 'O' | 'D' | 'E';
21
+ type OptionBehaviorOperation = 'sort' | 'force';
22
+ type OptionBehaviorOrder = 'O' | 'D' | 'E';
23
23
  export interface OptionItem extends OptionValue {
24
24
  selected: boolean;
25
25
  disabled: boolean;
26
26
  isGroup: boolean;
27
27
  }
28
- export declare type OptionProp = OptionValue | string;
28
+ export type OptionProp = OptionValue | string;
29
29
  export interface GroupValue {
30
30
  id: StrictOptionId;
31
31
  text: string;
32
32
  }
33
- export declare type RequestResult = {
33
+ export type RequestResult = {
34
34
  /** The total number of expecting options.
35
35
  * Needed to know if there are more items to fetch, and to size the scrollbar.
36
36
  */
@@ -38,20 +38,20 @@ export declare type RequestResult = {
38
38
  /** The list of the options. */
39
39
  result: OptionValue[];
40
40
  };
41
- export declare type FetchCallback = (_search: string, _offsetItem: number, _pageSize: number) => Promise<RequestResult>;
42
- export declare type GetCallback = (_ids: OptionId[]) => Promise<OptionValue[]>;
43
- export declare type FormatCallback = (_option: OptionItem) => OptionItem;
44
- export declare type SelectionOverflow =
41
+ export type FetchCallback = (_search: string, _offsetItem: number, _pageSize: number) => Promise<RequestResult>;
42
+ export type GetCallback = (_ids: OptionId[]) => Promise<OptionValue[]>;
43
+ export type FormatCallback = (_option: OptionItem) => OptionItem;
44
+ export type SelectionOverflow =
45
45
  /** Items are reduced in width and an ellipsis is displayed in their name. */
46
46
  'collapsed' | 'multiline';
47
- export declare type ListPosition =
47
+ export type ListPosition =
48
48
  /** Display the list at bottom */
49
49
  'bottom'
50
50
  /** Display the list at bottom */
51
51
  | 'top'
52
52
  /** Display the list at bottom but if there is not enough space, display it at top */
53
53
  | 'auto';
54
- export declare type HideFilter =
54
+ export type HideFilter =
55
55
  /** Display or hide the filter panel */
56
56
  boolean
57
57
  /** The handler to open the filter panel is hidden only if there is less
@@ -59,7 +59,7 @@ boolean
59
59
  | 'auto'
60
60
  /** The panel filter is always open */
61
61
  | 'open';
62
- export declare type SelectAllOption =
62
+ export type SelectAllOption =
63
63
  /** Display the "select all" only when data are all fetched or allowRevert */
64
64
  'auto'
65
65
  /** Always display the "select all" in mulitple mode. */
@@ -151,7 +151,7 @@ export interface Props {
151
151
  /** Method to call to get specific item */
152
152
  getItemsCallback?: GetCallback | null;
153
153
  }
154
- declare type InternalProps = MandateProps<Props>;
154
+ type InternalProps = MandateProps<Props>;
155
155
  export interface Data {
156
156
  /** Number of items displayed in a page (before scrolling) */
157
157
  itemsPerPage: number;
@@ -253,11 +253,11 @@ export interface SelecticStoreState {
253
253
  automaticClose: boolean;
254
254
  };
255
255
  }
256
- export declare type IconFamily = '' | 'selectic' | 'font-awesome-4' | 'font-awesome-5' | 'font-awesome-6' | 'raw' | `prefix:${string}`;
257
- export declare type IconKey = 'caret-down' | 'caret-up' | 'check' | 'dot' | 'search' | 'spinner' | 'strikethrough' | 'times' | 'question' | 'spin';
258
- export declare type IconValue = `selectic:${IconKey}${'' | ':spin'}` | `raw:${string}` | `current:${IconKey}${'' | ':spin'}` | string;
259
- export declare type Icons = Record<IconKey, IconValue>;
260
- export declare type PartialIcons = {
256
+ export type IconFamily = '' | 'selectic' | 'font-awesome-4' | 'font-awesome-5' | 'font-awesome-6' | 'raw' | `prefix:${string}`;
257
+ export type IconKey = 'caret-down' | 'caret-up' | 'check' | 'dot' | 'search' | 'spinner' | 'strikethrough' | 'times' | 'question' | 'spin';
258
+ export type IconValue = `selectic:${IconKey}${'' | ':spin'}` | `raw:${string}` | `current:${IconKey}${'' | ':spin'}` | string;
259
+ export type Icons = Record<IconKey, IconValue>;
260
+ export type PartialIcons = {
261
261
  [K in IconKey]?: Icons[K];
262
262
  };
263
263
  interface Messages {
@@ -279,7 +279,7 @@ interface Messages {
279
279
  unknownPropertyValue: string;
280
280
  wrongQueryResult: string;
281
281
  }
282
- export declare type PartialMessages = {
282
+ export type PartialMessages = {
283
283
  [K in keyof Messages]?: Messages[K];
284
284
  };
285
285
  export declare function changeTexts(texts: PartialMessages): void;
package/types/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import { OptionProp, OptionId, StrictOptionId, GroupValue, SelectedValue, FetchC
4
4
  import MainInput from './MainInput';
5
5
  import ExtendedList from './ExtendedList';
6
6
  export { GroupValue, OptionValue, OptionItem, OptionProp, OptionId, StrictOptionId, SelectedValue, PartialMessages, GetCallback, FetchCallback, FormatCallback, SelectionOverflow, ListPosition, HideFilter, };
7
- declare type EventType = 'input' | 'change' | 'open' | 'close' | 'focus' | 'blur' | 'item:click';
7
+ type EventType = 'input' | 'change' | 'open' | 'close' | 'focus' | 'blur' | 'item:click';
8
8
  export interface EventOptions {
9
9
  instance: Selectic;
10
10
  eventType: EventType;
@@ -77,8 +77,8 @@ export interface ParamProps {
77
77
  /** Avoid click on group name to select all items in this group. */
78
78
  disableGroupSelection?: boolean;
79
79
  }
80
- export declare type OnCallback = (event: string, ...args: any[]) => void;
81
- export declare type GetMethodsCallback = (methods: {
80
+ export type OnCallback = (event: string, ...args: any[]) => void;
81
+ export type GetMethodsCallback = (methods: {
82
82
  clearCache: Selectic['clearCache'];
83
83
  changeTexts: Selectic['changeTexts'];
84
84
  changeIcons: Selectic['changeIcons'];