Skip to content

Never collapse a collection row without the wrapper - #416

Merged
CarlosNZ merged 2 commits into
mainfrom
415-fix-collapse-state-on-no-wrapper-nodes
Sep 30, 2026
Merged

CarlosNZ merged 2 commits into
mainfrom
415-fix-collapse-state-on-no-wrapper-nodes

Conversation

@CarlosNZ

@CarlosNZ CarlosNZ commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Closes #415.

Summary

A collection row whose definition sets showCollectionWrapper: false has no chevron, and the README promises it "no chevron, brackets, or built-in collapse". But three paths could still collapse it, and once collapsed nothing could reopen it:

  1. The collapse filter (A row with showCollectionWrapper: false renders no children on first render when the collapse filter matches it #415). startCollapsed and the collapseFilter effect didn't consider showCollectionWrapper. The first render built CollectionChildren from a false hasBeenOpened and dropped the rows, and useCollapseTransition started the node at maxHeight: 0. The memoised node often never re-rendered to recover.
  2. Collapse broadcasts. "Collapse All" from an ancestor (or editorRef.collapse) collapsed the row too, leaving it clipped at zero height once the ancestor was reopened with a plain click.
  3. Click zones. A click in the left zone (on by default) or on the key collapsed it. With showKey: false that's a trap: the row shrinks to nothing clickable.

Changes

All in CollectionNode.tsx:

  • showCollectionWrapper is read at the top, and a wrapperless row skips the filter in both startCollapsed and the effect. Fixing startCollapsed (rather than moving the isCollapsed / hasBeenOpened lines above CollectionChildren) matters because it also seeds useCollapseTransition, so the row starts genuinely open instead of rendering its rows into a zero-height box with nodeData.collapsed: true.
  • useAppliedBroadcast gets NOOP in place of animateCollapse for a wrapperless row, and handleCollapse returns early for one. Alt-click on a wrapperless row therefore no longer cascades a collapse to its descendants either.
  • A wrapperless row doesn't render the left .jer-clickzone, which could no longer do anything.
  • A row whose definition drops the wrapper while it's collapsed reopens, via the existing render-time reopen line used for Tab editing.

Tests

Seven regression tests in test/customNode.test.tsx. Max-height checks expect '' (an open row has none). Apart from the reopen test, where reopening legitimately animates, they run before any timer, so a collapse that later reverses still fails them:

  • rows render at mount with collapse={2}, with no max-height (plus the collapse={false} baseline)
  • a custom component receives its rows as children at mount
  • rows stay rendered, with no max-height, when collapse changes to match the row
  • a broadcast targeting the visible wrapperless row (includeChildren) collapses its descendants but leaves the row without a max-height
  • the row has no left click zone, and a key click ('property' zone) neither collapses it nor fires onCollapse
  • a collapsed row that loses its wrapper reopens

Each fix was removed on its own and fails at least one test: the filter fix and the broadcast guard (row gets a max-height), the handleCollapse guard (onCollapse fires), the click-zone conditional (zone rendered) and the reopen line (rows missing). Against main's CollectionNode six fail. Full suite, lint and tsc pass.

🤖 Generated with Claude Code

A row whose definition sets `showCollectionWrapper: false` has no
chevron, so nothing can reopen it once collapsed, and the README
promises it no built-in collapse. But `startCollapsed` and the
`collapseFilter` effect both applied the `collapse` filter to it: the
first render built `CollectionChildren` from a false `hasBeenOpened`
and dropped the rows, and `useCollapseTransition` started it at
`maxHeight: 0`. The memoised node often never re-rendered to recover.

The filter now skips a wrapperless row, so it starts genuinely open.
The two other ways to collapse it are closed off too: a collapse
broadcast ("Collapse All" from an ancestor, `editorRef.collapse`) left
it clipped at zero height once the ancestor reopened, and a click in
the left or property click zone collapsed it (a trap with
`showKey: false`, since the row shrank to nothing clickable). A row
whose definition drops the wrapper while collapsed reopens.

Closes #415

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Bundle size impact

json-edit-react

Format Base raw PR raw Δ raw Base gzip PR gzip Δ gzip
esm 59.02 KB 59.06 KB 🔺 +41 B (+0.07%) 20.89 KB 20.92 KB 🔺 +29 B (+0.14%)
cjs 60.46 KB 60.50 KB 🔺 +34 B (+0.05%) 20.92 KB 20.94 KB 🔺 +25 B (+0.12%)

Measured from build/index.{cjs,esm}.js. Gzip at level 9.

Skip the left click zone on a row without the collection wrapper: it
can't collapse the row, so it would only be a dead element over the
indent.

Tighten the tests so each fix is pinned on its own. The max-height
checks read `not.toBe('0')`, which a jsdom that serialises a zero
max-height as '0px' always passes; they now expect '' (an open row
has no max-height). And the render-time reopen of a row that drops its
wrapper also undoes a collapse from the filter or a broadcast on the
next render, so the filter fix and the broadcast guard were masked:
their absence only showed as a transient max-height. The #415 tests
now check the row's max-height before any timer runs, the collapse-all
test becomes a broadcast targeting the visible wrapperless row, and the
click test uses a key click (the `property` zone, which still reaches
`handleCollapse`) plus a check that no left zone renders. Removing any
single fix now fails at least one test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@CarlosNZ
CarlosNZ merged commit efd9866 into main Sep 30, 2026
2 checks passed
@CarlosNZ
CarlosNZ deleted the 415-fix-collapse-state-on-no-wrapper-nodes branch September 30, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A row with showCollectionWrapper: false renders no children on first render when the collapse filter matches it

1 participant