@dile/ui 3.7.0 → 3.7.2

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.
@@ -33,7 +33,7 @@ describe('dile-input-number', () => {
33
33
  input.dispatchEvent(new Event('input'));
34
34
  await el.updateComplete;
35
35
 
36
- expect(el.value).toBe('12,75');
36
+ expect(el.value).toBe('12.75');
37
37
  expect(input.value).toBe('12,75');
38
38
  });
39
39
 
@@ -57,7 +57,7 @@ describe('dile-input-number', () => {
57
57
  input.dispatchEvent(new Event('blur'));
58
58
  await el.updateComplete;
59
59
 
60
- expect(el.value).toBe('1,25');
60
+ expect(el.value).toBe('1.25');
61
61
  expect(input.value).toBe('1,25');
62
62
  });
63
63
 
@@ -65,6 +65,7 @@ describe('dile-input-number', () => {
65
65
  const el = await renderInputNumber('<dile-input-number name="amount" decimalSeparator="," normalizeOn="blur"></dile-input-number>');
66
66
  const input = el.shadowRoot.querySelector('input');
67
67
 
68
+ input.dispatchEvent(new Event('focus'));
68
69
  input.value = '12.75';
69
70
  input.dispatchEvent(new Event('input'));
70
71
  await el.updateComplete;
@@ -75,7 +76,7 @@ describe('dile-input-number', () => {
75
76
  input.dispatchEvent(new Event('blur'));
76
77
  await el.updateComplete;
77
78
 
78
- expect(el.value).toBe('12,75');
79
+ expect(el.value).toBe('12.75');
79
80
  expect(input.value).toBe('12,75');
80
81
  });
81
82
 
@@ -90,7 +91,19 @@ describe('dile-input-number', () => {
90
91
  input.dispatchEvent(new Event('input'));
91
92
  await el.updateComplete;
92
93
 
93
- expect(el.value).toBe('12,75');
94
+ expect(el.value).toBe('12.75');
94
95
  expect(input.value).toBe('12,75');
95
96
  });
97
+
98
+ it('keeps canonical value when normalizeOn fallback applies', async () => {
99
+ const el = await renderInputNumber('<dile-input-number name="amount" decimalSeparator="," normalizeOn="foo"></dile-input-number>');
100
+ const input = el.shadowRoot.querySelector('input');
101
+
102
+ input.value = '8,5';
103
+ input.dispatchEvent(new Event('input'));
104
+ await el.updateComplete;
105
+
106
+ expect(el.value).toBe('8.5');
107
+ expect(input.value).toBe('8,5');
108
+ });
96
109
  });
@@ -37,6 +37,8 @@ export class DileInputNumber extends DileInput {
37
37
  this.decimals = null;
38
38
  this.normalizeOn = 'input';
39
39
  this.changed = false;
40
+ this.viewValueOnFocus = null;
41
+ this.isInputFocused = false;
40
42
  }
41
43
 
