@proteinjs/db-ui 1.11.3 → 1.12.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.
@@ -0,0 +1,323 @@
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 __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
65
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
66
+ if (ar || !(i in from)) {
67
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
68
+ ar[i] = from[i];
69
+ }
70
+ }
71
+ return to.concat(ar || Array.prototype.slice.call(from));
72
+ };
73
+ var __importDefault = (this && this.__importDefault) || function (mod) {
74
+ return (mod && mod.__esModule) ? mod : { "default": mod };
75
+ };
76
+ Object.defineProperty(exports, "__esModule", { value: true });
77
+ /**
78
+ * @jest-environment jsdom
79
+ *
80
+ * Custom field components on the record form (`RecordFormCustomization.getFieldRenderer`).
81
+ *
82
+ * A customization can take over a field's slot with its own component. The component OWNS the
83
+ * field: it presents the stored value and routes edits through a service of its own, then calls
84
+ * `reload` so the slot shows the stored truth. Consequences pinned here:
85
+ * - the component renders in the field's slot instead of the default control, and declaring a
86
+ * renderer surfaces a column the default form hides (ArrayColumn is ui.hidden by default —
87
+ * the reason user.roles never appeared on the user form);
88
+ * - the form's save payload never carries a custom-rendered field (its writes are the
89
+ * component's, through its service — a form save that echoed the value back would either be
90
+ * refused as a protected-column write or clobber the service's state);
91
+ * - `reload` re-reads the record through the db service and re-renders the slot;
92
+ * - renderers are not consulted on the new-record form: a record that doesn't exist yet has no
93
+ * stored state to present or service to write through.
94
+ */
95
+ var react_1 = __importDefault(require("react"));
96
+ var client_1 = require("react-dom/client");
97
+ var test_utils_1 = require("react-dom/test-utils");
98
+ var react_router_dom_1 = require("react-router-dom");
99
+ var db_1 = require("@proteinjs/db");
100
+ require("../generated");
101
+ var RecordForm_1 = require("../src/form/RecordForm");
102
+ var RecordFormCustomization_1 = require("../src/form/RecordFormCustomization");
103
+ var DocTable = /** @class */ (function (_super) {
104
+ __extends(DocTable, _super);
105
+ function DocTable() {
106
+ var _this = _super !== null && _super.apply(this, arguments) || this;
107
+ _this.name = 'field_renderer_test_doc';
108
+ _this.columns = (0, db_1.withRecordColumns)({
109
+ title: new db_1.StringColumn('title'),
110
+ // ArrayColumn is ui.hidden by default; only the renderer surfaces it
111
+ labels: new db_1.ArrayColumn('labels'),
112
+ });
113
+ return _this;
114
+ }
115
+ return DocTable;
116
+ }(db_1.Table));
117
+ /** The stored row the (fake) labels service writes and the db service reads back. */
118
+ var stored;
119
+ var mockLabelsService = {
120
+ addLabel: jest.fn(function (docId, label) { return __awaiter(void 0, void 0, void 0, function () {
121
+ return __generator(this, function (_a) {
122
+ stored = __assign(__assign({}, stored), { labels: __spreadArray(__spreadArray([], stored.labels, true), [label], false) });
123
+ return [2 /*return*/];
124
+ });
125
+ }); }),
126
+ };
127
+ var mockDbService = {
128
+ get: jest.fn(function (table, query) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
129
+ return [2 /*return*/, stored];
130
+ }); }); }),
131
+ insert: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
132
+ return [2 /*return*/, record];
133
+ }); }); }),
134
+ update: jest.fn(function (table, record) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
135
+ return [2 /*return*/, record];
136
+ }); }); }),
137
+ delete: jest.fn(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
138
+ return [2 /*return*/, 1];
139
+ }); }); }),
140
+ };
141
+ jest.mock('@proteinjs/db', function () { return (__assign(__assign({}, jest.requireActual('@proteinjs/db')), { getDbService: function () { return mockDbService; } })); });
142
+ function LabelsField(_a) {
143
+ var _this = this;
144
+ var record = _a.record, value = _a.value, reload = _a.reload, label = _a.label;
145
+ return (react_1.default.createElement("div", { "data-field-renderer": 'labels' },
146
+ react_1.default.createElement("span", null, label),
147
+ value.map(function (item) { return (react_1.default.createElement("span", { key: item, "data-label-chip": true }, item)); }),
148
+ react_1.default.createElement("button", { type: 'button', onClick: function () { return __awaiter(_this, void 0, void 0, function () {
149
+ return __generator(this, function (_a) {
150
+ switch (_a.label) {
151
+ case 0: return [4 /*yield*/, mockLabelsService.addLabel(record.id, 'c')];
152
+ case 1:
153
+ _a.sent();
154
+ return [4 /*yield*/, reload()];
155
+ case 2:
156
+ _a.sent();
157
+ return [2 /*return*/];
158
+ }
159
+ });
160
+ }); } }, "Add label")));
161
+ }
162
+ var DocRecordFormCustomization = /** @class */ (function (_super) {
163
+ __extends(DocRecordFormCustomization, _super);
164
+ function DocRecordFormCustomization() {
165
+ var _this = _super !== null && _super.apply(this, arguments) || this;
166
+ _this.table = new DocTable();
167
+ return _this;
168
+ }
169
+ DocRecordFormCustomization.prototype.getFieldRenderer = function (fieldName, record) {
170
+ return fieldName === 'labels' ? LabelsField : undefined;
171
+ };
172
+ return DocRecordFormCustomization;
173
+ }(RecordFormCustomization_1.RecordFormCustomization));
174
+ // Stands in for the SourceRepository registration a real customization gets from reflection-build.
175
+ jest.mock('../src/form/RecordFormCustomization', function () { return (__assign(__assign({}, jest.requireActual('../src/form/RecordFormCustomization')), { getRecordFormCustomization: function (tableName) {
176
+ return tableName === 'field_renderer_test_doc' ? new DocRecordFormCustomization() : undefined;
177
+ } })); });
178
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
179
+ describe('RecordForm custom field renderers', function () {
180
+ var container;
181
+ var root;
182
+ beforeEach(function () {
183
+ jest.clearAllMocks();
184
+ stored = { id: 'doc-1', title: 'Doc', labels: ['a', 'b'] };
185
+ container = document.createElement('div');
186
+ document.body.appendChild(container);
187
+ root = (0, client_1.createRoot)(container);
188
+ });
189
+ afterEach(function () {
190
+ (0, test_utils_1.act)(function () {
191
+ root.unmount();
192
+ });
193
+ container.remove();
194
+ });
195
+ var mount = function (record) { return __awaiter(void 0, void 0, void 0, function () {
196
+ return __generator(this, function (_a) {
197
+ switch (_a.label) {
198
+ case 0: return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
199
+ return __generator(this, function (_a) {
200
+ root.render(react_1.default.createElement(react_router_dom_1.MemoryRouter, null,
201
+ react_1.default.createElement(RecordForm_1.RecordForm, { table: new DocTable(), record: record })));
202
+ return [2 /*return*/];
203
+ });
204
+ }); })];
205
+ case 1:
206
+ _a.sent();
207
+ return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
208
+ return __generator(this, function (_a) {
209
+ switch (_a.label) {
210
+ case 0: return [4 /*yield*/, Promise.resolve()];
211
+ case 1:
212
+ _a.sent();
213
+ return [2 /*return*/];
214
+ }
215
+ });
216
+ }); })];
217
+ case 2:
218
+ _a.sent();
219
+ return [2 /*return*/];
220
+ }
221
+ });
222
+ }); };
223
+ var findButton = function (name) {
224
+ var button = Array.from(document.querySelectorAll('button')).find(function (b) { return b.textContent === name; });
225
+ if (!button) {
226
+ throw new Error("Button not rendered: ".concat(name));
227
+ }
228
+ return button;
229
+ };
230
+ var click = function (element) { return __awaiter(void 0, void 0, void 0, function () {
231
+ return __generator(this, function (_a) {
232
+ switch (_a.label) {
233
+ case 0: return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
234
+ return __generator(this, function (_a) {
235
+ element.dispatchEvent(new MouseEvent('click', { bubbles: true }));
236
+ return [2 /*return*/];
237
+ });
238
+ }); })];
239
+ case 1:
240
+ _a.sent();
241
+ return [4 /*yield*/, (0, test_utils_1.act)(function () { return __awaiter(void 0, void 0, void 0, function () {
242
+ return __generator(this, function (_a) {
243
+ switch (_a.label) {
244
+ case 0: return [4 /*yield*/, Promise.resolve()];
245
+ case 1:
246
+ _a.sent();
247
+ return [2 /*return*/];
248
+ }
249
+ });
250
+ }); })];
251
+ case 2:
252
+ _a.sent();
253
+ return [2 /*return*/];
254
+ }
255
+ });
256
+ }); };
257
+ var chips = function () { return Array.from(document.querySelectorAll('[data-label-chip]')).map(function (chip) { return chip.textContent; }); };
258
+ var labelsInput = function () { return Array.from(document.querySelectorAll('label')).find(function (l) { return l.textContent === 'Labels'; }); };
259
+ it('renders the customization component in the field slot, surfacing a column the default form hides', function () { return __awaiter(void 0, void 0, void 0, function () {
260
+ return __generator(this, function (_a) {
261
+ switch (_a.label) {
262
+ case 0: return [4 /*yield*/, mount(__assign({}, stored))];
263
+ case 1:
264
+ _a.sent();
265
+ expect(document.querySelector('[data-field-renderer="labels"]')).not.toBeNull();
266
+ expect(chips()).toEqual(['a', 'b']);
267
+ // The default control is replaced, not doubled: no text input for the field
268
+ expect(labelsInput()).toBeUndefined();
269
+ // Default controls still render for the other columns
270
+ expect(Array.from(document.querySelectorAll('label')).some(function (l) { var _a; return (_a = l.textContent) === null || _a === void 0 ? void 0 : _a.startsWith('Title'); })).toBe(true);
271
+ return [2 /*return*/];
272
+ }
273
+ });
274
+ }); });
275
+ it('never carries a custom-rendered field in the save payload', function () { return __awaiter(void 0, void 0, void 0, function () {
276
+ var payload;
277
+ return __generator(this, function (_a) {
278
+ switch (_a.label) {
279
+ case 0: return [4 /*yield*/, mount(__assign({}, stored))];
280
+ case 1:
281
+ _a.sent();
282
+ return [4 /*yield*/, click(findButton('Save'))];
283
+ case 2:
284
+ _a.sent();
285
+ expect(mockDbService.update).toHaveBeenCalledTimes(1);
286
+ payload = mockDbService.update.mock.calls[0][1];
287
+ expect(payload.title).toBe('Doc');
288
+ expect('labels' in payload).toBe(false);
289
+ return [2 /*return*/];
290
+ }
291
+ });
292
+ }); });
293
+ it('reload re-reads the record so the slot shows the stored truth after a service write', function () { return __awaiter(void 0, void 0, void 0, function () {
294
+ return __generator(this, function (_a) {
295
+ switch (_a.label) {
296
+ case 0: return [4 /*yield*/, mount(__assign({}, stored))];
297
+ case 1:
298
+ _a.sent();
299
+ return [4 /*yield*/, click(findButton('Add label'))];
300
+ case 2:
301
+ _a.sent();
302
+ expect(mockLabelsService.addLabel).toHaveBeenCalledWith('doc-1', 'c');
303
+ expect(mockDbService.get).toHaveBeenCalledTimes(1);
304
+ expect(mockDbService.get.mock.calls[0][1]).toEqual({ id: 'doc-1' });
305
+ expect(chips()).toEqual(['a', 'b', 'c']);
306
+ return [2 /*return*/];
307
+ }
308
+ });
309
+ }); });
310
+ it('does not consult renderers on the new-record form; hidden columns stay hidden there', function () { return __awaiter(void 0, void 0, void 0, function () {
311
+ return __generator(this, function (_a) {
312
+ switch (_a.label) {
313
+ case 0: return [4 /*yield*/, mount(undefined)];
314
+ case 1:
315
+ _a.sent();
316
+ expect(document.querySelector('[data-field-renderer="labels"]')).toBeNull();
317
+ expect(labelsInput()).toBeUndefined();
318
+ return [2 /*return*/];
319
+ }
320
+ });
321
+ }); });
322
+ });
323
+ //# sourceMappingURL=recordFormFieldRenderers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordFormFieldRenderers.test.js","sourceRoot":"","sources":["../../test/recordFormFieldRenderers.test.tsx"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import '@proteinjs/ui';
6
6
  /** Generate Source Graph */
