@vaadin/number-field 24.0.0-alpha9 → 24.0.0-beta2

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/number-field",
3
- "version": "24.0.0-alpha9",
3
+ "version": "24.0.0-beta2",
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-number-field.d.ts",
25
+ "!src/vaadin-lit-number-field.js",
24
26
  "theme",
25
27
  "vaadin-*.d.ts",
26
28
  "vaadin-*.js",
@@ -34,22 +36,24 @@
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/component-base": "24.0.0-alpha9",
39
- "@vaadin/field-base": "24.0.0-alpha9",
40
- "@vaadin/input-container": "24.0.0-alpha9",
41
- "@vaadin/vaadin-lumo-styles": "24.0.0-alpha9",
42
- "@vaadin/vaadin-material-styles": "24.0.0-alpha9",
43
- "@vaadin/vaadin-themable-mixin": "24.0.0-alpha9"
41
+ "@vaadin/component-base": "24.0.0-beta2",
42
+ "@vaadin/field-base": "24.0.0-beta2",
43
+ "@vaadin/input-container": "24.0.0-beta2",
44
+ "@vaadin/vaadin-lumo-styles": "24.0.0-beta2",
45
+ "@vaadin/vaadin-material-styles": "24.0.0-beta2",
46
+ "@vaadin/vaadin-themable-mixin": "24.0.0-beta2",
47
+ "lit": "^2.0.0"
44
48
  },
45
49
  "devDependencies": {
46
50
  "@esm-bundle/chai": "^4.3.4",
47
- "@vaadin/testing-helpers": "^0.3.2",
51
+ "@vaadin/testing-helpers": "^0.4.0",
48
52
  "sinon": "^13.0.2"
49
53
  },
50
54
  "web-types": [
51
55
  "web-types.json",
52
56
  "web-types.lit.json"
53
57
  ],
