@octane-xplat/ui 0.5.0 → 0.6.0

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 (281) hide show
  1. package/README.md +3 -0
  2. package/dist/native/Absolute.native.tsrx.js +43 -0
  3. package/dist/native/Accordion.native.tsrx.js +75 -0
  4. package/dist/native/ActivityIndicator.native.tsrx.js +35 -0
  5. package/dist/native/Alert.native.tsrx.js +74 -0
  6. package/dist/native/Avatar.native.tsrx.js +76 -0
  7. package/dist/native/AvatarGroup.native.tsrx.js +84 -0
  8. package/dist/native/Badge.native.tsrx.js +29 -0
  9. package/dist/native/Banner.native.tsrx.js +80 -0
  10. package/dist/native/Breadcrumb.native.tsrx.js +66 -0
  11. package/dist/native/Button.native.tsrx.js +62 -0
  12. package/dist/native/CameraView.native.tsrx.js +165 -0
  13. package/dist/native/Card.native.tsrx.js +64 -0
  14. package/dist/native/Checkbox.native.tsrx.js +58 -0
  15. package/dist/native/CheckboxGroup.native.tsrx.js +70 -0
  16. package/dist/native/Chip.native.tsrx.js +83 -0
  17. package/dist/native/Collapsible.native.tsrx.js +74 -0
  18. package/dist/native/CommandPalette.native.tsrx.js +143 -0
  19. package/dist/native/ContextMenu.native.tsrx.js +140 -0
  20. package/dist/native/Drawer.native.tsrx.js +60 -0
  21. package/dist/native/DropdownMenu.native.tsrx.js +155 -0
  22. package/dist/native/Empty.native.tsrx.js +82 -0
  23. package/dist/native/FieldGroup.native.tsrx.js +39 -0
  24. package/dist/native/FormField.native.tsrx.js +53 -0
  25. package/dist/native/{ui8.js → Grid.native.tsrx.js} +19 -8
  26. package/dist/native/{ui59.js → Heading.native.tsrx.js} +2 -2
  27. package/dist/native/{ui58.js → Icon.native.tsrx.js} +4 -4
  28. package/dist/native/{ui29.js → Image.native.tsrx.js} +4 -4
  29. package/dist/native/InputNumber.native.tsrx.js +146 -0
  30. package/dist/native/InputRating.native.tsrx.js +74 -0
  31. package/dist/native/InputTags.native.tsrx.js +131 -0
  32. package/dist/native/Kbd.native.tsrx.js +29 -0
  33. package/dist/native/{ui50.js → KeyboardAvoiding.native.tsrx.js} +1 -1
  34. package/dist/native/{ui17.js → Link.native.tsrx.js} +1 -1
  35. package/dist/native/{ui55.js → Meter.native.tsrx.js} +2 -2
  36. package/dist/native/{ui22.js → NavLink.native.tsrx.js} +3 -3
  37. package/dist/native/NavigationMenu.native.tsrx.js +67 -0
  38. package/dist/native/{ui35.js → Overlay.native.tsrx.js} +3 -3
  39. package/dist/native/Pager.native.tsrx.js +282 -0
  40. package/dist/native/Pagination.native.tsrx.js +107 -0
  41. package/dist/native/PinInput.native.tsrx.js +94 -0
  42. package/dist/native/{ui38.js → Popover.native.tsrx.js} +5 -5
  43. package/dist/native/{ui16.js → Pressable.native.tsrx.js} +20 -14
  44. package/dist/native/ProgressGroup.native.tsrx.js +66 -0
  45. package/dist/native/RadioGroup.native.tsrx.js +85 -0
  46. package/dist/native/{ui15.js → RichText.native.tsrx.js} +15 -22
  47. package/dist/native/Row.native.tsrx.js +98 -0
  48. package/dist/native/{ui49.js → SafeArea.native.tsrx.js} +2 -2
  49. package/dist/native/{ui45.js → Screen.native.tsrx.js} +3 -3
  50. package/dist/native/{ui26.js → ScrollBox.native.tsrx.js} +1 -1
  51. package/dist/native/ScrollView.native.tsrx.js +233 -0
  52. package/dist/native/SearchInput.native.tsrx.js +151 -0
  53. package/dist/native/SegmentedControl.native.tsrx.js +75 -0
  54. package/dist/native/Select.native.tsrx.js +202 -0
  55. package/dist/native/Separator.native.tsrx.js +25 -0
  56. package/dist/native/{ui63.js → Sheet.native.tsrx.js} +15 -7
  57. package/dist/native/Skeleton.native.tsrx.js +28 -0
  58. package/dist/native/Slider.native.tsrx.js +98 -0
  59. package/dist/native/{ui13.js → Spacer.native.tsrx.js} +2 -2
  60. package/dist/native/{ui9.js → Stack.native.tsrx.js} +20 -9
  61. package/dist/native/Stepper.native.tsrx.js +75 -0
  62. package/dist/native/Switch.native.tsrx.js +33 -0
  63. package/dist/native/Table.native.tsrx.js +103 -0
  64. package/dist/native/Tabs.native.tsrx.js +163 -0
  65. package/dist/native/{ui14.js → Text.native.tsrx.js} +4 -3
  66. package/dist/native/{ui24.js → TextArea.native.tsrx.js} +17 -16
  67. package/dist/native/{ui23.js → TextInput.native.tsrx.js} +21 -16
  68. package/dist/native/Timeline.native.tsrx.js +112 -0
  69. package/dist/native/Tree.native.tsrx.js +140 -0
  70. package/dist/native/User.native.tsrx.js +95 -0
  71. package/dist/native/Video.native.tsrx.js +248 -0
  72. package/dist/native/View.native.tsrx.js +98 -0
  73. package/dist/native/WebView.native.tsrx.js +110 -0
  74. package/dist/native/aliases.native.tsrx.js +38 -0
  75. package/dist/native/android/BottomNavigationView.android.tsrx.js +171 -0
  76. package/dist/native/android/CircularProgressIndicator.android.tsrx.js +27 -0
  77. package/dist/native/{ui51.js → android/DrawerLayout.android.tsrx.js} +5 -7
  78. package/dist/native/android/MaterialDialog.android.tsrx.js +62 -0
  79. package/dist/native/android/MaterialSwitch.android.tsrx.js +28 -0
  80. package/dist/native/android/RecyclerView.android.tsrx.js +248 -0
  81. package/dist/native/android/SeekBar.android.tsrx.js +34 -0
  82. package/dist/native/android/index.js +9 -0
  83. package/dist/native/android/openModal.android.js +39 -0
  84. package/dist/native/camera-permission.native.js +47 -0
  85. package/dist/native/child-array.js +15 -0
  86. package/dist/native/icons.js +25 -0
  87. package/dist/native/{ui11.js → ios/LiquidGlass.ios.tsrx.js} +4 -4
  88. package/dist/native/{ui12.js → ios/LiquidGlassContainer.ios.tsrx.js} +4 -4
  89. package/dist/native/ios/SideDrawer.ios.tsrx.js +64 -0
  90. package/dist/native/{ui53.js → ios/UIActivityIndicatorView.ios.tsrx.js} +5 -5
  91. package/dist/native/{ui32.js → ios/UIModal.ios.tsrx.js} +10 -6
  92. package/dist/native/{ui56.js → ios/UISlider.ios.tsrx.js} +5 -5
  93. package/dist/native/{ui47.js → ios/UISwitch.ios.tsrx.js} +5 -5
  94. package/dist/native/ios/UITabBar.ios.tsrx.js +166 -0
  95. package/dist/native/ios/UITableView.ios.tsrx.js +270 -0
  96. package/dist/native/ios/index.js +11 -0
  97. package/dist/native/{ui33.js → ios/openModal.ios.js} +5 -4
  98. package/dist/native/modal-presenter.native.js +19 -0
  99. package/dist/native/native/index.js +4 -0
  100. package/dist/native/octane-client-build.json +1 -1
  101. package/dist/native/{ui6.js → pan.native.tsrx.js} +9 -69
  102. package/dist/native/platform.native.js +10 -0
  103. package/dist/native/refresh-metrics.js +9 -0
  104. package/dist/native/refresh.native.js +177 -0
  105. package/dist/native/{ui34.js → root-layout.native.js} +6 -1
  106. package/dist/native/{ui21.js → route.native.js} +96 -12
  107. package/dist/native/sheet-detents.native.js +102 -0
  108. package/dist/native/{ui64.js → sheet-service.native.js} +19 -9
  109. package/dist/native/sheet-snap.js +35 -0
  110. package/dist/native/text-write.native.js +27 -0
  111. package/dist/native/{ui42.js → theme/status-bar-scheme.native.js} +2 -2
  112. package/dist/{web/ui29.js → native/theme/theme-scheme.js} +2 -2
  113. package/dist/native/{ui40.js → toast-anchor.native.tsrx.js} +3 -3
  114. package/dist/native/translate.native.js +11 -0
  115. package/dist/native/ui.js +93 -55
  116. package/dist/native/use-back.native.tsrx.js +9 -0
  117. package/dist/native/{ui52.js → useMeasure.native.tsrx.js} +1 -1
  118. package/dist/native/video-chrome.native.tsrx.js +365 -0
  119. package/dist/web/Absolute.web.tsrx.js +47 -0
  120. package/dist/web/Accordion.web.tsrx.js +85 -0
  121. package/dist/web/{ui48.js → ActivityIndicator.web.tsrx.js} +1 -1
  122. package/dist/web/Alert.web.tsrx.js +79 -0
  123. package/dist/web/Avatar.web.tsrx.js +106 -0
  124. package/dist/web/AvatarGroup.web.tsrx.js +108 -0
  125. package/dist/web/Badge.web.tsrx.js +37 -0
  126. package/dist/web/Banner.web.tsrx.js +79 -0
  127. package/dist/web/Breadcrumb.web.tsrx.js +135 -0
  128. package/dist/web/Button.web.tsrx.js +45 -0
  129. package/dist/web/CameraView.web.tsrx.js +310 -0
  130. package/dist/web/Card.web.tsrx.js +73 -0
  131. package/dist/web/Checkbox.web.tsrx.js +89 -0
  132. package/dist/web/CheckboxGroup.web.tsrx.js +79 -0
  133. package/dist/web/Chip.web.tsrx.js +64 -0
  134. package/dist/web/Collapsible.web.tsrx.js +94 -0
  135. package/dist/web/CommandPalette.web.tsrx.js +125 -0
  136. package/dist/web/ContextMenu.web.tsrx.js +154 -0
  137. package/dist/web/{ui45.js → Drawer.web.tsrx.js} +13 -9
  138. package/dist/web/DropdownMenu.web.tsrx.js +162 -0
  139. package/dist/web/Empty.web.tsrx.js +89 -0
  140. package/dist/web/FieldGroup.web.tsrx.js +57 -0
  141. package/dist/web/FormField.web.tsrx.js +92 -0
  142. package/dist/web/Grid.web.tsrx.js +65 -0
  143. package/dist/web/{ui54.js → Heading.web.tsrx.js} +2 -2
  144. package/dist/web/{ui53.js → Icon.web.tsrx.js} +2 -2
  145. package/dist/web/{ui25.js → Image.web.tsrx.js} +2 -2
  146. package/dist/web/InputNumber.web.tsrx.js +99 -0
  147. package/dist/web/InputRating.web.tsrx.js +72 -0
  148. package/dist/web/InputTags.web.tsrx.js +122 -0
  149. package/dist/web/Kbd.web.tsrx.js +37 -0
  150. package/dist/web/{ui50.js → Meter.web.tsrx.js} +1 -1
  151. package/dist/web/{ui19.js → NavLink.web.tsrx.js} +1 -1
  152. package/dist/web/NavigationMenu.web.tsrx.js +118 -0
  153. package/dist/web/{ui31.js → Overlay.web.tsrx.js} +2 -2
  154. package/dist/web/Pager.web.tsrx.js +337 -0
  155. package/dist/web/Pagination.web.tsrx.js +80 -0
  156. package/dist/web/PinInput.web.tsrx.js +72 -0
  157. package/dist/web/{ui33.js → Popover.web.tsrx.js} +3 -3
  158. package/dist/web/{ui15.js → Pressable.web.tsrx.js} +68 -95
  159. package/dist/web/ProgressGroup.web.tsrx.js +109 -0
  160. package/dist/web/RadioGroup.web.tsrx.js +69 -0
  161. package/dist/web/{ui14.js → RichText.web.tsrx.js} +2 -2
  162. package/dist/web/Row.web.tsrx.js +250 -0
  163. package/dist/web/{ui43.js → SafeArea.web.tsrx.js} +1 -1
  164. package/dist/web/{ui39.js → Screen.web.tsrx.js} +1 -1
  165. package/dist/web/{ui24.js → ScrollBox.web.tsrx.js} +1 -1
  166. package/dist/web/ScrollView.web.tsrx.js +388 -0
  167. package/dist/web/SearchInput.web.tsrx.js +137 -0
  168. package/dist/web/SegmentedControl.web.tsrx.js +75 -0
  169. package/dist/web/Select.web.tsrx.js +149 -0
  170. package/dist/web/Separator.web.tsrx.js +42 -0
  171. package/dist/web/Sheet.web.tsrx.js +81 -0
  172. package/dist/web/Skeleton.web.tsrx.js +56 -0
  173. package/dist/web/Slider.web.tsrx.js +97 -0
  174. package/dist/web/{ui12.js → Spacer.web.tsrx.js} +1 -1
  175. package/dist/web/Stack.web.tsrx.js +47 -0
  176. package/dist/web/Stepper.web.tsrx.js +65 -0
  177. package/dist/web/Switch.web.tsrx.js +59 -0
  178. package/dist/web/Table.web.tsrx.js +161 -0
  179. package/dist/web/{ui40.js → Tabs.web.tsrx.js} +33 -16
  180. package/dist/web/{ui13.js → Text.web.tsrx.js} +18 -50
  181. package/dist/web/{ui21.js → TextArea.web.tsrx.js} +4 -4
  182. package/dist/web/{ui20.js → TextInput.web.tsrx.js} +8 -8
  183. package/dist/web/Timeline.web.tsrx.js +172 -0
  184. package/dist/web/Tree.web.tsrx.js +146 -0
  185. package/dist/web/User.web.tsrx.js +116 -0
  186. package/dist/web/Video.web.tsrx.js +400 -0
  187. package/dist/web/View.web.tsrx.js +250 -0
  188. package/dist/web/WebView.web.tsrx.js +235 -0
  189. package/dist/web/aliases.web.tsrx.js +24 -0
  190. package/dist/web/{ui36.js → anim.web.tsrx.js} +3 -35
  191. package/dist/web/child-array.js +15 -0
  192. package/dist/web/icons.js +25 -0
  193. package/dist/web/octane-client-build.json +1 -1
  194. package/dist/web/pan.web.tsrx.js +66 -0
  195. package/dist/web/platform.web.js +9 -0
  196. package/dist/web/refresh-metrics.js +9 -0
  197. package/dist/web/refresh.web.js +133 -0
  198. package/dist/web/{ui18.js → route.web.js} +14 -2
  199. package/dist/web/sheet-detents.web.js +103 -0
  200. package/dist/web/{ui60.js → sheet-service.web.js} +5 -1
  201. package/dist/web/sheet-snap.js +35 -0
  202. package/dist/{native/ui31.js → web/theme/theme-scheme.js} +2 -2
  203. package/dist/web/{ui35.js → toast-anchor.web.tsrx.js} +3 -3
  204. package/dist/web/transform.web.js +43 -0
  205. package/dist/web/translate.web.js +13 -0
  206. package/dist/web/ui.js +91 -53
  207. package/dist/web/use-back.web.tsrx.js +9 -0
  208. package/dist/web/video-chrome.web.tsrx.js +207 -0
  209. package/dist/web/{ui34.js → web/Hoverable.web.tsrx.js} +6 -6
  210. package/dist/web/web/Tooltip.web.tsrx.js +183 -0
  211. package/dist/web/web/index.js +3 -0
  212. package/package.json +30 -3
  213. package/src/theme/tokens.css +1380 -200
  214. package/types/android.d.ts +53 -0
  215. package/types/index.d.ts +32 -17
  216. package/types/index.native.d.ts +273 -0
  217. package/types/index.web.d.ts +272 -0
  218. package/types/ios.d.ts +59 -0
  219. package/types/native.d.ts +5 -0
  220. package/types/props.d.ts +930 -40
  221. package/types/web.d.ts +17 -0
  222. package/dist/native/ui10.js +0 -32
  223. package/dist/native/ui25.js +0 -141
  224. package/dist/native/ui27.js +0 -61
  225. package/dist/native/ui39.js +0 -103
  226. package/dist/native/ui4.js +0 -24
  227. package/dist/native/ui46.js +0 -267
  228. package/dist/native/ui57.js +0 -19
  229. package/dist/native/ui60.js +0 -14
  230. package/dist/native/ui7.js +0 -52
  231. package/dist/web/ui10.js +0 -46
  232. package/dist/web/ui11.js +0 -42
  233. package/dist/web/ui22.js +0 -108
  234. package/dist/web/ui23.js +0 -96
  235. package/dist/web/ui26.js +0 -45
  236. package/dist/web/ui3.js +0 -31
  237. package/dist/web/ui30.js +0 -34
  238. package/dist/web/ui41.js +0 -43
  239. package/dist/web/ui44.js +0 -42
  240. package/dist/web/ui5.js +0 -168
  241. package/dist/web/ui51.js +0 -47
  242. package/dist/web/ui52.js +0 -19
  243. package/dist/web/ui55.js +0 -17
  244. package/dist/web/ui59.js +0 -44
  245. package/dist/web/ui6.js +0 -94
  246. package/dist/web/ui7.js +0 -62
  247. package/dist/web/ui8.js +0 -46
  248. package/dist/web/ui9.js +0 -46
  249. /package/dist/native/{ui20.js → RouteHost.native.tsrx.js} +0 -0
  250. /package/dist/native/{ui41.js → anim.native.tsrx.js} +0 -0
  251. /package/dist/native/{ui2.js → cx.js} +0 -0
  252. /package/dist/native/{ui3.js → escape-props.native.js} +0 -0
  253. /package/dist/native/{ui5.js → layout-child.native.js} +0 -0
  254. /package/dist/native/{ui54.js → meter-math.js} +0 -0
  255. /package/dist/native/{ui36.js → platformProps.native.js} +0 -0
  256. /package/dist/native/{ui37.js → popover-position.js} +0 -0
  257. /package/dist/native/{ui19.js → route-table.js} +0 -0
  258. /package/dist/native/{ui48.js → safeAreaInsets.native.tsrx.js} +0 -0
  259. /package/dist/native/{ui18.js → stacks.native.js} +0 -0
  260. /package/dist/native/{ui61.js → store.js} +0 -0
  261. /package/dist/native/{ui43.js → styled.native.tsrx.js} +0 -0
  262. /package/dist/native/{ui28.js → svg.native.js} +0 -0
  263. /package/dist/native/{ui30.js → theme/colorScheme.native.js} +0 -0
  264. /package/dist/native/{ui62.js → use-store.native.tsrx.js} +0 -0
  265. /package/dist/native/{ui44.js → windows.native.js} +0 -0
  266. /package/dist/web/{ui16.js → Link.web.tsrx.js} +0 -0
  267. /package/dist/web/{ui28.js → cx.js} +0 -0
  268. /package/dist/web/{ui2.js → escape-props.web.js} +0 -0
  269. /package/dist/web/{ui4.js → layout-child.web.js} +0 -0
  270. /package/dist/web/{ui49.js → meter-math.js} +0 -0
  271. /package/dist/web/{ui47.js → platformProps.web.js} +0 -0
  272. /package/dist/web/{ui32.js → popover-position.js} +0 -0
  273. /package/dist/web/{ui17.js → route-table.js} +0 -0
  274. /package/dist/web/{ui42.js → safeAreaInsets.web.tsrx.js} +0 -0
  275. /package/dist/web/{ui56.js → stacks.web.js} +0 -0
  276. /package/dist/web/{ui57.js → store.js} +0 -0
  277. /package/dist/web/{ui37.js → styled.web.tsrx.js} +0 -0
  278. /package/dist/web/{ui27.js → theme/colorScheme.web.js} +0 -0
  279. /package/dist/web/{ui58.js → use-store.web.tsrx.js} +0 -0
  280. /package/dist/web/{ui46.js → useMeasure.web.tsrx.js} +0 -0
  281. /package/dist/web/{ui38.js → windows.web.js} +0 -0
