@latty-ds/web 0.10.3 → 1.0.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/custom-elements.json +1970 -229
- package/dist/components/accordion/index.js +36 -2
- package/dist/components/alert/alert.d.ts +29 -6
- package/dist/components/alert/alert.types.d.ts +5 -0
- package/dist/components/alert/index.js +127 -20
- package/dist/components/avatar/avatar.d.ts +2 -0
- package/dist/components/avatar/index.js +10 -3
- package/dist/components/badge/index.js +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +4 -0
- package/dist/components/breadcrumb/breadcrumb.types.d.ts +5 -0
- package/dist/components/breadcrumb/index.js +20 -3
- package/dist/components/button/button.d.ts +33 -0
- package/dist/components/button/index.js +138 -15
- package/dist/components/calendar/calendar.d.ts +15 -7
- package/dist/components/calendar/calendar.types.d.ts +17 -1
- package/dist/components/calendar/index.js +80 -27
- package/dist/components/checkbox/checkbox.d.ts +14 -2
- package/dist/components/checkbox/checkbox.types.d.ts +5 -0
- package/dist/components/checkbox/index.js +82 -5
- package/dist/components/chip/chip.d.ts +4 -1
- package/dist/components/chip/chip.types.d.ts +5 -0
- package/dist/components/chip/index.js +29 -1
- package/dist/components/color-input/color-input.d.ts +11 -2
- package/dist/components/color-input/color-input.types.d.ts +5 -0
- package/dist/components/color-input/index.js +92 -8
- package/dist/components/combobox/combobox.d.ts +18 -2
- package/dist/components/combobox/combobox.types.d.ts +7 -0
- package/dist/components/combobox/index.js +164 -23
- package/dist/components/date-input/date-input.d.ts +22 -7
- package/dist/components/date-input/date-input.types.d.ts +7 -0
- package/dist/components/date-input/index.js +218 -55
- package/dist/components/datepicker/datepicker.d.ts +9 -1
- package/dist/components/datepicker/index.js +108 -15
- package/dist/components/dialog/dialog.d.ts +39 -19
- package/dist/components/dialog/dialog.types.d.ts +5 -0
- package/dist/components/dialog/index.js +240 -93
- package/dist/components/divider/index.js +2 -2
- package/dist/components/dropdown/dropdown-item.d.ts +1 -1
- package/dist/components/dropdown/dropdown.d.ts +4 -0
- package/dist/components/dropdown/index.js +43 -3
- package/dist/components/icon-button/icon-button.d.ts +4 -0
- package/dist/components/icon-button/index.js +39 -4
- package/dist/components/image/image.d.ts +7 -7
- package/dist/components/image/index.js +6 -11
- package/dist/components/list/index.js +14 -4
- package/dist/components/nav/index.js +37 -17
- package/dist/components/nav/nav-item.d.ts +8 -8
- package/dist/components/pagination/index.js +57 -16
- package/dist/components/pagination/pagination.d.ts +8 -1
- package/dist/components/pagination/pagination.types.d.ts +15 -0
- package/dist/components/progress/index.js +44 -6
- package/dist/components/progress/progress.d.ts +12 -0
- package/dist/components/radio/index.js +148 -9
- package/dist/components/radio/radio.d.ts +34 -2
- package/dist/components/radio/radio.types.d.ts +5 -0
- package/dist/components/radio-group/index.js +120 -29
- package/dist/components/radio-group/radio-group.d.ts +27 -2
- package/dist/components/select/index.js +197 -44
- package/dist/components/select/select.d.ts +38 -4
- package/dist/components/select/select.types.d.ts +5 -0
- package/dist/components/shared/aria-forwarding.d.ts +1 -1
- package/dist/components/shared/form-default.d.ts +30 -0
- package/dist/components/shared/form-disabled.d.ts +40 -0
- package/dist/components/shared/form-field.d.ts +7 -0
- package/dist/components/shared/form-validity.d.ts +17 -0
- package/dist/components/shared/roving.d.ts +11 -0
- package/dist/components/shared/scroll-lock.d.ts +2 -0
- package/dist/components/sidepanel/index.js +278 -75
- package/dist/components/sidepanel/sidepanel.d.ts +32 -10
- package/dist/components/sidepanel/sidepanel.types.d.ts +10 -1
- package/dist/components/skeleton/index.js +3 -8
- package/dist/components/slider/index.js +307 -75
- package/dist/components/slider/slider.d.ts +16 -3
- package/dist/components/snackbar/index.js +82 -9
- package/dist/components/snackbar/snackbar.d.ts +17 -1
- package/dist/components/snackbar/snackbar.types.d.ts +5 -0
- package/dist/components/surface/index.js +8 -0
- package/dist/components/switch/index.js +101 -5
- package/dist/components/switch/switch.d.ts +13 -2
- package/dist/components/switch/switch.types.d.ts +5 -0
- package/dist/components/tab/index.js +15 -14
- package/dist/components/tab/tab.d.ts +0 -1
- package/dist/components/tab-group/index.js +102 -30
- package/dist/components/tab-group/tab-group.d.ts +17 -2
- package/dist/components/table/index.js +105 -61
- package/dist/components/table/table.d.ts +8 -2
- package/dist/components/table/table.types.d.ts +4 -3
- package/dist/components/text/index.js +16 -1
- package/dist/components/text/text.d.ts +1 -1
- package/dist/components/textfield/index.js +243 -35
- package/dist/components/textfield/textfield.d.ts +24 -2
- package/dist/components/textfield/textfield.types.d.ts +11 -0
- package/dist/components/tooltip/index.js +176 -49
- package/dist/components/tooltip/tooltip.d.ts +17 -0
- package/dist/components/tooltip/tooltip.types.d.ts +5 -1
- package/dist/index.cjs +2467 -692
- package/dist/index.js +2478 -699
- package/dist/manifest.json +202 -32
- package/dist/utils/dev-warn.d.ts +5 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +4 -8
|
@@ -27,7 +27,7 @@ export declare class Text extends ThemeableElement {
|
|
|
27
27
|
static styles: import("lit").CSSResult;
|
|
28
28
|
/** Typographic style to apply. */
|
|
29
29
|
variant: TextVariant;
|
|
30
|
-
/** HTML tag to render. Defaults to the semantic tag for the chosen variant. */
|
|
30
|
+
/** HTML tag to render. Defaults to the semantic tag for the chosen variant; anything outside `TextTag` is ignored. */
|
|
31
31
|
as: TextTag | '';
|
|
32
32
|
render(): import("lit").TemplateResult;
|
|
33
33
|
}
|
|
@@ -10,7 +10,8 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/components/textfield/textfield.ts
|
|
13
|
-
import { html as html2 } from "lit";
|
|
13
|
+
import { html as html2, nothing } from "lit";
|
|
14
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
14
15
|
import { ifDefined } from "lit/directives/if-defined.js";
|
|
15
16
|
|
|
16
17
|
// src/base/themeable-element.ts
|
|
@@ -75,16 +76,25 @@ var textfieldStyles = css`
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
.icon-start {
|
|
78
|
-
|
|
79
|
+
inset-inline-start: var(--lt-spacing-3);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
.icon-end {
|
|
82
|
-
|
|
83
|
+
inset-inline-end: var(--lt-spacing-3);
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/* Unlike .variant-icon (a decorative lt-icon), the password toggle is a real <button> — reset the native
|
|
87
|
+
chrome so it reads as an inline icon, and re-declare align-items since it's no longer a plain absolutely-
|
|
88
|
+
positioned element inheriting the .icon-start/.icon-end flex-adjacent layout by accident. */
|
|
85
89
|
.password-toggle {
|
|
86
|
-
|
|
90
|
+
align-items: center;
|
|
91
|
+
background: none;
|
|
92
|
+
border: none;
|
|
87
93
|
cursor: pointer;
|
|
94
|
+
display: inline-flex;
|
|
95
|
+
font: inherit;
|
|
96
|
+
padding: 0;
|
|
97
|
+
pointer-events: auto;
|
|
88
98
|
transition: color 120ms ease;
|
|
89
99
|
}
|
|
90
100
|
|
|
@@ -92,6 +102,16 @@ var textfieldStyles = css`
|
|
|
92
102
|
color: var(--lt-text-default);
|
|
93
103
|
}
|
|
94
104
|
|
|
105
|
+
.password-toggle:disabled {
|
|
106
|
+
cursor: not-allowed;
|
|
107
|
+
opacity: 0.6;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.password-toggle:focus-visible {
|
|
111
|
+
outline: 2px solid var(--lt-border-focus);
|
|
112
|
+
outline-offset: 2px;
|
|
113
|
+
}
|
|
114
|
+
|
|
95
115
|
.variant-icon {
|
|
96
116
|
pointer-events: none;
|
|
97
117
|
}
|
|
@@ -164,12 +184,12 @@ var textfieldStyles = css`
|
|
|
164
184
|
|
|
165
185
|
:host([size='sm']) input.has-start-icon,
|
|
166
186
|
:host([size='sm']) textarea.has-start-icon {
|
|
167
|
-
padding-
|
|
187
|
+
padding-inline-start: calc(var(--lt-spacing-3) + 20px + var(--lt-spacing-2));
|
|
168
188
|
}
|
|
169
189
|
|
|
170
190
|
:host([size='sm']) input.has-end-icon,
|
|
171
191
|
:host([size='sm']) textarea.has-end-icon {
|
|
172
|
-
padding-
|
|
192
|
+
padding-inline-end: calc(var(--lt-spacing-3) + 20px + var(--lt-spacing-2));
|
|
173
193
|
}
|
|
174
194
|
|
|
175
195
|
:host([size='md']) input,
|
|
@@ -181,12 +201,12 @@ var textfieldStyles = css`
|
|
|
181
201
|
|
|
182
202
|
:host([size='md']) input.has-start-icon,
|
|
183
203
|
:host([size='md']) textarea.has-start-icon {
|
|
184
|
-
padding-
|
|
204
|
+
padding-inline-start: calc(var(--lt-spacing-4) + 20px + var(--lt-spacing-2));
|
|
185
205
|
}
|
|
186
206
|
|
|
187
207
|
:host([size='md']) input.has-end-icon,
|
|
188
208
|
:host([size='md']) textarea.has-end-icon {
|
|
189
|
-
padding-
|
|
209
|
+
padding-inline-end: calc(var(--lt-spacing-4) + 20px + var(--lt-spacing-2));
|
|
190
210
|
}
|
|
191
211
|
|
|
192
212
|
:host([size='lg']) input,
|
|
@@ -198,12 +218,12 @@ var textfieldStyles = css`
|
|
|
198
218
|
|
|
199
219
|
:host([size='lg']) input.has-start-icon,
|
|
200
220
|
:host([size='lg']) textarea.has-start-icon {
|
|
201
|
-
padding-
|
|
221
|
+
padding-inline-start: calc(var(--lt-spacing-5) + 24px + var(--lt-spacing-2));
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
:host([size='lg']) input.has-end-icon,
|
|
205
225
|
:host([size='lg']) textarea.has-end-icon {
|
|
206
|
-
padding-
|
|
226
|
+
padding-inline-end: calc(var(--lt-spacing-5) + 24px + var(--lt-spacing-2));
|
|
207
227
|
}
|
|
208
228
|
|
|
209
229
|
:host([size='lg']) .icon-start,
|
|
@@ -258,6 +278,25 @@ var textfieldStyles = css`
|
|
|
258
278
|
font-weight: 200;
|
|
259
279
|
}
|
|
260
280
|
|
|
281
|
+
/* .icon-end disables pointer events so decorative icons never swallow clicks; the clear button is the one real
|
|
282
|
+
control in that slot, so it opts back in and resets native button chrome. */
|
|
283
|
+
.clear-button {
|
|
284
|
+
align-items: center;
|
|
285
|
+
background: none;
|
|
286
|
+
border: 0;
|
|
287
|
+
border-radius: var(--lt-border-radius, 4px);
|
|
288
|
+
cursor: pointer;
|
|
289
|
+
display: flex;
|
|
290
|
+
padding: 0;
|
|
291
|
+
pointer-events: auto;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.counter {
|
|
295
|
+
color: var(--lt-text-subtle);
|
|
296
|
+
display: block;
|
|
297
|
+
text-align: end;
|
|
298
|
+
}
|
|
299
|
+
|
|
261
300
|
:host([variant='success']) .helper-text {
|
|
262
301
|
color: var(--lt-text-success);
|
|
263
302
|
}
|
|
@@ -272,7 +311,7 @@ var textfieldStyles = css`
|
|
|
272
311
|
}
|
|
273
312
|
|
|
274
313
|
.required-indicator {
|
|
275
|
-
color: var(--lt-
|
|
314
|
+
color: var(--lt-text-error);
|
|
276
315
|
}
|
|
277
316
|
|
|
278
317
|
:host([uppercase]) label lt-text::part(base) {
|
|
@@ -280,8 +319,71 @@ var textfieldStyles = css`
|
|
|
280
319
|
letter-spacing: 0.05rem;
|
|
281
320
|
text-transform: lowercase;
|
|
282
321
|
}
|
|
322
|
+
|
|
323
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
324
|
+
|
|
325
|
+
@media (prefers-reduced-motion: reduce) {
|
|
326
|
+
.password-toggle,
|
|
327
|
+
input,
|
|
328
|
+
textarea {
|
|
329
|
+
transition: none;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
283
332
|
`;
|
|
284
333
|
|
|
334
|
+
// src/components/shared/form-default.ts
|
|
335
|
+
var FormDefaultController = class {
|
|
336
|
+
constructor(host, get) {
|
|
337
|
+
this._captured = false;
|
|
338
|
+
this._get = get;
|
|
339
|
+
host.addController(this);
|
|
340
|
+
}
|
|
341
|
+
hostConnected() {
|
|
342
|
+
if (this._captured) return;
|
|
343
|
+
this._captured = true;
|
|
344
|
+
this.value = this._get();
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/components/shared/form-disabled.ts
|
|
349
|
+
var FormDisabledController = class {
|
|
350
|
+
constructor(host) {
|
|
351
|
+
this._fieldsetDisabled = false;
|
|
352
|
+
this._host = host;
|
|
353
|
+
host.addController(this);
|
|
354
|
+
}
|
|
355
|
+
// No-op — this controller has no connect-time work of its own (formDisabledCallback drives it instead), but
|
|
356
|
+
// ReactiveController's members are all optional, and a class satisfying an all-optional interface with no
|
|
357
|
+
// members in common is a TS error (TS2559); this gives it one.
|
|
358
|
+
hostConnected() {
|
|
359
|
+
}
|
|
360
|
+
/** Whether an ancestor `<fieldset>` is currently disabled. */
|
|
361
|
+
get fieldsetDisabled() {
|
|
362
|
+
return this._fieldsetDisabled;
|
|
363
|
+
}
|
|
364
|
+
setFieldsetDisabled(disabled) {
|
|
365
|
+
if (this._fieldsetDisabled === disabled) return;
|
|
366
|
+
this._fieldsetDisabled = disabled;
|
|
367
|
+
this._host.requestUpdate();
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/components/shared/form-validity.ts
|
|
372
|
+
function toValidityFlags(validity) {
|
|
373
|
+
return {
|
|
374
|
+
valueMissing: validity.valueMissing,
|
|
375
|
+
typeMismatch: validity.typeMismatch,
|
|
376
|
+
patternMismatch: validity.patternMismatch,
|
|
377
|
+
tooLong: validity.tooLong,
|
|
378
|
+
tooShort: validity.tooShort,
|
|
379
|
+
rangeUnderflow: validity.rangeUnderflow,
|
|
380
|
+
rangeOverflow: validity.rangeOverflow,
|
|
381
|
+
stepMismatch: validity.stepMismatch,
|
|
382
|
+
badInput: validity.badInput,
|
|
383
|
+
customError: validity.customError
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
285
387
|
// src/components/textfield/textfield.ts
|
|
286
388
|
import "@latty-ds/icons";
|
|
287
389
|
|
|
@@ -454,6 +556,20 @@ var textStyles = css2`
|
|
|
454
556
|
`;
|
|
455
557
|
|
|
456
558
|
// src/components/text/text.ts
|
|
559
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
560
|
+
"h1",
|
|
561
|
+
"h2",
|
|
562
|
+
"h3",
|
|
563
|
+
"h4",
|
|
564
|
+
"h5",
|
|
565
|
+
"h6",
|
|
566
|
+
"p",
|
|
567
|
+
"span",
|
|
568
|
+
"div",
|
|
569
|
+
"label",
|
|
570
|
+
"strong",
|
|
571
|
+
"em"
|
|
572
|
+
]);
|
|
457
573
|
var VARIANT_TAG = {
|
|
458
574
|
"display-2xl": "p",
|
|
459
575
|
"display-xl": "p",
|
|
@@ -478,7 +594,8 @@ var Text = class extends ThemeableElement {
|
|
|
478
594
|
this.as = "";
|
|
479
595
|
}
|
|
480
596
|
render() {
|
|
481
|
-
const
|
|
597
|
+
const requested = this.as && ALLOWED_TAGS.has(this.as) ? this.as : "";
|
|
598
|
+
const tag = unsafeStatic(requested || VARIANT_TAG[this.variant] || "p");
|
|
482
599
|
return html`<${tag} part="base"><slot></slot></${tag}>`;
|
|
483
600
|
}
|
|
484
601
|
};
|
|
@@ -494,9 +611,23 @@ Text = __decorateClass([
|
|
|
494
611
|
], Text);
|
|
495
612
|
|
|
496
613
|
// src/components/textfield/textfield.ts
|
|
614
|
+
var textfieldCount = 0;
|
|
615
|
+
var DEFAULT_LABELS = {
|
|
616
|
+
validationMessage: "Please match the requested format",
|
|
617
|
+
showPassword: "Show password",
|
|
618
|
+
hidePassword: "Hide password",
|
|
619
|
+
clear: "Clear"
|
|
620
|
+
};
|
|
497
621
|
var Textfield = class extends ThemeableElement {
|
|
498
622
|
constructor() {
|
|
499
623
|
super();
|
|
624
|
+
this._default = new FormDefaultController(this, () => this.value);
|
|
625
|
+
this._fieldsetDisabled = new FormDisabledController(this);
|
|
626
|
+
// Stable per-instance ids for `label for` and `aria-describedby` (TFD-4) — not the host's own `id` attribute,
|
|
627
|
+
// which a consumer may set for their own purposes (or leave unset).
|
|
628
|
+
this._uid = `lt-textfield-${textfieldCount++}`;
|
|
629
|
+
this._inputId = `${this._uid}-input`;
|
|
630
|
+
this._helperId = `${this._uid}-helper`;
|
|
500
631
|
this.variant = "default";
|
|
501
632
|
this.size = "md";
|
|
502
633
|
this.type = "text";
|
|
@@ -513,6 +644,9 @@ var Textfield = class extends ThemeableElement {
|
|
|
513
644
|
this.min = null;
|
|
514
645
|
this.max = null;
|
|
515
646
|
this.uppercase = false;
|
|
647
|
+
this.clearable = false;
|
|
648
|
+
this.counter = false;
|
|
649
|
+
this.labels = {};
|
|
516
650
|
this.isPasswordVisible = false;
|
|
517
651
|
this._touched = false;
|
|
518
652
|
this._autoError = false;
|
|
@@ -522,8 +656,19 @@ var Textfield = class extends ThemeableElement {
|
|
|
522
656
|
this._touched = true;
|
|
523
657
|
this._autoError = !this._validate();
|
|
524
658
|
};
|
|
659
|
+
// A native single-line <input> inside a <form> implicitly submits the form on Enter; the inner input here is
|
|
660
|
+
// in the shadow DOM, so the browser never wires that up on its own (TFD-2). Not on the multiline textarea —
|
|
661
|
+
// a native <textarea> never implicit-submits on Enter either, since Enter there means "new line".
|
|
662
|
+
this._onKeydown = (e) => {
|
|
663
|
+
if (e.key !== "Enter" || e.isComposing || this._isDisabled) return;
|
|
664
|
+
const form = this.closest("form");
|
|
665
|
+
form?.requestSubmit();
|
|
666
|
+
};
|
|
525
667
|
this._internals = this.attachInternals();
|
|
526
668
|
}
|
|
669
|
+
get _labels() {
|
|
670
|
+
return { ...DEFAULT_LABELS, ...this.labels };
|
|
671
|
+
}
|
|
527
672
|
/**
|
|
528
673
|
* Handles input events from the native input or textarea element.
|
|
529
674
|
* Updates the component's value property and dispatches a custom input event.
|
|
@@ -574,6 +719,19 @@ var Textfield = class extends ThemeableElement {
|
|
|
574
719
|
* @param e - The native change event
|
|
575
720
|
* @private
|
|
576
721
|
*/
|
|
722
|
+
get _showClear() {
|
|
723
|
+
return this.clearable && !!this.value && !this._isDisabled && !this.readonly && this.type !== "password";
|
|
724
|
+
}
|
|
725
|
+
_clear() {
|
|
726
|
+
this.value = "";
|
|
727
|
+
if (this._touched) this._autoError = !this._validate();
|
|
728
|
+
const detail = { value: "" };
|
|
729
|
+
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true, composed: true }));
|
|
730
|
+
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true, composed: true }));
|
|
731
|
+
this.updateComplete.then(() => {
|
|
732
|
+
this.shadowRoot?.querySelector("input, textarea")?.focus();
|
|
733
|
+
});
|
|
734
|
+
}
|
|
577
735
|
handleChange(e) {
|
|
578
736
|
e.stopPropagation();
|
|
579
737
|
const input = e.target;
|
|
@@ -595,6 +753,12 @@ var Textfield = class extends ThemeableElement {
|
|
|
595
753
|
togglePasswordVisibility() {
|
|
596
754
|
this.isPasswordVisible = !this.isPasswordVisible;
|
|
597
755
|
}
|
|
756
|
+
// Clicking the toggle would otherwise focus the button first (the default mousedown action), moving the
|
|
757
|
+
// caret out of the field. Suppressing just that default action leaves the click itself untouched, and the
|
|
758
|
+
// input stays focused throughout.
|
|
759
|
+
_preventFocusSteal(e) {
|
|
760
|
+
e.preventDefault();
|
|
761
|
+
}
|
|
598
762
|
/**
|
|
599
763
|
* Determines the appropriate end icon based on input type and variant.
|
|
600
764
|
*
|
|
@@ -623,15 +787,27 @@ var Textfield = class extends ThemeableElement {
|
|
|
623
787
|
super.updated(changed);
|
|
624
788
|
this.toggleAttribute("data-invalid", this._autoError);
|
|
625
789
|
this._internals.setFormValue(this.value || null);
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
-
|
|
790
|
+
const inner = this.shadowRoot.querySelector("input") ?? this.shadowRoot.querySelector("textarea");
|
|
791
|
+
if (!inner) return;
|
|
792
|
+
if (!inner.validity.valid) {
|
|
793
|
+
this._internals.setValidity(toValidityFlags(inner.validity), inner.validationMessage, inner);
|
|
794
|
+
} else if (!this._validate()) {
|
|
795
|
+
this._internals.setValidity({ customError: true }, this._labels.validationMessage, inner);
|
|
629
796
|
} else {
|
|
630
797
|
this._internals.setValidity({});
|
|
631
798
|
}
|
|
632
799
|
}
|
|
633
800
|
formResetCallback() {
|
|
634
|
-
this.value =
|
|
801
|
+
this.value = this._default.value;
|
|
802
|
+
this._touched = false;
|
|
803
|
+
this._autoError = false;
|
|
804
|
+
}
|
|
805
|
+
formDisabledCallback(disabled) {
|
|
806
|
+
this._fieldsetDisabled.setFieldsetDisabled(disabled);
|
|
807
|
+
}
|
|
808
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
809
|
+
get _isDisabled() {
|
|
810
|
+
return this.disabled || this._fieldsetDisabled.fieldsetDisabled;
|
|
635
811
|
}
|
|
636
812
|
checkValidity() {
|
|
637
813
|
return this._internals.checkValidity();
|
|
@@ -645,14 +821,16 @@ var Textfield = class extends ThemeableElement {
|
|
|
645
821
|
const isMultiline = this.type === "multiline";
|
|
646
822
|
const isNumber = this.type === "number";
|
|
647
823
|
const endIconName = this.getEndIconName();
|
|
648
|
-
const
|
|
824
|
+
const showClear = this._showClear;
|
|
825
|
+
const hasEndIcon = Boolean(endIconName) || showClear;
|
|
649
826
|
const actualInputType = isPasswordField && this.isPasswordVisible ? "text" : this.type;
|
|
650
827
|
const isInvalid = this._autoError || this.variant === "error";
|
|
651
828
|
const resolvedHelperText = typeof this.helperText === "function" ? this.helperText(isInvalid) : this.helperText;
|
|
829
|
+
const ariaLabel = this.label ? void 0 : this.placeholder || void 0;
|
|
652
830
|
return html2`
|
|
653
831
|
<div class="wrapper">
|
|
654
832
|
${this.label ? html2`
|
|
655
|
-
<label>
|
|
833
|
+
<label for=${this._inputId}>
|
|
656
834
|
<lt-text variant="label" as="span">${this.label}</lt-text>
|
|
657
835
|
${this.required ? html2`<span class="required-indicator">*</span>` : ""}
|
|
658
836
|
</label>
|
|
@@ -661,25 +839,27 @@ var Textfield = class extends ThemeableElement {
|
|
|
661
839
|
${hasStartIcon ? html2`<lt-icon class="icon-start" name=${this.iconStart}></lt-icon>` : ""}
|
|
662
840
|
${isMultiline ? html2`
|
|
663
841
|
<textarea
|
|
842
|
+
id=${this._inputId}
|
|
664
843
|
.value=${this.value}
|
|
665
844
|
name=${ifDefined(this.name || void 0)}
|
|
666
845
|
placeholder=${this.placeholder}
|
|
667
846
|
rows=${this.rows}
|
|
668
847
|
minlength=${ifDefined(this.min !== null ? this.min : void 0)}
|
|
669
848
|
maxlength=${ifDefined(this.max !== null ? this.max : void 0)}
|
|
670
|
-
?disabled=${this.
|
|
849
|
+
?disabled=${this._isDisabled}
|
|
671
850
|
?required=${this.required}
|
|
672
851
|
?readonly=${this.readonly}
|
|
673
852
|
@input=${this.handleInput}
|
|
674
853
|
@change=${this.handleChange}
|
|
675
854
|
@blur=${this._onBlur}
|
|
676
|
-
aria-label=${
|
|
855
|
+
aria-label=${ifDefined(ariaLabel)}
|
|
677
856
|
aria-invalid=${isInvalid ? "true" : "false"}
|
|
678
|
-
|
|
679
|
-
|
|
857
|
+
aria-describedby=${resolvedHelperText ? this._helperId : nothing}
|
|
858
|
+
class=${classMap({ "has-start-icon": hasStartIcon, "has-end-icon": hasEndIcon })}
|
|
680
859
|
></textarea>
|
|
681
860
|
` : html2`
|
|
682
861
|
<input
|
|
862
|
+
id=${this._inputId}
|
|
683
863
|
type=${actualInputType}
|
|
684
864
|
.value=${this.value}
|
|
685
865
|
name=${ifDefined(this.name || void 0)}
|
|
@@ -688,27 +868,46 @@ var Textfield = class extends ThemeableElement {
|
|
|
688
868
|
max=${ifDefined(isNumber && this.max !== null ? this.max : void 0)}
|
|
689
869
|
minlength=${ifDefined(!isNumber && this.min !== null ? this.min : void 0)}
|
|
690
870
|
maxlength=${ifDefined(!isNumber && this.max !== null ? this.max : void 0)}
|
|
691
|
-
?disabled=${this.
|
|
871
|
+
?disabled=${this._isDisabled}
|
|
692
872
|
?required=${this.required}
|
|
693
873
|
?readonly=${this.readonly}
|
|
694
874
|
@input=${this.handleInput}
|
|
695
875
|
@change=${this.handleChange}
|
|
696
876
|
@blur=${this._onBlur}
|
|
697
|
-
|
|
877
|
+
@keydown=${this._onKeydown}
|
|
878
|
+
aria-label=${ifDefined(ariaLabel)}
|
|
698
879
|
aria-invalid=${isInvalid ? "true" : "false"}
|
|
699
|
-
|
|
700
|
-
|
|
880
|
+
aria-describedby=${resolvedHelperText ? this._helperId : nothing}
|
|
881
|
+
class=${classMap({ "has-start-icon": hasStartIcon, "has-end-icon": hasEndIcon })}
|
|
701
882
|
/>
|
|
702
883
|
`}
|
|
703
|
-
${
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
884
|
+
${showClear ? html2`<button
|
|
885
|
+
type="button"
|
|
886
|
+
class="icon-end clear-button"
|
|
887
|
+
aria-label=${this._labels.clear}
|
|
888
|
+
@mousedown=${this._preventFocusSteal}
|
|
889
|
+
@click=${this._clear}
|
|
890
|
+
>
|
|
891
|
+
<lt-icon name="xmark"></lt-icon>
|
|
892
|
+
</button>` : ""}
|
|
893
|
+
${endIconName ? isPasswordField ? html2`
|
|
894
|
+
<button
|
|
895
|
+
type="button"
|
|
896
|
+
class="icon-end password-toggle"
|
|
897
|
+
?disabled=${this._isDisabled}
|
|
898
|
+
aria-label=${this.isPasswordVisible ? this._labels.hidePassword : this._labels.showPassword}
|
|
899
|
+
aria-pressed=${this.isPasswordVisible ? "true" : "false"}
|
|
900
|
+
@mousedown=${this._preventFocusSteal}
|
|
901
|
+
@click=${this.togglePasswordVisibility}
|
|
902
|
+
>
|
|
903
|
+
<lt-icon name=${endIconName}></lt-icon>
|
|
904
|
+
</button>
|
|
905
|
+
` : html2`<lt-icon class="icon-end variant-icon" name=${endIconName}></lt-icon>` : ""}
|
|
710
906
|
</div>
|
|
711
|
-
${resolvedHelperText ? html2`<lt-text variant="caption" as="span" class="helper-text"
|
|
907
|
+
${resolvedHelperText ? html2`<lt-text id=${this._helperId} variant="caption" as="span" class="helper-text"
|
|
908
|
+
>${resolvedHelperText}</lt-text
|
|
909
|
+
>` : ""}
|
|
910
|
+
${this.counter && !isNumber && this.max !== null ? html2`<lt-text variant="caption" as="span" class="counter">${this.value.length} / ${this.max}</lt-text>` : ""}
|
|
712
911
|
</div>
|
|
713
912
|
`;
|
|
714
913
|
}
|
|
@@ -725,7 +924,7 @@ __decorateClass([
|
|
|
725
924
|
property3({ reflect: true })
|
|
726
925
|
], Textfield.prototype, "type", 2);
|
|
727
926
|
__decorateClass([
|
|
728
|
-
property3(
|
|
927
|
+
property3()
|
|
729
928
|
], Textfield.prototype, "value", 2);
|
|
730
929
|
__decorateClass([
|
|
731
930
|
property3({ reflect: true })
|
|
@@ -763,6 +962,15 @@ __decorateClass([
|
|
|
763
962
|
__decorateClass([
|
|
764
963
|
property3({ type: Boolean, reflect: true })
|
|
765
964
|
], Textfield.prototype, "uppercase", 2);
|
|
965
|
+
__decorateClass([
|
|
966
|
+
property3({ type: Boolean, reflect: true })
|
|
967
|
+
], Textfield.prototype, "clearable", 2);
|
|
968
|
+
__decorateClass([
|
|
969
|
+
property3({ type: Boolean, reflect: true })
|
|
970
|
+
], Textfield.prototype, "counter", 2);
|
|
971
|
+
__decorateClass([
|
|
972
|
+
property3({ type: Object })
|
|
973
|
+
], Textfield.prototype, "labels", 2);
|
|
766
974
|
__decorateClass([
|
|
767
975
|
state()
|
|
768
976
|
], Textfield.prototype, "isPasswordVisible", 2);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PropertyValues } from 'lit';
|
|
2
2
|
import { ThemeableElement } from '../../base';
|
|
3
|
-
import { HelperText, TextfieldSize, TextfieldType, TextfieldVariant } from './textfield.types';
|
|
3
|
+
import { HelperText, TextfieldSize, TextfieldType, TextfieldVariant, type TextfieldLabels } from './textfield.types';
|
|
4
4
|
import '@latty-ds/icons';
|
|
5
5
|
import '../text/text';
|
|
6
6
|
/**
|
|
@@ -46,6 +46,11 @@ export declare class Textfield extends ThemeableElement {
|
|
|
46
46
|
static styles: import("lit").CSSResult;
|
|
47
47
|
static formAssociated: boolean;
|
|
48
48
|
private _internals;
|
|
49
|
+
private _default;
|
|
50
|
+
private _fieldsetDisabled;
|
|
51
|
+
private readonly _uid;
|
|
52
|
+
private readonly _inputId;
|
|
53
|
+
private readonly _helperId;
|
|
49
54
|
constructor();
|
|
50
55
|
/**
|
|
51
56
|
* Visual variant that determines styling and automatic end icon.
|
|
@@ -63,7 +68,10 @@ export declare class Textfield extends ThemeableElement {
|
|
|
63
68
|
*/
|
|
64
69
|
type: TextfieldType;
|
|
65
70
|
/**
|
|
66
|
-
* Current value of the input.
|
|
71
|
+
* Current value of the input. Not reflected as an attribute: for `type="password"` that would put whatever
|
|
72
|
+
* the user types in plain text in the DOM (`outerHTML`, DevTools, session-replay/DOM-mutation recorders) —
|
|
73
|
+
* visible via `getAttribute('value')`/`[value]` regardless of input type. Read the property, or the
|
|
74
|
+
* `input`/`change` event's `detail.value`, instead of an attribute selector.
|
|
67
75
|
* @default ''
|
|
68
76
|
*/
|
|
69
77
|
value: string;
|
|
@@ -132,6 +140,13 @@ export declare class Textfield extends ThemeableElement {
|
|
|
132
140
|
* @default false
|
|
133
141
|
*/
|
|
134
142
|
uppercase: boolean;
|
|
143
|
+
/** Overridable UI text (format validation message, password-toggle button labels), for i18n. English defaults — override only the keys you need. */
|
|
144
|
+
/** Shows a clear button while the field has a value. Not shown for password fields, which have their own toggle. */
|
|
145
|
+
clearable: boolean;
|
|
146
|
+
/** Shows the character count as `length / max` below the field. Uses `max` as the limit, which is also enforced natively. */
|
|
147
|
+
counter: boolean;
|
|
148
|
+
labels: Partial<TextfieldLabels>;
|
|
149
|
+
private get _labels();
|
|
135
150
|
private isPasswordVisible;
|
|
136
151
|
private _touched;
|
|
137
152
|
private _autoError;
|
|
@@ -152,7 +167,10 @@ export declare class Textfield extends ThemeableElement {
|
|
|
152
167
|
* @param e - The native change event
|
|
153
168
|
* @private
|
|
154
169
|
*/
|
|
170
|
+
private get _showClear();
|
|
171
|
+
private _clear;
|
|
155
172
|
private handleChange;
|
|
173
|
+
private _onKeydown;
|
|
156
174
|
/**
|
|
157
175
|
* Toggles password visibility for password-type inputs.
|
|
158
176
|
* Switches between showing plain text and masked password.
|
|
@@ -160,6 +178,7 @@ export declare class Textfield extends ThemeableElement {
|
|
|
160
178
|
* @private
|
|
161
179
|
*/
|
|
162
180
|
private togglePasswordVisibility;
|
|
181
|
+
private _preventFocusSteal;
|
|
163
182
|
/**
|
|
164
183
|
* Determines the appropriate end icon based on input type and variant.
|
|
165
184
|
*
|
|
@@ -173,6 +192,9 @@ export declare class Textfield extends ThemeableElement {
|
|
|
173
192
|
private getEndIconName;
|
|
174
193
|
updated(changed: PropertyValues): void;
|
|
175
194
|
formResetCallback(): void;
|
|
195
|
+
formDisabledCallback(disabled: boolean): void;
|
|
196
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
197
|
+
private get _isDisabled();
|
|
176
198
|
checkValidity(): boolean;
|
|
177
199
|
reportValidity(): boolean;
|
|
178
200
|
render(): import("lit").TemplateResult<1>;
|
|
@@ -38,3 +38,14 @@ export type TextfieldType = 'text' | 'email' | 'password' | 'tel' | 'url' | 'num
|
|
|
38
38
|
* helperText={(error) => error ? 'Enter a valid email' : 'We\'ll never share your email'}
|
|
39
39
|
*/
|
|
40
40
|
export type HelperText = string | ((error: boolean) => string);
|
|
41
|
+
/** Overridable UI text for `lt-textfield`, for i18n. */
|
|
42
|
+
export interface TextfieldLabels {
|
|
43
|
+
/** Validation message shown when the value fails a format rule HTML has no native constraint for (e.g. `tel`). */
|
|
44
|
+
validationMessage: string;
|
|
45
|
+
/** Accessible label for the password-visibility toggle when the password is hidden. */
|
|
46
|
+
showPassword: string;
|
|
47
|
+
/** Accessible label for the password-visibility toggle when the password is shown. */
|
|
48
|
+
hidePassword: string;
|
|
49
|
+
/** Accessible label for the clear button shown by `clearable`. */
|
|
50
|
+
clear: string;
|
|
51
|
+
}
|