From 00934a7c0c83f64fcf5c1d32eb4710b9706e555a Mon Sep 17 00:00:00 2001 From: Slawomir Koszewski Date: Fri, 30 Oct 2026 12:38:15 +0100 Subject: [PATCH] feature: RowDialog validates a row as part of a larger value, such as a whole document with the row put in. --- CHANGELOG.md | 8 ++++++++ README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- web/RowDialog.tsx | 15 ++++++++++----- web/useValidation.ts | 2 +- 6 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3248ad4..16375b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.6.0 + +feature: RowDialog validates a row as part of a larger value, such as a whole document with the row put in. + +- `RowDialog` takes `body`, the value posted to `validatePath` for the row, and `select`, the + row's problems by field and message taken from the answer. +- `useValidation` and `RowDialog` take an answer of any shape. + ## 0.5.0 feature: the entry point /shared exports the types and constants a server and its web application share. diff --git a/README.md b/README.md index 6038681..e88044c 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ API types imported by both, without the code of either: `CredentialField`, `Cred | `ThemeMenu` | the button cycling light, dark and system theme | | `SwitcherMenu`, `SwitcherItem` | a button naming the item chosen, whose menu chooses another, such as a page or a connection | | `TextInput` | a text field with clear and show/hide buttons | -| `RowDialog`, `useValidation` | a dialog that edits a value validated by the server as it changes, and the validation hook | +| `RowDialog`, `useValidation` | a dialog that edits a value validated by the server as it changes, and the validation hook; with `body` and `select` the dialog posts another value, such as a whole document with the row put in, and takes the row's problems from the answer | | `createBrowserSettings(keys, event)` | switches kept in localStorage and followed across tabs | | `CredentialsButton` | the dialog of a credential store; see [Credential store](#credential-store) | | `TfModule`, `TfInstance`, `TfvarsFileStatus`, `TF_NAME_PATTERN`, `TFVARS_FILE_PATTERN`, `STATE_SCHEMA_PATTERN` | the types and name patterns of the [Tfvars store](#tfvars-store) | diff --git a/package-lock.json b/package-lock.json index cc2053c..cc45726 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@koszewscy/sk-react-toolkit", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@koszewscy/sk-react-toolkit", - "version": "0.5.0", + "version": "0.6.0", "devDependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", diff --git a/package.json b/package.json index f716f31..907bfd2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@koszewscy/sk-react-toolkit", - "version": "0.5.0", + "version": "0.6.0", "description": "Shared code of the React and Fastify web applications: web components, server helpers and the Vite font plugin.", "private": true, "type": "module", diff --git a/web/RowDialog.tsx b/web/RowDialog.tsx index d1bac2f..8d423f6 100644 --- a/web/RowDialog.tsx +++ b/web/RowDialog.tsx @@ -6,12 +6,16 @@ import { useValidation } from './useValidation.ts' /** * A dialog that edits one row. The server validates the value as it changes (validatePath); onSave writes it and * the dialog closes when it succeeds. fields renders the inputs from the value, a setter and the problems by field; - * below shows extra information from the validation, such as subnet placements. + * below shows extra information from the validation, such as subnet placements. body gives the value posted to + * validatePath, such as a whole document with the row put in, and select the problems by field and the message of + * the row taken from the answer; without them the row itself is posted and the answer is its validation. */ -export default function RowDialog(props: { +export default function RowDialog(props: { title: string initial: T validatePath: string + body?: (value: T) => unknown + select?: (result: V) => Validation saveLabel: string onSave: (value: T) => Promise onCancel: () => void @@ -21,9 +25,10 @@ export default function RowDialog(props: { const [value, setValue] = useState(props.initial) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) - const validation = useValidation(props.validatePath, value) - const problems = validation.result?.problems ?? {} - const message = validation.result?.message ?? '' + const validation = useValidation(props.validatePath, props.body ? props.body(value) : value) + const selected = validation.result === null ? null : props.select ? props.select(validation.result) : validation.result as Validation + const problems = selected?.problems ?? {} + const message = selected?.message ?? '' const blocked = validation.pending || Object.keys(problems).length > 0 || message !== '' async function submit(event: FormEvent) { diff --git a/web/useValidation.ts b/web/useValidation.ts index a7cb517..a2ed6b2 100644 --- a/web/useValidation.ts +++ b/web/useValidation.ts @@ -5,7 +5,7 @@ import { request, type Validation } from './api.ts' * Asks the server to validate value at path (a .../validate endpoint) 250 ms after it last * changed. pending is true until the answer for the current value arrives; until then the previous result stays. */ -export function useValidation(path: string | null, value: unknown): { result: T | null; error: string; pending: boolean } { +export function useValidation(path: string | null, value: unknown): { result: T | null; error: string; pending: boolean } { const key = path === null ? '' : `${path}\n${JSON.stringify(value)}` const [answer, setAnswer] = useState<{ key: string; result: T | null; error: string } | null>(null)