42
44
  firstUpdated() {
@@ -88,14 +90,21 @@ export class DileInputNumber extends DileInput {
88
90
  return sanitized;
89
91
  }
90
92
 
93
+ normalizeToCanonical(value) {
94
+ return this.sanitizeValue(value, { normalizeSeparator: false }).replace(',', '.');
95
+ }
96
+
97
+ sanitizeForView(value) {
98
+ return this.sanitizeValue(value, { normalizeSeparator: false });
99
+ }
100
+
91
101
  normalizeForForm(value) {
92
102
  if (!value) {
93
103
  return '';
94
104
  }
95
105
  return value
96
106
  .toString()
97
- .replace(this.safeDecimalSeparator, '.')
98
- .replace(this.alternateDecimalSeparator, '.');
107
+ .replace(',', '.');
99
108
  }
100
109
 
101
110
  get normalizeOnBlur() {
@@ -160,13 +169,13 @@ export class DileInputNumber extends DileInput {
160
169
 
161
170
  formatNumber(number) {
162
171
  if (this.hasDecimalsConstraint()) {
163
- return number.toFixed(this.decimals).replace('.', this.safeDecimalSeparator);
172
+ return number.toFixed(this.decimals);
164
173
  }
165
- return number.toString().replace('.', this.safeDecimalSeparator);
174
+ return number.toString();
166
175
  }
167
176
 
168
177
  normalizeAndConstrain(value) {
169
- const sanitized = this.sanitizeValue(value);
178
+ const sanitized = this.normalizeToCanonical(value);
170
179
  const parsed = this.toNumber(sanitized);
171
180
 
172
181
  if (isNaN(parsed)) {
@@ -211,13 +220,20 @@ export class DileInputNumber extends DileInput {
211
220
  }
212
221
 
213
222
  _input(e) {
214
- const sanitized = this.sanitizeValue(e.target.value, {
215
- normalizeSeparator: !this.normalizeOnBlur,
216
- });
217
- if (sanitized !== e.target.value) {
218
- e.target.value = sanitized;
223
+ const sanitizedForView = this.sanitizeForView(e.target.value);
224
+ if (sanitizedForView !== e.target.value) {
225
+ e.target.value = sanitizedForView;
226
+ }
227
+ this.viewValueOnFocus = sanitizedForView;
228
+ this.value = this.normalizeToCanonical(sanitizedForView);
229
+
230
+ if (!this.normalizeOnBlur) {
231
+ const formatted = this.computeValue(this.value);
232
+ if (e.target.value !== formatted) {
233
+ e.target.value = formatted;
234
+ this.viewValueOnFocus = formatted;
235
+ }
219
236
  }
220
- this.value = sanitized;
221
237
 
222
238
  if (this.hideErrorOnInput && this.errored) {
223
239
  this.clearError();
@@ -225,6 +241,8 @@ export class DileInputNumber extends DileInput {
225
241
  }
226
242
 
227
243
  doBlur(e) {
244
+ this.isInputFocused = false;
245
+ this.viewValueOnFocus = null;
228
246
  const num = this.normalizeAndConstrain(e.target.value);
229
247
 
230
248
  if (num !== e.target.value || this.changed) {
@@ -232,6 +250,15 @@ export class DileInputNumber extends DileInput {
232
250
  this.emmitChange();
233
251
  this.changed = false;
234
252
  }
253
+
254
+ if (this.normalizeOnBlur) {
255
+ this.el.value = this.computeValue(this.value);
256
+ }
257
+ }
258
+
259
+ doFocus() {
260
+ this.isInputFocused = true;
261
+ super.doFocus();
235
262
  }
236
263
 
237
264
  updated(changedProperties) {
@@ -242,14 +269,6 @@ export class DileInputNumber extends DileInput {
242
269
  return;
243
270
  }
244
271
 
245
- if (changedProperties.has('decimalSeparator') && this.value) {
246
- const sanitized = this.sanitizeValue(this.value);
247
- if (sanitized !== this.value) {
248
- this.value = sanitized;
249
- return;
250
- }
251
- }
252
-
253
272
  if (changedProperties.has('value') && this.value) {
254
273
  this.changed = true;
255
274
  }
@@ -275,8 +294,10 @@ export class DileInputNumber extends DileInput {
275
294
  }
276
295
 
277
296
  computeValue(value) {
278
- return this.sanitizeValue(value, {
279
- normalizeSeparator: !this.normalizeOnBlur,
280
- });
297
+ if (this.normalizeOnBlur && this.isInputFocused && this.viewValueOnFocus !== null) {
298
+ return this.viewValueOnFocus;
299
+ }
300
+ const canonical = this.normalizeToCanonical(value);
301
+ return canonical.replace('.', this.safeDecimalSeparator);
281
302
  }
282
303
  }
@@ -20,11 +20,11 @@ export const DileForm = (superclass) => class extends superclass {
20
20
  };
21
21
  }
22
22
 
23
- firstUpdated() {
23
+ async firstUpdated() {
24
24
  super.firstUpdated();
25
25
  this.firstValue = this.getData();
26
26
  if(this.setOnInit) {
27
- this.setData(this.setOnInit);
27
+ await this.setData(this.setOnInit);
28
28
  }
29
29
  }
30
30
 
@@ -32,6 +32,7 @@ export const DileForm = (superclass) => class extends superclass {
32
32
  let data = {};
33
33
  this.allNodeElements.forEach(node => {
34
34
  const name = node.getAttribute('name');
35
+ if (!name) return;
35
36
  const isArray = name.endsWith('[]');
36
37
  const fieldName = isArray ? name.slice(0, -2) : name;
37
38
 
@@ -47,16 +48,18 @@ export const DileForm = (superclass) => class extends superclass {
47
48
  return this.adaptDataOut(data);
48
49
  }
49
50
 
50
- setData(data) {
51
+ async setData(data) {
52
+ await this.updateComplete;
51
53
  const adaptedData = this.adaptDataIn(data);
52
- this.allNodeElements.forEach(node => {
54
+ const allNodeElements = this.allNodeElements;
55
+ allNodeElements.forEach(node => {
53
56
  const name = node.getAttribute('name');
57
+ if (!name) return;
54
58
  const isArray = name.endsWith('[]');
55
59
  const fieldName = isArray ? name.slice(0, -2) : name;
56
60
 
57
61
  if (isArray && Array.isArray(adaptedData[fieldName])) {
58
- // with arrays, the index is the element order in the DOM
59
- const arrayElements = Array.from(this.allNodeElements).filter(el => el.getAttribute('name') === name);
62
+ const arrayElements = Array.from(allNodeElements).filter(el => el.getAttribute('name') === name);
60
63
  const elementIndex = arrayElements.indexOf(node);
61
64
  if (elementIndex >= 0 && elementIndex < adaptedData[fieldName].length) {
62
65
  const value = adaptedData[fieldName][elementIndex];
@@ -104,7 +107,7 @@ export const DileForm = (superclass) => class extends superclass {
104
107
  }
105
108
 
106
109
  resetData() {
107
- this.setData(this.firstValue);
110
+ return this.setData(this.firstValue);
108
111
  }
109
112
 
110
113
  resetField(name) {
@@ -189,7 +192,7 @@ export const DileForm = (superclass) => class extends superclass {
189
192
 
190
193
  isDirty() {
191
194
  const currentData = this.getData();
192
- const originProperties = Object.keys(this.firstValue);
195
+ const originProperties = Object.keys(this.firstValue || {});
193
196
  const currentProperties = Object.keys(currentData);
194
197
  if (originProperties.length !== currentProperties.length) {
195
198
  return true;
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { LitElement, html } from 'lit';
3
+ import { DileForm } from './DileForm.js';
4
+
5
+ class RaceTestForm extends DileForm(LitElement) {
6
+ render() {
7
+ return html`<input name="name" />`;
8
+ }
9
+ }
10
+
11
+ if (!customElements.get('race-test-form')) {
12
+ customElements.define('race-test-form', RaceTestForm);
13
+ }
14
+
15
+ describe('DileForm', () => {
16
+ afterEach(() => {
17
+ document.body.innerHTML = '';
18
+ });
19
+
20
+ it('applies setData after the first render is ready even when called immediately', async () => {
21
+ const form = document.createElement('race-test-form');
22
+ document.body.appendChild(form);
23
+
24
+ form.setData({ name: 'Alice' });
25
+ await form.updateComplete;
26
+
27
+ expect(form.shadowRoot.querySelector('input').value).toBe('Alice');
28
+ });
29
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.7.0",
3
+ "version": "3.7.2",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,5 +28,5 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "62016dd8f5a9bd165131bc2058be322eb3aea339"
31
+ "gitHead": "47effc45eac082504ecaa3554c8af5a8a1055a38"
32
32
  }