@multiplatform.one/frappe-ui 7.6.3 → 7.7.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 (229) 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/FrappeChartCard.cjs +3 -3
  5. package/dist/cjs/FrappeChartCard.native.js +3 -3
  6. package/dist/cjs/FrappeChartCard.native.js.map +1 -1
  7. package/dist/cjs/FrappeDashboard.cjs +6 -27
  8. package/dist/cjs/FrappeDashboard.native.js +6 -33
  9. package/dist/cjs/FrappeDashboard.native.js.map +1 -1
  10. package/dist/cjs/fields/CodeEditor/index.cjs +4 -0
  11. package/dist/cjs/fields/CodeEditor/index.native.js +4 -0
  12. package/dist/cjs/fields/CodeEditor/index.native.js.map +1 -1
  13. package/dist/cjs/formEngine/createPrefill.cjs +37 -0
  14. package/dist/cjs/formEngine/createPrefill.native.js +40 -0
  15. package/dist/cjs/formEngine/createPrefill.native.js.map +1 -0
  16. package/dist/cjs/formEngine/dirtyTracking.cjs +55 -0
  17. package/dist/cjs/formEngine/dirtyTracking.native.js +77 -0
  18. package/dist/cjs/formEngine/dirtyTracking.native.js.map +1 -0
  19. package/dist/cjs/formEngine/doctypeMeta.cjs +18 -0
  20. package/dist/cjs/formEngine/doctypeMeta.native.js +21 -0
  21. package/dist/cjs/formEngine/doctypeMeta.native.js.map +1 -0
  22. package/dist/cjs/formEngine/fetchFrom.cjs +67 -0
  23. package/dist/cjs/formEngine/fetchFrom.native.js +110 -0
  24. package/dist/cjs/formEngine/fetchFrom.native.js.map +1 -0
  25. package/dist/cjs/formEngine/fieldPermissions.cjs +55 -0
  26. package/dist/cjs/formEngine/fieldPermissions.native.js +80 -0
  27. package/dist/cjs/formEngine/fieldPermissions.native.js.map +1 -0
  28. package/dist/cjs/formEngine/formActions.cjs +189 -0
  29. package/dist/cjs/formEngine/formActions.native.js +199 -0
  30. package/dist/cjs/formEngine/formActions.native.js.map +1 -0
  31. package/dist/cjs/formEngine/formLayout.cjs +245 -0
  32. package/dist/cjs/formEngine/formLayout.native.js +388 -0
  33. package/dist/cjs/formEngine/formLayout.native.js.map +1 -0
  34. package/dist/cjs/formEngine/index.cjs +48 -0
  35. package/dist/cjs/formEngine/index.native.js +51 -0
  36. package/dist/cjs/formEngine/index.native.js.map +1 -0
  37. package/dist/cjs/formEngine/useFormEngine.cjs +387 -0
  38. package/dist/cjs/formEngine/useFormEngine.native.js +476 -0
  39. package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -0
  40. package/dist/cjs/index.cjs +19 -1
  41. package/dist/cjs/index.native.js +19 -1
  42. package/dist/cjs/index.native.js.map +1 -1
  43. package/dist/esm/ChartCard.mjs +27 -0
  44. package/dist/esm/ChartCard.mjs.map +1 -0
  45. package/dist/esm/ChartCard.native.js +33 -0
  46. package/dist/esm/ChartCard.native.js.map +1 -0
  47. package/dist/esm/FrappeChartCard.mjs +1 -1
  48. package/dist/esm/FrappeChartCard.native.js +1 -1
  49. package/dist/esm/FrappeChartCard.native.js.map +1 -1
  50. package/dist/esm/FrappeDashboard.mjs +6 -27
  51. package/dist/esm/FrappeDashboard.mjs.map +1 -1
  52. package/dist/esm/FrappeDashboard.native.js +6 -33
  53. package/dist/esm/FrappeDashboard.native.js.map +1 -1
  54. package/dist/esm/fields/CodeEditor/index.mjs +4 -0
  55. package/dist/esm/fields/CodeEditor/index.mjs.map +1 -1
  56. package/dist/esm/fields/CodeEditor/index.native.js +4 -0
  57. package/dist/esm/fields/CodeEditor/index.native.js.map +1 -1
  58. package/dist/esm/formEngine/createPrefill.mjs +11 -0
  59. package/dist/esm/formEngine/createPrefill.mjs.map +1 -0
  60. package/dist/esm/formEngine/createPrefill.native.js +11 -0
  61. package/dist/esm/formEngine/createPrefill.native.js.map +1 -0
  62. package/dist/esm/formEngine/dirtyTracking.mjs +28 -0
  63. package/dist/esm/formEngine/dirtyTracking.mjs.map +1 -0
  64. package/dist/esm/formEngine/dirtyTracking.native.js +47 -0
  65. package/dist/esm/formEngine/dirtyTracking.native.js.map +1 -0
  66. package/dist/esm/formEngine/doctypeMeta.mjs +2 -0
  67. package/dist/esm/formEngine/doctypeMeta.mjs.map +1 -0
  68. package/dist/esm/formEngine/doctypeMeta.native.js +2 -0
  69. package/dist/esm/formEngine/doctypeMeta.native.js.map +1 -0
  70. package/dist/esm/formEngine/fetchFrom.mjs +40 -0
  71. package/dist/esm/formEngine/fetchFrom.mjs.map +1 -0
  72. package/dist/esm/formEngine/fetchFrom.native.js +80 -0
  73. package/dist/esm/formEngine/fetchFrom.native.js.map +1 -0
  74. package/dist/esm/formEngine/fieldPermissions.mjs +28 -0
  75. package/dist/esm/formEngine/fieldPermissions.mjs.map +1 -0
  76. package/dist/esm/formEngine/fieldPermissions.native.js +50 -0
  77. package/dist/esm/formEngine/fieldPermissions.native.js.map +1 -0
  78. package/dist/esm/formEngine/formActions.mjs +146 -0
  79. package/dist/esm/formEngine/formActions.mjs.map +1 -0
  80. package/dist/esm/formEngine/formActions.native.js +153 -0
  81. package/dist/esm/formEngine/formActions.native.js.map +1 -0
  82. package/dist/esm/formEngine/formLayout.mjs +217 -0
  83. package/dist/esm/formEngine/formLayout.mjs.map +1 -0
  84. package/dist/esm/formEngine/formLayout.native.js +357 -0
  85. package/dist/esm/formEngine/formLayout.native.js.map +1 -0
  86. package/dist/esm/formEngine/index.mjs +8 -0
  87. package/dist/esm/formEngine/index.mjs.map +1 -0
  88. package/dist/esm/formEngine/index.native.js +8 -0
  89. package/dist/esm/formEngine/index.native.js.map +1 -0
  90. package/dist/esm/formEngine/useFormEngine.mjs +362 -0
  91. package/dist/esm/formEngine/useFormEngine.mjs.map +1 -0
  92. package/dist/esm/formEngine/useFormEngine.native.js +448 -0
  93. package/dist/esm/formEngine/useFormEngine.native.js.map +1 -0
  94. package/dist/esm/index.mjs +4 -2
  95. package/dist/esm/index.mjs.map +1 -1
  96. package/dist/esm/index.native.js +4 -2
  97. package/dist/esm/index.native.js.map +1 -1
  98. package/dist/jsx/ChartCard.mjs +27 -0
  99. package/dist/jsx/ChartCard.mjs.map +1 -0
  100. package/dist/jsx/ChartCard.native.js +72 -0
  101. package/dist/jsx/ChartCard.native.js.map +1 -0
  102. package/dist/jsx/FrappeChartCard.mjs +1 -1
  103. package/dist/jsx/FrappeChartCard.native.js +3 -3
  104. package/dist/jsx/FrappeChartCard.native.js.map +1 -1
  105. package/dist/jsx/FrappeDashboard.mjs +6 -27
  106. package/dist/jsx/FrappeDashboard.mjs.map +1 -1
  107. package/dist/jsx/FrappeDashboard.native.js +6 -33
  108. package/dist/jsx/FrappeDashboard.native.js.map +1 -1
  109. package/dist/jsx/fields/CodeEditor/index.mjs +4 -0
  110. package/dist/jsx/fields/CodeEditor/index.mjs.map +1 -1
  111. package/dist/jsx/fields/CodeEditor/index.native.js +4 -0
  112. package/dist/jsx/fields/CodeEditor/index.native.js.map +1 -1
  113. package/dist/jsx/formEngine/createPrefill.mjs +11 -0
  114. package/dist/jsx/formEngine/createPrefill.mjs.map +1 -0
  115. package/dist/jsx/formEngine/createPrefill.native.js +40 -0
  116. package/dist/jsx/formEngine/createPrefill.native.js.map +1 -0
  117. package/dist/jsx/formEngine/dirtyTracking.mjs +28 -0
  118. package/dist/jsx/formEngine/dirtyTracking.mjs.map +1 -0
  119. package/dist/jsx/formEngine/dirtyTracking.native.js +77 -0
  120. package/dist/jsx/formEngine/dirtyTracking.native.js.map +1 -0
  121. package/dist/jsx/formEngine/doctypeMeta.mjs +2 -0
  122. package/dist/jsx/formEngine/doctypeMeta.mjs.map +1 -0
  123. package/dist/jsx/formEngine/doctypeMeta.native.js +21 -0
  124. package/dist/jsx/formEngine/doctypeMeta.native.js.map +1 -0
  125. package/dist/jsx/formEngine/fetchFrom.mjs +40 -0
  126. package/dist/jsx/formEngine/fetchFrom.mjs.map +1 -0
  127. package/dist/jsx/formEngine/fetchFrom.native.js +110 -0
  128. package/dist/jsx/formEngine/fetchFrom.native.js.map +1 -0
  129. package/dist/jsx/formEngine/fieldPermissions.mjs +28 -0
  130. package/dist/jsx/formEngine/fieldPermissions.mjs.map +1 -0
  131. package/dist/jsx/formEngine/fieldPermissions.native.js +80 -0
  132. package/dist/jsx/formEngine/fieldPermissions.native.js.map +1 -0
  133. package/dist/jsx/formEngine/formActions.mjs +146 -0
  134. package/dist/jsx/formEngine/formActions.mjs.map +1 -0
  135. package/dist/jsx/formEngine/formActions.native.js +199 -0
  136. package/dist/jsx/formEngine/formActions.native.js.map +1 -0
  137. package/dist/jsx/formEngine/formLayout.mjs +217 -0
  138. package/dist/jsx/formEngine/formLayout.mjs.map +1 -0
  139. package/dist/jsx/formEngine/formLayout.native.js +388 -0
  140. package/dist/jsx/formEngine/formLayout.native.js.map +1 -0
  141. package/dist/jsx/formEngine/index.mjs +8 -0
  142. package/dist/jsx/formEngine/index.mjs.map +1 -0
  143. package/dist/jsx/formEngine/index.native.js +51 -0
  144. package/dist/jsx/formEngine/index.native.js.map +1 -0
  145. package/dist/jsx/formEngine/useFormEngine.mjs +362 -0
  146. package/dist/jsx/formEngine/useFormEngine.mjs.map +1 -0
  147. package/dist/jsx/formEngine/useFormEngine.native.js +476 -0
  148. package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -0
  149. package/dist/jsx/index.js +4 -2
  150. package/dist/jsx/index.js.map +1 -1
  151. package/dist/jsx/index.mjs +4 -2
  152. package/dist/jsx/index.mjs.map +1 -1
  153. package/dist/jsx/index.native.js +19 -1
  154. package/dist/jsx/index.native.js.map +1 -1
  155. package/package.json +23 -17
  156. package/src/ChartCard.tsx +31 -0
  157. package/src/FileBrowser/FileBrowser.spec.tsx +16 -0
  158. package/src/FileBrowser/FileBrowser.stories.tsx +18 -1
  159. package/src/FileBrowser/folderTree.spec.tsx +68 -0
  160. package/src/FrappeCalendar.stories.tsx +1 -2
  161. package/src/FrappeChartCard.stories.tsx +1 -1
  162. package/src/FrappeChartCard.tsx +1 -1
  163. package/src/FrappeDashboard.stories.tsx +6 -5
  164. package/src/FrappeDashboard.tsx +6 -33
  165. package/src/FrappeGantt.stories.tsx +1 -2
  166. package/src/FrappeList.stories.tsx +3 -10
  167. package/src/components/attachments.stories.tsx +9 -2
  168. package/src/dependsOn.ts +2 -2
  169. package/src/fields/CodeEditor/CodeEditor.spec.tsx +37 -0
  170. package/src/fields/CodeEditor/CodeEditor.stories.tsx +83 -66
  171. package/src/fields/CodeEditor/index.tsx +4 -0
  172. package/src/fields/DateRange/DateRange.spec.tsx +143 -0
  173. package/src/fields/FileUpload.spec.tsx +334 -0
  174. package/src/fields/FileUploadField/FileUploadField.spec.tsx +166 -0
  175. package/src/fields/HTML/Html.spec.tsx +179 -0
  176. package/src/fields/Icon/Icon.stories.tsx +138 -0
  177. package/src/fields/Link.stories.tsx +2 -0
  178. package/src/fields/LongText/LongText.spec.tsx +217 -0
  179. package/src/fields/OTPInput/OTPInput.spec.tsx +167 -0
  180. package/src/fields/Password/Password.stories.tsx +21 -0
  181. package/src/fields/Phone/Phone.spec.tsx +151 -0
  182. package/src/fields/ReadOnly/ReadOnly.spec.tsx +229 -0
  183. package/src/fields/SmallText/SmallText.spec.tsx +175 -0
  184. package/src/fields/shared/EditorPreviewPanes.spec.tsx +122 -0
  185. package/src/formEngine/createPrefill.ts +28 -0
  186. package/src/formEngine/dirtyTracking.spec.ts +91 -0
  187. package/src/formEngine/dirtyTracking.ts +64 -0
  188. package/src/formEngine/doctypeMeta.ts +117 -0
  189. package/src/formEngine/fetchFrom.spec.ts +92 -0
  190. package/src/formEngine/fetchFrom.ts +111 -0
  191. package/src/formEngine/fieldPermissions.spec.ts +61 -0
  192. package/src/formEngine/fieldPermissions.ts +68 -0
  193. package/src/formEngine/formActions.ts +285 -0
  194. package/src/formEngine/formLayout.spec.ts +373 -0
  195. package/src/formEngine/formLayout.ts +392 -0
  196. package/src/formEngine/index.ts +33 -0
  197. package/src/formEngine/seam.spec.ts +148 -0
  198. package/src/formEngine/useFormEngine.ts +496 -0
  199. package/src/frappeRemainder.designLaw.spec.ts +9 -0
  200. package/src/frappeViews.designLaw.spec.ts +1 -0
  201. package/src/index.spec.ts +7 -3
  202. package/src/index.ts +51 -1
  203. package/src/schemaForm/SchemaForm.spec.tsx +4 -1
  204. package/types/ChartCard.d.ts +13 -0
  205. package/types/ChartCard.d.ts.map +1 -0
  206. package/types/FrappeDashboard.d.ts +2 -10
  207. package/types/FrappeDashboard.d.ts.map +1 -1
  208. package/types/dependsOn.d.ts +2 -2
  209. package/types/fields/CodeEditor/index.d.ts.map +1 -1
  210. package/types/formEngine/createPrefill.d.ts +19 -0
  211. package/types/formEngine/createPrefill.d.ts.map +1 -0
  212. package/types/formEngine/dirtyTracking.d.ts +36 -0
  213. package/types/formEngine/dirtyTracking.d.ts.map +1 -0
  214. package/types/formEngine/doctypeMeta.d.ts +117 -0
  215. package/types/formEngine/doctypeMeta.d.ts.map +1 -0
  216. package/types/formEngine/fetchFrom.d.ts +43 -0
  217. package/types/formEngine/fetchFrom.d.ts.map +1 -0
  218. package/types/formEngine/fieldPermissions.d.ts +38 -0
  219. package/types/formEngine/fieldPermissions.d.ts.map +1 -0
  220. package/types/formEngine/formActions.d.ts +95 -0
  221. package/types/formEngine/formActions.d.ts.map +1 -0
  222. package/types/formEngine/formLayout.d.ts +91 -0
  223. package/types/formEngine/formLayout.d.ts.map +1 -0
  224. package/types/formEngine/index.d.ts +27 -0
  225. package/types/formEngine/index.d.ts.map +1 -0
  226. package/types/formEngine/useFormEngine.d.ts +61 -0
  227. package/types/formEngine/useFormEngine.d.ts.map +1 -0
  228. package/types/index.d.ts +4 -1
  229. package/types/index.d.ts.map +1 -1
