@12-apps/ui 6.77.1 → 6.77.2

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 (146) hide show
  1. package/dist/_internal/components/data-display/CardKit/CardKebab.js +2 -2
  2. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +2 -2
  3. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +2 -2
  4. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +17 -17
  5. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +23 -23
  6. package/dist/_internal/components/data-display/DataViews/DeleteViewDialog.js +2 -2
  7. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +3 -3
  8. package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +2 -2
  9. package/dist/_internal/components/data-display/DataViews/data-views-card-body.js +3 -3
  10. package/dist/_internal/components/data-display/DataViews/data-views-columns.js +3 -3
  11. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +3 -3
  12. package/dist/_internal/components/data-display/DataViews/data-views-empty.js +2 -2
  13. package/dist/_internal/components/data-display/DataViews/data-views-export.js +2 -2
  14. package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +5 -5
  15. package/dist/_internal/components/data-display/DataViews/data-views-grid-helpers.js +2 -2
  16. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +14 -14
  17. package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +3 -3
  18. package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +2 -2
  19. package/dist/_internal/components/data-display/DataViews/data-views-select-all-strip.js +2 -2
  20. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +7 -7
  21. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +5 -5
  22. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +6 -6
  23. package/dist/_internal/components/data-display/DataViews/data-views-view-nav.js +2 -2
  24. package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +4 -4
  25. package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +2 -2
  26. package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +2 -2
  27. package/dist/_internal/components/feedback/index.js +3 -3
  28. package/dist/_internal/components/form/Button/Button.js +1 -1
  29. package/dist/_internal/components/form/index.js +1 -1
  30. package/dist/_internal/components/form/total-form/SubmitButton.js +2 -2
  31. package/dist/_internal/components/index.js +11 -11
  32. package/dist/_internal/components/layout/SettingCard/SettingCard.js +4 -4
  33. package/dist/_internal/components/layout/SettingCard/SettingCard.parts.js +2 -2
  34. package/dist/_internal/components/layout/SettingCard/SettingCard.summary.js +3 -3
  35. package/dist/_internal/components/layout/SettingCard/SettingGrid.js +3 -3
  36. package/dist/_internal/components/layout/SettingCard/SettingGroup.js +5 -5
  37. package/dist/_internal/components/layout/SettingCard/SettingSwitch.js +4 -4
  38. package/dist/_internal/components/layout/SettingCard/SettingToggle.js +5 -5
  39. package/dist/_internal/components/layout/index.js +8 -8
  40. package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +2 -2
  41. package/dist/_internal/components/navigation/index.js +2 -2
  42. package/dist/_internal/social-login-button/social-login-button.js +2 -2
  43. package/dist/_internal/user-avatar/user-avatar.js +2 -2
  44. package/dist/{chunk-BF3SGOWT.js → chunk-2ONUSNPZ.js} +2 -2
  45. package/dist/{chunk-ULK4AGG4.js → chunk-3EIRRLCQ.js} +2 -2
  46. package/dist/{chunk-TVXUOJAL.js → chunk-4NG3LTRX.js} +2 -2
  47. package/dist/{chunk-XZE5FIRB.js → chunk-5IIHAYFF.js} +2 -2
  48. package/dist/{chunk-72L5C74V.js → chunk-5M7T36AT.js} +3 -3
  49. package/dist/{chunk-J32XJALK.js → chunk-5WUXJDFP.js} +2 -2
  50. package/dist/{chunk-3MQZBBUW.js → chunk-7FFY4LJY.js} +2 -2
  51. package/dist/{chunk-MKBIIYJM.js → chunk-7LMR2H7O.js} +4 -4
  52. package/dist/{chunk-H7QTDKA6.js → chunk-BGSUSSAV.js} +2 -2
  53. package/dist/{chunk-FR2RJXHS.js → chunk-C2NNJMVY.js} +3 -3
  54. package/dist/{chunk-NIZUFPV3.js → chunk-CW5Z67NA.js} +2 -2
  55. package/dist/{chunk-BEHM2VLH.js → chunk-FVNY5BNV.js} +2 -2
  56. package/dist/{chunk-7KZEQJPQ.js → chunk-GG7IOJAG.js} +3 -3
  57. package/dist/{chunk-J5QNJERV.js → chunk-HHFVDOA7.js} +3 -3
  58. package/dist/{chunk-HVGQH54G.js → chunk-HMWSMRLM.js} +2 -2
  59. package/dist/{chunk-MA6ENJDP.js → chunk-HXCUKZOB.js} +3 -3
  60. package/dist/{chunk-NIENIKZ5.js → chunk-NRADSKGH.js} +2 -2
  61. package/dist/{chunk-725H45MT.js → chunk-NZXRPWPW.js} +4 -4
  62. package/dist/{chunk-LBEZ7A3F.js → chunk-OK3OJGJY.js} +3 -3
  63. package/dist/{chunk-WJEJ65TF.js → chunk-OY437NRJ.js} +2 -2
  64. package/dist/{chunk-Y2CCXB7R.js → chunk-QITQFCFY.js} +2 -2
  65. package/dist/{chunk-P4LR2GLN.js → chunk-R2ODLE6G.js} +2 -2
  66. package/dist/{chunk-VQSBZPKO.js → chunk-RVZMKGWQ.js} +2 -2
  67. package/dist/{chunk-QWSYHENP.js → chunk-SKIAWOUC.js} +4 -4
  68. package/dist/{chunk-3FDSXSEP.js → chunk-TDWA2XFJ.js} +2 -2
  69. package/dist/{chunk-VWVA74JY.js → chunk-UXQVJXQ3.js} +2 -2
  70. package/dist/{chunk-BEYLI32I.js → chunk-WOUJSKQB.js} +3 -3
  71. package/dist/{chunk-NDQJDA7O.js → chunk-WULEQKU7.js} +2 -2
  72. package/dist/{chunk-4QGPRORO.js → chunk-X2GWR75Q.js} +2 -2
  73. package/dist/{chunk-NKBCUR4V.js → chunk-X3DN2BE3.js} +2 -2
  74. package/dist/{chunk-TRYYYWV3.js → chunk-Y2CHJLWD.js} +6 -6
  75. package/dist/{chunk-HDXUQKJ5.js → chunk-YNZUYZ2O.js} +2 -2
  76. package/dist/{chunk-364NH2NY.js → chunk-YOXYSAUI.js} +20 -11
  77. package/dist/chunk-YOXYSAUI.js.map +1 -0
  78. package/dist/{chunk-JA3WJPXH.js → chunk-ZAZOBTVS.js} +2 -2
  79. package/dist/{chunk-SZSS5KKX.js → chunk-ZCXQ4ZB6.js} +2 -2
  80. package/dist/{chunk-P7VAMJFJ.js → chunk-ZE4HF3KC.js} +3 -3
  81. package/dist/data-display/CardKit.js +3 -3
  82. package/dist/data-display/DataViews.js +23 -23
  83. package/dist/feedback/ConfirmAction.js +2 -2
  84. package/dist/feedback/NotificationStatusNotice.js +2 -2
  85. package/dist/form/Button.js +1 -1
  86. package/dist/form/total-form.js +2 -2
  87. package/dist/layout/SettingCard.js +8 -8
  88. package/dist/native/chunks/{chunk-2MFMON74.js → chunk-EZHCNYQO.js} +67 -33
  89. package/dist/native/chunks/chunk-EZHCNYQO.js.map +1 -0
  90. package/dist/native/chunks/{chunk-H22XVP5O.js → chunk-TZPNJ4AU.js} +2 -2
  91. package/dist/native/data-display/EmptyState.js +2 -2
  92. package/dist/native/data-display/ErrorState.js +1 -1
  93. package/dist/native/data-display/FleetMap.js +2 -2
  94. package/dist/native/form/Button.js +1 -1
  95. package/dist/navigation/AppHeader.js +2 -2
  96. package/dist/social-login-button.js +2 -2
  97. package/dist/types/components/form/Button/Button.d.ts.map +1 -1
  98. package/dist/types/components/form/Button/Button.native.loading.d.ts +36 -0
  99. package/dist/types/components/form/Button/Button.native.loading.d.ts.map +1 -0
  100. package/dist/types-native/components/form/Button/Button.native.d.ts.map +1 -1
  101. package/dist/types-native/components/form/Button/Button.native.loading.d.ts +36 -0
  102. package/dist/types-native/components/form/Button/Button.native.loading.d.ts.map +1 -0
  103. package/dist/user-avatar.js +2 -2
  104. package/package.json +1 -1
  105. package/src/components/form/Button/Button.md +4 -3
  106. package/src/components/form/Button/Button.native.loading.tsx +89 -0
  107. package/src/components/form/Button/Button.native.tsx +14 -24
  108. package/src/components/form/Button/Button.tsx +47 -16
  109. package/dist/chunk-364NH2NY.js.map +0 -1
  110. package/dist/native/chunks/chunk-2MFMON74.js.map +0 -1
  111. /package/dist/{chunk-BF3SGOWT.js.map → chunk-2ONUSNPZ.js.map} +0 -0
  112. /package/dist/{chunk-ULK4AGG4.js.map → chunk-3EIRRLCQ.js.map} +0 -0
  113. /package/dist/{chunk-TVXUOJAL.js.map → chunk-4NG3LTRX.js.map} +0 -0
  114. /package/dist/{chunk-XZE5FIRB.js.map → chunk-5IIHAYFF.js.map} +0 -0
  115. /package/dist/{chunk-72L5C74V.js.map → chunk-5M7T36AT.js.map} +0 -0
  116. /package/dist/{chunk-J32XJALK.js.map → chunk-5WUXJDFP.js.map} +0 -0
  117. /package/dist/{chunk-3MQZBBUW.js.map → chunk-7FFY4LJY.js.map} +0 -0
  118. /package/dist/{chunk-MKBIIYJM.js.map → chunk-7LMR2H7O.js.map} +0 -0
  119. /package/dist/{chunk-H7QTDKA6.js.map → chunk-BGSUSSAV.js.map} +0 -0
  120. /package/dist/{chunk-FR2RJXHS.js.map → chunk-C2NNJMVY.js.map} +0 -0
  121. /package/dist/{chunk-NIZUFPV3.js.map → chunk-CW5Z67NA.js.map} +0 -0
  122. /package/dist/{chunk-BEHM2VLH.js.map → chunk-FVNY5BNV.js.map} +0 -0
  123. /package/dist/{chunk-7KZEQJPQ.js.map → chunk-GG7IOJAG.js.map} +0 -0
  124. /package/dist/{chunk-J5QNJERV.js.map → chunk-HHFVDOA7.js.map} +0 -0
  125. /package/dist/{chunk-HVGQH54G.js.map → chunk-HMWSMRLM.js.map} +0 -0
  126. /package/dist/{chunk-MA6ENJDP.js.map → chunk-HXCUKZOB.js.map} +0 -0
  127. /package/dist/{chunk-NIENIKZ5.js.map → chunk-NRADSKGH.js.map} +0 -0
  128. /package/dist/{chunk-725H45MT.js.map → chunk-NZXRPWPW.js.map} +0 -0
  129. /package/dist/{chunk-LBEZ7A3F.js.map → chunk-OK3OJGJY.js.map} +0 -0
  130. /package/dist/{chunk-WJEJ65TF.js.map → chunk-OY437NRJ.js.map} +0 -0
  131. /package/dist/{chunk-Y2CCXB7R.js.map → chunk-QITQFCFY.js.map} +0 -0
  132. /package/dist/{chunk-P4LR2GLN.js.map → chunk-R2ODLE6G.js.map} +0 -0
  133. /package/dist/{chunk-VQSBZPKO.js.map → chunk-RVZMKGWQ.js.map} +0 -0
  134. /package/dist/{chunk-QWSYHENP.js.map → chunk-SKIAWOUC.js.map} +0 -0
  135. /package/dist/{chunk-3FDSXSEP.js.map → chunk-TDWA2XFJ.js.map} +0 -0
  136. /package/dist/{chunk-VWVA74JY.js.map → chunk-UXQVJXQ3.js.map} +0 -0
  137. /package/dist/{chunk-BEYLI32I.js.map → chunk-WOUJSKQB.js.map} +0 -0
  138. /package/dist/{chunk-NDQJDA7O.js.map → chunk-WULEQKU7.js.map} +0 -0
  139. /package/dist/{chunk-4QGPRORO.js.map → chunk-X2GWR75Q.js.map} +0 -0
  140. /package/dist/{chunk-NKBCUR4V.js.map → chunk-X3DN2BE3.js.map} +0 -0
  141. /package/dist/{chunk-TRYYYWV3.js.map → chunk-Y2CHJLWD.js.map} +0 -0
  142. /package/dist/{chunk-HDXUQKJ5.js.map → chunk-YNZUYZ2O.js.map} +0 -0
  143. /package/dist/{chunk-JA3WJPXH.js.map → chunk-ZAZOBTVS.js.map} +0 -0
  144. /package/dist/{chunk-SZSS5KKX.js.map → chunk-ZCXQ4ZB6.js.map} +0 -0
  145. /package/dist/{chunk-P7VAMJFJ.js.map → chunk-ZE4HF3KC.js.map} +0 -0
  146. /package/dist/native/chunks/{chunk-H22XVP5O.js.map → chunk-TZPNJ4AU.js.map} +0 -0
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import type { TextStyle } from 'react-native';
3
+ /**
4
+ * The native Button's loading state (FUT-3646): the label and icon stay laid
5
+ * out under the spinner — so the button keeps its width and shape — and the
6
+ * button keeps its name. Split from `Button.native.tsx` to keep that file
7
+ * under the line ceiling.
8
+ */
9
+ /**
10
+ * The spinner over the label while loading, across the whole button; `role`
11
+ * comes from react-native's ActivityIndicator. It used to REPLACE the label, so
12
+ * a loading button had no accessible name (FUT-3646).
13
+ */
14
+ export declare function Spinner({ color, testID }: {
15
+ color: string;
16
+ testID: string;
17
+ }): React.JSX.Element;
18
+ /**
19
+ * The button's name while loading: its text label, said outright. The label
20
+ * stays laid out (transparent) so the width holds, but a screen reader may skip
21
+ * a view at opacity 0 — VoiceOver does — so the name cannot ride on it alone.
22
+ * A caller's own `aria-label` still wins, being spread after this.
23
+ */
24
+ export declare const loadingName: (loading: boolean | undefined, children: React.ReactNode) => string | undefined;
25
+ /** Laid out but unseen under the spinner while loading, so the width holds. */
26
+ export declare const hiddenWhile: (loading: boolean | undefined) => {
27
+ opacity: number;
28
+ } | null;
29
+ /** The text label; while loading, kept laid out but unseen under the spinner. */
30
+ export declare function Label({ loading, label, testID, children, }: {
31
+ loading: boolean | undefined;
32
+ label: TextStyle;
33
+ testID: string;
34
+ children: React.ReactNode;
35
+ }): React.ReactNode;
36
+ //# sourceMappingURL=Button.native.loading.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.native.loading.d.ts","sourceRoot":"","sources":["../../../../../src/components/form/Button/Button.native.loading.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C;;;;;GAKG;AAEH;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAW/F;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,OAAO,GAAG,SAAS,EAAE,UAAU,KAAK,CAAC,SAAS,KAAG,MAAM,GAAG,SACvD,CAAC;AAazC,+EAA+E;AAC/E,eAAO,MAAM,WAAW,GAAI,SAAS,OAAO,GAAG,SAAS;;QAAqC,CAAC;AAE9F,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,EACpB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,GAAG,KAAK,CAAC,SAAS,CASlB"}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  UserAvatar,
3
3
  UserMenu
