selectic 3.0.19 → 3.0.20
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 +114 -15
- package/dist/selectic.esm.js +114 -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 +36 -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,17 @@ 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
|
+
errorMessage = labels.wrongQueryResult;
|
|
1042
|
+
setTimeout(() => this.buildAllOptions(true, true), 0);
|
|
1043
|
+
}
|
|
1044
|
+
else {
|
|
1045
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1046
|
+
}
|
|
995
1047
|
}
|
|
996
1048
|
/* Check request is not obsolete */
|
|
997
1049
|
if (requestId !== this.requestId) {
|
|
@@ -1014,13 +1066,13 @@ class SelecticStore {
|
|
|
1014
1066
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1015
1067
|
this.addStaticFilteredOptions(true);
|
|
1016
1068
|
}
|
|
1017
|
-
state.status.errorMessage =
|
|
1069
|
+
state.status.errorMessage = errorMessage;
|
|
1018
1070
|
}
|
|
1019
1071
|
catch (e) {
|
|
1020
1072
|
state.status.errorMessage = e.message;
|
|
1021
1073
|
if (!search) {
|
|
1022
1074
|
state.totalDynOptions = 0;
|
|
1023
|
-
this.buildAllOptions(true);
|
|
1075
|
+
this.buildAllOptions(true, true);
|
|
1024
1076
|
}
|
|
1025
1077
|
}
|
|
1026
1078
|
this.state.status.searching = false;
|
|
@@ -1220,6 +1272,8 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1220
1272
|
/* }}} */
|
|
1221
1273
|
/* {{{ data */
|
|
1222
1274
|
this.nbHiddenItems = 0;
|
|
1275
|
+
/* reactivity non needed */
|
|
1276
|
+
this.domObserver = null;
|
|
1223
1277
|
}
|
|
1224
1278
|
/* }}} */
|
|
1225
1279
|
/* {{{ computed */
|
|
@@ -1265,8 +1319,18 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1265
1319
|
get singleSelectedItem() {
|
|
1266
1320
|
const state = this.store.state;
|
|
1267
1321
|
const isMultiple = state.multiple;
|
|
1268
|
-
|
|
1269
|
-
|
|
1322
|
+
if (isMultiple) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
return this.selectedOptions;
|
|
1326
|
+
}
|
|
1327
|
+
get singleSelectedItemText() {
|
|
1328
|
+
const item = this.singleSelectedItem;
|
|
1329
|
+
return (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1330
|
+
}
|
|
1331
|
+
get singleSelectedItemTitle() {
|
|
1332
|
+
const item = this.singleSelectedItem;
|
|
1333
|
+
return (item === null || item === void 0 ? void 0 : item.title) || (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1270
1334
|
}
|
|
1271
1335
|
get singleStyle() {
|
|
1272
1336
|
const selected = this.selectedOptions;
|
|
@@ -1364,6 +1428,11 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1364
1428
|
* currently shown */
|
|
1365
1429
|
const el = this.$refs.selectedItems;
|
|
1366
1430
|
const parentEl = el.parentElement;
|
|
1431
|
+
if (!document.contains(parentEl)) {
|
|
1432
|
+
/* The element is currently not in DOM */
|
|
1433
|
+
this.createObserver(parentEl);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1367
1436
|
const parentPadding = parseInt(getComputedStyle(parentEl).getPropertyValue('padding-right'), 10);
|
|
1368
1437
|
const clearEl = parentEl.querySelector('.selectic-input__clear-icon');
|
|
1369
1438
|
const clearWidth = clearEl ? clearEl.offsetWidth : 0;
|
|
@@ -1395,6 +1464,33 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1395
1464
|
idx--;
|
|
1396
1465
|
this.nbHiddenItems = selectedOptions.length - idx;
|
|
1397
1466
|
}
|
|
1467
|
+
closeObserver() {
|
|
1468
|
+
const observer = this.domObserver;
|
|
1469
|
+
if (observer) {
|
|
1470
|
+
observer.disconnect();
|
|
1471
|
+
}
|
|
1472
|
+
this.domObserver = null;
|
|
1473
|
+
}
|
|
1474
|
+
createObserver(el) {
|
|
1475
|
+
this.closeObserver();
|
|
1476
|
+
const observer = new MutationObserver((mutationsList) => {
|
|
1477
|
+
for (const mutation of mutationsList) {
|
|
1478
|
+
if (mutation.type === 'childList') {
|
|
1479
|
+
for (const elMutated of Array.from(mutation.addedNodes)) {
|
|
1480
|
+
/* Check that element has been added to DOM */
|
|
1481
|
+
if (elMutated.contains(el)) {
|
|
1482
|
+
this.closeObserver();
|
|
1483
|
+
this.computeSize();
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
const config = { childList: true, subtree: true };
|
|
1491
|
+
observer.observe(document, config);
|
|
1492
|
+
this.domObserver = observer;
|
|
1493
|
+
}
|
|
1398
1494
|
/* }}} */
|
|
1399
1495
|
/* {{{ watch */
|
|
1400
1496
|
onInternalChange() {
|
|
@@ -1405,6 +1501,9 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1405
1501
|
updated() {
|
|
1406
1502
|
this.computeSize();
|
|
1407
1503
|
}
|
|
1504
|
+
beforeUnmount() {
|
|
1505
|
+
this.closeObserver();
|
|
1506
|
+
}
|
|
1408
1507
|
/* }}} */
|
|
1409
1508
|
render() {
|
|
1410
1509
|
return (vtyx.h("div", { class: "selectic-container has-feedback", on: {
|
|
@@ -1415,14 +1514,14 @@ let MainInput = class MainInput extends vtyx.Vue {
|
|
|
1415
1514
|
focused: this.store.state.isOpen,
|
|
1416
1515
|
disabled: this.store.state.disabled,
|
|
1417
1516
|
}] },
|
|
1418
|
-
this.hasValue && !this.store.state.multiple && (vtyx.h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.
|
|
1517
|
+
this.hasValue && !this.store.state.multiple && (vtyx.h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.singleSelectedItemTitle }, this.singleSelectedItemText)),
|
|
1419
1518
|
this.displayPlaceholder && (vtyx.h("span", { class: [
|
|
1420
1519
|
'selectic-input__selected-items__placeholder',
|
|
1421
1520
|
'selectic-item_text',
|
|
1422
1521
|
], title: this.store.state.placeholder }, this.store.state.placeholder)),
|
|
1423
1522
|
this.store.state.multiple && (vtyx.h("div", { class: "selectic-input__selected-items", ref: "selectedItems" },
|
|
1424
1523
|
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: {
|
|
1524
|
+
this.showSelectedOptions.map((item) => (vtyx.h("div", { class: "single-value", style: item.style, title: item.title || item.text, on: {
|
|
1426
1525
|
click: () => this.$emit('item:click', item.id),
|
|
1427
1526
|
} },
|
|
1428
1527
|
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,17 @@ 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
|
+
errorMessage = labels.wrongQueryResult;
|
|
1038
|
+
setTimeout(() => this.buildAllOptions(true, true), 0);
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1042
|
+
}
|
|
991
1043
|
}
|
|
992
1044
|
/* Check request is not obsolete */
|
|
993
1045
|
if (requestId !== this.requestId) {
|
|
@@ -1010,13 +1062,13 @@ class SelecticStore {
|
|
|
1010
1062
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1011
1063
|
this.addStaticFilteredOptions(true);
|
|
1012
1064
|
}
|
|
1013
|
-
state.status.errorMessage =
|
|
1065
|
+
state.status.errorMessage = errorMessage;
|
|
1014
1066
|
}
|
|
1015
1067
|
catch (e) {
|
|
1016
1068
|
state.status.errorMessage = e.message;
|
|
1017
1069
|
if (!search) {
|
|
1018
1070
|
state.totalDynOptions = 0;
|
|
1019
|
-
this.buildAllOptions(true);
|
|
1071
|
+
this.buildAllOptions(true, true);
|
|
1020
1072
|
}
|
|
1021
1073
|
}
|
|
1022
1074
|
this.state.status.searching = false;
|
|
@@ -1216,6 +1268,8 @@ let MainInput = class MainInput extends Vue {
|
|
|
1216
1268
|
/* }}} */
|
|
1217
1269
|
/* {{{ data */
|
|
1218
1270
|
this.nbHiddenItems = 0;
|
|
1271
|
+
/* reactivity non needed */
|
|
1272
|
+
this.domObserver = null;
|
|
1219
1273
|
}
|
|
1220
1274
|
/* }}} */
|
|
1221
1275
|
/* {{{ computed */
|
|
@@ -1261,8 +1315,18 @@ let MainInput = class MainInput extends Vue {
|
|
|
1261
1315
|
get singleSelectedItem() {
|
|
1262
1316
|
const state = this.store.state;
|
|
1263
1317
|
const isMultiple = state.multiple;
|
|
1264
|
-
|
|
1265
|
-
|
|
1318
|
+
if (isMultiple) {
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
return this.selectedOptions;
|
|
1322
|
+
}
|
|
1323
|
+
get singleSelectedItemText() {
|
|
1324
|
+
const item = this.singleSelectedItem;
|
|
1325
|
+
return (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1326
|
+
}
|
|
1327
|
+
get singleSelectedItemTitle() {
|
|
1328
|
+
const item = this.singleSelectedItem;
|
|
1329
|
+
return (item === null || item === void 0 ? void 0 : item.title) || (item === null || item === void 0 ? void 0 : item.text) || '';
|
|
1266
1330
|
}
|
|
1267
1331
|
get singleStyle() {
|
|
1268
1332
|
const selected = this.selectedOptions;
|
|
@@ -1360,6 +1424,11 @@ let MainInput = class MainInput extends Vue {
|
|
|
1360
1424
|
* currently shown */
|
|
1361
1425
|
const el = this.$refs.selectedItems;
|
|
1362
1426
|
const parentEl = el.parentElement;
|
|
1427
|
+
if (!document.contains(parentEl)) {
|
|
1428
|
+
/* The element is currently not in DOM */
|
|
1429
|
+
this.createObserver(parentEl);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1363
1432
|
const parentPadding = parseInt(getComputedStyle(parentEl).getPropertyValue('padding-right'), 10);
|
|
1364
1433
|
const clearEl = parentEl.querySelector('.selectic-input__clear-icon');
|
|
1365
1434
|
const clearWidth = clearEl ? clearEl.offsetWidth : 0;
|
|
@@ -1391,6 +1460,33 @@ let MainInput = class MainInput extends Vue {
|
|
|
1391
1460
|
idx--;
|
|
1392
1461
|
this.nbHiddenItems = selectedOptions.length - idx;
|
|
1393
1462
|
}
|
|
1463
|
+
closeObserver() {
|
|
1464
|
+
const observer = this.domObserver;
|
|
1465
|
+
if (observer) {
|
|
1466
|
+
observer.disconnect();
|
|
1467
|
+
}
|
|
1468
|
+
this.domObserver = null;
|
|
1469
|
+
}
|
|
1470
|
+
createObserver(el) {
|
|
1471
|
+
this.closeObserver();
|
|
1472
|
+
const observer = new MutationObserver((mutationsList) => {
|
|
1473
|
+
for (const mutation of mutationsList) {
|
|
1474
|
+
if (mutation.type === 'childList') {
|
|
1475
|
+
for (const elMutated of Array.from(mutation.addedNodes)) {
|
|
1476
|
+
/* Check that element has been added to DOM */
|
|
1477
|
+
if (elMutated.contains(el)) {
|
|
1478
|
+
this.closeObserver();
|
|
1479
|
+
this.computeSize();
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
const config = { childList: true, subtree: true };
|
|
1487
|
+
observer.observe(document, config);
|
|
1488
|
+
this.domObserver = observer;
|
|
1489
|
+
}
|
|
1394
1490
|
/* }}} */
|
|
1395
1491
|
/* {{{ watch */
|
|
1396
1492
|
onInternalChange() {
|
|
@@ -1401,6 +1497,9 @@ let MainInput = class MainInput extends Vue {
|
|
|
1401
1497
|
updated() {
|
|
1402
1498
|
this.computeSize();
|
|
1403
1499
|
}
|
|
1500
|
+
beforeUnmount() {
|
|
1501
|
+
this.closeObserver();
|
|
1502
|
+
}
|
|
1404
1503
|
/* }}} */
|
|
1405
1504
|
render() {
|
|
1406
1505
|
return (h("div", { class: "selectic-container has-feedback", on: {
|
|
@@ -1411,14 +1510,14 @@ let MainInput = class MainInput extends Vue {
|
|
|
1411
1510
|
focused: this.store.state.isOpen,
|
|
1412
1511
|
disabled: this.store.state.disabled,
|
|
1413
1512
|
}] },
|
|
1414
|
-
this.hasValue && !this.store.state.multiple && (h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.
|
|
1513
|
+
this.hasValue && !this.store.state.multiple && (h("span", { class: "selectic-item_text", style: this.singleStyle, title: this.singleSelectedItemTitle }, this.singleSelectedItemText)),
|
|
1415
1514
|
this.displayPlaceholder && (h("span", { class: [
|
|
1416
1515
|
'selectic-input__selected-items__placeholder',
|
|
1417
1516
|
'selectic-item_text',
|
|
1418
1517
|
], title: this.store.state.placeholder }, this.store.state.placeholder)),
|
|
1419
1518
|
this.store.state.multiple && (h("div", { class: "selectic-input__selected-items", ref: "selectedItems" },
|
|
1420
1519
|
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: {
|
|
1520
|
+
this.showSelectedOptions.map((item) => (h("div", { class: "single-value", style: item.style, title: item.title || item.text, on: {
|
|
1422
1521
|
click: () => this.$emit('item:click', item.id),
|
|
1423
1522
|
} },
|
|
1424
1523
|
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
|
|
package/doc/domProperties.md
CHANGED
|
@@ -10,7 +10,7 @@ It defines a unique identifier (ID) which must be unique in the whole document.
|
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
12
|
<selectic
|
|
13
|
-
options=
|
|
13
|
+
:options="['item1', 'item2']"
|
|
14
14
|
value="item2"
|
|
15
15
|
id="example"
|
|
16
16
|
/>
|
|
@@ -28,7 +28,7 @@ This is the id of the selected option or an array of id (if `multiple` is set).
|
|
|
28
28
|
|
|
29
29
|
```html
|
|
30
30
|
<selectic
|
|
31
|
-
options=
|
|
31
|
+
:options="['item1', 'item2']"
|
|
32
32
|
value="item2"
|
|
33
33
|
/>
|
|
34
34
|
```
|
|
@@ -39,7 +39,7 @@ When disabled is set, `selectic` cannot be open nor changed.
|
|
|
39
39
|
|
|
40
40
|
```html
|
|
41
41
|
<selectic
|
|
42
|
-
options=
|
|
42
|
+
:options="['item1', 'item2']"
|
|
43
43
|
disabled
|
|
44
44
|
/>
|
|
45
45
|
```
|
|
@@ -52,7 +52,7 @@ The `value` will be an array.
|
|
|
52
52
|
|
|
53
53
|
```html
|
|
54
54
|
<selectic
|
|
55
|
-
options=
|
|
55
|
+
:options="['item1', 'item2', 'item3']"
|
|
56
56
|
multiple
|
|
57
57
|
/>
|
|
58
58
|
```
|
|
@@ -65,7 +65,7 @@ It displays the given text if no option is selected.
|
|
|
65
65
|
|
|
66
66
|
```html
|
|
67
67
|
<selectic
|
|
68
|
-
options=
|
|
68
|
+
:options="['item1', 'item2', 'item3']"
|
|
69
69
|
placeholder="choose an item"
|
|
70
70
|
/>
|
|
71
71
|
```
|
|
@@ -76,7 +76,7 @@ It is added to the main element, and it behaves like `title` attribute of any HT
|
|
|
76
76
|
|
|
77
77
|
```html
|
|
78
78
|
<selectic
|
|
79
|
-
options=
|
|
79
|
+
:options="['item1', 'item2', 'item3']"
|
|
80
80
|
title="An information about this component"
|
|
81
81
|
/>
|
|
82
82
|
```
|
|
@@ -91,7 +91,7 @@ Note that it will be applied to the inner list element too.
|
|
|
91
91
|
|
|
92
92
|
```html
|
|
93
93
|
<selectic
|
|
94
|
-
options=
|
|
94
|
+
:options="['item1', 'item2']"
|
|
95
95
|
value="item2"
|
|
96
96
|
className="my-custom-class another-class"
|
|
97
97
|
/>
|