selectic 3.0.19 → 3.0.21
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 +5 -0
- package/README.md +42 -27
- package/dist/selectic.common.js +120 -15
- package/dist/selectic.esm.js +120 -15
- package/doc/changeText.md +10 -8
- package/doc/css.md +1 -1
- package/doc/domProperties.md +7 -7
- package/doc/dynamic.md +11 -11
- package/doc/extendedProperties.md +12 -12
- package/doc/images/example1.png +0 -0
- package/doc/images/example2.png +0 -0
- package/doc/images/selectic_example.png +0 -0
- package/doc/list.md +28 -15
- package/doc/methods.md +2 -2
- package/doc/params.md +56 -56
- package/package.json +1 -1
- package/src/MainInput.tsx +66 -7
- package/src/Store.tsx +42 -12
- package/src/tools.ts +31 -0
- package/test/Store/Store_creation.spec.js +1 -1
- package/test/Store/commit.spec.js +180 -0
- package/test/helper.js +10 -2
- package/types/MainInput.d.ts +7 -1
- package/types/Store.d.ts +1 -0
- package/types/tools.d.ts +5 -0
package/.tool-versions
ADDED
package/README.md
CHANGED
|
@@ -12,51 +12,66 @@ reverse selection, and many other possibilities.
|
|
|
12
12
|
|
|
13
13
|
It integrates well with VueJS and is reactive to option changes.
|
|
14
14
|
|
|
15
|
+
Typescript types are provided.
|
|
16
|
+
|
|
15
17
|
There are very few dependencies and code stays very small (~90kB).
|
|
16
18
|
|
|
19
|
+

