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