tyrell-components 1.0.0-TC56 → 1.0.0-TC58
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.css +3 -3
- package/dist/tyrell.js +1 -1
- package/dist/tyrell.js.map +1 -0
- package/lib/base/ty-component.d.ts +15 -3
- package/lib/base/ty-component.js +26 -18
- package/lib/components/button.d.ts +4 -0
- package/lib/components/button.js +33 -6
- package/lib/components/calendar-month.js +3 -10
- package/lib/components/calendar.d.ts +5 -1
- package/lib/components/calendar.js +28 -34
- package/lib/components/checkbox.d.ts +3 -1
- package/lib/components/checkbox.js +9 -1
- package/lib/components/date-picker.d.ts +61 -7
- package/lib/components/date-picker.js +274 -133
- package/lib/components/file-upload.d.ts +4 -1
- package/lib/components/file-upload.js +17 -3
- package/lib/components/input.d.ts +82 -3
- package/lib/components/input.js +178 -20
- package/lib/components/modal.d.ts +2 -0
- package/lib/components/modal.js +49 -10
- package/lib/components/option.js +7 -5
- package/lib/components/popup.js +59 -10
- package/lib/components/radio.d.ts +4 -1
- package/lib/components/radio.js +10 -1
- package/lib/components/resize-observer.js +2 -0
- package/lib/components/scroll-container.js +2 -0
- package/lib/components/select.d.ts +34 -3
- package/lib/components/select.js +135 -11
- package/lib/components/selected-tags.d.ts +7 -1
- package/lib/components/selected-tags.js +10 -1
- package/lib/components/switch.d.ts +5 -1
- package/lib/components/switch.js +9 -1
- package/lib/components/textarea.d.ts +46 -2
- package/lib/components/textarea.js +60 -9
- package/lib/styles/button.js +20 -19
- package/lib/styles/date-picker.js +18 -1
- package/lib/styles/file-upload.d.ts +1 -1
- package/lib/styles/file-upload.js +3 -3
- package/lib/styles/icon.d.ts +1 -1
- package/lib/styles/icon.js +2 -2
- package/lib/styles/input.js +1 -1
- package/lib/styles/radio.js +2 -2
- package/lib/styles/select-base.js +26 -0
- package/lib/styles/textarea.d.ts +1 -1
- package/lib/styles/textarea.js +7 -0
- package/lib/utils/error-flavor.d.ts +8 -0
- package/lib/utils/error-flavor.js +1 -0
- package/lib/utils/property-manager.d.ts +6 -0
- package/lib/utils/property-manager.js +15 -5
- package/lib/utils/upgrade-properties.d.ts +21 -0
- package/lib/utils/upgrade-properties.js +47 -0
- package/lib/utils/validity.d.ts +72 -2
- package/lib/utils/validity.js +192 -8
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +3 -2
- package/lib/base/ty-component.d.ts.map +0 -1
- package/lib/base/ty-component.js.map +0 -1
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js.map +0 -1
- package/lib/components/calendar-month.d.ts.map +0 -1
- package/lib/components/calendar-month.js.map +0 -1
- package/lib/components/calendar-navigation.d.ts.map +0 -1
- package/lib/components/calendar-navigation.js.map +0 -1
- package/lib/components/calendar.d.ts.map +0 -1
- package/lib/components/calendar.js.map +0 -1
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js.map +0 -1
- package/lib/components/copy.d.ts.map +0 -1
- package/lib/components/copy.js.map +0 -1
- package/lib/components/date-picker.d.ts.map +0 -1
- package/lib/components/date-picker.js.map +0 -1
- package/lib/components/dropdown.d.ts +0 -403
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -1315
- package/lib/components/dropdown.js.map +0 -1
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js.map +0 -1
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js.map +0 -1
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js.map +0 -1
- package/lib/components/multiselect.d.ts +0 -389
- package/lib/components/multiselect.d.ts.map +0 -1
- package/lib/components/multiselect.js +0 -1301
- package/lib/components/multiselect.js.map +0 -1
- package/lib/components/option.d.ts.map +0 -1
- package/lib/components/option.js.map +0 -1
- package/lib/components/popup.d.ts.map +0 -1
- package/lib/components/popup.js.map +0 -1
- package/lib/components/resize-observer.d.ts.map +0 -1
- package/lib/components/resize-observer.js.map +0 -1
- package/lib/components/scroll-container.d.ts.map +0 -1
- package/lib/components/scroll-container.js.map +0 -1
- package/lib/components/step.d.ts.map +0 -1
- package/lib/components/step.js.map +0 -1
- package/lib/components/tab.d.ts.map +0 -1
- package/lib/components/tab.js.map +0 -1
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js.map +0 -1
- package/lib/components/tag.d.ts.map +0 -1
- package/lib/components/tag.js.map +0 -1
- package/lib/components/textarea.d.ts.map +0 -1
- package/lib/components/textarea.js.map +0 -1
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js.map +0 -1
- package/lib/components/wizard.d.ts.map +0 -1
- package/lib/components/wizard.js.map +0 -1
- package/lib/icons/fontawesome/brands.d.ts.map +0 -1
- package/lib/icons/fontawesome/brands.js.map +0 -1
- package/lib/icons/fontawesome/regular.d.ts.map +0 -1
- package/lib/icons/fontawesome/regular.js.map +0 -1
- package/lib/icons/fontawesome/solid.d.ts.map +0 -1
- package/lib/icons/fontawesome/solid.js.map +0 -1
- package/lib/icons/heroicons/micro.d.ts.map +0 -1
- package/lib/icons/heroicons/micro.js.map +0 -1
- package/lib/icons/heroicons/mini.d.ts.map +0 -1
- package/lib/icons/heroicons/mini.js.map +0 -1
- package/lib/icons/heroicons/outline.d.ts.map +0 -1
- package/lib/icons/heroicons/outline.js.map +0 -1
- package/lib/icons/heroicons/solid.d.ts.map +0 -1
- package/lib/icons/heroicons/solid.js.map +0 -1
- package/lib/icons/lucide.d.ts.map +0 -1
- package/lib/icons/lucide.js.map +0 -1
- package/lib/icons/material/filled.d.ts.map +0 -1
- package/lib/icons/material/filled.js.map +0 -1
- package/lib/icons/material/outlined.d.ts.map +0 -1
- package/lib/icons/material/outlined.js.map +0 -1
- package/lib/icons/material/round.d.ts.map +0 -1
- package/lib/icons/material/round.js.map +0 -1
- package/lib/icons/material/sharp.d.ts.map +0 -1
- package/lib/icons/material/sharp.js.map +0 -1
- package/lib/icons/material/two-tone.d.ts.map +0 -1
- package/lib/icons/material/two-tone.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/styles/button.d.ts.map +0 -1
- package/lib/styles/button.js.map +0 -1
- package/lib/styles/calendar-month.d.ts.map +0 -1
- package/lib/styles/calendar-month.js.map +0 -1
- package/lib/styles/calendar-navigation.d.ts.map +0 -1
- package/lib/styles/calendar-navigation.js.map +0 -1
- package/lib/styles/calendar.d.ts.map +0 -1
- package/lib/styles/calendar.js.map +0 -1
- package/lib/styles/copy.d.ts.map +0 -1
- package/lib/styles/copy.js.map +0 -1
- package/lib/styles/date-picker.d.ts.map +0 -1
- package/lib/styles/date-picker.js.map +0 -1
- package/lib/styles/dropdown.d.ts +0 -12
- package/lib/styles/dropdown.d.ts.map +0 -1
- package/lib/styles/dropdown.js +0 -725
- package/lib/styles/dropdown.js.map +0 -1
- package/lib/styles/icon.d.ts.map +0 -1
- package/lib/styles/icon.js.map +0 -1
- package/lib/styles/input.d.ts.map +0 -1
- package/lib/styles/input.js.map +0 -1
- package/lib/styles/modal.d.ts.map +0 -1
- package/lib/styles/modal.js.map +0 -1
- package/lib/styles/multiselect.d.ts +0 -6
- package/lib/styles/multiselect.d.ts.map +0 -1
- package/lib/styles/multiselect.js +0 -568
- package/lib/styles/multiselect.js.map +0 -1
- package/lib/styles/option.d.ts.map +0 -1
- package/lib/styles/option.js.map +0 -1
- package/lib/styles/popup.d.ts.map +0 -1
- package/lib/styles/popup.js.map +0 -1
- package/lib/styles/resize-observer.d.ts.map +0 -1
- package/lib/styles/resize-observer.js.map +0 -1
- package/lib/styles/scroll-container.d.ts.map +0 -1
- package/lib/styles/scroll-container.js.map +0 -1
- package/lib/styles/step.d.ts.map +0 -1
- package/lib/styles/step.js.map +0 -1
- package/lib/styles/tab.d.ts.map +0 -1
- package/lib/styles/tab.js.map +0 -1
- package/lib/styles/tabs.d.ts.map +0 -1
- package/lib/styles/tabs.js.map +0 -1
- package/lib/styles/tag.d.ts.map +0 -1
- package/lib/styles/tag.js.map +0 -1
- package/lib/styles/textarea.d.ts.map +0 -1
- package/lib/styles/textarea.js.map +0 -1
- package/lib/styles/tooltip.d.ts.map +0 -1
- package/lib/styles/tooltip.js.map +0 -1
- package/lib/styles/wizard.d.ts.map +0 -1
- package/lib/styles/wizard.js.map +0 -1
- package/lib/types/common.d.ts.map +0 -1
- package/lib/types/common.js.map +0 -1
- package/lib/utils/calendar-utils.d.ts.map +0 -1
- package/lib/utils/calendar-utils.js.map +0 -1
- package/lib/utils/icon-registry.d.ts.map +0 -1
- package/lib/utils/icon-registry.js.map +0 -1
- package/lib/utils/locale.d.ts.map +0 -1
- package/lib/utils/locale.js.map +0 -1
- package/lib/utils/number-format.d.ts.map +0 -1
- package/lib/utils/number-format.js.map +0 -1
- package/lib/utils/parse-boolean.d.ts +0 -39
- package/lib/utils/parse-boolean.d.ts.map +0 -1
- package/lib/utils/parse-boolean.js +0 -23
- package/lib/utils/parse-boolean.js.map +0 -1
- package/lib/utils/positioning.d.ts.map +0 -1
- package/lib/utils/positioning.js.map +0 -1
- package/lib/utils/property-capture.d.ts.map +0 -1
- package/lib/utils/property-capture.js.map +0 -1
- package/lib/utils/property-manager.d.ts.map +0 -1
- package/lib/utils/property-manager.js.map +0 -1
- package/lib/utils/resize-observer.d.ts.map +0 -1
- package/lib/utils/resize-observer.js.map +0 -1
- package/lib/utils/scroll-lock.d.ts.map +0 -1
- package/lib/utils/scroll-lock.js.map +0 -1
- package/lib/utils/styles.d.ts.map +0 -1
- package/lib/utils/styles.js.map +0 -1
- package/lib/version.d.ts.map +0 -1
- package/lib/version.js.map +0 -1
package/lib/components/modal.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
2
|
+
import { coerceBoolean } from '../utils/property-manager.js';
|
|
2
3
|
import { ensureStyles } from '../utils/styles.js';
|
|
4
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
3
5
|
import { modalStyles } from '../styles/modal.js';
|
|
4
6
|
const backdropClickHandlers = new WeakMap();
|
|
5
7
|
const escapeKeyHandlers = new WeakMap();
|
|
@@ -97,7 +99,39 @@ event.preventDefault();
|
|
|
97
99
|
closeModal(el, 'backdrop');
|
|
98
100
|
}
|
|
99
101
|
}
|
|
102
|
+
function fromNestedDialog(dialog, event) {
|
|
103
|
+
for (const node of event.composedPath()) {
|
|
104
|
+
if (node === dialog)
|
|
105
|
+
return false;
|
|
106
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
function closeNestedDialogs(root, seen = new WeakSet()) {
|
|
112
|
+
const close = (node) => {
|
|
113
|
+
if (seen.has(node))
|
|
114
|
+
return;
|
|
115
|
+
seen.add(node);
|
|
116
|
+
if (node instanceof TyModal && node.open)
|
|
117
|
+
node.removeAttribute('open');
|
|
118
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
119
|
+
node.close();
|
|
120
|
+
if (node.shadowRoot)
|
|
121
|
+
closeNestedDialogs(node.shadowRoot, seen);
|
|
122
|
+
if (node instanceof HTMLSlotElement) {
|
|
123
|
+
for (const assigned of node.assignedElements({ flatten: true })) {
|
|
124
|
+
close(assigned);
|
|
125
|
+
closeNestedDialogs(assigned, seen);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
root.querySelectorAll('*').forEach(close);
|
|
130
|
+
}
|
|
100
131
|
function handleEscapeKey(el, event) {
|
|
132
|
+
const dialog = event.currentTarget;
|
|
133
|
+
if (fromNestedDialog(dialog, event))
|
|
134
|
+
return;
|
|
101
135
|
event.stopPropagation();
|
|
102
136
|
if (event.key === 'Escape') {
|
|
103
137
|
event.preventDefault();
|
|
@@ -154,7 +188,7 @@ dialog.addEventListener('keydown', handler);
|
|
|
154
188
|
}
|
|
155
189
|
else {
|
|
156
190
|
const handler = (e) => {
|
|
157
|
-
if (e.key === 'Escape')
|
|
191
|
+
if (e.key === 'Escape' && !fromNestedDialog(dialog, e))
|
|
158
192
|
e.preventDefault();
|
|
159
193
|
};
|
|
160
194
|
escapeKeyHandlers.set(el, handler);
|
|
@@ -252,6 +286,7 @@ dispatchModalEvent(el, 'open', {});
|
|
|
252
286
|
else {
|
|
253
287
|
if (dialog.open) {
|
|
254
288
|
unlockScroll(modalId);
|
|
289
|
+
closeNestedDialogs(el);
|
|
255
290
|
dialog.close();
|
|
256
291
|
syncBackdropZoom();
|
|
257
292
|
const reason = pendingCloseReasons.get(el) ?? 'programmatic';
|
|
@@ -259,10 +294,12 @@ pendingCloseReasons.delete(el);
|
|
|
259
294
|
dispatchModalEvent(el, 'close', { reason });
|
|
260
295
|
}
|
|
261
296
|
}
|
|
297
|
+
syncBackdropZoom();
|
|
262
298
|
dialog.onclose = (event) => {
|
|
263
299
|
if (event.target !== dialog)
|
|
264
300
|
return;
|
|
265
301
|
unlockScroll(modalId);
|
|
302
|
+
closeNestedDialogs(el);
|
|
266
303
|
if (el.hasAttribute('open')) {
|
|
267
304
|
el.removeAttribute('open');
|
|
268
305
|
dispatchModalEvent(el, 'close', {
|
|
@@ -274,10 +311,7 @@ syncBackdropZoom();
|
|
|
274
311
|
};
|
|
275
312
|
}
|
|
276
313
|
function syncBackdropZoom() {
|
|
277
|
-
const anyOpen = Array.from(document.querySelectorAll('ty-modal[open]
|
|
278
|
-
const v = m.getAttribute('backdrop-zoom').toLowerCase().trim();
|
|
279
|
-
return v !== 'false' && v !== '0';
|
|
280
|
-
});
|
|
314
|
+
const anyOpen = Array.from(document.querySelectorAll('ty-modal[open], ty-dialog[open]')).some((m) => parseBoolAttr(m, 'open') && !parseBoolAttr(m, 'no-zoom'));
|
|
281
315
|
document.documentElement.classList.toggle('ty-modal-zoom', anyOpen);
|
|
282
316
|
}
|
|
283
317
|
function cleanup(el) {
|
|
@@ -325,27 +359,32 @@ hoverLeaveHandlers.delete(el);
|
|
|
325
359
|
}
|
|
326
360
|
export class TyModal extends HTMLElement {
|
|
327
361
|
static get observedAttributes() {
|
|
328
|
-
return ['open', 'no-backdrop', 'prevent-outside-click', 'prevent-escape', 'label'];
|
|
362
|
+
return ['open', 'no-backdrop', 'no-zoom', 'prevent-outside-click', 'prevent-escape', 'label'];
|
|
329
363
|
}
|
|
330
364
|
get open() { return parseBoolAttr(this, 'open'); }
|
|
331
|
-
set open(v) { v ? this.setAttribute('open', 'true') : this.removeAttribute('open'); }
|
|
365
|
+
set open(v) { coerceBoolean(v) ? this.setAttribute('open', 'true') : this.removeAttribute('open'); }
|
|
332
366
|
get noBackdrop() { return parseBoolAttr(this, 'no-backdrop'); }
|
|
333
367
|
set noBackdrop(v) {
|
|
334
|
-
v ? this.setAttribute('no-backdrop', '') : this.removeAttribute('no-backdrop');
|
|
368
|
+
coerceBoolean(v) ? this.setAttribute('no-backdrop', '') : this.removeAttribute('no-backdrop');
|
|
369
|
+
}
|
|
370
|
+
get noZoom() { return parseBoolAttr(this, 'no-zoom'); }
|
|
371
|
+
set noZoom(v) {
|
|
372
|
+
coerceBoolean(v) ? this.setAttribute('no-zoom', '') : this.removeAttribute('no-zoom');
|
|
335
373
|
}
|
|
336
374
|
get preventOutsideClick() { return parseBoolAttr(this, 'prevent-outside-click'); }
|
|
337
375
|
set preventOutsideClick(v) {
|
|
338
|
-
v ? this.setAttribute('prevent-outside-click', '') : this.removeAttribute('prevent-outside-click');
|
|
376
|
+
coerceBoolean(v) ? this.setAttribute('prevent-outside-click', '') : this.removeAttribute('prevent-outside-click');
|
|
339
377
|
}
|
|
340
378
|
get preventEscape() { return parseBoolAttr(this, 'prevent-escape'); }
|
|
341
379
|
set preventEscape(v) {
|
|
342
|
-
v ? this.setAttribute('prevent-escape', '') : this.removeAttribute('prevent-escape');
|
|
380
|
+
coerceBoolean(v) ? this.setAttribute('prevent-escape', '') : this.removeAttribute('prevent-escape');
|
|
343
381
|
}
|
|
344
382
|
constructor() {
|
|
345
383
|
super();
|
|
346
384
|
this.attachShadow({ mode: 'open' });
|
|
347
385
|
}
|
|
348
386
|
connectedCallback() {
|
|
387
|
+
upgradeProperties(this);
|
|
349
388
|
render(this);
|
|
350
389
|
this.show = () => openModal(this);
|
|
351
390
|
this.hide = (opts) => closeModal(this, 'programmatic', opts);
|
package/lib/components/option.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
2
|
+
import { coerceBoolean } from '../utils/property-manager.js';
|
|
3
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
2
4
|
import { optionStyles } from '../styles/option.js';
|
|
3
5
|
const CHECK_SVG = `
|
|
4
6
|
<svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
@@ -22,11 +24,7 @@ static get observedAttributes() {
|
|
|
22
24
|
return ['value', 'disabled', 'selected', 'highlighted', 'hidden'];
|
|
23
25
|
}
|
|
24
26
|
connectedCallback() {
|
|
25
|
-
|
|
26
|
-
if (instanceValue) {
|
|
27
|
-
this._value = instanceValue.value;
|
|
28
|
-
delete this.value;
|
|
29
|
-
}
|
|
27
|
+
upgradeProperties(this);
|
|
30
28
|
this.render();
|
|
31
29
|
}
|
|
32
30
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -80,6 +78,7 @@ get selected() {
|
|
|
80
78
|
return this._selected;
|
|
81
79
|
}
|
|
82
80
|
set selected(value) {
|
|
81
|
+
value = coerceBoolean(value);
|
|
83
82
|
if (this._selected !== value) {
|
|
84
83
|
this._selected = value;
|
|
85
84
|
if (value) {
|
|
@@ -95,6 +94,7 @@ get disabled() {
|
|
|
95
94
|
return this._disabled;
|
|
96
95
|
}
|
|
97
96
|
set disabled(value) {
|
|
97
|
+
value = coerceBoolean(value);
|
|
98
98
|
if (this._disabled !== value) {
|
|
99
99
|
this._disabled = value;
|
|
100
100
|
if (value) {
|
|
@@ -110,6 +110,7 @@ get highlighted() {
|
|
|
110
110
|
return this._highlighted;
|
|
111
111
|
}
|
|
112
112
|
set highlighted(value) {
|
|
113
|
+
value = coerceBoolean(value);
|
|
113
114
|
if (this._highlighted !== value) {
|
|
114
115
|
this._highlighted = value;
|
|
115
116
|
if (value) {
|
|
@@ -125,6 +126,7 @@ get hidden() {
|
|
|
125
126
|
return this._hidden;
|
|
126
127
|
}
|
|
127
128
|
set hidden(value) {
|
|
129
|
+
value = coerceBoolean(value);
|
|
128
130
|
if (this._hidden !== value) {
|
|
129
131
|
this._hidden = value;
|
|
130
132
|
if (value) {
|
package/lib/components/popup.js
CHANGED
|
@@ -5,9 +5,11 @@ import { popupStyles } from '../styles/popup.js';
|
|
|
5
5
|
const anchorClickHandlers = new WeakMap();
|
|
6
6
|
const anchorKeyClickHandlers = new WeakMap();
|
|
7
7
|
const outsideClickHandlers = new WeakMap();
|
|
8
|
+
const closeTimers = new WeakMap();
|
|
8
9
|
const escapeHandlers = new WeakMap();
|
|
9
10
|
const closeRequestHandlers = new WeakMap();
|
|
10
11
|
const popupIds = new WeakMap();
|
|
12
|
+
const pointerPresses = new WeakSet();
|
|
11
13
|
function parseBoolAttr(el, name) {
|
|
12
14
|
return el.hasAttribute(name);
|
|
13
15
|
}
|
|
@@ -76,20 +78,33 @@ if (anchor?.hasAttribute('aria-haspopup'))
|
|
|
76
78
|
anchor.setAttribute('aria-expanded', 'false');
|
|
77
79
|
const exitMs = Math.max(0, ...getComputedStyle(dialog).transitionDuration
|
|
78
80
|
.split(',').map((d) => parseFloat(d) * (d.trim().endsWith('ms') ? 1 : 1000)));
|
|
79
|
-
|
|
81
|
+
clearTimeout(closeTimers.get(el));
|
|
82
|
+
closeTimers.set(el, setTimeout(() => {
|
|
83
|
+
closeTimers.delete(el);
|
|
80
84
|
dialog.classList.remove('position-calculated');
|
|
81
85
|
dialog.close();
|
|
82
|
-
}, exitMs + 20);
|
|
86
|
+
}, exitMs + 20));
|
|
83
87
|
}
|
|
84
88
|
function openPopup(el) {
|
|
85
|
-
const popupId = getPopupId(el);
|
|
86
|
-
lockScroll(popupId);
|
|
87
89
|
const shadowRoot = el.shadowRoot;
|
|
88
90
|
const dialog = shadowRoot ? getPopupDialog(shadowRoot) : null;
|
|
89
91
|
if (!dialog)
|
|
90
92
|
return;
|
|
91
|
-
dialog.showModal();
|
|
92
93
|
const anchor = getAnchorElement(el);
|
|
94
|
+
if (dialog.open) {
|
|
95
|
+
const pending = closeTimers.get(el);
|
|
96
|
+
if (pending !== undefined) {
|
|
97
|
+
clearTimeout(pending);
|
|
98
|
+
closeTimers.delete(el);
|
|
99
|
+
dialog.classList.add('open');
|
|
100
|
+
if (anchor?.hasAttribute('aria-haspopup'))
|
|
101
|
+
anchor.setAttribute('aria-expanded', 'true');
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const popupId = getPopupId(el);
|
|
106
|
+
lockScroll(popupId);
|
|
107
|
+
dialog.showModal();
|
|
93
108
|
if (anchor?.hasAttribute('aria-haspopup'))
|
|
94
109
|
anchor.setAttribute('aria-expanded', 'true');
|
|
95
110
|
requestAnimationFrame(() => {
|
|
@@ -128,7 +143,19 @@ event.preventDefault();
|
|
|
128
143
|
closePopup(el);
|
|
129
144
|
}
|
|
130
145
|
}
|
|
146
|
+
function fromNestedDialog(el, event) {
|
|
147
|
+
const dialog = el.shadowRoot ? getPopupDialog(el.shadowRoot) : null;
|
|
148
|
+
for (const node of event.composedPath()) {
|
|
149
|
+
if (node === dialog)
|
|
150
|
+
return false;
|
|
151
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
131
156
|
function handleEscape(el, event) {
|
|
157
|
+
if (fromNestedDialog(el, event))
|
|
158
|
+
return;
|
|
132
159
|
event.stopPropagation();
|
|
133
160
|
if (event.key === 'Escape') {
|
|
134
161
|
const { disableClose } = getPopupAttributes(el);
|
|
@@ -160,11 +187,21 @@ const existingKeyHandler = anchorKeyClickHandlers.get(el);
|
|
|
160
187
|
if (existingKeyHandler) {
|
|
161
188
|
anchor.removeEventListener('click', existingKeyHandler);
|
|
162
189
|
}
|
|
163
|
-
const handler = (e) =>
|
|
190
|
+
const handler = (e) => {
|
|
191
|
+
pointerPresses.add(el);
|
|
192
|
+
handleAnchorClick(el, e);
|
|
193
|
+
};
|
|
164
194
|
anchorClickHandlers.set(el, handler);
|
|
165
195
|
anchor.addEventListener('pointerdown', handler);
|
|
166
196
|
const keyHandler = (e) => {
|
|
167
|
-
if (
|
|
197
|
+
if (pointerPresses.delete(el))
|
|
198
|
+
return;
|
|
199
|
+
const d = e.detail;
|
|
200
|
+
const click = typeof d === 'number' ? e : d?.originalEvent ?? e;
|
|
201
|
+
if (click.detail !== 0)
|
|
202
|
+
return;
|
|
203
|
+
if (click.composedPath().includes(el))
|
|
204
|
+
return;
|
|
168
205
|
handleAnchorClick(el, e);
|
|
169
206
|
};
|
|
170
207
|
anchorKeyClickHandlers.set(el, keyHandler);
|
|
@@ -209,17 +246,29 @@ dialog.addEventListener('pointerdown', outsideHandler);
|
|
|
209
246
|
const escapeHandler = (e) => handleEscape(el, e);
|
|
210
247
|
escapeHandlers.set(el, escapeHandler);
|
|
211
248
|
el.addEventListener('keydown', escapeHandler);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
new MutationObserver(() => {
|
|
250
|
+
if (dialog.open)
|
|
251
|
+
return;
|
|
252
|
+
clearTimeout(closeTimers.get(el));
|
|
253
|
+
closeTimers.delete(el);
|
|
254
|
+
pointerPresses.delete(el);
|
|
255
|
+
unlockScroll(getPopupId(el));
|
|
215
256
|
dialog.classList.remove('open');
|
|
216
257
|
dialog.classList.remove('preparing-animation');
|
|
258
|
+
const anchor = getAnchorElement(el);
|
|
259
|
+
if (anchor?.hasAttribute('aria-haspopup'))
|
|
260
|
+
anchor.setAttribute('aria-expanded', 'false');
|
|
261
|
+
}).observe(dialog, { attributes: true, attributeFilter: ['open'] });
|
|
262
|
+
dialog.addEventListener('close', () => {
|
|
217
263
|
el.dispatchEvent(new CustomEvent('close'));
|
|
218
264
|
});
|
|
219
265
|
}
|
|
220
266
|
el.closePopup = () => closePopup(el, true);
|
|
221
267
|
el.openPopup = () => openPopup(el);
|
|
222
268
|
el.togglePopup = () => togglePopup(el);
|
|
269
|
+
const existingCloseRequest = closeRequestHandlers.get(el);
|
|
270
|
+
if (existingCloseRequest)
|
|
271
|
+
el.removeEventListener('ty:close-popup', existingCloseRequest);
|
|
223
272
|
const closeRequestHandler = (e) => handleCloseRequest(el, e);
|
|
224
273
|
closeRequestHandlers.set(el, closeRequestHandler);
|
|
225
274
|
el.addEventListener('ty:close-popup', closeRequestHandler);
|
|
@@ -160,10 +160,13 @@ export declare class TyRadioGroup extends TyComponent<RadioGroupState> implement
|
|
|
160
160
|
* the accessors after upgrade. Each radio fires ty-radio-connected once
|
|
161
161
|
* upgraded, which re-syncs the group.
|
|
162
162
|
*/
|
|
163
|
+
/** Focus the group's tab stop: the checked radio, else the first enabled one. */
|
|
164
|
+
focus(options?: FocusOptions): void;
|
|
163
165
|
private getRadios;
|
|
164
166
|
private syncChildren;
|
|
165
167
|
/** Report `required` to the owning form — without this the asterisk
|
|
166
|
-
showed but an empty group submitted anyway.
|
|
168
|
+
showed but an empty group submitted anyway. The message is a native
|
|
169
|
+
radio's; it needs a name, a nameless radio never reports valueMissing. */
|
|
167
170
|
private updateValidity;
|
|
168
171
|
private handleRadioSelect;
|
|
169
172
|
/**
|
package/lib/components/radio.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ensureStyles } from "../utils/styles.js";
|
|
|
4
4
|
import { syncCustomFlavorSheet } from "../utils/flavor-sheet.js";
|
|
5
5
|
import { radioStyles, radioCustomFlavorCss } from "../styles/radio.js";
|
|
6
6
|
import { REQUIRED_ICON_SVG } from "../utils/icons.js";
|
|
7
|
+
import { nativeMessage } from "../utils/validity.js";
|
|
7
8
|
export class TyRadio extends TyComponent {
|
|
8
9
|
constructor() {
|
|
9
10
|
super();
|
|
@@ -184,6 +185,14 @@ this.syncChildren();
|
|
|
184
185
|
getFormValue() {
|
|
185
186
|
return this.value || null;
|
|
186
187
|
}
|
|
188
|
+
focus(options) {
|
|
189
|
+
const enabled = this.getRadios().filter((r) => !r.disabled);
|
|
190
|
+
const stop = enabled.find((r) => r.checked) ?? enabled[0];
|
|
191
|
+
if (stop)
|
|
192
|
+
stop.focus(options);
|
|
193
|
+
else
|
|
194
|
+
super.focus(options);
|
|
195
|
+
}
|
|
187
196
|
getRadios() {
|
|
188
197
|
return Array.from(this.querySelectorAll("ty-radio"))
|
|
189
198
|
.filter((r) => typeof r.setTabStop === "function");
|
|
@@ -215,7 +224,7 @@ updateValidity() {
|
|
|
215
224
|
if (this.required && !this.disabled && !this.value) {
|
|
216
225
|
const anchor = this.getRadios().find((r) => !r.disabled);
|
|
217
226
|
const target = anchor?.shadowRoot?.querySelector(".radio-container");
|
|
218
|
-
this.applyValidity({ valueMissing: true }, "
|
|
227
|
+
this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "radio", name: "ty", required: true }), target ?? undefined);
|
|
219
228
|
}
|
|
220
229
|
else {
|
|
221
230
|
this.applyValidity({});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
2
2
|
import { resizeObserverStyles } from '../styles/resize-observer.js';
|
|
3
3
|
import { updateSize, removeSize } from '../utils/resize-observer.js';
|
|
4
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
4
5
|
export class TyResizeObserver extends HTMLElement {
|
|
5
6
|
static get observedAttributes() {
|
|
6
7
|
return ['id', 'debounce'];
|
|
@@ -20,6 +21,7 @@ ensureStyles(shadow, { css: resizeObserverStyles, id: 'ty-resize-observer' });
|
|
|
20
21
|
shadow.innerHTML = '<slot></slot>';
|
|
21
22
|
}
|
|
22
23
|
connectedCallback() {
|
|
24
|
+
upgradeProperties(this);
|
|
23
25
|
this.setupObserver();
|
|
24
26
|
}
|
|
25
27
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
2
2
|
import { scrollContainerStyles } from '../styles/scroll-container.js';
|
|
3
3
|
import { CustomScrollbar } from '../utils/custom-scrollbar.js';
|
|
4
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
4
5
|
export class TyScrollContainer extends HTMLElement {
|
|
5
6
|
static get observedAttributes() {
|
|
6
7
|
return ['no-shadow', 'max-height', 'hide-scrollbar', 'custom-scrollbar', 'overflow-x', 'scroll-anchoring'];
|
|
@@ -112,6 +113,7 @@ this._shadowLeft = shadow.querySelector('.shadow-left');
|
|
|
112
113
|
this._shadowRight = shadow.querySelector('.shadow-right');
|
|
113
114
|
}
|
|
114
115
|
connectedCallback() {
|
|
116
|
+
upgradeProperties(this);
|
|
115
117
|
this._scrollWrapper?.addEventListener('scroll', this._onScroll, { passive: true });
|
|
116
118
|
this._resizeObserver = new ResizeObserver(() => {
|
|
117
119
|
this._scrollbar?.update();
|
|
@@ -73,6 +73,11 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
73
73
|
visual: boolean;
|
|
74
74
|
default: boolean;
|
|
75
75
|
};
|
|
76
|
+
error: {
|
|
77
|
+
type: "string";
|
|
78
|
+
visual: boolean;
|
|
79
|
+
default: string;
|
|
80
|
+
};
|
|
76
81
|
noClear: {
|
|
77
82
|
type: "boolean";
|
|
78
83
|
visual: boolean;
|
|
@@ -190,11 +195,19 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
190
195
|
private _childObserver;
|
|
191
196
|
private _selectedClone;
|
|
192
197
|
private _customFlavorSheet;
|
|
198
|
+
private _ariaTrigger;
|
|
193
199
|
constructor();
|
|
194
200
|
/** TyComponent handles property capture automatically. */
|
|
195
201
|
protected onConnect(): void;
|
|
196
202
|
/** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
|
|
197
203
|
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;
|
|
198
211
|
protected onDisconnect(): void;
|
|
199
212
|
/** Handle state synchronization BEFORE render. */
|
|
200
213
|
protected onPropertiesChanged(changes: PropertyChange[]): void;
|
|
@@ -207,6 +220,8 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
207
220
|
private applyLoadingState;
|
|
208
221
|
/** Form value: a plain entry for single, FormData with repeated entries for multiple (HTMX standard). */
|
|
209
222
|
protected getFormValue(): FormDataEntryValue | FormData | null;
|
|
223
|
+
/** Focus the trigger: a slotted one, else the field. The host itself isn't focusable. */
|
|
224
|
+
focus(options?: FocusOptions): void;
|
|
210
225
|
private parseValue;
|
|
211
226
|
/** Initialize component state from the value property / pre-selected children. */
|
|
212
227
|
private initializeState;
|
|
@@ -367,8 +382,9 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
367
382
|
* Reflect constraint validation to the owning <form> via ElementInternals.
|
|
368
383
|
* Without this `required` is only cosmetic — form.checkValidity() (and a
|
|
369
384
|
* real submit) never actually blocks on an empty required select. Mirrors
|
|
370
|
-
* ty-input's updateValidity(): empty + required => valueMissing
|
|
371
|
-
* fields are barred from constraint
|
|
385
|
+
* ty-input's updateValidity(): empty + required => valueMissing, with a
|
|
386
|
+
* native <select>'s message; disabled fields are barred from constraint
|
|
387
|
+
* validation.
|
|
372
388
|
*/
|
|
373
389
|
private updateValidity;
|
|
374
390
|
/**
|
|
@@ -381,6 +397,16 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
381
397
|
private renderDesktop;
|
|
382
398
|
private renderMobile;
|
|
383
399
|
private setupMobileEventListeners;
|
|
400
|
+
/**
|
|
401
|
+
* The dialog was closed by someone else — a ty-modal around this select
|
|
402
|
+
* closes the dialogs still open inside it. Run our own close too, so the
|
|
403
|
+
* open state, aria-expanded and the scroll lock follow. Watches the `open`
|
|
404
|
+
* attribute rather than the `close` event: that one comes a task later,
|
|
405
|
+
* so right after modal.hide() the stub would still say expanded. Our own
|
|
406
|
+
* close() calls land here with the state already closed, and a dialog
|
|
407
|
+
* reopened since then is open again: both are no-ops.
|
|
408
|
+
*/
|
|
409
|
+
private syncOnOutsideClose;
|
|
384
410
|
private handleMobileStubClick;
|
|
385
411
|
/** Keyboard-open the (closed) mobile stub — see handleStubKeydown for why this is needed. */
|
|
386
412
|
private handleMobileStubKeydown;
|
|
@@ -442,7 +468,9 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
442
468
|
* Clear the entire selection programmatically WITH a change event
|
|
443
469
|
* (action: 'clear'). No-op if nothing is selected. Exists so slot="trigger"
|
|
444
470
|
* consumers (custom chrome, built-in clear UI hidden by design) can wire
|
|
445
|
-
* their own clear icon's click handler to it.
|
|
471
|
+
* their own clear icon's click handler to it. Shows no validation
|
|
472
|
+
* message (a code reset after a save shouldn't flag the field); the
|
|
473
|
+
* built-in × does.
|
|
446
474
|
*/
|
|
447
475
|
clear(): void;
|
|
448
476
|
get value(): string;
|
|
@@ -467,6 +495,8 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
467
495
|
set noClear(value: boolean);
|
|
468
496
|
get required(): boolean;
|
|
469
497
|
set required(value: boolean);
|
|
498
|
+
get error(): string;
|
|
499
|
+
set error(value: string);
|
|
470
500
|
get notSearchable(): boolean;
|
|
471
501
|
set notSearchable(value: boolean);
|
|
472
502
|
get externalSearch(): boolean;
|
|
@@ -481,6 +511,7 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
481
511
|
set placement(value: Placement | "");
|
|
482
512
|
get flavor(): Flavor;
|
|
483
513
|
set flavor(value: Flavor);
|
|
514
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
484
515
|
get form(): HTMLFormElement | null;
|
|
485
516
|
}
|
|
486
517
|
export {};
|