tyrell-components 1.0.0-TC58 → 1.0.0-TC59
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/css/tyrell-colors-static.css +14 -0
- package/css/tyrell-theme.css +1 -0
- package/css/tyrell.css +8 -6
- package/dist/tyrell-colors-static.css +1 -1
- package/dist/tyrell-theme.css +1 -1
- package/dist/tyrell.js +1 -1
- package/lib/base/ty-component.d.ts +8 -12
- package/lib/base/ty-component.js +38 -9
- package/lib/components/button.d.ts +10 -0
- package/lib/components/button.js +22 -1
- package/lib/components/calendar-month.d.ts +7 -1
- package/lib/components/calendar-month.js +32 -18
- package/lib/components/calendar-navigation.js +19 -6
- package/lib/components/calendar.d.ts +24 -5
- package/lib/components/calendar.js +83 -37
- package/lib/components/checkbox.d.ts +5 -0
- package/lib/components/checkbox.js +18 -5
- package/lib/components/copy.d.ts +2 -0
- package/lib/components/copy.js +15 -31
- package/lib/components/date-picker.d.ts +7 -26
- package/lib/components/date-picker.js +14 -84
- package/lib/components/file-upload.js +6 -2
- package/lib/components/input.d.ts +0 -9
- package/lib/components/input.js +17 -40
- package/lib/components/modal.js +59 -18
- package/lib/components/popup.js +56 -5
- package/lib/components/radio.d.ts +6 -0
- package/lib/components/radio.js +12 -28
- package/lib/components/select.d.ts +29 -11
- package/lib/components/select.js +43 -57
- package/lib/components/switch.d.ts +4 -0
- package/lib/components/switch.js +18 -5
- package/lib/components/tabs.js +1 -1
- package/lib/components/textarea.d.ts +1 -0
- package/lib/components/textarea.js +21 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/styles/button.js +1 -0
- package/lib/styles/calendar-month.js +1 -1
- package/lib/styles/calendar.js +3 -0
- package/lib/styles/checkbox.js +11 -5
- package/lib/styles/date-picker.js +5 -10
- package/lib/styles/input.js +5 -9
- package/lib/styles/option.d.ts +1 -1
- package/lib/styles/option.js +4 -0
- package/lib/styles/popup.d.ts +1 -1
- package/lib/styles/popup.js +2 -0
- package/lib/styles/radio.js +10 -4
- package/lib/styles/select-base.js +5 -10
- package/lib/styles/select.d.ts +1 -1
- package/lib/styles/select.js +4 -0
- package/lib/styles/switch.js +10 -5
- package/lib/styles/tag.js +2 -1
- package/lib/utils/calendar-utils.d.ts +49 -0
- package/lib/utils/calendar-utils.js +63 -6
- package/lib/utils/scroll-lock.d.ts +10 -4
- package/lib/utils/scroll-lock.js +102 -2
- package/lib/utils/validity.d.ts +27 -1
- package/lib/utils/validity.js +68 -16
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/dist/tyrell.js.map +0 -1
- package/lib/utils/error-flavor.d.ts +0 -8
- package/lib/utils/error-flavor.js +0 -1
|
@@ -3,7 +3,7 @@ import { liftProperties, upgradeProperties } from '../utils/upgrade-properties.j
|
|
|
3
3
|
import { calendarStyles } from '../styles/calendar.js';
|
|
4
4
|
import './calendar-month.js';
|
|
5
5
|
import './calendar-navigation.js';
|
|
6
|
-
import {
|
|
6
|
+
import { parseDateDay, formatISODay, toISODay, getMonthName, localMidnight, utcMidnight } from '../utils/calendar-utils.js';
|
|
7
7
|
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
8
8
|
import { ValidityController, nativeMessage } from '../utils/validity.js';
|
|
9
9
|
function getCurrentDate() {
|
|
@@ -32,14 +32,12 @@ const day = parseInt(dayStr, 10);
|
|
|
32
32
|
const daysInMonth = new Date(year, month, 0).getDate();
|
|
33
33
|
return day >= 1 && day <= daysInMonth ? day : null;
|
|
34
34
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
const dayStr = day.toString().padStart(2, '0');
|
|
38
|
-
return `${year}-${monthStr}-${dayStr}`;
|
|
35
|
+
function formatYear(year) {
|
|
36
|
+
return year.toString().padStart(4, '0');
|
|
39
37
|
}
|
|
40
38
|
export class TyCalendar extends HTMLElement {
|
|
41
39
|
static get observedAttributes() {
|
|
42
|
-
return ['value', 'year', 'month', 'day', 'hide-navigation', 'stateless', 'locale', 'name', 'size', 'width', 'min', 'max', 'flavor'];
|
|
40
|
+
return ['value', 'year', 'month', 'day', 'hide-navigation', 'stateless', 'locale', 'name', 'size', 'width', 'min', 'max', 'flavor', 'required'];
|
|
43
41
|
}
|
|
44
42
|
constructor() {
|
|
45
43
|
super();
|
|
@@ -51,12 +49,13 @@ this._view = 'days';
|
|
|
51
49
|
this._dayGridHeight = 0;
|
|
52
50
|
this._initialValue = '';
|
|
53
51
|
this._valueSets = 0;
|
|
52
|
+
this._pressing = false;
|
|
54
53
|
const current = getCurrentDate();
|
|
55
54
|
this._state = {
|
|
56
55
|
displayYear: current.year,
|
|
57
56
|
displayMonth: current.month,
|
|
58
57
|
};
|
|
59
|
-
this.attachShadow({ mode: 'open' });
|
|
58
|
+
this.attachShadow({ mode: 'open', delegatesFocus: true });
|
|
60
59
|
liftProperties(this);
|
|
61
60
|
if ('attachInternals' in this) {
|
|
62
61
|
this._internals = this.attachInternals();
|
|
@@ -132,6 +131,9 @@ this[name === 'min' ? '_min' : '_max'] = newValue || undefined;
|
|
|
132
131
|
this.syncChildComponents();
|
|
133
132
|
this.updateValidity();
|
|
134
133
|
break;
|
|
134
|
+
case 'required':
|
|
135
|
+
this.updateValidity();
|
|
136
|
+
break;
|
|
135
137
|
case 'flavor':
|
|
136
138
|
this._flavor = newValue || 'primary';
|
|
137
139
|
this.syncChildComponents();
|
|
@@ -165,6 +167,12 @@ this.setAttribute('flavor', value);
|
|
|
165
167
|
else
|
|
166
168
|
this.removeAttribute('flavor');
|
|
167
169
|
}
|
|
170
|
+
get required() {
|
|
171
|
+
return this.hasAttribute('required');
|
|
172
|
+
}
|
|
173
|
+
set required(value) {
|
|
174
|
+
this.toggleAttribute('required', !!value);
|
|
175
|
+
}
|
|
168
176
|
get year() {
|
|
169
177
|
return this._state.selectedYear;
|
|
170
178
|
}
|
|
@@ -172,12 +180,12 @@ set year(value) {
|
|
|
172
180
|
if (value !== undefined) {
|
|
173
181
|
if (this._stateless) {
|
|
174
182
|
this._state.displayYear = value;
|
|
175
|
-
this.setAttribute('year', value
|
|
183
|
+
this.setAttribute('year', formatYear(value));
|
|
176
184
|
}
|
|
177
185
|
else {
|
|
178
186
|
this._state.selectedYear = value;
|
|
179
187
|
this._state.displayYear = value;
|
|
180
|
-
this.setAttribute('year', value
|
|
188
|
+
this.setAttribute('year', formatYear(value));
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
191
|
else {
|
|
@@ -306,14 +314,14 @@ this.syncChildComponents();
|
|
|
306
314
|
get value() {
|
|
307
315
|
const { selectedYear, selectedMonth, selectedDay } = this._state;
|
|
308
316
|
if (selectedYear && selectedMonth && selectedDay) {
|
|
309
|
-
return
|
|
317
|
+
return formatISODay(selectedYear, selectedMonth, selectedDay);
|
|
310
318
|
}
|
|
311
319
|
return '';
|
|
312
320
|
}
|
|
313
321
|
set value(isoDate) {
|
|
314
322
|
const set = ++this._valueSets;
|
|
315
323
|
const superseded = () => set !== this._valueSets;
|
|
316
|
-
if (
|
|
324
|
+
if (isoDate === null || isoDate === undefined || isoDate === '') {
|
|
317
325
|
delete this._state.selectedYear;
|
|
318
326
|
delete this._state.selectedMonth;
|
|
319
327
|
delete this._state.selectedDay;
|
|
@@ -328,18 +336,16 @@ this.removeAttribute('day');
|
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
338
|
else {
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
333
|
-
const year =
|
|
334
|
-
const month = parseInt(match[2], 10);
|
|
335
|
-
const day = parseInt(match[3], 10);
|
|
339
|
+
const parsed = parseDateDay(isoDate);
|
|
340
|
+
if (parsed) {
|
|
341
|
+
const { year, month, day } = parsed;
|
|
336
342
|
this._state.selectedYear = year;
|
|
337
343
|
this._state.selectedMonth = month;
|
|
338
344
|
this._state.selectedDay = day;
|
|
339
345
|
this._state.displayYear = year;
|
|
340
346
|
this._state.displayMonth = month;
|
|
341
347
|
if (!this._stateless) {
|
|
342
|
-
this.setAttribute('year', year
|
|
348
|
+
this.setAttribute('year', formatYear(year));
|
|
343
349
|
if (superseded())
|
|
344
350
|
return;
|
|
345
351
|
this.setAttribute('month', month.toString());
|
|
@@ -455,13 +461,13 @@ this._monthDisplay.customCSS = this._customCSS;
|
|
|
455
461
|
}
|
|
456
462
|
const { selectedYear, selectedMonth, selectedDay } = this._state;
|
|
457
463
|
if (selectedYear && selectedMonth && selectedDay) {
|
|
458
|
-
|
|
459
|
-
this._monthDisplay.value = date.getTime();
|
|
464
|
+
this._monthDisplay.value = localMidnight(selectedYear, selectedMonth, selectedDay).getTime();
|
|
460
465
|
}
|
|
461
466
|
else {
|
|
462
467
|
this._monthDisplay.value = null;
|
|
463
468
|
}
|
|
464
469
|
}
|
|
470
|
+
this.updateValidity();
|
|
465
471
|
}
|
|
466
472
|
updateFormValue() {
|
|
467
473
|
if (this._stateless)
|
|
@@ -471,7 +477,7 @@ return;
|
|
|
471
477
|
const elementName = this.getAttribute('name');
|
|
472
478
|
const { selectedYear, selectedMonth, selectedDay } = this._state;
|
|
473
479
|
if (elementName && selectedYear && selectedMonth && selectedDay) {
|
|
474
|
-
const isoDate =
|
|
480
|
+
const isoDate = formatISODay(selectedYear, selectedMonth, selectedDay);
|
|
475
481
|
this._internals.setFormValue(isoDate);
|
|
476
482
|
}
|
|
477
483
|
else {
|
|
@@ -482,22 +488,27 @@ this.updateValidity();
|
|
|
482
488
|
updateValidity() {
|
|
483
489
|
if (!this._internals)
|
|
484
490
|
return;
|
|
491
|
+
const anchor = this.gridCell() ?? undefined;
|
|
485
492
|
const { selectedYear, selectedMonth, selectedDay } = this._state;
|
|
486
493
|
if (selectedYear && selectedMonth && selectedDay) {
|
|
487
|
-
const sel =
|
|
488
|
-
const min = this._min ?
|
|
489
|
-
const max = this._max ?
|
|
490
|
-
const message = () => nativeMessage('input', { type: 'date', min: this._min, max: this._max },
|
|
491
|
-
if (min && sel <
|
|
492
|
-
this._validity.set({ rangeUnderflow: true }, message());
|
|
494
|
+
const sel = utcMidnight(selectedYear, selectedMonth, selectedDay);
|
|
495
|
+
const min = this._min ? parseDateDay(this._min) : null;
|
|
496
|
+
const max = this._max ? parseDateDay(this._max) : null;
|
|
497
|
+
const message = () => nativeMessage('input', { type: 'date', min: toISODay(this._min) ?? undefined, max: toISODay(this._max) ?? undefined }, formatISODay(selectedYear, selectedMonth, selectedDay));
|
|
498
|
+
if (min && sel < utcMidnight(min.year, min.month, min.day)) {
|
|
499
|
+
this._validity.set({ rangeUnderflow: true }, message(), anchor);
|
|
493
500
|
return;
|
|
494
501
|
}
|
|
495
|
-
if (max && sel >
|
|
496
|
-
this._validity.set({ rangeOverflow: true }, message());
|
|
502
|
+
if (max && sel > utcMidnight(max.year, max.month, max.day)) {
|
|
503
|
+
this._validity.set({ rangeOverflow: true }, message(), anchor);
|
|
497
504
|
return;
|
|
498
505
|
}
|
|
499
506
|
}
|
|
500
|
-
this.
|
|
507
|
+
else if (this.required) {
|
|
508
|
+
this._validity.set({ valueMissing: true }, nativeMessage('input', { type: 'date', required: true }), anchor);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
this._validity.set({}, undefined, anchor);
|
|
501
512
|
}
|
|
502
513
|
get form() { return this._internals?.form ?? null; }
|
|
503
514
|
get validity() { return this._internals?.validity; }
|
|
@@ -547,7 +558,7 @@ this._state.selectedMonth = month;
|
|
|
547
558
|
this._state.selectedDay = day;
|
|
548
559
|
this._state.displayYear = year;
|
|
549
560
|
this._state.displayMonth = month;
|
|
550
|
-
this.setAttribute('year', year
|
|
561
|
+
this.setAttribute('year', formatYear(year));
|
|
551
562
|
this.setAttribute('month', month.toString());
|
|
552
563
|
this.setAttribute('day', day.toString());
|
|
553
564
|
this.updateFormValue();
|
|
@@ -572,6 +583,7 @@ this.syncChildComponents();
|
|
|
572
583
|
if (this._monthDisplay && typeof this._monthDisplay.refresh === 'function') {
|
|
573
584
|
this._monthDisplay.refresh();
|
|
574
585
|
}
|
|
586
|
+
this.updateValidity();
|
|
575
587
|
}
|
|
576
588
|
createNavigation() {
|
|
577
589
|
const nav = document.createElement('ty-calendar-navigation');
|
|
@@ -607,8 +619,7 @@ month.width = this._width;
|
|
|
607
619
|
}
|
|
608
620
|
const { selectedYear, selectedMonth, selectedDay } = this._state;
|
|
609
621
|
if (selectedYear && selectedMonth && selectedDay) {
|
|
610
|
-
|
|
611
|
-
month.value = date.getTime();
|
|
622
|
+
month.value = localMidnight(selectedYear, selectedMonth, selectedDay).getTime();
|
|
612
623
|
}
|
|
613
624
|
if (this._dayContentFn) {
|
|
614
625
|
month.dayContentFn = this._dayContentFn;
|
|
@@ -617,6 +628,7 @@ if (this._customCSS) {
|
|
|
617
628
|
month.customCSS = this._customCSS;
|
|
618
629
|
}
|
|
619
630
|
month.addEventListener('day-click', (e) => this.handleDayClick(e));
|
|
631
|
+
month.shadowRoot?.addEventListener('focusin', () => this.updateValidity());
|
|
620
632
|
this._monthDisplay = month;
|
|
621
633
|
return month;
|
|
622
634
|
}
|
|
@@ -629,7 +641,23 @@ root.innerHTML = '';
|
|
|
629
641
|
this._monthGrid = undefined;
|
|
630
642
|
const container = document.createElement('div');
|
|
631
643
|
container.className = 'calendar-container';
|
|
644
|
+
container.tabIndex = -1;
|
|
645
|
+
const press = () => {
|
|
646
|
+
this._pressing = true;
|
|
647
|
+
setTimeout(() => { this._pressing = false; });
|
|
648
|
+
};
|
|
649
|
+
container.addEventListener('pointerdown', press);
|
|
650
|
+
container.addEventListener('mousedown', press);
|
|
651
|
+
container.addEventListener('focus', () => {
|
|
652
|
+
if (this._pressing)
|
|
653
|
+
return;
|
|
654
|
+
queueMicrotask(() => {
|
|
655
|
+
if (this.shadowRoot?.activeElement === container)
|
|
656
|
+
this.gridCell()?.focus();
|
|
657
|
+
});
|
|
658
|
+
});
|
|
632
659
|
container.addEventListener('keydown', (e) => this.handleKeydown(e));
|
|
660
|
+
container.addEventListener('focusin', () => this.updateValidity());
|
|
633
661
|
this._container = container;
|
|
634
662
|
if (!this._hideNavigation) {
|
|
635
663
|
const nav = this.createNavigation();
|
|
@@ -640,6 +668,7 @@ container.appendChild(month);
|
|
|
640
668
|
root.appendChild(container);
|
|
641
669
|
if (this._view === 'months')
|
|
642
670
|
this.showBody();
|
|
671
|
+
this.updateValidity();
|
|
643
672
|
}
|
|
644
673
|
setView(view) {
|
|
645
674
|
if (view === this._view)
|
|
@@ -651,6 +680,7 @@ this._view = view;
|
|
|
651
680
|
if (this._navigation)
|
|
652
681
|
this._navigation.view = view;
|
|
653
682
|
this.showBody();
|
|
683
|
+
this.updateValidity();
|
|
654
684
|
}
|
|
655
685
|
showBody() {
|
|
656
686
|
const container = this._container;
|
|
@@ -676,8 +706,8 @@ static monthIndex(year, month) {
|
|
|
676
706
|
return year * 12 + (month - 1);
|
|
677
707
|
}
|
|
678
708
|
boundIndices() {
|
|
679
|
-
const min = this._min ?
|
|
680
|
-
const max = this._max ?
|
|
709
|
+
const min = this._min ? parseDateDay(this._min) : null;
|
|
710
|
+
const max = this._max ? parseDateDay(this._max) : null;
|
|
681
711
|
return {
|
|
682
712
|
minIdx: min ? TyCalendar.monthIndex(min.year, min.month) : null,
|
|
683
713
|
maxIdx: max ? TyCalendar.monthIndex(max.year, max.month) : null,
|
|
@@ -742,6 +772,11 @@ this.goTo(month, this._state.displayYear);
|
|
|
742
772
|
this.focusDayGrid();
|
|
743
773
|
}
|
|
744
774
|
handleKeydown(e) {
|
|
775
|
+
if (e.target === this._container && e.key.startsWith('Arrow')) {
|
|
776
|
+
e.preventDefault();
|
|
777
|
+
this.gridCell()?.focus();
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
745
780
|
if (e.key === 'Escape' && this._view === 'months') {
|
|
746
781
|
e.preventDefault();
|
|
747
782
|
e.stopPropagation();
|
|
@@ -777,10 +812,21 @@ requestAnimationFrame(() => {
|
|
|
777
812
|
this._navigation?.shadowRoot?.querySelector('.nav-title')?.focus();
|
|
778
813
|
});
|
|
779
814
|
}
|
|
815
|
+
gridCell() {
|
|
816
|
+
if (this._view === 'months') {
|
|
817
|
+
return this._monthGrid?.querySelector('.month-cell[tabindex="0"]:not(:disabled)')
|
|
818
|
+
?? this._monthGrid?.querySelector('.month-cell:not(:disabled)')
|
|
819
|
+
?? this._navigation?.shadowRoot?.querySelector('.nav-title')
|
|
820
|
+
?? null;
|
|
821
|
+
}
|
|
822
|
+
const root = this._monthDisplay?.shadowRoot;
|
|
823
|
+
return root?.querySelector('.calendar-day-cell[tabindex="0"]')
|
|
824
|
+
?? root?.querySelector('.calendar-day-cell.selected')
|
|
825
|
+
?? root?.querySelector('.calendar-day-cell')
|
|
826
|
+
?? null;
|
|
827
|
+
}
|
|
780
828
|
focus(options) {
|
|
781
|
-
const cell = this.
|
|
782
|
-
? this._monthGrid?.querySelector('.month-cell[tabindex="0"]')
|
|
783
|
-
: this._monthDisplay?.shadowRoot?.querySelector('.calendar-day-cell[tabindex="0"]');
|
|
829
|
+
const cell = this.gridCell();
|
|
784
830
|
if (cell)
|
|
785
831
|
cell.focus(options);
|
|
786
832
|
else
|
|
@@ -32,6 +32,8 @@ export interface TyCheckboxElement extends HTMLElement {
|
|
|
32
32
|
*/
|
|
33
33
|
export declare class TyCheckbox extends TyComponent<CheckboxState> implements TyCheckboxElement {
|
|
34
34
|
static formAssociated: boolean;
|
|
35
|
+
protected static delegatesFocus: boolean;
|
|
36
|
+
protected static labelClicks: boolean;
|
|
35
37
|
protected static properties: {
|
|
36
38
|
checked: {
|
|
37
39
|
type: "boolean";
|
|
@@ -98,6 +100,9 @@ export declare class TyCheckbox extends TyComponent<CheckboxState> implements Ty
|
|
|
98
100
|
protected getFormValue(): FormDataEntryValue | null;
|
|
99
101
|
private buildClassList;
|
|
100
102
|
private handleCheckboxClick;
|
|
103
|
+
/** The ring is for keyboard use: keys show it, a click hides it — also a
|
|
104
|
+
* <label>'s, which in Chromium focuses the box as :focus-visible. */
|
|
105
|
+
private showFocusRing;
|
|
101
106
|
private handleCheckboxKeydown;
|
|
102
107
|
private removeEventListeners;
|
|
103
108
|
/**
|
|
@@ -21,6 +21,9 @@ ensureStyles(shadow, { css: checkboxStyles, id: "ty-checkbox" });
|
|
|
21
21
|
onConnect() {
|
|
22
22
|
this.setupEventListeners();
|
|
23
23
|
this._syncCustomFlavor();
|
|
24
|
+
const el = this.shadowRoot.querySelector(".checkbox-container");
|
|
25
|
+
if (el && this.shadowRoot.activeElement !== el)
|
|
26
|
+
el.classList.remove("focused");
|
|
24
27
|
}
|
|
25
28
|
_syncCustomFlavor() {
|
|
26
29
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.flavor, ({ base }) => checkboxCustomFlavorCss(base));
|
|
@@ -47,8 +50,7 @@ return classes.join(" ");
|
|
|
47
50
|
handleCheckboxClick(e) {
|
|
48
51
|
if (this.disabled)
|
|
49
52
|
return;
|
|
50
|
-
|
|
51
|
-
return;
|
|
53
|
+
this.showFocusRing(e instanceof KeyboardEvent);
|
|
52
54
|
const newValue = this.indeterminate ? true : !this.checked;
|
|
53
55
|
if (this.indeterminate)
|
|
54
56
|
this.indeterminate = false;
|
|
@@ -72,6 +74,9 @@ composed: true,
|
|
|
72
74
|
}));
|
|
73
75
|
}, 0);
|
|
74
76
|
}
|
|
77
|
+
showFocusRing(on) {
|
|
78
|
+
this.shadowRoot?.querySelector(".checkbox-container")?.classList.toggle("focused", on);
|
|
79
|
+
}
|
|
75
80
|
handleCheckboxKeydown(e) {
|
|
76
81
|
if (this.disabled)
|
|
77
82
|
return;
|
|
@@ -124,7 +129,7 @@ this._keydownHandler = (e) => {
|
|
|
124
129
|
this.handleCheckboxKeydown(e);
|
|
125
130
|
};
|
|
126
131
|
this._focusHandler = () => {
|
|
127
|
-
checkboxEl.classList.
|
|
132
|
+
checkboxEl.classList.toggle("focused", checkboxEl.matches(":focus-visible"));
|
|
128
133
|
};
|
|
129
134
|
this._blurHandler = () => {
|
|
130
135
|
checkboxEl.classList.remove("focused");
|
|
@@ -143,7 +148,8 @@ const ariaChecked = this.indeterminate ? "mixed" : String(this.checked);
|
|
|
143
148
|
if (!checkboxEl) {
|
|
144
149
|
checkboxEl = document.createElement("div");
|
|
145
150
|
checkboxEl.className = "checkbox-container " + classes;
|
|
146
|
-
|
|
151
|
+
if (!this.disabled)
|
|
152
|
+
checkboxEl.tabIndex = 0;
|
|
147
153
|
checkboxEl.setAttribute("role", "checkbox");
|
|
148
154
|
checkboxEl.setAttribute("aria-checked", ariaChecked);
|
|
149
155
|
checkboxEl.setAttribute("aria-disabled", String(this.disabled));
|
|
@@ -154,8 +160,13 @@ checkboxEl.appendChild(checkboxIcon);
|
|
|
154
160
|
shadow.appendChild(checkboxEl);
|
|
155
161
|
}
|
|
156
162
|
else {
|
|
163
|
+
const focused = checkboxEl.classList.contains("focused") && shadow.activeElement === checkboxEl;
|
|
157
164
|
checkboxEl.className = "checkbox-container " + classes;
|
|
158
|
-
checkboxEl.
|
|
165
|
+
checkboxEl.classList.toggle("focused", focused && !this.disabled);
|
|
166
|
+
if (this.disabled)
|
|
167
|
+
checkboxEl.removeAttribute("tabindex");
|
|
168
|
+
else
|
|
169
|
+
checkboxEl.tabIndex = 0;
|
|
159
170
|
checkboxEl.setAttribute("aria-checked", ariaChecked);
|
|
160
171
|
checkboxEl.setAttribute("aria-disabled", String(this.disabled));
|
|
161
172
|
checkboxEl.setAttribute("aria-required", String(this.required));
|
|
@@ -239,6 +250,8 @@ return this._internals.form;
|
|
|
239
250
|
}
|
|
240
251
|
}
|
|
241
252
|
TyCheckbox.formAssociated = true;
|
|
253
|
+
TyCheckbox.delegatesFocus = true;
|
|
254
|
+
TyCheckbox.labelClicks = true;
|
|
242
255
|
TyCheckbox.properties = {
|
|
243
256
|
checked: {
|
|
244
257
|
type: "boolean",
|
package/lib/components/copy.d.ts
CHANGED
|
@@ -109,6 +109,8 @@ export declare class TyCopy extends TyComponent<CopyState> implements TyCopyElem
|
|
|
109
109
|
* Uses state flag so render() can show the correct icon
|
|
110
110
|
*/
|
|
111
111
|
private showCopySuccess;
|
|
112
|
+
/** Feedback over: the copy icon again. */
|
|
113
|
+
private resetCopyButton;
|
|
112
114
|
/**
|
|
113
115
|
* Show copy error animation
|
|
114
116
|
* Swaps copy icon → red X icon → copy icon
|
package/lib/components/copy.js
CHANGED
|
@@ -32,7 +32,7 @@ this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFla
|
|
|
32
32
|
onDisconnect() {
|
|
33
33
|
if (this._copyTimeout !== null) {
|
|
34
34
|
clearTimeout(this._copyTimeout);
|
|
35
|
-
this.
|
|
35
|
+
this.resetCopyButton();
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
onPropertiesChanged(changes) {
|
|
@@ -61,15 +61,15 @@ composed: true
|
|
|
61
61
|
}));
|
|
62
62
|
}
|
|
63
63
|
catch (err) {
|
|
64
|
-
|
|
64
|
+
const focused = this.shadowRoot.activeElement;
|
|
65
65
|
const textarea = document.createElement('textarea');
|
|
66
|
+
try {
|
|
66
67
|
textarea.value = value;
|
|
67
68
|
textarea.style.position = 'fixed';
|
|
68
69
|
textarea.style.opacity = '0';
|
|
69
70
|
document.body.appendChild(textarea);
|
|
70
71
|
textarea.select();
|
|
71
72
|
document.execCommand('copy');
|
|
72
|
-
document.body.removeChild(textarea);
|
|
73
73
|
this.dispatchEvent(new CustomEvent('copy-success', {
|
|
74
74
|
detail: { value },
|
|
75
75
|
bubbles: true,
|
|
@@ -86,6 +86,10 @@ composed: true
|
|
|
86
86
|
this.showCopyError();
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
|
+
finally {
|
|
90
|
+
textarea.remove();
|
|
91
|
+
focused?.focus();
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
94
|
this.showCopySuccess();
|
|
91
95
|
}
|
|
@@ -100,16 +104,17 @@ copyButton.classList.add('success');
|
|
|
100
104
|
copyButton.innerHTML = CHECK_ICON_SVG;
|
|
101
105
|
copyButton.setAttribute('aria-label', 'Copied!');
|
|
102
106
|
}
|
|
103
|
-
this._copyTimeout = window.setTimeout(() =>
|
|
107
|
+
this._copyTimeout = window.setTimeout(() => this.resetCopyButton(), 2000);
|
|
108
|
+
}
|
|
109
|
+
resetCopyButton() {
|
|
104
110
|
this._showingSuccess = false;
|
|
105
111
|
const btn = this.shadowRoot.querySelector('.copy-button');
|
|
106
112
|
if (btn) {
|
|
107
|
-
btn.classList.remove('success');
|
|
113
|
+
btn.classList.remove('success', 'error');
|
|
108
114
|
btn.innerHTML = COPY_ICON_SVG;
|
|
109
115
|
btn.setAttribute('aria-label', 'Copy to clipboard');
|
|
110
116
|
}
|
|
111
117
|
this._copyTimeout = null;
|
|
112
|
-
}, 2000);
|
|
113
118
|
}
|
|
114
119
|
showCopyError() {
|
|
115
120
|
if (this._copyTimeout !== null) {
|
|
@@ -122,34 +127,13 @@ copyButton.classList.add('error');
|
|
|
122
127
|
copyButton.innerHTML = ERROR_ICON_SVG;
|
|
123
128
|
copyButton.setAttribute('aria-label', 'Copy failed');
|
|
124
129
|
}
|
|
125
|
-
this._copyTimeout = window.setTimeout(() =>
|
|
126
|
-
const btn = this.shadowRoot.querySelector('.copy-button');
|
|
127
|
-
if (btn) {
|
|
128
|
-
btn.classList.remove('error');
|
|
129
|
-
btn.innerHTML = COPY_ICON_SVG;
|
|
130
|
-
btn.setAttribute('aria-label', 'Copy to clipboard');
|
|
131
|
-
}
|
|
132
|
-
this._copyTimeout = null;
|
|
133
|
-
}, 2000);
|
|
130
|
+
this._copyTimeout = window.setTimeout(() => this.resetCopyButton(), 2000);
|
|
134
131
|
}
|
|
135
132
|
setupEventListeners() {
|
|
136
|
-
const
|
|
137
|
-
const inputWrapper = shadow.querySelector('.input-wrapper');
|
|
138
|
-
const copyButton = shadow.querySelector('.copy-button');
|
|
133
|
+
const inputWrapper = this.shadowRoot.querySelector('.input-wrapper');
|
|
139
134
|
if (inputWrapper && !this.disabled) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (e.target === copyButton || e.target.closest('.copy-button')) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
this.copyToClipboard();
|
|
146
|
-
});
|
|
147
|
-
wrapperEl.style.cursor = 'pointer';
|
|
148
|
-
}
|
|
149
|
-
if (copyButton) {
|
|
150
|
-
copyButton.addEventListener('click', () => {
|
|
151
|
-
this.copyToClipboard();
|
|
152
|
-
});
|
|
135
|
+
inputWrapper.addEventListener('click', () => this.copyToClipboard());
|
|
136
|
+
inputWrapper.style.cursor = 'pointer';
|
|
153
137
|
}
|
|
154
138
|
}
|
|
155
139
|
render() {
|
|
@@ -8,22 +8,11 @@ import { type Placement } from '../utils/positioning.js';
|
|
|
8
8
|
import { TyComponent } from '../base/ty-component.js';
|
|
9
9
|
import type { Flavor } from '../types/common.js';
|
|
10
10
|
import type { PropertyChange } from '../utils/property-manager.js';
|
|
11
|
-
|
|
12
|
-
* Date components (internal representation in local timezone)
|
|
13
|
-
*/
|
|
14
|
-
interface DateComponents {
|
|
15
|
-
year?: number;
|
|
16
|
-
month?: number;
|
|
17
|
-
day?: number;
|
|
18
|
-
hour?: number;
|
|
19
|
-
minute?: number;
|
|
20
|
-
second?: number;
|
|
21
|
-
epoch?: number;
|
|
22
|
-
}
|
|
11
|
+
import { type DateTimeParts } from '../utils/calendar-utils.js';
|
|
23
12
|
/**
|
|
24
13
|
* Internal date picker state
|
|
25
14
|
*/
|
|
26
|
-
interface DatePickerState extends
|
|
15
|
+
interface DatePickerState extends DateTimeParts {
|
|
27
16
|
withTime: boolean;
|
|
28
17
|
open: boolean;
|
|
29
18
|
}
|
|
@@ -193,14 +182,6 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
|
193
182
|
* Compares before setting to prevent circular triggers
|
|
194
183
|
*/
|
|
195
184
|
private syncFormValue;
|
|
196
|
-
/**
|
|
197
|
-
* `error` turns an unflavored field ('default'/'neutral') danger, like
|
|
198
|
-
* ty-input; clearing it puts back exactly the flavor attribute the author
|
|
199
|
-
* had, or none, kept meanwhile in ERROR_FLAVOR. Runs once connected: while
|
|
200
|
-
* the element upgrades, `error` may come before the `flavor` written next
|
|
201
|
-
* to it.
|
|
202
|
-
*/
|
|
203
|
-
private syncErrorFlavor;
|
|
204
185
|
/**
|
|
205
186
|
* Handle time changes from the footer's hour/minute fields
|
|
206
187
|
*/
|
|
@@ -214,7 +195,6 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
|
214
195
|
set placement(v: Placement | '');
|
|
215
196
|
get flavor(): DatePickerFlavor;
|
|
216
197
|
set flavor(v: DatePickerFlavor);
|
|
217
|
-
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
218
198
|
get label(): string;
|
|
219
199
|
set label(v: string);
|
|
220
200
|
get placeholder(): string;
|
|
@@ -248,7 +228,7 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
|
248
228
|
*/
|
|
249
229
|
get value(): string | null;
|
|
250
230
|
/**
|
|
251
|
-
* Set value: any format
|
|
231
|
+
* Set value: any format parseDateTime() reads, a Date, epoch milliseconds,
|
|
252
232
|
* or null. It's normalized to the submitted form.
|
|
253
233
|
*
|
|
254
234
|
* When set to null/undefined/empty string, the attribute is removed.
|
|
@@ -268,9 +248,10 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
|
268
248
|
private renderTimeSection;
|
|
269
249
|
private renderCalendarDropdown;
|
|
270
250
|
/**
|
|
271
|
-
* Match the popup calendar to the stub's
|
|
272
|
-
*
|
|
273
|
-
* state, not a real flavor: the
|
|
251
|
+
* Match the popup calendar to the stub's look: danger while an error
|
|
252
|
+
* shows, as on the stub (styles: errorLook), else the user's flavor.
|
|
253
|
+
* 'default' is the stub's own unstyled state, not a real flavor: the
|
|
254
|
+
* calendar stays on its own default.
|
|
274
255
|
*/
|
|
275
256
|
private calendarFlavor;
|
|
276
257
|
/**
|