@multiplatform.one/forms 7.83.1 → 7.83.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 (210) hide show
  1. package/dist/cjs/Chip/index.cjs +2 -1
  2. package/dist/cjs/Chip/index.native.js +2 -1
  3. package/dist/cjs/Chip/index.native.js.map +1 -1
  4. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.cjs +7 -1
  5. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js +7 -1
  6. package/dist/cjs/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  7. package/dist/esm/AdaptivePopup/index.native.js.map +1 -1
  8. package/dist/esm/Button/IconButtonGroup.native.js.map +1 -1
  9. package/dist/esm/Button/circleGlyph.native.js.map +1 -1
  10. package/dist/esm/Button/iconButtonGroupContext.native.js.map +1 -1
  11. package/dist/esm/Button/index.native.js.map +1 -1
  12. package/dist/esm/Button/webGlass.native.js.map +1 -1
  13. package/dist/esm/Chip/index.mjs +3 -3
  14. package/dist/esm/Chip/index.mjs.map +1 -1
  15. package/dist/esm/Chip/index.native.js +3 -3
  16. package/dist/esm/Chip/index.native.js.map +1 -1
  17. package/dist/esm/ContextualSaveBar/UnsavedChangesDialog.native.js.map +1 -1
  18. package/dist/esm/ContextualSaveBar/clientKnownIdentity.native.js.map +1 -1
  19. package/dist/esm/ContextualSaveBar/commitImmediateField.native.js.map +1 -1
  20. package/dist/esm/ContextualSaveBar/index.native.js.map +1 -1
  21. package/dist/esm/ContextualSaveBar/useUnsavedChangesGuard.native.js.map +1 -1
  22. package/dist/esm/ControlGroup/index.native.js.map +1 -1
  23. package/dist/esm/DescriptionList/index.native.js.map +1 -1
  24. package/dist/esm/ErrorSummary.native.js.map +1 -1
  25. package/dist/esm/FieldDisplay/editMode.native.js.map +1 -1
  26. package/dist/esm/FieldDisplay/formatters.native.js.map +1 -1
  27. package/dist/esm/FieldDisplay/index.native.js.map +1 -1
  28. package/dist/esm/FieldGroup.native.js.map +1 -1
  29. package/dist/esm/FloatingPanel/PanelHeader.native.js.map +1 -1
  30. package/dist/esm/FloatingPanel/RegularPanel.native.js.map +1 -1
  31. package/dist/esm/FloatingPanel/compactFace.native.js.map +1 -1
  32. package/dist/esm/FloatingPanel/containingBlock.native.js.map +1 -1
  33. package/dist/esm/FloatingPanel/coverRect.native.js.map +1 -1
  34. package/dist/esm/FloatingPanel/echoContext.native.js.map +1 -1
  35. package/dist/esm/FloatingPanel/nestedAction.native.js.map +1 -1
  36. package/dist/esm/FloatingPanel/paintedBox.native.js.map +1 -1
  37. package/dist/esm/FloatingPanel/panelWidth.native.js.map +1 -1
  38. package/dist/esm/FloatingPanel/pointAnchor.native.js.map +1 -1
  39. package/dist/esm/FloatingPanel/regularPlacement.native.js.map +1 -1
  40. package/dist/esm/FloatingPanel/trailingAction.native.js.map +1 -1
  41. package/dist/esm/FloatingPanel/useFloatingPanel.native.js.map +1 -1
  42. package/dist/esm/Form/index.native.js.map +1 -1
  43. package/dist/esm/Form/submissionController.native.js.map +1 -1
  44. package/dist/esm/Form/validateOn.native.js.map +1 -1
  45. package/dist/esm/FormGrid.native.js.map +1 -1
  46. package/dist/esm/FormSection.native.js.map +1 -1
  47. package/dist/esm/InputParts/PasswordReveal.native.js.map +1 -1
  48. package/dist/esm/InputParts/index.native.js.map +1 -1
  49. package/dist/esm/InputParts/keyboardAppearance.native.js.map +1 -1
  50. package/dist/esm/Label/index.native.js.map +1 -1
  51. package/dist/esm/MediaPicker/base64.native.js.map +1 -1
  52. package/dist/esm/MediaPicker/payload.native.js.map +1 -1
  53. package/dist/esm/MediaPicker/types.native.js.map +1 -1
  54. package/dist/esm/MediaPicker/useMediaPicker.native.js.map +1 -1
  55. package/dist/esm/Skeleton/index.native.js.map +1 -1
  56. package/dist/esm/Spinner/SpinnerGlyph.native.js.map +1 -1
  57. package/dist/esm/Spinner/spinnerSize.native.js.map +1 -1
  58. package/dist/esm/Table/index.native.js.map +1 -1
  59. package/dist/esm/Wheel/haptic.native.js.map +1 -1
  60. package/dist/esm/Wheel/index.native.js.map +1 -1
  61. package/dist/esm/Wheel/wheelShared.native.js.map +1 -1
  62. package/dist/esm/actionLabel.native.js.map +1 -1
  63. package/dist/esm/fieldDefaults.native.js.map +1 -1
  64. package/dist/esm/fieldLayout.native.js.map +1 -1
  65. package/dist/esm/fields/Barcode/BarcodePreview.native.js.map +1 -1
  66. package/dist/esm/fields/Barcode/code128.native.js.map +1 -1
  67. package/dist/esm/fields/Barcode/index.native.js.map +1 -1
  68. package/dist/esm/fields/Barcode/qr.native.js.map +1 -1
  69. package/dist/esm/fields/Barcode/scannerTypes.native.js.map +1 -1
  70. package/dist/esm/fields/Checkbox/Checkbox.native.js.map +1 -1
  71. package/dist/esm/fields/Checkbox/CheckboxBox.native.js.map +1 -1
  72. package/dist/esm/fields/Checkbox/CheckboxCard.native.js.map +1 -1
  73. package/dist/esm/fields/Checkbox/CheckboxFieldFrame.native.js.map +1 -1
  74. package/dist/esm/fields/Checkbox/CheckboxGroup.native.js.map +1 -1
  75. package/dist/esm/fields/Checkbox/checkboxGroupContext.native.js.map +1 -1
  76. package/dist/esm/fields/Checkbox/index.native.js.map +1 -1
  77. package/dist/esm/fields/ColorPicker/SystemColorField.native.js.map +1 -1
  78. package/dist/esm/fields/ColorPicker/index.native.js.map +1 -1
  79. package/dist/esm/fields/ColorPicker/parts.native.js.map +1 -1
  80. package/dist/esm/fields/ColorPicker/systemColorWell.gnome.native.js.map +1 -1
  81. package/dist/esm/fields/Combobox/authorOptions.native.js.map +1 -1
  82. package/dist/esm/fields/Combobox/index.native.js.map +1 -1
  83. package/dist/esm/fields/DatePicker/Calendar.native.js.map +1 -1
  84. package/dist/esm/fields/DatePicker/DateRangePicker.native.js.map +1 -1
  85. package/dist/esm/fields/DatePicker/DatetimePicker.catalog.native.js.map +1 -1
  86. package/dist/esm/fields/DatePicker/MonthPicker.native.js.map +1 -1
  87. package/dist/esm/fields/DatePicker/MultiDatePicker.native.js.map +1 -1
  88. package/dist/esm/fields/DatePicker/catalog.native.js.map +1 -1
  89. package/dist/esm/fields/DatePicker/fieldValueSync.native.js.map +1 -1
  90. package/dist/esm/fields/DatePicker/parts.native.js.map +1 -1
  91. package/dist/esm/fields/DatePicker/utils.native.js.map +1 -1
  92. package/dist/esm/fields/Duration/durationMath.native.js.map +1 -1
  93. package/dist/esm/fields/Duration/durationWheelTypes.native.js.map +1 -1
  94. package/dist/esm/fields/Duration/index.native.js.map +1 -1
  95. package/dist/esm/fields/FileUpload/filePickerTypes.native.js.map +1 -1
  96. package/dist/esm/fields/FileUpload/index.native.js.map +1 -1
  97. package/dist/esm/fields/FileUpload/nativePickerLoader.native.js.map +1 -1
  98. package/dist/esm/fields/Geolocation/expoMapsLoader.native.js.map +1 -1
  99. package/dist/esm/fields/Geolocation/geoValue.native.js.map +1 -1
  100. package/dist/esm/fields/Geolocation/geocoder.native.js.map +1 -1
  101. package/dist/esm/fields/Geolocation/geocoderContext.native.js.map +1 -1
  102. package/dist/esm/fields/Geolocation/index.native.js.map +1 -1
  103. package/dist/esm/fields/Geolocation/mapMath.native.js.map +1 -1
  104. package/dist/esm/fields/Geolocation/mapTypes.native.js.map +1 -1
  105. package/dist/esm/fields/Geolocation/nativeSearchFocus.native.js.map +1 -1
  106. package/dist/esm/fields/Geolocation/photonGeocoder.native.js.map +1 -1
  107. package/dist/esm/fields/Icon/drawnSet.native.js.map +1 -1
  108. package/dist/esm/fields/Icon/index.native.js.map +1 -1
  109. package/dist/esm/fields/ImageField/index.native.js.map +1 -1
  110. package/dist/esm/fields/Input/index.native.js.map +1 -1
  111. package/dist/esm/fields/MentionInput/index.native.js.map +1 -1
  112. package/dist/esm/fields/OTPInput/index.native.js.map +1 -1
  113. package/dist/esm/fields/OTPInput/otpCellCss.native.js.map +1 -1
  114. package/dist/esm/fields/PhoneInput/dialCodes.native.js.map +1 -1
  115. package/dist/esm/fields/PhoneInput/index.native.js.map +1 -1
  116. package/dist/esm/fields/PhoneInput/phone.native.js.map +1 -1
  117. package/dist/esm/fields/PhoneInput/regionNames.native.js.map +1 -1
  118. package/dist/esm/fields/Progress/index.native.js.map +1 -1
  119. package/dist/esm/fields/RadioGroup/index.native.js.map +1 -1
  120. package/dist/esm/fields/Rating/index.native.js.map +1 -1
  121. package/dist/esm/fields/Rating/ratingMath.native.js.map +1 -1
  122. package/dist/esm/fields/Rating/types.native.js.map +1 -1
  123. package/dist/esm/fields/SearchInput/index.native.js.map +1 -1
  124. package/dist/esm/fields/Select/InlineChoices.native.js.map +1 -1
  125. package/dist/esm/fields/Select/SheetPickerRow.native.js.map +1 -1
  126. package/dist/esm/fields/Select/index.native.js.map +1 -1
  127. package/dist/esm/fields/Select/shouldInlineChoices.native.js.map +1 -1
  128. package/dist/esm/fields/Signature/index.native.js.map +1 -1
  129. package/dist/esm/fields/Slider/index.native.js.map +1 -1
  130. package/dist/esm/fields/Slider/jumpMotion.native.js.map +1 -1
  131. package/dist/esm/fields/Stepper/index.native.js.map +1 -1
  132. package/dist/esm/fields/Stepper/resolveStepperPlacement.native.js.map +1 -1
  133. package/dist/esm/fields/Switch/footprint.native.js.map +1 -1
  134. package/dist/esm/fields/Switch/index.native.js.map +1 -1
  135. package/dist/esm/fields/TextArea/index.native.js.map +1 -1
  136. package/dist/esm/fields/TimePicker/catalog.native.js.map +1 -1
  137. package/dist/esm/fields/TimePicker/parts.native.js.map +1 -1
  138. package/dist/esm/fields/TimeRangeScrubber/geometry.native.js.map +1 -1
  139. package/dist/esm/fields/TimeRangeScrubber/index.native.js.map +1 -1
  140. package/dist/esm/fields/ToggleGroup/index.native.js.map +1 -1
  141. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -2
  142. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  143. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js +8 -2
  144. package/dist/esm/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  145. package/dist/esm/fields/nativeDateTime/dateTimePickerLoader.native.js.map +1 -1
  146. package/dist/esm/fields/nativeDateTime/osDateTimeIosDisplay.native.js.map +1 -1
  147. package/dist/esm/fields/nativeDateTime/sheetKeyboard.native.js.map +1 -1
  148. package/dist/esm/fields/nativeSelect.native.js.map +1 -1
  149. package/dist/esm/fields/selectedOrder.native.js.map +1 -1
  150. package/dist/esm/formSpacing.native.js.map +1 -1
  151. package/dist/esm/hooks/useFocusManagement.native.js.map +1 -1
  152. package/dist/esm/index.native.js.map +1 -1
  153. package/dist/esm/keyboard/chain.native.js.map +1 -1
  154. package/dist/esm/keyboard/index.native.js.map +1 -1
  155. package/dist/esm/keyboard/types.native.js.map +1 -1
  156. package/dist/esm/requiredMarking.native.js.map +1 -1
  157. package/dist/esm/shared/CheckGlyph.native.js.map +1 -1
  158. package/dist/esm/shared/TimeColumn.native.js.map +1 -1
  159. package/dist/esm/shared/capabilityPreflight.native.js.map +1 -1
  160. package/dist/esm/shared/checkedOnField.native.js.map +1 -1
  161. package/dist/esm/shared/choiceRowPress.native.js.map +1 -1
  162. package/dist/esm/shared/colorRamps.native.js.map +1 -1
  163. package/dist/esm/shared/devWarn.native.js.map +1 -1
  164. package/dist/esm/shared/fieldRowContext.native.js.map +1 -1
  165. package/dist/esm/shared/floatingList.native.js.map +1 -1
  166. package/dist/esm/shared/focusManagement.native.js.map +1 -1
  167. package/dist/esm/shared/groupContext.native.js.map +1 -1
  168. package/dist/esm/shared/i18n.native.js.map +1 -1
  169. package/dist/esm/shared/instantMotion.native.js.map +1 -1
  170. package/dist/esm/shared/nativeA11y.native.js.map +1 -1
  171. package/dist/esm/shared/nativeAutoFocus.native.js.map +1 -1
  172. package/dist/esm/shared/overlaySizeClass.native.js.map +1 -1
  173. package/dist/esm/shared/overlayStack.native.js.map +1 -1
  174. package/dist/esm/shared/panelPortal.native.js.map +1 -1
  175. package/dist/esm/shared/pickerTriggerContract.native.js.map +1 -1
  176. package/dist/esm/shared/pressSlopProps.native.js.map +1 -1
  177. package/dist/esm/shared/restoreRetries.native.js.map +1 -1
  178. package/dist/esm/shared/sheetNavigation.native.js.map +1 -1
  179. package/dist/esm/shared/systemControlModules.gnome.native.js.map +1 -1
  180. package/dist/esm/shared/systemControls.native.js.map +1 -1
  181. package/dist/esm/shared/t.native.js.map +1 -1
  182. package/dist/esm/shared/tableCellContext.native.js.map +1 -1
  183. package/dist/esm/shared/useDragScroll.native.js.map +1 -1
  184. package/dist/esm/shared/useKeyboardInset.native.js.map +1 -1
  185. package/dist/esm/shared/useSheetEscapeDismiss.native.js.map +1 -1
  186. package/dist/esm/shared/useWheelIncrement.native.js.map +1 -1
  187. package/dist/esm/shared/utils.native.js.map +1 -1
  188. package/dist/esm/shared/zIndex.native.js.map +1 -1
  189. package/dist/esm/standardActions.native.js.map +1 -1
  190. package/dist/esm/themedPrimitives.native.js.map +1 -1
  191. package/dist/esm/types.native.js.map +1 -1
  192. package/dist/esm/utils/LinkCell.native.js.map +1 -1
  193. package/dist/jsx/Chip/index.mjs +3 -3
  194. package/dist/jsx/Chip/index.mjs.map +1 -1
  195. package/dist/jsx/Chip/index.native.js +2 -1
  196. package/dist/jsx/Chip/index.native.js.map +1 -1
  197. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs +8 -2
  198. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.mjs.map +1 -1
  199. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js +7 -1
  200. package/dist/jsx/fields/nativeDateTime/OsDateTimeSheet.native.js.map +1 -1
  201. package/package.json +8 -8
  202. package/src/Button/Button.statePrecedence.spec.tsx +151 -0
  203. package/src/Chip/Chip.spec.tsx +42 -1
  204. package/src/Chip/index.tsx +8 -5
  205. package/src/FloatingPanel/FloatingPanel.scrollThenSheet.spec.tsx +85 -0
  206. package/src/fields/nativeDateTime/OsDateTimeSheet.countdownValue.native.spec.tsx +94 -0
  207. package/src/fields/nativeDateTime/OsDateTimeSheet.tsx +11 -2
  208. package/types/Chip/index.d.ts +5 -0
  209. package/types/Chip/index.d.ts.map +1 -1
  210. package/types/fields/nativeDateTime/OsDateTimeSheet.d.ts.map +1 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * LC-187 STATE-PRECEDENCE, the state matrix on Button (FZ-8031).
