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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/css/tyrell-theme.css +167 -178
  3. package/css/tyrell.css +19 -0
  4. package/dist/tyrell-theme.css +167 -178
  5. package/dist/tyrell.css +19 -0
  6. package/dist/tyrell.js +1 -1
  7. package/lib/base/ty-component.d.ts +8 -3
  8. package/lib/base/ty-component.js +13 -5
  9. package/lib/components/date-picker.d.ts +8 -0
  10. package/lib/components/date-picker.js +13 -9
  11. package/lib/components/file-upload.d.ts +6 -0
  12. package/lib/components/file-upload.js +19 -4
  13. package/lib/components/input.js +1 -0
  14. package/lib/components/modal.d.ts +8 -0
  15. package/lib/components/modal.js +29 -6
  16. package/lib/components/popup.js +6 -10
  17. package/lib/components/radio.js +10 -2
  18. package/lib/components/resize-observer.d.ts +1 -0
  19. package/lib/components/resize-observer.js +12 -1
  20. package/lib/components/select.d.ts +11 -3
  21. package/lib/components/select.js +26 -9
  22. package/lib/components/selected-tags.js +1 -1
  23. package/lib/components/tabs.d.ts +2 -0
  24. package/lib/components/tabs.js +18 -1
  25. package/lib/components/tooltip.js +24 -6
  26. package/lib/components/wizard.d.ts +2 -0
  27. package/lib/components/wizard.js +15 -0
  28. package/lib/styles/button.js +9 -2
  29. package/lib/styles/calendar-month.js +1 -1
  30. package/lib/styles/calendar-navigation.d.ts +1 -1
  31. package/lib/styles/calendar-navigation.js +1 -1
  32. package/lib/styles/calendar.d.ts +1 -1
  33. package/lib/styles/calendar.js +1 -1
  34. package/lib/styles/date-picker.js +30 -4
  35. package/lib/styles/file-upload.d.ts +1 -1
  36. package/lib/styles/file-upload.js +1 -1
  37. package/lib/styles/icon.d.ts +1 -1
  38. package/lib/styles/icon.js +12 -1
  39. package/lib/styles/input.js +2 -1
  40. package/lib/styles/modal.d.ts +1 -1
  41. package/lib/styles/modal.js +18 -0
  42. package/lib/styles/option.d.ts +1 -1
  43. package/lib/styles/option.js +16 -1
  44. package/lib/styles/popup.d.ts +1 -1
  45. package/lib/styles/popup.js +22 -5
  46. package/lib/styles/radio.js +1 -1
  47. package/lib/styles/select-base.js +59 -9
  48. package/lib/styles/select.d.ts +1 -1
  49. package/lib/styles/select.js +5 -1
  50. package/lib/styles/tabs.d.ts +1 -1
  51. package/lib/styles/tabs.js +6 -1
  52. package/lib/styles/textarea.d.ts +1 -1
  53. package/lib/styles/textarea.js +2 -1
  54. package/lib/styles/wizard.d.ts +1 -1
  55. package/lib/styles/wizard.js +3 -3
  56. package/lib/utils/number-format.d.ts +6 -1
  57. package/lib/utils/number-format.js +9 -2
  58. package/lib/utils/positioning.d.ts +50 -5
  59. package/lib/utils/positioning.js +52 -8
  60. package/lib/version.d.ts +1 -1
  61. package/lib/version.js +1 -1
  62. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { findBestPosition, placementPreferences } from '../utils/positioning.js';
1
+ import { findBestPosition, preferenceChain } from '../utils/positioning.js';
2
2
  import { ensureStyles } from '../utils/styles.js';
3
3
  import { tooltipStyles } from '../styles/tooltip.js';
4
4
  const autoUpdateCleanup = new WeakMap();
@@ -57,7 +57,28 @@ function removeDescribedBy(anchor, id) {
57
57
  anchor.removeAttribute('aria-describedby');
58
58
  }
59
59
  }
