tyrell-components 1.0.0-TC47 → 1.0.0-TC49
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-theme.css +88 -92
- package/css/tyrell.css +19 -0
- package/dist/tyrell-theme.css +88 -92
- 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.js +2 -8
- 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 +12 -0
- package/lib/components/modal.js +56 -13
- package/lib/components/popup.js +4 -4
- 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.js +3 -5
- package/lib/components/selected-tags.js +1 -1
- package/lib/components/tabs.d.ts +2 -0
- package/lib/components/tabs.js +15 -0
- package/lib/components/tooltip.js +22 -0
- package/lib/components/wizard.d.ts +2 -0
- package/lib/components/wizard.js +15 -0
- package/lib/styles/button.js +4 -1
- package/lib/styles/date-picker.js +27 -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 +15 -0
- package/lib/styles/popup.d.ts +1 -1
- package/lib/styles/popup.js +22 -5
- package/lib/styles/select-base.js +53 -5
- package/lib/styles/select.d.ts +1 -1
- package/lib/styles/select.js +5 -1
- package/lib/utils/number-format.d.ts +6 -1
- package/lib/utils/number-format.js +9 -2
- package/lib/utils/positioning.js +4 -4
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/styles/modal.d.ts
CHANGED
|
@@ -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";
|
package/lib/styles/modal.js
CHANGED
|
@@ -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));
|
package/lib/styles/option.d.ts
CHANGED
|
@@ -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: 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: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";
|
package/lib/styles/option.js
CHANGED
|
@@ -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 {
|
package/lib/styles/popup.d.ts
CHANGED
|
@@ -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
|
|
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";
|
package/lib/styles/popup.js
CHANGED
|
@@ -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
|
|
24
|
-
visibility
|
|
25
|
-
transform
|
|
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
|
-
/*
|
|
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.
|
|
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 {
|
|
@@ -57,7 +57,17 @@ export const selectBaseStyles = `
|
|
|
57
57
|
/* Modal handles z-index automatically */
|
|
58
58
|
|
|
59
59
|
opacity: 0;
|
|
60
|
-
|
|
60
|
+
/* Panel-level entrance: individual scale/translate compose with the
|
|
61
|
+
collision-shift transform below. The single-select skin flattens the
|
|
62
|
+
inner .dropdown-options animation (select.ts: "panel appears as one"),
|
|
63
|
+
so the dialog carries the motion for both skins. */
|
|
64
|
+
scale: 0.96;
|
|
65
|
+
translate: 0 -6px;
|
|
66
|
+
transform-origin: top center;
|
|
67
|
+
transition:
|
|
68
|
+
opacity var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
|
|
69
|
+
scale var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
|
|
70
|
+
translate var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1));
|
|
61
71
|
|
|
62
72
|
transform: translate(var(--dropdown-offset-x, 0px), var(--dropdown-offset-y, 0px));
|
|
63
73
|
top: -1000px;
|
|
@@ -80,6 +90,8 @@ export const selectBaseStyles = `
|
|
|
80
90
|
bottom: var(--dropdown-y);
|
|
81
91
|
top: auto;
|
|
82
92
|
flex-direction: column-reverse;
|
|
93
|
+
translate: 0 6px;
|
|
94
|
+
transform-origin: bottom center;
|
|
83
95
|
}
|
|
84
96
|
|
|
85
97
|
.dropdown-dialog.position-above .dropdown-header {
|
|
@@ -94,8 +106,15 @@ export const selectBaseStyles = `
|
|
|
94
106
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1), var(--ty-shadow-md);
|
|
95
107
|
}
|
|
96
108
|
|
|
109
|
+
.dropdown-dialog.position-above .dropdown-options {
|
|
110
|
+
transform: translateY(6px) scale(0.96);
|
|
111
|
+
transform-origin: bottom center;
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
.dropdown-dialog.open {
|
|
98
115
|
opacity: 1;
|
|
116
|
+
scale: 1;
|
|
117
|
+
translate: 0 0;
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
.dropdown-dialog.open .dropdown-options {
|
|
@@ -103,6 +122,31 @@ export const selectBaseStyles = `
|
|
|
103
122
|
transform: translateY(0) scale(1);
|
|
104
123
|
}
|
|
105
124
|
|
|
125
|
+
/* Transitions never start on an element that was display:none last frame —
|
|
126
|
+
showModal() + .open land in the same frame, so the enter transition was
|
|
127
|
+
silently skipped. @starting-style supplies the first-frame "from" values
|
|
128
|
+
(this is the platform mechanism for animating dialog/top-layer entry). */
|
|
129
|
+
@starting-style {
|
|
130
|
+
.dropdown-dialog.open {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
scale: 0.96;
|
|
133
|
+
translate: 0 -6px;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.dropdown-dialog.open.position-above {
|
|
137
|
+
translate: 0 6px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.dropdown-dialog.open .dropdown-options {
|
|
141
|
+
opacity: 0;
|
|
142
|
+
transform: translateY(-6px) scale(0.96);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.dropdown-dialog.open.position-above .dropdown-options {
|
|
146
|
+
transform: translateY(6px) scale(0.96);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
106
150
|
.dropdown-dialog::backdrop {
|
|
107
151
|
background: transparent;
|
|
108
152
|
}
|
|
@@ -327,8 +371,11 @@ export const selectBaseStyles = `
|
|
|
327
371
|
min-width: 0;
|
|
328
372
|
}
|
|
329
373
|
|
|
374
|
+
/* No flex-grow here — the placeholder only ever renders inside .select-stub,
|
|
375
|
+
and select.ts sets flex: 1 1 0% on it. A bare flex-grow: 1 (implicit
|
|
376
|
+
flex-basis:auto) is the wrap bug that fix exists to prevent; leaving it
|
|
377
|
+
here would silently reintroduce it if the specific rule ever moved. */
|
|
330
378
|
.dropdown-placeholder {
|
|
331
|
-
flex-grow: 1;
|
|
332
379
|
color: var(--input-placeholder, var(--ty-input-placeholder, #9ca3af));
|
|
333
380
|
font-size: var(--ty-font-sm);
|
|
334
381
|
line-height: var(--ty-leading-sm);
|
|
@@ -364,10 +411,11 @@ export const selectBaseStyles = `
|
|
|
364
411
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
|
365
412
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
366
413
|
|
|
367
|
-
transform: translateY(-
|
|
414
|
+
transform: translateY(-6px) scale(0.96);
|
|
415
|
+
transform-origin: top center;
|
|
368
416
|
transition:
|
|
369
|
-
opacity
|
|
370
|
-
transform
|
|
417
|
+
opacity var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1)),
|
|
418
|
+
transform var(--ty-popup-duration, 180ms) var(--ty-popup-ease, cubic-bezier(0.34, 1.56, 0.64, 1));
|
|
371
419
|
|
|
372
420
|
display: flex;
|
|
373
421
|
flex-wrap: wrap;
|
package/lib/styles/select.d.ts
CHANGED
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* - .compact: content-hugging trigger for toolbars, count badge
|
|
9
9
|
* - .custom-trigger (slot="trigger"): all chrome stripped
|
|
10
10
|
*/
|
|
11
|
-
export declare const selectStyles = "\n/* Compact skin hugs its content (field skin = base styles) */\n.select-stub.compact {\n display: inline-flex;\n flex-wrap: nowrap;\n width: auto;\n gap: 0.5rem;\n /* extra right padding = room for the absolutely-positioned chevron */\n padding: 0.5rem 2.25rem 0.5rem 0.875rem;\n}\n\n/* Consumer-provided trigger (slot=\"trigger\"): no chrome at all */\n.select-stub.custom-trigger {\n display: inline-block;\n width: auto;\n border: none;\n background: none;\n padding: 0;\n min-height: 0;\n border-radius: 0;\n}\n.select-stub.custom-trigger:hover {\n border: none;\n}\n\n.select-count {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.25rem;\n height: 1.25rem;\n padding: 0 0.375rem;\n border-radius: 9999px;\n /* --ty-text-primary never existed as a token \u2014 this silently fell back\n to inherited/unset color. Now follows the select's own flavor. */\n background: color-mix(in oklab, var(--select-accent, var(--ty-color-primary)) 15%, transparent);\n color: var(--select-accent-bold, var(--ty-color-primary-strong));\n font-size: var(--ty-font-xs);\n font-weight: var(--ty-font-semibold);\n font-style: normal;\n}\n.select-count[hidden] {\n display: none;\n}\n\n/* Clear (x) button \u2014 normal flex child of .select-stub, not absolutely\n positioned like .dropdown-chevron: it just takes its own space in the\n row, no padding-right gutter math needed. Lives in the trigger slot's\n fallback content, so it's automatically absent under slot=\"trigger\". */\n.select-clear {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n padding: 0;\n border: none;\n background: none;\n color: var(--input-placeholder, var(--ty-input-placeholder));\n cursor: pointer;\n border-radius: var(--ty-radius-sm);\n transition: var(--ty-local-transition, all 0.15s ease);\n}\n.select-clear[hidden] {\n display: none;\n}\n.select-clear:hover {\n /* Destructive utility action \u2014 neutral at rest, warns danger-red on\n hover regardless of the field's own flavor. */\n color: var(--ty-color-danger);\n background-color: var(--ty-bg-danger-soft);\n}\n.select-clear svg {\n width: 100%;\n height: 100%;\n}\n\n/* Selection text (field skin): single line, ellipsis, input colors.\n flex-basis 0, not auto \u2014 .select-stub is flex-wrap:wrap, and auto's\n hypothetical size is the FULL untruncated text width (wrap-line\n assignment happens before shrinking), which pushed fixed-size siblings\n (.select-clear) onto a second line even though the text visibly had\n room after truncating. 0 makes this item contribute ~nothing to the\n line-fit check up front; grow/shrink + overflow:hidden still truncate\n it identically. */\n.select-stub .dropdown-placeholder {\n font-style: normal;\n flex: 1 1 0%;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--input-color, var(--ty-input-color));\n}\n\n.select-stub .dropdown-placeholder.placeholder-shown {\n color: var(--input-placeholder, var(--ty-input-placeholder));\n}\n.select-stub .dropdown-placeholder[hidden] {\n display: none;\n}\n\n/* Single-select display clone (slot=\"selected\"): the selected ty-option\n projected into the stub \u2014 its own :host([cloned]) styling strips the\n list-row chrome; here it just takes the text's flex slot. */\n.select-stub slot[name=\"selected\"] {\n display: none;\n}\n.select-stub.has-clone slot[name=\"selected\"] {\n display: block;\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n}\n.select-stub.compact.has-clone slot[name=\"selected\"] {\n flex: 0 1 auto;\n}\n\n/* start/end slot content (icons etc.) \u2014 spacing comes from the stub's flex\n gap; muted like ty-input adornments. end sits before the chevron. */\n.select-stub ::slotted([slot=\"start\"]),\n.select-stub ::slotted([slot=\"end\"]) {\n flex-shrink: 0;\n color: var(--ty-text-soft);\n}\n\n/* Compact skin: the text IS the trigger label \u2014 no grow, regular text color.\n flex-basis: auto (not the field skin's 0%) \u2014 content-hugging needs its\n natural content width as the starting point; 0% would collapse it since\n grow is also 0 here (nothing left to expand it back out). */\n.select-stub.compact .dropdown-placeholder {\n flex: 0 1 auto;\n color: var(--ty-text);\n}\n\n/* Trigger stays visible while the popup is open (the popup hides it) */\n.dropdown-wrapper:has(.dropdown-chevron.open) .select-stub {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* One-panel popup: search header fused with the options list */\n\n/* Single unified shadow around the whole panel (header + list as one silhouette) */\n.dropdown-dialog.open {\n filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.14)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.08));\n}\n\n/* Card-style header: the search input keeps its own bordered-field look\n (base styles) and sits inset in the panel, TagPicker-style \u2014 no divider,\n whitespace separates it from the list. The header's OWN surface/border is\n part of the floating panel's chrome, not a form field \u2014 --ty-surface-\n floating/--ty-floating-border, matching .dropdown-options below it (same\n reasoning as the base-panel fix: --ty-input-* is deliberately faint for\n resting form fields and read as a missing/mismatched edge here). */\n.dropdown-header {\n background: var(--ty-surface-floating);\n border: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n padding: var(--ty-spacing-2) var(--ty-spacing-2) var(--ty-spacing-1);\n}\n.dropdown-dialog.position-below .dropdown-header {\n margin-bottom: 0;\n}\n.dropdown-dialog.position-above .dropdown-header {\n margin-top: 0;\n border-top: none;\n border-bottom: 1px solid var(--ty-floating-border);\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n padding: var(--ty-spacing-1) var(--ty-spacing-2) var(--ty-spacing-2);\n}\n\n/* Magnifier adornment inside the search field (header padding + field inset) */\n.dropdown-search-icon {\n position: absolute;\n top: 50%;\n left: calc(var(--ty-spacing-2) + var(--ty-spacing-3));\n transform: translateY(-50%);\n width: 1rem;\n height: 1rem;\n color: var(--input-placeholder, var(--ty-input-placeholder));\n pointer-events: none;\n}\n.dropdown-search-icon svg {\n width: 100%;\n height: 100%;\n}\n\n/* Mobile header: flex row, icon flows inline before the input. Same 32x32\n footprint as .mobile-close-button (18px glyph centered in the box, not a\n bare 18px glyph flush at the edge) so the row has two matching bookends \u2014\n a bare icon on one side and a 32px circle on the other read as lopsided\n even with equal flex gap on both sides. Matching them also centers the\n search input's flex:1 fill symmetrically between the two. Fixed white +\n 18px glyph, same reasoning as .mobile-header-label \u2014 this sits on the\n dark ::backdrop blur in both themes, not a theme surface. */\n.dropdown-mode-mobile .mobile-header-content .dropdown-search-icon {\n position: static;\n transform: none;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n color: #ffffff;\n}\n.dropdown-mode-mobile .mobile-header-content .dropdown-search-icon svg {\n width: 18px;\n height: 18px;\n}\n\n/* Search input: bordered field from base styles; just size + icon room */\n.dropdown-search-input {\n min-height: 2.25rem;\n padding-right: var(--ty-spacing-2);\n /* room for the magnifier adornment */\n padding-left: calc(var(--ty-spacing-3) + 1rem + var(--ty-spacing-2));\n}\n/* Trigger already has a chevron - no second one inside the search row */\n.dropdown-search-chevron {\n display: none;\n}\n\n/* With the search row hidden nothing inside is focusable, so the <dialog>\n itself receives focus on open \u2014 suppress its UA focus ring */\n.dropdown-dialog:focus,\n.dropdown-dialog:focus-visible {\n outline: none;\n}\n\n/* Search row hidden ('auto' below threshold / searchable=\"false\"):\n the options panel owns the whole silhouette */\n.dropdown-header[hidden] {\n display: none;\n}\n.dropdown-header[hidden] ~ .dropdown-options-wrapper .dropdown-options {\n border-top: 1px solid var(--ty-floating-border);\n border-radius: var(--ty-radius-lg);\n}\n.mobile-search-input[hidden],\n.mobile-header-content .dropdown-search-icon[hidden] {\n display: none;\n}\n\n/* Options: vertical list rows fused under the search header.\n No own shadow/animation - the panel appears as one via dialog opacity. */\n.dropdown-options {\n flex-direction: column;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n /* Card-style list: rounded rows sit inset from the panel edges */\n padding: var(--ty-spacing-1);\n border-top: none;\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n box-shadow: none;\n transform: none;\n transition: none;\n /* ~9-10 visible rows before scrolling (custom scrollbar, native hidden) */\n max-height: 24rem;\n}\n.dropdown-dialog.position-below .dropdown-options {\n box-shadow: none;\n}\n.dropdown-dialog.position-above .dropdown-options {\n border-top: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n}\n\n/* Loading panel continues the fused silhouette exactly like .dropdown-options \u2014\n the base layer styles it as a floating card (radius + shadow), which left a\n see-through gap under the search header while loading. */\n.dropdown-loading {\n border-top: none;\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n box-shadow: none;\n}\n.dropdown-dialog.position-above .dropdown-loading {\n border-top: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n}\n\n/* Selection tick lives inside ty-option (lucide check on [selected]) */\n\n/* Mobile modal: same vertical list */\n.dropdown-mode-mobile .section-content ::slotted(ty-option) {\n display: block;\n width: 100%;\n}\n";
|
|
11
|
+
export declare const selectStyles = "\n/* Compact skin hugs its content (field skin = base styles) */\n.select-stub.compact {\n display: inline-flex;\n flex-wrap: nowrap;\n width: auto;\n gap: 0.5rem;\n /* extra right padding = room for the absolutely-positioned chevron */\n padding: 0.5rem 2.25rem 0.5rem 0.875rem;\n}\n\n/* Consumer-provided trigger (slot=\"trigger\"): no chrome at all */\n.select-stub.custom-trigger {\n display: inline-block;\n width: auto;\n border: none;\n background: none;\n padding: 0;\n min-height: 0;\n border-radius: 0;\n}\n.select-stub.custom-trigger:hover {\n border: none;\n}\n\n.select-count {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 1.25rem;\n height: 1.25rem;\n padding: 0 0.375rem;\n border-radius: 9999px;\n /* --ty-text-primary never existed as a token \u2014 this silently fell back\n to inherited/unset color. Now follows the select's own flavor. */\n background: color-mix(in oklab, var(--select-accent, var(--ty-color-primary)) 15%, transparent);\n color: var(--select-accent-bold, var(--ty-color-primary-strong));\n font-size: var(--ty-font-xs);\n font-weight: var(--ty-font-semibold);\n font-style: normal;\n}\n.select-count[hidden] {\n display: none;\n}\n\n/* Clear (x) button \u2014 normal flex child of .select-stub, not absolutely\n positioned like .dropdown-chevron: it just takes its own space in the\n row, no padding-right gutter math needed. Lives in the trigger slot's\n fallback content, so it's automatically absent under slot=\"trigger\". */\n.select-clear {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1rem;\n height: 1rem;\n padding: 0;\n border: none;\n background: none;\n color: var(--input-placeholder, var(--ty-input-placeholder));\n cursor: pointer;\n border-radius: var(--ty-radius-sm);\n transition: var(--ty-local-transition, all 0.15s ease);\n}\n.select-clear[hidden] {\n display: none;\n}\n.select-clear:hover {\n /* Destructive utility action \u2014 neutral at rest, warns danger-red on\n hover regardless of the field's own flavor. */\n color: var(--ty-color-danger);\n background-color: var(--ty-bg-danger-soft);\n}\n.select-clear svg {\n width: 100%;\n height: 100%;\n}\n\n/* Selection text (field skin): single line, ellipsis, input colors.\n flex-basis 0, not auto \u2014 .select-stub is flex-wrap:wrap, and auto's\n hypothetical size is the FULL untruncated text width (wrap-line\n assignment happens before shrinking), which pushed fixed-size siblings\n (.select-clear) onto a second line even though the text visibly had\n room after truncating. 0 makes this item contribute ~nothing to the\n line-fit check up front; grow/shrink + overflow:hidden still truncate\n it identically. */\n.select-stub .dropdown-placeholder {\n font-style: normal;\n flex: 1 1 0%;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--input-color, var(--ty-input-color));\n}\n\n.select-stub .dropdown-placeholder.placeholder-shown {\n color: var(--input-placeholder, var(--ty-input-placeholder));\n}\n.select-stub .dropdown-placeholder[hidden] {\n display: none;\n}\n\n/* Single-select display clone (slot=\"selected\"): the selected ty-option\n projected into the stub \u2014 its own :host([cloned]) styling strips the\n list-row chrome; here it just takes the text's flex slot. */\n.select-stub slot[name=\"selected\"] {\n display: none;\n}\n.select-stub.has-clone slot[name=\"selected\"] {\n display: block;\n /* flex-basis 0, not auto \u2014 same reasoning as .dropdown-placeholder above:\n .select-stub is flex-wrap:wrap, and auto's hypothetical size is the\n clone's full untruncated text width, which pushes .select-clear onto\n a second line. */\n flex: 1 1 0%;\n min-width: 0;\n overflow: hidden;\n}\n.select-stub.compact.has-clone slot[name=\"selected\"] {\n flex: 0 1 auto;\n}\n\n/* start/end slot content (icons etc.) \u2014 spacing comes from the stub's flex\n gap; muted like ty-input adornments. end sits before the chevron. */\n.select-stub ::slotted([slot=\"start\"]),\n.select-stub ::slotted([slot=\"end\"]) {\n flex-shrink: 0;\n color: var(--ty-text-soft);\n}\n\n/* Compact skin: the text IS the trigger label \u2014 no grow, regular text color.\n flex-basis: auto (not the field skin's 0%) \u2014 content-hugging needs its\n natural content width as the starting point; 0% would collapse it since\n grow is also 0 here (nothing left to expand it back out). */\n.select-stub.compact .dropdown-placeholder {\n flex: 0 1 auto;\n color: var(--ty-text);\n}\n\n/* Trigger stays visible while the popup is open (the popup hides it) */\n.dropdown-wrapper:has(.dropdown-chevron.open) .select-stub {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* One-panel popup: search header fused with the options list */\n\n/* Single unified shadow around the whole panel (header + list as one silhouette) */\n.dropdown-dialog.open {\n filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.14)) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.08));\n}\n\n/* Card-style header: the search input keeps its own bordered-field look\n (base styles) and sits inset in the panel, TagPicker-style \u2014 no divider,\n whitespace separates it from the list. The header's OWN surface/border is\n part of the floating panel's chrome, not a form field \u2014 --ty-surface-\n floating/--ty-floating-border, matching .dropdown-options below it (same\n reasoning as the base-panel fix: --ty-input-* is deliberately faint for\n resting form fields and read as a missing/mismatched edge here). */\n.dropdown-header {\n background: var(--ty-surface-floating);\n border: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n padding: var(--ty-spacing-2) var(--ty-spacing-2) var(--ty-spacing-1);\n}\n.dropdown-dialog.position-below .dropdown-header {\n margin-bottom: 0;\n}\n.dropdown-dialog.position-above .dropdown-header {\n margin-top: 0;\n border-top: none;\n border-bottom: 1px solid var(--ty-floating-border);\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n padding: var(--ty-spacing-1) var(--ty-spacing-2) var(--ty-spacing-2);\n}\n\n/* Magnifier adornment inside the search field (header padding + field inset) */\n.dropdown-search-icon {\n position: absolute;\n top: 50%;\n left: calc(var(--ty-spacing-2) + var(--ty-spacing-3));\n transform: translateY(-50%);\n width: 1rem;\n height: 1rem;\n color: var(--input-placeholder, var(--ty-input-placeholder));\n pointer-events: none;\n}\n.dropdown-search-icon svg {\n width: 100%;\n height: 100%;\n}\n\n/* Mobile header: flex row, icon flows inline before the input. Same 32x32\n footprint as .mobile-close-button (18px glyph centered in the box, not a\n bare 18px glyph flush at the edge) so the row has two matching bookends \u2014\n a bare icon on one side and a 32px circle on the other read as lopsided\n even with equal flex gap on both sides. Matching them also centers the\n search input's flex:1 fill symmetrically between the two. Fixed white +\n 18px glyph, same reasoning as .mobile-header-label \u2014 this sits on the\n dark ::backdrop blur in both themes, not a theme surface. */\n.dropdown-mode-mobile .mobile-header-content .dropdown-search-icon {\n position: static;\n transform: none;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n color: #ffffff;\n}\n.dropdown-mode-mobile .mobile-header-content .dropdown-search-icon svg {\n width: 18px;\n height: 18px;\n}\n\n/* Search input: bordered field from base styles; just size + icon room */\n.dropdown-search-input {\n min-height: 2.25rem;\n padding-right: var(--ty-spacing-2);\n /* room for the magnifier adornment */\n padding-left: calc(var(--ty-spacing-3) + 1rem + var(--ty-spacing-2));\n}\n/* Trigger already has a chevron - no second one inside the search row */\n.dropdown-search-chevron {\n display: none;\n}\n\n/* With the search row hidden nothing inside is focusable, so the <dialog>\n itself receives focus on open \u2014 suppress its UA focus ring */\n.dropdown-dialog:focus,\n.dropdown-dialog:focus-visible {\n outline: none;\n}\n\n/* Search row hidden ('auto' below threshold / searchable=\"false\"):\n the options panel owns the whole silhouette */\n.dropdown-header[hidden] {\n display: none;\n}\n.dropdown-header[hidden] ~ .dropdown-options-wrapper .dropdown-options {\n border-top: 1px solid var(--ty-floating-border);\n border-radius: var(--ty-radius-lg);\n}\n.mobile-search-input[hidden],\n.mobile-header-content .dropdown-search-icon[hidden] {\n display: none;\n}\n\n/* Options: vertical list rows fused under the search header.\n No own shadow/animation - the panel appears as one via dialog opacity. */\n.dropdown-options {\n flex-direction: column;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 0;\n /* Card-style list: rounded rows sit inset from the panel edges */\n padding: var(--ty-spacing-1);\n border-top: none;\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n box-shadow: none;\n transform: none;\n transition: none;\n /* ~9-10 visible rows before scrolling (custom scrollbar, native hidden) */\n max-height: 24rem;\n}\n.dropdown-dialog.position-below .dropdown-options {\n box-shadow: none;\n}\n.dropdown-dialog.position-above .dropdown-options {\n border-top: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n}\n\n/* Loading panel continues the fused silhouette exactly like .dropdown-options \u2014\n the base layer styles it as a floating card (radius + shadow), which left a\n see-through gap under the search header while loading. */\n.dropdown-loading {\n border-top: none;\n border-radius: 0 0 var(--ty-radius-lg) var(--ty-radius-lg);\n box-shadow: none;\n}\n.dropdown-dialog.position-above .dropdown-loading {\n border-top: 1px solid var(--ty-floating-border);\n border-bottom: none;\n border-radius: var(--ty-radius-lg) var(--ty-radius-lg) 0 0;\n}\n\n/* Selection tick lives inside ty-option (lucide check on [selected]) */\n\n/* Mobile modal: same vertical list */\n.dropdown-mode-mobile .section-content ::slotted(ty-option) {\n display: block;\n width: 100%;\n}\n";
|
package/lib/styles/select.js
CHANGED
|
@@ -110,7 +110,11 @@ export const selectStyles = `
|
|
|
110
110
|
}
|
|
111
111
|
.select-stub.has-clone slot[name="selected"] {
|
|
112
112
|
display: block;
|
|
113
|
-
flex
|
|
113
|
+
/* flex-basis 0, not auto — same reasoning as .dropdown-placeholder above:
|
|
114
|
+
.select-stub is flex-wrap:wrap, and auto's hypothetical size is the
|
|
115
|
+
clone's full untruncated text width, which pushes .select-clear onto
|
|
116
|
+
a second line. */
|
|
117
|
+
flex: 1 1 0%;
|
|
114
118
|
min-width: 0;
|
|
115
119
|
overflow: hidden;
|
|
116
120
|
}
|
|
@@ -33,6 +33,10 @@ export declare function formatNumber(value: number, config: FormatConfig): strin
|
|
|
33
33
|
* Simple rule: the last occurring . or , is the decimal separator.
|
|
34
34
|
* Everything else is stripped. This works for both US (1,234.56) and
|
|
35
35
|
* European (1.234,56) input, and for mobile keyboards that use , as decimal.
|
|
36
|
+
* One grouping exception: a repeated separator ("1.234.567") can't be a
|
|
37
|
+
* decimal point, so it's stripped as thousands grouping. A LONE separator is
|
|
38
|
+
* always decimal — "$1,234" pasted from formatted text parses as 1.234; this
|
|
39
|
+
* known ambiguity is resolved in favor of typed input ("12,50" → 12.5).
|
|
36
40
|
*
|
|
37
41
|
* @example
|
|
38
42
|
* ```typescript
|
|
@@ -41,7 +45,8 @@ export declare function formatNumber(value: number, config: FormatConfig): strin
|
|
|
41
45
|
* parseNumericValue("12,50") // 12.5 (last separator is ,)
|
|
42
46
|
* parseNumericValue("12.50") // 12.5 (last separator is .)
|
|
43
47
|
* parseNumericValue("1234") // 1234
|
|
44
|
-
* parseNumericValue("
|
|
48
|
+
* parseNumericValue("1.234.567") // 1234567 (repeated separator = grouping)
|
|
49
|
+
* parseNumericValue("$1,234") // 1.234 (lone separator = decimal; known paste ambiguity)
|
|
45
50
|
* parseNumericValue("15%") // 15
|
|
46
51
|
* parseNumericValue("") // null
|
|
47
52
|
* ```
|
|
@@ -43,8 +43,15 @@ export function parseNumericValue(value) {
|
|
|
43
43
|
const parsed = parseFloat(stripped);
|
|
44
44
|
return isNaN(parsed) ? null : parsed;
|
|
45
45
|
}
|
|
46
|
-
const
|
|
47
|
-
const
|
|
46
|
+
const sepChar = stripped[lastSep];
|
|
47
|
+
const before = stripped.slice(0, lastSep);
|
|
48
|
+
const after = stripped.slice(lastSep + 1);
|
|
49
|
+
if (before.includes(sepChar)) {
|
|
50
|
+
const parsed = parseFloat(stripped.replace(/[.,]/g, ''));
|
|
51
|
+
return isNaN(parsed) ? null : parsed;
|
|
52
|
+
}
|
|
53
|
+
const intPart = before.replace(/[.,]/g, '');
|
|
54
|
+
const decPart = after;
|
|
48
55
|
const parsed = parseFloat(intPart + '.' + decPart);
|
|
49
56
|
return isNaN(parsed) ? null : parsed;
|
|
50
57
|
}
|
package/lib/utils/positioning.js
CHANGED
|
@@ -140,13 +140,13 @@ function calculatePlacement(options) {
|
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
142
142
|
if (horizontal === 'start') {
|
|
143
|
-
x = targetRect.left;
|
|
143
|
+
x = targetRect.left - containerPadding;
|
|
144
144
|
}
|
|
145
145
|
else if (horizontal === 'center') {
|
|
146
146
|
x = targetRect.centerX - floatingRect.width / 2;
|
|
147
147
|
}
|
|
148
148
|
else {
|
|
149
|
-
x = targetRect.right - floatingRect.width;
|
|
149
|
+
x = targetRect.right - floatingRect.width + containerPadding;
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
let y;
|
|
@@ -155,10 +155,10 @@ function calculatePlacement(options) {
|
|
|
155
155
|
y = targetRect.centerY - floatingRect.height / 2;
|
|
156
156
|
}
|
|
157
157
|
else if (vertical === 'end') {
|
|
158
|
-
y = targetRect.bottom - floatingRect.height
|
|
158
|
+
y = targetRect.bottom - floatingRect.height + containerPadding;
|
|
159
159
|
}
|
|
160
160
|
else {
|
|
161
|
-
y = targetRect.top
|
|
161
|
+
y = targetRect.top - containerPadding;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
else {
|
package/lib/version.d.ts
CHANGED
package/lib/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.0.0-
|
|
1
|
+
export const VERSION = '1.0.0-TC49';
|