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.
- package/README.md +67 -29
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.br +0 -0
- package/dist/index.cjs.js.gz +0 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.br +0 -0
- package/dist/index.js.gz +0 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.br +0 -0
- package/dist/index.umd.js.gz +0 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/web-component/jb-select/lib/index.d.ts +7 -0
- package/dist/web-component/jb-select/lib/jb-option/jb-option.d.ts +21 -0
- package/dist/web-component/jb-select/lib/jb-option/render.d.ts +1 -0
- package/dist/web-component/jb-select/lib/jb-option/types.d.ts +4 -0
- package/dist/web-component/jb-select/lib/jb-option-list/jb-option-list.d.ts +10 -0
- package/dist/web-component/jb-select/lib/jb-option-list/types.d.ts +5 -0
- package/dist/web-component/jb-select/lib/jb-select.d.ts +3 -7
- package/dist/web-component/jb-select/lib/types.d.ts +6 -10
- package/index.js +2 -2
- package/lib/index.ts +8 -0
- package/lib/jb-option/jb-option.scss +23 -0
- package/lib/jb-option/jb-option.ts +136 -0
- package/lib/jb-option/render.ts +9 -0
- package/lib/jb-option/types.ts +4 -0
- package/lib/jb-option-list/jb-option-list.ts +125 -0
- package/lib/jb-option-list/types.ts +5 -0
- package/lib/jb-select.html +1 -1
- package/lib/jb-select.scss +36 -45
- package/lib/jb-select.ts +176 -239
- package/lib/types.ts +8 -9
- package/package.json +5 -3
- package/react/README.md +188 -0
- package/react/dist/common/hooks/use-event.d.ts +3 -0
- package/react/dist/common/hooks/useLazyRef.d.ts +4 -0
- package/react/dist/common/hooks/useMobx.d.ts +4 -0
- package/react/dist/common/scripts/device-detection.d.ts +1 -0
- package/react/dist/common/scripts/persian-helper.d.ts +3 -0
- package/react/dist/index.cjs.js +122 -0
- package/react/dist/index.cjs.js.map +1 -0
- package/react/dist/index.js +118 -0
- package/react/dist/index.js.map +1 -0
- package/react/dist/index.umd.js +125 -0
- package/react/dist/index.umd.js.map +1 -0
- package/react/dist/web-component/jb-select/react/lib/JBOption.d.ts +19 -0
- package/react/dist/web-component/jb-select/react/lib/JBOptionList.d.ts +22 -0
- package/react/dist/web-component/jb-select/react/lib/JBSelect.d.ts +38 -0
- package/react/dist/web-component/jb-select/react/lib/index.d.ts +3 -0
- package/react/index.js +1 -0
- package/react/lib/JBOption.tsx +45 -0
- package/react/lib/JBOptionList.tsx +59 -0
- package/react/lib/JBSelect.tsx +105 -0
- package/react/lib/index.tsx +3 -0
- package/react/package.json +33 -0
- package/react/tsconfig.json +19 -0
- package/dist/jb-select.cjs.js +0 -2
- package/dist/jb-select.cjs.js.br +0 -0
- package/dist/jb-select.cjs.js.gz +0 -0
- package/dist/jb-select.cjs.js.map +0 -1
- package/dist/jb-select.js +0 -2
- package/dist/jb-select.js.br +0 -0
- package/dist/jb-select.js.gz +0 -0
- package/dist/jb-select.js.map +0 -1
- package/dist/jb-select.umd.js +0 -2
- package/dist/jb-select.umd.js.br +0 -0
- package/dist/jb-select.umd.js.gz +0 -0
- 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<
|
|
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:
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.#
|
|
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.#
|
|
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<
|
|
82
|
+
get #ValidationValue(): ValidationValue<TValue> {
|
|
120
83
|
return {
|
|
121
|
-
inputtedText:this.#textValue,
|
|
122
|
-
selectedOption:this.#
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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 (
|
|
317
|
-
|
|
294
|
+
if (option.value == value) {
|
|
295
|
+
matchedOption = option;
|
|
318
296
|
}
|
|
319
297
|
});
|
|
320
|
-
if (matchedOption
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
);
|
|
514
|
-
|
|
515
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
this.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
this.#
|
|
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
|
-
|
|
544
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
558
|
+
return this.#createDefaultSelectedValueDom();
|
|
596
559
|
}
|
|
597
560
|
}
|
|
598
|
-
#createDefaultSelectedValueDom(
|
|
599
|
-
|
|
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.
|
|
569
|
+
selectedOptionDom.append(...contentNodes.map(n=>n.cloneNode()));
|
|
603
570
|
return selectedOptionDom;
|
|
604
571
|
}
|
|
605
|
-
#
|
|
606
|
-
|
|
607
|
-
|
|
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:({
|
|
643
|
-
return
|
|
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<
|
|
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) {
|