@yahoo/uds-mobile 2.24.0-beta.1 → 2.24.0-beta.10

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 (236) hide show
  1. package/README.md +37 -1
  2. package/dist/components/Avatar/AvatarIcon.cjs +8 -2
  3. package/dist/components/Avatar/AvatarIcon.d.cts.map +1 -1
  4. package/dist/components/Avatar/AvatarIcon.d.ts.map +1 -1
  5. package/dist/components/Avatar/AvatarIcon.js +9 -3
  6. package/dist/components/Avatar/AvatarIcon.js.map +1 -1
  7. package/dist/components/Avatar/AvatarImage.cjs +9 -2
  8. package/dist/components/Avatar/AvatarImage.d.cts.map +1 -1
  9. package/dist/components/Avatar/AvatarImage.d.ts.map +1 -1
  10. package/dist/components/Avatar/AvatarImage.js +10 -3
  11. package/dist/components/Avatar/AvatarImage.js.map +1 -1
  12. package/dist/components/Avatar/AvatarText.cjs +8 -2
  13. package/dist/components/Avatar/AvatarText.d.cts.map +1 -1
  14. package/dist/components/Avatar/AvatarText.d.ts.map +1 -1
  15. package/dist/components/Avatar/AvatarText.js +9 -3
  16. package/dist/components/Avatar/AvatarText.js.map +1 -1
  17. package/dist/components/Avatar/types.d.cts +7 -3
  18. package/dist/components/Avatar/types.d.cts.map +1 -1
  19. package/dist/components/Avatar/types.d.ts +7 -3
  20. package/dist/components/Avatar/types.d.ts.map +1 -1
  21. package/dist/components/Avatar/useAvatarFontScale.cjs +16 -0
  22. package/dist/components/Avatar/useAvatarFontScale.d.cts +7 -0
  23. package/dist/components/Avatar/useAvatarFontScale.d.cts.map +1 -0
  24. package/dist/components/Avatar/useAvatarFontScale.d.ts +7 -0
  25. package/dist/components/Avatar/useAvatarFontScale.d.ts.map +1 -0
  26. package/dist/components/Avatar/useAvatarFontScale.js +16 -0
  27. package/dist/components/Avatar/useAvatarFontScale.js.map +1 -0
  28. package/dist/components/Avatar/utils.cjs +9 -6
  29. package/dist/components/Avatar/utils.d.cts +2 -2
  30. package/dist/components/Avatar/utils.d.cts.map +1 -1
  31. package/dist/components/Avatar/utils.d.ts +2 -2
  32. package/dist/components/Avatar/utils.d.ts.map +1 -1
  33. package/dist/components/Avatar/utils.js +9 -6
  34. package/dist/components/Avatar/utils.js.map +1 -1
  35. package/dist/components/Button/buttonTheme.cjs +16 -7
  36. package/dist/components/Button/buttonTheme.d.cts +13 -6
  37. package/dist/components/Button/buttonTheme.d.cts.map +1 -1
  38. package/dist/components/Button/buttonTheme.d.ts +13 -6
  39. package/dist/components/Button/buttonTheme.d.ts.map +1 -1
  40. package/dist/components/Button/buttonTheme.js +16 -8
  41. package/dist/components/Button/buttonTheme.js.map +1 -1
  42. package/dist/components/Button.cjs +25 -11
  43. package/dist/components/Button.d.cts.map +1 -1
  44. package/dist/components/Button.d.ts.map +1 -1
  45. package/dist/components/Button.js +25 -11
  46. package/dist/components/Button.js.map +1 -1
  47. package/dist/components/Checkbox.cjs +16 -6
  48. package/dist/components/Checkbox.d.cts.map +1 -1
  49. package/dist/components/Checkbox.d.ts.map +1 -1
  50. package/dist/components/Checkbox.js +16 -6
  51. package/dist/components/Checkbox.js.map +1 -1
  52. package/dist/components/Chip/ChipBase.cjs +24 -21
  53. package/dist/components/Chip/ChipBase.js +24 -21
  54. package/dist/components/Chip/ChipBase.js.map +1 -1
  55. package/dist/components/Icon.cjs +22 -6
  56. package/dist/components/Icon.d.cts +6 -5
  57. package/dist/components/Icon.d.cts.map +1 -1
  58. package/dist/components/Icon.d.ts +6 -5
  59. package/dist/components/Icon.d.ts.map +1 -1
  60. package/dist/components/Icon.js +22 -6
  61. package/dist/components/Icon.js.map +1 -1
  62. package/dist/components/IconButton.cjs +17 -6
  63. package/dist/components/IconButton.d.cts +6 -0
  64. package/dist/components/IconButton.d.cts.map +1 -1
  65. package/dist/components/IconButton.d.ts +6 -0
  66. package/dist/components/IconButton.d.ts.map +1 -1
  67. package/dist/components/IconButton.js +18 -7
  68. package/dist/components/IconButton.js.map +1 -1
  69. package/dist/components/Input.cjs +56 -18
  70. package/dist/components/Input.d.cts +5 -0
  71. package/dist/components/Input.d.cts.map +1 -1
  72. package/dist/components/Input.d.ts +5 -0
  73. package/dist/components/Input.d.ts.map +1 -1
  74. package/dist/components/Input.js +56 -18
  75. package/dist/components/Input.js.map +1 -1
  76. package/dist/components/InputHelpText.cjs +7 -1
  77. package/dist/components/InputHelpText.d.cts +2 -0
  78. package/dist/components/InputHelpText.d.cts.map +1 -1
  79. package/dist/components/InputHelpText.d.ts +2 -0
  80. package/dist/components/InputHelpText.d.ts.map +1 -1
  81. package/dist/components/InputHelpText.js +7 -1
  82. package/dist/components/InputHelpText.js.map +1 -1
  83. package/dist/components/Link.cjs +13 -1
  84. package/dist/components/Link.d.cts +7 -0
  85. package/dist/components/Link.d.cts.map +1 -1
  86. package/dist/components/Link.d.ts +7 -0
  87. package/dist/components/Link.d.ts.map +1 -1
  88. package/dist/components/Link.js +13 -1
  89. package/dist/components/Link.js.map +1 -1
  90. package/dist/components/Pagination/Pagination.cjs +18 -10
  91. package/dist/components/Pagination/Pagination.d.cts +2 -0
  92. package/dist/components/Pagination/Pagination.d.cts.map +1 -1
  93. package/dist/components/Pagination/Pagination.d.ts +2 -0
  94. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  95. package/dist/components/Pagination/Pagination.js +18 -10
  96. package/dist/components/Pagination/Pagination.js.map +1 -1
  97. package/dist/components/Pagination/PaginationEllipsis.cjs +8 -2
  98. package/dist/components/Pagination/PaginationEllipsis.d.cts.map +1 -1
  99. package/dist/components/Pagination/PaginationEllipsis.d.ts.map +1 -1
  100. package/dist/components/Pagination/PaginationEllipsis.js +9 -3
  101. package/dist/components/Pagination/PaginationEllipsis.js.map +1 -1
  102. package/dist/components/Pagination/PaginationItem.cjs +2 -1
  103. package/dist/components/Pagination/PaginationItem.js +2 -1
  104. package/dist/components/Pagination/PaginationItem.js.map +1 -1
  105. package/dist/components/Pagination/PaginationLink.cjs +2 -1
  106. package/dist/components/Pagination/PaginationLink.js +2 -1
  107. package/dist/components/Pagination/PaginationLink.js.map +1 -1
  108. package/dist/components/Pagination/PaginationNext.cjs +3 -1
  109. package/dist/components/Pagination/PaginationNext.js +3 -1
  110. package/dist/components/Pagination/PaginationNext.js.map +1 -1
  111. package/dist/components/Pagination/PaginationPrev.cjs +3 -1
  112. package/dist/components/Pagination/PaginationPrev.js +3 -1
  113. package/dist/components/Pagination/PaginationPrev.js.map +1 -1
  114. package/dist/components/Pagination/paginationContext.d.cts +2 -0
  115. package/dist/components/Pagination/paginationContext.d.cts.map +1 -1
  116. package/dist/components/Pagination/paginationContext.d.ts +2 -0
  117. package/dist/components/Pagination/paginationContext.d.ts.map +1 -1
  118. package/dist/components/Pagination/paginationContext.js.map +1 -1
  119. package/dist/components/Pagination/usePaginationControlStyles.cjs +8 -1
  120. package/dist/components/Pagination/usePaginationControlStyles.d.cts.map +1 -1
  121. package/dist/components/Pagination/usePaginationControlStyles.d.ts.map +1 -1
  122. package/dist/components/Pagination/usePaginationControlStyles.js +9 -2
  123. package/dist/components/Pagination/usePaginationControlStyles.js.map +1 -1
  124. package/dist/components/Radio/Radio.cjs +4 -3
  125. package/dist/components/Radio/Radio.js +4 -3
  126. package/dist/components/Radio/Radio.js.map +1 -1
  127. package/dist/components/Select/Select.cjs +29 -6
  128. package/dist/components/Select/Select.d.cts +2 -0
  129. package/dist/components/Select/Select.d.cts.map +1 -1
  130. package/dist/components/Select/Select.d.ts +2 -0
  131. package/dist/components/Select/Select.d.ts.map +1 -1
  132. package/dist/components/Select/Select.js +29 -6
  133. package/dist/components/Select/Select.js.map +1 -1
  134. package/dist/components/Select/SelectContent.cjs +31 -23
  135. package/dist/components/Select/SelectContent.d.cts.map +1 -1
  136. package/dist/components/Select/SelectContent.d.ts.map +1 -1
  137. package/dist/components/Select/SelectContent.js +32 -24
  138. package/dist/components/Select/SelectContent.js.map +1 -1
  139. package/dist/components/Select/SelectItem.cjs +25 -15
  140. package/dist/components/Select/SelectItem.d.cts.map +1 -1
  141. package/dist/components/Select/SelectItem.d.ts.map +1 -1
  142. package/dist/components/Select/SelectItem.js +25 -15
  143. package/dist/components/Select/SelectItem.js.map +1 -1
  144. package/dist/components/Select/SelectTrigger.cjs +27 -13
  145. package/dist/components/Select/SelectTrigger.d.cts.map +1 -1
  146. package/dist/components/Select/SelectTrigger.d.ts.map +1 -1
  147. package/dist/components/Select/SelectTrigger.js +27 -13
  148. package/dist/components/Select/SelectTrigger.js.map +1 -1
  149. package/dist/components/Select/types.d.cts +2 -0
  150. package/dist/components/Select/types.d.cts.map +1 -1
  151. package/dist/components/Select/types.d.ts +2 -0
  152. package/dist/components/Select/types.d.ts.map +1 -1
  153. package/dist/components/Switch.cjs +31 -14
  154. package/dist/components/Switch.d.cts.map +1 -1
  155. package/dist/components/Switch.d.ts.map +1 -1
  156. package/dist/components/Switch.js +31 -14
  157. package/dist/components/Switch.js.map +1 -1
  158. package/dist/components/Tabs/Tab.cjs +8 -4
  159. package/dist/components/Tabs/Tab.d.cts.map +1 -1
  160. package/dist/components/Tabs/Tab.d.ts.map +1 -1
  161. package/dist/components/Tabs/Tab.js +8 -4
  162. package/dist/components/Tabs/Tab.js.map +1 -1
  163. package/dist/components/Text.cjs +5 -0
  164. package/dist/components/Text.d.cts +6 -5
  165. package/dist/components/Text.d.cts.map +1 -1
  166. package/dist/components/Text.d.ts +6 -5
  167. package/dist/components/Text.d.ts.map +1 -1
  168. package/dist/components/Text.js +5 -0
  169. package/dist/components/Text.js.map +1 -1
  170. package/dist/components/UDSProvider.cjs +1 -1
  171. package/dist/components/UDSProvider.d.cts +4 -3
  172. package/dist/components/UDSProvider.d.cts.map +1 -1
  173. package/dist/components/UDSProvider.d.ts +4 -3
  174. package/dist/components/UDSProvider.d.ts.map +1 -1
  175. package/dist/components/UDSProvider.js +1 -1
  176. package/dist/components/UDSProvider.js.map +1 -1
  177. package/dist/fontScaling/FontScalingContext.cjs +35 -3
  178. package/dist/fontScaling/FontScalingContext.d.cts +20 -5
  179. package/dist/fontScaling/FontScalingContext.d.cts.map +1 -1
  180. package/dist/fontScaling/FontScalingContext.d.ts +20 -5
  181. package/dist/fontScaling/FontScalingContext.d.ts.map +1 -1
  182. package/dist/fontScaling/FontScalingContext.js +34 -5
  183. package/dist/fontScaling/FontScalingContext.js.map +1 -1
  184. package/dist/fontScaling/androidNonlinearFontScaling.cjs +250 -0
  185. package/dist/fontScaling/androidNonlinearFontScaling.d.cts +7 -0
  186. package/dist/fontScaling/androidNonlinearFontScaling.d.cts.map +1 -0
  187. package/dist/fontScaling/androidNonlinearFontScaling.d.ts +7 -0
  188. package/dist/fontScaling/androidNonlinearFontScaling.d.ts.map +1 -0
  189. package/dist/fontScaling/androidNonlinearFontScaling.js +251 -0
  190. package/dist/fontScaling/androidNonlinearFontScaling.js.map +1 -0
  191. package/dist/fontScaling/constants.cjs +37 -3
  192. package/dist/fontScaling/constants.d.cts +15 -4
  193. package/dist/fontScaling/constants.d.cts.map +1 -1
  194. package/dist/fontScaling/constants.d.ts +15 -4
  195. package/dist/fontScaling/constants.d.ts.map +1 -1
  196. package/dist/fontScaling/constants.js +37 -4
  197. package/dist/fontScaling/constants.js.map +1 -1
  198. package/dist/fontScaling/index.cjs +2 -1
  199. package/dist/fontScaling/index.d.cts +2 -2
  200. package/dist/fontScaling/index.d.ts +2 -2
  201. package/dist/fontScaling/index.js +3 -3
  202. package/dist/fontScaling/resolveMaxFontSizeMultiplier.cjs +1 -2
  203. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts +1 -2
  204. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.cts.map +1 -1
  205. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts +1 -2
  206. package/dist/fontScaling/resolveMaxFontSizeMultiplier.d.ts.map +1 -1
  207. package/dist/fontScaling/resolveMaxFontSizeMultiplier.js +1 -2
  208. package/dist/fontScaling/resolveMaxFontSizeMultiplier.js.map +1 -1
  209. package/dist/fontScaling/types.d.cts +11 -8
  210. package/dist/fontScaling/types.d.cts.map +1 -1
  211. package/dist/fontScaling/types.d.ts +11 -8
  212. package/dist/fontScaling/types.d.ts.map +1 -1
  213. package/dist/fontScaling/useFontScale.cjs +30 -4
  214. package/dist/fontScaling/useFontScale.d.cts +6 -12
  215. package/dist/fontScaling/useFontScale.d.cts.map +1 -1
  216. package/dist/fontScaling/useFontScale.d.ts +6 -12
  217. package/dist/fontScaling/useFontScale.d.ts.map +1 -1
  218. package/dist/fontScaling/useFontScale.js +31 -6
  219. package/dist/fontScaling/useFontScale.js.map +1 -1
  220. package/dist/jest/mocks/styles.cjs +14 -1
  221. package/dist/jest/mocks/styles.d.cts.map +1 -1
  222. package/dist/jest/mocks/styles.d.ts.map +1 -1
  223. package/dist/jest/mocks/styles.js +14 -1
  224. package/dist/jest/mocks/styles.js.map +1 -1
  225. package/dist/mobile/generated/fontMetrics.cjs +530 -530
  226. package/dist/mobile/generated/fontMetrics.js +530 -530
  227. package/dist/mobile/generated/fontMetrics.js.map +1 -1
  228. package/dist/portal.cjs +8 -2
  229. package/dist/portal.d.cts.map +1 -1
  230. package/dist/portal.d.ts.map +1 -1
  231. package/dist/portal.js +8 -2
  232. package/dist/portal.js.map +1 -1
  233. package/fonts/index.cjs +214 -214
  234. package/fonts/index.mjs +214 -214
  235. package/generated/fontMetrics.ts +530 -530
  236. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/components/Avatar/utils.ts"],"mappings":";;;;;;cAKM,mBAAA,EAAqB,UAAA;AAAA,cACrB,sBAAA,EAAwB,aAAA;AAAA,iBAoBrB,0BAAA,CACP,IAAA,WACA,QAAA,GAAU,0BAAA;AAAA,iBAgBH,wBAAA,CAAyB,UAAA,YAAsB,SAAA;AAAA,iBAI/C,oBAAA,CAAqB,OAAA,EAAS,aAAA,GAAgB,YAAA"}
