@vaadin/custom-field 24.2.0-dev.f254716fe → 24.3.0-alpha2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/custom-field",
3
- "version": "24.2.0-dev.f254716fe",
3
+ "version": "24.3.0-alpha2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -21,6 +21,8 @@
21
21
  "type": "module",
22
22
  "files": [
23
23
  "src",
24
+ "!src/vaadin-lit-custom-field.d.ts",
25
+ "!src/vaadin-lit-custom-field.js",
24
26
  "theme",
25
27
  "vaadin-*.d.ts",
26
28
  "vaadin-*.js",
@@ -34,32 +36,33 @@
34
36
  "web-component"
35
37
  ],
36
38
  "dependencies": {
39
+ "@open-wc/dedupe-mixin": "^1.3.0",
37
40
  "@polymer/polymer": "^3.0.0",
38
- "@vaadin/a11y-base": "24.2.0-dev.f254716fe",
39
- "@vaadin/component-base": "24.2.0-dev.f254716fe",
40
- "@vaadin/field-base": "24.2.0-dev.f254716fe",
41
- "@vaadin/vaadin-lumo-styles": "24.2.0-dev.f254716fe",
42
- "@vaadin/vaadin-material-styles": "24.2.0-dev.f254716fe",
43
- "@vaadin/vaadin-themable-mixin": "24.2.0-dev.f254716fe"
41
+ "@vaadin/a11y-base": "24.3.0-alpha2",
42
+ "@vaadin/component-base": "24.3.0-alpha2",
43
+ "@vaadin/field-base": "24.3.0-alpha2",
44
+ "@vaadin/vaadin-lumo-styles": "24.3.0-alpha2",
45
+ "@vaadin/vaadin-material-styles": "24.3.0-alpha2",
46
+ "@vaadin/vaadin-themable-mixin": "24.3.0-alpha2"
44
47
  },
45
48
  "devDependencies": {
46
49
  "@esm-bundle/chai": "^4.3.4",
47
- "@vaadin/combo-box": "24.2.0-dev.f254716fe",
48
- "@vaadin/date-picker": "24.2.0-dev.f254716fe",
49
- "@vaadin/email-field": "24.2.0-dev.f254716fe",
50
- "@vaadin/form-layout": "24.2.0-dev.f254716fe",
51
- "@vaadin/number-field": "24.2.0-dev.f254716fe",
52
- "@vaadin/password-field": "24.2.0-dev.f254716fe",
53
- "@vaadin/select": "24.2.0-dev.f254716fe",
54
- "@vaadin/testing-helpers": "^0.4.3",
55
- "@vaadin/text-area": "24.2.0-dev.f254716fe",
56
- "@vaadin/text-field": "24.2.0-dev.f254716fe",
57
- "@vaadin/time-picker": "24.2.0-dev.f254716fe",
50
+ "@vaadin/combo-box": "24.3.0-alpha2",
51
+ "@vaadin/date-picker": "24.3.0-alpha2",
52
+ "@vaadin/email-field": "24.3.0-alpha2",
53
+ "@vaadin/form-layout": "24.3.0-alpha2",
54
+ "@vaadin/number-field": "24.3.0-alpha2",
55
+ "@vaadin/password-field": "24.3.0-alpha2",
56
+ "@vaadin/select": "24.3.0-alpha2",
57
+ "@vaadin/testing-helpers": "^0.5.0",
58
+ "@vaadin/text-area": "24.3.0-alpha2",
59
+ "@vaadin/text-field": "24.3.0-alpha2",
60
+ "@vaadin/time-picker": "24.3.0-alpha2",
58
61
  "sinon": "^13.0.2"
59
62
  },
60
63
  "web-types": [
61
64
  "web-types.json",
62
65
  "web-types.lit.json"
63
66
  ],
