Skip to content

fix(dia.Element): keep port ids that differ only in type apart - #3546

Merged
kumilingus merged 8 commits into
clientIO:masterfrom
Geliogabalus:fix/port-ids-differ-in-type
Oct 12, 2026
Merged

kumilingus merged 8 commits into
clientIO:masterfrom
Geliogabalus:fix/port-ids-differ-in-type

Conversation

@Geliogabalus

@Geliogabalus Geliogabalus commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes #3544

Ports are now indexed by the id itself (Map) rather than by its string form, so a numeric port id and a string one are two different ports everywhere - as getPortIndex(), portProp() and the port-id-type DOM round trip already assumed:

  • PortData - the port index (portsMap) and the per-group port metrics (getGroupPortsMetrics(), which is neither typed nor documented, now returns that Map)
  • ElementView - the rendered port nodes cache (_portElementsCache)
  • _processRemovedPort() - the set of removed ports whose links get removed
// element with a port `{ id: 5 }`
element.hasPort('5');    // true -> false
element.getPortBBox('5'); // bbox of port 5 -> throws (no such port)

Two ports 5 and '5' on one element now coexist instead of overwriting each other in these indexes.

Port ids read back from the DOM go through a new CellView.findNodePortId() (typed, internal and not documented for now), which restores a numeric id from the port-id-type attribute. getLinkEnd() did this already; ElementView.findProxyNode() and the useModelGeometry connection points used the raw string attribute and relied on the old stringified index.

Related fixes in the same area:

  • port id 0 was treated as "no port" in Element.getPort(), Element.getPointFromConnectedLink() and the multiLinks validation
  • getLinkEnd() truncated a non-integer numeric port id (parseInt -> Number), e.g. 1.5 became 1
  • types - a new dia.Element.PortID (an alias of dia.Cell.ID, so cell and port ids share one type) types the port id parameters of the dia.Element port methods, ElementView.findPortNode(s)() and generatePortId(). Port.id itself is unchanged (string), as widening it would be type-breaking - a TODO marks it to become Element.PortID in the next major version; insertPort(before) is unchanged, as a number there is an index

Known limitation: getPortsPositions() keeps returning a plain object (its typed public return value), so ports whose ids differ only in type overwrite each other in its result. A TODO marks it to return a Map in the next major version.

Motivation and Context

PortData keyed ports by a plain object, so hasPort(), getPort() and the port geometry methods answered for a port that was never asked for ('5' for 5), disagreeing with getPortIndex() / portProp(), which compare with ===.

Behaviour change worth noting: callers that stringify a numeric port id before passing it back (e.g. read it from a DOM attribute by hand, from JSON or a URL) no longer match the port. The changeset says port ids are matched by value and type.

Tests

  • New port ids of different types module in test/jointjs/elementPorts.js (11 tests) - each fails without the change
  • Numeric port ids and Element.PortID checked in test/ts/index.test.ts
  • karma:joint passes apart from two failures that are Windows-specific and present on master (util.breakText ellipsis, a float rounding in the port label matrix(...) string); test:ts and lint pass

🤖 Generated with Claude Code

Geliogabalus and others added 2 commits October 12, 2026 13:08
Index ports, their metrics and rendered port nodes by the id itself (Map)
instead of a plain object, so `hasPort('5')` no longer answers for a port
with id `5` and ports `5` and `'5'` can coexist. Removing a port no longer
keeps links to a removed port whose id stringifies like a remaining one.

Also handle port id `0` in `getPort()`, `getPointFromConnectedLink()` and
the `multiLinks` validation, keep non-integer numeric port ids in
`getLinkEnd()`, and accept numeric port ids in the port method typings.

Closes clientIO#3544

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`findProxyNode()` and the `useModelGeometry` connection points read the
`port` attribute as a string, which no longer matches a numeric port id
now that ports are indexed by the id itself. Read it through a shared
`_findPortId()` helper that honours `port-id-type`, as `getLinkEnd()` did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread packages/joint-core/src/dia/CellView.mjs Outdated
Comment thread packages/joint-core/types/dia.d.ts Outdated
Geliogabalus and others added 3 commits October 12, 2026 16:56
Port metrics are keyed by the port id in a Map, which `getGroupPortsMetrics()`
started returning. Keep the public method returning a plain object for
backwards compatibility and read the Map internally through
`_getGroupPortsMetrics()`.

Merge the port lookup changesets into one entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`getGroupPortsMetrics()` is neither typed nor documented, so return the
Map keyed by the port id directly instead of a plain object wrapper.
Note on `getPortsPositions()` that it should return a Map in the next
major version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Type port ids as `Element.Port.ID` (`string | number`), as cell ids are
typed as `Cell.ID`, in the `dia.Element` port methods,
`ElementView.findPortNode(s)()` and `generatePortId()`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Geliogabalus and others added 2 commits October 12, 2026 18:50
Replace the `Element.Port.ID` namespace with an `Element.PortID` alias of
`Cell.ID`, so cell and port ids share one type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expose the helper without the underscore, typed and marked internal
(not documented) for now, as `Paper.requestCellViewInsertion()` is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…major

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kumilingus
kumilingus merged commit 8d7c087 into clientIO:master Oct 12, 2026
3 checks passed
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.

dia.Element: ports whose ids differ only in type are not kept apart by PortData

2 participants