7
7
 
8
8
  const sourceGraph =
9
- '{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-ui/QueryCursorLoader","value":{"packageName":"@proteinjs/db-ui","name":"QueryCursorLoader","filePath":"src/table/QueryCursorLoader.ts","qualifiedName":"@proteinjs/db-ui/QueryCursorLoader","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"reactQueryKeys","type":{"packageName":"@proteinjs/ui","name":"ReactQueryKeys","filePath":null,"qualifiedName":"@proteinjs/ui/ReactQueryKeys","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"pager","type":{"packageName":"","name":"CursorWindowPager<T>","filePath":null,"qualifiedName":"/CursorWindowPager<T>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"loadWindow","returnType":{"packageName":"","name":"Promise<CursorWindow<T>>","filePath":null,"qualifiedName":"/Promise<CursorWindow<T>>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"cursor","type":{"packageName":"","name":"CursorValue | null","filePath":null,"qualifiedName":"/CursorValue | null","typeParameters":null,"directParents":null}},{"name":"windowSize","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]}],"typeParameters":["/T extends Record"],"directParentInterfaces":[{"packageName":"@proteinjs/ui","name":"CursorLoader","filePath":null,"qualifiedName":"@proteinjs/ui/CursorLoader","properties":[],"methods":[],"typeParameters":["/T"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/ui/CursorLoader"},{"v":"@proteinjs/db-ui/QueryTableLoader","value":{"packageName":"@proteinjs/db-ui","name":"QueryTableLoader","filePath":"src/table/QueryTableLoader.ts","qualifiedName":"@proteinjs/db-ui/QueryTableLoader","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"rowCountQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"paginationQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"reactQueryKeys","type":{"packageName":"","name":"TableLoader<T>[\'reactQueryKeys\']","filePath":null,"qualifiedName":"/TableLoader<T>[\'reactQueryKeys\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"table","type":{"packageName":"","name":"Table<T>","filePath":null,"qualifiedName":"/Table<T>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"query","type":{"packageName":"","name":"Query<T>","filePath":null,"qualifiedName":"/Query<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"sort","type":{"packageName":"","name":"SortCriteria<T>[]","filePath":null,"qualifiedName":"/SortCriteria<T>[]","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"queryOptions","type":{"packageName":"","name":"QueryOptions<T>","filePath":null,"qualifiedName":"/QueryOptions<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"load","returnType":{"packageName":"","name":"Promise<RowWindow<T>>","filePath":null,"qualifiedName":"/Promise<RowWindow<T>>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"startIndex","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}},{"name":"endIndex","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}},{"name":"skipRowCount","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}}]}],"typeParameters":["/T extends Record"],"directParentInterfaces":[{"packageName":"@proteinjs/ui","name":"TableLoader","filePath":null,"qualifiedName":"@proteinjs/ui/TableLoader","properties":[],"methods":[],"typeParameters":["/T"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/ui/TableLoader"},{"v":"@proteinjs/db-ui/RecordFormCustomization","value":{"packageName":"@proteinjs/db-ui","name":"RecordFormCustomization","filePath":"src/form/RecordFormCustomization.tsx","qualifiedName":"@proteinjs/db-ui/RecordFormCustomization","isAbstract":true,"isStatic":false,"visibility":"public","properties":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getFormButtons","returnType":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"record","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}},{"name":"defaultFormButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]},{"name":"getFieldLayout","returnType":{"packageName":"","name":"string[] | string[][]","filePath":null,"qualifiedName":"/string[] | string[][]","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"record","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}},{"name":"defaultFieldLayout","type":{"packageName":"","name":"string[] | string[][]","filePath":null,"qualifiedName":"/string[] | string[][]","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/reflection/Loadable"},{"v":"@proteinjs/db-ui/hashGeneratorPage","value":{"packageName":"@proteinjs/db-ui","name":"hashGeneratorPage","filePath":"src/pages/HashGeneratorPage.tsx","qualifiedName":"@proteinjs/db-ui/hashGeneratorPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/ui/Page"},{"v":"@proteinjs/db-ui/recordFormPage","value":{"packageName":"@proteinjs/db-ui","name":"recordFormPage","filePath":"src/pages/RecordFormPage.tsx","qualifiedName":"@proteinjs/db-ui/recordFormPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/recordTablePage","value":{"packageName":"@proteinjs/db-ui","name":"recordTablePage","filePath":"src/pages/RecordTablePage.tsx","qualifiedName":"@proteinjs/db-ui/recordTablePage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/tablesPage","value":{"packageName":"@proteinjs/db-ui","name":"tablesPage","filePath":"src/pages/TablesPage.tsx","qualifiedName":"@proteinjs/db-ui/tablesPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/uuidGeneratorPage","value":{"packageName":"@proteinjs/db-ui","name":"uuidGeneratorPage","filePath":"src/pages/UuidGeneratorPage.tsx","qualifiedName":"@proteinjs/db-ui/uuidGeneratorPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/SpecificTableProps"},{"v":"@proteinjs/db-ui/MigrationRecordFormCustomization","value":{"packageName":"@proteinjs/db-ui","name":"MigrationRecordFormCustomization","filePath":"src/form/customizations/MigrationRecordFormCustomization.tsx","qualifiedName":"@proteinjs/db-ui/MigrationRecordFormCustomization","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"table","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getFormButtons","returnType":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"migration","type":{"packageName":"@proteinjs/db","name":"Migration","filePath":null,"qualifiedName":"@proteinjs/db/Migration","typeParameters":null,"directParents":null}},{"name":"defaultFormButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]},{"name":"setRetired","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"migration","type":{"packageName":"@proteinjs/db","name":"Migration","filePath":null,"qualifiedName":"@proteinjs/db/Migration","typeParameters":null,"directParents":null}},{"name":"retired","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}},{"name":"fields","type":{"packageName":"@proteinjs/ui","name":"Fields","filePath":null,"qualifiedName":"@proteinjs/ui/Fields","typeParameters":null,"directParents":null}},{"name":"formButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db-ui","name":"RecordFormCustomization","filePath":null,"qualifiedName":"@proteinjs/db-ui/RecordFormCustomization","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}}],"edges":[{"v":"@proteinjs/db-ui/QueryCursorLoader","w":"@proteinjs/ui/CursorLoader","value":"implements interface"},{"v":"@proteinjs/db-ui/QueryTableLoader","w":"@proteinjs/ui/TableLoader","value":"implements interface"},{"v":"@proteinjs/db-ui/RecordFormCustomization","w":"@proteinjs/reflection/Loadable","value":"implements interface"},{"v":"@proteinjs/db-ui/hashGeneratorPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/recordFormPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/recordTablePage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/tablesPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/uuidGeneratorPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/MigrationRecordFormCustomization","w":"@proteinjs/db-ui/RecordFormCustomization","value":"extends class"}]}';
9
+ '{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-ui/QueryCursorLoader","value":{"packageName":"@proteinjs/db-ui","name":"QueryCursorLoader","filePath":"src/table/QueryCursorLoader.ts","qualifiedName":"@proteinjs/db-ui/QueryCursorLoader","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"reactQueryKeys","type":{"packageName":"@proteinjs/ui","name":"ReactQueryKeys","filePath":null,"qualifiedName":"@proteinjs/ui/ReactQueryKeys","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"pager","type":{"packageName":"","name":"CursorWindowPager<T>","filePath":null,"qualifiedName":"/CursorWindowPager<T>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"loadWindow","returnType":{"packageName":"","name":"Promise<CursorWindow<T>>","filePath":null,"qualifiedName":"/Promise<CursorWindow<T>>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"cursor","type":{"packageName":"","name":"CursorValue | null","filePath":null,"qualifiedName":"/CursorValue | null","typeParameters":null,"directParents":null}},{"name":"windowSize","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]}],"typeParameters":["/T extends Record"],"directParentInterfaces":[{"packageName":"@proteinjs/ui","name":"CursorLoader","filePath":null,"qualifiedName":"@proteinjs/ui/CursorLoader","properties":[],"methods":[],"typeParameters":["/T"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/ui/CursorLoader"},{"v":"@proteinjs/db-ui/QueryTableLoader","value":{"packageName":"@proteinjs/db-ui","name":"QueryTableLoader","filePath":"src/table/QueryTableLoader.ts","qualifiedName":"@proteinjs/db-ui/QueryTableLoader","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"rowCountQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"paginationQb","type":{"packageName":"","name":"QueryBuilder<T>","filePath":null,"qualifiedName":"/QueryBuilder<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"reactQueryKeys","type":{"packageName":"","name":"TableLoader<T>[\'reactQueryKeys\']","filePath":null,"qualifiedName":"/TableLoader<T>[\'reactQueryKeys\']","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"table","type":{"packageName":"","name":"Table<T>","filePath":null,"qualifiedName":"/Table<T>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"query","type":{"packageName":"","name":"Query<T>","filePath":null,"qualifiedName":"/Query<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"sort","type":{"packageName":"","name":"SortCriteria<T>[]","filePath":null,"qualifiedName":"/SortCriteria<T>[]","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"queryOptions","type":{"packageName":"","name":"QueryOptions<T>","filePath":null,"qualifiedName":"/QueryOptions<T>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"load","returnType":{"packageName":"","name":"Promise<RowWindow<T>>","filePath":null,"qualifiedName":"/Promise<RowWindow<T>>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"startIndex","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}},{"name":"endIndex","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}},{"name":"skipRowCount","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}}]}],"typeParameters":["/T extends Record"],"directParentInterfaces":[{"packageName":"@proteinjs/ui","name":"TableLoader","filePath":null,"qualifiedName":"@proteinjs/ui/TableLoader","properties":[],"methods":[],"typeParameters":["/T"],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/ui/TableLoader"},{"v":"/React.ComponentType"},{"v":"@proteinjs/db-ui/RecordFormCustomization","value":{"packageName":"@proteinjs/db-ui","name":"RecordFormCustomization","filePath":"src/form/RecordFormCustomization.tsx","qualifiedName":"@proteinjs/db-ui/RecordFormCustomization","isAbstract":true,"isStatic":false,"visibility":"public","properties":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getFormButtons","returnType":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"record","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}},{"name":"defaultFormButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]},{"name":"getFieldLayout","returnType":{"packageName":"","name":"string[] | string[][]","filePath":null,"qualifiedName":"/string[] | string[][]","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"record","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}},{"name":"defaultFieldLayout","type":{"packageName":"","name":"string[] | string[][]","filePath":null,"qualifiedName":"/string[] | string[][]","typeParameters":null,"directParents":null}}]},{"name":"getFieldRenderer","returnType":{"packageName":"","name":"RecordFormFieldRenderer | undefined","filePath":null,"qualifiedName":"/RecordFormFieldRenderer | undefined","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fieldName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"record","type":{"packageName":"","name":"any","filePath":null,"qualifiedName":"/any","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/reflection","name":"Loadable","filePath":null,"qualifiedName":"@proteinjs/reflection/Loadable","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/reflection/Loadable"},{"v":"@proteinjs/db-ui/hashGeneratorPage","value":{"packageName":"@proteinjs/db-ui","name":"hashGeneratorPage","filePath":"src/pages/HashGeneratorPage.tsx","qualifiedName":"@proteinjs/db-ui/hashGeneratorPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/ui/Page"},{"v":"@proteinjs/db-ui/recordFormPage","value":{"packageName":"@proteinjs/db-ui","name":"recordFormPage","filePath":"src/pages/RecordFormPage.tsx","qualifiedName":"@proteinjs/db-ui/recordFormPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/recordTablePage","value":{"packageName":"@proteinjs/db-ui","name":"recordTablePage","filePath":"src/pages/RecordTablePage.tsx","qualifiedName":"@proteinjs/db-ui/recordTablePage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/tablesPage","value":{"packageName":"@proteinjs/db-ui","name":"tablesPage","filePath":"src/pages/TablesPage.tsx","qualifiedName":"@proteinjs/db-ui/tablesPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/uuidGeneratorPage","value":{"packageName":"@proteinjs/db-ui","name":"uuidGeneratorPage","filePath":"src/pages/UuidGeneratorPage.tsx","qualifiedName":"@proteinjs/db-ui/uuidGeneratorPage","type":{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":[{"packageName":"@proteinjs/ui","name":"Page","filePath":null,"qualifiedName":"@proteinjs/ui/Page","typeParameters":[],"directParents":null}]},"isExported":true,"isConst":true,"sourceType":0}},{"v":"@proteinjs/db-ui/SpecificTableProps"},{"v":"@proteinjs/db-ui/MigrationRecordFormCustomization","value":{"packageName":"@proteinjs/db-ui","name":"MigrationRecordFormCustomization","filePath":"src/form/customizations/MigrationRecordFormCustomization.tsx","qualifiedName":"@proteinjs/db-ui/MigrationRecordFormCustomization","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"table","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getFormButtons","returnType":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"migration","type":{"packageName":"@proteinjs/db","name":"Migration","filePath":null,"qualifiedName":"@proteinjs/db/Migration","typeParameters":null,"directParents":null}},{"name":"defaultFormButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]},{"name":"setRetired","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"migration","type":{"packageName":"@proteinjs/db","name":"Migration","filePath":null,"qualifiedName":"@proteinjs/db/Migration","typeParameters":null,"directParents":null}},{"name":"retired","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}},{"name":"fields","type":{"packageName":"@proteinjs/ui","name":"Fields","filePath":null,"qualifiedName":"@proteinjs/ui/Fields","typeParameters":null,"directParents":null}},{"name":"formButtons","type":{"packageName":"","name":"FormButtons<any>","filePath":null,"qualifiedName":"/FormButtons<any>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db-ui","name":"RecordFormCustomization","filePath":null,"qualifiedName":"@proteinjs/db-ui/RecordFormCustomization","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}}],"edges":[{"v":"@proteinjs/db-ui/QueryCursorLoader","w":"@proteinjs/ui/CursorLoader","value":"implements interface"},{"v":"@proteinjs/db-ui/QueryTableLoader","w":"@proteinjs/ui/TableLoader","value":"implements interface"},{"v":"@proteinjs/db-ui/RecordFormCustomization","w":"@proteinjs/reflection/Loadable","value":"implements interface"},{"v":"@proteinjs/db-ui/hashGeneratorPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/recordFormPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/recordTablePage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/tablesPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/uuidGeneratorPage","w":"@proteinjs/ui/Page","value":"has type"},{"v":"@proteinjs/db-ui/MigrationRecordFormCustomization","w":"@proteinjs/db-ui/RecordFormCustomization","value":"extends class"}]}';
10
10
 
