@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/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { Context, defineCustomElement, getAttribute, getBooleanAttribute, getLiteralAttribute, getReactEventHandler, isAttrTrue, NectaryElement, setClass, subscribeContext, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateLiteralAttribute } from '../utils';
|
|
1
|
+
import { Context, defineCustomElement, getAttribute, getBooleanAttribute, getLiteralAttribute, getReactEventHandler, isAttrTrue, isElementFocused, NectaryElement, setClass, subscribeContext, updateAttribute, updateBooleanAttribute, updateExplicitBooleanAttribute, updateLiteralAttribute } from '../utils';
|
|
2
2
|
import { DEFAULT_SIZE, sizeValues } from '../utils/size';
|
|
3
|
-
const templateHTML = '<style>:host{all:initial;display:inline-block;vertical-align:middle}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;box-sizing:border-box;border-radius:var(--sinch-local-shape-radius);width:100%;height:var(--sinch-local-size);background-color:var(--sinch-comp-input-color-default-background-initial);--sinch-local-size:var(--sinch-comp-input-size-container-m);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-m);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-m)}:host([data-size="l"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-l);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-l);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-l)}:host([data-size="m"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-m);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-m);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-m)}:host([data-size="s"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-s);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-s);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-s)}#input{
|
|
4
|
-
import { inputTypes } from './utils';
|
|
3
|
+
const templateHTML = '<style>:host{all:initial;display:inline-block;vertical-align:middle}#wrapper{position:relative;display:flex;flex-direction:row;align-items:center;box-sizing:border-box;border-radius:var(--sinch-local-shape-radius);width:100%;height:var(--sinch-local-size);background-color:var(--sinch-comp-input-color-default-background-initial);--sinch-local-size:var(--sinch-comp-input-size-container-m);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-m);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-m)}:host([data-size="l"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-l);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-l);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-l)}:host([data-size="m"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-m);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-m);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-m)}:host([data-size="s"])>#wrapper{--sinch-local-size:var(--sinch-comp-input-size-container-s);--sinch-global-size-icon:var(--sinch-comp-input-size-icon-s);--sinch-local-shape-radius:var(--sinch-comp-input-shape-radius-size-s)}#input-wrapper{position:relative;flex:1;flex-basis:0;min-width:0;align-self:stretch}#input{all:initial;width:100%;height:100%;padding:0 12px;box-sizing:border-box;font:var(--sinch-comp-input-font-input);color:var(--sinch-comp-input-color-default-text-initial)}#input::placeholder{font:var(--sinch-comp-input-font-placeholder);color:var(--sinch-comp-input-color-default-text-placeholder);opacity:1}#input:disabled{color:var(--sinch-comp-input-color-disabled-text-initial);-webkit-text-fill-color:var(--sinch-comp-input-color-disabled-text-initial)}#input-mask{display:none;position:absolute;inset:0;padding:0 12px;pointer-events:none;color:var(--sinch-comp-input-color-default-text-placeholder);white-space:pre;height:fit-content;margin:auto 0;overflow:hidden}#border{position:absolute;border:1px solid var(--sinch-comp-input-color-default-border-initial);border-radius:var(--sinch-local-shape-radius);inset:0;pointer-events:none}:host([disabled]) #border{border-color:var(--sinch-comp-input-color-disabled-border-initial)}#input-wrapper:focus-within+#border{border-color:var(--sinch-comp-input-color-default-border-focus);border-width:2px}#input-mask,:host([mask]) #input{font:var(--sinch-sys-font-body-monospace-m)}:host([mask]) #input-mask{display:block}:host([invalid]:not([disabled])) #input-wrapper:not(:focus-within)+#border{border-color:var(--sinch-comp-input-color-invalid-border-initial)}#input[type=password]{font-size:1.5em;letter-spacing:.1em}#icon-wrapper{position:relative;height:100%}#icon{position:absolute;display:flex;align-items:center;left:12px;top:0;bottom:0;pointer-events:none;--sinch-global-color-icon:var(--sinch-comp-input-color-default-icon-initial)}:host([disabled]) #icon{--sinch-global-color-icon:var(--sinch-comp-input-color-disabled-icon-initial)}#icon-wrapper.empty{display:none}#icon-wrapper.empty~#input-wrapper>#input,#icon-wrapper.empty~#input-wrapper>#input-mask{padding-left:12px}#icon-wrapper:not(.empty)~#input-wrapper>#input,#icon-wrapper:not(.empty)~#input-wrapper>#input-mask{padding-left:calc(var(--sinch-global-size-icon) + 20px)}#right{display:flex;flex-direction:row;align-self:stretch;align-items:center;gap:4px;padding-right:4px}#right.empty{display:none}#left{display:flex;flex-direction:row;align-self:stretch;align-items:center;gap:4px;padding-left:4px}#left.empty{display:none}</style><div id="wrapper"><div id="left"><slot name="left"></slot></div><div id="icon-wrapper"><div id="icon"><slot name="icon"></slot></div></div><div id="input-wrapper"><div id="input-mask"></div><input id="input" type="text"/></div><div id="border"></div><div id="right"><slot name="right"></slot></div></div>';
|
|
4
|
+
import { deleteContentBackward, deleteContentForward, getMaskSymbols, inputTypes, insertText, beginMaskedComposition, endMaskedComposition, splitValueAndMask, getMergedValueSliced, insertFromPaste } from './utils';
|
|
5
5
|
const template = document.createElement('template');
|
|
6
6
|
template.innerHTML = templateHTML;
|
|
7
7
|
defineCustomElement('sinch-input', class extends NectaryElement {
|
|
8
8
|
#$input;
|
|
9
|
+
#$inputMask;
|
|
9
10
|
#$iconSlot;
|
|
10
11
|
#$iconWrapper;
|
|
11
12
|
#$rightSlot;
|
|
@@ -13,10 +14,14 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
13
14
|
#$leftSlot;
|
|
14
15
|
#$leftWrapper;
|
|
15
16
|
#$wrapper;
|
|
16
|
-
#
|
|
17
|
-
#
|
|
17
|
+
#selectionStart = 0;
|
|
18
|
+
#selectionEnd = 0;
|
|
19
|
+
#isCompositionInProgress = false;
|
|
20
|
+
#compositionBeginValue = '';
|
|
21
|
+
#wasClearedByMask = false;
|
|
18
22
|
#controller = null;
|
|
19
23
|
#sizeContext;
|
|
24
|
+
#maskSymbols = null;
|
|
20
25
|
constructor() {
|
|
21
26
|
super();
|
|
22
27
|
const shadowRoot = this.attachShadow({
|
|
@@ -24,6 +29,7 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
24
29
|
});
|
|
25
30
|
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
26
31
|
this.#$input = shadowRoot.querySelector('#input');
|
|
32
|
+
this.#$inputMask = shadowRoot.querySelector('#input-mask');
|
|
27
33
|
this.#$iconSlot = shadowRoot.querySelector('slot[name="icon"]');
|
|
28
34
|
this.#$iconWrapper = shadowRoot.querySelector('#icon-wrapper');
|
|
29
35
|
this.#$rightSlot = shadowRoot.querySelector('slot[name="right"]');
|
|
@@ -32,18 +38,23 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
32
38
|
this.#$leftWrapper = shadowRoot.querySelector('#left');
|
|
33
39
|
this.#$wrapper = shadowRoot.querySelector('#wrapper');
|
|
34
40
|
this.#sizeContext = new Context(this.#$wrapper, 'size');
|
|
41
|
+
this.#controller = new AbortController();
|
|
35
42
|
}
|
|
36
43
|
connectedCallback() {
|
|
37
44
|
super.connectedCallback();
|
|
38
45
|
this.setAttribute('role', 'textbox');
|
|
39
|
-
this.#controller
|
|
46
|
+
if (this.#controller === null) {
|
|
47
|
+
this.#controller = new AbortController();
|
|
48
|
+
}
|
|
40
49
|
const options = {
|
|
41
50
|
signal: this.#controller.signal
|
|
42
51
|
};
|
|
43
52
|
this.#$input.addEventListener('input', this.#onInput, options);
|
|
53
|
+
this.#$input.addEventListener('cut', this.#onCut, options);
|
|
54
|
+
this.#$input.addEventListener('copy', this.#onCopy, options);
|
|
55
|
+
this.#$input.addEventListener('paste', this.#onPaste, options);
|
|
44
56
|
this.#$input.addEventListener('compositionstart', this.#onCompositionStart, options);
|
|
45
|
-
this.#$input.addEventListener('
|
|
46
|
-
this.#$input.addEventListener('keydown', this.#onSelectionChange, options);
|
|
57
|
+
this.#$input.addEventListener('compositionend', this.#onCompositionEnd, options);
|
|
47
58
|
this.#$input.addEventListener('focus', this.#onInputFocus, options);
|
|
48
59
|
this.#$input.addEventListener('blur', this.#onInputBlur, options);
|
|
49
60
|
this.#$iconSlot.addEventListener('slotchange', this.#onIconSlotChange, options);
|
|
@@ -52,6 +63,9 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
52
63
|
this.addEventListener('-change', this.#onChangeReactHandler, options);
|
|
53
64
|
this.addEventListener('-focus', this.#onFocusReactHandler, options);
|
|
54
65
|
this.addEventListener('-blur', this.#onBlurReactHandler, options);
|
|
66
|
+
this.addEventListener('-copy', this.#onCopyReactHandler, options);
|
|
67
|
+
this.addEventListener('-cut', this.#onCutReactHandler, options);
|
|
68
|
+
this.addEventListener('-paste', this.#onPasteReactHandler, options);
|
|
55
69
|
this.#sizeContext.listen(this.#controller.signal);
|
|
56
70
|
subscribeContext(this, 'size', this.#onContextSize, this.#controller.signal);
|
|
57
71
|
this.#onIconSlotChange();
|
|
@@ -62,9 +76,10 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
62
76
|
disconnectedCallback() {
|
|
63
77
|
super.disconnectedCallback();
|
|
64
78
|
this.#controller.abort();
|
|
79
|
+
this.#controller = null;
|
|
65
80
|
}
|
|
66
81
|
static get observedAttributes() {
|
|
67
|
-
return ['type', 'value', 'placeholder', 'invalid', 'disabled', 'size', 'autocomplete', 'data-size', 'aria-label'];
|
|
82
|
+
return ['type', 'value', 'placeholder', 'mask', 'invalid', 'disabled', 'size', 'autocomplete', 'data-size', 'aria-label'];
|
|
68
83
|
}
|
|
69
84
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
70
85
|
if (oldVal === newVal) {
|
|
@@ -81,21 +96,40 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
81
96
|
{
|
|
82
97
|
const nextVal = newVal ?? '';
|
|
83
98
|
const prevVal = this.#$input.value;
|
|
99
|
+
if (this.#wasClearedByMask) {
|
|
100
|
+
this.#wasClearedByMask = false;
|
|
101
|
+
if (nextVal.length === 0) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (this.#maskSymbols !== null) {
|
|
106
|
+
const {
|
|
107
|
+
value,
|
|
108
|
+
placeholder
|
|
109
|
+
} = splitValueAndMask(nextVal, this.#maskSymbols);
|
|
110
|
+
this.#$input.value = value;
|
|
111
|
+
this.#$inputMask.textContent = placeholder;
|
|
112
|
+
if (isElementFocused(this.#$input)) {
|
|
113
|
+
this.#$input.setSelectionRange(this.#selectionEnd, this.#selectionEnd);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
84
117
|
if (nextVal !== prevVal) {
|
|
85
|
-
const prevCursorPos = this.#$input.selectionEnd;
|
|
86
|
-
const isPrevCursorEnd = prevCursorPos === prevVal.length;
|
|
87
118
|
this.#$input.value = nextVal;
|
|
88
|
-
if (
|
|
89
|
-
this.#$input.setSelectionRange(this.#
|
|
119
|
+
if (isElementFocused(this.#$input)) {
|
|
120
|
+
this.#$input.setSelectionRange(this.#selectionEnd, this.#selectionEnd);
|
|
90
121
|
}
|
|
91
122
|
}
|
|
92
|
-
this.#onRightSlotChange();
|
|
93
123
|
break;
|
|
94
124
|
}
|
|
95
125
|
case 'placeholder':
|
|
96
126
|
{
|
|
97
|
-
this
|
|
98
|
-
|
|
127
|
+
this.#updatePlaceholder();
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case 'mask':
|
|
131
|
+
{
|
|
132
|
+
this.#updateMask();
|
|
99
133
|
break;
|
|
100
134
|
}
|
|
101
135
|
case 'invalid':
|
|
@@ -146,6 +180,12 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
146
180
|
get value() {
|
|
147
181
|
return getAttribute(this, 'value', '');
|
|
148
182
|
}
|
|
183
|
+
set mask(value) {
|
|
184
|
+
updateAttribute(this, 'mask', value);
|
|
185
|
+
}
|
|
186
|
+
get mask() {
|
|
187
|
+
return getAttribute(this, 'mask');
|
|
188
|
+
}
|
|
149
189
|
set placeholder(value) {
|
|
150
190
|
updateAttribute(this, 'placeholder', value);
|
|
151
191
|
}
|
|
@@ -194,6 +234,9 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
194
234
|
set selectionDirection(value) {
|
|
195
235
|
this.#$input.selectionDirection = value;
|
|
196
236
|
}
|
|
237
|
+
setSelectionRange(selectionStart, selectionEnd) {
|
|
238
|
+
this.#$input.setSelectionRange(selectionStart, selectionEnd);
|
|
239
|
+
}
|
|
197
240
|
get focusable() {
|
|
198
241
|
return true;
|
|
199
242
|
}
|
|
@@ -204,15 +247,280 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
204
247
|
this.#$input.blur();
|
|
205
248
|
}
|
|
206
249
|
#onCompositionStart = () => {
|
|
207
|
-
this.#
|
|
250
|
+
this.#isCompositionInProgress = true;
|
|
251
|
+
if (this.#maskSymbols !== null) {
|
|
252
|
+
const selectionStart = this.#$input.selectionStart;
|
|
253
|
+
this.#compositionBeginValue = this.#$input.value;
|
|
254
|
+
if (selectionStart === this.#$input.value.length) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const {
|
|
258
|
+
value,
|
|
259
|
+
placeholder
|
|
260
|
+
} = beginMaskedComposition(this.#$input.value, this.#maskSymbols, selectionStart);
|
|
261
|
+
this.#$input.value = value;
|
|
262
|
+
this.#$input.setSelectionRange(selectionStart, selectionStart);
|
|
263
|
+
this.#$inputMask.textContent = placeholder;
|
|
264
|
+
this.#compositionBeginValue = value;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
#onCompositionEnd = e => {
|
|
268
|
+
this.#isCompositionInProgress = false;
|
|
269
|
+
if (this.#maskSymbols !== null) {
|
|
270
|
+
const value = this.#$input.value;
|
|
271
|
+
const wasValueInserted = value.length !== this.#compositionBeginValue.length;
|
|
272
|
+
const res = endMaskedComposition(value, e.data, this.#maskSymbols, this.#$input.selectionStart, wasValueInserted);
|
|
273
|
+
this.#compositionBeginValue = '';
|
|
274
|
+
if (res !== null) {
|
|
275
|
+
const {
|
|
276
|
+
value,
|
|
277
|
+
placeholder,
|
|
278
|
+
mergedValue,
|
|
279
|
+
cursorPos
|
|
280
|
+
} = res;
|
|
281
|
+
this.#$input.value = value;
|
|
282
|
+
this.#$input.setSelectionRange(cursorPos, cursorPos);
|
|
283
|
+
this.#$inputMask.textContent = placeholder;
|
|
284
|
+
if (mergedValue.length > 0) {
|
|
285
|
+
this.#selectionStart = cursorPos;
|
|
286
|
+
this.#selectionEnd = cursorPos;
|
|
287
|
+
this.#dispatchChangeEvent(mergedValue);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if ((res === null || res.mergedValue.length === 0) && this.value.length !== 0) {
|
|
291
|
+
this.#dispatchMaskClearChangeEvent();
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
this.#onInput();
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
#onMaskBeforeInput = e => {
|
|
298
|
+
this.#handleMaskBeforeInput(e.inputType, e.data);
|
|
299
|
+
e.preventDefault();
|
|
300
|
+
};
|
|
301
|
+
#handleMaskBeforeInput(inputType, data) {
|
|
302
|
+
const selectionStart = this.#$input.selectionStart ?? 0;
|
|
303
|
+
const selectionEnd = this.#$input.selectionEnd ?? 0;
|
|
304
|
+
let res = null;
|
|
305
|
+
switch (inputType) {
|
|
306
|
+
case 'insertText':
|
|
307
|
+
{
|
|
308
|
+
res = insertText(this.#$input.value, data, this.#maskSymbols, selectionStart, selectionEnd);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case 'insertFromPaste':
|
|
312
|
+
{
|
|
313
|
+
res = insertFromPaste(this.#$input.value, data, this.#maskSymbols, selectionStart, selectionEnd);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case 'deleteByCut':
|
|
317
|
+
case 'deleteContent':
|
|
318
|
+
case 'deleteContentBackward':
|
|
319
|
+
{
|
|
320
|
+
res = deleteContentBackward(this.#$input.value, this.#maskSymbols, selectionStart, selectionEnd);
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case 'deleteContentForward':
|
|
324
|
+
{
|
|
325
|
+
res = deleteContentForward(this.#$input.value, this.#maskSymbols, selectionStart, selectionEnd);
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (res !== null) {
|
|
330
|
+
const {
|
|
331
|
+
value,
|
|
332
|
+
placeholder,
|
|
333
|
+
mergedValue,
|
|
334
|
+
cursorPos
|
|
335
|
+
} = res;
|
|
336
|
+
this.#$input.value = value;
|
|
337
|
+
this.#$input.setSelectionRange(cursorPos, cursorPos);
|
|
338
|
+
this.#$inputMask.textContent = placeholder;
|
|
339
|
+
if (mergedValue.length > 0) {
|
|
340
|
+
this.#selectionStart = cursorPos;
|
|
341
|
+
this.#selectionEnd = cursorPos;
|
|
342
|
+
this.#dispatchChangeEvent(mergedValue);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if ((res === null || res.mergedValue.length === 0) && this.value.length !== 0) {
|
|
346
|
+
this.#dispatchMaskClearChangeEvent();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
#handleBeforeInput(inputType, data) {
|
|
350
|
+
const selectionStart = this.#$input.selectionStart ?? 0;
|
|
351
|
+
const selectionEnd = this.#$input.selectionEnd ?? 0;
|
|
352
|
+
switch (inputType) {
|
|
353
|
+
case 'insertFromPaste':
|
|
354
|
+
{
|
|
355
|
+
if (data === null) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const value = this.value;
|
|
359
|
+
const cursorPos = selectionStart + data.length;
|
|
360
|
+
const nextValue = value.substring(0, selectionStart) + data + value.substring(selectionEnd);
|
|
361
|
+
this.#selectionStart = cursorPos;
|
|
362
|
+
this.#selectionEnd = cursorPos;
|
|
363
|
+
this.#dispatchChangeEvent(nextValue);
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case 'deleteByCut':
|
|
367
|
+
{
|
|
368
|
+
const value = this.value;
|
|
369
|
+
const cursorPos = selectionStart;
|
|
370
|
+
const nextValue = value.substring(0, selectionStart) + value.substring(selectionEnd);
|
|
371
|
+
this.#selectionStart = cursorPos;
|
|
372
|
+
this.#selectionEnd = cursorPos;
|
|
373
|
+
this.#dispatchChangeEvent(nextValue);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
#onInput = () => {
|
|
379
|
+
if (this.#isCompositionInProgress) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (this.#maskSymbols !== null) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const nextValue = this.#$input.value;
|
|
386
|
+
const prevValue = this.value;
|
|
387
|
+
if (prevValue !== nextValue) {
|
|
388
|
+
const nextSelectionStart = this.#$input.selectionStart;
|
|
389
|
+
const nextSelectionEnd = this.#$input.selectionEnd;
|
|
390
|
+
this.#$input.value = prevValue;
|
|
391
|
+
this.#$input.setSelectionRange(this.#selectionStart, this.#selectionEnd);
|
|
392
|
+
this.#selectionStart = nextSelectionStart;
|
|
393
|
+
this.#selectionEnd = nextSelectionEnd;
|
|
394
|
+
this.#dispatchChangeEvent(nextValue);
|
|
395
|
+
}
|
|
208
396
|
};
|
|
209
|
-
#
|
|
210
|
-
|
|
397
|
+
#onMaskInputAutofillChange = () => {
|
|
398
|
+
const nextVal = this.#$input.value;
|
|
399
|
+
if (this.#maskSymbols !== null) {
|
|
400
|
+
const {
|
|
401
|
+
value,
|
|
402
|
+
placeholder,
|
|
403
|
+
mergedValue,
|
|
404
|
+
cursorPos
|
|
405
|
+
} = splitValueAndMask(nextVal, this.#maskSymbols);
|
|
406
|
+
this.#$input.value = value;
|
|
407
|
+
this.#$input.setSelectionRange(cursorPos, cursorPos);
|
|
408
|
+
this.#$inputMask.textContent = placeholder;
|
|
409
|
+
if (mergedValue.length > 0) {
|
|
410
|
+
this.#selectionStart = cursorPos;
|
|
411
|
+
this.#selectionEnd = cursorPos;
|
|
412
|
+
this.#dispatchChangeEvent(mergedValue);
|
|
413
|
+
} else {
|
|
414
|
+
this.#dispatchMaskClearChangeEvent();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
211
417
|
};
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
this
|
|
418
|
+
#onCopy = e => {
|
|
419
|
+
const value = this.#$input.value;
|
|
420
|
+
const selectionStart = this.#$input.selectionStart ?? 0;
|
|
421
|
+
const selectionEnd = this.#$input.selectionEnd ?? 0;
|
|
422
|
+
if (e.clipboardData === null || selectionStart === selectionEnd) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const copiedValue = this.#maskSymbols === null ? value.substring(selectionStart, selectionEnd) : getMergedValueSliced(value, this.#maskSymbols, selectionStart, selectionEnd);
|
|
426
|
+
let replacedValue = null;
|
|
427
|
+
const replaceWith = value => {
|
|
428
|
+
replacedValue = value ?? null;
|
|
429
|
+
};
|
|
430
|
+
if (this.#maskSymbols !== null) {
|
|
431
|
+
e.preventDefault();
|
|
432
|
+
e.clipboardData.setData('text/plain', copiedValue);
|
|
433
|
+
}
|
|
434
|
+
const event = new CustomEvent('-copy', {
|
|
435
|
+
detail: {
|
|
436
|
+
value: copiedValue,
|
|
437
|
+
replaceWith
|
|
438
|
+
},
|
|
439
|
+
cancelable: true
|
|
440
|
+
});
|
|
441
|
+
this.dispatchEvent(event);
|
|
442
|
+
if (event.defaultPrevented || replacedValue !== null) {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
}
|
|
445
|
+
if (replacedValue !== null) {
|
|
446
|
+
e.clipboardData.setData('text/plain', replacedValue);
|
|
447
|
+
}
|
|
215
448
|
};
|
|
449
|
+
#onCut = e => {
|
|
450
|
+
const value = this.#$input.value;
|
|
451
|
+
const selectionStart = this.#$input.selectionStart ?? 0;
|
|
452
|
+
const selectionEnd = this.#$input.selectionEnd ?? 0;
|
|
453
|
+
if (e.clipboardData === null || selectionStart === selectionEnd) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const copiedValue = this.#maskSymbols === null ? value.substring(selectionStart, selectionEnd) : getMergedValueSliced(value, this.#maskSymbols, selectionStart, selectionEnd);
|
|
457
|
+
let replacedValue = null;
|
|
458
|
+
const replaceWith = value => {
|
|
459
|
+
replacedValue = value ?? null;
|
|
460
|
+
};
|
|
461
|
+
if (this.#maskSymbols !== null) {
|
|
462
|
+
e.preventDefault();
|
|
463
|
+
e.clipboardData.setData('text/plain', copiedValue);
|
|
464
|
+
}
|
|
465
|
+
const event = new CustomEvent('-cut', {
|
|
466
|
+
detail: {
|
|
467
|
+
value: copiedValue,
|
|
468
|
+
replaceWith
|
|
469
|
+
},
|
|
470
|
+
cancelable: true
|
|
471
|
+
});
|
|
472
|
+
this.dispatchEvent(event);
|
|
473
|
+
if (event.defaultPrevented || replacedValue !== null) {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
}
|
|
476
|
+
if (replacedValue !== null) {
|
|
477
|
+
e.clipboardData.setData('text/plain', replacedValue);
|
|
478
|
+
if (this.#maskSymbols !== null) {
|
|
479
|
+
this.#handleMaskBeforeInput('deleteByCut', null);
|
|
480
|
+
} else {
|
|
481
|
+
this.#handleBeforeInput('deleteByCut', null);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
#onPaste = e => {
|
|
486
|
+
const pasteValue = e.clipboardData?.getData('text/plain') ?? '';
|
|
487
|
+
let replacedValue = '';
|
|
488
|
+
const replaceWith = value => {
|
|
489
|
+
replacedValue = value ?? '';
|
|
490
|
+
};
|
|
491
|
+
const event = new CustomEvent('-paste', {
|
|
492
|
+
detail: {
|
|
493
|
+
value: pasteValue,
|
|
494
|
+
replaceWith
|
|
495
|
+
},
|
|
496
|
+
cancelable: true
|
|
497
|
+
});
|
|
498
|
+
this.dispatchEvent(event);
|
|
499
|
+
if (event.defaultPrevented) {
|
|
500
|
+
e.preventDefault();
|
|
501
|
+
}
|
|
502
|
+
if (replacedValue.length === 0) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
e.preventDefault();
|
|
506
|
+
if (this.#maskSymbols !== null) {
|
|
507
|
+
this.#handleMaskBeforeInput('insertFromPaste', replacedValue);
|
|
508
|
+
} else {
|
|
509
|
+
this.#handleBeforeInput('insertFromPaste', replacedValue);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
#dispatchMaskClearChangeEvent() {
|
|
513
|
+
this.#wasClearedByMask = true;
|
|
514
|
+
this.#dispatchChangeEvent('');
|
|
515
|
+
}
|
|
516
|
+
#dispatchChangeEvent(value) {
|
|
517
|
+
if (value === this.value) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
this.dispatchEvent(new CustomEvent('-change', {
|
|
521
|
+
detail: value
|
|
522
|
+
}));
|
|
523
|
+
}
|
|
216
524
|
#onContextSize = e => {
|
|
217
525
|
if (this.hasAttribute('size')) {
|
|
218
526
|
return;
|
|
@@ -229,24 +537,38 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
229
537
|
}
|
|
230
538
|
}
|
|
231
539
|
};
|
|
232
|
-
#
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
this.#$input.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (!isPrevCursorEnd) {
|
|
242
|
-
this.#$input.setSelectionRange(prevCursorPos, prevCursorPos);
|
|
243
|
-
}
|
|
540
|
+
#updateMask() {
|
|
541
|
+
if (this.mask !== null) {
|
|
542
|
+
if (this.#maskSymbols === null) {
|
|
543
|
+
this.#$input.addEventListener('beforeinput', this.#onMaskBeforeInput, {
|
|
544
|
+
signal: this.#controller.signal
|
|
545
|
+
});
|
|
546
|
+
this.#$input.addEventListener('change', this.#onMaskInputAutofillChange, {
|
|
547
|
+
signal: this.#controller.signal
|
|
548
|
+
});
|
|
244
549
|
}
|
|
245
|
-
this.#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
})
|
|
550
|
+
this.#maskSymbols = getMaskSymbols(this.mask);
|
|
551
|
+
const {
|
|
552
|
+
value,
|
|
553
|
+
placeholder
|
|
554
|
+
} = splitValueAndMask(this.#$input.value, this.#maskSymbols);
|
|
555
|
+
this.#$input.value = value;
|
|
556
|
+
this.#$inputMask.textContent = placeholder;
|
|
557
|
+
} else {
|
|
558
|
+
this.#maskSymbols = null;
|
|
559
|
+
this.#$input.removeEventListener('beforeinput', this.#onMaskBeforeInput);
|
|
560
|
+
this.#$input.removeEventListener('change', this.#onMaskInputAutofillChange);
|
|
561
|
+
}
|
|
562
|
+
this.#updatePlaceholder();
|
|
563
|
+
}
|
|
564
|
+
#updatePlaceholder() {
|
|
565
|
+
if (this.#maskSymbols === null) {
|
|
566
|
+
const value = this.placeholder;
|
|
567
|
+
this.#$input.placeholder = value ?? '';
|
|
568
|
+
updateAttribute(this, 'aria-placeholder', value);
|
|
569
|
+
} else {
|
|
570
|
+
updateAttribute(this, 'aria-placeholder', null);
|
|
571
|
+
this.#$input.placeholder = '';
|
|
250
572
|
}
|
|
251
573
|
}
|
|
252
574
|
#onIconSlotChange = () => {
|
|
@@ -268,7 +590,7 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
268
590
|
this.dispatchEvent(new CustomEvent('-blur'));
|
|
269
591
|
};
|
|
270
592
|
#onSizeUpdate() {
|
|
271
|
-
if (!this.
|
|
593
|
+
if (!this.isDomConnected) {
|
|
272
594
|
return;
|
|
273
595
|
}
|
|
274
596
|
const size = this.getAttribute('data-size') ?? DEFAULT_SIZE;
|
|
@@ -283,4 +605,13 @@ defineCustomElement('sinch-input', class extends NectaryElement {
|
|
|
283
605
|
#onBlurReactHandler = () => {
|
|
284
606
|
getReactEventHandler(this, 'on-blur')?.();
|
|
285
607
|
};
|
|
608
|
+
#onCopyReactHandler = e => {
|
|
609
|
+
getReactEventHandler(this, 'on-copy')?.(e);
|
|
610
|
+
};
|
|
611
|
+
#onCutReactHandler = e => {
|
|
612
|
+
getReactEventHandler(this, 'on-cut')?.(e);
|
|
613
|
+
};
|
|
614
|
+
#onPasteReactHandler = e => {
|
|
615
|
+
getReactEventHandler(this, 'on-paste')?.(e);
|
|
616
|
+
};
|
|
286
617
|
});
|
package/input/types.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { TSinchElementReact } from '../types';
|
|
2
2
|
import type { TSinchSize } from '../utils/size';
|
|
3
3
|
export type TSinchInputType = 'text' | 'password';
|
|
4
|
+
export type TSinchInputClipboardEvent = CustomEvent<{
|
|
5
|
+
value: string;
|
|
6
|
+
replaceWith: (value: string) => void;
|
|
7
|
+
}>;
|
|
4
8
|
export type TSinchInputElement = HTMLElement & {
|
|
5
9
|
/** Text field type, `text` by default */
|
|
6
10
|
type: TSinchInputType;
|
|
7
11
|
/** Value */
|
|
8
12
|
value: string;
|
|
13
|
+
/** Mask */
|
|
14
|
+
mask: string | null;
|
|
9
15
|
/** Text that appears in the text field when it has no value set */
|
|
10
16
|
placeholder: string | null;
|
|
11
17
|
/** The HTML autocomplete attribute */
|
|
@@ -19,6 +25,7 @@ export type TSinchInputElement = HTMLElement & {
|
|
|
19
25
|
selectionStart: number | null;
|
|
20
26
|
selectionEnd: number | null;
|
|
21
27
|
selectionDirection: 'forward' | 'backward' | 'none' | null;
|
|
28
|
+
setSelectionRange(selectionStart: number, selectionEnd: number): void;
|
|
22
29
|
/** Change value event */
|
|
23
30
|
addEventListener(type: '-change', listener: (e: CustomEvent<string>) => void): void;
|
|
24
31
|
/** Focus event */
|
|
@@ -29,6 +36,8 @@ export type TSinchInputElement = HTMLElement & {
|
|
|
29
36
|
setAttribute(name: 'type', value: TSinchInputType): void;
|
|
30
37
|
/** Value */
|
|
31
38
|
setAttribute(name: 'value', value: string): void;
|
|
39
|
+
/** Mask */
|
|
40
|
+
setAttribute(name: 'mask', value: string): void;
|
|
32
41
|
/** Text that appears in the text field when it has no value set */
|
|
33
42
|
setAttribute(name: 'placeholder', value: string): void;
|
|
34
43
|
/** The HTML autocomplete attribute */
|
|
@@ -43,6 +52,8 @@ export type TSinchInputElement = HTMLElement & {
|
|
|
43
52
|
export type TSinchInputReact = TSinchElementReact<TSinchInputElement> & {
|
|
44
53
|
/** Controlled value, doesn't change on its own and requres an onChange-value state loop */
|
|
45
54
|
value: string;
|
|
55
|
+
/** Mask */
|
|
56
|
+
mask?: string;
|
|
46
57
|
/** Label that is used for a11y – might be different from `label` */
|
|
47
58
|
'aria-label': string;
|
|
48
59
|
/** Text field type, `text` by default */
|
|
@@ -63,4 +74,7 @@ export type TSinchInputReact = TSinchElementReact<TSinchInputElement> & {
|
|
|
63
74
|
'on-focus'?: (e: CustomEvent<void>) => void;
|
|
64
75
|
/** Blur handler */
|
|
65
76
|
'on-blur'?: (e: CustomEvent<void>) => void;
|
|
77
|
+
'on-cut'?: (e: TSinchInputClipboardEvent) => void;
|
|
78
|
+
'on-copy'?: (e: TSinchInputClipboardEvent) => void;
|
|
79
|
+
'on-paste'?: (e: TSinchInputClipboardEvent) => void;
|
|
66
80
|
};
|
package/input/utils.d.ts
CHANGED
|
@@ -1,2 +1,26 @@
|
|
|
1
1
|
import type { TSinchInputType } from './types';
|
|
2
|
+
type TSInchInputMaskSymbolModeDigit = 0;
|
|
3
|
+
type TSInchInputMaskSymbolModeLetter = 1;
|
|
4
|
+
type TSInchInputMaskSymbolModeExact = 2;
|
|
5
|
+
type TSinchInputMaskSymbol = {
|
|
6
|
+
value: string;
|
|
7
|
+
mode: TSInchInputMaskSymbolModeDigit | TSInchInputMaskSymbolModeLetter | TSInchInputMaskSymbolModeExact;
|
|
8
|
+
placeholder: string;
|
|
9
|
+
};
|
|
10
|
+
type TSinchMaskInputResult = {
|
|
11
|
+
value: string;
|
|
12
|
+
placeholder: string;
|
|
13
|
+
cursorPos: number;
|
|
14
|
+
mergedValue: string;
|
|
15
|
+
};
|
|
2
16
|
export declare const inputTypes: readonly TSinchInputType[];
|
|
17
|
+
export declare const getMaskSymbols: (maskValue: string) => TSinchInputMaskSymbol[];
|
|
18
|
+
export declare const deleteContentBackward: (inputValue: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, selectionEnd: number) => TSinchMaskInputResult | null;
|
|
19
|
+
export declare const deleteContentForward: (inputValue: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, selectionEnd: number) => TSinchMaskInputResult | null;
|
|
20
|
+
export declare const beginMaskedComposition: (inputValue: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number) => Pick<TSinchMaskInputResult, 'value' | 'placeholder'>;
|
|
21
|
+
export declare const endMaskedComposition: (inputValue: string, data: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, wasValueInserted: boolean) => TSinchMaskInputResult | null;
|
|
22
|
+
export declare const insertText: (inputValue: string, data: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, selectionEnd: number) => TSinchMaskInputResult | null;
|
|
23
|
+
export declare const insertFromPaste: (inputValue: string, data: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, selectionEnd: number) => TSinchMaskInputResult | null;
|
|
24
|
+
export declare const splitValueAndMask: (inputValue: string, maskSymbols: readonly TSinchInputMaskSymbol[]) => TSinchMaskInputResult;
|
|
25
|
+
export declare const getMergedValueSliced: (inputValue: string, maskSymbols: readonly TSinchInputMaskSymbol[], selectionStart: number, selectionEnd: number) => string;
|
|
26
|
+
export {};
|