3
+ *
4
+ * When states coincide the paint resolves in one order: disabled beats hover
5
+ * and press; loading is disabled plus the spinner in the leading slot (the
6
+ * label stays, FZ-7812); press beats hover; selected suppresses hover; error
7
+ * and focus compose with everything.
8
+ *
9
+ * happy-dom cascades no Tamagui CSS, so this reads the atomic classes the
10
+ * frame asks for: `_bg-<v>` at rest, `_bg-<n>hover-<v>` and `_bg-<n>active-<v>`
11
+ * for hover and press, and the focus-visible ring classes.
12
+ */
13
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
14
+ import { Preset } from "@multiplatform.one/theme";
15
+ import { cleanup, fireEvent, screen } from "@testing-library/react";
16
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
+ import { __resetDevWarnSeen } from "../shared/devWarn";
18
+ import { Button } from "./index";
19
+
20
+ beforeEach(() => {
21
+ __resetDevWarnSeen();
22
+ vi.spyOn(console, "warn").mockImplementation(() => {});
23
+ });
24
+
25
+ afterEach(() => {
26
+ cleanup();
27
+ vi.restoreAllMocks();
28
+ __resetDevWarnSeen();
29
+ });
30
+
31
+ const PSEUDO = /^_bg-\d*(hover|active|focus|press|focus-visible|focusVisible)-/;
32
+
33
+ /** The background value a class list asks for in one state, or "" for none. */
34
+ function bg(el: Element, state: "rest" | "hover" | "press"): string {
35
+ const classes = Array.from(el.classList).filter((c) => c.startsWith("_bg-"));
36
+ if (state === "rest") {
37
+ return classes
38
+ .filter((c) => !PSEUDO.test(c))
39
+ .map((c) => c.slice("_bg-".length))
40
+ .join(" ");
41
+ }
42
+ const pseudo = state === "hover" ? "hover" : "active";
43
+ const re = new RegExp(`^_bg-\\d*${pseudo}-(.+)$`);
44
+ return classes
45
+ .map((c) => c.match(re)?.[1])
46
+ .filter(Boolean)
47
+ .join(" ");
48
+ }
49
+
50
+ /** The paint a state actually shows: its own class, or the rest paint when it has none. */
51
+ const shown = (el: Element, state: "hover" | "press") => bg(el, state) || bg(el, "rest");
52
+
53
+ const ringed = (el: Element) => /focus-?visible/i.test(el.className);
54
+ const frame = (name: string) => screen.getByRole("button", { name: new RegExp(name) });
55
+ const dump = (el: Element) => `class="${el.className}"`;
56
+
57
+ describe("LC-187: Button resolves coinciding states in one order", () => {
58
+ it("an enabled button lights on hover, steps further on press, and rings on keyboard focus", () => {
59
+ renderWithProviders(<Button>Save</Button>);
60
+ const el = frame("Save");
61
+ expect(bg(el, "hover"), dump(el)).not.toBe("");
62
+ expect(shown(el, "hover"), dump(el)).not.toBe(bg(el, "rest"));
63
+ expect(shown(el, "press"), dump(el)).not.toBe(shown(el, "hover"));
64
+ expect(ringed(el), dump(el)).toBe(true);
65
+ });
66
+
67
+ it("disabled beats hover and press: an explained disable shows its rest paint in every state", () => {
68
+ renderWithProviders(
69
+ <Button disabled disabledReason="Needs owner approval">
70
+ Publish
71
+ </Button>,
72
+ );
73
+ const el = frame("Publish");
74
+ expect(el.getAttribute("aria-disabled")).toBe("true");
75
+ expect(shown(el, "hover"), dump(el)).toBe(bg(el, "rest"));
76
+ expect(shown(el, "press"), dump(el)).toBe(bg(el, "rest"));
77
+ });
78
+
79
+ it("disabled keeps focus-visible: the explained disable still rings", () => {
80
+ renderWithProviders(
81
+ <Button disabled disabledReason="Needs owner approval">
82
+ Publish
83
+ </Button>,
84
+ );
85
+ expect(ringed(frame("Publish")), dump(frame("Publish"))).toBe(true);
86
+ });
87
+
88
+ it("a plain disable takes no pointer at all, so nothing hovers or presses", () => {
89
+ const onPress = vi.fn();
90
+ renderWithProviders(
91
+ <Button disabled onPress={onPress}>
92
+ Publish
93
+ </Button>,
94
+ );
95
+ const el = frame("Publish");
96
+ expect(el.className, dump(el)).toMatch(/(^|\s)_pe-none(\s|$)/);
97
+ fireEvent.click(el);
98
+ expect(onPress).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it("loading is disabled plus the spinner: no press, no pointer, the label and the rest paint stay", () => {
102
+ const onPress = vi.fn();
103
+ renderWithProviders(
104
+ <>
105
+ <Button onPress={onPress}>Idle</Button>
106
+ <Button loading onPress={onPress}>
107
+ Busy
108
+ </Button>
109
+ </>,
110
+ );
111
+ const idle = frame("Idle");
112
+ const busy = frame("Busy");
113
+ expect(busy.getAttribute("aria-busy")).toBe("true");
114
+ expect(busy.querySelector("[data-button-loading]")).toBeTruthy();
115
+ expect(busy.textContent).toContain("Busy");
116
+ expect(busy.className, dump(busy)).toMatch(/(^|\s)_pe-none(\s|$)/);
117
+ expect(bg(busy, "rest")).toBe(bg(idle, "rest"));
118
+ fireEvent.click(busy);
119
+ expect(onPress).not.toHaveBeenCalled();
120
+ });
121
+
122
+ it("selected suppresses hover: a selected fill shows the same paint at rest and under the pointer", () => {
123
+ renderWithProviders(<Button selected>Bold</Button>);
124
+ const el = frame("Bold");
125
+ expect(shown(el, "hover"), dump(el)).toBe(bg(el, "rest"));
126
+ expect(ringed(el), dump(el)).toBe(true);
127
+ });
128
+
129
+ // FZ-8932 (FZ-8031 finding): an outlined selected Button rests on $background and
130
+ // lifts to $backgroundHover, a hover on an already-selected control. Once
131
+ // that is fixed this body passes, `it.fails` reports it, and it becomes `it`.
132
+ // A neutral Button's look is its LC-153 tier, so the outlined frame comes
133
+ // from the preset; the use site's `outlined` alone renders it tinted.
134
+ it.fails("selected suppresses hover on an outlined button too", () => {
135
+ renderWithProviders(
136
+ <Preset overrides={{ fillStyle: "outlined" }}>
137
+ <Button selected>Italic</Button>
138
+ </Preset>,
139
+ );
140
+ const el = frame("Italic");
141
+ expect(shown(el, "hover"), dump(el)).toBe(bg(el, "rest"));
142
+ });
143
+
144
+ it("error composes: an error button still lights on hover, steps on press, and rings", () => {
145
+ renderWithProviders(<Button error>Delete</Button>);
146
+ const el = frame("Delete");
147
+ expect(shown(el, "hover"), dump(el)).not.toBe(bg(el, "rest"));
148
+ expect(shown(el, "press"), dump(el)).not.toBe(shown(el, "hover"));
149
+ expect(ringed(el), dump(el)).toBe(true);
150
+ });
151
+ });
@@ -3,12 +3,20 @@ import { fireEvent } from "@testing-library/react";
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
  import { renderWithProviders } from "@multiplatform.one/test-utils";
5
5
  import {
6
+ DEFAULT_SCALE_ID,
6
7
  Preset,
8
+ applyThemeScale,
7
9
  generateScale,
8
10
  defaultScaleInputs,
9
11
  createDefaultThemeConfig,
12
+ densePreset,
10
13
  } from "@multiplatform.one/theme";
11
- import { CHIP_DISMISS_MIN_TARGET_FINE, Chip, chipDismissFloorOutset } from "./index";
14
+ import {
15
+ CHIP_DISMISS_MIN_TARGET_FINE,
16
+ Chip,
17
+ chipDismissFloorOutset,
18
+ chipFrameGeometry,
19
+ } from "./index";
12
20
 
13
21
  describe("Chip", () => {
14
22
  it("renders its label", () => {
@@ -248,6 +256,39 @@ describe("Chip", () => {
248
256
  });
249
257
  });
250
258
 
259
+ describe("Chip vertical inset on every preset's scale (lockstep with components Chip, FZ-8018)", () => {
260
+ const sizes = ["$2", "$3", "$4", "$5"] as const;
261
+
262
+ it("draws the default scale exactly as a quarter of padX", () => {
263
+ for (const size of sizes) {
264
+ const { paddingHorizontal, paddingVertical } = chipFrameGeometry(size);
265
+ expect(paddingVertical).toBe(Math.round(paddingHorizontal * 0.25));
266
+ }
267
+ });
268
+
269
+ it("paints a space token on the installed scale wherever the default does", () => {
270
+ const themeConfig = createDefaultThemeConfig({ presets: { dense: densePreset } });
271
+ const dense = themeConfig.scales?.dense;
272
+ const defaults = themeConfig.scales?.[DEFAULT_SCALE_ID];
273
+ if (!dense || !defaults) throw new Error("scales missing");
274
+ const onDefault = Object.fromEntries(
275
+ sizes.map((size) => [size, chipFrameGeometry(size).paddingVertical]),
276
+ );
277
+ const defaultSpace = new Set(Object.values(defaults.tokens.space));
278
+ const denseSpace = new Set(Object.values(dense.tokens.space));
279
+ expect(defaultSpace.has(onDefault.$3)).toBe(true);
280
+ try {
281
+ applyThemeScale(dense);
282
+ for (const size of sizes) {
283
+ if (!defaultSpace.has(onDefault[size])) continue;
284
+ expect(denseSpace.has(chipFrameGeometry(size).paddingVertical)).toBe(true);
285
+ }
286
+ } finally {
287
+ applyThemeScale(defaults);
288
+ }
289
+ });
290
+ });
291
+
251
292
  describe("Chip label line height", () => {
252
293
  for (const [size, key] of [
253
294
  [undefined, "3"],
@@ -9,6 +9,7 @@ import {
9
9
  pressTargetHitSlop,
10
10
  radiusStopFromToken,
11
11
  resolveRadiusClass,
12
+ scaleSpaceFractionPx,
12
13
  sizeRecipeForToken,
13
14
  sizeKeyBelow,
14
15
  stackRadiusProps,
@@ -138,18 +139,20 @@ function resolveVariant(variant: ChipVariantProp | undefined): ChipVariant {
138
139
  * Chip geometry rides the GENERATED `chip` family (LC-75 / LC-80):
139
140
  * padX / font / icon / inner gap come from the recipe table — no handwritten
140
141
  * per-size px map. The pill has no recipe height: it hugs its label, and the
141
- * vertical inset derives as a quarter of the recipe padX (preserves the
142
- * audited $3/$4 pill height). Module-eval objects so styled() can flatten
143
- * (pilot pattern: InputParts spreads `recipeFamilies.control.boxVariants`).
142
+ * vertical inset is a quarter of the recipe padX (preserves the audited $3/$4
143
+ * pill height), painted as the installed scale's `$space` token where the
144
+ * default scale's quarter lands on one (`scaleSpaceFractionPx`). Module-eval
145
+ * objects so styled() can flatten (pilot pattern: InputParts spreads
146
+ * `recipeFamilies.control.boxVariants`).
144
147
  */
145
148
  const CHIP_RECIPE_FAMILY = "chip" as const;
146
149
  const CHIP_SIZE_TOKENS = ["$2", "$3", "$4", "$5"] as const;
147
150
 
148
151
  function chipPadY(paddingHorizontal: number): number {
149
- return Math.round(paddingHorizontal * 0.25);
152
+ return scaleSpaceFractionPx(paddingHorizontal, 0.25);
150
153
  }
151
154
 
152
- function chipFrameGeometry(token: string) {
155
+ export function chipFrameGeometry(token: string) {
153
156
  const recipe = sizeRecipeForToken(token, { family: CHIP_RECIPE_FAMILY });
154
157
  return {
155
158
  paddingHorizontal: recipe.paddingHorizontal,
@@ -0,0 +1,85 @@
1
+ /**
2
+ * LC-219 SCROLL-THEN-SHEET on the floating panel's sheet face (FZ-8031), the
3
+ * twin of `SheetModal.scrollThenSheet.spec.tsx`: a drag on the panel's
4
+ * scrolling content scrolls it and leaves the sheet still, and only the
5
+ * grabber zone moves the sheet. The hand-off at the top of the scroll is the
6
+ * open half, the same as on SheetModal.
7
+ */
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { cleanup } from "@testing-library/react";
10
+ import { PanResponder } from "react-native";
11
+ import { afterEach, expect, it, vi } from "vitest";
12
+ import { Text } from "tamagui";
13
+ import { FloatingPanel } from "./index.native";
14
+
15
+ const views = vi.hoisted(() => [] as Array<{ kind: string; props: Record<string, unknown> }>);
16
+
17
+ vi.mock("react-native-web", async (importOriginal) => {
18
+ const actual = await importOriginal<typeof import("react-native")>();
19
+ const React = await import("react");
20
+ const record = (kind: string, Base: unknown) =>
21
+ React.forwardRef((props: Record<string, unknown>, ref) => {
22
+ views.push({ kind, props });
23
+ return React.createElement(Base as never, { ...props, ref });
24
+ });
25
+ return {
26
+ ...actual,
27
+ ActionSheetIOS: undefined,
28
+ Modal: ({ children }: { children: import("react").ReactNode }) => children,
29
+ useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
30
+ View: record("View", actual.View),
31
+ ScrollView: record("ScrollView", actual.ScrollView),
32
+ Animated: { ...actual.Animated, View: record("View", actual.Animated.View) },
33
+ };
34
+ });
35
+
36
+ afterEach(() => {
37
+ cleanup();
38
+ views.length = 0;
39
+ vi.restoreAllMocks();
40
+ });
41
+
42
+ function mountWithSheetPan() {
43
+ const move = vi.fn();
44
+ const claim = () => true;
45
+ vi.spyOn(PanResponder, "create").mockImplementation(
46
+ (config) =>
47
+ ({
48
+ panHandlers: { onResponderMove: move, onMoveShouldSetResponder: claim },
49
+ config,
50
+ }) as never,
51
+ );
52
+ renderWithProviders(
53
+ <FloatingPanel open onOpenChange={() => {}} trigger={<Text>Author</Text>} scrollable>
54
+ {Array.from({ length: 40 }, (_, i) => (
55
+ <Text key={i}>{`row ${i + 1}`}</Text>
56
+ ))}
57
+ </FloatingPanel>,
58
+ );
59
+ const latest = new Map<unknown, { kind: string; props: Record<string, unknown> }>();
60
+ for (const view of views) latest.set(view.props.testID ?? view, view);
61
+ return { move, claim, mounted: [...latest.values()] };
62
+ }
63
+
64
+ it("draws the sheet face at phone width, with a scrolling body", () => {
65
+ const { mounted } = mountWithSheetPan();
66
+ expect(mounted.some((v) => v.props.testID === "floating-panel-sheet")).toBe(true);
67
+ expect(mounted.some((v) => v.kind === "ScrollView")).toBe(true);
68
+ });
69
+
70
+ it("puts the sheet's pan on the grabber zone only", () => {
71
+ const { move, mounted } = mountWithSheetPan();
72
+ const holders = mounted.filter((v) => v.props.onResponderMove === move);
73
+ expect(holders.map((v) => v.props.testID)).toEqual(["floating-panel-grabber"]);
74
+ });
75
+
76
+ it("leaves the sheet still for a pan on the scrolling content", () => {
77
+ const { move, claim, mounted } = mountWithSheetPan();
78
+ for (const scroll of mounted.filter((v) => v.kind === "ScrollView")) {
79
+ expect(scroll.props.onResponderMove).not.toBe(move);
80
+ expect(scroll.props.onMoveShouldSetResponder).not.toBe(claim);
81
+ }
82
+ expect(move).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it.todo("at scroll offset 0 a downward pan on the content moves the sheet in the same gesture");
@@ -0,0 +1,94 @@
1
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
2
+ import { useEffect, useLayoutEffect } from "react";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { OsDateTimeSheet } from "./OsDateTimeSheet";
5
+
6
+ const wheel = vi.hoisted(() => ({
7
+ mode: undefined as string | undefined,
8
+ date: undefined as number | undefined,
9
+ minutes: undefined as number | undefined,
10
+ }));
11
+
12
+ vi.mock("react-native-web", async (importOriginal) => ({
13
+ ...(await importOriginal<typeof import("react-native")>()),
14
+ useWindowDimensions: () => ({ width: 390, height: 844, scale: 3, fontScale: 1 }),
15
+ }));
16
+
17
+ vi.mock("@multiplatform.one/platform", async (importOriginal) => {
18
+ const actual = (await importOriginal()) as Record<string, unknown>;
19
+ const real = actual.platform as Record<string, unknown>;
20
+ return {
21
+ ...actual,
22
+ platform: new Proxy(
23
+ {},
24
+ {
25
+ get(_target, prop) {
26
+ if (prop === "isIos") return true;
27
+ if (prop === "isAndroid") return false;
28
+ return real[prop as string];
29
+ },
30
+ },
31
+ ),
32
+ };
33
+ });
34
+
35
+ // RNDateTimePickerComponentView.updatePropsForPicker on each commit: a changed
36
+ // `date` is set before a changed `mode`, and UIDatePicker only turns a date
37
+ // into a countdown while it is already in countdown mode (its floor is 1 min).
38
+ vi.mock("./dateTimePickerLoader", () => {
39
+ function UIDatePicker(props: { value: Date; mode: string }) {
40
+ useLayoutEffect(() => {
41
+ const instant = props.value.getTime();
42
+ if (instant !== wheel.date) {
43
+ wheel.date = instant;
44
+ if (wheel.mode === "countdown")
45
+ wheel.minutes = props.value.getHours() * 60 + props.value.getMinutes();
46
+ }
47
+ if (props.mode !== wheel.mode) {
48
+ wheel.mode = props.mode;
49
+ if (props.mode === "countdown") wheel.minutes = 1;
50
+ }
51
+ });
52
+ useEffect(
53
+ () => () => {
54
+ wheel.mode = undefined;
55
+ wheel.date = undefined;
56
+ wheel.minutes = undefined;
57
+ },
58
+ [],
59
+ );
60
+ return <div data-testid="os-picker" />;
61
+ }
62
+ return { loadDateTimePicker: () => UIDatePicker };
63
+ });
64
+
65
+ const at = (h: number, m: number) => {
66
+ const d = new Date(2026, 10, 2);
67
+ d.setHours(h, m, 0, 0);
68
+ return d;
69
+ };
70
+
71
+ describe("OsDateTimeSheet countdown opens at the field's value (FZ-8111)", () => {
72
+ it("rests the iOS wheel at 1 h 30 m, not the 1 min floor", () => {
73
+ renderWithProviders(
74
+ <OsDateTimeSheet
75
+ open
76
+ mode="countdown"
77
+ value={at(1, 30)}
78
+ onCancel={vi.fn()}
79
+ onConfirm={vi.fn()}
80
+ />,
81
+ );
82
+ expect(wheel.mode).toBe("countdown");
83
+ expect(wheel.minutes).toBe(90);
84
+ });
85
+
86
+ it("reaches the value again when the sheet reopens", () => {
87
+ const props = { mode: "countdown" as const, onCancel: vi.fn(), onConfirm: vi.fn() };
88
+ const result = renderWithProviders(<OsDateTimeSheet {...props} open value={at(2, 15)} />);
89
+ expect(wheel.minutes).toBe(135);
90
+ result.rerender(<OsDateTimeSheet {...props} open={false} value={at(0, 45)} />);
91
+ result.rerender(<OsDateTimeSheet {...props} open value={at(0, 45)} />);
92
+ expect(wheel.minutes).toBe(45);
93
+ });
94
+ });
@@ -10,7 +10,7 @@
10
10
  * does a missing `@react-native-community/datetimepicker` (an optional peer,
11
11
  * loaded through `loadDateTimePicker`).
12
12
  */
13
- import { useCallback, useContext, useState } from "react";
13
+ import { useCallback, useContext, useEffect, useState } from "react";
14
14
  import {
15
15
  Modal,
16
16
  Pressable,
@@ -88,13 +88,22 @@ export function OsDateTimeSheet({
88
88
  }, [value]);
89
89
 
90
90
  const [wasOpen, setWasOpen] = useState(false);
91
+ const [countdownPrimed, setCountdownPrimed] = useState(false);
91
92
  if (open && !wasOpen) {
92
93
  setWasOpen(true);
93
94
  setTemp(value);
94
95
  setShowTimePicker(false);
96
+ setCountdownPrimed(false);
95
97
  } else if (!open && wasOpen) {
96
98
  setWasOpen(false);
97
99
  }
100
+ // The native picker applies `date` before `mode` on mount, so the countdown
101
+ // wheel drops the first value and rests at 0 h 1 m. A second, different
102
+ // instant after mount lands while the wheel is already in countdown mode.
103
+ const primeCountdown = open && isIOS && pickerMode === "countdown" && !countdownPrimed;
104
+ useEffect(() => {
105
+ if (primeCountdown) setCountdownPrimed(true);
106
+ }, [primeCountdown]);
98
107
 
99
108
  function handleAndroidChange(event: DateTimePickerEvent, selected?: Date) {
100
109
  if (event.type === "dismissed") {
@@ -241,7 +250,7 @@ export function OsDateTimeSheet({
241
250
  </XStack>
242
251
  <YStack {...knobProps.panelPadding} flexShrink={1} minHeight={0}>
243
252
  <DateTimePicker
244
- value={temp}
253
+ value={primeCountdown ? new Date(temp.getTime() + 1) : temp}
245
254
  mode={pickerMode}
246
255
  display={iosDisplay}
247
256
  themeVariant={themeVariant}
@@ -22,6 +22,11 @@ export interface ChipProps {
22
22
  groupPosition?: GroupPosition;
23
23
  groupOrientation?: GroupOrientation;
24
24
  }
25
+ export declare function chipFrameGeometry(token: string): {
26
+ paddingHorizontal: number;
27
+ paddingVertical: number;
28
+ gap: number;
29
+ };
25
30
  /**
26
31
  * Exported so chip-scale companions (e.g. the Tags "Add Tag" trigger) share
27
32
  * the exact same size variants — padding/font stay in lockstep with Chip for
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Chip/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAGtD,OAAO,EAYL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAA+B,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMrE,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AACzD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC3F,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAwID;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;6CA+BpB,CAAC;AAEH,eAAO,MAAM,QAAQ;;;6CAgBnB,CAAC;AAiBH,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AA2GD,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,IAAI,EACJ,OAAO,EAAE,WAAsB,EAC/B,KAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAgB,EAChB,OAAO,EACP,IAAI,EACJ,aAAa,EACb,gBAA+B,GAChC,EAAE,SAAS,2CA6NX;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Chip/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAGtD,OAAO,EAaL,KAAK,gBAAgB,EACrB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAA+B,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMrE,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AACzD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC3F,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,gFAAgF;AAChF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE5E,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AA0GD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM;;;;EAO9C;AAyBD;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;6CA+BpB,CAAC;AAEH,eAAO,MAAM,QAAQ;;;6CAgBnB,CAAC;AAiBH,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEnD;AAED;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AA2GD,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,IAAI,EACJ,OAAO,EAAE,WAAsB,EAC/B,KAAc,EACd,SAAS,EACT,QAAQ,EACR,QAAgB,EAChB,OAAO,EACP,IAAI,EACJ,aAAa,EACb,gBAA+B,GAChC,EAAE,SAAS,2CA6NX;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,SAAS,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OsDateTimeSheet.d.ts","sourceRoot":"","sources":["../../../src/fields/nativeDateTime/OsDateTimeSheet.tsx"],"names":[],"mappings":"AAmCA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;CAClC,CAAC;AASF,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,kDAqMtB"}
1
+ {"version":3,"file":"OsDateTimeSheet.d.ts","sourceRoot":"","sources":["../../../src/fields/nativeDateTime/OsDateTimeSheet.tsx"],"names":[],"mappings":"AAmCA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,CAAC;AAExE,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC;CAClC,CAAC;AASF,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,kDA8MtB"}