@multiplatform.one/frappe-ui 7.6.3 → 7.7.1

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 (285) hide show
  1. package/dist/cjs/ChartCard.cjs +52 -0
  2. package/dist/cjs/ChartCard.native.js +72 -0
  3. package/dist/cjs/ChartCard.native.js.map +1 -0
  4. package/dist/cjs/FileBrowser/AttachLibrary.cjs +2 -2
  5. package/dist/cjs/FileBrowser/AttachLibrary.native.js +8 -2
  6. package/dist/cjs/FileBrowser/AttachLibrary.native.js.map +1 -1
  7. package/dist/cjs/FrappeChartCard.cjs +3 -3
  8. package/dist/cjs/FrappeChartCard.native.js +3 -3
  9. package/dist/cjs/FrappeChartCard.native.js.map +1 -1
  10. package/dist/cjs/FrappeDashboard.cjs +6 -27
  11. package/dist/cjs/FrappeDashboard.native.js +6 -33
  12. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  13. package/dist/cjs/FrappeImageView.cjs +2 -8
  14. package/dist/cjs/FrappeImageView.native.js +2 -9
  15. package/dist/cjs/FrappeImageView.native.js.map +1 -1
  16. package/dist/cjs/fields/CodeEditor/index.cjs +4 -0
  17. package/dist/cjs/fields/CodeEditor/index.native.js +4 -0
  18. package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
  19. package/dist/cjs/fields/LinkTree.cjs +5 -3
  20. package/dist/cjs/fields/LinkTree.native.js +9 -5
  21. package/dist/cjs/fields/LinkTree.native.js.map +1 -1
  22. package/dist/cjs/formEngine/createPrefill.cjs +37 -0
  23. package/dist/cjs/formEngine/createPrefill.native.js +40 -0
  24. package/dist/cjs/formEngine/createPrefill.native.js.map +1 -0
  25. package/dist/cjs/formEngine/dirtyTracking.cjs +55 -0
  26. package/dist/cjs/formEngine/dirtyTracking.native.js +77 -0
  27. package/dist/cjs/formEngine/dirtyTracking.native.js.map +1 -0
  28. package/dist/cjs/formEngine/doctypeMeta.cjs +18 -0
  29. package/dist/cjs/formEngine/doctypeMeta.native.js +21 -0
  30. package/dist/cjs/formEngine/doctypeMeta.native.js.map +1 -0
  31. package/dist/cjs/formEngine/fetchFrom.cjs +67 -0
  32. package/dist/cjs/formEngine/fetchFrom.native.js +110 -0
  33. package/dist/cjs/formEngine/fetchFrom.native.js.map +1 -0
  34. package/dist/cjs/formEngine/fieldPermissions.cjs +55 -0
  35. package/dist/cjs/formEngine/fieldPermissions.native.js +80 -0
  36. package/dist/cjs/formEngine/fieldPermissions.native.js.map +1 -0
  37. package/dist/cjs/formEngine/formActions.cjs +189 -0
  38. package/dist/cjs/formEngine/formActions.native.js +199 -0
  39. package/dist/cjs/formEngine/formActions.native.js.map +1 -0
  40. package/dist/cjs/formEngine/formLayout.cjs +245 -0
  41. package/dist/cjs/formEngine/formLayout.native.js +388 -0
  42. package/dist/cjs/formEngine/formLayout.native.js.map +1 -0
  43. package/dist/cjs/formEngine/index.cjs +48 -0
  44. package/dist/cjs/formEngine/index.native.js +51 -0
  45. package/dist/cjs/formEngine/index.native.js.map +1 -0
  46. package/dist/cjs/formEngine/useFormEngine.cjs +387 -0
  47. package/dist/cjs/formEngine/useFormEngine.native.js +476 -0
  48. package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -0
  49. package/dist/cjs/frappeStoryTarget.cjs +4 -2
  50. package/dist/cjs/frappeStoryTarget.native.js +4 -3
  51. package/dist/cjs/frappeStoryTarget.native.js.map +1 -1
  52. package/dist/cjs/index.cjs +19 -1
  53. package/dist/cjs/index.native.js +19 -1
  54. package/dist/cjs/index.native.js.map +1 -1
  55. package/dist/esm/ChartCard.mjs +27 -0
  56. package/dist/esm/ChartCard.mjs.map +1 -0
  57. package/dist/esm/ChartCard.native.js +33 -0
  58. package/dist/esm/ChartCard.native.js.map +1 -0
  59. package/dist/esm/FileBrowser/AttachLibrary.mjs +2 -2
  60. package/dist/esm/FileBrowser/AttachLibrary.mjs.map +1 -1
  61. package/dist/esm/FileBrowser/AttachLibrary.native.js +8 -2
  62. package/dist/esm/FileBrowser/AttachLibrary.native.js.map +1 -1
  63. package/dist/esm/FrappeChartCard.mjs +1 -1
  64. package/dist/esm/FrappeChartCard.native.js +1 -1
  65. package/dist/esm/FrappeChartCard.native.js.map +1 -1
  66. package/dist/esm/FrappeDashboard.mjs +6 -27
  67. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  68. package/dist/esm/FrappeDashboard.native.js +6 -33
  69. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  70. package/dist/esm/FrappeImageView.mjs +2 -7
  71. package/dist/esm/FrappeImageView.mjs.map +1 -1
  72. package/dist/esm/FrappeImageView.native.js +2 -8
  73. package/dist/esm/FrappeImageView.native.js.map +1 -1
  74. package/dist/esm/fields/CodeEditor/index.mjs +4 -0
  75. package/dist/esm/fields/CodeEditor/index.mjs.map +1 -1
  76. package/dist/esm/fields/CodeEditor/index.native.js +4 -0
  77. package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
  78. package/dist/esm/fields/LinkTree.mjs +5 -3
  79. package/dist/esm/fields/LinkTree.mjs.map +1 -1
  80. package/dist/esm/fields/LinkTree.native.js +9 -5
  81. package/dist/esm/fields/LinkTree.native.js.map +1 -1
  82. package/dist/esm/formEngine/createPrefill.mjs +11 -0
  83. package/dist/esm/formEngine/createPrefill.mjs.map +1 -0
  84. package/dist/esm/formEngine/createPrefill.native.js +11 -0
  85. package/dist/esm/formEngine/createPrefill.native.js.map +1 -0
  86. package/dist/esm/formEngine/dirtyTracking.mjs +28 -0
  87. package/dist/esm/formEngine/dirtyTracking.mjs.map +1 -0
  88. package/dist/esm/formEngine/dirtyTracking.native.js +47 -0
  89. package/dist/esm/formEngine/dirtyTracking.native.js.map +1 -0
  90. package/dist/esm/formEngine/doctypeMeta.mjs +2 -0
  91. package/dist/esm/formEngine/doctypeMeta.mjs.map +1 -0
  92. package/dist/esm/formEngine/doctypeMeta.native.js +2 -0
  93. package/dist/esm/formEngine/doctypeMeta.native.js.map +1 -0
  94. package/dist/esm/formEngine/fetchFrom.mjs +40 -0
  95. package/dist/esm/formEngine/fetchFrom.mjs.map +1 -0
  96. package/dist/esm/formEngine/fetchFrom.native.js +80 -0
  97. package/dist/esm/formEngine/fetchFrom.native.js.map +1 -0
  98. package/dist/esm/formEngine/fieldPermissions.mjs +28 -0
  99. package/dist/esm/formEngine/fieldPermissions.mjs.map +1 -0
  100. package/dist/esm/formEngine/fieldPermissions.native.js +50 -0
  101. package/dist/esm/formEngine/fieldPermissions.native.js.map +1 -0
  102. package/dist/esm/formEngine/formActions.mjs +146 -0
  103. package/dist/esm/formEngine/formActions.mjs.map +1 -0
  104. package/dist/esm/formEngine/formActions.native.js +153 -0
  105. package/dist/esm/formEngine/formActions.native.js.map +1 -0
  106. package/dist/esm/formEngine/formLayout.mjs +217 -0
  107. package/dist/esm/formEngine/formLayout.mjs.map +1 -0
  108. package/dist/esm/formEngine/formLayout.native.js +357 -0
  109. package/dist/esm/formEngine/formLayout.native.js.map +1 -0
  110. package/dist/esm/formEngine/index.mjs +8 -0
  111. package/dist/esm/formEngine/index.mjs.map +1 -0
  112. package/dist/esm/formEngine/index.native.js +8 -0
  113. package/dist/esm/formEngine/index.native.js.map +1 -0
  114. package/dist/esm/formEngine/useFormEngine.mjs +362 -0
  115. package/dist/esm/formEngine/useFormEngine.mjs.map +1 -0
  116. package/dist/esm/formEngine/useFormEngine.native.js +448 -0
  117. package/dist/esm/formEngine/useFormEngine.native.js.map +1 -0
  118. package/dist/esm/frappeStoryTarget.mjs +4 -1
  119. package/dist/esm/frappeStoryTarget.mjs.map +1 -1
  120. package/dist/esm/frappeStoryTarget.native.js +4 -2
  121. package/dist/esm/frappeStoryTarget.native.js.map +1 -1
  122. package/dist/esm/index.mjs +4 -2
  123. package/dist/esm/index.mjs.map +1 -1
  124. package/dist/esm/index.native.js +4 -2
  125. package/dist/esm/index.native.js.map +1 -1
  126. package/dist/jsx/ChartCard.mjs +27 -0
  127. package/dist/jsx/ChartCard.mjs.map +1 -0
  128. package/dist/jsx/ChartCard.native.js +72 -0
  129. package/dist/jsx/ChartCard.native.js.map +1 -0
  130. package/dist/jsx/FileBrowser/AttachLibrary.mjs +2 -2
  131. package/dist/jsx/FileBrowser/AttachLibrary.mjs.map +1 -1
  132. package/dist/jsx/FileBrowser/AttachLibrary.native.js +8 -2
  133. package/dist/jsx/FileBrowser/AttachLibrary.native.js.map +1 -1
  134. package/dist/jsx/FrappeChartCard.mjs +1 -1
  135. package/dist/jsx/FrappeChartCard.native.js +3 -3
  136. package/dist/jsx/FrappeChartCard.native.js.map +1 -1
  137. package/dist/jsx/FrappeDashboard.mjs +6 -27
  138. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  139. package/dist/jsx/FrappeDashboard.native.js +6 -33
  140. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  141. package/dist/jsx/FrappeImageView.mjs +2 -7
  142. package/dist/jsx/FrappeImageView.mjs.map +1 -1
  143. package/dist/jsx/FrappeImageView.native.js +2 -9
  144. package/dist/jsx/FrappeImageView.native.js.map +1 -1
  145. package/dist/jsx/fields/CodeEditor/index.mjs +4 -0
  146. package/dist/jsx/fields/CodeEditor/index.mjs.map +1 -1
  147. package/dist/jsx/fields/CodeEditor/index.native.js +4 -0
  148. package/dist/jsx/fields/CodeEditor/index.native.js.map +1 -1
  149. package/dist/jsx/fields/LinkTree.mjs +5 -3
  150. package/dist/jsx/fields/LinkTree.mjs.map +1 -1
  151. package/dist/jsx/fields/LinkTree.native.js +9 -5
  152. package/dist/jsx/fields/LinkTree.native.js.map +1 -1
  153. package/dist/jsx/formEngine/createPrefill.mjs +11 -0
  154. package/dist/jsx/formEngine/createPrefill.mjs.map +1 -0
  155. package/dist/jsx/formEngine/createPrefill.native.js +40 -0
  156. package/dist/jsx/formEngine/createPrefill.native.js.map +1 -0
  157. package/dist/jsx/formEngine/dirtyTracking.mjs +28 -0
  158. package/dist/jsx/formEngine/dirtyTracking.mjs.map +1 -0
  159. package/dist/jsx/formEngine/dirtyTracking.native.js +77 -0
  160. package/dist/jsx/formEngine/dirtyTracking.native.js.map +1 -0
  161. package/dist/jsx/formEngine/doctypeMeta.mjs +2 -0
  162. package/dist/jsx/formEngine/doctypeMeta.mjs.map +1 -0
  163. package/dist/jsx/formEngine/doctypeMeta.native.js +21 -0
  164. package/dist/jsx/formEngine/doctypeMeta.native.js.map +1 -0
  165. package/dist/jsx/formEngine/fetchFrom.mjs +40 -0
  166. package/dist/jsx/formEngine/fetchFrom.mjs.map +1 -0
  167. package/dist/jsx/formEngine/fetchFrom.native.js +110 -0
  168. package/dist/jsx/formEngine/fetchFrom.native.js.map +1 -0
  169. package/dist/jsx/formEngine/fieldPermissions.mjs +28 -0
  170. package/dist/jsx/formEngine/fieldPermissions.mjs.map +1 -0
  171. package/dist/jsx/formEngine/fieldPermissions.native.js +80 -0
  172. package/dist/jsx/formEngine/fieldPermissions.native.js.map +1 -0
  173. package/dist/jsx/formEngine/formActions.mjs +146 -0
  174. package/dist/jsx/formEngine/formActions.mjs.map +1 -0
  175. package/dist/jsx/formEngine/formActions.native.js +199 -0
  176. package/dist/jsx/formEngine/formActions.native.js.map +1 -0
  177. package/dist/jsx/formEngine/formLayout.mjs +217 -0
  178. package/dist/jsx/formEngine/formLayout.mjs.map +1 -0
  179. package/dist/jsx/formEngine/formLayout.native.js +388 -0
  180. package/dist/jsx/formEngine/formLayout.native.js.map +1 -0
  181. package/dist/jsx/formEngine/index.mjs +8 -0
  182. package/dist/jsx/formEngine/index.mjs.map +1 -0
  183. package/dist/jsx/formEngine/index.native.js +51 -0
  184. package/dist/jsx/formEngine/index.native.js.map +1 -0
  185. package/dist/jsx/formEngine/useFormEngine.mjs +362 -0
  186. package/dist/jsx/formEngine/useFormEngine.mjs.map +1 -0
  187. package/dist/jsx/formEngine/useFormEngine.native.js +476 -0
  188. package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -0
  189. package/dist/jsx/frappeStoryTarget.mjs +4 -1
  190. package/dist/jsx/frappeStoryTarget.mjs.map +1 -1
  191. package/dist/jsx/frappeStoryTarget.native.js +4 -3
  192. package/dist/jsx/frappeStoryTarget.native.js.map +1 -1
  193. package/dist/jsx/index.js +4 -2
  194. package/dist/jsx/index.js.map +1 -1
  195. package/dist/jsx/index.mjs +4 -2
  196. package/dist/jsx/index.mjs.map +1 -1
  197. package/dist/jsx/index.native.js +19 -1
  198. package/dist/jsx/index.native.js.map +1 -1
  199. package/package.json +23 -17
  200. package/src/ChartCard.tsx +31 -0
  201. package/src/FileBrowser/AttachLibrary.tsx +13 -1
  202. package/src/FileBrowser/FileBrowser.spec.tsx +16 -0
  203. package/src/FileBrowser/FileBrowser.stories.tsx +18 -1
  204. package/src/FileBrowser/folderTree.spec.tsx +68 -0
  205. package/src/FrappeCalendar.stories.tsx +1 -2
  206. package/src/FrappeChartCard.stories.tsx +1 -1
  207. package/src/FrappeChartCard.tsx +1 -1
  208. package/src/FrappeDashboard.stories.tsx +6 -5
  209. package/src/FrappeDashboard.tsx +6 -33
  210. package/src/FrappeGantt.stories.tsx +1 -2
  211. package/src/FrappeImageView.tsx +16 -15
  212. package/src/FrappeList.stories.tsx +3 -10
  213. package/src/classicScriptSafety.spec.ts +47 -0
  214. package/src/components/attachments.stories.tsx +9 -2
  215. package/src/dependsOn.ts +2 -2
  216. package/src/desk/DeskFormSidebar.stories.tsx +19 -1
  217. package/src/fields/CodeEditor/CodeEditor.spec.tsx +37 -0
  218. package/src/fields/CodeEditor/CodeEditor.stories.tsx +83 -66
  219. package/src/fields/CodeEditor/index.tsx +4 -0
  220. package/src/fields/DateRange/DateRange.spec.tsx +143 -0
  221. package/src/fields/FileUpload.spec.tsx +334 -0
  222. package/src/fields/FileUploadField/FileUploadField.spec.tsx +166 -0
  223. package/src/fields/HTML/Html.spec.tsx +179 -0
  224. package/src/fields/Icon/Icon.stories.tsx +138 -0
  225. package/src/fields/Link.stories.tsx +2 -0
  226. package/src/fields/LinkTree.tsx +18 -3
  227. package/src/fields/LongText/LongText.spec.tsx +217 -0
  228. package/src/fields/OTPInput/OTPInput.spec.tsx +167 -0
  229. package/src/fields/Password/Password.stories.tsx +21 -0
  230. package/src/fields/Phone/Phone.spec.tsx +151 -0
  231. package/src/fields/Progress/Progress.spec.tsx +160 -0
  232. package/src/fields/ReadOnly/ReadOnly.spec.tsx +229 -0
  233. package/src/fields/SmallText/SmallText.spec.tsx +175 -0
  234. package/src/fields/shared/EditorPreviewPanes.spec.tsx +122 -0
  235. package/src/formEngine/createPrefill.ts +28 -0
  236. package/src/formEngine/dirtyTracking.spec.ts +91 -0
  237. package/src/formEngine/dirtyTracking.ts +64 -0
  238. package/src/formEngine/doctypeMeta.ts +117 -0
  239. package/src/formEngine/fetchFrom.spec.ts +92 -0
  240. package/src/formEngine/fetchFrom.ts +111 -0
  241. package/src/formEngine/fieldPermissions.spec.ts +61 -0
  242. package/src/formEngine/fieldPermissions.ts +68 -0
  243. package/src/formEngine/formActions.ts +285 -0
  244. package/src/formEngine/formLayout.spec.ts +373 -0
  245. package/src/formEngine/formLayout.ts +392 -0
  246. package/src/formEngine/index.ts +33 -0
  247. package/src/formEngine/seam.spec.ts +148 -0
  248. package/src/formEngine/useFormEngine.ts +496 -0
  249. package/src/frappeRemainder.designLaw.spec.ts +9 -0
  250. package/src/frappeStoryTarget.tsx +25 -9
  251. package/src/frappeViews.designLaw.spec.ts +1 -0
  252. package/src/index.spec.ts +7 -3
  253. package/src/index.ts +51 -1
  254. package/src/schemaForm/SchemaForm.spec.tsx +4 -1
  255. package/types/ChartCard.d.ts +13 -0
  256. package/types/ChartCard.d.ts.map +1 -0
  257. package/types/FileBrowser/AttachLibrary.d.ts.map +1 -1
  258. package/types/FrappeDashboard.d.ts +2 -10
  259. package/types/FrappeDashboard.d.ts.map +1 -1
  260. package/types/FrappeImageView.d.ts.map +1 -1
  261. package/types/dependsOn.d.ts +2 -2
  262. package/types/fields/CodeEditor/index.d.ts.map +1 -1
  263. package/types/fields/LinkTree.d.ts.map +1 -1
  264. package/types/formEngine/createPrefill.d.ts +19 -0
  265. package/types/formEngine/createPrefill.d.ts.map +1 -0
  266. package/types/formEngine/dirtyTracking.d.ts +36 -0
  267. package/types/formEngine/dirtyTracking.d.ts.map +1 -0
  268. package/types/formEngine/doctypeMeta.d.ts +117 -0
  269. package/types/formEngine/doctypeMeta.d.ts.map +1 -0
  270. package/types/formEngine/fetchFrom.d.ts +43 -0
  271. package/types/formEngine/fetchFrom.d.ts.map +1 -0
  272. package/types/formEngine/fieldPermissions.d.ts +38 -0
  273. package/types/formEngine/fieldPermissions.d.ts.map +1 -0
  274. package/types/formEngine/formActions.d.ts +95 -0
  275. package/types/formEngine/formActions.d.ts.map +1 -0
  276. package/types/formEngine/formLayout.d.ts +91 -0
  277. package/types/formEngine/formLayout.d.ts.map +1 -0
  278. package/types/formEngine/index.d.ts +27 -0
  279. package/types/formEngine/index.d.ts.map +1 -0
  280. package/types/formEngine/useFormEngine.d.ts +61 -0
  281. package/types/formEngine/useFormEngine.d.ts.map +1 -0
  282. package/types/frappeStoryTarget.d.ts +2 -2
  283. package/types/frappeStoryTarget.d.ts.map +1 -1
  284. package/types/index.d.ts +4 -1
  285. package/types/index.d.ts.map +1 -1