@@ -70,15 +70,26 @@ img {
70
70
  display: flex;
71
71
  flex-direction: column;
72
72
  }
73
- /* Native has no scroll chaining — a ScrollView/ListView just bounces
74
- inside itself because the parent is never a scroller. contain is the
75
- DOM equivalent: scroll stays in the leaf it started in. */
76
- .vx-scroll,
77
- .vx-list {
73
+ /* Native has no scroll chaining — a ScrollView just bounces inside
74
+ itself because the parent is never a scroller. contain is the DOM
75
+ equivalent: scroll stays in the leaf it started in. */
76
+ .vx-scroll {
78
77
  overscroll-behavior: contain;
79
78
  }
80
79
  /* xplat-web-only:end */
81
80
 
81
+ /* Shared normalization — NS FlexboxLayout defaults flex children to
82
+ shrink:1 with NO min-content floor (its shrink pass clamps at
83
+ effectiveMinWidth/Height = the explicit min-* prop, default 0), so an
84
+ overflowing column crushes intrinsic children toward zero where the
85
+ web stops at min-content. RN's Yoga defaults flexShrink:0 for the same
86
+ reason — do the same on both engines. `*` is zero-specificity: flex-1
87
+ (the shorthand sets shrink:1) and .shrink still opt back in, and
88
+ truncation patterns pair them with min-w-0/min-h-0. */
89
+ * {
90
+ flex-shrink: 0;
91
+ }
92
+
82
93
  /* vocabulary base styles */
83
94
 
84
95
  .vx-view,
@@ -109,8 +120,17 @@ img {
109
120
  display: flex;
110
121
  flex-direction: column;
111
122
  }
112
- /* xplat-web-only:start — the native Drawer leaf is the ui-drawer widget;
113
- these rules style the web DOM drawer overlay */
123
+ /* Self-drawn drawer — shared geometry. Native grid children overlay the
124
+ cell (panel pins left via horizontalAlignment in the leaf); DOM
125
+ positioning extras are web-only below. */
126
+ .vx-drawer-backdrop {
127
+ background-color: rgba(0, 0, 0, 0.4);
128
+ }
129
+ .vx-drawer-panel {
130
+ width: 280px;
131
+ background-color: var(--color-surface);
132
+ }
133
+ /* xplat-web-only:start */
114
134
  .vx-drawer {
115
135
  display: flex;
116
136
  flex-direction: row;
@@ -125,7 +145,6 @@ img {
125
145
  .vx-drawer-backdrop {
126
146
  position: absolute;
127
147
  inset: 0;
128
- background: rgba(0, 0, 0, 0.4);
129
148
  }
130
149
  .vx-drawer-panel {
131
150
  position: absolute;
@@ -133,9 +152,7 @@ img {
133
152
  top: 0;
134
153
  bottom: 0;
135
154
  left: 0;
136
- width: 280px;
137
155
  overflow: auto;
138
- background: var(--color-surface);
139
156
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
140
157
  }
141
158
  /* xplat-web-only:end */
@@ -167,189 +184,1327 @@ img {
167
184
  .vx-spacer {
168
185
  flex-grow: 1;
169
186
  }
170
-
171
- .vx-pressable {
172
- display: flex;
187
+
188
+ .vx-pressable {
189
+ display: flex;
190
+ flex-direction: column;
191
+ user-select: none;
192
+ }
193
+
194
+ /* web press feedback */
195
+ .vx-pressable:active {
196
+ opacity: 0.85;
197
+ }
198
+ /* native press feedback (NS pseudo — dead rule on web, and vice versa) */
199
+ .vx-pressable:pressed {
200
+ opacity: 0.85;
201
+ }
202
+
203
+ /* xplat-web-only:start — overlays/toasts/popovers render through RootLayout
204
+ or showModal on native; these portal/positioning rules are web-only */
205
+ .vx-overlay {
206
+ z-index: 1000;
207
+ }
208
+
209
+ .vx-overlay-shade {
210
+ position: absolute;
211
+ inset: 0;
212
+ background-color: rgba(0, 0, 0, 0.4);
213
+ }
214
+
215
+ .vx-overlay-content {
216
+ position: relative;
217
+ z-index: 1;
218
+ }
219
+
220
+ .vx-popover {
221
+ z-index: 1001;
222
+ }
223
+
224
+ .vx-popover-backdrop {
225
+ background-color: transparent;
226
+ }
227
+
228
+ .vx-tooltip {
229
+ padding: 4px 8px;
230
+ border-width: 1px;
231
+ border-color: var(--color-border);
232
+ border-radius: var(--radius-sm);
233
+ corner-shape: squircle;
234
+ background-color: var(--color-surface);
235
+ color: var(--color-text);
236
+ font-size: 12px;
237
+ line-height: 16px;
238
+ }
239
+
240
+ .vx-toast-host {
241
+ position: fixed;
242
+ z-index: 1002;
243
+ left: 0;
244
+ right: 0;
245
+ display: flex;
246
+ justify-content: center;
247
+ pointer-events: none;
248
+ }
249
+
250
+ .vx-toast-top {
251
+ top: 24px;
252
+ }
253
+ .vx-toast-bottom {
254
+ bottom: 24px;
255
+ }
256
+
257
+ .vx-toast {
258
+ pointer-events: auto;
259
+ }
260
+ /* xplat-web-only:end */
261
+
262
+ /* Normalized OS-widget reset — TextInput/TextArea are real
263
+ <input>/<textarea>/TextField/TextView; the leaves emit vx-input/
264
+ vx-textarea unconditionally so the platform chrome is stripped before
265
+ app styles apply. On Android the transparent background replaces the
266
+ default EditText drawable (kills the Material underline); zero padding
267
+ removes native insets. Legitimate semantics — IME, secure entry,
268
+ selection, autofill, caret, focus — stay platform-native by design. */
269
+ .vx-input,
270
+ .vx-textarea {
271
+ padding: 0;
272
+ margin: 0;
273
+ border-width: 0;
274
+ background-color: transparent;
275
+ color: var(--color-text);
276
+ font-size: 14px;
277
+ /* NS placeholder color; web uses the ::placeholder pseudo below. */
278
+ placeholder-color: var(--color-text-secondary);
279
+ }
280
+ /* xplat-web-only:start — DOM-only reset: element UA chrome and the
281
+ focus outline (normalized to the same thin ring on both). */
282
+ .vx-input,
283
+ .vx-textarea {
284
+ appearance: none;
285
+ -webkit-appearance: none;
286
+ outline: none;
287
+ }
288
+ .vx-input:focus,
289
+ .vx-textarea:focus {
290
+ outline: 1px solid var(--color-primary);
291
+ outline-offset: -1px;
292
+ }
293
+ /* xplat-web-only:end */
294
+
295
+ /* auto-grow owns height — the manual resize handle has no native analog */
296
+ /* xplat-web-only:start */
297
+ .vx-textarea {
298
+ resize: none;
299
+ }
300
+ /* xplat-web-only:end */
301
+
302
+ /* WebView chrome reset — the web leaf is a real <iframe>: strip the UA's
303
+ inset border and the inline-element baseline gap. WKWebView and
304
+ android.webkit.WebView ship chrome-free, so there is no native reset. */
305
+ /* xplat-web-only:start */
306
+ .vx-webview {
307
+ display: block;
308
+ border-width: 0;
309
+ }
310
+ /* xplat-web-only:end */
311
+
312
+ /* Video — surface-hosted leaf: the engine surface fills the frame and the
313
+ self-drawn transport layers stack on top in the same grid cell
314
+ (vx-stack). NS-side rules only — DOM resets sit in the web block. */
315
+ .vx-video {
316
+ background-color: #000;
317
+ }
318
+
319
+ .vx-video-centerbtn {
320
+ width: 56px;
321
+ height: 56px;
322
+ border-radius: 28px;
323
+ background-color: rgba(0, 0, 0, 0.55);
324
+ align-items: center;
325
+ justify-content: center;
326
+ }
327
+
328
+ .vx-video-bar {
329
+ background-color: rgba(0, 0, 0, 0.45);
330
+ padding: 4px 8px;
331
+ }
332
+
333
+ .vx-video-btn {
334
+ padding: 4px;
335
+ }
336
+
337
+ /* Full-frame tap layer — hit target only, never paints. */
338
+ .vx-video-tap {
339
+ background-color: transparent;
340
+ }
341
+
342
+ .vx-video-time {
343
+ color: #fff;
344
+ font-size: 11px;
345
+ min-width: 34px;
346
+ text-align: center;
347
+ }
348
+
349
+ /* xplat-web-only:start — the <video> element is inline and overlays would
350
+ otherwise swallow pointer events; native layers are hit-transparent by
351
+ default, so none of this exists on NS. */
352
+ .vx-video-surface {
353
+ display: block;
354
+ width: 100%;
355
+ height: 100%;
356
+ }
357
+
358
+ .vx-video-poster,
359
+ .vx-video-barwrap {
360
+ pointer-events: none;
361
+ }
362
+
363
+ .vx-video-barwrap > * {
364
+ pointer-events: auto;
365
+ }
366
+
367
+ .vx-video-poster {
368
+ object-fit: cover;
369
+ }
370
+
371
+ /* xplat-web-only:end */
372
+
373
+ /* CameraView — surface-hosted: the preview fills the frame; web shows it
374
+ through an object-fit:cover <video>. */
375
+ .vx-cameraview {
376
+ background-color: #000;
377
+ }
378
+
379
+ /* xplat-web-only:start */
380
+ .vx-cameraview-surface {
381
+ display: block;
382
+ width: 100%;
383
+ height: 100%;
384
+ object-fit: cover;
385
+ }
386
+ /* xplat-web-only:end */
387
+
388
+ /* xplat-web-only:start */
389
+ /* Modal ROUTES (pushRoute presentation:'modal') — the URL stays underneath,
390
+ so the overlay is a fixed layer over the shell rather than a dialog. */
391
+ .vx-modalroute {
392
+ position: fixed;
393
+ inset: 0;
394
+ z-index: 1000;
395
+ display: flex;
396
+ align-items: stretch;
397
+ justify-content: stretch;
398
+ }
399
+
400
+ .vx-modalroute-shade {
401
+ position: absolute;
402
+ inset: 0;
403
+ border: 0;
404
+ padding: 0;
405
+ background: rgba(0, 0, 0, 0.4);
406
+ cursor: default;
407
+ }
408
+
409
+ .vx-modalroute-body {
410
+ position: relative;
411
+ flex: 1;
412
+ min-width: 0;
413
+ overflow: auto;
414
+ color: var(--color-text);
415
+ background-color: var(--color-surface);
416
+ }
417
+ /* xplat-web-only:end */
418
+
419
+ /* xplat-web-only:start — placeholder pseudos are DOM-only. Muted like
420
+ real placeholder text, not currentColor (a full-opacity hint reads as
421
+ a typed value); --placeholder-color is the per-field prop escape. */
422
+ input::placeholder,
423
+ textarea::placeholder {
424
+ color: var(--placeholder-color, var(--color-text-secondary));
425
+ }
426
+ /* xplat-web-only:end */
427
+
428
+ /* xplat-web-only:start — form controls don't inherit the body font in the
429
+ DOM (UA stylesheet wins); without this they render in the UA face. */
430
+ input,
431
+ textarea,
432
+ select,
433
+ button {
434
+ font-family: inherit;
435
+ }
436
+ /* xplat-web-only:end */
437
+
438
+ /* Self-drawn spinner — the border ring is shared (native rotates the
439
+ view via `animate`; DOM spins via the keyframes below). */
440
+ .vx-spinner {
441
+ width: 24px;
442
+ height: 24px;
443
+ border-width: 2px;
444
+ border-color: var(--color-text-secondary);
445
+ border-right-color: transparent;
446
+ border-radius: 50%;
447
+ }
448
+ /* Pull-to-refresh — the overlay strip hosting the spinner. Its height is
449
+ the default `refreshThreshold` (64px web / 64dip native); the leaf
450
+ translates it in/out — reveal math lives in refresh.*.ts. */
451
+ .vx-refresh {
452
+ display: flex;
453
+ flex-direction: column;
454
+ align-items: center;
455
+ justify-content: center;
456
+ height: 64px;
457
+ }
458
+ /* xplat-web-only:start — the DOM leaf needs explicit clipping plus the
459
+ absolute overlay; native clips via the wrapper grid
460
+ (clipsToBounds/ViewGroup) and stacks children in one cell. */
461
+ .vx-refresh-wrap {
462
+ position: relative;
463
+ overflow: hidden;
464
+ display: flex;
465
+ flex-direction: column;
466
+ }
467
+ .vx-refresh-wrap > .vx-scroll {
468
+ flex: 1;
469
+ min-height: 0;
470
+ }
471
+ .vx-refresh {
472
+ position: absolute;
473
+ top: 0;
474
+ left: 0;
475
+ right: 0;
476
+ }
477
+ /* xplat-web-only:end */
478
+ /* xplat-web-only:start — DOM needs an inline-block box + CSS animation;
479
+ the native leaf drives view.animate instead */
480
+ .vx-spinner {
481
+ display: inline-block;
482
+ box-sizing: border-box;
483
+ animation: vx-spinner-turn 0.8s linear infinite;
484
+ }
485
+
486
+ @keyframes vx-spinner-turn {
487
+ to {
488
+ transform: rotate(360deg);
489
+ }
490
+ }
491
+ /* xplat-web-only:end */
492
+
493
+ /* Self-drawn switch — flex track + thumb. `--on` flips justify-content,
494
+ which slides the thumb; identical geometry on web and native. */
495
+ .vx-switch {
496
+ width: 48px;
497
+ height: 28px;
498
+ padding-left: 3px;
499
+ padding-right: 3px;
500
+ border-radius: 14px;
501
+ background-color: var(--color-border);
502
+ justify-content: flex-start;
503
+ align-items: center;
504
+ }
505
+ .vx-switch--on {
506
+ background-color: var(--color-primary);
507
+ justify-content: flex-end;
508
+ }
509
+ .vx-switch-thumb {
510
+ width: 22px;
511
+ height: 22px;
512
+ border-radius: 50%;
513
+ background-color: var(--color-surface);
514
+ }
515
+ /* xplat-web-only:start — the DOM host is a div; flex + cursor live here */
516
+ .vx-switch {
517
+ display: flex;
518
+ cursor: pointer;
519
+ }
520
+ /* xplat-web-only:end */
521
+
522
+ /* Self-drawn slider — host is a grid (native) / relative div (web);
523
+ track + fill + thumb overlay the same box. */
524
+ .vx-slider {
525
+ height: 28px;
526
+ }
527
+ .vx-slider-track {
528
+ height: 4px;
529
+ border-radius: 2px;
530
+ background-color: var(--color-border);
531
+ vertical-align: middle;
532
+ }
533
+ .vx-slider-fill {
534
+ height: 4px;
535
+ border-radius: 2px;
536
+ background-color: var(--color-primary);
537
+ vertical-align: middle;
538
+ }
539
+ .vx-slider-thumb {
540
+ width: 20px;
541
+ height: 20px;
542
+ border-radius: 50%;
543
+ background-color: var(--color-primary);
544
+ vertical-align: middle;
545
+ }
546
+ /* xplat-web-only:start — absolute positioning is DOM-only; native grid
547
+ children center via vertical-align and pin via margin-left. */
548
+ .vx-slider {
549
+ position: relative;
550
+ }
551
+ .vx-slider-track,
552
+ .vx-slider-fill {
553
+ position: absolute;
554
+ top: 50%;
555
+ left: 0;
556
+ right: 0;
557
+ transform: translateY(-50%);
558
+ }
559
+ .vx-slider-thumb {
560
+ position: absolute;
561
+ top: 50%;
562
+ transform: translate(-50%, -50%);
563
+ }
564
+ /* xplat-web-only:end */
565
+
566
+ /* xplat-web-only:start — Pager's web leaf is a mandatory scroll-snap row:
567
+ one page per item at 100% host width so scrollLeft / clientWidth is a
568
+ clean page index. Native pages are sized by the widget itself, so the
569
+ whole block is DOM-only. Scrollbar hidden (native has none); contain
570
+ keeps the swipe from chaining to a parent scroller, matching .vx-scroll. */
571
+ .vx-pager {
572
+ display: flex;
573
+ flex-direction: row;
574
+ overflow-x: auto;
575
+ overflow-y: hidden;
576
+ scroll-snap-type: x mandatory;
577
+ overscroll-behavior-x: contain;
578
+ scrollbar-width: none;
579
+ touch-action: pan-x;
580
+ }
581
+ .vx-pager::-webkit-scrollbar {
582
+ display: none;
583
+ }
584
+ .vx-pager-page {
585
+ flex: 0 0 100%;
586
+ min-width: 0;
587
+ display: flex;
588
+ flex-direction: column;
589
+ scroll-snap-align: start;
590
+ }
591
+ /* xplat-web-only:end */
592
+
593
+ /* Phase-1 self-drawn components — functional geometry + token chrome only;
594
+ visual design is the app's job. */
595
+
596
+ /* Button — row-direction pressable (vx-pressable already supplies display
597
+ and press feedback). */
598
+ .vx-button {
599
+ flex-direction: row;
600
+ align-items: center;
601
+ justify-content: center;
602
+ }
603
+
604
+ /* Collapsible/Accordion — column stacks. */
605
+ .vx-collapsible,
606
+ .vx-collapsible-content,
607
+ .vx-accordion,
608
+ .vx-accordion-item {
609
+ flex-direction: column;
610
+ }
611
+
612
+ /* Checkbox — 20px box + check glyph; --on fills with primary. */
613
+ .vx-checkbox-row {
614
+ flex-direction: row;
615
+ align-items: center;
616
+ }
617
+ .vx-checkbox {
618
+ width: 20px;
619
+ height: 20px;
620
+ border-width: 1px;
621
+ border-color: var(--color-border);
622
+ border-radius: var(--radius-sm);
623
+ align-items: center;
624
+ justify-content: center;
625
+ }
626
+ .vx-checkbox--on .vx-checkbox {
627
+ background-color: var(--color-primary);
628
+ border-color: var(--color-primary);
629
+ }
630
+ .vx-checkbox-check {
631
+ font-size: 14px;
632
+ line-height: 20px;
633
+ color: var(--color-onprimary);
634
+ }
635
+ .vx-checkbox-label {
636
+ margin-left: 8px;
637
+ }
638
+ .vx-checkbox--disabled {
639
+ opacity: 0.5;
640
+ }
641
+
642
+ /* Radio group — column/row of dot options. */
643
+ .vx-radio-group {
644
+ flex-direction: column;
645
+ }
646
+ .vx-radio-group--row {
647
+ flex-direction: row;
648
+ }
649
+ .vx-radio-row {
650
+ flex-direction: row;
651
+ align-items: center;
652
+ }
653
+ .vx-radio {
654
+ width: 18px;
655
+ height: 18px;
656
+ border-width: 1px;
657
+ border-color: var(--color-border);
658
+ border-radius: 50%;
659
+ align-items: center;
660
+ justify-content: center;
661
+ }
662
+ .vx-radio--on .vx-radio {
663
+ border-color: var(--color-primary);
664
+ }
665
+ .vx-radio-dot {
666
+ width: 8px;
667
+ height: 8px;
668
+ border-radius: 50%;
669
+ background-color: var(--color-primary);
670
+ }
671
+ .vx-radio-label {
672
+ margin-left: 8px;
673
+ }
674
+ .vx-radio--disabled {
675
+ opacity: 0.5;
676
+ }
677
+
678
+ /* SegmentedControl — equal-width segments in a rounded track; `--on`
679
+ raises the selected one. flex-basis is dead on NS (the shorthand parser
680
+ drops it and the standalone property isn't registered), so width:0 +
681
+ flex-grow:1 is what distributes segments equally on both engines. */
682
+ .vx-segmented {
683
+ flex-direction: row;
684
+ padding: 3px;
685
+ border-width: 1px;
686
+ border-color: var(--color-border);
687
+ border-radius: var(--radius-md);
688
+ corner-shape: squircle;
689
+ background-color: var(--color-surface-secondary);
690
+ color: var(--color-text);
691
+ }
692
+ .vx-segment {
693
+ flex-grow: 1;
694
+ width: 0;
695
+ flex-direction: row;
696
+ align-items: center;
697
+ justify-content: center;
698
+ padding-top: 6px;
699
+ padding-bottom: 6px;
700
+ padding-left: 10px;
701
+ padding-right: 10px;
702
+ border-radius: var(--radius-sm);
703
+ }
704
+ .vx-segment--on {
705
+ background-color: var(--color-surface);
706
+ }
707
+ .vx-segment-label {
708
+ font-size: 13px;
709
+ color: var(--color-text-secondary);
710
+ }
711
+ .vx-segment--on .vx-segment-label {
712
+ color: var(--color-text);
713
+ font-weight: 600;
714
+ }
715
+ .vx-segmented--disabled,
716
+ .vx-segment--disabled {
717
+ opacity: 0.5;
718
+ }
719
+ /* xplat-web-only:start — the DOM hosts are divs; flex + cursor live here */
720
+ .vx-segmented {
721
+ display: flex;
722
+ }
723
+ .vx-segment {
724
+ display: flex;
725
+ cursor: pointer;
726
+ user-select: none;
727
+ }
728
+ .vx-segment:focus {
729
+ outline: 1px solid var(--color-primary);
730
+ outline-offset: -1px;
731
+ }
732
+ .vx-segment--disabled {
733
+ cursor: default;
734
+ }
735
+ /* xplat-web-only:end */
736
+
737
+ /* SearchInput — chrome-reset field: bordered shell, leading glyph, the
738
+ field owns the remaining width, self-drawn clear. The web leaf hides the
739
+ engine's own search-cancel button so native's (hand-drawn) matches. */
740
+ .vx-search {
741
+ flex-direction: row;
742
+ align-items: center;
743
+ padding-top: 6px;
744
+ padding-bottom: 6px;
745
+ padding-left: 10px;
746
+ padding-right: 6px;
747
+ border-width: 1px;
748
+ border-color: var(--color-border);
749
+ border-radius: var(--radius-md);
750
+ corner-shape: squircle;
751
+ background-color: var(--color-surface);
752
+ color: var(--color-text);
753
+ }
754
+ .vx-search-icon {
755
+ color: var(--color-text-secondary);
756
+ }
757
+ .vx-search-field {
758
+ flex-grow: 1;
759
+ padding-left: 8px;
760
+ padding-right: 8px;
761
+ font-size: 14px;
762
+ color: var(--color-text);
763
+ }
764
+ .vx-search-clear {
765
+ width: 24px;
766
+ height: 24px;
767
+ border-radius: 9999px;
768
+ align-items: center;
769
+ justify-content: center;
770
+ background-color: var(--color-surface-secondary);
771
+ color: var(--color-text-secondary);
772
+ }
773
+ .vx-search--disabled {
774
+ opacity: 0.5;
775
+ }
776
+ /* xplat-web-only:start — the DOM host/field/buttons need display + the UA
777
+ reset; native's flexbox children carry these from the class already. */
778
+ .vx-search {
779
+ display: flex;
780
+ }
781
+ .vx-search-icon {
782
+ display: inline-flex;
783
+ flex: none;
784
+ }
785
+ .vx-search-field {
786
+ min-width: 0;
787
+ border: 0;
788
+ background: none;
789
+ outline: none;
790
+ appearance: none;
791
+ -webkit-appearance: none;
792
+ }
793
+ input.vx-search-field::-webkit-search-cancel-button {
794
+ display: none;
795
+ }
796
+ .vx-search-clear {
797
+ display: inline-flex;
798
+ border: 0;
799
+ padding: 0;
800
+ cursor: pointer;
801
+ }
802
+ /* xplat-web-only:end */
803
+
804
+ /* Anchored menus — panel + item rows (the Popover leaf positions it). */
805
+ .vx-menu {
806
+ min-width: 140px;
807
+ padding: 4px;
808
+ border-width: 1px;
809
+ border-color: var(--color-border);
810
+ border-radius: var(--radius-md);
811
+ corner-shape: squircle;
812
+ background-color: var(--color-surface);
813
+ color: var(--color-text);
814
+ }
815
+ .vx-menu-list {
816
+ flex-direction: column;
817
+ }
818
+ .vx-menu-item {
819
+ flex-direction: row;
820
+ align-items: center;
821
+ padding: 6px 10px;
822
+ border-radius: var(--radius-sm);
823
+ }
824
+ .vx-menu-item--disabled {
825
+ opacity: 0.5;
826
+ }
827
+ /* xplat-web-only:start — hover affordance and cursor are DOM-only */
828
+ .vx-menu-item {
829
+ cursor: pointer;
830
+ }
831
+ .vx-menu-item:hover {
832
+ background-color: var(--color-border);
833
+ }
834
+ /* xplat-web-only:end */
835
+
836
+ /* Badge — inline chip row. */
837
+ .vx-badge {
838
+ flex-direction: row;
839
+ align-items: center;
840
+ border-radius: var(--radius-sm);
841
+ padding: 1px 6px;
842
+ font-size: 12px;
843
+ line-height: 16px;
844
+ }
845
+ /* xplat-web-only:start — span is inline by default; needs inline-flex */
846
+ .vx-badge {
847
+ display: inline-flex;
848
+ }
849
+ /* xplat-web-only:end */
850
+
851
+ /* Separator — hairline rule; --h spans width, --v spans height. */
852
+ .vx-separator--h {
853
+ height: 1px;
854
+ background-color: var(--color-border);
855
+ }
856
+ .vx-separator--v {
857
+ width: 1px;
858
+ align-self: stretch;
859
+ background-color: var(--color-border);
860
+ }
861
+
862
+ /* Skeleton — placeholder block. */
863
+ .vx-skeleton {
864
+ background-color: var(--color-border);
865
+ border-radius: var(--radius-md);
866
+ corner-shape: squircle;
867
+ }
868
+
869
+ /* Avatar — centered fallback inside the circle. */
870
+ .vx-avatar {
871
+ align-items: center;
872
+ justify-content: center;
873
+ background-color: var(--color-border);
874
+ }
875
+ .vx-avatar-group {
876
+ flex-direction: row;
877
+ align-items: center;
878
+ }
879
+ /* xplat-web-only:start — display:flex for the div-based flex classes */
880
+ .vx-collapsible,
881
+ .vx-collapsible-content,
882
+ .vx-accordion,
883
+ .vx-accordion-item,
884
+ .vx-checkbox-row,
885
+ .vx-checkbox,
886
+ .vx-radio-group,
887
+ .vx-radio-row,
888
+ .vx-radio,
889
+ .vx-menu-list,
890
+ .vx-menu-item,
891
+ .vx-contextmenu,
892
+ .vx-avatar,
893
+ .vx-avatar-group,
894
+ .vx-avatar-group-item {
895
+ display: flex;
896
+ }
897
+ .vx-checkbox-row,
898
+ .vx-radio-row {
899
+ cursor: pointer;
900
+ }
901
+ .vx-checkbox-check {
902
+ display: block;
903
+ }
904
+ /* xplat-web-only:end */
905
+
906
+ /* Phase-2 form field layer. */
907
+
908
+ /* FormField — label / control / message column. */
909
+ .vx-field {
910
+ flex-direction: column;
911
+ }
912
+ .vx-field-label {
913
+ font-size: 13px;
914
+ line-height: 20px;
915
+ font-weight: 500;
916
+ margin-bottom: 4px;
917
+ }
918
+ .vx-field-required {
919
+ color: var(--color-danger);
920
+ }
921
+ .vx-field-msg {
922
+ font-size: 12px;
923
+ line-height: 16px;
924
+ margin-top: 4px;
925
+ color: var(--color-text-secondary);
926
+ }
927
+ .vx-field-msg--error {
928
+ color: var(--color-danger);
929
+ }
930
+ .vx-fieldgroup {
931
+ flex-direction: column;
932
+ }
933
+ .vx-fieldgroup-label {
934
+ font-size: 13px;
935
+ line-height: 20px;
936
+ font-weight: 600;
937
+ margin-bottom: 8px;
938
+ }
939
+
940
+ /* InputNumber — steppers flanking a centered field. */
941
+ .vx-inputnumber {
942
+ flex-direction: row;
943
+ align-items: center;
944
+ }
945
+ .vx-inputnumber-step {
946
+ padding: 4px 10px;
947
+ border-width: 1px;
948
+ border-color: var(--color-border);
949
+ border-radius: var(--radius-sm);
950
+ align-items: center;
951
+ justify-content: center;
952
+ }
953
+ .vx-inputnumber-field {
954
+ width: 64px;
955
+ margin-left: 8px;
956
+ margin-right: 8px;
957
+ text-align: center;
958
+ }
959
+ .vx-inputnumber--disabled {
960
+ opacity: 0.5;
961
+ }
962
+
963
+ /* PinInput — row of single cells. */
964
+ .vx-pininput {
965
+ flex-direction: row;
966
+ }
967
+ .vx-pininput-cell {
968
+ width: 40px;
969
+ margin-right: 8px;
970
+ text-align: center;
971
+ }
972
+ .vx-pininput--disabled {
973
+ opacity: 0.5;
974
+ }
975
+
976
+ /* Select — trigger row + check column inside the anchored listbox. */
977
+ .vx-select-trigger {
978
+ flex-direction: row;
979
+ align-items: center;
980
+ justify-content: space-between;
981
+ padding: 6px 10px;
982
+ border-width: 1px;
983
+ border-color: var(--color-border);
984
+ border-radius: var(--radius-sm);
985
+ }
986
+ .vx-select--disabled {
987
+ opacity: 0.5;
988
+ }
989
+ .vx-select-placeholder {
990
+ color: var(--color-text-secondary);
991
+ }
992
+ .vx-select-caret {
993
+ margin-left: 8px;
994
+ }
995
+ .vx-select-check {
996
+ width: 16px;
997
+ }
998
+ .vx-select-search {
999
+ margin-bottom: 4px;
1000
+ }
1001
+
1002
+ /* InputTags — wrapped chip row + trailing field. */
1003
+ .vx-inputtags {
1004
+ flex-direction: row;
1005
+ flex-wrap: wrap;
1006
+ align-items: center;
1007
+ }
1008
+ .vx-inputtags-tag {
1009
+ flex-direction: row;
1010
+ align-items: center;
1011
+ padding: 1px 6px;
1012
+ margin-right: 4px;
1013
+ margin-bottom: 4px;
1014
+ border-radius: var(--radius-sm);
1015
+ background-color: var(--color-border);
1016
+ }
1017
+ .vx-inputtags-remove {
1018
+ margin-left: 4px;
1019
+ }
1020
+ .vx-inputtags-field {
1021
+ flex: 1;
1022
+ min-width: 80px;
1023
+ }
1024
+ .vx-inputtags--disabled {
1025
+ opacity: 0.5;
1026
+ }
1027
+
1028
+ /* InputRating — glyph row; --on lights the cell. */
1029
+ .vx-rating {
1030
+ flex-direction: row;
1031
+ }
1032
+ .vx-rating-cell {
1033
+ padding: 2px;
1034
+ }
1035
+ .vx-rating-glyph {
1036
+ font-size: 20px;
1037
+ line-height: 24px;
1038
+ color: var(--color-border);
1039
+ }
1040
+ .vx-rating-cell--on .vx-rating-glyph {
1041
+ color: var(--color-primary);
1042
+ }
1043
+ .vx-rating--disabled {
1044
+ opacity: 0.5;
1045
+ }
1046
+
1047
+ /* CheckboxGroup — column/row of checkboxes. */
1048
+ .vx-checkbox-group {
1049
+ flex-direction: column;
1050
+ }
1051
+ .vx-checkbox-group--row {
1052
+ flex-direction: row;
1053
+ }
1054
+ /* xplat-web-only:start — display:flex for the div/span flex classes */
1055
+ .vx-field,
1056
+ .vx-fieldgroup,
1057
+ .vx-inputnumber,
1058
+ .vx-inputnumber-step,
1059
+ .vx-select-trigger,
1060
+ .vx-select-list,
1061
+ .vx-inputtags,
1062
+ .vx-inputtags-tag,
1063
+ .vx-rating,
1064
+ .vx-rating-cell,
1065
+ .vx-checkbox-group {
1066
+ display: flex;
1067
+ }
1068
+ .vx-rating-cell {
1069
+ cursor: pointer;
1070
+ }
1071
+ /* xplat-web-only:end */
1072
+
1073
+ /* Phase-3 navigation & wayfinding. */
1074
+
1075
+ /* Breadcrumb — inline trail. */
1076
+ .vx-breadcrumb {
1077
+ flex-direction: row;
1078
+ align-items: center;
1079
+ flex-wrap: wrap;
1080
+ }
1081
+ .vx-breadcrumb-item {
1082
+ flex-direction: row;
1083
+ align-items: center;
1084
+ }
1085
+ .vx-breadcrumb-sep {
1086
+ margin-left: 6px;
1087
+ margin-right: 6px;
1088
+ color: var(--color-text-secondary);
1089
+ }
1090
+ .vx-breadcrumb-link {
1091
+ color: var(--color-primary);
1092
+ }
1093
+
1094
+ /* Pagination — windowed page buttons. */
1095
+ .vx-pagination {
1096
+ flex-direction: row;
1097
+ align-items: center;
1098
+ }
1099
+ .vx-page-btn {
1100
+ min-width: 28px;
1101
+ height: 28px;
1102
+ padding: 0 6px;
1103
+ margin-right: 4px;
1104
+ border-width: 1px;
1105
+ border-color: var(--color-border);
1106
+ border-radius: var(--radius-sm);
1107
+ align-items: center;
1108
+ justify-content: center;
1109
+ }
1110
+ .vx-page-btn--on {
1111
+ background-color: var(--color-primary);
1112
+ border-color: var(--color-primary);
1113
+ color: var(--color-onprimary);
1114
+ }
1115
+ .vx-page-ellipsis {
1116
+ margin-right: 4px;
1117
+ color: var(--color-text-secondary);
1118
+ }
1119
+ .vx-pagination--disabled {
1120
+ opacity: 0.5;
1121
+ }
1122
+
1123
+ /* Stepper — dot + label cells joined by a hairline. */
1124
+ .vx-stepper {
1125
+ flex-direction: row;
1126
+ align-items: center;
1127
+ }
1128
+ .vx-stepper-cell {
1129
+ flex-direction: row;
1130
+ align-items: center;
1131
+ }
1132
+ .vx-stepper-line {
1133
+ width: 24px;
1134
+ height: 1px;
1135
+ background-color: var(--color-border);
1136
+ }
1137
+ .vx-stepper-line--done {
1138
+ background-color: var(--color-primary);
1139
+ }
1140
+ .vx-step {
1141
+ flex-direction: row;
1142
+ align-items: center;
1143
+ }
1144
+ .vx-step-dot {
1145
+ width: 24px;
1146
+ height: 24px;
1147
+ border-width: 1px;
1148
+ border-color: var(--color-border);
1149
+ border-radius: 50%;
1150
+ text-align: center;
1151
+ font-size: 12px;
1152
+ line-height: 22px;
1153
+ color: var(--color-text-secondary);
1154
+ }
1155
+ .vx-step--on .vx-step-dot {
1156
+ border-color: var(--color-primary);
1157
+ color: var(--color-primary);
1158
+ }
1159
+ .vx-step--done .vx-step-dot {
1160
+ background-color: var(--color-primary);
1161
+ border-color: var(--color-primary);
1162
+ color: var(--color-onprimary);
1163
+ }
1164
+ .vx-step-label {
1165
+ margin-left: 6px;
1166
+ }
1167
+ .vx-step--disabled {
1168
+ opacity: 0.5;
1169
+ }
1170
+
1171
+ /* NavigationMenu — row/column of nav items. */
1172
+ .vx-navmenu,
1173
+ .vx-navmenu--row {
1174
+ flex-direction: row;
1175
+ align-items: center;
1176
+ }
1177
+ .vx-navmenu--col {
1178
+ flex-direction: column;
1179
+ align-items: flex-start;
1180
+ }
1181
+ .vx-navmenu-item {
1182
+ flex-direction: row;
1183
+ align-items: center;
1184
+ padding: 6px 10px;
1185
+ border-radius: var(--radius-sm);
1186
+ }
1187
+ .vx-navmenu-item--on {
1188
+ background-color: var(--color-border);
1189
+ }
1190
+ .vx-navmenu-item--disabled {
1191
+ opacity: 0.5;
1192
+ }
1193
+ .vx-navmenu-label {
1194
+ margin-left: 4px;
1195
+ }
1196
+
1197
+ /* CommandPalette — panel centered by the overlay layer. */
1198
+ .vx-commandpalette {
1199
+ width: 360px;
1200
+ max-width: 90%;
1201
+ padding: 8px;
1202
+ border-width: 1px;
1203
+ border-color: var(--color-border);
1204
+ border-radius: var(--radius-md);
1205
+ corner-shape: squircle;
1206
+ background-color: var(--color-surface);
1207
+ color: var(--color-text);
1208
+ }
1209
+ .vx-cmdk-search {
1210
+ margin-bottom: 4px;
1211
+ }
1212
+ /* xplat-web-only:start — display, centering, and cursor are DOM-only */
1213
+ .vx-breadcrumb,
1214
+ .vx-breadcrumb-item,
1215
+ .vx-pagination,
1216
+ .vx-page-btn,
1217
+ .vx-stepper,
1218
+ .vx-stepper-cell,
1219
+ .vx-step,
1220
+ .vx-navmenu,
1221
+ .vx-navmenu-item {
1222
+ display: flex;
1223
+ }
1224
+ .vx-cmdk-layer .vx-overlay-content {
1225
+ position: absolute;
1226
+ top: 15%;
1227
+ left: 50%;
1228
+ transform: translateX(-50%);
1229
+ }
1230
+ .vx-page-btn,
1231
+ .vx-breadcrumb-link,
1232
+ .vx-step,
1233
+ .vx-navmenu-item {
1234
+ cursor: pointer;
1235
+ }
1236
+ /* xplat-web-only:end */
1237
+
1238
+ /* Phase-4 data display. */
1239
+
1240
+ /* Table — header + bounded rows (unvirtualized by design). */
1241
+ .vx-table {
1242
+ flex-direction: column;
1243
+ border-width: 1px;
1244
+ border-color: var(--color-border);
1245
+ border-radius: var(--radius-md);
1246
+ }
1247
+ .vx-table-head,
1248
+ .vx-table-row {
1249
+ flex-direction: row;
1250
+ }
1251
+ .vx-table-th {
1252
+ font-weight: 600;
1253
+ }
1254
+ .vx-table-cell {
1255
+ padding: 8px 12px;
1256
+ }
1257
+ .vx-table-head {
1258
+ border-bottom-width: 1px;
1259
+ border-bottom-color: var(--color-border);
1260
+ }
1261
+ .vx-table-empty {
1262
+ padding: 16px;
1263
+ align-items: center;
1264
+ }
1265
+
1266
+ /* Timeline — dot + connector rail beside content. */
1267
+ .vx-timeline {
1268
+ flex-direction: column;
1269
+ }
1270
+ .vx-timeline-item {
1271
+ flex-direction: row;
1272
+ }
1273
+ .vx-timeline-rail {
1274
+ flex-direction: column;
1275
+ align-items: center;
1276
+ width: 20px;
1277
+ }
1278
+ .vx-timeline-dot {
1279
+ width: 10px;
1280
+ height: 10px;
1281
+ margin-top: 5px;
1282
+ border-radius: 50%;
1283
+ background-color: var(--color-primary);
1284
+ align-items: center;
1285
+ justify-content: center;
1286
+ }
1287
+ .vx-timeline-line {
1288
+ width: 1px;
1289
+ flex: 1;
1290
+ background-color: var(--color-border);
1291
+ }
1292
+ .vx-timeline-body {
173
1293
  flex-direction: column;
174
- user-select: none;
1294
+ padding-left: 10px;
1295
+ padding-bottom: 16px;
175
1296
  }
176
-
177
- /* web press feedback */
178
- .vx-pressable:active {
179
- opacity: 0.85;
1297
+ .vx-timeline-time {
1298
+ font-size: 12px;
1299
+ line-height: 16px;
1300
+ color: var(--color-text-secondary);
180
1301
  }
181
- .vx-pressable-no-feedback:active {
182
- opacity: 1;
1302
+ .vx-timeline-title {
1303
+ font-weight: 600;
183
1304
  }
184
- /* native press feedback (NS pseudo — dead rule on web, and vice versa) */
185
- .vx-pressable:pressed {
186
- opacity: 0.85;
1305
+ .vx-timeline-desc {
1306
+ color: var(--color-text-secondary);
187
1307
  }
188
1308
 
189
- /* xplat-web-only:start — overlays/toasts/popovers render through RootLayout
190
- or showModal on native; these portal/positioning rules are web-only */
191
- .vx-overlay {
192
- z-index: 1000;
1309
+ /* Tree — indented collapsible rows. */
1310
+ .vx-tree {
1311
+ flex-direction: column;
193
1312
  }
194
-
195
- .vx-overlay-shade {
196
- position: absolute;
197
- inset: 0;
198
- background-color: rgba(0, 0, 0, 0.4);
1313
+ .vx-tree-node {
1314
+ flex-direction: column;
199
1315
  }
200
-
201
- .vx-overlay-content {
202
- position: relative;
203
- z-index: 1;
1316
+ .vx-tree-row {
1317
+ flex-direction: row;
1318
+ align-items: center;
1319
+ padding: 4px 0;
204
1320
  }
205
-
206
- .vx-popover {
207
- z-index: 1001;
1321
+ .vx-tree-row--disabled {
1322
+ opacity: 0.5;
208
1323
  }
209
-
210
- .vx-popover-backdrop {
211
- background-color: transparent;
1324
+ .vx-tree-caret {
1325
+ width: 14px;
1326
+ margin-left: 4px;
212
1327
  }
213
1328
 
214
- .vx-toast-host {
215
- position: fixed;
216
- z-index: 1002;
217
- left: 0;
218
- right: 0;
219
- display: flex;
220
- justify-content: center;
221
- pointer-events: none;
1329
+ /* Alert — icon + body callout; tone picks border/text colors. */
1330
+ .vx-alert {
1331
+ flex-direction: row;
1332
+ padding: 10px 12px;
1333
+ border-width: 1px;
1334
+ border-radius: var(--radius-md);
1335
+ corner-shape: squircle;
1336
+ border-color: var(--color-border);
222
1337
  }
223
-
224
- .vx-toast-top {
225
- top: 24px;
1338
+ .vx-alert-body {
1339
+ flex-direction: column;
1340
+ flex: 1;
1341
+ margin-left: 8px;
226
1342
  }
227
- .vx-toast-bottom {
228
- bottom: 24px;
1343
+ .vx-alert-title {
1344
+ font-weight: 600;
229
1345
  }
230
-
231
- .vx-toast {
232
- pointer-events: auto;
1346
+ .vx-alert--error {
1347
+ border-color: var(--color-danger);
233
1348
  }
234
- /* xplat-web-only:end */
235
-
236
- /* auto-grow owns height — the manual resize handle has no native analog */
237
- /* xplat-web-only:start */
238
- .vx-textarea {
239
- resize: none;
1349
+ .vx-alert--warning {
1350
+ border-color: var(--color-warning, #d9a400);
1351
+ }
1352
+ .vx-alert--success {
1353
+ border-color: var(--color-success, #2e9e5b);
1354
+ }
1355
+ .vx-alert--info {
1356
+ border-color: var(--color-primary);
240
1357
  }
241
- /* xplat-web-only:end */
242
1358
 
243
- /* Modal is a real top-layer dialog on web. Presentation variants keep their
244
- sizing and anchoring in tokens so both the component and openModal share it. */
245
- /* xplat-web-only:start */
246
- .vx-modal {
247
- width: 480px;
248
- max-width: 90%;
249
- max-height: 90%;
250
- padding: 24px;
251
- border: 1px solid var(--color-border);
252
- border-radius: var(--radius-xl);
1359
+ /* Card — sectioned container. */
1360
+ .vx-card {
1361
+ flex-direction: column;
1362
+ border-width: 1px;
1363
+ border-color: var(--color-border);
1364
+ border-radius: var(--radius-md);
253
1365
  corner-shape: squircle;
254
- color: var(--color-text);
255
1366
  background-color: var(--color-surface);
256
1367
  }
257
-
258
- .vx-modal::backdrop {
259
- background: rgba(0, 0, 0, 0.4);
1368
+ .vx-card-header {
1369
+ padding: 12px;
1370
+ border-bottom-width: 1px;
1371
+ border-bottom-color: var(--color-border);
260
1372
  }
261
-
262
- .vx-modal--dialog {
263
- width: 480px;
264
- max-width: 90%;
1373
+ .vx-card-body {
1374
+ padding: 12px;
1375
+ flex-direction: column;
1376
+ }
1377
+ .vx-card-footer {
1378
+ padding: 12px;
1379
+ border-top-width: 1px;
1380
+ border-top-color: var(--color-border);
265
1381
  }
266
1382
 
267
- .vx-modal--sheet {
268
- width: 100%;
269
- max-width: none;
270
- max-height: 85%;
271
- position: fixed;
272
- inset: auto 0 0;
273
- margin: 0;
274
- border-top-left-radius: var(--radius-xl);
275
- border-top-right-radius: var(--radius-xl);
1383
+ /* Chip — pressable pill with selected + remove states. */
1384
+ .vx-chip {
1385
+ flex-direction: row;
1386
+ align-items: center;
1387
+ padding: 3px 10px;
1388
+ border-width: 1px;
1389
+ border-color: var(--color-border);
1390
+ border-radius: var(--radius-md);
276
1391
  corner-shape: squircle;
1392
+ font-size: 13px;
277
1393
  }
278
-
279
- .vx-modal--fullscreen {
280
- width: 100%;
281
- max-width: none;
282
- height: 100%;
283
- max-height: none;
284
- margin: 0;
285
- border-radius: 0;
1394
+ .vx-chip--on {
1395
+ background-color: var(--color-primary);
1396
+ border-color: var(--color-primary);
1397
+ color: var(--color-onprimary);
1398
+ }
1399
+ .vx-chip--disabled {
1400
+ opacity: 0.5;
1401
+ }
1402
+ .vx-chip-remove {
1403
+ margin-left: 6px;
286
1404
  }
287
1405
 
288
- /* Modal ROUTES (pushRoute presentation:'modal') — the URL stays underneath,
289
- so the overlay is a fixed layer over the shell rather than a dialog. */
290
- .vx-modalroute {
291
- position: fixed;
292
- inset: 0;
293
- z-index: 1000;
294
- display: flex;
295
- align-items: stretch;
296
- justify-content: stretch;
1406
+ /* Kbd — keycap glyph. */
1407
+ .vx-kbd {
1408
+ padding: 1px 5px;
1409
+ border-width: 1px;
1410
+ border-color: var(--color-border);
1411
+ border-bottom-width: 2px;
1412
+ border-radius: var(--radius-sm);
1413
+ font-size: 12px;
297
1414
  }
298
1415
 
299
- .vx-modalroute-shade {
300
- position: absolute;
301
- inset: 0;
302
- border: 0;
303
- padding: 0;
304
- background: rgba(0, 0, 0, 0.4);
305
- cursor: default;
1416
+ /* Empty — centered placeholder. */
1417
+ .vx-empty {
1418
+ flex-direction: column;
1419
+ align-items: center;
1420
+ padding: 24px;
1421
+ }
1422
+ .vx-empty-title {
1423
+ font-weight: 600;
1424
+ margin-top: 8px;
1425
+ }
1426
+ .vx-empty-desc {
1427
+ color: var(--color-text-secondary);
1428
+ margin-top: 2px;
306
1429
  }
307
1430
 
308
- .vx-modalroute-body {
309
- position: relative;
310
- flex: 1;
311
- min-width: 0;
312
- overflow: auto;
313
- color: var(--color-text);
1431
+ /* Banner — notice strip. */
1432
+ .vx-banner {
1433
+ flex-direction: row;
1434
+ align-items: center;
1435
+ padding: 8px 12px;
1436
+ border-width: 1px;
1437
+ border-color: var(--color-border);
1438
+ border-radius: var(--radius-md);
314
1439
  background-color: var(--color-surface);
315
1440
  }
316
- /* xplat-web-only:end */
317
-
318
- /* xplat-web-only:start — placeholder pseudos are DOM-only. Muted like
319
- real placeholder text, not currentColor (a full-opacity hint reads as
320
- a typed value); --placeholder-color is the per-field prop escape. */
321
- input::placeholder,
322
- textarea::placeholder {
323
- color: var(--placeholder-color, var(--color-text-secondary));
1441
+ .vx-banner-body {
1442
+ flex-direction: column;
1443
+ flex: 1;
1444
+ margin-left: 8px;
324
1445
  }
325
- /* xplat-web-only:end */
326
-
327
- /* xplat-web-only:start — form controls don't inherit the body font in the
328
- DOM (UA stylesheet wins); without this they render in the UA face. */
329
- input,
330
- textarea,
331
- select,
332
- button {
333
- font-family: inherit;
1446
+ .vx-banner-dismiss {
1447
+ margin-left: 8px;
334
1448
  }
335
- /* xplat-web-only:end */
336
1449
 
337
- /* xplat-web-only:start — native ActivityIndicator is the NS widget */
338
- .vx-spinner {
339
- display: inline-block;
340
- width: 1em;
341
- height: 1em;
342
- box-sizing: border-box;
343
- border: 2px solid currentColor;
344
- border-right-color: transparent;
345
- border-radius: 50%;
346
- animation: vx-spinner-turn 0.8s linear infinite;
1450
+ /* User — avatar + text column. */
1451
+ .vx-user {
1452
+ flex-direction: row;
1453
+ align-items: center;
1454
+ }
1455
+ .vx-user-body {
1456
+ flex-direction: column;
1457
+ margin-left: 10px;
1458
+ }
1459
+ .vx-user-name {
1460
+ font-weight: 600;
1461
+ }
1462
+ .vx-user-desc {
1463
+ font-size: 12px;
1464
+ line-height: 16px;
1465
+ color: var(--color-text-secondary);
347
1466
  }
348
1467
 
349
- @keyframes vx-spinner-turn {
350
- to {
351
- transform: rotate(360deg);
352
- }
1468
+ /* ProgressGroup — stacked labeled meters. */
1469
+ .vx-progressgroup {
1470
+ flex-direction: column;
1471
+ }
1472
+ .vx-progressgroup-row {
1473
+ flex-direction: row;
1474
+ align-items: center;
1475
+ }
1476
+ .vx-progressgroup-label {
1477
+ margin-left: 8px;
1478
+ }
1479
+ /* xplat-web-only:start — display:flex for the div-based flex classes */
1480
+ .vx-table,
1481
+ .vx-table-head,
1482
+ .vx-table-row,
1483
+ .vx-timeline,
1484
+ .vx-timeline-item,
1485
+ .vx-timeline-rail,
1486
+ .vx-timeline-body,
1487
+ .vx-tree,
1488
+ .vx-tree-node,
1489
+ .vx-tree-row,
1490
+ .vx-alert,
1491
+ .vx-alert-body,
1492
+ .vx-card,
1493
+ .vx-card-body,
1494
+ .vx-banner,
1495
+ .vx-banner-body,
1496
+ .vx-user,
1497
+ .vx-user-body,
1498
+ .vx-progressgroup,
1499
+ .vx-progressgroup-row {
1500
+ display: flex;
1501
+ }
1502
+ .vx-table-row--pressable,
1503
+ .vx-tree-row,
1504
+ .vx-user--pressable,
1505
+ .vx-banner-dismiss,
1506
+ .vx-chip-remove {
1507
+ cursor: pointer;
353
1508
  }
354
1509
  /* xplat-web-only:end */
355
1510
 
@@ -378,35 +1533,6 @@ button {
378
1533
  font-weight: bold;
379
1534
  }
380
1535
 
381
- /* xplat-web-only:start — glass material is real on iOS 26+; web approximates
382
- with backdrop-filter. The class also lands on native elements (inert) so
383
- apps can style their own fallback — but these rules are DOM-only. */
384
- .vx-glass {
385
- background-color: color-mix(in srgb, var(--color-surface) 30%, transparent);
386
- border: 1px solid rgba(255, 255, 255, 0.25);
387
- }
388
-
389
- @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
390
- .vx-glass {
391
- background-color: color-mix(in srgb, var(--color-surface) 20%, transparent);
392
- -webkit-backdrop-filter: blur(20px) saturate(1.6);
393
- backdrop-filter: blur(20px) saturate(1.6);
394
- }
395
- }
396
-
397
- .vx-glass--clear {
398
- background-color: color-mix(in srgb, var(--color-surface) 12%, transparent);
399
- border-color: rgba(255, 255, 255, 0.15);
400
- }
401
-
402
- @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
403
- .vx-glass--clear {
404
- -webkit-backdrop-filter: blur(8px) saturate(1.3);
405
- backdrop-filter: blur(8px) saturate(1.3);
406
- }
407
- }
408
- /* xplat-web-only:end */
409
-
410
1536
  /* utilities (hand-written subset — do NOT adopt Tailwind until
411
1537
  NativeScript/tailwind#226 is resolved; the plugin strips core-supported
412
1538
  props incl. gap, drops all @media, and rewrites invisible→collapse) */
@@ -420,14 +1546,19 @@ button {
420
1546
  /* Flex divergence guards — NS FlexboxLayout diverges from web flexbox on
421
1547
  * shrinkability: grow children never shrink below intrinsic width, fixed
422
1548
  * children get crushed, and auto-height rows re-measure grow children at
423
- * zero height. min-w-0/min-h-0 let a grow child shrink; shrink-0 protects
424
- * a fixed-size child. All three are plain CSS on web. */
1549
+ * zero height. The shared `* { flex-shrink: 0 }` default (RN semantics)
1550
+ * makes .shrink the opt-in for a shrinkable child; min-w-0/min-h-0 lower
1551
+ * the min-content floor so it can actually shrink, and shrink-0 remains
1552
+ * for local overrides. All four are plain CSS on web. */
425
1553
  .min-w-0 {
426
1554
  min-width: 0;
427
1555
  }
428
1556
  .min-h-0 {
429
1557
  min-height: 0;
430
1558
  }
1559
+ .shrink {
1560
+ flex-shrink: 1;
1561
+ }
431
1562
  .shrink-0 {
432
1563
  flex-shrink: 0;
433
1564
  }
@@ -462,17 +1593,25 @@ button {
462
1593
  gap: 24px;
463
1594
  }
464
1595
 
1596
+ /* line-height is REQUIRED on native: without it a Label's measured height
1597
+ misses the font's line metrics, and Android clips the descenders/bottom
1598
+ of the glyphs (observed: section-labels 21–29px bounds for 12sp text at
1599
+ 3.25x). Tailwind pairs every text-* size with a leading, same here. */
465
1600
  .text-2xl {
466
1601
  font-size: 28px;
1602
+ line-height: 36px;
467
1603
  }
468
1604
  .text-xl {
469
1605
  font-size: 20px;
1606
+ line-height: 28px;
470
1607
  }
471
1608
  .text-lg {
472
1609
  font-size: 18px;
1610
+ line-height: 28px;
473
1611
  }
474
1612
  .text-sm {
475
1613
  font-size: 13px;
1614
+ line-height: 20px;
476
1615
  }
477
1616
  .font-bold {
478
1617
  font-weight: bold;
@@ -581,6 +1720,9 @@ a.chip {
581
1720
  .vx-tab.active {
582
1721
  color: var(--color-text);
583
1722
  }
1723
+ .vx-tabicon {
1724
+ margin-right: 6px;
1725
+ }
584
1726
 
585
1727
  /* xplat-web-only:start — DOM defaults are display:block (div) and
586
1728
  inline-block (button); every flex-bearing vx- class needs an explicit
@@ -689,10 +1831,6 @@ button.vx-tab {
689
1831
  border-bottom-color: var(--color-border);
690
1832
  }
691
1833
 
692
- .vx-list {
693
- flex-grow: 1;
694
- }
695
-
696
1834
  .gap-2 {
697
1835
  gap: 8px;
698
1836
  }
@@ -700,27 +1838,16 @@ button.vx-tab {
700
1838
  /* In-window sheet — bottom-anchored panel (matches the native RootLayout
701
1839
  sheet). Layer is pointer-events:none; backdrop + panel re-enable. */
702
1840
  .vx-sheet-layer {
703
- position: fixed;
704
- inset: 0;
705
1841
  z-index: 1100;
706
- pointer-events: none;
707
1842
  }
708
1843
 
709
1844
  .vx-sheet-backdrop {
710
- position: absolute;
711
- inset: 0;
712
1845
  background: rgba(0, 0, 0, 0.4);
713
- pointer-events: auto;
714
1846
  }
715
1847
 
716
1848
  .vx-sheet {
717
- position: absolute;
718
- left: 0;
719
- right: 0;
720
- bottom: 0;
721
1849
  max-height: 85%;
722
1850
  padding: 24px;
723
- overflow: auto;
724
1851
  border-width: 1px;
725
1852
  border-color: var(--color-border);
726
1853
  border-top-left-radius: var(--radius-xl);
@@ -728,5 +1855,58 @@ button.vx-tab {
728
1855
  corner-shape: squircle;
729
1856
  background-color: var(--color-surface);
730
1857
  color: var(--color-text);
1858
+ }
1859
+
1860
+ /* xplat-web-only:start — DOM positioning + hit-testing for the sheet.
1861
+ The native leaf renders on RootLayout with its own geometry. */
1862
+ .vx-sheet-layer {
1863
+ position: fixed;
1864
+ inset: 0;
1865
+ pointer-events: none;
1866
+ }
1867
+
1868
+ .vx-sheet-backdrop,
1869
+ .vx-sheet {
1870
+ position: absolute;
1871
+ inset: 0;
731
1872
  pointer-events: auto;
732
1873
  }
1874
+
1875
+ .vx-sheet {
1876
+ top: auto;
1877
+ bottom: 0;
1878
+ overflow: auto;
1879
+ }
1880
+ /* xplat-web-only:end */
1881
+
1882
+ .vx-sheet-grabber {
1883
+ display: flex;
1884
+ align-items: center;
1885
+ justify-content: center;
1886
+ height: 28px;
1887
+ }
1888
+
1889
+ .vx-sheet-grip {
1890
+ width: 36px;
1891
+ height: 4px;
1892
+ border-radius: 999px;
1893
+ background-color: var(--color-text-secondary);
1894
+ opacity: 0.4;
1895
+ }
1896
+
1897
+ /* xplat-web-only:start — detents size the panel to the largest fraction
1898
+ (overriding the 85% cap) and overlay the grabber; native stacks it as a
1899
+ top-aligned grid child. touch-action keeps drags off native scroll. */
1900
+ .vx-sheet-detents {
1901
+ max-height: none;
1902
+ }
1903
+ .vx-sheet-detents > .vx-sheet-grabber {
1904
+ position: absolute;
1905
+ top: 0;
1906
+ left: 0;
1907
+ right: 0;
1908
+ z-index: 2;
1909
+ cursor: grab;
1910
+ touch-action: none;
1911
+ }
1912
+ /* xplat-web-only:end */