@vaadin/custom-field 23.2.0-dev.8a7678b70 → 23.2.0-rc1

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": "23.2.0-dev.8a7678b70",
3
+ "version": "23.2.0-rc1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -23,7 +23,9 @@
23
23
  "src",
24
24
  "theme",
25
25
  "vaadin-*.d.ts",
26
- "vaadin-*.js"
26
+ "vaadin-*.js",
27
+ "web-types.json",
28
+ "web-types.lit.json"
27
29
  ],
28
30
  "keywords": [
29
31
  "Vaadin",
@@ -33,28 +35,32 @@
33
35
  ],
34
36
  "dependencies": {
35
37
  "@polymer/polymer": "^3.0.0",
36
- "@vaadin/component-base": "23.2.0-dev.8a7678b70",
37
- "@vaadin/field-base": "23.2.0-dev.8a7678b70",
38
- "@vaadin/vaadin-lumo-styles": "23.2.0-dev.8a7678b70",
39
- "@vaadin/vaadin-material-styles": "23.2.0-dev.8a7678b70",
40
- "@vaadin/vaadin-themable-mixin": "23.2.0-dev.8a7678b70"
38
+ "@vaadin/component-base": "23.2.0-rc1",
39
+ "@vaadin/field-base": "23.2.0-rc1",
40
+ "@vaadin/vaadin-lumo-styles": "23.2.0-rc1",
41
+ "@vaadin/vaadin-material-styles": "23.2.0-rc1",
42
+ "@vaadin/vaadin-themable-mixin": "23.2.0-rc1"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@esm-bundle/chai": "^4.3.4",
44
- "@vaadin/combo-box": "23.2.0-dev.8a7678b70",
45
- "@vaadin/date-picker": "23.2.0-dev.8a7678b70",
46
- "@vaadin/email-field": "23.2.0-dev.8a7678b70",
47
- "@vaadin/form-layout": "23.2.0-dev.8a7678b70",
48
- "@vaadin/list-box": "23.2.0-dev.8a7678b70",
49
- "@vaadin/number-field": "23.2.0-dev.8a7678b70",
50
- "@vaadin/password-field": "23.2.0-dev.8a7678b70",
51
- "@vaadin/polymer-legacy-adapter": "23.2.0-dev.8a7678b70",
52
- "@vaadin/select": "23.2.0-dev.8a7678b70",
46
+ "@vaadin/combo-box": "23.2.0-rc1",
47
+ "@vaadin/date-picker": "23.2.0-rc1",
48
+ "@vaadin/email-field": "23.2.0-rc1",
49
+ "@vaadin/form-layout": "23.2.0-rc1",
50
+ "@vaadin/list-box": "23.2.0-rc1",
51
+ "@vaadin/number-field": "23.2.0-rc1",
52
+ "@vaadin/password-field": "23.2.0-rc1",
53
+ "@vaadin/polymer-legacy-adapter": "23.2.0-rc1",
54
+ "@vaadin/select": "23.2.0-rc1",
53
55
  "@vaadin/testing-helpers": "^0.3.2",
54
- "@vaadin/text-area": "23.2.0-dev.8a7678b70",
55
- "@vaadin/text-field": "23.2.0-dev.8a7678b70",
56
- "@vaadin/time-picker": "23.2.0-dev.8a7678b70",
56
+ "@vaadin/text-area": "23.2.0-rc1",
57
+ "@vaadin/text-field": "23.2.0-rc1",
58
+ "@vaadin/time-picker": "23.2.0-rc1",
57
59
  "sinon": "^13.0.2"
58
60
  },
59
- "gitHead": "85b403f96d8282f262322b56c0ff4289f843d02a"
61
+ "web-types": [
62
+ "web-types.json",
63
+ "web-types.lit.json"
64
+ ],
65
+ "gitHead": "e78a1f2fe6f42d78cefa3f48085b09a3033c9588"
60
66
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
7
  import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
8
+ import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
8
9
  import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
9
10
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
10
11
 
@@ -35,10 +36,26 @@ export type CustomFieldInvalidChangedEvent = CustomEvent<{ value: boolean }>;
35
36
  */
36
37
  export type CustomFieldValueChangedEvent = CustomEvent<{ value: string }>;
37
38
 
