@btndprojects/loam-ui-native 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accordion/index.js +3 -0
  3. package/dist/adaptive/index.js +2 -0
  4. package/dist/alert/index.js +3 -0
  5. package/dist/analyzer/index.js +6 -0
  6. package/dist/announcement/index.js +6 -0
  7. package/dist/audio-player/index.js +3 -0
  8. package/dist/avatar/index.js +3 -0
  9. package/dist/avatar-group/index.js +3 -0
  10. package/dist/badge/index.js +5 -0
  11. package/dist/banner/index.js +2 -0
  12. package/dist/board/index.js +7 -0
  13. package/dist/button/index.js +5 -0
  14. package/dist/camera-permission/index.js +13 -0
  15. package/dist/capture-result/index.js +18 -0
  16. package/dist/card/index.js +4 -0
  17. package/dist/card-form/index.js +10 -0
  18. package/dist/carousel/index.js +2 -0
  19. package/dist/chart/index.js +6 -0
  20. package/dist/charts/index.js +7 -0
  21. package/dist/checkbox/index.js +7 -0
  22. package/dist/checklist/index.js +10 -0
  23. package/dist/checkout/index.js +13 -0
  24. package/dist/chip/index.js +7 -0
  25. package/dist/chunk-223Q2T2Q.js +229 -0
  26. package/dist/chunk-2A5NIQAK.js +254 -0
  27. package/dist/chunk-2GK6PHGE.js +363 -0
  28. package/dist/chunk-2WUTXHMU.js +1 -0
  29. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  30. package/dist/chunk-35OFI3JG.js +499 -0
  31. package/dist/chunk-3ARE4TES.js +195 -0
  32. package/dist/chunk-3ECPOSKO.js +1 -0
  33. package/dist/chunk-3ENTMPQY.js +229 -0
  34. package/dist/chunk-3HJH6FF4.js +98 -0
  35. package/dist/chunk-3NMD74ZU.js +4 -0
  36. package/dist/chunk-3WPW4KUN.js +182 -0
  37. package/dist/chunk-47FWHGUN.js +394 -0
  38. package/dist/chunk-4BXMXBJO.js +141 -0
  39. package/dist/chunk-4DS5ZWVC.js +83 -0
  40. package/dist/chunk-4GBOGOTV.js +295 -0
  41. package/dist/chunk-4SZYY6AN.js +99 -0
  42. package/dist/chunk-4YIP3CWS.js +237 -0
  43. package/dist/chunk-4YQOR7JM.js +204 -0
  44. package/dist/chunk-4ZAHCY44.js +177 -0
  45. package/dist/chunk-52X2VYTD.js +228 -0
  46. package/dist/chunk-5P26LBDL.js +1 -0
  47. package/dist/chunk-5XLRYDAX.js +1 -0
  48. package/dist/chunk-5YNTNN5A.js +177 -0
  49. package/dist/chunk-66SG2H23.js +129 -0
  50. package/dist/chunk-6HT2LP62.js +504 -0
  51. package/dist/chunk-6N33LFQA.js +35 -0
  52. package/dist/chunk-6X6PQ443.js +432 -0
  53. package/dist/chunk-7CFF4HXS.js +126 -0
  54. package/dist/chunk-7O2FMUPT.js +755 -0
  55. package/dist/chunk-AI4DWZCD.js +162 -0
  56. package/dist/chunk-AWJOCGFZ.js +213 -0
  57. package/dist/chunk-AZRX6SAC.js +285 -0
  58. package/dist/chunk-B54QE2FN.js +1 -0
  59. package/dist/chunk-BC475SJH.js +1 -0
  60. package/dist/chunk-BEDBFJND.js +202 -0
  61. package/dist/chunk-BTTFCMKG.js +166 -0
  62. package/dist/chunk-BV3B2HYZ.js +1635 -0
  63. package/dist/chunk-C2NTXVWM.js +98 -0
  64. package/dist/chunk-CDRZHT42.js +424 -0
  65. package/dist/chunk-CJ5EOYZE.js +62 -0
  66. package/dist/chunk-CUMUNPXY.js +128 -0
  67. package/dist/chunk-CUS3ZSCU.js +1 -0
  68. package/dist/chunk-CYRBHQQZ.js +411 -0
  69. package/dist/chunk-DHWADYZX.js +144 -0
  70. package/dist/chunk-DKCSPATL.js +1 -0
  71. package/dist/chunk-DM5QUKKO.js +326 -0
  72. package/dist/chunk-DSJBXLUG.js +845 -0
  73. package/dist/chunk-DYBH2I6W.js +1 -0
  74. package/dist/chunk-DZ255O2F.js +1 -0
  75. package/dist/chunk-E35BPSGW.js +1 -0
  76. package/dist/chunk-EJBDRVIH.js +166 -0
  77. package/dist/chunk-F43NP42L.js +1 -0
  78. package/dist/chunk-F5XNPWS7.js +1024 -0
  79. package/dist/chunk-FIF2M24N.js +1521 -0
  80. package/dist/chunk-FOKG57CR.js +993 -0
  81. package/dist/chunk-G3HOYSF7.js +1 -0
  82. package/dist/chunk-GDKZTW34.js +395 -0
  83. package/dist/chunk-GL7B2DOW.js +134 -0
  84. package/dist/chunk-GNNJ4GCC.js +471 -0
  85. package/dist/chunk-GNZ56EUI.js +1 -0
  86. package/dist/chunk-H6NIOQHK.js +346 -0
  87. package/dist/chunk-H6PC7RDA.js +1 -0
  88. package/dist/chunk-HPPW424V.js +334 -0
  89. package/dist/chunk-HQOQIBJI.js +1 -0
  90. package/dist/chunk-HU2EQNRZ.js +44 -0
  91. package/dist/chunk-IIQTAY3J.js +715 -0
  92. package/dist/chunk-ITTYJZWX.js +271 -0
  93. package/dist/chunk-IVNRBPGC.js +1018 -0
  94. package/dist/chunk-J4KLDGI4.js +1 -0
  95. package/dist/chunk-J7Q7VCVC.js +254 -0
  96. package/dist/chunk-JDEOITQ2.js +1 -0
  97. package/dist/chunk-JNH2TYIN.js +96 -0
  98. package/dist/chunk-JRLTGLN4.js +1 -0
  99. package/dist/chunk-JTRJJLKL.js +1 -0
  100. package/dist/chunk-JUAMLEHX.js +379 -0
  101. package/dist/chunk-JXJB2X6M.js +131 -0
  102. package/dist/chunk-KMI5ML5I.js +262 -0
  103. package/dist/chunk-KTAC4MGE.js +4 -0
  104. package/dist/chunk-LAECSR4H.js +178 -0
  105. package/dist/chunk-LK4BCSGX.js +1 -0
  106. package/dist/chunk-LOE5YNN3.js +470 -0
  107. package/dist/chunk-LRJNDEIA.js +302 -0
  108. package/dist/chunk-LSS54GLJ.js +116 -0
  109. package/dist/chunk-LZLHELAG.js +137 -0
  110. package/dist/chunk-M2LUXGKZ.js +1 -0
  111. package/dist/chunk-MOUJOP55.js +425 -0
  112. package/dist/chunk-N32RGOA5.js +159 -0
  113. package/dist/chunk-N5ZNE2YE.js +1 -0
  114. package/dist/chunk-NBNCBML6.js +1 -0
  115. package/dist/chunk-NKB7BZYK.js +167 -0
  116. package/dist/chunk-NQYBUO3K.js +1795 -0
  117. package/dist/chunk-NY2U3PBI.js +59 -0
  118. package/dist/chunk-O4AEHWII.js +464 -0
  119. package/dist/chunk-O7AE6DAF.js +146 -0
  120. package/dist/chunk-OETCUXQP.js +134 -0
  121. package/dist/chunk-OOPPQH27.js +179 -0
  122. package/dist/chunk-OPXFZ4Y5.js +204 -0
  123. package/dist/chunk-P23EQNRQ.js +338 -0
  124. package/dist/chunk-PJXUJYS6.js +248 -0
  125. package/dist/chunk-PNWUWOBF.js +1 -0
  126. package/dist/chunk-PUPM2YGF.js +376 -0
  127. package/dist/chunk-PZXZJBMP.js +620 -0
  128. package/dist/chunk-Q72LJOVE.js +169 -0
  129. package/dist/chunk-QCDXZBC5.js +247 -0
  130. package/dist/chunk-QDYDBCFP.js +197 -0
  131. package/dist/chunk-QJJ6MADV.js +274 -0
  132. package/dist/chunk-QKXCPTFS.js +349 -0
  133. package/dist/chunk-QWCMU5B2.js +860 -0
  134. package/dist/chunk-RGQWWKLA.js +634 -0
  135. package/dist/chunk-RIBDXGUG.js +108 -0
  136. package/dist/chunk-RJQOOHT2.js +214 -0
  137. package/dist/chunk-RNGLIHFA.js +191 -0
  138. package/dist/chunk-RQ3HASCC.js +6 -0
  139. package/dist/chunk-RSOJT5YW.js +124 -0
  140. package/dist/chunk-RTIKSY6A.js +1 -0
  141. package/dist/chunk-RUZHTGKB.js +237 -0
  142. package/dist/chunk-RW6ZL2FQ.js +284 -0
  143. package/dist/chunk-S7F6XLTK.js +540 -0
  144. package/dist/chunk-SBMNWWBV.js +155 -0
  145. package/dist/chunk-SDTQPRRB.js +1 -0
  146. package/dist/chunk-SYNOJOQO.js +377 -0
  147. package/dist/chunk-T753XT36.js +1 -0
  148. package/dist/chunk-TKQYWZ5V.js +196 -0
  149. package/dist/chunk-TKVFVF5D.js +95 -0
  150. package/dist/chunk-TOXAGRDQ.js +144 -0
  151. package/dist/chunk-TUTI5HOZ.js +112 -0
  152. package/dist/chunk-U7TFM2JA.js +140 -0
  153. package/dist/chunk-UC2WGUP3.js +627 -0
  154. package/dist/chunk-UWFLYMAR.js +1126 -0
  155. package/dist/chunk-UWSZB7BJ.js +1 -0
  156. package/dist/chunk-UYG5L34B.js +87 -0
  157. package/dist/chunk-VBOXAEPI.js +336 -0
  158. package/dist/chunk-VCDBXW5H.js +188 -0
  159. package/dist/chunk-VCXPHHDS.js +1 -0
  160. package/dist/chunk-VEAYTRPX.js +414 -0
  161. package/dist/chunk-VFCUIDRT.js +1 -0
  162. package/dist/chunk-VQHPQEUH.js +273 -0
  163. package/dist/chunk-VXDZFJGP.js +874 -0
  164. package/dist/chunk-WS56BL3D.js +1 -0
  165. package/dist/chunk-WSW6ZFAQ.js +98 -0
  166. package/dist/chunk-WVKKJA6U.js +870 -0
  167. package/dist/chunk-XNUVB5OL.js +375 -0
  168. package/dist/chunk-XOSCY2CW.js +242 -0
  169. package/dist/chunk-XXXEFXY5.js +99 -0
  170. package/dist/chunk-Y5CSSPV4.js +1 -0
  171. package/dist/chunk-Y6DU4J47.js +1 -0
  172. package/dist/chunk-YC6WORFL.js +535 -0
  173. package/dist/chunk-YDGVJETP.js +60 -0
  174. package/dist/chunk-YF4NSSMT.js +366 -0
  175. package/dist/chunk-YG3ZJHGU.js +223 -0
  176. package/dist/chunk-YIJ55TBC.js +1 -0
  177. package/dist/chunk-YKZ4RZZF.js +1 -0
  178. package/dist/chunk-YLLCXTMC.js +37 -0
  179. package/dist/chunk-YTA74UZR.js +1 -0
  180. package/dist/chunk-Z7H7VSFZ.js +323 -0
  181. package/dist/chunk-ZK2H4O5P.js +1 -0
  182. package/dist/chunk-ZVFE5QGU.js +524 -0
  183. package/dist/coachmark/index.js +4 -0
  184. package/dist/code-block/index.js +4 -0
  185. package/dist/color-field/index.js +7 -0
  186. package/dist/commerce/index.js +20 -0
  187. package/dist/connectivity/index.js +2 -0
  188. package/dist/context-menu/index.js +5 -0
  189. package/dist/copy-button/index.js +3 -0
  190. package/dist/data-grid/index.js +24 -0
  191. package/dist/date-range-picker/index.js +13 -0
  192. package/dist/description-list/index.js +2 -0
  193. package/dist/dialog/index.js +3 -0
  194. package/dist/empty-state/index.js +3 -0
  195. package/dist/error-summary/index.js +3 -0
  196. package/dist/fab/index.js +2 -0
  197. package/dist/file-upload/index.js +9 -0
  198. package/dist/filter-bar/index.js +8 -0
  199. package/dist/flow-run/index.js +14 -0
  200. package/dist/form/index.js +3 -0
  201. package/dist/gallery/index.js +2 -0
  202. package/dist/header/index.js +5 -0
  203. package/dist/hold-to-confirm/index.js +4 -0
  204. package/dist/index.d.ts +22883 -0
  205. package/dist/index.js +158 -0
  206. package/dist/infinite-list/index.js +2 -0
  207. package/dist/inline-edit/index.js +5 -0
  208. package/dist/input/index.js +5 -0
  209. package/dist/interactions/index.js +2 -0
  210. package/dist/invoice/index.js +10 -0
  211. package/dist/kbd/index.js +1 -0
  212. package/dist/keyboard/index.js +3 -0
  213. package/dist/list-row/index.js +7 -0
  214. package/dist/log-viewer/index.js +2 -0
  215. package/dist/map-view/index.js +3 -0
  216. package/dist/markdown/index.js +6 -0
  217. package/dist/metric/index.js +4 -0
  218. package/dist/money/index.js +3 -0
  219. package/dist/nav-drawer/index.js +2 -0
  220. package/dist/navigation/index.js +2 -0
  221. package/dist/notification-center/index.js +16 -0
  222. package/dist/number-roll/index.js +3 -0
  223. package/dist/one-handed/index.js +5 -0
  224. package/dist/os-surfaces/index.js +3 -0
  225. package/dist/otp/index.js +7 -0
  226. package/dist/page-header/index.js +1 -0
  227. package/dist/payment-method/index.js +12 -0
  228. package/dist/payment-status/index.js +16 -0
  229. package/dist/permission/index.js +2 -0
  230. package/dist/picker/index.js +7 -0
  231. package/dist/progress/index.js +5 -0
  232. package/dist/pull-to-refresh/index.js +4 -0
  233. package/dist/radio-group/index.js +6 -0
  234. package/dist/rating/index.js +2 -0
  235. package/dist/receivables/index.js +20 -0
  236. package/dist/refund/index.js +16 -0
  237. package/dist/save-status/index.js +3 -0
  238. package/dist/scroll-area/index.js +1 -0
  239. package/dist/search-bar/index.js +5 -0
  240. package/dist/segmented-control/index.js +5 -0
  241. package/dist/sheet/index.js +4 -0
  242. package/dist/skeleton/index.js +2 -0
  243. package/dist/slider/index.js +5 -0
  244. package/dist/spinner/index.js +2 -0
  245. package/dist/split-button/index.js +7 -0
  246. package/dist/spotlight/index.js +2 -0
  247. package/dist/stage-timeline/index.js +4 -0
  248. package/dist/status-dot/index.js +2 -0
  249. package/dist/stepper/index.js +6 -0
  250. package/dist/streaming-text/index.js +3 -0
  251. package/dist/survey/index.js +19 -0
  252. package/dist/switch/index.js +5 -0
  253. package/dist/system-state/index.js +4 -0
  254. package/dist/tab-bar/index.js +2 -0
  255. package/dist/tabs/index.js +2 -0
  256. package/dist/textarea/index.js +6 -0
  257. package/dist/thinking/index.js +2 -0
  258. package/dist/timeline/index.js +4 -0
  259. package/dist/toast/index.js +2 -0
  260. package/dist/toolbar/index.js +2 -0
  261. package/dist/tour/index.js +11 -0
  262. package/dist/truncation-notice/index.js +2 -0
  263. package/dist/update-notice/index.js +11 -0
  264. package/dist/video-player/index.js +2 -0
  265. package/dist/viewfinder/index.js +5 -0
  266. package/dist/widget/index.js +4 -0
  267. package/dist/wizard/index.js +6 -0
  268. package/package.json +76 -3
  269. package/README.md +0 -3
