jb-select 2.0.3 → 3.0.0

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,48 +1,80 @@
1
1
  import HTML from './JBSelect.html';
2
2
  import CSS from './JBSelect.scss';
3
- class JBSelectWebComponent extends HTMLElement {
3
+ import { JBSelectCallbacks, JBSelectElements, JBSelectOptionElement } from './Types';
4
+ export class JBSelectWebComponent extends HTMLElement {
5
+ #value: any;
6
+ #textValue = "";
7
+ // if user set value and current option list is not contain the option.
8
+ // we hold it in _notFindedValue and select value when option value get updated
9
+ #notFindedValue: any = null;
10
+ required = false;
11
+ callbacks: JBSelectCallbacks = {
12
+ getOptionTitle: (option) => { return option; },
13
+ getOptionValue: (option) => { return option; },
14
+ getOptionDOM: null,
15
+ getSelectedValueDOM: null,
16
+ };
17
+ elements!: JBSelectElements;
4
18
  get value() {
5
- if (this._value) {
6
- return this.callbacks.getOptionValue(this._value);
19
+ if (this.#value) {
20
+ return this.callbacks.getOptionValue(this.#value);
7
21
  } else {
8
22
  return null;
9
23
  }
10
24
  }
11
25
  set value(value) {
12
- this._setValueFromOutside(value);
26
+ this.#setValueFromOutside(value);
13
27
  }
14
28
  get textValue() {
15
- return this._textValue;
29
+ return this.#textValue;
16
30
 
17
31
  }
18
32
  set textValue(value) {
19
- this._textValue = value;
33
+ this.#textValue = value;
20
34
  this.elements.input.value = value;
21
35
  this.updateOptionList(value);
22
36
  }
23
37
  get selectedOptionTitle() {
24
38
  if (this.value) {
25
- return this.callbacks.getOptionTitle(this._value);
39
+ return this.callbacks.getOptionTitle(this.#value);
26
40
  } else {
27
41
  return "";
28
42
  }
29
43
  }
44
+ #optionList:any[] = [];
45
+ #displayOptionList:any[] = [];
30
46
  get optionList() {
31
- return this._optionList || [];
47
+ return this.#optionList || [];
32
48
  }
33
49
  set optionList(value) {
34
50
  if (!Array.isArray(value)) {
35
51
  console.error('your provided option list to jb-select is not a array. you must provide array value', { value });
36
52
  return;
37
53
  }
38
- this._optionList = value;
54
+ this.#optionList = value;
39
55
  //every time optionList get updated we set our value base on current option list we use _notFindedValue in case of value provided to component before optionList
40
- this._displayOptionList = this.filterOptionList(this.textValue);
56
+ this.displayOptionList = this.filterOptionList(this.textValue);
41
57
  this._setValueOnOptionListChanged();
42
- this.updateOptionListDOM();
58
+ }
59
+ #placeholder = "";
60
+ get placeholder() {
61
+ return this.#placeholder;
62
+ }
63
+ set placeholder(value:string) {
64
+ this.#placeholder = value;
65
+ this.elements.input.placeholder = value;
43
66
  }
44
67
  get displayOptionList() {
45
- return this._displayOptionList;
68
+ return this.#displayOptionList;
69
+ }
70
+ set displayOptionList(value:any[]){
71
+ if(Array.isArray(value) && value.length == 0){
72
+ this.elements.emptyListPlaceholder.classList.add('--show');
73
+ }else if(Array.isArray(value)){
74
+ this.elements.emptyListPlaceholder.classList.remove('--show');
75
+ }
76
+ this.#displayOptionList = value;
77
+ this.updateOptionListDOM();
46
78
  }
47
79
  get isMobileDevice() { return /Mobi/i.test(window.navigator.userAgent); }
48
80
 
@@ -59,11 +91,11 @@ class JBSelectWebComponent extends HTMLElement {
59
91
  this.callOnInitEvent();
60
92
  }
61
93
  callOnInitEvent() {
62
- var event = new CustomEvent('init', { bubbles: true, composed: true });
94
+ const event = new CustomEvent('init', { bubbles: true, composed: true });
63
95
  this.dispatchEvent(event);
64
96
  }
65
97
  callOnLoadEvent() {
66
- var event = new CustomEvent('load', { bubbles: true, composed: true });
98
+ const event = new CustomEvent('load', { bubbles: true, composed: true });
67
99
  this.dispatchEvent(event);
68
100
  }
69
101
  initWebComponent() {
@@ -75,41 +107,35 @@ class JBSelectWebComponent extends HTMLElement {
75
107
  element.innerHTML = html;
76
108
  shadowRoot.appendChild(element.content.cloneNode(true));
77
109
  this.elements = {
78
- input: shadowRoot.querySelector('.select-box input'),
79
- componentWrapper: shadowRoot.querySelector('.jb-select-web-component'),
80
- selectedValueWrapper: shadowRoot.querySelector('.selected-value-wrapper'),
110
+ input: shadowRoot.querySelector('.select-box input')!,
111
+ componentWrapper: shadowRoot.querySelector('.jb-select-web-component')!,
112
+ selectedValueWrapper: shadowRoot.querySelector('.selected-value-wrapper')!,
113
+ messageBox: shadowRoot.querySelector('.message-box')!,
114
+ optionList: shadowRoot.querySelector('.select-list')!,
115
+ optionListWrapper: shadowRoot.querySelector('.select-list-wrapper')!,
116
+ arrowIcon: shadowRoot.querySelector('.arrow-icon')!,
117
+ label:{
118
+ wrapper: shadowRoot.querySelector('label')!,
119
+ text: shadowRoot.querySelector('label .label-value')!,
120
+ },
121
+ emptyListPlaceholder: shadowRoot.querySelector('.empty-list-placeholder')!,
81
122
  };
82
- this._optionListElement = shadowRoot.querySelector('.select-list');
83
- this._optionListElementWrapper = shadowRoot.querySelector('.select-list-wrapper');
84
123
  this.registerEventListener();
85
124
 
86
125
  }
87
126
  registerEventListener() {
88
- this.elements.input.addEventListener('change', this.onInputChange.bind(this));
127
+ this.elements.input.addEventListener('change', (e)=>{this.onInputChange(e);});
89
128
  this.elements.input.addEventListener('keypress', this.onInputKeyPress.bind(this));
90
129
  this.elements.input.addEventListener('keyup', this.onInputKeyup.bind(this));
91
130
  this.elements.input.addEventListener('beforeinput', this.onInputBeforeInput.bind(this));
92
- this.elements.input.addEventListener('input', this.onInputInput.bind(this));
131
+ this.elements.input.addEventListener('input', (e)=>{this.onInputInput(e as unknown as InputEvent);});
93
132
  this.elements.input.addEventListener('focus', this.onInputFocus.bind(this));
94
133
  this.elements.input.addEventListener('blur', this.onInputBlur.bind(this));
95
- this.shadowRoot.querySelector('.arrow-icon').addEventListener('click', this.onArrowKeyClick.bind(this));
134
+ this.elements.arrowIcon.addEventListener('click', this.onArrowKeyClick.bind(this));
96
135
  }
97
136
  initProp() {
98
137
  this.textValue = '';
99
- /**
100
- * @type {Record<string, function|null>} callbacks
101
- */
102
- this.callbacks = {
103
- getOptionTitle: (option) => { return option; },
104
- getOptionValue: (option) => { return option; },
105
- getOptionDOM: null,
106
- getSelectedValueDOM: null,
107
- };
108
138
  this.value = this.getAttribute('value') || null;
109
- // if user set value and current option list is not contain the option.
110
- // we hold it in _notFindedValue and select value when option value get updated
111
- this._notFindedValue = null;
112
- this.required = false;
113
139
  }
114
140
  static get observedAttributes() {
115
141
  return ['label', 'message', 'value', 'required', 'placeholder'];
@@ -121,18 +147,18 @@ class JBSelectWebComponent extends HTMLElement {
121
147
  onAttributeChange(name, value) {
122
148
  switch (name) {
123
149
  case 'label':
124
- this.shadowRoot.querySelector('label .label-value').innerHTML = value;
150
+ this.elements.label.text.innerHTML = value;
125
151
  if (value == null || value == undefined || value == "") {
126
- this.shadowRoot.querySelector('label').classList.add('--hide');
152
+ this.elements.label.wrapper.classList.add('--hide');
127
153
  } else {
128
- this.shadowRoot.querySelector('label').classList.remove('--hide');
154
+ this.elements.label.wrapper.classList.remove('--hide');
129
155
  }
130
156
  break;
131
157
  case 'message':
132
- this.shadowRoot.querySelector('.message-box').innerHTML = value;
158
+ this.elements.messageBox.innerHTML = value;
133
159
  break;
134
160
  case 'value':
135
- this._setValueFromOutside(value);
161
+ this.#setValueFromOutside(value);
136
162
  break;
137
163
  case 'required':
138
164
  if (value === "" || value == "true" || value == true) {
@@ -142,7 +168,7 @@ class JBSelectWebComponent extends HTMLElement {
142
168
  }
143
169
  break;
144
170
  case 'placeholder':
145
- this.elements.input.placeholder = value;
171
+ this.placeholder = value;
146
172
  break;
147
173
  }
148
174
 
@@ -150,13 +176,13 @@ class JBSelectWebComponent extends HTMLElement {
150
176
  _setValueOnOptionListChanged() {
151
177
  //when option list changed we see if current value is valid for new optionlist we set it if not we reset value to null.
152
178
  //in some scenario value is setted before otionList attached so we store it on this._notFindedValue and after option list setted we set value from this._notFindedValue
153
- if (this.value || this._notFindedValue) {
179
+ if (this.value || this.#notFindedValue) {
154
180
  //if select has no prev value or pending not finded value we dont set it becuase user may input some search terms in input box and developer-user update list base on that value
155
181
  //if we set it to null the search term and this.textvalue will become null and empty too and it make impossible for user to search in dynamic back-end provided searchable list so we put this condition to prevent it
156
- this._setValueFromOutside(this.value || this._notFindedValue);
182
+ this.#setValueFromOutside(this.value || this.#notFindedValue);
157
183
  }
158
184
  }
159
- _setValueFromOutside(value) {
185
+ #setValueFromOutside(value:any) {
160
186
  //when user set value by attribute or value prop directly we call this function
161
187
  const matchedOption = this.optionList.find((option) => { // if we have value mapper we set selected value by object that match mapper
162
188
  if (this.callbacks.getOptionValue(option) == value) {
@@ -166,27 +192,29 @@ class JBSelectWebComponent extends HTMLElement {
166
192
  if (matchedOption || value == null) {
167
193
  this._setValue(matchedOption);
168
194
  } else {
169
- this._notFindedValue = value;
195
+ this.#notFindedValue = value;
170
196
  }
171
197
 
172
198
  }
173
- _setValue(value) {
174
- this._notFindedValue = null;
175
- this._value = value;
199
+ _setValue(value:any) {
200
+ this.#notFindedValue = null;
201
+ this.#value = value;
176
202
  if ((value == null || value == undefined)) {
177
203
  this.textValue = '';
178
204
  this.setSelectedOptionDom(null);
179
- this.shadowRoot.querySelector('.jb-select-web-component').classList.remove('--has-value');
205
+ this.elements.componentWrapper.classList.remove('--has-value');
206
+ this.elements.input.setAttribute('placeholder', this.placeholder);
180
207
  } else {
181
208
  this.textValue = '';
182
209
  this.setSelectedOptionDom(value);
183
- this.shadowRoot.querySelector('.jb-select-web-component').classList.add('--has-value');
210
+ this.elements.componentWrapper.classList.add('--has-value');
211
+ this.elements.input.setAttribute('placeholder', '');
184
212
  }
185
213
  //if user select an option we rest filter so user see all option again when open a select
186
214
  this.updateOptionList('');
187
215
  }
188
216
  onArrowKeyClick() {
189
- if (this._optionListElementWrapper.classList.contains('--show')) {
217
+ if (this.elements.optionListWrapper.classList.contains('--show')) {
190
218
  this.blur();
191
219
  } else {
192
220
  this.focus();
@@ -195,15 +223,16 @@ class JBSelectWebComponent extends HTMLElement {
195
223
  onInputKeyPress() {
196
224
  //TODO: raise keypress event
197
225
  }
198
- onInputBeforeInput(e) {
199
- this.handleSelectedValueDisplay(e.data);
226
+ onInputBeforeInput(e:InputEvent) {
227
+ const inputedText = e.data || '';
228
+ this.handleSelectedValueDisplay(inputedText);
200
229
  }
201
- onInputInput(e) {
202
- this.textValue = e.target.value;
230
+ onInputInput(e:InputEvent) {
231
+ this.textValue = (e.target as HTMLInputElement).value;
203
232
  }
204
- onInputKeyup(e) {
205
- const inputText = e.target.value;
206
- //here is the rare time we update _value directly becuase we want trigger event that may read value directly from dom
233
+ onInputKeyup(e:KeyboardEvent) {
234
+ const inputText = (e.target as HTMLInputElement).value;
235
+ //here is the rare time we update #value directly becuase we want trigger event that may read value directly from dom
207
236
  if (e.key === "Backspace" || e.key === "Delete") {
208
237
  //becuase on keyprees dont recieve backspace key press
209
238
  this.handleSelectedValueDisplay(inputText);
@@ -212,14 +241,14 @@ class JBSelectWebComponent extends HTMLElement {
212
241
  this.triggerOnInputKeyup(e);
213
242
 
214
243
  }
215
- handleSelectedValueDisplay(inputValue) {
244
+ handleSelectedValueDisplay(inputValue:string) {
216
245
  if (inputValue !== "") {
217
246
  this.elements.selectedValueWrapper.classList.add('--search-typed');
218
247
  } else {
219
248
  this.elements.selectedValueWrapper.classList.remove('--search-typed');
220
249
  }
221
250
  }
222
- triggerOnInputKeyup(e) {
251
+ triggerOnInputKeyup(e:KeyboardEvent) {
223
252
  const event = new KeyboardEvent('keyup', {
224
253
  altKey: e.altKey,
225
254
  bubbles: e.bubbles,
@@ -240,17 +269,17 @@ class JBSelectWebComponent extends HTMLElement {
240
269
  });
241
270
  this.dispatchEvent(event);
242
271
  }
243
- onInputChange(e) {
244
- const inputText = e.target.value;
272
+ onInputChange(e: Event) {
273
+ const inputText = (e.target as HTMLInputElement).value;
245
274
  //here is the rare time we update _text_value directly becuase we want trigger event that may read value directly from dom
246
- this._textValue = inputText;
275
+ this.#textValue = inputText;
247
276
  }
248
277
  onInputFocus() {
249
278
  this.focus();
250
279
  }
251
- onInputBlur(e) {
252
- let focusedElement = e.relatedTarget;
253
- if (focusedElement === this._optionListElement) {
280
+ onInputBlur(e: FocusEvent) {
281
+ const focusedElement = e.relatedTarget;
282
+ if (focusedElement === this.elements.optionListWrapper) {
254
283
  //user click on a menu item
255
284
  } else {
256
285
  this.blur();
@@ -270,28 +299,27 @@ class JBSelectWebComponent extends HTMLElement {
270
299
  this.triggerInputValidation();
271
300
  }
272
301
  showOptionList() {
273
- this._optionListElementWrapper.classList.add('--show');
302
+ this.elements.optionListWrapper.classList.add('--show');
274
303
  }
275
304
  hideOptionList() {
276
- this._optionListElementWrapper.classList.remove('--show');
305
+ this.elements.optionListWrapper.classList.remove('--show');
277
306
  }
278
- updateOptionList(filterText) {
279
- this._displayOptionList = this.filterOptionList(filterText);
280
- this.updateOptionListDOM();
307
+ updateOptionList(filterText:string) {
308
+ this.displayOptionList = this.filterOptionList(filterText);
281
309
  }
282
310
  updateOptionListDOM() {
283
- const optionDomList = [];
311
+ const optionDomList: HTMLElement[] = [];
284
312
  this.displayOptionList.forEach((item) => {
285
313
  const optionDOM = this.createOptionDOM(item);
286
314
  optionDomList.push(optionDOM);
287
315
  });
288
- this._optionListElement.innerHTML = '';
289
- optionDomList.forEach(optionElement => { this._optionListElement.appendChild(optionElement); });
316
+ this.elements.optionList.innerHTML = '';
317
+ optionDomList.forEach(optionElement => { this.elements.optionList.appendChild(optionElement); });
290
318
 
291
319
 
292
320
  }
293
- createOptionDOM(item) {
294
- let optionDOM = null;
321
+ createOptionDOM(item:any):JBSelectOptionElement{
322
+ let optionDOM: JBSelectOptionElement | null = null;
295
323
  if (typeof this.callbacks.getOptionDOM == 'function') {
296
324
  optionDOM = this.callbacks.getOptionDOM(item, this.onOptionClicked.bind(this));
297
325
  } else {
@@ -301,7 +329,7 @@ class JBSelectWebComponent extends HTMLElement {
301
329
  return optionDOM;
302
330
  }
303
331
 
304
- _createOptionDom(item) {
332
+ _createOptionDom(item:any):JBSelectOptionElement{
305
333
  const optionElement = document.createElement('div');
306
334
  optionElement.classList.add('select-option');
307
335
  //it has defualt function who return wxact same input
@@ -309,18 +337,18 @@ class JBSelectWebComponent extends HTMLElement {
309
337
  optionElement.addEventListener('click', this.onOptionClicked.bind(this));
310
338
  return optionElement;
311
339
  }
312
- onOptionClicked(e) {
313
- const value = e.currentTarget.value;
340
+ onOptionClicked(e:MouseEvent) {
341
+ const value = (e.currentTarget as JBSelectOptionElement).value;
314
342
  this.selectOption(value);
315
343
  this.blur();
316
344
  this._triggerOnChangeEvent();
317
345
  }
318
- selectOption(value) {
346
+ selectOption(value:any) {
319
347
  this._setValue(value);
320
348
  this.triggerInputValidation();
321
349
  }
322
- filterOptionList(filterString) {
323
- const displayOptionList = [];
350
+ filterOptionList(filterString:string):any[] {
351
+ const displayOptionList: any[] = [];
324
352
  this.optionList.filter((option) => {
325
353
  const optionTitle = this.callbacks.getOptionTitle(option);
326
354
  const isString = typeof optionTitle == 'string';
@@ -344,7 +372,7 @@ class JBSelectWebComponent extends HTMLElement {
344
372
  errorType = 'REQUIRED';
345
373
  }
346
374
  }
347
- let isAllValid = requiredValid; //& other validation if they added
375
+ const isAllValid = requiredValid; //& other validation if they added
348
376
  if (isAllValid) {
349
377
  this.clearValidationError();
350
378
  } else if (showError) {
@@ -357,20 +385,20 @@ class JBSelectWebComponent extends HTMLElement {
357
385
  showValidationError(errorType) {
358
386
  if (errorType == 'REQUIRED') {
359
387
  const label = this.getAttribute('label');
360
- this.shadowRoot.querySelector('.message-box').innerHTML = `${label} حتما باید انتخاب شود`;
361
- this.shadowRoot.querySelector('.message-box').classList.add('--error');
388
+ this.elements.messageBox.innerHTML = `${label} حتما باید انتخاب شود`;
389
+ this.elements.messageBox.classList.add('--error');
362
390
  }
363
391
  }
364
392
  clearValidationError() {
365
- this.shadowRoot.querySelector('.message-box').innerHTML = this.getAttribute('message') || '';
366
- this.shadowRoot.querySelector('.message-box').classList.remove('--error');
393
+ this.elements.messageBox.innerHTML = this.getAttribute('message') || '';
394
+ this.elements.messageBox.classList.remove('--error');
367
395
 
368
396
  }
369
397
  _triggerOnChangeEvent() {
370
398
  const event = new Event("change");
371
399
  this.dispatchEvent(event);
372
400
  }
373
- setSelectedOptionDom(value) {
401
+ setSelectedOptionDom(value:any) {
374
402
  //when user select option or value changed in any condition we set selected option DOM
375
403
  this.elements.selectedValueWrapper.innerHTML = '';
376
404
  //if value was null or undifined it remain empty
@@ -379,14 +407,14 @@ class JBSelectWebComponent extends HTMLElement {
379
407
  this.elements.selectedValueWrapper.appendChild(selectedOptionDom);
380
408
  }
381
409
  }
382
- createSelectedValueDom(value) {
410
+ private createSelectedValueDom(value:any) {
383
411
  if (typeof this.callbacks.getSelectedValueDOM == 'function') {
384
412
  return this.callbacks.getSelectedValueDOM(value);
385
413
  } else {
386
- return this._createSelectedValueDom(value);
414
+ return this.#createSelectedValueDom(value);
387
415
  }
388
416
  }
389
- _createSelectedValueDom(value) {
417
+ #createSelectedValueDom(value:any) {
390
418
  const valueText = this.callbacks.getOptionTitle(value);
391
419
  const selectedOptionDom = document.createElement('div');
392
420
  selectedOptionDom.classList.add('selected-value');
package/lib/Types.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { string } from "prop-types";
2
+
3
+ export type JBSelectCallbacks = {
4
+ getOptionTitle: (option:any) => string;
5
+ getOptionValue: (option:any) => any;
6
+ getOptionDOM: null | ((option:any, onSelectCallback:(e:MouseEvent)=>void) => HTMLOptionElement);
7
+ getSelectedValueDOM: null | ((option:any) => HTMLElement);
8
+ }
9
+ export type JBSelectOptionElement = HTMLElement & {value?: any};
10
+ export type JBSelectElements = {
11
+ input: HTMLInputElement,
12
+ componentWrapper: HTMLDivElement,
13
+ selectedValueWrapper: HTMLDivElement,
14
+ messageBox:HTMLDivElement,
15
+ optionList: HTMLDivElement,
16
+ optionListWrapper: HTMLDivElement,
17
+ arrowIcon: HTMLDivElement,
18
+ label:{
19
+ wrapper: HTMLLabelElement,
20
+ text: HTMLSpanElement
21
+ },
22
+ emptyListPlaceholder: HTMLDivElement,
23
+ }
@@ -0,0 +1,11 @@
1
+ type FileStringModule = {
2
+ readonly default: string;
3
+ }
4
+ declare module '*.scss' {
5
+ const value: FileStringModule;
6
+ export default value;
7
+ }
8
+ declare module '*.html' {
9
+ const value: FileStringModule;
10
+ export default value.default
11
+ }
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "select",
13
13
  "web component"
14
14
  ],
15
- "version": "2.0.3",
15
+ "version": "3.0.0",
16
16
  "bugs": "https://github.com/javadbat/jb-select/issues",
17
17
  "license": "MIT",
18
18
  "files": [
@@ -21,6 +21,7 @@
21
21
  "lib/",
22
22
  "dist/"
23
23
  ],
24
+ "types": "./dist/JBSelect.d.ts",
24
25
  "main": "index.js",
25
26
  "repository": {
26
27
  "type": "git",