Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ Compatibility with the old project is intentional.
- Name paths define output shape (`person.name.first`).
- Array and indexed syntax is preserved (`items[]`, `items[5].name`).
- Rails-style names are supported (`rails[field][value]`).
- Checkbox/radio `"true"` and `"false"` quirks are preserved.
- DOM extraction follows native browser form submission semantics for checkbox and radio values.
- Unsafe key path segments (`__proto__`, `prototype`, `constructor`) are rejected by default.
- This library does data shaping, not JSON/XML serialization.

Expand All @@ -274,7 +274,7 @@ Compatibility with the old project is intentional.
These boundaries are intentional and are used for issue triage.

- Sparse indexes are compacted in first-seen order (`items[5]`, `items[8]` -> `items[0]`, `items[1]`).
- Type inference is minimal by design; only legacy checkbox/radio `"true"` and `"false"` coercion is built in.
- Type inference is minimal by design; DOM extraction keeps native string values instead of coercing checkbox/radio fields.
- `formToObject` reads successful form control values, not option labels. Disabled controls (including disabled fieldset descendants) and button-like inputs are excluded unless you explicitly opt in to disabled values.
- `extractPairs`/`formToObject` support `nodeCallback`; return `SKIP_NODE` to exclude a node entirely, or `{ name|key, value }` to inject a custom entry.
- Parser inputs reject unsafe path segments by default. Use `allowUnsafePathSegments: true` only with trusted inputs.
Expand Down
1 change: 0 additions & 1 deletion apps/docs/test/standard-variants.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ describe("standard playground variants", () => {
errorMessage: null,
parsedPayload: {
person: {
approved: false,
city: "lancre",
guild: "witches",
name: { first: "Esme", last: "Weatherwax" },
Expand Down
7 changes: 3 additions & 4 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,9 @@ export function form2js(
### Behavior notes

- `select name="colors[]"` is emitted as key `colors` (the trailing `[]` is removed for selects).
- Legacy checkbox/radio quirks are preserved:
- checked `"true"` -> `true`
- unchecked `"true"` -> `false`
- checked `"false"` (radio/checkbox) -> `false`
- Checkbox and radio values follow native browser form submission semantics:
- checked controls emit their string `value`
- unchecked controls are omitted
- Button-like inputs (`button`, `reset`, `submit`, `image`) are excluded from extraction.
- Can merge multiple roots (`NodeList`, arrays, `HTMLCollection`) into one object.
- If callback returns `SKIP_NODE`, that node is excluded from extraction entirely.
Expand Down
22 changes: 0 additions & 22 deletions packages/dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,29 +208,7 @@ function getFieldValue(fieldNode: Node, getDisabled: boolean): unknown {

switch (inputType) {
case "radio":
if (fieldNode.checked && fieldNode.value === "false") {
return false;
}

if (fieldNode.checked && fieldNode.value === "true") {
return true;
}

if (fieldNode.checked) {
return fieldNode.value;
}

return null;

case "checkbox":
if (fieldNode.checked && fieldNode.value === "true") {
return true;
}

if (!fieldNode.checked && fieldNode.value === "true") {
return false;
}

if (fieldNode.checked) {
return fieldNode.value;
}
Comment on lines 210 to 214
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve indexed row positions for checkbox/radio fields

For forms that use indexed names like items[0].selected / items[1].selected, omitting every unchecked checkbox/radio here corrupts the parsed array shape. entriesToObject() compacts indexed paths by encounter order in packages/core/src/index.ts (setPathValue), so a form where only items[1].selected is checked now parses as items[0].selected, attaching the checked state to the wrong row. The previous false placeholder avoided that reindexing, so this is a real data-corruption regression for checkbox/radio-only indexed collections or repeated rows whose only indexed field is a boolean.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this against the current parser and reproduced it. The outcome is real, but it is not specific to #99: it comes from the existing documented sparse-index compaction rule in @form2js/core, which already compacts any omitted indexed entry (for example, a lone items[1].name also becomes items[0].name). Reintroducing unchecked checkbox placeholders here would undo the browser-aligned DOM behavior we intentionally restored in #99. Follow-up in #100 adds an explicit DOM test for this interaction and documents that omitted unchecked indexed controls do not reserve compacted array slots, so callers that need stable row identity should submit another field for that row.

Expand Down
14 changes: 9 additions & 5 deletions packages/dom/test/dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,14 @@ describe("extractPairs", () => {
});

describe("formToObject", () => {
it("keeps legacy checkbox and radio coercion quirks", () => {
it("keeps checkbox and radio values aligned with native form submission", () => {
document.body.innerHTML = `
<form id="testForm">
<input type="checkbox" name="person.agree" value="true" />
<input type="radio" name="person.optOut" value="false" checked />
<input type="checkbox" name="person.checkboxTrueChecked" value="true" checked />
<input type="checkbox" name="person.checkboxTrueUnchecked" value="true" />
<input type="checkbox" name="person.checkboxFalseChecked" value="false" checked />
<input type="checkbox" name="person.checkboxFalseUnchecked" value="false" />
<input type="radio" name="person.radio" value="false" checked />
</form>
`;

Expand All @@ -129,8 +132,9 @@ describe("formToObject", () => {

expect(result).toEqual({
person: {
agree: false,
optOut: false
checkboxTrueChecked: "true",
checkboxFalseChecked: "false",
radio: "false"
}
});
});
Expand Down
Loading