@nectary/components 2.1.5 → 2.2.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 (52) hide show
  1. package/action-menu/index.js +1 -0
  2. package/action-menu-option/index.js +1 -0
  3. package/avatar/index.js +1 -1
  4. package/button/index.js +3 -2
  5. package/card/index.js +3 -2
  6. package/chip/index.js +2 -1
  7. package/color-menu/index.js +2 -2
  8. package/color-swatch/index.js +1 -1
  9. package/date-picker/index.js +11 -9
  10. package/date-picker/utils.d.ts +1 -0
  11. package/date-picker/utils.js +8 -0
  12. package/dialog/index.js +3 -2
  13. package/emoji/index.js +1 -1
  14. package/emoji-picker/index.js +2 -1
  15. package/field/index.js +1 -0
  16. package/flag/index.js +1 -1
  17. package/help-tooltip/index.js +1 -0
  18. package/icon-button/index.js +3 -2
  19. package/input/index.js +371 -40
  20. package/input/types.d.ts +14 -0
  21. package/input/utils.d.ts +24 -0
  22. package/input/utils.js +302 -1
  23. package/package.json +1 -1
  24. package/pop/index.js +5 -5
  25. package/popover/index.js +1 -0
  26. package/progress-stepper/index.d.ts +11 -0
  27. package/progress-stepper/index.js +209 -0
  28. package/progress-stepper/types.d.ts +22 -0
  29. package/progress-stepper/types.js +1 -0
  30. package/progress-stepper-item/index.d.ts +12 -0
  31. package/progress-stepper-item/index.js +82 -0
  32. package/progress-stepper-item/types.d.ts +23 -0
  33. package/progress-stepper-item/types.js +1 -0
  34. package/progress-stepper-item/utils.d.ts +11 -0
  35. package/progress-stepper-item/utils.js +13 -0
  36. package/select-button/index.js +2 -1
  37. package/select-menu/index.js +2 -1
  38. package/spinner/index.js +1 -0
  39. package/stop-events/index.js +1 -0
  40. package/tabs/index.js +1 -0
  41. package/tag/index.js +1 -1
  42. package/textarea/index.js +2 -1
  43. package/time-picker/index.js +1 -0
  44. package/time-picker/utils.js +2 -15
  45. package/tooltip/index.js +4 -3
  46. package/utils/countries.d.ts +1 -0
  47. package/utils/countries.json +487 -268
  48. package/utils/element.d.ts +1 -1
  49. package/utils/element.js +5 -5
  50. package/utils/event-target.d.ts +1 -0
  51. package/utils/event-target.js +9 -0
  52. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "2.1.5",
3
+ "version": "2.2.0",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
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.isConnected && getBooleanAttribute(this, 'open')) {
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.isConnected || this.#$dialog.open) {
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.isConnected && this.#$dialog.open) {
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.isConnected && this.#$dialog.open) {
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.isConnected && !this.#$dialog.open) {
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
@@ -34,6 +34,7 @@ defineCustomElement('sinch-popover', class extends NectaryElement {
34
34
  }
35
35
  disconnectedCallback() {
36
36
  this.#controller.abort();
37
+ this.#controller = null;
37
38
  }
38
39
  static get observedAttributes() {
39
40
  return ['orientation', 'open', 'modal', 'tip'];
@@ -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
+ }