4
- } from "./chunk-3FDSXSEP.js";
4
+ } from "./chunk-TDWA2XFJ.js";
5
5
  import "./chunk-UDQSIRAP.js";
6
6
  import "./chunk-KARMRTTM.js";
7
7
  import "./chunk-7XSCEHGA.js";
@@ -15,7 +15,7 @@ import "./chunk-FKUYRQSP.js";
15
15
  import "./chunk-PLB6WWXW.js";
16
16
  import "./chunk-HDBP55R3.js";
17
17
  import "./chunk-BA4RHQ3B.js";
18
- import "./chunk-364NH2NY.js";
18
+ import "./chunk-YOXYSAUI.js";
19
19
  import "./chunk-PPEHBI5O.js";
20
20
  import "./chunk-G2FUZALJ.js";
21
21
  import "./chunk-TAMIZE4Q.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.77.1",
3
+ "version": "6.77.2",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -10,11 +10,11 @@ The Button component is a versatile and highly customizable interactive element
10
10
  - **Color Themes**: primary, secondary, success, warning, info, danger, neutral
11
11
  - **Size Options**: xs, sm, md, lg, xl
12
12
  - **Special Effects**: glow, pulse animations
13
- - **Loading States**: Built-in loading spinner
13
+ - **Loading States**: Built-in loading spinner over the label, which stays as the button's accessible name
14
14
  - **Icon Support**: Leading icons with automatic loading state handling
15
15
  - **Renders As Anything**: `component` swaps the element, carrying that
16
16
  component's own props through the types (a router link, an `<a>`, a label)
17
- - **Accessibility**: Full ARIA support, keyboard navigation
17
+ - **Accessibility**: Native `<button>` semantics, keyboard navigation; a loading button keeps its name and sets `aria-busy`
18
18
  - **Responsive Design**: Adapts to different screen sizes
19
19
 
20
20
  ## Usage
@@ -56,7 +56,7 @@ import { Button } from '@procurement/ui';
56
56
  | `variant` | `'solid' \| 'outline' \| 'ghost' \| 'text' \| 'glass' \| 'gradient'` | `'solid'` | Visual style variant |
57
57
  | `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'info' \| 'danger' \| 'neutral'` | `'primary'` | Color theme |
58
58
  | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Button size |
59
- | `loading` | `boolean` | `false` | Shows loading spinner and disables button |
59
+ | `loading` | `boolean` | `false` | Shows loading spinner over the label, sets `aria-busy`, disables button |
60
60
  | `disabled` | `boolean` | `false` | Disables button interaction |
61
61
  | `icon` | `ReactNode` | - | Icon element to display before text |
62
62
  | `glow` | `boolean` | `false` | Adds glow visual effect |
@@ -274,6 +274,7 @@ The Button component includes comprehensive test IDs for reliable automated test
274
274
  | Button | `{dataTestId}` or `'button'` | Main button element |
275
275
  | Icon | `{dataTestId}-icon` or `'button-icon'` | Icon wrapper when icon prop provided |
276
276
  | Loading Spinner | `{dataTestId}-loading` or `'button-loading'` | Circular progress during loading state |
277
+ | Loading Label | `{dataTestId}-busy-label` or `'button-busy-label'` | The label kept, transparent, under the spinner while loading |
277
278
 
278
279
  ### Testing Best Practices
279
280
 
