selectic 1.3.11 → 3.0.3
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/README.md +6 -5
- package/dist/selectic.common.js +708 -594
- package/dist/selectic.esm.js +670 -557
- package/doc/breakingChanges.md +55 -0
- package/doc/events.md +68 -17
- package/doc/main.md +7 -0
- package/doc/params.md +5 -2
- package/package.json +46 -41
- package/src/ExtendedList.tsx +14 -13
- package/src/Filter.tsx +34 -22
- package/src/List.tsx +14 -13
- package/src/MainInput.tsx +26 -27
- package/src/Store.tsx +446 -291
- package/src/css/selectic.css +6 -0
- package/src/index.tsx +249 -140
- package/test/Selectic/Selectic_props.spec.js +29 -10
- package/test/Store/Store_creation.spec.js +451 -438
- package/test/Store/Store_props.spec.js +119 -159
- package/test/Store/Store_state.spec.js +9 -13
- package/test/Store/changeGroups.spec.js +4 -6
- package/test/Store/changeTexts.spec.js +28 -30
- package/test/Store/clearCache.spec.js +24 -8
- package/test/Store/commit.spec.js +256 -99
- package/test/Store/getItem.spec.js +30 -38
- package/test/Store/getItems.spec.js +33 -43
- package/test/Store/selectItem.spec.js +4 -4
- package/test/Store/toggleSelectAll.spec.js +50 -52
- package/test/helper.js +3 -0
- package/test/tools.js +4 -2
- package/tsconfig.json +5 -0
- package/types/ExtendedList.d.ts +6 -6
- package/types/Filter.d.ts +5 -5
- package/types/List.d.ts +33 -9
- package/types/MainInput.d.ts +5 -5
- package/types/Store.d.ts +176 -42
- package/types/index.d.ts +54 -24
- package/types/OldStore.d.ts +0 -195
package/src/Store.tsx
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/* File Purpose:
|
|
2
2
|
* It keeps and computes all states at a single place.
|
|
3
|
-
* Every inner components of Selectic should
|
|
3
|
+
* Every inner components of Selectic should communicate with this file to
|
|
4
4
|
* change or to get states.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { reactive, watch, unref, computed, ComputedRef } from 'vue';
|
|
8
8
|
|
|
9
9
|
/* {{{ Types definitions */
|
|
10
10
|
|
|
11
|
+
type MandateProps<T extends {}> = {
|
|
12
|
+
[TK in keyof T]-?: T[TK];
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
type voidCaller = () => void;
|
|
12
16
|
|
|
13
17
|
export type StrictOptionId = string | number;
|
|
@@ -65,9 +69,18 @@ export type ListPosition =
|
|
|
65
69
|
'bottom'
|
|
66
70
|
/* Display the list at bottom */
|
|
67
71
|
| 'top'
|
|
68
|
-
/* Display the list at
|
|
72
|
+
/* Display the list at bottom but if there is not enough space, display it at top */
|
|
69
73
|
| 'auto';
|
|
70
74
|
|
|
75
|
+
export type HideFilter =
|
|
76
|
+
/* Display or hide the filter panel */
|
|
77
|
+
boolean
|
|
78
|
+
/* The handler to open the filter panel is hidden only if there is less
|
|
79
|
+
* than 10 options */
|
|
80
|
+
| 'auto'
|
|
81
|
+
/* The panel filter is always open */
|
|
82
|
+
| 'open';
|
|
83
|
+
|
|
71
84
|
export interface SelecticStoreStateParams {
|
|
72
85
|
/* Equivalent of <select>'s "multiple" attribute */
|
|
73
86
|
multiple?: boolean;
|
|
@@ -76,7 +89,7 @@ export interface SelecticStoreStateParams {
|
|
|
76
89
|
placeholder?: string;
|
|
77
90
|
|
|
78
91
|
/* Hide filter component when enabled */
|
|
79
|
-
hideFilter?:
|
|
92
|
+
hideFilter?: HideFilter;
|
|
80
93
|
|
|
81
94
|
/* Allow to reverse selection.
|
|
82
95
|
* If true, parent should support the selectionIsExcluded property.
|
|
@@ -124,31 +137,34 @@ export interface SelecticStoreStateParams {
|
|
|
124
137
|
*/
|
|
125
138
|
optionBehavior?: string;
|
|
126
139
|
|
|
140
|
+
/* Indicate where the list should be deployed */
|
|
141
|
+
listPosition?: ListPosition;
|
|
142
|
+
|
|
127
143
|
/* If true, the component is open at start */
|
|
128
144
|
isOpen?: boolean;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
export interface Props {
|
|
132
148
|
/* Selected value */
|
|
133
|
-
value?: SelectedValue;
|
|
149
|
+
value?: SelectedValue | null;
|
|
134
150
|
|
|
135
151
|
/* If true, the value represents the ones we don't want to select */
|
|
136
152
|
selectionIsExcluded?: boolean;
|
|
137
153
|
|
|
138
|
-
/* Equivalent of "disabled"
|
|
154
|
+
/* Equivalent of "disabled" Select's attribute */
|
|
139
155
|
disabled?: boolean;
|
|
140
156
|
|
|
141
157
|
/* List of options to display */
|
|
142
|
-
options?: OptionProp[];
|
|
158
|
+
options?: OptionProp[] | null;
|
|
143
159
|
|
|
144
160
|
/* List of options to display from child elements */
|
|
145
|
-
childOptions?:
|
|
161
|
+
childOptions?: OptionValue[];
|
|
146
162
|
|
|
147
163
|
/* Define groups which will be used by items */
|
|
148
164
|
groups?: GroupValue[];
|
|
149
165
|
|
|
150
166
|
/* Overwrite default texts */
|
|
151
|
-
texts?: PartialMessages;
|
|
167
|
+
texts?: PartialMessages | null;
|
|
152
168
|
|
|
153
169
|
/* Keep this component open if another Selectic component opens */
|
|
154
170
|
keepOpenWithOtherSelectic?: boolean;
|
|
@@ -157,10 +173,24 @@ export interface Props {
|
|
|
157
173
|
params?: SelecticStoreStateParams;
|
|
158
174
|
|
|
159
175
|
/* Method to call to fetch extra data */
|
|
160
|
-
fetchCallback?: FetchCallback;
|
|
176
|
+
fetchCallback?: FetchCallback | null;
|
|
161
177
|
|
|
162
178
|
/* Method to call to get specific item */
|
|
163
|
-
getItemsCallback?: GetCallback;
|
|
179
|
+
getItemsCallback?: GetCallback | null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type InternalProps = MandateProps<Props>;
|
|
183
|
+
|
|
184
|
+
export interface Data {
|
|
185
|
+
/* Number of items displayed in a page (before scrolling) */
|
|
186
|
+
itemsPerPage: number;
|
|
187
|
+
|
|
188
|
+
labels: Messages;
|
|
189
|
+
/* used to avoid checking and updating table while doing batch stuff */
|
|
190
|
+
doNotUpdate: boolean;
|
|
191
|
+
cacheItem: Map<OptionId, OptionValue>;
|
|
192
|
+
activeOrder: OptionBehaviorOrder;
|
|
193
|
+
dynOffset: number;
|
|
164
194
|
}
|
|
165
195
|
|
|
166
196
|
export interface SelecticStoreState {
|
|
@@ -182,6 +212,9 @@ export interface SelecticStoreState {
|
|
|
182
212
|
/* If true, filters and controls are hidden */
|
|
183
213
|
hideFilter: boolean;
|
|
184
214
|
|
|
215
|
+
/* If true, the filter panel is always open */
|
|
216
|
+
keepFilterOpen: boolean;
|
|
217
|
+
|
|
185
218
|
/* Allow to reverse selection.
|
|
186
219
|
* If true, parent should support the selectionIsExcluded property.
|
|
187
220
|
* If false, the action is never available.
|
|
@@ -191,7 +224,7 @@ export interface SelecticStoreState {
|
|
|
191
224
|
allowRevert?: boolean;
|
|
192
225
|
|
|
193
226
|
/* If true, user can clear current selection
|
|
194
|
-
* (if false, it is still possible to clear it
|
|
227
|
+
* (if false, it is still possible to clear it programmatically) */
|
|
195
228
|
allowClearSelection: boolean;
|
|
196
229
|
|
|
197
230
|
/* If false, do not select the first available option even if value is mandatory */
|
|
@@ -273,6 +306,12 @@ export interface SelecticStoreState {
|
|
|
273
306
|
|
|
274
307
|
/* If true, a change has been done by user */
|
|
275
308
|
hasChanged: boolean;
|
|
309
|
+
|
|
310
|
+
/* If true, it means the current change has been done automatically by Selectic */
|
|
311
|
+
automaticChange: boolean;
|
|
312
|
+
|
|
313
|
+
/* If true, it means the current close has been done automatically by Selectic */
|
|
314
|
+
automaticClose: boolean;
|
|
276
315
|
};
|
|
277
316
|
}
|
|
278
317
|
|
|
@@ -298,6 +337,7 @@ interface Messages {
|
|
|
298
337
|
export type PartialMessages = { [K in keyof Messages]?: Messages[K] };
|
|
299
338
|
|
|
300
339
|
/* }}} */
|
|
340
|
+
/* {{{ Helper */
|
|
301
341
|
|
|
302
342
|
/**
|
|
303
343
|
* Escape search string to consider regexp special characters as they
|
|
@@ -317,6 +357,30 @@ function convertToRegExp(name: string, flag = 'i'): RegExp {
|
|
|
317
357
|
return new RegExp(pattern, flag);
|
|
318
358
|
}
|
|
319
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
|
+
/* }}} */
|
|
376
|
+
/* {{{ Static */
|
|
377
|
+
|
|
378
|
+
export function changeTexts(texts: PartialMessages) {
|
|
379
|
+
messages = Object.assign(messages, texts);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* }}} */
|
|
383
|
+
|
|
320
384
|
let messages: Messages = {
|
|
321
385
|
noFetchMethod: 'Fetch callback is missing: it is not possible to retrieve data.',
|
|
322
386
|
searchPlaceholder: 'Search',
|
|
@@ -338,62 +402,21 @@ let messages: Messages = {
|
|
|
338
402
|
|
|
339
403
|
let closePreviousSelectic: undefined | voidCaller;
|
|
340
404
|
|
|
341
|
-
/* {{{ Static */
|
|
342
|
-
|
|
343
|
-
export function changeTexts(texts: PartialMessages) {
|
|
344
|
-
messages = Object.assign(messages, texts);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
405
|
/* }}} */
|
|
348
406
|
|
|
349
|
-
|
|
350
|
-
export default class SelecticStore extends Vue<Props> {
|
|
351
|
-
/* {{{ props */
|
|
352
|
-
|
|
353
|
-
@Prop()
|
|
354
|
-
public value?: SelectedValue;
|
|
355
|
-
|
|
356
|
-
@Prop({default: false})
|
|
357
|
-
public selectionIsExcluded: boolean;
|
|
358
|
-
|
|
359
|
-
@Prop({default: false})
|
|
360
|
-
public disabled: boolean;
|
|
361
|
-
|
|
362
|
-
@Prop()
|
|
363
|
-
public options?: OptionProp[];
|
|
364
|
-
|
|
365
|
-
@Prop()
|
|
366
|
-
public childOptions?: OptionValue[];
|
|
367
|
-
|
|
368
|
-
@Prop({default: () => []})
|
|
369
|
-
public groups: GroupValue[];
|
|
370
|
-
|
|
371
|
-
@Prop()
|
|
372
|
-
public texts?: PartialMessages;
|
|
407
|
+
let uid = 0;
|
|
373
408
|
|
|
374
|
-
|
|
375
|
-
|
|
409
|
+
export default class SelecticStore {
|
|
410
|
+
public props: InternalProps;
|
|
376
411
|
|
|
377
|
-
@Prop()
|
|
378
|
-
private fetchCallback?: FetchCallback;
|
|
379
|
-
|
|
380
|
-
@Prop()
|
|
381
|
-
private getItemsCallback?: GetCallback;
|
|
382
|
-
|
|
383
|
-
@Prop({ default: false })
|
|
384
|
-
private keepOpenWithOtherSelectic: boolean;
|
|
385
|
-
|
|
386
|
-
/* }}} */
|
|
387
412
|
/* {{{ data */
|
|
388
413
|
|
|
389
|
-
|
|
390
|
-
public itemsPerPage = 10;
|
|
391
|
-
|
|
392
|
-
public state: SelecticStoreState = {
|
|
414
|
+
public state = reactive<SelecticStoreState>({
|
|
393
415
|
multiple: false,
|
|
394
416
|
disabled: false,
|
|
395
417
|
placeholder: '',
|
|
396
418
|
hideFilter: false,
|
|
419
|
+
keepFilterOpen: false,
|
|
397
420
|
allowRevert: undefined,
|
|
398
421
|
allowClearSelection: false,
|
|
399
422
|
autoSelect: true,
|
|
@@ -426,59 +449,224 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
426
449
|
errorMessage: '',
|
|
427
450
|
areAllSelected: false,
|
|
428
451
|
hasChanged: false,
|
|
452
|
+
automaticChange: false,
|
|
453
|
+
automaticClose: false,
|
|
429
454
|
},
|
|
430
|
-
};
|
|
431
|
-
public
|
|
432
|
-
/* used to avoid checking and updating table while doing batch stuff */
|
|
433
|
-
private doNotUpdate = false;
|
|
434
|
-
private cacheItem: Map<OptionId, OptionValue> = new Map();
|
|
435
|
-
private activeOrder: OptionBehaviorOrder = 'D';
|
|
436
|
-
private dynOffset: number = 0;
|
|
455
|
+
});
|
|
456
|
+
public data: Data;
|
|
437
457
|
|
|
438
458
|
/* Do not need reactivity */
|
|
439
|
-
private requestId: number;
|
|
459
|
+
private requestId: number = 0;
|
|
440
460
|
private cacheRequest: Map<string, Promise<OptionValue[]>>;
|
|
461
|
+
private closeSelectic: () => void;
|
|
441
462
|
|
|
442
463
|
/* }}} */
|
|
443
464
|
/* {{{ computed */
|
|
444
465
|
|
|
445
466
|
/* Number of item to pre-display */
|
|
446
|
-
|
|
447
|
-
return this.state.pageSize / 2;
|
|
448
|
-
}
|
|
467
|
+
public marginSize: ComputedRef<number>;
|
|
449
468
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
469
|
+
public isPartial: ComputedRef<boolean>;
|
|
470
|
+
public hasAllItems: ComputedRef<boolean>;
|
|
471
|
+
public hasFetchedAllItems: ComputedRef<boolean>;
|
|
472
|
+
private listOptions: ComputedRef<OptionValue[]>;
|
|
473
|
+
private elementOptions: ComputedRef<OptionValue[]>;
|
|
453
474
|
|
|
454
|
-
|
|
455
|
-
|
|
475
|
+
/* }}} */
|
|
476
|
+
|
|
477
|
+
public _uid: number; /* Mainly for debugging */
|
|
478
|
+
|
|
479
|
+
constructor(props: Props = {}) {
|
|
480
|
+
this._uid = ++uid;
|
|
481
|
+
|
|
482
|
+
/* {{{ Props */
|
|
483
|
+
|
|
484
|
+
const defaultProps: InternalProps = {
|
|
485
|
+
value: null,
|
|
486
|
+
selectionIsExcluded: false,
|
|
487
|
+
disabled: false,
|
|
488
|
+
options: null,
|
|
489
|
+
childOptions: [],
|
|
490
|
+
groups: [],
|
|
491
|
+
texts: null,
|
|
492
|
+
params: {},
|
|
493
|
+
fetchCallback: null,
|
|
494
|
+
getItemsCallback: null,
|
|
495
|
+
keepOpenWithOtherSelectic: false,
|
|
496
|
+
};
|
|
497
|
+
const propsVal: InternalProps = assignObject(defaultProps, props);
|
|
498
|
+
this.props = reactive(propsVal);
|
|
499
|
+
|
|
500
|
+
/* }}} */
|
|
501
|
+
/* {{{ data */
|
|
502
|
+
|
|
503
|
+
this.data = reactive({
|
|
504
|
+
labels: Object.assign({}, messages),
|
|
505
|
+
itemsPerPage: 10,
|
|
506
|
+
doNotUpdate: false,
|
|
507
|
+
cacheItem: new Map(),
|
|
508
|
+
activeOrder: 'D',
|
|
509
|
+
dynOffset: 0,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
/* }}} */
|
|
513
|
+
/* {{{ computed */
|
|
514
|
+
|
|
515
|
+
this.marginSize = computed(() => {
|
|
516
|
+
return this.state.pageSize / 2;
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
this.isPartial = computed(() => {
|
|
520
|
+
const state = this.state;
|
|
521
|
+
let isPartial = typeof this.props.fetchCallback === 'function';
|
|
522
|
+
|
|
523
|
+
if (isPartial &&
|
|
524
|
+
state.optionBehaviorOperation === 'force' &&
|
|
525
|
+
this.data.activeOrder !== 'D'
|
|
526
|
+
) {
|
|
527
|
+
isPartial = false;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return isPartial;
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
this.hasAllItems = computed(() => {
|
|
534
|
+
const state = this.state;
|
|
535
|
+
const nbItems = state.totalFilteredOptions + state.groups.size;
|
|
536
|
+
|
|
537
|
+
return this.state.filteredOptions.length >= nbItems;
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
this.hasFetchedAllItems = computed(() => {
|
|
541
|
+
const isPartial = unref(this.isPartial);
|
|
542
|
+
|
|
543
|
+
if (!isPartial) {
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
const state = this.state;
|
|
547
|
+
|
|
548
|
+
return state.dynOptions.length === state.totalDynOptions;
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
this.listOptions = computed(() => {
|
|
552
|
+
return this.getListOptions();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
this.elementOptions = computed(() => {
|
|
556
|
+
return this.getElementOptions();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
/* }}} */
|
|
560
|
+
/* {{{ watch */
|
|
561
|
+
|
|
562
|
+
watch(() => [this.props.options, this.props.childOptions], () => {
|
|
563
|
+
this.data.cacheItem.clear();
|
|
564
|
+
this.setAutomaticClose();
|
|
565
|
+
this.commit('isOpen', false);
|
|
566
|
+
this.buildAllOptions(true);
|
|
567
|
+
this.buildSelectedOptions();
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
watch(() => [this.listOptions, this.elementOptions], () => {
|
|
571
|
+
/* TODO: transform allOptions as a computed properties and this
|
|
572
|
+
* watcher become useless */
|
|
573
|
+
this.buildAllOptions(true);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
watch(() => this.props.value, () => {
|
|
577
|
+
const value = this.props.value ?? null;
|
|
578
|
+
this.commit('internalValue', value);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
watch(() => this.props.selectionIsExcluded, () => {
|
|
582
|
+
this.commit('selectionIsExcluded', this.props.selectionIsExcluded);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
watch(() => this.props.disabled, () => {
|
|
586
|
+
this.commit('disabled', this.props.disabled);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
watch(() => this.state.filteredOptions, () => {
|
|
590
|
+
let areAllSelected = false;
|
|
591
|
+
const hasAllItems = unref(this.hasAllItems);
|
|
592
|
+
|
|
593
|
+
if (hasAllItems) {
|
|
594
|
+
const selectionIsExcluded = +this.state.selectionIsExcluded;
|
|
595
|
+
/* eslint-disable-next-line no-bitwise */
|
|
596
|
+
areAllSelected = this.state.filteredOptions.every((item) =>
|
|
597
|
+
!!(+item.selected ^ selectionIsExcluded));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
this.state.status.areAllSelected = areAllSelected;
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
watch(() => this.state.internalValue, () => {
|
|
604
|
+
this.buildSelectedOptions();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
watch(() => this.state.allOptions, () => {
|
|
608
|
+
this.checkAutoSelect();
|
|
609
|
+
this.checkAutoDisabled();
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
watch(() => this.state.totalAllOptions, () => {
|
|
613
|
+
this.checkHideFilter();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
/* }}} */
|
|
617
|
+
|
|
618
|
+
this.closeSelectic = () => {
|
|
619
|
+
this.setAutomaticClose();
|
|
620
|
+
this.commit('isOpen', false);
|
|
456
621
|
}
|
|
457
622
|
|
|
458
|
-
|
|
459
|
-
}
|
|
623
|
+
const value = this.props.value;
|
|
460
624
|
|
|
461
|
-
|
|
462
|
-
|
|
625
|
+
/* set initial value for non reactive attribute */
|
|
626
|
+
this.cacheRequest = new Map();
|
|
463
627
|
|
|
464
|
-
|
|
465
|
-
|
|
628
|
+
const stateParam: SelecticStoreStateParams | SelecticStoreState =
|
|
629
|
+
Object.assign({}, this.props.params);
|
|
466
630
|
|
|
467
|
-
|
|
468
|
-
|
|
631
|
+
if (stateParam.optionBehavior) {
|
|
632
|
+
this.buildOptionBehavior(
|
|
633
|
+
stateParam.optionBehavior,
|
|
634
|
+
stateParam as SelecticStoreState
|
|
635
|
+
);
|
|
636
|
+
delete stateParam.optionBehavior;
|
|
637
|
+
}
|
|
469
638
|
|
|
470
|
-
if (
|
|
471
|
-
|
|
639
|
+
if (stateParam.hideFilter === 'auto') {
|
|
640
|
+
delete stateParam.hideFilter;
|
|
641
|
+
} else if (stateParam.hideFilter === 'open') {
|
|
642
|
+
this.state.keepFilterOpen = true;
|
|
643
|
+
delete stateParam.hideFilter;
|
|
472
644
|
}
|
|
473
645
|
|
|
474
|
-
|
|
475
|
-
|
|
646
|
+
/* Update state */
|
|
647
|
+
assignObject(this.state, stateParam as SelecticStoreState);
|
|
648
|
+
/* XXX: should be done in 2 lines, in order to set the multiple state
|
|
649
|
+
* and ensure convertValue run with correct state */
|
|
650
|
+
assignObject(this.state, {
|
|
651
|
+
internalValue: this.convertTypeValue(value),
|
|
652
|
+
selectionIsExcluded: props.selectionIsExcluded,
|
|
653
|
+
disabled: props.disabled,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
this.checkHideFilter();
|
|
476
657
|
|
|
477
|
-
|
|
478
|
-
|
|
658
|
+
if (this.props.texts) {
|
|
659
|
+
this.changeTexts(this.props.texts);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
this.addGroups(this.props.groups);
|
|
663
|
+
this.assertValueType();
|
|
664
|
+
this.buildAllOptions();
|
|
665
|
+
|
|
666
|
+
this.buildSelectedOptions();
|
|
667
|
+
this.checkAutoDisabled();
|
|
479
668
|
}
|
|
480
669
|
|
|
481
|
-
/* }}} */
|
|
482
670
|
/* {{{ methods */
|
|
483
671
|
/* {{{ public methods */
|
|
484
672
|
|
|
@@ -524,7 +712,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
524
712
|
if (typeof closePreviousSelectic === 'function') {
|
|
525
713
|
closePreviousSelectic();
|
|
526
714
|
}
|
|
527
|
-
if (!this.keepOpenWithOtherSelectic) {
|
|
715
|
+
if (!this.props.keepOpenWithOtherSelectic) {
|
|
528
716
|
closePreviousSelectic = this.closeSelectic;
|
|
529
717
|
}
|
|
530
718
|
}
|
|
@@ -543,17 +731,28 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
543
731
|
break;
|
|
544
732
|
case 'disabled':
|
|
545
733
|
if (value) {
|
|
734
|
+
this.setAutomaticClose();
|
|
546
735
|
this.commit('isOpen', false);
|
|
547
736
|
}
|
|
548
737
|
break;
|
|
549
738
|
}
|
|
550
739
|
}
|
|
551
740
|
|
|
741
|
+
public setAutomaticChange() {
|
|
742
|
+
this.state.status.automaticChange = true;
|
|
743
|
+
setTimeout(() => this.state.status.automaticChange = false, 0);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
public setAutomaticClose() {
|
|
747
|
+
this.state.status.automaticClose = true;
|
|
748
|
+
setTimeout(() => this.state.status.automaticClose = false, 0);
|
|
749
|
+
}
|
|
750
|
+
|
|
552
751
|
public getItem(id: OptionId): OptionValue {
|
|
553
752
|
let item: OptionValue;
|
|
554
753
|
|
|
555
754
|
if (this.hasItemInStore(id)) {
|
|
556
|
-
item = this.cacheItem.get(id) as OptionValue;
|
|
755
|
+
item = this.data.cacheItem.get(id) as OptionValue;
|
|
557
756
|
} else {
|
|
558
757
|
this.getItems([id]);
|
|
559
758
|
item = {
|
|
@@ -567,8 +766,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
567
766
|
|
|
568
767
|
public async getItems(ids: OptionId[]): Promise<OptionItem[]> {
|
|
569
768
|
const itemsToFetch: OptionId[] = ids.filter((id) => !this.hasItemInStore(id));
|
|
769
|
+
const getItemsCallback = this.props.getItemsCallback;
|
|
570
770
|
|
|
571
|
-
if (itemsToFetch.length && typeof
|
|
771
|
+
if (itemsToFetch.length && typeof getItemsCallback === 'function') {
|
|
572
772
|
const cacheRequest = this.cacheRequest;
|
|
573
773
|
const requestId = itemsToFetch.toString();
|
|
574
774
|
let promise: Promise<OptionValue[]>;
|
|
@@ -576,7 +776,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
576
776
|
if (cacheRequest.has(requestId)) {
|
|
577
777
|
promise = cacheRequest.get(requestId)!;
|
|
578
778
|
} else {
|
|
579
|
-
promise =
|
|
779
|
+
promise = getItemsCallback(itemsToFetch);
|
|
580
780
|
cacheRequest.set(requestId, promise);
|
|
581
781
|
promise.then(() => {
|
|
582
782
|
cacheRequest.delete(requestId);
|
|
@@ -584,10 +784,11 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
584
784
|
}
|
|
585
785
|
|
|
586
786
|
const items = await promise;
|
|
787
|
+
const cacheItem = this.data.cacheItem;
|
|
587
788
|
|
|
588
789
|
for (const item of items) {
|
|
589
790
|
if (item) {
|
|
590
|
-
|
|
791
|
+
cacheItem.set(item.id, item);
|
|
591
792
|
}
|
|
592
793
|
}
|
|
593
794
|
}
|
|
@@ -598,9 +799,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
598
799
|
public selectItem(id: OptionId, selected?: boolean, keepOpen = false) {
|
|
599
800
|
const state = this.state;
|
|
600
801
|
let hasChanged = false;
|
|
802
|
+
const isPartial = unref(this.isPartial);
|
|
601
803
|
|
|
602
804
|
/* Check that item is not disabled */
|
|
603
|
-
if (!
|
|
805
|
+
if (!isPartial) {
|
|
604
806
|
const item = state.allOptions.find((opt) => opt.id === id);
|
|
605
807
|
if (item && item.disabled) {
|
|
606
808
|
return;
|
|
@@ -659,6 +861,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
659
861
|
return;
|
|
660
862
|
}
|
|
661
863
|
|
|
864
|
+
if (keepOpen) {
|
|
865
|
+
/* if keepOpen is true it means that it is an automatic change */
|
|
866
|
+
this.setAutomaticChange();
|
|
867
|
+
}
|
|
662
868
|
this.commit('internalValue', id);
|
|
663
869
|
hasChanged = true;
|
|
664
870
|
}
|
|
@@ -672,15 +878,18 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
672
878
|
if (!this.state.multiple) {
|
|
673
879
|
return;
|
|
674
880
|
}
|
|
881
|
+
const hasAllItems = unref(this.hasAllItems);
|
|
882
|
+
|
|
883
|
+
if (!hasAllItems) {
|
|
884
|
+
const labels = this.data.labels;
|
|
675
885
|
|
|
676
|
-
if (!this.hasAllItems) {
|
|
677
886
|
if (this.state.searchText) {
|
|
678
|
-
this.state.status.errorMessage =
|
|
887
|
+
this.state.status.errorMessage = labels.cannotSelectAllSearchedItems;
|
|
679
888
|
return;
|
|
680
889
|
}
|
|
681
890
|
|
|
682
891
|
if (!this.state.allowRevert) {
|
|
683
|
-
this.state.status.errorMessage =
|
|
892
|
+
this.state.status.errorMessage = labels.cannotSelectAllRevertItems;
|
|
684
893
|
return;
|
|
685
894
|
}
|
|
686
895
|
|
|
@@ -695,9 +904,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
695
904
|
|
|
696
905
|
const selectAll = !this.state.status.areAllSelected;
|
|
697
906
|
this.state.status.areAllSelected = selectAll;
|
|
698
|
-
this.doNotUpdate = true;
|
|
907
|
+
this.data.doNotUpdate = true;
|
|
699
908
|
this.state.filteredOptions.forEach((item) => this.selectItem(item.id, selectAll));
|
|
700
|
-
this.doNotUpdate = false;
|
|
909
|
+
this.data.doNotUpdate = false;
|
|
701
910
|
this.updateFilteredOptions();
|
|
702
911
|
}
|
|
703
912
|
|
|
@@ -710,9 +919,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
710
919
|
}
|
|
711
920
|
|
|
712
921
|
public clearCache(forceReset = false) {
|
|
713
|
-
const
|
|
922
|
+
const isPartial = unref(this.isPartial);
|
|
923
|
+
const total = isPartial ? Infinity : 0;
|
|
714
924
|
|
|
715
|
-
this.cacheItem.clear();
|
|
925
|
+
this.data.cacheItem.clear();
|
|
716
926
|
|
|
717
927
|
this.state.allOptions = [];
|
|
718
928
|
this.state.totalAllOptions = total;
|
|
@@ -746,7 +956,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
746
956
|
}
|
|
747
957
|
|
|
748
958
|
public changeTexts(texts: PartialMessages) {
|
|
749
|
-
this.labels = Object.assign({}, this.labels, texts);
|
|
959
|
+
this.data.labels = Object.assign({}, this.data.labels, texts);
|
|
750
960
|
}
|
|
751
961
|
|
|
752
962
|
/* }}} */
|
|
@@ -767,25 +977,53 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
767
977
|
|
|
768
978
|
return this.state.filteredOptions.find(findId) ||
|
|
769
979
|
this.state.dynOptions.find(findId) ||
|
|
770
|
-
this.
|
|
771
|
-
this.
|
|
980
|
+
unref(this.listOptions).find(findId) ||
|
|
981
|
+
unref(this.elementOptions).find(findId);
|
|
772
982
|
}
|
|
773
983
|
|
|
774
|
-
private
|
|
984
|
+
private convertTypeValue(oldValue: OptionId | StrictOptionId[]) {
|
|
775
985
|
const state = this.state;
|
|
986
|
+
const isMultiple = state.multiple;
|
|
987
|
+
let newValue = oldValue;
|
|
988
|
+
|
|
989
|
+
if (isMultiple) {
|
|
990
|
+
if (!Array.isArray(oldValue)) {
|
|
991
|
+
newValue = oldValue === null ? [] : [oldValue];
|
|
992
|
+
}
|
|
993
|
+
} else {
|
|
994
|
+
if (Array.isArray(oldValue)) {
|
|
995
|
+
const value = oldValue[0];
|
|
996
|
+
newValue = typeof value === 'undefined' ? null : value;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return newValue;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
private assertValueType() {
|
|
1003
|
+
const state = this.state;
|
|
1004
|
+
const internalValue = state.internalValue;
|
|
1005
|
+
const newValue = this.convertTypeValue(internalValue);
|
|
1006
|
+
|
|
1007
|
+
if (newValue !== internalValue) {
|
|
1008
|
+
this.setAutomaticChange();
|
|
1009
|
+
state.internalValue = newValue;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
private assertCorrectValue(applyStrict = false) {
|
|
1014
|
+
const state = this.state;
|
|
1015
|
+
this.assertValueType();
|
|
776
1016
|
const internalValue = state.internalValue;
|
|
777
1017
|
const selectionIsExcluded = state.selectionIsExcluded;
|
|
778
1018
|
const isMultiple = state.multiple;
|
|
779
1019
|
const checkStrict = state.strictValue;
|
|
780
1020
|
let newValue = internalValue;
|
|
781
|
-
const isPartial = this.isPartial;
|
|
1021
|
+
const isPartial = unref(this.isPartial);
|
|
782
1022
|
|
|
783
1023
|
if (isMultiple) {
|
|
784
|
-
|
|
785
|
-
newValue = internalValue === null ? [] : [internalValue];
|
|
786
|
-
}
|
|
1024
|
+
const hasFetchedAllItems = unref(this.hasFetchedAllItems);
|
|
787
1025
|
|
|
788
|
-
if (selectionIsExcluded &&
|
|
1026
|
+
if (selectionIsExcluded && hasFetchedAllItems) {
|
|
789
1027
|
newValue = state.allOptions.reduce((values, option) => {
|
|
790
1028
|
const id = option.id as StrictOptionId;
|
|
791
1029
|
|
|
@@ -798,11 +1036,6 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
798
1036
|
state.selectionIsExcluded = false;
|
|
799
1037
|
}
|
|
800
1038
|
} else {
|
|
801
|
-
if (Array.isArray(internalValue)) {
|
|
802
|
-
const value = internalValue[0];
|
|
803
|
-
newValue = typeof value === 'undefined' ? null : value;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
1039
|
state.selectionIsExcluded = false;
|
|
807
1040
|
}
|
|
808
1041
|
|
|
@@ -815,22 +1048,25 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
815
1048
|
.filter((value) => this.hasItemInStore(value));
|
|
816
1049
|
isDifferent = filteredValue.length !== (newValue as StrictOptionId[]).length;
|
|
817
1050
|
|
|
818
|
-
if (isDifferent && isPartial && !
|
|
819
|
-
this.getItems(newValue as StrictOptionId[])
|
|
1051
|
+
if (isDifferent && isPartial && !applyStrict) {
|
|
1052
|
+
this.getItems(newValue as StrictOptionId[])
|
|
1053
|
+
.then(() => this.assertCorrectValue(true));
|
|
820
1054
|
return;
|
|
821
1055
|
}
|
|
822
1056
|
} else
|
|
823
|
-
if (!this.hasItemInStore(newValue as OptionId)) {
|
|
1057
|
+
if (newValue !== null && !this.hasItemInStore(newValue as OptionId)) {
|
|
824
1058
|
filteredValue = null;
|
|
825
1059
|
isDifferent = true;
|
|
826
1060
|
|
|
827
|
-
if (isPartial && !
|
|
828
|
-
this.getItems([newValue as OptionId])
|
|
1061
|
+
if (isPartial && !applyStrict) {
|
|
1062
|
+
this.getItems([newValue as OptionId])
|
|
1063
|
+
.then(() => this.assertCorrectValue(true));
|
|
829
1064
|
return;
|
|
830
1065
|
}
|
|
831
1066
|
}
|
|
832
1067
|
|
|
833
1068
|
if (isDifferent) {
|
|
1069
|
+
this.setAutomaticChange();
|
|
834
1070
|
newValue = filteredValue!;
|
|
835
1071
|
}
|
|
836
1072
|
}
|
|
@@ -843,8 +1079,9 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
843
1079
|
}
|
|
844
1080
|
|
|
845
1081
|
private updateFilteredOptions() {
|
|
846
|
-
if (!this.doNotUpdate) {
|
|
1082
|
+
if (!this.data.doNotUpdate) {
|
|
847
1083
|
this.state.filteredOptions = this.buildItems(this.state.filteredOptions);
|
|
1084
|
+
this.buildSelectedOptions();
|
|
848
1085
|
}
|
|
849
1086
|
}
|
|
850
1087
|
|
|
@@ -854,14 +1091,15 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
854
1091
|
});
|
|
855
1092
|
}
|
|
856
1093
|
|
|
857
|
-
/*
|
|
1094
|
+
/* This method is for the computed property listOptions */
|
|
858
1095
|
private getListOptions(): OptionValue[] {
|
|
859
|
-
const options = this.options;
|
|
1096
|
+
const options = this.props.options;
|
|
860
1097
|
const listOptions: OptionValue[] = [];
|
|
861
1098
|
|
|
862
1099
|
if (!Array.isArray(options)) {
|
|
863
1100
|
return listOptions;
|
|
864
1101
|
}
|
|
1102
|
+
const state = this.state;
|
|
865
1103
|
|
|
866
1104
|
options.forEach((option) => {
|
|
867
1105
|
/* manage simple string */
|
|
@@ -877,14 +1115,14 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
877
1115
|
const subOptions = option.options;
|
|
878
1116
|
|
|
879
1117
|
/* check for groups */
|
|
880
|
-
if (group && !
|
|
881
|
-
|
|
1118
|
+
if (group && !state.groups.has(group)) {
|
|
1119
|
+
state.groups.set(group, String(group));
|
|
882
1120
|
}
|
|
883
1121
|
|
|
884
1122
|
/* check for sub options */
|
|
885
1123
|
if (subOptions) {
|
|
886
1124
|
const groupId = option.id as StrictOptionId;
|
|
887
|
-
|
|
1125
|
+
state.groups.set(groupId, option.text);
|
|
888
1126
|
|
|
889
1127
|
subOptions.forEach((subOpt) => {
|
|
890
1128
|
subOpt.group = groupId;
|
|
@@ -899,28 +1137,29 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
899
1137
|
return listOptions;
|
|
900
1138
|
}
|
|
901
1139
|
|
|
902
|
-
/*
|
|
1140
|
+
/* This method is for the computed property elementOptions */
|
|
903
1141
|
private getElementOptions(): OptionValue[] {
|
|
904
|
-
const options = this.childOptions;
|
|
1142
|
+
const options = this.props.childOptions;
|
|
905
1143
|
const childOptions: OptionValue[] = [];
|
|
906
1144
|
|
|
907
|
-
if (!Array.isArray(options)) {
|
|
1145
|
+
if (!Array.isArray(options) || options.length === 0) {
|
|
908
1146
|
return childOptions;
|
|
909
1147
|
}
|
|
1148
|
+
const state = this.state;
|
|
910
1149
|
|
|
911
1150
|
options.forEach((option) => {
|
|
912
1151
|
const group = option.group;
|
|
913
1152
|
const subOptions = option.options;
|
|
914
1153
|
|
|
915
1154
|
/* check for groups */
|
|
916
|
-
if (group && !
|
|
917
|
-
|
|
1155
|
+
if (group && !state.groups.has(group)) {
|
|
1156
|
+
state.groups.set(group, String(group));
|
|
918
1157
|
}
|
|
919
1158
|
|
|
920
1159
|
/* check for sub options */
|
|
921
1160
|
if (subOptions) {
|
|
922
1161
|
const groupId = option.id as StrictOptionId;
|
|
923
|
-
|
|
1162
|
+
state.groups.set(groupId, option.text);
|
|
924
1163
|
|
|
925
1164
|
const sOpts: OptionValue[] = subOptions.map((subOpt) => {
|
|
926
1165
|
return Object.assign({}, subOpt, {
|
|
@@ -943,6 +1182,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
943
1182
|
let elementOptions: OptionValue[] = [];
|
|
944
1183
|
const optionBehaviorOrder = this.state.optionBehaviorOrder;
|
|
945
1184
|
let length: number = Infinity;
|
|
1185
|
+
const isPartial = unref(this.isPartial);
|
|
946
1186
|
|
|
947
1187
|
const arrayFromOrder = (orderValue: OptionBehaviorOrder): OptionValue[] => {
|
|
948
1188
|
switch(orderValue) {
|
|
@@ -963,7 +1203,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
963
1203
|
};
|
|
964
1204
|
|
|
965
1205
|
if (!keepFetched) {
|
|
966
|
-
if (
|
|
1206
|
+
if (isPartial) {
|
|
967
1207
|
this.state.totalAllOptions = Infinity;
|
|
968
1208
|
this.state.totalDynOptions = Infinity;
|
|
969
1209
|
} else {
|
|
@@ -971,15 +1211,15 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
971
1211
|
}
|
|
972
1212
|
}
|
|
973
1213
|
|
|
974
|
-
listOptions = this.
|
|
975
|
-
elementOptions = this.
|
|
1214
|
+
listOptions = unref(this.listOptions);
|
|
1215
|
+
elementOptions = unref(this.elementOptions);
|
|
976
1216
|
|
|
977
1217
|
if (this.state.optionBehaviorOperation === 'force') {
|
|
978
1218
|
const orderValue = optionBehaviorOrder.find((value) => lengthFromOrder(value) > 0)!;
|
|
979
1219
|
allOptions.push(...arrayFromOrder(orderValue));
|
|
980
1220
|
length = lengthFromOrder(orderValue);
|
|
981
|
-
this.activeOrder = orderValue;
|
|
982
|
-
this.dynOffset = 0;
|
|
1221
|
+
this.data.activeOrder = orderValue;
|
|
1222
|
+
this.data.dynOffset = 0;
|
|
983
1223
|
} else {
|
|
984
1224
|
/* sort */
|
|
985
1225
|
let offset = 0;
|
|
@@ -988,7 +1228,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
988
1228
|
const lngth = lengthFromOrder(orderValue);
|
|
989
1229
|
|
|
990
1230
|
if (orderValue === 'D') {
|
|
991
|
-
this.dynOffset = offset;
|
|
1231
|
+
this.data.dynOffset = offset;
|
|
992
1232
|
} else {
|
|
993
1233
|
offset += lngth;
|
|
994
1234
|
}
|
|
@@ -999,7 +1239,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
999
1239
|
break;
|
|
1000
1240
|
}
|
|
1001
1241
|
}
|
|
1002
|
-
this.activeOrder = 'D';
|
|
1242
|
+
this.data.activeOrder = 'D';
|
|
1003
1243
|
length = optionBehaviorOrder.reduce((total, orderValue) => total + lengthFromOrder(orderValue), 0);
|
|
1004
1244
|
}
|
|
1005
1245
|
|
|
@@ -1008,7 +1248,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1008
1248
|
if (keepFetched) {
|
|
1009
1249
|
this.state.totalAllOptions = length;
|
|
1010
1250
|
} else {
|
|
1011
|
-
if (!
|
|
1251
|
+
if (!isPartial) {
|
|
1012
1252
|
this.state.totalAllOptions = allOptions.length;
|
|
1013
1253
|
}
|
|
1014
1254
|
}
|
|
@@ -1016,7 +1256,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1016
1256
|
this.state.filteredOptions = [];
|
|
1017
1257
|
this.state.totalFilteredOptions = Infinity;
|
|
1018
1258
|
|
|
1019
|
-
this.buildFilteredOptions()
|
|
1259
|
+
this.buildFilteredOptions().then(() => {
|
|
1260
|
+
/* XXX: To recompute for strict mode and auto-select */
|
|
1261
|
+
this.assertCorrectValue();
|
|
1262
|
+
});
|
|
1020
1263
|
}
|
|
1021
1264
|
|
|
1022
1265
|
private async buildFilteredOptions() {
|
|
@@ -1030,14 +1273,16 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1030
1273
|
const totalAllOptions = this.state.totalAllOptions;
|
|
1031
1274
|
const allOptionsLength = allOptions.length;
|
|
1032
1275
|
let filteredOptionsLength = this.state.filteredOptions.length;
|
|
1276
|
+
const hasAllItems = unref(this.hasAllItems);
|
|
1033
1277
|
|
|
1034
|
-
if (
|
|
1278
|
+
if (hasAllItems) {
|
|
1035
1279
|
/* Everything has already been fetched and stored in filteredOptions */
|
|
1036
1280
|
return;
|
|
1037
1281
|
}
|
|
1038
1282
|
|
|
1283
|
+
const hasFetchedAllItems = unref(this.hasFetchedAllItems);
|
|
1039
1284
|
/* Check if all options have been fetched */
|
|
1040
|
-
if (
|
|
1285
|
+
if (hasFetchedAllItems) {
|
|
1041
1286
|
if (!search) {
|
|
1042
1287
|
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
1043
1288
|
this.state.totalFilteredOptions = this.state.filteredOptions.length;
|
|
@@ -1053,7 +1298,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1053
1298
|
/* When we only have partial options */
|
|
1054
1299
|
|
|
1055
1300
|
const offsetItem = this.state.offsetItem;
|
|
1056
|
-
const marginSize = this.marginSize;
|
|
1301
|
+
const marginSize = unref(this.marginSize);
|
|
1057
1302
|
const endIndex = offsetItem + marginSize;
|
|
1058
1303
|
|
|
1059
1304
|
if (endIndex <= filteredOptionsLength) {
|
|
@@ -1063,7 +1308,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1063
1308
|
if (!search && endIndex <= allOptionsLength) {
|
|
1064
1309
|
this.state.filteredOptions = this.buildGroupItems(allOptions);
|
|
1065
1310
|
this.state.totalFilteredOptions = totalAllOptions + this.state.groups.size;
|
|
1066
|
-
|
|
1311
|
+
const isPartial = unref(this.isPartial);
|
|
1312
|
+
if (isPartial && this.state.totalDynOptions === Infinity) {
|
|
1067
1313
|
this.fetchData();
|
|
1068
1314
|
}
|
|
1069
1315
|
return;
|
|
@@ -1073,7 +1319,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1073
1319
|
this.addStaticFilteredOptions();
|
|
1074
1320
|
|
|
1075
1321
|
filteredOptionsLength = this.state.filteredOptions.length;
|
|
1076
|
-
this.dynOffset = filteredOptionsLength;
|
|
1322
|
+
this.data.dynOffset = filteredOptionsLength;
|
|
1077
1323
|
if (endIndex <= filteredOptionsLength) {
|
|
1078
1324
|
return;
|
|
1079
1325
|
}
|
|
@@ -1084,66 +1330,71 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1084
1330
|
|
|
1085
1331
|
private async buildSelectedOptions() {
|
|
1086
1332
|
const internalValue = this.state.internalValue;
|
|
1333
|
+
const state = this.state;
|
|
1087
1334
|
|
|
1088
|
-
if (
|
|
1335
|
+
if (state.multiple) {
|
|
1089
1336
|
/* display partial information about selected items */
|
|
1090
|
-
|
|
1337
|
+
state.selectedOptions = this.buildSelectedItems(internalValue as StrictOptionId[]);
|
|
1091
1338
|
|
|
1092
1339
|
const items: OptionItem[] = await this.getItems(internalValue as StrictOptionId[]).catch(() => []);
|
|
1093
|
-
if (internalValue !==
|
|
1340
|
+
if (internalValue !== state.internalValue) {
|
|
1094
1341
|
/* Values have been deprecated */
|
|
1095
1342
|
return;
|
|
1096
1343
|
}
|
|
1097
1344
|
|
|
1098
1345
|
if (items.length !== (internalValue as StrictOptionId[]).length) {
|
|
1099
|
-
if (!
|
|
1100
|
-
const updatedItems =
|
|
1346
|
+
if (!state.strictValue) {
|
|
1347
|
+
const updatedItems = state.selectedOptions.map((option) => {
|
|
1101
1348
|
const foundItem = items.find((item) => item.id === option.id);
|
|
1102
1349
|
|
|
1103
1350
|
return foundItem || option;
|
|
1104
1351
|
});
|
|
1105
1352
|
|
|
1106
|
-
|
|
1353
|
+
state.selectedOptions = updatedItems;
|
|
1107
1354
|
} else {
|
|
1108
1355
|
const itemIds = items.map((item) => item.id as StrictOptionId) ;
|
|
1109
1356
|
|
|
1357
|
+
this.setAutomaticChange();
|
|
1110
1358
|
this.commit('internalValue', itemIds);
|
|
1111
1359
|
}
|
|
1112
1360
|
return;
|
|
1113
1361
|
}
|
|
1114
1362
|
|
|
1115
1363
|
/* display full information about selected items */
|
|
1116
|
-
|
|
1364
|
+
state.selectedOptions = items;
|
|
1117
1365
|
} else
|
|
1118
1366
|
if (internalValue === null) {
|
|
1119
|
-
|
|
1367
|
+
state.selectedOptions = null;
|
|
1120
1368
|
} else {
|
|
1121
1369
|
/* display partial information about selected items */
|
|
1122
|
-
|
|
1370
|
+
state.selectedOptions = this.buildSelectedItems([internalValue as OptionId])[0];
|
|
1123
1371
|
|
|
1124
1372
|
const items = await this.getItems([internalValue as OptionId]).catch(() => []);
|
|
1125
|
-
if (internalValue !==
|
|
1373
|
+
if (internalValue !== state.internalValue) {
|
|
1126
1374
|
/* Values have been deprecated */
|
|
1127
1375
|
return;
|
|
1128
1376
|
}
|
|
1129
1377
|
|
|
1130
1378
|
if (!items.length) {
|
|
1131
|
-
if (
|
|
1379
|
+
if (state.strictValue) {
|
|
1380
|
+
this.setAutomaticChange();
|
|
1132
1381
|
this.commit('internalValue', null);
|
|
1133
1382
|
}
|
|
1134
1383
|
return;
|
|
1135
1384
|
}
|
|
1136
1385
|
|
|
1137
1386
|
/* display full information about selected items */
|
|
1138
|
-
|
|
1387
|
+
state.selectedOptions = items[0];
|
|
1139
1388
|
}
|
|
1140
1389
|
}
|
|
1141
1390
|
|
|
1142
1391
|
private async fetchData() {
|
|
1143
1392
|
const state = this.state;
|
|
1393
|
+
const labels = this.data.labels;
|
|
1394
|
+
const fetchCallback = this.props.fetchCallback;
|
|
1144
1395
|
|
|
1145
|
-
if (!
|
|
1146
|
-
state.status.errorMessage =
|
|
1396
|
+
if (!fetchCallback) {
|
|
1397
|
+
state.status.errorMessage = labels.noFetchMethod;
|
|
1147
1398
|
return;
|
|
1148
1399
|
}
|
|
1149
1400
|
|
|
@@ -1151,25 +1402,26 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1151
1402
|
const filteredOptionsLength = state.filteredOptions.length;
|
|
1152
1403
|
const offsetItem = state.offsetItem;
|
|
1153
1404
|
const pageSize = state.pageSize;
|
|
1154
|
-
const marginSize = this.marginSize;
|
|
1405
|
+
const marginSize = unref(this.marginSize);
|
|
1155
1406
|
const endIndex = offsetItem + marginSize;
|
|
1407
|
+
const dynOffset = this.data.dynOffset;
|
|
1156
1408
|
|
|
1157
1409
|
/* Run the query */
|
|
1158
1410
|
this.state.status.searching = true;
|
|
1159
1411
|
|
|
1160
1412
|
/* Manage cases where offsetItem is not equal to the last item received */
|
|
1161
|
-
const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) -
|
|
1413
|
+
const offset = filteredOptionsLength - this.nbGroups(state.filteredOptions) - dynOffset;
|
|
1162
1414
|
const nbItems = endIndex - offset;
|
|
1163
1415
|
const limit = Math.ceil(nbItems / pageSize) * pageSize;
|
|
1164
1416
|
|
|
1165
1417
|
try {
|
|
1166
1418
|
const requestId = ++this.requestId;
|
|
1167
|
-
const {total: rTotal, result} = await
|
|
1419
|
+
const {total: rTotal, result} = await fetchCallback(search, offset, limit);
|
|
1168
1420
|
let total = rTotal;
|
|
1169
1421
|
|
|
1170
1422
|
/* Assert result is correctly formatted */
|
|
1171
1423
|
if (!Array.isArray(result)) {
|
|
1172
|
-
throw new Error(
|
|
1424
|
+
throw new Error(labels.wrongFormattedData);
|
|
1173
1425
|
}
|
|
1174
1426
|
|
|
1175
1427
|
/* Handle case where total is not returned */
|
|
@@ -1186,7 +1438,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1186
1438
|
/* update cache */
|
|
1187
1439
|
state.totalDynOptions = total;
|
|
1188
1440
|
state.dynOptions.splice(offset, limit, ...result);
|
|
1189
|
-
|
|
1441
|
+
setTimeout(() => this.buildAllOptions(true), 0);
|
|
1190
1442
|
}
|
|
1191
1443
|
|
|
1192
1444
|
/* Check request is not obsolete */
|
|
@@ -1201,7 +1453,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1201
1453
|
const options = this.buildGroupItems(result, previousItem);
|
|
1202
1454
|
const nbGroups1 = this.nbGroups(options);
|
|
1203
1455
|
|
|
1204
|
-
state.filteredOptions.splice(offset +
|
|
1456
|
+
state.filteredOptions.splice(offset + dynOffset, limit + nbGroups1, ...options);
|
|
1205
1457
|
}
|
|
1206
1458
|
|
|
1207
1459
|
let nbGroups = state.groups.size;
|
|
@@ -1209,7 +1461,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1209
1461
|
nbGroups = this.nbGroups(state.filteredOptions);
|
|
1210
1462
|
}
|
|
1211
1463
|
|
|
1212
|
-
state.totalFilteredOptions = total + nbGroups +
|
|
1464
|
+
state.totalFilteredOptions = total + nbGroups + dynOffset;
|
|
1213
1465
|
|
|
1214
1466
|
if (search && state.totalFilteredOptions <= state.filteredOptions.length) {
|
|
1215
1467
|
this.addStaticFilteredOptions(true);
|
|
@@ -1217,7 +1469,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1217
1469
|
|
|
1218
1470
|
state.status.errorMessage = '';
|
|
1219
1471
|
} catch (e) {
|
|
1220
|
-
state.status.errorMessage = e.message;
|
|
1472
|
+
state.status.errorMessage = (e as Error).message;
|
|
1221
1473
|
if (!search) {
|
|
1222
1474
|
state.totalDynOptions = 0;
|
|
1223
1475
|
this.buildAllOptions(true);
|
|
@@ -1261,10 +1513,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1261
1513
|
|
|
1262
1514
|
switch (order) {
|
|
1263
1515
|
case 'O':
|
|
1264
|
-
options = this.filterOptions(this.
|
|
1516
|
+
options = this.filterOptions(unref(this.listOptions), search);
|
|
1265
1517
|
break;
|
|
1266
1518
|
case 'E':
|
|
1267
|
-
options = this.filterOptions(this.
|
|
1519
|
+
options = this.filterOptions(unref(this.elementOptions), search);
|
|
1268
1520
|
break;
|
|
1269
1521
|
}
|
|
1270
1522
|
this.state.filteredOptions.push(...options);
|
|
@@ -1274,7 +1526,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1274
1526
|
|
|
1275
1527
|
private buildSelectedItems(ids: OptionId[]): OptionItem[] {
|
|
1276
1528
|
return this.buildItems(ids.map((id) => {
|
|
1277
|
-
const
|
|
1529
|
+
const cacheItem = this.data.cacheItem;
|
|
1530
|
+
const item = cacheItem.get(id);
|
|
1278
1531
|
|
|
1279
1532
|
return item || {
|
|
1280
1533
|
id,
|
|
@@ -1284,13 +1537,14 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1284
1537
|
}
|
|
1285
1538
|
|
|
1286
1539
|
private hasItemInStore(id: OptionId): boolean {
|
|
1287
|
-
|
|
1540
|
+
const cacheItem = this.data.cacheItem;
|
|
1541
|
+
let item: OptionValue | undefined = cacheItem.get(id);
|
|
1288
1542
|
|
|
1289
1543
|
if (!item) {
|
|
1290
1544
|
item = this.getValue(id);
|
|
1291
1545
|
|
|
1292
1546
|
if (item) {
|
|
1293
|
-
|
|
1547
|
+
cacheItem.set(item.id, item);
|
|
1294
1548
|
}
|
|
1295
1549
|
}
|
|
1296
1550
|
|
|
@@ -1311,7 +1565,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1311
1565
|
disabled: false,
|
|
1312
1566
|
isGroup: false,
|
|
1313
1567
|
}, option, {
|
|
1314
|
-
|
|
1568
|
+
/* eslint-disable-next-line no-bitwise */
|
|
1315
1569
|
selected: !!(+selected.includes(id) ^ selectionIsExcluded),
|
|
1316
1570
|
});
|
|
1317
1571
|
});
|
|
@@ -1355,7 +1609,8 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1355
1609
|
isValid = isValid && /^[ODE]+$/.test(order);
|
|
1356
1610
|
|
|
1357
1611
|
if (!isValid) {
|
|
1358
|
-
|
|
1612
|
+
const labels = this.data.labels;
|
|
1613
|
+
this.state.status.errorMessage = labels.unknownPropertyValue.replace(/%s/, 'optionBehavior');
|
|
1359
1614
|
operation = 'sort';
|
|
1360
1615
|
orderArray = ['O', 'D', 'E'];
|
|
1361
1616
|
} else {
|
|
@@ -1387,6 +1642,7 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1387
1642
|
for (const option of options) {
|
|
1388
1643
|
if (!option.disabled) {
|
|
1389
1644
|
this.selectItem(option.id, true, true);
|
|
1645
|
+
this.checkAutoDisabled();
|
|
1390
1646
|
return;
|
|
1391
1647
|
}
|
|
1392
1648
|
}
|
|
@@ -1394,9 +1650,11 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1394
1650
|
|
|
1395
1651
|
private checkAutoDisabled() {
|
|
1396
1652
|
const state = this.state;
|
|
1397
|
-
const
|
|
1653
|
+
const isPartial = unref(this.isPartial);
|
|
1654
|
+
const doNotCheck = isPartial || this.props.disabled || !state.autoDisabled;
|
|
1655
|
+
const hasFetchedAllItems = unref(this.hasFetchedAllItems);
|
|
1398
1656
|
|
|
1399
|
-
if (doNotCheck || !
|
|
1657
|
+
if (doNotCheck || !hasFetchedAllItems) {
|
|
1400
1658
|
return;
|
|
1401
1659
|
}
|
|
1402
1660
|
|
|
@@ -1412,7 +1670,10 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1412
1670
|
const hasOnlyOneOption = nb === 1 && hasValidValue && !state.allowClearSelection;
|
|
1413
1671
|
|
|
1414
1672
|
if (hasOnlyOneOption || isEmpty) {
|
|
1415
|
-
|
|
1673
|
+
if (state.isOpen) {
|
|
1674
|
+
this.setAutomaticClose();
|
|
1675
|
+
this.commit('isOpen', false);
|
|
1676
|
+
}
|
|
1416
1677
|
this.commit('disabled', true);
|
|
1417
1678
|
} else {
|
|
1418
1679
|
this.commit('disabled', false);
|
|
@@ -1420,128 +1681,22 @@ export default class SelecticStore extends Vue<Props> {
|
|
|
1420
1681
|
}
|
|
1421
1682
|
|
|
1422
1683
|
private checkHideFilter() {
|
|
1423
|
-
|
|
1684
|
+
const params = this.props.params;
|
|
1685
|
+
if (params && params.hideFilter !== 'auto') {
|
|
1424
1686
|
return;
|
|
1425
1687
|
}
|
|
1426
1688
|
|
|
1427
1689
|
const state = this.state;
|
|
1690
|
+
const isPartial = unref(this.isPartial);
|
|
1428
1691
|
|
|
1429
|
-
if (state.multiple ||
|
|
1692
|
+
if (state.multiple || isPartial) {
|
|
1430
1693
|
state.hideFilter = false;
|
|
1431
1694
|
} else {
|
|
1432
|
-
state.hideFilter = state.totalAllOptions <= this.itemsPerPage;
|
|
1695
|
+
state.hideFilter = state.totalAllOptions <= this.data.itemsPerPage;
|
|
1433
1696
|
}
|
|
1434
1697
|
}
|
|
1435
1698
|
|
|
1436
1699
|
/* }}} */
|
|
1437
1700
|
/* }}} */
|
|
1438
|
-
/* {{{ watch */
|
|
1439
|
-
|
|
1440
|
-
@Watch('options')
|
|
1441
|
-
protected onOptionsChange(options: OptionValue[] = [], oldOptions: OptionValue[] = []) {
|
|
1442
|
-
if (JSON.stringify(options) === JSON.stringify(oldOptions)) {
|
|
1443
|
-
/* There is no real difference, only a change of reference */
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
this.cacheItem.clear();
|
|
1447
|
-
this.commit('isOpen', false);
|
|
1448
|
-
this.buildAllOptions(true);
|
|
1449
|
-
this.assertCorrectValue();
|
|
1450
|
-
this.buildSelectedOptions();
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
@Watch('childOptions')
|
|
1454
|
-
protected onChildOptionsChange(childOptions: OptionValue[] = [], oldChildOptions: OptionValue[] = []) {
|
|
1455
|
-
if (JSON.stringify(childOptions) === JSON.stringify(oldChildOptions)) {
|
|
1456
|
-
/* There is no real difference, only a change of reference */
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
this.cacheItem.clear();
|
|
1460
|
-
this.commit('isOpen', false);
|
|
1461
|
-
this.buildAllOptions(true);
|
|
1462
|
-
this.assertCorrectValue();
|
|
1463
|
-
this.buildSelectedOptions();
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
@Watch('value')
|
|
1467
|
-
protected onValueChange() {
|
|
1468
|
-
const value = typeof this.value === 'undefined' ? null : this.value;
|
|
1469
|
-
this.commit('internalValue', value);
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
@Watch('selectionIsExcluded')
|
|
1473
|
-
protected onSelectionExcludedChange() {
|
|
1474
|
-
this.commit('selectionIsExcluded', this.selectionIsExcluded);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
@Watch('disabled')
|
|
1478
|
-
protected onDisabledChange() {
|
|
1479
|
-
this.commit('disabled', this.disabled);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
@Watch('state.filteredOptions')
|
|
1483
|
-
protected onFilteredChange() {
|
|
1484
|
-
let areAllSelected = false;
|
|
1485
|
-
|
|
1486
|
-
if (this.hasAllItems) {
|
|
1487
|
-
const selectionIsExcluded = +this.state.selectionIsExcluded;
|
|
1488
|
-
// tslint:disable-next-line:no-bitwise
|
|
1489
|
-
areAllSelected = this.state.filteredOptions.every((item) => !!(+item.selected ^ selectionIsExcluded));
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
this.state.status.areAllSelected = areAllSelected;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
@Watch('state.internalValue')
|
|
1496
|
-
protected onInternalValueChange() {
|
|
1497
|
-
this.buildSelectedOptions();
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
@Watch('state.allOptions')
|
|
1501
|
-
protected onAllOptionChange() {
|
|
1502
|
-
this.checkAutoSelect();
|
|
1503
|
-
this.checkAutoDisabled();
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
@Watch('state.totalAllOptions')
|
|
1507
|
-
protected onTotalAllOptionsChange() {
|
|
1508
|
-
this.checkHideFilter();
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
/* }}} */
|
|
1512
|
-
/* {{{ life cycles methods */
|
|
1513
|
-
|
|
1514
|
-
protected created() {
|
|
1515
|
-
const value = typeof this.value === 'undefined' ? null : this.value;
|
|
1516
|
-
|
|
1517
|
-
/* set initial value for non reactive attribute */
|
|
1518
|
-
this.requestId = 0;
|
|
1519
|
-
this.cacheRequest = new Map();
|
|
1520
|
-
|
|
1521
|
-
const stateParam = Object.assign({}, this.params);
|
|
1522
1701
|
|
|
1523
|
-
|
|
1524
|
-
this.buildOptionBehavior(stateParam.optionBehavior, stateParam as SelecticStoreState);
|
|
1525
|
-
delete stateParam.optionBehavior;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
this.state = Object.assign(this.state, stateParam, {
|
|
1529
|
-
internalValue: value,
|
|
1530
|
-
selectionIsExcluded: this.selectionIsExcluded,
|
|
1531
|
-
disabled: this.disabled,
|
|
1532
|
-
});
|
|
1533
|
-
|
|
1534
|
-
this.checkHideFilter();
|
|
1535
|
-
|
|
1536
|
-
if (this.texts) {
|
|
1537
|
-
this.changeTexts(this.texts);
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
this.addGroups(this.groups);
|
|
1541
|
-
this.buildAllOptions();
|
|
1542
|
-
this.assertCorrectValue();
|
|
1543
|
-
this.buildSelectedOptions();
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/* }}} */
|
|
1547
|
-
}
|
|
1702
|
+
};
|