@@ -0,0 +1,1126 @@
1
+ import { Slider } from './chunk-H6NIOQHK.js';
2
+ import { Textarea } from './chunk-BTTFCMKG.js';
3
+ import { Picker } from './chunk-NKB7BZYK.js';
4
+ import { Progress } from './chunk-2ZZ7TDJ4.js';
5
+ import { Sheet } from './chunk-ZVFE5QGU.js';
6
+ import { Chip } from './chunk-AI4DWZCD.js';
7
+ import { Input } from './chunk-ITTYJZWX.js';
8
+ import { Button } from './chunk-3ARE4TES.js';
9
+ import { Icon } from './chunk-TKVFVF5D.js';
10
+ import * as React2 from 'react';
11
+ import { View, Text, Pressable } from 'react-native';
12
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
13
+ import { PMF_OPTIONS, scaleShape, resolveQuestion, resolveText, resolveHiddenFields, surveyPages, nextPageIndex, visibleQuestions, visiblePages, pickSurveyEnding, surveyCompletion, isQuestionRequired, isAnswerEmpty, prevPageIndex, npsBand, validatePage, branchedEndingId } from '@btndprojects/loam-engage';
14
+ export { PMF_OPTIONS, SURVEY_OPERATORS, cesScore, csatScore, isAnswerEmpty, isPageVisible, matchSurveyCondition, nextPageIndex, npsBand, npsScore, prevPageIndex, scaleShape, surveyPages, surveyProgress, validatePage, visiblePages, visibleQuestions } from '@btndprojects/loam-engage';
15
+ import { jsxs, jsx } from 'react/jsx-runtime';
16
+
17
+ var PMF_RESOLVED = PMF_OPTIONS.map((label) => ({
18
+ value: label,
19
+ label
20
+ }));
21
+ var SURVEY_FIELD_LABELS = {
22
+ required: "required",
23
+ other: "Other",
24
+ otherPlaceholder: "Tell us",
25
+ yes: "Yes",
26
+ no: "No",
27
+ rankHint: "Most important at the top",
28
+ moveUp: "Move up",
29
+ moveDown: "Move down",
30
+ choosePlaceholder: "Choose one"
31
+ };
32
+ var SURVEY_OTHER_PREFIX = "Other: ";
33
+ function SurveyField({
34
+ question,
35
+ error,
36
+ foot,
37
+ labels: labelOverrides,
38
+ children
39
+ }) {
40
+ const { styles } = useStyles(sheet);
41
+ const labels = { ...SURVEY_FIELD_LABELS, ...labelOverrides };
42
+ return (
43
+ // No accessible name on the wrapper: the question text is a visible label
44
+ // and every control below carries its own, so naming the group as well
45
+ // makes the same sentence arrive twice.
46
+ /* @__PURE__ */ jsxs(View, { style: styles.field, children: [
47
+ /* @__PURE__ */ jsxs(View, { style: styles.labelRow, children: [
48
+ /* @__PURE__ */ jsx(Text, { style: styles.label, children: question.text }),
49
+ question.required ? /* @__PURE__ */ jsx(Text, { style: styles.requiredTag, children: labels.required }) : null
50
+ ] }),
51
+ question.help ? /* @__PURE__ */ jsx(Text, { style: styles.help, children: question.help }) : null,
52
+ children,
53
+ foot ? /* @__PURE__ */ jsx(View, { style: styles.foot, children: foot }) : null,
54
+ error ? /* @__PURE__ */ jsx(SurveyFieldError, { children: error }) : null
55
+ ] })
56
+ );
57
+ }
58
+ function SurveyFieldError({ children }) {
59
+ const theme = useLoamTheme();
60
+ const { styles } = useStyles(sheet);
61
+ return /* @__PURE__ */ jsxs(View, { accessibilityLiveRegion: "polite", style: styles.errorRow, children: [
62
+ /* @__PURE__ */ jsx(Icon, { name: "alert-circle", size: theme.icon.xs, color: theme.color.danger }),
63
+ /* @__PURE__ */ jsx(Text, { style: styles.errorText, children })
64
+ ] });
65
+ }
66
+ function SurveyScale({ question, value, onChange }) {
67
+ const theme = useLoamTheme();
68
+ const { styles } = useStyles(sheet);
69
+ const { count, from } = scaleShape(question);
70
+ const top = from + count - 1;
71
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackSm, children: [
72
+ /* @__PURE__ */ jsx(View, { accessibilityRole: "radiogroup", style: styles.scaleRow, children: Array.from({ length: count }, (_, i) => {
73
+ const v = from + i;
74
+ const on = value === v;
75
+ const ends = [
76
+ v === from && question.low ? ` \u2014 ${question.low}` : "",
77
+ v === top && question.high ? ` \u2014 ${question.high}` : ""
78
+ ].join("");
79
+ return /* @__PURE__ */ jsx(
80
+ Pressable,
81
+ {
82
+ accessibilityRole: "radio",
83
+ accessibilityState: { checked: on },
84
+ "aria-checked": on,
85
+ accessibilityLabel: `Score ${v} of ${top}${ends}`,
86
+ onPress: () => onChange(v, true),
87
+ style: ({ pressed }) => [
88
+ styles.scaleCell,
89
+ on ? styles.scaleCellOn : null,
90
+ pressed && !on ? { backgroundColor: theme.state.pressed } : null
91
+ ],
92
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.scaleNumber, on ? styles.scaleNumberOn : null], children: v })
93
+ },
94
+ v
95
+ );
96
+ }) }),
97
+ question.low || question.high ? /* @__PURE__ */ jsxs(View, { style: styles.endsRow, children: [
98
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: question.low }),
99
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: question.high })
100
+ ] }) : null
101
+ ] });
102
+ }
103
+ function SurveyOptionRow({
104
+ label,
105
+ checked,
106
+ onPress,
107
+ role = "radio",
108
+ disabled = false
109
+ }) {
110
+ const theme = useLoamTheme();
111
+ const { styles } = useStyles(sheet);
112
+ return /* @__PURE__ */ jsxs(
113
+ Pressable,
114
+ {
115
+ accessibilityRole: role === "radio" ? "radio" : "checkbox",
116
+ accessibilityState: { checked, disabled },
117
+ "aria-checked": checked,
118
+ accessibilityLabel: label,
119
+ disabled,
120
+ onPress,
121
+ style: ({ pressed }) => [
122
+ styles.optionRow,
123
+ checked ? styles.optionRowOn : null,
124
+ pressed && !checked ? { backgroundColor: theme.state.pressed } : null
125
+ ],
126
+ children: [
127
+ /* @__PURE__ */ jsx(View, { style: [styles.mark, role === "checkbox" ? styles.markSquare : null, checked ? styles.markOn : null], children: checked ? role === "checkbox" ? /* @__PURE__ */ jsx(Icon, { name: "check", size: theme.icon.xs, color: theme.color.accentInk }) : /* @__PURE__ */ jsx(View, { style: styles.markDot }) : null }),
128
+ /* @__PURE__ */ jsx(Text, { style: [styles.optionLabel, checked ? styles.optionLabelOn : null], children: label })
129
+ ]
130
+ }
131
+ );
132
+ }
133
+ function SurveyOptions({
134
+ question,
135
+ value,
136
+ onChange,
137
+ labels: labelOverrides
138
+ }) {
139
+ const { styles } = useStyles(sheet);
140
+ const labels = { ...SURVEY_FIELD_LABELS, ...labelOverrides };
141
+ const options = question.options ?? (question.type === "pmf" ? PMF_RESOLVED : []);
142
+ const raw = typeof value === "string" ? value : void 0;
143
+ const isOther = Boolean(question.other && raw?.startsWith(SURVEY_OTHER_PREFIX));
144
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackSm, accessibilityRole: "radiogroup", children: [
145
+ options.map((o) => /* @__PURE__ */ jsx(
146
+ SurveyOptionRow,
147
+ {
148
+ label: o.label,
149
+ checked: raw === o.value,
150
+ onPress: () => onChange(o.value, true)
151
+ },
152
+ o.value
153
+ )),
154
+ question.other ? /* @__PURE__ */ jsx(
155
+ SurveyOptionRow,
156
+ {
157
+ label: labels.other,
158
+ checked: isOther,
159
+ onPress: () => onChange(SURVEY_OTHER_PREFIX)
160
+ }
161
+ ) : null,
162
+ isOther ? /* @__PURE__ */ jsx(
163
+ Input,
164
+ {
165
+ size: "lg",
166
+ autoFocus: true,
167
+ placeholder: labels.otherPlaceholder,
168
+ accessibilityLabel: `${question.text} \u2014 ${labels.other}`,
169
+ value: (raw ?? SURVEY_OTHER_PREFIX).slice(SURVEY_OTHER_PREFIX.length),
170
+ onChangeText: (t) => onChange(SURVEY_OTHER_PREFIX + t)
171
+ }
172
+ ) : null
173
+ ] });
174
+ }
175
+ function SurveyCheckboxList({ question, value, onChange }) {
176
+ const theme = useLoamTheme();
177
+ const { styles } = useStyles(sheet);
178
+ const picked = Array.isArray(value) ? [...value] : [];
179
+ const atCap = question.maxSelections != null && picked.length >= question.maxSelections;
180
+ return /* @__PURE__ */ jsx(View, { style: styles.stackSm, children: (question.options ?? []).map((o) => {
181
+ const on = picked.includes(o.value);
182
+ const blocked = atCap && !on;
183
+ return /* @__PURE__ */ jsx(
184
+ View,
185
+ {
186
+ style: blocked ? { opacity: theme.state.disabledOpacity } : void 0,
187
+ pointerEvents: blocked ? "none" : "auto",
188
+ children: /* @__PURE__ */ jsx(
189
+ SurveyOptionRow,
190
+ {
191
+ label: o.label,
192
+ checked: on,
193
+ role: "checkbox",
194
+ disabled: blocked,
195
+ onPress: () => onChange(on ? picked.filter((p) => p !== o.value) : [...picked, o.value])
196
+ }
197
+ )
198
+ },
199
+ o.value
200
+ );
201
+ }) });
202
+ }
203
+ function SurveyChips({ question, value, onChange }) {
204
+ const { styles } = useStyles(sheet);
205
+ const picked = Array.isArray(value) ? [...value] : [];
206
+ const atCap = question.maxSelections != null && picked.length >= question.maxSelections;
207
+ return /* @__PURE__ */ jsx(View, { style: styles.chipWrap, children: (question.options ?? []).map((o) => {
208
+ const on = picked.includes(o.value);
209
+ return /* @__PURE__ */ jsx(
210
+ Chip,
211
+ {
212
+ variant: "filter",
213
+ size: "md",
214
+ selectable: true,
215
+ selected: on,
216
+ disabled: !on && atCap,
217
+ onSelectedChange: () => onChange(on ? picked.filter((p) => p !== o.value) : [...picked, o.value]),
218
+ children: o.label
219
+ },
220
+ o.value
221
+ );
222
+ }) });
223
+ }
224
+ function SurveyRank({
225
+ question,
226
+ value,
227
+ onChange,
228
+ labels: labelOverrides
229
+ }) {
230
+ const { styles } = useStyles(sheet);
231
+ const labels = { ...SURVEY_FIELD_LABELS, ...labelOverrides };
232
+ const declared = question.items ?? [];
233
+ const labelOf = (v) => declared.find((x) => x.value === v)?.label ?? v;
234
+ const items = Array.isArray(value) ? [...value] : declared.map((x) => x.value);
235
+ const move = (from, dir) => {
236
+ const to = from + dir;
237
+ const next = [...items];
238
+ const a = next[from];
239
+ const b = next[to];
240
+ if (a === void 0 || b === void 0) return;
241
+ next[from] = b;
242
+ next[to] = a;
243
+ onChange(next);
244
+ };
245
+ return /* @__PURE__ */ jsx(View, { style: styles.stackXs, children: items.map((item, i) => /* @__PURE__ */ jsxs(View, { style: styles.rankRow, children: [
246
+ /* @__PURE__ */ jsx(Text, { style: styles.rankIndex, children: i + 1 }),
247
+ /* @__PURE__ */ jsx(Text, { style: styles.rankLabel, children: labelOf(item) }),
248
+ /* @__PURE__ */ jsx(
249
+ Button,
250
+ {
251
+ size: "sm",
252
+ variant: "outline",
253
+ leadingIcon: "chevron-up",
254
+ accessibilityLabel: `${labels.moveUp}: ${labelOf(item)}`,
255
+ disabled: i === 0,
256
+ onPress: () => move(i, -1)
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsx(
260
+ Button,
261
+ {
262
+ size: "sm",
263
+ variant: "outline",
264
+ leadingIcon: "chevron-down",
265
+ accessibilityLabel: `${labels.moveDown}: ${labelOf(item)}`,
266
+ disabled: i === items.length - 1,
267
+ onPress: () => move(i, 1)
268
+ }
269
+ )
270
+ ] }, item)) });
271
+ }
272
+ function SurveyMatrix({ question, value, onChange }) {
273
+ const theme = useLoamTheme();
274
+ const { styles } = useStyles(sheet);
275
+ const rows = question.rows ?? [];
276
+ const points = question.points ?? 5;
277
+ const current = value ?? {};
278
+ return /* @__PURE__ */ jsx(View, { style: styles.stackMd, children: rows.map((row) => /* @__PURE__ */ jsxs(View, { style: styles.matrixCard, children: [
279
+ /* @__PURE__ */ jsx(Text, { style: styles.matrixStatement, children: row.label }),
280
+ /* @__PURE__ */ jsx(View, { accessibilityRole: "radiogroup", accessibilityLabel: row.label, style: styles.matrixRow, children: Array.from({ length: points }, (_, i) => i + 1).map((v) => {
281
+ const on = current[row.value] === v;
282
+ return /* @__PURE__ */ jsx(
283
+ Pressable,
284
+ {
285
+ accessibilityRole: "radio",
286
+ accessibilityState: { checked: on },
287
+ "aria-checked": on,
288
+ accessibilityLabel: `${row.label}: ${v} of ${points}`,
289
+ onPress: () => onChange({ ...current, [row.value]: v }),
290
+ style: ({ pressed }) => [
291
+ styles.matrixCell,
292
+ on ? styles.matrixCellOn : null,
293
+ pressed && !on ? { backgroundColor: theme.state.pressed } : null
294
+ ],
295
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.matrixNumber, on ? styles.scaleNumberOn : null], children: v })
296
+ },
297
+ v
298
+ );
299
+ }) }),
300
+ /* @__PURE__ */ jsxs(View, { style: styles.endsRow, children: [
301
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: question.low }),
302
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: question.high })
303
+ ] })
304
+ ] }, row.value)) });
305
+ }
306
+ function SurveyConsent({ question, value, onChange, error }) {
307
+ const theme = useLoamTheme();
308
+ const { styles } = useStyles(sheet);
309
+ const on = value === true;
310
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackSm, children: [
311
+ /* @__PURE__ */ jsxs(
312
+ Pressable,
313
+ {
314
+ accessibilityRole: "checkbox",
315
+ accessibilityState: { checked: on },
316
+ "aria-checked": on,
317
+ accessibilityLabel: question.text,
318
+ onPress: () => onChange(!on),
319
+ style: [styles.consentCard, on ? styles.consentCardOn : null],
320
+ children: [
321
+ /* @__PURE__ */ jsx(View, { style: [styles.mark, styles.markSquare, on ? styles.markOn : null], children: on ? /* @__PURE__ */ jsx(Icon, { name: "check", size: theme.icon.xs, color: theme.color.accentInk }) : null }),
322
+ /* @__PURE__ */ jsx(Text, { style: [styles.consentText, on ? styles.optionLabelOn : null], children: question.text })
323
+ ]
324
+ }
325
+ ),
326
+ error ? /* @__PURE__ */ jsx(SurveyFieldError, { children: error }) : null
327
+ ] });
328
+ }
329
+ function SurveySlider({ question, value, onChange }) {
330
+ const { styles } = useStyles(sheet);
331
+ const min = question.min ?? 0;
332
+ const max = question.max ?? 100;
333
+ const v = typeof value === "number" ? value : question.value ?? Math.round((min + max) / 2);
334
+ return /* @__PURE__ */ jsxs(View, { style: styles.stackSm, children: [
335
+ /* @__PURE__ */ jsxs(View, { style: styles.readout, children: [
336
+ /* @__PURE__ */ jsx(Text, { style: styles.readoutValue, children: v }),
337
+ question.format ? /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: question.format }) : null
338
+ ] }),
339
+ /* @__PURE__ */ jsx(
340
+ Slider,
341
+ {
342
+ min,
343
+ max,
344
+ step: question.step ?? 1,
345
+ value: v,
346
+ onValueChange: (next) => onChange(next),
347
+ accessibilityLabel: question.text
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsxs(View, { style: styles.endsRow, children: [
351
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: min }),
352
+ /* @__PURE__ */ jsx(Text, { style: styles.endLabel, children: max })
353
+ ] })
354
+ ] });
355
+ }
356
+ function SurveyBoolean({
357
+ question,
358
+ value,
359
+ onChange,
360
+ labels: labelOverrides
361
+ }) {
362
+ const { styles } = useStyles(sheet);
363
+ const labels = { ...SURVEY_FIELD_LABELS, ...labelOverrides };
364
+ return /* @__PURE__ */ jsx(View, { style: styles.booleanRow, accessibilityRole: "radiogroup", accessibilityLabel: question.text, children: [[labels.yes, true], [labels.no, false]].map(([label, val]) => /* @__PURE__ */ jsx(View, { style: styles.grow, children: /* @__PURE__ */ jsx(
365
+ Button,
366
+ {
367
+ size: "lg",
368
+ fullWidth: true,
369
+ variant: value === val ? "primary" : "outline",
370
+ accessibilityLabel: label,
371
+ onPress: () => onChange(val, true),
372
+ children: label
373
+ }
374
+ ) }, label)) });
375
+ }
376
+ function SurveyOpenText({ question, value, onChange, error }) {
377
+ const text = typeof value === "string" ? value : "";
378
+ return /* @__PURE__ */ jsx(
379
+ Textarea,
380
+ {
381
+ accessibilityLabel: question.text,
382
+ placeholder: question.placeholder,
383
+ maxLength: question.maxLength ?? 400,
384
+ minRows: 3,
385
+ error: Boolean(error),
386
+ value: text,
387
+ onChangeText: (t) => onChange(t)
388
+ }
389
+ );
390
+ }
391
+ function SurveyTextInput({ question, value, onChange, error }) {
392
+ const t = question.type;
393
+ const text = value == null ? "" : String(value);
394
+ return /* @__PURE__ */ jsx(
395
+ Input,
396
+ {
397
+ size: "lg",
398
+ accessibilityLabel: question.text,
399
+ keyboardType: t === "number" ? "number-pad" : t === "email" ? "email-address" : "default",
400
+ autoCapitalize: t === "email" ? "none" : "sentences",
401
+ autoComplete: t === "email" ? "email" : void 0,
402
+ placeholder: question.placeholder ?? (t === "email" ? "you@company.com" : void 0),
403
+ error: Boolean(error),
404
+ value: text,
405
+ onChangeText: (next) => onChange(t === "number" ? next.replace(/[^\d.-]/g, "") : next)
406
+ }
407
+ );
408
+ }
409
+ function SurveyDropdown({
410
+ question,
411
+ value,
412
+ onChange,
413
+ labels: labelOverrides
414
+ }) {
415
+ const labels = { ...SURVEY_FIELD_LABELS, ...labelOverrides };
416
+ return /* @__PURE__ */ jsx(
417
+ Picker,
418
+ {
419
+ items: (question.options ?? []).map((o) => ({ value: o.value, label: o.label })),
420
+ value: typeof value === "string" ? value : void 0,
421
+ onValueChange: (v) => onChange(v),
422
+ placeholder: question.placeholder ?? labels.choosePlaceholder,
423
+ title: question.text,
424
+ size: "lg",
425
+ accessibilityLabel: question.text
426
+ }
427
+ );
428
+ }
429
+ function SurveyQuestionField({
430
+ question: authored,
431
+ locale,
432
+ defaultLocale,
433
+ ...control
434
+ }) {
435
+ const { styles } = useStyles(sheet);
436
+ const question = React2.useMemo(
437
+ () => resolveQuestion(authored, locale, defaultLocale),
438
+ [authored, locale, defaultLocale]
439
+ );
440
+ const props = { ...control, question };
441
+ const { value, error, labels } = props;
442
+ const t = question.type;
443
+ const merged = { ...SURVEY_FIELD_LABELS, ...labels };
444
+ if (t === "consent") return /* @__PURE__ */ jsx(SurveyConsent, { ...props });
445
+ const foot = (node) => node;
446
+ if (t === "nps" || t === "csat" || t === "ces" || t === "scale") {
447
+ return /* @__PURE__ */ jsx(SurveyField, { question, error, labels, children: /* @__PURE__ */ jsx(SurveyScale, { ...props }) });
448
+ }
449
+ if (t === "choice" || t === "pmf") {
450
+ return /* @__PURE__ */ jsx(SurveyField, { question, error, labels, children: /* @__PURE__ */ jsx(SurveyOptions, { ...props }) });
451
+ }
452
+ if (t === "multi" || t === "chips") {
453
+ const picked = Array.isArray(value) ? value : [];
454
+ const cap = question.maxSelections;
455
+ return /* @__PURE__ */ jsx(
456
+ SurveyField,
457
+ {
458
+ question,
459
+ error,
460
+ labels,
461
+ foot: foot(
462
+ cap ? /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: t === "multi" ? `${picked.length} of up to ${cap} selected` : `${picked.length} / ${cap}` }) : null
463
+ ),
464
+ children: t === "multi" ? /* @__PURE__ */ jsx(SurveyCheckboxList, { ...props }) : /* @__PURE__ */ jsx(SurveyChips, { ...props })
465
+ }
466
+ );
467
+ }
468
+ if (t === "dropdown") {
469
+ return /* @__PURE__ */ jsx(SurveyField, { question, error, labels, children: /* @__PURE__ */ jsx(SurveyDropdown, { ...props }) });
470
+ }
471
+ if (t === "boolean") {
472
+ return /* @__PURE__ */ jsx(SurveyField, { question, error, labels, children: /* @__PURE__ */ jsx(SurveyBoolean, { ...props }) });
473
+ }
474
+ if (t === "slider") {
475
+ return /* @__PURE__ */ jsx(SurveyField, { question, error, labels, children: /* @__PURE__ */ jsx(SurveySlider, { ...props }) });
476
+ }
477
+ if (t === "rank") {
478
+ return /* @__PURE__ */ jsx(
479
+ SurveyField,
480
+ {
481
+ question,
482
+ error,
483
+ labels,
484
+ foot: /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: merged.rankHint }),
485
+ children: /* @__PURE__ */ jsx(SurveyRank, { ...props })
486
+ }
487
+ );
488
+ }
489
+ if (t === "matrix") {
490
+ const current = value ?? {};
491
+ return /* @__PURE__ */ jsx(
492
+ SurveyField,
493
+ {
494
+ question,
495
+ error,
496
+ labels,
497
+ foot: /* @__PURE__ */ jsxs(Text, { style: styles.metaText, children: [
498
+ Object.keys(current).length,
499
+ " / ",
500
+ (question.rows ?? []).length,
501
+ " answered"
502
+ ] }),
503
+ children: /* @__PURE__ */ jsx(SurveyMatrix, { ...props })
504
+ }
505
+ );
506
+ }
507
+ if (t === "open") {
508
+ const text = typeof value === "string" ? value : "";
509
+ const budget = question.maxLength ?? 400;
510
+ return /* @__PURE__ */ jsx(
511
+ SurveyField,
512
+ {
513
+ question,
514
+ error,
515
+ labels,
516
+ foot: /* @__PURE__ */ jsxs(Text, { style: styles.metaText, children: [
517
+ text.length,
518
+ " / ",
519
+ budget
520
+ ] }),
521
+ children: /* @__PURE__ */ jsx(SurveyOpenText, { ...props })
522
+ }
523
+ );
524
+ }
525
+ return /* @__PURE__ */ jsx(
526
+ SurveyField,
527
+ {
528
+ question,
529
+ error,
530
+ labels,
531
+ foot: question.suffix ? /* @__PURE__ */ jsx(Text, { style: styles.metaText, children: question.suffix }) : null,
532
+ children: /* @__PURE__ */ jsx(SurveyTextInput, { ...props })
533
+ }
534
+ );
535
+ }
536
+ var sheet = createStyleSheet((theme) => ({
537
+ field: { gap: theme.space[9] },
538
+ labelRow: { flexDirection: "row", alignItems: "baseline", gap: theme.space[8] },
539
+ label: {
540
+ flexShrink: 1,
541
+ fontFamily: theme.font.sans,
542
+ fontWeight: theme.fontWeight.bold,
543
+ fontSize: theme.type.callout.fontSize,
544
+ lineHeight: theme.type.callout.lineHeight,
545
+ color: theme.color.text
546
+ },
547
+ requiredTag: {
548
+ fontFamily: theme.font.mono,
549
+ fontSize: theme.type.caption2.fontSize,
550
+ letterSpacing: theme.tracking.caps,
551
+ textTransform: "uppercase",
552
+ color: theme.color.text3
553
+ },
554
+ help: {
555
+ fontFamily: theme.font.sans,
556
+ fontSize: theme.type.footnote.fontSize,
557
+ lineHeight: theme.type.footnote.lineHeight,
558
+ color: theme.color.text3,
559
+ marginTop: -theme.space[4]
560
+ },
561
+ foot: { flexDirection: "row", alignItems: "center", gap: theme.space[8] },
562
+ metaText: {
563
+ fontFamily: theme.font.mono,
564
+ fontSize: theme.type.caption2.fontSize,
565
+ color: theme.color.text3
566
+ },
567
+ errorRow: { flexDirection: "row", alignItems: "center", gap: theme.space[7] },
568
+ errorText: {
569
+ fontFamily: theme.font.sans,
570
+ fontWeight: theme.fontWeight.semibold,
571
+ fontSize: theme.type.footnote.fontSize,
572
+ color: theme.color.danger
573
+ },
574
+ stackXs: { gap: theme.space[6] },
575
+ stackSm: { gap: theme.space[7] },
576
+ stackMd: { gap: theme.space[12] },
577
+ grow: { flex: 1 },
578
+ scaleRow: { flexDirection: "row", gap: theme.space[4] },
579
+ scaleCell: {
580
+ flex: 1,
581
+ minHeight: theme.tapMin,
582
+ alignItems: "center",
583
+ justifyContent: "center",
584
+ borderRadius: theme.radius.sm,
585
+ borderWidth: theme.borderWidth.thin,
586
+ borderColor: theme.color.borderStrong,
587
+ backgroundColor: theme.color.surface2
588
+ },
589
+ scaleCellOn: { borderColor: theme.color.accent, backgroundColor: theme.color.accent },
590
+ scaleNumber: {
591
+ fontFamily: theme.font.mono,
592
+ fontWeight: theme.fontWeight.semibold,
593
+ fontSize: theme.type.footnote.fontSize,
594
+ color: theme.color.text,
595
+ fontVariant: ["tabular-nums"]
596
+ },
597
+ scaleNumberOn: { color: theme.color.accentInk },
598
+ endsRow: { flexDirection: "row", justifyContent: "space-between" },
599
+ endLabel: {
600
+ fontFamily: theme.font.sans,
601
+ fontSize: theme.type.footnote.fontSize,
602
+ color: theme.color.text3
603
+ },
604
+ optionRow: {
605
+ flexDirection: "row",
606
+ alignItems: "center",
607
+ gap: theme.space[11],
608
+ minHeight: theme.tapMin + theme.space[4],
609
+ paddingHorizontal: theme.space[13],
610
+ paddingVertical: theme.space[8],
611
+ borderRadius: theme.radius.md,
612
+ borderWidth: theme.borderWidth.thin,
613
+ borderColor: theme.color.borderStrong,
614
+ backgroundColor: theme.color.surface2
615
+ },
616
+ optionRowOn: {
617
+ borderColor: theme.edge.accent,
618
+ backgroundColor: theme.color.accentSoft
619
+ },
620
+ optionLabel: {
621
+ flex: 1,
622
+ fontFamily: theme.font.sans,
623
+ fontSize: theme.type.subheadline.fontSize,
624
+ color: theme.color.text
625
+ },
626
+ optionLabelOn: { fontWeight: theme.fontWeight.bold, color: theme.color.accentOnSoft },
627
+ mark: {
628
+ width: theme.space[18],
629
+ height: theme.space[18],
630
+ borderRadius: theme.radius.full,
631
+ borderWidth: theme.borderWidth.thick,
632
+ borderColor: theme.color.borderStrong,
633
+ alignItems: "center",
634
+ justifyContent: "center"
635
+ },
636
+ markSquare: { borderRadius: theme.radius.xs },
637
+ markOn: { borderColor: theme.color.accent, backgroundColor: theme.color.accent },
638
+ markDot: {
639
+ width: theme.space[7],
640
+ height: theme.space[7],
641
+ borderRadius: theme.radius.full,
642
+ backgroundColor: theme.color.accentInk
643
+ },
644
+ chipWrap: { flexDirection: "row", flexWrap: "wrap", gap: theme.space[7] },
645
+ rankRow: {
646
+ flexDirection: "row",
647
+ alignItems: "center",
648
+ gap: theme.space[10],
649
+ minHeight: theme.tapMin + theme.space[4],
650
+ paddingLeft: theme.space[12],
651
+ paddingRight: theme.space[8],
652
+ paddingVertical: theme.space[6],
653
+ borderRadius: theme.radius.md,
654
+ borderWidth: theme.borderWidth.thin,
655
+ borderColor: theme.color.border,
656
+ backgroundColor: theme.color.surface2
657
+ },
658
+ rankIndex: {
659
+ width: theme.space[20],
660
+ fontFamily: theme.font.mono,
661
+ fontWeight: theme.fontWeight.semibold,
662
+ fontSize: theme.type.footnote.fontSize,
663
+ color: theme.color.accentStrong,
664
+ fontVariant: ["tabular-nums"]
665
+ },
666
+ rankLabel: {
667
+ flex: 1,
668
+ fontFamily: theme.font.sans,
669
+ fontSize: theme.type.subheadline.fontSize,
670
+ color: theme.color.text
671
+ },
672
+ matrixCard: {
673
+ gap: theme.space[8],
674
+ padding: theme.space[11],
675
+ borderRadius: theme.radius.md,
676
+ borderWidth: theme.borderWidth.thin,
677
+ borderColor: theme.color.border,
678
+ backgroundColor: theme.color.surface2
679
+ },
680
+ matrixStatement: {
681
+ fontFamily: theme.font.sans,
682
+ fontWeight: theme.fontWeight.bold,
683
+ fontSize: theme.type.subheadline.fontSize,
684
+ color: theme.color.text
685
+ },
686
+ matrixRow: { flexDirection: "row", gap: theme.space[5] },
687
+ matrixCell: {
688
+ flex: 1,
689
+ height: theme.space[40],
690
+ alignItems: "center",
691
+ justifyContent: "center",
692
+ borderRadius: theme.radius.sm,
693
+ borderWidth: theme.borderWidth.thin,
694
+ borderColor: theme.color.borderStrong,
695
+ backgroundColor: theme.color.surface
696
+ },
697
+ matrixCellOn: { borderColor: theme.color.accent, backgroundColor: theme.color.accent },
698
+ matrixNumber: {
699
+ fontFamily: theme.font.mono,
700
+ fontSize: theme.type.footnote.fontSize,
701
+ color: theme.color.text,
702
+ fontVariant: ["tabular-nums"]
703
+ },
704
+ booleanRow: { flexDirection: "row", gap: theme.space[9] },
705
+ consentCard: {
706
+ flexDirection: "row",
707
+ alignItems: "flex-start",
708
+ gap: theme.space[11],
709
+ padding: theme.space[12],
710
+ borderRadius: theme.radius.md,
711
+ borderWidth: theme.borderWidth.thin,
712
+ borderColor: theme.color.borderStrong,
713
+ backgroundColor: theme.color.surface2
714
+ },
715
+ consentCardOn: { borderColor: theme.edge.accent, backgroundColor: theme.color.accentSoft },
716
+ consentText: {
717
+ flex: 1,
718
+ fontFamily: theme.font.sans,
719
+ fontSize: theme.type.footnote.fontSize,
720
+ lineHeight: theme.type.footnote.lineHeight,
721
+ color: theme.color.text2
722
+ },
723
+ readout: { flexDirection: "row", alignItems: "baseline", gap: theme.space[8] },
724
+ readoutValue: {
725
+ fontFamily: theme.font.display,
726
+ fontWeight: theme.font.displayWeight,
727
+ fontSize: theme.type.title2.fontSize,
728
+ color: theme.color.text,
729
+ fontVariant: ["tabular-nums"]
730
+ }
731
+ }));
732
+ var DEFAULT_LABELS = {
733
+ ...SURVEY_FIELD_LABELS,
734
+ back: "Back",
735
+ next: "Continue",
736
+ submit: "Send",
737
+ skip: "Skip",
738
+ close: "Close",
739
+ dismiss: "Dismiss survey",
740
+ doneTitle: "Thank you \u2014 logged."
741
+ };
742
+ var TAP_TO_COMMIT = /* @__PURE__ */ new Set(["nps", "csat", "ces", "scale", "pmf", "boolean", "choice"]);
743
+ function SurveyPager({ total, index }) {
744
+ const theme = useLoamTheme();
745
+ const { styles } = useStyles(sheetStyles);
746
+ return /* @__PURE__ */ jsx(View, { accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: styles.pager, children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx(
747
+ View,
748
+ {
749
+ style: [
750
+ styles.pagerDot,
751
+ i === index ? { width: theme.space[16], backgroundColor: theme.color.accent } : { backgroundColor: i < index ? theme.color.text3 : theme.color.borderStrong }
752
+ ]
753
+ },
754
+ i
755
+ )) });
756
+ }
757
+ function SurveyDoneCard({
758
+ title = "Thank you \u2014 logged.",
759
+ body,
760
+ action,
761
+ onClose,
762
+ closeLabel = "Close"
763
+ }) {
764
+ const theme = useLoamTheme();
765
+ const { styles } = useStyles(sheetStyles);
766
+ return /* @__PURE__ */ jsxs(View, { style: styles.doneBody, children: [
767
+ /* @__PURE__ */ jsxs(View, { style: styles.doneHead, children: [
768
+ /* @__PURE__ */ jsx(Icon, { name: "check-circle", size: theme.icon.md, color: theme.color.good }),
769
+ /* @__PURE__ */ jsx(Text, { style: styles.doneTitle, children: title })
770
+ ] }),
771
+ body ? /* @__PURE__ */ jsx(Text, { style: styles.doneText, children: body }) : null,
772
+ /* @__PURE__ */ jsxs(View, { style: styles.footer, children: [
773
+ action,
774
+ /* @__PURE__ */ jsx(View, { style: styles.pushEnd, children: onClose ? /* @__PURE__ */ jsx(Button, { size: "lg", variant: "primary", onPress: onClose, children: closeLabel }) : null })
775
+ ] })
776
+ ] });
777
+ }
778
+ function Survey({
779
+ survey,
780
+ open = true,
781
+ answers: answersProp,
782
+ defaultAnswers,
783
+ onAnswersChange,
784
+ pageIndex: pageProp,
785
+ onPageIndexChange,
786
+ layout = "sheet",
787
+ onSubmit,
788
+ onDismiss,
789
+ onAction,
790
+ onEvent,
791
+ autoAdvance = true,
792
+ autoCloseMs,
793
+ dismissible = true,
794
+ locale,
795
+ hiddenFields,
796
+ metadata,
797
+ urlParams,
798
+ labels: labelOverrides,
799
+ motion = "full"
800
+ }) {
801
+ const theme = useLoamTheme();
802
+ const { styles } = useStyles(sheetStyles);
803
+ const labels = React2.useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
804
+ const fallbackLocale = survey.defaultLocale;
805
+ const t = React2.useCallback(
806
+ (value) => resolveText(value, locale, fallbackLocale),
807
+ [locale, fallbackLocale]
808
+ );
809
+ const ctx = React2.useMemo(
810
+ () => ({
811
+ hidden: resolveHiddenFields(survey.hiddenFields, { supplied: hiddenFields, urlParams, metadata }),
812
+ metadata,
813
+ urlParams
814
+ }),
815
+ // eslint-disable-next-line react-hooks/exhaustive-deps
816
+ []
817
+ );
818
+ const [uncontrolled, setUncontrolled] = React2.useState(defaultAnswers ?? {});
819
+ const answers = answersProp ?? uncontrolled;
820
+ const pages = surveyPages(survey);
821
+ const firstPage = React2.useMemo(() => nextPageIndex(survey, answers, null, ctx), []);
822
+ const [uncontrolledPage, setUncontrolledPage] = React2.useState(Math.max(0, firstPage));
823
+ const pageIdx = pageProp ?? uncontrolledPage;
824
+ const page = pages[pageIdx];
825
+ const [errors, setErrors] = React2.useState({});
826
+ const [submitted, setSubmitted] = React2.useState(firstPage < 0);
827
+ const finished = submitted || pageIdx < 0;
828
+ const [trail, setTrail] = React2.useState([]);
829
+ const [endingId, setEndingId] = React2.useState(void 0);
830
+ const emit = React2.useCallback(
831
+ (name, extra) => {
832
+ onEvent?.({ name, survey: survey.id, ...extra });
833
+ },
834
+ [onEvent, survey.id]
835
+ );
836
+ const emitRef = React2.useRef(emit);
837
+ emitRef.current = emit;
838
+ React2.useEffect(() => {
839
+ emitRef.current("survey_started");
840
+ }, []);
841
+ React2.useEffect(() => {
842
+ if (!finished && page) emitRef.current("survey_page_viewed", { page: page.id });
843
+ }, [finished, page]);
844
+ const answersRef = React2.useRef(answers);
845
+ answersRef.current = answers;
846
+ const shownAt = React2.useRef(/* @__PURE__ */ new Map());
847
+ React2.useEffect(() => {
848
+ if (finished || !page) return;
849
+ const asked = visibleQuestions(page, answersRef.current, ctx);
850
+ const opened = shownAt.current;
851
+ const at = Date.now();
852
+ for (const q of asked) {
853
+ opened.set(q.id, at);
854
+ emitRef.current("survey_question_viewed", {
855
+ page: page.id,
856
+ question: q.id,
857
+ props: { type: q.type }
858
+ });
859
+ }
860
+ return () => {
861
+ for (const q of asked) {
862
+ const from = opened.get(q.id);
863
+ if (from == null) continue;
864
+ opened.delete(q.id);
865
+ emitRef.current("survey_question_left", {
866
+ page: page.id,
867
+ question: q.id,
868
+ props: { dwellMs: Date.now() - from }
869
+ });
870
+ }
871
+ };
872
+ }, [finished, page, ctx]);
873
+ const setPage = (next) => {
874
+ if (pageProp === void 0) setUncontrolledPage(next);
875
+ onPageIndexChange?.(next);
876
+ };
877
+ const answered = visiblePages(survey, answers, ctx);
878
+ const position = page ? answered.indexOf(page) + 1 : answered.length;
879
+ const soloTapPage = autoAdvance && page != null && (() => {
880
+ const qs = visibleQuestions(page, answers, ctx);
881
+ return qs.length === 1 && qs[0] != null && TAP_TO_COMMIT.has(qs[0].type);
882
+ })();
883
+ const advanceRef = React2.useRef(() => {
884
+ });
885
+ const setAnswer = (question, value, commit = false) => {
886
+ const next = { ...answers, [question.id]: value };
887
+ if (answersProp === void 0) setUncontrolled(next);
888
+ onAnswersChange?.(next);
889
+ setErrors((e) => {
890
+ if (!e[question.id]) return e;
891
+ const { [question.id]: _drop, ...restErrors } = e;
892
+ return restErrors;
893
+ });
894
+ emitRef.current("survey_answered", {
895
+ page: page?.id,
896
+ question: question.id,
897
+ props: { type: question.type, value }
898
+ });
899
+ if (commit && soloTapPage) setTimeout(() => advanceRef.current(), 240);
900
+ };
901
+ const finish = React2.useCallback(
902
+ (final, ending2) => {
903
+ setSubmitted(true);
904
+ setEndingId(ending2);
905
+ const chosen = pickSurveyEnding(survey, final, ctx, ending2);
906
+ emitRef.current("survey_completed", {
907
+ props: {
908
+ answered: Object.keys(final).filter((k) => final[k] !== void 0 && final[k] !== "").length,
909
+ // How much was collected — a different number from the progress bar,
910
+ // and the one worth trending.
911
+ completion: surveyCompletion(survey, final, ctx),
912
+ ending: ending2,
913
+ screenOut: chosen?.screenOut,
914
+ redirectUrl: chosen?.redirectUrl
915
+ }
916
+ });
917
+ onSubmit?.(final);
918
+ },
919
+ [onSubmit, survey, ctx]
920
+ );
921
+ const advance = () => {
922
+ const found = validatePage(page, answers, ctx, locale, fallbackLocale);
923
+ if (Object.keys(found).length) {
924
+ setErrors(found);
925
+ emitRef.current("survey_validation_failed", {
926
+ page: page?.id,
927
+ props: { fields: Object.keys(found) }
928
+ });
929
+ return;
930
+ }
931
+ setErrors({});
932
+ const next = nextPageIndex(survey, answers, pageIdx, ctx);
933
+ setTrail((tr) => [...tr, pageIdx]);
934
+ if (next < 0) finish(answers, branchedEndingId(survey, answers, pageIdx));
935
+ else setPage(next);
936
+ };
937
+ advanceRef.current = advance;
938
+ const back = () => {
939
+ const previous = trail.length ? trail[trail.length - 1] : prevPageIndex(survey, answers, pageIdx, ctx);
940
+ if (previous < 0) return;
941
+ setTrail((t2) => t2.slice(0, -1));
942
+ setErrors({});
943
+ setPage(previous);
944
+ };
945
+ const dismiss = React2.useCallback(
946
+ (reason) => {
947
+ if (reason !== "done") {
948
+ emitRef.current("survey_dismissed", {
949
+ page: page?.id,
950
+ props: {
951
+ answered: Object.keys(answers).length,
952
+ // Abandoning on the last page of six is a different event from
953
+ // abandoning on the first; only this number tells them apart.
954
+ completion: surveyCompletion(survey, answers, ctx)
955
+ }
956
+ });
957
+ }
958
+ onDismiss?.(reason);
959
+ },
960
+ [onDismiss, page, answers, survey, ctx]
961
+ );
962
+ const ending = pickSurveyEnding(survey, answers, ctx, endingId);
963
+ const doneCta = ending?.cta;
964
+ const closeAfter = autoCloseMs ?? (doneCta ? 9e3 : 3400);
965
+ React2.useEffect(() => {
966
+ if (!finished || closeAfter <= 0) return;
967
+ const t2 = setTimeout(() => dismiss("done"), closeAfter);
968
+ return () => clearTimeout(t2);
969
+ }, [finished, closeAfter, dismiss]);
970
+ const title = t(survey.title) ?? "";
971
+ const questions = visibleQuestions(page, answers, ctx);
972
+ const allOptional = questions.length > 0 && questions.every((q) => !isQuestionRequired(q, answers, ctx) && isAnswerEmpty(q, answers[q.id]));
973
+ const isLast = nextPageIndex(survey, answers, pageIdx, ctx) < 0;
974
+ const canBack = trail.length > 0 || prevPageIndex(survey, answers, pageIdx, ctx) >= 0;
975
+ const content = finished ? /* @__PURE__ */ jsxs(View, { style: styles.body, children: [
976
+ /* @__PURE__ */ jsx(Text, { style: styles.kicker, children: title }),
977
+ /* @__PURE__ */ jsx(
978
+ SurveyDoneCard,
979
+ {
980
+ title: t(ending?.title) ?? labels.doneTitle,
981
+ body: t(ending?.body) ?? `Recorded against this account${typeof answers.nps === "number" ? ` as a ${npsBand(answers.nps)}` : ""}. You will not be asked this again for a while.`,
982
+ action: doneCta ? /* @__PURE__ */ jsx(Button, { size: "md", variant: "outline", trailingIcon: "arrow-right", onPress: () => onAction?.(doneCta.action), children: t(doneCta.label) }) : null,
983
+ onClose: () => dismiss("done"),
984
+ closeLabel: labels.close
985
+ }
986
+ )
987
+ ] }) : /* @__PURE__ */ jsxs(View, { style: styles.body, children: [
988
+ /* @__PURE__ */ jsxs(View, { style: styles.header, children: [
989
+ /* @__PURE__ */ jsxs(View, { style: styles.headerText, children: [
990
+ /* @__PURE__ */ jsx(Text, { style: styles.kicker, children: title }),
991
+ page?.title ? /* @__PURE__ */ jsx(Text, { style: styles.pageTitle, children: t(page.title) }) : null
992
+ ] }),
993
+ answered.length > 1 ? /* @__PURE__ */ jsxs(Text, { style: styles.counter, children: [
994
+ position,
995
+ " / ",
996
+ answered.length
997
+ ] }) : null,
998
+ dismissible ? /* @__PURE__ */ jsx(
999
+ Pressable,
1000
+ {
1001
+ accessibilityRole: "button",
1002
+ accessibilityLabel: labels.dismiss,
1003
+ onPress: () => dismiss("user"),
1004
+ hitSlop: theme.space[10],
1005
+ style: styles.iconButton,
1006
+ children: /* @__PURE__ */ jsx(Icon, { name: "x", size: theme.icon.sm, color: theme.color.text3 })
1007
+ }
1008
+ ) : null
1009
+ ] }),
1010
+ /* @__PURE__ */ jsx(
1011
+ Progress,
1012
+ {
1013
+ size: "sm",
1014
+ value: Math.max(0, position - 1),
1015
+ max: Math.max(1, answered.length),
1016
+ accessibilityLabel: `${title} \u2014 page ${position} of ${answered.length}`,
1017
+ motion
1018
+ }
1019
+ ),
1020
+ survey.intro && position === 1 ? /* @__PURE__ */ jsx(Text, { style: styles.intro, children: t(survey.intro) }) : null,
1021
+ /* @__PURE__ */ jsx(View, { style: styles.questions, children: questions.map((q) => /* @__PURE__ */ jsx(
1022
+ SurveyQuestionField,
1023
+ {
1024
+ question: q,
1025
+ locale,
1026
+ defaultLocale: fallbackLocale,
1027
+ value: answers[q.id],
1028
+ error: errors[q.id],
1029
+ labels,
1030
+ onChange: (value, commit) => setAnswer(q, value, commit)
1031
+ },
1032
+ q.id
1033
+ )) }),
1034
+ /* @__PURE__ */ jsxs(View, { style: styles.footer, children: [
1035
+ canBack ? /* @__PURE__ */ jsx(Button, { size: "md", variant: "outline", leadingIcon: "arrow-left", onPress: back, children: labels.back }) : null,
1036
+ answered.length > 1 ? /* @__PURE__ */ jsx(SurveyPager, { total: answered.length, index: position - 1 }) : /* @__PURE__ */ jsx(View, { style: styles.grow }),
1037
+ allOptional && answered.length > 1 ? /* @__PURE__ */ jsx(
1038
+ Button,
1039
+ {
1040
+ size: "md",
1041
+ variant: "ghost",
1042
+ onPress: () => {
1043
+ emitRef.current("survey_page_skipped", { page: page?.id });
1044
+ setTrail((t2) => [...t2, pageIdx]);
1045
+ const next = nextPageIndex(survey, answers, pageIdx);
1046
+ if (next < 0) finish(answers);
1047
+ else setPage(next);
1048
+ },
1049
+ children: labels.skip
1050
+ }
1051
+ ) : null,
1052
+ /* @__PURE__ */ jsx(Button, { size: "lg", variant: "primary", onPress: advance, children: isLast ? labels.submit : labels.next })
1053
+ ] })
1054
+ ] });
1055
+ if (layout === "inline") return content;
1056
+ return /* @__PURE__ */ jsx(
1057
+ Sheet,
1058
+ {
1059
+ open,
1060
+ onClose: () => dismiss(finished ? "done" : "user"),
1061
+ dismissible,
1062
+ detents: pages.length > 1 ? ["half", "full"] : void 0,
1063
+ motion,
1064
+ children: content
1065
+ }
1066
+ );
1067
+ }
1068
+ var sheetStyles = createStyleSheet((theme) => ({
1069
+ body: { gap: theme.space[16] },
1070
+ header: { flexDirection: "row", alignItems: "center", gap: theme.space[9] },
1071
+ headerText: { flex: 1, gap: theme.space[2] },
1072
+ kicker: {
1073
+ fontFamily: theme.font.mono,
1074
+ fontSize: theme.type.caption2.fontSize,
1075
+ letterSpacing: theme.tracking.caps,
1076
+ textTransform: "uppercase",
1077
+ color: theme.color.accentStrong
1078
+ },
1079
+ pageTitle: {
1080
+ fontFamily: theme.font.sans,
1081
+ fontWeight: theme.fontWeight.bold,
1082
+ fontSize: theme.type.subheadline.fontSize,
1083
+ color: theme.color.text
1084
+ },
1085
+ counter: {
1086
+ fontFamily: theme.font.mono,
1087
+ fontSize: theme.type.caption2.fontSize,
1088
+ color: theme.color.text3,
1089
+ fontVariant: ["tabular-nums"]
1090
+ },
1091
+ iconButton: {
1092
+ width: theme.ctl.sm,
1093
+ height: theme.ctl.sm,
1094
+ alignItems: "center",
1095
+ justifyContent: "center",
1096
+ borderRadius: theme.radius.full
1097
+ },
1098
+ intro: {
1099
+ fontFamily: theme.font.sans,
1100
+ fontSize: theme.type.footnote.fontSize,
1101
+ lineHeight: theme.type.footnote.lineHeight,
1102
+ color: theme.color.text3
1103
+ },
1104
+ questions: { gap: theme.space[20] },
1105
+ footer: { flexDirection: "row", alignItems: "center", gap: theme.space[9] },
1106
+ grow: { flex: 1 },
1107
+ pushEnd: { marginLeft: "auto" },
1108
+ pager: { flex: 1, flexDirection: "row", alignItems: "center", gap: theme.space[5] },
1109
+ pagerDot: { width: theme.space[5], height: theme.space[5], borderRadius: theme.radius.full },
1110
+ doneBody: { gap: theme.space[11] },
1111
+ doneHead: { flexDirection: "row", alignItems: "center", gap: theme.space[10] },
1112
+ doneTitle: {
1113
+ fontFamily: theme.font.display,
1114
+ fontWeight: theme.font.displayWeight,
1115
+ fontSize: theme.type.title3.fontSize,
1116
+ color: theme.color.text
1117
+ },
1118
+ doneText: {
1119
+ fontFamily: theme.font.sans,
1120
+ fontSize: theme.type.subheadline.fontSize,
1121
+ lineHeight: theme.type.subheadline.lineHeight,
1122
+ color: theme.color.text2
1123
+ }
1124
+ }));
1125
+
1126
+ export { SURVEY_FIELD_LABELS, SURVEY_OTHER_PREFIX, Survey, SurveyBoolean, SurveyCheckboxList, SurveyChips, SurveyConsent, SurveyDoneCard, SurveyDropdown, SurveyField, SurveyFieldError, SurveyMatrix, SurveyOpenText, SurveyOptionRow, SurveyOptions, SurveyPager, SurveyQuestionField, SurveyRank, SurveyScale, SurveySlider, SurveyTextInput };