@proteinjs/db-ui 1.13.1 → 1.14.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.
- package/CHANGELOG.md +16 -0
- package/dist/src/form/RecordForm.d.ts.map +1 -1
- package/dist/src/form/RecordForm.js +124 -11
- package/dist/src/form/RecordForm.js.map +1 -1
- package/dist/src/table/ReferenceCellValue.d.ts.map +1 -1
- package/dist/src/table/ReferenceCellValue.js +4 -1
- package/dist/src/table/ReferenceCellValue.js.map +1 -1
- package/dist/test/recordFormInteractions.test.js +12 -7
- package/dist/test/recordFormInteractions.test.js.map +1 -1
- package/dist/test/recordFormSections.test.d.ts +5 -0
- package/dist/test/recordFormSections.test.d.ts.map +1 -0
- package/dist/test/recordFormSections.test.js +289 -0
- package/dist/test/recordFormSections.test.js.map +1 -0
- package/dist/test/recordFormStructuredFields.test.js +11 -5
- package/dist/test/recordFormStructuredFields.test.js.map +1 -1
- package/package.json +4 -4
- package/src/form/RecordForm.tsx +143 -14
- package/src/table/ReferenceCellValue.tsx +3 -1
- package/test/recordFormInteractions.test.tsx +8 -6
- package/test/recordFormSections.test.tsx +189 -0
- package/test/recordFormStructuredFields.test.tsx +12 -4
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __assign = (this && this.__assign) || function () {
|
|
18
|
+
__assign = Object.assign || function(t) {
|
|
19
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
20
|
+
s = arguments[i];
|
|
21
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
22
|
+
t[p] = s[p];
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
};
|
|
26
|
+
return __assign.apply(this, arguments);
|
|
27
|
+
};
|
|
28
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
29
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
30
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
31
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
32
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
33
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
34
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
38
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
39
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
40
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
41
|
+
function step(op) {
|
|
42
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
43
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
44
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
45
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
46
|
+
switch (op[0]) {
|
|
47
|
+
case 0: case 1: t = op; break;
|
|
48
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
49
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
50
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
51
|
+
default:
|
|
52
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
53
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
54
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
55
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
56
|
+
if (t[2]) _.ops.pop();
|
|
57
|
+
_.trys.pop(); continue;
|
|
58
|
+
}
|
|
59
|
+
op = body.call(thisArg, _);
|
|
60
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
61
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
65
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
66
|
+
};
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
/**
|
|
69
|
+
* @jest-environment jsdom
|
|
70
|
+
*
|
|
71
|
+
* RecordForm's field GROUPING (round 2). Contracts as OUTCOMES:
|
|
72
|
+
* 1. Sections derive from column type + name in a fixed order: identity (name/email/title)
|
|
73
|
+
* → Content (long text, structured values) → Details (everything else) → System
|
|
74
|
+
* (id/created/updated), and System is always last.
|
|
75
|
+
* 2. A column's `ui.formGroup` hint overrides the derivation; an unknown hint value becomes
|
|
76
|
+
* its own titled section, ordered after Details.
|
|
77
|
+
* 3. The record's id renders in System as a readonly value row — a record form's address is
|
|
78
|
+
* what an admin copies, and the table layer's `ui.hidden` (a DATA-column flag) doesn't
|
|
79
|
+
* bury it here.
|
|
80
|
+
* 4. A single-section form stays unlabeled — one group needs no header.
|
|
81
|
+
*/
|
|
82
|
+
var react_1 = __importDefault(require("react"));
|
|
83
|
+
var moment_1 = __importDefault(require("moment"));
|
|
84
|
+
var client_1 = require("react-dom/client");
|
|
85
|
+
var test_utils_1 = require("react-dom/test-utils");
|
|
86
|
+
var react_router_dom_1 = require("react-router-dom");
|
|
87
|
+
var db_1 = require("@proteinjs/db");
|
|
88
|
+
require("../generated");
|
|
89
|
+
var RecordForm_1 = require("../src/form/RecordForm");
|
|
90
|
+
var mockDbService = {
|
|
91
|
+
get: jest.fn(),
|
|
92
|
+
insert: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
93
|
+
return [2 /*return*/, record];
|
|
94
|
+
}); }); }),
|
|
95
|
+
update: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
96
|
+
return [2 /*return*/, record];
|
|
97
|
+
}); }); }),
|
|
98
|
+
delete: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
99
|
+
return [2 /*return*/, 1];
|
|
100
|
+
}); }); }),
|
|
101
|
+
};
|
|
102
|
+
jest.mock('@proteinjs/db', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/db')), { getDbService: function () { return mockDbService; } })); });
|
|
103
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
104
|
+
var JobTable = /** @class */ (function (_super) {
|
|
105
|
+
__extends(JobTable, _super);
|
|
106
|
+
function JobTable() {
|
|
107
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
108
|
+
_this.name = 'admin_section_job';
|
|
109
|
+
_this.columns = (0, db_1.withRecordColumns)({
|
|
110
|
+
// Declared deliberately out of section order: the form must GROUP, not echo the schema.
|
|
111
|
+
status: new db_1.StringColumn('status'),
|
|
112
|
+
failureMessage: new db_1.StringColumn('failure_message', {}, 4000),
|
|
113
|
+
name: new db_1.StringColumn('name'),
|
|
114
|
+
manual: new db_1.BooleanColumn('manual'),
|
|
115
|
+
payload: new db_1.ObjectColumn('payload', { ui: { hidden: false } }),
|
|
116
|
+
});
|
|
117
|
+
return _this;
|
|
118
|
+
}
|
|
119
|
+
return JobTable;
|
|
120
|
+
}(db_1.Table));
|
|
121
|
+
/** Same shape, but the status column claims the Content section by hint. */
|
|
122
|
+
var HintedJobTable = /** @class */ (function (_super) {
|
|
123
|
+
__extends(HintedJobTable, _super);
|
|
124
|
+
function HintedJobTable() {
|
|
125
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
126
|
+
_this.name = 'admin_section_hinted_job';
|
|
127
|
+
_this.columns = (0, db_1.withRecordColumns)({
|
|
128
|
+
status: new db_1.StringColumn('status', { ui: { formGroup: 'content' } }),
|
|
129
|
+
failureMessage: new db_1.StringColumn('failure_message', {}, 4000),
|
|
130
|
+
name: new db_1.StringColumn('name'),
|
|
131
|
+
manual: new db_1.BooleanColumn('manual', { ui: { formGroup: 'operations' } }),
|
|
132
|
+
payload: new db_1.ObjectColumn('payload', { ui: { hidden: false } }),
|
|
133
|
+
});
|
|
134
|
+
return _this;
|
|
135
|
+
}
|
|
136
|
+
return HintedJobTable;
|
|
137
|
+
}(db_1.Table));
|
|
138
|
+
var created = (0, moment_1.default)('2026-01-02T03:04:05.000Z');
|
|
139
|
+
var updated = (0, moment_1.default)('2026-02-03T04:05:06.000Z');
|
|
140
|
+
function loadedRecord() {
|
|
141
|
+
return {
|
|
142
|
+
id: 'job-77',
|
|
143
|
+
name: 'Nightly export',
|
|
144
|
+
failureMessage: 'It broke',
|
|
145
|
+
payload: { retries: 3 },
|
|
146
|
+
status: 'failure',
|
|
147
|
+
manual: false,
|
|
148
|
+
created: created,
|
|
149
|
+
updated: updated,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
describe('RecordForm sections', function () {
|
|
153
|
+
var container;
|
|
154
|
+
var root;
|
|
155
|
+
beforeEach(function () {
|
|
156
|
+
jest.clearAllMocks();
|
|
157
|
+
container = document.createElement('div');
|
|
158
|
+
document.body.appendChild(container);
|
|
159
|
+
root = (0, client_1.createRoot)(container);
|
|
160
|
+
});
|
|
161
|
+
afterEach(function () {
|
|
162
|
+
(0, test_utils_1.act)(function () { return root.unmount(); });
|
|
163
|
+
container.remove();
|
|
164
|
+
});
|
|
165
|
+
var mount = function (table, record) { return __awaiter(void 0, void 0, void 0, function () {
|
|
166
|
+
return __generator(this, function (_a) {
|
|
167
|
+
switch (_a.label) {
|
|
168
|
+
case 0: return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
169
|
+
return __generator(this, function (_a) {
|
|
170
|
+
root.render(react_1.default.createElement(react_router_dom_1.MemoryRouter, null,
|
|
171
|
+
react_1.default.createElement(RecordForm_1.RecordForm, { table: table, record: record })));
|
|
172
|
+
return [2 /*return*/];
|
|
173
|
+
});
|
|
174
|
+
}); })];
|
|
175
|
+
case 1:
|
|
176
|
+
_a.sent();
|
|
177
|
+
return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
178
|
+
return __generator(this, function (_a) {
|
|
179
|
+
switch (_a.label) {
|
|
180
|
+
case 0: return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 0); })];
|
|
181
|
+
case 1:
|
|
182
|
+
_a.sent();
|
|
183
|
+
return [2 /*return*/];
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}); })];
|
|
187
|
+
case 2:
|
|
188
|
+
_a.sent();
|
|
189
|
+
return [2 /*return*/];
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}); };
|
|
193
|
+
var sectionLabels = function () {
|
|
194
|
+
return Array.from(document.querySelectorAll('[data-form-section-label]')).map(function (el) { return el.textContent; });
|
|
195
|
+
};
|
|
196
|
+
/** The section element a field's label sits inside. */
|
|
197
|
+
var sectionOf = function (labelText) {
|
|
198
|
+
var label = Array.from(document.querySelectorAll('label')).find(function (l) { var _a; return (_a = l.textContent) === null || _a === void 0 ? void 0 : _a.startsWith(labelText); });
|
|
199
|
+
if (!label) {
|
|
200
|
+
throw new Error("No field labeled: ".concat(labelText));
|
|
201
|
+
}
|
|
202
|
+
return label.closest('[data-form-section]');
|
|
203
|
+
};
|
|
204
|
+
var sectionIndex = function (labelText) {
|
|
205
|
+
return Array.from(document.querySelectorAll('[data-form-section]')).indexOf(sectionOf(labelText));
|
|
206
|
+
};
|
|
207
|
+
it('groups fields into identity → Content → Details → System, in that order', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
208
|
+
var sections;
|
|
209
|
+
return __generator(this, function (_a) {
|
|
210
|
+
switch (_a.label) {
|
|
211
|
+
case 0: return [4 /*yield*/, mount(new JobTable(), loadedRecord())];
|
|
212
|
+
case 1:
|
|
213
|
+
_a.sent();
|
|
214
|
+
expect(sectionLabels()).toEqual(['Content', 'Details', 'System']);
|
|
215
|
+
// Identity leads (unlabeled), long text/structured land in Content, the rest in Details,
|
|
216
|
+
// server-managed meta last — regardless of the schema's declaration order.
|
|
217
|
+
expect(sectionIndex('Name')).toBe(0);
|
|
218
|
+
expect(sectionOf('Failure message').textContent).toContain('Content');
|
|
219
|
+
expect(sectionOf('Payload').textContent).toContain('Content');
|
|
220
|
+
expect(sectionOf('Status').textContent).toContain('Details');
|
|
221
|
+
expect(sectionOf('Manual').textContent).toContain('Details');
|
|
222
|
+
expect(sectionOf('Created').textContent).toContain('System');
|
|
223
|
+
expect(sectionOf('Updated').textContent).toContain('System');
|
|
224
|
+
sections = document.querySelectorAll('[data-form-section]');
|
|
225
|
+
expect(sectionIndex('Created')).toBe(sections.length - 1);
|
|
226
|
+
return [2 /*return*/];
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}); });
|
|
230
|
+
it('renders the record id in System as a readonly value row', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
231
|
+
var idSection, row;
|
|
232
|
+
return __generator(this, function (_a) {
|
|
233
|
+
switch (_a.label) {
|
|
234
|
+
case 0: return [4 /*yield*/, mount(new JobTable(), loadedRecord())];
|
|
235
|
+
case 1:
|
|
236
|
+
_a.sent();
|
|
237
|
+
idSection = sectionOf('Id');
|
|
238
|
+
expect(idSection.textContent).toContain('System');
|
|
239
|
+
row = idSection.querySelector('[data-readonly-value-row]');
|
|
240
|
+
expect(row).not.toBeNull();
|
|
241
|
+
expect(idSection.textContent).toContain('job-77');
|
|
242
|
+
return [2 /*return*/];
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}); });
|
|
246
|
+
it('ui.formGroup overrides the derivation; an unknown hint becomes its own section after Details', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
247
|
+
return __generator(this, function (_a) {
|
|
248
|
+
switch (_a.label) {
|
|
249
|
+
case 0: return [4 /*yield*/, mount(new HintedJobTable(), loadedRecord())];
|
|
250
|
+
case 1:
|
|
251
|
+
_a.sent();
|
|
252
|
+
// 'status' (a short string that would derive to Details) claims Content by hint.
|
|
253
|
+
expect(sectionOf('Status').textContent).toContain('Content');
|
|
254
|
+
// 'manual' claims a section of its own, humanized, ordered after Details — and with both
|
|
255
|
+
// would-be Details fields re-hinted, Details doesn't render at all (no empty headers).
|
|
256
|
+
expect(sectionLabels()).toEqual(['Content', 'Operations', 'System']);
|
|
257
|
+
expect(sectionOf('Manual').textContent).toContain('Operations');
|
|
258
|
+
return [2 /*return*/];
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}); });
|
|
262
|
+
it('a new-record form (no System meta) stays a single unlabeled section when one group covers it', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
263
|
+
var SimpleTable;
|
|
264
|
+
return __generator(this, function (_a) {
|
|
265
|
+
switch (_a.label) {
|
|
266
|
+
case 0:
|
|
267
|
+
SimpleTable = /** @class */ (function (_super) {
|
|
268
|
+
__extends(SimpleTable, _super);
|
|
269
|
+
function SimpleTable() {
|
|
270
|
+
var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
271
|
+
_this.name = 'admin_section_simple';
|
|
272
|
+
_this.columns = (0, db_1.withRecordColumns)({
|
|
273
|
+
name: new db_1.StringColumn('name'),
|
|
274
|
+
});
|
|
275
|
+
return _this;
|
|
276
|
+
}
|
|
277
|
+
return SimpleTable;
|
|
278
|
+
}(db_1.Table));
|
|
279
|
+
return [4 /*yield*/, mount(new SimpleTable(), undefined)];
|
|
280
|
+
case 1:
|
|
281
|
+
_a.sent();
|
|
282
|
+
expect(document.querySelectorAll('[data-form-section]').length).toBe(1);
|
|
283
|
+
expect(sectionLabels()).toEqual([]);
|
|
284
|
+
return [2 /*return*/];
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}); });
|
|
288
|
+
});
|
|
289
|
+
//# sourceMappingURL=recordFormSections.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordFormSections.test.js","sourceRoot":"","sources":["../../test/recordFormSections.test.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;GAaG;AACH,gDAA0B;AAC1B,kDAA4B;AAC5B,2CAAoD;AACpD,mDAA2C;AAC3C,qDAAgD;AAChD,oCAA4G;AAC5G,wBAAsB;AACtB,qDAAoD;AAEpD,IAAM,aAAa,GAAG;IACpB,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;IACd,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,UAAO,KAAU,EAAE,MAAW;QAAK,sBAAA,MAAM,EAAA;aAAA,CAAC;IAC1D,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,UAAO,KAAU,EAAE,MAAW;QAAK,sBAAA,MAAM,EAAA;aAAA,CAAC;IAC1D,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;QAAY,sBAAA,CAAC,EAAA;aAAA,CAAC;CAC/B,CAAC;AAEF,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,cAAM,OAAA,uBAC5B,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,KACtC,YAAY,EAAE,cAAM,OAAA,aAAa,EAAb,CAAa,IACjC,EAH+B,CAG/B,CAAC,CAAC;AAMJ,UAAU,CAAC,wBAAwB,GAAG,IAAI,CAAC;AAU3C;IAAuB,4BAAU;IAAjC;QAAA,qEAUC;QATQ,UAAI,GAAG,mBAAmB,CAAC;QAC3B,aAAO,GAAG,IAAA,sBAAiB,EAAM;YACtC,wFAAwF;YACxF,MAAM,EAAE,IAAI,iBAAY,CAAC,QAAQ,CAAC;YAClC,cAAc,EAAE,IAAI,iBAAY,CAAC,iBAAiB,EAAE,EAAE,EAAE,IAAI,CAAC;YAC7D,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,MAAM,EAAE,IAAI,kBAAa,CAAC,QAAQ,CAAC;YACnC,OAAO,EAAE,IAAI,iBAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;SAChE,CAAC,CAAC;;IACL,CAAC;IAAD,eAAC;AAAD,CAAC,AAVD,CAAuB,UAAK,GAU3B;AAED,4EAA4E;AAC5E;IAA6B,kCAAU;IAAvC;QAAA,qEASC;QARQ,UAAI,GAAG,0BAA0B,CAAC;QAClC,aAAO,GAAG,IAAA,sBAAiB,EAAM;YACtC,MAAM,EAAE,IAAI,iBAAY,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,CAAC;YACpE,cAAc,EAAE,IAAI,iBAAY,CAAC,iBAAiB,EAAE,EAAE,EAAE,IAAI,CAAC;YAC7D,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,MAAM,EAAE,IAAI,kBAAa,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,CAAC;YACxE,OAAO,EAAE,IAAI,iBAAY,CAAC,SAAS,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;SAChE,CAAC,CAAC;;IACL,CAAC;IAAD,qBAAC;AAAD,CAAC,AATD,CAA6B,UAAK,GASjC;AAED,IAAM,OAAO,GAAG,IAAA,gBAAM,EAAC,0BAA0B,CAAC,CAAC;AACnD,IAAM,OAAO,GAAG,IAAA,gBAAM,EAAC,0BAA0B,CAAC,CAAC;AAEnD,SAAS,YAAY;IACnB,OAAO;QACL,EAAE,EAAE,QAAQ;QACZ,IAAI,EAAE,gBAAgB;QACtB,cAAc,EAAE,UAAU;QAC1B,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE;QACvB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,KAAK;QACb,OAAO,SAAA;QACP,OAAO,SAAA;KACD,CAAC;AACX,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE;IAC9B,IAAI,SAAyB,CAAC;IAC9B,IAAI,IAAU,CAAC;IAEf,UAAU,CAAC;QACT,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,GAAG,IAAA,mBAAU,EAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC;QACR,IAAA,gBAAG,EAAC,cAAM,OAAA,IAAI,CAAC,OAAO,EAAE,EAAd,CAAc,CAAC,CAAC;QAC1B,SAAS,CAAC,MAAM,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,IAAM,KAAK,GAAG,UAAO,KAAiB,EAAE,MAAY;;;wBAClD,qBAAM,IAAA,gBAAG,EAAC;;4BACR,IAAI,CAAC,MAAM,CACT,8BAAC,+BAAY;gCACX,8BAAC,uBAAU,IAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAI,CAC/B,CAChB,CAAC;;;yBACH,CAAC,EAAA;;oBANF,SAME,CAAC;oBACH,qBAAM,IAAA,gBAAG,EAAC;;;4CACR,qBAAM,IAAI,OAAO,CAAC,UAAC,OAAO,IAAK,OAAA,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,EAAtB,CAAsB,CAAC,EAAA;;wCAAtD,SAAsD,CAAC;;;;6BACxD,CAAC,EAAA;;oBAFF,SAEE,CAAC;;;;SACJ,CAAC;IAEF,IAAM,aAAa,GAAG;QACpB,OAAA,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC,CAAC,GAAG,CAAC,UAAC,EAAE,IAAK,OAAA,EAAE,CAAC,WAAW,EAAd,CAAc,CAAC;IAA9F,CAA8F,CAAC;IAEjG,uDAAuD;IACvD,IAAM,SAAS,GAAG,UAAC,SAAiB;QAClC,IAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,UAAC,CAAC,YAAK,OAAA,MAAA,CAAC,CAAC,WAAW,0CAAE,UAAU,CAAC,SAAS,CAAC,CAAA,EAAA,CAAC,CAAC;QAC/G,IAAI,CAAC,KAAK,EAAE;YACV,MAAM,IAAI,KAAK,CAAC,4BAAqB,SAAS,CAAE,CAAC,CAAC;SACnD;QAED,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,CAAE,CAAC;IAC/C,CAAC,CAAC;IAEF,IAAM,YAAY,GAAG,UAAC,SAAiB;QACrC,OAAA,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,CAAgB,CAAC;IAAzG,CAAyG,CAAC;IAE5G,EAAE,CAAC,yEAAyE,EAAE;;;;wBAC5E,qBAAM,KAAK,CAAC,IAAI,QAAQ,EAAE,EAAE,YAAY,EAAE,CAAC,EAAA;;oBAA3C,SAA2C,CAAC;oBAE5C,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;oBAElE,yFAAyF;oBACzF,2EAA2E;oBAC3E,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACrC,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBACtE,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBAC9D,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBAC7D,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBAC7D,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;oBAC7D,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;oBAGvD,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;oBAClE,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;;;;SAC3D,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE;;;;wBAC5D,qBAAM,KAAK,CAAC,IAAI,QAAQ,EAAE,EAAE,YAAY,EAAE,CAAC,EAAA;;oBAA3C,SAA2C,CAAC;oBAEtC,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;oBAClC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;oBAC5C,GAAG,GAAG,SAAS,CAAC,aAAa,CAAC,2BAA2B,CAAE,CAAC;oBAClE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;oBAC3B,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;;;;SACnD,CAAC,CAAC;IAEH,EAAE,CAAC,8FAA8F,EAAE;;;wBACjG,qBAAM,KAAK,CAAC,IAAI,cAAc,EAAE,EAAE,YAAY,EAAE,CAAC,EAAA;;oBAAjD,SAAiD,CAAC;oBAElD,iFAAiF;oBACjF,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;oBAC7D,yFAAyF;oBACzF,uFAAuF;oBACvF,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;oBACrE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;;;;SACjE,CAAC,CAAC;IAEH,EAAE,CAAC,8FAA8F,EAAE;;;;;oBACjG;wBAA0B,+BAAgC;wBAA1D;4BAAA,qEAKC;4BAJQ,UAAI,GAAG,sBAAsB,CAAC;4BAC9B,aAAO,GAAG,IAAA,sBAAiB,EAA4B;gCAC5D,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;6BAC/B,CAAC,CAAC;;wBACL,CAAC;wBAAD,kBAAC;oBAAD,CAAC,AALD,CAA0B,UAAK,GAK9B;oBAED,qBAAM,KAAK,CAAC,IAAI,WAAW,EAAS,EAAE,SAAS,CAAC,EAAA;;oBAAhD,SAAgD,CAAC;oBAEjD,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACxE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;;;;SACrC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -283,16 +283,22 @@ describe('RecordForm structured fields', function () {
|
|
|
283
283
|
}
|
|
284
284
|
});
|
|
285
285
|
}); });
|
|
286
|
-
it('readonly timestamps
|
|
287
|
-
var
|
|
286
|
+
it('readonly timestamps render as a value ROW (no input chrome) — compact stamp with the relative read inline', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
287
|
+
var labels, createdLabel, row;
|
|
288
288
|
return __generator(this, function (_a) {
|
|
289
289
|
switch (_a.label) {
|
|
290
290
|
case 0: return [4 /*yield*/, mount(loadedRecord())];
|
|
291
291
|
case 1:
|
|
292
292
|
_a.sent();
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
expect(
|
|
293
|
+
labels = Array.from(document.body.querySelectorAll('label'));
|
|
294
|
+
createdLabel = labels.find(function (candidate) { var _a; return (_a = candidate.textContent) === null || _a === void 0 ? void 0 : _a.startsWith('Created'); });
|
|
295
|
+
expect(createdLabel).toBeDefined();
|
|
296
|
+
// No control is wired to it: a readonly timestamp is text, not an input.
|
|
297
|
+
expect(createdLabel.htmlFor).toBeFalsy();
|
|
298
|
+
row = createdLabel.closest('[data-form-field-row]').querySelector('[data-readonly-value-row]');
|
|
299
|
+
expect(row).not.toBeNull();
|
|
300
|
+
expect(row.textContent).toContain(created.format('MMM D, YYYY, h:mm A'));
|
|
301
|
+
expect(row.textContent).toContain(created.fromNow());
|
|
296
302
|
return [2 /*return*/];
|
|
297
303
|
}
|
|
298
304
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recordFormStructuredFields.test.js","sourceRoot":"","sources":["../../test/recordFormStructuredFields.test.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"recordFormStructuredFields.test.js","sourceRoot":"","sources":["../../test/recordFormStructuredFields.test.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Db ui components",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"@emotion/styled": "11.11.0",
|
|
27
27
|
"@mui/icons-material": "5.14.11",
|
|
28
28
|
"@mui/material": "5.14.11",
|
|
29
|
-
"@proteinjs/db": "^1.
|
|
29
|
+
"@proteinjs/db": "^1.38.0",
|
|
30
30
|
"@proteinjs/reflection": "^1.2.0",
|
|
31
|
-
"@proteinjs/ui": "^4.
|
|
31
|
+
"@proteinjs/ui": "^4.12.0",
|
|
32
32
|
"@proteinjs/util": "^1.6.1",
|
|
33
33
|
"moment": "2.29.4",
|
|
34
34
|
"react": "18.2.0",
|
|
@@ -56,5 +56,5 @@
|
|
|
56
56
|
},
|
|
57
57
|
"main": "./dist/generated/index.js",
|
|
58
58
|
"types": "./dist/generated/index.d.ts",
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "7c4ebb2ab218d48a276940140161979f0aa31730"
|
|
60
60
|
}
|
package/src/form/RecordForm.tsx
CHANGED
|
@@ -2,7 +2,16 @@ import React from 'react';
|
|
|
2
2
|
import S from 'string';
|
|
3
3
|
import moment from 'moment';
|
|
4
4
|
import { StringUtil, isInstanceOf } from '@proteinjs/util';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Form,
|
|
7
|
+
Fields,
|
|
8
|
+
FieldComponentProps,
|
|
9
|
+
FormFieldSection,
|
|
10
|
+
textField,
|
|
11
|
+
checkboxField,
|
|
12
|
+
dateField,
|
|
13
|
+
FormButtons,
|
|
14
|
+
} from '@proteinjs/ui';
|
|
6
15
|
import {
|
|
7
16
|
Table,
|
|
8
17
|
Record,
|
|
@@ -115,16 +124,17 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
115
124
|
const recordFormCustomization = getRecordFormCustomization(table.name);
|
|
116
125
|
const defaultFieldLayout = fieldLayout();
|
|
117
126
|
const defaultFormButtons = buttons();
|
|
127
|
+
// The customization contract stays FLAT rows (string[] | string[][]) — customizations
|
|
128
|
+
// narrow/reorder without knowing about sections; the final layout is sectioned afterwards.
|
|
129
|
+
const customizedFieldLayout = recordFormCustomization
|
|
130
|
+
? recordFormCustomization.getFieldLayout(record, defaultFieldLayout)
|
|
131
|
+
: defaultFieldLayout;
|
|
118
132
|
|
|
119
133
|
return (
|
|
120
134
|
<Form
|
|
121
135
|
name={S(table.name).humanize().s}
|
|
122
136
|
createFields={createFields()}
|
|
123
|
-
fieldLayout={
|
|
124
|
-
recordFormCustomization
|
|
125
|
-
? recordFormCustomization.getFieldLayout(record, defaultFieldLayout)
|
|
126
|
-
: defaultFieldLayout
|
|
127
|
-
}
|
|
137
|
+
fieldLayout={sectionizeFieldLayout(customizedFieldLayout)}
|
|
128
138
|
buttons={
|
|
129
139
|
recordFormCustomization
|
|
130
140
|
? recordFormCustomization.getFormButtons(record, defaultFormButtons)
|
|
@@ -148,7 +158,12 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
148
158
|
|
|
149
159
|
for (const columnPropertyName in table.columns) {
|
|
150
160
|
const column = getColumn(columnPropertyName);
|
|
151
|
-
if (
|
|
161
|
+
if (
|
|
162
|
+
columnPropertyName == 'name' ||
|
|
163
|
+
columnPropertyName == 'id' ||
|
|
164
|
+
columnPropertyName == 'created' ||
|
|
165
|
+
columnPropertyName == 'updated'
|
|
166
|
+
) {
|
|
152
167
|
continue;
|
|
153
168
|
}
|
|
154
169
|
|
|
@@ -161,6 +176,12 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
if (!isNewRecord) {
|
|
179
|
+
// The System section: id (schema-hidden as a DATA column, but the record's address is
|
|
180
|
+
// exactly what an admin copies off a record form — it renders as a readonly mono row),
|
|
181
|
+
// then the stored timestamps.
|
|
182
|
+
if ((table.columns as any)['id']) {
|
|
183
|
+
columns['id'] = getColumn('id');
|
|
184
|
+
}
|
|
164
185
|
columns['created'] = getColumn('created');
|
|
165
186
|
columns['updated'] = getColumn('updated');
|
|
166
187
|
}
|
|
@@ -233,10 +254,10 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
233
254
|
return customField(name, label, column, fieldRenderer);
|
|
234
255
|
}
|
|
235
256
|
|
|
236
|
-
// Readonly values render as text (
|
|
237
|
-
// ids/timestamps beat a type-specific control the user can't interact with anyway.
|
|
257
|
+
// Readonly values render as text ROWS (no input chrome — @proteinjs/ui's ReadonlyValueRow):
|
|
258
|
+
// copyable ids/timestamps beat a type-specific control the user can't interact with anyway.
|
|
238
259
|
if (isReadonlyField(columnPropertyName, column)) {
|
|
239
|
-
return textField({ name, label, accessibility: { readonly: true } });
|
|
260
|
+
return textField({ name, label, accessibility: { readonly: true }, monospace: columnPropertyName === 'id' });
|
|
240
261
|
}
|
|
241
262
|
|
|
242
263
|
if (isInstanceOf(column, BooleanColumn)) {
|
|
@@ -322,14 +343,75 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
322
343
|
};
|
|
323
344
|
}
|
|
324
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Which form section a column belongs to (the founder's grouping order: identity → content
|
|
348
|
+
* → details/config → system meta last). `column.options.ui.formGroup` overrides the
|
|
349
|
+
* derivation; unknown hint strings become their own titled sections after Details.
|
|
350
|
+
*/
|
|
351
|
+
function sectionForColumn(columnPropertyName: string, column: Column<T, any> | undefined): string {
|
|
352
|
+
if (['id', 'created', 'updated'].includes(columnPropertyName)) {
|
|
353
|
+
return 'system';
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const hint = column?.options?.ui?.formGroup;
|
|
357
|
+
if (hint) {
|
|
358
|
+
return hint;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Custom-rendered fields (service-owned state like user.roles) are config their component
|
|
362
|
+
// presents — Details unless the column hints otherwise, never Content by storage shape.
|
|
363
|
+
if (getFieldRenderer(columnPropertyName)) {
|
|
364
|
+
return 'details';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (columnPropertyName === 'name' || isIdentityColumnName(columnPropertyName)) {
|
|
368
|
+
return 'identity';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
column &&
|
|
373
|
+
(isLongTextColumn(column) ||
|
|
374
|
+
((isInstanceOf(column, ObjectColumn) || isInstanceOf(column, ArrayColumn)) &&
|
|
375
|
+
!isInstanceOf(column, ReferenceArrayColumn)))
|
|
376
|
+
) {
|
|
377
|
+
return 'content';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return 'details';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Identity-named strings promote to the top (same suffix grammar as the table's column pick). */
|
|
384
|
+
function isIdentityColumnName(columnPropertyName: string) {
|
|
385
|
+
const name = columnPropertyName.toLowerCase();
|
|
386
|
+
return name.endsWith('email') || name.endsWith('title') || ['label', 'subject'].includes(name);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** The default flat layout, ordered by section; the customization contract stays flat rows. */
|
|
325
390
|
function fieldLayout(): any {
|
|
326
391
|
const columns = getColumns();
|
|
327
392
|
const layoutColumns = Object.entries(columns).length > 6 ? 2 : 1;
|
|
393
|
+
|
|
394
|
+
// Stable order: identity → content → details → custom-hint sections → system.
|
|
395
|
+
const sectionRank = (section: string) => ({ identity: 0, content: 1, details: 2, system: 4 })[section] ?? 3;
|
|
396
|
+
const ordered = Object.keys(columns).sort((a, b) => {
|
|
397
|
+
const rankDelta = sectionRank(sectionForColumn(a, columns[a])) - sectionRank(sectionForColumn(b, columns[b]));
|
|
398
|
+
if (rankDelta !== 0) {
|
|
399
|
+
return rankDelta;
|
|
400
|
+
}
|
|
401
|
+
// Within a section, keep getColumns() order (name first, then schema order).
|
|
402
|
+
return Object.keys(columns).indexOf(a) - Object.keys(columns).indexOf(b);
|
|
403
|
+
});
|
|
404
|
+
|
|
328
405
|
if (layoutColumns > 1) {
|
|
329
406
|
const layout: (keyof T)[][] = [];
|
|
330
407
|
let lastRowIsSolo = false;
|
|
331
|
-
|
|
408
|
+
let lastSection: string | undefined;
|
|
409
|
+
for (const columnPropertyName of ordered) {
|
|
332
410
|
const column = columns[columnPropertyName];
|
|
411
|
+
const section = sectionForColumn(columnPropertyName, column);
|
|
412
|
+
// Rows never straddle sections — a section boundary always starts a fresh row.
|
|
413
|
+
const sectionChanged = section !== lastSection;
|
|
414
|
+
lastSection = section;
|
|
333
415
|
// Multiline fields (long text / JSON) take a full-width row of their own — half a
|
|
334
416
|
// two-column row squeezes exactly the fields that hold the most text.
|
|
335
417
|
const isMultiline =
|
|
@@ -338,13 +420,20 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
338
420
|
(isLongTextColumn(column) ||
|
|
339
421
|
((isInstanceOf(column, ObjectColumn) || isInstanceOf(column, ArrayColumn)) &&
|
|
340
422
|
!isInstanceOf(column, ReferenceArrayColumn)));
|
|
341
|
-
|
|
423
|
+
// The record's address wants its own line — a uuid halved into a two-column row wraps.
|
|
424
|
+
const isSoloValue = isMultiline || columnPropertyName === 'id';
|
|
425
|
+
if (isSoloValue) {
|
|
342
426
|
layout.push([columnPropertyName as keyof T]);
|
|
343
427
|
lastRowIsSolo = true;
|
|
344
428
|
continue;
|
|
345
429
|
}
|
|
346
430
|
|
|
347
|
-
if (
|
|
431
|
+
if (
|
|
432
|
+
layout.length == 0 ||
|
|
433
|
+
lastRowIsSolo ||
|
|
434
|
+
sectionChanged ||
|
|
435
|
+
layout[layout.length - 1].length >= layoutColumns
|
|
436
|
+
) {
|
|
348
437
|
layout.push([]);
|
|
349
438
|
lastRowIsSolo = false;
|
|
350
439
|
}
|
|
@@ -355,7 +444,47 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
|
|
|
355
444
|
return layout;
|
|
356
445
|
}
|
|
357
446
|
|
|
358
|
-
return
|
|
447
|
+
return ordered as (keyof T)[];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Wrap a FLAT layout (default or customization-returned) into the quiet form sections the
|
|
452
|
+
* base Form renders. Each row maps to its first field's section; contiguous runs merge, so a
|
|
453
|
+
* customization's reordering stays honest (an interleaved order yields repeated sections
|
|
454
|
+
* rather than silently re-sorting the fields it chose). A single-section result stays
|
|
455
|
+
* unlabeled — one group needs no header.
|
|
456
|
+
*/
|
|
457
|
+
function sectionizeFieldLayout(layout: string[] | string[][]): FormFieldSection<Fields>[] {
|
|
458
|
+
const sectionLabels: { [section: string]: string | undefined } = {
|
|
459
|
+
identity: undefined,
|
|
460
|
+
content: 'Content',
|
|
461
|
+
details: 'Details',
|
|
462
|
+
system: 'System',
|
|
463
|
+
};
|
|
464
|
+
const rows = (layout as (string | string[])[]).map((entry) => (Array.isArray(entry) ? entry : [entry]));
|
|
465
|
+
const sections: { section: string; fields: string[][] }[] = [];
|
|
466
|
+
for (const row of rows) {
|
|
467
|
+
if (row.length === 0) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const section = sectionForColumn(row[0], getColumn(row[0]));
|
|
472
|
+
const last = sections[sections.length - 1];
|
|
473
|
+
if (last && last.section === section) {
|
|
474
|
+
last.fields.push(row);
|
|
475
|
+
} else {
|
|
476
|
+
sections.push({ section, fields: [row] });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (sections.length <= 1) {
|
|
481
|
+
return sections.map(({ fields }) => ({ fields }) as FormFieldSection<Fields>);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return sections.map(({ section, fields }) => ({
|
|
485
|
+
label: section in sectionLabels ? sectionLabels[section] : StringUtil.humanizeCamel(section),
|
|
486
|
+
fields,
|
|
487
|
+
})) as FormFieldSection<Fields>[];
|
|
359
488
|
}
|
|
360
489
|
|
|
361
490
|
function getFieldValue(columnPropertyName: string, fieldValue: unknown) {
|
|
@@ -94,7 +94,9 @@ export function ReferenceCellValue({ tableName, id }: { tableName?: string; id?:
|
|
|
94
94
|
event.preventDefault();
|
|
95
95
|
navigate(url);
|
|
96
96
|
}}
|
|
97
|
-
|
|
97
|
+
// Data ink, link affordance: a table of primary-colored names competes with the row's
|
|
98
|
+
// identity (round 2's fewer-competing-inks pass) — the hover underline still says door.
|
|
99
|
+
sx={{ whiteSpace: 'nowrap', color: 'text.primary' }}
|
|
98
100
|
>
|
|
99
101
|
{name !== null ? (
|
|
100
102
|
<Typography variant='body2' component='span'>
|
|
@@ -170,13 +170,15 @@ describe('RecordForm', () => {
|
|
|
170
170
|
expect(dueDate.type).toBe('date');
|
|
171
171
|
expect(dueDate.value).toBe('2026-08-10');
|
|
172
172
|
|
|
173
|
-
// Readonly fields render as
|
|
174
|
-
//
|
|
173
|
+
// Readonly fields render as value ROWS (round 2): selectable text with a copy control
|
|
174
|
+
// and no input chrome at all.
|
|
175
175
|
for (const labelText of ['Created', 'Updated', 'Archived at']) {
|
|
176
|
-
const
|
|
177
|
-
expect(
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
const label = Array.from(document.querySelectorAll('label')).find((l) => l.textContent?.startsWith(labelText))!;
|
|
177
|
+
expect(label).toBeDefined();
|
|
178
|
+
// Scope to the field's own shell: two readonly fields can share one two-column row.
|
|
179
|
+
const row = label.parentElement!.parentElement!.querySelector('[data-readonly-value-row]')!;
|
|
180
|
+
expect(row).not.toBeNull();
|
|
181
|
+
expect(row.querySelector(`button[aria-label="Copy ${labelText}"]`)).not.toBeNull();
|
|
180
182
|
}
|
|
181
183
|
});
|
|
182
184
|
|