1
+ {"version":3,"file":"utils.d.ts","names":[],"sources":["../../../src/components/Avatar/utils.ts"],"mappings":";;;;;;cAKM,mBAAA,EAAqB,UAAA;AAAA,cACrB,sBAAA,EAAwB,aAAA;AAAA,iBAoBrB,0BAAA,CACP,IAAA,WACA,QAAA,GAAU,0BAAA;AAAA,iBAgBH,kBAAA,CACP,SAAA,EAAW,SAAA,EACX,UAAA,sBACA,eAAA,WACC,SAAA;AAAA,iBAgBM,oBAAA,CAAqB,OAAA,EAAS,aAAA,GAAgB,YAAA"}
@@ -20,16 +20,19 @@ function generateAvatarAbbreviation(name, strategy = "firstAndLast") {
20
20
  const initials = name.trim().split(/\s+/).filter(Boolean).map((word) => word[0]?.toUpperCase() ?? "");
21
21
  return abbreviationStrategies[strategy](initials);
22
22
  }
23
- function getAvatarCustomSizeStyle(customSize) {
24
- return customSize === void 0 ? void 0 : {
25
- width: customSize,
26
- height: customSize
27
- };
23
+ function getAvatarSizeStyle(rootStyle, customSize, fontScaleFactor) {
24
+ if (customSize === void 0 && fontScaleFactor === 1) return;
25
+ const width = customSize ?? rootStyle.width;
26
+ const height = customSize ?? rootStyle.height;
27
+ return typeof width === "number" && typeof height === "number" ? {
28
+ width: Math.round(width * fontScaleFactor),
29
+ height: Math.round(height * fontScaleFactor)
30
+ } : void 0;
28
31
  }
29
32
  function getDefaultAvatarIcon(variant) {
30
33
  return DEFAULT_AVATAR_ICON[variant];
31
34
  }
32
35
  //#endregion
33
- export { DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_VARIANT, generateAvatarAbbreviation, getAvatarCustomSizeStyle, getDefaultAvatarIcon };
36
+ export { DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_VARIANT, generateAvatarAbbreviation, getAvatarSizeStyle, getDefaultAvatarIcon };
34
37
 
35
38
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/components/Avatar/utils.ts"],"sourcesContent":["import type { AvatarAbbreviationStrategy, AvatarSize, AvatarVariant } from '@yahoo/uds-types';\nimport type { ViewStyle } from 'react-native';\n\nimport type { IconSlotType } from '../IconSlot';\n\nconst DEFAULT_AVATAR_SIZE: AvatarSize = 'md';\nconst DEFAULT_AVATAR_VARIANT: AvatarVariant = 'primary';\n\nconst DEFAULT_AVATAR_ICON: Record<AvatarVariant, IconSlotType> = {\n primary: 'Person',\n secondary: 'Building',\n};\n\nconst abbreviationStrategies: Record<\n Exclude<AvatarAbbreviationStrategy, (name: string) => string>,\n (initials: string[]) => string\n> = {\n first: (initials) => initials[0] ?? '',\n last: (initials) => initials[initials.length - 1] ?? '',\n firstAndLast: (initials) =>\n initials.length === 1 ? (initials[0] ?? '') : `${initials[0]}${initials[initials.length - 1]}`,\n firstTwo: (initials) => initials.slice(0, 2).join(''),\n firstThree: (initials) => initials.slice(0, 3).join(''),\n firstOfEach: (initials) => initials.join(''),\n};\n\nfunction generateAvatarAbbreviation(\n name?: string,\n strategy: AvatarAbbreviationStrategy = 'firstAndLast',\n): string {\n if (!name) {\n return '';\n }\n\n if (typeof strategy === 'function') {\n return strategy(name);\n }\n\n const words = name.trim().split(/\\s+/).filter(Boolean);\n const initials = words.map((word) => word[0]?.toUpperCase() ?? '');\n\n return abbreviationStrategies[strategy](initials);\n}\n\nfunction getAvatarCustomSizeStyle(customSize?: number): ViewStyle | undefined {\n return customSize === undefined ? undefined : { width: customSize, height: customSize };\n}\n\nfunction getDefaultAvatarIcon(variant: AvatarVariant): IconSlotType {\n return DEFAULT_AVATAR_ICON[variant];\n}\n\nexport {\n DEFAULT_AVATAR_SIZE,\n DEFAULT_AVATAR_VARIANT,\n generateAvatarAbbreviation,\n getAvatarCustomSizeStyle,\n getDefaultAvatarIcon,\n};\n"],"mappings":";;AAKA,MAAM,sBAAkC;AACxC,MAAM,yBAAwC;AAE9C,MAAM,sBAA2D;CAC/D,SAAS;CACT,WAAW;CACZ;AAED,MAAM,yBAGF;CACF,QAAQ,aAAa,SAAS,MAAM;CACpC,OAAO,aAAa,SAAS,SAAS,SAAS,MAAM;CACrD,eAAe,aACb,SAAS,WAAW,IAAK,SAAS,MAAM,KAAM,GAAG,SAAS,KAAK,SAAS,SAAS,SAAS;CAC5F,WAAW,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG;CACrD,aAAa,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG;CACvD,cAAc,aAAa,SAAS,KAAK,GAAG;CAC7C;AAED,SAAS,2BACP,MACA,WAAuC,gBAC/B;CACR,IAAI,CAAC,MACH,OAAO;CAGT,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,KAAK;CAIvB,MAAM,WADQ,KAAK,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QACxB,CAAC,KAAK,SAAS,KAAK,IAAI,aAAa,IAAI,GAAG;CAElE,OAAO,uBAAuB,UAAU,SAAS;;AAGnD,SAAS,yBAAyB,YAA4C;CAC5E,OAAO,eAAe,KAAA,IAAY,KAAA,IAAY;EAAE,OAAO;EAAY,QAAQ;EAAY;;AAGzF,SAAS,qBAAqB,SAAsC;CAClE,OAAO,oBAAoB"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/components/Avatar/utils.ts"],"sourcesContent":["import type { AvatarAbbreviationStrategy, AvatarSize, AvatarVariant } from '@yahoo/uds-types';\nimport type { ViewStyle } from 'react-native';\n\nimport type { IconSlotType } from '../IconSlot';\n\nconst DEFAULT_AVATAR_SIZE: AvatarSize = 'md';\nconst DEFAULT_AVATAR_VARIANT: AvatarVariant = 'primary';\n\nconst DEFAULT_AVATAR_ICON: Record<AvatarVariant, IconSlotType> = {\n primary: 'Person',\n secondary: 'Building',\n};\n\nconst abbreviationStrategies: Record<\n Exclude<AvatarAbbreviationStrategy, (name: string) => string>,\n (initials: string[]) => string\n> = {\n first: (initials) => initials[0] ?? '',\n last: (initials) => initials[initials.length - 1] ?? '',\n firstAndLast: (initials) =>\n initials.length === 1 ? (initials[0] ?? '') : `${initials[0]}${initials[initials.length - 1]}`,\n firstTwo: (initials) => initials.slice(0, 2).join(''),\n firstThree: (initials) => initials.slice(0, 3).join(''),\n firstOfEach: (initials) => initials.join(''),\n};\n\nfunction generateAvatarAbbreviation(\n name?: string,\n strategy: AvatarAbbreviationStrategy = 'firstAndLast',\n): string {\n if (!name) {\n return '';\n }\n\n if (typeof strategy === 'function') {\n return strategy(name);\n }\n\n const words = name.trim().split(/\\s+/).filter(Boolean);\n const initials = words.map((word) => word[0]?.toUpperCase() ?? '');\n\n return abbreviationStrategies[strategy](initials);\n}\n\nfunction getAvatarSizeStyle(\n rootStyle: ViewStyle,\n customSize: number | undefined,\n fontScaleFactor: number,\n): ViewStyle | undefined {\n if (customSize === undefined && fontScaleFactor === 1) {\n return undefined;\n }\n\n const width = customSize ?? rootStyle.width;\n const height = customSize ?? rootStyle.height;\n\n return typeof width === 'number' && typeof height === 'number'\n ? {\n width: Math.round(width * fontScaleFactor),\n height: Math.round(height * fontScaleFactor),\n }\n : undefined;\n}\n\nfunction getDefaultAvatarIcon(variant: AvatarVariant): IconSlotType {\n return DEFAULT_AVATAR_ICON[variant];\n}\n\nexport {\n DEFAULT_AVATAR_SIZE,\n DEFAULT_AVATAR_VARIANT,\n generateAvatarAbbreviation,\n getAvatarSizeStyle,\n getDefaultAvatarIcon,\n};\n"],"mappings":";;AAKA,MAAM,sBAAkC;AACxC,MAAM,yBAAwC;AAE9C,MAAM,sBAA2D;CAC/D,SAAS;CACT,WAAW;CACZ;AAED,MAAM,yBAGF;CACF,QAAQ,aAAa,SAAS,MAAM;CACpC,OAAO,aAAa,SAAS,SAAS,SAAS,MAAM;CACrD,eAAe,aACb,SAAS,WAAW,IAAK,SAAS,MAAM,KAAM,GAAG,SAAS,KAAK,SAAS,SAAS,SAAS;CAC5F,WAAW,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG;CACrD,aAAa,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG;CACvD,cAAc,aAAa,SAAS,KAAK,GAAG;CAC7C;AAED,SAAS,2BACP,MACA,WAAuC,gBAC/B;CACR,IAAI,CAAC,MACH,OAAO;CAGT,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,KAAK;CAIvB,MAAM,WADQ,KAAK,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QACxB,CAAC,KAAK,SAAS,KAAK,IAAI,aAAa,IAAI,GAAG;CAElE,OAAO,uBAAuB,UAAU,SAAS;;AAGnD,SAAS,mBACP,WACA,YACA,iBACuB;CACvB,IAAI,eAAe,KAAA,KAAa,oBAAoB,GAClD;CAGF,MAAM,QAAQ,cAAc,UAAU;CACtC,MAAM,SAAS,cAAc,UAAU;CAEvC,OAAO,OAAO,UAAU,YAAY,OAAO,WAAW,WAClD;EACE,OAAO,KAAK,MAAM,QAAQ,gBAAgB;EAC1C,QAAQ,KAAK,MAAM,SAAS,gBAAgB;EAC7C,GACD,KAAA;;AAGN,SAAS,qBAAqB,SAAsC;CAClE,OAAO,oBAAoB"}
@@ -22,6 +22,10 @@ function getLayerStyle(theme, path) {
22
22
  function getIconSize(iconStyle) {
23
23
  return (typeof iconStyle.fontSize === "number" ? iconStyle.fontSize : void 0) ?? (typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : void 0) ?? 16;
24
24
  }
25
+ /** Base icon size for an IconButton size layer. */
26
+ function getIconButtonIconSize(theme, size) {
27
+ return getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
28
+ }
25
29
  function getVerticalPadding(rootStyle) {
26
30
  return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
27
31
  }
@@ -47,10 +51,11 @@ function getSafeIconCell(fontSize) {
47
51
  /**
48
52
  * Safe native content metrics while retaining the token-derived target control height.
49
53
  *
50
- * `fontScaleFactor` (the capped OS font scale, >= 1) pre-scales container
51
- * metrics only. The returned `labelLineHeight`/`iconCellHeight` stay unscaled
52
- * because they go into Text/Icon styles, which native multiplies by the same
53
- * capped factor container math must mirror that, not double-apply it.
54
+ * `fontScaleFactor` pre-scales container metrics only. A single number keeps
55
+ * the legacy linear behavior; distinct label/icon factors let Android 14+
56
+ * geometry follow its size-dependent nonlinear font curve. The returned
57
+ * `labelLineHeight`/`iconCellHeight` stay unscaled because native Text applies
58
+ * the same cap.
54
59
  */
55
60
  function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
56
61
  const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
@@ -60,11 +65,14 @@ function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
60
65
  const iconSize = getIconSize(iconStyle);
61
66
  const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
62
67
  const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
63
- const targetContentHeight = Math.max(iconSize, tokenLineHeight) * fontScaleFactor;
68
+ const labelScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.label;
69
+ const iconScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.icon;
70
+ const iconCellScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.iconCell ?? fontScaleFactor.icon;
71
+ const targetContentHeight = Math.max(iconSize * iconScaleFactor, tokenLineHeight * labelScaleFactor);
64
72
  const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);
65
73
  const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);
