selectic 3.0.0 → 3.0.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
@@ -4,7 +4,7 @@
4
4
  * change or to get states.
5
5
  */
6
6
 
7
- import { reactive, watch, computed, ComputedRef } from 'vue';
7
+ import { reactive, watch, unref, computed, ComputedRef } from 'vue';
8
8
 
9
9
  /* {{{ Types definitions */
10
10
 
@@ -72,6 +72,15 @@ export type ListPosition =
72
72
  /* Display the list at bottom but if there is not enough space, display it at top */
73
73
  | 'auto';
74
74
 
75
+ export type HideFilter =
76
+ /* Display or hide the filter panel */
77
+ boolean
78
+ /* The handler to open the filter panel is hidden only if there is less
79
+ * than 10 options */
80
+ | 'auto'
81
+ /* The panel filter is always open */
82
+ | 'open';
83
+
75
84
  export interface SelecticStoreStateParams {
76
85
  /* Equivalent of <select>'s "multiple" attribute */
77
86
  multiple?: boolean;
@@ -80,7 +89,7 @@ export interface SelecticStoreStateParams {
80
89
  placeholder?: string;
81
90
 
82
91
  /* Hide filter component when enabled */
83
- hideFilter?: boolean | 'auto';
92
+ hideFilter?: HideFilter;
84
93
 
85
94
  /* Allow to reverse selection.
86
95
  * If true, parent should support the selectionIsExcluded property.
@@ -203,6 +212,9 @@ export interface SelecticStoreState {
203
212
  /* If true, filters and controls are hidden */
204
213
  hideFilter: boolean;
205
214
 
215
+ /* If true, the filter panel is always open */
216
+ keepFilterOpen: boolean;
217
+
206
218
  /* Allow to reverse selection.
207
219
  * If true, parent should support the selectionIsExcluded property.
208
220
  * If false, the action is never available.
@@ -294,6 +306,12 @@ export interface SelecticStoreState {
294
306
 
295
307
  /* If true, a change has been done by user */
296
308
  hasChanged: boolean;
309
+
310
+ /* If true, it means the current change has been done automatically by Selectic */
311
+ automaticChange: boolean;
312
+
313
+ /* If true, it means the current close has been done automatically by Selectic */
314
+ automaticClose: boolean;
297
315
  };
298
316
  }
299
317
 
@@ -398,6 +416,7 @@ export default class SelecticStore {
398
416
  disabled: false,
399
417
  placeholder: '',
400
418
  hideFilter: false,
419
+ keepFilterOpen: false,
401
420
  allowRevert: undefined,
402
421
  allowClearSelection: false,
403
422
  autoSelect: true,
@@ -430,6 +449,8 @@ export default class SelecticStore {
430
449
  errorMessage: '',
431
450
  areAllSelected: false,
432
451
  hasChanged: false,
452
+ automaticChange: false,
453
+ automaticClose: false,
433
454
  },
434
455
  });
435
456
  public data: Data;
@@ -448,6 +469,8 @@ export default class SelecticStore {
448
469
  public isPartial: ComputedRef<boolean>;
449
470
  public hasAllItems: ComputedRef<boolean>;
450
471
  public hasFetchedAllItems: ComputedRef<boolean>;
472
+ private listOptions: ComputedRef<OptionValue[]>;
473
+ private elementOptions: ComputedRef<OptionValue[]>;
451
474
 
452
475
  /* }}} */
453
476
 
@@ -515,7 +538,7 @@ export default class SelecticStore {
515
538
  });
516
539
 