39
+ /**
40
+ * Fired whenever the field is validated.
41
+ */
42
+ export type CustomFieldValidatedEvent = CustomEvent<{ valid: boolean }>;
43
+
44
+ /**
45
+ * Fired on Tab keydown triggered from the internal inputs, meaning focus will not leave the inputs.
46
+ */
47
+ export type CustomFieldInternalTabEvent = Event & {
48
+ target: CustomField;
49
+ };
50
+
38
51
  export interface CustomFieldCustomEventMap {
39
52
  'invalid-changed': CustomFieldInvalidChangedEvent;
40
53
 
41
54
  'value-changed': CustomFieldValueChangedEvent;
55
+
56
+ 'internal-tab': CustomFieldInternalTabEvent;
57
+
58
+ validated: CustomFieldValidatedEvent;
42
59
  }
43
60
 
44
61
  export interface CustomFieldEventMap extends HTMLElementEventMap, CustomFieldCustomEventMap {
@@ -84,8 +101,9 @@ export interface CustomFieldEventMap extends HTMLElementEventMap, CustomFieldCus
84
101
  * @fires {Event} internal-tab - Fired on Tab keydown triggered from the internal inputs, meaning focus will not leave the inputs.
85
102
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
86
103
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
104
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
87
105
  */
88
- declare class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMixin(HTMLElement)))) {
106
+ declare class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(ElementMixin(HTMLElement))))) {
89
107
  /**
90
108
  * Array of available input nodes
91
109
  */
@@ -144,13 +162,13 @@ declare class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMix
144
162
  addEventListener<K extends keyof CustomFieldEventMap>(
145
163
  type: K,
146
164
  listener: (this: CustomField, ev: CustomFieldEventMap[K]) => void,
147
- options?: boolean | AddEventListenerOptions,
165
+ options?: AddEventListenerOptions | boolean,
148
166
  ): void;
149
167
 
150
168
  removeEventListener<K extends keyof CustomFieldEventMap>(
151
169
  type: K,
152
170
  listener: (this: CustomField, ev: CustomFieldEventMap[K]) => void,
153
- options?: boolean | EventListenerOptions,
171
+ options?: EventListenerOptions | boolean,
154
172
  ): void;
155
173
  }
156
174
 
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
7
7
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
8
- import { isChrome } from '@vaadin/component-base/src/browser-utils.js';
9
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
9
  import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js';
10
+ import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js';
11
11
  import { FieldMixin } from '@vaadin/field-base/src/field-mixin.js';
12
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
13
 
@@ -50,14 +50,16 @@ import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mix
50
50
  * @fires {Event} internal-tab - Fired on Tab keydown triggered from the internal inputs, meaning focus will not leave the inputs.
51
51
  * @fires {CustomEvent} invalid-changed - Fired when the `invalid` property changes.
52
52
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
53
+ * @fires {CustomEvent} validated - Fired whenever the field is validated.
53
54
  *
54
55
  * @extends HTMLElement
55
56
  * @mixes FieldMixin
56
57
  * @mixes FocusMixin
57
58
  * @mixes ElementMixin
59
+ * @mixes KeyboardMixin
58
60
  * @mixes ThemableMixin
59
61
  */
