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
|
@@ -17,6 +17,7 @@ export interface TyRadioElement extends HTMLElement {
|
|
|
17
17
|
flavor: Flavor;
|
|
18
18
|
}
|
|
19
19
|
export declare class TyRadio extends TyComponent<RadioState> implements TyRadioElement {
|
|
20
|
+
protected static labelClicks: boolean;
|
|
20
21
|
protected static properties: {
|
|
21
22
|
value: {
|
|
22
23
|
type: "string";
|
|
@@ -169,6 +170,11 @@ export declare class TyRadioGroup extends TyComponent<RadioGroupState> implement
|
|
|
169
170
|
radio's; it needs a name, a nameless radio never reports valueMissing. */
|
|
170
171
|
private updateValidity;
|
|
171
172
|
private handleRadioSelect;
|
|
173
|
+
/**
|
|
174
|
+
* Select a value as the user did: input + change, like a native radio,
|
|
175
|
+
* whether by click, arrow keys or Space. Nothing fires when it is already selected.
|
|
176
|
+
*/
|
|
177
|
+
private select;
|
|
172
178
|
/**
|
|
173
179
|
* Arrow-key navigation within group (W3C radio practice: arrow keys move
|
|
174
180
|
* focus and selection together).
|
package/lib/components/radio.js
CHANGED
|
@@ -134,6 +134,7 @@ set size(v) { this.setProperty("size", v); }
|
|
|
134
134
|
get flavor() { return this.getProperty("flavor"); }
|
|
135
135
|
set flavor(v) { this.setProperty("flavor", v); }
|
|
136
136
|
}
|
|
137
|
+
TyRadio.labelClicks = true;
|
|
137
138
|
TyRadio.properties = {
|
|
138
139
|
value: { type: "string", default: "" },
|
|
139
140
|
checked: {
|
|
@@ -237,16 +238,17 @@ const detail = e.detail;
|
|
|
237
238
|
const newValue = detail?.value;
|
|
238
239
|
if (typeof newValue !== "string" || newValue === this.value)
|
|
239
240
|
return;
|
|
240
|
-
this.
|
|
241
|
+
this.select(newValue, e);
|
|
242
|
+
}
|
|
243
|
+
select(value, originalEvent) {
|
|
244
|
+
if (value === this.value)
|
|
245
|
+
return;
|
|
246
|
+
this.value = value;
|
|
241
247
|
this.syncChildren();
|
|
248
|
+
const detail = { value, formValue: value, originalEvent };
|
|
242
249
|
setTimeout(() => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
formValue: newValue,
|
|
246
|
-
originalEvent: e,
|
|
247
|
-
};
|
|
248
|
-
this.dispatchEvent(new CustomEvent("input", { detail: eventDetail, bubbles: true, composed: true }));
|
|
249
|
-
this.dispatchEvent(new CustomEvent("change", { detail: eventDetail, bubbles: true, composed: true }));
|
|
250
|
+
this.dispatchEvent(new CustomEvent("input", { detail, bubbles: true, composed: true }));
|
|
251
|
+
this.dispatchEvent(new CustomEvent("change", { detail, bubbles: true, composed: true }));
|
|
250
252
|
}, 0);
|
|
251
253
|
}
|
|
252
254
|
handleKeydown(e) {
|
|
@@ -271,16 +273,7 @@ nextIndex = current
|
|
|
271
273
|
else if (["Space", " ", "Enter"].includes(e.key)) {
|
|
272
274
|
if (current) {
|
|
273
275
|
e.preventDefault();
|
|
274
|
-
this.
|
|
275
|
-
this.syncChildren();
|
|
276
|
-
const eventDetail = {
|
|
277
|
-
value: current.value,
|
|
278
|
-
formValue: current.value,
|
|
279
|
-
originalEvent: e,
|
|
280
|
-
};
|
|
281
|
-
setTimeout(() => {
|
|
282
|
-
this.dispatchEvent(new CustomEvent("change", { detail: eventDetail, bubbles: true, composed: true }));
|
|
283
|
-
}, 0);
|
|
276
|
+
this.select(current.value, e);
|
|
284
277
|
}
|
|
285
278
|
return;
|
|
286
279
|
}
|
|
@@ -290,16 +283,7 @@ return;
|
|
|
290
283
|
const next = radios[nextIndex];
|
|
291
284
|
if (next) {
|
|
292
285
|
next.focus();
|
|
293
|
-
this.
|
|
294
|
-
this.syncChildren();
|
|
295
|
-
const eventDetail = {
|
|
296
|
-
value: next.value,
|
|
297
|
-
formValue: next.value,
|
|
298
|
-
originalEvent: e,
|
|
299
|
-
};
|
|
300
|
-
setTimeout(() => {
|
|
301
|
-
this.dispatchEvent(new CustomEvent("change", { detail: eventDetail, bubbles: true, composed: true }));
|
|
302
|
-
}, 0);
|
|
286
|
+
this.select(next.value, e);
|
|
303
287
|
}
|
|
304
288
|
}
|
|
305
289
|
removeEventListeners() {
|
|
@@ -196,18 +196,12 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
196
196
|
private _selectedClone;
|
|
197
197
|
private _customFlavorSheet;
|
|
198
198
|
private _ariaTrigger;
|
|
199
|
+
private _ariaObserver;
|
|
199
200
|
constructor();
|
|
200
201
|
/** TyComponent handles property capture automatically. */
|
|
201
202
|
protected onConnect(): void;
|
|
202
203
|
/** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
|
|
203
204
|
private _syncCustomFlavor;
|
|
204
|
-
/**
|
|
205
|
-
* `error` turns a neutral select danger, like ty-input; clearing it puts
|
|
206
|
-
* back exactly the flavor attribute the author had, or none, kept meanwhile
|
|
207
|
-
* in ERROR_FLAVOR. Runs once connected: while the element upgrades, `error`
|
|
208
|
-
* may come before the `flavor` written next to it.
|
|
209
|
-
*/
|
|
210
|
-
private syncErrorFlavor;
|
|
211
205
|
protected onDisconnect(): void;
|
|
212
206
|
/** Handle state synchronization BEFORE render. */
|
|
213
207
|
protected onPropertiesChanged(changes: PropertyChange[]): void;
|
|
@@ -220,7 +214,11 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
220
214
|
private applyLoadingState;
|
|
221
215
|
/** Form value: a plain entry for single, FormData with repeated entries for multiple (HTMX standard). */
|
|
222
216
|
protected getFormValue(): FormDataEntryValue | FormData | null;
|
|
223
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Focus the trigger: a slotted one, else the field. The host itself isn't
|
|
219
|
+
* focusable. A slotted trigger that can't take focus (a <span>, a disabled
|
|
220
|
+
* button) leaves it to the field.
|
|
221
|
+
*/
|
|
224
222
|
focus(options?: FocusOptions): void;
|
|
225
223
|
private parseValue;
|
|
226
224
|
/** Initialize component state from the value property / pre-selected children. */
|
|
@@ -252,6 +250,12 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
252
250
|
* Get all option elements from the component (ty-option preferred, ty-tag accepted)
|
|
253
251
|
*/
|
|
254
252
|
private getTagElements;
|
|
253
|
+
/**
|
|
254
|
+
* The options on offer: all but the ones the author hid (`hidden`, like a
|
|
255
|
+
* native <option hidden>). The search filters these and the keyboard walks
|
|
256
|
+
* them; a hidden option can still be the value.
|
|
257
|
+
*/
|
|
258
|
+
private getOfferedTags;
|
|
255
259
|
private getTagData;
|
|
256
260
|
/**
|
|
257
261
|
* Select an option - attribute only. The option stays in the list with
|
|
@@ -350,11 +354,17 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
350
354
|
/**
|
|
351
355
|
* Next tag index in `direction` (-1/1) that isn't disabled, wrapping
|
|
352
356
|
* around — mirrors handleTagClick's disabled check so keyboard nav can
|
|
353
|
-
* never land on/select what a click already refuses to.
|
|
354
|
-
*
|
|
357
|
+
* never land on/select what a click already refuses to. Nor on one out of
|
|
358
|
+
* sight: `tags` is the list as it was offered, and an option can get
|
|
359
|
+
* `hidden` while it's open (a server patch). Returns -1 if every tag is
|
|
360
|
+
* skipped (or there are none).
|
|
355
361
|
*/
|
|
356
362
|
private nextEnabledTagIndex;
|
|
357
363
|
private filterTags;
|
|
364
|
+
/**
|
|
365
|
+
* Search filtering marks the options it leaves out `filtered-out`, never
|
|
366
|
+
* `hidden`: that one is the author's (see getOfferedTags).
|
|
367
|
+
*/
|
|
358
368
|
private updateTagVisibility;
|
|
359
369
|
private updateOptionsVisibility;
|
|
360
370
|
private clearHighlights;
|
|
@@ -378,6 +388,15 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
378
388
|
* `compact` after connect never showed up.
|
|
379
389
|
*/
|
|
380
390
|
private syncFieldChrome;
|
|
391
|
+
/**
|
|
392
|
+
* A slotted trigger is what takes focus (focus()), so it carries the error
|
|
393
|
+
* state too. aria-describedby can't reach into our shadow root from the
|
|
394
|
+
* light DOM: the message goes in aria-description instead. The trigger is
|
|
395
|
+
* the author's markup, so a morph can strip these while the error stays:
|
|
396
|
+
* a watch on them puts them back. Writes only on a real change, or the
|
|
397
|
+
* watch would feed itself.
|
|
398
|
+
*/
|
|
399
|
+
private syncTriggerAria;
|
|
381
400
|
/**
|
|
382
401
|
* Reflect constraint validation to the owning <form> via ElementInternals.
|
|
383
402
|
* Without this `required` is only cosmetic — form.checkValidity() (and a
|
|
@@ -511,7 +530,6 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
511
530
|
set placement(value: Placement | "");
|
|
512
531
|
get flavor(): Flavor;
|
|
513
532
|
set flavor(value: Flavor);
|
|
514
|
-
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
515
533
|
get form(): HTMLFormElement | null;
|
|
516
534
|
}
|
|
517
535
|
export {};
|
package/lib/components/select.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_SIZE } from "../types/common.js";
|
|
2
2
|
import { ensureStyles } from "../utils/styles.js";
|
|
3
3
|
import { syncCustomFlavorSheet } from "../utils/flavor-sheet.js";
|
|
4
|
-
import { ERROR_FLAVOR } from "../utils/error-flavor.js";
|
|
5
4
|
import { selectBaseStyles, selectCustomFlavorCss } from "../styles/select-base.js";
|
|
6
5
|
import { selectStyles } from "../styles/select.js";
|
|
7
6
|
import { getLoaderSvg } from "../utils/loader-registry.js";
|
|
@@ -84,6 +83,7 @@ this._childObserver = null;
|
|
|
84
83
|
this._selectedClone = null;
|
|
85
84
|
this._customFlavorSheet = null;
|
|
86
85
|
this._ariaTrigger = null;
|
|
86
|
+
this._ariaObserver = null;
|
|
87
87
|
const shadow = this.shadowRoot;
|
|
88
88
|
ensureStyles(shadow, { css: selectBaseStyles, id: "ty-select-base" });
|
|
89
89
|
ensureStyles(shadow, { css: selectStyles, id: "ty-select" });
|
|
@@ -112,7 +112,6 @@ this.updateMobileSelectedState();
|
|
|
112
112
|
this._childObserver.observe(this, { childList: true });
|
|
113
113
|
this.ensureOptionRoles();
|
|
114
114
|
this._syncCustomFlavor();
|
|
115
|
-
this.syncErrorFlavor();
|
|
116
115
|
if (!this._keyboardHandler) {
|
|
117
116
|
this._keyboardHandler = this.handleKeyboard.bind(this);
|
|
118
117
|
document.addEventListener("keydown", this._keyboardHandler, true);
|
|
@@ -121,27 +120,6 @@ document.addEventListener("keydown", this._keyboardHandler, true);
|
|
|
121
120
|
_syncCustomFlavor() {
|
|
122
121
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.getProperty("flavor"), ({ base }) => selectCustomFlavorCss(base));
|
|
123
122
|
}
|
|
124
|
-
syncErrorFlavor() {
|
|
125
|
-
if (!this._connected)
|
|
126
|
-
return;
|
|
127
|
-
const flavor = this.getProperty("flavor");
|
|
128
|
-
const replaced = this.getAttribute(ERROR_FLAVOR);
|
|
129
|
-
if (this.getProperty("error")) {
|
|
130
|
-
if (replaced === null && flavor === "neutral") {
|
|
131
|
-
this.setAttribute(ERROR_FLAVOR, this.getAttribute("flavor") ?? "");
|
|
132
|
-
this.setProperty("flavor", "danger");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else if (replaced !== null) {
|
|
136
|
-
this.removeAttribute(ERROR_FLAVOR);
|
|
137
|
-
if (flavor !== "danger")
|
|
138
|
-
return;
|
|
139
|
-
if (!replaced)
|
|
140
|
-
this.removeAttribute("flavor");
|
|
141
|
-
else
|
|
142
|
-
this.setProperty("flavor", replaced);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
123
|
onDisconnect() {
|
|
146
124
|
if (this._state.open) {
|
|
147
125
|
if (this._state.mode === "mobile")
|
|
@@ -186,15 +164,8 @@ case "name":
|
|
|
186
164
|
this._name = newValue || "";
|
|
187
165
|
break;
|
|
188
166
|
case "flavor":
|
|
189
|
-
if (newValue !== "danger") {
|
|
190
|
-
this.removeAttribute(ERROR_FLAVOR);
|
|
191
|
-
this.syncErrorFlavor();
|
|
192
|
-
}
|
|
193
167
|
this._syncCustomFlavor();
|
|
194
168
|
break;
|
|
195
|
-
case "error":
|
|
196
|
-
this.syncErrorFlavor();
|
|
197
|
-
break;
|
|
198
169
|
case "multiple":
|
|
199
170
|
this._multiple = newValue;
|
|
200
171
|
if (!newValue && this._state.selectedValues.length > 1) {
|
|
@@ -301,10 +272,15 @@ focus(options) {
|
|
|
301
272
|
if (this._disabled)
|
|
302
273
|
return;
|
|
303
274
|
const trigger = this.querySelector(':scope > [slot="trigger"]');
|
|
275
|
+
if (trigger) {
|
|
276
|
+
trigger.focus(options);
|
|
277
|
+
const active = trigger.getRootNode().activeElement;
|
|
278
|
+
if (active && trigger.contains(active))
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
304
281
|
const stub = this.shadowRoot?.querySelector(".select-stub");
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
target.focus(options);
|
|
282
|
+
if (stub)
|
|
283
|
+
stub.focus(options);
|
|
308
284
|
else
|
|
309
285
|
super.focus(options);
|
|
310
286
|
}
|
|
@@ -348,6 +324,11 @@ this.resolveDeclarativeSelection(); }, { once: true });
|
|
|
348
324
|
getTagElements() {
|
|
349
325
|
return Array.from(this.querySelectorAll("ty-option:not([cloned]), ty-tag:not([cloned])"));
|
|
350
326
|
}
|
|
327
|
+
getOfferedTags() {
|
|
328
|
+
return this.getTagElements()
|
|
329
|
+
.filter((el) => !el.hasAttribute("hidden"))
|
|
330
|
+
.map((el) => this.getTagData(el));
|
|
331
|
+
}
|
|
351
332
|
getTagData(element) {
|
|
352
333
|
const value = element.value ||
|
|
353
334
|
element.getAttribute("value") ||
|
|
@@ -514,8 +495,7 @@ chevron.classList.add("open");
|
|
|
514
495
|
const searchChevron = shadow.querySelector(".dropdown-search-chevron");
|
|
515
496
|
if (searchChevron)
|
|
516
497
|
searchChevron.classList.add("open");
|
|
517
|
-
|
|
518
|
-
this._state.filteredTags = tags;
|
|
498
|
+
this._state.filteredTags = this.getOfferedTags();
|
|
519
499
|
this._state.highlightedIndex = -1;
|
|
520
500
|
this.updateOptionsVisibility(true);
|
|
521
501
|
this._setupOptionsScrollbar();
|
|
@@ -567,7 +547,7 @@ this.fireSearchEvent("");
|
|
|
567
547
|
}
|
|
568
548
|
else {
|
|
569
549
|
const allTags = this.getTagElements().map((el) => this.getTagData(el));
|
|
570
|
-
this._state.filteredTags =
|
|
550
|
+
this._state.filteredTags = this.getOfferedTags();
|
|
571
551
|
this.updateTagVisibility(allTags, allTags);
|
|
572
552
|
this.clearHighlights(allTags);
|
|
573
553
|
}
|
|
@@ -589,7 +569,7 @@ dialog.getBoundingClientRect();
|
|
|
589
569
|
dialog.classList.add("open");
|
|
590
570
|
this._state.open = true;
|
|
591
571
|
const tags = this.getTagElements().map((el) => this.getTagData(el));
|
|
592
|
-
this._state.filteredTags =
|
|
572
|
+
this._state.filteredTags = this.getOfferedTags();
|
|
593
573
|
this.updateTagVisibility(tags, tags);
|
|
594
574
|
requestAnimationFrame(() => {
|
|
595
575
|
this.updateMobileSelectedState();
|
|
@@ -629,7 +609,7 @@ this.fireSearchEvent("");
|
|
|
629
609
|
}
|
|
630
610
|
else {
|
|
631
611
|
const allTags = this.getTagElements();
|
|
632
|
-
allTags.forEach((el) => el.removeAttribute("
|
|
612
|
+
allTags.forEach((el) => el.removeAttribute("filtered-out"));
|
|
633
613
|
}
|
|
634
614
|
this.fireCloseEvent();
|
|
635
615
|
}
|
|
@@ -749,7 +729,7 @@ desktopInput.value = "";
|
|
|
749
729
|
if (mobileInput)
|
|
750
730
|
mobileInput.value = "";
|
|
751
731
|
const allTags = this.getTagElements().map((el) => this.getTagData(el));
|
|
752
|
-
this._state.filteredTags =
|
|
732
|
+
this._state.filteredTags = this.getOfferedTags();
|
|
753
733
|
this.updateTagVisibility(allTags, allTags);
|
|
754
734
|
this.updateOptionsVisibility(true);
|
|
755
735
|
this.clearHighlights(allTags);
|
|
@@ -770,7 +750,7 @@ if (this._externalSearch) {
|
|
|
770
750
|
this.dispatchSearchEvent(query);
|
|
771
751
|
return;
|
|
772
752
|
}
|
|
773
|
-
const allTags = this.
|
|
753
|
+
const allTags = this.getOfferedTags();
|
|
774
754
|
const filtered = this.filterTags(allTags, query);
|
|
775
755
|
this._state.filteredTags = filtered;
|
|
776
756
|
this._state.highlightedIndex = -1;
|
|
@@ -838,7 +818,8 @@ let index = fromIndex === -1
|
|
|
838
818
|
? (direction === 1 ? 0 : count - 1)
|
|
839
819
|
: (fromIndex + direction + count) % count;
|
|
840
820
|
for (let i = 0; i < count; i++) {
|
|
841
|
-
|
|
821
|
+
const el = tags[index].element;
|
|
822
|
+
if (!el.hasAttribute("disabled") && !el.hasAttribute("hidden") && !el.hasAttribute("filtered-out"))
|
|
842
823
|
return index;
|
|
843
824
|
index = (index + direction + count) % count;
|
|
844
825
|
}
|
|
@@ -854,12 +835,7 @@ return tags.filter(({ text }) => text.toLowerCase().includes(searchLower));
|
|
|
854
835
|
updateTagVisibility(filteredTags, allTags) {
|
|
855
836
|
const visibleValues = new Set(filteredTags.map((tag) => tag.value));
|
|
856
837
|
allTags.forEach(({ value, element }) => {
|
|
857
|
-
|
|
858
|
-
element.removeAttribute("hidden");
|
|
859
|
-
}
|
|
860
|
-
else {
|
|
861
|
-
element.setAttribute("hidden", "");
|
|
862
|
-
}
|
|
838
|
+
element.toggleAttribute("filtered-out", !visibleValues.has(value));
|
|
863
839
|
});
|
|
864
840
|
}
|
|
865
841
|
updateOptionsVisibility(hasOptions) {
|
|
@@ -1002,6 +978,13 @@ stub.removeAttribute("aria-invalid");
|
|
|
1002
978
|
stub.removeAttribute("aria-describedby");
|
|
1003
979
|
}
|
|
1004
980
|
}
|
|
981
|
+
this.syncTriggerAria();
|
|
982
|
+
shadow
|
|
983
|
+
.querySelectorAll(".dropdown-search-input, .mobile-search-input")
|
|
984
|
+
.forEach((input) => (input.disabled = this._disabled));
|
|
985
|
+
}
|
|
986
|
+
syncTriggerAria() {
|
|
987
|
+
const error = this.getProperty("error") || "";
|
|
1005
988
|
const trigger = error
|
|
1006
989
|
? this.querySelector(':scope > [slot="trigger"]')
|
|
1007
990
|
: null;
|
|
@@ -1010,13 +993,20 @@ this._ariaTrigger.removeAttribute("aria-invalid");
|
|
|
1010
993
|
this._ariaTrigger.removeAttribute("aria-description");
|
|
1011
994
|
}
|
|
1012
995
|
if (trigger) {
|
|
996
|
+
if (trigger.getAttribute("aria-invalid") !== "true")
|
|
1013
997
|
trigger.setAttribute("aria-invalid", "true");
|
|
998
|
+
if (trigger.getAttribute("aria-description") !== error)
|
|
1014
999
|
trigger.setAttribute("aria-description", error);
|
|
1015
1000
|
}
|
|
1016
1001
|
this._ariaTrigger = trigger;
|
|
1017
|
-
|
|
1018
|
-
.
|
|
1019
|
-
|
|
1002
|
+
this._ariaObserver ?? (this._ariaObserver = new MutationObserver(() => this.syncTriggerAria()));
|
|
1003
|
+
this._ariaObserver.disconnect();
|
|
1004
|
+
if (trigger) {
|
|
1005
|
+
this._ariaObserver.observe(trigger, {
|
|
1006
|
+
attributes: true,
|
|
1007
|
+
attributeFilter: ["aria-invalid", "aria-description"],
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1020
1010
|
}
|
|
1021
1011
|
updateValidity() {
|
|
1022
1012
|
if (this.disabled) {
|
|
@@ -1349,7 +1339,7 @@ const shadow = this.shadowRoot;
|
|
|
1349
1339
|
this.renderMobileSelectedChips();
|
|
1350
1340
|
const availableSection = shadow.querySelector(".mobile-available-section");
|
|
1351
1341
|
if (availableSection) {
|
|
1352
|
-
const visibleAvailable = this.
|
|
1342
|
+
const visibleAvailable = this.getOfferedTags().filter(({ element }) => !element.hasAttribute("filtered-out") && !element.hasAttribute("selected")).length;
|
|
1353
1343
|
availableSection.setAttribute("data-empty", String(visibleAvailable === 0));
|
|
1354
1344
|
this.setMobileSectionTitle(availableSection, this._availableLabel, visibleAvailable);
|
|
1355
1345
|
}
|
|
@@ -1494,6 +1484,8 @@ const option = this.getTagElements().find((el) => this.getTagData(el).value ===
|
|
|
1494
1484
|
if (option) {
|
|
1495
1485
|
const clone = option.cloneNode(true);
|
|
1496
1486
|
clone.removeAttribute("selected");
|
|
1487
|
+
clone.removeAttribute("hidden");
|
|
1488
|
+
clone.removeAttribute("filtered-out");
|
|
1497
1489
|
clone.setAttribute("slot", "selected");
|
|
1498
1490
|
clone.setAttribute("cloned", "true");
|
|
1499
1491
|
this.appendChild(clone);
|
|
@@ -1631,14 +1623,8 @@ get flavor() {
|
|
|
1631
1623
|
return this.getProperty("flavor");
|
|
1632
1624
|
}
|
|
1633
1625
|
set flavor(value) {
|
|
1634
|
-
this.removeAttribute(ERROR_FLAVOR);
|
|
1635
1626
|
this.setProperty("flavor", value);
|
|
1636
1627
|
}
|
|
1637
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
1638
|
-
if (name === "flavor" && newValue === "danger" && oldValue === newValue)
|
|
1639
|
-
this.removeAttribute(ERROR_FLAVOR);
|
|
1640
|
-
super.attributeChangedCallback(name, oldValue, newValue);
|
|
1641
|
-
}
|
|
1642
1628
|
get form() {
|
|
1643
1629
|
return this._internals.form;
|
|
1644
1630
|
}
|
|
@@ -24,6 +24,8 @@ export interface TySwitchElement extends HTMLElement {
|
|
|
24
24
|
}
|
|
25
25
|
export declare class TySwitch extends TyComponent<SwitchState> implements TySwitchElement {
|
|
26
26
|
static formAssociated: boolean;
|
|
27
|
+
protected static delegatesFocus: boolean;
|
|
28
|
+
protected static labelClicks: boolean;
|
|
27
29
|
protected static properties: {
|
|
28
30
|
checked: {
|
|
29
31
|
type: "boolean";
|
|
@@ -78,6 +80,8 @@ export declare class TySwitch extends TyComponent<SwitchState> implements TySwit
|
|
|
78
80
|
protected getFormValue(): FormDataEntryValue | null;
|
|
79
81
|
private buildClassList;
|
|
80
82
|
private handleSwitchClick;
|
|
83
|
+
/** Keys show the ring, a click hides it (see checkbox.ts). */
|
|
84
|
+
private showFocusRing;
|
|
81
85
|
private handleSwitchKeydown;
|
|
82
86
|
private removeEventListeners;
|
|
83
87
|
private setupEventListeners;
|
package/lib/components/switch.js
CHANGED
|
@@ -19,6 +19,9 @@ ensureStyles(shadow, { css: switchStyles, id: "ty-switch" });
|
|
|
19
19
|
onConnect() {
|
|
20
20
|
this.setupEventListeners();
|
|
21
21
|
this._syncCustomFlavor();
|
|
22
|
+
const el = this.shadowRoot.querySelector(".switch-container");
|
|
23
|
+
if (el && this.shadowRoot.activeElement !== el)
|
|
24
|
+
el.classList.remove("focused");
|
|
22
25
|
}
|
|
23
26
|
onDisconnect() {
|
|
24
27
|
this.removeEventListeners();
|
|
@@ -45,8 +48,7 @@ return classes.join(" ");
|
|
|
45
48
|
handleSwitchClick(e) {
|
|
46
49
|
if (this.disabled)
|
|
47
50
|
return;
|
|
48
|
-
|
|
49
|
-
return;
|
|
51
|
+
this.showFocusRing(e instanceof KeyboardEvent);
|
|
50
52
|
const newValue = !this.checked;
|
|
51
53
|
this.checked = newValue;
|
|
52
54
|
setTimeout(() => {
|
|
@@ -60,6 +62,9 @@ this.dispatchEvent(new CustomEvent("input", { detail: eventDetail, bubbles: true
|
|
|
60
62
|
this.dispatchEvent(new CustomEvent("change", { detail: eventDetail, bubbles: true, composed: true }));
|
|
61
63
|
}, 0);
|
|
62
64
|
}
|
|
65
|
+
showFocusRing(on) {
|
|
66
|
+
this.shadowRoot?.querySelector(".switch-container")?.classList.toggle("focused", on);
|
|
67
|
+
}
|
|
63
68
|
handleSwitchKeydown(e) {
|
|
64
69
|
if (this.disabled)
|
|
65
70
|
return;
|
|
@@ -107,7 +112,7 @@ if (!el)
|
|
|
107
112
|
return;
|
|
108
113
|
this._clickHandler = (e) => this.handleSwitchClick(e);
|
|
109
114
|
this._keydownHandler = (e) => this.handleSwitchKeydown(e);
|
|
110
|
-
this._focusHandler = () => el.classList.
|
|
115
|
+
this._focusHandler = () => el.classList.toggle("focused", el.matches(":focus-visible"));
|
|
111
116
|
this._blurHandler = () => el.classList.remove("focused");
|
|
112
117
|
this.addEventListener("click", this._clickHandler);
|
|
113
118
|
el.addEventListener("keydown", this._keydownHandler);
|
|
@@ -122,7 +127,8 @@ const classes = this.buildClassList();
|
|
|
122
127
|
if (!switchEl) {
|
|
123
128
|
switchEl = document.createElement("div");
|
|
124
129
|
switchEl.className = "switch-container " + classes;
|
|
125
|
-
|
|
130
|
+
if (!this.disabled)
|
|
131
|
+
switchEl.tabIndex = 0;
|
|
126
132
|
switchEl.setAttribute("role", "switch");
|
|
127
133
|
switchEl.setAttribute("aria-checked", this.checked ? "true" : "false");
|
|
128
134
|
switchEl.setAttribute("aria-disabled", this.disabled ? "true" : "false");
|
|
@@ -136,8 +142,13 @@ switchEl.appendChild(track);
|
|
|
136
142
|
shadow.appendChild(switchEl);
|
|
137
143
|
}
|
|
138
144
|
else {
|
|
145
|
+
const focused = switchEl.classList.contains("focused") && shadow.activeElement === switchEl;
|
|
139
146
|
switchEl.className = "switch-container " + classes;
|
|
140
|
-
switchEl.
|
|
147
|
+
switchEl.classList.toggle("focused", focused && !this.disabled);
|
|
148
|
+
if (this.disabled)
|
|
149
|
+
switchEl.removeAttribute("tabindex");
|
|
150
|
+
else
|
|
151
|
+
switchEl.tabIndex = 0;
|
|
141
152
|
switchEl.setAttribute("aria-checked", this.checked ? "true" : "false");
|
|
142
153
|
switchEl.setAttribute("aria-disabled", this.disabled ? "true" : "false");
|
|
143
154
|
switchEl.setAttribute("aria-required", this.required ? "true" : "false");
|
|
@@ -183,6 +194,8 @@ set flavor(value) { this.setProperty("flavor", value); }
|
|
|
183
194
|
get form() { return this._internals.form; }
|
|
184
195
|
}
|
|
185
196
|
TySwitch.formAssociated = true;
|
|
197
|
+
TySwitch.delegatesFocus = true;
|
|
198
|
+
TySwitch.labelClicks = true;
|
|
186
199
|
TySwitch.properties = {
|
|
187
200
|
checked: {
|
|
188
201
|
type: "boolean",
|
package/lib/components/tabs.js
CHANGED
|
@@ -251,7 +251,7 @@ marker.style.top = `${position.top}px`;
|
|
|
251
251
|
marker.style.width = `${position.width}px`;
|
|
252
252
|
marker.style.height = `${position.height}px`;
|
|
253
253
|
if (snap) {
|
|
254
|
-
marker.
|
|
254
|
+
marker.getBoundingClientRect();
|
|
255
255
|
marker.style.transition = '';
|
|
256
256
|
}
|
|
257
257
|
}
|
|
@@ -191,6 +191,7 @@ export declare class TyTextarea extends TyComponent<TextareaState> implements Ty
|
|
|
191
191
|
private buildClassList;
|
|
192
192
|
private applyHeightConstraints;
|
|
193
193
|
private setupTextareaEvents;
|
|
194
|
+
private removeTextareaEvents;
|
|
194
195
|
protected render(): void;
|
|
195
196
|
/**
|
|
196
197
|
* minlength goes on the inner <textarea> for the browser's own too-short
|
|
@@ -15,6 +15,8 @@ this._scrollbar = null;
|
|
|
15
15
|
this._isFocused = false;
|
|
16
16
|
this.handleInputEvent = (e) => {
|
|
17
17
|
this._userEdited = true;
|
|
18
|
+
e.stopPropagation();
|
|
19
|
+
e.stopImmediatePropagation();
|
|
18
20
|
const target = e.target;
|
|
19
21
|
const newValue = target.value;
|
|
20
22
|
this.value = newValue;
|
|
@@ -46,20 +48,22 @@ ensureStyles(shadow, { css: customScrollbarStyles, id: 'ty-custom-scrollbar' });
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
onConnect() {
|
|
51
|
+
this.updateFormValue();
|
|
52
|
+
if (this._rendered) {
|
|
53
|
+
this.render();
|
|
54
|
+
if (this._textareaEl)
|
|
55
|
+
this.setupTextareaEvents(this._textareaEl);
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
onDisconnect() {
|
|
51
59
|
this._destroyScrollbar();
|
|
52
|
-
if (this._textareaEl)
|
|
53
|
-
|
|
54
|
-
this._textareaEl.parentNode?.replaceChild(newTextarea, this._textareaEl);
|
|
55
|
-
this._textareaEl = null;
|
|
56
|
-
}
|
|
57
|
-
this._dummyEl = null;
|
|
60
|
+
if (this._textareaEl)
|
|
61
|
+
this.removeTextareaEvents(this._textareaEl);
|
|
58
62
|
}
|
|
59
63
|
onPropertiesChanged(changes) {
|
|
60
64
|
}
|
|
61
65
|
getFormValue() {
|
|
62
|
-
return this.getProperty('value') ||
|
|
66
|
+
return this.getProperty('value') || '';
|
|
63
67
|
}
|
|
64
68
|
get value() { return this.getProperty('value'); }
|
|
65
69
|
set value(v) { this.setProperty('value', v); }
|
|
@@ -190,6 +194,10 @@ textareaEl.style.overflowY = 'hidden';
|
|
|
190
194
|
this._destroyScrollbar();
|
|
191
195
|
}
|
|
192
196
|
}
|
|
197
|
+
else if (maxHeight !== null && measuredHeight >= maxHeight && !this._scrollbar) {
|
|
198
|
+
textareaEl.style.overflowY = 'auto';
|
|
199
|
+
this._setupScrollbar(textareaEl);
|
|
200
|
+
}
|
|
193
201
|
this._scrollbar?.update();
|
|
194
202
|
}
|
|
195
203
|
_setupScrollbar(textareaEl) {
|
|
@@ -226,7 +234,7 @@ value: this.value,
|
|
|
226
234
|
originalEvent
|
|
227
235
|
}
|
|
228
236
|
};
|
|
229
|
-
|
|
237
|
+
this.dispatchEvent(new CustomEvent(type, data));
|
|
230
238
|
}
|
|
231
239
|
buildClassList() {
|
|
232
240
|
const classes = [this.size];
|
|
@@ -256,6 +264,11 @@ textareaEl.addEventListener('input', this.handleInputEvent);
|
|
|
256
264
|
textareaEl.addEventListener('focus', this.handleFocusEvent);
|
|
257
265
|
textareaEl.addEventListener('blur', this.handleBlurEvent);
|
|
258
266
|
}
|
|
267
|
+
removeTextareaEvents(textareaEl) {
|
|
268
|
+
textareaEl.removeEventListener('input', this.handleInputEvent);
|
|
269
|
+
textareaEl.removeEventListener('focus', this.handleFocusEvent);
|
|
270
|
+
textareaEl.removeEventListener('blur', this.handleBlurEvent);
|
|
271
|
+
}
|
|
259
272
|
render() {
|
|
260
273
|
const root = this.shadowRoot;
|
|
261
274
|
const existingContainer = root.querySelector('.textarea-container');
|
package/lib/index.d.ts
CHANGED
|
@@ -75,6 +75,7 @@ declare global {
|
|
|
75
75
|
tyResizeObserver: {
|
|
76
76
|
getSize: (id: string) => ResizeSize | undefined;
|
|
77
77
|
onResize: (id: string, callback: ResizeCallback) => () => void;
|
|
78
|
+
getAllSizes: () => Record<string, ResizeSize>;
|
|
78
79
|
sizes: Record<string, ResizeSize>;
|
|
79
80
|
};
|
|
80
81
|
tyLoader: {
|
package/lib/index.js
CHANGED
package/lib/styles/button.js
CHANGED
|
@@ -124,7 +124,7 @@ color: var(--ty-calendar-weekend-color, var(--ty-calendar-day-color, var(--ty-te
|
|
|
124
124
|
color: var(--ty-calendar-other-month-color, color-mix(in oklab, var(--ty-text-faint) 65%, transparent));
|
|
125
125
|
}
|
|
126
126
|
.calendar-day-cell.other-month:hover {
|
|
127
|
-
color: var(--ty-calendar-day-hover-color, var(--ty-text-soft
|
|
127
|
+
color: var(--ty-calendar-day-hover-color, var(--ty-text-soft));
|
|
128
128
|
background-color: var(--ty-calendar-day-hover-bg, var(--ty-bg-neutral-soft));
|
|
129
129
|
}
|
|
130
130
|
.calendar-day-cell.disabled {
|