517
540
  this.hasFetchedAllItems = computed(() => {
518
- const isPartial = this.isPartial.value ?? this.isPartial;
541
+ const isPartial = unref(this.isPartial);
519
542
 
520
543
  if (!isPartial) {
521
544
  return true;
@@ -525,16 +548,31 @@ export default class SelecticStore {
525
548
  return state.dynOptions.length === state.totalDynOptions;
526
549
  });
527
550
 
551
+ this.listOptions = computed(() => {
552
+ return this.getListOptions();
553
+ });
554
+
555
+ this.elementOptions = computed(() => {
556
+ return this.getElementOptions();
557
+ });
558
+
528
559
  /* }}} */
529
560
  /* {{{ watch */
530
561
 
531
562
  watch(() => [this.props.options, this.props.childOptions], () => {
532
563
  this.data.cacheItem.clear();
564
+ this.setAutomaticClose();
533
565
  this.commit('isOpen', false);
534
566
  this.buildAllOptions(true);
535
567
  this.buildSelectedOptions();
536
568
  });
537
569
 
570
+ watch(() => [this.listOptions, this.elementOptions], () => {
571
+ /* TODO: transform allOptions as a computed properties and this
572
+ * watcher become useless */
573
+ this.buildAllOptions(true);
574
+ });
575
+
538
576
  watch(() => this.props.value, () => {
539
577
  const value = this.props.value ?? null;
540
578
  this.commit('internalValue', value);
@@ -550,7 +588,7 @@ export default class SelecticStore {
550
588
 
551
589
  watch(() => this.state.filteredOptions, () => {
552
590
  let areAllSelected = false;
553
- const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
591
+ const hasAllItems = unref(this.hasAllItems);
554
592
 
555
593
  if (hasAllItems) {
556
594
  const selectionIsExcluded = +this.state.selectionIsExcluded;
@@ -578,6 +616,7 @@ export default class SelecticStore {
578
616
  /* }}} */
579
617
 
580
618
  this.closeSelectic = () => {
619
+ this.setAutomaticClose();
581
620
  this.commit('isOpen', false);
582
621
  }
583
622
 
@@ -599,11 +638,17 @@ export default class SelecticStore {
599
638
 
600
639
  if (stateParam.hideFilter === 'auto') {
601
640
  delete stateParam.hideFilter;
641
+ } else if (stateParam.hideFilter === 'open') {
642
+ this.state.keepFilterOpen = true;
643
+ delete stateParam.hideFilter;
602
644
  }
603
645
 
604
646
  /* Update state */
605
- assignObject(this.state, stateParam as SelecticStoreState, {
606
- internalValue: value,
647
+ assignObject(this.state, stateParam as SelecticStoreState);
648
+ /* XXX: should be done in 2 lines, in order to set the multiple state
649
+ * and ensure convertValue run with correct state */
650
+ assignObject(this.state, {
651
+ internalValue: this.convertTypeValue(value),
607
652
  selectionIsExcluded: props.selectionIsExcluded,
608
653
  disabled: props.disabled,
609
654
  });
@@ -686,12 +731,23 @@ export default class SelecticStore {
686
731
  break;
687
732
  case 'disabled':
688
733
  if (value) {
734
+ this.setAutomaticClose();
689
735
  this.commit('isOpen', false);
690
736
  }
691
737
  break;
692
738
  }
693
739
  }
694
740
 
741
+ public setAutomaticChange() {
742
+ this.state.status.automaticChange = true;
743
+ setTimeout(() => this.state.status.automaticChange = false, 0);
744
+ }
745
+
746
+ public setAutomaticClose() {
747
+ this.state.status.automaticClose = true;
748
+ setTimeout(() => this.state.status.automaticClose = false, 0);
749
+ }
750
+
695
751
  public getItem(id: OptionId): OptionValue {
696
752
  let item: OptionValue;
697
753
 
@@ -743,7 +799,7 @@ export default class SelecticStore {
743
799
  public selectItem(id: OptionId, selected?: boolean, keepOpen = false) {
744
800
  const state = this.state;
745
801
  let hasChanged = false;
746
- const isPartial = this.isPartial.value ?? this.isPartial;
802
+ const isPartial = unref(this.isPartial);
747
803
 
748
804
  /* Check that item is not disabled */
749
805
  if (!isPartial) {
@@ -805,6 +861,10 @@ export default class SelecticStore {
805
861
  return;
806
862
  }
807
863
 
864
+ if (keepOpen) {
865
+ /* if keepOpen is true it means that it is an automatic change */
866
+ this.setAutomaticChange();
867
+ }
808
868
  this.commit('internalValue', id);
809
869
  hasChanged = true;
810
870
  }
@@ -818,7 +878,7 @@ export default class SelecticStore {
818
878
  if (!this.state.multiple) {
819
879
  return;
820
880
  }
821
- const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
881
+ const hasAllItems = unref(this.hasAllItems);
822
882
 
823
883
  if (!hasAllItems) {
824
884
  const labels = this.data.labels;
@@ -859,7 +919,7 @@ export default class SelecticStore {
859
919
  }
860
920
 
861
921
  public clearCache(forceReset = false) {
862
- const isPartial = this.isPartial.value ?? this.isPartial;
922
+ const isPartial = unref(this.isPartial);
863
923
  const total = isPartial ? Infinity : 0;
864
924
 
865
925
  this.data.cacheItem.clear();
@@ -917,41 +977,51 @@ export default class SelecticStore {
917
977
 
918
978
  return this.state.filteredOptions.find(findId) ||
919
979
  this.state.dynOptions.find(findId) ||
920
- this.getListOptions().find(findId) ||
921
- this.getElementOptions().find(findId);
980
+ unref(this.listOptions).find(findId) ||
981
+ unref(this.elementOptions).find(findId);
922
982
  }
923
983
 
924
- private assertValueType() {
984
+ private convertTypeValue(oldValue: OptionId | StrictOptionId[]) {
925
985
  const state = this.state;
926
- const internalValue = state.internalValue;
927
986
  const isMultiple = state.multiple;
928
- let newValue = internalValue;
987
+ let newValue = oldValue;
929
988
 
930
989
  if (isMultiple) {
931
- if (!Array.isArray(internalValue)) {
932
- newValue = internalValue === null ? [] : [internalValue];
990
+ if (!Array.isArray(oldValue)) {
991
+ newValue = oldValue === null ? [] : [oldValue];
933
992
  }
934
993
  } else {
935
- if (Array.isArray(internalValue)) {
936
- const value = internalValue[0];
994
+ if (Array.isArray(oldValue)) {
995
+ const value = oldValue[0];
937
996
  newValue = typeof value === 'undefined' ? null : value;
938
997
  }
939
998
  }
940
- state.internalValue = newValue;
999
+ return newValue;
1000
+ }
1001
+
1002
+ private assertValueType() {
1003
+ const state = this.state;
1004
+ const internalValue = state.internalValue;
1005
+ const newValue = this.convertTypeValue(internalValue);
1006
+
1007
+ if (newValue !== internalValue) {
1008
+ this.setAutomaticChange();
1009
+ state.internalValue = newValue;
1010
+ }
941
1011
  }
942
1012
 
943
1013
  private assertCorrectValue(applyStrict = false) {
944
1014
  const state = this.state;
1015
+ this.assertValueType();
945
1016
  const internalValue = state.internalValue;
946
1017
  const selectionIsExcluded = state.selectionIsExcluded;
947
1018
  const isMultiple = state.multiple;
948
1019
  const checkStrict = state.strictValue;
949
1020
  let newValue = internalValue;
950
- const isPartial = this.isPartial.value ?? this.isPartial;
1021
+ const isPartial = unref(this.isPartial);
951
1022
 
952
- this.assertValueType();
953
1023
  if (isMultiple) {
954
- const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
1024
+ const hasFetchedAllItems = unref(this.hasFetchedAllItems);
955
1025
 
956
1026
  if (selectionIsExcluded && hasFetchedAllItems) {
957
1027
  newValue = state.allOptions.reduce((values, option) => {
@@ -984,7 +1054,7 @@ export default class SelecticStore {
984
1054
  return;
985
1055
  }
986
1056
  } else
987
- if (!this.hasItemInStore(newValue as OptionId)) {
1057
+ if (newValue !== null && !this.hasItemInStore(newValue as OptionId)) {
988
1058
  filteredValue = null;
989
1059
  isDifferent = true;
990
1060
 
@@ -996,6 +1066,7 @@ export default class SelecticStore {
996
1066
  }
997
1067
 
998
1068
  if (isDifferent) {
1069
+ this.setAutomaticChange();
999
1070
  newValue = filteredValue!;
1000
1071
  }
1001
1072
  }
@@ -1020,7 +1091,7 @@ export default class SelecticStore {
1020
1091
  });
1021
1092
  }
1022
1093
 
1023
- /* XXX: This is not a computed property to avoid consuming more memory */
1094
+ /* This method is for the computed property listOptions */
1024
1095
  private getListOptions(): OptionValue[] {
1025
1096
  const options = this.props.options;
1026
1097
  const listOptions: OptionValue[] = [];
@@ -1028,6 +1099,7 @@ export default class SelecticStore {
1028
1099
  if (!Array.isArray(options)) {
1029
1100
  return listOptions;
1030
1101
  }
1102
+ const state = this.state;
1031
1103
 
1032
1104
  options.forEach((option) => {
1033
1105
  /* manage simple string */
@@ -1043,14 +1115,14 @@ export default class SelecticStore {
1043
1115
  const subOptions = option.options;
1044
1116
 
1045
1117
  /* check for groups */
1046
- if (group && !this.state.groups.has(group)) {
1047
- this.state.groups.set(group, String(group));
1118
+ if (group && !state.groups.has(group)) {
1119
+ state.groups.set(group, String(group));
1048
1120
  }
1049
1121
 
1050
1122
  /* check for sub options */
1051
1123
  if (subOptions) {
1052
1124
  const groupId = option.id as StrictOptionId;
1053
- this.state.groups.set(groupId, option.text);
1125
+ state.groups.set(groupId, option.text);
1054
1126
 
1055
1127
  subOptions.forEach((subOpt) => {
1056
1128
  subOpt.group = groupId;
@@ -1065,7 +1137,7 @@ export default class SelecticStore {
1065
1137
  return listOptions;
1066
1138
  }
1067
1139
 
1068
- /* XXX: This is not a computed property to avoid consuming more memory */
1140
+ /* This method is for the computed property elementOptions */
1069
1141
  private getElementOptions(): OptionValue[] {
1070
1142
  const options = this.props.childOptions;
1071
1143
  const childOptions: OptionValue[] = [];
@@ -1073,20 +1145,21 @@ export default class SelecticStore {
1073
1145
  if (!Array.isArray(options) || options.length === 0) {
1074
1146
  return childOptions;
1075
1147
  }
1148
+ const state = this.state;
1076
1149
 
1077
1150
  options.forEach((option) => {
1078
1151
  const group = option.group;
1079
1152
  const subOptions = option.options;
1080
1153
 
1081
1154
  /* check for groups */
1082
- if (group && !this.state.groups.has(group)) {
1083
- this.state.groups.set(group, String(group));
1155
+ if (group && !state.groups.has(group)) {
1156
+ state.groups.set(group, String(group));
1084
1157
  }
1085
1158
 
1086
1159
  /* check for sub options */
1087
1160
  if (subOptions) {
1088
1161
  const groupId = option.id as StrictOptionId;
1089
- this.state.groups.set(groupId, option.text);
1162
+ state.groups.set(groupId, option.text);
1090
1163
 
1091
1164
  const sOpts: OptionValue[] = subOptions.map((subOpt) => {
1092
1165
  return Object.assign({}, subOpt, {
@@ -1109,7 +1182,7 @@ export default class SelecticStore {
1109
1182
  let elementOptions: OptionValue[] = [];
1110
1183
  const optionBehaviorOrder = this.state.optionBehaviorOrder;
1111
1184
  let length: number = Infinity;
1112
- const isPartial = this.isPartial.value ?? this.isPartial;
1185
+ const isPartial = unref(this.isPartial);
1113
1186
 
1114
1187
  const arrayFromOrder = (orderValue: OptionBehaviorOrder): OptionValue[] => {
1115
1188
  switch(orderValue) {
@@ -1138,8 +1211,8 @@ export default class SelecticStore {
1138
1211
  }
1139
1212
  }
1140
1213
 
1141
- listOptions = this.getListOptions();
1142
- elementOptions = this.getElementOptions();
1214
+ listOptions = unref(this.listOptions);
1215
+ elementOptions = unref(this.elementOptions);
1143
1216
 
1144
1217
  if (this.state.optionBehaviorOperation === 'force') {
1145
1218
  const orderValue = optionBehaviorOrder.find((value) => lengthFromOrder(value) > 0)!;
@@ -1200,14 +1273,14 @@ export default class SelecticStore {
1200
1273
  const totalAllOptions = this.state.totalAllOptions;
1201
1274
  const allOptionsLength = allOptions.length;
1202
1275
  let filteredOptionsLength = this.state.filteredOptions.length;
1203
- const hasAllItems = this.hasAllItems.value ?? this.hasAllItems;
1276
+ const hasAllItems = unref(this.hasAllItems);
1204
1277
 
1205
1278
  if (hasAllItems) {
1206
1279
  /* Everything has already been fetched and stored in filteredOptions */
1207
1280
  return;
1208
1281
  }
1209
1282
 
1210
- const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
1283
+ const hasFetchedAllItems = unref(this.hasFetchedAllItems);
1211
1284
  /* Check if all options have been fetched */
1212
1285
  if (hasFetchedAllItems) {
1213
1286
  if (!search) {
@@ -1225,7 +1298,7 @@ export default class SelecticStore {
1225
1298
  /* When we only have partial options */
1226
1299
 
1227
1300
  const offsetItem = this.state.offsetItem;
1228
- const marginSize = this.marginSize.value ?? this.marginSize;
1301
+ const marginSize = unref(this.marginSize);
1229
1302
  const endIndex = offsetItem + marginSize;
1230
1303
 
1231
1304
  if (endIndex <= filteredOptionsLength) {
@@ -1235,7 +1308,7 @@ export default class SelecticStore {
1235
1308
  if (!search && endIndex <= allOptionsLength) {
1236
1309
  this.state.filteredOptions = this.buildGroupItems(allOptions);
1237
1310
  this.state.totalFilteredOptions = totalAllOptions + this.state.groups.size;
1238
- const isPartial = this.isPartial.value ?? this.isPartial;
1311
+ const isPartial = unref(this.isPartial);
1239
1312
  if (isPartial && this.state.totalDynOptions === Infinity) {
1240
1313
  this.fetchData();
1241
1314
  }
@@ -1257,58 +1330,61 @@ export default class SelecticStore {
1257
1330
 
1258
1331
  private async buildSelectedOptions() {
1259
1332
  const internalValue = this.state.internalValue;
1333
+ const state = this.state;
1260
1334
 
1261
- if (this.state.multiple) {
1335
+ if (state.multiple) {
1262
1336
  /* display partial information about selected items */
1263
- this.state.selectedOptions = this.buildSelectedItems(internalValue as StrictOptionId[]);
1337
+ state.selectedOptions = this.buildSelectedItems(internalValue as StrictOptionId[]);
1264
1338
 
1265
1339
  const items: OptionItem[] = await this.getItems(internalValue as StrictOptionId[]).catch(() => []);
1266
- if (internalValue !== this.state.internalValue) {
1340
+ if (internalValue !== state.internalValue) {
1267
1341
  /* Values have been deprecated */
1268
1342
  return;
1269
1343
  }
1270
1344
 
1271
1345
  if (items.length !== (internalValue as StrictOptionId[]).length) {
1272
- if (!this.state.strictValue) {
1273
- const updatedItems = this.state.selectedOptions.map((option) => {
1346
+ if (!state.strictValue) {
1347
+ const updatedItems = state.selectedOptions.map((option) => {
1274
1348
  const foundItem = items.find((item) => item.id === option.id);
1275
1349
 
1276
1350
  return foundItem || option;
1277
1351
  });
1278
1352
 
1279
- this.state.selectedOptions = updatedItems;
1353
+ state.selectedOptions = updatedItems;
1280
1354
  } else {
1281
1355
  const itemIds = items.map((item) => item.id as StrictOptionId) ;
1282
1356
 
1357
+ this.setAutomaticChange();
1283
1358
  this.commit('internalValue', itemIds);
1284
1359
  }
1285
1360
  return;
1286
1361
  }
1287
1362
 
1288
1363
  /* display full information about selected items */
1289
- this.state.selectedOptions = items;
1364
+ state.selectedOptions = items;
1290
1365
  } else
1291
1366
  if (internalValue === null) {
1292
- this.state.selectedOptions = null;
1367
+ state.selectedOptions = null;
1293
1368
  } else {
1294
1369
  /* display partial information about selected items */
1295
- this.state.selectedOptions = this.buildSelectedItems([internalValue as OptionId])[0];
1370
+ state.selectedOptions = this.buildSelectedItems([internalValue as OptionId])[0];
1296
1371
 
1297
1372
  const items = await this.getItems([internalValue as OptionId]).catch(() => []);
1298
- if (internalValue !== this.state.internalValue) {
1373
+ if (internalValue !== state.internalValue) {
1299
1374
  /* Values have been deprecated */
1300
1375
  return;
1301
1376
  }
1302
1377
 
1303
1378
  if (!items.length) {
1304
- if (this.state.strictValue) {
1379
+ if (state.strictValue) {
1380
+ this.setAutomaticChange();
1305
1381
  this.commit('internalValue', null);
1306
1382
  }
1307
1383
  return;
1308
1384
  }
1309
1385
 
1310
1386
  /* display full information about selected items */
1311
- this.state.selectedOptions = items[0];
1387
+ state.selectedOptions = items[0];
1312
1388
  }
1313
1389
  }
1314
1390
 
@@ -1326,7 +1402,7 @@ export default class SelecticStore {
1326
1402
  const filteredOptionsLength = state.filteredOptions.length;
1327
1403
  const offsetItem = state.offsetItem;
1328
1404
  const pageSize = state.pageSize;
1329
- const marginSize = this.marginSize.value ?? this.marginSize;
1405
+ const marginSize = unref(this.marginSize);
1330
1406
  const endIndex = offsetItem + marginSize;
1331
1407
  const dynOffset = this.data.dynOffset;
1332
1408
 
@@ -1437,10 +1513,10 @@ export default class SelecticStore {
1437
1513
 
1438
1514
  switch (order) {
1439
1515
  case 'O':
1440
- options = this.filterOptions(this.getListOptions(), search);
1516
+ options = this.filterOptions(unref(this.listOptions), search);
1441
1517
  break;
1442
1518
  case 'E':
1443
- options = this.filterOptions(this.getElementOptions(), search);
1519
+ options = this.filterOptions(unref(this.elementOptions), search);
1444
1520
  break;
1445
1521
  }
1446
1522
  this.state.filteredOptions.push(...options);
@@ -1574,9 +1650,9 @@ export default class SelecticStore {
1574
1650
 
1575
1651
  private checkAutoDisabled() {
1576
1652
  const state = this.state;
1577
- const isPartial = this.isPartial.value ?? this.isPartial;
1653
+ const isPartial = unref(this.isPartial);
1578
1654
  const doNotCheck = isPartial || this.props.disabled || !state.autoDisabled;
1579
- const hasFetchedAllItems = this.hasFetchedAllItems.value ?? this.hasFetchedAllItems;
1655
+ const hasFetchedAllItems = unref(this.hasFetchedAllItems);
1580
1656
 
1581
1657
  if (doNotCheck || !hasFetchedAllItems) {
1582
1658
  return;
@@ -1594,7 +1670,10 @@ export default class SelecticStore {
1594
1670
  const hasOnlyOneOption = nb === 1 && hasValidValue && !state.allowClearSelection;
1595
1671
 
1596
1672
  if (hasOnlyOneOption || isEmpty) {
1597
- this.commit('isOpen', false);
1673
+ if (state.isOpen) {
1674
+ this.setAutomaticClose();
1675
+ this.commit('isOpen', false);
1676
+ }
1598
1677
  this.commit('disabled', true);
1599
1678
  } else {
1600
1679
  this.commit('disabled', false);
@@ -1608,7 +1687,7 @@ export default class SelecticStore {
1608
1687
  }
1609
1688
 
1610
1689
  const state = this.state;
1611
- const isPartial = this.isPartial.value ?? this.isPartial;
1690
+ const isPartial = unref(this.isPartial);
1612
1691
 
1613
1692
  if (state.multiple || isPartial) {
1614
1693
  state.hideFilter = false;