simp-select 1.0.4 → 1.0.5

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.
@@ -0,0 +1,608 @@
1
+ import { IItemLocalOptions, ISimpleSelectOptions } from './types/simpleSelect.types';
2
+ import {
3
+ cloneObj, createButton, getClass, ifTrueDataAttr, toCamelCase,
4
+ } from './utils/simpleSelection.utils';
5
+ import { ICreateLiReturn, IOptionItem, IOptionItems } from './types/item.types';
6
+ import { store } from './utils/store';
7
+ import { initClass } from './const/simpleSelection.const';
8
+
9
+ interface IState {
10
+ items: IOptionItems[];
11
+ isOpen: boolean;
12
+ isFloat: boolean;
13
+ filterStr: string;
14
+ }
15
+ export class SimpleSelectItemDOM {
16
+ options!: ISimpleSelectOptions;
17
+
18
+ $select!:HTMLSelectElement;
19
+
20
+ id!: string;
21
+
22
+ titlePlaceholder!: string;
23
+
24
+ isDisabled: boolean = false;
25
+
26
+ isMulti!: boolean;
27
+
28
+ state = store<IState>({
29
+ items: [],
30
+ isOpen: false,
31
+ filterStr: '',
32
+ isFloat: false,
33
+ });
34
+
35
+ // classSelectInit= 'SimpleSel__select_init'
36
+ classSelectInit = getClass('select_init');
37
+
38
+ isNative: boolean;
39
+
40
+ elemWrap: HTMLDivElement = document.createElement('div'); // all
41
+
42
+ elemTop: HTMLDivElement = document.createElement('div'); // all
43
+
44
+ elemTopBody: HTMLDivElement = document.createElement('div'); // all
45
+
46
+ elemDropDown: HTMLDivElement | null = null; // not native
47
+
48
+ elemDropDownClose: HTMLButtonElement | null = null; // not native
49
+
50
+ elemListBody: HTMLUListElement | null = null; // not native
51
+
52
+ elemInputSearch: HTMLInputElement | null = null; // not native
53
+
54
+ elemTitle!: HTMLDivElement; // not native
55
+
56
+ confirmOk: HTMLButtonElement | null = null; // not native
57
+
58
+ confirmNo: HTMLButtonElement | null = null; // not native
59
+
60
+ elemControl: HTMLDivElement | null = null; // not native
61
+
62
+ elemSelectAll: HTMLButtonElement | null = null; // not native
63
+
64
+ elemResetAll: HTMLButtonElement | null = null; // not native
65
+
66
+ cloneClasses = '';
67
+
68
+ isShowCheckbox = false;
69
+
70
+ bodyLiHTMLBeforeFromSelect: null | string = null;
71
+
72
+ bodyLiHTMLAfterFromSelect: null | string = null;
73
+
74
+ isFloatWidth = false;
75
+
76
+ bodyOpenClass = `${initClass}--body_open`;
77
+
78
+ constructor(select: HTMLSelectElement, options: ISimpleSelectOptions, localOptions: IItemLocalOptions) {
79
+ const { id, isNative } = localOptions;
80
+ this.$select = select;
81
+ this.isMulti = select.multiple;
82
+ this.id = id;
83
+ this.isNative = isNative;
84
+
85
+ this.options = cloneObj(options);
86
+ if (this.options.isCloneClass) {
87
+ this.cloneClasses = this.$select.className;
88
+ }
89
+ if (options.callbackInitialization) {
90
+ this.options.callbackInitialization = options.callbackInitialization;
91
+ }
92
+ if (options.callbackInitialized) {
93
+ this.options.callbackInitialized = options.callbackInitialized;
94
+ }
95
+ if (options.callbackOpen) {
96
+ this.options.callbackOpen = options.callbackOpen;
97
+ }
98
+ if (options.callbackClose) {
99
+ this.options.callbackClose = options.callbackClose;
100
+ }
101
+ if (options.callbackDestroyInit) {
102
+ this.options.callbackDestroyInit = options.callbackDestroyInit;
103
+ }
104
+ if (options.callbackDestroy) {
105
+ this.options.callbackDestroy = options.callbackDestroy;
106
+ }
107
+ if (options.callbackChangeSelect) {
108
+ this.options.callbackChangeSelect = options.callbackChangeSelect;
109
+ }
110
+ if (options.changeBodyLi) {
111
+ this.options.changeBodyLi = options.changeBodyLi;
112
+ }
113
+
114
+ const dataConfirm = this.$select.dataset[toCamelCase('simple-is-confirm')];
115
+ if (this.isMulti && dataConfirm) {
116
+ this.options.isConfirmInMulti = dataConfirm === '1' || dataConfirm === 'true';
117
+ }
118
+
119
+ this.optionOverride();
120
+
121
+ this.isDisabled = this.$select.disabled;
122
+
123
+ // this.initDom()
124
+ }
125
+
126
+ optionOverride() {
127
+ const dataPlaceholder = toCamelCase('simple-placeholder');
128
+ if (this.$select.dataset[dataPlaceholder]) {
129
+ this.titlePlaceholder = this.$select.dataset[dataPlaceholder] || '';
130
+ } else {
131
+ this.titlePlaceholder = this.options.locale.title;
132
+ }
133
+
134
+ const dataResetAll = toCamelCase('simple-reset-all');
135
+ if (dataResetAll in this.$select.dataset) {
136
+ const resReset = this.$select.dataset[dataResetAll];
137
+ this.options.resetAll = !(resReset === 'false' || resReset === '0');
138
+ }
139
+ // const dataSelect = toCamelCase('simple-select-all');
140
+ // if (dataSelect in this.$select.dataset) {
141
+ // const resSelect = this.$select.dataset[dataSelect];
142
+ // this.options.selectAll = !(resSelect === 'false' || resSelect === '0');
143
+ // }
144
+ if (this.$select.hasAttribute('data-simple-select-all')) {
145
+ const resSelect = this.$select.getAttribute('data-simple-select-all');
146
+ this.options.selectAll = ifTrueDataAttr(resSelect);
147
+ }
148
+
149
+ const isShowCheckboxLocal = this.$select.dataset[toCamelCase('simple-show-checkbox')];
150
+ if (this.isMulti) {
151
+ this.isShowCheckbox = !((isShowCheckboxLocal && !ifTrueDataAttr(isShowCheckboxLocal)));
152
+ } else if (isShowCheckboxLocal === 'true') {
153
+ this.isShowCheckbox = true;
154
+ }
155
+
156
+ const itemHtmlBefore = this.$select.dataset[toCamelCase('simple-item-html-before')];
157
+ if (itemHtmlBefore) {
158
+ this.bodyLiHTMLBeforeFromSelect = itemHtmlBefore;
159
+ }
160
+ const itemHtmlAfter = this.$select.dataset[toCamelCase('simple-item-html-after')];
161
+ if (itemHtmlAfter) {
162
+ this.bodyLiHTMLAfterFromSelect = itemHtmlAfter;
163
+ }
164
+
165
+ if (this.$select.hasAttribute('data-simple-up')) {
166
+ this.options.isUp = ifTrueDataAttr(this.$select.getAttribute('data-simple-up'));
167
+ }
168
+ }
169
+
170
+ initDom() {
171
+ this.createList(false);
172
+ this.createHTML();
173
+
174
+ this.state.subscribe('items', (_val: IOptionItems[]) => {
175
+ this.createListHTML();
176
+ this.createTitleHTML();
177
+ });
178
+ this.state.subscribe('isOpen', (val: boolean) => {
179
+ this.createListHTML();
180
+ this.createTitleHTML();
181
+
182
+ this.toggleTabIndex(val);
183
+ });
184
+ this.state.subscribe('isFloat', (val: boolean) => {
185
+ this.isFloatWidth = val;
186
+ const cls = getClass('float', true);
187
+ this.elemWrap.classList.toggle(cls, val);
188
+ });
189
+ }
190
+
191
+ toggleTabIndex(isOpen: boolean) {
192
+ const tabIndex = isOpen ? 0 : -1;
193
+
194
+ if (this.state.getState('isFloat')) {
195
+ document.body.classList.toggle(this.bodyOpenClass, isOpen);
196
+ }
197
+
198
+ if (this.elemInputSearch) {
199
+ this.elemInputSearch.tabIndex = tabIndex;
200
+ }
201
+ if (this.elemResetAll) {
202
+ this.elemResetAll.tabIndex = tabIndex;
203
+ }
204
+ if (this.elemSelectAll) {
205
+ this.elemSelectAll.tabIndex = tabIndex;
206
+ }
207
+ if (this.confirmOk) {
208
+ this.confirmOk.tabIndex = tabIndex;
209
+ }
210
+ if (this.confirmNo) {
211
+ this.confirmNo.tabIndex = tabIndex;
212
+ }
213
+ }
214
+
215
+ public updateHTML() {
216
+ this.createList(true);
217
+ }
218
+
219
+ private createHTML() {
220
+ this.$select.classList.add(this.classSelectInit);
221
+ this.$select.tabIndex = -1;
222
+
223
+ this.elemTopBody.className = getClass('top_body');
224
+ this.elemTopBody.tabIndex = this.isDisabled ? -1 : 0;
225
+
226
+ this.createIcon();
227
+ this.elemTop.append(this.elemTopBody);
228
+ let resClassesWrap = initClass;
229
+ if (this.options.isCloneClass) {
230
+ resClassesWrap += ` ${this.cloneClasses}`;
231
+ }
232
+ if (this.$select.hasAttribute('data-simple-add-classes')) {
233
+ resClassesWrap += ` ${this.$select.getAttribute('data-simple-add-classes')}`;
234
+ }
235
+ if (this.isDisabled) {
236
+ resClassesWrap += ` ${getClass('disabled', true)}`;
237
+ }
238
+ if (this.options.isUp) {
239
+ resClassesWrap += ` ${getClass('up', true)}`;
240
+ }
241
+ resClassesWrap += ` ${this.isMulti ? getClass('multi', true) : getClass('single', true)}`;
242
+ this.elemWrap.className = resClassesWrap;
243
+ this.elemWrap.dataset.countAll = this.$select.options.length.toString();
244
+
245
+ this.elemTop.className = getClass('top');
246
+
247
+ // creating an initial structure
248
+ const parentElement = this.$select.parentNode;
249
+ if (parentElement) {
250
+ parentElement.replaceChild(this.elemWrap, this.$select);
251
+ this.elemWrap.appendChild(this.$select);
252
+ }
253
+ this.elemWrap.append(this.elemTop);
254
+
255
+ if (this.isNative) {
256
+ this.$select.classList.add(getClass('native', true, this.classSelectInit));
257
+ this.elemWrap.classList.add(getClass('native', true));
258
+ } else {
259
+ this.createDropDown();
260
+
261
+ this.createControlHTML();
262
+
263
+ this.createInputHTML();
264
+ }
265
+
266
+ this.createTitleHTML();
267
+ }
268
+
269
+ private createControlHTML() {
270
+ if (!this.elemDropDown || !this.isMulti) {
271
+ return;
272
+ }
273
+ if (!this.options.selectAll && !this.options.resetAll) {
274
+ return;
275
+ }
276
+ this.elemControl = document.createElement('div');
277
+ this.elemControl.classList.add(getClass('controls'));
278
+
279
+ this.elemDropDown.prepend(this.elemControl);
280
+
281
+ const classControl = getClass('control');
282
+ if (this.options.selectAll) {
283
+ this.elemSelectAll = createButton();
284
+ this.elemSelectAll.className = `${classControl} ${getClass('select_all', true, classControl)}`;
285
+
286
+ this.elemSelectAll.innerHTML = `<span class="${getClass('select_all__icon')}"></span> ${this.options.locale.selectAll}`;
287
+
288
+ this.elemControl.append(this.elemSelectAll);
289
+ }
290
+
291
+ if (this.options.resetAll) {
292
+ this.elemResetAll = createButton();
293
+ this.elemResetAll.className = `${classControl} ${getClass('reset_all', true, classControl)}`;
294
+
295
+ this.elemResetAll.innerHTML = `<span class="${getClass('reset_all__icon')}"></span> ${this.options.locale.resetAll}`;
296
+
297
+ this.elemControl.append(this.elemResetAll);
298
+ }
299
+ }
300
+
301
+ private createIcon() {
302
+ const icon = document.createElement('span');
303
+ icon.className = getClass('icon');
304
+ this.elemTopBody.append(icon);
305
+ }
306
+
307
+ private createDropDown() {
308
+ if (this.isNative) {
309
+ return;
310
+ }
311
+ this.elemDropDown = document.createElement('div');
312
+ this.elemDropDown.className = getClass('body');
313
+ this.elemListBody = document.createElement('ul');
314
+
315
+ this.elemListBody.className = getClass('list');
316
+
317
+ this.elemWrap.append(this.elemDropDown);
318
+ this.elemDropDown.append(this.elemListBody);
319
+
320
+ this.elemDropDownClose = createButton();
321
+ this.elemDropDownClose.classList.add(getClass('close'));
322
+
323
+ this.elemDropDown.append(this.elemDropDownClose);
324
+
325
+ if (this.isMulti) {
326
+ this.createIsConfirmInMultiHTML();
327
+ }
328
+ this.handlerChangeChecked();
329
+ }
330
+
331
+ private createIsConfirmInMultiHTML() {
332
+ const confirm = document.createElement('div');
333
+
334
+ const classesItem = getClass('bottom_control');
335
+ this.confirmOk = createButton();
336
+ this.confirmNo = createButton();
337
+ confirm.append(this.confirmOk);
338
+ confirm.append(this.confirmNo);
339
+
340
+ this.confirmOk.innerHTML = this.options.locale.ok;
341
+ this.confirmNo.innerHTML = this.options.locale.cansel;
342
+
343
+ this.confirmOk.className = `${classesItem} ${getClass('ok', true, classesItem)}`;
344
+ this.confirmNo.className = `${classesItem} ${getClass('no', true, classesItem)}`;
345
+
346
+ let classes = getClass('bottom_controls');
347
+ if (!this.options.isConfirmInMulti) {
348
+ classes += ` ${getClass('hide', true, classes)}`;
349
+ }
350
+ confirm.className = classes;
351
+
352
+ this.elemDropDown?.append(confirm);
353
+ }
354
+
355
+ private createTitleHTML() {
356
+ if (!this.elemTitle) {
357
+ this.elemTitle = document.createElement('div');
358
+ this.elemTitle.className = getClass('title');
359
+ this.elemTopBody.prepend(this.elemTitle);
360
+ }
361
+
362
+ const itemsChecked = this.getChecked();
363
+
364
+ this.elemTop.title = '';
365
+ const isPlaceholder = !itemsChecked.length;
366
+ let title:string = this.titlePlaceholder;
367
+ if (itemsChecked.length) {
368
+ let attrTitle = '';
369
+ itemsChecked.forEach((item, index) => {
370
+ if (index !== 0) {
371
+ attrTitle += `${this.options.sepChars}<span class="${getClass('sep_space', true)}">&nbsp;</span>`;
372
+ }
373
+ attrTitle += `${item.title}`;
374
+ });
375
+ this.elemTop.title = attrTitle;
376
+
377
+ let maxShow = this.options.countShowSelected;
378
+ const maxShowAttr = Number(this.$select.dataset.simpleCountShowsSelected);
379
+ if (maxShowAttr && maxShowAttr > 0) {
380
+ maxShow = maxShowAttr;
381
+ }
382
+ if (itemsChecked.length > maxShow) {
383
+ title = `${this.options.locale.selected} ${itemsChecked.length}`;
384
+
385
+ if (this.$select.querySelectorAll('option').length === itemsChecked.length) {
386
+ title += ` (${this.options.locale.all})`;
387
+ }
388
+ } else if (attrTitle) {
389
+ title = attrTitle;
390
+ }
391
+ }
392
+
393
+ this.elemTitle.innerHTML = title;
394
+ this.elemTitle.classList.toggle('SimpleSel__title--placeholder', isPlaceholder);
395
+ this.elemTitle.classList.toggle('SimpleSel__title--fill', !isPlaceholder);
396
+
397
+ this.elemWrap.classList.toggle(getClass('fill', true), !isPlaceholder);
398
+ }
399
+
400
+ protected createListHTML() {
401
+ if (!this.elemListBody) {
402
+ return;
403
+ }
404
+ let resBodyList = '';
405
+ let countShowItem = 0;
406
+ let countCheckedItems = 0;
407
+ let countCheckedFullItems = 0;
408
+
409
+ // this.items.forEach(group => {
410
+ this.state.getState('items').forEach((group:IOptionItems) => {
411
+ if (!group.isGroup) {
412
+ const {
413
+ result, countShow, countChecked, countCheckedFull,
414
+ } = this.createLi(group);
415
+ resBodyList += result;
416
+ countShowItem += countShow;
417
+ countCheckedItems += countChecked;
418
+ countCheckedFullItems += countCheckedFull;
419
+ } else {
420
+ const {
421
+ result, countShow, countChecked, countCheckedFull,
422
+ } = this.createLi(group);
423
+ resBodyList += `<div class="${getClass('group_items')}">`;
424
+ resBodyList += result;
425
+ resBodyList += '</div>';
426
+
427
+ countCheckedItems += countChecked;
428
+ countShowItem += countShow;
429
+ countCheckedFullItems += countCheckedFull;
430
+ }
431
+ });
432
+
433
+ const isSearch:string = this.state.getState('filterStr');
434
+
435
+ if (isSearch && isSearch.length && countShowItem === 0) {
436
+ resBodyList = `<div class="${getClass('no_match')}">`;
437
+ resBodyList = `${this.options.locale.noSearch} "${isSearch}"`;
438
+ resBodyList += '</div>';
439
+ }
440
+
441
+ this.elemWrap.dataset.countChecked = countCheckedItems.toString();
442
+ this.elemWrap.dataset.countCheckedFull = countCheckedFullItems.toString();
443
+ if (this.isMulti) {
444
+ this.elemWrap.dataset.checkAllMulti = (this.$select.options.length === countCheckedItems) ? 'yes' : 'no';
445
+ }
446
+ this.elemListBody.innerHTML = resBodyList;
447
+ }
448
+
449
+ private createInputHTML(): void {
450
+ let { isSearch } = this.options;
451
+ let { isSearchInDropdown } = this.options;
452
+ if ('simpleSelectSearch' in this.$select.dataset) {
453
+ isSearch = this.$select.dataset.simpleSelectSearch !== 'false';
454
+ }
455
+ if ('simpleSelectSearchDropdown' in this.$select.dataset) {
456
+ isSearchInDropdown = this.$select.dataset.simpleSelectSearchDropdown !== 'false';
457
+ }
458
+ if (!isSearch && !isSearchInDropdown) {
459
+ return;
460
+ }
461
+ this.elemInputSearch = document.createElement('input');
462
+ this.elemInputSearch.type = 'text';
463
+ this.elemInputSearch.tabIndex = -1;
464
+ this.elemInputSearch.autocomplete = 'off';
465
+ this.elemInputSearch.ariaAutoComplete = 'none';
466
+ this.elemInputSearch.inputMode = 'off';
467
+ this.elemInputSearch.placeholder = this.options.locale.searchText;
468
+ this.elemInputSearch.name = `${initClass}_name_${this.id}`;
469
+
470
+ const className = getClass('search');
471
+ if (isSearchInDropdown) {
472
+ if (this.elemDropDown) {
473
+ this.elemInputSearch.className = `${className} ${getClass('dropdown', true, className)}`;
474
+ this.elemDropDown.prepend(this.elemInputSearch);
475
+ }
476
+ } else {
477
+ this.elemInputSearch.className = `${className} ${getClass('top', true, className)}`;
478
+ this.elemTop.append(this.elemInputSearch);
479
+ }
480
+
481
+ this.inputSearchHandler();
482
+ }
483
+
484
+ getChecked(): IOptionItem[] {
485
+ const items: IOptionItems[] = this.state.getState('items');
486
+ let res: IOptionItem[] = [];
487
+
488
+ items.forEach((group) => {
489
+ res = [
490
+ ...res,
491
+ ...group.items.filter((i) => i.checked),
492
+ ];
493
+ });
494
+
495
+ return res;
496
+ }
497
+
498
+ private createLi(data: IOptionItems): ICreateLiReturn {
499
+ let result = '';
500
+ let countShow = 0;
501
+ let countChecked = 0;
502
+ let countCheckedFull = 0;
503
+
504
+ if (!data.isShowFilter) {
505
+ return {
506
+ result, countShow, countChecked, countCheckedFull,
507
+ };
508
+ }
509
+
510
+ if (data.isGroup) {
511
+ result += `<label class="${getClass('group_title')}">${data.titleGroup}</label>`;
512
+ result += `<ul class="${getClass('group')}">`;
513
+ }
514
+ data.items.forEach((option) => {
515
+ if (!option.isShowFilter) {
516
+ return;
517
+ }
518
+ countShow++;
519
+ const classLiInit = getClass('list_item');
520
+ let classLi = classLiInit;
521
+ if (option.checked) {
522
+ countChecked++;
523
+ classLi += ` ${getClass('checked', true, classLiInit)}`;
524
+
525
+ if (option.value) {
526
+ countCheckedFull++;
527
+ }
528
+ }
529
+ if (option.disabled) {
530
+ classLi += ` ${getClass('disabled', true, classLiInit)}`;
531
+ }
532
+ if (!option.value) {
533
+ classLi += ` ${getClass('not_value', true, classLiInit)}`;
534
+ }
535
+
536
+ let dataAttr = `data-sel-group-id="${data.idGroup}"`;
537
+ dataAttr += ` data-sel-position="${option.position}"`;
538
+ dataAttr += ` data-sel-id="${option.id}"`;
539
+
540
+ if (option.value) {
541
+ dataAttr += ` data-sel-value="${option.value}"`;
542
+ }
543
+
544
+ dataAttr += ' data-sel-opt-item';
545
+ dataAttr += ` data-sel-opt-checked="${option.checked}"`;
546
+ dataAttr += ` data-sel-opt-disabled="${option.disabled}"`;
547
+
548
+ result += `<li class="${classLi}" ${dataAttr}>`;
549
+ const createLiBodyRes = this.createLiBody(option, this.$select.options[option.position]);
550
+ result += typeof createLiBodyRes === 'string' ? createLiBodyRes : createLiBodyRes.outerHTML;
551
+ result += '</li>';
552
+ });
553
+ if (data.isGroup) {
554
+ result += '</ul>';
555
+ }
556
+ return {
557
+ result, countShow, countChecked, countCheckedFull,
558
+ };
559
+ }
560
+
561
+ private createLiBody(option: IOptionItem, optionNative: HTMLOptionElement): HTMLElement | string {
562
+ const item = document.createElement('div');
563
+
564
+ item.className = getClass('list_item_body');
565
+
566
+ let res:string = '';
567
+ if (this.isShowCheckbox) {
568
+ res = `<span class="${getClass('list_item_icon')}"></span>`;
569
+ }
570
+
571
+ if (this.bodyLiHTMLBeforeFromSelect) {
572
+ res += this.bodyLiHTMLBeforeFromSelect;
573
+ }
574
+
575
+ if (optionNative.hasAttribute('data-simple-html-before')) {
576
+ res += optionNative.getAttribute('data-simple-html-before');
577
+ }
578
+
579
+ res += `${option.title}`;
580
+ if (this.bodyLiHTMLAfterFromSelect) {
581
+ res += this.bodyLiHTMLAfterFromSelect;
582
+ }
583
+ if (optionNative.hasAttribute('data-simple-html-after')) {
584
+ res += optionNative.getAttribute('data-simple-html-after');
585
+ }
586
+
587
+ item.innerHTML = res;
588
+
589
+ if (this.options.changeBodyLi) {
590
+ return this.options.changeBodyLi(item, optionNative);
591
+ }
592
+ return item;
593
+ }
594
+
595
+ protected handlerChangeChecked() {
596
+ console.error('This method need redefine');
597
+ }
598
+
599
+ // only desktop
600
+ protected createList(_isCompare: boolean) {
601
+ console.error('This method need redefine');
602
+ }
603
+
604
+ // only desktop
605
+ protected inputSearchHandler() {
606
+ console.error('This method need redefine');
607
+ }
608
+ }