60
+ function ensureTooltipMotionStyles() {
61
+ if (document.getElementById('ty-tooltip-motion'))
62
+ return;
63
+ const style = document.createElement('style');
64
+ style.id = 'ty-tooltip-motion';
65
+ style.textContent = `
66
+ .ty-tooltip-popover:popover-open {
67
+ transition:
68
+ opacity var(--ty-tooltip-duration, 120ms) ease-out,
69
+ translate var(--ty-tooltip-duration, 120ms) ease-out;
70
+ }
71
+ @starting-style {
72
+ .ty-tooltip-popover:popover-open { opacity: 0; translate: 0 4px; }
73
+ .ty-tooltip-popover:popover-open[data-side="bottom"] { translate: 0 -4px; }
74
+ .ty-tooltip-popover:popover-open[data-side="left"] { translate: 4px 0; }
75
+ .ty-tooltip-popover:popover-open[data-side="right"] { translate: -4px 0; }
76
+ }
77
+ `;
78
+ document.head.appendChild(style);
79
+ }
60
80
  function getOrCreatePopover(el) {
81
+ ensureTooltipMotionStyles();
61
82
  let popover = popoverElements.get(el);
62
83
  if (!popover) {
63
84
  popover = document.createElement('div');
@@ -134,11 +155,7 @@ function updatePosition(el) {
134
155
  console.warn('[ty-tooltip] updatePosition: missing anchor or popover');
135
156
  return;
136
157
  }
137
- const preferences = placement === 'top' ? placementPreferences.tooltip :
138
- placement === 'bottom' ? ['bottom', 'top', 'left', 'right'] :
139
- placement === 'left' ? ['left', 'right', 'top', 'bottom'] :
140
- placement === 'right' ? ['right', 'left', 'top', 'bottom'] :
141
- placementPreferences.tooltip;
158
+ const preferences = preferenceChain(placement);
142
159
  const position = findBestPosition({
143
160
  targetEl: anchor,
144
161
  floatingEl: popover,
@@ -147,6 +164,7 @@ function updatePosition(el) {
147
164
  });
148
165
  popover.style.left = `${position.x}px`;
149
166
  popover.style.top = `${position.y}px`;
167
+ popover.dataset.side = position.placement.split('-')[0];
150
168
  }
151
169
  function cleanupAutoUpdate(el) {
152
170
  const cleanup = autoUpdateCleanup.get(el);
@@ -30,6 +30,8 @@ export interface WizardStepChangeDetail {
30
30
  export declare class TyWizard extends HTMLElement {
31
31
  static get observedAttributes(): string[];
32
32
  constructor();
33
+ private _childObserver;
34
+ private _childRenderQueued;
33
35
  connectedCallback(): void;
34
36
  disconnectedCallback(): void;
35
37
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
@@ -399,12 +399,27 @@ export class TyWizard extends HTMLElement {
399
399
  }
400
400
  constructor() {
401
401
  super();
402
+ this._childObserver = null;
403
+ this._childRenderQueued = false;
402
404
  this.attachShadow({ mode: 'open' });
403
405
  }
404
406
  connectedCallback() {
405
407
  render(this);
408
+ this._childObserver = new MutationObserver(() => {
409
+ if (this._childRenderQueued)
410
+ return;
411
+ this._childRenderQueued = true;
412
+ queueMicrotask(() => {
413
+ this._childRenderQueued = false;
414
+ if (this.isConnected)
415
+ render(this);
416
+ });
417
+ });
418
+ this._childObserver.observe(this, { childList: true });
406
419
  }
407
420
  disconnectedCallback() {
421
+ this._childObserver?.disconnect();
422
+ this._childObserver = null;
408
423
  cleanup(this);
409
424
  }
410
425
  attributeChangedCallback(name, oldValue, newValue) {
@@ -8,7 +8,7 @@ const solidFlavor = (f, fb) => {
8
8
  const solid = (suffix) => tokenRef('--ty-solid', f, suffix, fb);
9
9
  const borderL = f === 'neutral'
10
10
  ? ' --_border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));'
11
- : ' --_border-l: var(--ty-button-border-l, -0.15);';
11
+ : ' --_border-l: var(--_muted-border-l, var(--ty-button-border-l, -0.15));';
12
12
  const plusNudge = f === 'neutral' ? nudgedMid : nudged;
13
13
  return `
14
14
  button.solid.${f} { --_ring: ${tokenRef('--ty-color', f, '', fb)}; --_solid-bg: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('')})); --_solid-rest: var(--ty-button-bg, var(--_muted-solid-bg, ${solid('')}));${borderL} color: var(--ty-button-color, var(--_muted-solid-fg, ${solid('-fg')})); }
@@ -82,7 +82,7 @@ export const buttonCustomFlavorCss = (base) => solidFlavor(base, 'neutral') + ou
82
82
  export const buttonStyles = `
83
83
  :host {
84
84
  display: inline-block;
85
- font-family: var(--ty-font-sans);
85
+ font-family: inherit;
86
86
  }
87
87
 
88
88
  :host([wide]) {
@@ -91,6 +91,10 @@ export const buttonStyles = `
91
91
  }
92
92
 
93
93
  button {
94
+ /* Native form controls do NOT inherit font from the shadow host — the UA
95
+ stylesheet sets one on them, which beats inheritance. Must be explicit
96
+ here or the button silently falls back to the system font. */
97
+ font-family: inherit;
94
98
  position: relative;
95
99
  display: flex;
96
100
  align-items: center;
@@ -284,6 +288,7 @@ button.muted {
284
288
  --_muted-solid-fg: var(--ty-solid-neutral-fg);
285
289
  --_muted-outlined-line: var(--ty-color-neutral);
286
290
  --_muted-ghost-fg: var(--ty-color-neutral);
291
+ --_muted-border-l: var(--ty-button-border-l-neutral, var(--ty-button-border-l, -0.15));
287
292
  }
288
293
  button.muted.tone-plus {
289
294
  --_muted-solid-bg: var(--ty-solid-neutral-strong);
@@ -306,6 +311,7 @@ button.muted.tone-minus {
306
311
  --_muted-solid-fg: unset;
307
312
  --_muted-outlined-line: unset;
308
313
  --_muted-ghost-fg: unset;
314
+ --_muted-border-l: unset;
309
315
  }
310
316
  }
311
317
  button.muted:active:not(:disabled),
@@ -314,6 +320,7 @@ button.muted:focus-visible:not(:disabled) {
314
320
  --_muted-solid-fg: unset;
315
321
  --_muted-outlined-line: unset;
316
322
  --_muted-ghost-fg: unset;
323
+ --_muted-border-l: unset;
317
324
  }
318
325
 
319
326
  button.pill {
@@ -42,7 +42,7 @@ export const calendarMonthStyles = `
42
42
  gap: 0;
43
43
  padding: 0.75rem;
44
44
  border-radius: var(--ty-radius-md);
45
- font-family: system-ui, sans-serif;
45
+ font-family: inherit;
46
46
  user-select: none;
47
47
  width: var(--calendar-width, fit-content);
48
48
  min-width: 280px;
@@ -1,2 +1,2 @@
1
1
  /** Calendar Navigation Styles */
2
- export declare const calendarNavigationStyles = "\n/* Theming tokens applied as var(--ty-calendar-nav-*, <default>) at point of\n use (NOT on :host) so consumers can override them from outside \u2014 this\n component is nested inside ty-calendar's shadow, so a :host default would\n block inherited overrides. --ty-calendar-accent still cascades in. */\n\n:host([data-size=\"md\"]) {\n --nav-btn-size: 2rem;\n --nav-btn-icon-size: 1.25rem;\n --nav-font-size: 1rem;\n --nav-padding: 0.5rem 0.75rem;\n --nav-default-width: 280px;\n}\n\n:host([data-size=\"sm\"]) {\n --nav-btn-size: 1.5rem;\n --nav-btn-icon-size: 1rem;\n --nav-font-size: 0.875rem;\n --nav-padding: 0.375rem 0.5rem;\n --nav-default-width: 240px;\n}\n\n:host([data-size=\"lg\"]) {\n --nav-btn-size: 2.5rem;\n --nav-btn-icon-size: 1.5rem;\n --nav-font-size: 1.125rem;\n --nav-padding: 0.625rem 1rem;\n --nav-default-width: 360px;\n}\n\n.calendar-navigation-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--nav-padding);\n font-family: system-ui, sans-serif;\n user-select: none;\n width: var(--nav-width, var(--nav-default-width));\n box-sizing: border-box;\n}\n\n/* Navigation groups: left, center, right */\n.nav-group {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.nav-group-left,\n.nav-group-right {\n flex: 0 0 auto;\n}\n\n.nav-group-center {\n flex: 1;\n justify-content: center;\n}\n\n.nav-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--nav-btn-size);\n height: var(--nav-btn-size);\n padding: 0;\n border: none;\n border-radius: var(--ty-radius-base);\n background-color: transparent;\n color: var(--ty-calendar-nav-color, var(--ty-color-neutral));\n cursor: pointer;\n transition: var(--ty-local-transition, all 0.15s ease);\n outline: none;\n}\n\n.nav-btn:hover {\n background-color: var(--ty-calendar-nav-hover-bg, var(--ty-bg-neutral-soft));\n color: var(--ty-calendar-nav-hover-color, var(--ty-color-neutral-strong));\n}\n\n.nav-btn:active {\n background-color: var(--ty-calendar-nav-active-bg, var(--ty-bg-neutral));\n transform: scale(0.95);\n}\n\n.nav-btn:disabled {\n opacity: 0.35;\n cursor: default;\n pointer-events: none;\n}\n\n.nav-btn:focus-visible {\n outline: 2px solid var(--ty-calendar-nav-focus-outline, var(--ty-calendar-accent, var(--ty-color-primary)));\n outline-offset: 2px;\n}\n\n.nav-btn svg {\n width: var(--nav-btn-icon-size);\n height: var(--nav-btn-icon-size);\n display: block;\n}\n\n.month-year-display {\n font-size: var(--nav-font-size);\n font-weight: 600;\n color: var(--ty-calendar-nav-title-color, var(--ty-color-neutral-strong));\n text-align: center;\n white-space: nowrap;\n letter-spacing: -0.01em;\n}\n";
2
+ export declare const calendarNavigationStyles = "\n/* Theming tokens applied as var(--ty-calendar-nav-*, <default>) at point of\n use (NOT on :host) so consumers can override them from outside \u2014 this\n component is nested inside ty-calendar's shadow, so a :host default would\n block inherited overrides. --ty-calendar-accent still cascades in. */\n\n:host([data-size=\"md\"]) {\n --nav-btn-size: 2rem;\n --nav-btn-icon-size: 1.25rem;\n --nav-font-size: 1rem;\n --nav-padding: 0.5rem 0.75rem;\n --nav-default-width: 280px;\n}\n\n:host([data-size=\"sm\"]) {\n --nav-btn-size: 1.5rem;\n --nav-btn-icon-size: 1rem;\n --nav-font-size: 0.875rem;\n --nav-padding: 0.375rem 0.5rem;\n --nav-default-width: 240px;\n}\n\n:host([data-size=\"lg\"]) {\n --nav-btn-size: 2.5rem;\n --nav-btn-icon-size: 1.5rem;\n --nav-font-size: 1.125rem;\n --nav-padding: 0.625rem 1rem;\n --nav-default-width: 360px;\n}\n\n.calendar-navigation-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--nav-padding);\n font-family: inherit;\n user-select: none;\n width: var(--nav-width, var(--nav-default-width));\n box-sizing: border-box;\n}\n\n/* Navigation groups: left, center, right */\n.nav-group {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.nav-group-left,\n.nav-group-right {\n flex: 0 0 auto;\n}\n\n.nav-group-center {\n flex: 1;\n justify-content: center;\n}\n\n.nav-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--nav-btn-size);\n height: var(--nav-btn-size);\n padding: 0;\n border: none;\n border-radius: var(--ty-radius-base);\n background-color: transparent;\n color: var(--ty-calendar-nav-color, var(--ty-color-neutral));\n cursor: pointer;\n transition: var(--ty-local-transition, all 0.15s ease);\n outline: none;\n}\n\n.nav-btn:hover {\n background-color: var(--ty-calendar-nav-hover-bg, var(--ty-bg-neutral-soft));\n color: var(--ty-calendar-nav-hover-color, var(--ty-color-neutral-strong));\n}\n\n.nav-btn:active {\n background-color: var(--ty-calendar-nav-active-bg, var(--ty-bg-neutral));\n transform: scale(0.95);\n}\n\n.nav-btn:disabled {\n opacity: 0.35;\n cursor: default;\n pointer-events: none;\n}\n\n.nav-btn:focus-visible {\n outline: 2px solid var(--ty-calendar-nav-focus-outline, var(--ty-calendar-accent, var(--ty-color-primary)));\n outline-offset: 2px;\n}\n\n.nav-btn svg {\n width: var(--nav-btn-icon-size);\n height: var(--nav-btn-icon-size);\n display: block;\n}\n\n.month-year-display {\n font-size: var(--nav-font-size);\n font-weight: 600;\n color: var(--ty-calendar-nav-title-color, var(--ty-color-neutral-strong));\n text-align: center;\n white-space: nowrap;\n letter-spacing: -0.01em;\n}\n";
@@ -33,7 +33,7 @@ export const calendarNavigationStyles = `
33
33
  align-items: center;
34
34
  justify-content: space-between;
35
35
  padding: var(--nav-padding);
36
- font-family: system-ui, sans-serif;
36
+ font-family: inherit;
37
37
  user-select: none;
38
38
  width: var(--nav-width, var(--nav-default-width));
39
39
  box-sizing: border-box;
@@ -2,4 +2,4 @@
2
2
  * Calendar Orchestration Styles
3
3
  * Simple container for navigation + month display
4
4
  */
5
- export declare const calendarStyles = "\n.calendar-container {\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n box-sizing: border-box;\n font-family: system-ui, sans-serif;\n}\n\n@media (max-width: 320px) {\n .calendar-container {\n gap: 0.375rem;\n }\n}\n";
5
+ export declare const calendarStyles = "\n.calendar-container {\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n box-sizing: border-box;\n font-family: inherit;\n}\n\n@media (max-width: 320px) {\n .calendar-container {\n gap: 0.375rem;\n }\n}\n";
@@ -4,7 +4,7 @@ export const calendarStyles = `
4
4
  flex-direction: column;
5
5
  gap: 0.125rem;
6
6
  box-sizing: border-box;
7
- font-family: system-ui, sans-serif;
7
+ font-family: inherit;
8
8
  }
9
9
 
10
10
  @media (max-width: 320px) {
@@ -31,7 +31,7 @@ export const datePickerStyles = `
31
31
  display: block;
32
32
  width: auto;
33
33
  min-width: 200px;
34
- font-family: var(--ty-font-sans);
34
+ font-family: inherit;
35
35
 
36
36
  /* Theming tokens — date-picker stub. Thin shim over --ty-input-*: override
37
37
  these to retheme just the date-picker trigger, not other inputs. */
@@ -123,7 +123,7 @@ export const datePickerStyles = `
123
123
  color: var(--ty-date-picker-color);
124
124
  border: 1px solid var(--date-picker-accent, var(--ty-date-picker-border));
125
125
  border-radius: var(--ty-date-picker-radius);
126
- font-family: var(--ty-font-sans);
126
+ font-family: inherit;
127
127
  font-size: var(--ty-font-sm);
128
128
  font-weight: var(--ty-font-normal);
129
129
  line-height: var(--ty-line-height-tight);
@@ -252,7 +252,15 @@ ${FLAVORS.filter((f) => f !== "neutral").map((f) => datePickerFlavor(f)).join(""
252
252
  padding: var(--calendar-padding, 8px);
253
253
 
254
254
  opacity: 0;
255
- transition: opacity 200ms ease;
255
+ /* Entrance motion uses the individual scale/translate properties so it
256
+ composes with the collision-shift transform below (they apply first). */
257
+ scale: 0.96;
258
+ translate: 0 -6px;
259
+ transform-origin: top center;
260
+ transition:
261
+ opacity var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
262
+ scale var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
263
+ translate var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1));
256
264
 
257
265
  transform: translate(var(--calendar-offset-x, 0px), var(--calendar-offset-y, 0px));
258
266
  top: -1000px;
@@ -269,10 +277,28 @@ ${FLAVORS.filter((f) => f !== "neutral").map((f) => datePickerFlavor(f)).join(""
269
277
  bottom: var(--calendar-y);
270
278
  top: auto;
271
279
  flex-direction: column-reverse;
280
+ translate: 0 6px;
281
+ transform-origin: bottom center;
272
282
  }
273
283
 
274
284
  .calendar-dialog.open {
275
285
  opacity: 1;
286
+ scale: 1;
287
+ translate: 0 0;
288
+ }
289
+
290
+ /* First-frame "from" values — see select-base.ts: transitions can't start on
291
+ an element that was display:none last frame; @starting-style fixes that. */
292
+ @starting-style {
293
+ .calendar-dialog.open {
294
+ opacity: 0;
295
+ scale: 0.96;
296
+ translate: 0 -6px;
297
+ }
298
+
299
+ .calendar-dialog.open.position-above {
300
+ translate: 0 6px;
301
+ }
276
302
  }
277
303
 
278
304
  .calendar-dialog::backdrop {
@@ -341,7 +367,7 @@ ${FLAVORS.filter((f) => f !== "neutral").map((f) => datePickerFlavor(f)).join(""
341
367
  border-radius: var(--ty-radius-sm);
342
368
  background: var(--ty-input-bg);
343
369
  color: var(--ty-calendar-time-input-color);
344
- font-family: var(--ty-font-sans);
370
+ font-family: inherit;
345
371
  font-size: var(--ty-font-sm);
346
372
  font-variant-numeric: tabular-nums;
347
373
  text-align: center;
@@ -1 +1 @@
1
- export declare const fileUploadStyles = "\n:host {\n display: block;\n font-family: var(--ty-font-sans);\n width: 100%;\n}\n\n.file-upload-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n box-sizing: border-box;\n}\n\n.upload-label {\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n font-weight: var(--ty-font-medium);\n color: var(--ty-label-color);\n margin-bottom: 6px;\n padding-left: 4px;\n display: flex;\n align-items: center;\n}\n\n.required-icon {\n color: var(--ty-color-danger);\n margin-left: 4px;\n font-size: 0.75rem;\n line-height: 1;\n}\n\n.drop-zone {\n border: 2px dashed var(--ty-border);\n border-radius: var(--ty-radius-base);\n padding: 2rem 1.5rem;\n text-align: center;\n cursor: pointer;\n transition: var(--ty-local-transition, border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease);\n outline: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n min-height: 140px;\n user-select: none;\n}\n\n.drop-zone:hover:not(.disabled) {\n border-color: var(--ty-input-border-hover);\n background: var(--ty-surface-content);\n}\n\n.drop-zone.focused {\n border-color: var(--ty-input-border-focus);\n box-shadow: 0 0 0 3px var(--ty-input-shadow-focus);\n}\n\n.drop-zone.drag-active {\n border-color: var(--ty-color-primary);\n border-style: solid;\n background: var(--ty-bg-primary-);\n}\n\n.drop-zone.drag-active .upload-icon {\n color: var(--ty-color-primary);\n transform: translateY(-2px);\n}\n\n.drop-zone.has-files {\n min-height: 80px;\n padding: 1.25rem 1.5rem;\n}\n\n.drop-zone.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.drop-zone.error {\n border-color: var(--ty-color-danger);\n}\n\n.drop-zone.error:not(.disabled) {\n background: var(--ty-bg-danger-);\n}\n\n.upload-icon {\n color: var(--ty-text-faint);\n width: 2.5rem;\n height: 2.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: var(--ty-local-transition, color 0.15s ease, transform 0.15s ease);\n pointer-events: none;\n}\n\n.upload-icon svg {\n width: 100%;\n height: 100%;\n}\n\n.upload-hint {\n color: var(--ty-text-soft);\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n pointer-events: none;\n}\n\n.browse-link {\n color: var(--ty-color-primary);\n font-weight: var(--ty-font-medium);\n}\n\n.upload-sub-hint {\n color: var(--ty-text-faint);\n font-size: var(--ty-font-xs);\n line-height: var(--ty-leading-xs);\n pointer-events: none;\n}\n\n.file-list {\n display: flex;\n flex-direction: column;\n gap: 0.375rem;\n margin-top: 0.5rem;\n}\n\n.file-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem 0.75rem;\n border-radius: var(--ty-radius-base);\n background: var(--ty-surface-content);\n border: 1px solid var(--ty-border-soft);\n}\n\n.file-icon {\n color: var(--ty-text-faint);\n flex-shrink: 0;\n width: 1rem;\n height: 1rem;\n display: flex;\n align-items: center;\n}\n\n.file-icon svg {\n width: 100%;\n height: 100%;\n}\n\n.file-name {\n flex: 1;\n font-size: var(--ty-font-sm);\n color: var(--ty-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.file-size {\n font-size: var(--ty-font-xs);\n color: var(--ty-text-faint);\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n}\n\n.file-remove {\n flex-shrink: 0;\n cursor: pointer;\n color: var(--ty-text-faint);\n width: 1.25rem;\n height: 1.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition: var(--ty-local-transition, color 0.15s ease, background-color 0.15s ease);\n border: none;\n background: none;\n padding: 0;\n outline: none;\n}\n\n.file-remove:hover {\n color: var(--ty-color-danger);\n background: var(--ty-bg-danger-);\n}\n\n.file-remove:focus-visible {\n box-shadow: 0 0 0 2px var(--ty-color-danger);\n}\n\n.file-remove svg {\n width: 12px;\n height: 12px;\n}\n\n.error-message {\n font-size: var(--ty-font-xs);\n line-height: var(--ty-leading-xs);\n color: var(--ty-color-danger);\n margin-top: 4px;\n padding-left: 4px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .drop-zone,\n .upload-icon,\n .file-remove {\n transition: none;\n }\n}\n\n@media (prefers-contrast: high) {\n .drop-zone {\n border-width: 3px;\n }\n}\n";
1
+ export declare const fileUploadStyles = "\n:host {\n display: block;\n font-family: inherit;\n width: 100%;\n}\n\n.file-upload-container {\n display: flex;\n flex-direction: column;\n width: 100%;\n box-sizing: border-box;\n}\n\n.upload-label {\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n font-weight: var(--ty-font-medium);\n color: var(--ty-label-color);\n margin-bottom: 6px;\n padding-left: 4px;\n display: flex;\n align-items: center;\n}\n\n.required-icon {\n color: var(--ty-color-danger);\n margin-left: 4px;\n font-size: 0.75rem;\n line-height: 1;\n}\n\n.drop-zone {\n border: 2px dashed var(--ty-border);\n border-radius: var(--ty-radius-base);\n padding: 2rem 1.5rem;\n text-align: center;\n cursor: pointer;\n transition: var(--ty-local-transition, border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease);\n outline: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n min-height: 140px;\n user-select: none;\n}\n\n.drop-zone:hover:not(.disabled) {\n border-color: var(--ty-input-border-hover);\n background: var(--ty-surface-content);\n}\n\n.drop-zone.focused {\n border-color: var(--ty-input-border-focus);\n box-shadow: 0 0 0 3px var(--ty-input-shadow-focus);\n}\n\n.drop-zone.drag-active {\n border-color: var(--ty-color-primary);\n border-style: solid;\n background: var(--ty-bg-primary-);\n}\n\n.drop-zone.drag-active .upload-icon {\n color: var(--ty-color-primary);\n transform: translateY(-2px);\n}\n\n.drop-zone.has-files {\n min-height: 80px;\n padding: 1.25rem 1.5rem;\n}\n\n.drop-zone.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.drop-zone.error {\n border-color: var(--ty-color-danger);\n}\n\n.drop-zone.error:not(.disabled) {\n background: var(--ty-bg-danger-);\n}\n\n.upload-icon {\n color: var(--ty-text-faint);\n width: 2.5rem;\n height: 2.5rem;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: var(--ty-local-transition, color 0.15s ease, transform 0.15s ease);\n pointer-events: none;\n}\n\n.upload-icon svg {\n width: 100%;\n height: 100%;\n}\n\n.upload-hint {\n color: var(--ty-text-soft);\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n pointer-events: none;\n}\n\n.browse-link {\n color: var(--ty-color-primary);\n font-weight: var(--ty-font-medium);\n}\n\n.upload-sub-hint {\n color: var(--ty-text-faint);\n font-size: var(--ty-font-xs);\n line-height: var(--ty-leading-xs);\n pointer-events: none;\n}\n\n.file-list {\n display: flex;\n flex-direction: column;\n gap: 0.375rem;\n margin-top: 0.5rem;\n}\n\n.file-item {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.5rem 0.75rem;\n border-radius: var(--ty-radius-base);\n background: var(--ty-surface-content);\n border: 1px solid var(--ty-border-soft);\n}\n\n.file-icon {\n color: var(--ty-text-faint);\n flex-shrink: 0;\n width: 1rem;\n height: 1rem;\n display: flex;\n align-items: center;\n}\n\n.file-icon svg {\n width: 100%;\n height: 100%;\n}\n\n.file-name {\n flex: 1;\n font-size: var(--ty-font-sm);\n color: var(--ty-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n}\n\n.file-size {\n font-size: var(--ty-font-xs);\n color: var(--ty-text-faint);\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n}\n\n.file-remove {\n flex-shrink: 0;\n cursor: pointer;\n color: var(--ty-text-faint);\n width: 1.25rem;\n height: 1.25rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition: var(--ty-local-transition, color 0.15s ease, background-color 0.15s ease);\n border: none;\n background: none;\n padding: 0;\n outline: none;\n}\n\n.file-remove:hover {\n color: var(--ty-color-danger);\n background: var(--ty-bg-danger-);\n}\n\n.file-remove:focus-visible {\n box-shadow: 0 0 0 2px var(--ty-color-danger);\n}\n\n.file-remove svg {\n width: 12px;\n height: 12px;\n}\n\n.error-message {\n font-size: var(--ty-font-xs);\n line-height: var(--ty-leading-xs);\n color: var(--ty-color-danger);\n margin-top: 4px;\n padding-left: 4px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .drop-zone,\n .upload-icon,\n .file-remove {\n transition: none;\n }\n}\n\n@media (prefers-contrast: high) {\n .drop-zone {\n border-width: 3px;\n }\n}\n";
@@ -1,7 +1,7 @@
1
1
  export const fileUploadStyles = `
2
2
  :host {
3
3
  display: block;
4
- font-family: var(--ty-font-sans);
4
+ font-family: inherit;
5
5
  width: 100%;
6
6
  }
7
7
 
@@ -1,2 +1,2 @@
1
1
  /** Icon Component Styles */
2
- export declare const iconStyles = "\n:host {\n /* Display & Layout - CRITICAL: No containment to prevent paint deferral */\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 0;\n \n width: var(--ty-icon-size, 1em);\n height: var(--ty-icon-size, 1em);\n \n aspect-ratio: 1 / 1;\n \n \n color: inherit;\n transition: color var(--ty-transition-fast);\n \n flex-shrink: 0;\n \n /* Visibility - Force immediate rendering */\n visibility: visible !important;\n opacity: 1;\n \n /* ANTI-FLICKER: Force browser to paint immediately */\n will-change: contents;\n \n /* Prevent any sub-pixel rendering issues */\n transform: translateZ(0);\n -webkit-font-smoothing: antialiased;\n}\n\n/* The SVG element inside shadow DOM (registry / fallback path) */\n:host svg {\n width: 100%;\n height: 100%;\n display: block;\n\n /* Force immediate SVG rendering */\n will-change: auto;\n transform: translateZ(0);\n}\n\n/* Slotted SVG (server-side path: <ty-icon><svg>...</svg></ty-icon>).\n ::slotted() targets light-DOM children so they fill the host the same way\n shadow-DOM SVG does. Color is inherited from :host via currentColor. */\n::slotted(svg),\n::slotted(img) {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n:host(.icon-xs) {\n width: 0.75em;\n height: 0.75em;\n}\n\n:host(.icon-sm) {\n width: 0.875em;\n height: 0.875em;\n}\n\n:host(.icon-md) {\n width: 1em;\n height: 1em;\n}\n\n:host(.icon-lg) {\n width: 1.25em;\n height: 1.25em;\n}\n\n:host(.icon-xl) {\n width: 1.5em;\n height: 1.5em;\n}\n\n:host(.icon-2xl) {\n width: 2em;\n height: 2em;\n}\n\n/* Fixed pixel sizes */\n:host(.icon-12) {\n width: 12px;\n height: 12px;\n}\n\n:host(.icon-14) {\n width: 14px;\n height: 14px;\n}\n\n:host(.icon-16) {\n width: 16px;\n height: 16px;\n}\n\n:host(.icon-18) {\n width: 18px;\n height: 18px;\n}\n\n:host(.icon-20) {\n width: 20px;\n height: 20px;\n}\n\n:host(.icon-24) {\n width: 24px;\n height: 24px;\n}\n\n:host(.icon-32) {\n width: 32px;\n height: 32px;\n}\n\n:host(.icon-48) {\n width: 48px;\n height: 48px;\n}\n\n:host(.icon-64) {\n width: 64px;\n height: 64px;\n}\n\n:host(.icon-80) {\n width: 80px;\n height: 80px;\n}\n\n:host(.icon-96) {\n width: 96px;\n height: 96px;\n}\n\n:host(.icon-spin) {\n animation: icon-spin 1s linear infinite;\n}\n\n:host(.icon-spin.icon-tempo-slow) {\n animation: icon-spin 2s linear infinite;\n}\n\n:host(.icon-spin.icon-tempo-fast) {\n animation: icon-spin 0.5s linear infinite;\n}\n\n@keyframes icon-spin {\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n:host(.icon-pulse) {\n animation: icon-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n:host(.icon-pulse.icon-tempo-slow) {\n animation: icon-pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n:host(.icon-pulse.icon-tempo-fast) {\n animation: icon-pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n@keyframes icon-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.5;\n }\n}\n\n/* Accessibility: ensure icons are not announced by screen readers by default */\n:host {\n -webkit-user-select: none;\n user-select: none;\n}\n\n:host(:not(.icon-clickable)) {\n pointer-events: none;\n}\n\n:host(.icon-clickable) {\n cursor: pointer;\n pointer-events: auto;\n transition: color var(--ty-transition-fast), transform var(--ty-transition-fast);\n}\n\n:host(.icon-clickable:hover) {\n transform: scale(1.1);\n}\n\n:host(.icon-clickable:active) {\n transform: scale(0.95);\n}\n\n:host(.icon-disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n:host(.icon-block) {\n display: block;\n}\n";
2
+ export declare const iconStyles = "\n:host {\n /* Display & Layout - CRITICAL: No containment to prevent paint deferral */\n /* Block-level flex, NOT inline-flex: as an inline box the icon generates a\n line box in its parent, so a shrink-wrapping wrapper (badge, .rounded-full\n chip) inherits the parent's line-height strut and renders taller than wide\n \u2014 a 12px icon in a 24px-line-height badge becomes a 22x34 oval. To place an\n icon inline with text, make the *text wrapper* inline-flex instead. */\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 0;\n \n width: var(--ty-icon-size, 1em);\n height: var(--ty-icon-size, 1em);\n \n aspect-ratio: 1 / 1;\n \n \n color: inherit;\n transition: color var(--ty-transition-fast);\n \n flex-shrink: 0;\n \n /* Visibility - Force immediate rendering */\n visibility: visible !important;\n opacity: 1;\n \n /* ANTI-FLICKER: Force browser to paint immediately */\n will-change: contents;\n \n /* Prevent any sub-pixel rendering issues */\n transform: translateZ(0);\n -webkit-font-smoothing: antialiased;\n}\n\n/* The SVG element inside shadow DOM (registry / fallback path) */\n:host svg {\n width: 100%;\n height: 100%;\n display: block;\n\n /* Force immediate SVG rendering */\n will-change: auto;\n transform: translateZ(0);\n}\n\n/* Slotted SVG (server-side path: <ty-icon><svg>...</svg></ty-icon>).\n ::slotted() targets light-DOM children so they fill the host the same way\n shadow-DOM SVG does. Color is inherited from :host via currentColor. */\n::slotted(svg),\n::slotted(img) {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* In a named slot the icon sits alongside text (button start/end), so inline\n layout is what the host expects there. */\n:host([slot]) {\n display: inline-flex;\n}\n\n:host(.icon-xs) {\n width: 0.75em;\n height: 0.75em;\n}\n\n:host(.icon-sm) {\n width: 0.875em;\n height: 0.875em;\n}\n\n:host(.icon-md) {\n width: 1em;\n height: 1em;\n}\n\n:host(.icon-lg) {\n width: 1.25em;\n height: 1.25em;\n}\n\n:host(.icon-xl) {\n width: 1.5em;\n height: 1.5em;\n}\n\n:host(.icon-2xl) {\n width: 2em;\n height: 2em;\n}\n\n/* Fixed pixel sizes */\n:host(.icon-12) {\n width: 12px;\n height: 12px;\n}\n\n:host(.icon-14) {\n width: 14px;\n height: 14px;\n}\n\n:host(.icon-16) {\n width: 16px;\n height: 16px;\n}\n\n:host(.icon-18) {\n width: 18px;\n height: 18px;\n}\n\n:host(.icon-20) {\n width: 20px;\n height: 20px;\n}\n\n:host(.icon-24) {\n width: 24px;\n height: 24px;\n}\n\n:host(.icon-32) {\n width: 32px;\n height: 32px;\n}\n\n:host(.icon-48) {\n width: 48px;\n height: 48px;\n}\n\n:host(.icon-64) {\n width: 64px;\n height: 64px;\n}\n\n:host(.icon-80) {\n width: 80px;\n height: 80px;\n}\n\n:host(.icon-96) {\n width: 96px;\n height: 96px;\n}\n\n:host(.icon-spin) {\n animation: icon-spin 1s linear infinite;\n}\n\n:host(.icon-spin.icon-tempo-slow) {\n animation: icon-spin 2s linear infinite;\n}\n\n:host(.icon-spin.icon-tempo-fast) {\n animation: icon-spin 0.5s linear infinite;\n}\n\n@keyframes icon-spin {\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n:host(.icon-pulse) {\n animation: icon-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n:host(.icon-pulse.icon-tempo-slow) {\n animation: icon-pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n:host(.icon-pulse.icon-tempo-fast) {\n animation: icon-pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n@keyframes icon-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.5;\n }\n}\n\n/* Accessibility: ensure icons are not announced by screen readers by default */\n:host {\n -webkit-user-select: none;\n user-select: none;\n}\n\n:host(:not(.icon-clickable)) {\n pointer-events: none;\n}\n\n:host(.icon-clickable) {\n cursor: pointer;\n pointer-events: auto;\n transition: color var(--ty-transition-fast), transform var(--ty-transition-fast);\n}\n\n:host(.icon-clickable:hover) {\n transform: scale(1.1);\n}\n\n:host(.icon-clickable:active) {\n transform: scale(0.95);\n}\n\n:host(.icon-disabled) {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n:host(.icon-block) {\n display: block;\n}\n";
@@ -1,7 +1,12 @@
1
1
  export const iconStyles = `
2
2
  :host {
3
3
  /* Display & Layout - CRITICAL: No containment to prevent paint deferral */
4
- display: inline-flex;
4
+ /* Block-level flex, NOT inline-flex: as an inline box the icon generates a
5
+ line box in its parent, so a shrink-wrapping wrapper (badge, .rounded-full
6
+ chip) inherits the parent's line-height strut and renders taller than wide
7
+ — a 12px icon in a 24px-line-height badge becomes a 22x34 oval. To place an
8
+ icon inline with text, make the *text wrapper* inline-flex instead. */
9
+ display: flex;
5
10
  align-items: center;
6
11
  justify-content: center;
7
12
  line-height: 0;
@@ -50,6 +55,12 @@ export const iconStyles = `
50
55
  display: block;
51
56
  }
52
57
 
58
+ /* In a named slot the icon sits alongside text (button start/end), so inline
59
+ layout is what the host expects there. */
60
+ :host([slot]) {
61
+ display: inline-flex;
62
+ }
63
+
53
64
  :host(.icon-xs) {
54
65
  width: 0.75em;
55
66
  height: 0.75em;
@@ -29,7 +29,7 @@ export const inputCustomFlavorCss = (base) => inputFlavor(base, "neutral");
29
29
  export const inputStyles = `
30
30
  :host {
31
31
  display: block;
32
- font-family: var(--ty-font-sans);
32
+ font-family: inherit;
33
33
  width: 100%;
34
34
  }
35
35
 
@@ -178,6 +178,7 @@ export const inputStyles = `
178
178
 
179
179
  input {
180
180
  /* Linear-paired typography */
181
+ font-family: inherit;
181
182
  flex: 1;
182
183
  min-width: 0;
183
184
  box-sizing: border-box;
@@ -4,4 +4,4 @@
4
4
  * CSS for the modal component wrapper.
5
5
  * The modal is a pure wrapper - all content styling is user-defined.
6
6
  */
7
- export declare const modalStyles = "\n:host {\n display: contents;\n /* Don't interfere with layout */\n}\n\n/* Part of the modal wrapper functionality, not user content styling */\n.close-button {\n position: absolute;\n top: -8px;\n right: -8px;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: var(--ty-transition-colors);\n z-index: 1;\n color: #d8d8d8;\n\n /* Remove all default browser styling that could cause outlines/borders */\n outline: none;\n box-shadow: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n\n /* Remove mobile tap highlights and focus rings */\n -webkit-tap-highlight-color: transparent;\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.close-button:hover {\n color: white;\n}\n\n/* Explicitly remove focus and active states for all interaction modes */\n.close-button:focus,\n.close-button:focus-visible,\n.close-button:focus-within,\n.close-button:active {\n outline: none !important;\n box-shadow: none !important;\n border: none !important;\n background: transparent !important;\n}\n\n/* Override hover color even in focus/active states */\n.close-button:hover,\n.close-button:focus:hover,\n.close-button:active:hover {\n color: white;\n}\n\n.close-button.hide {\n visibility: hidden;\n}\n\n/* Pure wrapper: only essential modal functionality, no styling of user content */\n.ty-modal-dialog {\n /* Reset default dialog styles */\n padding: 18px;\n border: none;\n margin: 0;\n background: transparent;\n\n /* Centered */\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n\n z-index: 1000;\n overflow: hidden;\n\n /* No sizing - let user content determine size */\n max-width: 85vw;\n max-height: 90vh;\n\n outline: none;\n}\n\n/* Backdrop is part of modal functionality, not user content */\n.ty-modal-dialog::backdrop {\n background: var(--ty-modal-backdrop, rgba(0, 0, 0, 0.5));\n backdrop-filter: var(--ty-modal-backdrop-blur, blur(2px));\n animation: ty-modal-backdrop-enter var(--ty-modal-duration, 200ms) ease-out;\n}\n\n/* When backdrop is disabled */\n.ty-modal-dialog:not([data-backdrop])::backdrop {\n background: transparent;\n backdrop-filter: none;\n}\n\n/* Minimal container - no styling imposed on user content */\n.modal-content {\n color: var(--ty-text);\n}\n\n@keyframes ty-modal-backdrop-enter {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n";
7
+ export declare const modalStyles = "\n:host {\n display: contents;\n /* Don't interfere with layout */\n}\n\n/* Part of the modal wrapper functionality, not user content styling */\n.close-button {\n position: absolute;\n top: -8px;\n right: -8px;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: var(--ty-transition-colors);\n z-index: 1;\n color: #d8d8d8;\n\n /* Remove all default browser styling that could cause outlines/borders */\n outline: none;\n box-shadow: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n\n /* Remove mobile tap highlights and focus rings */\n -webkit-tap-highlight-color: transparent;\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.close-button:hover {\n color: white;\n}\n\n/* Explicitly remove focus and active states for all interaction modes */\n.close-button:focus,\n.close-button:focus-visible,\n.close-button:focus-within,\n.close-button:active {\n outline: none !important;\n box-shadow: none !important;\n border: none !important;\n background: transparent !important;\n}\n\n/* Override hover color even in focus/active states */\n.close-button:hover,\n.close-button:focus:hover,\n.close-button:active:hover {\n color: white;\n}\n\n.close-button.hide {\n visibility: hidden;\n}\n\n/* Pure wrapper: only essential modal functionality, no styling of user content */\n.ty-modal-dialog {\n /* Reset default dialog styles */\n padding: 18px;\n border: none;\n margin: 0;\n background: transparent;\n\n /* Centered */\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n\n z-index: 1000;\n overflow: hidden;\n\n /* No sizing - let user content determine size */\n max-width: 85vw;\n max-height: 90vh;\n\n outline: none;\n}\n\n/* Panel zoom-in synced with the backdrop fade (shadcn-style zoom-in-95).\n Enter-only: animating exit on a native <dialog> means delaying close(). */\n.ty-modal-dialog[open] {\n animation: ty-modal-enter var(--ty-modal-duration, 200ms) ease-out;\n}\n\n@keyframes ty-modal-enter {\n from {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.93);\n }\n\n to {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n}\n\n/* Backdrop is part of modal functionality, not user content */\n.ty-modal-dialog::backdrop {\n background: var(--ty-modal-backdrop, rgba(0, 0, 0, 0.5));\n backdrop-filter: var(--ty-modal-backdrop-blur, blur(2px));\n animation: ty-modal-backdrop-enter var(--ty-modal-duration, 200ms) ease-out;\n}\n\n/* When backdrop is disabled */\n.ty-modal-dialog:not([data-backdrop])::backdrop {\n background: transparent;\n backdrop-filter: none;\n}\n\n/* Minimal container - no styling imposed on user content */\n.modal-content {\n color: var(--ty-text);\n}\n\n@keyframes ty-modal-backdrop-enter {\n from {\n opacity: 0;\n }\n\n to {\n opacity: 1;\n }\n}\n";
@@ -84,6 +84,24 @@ export const modalStyles = `
84
84
  outline: none;
85
85
  }
86
86
 
87
+ /* Panel zoom-in synced with the backdrop fade (shadcn-style zoom-in-95).
88
+ Enter-only: animating exit on a native <dialog> means delaying close(). */
89
+ .ty-modal-dialog[open] {
90
+ animation: ty-modal-enter var(--ty-modal-duration, 200ms) ease-out;
91
+ }
92
+
93
+ @keyframes ty-modal-enter {
94
+ from {
95
+ opacity: 0;
96
+ transform: translate(-50%, -50%) scale(0.93);
97
+ }
98
+
99
+ to {
100
+ opacity: 1;
101
+ transform: translate(-50%, -50%) scale(1);
102
+ }
103
+ }
104
+
87
105
  /* Backdrop is part of modal functionality, not user content */
88
106
  .ty-modal-dialog::backdrop {
89
107
  background: var(--ty-modal-backdrop, rgba(0, 0, 0, 0.5));
@@ -1,2 +1,2 @@
1
1
  /** Option Component Styles */
2
- export declare const optionStyles = "\n.option-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ty-spacing-2);\n box-sizing: border-box;\n width: 100%;\n\n cursor: pointer;\n user-select: none;\n\n transition: var(--ty-transition-all);\n\n /* Linear-paired typography */\n color: var(--ty-text);\n font-family: var(--ty-font-sans);\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n padding: var(--ty-spacing-2) var(--ty-spacing-3);\n border-radius: var(--ty-radius-md);\n}\n\n:host([cloned]) .option-content {\n padding: 0px;\n background: transparent;\n}\n\n.option-content:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[highlighted] {\n background: var(--ty-bg-neutral-soft);\n color: var(--ty-text-strong);\n font-weight: var(--ty-font-semibold);\n}\n\n/* Selected: quiet \u2014 bolder text color only, no background slab, no weight change */\n.option-content[selected] {\n background: transparent;\n color: var(--ty-text-bold);\n}\n\n/* Lucide check \u2014 right-aligned, visible only on selected options */\n.option-check {\n display: none;\n flex-shrink: 0;\n width: 14px;\n height: 14px;\n margin-left: auto;\n color: var(--ty-color-primary);\n}\n\n.option-content[selected] .option-check {\n display: block;\n}\n\n/* Never show the check on the copy cloned into the select's trigger field */\n:host([cloned]) .option-check {\n display: none;\n}\n\n.option-content[selected]:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.option-content[hidden] {}\n\n.option-content * {\n pointer-events: none;\n}\n";
2
+ export declare const optionStyles = "\n.option-content {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--ty-spacing-2);\n box-sizing: border-box;\n width: 100%;\n\n cursor: pointer;\n user-select: none;\n\n transition: var(--ty-transition-all);\n\n /* Linear-paired typography */\n color: var(--ty-text);\n font-family: inherit;\n font-size: var(--ty-font-sm);\n line-height: var(--ty-leading-sm);\n letter-spacing: var(--ty-tracking-sm);\n padding: var(--ty-spacing-2) var(--ty-spacing-3);\n border-radius: var(--ty-radius-md);\n}\n\n/* Cloned copy (projected into the select's trigger field): host defaults to\n inline like any unstyled custom element, which ignores width/overflow \u2014\n force a block box so the parent's flex-basis:0 truncation (select.ts\n .select-stub.has-clone slot[name=\"selected\"]) actually clips it, and\n collapse the content to a single ellipsized line instead of wrapping. */\n:host([cloned]) {\n display: block;\n min-width: 0;\n overflow: hidden;\n}\n:host([cloned]) .option-content {\n display: block;\n padding: 0px;\n background: transparent;\n min-width: 0;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.option-content:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[highlighted] {\n background: var(--ty-bg-neutral-soft);\n color: var(--ty-text-strong);\n font-weight: var(--ty-font-semibold);\n}\n\n/* Selected: quiet \u2014 bolder text color only, no background slab, no weight change */\n.option-content[selected] {\n background: transparent;\n color: var(--ty-text-bold);\n}\n\n/* Lucide check \u2014 right-aligned, visible only on selected options */\n.option-check {\n display: none;\n flex-shrink: 0;\n width: 14px;\n height: 14px;\n margin-left: auto;\n color: var(--ty-color-primary);\n}\n\n.option-content[selected] .option-check {\n display: block;\n}\n\n/* Never show the check on the copy cloned into the select's trigger field */\n:host([cloned]) .option-check {\n display: none;\n}\n\n.option-content[selected]:hover {\n background: var(--ty-bg-neutral-soft);\n}\n\n.option-content[disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.option-content[hidden] {}\n\n.option-content * {\n pointer-events: none;\n}\n";
@@ -14,7 +14,7 @@ export const optionStyles = `
14
14
 
15
15
  /* Linear-paired typography */
16
16
  color: var(--ty-text);
17
- font-family: var(--ty-font-sans);
17
+ font-family: inherit;
18
18
  font-size: var(--ty-font-sm);
19
19
  line-height: var(--ty-leading-sm);
20
20
  letter-spacing: var(--ty-tracking-sm);
@@ -22,9 +22,24 @@ export const optionStyles = `
22
22
  border-radius: var(--ty-radius-md);
23
23
  }
24
24
 
25
+ /* Cloned copy (projected into the select's trigger field): host defaults to
26
+ inline like any unstyled custom element, which ignores width/overflow —
27
+ force a block box so the parent's flex-basis:0 truncation (select.ts
28
+ .select-stub.has-clone slot[name="selected"]) actually clips it, and
29
+ collapse the content to a single ellipsized line instead of wrapping. */
30
+ :host([cloned]) {
31
+ display: block;
32
+ min-width: 0;
33
+ overflow: hidden;
34
+ }
25
35
  :host([cloned]) .option-content {
36
+ display: block;
26
37
  padding: 0px;
27
38
  background: transparent;
39
+ min-width: 0;
40
+ overflow: hidden;
41
+ white-space: nowrap;
42
+ text-overflow: ellipsis;
28
43
  }
29
44
 
30
45
  .option-content:hover {
@@ -4,4 +4,4 @@
4
4
  * Popup component with dropdown-like behavior and tooltip positioning.
5
5
  * Uses dialog element for top layer rendering like dropdown and date-picker.
6
6
  */
7
- export declare const popupStyles = "\n.popup-dialog {\n position: fixed;\n top: 0;\n left: 0;\n\n /* Reset dialog defaults */\n margin: 0;\n border: none;\n background: transparent;\n max-width: none;\n max-height: none;\n\n /* Shadow infrastructure - provide space for user shadows */\n padding: var(--popup-padding, 16px);\n\n /* Hidden by default - even when [open] (for positioning phase) */\n visibility: hidden;\n opacity: 0;\n /* NO TRANSFORM here - apply only when animating to avoid measurement errors! */\n\n transition:\n opacity 150ms ease-out,\n visibility 150ms ease-out,\n transform 150ms ease-out;\n\n /* Prevent interactions when hidden */\n pointer-events: none;\n}\n\n/* Apply position variables only when position is calculated (like dropdown) */\n.popup-dialog.position-calculated {\n top: var(--popup-y, 0px);\n left: var(--popup-x, 0px);\n}\n\n/* Apply scale for entrance animation (right before .open) */\n.popup-dialog.preparing-animation {\n transform: scale(0.95);\n}\n\n.popup-dialog.open {\n visibility: visible;\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n}\n\n/* Make dialog backdrop transparent like dropdown/date-picker */\n.popup-dialog::backdrop {\n background: transparent;\n}\n\n/* Neutral structural container \u2014 users control all visuals via slotted content */\n.popup-container {\n display: contents;\n}\n\n/* Slotted content defines its own styling */\n#popup-content {\n display: contents;\n}\n\n/* Users have complete control over the popup's appearance; only essential\n positioning is imposed here. */\n#popup-content ::slotted(*) {\n position: relative;\n\n /* Prevent content from being too wide - still helpful */\n max-width: min(400px, 90vw);\n max-height: min(500px, 80vh);\n overflow: auto;\n}\n";
7
+ export declare const popupStyles = "\n.popup-dialog {\n position: fixed;\n top: 0;\n left: 0;\n\n /* Reset dialog defaults */\n margin: 0;\n border: none;\n background: transparent;\n max-width: none;\n max-height: none;\n\n /* Shadow infrastructure - provide space for user shadows */\n padding: var(--popup-padding, 16px);\n\n /* Hidden by default - even when [open] (for positioning phase) */\n visibility: hidden;\n opacity: 0;\n /* NO TRANSFORM here - apply only when animating to avoid measurement errors! */\n\n transition:\n opacity var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),\n visibility var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),\n transform var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1));\n\n /* Prevent interactions when hidden */\n pointer-events: none;\n}\n\n/* Apply position variables only when position is calculated (like dropdown) */\n.popup-dialog.position-calculated {\n top: var(--popup-y, 0px);\n left: var(--popup-x, 0px);\n}\n\n/* Entrance prep (right before .open): scale + a short slide from the anchor\n side. data-side is the RESOLVED side (post-flip), set by updatePosition. */\n.popup-dialog.preparing-animation {\n transform: scale(0.96);\n}\n\n.popup-dialog[data-side=\"bottom\"].preparing-animation:not(.open) {\n transform: translateY(-6px) scale(0.96);\n}\n\n.popup-dialog[data-side=\"top\"].preparing-animation:not(.open) {\n transform: translateY(6px) scale(0.96);\n}\n\n.popup-dialog[data-side=\"left\"].preparing-animation:not(.open) {\n transform: translateX(6px) scale(0.96);\n}\n\n.popup-dialog[data-side=\"right\"].preparing-animation:not(.open) {\n transform: translateX(-6px) scale(0.96);\n}\n\n.popup-dialog.open {\n visibility: visible;\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n}\n\n/* Make dialog backdrop transparent like dropdown/date-picker */\n.popup-dialog::backdrop {\n background: transparent;\n}\n\n/* Neutral structural container \u2014 users control all visuals via slotted content */\n.popup-container {\n display: contents;\n}\n\n/* Slotted content defines its own styling */\n#popup-content {\n display: contents;\n}\n\n/* Users have complete control over the popup's appearance; only essential\n positioning is imposed here. */\n#popup-content ::slotted(*) {\n position: relative;\n\n /* Prevent content from being too wide - still helpful */\n max-width: min(400px, 90vw);\n max-height: min(500px, 80vh);\n overflow: auto;\n}\n";
@@ -20,9 +20,9 @@ export const popupStyles = `
20
20
  /* NO TRANSFORM here - apply only when animating to avoid measurement errors! */
21
21
 
22
22
  transition:
23
- opacity 150ms ease-out,
24
- visibility 150ms ease-out,
25
- transform 150ms ease-out;
23
+ opacity var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
24
+ visibility var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
25
+ transform var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1));
26
26
 
27
27
  /* Prevent interactions when hidden */
28
28
  pointer-events: none;
@@ -34,9 +34,26 @@ export const popupStyles = `
34
34
  left: var(--popup-x, 0px);
35
35
  }
36
36
 
37
- /* Apply scale for entrance animation (right before .open) */
37
+ /* Entrance prep (right before .open): scale + a short slide from the anchor
38
+ side. data-side is the RESOLVED side (post-flip), set by updatePosition. */
38
39
  .popup-dialog.preparing-animation {
39
- transform: scale(0.95);
40
+ transform: scale(0.96);
41
+ }
42
+
43
+ .popup-dialog[data-side="bottom"].preparing-animation:not(.open) {
44
+ transform: translateY(-6px) scale(0.96);
45
+ }
46
+
47
+ .popup-dialog[data-side="top"].preparing-animation:not(.open) {
48
+ transform: translateY(6px) scale(0.96);
49
+ }
50
+
51
+ .popup-dialog[data-side="left"].preparing-animation:not(.open) {
52
+ transform: translateX(6px) scale(0.96);
53
+ }
54
+
55
+ .popup-dialog[data-side="right"].preparing-animation:not(.open) {
56
+ transform: translateX(-6px) scale(0.96);
40
57
  }
41
58
 
42
59
  .popup-dialog.open {
@@ -31,7 +31,7 @@ ${inputStyles}
31
31
  display: flex;
32
32
  flex-direction: column;
33
33
  gap: 0.4rem;
34
- font-family: var(--ty-font-sans);
34
+ font-family: inherit;
35
35
  color: var(--ty-color-text);
36
36
  }
37
37