@@ -0,0 +1,89 @@
1
+ import * as React from 'react';
2
+ import { ActivityIndicator, Platform, StyleSheet, View } from 'react-native';
3
+ import type { TextStyle } from 'react-native';
4
+
5
+ import { BUTTON_SPINNER_SIZE } from './Button.metrics';
6
+ import { renderTextChildren } from '../../../platform/text-children';
7
+
8
+ /**
9
+ * The native Button's loading state (FUT-3646): the label and icon stay laid
10
+ * out under the spinner — so the button keeps its width and shape — and the
11
+ * button keeps its name. Split from `Button.native.tsx` to keep that file
12
+ * under the line ceiling.
13
+ */
14
+
15
+ /**
16
+ * The spinner over the label while loading, across the whole button; `role`
17
+ * comes from react-native's ActivityIndicator. It used to REPLACE the label, so
18
+ * a loading button had no accessible name (FUT-3646).
19
+ */
20
+ export function Spinner({ color, testID }: { color: string; testID: string }): React.JSX.Element {
21
+ return (
22
+ <View style={styles.spinner}>
23
+ <ActivityIndicator
24
+ size={Platform.OS === 'ios' ? 'small' : BUTTON_SPINNER_SIZE}
25
+ color={color}
26
+ testID={testID}
27
+ accessibilityLabel="loading"
28
+ />
29
+ </View>
30
+ );
31
+ }
32
+
33
+ /**
34
+ * The button's name while loading: its text label, said outright. The label
35
+ * stays laid out (transparent) so the width holds, but a screen reader may skip
36
+ * a view at opacity 0 — VoiceOver does — so the name cannot ride on it alone.
37
+ * A caller's own `aria-label` still wins, being spread after this.
38
+ */
39
+ export const loadingName = (loading: boolean | undefined, children: React.ReactNode): string | undefined =>
40
+ loading ? textOf(children) : undefined;
41
+
42
+ /**
43
+ * The text of a label made only of strings and numbers — `Salvar`, or
44
+ * `Salvar {n}`, which React hands over as an array. Anything else (an element)
45
+ * has no text to say here, so the name is left to the label itself.
46
+ */
47
+ function textOf(children: React.ReactNode): string | undefined {
48
+ const parts = React.Children.toArray(children);
49
+ const textual = parts.every((part) => typeof part === 'string' || typeof part === 'number');
50
+ return textual && parts.length > 0 ? parts.join('') : undefined;
51
+ }
52
+
53
+ /** Laid out but unseen under the spinner while loading, so the width holds. */
54
+ export const hiddenWhile = (loading: boolean | undefined) => (loading ? styles.hidden : null);
55
+
56
+ /** The text label; while loading, kept laid out but unseen under the spinner. */
57
+ export function Label({
58
+ loading,
59
+ label,
60
+ testID,
61
+ children,
62
+ }: {
63
+ loading: boolean | undefined;
64
+ label: TextStyle;
65
+ testID: string;
66
+ children: React.ReactNode;
67
+ }): React.ReactNode {
68
+ const text = renderTextChildren(children, label, 1);
69
+ return loading ? (
70
+ <View style={styles.hidden} testID={testID}>
71
+ {text}
72
+ </View>
73
+ ) : (
74
+ text
75
+ );
76
+ }
77
+
78
+ const styles = StyleSheet.create({
79
+ // The label and icon under the spinner: laid out, so the width holds, but unseen.
80
+ hidden: {
81
+ opacity: 0,
82
+ },
83
+ spinner: {
84
+ ...StyleSheet.absoluteFillObject,
85
+ pointerEvents: 'none',
86
+ alignItems: 'center',
87
+ justifyContent: 'center',
88
+ },
89
+ });
@@ -1,6 +1,5 @@
1
1
  import * as React from 'react';