66
74
  const iconCellHeight = getSafeIconCell(iconSize);
67
- const safeContentHeight = Math.max(labelLineHeight, iconCellHeight) * fontScaleFactor;
75
+ const safeContentHeight = Math.max(labelLineHeight * labelScaleFactor, iconCellHeight * iconCellScaleFactor);
68
76
  return {
69
77
  minHeight,
70
78
  labelLineHeight,
@@ -81,7 +89,7 @@ function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
81
89
  function getIconButtonControlMetrics(theme, size, fontScaleFactor = 1) {
82
90
  const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
83
91
  const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, "icon"));
84
- const iconSize = getIconSize(iconStyle);
92
+ const iconSize = getIconButtonIconSize(theme, size);
85
93
  const tokenLineHeight = typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : iconSize;
86
94
  const paddingVertical = getVerticalPadding(rootStyle);
87
95
  const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
@@ -106,4 +114,5 @@ function getButtonTextStyle(textStyle, labelLineHeight) {
106
114
  exports.getButtonControlMetrics = getButtonControlMetrics;
107
115
  exports.getButtonTextStyle = getButtonTextStyle;
108
116
  exports.getIconButtonControlMetrics = getIconButtonControlMetrics;
117
+ exports.getIconButtonIconSize = getIconButtonIconSize;
109
118
  exports.getSafeIconCell = getSafeIconCell;
@@ -6,17 +6,24 @@ import { TextStyle } from "react-native";
6
6
  type ButtonTheme = {
7
7
  components: Record<string, any>;
8
8
  };
9
+ /** Base icon size for an IconButton size layer. */
10
+ declare function getIconButtonIconSize(theme: ButtonTheme, size: IconButtonSize): number;
9
11
  /** Safe line box for the icon font's em square plus any bbox overflow. */
10
12
  declare function getSafeIconCell(fontSize: number): number;
11
13
  /**
12
14
  * Safe native content metrics while retaining the token-derived target control height.
13
15
  *
14
- * `fontScaleFactor` (the capped OS font scale, >= 1) pre-scales container
15
- * metrics only. The returned `labelLineHeight`/`iconCellHeight` stay unscaled
16
- * because they go into Text/Icon styles, which native multiplies by the same
17
- * capped factor container math must mirror that, not double-apply it.
16
+ * `fontScaleFactor` pre-scales container metrics only. A single number keeps
17
+ * the legacy linear behavior; distinct label/icon factors let Android 14+
18
+ * geometry follow its size-dependent nonlinear font curve. The returned
19
+ * `labelLineHeight`/`iconCellHeight` stay unscaled because native Text applies
20
+ * the same cap.
18
21
  */
19
- declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize, fontScaleFactor?: number): {
22
+ declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize, fontScaleFactor?: number | {
23
+ label: number;
24
+ icon: number;
25
+ iconCell?: number;
26
+ }): {
20
27
  minHeight: number;
21
28
  labelLineHeight: number;
22
29
  iconCellHeight: number;
@@ -37,5 +44,5 @@ declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButto
37
44
  /** Apply the metric-safe line height after the token typography styles. */
38
45
  declare function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle;
39
46
  //#endregion
40
- export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
47
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getIconButtonIconSize, getSafeIconCell };
41
48
  //# sourceMappingURL=buttonTheme.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.d.cts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA4DE,eAAA,CAAgB,QAAA;;;AAAhC;;;;;AAwBA;iBAAgB,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA,EACN,eAAA;EAEA,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;;;;;;;iBA8Bc,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA,EACN,eAAA;EAEA,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBA4Bc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