|
|
20
|
+
|
|
17
21
|
## Example
|
|
18
22
|
|
|
19
23
|
```html
|
|
20
24
|
<Selectic
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
:options="['first choice', 'second choice', 'third choice']"
|
|
26
|
+
v-model="selection"
|
|
23
27
|
/>
|
|
24
28
|
|
|
25
29
|
<Selectic
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
multiple
|
|
31
|
+
value="item2"
|
|
32
|
+
:options="[{
|
|
33
|
+
id: 'item1',
|
|
34
|
+
text: 'The first item',
|
|
35
|
+
icon: 'fa fa-thumbs-o-up',
|
|
36
|
+
}, {
|
|
37
|
+
id: 'item2',
|
|
38
|
+
text: 'Another item',
|
|
39
|
+
title: 'second choice',
|
|
40
|
+
}, {
|
|
41
|
+
id: 'item3',
|
|
42
|
+
text: 'Disabled item',
|
|
43
|
+
disabled: true,
|
|
44
|
+
}]"
|
|
45
|
+
|
|
46
|
+
@input="onChange"
|
|
43
47
|
/>
|
|
44
48
|
|
|
45
|
-
<Selectic
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
<Selectic
|
|
50
|
+
:options="[{
|
|
51
|
+
id: 'animals',
|
|
52
|
+
text: 'Animals',
|
|
53
|
+
options: [{
|
|
54
|
+
id: 'cat',
|
|
55
|
+
text: 'Cat',
|
|
56
|
+
}, {
|
|
57
|
+
id: 'dog',
|
|
58
|
+
text: 'A dog',
|
|
59
|
+
}, {
|
|
60
|
+
id: 42,
|
|
61
|
+
text: '42 goldfishes',
|
|
62
|
+
}],
|
|
63
|
+
}]"
|
|
64
|
+
/>
|
|
52
65
|
```
|
|
53
66
|
|
|
67
|
+
[Full documentation](./doc/main.md)
|
|
68
|
+
|
|
54
69
|
## Features
|
|
55
70
|
|
|
56
71
|
* List of items (either string array or object array).
|
|
57
72
|
* Can load dynamically list from a server and the list can be paginate (with a
|
|
58
73
|
cache system to avoid reloading previous requests).
|
|
59
|
-
* Slots: options may be added from Vue template (by writing explicit `<option>` or `<optgroup>`) in a reactive way _(currently disabled in 3.0.0)_.
|
|
74
|
+
* ~~Slots: options may be added from Vue template (by writing explicit `<option>` or `<optgroup>`) in a reactive way~~ _(currently disabled in 3.0.0+)_.
|
|
60
75
|
* Multi-sources: Possibility to combine options from different sources (static, dynamic or slots) or to use the other as fallback (if the list is empty).
|
|
61
76
|
* Supports basic Select properties like `multiple`, `disabled`, `title`
|
|
62
77
|
* Supports group element (equivalent of optGroup), even for dynamic list.
|
package/dist/selectic.common.js
CHANGED
|
@@ -111,6 +111,33 @@ function assignObject(obj, ...sourceObjects) {
|
|
|
111
111
|
}
|
|
112
112
|
return result;
|
|
113
113
|
}
|
|
114
|
+
/** Compare 2 list of options.
|
|
115
|
+
* @returns true if there are no difference
|
|
116
|
+
*/
|
|
117
|
+
function compareOptions(oldOptions, newOptions) {
|
|
118
|
+
if (oldOptions.length !== newOptions.length) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return oldOptions.every((oldOption, idx) => {
|
|
122
|
+
const newOption = newOptions[idx];
|
|
123
|
+
const keys = Object.keys(oldOption);
|
|
124
|
+
if (keys.length !== Object.keys(newOption).length) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return keys.every((optionKey) => {
|
|
128
|
+
const key = optionKey;
|
|
129
|
+
const oldValue = oldOption[key];
|
|
130
|
+
const newValue = newOption[key];
|
|
131
|
+
if (key === 'options') {
|
|
132
|
+
return compareOptions(oldValue, newValue);
|
|
133
|
+
}
|
|
134
|
+
if (key === 'data') {
|
|
135
|
+
return JSON.stringify(oldValue) === JSON.stringify(newValue);
|
|
136
|
+
}
|
|
137
|
+
return oldValue === newValue;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
114
141
|
|
|
115
142
|
/* File Purpose:
|
|
116
143
|
* It keeps and computes all states at a single place.
|
|
@@ -140,6 +167,7 @@ let messages = {
|
|
|
140
167
|
moreSelectedItem: '+1 other',
|
|
141
168
|
moreSelectedItems: '+%d others',
|
|
142
169
|
unknownPropertyValue: 'property "%s" has incorrect values.',
|
|
170
|
+
wrongQueryResult: 'Query did not return all results.',
|
|
143
171
|
};
|
|
144
172
|
let closePreviousSelectic;
|
|
145
173
|
/* }}} */
|
|
@@ -767,7 +795,7 @@ class SelecticStore {
|
|
|
767
795
|
});
|
|
768
796
|
return childOptions;
|
|
769
797
|
}
|
|
770
|
-
buildAllOptions(keepFetched = false) {
|
|
798
|
+
buildAllOptions(keepFetched = false, stopFetch = false) {
|
|
771
799
|
const allOptions = [];
|
|
772
800
|
let listOptions = [];
|
|
773
801
|
let elementOptions = [];
|
|
@@ -838,12 +866,26 @@ class SelecticStore {
|
|
|
838
866
|
this.state.totalAllOptions = allOptions.length;
|
|
839
867
|
}
|
|
840
868
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
869
|
+
if (!stopFetch) {
|
|
870
|
+
this.state.filteredOptions = [];
|
|
871
|
+
this.state.totalFilteredOptions = Infinity;
|
|
872
|
+
this.buildFilteredOptions().then(() => {
|
|
873
|
+
/* XXX: To recompute for strict mode and auto-select */
|
|
874
|
+
this.assertCorrectValue();
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
else {
|
|
878
|
+
/* Do not fetch again just build filteredOptions */
|
|
879
|
+
const search = this.state.searchText;
|
|
880
|
+
if (!search) {
|
|
881
|
+
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
882
|
+
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const options = this.filterOptions(allOptions, search);
|
|
886
|
+
this.state.filteredOptions = options;
|
|
887
|
+
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
888
|
+
}
|
|
847
889
|
}
|
|
848
890
|
async buildFilteredOptions() {
|
|
849
891
|
if (!this.state.isOpen) {
|
|
@@ -975,6 +1017,7 @@ class SelecticStore {
|
|
|
975
1017
|
const requestId = ++this.requestId;
|
|
976
1018
|
const { total: rTotal, result } = await fetchCallback(search, offset, limit);
|
|
977
1019
|
let total = rTotal;
|
|
1020
|
+
let errorMessage = '';
|
|
978
1021
|
/* Assert result is correctly formatted */
|
|
979
1022
|
if (!Array.isArray(result)) {
|
|
980
1023
|
throw new Error(labels.wrongFormattedData);
|
|
@@ -990,8 +1033,23 @@ class SelecticStore {
|
|
|
990
1033
|
if (!search) {
|
|
991
1034
|
/* update cache */
|
|
992
1035
|
state.totalDynOptions = total;
|
|
993
|
-
state.dynOptions.splice(offset, limit, ...result);
|
|
994
|
-
|
|
1036
|
+
const old = state.dynOptions.splice(offset, limit, ...result);
|
|
1037
|
+
if (compareOptions(old, result)) {
|
|
1038
|
+
/* Added options are the same as previous ones.
|
|
1039
|
+
* Stop fetching to avoid infinite loop
|
|
1040
|
+
*/
|
|
1041
|
+
if (!this.hasFetchedAllItems) {
|
|
1042
|
+
/* Display error if all items are not fetch
|
|
1043
|
+
* We can have the case where old value and result
|
|
1044
|
+
* are the same but total is correct when the
|
|
1045
|
+
* total is 0 */
|
|
1046
|
+
errorMessage = labels.wrongQueryResult;
|
|
1047
|
+
}
|
|
1048
|
+
setTimeout(() => this.buildAllOptions(true, true), 0);
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1052
|
+
}
|
|
995
1053
|
}
|
|
996
1054
|
/* Check request is not obsolete */
|
|
997
1055
|
if (requestId !== this.requestId) {
|
|
@@ -1014,13 +1072,13 @@ class SelecticStore {
|
|
|
1014
1072
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1015
1073
|
this.addStaticFilteredOptions(true);
|
|
1016
1074
|
}
|
|
1017
|
-
state.status.errorMessage =
|
|
1075
|
+
state.status.errorMessage = errorMessage;
|
|
1018
1076
|
}
|
|
1019
1077
|
catch (e) {
|
|
1020
1078
|
state.status.errorMessage = e.message;
|
|
1021
1079
|
if (!search) {
|
|
1022
1080
|
state.totalDynOptions = 0;
|
|
1023
|
-
this.buildAllOptions(true);
|
|
1081
|
+
this.buildAllOptions(true, true);
|
|
1024
1082
|
}
|
|
1025
1083
|
}
|
|
1026
1084
|
this.state.status.searching = false;
|
|
@@ -1220,6 +1278,8 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1220
1278
|
/* }}} */
|
|
1221
1279
|
/* {{{ data */
|
|
1222
1280
|
this.nbHiddenItems = 0;
|
|
1281
|
+
/* reactivity non needed */
|
|
1282
|
+
this.domObserver = null;
|
|
1223
1283
|
}
|
|
1224
1284
|
/* }}} */
|
|
1225
1285
|
/* {{{ computed */
|
|
@@ -1265,8 +1325,18 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1265
1325
|
get singleSelectedItem() {
|
|
1266
1326
|
const state = this.store.state;
|
|
1267
1327
|
const isMultiple = state.multiple;
|
|
1268
|
-
|
|
1269
|
-
|
|
1328
|
+
if (isMultiple) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
return this.selectedOptions;
|
|
1332
|
+
}
|
|
1333
|
+
get singleSelectedItemText() {
|
|
1334
|
+
const item = this.singleSelectedItem;
|
|
1335
|
+
return (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1336
|
+
}
|
|
1337
|
+
get singleSelectedItemTitle() {
|
|
1338
|
+
const item = this.singleSelectedItem;
|
|
1339
|
+
return (item === null || item === void 0 ? void 0 : item.title) || (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1270
1340
|
}
|
|
1271
1341
|
get singleStyle() {
|
|
1272
1342
|
const selected = this.selectedOptions;
|
|
@@ -1364,6 +1434,11 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1364
1434
|
* currently shown */
|
|
1365
1435
|
const el = this.$refs.selectedItems;
|
|
1366
1436
|
const parentEl = el.parentElement;
|
|
1437
|
+
if (!document.contains(parentEl)) {
|
|
1438
|
+
/* The element is currently not in DOM */
|
|
1439
|
+
this.createObserver(parentEl);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1367
1442
|
const parentPadding = parseInt(getComputedStyle(parentEl).getPropertyValue('padding-right'), 10);
|
|
1368
1443
|
const clearEl = parentEl.querySelector('.selectic-input__clear-icon');
|
|
1369
1444
|
const clearWidth = clearEl ? clearEl.offsetWidth : 0;
|
|
@@ -1395,6 +1470,33 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1395
1470
|
idx--;
|
|
1396
1471
|
this.nbHiddenItems = selectedOptions.length - idx;
|
|
1397
1472
|
}
|
|
1473
|
+
closeObserver() {
|
|
1474
|
+
const observer = this.domObserver;
|
|
1475
|
+
if (observer) {
|
|
1476
|
+
observer.disconnect();
|
|
1477
|
+
}
|
|
1478
|
+
this.domObserver = null;
|
|
1479
|
+
}
|
|
1480
|
+
createObserver(el) {
|
|
1481
|
+
this.closeObserver();
|
|
1482
|
+
const observer = new MutationObserver((mutationsList) => {
|
|
1483
|
+
for (const mutation of mutationsList) {
|
|
1484
|
+
if (mutation.type === 'childList') {
|
|
1485
|
+
for (const elMutated of Array.from(mutation.addedNodes)) {
|
|
1486
|
+
/* Check that element has been added to DOM */
|
|
1487
|
+
if (elMutated.contains(el)) {
|
|
1488
|
+
this.closeObserver();
|
|
1489
|
+
this.computeSize();
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
const config = { childList: true, subtree: true };
|
|
1497
|
+
observer.observe(document, config);
|
|
1498
|
+
this.domObserver = observer;
|
|
1499
|
+
}
|
|
1398
1500
|
/* }}} */
|
|
1399
1501
|
/* {{{ watch */
|
|
1400
1502
|
onInternalChange() {
|
|
@@ -1405,6 +1507,9 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1405
1507
|
updated() {
|
|
1406
1508
|
this.computeSize();
|
|
1407
1509
|
}
|
|
1510
|
+
beforeUnmount() {
|
|
1511
|
+
this.closeObserver();
|
|
1512
|
+
}
|
|
1408
1513
|
/* }}} */
|
|
1409
1514
|
render() {
|
|
1410
1515
|
return (vtyx.h("div", { class: "selectic-container has-feedback", on: {
|
|
@@ -1415,14 +1520,14 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1415
1520
|
focused: this.store.state.isOpen,
|
|
1416
1521
|
disabled: this.store.state.disabled,
|
|
1417
1522
|
}] },
|
|
1418
|
-
this.hasValue && !this.store.state.multiple && (vtyx.h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.
|
|
1523
|
+
this.hasValue && !this.store.state.multiple && (vtyx.h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.singleSelectedItemTitle }, this.singleSelectedItemText)),
|
|
1419
1524
|
this.displayPlaceholder && (vtyx.h("span", { class: [
|
|
1420
1525
|
'selectic-input__selected-items__placeholder',
|
|
1421
1526
|
'selectic-item_text',
|
|
1422
1527
|
], title: this.store.state.placeholder }, this.store.state.placeholder)),
|
|
1423
1528
|
this.store.state.multiple && (vtyx.h("div", { class: "selectic-input__selected-items", ref: "selectedItems" },
|
|
1424
1529
|
this.isSelectionReversed && (vtyx.h("span", { class: "fa fa-strikethrough selectic-input__reverse-icon", title: this.reverseSelectionLabel })),
|
|
1425
|
-
this.showSelectedOptions.map((item) => (vtyx.h("div", { class: "single-value", style: item.style, title: item.text, on: {
|
|
1530
|
+
this.showSelectedOptions.map((item) => (vtyx.h("div", { class: "single-value", style: item.style, title: item.title || item.text, on: {
|
|
1426
1531
|
click: () => this.$emit('item:click', item.id),
|
|
1427
1532
|
} },
|
|
1428
1533
|
vtyx.h("span", { class: "selectic-input__selected-items__value" }, item.text),
|
package/dist/selectic.esm.js
CHANGED
|
@@ -107,6 +107,33 @@ function assignObject(obj, ...sourceObjects) {
|
|
|
107
107
|
}
|
|
108
108
|
return result;
|
|
109
109
|
}
|
|
110
|
+
/** Compare 2 list of options.
|
|
111
|
+
* @returns true if there are no difference
|
|
112
|
+
*/
|
|
113
|
+
function compareOptions(oldOptions, newOptions) {
|
|
114
|
+
if (oldOptions.length !== newOptions.length) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return oldOptions.every((oldOption, idx) => {
|
|
118
|
+
const newOption = newOptions[idx];
|
|
119
|
+
const keys = Object.keys(oldOption);
|
|
120
|
+
if (keys.length !== Object.keys(newOption).length) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return keys.every((optionKey) => {
|
|
124
|
+
const key = optionKey;
|
|
125
|
+
const oldValue = oldOption[key];
|
|
126
|
+
const newValue = newOption[key];
|
|
127
|
+
if (key === 'options') {
|
|
128
|
+
return compareOptions(oldValue, newValue);
|
|
129
|
+
}
|
|
130
|
+
if (key === 'data') {
|
|
131
|
+
return JSON.stringify(oldValue) === JSON.stringify(newValue);
|
|
132
|
+
}
|
|
133
|
+
return oldValue === newValue;
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
110
137
|
|
|
111
138
|
/* File Purpose:
|
|
112
139
|
* It keeps and computes all states at a single place.
|
|
@@ -136,6 +163,7 @@ let messages = {
|
|
|
136
163
|
moreSelectedItem: '+1 other',
|
|
137
164
|
moreSelectedItems: '+%d others',
|
|
138
165
|
unknownPropertyValue: 'property "%s" has incorrect values.',
|
|
166
|
+
wrongQueryResult: 'Query did not return all results.',
|
|
139
167
|
};
|
|
140
168
|
let closePreviousSelectic;
|
|
141
169
|
/* }}} */
|
|
@@ -763,7 +791,7 @@ class SelecticStore {
|
|
|
763
791
|
});
|
|
764
792
|
return childOptions;
|
|
765
793
|
}
|
|
766
|
-
buildAllOptions(keepFetched = false) {
|
|
794
|
+
buildAllOptions(keepFetched = false, stopFetch = false) {
|
|
767
795
|
const allOptions = [];
|
|
768
796
|
let listOptions = [];
|
|
769
797
|
let elementOptions = [];
|
|
@@ -834,12 +862,26 @@ class SelecticStore {
|
|
|
834
862
|
this.state.totalAllOptions = allOptions.length;
|
|
835
863
|
}
|
|
836
864
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
865
|
+
if (!stopFetch) {
|
|
866
|
+
this.state.filteredOptions = [];
|
|
867
|
+
this.state.totalFilteredOptions = Infinity;
|
|
868
|
+
this.buildFilteredOptions().then(() => {
|
|
869
|
+
/* XXX: To recompute for strict mode and auto-select */
|
|
870
|
+
this.assertCorrectValue();
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
/* Do not fetch again just build filteredOptions */
|
|
875
|
+
const search = this.state.searchText;
|
|
876
|
+
if (!search) {
|
|
877
|
+
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
878
|
+
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const options = this.filterOptions(allOptions, search);
|
|
882
|
+
this.state.filteredOptions = options;
|
|
883
|
+
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
884
|
+
}
|
|
843
885
|
}
|
|
844
886
|
async buildFilteredOptions() {
|
|
845
887
|
if (!this.state.isOpen) {
|
|
@@ -971,6 +1013,7 @@ class SelecticStore {
|
|
|
971
1013
|
const requestId = ++this.requestId;
|
|
972
1014
|
const { total: rTotal, result } = await fetchCallback(search, offset, limit);
|
|
973
1015
|
let total = rTotal;
|
|
1016
|
+
let errorMessage = '';
|
|
974
1017
|
/* Assert result is correctly formatted */
|
|
975
1018
|
if (!Array.isArray(result)) {
|
|
976
1019
|
throw new Error(labels.wrongFormattedData);
|
|
@@ -986,8 +1029,23 @@ class SelecticStore {
|
|
|
986
1029
|
if (!search) {
|
|
987
1030
|
/* update cache */
|
|
988
1031
|
state.totalDynOptions = total;
|
|
989
|
-
state.dynOptions.splice(offset, limit, ...result);
|
|
990
|
-
|
|
1032
|
+
const old = state.dynOptions.splice(offset, limit, ...result);
|
|
1033
|
+
if (compareOptions(old, result)) {
|
|
1034
|
+
/* Added options are the same as previous ones.
|
|
1035
|
+
* Stop fetching to avoid infinite loop
|
|
1036
|
+
*/
|
|
1037
|
+
if (!this.hasFetchedAllItems) {
|
|
1038
|
+
/* Display error if all items are not fetch
|
|
1039
|
+
* We can have the case where old value and result
|
|
1040
|
+
* are the same but total is correct when the
|
|
1041
|
+
* total is 0 */
|
|
1042
|
+
errorMessage = labels.wrongQueryResult;
|
|
1043
|
+
}
|
|
1044
|
+
setTimeout(() => this.buildAllOptions(true, true), 0);
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1048
|
+
}
|
|
991
1049
|
}
|
|
992
1050
|
/* Check request is not obsolete */
|
|
993
1051
|
if (requestId !== this.requestId) {
|
|
@@ -1010,13 +1068,13 @@ class SelecticStore {
|
|
|
1010
1068
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1011
1069
|
this.addStaticFilteredOptions(true);
|
|
1012
1070
|
}
|
|
1013
|
-
state.status.errorMessage =
|
|
1071
|
+
state.status.errorMessage = errorMessage;
|
|
1014
1072
|
}
|
|
1015
1073
|
catch (e) {
|
|
1016
1074
|
state.status.errorMessage = e.message;
|
|
1017
1075
|
if (!search) {
|
|
1018
1076
|
state.totalDynOptions = 0;
|
|
1019
|
-
this.buildAllOptions(true);
|
|
1077
|
+
this.buildAllOptions(true, true);
|
|
1020
1078
|
}
|
|
1021
1079
|
}
|
|
1022
1080
|
this.state.status.searching = false;
|
|
@@ -1216,6 +1274,8 @@ let MainInput = class MainInput extends Vue {
|
|
|
1216
1274
|
/* }}} */
|
|
1217
1275
|
/* {{{ data */
|
|
1218
1276
|
this.nbHiddenItems = 0;
|
|
1277
|
+
/* reactivity non needed */
|
|
1278
|
+
this.domObserver = null;
|
|
1219
1279
|
}
|
|
1220
1280
|
/* }}} */
|
|
1221
1281
|
/* {{{ computed */
|
|
@@ -1261,8 +1321,18 @@ let MainInput = class MainInput extends Vue {
|
|
|
1261
1321
|
get singleSelectedItem() {
|
|
1262
1322
|
const state = this.store.state;
|
|
1263
1323
|
const isMultiple = state.multiple;
|
|
1264
|
-
|
|
1265
|
-
|
|
1324
|
+
if (isMultiple) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
return this.selectedOptions;
|
|
1328
|
+
}
|
|
1329
|
+
get singleSelectedItemText() {
|
|
1330
|
+
const item = this.singleSelectedItem;
|
|
1331
|
+
return (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1332
|
+
}
|
|
1333
|
+
get singleSelectedItemTitle() {
|
|
1334
|
+
const item = this.singleSelectedItem;
|
|
1335
|
+
return (item === null || item === void 0 ? void 0 : item.title) || (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1266
1336
|
}
|
|
1267
1337
|
get singleStyle() {
|
|
1268
1338
|
const selected = this.selectedOptions;
|
|
@@ -1360,6 +1430,11 @@ let MainInput = class MainInput extends Vue {
|
|
|
1360
1430
|
* currently shown */
|
|
1361
1431
|
const el = this.$refs.selectedItems;
|
|
1362
1432
|
const parentEl = el.parentElement;
|
|
1433
|
+
if (!document.contains(parentEl)) {
|
|
1434
|
+
/* The element is currently not in DOM */
|
|
1435
|
+
this.createObserver(parentEl);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1363
1438
|
const parentPadding = parseInt(getComputedStyle(parentEl).getPropertyValue('padding-right'), 10);
|
|
1364
1439
|
const clearEl = parentEl.querySelector('.selectic-input__clear-icon');
|
|
1365
1440
|
const clearWidth = clearEl ? clearEl.offsetWidth : 0;
|
|
@@ -1391,6 +1466,33 @@ let MainInput = class MainInput extends Vue {
|
|
|
1391
1466
|
idx--;
|
|
1392
1467
|
this.nbHiddenItems = selectedOptions.length - idx;
|
|
1393
1468
|
}
|
|
1469
|
+
closeObserver() {
|
|
1470
|
+
const observer = this.domObserver;
|
|
1471
|
+
if (observer) {
|
|
1472
|
+
observer.disconnect();
|
|
1473
|
+
}
|
|
1474
|
+
this.domObserver = null;
|
|
1475
|
+
}
|
|
1476
|
+
createObserver(el) {
|
|
1477
|
+
this.closeObserver();
|
|
1478
|
+
const observer = new MutationObserver((mutationsList) => {
|
|
1479
|
+
for (const mutation of mutationsList) {
|
|
1480
|
+
if (mutation.type === 'childList') {
|
|
1481
|
+
for (const elMutated of Array.from(mutation.addedNodes)) {
|
|
1482
|
+
/* Check that element has been added to DOM */
|
|
1483
|
+
if (elMutated.contains(el)) {
|
|
1484
|
+
this.closeObserver();
|
|
1485
|
+
this.computeSize();
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
const config = { childList: true, subtree: true };
|
|
1493
|
+
observer.observe(document, config);
|
|
1494
|
+
this.domObserver = observer;
|
|
1495
|
+
}
|
|
1394
1496
|
/* }}} */
|
|
1395
1497
|
/* {{{ watch */
|
|
1396
1498
|
onInternalChange() {
|
|
@@ -1401,6 +1503,9 @@ let MainInput = class MainInput extends Vue {
|
|
|
1401
1503
|
updated() {
|
|
1402
1504
|
this.computeSize();
|
|
1403
1505
|
}
|
|
1506
|
+
beforeUnmount() {
|
|
1507
|
+
this.closeObserver();
|
|
1508
|
+
}
|
|
1404
1509
|
/* }}} */
|
|
1405
1510
|
render() {
|
|
1406
1511
|
return (h("div", { class: "selectic-container has-feedback", on: {
|
|
@@ -1411,14 +1516,14 @@ let MainInput = class MainInput extends Vue {
|
|
|
1411
1516
|
focused: this.store.state.isOpen,
|
|
1412
1517
|
disabled: this.store.state.disabled,
|
|
1413
1518
|
}] },
|
|
1414
|
-
this.hasValue && !this.store.state.multiple && (h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.
|
|
1519
|
+
this.hasValue && !this.store.state.multiple && (h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.singleSelectedItemTitle }, this.singleSelectedItemText)),
|
|
1415
1520
|
this.displayPlaceholder && (h("span", { class: [
|
|
1416
1521
|
'selectic-input__selected-items__placeholder',
|
|
1417
1522
|
'selectic-item_text',
|
|
1418
1523
|
], title: this.store.state.placeholder }, this.store.state.placeholder)),
|
|
1419
1524
|
this.store.state.multiple && (h("div", { class: "selectic-input__selected-items", ref: "selectedItems" },
|
|
1420
1525
|
this.isSelectionReversed && (h("span", { class: "fa fa-strikethrough selectic-input__reverse-icon", title: this.reverseSelectionLabel })),
|
|
1421
|
-
this.showSelectedOptions.map((item) => (h("div", { class: "single-value", style: item.style, title: item.text, on: {
|
|
1526
|
+
this.showSelectedOptions.map((item) => (h("div", { class: "single-value", style: item.style, title: item.title || item.text, on: {
|
|
1422
1527
|
click: () => this.$emit('item:click', item.id),
|
|
1423
1528
|
} },
|
|
1424
1529
|
h("span", { class: "selectic-input__selected-items__value" }, item.text),
|
package/doc/changeText.md
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
There are some texts in selectic. But sometimes it is useful to change them (because you want to translate them or to be more precise for the context usage).
|
|
6
6
|
|
|
7
7
|
There are 3 ways to changes these texts:
|
|
8
|
-
* Call the static `
|
|
8
|
+
* Call the static `changeTexts()` method. It changes texts for all selectic components.
|
|
9
9
|
* Change the `texts` property. It changes texts only for the component.
|
|
10
|
-
* Call the `
|
|
10
|
+
* Call the `changeTexts()` method on the component. It changes texts only for the component.
|
|
11
11
|
|
|
12
12
|
_Changes done locally are prioritary on changes done globally_.
|
|
13
13
|
|
|
14
|
-
Changing texts on the component with property or with `
|
|
14
|
+
Changing texts on the component with property or with `changeTexts()` are equivalent.
|
|
15
15
|
|
|
16
16
|
They accept the same argument: an object which contains keys of sentences.
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ It is possible to replace only some sentences.
|
|
|
19
19
|
|
|
20
20
|
## Keys
|
|
21
21
|
|
|
22
|
-
* **noFetchMethod**: This is an error message which is displayed if some options are missing and `
|
|
22
|
+
* **noFetchMethod**: This is an error message which is displayed if some options are missing and `fetchCallback` is not defined. _Default value is `'Fetch callback is missing: it is not possible to retrieve data.'`_.
|
|
23
23
|
|
|
24
24
|
* **searchPlaceholder**: This is the message in the input placeholder to search for options. _Default value is `'Search'`_.
|
|
25
25
|
|
|
@@ -47,6 +47,8 @@ It is possible to replace only some sentences.
|
|
|
47
47
|
|
|
48
48
|
* **moreSelectedItems**: This is a message displayed in a badge if there are more selected options than the size of the component. _Default value is `'+%d others'`_.
|
|
49
49
|
|
|
50
|
+
* **wrongQueryResult**: This is an error message displayed when result from the `fetchCallback` don't return all expected values. _Default value is `'Query did not return all results.'`_.
|
|
51
|
+
|
|
50
52
|
|
|
51
53
|
## Example
|
|
52
54
|
|
|
@@ -64,11 +66,11 @@ this.$refs.selectic.changeTexts({
|
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
```html
|
|
67
|
-
<
|
|
68
|
-
texts
|
|
69
|
+
<Selectic
|
|
70
|
+
:texts="{
|
|
69
71
|
searchPlaceholder: 'Search for specific options?',
|
|
70
72
|
searching: 'Loading information about this option',
|
|
71
|
-
noData: '
|
|
72
|
-
}
|
|
73
|
+
noData: 'ouch, I forgot to fill this select',
|
|
74
|
+
}"
|
|
73
75
|
/>
|
|
74
76
|
```
|
package/doc/css.md
CHANGED
|
@@ -74,7 +74,7 @@ body {
|
|
|
74
74
|
* **--selectic-active-item-bg** _(default: `#66afe9`)_: Background color of items where cursor is over or the active by the arrow keys.
|
|
75
75
|
|
|
76
76
|
* **--selectic-input-height** _(default: `30px`)_: The height of each items.<br>
|
|
77
|
-
|
|
77
|
+
**:warning: Currently this value is also hard-coded in javascript, so it can break the scroll height estimation if this value is changed.**
|
|
78
78
|
|
|
79
79
|
### Messages
|
|
80
80
|
|