@@ -3,13 +3,7 @@ import {
3
3
  useFrappeDoctypeEvents,
4
4
  useLiveQuery,
5
5
  } from "@multiplatform.one/frappe";
6
- import {
7
- ChartCard as BaseChartCard,
8
- Dashboard,
9
- type ChartCardProps,
10
- type ChartDataPoint,
11
- type DashboardWidget,
12
- } from "@multiplatform.one/components";
6
+ import { Dashboard, type DashboardWidget } from "@multiplatform.one/components";
13
7
  import { Button, Spinner, formCommonColors } from "@multiplatform.one/forms";
14
8
  import { ArrowsClockwiseIcon } from "@phosphor-icons/react";
15
9
  import React, { useMemo, useCallback, useState, useEffect, useRef } from "react";
@@ -168,7 +162,7 @@ function LiveDashboardContent<TDoc extends { name: string; doctype: string }>({
168
162
  return (
169
163
  <YStack {...knobProps.panelPadding} alignItems="center" {...stackProps}>
170
164
  <Spinner size="large" />
171
- <Text color={formCommonColors.muted} marginTop="$2" {...knobProps.body}>
165
+ <Text color={knobProps.textAccentColor} marginTop="$2" {...knobProps.body}>
172
166
  {t("Loading dashboard...")}
173
167
  </Text>
174
168
  </YStack>
@@ -181,7 +175,7 @@ function LiveDashboardContent<TDoc extends { name: string; doctype: string }>({
181
175
  return (
182
176
  <YStack {...knobProps.panelPadding} alignItems="center" {...stackProps} {...knobProps.gap}>
183
177
  <Text color={formCommonColors.error}>{t("Failed to load dashboard")}</Text>
184
- <Text color={formCommonColors.muted} {...knobProps.body}>
178
+ <Text color={knobProps.textAccentColor} {...knobProps.body}>
185
179
  {err.message}
186
180
  </Text>
187
181
  </YStack>
@@ -201,7 +195,7 @@ function LiveDashboardContent<TDoc extends { name: string; doctype: string }>({
201
195
  {showRefresh && (
202
196
  <XStack {...knobProps.gap} alignItems="center">
203
197
  {isLiveStale && <Spinner size="small" aria-label={t("Updating")} />}
204
- <Text color={formCommonColors.muted} {...knobProps.label}>
198
+ <Text color={knobProps.textAccentColor} {...knobProps.label}>
205
199
  {t("Last updated: {{time}}", {
206
200
  time: formatTimeOfDay(dataUpdatedAt ?? lastRefresh),
207
201
  })}
@@ -321,7 +315,7 @@ export function FrappeDashboard<TDoc extends { name: string; doctype: string }>(
321
315
  {title ? <H3 {...knobProps.heading}>{title}</H3> : <YStack flex={1} />}
322
316
  <XStack {...knobProps.gap} alignItems="center">
323
317
  {autoRefresh && (
324
- <Text color={formCommonColors.muted} {...knobProps.label}>
318
+ <Text color={knobProps.textAccentColor} {...knobProps.label}>
325
319
  {t("Last updated: {{time}}", { time: formatTimeOfDay(lastRefresh) })}
326
320
  </Text>
327
321
  )}
@@ -380,25 +374,4 @@ export function FrappeDashboard<TDoc extends { name: string; doctype: string }>(
380
374
  );
381
375
  }
382
376
 
383
- /**
384
- * ChartCard bridge that sanitizes series rows before render: values are
385
- * coerced to numbers and rows without a finite value (missing, NaN,
386
- * non-numeric, Infinity) are dropped, so malformed series can't flood the
387
- * SVG layer with invalid-length (`NaN`) attribute errors. Bar and pie
388
- * renderers can't draw negative magnitudes (negative rect heights / arc
389
- * angles are invalid SVG), so negatives clamp to the baseline there.
390
- */
391
- export function ChartCard({ config, ...chartCardProps }: ChartCardProps) {
392
- const clampNegatives = config.type === "bar" || config.type === "pie";
393
- const data = useMemo(
394
- () =>
395
- (config.data ?? []).flatMap((point) => {
396
- const value = Number((point as Partial<ChartDataPoint> | undefined)?.value);
397
- if (!Number.isFinite(value)) return [];
398
- return [{ ...point, value: clampNegatives && value < 0 ? 0 : value }];
399
- }),
400
- [config.data, clampNegatives],
401
- );
402
- const sanitizedConfig = useMemo(() => ({ ...config, data }), [config.data, data]);
403
- return <BaseChartCard {...chartCardProps} config={sanitizedConfig} />;
404
- }
377
+ export { ChartCard } from "./ChartCard";
@@ -77,8 +77,7 @@ function jsonResponse(body: unknown, status = 200): Response {
77
77
  return jsonResponse({ docs: [taskMetaRaw(doctype)] });
78
78
  }
79
79
  const metaMatch = url.pathname.match(/^\/api\/resource\/DocType\/(.+)$/);
80
- if (metaMatch)
81
- return jsonResponse({ data: taskMetaRaw(decodeURIComponent(metaMatch[1])) });
80
+ if (metaMatch) return jsonResponse({ data: taskMetaRaw(decodeURIComponent(metaMatch[1])) });
82
81
  if (url.pathname === "/api/resource/DocType") {
83
82
  return jsonResponse({ data: [{ name: "Task", module: "Projects" }] });
84
83
  }
@@ -5,25 +5,26 @@ import React, { useMemo } from "react";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import { Spinner, formCommonColors } from "@multiplatform.one/forms";
7
7
  import { Image, Text, YStack, type YStackProps } from "tamagui";
8
+ import { isServer } from "@multiplatform.one/platform";
8
9
  import { useResolvedKnobs } from "@multiplatform.one/theme";
9
10
  import { useFrappeConfig } from "./FrappeProvider";
10
11
  import { FrappeReconnectIndicator } from "./FrappeReconnectIndicator";
11
12
 
12
13
  /**
13
- * True when this module is evaluated during web server rendering. Same
14
- * guarded resolution as `frappeStoryTarget` / `frappe`'s `useLiveQuery`:
15
- * a bare `import.meta.env.SSR` member access throws on native
16
- * (Metro/Hermes has no Vite define — `import.meta.env` is undefined at
17
- * runtime), which crashed this component on device. Native lands on
18
- * `false`, which is correct — native is always a client.
14
+ * True when this module is evaluated during web server rendering.
15
+ *
16
+ * This used to read `import.meta.env.SSR` behind a try/catch, which MPO-192
17
+ * ruled out: in a classic script — the vxrn/rolldown Hermes bundle, or any
18
+ * Metro bundle for platform=web, which is what an Expo DOM component
19
+ * (`'use dom'`) is — `import.meta` is a PARSE-time SyntaxError, so the catch
20
+ * never runs and the whole module fails to parse.
21
+ *
22
+ * `isServer` carries no `import.meta` and is split at the FILE level
23
+ * (@tamagui/constants ships a `.native` flavour hardcoding it false), so it
24
+ * answers correctly in all four places: true only under web server render,
25
+ * false in a browser, on native, and inside a DOM component's WebView.
19
26
  */
20
- const IS_SSR: boolean = (() => {
21
- try {
22
- return (import.meta as ImportMeta & { env?: { SSR?: boolean } }).env?.SSR === true;
23
- } catch {
24
- return false;
25
- }
26
- })();
27
+ const IS_SSR: boolean = isServer;
27
28
 
28
29
  /**
29
30
  * Resolves an image field value to a full URL.
@@ -85,8 +86,8 @@ export function FrappeImageView<TDoc extends { name: string; doctype: string }>(
85
86
  // when FrappeProvider already carries it.
86
87
  const providerConfig = useFrappeConfig();
87
88
  // Note: `typeof window` is unreliable because react-native-web polyfills
88
- // `window` during SSR. Use the module-level Vite SSR flag instead
89
- // (bare `import.meta.env.SSR` crashes native — see IS_SSR above).
89
+ // `window` during SSR. Use the module-level platform flag instead
90
+ // (see IS_SSR above).
90
91
  if (IS_SSR) {
91
92
  const {
92
93
  doctype,
@@ -85,8 +85,7 @@ function jsonResponse(body: unknown, status = 200): Response {
85
85
  return jsonResponse({ docs: [todoMetaRaw(doctype)] });
86
86
  }
87
87
  const metaMatch = url.pathname.match(/^\/api\/resource\/DocType\/(.+)$/);
88
- if (metaMatch)
89
- return jsonResponse({ data: todoMetaRaw(decodeURIComponent(metaMatch[1])) });
88
+ if (metaMatch) return jsonResponse({ data: todoMetaRaw(decodeURIComponent(metaMatch[1])) });
90
89
  if (url.pathname === "/api/resource/DocType") {
91
90
  return jsonResponse({ data: [{ name: "ToDo", module: "Desk" }] });
92
91
  }
@@ -270,10 +269,7 @@ export const Loading = () => (
270
269
 
271
270
  /** Zero documents, no failure — "No ToDo records found" (empty ≠ error). */
272
271
  export const Empty = () => (
273
- <FrappeProvider
274
- baseURL={MOCK_HOST}
275
- fixtures={new InMemoryFixtureProvider({ ToDo: [] })}
276
- >
272
+ <FrappeProvider baseURL={MOCK_HOST} fixtures={new InMemoryFixtureProvider({ ToDo: [] })}>
277
273
  <FrappeList<ToDo> doctype="ToDo" renderItem={renderTodo} height={400} />
278
274
  </FrappeProvider>
279
275
  );
@@ -286,10 +282,7 @@ export const Empty = () => (
286
282
  * AsyncBoundary error panel.
287
283
  */
288
284
  export const LoadError = () => (
289
- <FrappeProvider
290
- baseURL={MOCK_HOST}
291
- fixtures={new InMemoryFixtureProvider({ ToDo: [] })}
292
- >
285
+ <FrappeProvider baseURL={MOCK_HOST} fixtures={new InMemoryFixtureProvider({ ToDo: [] })}>
293
286
  <FrappeList<ToDo>
294
287
  doctype="ToDo"
295
288
  renderItem={renderTodo}
@@ -0,0 +1,47 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { isServer } from "@multiplatform.one/platform";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ /**
7
+ * MPO-204 / MPO-192. `import.meta` is a PARSE-time SyntaxError in a classic
8
+ * script, and two bundles execute mpo as one: the vxrn/rolldown native bundle
9
+ * for Hermes, and any Metro bundle for platform=web — which is what an Expo
10
+ * DOM component (`'use dom'`) is. A try/catch never runs, because the file
11
+ * never parses, so the token has to be ABSENT rather than guarded. Both files
12
+ * below are reachable from a DOM component through the frappe-ui surface.
13
+ */
14
+ const src = (name: string) =>
15
+ readFileSync(resolve(process.cwd(), "src", name), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
16
+
17
+ describe("frappeStoryTarget carries no import.meta (MPO-204)", () => {
18
+ const source = src("frappeStoryTarget.tsx");
19
+
20
+ it("contains no import.meta token", () => {
21
+ expect(source).not.toMatch(/import\.meta/);
22
+ });
23
+
24
+ it("reads both Storybook keys as static process.env member expressions, the only spelling bundlers inline", () => {
25
+ expect(source).toMatch(/process\.env\.STORYBOOK_FRAPPE_LIVE\b/);
26
+ expect(source).toMatch(/process\.env\.STORYBOOK_FRAPPE_URL\b/);
27
+ // A computed read is never inlined, so it would always resolve undefined.
28
+ expect(source).not.toMatch(/process\.env\[/);
29
+ });
30
+ });
31
+
32
+ describe("FrappeImageView carries no import.meta (MPO-204)", () => {
33
+ const source = src("FrappeImageView.tsx");
34
+
35
+ it("contains no import.meta token", () => {
36
+ expect(source).not.toMatch(/import\.meta/);
37
+ });
38
+
39
+ it("takes its SSR answer from the platform package, which is split by FILE and not by a bundler define", () => {
40
+ expect(source).toMatch(/import \{ isServer \} from "@multiplatform\.one\/platform";/);
41
+ expect(source).toMatch(/const IS_SSR: boolean = isServer;/);
42
+ });
43
+
44
+ it("resolves isServer false in a browser-like environment, so the client branch renders", () => {
45
+ expect(isServer).toBe(false);
46
+ });
47
+ });
@@ -91,6 +91,13 @@ function jsonResponse(body: unknown, status = 200): Response {
91
91
  };
92
92
  })();
93
93
 
94
+ // 1x1 PNG so the image thumbnail does not GET /files/diagram.png off the
95
+ // Storybook origin (that 404 was the recorded fuzz FAIL). file_type is the
96
+ // MIME the thumbnail recognises; file_name keeps the .png extension for the
97
+ // type well. Same byte string as FrappeFileUpload.stories.
98
+ const TINY_PNG =
99
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
100
+
94
101
  const fileFixtures: FrappeFile[] = [
95
102
  {
96
103
  name: "FILE-001",
@@ -107,9 +114,9 @@ const fileFixtures: FrappeFile[] = [
107
114
  name: "FILE-002",
108
115
  doctype: "File",
109
116
  file_name: "diagram.png",
110
- file_url: "/files/diagram.png",
117
+ file_url: TINY_PNG,
111
118
  file_size: 51200,
112
- file_type: "PNG",
119
+ file_type: "image/png",
113
120
  attached_to_doctype: "Task",
114
121
  attached_to_name: "TASK-001",
115
122
  attached_to_field: "design_attachment",
package/src/dependsOn.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Safe evaluator for Frappe `depends_on` / `mandatory_depends_on` /
3
3
  * `read_only_depends_on` expressions — the ONE parser for every surface
4
- * (SchemaForm here, the backoffice form engine re-exports it from
5
- * `features/backoffice/form/dependsOn.ts`).
4
+ * (SchemaForm here, and the desk form engine next door in ./formEngine;
5
+ * `@multiplatform.one/backoffice` re-exports it from its old path).
6
6
  *
7
7
  * Frappe's desk evaluates these with JS `eval`; Hermes (React Native), GJS,
8
8
  * and CSP-restricted webviews cannot construct functions from strings, so
@@ -20,6 +20,24 @@ export default {
20
20
 
21
21
  const MOCK_HOST = "https://deskformsidebar.mock.test";
22
22
 
23
+ /**
24
+ * The document image is an inline data: URI, not "/files/todo.png".
25
+ * `resolveFileUrl` prefixes a "/..." value with baseURL, so a path fixture
26
+ * makes the <img> fetch https://deskformsidebar.mock.test/files/todo.png for
27
+ * real — the story's window.fetch shim only intercepts fetch(), never an
28
+ * image load — and the story prints ERR_TUNNEL_CONNECTION_FAILED. A data: URI
29
+ * goes through resolveFileUrl untouched and paints without a request, which
30
+ * is what G-15 story honesty asks for (MPO-197).
31
+ */
32
+ const storyImage =
33
+ "data:image/svg+xml;utf8," +
34
+ encodeURIComponent(
35
+ '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" viewBox="0 0 24 24">' +
36
+ '<rect width="24" height="24" rx="3" fill="#4c6ef5"/>' +
37
+ '<path d="M6 12.5l3.5 3.5L18 8" fill="none" stroke="#fff" stroke-width="2" ' +
38
+ 'stroke-linecap="round" stroke-linejoin="round"/></svg>',
39
+ );
40
+
23
41
  function jsonResponse(body: unknown, status = 200): Response {
24
42
  return new Response(JSON.stringify(body), {
25
43
  status,
@@ -70,7 +88,7 @@ const todoDocs = [
70
88
  owner: "jane@example.com",
71
89
  modified_by: "john@example.com",
72
90
  creation: "2026-02-01",
73
- image: "/files/todo.png",
91
+ image: storyImage,
74
92
  _assign: JSON.stringify(["jane@example.com"]),
75
93
  },
76
94
  ];
@@ -4,6 +4,9 @@
4
4
  * table-cell chromeless branch, and registry resolution for the
5
5
  * "Code" and "JSON" fieldtypes.
6
6
  */
7
+ import { readFileSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
7
10
  import { TableCellContext } from "@multiplatform.one/forms";
8
11
  import { renderWithProviders } from "@multiplatform.one/test-utils";
9
12
  import { act, cleanup, fireEvent } from "@testing-library/react";
@@ -12,6 +15,24 @@ import { afterEach, describe, expect, it, vi } from "vitest";
12
15
  import { getFieldComponent, type FieldRenderProps } from "../../fieldRegistry";
13
16
  import { CodeEditorField, frappeCodeLanguage } from ".";
14
17
 
18
+ function codeEditorDir() {
19
+ try {
20
+ const meta = import.meta.url;
21
+ if (meta.startsWith("file:")) return dirname(fileURLToPath(meta));
22
+ } catch {
23
+ /* vite-node may serve a non-file import.meta.url */
24
+ }
25
+ return join(process.cwd(), "src/fields/CodeEditor");
26
+ }
27
+
28
+ function readCodeEditorSource() {
29
+ return readFileSync(join(codeEditorDir(), "index.tsx"), "utf8");
30
+ }
31
+
32
+ function readCodeEditorStories() {
33
+ return readFileSync(join(codeEditorDir(), "CodeEditor.stories.tsx"), "utf8");
34
+ }
35
+
15
36
  afterEach(cleanup);
16
37
 
17
38
  describe("frappeCodeLanguage (stock ControlCode language_map)", () => {
@@ -80,6 +101,22 @@ describe("CodeEditorField (standalone)", () => {
80
101
  );
81
102
  expect(result.getTextArea()).toBeNull();
82
103
  });
104
+
105
+ it("ships CSF3 story objects so native gallery keeps args (KS blank-pane)", () => {
106
+ const stories = readCodeEditorStories();
107
+ expect(stories).toMatch(/export const Main = \{/);
108
+ expect(stories).not.toMatch(/Main\.args\s*=/);
109
+ expect(stories).toContain("value: PYTHON_FIXTURE");
110
+ });
111
+
112
+ it("forwards knob containerRadius onto the source textarea (F14)", () => {
113
+ const source = readCodeEditorSource();
114
+ expect(source).toContain("...knobProps.containerRadius");
115
+ const result = renderWithProviders(
116
+ <CodeEditorField label="Script" value="print(1)" language="python" onChange={() => {}} />,
117
+ );
118
+ expect(result.getTextArea()).toBeTruthy();
119
+ });
83
120
  });
84
121
 
85
122
  describe("registry resolution (Code / JSON fieldtypes)", () => {
@@ -30,11 +30,11 @@ export default {
30
30
  title: "FrappeUI/CodeEditorField",
31
31
  component: CodeEditorField,
32
32
  parameters: {
33
- status: { type: "beta" },
33
+ status: { type: "stable" },
34
34
  docs: {
35
35
  description: {
36
36
  component:
37
- "Frappe `Code` / `JSON` fieldtypes: monospace source pane + shiki-highlighted preview (unified components Highlighter). Language follows the stock df.options convention via `frappeCodeLanguage()`.",
37
+ "Frappe `Code` / `JSON` fieldtypes: monospace source pane + shiki-highlighted preview (unified components Highlighter). Language follows the stock df.options convention via `frappeCodeLanguage()`. Source textarea honours knob `containerRadius` (F14).",
38
38
  },
39
39
  },
40
40
  },
@@ -74,76 +74,93 @@ function Controlled(props: Omit<StoryProps, "onChange">) {
74
74
  );
75
75
  }
76
76
 
77
- export const Main = (args: StoryProps) => <Controlled {...args} />;
78
- Main.args = {
79
- label: "Server script",
80
- helperText: 'Language from df.options ("Python")',
81
- value: PYTHON_FIXTURE,
82
- language: frappeCodeLanguage("Python"),
83
- } as Partial<StoryProps>;
84
- Main.storyName = "Main";
85
-
86
- export const Json = (args: StoryProps) => <Controlled {...args} />;
87
- Json.args = {
88
- label: "Filters (JSON fieldtype)",
89
- value: JSON_FIXTURE,
90
- language: "json",
91
- } as Partial<StoryProps>;
77
+ // CSF3 objects (not `export const Main = (args) => …; Main.args = …`).
78
+ // Native Kitchen Sink `storyIndex.ts` treats a function export as
79
+ // `{ render: fn }` and drops `fn.args`, so CSF2 Main/Json/ReadOnly
80
+ // painted an empty Code pane on OTA (value/readOnly never arrived).
81
+ export const Main = {
82
+ name: "Main",
83
+ args: {
84
+ label: "Server script",
85
+ helperText: 'Language from df.options ("Python")',
86
+ value: PYTHON_FIXTURE,
87
+ language: frappeCodeLanguage("Python"),
88
+ } as Partial<StoryProps>,
89
+ render: (args: StoryProps) => <Controlled {...args} />,
90
+ };
92
91
 
93
- export const FormIntegrated = () => {
94
- const form = useForm({
95
- defaultValues: { script: PYTHON_FIXTURE },
96
- onSubmit: async ({ value }) => action("onSubmit")(value),
97
- });
98
- return (
99
- <Form form={form}>
100
- <YStack gap="$4">
101
- <CodeEditorField
102
- form={form as unknown as AnyFormApi}
103
- name="script"
104
- label="Script (form-integrated)"
105
- language="python"
106
- required
107
- helperText="Bound to TanStack Form field state"
108
- />
109
- </YStack>
110
- </Form>
111
- );
92
+ export const Json = {
93
+ args: {
94
+ label: "Filters (JSON fieldtype)",
95
+ value: JSON_FIXTURE,
96
+ language: "json",
97
+ } as Partial<StoryProps>,
98
+ render: (args: StoryProps) => <Controlled {...args} />,
112
99
  };
113
100
 
114
- export const ReadOnly = (args: StoryProps) => <Controlled {...args} />;
115
- ReadOnly.args = {
116
- label: "Read-only script",
117
- value: PYTHON_FIXTURE,
118
- language: "python",
119
- readOnly: true,
120
- } as Partial<StoryProps>;
101
+ export const FormIntegrated = {
102
+ name: "Form Integrated",
103
+ render: function FormIntegratedRender() {
104
+ const form = useForm({
105
+ defaultValues: { script: PYTHON_FIXTURE },
106
+ onSubmit: async ({ value }) => action("onSubmit")(value),
107
+ });
108
+ return (
109
+ <Form form={form}>
110
+ <YStack gap="$4">
111
+ <CodeEditorField
112
+ form={form as unknown as AnyFormApi}
113
+ name="script"
114
+ label="Script (form-integrated)"
115
+ language="python"
116
+ required
117
+ helperText="Bound to TanStack Form field state"
118
+ />
119
+ </YStack>
120
+ </Form>
121
+ );
122
+ },
123
+ };
121
124
 
122
- export const States = (args: StoryProps) => (
123
- <YStack gap="$5">
124
- <Controlled {...args} label="Error" error="Script fails to parse at line 3" />
125
- <Controlled {...args} label="Disabled" disabled />
126
- <Controlled {...args} label="Compact" compact />
127
- <CodeEditorField label="Skeleton" skeleton onChange={() => {}} />
128
- </YStack>
129
- );
130
- States.args = {
131
- value: 'const answer = 42;\nconsole.log("state fixtures");',
132
- language: "javascript",
133
- } as Partial<StoryProps>;
125
+ export const ReadOnly = {
126
+ args: {
127
+ label: "Read-only script",
128
+ value: PYTHON_FIXTURE,
129
+ language: "python",
130
+ readOnly: true,
131
+ } as Partial<StoryProps>,
132
+ render: (args: StoryProps) => <Controlled {...args} />,
133
+ };
134
134
 
135
- export const DarkScheme = (args: StoryProps) => (
136
- <Theme name="dark">
137
- <YStack backgroundColor="$background" padding="$4">
138
- <Controlled {...args} />
135
+ export const States = {
136
+ args: {
137
+ value: 'const answer = 42;\nconsole.log("state fixtures");',
138
+ language: "javascript",
139
+ } as Partial<StoryProps>,
140
+ render: (args: StoryProps) => (
141
+ <YStack gap="$5">
142
+ <Controlled {...args} label="Error" error="Script fails to parse at line 3" />
143
+ <Controlled {...args} label="Disabled" disabled />
144
+ <Controlled {...args} label="Compact" compact />
145
+ <CodeEditorField label="Skeleton" skeleton onChange={() => {}} />
139
146
  </YStack>
140
- </Theme>
141
- );
142
- DarkScheme.args = {
143
- label: "Server script (dark)",
144
- value: PYTHON_FIXTURE,
145
- language: "python",
146
- } as Partial<StoryProps>;
147
+ ),
148
+ };
149
+
150
+ export const DarkScheme = {
151
+ args: {
152
+ label: "Server script (dark)",
153
+ value: PYTHON_FIXTURE,
154
+ language: "python",
155
+ } as Partial<StoryProps>,
156
+ render: (args: StoryProps) => (
157
+ <Theme name="dark">
158
+ <YStack backgroundColor="$background" padding="$4">
159
+ <Controlled {...args} />
160
+ </YStack>
161
+ </Theme>
162
+ ),
163
+ };
147
164
 
148
165
  type CodeRow = { id: string; label: string; value: string };
149
166
 
@@ -213,6 +213,10 @@ export function CodeEditorField({
213
213
  maxRows={maxRows}
214
214
  textAreaProps={{
215
215
  fontFamily: "$mono",
216
+ // F14: TextArea's inner <textarea> takes radius from `size` unless
217
+ // containerRadius lands after the size recipe. FieldLayout already
218
+ // gets knobProps; the measured source surface must too.
219
+ ...knobProps.containerRadius,
216
220
  autoCapitalize: "none",
217
221
  autoCorrect: false,
218
222
  spellCheck: false,