1
+ {"version":3,"file":"buttonTheme.d.cts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA6BL,qBAAA,CAAsB,KAAA,EAAO,WAAA,EAAa,IAAA,EAAM,cAAA;;iBAoCzC,eAAA,CAAgB,QAAA;AAjEZ;;;;;;;;;AAAA,iBA0FJ,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA,EACN,eAAA;EAA4B,KAAA;EAAe,IAAA;EAAc,QAAA;AAAA;EAEzD,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;;;;;;;iBA4Cc,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA,EACN,eAAA;EAEA,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBA4Bc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
@@ -6,17 +6,24 @@ import { TextStyle } from "react-native";
6
6
  type ButtonTheme = {
7
7
  components: Record<string, any>;
8
8
  };
9
+ /** Base icon size for an IconButton size layer. */
10
+ declare function getIconButtonIconSize(theme: ButtonTheme, size: IconButtonSize): number;
9
11
  /** Safe line box for the icon font's em square plus any bbox overflow. */
10
12
  declare function getSafeIconCell(fontSize: number): number;
11
13
  /**
12
14
  * Safe native content metrics while retaining the token-derived target control height.
13
15
  *
14
- * `fontScaleFactor` (the capped OS font scale, >= 1) pre-scales container
15
- * metrics only. The returned `labelLineHeight`/`iconCellHeight` stay unscaled
16
- * because they go into Text/Icon styles, which native multiplies by the same
17
- * capped factor container math must mirror that, not double-apply it.
16
+ * `fontScaleFactor` pre-scales container metrics only. A single number keeps
17
+ * the legacy linear behavior; distinct label/icon factors let Android 14+
18
+ * geometry follow its size-dependent nonlinear font curve. The returned
19
+ * `labelLineHeight`/`iconCellHeight` stay unscaled because native Text applies
20
+ * the same cap.
18
21
  */
19
- declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize, fontScaleFactor?: number): {
22
+ declare function getButtonControlMetrics(theme: ButtonTheme, size: ButtonSize, fontScaleFactor?: number | {
23
+ label: number;
24
+ icon: number;
25
+ iconCell?: number;
26
+ }): {
20
27
  minHeight: number;
21
28
  labelLineHeight: number;
22
29
  iconCellHeight: number;
@@ -37,5 +44,5 @@ declare function getIconButtonControlMetrics(theme: ButtonTheme, size: IconButto
37
44
  /** Apply the metric-safe line height after the token typography styles. */
38
45
  declare function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle;
39
46
  //#endregion
40
- export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
47
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getIconButtonIconSize, getSafeIconCell };
41
48
  //# sourceMappingURL=buttonTheme.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.d.ts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA4DE,eAAA,CAAgB,QAAA;;;AAAhC;;;;;AAwBA;iBAAgB,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA,EACN,eAAA;EAEA,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;;;;;;;iBA8Bc,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA,EACN,eAAA;EAEA,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBA4Bc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
1
+ {"version":3,"file":"buttonTheme.d.ts","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"mappings":";;;;;KAKK,WAAA;EAEH,UAAA,EAAY,MAAA;AAAA;;iBA6BL,qBAAA,CAAsB,KAAA,EAAO,WAAA,EAAa,IAAA,EAAM,cAAA;;iBAoCzC,eAAA,CAAgB,QAAA;AAjEZ;;;;;;;;;AAAA,iBA0FJ,uBAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,UAAA,EACN,eAAA;EAA4B,KAAA;EAAe,IAAA;EAAc,QAAA;AAAA;EAEzD,SAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;AAAA;;;;;;;iBA4Cc,2BAAA,CACd,KAAA,EAAO,WAAA,EACP,IAAA,EAAM,cAAA,EACN,eAAA;EAEA,SAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;AAAA;;iBA4Bc,kBAAA,CAAmB,SAAA,EAAW,SAAA,EAAW,eAAA,WAA0B,SAAA"}
@@ -21,6 +21,10 @@ function getLayerStyle(theme, path) {
21
21
  function getIconSize(iconStyle) {
22
22
  return (typeof iconStyle.fontSize === "number" ? iconStyle.fontSize : void 0) ?? (typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : void 0) ?? 16;
23
23
  }
24
+ /** Base icon size for an IconButton size layer. */
25
+ function getIconButtonIconSize(theme, size) {
26
+ return getIconSize(getLayerStyle(theme, iconButtonSizePath(size, "icon")));
27
+ }
24
28
  function getVerticalPadding(rootStyle) {
25
29
  return (typeof rootStyle.paddingVertical === "number" ? rootStyle.paddingVertical : void 0) ?? (typeof rootStyle.padding === "number" ? rootStyle.padding : void 0) ?? 0;
26
30
  }
@@ -46,10 +50,11 @@ function getSafeIconCell(fontSize) {
46
50
  /**
47
51
  * Safe native content metrics while retaining the token-derived target control height.
48
52
  *
49
- * `fontScaleFactor` (the capped OS font scale, >= 1) pre-scales container
50
- * metrics only. The returned `labelLineHeight`/`iconCellHeight` stay unscaled
51
- * because they go into Text/Icon styles, which native multiplies by the same
52
- * capped factor container math must mirror that, not double-apply it.
53
+ * `fontScaleFactor` pre-scales container metrics only. A single number keeps
54
+ * the legacy linear behavior; distinct label/icon factors let Android 14+
55
+ * geometry follow its size-dependent nonlinear font curve. The returned
56
+ * `labelLineHeight`/`iconCellHeight` stay unscaled because native Text applies
57
+ * the same cap.
53
58
  */
54
59
  function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
55
60
  const rootStyle = getLayerStyle(theme, buttonSizePath(size, "root"));
@@ -59,11 +64,14 @@ function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
59
64
  const iconSize = getIconSize(iconStyle);
60
65
  const fontSize = typeof textStyle.fontSize === "number" ? textStyle.fontSize : iconSize;
61
66
  const tokenLineHeight = typeof textStyle.lineHeight === "number" ? textStyle.lineHeight : fontSize;
62
- const targetContentHeight = Math.max(iconSize, tokenLineHeight) * fontScaleFactor;
67
+ const labelScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.label;
68
+ const iconScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.icon;
69
+ const iconCellScaleFactor = typeof fontScaleFactor === "number" ? fontScaleFactor : fontScaleFactor.iconCell ?? fontScaleFactor.icon;
70
+ const targetContentHeight = Math.max(iconSize * iconScaleFactor, tokenLineHeight * labelScaleFactor);
63
71
  const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);
64
72
  const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);
65
73
  const iconCellHeight = getSafeIconCell(iconSize);
66
- const safeContentHeight = Math.max(labelLineHeight, iconCellHeight) * fontScaleFactor;
74
+ const safeContentHeight = Math.max(labelLineHeight * labelScaleFactor, iconCellHeight * iconCellScaleFactor);
67
75
  return {
68
76
  minHeight,
69
77
  labelLineHeight,
@@ -80,7 +88,7 @@ function getButtonControlMetrics(theme, size, fontScaleFactor = 1) {
80
88
  function getIconButtonControlMetrics(theme, size, fontScaleFactor = 1) {
81
89
  const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, "root"));
82
90
  const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, "icon"));
83
- const iconSize = getIconSize(iconStyle);
91
+ const iconSize = getIconButtonIconSize(theme, size);
84
92
  const tokenLineHeight = typeof iconStyle.lineHeight === "number" ? iconStyle.lineHeight : iconSize;
85
93
  const paddingVertical = getVerticalPadding(rootStyle);
86
94
  const explicitHeight = typeof rootStyle.height === "number" ? rootStyle.height : void 0;
@@ -102,6 +110,6 @@ function getButtonTextStyle(textStyle, labelLineHeight) {
102
110
  };
103
111
  }
104
112
  //#endregion
105
- export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getSafeIconCell };
113
+ export { getButtonControlMetrics, getButtonTextStyle, getIconButtonControlMetrics, getIconButtonIconSize, getSafeIconCell };
106
114
 