11
11
  /** Generate Source Links */
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db-ui",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
4
4
  "description": "Db ui components",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -55,5 +55,5 @@
55
55
  },
56
56
  "main": "./dist/generated/index.js",
57
57
  "types": "./dist/generated/index.d.ts",
58
- "gitHead": "521d5d1ca2f86f0ed2bef0309b0dde30506f132b"
58
+ "gitHead": "06ddfa575d927114076efa181555521087cf3625"
59
59
  }
@@ -2,7 +2,7 @@ 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 { Form, Fields, textField, checkboxField, dateField, FormButtons } from '@proteinjs/ui';
5
+ import { Form, Fields, FieldComponentProps, textField, checkboxField, dateField, FormButtons } from '@proteinjs/ui';
6
6
  import {
7
7
  Table,
8
8
  Record,
@@ -18,7 +18,7 @@ import {
18
18
  } from '@proteinjs/db';
19
19
  import { recordTableLink } from '../pages/RecordTablePage';
20
20
  import { recordFormLink } from '../pages/RecordFormPage';
21
- import { getRecordFormCustomization } from './RecordFormCustomization';
21
+ import { getRecordFormCustomization, RecordFormFieldRenderer } from './RecordFormCustomization';
22
22
 
23
23
  export type RecordFormProps<T extends Record> = {
24
24
  table: Table<T>;
@@ -149,7 +149,8 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
149
149
  continue;
150
150
  }
151
151
 
152
- if (column.options?.ui?.hidden) {
152
+ // A customization's field component surfaces a column the default form hides
153
+ if (column.options?.ui?.hidden && !getFieldRenderer(columnPropertyName)) {
153
154
  continue;
154
155
  }
155
156
 
@@ -192,11 +193,29 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
192
193
  );
193
194
  }
