tyrell-components 1.0.0-TC56 → 1.0.0-TC58
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/css/tyrell.css +3 -3
- package/dist/tyrell.js +1 -1
- package/dist/tyrell.js.map +1 -0
- package/lib/base/ty-component.d.ts +15 -3
- package/lib/base/ty-component.js +26 -18
- package/lib/components/button.d.ts +4 -0
- package/lib/components/button.js +33 -6
- package/lib/components/calendar-month.js +3 -10
- package/lib/components/calendar.d.ts +5 -1
- package/lib/components/calendar.js +28 -34
- package/lib/components/checkbox.d.ts +3 -1
- package/lib/components/checkbox.js +9 -1
- package/lib/components/date-picker.d.ts +61 -7
- package/lib/components/date-picker.js +274 -133
- package/lib/components/file-upload.d.ts +4 -1
- package/lib/components/file-upload.js +17 -3
- package/lib/components/input.d.ts +82 -3
- package/lib/components/input.js +178 -20
- package/lib/components/modal.d.ts +2 -0
- package/lib/components/modal.js +49 -10
- package/lib/components/option.js +7 -5
- package/lib/components/popup.js +59 -10
- package/lib/components/radio.d.ts +4 -1
- package/lib/components/radio.js +10 -1
- package/lib/components/resize-observer.js +2 -0
- package/lib/components/scroll-container.js +2 -0
- package/lib/components/select.d.ts +34 -3
- package/lib/components/select.js +135 -11
- package/lib/components/selected-tags.d.ts +7 -1
- package/lib/components/selected-tags.js +10 -1
- package/lib/components/switch.d.ts +5 -1
- package/lib/components/switch.js +9 -1
- package/lib/components/textarea.d.ts +46 -2
- package/lib/components/textarea.js +60 -9
- package/lib/styles/button.js +20 -19
- package/lib/styles/date-picker.js +18 -1
- package/lib/styles/file-upload.d.ts +1 -1
- package/lib/styles/file-upload.js +3 -3
- package/lib/styles/icon.d.ts +1 -1
- package/lib/styles/icon.js +2 -2
- package/lib/styles/input.js +1 -1
- package/lib/styles/radio.js +2 -2
- package/lib/styles/select-base.js +26 -0
- package/lib/styles/textarea.d.ts +1 -1
- package/lib/styles/textarea.js +7 -0
- package/lib/utils/error-flavor.d.ts +8 -0
- package/lib/utils/error-flavor.js +1 -0
- package/lib/utils/property-manager.d.ts +6 -0
- package/lib/utils/property-manager.js +15 -5
- package/lib/utils/upgrade-properties.d.ts +21 -0
- package/lib/utils/upgrade-properties.js +47 -0
- package/lib/utils/validity.d.ts +72 -2
- package/lib/utils/validity.js +192 -8
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -2
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/dropdown.d.ts +0 -403
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -1315
- package/lib/components/dropdown.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/multiselect.d.ts +0 -389
- package/lib/components/multiselect.d.ts.map +0 -1
- package/lib/components/multiselect.js +0 -1301
- package/lib/components/multiselect.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/dropdown.d.ts +0 -12
- package/lib/styles/dropdown.d.ts.map +0 -1
- package/lib/styles/dropdown.js +0 -725
- package/lib/styles/dropdown.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -6
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -568
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -23
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
package/lib/components/select.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_SIZE } from "../types/common.js";
|
|
2
2
|
import { ensureStyles } from "../utils/styles.js";
|
|
3
3
|
import { syncCustomFlavorSheet } from "../utils/flavor-sheet.js";
|
|
4
|
+
import { ERROR_FLAVOR } from "../utils/error-flavor.js";
|
|
4
5
|
import { selectBaseStyles, selectCustomFlavorCss } from "../styles/select-base.js";
|
|
5
6
|
import { selectStyles } from "../styles/select.js";
|
|
6
7
|
import { getLoaderSvg } from "../utils/loader-registry.js";
|
|
@@ -20,6 +21,7 @@ elementIds.set(element, id);
|
|
|
20
21
|
return id;
|
|
21
22
|
}
|
|
22
23
|
import { REQUIRED_ICON_SVG } from "../utils/icons.js";
|
|
24
|
+
import { nativeMessage } from "../utils/validity.js";
|
|
23
25
|
const SEARCH_AUTO_THRESHOLD = 7;
|
|
24
26
|
const CHEVRON_DOWN_SVG = `<svg viewBox="0 0 20 20" fill="currentColor">
|
|
25
27
|
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
@@ -81,6 +83,7 @@ this._optionsScrollbar = null;
|
|
|
81
83
|
this._childObserver = null;
|
|
82
84
|
this._selectedClone = null;
|
|
83
85
|
this._customFlavorSheet = null;
|
|
86
|
+
this._ariaTrigger = null;
|
|
84
87
|
const shadow = this.shadowRoot;
|
|
85
88
|
ensureStyles(shadow, { css: selectBaseStyles, id: "ty-select-base" });
|
|
86
89
|
ensureStyles(shadow, { css: selectStyles, id: "ty-select" });
|
|
@@ -96,6 +99,7 @@ dialog.close();
|
|
|
96
99
|
});
|
|
97
100
|
this.render();
|
|
98
101
|
this.initializeState();
|
|
102
|
+
this.updateFormValue();
|
|
99
103
|
this._childObserver = new MutationObserver(() => {
|
|
100
104
|
this.ensureOptionRoles();
|
|
101
105
|
if (this._state.selectedValues.length > 0) {
|
|
@@ -108,11 +112,43 @@ this.updateMobileSelectedState();
|
|
|
108
112
|
this._childObserver.observe(this, { childList: true });
|
|
109
113
|
this.ensureOptionRoles();
|
|
110
114
|
this._syncCustomFlavor();
|
|
115
|
+
this.syncErrorFlavor();
|
|
116
|
+
if (!this._keyboardHandler) {
|
|
117
|
+
this._keyboardHandler = this.handleKeyboard.bind(this);
|
|
118
|
+
document.addEventListener("keydown", this._keyboardHandler, true);
|
|
119
|
+
}
|
|
111
120
|
}
|
|
112
121
|
_syncCustomFlavor() {
|
|
113
122
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.getProperty("flavor"), ({ base }) => selectCustomFlavorCss(base));
|
|
114
123
|
}
|
|
124
|
+
syncErrorFlavor() {
|
|
125
|
+
if (!this._connected)
|
|
126
|
+
return;
|
|
127
|
+
const flavor = this.getProperty("flavor");
|
|
128
|
+
const replaced = this.getAttribute(ERROR_FLAVOR);
|
|
129
|
+
if (this.getProperty("error")) {
|
|
130
|
+
if (replaced === null && flavor === "neutral") {
|
|
131
|
+
this.setAttribute(ERROR_FLAVOR, this.getAttribute("flavor") ?? "");
|
|
132
|
+
this.setProperty("flavor", "danger");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (replaced !== null) {
|
|
136
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
137
|
+
if (flavor !== "danger")
|
|
138
|
+
return;
|
|
139
|
+
if (!replaced)
|
|
140
|
+
this.removeAttribute("flavor");
|
|
141
|
+
else
|
|
142
|
+
this.setProperty("flavor", replaced);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
115
145
|
onDisconnect() {
|
|
146
|
+
if (this._state.open) {
|
|
147
|
+
if (this._state.mode === "mobile")
|
|
148
|
+
this.closeMobileModal();
|
|
149
|
+
else
|
|
150
|
+
this.closeDropdown();
|
|
151
|
+
}
|
|
116
152
|
const shadow = this.shadowRoot;
|
|
117
153
|
const dialogs = shadow.querySelectorAll("dialog");
|
|
118
154
|
dialogs.forEach((dialog) => {
|
|
@@ -121,7 +157,7 @@ dialog.close();
|
|
|
121
157
|
}
|
|
122
158
|
});
|
|
123
159
|
if (this._keyboardHandler) {
|
|
124
|
-
document.removeEventListener("keydown", this._keyboardHandler);
|
|
160
|
+
document.removeEventListener("keydown", this._keyboardHandler, true);
|
|
125
161
|
this._keyboardHandler = null;
|
|
126
162
|
}
|
|
127
163
|
if (this._searchDebounceTimer !== null) {
|
|
@@ -150,8 +186,15 @@ case "name":
|
|
|
150
186
|
this._name = newValue || "";
|
|
151
187
|
break;
|
|
152
188
|
case "flavor":
|
|
189
|
+
if (newValue !== "danger") {
|
|
190
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
191
|
+
this.syncErrorFlavor();
|
|
192
|
+
}
|
|
153
193
|
this._syncCustomFlavor();
|
|
154
194
|
break;
|
|
195
|
+
case "error":
|
|
196
|
+
this.syncErrorFlavor();
|
|
197
|
+
break;
|
|
155
198
|
case "multiple":
|
|
156
199
|
this._multiple = newValue;
|
|
157
200
|
if (!newValue && this._state.selectedValues.length > 1) {
|
|
@@ -250,8 +293,21 @@ formData.append(this._name, value);
|
|
|
250
293
|
});
|
|
251
294
|
return formData;
|
|
252
295
|
}
|
|
296
|
+
if (this._name && !this._multiple)
|
|
297
|
+
return "";
|
|
253
298
|
return null;
|
|
254
299
|
}
|
|
300
|
+
focus(options) {
|
|
301
|
+
if (this._disabled)
|
|
302
|
+
return;
|
|
303
|
+
const trigger = this.querySelector(':scope > [slot="trigger"]');
|
|
304
|
+
const stub = this.shadowRoot?.querySelector(".select-stub");
|
|
305
|
+
const target = trigger ?? stub;
|
|
306
|
+
if (target)
|
|
307
|
+
target.focus(options);
|
|
308
|
+
else
|
|
309
|
+
super.focus(options);
|
|
310
|
+
}
|
|
255
311
|
parseValue(value) {
|
|
256
312
|
if (!value || typeof value !== "string" || value.trim() === "")
|
|
257
313
|
return [];
|
|
@@ -333,7 +389,7 @@ if (!tag.hasAttribute("aria-selected"))
|
|
|
333
389
|
tag.setAttribute("aria-selected", "false");
|
|
334
390
|
});
|
|
335
391
|
}
|
|
336
|
-
updateComponentValue(newValues, dispatchChange = false, action = "set", item = null) {
|
|
392
|
+
updateComponentValue(newValues, dispatchChange = false, action = "set", item = null, reveal = dispatchChange) {
|
|
337
393
|
const oldValues = this.getSelectedValues();
|
|
338
394
|
const valueStr = newValues.join(",");
|
|
339
395
|
if (JSON.stringify(newValues.sort()) !== JSON.stringify(oldValues.sort())) {
|
|
@@ -347,6 +403,8 @@ items: this.getSelectedItems(newValues),
|
|
|
347
403
|
action,
|
|
348
404
|
item,
|
|
349
405
|
});
|
|
406
|
+
if (reveal)
|
|
407
|
+
this.revealValidity();
|
|
350
408
|
}
|
|
351
409
|
}
|
|
352
410
|
}
|
|
@@ -590,6 +648,7 @@ if (this._disabled || this._readonly) {
|
|
|
590
648
|
return;
|
|
591
649
|
}
|
|
592
650
|
this.clear();
|
|
651
|
+
this.revealValidity();
|
|
593
652
|
}
|
|
594
653
|
handleStubKeydown(e) {
|
|
595
654
|
if (this._disabled || this._readonly || this._state.open)
|
|
@@ -742,6 +801,7 @@ this.closeMobileModal();
|
|
|
742
801
|
else {
|
|
743
802
|
this.closeDropdown();
|
|
744
803
|
}
|
|
804
|
+
this.focus();
|
|
745
805
|
break;
|
|
746
806
|
case "Enter":
|
|
747
807
|
e.preventDefault();
|
|
@@ -903,10 +963,26 @@ label.innerHTML = `${this._label}${this._required ? `<span class="required-icon"
|
|
|
903
963
|
else {
|
|
904
964
|
label?.remove();
|
|
905
965
|
}
|
|
966
|
+
const error = this.getProperty("error") || "";
|
|
967
|
+
const errorId = `select-error-${getElementHash(this)}`;
|
|
968
|
+
let errorLine = container.querySelector(":scope > .error-message");
|
|
969
|
+
if (error) {
|
|
970
|
+
if (!errorLine) {
|
|
971
|
+
errorLine = document.createElement("div");
|
|
972
|
+
errorLine.className = "error-message";
|
|
973
|
+
errorLine.id = errorId;
|
|
974
|
+
container.append(errorLine);
|
|
975
|
+
}
|
|
976
|
+
errorLine.textContent = error;
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
errorLine?.remove();
|
|
980
|
+
}
|
|
906
981
|
const stub = shadow.querySelector(".select-stub");
|
|
907
982
|
if (stub) {
|
|
908
983
|
stub.classList.toggle("disabled", this._disabled);
|
|
909
984
|
stub.classList.toggle("compact", this._compact);
|
|
985
|
+
stub.classList.toggle("error", !!error);
|
|
910
986
|
stub.tabIndex = this._disabled ? -1 : 0;
|
|
911
987
|
stub.toggleAttribute("disabled", this._disabled);
|
|
912
988
|
if (this._disabled)
|
|
@@ -917,7 +993,27 @@ if (this._label)
|
|
|
917
993
|
stub.setAttribute("aria-labelledby", labelId);
|
|
918
994
|
else
|
|
919
995
|
stub.removeAttribute("aria-labelledby");
|
|
996
|
+
if (error) {
|
|
997
|
+
stub.setAttribute("aria-invalid", "true");
|
|
998
|
+
stub.setAttribute("aria-describedby", errorId);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
stub.removeAttribute("aria-invalid");
|
|
1002
|
+
stub.removeAttribute("aria-describedby");
|
|
1003
|
+
}
|
|
920
1004
|
}
|
|
1005
|
+
const trigger = error
|
|
1006
|
+
? this.querySelector(':scope > [slot="trigger"]')
|
|
1007
|
+
: null;
|
|
1008
|
+
if (this._ariaTrigger && this._ariaTrigger !== trigger) {
|
|
1009
|
+
this._ariaTrigger.removeAttribute("aria-invalid");
|
|
1010
|
+
this._ariaTrigger.removeAttribute("aria-description");
|
|
1011
|
+
}
|
|
1012
|
+
if (trigger) {
|
|
1013
|
+
trigger.setAttribute("aria-invalid", "true");
|
|
1014
|
+
trigger.setAttribute("aria-description", error);
|
|
1015
|
+
}
|
|
1016
|
+
this._ariaTrigger = trigger;
|
|
921
1017
|
shadow
|
|
922
1018
|
.querySelectorAll(".dropdown-search-input, .mobile-search-input")
|
|
923
1019
|
.forEach((input) => (input.disabled = this._disabled));
|
|
@@ -929,7 +1025,7 @@ return;
|
|
|
929
1025
|
}
|
|
930
1026
|
if (this.required && this.getSelectedValues().length === 0) {
|
|
931
1027
|
const stub = this.shadowRoot?.querySelector(".select-stub");
|
|
932
|
-
this.applyValidity({ valueMissing: true }, "
|
|
1028
|
+
this.applyValidity({ valueMissing: true }, nativeMessage("select", { required: true, multiple: this._multiple }), stub ?? undefined);
|
|
933
1029
|
}
|
|
934
1030
|
else {
|
|
935
1031
|
this.applyValidity({});
|
|
@@ -946,7 +1042,10 @@ const hasTrigger = slot.assignedElements().length > 0;
|
|
|
946
1042
|
stub.classList.toggle("custom-trigger", hasTrigger);
|
|
947
1043
|
this.toggleAttribute("has-trigger", hasTrigger);
|
|
948
1044
|
};
|
|
949
|
-
slot.addEventListener("slotchange",
|
|
1045
|
+
slot.addEventListener("slotchange", () => {
|
|
1046
|
+
sync();
|
|
1047
|
+
this.syncFieldChrome();
|
|
1048
|
+
});
|
|
950
1049
|
sync();
|
|
951
1050
|
}
|
|
952
1051
|
setupEventListeners() {
|
|
@@ -979,9 +1078,13 @@ if (e.target === dialog) {
|
|
|
979
1078
|
this.closeDropdown();
|
|
980
1079
|
}
|
|
981
1080
|
});
|
|
1081
|
+
dialog.addEventListener("cancel", (e) => {
|
|
1082
|
+
e.preventDefault();
|
|
1083
|
+
this.closeDropdown();
|
|
1084
|
+
this.focus();
|
|
1085
|
+
});
|
|
1086
|
+
this.syncOnOutsideClose(dialog, () => this.closeDropdown());
|
|
982
1087
|
}
|
|
983
|
-
this._keyboardHandler = this.handleKeyboard.bind(this);
|
|
984
|
-
document.addEventListener("keydown", this._keyboardHandler);
|
|
985
1088
|
}
|
|
986
1089
|
buildStubClasses() {
|
|
987
1090
|
const classes = [];
|
|
@@ -1175,10 +1278,6 @@ this.setupMobileEventListeners();
|
|
|
1175
1278
|
this.updateSelectionDisplay();
|
|
1176
1279
|
}
|
|
1177
1280
|
setupMobileEventListeners() {
|
|
1178
|
-
if (!this._keyboardHandler) {
|
|
1179
|
-
this._keyboardHandler = this.handleKeyboard.bind(this);
|
|
1180
|
-
document.addEventListener("keydown", this._keyboardHandler);
|
|
1181
|
-
}
|
|
1182
1281
|
const shadow = this.shadowRoot;
|
|
1183
1282
|
const stub = shadow.querySelector(".select-stub");
|
|
1184
1283
|
const optionsSlot = shadow.querySelector("#options-slot");
|
|
@@ -1213,8 +1312,16 @@ this.closeMobileModal();
|
|
|
1213
1312
|
dialog.addEventListener("cancel", (e) => {
|
|
1214
1313
|
e.preventDefault();
|
|
1215
1314
|
this.closeMobileModal();
|
|
1315
|
+
this.focus();
|
|
1216
1316
|
});
|
|
1317
|
+
this.syncOnOutsideClose(dialog, () => this.closeMobileModal());
|
|
1318
|
+
}
|
|
1217
1319
|
}
|
|
1320
|
+
syncOnOutsideClose(dialog, close) {
|
|
1321
|
+
new MutationObserver(() => {
|
|
1322
|
+
if (this._state.open && !dialog.open)
|
|
1323
|
+
close();
|
|
1324
|
+
}).observe(dialog, { attributes: true, attributeFilter: ["open"] });
|
|
1218
1325
|
}
|
|
1219
1326
|
handleMobileStubClick(e) {
|
|
1220
1327
|
e.preventDefault();
|
|
@@ -1409,7 +1516,7 @@ return;
|
|
|
1409
1516
|
this.updateComponentValue(current.filter((v) => v !== value), true, "remove", value);
|
|
1410
1517
|
}
|
|
1411
1518
|
clear() {
|
|
1412
|
-
this.updateComponentValue([], true, "clear", null);
|
|
1519
|
+
this.updateComponentValue([], true, "clear", null, false);
|
|
1413
1520
|
}
|
|
1414
1521
|
get value() {
|
|
1415
1522
|
return this.getSelectedValues().join(",");
|
|
@@ -1477,6 +1584,12 @@ return this.getProperty("required");
|
|
|
1477
1584
|
set required(value) {
|
|
1478
1585
|
this.setProperty("required", value);
|
|
1479
1586
|
}
|
|
1587
|
+
get error() {
|
|
1588
|
+
return this.getProperty("error");
|
|
1589
|
+
}
|
|
1590
|
+
set error(value) {
|
|
1591
|
+
this.setProperty("error", value);
|
|
1592
|
+
}
|
|
1480
1593
|
get notSearchable() {
|
|
1481
1594
|
return this.getProperty("notSearchable");
|
|
1482
1595
|
}
|
|
@@ -1518,8 +1631,14 @@ get flavor() {
|
|
|
1518
1631
|
return this.getProperty("flavor");
|
|
1519
1632
|
}
|
|
1520
1633
|
set flavor(value) {
|
|
1634
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
1521
1635
|
this.setProperty("flavor", value);
|
|
1522
1636
|
}
|
|
1637
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
1638
|
+
if (name === "flavor" && newValue === "danger" && oldValue === newValue)
|
|
1639
|
+
this.removeAttribute(ERROR_FLAVOR);
|
|
1640
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
1641
|
+
}
|
|
1523
1642
|
get form() {
|
|
1524
1643
|
return this._internals.form;
|
|
1525
1644
|
}
|
|
@@ -1580,6 +1699,11 @@ type: "boolean",
|
|
|
1580
1699
|
visual: true,
|
|
1581
1700
|
default: false,
|
|
1582
1701
|
},
|
|
1702
|
+
error: {
|
|
1703
|
+
type: "string",
|
|
1704
|
+
visual: true,
|
|
1705
|
+
default: "",
|
|
1706
|
+
},
|
|
1583
1707
|
noClear: {
|
|
1584
1708
|
type: "boolean",
|
|
1585
1709
|
visual: true,
|
|
@@ -28,6 +28,8 @@ export declare class TySelectedOptions extends HTMLElement {
|
|
|
28
28
|
* Interpolation context for a value: value, label, flavor plus every
|
|
29
29
|
* data-* attribute of the picker's matching option.
|
|
30
30
|
*/
|
|
31
|
+
/** The picker's option for a value (never its display clone). */
|
|
32
|
+
private option;
|
|
31
33
|
private context;
|
|
32
34
|
/**
|
|
33
35
|
* The template's blueprint fragment. HTML-parsed templates carry children in
|
|
@@ -42,7 +44,11 @@ export declare class TySelectedOptions extends HTMLElement {
|
|
|
42
44
|
* no flavor on the option).
|
|
43
45
|
*/
|
|
44
46
|
private stampTemplate;
|
|
45
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Default chip when no template is provided: the option's own content,
|
|
49
|
+
* cloned, so a rich option makes a rich chip. An explicit `label` on the
|
|
50
|
+
* option replaces it with that text; a <template> child replaces it entirely.
|
|
51
|
+
*/
|
|
46
52
|
private defaultChip;
|
|
47
53
|
private renderChips;
|
|
48
54
|
/** Remove a value from the picker; picker re-emits change → chips re-render. */
|
|
@@ -93,8 +93,11 @@ return parseValues(this._picker?.value ?? '');
|
|
|
93
93
|
template() {
|
|
94
94
|
return this.querySelector(':scope > template');
|
|
95
95
|
}
|
|
96
|
+
option(value) {
|
|
97
|
+
return this._picker?.querySelector(`[value="${CSS.escape(value)}"]:not([cloned])`);
|
|
98
|
+
}
|
|
96
99
|
context(value) {
|
|
97
|
-
const opt = this.
|
|
100
|
+
const opt = this.option(value);
|
|
98
101
|
const ctx = {
|
|
99
102
|
value,
|
|
100
103
|
label: opt?.getAttribute('label') || opt?.textContent?.trim() || value,
|
|
@@ -150,12 +153,18 @@ return clone;
|
|
|
150
153
|
}
|
|
151
154
|
defaultChip(value) {
|
|
152
155
|
const ctx = this.context(value);
|
|
156
|
+
const opt = this.option(value);
|
|
153
157
|
const tag = document.createElement('ty-tag');
|
|
154
158
|
tag.setAttribute('value', value);
|
|
155
159
|
tag.setAttribute('dismissible', 'true');
|
|
156
160
|
if (ctx.flavor)
|
|
157
161
|
tag.setAttribute('flavor', ctx.flavor);
|
|
162
|
+
if (opt && !opt.hasAttribute('label') && opt.hasChildNodes()) {
|
|
163
|
+
opt.childNodes.forEach(n => tag.appendChild(n.cloneNode(true)));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
158
166
|
tag.textContent = ctx.label;
|
|
167
|
+
}
|
|
159
168
|
tag.addEventListener('dismiss', () => this.deselect(value));
|
|
160
169
|
return tag;
|
|
161
170
|
}
|
|
@@ -82,7 +82,11 @@ export declare class TySwitch extends TyComponent<SwitchState> implements TySwit
|
|
|
82
82
|
private removeEventListeners;
|
|
83
83
|
private setupEventListeners;
|
|
84
84
|
protected render(): void;
|
|
85
|
-
/**
|
|
85
|
+
/** Focus the switch itself (the host is not focusable; its role element is). */
|
|
86
|
+
focus(options?: FocusOptions): void;
|
|
87
|
+
/** required + unchecked => valueMissing; otherwise valid. The message is a
|
|
88
|
+
* native checkbox's, as a switch where the browser has one (Safari's
|
|
89
|
+
* `switch` attribute). */
|
|
86
90
|
private updateValidity;
|
|
87
91
|
get checked(): boolean;
|
|
88
92
|
set checked(value: boolean);
|
package/lib/components/switch.js
CHANGED
|
@@ -3,6 +3,7 @@ import { TyComponent } from "../base/ty-component.js";
|
|
|
3
3
|
import { ensureStyles } from "../utils/styles.js";
|
|
4
4
|
import { syncCustomFlavorSheet } from "../utils/flavor-sheet.js";
|
|
5
5
|
import { switchStyles, switchCustomFlavorCss } from "../styles/switch.js";
|
|
6
|
+
import { nativeMessage } from "../utils/validity.js";
|
|
6
7
|
export class TySwitch extends TyComponent {
|
|
7
8
|
constructor() {
|
|
8
9
|
super();
|
|
@@ -145,6 +146,13 @@ this.setupEventListeners();
|
|
|
145
146
|
this.applyLabelName(switchEl);
|
|
146
147
|
this.updateValidity();
|
|
147
148
|
}
|
|
149
|
+
focus(options) {
|
|
150
|
+
const el = this.shadowRoot?.querySelector(".switch-container");
|
|
151
|
+
if (el)
|
|
152
|
+
el.focus(options);
|
|
153
|
+
else
|
|
154
|
+
super.focus(options);
|
|
155
|
+
}
|
|
148
156
|
updateValidity() {
|
|
149
157
|
const anchor = this.shadowRoot?.querySelector(".switch-container");
|
|
150
158
|
if (this.disabled) {
|
|
@@ -152,7 +160,7 @@ this.applyValidity({});
|
|
|
152
160
|
return;
|
|
153
161
|
}
|
|
154
162
|
if (this.required && !this.checked) {
|
|
155
|
-
this.applyValidity({ valueMissing: true }, "
|
|
163
|
+
this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "checkbox", switch: true, required: true }), anchor ?? undefined);
|
|
156
164
|
}
|
|
157
165
|
else {
|
|
158
166
|
this.applyValidity({});
|
|
@@ -21,6 +21,10 @@ export interface TyTextareaElement extends HTMLElement {
|
|
|
21
21
|
label?: string;
|
|
22
22
|
disabled?: boolean;
|
|
23
23
|
required?: boolean;
|
|
24
|
+
minLength?: number;
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
tooShortMessage?: string;
|
|
27
|
+
tooLongMessage?: string;
|
|
24
28
|
error?: string;
|
|
25
29
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
26
30
|
rows?: string | number;
|
|
@@ -63,6 +67,26 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
|
|
|
63
67
|
visual: boolean;
|
|
64
68
|
default: boolean;
|
|
65
69
|
};
|
|
70
|
+
minlength: {
|
|
71
|
+
type: "string";
|
|
72
|
+
visual: boolean;
|
|
73
|
+
default: string;
|
|
74
|
+
};
|
|
75
|
+
maxlength: {
|
|
76
|
+
type: "string";
|
|
77
|
+
visual: boolean;
|
|
78
|
+
default: string;
|
|
79
|
+
};
|
|
80
|
+
tooShortMessage: {
|
|
81
|
+
type: "string";
|
|
82
|
+
visual: boolean;
|
|
83
|
+
default: string;
|
|
84
|
+
};
|
|
85
|
+
tooLongMessage: {
|
|
86
|
+
type: "string";
|
|
87
|
+
visual: boolean;
|
|
88
|
+
default: string;
|
|
89
|
+
};
|
|
66
90
|
error: {
|
|
67
91
|
type: "string";
|
|
68
92
|
visual: boolean;
|
|
@@ -125,8 +149,18 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
|
|
|
125
149
|
set label(v: string);
|
|
126
150
|
get disabled(): boolean;
|
|
127
151
|
set disabled(v: boolean);
|
|
152
|
+
/** Focus the inner <textarea>: the host itself isn't focusable. */
|
|
153
|
+
focus(options?: FocusOptions): void;
|
|
128
154
|
get required(): boolean;
|
|
129
155
|
set required(v: boolean);
|
|
156
|
+
get minLength(): number;
|
|
157
|
+
set minLength(v: number);
|
|
158
|
+
get maxLength(): number;
|
|
159
|
+
set maxLength(v: number);
|
|
160
|
+
get tooShortMessage(): string;
|
|
161
|
+
set tooShortMessage(v: string);
|
|
162
|
+
get tooLongMessage(): string;
|
|
163
|
+
set tooLongMessage(v: string);
|
|
130
164
|
get error(): string;
|
|
131
165
|
set error(v: string);
|
|
132
166
|
get size(): 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
@@ -158,8 +192,18 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
|
|
|
158
192
|
private applyHeightConstraints;
|
|
159
193
|
private setupTextareaEvents;
|
|
160
194
|
protected render(): void;
|
|
161
|
-
/**
|
|
162
|
-
*
|
|
195
|
+
/**
|
|
196
|
+
* minlength goes on the inner <textarea> for the browser's own too-short
|
|
197
|
+
* message after the user types, maxlength so typing stops at the limit
|
|
198
|
+
* (like ty-input).
|
|
199
|
+
*/
|
|
200
|
+
private syncConstraintAttributes;
|
|
201
|
+
/**
|
|
202
|
+
* required + empty => valueMissing (the browser's own message); otherwise
|
|
203
|
+
* minlength / maxlength, worded like ty-input's (lengthValidity). Empty
|
|
204
|
+
* values only fail `required`, like native. Disabled fields are barred
|
|
205
|
+
* from constraint validation.
|
|
206
|
+
*/
|
|
163
207
|
private updateValidity;
|
|
164
208
|
}
|
|
165
209
|
export {};
|
|
@@ -4,7 +4,8 @@ import { ensureStyles } from '../utils/styles.js';
|
|
|
4
4
|
import { textareaStyles } from '../styles/textarea.js';
|
|
5
5
|
import { CustomScrollbar, isCustomScrollbarEnabled } from '../utils/custom-scrollbar.js';
|
|
6
6
|
import { customScrollbarStyles } from '../styles/custom-scrollbar.js';
|
|
7
|
-
|
|
7
|
+
import { nativeMessage, lengthValidity, toLimit } from '../utils/validity.js';
|
|
8
|
+
import { REQUIRED_ICON_SVG } from '../utils/icons.js';
|
|
8
9
|
export class TyTextarea extends TyComponent {
|
|
9
10
|
constructor() {
|
|
10
11
|
super();
|
|
@@ -13,6 +14,7 @@ this._dummyEl = null;
|
|
|
13
14
|
this._scrollbar = null;
|
|
14
15
|
this._isFocused = false;
|
|
15
16
|
this.handleInputEvent = (e) => {
|
|
17
|
+
this._userEdited = true;
|
|
16
18
|
const target = e.target;
|
|
17
19
|
const newValue = target.value;
|
|
18
20
|
this.value = newValue;
|
|
@@ -30,6 +32,8 @@ this._valueAtFocus = this.value;
|
|
|
30
32
|
};
|
|
31
33
|
this.handleBlurEvent = (e) => {
|
|
32
34
|
this._isFocused = false;
|
|
35
|
+
if (this._userEdited)
|
|
36
|
+
this.revealValidity();
|
|
33
37
|
if (this._valueAtFocus !== null && this.value !== this._valueAtFocus) {
|
|
34
38
|
this.emitValueEvent('change', e);
|
|
35
39
|
}
|
|
@@ -67,8 +71,19 @@ get label() { return this.getProperty('label'); }
|
|
|
67
71
|
set label(v) { this.setProperty('label', v); }
|
|
68
72
|
get disabled() { return this.getProperty('disabled'); }
|
|
69
73
|
set disabled(v) { this.setProperty('disabled', v); }
|
|
74
|
+
focus(options) {
|
|
75
|
+
this.shadowRoot?.querySelector('textarea')?.focus(options);
|
|
76
|
+
}
|
|
70
77
|
get required() { return this.getProperty('required'); }
|
|
71
78
|
set required(v) { this.setProperty('required', v); }
|
|
79
|
+
get minLength() { return toLimit(this.getProperty('minlength')) ?? -1; }
|
|
80
|
+
set minLength(v) { this.setProperty('minlength', v == null || v < 0 ? '' : String(v)); }
|
|
81
|
+
get maxLength() { return toLimit(this.getProperty('maxlength')) ?? -1; }
|
|
82
|
+
set maxLength(v) { this.setProperty('maxlength', v == null || v < 0 ? '' : String(v)); }
|
|
83
|
+
get tooShortMessage() { return this.getProperty('tooShortMessage'); }
|
|
84
|
+
set tooShortMessage(v) { this.setProperty('tooShortMessage', v ?? ''); }
|
|
85
|
+
get tooLongMessage() { return this.getProperty('tooLongMessage'); }
|
|
86
|
+
set tooLongMessage(v) { this.setProperty('tooLongMessage', v ?? ''); }
|
|
72
87
|
get error() { return this.getProperty('error'); }
|
|
73
88
|
set error(v) { this.setProperty('error', v); }
|
|
74
89
|
get size() {
|
|
@@ -250,18 +265,19 @@ const existingError = root.querySelector('.error-message');
|
|
|
250
265
|
if (existingContainer && existingTextarea) {
|
|
251
266
|
if (this.label) {
|
|
252
267
|
if (existingLabel) {
|
|
253
|
-
existingLabel.innerHTML = this.label
|
|
254
|
-
|
|
255
|
-
existingLabel.style.display = 'block';
|
|
268
|
+
existingLabel.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
|
|
269
|
+
existingLabel.style.display = 'flex';
|
|
256
270
|
}
|
|
257
271
|
}
|
|
258
272
|
else if (existingLabel) {
|
|
259
273
|
existingLabel.style.display = 'none';
|
|
260
274
|
}
|
|
275
|
+
if (existingTextarea.value !== (this.value || ''))
|
|
261
276
|
existingTextarea.value = this.value || '';
|
|
262
277
|
existingTextarea.placeholder = this.placeholder || '';
|
|
263
278
|
existingTextarea.disabled = this.disabled;
|
|
264
279
|
existingTextarea.required = this.required;
|
|
280
|
+
this.syncConstraintAttributes(existingTextarea);
|
|
265
281
|
existingTextarea.className = this.buildClassList();
|
|
266
282
|
if (this.label)
|
|
267
283
|
existingTextarea.setAttribute('aria-label', this.label);
|
|
@@ -301,9 +317,8 @@ container.className = 'textarea-container';
|
|
|
301
317
|
const labelEl = document.createElement('label');
|
|
302
318
|
labelEl.className = 'ty-field-label';
|
|
303
319
|
if (this.label) {
|
|
304
|
-
labelEl.innerHTML = this.label
|
|
305
|
-
|
|
306
|
-
labelEl.style.display = 'block';
|
|
320
|
+
labelEl.innerHTML = `${this.label}${this.required ? `<span class="required-icon">${REQUIRED_ICON_SVG}</span>` : ''}`;
|
|
321
|
+
labelEl.style.display = 'flex';
|
|
307
322
|
}
|
|
308
323
|
else {
|
|
309
324
|
labelEl.style.display = 'none';
|
|
@@ -313,6 +328,7 @@ textareaEl.value = this.value || '';
|
|
|
313
328
|
textareaEl.placeholder = this.placeholder || '';
|
|
314
329
|
textareaEl.disabled = this.disabled;
|
|
315
330
|
textareaEl.required = this.required;
|
|
331
|
+
this.syncConstraintAttributes(textareaEl);
|
|
316
332
|
textareaEl.className = this.buildClassList();
|
|
317
333
|
if (this.label)
|
|
318
334
|
textareaEl.setAttribute('aria-label', this.label);
|
|
@@ -364,6 +380,11 @@ this._textareaEl = textareaEl;
|
|
|
364
380
|
}
|
|
365
381
|
this.updateValidity();
|
|
366
382
|
}
|
|
383
|
+
syncConstraintAttributes(ta) {
|
|
384
|
+
const set = (name, limit) => limit !== null ? ta.setAttribute(name, String(limit)) : ta.removeAttribute(name);
|
|
385
|
+
set('minlength', toLimit(this.getProperty('minlength')));
|
|
386
|
+
set('maxlength', toLimit(this.getProperty('maxlength')));
|
|
387
|
+
}
|
|
367
388
|
updateValidity() {
|
|
368
389
|
const ta = this.shadowRoot?.querySelector('textarea');
|
|
369
390
|
if (this.disabled) {
|
|
@@ -371,12 +392,22 @@ this.applyValidity({});
|
|
|
371
392
|
return;
|
|
372
393
|
}
|
|
373
394
|
const current = ta ? ta.value : String(this.value ?? '');
|
|
374
|
-
if (
|
|
375
|
-
this.
|
|
395
|
+
if (current.trim() === '') {
|
|
396
|
+
if (this.required) {
|
|
397
|
+
this.applyValidity({ valueMissing: true }, nativeMessage('textarea', { required: true }), ta ?? undefined);
|
|
376
398
|
}
|
|
377
399
|
else {
|
|
378
400
|
this.applyValidity({});
|
|
379
401
|
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const { tooShort, tooLong } = lengthValidity(current, {
|
|
405
|
+
minlength: this.getProperty('minlength'),
|
|
406
|
+
maxlength: this.getProperty('maxlength'),
|
|
407
|
+
tooShortMessage: this.tooShortMessage,
|
|
408
|
+
tooLongMessage: this.tooLongMessage,
|
|
409
|
+
}, ta);
|
|
410
|
+
this.applyValidity({ tooShort: !!tooShort, tooLong: !!tooLong }, tooShort ?? tooLong, ta ?? undefined);
|
|
380
411
|
}
|
|
381
412
|
}
|
|
382
413
|
TyTextarea.formAssociated = true;
|
|
@@ -412,6 +443,26 @@ type: 'boolean',
|
|
|
412
443
|
visual: true,
|
|
413
444
|
default: false
|
|
414
445
|
},
|
|
446
|
+
minlength: {
|
|
447
|
+
type: 'string',
|
|
448
|
+
visual: true,
|
|
449
|
+
default: ''
|
|
450
|
+
},
|
|
451
|
+
maxlength: {
|
|
452
|
+
type: 'string',
|
|
453
|
+
visual: true,
|
|
454
|
+
default: ''
|
|
455
|
+
},
|
|
456
|
+
tooShortMessage: {
|
|
457
|
+
type: 'string',
|
|
458
|
+
visual: true,
|
|
459
|
+
default: ''
|
|
460
|
+
},
|
|
461
|
+
tooLongMessage: {
|
|
462
|
+
type: 'string',
|
|
463
|
+
visual: true,
|
|
464
|
+
default: ''
|
|
465
|
+
},
|
|
415
466
|
error: {
|
|
416
467
|
type: 'string',
|
|
417
468
|
visual: true,
|