fix(joint-react): deliver useOnElementsMeasured once per settled change - #3518
Merged
Merged
Conversation
…ment writes, not application resizes
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
Only minor documentation and changeset wording nits remain; no blocking issues were identified.
Review effort: Lite
Findings: 1
What changed in this PR
Updates @joint/react so useOnElementsMeasured ignores later application-driven resizes while preserving initial size tracking.
Changes:
- Forwards
change:sizeoptions through graph listeners. - Filters measurement notifications by resize origin.
- Adds regression tests, documentation, and a patch changeset.
| File | Summary |
|---|---|
packages/joint-react/src/store/graph-store.ts |
Filters measurement-state updates. |
packages/joint-react/src/store/graph-projection.ts |
Extends size-change callback options. |
packages/joint-react/src/store/graph-changes.ts |
Forwards size-change options. |
packages/joint-react/src/store/__tests__/graph-changes.test.ts |
Tests option forwarding. |
packages/joint-react/src/hooks/use-on-elements-measured.ts |
Documents updated hook behavior. |
packages/joint-react/src/hooks/__tests__/use-on-elements-measured.test.tsx |
Adds resize behavior regression tests. |
.changeset/react-elements-measured-app-resize.md |
Adds the patch changeset. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
kumilingus
added a commit
to kumilingus/joint
that referenced
this pull request
Sep 24, 2026
Whether a size written by the application deserves an event depends on what it does to the graph, not on who wrote it. Resizing an element that already has a size changes nothing about readiness, so it must stay silent: that is the re-entrant layout of clientIO#3514. Sizing an element that had none is the write that makes the graph settled, so it is the event a consumer is waiting for. Pinning both keeps a fix from satisfying one by breaking the other. Suppressing every application resize, as clientIO#3518 does, silences the second one and leaves the graph fully sized with nobody told. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
kumilingus
requested changes
Sep 28, 2026
kumilingus
left a comment
Contributor
There was a problem hiding this comment.
Update the useOnElementsMeasured using this specs #3520 as a single fix.
- Introduced a new measurement generation system in GraphStore to track resets and initial measurement passes. - Updated `useOnElementsMeasured` to deliver events based on settled changes, ensuring accurate measurement reporting. - Modified the measurement observer to handle cases where elements are added without sizes and to trigger events when they are measured. - Improved test coverage for measurement events, including scenarios for element resets and size changes. - Refactored size change handling to differentiate between application-resized elements and those measured by the observer. - Ensured that the `markElementRendered` function correctly settles elements that arrive without sizes.
…ter content rendering
…ed elements - Updated the measurement handling logic in useOnElementsMeasured to account for elements that are not rendered by the paper or are zero-sized. - Introduced new tests to cover scenarios where elements are culled or removed before measurement. - Improved the flush function to ensure all rendering and measurement processes are settled before events are delivered. - Refactored GraphStore to manage outstanding elements more effectively, ensuring that elements not rendered by any paper do not block measurement events. - Added a settleUnrenderedElements method to clear outstanding elements that are not accounted for by any paper. - Enhanced documentation to clarify the behavior of the measurement system regarding application-written sizes and unmeasured elements.
… change without pending measurements
kumilingus
approved these changes
Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
useOnElementsMeasurednow delivers one event per settled change, implementing the specification written as tests in #3520. Its test file is in this PR (all 16 cases pass), plus a sibling file with a culled element, removal while waiting, and a content change that re-measures.An element is outstanding from the moment it enters the graph until it is accounted for by a positive signal, never by its size value (a zero size is a legal final size, e.g. a layout anchor):
markElementRendered(), called from the element portal item's existing layout effect, after the subtree'suseMeasureElementhas had its chance to register (O(1));cellVisibility, a hidden group) —settleUnrenderedElements(), run on each paper'srender:done, once the views of that pass are in the DOM; O(outstanding), which is empty between changes;useMeasureElement/HTMLHost): then until the observer measures it — anautoSizewrite, or a measurement equal to the size it already holds (the observer writes nothing then and reports it through the newonElementMeasuredoption);While anything is outstanding no event is delivered: a batch mixing plain and measured elements is one event once the last one is measured. Sizes the application writes (
cell.resize(), controlledcellssync) are not measurements and never fire, whether the element is settled or still waiting (#3514). Without any paper nothing renders or measures, so nothing is waited for (store-only use keeps the old timing).A graph
resetstarts a new measurement history:measureStatereturns to 0 until the new diagram settles, andisInitialis reported again for its first pass (measureGeneration).packages/joint-react/src/storegraph-changes.ts:MeasurementListeners(onElementsSizeChange,onElementRemove,onReset), forwarded bygraph-projection.ts.graph-store.ts: the bookkeeping above;markElementRendered(),settleUnrenderedElements(),measureGeneration.paper-store.ts: subscribessettleUnrenderedElementsto the paper'srender:done.create-elements-size-observer.ts:onElementMeasuredoption.packages/joint-react/srchooks/use-on-elements-measured.ts:isInitialper generation; the JSDoc states the contract.components/paper/render-element/paper-element-item.tsx: callsmarkElementRendered()from the existing layout effect (no extra effect per element).Tests
use-on-elements-measured-events.test.tsx(from test(joint-react): specify how many events useOnElementsMeasured delivers #3520, 16 cases). Two adjustments to the file, both in test scaffolding:flush()now also awaits onerequestAnimationFrame. The paper is async, so an added element's view mounts, and itsuseMeasureElementregisters, only in the next frame; a plainsetTimeout(0)lands before that, while the portal does not exist yet (measured in jsdom:observer.has(id)isfalseaftersetTimeout(0)andtrueafter a frame). The helper's own comment describes waiting for that commit, so it now does.set()in JointJS with nochange:sizefor anything to observe. The equal-size measurement is covered separately, through the observer.use-on-elements-measured-settling.test.tsx(same harness, beside the spec): a culled zero-sized element (seed and batch), a waiting element removed before measurement, and, through a real ResizeObserver mock, a content change that measures larger (one event, model resized), one that measures the same (no event), and an element the application pre-sized to what it then measures (delivered).graph-changes.test.ts,graph-store.test.ts: forwarding and the equal-size measurement; thefromMeasureflag in older tests replaced byautoSize.yarn testinpackages/joint-reactpasses: typecheck, knip, duplicity, React 19 (1096 tests) and React 18 (1092 tests).yarn lintclean.yarn benchpasses.Changeset (
@joint/reactpatch):Motivation and Context
Fixes #3514.
Implements and supersedes #3520 (the tests-only spec PR), including its "element that stays zero-sized" cases: an earlier revision of this PR inferred "waiting" from a zero size and held every later batch open for a permanently zero-sized element that no paper renders (the
GroupEndModelmarkers in the ci-pipeline demo). That inference is gone.Related: #3485 (auto-size re-measurement after external size writes, against
master). Both touchgraph-store.ts, so expect a small conflict when #3485 reachesdev.Notes
dev, which has no CI; the full suite was run locally (above).render:done, so elements added meanwhile are delivered once it renders.