@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
|
@@ -35,7 +35,7 @@ __decorateClass([
|
|
|
35
35
|
], ThemeableElement.prototype, "theme", 2);
|
|
36
36
|
|
|
37
37
|
// src/components/slider/slider.ts
|
|
38
|
-
import { customElement as customElement3, property as property4
|
|
38
|
+
import { customElement as customElement3, property as property4 } from "lit/decorators.js";
|
|
39
39
|
|
|
40
40
|
// src/components/slider/slider.styles.ts
|
|
41
41
|
import { css } from "lit";
|
|
@@ -65,17 +65,20 @@ var sliderStyles = css`
|
|
|
65
65
|
width: 100%;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/* Position the tooltip above the thumb rather than the track center
|
|
68
|
+
/* Position the tooltip above the thumb rather than the track center. --_fill is a unitless 0–1 ratio (set from
|
|
69
|
+
JS on :host) — computed in CSS, not from a JS-read offsetWidth, so it can never drift after a resize or when
|
|
70
|
+
first shown from display:none (SLD-1). The thumb travels only (100% - thumb size), offset by half the thumb
|
|
71
|
+
so it's centered on the value, matching a native range input's own thumb geometry. */
|
|
69
72
|
lt-tooltip::part(tooltip) {
|
|
70
|
-
left: var(--
|
|
73
|
+
left: calc(var(--_fill, 0) * (100% - var(--lt-slider-thumb-size, 18px)) + var(--lt-slider-thumb-size, 18px) / 2);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
input[type='range'] {
|
|
74
77
|
appearance: none;
|
|
75
78
|
background: linear-gradient(
|
|
76
79
|
to right,
|
|
77
|
-
var(--lt-interactive-primary-bg) var(--
|
|
78
|
-
var(--lt-border-strong) var(--
|
|
80
|
+
var(--lt-interactive-primary-bg) calc(var(--_fill, 0) * 100%),
|
|
81
|
+
var(--lt-border-strong) calc(var(--_fill, 0) * 100%)
|
|
79
82
|
);
|
|
80
83
|
border-radius: 9999px;
|
|
81
84
|
cursor: pointer;
|
|
@@ -162,17 +165,102 @@ var sliderStyles = css`
|
|
|
162
165
|
--lt-slider-thumb-size: 22px;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
/* Disabled
|
|
166
|
-
|
|
168
|
+
/* Disabled. :host(:disabled) — the browser matches this automatically for a form-associated element inside a
|
|
169
|
+
disabled <fieldset>, no JS required; :host([disabled]) covers the component's own explicit attribute. */
|
|
170
|
+
:host([disabled]),
|
|
171
|
+
:host(:disabled) {
|
|
167
172
|
opacity: 0.5;
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
:host([disabled]) input[type='range']
|
|
175
|
+
:host([disabled]) input[type='range'],
|
|
176
|
+
:host(:disabled) input[type='range'] {
|
|
171
177
|
cursor: not-allowed;
|
|
172
178
|
pointer-events: none;
|
|
173
179
|
}
|
|
180
|
+
|
|
181
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
182
|
+
|
|
183
|
+
@media (prefers-reduced-motion: reduce) {
|
|
184
|
+
input[type='range']::-webkit-slider-thumb,
|
|
185
|
+
input[type='range']::-moz-range-thumb {
|
|
186
|
+
transition: none;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ── Forced colors (Windows High Contrast) ─────────────────────────────────
|
|
191
|
+
Under forced-colors, the track's linear-gradient (WebKit/Chromium) and the Firefox ::-moz-range-track/
|
|
192
|
+
::-moz-range-progress pair are all flattened to Canvas, with no border to fall back on — the whole track
|
|
193
|
+
disappears. Restoring a visible track boundary, plus a solid thumb fill, is enough: the thumb's own
|
|
194
|
+
position already conveys the value (the primary way a slider communicates it visually), matching how
|
|
195
|
+
native <input type="range"> itself renders under forced-colors in every engine — no attempt to also
|
|
196
|
+
recreate the fill-amount gradient with system colors, which native sliders don't do either. System color
|
|
197
|
+
keywords are exempt from the flattening and always resolve to the OS's own guaranteed-contrasting
|
|
198
|
+
palette. ─────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
199
|
+
|
|
200
|
+
@media (forced-colors: active) {
|
|
201
|
+
input[type='range'] {
|
|
202
|
+
background: Canvas;
|
|
203
|
+
border: 1px solid ButtonText;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
input[type='range']::-webkit-slider-thumb {
|
|
207
|
+
background: Highlight;
|
|
208
|
+
border-color: ButtonText;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
input[type='range']::-moz-range-thumb {
|
|
212
|
+
background: Highlight;
|
|
213
|
+
border-color: ButtonText;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
input[type='range']::-moz-range-track {
|
|
217
|
+
background: Canvas;
|
|
218
|
+
border: 1px solid ButtonText;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
input[type='range']::-moz-range-progress {
|
|
222
|
+
background: Highlight;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
174
225
|
`;
|
|
175
226
|
|
|
227
|
+
// src/components/shared/form-default.ts
|
|
228
|
+
var FormDefaultController = class {
|
|
229
|
+
constructor(host, get) {
|
|
230
|
+
this._captured = false;
|
|
231
|
+
this._get = get;
|
|
232
|
+
host.addController(this);
|
|
233
|
+
}
|
|
234
|
+
hostConnected() {
|
|
235
|
+
if (this._captured) return;
|
|
236
|
+
this._captured = true;
|
|
237
|
+
this.value = this._get();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// src/components/shared/form-disabled.ts
|
|
242
|
+
var FormDisabledController = class {
|
|
243
|
+
constructor(host) {
|
|
244
|
+
this._fieldsetDisabled = false;
|
|
245
|
+
this._host = host;
|
|
246
|
+
host.addController(this);
|
|
247
|
+
}
|
|
248
|
+
// No-op — this controller has no connect-time work of its own (formDisabledCallback drives it instead), but
|
|
249
|
+
// ReactiveController's members are all optional, and a class satisfying an all-optional interface with no
|
|
250
|
+
// members in common is a TS error (TS2559); this gives it one.
|
|
251
|
+
hostConnected() {
|
|
252
|
+
}
|
|
253
|
+
/** Whether an ancestor `<fieldset>` is currently disabled. */
|
|
254
|
+
get fieldsetDisabled() {
|
|
255
|
+
return this._fieldsetDisabled;
|
|
256
|
+
}
|
|
257
|
+
setFieldsetDisabled(disabled) {
|
|
258
|
+
if (this._fieldsetDisabled === disabled) return;
|
|
259
|
+
this._fieldsetDisabled = disabled;
|
|
260
|
+
this._host.requestUpdate();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
176
264
|
// src/components/tooltip/tooltip.ts
|
|
177
265
|
import { html as html2 } from "lit";
|
|
178
266
|
import { customElement as customElement2, property as property3, query } from "lit/decorators.js";
|
|
@@ -187,20 +275,39 @@ var tooltipStyles = css2`
|
|
|
187
275
|
--lt-tooltip-offset: 8px;
|
|
188
276
|
}
|
|
189
277
|
|
|
278
|
+
/* The bubble is a "manual" popover so it renders in the top layer — above an open lt-dialog/lt-sidepanel
|
|
279
|
+
regardless of z-index, and never clipped by an overflow:hidden ancestor. Its position is set from JS (see
|
|
280
|
+
_reposition in tooltip.ts), since a top-layer element is positioned against the viewport, not the host.
|
|
281
|
+
display/overlay need allow-discrete so hidePopover() doesn't cut the exit fade short — same as lt-snackbar. */
|
|
190
282
|
.tooltip {
|
|
191
283
|
background: var(--lt-tooltip-bg, var(--lt-bg-inverse));
|
|
284
|
+
border: 0;
|
|
192
285
|
border-radius: 4px;
|
|
193
286
|
color: var(--lt-tooltip-color, var(--lt-text-inverse));
|
|
287
|
+
inset: auto;
|
|
288
|
+
margin: 0;
|
|
194
289
|
opacity: 0;
|
|
290
|
+
overflow: visible;
|
|
195
291
|
padding: var(--lt-spacing-1) var(--lt-spacing-2);
|
|
196
292
|
pointer-events: none;
|
|
197
|
-
position:
|
|
293
|
+
position: fixed;
|
|
198
294
|
transition:
|
|
199
295
|
opacity 150ms ease,
|
|
200
|
-
|
|
201
|
-
|
|
296
|
+
display 150ms allow-discrete,
|
|
297
|
+
overlay 150ms allow-discrete;
|
|
202
298
|
white-space: nowrap;
|
|
203
|
-
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.tooltip:popover-open {
|
|
302
|
+
opacity: 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* A newly-shown popover has no earlier rendered frame to transition from — @starting-style supplies one so the
|
|
306
|
+
fade-in actually animates instead of snapping straight to the open state. */
|
|
307
|
+
@starting-style {
|
|
308
|
+
.tooltip:popover-open {
|
|
309
|
+
opacity: 0;
|
|
310
|
+
}
|
|
204
311
|
}
|
|
205
312
|
|
|
206
313
|
.arrow {
|
|
@@ -210,78 +317,60 @@ var tooltipStyles = css2`
|
|
|
210
317
|
width: 0;
|
|
211
318
|
}
|
|
212
319
|
|
|
213
|
-
:host(:hover) .tooltip,
|
|
214
|
-
:host(:focus-within) .tooltip {
|
|
215
|
-
opacity: 1;
|
|
216
|
-
visibility: visible;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
320
|
:host([disabled]) .tooltip,
|
|
220
321
|
:host(:not([content])) .tooltip,
|
|
221
322
|
:host([content='']) .tooltip {
|
|
222
323
|
display: none;
|
|
223
324
|
}
|
|
224
325
|
|
|
225
|
-
/* ──
|
|
326
|
+
/* ── Arrows ─────────────────────────────────────────────────────────────── The bubble's own position is set
|
|
327
|
+
from JS; each arrow sits on the bubble edge facing the host. Centering is direction-symmetric, so
|
|
328
|
+
inset-inline-start:50% + translateX(-50%) is correct in both LTR and RTL. Start/end use logical properties, so
|
|
329
|
+
they flip with the page direction without a custom-property trick. ─────────────────────────── */
|
|
226
330
|
|
|
227
|
-
|
|
228
|
-
:host([placement='top']) .tooltip {
|
|
229
|
-
bottom: calc(100% + var(--lt-tooltip-offset));
|
|
230
|
-
left: 50%;
|
|
231
|
-
transform: translateX(-50%);
|
|
232
|
-
}
|
|
331
|
+
/* ── Top (default) ──────────────────────────── */
|
|
233
332
|
|
|
234
333
|
:host(:not([placement])) .arrow,
|
|
235
334
|
:host([placement='top']) .arrow {
|
|
236
335
|
border-top-color: var(--lt-tooltip-bg, var(--lt-color-neutral-900));
|
|
237
|
-
|
|
336
|
+
inset-inline-start: 50%;
|
|
238
337
|
top: 100%;
|
|
239
338
|
transform: translateX(-50%);
|
|
240
339
|
}
|
|
241
340
|
|
|
242
341
|
/* ── Bottom ─────────────────────────────────── */
|
|
243
342
|
|
|
244
|
-
:host([placement='bottom']) .tooltip {
|
|
245
|
-
left: 50%;
|
|
246
|
-
top: calc(100% + var(--lt-tooltip-offset));
|
|
247
|
-
transform: translateX(-50%);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
343
|
:host([placement='bottom']) .arrow {
|
|
251
344
|
border-bottom-color: var(--lt-tooltip-bg, var(--lt-color-neutral-900));
|
|
252
345
|
bottom: 100%;
|
|
253
|
-
|
|
346
|
+
inset-inline-start: 50%;
|
|
254
347
|
transform: translateX(-50%);
|
|
255
348
|
}
|
|
256
349
|
|
|
257
|
-
/* ──
|
|
350
|
+
/* ── Start ──────────────────────────────────── */
|
|
258
351
|
|
|
259
|
-
:host([placement='
|
|
260
|
-
|
|
352
|
+
:host([placement='start']) .arrow {
|
|
353
|
+
border-inline-start-color: var(--lt-tooltip-bg, var(--lt-color-neutral-900));
|
|
354
|
+
inset-inline-start: 100%;
|
|
261
355
|
top: 50%;
|
|
262
356
|
transform: translateY(-50%);
|
|
263
357
|
}
|
|
264
358
|
|
|
265
|
-
|
|
266
|
-
border-left-color: var(--lt-tooltip-bg, var(--lt-color-neutral-900));
|
|
267
|
-
left: 100%;
|
|
268
|
-
top: 50%;
|
|
269
|
-
transform: translateY(-50%);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* ── Right ──────────────────────────────────── */
|
|
359
|
+
/* ── End ────────────────────────────────────── */
|
|
273
360
|
|
|
274
|
-
:host([placement='
|
|
275
|
-
|
|
361
|
+
:host([placement='end']) .arrow {
|
|
362
|
+
border-inline-end-color: var(--lt-tooltip-bg, var(--lt-color-neutral-900));
|
|
363
|
+
inset-inline-end: 100%;
|
|
276
364
|
top: 50%;
|
|
277
365
|
transform: translateY(-50%);
|
|
278
366
|
}
|
|
279
367
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
368
|
+
/* ── Reduced motion ─────────────────────────────────────────────────────── */
|
|
369
|
+
|
|
370
|
+
@media (prefers-reduced-motion: reduce) {
|
|
371
|
+
.tooltip {
|
|
372
|
+
transition: none;
|
|
373
|
+
}
|
|
285
374
|
}
|
|
286
375
|
`;
|
|
287
376
|
|
|
@@ -457,6 +546,20 @@ var textStyles = css3`
|
|
|
457
546
|
`;
|
|
458
547
|
|
|
459
548
|
// src/components/text/text.ts
|
|
549
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
550
|
+
"h1",
|
|
551
|
+
"h2",
|
|
552
|
+
"h3",
|
|
553
|
+
"h4",
|
|
554
|
+
"h5",
|
|
555
|
+
"h6",
|
|
556
|
+
"p",
|
|
557
|
+
"span",
|
|
558
|
+
"div",
|
|
559
|
+
"label",
|
|
560
|
+
"strong",
|
|
561
|
+
"em"
|
|
562
|
+
]);
|
|
460
563
|
var VARIANT_TAG = {
|
|
461
564
|
"display-2xl": "p",
|
|
462
565
|
"display-xl": "p",
|
|
@@ -481,7 +584,8 @@ var Text = class extends ThemeableElement {
|
|
|
481
584
|
this.as = "";
|
|
482
585
|
}
|
|
483
586
|
render() {
|
|
484
|
-
const
|
|
587
|
+
const requested = this.as && ALLOWED_TAGS.has(this.as) ? this.as : "";
|
|
588
|
+
const tag = unsafeStatic(requested || VARIANT_TAG[this.variant] || "p");
|
|
485
589
|
return html`<${tag} part="base"><slot></slot></${tag}>`;
|
|
486
590
|
}
|
|
487
591
|
};
|
|
@@ -502,17 +606,117 @@ var Tooltip = class extends ThemeableElement {
|
|
|
502
606
|
constructor() {
|
|
503
607
|
super(...arguments);
|
|
504
608
|
this._tooltipId = `lt-tooltip-${++_tooltipIdCounter}`;
|
|
609
|
+
this._hovered = false;
|
|
610
|
+
this._focused = false;
|
|
505
611
|
this.content = "";
|
|
506
612
|
this.placement = "top";
|
|
507
613
|
this.disabled = false;
|
|
508
614
|
this.background = "";
|
|
509
615
|
this.color = "";
|
|
510
|
-
|
|
511
|
-
|
|
616
|
+
// A top-layer popover is positioned against the viewport, not the host, so the bubble's fixed coordinates are
|
|
617
|
+
// computed here from the host's rect. Fixed coordinates need a physical side, so the logical start/end placements
|
|
618
|
+
// are resolved against the host's computed direction (start is the left side in LTR, the right side in RTL).
|
|
619
|
+
this._reposition = () => {
|
|
620
|
+
if (!this._isShown()) return;
|
|
621
|
+
const host = this.getBoundingClientRect();
|
|
622
|
+
const { width, height } = this._tooltipEl.getBoundingClientRect();
|
|
623
|
+
const style = getComputedStyle(this);
|
|
624
|
+
const offset = parseFloat(style.getPropertyValue("--lt-tooltip-offset")) || 8;
|
|
625
|
+
const rtl = style.direction === "rtl";
|
|
626
|
+
let left;
|
|
627
|
+
let top;
|
|
628
|
+
switch (this.placement) {
|
|
629
|
+
case "bottom":
|
|
630
|
+
left = host.left + host.width / 2 - width / 2;
|
|
631
|
+
top = host.bottom + offset;
|
|
632
|
+
break;
|
|
633
|
+
case "start":
|
|
634
|
+
case "end": {
|
|
635
|
+
const onLeft = this.placement === "start" !== rtl;
|
|
636
|
+
left = onLeft ? host.left - offset - width : host.right + offset;
|
|
637
|
+
top = host.top + host.height / 2 - height / 2;
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
default:
|
|
641
|
+
left = host.left + host.width / 2 - width / 2;
|
|
642
|
+
top = host.top - offset - height;
|
|
643
|
+
}
|
|
644
|
+
this._tooltipEl.style.left = `${left}px`;
|
|
645
|
+
this._tooltipEl.style.top = `${top}px`;
|
|
646
|
+
};
|
|
647
|
+
this._onPointerEnter = () => {
|
|
648
|
+
this._hovered = true;
|
|
649
|
+
this._sync();
|
|
650
|
+
};
|
|
651
|
+
this._onPointerLeave = () => {
|
|
652
|
+
this._hovered = false;
|
|
653
|
+
this._sync();
|
|
654
|
+
};
|
|
655
|
+
this._onFocusIn = () => {
|
|
656
|
+
this._focused = true;
|
|
657
|
+
this._sync();
|
|
658
|
+
};
|
|
659
|
+
this._onFocusOut = (e) => {
|
|
660
|
+
if (this.contains(e.relatedTarget)) return;
|
|
661
|
+
this._focused = false;
|
|
662
|
+
this._sync();
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
// aria-describedby's id lookup only works within one tree: the tooltip bubble lives in this element's own
|
|
666
|
+
// shadow root, but the trigger the id is set on is the slotted (light DOM) element — an id reference can't
|
|
667
|
+
// cross that boundary, so no AT ever actually announces it (verified: resolves to nothing in the accessibility
|
|
668
|
+
// tree). aria-description carries the text itself, so there's no id, and nothing to fail to resolve.
|
|
669
|
+
_describeTrigger() {
|
|
512
670
|
const trigger = this._slot?.assignedElements()[0];
|
|
513
|
-
|
|
514
|
-
|
|
671
|
+
trigger?.setAttribute("aria-description", this.content);
|
|
672
|
+
}
|
|
673
|
+
_isShown() {
|
|
674
|
+
return this._tooltipEl?.matches(":popover-open") ?? false;
|
|
675
|
+
}
|
|
676
|
+
_sync() {
|
|
677
|
+
const shouldShow = (this._hovered || this._focused) && !this.disabled && !!this.content;
|
|
678
|
+
if (shouldShow) this._show();
|
|
679
|
+
else this._hide();
|
|
680
|
+
}
|
|
681
|
+
_show() {
|
|
682
|
+
if (!this._isShown()) {
|
|
683
|
+
this._tooltipEl.showPopover();
|
|
684
|
+
window.addEventListener("scroll", this._reposition, true);
|
|
685
|
+
window.addEventListener("resize", this._reposition);
|
|
515
686
|
}
|
|
687
|
+
this._reposition();
|
|
688
|
+
}
|
|
689
|
+
_hide() {
|
|
690
|
+
if (!this._isShown()) return;
|
|
691
|
+
this._tooltipEl.hidePopover();
|
|
692
|
+
window.removeEventListener("scroll", this._reposition, true);
|
|
693
|
+
window.removeEventListener("resize", this._reposition);
|
|
694
|
+
}
|
|
695
|
+
connectedCallback() {
|
|
696
|
+
super.connectedCallback();
|
|
697
|
+
this.addEventListener("pointerenter", this._onPointerEnter);
|
|
698
|
+
this.addEventListener("pointerleave", this._onPointerLeave);
|
|
699
|
+
this.addEventListener("focusin", this._onFocusIn);
|
|
700
|
+
this.addEventListener("focusout", this._onFocusOut);
|
|
701
|
+
}
|
|
702
|
+
disconnectedCallback() {
|
|
703
|
+
super.disconnectedCallback();
|
|
704
|
+
this.removeEventListener("pointerenter", this._onPointerEnter);
|
|
705
|
+
this.removeEventListener("pointerleave", this._onPointerLeave);
|
|
706
|
+
this.removeEventListener("focusin", this._onFocusIn);
|
|
707
|
+
this.removeEventListener("focusout", this._onFocusOut);
|
|
708
|
+
this._hovered = false;
|
|
709
|
+
this._focused = false;
|
|
710
|
+
this._hide();
|
|
711
|
+
}
|
|
712
|
+
firstUpdated() {
|
|
713
|
+
this._describeTrigger();
|
|
714
|
+
}
|
|
715
|
+
updated(changed) {
|
|
716
|
+
super.updated(changed);
|
|
717
|
+
if (changed.has("content")) this._describeTrigger();
|
|
718
|
+
if (changed.has("content") || changed.has("disabled")) this._sync();
|
|
719
|
+
else if (changed.has("placement")) this._reposition();
|
|
516
720
|
}
|
|
517
721
|
render() {
|
|
518
722
|
const tooltipStyle = styleMap({
|
|
@@ -521,7 +725,15 @@ var Tooltip = class extends ThemeableElement {
|
|
|
521
725
|
});
|
|
522
726
|
return html2`
|
|
523
727
|
<slot></slot>
|
|
524
|
-
<div
|
|
728
|
+
<div
|
|
729
|
+
class="tooltip"
|
|
730
|
+
id=${this._tooltipId}
|
|
731
|
+
popover="manual"
|
|
732
|
+
role="tooltip"
|
|
733
|
+
part="tooltip"
|
|
734
|
+
aria-hidden="true"
|
|
735
|
+
style=${tooltipStyle}
|
|
736
|
+
>
|
|
525
737
|
<lt-text variant="caption" as="span">${this.content}</lt-text>
|
|
526
738
|
<span class="arrow" part="arrow"></span>
|
|
527
739
|
</div>
|
|
@@ -532,6 +744,9 @@ Tooltip.styles = tooltipStyles;
|
|
|
532
744
|
__decorateClass([
|
|
533
745
|
query("slot")
|
|
534
746
|
], Tooltip.prototype, "_slot", 2);
|
|
747
|
+
__decorateClass([
|
|
748
|
+
query(".tooltip")
|
|
749
|
+
], Tooltip.prototype, "_tooltipEl", 2);
|
|
535
750
|
__decorateClass([
|
|
536
751
|
property3({ reflect: true })
|
|
537
752
|
], Tooltip.prototype, "content", 2);
|
|
@@ -555,6 +770,8 @@ Tooltip = __decorateClass([
|
|
|
555
770
|
var Slider = class extends ThemeableElement {
|
|
556
771
|
constructor() {
|
|
557
772
|
super();
|
|
773
|
+
this._default = new FormDefaultController(this, () => this.value);
|
|
774
|
+
this._fieldsetDisabled = new FormDisabledController(this);
|
|
558
775
|
this.size = "md";
|
|
559
776
|
this.disabled = false;
|
|
560
777
|
this.tooltip = false;
|
|
@@ -566,26 +783,44 @@ var Slider = class extends ThemeableElement {
|
|
|
566
783
|
this.name = "";
|
|
567
784
|
this._internals = this.attachInternals();
|
|
568
785
|
}
|
|
569
|
-
|
|
570
|
-
|
|
786
|
+
// A unitless 0–1 ratio, not a percentage — CSS derives both the track's fill percentage and the tooltip's
|
|
787
|
+
// thumb-following offset from it via calc() (SLD-1), so there's only one number to keep in sync.
|
|
788
|
+
get _fillRatio() {
|
|
789
|
+
if (this.max === this.min) return 0;
|
|
790
|
+
return (this.value - this.min) / (this.max - this.min);
|
|
791
|
+
}
|
|
792
|
+
/** Clamps to [min, max] and snaps to the nearest step from min, matching how a native <input type="range">
|
|
793
|
+
* sanitizes its own value — including when `value`/`min`/`max`/`step` are set programmatically, which a native
|
|
794
|
+
* range input clamps on its own internal value but this component didn't mirror onto its own `value` property
|
|
795
|
+
* (SLD-1: `value` wasn't clamped/stepped, so e.g. `value=150` with `max=100` produced a 150% fill and
|
|
796
|
+
* submitted "150" to the form even though the visible thumb was pinned at the end). */
|
|
797
|
+
_sanitize(value) {
|
|
798
|
+
if (this.min === this.max) return this.min;
|
|
799
|
+
const clamped = Math.min(this.max, Math.max(this.min, value));
|
|
800
|
+
if (this.step <= 0) return clamped;
|
|
801
|
+
const steps = Math.round((clamped - this.min) / this.step);
|
|
802
|
+
return Math.min(this.max, Math.max(this.min, this.min + steps * this.step));
|
|
571
803
|
}
|
|
572
|
-
|
|
573
|
-
|
|
804
|
+
willUpdate(changed) {
|
|
805
|
+
if (changed.has("value") || changed.has("min") || changed.has("max") || changed.has("step")) {
|
|
806
|
+
const sanitized = this._sanitize(this.value);
|
|
807
|
+
if (sanitized !== this.value) this.value = sanitized;
|
|
808
|
+
}
|
|
574
809
|
}
|
|
575
810
|
updated() {
|
|
576
|
-
|
|
577
|
-
this.style.setProperty("--_fill", `${fill}%`);
|
|
578
|
-
const trackW = this.input?.offsetWidth ?? 0;
|
|
579
|
-
if (trackW) {
|
|
580
|
-
const thumbPx = this._thumbSizePx();
|
|
581
|
-
const centerPx = fill / 100 * (trackW - thumbPx) + thumbPx / 2;
|
|
582
|
-
this.style.setProperty("--_thumb-left", `${centerPx / trackW * 100}%`);
|
|
583
|
-
}
|
|
811
|
+
this.style.setProperty("--_fill", String(this._fillRatio));
|
|
584
812
|
this._internals.setFormValue(String(this.value));
|
|
585
813
|
this._internals.setValidity({});
|
|
586
814
|
}
|
|
587
815
|
formResetCallback() {
|
|
588
|
-
this.value = this.
|
|
816
|
+
this.value = this._default.value;
|
|
817
|
+
}
|
|
818
|
+
formDisabledCallback(disabled) {
|
|
819
|
+
this._fieldsetDisabled.setFieldsetDisabled(disabled);
|
|
820
|
+
}
|
|
821
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
822
|
+
get _isDisabled() {
|
|
823
|
+
return this.disabled || this._fieldsetDisabled.fieldsetDisabled;
|
|
589
824
|
}
|
|
590
825
|
checkValidity() {
|
|
591
826
|
return this._internals.checkValidity();
|
|
@@ -626,7 +861,7 @@ var Slider = class extends ThemeableElement {
|
|
|
626
861
|
max=${this.max}
|
|
627
862
|
step=${this.step}
|
|
628
863
|
.value=${String(this.value)}
|
|
629
|
-
?disabled=${this.
|
|
864
|
+
?disabled=${this._isDisabled}
|
|
630
865
|
name=${this.name}
|
|
631
866
|
aria-label=${this.label || "slider"}
|
|
632
867
|
aria-valuemin=${this.min}
|
|
@@ -665,7 +900,7 @@ __decorateClass([
|
|
|
665
900
|
property4({ type: Number })
|
|
666
901
|
], Slider.prototype, "step", 2);
|
|
667
902
|
__decorateClass([
|
|
668
|
-
property4({ type: Number
|
|
903
|
+
property4({ type: Number })
|
|
669
904
|
], Slider.prototype, "value", 2);
|
|
670
905
|
__decorateClass([
|
|
671
906
|
property4({ reflect: true })
|
|
@@ -673,9 +908,6 @@ __decorateClass([
|
|
|
673
908
|
__decorateClass([
|
|
674
909
|
property4({ reflect: true })
|
|
675
910
|
], Slider.prototype, "name", 2);
|
|
676
|
-
__decorateClass([
|
|
677
|
-
query2("input")
|
|
678
|
-
], Slider.prototype, "input", 2);
|
|
679
911
|
Slider = __decorateClass([
|
|
680
912
|
customElement3("lt-slider")
|
|
681
913
|
], Slider);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PropertyValues } from 'lit';
|
|
1
2
|
import { ThemeableElement } from '../../base';
|
|
2
3
|
import type { SliderSize } from './slider.types';
|
|
3
4
|
import '../tooltip/tooltip';
|
|
@@ -13,6 +14,8 @@ export declare class Slider extends ThemeableElement {
|
|
|
13
14
|
static styles: import("lit").CSSResult;
|
|
14
15
|
static formAssociated: boolean;
|
|
15
16
|
private _internals;
|
|
17
|
+
private _default;
|
|
18
|
+
private _fieldsetDisabled;
|
|
16
19
|
constructor();
|
|
17
20
|
size: SliderSize;
|
|
18
21
|
disabled: boolean;
|
|
@@ -21,16 +24,26 @@ export declare class Slider extends ThemeableElement {
|
|
|
21
24
|
min: number;
|
|
22
25
|
max: number;
|
|
23
26
|
step: number;
|
|
27
|
+
/** Not reflected as an attribute — read the property, or the `change` event's `detail.value`, instead of
|
|
28
|
+
* `getAttribute('value')`/`[value]`. */
|
|
24
29
|
value: number;
|
|
25
30
|
/** Label displayed above the track */
|
|
26
31
|
label: string;
|
|
27
32
|
/** Name attribute for form submission */
|
|
28
33
|
name: string;
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
private get _fillRatio();
|
|
35
|
+
/** Clamps to [min, max] and snaps to the nearest step from min, matching how a native <input type="range">
|
|
36
|
+
* sanitizes its own value — including when `value`/`min`/`max`/`step` are set programmatically, which a native
|
|
37
|
+
* range input clamps on its own internal value but this component didn't mirror onto its own `value` property
|
|
38
|
+
* (SLD-1: `value` wasn't clamped/stepped, so e.g. `value=150` with `max=100` produced a 150% fill and
|
|
39
|
+
* submitted "150" to the form even though the visible thumb was pinned at the end). */
|
|
40
|
+
private _sanitize;
|
|
41
|
+
willUpdate(changed: PropertyValues): void;
|
|
32
42
|
updated(): void;
|
|
33
43
|
formResetCallback(): void;
|
|
44
|
+
formDisabledCallback(disabled: boolean): void;
|
|
45
|
+
/** `this.disabled` combined with an ancestor `<fieldset>`'s own disabled state (see `FormDisabledController`). */
|
|
46
|
+
private get _isDisabled();
|
|
34
47
|
checkValidity(): boolean;
|
|
35
48
|
reportValidity(): boolean;
|
|
36
49
|
private _onInput;
|