@multiplatform.one/frappe-ui 7.22.0 → 7.23.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 (222) hide show
  1. package/README.md +14 -0
  2. package/dist/cjs/FrappeForm.cjs +96 -52
  3. package/dist/cjs/FrappeForm.native.js +103 -52
  4. package/dist/cjs/FrappeForm.native.js.map +1 -1
  5. package/dist/cjs/FrappeFormActions.cjs +161 -0
  6. package/dist/cjs/FrappeFormActions.native.js +167 -0
  7. package/dist/cjs/FrappeFormActions.native.js.map +1 -0
  8. package/dist/cjs/FrappeTable.cjs +42 -13
  9. package/dist/cjs/FrappeTable.native.js +55 -19
  10. package/dist/cjs/FrappeTable.native.js.map +1 -1
  11. package/dist/cjs/desk/DeskFormPage.cjs +2 -1
  12. package/dist/cjs/desk/DeskFormPage.native.js +3 -0
  13. package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
  14. package/dist/cjs/filterFields.cjs +277 -34
  15. package/dist/cjs/filterFields.native.js +311 -36
  16. package/dist/cjs/filterFields.native.js.map +1 -1
  17. package/dist/cjs/formEngine/docLifecycle.cjs +98 -0
  18. package/dist/cjs/formEngine/docLifecycle.native.js +131 -0
  19. package/dist/cjs/formEngine/docLifecycle.native.js.map +1 -0
  20. package/dist/cjs/formEngine/useAmendedAs.cjs +48 -0
  21. package/dist/cjs/formEngine/useAmendedAs.native.js +60 -0
  22. package/dist/cjs/formEngine/useAmendedAs.native.js.map +1 -0
  23. package/dist/cjs/formEngine/useDocRights.cjs +2 -2
  24. package/dist/cjs/formEngine/useDocRights.native.js +2 -2
  25. package/dist/cjs/formEngine/useDocRights.native.js.map +1 -1
  26. package/dist/cjs/formEngine/useFormEngine.cjs +1 -0
  27. package/dist/cjs/formEngine/useFormEngine.native.js +1 -0
  28. package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
  29. package/dist/cjs/index.cjs +30 -1
  30. package/dist/cjs/index.native.js +30 -1
  31. package/dist/cjs/index.native.js.map +1 -1
  32. package/dist/cjs/list/listColumns.cjs +397 -0
  33. package/dist/cjs/list/listColumns.native.js +447 -0
  34. package/dist/cjs/list/listColumns.native.js.map +1 -0
  35. package/dist/cjs/list/listFields.cjs +50 -0
  36. package/dist/cjs/list/listFields.native.js +79 -0
  37. package/dist/cjs/list/listFields.native.js.map +1 -0
  38. package/dist/cjs/list/listFormat.cjs +68 -0
  39. package/dist/cjs/list/listFormat.native.js +74 -0
  40. package/dist/cjs/list/listFormat.native.js.map +1 -0
  41. package/dist/cjs/list/statusColors.cjs +102 -0
  42. package/dist/cjs/list/statusColors.native.js +109 -0
  43. package/dist/cjs/list/statusColors.native.js.map +1 -0
  44. package/dist/cjs/useFrappeAggregate.cjs +1 -1
  45. package/dist/cjs/useFrappeAggregate.native.js +1 -1
  46. package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
  47. package/dist/esm/FrappeForm.mjs +97 -53
  48. package/dist/esm/FrappeForm.mjs.map +1 -1
  49. package/dist/esm/FrappeForm.native.js +104 -53
  50. package/dist/esm/FrappeForm.native.js.map +1 -1
  51. package/dist/esm/FrappeFormActions.mjs +136 -0
  52. package/dist/esm/FrappeFormActions.mjs.map +1 -0
  53. package/dist/esm/FrappeFormActions.native.js +139 -0
  54. package/dist/esm/FrappeFormActions.native.js.map +1 -0
  55. package/dist/esm/FrappeTable.mjs +42 -13
  56. package/dist/esm/FrappeTable.mjs.map +1 -1
  57. package/dist/esm/FrappeTable.native.js +55 -19
  58. package/dist/esm/FrappeTable.native.js.map +1 -1
  59. package/dist/esm/desk/DeskFormPage.mjs +2 -1
  60. package/dist/esm/desk/DeskFormPage.mjs.map +1 -1
  61. package/dist/esm/desk/DeskFormPage.native.js +3 -0
  62. package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
  63. package/dist/esm/filterFields.mjs +263 -34
  64. package/dist/esm/filterFields.mjs.map +1 -1
  65. package/dist/esm/filterFields.native.js +297 -36
  66. package/dist/esm/filterFields.native.js.map +1 -1
  67. package/dist/esm/formEngine/docLifecycle.mjs +68 -0
  68. package/dist/esm/formEngine/docLifecycle.mjs.map +1 -0
  69. package/dist/esm/formEngine/docLifecycle.native.js +98 -0
  70. package/dist/esm/formEngine/docLifecycle.native.js.map +1 -0
  71. package/dist/esm/formEngine/useAmendedAs.mjs +23 -0
  72. package/dist/esm/formEngine/useAmendedAs.mjs.map +1 -0
  73. package/dist/esm/formEngine/useAmendedAs.native.js +32 -0
  74. package/dist/esm/formEngine/useAmendedAs.native.js.map +1 -0
  75. package/dist/esm/formEngine/useDocRights.mjs +2 -2
  76. package/dist/esm/formEngine/useDocRights.mjs.map +1 -1
  77. package/dist/esm/formEngine/useDocRights.native.js +2 -2
  78. package/dist/esm/formEngine/useDocRights.native.js.map +1 -1
  79. package/dist/esm/formEngine/useFormEngine.mjs +1 -1
  80. package/dist/esm/formEngine/useFormEngine.mjs.map +1 -1
  81. package/dist/esm/formEngine/useFormEngine.native.js +1 -1
  82. package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
  83. package/dist/esm/index.mjs +6 -2
  84. package/dist/esm/index.mjs.map +1 -1
  85. package/dist/esm/index.native.js +6 -2
  86. package/dist/esm/index.native.js.map +1 -1
  87. package/dist/esm/list/listColumns.mjs +372 -0
  88. package/dist/esm/list/listColumns.mjs.map +1 -0
  89. package/dist/esm/list/listColumns.native.js +419 -0
  90. package/dist/esm/list/listColumns.native.js.map +1 -0
  91. package/dist/esm/list/listFields.mjs +23 -0
  92. package/dist/esm/list/listFields.mjs.map +1 -0
  93. package/dist/esm/list/listFields.native.js +49 -0
  94. package/dist/esm/list/listFields.native.js.map +1 -0
  95. package/dist/esm/list/listFormat.mjs +38 -0
  96. package/dist/esm/list/listFormat.mjs.map +1 -0
  97. package/dist/esm/list/listFormat.native.js +41 -0
  98. package/dist/esm/list/listFormat.native.js.map +1 -0
  99. package/dist/esm/list/statusColors.mjs +77 -0
  100. package/dist/esm/list/statusColors.mjs.map +1 -0
  101. package/dist/esm/list/statusColors.native.js +81 -0
  102. package/dist/esm/list/statusColors.native.js.map +1 -0
  103. package/dist/esm/useFrappeAggregate.mjs +1 -1
  104. package/dist/esm/useFrappeAggregate.mjs.map +1 -1
  105. package/dist/esm/useFrappeAggregate.native.js +1 -1
  106. package/dist/esm/useFrappeAggregate.native.js.map +1 -1
  107. package/dist/jsx/FrappeForm.mjs +97 -53
  108. package/dist/jsx/FrappeForm.mjs.map +1 -1
  109. package/dist/jsx/FrappeForm.native.js +103 -52
  110. package/dist/jsx/FrappeForm.native.js.map +1 -1
  111. package/dist/jsx/FrappeFormActions.mjs +136 -0
  112. package/dist/jsx/FrappeFormActions.mjs.map +1 -0
  113. package/dist/jsx/FrappeFormActions.native.js +167 -0
  114. package/dist/jsx/FrappeFormActions.native.js.map +1 -0
  115. package/dist/jsx/FrappeTable.mjs +42 -13
  116. package/dist/jsx/FrappeTable.mjs.map +1 -1
  117. package/dist/jsx/FrappeTable.native.js +55 -19
  118. package/dist/jsx/FrappeTable.native.js.map +1 -1
  119. package/dist/jsx/desk/DeskFormPage.mjs +2 -1
  120. package/dist/jsx/desk/DeskFormPage.mjs.map +1 -1
  121. package/dist/jsx/desk/DeskFormPage.native.js +3 -0
  122. package/dist/jsx/desk/DeskFormPage.native.js.map +1 -1
  123. package/dist/jsx/filterFields.mjs +263 -34
  124. package/dist/jsx/filterFields.mjs.map +1 -1
  125. package/dist/jsx/filterFields.native.js +311 -36
  126. package/dist/jsx/filterFields.native.js.map +1 -1
  127. package/dist/jsx/formEngine/docLifecycle.mjs +68 -0
  128. package/dist/jsx/formEngine/docLifecycle.mjs.map +1 -0
  129. package/dist/jsx/formEngine/docLifecycle.native.js +131 -0
  130. package/dist/jsx/formEngine/docLifecycle.native.js.map +1 -0
  131. package/dist/jsx/formEngine/useAmendedAs.mjs +23 -0
  132. package/dist/jsx/formEngine/useAmendedAs.mjs.map +1 -0
  133. package/dist/jsx/formEngine/useAmendedAs.native.js +60 -0
  134. package/dist/jsx/formEngine/useAmendedAs.native.js.map +1 -0
  135. package/dist/jsx/formEngine/useDocRights.mjs +2 -2
  136. package/dist/jsx/formEngine/useDocRights.mjs.map +1 -1
  137. package/dist/jsx/formEngine/useDocRights.native.js +2 -2
  138. package/dist/jsx/formEngine/useDocRights.native.js.map +1 -1
  139. package/dist/jsx/formEngine/useFormEngine.mjs +1 -1
  140. package/dist/jsx/formEngine/useFormEngine.mjs.map +1 -1
  141. package/dist/jsx/formEngine/useFormEngine.native.js +1 -0
  142. package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -1
  143. package/dist/jsx/index.js +6 -2
  144. package/dist/jsx/index.js.map +1 -1
  145. package/dist/jsx/index.mjs +6 -2
  146. package/dist/jsx/index.mjs.map +1 -1
  147. package/dist/jsx/index.native.js +30 -1
  148. package/dist/jsx/index.native.js.map +1 -1
  149. package/dist/jsx/list/listColumns.mjs +372 -0
  150. package/dist/jsx/list/listColumns.mjs.map +1 -0
  151. package/dist/jsx/list/listColumns.native.js +447 -0
  152. package/dist/jsx/list/listColumns.native.js.map +1 -0
  153. package/dist/jsx/list/listFields.mjs +23 -0
  154. package/dist/jsx/list/listFields.mjs.map +1 -0
  155. package/dist/jsx/list/listFields.native.js +79 -0
  156. package/dist/jsx/list/listFields.native.js.map +1 -0
  157. package/dist/jsx/list/listFormat.mjs +38 -0
  158. package/dist/jsx/list/listFormat.mjs.map +1 -0
  159. package/dist/jsx/list/listFormat.native.js +74 -0
  160. package/dist/jsx/list/listFormat.native.js.map +1 -0
  161. package/dist/jsx/list/statusColors.mjs +77 -0
  162. package/dist/jsx/list/statusColors.mjs.map +1 -0
  163. package/dist/jsx/list/statusColors.native.js +109 -0
  164. package/dist/jsx/list/statusColors.native.js.map +1 -0
  165. package/dist/jsx/useFrappeAggregate.mjs +1 -1
  166. package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
  167. package/dist/jsx/useFrappeAggregate.native.js +1 -1
  168. package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
  169. package/package.json +22 -16
  170. package/src/FrappeForm.deskSemantics.spec.tsx +1 -1
  171. package/src/FrappeForm.lifecycle.spec.tsx +463 -0
  172. package/src/FrappeForm.lifecycle.stories.tsx +84 -0
  173. package/src/FrappeForm.tsx +124 -84
  174. package/src/FrappeFormActions.tsx +216 -0
  175. package/src/FrappeTable.defaultColumns.spec.tsx +229 -0
  176. package/src/FrappeTable.tsx +79 -32
  177. package/src/desk/DeskFormPage.tsx +1 -0
  178. package/src/filterFields.spec.ts +57 -2
  179. package/src/filterFields.ts +365 -38
  180. package/src/formEngine/docLifecycle.spec.ts +195 -0
  181. package/src/formEngine/docLifecycle.ts +148 -0
  182. package/src/formEngine/useAmendedAs.ts +38 -0
  183. package/src/formEngine/useDocRights.ts +11 -3
  184. package/src/formEngine/useFormEngine.ts +1 -1
  185. package/src/frappeViews.designLaw.spec.ts +6 -0
  186. package/src/index.ts +47 -1
  187. package/src/list/listColumns.tsx +488 -0
  188. package/src/list/listFields.spec.ts +147 -0
  189. package/src/list/listFields.ts +68 -0
  190. package/src/list/listFormat.spec.ts +151 -0
  191. package/src/list/listFormat.ts +80 -0
  192. package/src/list/seam.spec.ts +116 -0
  193. package/src/list/statusColors.spec.ts +100 -0
  194. package/src/list/statusColors.ts +94 -0
  195. package/src/useFrappeAggregate.ts +1 -1
  196. package/types/FrappeForm.d.ts +13 -3
  197. package/types/FrappeForm.d.ts.map +1 -1
  198. package/types/FrappeFormActions.d.ts +36 -0
  199. package/types/FrappeFormActions.d.ts.map +1 -0
  200. package/types/FrappeTable.d.ts +11 -5
  201. package/types/FrappeTable.d.ts.map +1 -1
  202. package/types/desk/DeskFormPage.d.ts.map +1 -1
  203. package/types/filterFields.d.ts +81 -10
  204. package/types/filterFields.d.ts.map +1 -1
  205. package/types/formEngine/docLifecycle.d.ts +59 -0
  206. package/types/formEngine/docLifecycle.d.ts.map +1 -0
  207. package/types/formEngine/useAmendedAs.d.ts +9 -0
  208. package/types/formEngine/useAmendedAs.d.ts.map +1 -0
  209. package/types/formEngine/useDocRights.d.ts +1 -1
  210. package/types/formEngine/useDocRights.d.ts.map +1 -1
  211. package/types/formEngine/useFormEngine.d.ts +1 -0
  212. package/types/formEngine/useFormEngine.d.ts.map +1 -1
  213. package/types/index.d.ts +5 -1
  214. package/types/index.d.ts.map +1 -1
  215. package/types/list/listColumns.d.ts +41 -0
  216. package/types/list/listColumns.d.ts.map +1 -0
  217. package/types/list/listFields.d.ts +16 -0
  218. package/types/list/listFields.d.ts.map +1 -0
  219. package/types/list/listFormat.d.ts +30 -0
  220. package/types/list/listFormat.d.ts.map +1 -0
  221. package/types/list/statusColors.d.ts +14 -0
  222. package/types/list/statusColors.d.ts.map +1 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * listFormat — frappe datetime parsing and desk-style list-cell display
