tyrell-components 1.0.0-TC46 → 1.0.0-TC48
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/README.md +1 -1
- package/css/tyrell-theme.css +167 -178
- package/css/tyrell.css +19 -0
- package/dist/tyrell-theme.css +167 -178
- package/dist/tyrell.css +19 -0
- package/dist/tyrell.js +1 -1
- package/lib/base/ty-component.d.ts +8 -3
- package/lib/base/ty-component.js +13 -5
- package/lib/components/date-picker.d.ts +8 -0
- package/lib/components/date-picker.js +13 -9
- package/lib/components/file-upload.d.ts +6 -0
- package/lib/components/file-upload.js +19 -4
- package/lib/components/input.js +1 -0
- package/lib/components/modal.d.ts +8 -0
- package/lib/components/modal.js +29 -6
- package/lib/components/popup.js +6 -10
- package/lib/components/radio.js +10 -2
- package/lib/components/resize-observer.d.ts +1 -0
- package/lib/components/resize-observer.js +12 -1
- package/lib/components/select.d.ts +11 -3
- package/lib/components/select.js +26 -9
- package/lib/components/selected-tags.js +1 -1
- package/lib/components/tabs.d.ts +2 -0
- package/lib/components/tabs.js +18 -1
- package/lib/components/tooltip.js +24 -6
- package/lib/components/wizard.d.ts +2 -0
- package/lib/components/wizard.js +15 -0
- package/lib/styles/button.js +9 -2
- package/lib/styles/calendar-month.js +1 -1
- package/lib/styles/calendar-navigation.d.ts +1 -1
- package/lib/styles/calendar-navigation.js +1 -1
- package/lib/styles/calendar.d.ts +1 -1
- package/lib/styles/calendar.js +1 -1
- package/lib/styles/date-picker.js +30 -4
- package/lib/styles/file-upload.d.ts +1 -1
- package/lib/styles/file-upload.js +1 -1
- package/lib/styles/icon.d.ts +1 -1
- package/lib/styles/icon.js +12 -1
- package/lib/styles/input.js +2 -1
- package/lib/styles/modal.d.ts +1 -1
- package/lib/styles/modal.js +18 -0
- package/lib/styles/option.d.ts +1 -1
- package/lib/styles/option.js +16 -1
- package/lib/styles/popup.d.ts +1 -1
- package/lib/styles/popup.js +22 -5
- package/lib/styles/radio.js +1 -1
- package/lib/styles/select-base.js +59 -9
- package/lib/styles/select.d.ts +1 -1
- package/lib/styles/select.js +5 -1
- package/lib/styles/tabs.d.ts +1 -1
- package/lib/styles/tabs.js +6 -1
- package/lib/styles/textarea.d.ts +1 -1
- package/lib/styles/textarea.js +2 -1
- package/lib/styles/wizard.d.ts +1 -1
- package/lib/styles/wizard.js +3 -3
- package/lib/utils/number-format.d.ts +6 -1
- package/lib/utils/number-format.js +9 -2
- package/lib/utils/positioning.d.ts +50 -5
- package/lib/utils/positioning.js +52 -8
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
|
@@ -132,9 +132,14 @@ export declare abstract class TyComponent<T = any> extends HTMLElement {
|
|
|
132
132
|
* two genuinely separate clicks are always in different tasks.
|
|
133
133
|
*/
|
|
134
134
|
protected isDuplicateActivationClick(): boolean;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
*
|
|
135
|
+
private _resetDefaults;
|
|
136
|
+
/**
|
|
137
|
+
* Form reset callback - called when form.reset() is invoked.
|
|
138
|
+
* Restores every formValue-bearing property to its attribute-declared
|
|
139
|
+
* default captured at first connect (native defaultValue/defaultChecked
|
|
140
|
+
* semantics). Non-form state (e.g. a checkbox's submission `value`, a
|
|
141
|
+
* ty-radio's option `value`) is deliberately left alone — the old
|
|
142
|
+
* blanket 'value' reset corrupted radio groups by wiping child values.
|
|
138
143
|
*/
|
|
139
144
|
formResetCallback(): void;
|
|
140
145
|
/**
|
package/lib/base/ty-component.js
CHANGED
|
@@ -5,6 +5,7 @@ export class TyComponent extends HTMLElement {
|
|
|
5
5
|
this._connected = false;
|
|
6
6
|
this._rendered = false;
|
|
7
7
|
this._handlingActivationClick = false;
|
|
8
|
+
this._resetDefaults = null;
|
|
8
9
|
const ctor = this.constructor;
|
|
9
10
|
this.props = new PropertyManager(ctor.properties);
|
|
10
11
|
this._internals = this.attachInternals();
|
|
@@ -79,6 +80,16 @@ export class TyComponent extends HTMLElement {
|
|
|
79
80
|
}
|
|
80
81
|
connectedCallback() {
|
|
81
82
|
this._connected = true;
|
|
83
|
+
if (!this._resetDefaults) {
|
|
84
|
+
this._resetDefaults = new Map();
|
|
85
|
+
const ctor = this.constructor;
|
|
86
|
+
for (const [name, config] of Object.entries(ctor.properties)) {
|
|
87
|
+
if (!config.formValue)
|
|
88
|
+
continue;
|
|
89
|
+
const attr = this.getAttribute(this._toKebabCase(name));
|
|
90
|
+
this._resetDefaults.set(name, attr ?? config.default ?? null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
82
93
|
const preSetProps = this._capturePreSetProperties();
|
|
83
94
|
if (preSetProps.length > 0) {
|
|
84
95
|
this._handlePropertyChanges(preSetProps);
|
|
@@ -178,11 +189,8 @@ export class TyComponent extends HTMLElement {
|
|
|
178
189
|
return false;
|
|
179
190
|
}
|
|
180
191
|
formResetCallback() {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
if (valueConfig) {
|
|
184
|
-
const defaultValue = valueConfig.default ?? '';
|
|
185
|
-
this.setProperty('value', defaultValue);
|
|
192
|
+
for (const [name, def] of this._resetDefaults ?? []) {
|
|
193
|
+
this.setProperty(name, def);
|
|
186
194
|
}
|
|
187
195
|
this.onFormReset();
|
|
188
196
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* (modal <dialog>), optional time input, form participation via
|
|
4
4
|
* ElementInternals. Value is UTC; display is local.
|
|
5
5
|
*/
|
|
6
|
+
import { type Placement } from '../utils/positioning.js';
|
|
6
7
|
import { TyComponent } from '../base/ty-component.js';
|
|
7
8
|
import type { Flavor } from '../types/common.js';
|
|
8
9
|
import type { PropertyChange } from '../utils/property-manager.js';
|
|
@@ -41,6 +42,11 @@ export interface DatePickerChangeDetail {
|
|
|
41
42
|
}
|
|
42
43
|
export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
43
44
|
protected static properties: {
|
|
45
|
+
placement: {
|
|
46
|
+
type: "string";
|
|
47
|
+
visual: boolean;
|
|
48
|
+
default: string;
|
|
49
|
+
};
|
|
44
50
|
size: {
|
|
45
51
|
type: "string";
|
|
46
52
|
visual: boolean;
|
|
@@ -169,6 +175,8 @@ export declare class TyDatePicker extends TyComponent<DatePickerState> {
|
|
|
169
175
|
private emitChangeEvent;
|
|
170
176
|
get size(): DatePickerSize;
|
|
171
177
|
set size(v: DatePickerSize);
|
|
178
|
+
get placement(): Placement | '';
|
|
179
|
+
set placement(v: Placement | '');
|
|
172
180
|
get flavor(): DatePickerFlavor;
|
|
173
181
|
set flavor(v: DatePickerFlavor);
|
|
174
182
|
get label(): string;
|
|
@@ -2,7 +2,7 @@ import { ensureStyles } from '../utils/styles.js';
|
|
|
2
2
|
import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
|
|
3
3
|
import { datePickerStyles, datePickerCustomFlavorCss } from '../styles/date-picker.js';
|
|
4
4
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
5
|
-
import { computeAnchoredPosition } from '../utils/positioning.js';
|
|
5
|
+
import { computeAnchoredPosition, placementToAnchored } from '../utils/positioning.js';
|
|
6
6
|
import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
|
|
7
7
|
import { isMobileTouch } from '../utils/mobile.js';
|
|
8
8
|
import { TyComponent } from '../base/ty-component.js';
|
|
@@ -506,6 +506,8 @@ export class TyDatePicker extends TyComponent {
|
|
|
506
506
|
}
|
|
507
507
|
get size() { return this.getProperty('size'); }
|
|
508
508
|
set size(v) { this.setProperty('size', v); }
|
|
509
|
+
get placement() { return this.getProperty('placement'); }
|
|
510
|
+
set placement(v) { this.setProperty('placement', v); }
|
|
509
511
|
get flavor() { return this.getProperty('flavor'); }
|
|
510
512
|
set flavor(v) { this.setProperty('flavor', v); }
|
|
511
513
|
get label() { return this.getProperty('label'); }
|
|
@@ -755,11 +757,14 @@ export class TyDatePicker extends TyComponent {
|
|
|
755
757
|
const estimatedHeight = dialogRect.height || 400;
|
|
756
758
|
const popupWidth = dialogRect.width || 320;
|
|
757
759
|
const wrapPadding = 8;
|
|
760
|
+
const anchored = placementToAnchored(this.getProperty('placement'));
|
|
758
761
|
const pos = computeAnchoredPosition({
|
|
759
762
|
anchorRect: stubRect,
|
|
760
763
|
popupWidth,
|
|
761
764
|
popupHeight: estimatedHeight,
|
|
762
765
|
gap: 0,
|
|
766
|
+
align: anchored.align,
|
|
767
|
+
side: anchored.side,
|
|
763
768
|
});
|
|
764
769
|
const positionBelow = pos.below;
|
|
765
770
|
const x = pos.x - wrapPadding;
|
|
@@ -975,10 +980,7 @@ export class TyDatePicker extends TyComponent {
|
|
|
975
980
|
if (isMobileTouch())
|
|
976
981
|
return;
|
|
977
982
|
this.updateState({ open: true });
|
|
978
|
-
this.dispatchEvent(new CustomEvent('open'
|
|
979
|
-
bubbles: true,
|
|
980
|
-
composed: true
|
|
981
|
-
}));
|
|
983
|
+
this.dispatchEvent(new CustomEvent('open'));
|
|
982
984
|
this.render();
|
|
983
985
|
requestAnimationFrame(() => {
|
|
984
986
|
if (!this.shadowRoot)
|
|
@@ -1017,10 +1019,7 @@ export class TyDatePicker extends TyComponent {
|
|
|
1017
1019
|
if (stubEl)
|
|
1018
1020
|
stubEl.setAttribute('aria-expanded', 'false');
|
|
1019
1021
|
}
|
|
1020
|
-
this.dispatchEvent(new CustomEvent('close'
|
|
1021
|
-
bubbles: true,
|
|
1022
|
-
composed: true
|
|
1023
|
-
}));
|
|
1022
|
+
this.dispatchEvent(new CustomEvent('close'));
|
|
1024
1023
|
this.render();
|
|
1025
1024
|
}
|
|
1026
1025
|
cleanup() {
|
|
@@ -1047,6 +1046,11 @@ export class TyDatePicker extends TyComponent {
|
|
|
1047
1046
|
}
|
|
1048
1047
|
}
|
|
1049
1048
|
TyDatePicker.properties = {
|
|
1049
|
+
placement: {
|
|
1050
|
+
type: 'string',
|
|
1051
|
+
visual: true,
|
|
1052
|
+
default: '',
|
|
1053
|
+
},
|
|
1050
1054
|
size: {
|
|
1051
1055
|
type: 'string',
|
|
1052
1056
|
visual: true,
|
|
@@ -80,6 +80,12 @@ export declare class TyFileUpload extends TyComponent<FileUploadState> implement
|
|
|
80
80
|
protected onPropertiesChanged(_changes: PropertyChange[]): void;
|
|
81
81
|
protected onFormReset(): void;
|
|
82
82
|
protected getFormValue(): FormData | string | null;
|
|
83
|
+
/**
|
|
84
|
+
* Match a file against the `accept` attribute the way native inputs do:
|
|
85
|
+
* comma-separated list of `.ext`, `type/subtype` or `type/*` entries.
|
|
86
|
+
* No/blank accept matches everything.
|
|
87
|
+
*/
|
|
88
|
+
private matchesAccept;
|
|
83
89
|
get files(): File[];
|
|
84
90
|
get multiple(): boolean;
|
|
85
91
|
set multiple(v: boolean);
|
|
@@ -44,15 +44,30 @@ export class TyFileUpload extends TyComponent {
|
|
|
44
44
|
fileInput.value = "";
|
|
45
45
|
}
|
|
46
46
|
getFormValue() {
|
|
47
|
-
if (this._files.length === 0)
|
|
47
|
+
if (this._files.length === 0 || !this.name)
|
|
48
48
|
return null;
|
|
49
49
|
const fd = new FormData();
|
|
50
|
-
const fieldName = this.name || "files";
|
|
51
50
|
for (const file of this._files) {
|
|
52
|
-
fd.append(
|
|
51
|
+
fd.append(this.name, file);
|
|
53
52
|
}
|
|
54
53
|
return fd;
|
|
55
54
|
}
|
|
55
|
+
matchesAccept(file) {
|
|
56
|
+
const accept = this.accept?.trim();
|
|
57
|
+
if (!accept)
|
|
58
|
+
return true;
|
|
59
|
+
return accept.split(",").some((raw) => {
|
|
60
|
+
const entry = raw.trim().toLowerCase();
|
|
61
|
+
if (!entry)
|
|
62
|
+
return false;
|
|
63
|
+
if (entry.startsWith("."))
|
|
64
|
+
return file.name.toLowerCase().endsWith(entry);
|
|
65
|
+
const type = file.type.toLowerCase();
|
|
66
|
+
if (entry.endsWith("/*"))
|
|
67
|
+
return type.startsWith(entry.slice(0, -1));
|
|
68
|
+
return type === entry;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
56
71
|
get files() {
|
|
57
72
|
return [...this._files];
|
|
58
73
|
}
|
|
@@ -178,7 +193,7 @@ export class TyFileUpload extends TyComponent {
|
|
|
178
193
|
e.stopPropagation();
|
|
179
194
|
this._isDragging = false;
|
|
180
195
|
dropZone.classList.remove("drag-active");
|
|
181
|
-
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
196
|
+
const files = Array.from(e.dataTransfer?.files ?? []).filter((f) => this.matchesAccept(f));
|
|
182
197
|
if (files.length > 0)
|
|
183
198
|
this.setFiles(files);
|
|
184
199
|
};
|
package/lib/components/input.js
CHANGED
|
@@ -48,6 +48,14 @@ export declare class TyModal extends HTMLElement {
|
|
|
48
48
|
force?: boolean;
|
|
49
49
|
}) => void;
|
|
50
50
|
static get observedAttributes(): string[];
|
|
51
|
+
get open(): boolean;
|
|
52
|
+
set open(v: boolean);
|
|
53
|
+
get backdrop(): boolean;
|
|
54
|
+
set backdrop(v: boolean);
|
|
55
|
+
get closeOnOutsideClick(): boolean;
|
|
56
|
+
set closeOnOutsideClick(v: boolean);
|
|
57
|
+
get closeOnEscape(): boolean;
|
|
58
|
+
set closeOnEscape(v: boolean);
|
|
51
59
|
constructor();
|
|
52
60
|
connectedCallback(): void;
|
|
53
61
|
disconnectedCallback(): void;
|
package/lib/components/modal.js
CHANGED
|
@@ -7,6 +7,7 @@ const closeButtonHandlers = new WeakMap();
|
|
|
7
7
|
const hoverEnterHandlers = new WeakMap();
|
|
8
8
|
const hoverLeaveHandlers = new WeakMap();
|
|
9
9
|
const modalIds = new WeakMap();
|
|
10
|
+
const pendingCloseReasons = new WeakMap();
|
|
10
11
|
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>`;
|
|
11
12
|
function parseBoolAttr(el, name) {
|
|
12
13
|
if (!el.hasAttribute(name)) {
|
|
@@ -47,7 +48,6 @@ function isMobile() {
|
|
|
47
48
|
function dispatchModalEvent(el, eventType, detail) {
|
|
48
49
|
const event = new CustomEvent(eventType, {
|
|
49
50
|
detail,
|
|
50
|
-
bubbles: true,
|
|
51
51
|
cancelable: true,
|
|
52
52
|
});
|
|
53
53
|
el.dispatchEvent(event);
|
|
@@ -80,14 +80,13 @@ function closeModal(el, reason = 'programmatic', opts) {
|
|
|
80
80
|
if (!opts?.force) {
|
|
81
81
|
const beforeClose = new CustomEvent('beforeclose', {
|
|
82
82
|
detail: { reason },
|
|
83
|
-
bubbles: true,
|
|
84
|
-
composed: true,
|
|
85
83
|
cancelable: true,
|
|
86
84
|
});
|
|
87
85
|
el.dispatchEvent(beforeClose);
|
|
88
86
|
if (beforeClose.defaultPrevented)
|
|
89
87
|
return;
|
|
90
88
|
}
|
|
89
|
+
pendingCloseReasons.set(el, reason);
|
|
91
90
|
el.removeAttribute('open');
|
|
92
91
|
}
|
|
93
92
|
function openModal(el) {
|
|
@@ -226,6 +225,11 @@ function render(el) {
|
|
|
226
225
|
}
|
|
227
226
|
setupBackdropClick(el, dialog, attributes.closeOnOutsideClick);
|
|
228
227
|
setupEscapeKey(el, dialog, attributes.closeOnEscape);
|
|
228
|
+
dialog.oncancel = (event) => {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
if (getModalAttributes(el).closeOnEscape)
|
|
231
|
+
closeModal(el, 'escape');
|
|
232
|
+
};
|
|
229
233
|
setupCloseButton(el, dialog, attributes.closeOnOutsideClick);
|
|
230
234
|
setupCloseButtonHover(el, dialog);
|
|
231
235
|
if (attributes.open) {
|
|
@@ -237,6 +241,7 @@ function render(el) {
|
|
|
237
241
|
else {
|
|
238
242
|
dialog.show();
|
|
239
243
|
}
|
|
244
|
+
syncBackdropZoom();
|
|
240
245
|
dispatchModalEvent(el, 'open', {});
|
|
241
246
|
}
|
|
242
247
|
}
|
|
@@ -244,9 +249,10 @@ function render(el) {
|
|
|
244
249
|
if (dialog.open) {
|
|
245
250
|
unlockScroll(modalId);
|
|
246
251
|
dialog.close();
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
syncBackdropZoom();
|
|
253
|
+
const reason = pendingCloseReasons.get(el) ?? 'programmatic';
|
|
254
|
+
pendingCloseReasons.delete(el);
|
|
255
|
+
dispatchModalEvent(el, 'close', { reason });
|
|
250
256
|
}
|
|
251
257
|
}
|
|
252
258
|
dialog.onclose = (event) => {
|
|
@@ -260,8 +266,16 @@ function render(el) {
|
|
|
260
266
|
returnValue: dialog.returnValue || undefined
|
|
261
267
|
});
|
|
262
268
|
}
|
|
269
|
+
syncBackdropZoom();
|
|
263
270
|
};
|
|
264
271
|
}
|
|
272
|
+
function syncBackdropZoom() {
|
|
273
|
+
const anyOpen = Array.from(document.querySelectorAll('ty-modal[open][backdrop-zoom], ty-dialog[open][backdrop-zoom]')).some((m) => {
|
|
274
|
+
const v = m.getAttribute('backdrop-zoom').toLowerCase().trim();
|
|
275
|
+
return v !== 'false' && v !== '0';
|
|
276
|
+
});
|
|
277
|
+
document.documentElement.classList.toggle('ty-modal-zoom', anyOpen);
|
|
278
|
+
}
|
|
265
279
|
function cleanup(el) {
|
|
266
280
|
const modalId = modalIds.get(el);
|
|
267
281
|
const shadowRoot = el.shadowRoot;
|
|
@@ -270,6 +284,7 @@ function cleanup(el) {
|
|
|
270
284
|
unlockScroll(modalId);
|
|
271
285
|
modalIds.delete(el);
|
|
272
286
|
}
|
|
287
|
+
syncBackdropZoom();
|
|
273
288
|
if (!dialog)
|
|
274
289
|
return;
|
|
275
290
|
const backdropHandler = backdropClickHandlers.get(el);
|
|
@@ -308,6 +323,14 @@ export class TyModal extends HTMLElement {
|
|
|
308
323
|
static get observedAttributes() {
|
|
309
324
|
return ['open', 'backdrop', 'close-on-outside-click', 'close-on-escape', 'label'];
|
|
310
325
|
}
|
|
326
|
+
get open() { return parseBoolAttr(this, 'open'); }
|
|
327
|
+
set open(v) { v ? this.setAttribute('open', 'true') : this.removeAttribute('open'); }
|
|
328
|
+
get backdrop() { return getModalAttributes(this).backdrop; }
|
|
329
|
+
set backdrop(v) { this.setAttribute('backdrop', String(!!v)); }
|
|
330
|
+
get closeOnOutsideClick() { return getModalAttributes(this).closeOnOutsideClick; }
|
|
331
|
+
set closeOnOutsideClick(v) { this.setAttribute('close-on-outside-click', String(!!v)); }
|
|
332
|
+
get closeOnEscape() { return getModalAttributes(this).closeOnEscape; }
|
|
333
|
+
set closeOnEscape(v) { this.setAttribute('close-on-escape', String(!!v)); }
|
|
311
334
|
constructor() {
|
|
312
335
|
super();
|
|
313
336
|
this.attachShadow({ mode: 'open' });
|
package/lib/components/popup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findBestPosition } from '../utils/positioning.js';
|
|
1
|
+
import { findBestPosition, preferenceChain } from '../utils/positioning.js';
|
|
2
2
|
import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
|
|
3
3
|
import { ensureStyles } from '../utils/styles.js';
|
|
4
4
|
import { popupStyles } from '../styles/popup.js';
|
|
@@ -47,11 +47,7 @@ function updatePosition(el) {
|
|
|
47
47
|
const dialog = shadowRoot ? getPopupDialog(shadowRoot) : null;
|
|
48
48
|
if (!anchor || !dialog)
|
|
49
49
|
return;
|
|
50
|
-
const preferences = placement
|
|
51
|
-
placement === 'bottom' ? ['bottom', 'top', 'left', 'right'] :
|
|
52
|
-
placement === 'left' ? ['left', 'right', 'top', 'bottom'] :
|
|
53
|
-
placement === 'right' ? ['right', 'left', 'top', 'bottom'] :
|
|
54
|
-
['bottom', 'top', 'right', 'left'];
|
|
50
|
+
const preferences = preferenceChain(placement);
|
|
55
51
|
const positionData = findBestPosition({
|
|
56
52
|
targetEl: anchor,
|
|
57
53
|
floatingEl: dialog,
|
|
@@ -62,6 +58,7 @@ function updatePosition(el) {
|
|
|
62
58
|
});
|
|
63
59
|
el.style.setProperty('--popup-x', `${positionData.x}px`);
|
|
64
60
|
el.style.setProperty('--popup-y', `${positionData.y}px`);
|
|
61
|
+
dialog.dataset.side = positionData.placement.split('-')[0];
|
|
65
62
|
dialog.classList.add('position-calculated');
|
|
66
63
|
}
|
|
67
64
|
function closePopup(el, force = false) {
|
|
@@ -80,8 +77,7 @@ function closePopup(el, force = false) {
|
|
|
80
77
|
setTimeout(() => {
|
|
81
78
|
dialog.classList.remove('position-calculated');
|
|
82
79
|
dialog.close();
|
|
83
|
-
},
|
|
84
|
-
el.dispatchEvent(new CustomEvent('close', { bubbles: true }));
|
|
80
|
+
}, 200);
|
|
85
81
|
}
|
|
86
82
|
function openPopup(el) {
|
|
87
83
|
const popupId = getPopupId(el);
|
|
@@ -99,7 +95,7 @@ function openPopup(el) {
|
|
|
99
95
|
dialog.classList.add('preparing-animation');
|
|
100
96
|
requestAnimationFrame(() => {
|
|
101
97
|
dialog.classList.add('open');
|
|
102
|
-
el.dispatchEvent(new CustomEvent('open'
|
|
98
|
+
el.dispatchEvent(new CustomEvent('open'));
|
|
103
99
|
});
|
|
104
100
|
});
|
|
105
101
|
}
|
|
@@ -215,7 +211,7 @@ function render(el) {
|
|
|
215
211
|
unlockScroll(popupId);
|
|
216
212
|
dialog.classList.remove('open');
|
|
217
213
|
dialog.classList.remove('preparing-animation');
|
|
218
|
-
el.dispatchEvent(new CustomEvent('close'
|
|
214
|
+
el.dispatchEvent(new CustomEvent('close'));
|
|
219
215
|
});
|
|
220
216
|
}
|
|
221
217
|
el.closePopup = () => closePopup(el, true);
|
package/lib/components/radio.js
CHANGED
|
@@ -176,8 +176,16 @@ export class TyRadioGroup extends TyComponent {
|
|
|
176
176
|
const radios = this.getRadios();
|
|
177
177
|
for (const radio of radios) {
|
|
178
178
|
radio.checked = radio.value === this.value;
|
|
179
|
-
if (this.disabled)
|
|
180
|
-
radio.disabled
|
|
179
|
+
if (this.disabled) {
|
|
180
|
+
if (!radio.disabled) {
|
|
181
|
+
radio.disabled = true;
|
|
182
|
+
radio.dataset.tyGroupDisabled = "";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if ("tyGroupDisabled" in radio.dataset) {
|
|
186
|
+
radio.disabled = false;
|
|
187
|
+
delete radio.dataset.tyGroupDisabled;
|
|
188
|
+
}
|
|
181
189
|
radio.size = this.size;
|
|
182
190
|
radio.flavor = this.flavor;
|
|
183
191
|
}
|
|
@@ -13,6 +13,7 @@ export declare class TyResizeObserver extends HTMLElement {
|
|
|
13
13
|
set debounce(value: number);
|
|
14
14
|
constructor();
|
|
15
15
|
connectedCallback(): void;
|
|
16
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
16
17
|
disconnectedCallback(): void;
|
|
17
18
|
private setupObserver;
|
|
18
19
|
private handleResize;
|
|
@@ -22,6 +22,15 @@ export class TyResizeObserver extends HTMLElement {
|
|
|
22
22
|
connectedCallback() {
|
|
23
23
|
this.setupObserver();
|
|
24
24
|
}
|
|
25
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
26
|
+
if (name === 'id' && oldValue && oldValue !== newValue) {
|
|
27
|
+
removeSize(oldValue);
|
|
28
|
+
if (newValue && this.isConnected) {
|
|
29
|
+
const { width, height } = this.getBoundingClientRect();
|
|
30
|
+
updateSize(newValue, width, height);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
disconnectedCallback() {
|
|
26
35
|
this.cleanup();
|
|
27
36
|
}
|
|
@@ -60,4 +69,6 @@ export class TyResizeObserver extends HTMLElement {
|
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
|
-
customElements.
|
|
72
|
+
if (!customElements.get('ty-resize-observer')) {
|
|
73
|
+
customElements.define('ty-resize-observer', TyResizeObserver);
|
|
74
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Skins: default form field, `compact` content-hugging trigger, slot="trigger" custom.
|
|
6
6
|
*/
|
|
7
7
|
import type { Flavor, Size } from "../types/common.js";
|
|
8
|
+
import { type Placement } from "../utils/positioning.js";
|
|
8
9
|
import { TyComponent } from "../base/ty-component.js";
|
|
9
10
|
import type { PropertyChange } from "../utils/property-manager.js";
|
|
10
11
|
interface TagData {
|
|
@@ -116,7 +117,12 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
116
117
|
visual: boolean;
|
|
117
118
|
default: string;
|
|
118
119
|
validate: (v: any) => boolean;
|
|
119
|
-
coerce: (v: any) => "start" | "end";
|
|
120
|
+
coerce: (v: any) => "start" | "end" | "center";
|
|
121
|
+
};
|
|
122
|
+
placement: {
|
|
123
|
+
type: "string";
|
|
124
|
+
visual: boolean;
|
|
125
|
+
default: string;
|
|
120
126
|
};
|
|
121
127
|
size: {
|
|
122
128
|
type: "string";
|
|
@@ -402,8 +408,10 @@ export declare class TySelect extends TyComponent<SelectState> {
|
|
|
402
408
|
set debounce(value: number | string);
|
|
403
409
|
get size(): Size;
|
|
404
410
|
set size(value: Size);
|
|
405
|
-
get align(): "start" | "end";
|
|
406
|
-
set align(value: "start" | "end");
|
|
411
|
+
get align(): "start" | "center" | "end";
|
|
412
|
+
set align(value: "start" | "center" | "end");
|
|
413
|
+
get placement(): Placement | "";
|
|
414
|
+
set placement(value: Placement | "");
|
|
407
415
|
get flavor(): Flavor;
|
|
408
416
|
set flavor(value: Flavor);
|
|
409
417
|
get form(): HTMLFormElement | null;
|
package/lib/components/select.js
CHANGED
|
@@ -4,7 +4,7 @@ import { selectBaseStyles, selectCustomFlavorCss } from "../styles/select-base.j
|
|
|
4
4
|
import { selectStyles } from "../styles/select.js";
|
|
5
5
|
import { getLoaderSvg } from "../utils/loader-registry.js";
|
|
6
6
|
import { lockScroll, unlockScroll } from "../utils/scroll-lock.js";
|
|
7
|
-
import { computeAnchoredPosition } from "../utils/positioning.js";
|
|
7
|
+
import { computeAnchoredPosition, placementToAnchored } from "../utils/positioning.js";
|
|
8
8
|
import { isMobileTouch } from "../utils/mobile.js";
|
|
9
9
|
import { TyComponent } from "../base/ty-component.js";
|
|
10
10
|
import { CustomScrollbar, isCustomScrollbarEnabled, } from "../utils/custom-scrollbar.js";
|
|
@@ -366,11 +366,20 @@ export class TySelect extends TyComponent {
|
|
|
366
366
|
const popupWidth = !isNaN(cssWidth)
|
|
367
367
|
? cssWidth
|
|
368
368
|
: Math.max(stubRect.width, 320);
|
|
369
|
+
const requested = this.getProperty("placement");
|
|
370
|
+
const anchored = requested
|
|
371
|
+
? placementToAnchored(requested)
|
|
372
|
+
: {
|
|
373
|
+
side: "auto",
|
|
374
|
+
align: this.getProperty("align"),
|
|
375
|
+
};
|
|
369
376
|
const pos = computeAnchoredPosition({
|
|
370
377
|
anchorRect: stubRect,
|
|
371
378
|
popupWidth,
|
|
372
379
|
popupHeight: estimatedHeight,
|
|
373
|
-
align:
|
|
380
|
+
align: anchored.align,
|
|
381
|
+
side: anchored.side,
|
|
382
|
+
gap: 8,
|
|
374
383
|
});
|
|
375
384
|
const positionBelow = pos.below;
|
|
376
385
|
const x = pos.x - wrapPadding;
|
|
@@ -428,8 +437,8 @@ export class TySelect extends TyComponent {
|
|
|
428
437
|
this._scrollLockId = lockId;
|
|
429
438
|
lockScroll(lockId);
|
|
430
439
|
dialog.showModal();
|
|
431
|
-
dialog.classList.add("open");
|
|
432
440
|
this.calculatePosition();
|
|
441
|
+
dialog.classList.add("open");
|
|
433
442
|
this._state.open = true;
|
|
434
443
|
const chevron = shadow.querySelector(".dropdown-chevron");
|
|
435
444
|
if (chevron)
|
|
@@ -508,6 +517,7 @@ export class TySelect extends TyComponent {
|
|
|
508
517
|
this._scrollLockId = lockId;
|
|
509
518
|
lockScroll(lockId);
|
|
510
519
|
dialog.showModal();
|
|
520
|
+
dialog.getBoundingClientRect();
|
|
511
521
|
dialog.classList.add("open");
|
|
512
522
|
this._state.open = true;
|
|
513
523
|
const tags = this.getTagElements().map((el) => this.getTagData(el));
|
|
@@ -837,8 +847,6 @@ export class TySelect extends TyComponent {
|
|
|
837
847
|
fireOpenEvent() {
|
|
838
848
|
this.dispatchEvent(new CustomEvent("open", {
|
|
839
849
|
detail: { mode: this._state.mode, element: this },
|
|
840
|
-
bubbles: true,
|
|
841
|
-
composed: true,
|
|
842
850
|
}));
|
|
843
851
|
if (this._externalSearch) {
|
|
844
852
|
this.fireSearchEvent("");
|
|
@@ -847,8 +855,6 @@ export class TySelect extends TyComponent {
|
|
|
847
855
|
fireCloseEvent() {
|
|
848
856
|
this.dispatchEvent(new CustomEvent("close", {
|
|
849
857
|
detail: { mode: this._state.mode, element: this },
|
|
850
|
-
bubbles: true,
|
|
851
|
-
composed: true,
|
|
852
858
|
}));
|
|
853
859
|
}
|
|
854
860
|
dispatchChangeEvent(detail) {
|
|
@@ -1377,6 +1383,12 @@ export class TySelect extends TyComponent {
|
|
|
1377
1383
|
set align(value) {
|
|
1378
1384
|
this.setProperty("align", value);
|
|
1379
1385
|
}
|
|
1386
|
+
get placement() {
|
|
1387
|
+
return this.getProperty("placement");
|
|
1388
|
+
}
|
|
1389
|
+
set placement(value) {
|
|
1390
|
+
this.setProperty("placement", value);
|
|
1391
|
+
}
|
|
1380
1392
|
get flavor() {
|
|
1381
1393
|
return this.getProperty("flavor");
|
|
1382
1394
|
}
|
|
@@ -1484,8 +1496,13 @@ TySelect.properties = {
|
|
|
1484
1496
|
type: "string",
|
|
1485
1497
|
visual: true,
|
|
1486
1498
|
default: "start",
|
|
1487
|
-
validate: (v) => ["start", "end"].includes(v),
|
|
1488
|
-
coerce: (v) =>
|
|
1499
|
+
validate: (v) => ["start", "center", "end"].includes(v),
|
|
1500
|
+
coerce: (v) => v === "end" ? "end" : v === "center" ? "center" : "start",
|
|
1501
|
+
},
|
|
1502
|
+
placement: {
|
|
1503
|
+
type: "string",
|
|
1504
|
+
visual: true,
|
|
1505
|
+
default: "",
|
|
1489
1506
|
},
|
|
1490
1507
|
size: {
|
|
1491
1508
|
type: "string",
|
|
@@ -105,7 +105,7 @@ export class TySelectedOptions extends HTMLElement {
|
|
|
105
105
|
return ctx;
|
|
106
106
|
}
|
|
107
107
|
templateSource(template) {
|
|
108
|
-
if (template.content.
|
|
108
|
+
if (template.content.hasChildNodes())
|
|
109
109
|
return template.content;
|
|
110
110
|
const frag = document.createDocumentFragment();
|
|
111
111
|
template.childNodes.forEach(n => frag.appendChild(n.cloneNode(true)));
|
package/lib/components/tabs.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface TabChangeDetail {
|
|
|
27
27
|
export declare class TyTabs extends HTMLElement {
|
|
28
28
|
static get observedAttributes(): string[];
|
|
29
29
|
constructor();
|
|
30
|
+
private _childObserver;
|
|
31
|
+
private _childRenderQueued;
|
|
30
32
|
connectedCallback(): void;
|
|
31
33
|
disconnectedCallback(): void;
|
|
32
34
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
package/lib/components/tabs.js
CHANGED
|
@@ -231,7 +231,7 @@ function setupResizeObserver(el) {
|
|
|
231
231
|
const activeId = getActiveTabId(el, tabs);
|
|
232
232
|
const activeIndex = activeId ? findTabIndex(tabs, activeId) : 0;
|
|
233
233
|
if (width.includes('%')) {
|
|
234
|
-
el.style.setProperty('--tabs-width', `${entry.contentRect.width}px`);
|
|
234
|
+
el.style.setProperty('--tabs-page-width', `${entry.contentRect.width}px`);
|
|
235
235
|
}
|
|
236
236
|
if (activeIndex !== undefined) {
|
|
237
237
|
updateTransform(el, activeIndex);
|
|
@@ -370,6 +370,8 @@ function updateOverflow(el) {
|
|
|
370
370
|
if (buttons.length === 0)
|
|
371
371
|
return;
|
|
372
372
|
const available = container.clientWidth;
|
|
373
|
+
if (available <= 0)
|
|
374
|
+
return;
|
|
373
375
|
let total = 0;
|
|
374
376
|
let cutoff = buttons.length;
|
|
375
377
|
for (let i = 0; i < buttons.length; i++) {
|
|
@@ -513,12 +515,27 @@ export class TyTabs extends HTMLElement {
|
|
|
513
515
|
}
|
|
514
516
|
constructor() {
|
|
515
517
|
super();
|
|
518
|
+
this._childObserver = null;
|
|
519
|
+
this._childRenderQueued = false;
|
|
516
520
|
this.attachShadow({ mode: 'open' });
|
|
517
521
|
}
|
|
518
522
|
connectedCallback() {
|
|
519
523
|
render(this);
|
|
524
|
+
this._childObserver = new MutationObserver(() => {
|
|
525
|
+
if (this._childRenderQueued)
|
|
526
|
+
return;
|
|
527
|
+
this._childRenderQueued = true;
|
|
528
|
+
queueMicrotask(() => {
|
|
529
|
+
this._childRenderQueued = false;
|
|
530
|
+
if (this.isConnected)
|
|
531
|
+
render(this);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
this._childObserver.observe(this, { childList: true });
|
|
520
535
|
}
|
|
521
536
|
disconnectedCallback() {
|
|
537
|
+
this._childObserver?.disconnect();
|
|
538
|
+
this._childObserver = null;
|
|
522
539
|
cleanup(this);
|
|
523
540
|
}
|
|
524
541
|
attributeChangedCallback(name, oldValue, newValue) {
|