jb-select 5.3.2 → 6.0.1

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.
Files changed (69) hide show
  1. package/README.md +67 -29
  2. package/dist/index.cjs.js +2 -0
  3. package/dist/index.cjs.js.br +0 -0
  4. package/dist/index.cjs.js.gz +0 -0
  5. package/dist/index.cjs.js.map +1 -0
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.br +0 -0
  8. package/dist/index.js.gz +0 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/index.umd.js +2 -0
  11. package/dist/index.umd.js.br +0 -0
  12. package/dist/index.umd.js.gz +0 -0
  13. package/dist/index.umd.js.map +1 -0
  14. package/dist/web-component/jb-select/lib/index.d.ts +7 -0
  15. package/dist/web-component/jb-select/lib/jb-option/jb-option.d.ts +21 -0
  16. package/dist/web-component/jb-select/lib/jb-option/render.d.ts +1 -0
  17. package/dist/web-component/jb-select/lib/jb-option/types.d.ts +4 -0
  18. package/dist/web-component/jb-select/lib/jb-option-list/jb-option-list.d.ts +10 -0
  19. package/dist/web-component/jb-select/lib/jb-option-list/types.d.ts +5 -0
  20. package/dist/web-component/jb-select/lib/jb-select.d.ts +3 -7
  21. package/dist/web-component/jb-select/lib/types.d.ts +6 -10
  22. package/index.js +2 -2
  23. package/lib/index.ts +8 -0
  24. package/lib/jb-option/jb-option.scss +23 -0
  25. package/lib/jb-option/jb-option.ts +136 -0
  26. package/lib/jb-option/render.ts +9 -0
  27. package/lib/jb-option/types.ts +4 -0
  28. package/lib/jb-option-list/jb-option-list.ts +125 -0
  29. package/lib/jb-option-list/types.ts +5 -0
  30. package/lib/jb-select.html +1 -1
  31. package/lib/jb-select.scss +36 -45
  32. package/lib/jb-select.ts +176 -239
  33. package/lib/types.ts +8 -9
  34. package/package.json +5 -3
  35. package/react/README.md +188 -0
  36. package/react/dist/common/hooks/use-event.d.ts +3 -0
  37. package/react/dist/common/hooks/useLazyRef.d.ts +4 -0
  38. package/react/dist/common/hooks/useMobx.d.ts +4 -0
  39. package/react/dist/common/scripts/device-detection.d.ts +1 -0
  40. package/react/dist/common/scripts/persian-helper.d.ts +3 -0
  41. package/react/dist/index.cjs.js +122 -0
  42. package/react/dist/index.cjs.js.map +1 -0
  43. package/react/dist/index.js +118 -0
  44. package/react/dist/index.js.map +1 -0
  45. package/react/dist/index.umd.js +125 -0
  46. package/react/dist/index.umd.js.map +1 -0
  47. package/react/dist/web-component/jb-select/react/lib/JBOption.d.ts +19 -0
  48. package/react/dist/web-component/jb-select/react/lib/JBOptionList.d.ts +22 -0
  49. package/react/dist/web-component/jb-select/react/lib/JBSelect.d.ts +38 -0
  50. package/react/dist/web-component/jb-select/react/lib/index.d.ts +3 -0
  51. package/react/index.js +1 -0
  52. package/react/lib/JBOption.tsx +45 -0
  53. package/react/lib/JBOptionList.tsx +59 -0
  54. package/react/lib/JBSelect.tsx +105 -0
  55. package/react/lib/index.tsx +3 -0
  56. package/react/package.json +33 -0
  57. package/react/tsconfig.json +19 -0
  58. package/dist/jb-select.cjs.js +0 -2
  59. package/dist/jb-select.cjs.js.br +0 -0
  60. package/dist/jb-select.cjs.js.gz +0 -0
  61. package/dist/jb-select.cjs.js.map +0 -1
  62. package/dist/jb-select.js +0 -2
  63. package/dist/jb-select.js.br +0 -0
  64. package/dist/jb-select.js.gz +0 -0
  65. package/dist/jb-select.js.map +0 -1
  66. package/dist/jb-select.umd.js +0 -2
  67. package/dist/jb-select.umd.js.br +0 -0
  68. package/dist/jb-select.umd.js.gz +0 -0
  69. package/dist/jb-select.umd.js.map +0 -1
