@nectary/components 2.1.5 → 2.2.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/action-menu/index.js +1 -0
- package/action-menu-option/index.js +1 -0
- package/avatar/index.js +1 -1
- package/button/index.js +3 -2
- package/card/index.js +3 -2
- package/chip/index.js +2 -1
- package/color-menu/index.js +2 -2
- package/color-swatch/index.js +1 -1
- package/date-picker/index.js +11 -9
- package/date-picker/utils.d.ts +1 -0
- package/date-picker/utils.js +8 -0
- package/dialog/index.js +3 -2
- package/emoji/index.js +1 -1
- package/emoji-picker/index.js +2 -1
- package/field/index.js +1 -0
- package/flag/index.js +1 -1
- package/help-tooltip/index.js +1 -0
- package/icon-button/index.js +3 -2
- package/input/index.js +371 -40
- package/input/types.d.ts +14 -0
- package/input/utils.d.ts +24 -0
- package/input/utils.js +302 -1
- package/package.json +1 -1
- package/pop/index.js +5 -5
- package/popover/index.js +1 -0
- package/progress-stepper/index.d.ts +11 -0
- package/progress-stepper/index.js +209 -0
- package/progress-stepper/types.d.ts +22 -0
- package/progress-stepper/types.js +1 -0
- package/progress-stepper-item/index.d.ts +12 -0
- package/progress-stepper-item/index.js +82 -0
- package/progress-stepper-item/types.d.ts +23 -0
- package/progress-stepper-item/types.js +1 -0
- package/progress-stepper-item/utils.d.ts +11 -0
- package/progress-stepper-item/utils.js +13 -0
- package/select-button/index.js +2 -1
- package/select-menu/index.js +2 -1
- package/spinner/index.js +1 -0
- package/stop-events/index.js +1 -0
- package/tabs/index.js +1 -0
- package/tag/index.js +1 -1
- package/textarea/index.js +2 -1
- package/time-picker/index.js +1 -0
- package/time-picker/utils.js +2 -15
- package/tooltip/index.js +4 -3
- package/utils/countries.d.ts +1 -0
- package/utils/countries.json +487 -268
- package/utils/element.d.ts +1 -1
- package/utils/element.js +5 -5
- package/utils/event-target.d.ts +1 -0
- package/utils/event-target.js +9 -0
- package/utils/get-react-event-handler.js +1 -1
package/input/utils.js
CHANGED
|
@@ -1 +1,302 @@
|
|
|
1
|
-
export const inputTypes = ['text', 'password'];
|
|
1
|
+
export const inputTypes = ['text', 'password'];
|
|
2
|
+
const MASK_SYMBOL_LETTER = 'A';
|
|
3
|
+
const MASK_SYMBOL_DIGIT = '0';
|
|
4
|
+
const EMPTY_CHAR = ' ';
|
|
5
|
+
const MASK_MODE_DIGIT = 0;
|
|
6
|
+
const MASK_MODE_LETTER = 1;
|
|
7
|
+
const MASK_MODE_EXACT = 2;
|
|
8
|
+
const MASK_PLACEHOLDER_DELIMITER = '@@';
|
|
9
|
+
const MASK_PLACEHOLDER = '_';
|
|
10
|
+
const testMaskedValue = (maskSymbol, inputChar) => {
|
|
11
|
+
switch (maskSymbol.mode) {
|
|
12
|
+
case MASK_MODE_DIGIT:
|
|
13
|
+
{
|
|
14
|
+
return /\d/.test(inputChar);
|
|
15
|
+
}
|
|
16
|
+
case MASK_MODE_LETTER:
|
|
17
|
+
{
|
|
18
|
+
return /\p{Letter}/u.test(inputChar);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
23
|
+
const isExactMode = maskSymbol => maskSymbol.mode === MASK_MODE_EXACT;
|
|
24
|
+
const isEmptyChar = char => char === EMPTY_CHAR;
|
|
25
|
+
export const getMaskSymbols = maskValue => {
|
|
26
|
+
const res = [];
|
|
27
|
+
const [mask, placeholder] = maskValue.split(MASK_PLACEHOLDER_DELIMITER);
|
|
28
|
+
const maskSymbols = [...mask];
|
|
29
|
+
const placeholderChars = placeholder != null ? [...placeholder] : [];
|
|
30
|
+
for (let maskIndex = 0, placeholderIndex = 0; maskIndex < maskSymbols.length; maskIndex++, placeholderIndex++) {
|
|
31
|
+
if (maskSymbols[maskIndex] === '\\') {
|
|
32
|
+
maskIndex += 1;
|
|
33
|
+
res.push({
|
|
34
|
+
value: maskSymbols[maskIndex],
|
|
35
|
+
mode: MASK_MODE_EXACT,
|
|
36
|
+
placeholder: placeholderChars[placeholderIndex] ?? maskSymbols[maskIndex]
|
|
37
|
+
});
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (maskSymbols[maskIndex] === MASK_SYMBOL_LETTER || maskSymbols[maskIndex] === MASK_SYMBOL_DIGIT) {
|
|
41
|
+
res.push({
|
|
42
|
+
value: maskSymbols[maskIndex],
|
|
43
|
+
mode: maskSymbols[maskIndex] === MASK_SYMBOL_LETTER ? MASK_MODE_LETTER : MASK_MODE_DIGIT,
|
|
44
|
+
placeholder: placeholderChars[placeholderIndex] ?? MASK_PLACEHOLDER
|
|
45
|
+
});
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
res.push({
|
|
49
|
+
value: maskSymbols[maskIndex],
|
|
50
|
+
mode: MASK_MODE_EXACT,
|
|
51
|
+
placeholder: placeholderChars[placeholderIndex] ?? maskSymbols[maskIndex]
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return res;
|
|
55
|
+
};
|
|
56
|
+
const clearCharsSelection = (chars, selectionStart, selectionEnd) => {
|
|
57
|
+
for (let i = selectionStart; i < selectionEnd && i < chars.length; i++) {
|
|
58
|
+
chars[i] = EMPTY_CHAR;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const getPlaceholder = (chars, maskSymbols) => {
|
|
62
|
+
const res = new Array(maskSymbols.length);
|
|
63
|
+
for (let i = 0; i < maskSymbols.length; i++) {
|
|
64
|
+
res[i] = i >= chars.length || isEmptyChar(chars[i]) || isExactMode(maskSymbols[i]) ? maskSymbols[i].placeholder : EMPTY_CHAR;
|
|
65
|
+
}
|
|
66
|
+
return res.join('');
|
|
67
|
+
};
|
|
68
|
+
const isMaskedInputComplete = (chars, maskSymbols) => {
|
|
69
|
+
if (chars.length > maskSymbols.length) {
|
|
70
|
+
throw new Error('chars.length > maskSymbols.length');
|
|
71
|
+
}
|
|
72
|
+
if (chars.length < maskSymbols.length) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
for (let i = 0; i < maskSymbols.length; i++) {
|
|
76
|
+
if (i >= chars.length || !isExactMode(maskSymbols[i]) && !testMaskedValue(maskSymbols[i], chars[i])) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
const getCharsFromInputValue = (inputValue, maskSymbols) => {
|
|
83
|
+
const chars = new Array(maskSymbols.length);
|
|
84
|
+
let i = 0;
|
|
85
|
+
for (const c of inputValue) {
|
|
86
|
+
chars[i++] = c;
|
|
87
|
+
}
|
|
88
|
+
chars.fill(EMPTY_CHAR, i);
|
|
89
|
+
return chars;
|
|
90
|
+
};
|
|
91
|
+
const isCursorAtTheBeginning = (maskSymbols, selectoinStart) => {
|
|
92
|
+
if (selectoinStart >= maskSymbols.length) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
for (let i = 0; i < selectoinStart; i++) {
|
|
96
|
+
if (!isExactMode(maskSymbols[i])) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
};
|
|
102
|
+
const getMergedValue = (chars, maskSymbols) => {
|
|
103
|
+
if (!isMaskedInputComplete(chars, maskSymbols)) {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
const res = new Array(chars.length);
|
|
107
|
+
for (let i = 0; i < chars.length; i++) {
|
|
108
|
+
res[i] = isExactMode(maskSymbols[i]) ? maskSymbols[i].value : chars[i];
|
|
109
|
+
}
|
|
110
|
+
return res.join('');
|
|
111
|
+
};
|
|
112
|
+
const findLastNonEmptyCharIndex = chars => {
|
|
113
|
+
for (let i = chars.length - 1; i >= 0; i--) {
|
|
114
|
+
if (!isEmptyChar(chars[i])) {
|
|
115
|
+
return i;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return -1;
|
|
119
|
+
};
|
|
120
|
+
const compileResult = (chars, maskSymbols, cursorPos) => {
|
|
121
|
+
if (chars.length > maskSymbols.length) {
|
|
122
|
+
chars.length = maskSymbols.length;
|
|
123
|
+
}
|
|
124
|
+
let lastEmptyPos = findLastNonEmptyCharIndex(chars) + 1;
|
|
125
|
+
while (lastEmptyPos < maskSymbols.length && isExactMode(maskSymbols[lastEmptyPos])) {
|
|
126
|
+
++lastEmptyPos;
|
|
127
|
+
}
|
|
128
|
+
if (lastEmptyPos < chars.length) {
|
|
129
|
+
chars.length = lastEmptyPos;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
value: chars.join(''),
|
|
133
|
+
placeholder: getPlaceholder(chars, maskSymbols),
|
|
134
|
+
cursorPos: Math.min(cursorPos, chars.length),
|
|
135
|
+
mergedValue: getMergedValue(chars, maskSymbols)
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
export const deleteContentBackward = (inputValue, maskSymbols, selectionStart, selectionEnd) => {
|
|
139
|
+
if (selectionEnd === 0) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
143
|
+
if (selectionStart !== selectionEnd) {
|
|
144
|
+
clearCharsSelection(chars, selectionStart, selectionEnd);
|
|
145
|
+
return compileResult(chars, maskSymbols, selectionStart);
|
|
146
|
+
}
|
|
147
|
+
let cusrsorPos = selectionStart;
|
|
148
|
+
while (cusrsorPos > 0 && isExactMode(maskSymbols[cusrsorPos - 1])) {
|
|
149
|
+
--cusrsorPos;
|
|
150
|
+
}
|
|
151
|
+
if (cusrsorPos > 0) {
|
|
152
|
+
clearCharsSelection(chars, cusrsorPos - 1, cusrsorPos);
|
|
153
|
+
do {
|
|
154
|
+
--cusrsorPos;
|
|
155
|
+
} while (cusrsorPos > 0 && isExactMode(maskSymbols[cusrsorPos - 1]));
|
|
156
|
+
}
|
|
157
|
+
return compileResult(chars, maskSymbols, cusrsorPos);
|
|
158
|
+
};
|
|
159
|
+
export const deleteContentForward = (inputValue, maskSymbols, selectionStart, selectionEnd) => {
|
|
160
|
+
if (selectionStart >= maskSymbols.length) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
164
|
+
if (selectionStart !== selectionEnd) {
|
|
165
|
+
clearCharsSelection(chars, selectionStart, selectionEnd);
|
|
166
|
+
return compileResult(chars, maskSymbols, selectionEnd);
|
|
167
|
+
}
|
|
168
|
+
let cursorPos = selectionStart;
|
|
169
|
+
while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos])) {
|
|
170
|
+
cursorPos++;
|
|
171
|
+
}
|
|
172
|
+
if (cursorPos < chars.length) {
|
|
173
|
+
clearCharsSelection(chars, cursorPos, cursorPos + 1);
|
|
174
|
+
}
|
|
175
|
+
return compileResult(chars, maskSymbols, cursorPos + 1);
|
|
176
|
+
};
|
|
177
|
+
export const beginMaskedComposition = (inputValue, maskSymbols, selectionStart) => {
|
|
178
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
179
|
+
const placeholder = new Array(chars.length);
|
|
180
|
+
for (let i = 0; i < maskSymbols.length; i++) {
|
|
181
|
+
placeholder[i] = isEmptyChar(chars[i]) || isExactMode(maskSymbols[i]) ? maskSymbols[i].placeholder : EMPTY_CHAR;
|
|
182
|
+
}
|
|
183
|
+
placeholder[selectionStart] = EMPTY_CHAR;
|
|
184
|
+
chars.splice(selectionStart, 1);
|
|
185
|
+
return {
|
|
186
|
+
value: chars.join(''),
|
|
187
|
+
placeholder: placeholder.join('')
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
export const endMaskedComposition = (inputValue, data, maskSymbols, selectionStart, wasValueInserted) => {
|
|
191
|
+
let cursorPos = selectionStart;
|
|
192
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
193
|
+
const dataChars = [...data];
|
|
194
|
+
if (!wasValueInserted) {
|
|
195
|
+
chars.splice(cursorPos, 0, ...dataChars);
|
|
196
|
+
chars.length = maskSymbols.length;
|
|
197
|
+
cursorPos += dataChars.length;
|
|
198
|
+
}
|
|
199
|
+
cursorPos -= dataChars.length;
|
|
200
|
+
clearCharsSelection(chars, cursorPos, cursorPos + dataChars.length);
|
|
201
|
+
while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos])) {
|
|
202
|
+
cursorPos++;
|
|
203
|
+
}
|
|
204
|
+
if (cursorPos >= maskSymbols.length) {
|
|
205
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
206
|
+
}
|
|
207
|
+
for (let dataPos = 0; dataPos < dataChars.length && cursorPos < maskSymbols.length; ++dataPos, ++cursorPos) {
|
|
208
|
+
const data = dataChars[dataPos];
|
|
209
|
+
if (isEmptyChar(chars[cursorPos]) && testMaskedValue(maskSymbols[cursorPos], data)) {
|
|
210
|
+
chars[cursorPos] = data;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos])) {
|
|
214
|
+
cursorPos++;
|
|
215
|
+
}
|
|
216
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
217
|
+
};
|
|
218
|
+
export const insertText = (inputValue, data, maskSymbols, selectionStart, selectionEnd) => {
|
|
219
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
220
|
+
let cursorPos = selectionStart;
|
|
221
|
+
clearCharsSelection(chars, selectionStart, selectionEnd);
|
|
222
|
+
while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos])) {
|
|
223
|
+
cursorPos++;
|
|
224
|
+
}
|
|
225
|
+
if (cursorPos >= maskSymbols.length) {
|
|
226
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
227
|
+
}
|
|
228
|
+
if (testMaskedValue(maskSymbols[cursorPos], data)) {
|
|
229
|
+
if (cursorPos >= chars.length) {
|
|
230
|
+
chars.length = cursorPos + 1;
|
|
231
|
+
}
|
|
232
|
+
chars[cursorPos] = data;
|
|
233
|
+
do {
|
|
234
|
+
cursorPos++;
|
|
235
|
+
} while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos]));
|
|
236
|
+
}
|
|
237
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
238
|
+
};
|
|
239
|
+
export const insertFromPaste = (inputValue, data, maskSymbols, selectionStart, selectionEnd) => {
|
|
240
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
241
|
+
let cursorPos = selectionStart;
|
|
242
|
+
clearCharsSelection(chars, selectionStart, selectionEnd);
|
|
243
|
+
if (isCursorAtTheBeginning(maskSymbols, cursorPos)) {
|
|
244
|
+
cursorPos = 0;
|
|
245
|
+
} else {
|
|
246
|
+
while (cursorPos < maskSymbols.length && isExactMode(maskSymbols[cursorPos])) {
|
|
247
|
+
cursorPos++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (cursorPos >= maskSymbols.length) {
|
|
251
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
252
|
+
}
|
|
253
|
+
const dataChars = [...data];
|
|
254
|
+
for (let dataPos = 0; dataPos < dataChars.length && cursorPos < maskSymbols.length;) {
|
|
255
|
+
const data = dataChars[dataPos];
|
|
256
|
+
const maskSymbol = maskSymbols[cursorPos];
|
|
257
|
+
if (isExactMode(maskSymbol)) {
|
|
258
|
+
if (maskSymbol.value === data) {
|
|
259
|
+
++dataPos;
|
|
260
|
+
}
|
|
261
|
+
++cursorPos;
|
|
262
|
+
} else {
|
|
263
|
+
if (testMaskedValue(maskSymbol, data)) {
|
|
264
|
+
chars[cursorPos] = data;
|
|
265
|
+
++cursorPos;
|
|
266
|
+
}
|
|
267
|
+
++dataPos;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return compileResult(chars, maskSymbols, cursorPos);
|
|
271
|
+
};
|
|
272
|
+
export const splitValueAndMask = (inputValue, maskSymbols) => {
|
|
273
|
+
const chars = getCharsFromInputValue('', maskSymbols);
|
|
274
|
+
const dataChars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
275
|
+
let cursorPos = 0;
|
|
276
|
+
for (let dataPos = 0; dataPos < dataChars.length && cursorPos < maskSymbols.length;) {
|
|
277
|
+
const data = dataChars[dataPos];
|
|
278
|
+
const maskSymbol = maskSymbols[cursorPos];
|
|
279
|
+
if (isExactMode(maskSymbol)) {
|
|
280
|
+
if (maskSymbol.value === data) {
|
|
281
|
+
++dataPos;
|
|
282
|
+
}
|
|
283
|
+
++cursorPos;
|
|
284
|
+
} else {
|
|
285
|
+
if (testMaskedValue(maskSymbol, data)) {
|
|
286
|
+
chars[cursorPos] = data;
|
|
287
|
+
++cursorPos;
|
|
288
|
+
}
|
|
289
|
+
++dataPos;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return compileResult(chars, maskSymbols, chars.length);
|
|
293
|
+
};
|
|
294
|
+
export const getMergedValueSliced = (inputValue, maskSymbols, selectionStart, selectionEnd) => {
|
|
295
|
+
const chars = getCharsFromInputValue(inputValue, maskSymbols);
|
|
296
|
+
for (let i = selectionStart; i < selectionEnd && i < maskSymbols.length; i++) {
|
|
297
|
+
if (isExactMode(maskSymbols[i])) {
|
|
298
|
+
chars[i] = maskSymbols[i].value;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return chars.slice(selectionStart, selectionEnd).join('');
|
|
302
|
+
};
|
package/package.json
CHANGED
package/pop/index.js
CHANGED
|
@@ -109,7 +109,7 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
|
|
|
109
109
|
{
|
|
110
110
|
if (isAttrTrue(newVal)) {
|
|
111
111
|
requestAnimationFrame(() => {
|
|
112
|
-
if (this.
|
|
112
|
+
if (this.isDomConnected && getBooleanAttribute(this, 'open')) {
|
|
113
113
|
this.#onExpand();
|
|
114
114
|
}
|
|
115
115
|
});
|
|
@@ -149,7 +149,7 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
|
|
|
149
149
|
return item;
|
|
150
150
|
}
|
|
151
151
|
#onExpand() {
|
|
152
|
-
if (!this.
|
|
152
|
+
if (!this.isDomConnected || this.#$dialog.open) {
|
|
153
153
|
return;
|
|
154
154
|
}
|
|
155
155
|
this.#$targetSlot.addEventListener('blur', this.#stopEventPropagation, true);
|
|
@@ -194,7 +194,7 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
|
|
|
194
194
|
this.#$targetOpenSlot.removeEventListener('focus', this.#stopEventPropagation, true);
|
|
195
195
|
if (!isElementFocused(this.#targetActiveElement)) {
|
|
196
196
|
requestAnimationFrame(() => {
|
|
197
|
-
if (this.
|
|
197
|
+
if (this.isDomConnected && this.#$dialog.open) {
|
|
198
198
|
this.#$targetOpenSlot.addEventListener('focus', this.#stopEventPropagation, true);
|
|
199
199
|
this.#targetActiveElement.focus();
|
|
200
200
|
this.#$targetOpenSlot.removeEventListener('focus', this.#stopEventPropagation, true);
|
|
@@ -209,7 +209,7 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
|
|
|
209
209
|
});
|
|
210
210
|
window.addEventListener('resize', this.#onResize);
|
|
211
211
|
requestAnimationFrame(() => {
|
|
212
|
-
if (this.
|
|
212
|
+
if (this.isDomConnected && this.#$dialog.open) {
|
|
213
213
|
this.#$contentSlot.addEventListener('slotchange', this.#onContentSlotChange);
|
|
214
214
|
}
|
|
215
215
|
});
|
|
@@ -254,7 +254,7 @@ defineCustomElement('sinch-pop', class extends NectaryElement {
|
|
|
254
254
|
if (!isElementFocused(this.#targetActiveElement)) {
|
|
255
255
|
const $targetEl = this.#targetActiveElement;
|
|
256
256
|
requestAnimationFrame(() => {
|
|
257
|
-
if (this.
|
|
257
|
+
if (this.isDomConnected && !this.#$dialog.open) {
|
|
258
258
|
this.#$targetSlot.addEventListener('focus', this.#stopEventPropagation, true);
|
|
259
259
|
$targetEl.focus({
|
|
260
260
|
preventScroll: true
|
package/popover/index.js
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TSinchProgressStepperElement, TSinchProgressStepperReact } from './types';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace JSX {
|
|
4
|
+
interface IntrinsicElements {
|
|
5
|
+
'sinch-progress-stepper': TSinchProgressStepperReact;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
interface HTMLElementTagNameMap {
|
|
9
|
+
'sinch-progress-stepper': TSinchProgressStepperElement;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { isProgressStepperItemActive, isProgressStepperItemChecked, setProgressStepperItemActiveDescendant, setProgressStepperItemChecked, setProgressStepperItemStatus } from '../progress-stepper-item/utils';
|
|
2
|
+
import { defineCustomElement, getAttribute, getReactEventHandler, getRect, getTargetByAttribute, NectaryElement, updateAttribute } from '../utils';
|
|
3
|
+
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;width:100%}::slotted(sinch-progress-stepper-item){flex:1;min-width:0}</style><div id="wrapper"><slot></slot></div>';
|
|
4
|
+
const template = document.createElement('template');
|
|
5
|
+
template.innerHTML = templateHTML;
|
|
6
|
+
defineCustomElement('sinch-progress-stepper', class extends NectaryElement {
|
|
7
|
+
#$slot;
|
|
8
|
+
#controller = null;
|
|
9
|
+
#$items = [];
|
|
10
|
+
#currentActiveDescendantIndex = -1;
|
|
11
|
+
#isGoingToFocusItem = false;
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
const shadowRoot = this.attachShadow();
|
|
15
|
+
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
16
|
+
this.#$slot = shadowRoot.querySelector('slot');
|
|
17
|
+
}
|
|
18
|
+
connectedCallback() {
|
|
19
|
+
this.#controller = new AbortController();
|
|
20
|
+
const {
|
|
21
|
+
signal
|
|
22
|
+
} = this.#controller;
|
|
23
|
+
const options = {
|
|
24
|
+
signal
|
|
25
|
+
};
|
|
26
|
+
this.role = 'tablist';
|
|
27
|
+
this.#$slot.addEventListener('click', this.#onOptionClick, options);
|
|
28
|
+
this.#$slot.addEventListener('keydown', this.#onOptionKeydown, options);
|
|
29
|
+
this.#$slot.addEventListener('focusout', this.#onOptionBlur, options);
|
|
30
|
+
this.addEventListener('-change', this.#onChangeReactHandler, options);
|
|
31
|
+
queueMicrotask(() => {
|
|
32
|
+
this.#$slot.addEventListener('slotchange', this.#onSlotChange, options);
|
|
33
|
+
this.#onSlotChange();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
disconnectedCallback() {
|
|
37
|
+
this.#controller.abort();
|
|
38
|
+
this.#controller = null;
|
|
39
|
+
}
|
|
40
|
+
static get observedAttributes() {
|
|
41
|
+
return ['value', 'progressvalue'];
|
|
42
|
+
}
|
|
43
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
44
|
+
if (oldVal === newVal) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
switch (name) {
|
|
48
|
+
case 'value':
|
|
49
|
+
{
|
|
50
|
+
this.#onValueChange(newVal);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case 'progressvalue':
|
|
54
|
+
{
|
|
55
|
+
this.#updateProgressValue();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
set value(value) {
|
|
61
|
+
updateAttribute(this, 'value', value);
|
|
62
|
+
}
|
|
63
|
+
get value() {
|
|
64
|
+
return getAttribute(this, 'value', '');
|
|
65
|
+
}
|
|
66
|
+
set progressValue(value) {
|
|
67
|
+
updateAttribute(this, 'progressvalue', value);
|
|
68
|
+
}
|
|
69
|
+
get progressValue() {
|
|
70
|
+
return getAttribute(this, 'progressvalue', '');
|
|
71
|
+
}
|
|
72
|
+
nthOptionRect(index) {
|
|
73
|
+
const $el = this.#$slot.assignedElements()[index];
|
|
74
|
+
if ($el != null) {
|
|
75
|
+
return getRect($el);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
#onSlotChange = () => {
|
|
80
|
+
this.#$items = this.#$slot.assignedElements();
|
|
81
|
+
this.#onValueChange(this.value);
|
|
82
|
+
this.#updateProgressValue();
|
|
83
|
+
};
|
|
84
|
+
#onOptionClick = e => {
|
|
85
|
+
const target = getTargetByAttribute(e, 'value');
|
|
86
|
+
if (target !== null && isProgressStepperItemActive(target)) {
|
|
87
|
+
this.dispatchEvent(new CustomEvent('-change', {
|
|
88
|
+
detail: getAttribute(target, 'value')
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
#onValueChange(value) {
|
|
93
|
+
for (const $item of this.#$items) {
|
|
94
|
+
const isChecked = value === getAttribute($item, 'value');
|
|
95
|
+
setProgressStepperItemChecked($item, isChecked);
|
|
96
|
+
}
|
|
97
|
+
this.#resetActiveDescendant();
|
|
98
|
+
}
|
|
99
|
+
#focusNextItem() {
|
|
100
|
+
for (let i = 0; i < this.#$items.length; i++) {
|
|
101
|
+
const nextIndex = (this.#currentActiveDescendantIndex + 1 + i) % this.#$items.length;
|
|
102
|
+
const item = this.#$items[nextIndex];
|
|
103
|
+
if (isProgressStepperItemActive(item)) {
|
|
104
|
+
setProgressStepperItemActiveDescendant(item, true);
|
|
105
|
+
this.#isGoingToFocusItem = true;
|
|
106
|
+
item.focus();
|
|
107
|
+
if (this.#currentActiveDescendantIndex >= 0) {
|
|
108
|
+
setProgressStepperItemActiveDescendant(this.#$items[this.#currentActiveDescendantIndex], false);
|
|
109
|
+
}
|
|
110
|
+
this.#currentActiveDescendantIndex = nextIndex;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
#focusPrevItem() {
|
|
116
|
+
for (let i = 0; i < this.#$items.length; i++) {
|
|
117
|
+
const nextIndex = (this.#currentActiveDescendantIndex - i - 1 + this.#$items.length) % this.#$items.length;
|
|
118
|
+
const item = this.#$items[nextIndex];
|
|
119
|
+
if (isProgressStepperItemActive(item)) {
|
|
120
|
+
setProgressStepperItemActiveDescendant(item, true);
|
|
121
|
+
this.#isGoingToFocusItem = true;
|
|
122
|
+
item.focus();
|
|
123
|
+
if (this.#currentActiveDescendantIndex >= 0) {
|
|
124
|
+
setProgressStepperItemActiveDescendant(this.#$items[this.#currentActiveDescendantIndex], false);
|
|
125
|
+
}
|
|
126
|
+
this.#currentActiveDescendantIndex = nextIndex;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
#updateProgressValue() {
|
|
132
|
+
const progressValue = this.progressValue;
|
|
133
|
+
const $items = this.#$slot.assignedElements();
|
|
134
|
+
const $progressValueIndex = $items.findIndex(it => getAttribute(it, 'value') === progressValue);
|
|
135
|
+
for (let i = 0; i < $items.length; i++) {
|
|
136
|
+
if ($progressValueIndex < 0 || $progressValueIndex < i) {
|
|
137
|
+
setProgressStepperItemStatus($items[i], 'inactive');
|
|
138
|
+
} else if ($progressValueIndex > i) {
|
|
139
|
+
setProgressStepperItemStatus($items[i], 'complete');
|
|
140
|
+
} else {
|
|
141
|
+
setProgressStepperItemStatus($items[i], 'incomplete');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.#resetActiveDescendant();
|
|
145
|
+
}
|
|
146
|
+
#getCheckedItemIndex() {
|
|
147
|
+
for (let i = 0; i < this.#$items.length; i++) {
|
|
148
|
+
if (isProgressStepperItemChecked(this.#$items[i])) {
|
|
149
|
+
return i;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return -1;
|
|
153
|
+
}
|
|
154
|
+
#getFirstActiveItemIndex() {
|
|
155
|
+
for (let i = 0; i < this.#$items.length; i++) {
|
|
156
|
+
if (isProgressStepperItemActive(this.#$items[i])) {
|
|
157
|
+
return i;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return -1;
|
|
161
|
+
}
|
|
162
|
+
#resetActiveDescendant() {
|
|
163
|
+
if (this.#currentActiveDescendantIndex >= 0) {
|
|
164
|
+
setProgressStepperItemActiveDescendant(this.#$items[this.#currentActiveDescendantIndex], false);
|
|
165
|
+
}
|
|
166
|
+
this.#currentActiveDescendantIndex = this.#getCheckedItemIndex();
|
|
167
|
+
if (this.#currentActiveDescendantIndex >= 0) {
|
|
168
|
+
setProgressStepperItemActiveDescendant(this.#$items[this.#currentActiveDescendantIndex], true);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.#currentActiveDescendantIndex = this.#getFirstActiveItemIndex();
|
|
172
|
+
if (this.#currentActiveDescendantIndex >= 0) {
|
|
173
|
+
setProgressStepperItemActiveDescendant(this.#$items[this.#currentActiveDescendantIndex], true);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
#onOptionKeydown = e => {
|
|
177
|
+
switch (e.code) {
|
|
178
|
+
case 'Enter':
|
|
179
|
+
case 'Space':
|
|
180
|
+
{
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
getTargetByAttribute(e, 'value')?.click();
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'ArrowRight':
|
|
186
|
+
{
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
this.#focusNextItem();
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'ArrowLeft':
|
|
192
|
+
{
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
this.#focusPrevItem();
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
#onOptionBlur = () => {
|
|
200
|
+
if (this.#isGoingToFocusItem) {
|
|
201
|
+
this.#isGoingToFocusItem = false;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.#resetActiveDescendant();
|
|
205
|
+
};
|
|
206
|
+
#onChangeReactHandler = e => {
|
|
207
|
+
getReactEventHandler(this, 'on-change')?.(e);
|
|
208
|
+
};
|
|
209
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TRect, TSinchElementReact } from '../types';
|
|
2
|
+
export type TSinchProgressStepperElement = HTMLElement & {
|
|
3
|
+
/** Current selected item value */
|
|
4
|
+
value: string;
|
|
5
|
+
/** Current progress value */
|
|
6
|
+
progressValue: string;
|
|
7
|
+
nthOptionRect(index: number): TRect | null;
|
|
8
|
+
/** Change value event */
|
|
9
|
+
addEventListener(type: '-change', listener: (e: CustomEvent<string>) => void): void;
|
|
10
|
+
/** Current selected item value */
|
|
11
|
+
setAttribute(name: 'value', value: string): void;
|
|
12
|
+
/** Current progress value */
|
|
13
|
+
setAttribute(name: 'progressvalue', value: string): void;
|
|
14
|
+
};
|
|
15
|
+
export type TSinchProgressStepperReact = TSinchElementReact<TSinchProgressStepperElement> & {
|
|
16
|
+
/** Current selected item value */
|
|
17
|
+
value: string;
|
|
18
|
+
/** Current progress value */
|
|
19
|
+
progressValue: string;
|
|
20
|
+
/** Change selected value event */
|
|
21
|
+
'on-change'?: (e: CustomEvent<string>) => void;
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '../text';
|
|
2
|
+
import type { TSinchProgressStepperItemElement, TSinchProgressStepperItemReact } from './types';
|
|
3
|
+
declare global {
|
|
4
|
+
namespace JSX {
|
|
5
|
+
interface IntrinsicElements {
|
|
6
|
+
'sinch-progress-stepper-item': TSinchProgressStepperItemReact;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'sinch-progress-stepper-item': TSinchProgressStepperItemElement;
|
|
11
|
+
}
|
|
12
|
+
}
|