selectic 3.1.2 → 3.1.4

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/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
  });
@@ -26,7 +26,6 @@ export default class ExtendedList extends Vue<Props> {
26
26
  get searching(): boolean;
27
27
  get errorMessage(): string;
28
28
  get infoMessage(): string;
29
- get onKeyDown(): (evt: KeyboardEvent) => void;
30
29
  get bestPosition(): 'top' | 'bottom';
31
30
  get position(): 'top' | 'bottom';
32
31
  get horizontalStyle(): string;
@@ -39,6 +38,7 @@ export default class ExtendedList extends Vue<Props> {
39
38
  private getGroup;
40
39
  private computeListSize;
41
40
  private clickHeaderGroup;
41
+ private onKeyDown;
42
42
  mounted(): void;
43
43
  unmounted(): void;
44
44
  render(): h.JSX.Element;
@@ -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;