@multiplatform.one/frappe-ui 7.6.2 → 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
@@ -0,0 +1,496 @@
1
+ /**
2
+ * The desk form state engine.
3
+ *
4
+ * - Edit mode: the doc lives in a name-filtered live collection
5
+ * (useFrappeCollection + useLiveQuery) so realtime updates flow in; saves
6
+ * write only dirty fields through the collection's optimistic update path
7
+ * (live update_doc enforces If-Match OCC — a version conflict surfaces as
8
+ * a save error and flips `conflict` so the UI can offer a reload).
9
+ * - Create mode: seeds meta defaults (+ optional Duplicate/Amend prefill)
10
+ * and inserts via direct REST so the server-issued name is returned for
11
+ * router.replace(formPath(...)).
12
+ * - Dirty tracking against the last server baseline; external updates land
13
+ * on clean fields in place, dirty fields are preserved.
14
+ * - Submit / Cancel lifecycle for is_submittable doctypes via
15
+ * frappe.client.submit / frappe.client.cancel (probed) — realtime then
16
+ * refreshes the live doc; Amend is a create-mode copy with amended_from.
17
+ */
18
+
19
+ import { eq, useFrappeCollection, useLiveQuery } from "@multiplatform.one/frappe";
20
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
21
+ import type { DoctypeMeta } from "./doctypeMeta";
22
+ import type { FrappeConnection } from "./formActions";
23
+ import {
24
+ cancelDoc,
25
+ createHttpClient,
26
+ getLoggedUser,
27
+ insertDoc,
28
+ submitDoc,
29
+ validateLink,
30
+ } from "./formActions";
31
+ import { buildCreateDefaults, isPromptNamed } from "./formLayout";
32
+ import { takeCreatePrefill } from "./createPrefill";
33
+ import { evaluateDependsOn } from "../dependsOn";
34
+ import { computeDirtyFields, isEmptyValue, isSameValue } from "./dirtyTracking";
35
+ import { applyFetchedValues, buildFetchMap } from "./fetchFrom";
36
+
37
+ type DocRecord = Record<string, unknown> & { name: string; doctype: string };
38
+
39
+ export interface FormEngine {
40
+ /** Current (locally edited) doc values. */
41
+ doc: Record<string, unknown>;
42
+ /** Last authoritative server snapshot (undefined in create mode). */
43
+ baseline: Record<string, unknown> | undefined;
44
+ isLoading: boolean;
45
+ /** Edit-mode doc missing after load settles (bad name / no permission). */
46
+ notFound: boolean;
47
+ /** The open doc was deleted on the server while open. */
48
+ wasDeleted: boolean;
49
+ isCreate: boolean;
50
+ isDirty: boolean;
51
+ dirtyFields: string[];
52
+ isSaving: boolean;
53
+ /** Per-field + `general` error messages. */
54
+ errors: Record<string, string>;
55
+ /** True after a save failed with a version conflict (OCC 409). */
56
+ conflict: boolean;
57
+ /** Logged-in user (for comments / likes / assignments). */
58
+ currentUser: string;
59
+ docstatus: number;
60
+ setValue: (field: string, value: unknown) => void;
61
+ /**
62
+ * Save. Create mode resolves with the server-issued doc name;
63
+ * edit mode resolves with the current name. Undefined = validation failed.
64
+ */
65
+ save: () => Promise<string | undefined>;
66
+ /** Submit lifecycle (is_submittable): docstatus 0 → 1. */
67
+ submit: () => Promise<boolean>;
68
+ /** Cancel lifecycle: docstatus 1 → 2. */
69
+ cancel: () => Promise<boolean>;
70
+ /** Delete the doc through the live collection. */
71
+ remove: () => Promise<boolean>;
72
+ /** Discard local edits / clear conflict, back to the server baseline. */
73
+ reload: () => void;
74
+ /** Client-side required validation (fires on save too). */
75
+ validate: () => Record<string, string>;
76
+ }
77
+
78
+ const LAYOUT_TYPES = new Set(["Section Break", "Column Break", "Tab Break", "Fold"]);
79
+
80
+ function isConflictError(err: unknown): boolean {
81
+ const e = err as { status?: number; exc_type?: string; message?: string } | undefined;
82
+ if (!e) return false;
83
+ if (e.status === 409) return true;
84
+ if (e.exc_type === "TimestampMismatchError") return true;
85
+ return /TimestampMismatch|has been modified/i.test(e.message ?? "");
86
+ }
87
+
88
+ function extractServerError(err: unknown, fallback: string): string {
89
+ const e = err as { _server_messages?: string; message?: string; exc_type?: string } | undefined;
90
+ if (e?._server_messages) {
91
+ try {
92
+ const messages = JSON.parse(e._server_messages) as string[];
93
+ const parsed = messages
94
+ .map((msg) => {
95
+ try {
96
+ return (JSON.parse(msg) as { message?: string }).message ?? "";
97
+ } catch {
98
+ return msg;
99
+ }
100
+ })
101
+ .filter(Boolean);
102
+ if (parsed.length > 0) return parsed.join("\n");
103
+ } catch {
104
+ // fall through
105
+ }
106
+ }
107
+ return e?.message || fallback;
108
+ }
109
+
110
+ export function useFormEngine(
111
+ connection: FrappeConnection | undefined,
112
+ doctype: string,
113
+ /** Document name, or undefined for create mode. */
114
+ name: string | undefined,
115
+ meta: DoctypeMeta | undefined,
116
+ translate: (key: string, options?: Record<string, unknown>) => string,
117
+ ): FormEngine {
118
+ const isCreate = !name;
119
+ const [localDoc, setLocalDoc] = useState<Record<string, unknown>>({});
120
+ const [baseline, setBaseline] = useState<Record<string, unknown> | undefined>(undefined);
121
+ const [isSaving, setIsSaving] = useState(false);
122
+ const [errors, setErrors] = useState<Record<string, string>>({});
123
+ const [conflict, setConflict] = useState(false);
124
+ const [wasDeleted, setWasDeleted] = useState(false);
125
+ const [currentUser, setCurrentUser] = useState("");
126
+ const everLoadedRef = useRef(false);
127
+ const seededRef = useRef(false);
128
+ const localDocRef = useRef(localDoc);
129
+ localDocRef.current = localDoc;
130
+ const baselineRef = useRef(baseline);
131
+ baselineRef.current = baseline;
132
+
133
+ // ---------------------------------------------------------------------
134
+ // Live doc subscription (edit mode)
135
+ // ---------------------------------------------------------------------
136
+ const docCollection = useFrappeCollection<DocRecord>(name ? connection : undefined, doctype, {
137
+ filters: name ? { name } : undefined,
138
+ });
139
+ const { data: rawDocData, isLoading: isDocLoading } = useLiveQuery(
140
+ (q) => {
141
+ if (!docCollection || !name) return null;
142
+ return q.from({ _doc: docCollection }).where(({ _doc }) => eq(_doc.name, name));
143
+ },
144
+ [docCollection, name],
145
+ );
146
+ const liveDoc = useMemo(() => {
147
+ const arr = Array.isArray(rawDocData) ? rawDocData : rawDocData ? [rawDocData] : [];
148
+ return (arr[0] ?? null) as DocRecord | null;
149
+ }, [rawDocData]);
150
+
151
+ // ---------------------------------------------------------------------
152
+ // Session user
153
+ // ---------------------------------------------------------------------
154
+ useEffect(() => {
155
+ if (!connection) return;
156
+ let cancelled = false;
157
+ getLoggedUser(createHttpClient(connection))
158
+ .then((user) => {
159
+ if (!cancelled) setCurrentUser(user);
160
+ })
161
+ .catch(() => {
162
+ // Anonymous fallback — comment composer degrades gracefully.
163
+ });
164
+ return () => {
165
+ cancelled = true;
166
+ };
167
+ }, [connection?.baseURL]);
168
+
169
+ // ---------------------------------------------------------------------
170
+ // Reset per-document
171
+ // ---------------------------------------------------------------------
172
+ useEffect(() => {
173
+ everLoadedRef.current = false;
174
+ seededRef.current = false;
175
+ setWasDeleted(false);
176
+ setConflict(false);
177
+ setErrors({});
178
+ setBaseline(undefined);
179
+ setLocalDoc({});
180
+ }, [doctype, name]);
181
+
182
+ // ---------------------------------------------------------------------
183
+ // Create-mode seeding (meta defaults + Duplicate/Amend prefill)
184
+ // ---------------------------------------------------------------------
185
+ useEffect(() => {
186
+ if (!isCreate || !meta || seededRef.current) return;
187
+ seededRef.current = true;
188
+ const defaults = buildCreateDefaults(meta.fields, currentUser || undefined);
189
+ const prefill = takeCreatePrefill(doctype);
190
+ setLocalDoc({ ...defaults, ...prefill, doctype });
191
+ // currentUser intentionally not a dep: seeding happens once per mount;
192
+ // a late-resolving user only matters for brand-new blank forms where
193
+ // the meta usually arrives later anyway.
194
+ }, [isCreate, meta, doctype]);
195
+
196
+ // ---------------------------------------------------------------------
197
+ // Field-level in-place sync (edit mode): server updates land on clean
198
+ // fields; dirty fields keep the user's edits.
199
+ // ---------------------------------------------------------------------
200
+ useEffect(() => {
201
+ if (!name) return;
202
+ if (liveDoc) {
203
+ everLoadedRef.current = true;
204
+ setWasDeleted(false);
205
+ const incoming = liveDoc as Record<string, unknown>;
206
+ const prevBaseline = baselineRef.current;
207
+ if (!prevBaseline) {
208
+ setBaseline(incoming);
209
+ setLocalDoc(incoming);
210
+ return;
211
+ }
212
+ const local = localDocRef.current;
213
+ const merged: Record<string, unknown> = { ...incoming };
214
+ for (const key of Object.keys(local)) {
215
+ // Same predicate as dirty tracking: a normalization-identical local
216
+ // value is not an edit, so the incoming server value wins.
217
+ if (!isSameValue(local[key], prevBaseline[key])) merged[key] = local[key];
218
+ }
219
+ setBaseline(incoming);
220
+ setLocalDoc(merged);
221
+ } else if (everLoadedRef.current && !isDocLoading) {
222
+ setWasDeleted(true);
223
+ }
224
+ }, [liveDoc, isDocLoading, name]);
225
+
226
+ // ---------------------------------------------------------------------
227
+ // Dirty tracking
228
+ // ---------------------------------------------------------------------
229
+ const dirtyFields = useMemo(() => computeDirtyFields(localDoc, baseline), [localDoc, baseline]);
230
+ const isDirty = dirtyFields.length > 0;
231
+
232
+ // ---------------------------------------------------------------------
233
+ // fetch_from preview: when a Link with dependent fetch_from targets
234
+ // changes, pull the source columns via frappe.client.validate_link and
235
+ // patch the targets (desk validate_link_and_fetch). Server stays
236
+ // authoritative on save.
237
+ // ---------------------------------------------------------------------
238
+ const fetchMap = useMemo(() => (meta ? buildFetchMap(meta.fields) : undefined), [meta]);
239
+ const fetchStateRef = useRef<{
240
+ meta: DoctypeMeta | undefined;
241
+ connection: FrappeConnection | undefined;
242
+ }>({ meta: undefined, connection: undefined });
243
+ fetchStateRef.current = { meta, connection };
244
+ const fetchMapRef = useRef(fetchMap);
245
+ fetchMapRef.current = fetchMap;
246
+
247
+ const runFetchFrom = useCallback((field: string, value: unknown) => {
248
+ const rules = fetchMapRef.current?.get(field);
249
+ if (!rules || rules.length === 0) return;
250
+ const { meta: currentMeta, connection: currentConnection } = fetchStateRef.current;
251
+ const linkField = currentMeta?.fields.find((f) => f.fieldname === field);
252
+ const targetDoctype = typeof linkField?.options === "string" ? linkField.options : "";
253
+ const applyPatch = (fetched: Record<string, unknown>) => {
254
+ setLocalDoc((prev) => {
255
+ // The link changed again while the fetch was in flight — drop it.
256
+ if (prev[field] !== value) return prev;
257
+ const patch = applyFetchedValues(rules, fetched, prev);
258
+ return Object.keys(patch).length > 0 ? { ...prev, ...patch } : prev;
259
+ });
260
+ };
261
+ if (value === undefined || value === null || value === "") {
262
+ // Cleared link → desk blanks the dependent targets.
263
+ applyPatch({});
264
+ return;
265
+ }
266
+ if (!currentConnection || !targetDoctype || currentConnection.fixtures) return;
267
+ const sources = Array.from(new Set(rules.map((rule) => rule.source)));
268
+ validateLink(createHttpClient(currentConnection), targetDoctype, String(value), sources)
269
+ .then(applyPatch)
270
+ .catch(() => {
271
+ // Invalid link / no read perm on source — leave targets as-is;
272
+ // the server re-validates on save.
273
+ });
274
+ }, []);
275
+
276
+ const setValue = useCallback(
277
+ (field: string, value: unknown) => {
278
+ setLocalDoc((prev) => ({ ...prev, [field]: value }));
279
+ setErrors((prev) => {
280
+ if (!(field in prev)) return prev;
281
+ const next = { ...prev };
282
+ delete next[field];
283
+ return next;
284
+ });
285
+ runFetchFrom(field, value);
286
+ },
287
+ [runFetchFrom],
288
+ );
289
+
290
+ // ---------------------------------------------------------------------
291
+ // Validation
292
+ // ---------------------------------------------------------------------
293
+ const validate = useCallback((): Record<string, string> => {
294
+ if (!meta) return {};
295
+ // Same evaluation context desk gives depends_on: `__islocal` marks an
296
+ // unsaved doc, `__session_user` backs `frappe.session.user` references.
297
+ const doc: Record<string, unknown> = {
298
+ ...localDocRef.current,
299
+ __islocal: isCreate ? 1 : 0,
300
+ ...(currentUser ? { __session_user: currentUser } : {}),
301
+ };
302
+ const validationErrors: Record<string, string> = {};
303
+ // Prompt-named doctypes need a user-entered name before insert
304
+ // (probed: REST insert without one fails with "Please set the document name").
305
+ if (isCreate && isPromptNamed(meta.autoname, meta.naming_rule) && isEmptyValue(doc.name)) {
306
+ validationErrors.name = translate("nameRequired", {
307
+ defaultValue: "Name is required",
308
+ });
309
+ }
310
+ for (const field of meta.fields) {
311
+ if (field.hidden || LAYOUT_TYPES.has(field.fieldtype)) continue;
312
+ // Desk parity: a field hidden by depends_on is not enforced client-side.
313
+ if (!evaluateDependsOn(field.depends_on, doc, true)) continue;
314
+ const required =
315
+ !!field.reqd ||
316
+ (!!field.mandatory_depends_on && evaluateDependsOn(field.mandatory_depends_on, doc, false));
317
+ if (!required) continue;
318
+ if (isEmptyValue(doc[field.fieldname])) {
319
+ validationErrors[field.fieldname] = translate("fieldRequired", {
320
+ defaultValue: "{{label}} is required",
321
+ label: field.label || field.fieldname,
322
+ });
323
+ }
324
+ }
325
+ return validationErrors;
326
+ }, [meta, isCreate, currentUser, translate]);
327
+
328
+ // ---------------------------------------------------------------------
329
+ // Save
330
+ // ---------------------------------------------------------------------
331
+ const save = useCallback(async (): Promise<string | undefined> => {
332
+ if (wasDeleted || !connection) return undefined;
333
+ const requiredErrors = validate();
334
+ if (Object.keys(requiredErrors).length > 0) {
335
+ setErrors(requiredErrors);
336
+ return undefined;
337
+ }
338
+ setIsSaving(true);
339
+ setErrors({});
340
+ try {
341
+ if (name) {
342
+ if (!docCollection) return undefined;
343
+ const local = localDocRef.current;
344
+ const base = baselineRef.current ?? {};
345
+ // Same predicate as dirty tracking: normalization-identical values
346
+ // must not be written back to the server as phantom edits.
347
+ const changed = Object.keys(local).filter(
348
+ (key) => key !== "doctype" && key !== "name" && !isSameValue(local[key], base[key]),
349
+ );
350
+ if (changed.length > 0) {
351
+ const tx = docCollection.update(name, (draft) => {
352
+ for (const key of changed) {
353
+ (draft as Record<string, unknown>)[key] = local[key];
354
+ }
355
+ });
356
+ await tx.isPersisted.promise;
357
+ }
358
+ setConflict(false);
359
+ return name;
360
+ }
361
+ // Create mode: direct REST insert to obtain the server-issued name.
362
+ // A user-entered `name` is kept for prompt-named doctypes; empty names
363
+ // are stripped so hash/series autonaming applies.
364
+ const { doctype: _dt, name: newName, ...values } = localDocRef.current;
365
+ if (typeof newName === "string" && newName.trim()) {
366
+ values.name = newName.trim();
367
+ }
368
+ const created = await insertDoc(createHttpClient(connection), doctype, values);
369
+ return String(created.name ?? "");
370
+ } catch (err) {
371
+ if (isConflictError(err)) {
372
+ setConflict(true);
373
+ setErrors({
374
+ general: translate("conflictError", {
375
+ defaultValue:
376
+ "This document was modified on the server after you opened it. Reload to get the latest version — your unsaved edits will be discarded.",
377
+ }),
378
+ });
379
+ } else {
380
+ setErrors({
381
+ general: extractServerError(
382
+ err,
383
+ translate("saveFailed", { defaultValue: "Failed to save" }),
384
+ ),
385
+ });
386
+ }
387
+ return undefined;
388
+ } finally {
389
+ setIsSaving(false);
390
+ }
391
+ }, [wasDeleted, connection, validate, name, docCollection, doctype, translate]);
392
+
393
+ // ---------------------------------------------------------------------
394
+ // Submit / Cancel lifecycle
395
+ // ---------------------------------------------------------------------
396
+ const submit = useCallback(async (): Promise<boolean> => {
397
+ if (!connection || !name) return false;
398
+ setIsSaving(true);
399
+ setErrors({});
400
+ try {
401
+ // The full current server doc (incl. `modified`) is required for OCC.
402
+ const base = baselineRef.current;
403
+ if (!base) return false;
404
+ await submitDoc(createHttpClient(connection), { ...base, doctype });
405
+ return true;
406
+ } catch (err) {
407
+ if (isConflictError(err)) setConflict(true);
408
+ setErrors({
409
+ general: extractServerError(
410
+ err,
411
+ translate("submitFailed", { defaultValue: "Failed to submit" }),
412
+ ),
413
+ });
414
+ return false;
415
+ } finally {
416
+ setIsSaving(false);
417
+ }
418
+ }, [connection, name, doctype, translate]);
419
+
420
+ const cancel = useCallback(async (): Promise<boolean> => {
421
+ if (!connection || !name) return false;
422
+ setIsSaving(true);
423
+ setErrors({});
424
+ try {
425
+ await cancelDoc(createHttpClient(connection), doctype, name);
426
+ return true;
427
+ } catch (err) {
428
+ setErrors({
429
+ general: extractServerError(
430
+ err,
431
+ translate("cancelFailed", { defaultValue: "Failed to cancel" }),
432
+ ),
433
+ });
434
+ return false;
435
+ } finally {
436
+ setIsSaving(false);
437
+ }
438
+ }, [connection, name, doctype, translate]);
439
+
440
+ const remove = useCallback(async (): Promise<boolean> => {
441
+ if (!docCollection || !name || wasDeleted) return false;
442
+ setIsSaving(true);
443
+ setErrors({});
444
+ try {
445
+ const tx = docCollection.delete(name);
446
+ await tx.isPersisted.promise;
447
+ return true;
448
+ } catch (err) {
449
+ setErrors({
450
+ general: extractServerError(
451
+ err,
452
+ translate("deleteFailed", { defaultValue: "Failed to delete" }),
453
+ ),
454
+ });
455
+ return false;
456
+ } finally {
457
+ setIsSaving(false);
458
+ }
459
+ }, [docCollection, name, wasDeleted, translate]);
460
+
461
+ const reload = useCallback(() => {
462
+ setConflict(false);
463
+ setErrors({});
464
+ if (baselineRef.current) {
465
+ setLocalDoc(baselineRef.current);
466
+ } else if (!name) {
467
+ seededRef.current = false;
468
+ setLocalDoc({ doctype });
469
+ }
470
+ }, [doctype, name]);
471
+
472
+ const docstatus = Number((baseline as { docstatus?: unknown } | undefined)?.docstatus ?? 0);
473
+
474
+ return {
475
+ doc: localDoc,
476
+ baseline,
477
+ isLoading: !!name && isDocLoading && !everLoadedRef.current,
478
+ notFound: !!name && !isDocLoading && !liveDoc && !everLoadedRef.current,
479
+ wasDeleted,
480
+ isCreate,
481
+ isDirty,
482
+ dirtyFields,
483
+ isSaving,
484
+ errors,
485
+ conflict,
486
+ currentUser,
487
+ docstatus,
488
+ setValue,
489
+ save,
490
+ submit,
491
+ cancel,
492
+ remove,
493
+ reload,
494
+ validate,
495
+ };
496
+ }
@@ -75,6 +75,15 @@ describe("Frappe remainder wrappers — design-law 100%", () => {
75
75
  expect(source).not.toContain("<Tint");
76
76
  });
