selectic 3.0.5 → 3.0.9

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.
@@ -35,13 +35,42 @@ function styleInject(css, ref) {
35
35
  var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n z-index: 2000;\n height: auto;\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n min-width: 200px;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n overflow: auto;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* }}} */\n";
36
36
  styleInject(css_248z);
37
37
 
38
- /* File Purpose:
39
- * It keeps and computes all states at a single place.
40
- * Every inner components of Selectic should communicate with this file to
41
- * change or to get states.
38
+ /**
39
+ * Clone the object and its inner properties.
40
+ * @param obj The object to be clone.
41
+ * @param refs internal reference to object to avoid cyclic references
42
+ * @returns a copy of obj
42
43
  */
43
- /* }}} */
44
- /* {{{ Helper */
44
+ function deepClone(obj, refs = new WeakMap()) {
45
+ /* For circular references */
46
+ if (refs.has(obj)) {
47
+ return refs.get(obj);
48
+ }
49
+ if (typeof obj === 'object') {
50
+ if (Array.isArray(obj)) {
51
+ const ref = [];
52
+ refs.set(obj, ref);
53
+ obj.forEach((val, idx) => {
54
+ ref[idx] = deepClone(val, refs);
55
+ });
56
+ return ref;
57
+ }
58
+ if (obj instanceof RegExp) {
59
+ const ref = new RegExp(obj.source, obj.flags);
60
+ refs.set(obj, ref);
61
+ return ref;
62
+ }
63
+ /* This should be an object */
64
+ const ref = {};
65
+ refs.set(obj, ref);
66
+ for (const [key, val] of Object.entries(obj)) {
67
+ ref[key] = deepClone(val, refs);
68
+ }
69
+ return ref;
70
+ }
71
+ /* This should be a primitive */
72
+ return obj;
73
+ }
45
74
  /**
46
75
  * Escape search string to consider regexp special characters as they
47
76
  * are and not like special characters.
@@ -72,6 +101,12 @@ function assignObject(obj, ...sourceObjects) {
72
101
  }
73
102
  return result;
74
103
  }
104
+
105
+ /* File Purpose:
106
+ * It keeps and computes all states at a single place.
107
+ * Every inner components of Selectic should communicate with this file to
108
+ * change or to get states.
109
+ */
75
110
  /* }}} */
76
111
  /* {{{ Static */
77
112
  function changeTexts$1(texts) {
@@ -101,6 +136,26 @@ let closePreviousSelectic;
101
136
  let uid = 0;
102
137
  class SelecticStore {
103
138
  constructor(props = {}) {
139
+ /* Do not need reactivity */
140
+ this.requestId = 0;
141
+ this._uid = ++uid;
142
+ /* {{{ Props */
143
+ const defaultProps = {
144
+ value: null,
145
+ selectionIsExcluded: false,
146
+ disabled: false,
147
+ options: null,
148
+ childOptions: [],
149
+ groups: [],
150
+ texts: null,
151
+ params: {},
152
+ fetchCallback: null,
153
+ getItemsCallback: null,
154
+ keepOpenWithOtherSelectic: false,
155
+ };
156
+ const propsVal = assignObject(defaultProps, props);
157
+ this.props = vue.reactive(propsVal);
158
+ /* }}} */
104
159
  /* {{{ data */
105
160
  this.state = vue.reactive({
106
161
  multiple: false,
@@ -141,27 +196,6 @@ class SelecticStore {
141
196
  automaticClose: false,
142
197
  },
143
198
  });
144
- /* Do not need reactivity */
145
- this.requestId = 0;
146
- this._uid = ++uid;
147
- /* {{{ Props */
148
- const defaultProps = {
149
- value: null,
150
- selectionIsExcluded: false,
151
- disabled: false,
152
- options: null,
153
- childOptions: [],
154
- groups: [],
155
- texts: null,
156
- params: {},
157
- fetchCallback: null,
158
- getItemsCallback: null,
159
- keepOpenWithOtherSelectic: false,
160
- };
161
- const propsVal = assignObject(defaultProps, props);
162
- this.props = vue.reactive(propsVal);
163
- /* }}} */
164
- /* {{{ data */
165
199
  this.data = vue.reactive({
166
200
  labels: Object.assign({}, messages),
167
201
  itemsPerPage: 10,
@@ -212,17 +246,17 @@ class SelecticStore {
212
246
  this.commit('isOpen', false);
213
247
  this.buildAllOptions(true);
214
248
  this.buildSelectedOptions();
215
- });
249
+ }, { deep: true });
216
250
  vue.watch(() => [this.listOptions, this.elementOptions], () => {
217
251
  /* TODO: transform allOptions as a computed properties and this
218
252
  * watcher become useless */
219
253
  this.buildAllOptions(true);
220
- });
254
+ }, { deep: true });
221
255
  vue.watch(() => this.props.value, () => {
222
256
  var _a;
223
257
  const value = (_a = this.props.value) !== null && _a !== void 0 ? _a : null;
224
258
  this.commit('internalValue', value);
225
- });
259
+ }, { deep: true });
226
260
  vue.watch(() => this.props.selectionIsExcluded, () => {
227
261
  this.commit('selectionIsExcluded', this.props.selectionIsExcluded);
228
262
  });
@@ -238,14 +272,14 @@ class SelecticStore {
238
272
  areAllSelected = this.state.filteredOptions.every((item) => !!(+item.selected ^ selectionIsExcluded));
239
273
  }
240
274
  this.state.status.areAllSelected = areAllSelected;
241
- });
275
+ }, { deep: true });
242
276
  vue.watch(() => this.state.internalValue, () => {
243
277
  this.buildSelectedOptions();
244
- });
278
+ }, { deep: true });
245
279
  vue.watch(() => this.state.allOptions, () => {
246
280
  this.checkAutoSelect();
247
281
  this.checkAutoDisabled();
248
- });
282
+ }, { deep: true });
249
283
  vue.watch(() => this.state.totalAllOptions, () => {
250
284
  this.checkHideFilter();
251
285
  });