60
- class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMixin(PolymerElement)))) {
62
+ class CustomField extends FieldMixin(FocusMixin(KeyboardMixin(ThemableMixin(ElementMixin(PolymerElement))))) {
61
63
  static get is() {
62
64
  return 'vaadin-custom-field';
63
65
  }
@@ -220,8 +222,6 @@ class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMixin(Polym
220
222
  this.__observer = new FlattenedNodesObserver(this.$.slot, () => {
221
223
  this.__setInputsFromSlot();
222
224
  });
223
-
224
- this.__fixChromeFocus();
225
225
  }
226
226
 
227
227
  /** @protected */
@@ -272,28 +272,23 @@ class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMixin(Polym
272
272
  return true;
273
273
  }
274
274
 
275
- /** @private */
276
- __fixChromeFocus() {
277
- this.addEventListener('keydown', (e) => {
278
- if (e.keyCode === 9) {
279
- // FIXME(yuriy): remove this workaround once this issue is fixed:
280
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1014868&can=2&num=100&q=slot%20shift%20tab
281
- if (e.target.parentElement.localName === 'slot' && !e.defaultPrevented && isChrome) {
282
- const slot = e.target.parentElement;
283
- slot.setAttribute('tabindex', -1);
284
- setTimeout(() => slot.removeAttribute('tabindex'));
285
- }
286
- if (
287
- (this.inputs.indexOf(e.target) < this.inputs.length - 1 && !e.shiftKey) ||
288
- (this.inputs.indexOf(e.target) > 0 && e.shiftKey)
289
- ) {
290
- this.dispatchEvent(new CustomEvent('internal-tab'));
291
- } else {
292
- // FIXME(yuriy): remove this workaround when value should not be updated before focusout
293
- this.__setValue();
294
- }
275
+ /**
276
+ * @param {KeyboardEvent} e
277
+ * @protected
278
+ * @override
279
+ */
280
+ _onKeyDown(e) {
281
+ if (e.key === 'Tab') {
282
+ if (
283
+ (this.inputs.indexOf(e.target) < this.inputs.length - 1 && !e.shiftKey) ||
284
+ (this.inputs.indexOf(e.target) > 0 && e.shiftKey)
285
+ ) {
286
+ this.dispatchEvent(new CustomEvent('internal-tab'));
287
+ } else {
288
+ // FIXME(yuriy): remove this workaround when value should not be updated before focusout
289
+ this.__setValue();
295
290
  }
296
- });
291
+ }
297
292
  }
298
293
 
299
294
  /** @private */
@@ -374,7 +369,9 @@ class CustomField extends FieldMixin(FocusMixin(ThemableMixin(ElementMixin(Polym
374
369
  return;
375
370
  }
376
371
 
377
- this.inputs.forEach((input, id) => (input.value = valuesArray[id]));
372
+ this.inputs.forEach((input, id) => {
373
+ input.value = valuesArray[id];
374
+ });
378
375
  if (oldValue !== undefined) {
379
376
  this.validate();
380
377
  }
package/web-types.json ADDED
@@ -0,0 +1,210 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/web-types",
3
+ "name": "@vaadin/custom-field",
4
+ "version": "23.2.0-rc1",
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`disabled` | Set when the element is disabled | :host\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\nSee [Styling Components](https://vaadin.com/docs/latest/ds/customization/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": "name",
70
+ "description": "The name of the control, which is submitted with the form data.",
71
+ "value": {
72
+ "type": [
73
+ "string",
74
+ "null",
75
+ "undefined"
76
+ ]
77
+ }
78
+ },
79
+ {
80
+ "name": "value",
81
+ "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. Use the [`i18n`](https://cdn.vaadin.com/vaadin-web-components/23.2.0-rc1/#/elements/vaadin-custom-field#property-i18n)\nproperty to customize this behavior.",
82
+ "value": {
83
+ "type": [
84
+ "string",
85
+ "null",
86
+ "undefined"
87
+ ]
88
+ }
89
+ },
90
+ {
91
+ "name": "theme",
92
+ "description": "The theme variants to apply to the component.",
93
+ "value": {
94
+ "type": [
95
+ "string",
96
+ "null",
97
+ "undefined"
98
+ ]
99
+ }
100
+ }
101
+ ],
102
+ "js": {
103
+ "properties": [
104
+ {
105
+ "name": "label",
106
+ "description": "The label text for the input node.\nWhen no light dom defined via [slot=label], this value will be used.",
107
+ "value": {
108
+ "type": [
109
+ "string",
110
+ "null",
111
+ "undefined"
112
+ ]
113
+ }
114
+ },
115
+ {
116
+ "name": "invalid",
117
+ "description": "Set to true when the field is invalid.",
118
+ "value": {
119
+ "type": [
120
+ "boolean",
121
+ "null",
122
+ "undefined"
123
+ ]
124
+ }
125
+ },
126
+ {
127
+ "name": "required",
128
+ "description": "Specifies that the user must fill in a value.",
129
+ "value": {
130
+ "type": [
131
+ "boolean",
132
+ "null",
133
+ "undefined"
134
+ ]
135
+ }
136
+ },
137
+ {
138
+ "name": "errorMessage",
139
+ "description": "Error to show when the field is invalid.",
140
+ "value": {
141
+ "type": [
142
+ "string",
143
+ "null",
144
+ "undefined"
145
+ ]
146
+ }
147
+ },
148
+ {
149
+ "name": "helperText",
150
+ "description": "String used for the helper text.",
151
+ "value": {
152
+ "type": [
153
+ "string",
154
+ "null",
155
+ "undefined"
156
+ ]
157
+ }
158
+ },
159
+ {
160
+ "name": "name",
161
+ "description": "The name of the control, which is submitted with the form data.",
162
+ "value": {
163
+ "type": [
164
+ "string",
165
+ "null",
166
+ "undefined"
167
+ ]
168
+ }
169
+ },
170
+ {
171
+ "name": "value",
172
+ "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. Use the [`i18n`](https://cdn.vaadin.com/vaadin-web-components/23.2.0-rc1/#/elements/vaadin-custom-field#property-i18n)\nproperty to customize this behavior.",
173
+ "value": {
174
+ "type": [
175
+ "string",
176
+ "null",
177
+ "undefined"
178
+ ]
179
+ }
180
+ },
181
+ {
182
+ "name": "i18n",
183
+ "description": "The object used to localize this component.\nTo change the default localization, replace the entire\n_i18n_ object or just the property you want to modify.\n\nThe object has the following JSON structure:\n\n```\n{\n // A function to format given `Array` as\n // component value. Array is list of all internal values\n // in the order of their presence in the DOM\n // This function is called each time the internal input\n // value is changed.\n formatValue: inputValues => {\n // returns a representation of the given array of values\n // in the form of string with delimiter characters\n },\n\n // A function to parse the given value to an `Array` in the format\n // of the list of all internal values\n // in the order of their presence in the DOM\n // This function is called when value of the\n // custom field is set.\n parseValue: value => {\n // returns the array of values from parsed value string.\n }\n}\n```",
184
+ "value": {
185
+ "type": [
186
+ "CustomFieldI18n"
187
+ ]
188
+ }
189
+ }
190
+ ],
191
+ "events": [
192
+ {
193
+ "name": "validated",
194
+ "description": "Fired whenever the field is validated."
195
+ },
196
+ {
197
+ "name": "change",
198
+ "description": "Fired when the user commits a value change for any of the internal inputs."
199
+ },
200
+ {
201
+ "name": "value-changed",
202
+ "description": "Fired when the `value` property changes."
203
+ }
204
+ ]
205
+ }
206
+ }
207
+ ]
208
+ }
209
+ }
210
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/web-types",
3
+ "name": "@vaadin/custom-field",
4
+ "version": "23.2.0-rc1",
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`disabled` | Set when the element is disabled | :host\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\nSee [Styling Components](https://vaadin.com/docs/latest/ds/customization/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": ".name",
59
+ "description": "The name of the control, which is submitted with the form data.",
60
+ "value": {
61
+ "kind": "expression"
62
+ }
63
+ },
64
+ {
65
+ "name": ".value",
66
+ "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. Use the [`i18n`](https://cdn.vaadin.com/vaadin-web-components/23.2.0-rc1/#/elements/vaadin-custom-field#property-i18n)\nproperty to customize this behavior.",
67
+ "value": {
68
+ "kind": "expression"
69
+ }
70
+ },
71
+ {
72
+ "name": ".i18n",
73
+ "description": "The object used to localize this component.\nTo change the default localization, replace the entire\n_i18n_ object or just the property you want to modify.\n\nThe object has the following JSON structure:\n\n```\n{\n // A function to format given `Array` as\n // component value. Array is list of all internal values\n // in the order of their presence in the DOM\n // This function is called each time the internal input\n // value is changed.\n formatValue: inputValues => {\n // returns a representation of the given array of values\n // in the form of string with delimiter characters\n },\n\n // A function to parse the given value to an `Array` in the format\n // of the list of all internal values\n // in the order of their presence in the DOM\n // This function is called when value of the\n // custom field is set.\n parseValue: value => {\n // returns the array of values from parsed value string.\n }\n}\n```",
74
+ "value": {
75
+ "kind": "expression"
76
+ }
77
+ },
78
+ {
79
+ "name": "@validated",
80
+ "description": "Fired whenever the field is validated.",
81
+ "value": {
82
+ "kind": "expression"
83
+ }
84
+ },
85
+ {
86
+ "name": "@change",
87
+ "description": "Fired when the user commits a value change for any of the internal inputs.",
88
+ "value": {
89
+ "kind": "expression"
90
+ }
91
+ },
92
+ {
93
+ "name": "@value-changed",
94
+ "description": "Fired when the `value` property changes.",
95
+ "value": {
96
+ "kind": "expression"
97
+ }
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ }
103
+ }
104
+ }