77
77
 
78
+ it("FrappeChartCard does not import the t5 FrappeDashboard module", () => {
79
+ const card = read("FrappeChartCard.tsx");
80
+ const stories = read("FrappeChartCard.stories.tsx");
81
+ expect(card).not.toMatch(/from ["']\.\/FrappeDashboard["']/);
82
+ expect(stories).not.toMatch(/from ["']\.\/FrappeDashboard["']/);
83
+ expect(card).toMatch(/from ["']\.\/ChartCard["']/);
84
+ expect(stories).toMatch(/from ["']\.\/ChartCard["']/);
85
+ });
86
+
78
87
  it("FrappeImageView uses catalog Card, controlIcon icons, and no media pip (R11/R12)", () => {
79
88
  const source = read("FrappeImageView.tsx");
80
89
  expect(source).toContain("Card");
@@ -43,6 +43,7 @@ describe("Frappe view adapters — design-law 100%", () => {
43
43
  expect(source).toContain("formCommonColors");
44
44
  expect(source).toContain("knobProps.label");
45
45
  expect(source).toContain("knobProps.heading");
46
+ expect(source).toContain("knobProps.textAccentColor");
46
47
  expect(source).not.toMatch(/fontSize="\$1"/);
47
48
  });
48
49
 
package/src/index.spec.ts CHANGED
@@ -11,14 +11,16 @@
11
11
  * that seam before anything rendered — measured downstream 2026-08-13.
12
12
  * This spec locks the fix both ways:
13
13
  *
14
- * 1. ./FrappeDashboard exports only what it defines (FrappeDashboard and
15
- * the sanitizing ChartCard bridge) — no catalog names.
14
+ * 1. ./FrappeDashboard exports only what it defines (FrappeDashboard;
15
+ * ChartCard is a re-export of the sanitizing bridge in ./ChartCard)
16
+ * — no catalog names.
16
17
  * 2. The barrel still ships the catalog, and each name IS the components
17
18
  * export, not a copy and not a bounce.
18
19
  */
19
20
  import * as components from "@multiplatform.one/components";
20
21
  import { describe, expect, it } from "vitest";
21
22
  import * as dashboard from "./FrappeDashboard";
23
+ import { ChartCard as BridgeChartCard } from "./ChartCard";
22
24
  import * as barrel from "./index";
23
25
 
24
26
  const CATALOG_VALUE_NAMES = [
@@ -37,7 +39,9 @@ describe("dashboard catalog seam", () => {
37
39
  ).toBeUndefined();
38
40
  }
39
41
  expect(dashboard.FrappeDashboard).toBeDefined();
40
- expect(dashboard.ChartCard, "the sanitizing ChartCard bridge is its own export").toBeDefined();
42
+ expect(dashboard.ChartCard, "the sanitizing ChartCard bridge is re-exported").toBe(
43
+ BridgeChartCard,
44
+ );
41
45
  expect(
42
46
  dashboard.ChartCard,
43
47
  "ChartCard must be the sanitizing bridge, not the raw catalog card",
package/src/index.ts CHANGED
@@ -81,11 +81,15 @@ export {
81
81
 
82
82
  export {
83
83
  FrappeDashboard,
84
- ChartCard,
85
84
  type FrappeDashboardProps,
86
85
  type DashboardDataSource,
87
86
  } from "./FrappeDashboard";
88
87
 
88
+ // Sanitizing bridge — own file, not FrappeDashboard.tsx (t5). t4
89
+ // FrappeChartCard imports this module; re-exporting from the dashboard
90
+ // file was the catalog import defect.
91
+ export { ChartCard } from "./ChartCard";
92
+
89
93
  // The KPI/trend catalog is the components package's — re-export it from
90
94
  // there, NOT through ./FrappeDashboard. The old bounce made the built
91
95
  // FrappeDashboard.native module re-export names it does not define, and a
@@ -388,3 +392,49 @@ export {
388
392
  type UseFrappeReportOptions,
389
393
  type UseFrappeReportResult,
390
394
  } from "./FrappeReportView";
395
+
396
+ // ---------------------------------------------------------------------------
397
+ // Desk Form Engine (MPO-15)
398
+ // ---------------------------------------------------------------------------
399
+
400
+ // Meta-driven layout + state engine behind a desk form: tabs/sections,
401
+ // depends_on visibility, mandatory_depends_on, read_only_depends_on,
402
+ // permlevel gating, set_only_once, fetch_from locking, docstatus gating, and
403
+ // the save/submit/cancel/amend lifecycle. Hoisted out of
404
+ // @multiplatform.one/backoffice so FrappeForm can reach it without a
405
+ // higher-tier import; backoffice re-exports these names from their old paths.
406
+ //
407
+ // `formActions` stays deep-import only
408
+ // (`@multiplatform.one/frappe-ui/src/formEngine/formActions`) — its RPC
409
+ // helper names collide with the list and shell surfaces.
410
+ export {
411
+ applyFetchedValues,
412
+ buildCopyValues,
413
+ buildCreateDefaults,
414
+ buildFetchMap,
415
+ buildFormTabs,
416
+ buildPermLevelMatrix,
417
+ canReadLevel,
418
+ canWriteLevel,
419
+ computeDirtyFields,
420
+ isEmptyValue,
421
+ isFetchLocked,
422
+ isPromptNamed,
423
+ isSameValue,
424
+ setCreatePrefill,
425
+ takeCreatePrefill,
426
+ useFormEngine,
427
+ } from "./formEngine";
428
+ export type {
429
+ BuildTabsOptions,
430
+ DocFieldMeta,
431
+ DocLinkMeta,
432
+ DocPermissionRule,
433
+ DoctypeMeta,
434
+ FetchMap,
435
+ FetchRule,
436
+ FormEngine,
437
+ FormTab,
438
+ LevelPerm,
439
+ PermLevelMatrix,
440
+ } from "./formEngine";
@@ -144,7 +144,10 @@ describe("SchemaForm design-law (MPO-60 / R10)", () => {
144
144
  renderWithProviders(
145
145
  <SchemaForm schema={schema} doc={{ first_name: "Jane" }} setValue={vi.fn()} />,
146
146
  );
147
- const input = screen.getByLabelText("First Name", { hidden: true }) as HTMLInputElement;
147
+ // `hidden` is a getByRole option, not a SelectorMatcherOptions one —
148
+ // getByLabelText never filters on visibility, so it was a no-op that only
149
+ // typechecked by accident (MPO-109).
150
+ const input = screen.getByLabelText("First Name") as HTMLInputElement;
148
151
  expect(input).toBeTruthy();
149
152
  expect(input.value).toBe("Jane");
150
153
  const footer = input.closest("[aria-hidden='true']") as HTMLElement | null;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sanitizing ChartCard bridge. Lives in its own module so t4
3
+ * `FrappeChartCard` does not import the t5 `FrappeDashboard` experience
4
+ * file (component-tiers catalog defect). Values are coerced to numbers
5
+ * and rows without a finite value (missing, NaN, non-numeric, Infinity)
6
+ * are dropped, so malformed series can't flood the SVG layer with
7
+ * invalid-length (`NaN`) attribute errors. Bar and pie renderers can't
8
+ * draw negative magnitudes (negative rect heights / arc angles are
9
+ * invalid SVG), so negatives clamp to the baseline there.
10
+ */
11
+ import { type ChartCardProps } from "@multiplatform.one/components";
12
+ export declare function ChartCard({ config, ...chartCardProps }: ChartCardProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=ChartCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChartCard.d.ts","sourceRoot":"","sources":["../src/ChartCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,+BAA+B,CAAC;AAGvC,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,cAAc,2CAatE"}
@@ -1,4 +1,4 @@
1
- import { type ChartCardProps, type DashboardWidget } from "@multiplatform.one/components";
1
+ import { type DashboardWidget } from "@multiplatform.one/components";
2
2
  import React from "react";
3
3
  import { type YStackProps } from "tamagui";
4
4
  /**
@@ -50,13 +50,5 @@ export declare function FrappeDashboard<TDoc extends {
50
50
  name: string;
51
51
  doctype: string;
52
52
  }>({ baseURL, auth, syncOptions, title, description, widgets: staticWidgets, dataSource, transformData, columns, gap, padding, autoRefresh, refreshInterval, loading, error, ...stackProps }: FrappeDashboardProps<TDoc>): import("react/jsx-runtime").JSX.Element;
53
- /**
54
- * ChartCard bridge that sanitizes series rows before render: values are
55
- * coerced to numbers and rows without a finite value (missing, NaN,
56
- * non-numeric, Infinity) are dropped, so malformed series can't flood the
57
- * SVG layer with invalid-length (`NaN`) attribute errors. Bar and pie
58
- * renderers can't draw negative magnitudes (negative rect heights / arc
59
- * angles are invalid SVG), so negatives clamp to the baseline there.
60
- */
61
- export declare function ChartCard({ config, ...chartCardProps }: ChartCardProps): import("react/jsx-runtime").JSX.Element;
53
+ export { ChartCard } from "./ChartCard";
62
54
  //# sourceMappingURL=FrappeDashboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FrappeDashboard.d.ts","sourceRoot":"","sources":["../src/FrappeDashboard.tsx"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,cAAc,EAEnB,KAAK,eAAe,EACrB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAAwB,KAAK,WAAW,EAAM,MAAM,SAAS,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IACpE,YAAY,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;QAC3B,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;QACnD,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;KAC/B,EAAE,CAAC;CACL;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAE,SAAQ,IAAI,CAChG,WAAW,EACX,UAAU,CACX;IACC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,eAAe,EAAE,CAAC;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;CAC3C;AA8LD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC9E,OAAO,EACP,IAAI,EACJ,WAAW,EACX,KAAK,EACL,WAAW,EACX,OAAO,EAAE,aAAa,EACtB,UAAU,EACV,aAAa,EACb,OAAW,EACX,GAAQ,EACR,OAAY,EACZ,WAAmB,EACnB,eAAoB,EACpB,OAAO,EACP,KAAK,EACL,GAAG,UAAU,EACd,EAAE,oBAAoB,CAAC,IAAI,CAAC,2CAgH5B;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,cAAc,2CAatE"}
1
+ {"version":3,"file":"FrappeDashboard.d.ts","sourceRoot":"","sources":["../src/FrappeDashboard.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGhF,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,EAAwB,KAAK,WAAW,EAAM,MAAM,SAAS,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,IAAI;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IACpE,YAAY,CAAC,EAAE;QACb,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;QAC3B,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;QACnD,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;KAC/B,EAAE,CAAC;CACL;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAE,SAAQ,IAAI,CAChG,WAAW,EACX,UAAU,CACX;IACC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,eAAe,EAAE,CAAC;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;CAC3C;AA8LD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC9E,OAAO,EACP,IAAI,EACJ,WAAW,EACX,KAAK,EACL,WAAW,EACX,OAAO,EAAE,aAAa,EACtB,UAAU,EACV,aAAa,EACb,OAAW,EACX,GAAQ,EACR,OAAY,EACZ,WAAmB,EACnB,eAAoB,EACpB,OAAO,EACP,KAAK,EACL,GAAG,UAAU,EACd,EAAE,oBAAoB,CAAC,IAAI,CAAC,2CAgH5B;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}