|
69 | 69 | * objects.<name>.fields.<field>.placeholder |
70 | 70 | * objects.<name>.fields.<field>.options.<value> |
71 | 71 | * objects.<name>._sections.<section>.label |
| 72 | + * objects.<name>._sections.<section>.fields.<field>.{label,placeholder,helpText} |
72 | 73 | * objects.<name>._views.<view>.label |
73 | 74 | * objects.<name>._views.<view>.description |
74 | 75 | * objects.<name>._views.<view>.emptyState.title / .message |
@@ -953,12 +954,61 @@ function addSectionList(index: SectionIndex, sections: unknown, objectName: unkn |
953 | 954 | } |
954 | 955 | } |
955 | 956 |
|
| 957 | +/** |
| 958 | + * object name → section name → entry `field` → the override copy that entry |
| 959 | + * authors, RAW — the `objects.<o>._sections.<s>.fields.<f>.*` keys. |
| 960 | + */ |
| 961 | +type SectionEntryIndex = Map<string, Map<string, Map<string, Partial<Record<SectionEntryAttr, AuthoredLabel>>>>>; |
| 962 | + |
| 963 | +/** The override copy a form section entry carries (`FormFieldSchema`). */ |
| 964 | +type SectionEntryAttr = 'label' | 'placeholder' | 'helpText'; |
| 965 | +const SECTION_ENTRY_ATTRS: readonly SectionEntryAttr[] = ['label', 'placeholder', 'helpText']; |
| 966 | + |
| 967 | +/** |
| 968 | + * Read the override copy of a FORM VIEW's section entries into the index — |
| 969 | + * the `label`, `placeholder` and `helpText` an entry object authors over the |
| 970 | + * object field it renders, addressed under its section's `name` by its own |
| 971 | + * `field`, which is where `translateView` reads them. |
| 972 | + * |
| 973 | + * Form views only: a `record:details` section and a `fieldGroups` group have |
| 974 | + * no entry overrides the resolver reads. A nameless section has no address, |
| 975 | + * and a bare field-name entry authors no copy, so neither contributes. One |
| 976 | + * string, one key: the first authored value wins, as for a heading. |
| 977 | + */ |
| 978 | +function addSectionEntries(index: SectionEntryIndex, sections: unknown, objectName: unknown): void { |
| 979 | + if (!Array.isArray(sections)) return; |
| 980 | + if (typeof objectName !== 'string' || objectName.length === 0) return; |
| 981 | + for (const section of sections) { |
| 982 | + if (!section || typeof section !== 'object') continue; |
| 983 | + const s = section as Record<string, unknown>; |
| 984 | + if (typeof s.name !== 'string' || s.name.length === 0 || !Array.isArray(s.fields)) continue; |
| 985 | + for (const raw of s.fields) { |
| 986 | + if (!raw || typeof raw !== 'object') continue; |
| 987 | + const entry = raw as Record<string, unknown>; |
| 988 | + if (typeof entry.field !== 'string' || entry.field.length === 0) continue; |
| 989 | + let byObject = index.get(objectName); |
| 990 | + if (!byObject) index.set(objectName, (byObject = new Map())); |
| 991 | + let bySection = byObject.get(s.name); |
| 992 | + if (!bySection) byObject.set(s.name, (bySection = new Map())); |
| 993 | + let copy = bySection.get(entry.field); |
| 994 | + if (!copy) bySection.set(entry.field, (copy = {})); |
| 995 | + for (const attr of SECTION_ENTRY_ATTRS) { |
| 996 | + const authored = asAuthoredLabel(entry[attr]); |
| 997 | + if (copy[attr] === undefined && authored !== undefined) copy[attr] = authored; |
| 998 | + } |
| 999 | + } |
| 1000 | + } |
| 1001 | +} |
| 1002 | + |
956 | 1003 | /** |
957 | 1004 | * Emit `objects.<object>._sections.<section>.label` for every section the |
958 | | - * stack renders, from both authoring surfaces. |
| 1005 | + * stack renders, from both authoring surfaces, and |
| 1006 | + * `objects.<object>._sections.<section>.fields.<field>.{label,placeholder,helpText}` |
| 1007 | + * for the override copy a form view's section entries author. |
959 | 1008 | */ |
960 | 1009 | function walkObjectSections(config: any, out: ExpectedEntry[]): void { |
961 | 1010 | const index: SectionIndex = new Map(); |
| 1011 | + const entries: SectionEntryIndex = new Map(); |
962 | 1012 |
|
963 | 1013 | // (a) `fieldGroups` × field `group` membership. `deriveFieldGroupLayout` is |
964 | 1014 | // the shared derivation the renderers themselves consume, so "which |
@@ -991,13 +1041,18 @@ function walkObjectSections(config: any, out: ExpectedEntry[]): void { |
991 | 1041 | for (const view of views) { |
992 | 1042 | const containerObject = viewObjectName(view); |
993 | 1043 | addSectionList(index, view?.sections, containerObject); |
| 1044 | + addSectionEntries(entries, view?.sections, containerObject); |
994 | 1045 | if (view?.form && typeof view.form === 'object') { |
995 | | - addSectionList(index, view.form.sections, viewObjectName(view.form) ?? containerObject); |
| 1046 | + const formObject = viewObjectName(view.form) ?? containerObject; |
| 1047 | + addSectionList(index, view.form.sections, formObject); |
| 1048 | + addSectionEntries(entries, view.form.sections, formObject); |
996 | 1049 | } |
997 | 1050 | if (view?.formViews && typeof view.formViews === 'object') { |
998 | 1051 | for (const form of Object.values<any>(view.formViews)) { |
999 | 1052 | if (!form || typeof form !== 'object') continue; |
1000 | | - addSectionList(index, form.sections, viewObjectName(form) ?? containerObject); |
| 1053 | + const formObject = viewObjectName(form) ?? containerObject; |
| 1054 | + addSectionList(index, form.sections, formObject); |
| 1055 | + addSectionEntries(entries, form.sections, formObject); |
1001 | 1056 | } |
1002 | 1057 | } |
1003 | 1058 | } |
@@ -1045,6 +1100,25 @@ function walkObjectSections(config: any, out: ExpectedEntry[]): void { |
1045 | 1100 | ); |
1046 | 1101 | } |
1047 | 1102 | } |
| 1103 | + |
| 1104 | + // Authored copy only (`pushEntry`): the resolver translates an entry string |
| 1105 | + // only where the entry authors one, so a key for a string nobody wrote is |
| 1106 | + // one nothing reads. |
| 1107 | + for (const [objectName, sections] of entries) { |
| 1108 | + for (const [sectionName, fields] of sections) { |
| 1109 | + for (const [fieldName, copy] of fields) { |
| 1110 | + for (const attr of SECTION_ENTRY_ATTRS) { |
| 1111 | + pushEntry( |
| 1112 | + out, |
| 1113 | + ['objects', objectName, '_sections', sectionName, 'fields', fieldName, attr], |
| 1114 | + copy[attr], |
| 1115 | + 'section', |
| 1116 | + { objectName }, |
| 1117 | + ); |
| 1118 | + } |
| 1119 | + } |
| 1120 | + } |
| 1121 | + } |
1048 | 1122 | } |
1049 | 1123 |
|
1050 | 1124 | // ─── Object filter-preset tabs (`objects.<o>._tabs.<tab>.label`) ─────── |
|
0 commit comments