package/lib/jb-select.ts CHANGED
@@ -3,45 +3,38 @@ import CSS from "./jb-select.scss";
3
3
  import {
4
4
  JBSelectCallbacks,
5
5
  JBSelectElements,
6
- JBSelectOptionElement,
7
6
  ValidationValue,
8
7
  } from "./types";
9
- import {ShowValidationErrorInput, ValidationHelper, type ValidationItem, type ValidationResult, type WithValidation} from "jb-validation";
8
+ import { ShowValidationErrorInput, ValidationHelper, type ValidationItem, type ValidationResult, type WithValidation } from "jb-validation";
10
9
  import { isMobile } from "../../../common/scripts/device-detection";
11
- import {JBFormInputStandards} from 'jb-form';
10
+ import { JBFormInputStandards } from 'jb-form';
11
+ // eslint-disable-next-line no-duplicate-imports
12
+ import { JBOptionWebComponent } from "./jb-option/jb-option";
13
+ // eslint-disable-next-line no-duplicate-imports
12
14
  //TOption is the type of option, TValue is the type of value we extract from option
13
- export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLElement implements WithValidation<ValidationValue<TOption,TValue>>, JBFormInputStandards<TValue> {
15
+ export class JBSelectWebComponent<TValue = any> extends HTMLElement implements WithValidation<ValidationValue<TValue>>, JBFormInputStandards<TValue> {
14
16
  static get formAssociated() {
15
17
  return true;
16
18
  }
17
19
  // we keep selected option here by option but we return TValue when user demand
18
- #value: TOption;
20
+ #value: TValue;
19
21
  #textValue = "";
20
22
  // if user set value and current option list is not contain the option.
21
23
  // we hold it in _notFoundedValue and select value when option value get updated
22
24
  #notFoundedValue: TValue = null;
23
- callbacks: JBSelectCallbacks<TOption,TValue> = {
24
- getOptionTitle: (option) => {
25
- if(typeof option == "string" || typeof option == "number"){
26
- return option.toString();
27
- }else{
28
- console.error("title must be string please provide a valid getOptionTitle","provided title:",option);
29
- return "NOT SUPPORTED TITLE TYPE";
30
- }
31
- },
32
- getOptionValue: null,
33
- getOptionDOM: null,
34
- getSelectedValueDOM: null,
35
- };
25
+ #optionList = new Set<JBOptionWebComponent<TValue>>()
26
+ //keep selected option dom
27
+ #selectedOption: JBOptionWebComponent<TValue> | null = null;
28
+ callbacks: JBSelectCallbacks<TValue> = {}
36
29
  elements!: JBSelectElements;
37
- get value():TValue{
30
+ get value(): TValue {
38
31
  if (this.#value) {
39
- return this.#getOptionValue(this.#value);
32
+ return this.#value;
40
33
  } else {
41
34
  return null;
42
35
  }
43
36
  }
44
- set value(value:TValue) {
37
+ set value(value: TValue) {
45
38
  this.#setValueFromOutside(value);
46
39
  }
47
40
  get textValue() {
@@ -54,29 +47,11 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
54
47
  }
55
48
  get selectedOptionTitle() {
56
49
  if (this.value) {
57
- return this.#getOptionTitle(this.#value);
50
+ return this.#selectedOption.optionContentText;
58
51
  } else {
59
52
  return "";
60
53
  }
61
54
  }
62
- #optionList: TOption[] = [];
63
- #displayOptionList: TOption[] = [];
64
- get optionList() {
65
- return this.#optionList || [];
66
- }
67
- set optionList(value) {
68
- if (!Array.isArray(value)) {
69
- console.error(
70
- "your provided option list to jb-select is not a array. you must provide array value",
71
- { value }
72
- );
73
- return;
74
- }
75
- this.#optionList = value;
76
- //every time optionList get updated we set our value base on current option list we use _notFoundedValue in case of value provided to component before optionList
77
- this.displayOptionList = this.#filterOptionList(this.textValue);
78
- this.#setValueOnOptionListChanged();
79
- }
80
55
  #placeholder = "";
81
56
  get placeholder() {
82
57
  return this.#placeholder;
@@ -97,18 +72,6 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
97
72
  set searchPlaceholder(value) {
98
73
  this.#searchPlaceholder = value;
99
74
  }
100
- get displayOptionList() {
101
- return this.#displayOptionList;
102
- }
103
- set displayOptionList(value: TOption[]) {
104
- if (Array.isArray(value) && value.length == 0) {
105
- this.elements.emptyListPlaceholder.classList.add("--show");
106
- } else if (Array.isArray(value)) {
107
- this.elements.emptyListPlaceholder.classList.remove("--show");
108
- }
109
- this.#displayOptionList = value;
110
- this.#updateOptionListDOM();
111
- }
112
75
  get isMobileDevice() {
113
76
  return isMobile();
114
77
  }
@@ -116,42 +79,41 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
116
79
  return this.elements.componentWrapper.classList.contains("--focused");
117
80
  }
118
81
  // this value used by validation module to send to validation callbacks
119
- get #ValidationValue():ValidationValue<TOption,TValue>{
82
+ get #ValidationValue(): ValidationValue<TValue> {
120
83
  return {
121
- inputtedText:this.#textValue,
122
- selectedOption:this.#value,
123
- value:this.value
84
+ inputtedText: this.#textValue,
85
+ selectedOption: this.#selectedOption,
86
+ value: this.value
124
87
  };
125
88
  }
126
- #validation = new ValidationHelper<ValidationValue<TOption,TValue>>({
127
- clearValidationError:this.clearValidationError.bind(this),
128
- showValidationError:this.showValidationError.bind(this),
129
- getValue:()=>this.#ValidationValue,
130
- getValidations:this.#getInsideValidation.bind(this),
131
- getValueString:()=>this.#textValue,
132
- setValidationResult:this.#setValidationResult.bind(this)
89
+ #validation = new ValidationHelper<ValidationValue<TValue>>({
90
+ clearValidationError: this.clearValidationError.bind(this),
91
+ showValidationError: this.showValidationError.bind(this),
92
+ getValue: () => this.#ValidationValue,
93
+ getValidations: this.#getInsideValidation.bind(this),
94
+ getValueString: () => this.#textValue,
95
+ setValidationResult: this.#setValidationResult.bind(this)
133
96
  });
134
- get validation(){
97
+ get validation() {
135
98
  return this.#validation;
136
99
  }
137
100
  #disabled = false;
138
- get disabled(){
101
+ get disabled() {
139
102
  return this.#disabled;
140
103
  }
141
- set disabled(value:boolean){
104
+ set disabled(value: boolean) {
142
105
  this.#disabled = value;
143
106
  this.elements.input.disabled = value;
144
- if(value){
145
- //TODO: remove as any when typescript support
107
+ if (value) {
146
108
  (this.#internals as any).states?.add("disabled");
147
- }else{
109
+ } else {
148
110
  (this.#internals as any).states?.delete("disabled");
149
111
  }
150
112
  }
151
113
  #required = false;
152
- set required(value:boolean){
114
+ set required(value: boolean) {
153
115
  this.#required = value;
154
- this.#validation.checkValiditySync({showError:false});
116
+ this.#validation.checkValiditySync({ showError: false });
155
117
  }
156
118
  get required() {
157
119
  return this.#required;
@@ -164,11 +126,11 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
164
126
  //currently we only support disable-validation in attribute and only in initiate time but later we can add support for change of this
165
127
  return this.getAttribute('disable-auto-validation') === '' || this.getAttribute('disable-auto-validation') === 'true' ? true : false;
166
128
  }
167
- get name(){
129
+ get name() {
168
130
  return this.getAttribute('name') || '';
169
131
  }
170
132
  initialValue: TValue | null = null;
171
- get isDirty(): boolean{
133
+ get isDirty(): boolean {
172
134
  return this.value !== this.initialValue;
173
135
  }
174
136
  constructor() {
@@ -196,7 +158,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
196
158
  #initWebComponent() {
197
159
  const shadowRoot = this.attachShadow({
198
160
  mode: "open",
199
- delegatesFocus:true,
161
+ delegatesFocus: true,
200
162
  });
201
163
  const html = `<style>${CSS}</style>` + "\n" + HTML;
202
164
  const element = document.createElement("template");
@@ -211,43 +173,37 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
211
173
  messageBox: shadowRoot.querySelector(".message-box")!,
212
174
  optionList: shadowRoot.querySelector(".select-list")!,
213
175
  optionListWrapper: shadowRoot.querySelector(".select-list-wrapper")!,
176
+ optionListSlot: shadowRoot.querySelector(".select-list-wrapper .select-list slot")!,
214
177
  arrowIcon: shadowRoot.querySelector(".arrow-icon")!,
215
178
  label: {
216
179
  wrapper: shadowRoot.querySelector("label")!,
217
180
  text: shadowRoot.querySelector("label .label-value")!,
218
181
  },
219
- emptyListPlaceholder: shadowRoot.querySelector(
220
- ".empty-list-placeholder"
221
- )!,
182
+ emptyListPlaceholder: shadowRoot.querySelector(".empty-list-placeholder")!,
222
183
  };
223
184
  this.#registerEventListener();
224
185
  }
225
186
  #registerEventListener() {
226
- this.elements.input.addEventListener("change", (e:Event) => {
187
+ this.elements.input.addEventListener("change", (e: Event) => {
227
188
  this.#onInputChange(e);
228
189
  });
229
- this.elements.input.addEventListener(
230
- "keypress",
231
- this.#onInputKeyPress.bind(this)
232
- );
190
+ this.elements.input.addEventListener("keypress", this.#onInputKeyPress.bind(this));
233
191
  this.elements.input.addEventListener("keyup", this.#onInputKeyup.bind(this));
234
- this.elements.input.addEventListener(
235
- "beforeinput",
236
- this.#onInputBeforeInput.bind(this)
237
- );
238
- this.elements.input.addEventListener("input", (e) => {
239
- this.#onInputInput(e as unknown as InputEvent);
240
- });
192
+ this.elements.input.addEventListener("beforeinput", this.#onInputBeforeInput.bind(this));
193
+ this.elements.input.addEventListener("input", (e) => { this.#onInputInput(e as unknown as InputEvent); });
241
194
  this.elements.input.addEventListener("focus", this.#onInputFocus.bind(this));
242
195
  this.elements.input.addEventListener("blur", this.#onInputBlur.bind(this));
243
- this.elements.arrowIcon.addEventListener(
244
- "click",
245
- this.#onArrowKeyClick.bind(this)
246
- );
196
+ this.elements.arrowIcon.addEventListener("click", this.#onArrowKeyClick.bind(this));
197
+ //events to work with options
198
+ this.addEventListener("select", this.#onOptionSelect.bind(this));
199
+ this.addEventListener("jb-option-connected", this.#onOptionConnected.bind(this));
200
+ this.elements.optionListSlot.addEventListener("slotchange",this.#onOptionSlotChange.bind(this));
201
+
247
202
  }
203
+
248
204
  #initProp() {
249
205
  this.textValue = "";
250
- this.value = this.getAttribute("value") as TValue || null ;
206
+ this.value = this.getAttribute("value") as TValue || null;
251
207
  }
252
208
  static get observedAttributes() {
253
209
  return [
@@ -259,7 +215,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
259
215
  "search-placeholder",
260
216
  ];
261
217
  }
262
- attributeChangedCallback(name:string, oldValue:string, newValue:string) {
218
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
263
219
  // do something when an attribute has changed
264
220
  this.#onAttributeChange(name, newValue);
265
221
  }
@@ -294,6 +250,23 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
294
250
  break;
295
251
  }
296
252
  }
253
+ /**
254
+ * will check option list and if select has no option it will show empty list placeholder
255
+ */
256
+ #updateListEmptyPlaceholder(){
257
+ const ss = Array.from(this.#optionList);
258
+ console.log(ss);
259
+ const isAnyOptionVisible = Array.from(this.#optionList).some(x=>x.hidden==false);
260
+ if(isAnyOptionVisible){
261
+ this.elements.emptyListPlaceholder.classList.remove("--show");
262
+ }else{
263
+ this.elements.emptyListPlaceholder.classList.add("--show");
264
+ }
265
+ }
266
+ #onOptionSlotChange(e:Event){
267
+ this.#setValueOnOptionListChanged();
268
+ this.#updateListEmptyPlaceholder();
269
+ }
297
270
  #setValueOnOptionListChanged() {
298
271
  //when option list changed we see if current value is valid for new optionlist we set it if not we reset value to null.
299
272
  //in some scenario value is setted before optionList attached so we store it on this.#notFoundedValue and after option list setted we set value from this.#notFoundedValue
@@ -309,28 +282,43 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
309
282
  this.#setValueFromOutside(this.value);
310
283
  }
311
284
  }
285
+ //when user set value by attribute or value prop directly we call this function
312
286
  #setValueFromOutside(value: TValue): boolean {
313
- //when user set value by attribute or value prop directly we call this function
314
- const matchedOption = this.optionList.find((option) => {
287
+ if(value === null || value === undefined){
288
+ this.#setValue(null,null);
289
+ return true;
290
+ }
291
+ let matchedOption:JBOptionWebComponent<TValue>| null = null;
292
+ this.#optionList.forEach((option) => {
315
293
  // if we have value mapper we set selected value by object that match mapper
316
- if (this.#getOptionValue(option) == value) {
317
- return option;
294
+ if (option.value == value) {
295
+ matchedOption = option;
318
296
  }
319
297
  });
320
- if (matchedOption || value === null || value === undefined) {
321
- this.#setValue(matchedOption);
298
+ if (matchedOption) {
299
+ this.#setValue(matchedOption.value,matchedOption);
322
300
  return true;
323
301
  } else {
324
302
  this.#notFoundedValue = value;
325
303
  return false;
326
304
  }
327
305
  }
328
- #setValue(value: TOption) {
306
+ //null option mean deselect all
307
+ #changeSelectedOption(option:JBOptionWebComponent<TValue>|null){
308
+ this.#optionList.forEach((x)=>x.selected = false);
309
+ if(option){
310
+ option.selected = true;
311
+ this.#selectedOption = option;
312
+ }
313
+ }
314
+ #setValue(value: TValue,option:JBOptionWebComponent<TValue>|null) {
329
315
  this.#notFoundedValue = null;
330
316
  this.#value = value;
331
317
  if (value === null || value === undefined) {
332
318
  this.textValue = "";
333
319
  this.#setSelectedOptionDom(null);
320
+ //will deselect all option
321
+ this.#changeSelectedOption(null);
334
322
  this.elements.componentWrapper.classList.remove("--has-value");
335
323
  //show placeholder when user empty data
336
324
  if (!(this.isMobileDevice && this.isOpen)) {
@@ -338,6 +326,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
338
326
  }
339
327
  } else {
340
328
  this.textValue = "";
329
+ this.#changeSelectedOption(option);
341
330
  this.#setSelectedOptionDom(value);
342
331
  this.elements.componentWrapper.classList.add("--has-value");
343
332
  //hide placeholder when user select data
@@ -355,24 +344,24 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
355
344
  this.focus();
356
345
  }
357
346
  }
358
- #onInputKeyPress(e:KeyboardEvent) {
359
- const eventOptions:KeyboardEventInit = {
360
- altKey:e.altKey,
361
- bubbles:e.bubbles,
362
- cancelable:e.cancelable,
363
- code:e.code,
364
- composed:e.composed,
365
- ctrlKey:e.ctrlKey,
366
- detail:e.detail,
367
- isComposing:e.isComposing,
368
- key:e.key,
369
- location:e.location,
370
- metaKey:e.metaKey,
371
- view:e.view,
372
- repeat:e.repeat,
373
- shiftKey:e.shiftKey
347
+ #onInputKeyPress(e: KeyboardEvent) {
348
+ const eventOptions: KeyboardEventInit = {
349
+ altKey: e.altKey,
350
+ bubbles: e.bubbles,
351
+ cancelable: e.cancelable,
352
+ code: e.code,
353
+ composed: e.composed,
354
+ ctrlKey: e.ctrlKey,
355
+ detail: e.detail,
356
+ isComposing: e.isComposing,
357
+ key: e.key,
358
+ location: e.location,
359
+ metaKey: e.metaKey,
360
+ view: e.view,
361
+ repeat: e.repeat,
362
+ shiftKey: e.shiftKey
374
363
  };
375
- const event = new KeyboardEvent("keypress",eventOptions);
364
+ const event = new KeyboardEvent("keypress", eventOptions);
376
365
  this.dispatchEvent(event);
377
366
  }
378
367
  #onInputBeforeInput(e: InputEvent) {
@@ -383,8 +372,9 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
383
372
  const inputtedText = (e.target as HTMLInputElement).value;
384
373
  this.textValue = inputtedText;
385
374
  this.#handleSelectedValueDisplay(inputtedText);
386
- this.#validation.checkValidity({showError:false});
375
+ this.#validation.checkValidity({ showError: false });
387
376
  this.#dispatchInputEvent(e);
377
+ this.#updateListEmptyPlaceholder();
388
378
  }
389
379
  #dispatchInputEvent(e: InputEvent) {
390
380
  const event = new InputEvent("input", {
@@ -471,7 +461,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
471
461
  this.textValue = "";
472
462
  this.#handleSelectedValueDisplay("");
473
463
  this.#hideOptionList();
474
- this.#validation.checkValidity({showError:true});
464
+ this.#validation.checkValidity({ showError: true });
475
465
  if (this.isMobileDevice) {
476
466
  if (this.value) {
477
467
  this.elements.input.placeholder = "";
@@ -488,85 +478,58 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
488
478
  this.elements.optionListWrapper.classList.remove("--show");
489
479
  }
490
480
  #updateOptionList(filterText: string) {
491
- this.displayOptionList = this.#filterOptionList(filterText);
492
- }
493
- #updateOptionListDOM() {
494
- const optionDomList: HTMLElement[] = [];
495
- this.displayOptionList.forEach((item) => {
496
- const optionDOM = this.#createOptionDOM(item);
497
- optionDomList.push(optionDOM);
498
- });
499
- this.elements.optionList.innerHTML = "";
500
- optionDomList.forEach((optionElement) => {
501
- this.elements.optionList.appendChild(optionElement);
502
- });
481
+ const event = new CustomEvent("filter-change", { detail: { filterText }, bubbles: false, cancelable: false, composed: false });
482
+ this.dispatchEvent(event);
503
483
  }
504
- #createOptionDOM(item: TOption): JBSelectOptionElement<TOption> {
505
- let optionDOM: JBSelectOptionElement<TOption> | null = null;
506
- const isSelected =
507
- this.#getOptionValue(this.#value) == this.#getOptionValue(item);
508
- if (typeof this.callbacks.getOptionDOM == "function") {
509
- optionDOM = this.callbacks.getOptionDOM(
510
- item,
511
- this.#onOptionClicked.bind(this),
512
- isSelected
513
- );
514
- } else {
515
- optionDOM = this.#createDefaultOptionDom(item, isSelected);
484
+ #onOptionSelect(e: CustomEvent) {
485
+ const prevValue = this.#value;
486
+ const prevOption = this.#selectedOption;
487
+ //because jb-option may be in another shadow dom like jb-option-list we have to get first composed element as a target
488
+ const target = (e.composedPath()[0] as JBOptionWebComponent<TValue>);
489
+ if (target instanceof JBOptionWebComponent) {
490
+ const value = target.value;
491
+ this.#selectOption(value,target);
492
+ this.blur();
493
+ const dispatchedEvent = this.#dispatchOnChangeEvent();
494
+ if (dispatchedEvent.defaultPrevented) {
495
+ e.preventDefault();
496
+ this.#selectOption(prevValue,prevOption);
497
+ }
516
498
  }
517
- optionDOM.value = item;
518
- return optionDOM;
519
- }
520
499
 
521
- #createDefaultOptionDom(item: TOption, isSelected: boolean): JBSelectOptionElement<TOption> {
522
- const optionElement = document.createElement("div");
523
- optionElement.classList.add("select-option");
524
- if (isSelected) {
525
- optionElement.classList.add("--selected-option");
526
- }
527
- //it has default function who return exact same input
528
- optionElement.innerHTML = this.#getOptionTitle(item);
529
- optionElement.addEventListener("click", this.#onOptionClicked.bind(this));
530
- return optionElement;
531
500
  }
532
- #onOptionClicked(e: MouseEvent) {
533
- const prevValue = this.#value;
534
- const value = (e.currentTarget as JBSelectOptionElement<TOption>).value;
535
- this.#selectOption(value);
536
- this.blur();
537
- const dispatchedEvent = this.#dispatchOnChangeEvent();
538
- if(dispatchedEvent.defaultPrevented){
539
- e.preventDefault();
540
- this.#selectOption(prevValue);
501
+ //called when an jb-Option connected to the dom
502
+ #onOptionConnected(e: CustomEvent) {
503
+ e.stopPropagation();
504
+ const target = (e.composedPath()[0] as JBOptionWebComponent<TValue>);
505
+ target.addEventListener("jb-option-disconnected",this.#onOptionDisconnected.bind(this),{once:true,passive:true});
506
+ target.setSelectElement(this);
507
+ this.#optionList.add(target);
508
+ if(this.#notFoundedValue){
509
+ this.#setValueOnOptionListChanged();
510
+ }
511
+ this.#updateListEmptyPlaceholder();
512
+ }
513
+ #onOptionDisconnected(e:CustomEvent){
514
+ e.stopPropagation();
515
+ const target = e.target as JBOptionWebComponent<TValue>;
516
+ this.#optionList.delete(target);
517
+ this.#updateListEmptyPlaceholder();
518
+ if(target.value == this.#value){
519
+ this.#setValueOnOptionListChanged();
541
520
  }
542
521
  }
543
- #selectOption(value: TOption) {
544
- this.#setValue(value);
522
+
523
+ #selectOption(value: TValue, optionDom:JBOptionWebComponent<TValue>) {
524
+ this.#setValue(value,optionDom);
545
525
  this.#checkValidity(true);
546
526
  }
547
- #filterOptionList(filterString: string): TOption[] {
548
- const displayOptionList: TOption[] = [];
549
- this.optionList.filter((option) => {
550
- const optionTitle = this.#getOptionTitle(option);
551
- const isString = typeof optionTitle == "string";
552
- if (isString && optionTitle.includes(filterString)) {
553
- displayOptionList.push(option);
554
- }
555
- if (!isString) {
556
- console.warn(
557
- "the provided values for optionsList is not of type string.",
558
- { option, title: optionTitle }
559
- );
560
- }
561
- });
562
- return displayOptionList;
563
- }
564
527
  /**
565
528
  * @description show given string as a error in message place
566
529
  * @public
567
530
  */
568
531
  showValidationError(error: ShowValidationErrorInput | string) {
569
- const message = typeof error == "string"?error:error.message;
532
+ const message = typeof error == "string" ? error : error.message;
570
533
  this.elements.messageBox.innerHTML = message;
571
534
  this.elements.messageBox.classList.add("--error");
572
535
  }
@@ -575,11 +538,11 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
575
538
  this.elements.messageBox.classList.remove("--error");
576
539
  }
577
540
  #dispatchOnChangeEvent() {
578
- const event = new Event("change",{bubbles:true,cancelable:true});
541
+ const event = new Event("change", { bubbles: true, cancelable: true });
579
542
  this.dispatchEvent(event);
580
543
  return event;
581
544
  }
582
- #setSelectedOptionDom(value: TOption) {
545
+ #setSelectedOptionDom(value: TValue) {
583
546
  //when user select option or value changed in any condition we set selected option DOM
584
547
  this.elements.selectedValueWrapper.innerHTML = "";
585
548
  //if value was null or undefined it remain empty
@@ -588,62 +551,35 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
588
551
  this.elements.selectedValueWrapper.appendChild(selectedOptionDom);
589
552
  }
590
553
  }
591
- #createSelectedValueDom(value: TOption) {
554
+ #createSelectedValueDom(value: TValue) {
592
555
  if (typeof this.callbacks.getSelectedValueDOM == "function") {
593
- return this.callbacks.getSelectedValueDOM(value);
556
+ return this.callbacks.getSelectedValueDOM(value,this.#selectedOption);
594
557
  } else {
595
- return this.#createDefaultSelectedValueDom(value);
558
+ return this.#createDefaultSelectedValueDom();
596
559
  }
597
560
  }
598
- #createDefaultSelectedValueDom(value: TOption) {
599
- const valueText = this.#getOptionTitle(value);
561
+ #createDefaultSelectedValueDom() {
562
+ //TODO: put some backup way for when we have value but no option provided
563
+ let contentNodes:Node[] = [];
564
+ if(this.#selectedOption){
565
+ contentNodes = this.#selectedOption.optionContent;
566
+ }
600
567
  const selectedOptionDom = document.createElement("div");
601
568
  selectedOptionDom.classList.add("selected-value");
602
- selectedOptionDom.innerHTML = valueText;
569
+ selectedOptionDom.append(...contentNodes.map(n=>n.cloneNode()));
603
570
  return selectedOptionDom;
604
571
  }
605
- #getOptionValue(option: TOption):TValue{
606
- if (this.callbacks.getOptionValue && typeof this.callbacks.getOptionValue !== "function") {
607
- console.error("getOptionValue callback is not a function");
608
- }
609
- try {
610
- if(typeof this.callbacks.getOptionValue == "function"){
611
- return this.callbacks.getOptionValue(option);
612
- }else{
613
- return option as unknown as TValue;
614
- }
615
- } catch (e) {
616
- console.error(
617
- `Invalid getOptionValue callback Result, must be a function that returns the value of an option`,
618
- option
619
- );
620
- }
621
- }
622
- #getOptionTitle(option: TOption): string {
623
- if (typeof this.callbacks.getOptionTitle !== "function") {
624
- console.error("getOptionTitle callback is not a function");
625
- }
626
- try {
627
- return this.callbacks.getOptionTitle(option);
628
- } catch (e) {
629
- console.error(
630
- `Invalid getOptionTitle callback Result, must be a function that returns the value of an option`,
631
- option
632
- );
633
- }
634
- return "";
635
- }
636
- #getInsideValidation(){
637
- const ValidationList:ValidationItem<ValidationValue<TOption,TValue>>[] = [];
638
- if(this.required){
572
+ #getInsideValidation() {
573
+ const ValidationList: ValidationItem<ValidationValue<TValue>>[] = [];
574
+ if (this.required) {
639
575
  const label = this.getAttribute("label") || "";
640
576
  const message = `${label} حتما باید انتخاب شود`;
641
577
  ValidationList.push({
642
- validator:({selectedOption})=>{
643
- return selectedOption !== null && selectedOption !== undefined;
578
+ validator: ({ value }) => {
579
+ return value !== null && value !== undefined;
644
580
  },
645
- message:message,
646
- stateType:"valueMissing"
581
+ message: message,
582
+ stateType: "valueMissing"
647
583
  });
648
584
  }
649
585
  return ValidationList;
@@ -651,7 +587,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
651
587
  //
652
588
  #checkValidity(showError: boolean) {
653
589
  if (!this.isAutoValidationDisabled) {
654
- return this.#validation.checkValidity({showError});
590
+ return this.#validation.checkValidity({ showError });
655
591
  }
656
592
  }
657
593
  /**
@@ -660,7 +596,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
660
596
  * this method used by #internal of component
661
597
  */
662
598
  checkValidity(): boolean {
663
- const validationResult = this.#validation.checkValiditySync({showError:false});
599
+ const validationResult = this.#validation.checkValiditySync({ showError: false });
664
600
  if (!validationResult.isAllValid) {
665
601
  const event = new CustomEvent('invalid');
666
602
  this.dispatchEvent(event);
@@ -672,7 +608,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
672
608
  * @description this method used to check for validity and show error to user
673
609
  */
674
610
  reportValidity(): boolean {
675
- const validationResult = this.#validation.checkValiditySync({showError:true});
611
+ const validationResult = this.#validation.checkValiditySync({ showError: true });
676
612
  if (!validationResult.isAllValid) {
677
613
  const event = new CustomEvent('invalid');
678
614
  this.dispatchEvent(event);
@@ -682,7 +618,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
682
618
  /**
683
619
  * @description this method called on every checkValidity calls and update validation result of #internal
684
620
  */
685
- #setValidationResult(result: ValidationResult<ValidationValue<TOption,TValue>>) {
621
+ #setValidationResult(result: ValidationResult<ValidationValue<TValue>>) {
686
622
  if (result.isAllValid) {
687
623
  this.#internals?.setValidity({}, '');
688
624
  } else {
@@ -692,7 +628,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
692
628
  if (!res.isValid) {
693
629
  if (res.validation.stateType) {
694
630
  states[res.validation.stateType] = true;
695
- }else{
631
+ } else {
696
632
  states["customError"] = true;
697
633
  }
698
634
  if (message == '') { message = res.message; }
@@ -702,9 +638,10 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
702
638
  this.#internals?.setValidity(states, message);
703
639
  }
704
640
  }
705
- get validationMessage(){
641
+ get validationMessage() {
706
642
  return this.#internals?.validationMessage || this.#validation.resultSummary.message;
707
643
  }
644
+
708
645
  }
709
646
  const myElementNotExists = !customElements.get("jb-select");
710
647
  if (myElementNotExists) {