107
115
  //# sourceMappingURL=buttonTheme.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttonTheme.js","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"sourcesContent":["import type { ButtonSize, IconButtonSize } from '@yahoo/uds-types';\nimport type { TextStyle, ViewStyle } from 'react-native';\n\nimport { fontMetrics } from '../../../generated/fontMetrics';\n\ntype ButtonTheme = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: Record<string, any>;\n};\n\nconst SHARED_BUTTON_ICONBUTTON_SIZES = new Set<IconButtonSize>(['xs', 'sm', 'md', 'lg']);\n// Covers common native font boxes (roughly 1.2em) plus pixel-rounding headroom.\nconst UNKNOWN_FONT_LINE_HEIGHT_RATIO = 1.3;\n\nfunction buttonSizePath(size: ButtonSize, layer: 'root' | 'icon' | 'rootText'): string {\n return `button/size/${size}/${layer}/rest`;\n}\n\nfunction iconButtonSizePath(size: IconButtonSize, layer: 'root' | 'icon'): string {\n return `iconButton/size/${size}/${layer}/rest`;\n}\n\nfunction getLayerStyle(theme: ButtonTheme, path: string): ViewStyle | TextStyle {\n const style = theme.components[path];\n return style && typeof style === 'object' ? style : {};\n}\n\nfunction getIconSize(iconStyle: TextStyle): number {\n return (\n (typeof iconStyle.fontSize === 'number' ? iconStyle.fontSize : undefined) ??\n (typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : undefined) ??\n 16\n );\n}\n\nfunction getVerticalPadding(rootStyle: ViewStyle): number {\n return (\n (typeof rootStyle.paddingVertical === 'number' ? rootStyle.paddingVertical : undefined) ??\n (typeof rootStyle.padding === 'number' ? rootStyle.padding : undefined) ??\n 0\n );\n}\n\nfunction getMetricHeight(value: number, unitsPerEm: number, fontSize: number): number {\n return Math.ceil((Math.max(0, value) / unitsPerEm) * fontSize);\n}\n\nfunction getSafeLabelLineHeight(textStyle: TextStyle, fallbackFontSize: number): number {\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : fallbackFontSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const metrics =\n typeof textStyle.fontFamily === 'string' ? fontMetrics[textStyle.fontFamily] : undefined;\n\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.max(tokenLineHeight, Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO));\n }\n\n const metricsLineHeight =\n getMetricHeight(metrics.ascent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(-metrics.descent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(metrics.lineGap, metrics.unitsPerEm, fontSize);\n\n return Math.max(tokenLineHeight, metricsLineHeight);\n}\n\n/** Safe line box for the icon font's em square plus any bbox overflow. */\nexport function getSafeIconCell(fontSize: number): number {\n const metrics = fontMetrics['uds-icons'];\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO);\n }\n\n const overflowAboveEm = Math.max(0, metrics.bbox.maxY - metrics.unitsPerEm);\n const overflowBelowEm = Math.max(0, -metrics.bbox.minY);\n\n return (\n Math.ceil(fontSize) +\n getMetricHeight(overflowAboveEm, metrics.unitsPerEm, fontSize) +\n getMetricHeight(overflowBelowEm, metrics.unitsPerEm, fontSize)\n );\n}\n\n/**\n * Safe native content metrics while retaining the token-derived target control height.\n *\n * `fontScaleFactor` (the capped OS font scale, >= 1) pre-scales container\n * metrics only. The returned `labelLineHeight`/`iconCellHeight` stay unscaled\n * because they go into Text/Icon styles, which native multiplies by the same\n * capped factor — container math must mirror that, not double-apply it.\n */\nexport function getButtonControlMetrics(\n theme: ButtonTheme,\n size: ButtonSize,\n fontScaleFactor = 1,\n): {\n minHeight: number;\n labelLineHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n} {\n const rootStyle = getLayerStyle(theme, buttonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, buttonSizePath(size, 'icon')) as TextStyle;\n const textStyle = getLayerStyle(theme, buttonSizePath(size, 'rootText')) as TextStyle;\n const paddingVertical = getVerticalPadding(rootStyle);\n const iconSize = getIconSize(iconStyle);\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : iconSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const targetContentHeight = Math.max(iconSize, tokenLineHeight) * fontScaleFactor;\n const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);\n const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);\n const iconCellHeight = getSafeIconCell(iconSize);\n const safeContentHeight = Math.max(labelLineHeight, iconCellHeight) * fontScaleFactor;\n\n return {\n minHeight,\n labelLineHeight,\n iconCellHeight,\n paddingVertical: Math.max(0, (minHeight - safeContentHeight) / 2),\n };\n}\n\n/**\n * Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target.\n *\n * `fontScaleFactor` behaves as in {@link getButtonControlMetrics}; an explicit\n * token height stays fixed, with padding absorbing the scaled content.\n */\nexport function getIconButtonControlMetrics(\n theme: ButtonTheme,\n size: IconButtonSize,\n fontScaleFactor = 1,\n): {\n minHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n hasExplicitHeight: boolean;\n} {\n const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle;\n const iconSize = getIconSize(iconStyle);\n const tokenLineHeight =\n typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : iconSize;\n const paddingVertical = getVerticalPadding(rootStyle);\n const explicitHeight = typeof rootStyle.height === 'number' ? rootStyle.height : undefined;\n const hasExplicitHeight =\n SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== undefined;\n const minHeight = hasExplicitHeight\n ? (explicitHeight as number)\n : Math.ceil(paddingVertical * 2 + Math.max(iconSize, tokenLineHeight) * fontScaleFactor);\n const iconCellHeight = getSafeIconCell(iconSize);\n\n return {\n minHeight,\n iconCellHeight,\n paddingVertical: Math.max(\n 0,\n (minHeight - Math.max(iconSize, iconCellHeight) * fontScaleFactor) / 2,\n ),\n hasExplicitHeight,\n };\n}\n\n/** Apply the metric-safe line height after the token typography styles. */\nexport function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle {\n return {\n ...textStyle,\n lineHeight: labelLineHeight,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"buttonTheme.js","names":[],"sources":["../../../src/components/Button/buttonTheme.ts"],"sourcesContent":["import type { ButtonSize, IconButtonSize } from '@yahoo/uds-types';\nimport type { TextStyle, ViewStyle } from 'react-native';\n\nimport { fontMetrics } from '../../../generated/fontMetrics';\n\ntype ButtonTheme = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: Record<string, any>;\n};\n\nconst SHARED_BUTTON_ICONBUTTON_SIZES = new Set<IconButtonSize>(['xs', 'sm', 'md', 'lg']);\n// Covers common native font boxes (roughly 1.2em) plus pixel-rounding headroom.\nconst UNKNOWN_FONT_LINE_HEIGHT_RATIO = 1.3;\n\nfunction buttonSizePath(size: ButtonSize, layer: 'root' | 'icon' | 'rootText'): string {\n return `button/size/${size}/${layer}/rest`;\n}\n\nfunction iconButtonSizePath(size: IconButtonSize, layer: 'root' | 'icon'): string {\n return `iconButton/size/${size}/${layer}/rest`;\n}\n\nfunction getLayerStyle(theme: ButtonTheme, path: string): ViewStyle | TextStyle {\n const style = theme.components[path];\n return style && typeof style === 'object' ? style : {};\n}\n\nfunction getIconSize(iconStyle: TextStyle): number {\n return (\n (typeof iconStyle.fontSize === 'number' ? iconStyle.fontSize : undefined) ??\n (typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : undefined) ??\n 16\n );\n}\n\n/** Base icon size for an IconButton size layer. */\nfunction getIconButtonIconSize(theme: ButtonTheme, size: IconButtonSize): number {\n return getIconSize(getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle);\n}\n\nfunction getVerticalPadding(rootStyle: ViewStyle): number {\n return (\n (typeof rootStyle.paddingVertical === 'number' ? rootStyle.paddingVertical : undefined) ??\n (typeof rootStyle.padding === 'number' ? rootStyle.padding : undefined) ??\n 0\n );\n}\n\nfunction getMetricHeight(value: number, unitsPerEm: number, fontSize: number): number {\n return Math.ceil((Math.max(0, value) / unitsPerEm) * fontSize);\n}\n\nfunction getSafeLabelLineHeight(textStyle: TextStyle, fallbackFontSize: number): number {\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : fallbackFontSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const metrics =\n typeof textStyle.fontFamily === 'string' ? fontMetrics[textStyle.fontFamily] : undefined;\n\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.max(tokenLineHeight, Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO));\n }\n\n const metricsLineHeight =\n getMetricHeight(metrics.ascent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(-metrics.descent, metrics.unitsPerEm, fontSize) +\n getMetricHeight(metrics.lineGap, metrics.unitsPerEm, fontSize);\n\n return Math.max(tokenLineHeight, metricsLineHeight);\n}\n\n/** Safe line box for the icon font's em square plus any bbox overflow. */\nexport function getSafeIconCell(fontSize: number): number {\n const metrics = fontMetrics['uds-icons'];\n if (!metrics || metrics.unitsPerEm <= 0) {\n return Math.ceil(fontSize * UNKNOWN_FONT_LINE_HEIGHT_RATIO);\n }\n\n const overflowAboveEm = Math.max(0, metrics.bbox.maxY - metrics.unitsPerEm);\n const overflowBelowEm = Math.max(0, -metrics.bbox.minY);\n\n return (\n Math.ceil(fontSize) +\n getMetricHeight(overflowAboveEm, metrics.unitsPerEm, fontSize) +\n getMetricHeight(overflowBelowEm, metrics.unitsPerEm, fontSize)\n );\n}\n\n/**\n * Safe native content metrics while retaining the token-derived target control height.\n *\n * `fontScaleFactor` pre-scales container metrics only. A single number keeps\n * the legacy linear behavior; distinct label/icon factors let Android 14+\n * geometry follow its size-dependent nonlinear font curve. The returned\n * `labelLineHeight`/`iconCellHeight` stay unscaled because native Text applies\n * the same cap.\n */\nexport function getButtonControlMetrics(\n theme: ButtonTheme,\n size: ButtonSize,\n fontScaleFactor: number | { label: number; icon: number; iconCell?: number } = 1,\n): {\n minHeight: number;\n labelLineHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n} {\n const rootStyle = getLayerStyle(theme, buttonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, buttonSizePath(size, 'icon')) as TextStyle;\n const textStyle = getLayerStyle(theme, buttonSizePath(size, 'rootText')) as TextStyle;\n const paddingVertical = getVerticalPadding(rootStyle);\n const iconSize = getIconSize(iconStyle);\n const fontSize = typeof textStyle.fontSize === 'number' ? textStyle.fontSize : iconSize;\n const tokenLineHeight =\n typeof textStyle.lineHeight === 'number' ? textStyle.lineHeight : fontSize;\n const labelScaleFactor =\n typeof fontScaleFactor === 'number' ? fontScaleFactor : fontScaleFactor.label;\n const iconScaleFactor =\n typeof fontScaleFactor === 'number' ? fontScaleFactor : fontScaleFactor.icon;\n const iconCellScaleFactor =\n typeof fontScaleFactor === 'number'\n ? fontScaleFactor\n : (fontScaleFactor.iconCell ?? fontScaleFactor.icon);\n const targetContentHeight = Math.max(\n iconSize * iconScaleFactor,\n tokenLineHeight * labelScaleFactor,\n );\n const minHeight = Math.ceil(paddingVertical * 2 + targetContentHeight);\n const labelLineHeight = getSafeLabelLineHeight(textStyle, iconSize);\n const iconCellHeight = getSafeIconCell(iconSize);\n const safeContentHeight = Math.max(\n labelLineHeight * labelScaleFactor,\n iconCellHeight * iconCellScaleFactor,\n );\n\n return {\n minHeight,\n labelLineHeight,\n iconCellHeight,\n paddingVertical: Math.max(0, (minHeight - safeContentHeight) / 2),\n };\n}\n\n/**\n * Safe icon cell within the token-derived control size; explicit token height (match-button-height opt-in) wins as the target.\n *\n * `fontScaleFactor` behaves as in {@link getButtonControlMetrics}; an explicit\n * token height stays fixed, with padding absorbing the scaled content.\n */\nexport function getIconButtonControlMetrics(\n theme: ButtonTheme,\n size: IconButtonSize,\n fontScaleFactor = 1,\n): {\n minHeight: number;\n iconCellHeight: number;\n paddingVertical: number;\n hasExplicitHeight: boolean;\n} {\n const rootStyle = getLayerStyle(theme, iconButtonSizePath(size, 'root')) as ViewStyle;\n const iconStyle = getLayerStyle(theme, iconButtonSizePath(size, 'icon')) as TextStyle;\n const iconSize = getIconButtonIconSize(theme, size);\n const tokenLineHeight =\n typeof iconStyle.lineHeight === 'number' ? iconStyle.lineHeight : iconSize;\n const paddingVertical = getVerticalPadding(rootStyle);\n const explicitHeight = typeof rootStyle.height === 'number' ? rootStyle.height : undefined;\n const hasExplicitHeight =\n SHARED_BUTTON_ICONBUTTON_SIZES.has(size) && explicitHeight !== undefined;\n const minHeight = hasExplicitHeight\n ? (explicitHeight as number)\n : Math.ceil(paddingVertical * 2 + Math.max(iconSize, tokenLineHeight) * fontScaleFactor);\n const iconCellHeight = getSafeIconCell(iconSize);\n\n return {\n minHeight,\n iconCellHeight,\n paddingVertical: Math.max(\n 0,\n (minHeight - Math.max(iconSize, iconCellHeight) * fontScaleFactor) / 2,\n ),\n hasExplicitHeight,\n };\n}\n\n/** Apply the metric-safe line height after the token typography styles. */\nexport function getButtonTextStyle(textStyle: TextStyle, labelLineHeight: number): TextStyle {\n return {\n ...textStyle,\n lineHeight: labelLineHeight,\n };\n}\n\nexport { getIconButtonIconSize };\n"],"mappings":"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"}
@@ -3,11 +3,12 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
4
4
  const require_index = require("../motion-tokens/dist/index.cjs");
5
5
  const require_motion = require("../motion.cjs");
6
+ const require_fontScaling_FontScalingContext = require("../fontScaling/FontScalingContext.cjs");
7
+ const require_fontScaling_useFontScale = require("../fontScaling/useFontScale.cjs");
6
8
  const require_fontScaling_useMaxFontSizeMultiplier = require("../fontScaling/useMaxFontSizeMultiplier.cjs");
7
9
  const require_components_Button_buttonTheme = require("./Button/buttonTheme.cjs");
8
10
  const require_components_IconSlot = require("./IconSlot.cjs");
9
11
  const require_components_Text = require("./Text.cjs");
10
- const require_fontScaling_useFontScale = require("../fontScaling/useFontScale.cjs");
11
12
  const require_components_Pressable = require("./Pressable.cjs");
12
13
  let react = require("react");
13
14
  let react_native = require("react-native");
@@ -88,14 +89,10 @@ function AnimatedIconSlot({ children, visible, iconSize, iconCellHeight, gap })
88
89
  */
