selectic 3.1.1 → 3.1.2

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.
@@ -134,12 +134,25 @@ function compareOptions(oldOptions, newOptions) {
134
134
  });
135
135
  });
136
136
  }
137
+ let displayLog = false;
138
+ function debug(fName, step, ...args) {
139
+ if (!displayLog) {
140
+ return;
141
+ }
142
+ console.log('--%s-- [%s]', fName, step, ...args);
143
+ }
144
+ /** Enable logs for debugging */
145
+ debug.enable = (display) => {
146
+ displayLog = display;
147
+ };
137
148
 
138
149
  /* File Purpose:
139
150
  * It keeps and computes all states at a single place.
140
151
  * Every inner components of Selectic should communicate with this file to
141
152
  * change or to get states.
142
153
  */
154
+ /* For debugging */
155
+ debug.enable(false);
143
156
  /* }}} */
144
157
  /* {{{ Static */
145
158
  function changeTexts$1(texts) {
@@ -174,12 +187,19 @@ let messages = {
174
187
  let defaultFamilyIcon = 'selectic';
175
188
  let icons = {};
176
189
  let closePreviousSelectic;
190
+ /**
191
+ * Time to wait before considering there is no other requests.
192
+ * This time is await only if there is already a requested request.
193
+ */
194
+ const DEBOUNCE_REQUEST = 250;
177
195
  /* }}} */
178
196
  let uid = 0;
179
197
  class SelecticStore {
180
198
  constructor(props = {}) {
181
199
  /* Do not need reactivity */
182
200
  this.requestId = 0;
201
+ this.requestSearchId = 0; /* Used for search request */
202
+ this.isRequesting = false;
183
203
  this._uid = ++uid;
184
204
  /* {{{ Props */
185
205
  const defaultProps = {
@@ -295,6 +315,7 @@ class SelecticStore {
295
315
  this.data.cacheItem.clear();
296
316
  this.setAutomaticClose();
297
317
  this.commit('isOpen', false);
318
+ this.clearDisplay();
298
319
  this.buildAllOptions(true);
299
320
  this.buildSelectedOptions();
300
321
  }, { deep: true });
@@ -385,6 +406,7 @@ class SelecticStore {
385
406
  /* {{{ public methods */
386
407
  commit(name, value) {
387
408
  const oldValue = this.state[name];
409
+ debug('commit', 'start', name, value, 'oldValue:', oldValue);
388
410
  if (oldValue === value) {
389
411
  return;
390
412
  }
@@ -393,8 +415,7 @@ class SelecticStore {
393
415
  case 'searchText':
394
416
  this.state.offsetItem = 0;
395
417
  this.state.activeItemIdx = -1;
396
- this.state.filteredOptions = [];
397
- this.state.totalFilteredOptions = Infinity;
418
+ this.clearDisplay();
398
419
  if (value) {
399
420
  this.buildFilteredOptions();
400
421
  }
@@ -442,6 +463,7 @@ class SelecticStore {
442
463
  }
443
464
  break;
444
465
  }
466
+ debug('commit', '(done)', name);
445
467
  }
446
468
  setAutomaticChange() {
447
469
  this.state.status.automaticChange = true;
@@ -627,14 +649,14 @@ class SelecticStore {
627
649
  this.state.status.errorMessage = '';
628
650
  }
629
651
  clearCache(forceReset = false) {
652
+ debug('clearCache', 'start', forceReset);
630
653
  const isPartial = unref(this.isPartial);
631
654
  const total = isPartial ? Infinity : 0;
632
655
  this.data.cacheItem.clear();
633
656
  this.state.allOptions = [];
634
657
  this.state.totalAllOptions = total;
635
658
  this.state.totalDynOptions = total;
636
- this.state.filteredOptions = [];
637
- this.state.totalFilteredOptions = Infinity;
659
+ this.clearDisplay();
638
660
  this.state.status.errorMessage = '';
639
661
  this.state.status.hasChanged = false;
640
662
  if (forceReset) {
@@ -766,6 +788,13 @@ class SelecticStore {
766
788
  this.checkAutoSelect();
767
789
  }
768
790
  }
791
+ /** Reset the display cache in order to rebuild it */
792
+ clearDisplay() {
793
+ debug('clearDisplay', 'start');
794
+ this.state.filteredOptions = [];
795
+ this.state.totalFilteredOptions = Infinity;
796
+ }
797
+ /** rebuild the state filteredOptions to normalize their values */
769
798
  updateFilteredOptions() {
770
799
  if (!this.data.doNotUpdate) {
771
800
  this.state.filteredOptions = this.buildItems(this.state.filteredOptions);
@@ -815,7 +844,7 @@ class SelecticStore {
815
844
  });
816
845
  return listOptions;
817
846
  }
818
- /* This method is for the computed property elementOptions */
847
+ /** This method is for the computed property elementOptions */
819
848
  getElementOptions() {
820
849
  const options = deepClone(this.props.childOptions, ['data']);
821
850
  const childOptions = [];
@@ -846,7 +875,9 @@ class SelecticStore {
846
875
  });
847
876
  return childOptions;
848
877
  }
878
+ /** Generate the list of all options by combining the 3 option lists */
849
879
  buildAllOptions(keepFetched = false, stopFetch = false) {
880
+ debug('buildAllOptions', 'start', 'keepFetched', keepFetched, 'stopFetch', stopFetch);
850
881
  const allOptions = [];
851
882
  let listOptions = [];
852
883
  let elementOptions = [];
@@ -918,8 +949,6 @@ class SelecticStore {
918
949
  }
919
950
  }
920
951
  if (!stopFetch) {
921
- this.state.filteredOptions = [];
922
- this.state.totalFilteredOptions = Infinity;
923
952
  this.buildFilteredOptions().then(() => {
924
953
  /* XXX: To recompute for strict mode and auto-select */
925
954
  this.assertCorrectValue();
@@ -935,18 +964,21 @@ class SelecticStore {
935
964
  const options = this.filterOptions(allOptions, search);
936
965
  this.setFilteredOptions(options);
937
966
  }
967
+ debug('buildAllOptions', 'end', 'allOptions:', this.state.allOptions.length, 'totalAllOptions:', this.state.totalAllOptions);
938
968
  }
939
969
  async buildFilteredOptions() {
940
- if (!this.state.isOpen) {
970
+ const state = this.state;
971
+ if (!state.isOpen) {
941
972
  /* Do not try to fetch anything while the select is not open */
942
973
  return;
943
974
  }
944
- const allOptions = this.state.allOptions;
945
- const search = this.state.searchText;
946
- const totalAllOptions = this.state.totalAllOptions;
975
+ const allOptions = state.allOptions;
976
+ const search = state.searchText;
977
+ const totalAllOptions = state.totalAllOptions;
947
978
  const allOptionsLength = allOptions.length;
948
- let filteredOptionsLength = this.state.filteredOptions.length;
979
+ let filteredOptionsLength = state.filteredOptions.length;
949
980
  const hasAllItems = unref(this.hasAllItems);
981
+ debug('buildFilteredOptions', 'start', 'hasAllItems:', hasAllItems, 'allOptions', allOptions.length, 'search:', search, 'filteredOptionsLength:', filteredOptionsLength);
950
982
  if (hasAllItems) {
951
983
  /* Everything has already been fetched and stored in filteredOptions */
952
984
  return;
@@ -963,16 +995,17 @@ class SelecticStore {
963
995
  return;
964
996
  }
965
997
  /* When we only have partial options */
966
- const offsetItem = this.state.offsetItem;
998
+ const offsetItem = state.offsetItem;
967
999
  const marginSize = unref(this.marginSize);
968
1000
  const endIndex = offsetItem + marginSize;
1001
+ debug('buildFilteredOptions', 'partial options', 'offsetItem:', offsetItem, 'marginSize:', marginSize, 'filteredOptionsLength', filteredOptionsLength);
969
1002
  if (endIndex <= filteredOptionsLength) {
970
1003
  return;
971
1004
  }
972
1005
  if (!search && endIndex <= allOptionsLength) {
973
- this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + this.state.groups.size);
1006
+ this.setFilteredOptions(this.buildGroupItems(allOptions), false, totalAllOptions + state.groups.size);
974
1007
  const isPartial = unref(this.isPartial);
975
- if (isPartial && this.state.totalDynOptions === Infinity) {
1008
+ if (isPartial && state.totalDynOptions === Infinity) {
976
1009
  this.fetchData();
977
1010
  }
978
1011
  return;
@@ -985,6 +1018,7 @@ class SelecticStore {
985
1018
  return;
986
1019
  }
987
1020
  }
1021
+ debug('buildFilteredOptions', 'end', '(will call fetchData)', this.state.filteredOptions.length);
988
1022
  await this.fetchData();
989
1023
  }
990
1024
  async buildSelectedOptions() {
@@ -1038,6 +1072,39 @@ class SelecticStore {
1038
1072
  state.selectedOptions = items[0];
1039
1073
  }
1040
1074
  }
1075
+ async fetchRequest(fetchCallback, search, offset, limit) {
1076
+ const searchRqId = ++this.requestSearchId;
1077
+ if (!search) {
1078
+ ++this.requestId;
1079
+ }
1080
+ const requestId = this.requestId;
1081
+ debug('fetchRequest', 'start', 'search:', search, 'offset:', offset, 'limit:', limit, 'requestId:', requestId, 'requestSearchId:', searchRqId, 'isRequesting:', this.isRequesting);
1082
+ if (this.isRequesting) {
1083
+ debug('fetchRequest', `await ${DEBOUNCE_REQUEST}ms`);
1084
+ /* debounce the call to avoid sending too much requests */
1085
+ await new Promise((resolve) => {
1086
+ setTimeout(resolve, DEBOUNCE_REQUEST);
1087
+ });
1088
+ /* Check if there are other requested requests, in such case drop this one */
1089
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1090
+ debug('fetchRequest', '××deprecated××', requestId, searchRqId);
1091
+ return false;
1092
+ }
1093
+ }
1094
+ this.isRequesting = true;
1095
+ const response = await fetchCallback(search, offset, limit);
1096
+ /* Check if request is obsolete */
1097
+ if (requestId !== this.requestId || (search && searchRqId !== this.requestSearchId)) {
1098
+ debug('fetchRequest', '×××deprecated×××', requestId, searchRqId);
1099
+ return false;
1100
+ }
1101
+ this.isRequesting = false;
1102
+ const deprecated = searchRqId !== this.requestSearchId;
1103
+ debug('fetchRequest', 'end', response.result.length, response.total, deprecated);
1104
+ return Object.assign(Object.assign({}, response), {
1105
+ /* this is to fulfill the cache */
1106
+ deprecated: deprecated });
1107
+ }
1041
1108
  async fetchData() {
1042
1109
  const state = this.state;
1043
1110
  const labels = this.data.labels;
@@ -1059,9 +1126,14 @@ class SelecticStore {
1059
1126
  const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) - dynOffset;
1060
1127
  const nbItems = endIndex - offset;
1061
1128
  const limit = Math.ceil(nbItems / pageSize) * pageSize;
1129
+ debug('fetchData', 'start', 'search:', search, 'offset:', offset, 'limit:', limit);
1062
1130
  try {
1063
- const requestId = ++this.requestId;
1064
- const { total: rTotal, result } = await fetchCallback(search, offset, limit);
1131
+ const response = await this.fetchRequest(fetchCallback, search, offset, limit);
1132
+ if (!response) {
1133
+ debug('fetchData', '×× deprecated ××', search, offset, limit);
1134
+ return;
1135
+ }
1136
+ const { total: rTotal, result, deprecated } = response;
1065
1137
  let total = rTotal;
1066
1138
  let errorMessage = '';
1067
1139
  /* Assert result is correctly formatted */
@@ -1079,11 +1151,12 @@ class SelecticStore {
1079
1151
  if (!search) {
1080
1152
  /* update cache */
1081
1153
  state.totalDynOptions = total;
1082
- const old = state.dynOptions.splice(offset, limit, ...result);
1154
+ const old = state.dynOptions.splice(offset, result.length, ...result);
1083
1155
  if (compareOptions(old, result)) {
1084
1156
  /* Added options are the same as previous ones.
1085
1157
  * Stop fetching to avoid infinite loop
1086
1158
  */
1159
+ debug('fetchData', 'no new values');
1087
1160
  if (!unref(this.hasFetchedAllItems)) {
1088
1161
  /* Display error if all items are not fetch
1089
1162
  * We can have the case where old value and result
@@ -1091,14 +1164,21 @@ class SelecticStore {
1091
1164
  * total is 0 */
1092
1165
  errorMessage = labels.wrongQueryResult;
1093
1166
  }
1094
- setTimeout(() => this.buildAllOptions(true, true), 0);
1167
+ setTimeout(() => {
1168
+ debug('fetchData', 'before buildAllOptions (stopped)', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1169
+ this.buildAllOptions(true, true);
1170
+ }, 0);
1095
1171
  }
1096
1172
  else {
1097
- setTimeout(() => this.buildAllOptions(true), 0);
1173
+ setTimeout(() => {
1174
+ debug('fetchData', 'before buildAllOptions', 'offsetItem:', this.state.offsetItem, 'allOptions:', this.state.allOptions.length);
1175
+ this.buildAllOptions(true);
1176
+ }, 0);
1098
1177
  }
1099
1178
  }
1100
- /* Check request is not obsolete */
1101
- if (requestId !== this.requestId) {
1179
+ /* Check request (without search) is not obsolete */
1180
+ if (deprecated) {
1181
+ debug('fetchData', '××× deprecated ×××', search, offset, limit);
1102
1182
  return;
1103
1183
  }
1104
1184
  if (!search) {
@@ -1127,14 +1207,17 @@ class SelecticStore {
1127
1207
  }
1128
1208
  catch (e) {
1129
1209
  state.status.errorMessage = e.message;
1210
+ debug('fetchData', 'error', e.message);
1130
1211
  if (!search) {
1131
1212
  state.totalDynOptions = 0;
1132
1213
  this.buildAllOptions(true, true);
1133
1214
  }
1134
1215
  }
1135
1216
  this.state.status.searching = false;
1217
+ debug('fetchData', 'end');
1136
1218
  }
1137
1219
  filterOptions(options, search) {
1220
+ debug('filterOptions', 'start', 'options:', options.length, 'search:', search);
1138
1221
  if (!search) {
1139
1222
  return this.buildGroupItems(options);
1140
1223
  }
@@ -1145,6 +1228,7 @@ class SelecticStore {
1145
1228
  addStaticFilteredOptions(fromDynamic = false) {
1146
1229
  const search = this.state.searchText;
1147
1230
  let continueLoop = fromDynamic;
1231
+ debug('addStaticFilteredOptions', 'start', 'fromDynamic:', fromDynamic, 'optionBehaviorOperation:', this.state.optionBehaviorOperation);
1148
1232
  if (this.state.optionBehaviorOperation !== 'sort') {
1149
1233
  return;
1150
1234
  }
@@ -1170,6 +1254,7 @@ class SelecticStore {
1170
1254
  }
1171
1255
  this.setFilteredOptions(options, true);
1172
1256
  }
1257
+ debug('addStaticFilteredOptions', 'end');
1173
1258
  }
1174
1259
  buildSelectedItems(ids) {
1175
1260
  return this.buildItems(ids.map((id) => {
@@ -1211,6 +1296,7 @@ class SelecticStore {
1211
1296
  }
1212
1297
  buildGroupItems(options, previousItem) {
1213
1298
  let previousGroupId = previousItem && previousItem.group;
1299
+ debug('buildGroupItems', 'start', 'options:', options.length, 'previousGroupId:', previousGroupId);
1214
1300
  const list = this.buildItems(options).reduce((items, item) => {
1215
1301
  if (item.group !== previousGroupId) {
1216
1302
  const groupId = item.group;
@@ -1227,6 +1313,7 @@ class SelecticStore {
1227
1313
  items.push(item);
1228
1314
  return items;
1229
1315
  }, []);
1316
+ debug('buildGroupItems', 'end', list.length);
1230
1317
  return list;
1231
1318
  }
1232
1319
  buildOptionBehavior(optionBehavior, state) {
@@ -1344,6 +1431,7 @@ class SelecticStore {
1344
1431
  }
1345
1432
  /** assign new value to the filteredOptions and apply change depending on it */
1346
1433
  setFilteredOptions(options, add = false, length = 0) {
1434
+ debug('setFilteredOptions', 'start', 'options:', options.length, 'add', add, 'length', length);
1347
1435
  if (!add) {
1348
1436
  this.state.filteredOptions = options;
1349
1437
  this.state.totalFilteredOptions = length || options.length;
@@ -7,6 +7,14 @@ version which want to upgrade them to latest version.
7
7
 
8
8
  **This is not something you have to read to understand and to use Selectic.**
9
9
 
10
+ ## 3.0.x → 3.1.x
11
+
12
+ Selectic no more depends on Font-awesome. It embeds its own icons (from Material Design Icons).
13
+
14
+ It is still possible to use Font-awesome icons (or from any other libraries).
15
+
16
+ Read [the documentation section related to changing icons](./changeIcons.md) for more information on how to handle them.
17
+
10
18
  ## 1.3.x → 3.x
11
19
 
12
20
  ### Vue2 → Vue3
@@ -5,10 +5,10 @@
5
5
  There are some icons in selectic. But sometimes it is useful to change them (because you want to use yours or the same of your favorite theme).
6
6
 
7
7
  There are 3 ways to changes these icons:
8
- * Call the static `changeIcons()` method. It changes icons for all selectic components.
8
+ * Call the static `changeIcons(icons, iconFamily)` method. It changes icons for all selectic components.
9
9
  * Change the `icons` property. It changes icons only for the component.
10
10
  * Change the `iconFamily` property. It changes the icons origin only for the component.
11
- * Call the `changeIcons()` method on the component. It changes icons only for the component.
11
+ * Call the `changeIcons(icons, iconFamily)` method on the component. It changes icons only for the component.
12
12
 
13
13
  _Changes made locally take precedence over changes made globally_.
14
14
 
@@ -116,3 +116,9 @@ this.$refs.selectic.changeIcons({
116
116
  * <span class="fa fa-thumbs-o-up"></span>
117
117
  */
118
118
  ```
119
+
120
+ It is possible to change only the icon family:
121
+ ```javascript
122
+ // change icons for all selectic components to use FA-6 icons
123
+ Selectic.changeIcons(null, 'font-awesome-6');
124
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selectic",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "Smart Select for VueJS 3.x",
5
5
  "main": "dist/selectic.common.js",
6
6
  "module": "dist/selectic.esm.js",
@@ -13,13 +13,13 @@ import Icon from './Icon';
13
13
 
14
14
  export interface Props {
15
15
  store: Store;
16
- width: number;
16
+ width?: number;
17
17
 
18
18
  /* positions of the main element related to current window */
19
- elementTop: number;
20
- elementBottom: number;
21
- elementLeft: number;
22
- elementRight: number;
19
+ elementTop?: number;
20
+ elementBottom?: number;
21
+ elementLeft?: number;
22
+ elementRight?: number;
23
23
  }
24
24
 
25
25
  /* list estimation height
package/src/Icon.tsx CHANGED
@@ -30,7 +30,7 @@ export default class Icon extends Vue<Props> {
30
30
  private store: Store;
31
31
 
32
32
  @Prop()
33
- private icon: IconKey;
33
+ private icon: string;
34
34
 
35
35
  @Prop()
36
36
  private spin?: boolean;
@@ -42,7 +42,7 @@ export default class Icon extends Vue<Props> {
42
42
  /* {{{ computed */
43
43
 
44
44
  private get rawIconValue(): IconValue {
45
- const key = this.icon;
45
+ const key = this.icon as IconKey;
46
46
  const icon = this.store.data.icons[key];
47
47
 
48
48
  if (icon === undefined) {
package/src/List.tsx CHANGED
@@ -14,10 +14,6 @@ import Icon from './Icon';
14
14
 
15
15
  export interface Props {
16
16
  store: Store;
17
-
18
- options?: any[];
19
- nbItems?: number;
20
- multiple?: boolean;
21
17
  }
22
18
 
23
19
  @Component