@dile/ui 3.8.2 → 3.9.0
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/components/button/button.spec.js +52 -0
- package/components/button/src/DileButton.js +100 -9
- package/components/button/src/DileButtonIcon.js +17 -15
- package/components/card/src/DileCard.js +3 -1
- package/components/input/__screenshots__/input-mask.spec.js/dile-input-mask-applies-lazy-separators-only-when-next-block-is-filled-1.png +0 -0
- package/components/input/input-mask.spec.js +55 -1
- package/components/input/src/DileInputMask.js +7 -1
- package/components/input/src/MaskPattern.js +13 -3
- package/components/tabs/src/DileTab.js +4 -4
- package/package.json +2 -2
|
@@ -46,6 +46,58 @@ describe('dile-button', () => {
|
|
|
46
46
|
expect(el.shadowRoot.querySelector('dile-spinner-icon')).toBeTruthy();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('renders the loading spinner after the label (far right)', async () => {
|
|
50
|
+
const el = await renderButton('<dile-button loading>Save</dile-button>');
|
|
51
|
+
const children = [...el.shadowRoot.querySelector('button').children].map(n => n.tagName.toLowerCase());
|
|
52
|
+
expect(children.indexOf('dile-spinner-icon')).toBeGreaterThan(children.indexOf('slot'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('icon support', () => {
|
|
56
|
+
it('does not render an icon element when no icon is provided (backwards compatible)', async () => {
|
|
57
|
+
const el = await renderButton('<dile-button>Plain</dile-button>');
|
|
58
|
+
expect(el.shadowRoot.querySelector('dile-iconlib')).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders a dile-iconlib icon from a "family.name" string', async () => {
|
|
62
|
+
const el = await renderButton('<dile-button icon="lucide.rocket">Launch</dile-button>');
|
|
63
|
+
const icon = el.shadowRoot.querySelector('dile-iconlib');
|
|
64
|
+
expect(icon).toBeTruthy();
|
|
65
|
+
expect(icon.getAttribute('icon')).toBe('lucide.rocket');
|
|
66
|
+
expect(icon.getAttribute('aria-hidden')).toBe('true');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('places the icon before the label by default and after it when iconPosition is "right"', async () => {
|
|
70
|
+
const el = await renderButton('<dile-button icon="lucide.rocket">Launch</dile-button>');
|
|
71
|
+
let children = [...el.shadowRoot.querySelector('button').children].map(n => n.tagName.toLowerCase());
|
|
72
|
+
expect(children.indexOf('dile-iconlib')).toBeLessThan(children.indexOf('slot'));
|
|
73
|
+
|
|
74
|
+
el.iconPosition = 'right';
|
|
75
|
+
await el.updateComplete;
|
|
76
|
+
children = [...el.shadowRoot.querySelector('button').children].map(n => n.tagName.toLowerCase());
|
|
77
|
+
expect(children.indexOf('dile-iconlib')).toBeGreaterThan(children.indexOf('slot'));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('reads the icon position from the "icon-position" attribute', async () => {
|
|
81
|
+
const el = await renderButton('<dile-button icon="lucide.rocket" icon-position="right">Continue</dile-button>');
|
|
82
|
+
expect(el.iconPosition).toBe('right');
|
|
83
|
+
const children = [...el.shadowRoot.querySelector('button').children].map(n => n.tagName.toLowerCase());
|
|
84
|
+
expect(children.indexOf('dile-iconlib')).toBeGreaterThan(children.indexOf('slot'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('marks the button as having text only when the slot has content', async () => {
|
|
88
|
+
const withText = await renderButton('<dile-button icon="lucide.rocket">Launch</dile-button>');
|
|
89
|
+
expect(withText.shadowRoot.querySelector('button').classList.contains('has-text')).toBe(true);
|
|
90
|
+
|
|
91
|
+
const iconOnly = await renderButton('<dile-button icon="lucide.rocket" label="Launch"></dile-button>');
|
|
92
|
+
expect(iconOnly.shadowRoot.querySelector('button').classList.contains('has-text')).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('exposes the label as the accessible name of the inner button', async () => {
|
|
96
|
+
const el = await renderButton('<dile-button icon="lucide.rocket" label="Launch rocket"></dile-button>');
|
|
97
|
+
expect(el.shadowRoot.querySelector('button').getAttribute('aria-label')).toBe('Launch rocket');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
49
101
|
describe('Native form submission', () => {
|
|
50
102
|
it('includes name/value in the FormData when clicked', async () => {
|
|
51
103
|
document.body.innerHTML = `
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { html, css, LitElement } from "lit";
|
|
2
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
3
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
2
4
|
import '../../spinner/spinner-icon.js';
|
|
5
|
+
import '@dile/iconlib/dile-iconlib.js';
|
|
3
6
|
|
|
4
7
|
export class DileButton extends LitElement {
|
|
5
8
|
|
|
@@ -12,6 +15,19 @@ export class DileButton extends LitElement {
|
|
|
12
15
|
name: { type: String, reflect: true },
|
|
13
16
|
type: { type: String },
|
|
14
17
|
value: { type: String },
|
|
18
|
+
/**
|
|
19
|
+
* Icon to display, in the dile-iconlib `"family.name"` format
|
|
20
|
+
* (e.g. `"lucide.rocket"`, `"material.home"`). When empty, the button
|
|
21
|
+
* behaves exactly as a plain text button (backwards compatible).
|
|
22
|
+
*/
|
|
23
|
+
icon: { type: String },
|
|
24
|
+
/** Icon placement relative to the label: `"left"` (default) or `"right"`. */
|
|
25
|
+
iconPosition: { type: String, reflect: true, attribute: 'icon-position' },
|
|
26
|
+
/** Prevent the label from wrapping into several lines. */
|
|
27
|
+
noWrap: { type: Boolean, reflect: true, attribute: 'no-wrap' },
|
|
28
|
+
/** Accessible name for the button. Required when the button only shows an icon. */
|
|
29
|
+
label: { type: String },
|
|
30
|
+
_hasText: { type: Boolean, state: true },
|
|
15
31
|
};
|
|
16
32
|
}
|
|
17
33
|
|
|
@@ -20,9 +36,21 @@ export class DileButton extends LitElement {
|
|
|
20
36
|
this.disabled = false;
|
|
21
37
|
this.loading = false;
|
|
22
38
|
this.type = "button";
|
|
39
|
+
this.icon = "";
|
|
40
|
+
this.iconPosition = "left";
|
|
41
|
+
this.noWrap = false;
|
|
42
|
+
this.label = "";
|
|
43
|
+
this._hasText = true;
|
|
23
44
|
this._internals = this.attachInternals();
|
|
24
45
|
}
|
|
25
46
|
|
|
47
|
+
connectedCallback() {
|
|
48
|
+
super.connectedCallback();
|
|
49
|
+
// Seed the layout state before the first slotchange so an icon-only button
|
|
50
|
+
// does not flash the text-spacing margin on first paint.
|
|
51
|
+
this._hasText = this._detectText();
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
static get styles() {
|
|
27
55
|
return css`
|
|
28
56
|
:host {
|
|
@@ -31,6 +59,9 @@ export class DileButton extends LitElement {
|
|
|
31
59
|
}
|
|
32
60
|
button {
|
|
33
61
|
cursor: pointer;
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
34
65
|
padding-top: var(--dile-button-padding-y, 0.5rem);
|
|
35
66
|
padding-bottom: var(--dile-button-padding-y, 0.5rem);
|
|
36
67
|
padding-right: var(--dile-button-padding-x, 0.8rem);
|
|
@@ -43,18 +74,20 @@ export class DileButton extends LitElement {
|
|
|
43
74
|
transition-duration: 0.3s;
|
|
44
75
|
transition-timing-function: ease-in-out;
|
|
45
76
|
border-style: solid;
|
|
46
|
-
color: var(--dile-button-text-color, var(--dile-on-primary-color, #fff));
|
|
77
|
+
color: var(--dile-button-text-color, var(--dile-on-primary-color, #fff));
|
|
47
78
|
font-size: var(--dile-button-font-size, 1rem);
|
|
48
79
|
font-weight: var(--dile-button-font-weight, bold);
|
|
49
80
|
text-transform: var(--dile-button-text-transform, none);
|
|
50
81
|
letter-spacing: var(--dile-button-letter-spacing, 0);
|
|
51
82
|
user-select: none;
|
|
52
83
|
box-shadow: var(--dile-button-box-shadow, none);
|
|
84
|
+
--dile-icon-color: var(--dile-button-icon-color, var(--dile-button-text-color, var(--dile-on-primary-color, #fff)));
|
|
53
85
|
}
|
|
54
86
|
button:hover {
|
|
55
87
|
background-color: var(--dile-button-hover-background-color, var(--dile-button-background-color, var(--dile-primary-light-color, #f3f3ae)));
|
|
56
88
|
color: var(--dile-button-hover-text-color, var(--dile-button-text-color, var(--dile-on-primary-light-color, #303030)));
|
|
57
89
|
border-color: var(--dile-button-hover-border-color, var(--dile-button-border-color, var(--dile-primary-color, #666666)));
|
|
90
|
+
--dile-icon-color: var(--dile-button-icon-hover-color, var(--dile-button-hover-text-color, var(--dile-button-text-color, var(--dile-on-primary-light-color, #303030))));
|
|
58
91
|
}
|
|
59
92
|
button:focus-visible {
|
|
60
93
|
outline: 2px solid var(--dile-button-ring-color, #12c9e9);
|
|
@@ -101,32 +134,90 @@ export class DileButton extends LitElement {
|
|
|
101
134
|
:host([loading]) {
|
|
102
135
|
pointer-events: none;
|
|
103
136
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
justify-content: center;
|
|
108
|
-
gap: var(--dile-button-spinner-gap, 0.5rem);
|
|
137
|
+
|
|
138
|
+
:host([no-wrap]) button {
|
|
139
|
+
white-space: nowrap;
|
|
109
140
|
}
|
|
141
|
+
|
|
110
142
|
dile-spinner-icon {
|
|
111
143
|
--dile-icon-size: var(--dile-button-spinner-size, calc(var(--dile-button-font-size, 1rem) * 0.8));
|
|
112
144
|
--dile-icon-color: var(--dile-on-primary-color, #fff);
|
|
113
145
|
}
|
|
146
|
+
button.has-text dile-spinner-icon {
|
|
147
|
+
margin-left: var(--dile-button-spinner-gap, 0.5rem);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.button-icon {
|
|
151
|
+
display: flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
--dile-icon-size: var(--dile-button-icon-size, calc(var(--dile-button-font-size, 1rem) * 1.125));
|
|
154
|
+
}
|
|
155
|
+
/* The icon-to-text separation is only applied when the button has a label,
|
|
156
|
+
so an icon-only button stays perfectly centered with no dead space. */
|
|
157
|
+
button.has-text .button-icon.icon-left {
|
|
158
|
+
margin-right: var(--dile-button-icon-separation, 0.3rem);
|
|
159
|
+
}
|
|
160
|
+
button.has-text .button-icon.icon-right {
|
|
161
|
+
margin-left: var(--dile-button-icon-separation, 0.3rem);
|
|
162
|
+
}
|
|
114
163
|
`;
|
|
115
164
|
}
|
|
116
165
|
|
|
117
166
|
render() {
|
|
167
|
+
const showIcon = !!this.icon && !this.loading;
|
|
118
168
|
return html`
|
|
119
|
-
<button
|
|
120
|
-
|
|
169
|
+
<button
|
|
170
|
+
class=${classMap({ 'has-text': this._hasText })}
|
|
171
|
+
@click=${this._onClick}
|
|
121
172
|
?disabled=${this.disabled || this.loading}
|
|
122
173
|
type=${this.type || "button"}
|
|
174
|
+
aria-label=${ifDefined(this._computedAriaLabel)}
|
|
175
|
+
aria-busy=${ifDefined(this.loading ? 'true' : undefined)}
|
|
123
176
|
>
|
|
177
|
+
${showIcon && this.iconPosition !== 'right' ? this.iconTemplate : ''}
|
|
178
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
179
|
+
${showIcon && this.iconPosition === 'right' ? this.iconTemplate : ''}
|
|
124
180
|
${this.loading ? html`<dile-spinner-icon active></dile-spinner-icon>` : ''}
|
|
125
|
-
<slot></slot>
|
|
126
181
|
</button>
|
|
127
182
|
`;
|
|
128
183
|
}
|
|
129
184
|
|
|
185
|
+
get iconTemplate() {
|
|
186
|
+
const side = this.iconPosition === 'right' ? 'icon-right' : 'icon-left';
|
|
187
|
+
return html`
|
|
188
|
+
<dile-iconlib
|
|
189
|
+
class="button-icon ${side}"
|
|
190
|
+
icon=${this.icon}
|
|
191
|
+
aria-hidden="true"
|
|
192
|
+
></dile-iconlib>
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
get _computedAriaLabel() {
|
|
197
|
+
return this.label || undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
_detectText() {
|
|
201
|
+
if (this.textContent && this.textContent.trim().length > 0) return true;
|
|
202
|
+
return this.childElementCount > 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_onSlotChange(e) {
|
|
206
|
+
const nodes = e.target.assignedNodes({ flatten: true });
|
|
207
|
+
this._hasText = nodes.some(node =>
|
|
208
|
+
node.nodeType === Node.ELEMENT_NODE ||
|
|
209
|
+
(node.nodeType === Node.TEXT_NODE && node.textContent.trim() !== '')
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
updated(changedProperties) {
|
|
214
|
+
if (changedProperties.has('icon') || changedProperties.has('label') || changedProperties.has('_hasText')) {
|
|
215
|
+
if (this.icon && !this._hasText && !this.label) {
|
|
216
|
+
console.warn('dile-button: an icon-only button should set the "label" property so it has an accessible name.');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
130
221
|
_onClick(e) {
|
|
131
222
|
if (this.disabled || this.loading) {
|
|
132
223
|
e.preventDefault();
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { html, css
|
|
1
|
+
import { html, css } from "lit";
|
|
2
2
|
import { DileButton } from './DileButton.js';
|
|
3
3
|
import '../../icon/icon.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* dile-button-icon renders an icon supplied as a lit-html template (or any HTML
|
|
7
|
+
* template, e.g. an `<img>`). If you want string-based icons with icon-family
|
|
8
|
+
* support ("lucide.rocket", "material.home", ...), use `<dile-button>` together
|
|
9
|
+
* with its `icon` property instead.
|
|
10
|
+
*
|
|
11
|
+
* It inherits every feature from dile-button: `iconPosition`, `no-wrap`,
|
|
12
|
+
* `label`, loading spinner and form integration.
|
|
13
|
+
*/
|
|
5
14
|
export class DileButtonIcon extends DileButton {
|
|
6
15
|
static get properties() {
|
|
7
16
|
return {
|
|
@@ -14,30 +23,23 @@ export class DileButtonIcon extends DileButton {
|
|
|
14
23
|
super.styles,
|
|
15
24
|
css`
|
|
16
25
|
button {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: center;
|
|
19
26
|
--dile-icon-color: var(--dile-button-icon-color, var(--dile-on-primary-color, #ffffff));
|
|
20
27
|
}
|
|
21
|
-
dile-icon {
|
|
22
|
-
--dile-icon-size: var(--dile-button-icon-size, calc(var(--dile-button-font-size, 1rem) * 1.125));
|
|
23
|
-
margin-right: var(--dile-button-icon-separation, 0.3rem);
|
|
24
|
-
}
|
|
25
28
|
button:hover {
|
|
26
29
|
--dile-icon-color: var(--dile-button-icon-hover-color, var(--dile-on-primary-light-color, #888));
|
|
27
30
|
}
|
|
28
|
-
:host([no-wrap]) {
|
|
29
|
-
white-space: nowrap;
|
|
30
|
-
}
|
|
31
31
|
`
|
|
32
32
|
];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
get iconTemplate() {
|
|
36
|
+
const side = this.iconPosition === 'right' ? 'icon-right' : 'icon-left';
|
|
36
37
|
return html`
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
<dile-icon
|
|
39
|
+
class="button-icon ${side}"
|
|
40
|
+
.icon=${this.icon}
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
></dile-icon>
|
|
41
43
|
`;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -7,7 +7,8 @@ export class DileCard extends LitElement {
|
|
|
7
7
|
box-sizing: border-box;
|
|
8
8
|
}
|
|
9
9
|
:host {
|
|
10
|
-
display:
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
11
12
|
border: var(--dile-card-border, 1px solid #ccc);
|
|
12
13
|
border-radius: var(--dile-card-border-radius, 0.5rem);
|
|
13
14
|
background-color: var(--dile-card-background-color, #fff);
|
|
@@ -35,6 +36,7 @@ export class DileCard extends LitElement {
|
|
|
35
36
|
main {
|
|
36
37
|
padding-top: var(--dile-card-padding-y, 1rem);
|
|
37
38
|
padding-bottom: var(--dile-card-padding-y, 1rem);
|
|
39
|
+
flex-grow: 1;
|
|
38
40
|
}
|
|
39
41
|
footer {
|
|
40
42
|
border-top: var(--dile-card-footer-border-separator, 1px solid #ccc);
|
|
Binary file
|
|
@@ -223,7 +223,7 @@ describe('dile-input-mask', () => {
|
|
|
223
223
|
});
|
|
224
224
|
|
|
225
225
|
it('applies lazy separators only when next block is filled', async () => {
|
|
226
|
-
const el = await renderInputMask('<dile-input-mask name="iban" mask="AA-XXXX-0000"></dile-input-mask>');
|
|
226
|
+
const el = await renderInputMask('<dile-input-mask name="iban" mask="AA-XXXX-0000" lazySeparators></dile-input-mask>');
|
|
227
227
|
const input = el.shadowRoot.querySelector('input');
|
|
228
228
|
|
|
229
229
|
// Type 'ES' - should show 'ES' without trailing separator
|
|
@@ -256,5 +256,59 @@ describe('dile-input-mask', () => {
|
|
|
256
256
|
await el.updateComplete;
|
|
257
257
|
expect(el.maskedValue).toBe('ES-WSWQ-0000');
|
|
258
258
|
});
|
|
259
|
+
|
|
260
|
+
it('uses normal separators by default (lazySeparators=false)', async () => {
|
|
261
|
+
const el = await renderInputMask('<dile-input-mask name="test" mask="AA-XXXX-0000"></dile-input-mask>');
|
|
262
|
+
const input = el.shadowRoot.querySelector('input');
|
|
263
|
+
|
|
264
|
+
// Type 'ES' - should show 'ES-' (separator added immediately)
|
|
265
|
+
input.value = 'ES';
|
|
266
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
267
|
+
await el.updateComplete;
|
|
268
|
+
expect(el.maskedValue).toBe('ES-');
|
|
269
|
+
|
|
270
|
+
// Type 'ESWSWQ0000' - separators already included
|
|
271
|
+
input.value = 'ESWSWQ0000';
|
|
272
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
273
|
+
await el.updateComplete;
|
|
274
|
+
expect(el.maskedValue).toBe('ES-WSWQ-0000');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('supports lazySeparators property', async () => {
|
|
278
|
+
const el = await renderInputMask('<dile-input-mask name="test" mask="AA-XXXX-0000" lazySeparators></dile-input-mask>');
|
|
279
|
+
const input = el.shadowRoot.querySelector('input');
|
|
280
|
+
|
|
281
|
+
// Type 'ES' - should show 'ES' without separator (lazy behavior)
|
|
282
|
+
input.value = 'ES';
|
|
283
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
284
|
+
await el.updateComplete;
|
|
285
|
+
expect(el.maskedValue).toBe('ES');
|
|
286
|
+
|
|
287
|
+
// Type 'ESW' - should show 'ES-W' (separator added when next block starts)
|
|
288
|
+
input.value = 'ESW';
|
|
289
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
290
|
+
await el.updateComplete;
|
|
291
|
+
expect(el.maskedValue).toBe('ES-W');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('can toggle lazySeparators property dynamically', async () => {
|
|
295
|
+
const el = await renderInputMask('<dile-input-mask name="test" mask="AA-XXXX-0000"></dile-input-mask>');
|
|
296
|
+
const input = el.shadowRoot.querySelector('input');
|
|
297
|
+
|
|
298
|
+
// Initially with normal separators
|
|
299
|
+
input.value = 'ES';
|
|
300
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
301
|
+
await el.updateComplete;
|
|
302
|
+
expect(el.maskedValue).toBe('ES-');
|
|
303
|
+
|
|
304
|
+
// Toggle to lazy separators
|
|
305
|
+
el.lazySeparators = true;
|
|
306
|
+
await el.updateComplete;
|
|
307
|
+
input.value = 'ES';
|
|
308
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
309
|
+
await el.updateComplete;
|
|
310
|
+
expect(el.maskedValue).toBe('ES');
|
|
311
|
+
});
|
|
259
312
|
});
|
|
260
313
|
|
|
314
|
+
|
|
@@ -9,6 +9,7 @@ export class DileInputMask extends DileInput {
|
|
|
9
9
|
mask: { type: String },
|
|
10
10
|
maskedValue: { type: String },
|
|
11
11
|
language: { type: String },
|
|
12
|
+
lazySeparators: { type: Boolean },
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ export class DileInputMask extends DileInput {
|
|
|
18
19
|
this.maskedValue = '';
|
|
19
20
|
this.content = '';
|
|
20
21
|
this.language = 'en';
|
|
22
|
+
this.lazySeparators = false;
|
|
21
23
|
this._keyDownHandler = this.handleKeyDown.bind(this);
|
|
22
24
|
this._blurHandler = this.handleBlur.bind(this);
|
|
23
25
|
}
|
|
@@ -32,7 +34,7 @@ export class DileInputMask extends DileInput {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
createMaskController(mask) {
|
|
35
|
-
this.maskController = new MaskPattern(mask, this.language);
|
|
37
|
+
this.maskController = new MaskPattern(mask, this.language, this.lazySeparators);
|
|
36
38
|
this.maxChars = this.maskController.getTotalCharactersExpected();
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -46,6 +48,10 @@ export class DileInputMask extends DileInput {
|
|
|
46
48
|
if (changedProperties.has('language')) {
|
|
47
49
|
this.maskController.setLanguage(this.language);
|
|
48
50
|
}
|
|
51
|
+
if (changedProperties.has('lazySeparators')) {
|
|
52
|
+
this.maskController.setLazySeparators(this.lazySeparators);
|
|
53
|
+
this.updateMaskedValue();
|
|
54
|
+
}
|
|
49
55
|
if (changedProperties.has('value')) {
|
|
50
56
|
const transformedValue = this.maskController.transformValue(this.value.slice(0, this.maxChars));
|
|
51
57
|
this.content = transformedValue;
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* '(000) 000-0000' = US phone format
|
|
15
15
|
*/
|
|
16
16
|
export class MaskPattern {
|
|
17
|
-
constructor(pattern, language = 'en') {
|
|
17
|
+
constructor(pattern, language = 'en', lazySeparators = false) {
|
|
18
18
|
this.pattern = pattern;
|
|
19
19
|
this.language = language;
|
|
20
|
+
this.lazySeparators = lazySeparators;
|
|
20
21
|
this.parsePattern();
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -50,6 +51,10 @@ export class MaskPattern {
|
|
|
50
51
|
this.language = language;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
setLazySeparators(lazy) {
|
|
55
|
+
this.lazySeparators = lazy;
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
getTotalCharactersExpected() {
|
|
54
59
|
return this.patternMap.filter(p => p.type !== 'literal').length;
|
|
55
60
|
}
|
|
@@ -62,8 +67,13 @@ export class MaskPattern {
|
|
|
62
67
|
const patternInfo = this.patternMap[i];
|
|
63
68
|
|
|
64
69
|
if (patternInfo.type === 'literal') {
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
if (this.lazySeparators) {
|
|
71
|
+
// Lazy literals: only add if there are more input characters to fill slots after this literal
|
|
72
|
+
if (slotIndex < value.length) {
|
|
73
|
+
masked += patternInfo.char;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// Normal behavior: always add the literal
|
|
67
77
|
masked += patternInfo.char;
|
|
68
78
|
}
|
|
69
79
|
} else {
|
|
@@ -13,7 +13,7 @@ export class DileTab extends DileSelectableItem(LitElement) {
|
|
|
13
13
|
border-top-left-radius: var(--dile-tab-border-radius, 4px);
|
|
14
14
|
border-top-right-radius: var(--dile-tab-border-radius, 4px);
|
|
15
15
|
transition: all 0.3s ease;
|
|
16
|
-
color: var(--dile-tab-text-color, #666);
|
|
16
|
+
color: var(--dile-tab-text-color, var(--dile-on-background-color, #666));
|
|
17
17
|
background-color: var(--dile-tab-background-color, transparent);
|
|
18
18
|
cursor: pointer;
|
|
19
19
|
border: var(--dile-tab-border, none);
|
|
@@ -26,15 +26,15 @@ export class DileTab extends DileSelectableItem(LitElement) {
|
|
|
26
26
|
white-space: nowrap;
|
|
27
27
|
}
|
|
28
28
|
.selected {
|
|
29
|
-
background-color: var(--dile-tab-selected-background-color, #039be5);
|
|
30
|
-
color: var(--dile-tab-selected-text-color, #fff);
|
|
29
|
+
background-color: var(--dile-tab-selected-background-color, var(--dile-primary-color, #039be5));
|
|
30
|
+
color: var(--dile-tab-selected-text-color, var(--dile-on-primary-color, #fff));
|
|
31
31
|
border: var(--dile-tab-selected-border, none);
|
|
32
32
|
}
|
|
33
33
|
span {
|
|
34
34
|
display: block;
|
|
35
35
|
height: var(--dile-tab-selected-line-height, 5px);
|
|
36
36
|
width: 0;
|
|
37
|
-
background-color: var(--dile-tab-selected-line-color, #0070c0);
|
|
37
|
+
background-color: var(--dile-tab-selected-line-color, var(--dile-secondary-color, #0070c0));
|
|
38
38
|
transition: width 0.3s ease;
|
|
39
39
|
}
|
|
40
40
|
.markselected {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/ui",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
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": "
|
|
31
|
+
"gitHead": "771621d06e1fa74693baba84ed3befd6f41df664"
|
|
32
32
|
}
|