tyrell-components 1.0.0-TC57 → 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 +20 -15
- package/lib/base/ty-component.js +58 -25
- package/lib/components/button.d.ts +14 -0
- package/lib/components/button.js +55 -7
- package/lib/components/calendar-month.d.ts +7 -1
- package/lib/components/calendar-month.js +35 -28
- package/lib/components/calendar-navigation.js +19 -6
- package/lib/components/calendar.d.ts +25 -4
- package/lib/components/calendar.js +98 -67
- package/lib/components/checkbox.d.ts +6 -1
- package/lib/components/checkbox.js +20 -6
- package/lib/components/copy.d.ts +2 -0
- package/lib/components/copy.js +15 -31
- package/lib/components/date-picker.d.ts +52 -19
- package/lib/components/date-picker.js +211 -147
- package/lib/components/file-upload.d.ts +2 -1
- package/lib/components/file-upload.js +8 -3
- package/lib/components/input.d.ts +4 -6
- package/lib/components/input.js +37 -62
- package/lib/components/modal.js +86 -9
- package/lib/components/option.js +2 -5
- package/lib/components/popup.js +99 -11
- package/lib/components/radio.d.ts +8 -1
- package/lib/components/radio.js +14 -29
- package/lib/components/resize-observer.js +2 -0
- package/lib/components/scroll-container.js +2 -0
- package/lib/components/select.d.ts +53 -6
- package/lib/components/select.js +127 -29
- package/lib/components/switch.d.ts +7 -1
- package/lib/components/switch.js +20 -6
- package/lib/components/tabs.js +1 -1
- package/lib/components/textarea.d.ts +45 -4
- package/lib/components/textarea.js +75 -18
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/styles/button.js +21 -19
- 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 +15 -3
- 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 +24 -3
- 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/styles/textarea.d.ts +1 -1
- package/lib/styles/textarea.js +7 -0
- 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/upgrade-properties.d.ts +21 -0
- package/lib/utils/upgrade-properties.js +47 -0
- package/lib/utils/validity.d.ts +89 -5
- package/lib/utils/validity.js +198 -25
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -1
- 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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { TyComponent } from "../base/ty-component.js";
|
|
2
2
|
import { ensureStyles } from "../utils/styles.js";
|
|
3
3
|
import { fileUploadStyles } from "../styles/file-upload.js";
|
|
4
|
+
import { nativeMessage } from "../utils/validity.js";
|
|
5
|
+
const DEFAULT_PLACEHOLDER = "Drop files here or click to browse";
|
|
4
6
|
const UPLOAD_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>`;
|
|
5
7
|
const FILE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`;
|
|
6
8
|
const X_ICON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
@@ -52,7 +54,7 @@ this.updateValidity();
|
|
|
52
54
|
updateValidity() {
|
|
53
55
|
if (this.required && !this.disabled && this._files.length === 0) {
|
|
54
56
|
const anchor = this.shadowRoot?.querySelector(".drop-zone");
|
|
55
|
-
this.applyValidity({ valueMissing: true }, "
|
|
57
|
+
this.applyValidity({ valueMissing: true }, nativeMessage("input", { type: "file", required: true, multiple: this.multiple }), anchor ?? undefined);
|
|
56
58
|
}
|
|
57
59
|
else {
|
|
58
60
|
this.applyValidity({});
|
|
@@ -380,9 +382,12 @@ const hintEl = container.querySelector(".upload-hint");
|
|
|
380
382
|
if (this._files.length > 0) {
|
|
381
383
|
hintEl.innerHTML = `<strong>${this._files.length} file${this._files.length !== 1 ? "s" : ""} selected</strong>`;
|
|
382
384
|
}
|
|
383
|
-
else {
|
|
385
|
+
else if (!this.placeholder || this.placeholder === DEFAULT_PLACEHOLDER) {
|
|
384
386
|
hintEl.innerHTML = `Drop files here or <span class="browse-link">click to browse</span>`;
|
|
385
387
|
}
|
|
388
|
+
else {
|
|
389
|
+
hintEl.textContent = this.placeholder;
|
|
390
|
+
}
|
|
386
391
|
const subHintEl = container.querySelector(".upload-sub-hint");
|
|
387
392
|
if (this.accept && this._files.length === 0) {
|
|
388
393
|
subHintEl.textContent = `Accepted: ${this.accept}`;
|
|
@@ -433,7 +438,7 @@ default: "",
|
|
|
433
438
|
placeholder: {
|
|
434
439
|
type: "string",
|
|
435
440
|
visual: true,
|
|
436
|
-
default:
|
|
441
|
+
default: DEFAULT_PLACEHOLDER,
|
|
437
442
|
},
|
|
438
443
|
error: {
|
|
439
444
|
type: "string",
|
|
@@ -137,10 +137,7 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
137
137
|
};
|
|
138
138
|
private _shadowValue;
|
|
139
139
|
private _isFocused;
|
|
140
|
-
/** The user typed since the field was created: blur then shows its validation message. */
|
|
141
|
-
private _userEdited;
|
|
142
140
|
private _listenersSetup;
|
|
143
|
-
private _errorAutoDanger;
|
|
144
141
|
private _customFlavorSheet;
|
|
145
142
|
private _inputHandler;
|
|
146
143
|
private _changeHandler;
|
|
@@ -235,10 +232,11 @@ export declare class TyInput extends TyComponent<InputState> implements TyInputE
|
|
|
235
232
|
*/
|
|
236
233
|
private setupPasswordToggle;
|
|
237
234
|
/**
|
|
238
|
-
* pattern and maxlength go on the inner <input>: pattern for the
|
|
239
|
-
* own patternMismatch check,
|
|
235
|
+
* pattern, minlength and maxlength go on the inner <input>: pattern for the
|
|
236
|
+
* browser's own patternMismatch check, minlength for its tooShort message
|
|
237
|
+
* after the user types, maxlength so typing stops at the limit like a
|
|
240
238
|
* native field. Formatted (numeric) types show a display string, so they
|
|
241
|
-
* get
|
|
239
|
+
* get none.
|
|
242
240
|
*/
|
|
243
241
|
private syncConstraintAttributes;
|
|
244
242
|
/**
|
package/lib/components/input.js
CHANGED
|
@@ -6,6 +6,7 @@ import { inputStyles, inputCustomFlavorCss } from '../styles/input.js';
|
|
|
6
6
|
import { formatNumber, parseNumericValue, shouldFormat as shouldFormatType } from '../utils/number-format.js';
|
|
7
7
|
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
8
8
|
import { REQUIRED_ICON_SVG } from '../utils/icons.js';
|
|
9
|
+
import { nativeMessage, nativeValidity, lengthValidity, toLimit } from '../utils/validity.js';
|
|
9
10
|
const EYE_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/></svg>`;
|
|
10
11
|
const EYE_OFF_ICON_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143"/><path d="m2 2 20 20"/></svg>`;
|
|
11
12
|
export class TyInput extends TyComponent {
|
|
@@ -13,9 +14,7 @@ constructor() {
|
|
|
13
14
|
super();
|
|
14
15
|
this._shadowValue = null;
|
|
15
16
|
this._isFocused = false;
|
|
16
|
-
this._userEdited = false;
|
|
17
17
|
this._listenersSetup = false;
|
|
18
|
-
this._errorAutoDanger = false;
|
|
19
18
|
this._customFlavorSheet = null;
|
|
20
19
|
this._inputHandler = null;
|
|
21
20
|
this._changeHandler = null;
|
|
@@ -60,6 +59,10 @@ this._localeObserver = observeLocaleChanges(this, () => {
|
|
|
60
59
|
this.render();
|
|
61
60
|
});
|
|
62
61
|
this._syncCustomFlavor();
|
|
62
|
+
if (this._rendered) {
|
|
63
|
+
this.render();
|
|
64
|
+
this.setupEventListeners();
|
|
65
|
+
}
|
|
63
66
|
}
|
|
64
67
|
_syncCustomFlavor() {
|
|
65
68
|
this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, this.flavor, ({ base }) => inputCustomFlavorCss(base));
|
|
@@ -92,20 +95,6 @@ case 'type':
|
|
|
92
95
|
const currentValue = this.getProperty('value') || '';
|
|
93
96
|
this._shadowValue = this.parseShadowValue(currentValue);
|
|
94
97
|
break;
|
|
95
|
-
case 'error':
|
|
96
|
-
if (newValue) {
|
|
97
|
-
if (this.getProperty('flavor') === 'neutral') {
|
|
98
|
-
this.setProperty('flavor', 'danger');
|
|
99
|
-
this._errorAutoDanger = true;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else if (this._errorAutoDanger) {
|
|
103
|
-
if (this.getProperty('flavor') === 'danger') {
|
|
104
|
-
this.setProperty('flavor', 'neutral');
|
|
105
|
-
}
|
|
106
|
-
this._errorAutoDanger = false;
|
|
107
|
-
}
|
|
108
|
-
break;
|
|
109
98
|
}
|
|
110
99
|
}
|
|
111
100
|
}
|
|
@@ -393,6 +382,9 @@ const existingWrapper = shadow.querySelector('.input-wrapper');
|
|
|
393
382
|
const existingLabel = shadow.querySelector('.ty-field-label');
|
|
394
383
|
const existingError = shadow.querySelector('.error-message');
|
|
395
384
|
const classes = this.buildClassList();
|
|
385
|
+
const focused = !!existingInput && shadow.activeElement === existingInput;
|
|
386
|
+
if (!focused)
|
|
387
|
+
this._isFocused = false;
|
|
396
388
|
const inputType = ['password', 'email', 'tel', 'url'].includes(this.type)
|
|
397
389
|
? this.type
|
|
398
390
|
: 'text';
|
|
@@ -403,7 +395,10 @@ const inputMode = shouldFormatType(this.type) ? 'decimal'
|
|
|
403
395
|
: undefined;
|
|
404
396
|
const displayValue = this.getDisplayValue();
|
|
405
397
|
if (existingInput && existingWrapper) {
|
|
406
|
-
|
|
398
|
+
const revealed = inputType === 'password' &&
|
|
399
|
+
shadow.querySelector('.password-toggle')?.getAttribute('aria-pressed') === 'true';
|
|
400
|
+
existingInput.type = revealed ? 'text' : inputType;
|
|
401
|
+
if (existingInput.value !== displayValue)
|
|
407
402
|
existingInput.value = displayValue;
|
|
408
403
|
existingInput.placeholder = this.placeholder;
|
|
409
404
|
existingInput.name = this.name;
|
|
@@ -412,6 +407,7 @@ existingInput.inputMode = inputMode;
|
|
|
412
407
|
else
|
|
413
408
|
existingInput.removeAttribute('inputmode');
|
|
414
409
|
existingWrapper.className = `input-wrapper ${classes}`;
|
|
410
|
+
existingWrapper.classList.toggle('focused', focused);
|
|
415
411
|
existingInput.disabled = this.disabled;
|
|
416
412
|
if (this.disabled) {
|
|
417
413
|
existingInput.setAttribute('disabled', '');
|
|
@@ -517,7 +513,12 @@ return;
|
|
|
517
513
|
toggle.addEventListener('mousedown', (e) => e.preventDefault());
|
|
518
514
|
toggle.addEventListener('click', () => {
|
|
519
515
|
const reveal = inputEl.type === 'password';
|
|
516
|
+
const { selectionStart, selectionEnd, selectionDirection } = inputEl;
|
|
520
517
|
inputEl.type = reveal ? 'text' : 'password';
|
|
518
|
+
if (selectionStart !== null && shadow.activeElement === inputEl) {
|
|
519
|
+
inputEl.getBoundingClientRect();
|
|
520
|
+
inputEl.setSelectionRange(selectionStart, selectionEnd, selectionDirection ?? undefined);
|
|
521
|
+
}
|
|
521
522
|
toggle.innerHTML = reveal ? EYE_OFF_ICON_SVG : EYE_ICON_SVG;
|
|
522
523
|
toggle.setAttribute('aria-label', reveal ? 'Hide password' : 'Show password');
|
|
523
524
|
toggle.setAttribute('aria-pressed', String(reveal));
|
|
@@ -527,6 +528,7 @@ syncConstraintAttributes(inputEl) {
|
|
|
527
528
|
const text = !shouldFormatType(this.type);
|
|
528
529
|
const set = (name, v) => text && v !== '' ? inputEl.setAttribute(name, v) : inputEl.removeAttribute(name);
|
|
529
530
|
set('pattern', this.pattern);
|
|
531
|
+
set('minlength', toLimit(this.getProperty('minlength')) != null ? String(this.minLength) : '');
|
|
530
532
|
set('maxlength', toLimit(this.getProperty('maxlength')) != null ? String(this.maxLength) : '');
|
|
531
533
|
}
|
|
532
534
|
updateValidity() {
|
|
@@ -538,7 +540,7 @@ return;
|
|
|
538
540
|
const current = inputEl ? inputEl.value : String(this.getProperty('value') ?? '');
|
|
539
541
|
if (current.trim() === '') {
|
|
540
542
|
if (this.required) {
|
|
541
|
-
this.applyValidity({ valueMissing: true }, nativeMessage('
|
|
543
|
+
this.applyValidity({ valueMissing: true }, nativeMessage('input', { type: 'text', required: true }), inputEl ?? undefined);
|
|
542
544
|
}
|
|
543
545
|
else {
|
|
544
546
|
this.applyValidity({});
|
|
@@ -553,29 +555,29 @@ messages.push(message);
|
|
|
553
555
|
};
|
|
554
556
|
if (shouldFormatType(this.type)) {
|
|
555
557
|
const n = typeof this._shadowValue === 'number' ? this._shadowValue : null;
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
558
|
+
if (n !== null && (this.min || this.max)) {
|
|
559
|
+
const { validity, message } = nativeValidity('input', { type: 'number', step: 'any', min: this.min, max: this.max }, String(n));
|
|
560
|
+
if (validity.rangeUnderflow)
|
|
561
|
+
fail('rangeUnderflow', message);
|
|
562
|
+
if (validity.rangeOverflow)
|
|
563
|
+
fail('rangeOverflow', message);
|
|
564
|
+
}
|
|
563
565
|
}
|
|
564
566
|
else {
|
|
565
567
|
if (inputEl?.validity.typeMismatch)
|
|
566
568
|
fail('typeMismatch', inputEl.validationMessage);
|
|
567
569
|
if (inputEl?.validity.patternMismatch)
|
|
568
570
|
fail('patternMismatch', inputEl.validationMessage);
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
571
|
+
const { tooShort, tooLong } = lengthValidity(current, {
|
|
572
|
+
minlength: this.getProperty('minlength'),
|
|
573
|
+
maxlength: this.getProperty('maxlength'),
|
|
574
|
+
tooShortMessage: this.tooShortMessage,
|
|
575
|
+
tooLongMessage: this.tooLongMessage,
|
|
576
|
+
}, inputEl);
|
|
577
|
+
if (tooShort)
|
|
578
|
+
fail('tooShort', tooShort);
|
|
579
|
+
if (tooLong)
|
|
580
|
+
fail('tooLong', tooLong);
|
|
579
581
|
}
|
|
580
582
|
this.applyValidity(flags, messages[0], inputEl ?? undefined);
|
|
581
583
|
}
|
|
@@ -737,33 +739,6 @@ return Math.max(0, Math.min(5000, Math.floor(num)));
|
|
|
737
739
|
}
|
|
738
740
|
}
|
|
739
741
|
};
|
|
740
|
-
let probe = null;
|
|
741
|
-
function nativeMessage(type, attrs, value) {
|
|
742
|
-
probe ?? (probe = document.createElement('input'));
|
|
743
|
-
for (const name of Array.from(probe.getAttributeNames()))
|
|
744
|
-
probe.removeAttribute(name);
|
|
745
|
-
probe.type = type;
|
|
746
|
-
for (const [name, v] of Object.entries(attrs))
|
|
747
|
-
if (v !== '' || name === 'required')
|
|
748
|
-
probe.setAttribute(name, v);
|
|
749
|
-
probe.value = value;
|
|
750
|
-
return probe.validationMessage;
|
|
751
|
-
}
|
|
752
|
-
function fillMessage(template, vars) {
|
|
753
|
-
return template.replace(/\{(\w+)\}/g, (match, key) => vars[key] != null ? String(vars[key]) : match);
|
|
754
|
-
}
|
|
755
|
-
function toLimit(v) {
|
|
756
|
-
if (v === null || v === undefined || String(v).trim() === '')
|
|
757
|
-
return null;
|
|
758
|
-
const n = Number(v);
|
|
759
|
-
return Number.isInteger(n) && n >= 0 ? n : null;
|
|
760
|
-
}
|
|
761
|
-
function toNumber(v) {
|
|
762
|
-
if (v === null || v === undefined || String(v).trim() === '')
|
|
763
|
-
return null;
|
|
764
|
-
const n = Number(v);
|
|
765
|
-
return Number.isFinite(n) ? n : null;
|
|
766
|
-
}
|
|
767
742
|
if (!customElements.get('ty-input')) {
|
|
768
743
|
customElements.define('ty-input', TyInput);
|
|
769
744
|
}
|
package/lib/components/modal.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
2
2
|
import { coerceBoolean } from '../utils/property-manager.js';
|
|
3
3
|
import { ensureStyles } from '../utils/styles.js';
|
|
4
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
4
5
|
import { modalStyles } from '../styles/modal.js';
|
|
5
6
|
const backdropClickHandlers = new WeakMap();
|
|
6
7
|
const escapeKeyHandlers = new WeakMap();
|
|
@@ -9,6 +10,13 @@ const hoverEnterHandlers = new WeakMap();
|
|
|
9
10
|
const hoverLeaveHandlers = new WeakMap();
|
|
10
11
|
const modalIds = new WeakMap();
|
|
11
12
|
const pendingCloseReasons = new WeakMap();
|
|
13
|
+
const detachedOpen = new WeakSet();
|
|
14
|
+
const openStack = [];
|
|
15
|
+
function dropFromStack(el) {
|
|
16
|
+
const i = openStack.indexOf(el);
|
|
17
|
+
if (i >= 0)
|
|
18
|
+
openStack.splice(i, 1);
|
|
19
|
+
}
|
|
12
20
|
const CLOSE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`;
|
|
13
21
|
function parseBoolAttr(el, name) {
|
|
14
22
|
if (!el.hasAttribute(name)) {
|
|
@@ -98,13 +106,72 @@ event.preventDefault();
|
|
|
98
106
|
closeModal(el, 'backdrop');
|
|
99
107
|
}
|
|
100
108
|
}
|
|
109
|
+
function fromNestedDialog(dialog, event) {
|
|
110
|
+
for (const node of event.composedPath()) {
|
|
111
|
+
if (node === dialog)
|
|
112
|
+
return false;
|
|
113
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
function closeNestedDialogs(root) {
|
|
119
|
+
walkNested(root, (node) => {
|
|
120
|
+
if (node instanceof TyModal && node.open)
|
|
121
|
+
node.removeAttribute('open');
|
|
122
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
123
|
+
node.close();
|
|
124
|
+
return false;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function hasOpenNestedDialog(root) {
|
|
128
|
+
return walkNested(root, (node) => node instanceof HTMLDialogElement && node.matches(':modal'));
|
|
129
|
+
}
|
|
130
|
+
function walkNested(root, visit, seen = new WeakSet()) {
|
|
131
|
+
const walk = (node) => {
|
|
132
|
+
if (seen.has(node))
|
|
133
|
+
return false;
|
|
134
|
+
seen.add(node);
|
|
135
|
+
if (visit(node))
|
|
136
|
+
return true;
|
|
137
|
+
if (node.shadowRoot && walkNested(node.shadowRoot, visit, seen))
|
|
138
|
+
return true;
|
|
139
|
+
if (node instanceof HTMLSlotElement) {
|
|
140
|
+
for (const assigned of node.assignedElements({ flatten: true })) {
|
|
141
|
+
if (walk(assigned) || walkNested(assigned, visit, seen))
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
return Array.from(root.querySelectorAll('*')).some(walk);
|
|
148
|
+
}
|
|
101
149
|
function handleEscapeKey(el, event) {
|
|
150
|
+
const dialog = event.currentTarget;
|
|
151
|
+
if (fromNestedDialog(dialog, event))
|
|
152
|
+
return;
|
|
102
153
|
event.stopPropagation();
|
|
103
154
|
if (event.key === 'Escape') {
|
|
104
155
|
event.preventDefault();
|
|
105
156
|
closeModal(el, 'escape');
|
|
106
157
|
}
|
|
107
158
|
}
|
|
159
|
+
function handleDocumentEscape(event) {
|
|
160
|
+
if (event.key !== 'Escape' || event.defaultPrevented)
|
|
161
|
+
return;
|
|
162
|
+
const top = openStack[openStack.length - 1];
|
|
163
|
+
if (!top)
|
|
164
|
+
return;
|
|
165
|
+
if (event.composedPath().some((n) => n instanceof HTMLDialogElement && n.open))
|
|
166
|
+
return;
|
|
167
|
+
if (hasOpenNestedDialog(top))
|
|
168
|
+
return;
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
if (getModalAttributes(top).closeOnEscape)
|
|
171
|
+
closeModal(top, 'escape');
|
|
172
|
+
}
|
|
173
|
+
if (typeof document !== 'undefined')
|
|
174
|
+
document.addEventListener('keydown', handleDocumentEscape);
|
|
108
175
|
function handleCloseButtonClick(el, event) {
|
|
109
176
|
event.preventDefault();
|
|
110
177
|
event.stopPropagation();
|
|
@@ -155,7 +222,7 @@ dialog.addEventListener('keydown', handler);
|
|
|
155
222
|
}
|
|
156
223
|
else {
|
|
157
224
|
const handler = (e) => {
|
|
158
|
-
if (e.key === 'Escape')
|
|
225
|
+
if (e.key === 'Escape' && !fromNestedDialog(dialog, e))
|
|
159
226
|
e.preventDefault();
|
|
160
227
|
};
|
|
161
228
|
escapeKeyHandlers.set(el, handler);
|
|
@@ -240,19 +307,20 @@ setupCloseButtonHover(el, dialog);
|
|
|
240
307
|
if (attributes.open) {
|
|
241
308
|
if (!dialog.open) {
|
|
242
309
|
lockScroll(modalId);
|
|
243
|
-
if (attributes.backdrop) {
|
|
244
310
|
dialog.showModal();
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
dialog.show();
|
|
248
|
-
}
|
|
311
|
+
dropFromStack(el);
|
|
312
|
+
openStack.push(el);
|
|
249
313
|
syncBackdropZoom();
|
|
314
|
+
if (!detachedOpen.delete(el))
|
|
250
315
|
dispatchModalEvent(el, 'open', {});
|
|
251
316
|
}
|
|
252
317
|
}
|
|
253
318
|
else {
|
|
319
|
+
detachedOpen.delete(el);
|
|
320
|
+
dropFromStack(el);
|
|
254
321
|
if (dialog.open) {
|
|
255
322
|
unlockScroll(modalId);
|
|
323
|
+
closeNestedDialogs(el);
|
|
256
324
|
dialog.close();
|
|
257
325
|
syncBackdropZoom();
|
|
258
326
|
const reason = pendingCloseReasons.get(el) ?? 'programmatic';
|
|
@@ -262,9 +330,11 @@ dispatchModalEvent(el, 'close', { reason });
|
|
|
262
330
|
}
|
|
263
331
|
syncBackdropZoom();
|
|
264
332
|
dialog.onclose = (event) => {
|
|
265
|
-
if (event.target !== dialog)
|
|
333
|
+
if (event.target !== dialog || dialog.open || !el.isConnected)
|
|
266
334
|
return;
|
|
267
335
|
unlockScroll(modalId);
|
|
336
|
+
dropFromStack(el);
|
|
337
|
+
closeNestedDialogs(el);
|
|
268
338
|
if (el.hasAttribute('open')) {
|
|
269
339
|
el.removeAttribute('open');
|
|
270
340
|
dispatchModalEvent(el, 'close', {
|
|
@@ -276,13 +346,15 @@ syncBackdropZoom();
|
|
|
276
346
|
};
|
|
277
347
|
}
|
|
278
348
|
function syncBackdropZoom() {
|
|
279
|
-
const
|
|
280
|
-
|
|
349
|
+
const zoom = Array.from(document.querySelectorAll('ty-modal[open], ty-dialog[open]'))
|
|
350
|
+
.some((m) => parseBoolAttr(m, 'open') && !parseBoolAttr(m, 'no-backdrop') && !parseBoolAttr(m, 'no-zoom'));
|
|
351
|
+
document.documentElement.classList.toggle('ty-modal-zoom', zoom);
|
|
281
352
|
}
|
|
282
353
|
function cleanup(el) {
|
|
283
354
|
const modalId = modalIds.get(el);
|
|
284
355
|
const shadowRoot = el.shadowRoot;
|
|
285
356
|
const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
|
|
357
|
+
dropFromStack(el);
|
|
286
358
|
if (modalId) {
|
|
287
359
|
unlockScroll(modalId);
|
|
288
360
|
modalIds.delete(el);
|
|
@@ -290,6 +362,10 @@ modalIds.delete(el);
|
|
|
290
362
|
syncBackdropZoom();
|
|
291
363
|
if (!dialog)
|
|
292
364
|
return;
|
|
365
|
+
if (dialog.open) {
|
|
366
|
+
dialog.close();
|
|
367
|
+
detachedOpen.add(el);
|
|
368
|
+
}
|
|
293
369
|
const backdropHandler = backdropClickHandlers.get(el);
|
|
294
370
|
if (backdropHandler) {
|
|
295
371
|
dialog.removeEventListener('click', backdropHandler);
|
|
@@ -349,6 +425,7 @@ super();
|
|
|
349
425
|
this.attachShadow({ mode: 'open' });
|
|
350
426
|
}
|
|
351
427
|
connectedCallback() {
|
|
428
|
+
upgradeProperties(this);
|
|
352
429
|
render(this);
|
|
353
430
|
this.show = () => openModal(this);
|
|
354
431
|
this.hide = (opts) => closeModal(this, 'programmatic', opts);
|
package/lib/components/option.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensureStyles } from '../utils/styles.js';
|
|
2
2
|
import { coerceBoolean } from '../utils/property-manager.js';
|
|
3
|
+
import { upgradeProperties } from '../utils/upgrade-properties.js';
|
|
3
4
|
import { optionStyles } from '../styles/option.js';
|
|
4
5
|
const CHECK_SVG = `
|
|
5
6
|
<svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
@@ -23,11 +24,7 @@ static get observedAttributes() {
|
|
|
23
24
|
return ['value', 'disabled', 'selected', 'highlighted', 'hidden'];
|
|
24
25
|
}
|
|
25
26
|
connectedCallback() {
|
|
26
|
-
|
|
27
|
-
if (instanceValue) {
|
|
28
|
-
this._value = instanceValue.value;
|
|
29
|
-
delete this.value;
|
|
30
|
-
}
|
|
27
|
+
upgradeProperties(this);
|
|
31
28
|
this.render();
|
|
32
29
|
}
|
|
33
30
|
attributeChangedCallback(name, oldValue, newValue) {
|
package/lib/components/popup.js
CHANGED
|
@@ -9,6 +9,9 @@ const closeTimers = new WeakMap();
|
|
|
9
9
|
const escapeHandlers = new WeakMap();
|
|
10
10
|
const closeRequestHandlers = new WeakMap();
|
|
11
11
|
const popupIds = new WeakMap();
|
|
12
|
+
const anchors = new WeakMap();
|
|
13
|
+
const escapeGuards = new WeakMap();
|
|
14
|
+
const pointerPresses = new WeakSet();
|
|
12
15
|
function parseBoolAttr(el, name) {
|
|
13
16
|
return el.hasAttribute(name);
|
|
14
17
|
}
|
|
@@ -87,29 +90,45 @@ dialog.close();
|
|
|
87
90
|
function openPopup(el) {
|
|
88
91
|
const shadowRoot = el.shadowRoot;
|
|
89
92
|
const dialog = shadowRoot ? getPopupDialog(shadowRoot) : null;
|
|
90
|
-
if (!dialog)
|
|
93
|
+
if (!dialog || !el.isConnected)
|
|
91
94
|
return;
|
|
95
|
+
const anchor = getAnchorElement(el);
|
|
92
96
|
if (dialog.open) {
|
|
93
97
|
const pending = closeTimers.get(el);
|
|
94
98
|
if (pending !== undefined) {
|
|
95
99
|
clearTimeout(pending);
|
|
96
100
|
closeTimers.delete(el);
|
|
97
101
|
dialog.classList.add('open');
|
|
102
|
+
if (anchor?.hasAttribute('aria-haspopup'))
|
|
103
|
+
anchor.setAttribute('aria-expanded', 'true');
|
|
98
104
|
}
|
|
99
105
|
return;
|
|
100
106
|
}
|
|
101
107
|
const popupId = getPopupId(el);
|
|
102
108
|
lockScroll(popupId);
|
|
103
109
|
dialog.showModal();
|
|
104
|
-
|
|
110
|
+
guardEscape(el);
|
|
105
111
|
if (anchor?.hasAttribute('aria-haspopup'))
|
|
106
112
|
anchor.setAttribute('aria-expanded', 'true');
|
|
107
113
|
requestAnimationFrame(() => {
|
|
108
114
|
updatePosition(el);
|
|
109
115
|
dialog.classList.add('preparing-animation');
|
|
110
|
-
|
|
116
|
+
dialog.getBoundingClientRect();
|
|
111
117
|
requestAnimationFrame(() => {
|
|
112
118
|
dialog.classList.add('open');
|
|
119
|
+
if (!el.matches(':focus-within')) {
|
|
120
|
+
const target = el.querySelector('[autofocus]');
|
|
121
|
+
target?.focus({ preventScroll: true });
|
|
122
|
+
if (!el.matches(':focus-within')) {
|
|
123
|
+
dialog.focus({ preventScroll: true });
|
|
124
|
+
if (target) {
|
|
125
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
126
|
+
if (dialog.open && shadowRoot.activeElement === dialog)
|
|
127
|
+
target.focus({ preventScroll: true });
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
113
132
|
el.dispatchEvent(new CustomEvent('open'));
|
|
114
133
|
});
|
|
115
134
|
});
|
|
@@ -140,7 +159,36 @@ event.preventDefault();
|
|
|
140
159
|
closePopup(el);
|
|
141
160
|
}
|
|
142
161
|
}
|
|
162
|
+
function fromNestedDialog(el, event) {
|
|
163
|
+
const dialog = el.shadowRoot ? getPopupDialog(el.shadowRoot) : null;
|
|
164
|
+
for (const node of event.composedPath()) {
|
|
165
|
+
if (node === dialog)
|
|
166
|
+
return false;
|
|
167
|
+
if (node instanceof HTMLDialogElement && node.matches(':modal'))
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
function guardEscape(el) {
|
|
173
|
+
if (escapeGuards.has(el))
|
|
174
|
+
return;
|
|
175
|
+
const guard = (e) => {
|
|
176
|
+
if (e.key === 'Escape' && getPopupAttributes(el).disableClose && !fromNestedDialog(el, e))
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
};
|
|
179
|
+
escapeGuards.set(el, guard);
|
|
180
|
+
document.addEventListener('keydown', guard, true);
|
|
181
|
+
}
|
|
182
|
+
function unguardEscape(el) {
|
|
183
|
+
const guard = escapeGuards.get(el);
|
|
184
|
+
if (!guard)
|
|
185
|
+
return;
|
|
186
|
+
document.removeEventListener('keydown', guard, true);
|
|
187
|
+
escapeGuards.delete(el);
|
|
188
|
+
}
|
|
143
189
|
function handleEscape(el, event) {
|
|
190
|
+
if (fromNestedDialog(el, event))
|
|
191
|
+
return;
|
|
144
192
|
event.stopPropagation();
|
|
145
193
|
if (event.key === 'Escape') {
|
|
146
194
|
const { disableClose } = getPopupAttributes(el);
|
|
@@ -164,26 +212,39 @@ return;
|
|
|
164
212
|
anchor.setAttribute('aria-haspopup', 'dialog');
|
|
165
213
|
if (!anchor.hasAttribute('aria-expanded'))
|
|
166
214
|
anchor.setAttribute('aria-expanded', 'false');
|
|
215
|
+
const previous = anchors.get(el) ?? anchor;
|
|
167
216
|
const existingHandler = anchorClickHandlers.get(el);
|
|
168
217
|
if (existingHandler) {
|
|
169
|
-
|
|
218
|
+
previous.removeEventListener('pointerdown', existingHandler);
|
|
170
219
|
}
|
|
171
220
|
const existingKeyHandler = anchorKeyClickHandlers.get(el);
|
|
172
221
|
if (existingKeyHandler) {
|
|
173
|
-
|
|
222
|
+
previous.removeEventListener('click', existingKeyHandler);
|
|
174
223
|
}
|
|
175
|
-
|
|
224
|
+
anchors.set(el, anchor);
|
|
225
|
+
const handler = (e) => {
|
|
226
|
+
pointerPresses.add(el);
|
|
227
|
+
handleAnchorClick(el, e);
|
|
228
|
+
};
|
|
176
229
|
anchorClickHandlers.set(el, handler);
|
|
177
230
|
anchor.addEventListener('pointerdown', handler);
|
|
178
231
|
const keyHandler = (e) => {
|
|
179
|
-
if (
|
|
232
|
+
if (pointerPresses.delete(el))
|
|
233
|
+
return;
|
|
234
|
+
const d = e.detail;
|
|
235
|
+
const click = typeof d === 'number' ? e : d?.originalEvent ?? e;
|
|
236
|
+
if (click.detail !== 0)
|
|
237
|
+
return;
|
|
238
|
+
if (click.composedPath().includes(el))
|
|
239
|
+
return;
|
|
180
240
|
handleAnchorClick(el, e);
|
|
181
241
|
};
|
|
182
242
|
anchorKeyClickHandlers.set(el, keyHandler);
|
|
183
243
|
anchor.addEventListener('click', keyHandler);
|
|
184
244
|
}
|
|
185
245
|
function cleanupAnchorEvents(el) {
|
|
186
|
-
const anchor =
|
|
246
|
+
const anchor = anchors.get(el);
|
|
247
|
+
anchors.delete(el);
|
|
187
248
|
const handler = anchorClickHandlers.get(el);
|
|
188
249
|
if (anchor && handler) {
|
|
189
250
|
anchor.removeEventListener('pointerdown', handler);
|
|
@@ -208,6 +269,7 @@ const dialog = document.createElement('dialog');
|
|
|
208
269
|
const container = document.createElement('div');
|
|
209
270
|
const content = document.createElement('slot');
|
|
210
271
|
dialog.className = 'popup-dialog';
|
|
272
|
+
dialog.tabIndex = -1;
|
|
211
273
|
container.className = 'popup-container';
|
|
212
274
|
content.id = 'popup-content';
|
|
213
275
|
shadowRoot.appendChild(dialog);
|
|
@@ -221,17 +283,40 @@ dialog.addEventListener('pointerdown', outsideHandler);
|
|
|
221
283
|
const escapeHandler = (e) => handleEscape(el, e);
|
|
222
284
|
escapeHandlers.set(el, escapeHandler);
|
|
223
285
|
el.addEventListener('keydown', escapeHandler);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
286
|
+
const inertKeys = (e) => {
|
|
287
|
+
if ((e.key === 'Enter' || e.key === ' ') && e.composedPath()[0] === dialog)
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
};
|
|
290
|
+
for (const type of ['keydown', 'keypress', 'keyup'])
|
|
291
|
+
dialog.addEventListener(type, inertKeys);
|
|
292
|
+
dialog.addEventListener('cancel', (e) => {
|
|
293
|
+
if (getPopupAttributes(el).disableClose)
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
});
|
|
296
|
+
new MutationObserver(() => {
|
|
297
|
+
if (dialog.open)
|
|
298
|
+
return;
|
|
299
|
+
clearTimeout(closeTimers.get(el));
|
|
300
|
+
closeTimers.delete(el);
|
|
301
|
+
pointerPresses.delete(el);
|
|
302
|
+
unguardEscape(el);
|
|
303
|
+
unlockScroll(getPopupId(el));
|
|
227
304
|
dialog.classList.remove('open');
|
|
228
305
|
dialog.classList.remove('preparing-animation');
|
|
306
|
+
const anchor = getAnchorElement(el);
|
|
307
|
+
if (anchor?.hasAttribute('aria-haspopup'))
|
|
308
|
+
anchor.setAttribute('aria-expanded', 'false');
|
|
309
|
+
}).observe(dialog, { attributes: true, attributeFilter: ['open'] });
|
|
310
|
+
dialog.addEventListener('close', () => {
|
|
229
311
|
el.dispatchEvent(new CustomEvent('close'));
|
|
230
312
|
});
|
|
231
313
|
}
|
|
232
314
|
el.closePopup = () => closePopup(el, true);
|
|
233
315
|
el.openPopup = () => openPopup(el);
|
|
234
316
|
el.togglePopup = () => togglePopup(el);
|
|
317
|
+
const existingCloseRequest = closeRequestHandlers.get(el);
|
|
318
|
+
if (existingCloseRequest)
|
|
319
|
+
el.removeEventListener('ty:close-popup', existingCloseRequest);
|
|
235
320
|
const closeRequestHandler = (e) => handleCloseRequest(el, e);
|
|
236
321
|
closeRequestHandlers.set(el, closeRequestHandler);
|
|
237
322
|
el.addEventListener('ty:close-popup', closeRequestHandler);
|
|
@@ -252,12 +337,15 @@ if (outsideHandler) {
|
|
|
252
337
|
dialog.removeEventListener('pointerdown', outsideHandler);
|
|
253
338
|
outsideClickHandlers.delete(el);
|
|
254
339
|
}
|
|
340
|
+
if (dialog.open)
|
|
341
|
+
dialog.close();
|
|
255
342
|
}
|
|
256
343
|
const escapeHandler = escapeHandlers.get(el);
|
|
257
344
|
if (escapeHandler) {
|
|
258
345
|
el.removeEventListener('keydown', escapeHandler);
|
|
259
346
|
escapeHandlers.delete(el);
|
|
260
347
|
}
|
|
348
|
+
unguardEscape(el);
|
|
261
349
|
const closeRequestHandler = closeRequestHandlers.get(el);
|
|
262
350
|
if (closeRequestHandler) {
|
|
263
351
|
el.removeEventListener('ty:close-popup', closeRequestHandler);
|