simp-select 1.0.3 → 1.0.4

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.
@@ -1,158 +0,0 @@
1
- import { ISimpleSelectOptions, ISimpleSelectProps } from './types/simpleSelect.types';
2
- import { selectorType } from './types/item.types';
3
-
4
- import {
5
- nameMark, nameSelect, simpleSelectionOptions, simpleSelectLocale,
6
- } from './const/simpleSelection.const';
7
- import { createDataAttr, toCamelCase } from './utils/simpleSelection.utils';
8
- import { SimpleSelectItem } from './simpleSelectItem';
9
- import './style.css';
10
-
11
- export {
12
- simpleSelectionOptions, simpleSelectLocale,
13
- };
14
-
15
- export default class SimpleSelect {
16
- callCount = Date.now();
17
-
18
- countInit = 0;
19
-
20
- // $selects: HTMLSelectElement[] = [];
21
- $selects: SimpleSelectItem[] = [];
22
-
23
- options!: ISimpleSelectOptions;
24
-
25
- nameMarkTransform = toCamelCase(nameMark);
26
-
27
- dataNameMark = createDataAttr(nameMark);
28
-
29
- isNative!: boolean;
30
-
31
- constructor(selector: selectorType, options?: ISimpleSelectProps) {
32
- if (!selector) {
33
- selector = 'select';
34
- }
35
- // this.$selects = Array.from(document.querySelectorAll(selector));
36
-
37
- this.options = {
38
- ...simpleSelectionOptions,
39
- ...options,
40
- };
41
-
42
- if (typeof selector === 'string') {
43
- this.init(Array.from(document.querySelectorAll(selector)));
44
- } else if (selector instanceof HTMLSelectElement) {
45
- this.init([selector]);
46
- } else if (selector instanceof NodeList) {
47
- this.init(Array.from(selector));
48
- } else if (Array.isArray(selector)) {
49
- this.init(selector);
50
- } else {
51
- console.warn('Wrong selector: ', selector);
52
- }
53
- }
54
-
55
- detectMobile() {
56
- if (this.options.detectNative) {
57
- this.isNative = this.options.detectNative();
58
- return;
59
- }
60
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
61
- // @ts-ignore
62
- const ua = navigator.userAgent || navigator.vendor || window.opera;
63
-
64
- let res = false;
65
- // Checks for iOs, Android, Blackberry, Opera Mini, and Windows mobile devices
66
- for (let i = 0; i < this.options.nativeOnDevice.length; i++) {
67
- if (ua.toString().toLowerCase().indexOf(this.options.nativeOnDevice[i].toLowerCase()) > 0) {
68
- if (this.options.nativeOnDevice[i]) {
69
- res = true;
70
- }
71
- }
72
- }
73
- this.isNative = res;
74
- }
75
-
76
- private init(selects: HTMLSelectElement[]) {
77
- this.detectMobile();
78
- selects.forEach(($select) => {
79
- this.build($select);
80
- });
81
- }
82
-
83
- createMethods(select: SimpleSelectItem) {
84
- const self = this;
85
- return {
86
- getInstance: () => select.getSelect(),
87
- reload() {
88
- self.rebuild(select);
89
- },
90
- update() {
91
- select.updateHTML();
92
- },
93
- detach() {
94
- self.detach(select);
95
- },
96
- };
97
- }
98
-
99
- setMethods(select: SimpleSelectItem) {
100
- // @ts-ignore
101
- select.$select[nameSelect] = this.createMethods(select);
102
- }
103
-
104
- setMethodsClear(select: SimpleSelectItem) {
105
- // @ts-ignore
106
- delete select.$select[nameSelect];
107
- }
108
-
109
- private build(select: HTMLSelectElement) {
110
- const isProcessed = this.nameMarkTransform in select.dataset;
111
- if (isProcessed) {
112
- console.warn('This element has already been initialized', select);
113
- return;
114
- }
115
-
116
- this.countInit += 1;
117
- const id = `${this.callCount}-${this.countInit}`;
118
- select.setAttribute(this.dataNameMark, id);
119
- // this.$selects.push(select);
120
-
121
- const newSelect = new SimpleSelectItem(select, this.options, {
122
- id, isNative: this.isNative,
123
- });
124
- this.$selects.push(newSelect);
125
- this.setMethods(newSelect);
126
- }
127
-
128
- private detach(itemSelect: SimpleSelectItem) {
129
- itemSelect.detachItem();
130
-
131
- itemSelect.$select.removeAttribute(this.dataNameMark);
132
- this.setMethodsClear(itemSelect);
133
- this.$selects = this.$selects.filter((item) => item !== itemSelect);
134
- }
135
-
136
- public rebuild(selectsItems: SimpleSelectItem) {
137
- const select = selectsItems.$select;
138
- this.detach(selectsItems);
139
- this.build(select);
140
- }
141
-
142
- public getSelects() {
143
- return this.$selects;
144
- }
145
-
146
- public getSelectFirst() {
147
- // return this.$selects[0];
148
- return this.createMethods(this.$selects[0]);
149
- }
150
-
151
- public getSelectById(id:string) {
152
- const search = this.$selects.filter((item) => item.id === id)[0];
153
- if (!search) {
154
- return null;
155
- }
156
- return this.createMethods(search);
157
- }
158
- }
@@ -1,535 +0,0 @@
1
- import { IItemLocalOptions, ISimpleSelectOptions } from './types/simpleSelect.types';
2
- import { IOptionItems } from './types/item.types';
3
- import {
4
- cloneObj,
5
- compareObj,
6
- getCreateListItem,
7
- toCamelCase,
8
- triggerInputEvent,
9
- } from './utils/simpleSelection.utils';
10
- import { SimpleSelectItemDOM } from './simpleSelectItemDOM';
11
-
12
- export class SimpleSelectItem extends SimpleSelectItemDOM {
13
- closeOutsideHandler!: (e:MouseEvent) => void; // not native
14
-
15
- closeEscHandler!: (e:KeyboardEvent) => void; // not native
16
-
17
- changeListener!: (e:Event) => void; // not native
18
-
19
- searchHandler!: (e:Event) => void; // not native
20
-
21
- clickToggleOpen!: (e:MouseEvent | KeyboardEvent) => void; // not native
22
-
23
- triggerSetup!: (e: MouseEvent) => void; // not native
24
-
25
- confirmOkHandler!: (e: MouseEvent) => void; // not native
26
-
27
- confirmNoHandler!: (e: MouseEvent) => void; // not native
28
-
29
- selectAllHandler!: (e: MouseEvent) => void; // not native
30
-
31
- resetAllHandler!: (e: MouseEvent) => void; // not native
32
-
33
- closeHandler!: (e: MouseEvent) => void; // not native
34
-
35
- handleResize!: (e: MediaQueryList | null) => void; // not native
36
-
37
- mql: MediaQueryList | null = null;
38
-
39
- countOpen = 0;
40
-
41
- multiDebounceTime = 0;
42
-
43
- timeoutDebounceId: NodeJS.Timeout | null = null;
44
-
45
- constructor(select: HTMLSelectElement, options: ISimpleSelectOptions, localOptions: IItemLocalOptions) {
46
- super(select, options, localOptions);
47
-
48
- if (!select) {
49
- throw Error('Select is required');
50
- }
51
- this.init();
52
- super.initDom();
53
- this.initAfterDom();
54
- }
55
-
56
- init() {
57
- this.changeListener = this.changeListenerInit.bind(this);
58
- this.$select.addEventListener('change', this.changeListener);
59
-
60
- this.searchHandler = this.searchHandlerInit.bind(this);
61
- this.closeOutsideHandler = this.closeOutsideHandlerInit.bind(this);
62
- this.closeEscHandler = this.closeEscHandlerInit.bind(this);
63
- this.clickToggleOpen = this.clickToggleOpenInit.bind(this);
64
-
65
- this.triggerSetup = this.triggerSetupInit.bind(this);
66
-
67
- this.confirmOkHandler = this.confirmOkHandlerInit.bind(this);
68
- this.confirmNoHandler = this.confirmNoHandlerInit.bind(this);
69
-
70
- this.selectAllHandler = this.selectAllHandlerInit.bind(this);
71
- this.resetAllHandler = this.resetAllHandlerInit.bind(this);
72
-
73
- this.closeHandler = this.closeHandlerInit.bind(this);
74
-
75
- this.handleResize = this.handleResizeInit.bind(this);
76
-
77
- if (this.options.callbackInitialization) {
78
- this.options.callbackInitialization(this);
79
- }
80
-
81
- if (!this.isNative && this.options.floatWidth) {
82
- this.mql = window.matchMedia(`(max-width: ${this.options.floatWidth}px)`);
83
- if (this.mql) {
84
- // @ts-ignore
85
- this.mql.onchange = this.handleResize;
86
- this.handleResizeInit(this.mql);
87
- }
88
- }
89
-
90
- this.state.subscribe('isOpen', (val: IOptionItems[]) => {
91
- this.toggleOpenHandler();
92
- if (!val && this.options.isConfirmInMulti) {
93
- this.createList();
94
- }
95
- if (val) {
96
- if (this.elemInputSearch) {
97
- this.elemInputSearch.value = '';
98
- }
99
- }
100
- // if (!val) {
101
- // if (this.options.isConfirmInMulti) {
102
- // this.triggerInit();
103
- // }
104
- // }
105
- });
106
-
107
- this.state.subscribe('filterStr', (val: string) => {
108
- this.filterList(val);
109
- });
110
-
111
- if (!this.isNative) {
112
- this.elemTopBody.addEventListener('click', this.clickToggleOpen);
113
- this.elemTopBody.addEventListener('keyup', this.clickToggleOpen);
114
- }
115
- }
116
-
117
- private handleResizeInit(e: MediaQueryList | null) {
118
- if (!e) {
119
- return;
120
- }
121
-
122
- if (e.matches) {
123
- this.state.setState('isFloat', true);
124
- } else {
125
- this.state.setState('isFloat', false);
126
- }
127
- }
128
-
129
- private initAfterDom() {
130
- if (this.confirmOk) {
131
- this.confirmOk.addEventListener('click', this.confirmOkHandler);
132
- }
133
- if (this.confirmNo) {
134
- this.confirmNo.addEventListener('click', this.confirmNoHandler);
135
- }
136
-
137
- if (this.options.callbackInitialized) {
138
- this.options.callbackInitialized(this);
139
- }
140
-
141
- if (this.isMulti && !this.options.isConfirmInMulti) {
142
- if (toCamelCase('simple-debounce-time') in this.$select.dataset) {
143
- this.multiDebounceTime = Number(this.$select.dataset[toCamelCase('simple-debounce-time')]);
144
- } else if (this.options.debounceTime || this.options.debounceTime === 0) {
145
- this.multiDebounceTime = this.options.debounceTime;
146
- }
147
- }
148
-
149
- if (this.multiDebounceTime) {
150
- this.multiDebounceChange = this.debounce(this.multiDebounceChange.bind(this), this.multiDebounceTime);
151
- }
152
-
153
- if (this.elemSelectAll) {
154
- this.elemSelectAll.addEventListener('click', this.selectAllHandler);
155
- }
156
- if (this.elemResetAll) {
157
- this.elemResetAll.addEventListener('click', this.resetAllHandler);
158
- }
159
- if (this.elemDropDownClose) {
160
- this.elemDropDownClose.addEventListener('click', this.closeHandler);
161
- }
162
- }
163
-
164
- debounce<T extends (
165
- ...args: never[]) => void>(
166
- func: T,
167
- delay: number,
168
- ): (...args: Parameters<T>) => void {
169
- return (...args: Parameters<T>): void => {
170
- if (this.timeoutDebounceId) {
171
- clearTimeout(this.timeoutDebounceId);
172
- }
173
-
174
- this.timeoutDebounceId = setTimeout(() => {
175
- func(...args);
176
- this.timeoutDebounceId = null;
177
- }, delay);
178
- };
179
- }
180
-
181
- confirmOkHandlerInit(e:MouseEvent) {
182
- e.preventDefault();
183
-
184
- this.confirmOkBuild();
185
- }
186
-
187
- confirmOkBuild() {
188
- const { options } = this.$select;
189
- if (!this.elemListBody) {
190
- return;
191
- }
192
- const liItems: NodeListOf<HTMLLIElement> = this.elemListBody.querySelectorAll('[data-sel-position]');
193
- liItems.forEach((item:HTMLLIElement) => {
194
- const pos = parseInt(item.dataset[toCamelCase('sel-position')]!, 10);
195
- if (!pos && pos !== 0) {
196
- return;
197
- }
198
- const option = options[pos];
199
- if (!option || option.disabled) {
200
- return;
201
- }
202
- option.selected = item.dataset[toCamelCase('sel-opt-checked')] === 'true';
203
- });
204
- this.state.setState('isOpen', false);
205
- this.triggerInit();
206
- }
207
-
208
- confirmNoHandlerInit(e:MouseEvent) {
209
- e.preventDefault();
210
- this.state.setState('isOpen', false);
211
- }
212
-
213
- closeHandlerInit(e:MouseEvent) {
214
- e.preventDefault();
215
- this.state.setState('isOpen', false);
216
- }
217
-
218
- selectAllHandlerInit(e:MouseEvent) {
219
- e.preventDefault();
220
- Array.from(this.$select.options).forEach((option) => {
221
- if (option.disabled) {
222
- return;
223
- }
224
- option.selected = true;
225
- });
226
- this.createList();
227
- if (this.options.selectAllAfterClose) {
228
- this.state.setState('isOpen', false);
229
- }
230
- this.triggerInit();
231
- }
232
-
233
- resetAllHandlerInit(e:MouseEvent) {
234
- e.preventDefault();
235
- Array.from(this.$select.options).forEach((option) => {
236
- if (option.disabled) {
237
- return;
238
- }
239
- option.selected = false;
240
- });
241
- this.createList();
242
- if (this.options.selectAllAfterClose) {
243
- this.state.setState('isOpen', false);
244
- }
245
- this.triggerInit();
246
- }
247
-
248
- // click for LI
249
- triggerSetupInit(e:MouseEvent) {
250
- if (e.button !== 0) return;
251
- const target = e.target as HTMLElement;
252
- const targetLi = target.closest('li');
253
- if (targetLi) {
254
- this.changeClickItem(targetLi);
255
- }
256
- }
257
-
258
- changeClickItem(item: HTMLLIElement) {
259
- if (item) {
260
- const pos = Number(item.dataset[toCamelCase('sel-position')]) || 0;
261
- const option = this.$select.options[pos];
262
- if (option && !option.disabled) {
263
- if (this.isMulti) {
264
- if (this.options.isConfirmInMulti || this.isFloatWidth) {
265
- if (item.dataset[toCamelCase('sel-opt-checked')] === 'true') {
266
- item.dataset[toCamelCase('sel-opt-checked')] = 'false';
267
- item.classList.remove('SimpleSel__list_item--checked');
268
- } else {
269
- item.dataset[toCamelCase('sel-opt-checked')] = 'true';
270
- item.classList.add('SimpleSel__list_item--checked');
271
- }
272
- } else {
273
- option.selected = !option.selected;
274
- this.createList();
275
- this.multiDebounceChange();
276
- }
277
- } else {
278
- option.selected = !option.selected;
279
- this.createList();
280
- this.state.setState('isOpen', false);
281
- this.triggerInit();
282
- }
283
- }
284
- }
285
- }
286
-
287
- multiDebounceChange() {
288
- // can be overridden for multiselect - debounce
289
- this.triggerInit();
290
- }
291
-
292
- triggerInit() {
293
- triggerInputEvent(this.$select);
294
- }
295
-
296
- clickToggleOpenInit(e:MouseEvent | KeyboardEvent) {
297
- e.preventDefault();
298
- if (this.isDisabled) {
299
- return;
300
- }
301
- if (e.type === 'click') {
302
- this.state.setState('isOpen', !this.state.getState('isOpen'));
303
- return;
304
- }
305
- if (e instanceof KeyboardEvent) {
306
- if (e.key === 'Enter') {
307
- this.state.setState('isOpen', !this.state.getState('isOpen'));
308
- }
309
- }
310
- }
311
-
312
- closeOutsideHandlerInit(e: MouseEvent) {
313
- const target: HTMLElement = e.target as HTMLElement;
314
- if (!target) {
315
- return;
316
- }
317
- if (!this.elemWrap.contains(target)) {
318
- if (this.options.isConfirmInMulti && this.options.isConfirmInMultiOkClickOutside) {
319
- this.confirmOkBuild();
320
- }
321
-
322
- this.state.setState('isOpen', false);
323
- }
324
- }
325
-
326
- closeEscHandlerInit(e: KeyboardEvent) {
327
- if (e.code === 'Escape') {
328
- e.preventDefault();
329
- e.stopPropagation();
330
- this.state.setState('isOpen', false);
331
- }
332
- if (e.code === 'Tab') {
333
- e.preventDefault();
334
- e.stopPropagation();
335
-
336
- if (!this.elemWrap.contains(e.target as HTMLElement)) {
337
- this.state.setState('isOpen', false);
338
- }
339
- }
340
- if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
341
- e.preventDefault();
342
- e.stopPropagation();
343
- this.keyBoardChangeChecked(e.key === 'ArrowDown');
344
- }
345
- if (e.key === 'Enter') {
346
- const target = e.target as HTMLLIElement;
347
- if (target && toCamelCase('sel-opt-item') in target.dataset) {
348
- e.preventDefault();
349
- e.stopPropagation();
350
- this.changeClickItem(target);
351
- }
352
- }
353
- }
354
-
355
- keyBoardChangeChecked(isDown: boolean) {
356
- // eslint-disable-next-line max-len
357
- const liItems: NodeListOf<HTMLLIElement> = this.elemListBody!.querySelectorAll('[data-sel-position]:not([data-sel-opt-disabled="true"])');
358
- if (!liItems.length) {
359
- return;
360
- }
361
-
362
- let indCurrent = 0;
363
- let firstOption!: HTMLLIElement;
364
- liItems.forEach((el, i) => {
365
- if (document.activeElement === el) {
366
- indCurrent = i;
367
- firstOption = el;
368
- }
369
- el.removeAttribute('tabindex');
370
- });
371
-
372
- if (!firstOption) {
373
- firstOption = isDown ? liItems[0] : liItems[liItems.length - 1];
374
- } else if (isDown) {
375
- firstOption = liItems[indCurrent + 1] || liItems[0];
376
- } else {
377
- firstOption = liItems[indCurrent - 1] || liItems[liItems.length - 1];
378
- }
379
-
380
- firstOption.tabIndex = 0;
381
- firstOption.focus();
382
- }
383
-
384
- searchHandlerInit(e: Event) {
385
- const target = e.target as HTMLInputElement;
386
- if (!target) {
387
- return;
388
- }
389
- const { value } = target;
390
- this.state.setState('filterStr', value);
391
- }
392
-
393
- toggleOpenHandler() {
394
- const isOpen = this.state.getState('isOpen');
395
-
396
- if (isOpen) {
397
- this.elemWrap.classList.add('SimpleSel--open');
398
- document.addEventListener('click', this.closeOutsideHandler);
399
- document.addEventListener('keyup', this.closeEscHandler);
400
-
401
- if (this.elemInputSearch) {
402
- this.elemInputSearch.focus();
403
- // this.elemInputSearch.tabIndex = 0;
404
- }
405
-
406
- if (this.options.callbackOpen) {
407
- this.options.callbackOpen(this);
408
- }
409
- this.countOpen++;
410
- } else {
411
- this.state.setState('filterList', '');
412
- this.elemWrap.classList.remove('SimpleSel--open');
413
- document.removeEventListener('click', this.closeOutsideHandler);
414
- document.removeEventListener('keyup', this.closeEscHandler);
415
-
416
- if (this.timeoutDebounceId) {
417
- clearTimeout(this.timeoutDebounceId);
418
- this.triggerInit();
419
- }
420
-
421
- if (this.options.callbackClose && this.countOpen > 0) {
422
- this.options.callbackClose(this);
423
- }
424
- }
425
- // this.bodyElement.classList.toggle('SimpleSel--open', this.state.getState('isOpen'))
426
- }
427
-
428
- private changeListenerInit(e: Event) {
429
- if (this.options.callbackChangeSelect) {
430
- this.options.callbackChangeSelect(e, this);
431
- }
432
-
433
- this.createList(true);
434
- // alert(e.target.value);
435
- }
436
-
437
- public getSelect() {
438
- return this.$select;
439
- }
440
-
441
- protected handlerChangeChecked() {
442
- if (this.elemListBody) {
443
- this.elemListBody.addEventListener('mouseup', this.triggerSetup);
444
- // this.elemListBody.addEventListener('mouseup', (e) => {})
445
- }
446
- }
447
-
448
- protected createList(isCompare = false) {
449
- const newItems:IOptionItems[] = [];
450
- const group = this.$select.querySelectorAll('optgroup');
451
- if (group && group.length) {
452
- group.forEach((item, ind) => {
453
- newItems.push(getCreateListItem(item, (ind + 1).toString(), true));
454
- });
455
- } else {
456
- newItems.push(getCreateListItem(this.$select, '1', false));
457
- }
458
-
459
- if (isCompare) {
460
- const old = this.state.getState('items');
461
- if (!compareObj(old, newItems)) {
462
- this.state.setState('items', newItems);
463
- }
464
- } else {
465
- this.state.setState('items', newItems);
466
- }
467
- }
468
-
469
- private filterList(val: string) {
470
- val = val.toLowerCase();
471
- const items:IOptionItems[] = cloneObj(this.state.getState('items'));
472
-
473
- items.forEach((group) => {
474
- let isShowGroup = false;
475
- group.items.forEach((item) => {
476
- if (item.title.toLowerCase().includes(val)) {
477
- isShowGroup = true;
478
- item.isShowFilter = true;
479
- } else {
480
- item.isShowFilter = false;
481
- }
482
- });
483
- group.isShowFilter = isShowGroup;
484
- });
485
- this.state.setState('items', items);
486
- }
487
-
488
- inputSearchHandler() {
489
- if (!this.elemInputSearch) {
490
- return;
491
- }
492
- this.elemInputSearch.addEventListener('input', this.searchHandler);
493
- }
494
-
495
- public detachItem() {
496
- if (this.options.callbackDestroyInit) {
497
- this.options.callbackDestroyInit(this);
498
- }
499
- const parentElement = this.elemWrap.parentNode;
500
- this.$select.removeEventListener('change', this.changeListener);
501
-
502
- if (this.elemInputSearch) {
503
- this.elemInputSearch.removeEventListener('input', this.searchHandler);
504
- }
505
-
506
- if (this.confirmOk) {
507
- this.confirmOk.removeEventListener('click', this.confirmOkHandler);
508
- }
509
- if (this.confirmNo) {
510
- this.confirmNo.removeEventListener('click', this.confirmNoHandler);
511
- }
512
-
513
- parentElement!.replaceChild(this.$select, this.elemWrap);
514
- this.$select.classList.remove(this.classSelectInit);
515
-
516
- if (this.elemSelectAll) {
517
- this.elemSelectAll.removeEventListener('click', this.selectAllHandler);
518
- }
519
- if (this.elemResetAll) {
520
- this.elemResetAll.removeEventListener('click', this.resetAllHandler);
521
- }
522
-
523
- if (this.options.callbackDestroy) {
524
- this.options.callbackDestroy(this);
525
- }
526
-
527
- if (this.elemDropDownClose) {
528
- this.elemDropDownClose.removeEventListener('click', this.closeHandler);
529
- }
530
- if (this.mql) {
531
- this.mql.onchange = null;
532
- this.mql = null;
533
- }
534
- }
535
- }