3
+ * helpers. Time-ago fixtures anchor `now` to the parsed date itself so the
4
+ * assertions are timezone-independent.
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+ import {
8
+ formatDateCell,
9
+ formatTimeAgo,
10
+ parseFrappeDate,
11
+ parseTagsField,
12
+ parseUserListField,
13
+ stripHtmlPreview,
14
+ } from "./listFormat";
15
+
16
+ describe("parseFrappeDate", () => {
17
+ it("parses frappe datetime strings (space-separated, microseconds)", () => {
18
+ const date = parseFrappeDate("2026-08-12 08:50:24.691936");
19
+ expect(date).toBeInstanceOf(Date);
20
+ expect(date?.getFullYear()).toBe(2026);
21
+ expect(date?.getMonth()).toBe(7); // August
22
+ expect(date?.getDate()).toBe(12);
23
+ expect(date?.getHours()).toBe(8);
24
+ expect(date?.getMinutes()).toBe(50);
25
+ expect(date?.getSeconds()).toBe(24);
26
+ });
27
+
28
+ it("parses plain dates", () => {
29
+ const date = parseFrappeDate("2026-08-12");
30
+ expect(date).toBeInstanceOf(Date);
31
+ expect(Number.isNaN(date?.getTime())).toBe(false);
32
+ });
33
+
34
+ it("returns undefined for invalid input", () => {
35
+ expect(parseFrappeDate("not a date")).toBeUndefined();
36
+ expect(parseFrappeDate("")).toBeUndefined();
37
+ });
38
+ });
39
+
40
+ describe("formatDateCell (LC-12 compact-absolute display register)", () => {
41
+ // Anchor `now` inside the stamp's year so current-year elision is stable.
42
+ const now = parseFrappeDate("2026-01-01 00:00:00") as Date;
43
+
44
+ it("renders the house compact-absolute register, never raw ISO", () => {
45
+ expect(formatDateCell("2026-08-12 08:50:24", now)).toBe("Aug 12");
46
+ expect(formatDateCell("2026-08-12 08:50:24.691936", now)).toBe("Aug 12");
47
+ expect(formatDateCell("2026-08-12", now)).toBe("Aug 12");
48
+ });
49
+
50
+ it("keeps the year when not current (display register year:auto)", () => {
51
+ expect(formatDateCell("2025-07-01", now)).toBe("Jul 1, 2025");
52
+ });
53
+
54
+ it("passes unparseable input through unchanged (honest passthrough)", () => {
55
+ expect(formatDateCell("garbage", now)).toBe("garbage");
56
+ });
57
+ });
58
+
59
+ describe("formatTimeAgo (LC-12 relative register)", () => {
60
+ const stamp = "2026-08-12 08:00:00";
61
+ const base = parseFrappeDate(stamp) as Date;
62
+ const after = (ms: number) => new Date(base.getTime() + ms);
63
+ const MINUTE = 60_000;
64
+ const HOUR = 60 * MINUTE;
65
+ const DAY = 24 * HOUR;
66
+
67
+ it("speaks the house relative register — one voice, unambiguous units", () => {
68
+ expect(formatTimeAgo(stamp, after(0))).toBe("just now");
69
+ expect(formatTimeAgo(stamp, after(59_000))).toBe("just now");
70
+ expect(formatTimeAgo(stamp, after(MINUTE))).toBe("1m ago");
71
+ expect(formatTimeAgo(stamp, after(59 * MINUTE))).toBe("59m ago");
72
+ expect(formatTimeAgo(stamp, after(HOUR))).toBe("1h ago");
73
+ expect(formatTimeAgo(stamp, after(23 * HOUR + 59 * MINUTE))).toBe("23h ago");
74
+ expect(formatTimeAgo(stamp, after(DAY))).toBe("1 day ago");
75
+ expect(formatTimeAgo(stamp, after(29 * DAY))).toBe("29 days ago");
76
+ });
77
+
78
+ it("months use 'mo', never an ambiguous capital M", () => {
79
+ expect(formatTimeAgo(stamp, after(31 * DAY))).toBe("1mo ago");
80
+ expect(formatTimeAgo(stamp, after(330 * DAY))).toBe("11mo ago");
81
+ });
82
+
83
+ it("shows years beyond ~12 months", () => {
84
+ expect(formatTimeAgo(stamp, after(366 * DAY))).toBe("1y ago");
85
+ expect(formatTimeAgo(stamp, after(2 * 366 * DAY))).toBe("2y ago");
86
+ });
87
+
88
+ it("returns the raw value when unparseable", () => {
89
+ expect(formatTimeAgo("garbage", new Date())).toBe("garbage");
90
+ });
91
+
92
+ it("treats future timestamps as just now (clamped, never negative)", () => {
93
+ expect(formatTimeAgo(stamp, after(-5 * HOUR))).toBe("just now");
94
+ });
95
+ });
96
+
97
+ describe("parseUserListField (_assign / _liked_by JSON-array-string columns)", () => {
98
+ it("parses the bench shape: a JSON array string", () => {
99
+ expect(parseUserListField('["admin@example.com", "test@example.com"]')).toEqual([
100
+ "admin@example.com",
101
+ "test@example.com",
102
+ ]);
103
+ });
104
+
105
+ it("passes real arrays through, stringifying members", () => {
106
+ expect(parseUserListField(["a@b.c", 42])).toEqual(["a@b.c", "42"]);
107
+ });
108
+
109
+ it("returns [] for empty, malformed, or non-array JSON", () => {
110
+ expect(parseUserListField("")).toEqual([]);
111
+ expect(parseUserListField(null)).toEqual([]);
112
+ expect(parseUserListField(undefined)).toEqual([]);
113
+ expect(parseUserListField("not json")).toEqual([]);
114
+ expect(parseUserListField('{"a":1}')).toEqual([]);
115
+ expect(parseUserListField(42)).toEqual([]);
116
+ });
117
+ });
118
+
119
+ describe("stripHtmlPreview (rich-text cell previews, desk strip_html parity)", () => {
120
+ it("strips Text Editor markup down to its text", () => {
121
+ expect(stripHtmlPreview('<div class="ql-editor read-mode"><p>Call the vendor</p></div>')).toBe(
122
+ "Call the vendor",
123
+ );
124
+ });
125
+
126
+ it("collapses tags and entities into single spaces", () => {
127
+ expect(stripHtmlPreview("<p>a</p><p>b&nbsp;c</p>")).toBe("a b c");
128
+ expect(stripHtmlPreview("x &amp; y &lt;z&gt;")).toBe("x & y <z>");
129
+ });
130
+
131
+ it("passes plain text through untouched (fast path)", () => {
132
+ expect(stripHtmlPreview("plain description")).toBe("plain description");
133
+ });
134
+ });
135
+
136
+ describe("parseTagsField (_user_tags comma-joined column)", () => {
137
+ it("parses the bench shape: leading-comma joined tags", () => {
138
+ expect(parseTagsField(",Urgent,Follow Up")).toEqual(["Urgent", "Follow Up"]);
139
+ });
140
+
141
+ it("trims whitespace and drops empty segments", () => {
142
+ expect(parseTagsField(" , a , ,b, ")).toEqual(["a", "b"]);
143
+ });
144
+
145
+ it("returns [] for empty or non-string values", () => {
146
+ expect(parseTagsField("")).toEqual([]);
147
+ expect(parseTagsField(null)).toEqual([]);
148
+ expect(parseTagsField(undefined)).toEqual([]);
149
+ expect(parseTagsField(["a"])).toEqual([]);
150
+ });
151
+ });
@@ -0,0 +1,80 @@
1
+ // Cross-platform display helpers for list cells — frappe datetimes like
2
+ // "2026-08-12 08:50:24.691936". Formatting routes through the house date
3
+ // channel (LC-12 DATA-REGISTER-CHANNEL): one formatter owns each register,
4
+ // this module only parses the frappe wire format and picks the register.
5
+ import { formatAbsoluteDate, formatRelativeTimestamp } from "@multiplatform.one/theme";
6
+
7
+ /** Parse a frappe date/datetime string into a Date (undefined when invalid). */
8
+ export function parseFrappeDate(value: string): Date | undefined {
9
+ // "YYYY-MM-DD HH:mm:ss.SSSSSS" → ISO-ish so JSC/Hermes parse it too.
10
+ const isoish = value.includes(" ") ? value.replace(" ", "T") : value;
11
+ const date = new Date(isoish);
12
+ return Number.isNaN(date.getTime()) ? undefined : date;
13
+ }
14
+
15
+ /**
16
+ * Table date cell — the house compact-absolute display register
17
+ * ("Aug 12" / "Jul 1, 2025", current year elided). Raw ISO wire format
18
+ * never reaches the cell (LC-12); unparseable input passes through.
19
+ * The STRING (not a parsed Date) rides into the channel so its date-only
20
+ * UTC branch engages — a parsed "YYYY-MM-DD" would shift a day in
21
+ * negative-offset timezones.
22
+ */
23
+ export function formatDateCell(value: string, now?: Date): string {
24
+ if (!parseFrappeDate(value)) return value;
25
+ const isoish = value.includes(" ") ? value.replace(" ", "T") : value;
26
+ return formatAbsoluteDate(isoish, now ? { now } : undefined);
27
+ }
28
+
29
+ /**
30
+ * Recency column ("Last Updated On") — the house relative register
31
+ * ("just now", "5m ago", "2h ago", "2mo ago"). One voice, unambiguous
32
+ * units (LC-12); unparseable input passes through.
33
+ */
34
+ export function formatTimeAgo(value: string, now: Date = new Date()): string {
35
+ const date = parseFrappeDate(value);
36
+ if (!date) return value;
37
+ return formatRelativeTimestamp(date, now.getTime());
38
+ }
39
+
40
+ /**
41
+ * Plain-text preview of rich/HTML field content for list cells (desk
42
+ * `strip_html` parity): tags dropped, entities collapsed, whitespace
43
+ * normalized. A Text Editor value like `<div class="ql-editor"><p>Call the
44
+ * vendor</p></div>` renders as "Call the vendor" — markup never paints in
45
+ * a one-line cell (LC-06/LC-10 containment; cell-containment rule candidate).
46
+ */
47
+ export function stripHtmlPreview(value: string): string {
48
+ if (!value.includes("<") && !value.includes("&")) return value;
49
+ return value
50
+ .replace(/<[^>]*>/g, " ")
51
+ .replace(/&nbsp;/gi, " ")
52
+ .replace(/&amp;/gi, "&")
53
+ .replace(/&lt;/gi, "<")
54
+ .replace(/&gt;/gi, ">")
55
+ .replace(/&#39;|&apos;/gi, "'")
56
+ .replace(/&quot;/gi, '"')
57
+ .replace(/\s+/g, " ")
58
+ .trim();
59
+ }
60
+
61
+ /** Parse frappe's `_assign` / `_liked_by` JSON-array-string columns. */
62
+ export function parseUserListField(value: unknown): string[] {
63
+ if (Array.isArray(value)) return value.map(String);
64
+ if (typeof value !== "string" || !value) return [];
65
+ try {
66
+ const parsed = JSON.parse(value);
67
+ return Array.isArray(parsed) ? parsed.map(String) : [];
68
+ } catch {
69
+ return [];
70
+ }
71
+ }
72
+
73
+ /** Parse frappe's `_user_tags` comma-joined column (",a,b" → ["a","b"]). */
74
+ export function parseTagsField(value: unknown): string[] {
75
+ if (typeof value !== "string") return [];
76
+ return value
77
+ .split(",")
78
+ .map((tag) => tag.trim())
79
+ .filter(Boolean);
80
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * MPO-15 — the desk list seam, from this side.
3
+ *
4
+ * The list column builder and the filterable-field vocabulary used to live
5
+ * in `@multiplatform.one/backoffice`, one tier above this package, so
6
+ * FrappeTable could not default its columns from meta and the two list
7
+ * stacks kept two filter-field tables. Both live here now, and three things
8
+ * have to stay true for that to mean anything:
9
+ *
10
+ * 1. The package barrel hands every name out.
11
+ * 2. The list modules reach nothing above them: no backoffice import, and
12
+ * no bounce back through this package's own barrel.
13
+ * 3. One filterable-field vocabulary: only `filterFields.ts` declares which
14
+ * fieldtypes filter and which standard doc fields every doctype has.
15
+ */
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { describe, expect, it } from "vitest";
19
+
20
+ const listDir = path.resolve(__dirname);
21
+ const srcDir = path.resolve(__dirname, "..");
22
+ const publicDir = path.resolve(srcDir, "../..");
23
+
24
+ function sourceFiles(dir: string): string[] {
25
+ const out: string[] = [];
26
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
27
+ const full = path.join(dir, entry.name);
28
+ if (entry.isDirectory()) {
29
+ out.push(...sourceFiles(full));
30
+ continue;
31
+ }
32
+ if (!/\.(ts|tsx)$/.test(entry.name)) continue;
33
+ if (/\.(spec|stories)\./.test(entry.name)) continue;
34
+ out.push(full);
35
+ }
36
+ return out;
37
+ }
38
+
39
+ const LIST_NAMES = [
40
+ "buildListColumns",
41
+ "BuildListColumnsArgs",
42
+ "ListDoc",
43
+ "ListRow",
44
+ "listDocumentColumnIds",
45
+ "listFieldsToFetch",
46
+ "listViewFields",
47
+ "formatDateCell",
48
+ "formatTimeAgo",
49
+ "parseFrappeDate",
50
+ "parseTagsField",
51
+ "parseUserListField",
52
+ "stripHtmlPreview",
53
+ "statusPillColors",
54
+ "StatusPillColors",
55
+ ];
56
+
57
+ const VOCABULARY_NAMES = [
58
+ "defaultOperatorForKind",
59
+ "DESK_OPERATOR_LABELS",
60
+ "docFieldsToFilterFields",
61
+ "draftToTuple",
62
+ "fieldKind",
63
+ "fieldLabelFor",
64
+ "filterableFieldsFromMeta",
65
+ "filterTupleLabel",
66
+ "operatorsForKind",
67
+ "quickFilterValue",
68
+ "selectOptionsFromField",
69
+ "setFieldFilter",
70
+ "standardDocFilterFields",
71
+ "standardFiltersFromMeta",
72
+ "tupleToDraft",
73
+ "DeskOperator",
74
+ "FilterFieldDescriptor",
75
+ "FilterFieldKind",
76
+ "FilterRowDraft",
77
+ ];
78
+
79
+ describe("desk list seam (MPO-15)", () => {
80
+ it("hands the list and the vocabulary out of the package barrel", () => {
81
+ const barrel = fs.readFileSync(path.resolve(srcDir, "index.ts"), "utf-8");
82
+ const block = barrel.slice(barrel.indexOf("Desk List (MPO-15)"));
83
+ expect(block, "the barrel must carry a desk list block").toContain('from "./list/listColumns"');
84
+ for (const name of [...LIST_NAMES, ...VOCABULARY_NAMES]) {
85
+ expect(block, `the barrel must re-export "${name}"`).toMatch(new RegExp(`\\b${name}\\b`));
86
+ }
87
+ });
88
+
89
+ it("keeps the list modules off backoffice and off their own barrel", () => {
90
+ for (const file of sourceFiles(listDir)) {
91
+ const source = fs.readFileSync(file, "utf-8");
92
+ expect(source, file).not.toMatch(/from ["']@multiplatform\.one\/backoffice/);
93
+ expect(source, file).not.toMatch(/from ["']@multiplatform\.one\/frappe-ui/);
94
+ expect(source, file).not.toMatch(/from ["']\.\.\/index["']/);
95
+ }
96
+ });
97
+
98
+ it("declares the filterable-field table once, across both list stacks", () => {
99
+ const declares =
100
+ /NON_FILTERABLE_FIELDTYPES\s*=|STANDARD_DOC_FIELDS\s*[:=]|function standardDocFilterFields/;
101
+ const owners = [
102
+ ...sourceFiles(srcDir),
103
+ ...sourceFiles(path.resolve(publicDir, "backoffice/src")),
104
+ ]
105
+ .filter((file) => declares.test(fs.readFileSync(file, "utf-8")))
106
+ .map((file) => path.relative(publicDir, file));
107
+ expect(owners).toEqual(["frappe-ui/src/filterFields.ts"]);
108
+ });
109
+
110
+ it("derives FrappeTable's default columns through the one column builder", () => {
111
+ const table = fs.readFileSync(path.resolve(srcDir, "FrappeTable.tsx"), "utf-8");
112
+ expect(table).toMatch(/columns\?: DataTableColumn/);
113
+ expect(table).toContain('from "./list/listColumns"');
114
+ expect(table).toMatch(/buildListColumns\(/);
115
+ });
116
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * statusColors — desk indicator-color parity for the list status pill.
3
+ * Workflow `states` from the doctype meta must win over the heuristic
4
+ * word map; everything unknown lands on gray.
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+ import type { DoctypeMeta } from "../formEngine/doctypeMeta";
8
+ import { statusPillColors } from "./statusColors";
9
+
10
+ function makeMeta(states: Array<{ title?: string; color?: string }>): DoctypeMeta {
11
+ return { name: "ToDo", fields: [], states } as DoctypeMeta;
12
+ }
13
+
14
+ describe("statusPillColors heuristics (no meta)", () => {
15
+ it("maps done/healthy words to green", () => {
16
+ for (const status of ["Closed", "Completed", "Approved", "Paid", "Active", "Delivered"]) {
17
+ expect(statusPillColors(status)).toEqual({ background: "$green3", text: "$green11" });
18
+ }
19
+ });
20
+
21
+ it("maps in-flight words to orange", () => {
22
+ for (const status of ["Open", "Pending", "In Progress", "Overdue", "Medium"]) {
23
+ expect(statusPillColors(status)).toEqual({ background: "$orange3", text: "$orange11" });
24
+ }
25
+ });
26
+
27
+ it("maps informational words to blue", () => {
28
+ for (const status of ["Submitted", "Running", "New", "In Review", "Low"]) {
29
+ expect(statusPillColors(status)).toEqual({ background: "$blue3", text: "$blue11" });
30
+ }
31
+ });
32
+
33
+ it("maps failed/stopped words to red", () => {
34
+ for (const status of ["Cancelled", "Canceled", "Failed", "Rejected", "Urgent", "High"]) {
35
+ expect(statusPillColors(status)).toEqual({ background: "$red3", text: "$red11" });
36
+ }
37
+ });
38
+
39
+ it("maps draft and unknown values to gray", () => {
40
+ expect(statusPillColors("Draft")).toEqual({ background: "$gray3", text: "$gray11" });
41
+ expect(statusPillColors("Something Custom")).toEqual({
42
+ background: "$gray3",
43
+ text: "$gray11",
44
+ });
45
+ expect(statusPillColors("")).toEqual({ background: "$gray3", text: "$gray11" });
46
+ });
47
+
48
+ it("normalizes case and whitespace", () => {
49
+ expect(statusPillColors(" OPEN ")).toEqual({ background: "$orange3", text: "$orange11" });
50
+ expect(statusPillColors("paid")).toEqual({ background: "$green3", text: "$green11" });
51
+ });
52
+ });
53
+
54
+ describe("statusPillColors with workflow states", () => {
55
+ it("prefers the workflow state color over the word heuristic", () => {
56
+ // Heuristic would say orange for "Open"; the workflow says Red.
57
+ const meta = makeMeta([{ title: "Open", color: "Red" }]);
58
+ expect(statusPillColors("Open", meta)).toEqual({ background: "$red3", text: "$red11" });
59
+ });
60
+
61
+ it("matches state titles case-insensitively and trims", () => {
62
+ const meta = makeMeta([{ title: " open ", color: "Green" }]);
63
+ expect(statusPillColors("Open", meta)).toEqual({ background: "$green3", text: "$green11" });
64
+ });
65
+
66
+ it("maps desk color aliases onto ramp families", () => {
67
+ const cases: Array<[string, string]> = [
68
+ ["Light-Blue", "blue"],
69
+ ["cyan", "blue"],
70
+ ["Amber", "yellow"],
71
+ ["Grey", "gray"],
72
+ ["darkgrey", "gray"],
73
+ ["Purple", "purple"],
74
+ ["Pink", "pink"],
75
+ ];
76
+ for (const [deskColor, ramp] of cases) {
77
+ const meta = makeMeta([{ title: "Custom", color: deskColor }]);
78
+ expect(statusPillColors("Custom", meta)).toEqual({
79
+ background: `$${ramp}3`,
80
+ text: `$${ramp}11`,
81
+ });
82
+ }
83
+ });
84
+
85
+ it("falls back to the heuristic for unknown desk colors", () => {
86
+ const meta = makeMeta([{ title: "Open", color: "Teal" }]);
87
+ expect(statusPillColors("Open", meta)).toEqual({ background: "$orange3", text: "$orange11" });
88
+ });
89
+
90
+ it("falls back to the heuristic when the state has no color or no match", () => {
91
+ expect(statusPillColors("Open", makeMeta([{ title: "Open" }]))).toEqual({
92
+ background: "$orange3",
93
+ text: "$orange11",
94
+ });
95
+ expect(statusPillColors("Closed", makeMeta([{ title: "Open", color: "Red" }]))).toEqual({
96
+ background: "$green3",
97
+ text: "$green11",
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,94 @@
1
+ // Desk indicator-color parity: map a status value to theme color tokens for
2
+ // the list-view status pill. Doctype workflow `states` (which carry desk
3
+ // color names) win over the heuristic word map.
4
+ import type { DoctypeMeta } from "../formEngine/doctypeMeta";
5
+
6
+ export interface StatusPillColors {
7
+ /** Pill surface (step-3 ramp). */
8
+ background: string;
9
+ /** Pill text (step-11 ramp — AA on the step-3 surface). */
10
+ text: string;
11
+ }
12
+
13
+ /** Desk indicator color name → tamagui ramp family. */
14
+ const DESK_COLOR_TO_RAMP: Record<string, string> = {
15
+ red: "red",
16
+ green: "green",
17
+ blue: "blue",
18
+ orange: "orange",
19
+ yellow: "yellow",
20
+ amber: "yellow",
21
+ gray: "gray",
22
+ grey: "gray",
23
+ darkgrey: "gray",
24
+ "light-blue": "blue",
25
+ purple: "purple",
26
+ pink: "pink",
27
+ cyan: "blue",
28
+ };
29
+
30
+ /** Desk's common listview indicator heuristics (frappe list_view indicators). */
31
+ const STATUS_WORD_TO_RAMP: Record<string, string> = {
32
+ // green — done / healthy
33
+ closed: "green",
34
+ completed: "green",
35
+ complete: "green",
36
+ finished: "green",
37
+ active: "green",
38
+ enabled: "green",
39
+ approved: "green",
40
+ paid: "green",
41
+ success: "green",
42
+ resolved: "green",
43
+ delivered: "green",
44
+ received: "green",
45
+ // orange — needs attention / in flight
46
+ open: "orange",
47
+ pending: "orange",
48
+ "in progress": "orange",
49
+ "to deliver": "orange",
50
+ "to bill": "orange",
51
+ overdue: "orange",
52
+ medium: "orange",
53
+ // blue — submitted / informational
54
+ submitted: "blue",
55
+ running: "blue",
56
+ new: "blue",
57
+ "in review": "blue",
58
+ low: "blue",
59
+ // red — failed / stopped
60
+ cancelled: "red",
61
+ canceled: "red",
62
+ disabled: "red",
63
+ failed: "red",
64
+ rejected: "red",
65
+ error: "red",
66
+ expired: "red",
67
+ inactive: "red",
68
+ unpaid: "red",
69
+ lost: "red",
70
+ high: "red",
71
+ urgent: "red",
72
+ // gray — inert
73
+ draft: "gray",
74
+ };
75
+
76
+ function rampToPill(ramp: string): StatusPillColors {
77
+ return { background: `$${ramp}3`, text: `$${ramp}11` };
78
+ }
79
+
80
+ /**
81
+ * Resolve the pill colors for a status value. Workflow states from the
82
+ * doctype meta (desk color names) take precedence; otherwise a heuristic
83
+ * word map mirrors stock desk indicators; unknown values fall back to gray.
84
+ */
85
+ export function statusPillColors(status: string, meta?: DoctypeMeta): StatusPillColors {
86
+ const normalized = status.trim().toLowerCase();
87
+ const state = meta?.states?.find((s) => (s.title ?? "").trim().toLowerCase() === normalized);
88
+ if (state?.color) {
89
+ const ramp = DESK_COLOR_TO_RAMP[state.color.trim().toLowerCase()];
90
+ if (ramp) return rampToPill(ramp);
91
+ }
92
+ const ramp = STATUS_WORD_TO_RAMP[normalized];
93
+ return rampToPill(ramp ?? "gray");
94
+ }
@@ -99,7 +99,7 @@ const DEFAULT_DEBOUNCE_MS = 300;
99
99
  const dbCache = new Map<string, DbModule>();
100
100
 
101
101
  function getDb(baseURL: string, auth: FrappeAggregateConnection["auth"]): DbModule {
102
- const key = `${baseURL}:${auth?.token ?? ""}:${auth?.type ?? ""}`;
102
+ const key = `${baseURL}:${auth?.useToken ? "t" : ""}:${auth?.token ?? ""}:${auth?.type ?? ""}`;
103
103
  let db = dbCache.get(key);
104
104
  if (!db) {
105
105
  const authConfig: AuthConfig | undefined = auth
@@ -81,9 +81,18 @@ export interface FrappeFormProps<TDoc extends {
81
81
  onSave?: (doc: Partial<TDoc>) => void;
82
82
  onCancel?: () => void;
83
83
  onDelete?: (doc: Partial<TDoc>) => void;
84
+ /**
85
+ * Opens the amended draft of a cancelled document (desk Amend). The draft
86
+ * carries `amended_from` and is also handed to the doctype's next
87
+ * create-mode FrappeForm, so navigating to one is enough. Absent, a
88
+ * cancelled document offers no Amend.
89
+ */
90
+ onAmend?: (draft: Partial<TDoc>) => void;
84
91
  /**
85
92
  * Given, the form is a read-only view of the record: its toolbar offers
86
- * Edit (this callback) and Delete instead of Save and Cancel.
93
+ * Edit (this callback) instead of Save and Cancel, beside Delete and the
94
+ * document's Submit, Cancel or Amend. A document with nothing left to
95
+ * edit (cancelled, or submitted without allow_on_submit fields) shows no Edit.
87
96
  */
88
97
  onEdit?: () => void;
89
98
  /**
@@ -91,8 +100,9 @@ export interface FrappeFormProps<TDoc extends {
91
100
  * the doctype's record page: it reads the record from the path through
92
101
  * the FrappeProvider's navigation (`route/new` creates, `route/<name>`
93
102
  * views, `route/<name>/edit` edits), carries the detail page template,
94
- * and after a save, cancel, edit or delete moves to the matching page.
95
- * Callbacks given alongside it replace those moves.
103
+ * and after a save, cancel, edit or delete moves to the matching page;
104
+ * Amend opens the amended draft on `route/new`. Callbacks given alongside
105
+ * it replace those moves.
96
106
  */
97
107
  route?: string;
98
108
  showToolbar?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"FrappeForm.d.ts","sourceRoot":"","sources":["../src/FrappeForm.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FrappeForm.d.ts","sourceRoot":"","sources":["../src/FrappeForm.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import type { DocAction, DocStatusIndicator } from "./formEngine/docLifecycle";
2
+ export interface FrappeFormActionsProps {
3
+ /** Desk's state pill: Not Saved, Draft, Submitted or Cancelled. */
4
+ indicator?: DocStatusIndicator;
5
+ /** The one lifecycle action the document's state offers (resolveDocAction). */
6
+ action?: DocAction;
7
+ /** A save or a docstatus transition is in flight. */
8
+ busy: boolean;
9
+ /** The form is read-only: actions render, disabled with the reason. */
10
+ disabled: boolean;
11
+ onSave: () => void;
12
+ onSubmit: () => void;
13
+ /** Cancels the submitted document (docstatus 1 → 2). */
14
+ onCancelDocument: () => void;
15
+ /** Absent, a cancelled document offers no Amend: the host has nowhere to open the draft. */
16
+ onAmend?: () => void;
17
+ /** The live amendment of this cancelled document; Amend stays disabled while it exists. */
18
+ amendedAs?: string;
19
+ onDelete?: () => void;
20
+ /**
21
+ * Given, the form is a read-only view and this opens its editor. The
22
+ * caller leaves it out when the document has nothing left to edit.
23
+ */
24
+ onEdit?: () => void;
25
+ /** The host's leave action (discards unsaved edits first). */
26
+ onClose?: () => void;
27
+ closeDisabled?: boolean;
28
+ }
29
+ /**
30
+ * FrappeForm's action bar with desk's docstatus lifecycle: one primary
31
+ * action at a time (Save, Submit, Update or Amend), Cancel Document as the
32
+ * secondary destructive action on a submitted document, and the state pill.
33
+ * A read-only view trades Save and the leave button for Edit.
34
+ */
35
+ export declare function FrappeFormActions({ indicator, action, busy, disabled, onSave, onSubmit, onCancelDocument, onAmend, amendedAs, onDelete, onEdit, onClose, closeDisabled, }: FrappeFormActionsProps): import("react/jsx-runtime").JSX.Element;
36
+ //# sourceMappingURL=FrappeFormActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FrappeFormActions.d.ts","sourceRoot":"","sources":["../src/FrappeFormActions.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAS/E,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,uEAAuE;IACvE,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,OAAO,EACP,SAAS,EACT,QAAQ,EACR,MAAM,EACN,OAAO,EACP,aAAa,GACd,EAAE,sBAAsB,2CAgJxB"}