54
- "gitHead": "cc3f747164041566b300bde4b105d2475649e93f"
58
+ "gitHead": "00086f1f6d487f042f189c9b9ecd7ba736960888"
55
59
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 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 { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
8
+ import type { DelegateFocusMixinClass } from '@vaadin/component-base/src/delegate-focus-mixin.js';
9
+ import type { DelegateStateMixinClass } from '@vaadin/component-base/src/delegate-state-mixin.js';
10
+ import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js';
11
+ import type { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js';
12
+ import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js';
13
+ import type { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
14
+ import type { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js';
15
+ import type { InputControlMixinClass } from '@vaadin/field-base/src/input-control-mixin.js';
16
+ import type { InputFieldMixinClass } from '@vaadin/field-base/src/input-field-mixin.js';
17
+ import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js';
18
+ import type { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
19
+ import type { SlotStylesMixinClass } from '@vaadin/field-base/src/slot-styles-mixin.js';
20
+ import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js';
21
+
22
+ /**
23
+ * A mixin providing common number field functionality.
24
+ */
25
+ export declare function NumberFieldMixin<T extends Constructor<HTMLElement>>(
26
+ base: T,
27
+ ): Constructor<ControllerMixinClass> &
28
+ Constructor<DelegateFocusMixinClass> &
29
+ Constructor<DelegateStateMixinClass> &
30
+ Constructor<DisabledMixinClass> &
31
+ Constructor<FieldMixinClass> &
32
+ Constructor<FocusMixinClass> &
33
+ Constructor<InputConstraintsMixinClass> &
34
+ Constructor<InputControlMixinClass> &
35
+ Constructor<InputFieldMixinClass> &
36
+ Constructor<InputMixinClass> &
37
+ Constructor<KeyboardMixinClass> &
38
+ Constructor<LabelMixinClass> &
39
+ Constructor<NumberFieldMixinClass> &
40
+ Constructor<SlotStylesMixinClass> &
41
+ Constructor<ValidateMixinClass> &
42
+ T;
43
+
44
+ export declare class NumberFieldMixinClass {
45
+ /**
46
+ * The minimum value of the field.
47
+ */
48
+ min: number | null | undefined;
49
+
50
+ /**
51
+ * The maximum value of the field.
52
+ */
53
+ max: number | null | undefined;
54
+
55
+ /**
56
+ * Specifies the allowed number intervals of the field.
57
+ */
58
+ step: number;
59
+
60
+ /**
61
+ * Set to true to show increase/decrease buttons.
62
+ * @attr {boolean} step-buttons-visible
63
+ */
64
+ stepButtonsVisible: boolean;
65
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { InputController } from '@vaadin/field-base/src/input-controller.js';
7
+ import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js';
8
+ import { LabelledInputController } from '@vaadin/field-base/src/labelled-input-controller.js';
9
+
10
+ /**
11
+ * A mixin providing common number field functionality.
12
+ *
13
+ * @polymerMixin
14
+ * @mixes InputFieldMixin
15
+ */
16
+ export const NumberFieldMixin = (superClass) =>
17
+ class NumberFieldMixinClass extends InputFieldMixin(superClass) {
18
+ static get properties() {
19
+ return {
20
+ /**
21
+ * The minimum value of the field.
22
+ */
23
+ min: {
24
+ type: Number,
25
+ },
26
+
27
+ /**
28
+ * The maximum value of the field.
29
+ */
30
+ max: {
31
+ type: Number,
32
+ },
33
+
34
+ /**
35
+ * Specifies the allowed number intervals of the field.
36
+ * @type {number}
37
+ */
38
+ step: {
39
+ type: Number,
40
+ },
41
+
42
+ /**
43
+ * Set to true to show increase/decrease buttons.
44
+ * @attr {boolean} step-buttons-visible
45
+ */
46
+ stepButtonsVisible: {
47
+ type: Boolean,
48
+ value: false,
49
+ reflectToAttribute: true,
50
+ },
51
+ };
52
+ }
53
+
54
+ static get observers() {
55
+ return ['_stepChanged(step, inputElement)'];
56
+ }
57
+
58
+ static get delegateProps() {
59
+ return [...super.delegateProps, 'min', 'max'];
60
+ }
61
+
62
+ static get constraints() {
63
+ return [...super.constraints, 'min', 'max', 'step'];
64
+ }
65
+
66
+ constructor() {
67
+ super();
68
+ this._setType('number');
69
+ }
70
+
71
+ /** @protected */
72
+ get slotStyles() {
73
+ const tag = this.localName;
74
+ return [
75
+ `
76
+ ${tag} input[type="number"]::-webkit-outer-spin-button,
77
+ ${tag} input[type="number"]::-webkit-inner-spin-button {
78
+ -webkit-appearance: none;
79
+ margin: 0;
80
+ }
81
+
82
+ ${tag} input[type="number"] {
83
+ -moz-appearance: textfield;
84
+ }
85
+
86
+ ${tag}[dir='rtl'] input[type="number"]::placeholder {
87
+ direction: rtl;
88
+ }
89
+
90
+ ${tag}[dir='rtl']:not([step-buttons-visible]) input[type="number"]::placeholder {
91
+ text-align: left;
92
+ }
93
+ `,
94
+ ];
95
+ }
96
+
97
+ /**
98
+ * Used by `InputControlMixin` as a reference to the clear button element.
99
+ * @protected
100
+ */
101
+ get clearElement() {
102
+ return this.$.clearButton;
103
+ }
104
+
105
+ /** @protected */
106
+ ready() {
107
+ super.ready();
108
+
109
+ this.addController(
110
+ new InputController(this, (input) => {
111
+ this._setInputElement(input);
112
+ this._setFocusElement(input);
113
+ this.stateTarget = input;
114
+ this.ariaTarget = input;
115
+ }),
116
+ );
117
+
118
+ this.addController(new LabelledInputController(this.inputElement, this._labelController));
119
+ }
120
+
121
+ /**
122
+ * Override the method from `InputConstraintsMixin`
123
+ * to enforce HTML constraint validation even if
124
+ * the user didn't add any constraints explicitly:
125
+ * the field has to be regardless checked for bad input.
126
+ *
127
+ * @override
128
+ */
129
+ checkValidity() {
130
+ if (this.inputElement) {
131
+ return this.inputElement.checkValidity();
132
+ }
133
+
134
+ return !this.invalid;
135
+ }
136
+
137
+ /** @protected */
138
+ _onDecreaseButtonTouchend(e) {
139
+ // Cancel the following click and focus events
140
+ e.preventDefault();
141
+ this._decreaseValue();
142
+ }
143
+
144
+ /** @protected */
145
+ _onIncreaseButtonTouchend(e) {
146
+ // Cancel the following click and focus events
147
+ e.preventDefault();
148
+ this._increaseValue();
149
+ }
150
+
151
+ /** @protected */
152
+ _onDecreaseButtonClick() {
153
+ this._decreaseValue();
154
+ }
155
+
156
+ /** @protected */
157
+ _onIncreaseButtonClick() {
158
+ this._increaseValue();
159
+ }
160
+
161
+ /** @private */
162
+ _decreaseValue() {
163
+ this._incrementValue(-1);
164
+ }
165
+
166
+ /** @private */
167
+ _increaseValue() {
168
+ this._incrementValue(1);
169
+ }
170
+
171
+ /** @private */
172
+ _incrementValue(incr) {
173
+ if (this.disabled || this.readonly) {
174
+ return;
175
+ }
176
+
177
+ const step = this.step || 1;
178
+ let value = parseFloat(this.value);
179
+
180
+ if (!this.value) {
181
+ if ((this.min === 0 && incr < 0) || (this.max === 0 && incr > 0) || (this.max === 0 && this.min === 0)) {
182
+ incr = 0;
183
+ value = 0;
184
+ } else if ((this.max == null || this.max >= 0) && (this.min == null || this.min <= 0)) {
185
+ value = 0;
186
+ } else if (this.min > 0) {
187
+ value = this.min;
188
+ if (this.max < 0 && incr < 0) {
189
+ value = this.max;
190
+ }
191
+ incr = 0;
192
+ } else if (this.max < 0) {
193
+ value = this.max;
194
+ if (incr < 0) {
195
+ incr = 0;
196
+ } else if (this._getIncrement(1, value - step) > this.max) {
197
+ value -= 2 * step;
198
+ // FIXME(yuriy): find a proper solution to make correct step back
199
+ } else {
200
+ value -= step;
201
+ }
202
+ }
203
+ } else if (value < this.min) {
204
+ incr = 0;
205
+ value = this.min;
206
+ } else if (value > this.max) {
207
+ incr = 0;
208
+ value = this.max;
209
+ }
210
+
211
+ const newValue = this._getIncrement(incr, value);
212
+ if (!this.value || incr === 0 || this._incrementIsInsideTheLimits(incr, value)) {
213
+ this._setValue(newValue);
214
+ }
215
+ }
216
+
217
+ /** @private */
218
+ _setValue(value) {
219
+ this.value = this.inputElement.value = String(parseFloat(value));
220
+ this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
221
+ }
222
+
223
+ /** @private */
224
+ _getIncrement(incr, currentValue) {
225
+ let step = this.step || 1,
226
+ min = this.min || 0;
227
+
228
+ // To avoid problems with decimal math, multiplying to operate with integers.
229
+ const multiplier = Math.max(
230
+ this._getMultiplier(currentValue),
231
+ this._getMultiplier(step),
232
+ this._getMultiplier(min),
233
+ );
234
+
235
+ step *= multiplier;
236
+ currentValue = Math.round(currentValue * multiplier);
237
+ min *= multiplier;
238
+
239
+ const margin = (currentValue - min) % step;
240
+
241
+ if (incr > 0) {
242
+ return (currentValue - margin + step) / multiplier;
243
+ } else if (incr < 0) {
244
+ return (currentValue - (margin || step)) / multiplier;
245
+ }
246
+ return currentValue / multiplier;
247
+ }
248
+
249
+ /** @private */
250
+ _getDecimalCount(number) {
251
+ const s = String(number);
252
+ const i = s.indexOf('.');
253
+ return i === -1 ? 1 : s.length - i - 1;
254
+ }
255
+
256
+ /** @private */
257
+ _getMultiplier(number) {
258
+ if (!isNaN(number)) {
259
+ return 10 ** this._getDecimalCount(number);
260
+ }
261
+ }
262
+
263
+ /** @private */
264
+ _incrementIsInsideTheLimits(incr, value) {
265
+ if (incr < 0) {
266
+ return this.min == null || this._getIncrement(incr, value) >= this.min;
267
+ } else if (incr > 0) {
268
+ return this.max == null || this._getIncrement(incr, value) <= this.max;
269
+ }
270
+ return this._getIncrement(incr, value) <= this.max && this._getIncrement(incr, value) >= this.min;
271
+ }
272
+
273
+ /** @protected */
274
+ _isButtonEnabled(sign) {
275
+ const incr = sign * (this.step || 1);
276
+ const value = parseFloat(this.value);
277
+ return !this.value || (!this.disabled && this._incrementIsInsideTheLimits(incr, value));
278
+ }
279
+
280
+ /**
281
+ * @param {number} step
282
+ * @param {HTMLElement | undefined} inputElement
283
+ * @protected
284
+ */
285
+ _stepChanged(step, inputElement) {
286
+ if (inputElement) {
287
+ inputElement.step = step || 'any';
288
+ }
289
+ }
290
+
291
+ /**
292
+ * @param {unknown} newVal
293
+ * @param {unknown} oldVal
294
+ * @protected
295
+ * @override
296
+ */
297
+ _valueChanged(newVal, oldVal) {
298
+ // Validate value to be numeric
299
+ if (newVal && isNaN(parseFloat(newVal))) {
300
+ this.value = '';
301
+ } else if (typeof this.value !== 'string') {
302
+ this.value = String(this.value);
303
+ }
304
+
305
+ super._valueChanged(this.value, oldVal);
306
+ }
307
+
308
+ /**
309
+ * Override an event listener from `InputControlMixin`
310
+ * to avoid adding a separate listener.
311
+ * @param {!KeyboardEvent} event
312
+ * @protected
313
+ * @override
314
+ */
315
+ _onKeyDown(event) {
316
+ if (event.key === 'ArrowUp') {
317
+ event.preventDefault();
318
+ this._increaseValue();
319
+ } else if (event.key === 'ArrowDown') {
320
+ event.preventDefault();
321
+ this._decreaseValue();
322
+ }
323
+
324
+ super._onKeyDown(event);
325
+ }
326
+
327
+ /**
328
+ * Native [type=number] inputs don't update their value
329
+ * when you are entering input that the browser is unable to parse
330
+ * e.g. "--5", hence we have to override this method from `InputMixin`
331
+ * so that, when value is empty, it would additionally check
332
+ * for bad input based on the native `validity.badInput` property.
333
+ *
334
+ * @param {InputEvent} event
335
+ * @protected
336
+ * @override
337
+ */
338
+ _setHasInputValue(event) {
339
+ const target = event.composedPath()[0];
340
+ this._hasInputValue = target.value.length > 0 || target.validity.badInput;
341
+ }
342
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 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 numberFieldStyles: CSSResult;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 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 numberFieldStyles = css`
9
+ :host([readonly]) [part$='button'] {
10
+ pointer-events: none;
11
+ }
12
+
13
+ [part='decrease-button']::before {
14
+ content: '\\2212';
15
+ }
16
+
17
+ [part='increase-button']::before {
18
+ content: '+';
19
+ }
20
+
21
+ [part='decrease-button'],
22
+ [part='increase-button'] {
23
+ -webkit-user-select: none;
24
+ -moz-user-select: none;
25
+ user-select: none;
26
+ }
27
+
28
+ :host([dir='rtl']) [part='input-field'] {
29
+ direction: ltr;
30
+ }
31
+ `;
@@ -6,6 +6,7 @@
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
7
  import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js';
8
8
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
+ import { NumberFieldMixin } from './vaadin-number-field-mixin.js';
9
10
 
10
11
  /**
11
12
  * Fired when the user commits a value change.
@@ -68,28 +69,7 @@ export interface NumberFieldEventMap extends HTMLElementEventMap, NumberFieldCus
68
69
  * @fires {CustomEvent} value-changed - Fired when the `value` property changes.
69
70
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
70
71
  */
71
- declare class NumberField extends InputFieldMixin(ThemableMixin(ElementMixin(HTMLElement))) {
72
- /**
73
- * Set to true to show increase/decrease buttons.
74
- * @attr {boolean} step-buttons-visible
75
- */
76
- stepButtonsVisible: boolean;
77
-
78
- /**
79
- * The minimum value of the field.
80
- */
81
- min: number | null | undefined;
82
-
83
- /**
84
- * The maximum value of the field.
85
- */
86
- max: number | null | undefined;
87
-
88
- /**
89
- * Specifies the allowed number intervals of the field.
90
- */
91
- step: number | null | undefined;
92
-
72
+ declare class NumberField extends NumberFieldMixin(ThemableMixin(ElementMixin(HTMLElement))) {
93
73
  addEventListener<K extends keyof NumberFieldEventMap>(
94
74
  type: K,
95
75
  listener: (this: NumberField, ev: NumberFieldEventMap[K]) => void,
@@ -7,13 +7,14 @@ import '@vaadin/input-container/src/vaadin-input-container.js';
7
7
  import { html, PolymerElement } from '@polymer/polymer';
8
8
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
9
9
  import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
10
- import { InputController } from '@vaadin/field-base/src/input-controller.js';
11
- import { InputFieldMixin } from '@vaadin/field-base/src/input-field-mixin.js';
12
- import { LabelledInputController } from '@vaadin/field-base/src/labelled-input-controller.js';
13
10
  import { inputFieldShared } from '@vaadin/field-base/src/styles/input-field-shared-styles.js';
14
11
  import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
+ import { NumberFieldMixin } from './vaadin-number-field-mixin.js';
13
+ import { numberFieldStyles } from './vaadin-number-field-styles.js';
15
14
 
16
- registerStyles('vaadin-number-field', inputFieldShared, { moduleId: 'vaadin-number-field-styles' });
15
+ registerStyles('vaadin-number-field', [inputFieldShared, numberFieldStyles], {
16
+ moduleId: 'vaadin-number-field-styles',
17
+ });
17
18
 
18
19
  /**
19
20
  * `<vaadin-number-field>` is an input field web component that only accepts numeric input.
@@ -45,43 +46,17 @@ registerStyles('vaadin-number-field', inputFieldShared, { moduleId: 'vaadin-numb
45
46
  * @fires {CustomEvent} validated - Fired whenever the field is validated.
46
47
  *
47
48
  * @extends HTMLElement
48
- * @mixes InputFieldMixin
49
- * @mixes SlotStylesMixin
49
+ * @mixes NumberFieldMixin
50
50
  * @mixes ElementMixin
51
51
  * @mixes ThemableMixin
52
52
  */
53
- export class NumberField extends InputFieldMixin(ThemableMixin(ElementMixin(PolymerElement))) {
53
+ export class NumberField extends NumberFieldMixin(ThemableMixin(ElementMixin(PolymerElement))) {
54
54
  static get is() {
55
55
  return 'vaadin-number-field';
56
56
  }
57
57
 
58
58
  static get template() {
59
59
  return html`
60
- <style>
61
- :host([readonly]) [part$='button'] {
62
- pointer-events: none;
63
- }
64
-
65
- [part='decrease-button']::before {
66
- content: '−';
67
- }
68
-
69
- [part='increase-button']::before {
70
- content: '+';
71
- }
72
-
73
- [part='decrease-button'],
74
- [part='increase-button'] {
75
- -webkit-user-select: none;
76
- -moz-user-select: none;
77
- user-select: none;
78
- }
79
-
80
- :host([dir='rtl']) [part='input-field'] {
81
- direction: ltr;
82
- }
83
- </style>
84
-
85
60
  <div class="vaadin-field-container">
86
61
  <div part="label">
87
62
  <slot name="label"></slot>
@@ -96,10 +71,10 @@ export class NumberField extends InputFieldMixin(ThemableMixin(ElementMixin(Poly
96
71
  theme$="[[_theme]]"
97
72
  >
98
73
  <div
99
- disabled$="[[!_allowed(-1, value, min, max, step)]]"
74
+ disabled$="[[!_isButtonEnabled(-1, value, min, max, step)]]"
100
75
  part="decrease-button"
101
- on-click="_decreaseValue"
102
- on-touchend="_decreaseButtonTouchend"
76
+ on-click="_onDecreaseButtonClick"
77
+ on-touchend="_onDecreaseButtonTouchend"
103
78
  hidden$="[[!stepButtonsVisible]]"
104
79
  aria-hidden="true"
105
80
  slot="prefix"
@@ -109,10 +84,10 @@ export class NumberField extends InputFieldMixin(ThemableMixin(ElementMixin(Poly
109
84
  <slot name="suffix" slot="suffix"></slot>
110
85
  <div id="clearButton" part="clear-button" slot="suffix" aria-hidden="true"></div>
111
86
  <div
112
- disabled$="[[!_allowed(1, value, min, max, step)]]"
87
+ disabled$="[[!_isButtonEnabled(1, value, min, max, step)]]"
113
88
  part="increase-button"
114
- on-click="_increaseValue"
115
- on-touchend="_increaseButtonTouchend"
89
+ on-click="_onIncreaseButtonClick"
90
+ on-touchend="_onIncreaseButtonTouchend"
116
91
  hidden$="[[!stepButtonsVisible]]"
117
92
  aria-hidden="true"
118
93
  slot="suffix"
@@ -132,321 +107,14 @@ export class NumberField extends InputFieldMixin(ThemableMixin(ElementMixin(Poly
132
107
  `;
133
108
  }
134
109
 
135
- static get properties() {
136
- return {
137
- /**
138
- * Set to true to show increase/decrease buttons.
139
- * @attr {boolean} step-buttons-visible
140
- */
141
- stepButtonsVisible: {
142
- type: Boolean,
143
- value: false,
144
- reflectToAttribute: true,
145
- },
146
-
147
- /**
148
- * The minimum value of the field.
149
- */
150
- min: {
151
- type: Number,
152
- },
153
-
154
- /**
155
- * The maximum value of the field.
156
- */
157
- max: {
158
- type: Number,
159
- },
160
-
161
- /**
162
- * Specifies the allowed number intervals of the field.
163
- * @type {number}
164
- */
165
- step: {
166
- type: Number,
167
- },
168
- };
169
- }
170
-
171
- static get observers() {
172
- return ['_stepChanged(step, inputElement)'];
173
- }
174
-
175
- static get delegateProps() {
176
- return [...super.delegateProps, 'min', 'max'];
177
- }
178
-
179
- static get constraints() {
180
- return [...super.constraints, 'min', 'max', 'step'];
181
- }
182
-
183
- constructor() {
184
- super();
185
- this._setType('number');
186
- }
187
-
188
- /** @protected */
189
- get slotStyles() {
190
- const tag = this.localName;
191
- return [
192
- ...super.slotStyles,
193
- `
194
- ${tag} input[type="number"]::-webkit-outer-spin-button,
195
- ${tag} input[type="number"]::-webkit-inner-spin-button {
196
- -webkit-appearance: none;
197
- margin: 0;
198
- }
199
-
200
- ${tag} input[type="number"] {
201
- -moz-appearance: textfield;
202
- }
203
-
204
- ${tag}[dir='rtl'] input[type="number"]::placeholder {
205
- direction: rtl;
206
- }
207
-
208
- ${tag}[dir='rtl']:not([step-buttons-visible]) input[type="number"]::placeholder {
209
- text-align: left;
210
- }
211
- `,
212
- ];
213
- }
214
-
215
- /**
216
- * Used by `InputControlMixin` as a reference to the clear button element.
217
- * @protected
218
- */
219
- get clearElement() {
220
- return this.$.clearButton;
221
- }
222
-
223
110
  /** @protected */
224
111
  ready() {
225
112
  super.ready();
226
113
 
227
- this.addController(
228
- new InputController(this, (input) => {
229
- this._setInputElement(input);
230
- this._setFocusElement(input);
231
- this.stateTarget = input;
232
- this.ariaTarget = input;
233
- }),
234
- );
235
-
236
- this.addController(new LabelledInputController(this.inputElement, this._labelController));
237
-
238
114
  this._tooltipController = new TooltipController(this);
239
115
  this.addController(this._tooltipController);
240
116
  this._tooltipController.setPosition('top');
241
117
  }
242
-
243
- /**
244
- * Override the method from `InputConstraintsMixin`
245
- * to enforce HTML constraint validation even if
246
- * the user didn't add any constraints explicitly:
247
- * the field has to be regardless checked for bad input.
248
- *
249
- * @override
250
- */
251
- checkValidity() {
252
- if (this.inputElement) {
253
- return this.inputElement.checkValidity();
254
- }
255
-
256
- return !this.invalid;
257
- }
258
-
259
- /** @private */
260
- _decreaseButtonTouchend(e) {
261
- // Cancel the following click and focus events
262
- e.preventDefault();
263
- this._decreaseValue();
264
- }
265
-
266
- /** @private */
267
- _increaseButtonTouchend(e) {
268
- // Cancel the following click and focus events
269
- e.preventDefault();
270
- this._increaseValue();
271
- }
272
-
273
- /** @private */
274
- _decreaseValue() {
275
- this._incrementValue(-1);
276
- }
277
-
278
- /** @private */
279
- _increaseValue() {
280
- this._incrementValue(1);
281
- }
282
-
283
- /** @private */
284
- _incrementValue(incr) {
285
- if (this.disabled || this.readonly) {
286
- return;
287
- }
288
-
289
- const step = this.step || 1;
290
- let value = parseFloat(this.value);
291
-
292
- if (!this.value) {
293
- if ((this.min === 0 && incr < 0) || (this.max === 0 && incr > 0) || (this.max === 0 && this.min === 0)) {
294
- incr = 0;
295
- value = 0;
296
- } else if ((this.max == null || this.max >= 0) && (this.min == null || this.min <= 0)) {
297
- value = 0;
298
- } else if (this.min > 0) {
299
- value = this.min;
300
- if (this.max < 0 && incr < 0) {
301
- value = this.max;
302
- }
303
- incr = 0;
304
- } else if (this.max < 0) {
305
- value = this.max;
306
- if (incr < 0) {
307
- incr = 0;
308
- } else if (this._getIncrement(1, value - step) > this.max) {
309
- value -= 2 * step;
310
- // FIXME(yuriy): find a proper solution to make correct step back
311
- } else {
312
- value -= step;
313
- }
314
- }
315
- } else if (value < this.min) {
316
- incr = 0;
317
- value = this.min;
318
- } else if (value > this.max) {
319
- incr = 0;
320
- value = this.max;
321
- }
322
-
323
- const newValue = this._getIncrement(incr, value);
324
- if (!this.value || incr === 0 || this._incrementIsInsideTheLimits(incr, value)) {
325
- this._setValue(newValue);
326
- }
327
- }
328
-
329
- /** @private */
330
- _setValue(value) {
331
- this.value = this.inputElement.value = String(parseFloat(value));
332
- this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
333
- }
334
-
335
- /** @private */
336
- _getIncrement(incr, currentValue) {
337
- let step = this.step || 1,
338
- min = this.min || 0;
339
-
340
- // To avoid problems with decimal math, multiplying to operate with integers.
341
- const multiplier = Math.max(this._getMultiplier(currentValue), this._getMultiplier(step), this._getMultiplier(min));
342
-
343
- step *= multiplier;
344
- currentValue = Math.round(currentValue * multiplier);
345
- min *= multiplier;
346
-
347
- const margin = (currentValue - min) % step;
348
-
349
- if (incr > 0) {
350
- return (currentValue - margin + step) / multiplier;
351
- } else if (incr < 0) {
352
- return (currentValue - (margin || step)) / multiplier;
353
- }
354
- return currentValue / multiplier;
355
- }
356
-
357
- /** @private */
358
- _getDecimalCount(number) {
359
- const s = String(number);
360
- const i = s.indexOf('.');
361
- return i === -1 ? 1 : s.length - i - 1;
362
- }
363
-
364
- /** @private */
365
- _getMultiplier(number) {
366
- if (!isNaN(number)) {
367
- return 10 ** this._getDecimalCount(number);
368
- }
369
- }
370
-
371
- /** @private */
372
- _incrementIsInsideTheLimits(incr, value) {
373
- if (incr < 0) {
374
- return this.min == null || this._getIncrement(incr, value) >= this.min;
375
- } else if (incr > 0) {
376
- return this.max == null || this._getIncrement(incr, value) <= this.max;
377
- }
378
- return this._getIncrement(incr, value) <= this.max && this._getIncrement(incr, value) >= this.min;
379
- }
380
-
381
- /** @private */
382
- _allowed(sign) {
383
- const incr = sign * (this.step || 1);
384
- const value = parseFloat(this.value);
385
- return !this.value || (!this.disabled && this._incrementIsInsideTheLimits(incr, value));
386
- }
387
-
388
- /**
389
- * @param {number} step
390
- * @param {HTMLElement | undefined} inputElement
391
- * @protected
392
- */
393
- _stepChanged(step, inputElement) {
394
- if (inputElement) {
395
- inputElement.step = step || 'any';
396
- }
397
- }
398
-
399
- /**
400
- * @param {unknown} newVal
401
- * @param {unknown} oldVal
402
- * @protected
403
- * @override
404
- */
405
- _valueChanged(newVal, oldVal) {
406
- // Validate value to be numeric
407
- if (newVal && isNaN(parseFloat(newVal))) {
408
- this.value = '';
409
- } else if (typeof this.value !== 'string') {
410
- this.value = String(this.value);
411
- }
412
-
413
- super._valueChanged(this.value, oldVal);
414
- }
415
-
416
- /**
417
- * Override an event listener from `InputControlMixin`
418
- * to avoid adding a separate listener.
419
- * @param {!KeyboardEvent} event
420
- * @protected
421
- * @override
422
- */
423
- _onKeyDown(event) {
424
- if (event.key === 'ArrowUp') {
425
- event.preventDefault();
426
- this._increaseValue();
427
- } else if (event.key === 'ArrowDown') {
428
- event.preventDefault();
429
- this._decreaseValue();
430
- }
431
-
432
- super._onKeyDown(event);
433
- }
434
-
435
- /**
436
- * Native [type=number] inputs don't update their value
437
- * when you are entering input that the browser is unable to parse
438
- * e.g. "--5", hence we have to override this method from `InputMixin`
439
- * so that, when value is empty, it would additionally check
440
- * for bad input based on the native `validity.badInput` property.
441
- *
442
- * @param {InputEvent} event
443
- * @protected
444
- * @override
445
- */
446
- _setHasInputValue(event) {
447
- const target = event.composedPath()[0];
448
- this._hasInputValue = target.value.length > 0 || target.validity.badInput;
449
- }
450
118
  }
451
119
 
452
120
  customElements.define(NumberField.is, NumberField);
@@ -9,10 +9,6 @@ import { inputFieldShared } from '@vaadin/vaadin-lumo-styles/mixins/input-field-
9
9
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
10
10
 
11
11
  const numberField = css`
12
- :host {
13
- width: 8em;
14
- }
15
-
16
12
  :host([step-buttons-visible]:not([theme~='align-right'])) ::slotted(input) {
17
13
  text-align: center;
18
14
  }
@@ -8,10 +8,6 @@ import { inputFieldShared } from '@vaadin/vaadin-material-styles/mixins/input-fi
8
8
  import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
9
9
 
10
10
  const numberField = css`
11
- :host {
12
- width: 8em;
13
- }
14
-
15
11
  :host([step-buttons-visible]) ::slotted(input) {
16
12
  text-align: center;
17
13
  }
package/web-types.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/number-field",
4
- "version": "24.0.0-alpha9",
4
+ "version": "24.0.0-beta2",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
8
8
  "elements": [
9
9
  {
10
10
  "name": "vaadin-number-field",
11
- "description": "`<vaadin-number-field>` is an input field web component that only accepts numeric input.\n\n```html\n<vaadin-number-field label=\"Balance\"></vaadin-number-field>\n```\n\n### Styling\n\n`<vaadin-number-field>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.\nSee [`<vaadin-text-field>`](https://cdn.vaadin.com/vaadin-web-components/24.0.0-alpha9/#/elements/vaadin-text-field) for the styling documentation.\n\nIn addition to `<vaadin-text-field>` parts, the following parts are available for theming:\n\nPart name | Description\n------------------|-------------------------\n`increase-button` | Increase (\"plus\") button\n`decrease-button` | Decrease (\"minus\") button\n\nNote, the `input-prevented` state attribute is only supported when `allowedCharPattern` is set.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.",
11
+ "description": "`<vaadin-number-field>` is an input field web component that only accepts numeric input.\n\n```html\n<vaadin-number-field label=\"Balance\"></vaadin-number-field>\n```\n\n### Styling\n\n`<vaadin-number-field>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.\nSee [`<vaadin-text-field>`](https://cdn.vaadin.com/vaadin-web-components/24.0.0-beta2/#/elements/vaadin-text-field) for the styling documentation.\n\nIn addition to `<vaadin-text-field>` parts, the following parts are available for theming:\n\nPart name | Description\n------------------|-------------------------\n`increase-button` | Increase (\"plus\") button\n`decrease-button` | Decrease (\"minus\") button\n\nNote, the `input-prevented` state attribute is only supported when `allowedCharPattern` is set.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.",
12
12
  "attributes": [
13
13
  {
14
14
  "name": "disabled",
@@ -99,30 +99,30 @@
99
99
  }
100
100
  },
101
101
  {
102
- "name": "allowed-char-pattern",
103
- "description": "A pattern matched against individual characters the user inputs.\n\nWhen set, the field will prevent:\n- `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`\n- `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`\n- `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`\n\nFor example, to allow entering only numbers and minus signs, use:\n`allowedCharPattern = \"[\\\\d-]\"`",
102
+ "name": "clear-button-visible",
103
+ "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
104
104
  "value": {
105
105
  "type": [
106
- "string",
106
+ "boolean",
107
107
  "null",
108
108
  "undefined"
109
109
  ]
110
110
  }
111
111
  },
112
112
  {
113
- "name": "autoselect",
114
- "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
113
+ "name": "allowed-char-pattern",
114
+ "description": "A pattern matched against individual characters the user inputs.\n\nWhen set, the field will prevent:\n- `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`\n- `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`\n- `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`\n\nFor example, to allow entering only numbers and minus signs, use:\n`allowedCharPattern = \"[\\\\d-]\"`",
115
115
  "value": {
116
116
  "type": [
117
- "boolean",
117
+ "string",
118
118
  "null",
119
119
  "undefined"
120
120
  ]
121
121
  }
122
122
  },
123
123
  {
124
- "name": "clear-button-visible",
125
- "description": "Set to true to display the clear icon which clears the input.",
124
+ "name": "autoselect",
125
+ "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
126
126
  "value": {
127
127
  "type": [
128
128
  "boolean",
@@ -208,17 +208,6 @@
208
208
  ]
209
209
  }
210
210
  },
211
- {
212
- "name": "step-buttons-visible",
213
- "description": "Set to true to show increase/decrease buttons.",
214
- "value": {
215
- "type": [
216
- "boolean",
217
- "null",
218
- "undefined"
219
- ]
220
- }
221
- },
222
211
  {
223
212
  "name": "min",
224
213
  "description": "The minimum value of the field.",
@@ -250,6 +239,17 @@
250
239
  ]
251
240
  }
252
241
  },
242
+ {
243
+ "name": "step-buttons-visible",
244
+ "description": "Set to true to show increase/decrease buttons.",
245
+ "value": {
246
+ "type": [
247
+ "boolean",
248
+ "null",
249
+ "undefined"
250
+ ]
251
+ }
252
+ },
253
253
  {
254
254
  "name": "theme",
255
255
  "description": "The theme variants to apply to the component.",
@@ -353,30 +353,30 @@
353
353
  }
354
354
  },
355
355
  {
356
- "name": "allowedCharPattern",
357
- "description": "A pattern matched against individual characters the user inputs.\n\nWhen set, the field will prevent:\n- `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`\n- `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`\n- `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`\n\nFor example, to allow entering only numbers and minus signs, use:\n`allowedCharPattern = \"[\\\\d-]\"`",
356
+ "name": "clearButtonVisible",
357
+ "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
358
358
  "value": {
359
359
  "type": [
360
- "string",
360
+ "boolean",
361
361
  "null",
362
362
  "undefined"
363
363
  ]
364
364
  }
365
365
  },
366
366
  {
367
- "name": "autoselect",
368
- "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
367
+ "name": "allowedCharPattern",
368
+ "description": "A pattern matched against individual characters the user inputs.\n\nWhen set, the field will prevent:\n- `keydown` events if the entered key doesn't match `/^allowedCharPattern$/`\n- `paste` events if the pasted text doesn't match `/^allowedCharPattern*$/`\n- `drop` events if the dropped text doesn't match `/^allowedCharPattern*$/`\n\nFor example, to allow entering only numbers and minus signs, use:\n`allowedCharPattern = \"[\\\\d-]\"`",
369
369
  "value": {
370
370
  "type": [
371
- "boolean",
371
+ "string",
372
372
  "null",
373
373
  "undefined"
374
374
  ]
375
375
  }
376
376
  },
377
377
  {
378
- "name": "clearButtonVisible",
379
- "description": "Set to true to display the clear icon which clears the input.",
378
+ "name": "autoselect",
379
+ "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
380
380
  "value": {
381
381
  "type": [
382
382
  "boolean",
@@ -462,17 +462,6 @@
462
462
  ]
463
463
  }
464
464
  },
465
- {
466
- "name": "stepButtonsVisible",
467
- "description": "Set to true to show increase/decrease buttons.",
468
- "value": {
469
- "type": [
470
- "boolean",
471
- "null",
472
- "undefined"
473
- ]
474
- }
475
- },
476
465
  {
477
466
  "name": "min",
478
467
  "description": "The minimum value of the field.",
@@ -503,6 +492,17 @@
503
492
  "number"
504
493
  ]
505
494
  }
495
+ },
496
+ {
497
+ "name": "stepButtonsVisible",
498
+ "description": "Set to true to show increase/decrease buttons.",
499
+ "value": {
500
+ "type": [
501
+ "boolean",
502
+ "null",
503
+ "undefined"
504
+ ]
505
+ }
506
506
  }
507
507
  ],
508
508
  "events": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/number-field",
4
- "version": "24.0.0-alpha9",
4
+ "version": "24.0.0-beta2",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {
@@ -16,7 +16,7 @@
16
16
  "elements": [
17
17
  {
18
18
  "name": "vaadin-number-field",
19
- "description": "`<vaadin-number-field>` is an input field web component that only accepts numeric input.\n\n```html\n<vaadin-number-field label=\"Balance\"></vaadin-number-field>\n```\n\n### Styling\n\n`<vaadin-number-field>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.\nSee [`<vaadin-text-field>`](https://cdn.vaadin.com/vaadin-web-components/24.0.0-alpha9/#/elements/vaadin-text-field) for the styling documentation.\n\nIn addition to `<vaadin-text-field>` parts, the following parts are available for theming:\n\nPart name | Description\n------------------|-------------------------\n`increase-button` | Increase (\"plus\") button\n`decrease-button` | Decrease (\"minus\") button\n\nNote, the `input-prevented` state attribute is only supported when `allowedCharPattern` is set.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.",
19
+ "description": "`<vaadin-number-field>` is an input field web component that only accepts numeric input.\n\n```html\n<vaadin-number-field label=\"Balance\"></vaadin-number-field>\n```\n\n### Styling\n\n`<vaadin-number-field>` provides the same set of shadow DOM parts and state attributes as `<vaadin-text-field>`.\nSee [`<vaadin-text-field>`](https://cdn.vaadin.com/vaadin-web-components/24.0.0-beta2/#/elements/vaadin-text-field) for the styling documentation.\n\nIn addition to `<vaadin-text-field>` parts, the following parts are available for theming:\n\nPart name | Description\n------------------|-------------------------\n`increase-button` | Increase (\"plus\") button\n`decrease-button` | Decrease (\"minus\") button\n\nNote, the `input-prevented` state attribute is only supported when `allowedCharPattern` is set.\n\nSee [Styling Components](https://vaadin.com/docs/latest/styling/custom-theme/styling-components) documentation.",
20
20
  "extension": true,
21
21
  "attributes": [
22
22
  {
@@ -48,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  {
51
- "name": "?autoselect",
52
- "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
51
+ "name": "?clearButtonVisible",
52
+ "description": "Set to true to display the clear icon which clears the input.\n\nIt is up to the component to choose where to place the clear icon:\nin the Shadow DOM or in the light DOM. In any way, a reference to\nthe clear icon element should be provided via the `clearElement` getter.",
53
53
  "value": {
54
54
  "kind": "expression"
55
55
  }
56
56
  },
57
57
  {
58
- "name": "?clearButtonVisible",
59
- "description": "Set to true to display the clear icon which clears the input.",
58
+ "name": "?autoselect",
59
+ "description": "If true, the input text gets fully selected when the field is focused using click or touch / tap.",
60
60
  "value": {
61
61
  "kind": "expression"
62
62
  }