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