89
90
  const Button = (0, react.memo)(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, maxFontSizeMultiplier, onPressIn, onPressOut, ref, ...props }) {
90
91
  const shouldAnimate = !disableEffects;
91
- const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
92
- const fontScaleFactor = require_fontScaling_useFontScale.useFontScale(resolvedMaxFontSizeMultiplier);
92
+ const resolvedMaxFontSizeMultiplier = require_fontScaling_useMaxFontSizeMultiplier.useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
93
93
  const { theme } = (0, react_native_unistyles.useUnistyles)();
94
- const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size, fontScaleFactor), [
95
- theme,
96
- size,
97
- fontScaleFactor
98
- ]);
94
+ const baseMetrics = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size), [theme, size]);
95
+ const labelScaleFactor = require_fontScaling_useFontScale.useComponentFontScale(resolvedMaxFontSizeMultiplier, baseMetrics.labelLineHeight);
99
96
  const [pressed, setPressed] = (0, react.useState)(false);
100
97
  generated_styles.buttonStyles.useVariants({
101
98
  size,
@@ -105,6 +102,19 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
105
102
  });
106
103
  const buttonGap = generated_styles.buttonStyles.root.gap;
107
104
  const iconSize = generated_styles.buttonStyles.icon.fontSize;
105
+ const iconScaleFactor = require_fontScaling_useFontScale.useComponentFontScale(resolvedMaxFontSizeMultiplier, iconSize);
106
+ const iconCellScaleFactor = require_fontScaling_useFontScale.useComponentFontScale(resolvedMaxFontSizeMultiplier, baseMetrics.iconCellHeight);
107
+ const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = (0, react.useMemo)(() => require_components_Button_buttonTheme.getButtonControlMetrics(theme, size, {
108
+ label: labelScaleFactor,
109
+ icon: iconScaleFactor,
110
+ iconCell: iconCellScaleFactor
111
+ }), [
112
+ theme,
113
+ size,
114
+ labelScaleFactor,
115
+ iconScaleFactor,
116
+ iconCellScaleFactor
117
+ ]);
108
118
  const animatedTheme = (0, react_native_unistyles_reanimated.useAnimatedTheme)();
109
119
  const scale = (0, react_native_reanimated.useSharedValue)(require_index.SCALE_EFFECTS.none);
110
120
  const handlePressIn = (0, react.useCallback)((event) => {
@@ -125,7 +135,11 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
125
135
  scale,
126
136
  onPressOut
127
137
  ]);