64
- "gitHead": "da54950b9f8c14c6451ede0d426e16a489c7fb9b"
67
+ "gitHead": "0fd437292fa2a2f65e29b424d2456909ad2d684b"
65
68
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { Constructor } from '@open-wc/dedupe-mixin';
7
+ import type { FocusMixinClass } from '@vaadin/a11y-base/src/focus-mixin.js';
8
+ import type { KeyboardMixinClass } from '@vaadin/a11y-base/src/keyboard-mixin.js';
9
+ import type { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
10
+ import type { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
11
+ import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
12
+
13
+ export type CustomFieldParseValueFn = (value: string) => unknown[];
14
+
15
+ export type CustomFieldFormatValueFn = (inputValues: unknown[]) => string;
16
+
17
+ /**
18
+ * A mixin providing common custom field functionality.
19
+ */
20
+ export declare function CustomFieldMixin<T extends Constructor<HTMLElement>>(
21
+ base: T,
22
+ ): Constructor<CustomFieldMixinClass> &
23
+ Constructor<FieldMixinClass> &
24
+ Constructor<FocusMixinClass> &
25
+ Constructor<KeyboardMixinClass> &
26
+ Constructor<LabelMixinClass> &
27
+ Constructor<ValidateMixinClass> &
28
+ T;
29
+
30
+ export declare class CustomFieldMixinClass {
31
+ /**
32
+ * Array of available input nodes
33
+ */
34
+ readonly inputs: HTMLElement[] | undefined;
35
+
36
+ /**
37
+ * A function to format the values of the individual fields contained by
38
+ * the custom field into a single component value. The function receives
39
+ * an array of all values of the individual fields in the order of their
40
+ * presence in the DOM, and must return a single component value.
41
+ * This function is called each time a value of an internal field is
42
+ * changed.
43
+ *
44
+ * Example:
45
+ * ```js
46
+ * customField.formatValue = (fieldValues) => {
47
+ * return fieldValues.join("-");
48
+ * }
49
+ * ```
50
+ */
51
+ formatValue: CustomFieldFormatValueFn | undefined;
52
+
53
+ /**
54
+ * A function to parse the component value into values for the individual
55
+ * fields contained by the custom field. The function receives the
56
+ * component value, and must return an array of values for the individual
57
+ * fields in the order of their presence in the DOM.
58
+ * The function is called each time the value of the component changes.
59
+ *
60
+ * Example:
61
+ * ```js
62
+ * customField.parseValue = (componentValue) => {
63
+ * return componentValue.split("-");
64
+ * }
65
+ * ```
66
+ */
67
+ parseValue: CustomFieldParseValueFn | undefined;
68
+
69
+ /**
70
+ * The name of the control, which is submitted with the form data.
71
+ */
72
+ name: string | null | undefined;
73
+
74
+ /**
75
+ * The value of the field. When wrapping several inputs, it will contain `\t`
76
+ * (Tab character) as a delimiter indicating parts intended to be used as the
77
+ * corresponding inputs values.
78
+ * Use the [`formatValue`](#/elements/vaadin-custom-field#property-formatValue)
79
+ * and [`parseValue`](#/elements/vaadin-custom-field#property-parseValue)
80
+ * properties to customize this behavior.
81
+ */
82
+ value: string | null | undefined;
83
+ }
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
+ import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
8
+ import { getFlattenedElements } from '@vaadin/component-base/src/dom-utils.js';
9
+ import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
10
+ import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
11
+
12
+ /**
13
+ * Default implementation of the parse function that creates individual field
14
+ * values from the single component value.
15
+ * @param value
16
+ * @returns {*}
17
+ */
18
+ const defaultParseValue = (value) => {
19
+ return value.split('\t');
20
+ };
21
+
22
+ /**
23
+ * Default implementation of the format function that creates a single component
24
+ * value from individual field values.
25
+ * @param inputValues
26
+ * @returns {*}
27
+ */
28
+ const defaultFormatValue = (inputValues) => {
29
+ return inputValues.join('\t');
30
+ };
31
+
32
+ /**
33
+ * @polymerMixin
34
+ * @mixes FieldMixin
35
+ * @mixes FocusMixin
36
+ * @mixes KeyboardMixin
37
+ */
38
+ export const CustomFieldMixin = (superClass) =>
39
+ class CustomFieldMixin extends FieldMixin(FocusMixin(KeyboardMixin(superClass))) {
40
+ static get properties() {
41
+ return {
42
+ /**
43
+ * The name of the control, which is submitted with the form data.
44
+ */
45
+ name: String,
46
+
47
+ /**
48
+ * The value of the field. When wrapping several inputs, it will contain `\t`
49
+ * (Tab character) as a delimiter indicating parts intended to be used as the
50
+ * corresponding inputs values.
51
+ * Use the [`formatValue`](#/elements/vaadin-custom-field#property-formatValue)
52
+ * and [`parseValue`](#/elements/vaadin-custom-field#property-parseValue)
53
+ * properties to customize this behavior.
54
+ */
55
+ value: {
56
+ type: String,
57
+ observer: '__valueChanged',
58
+ notify: true,
59
+ },
60
+
61
+ /**
62
+ * Array of available input nodes
63
+ * @type {!Array<!HTMLElement> | undefined}
64
+ */
65
+ inputs: {
66
+ type: Array,
67
+ readOnly: true,
68
+ },
69
+
70
+ /**
71
+ * A function to format the values of the individual fields contained by
72
+ * the custom field into a single component value. The function receives
73
+ * an array of all values of the individual fields in the order of their
74
+ * presence in the DOM, and must return a single component value.
75
+ * This function is called each time a value of an internal field is
76
+ * changed.
77
+ *
78
+ * Example:
79
+ * ```js
80
+ * customField.formatValue = (fieldValues) => {
81
+ * return fieldValues.join("-");
82
+ * }
83
+ * ```
84
+ * @type {!CustomFieldFormatValueFn | undefined}
85
+ */
86
+ formatValue: {
87
+ type: Function,
88
+ },
89
+
90
+ /**
91
+ * A function to parse the component value into values for the individual
92
+ * fields contained by the custom field. The function receives the
93
+ * component value, and must return an array of values for the individual
94
+ * fields in the order of their presence in the DOM.
95
+ * The function is called each time the value of the component changes.
96
+ *
97
+ * Example:
98
+ * ```js
99
+ * customField.parseValue = (componentValue) => {
100
+ * return componentValue.split("-");
101
+ * }
102
+ * ```
103
+ * @type {!CustomFieldParseValueFn | undefined}
104
+ */
105
+ parseValue: {
106
+ type: Function,
107
+ },
108
+ };
109
+ }
110
+
111
+ /** @protected */
112
+ ready() {
113
+ super.ready();
114
+
115
+ // See https://github.com/vaadin/vaadin-web-components/issues/94
116
+ this.setAttribute('role', 'group');
117
+
118
+ this.ariaTarget = this;
119
+
120
+ this.__childrenObserver = new MutationObserver(() => {
121
+ this.__setInputsFromSlot();
122
+ });
123
+
124
+ this.__setInputsFromSlot();
125
+ this.$.slot.addEventListener('slotchange', () => {
126
+ this.__setInputsFromSlot();
127
+
128
+ // Observe changes to any children except inputs
129
+ // to allow wrapping `<input>` with `<div>` etc.
130
+ getFlattenedElements(this.$.slot)
131
+ .filter((el) => !this.__isInput(el))
132
+ .forEach((el) => {
133
+ this.__childrenObserver.observe(el, { childList: true });
134
+ });
135
+ });
136
+
137
+ this._tooltipController = new TooltipController(this);
138
+ this.addController(this._tooltipController);
139
+ this._tooltipController.setShouldShow((target) => {
140
+ const inputs = target.inputs || [];
141
+ return !inputs.some((el) => el.opened);
142
+ });
143
+ }
144
+
145
+ /** @protected */
146
+ focus() {
147
+ if (this.inputs && this.inputs[0]) {
148
+ this.inputs[0].focus();
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Override method inherited from `FocusMixin` to validate on blur.
154
+ * @param {boolean} focused
155
+ * @protected
156
+ */
157
+ _setFocused(focused) {
158
+ super._setFocused(focused);
159
+
160
+ if (!focused) {
161
+ this.validate();
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Override method inherited from `FocusMixin` to not remove focused
167
+ * state when focus moves to another input in the custom field.
168
+ * @param {FocusEvent} event
169
+ * @return {boolean}
170
+ * @protected
171
+ */
172
+ _shouldRemoveFocus(event) {
173
+ const { relatedTarget } = event;
174
+ return !this.inputs.some((el) => relatedTarget === (el.focusElement || el));
175
+ }
176
+
177
+ /**
178
+ * Returns true if the current inputs values satisfy all constraints (if any).
179
+ *
180
+ * @return {boolean}
181
+ */
182
+ checkValidity() {
183
+ const invalidFields = this.inputs.filter((input) => !(input.validate || input.checkValidity).call(input));
184
+
185
+ if (invalidFields.length || (this.required && !this.value.trim())) {
186
+ // Either 1. one of the input fields is invalid or
187
+ // 2. the custom field itself is required but doesn't have a value
188
+ return false;
189
+ }
190
+ return true;
191
+ }
192
+
193
+ /**
194
+ * @param {KeyboardEvent} e
195
+ * @protected
196
+ * @override
197
+ */
198
+ _onKeyDown(e) {
199
+ if (e.key === 'Tab') {
200
+ if (
201
+ (this.inputs.indexOf(e.target) < this.inputs.length - 1 && !e.shiftKey) ||
202
+ (this.inputs.indexOf(e.target) > 0 && e.shiftKey)
203
+ ) {
204
+ this.dispatchEvent(new CustomEvent('internal-tab'));
205
+ } else {
206
+ // FIXME(yuriy): remove this workaround when value should not be updated before focusout
207
+ this.__setValue();
208
+ }
209
+ }
210
+ }
211
+
212
+ /** @protected */
213
+ _onInputChange(event) {
214
+ // Stop native change events
215
+ event.stopPropagation();
216
+
217
+ this.__setValue();
218
+ this.validate();
219
+ this.dispatchEvent(
220
+ new CustomEvent('change', {
221
+ bubbles: true,
222
+ cancelable: false,
223
+ detail: {
224
+ value: this.value,
225
+ },
226
+ }),
227
+ );
228
+ }
229
+
230
+ /** @private */
231
+ __setValue() {
232
+ this.__settingValue = true;
233
+ const formatFn = this.formatValue || defaultFormatValue;
234
+ this.value = formatFn.apply(this, [this.inputs.map((input) => input.value)]);
235
+ this.__settingValue = false;
236
+ }
237
+
238
+ /** @private */
239
+ __isInput(node) {
240
+ const isSlottedInput = node.getAttribute('slot') === 'input' || node.getAttribute('slot') === 'textarea';
241
+ return !isSlottedInput && (node.validate || node.checkValidity);
242
+ }
243
+
244
+ /** @private */
245
+ __getInputsFromSlot() {
246
+ return getFlattenedElements(this.$.slot).filter((node) => this.__isInput(node));
247
+ }
248
+
249
+ /** @private */
250
+ __setInputsFromSlot() {
251
+ this._setInputs(this.__getInputsFromSlot());
252
+ this.__setValue();
253
+ }
254
+
255
+ /** @private */
256
+ __toggleHasValue(value) {
257
+ this.toggleAttribute('has-value', value !== null && value.trim() !== '');
258
+ }
259
+
260
+ /** @private */
261
+ __valueChanged(value, oldValue) {
262
+ if (this.__settingValue || !this.inputs) {
263
+ return;
264
+ }
265
+
266
+ this.__toggleHasValue(value);
267
+
268
+ const parseFn = this.parseValue || defaultParseValue;
269
+ const valuesArray = parseFn.apply(this, [value]);
270
+ if (!valuesArray || valuesArray.length === 0) {
271
+ console.warn('Value parser has not provided values array');
272
+ return;
273
+ }
274
+
275
+ this.inputs.forEach((input, id) => {
276
+ input.value = valuesArray[id];
277
+ });
278
+ if (oldValue !== undefined) {
279
+ this.validate();
280
+ }
281
+ }
282
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { CSSResult } from 'lit';
7
+
8
+ export const customFieldStyles: CSSResult;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { css } from 'lit';
7
+
8
+ export const customFieldStyles = css`
9
+ :host {
10
+ display: inline-flex;
11
+ }
12
+
13
+ :host::before {
14
+ content: '\\2003';
15
+ width: 0;
16
+ display: inline-block;
17
+ /* Size and position this element on the same vertical position as the input-field element
18
+ to make vertical align for the host element work as expected */
19
+ }
20
+
21
+ :host([hidden]) {
22
+ display: none !important;
23
+ }
24
+
25
+ .vaadin-custom-field-container {
26
+ width: 100%;
27
+ display: flex;
28
+ flex-direction: column;
29
+ }
30
+
31
+ .inputs-wrapper {
32
+ flex: none;
33
+ }
34
+ `;
@@ -3,15 +3,11 @@
3
3
  * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
7
- import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
8
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
9
- import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
10
7
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8
+ import { CustomFieldMixin } from './vaadin-custom-field-mixin.js';
11
9
 
12
- export type CustomFieldParseValueFn = (value: string) => unknown[];
13
-
14
- export type CustomFieldFormatValueFn = (inputValues: unknown[]) => string;
10
+ export { CustomFieldFormatValueFn, CustomFieldParseValueFn } from './vaadin-custom-field-mixin.js';
15
11
 
16
12
  /**
17
13
  * Fired when the user commits a value change.
@@ -99,60 +95,7 @@ export interface CustomFieldEventMap extends HTMLElementEventMap, CustomFieldCus
99
95
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
100
96
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
101
97
  */
102
- declare class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(ElementMixin(HTMLElement))))) {
103
- /**
104
- * Array of available input nodes
105
- */
106
- readonly inputs: HTMLElement[] | undefined;
107
-
108
- /**
109
- * A function to format the values of the individual fields contained by
110
- * the custom field into a single component value. The function receives
111
- * an array of all values of the individual fields in the order of their
112
- * presence in the DOM, and must return a single component value.
113
- * This function is called each time a value of an internal field is
114
- * changed.
115
- *
116
- * Example:
117
- * ```js
118
- * customField.formatValue = (fieldValues) => {
119
- * return fieldValues.join("-");
120
- * }
121
- * ```
122
- */
123
- formatValue: CustomFieldFormatValueFn | undefined;
124
-
125
- /**
126
- * A function to parse the component value into values for the individual
127
- * fields contained by the custom field. The function receives the
128
- * component value, and must return an array of values for the individual
129
- * fields in the order of their presence in the DOM.
130
- * The function is called each time the value of the component changes.
131
- *
132
- * Example:
133
- * ```js
134
- * customField.parseValue = (componentValue) => {
135
- * return componentValue.split("-");
136
- * }
137
- * ```
138
- */
139
- parseValue: CustomFieldParseValueFn | undefined;
140
-
141
- /**
142
- * The name of the control, which is submitted with the form data.
143
- */
144
- name: string | null | undefined;
145
-
146
- /**
147
- * The value of the field. When wrapping several inputs, it will contain `\t`
148
- * (Tab character) as a delimiter indicating parts intended to be used as the
149
- * corresponding inputs values.
150
- * Use the [`formatValue`](#/elements/vaadin-custom-field#property-formatValue)
151
- * and [`parseValue`](#/elements/vaadin-custom-field#property-parseValue)
152
- * properties to customize this behavior.
153
- */
154
- value: string | null | undefined;
155
-
98
+ declare class CustomField extends CustomFieldMixin(ThemableMixin(ElementMixin(HTMLElement))) {
156
99
  addEventListener<K extends keyof CustomFieldEventMap>(
157
100
  type: K,
158
101
  listener: (this: CustomField, ev: CustomFieldEventMap[K]) => void,
@@ -3,34 +3,14 @@
3
3
  * Copyright (c) 2019 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
7
6
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
8
- import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
9
- import { KeyboardMixin } from '@vaadin/a11y-base/src/keyboard-mixin.js';
7
+ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
10
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
11
- import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
12
- import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
13
- import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
+ import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
10
+ import { CustomFieldMixin } from './vaadin-custom-field-mixin.js';
11
+ import { customFieldStyles } from './vaadin-custom-field-styles.js';
14
12
 
15
- /**
16
- * Default implementation of the parse function that creates individual field
17
- * values from the single component value.
18
- * @param value
19
- * @returns {*}
20
- */
21
- const defaultParseValue = (value) => {
22
- return value.split('\t');
23
- };
24
-
25
- /**
26
- * Default implementation of the format function that creates a single component
27
- * value from individual field values.
28
- * @param inputValues
29
- * @returns {*}
30
- */
31
- const defaultFormatValue = (inputValues) => {
32
- return inputValues.join('\t');
33
- };
13
+ registerStyles('vaadin-custom-field', customFieldStyles, { moduleId: 'vaadin-custom-field-styles' });
34
14
 
35
15
  /**
36
16
  * `<vaadin-custom-field>` is a web component for wrapping multiple components as a single field.
@@ -75,55 +55,26 @@ const defaultFormatValue = (inputValues) => {
75
55
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
76
56
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
77
57
  *
58
+ * @customElement
78
59
  * @extends HTMLElement
79
- * @mixes FieldMixin
80
- * @mixes FocusMixin
60
+ * @mixes CustomFieldMixin
81
61
  * @mixes ElementMixin
82
- * @mixes KeyboardMixin
83
62
  * @mixes ThemableMixin
84
63
  */
85
- class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(ElementMixin(PolymerElement))))) {
64
+ class CustomField extends CustomFieldMixin(ThemableMixin(ElementMixin(PolymerElement))) {
86
65
  static get is() {
87
66
  return 'vaadin-custom-field';
88
67
  }
89
68
 
90
69
  static get template() {
91
70
  return html`
92
- <style>
93
- :host {
94
- display: inline-flex;
95
- }
96
-
97
- :host::before {
98
- content: '\\2003';
99
- width: 0;
100
- display: inline-block;
101
- /* Size and position this element on the same vertical position as the input-field element
102
- to make vertical align for the host element work as expected */
103
- }
104
-
105
- :host([hidden]) {
106
- display: none !important;
107
- }
108
-
109
- .vaadin-custom-field-container {
110
- width: 100%;
111
- display: flex;
112
- flex-direction: column;
113
- }
114
-
115
- .inputs-wrapper {
116
- flex: none;
117
- }
118
- </style>
119
-
120
71
  <div class="vaadin-custom-field-container">
121
72
  <div part="label" on-click="focus">
122
73
  <slot name="label"></slot>
123
74
  <span part="required-indicator" aria-hidden="true"></span>
124
75
  </div>
125
76
 
126
- <div class="inputs-wrapper" on-change="__onInputChange">
77
+ <div class="inputs-wrapper" on-change="_onInputChange">
127
78
  <slot id="slot"></slot>
128
79
  </div>
129
80
 
@@ -140,272 +91,6 @@ class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(Elem
140
91
  `;
141
92
  }
142
93
 
143
- static get properties() {
144
- return {
145
- /**
146
- * The name of the control, which is submitted with the form data.
147
- */
148
- name: String,
149
-
150
- /**
151
- * The value of the field. When wrapping several inputs, it will contain `\t`
152
- * (Tab character) as a delimiter indicating parts intended to be used as the
153
- * corresponding inputs values.
154
- * Use the [`formatValue`](#/elements/vaadin-custom-field#property-formatValue)
155
- * and [`parseValue`](#/elements/vaadin-custom-field#property-parseValue)
156
- * properties to customize this behavior.
157
- */
158
- value: {
159
- type: String,
160
- observer: '__valueChanged',
161
- notify: true,
162
- },
163
-
164
- /**
165
- * Array of available input nodes
166
- * @type {!Array<!HTMLElement> | undefined}
167
- */
168
- inputs: {
169
- type: Array,
170
- readOnly: true,
171
- },
172
-
173
- /**
174
- * A function to format the values of the individual fields contained by
175
- * the custom field into a single component value. The function receives
176
- * an array of all values of the individual fields in the order of their
177
- * presence in the DOM, and must return a single component value.
178
- * This function is called each time a value of an internal field is
179
- * changed.
180
- *
181
- * Example:
182
- * ```js
183
- * customField.formatValue = (fieldValues) => {
184
- * return fieldValues.join("-");
185
- * }
186
- * ```
187
- * @type {!CustomFieldFormatValueFn | undefined}
188
- */
189
- formatValue: {
190
- type: Function,
191
- },
192
-
193
- /**
194
- * A function to parse the component value into values for the individual
195
- * fields contained by the custom field. The function receives the
196
- * component value, and must return an array of values for the individual
197
- * fields in the order of their presence in the DOM.
198
- * The function is called each time the value of the component changes.
199
- *
200
- * Example:
201
- * ```js
202
- * customField.parseValue = (componentValue) => {
203
- * return componentValue.split("-");
204
- * }
205
- * ```
206
- * @type {!CustomFieldParseValueFn | undefined}
207
- */
208
- parseValue: {
209
- type: Function,
210
- },
211
- };
212
- }
213
-
214
- /** @protected */
215
- connectedCallback() {
216
- super.connectedCallback();
217
-
218
- if (this.__observer) {
219
- this.__observer.connect();
220
- }
221
- }
222
-
223
- /** @protected */
224
- disconnectedCallback() {
225
- super.disconnectedCallback();
226
-
227
- if (this.__observer) {
228
- this.__observer.disconnect();
229
- }
230
- }
231
-
232
- /** @protected */
233
- ready() {
234
- super.ready();
235
-
236
- // See https://github.com/vaadin/vaadin-web-components/issues/94
237
- this.setAttribute('role', 'group');
238
-
239
- this.ariaTarget = this;
240
-
241
- this.__setInputsFromSlot();
242
- this.__observer = new FlattenedNodesObserver(this.$.slot, () => {
243
- this.__setInputsFromSlot();
244
- });
245
-
246
- this._tooltipController = new TooltipController(this);
247
- this.addController(this._tooltipController);
248
- this._tooltipController.setShouldShow((target) => {
249
- const inputs = target.inputs || [];
250
- return !inputs.some((el) => el.opened);
251
- });
252
- }
253
-
254
- /** @protected */
255
- focus() {
256
- if (this.inputs && this.inputs[0]) {
257
- this.inputs[0].focus();
258
- }
259
- }
260
-
261
- /**
262
- * Override method inherited from `FocusMixin` to validate on blur.
263
- * @param {boolean} focused
264
- * @protected
265
- */
266
- _setFocused(focused) {
267
- super._setFocused(focused);
268
-
269
- if (!focused) {
270
- this.validate();
271
- }
272
- }
273
-
274
- /**
275
- * Override method inherited from `FocusMixin` to not remove focused
276
- * state when focus moves to another input in the custom field.
277
- * @param {FocusEvent} event
278
- * @return {boolean}
279
- * @protected
280
- */
281
- _shouldRemoveFocus(event) {
282
- const { relatedTarget } = event;
283
- return !this.inputs.some((el) => relatedTarget === (el.focusElement || el));
284
- }
285
-
286
- /**
287
- * Returns true if the current inputs values satisfy all constraints (if any).
288
- *
289
- * @return {boolean}
290
- */
291
- checkValidity() {
292
- const invalidFields = this.inputs.filter((input) => !(input.validate || input.checkValidity).call(input));
293
-
294
- if (invalidFields.length || (this.required && !this.value.trim())) {
295
- // Either 1. one of the input fields is invalid or
296
- // 2. the custom field itself is required but doesn't have a value
297
- return false;
298
- }
299
- return true;
300
- }
301
-
302
- /**
303
- * @param {KeyboardEvent} e
304
- * @protected
305
- * @override
306
- */
307
- _onKeyDown(e) {
308
- if (e.key === 'Tab') {
309
- if (
310
- (this.inputs.indexOf(e.target) < this.inputs.length - 1 && !e.shiftKey) ||
311
- (this.inputs.indexOf(e.target) > 0 && e.shiftKey)
312
- ) {
313
- this.dispatchEvent(new CustomEvent('internal-tab'));
314
- } else {
315
- // FIXME(yuriy): remove this workaround when value should not be updated before focusout
316
- this.__setValue();
317
- }
318
- }
319
- }
320
-
321
- /** @private */
322
- __onInputChange(event) {
323
- // Stop native change events
324
- event.stopPropagation();
325
-
326
- this.__setValue();
327
- this.validate();
328
- this.dispatchEvent(
329
- new CustomEvent('change', {
330
- bubbles: true,
331
- cancelable: false,
332
- detail: {
333
- value: this.value,
334
- },
335
- }),
336
- );
337
- }
338
-
339
- /** @private */
340
- __setValue() {
341
- this.__settingValue = true;
342
- const formatFn = this.formatValue || defaultFormatValue;
343
- this.value = formatFn.apply(this, [this.inputs.map((input) => input.value)]);
344
- this.__settingValue = false;
345
- }
346
-
347
- /**
348
- * Like querySelectorAll('*') but also gets all elements through any nested slots recursively
349
- * @private
350
- */
351
- __queryAllAssignedElements(elem) {
352
- const result = [];
353
- let elements;
354
- if (elem.tagName === 'SLOT') {
355
- elements = elem.assignedElements({ flatten: true });
356
- } else {
357
- result.push(elem);
358
- elements = Array.from(elem.children);
359
- }
360
- elements.forEach((elem) => result.push(...this.__queryAllAssignedElements(elem)));
361
- return result;
362
- }
363
-
364
- /** @private */
365
- __isInput(node) {
366
- const isSlottedInput = node.getAttribute('slot') === 'input' || node.getAttribute('slot') === 'textarea';
367
- return !isSlottedInput && (node.validate || node.checkValidity);
368
- }
369
-
370
- /** @private */
371
- __getInputsFromSlot() {
372
- return this.__queryAllAssignedElements(this.$.slot).filter((node) => this.__isInput(node));
373
- }
374
-
375
- /** @private */
376
- __setInputsFromSlot() {
377
- this._setInputs(this.__getInputsFromSlot());
378
- this.__setValue();
379
- }
380
-
381
- /** @private */
382
- __toggleHasValue(value) {
383
- this.toggleAttribute('has-value', value !== null && value.trim() !== '');
384
- }
385
-
386
- /** @private */
387
- __valueChanged(value, oldValue) {
388
- if (this.__settingValue || !this.inputs) {
389
- return;
390
- }
391
-
392
- this.__toggleHasValue(value);
393
-
394
- const parseFn = this.parseValue || defaultParseValue;
395
- const valuesArray = parseFn.apply(this, [value]);
396
- if (!valuesArray || valuesArray.length === 0) {
397
- console.warn('Value parser has not provided values array');
398
- return;
399
- }
400
-
401
- this.inputs.forEach((input, id) => {
402
- input.value = valuesArray[id];
403
- });
404
- if (oldValue !== undefined) {
405
- this.validate();
406
- }
407
- }
408
-
409
94
  /**
410
95
  * Fired when the user commits a value change for any of the internal inputs.
411
96
  *
@@ -413,6 +98,6 @@ class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(Elem
413
98
  */
414
99
  }
415
100
 
416
- customElements.define(CustomField.is, CustomField);
101
+ defineCustomElement(CustomField);
417
102
 
418
103
  export { CustomField };
package/web-types.json ADDED
@@ -0,0 +1,269 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/web-types",
3
+ "name": "@vaadin/custom-field",
4
+ "version": "24.3.0-alpha2",
5
+ "description-markup": "markdown",
6
+ "contributions": {
7
+ "html": {
8
+ "elements": [
9
+ {
10
+ "name": "vaadin-custom-field",
11
+ "description": "`<vaadin-custom-field>` is a web component for wrapping multiple components as a single field.\n\n```\n<vaadin-custom-field label=\"Appointment time\">\n <vaadin-date-picker></vaadin-date-picker>\n <vaadin-time-picker></vaadin-time-picker>\n</vaadin-custom-field>\n```\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------------|----------------\n`label` | The slotted label element wrapper\n`helper-text` | The slotted helper text element wrapper\n`error-message` | The slotted error message element wrapper\n`required-indicator` | The `required` state indicator element\n\nThe following state attributes are available for styling:\n\nAttribute | Description | Part name\n--------------------|-------------------------------------------|------------\n`invalid` | Set when the element is invalid | :host\n`focused` | Set when the element is focused | :host\n`has-label` | Set when the element has a label | :host\n`has-value` | Set when the element has a value | :host\n`has-helper` | Set when the element has helper text | :host\n`has-error-message` | Set when the element has an error message | :host\n\nYou may also manually set `disabled` or `readonly` attribute on this component to make the label\npart look visually the same as on a `<vaadin-text-field>` when it is disabled or readonly.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
12
+ "attributes": [
13
+ {
14
+ "name": "label",
15
+ "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
16
+ "value": {
17
+ "type": [
18
+ "string",
19
+ "null",
20
+ "undefined"
21
+ ]
22
+ }
23
+ },
24
+ {
25
+ "name": "invalid",
26
+ "description": "Set to true when the field is invalid.",
27
+ "value": {
28
+ "type": [
29
+ "boolean",
30
+ "null",
31
+ "undefined"
32
+ ]
33
+ }
34
+ },
35
+ {
36
+ "name": "required",
37
+ "description": "Specifies that the user must fill in a value.",
38
+ "value": {
39
+ "type": [
40
+ "boolean",
41
+ "null",
42
+ "undefined"
43
+ ]
44
+ }
45
+ },
46
+ {
47
+ "name": "error-message",
48
+ "description": "Error to show when the field is invalid.",
49
+ "value": {
50
+ "type": [
51
+ "string",
52
+ "null",
53
+ "undefined"
54
+ ]
55
+ }
56
+ },
57
+ {
58
+ "name": "helper-text",
59
+ "description": "String used for the helper text.",
60
+ "value": {
61
+ "type": [
62
+ "string",
63
+ "null",
64
+ "undefined"
65
+ ]
66
+ }
67
+ },
68
+ {
69
+ "name": "accessible-name",
70
+ "description": "String used to label the component to screen reader users.",
71
+ "value": {
72
+ "type": [
73
+ "string",
74
+ "null",
75
+ "undefined"
76
+ ]
77
+ }
78
+ },
79
+ {
80
+ "name": "accessible-name-ref",
81
+ "description": "Id of the element used as label of the component to screen reader users.",
82
+ "value": {
83
+ "type": [
84
+ "string",
85
+ "null",
86
+ "undefined"
87
+ ]
88
+ }
89
+ },
90
+ {
91
+ "name": "name",
92
+ "description": "The name of the control, which is submitted with the form data.",
93
+ "value": {
94
+ "type": [
95
+ "string",
96
+ "null",
97
+ "undefined"
98
+ ]
99
+ }
100
+ },
101
+ {
102
+ "name": "value",
103
+ "description": "The value of the field. When wrapping several inputs, it will contain `\\t`\n(Tab character) as a delimiter indicating parts intended to be used as the\ncorresponding inputs values.\nUse the [`formatValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-formatValue)\nand [`parseValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-parseValue)\nproperties to customize this behavior.",
104
+ "value": {
105
+ "type": [
106
+ "string",
107
+ "null",
108
+ "undefined"
109
+ ]
110
+ }
111
+ },
112
+ {
113
+ "name": "theme",
114
+ "description": "The theme variants to apply to the component.",
115
+ "value": {
116
+ "type": [
117
+ "string",
118
+ "null",
119
+ "undefined"
120
+ ]
121
+ }
122
+ }
123
+ ],
124
+ "js": {
125
+ "properties": [
126
+ {
127
+ "name": "label",
128
+ "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
129
+ "value": {
130
+ "type": [
131
+ "string",
132
+ "null",
133
+ "undefined"
134
+ ]
135
+ }
136
+ },
137
+ {
138
+ "name": "invalid",
139
+ "description": "Set to true when the field is invalid.",
140
+ "value": {
141
+ "type": [
142
+ "boolean",
143
+ "null",
144
+ "undefined"
145
+ ]
146
+ }
147
+ },
148
+ {
149
+ "name": "required",
150
+ "description": "Specifies that the user must fill in a value.",
151
+ "value": {
152
+ "type": [
153
+ "boolean",
154
+ "null",
155
+ "undefined"
156
+ ]
157
+ }
158
+ },
159
+ {
160
+ "name": "errorMessage",
161
+ "description": "Error to show when the field is invalid.",
162
+ "value": {
163
+ "type": [
164
+ "string",
165
+ "null",
166
+ "undefined"
167
+ ]
168
+ }
169
+ },
170
+ {
171
+ "name": "helperText",
172
+ "description": "String used for the helper text.",
173
+ "value": {
174
+ "type": [
175
+ "string",
176
+ "null",
177
+ "undefined"
178
+ ]
179
+ }
180
+ },
181
+ {
182
+ "name": "accessibleName",
183
+ "description": "String used to label the component to screen reader users.",
184
+ "value": {
185
+ "type": [
186
+ "string",
187
+ "null",
188
+ "undefined"
189
+ ]
190
+ }
191
+ },
192
+ {
193
+ "name": "accessibleNameRef",
194
+ "description": "Id of the element used as label of the component to screen reader users.",
195
+ "value": {
196
+ "type": [
197
+ "string",
198
+ "null",
199
+ "undefined"
200
+ ]
201
+ }
202
+ },
203
+ {
204
+ "name": "name",
205
+ "description": "The name of the control, which is submitted with the form data.",
206
+ "value": {
207
+ "type": [
208
+ "string",
209
+ "null",
210
+ "undefined"
211
+ ]
212
+ }
213
+ },
214
+ {
215
+ "name": "value",
216
+ "description": "The value of the field. When wrapping several inputs, it will contain `\\t`\n(Tab character) as a delimiter indicating parts intended to be used as the\ncorresponding inputs values.\nUse the [`formatValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-formatValue)\nand [`parseValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-parseValue)\nproperties to customize this behavior.",
217
+ "value": {
218
+ "type": [
219
+ "string",
220
+ "null",
221
+ "undefined"
222
+ ]
223
+ }
224
+ },
225
+ {
226
+ "name": "formatValue",
227
+ "description": "A function to format the values of the individual fields contained by\nthe custom field into a single component value. The function receives\nan array of all values of the individual fields in the order of their\npresence in the DOM, and must return a single component value.\nThis function is called each time a value of an internal field is\nchanged.\n\nExample:\n```js\ncustomField.formatValue = (fieldValues) => {\n return fieldValues.join(\"-\");\n}\n```",
228
+ "value": {
229
+ "type": [
230
+ "CustomFieldFormatValueFn",
231
+ "undefined"
232
+ ]
233
+ }
234
+ },
235
+ {
236
+ "name": "parseValue",
237
+ "description": "A function to parse the component value into values for the individual\nfields contained by the custom field. The function receives the\ncomponent value, and must return an array of values for the individual\nfields in the order of their presence in the DOM.\nThe function is called each time the value of the component changes.\n\nExample:\n```js\ncustomField.parseValue = (componentValue) => {\n return componentValue.split(\"-\");\n}\n```",
238
+ "value": {
239
+ "type": [
240
+ "CustomFieldParseValueFn",
241
+ "undefined"
242
+ ]
243
+ }
244
+ }
245
+ ],
246
+ "events": [
247
+ {
248
+ "name": "validated",
249
+ "description": "Fired whenever the field is validated."
250
+ },
251
+ {
252
+ "name": "change",
253
+ "description": "Fired when the user commits a value change for any of the internal inputs."
254
+ },
255
+ {
256
+ "name": "invalid-changed",
257
+ "description": "Fired when the `invalid` property changes."
258
+ },
259
+ {
260
+ "name": "value-changed",
261
+ "description": "Fired when the `value` property changes."
262
+ }
263
+ ]
264
+ }
265
+ }
266
+ ]
267
+ }
268
+ }
269
+ }
@@ -0,0 +1,132 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/web-types",
3
+ "name": "@vaadin/custom-field",
4
+ "version": "24.3.0-alpha2",
5
+ "description-markup": "markdown",
6
+ "framework": "lit",
7
+ "framework-config": {
8
+ "enable-when": {
9
+ "node-packages": [
10
+ "lit"
11
+ ]
12
+ }
13
+ },
14
+ "contributions": {
15
+ "html": {
16
+ "elements": [
17
+ {
18
+ "name": "vaadin-custom-field",
19
+ "description": "`<vaadin-custom-field>` is a web component for wrapping multiple components as a single field.\n\n```\n<vaadin-custom-field label=\"Appointment time\">\n <vaadin-date-picker></vaadin-date-picker>\n <vaadin-time-picker></vaadin-time-picker>\n</vaadin-custom-field>\n```\n\n### Styling\n\nThe following shadow DOM parts are available for styling:\n\nPart name | Description\n---------------------|----------------\n`label` | The slotted label element wrapper\n`helper-text` | The slotted helper text element wrapper\n`error-message` | The slotted error message element wrapper\n`required-indicator` | The `required` state indicator element\n\nThe following state attributes are available for styling:\n\nAttribute | Description | Part name\n--------------------|-------------------------------------------|------------\n`invalid` | Set when the element is invalid | :host\n`focused` | Set when the element is focused | :host\n`has-label` | Set when the element has a label | :host\n`has-value` | Set when the element has a value | :host\n`has-helper` | Set when the element has helper text | :host\n`has-error-message` | Set when the element has an error message | :host\n\nYou may also manually set `disabled` or `readonly` attribute on this component to make the label\npart look visually the same as on a `<vaadin-text-field>` when it is disabled or readonly.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.",
20
+ "extension": true,
21
+ "attributes": [
22
+ {
23
+ "name": "?invalid",
24
+ "description": "Set to true when the field is invalid.",
25
+ "value": {
26
+ "kind": "expression"
27
+ }
28
+ },
29
+ {
30
+ "name": "?required",
31
+ "description": "Specifies that the user must fill in a value.",
32
+ "value": {
33
+ "kind": "expression"
34
+ }
35
+ },
36
+ {
37
+ "name": ".label",
38
+ "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
39
+ "value": {
40
+ "kind": "expression"
41
+ }
42
+ },
43
+ {
44
+ "name": ".errorMessage",
45
+ "description": "Error to show when the field is invalid.",
46
+ "value": {
47
+ "kind": "expression"
48
+ }
49
+ },
50
+ {
51
+ "name": ".helperText",
52
+ "description": "String used for the helper text.",
53
+ "value": {
54
+ "kind": "expression"
55
+ }
56
+ },
57
+ {
58
+ "name": ".accessibleName",
59
+ "description": "String used to label the component to screen reader users.",
60
+ "value": {
61
+ "kind": "expression"
62
+ }
63
+ },
64
+ {
65
+ "name": ".accessibleNameRef",
66
+ "description": "Id of the element used as label of the component to screen reader users.",
67
+ "value": {
68
+ "kind": "expression"
69
+ }
70
+ },
71
+ {
72
+ "name": ".name",
73
+ "description": "The name of the control, which is submitted with the form data.",
74
+ "value": {
75
+ "kind": "expression"
76
+ }
77
+ },
78
+ {
79
+ "name": ".value",
80
+ "description": "The value of the field. When wrapping several inputs, it will contain `\\t`\n(Tab character) as a delimiter indicating parts intended to be used as the\ncorresponding inputs values.\nUse the [`formatValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-formatValue)\nand [`parseValue`](https://cdn.vaadin.com/vaadin-web-components/24.3.0-alpha2/#/elements/vaadin-custom-field#property-parseValue)\nproperties to customize this behavior.",
81
+ "value": {
82
+ "kind": "expression"
83
+ }
84
+ },
85
+ {
86
+ "name": ".formatValue",
87
+ "description": "A function to format the values of the individual fields contained by\nthe custom field into a single component value. The function receives\nan array of all values of the individual fields in the order of their\npresence in the DOM, and must return a single component value.\nThis function is called each time a value of an internal field is\nchanged.\n\nExample:\n```js\ncustomField.formatValue = (fieldValues) => {\n return fieldValues.join(\"-\");\n}\n```",
88
+ "value": {
89
+ "kind": "expression"
90
+ }
91
+ },
92
+ {
93
+ "name": ".parseValue",
94
+ "description": "A function to parse the component value into values for the individual\nfields contained by the custom field. The function receives the\ncomponent value, and must return an array of values for the individual\nfields in the order of their presence in the DOM.\nThe function is called each time the value of the component changes.\n\nExample:\n```js\ncustomField.parseValue = (componentValue) => {\n return componentValue.split(\"-\");\n}\n```",
95
+ "value": {
96
+ "kind": "expression"
97
+ }
98
+ },
99
+ {
100
+ "name": "@validated",
101
+ "description": "Fired whenever the field is validated.",
102
+ "value": {
103
+ "kind": "expression"
104
+ }
105
+ },
106
+ {
107
+ "name": "@change",
108
+ "description": "Fired when the user commits a value change for any of the internal inputs.",
109
+ "value": {
110
+ "kind": "expression"
111
+ }
112
+ },
113
+ {
114
+ "name": "@invalid-changed",
115
+ "description": "Fired when the `invalid` property changes.",
116
+ "value": {
117
+ "kind": "expression"
118
+ }
119
+ },
120
+ {
121
+ "name": "@value-changed",
122
+ "description": "Fired when the `value` property changes.",
123
+ "value": {
124
+ "kind": "expression"
125
+ }
126
+ }
127
+ ]
128
+ }
129
+ ]
130
+ }
131
+ }
132
+ }