@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.
- package/README.md +14 -0
- package/dist/cjs/FrappeForm.cjs +96 -52
- package/dist/cjs/FrappeForm.native.js +103 -52
- package/dist/cjs/FrappeForm.native.js.map +1 -1
- package/dist/cjs/FrappeFormActions.cjs +161 -0
- package/dist/cjs/FrappeFormActions.native.js +167 -0
- package/dist/cjs/FrappeFormActions.native.js.map +1 -0
- package/dist/cjs/FrappeTable.cjs +42 -13
- package/dist/cjs/FrappeTable.native.js +55 -19
- package/dist/cjs/FrappeTable.native.js.map +1 -1
- package/dist/cjs/desk/DeskFormPage.cjs +2 -1
- package/dist/cjs/desk/DeskFormPage.native.js +3 -0
- package/dist/cjs/desk/DeskFormPage.native.js.map +1 -1
- package/dist/cjs/filterFields.cjs +277 -34
- package/dist/cjs/filterFields.native.js +311 -36
- package/dist/cjs/filterFields.native.js.map +1 -1
- package/dist/cjs/formEngine/docLifecycle.cjs +98 -0
- package/dist/cjs/formEngine/docLifecycle.native.js +131 -0
- package/dist/cjs/formEngine/docLifecycle.native.js.map +1 -0
- package/dist/cjs/formEngine/useAmendedAs.cjs +48 -0
- package/dist/cjs/formEngine/useAmendedAs.native.js +60 -0
- package/dist/cjs/formEngine/useAmendedAs.native.js.map +1 -0
- package/dist/cjs/formEngine/useDocRights.cjs +2 -2
- package/dist/cjs/formEngine/useDocRights.native.js +2 -2
- package/dist/cjs/formEngine/useDocRights.native.js.map +1 -1
- package/dist/cjs/formEngine/useFormEngine.cjs +1 -0
- package/dist/cjs/formEngine/useFormEngine.native.js +1 -0
- package/dist/cjs/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/cjs/index.cjs +30 -1
- package/dist/cjs/index.native.js +30 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/list/listColumns.cjs +397 -0
- package/dist/cjs/list/listColumns.native.js +447 -0
- package/dist/cjs/list/listColumns.native.js.map +1 -0
- package/dist/cjs/list/listFields.cjs +50 -0
- package/dist/cjs/list/listFields.native.js +79 -0
- package/dist/cjs/list/listFields.native.js.map +1 -0
- package/dist/cjs/list/listFormat.cjs +68 -0
- package/dist/cjs/list/listFormat.native.js +74 -0
- package/dist/cjs/list/listFormat.native.js.map +1 -0
- package/dist/cjs/list/statusColors.cjs +102 -0
- package/dist/cjs/list/statusColors.native.js +109 -0
- package/dist/cjs/list/statusColors.native.js.map +1 -0
- package/dist/cjs/useFrappeAggregate.cjs +1 -1
- package/dist/cjs/useFrappeAggregate.native.js +1 -1
- package/dist/cjs/useFrappeAggregate.native.js.map +1 -1
- package/dist/esm/FrappeForm.mjs +97 -53
- package/dist/esm/FrappeForm.mjs.map +1 -1
- package/dist/esm/FrappeForm.native.js +104 -53
- package/dist/esm/FrappeForm.native.js.map +1 -1
- package/dist/esm/FrappeFormActions.mjs +136 -0
- package/dist/esm/FrappeFormActions.mjs.map +1 -0
- package/dist/esm/FrappeFormActions.native.js +139 -0
- package/dist/esm/FrappeFormActions.native.js.map +1 -0
- package/dist/esm/FrappeTable.mjs +42 -13
- package/dist/esm/FrappeTable.mjs.map +1 -1
- package/dist/esm/FrappeTable.native.js +55 -19
- package/dist/esm/FrappeTable.native.js.map +1 -1
- package/dist/esm/desk/DeskFormPage.mjs +2 -1
- package/dist/esm/desk/DeskFormPage.mjs.map +1 -1
- package/dist/esm/desk/DeskFormPage.native.js +3 -0
- package/dist/esm/desk/DeskFormPage.native.js.map +1 -1
- package/dist/esm/filterFields.mjs +263 -34
- package/dist/esm/filterFields.mjs.map +1 -1
- package/dist/esm/filterFields.native.js +297 -36
- package/dist/esm/filterFields.native.js.map +1 -1
- package/dist/esm/formEngine/docLifecycle.mjs +68 -0
- package/dist/esm/formEngine/docLifecycle.mjs.map +1 -0
- package/dist/esm/formEngine/docLifecycle.native.js +98 -0
- package/dist/esm/formEngine/docLifecycle.native.js.map +1 -0
- package/dist/esm/formEngine/useAmendedAs.mjs +23 -0
- package/dist/esm/formEngine/useAmendedAs.mjs.map +1 -0
- package/dist/esm/formEngine/useAmendedAs.native.js +32 -0
- package/dist/esm/formEngine/useAmendedAs.native.js.map +1 -0
- package/dist/esm/formEngine/useDocRights.mjs +2 -2
- package/dist/esm/formEngine/useDocRights.mjs.map +1 -1
- package/dist/esm/formEngine/useDocRights.native.js +2 -2
- package/dist/esm/formEngine/useDocRights.native.js.map +1 -1
- package/dist/esm/formEngine/useFormEngine.mjs +1 -1
- package/dist/esm/formEngine/useFormEngine.mjs.map +1 -1
- package/dist/esm/formEngine/useFormEngine.native.js +1 -1
- package/dist/esm/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/esm/index.mjs +6 -2
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +6 -2
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/list/listColumns.mjs +372 -0
- package/dist/esm/list/listColumns.mjs.map +1 -0
- package/dist/esm/list/listColumns.native.js +419 -0
- package/dist/esm/list/listColumns.native.js.map +1 -0
- package/dist/esm/list/listFields.mjs +23 -0
- package/dist/esm/list/listFields.mjs.map +1 -0
- package/dist/esm/list/listFields.native.js +49 -0
- package/dist/esm/list/listFields.native.js.map +1 -0
- package/dist/esm/list/listFormat.mjs +38 -0
- package/dist/esm/list/listFormat.mjs.map +1 -0
- package/dist/esm/list/listFormat.native.js +41 -0
- package/dist/esm/list/listFormat.native.js.map +1 -0
- package/dist/esm/list/statusColors.mjs +77 -0
- package/dist/esm/list/statusColors.mjs.map +1 -0
- package/dist/esm/list/statusColors.native.js +81 -0
- package/dist/esm/list/statusColors.native.js.map +1 -0
- package/dist/esm/useFrappeAggregate.mjs +1 -1
- package/dist/esm/useFrappeAggregate.mjs.map +1 -1
- package/dist/esm/useFrappeAggregate.native.js +1 -1
- package/dist/esm/useFrappeAggregate.native.js.map +1 -1
- package/dist/jsx/FrappeForm.mjs +97 -53
- package/dist/jsx/FrappeForm.mjs.map +1 -1
- package/dist/jsx/FrappeForm.native.js +103 -52
- package/dist/jsx/FrappeForm.native.js.map +1 -1
- package/dist/jsx/FrappeFormActions.mjs +136 -0
- package/dist/jsx/FrappeFormActions.mjs.map +1 -0
- package/dist/jsx/FrappeFormActions.native.js +167 -0
- package/dist/jsx/FrappeFormActions.native.js.map +1 -0
- package/dist/jsx/FrappeTable.mjs +42 -13
- package/dist/jsx/FrappeTable.mjs.map +1 -1
- package/dist/jsx/FrappeTable.native.js +55 -19
- package/dist/jsx/FrappeTable.native.js.map +1 -1
- package/dist/jsx/desk/DeskFormPage.mjs +2 -1
- package/dist/jsx/desk/DeskFormPage.mjs.map +1 -1
- package/dist/jsx/desk/DeskFormPage.native.js +3 -0
- package/dist/jsx/desk/DeskFormPage.native.js.map +1 -1
- package/dist/jsx/filterFields.mjs +263 -34
- package/dist/jsx/filterFields.mjs.map +1 -1
- package/dist/jsx/filterFields.native.js +311 -36
- package/dist/jsx/filterFields.native.js.map +1 -1
- package/dist/jsx/formEngine/docLifecycle.mjs +68 -0
- package/dist/jsx/formEngine/docLifecycle.mjs.map +1 -0
- package/dist/jsx/formEngine/docLifecycle.native.js +131 -0
- package/dist/jsx/formEngine/docLifecycle.native.js.map +1 -0
- package/dist/jsx/formEngine/useAmendedAs.mjs +23 -0
- package/dist/jsx/formEngine/useAmendedAs.mjs.map +1 -0
- package/dist/jsx/formEngine/useAmendedAs.native.js +60 -0
- package/dist/jsx/formEngine/useAmendedAs.native.js.map +1 -0
- package/dist/jsx/formEngine/useDocRights.mjs +2 -2
- package/dist/jsx/formEngine/useDocRights.mjs.map +1 -1
- package/dist/jsx/formEngine/useDocRights.native.js +2 -2
- package/dist/jsx/formEngine/useDocRights.native.js.map +1 -1
- package/dist/jsx/formEngine/useFormEngine.mjs +1 -1
- package/dist/jsx/formEngine/useFormEngine.mjs.map +1 -1
- package/dist/jsx/formEngine/useFormEngine.native.js +1 -0
- package/dist/jsx/formEngine/useFormEngine.native.js.map +1 -1
- package/dist/jsx/index.js +6 -2
- package/dist/jsx/index.js.map +1 -1
- package/dist/jsx/index.mjs +6 -2
- package/dist/jsx/index.mjs.map +1 -1
- package/dist/jsx/index.native.js +30 -1
- package/dist/jsx/index.native.js.map +1 -1
- package/dist/jsx/list/listColumns.mjs +372 -0
- package/dist/jsx/list/listColumns.mjs.map +1 -0
- package/dist/jsx/list/listColumns.native.js +447 -0
- package/dist/jsx/list/listColumns.native.js.map +1 -0
- package/dist/jsx/list/listFields.mjs +23 -0
- package/dist/jsx/list/listFields.mjs.map +1 -0
- package/dist/jsx/list/listFields.native.js +79 -0
- package/dist/jsx/list/listFields.native.js.map +1 -0
- package/dist/jsx/list/listFormat.mjs +38 -0
- package/dist/jsx/list/listFormat.mjs.map +1 -0
- package/dist/jsx/list/listFormat.native.js +74 -0
- package/dist/jsx/list/listFormat.native.js.map +1 -0
- package/dist/jsx/list/statusColors.mjs +77 -0
- package/dist/jsx/list/statusColors.mjs.map +1 -0
- package/dist/jsx/list/statusColors.native.js +109 -0
- package/dist/jsx/list/statusColors.native.js.map +1 -0
- package/dist/jsx/useFrappeAggregate.mjs +1 -1
- package/dist/jsx/useFrappeAggregate.mjs.map +1 -1
- package/dist/jsx/useFrappeAggregate.native.js +1 -1
- package/dist/jsx/useFrappeAggregate.native.js.map +1 -1
- package/package.json +22 -16
- package/src/FrappeForm.deskSemantics.spec.tsx +1 -1
- package/src/FrappeForm.lifecycle.spec.tsx +463 -0
- package/src/FrappeForm.lifecycle.stories.tsx +84 -0
- package/src/FrappeForm.tsx +124 -84
- package/src/FrappeFormActions.tsx +216 -0
- package/src/FrappeTable.defaultColumns.spec.tsx +229 -0
- package/src/FrappeTable.tsx +79 -32
- package/src/desk/DeskFormPage.tsx +1 -0
- package/src/filterFields.spec.ts +57 -2
- package/src/filterFields.ts +365 -38
- package/src/formEngine/docLifecycle.spec.ts +195 -0
- package/src/formEngine/docLifecycle.ts +148 -0
- package/src/formEngine/useAmendedAs.ts +38 -0
- package/src/formEngine/useDocRights.ts +11 -3
- package/src/formEngine/useFormEngine.ts +1 -1
- package/src/frappeViews.designLaw.spec.ts +6 -0
- package/src/index.ts +47 -1
- package/src/list/listColumns.tsx +488 -0
- package/src/list/listFields.spec.ts +147 -0
- package/src/list/listFields.ts +68 -0
- package/src/list/listFormat.spec.ts +151 -0
- package/src/list/listFormat.ts +80 -0
- package/src/list/seam.spec.ts +116 -0
- package/src/list/statusColors.spec.ts +100 -0
- package/src/list/statusColors.ts +94 -0
- package/src/useFrappeAggregate.ts +1 -1
- package/types/FrappeForm.d.ts +13 -3
- package/types/FrappeForm.d.ts.map +1 -1
- package/types/FrappeFormActions.d.ts +36 -0
- package/types/FrappeFormActions.d.ts.map +1 -0
- package/types/FrappeTable.d.ts +11 -5
- package/types/FrappeTable.d.ts.map +1 -1
- package/types/desk/DeskFormPage.d.ts.map +1 -1
- package/types/filterFields.d.ts +81 -10
- package/types/filterFields.d.ts.map +1 -1
- package/types/formEngine/docLifecycle.d.ts +59 -0
- package/types/formEngine/docLifecycle.d.ts.map +1 -0
- package/types/formEngine/useAmendedAs.d.ts +9 -0
- package/types/formEngine/useAmendedAs.d.ts.map +1 -0
- package/types/formEngine/useDocRights.d.ts +1 -1
- package/types/formEngine/useDocRights.d.ts.map +1 -1
- package/types/formEngine/useFormEngine.d.ts +1 -0
- package/types/formEngine/useFormEngine.d.ts.map +1 -1
- package/types/index.d.ts +5 -1
- package/types/index.d.ts.map +1 -1
- package/types/list/listColumns.d.ts +41 -0
- package/types/list/listColumns.d.ts.map +1 -0
- package/types/list/listFields.d.ts +16 -0
- package/types/list/listFields.d.ts.map +1 -0
- package/types/list/listFormat.d.ts +30 -0
- package/types/list/listFormat.d.ts.map +1 -0
- package/types/list/statusColors.d.ts +14 -0
- 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 c</p>")).toBe("a b c");
|
|
128
|
+
expect(stripHtmlPreview("x & y <z>")).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(/ /gi, " ")
|
|
52
|
+
.replace(/&/gi, "&")
|
|
53
|
+
.replace(/</gi, "<")
|
|
54
|
+
.replace(/>/gi, ">")
|
|
55
|
+
.replace(/'|'/gi, "'")
|
|
56
|
+
.replace(/"/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
|
package/types/FrappeForm.d.ts
CHANGED
|
@@ -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)
|
|
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
|
-
*
|
|
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":"AAAA,OAAO,EAML,KAAK,WAAW,EAEhB,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAe,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;
|
|
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"}
|