128
- const childrenNode = children && ((0, react.isValidElement)(children) ? children : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
138
+ const childrenNode = children && ((0, react.isValidElement)(children) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_fontScaling_FontScalingContext.ResolvedControlFontScalingProvider, {
139
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
140
+ children
141
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_Text.Text, {
142
+ variant: "inherit",
129
143
  numberOfLines: 1,
130
144
  textAlign: "center",
131
145
  maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
@@ -151,8 +165,8 @@ const Button = (0, react.memo)(function Button({ variant = "primary", size = "md
151
165
  const showStartIcon = !!startIcon && !loading;
152
166
  const showEndIcon = !!endIcon && !loading;
153
167
  const iconSizeToken = generated_styles.buttonStyles.icon.iconSizeToken ?? "sm";
154
- const scaledIconSize = Math.round(iconSize * fontScaleFactor);
155
- const scaledIconCellHeight = Math.ceil(iconCellHeight * fontScaleFactor);
168
+ const scaledIconSize = Math.round(iconSize * iconScaleFactor);
169
+ const scaledIconCellHeight = Math.ceil(iconCellHeight * iconCellScaleFactor);
156
170
  const startContent = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnimatedIconSlot, {
157
171
  visible: showLoading || showStartIcon,
158
172
  iconSize: scaledIconSize,
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAuHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAkBU;EAhBV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;;;;;;EAwBA,qBAAA;EAlBU;EAoBV,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Button.d.cts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAwHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAkBU;EAhBV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;;;;;;EAwBA,qBAAA;EAlBU;EAoBV,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAuHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAkBU;EAhBV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;;;;;;EAwBA,qBAAA;EAlBU;EAoBV,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
1
+ {"version":3,"file":"Button.d.ts","names":[],"sources":["../../src/components/Button.tsx"],"mappings":";;;;;;;;;UAwHU,WAAA,SAAoB,IAAA,CAAK,gBAAA;;EAEjC,OAAA,GAAU,iBAAA;EAFF;EAIR,IAAA,GAAO,UAAA;;EAEP,WAAA,GAAc,WAAA;EAJJ;EAMV,SAAA,GAAY,YAAA;EAFE;EAId,OAAA,GAAU,YAAA;EAAA;EAEV,OAAA;EAkBU;EAhBV,QAAA;EAd4B;EAgB5B,QAAA,GAAW,KAAA,CAAM,SAAA;EAhBe;;;;EAqBhC,cAAA;EAjBA;;;;;;EAwBA,qBAAA;EAlBU;EAoBV,GAAA,GAAM,GAAA,CAAI,IAAA;AAAA;;;;;;;;;;;AAAI;;;;;;;;;;;;;;;;;;;;;;cAuCV,MAAA,EAAM,OAAA,CAAA,oBAAA,CAAA,WAAA"}
@@ -1,11 +1,12 @@
1
1
  /*! © 2026 Yahoo, Inc. UDS Mobile v0.0.0-development */
2
2
  import { SCALE_EFFECTS } from "../motion-tokens/dist/index.js";
3
3
  import { BUTTON_SPRING_CONFIG } from "../motion.js";
4
+ import { ResolvedControlFontScalingProvider } from "../fontScaling/FontScalingContext.js";
5
+ import { useComponentFontScale } from "../fontScaling/useFontScale.js";
4
6
  import { useMaxFontSizeMultiplier } from "../fontScaling/useMaxFontSizeMultiplier.js";
5
7
  import { getButtonControlMetrics, getButtonTextStyle } from "./Button/buttonTheme.js";
6
8
  import { IconSlot } from "./IconSlot.js";
7
9
  import { Text as Text$1 } from "./Text.js";
8
- import { useFontScale } from "../fontScaling/useFontScale.js";
9
10
  import { AnimatedPressable } from "./Pressable.js";
10
11
  import { isValidElement, memo, useCallback, useMemo, useState } from "react";
11
12
  import { ActivityIndicator } from "react-native";
@@ -85,14 +86,10 @@ function AnimatedIconSlot({ children, visible, iconSize, iconCellHeight, gap })
85
86
  */
86
87
  const Button = memo(function Button({ variant = "primary", size = "md", iconVariant = "outline", startIcon, endIcon, loading, disabled, width: _width, children, style, accessibilityLabel, accessibilityHint, disableEffects = false, maxFontSizeMultiplier, onPressIn, onPressOut, ref, ...props }) {
87
88
  const shouldAnimate = !disableEffects;
88
- const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier) ?? 0;
89
- const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);
89
+ const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier("control", maxFontSizeMultiplier);
90
90
  const { theme } = useUnistyles();
91
- const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = useMemo(() => getButtonControlMetrics(theme, size, fontScaleFactor), [
92
- theme,
93
- size,
94
- fontScaleFactor
95
- ]);
91
+ const baseMetrics = useMemo(() => getButtonControlMetrics(theme, size), [theme, size]);
92
+ const labelScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, baseMetrics.labelLineHeight);
96
93
  const [pressed, setPressed] = useState(false);
97
94
  buttonStyles.useVariants({
98
95
  size,
@@ -102,6 +99,19 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
102
99
  });
103
100
  const buttonGap = buttonStyles.root.gap;
104
101
  const iconSize = buttonStyles.icon.fontSize;
102
+ const iconScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, iconSize);
103
+ const iconCellScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, baseMetrics.iconCellHeight);
104
+ const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = useMemo(() => getButtonControlMetrics(theme, size, {
105
+ label: labelScaleFactor,
106
+ icon: iconScaleFactor,
107
+ iconCell: iconCellScaleFactor
108
+ }), [
109
+ theme,
110
+ size,
111
+ labelScaleFactor,
112
+ iconScaleFactor,
113
+ iconCellScaleFactor
114
+ ]);
105
115
  const animatedTheme = useAnimatedTheme();
106
116
  const scale = useSharedValue(SCALE_EFFECTS.none);
107
117
  const handlePressIn = useCallback((event) => {
@@ -122,7 +132,11 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
122
132
  scale,
123
133
  onPressOut
124
134
  ]);
125
- const childrenNode = children && (isValidElement(children) ? children : /* @__PURE__ */ jsx(Text$1, {
135
+ const childrenNode = children && (isValidElement(children) ? /* @__PURE__ */ jsx(ResolvedControlFontScalingProvider, {
136
+ maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
137
+ children
138
+ }) : /* @__PURE__ */ jsx(Text$1, {
139
+ variant: "inherit",
126
140
  numberOfLines: 1,
127
141
  textAlign: "center",
128
142
  maxFontSizeMultiplier: resolvedMaxFontSizeMultiplier,
@@ -148,8 +162,8 @@ const Button = memo(function Button({ variant = "primary", size = "md", iconVari
148
162
  const showStartIcon = !!startIcon && !loading;
149
163
  const showEndIcon = !!endIcon && !loading;
150
164
  const iconSizeToken = buttonStyles.icon.iconSizeToken ?? "sm";
151
- const scaledIconSize = Math.round(iconSize * fontScaleFactor);
152
- const scaledIconCellHeight = Math.ceil(iconCellHeight * fontScaleFactor);
165
+ const scaledIconSize = Math.round(iconSize * iconScaleFactor);
166
+ const scaledIconCellHeight = Math.ceil(iconCellHeight * iconCellScaleFactor);
153
167
  const startContent = /* @__PURE__ */ jsx(AnimatedIconSlot, {
154
168
  visible: showLoading || showStartIcon,
155
169
  iconSize: scaledIconSize,
@@ -1 +1 @@
1
- {"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- button control height from theme size layers\nimport { useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { useFontScale } from '../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../fontScaling/useMaxFontSizeMultiplier';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport { getButtonControlMetrics, getButtonTextStyle } from './Button/buttonTheme';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\n */\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n iconCellHeight,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n iconCellHeight: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n minHeight: iconCellHeight,\n justifyContent: 'center' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /**\n * Caps how far the label and icons grow with the OS text-size setting; the\n * button grows to fit the scaled content. Set null to remove the cap;\n * override app-wide via UDSFontScalingProvider.\n * @default 2\n */\n maxFontSizeMultiplier?: number | null;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile/Button';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n maxFontSizeMultiplier,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n // Inside a control, \"no cap resolved\" must force-uncap (0) rather than let\n // the label's own variant default or an inherited cap apply.\n const resolvedMaxFontSizeMultiplier =\n useMaxFontSizeMultiplier('control', maxFontSizeMultiplier) ?? 0;\n const fontScaleFactor = useFontScale(resolvedMaxFontSizeMultiplier);\n\n const { theme } = useUnistyles();\n const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = useMemo(\n () => getButtonControlMetrics(theme, size, fontScaleFactor),\n [theme, size, fontScaleFactor],\n );\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n children\n ) : (\n <Text\n numberOfLines={1}\n textAlign=\"center\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={getButtonTextStyle(buttonStyles.text, labelLineHeight)}\n >\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Containers pre-scale by the capped factor; icon styles stay unscaled and\n // native grows them by the same capped multiplier.\n const scaledIconSize = Math.round(iconSize * fontScaleFactor);\n const scaledIconCellHeight = Math.ceil(iconCellHeight * fontScaleFactor);\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot\n visible={showLoading || showStartIcon}\n iconSize={scaledIconSize}\n iconCellHeight={scaledIconCellHeight}\n gap={buttonGap}\n >\n {showLoading ? (\n <ActivityIndicator size={scaledIconSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={[buttonStyles.icon, { lineHeight: iconCellHeight }]}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot\n visible={showEndIcon}\n iconSize={scaledIconSize}\n iconCellHeight={scaledIconCellHeight}\n gap={buttonGap}\n >\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={[buttonStyles.icon, { lineHeight: iconCellHeight }]}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n { minHeight, paddingVertical },\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, minHeight, paddingVertical, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":"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"}
1
+ {"version":3,"file":"Button.js","names":["Text"],"sources":["../../src/components/Button.tsx"],"sourcesContent":["import type { ButtonSize, ButtonVariantFlat, IconSize, IconVariant } from '@yahoo/uds-types';\nimport type { Ref } from 'react';\nimport { isValidElement, memo, useCallback, useMemo, useState } from 'react';\nimport type { View } from 'react-native';\nimport { ActivityIndicator } from 'react-native';\nimport Animated, {\n Easing,\n interpolate,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n withTiming,\n} from 'react-native-reanimated';\n// eslint-disable-next-line uds/no-use-unistyles -- button control height from theme size layers\nimport { useUnistyles } from 'react-native-unistyles';\nimport { useAnimatedTheme } from 'react-native-unistyles/reanimated';\n\nimport { buttonStyles } from '../../generated/styles';\nimport { ResolvedControlFontScalingProvider } from '../fontScaling/FontScalingContext';\nimport { useComponentFontScale } from '../fontScaling/useFontScale';\nimport { useMaxFontSizeMultiplier } from '../fontScaling/useMaxFontSizeMultiplier';\nimport { BUTTON_SPRING_CONFIG, SCALE_EFFECTS } from '../motion';\nimport { getButtonControlMetrics, getButtonTextStyle } from './Button/buttonTheme';\nimport type { IconSlotType } from './IconSlot';\nimport { IconSlot } from './IconSlot';\nimport type { PressableProps } from './Pressable';\nimport { AnimatedPressable } from './Pressable';\nimport { Text } from './Text';\n\n/* -------------------------------------------------------------------------- */\n/* Animation Helpers */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Interpolates a boxShadow string by scaling the alpha of all colors.\n * This allows smooth fade-in/out of shadows.\n */\nfunction interpolateShadowAlpha(shadow: string | undefined, alpha: number): string {\n 'worklet';\n if (!shadow) {\n return '';\n }\n if (alpha >= 1) {\n return shadow;\n }\n if (alpha <= 0) {\n return '';\n }\n\n return shadow.replace(/rgba\\(([^,]+),\\s*([^,]+),\\s*([^,]+),\\s*([^)]+)\\)/g, (_, r, g, b, a) => {\n const newAlpha = parseFloat(a) * alpha;\n return `rgba(${r}, ${g}, ${b}, ${newAlpha.toFixed(3)})`;\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* Animated Icon Slot */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Animated wrapper for icon/loading content.\n * Matches web Button icon animation: scale 0.7→1, opacity 0→1, width 0→auto\n * Uses staggered animation: opacity waits until halfway through width animation.\n */\nfunction AnimatedIconSlot({\n children,\n visible,\n iconSize,\n iconCellHeight,\n gap,\n}: {\n children: React.ReactNode;\n visible: boolean;\n iconSize: number;\n iconCellHeight: number;\n gap: number;\n}) {\n // Use useDerivedValue instead of useEffect + useSharedValue\n // This is the idiomatic Reanimated pattern for deriving animated values from React state\n const progress = useDerivedValue(\n () => withSpring(visible ? 1 : 0, BUTTON_SPRING_CONFIG),\n [visible],\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n // Total width includes icon + gap when visible\n const totalWidth = iconSize + gap;\n const width = interpolate(progress.value, [0, 1], [0, totalWidth]);\n\n // Staggered animation: opacity starts at 50% of width animation\n // On enter: width expands first, then icon fades in\n // On exit: icon fades out first, then width collapses\n const opacity = interpolate(progress.value, [0.5, 1], [0, 1], 'clamp');\n const scale = interpolate(progress.value, [0.5, 1], [0.7, 1], 'clamp');\n\n return {\n width,\n opacity,\n transform: [{ scale }],\n overflow: 'hidden' as const,\n minHeight: iconCellHeight,\n justifyContent: 'center' as const,\n };\n });\n\n return <Animated.View style={animatedStyle}>{children}</Animated.View>;\n}\n\n// function LoadingIcon({ size, variant }: { size: ButtonSize, variant: ButtonVariantFlat }) {\n// const { theme } = useUnistyles();\n// const themeKey = `buttonVariant${variantToCapitalMap[variant]}IconRest` as const;\n// const iconSize = theme.components.buttonSizeLgIconRest.fontSize;\n// return <ActivityIndicator size={iconSize} color={theme.colors.text.primary} />;\n// }\n\n/* -------------------------------------------------------------------------- */\n/* Button Props */\n/* -------------------------------------------------------------------------- */\n\ninterface ButtonProps extends Omit<PressableProps, 'children' | 'disabled'> {\n /** The visual style variant of the button @default 'primary' */\n variant?: ButtonVariantFlat;\n /** The size of the button @default 'md' */\n size?: ButtonSize;\n /** The icon style variant @default 'outline' */\n iconVariant?: IconVariant;\n /** Icon displayed before the button label */\n startIcon?: IconSlotType;\n /** Icon displayed after the button label */\n endIcon?: IconSlotType;\n /** Shows a loading spinner and disables the button */\n loading?: boolean;\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Button label content */\n children?: React.ReactNode;\n /**\n * Disable motion effects (scale on press, icon animations)\n * @default false\n */\n disableEffects?: boolean;\n /**\n * Caps how far the label and icons grow with the OS text-size setting; the\n * button grows to fit the scaled content. Set null to remove the cap;\n * override app-wide via UDSFontScalingProvider.\n * @default 2\n */\n maxFontSizeMultiplier?: number | null;\n /** Ref to the underlying View */\n ref?: Ref<View>;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Button Component */\n/* -------------------------------------------------------------------------- */\n\n/**\n * **🖲️ A button element that can be used to trigger an action**\n *\n * @description\n * A button is a fundamental component used to trigger an action or event.\n * Buttons are interactive elements that users can click, tap, or otherwise\n * engage with to submit forms, open dialogues, or perform any other interaction.\n *\n * Features animated scale effect on press and smooth icon transitions matching\n * the web UDS Button behavior.\n *\n * @category Interactive\n * @platform mobile\n *\n * @example\n * ```tsx\n * import { Button } from '@yahoo/uds-mobile/Button';\n *\n * <Button onPress={() => console.log('pressed')}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button startIcon=\"Add\" variant=\"brand\">Add Item</Button>\n * <Button loading>Saving...</Button>\n * ```\n *\n * @accessibility\n * - Sets `accessibilityRole=\"button\"` automatically\n * - Announces loading state to screen readers\n * - Use `accessibilityLabel` for icon-only buttons\n *\n * @see {@link IconButton} for icon-only buttons\n * @see {@link Link} for navigation actions\n */\nconst Button = memo(function Button({\n variant = 'primary',\n size = 'md',\n iconVariant = 'outline',\n startIcon,\n endIcon,\n loading,\n disabled,\n width: _width,\n children,\n style,\n accessibilityLabel,\n accessibilityHint,\n disableEffects = false,\n maxFontSizeMultiplier,\n onPressIn,\n onPressOut,\n ref,\n ...props\n}: ButtonProps) {\n const shouldAnimate = !disableEffects;\n\n // Inside a control, \"no cap resolved\" must force-uncap (0) rather than let\n // the label's own variant default or an inherited cap apply.\n const resolvedMaxFontSizeMultiplier = useMaxFontSizeMultiplier('control', maxFontSizeMultiplier);\n\n const { theme } = useUnistyles();\n const baseMetrics = useMemo(() => getButtonControlMetrics(theme, size), [theme, size]);\n const labelScaleFactor = useComponentFontScale(\n resolvedMaxFontSizeMultiplier,\n baseMetrics.labelLineHeight,\n );\n\n /* --------------------------------- State ---------------------------------- */\n const [pressed, setPressed] = useState(false);\n\n buttonStyles.useVariants({ size, variant, disabled, pressed });\n\n // Get gap and icon size from current variant styles\n const buttonGap = buttonStyles.root.gap;\n const iconSize = buttonStyles.icon.fontSize;\n const iconScaleFactor = useComponentFontScale(resolvedMaxFontSizeMultiplier, iconSize);\n const iconCellScaleFactor = useComponentFontScale(\n resolvedMaxFontSizeMultiplier,\n baseMetrics.iconCellHeight,\n );\n const { minHeight, labelLineHeight, iconCellHeight, paddingVertical } = useMemo(\n () =>\n getButtonControlMetrics(theme, size, {\n label: labelScaleFactor,\n icon: iconScaleFactor,\n iconCell: iconCellScaleFactor,\n }),\n [theme, size, labelScaleFactor, iconScaleFactor, iconCellScaleFactor],\n );\n\n // Get animated theme for boxShadow (useAnimatedVariantColor doesn't support non-color props)\n const animatedTheme = useAnimatedTheme();\n\n /* ------------------------------- Animation -------------------------------- */\n const scale = useSharedValue<number>(SCALE_EFFECTS.none);\n\n const handlePressIn = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressIn']>>[0]) => {\n setPressed(true);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.down, BUTTON_SPRING_CONFIG);\n }\n onPressIn?.(event);\n },\n [shouldAnimate, scale, onPressIn],\n );\n\n const handlePressOut = useCallback(\n (event: Parameters<NonNullable<PressableProps['onPressOut']>>[0]) => {\n setPressed(false);\n if (shouldAnimate) {\n scale.value = withSpring(SCALE_EFFECTS.none, BUTTON_SPRING_CONFIG);\n }\n onPressOut?.(event);\n },\n [shouldAnimate, scale, onPressOut],\n );\n\n /* -------------------------------- Content --------------------------------- */\n const childrenNode =\n children &&\n (isValidElement(children) ? (\n <ResolvedControlFontScalingProvider maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}>\n {children}\n </ResolvedControlFontScalingProvider>\n ) : (\n <Text\n variant=\"inherit\"\n numberOfLines={1}\n textAlign=\"center\"\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={getButtonTextStyle(buttonStyles.text, labelLineHeight)}\n >\n {children}\n </Text>\n ));\n\n const a11yState = useMemo(() => ({ disabled, busy: loading }), [disabled, loading]);\n\n /* --------------------------------- Styles --------------------------------- */\n // Animate pressed state for shadow (0 = rest, 1 = pressed)\n const pressProgress = useDerivedValue(\n () => withTiming(pressed ? 1 : 0, { duration: 220, easing: Easing.bezier(0, 0, 0.2, 1) }),\n [pressed],\n );\n\n // Animate using Unistyles' variant color system + boxShadow from theme\n const animatedStyles = useAnimatedStyle(() => {\n // Get boxShadow from theme using flattened path (no camelCase conversion needed!)\n const components = animatedTheme.value.components;\n const buttonVariantPath = `button/variant/${variant}/root/pressed` as const;\n const shadowPressed = components[buttonVariantPath]?.boxShadow;\n\n return {\n transform: [{ scale: scale.value }],\n // Only animate shadow if the theme defines one for this variant\n ...(shadowPressed && {\n boxShadow: interpolateShadowAlpha(shadowPressed, pressProgress.value),\n }),\n };\n });\n\n // Determine what should be visible in start slot\n const showLoading = !!loading;\n const showStartIcon = !!startIcon && !loading;\n const showEndIcon = !!endIcon && !loading;\n\n const iconSizeToken = (buttonStyles.icon.iconSizeToken as IconSize) ?? 'sm';\n\n // Containers pre-scale by the capped factor; icon styles stay unscaled and\n // native grows them by the same capped multiplier.\n const scaledIconSize = Math.round(iconSize * iconScaleFactor);\n const scaledIconCellHeight = Math.ceil(iconCellHeight * iconCellScaleFactor);\n\n // Start slot: either loading spinner or start icon\n const startContent = (\n <AnimatedIconSlot\n visible={showLoading || showStartIcon}\n iconSize={scaledIconSize}\n iconCellHeight={scaledIconCellHeight}\n gap={buttonGap}\n >\n {showLoading ? (\n <ActivityIndicator size={scaledIconSize} color={buttonStyles.icon.color} />\n ) : (\n <IconSlot\n icon={startIcon}\n size={iconSizeToken}\n variant={iconVariant}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={[buttonStyles.icon, { lineHeight: iconCellHeight }]}\n />\n )}\n </AnimatedIconSlot>\n );\n\n // End slot: only end icon (no loading here)\n const endContent = (\n <AnimatedIconSlot\n visible={showEndIcon}\n iconSize={scaledIconSize}\n iconCellHeight={scaledIconCellHeight}\n gap={buttonGap}\n >\n <IconSlot\n icon={endIcon}\n size={iconSizeToken}\n variant={iconVariant}\n maxFontSizeMultiplier={resolvedMaxFontSizeMultiplier}\n style={[buttonStyles.icon, { lineHeight: iconCellHeight }]}\n />\n </AnimatedIconSlot>\n );\n\n const rootStyles = useMemo(\n () => [\n buttonStyles.root,\n { minHeight, paddingVertical },\n animatedStyles,\n typeof style === 'function' ? style({ pressed }) : style,\n ],\n [buttonStyles.root, minHeight, paddingVertical, animatedStyles, style, pressed],\n );\n\n /* --------------------------------- Render --------------------------------- */\n return (\n <AnimatedPressable\n ref={ref}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"center\"\n overflow=\"hidden\"\n accessibilityLabel={loading ? `${accessibilityLabel ?? ''}, loading` : accessibilityLabel}\n accessibilityHint={accessibilityHint}\n accessibilityRole=\"button\"\n accessibilityState={a11yState}\n alignContent=\"center\"\n style={rootStyles}\n {...props}\n >\n {startContent}\n {childrenNode}\n {endContent}\n </AnimatedPressable>\n );\n});\n\nButton.displayName = 'Button';\n\nexport { Button, type ButtonProps };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAS,uBAAuB,QAA4B,OAAuB;AACjF;CACA,IAAI,CAAC,QACH,OAAO;CAET,IAAI,SAAS,GACX,OAAO;CAET,IAAI,SAAS,GACX,OAAO;CAGT,OAAO,OAAO,QAAQ,sDAAsD,GAAG,GAAG,GAAG,GAAG,MAAM;EAE5F,OAAO,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KADZ,WAAW,EAAE,GAAG,OACS,QAAQ,EAAE,CAAC;GACrD;;;;;;;AAYJ,SAAS,iBAAiB,EACxB,UACA,SACA,UACA,gBACA,OAOC;CAGD,MAAM,WAAW,sBACT,WAAW,UAAU,IAAI,GAAG,qBAAqB,EACvD,CAAC,QAAQ,CACV;CAED,MAAM,gBAAgB,uBAAuB;EAE3C,MAAM,aAAa,WAAW;EAS9B,OAAO;GACL,OATY,YAAY,SAAS,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,WAAW,CAS1D;GACL,SALc,YAAY,SAAS,OAAO,CAAC,IAAK,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,QAKrD;GACP,WAAW,CAAC,EAAE,OALF,YAAY,SAAS,OAAO,CAAC,IAAK,EAAE,EAAE,CAAC,IAAK,EAAE,EAAE,QAKzC,EAAE,CAAC;GACtB,UAAU;GACV,WAAW;GACX,gBAAgB;GACjB;GACD;CAEF,OAAO,oBAAC,SAAS,MAAV;EAAe,OAAO;EAAgB;EAAyB,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmFxE,MAAM,SAAS,KAAK,SAAS,OAAO,EAClC,UAAU,WACV,OAAO,MACP,cAAc,WACd,WACA,SACA,SACA,UACA,OAAO,QACP,UACA,OACA,oBACA,mBACA,iBAAiB,OACjB,uBACA,WACA,YACA,KACA,GAAG,SACW;CACd,MAAM,gBAAgB,CAAC;CAIvB,MAAM,gCAAgC,yBAAyB,WAAW,sBAAsB;CAEhG,MAAM,EAAE,UAAU,cAAc;CAChC,MAAM,cAAc,cAAc,wBAAwB,OAAO,KAAK,EAAE,CAAC,OAAO,KAAK,CAAC;CACtF,MAAM,mBAAmB,sBACvB,+BACA,YAAY,gBACb;CAGD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAE7C,aAAa,YAAY;EAAE;EAAM;EAAS;EAAU;EAAS,CAAC;CAG9D,MAAM,YAAY,aAAa,KAAK;CACpC,MAAM,WAAW,aAAa,KAAK;CACnC,MAAM,kBAAkB,sBAAsB,+BAA+B,SAAS;CACtF,MAAM,sBAAsB,sBAC1B,+BACA,YAAY,eACb;CACD,MAAM,EAAE,WAAW,iBAAiB,gBAAgB,oBAAoB,cAEpE,wBAAwB,OAAO,MAAM;EACnC,OAAO;EACP,MAAM;EACN,UAAU;EACX,CAAC,EACJ;EAAC;EAAO;EAAM;EAAkB;EAAiB;EAAoB,CACtE;CAGD,MAAM,gBAAgB,kBAAkB;CAGxC,MAAM,QAAQ,eAAuB,cAAc,KAAK;CAExD,MAAM,gBAAgB,aACnB,UAAmE;EAClE,WAAW,KAAK;EAChB,IAAI,eACF,MAAM,QAAQ,WAAW,cAAc,MAAM,qBAAqB;EAEpE,YAAY,MAAM;IAEpB;EAAC;EAAe;EAAO;EAAU,CAClC;CAED,MAAM,iBAAiB,aACpB,UAAoE;EACnE,WAAW,MAAM;EACjB,IAAI,eACF,MAAM,QAAQ,WAAW,cAAc,MAAM,qBAAqB;EAEpE,aAAa,MAAM;IAErB;EAAC;EAAe;EAAO;EAAW,CACnC;CAGD,MAAM,eACJ,aACC,eAAe,SAAS,GACvB,oBAAC,oCAAD;EAAoC,uBAAuB;EACxD;EACkC,CAAA,GAErC,oBAACA,QAAD;EACE,SAAQ;EACR,eAAe;EACf,WAAU;EACV,uBAAuB;EACvB,OAAO,mBAAmB,aAAa,MAAM,gBAAgB;EAE5D;EACI,CAAA;CAGX,MAAM,YAAY,eAAe;EAAE;EAAU,MAAM;EAAS,GAAG,CAAC,UAAU,QAAQ,CAAC;CAInF,MAAM,gBAAgB,sBACd,WAAW,UAAU,IAAI,GAAG;EAAE,UAAU;EAAK,QAAQ,OAAO,OAAO,GAAG,GAAG,IAAK,EAAE;EAAE,CAAC,EACzF,CAAC,QAAQ,CACV;CAGD,MAAM,iBAAiB,uBAAuB;EAI5C,MAAM,gBAFa,cAAc,MAAM,WAEN,kBADW,QAAQ,iBACC;EAErD,OAAO;GACL,WAAW,CAAC,EAAE,OAAO,MAAM,OAAO,CAAC;GAEnC,GAAI,iBAAiB,EACnB,WAAW,uBAAuB,eAAe,cAAc,MAAM,EACtE;GACF;GACD;CAGF,MAAM,cAAc,CAAC,CAAC;CACtB,MAAM,gBAAgB,CAAC,CAAC,aAAa,CAAC;CACtC,MAAM,cAAc,CAAC,CAAC,WAAW,CAAC;CAElC,MAAM,gBAAiB,aAAa,KAAK,iBAA8B;CAIvE,MAAM,iBAAiB,KAAK,MAAM,WAAW,gBAAgB;CAC7D,MAAM,uBAAuB,KAAK,KAAK,iBAAiB,oBAAoB;CAG5E,MAAM,eACJ,oBAAC,kBAAD;EACE,SAAS,eAAe;EACxB,UAAU;EACV,gBAAgB;EAChB,KAAK;YAEJ,cACC,oBAAC,mBAAD;GAAmB,MAAM;GAAgB,OAAO,aAAa,KAAK;GAAS,CAAA,GAE3E,oBAAC,UAAD;GACE,MAAM;GACN,MAAM;GACN,SAAS;GACT,uBAAuB;GACvB,OAAO,CAAC,aAAa,MAAM,EAAE,YAAY,gBAAgB,CAAC;GAC1D,CAAA;EAEa,CAAA;CAIrB,MAAM,aACJ,oBAAC,kBAAD;EACE,SAAS;EACT,UAAU;EACV,gBAAgB;EAChB,KAAK;YAEL,oBAAC,UAAD;GACE,MAAM;GACN,MAAM;GACN,SAAS;GACT,uBAAuB;GACvB,OAAO,CAAC,aAAa,MAAM,EAAE,YAAY,gBAAgB,CAAC;GAC1D,CAAA;EACe,CAAA;CAGrB,MAAM,aAAa,cACX;EACJ,aAAa;EACb;GAAE;GAAW;GAAiB;EAC9B;EACA,OAAO,UAAU,aAAa,MAAM,EAAE,SAAS,CAAC,GAAG;EACpD,EACD;EAAC,aAAa;EAAM;EAAW;EAAiB;EAAgB;EAAO;EAAQ,CAChF;CAGD,OACE,qBAAC,mBAAD;EACO;EACK;EACV,WAAW;EACX,YAAY;EACZ,eAAc;EACd,YAAW;EACX,gBAAe;EACf,UAAS;EACT,oBAAoB,UAAU,GAAG,sBAAsB,GAAG,aAAa;EACpD;EACnB,mBAAkB;EAClB,oBAAoB;EACpB,cAAa;EACb,OAAO;EACP,GAAI;YAfN;GAiBG;GACA;GACA;GACiB;;EAEtB;AAEF,OAAO,cAAc"}