jb-select 5.3.2 → 6.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.
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 +165 -238
  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,23 +282,29 @@ 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);
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);
300
+ matchedOption.selected = true;
322
301
  return true;
323
302
  } else {
324
303
  this.#notFoundedValue = value;
325
304
  return false;
326
305
  }
327
306
  }
328
- #setValue(value: TOption) {
307
+ #setValue(value: TValue) {
329
308
  this.#notFoundedValue = null;
330
309
  this.#value = value;
331
310
  if (value === null || value === undefined) {
@@ -355,24 +334,24 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
355
334
  this.focus();
356
335
  }
357
336
  }
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
337
+ #onInputKeyPress(e: KeyboardEvent) {
338
+ const eventOptions: KeyboardEventInit = {
339
+ altKey: e.altKey,
340
+ bubbles: e.bubbles,
341
+ cancelable: e.cancelable,
342
+ code: e.code,
343
+ composed: e.composed,
344
+ ctrlKey: e.ctrlKey,
345
+ detail: e.detail,
346
+ isComposing: e.isComposing,
347
+ key: e.key,
348
+ location: e.location,
349
+ metaKey: e.metaKey,
350
+ view: e.view,
351
+ repeat: e.repeat,
352
+ shiftKey: e.shiftKey
374
353
  };
375
- const event = new KeyboardEvent("keypress",eventOptions);
354
+ const event = new KeyboardEvent("keypress", eventOptions);
376
355
  this.dispatchEvent(event);
377
356
  }
378
357
  #onInputBeforeInput(e: InputEvent) {
@@ -383,8 +362,9 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
383
362
  const inputtedText = (e.target as HTMLInputElement).value;
384
363
  this.textValue = inputtedText;
385
364
  this.#handleSelectedValueDisplay(inputtedText);
386
- this.#validation.checkValidity({showError:false});
365
+ this.#validation.checkValidity({ showError: false });
387
366
  this.#dispatchInputEvent(e);
367
+ this.#updateListEmptyPlaceholder();
388
368
  }
389
369
  #dispatchInputEvent(e: InputEvent) {
390
370
  const event = new InputEvent("input", {
@@ -471,7 +451,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
471
451
  this.textValue = "";
472
452
  this.#handleSelectedValueDisplay("");
473
453
  this.#hideOptionList();
474
- this.#validation.checkValidity({showError:true});
454
+ this.#validation.checkValidity({ showError: true });
475
455
  if (this.isMobileDevice) {
476
456
  if (this.value) {
477
457
  this.elements.input.placeholder = "";
@@ -488,85 +468,58 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
488
468
  this.elements.optionListWrapper.classList.remove("--show");
489
469
  }
490
470
  #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
- });
471
+ const event = new CustomEvent("filter-change", { detail: { filterText }, bubbles: false, cancelable: false, composed: false });
472
+ this.dispatchEvent(event);
503
473
  }
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);
474
+ #onOptionSelect(e: CustomEvent) {
475
+ const prevValue = this.#value;
476
+ const prevOption = this.#selectedOption;
477
+ //because jb-option may be in another shadow dom like jb-option-list we have to get first composed element as a target
478
+ const target = (e.composedPath()[0] as JBOptionWebComponent<TValue>);
479
+ if (target instanceof JBOptionWebComponent) {
480
+ const value = target.value;
481
+ this.#selectOption(value,target);
482
+ this.blur();
483
+ const dispatchedEvent = this.#dispatchOnChangeEvent();
484
+ if (dispatchedEvent.defaultPrevented) {
485
+ e.preventDefault();
486
+ this.#selectOption(prevValue,prevOption);
487
+ }
516
488
  }
517
- optionDOM.value = item;
518
- return optionDOM;
519
- }
520
489
 
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
490
  }
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);
491
+ //called when an jb-Option connected to the dom
492
+ #onOptionConnected(e: CustomEvent) {
493
+ e.stopPropagation();
494
+ const target = (e.composedPath()[0] as JBOptionWebComponent<TValue>);
495
+ target.addEventListener("jb-option-disconnected",this.#onOptionDisconnected.bind(this),{once:true,passive:true});
496
+ target.setSelectElement(this);
497
+ this.#optionList.add(target);
498
+ if(this.#notFoundedValue){
499
+ this.#setValueOnOptionListChanged();
500
+ }
501
+ this.#updateListEmptyPlaceholder();
502
+ }
503
+ #onOptionDisconnected(e:CustomEvent){
504
+ e.stopPropagation();
505
+ const target = e.target as JBOptionWebComponent<TValue>;
506
+ this.#optionList.delete(target);
507
+ this.#updateListEmptyPlaceholder();
508
+ if(target.value == this.#value){
509
+ this.#setValueOnOptionListChanged();
541
510
  }
542
511
  }
543
- #selectOption(value: TOption) {
512
+ #selectOption(value: TValue, optionDom:JBOptionWebComponent<TValue>) {
513
+ this.#selectedOption = optionDom;
544
514
  this.#setValue(value);
545
515
  this.#checkValidity(true);
546
516
  }
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
517
  /**
565
518
  * @description show given string as a error in message place
566
519
  * @public
567
520
  */
568
521
  showValidationError(error: ShowValidationErrorInput | string) {
569
- const message = typeof error == "string"?error:error.message;
522
+ const message = typeof error == "string" ? error : error.message;
570
523
  this.elements.messageBox.innerHTML = message;
571
524
  this.elements.messageBox.classList.add("--error");
572
525
  }