@@ -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,
@@ -0,0 +1,143 @@
1
+ /**
2
+ * DateRangeField contract specs. The `Date Range` fieldtype composes the forms
3
+ * DateRangePicker, so the adapter's contract is which props survive the hop.
4
+ *
5
+ * One of them does not, and that is pinned here deliberately: `value` and
6
+ * `onChange` are DECLARED on DateRangeFieldProps and then destructured away
7
+ * (`value: _value`, `onChange: _onChange`) instead of being handed to
8
+ * DateRangePicker. The registry feeds `props.value` straight into the adapter,
9
+ * so a saved Date Range never paints and a picked range never emits. These
10
+ * tests describe today's behaviour and will go red the moment someone wires it
11
+ * up — which is the point: that is the signal to update them alongside the fix.
12
+ * Reported on MPO-123.
13
+ */
14
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
15
+ import { cleanup } from "@testing-library/react";
16
+ import type React from "react";
17
+ import { afterEach, describe, expect, it } from "vitest";
18
+ import { getFieldComponent, type FieldRenderProps } from "../../fieldRegistry";
19
+ import { DateRangeField } from ".";
20
+
21
+ afterEach(cleanup);
22
+
23
+ const trigger = (container: HTMLElement) =>
24
+ container.querySelector("input") as HTMLInputElement | null;
25
+
26
+ /** The accessible control: role="combobox", not the decorative inner input. */
27
+ const combobox = (container: HTMLElement) =>
28
+ container.querySelector('[data-testid="date-picker-trigger"]');
29
+
30
+ describe("DateRangeField composes the forms DateRangePicker", () => {
31
+ it("renders a single range trigger, not two date inputs", () => {
32
+ const result = renderWithProviders(<DateRangeField label="Period" />);
33
+ expect(result.container.querySelectorAll("input")).toHaveLength(1);
34
+ });
35
+
36
+ it("carries the picker's own placeholder when the field sets none", () => {
37
+ const result = renderWithProviders(<DateRangeField label="Period" />);
38
+ expect(trigger(result.container)?.getAttribute("placeholder")).toContain("date");
39
+ });
40
+
41
+ it("forwards an explicit placeholder", () => {
42
+ const result = renderWithProviders(<DateRangeField label="Period" placeholder="From – To" />);
43
+ expect(trigger(result.container)?.getAttribute("placeholder")).toBe("From – To");
44
+ });
45
+
46
+ it("forwards readOnly, which swaps the popover trigger for a flat read-only box", () => {
47
+ const result = renderWithProviders(<DateRangeField label="Period" readOnly />);
48
+ expect(trigger(result.container)?.hasAttribute("readonly")).toBe(true);
49
+ });
50
+
51
+ it("forwards required to the control, not just to the label", () => {
52
+ // The accessible control is the combobox box, not the inner <input>: that
53
+ // input is readOnly / tabIndex -1 / pointerEvents none, so the aria state
54
+ // rides the role="combobox" wrapper. Asserting it on the input passes
55
+ // vacuously against a picker that sets neither.
56
+ const result = renderWithProviders(<DateRangeField label="Period" required />);
57
+ expect(combobox(result.container)?.getAttribute("aria-required")).toBe("true");
58
+ expect(combobox(result.container)?.getAttribute("role")).toBe("combobox");
59
+ });
60
+
61
+ it("forwards error, which marks the control invalid", () => {
62
+ const result = renderWithProviders(<DateRangeField label="Period" error="pick a range" />);
63
+ expect(combobox(result.container)?.getAttribute("aria-invalid")).toBe("true");
64
+ expect(result.container.textContent).toContain("pick a range");
65
+ });
66
+
67
+ it("leaves both aria states off a field that is neither required nor errored", () => {
68
+ const result = renderWithProviders(<DateRangeField label="Period" />);
69
+ expect(combobox(result.container)?.getAttribute("aria-required")).toBeNull();
70
+ expect(combobox(result.container)?.getAttribute("aria-invalid")).toBeNull();
71
+ });
72
+
73
+ it("forwards the label and helperText", () => {
74
+ const result = renderWithProviders(
75
+ <DateRangeField label="Period" helperText="inclusive of both ends" />,
76
+ );
77
+ expect(result.container.textContent).toContain("Period");
78
+ expect(result.container.textContent).toContain("inclusive of both ends");
79
+ });
80
+
81
+ it("skeleton branch renders no trigger", () => {
82
+ const result = renderWithProviders(<DateRangeField label="Period" skeleton />);
83
+ expect(trigger(result.container)).toBeNull();
84
+ });
85
+ });
86
+
87
+ describe("DateRangeField value gap (MPO-123)", () => {
88
+ it("declares value on its props type", () => {
89
+ // Type-level promise: the adapter advertises a controlled value.
90
+ const props: React.ComponentProps<typeof DateRangeField> = { value: "2026-01-01" };
91
+ expect(props.value).toBe("2026-01-01");
92
+ });
93
+
94
+ it("but a controlled value does not reach the control — the trigger stays empty", () => {
95
+ const result = renderWithProviders(
96
+ <DateRangeField label="Period" value="2026-01-01,2026-01-31" />,
97
+ );
98
+ expect(trigger(result.container)?.value).toBe("");
99
+ });
100
+
101
+ it("and the registry hands that value straight to the adapter, so the drop is on this file", () => {
102
+ const Component = getFieldComponent("Date Range")!;
103
+ const element = (Component as Function)({
104
+ value: "2026-01-01,2026-01-31",
105
+ onChange: () => {},
106
+ } as FieldRenderProps) as React.ReactElement<Record<string, any>>;
107
+ expect(element.type).toBe(DateRangeField);
108
+ expect(element.props.value).toBe("2026-01-01,2026-01-31");
109
+ });
110
+ });
111
+
112
+ describe("registry resolution (Date Range fieldtype)", () => {
113
+ function propsFor(overrides: Partial<FieldRenderProps> = {}): FieldRenderProps {
114
+ return { value: "", onChange: () => {}, ...overrides };
115
+ }
116
+
117
+ it("resolves 'Date Range' to DateRangeField, and not to the Date adapter", () => {
118
+ const Component = getFieldComponent("Date Range")!;
119
+ expect(Component).toBeTruthy();
120
+ expect(Component).not.toBe(getFieldComponent("Date"));
121
+ const element = (Component as Function)(propsFor({ label: "Period" })) as React.ReactElement<
122
+ Record<string, any>
123
+ >;
124
+ expect(element.type).toBe(DateRangeField);
125
+ expect(element.props.label).toBe("Period");
126
+ });
127
+
128
+ it("passes helperText / readOnly / skeleton / compact through", () => {
129
+ const Component = getFieldComponent("Date Range")!;
130
+ const element = (Component as Function)(
131
+ propsFor({
132
+ helperText: "both ends inclusive",
133
+ readOnly: true,
134
+ skeleton: true,
135
+ compact: true,
136
+ }),
137
+ ) as React.ReactElement<Record<string, any>>;
138
+ expect(element.props.helperText).toBe("both ends inclusive");
139
+ expect(element.props.readOnly).toBe(true);
140
+ expect(element.props.skeleton).toBe(true);
141
+ expect(element.props.compact).toBe(true);
142
+ });
143
+ });