@brandup/ui-input 1.0.38 → 1.0.39
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/package.json +2 -2
- package/source/input.ts +88 -2
package/package.json
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"email": "it@brandup.online"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
|
-
"version": "1.0.
|
|
27
|
+
"version": "1.0.39",
|
|
28
28
|
"main": "source/index.ts",
|
|
29
29
|
"types": "source/index.ts",
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@brandup/ui": "^2.0.7",
|
|
32
|
-
"@brandup/ui-kit": "^1.0.
|
|
32
|
+
"@brandup/ui-kit": "^1.0.39"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"source",
|
package/source/input.ts
CHANGED
|
@@ -12,13 +12,16 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
12
12
|
{
|
|
13
13
|
protected __valueElem: FormInput<T>;
|
|
14
14
|
protected __submitEvent?: (e: SubmitEvent) => void;
|
|
15
|
+
private __submitCaptureEvent?: (e: Event) => void;
|
|
15
16
|
private __invalidEvent?: (e: Event) => void;
|
|
17
|
+
private __overrides?: ValueElemOverrides;
|
|
16
18
|
private __isValidating?: boolean; // true, когда выполняется checkValidity в validate.
|
|
17
19
|
|
|
18
|
-
constructor(typeName: string, elem: HTMLElement, valueElem: FormInput<T
|
|
20
|
+
constructor(typeName: string, elem: HTMLElement, valueElem: FormInput<T>, overrides?: ValueElemOverrides) {
|
|
19
21
|
super(typeName, elem);
|
|
20
22
|
|
|
21
23
|
this.__valueElem = valueElem;
|
|
24
|
+
this.__overrides = overrides;
|
|
22
25
|
|
|
23
26
|
// то, что раньше делал _onRenderElement-override; теперь применяем после super, чтобы видеть valueElem
|
|
24
27
|
elem.classList.add(INPUT_CSS_CLASS);
|
|
@@ -29,6 +32,20 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
29
32
|
this.__initForm();
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Готовит поле-носитель к обёртке контейнером контрола: класс-скрыватель переезжает на поле,
|
|
37
|
+
* а собственные классы поля — на контейнер, чтобы оформление из разметки применялось к тому,
|
|
38
|
+
* что видно. Статический, потому что вызывается до `super(...)`.
|
|
39
|
+
*
|
|
40
|
+
* Саму вставку в DOM делает контрол: поле встаёт в контейнер первым или последним
|
|
41
|
+
* в зависимости от вёрстки, и на этот порядок завязаны соседские селекторы в стилях.
|
|
42
|
+
*/
|
|
43
|
+
protected static prepareValueElem(valueElem: HTMLElement, container: HTMLElement, inputClass: string) {
|
|
44
|
+
container.classList.add(...Array.from(valueElem.classList));
|
|
45
|
+
container.classList.remove(inputClass);
|
|
46
|
+
valueElem.classList.add(inputClass);
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
get form(): HTMLFormElement | null {
|
|
33
50
|
return this.__valueElem.form;
|
|
34
51
|
}
|
|
@@ -42,6 +59,14 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
42
59
|
return this.__valueElem.hasAttribute("readonly") || this.__valueElem.hasAttribute("data-readonly");
|
|
43
60
|
}
|
|
44
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Довести значение до `__valueElem`, если контрол держит его отдельно и обновляет не мгновенно
|
|
64
|
+
* (например, редактор с отложенной сериализацией). Вызывается перед каждым чтением значения
|
|
65
|
+
* снаружи: валидация, отправка формы, сбор `FormData`. По умолчанию ничего не делает —
|
|
66
|
+
* у контролов, пишущих в поле сразу, синхронизировать нечего.
|
|
67
|
+
*/
|
|
68
|
+
protected __syncValue(): void {}
|
|
69
|
+
|
|
45
70
|
private __initForm() {
|
|
46
71
|
this.__invalidEvent = (e: Event) => {
|
|
47
72
|
e.preventDefault();
|
|
@@ -51,6 +76,11 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
51
76
|
this.__valueElem.addEventListener("invalid", this.__invalidEvent);
|
|
52
77
|
|
|
53
78
|
this.__submitEvent = (e: SubmitEvent) => {
|
|
79
|
+
// Значение синхронизируем до любых проверок и независимо от них: при отключённой
|
|
80
|
+
// валидации обработчик выходит ниже, а форма всё равно отправится — уже с этим значением.
|
|
81
|
+
// Событие submit приходит до действия по умолчанию, поэтому успеваем.
|
|
82
|
+
this.__syncValue();
|
|
83
|
+
|
|
54
84
|
if ((e.submitter as HTMLButtonElement | null)?.formNoValidate || (<HTMLFormElement>e.target).noValidate)
|
|
55
85
|
return; // Не делаем валидацию, если она отключена в форме или в инициаторе события submit
|
|
56
86
|
|
|
@@ -67,7 +97,23 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
67
97
|
}
|
|
68
98
|
};
|
|
69
99
|
|
|
70
|
-
if (this.form)
|
|
100
|
+
if (!this.form) return;
|
|
101
|
+
|
|
102
|
+
this.form.addEventListener("submit", this.__submitEvent);
|
|
103
|
+
|
|
104
|
+
// Тот же сброс, но гарантированно раньше любого обработчика самой формы: в фазе перехвата
|
|
105
|
+
// на документе событие приходит до цели, в каком бы порядке ни вешали слушатели. Иначе
|
|
106
|
+
// обработчик submit, повешенный приложением раньше контрола, успел бы собрать FormData
|
|
107
|
+
// со старым значением. Для формы вне документа перехвата не будет — там работает
|
|
108
|
+
// __submitEvent выше.
|
|
109
|
+
//
|
|
110
|
+
// Документ берём у самого поля, а не глобальный: destroy может случиться в любой момент
|
|
111
|
+
// (UIElement разрушает контрол сам, заметив удаление элемента через MutationObserver),
|
|
112
|
+
// и тогда обращаться к глобальному окружению уже небезопасно.
|
|
113
|
+
this.__submitCaptureEvent = (e: Event) => {
|
|
114
|
+
if (e.target === this.form) this.__syncValue();
|
|
115
|
+
};
|
|
116
|
+
this.__valueElem.ownerDocument.addEventListener("submit", this.__submitCaptureEvent, true);
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
protected __submitForm() {
|
|
@@ -79,6 +125,8 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
79
125
|
validate(): boolean {
|
|
80
126
|
if (this.__isValidating) return true;
|
|
81
127
|
|
|
128
|
+
this.__syncValue(); // checkValidity читает поле напрямую
|
|
129
|
+
|
|
82
130
|
this.__isValidating = true;
|
|
83
131
|
const result = this.__valueElem.checkValidity();
|
|
84
132
|
this.__isValidating = false;
|
|
@@ -91,15 +139,53 @@ export abstract class InputControl<T extends InputType, TEvents = {}>
|
|
|
91
139
|
this.element.scrollIntoView({ block: "center", inline: "center" });
|
|
92
140
|
}
|
|
93
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Возвращает поле-носитель в исходное состояние: снимает класс, которым контрол увёл его
|
|
144
|
+
* с экрана, возвращает подменённые атрибуты и вынимает поле из контейнера, а сам контейнер
|
|
145
|
+
* удаляет. Одинаково для всех контролов: поле остаётся в форме, а UI над ним — временный.
|
|
146
|
+
*/
|
|
147
|
+
private __restoreValueElem() {
|
|
148
|
+
if (this.__overrides?.class) this.__valueElem.classList.remove(this.__overrides.class);
|
|
149
|
+
|
|
150
|
+
for (const [name, value] of this.__overrides?.attrs ?? []) {
|
|
151
|
+
if (value === null) this.__valueElem.removeAttribute(name);
|
|
152
|
+
else this.__valueElem.setAttribute(name, value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// контрол мог и не оборачивать поле (элемент контрола — само поле)
|
|
156
|
+
if (this.element === this.__valueElem || !this.element.parentElement) return;
|
|
157
|
+
|
|
158
|
+
this.element.insertAdjacentElement("afterend", this.__valueElem);
|
|
159
|
+
this.element.remove();
|
|
160
|
+
}
|
|
161
|
+
|
|
94
162
|
override destroy() {
|
|
95
163
|
if (this.form && this.__submitEvent) this.form.removeEventListener("submit", this.__submitEvent);
|
|
96
164
|
|
|
165
|
+
if (this.__submitCaptureEvent)
|
|
166
|
+
this.__valueElem.ownerDocument.removeEventListener("submit", this.__submitCaptureEvent, true);
|
|
167
|
+
|
|
97
168
|
if (this.__invalidEvent) this.__valueElem.removeEventListener("invalid", this.__invalidEvent);
|
|
98
169
|
|
|
170
|
+
this.__restoreValueElem();
|
|
171
|
+
|
|
99
172
|
super.destroy();
|
|
100
173
|
}
|
|
101
174
|
}
|
|
102
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Что контрол навязал полю-носителю и что нужно вернуть при `destroy`.
|
|
178
|
+
*
|
|
179
|
+
* Снимок делается до правок, то есть до `super(...)`, поэтому передаётся снаружи, а не
|
|
180
|
+
* собирается базовым классом.
|
|
181
|
+
*/
|
|
182
|
+
export interface ValueElemOverrides {
|
|
183
|
+
/** Класс, добавленный полю контролом (обычно уводит его с экрана). */
|
|
184
|
+
class?: string;
|
|
185
|
+
/** Подменённые атрибуты: имя и исходное значение (`null` — атрибута не было). */
|
|
186
|
+
attrs?: [name: string, value: string | null][];
|
|
187
|
+
}
|
|
188
|
+
|
|
103
189
|
export interface IInputControl {
|
|
104
190
|
get form(): HTMLFormElement | null;
|
|
105
191
|
get disabled(): boolean;
|