@@ -575,11 +528,11 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
575
528
  this.elements.messageBox.classList.remove("--error");
576
529
  }
577
530
  #dispatchOnChangeEvent() {
578
- const event = new Event("change",{bubbles:true,cancelable:true});
531
+ const event = new Event("change", { bubbles: true, cancelable: true });
579
532
  this.dispatchEvent(event);
580
533
  return event;
581
534
  }
582
- #setSelectedOptionDom(value: TOption) {
535
+ #setSelectedOptionDom(value: TValue) {
583
536
  //when user select option or value changed in any condition we set selected option DOM
584
537
  this.elements.selectedValueWrapper.innerHTML = "";
585
538
  //if value was null or undefined it remain empty
@@ -588,62 +541,35 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
588
541
  this.elements.selectedValueWrapper.appendChild(selectedOptionDom);
589
542
  }
590
543
  }
591
- #createSelectedValueDom(value: TOption) {
544
+ #createSelectedValueDom(value: TValue) {
592
545
  if (typeof this.callbacks.getSelectedValueDOM == "function") {
593
- return this.callbacks.getSelectedValueDOM(value);
546
+ return this.callbacks.getSelectedValueDOM(value,this.#selectedOption);
594
547
  } else {
595
- return this.#createDefaultSelectedValueDom(value);
548
+ return this.#createDefaultSelectedValueDom();
596
549
  }
597
550
  }
598
- #createDefaultSelectedValueDom(value: TOption) {
599
- const valueText = this.#getOptionTitle(value);
551
+ #createDefaultSelectedValueDom() {
552
+ //TODO: put some backup way for when we have value but no option provided
553
+ let contentNodes:Node[] = [];
554
+ if(this.#selectedOption){
555
+ contentNodes = this.#selectedOption.optionContent;
556
+ }
600
557
  const selectedOptionDom = document.createElement("div");
601
558
  selectedOptionDom.classList.add("selected-value");
602
- selectedOptionDom.innerHTML = valueText;
559
+ selectedOptionDom.append(...contentNodes.map(n=>n.cloneNode()));
603
560
  return selectedOptionDom;
604
561
  }
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){
562
+ #getInsideValidation() {
563
+ const ValidationList: ValidationItem<ValidationValue<TValue>>[] = [];
564
+ if (this.required) {
639
565
  const label = this.getAttribute("label") || "";
640
566
  const message = `${label} حتما باید انتخاب شود`;
641
567
  ValidationList.push({
642
- validator:({selectedOption})=>{
643
- return selectedOption !== null && selectedOption !== undefined;
568
+ validator: ({ value }) => {
569
+ return value !== null && value !== undefined;
644
570
  },
645
- message:message,
646
- stateType:"valueMissing"
571
+ message: message,
572
+ stateType: "valueMissing"
647
573
  });
648
574
  }
649
575
  return ValidationList;
@@ -651,7 +577,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
651
577
  //
652
578
  #checkValidity(showError: boolean) {
653
579
  if (!this.isAutoValidationDisabled) {
654
- return this.#validation.checkValidity({showError});
580
+ return this.#validation.checkValidity({ showError });
655
581
  }
656
582
  }
657
583
  /**
@@ -660,7 +586,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
660
586
  * this method used by #internal of component
661
587
  */
662
588
  checkValidity(): boolean {
663
- const validationResult = this.#validation.checkValiditySync({showError:false});
589
+ const validationResult = this.#validation.checkValiditySync({ showError: false });
664
590
  if (!validationResult.isAllValid) {
665
591
  const event = new CustomEvent('invalid');
666
592
  this.dispatchEvent(event);
@@ -672,7 +598,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
672
598
  * @description this method used to check for validity and show error to user
673
599
  */
674
600
  reportValidity(): boolean {
675
- const validationResult = this.#validation.checkValiditySync({showError:true});
601
+ const validationResult = this.#validation.checkValiditySync({ showError: true });
676
602
  if (!validationResult.isAllValid) {
677
603
  const event = new CustomEvent('invalid');
678
604
  this.dispatchEvent(event);
@@ -682,7 +608,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
682
608
  /**
683
609
  * @description this method called on every checkValidity calls and update validation result of #internal
684
610
  */
685
- #setValidationResult(result: ValidationResult<ValidationValue<TOption,TValue>>) {
611
+ #setValidationResult(result: ValidationResult<ValidationValue<TValue>>) {
686
612
  if (result.isAllValid) {
687
613
  this.#internals?.setValidity({}, '');
688
614
  } else {
@@ -692,7 +618,7 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
692
618
  if (!res.isValid) {
693
619
  if (res.validation.stateType) {
694
620
  states[res.validation.stateType] = true;
695
- }else{
621
+ } else {
696
622
  states["customError"] = true;
697
623
  }
698
624
  if (message == '') { message = res.message; }
@@ -702,9 +628,10 @@ export class JBSelectWebComponent<TOption = any, TValue = TOption> extends HTMLE
702
628
  this.#internals?.setValidity(states, message);
703
629
  }
704
630
  }
705
- get validationMessage(){
631
+ get validationMessage() {
706
632
  return this.#internals?.validationMessage || this.#validation.resultSummary.message;
707
633
  }
634
+
708
635
  }
709
636
  const myElementNotExists = !customElements.get("jb-select");
710
637
  if (myElementNotExists) {