feat(form): ui:mirrorFrom — reflect a sibling field into a read-only field
The project-rollup forms derive originator from the selected Package
(party folder) server-side, so the field is read-only and was blank
until submit. Add a declarative `ui:mirrorFrom: <sibling>` hint: the
object renderer wires the named sibling's input to the field so the
read-only originator updates live as the user picks a party — the
composing tracking number is visible while filling the form. Display
only; the server stays authoritative via the cascade's folder_fields.
Set `ui:mirrorFrom: party` on originator in the embedded
default-project-{mdl,rsk}.form.yaml. Generic hint, not hardcoded field
names, so operators can reuse it.
Test: form-safety.spec.js — filling the source field updates the
read-only target; the target is not editable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9341c47937
commit
7dfedc2342
4 changed files with 66 additions and 0 deletions
|
|
@ -71,6 +71,29 @@
|
|||
fs.appendChild(childWidget.el);
|
||||
}
|
||||
|
||||
// Cross-field mirror: a field with `ui:mirrorFrom: <sibling>`
|
||||
// shows the live value of that sibling. Used by the project-
|
||||
// rollup forms so the read-only `originator` reflects the
|
||||
// selected Package (party) — the party folder is the
|
||||
// originator's source of truth. Display-only: the server is
|
||||
// still authoritative via the cascade's folder_fields.
|
||||
for (let i = 0; i < ordered.length; i++) {
|
||||
const name = ordered[i];
|
||||
const mirrorFrom = ui && ui[name] && ui[name]['ui:mirrorFrom'];
|
||||
if (!mirrorFrom || !children[name] || !children[mirrorFrom]) {
|
||||
continue;
|
||||
}
|
||||
const targetInput = children[name].el.querySelector('input, select, textarea');
|
||||
const sourceInput = children[mirrorFrom].el.querySelector('input, select, textarea');
|
||||
if (!targetInput || !sourceInput) {
|
||||
continue;
|
||||
}
|
||||
const sync = function () { targetInput.value = sourceInput.value; };
|
||||
sourceInput.addEventListener('input', sync);
|
||||
sourceInput.addEventListener('change', sync);
|
||||
sync(); // initialize from any pre-filled party value
|
||||
}
|
||||
|
||||
return {
|
||||
el: fs,
|
||||
path: path,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,39 @@ test.describe('form/ — safety check-in renderer', () => {
|
|||
await expect(page.locator('#table-title')).toContainText('Safety Check-In');
|
||||
});
|
||||
|
||||
test('ui:mirrorFrom reflects a sibling field into a read-only field', async ({ page }) => {
|
||||
// The project-rollup forms use this so the read-only originator
|
||||
// shows the selected Package (party) — the party folder is the
|
||||
// originator's source of truth.
|
||||
await loadFormWithContext(page, {
|
||||
title: 'Rollup deliverable',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['party'],
|
||||
properties: {
|
||||
party: { type: 'string', title: 'Package' },
|
||||
originator: { type: 'string', title: 'Originator', readOnly: true },
|
||||
},
|
||||
},
|
||||
ui: { originator: { 'ui:mirrorFrom': 'party' } },
|
||||
data: null,
|
||||
submitUrl: '/test/rollup.form.html',
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => document.getElementById('form-root') && document.getElementById('form-root').children.length > 0,
|
||||
null,
|
||||
{ timeout: 5000 },
|
||||
);
|
||||
|
||||
const inputs = page.locator('#form-root input[type="text"]');
|
||||
await expect(inputs).toHaveCount(2);
|
||||
const party = inputs.nth(0);
|
||||
const originator = inputs.nth(1);
|
||||
await expect(originator).not.toBeEditable(); // read-only
|
||||
await party.fill('0330C1');
|
||||
await expect(originator).toHaveValue('0330C1');
|
||||
});
|
||||
|
||||
test('add/remove hazard rows works', async ({ page }) => {
|
||||
await loadFormWithContext(page, {
|
||||
schema: SAFETY_SCHEMA,
|
||||
|
|
|
|||
|
|
@ -122,5 +122,10 @@ schema:
|
|||
title: Previous SHA
|
||||
readOnly: true
|
||||
ui:
|
||||
# originator is server-derived from the selected Package (party
|
||||
# folder); mirror the party value into its read-only field so the
|
||||
# composing tracking number is visible as the user fills the form.
|
||||
originator:
|
||||
ui:mirrorFrom: party
|
||||
notes:
|
||||
ui:widget: textarea
|
||||
|
|
|
|||
|
|
@ -152,6 +152,11 @@ schema:
|
|||
title: Previous SHA
|
||||
readOnly: true
|
||||
ui:
|
||||
# originator is server-derived from the selected Package (party
|
||||
# folder); mirror the party value into its read-only field so the
|
||||
# composing tracking number is visible as the user fills the form.
|
||||
originator:
|
||||
ui:mirrorFrom: party
|
||||
description:
|
||||
ui:widget: textarea
|
||||
mitigation:
|
||||
|
|
|
|||
Loading…
Reference in a new issue