194
195
 
196
+ /**
197
+ * A customization's component for the field, if it declared one. Only existing records have
198
+ * stored state to present, so the new-record form never consults renderers (see
199
+ * `RecordFormCustomization.getFieldRenderer`).
200
+ */
201
+ function getFieldRenderer(columnPropertyName: string): RecordFormFieldRenderer<T> | undefined {
202
+ if (isNewRecord || !recordFormCustomization) {
203
+ return undefined;
204
+ }
205
+
206
+ return recordFormCustomization.getFieldRenderer(columnPropertyName, record);
207
+ }
208
+
195
209
  /** Pick the field control that tells the truth about the column's type. */
196
210
  function createField(columnPropertyName: string, column: Column<T, any>) {
197
211
  const name = columnPropertyName;
198
212
  const label = StringUtil.humanizeCamel(columnPropertyName);
199
213
 
214
+ const fieldRenderer = getFieldRenderer(columnPropertyName);
215
+ if (fieldRenderer) {
216
+ return customField(name, label, column, fieldRenderer);
217
+ }
218
+
200
219
  // Readonly values render as text (a native date input isn't text-selectable): copyable
201
220
  // ids/timestamps beat a type-specific control the user can't interact with anyway.
202
221
  if (isReadonlyField(columnPropertyName, column)) {
@@ -242,6 +261,38 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
242
261
  return textField({ name, label });
243
262
  }
244
263
 
264
+ /**
265
+ * The slot a customization's component renders in. The component reads its value from the
266
+ * loaded record (not the form's field value), so one `reload` refreshes every custom field:
267
+ * reload re-reads the record in place and reports the change through the form's own onChange,
268
+ * which is what re-renders the form.
269
+ */
270
+ function customField(name: string, label: string, column: Column<T, any>, FieldRenderer: RecordFormFieldRenderer<T>) {
271
+ const loadedRecord = record as T;
272
+ return {
273
+ field: { name, label },
274
+ component: ({ field, onChange }: FieldComponentProps<any, Fields>) => (
275
+ <FieldRenderer
276
+ table={table}
277
+ column={column}
278
+ fieldName={name}
279
+ label={label}
280
+ record={loadedRecord}
281
+ value={(loadedRecord as any)[name]}
282
+ reload={async () => {
283
+ const fresh = await getDbService().get(table, { id: loadedRecord.id } as any);
284
+ if (!fresh) {
285
+ throw new Error(`${S(table.name).humanize().s} no longer exists`);
286
+ }
287
+
288
+ Object.assign(loadedRecord, fresh);
289
+ await onChange(field, (fresh as any)[name], () => {});
290
+ }}
291
+ />
292
+ ),
293
+ };
294
+ }
295
+
245
296
  function fieldLayout(): any {
246
297
  const columns = getColumns();
247
298
  const layoutColumns = Object.entries(columns).length > 6 ? 2 : 1;
@@ -298,6 +349,23 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
298
349
  return fieldValue;
299
350
  }
300
351
 
352
+ /**
353
+ * The update payload is the loaded record minus what this form doesn't own: service-protected
354
+ * columns, and custom-rendered fields — those are their component's, written through the
355
+ * component's own service. Echoing a loaded value back would clobber whatever that service
356
+ * wrote since the load.
357
+ */
358
+ function savePayload(): Partial<T> {
359
+ const payload: any = stripServiceProtectedColumns(table, record as T);
360
+ for (const columnPropertyName in getColumns()) {
361
+ if (getFieldRenderer(columnPropertyName)) {
362
+ delete payload[columnPropertyName];
363
+ }
364
+ }
365
+
366
+ return payload;
367
+ }
368
+
301
369
  function buttons(): FormButtons<any> {
302
370
  let newRecord: T;
303
371
  return {
@@ -352,11 +420,15 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
352
420
  continue;
353
421
  }
354
422
 
423
+ if (getFieldRenderer(columnPropertyName)) {
424
+ continue;
425
+ }
426
+
355
427
  const field = fields[columnPropertyName];
356
428
  (record as any)[columnPropertyName] = getFieldValue(columnPropertyName, field.field.value);
357
429
  }
358
430
 
359
- await getDbService().update(table, stripServiceProtectedColumns(table, record));
431
+ await getDbService().update(table, savePayload());
360
432
  return `Saved ${S(table.name).humanize().s}`;
361
433
  },
362
434
  progressMessage: (fields: Fields) => {
@@ -398,6 +470,11 @@ export function RecordForm<T extends Record>({ table, record }: RecordFormProps<
398
470
  }
399
471
 
400
472
  for (const columnPropertyName in fields) {
473
+ // Custom-rendered fields read straight from the loaded record
474
+ if (getFieldRenderer(columnPropertyName)) {
475
+ continue;
476
+ }
477
+
401
478
  const column = getColumn(columnPropertyName);
402
479
  const field = fields[columnPropertyName].field;
403
480
  let fieldValue = (record as any)[columnPropertyName];