@@ -275,8 +309,8 @@ class SelecticStore {
275
309
  * and ensure convertValue run with correct state */
276
310
  assignObject(this.state, {
277
311
  internalValue: this.convertTypeValue(value),
278
- selectionIsExcluded: props.selectionIsExcluded,
279
- disabled: props.disabled,
312
+ selectionIsExcluded: !!this.props.selectionIsExcluded,
313
+ disabled: !!this.props.disabled, /* XXX: !! is needed to copy value and not proxy reference */
280
314
  });
281
315
  this.checkHideFilter();
282
316
  if (this.props.texts) {
@@ -1394,7 +1428,7 @@ __decorate$4([
1394
1428
  vtyx.Prop({ default: '' })
1395
1429
  ], MainInput.prototype, "id", void 0);
1396
1430
  __decorate$4([
1397
- vtyx.Watch('store.state.internalValue')
1431
+ vtyx.Watch('store.state.internalValue', { deep: true })
1398
1432
  ], MainInput.prototype, "onInternalChange", null);
1399
1433
  MainInput = __decorate$4([
1400
1434
  vtyx.Component
@@ -1498,7 +1532,7 @@ let FilterPanel = class FilterPanel extends vtyx.Vue {
1498
1532
  document.addEventListener('keypress', this.onKeyPressed);
1499
1533
  this.getFocus();
1500
1534
  }
1501
- destroyed() {
1535
+ unmounted() {
1502
1536
  document.removeEventListener('keypress', this.onKeyPressed);
1503
1537
  }
1504
1538
  /* }}} */
@@ -1758,7 +1792,7 @@ __decorate$2([
1758
1792
  vtyx.Watch('store.state.offsetItem')
1759
1793
  ], List.prototype, "onOffsetChange", null);
1760
1794
  __decorate$2([
1761
- vtyx.Watch('filteredOptions')
1795
+ vtyx.Watch('filteredOptions', { deep: true })
1762
1796
  ], List.prototype, "onFilteredOptionsChange", null);
1763
1797
  __decorate$2([
1764
1798
  vtyx.Watch('groupId')
@@ -1929,7 +1963,7 @@ let ExtendedList = class ExtendedList extends vtyx.Vue {
1929
1963
  document.body.addEventListener('keydown', this.onKeyDown);
1930
1964
  this.computeListSize();
1931
1965
  }
1932
- destroyed() {
1966
+ unmounted() {
1933
1967
  document.body.removeEventListener('keydown', this.onKeyDown);
1934
1968
  /* force the element to be removed from DOM */
1935
1969
  if (this.$el.parentNode) {
@@ -1974,7 +2008,7 @@ __decorate$1([
1974
2008
  vtyx.Prop({ default: 300 })
1975
2009
  ], ExtendedList.prototype, "width", void 0);
1976
2010
  __decorate$1([
1977
- vtyx.Watch('store.state.filteredOptions')
2011
+ vtyx.Watch('store.state.filteredOptions', { deep: true })
1978
2012
  ], ExtendedList.prototype, "onFilteredOptionsChange", null);
1979
2013
  __decorate$1([
1980
2014
  vtyx.Watch('store.state.hideFilter')
@@ -2222,7 +2256,7 @@ let Selectic = class Selectic extends vtyx.Vue {
2222
2256
  this.store.props.selectionIsExcluded = this.selectionIsExcluded;
2223
2257
  }
2224
2258
  onOptionsChange() {
2225
- this.store.props.options = Array.from(this.options);
2259
+ this.store.props.options = deepClone(this.options);
2226
2260
  }
2227
2261
  onTextsChange() {
2228
2262
  const texts = this.texts;
@@ -2463,7 +2497,7 @@ let Selectic = class Selectic extends vtyx.Vue {
2463
2497
  // }
2464
2498
  // this.store.childOptions = options;
2465
2499
  }
2466
- beforeDestroy() {
2500
+ beforeUnmount() {
2467
2501
  this.removeListeners();
2468
2502
  }
2469
2503
  /* }}} */
@@ -2539,22 +2573,22 @@ __decorate([
2539
2573
  vtyx.Prop()
2540
2574
  ], Selectic.prototype, "_getMethods", void 0);
2541
2575
  __decorate([
2542
- vtyx.Watch('value')
2576
+ vtyx.Watch('value', { deep: true })
2543
2577
  ], Selectic.prototype, "onValueChange", null);
2544
2578
  __decorate([
2545
2579
  vtyx.Watch('selectionIsExcluded')
2546
2580
  ], Selectic.prototype, "onExcludedChange", null);
2547
2581
  __decorate([
2548
- vtyx.Watch('options')
2582
+ vtyx.Watch('options', { deep: true })
2549
2583
  ], Selectic.prototype, "onOptionsChange", null);
2550
2584
  __decorate([
2551
- vtyx.Watch('texts')
2585
+ vtyx.Watch('texts', { deep: true })
2552
2586
  ], Selectic.prototype, "onTextsChange", null);
2553
2587
  __decorate([
2554
2588
  vtyx.Watch('disabled')
2555
2589
  ], Selectic.prototype, "onDisabledChange", null);
2556
2590
  __decorate([
2557
- vtyx.Watch('groups')
2591
+ vtyx.Watch('groups', { deep: true })
2558
2592
  ], Selectic.prototype, "onGroupsChanged", null);
2559
2593
  __decorate([
2560
2594
  vtyx.Watch('placeholder')
@@ -2566,7 +2600,7 @@ __decorate([
2566
2600
  vtyx.Watch('isFocused')
2567
2601
  ], Selectic.prototype, "onFocusChanged", null);
2568
2602
  __decorate([
2569
- vtyx.Watch('store.state.internalValue')
2603
+ vtyx.Watch('store.state.internalValue', { deep: true })
2570
2604
  ], Selectic.prototype, "onInternalValueChange", null);
2571
2605
  __decorate([
2572
2606
  vtyx.Emit('input'),
@@ -31,13 +31,42 @@ function styleInject(css, ref) {
31
31
  var css_248z = "/* {{{ Variables */\n\n:root {\n --selectic-font-size: 14px;\n --selectic-cursor-disabled: not-allowed;\n\n /* The main element */\n --selectic-color: #555555;\n --selectic-bg: #ffffff;\n\n /* The main element (when disabled) */\n --selectic-color-disabled: #787878;\n --selectic-bg-disabled: #eeeeee;\n\n /* The list */\n --selectic-panel-bg: #f0f0f0;\n --selectic-separator-bordercolor: #cccccc;\n /* --selectic-item-color: var(--selectic-color); /* Can be set in any CSS configuration */\n\n /* The current selected item */\n --selectic-selected-item-color: #428bca;\n\n /* When mouse is over items or by selecting with key arrows */\n --selectic-active-item-color: #ffffff;\n --selectic-active-item-bg: #66afe9;\n\n /* Selected values in main element */\n --selectic-value-bg: #f0f0f0;\n /* --selectic-more-items-bg: var(--selectic-info-bg); /* can be set in any CSS configuration */\n /* --selectic-more-items-color: var(--selectic-info-color); /* can be set in any CSS configuration */\n --selectic-more-items-bg-disabled: #cccccc;\n\n /* Information message */\n --selectic-info-bg: #5bc0de;\n --selectic-info-color: #ffffff;\n\n /* Error message */\n --selectic-error-bg: #b72c29;\n --selectic-error-color: #ffffff;\n\n /* XXX: Currently it is important to keep this size for a correct scroll\n * height estimation */\n --selectic-input-height: 30px;\n}\n\n/* }}} */\n/* {{{ Bootstrap equivalent style */\n\n.selectic .form-control {\n display: block;\n width: 100%;\n height: calc(var(--selectic-input-height) - 2px);\n font-size: var(--selectic-font-size);\n line-height: 1.42857143;\n color: var(--selectic-color);\n background-color: var(--selectic-bg);\n background-image: none;\n border: 1px solid var(--selectic-separator-bordercolor); /* should use a better variable */\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;\n}\n.selectic .has-feedback {\n position: relative;\n}\n.selectic .has-feedback .form-control {\n padding-right: calc(var(--selectic-input-height) + 4px);\n}\n\n.selectic .form-control-feedback.fa,\n.selectic .form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: calc(var(--selectic-input-height) + 4px);\n height: calc(var(--selectic-input-height) + 4px);\n line-height: var(--selectic-input-height);\n text-align: center;\n pointer-events: none;\n}\n\n.selectic .alert-info {\n background-color: var(--selectic-info-bg);\n color: var(--selectic-info-color);\n}\n\n.selectic .alert-danger {\n background-color: var(--selectic-error-bg);\n color: var(--selectic-error-color);\n}\n\n/* }}} */\n\n.selectic * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.selectic.form-control {\n display: inline-block;\n padding: 0;\n cursor: pointer;\n border: unset;\n}\n\n.has-feedback .selectic__icon-container.form-control-feedback {\n right: 0;\n}\n\n/* The input which contains the selected value\n * XXX: This input should stay hidden behind other elements, but is \"visible\"\n * (in term of DOM point of view) in order to get and to trigger the `focus`\n * DOM event. */\n.selectic__input-value {\n position: fixed;\n opacity: 0;\n z-index: -1000;\n top: -100px;\n}\n\n/* XXX: .form-control has been added to this selector to improve priority and\n * override some rules of the original .form-control */\n.selectic-input.form-control {\n display: inline-flex;\n justify-content: space-between;\n overflow: hidden;\n width: 100%;\n min-height: var(--selectic-input-height);\n padding-top: 0;\n padding-bottom: 0;\n padding-left: 5px;\n line-height: calc(var(--selectic-input-height) - 4px);\n color: var(--selectic-color);\n}\n\n.selectic-input__reverse-icon {\n align-self: center;\n margin-right: 3px;\n cursor: default;\n}\n.selectic-input__clear-icon {\n align-self: center;\n margin-left: 3px;\n cursor: pointer;\n}\n.selectic-input__clear-icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic-input.focused {\n border-bottom-left-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n\n.selectic-input.disabled {\n cursor: var(--selectic-cursor-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n.selectic-input.disabled .more-items {\n\tbackground-color: var(--selectic-more-items-bg-disabled);\n}\n\n.selectic-input__selected-items {\n display: inline-flex;\n flex-wrap: nowrap;\n align-items: center;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__placeholder {\n font-style: italic;\n opacity: 0.7;\n white-space: nowrap;\n}\n\n.selectic-icon {\n color: var(--selectic-color);\n text-align: center;\n vertical-align: middle;\n}\n\n.selectic__extended-list {\n position: fixed;\n z-index: 2000;\n height: auto;\n background-color: var(--selectic-bg, #ffffff);\n box-shadow: 2px 5px 12px 0px #888888;\n border-radius: 0 0 4px 4px;\n padding: 0;\n min-width: 200px;\n}\n.selectic__extended-list__list-items {\n max-height: calc(var(--selectic-input-height) * 10);\n overflow: auto;\n padding-left: 0;\n}\n\n.selectic-item {\n display: block;\n position: relative;\n box-sizing: border-box;\n padding: 2px 8px;\n color: var(--selectic-item-color, var(--selectic-color));\n min-height: calc(var(--selectic-input-height) - 3px);\n list-style-type: none;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item:not(.selected) .selectic-item_icon {\n opacity: 0;\n}\n\n.selectic-item_text {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.selectic-item__active {\n background-color: var(--selectic-active-item-bg);\n color: var(--selectic-active-item-color);\n}\n.selectic-item__active:not(.selected) .selectic-item_icon {\n opacity: 0.2;\n}\n\n.selectic-item__disabled {\n color: var(--selectic-color-disabled);\n background-color: var(--selectic-bg-disabled);\n}\n\n.selectic-item__is-in-group {\n padding-left: 2em;\n}\n\n.selectic-item__is-group {\n font-weight: bold;\n cursor: default;\n}\n\n.selectic-item.selected {\n color: var(--selectic-selected-item-color);\n}\n.selectic-search-scope {\n color: #e0e0e0;\n left: auto;\n right: 10px;\n}\n\n.selectic__message {\n text-align: center;\n padding: 3px;\n}\n\n.selectic .filter-panel {\n padding: 3px;\n margin-left: 0px;\n margin-right: 0px;\n background-color: var(--selectic-panel-bg);\n border-bottom: 1px solid var(--selectic-separator-bordercolor);\n}\n\n.selectic .panelclosed {\n max-height: 0px;\n transition: max-height 0.3s ease-out;\n overflow: hidden;\n}\n\n.panelopened {\n max-height: 200px;\n transition: max-height 0.3s ease-in;\n overflow: hidden;\n}\n\n.selectic .filter-panel__input {\n padding-left: 0px;\n padding-right: 0px;\n padding-bottom: 10px;\n margin-bottom: 0px;\n}\n.selectic .filter-input {\n height: calc(var(--selectic-input-height) * 0.75);\n}\n\n.selectic .checkbox-filter {\n padding: 5px;\n text-align: center;\n}\n\n.selectic .curtain-handler {\n text-align: center;\n}\n\n.selectic .toggle-selectic {\n margin: 5px;\n padding-left: 0px;\n padding-right: 0px;\n}\n\n.selectic .toggle-boolean-select-all-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .toggle-boolean-excluding-toggle {\n display: inline;\n margin-right: 15px;\n}\n\n.selectic .single-value {\n display: grid;\n grid-template: \"value icon\" 1fr / max-content max-content;\n\n padding: 2px;\n padding-left: 5px;\n margin-left: 0;\n margin-right: 5px;\n /* margin top/bottom are mainly to create a gutter in multilines */\n margin-top: 2px;\n margin-bottom: 2px;\n\n border-radius: 3px;\n background-color: var(--selectic-value-bg);\n max-height: calc(var(--selectic-input-height) - 10px);\n max-width: 100%;\n min-width: 30px;\n\n overflow: hidden;\n white-space: nowrap;\n line-height: initial;\n vertical-align: middle;\n}\n\n.selectic .more-items {\n display: inline-block;\n\n padding-left: 5px;\n padding-right: 5px;\n border-radius: 10px;\n\n background-color: var(--selectic-more-items-bg, var(--selectic-info-bg));\n color: var(--selectic-more-items-color, var(--selectic-info-color));\n cursor: help;\n}\n.selectic-input__selected-items__value {\n grid-area: value;\n align-self: center;\n justify-self: normal;\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n}\n\n.selectic-input__selected-items__icon {\n grid-area: icon;\n align-self: center;\n justify-self: center;\n margin-left: 5px;\n}\n.selectic-input__selected-items__icon:hover {\n color: var(--selectic-selected-item-color);\n}\n\n.selectic__label-disabled {\n opacity: 0.5;\n transition: opacity 400ms;\n}\n\n/* XXX: override padding of bootstrap input-sm.\n * This padding introduce a line shift. */\n.selectic.input-sm {\n padding: 0;\n}\n\n/* {{{ overflow multiline */\n\n.selectic--overflow-multiline,\n.selectic--overflow-multiline.form-control,\n.selectic--overflow-multiline .form-control {\n height: unset;\n}\n\n.selectic--overflow-multiline .selectic-input {\n overflow: unset;\n}\n\n.selectic--overflow-multiline .selectic-input__selected-items {\n flex-wrap: wrap;\n}\n\n/* }}} */\n";
32
32
  styleInject(css_248z);
33
33
 
34
- /* File Purpose:
35
- * It keeps and computes all states at a single place.
36
- * Every inner components of Selectic should communicate with this file to
37
- * change or to get states.
34
+ /**
35
+ * Clone the object and its inner properties.
36
+ * @param obj The object to be clone.
37
+ * @param refs internal reference to object to avoid cyclic references
38
+ * @returns a copy of obj
38
39
  */
39
- /* }}} */
40
- /* {{{ Helper */
40
+ function deepClone(obj, refs = new WeakMap()) {
41
+ /* For circular references */
42
+ if (refs.has(obj)) {
43
+ return refs.get(obj);
44
+ }
45
+ if (typeof obj === 'object') {
46
+ if (Array.isArray(obj)) {
47
+ const ref = [];
48
+ refs.set(obj, ref);
49
+ obj.forEach((val, idx) => {
50
+ ref[idx] = deepClone(val, refs);
51
+ });
52
+ return ref;
53
+ }
54
+ if (obj instanceof RegExp) {
55
+ const ref = new RegExp(obj.source, obj.flags);
56
+ refs.set(obj, ref);
57
+ return ref;
58
+ }
59
+ /* This should be an object */
60
+ const ref = {};
61
+ refs.set(obj, ref);
62
+ for (const [key, val] of Object.entries(obj)) {
63
+ ref[key] = deepClone(val, refs);
64
+ }
65
+ return ref;
66
+ }
67
+ /* This should be a primitive */
68
+ return obj;
69
+ }
41
70
  /**
42
71
  * Escape search string to consider regexp special characters as they
43
72
  * are and not like special characters.
@@ -68,6 +97,12 @@ function assignObject(obj, ...sourceObjects) {
68
97
  }
69
98
  return result;
70
99
  }
100
+
101
+ /* File Purpose:
102
+ * It keeps and computes all states at a single place.
103
+ * Every inner components of Selectic should communicate with this file to
104
+ * change or to get states.
105
+ */
71
106
  /* }}} */
72
107
  /* {{{ Static */
73
108
  function changeTexts$1(texts) {
@@ -97,6 +132,26 @@ let closePreviousSelectic;
97
132
  let uid = 0;
98
133
  class SelecticStore {
99
134
  constructor(props = {}) {
135
+ /* Do not need reactivity */
136
+ this.requestId = 0;
137
+ this._uid = ++uid;
138
+ /* {{{ Props */
139
+ const defaultProps = {
140
+ value: null,
141
+ selectionIsExcluded: false,
142
+ disabled: false,
143
+ options: null,
144
+ childOptions: [],
145
+ groups: [],
146
+ texts: null,
147
+ params: {},
148
+ fetchCallback: null,
149
+ getItemsCallback: null,
150
+ keepOpenWithOtherSelectic: false,
151
+ };
152
+ const propsVal = assignObject(defaultProps, props);
153
+ this.props = reactive(propsVal);
154
+ /* }}} */
100
155
  /* {{{ data */
101
156
  this.state = reactive({
102
157
  multiple: false,
@@ -137,27 +192,6 @@ class SelecticStore {
137
192
  automaticClose: false,
138
193
  },
139
194
  });
140
- /* Do not need reactivity */
141
- this.requestId = 0;
142
- this._uid = ++uid;
143
- /* {{{ Props */
144
- const defaultProps = {
145
- value: null,
146
- selectionIsExcluded: false,
147
- disabled: false,
148
- options: null,
149
- childOptions: [],
150
- groups: [],
151
- texts: null,
152
- params: {},
153
- fetchCallback: null,
154
- getItemsCallback: null,
155
- keepOpenWithOtherSelectic: false,
156
- };
157
- const propsVal = assignObject(defaultProps, props);
158
- this.props = reactive(propsVal);
159
- /* }}} */
160
- /* {{{ data */
161
195
  this.data = reactive({
162
196
  labels: Object.assign({}, messages),
163
197
  itemsPerPage: 10,
@@ -208,17 +242,17 @@ class SelecticStore {
208
242
  this.commit('isOpen', false);
209
243
  this.buildAllOptions(true);
210
244
  this.buildSelectedOptions();
211
- });
245
+ }, { deep: true });
212
246
  watch(() => [this.listOptions, this.elementOptions], () => {
213
247
  /* TODO: transform allOptions as a computed properties and this
214
248
  * watcher become useless */
215
249
  this.buildAllOptions(true);
216
- });
250
+ }, { deep: true });
217
251
  watch(() => this.props.value, () => {
218
252
  var _a;
219
253
  const value = (_a = this.props.value) !== null && _a !== void 0 ? _a : null;
220
254
  this.commit('internalValue', value);
221
- });
255
+ }, { deep: true });
222
256
  watch(() => this.props.selectionIsExcluded, () => {
223
257
  this.commit('selectionIsExcluded', this.props.selectionIsExcluded);
224
258
  });
@@ -234,14 +268,14 @@ class SelecticStore {
234
268
  areAllSelected = this.state.filteredOptions.every((item) => !!(+item.selected ^ selectionIsExcluded));
235
269
  }
236
270
  this.state.status.areAllSelected = areAllSelected;
237
- });
271
+ }, { deep: true });
238
272
  watch(() => this.state.internalValue, () => {
239
273
  this.buildSelectedOptions();
240
- });
274
+ }, { deep: true });
241
275
  watch(() => this.state.allOptions, () => {
242
276
  this.checkAutoSelect();
243
277
  this.checkAutoDisabled();
244
- });
278
+ }, { deep: true });
245
279
  watch(() => this.state.totalAllOptions, () => {
246
280
  this.checkHideFilter();
247
281
  });
@@ -271,8 +305,8 @@ class SelecticStore {
271
305
  * and ensure convertValue run with correct state */
272
306
  assignObject(this.state, {
273
307
  internalValue: this.convertTypeValue(value),
274
- selectionIsExcluded: props.selectionIsExcluded,
275
- disabled: props.disabled,
308
+ selectionIsExcluded: !!this.props.selectionIsExcluded,
309
+ disabled: !!this.props.disabled, /* XXX: !! is needed to copy value and not proxy reference */
276
310
  });
277
311
  this.checkHideFilter();
278
312
  if (this.props.texts) {
@@ -1390,7 +1424,7 @@ __decorate$4([
1390
1424
  Prop({ default: '' })
1391
1425
  ], MainInput.prototype, "id", void 0);
1392
1426
  __decorate$4([
1393
- Watch('store.state.internalValue')
1427
+ Watch('store.state.internalValue', { deep: true })
1394
1428
  ], MainInput.prototype, "onInternalChange", null);
1395
1429
  MainInput = __decorate$4([
1396
1430
  Component
@@ -1494,7 +1528,7 @@ let FilterPanel = class FilterPanel extends Vue {
1494
1528
  document.addEventListener('keypress', this.onKeyPressed);
1495
1529
  this.getFocus();
1496
1530
  }
1497
- destroyed() {
1531
+ unmounted() {
1498
1532
  document.removeEventListener('keypress', this.onKeyPressed);
1499
1533
  }
1500
1534
  /* }}} */
@@ -1754,7 +1788,7 @@ __decorate$2([
1754
1788
  Watch('store.state.offsetItem')
1755
1789
  ], List.prototype, "onOffsetChange", null);
1756
1790
  __decorate$2([
1757
- Watch('filteredOptions')
1791
+ Watch('filteredOptions', { deep: true })
1758
1792
  ], List.prototype, "onFilteredOptionsChange", null);
1759
1793
  __decorate$2([
1760
1794
  Watch('groupId')
@@ -1925,7 +1959,7 @@ let ExtendedList = class ExtendedList extends Vue {
1925
1959
  document.body.addEventListener('keydown', this.onKeyDown);
1926
1960
  this.computeListSize();
1927
1961
  }
1928
- destroyed() {
1962
+ unmounted() {
1929
1963
  document.body.removeEventListener('keydown', this.onKeyDown);
1930
1964
  /* force the element to be removed from DOM */
1931
1965
  if (this.$el.parentNode) {
@@ -1970,7 +2004,7 @@ __decorate$1([
1970
2004
  Prop({ default: 300 })
1971
2005
  ], ExtendedList.prototype, "width", void 0);
1972
2006
  __decorate$1([
1973
- Watch('store.state.filteredOptions')
2007
+ Watch('store.state.filteredOptions', { deep: true })
1974
2008
  ], ExtendedList.prototype, "onFilteredOptionsChange", null);
1975
2009
  __decorate$1([
1976
2010
  Watch('store.state.hideFilter')
@@ -2218,7 +2252,7 @@ let Selectic = class Selectic extends Vue {
2218
2252
  this.store.props.selectionIsExcluded = this.selectionIsExcluded;
2219
2253
  }
2220
2254
  onOptionsChange() {
2221
- this.store.props.options = Array.from(this.options);
2255
+ this.store.props.options = deepClone(this.options);
2222
2256
  }
2223
2257
  onTextsChange() {
2224
2258
  const texts = this.texts;
@@ -2459,7 +2493,7 @@ let Selectic = class Selectic extends Vue {
2459
2493
  // }
2460
2494
  // this.store.childOptions = options;
2461
2495
  }
2462
- beforeDestroy() {
2496
+ beforeUnmount() {
2463
2497
  this.removeListeners();
2464
2498
  }
2465
2499
  /* }}} */
@@ -2535,22 +2569,22 @@ __decorate([
2535
2569
  Prop()
2536
2570
  ], Selectic.prototype, "_getMethods", void 0);
2537
2571
  __decorate([
2538
- Watch('value')
2572
+ Watch('value', { deep: true })
2539
2573
  ], Selectic.prototype, "onValueChange", null);
2540
2574
  __decorate([
2541
2575
  Watch('selectionIsExcluded')
2542
2576
  ], Selectic.prototype, "onExcludedChange", null);
2543
2577
  __decorate([
2544
- Watch('options')
2578
+ Watch('options', { deep: true })
2545
2579
  ], Selectic.prototype, "onOptionsChange", null);
2546
2580
  __decorate([
2547
- Watch('texts')
2581
+ Watch('texts', { deep: true })
2548
2582
  ], Selectic.prototype, "onTextsChange", null);
2549
2583
  __decorate([
2550
2584
  Watch('disabled')
2551
2585
  ], Selectic.prototype, "onDisabledChange", null);
2552
2586
  __decorate([
2553
- Watch('groups')
2587
+ Watch('groups', { deep: true })
2554
2588
  ], Selectic.prototype, "onGroupsChanged", null);
2555
2589
  __decorate([
2556
2590
  Watch('placeholder')
@@ -2562,7 +2596,7 @@ __decorate([
2562
2596
  Watch('isFocused')
2563
2597
  ], Selectic.prototype, "onFocusChanged", null);
2564
2598
  __decorate([
2565
- Watch('store.state.internalValue')
2599
+ Watch('store.state.internalValue', { deep: true })
2566
2600
  ], Selectic.prototype, "onInternalValueChange", null);
2567
2601
  __decorate([
2568
2602
  Emit('input'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selectic",
3
- "version": "3.0.5",
3
+ "version": "3.0.9",
4
4
  "description": "Smart Select for VueJS 3.x",
5
5
  "main": "dist/selectic.common.js",
6
6
  "module": "dist/selectic.esm.js",
@@ -192,7 +192,7 @@ export default class ExtendedList extends Vue<Props> {
192
192
  /* }}} */
193
193
  /* {{{ watch */
194
194
 
195
- @Watch('store.state.filteredOptions')
195
+ @Watch('store.state.filteredOptions', { deep: true })
196
196
  public onFilteredOptionsChange() {
197
197
  this.$nextTick(this.computeListSize);
198
198
  }
@@ -228,7 +228,7 @@ export default class ExtendedList extends Vue<Props> {
228
228
  this.computeListSize();
229
229
  }
230
230
 
231
- public destroyed() {
231
+ public unmounted() {
232
232
  document.body.removeEventListener('keydown', this.onKeyDown);
233
233
 
234
234
  /* force the element to be removed from DOM */
package/src/Filter.tsx CHANGED
@@ -133,7 +133,7 @@ export default class FilterPanel extends Vue<Props> {
133
133
  this.getFocus();
134
134
  }
135
135
 
136
- public destroyed() {
136
+ public unmounted() {
137
137
  document.removeEventListener('keypress', this.onKeyPressed);
138
138
  }
139
139
 
package/src/List.tsx CHANGED
@@ -220,7 +220,7 @@ export default class List extends Vue<Props> {
220
220
  this.checkOffset();
221
221
  }
222
222
 
223
- @Watch('filteredOptions')
223
+ @Watch('filteredOptions', { deep: true })
224
224
  public onFilteredOptionsChange() {
225
225
  this.checkOffset();
226
226
  }
package/src/MainInput.tsx CHANGED
@@ -264,7 +264,7 @@ export default class MainInput extends Vue<Props> {
264
264
  /* }}} */
265
265
  /* {{{ watch */
266
266
 
267
- @Watch('store.state.internalValue')
267
+ @Watch('store.state.internalValue', { deep: true })
268
268
  public onInternalChange() {
269
269
  this.nbHiddenItems = 0;
270
270
  }
package/src/Store.tsx CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { reactive, watch, unref, computed, ComputedRef } from 'vue';
8
+ import { convertToRegExp, assignObject } from './tools';
8
9
 
9
10
  /* {{{ Types definitions */
10
11
 
@@ -336,42 +337,6 @@ interface Messages {
336
337
 
337
338
  export type PartialMessages = { [K in keyof Messages]?: Messages[K] };
338
339
 
339
- /* }}} */
340
- /* {{{ Helper */
341
-
342
- /**
343
- * Escape search string to consider regexp special characters as they
344
- * are and not like special characters.
345
- * Consider * characters as a wildcards characters (meanings 0 or
346
- * more characters) and convert them to .* (the wildcard characters
347
- * in Regexp)
348
- *
349
- * @param {String} name the original string to convert
350
- * @param {String} [flag] mode to apply for regExp
351
- * @return {String} the string ready to use for RegExp format
352
- */
353
- function convertToRegExp(name: string, flag = 'i'): RegExp {
354
- const pattern = name.replace(/[\\^$.+?(){}[\]|]/g, '\\$&')
355
- .replace(/\*/g, '.*');
356
-
357
- return new RegExp(pattern, flag);
358
- }
359
-
360
- /** Does the same as Object.assign but does not replace if value is undefined */
361
- function assignObject<T>(obj: Partial<T>, ...sourceObjects: Array<Partial<T>>): T {
362
- const result = obj;
363
- for (const source of sourceObjects) {
364
- for (const key of Object.keys(source)) {
365
- const value = source[key as keyof T];
366
- if (value === undefined) {
367
- continue;
368
- }
369
- result[key as keyof T] = value;
370
- }
371
- }
372
- return result as T;
373
- }
374
-
375
340
  /* }}} */
376
341
  /* {{{ Static */
377
342
 
@@ -411,48 +376,7 @@ export default class SelecticStore {
411
376
 
412
377
  /* {{{ data */
413
378
 
414
- public state = reactive<SelecticStoreState>({
415
- multiple: false,
416
- disabled: false,
417
- placeholder: '',
418
- hideFilter: false,
419
- keepFilterOpen: false,
420
- allowRevert: undefined,
421
- allowClearSelection: false,
422
- autoSelect: true,
423
- autoDisabled: true,
424
- strictValue: false,
425
- selectionOverflow: 'collapsed',
426
-
427
- internalValue: null,
428
- isOpen: false,
429
- searchText: '',
430
- selectionIsExcluded: false,
431
- allOptions: [],
432
- dynOptions: [],
433
- filteredOptions: [],
434
- selectedOptions: null,
435
- totalAllOptions: Infinity,
436
- totalDynOptions: Infinity,
437
- totalFilteredOptions: Infinity,
438
- groups: new Map(),
439
- offsetItem: 0,
440
- activeItemIdx: -1,
441
- pageSize: 100,
442
- listPosition: 'auto',
443
-
444
- optionBehaviorOperation: 'sort',
445
- optionBehaviorOrder: ['O', 'D', 'E'],
446
-
447
- status: {
448
- searching: false,
449
- errorMessage: '',
450
- areAllSelected: false,
451
- hasChanged: false,
452
- automaticChange: false,
453
- automaticClose: false,
454
- },
455
- });
379
+ public state: SelecticStoreState;
456
380
  public data: Data;
457
381
 
458
382
  /* Do not need reactivity */
@@ -500,6 +424,49 @@ export default class SelecticStore {
500
424
  /* }}} */
501
425
  /* {{{ data */
502
426
 
427
+ this.state = reactive<SelecticStoreState>({
428
+ multiple: false,
429
+ disabled: false,
430
+ placeholder: '',
431
+ hideFilter: false,
432
+ keepFilterOpen: false,
433
+ allowRevert: undefined,
434
+ allowClearSelection: false,
435
+ autoSelect: true,
436
+ autoDisabled: true,
437
+ strictValue: false,
438
+ selectionOverflow: 'collapsed',
439
+
440
+ internalValue: null,
441
+ isOpen: false,
442
+ searchText: '',
443
+ selectionIsExcluded: false,
444
+ allOptions: [],
445
+ dynOptions: [],
446
+ filteredOptions: [],
447
+ selectedOptions: null,
448
+ totalAllOptions: Infinity,
449
+ totalDynOptions: Infinity,
450
+ totalFilteredOptions: Infinity,
451
+ groups: new Map(),
452
+ offsetItem: 0,
453
+ activeItemIdx: -1,
454
+ pageSize: 100,
455
+ listPosition: 'auto',
456
+
457
+ optionBehaviorOperation: 'sort',
458
+ optionBehaviorOrder: ['O', 'D', 'E'],
459
+
460
+ status: {
461
+ searching: false,
462
+ errorMessage: '',
463
+ areAllSelected: false,
464
+ hasChanged: false,
465
+ automaticChange: false,
466
+ automaticClose: false,
467
+ },
468
+ });
469
+
503
470
  this.data = reactive({
504
471
  labels: Object.assign({}, messages),
505
472
  itemsPerPage: 10,
@@ -565,18 +532,18 @@ export default class SelecticStore {
565
532
  this.commit('isOpen', false);
566
533
  this.buildAllOptions(true);
567
534
  this.buildSelectedOptions();
568
- });
535
+ }, { deep: true });
569
536
 
570
537
  watch(() => [this.listOptions, this.elementOptions], () => {
571
538
  /* TODO: transform allOptions as a computed properties and this
572
539
  * watcher become useless */
573
540
  this.buildAllOptions(true);
574
- });
541
+ }, { deep: true });
575
542
 
576
543
  watch(() => this.props.value, () => {
577
544
  const value = this.props.value ?? null;
578
545
  this.commit('internalValue', value);
579
- });
546
+ }, { deep: true });
580
547
 
581
548
  watch(() => this.props.selectionIsExcluded, () => {
582
549
  this.commit('selectionIsExcluded', this.props.selectionIsExcluded);
@@ -598,16 +565,16 @@ export default class SelecticStore {
598
565
  }
599
566
 
600
567
  this.state.status.areAllSelected = areAllSelected;
601
- });
568
+ }, { deep: true });
602
569
 
603
570
  watch(() => this.state.internalValue, () => {
604
571
  this.buildSelectedOptions();
605
- });
572
+ }, { deep: true });
606
573
 
607
574
  watch(() => this.state.allOptions, () => {
608
575
  this.checkAutoSelect();
609
576
  this.checkAutoDisabled();
610
- });
577
+ }, { deep: true });
611
578
 
612
579
  watch(() => this.state.totalAllOptions, () => {
613
580
  this.checkHideFilter();
@@ -649,8 +616,8 @@ export default class SelecticStore {
649
616
  * and ensure convertValue run with correct state */
650
617
  assignObject(this.state, {
651
618
  internalValue: this.convertTypeValue(value),
652
- selectionIsExcluded: props.selectionIsExcluded,
653
- disabled: props.disabled,
619
+ selectionIsExcluded: !!this.props.selectionIsExcluded,
620
+ disabled: !!this.props.disabled, /* XXX: !! is needed to copy value and not proxy reference */
654
621
  });
655
622
 
656
623
  this.checkHideFilter();
package/src/index.tsx CHANGED
@@ -21,6 +21,8 @@
21
21
  import {Vue, Component, Emit, Prop, Watch, h} from 'vtyx';
22
22
  import './css/selectic.css';
23
23
 
24
+ import { deepClone } from './tools';
25
+
24
26
  import Store, {
25
27
  changeTexts as storeChangeTexts,
26
28
  OptionProp,
@@ -537,7 +539,7 @@ export default class Selectic extends Vue<Props> {
537
539
  /* }}} */
538
540
  /* {{{ watch */
539
541
 
540
- @Watch('value')
542
+ @Watch('value', { deep: true })
541
543
  public onValueChange() {
542
544
  const currentValue = this.store.state.internalValue;
543
545
  const newValue = this.value ?? null;
@@ -556,12 +558,12 @@ export default class Selectic extends Vue<Props> {
556
558
  this.store.props.selectionIsExcluded = this.selectionIsExcluded;
557
559
  }
558
560
 
559
- @Watch('options')
561
+ @Watch('options', { deep: true })
560
562
  public onOptionsChange() {
561
- this.store.props.options = Array.from(this.options);
563
+ this.store.props.options = deepClone(this.options);
562
564
  }
563
565
 
564
- @Watch('texts')
566
+ @Watch('texts', { deep: true })
565
567
  public onTextsChange() {
566
568
  const texts = this.texts;
567
569
 
@@ -575,7 +577,7 @@ export default class Selectic extends Vue<Props> {
575
577
  this.store.props.disabled = this.disabled;
576
578
  }
577
579
 
578
- @Watch('groups')
580
+ @Watch('groups', { deep: true })
579
581
  public onGroupsChanged() {
580
582
  this.store.changeGroups(this.groups);
581
583
  }
@@ -595,7 +597,7 @@ export default class Selectic extends Vue<Props> {
595
597
  this.focusToggled();
596
598
  }
597
599
 
598
- @Watch('store.state.internalValue')
600
+ @Watch('store.state.internalValue', { deep: true })
599
601
  public onInternalValueChange() {
600
602
  const oldValue = this._oldValue;
601
603
  const value = this.getValue();
@@ -851,7 +853,7 @@ export default class Selectic extends Vue<Props> {
851
853
  // this.store.childOptions = options;
852
854
  }
853
855
 
854
- public beforeDestroy() {
856
+ public beforeUnmount() {
855
857
  this.removeListeners();
856
858
  }
857
859
 
package/src/tools.ts ADDED
@@ -0,0 +1,75 @@
1
+
2
+ /**
3
+ * Clone the object and its inner properties.
4
+ * @param obj The object to be clone.
5
+ * @param refs internal reference to object to avoid cyclic references
6
+ * @returns a copy of obj
7
+ */
8
+ export function deepClone<T = any>(obj: T, refs: WeakMap<any, any> = new WeakMap()): T {
9
+ /* For circular references */
10
+ if (refs.has(obj)) {
11
+ return refs.get(obj);
12
+ }
13
+
14
+ if (typeof obj === 'object') {
15
+ if (Array.isArray(obj)) {
16
+ const ref: any[] = [];
17
+ refs.set(obj, ref);
18
+ obj.forEach((val, idx) => {
19
+ ref[idx] = deepClone(val, refs);
20
+ });
21
+ return ref as unknown as T;
22
+ }
23
+
24
+ if (obj instanceof RegExp) {
25
+ const ref = new RegExp(obj.source, obj.flags);
26
+ refs.set(obj, ref);
27
+ return ref as unknown as T;
28
+ }
29
+
30
+ /* This should be an object */
31
+ const ref: any = {};
32
+ refs.set(obj, ref);
33
+ for (const [key, val] of Object.entries(obj)) {
34
+ ref[key] = deepClone(val, refs);
35
+ }
36
+ return ref as unknown as T;
37
+ }
38
+
39
+ /* This should be a primitive */
40
+ return obj;
41
+ }
42
+
43
+
44
+ /**
45
+ * Escape search string to consider regexp special characters as they
46
+ * are and not like special characters.
47
+ * Consider * characters as a wildcards characters (meanings 0 or
48
+ * more characters) and convert them to .* (the wildcard characters
49
+ * in Regexp)
50
+ *
51
+ * @param {String} name the original string to convert
52
+ * @param {String} [flag] mode to apply for regExp
53
+ * @return {String} the string ready to use for RegExp format
54
+ */
55
+ export function convertToRegExp(name: string, flag = 'i'): RegExp {
56
+ const pattern = name.replace(/[\\^$.+?(){}[\]|]/g, '\\$&')
57
+ .replace(/\*/g, '.*');
58
+
59
+ return new RegExp(pattern, flag);
60
+ }
61
+
62
+ /** Does the same as Object.assign but does not replace if value is undefined */
63
+ export function assignObject<T>(obj: Partial<T>, ...sourceObjects: Array<Partial<T>>): T {
64
+ const result = obj;
65
+ for (const source of sourceObjects) {
66
+ for (const key of Object.keys(source)) {
67
+ const value = source[key as keyof T];
68
+ if (value === undefined) {
69
+ continue;
70
+ }
71
+ result[key as keyof T] = value;
72
+ }
73
+ }
74
+ return result as T;
75
+ }
@@ -223,6 +223,7 @@ tape.test('change props', (subT) => {
223
223
  const store = new Store({
224
224
  value: 2,
225
225
  options: getOptions(5, 'alpha'),
226
+ disabled: false,
226
227
  params: {
227
228
  autoDisabled: true,
228
229
  },
@@ -249,6 +250,7 @@ tape.test('change props', (subT) => {
249
250
  const store = new Store({
250
251
  value: 2,
251
252
  options: getOptions(5, 'alpha'),
253
+ disabled: false,
252
254
  params: {
253
255
  autoDisabled: true,
254
256
  strictValue: true,
@@ -298,6 +300,37 @@ tape.test('change props', (subT) => {
298
300
  t.end();
299
301
  });
300
302
 
303
+ sTest.test('should re-enable the select when data are loaded', async (t) => {
304
+ const store = new Store({
305
+ options: [],
306
+ disabled: false,
307
+ params: {
308
+ autoDisabled: true,
309
+ },
310
+ });
311
+ await _.nextVueTick(store);
312
+
313
+ store.commit('isOpen', true);
314
+ await _.nextVueTick(store);
315
+
316
+ t.is(store.state.internalValue, null);
317
+ t.is(store.state.disabled, true);
318
+ t.is(store.state.isOpen, false);
319
+
320
+ store.props.options = getOptions(0, 'alpha');
321
+ await _.nextVueTick(store);
322
+
323
+ t.is(store.state.internalValue, null);
324
+ t.is(store.state.disabled, true), 'should be disabled';
325
+
326
+ store.props.options = getOptions(5, 'beta');
327
+ await _.nextVueTick(store);
328
+
329
+ t.is(store.state.internalValue, 0);
330
+ t.is(store.state.disabled, false), 'should be enabled';
331
+ t.end();
332
+ });
333
+
301
334
  sTest.test('should not re-enable the select if disable is set', async (t) => {
302
335
  const store = new Store({
303
336
  options: getOptions(1, 'alpha'),
@@ -31,6 +31,6 @@ export default class ExtendedList extends Vue<Props> {
31
31
  private getGroup;
32
32
  private computeListSize;
33
33
  mounted(): void;
34
- destroyed(): void;
34
+ unmounted(): void;
35
35
  render(): h.JSX.Element;
36
36
  }
package/types/Filter.d.ts CHANGED
@@ -23,6 +23,6 @@ export default class FilterPanel extends Vue<Props> {
23
23
  private getFocus;
24
24
  onClosed(): void;
25
25
  mounted(): void;
26
- destroyed(): void;
26
+ unmounted(): void;
27
27
  render(): h.JSX.Element;
28
28
  }
package/types/List.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Vue, h } from 'vtyx';
2
- import Store, { OptionItem, OptionId } from './Store';
2
+ import Store, { OptionItem } from './Store';
3
3
  export interface Props {
4
4
  store: Store;
5
5
  options?: any[];
@@ -14,31 +14,7 @@ export default class List extends Vue<Props> {
14
14
  private itemHeight;
15
15
  private groupId;
16
16
  private doNotScroll;
17
- get filteredOptions(): {
18
- selected: boolean;
19
- disabled: boolean;
20
- isGroup: boolean;
21
- id: OptionId;
22
- text: string;
23
- title?: string | undefined;
24
- group?: import("./Store").StrictOptionId | undefined;
25
- className?: string | undefined;
26
- style?: string | undefined;
27
- icon?: string | undefined;
28
- options?: {
29
- id: OptionId;
30
- text: string;
31
- title?: string | undefined;
32
- disabled?: boolean | undefined;
33
- group?: import("./Store").StrictOptionId | undefined;
34
- className?: string | undefined;
35
- style?: string | undefined;
36
- icon?: string | undefined;
37
- options?: any[] | undefined;
38
- data?: any;
39
- }[] | undefined;
40
- data?: any;
41
- }[];
17
+ get filteredOptions(): OptionItem[];
42
18
  get isMultiple(): boolean;
43
19
  get itemsMargin(): number;
44
20
  get shortOptions(): OptionItem[];
package/types/Store.d.ts CHANGED
@@ -142,141 +142,7 @@ export declare type PartialMessages = {
142
142
  export declare function changeTexts(texts: PartialMessages): void;
143
143
  export default class SelecticStore {
144
144
  props: InternalProps;
145
- state: {
146
- internalValue: OptionId | StrictOptionId[];
147
- selectionIsExcluded: boolean;
148
- multiple: boolean;
149
- disabled: boolean;
150
- placeholder: string;
151
- hideFilter: boolean;
152
- keepFilterOpen: boolean;
153
- allowRevert?: boolean | undefined;
154
- allowClearSelection: boolean;
155
- autoSelect: boolean;
156
- autoDisabled: boolean;
157
- strictValue: boolean;
158
- selectionOverflow: SelectionOverflow;
159
- isOpen: boolean;
160
- searchText: string;
161
- allOptions: {
162
- id: OptionId;
163
- text: string;
164
- title?: string | undefined;
165
- disabled?: boolean | undefined;
166
- group?: StrictOptionId | undefined;
167
- className?: string | undefined;
168
- style?: string | undefined;
169
- icon?: string | undefined;
170
- options?: any[] | undefined;
171
- data?: any;
172
- }[];
173
- dynOptions: {
174
- id: OptionId;
175
- text: string;
176
- title?: string | undefined;
177
- disabled?: boolean | undefined;
178
- group?: StrictOptionId | undefined;
179
- className?: string | undefined;
180
- style?: string | undefined;
181
- icon?: string | undefined;
182
- options?: any[] | undefined;
183
- data?: any;
184
- }[];
185
- filteredOptions: {
186
- selected: boolean;
187
- disabled: boolean;
188
- isGroup: boolean;
189
- id: OptionId;
190
- text: string;
191
- title?: string | undefined;
192
- group?: StrictOptionId | undefined;
193
- className?: string | undefined;
194
- style?: string | undefined;
195
- icon?: string | undefined;
196
- options?: {
197
- id: OptionId;
198
- text: string;
199
- title?: string | undefined;
200
- disabled?: boolean | undefined;
201
- group?: StrictOptionId | undefined;
202
- className?: string | undefined;
203
- style?: string | undefined;
204
- icon?: string | undefined;
205
- options?: any[] | undefined;
206
- data?: any;
207
- }[] | undefined;
208
- data?: any;
209
- }[];
210
- selectedOptions: {
211
- selected: boolean;
212
- disabled: boolean;
213
- isGroup: boolean;
214
- id: OptionId;
215
- text: string;
216
- title?: string | undefined;
217
- group?: StrictOptionId | undefined;
218
- className?: string | undefined;
219
- style?: string | undefined;
220
- icon?: string | undefined;
221
- options?: {
222
- id: OptionId;
223
- text: string;
224
- title?: string | undefined;
225
- disabled?: boolean | undefined;
226
- group?: StrictOptionId | undefined;
227
- className?: string | undefined;
228
- style?: string | undefined;
229
- icon?: string | undefined;
230
- options?: any[] | undefined;
231
- data?: any;
232
- }[] | undefined;
233
- data?: any;
234
- } | {
235
- selected: boolean;
236
- disabled: boolean;
237
- isGroup: boolean;
238
- id: OptionId;
239
- text: string;
240
- title?: string | undefined;
241
- group?: StrictOptionId | undefined;
242
- className?: string | undefined;
243
- style?: string | undefined;
244
- icon?: string | undefined;
245
- options?: {
246
- id: OptionId;
247
- text: string;
248
- title?: string | undefined;
249
- disabled?: boolean | undefined;
250
- group?: StrictOptionId | undefined;
251
- className?: string | undefined;
252
- style?: string | undefined;
253
- icon?: string | undefined;
254
- options?: any[] | undefined;
255
- data?: any;
256
- }[] | undefined;
257
- data?: any;
258
- }[] | null;
259
- totalAllOptions: number;
260
- totalDynOptions: number;
261
- totalFilteredOptions: number;
262
- groups: Map<OptionId, string>;
263
- offsetItem: number;
264
- activeItemIdx: number;
265
- pageSize: number;
266
- formatOption?: FormatCallback | undefined;
267
- formatSelection?: FormatCallback | undefined;
268
- optionBehaviorOperation: OptionBehaviorOperation;
269
- optionBehaviorOrder: OptionBehaviorOrder[];
270
- listPosition: ListPosition;
271
- status: {
272
- searching: boolean;
273
- errorMessage: string;
274
- areAllSelected: boolean;
275
- hasChanged: boolean;
276
- automaticChange: boolean;
277
- automaticClose: boolean;
278
- };
279
- };
145
+ state: SelecticStoreState;
280
146
  data: Data;
281
147
  private requestId;
282
148
  private cacheRequest;
package/types/index.d.ts CHANGED
@@ -136,6 +136,6 @@ export default class Selectic extends Vue<Props> {
136
136
  created(): void;
137
137
  mounted(): void;
138
138
  beforeUpdate(): void;
139
- beforeDestroy(): void;
139
+ beforeUnmount(): void;
140
140
  render(): h.JSX.Element | undefined;
141
141
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Clone the object and its inner properties.
3
+ * @param obj The object to be clone.
4
+ * @param refs internal reference to object to avoid cyclic references
5
+ * @returns a copy of obj
6
+ */
7
+ export declare function deepClone<T = any>(obj: T, refs?: WeakMap<any, any>): T;
8
+ /**
9
+ * Escape search string to consider regexp special characters as they
10
+ * are and not like special characters.
11
+ * Consider * characters as a wildcards characters (meanings 0 or
12
+ * more characters) and convert them to .* (the wildcard characters
13
+ * in Regexp)
14
+ *
15
+ * @param {String} name the original string to convert
16
+ * @param {String} [flag] mode to apply for regExp
17
+ * @return {String} the string ready to use for RegExp format
18
+ */
19
+ export declare function convertToRegExp(name: string, flag?: string): RegExp;
20
+ /** Does the same as Object.assign but does not replace if value is undefined */
21
+ export declare function assignObject<T>(obj: Partial<T>, ...sourceObjects: Array<Partial<T>>): T;