2
2
  import {
3
- ActivityIndicator,
4
3
  Animated,
5
4
  Easing,
6
5
  Platform,
@@ -20,7 +19,6 @@ import {
20
19
  BUTTON_ICON_GAP_UNITS,
21
20
  BUTTON_LINE_HEIGHT,
22
21
  BUTTON_SIZES,
23
- BUTTON_SPINNER_SIZE,
24
22
  BUTTON_WASH_ALPHA,
25
23
  GLASS_BACKGROUND_ALPHA,
26
24
  GLASS_BACKGROUND_ALPHA_PRESSED,
@@ -35,7 +33,7 @@ import {
35
33
  quietInk,
36
34
  } from './Button.metrics';
37
35
  import type { ButtonProps, ButtonVariant } from './Button.types.native';
38
- import { renderTextChildren } from '../../../platform/text-children';
36
+ import { hiddenWhile, Label, loadingName, Spinner } from './Button.native.loading';
39
37
  import { childTestId, resolveTestId, withoutTestIdProps } from '../../../platform/test-id';
40
38
  import { useUiTheme } from '../../../provider/use-ui-theme.native';
41
39
  import { resolveFieldEdge } from '../../../tokens/field-edge.core';
@@ -238,7 +236,8 @@ interface ButtonLook {
238
236
  function buttonLook(theme: UiTheme, props: ResolvedProps): ButtonLook {
239
237
  const { variant, color, size, loading, icon, children, glow, pulse, active, disabled, style } = props;
240
238
  const palette = buttonPalette(theme, color);
241
- const iconOnly = !loading && icon != null && children == null;
239
+ // Loading keeps the icon (transparent), so it changes neither the shape nor the gap (FUT-3646).
240
+ const iconOnly = icon != null && children == null;
242
241
  const inactive = Boolean(disabled) || loading;
243
242
  const paint = inactive ? disabledPaint(theme, variant) : paintFor(theme, variant, color, palette, active);
244
243
  // The theme's one field radius, so a button and the input beside it agree.
@@ -248,7 +247,7 @@ function buttonLook(theme: UiTheme, props: ResolvedProps): ButtonLook {
248
247
  // ring turns it visible; on native the glow's shadow needs it visible too,
249
248
  // because a view's own shadow is clipped by its own overflow there.
250
249
  const overflow: ViewStyle['overflow'] = pulse || glow ? 'visible' : 'hidden';
251
- const sized = sizeStyle(theme, size, iconOnly, icon != null && !loading);
250
+ const sized = sizeStyle(theme, size, iconOnly, icon != null);
252
251
  const glowing = glow && !inactive ? glowStyle(palette) : null;
253
252
 
254
253
  return {
@@ -275,18 +274,6 @@ function buttonLook(theme: UiTheme, props: ResolvedProps): ButtonLook {
275
274
  };
276
275
  }
277
276
 
278
- /** The spinner that replaces the label while loading; `role` comes from react-native's ActivityIndicator. */
279
- function Spinner({ color, testID }: { color: string; testID: string }): React.JSX.Element {
280
- return (
281
- <ActivityIndicator
282
- size={Platform.OS === 'ios' ? 'small' : BUTTON_SPINNER_SIZE}
283
- color={color}
284
- testID={testID}
285
- accessibilityLabel="loading"
286
- />
287
- );
288
- }
289
-
290
277
  export const Button = React.forwardRef<View, ButtonProps>((rawProps, ref) => {
291
278
  const resolved = resolveButtonProps(rawProps);
292
279
  const { loading, icon, iconPosition, pulse, ripple, children, onClick, onPress, ...others } = resolved;
@@ -306,12 +293,11 @@ export const Button = React.forwardRef<View, ButtonProps>((rawProps, ref) => {
306
293
  onPress?.(event);
307
294
  };
308
295
 
309
- const iconNode =
310
- !loading && icon ? (
311
- <View style={styles.icon} testID={idFor('icon')}>
312
- {icon}
313
- </View>
314
- ) : null;
296
+ const iconNode = icon ? (
297
+ <View style={[styles.icon, hiddenWhile(loading)]} testID={idFor('icon')}>
298
+ {icon}
299
+ </View>
300
+ ) : null;
315
301
 
316
302
  return (
317
303
  <Pressable
@@ -319,6 +305,7 @@ export const Button = React.forwardRef<View, ButtonProps>((rawProps, ref) => {
319
305
  role="button"
320
306
  aria-disabled={look.inactive}
321
307
  aria-busy={loading}
308
+ aria-label={loadingName(loading, children)}
322
309
  disabled={look.inactive}
323
310
  onPress={handlePress}
324
311
  android_ripple={ripple && !look.inactive ? { color: alpha(look.palette.main, 0.2), borderless: false } : undefined}
@@ -328,8 +315,11 @@ export const Button = React.forwardRef<View, ButtonProps>((rawProps, ref) => {
328
315
  >
329
316
  {pulse && !look.inactive ? <Pulse color={look.palette.main} radius={look.radius} testID={idFor('pulse')} /> : null}
330
317
  {iconPosition === 'left' ? iconNode : null}
331
- {loading ? <Spinner color={look.label.color as string} testID={idFor('loading')} /> : renderTextChildren(children, look.label, 1)}
318
+ <Label loading={loading} label={look.label} testID={idFor('busy-label')}>
319
+ {children}
320
+ </Label>
332
321
  {iconPosition === 'right' ? iconNode : null}
322
+ {loading ? <Spinner color={look.label.color as string} testID={idFor('loading')} /> : null}
333
323
  </Pressable>
334
324
  );
335
325
  });
@@ -100,20 +100,44 @@ const muiColorFor = (color: NonNullable<ButtonProps['color']>): MuiButtonColor =
100
100
  */
101
101
  const iconGlyphStyle = (theme: Theme) => ({ fontSize: rem(theme, BUTTON_ICON_GLYPH_SIZE) });
102
102
 
103
+ /**
104
+ * The label while the button loads (FUT-3646): still in the DOM and in the
105
+ * accessibility tree, only transparent. The spinner used to REPLACE it, which
106
+ * left the button with no accessible name — a screen reader on it heard just
107
+ * "button, dimmed" — and let it shrink to the spinner's width mid-press.
108
+ *
109
+ * `opacity`, never `visibility: hidden` or `display: none`: those two take the
110
+ * text out of the accessibility tree, which is the bug again.
111
+ */
112
+ const LOADING_LABEL_STYLE: React.CSSProperties = { opacity: 0 };
113
+
114
+ /** The spinner, centred over the label it stands in for. */
115
+ const LOADING_SPINNER_STYLE: React.CSSProperties = {
116
+ position: 'absolute',
117
+ inset: 0,
118
+ display: 'flex',
119
+ alignItems: 'center',
120
+ justifyContent: 'center',
121
+ };
122
+
123
+ /**
124
+ * The icon slot while loading keeps its glyph, transparent like the label
125
+ * (FUT-3646): dropping it made an icon-and-label button jump 28px narrower the
126
+ * moment it started loading, and a square icon button turn into a 64px slab.
127
+ */
128
+ const iconSlotStyle = (theme: Theme, loading: boolean | undefined): React.CSSProperties =>
129
+ loading ? { ...iconGlyphStyle(theme), ...LOADING_LABEL_STYLE } : iconGlyphStyle(theme);
130
+
103
131
  /**
104
132
  * An icon and nothing else — the shape that should render square.
105
133
  *
106
134
  * A separate predicate rather than three clauses inline: the component function
107
135
  * is already at the complexity ceiling, and "what counts as an icon button" is
108
- * a rule worth naming anyway. `loading` disqualifies, because a spinner
109
- * replaces the children and would otherwise make every loading button square.
136
+ * a rule worth naming anyway. Loading does not change it: the icon stays,
137
+ * transparent under the spinner, so the button keeps its square.
110
138
  */
111
- function isIconOnly({
112
- loading,
113
- icon,
114
- children,
115
- }: Pick<ButtonProps, 'loading' | 'icon' | 'children'>): boolean {
116
- return !loading && icon != null && children == null;
139
+ function isIconOnly({ icon, children }: Pick<ButtonProps, 'icon' | 'children'>): boolean {
140
+ return icon != null && children == null;
117
141
  }
118
142
 
119
143
  /**
@@ -163,15 +187,14 @@ const ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonProps>(
163
187
  const theme = useTheme();
164
188
 
165
189
  // Wrap icon with testId if provided
166
- const iconWithTestId =
167
- !loading && icon ? (
168
- <span data-testid={testId('icon')} style={iconGlyphStyle(theme)}>
169
- {icon}
170
- </span>
171
- ) : undefined;
190
+ const iconWithTestId = icon ? (
191
+ <span data-testid={testId('icon')} style={iconSlotStyle(theme, loading)}>
192
+ {icon}
193
+ </span>
194
+ ) : undefined;
172
195
 
173
196
  // Only an icon, so the button is a square rather than a 64px slab.
174
- const iconOnly = isIconOnly({ loading, icon, children });
197
+ const iconOnly = isIconOnly({ icon, children });
175
198
 
176
199
  const mergedClassName =
177
200
  [active ? 'active' : '', props.className].filter(Boolean).join(' ') || undefined;
@@ -188,6 +211,7 @@ const ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonProps>(
188
211
  pulse={pulse}
189
212
  ripple={ripple}
190
213
  disabled={disabled || loading}
214
+ aria-busy={loading ? true : undefined}
191
215
  disableRipple={!ripple}
192
216
  startIcon={iconPosition === 'left' ? iconWithTestId : undefined}
193
217
  endIcon={iconPosition === 'right' ? iconWithTestId : undefined}
@@ -200,7 +224,14 @@ const ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonProps>(
200
224
  className={mergedClassName}
201
225
  >
202
226
  {loading ? (
203
- <CircularProgress size={rem(theme, 16)} color="inherit" data-testid={testId('loading')} />
227
+ <>
228
+ <span style={LOADING_LABEL_STYLE} data-testid={testId('busy-label')}>
229
+ {children}
230
+ </span>
231
+ <span style={LOADING_SPINNER_STYLE}>
232
+ <CircularProgress size={rem(theme, 16)} color="inherit" data-testid={testId('loading')} />
233
+ </span>
234
+ </>
204
235
  ) : (
205
236
  children
206
237
  )}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/form/Button/Button.tsx"],"sourcesContent":["import MuiButton from '@mui/material/Button/index.js';\nimport CircularProgress from '@mui/material/CircularProgress/index.js';\nimport { styled, useTheme } from '@mui/material/styles/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport * as React from 'react';\n\nimport { resolveButtonProps } from './Button.helpers';\nimport { BUTTON_ICON_GLYPH_SIZE } from './Button.metrics';\nimport { childTestId, resolveTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { fieldRadius } from '../../../tokens/field-radius';\nimport { rem } from '../../../tokens/relative';\nimport {\n buttonEmphasisStyles,\n buttonVariantStyles,\n getColorFromTheme,\n iconAlignmentStyles,\n buttonSize,\n} from './Button.styles';\nimport type { ButtonComponent, ButtonProps } from './Button.types';\n\nconst StyledButton = styled(MuiButton, {\n shouldForwardProp: (prop) =>\n ['glow', 'pulse', 'loading', 'customVariant', 'customColor', 'ripple'].indexOf(\n prop as string,\n ) === -1,\n})<{\n customVariant?: string;\n customColor?: string;\n glow?: boolean;\n pulse?: boolean;\n ripple?: boolean;\n}>(({ theme, customVariant, customColor = 'primary', glow, pulse }) => {\n const colorPalette = getColorFromTheme(theme, customColor);\n\n return {\n textTransform: 'none',\n fontWeight: 500,\n borderRadius: fieldRadius(theme),\n transition: 'all 0.3s ease',\n position: 'relative',\n overflow: 'hidden',\n ...iconAlignmentStyles(theme),\n ...buttonVariantStyles(theme, customVariant, colorPalette, customColor),\n ...buttonEmphasisStyles(theme, colorPalette, glow, pulse),\n };\n});\n\n/** Our six variants collapse onto MUI's three; only outline and text differ. */\nconst muiVariantFor = (variant?: string): 'outlined' | 'text' | 'contained' => {\n if (variant === 'outline') return 'outlined';\n return variant === 'text' ? 'text' : 'contained';\n};\n\ntype MuiButtonColor = 'inherit' | 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'error';\n\n/**\n * Our five sizes collapse onto MUI's three, so the rendered class NAMES the\n * size the button actually is (12-74).\n *\n * It was never forwarded, so every button — `xs` through `xl` — carried\n * `MuiButton-sizeMedium`. The pixels were right (they come from `buttonSize`'s\n * `sx`, which still decides them) but the DOM said \"medium\" about a large\n * button: a test could never assert the size, and devtools lied to whoever\n * opened them.\n *\n * MUI's own size styles are padding, font-size and the icon glyph. The first\n * two are overridden by that same `sx`; the third is pinned by\n * `BUTTON_ICON_GLYPH_SIZE`, so forwarding this moves nothing on screen.\n */\nconst muiSizeFor = (size: NonNullable<ButtonProps['size']>): 'small' | 'medium' | 'large' => {\n if (size === 'xs' || size === 'sm') return 'small';\n return size === 'md' ? 'medium' : 'large';\n};\n\n/** `danger` and `neutral` are ours; the rest are MUI's own names already. */\nconst muiColorFor = (color: NonNullable<ButtonProps['color']>): MuiButtonColor => {\n if (color === 'danger') return 'error';\n return color === 'neutral' ? 'inherit' : color;\n};\n\n/**\n * The icon slot's font-size, pinned on the wrapper this component already owns\n * (12-74).\n *\n * MUI writes 18/20/22 there off its own `size`, and that prop is now forwarded,\n * so a glyph that takes its size by INHERITANCE would start moving with the\n * button — measured at 18px on `xs` and 22px on `lg` against the 20px every\n * size has always drawn. See {@link BUTTON_ICON_GLYPH_SIZE} for what that does\n * and does not reach.\n *\n * Written as an inline style rather than a selector in `sx` because MUI's own\n * rule sits on the start/end-icon span itself. Beating it from the root means\n * winning a specificity contest at a distance — true in a browser, but not\n * something a test can hold, since jsdom resolves `getComputedStyle` by\n * document order and never by specificity. Inline wins both, and it sits on the\n * one element we already render for this glyph.\n *\n * Through the theme's type scale (FUT-2594): `1.25rem` at MUI's defaults, which\n * is the 20px MUI was emitting before at a 16px root.\n */\nconst iconGlyphStyle = (theme: Theme) => ({ fontSize: rem(theme, BUTTON_ICON_GLYPH_SIZE) });\n\n/**\n * An icon and nothing else — the shape that should render square.\n *\n * A separate predicate rather than three clauses inline: the component function\n * is already at the complexity ceiling, and \"what counts as an icon button\" is\n * a rule worth naming anyway. `loading` disqualifies, because a spinner\n * replaces the children and would otherwise make every loading button square.\n */\nfunction isIconOnly({\n loading,\n icon,\n children,\n}: Pick<ButtonProps, 'loading' | 'icon' | 'children'>): boolean {\n return !loading && icon != null && children == null;\n}\n\n/**\n * The design system's button.\n *\n * Typed as an `OverridableComponent` (12-70) rather than a plain `forwardRef`,\n * which is how MUI models a component that can render as something else. The\n * practical difference is one line at a call site:\n *\n * ```tsx\n * <Button component={Link} to=\"/account/compras/42\">Acompanhar pedido</Button>\n * ```\n *\n * `to` is typed here because the generic carries the target component's own\n * props through. A plain `component?: React.ElementType` — what this was —\n * accepts the component and throws its props away, so `to` typechecked\n * nowhere even though it arrived intact at runtime. A host wanting a router\n * navigation painted as a button had to choose between a type assertion and\n * losing the `href`; both were shipped, and both are the reason this ticket\n * exists.\n */\nconst ButtonRoot = React.forwardRef<HTMLButtonElement, ButtonProps>(\n (rawProps, ref) => {\n const {\n variant,\n color,\n size,\n loading,\n icon,\n iconPosition,\n glow,\n pulse,\n ripple,\n active,\n children,\n disabled,\n onClick,\n onFocus,\n onBlur,\n ...others\n } = resolveButtonProps(rawProps);\n // Every spelling of the test id the shared contract allows, mapped to the\n // one the DOM reads; the native Button does the same in reverse.\n const ownTestId = resolveTestId(others, 'button');\n const testId = (suffix: string) => childTestId(others, suffix, 'button');\n const props = withoutTestIdProps(others);\n const theme = useTheme();\n\n // Wrap icon with testId if provided\n const iconWithTestId =\n !loading && icon ? (\n <span data-testid={testId('icon')} style={iconGlyphStyle(theme)}>\n {icon}\n </span>\n ) : undefined;\n\n // Only an icon, so the button is a square rather than a 64px slab.\n const iconOnly = isIconOnly({ loading, icon, children });\n\n const mergedClassName =\n [active ? 'active' : '', props.className].filter(Boolean).join(' ') || undefined;\n\n return (\n <StyledButton\n ref={ref}\n variant={muiVariantFor(variant)}\n color={muiColorFor(color)}\n size={muiSizeFor(size)}\n customVariant={variant}\n customColor={color}\n glow={glow}\n pulse={pulse}\n ripple={ripple}\n disabled={disabled || loading}\n disableRipple={!ripple}\n startIcon={iconPosition === 'left' ? iconWithTestId : undefined}\n endIcon={iconPosition === 'right' ? iconWithTestId : undefined}\n onClick={onClick}\n onFocus={onFocus}\n onBlur={onBlur}\n sx={buttonSize(size, iconOnly)}\n data-testid={ownTestId}\n {...props}\n className={mergedClassName}\n >\n {loading ? (\n <CircularProgress size={rem(theme, 16)} color=\"inherit\" data-testid={testId('loading')} />\n ) : (\n children\n )}\n </StyledButton>\n );\n },\n);\n\nButtonRoot.displayName = 'Button';\n\n/**\n * The cast is what publishes the generic — `forwardRef` cannot express\n * \"renders as C, and takes C's props\" on its own, so MUI's own components do\n * exactly this. `ButtonRoot` above is the implementation and is not exported.\n */\nexport const Button = ButtonRoot as ButtonComponent;\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/components/form/Button/Button.native.tsx","../../../src/components/form/Button/Button.helpers.ts","../../../src/components/form/Button/Button.metrics.ts"],"sourcesContent":["import * as React from 'react';\nimport {\n ActivityIndicator,\n Animated,\n Easing,\n Platform,\n Pressable,\n StyleSheet,\n View,\n type GestureResponderEvent,\n type PressableStateCallbackType,\n type StyleProp,\n type TextStyle,\n type ViewStyle,\n} from 'react-native';\n\nimport { resolveButtonProps } from './Button.helpers';\nimport {\n BUTTON_FONT_WEIGHT,\n BUTTON_ICON_GAP_UNITS,\n BUTTON_LINE_HEIGHT,\n BUTTON_SIZES,\n BUTTON_SPINNER_SIZE,\n BUTTON_WASH_ALPHA,\n GLASS_BACKGROUND_ALPHA,\n GLASS_BACKGROUND_ALPHA_PRESSED,\n GLASS_BORDER_ALPHA,\n GLOW_INNER_ALPHA,\n GLOW_RADIUS,\n PULSE_ALPHA,\n PULSE_DURATION_MS,\n PULSE_SPREAD,\n buttonPalette,\n gradientStops,\n quietInk,\n} from './Button.metrics';\nimport type { ButtonProps, ButtonVariant } from './Button.types.native';\nimport { renderTextChildren } from '../../../platform/text-children';\nimport { childTestId, resolveTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { useUiTheme } from '../../../provider/use-ui-theme.native';\nimport { resolveFieldEdge } from '../../../tokens/field-edge.core';\nimport { FIELD_BORDER_WIDTH, fieldHeightPx } from '../../../tokens/field-height.core';\nimport { alpha } from '../../../tokens/color';\nimport type { ColorValue, SizeValue } from '../../../tokens/vocabulary';\nimport type { UiPaletteColor, UiTheme } from '../../../tokens/theme';\n\n/** MUI's `min-width` for a button that carries a label. */\nconst MIN_WIDTH = 64;\n\ninterface Paint {\n container: ViewStyle;\n pressed: ViewStyle;\n label: TextStyle;\n}\n\n/**\n * What the web's `VARIANT_STYLES` paint, as three React Native styles: at rest,\n * while pressed (the web's `:hover`, which touch has no equivalent of), and the\n * label. Same palette arithmetic, same alphas.\n *\n * `gradient` is the one honest gap: React Native core has no gradient fill, so\n * it paints the gradient's first stop. A host wanting the real thing adds a\n * gradient library; the parity ledger records this.\n */\n/** A coloured outline: its colour for the border and the label, washed when pressed. */\nfunction outlineOf(palette: UiPaletteColor, wash: string): Paint {\n return {\n container: { backgroundColor: 'transparent', borderWidth: FIELD_BORDER_WIDTH, borderColor: palette.main },\n pressed: { backgroundColor: wash, borderColor: palette.dark },\n label: { color: palette.main },\n };\n}\n\n/**\n * A neutral outline, as on the web: it rests on the one field border and\n * writes in the theme's text ink, a role that turns with the mode — grey 700\n * was dark on a dark ground.\n */\nfunction neutralOutline(theme: UiTheme): Paint {\n const ink = theme.palette.text.primary;\n return {\n container: {\n backgroundColor: 'transparent',\n borderWidth: FIELD_BORDER_WIDTH,\n borderColor: resolveFieldEdge(theme.palette.divider, theme.palette.background.paper),\n },\n pressed: { backgroundColor: alpha(ink, BUTTON_WASH_ALPHA), borderColor: theme.palette.text.secondary },\n label: { color: ink },\n };\n}\n\nfunction paintFor(\n theme: UiTheme,\n variant: ButtonVariant,\n color: ColorValue,\n palette: UiPaletteColor,\n active: boolean,\n): Paint {\n const wash = alpha(palette.main, BUTTON_WASH_ALPHA);\n switch (variant) {\n case 'outline':\n return color === 'neutral' ? neutralOutline(theme) : outlineOf(palette, wash);\n case 'ghost':\n return {\n container: { backgroundColor: 'transparent' },\n pressed: { backgroundColor: wash },\n label: { color: quietInk(theme, palette) },\n };\n case 'text':\n return {\n container: { backgroundColor: active ? wash : 'transparent' },\n pressed: { backgroundColor: wash },\n label: { color: quietInk(theme, palette) },\n };\n case 'glass':\n return {\n container: {\n backgroundColor: alpha(theme.palette.background.paper, GLASS_BACKGROUND_ALPHA),\n borderWidth: 1,\n borderColor: alpha(theme.palette.divider, GLASS_BORDER_ALPHA),\n },\n pressed: { backgroundColor: alpha(theme.palette.background.paper, GLASS_BACKGROUND_ALPHA_PRESSED) },\n label: { color: palette.main },\n };\n case 'gradient': {\n const [from, to] = gradientStops(theme, color, palette);\n return {\n container: { backgroundColor: from },\n pressed: { backgroundColor: to },\n label: { color: '#fff' },\n };\n }\n default:\n return {\n container: { backgroundColor: palette.main },\n pressed: { backgroundColor: palette.dark },\n label: { color: palette.contrastText || '#fff' },\n };\n }\n}\n\n/** MUI's disabled treatment per variant family, so a disabled button reads as one on both sides. */\nfunction disabledPaint(theme: UiTheme, variant: ButtonVariant): Paint {\n const filled = variant === 'solid' || variant === 'gradient' || variant === 'glass';\n return {\n container: filled\n ? { backgroundColor: theme.palette.action.disabledBackground, borderWidth: 0 }\n : variant === 'outline'\n ? { backgroundColor: 'transparent', borderWidth: 1, borderColor: theme.palette.action.disabledBackground }\n : { backgroundColor: 'transparent' },\n pressed: {},\n label: { color: theme.palette.action.disabled },\n };\n}\n\nfunction sizeStyle(theme: UiTheme, size: SizeValue, iconOnly: boolean, hasIcon: boolean): ViewStyle {\n const metrics = BUTTON_SIZES[size];\n // The theme's field height for the size, as on the web: a button stands\n // level with the field beside it, and an icon-only one is that square.\n const height = fieldHeightPx(theme.fieldHeight, size);\n if (iconOnly) {\n return { padding: 0, minWidth: height, minHeight: height };\n }\n return {\n minHeight: height,\n paddingVertical: 0,\n paddingHorizontal: metrics.paddingHorizontal,\n minWidth: MIN_WIDTH,\n gap: hasIcon ? theme.spacing(BUTTON_ICON_GAP_UNITS) : undefined,\n };\n}\n\nfunction glowStyle(palette: UiPaletteColor): ViewStyle {\n return Platform.select<ViewStyle>({\n android: { elevation: 8 },\n default: {\n shadowColor: palette.main,\n shadowOpacity: GLOW_INNER_ALPHA,\n shadowRadius: GLOW_RADIUS,\n shadowOffset: { width: 0, height: 0 },\n },\n });\n}\n\n/** The web's `::after` pulse ring: a looped scale-and-fade behind the button. */\nfunction Pulse({ color, radius, testID }: { color: string; radius: number; testID: string }): React.JSX.Element {\n const progress = React.useRef(new Animated.Value(0)).current;\n\n React.useEffect(() => {\n const loop = Animated.loop(\n Animated.timing(progress, {\n toValue: 1,\n duration: PULSE_DURATION_MS,\n easing: Easing.out(Easing.ease),\n useNativeDriver: Platform.OS !== 'web',\n }),\n );\n loop.start();\n return () => loop.stop();\n }, [progress]);\n\n return (\n <Animated.View\n testID={testID}\n aria-hidden\n style={[\n StyleSheet.absoluteFill,\n {\n pointerEvents: 'none',\n borderRadius: radius,\n backgroundColor: color,\n opacity: progress.interpolate({ inputRange: [0, 0.7, 1], outputRange: [PULSE_ALPHA, 0, 0] }),\n transform: [\n { scale: progress.interpolate({ inputRange: [0, 0.7, 1], outputRange: [1, 1 + PULSE_SPREAD / 40, 1] }) },\n ],\n },\n ]}\n />\n );\n}\n\n\ntype ResolvedProps = ReturnType<typeof resolveButtonProps<ButtonProps>>;\n\ninterface ButtonLook {\n inactive: boolean;\n palette: UiPaletteColor;\n radius: number;\n label: TextStyle;\n container: (state: PressableStateCallbackType) => StyleProp<ViewStyle>;\n}\n\n/**\n * Everything the button paints, decided once per render from the resolved\n * props and the theme. Kept out of the component so the component reads as\n * structure — what renders where — and this reads as arithmetic.\n */\nfunction buttonLook(theme: UiTheme, props: ResolvedProps): ButtonLook {\n const { variant, color, size, loading, icon, children, glow, pulse, active, disabled, style } = props;\n const palette = buttonPalette(theme, color);\n const iconOnly = !loading && icon != null && children == null;\n const inactive = Boolean(disabled) || loading;\n const paint = inactive ? disabledPaint(theme, variant) : paintFor(theme, variant, color, palette, active);\n // The theme's one field radius, so a button and the input beside it agree.\n const radius = theme.radius.field;\n const metrics = BUTTON_SIZES[size];\n // The web button is `overflow: hidden` (its ripple needs it) unless the pulse\n // ring turns it visible; on native the glow's shadow needs it visible too,\n // because a view's own shadow is clipped by its own overflow there.\n const overflow: ViewStyle['overflow'] = pulse || glow ? 'visible' : 'hidden';\n const sized = sizeStyle(theme, size, iconOnly, icon != null && !loading);\n const glowing = glow && !inactive ? glowStyle(palette) : null;\n\n return {\n inactive,\n palette,\n radius,\n label: {\n fontFamily: theme.typography.fontFamily,\n fontSize: metrics.fontSize,\n lineHeight: metrics.fontSize * BUTTON_LINE_HEIGHT,\n fontWeight: String(BUTTON_FONT_WEIGHT) as TextStyle['fontWeight'],\n textAlign: 'center',\n ...paint.label,\n },\n container: (state) => [\n styles.base,\n { borderRadius: radius, overflow },\n sized,\n paint.container,\n state.pressed && !inactive ? paint.pressed : null,\n glowing,\n style,\n ],\n };\n}\n\n/** The spinner that replaces the label while loading; `role` comes from react-native's ActivityIndicator. */\nfunction Spinner({ color, testID }: { color: string; testID: string }): React.JSX.Element {\n return (\n <ActivityIndicator\n size={Platform.OS === 'ios' ? 'small' : BUTTON_SPINNER_SIZE}\n color={color}\n testID={testID}\n accessibilityLabel=\"loading\"\n />\n );\n}\n\nexport const Button = React.forwardRef<View, ButtonProps>((rawProps, ref) => {\n const resolved = resolveButtonProps(rawProps);\n const { loading, icon, iconPosition, pulse, ripple, children, onClick, onPress, ...others } = resolved;\n const theme = useUiTheme();\n const look = buttonLook(theme, resolved);\n\n // The shared stories pass `'data-testid'` as a plain prop; honour it here and\n // keep all three spellings off the element (react-native-web would drop them).\n const testId = resolveTestId(others, 'button');\n const idFor = (suffix: string) => childTestId(others, suffix, 'button');\n const rest = withoutTestIdProps(others);\n // `buttonLook` consumed these; they are not Pressable props.\n const { variant: _v, color: _c, size: _s, glow: _g, active: _a, disabled: _d, style: _st, ...pressable } = rest;\n\n const handlePress = (event: GestureResponderEvent): void => {\n onClick?.(event);\n onPress?.(event);\n };\n\n const iconNode =\n !loading && icon ? (\n <View style={styles.icon} testID={idFor('icon')}>\n {icon}\n </View>\n ) : null;\n\n return (\n <Pressable\n ref={ref}\n role=\"button\"\n aria-disabled={look.inactive}\n aria-busy={loading}\n disabled={look.inactive}\n onPress={handlePress}\n android_ripple={ripple && !look.inactive ? { color: alpha(look.palette.main, 0.2), borderless: false } : undefined}\n style={look.container}\n testID={testId}\n {...pressable}\n >\n {pulse && !look.inactive ? <Pulse color={look.palette.main} radius={look.radius} testID={idFor('pulse')} /> : null}\n {iconPosition === 'left' ? iconNode : null}\n {loading ? <Spinner color={look.label.color as string} testID={idFor('loading')} /> : renderTextChildren(children, look.label, 1)}\n {iconPosition === 'right' ? iconNode : null}\n </Pressable>\n );\n});\n\nButton.displayName = 'Button';\n\nconst styles = StyleSheet.create({\n base: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n alignSelf: 'flex-start',\n position: 'relative',\n },\n icon: {\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n","import type { ButtonBaseProps } from './Button.base';\n\ntype ButtonDefaultedKeys =\n | 'variant'\n | 'color'\n | 'size'\n | 'loading'\n | 'iconPosition'\n | 'glow'\n | 'pulse'\n | 'ripple'\n | 'active';\n\n/**\n * Generic over the renderer's own props: the web and the native `Button` pass\n * different handler types through, and both come back out untouched.\n */\ntype ResolvedButtonProps<P extends ButtonBaseProps> = P &\n Required<Pick<ButtonBaseProps, ButtonDefaultedKeys>>;\n\nconst BUTTON_DEFAULTS: Required<Pick<ButtonBaseProps, ButtonDefaultedKeys>> = {\n variant: 'solid',\n color: 'primary',\n size: 'md',\n loading: false,\n iconPosition: 'left',\n glow: false,\n pulse: false,\n ripple: true,\n active: false,\n};\n\n// Strips explicitly-undefined props before the merge, so `size={undefined}` still\n// falls back to the default exactly as a destructuring default would.\nconst definedProps = <P extends ButtonBaseProps>(props: P): Partial<P> =>\n Object.fromEntries(\n Object.entries(props).filter(([, value]) => value !== undefined),\n ) as Partial<P>;\n\nexport const resolveButtonProps = <P extends ButtonBaseProps>(props: P): ResolvedButtonProps<P> =>\n ({ ...BUTTON_DEFAULTS, ...definedProps(props) }) as ResolvedButtonProps<P>;\n","import { darken, lighten } from '../../../tokens/color';\nimport { ABSOLUTE_INK, NEUTRAL_RAMP } from '../../../tokens/ink.core';\nimport type { ColorValue, SizeValue } from '../../../tokens/vocabulary';\nimport type { UiPaletteColor, UiTheme } from '../../../tokens/theme';\n\n/**\n * THE NUMBERS BOTH `Button` RENDERERS DRAW WITH.\n *\n * `Button.styles.ts` (web, emotion) and `Button.native.tsx` (React Native)\n * read this one table. The web turns the px into rem and the padding into a\n * shorthand string; native uses the numbers as they are. Neither restates a\n * value, which is what keeps a native `md` button 16px tall of padding and\n * 16px of type exactly like the web one.\n */\nexport interface ButtonSizeMetrics {\n paddingVertical: number;\n paddingHorizontal: number;\n fontSize: number;\n}\n\nexport const BUTTON_SIZES: Record<SizeValue, ButtonSizeMetrics> = {\n xs: { paddingVertical: 2, paddingHorizontal: 8, fontSize: 12 },\n sm: { paddingVertical: 6, paddingHorizontal: 12, fontSize: 14 },\n md: { paddingVertical: 8, paddingHorizontal: 16, fontSize: 16 },\n lg: { paddingVertical: 10, paddingHorizontal: 20, fontSize: 18 },\n xl: { paddingVertical: 12, paddingHorizontal: 24, fontSize: 20 },\n};\n\n/** A button that is only an icon is square: uniform padding, no min-width. */\nexport const ICON_ONLY_PADDING: Record<SizeValue, number> = {\n xs: 2,\n sm: 5,\n md: 7,\n lg: 9,\n xl: 11,\n};\n\n/**\n * The start/end icon slot's font-size, in px — MUI's `medium` value, pinned\n * (12-74).\n *\n * MUI sizes that slot off its OWN `size` prop, writing 18/20/22 onto the first\n * child of `.MuiButton-startIcon`. We never passed a `size`, so every button in\n * every size has always drawn medium's 20px; now that `size` IS forwarded — so\n * the rendered class finally names the size the button actually is — the number\n * would start moving with it.\n *\n * WHAT THAT ACTUALLY REACHES, measured in a browser rather than assumed: this\n * Button renders its own wrapper span around the icon (it carries the child\n * test id), so MUI's rule lands on the WRAPPER and reaches the glyph only when\n * the glyph takes its size by inheritance — `fontSize=\"inherit\"`, an em-sized\n * SVG, a text or emoji glyph. With the pin removed, such a glyph measured 18px\n * at `xs`/`sm` and 22px at `lg`/`xl` against the 20px it has always been. An\n * icon carrying an absolute size of its own does not move at all: a default\n * `SvgIcon` stayed 24×24 across all five.\n *\n * A silent resize of the inherited case is still a design decision arriving as\n * a side effect of a class-name fix, so it is pinned to what shipped before —\n * `Button.tsx` puts it on that same wrapper. Scaling the glyph with the button\n * is a ticket of its own.\n */\nexport const BUTTON_ICON_GLYPH_SIZE = 20;\n\nexport const BUTTON_FONT_WEIGHT = 500;\n/** Between the icon and the label: `theme.spacing(0.5)`. */\nexport const BUTTON_ICON_GAP_UNITS = 0.5;\nexport const BUTTON_SPINNER_SIZE = 16;\n/** MUI's `line-height` for button text. */\nexport const BUTTON_LINE_HEIGHT = 1.75;\n\n/** The wash behind an outline, ghost or text button while it is hovered or pressed. */\nexport const BUTTON_WASH_ALPHA = 0.1;\nexport const GLASS_BACKGROUND_ALPHA = 0.1;\nexport const GLASS_BACKGROUND_ALPHA_PRESSED = 0.2;\nexport const GLASS_BORDER_ALPHA = 0.2;\nexport const GLOW_INNER_ALPHA = 0.6;\nexport const GLOW_OUTER_ALPHA = 0.3;\nexport const GLOW_RADIUS = 20;\nexport const PULSE_ALPHA = 0.3;\nexport const PULSE_DURATION_MS = 2000;\nexport const PULSE_SPREAD = 15;\n\n/**\n * `neutral` has no palette slot of its own in MUI, so `Button.styles.ts` has\n * always drawn it from three greys. Same three greys here.\n */\nexport function buttonPalette(theme: UiTheme, color: ColorValue): UiPaletteColor {\n if (color === 'neutral') {\n return {\n main: NEUTRAL_RAMP[700],\n dark: NEUTRAL_RAMP[800],\n light: NEUTRAL_RAMP[500],\n contrastText: ABSOLUTE_INK.white,\n };\n }\n return theme.palette[color];\n}\n\n/**\n * The label colour for `ghost` and `text`: one step darker than `main` in\n * light mode (lighter in dark), because `main` is chosen to sit UNDER white and\n * is not legible AS text on a tinted surface. See the web `quietInk`.\n */\nexport const quietInk = (theme: UiTheme, palette: UiPaletteColor): string =>\n theme.mode === 'dark' ? lighten(palette.main, 0.35) : darken(palette.main, 0.3);\n\n/** Gradients run between two related hues; the named colours pick a specific pair. */\nexport function gradientStops(\n theme: UiTheme,\n color: ColorValue,\n palette: UiPaletteColor,\n): [string, string] {\n switch (color) {\n case 'primary':\n return [theme.palette.primary.main, theme.palette.secondary.main];\n case 'secondary':\n return [theme.palette.secondary.main, theme.palette.primary.main];\n case 'success':\n case 'warning':\n return [theme.palette[color].light, theme.palette[color].dark];\n case 'danger':\n return [theme.palette.danger.light, theme.palette.danger.dark];\n default:\n return [palette.main, palette.dark];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;;;ACMP,IAAM,kBAAwE;AAAA,EAC5E,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,cAAc;AAAA,EACd,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAIA,IAAM,eAAe,CAA4B,UAC/C,OAAO;AAAA,EACL,OAAO,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,UAAU,MAAS;AACjE;AAEK,IAAM,qBAAqB,CAA4B,WAC3D,EAAE,GAAG,iBAAiB,GAAG,aAAa,KAAK,EAAE;;;ACpBzC,IAAM,eAAqD;AAAA,EAChE,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,UAAU,GAAG;AAAA,EAC7D,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC9D,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC9D,IAAI,EAAE,iBAAiB,IAAI,mBAAmB,IAAI,UAAU,GAAG;AAAA,EAC/D,IAAI,EAAE,iBAAiB,IAAI,mBAAmB,IAAI,UAAU,GAAG;AACjE;AAqCO,IAAM,qBAAqB;AAE3B,IAAM,wBAAwB;AAC9B,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB;AAG3B,IAAM,oBAAoB;AAC1B,IAAM,yBAAyB;AAC/B,IAAM,iCAAiC;AACvC,IAAM,qBAAqB;AAC3B,IAAM,mBAAmB;AAEzB,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,oBAAoB;AAC1B,IAAM,eAAe;AAMrB,SAAS,cAAc,OAAgB,OAAmC;AAC/E,MAAI,UAAU,WAAW;AACvB,WAAO;AAAA,MACL,MAAM,aAAa,GAAG;AAAA,MACtB,MAAM,aAAa,GAAG;AAAA,MACtB,OAAO,aAAa,GAAG;AAAA,MACvB,cAAc,aAAa;AAAA,IAC7B;AAAA,EACF;AACA,SAAO,MAAM,QAAQ,KAAK;AAC5B;AAOO,IAAM,WAAW,CAAC,OAAgB,YACvC,MAAM,SAAS,SAAS,QAAQ,QAAQ,MAAM,IAAI,IAAI,OAAO,QAAQ,MAAM,GAAG;AAGzE,SAAS,cACd,OACA,OACA,SACkB;AAClB,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,QAAQ,MAAM,MAAM,QAAQ,UAAU,IAAI;AAAA,IAClE,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,UAAU,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAAA,IAClE,KAAK;AAAA,IACL,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,KAAK,EAAE,OAAO,MAAM,QAAQ,KAAK,EAAE,IAAI;AAAA,IAC/D,KAAK;AACH,aAAO,CAAC,MAAM,QAAQ,OAAO,OAAO,MAAM,QAAQ,OAAO,IAAI;AAAA,IAC/D;AACE,aAAO,CAAC,QAAQ,MAAM,QAAQ,IAAI;AAAA,EACtC;AACF;;;AF6EI,cAkHA,YAlHA;AA3JJ,IAAM,YAAY;AAkBlB,SAAS,UAAU,SAAyB,MAAqB;AAC/D,SAAO;AAAA,IACL,WAAW,EAAE,iBAAiB,eAAe,aAAa,oBAAoB,aAAa,QAAQ,KAAK;AAAA,IACxG,SAAS,EAAE,iBAAiB,MAAM,aAAa,QAAQ,KAAK;AAAA,IAC5D,OAAO,EAAE,OAAO,QAAQ,KAAK;AAAA,EAC/B;AACF;AAOA,SAAS,eAAe,OAAuB;AAC7C,QAAM,MAAM,MAAM,QAAQ,KAAK;AAC/B,SAAO;AAAA,IACL,WAAW;AAAA,MACT,iBAAiB;AAAA,MACjB,aAAa;AAAA,MACb,aAAa,iBAAiB,MAAM,QAAQ,SAAS,MAAM,QAAQ,WAAW,KAAK;AAAA,IACrF;AAAA,IACA,SAAS,EAAE,iBAAiB,MAAM,KAAK,iBAAiB,GAAG,aAAa,MAAM,QAAQ,KAAK,UAAU;AAAA,IACrG,OAAO,EAAE,OAAO,IAAI;AAAA,EACtB;AACF;AAEA,SAAS,SACP,OACA,SACA,OACA,SACA,QACO;AACP,QAAM,OAAO,MAAM,QAAQ,MAAM,iBAAiB;AAClD,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,aAAO,UAAU,YAAY,eAAe,KAAK,IAAI,UAAU,SAAS,IAAI;AAAA,IAC9E,KAAK;AACH,aAAO;AAAA,QACL,WAAW,EAAE,iBAAiB,cAAc;AAAA,QAC5C,SAAS,EAAE,iBAAiB,KAAK;AAAA,QACjC,OAAO,EAAE,OAAO,SAAS,OAAO,OAAO,EAAE;AAAA,MAC3C;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,WAAW,EAAE,iBAAiB,SAAS,OAAO,cAAc;AAAA,QAC5D,SAAS,EAAE,iBAAiB,KAAK;AAAA,QACjC,OAAO,EAAE,OAAO,SAAS,OAAO,OAAO,EAAE;AAAA,MAC3C;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,WAAW;AAAA,UACT,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,sBAAsB;AAAA,UAC7E,aAAa;AAAA,UACb,aAAa,MAAM,MAAM,QAAQ,SAAS,kBAAkB;AAAA,QAC9D;AAAA,QACA,SAAS,EAAE,iBAAiB,MAAM,MAAM,QAAQ,WAAW,OAAO,8BAA8B,EAAE;AAAA,QAClG,OAAO,EAAE,OAAO,QAAQ,KAAK;AAAA,MAC/B;AAAA,IACF,KAAK,YAAY;AACf,YAAM,CAAC,MAAM,EAAE,IAAI,cAAc,OAAO,OAAO,OAAO;AACtD,aAAO;AAAA,QACL,WAAW,EAAE,iBAAiB,KAAK;AAAA,QACnC,SAAS,EAAE,iBAAiB,GAAG;AAAA,QAC/B,OAAO,EAAE,OAAO,OAAO;AAAA,MACzB;AAAA,IACF;AAAA,IACA;AACE,aAAO;AAAA,QACL,WAAW,EAAE,iBAAiB,QAAQ,KAAK;AAAA,QAC3C,SAAS,EAAE,iBAAiB,QAAQ,KAAK;AAAA,QACzC,OAAO,EAAE,OAAO,QAAQ,gBAAgB,OAAO;AAAA,MACjD;AAAA,EACJ;AACF;AAGA,SAAS,cAAc,OAAgB,SAA+B;AACpE,QAAM,SAAS,YAAY,WAAW,YAAY,cAAc,YAAY;AAC5E,SAAO;AAAA,IACL,WAAW,SACP,EAAE,iBAAiB,MAAM,QAAQ,OAAO,oBAAoB,aAAa,EAAE,IAC3E,YAAY,YACV,EAAE,iBAAiB,eAAe,aAAa,GAAG,aAAa,MAAM,QAAQ,OAAO,mBAAmB,IACvG,EAAE,iBAAiB,cAAc;AAAA,IACvC,SAAS,CAAC;AAAA,IACV,OAAO,EAAE,OAAO,MAAM,QAAQ,OAAO,SAAS;AAAA,EAChD;AACF;AAEA,SAAS,UAAU,OAAgB,MAAiB,UAAmB,SAA6B;AAClG,QAAM,UAAU,aAAa,IAAI;AAGjC,QAAM,SAAS,cAAc,MAAM,aAAa,IAAI;AACpD,MAAI,UAAU;AACZ,WAAO,EAAE,SAAS,GAAG,UAAU,QAAQ,WAAW,OAAO;AAAA,EAC3D;AACA,SAAO;AAAA,IACL,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,mBAAmB,QAAQ;AAAA,IAC3B,UAAU;AAAA,IACV,KAAK,UAAU,MAAM,QAAQ,qBAAqB,IAAI;AAAA,EACxD;AACF;AAEA,SAAS,UAAU,SAAoC;AACrD,SAAO,SAAS,OAAkB;AAAA,IAChC,SAAS,EAAE,WAAW,EAAE;AAAA,IACxB,SAAS;AAAA,MACP,aAAa,QAAQ;AAAA,MACrB,eAAe;AAAA,MACf,cAAc;AAAA,MACd,cAAc,EAAE,OAAO,GAAG,QAAQ,EAAE;AAAA,IACtC;AAAA,EACF,CAAC;AACH;AAGA,SAAS,MAAM,EAAE,OAAO,QAAQ,OAAO,GAAyE;AAC9G,QAAM,WAAiB,aAAO,IAAI,SAAS,MAAM,CAAC,CAAC,EAAE;AAErD,EAAM,gBAAU,MAAM;AACpB,UAAM,OAAO,SAAS;AAAA,MACpB,SAAS,OAAO,UAAU;AAAA,QACxB,SAAS;AAAA,QACT,UAAU;AAAA,QACV,QAAQ,OAAO,IAAI,OAAO,IAAI;AAAA,QAC9B,iBAAiB,SAAS,OAAO;AAAA,MACnC,CAAC;AAAA,IACH;AACA,SAAK,MAAM;AACX,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB,GAAG,CAAC,QAAQ,CAAC;AAEb,SACE;AAAA,IAAC,SAAS;AAAA,IAAT;AAAA,MACC;AAAA,MACA,eAAW;AAAA,MACX,OAAO;AAAA,QACL,WAAW;AAAA,QACX;AAAA,UACE,eAAe;AAAA,UACf,cAAc;AAAA,UACd,iBAAiB;AAAA,UACjB,SAAS,SAAS,YAAY,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,GAAG,aAAa,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;AAAA,UAC3F,WAAW;AAAA,YACT,EAAE,OAAO,SAAS,YAAY,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,GAAG,aAAa,CAAC,GAAG,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC,EAAE;AAAA,UACzG;AAAA,QACF;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAkBA,SAAS,WAAW,OAAgB,OAAkC;AACpE,QAAM,EAAE,SAAS,OAAO,MAAM,SAAS,MAAM,UAAU,MAAM,OAAO,QAAQ,UAAU,MAAM,IAAI;AAChG,QAAM,UAAU,cAAc,OAAO,KAAK;AAC1C,QAAM,WAAW,CAAC,WAAW,QAAQ,QAAQ,YAAY;AACzD,QAAM,WAAW,QAAQ,QAAQ,KAAK;AACtC,QAAM,QAAQ,WAAW,cAAc,OAAO,OAAO,IAAI,SAAS,OAAO,SAAS,OAAO,SAAS,MAAM;AAExG,QAAM,SAAS,MAAM,OAAO;AAC5B,QAAM,UAAU,aAAa,IAAI;AAIjC,QAAM,WAAkC,SAAS,OAAO,YAAY;AACpE,QAAM,QAAQ,UAAU,OAAO,MAAM,UAAU,QAAQ,QAAQ,CAAC,OAAO;AACvE,QAAM,UAAU,QAAQ,CAAC,WAAW,UAAU,OAAO,IAAI;AAEzD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACL,YAAY,MAAM,WAAW;AAAA,MAC7B,UAAU,QAAQ;AAAA,MAClB,YAAY,QAAQ,WAAW;AAAA,MAC/B,YAAY,OAAO,kBAAkB;AAAA,MACrC,WAAW;AAAA,MACX,GAAG,MAAM;AAAA,IACX;AAAA,IACA,WAAW,CAAC,UAAU;AAAA,MACpB,OAAO;AAAA,MACP,EAAE,cAAc,QAAQ,SAAS;AAAA,MACjC;AAAA,MACA,MAAM;AAAA,MACN,MAAM,WAAW,CAAC,WAAW,MAAM,UAAU;AAAA,MAC7C;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,QAAQ,EAAE,OAAO,OAAO,GAAyD;AACxF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,SAAS,OAAO,QAAQ,UAAU;AAAA,MACxC;AAAA,MACA;AAAA,MACA,oBAAmB;AAAA;AAAA,EACrB;AAEJ;AAEO,IAAM,SAAe,iBAA8B,CAAC,UAAU,QAAQ;AAC3E,QAAM,WAAW,mBAAmB,QAAQ;AAC5C,QAAM,EAAE,SAAS,MAAM,cAAc,OAAO,QAAQ,UAAU,SAAS,SAAS,GAAG,OAAO,IAAI;AAC9F,QAAM,QAAQ,WAAW;AACzB,QAAM,OAAO,WAAW,OAAO,QAAQ;AAIvC,QAAM,SAAS,cAAc,QAAQ,QAAQ;AAC7C,QAAM,QAAQ,CAAC,WAAmB,YAAY,QAAQ,QAAQ,QAAQ;AACtE,QAAM,OAAO,mBAAmB,MAAM;AAEtC,QAAM,EAAE,SAAS,IAAI,OAAO,IAAI,MAAM,IAAI,MAAM,IAAI,QAAQ,IAAI,UAAU,IAAI,OAAO,KAAK,GAAG,UAAU,IAAI;AAE3G,QAAM,cAAc,CAAC,UAAuC;AAC1D,cAAU,KAAK;AACf,cAAU,KAAK;AAAA,EACjB;AAEA,QAAM,WACJ,CAAC,WAAW,OACV,oBAAC,QAAK,OAAO,OAAO,MAAM,QAAQ,MAAM,MAAM,GAC3C,gBACH,IACE;AAEN,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,iBAAe,KAAK;AAAA,MACpB,aAAW;AAAA,MACX,UAAU,KAAK;AAAA,MACf,SAAS;AAAA,MACT,gBAAgB,UAAU,CAAC,KAAK,WAAW,EAAE,OAAO,MAAM,KAAK,QAAQ,MAAM,GAAG,GAAG,YAAY,MAAM,IAAI;AAAA,MACzG,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACP,GAAG;AAAA,MAEH;AAAA,iBAAS,CAAC,KAAK,WAAW,oBAAC,SAAM,OAAO,KAAK,QAAQ,MAAM,QAAQ,KAAK,QAAQ,QAAQ,MAAM,OAAO,GAAG,IAAK;AAAA,QAC7G,iBAAiB,SAAS,WAAW;AAAA,QACrC,UAAU,oBAAC,WAAQ,OAAO,KAAK,MAAM,OAAiB,QAAQ,MAAM,SAAS,GAAG,IAAK,mBAAmB,UAAU,KAAK,OAAO,CAAC;AAAA,QAC/H,iBAAiB,UAAU,WAAW;AAAA;AAAA;AAAA,EACzC;AAEJ,CAAC;AAED,OAAO,cAAc;AAErB,IAAM,SAAS,WAAW,OAAO;AAAA,EAC/B,MAAM;AAAA,IACJ,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,WAAW;AAAA,IACX,UAAU;AAAA,EACZ;AAAA,EACA,MAAM;AAAA,IACJ,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AACF,CAAC;","names":[]}