Skip to content

feat(s2): legend navigation, keyboard right-click and hardening for the data navigator - #1097

Open
c-lamoureux wants to merge 65 commits into
mainfrom
dataNavigator2
Open

c-lamoureux wants to merge 65 commits into
mainfrom
dataNavigator2

Conversation

@c-lamoureux

@c-lamoureux c-lamoureux commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Merges dataNavigator2 (everything on dataNavigator, plus the review stack built on it) into main. main already has the S2 bar keyboard navigator; this extends it, hardens it, and restructures it so more chart types can follow. dataNavigator stays the target for the line navigator PR, which will be reconciled after this lands.

Summary

Bar navigation fixes

  • Dodged-and-stacked bars (a color array) are navigable, with bar and group focus rings.
  • Series spanning several fields (e.g. time comparisons with color and opacity) are distinct nodes and read every field.
  • Focus rings show for number and time dimensions; 0 and empty-string dimension values no longer break navigation; dimensionDataType="time" with date strings works.
  • Dodged bars without order navigate in drawn order, not reversed.
  • Named bars work: Enter/Space fire onClick and open the popover, and focus drives hover. The bottom-axis region is added for the default dimension.
  • Focus returns to the entry point after data changes instead of keeping a stale item.

Legend navigation and keyboard right-click

  • With accessibleNavigation on, the legend is its own region, reached with the arrow pointing at it. Enter drills into series, then into that series' marks; arrows follow the rendered rows and columns.
  • Space does what clicking an entry does (popover, onClick, or toggle). Toggled-off series are skipped by content navigation, and focus is restored after a toggle with the new state announced.
  • A focused series gets a focus ring, its description tooltip, and the legend hover highlight; onMouseOver/onMouseOut fire on focus so controlled legends follow.
  • Shift+F10 / ContextMenu is the keyboard right-click: opens rightClick popovers on marks and legend series and calls onContextMenu.

Architecture

  • Navigation runs in a framework-agnostic controller rather than React, split into regions (content, x-axis, legend) and per-chart strategies so new chart types plug in.
  • The spec builder owns focus rings and focus signals in one shared accessibleNavigation module, and records the navigation config in usermeta.navigation, so the hook no longer rebuilds it from React children.

Production hardening

  • Fail closed: navigation is only on for a chart with exactly one untrellised bar mark. Any other chart gets none of it, plus a console warning, instead of a partial setup.
  • Development check: development builds warn when navigation and the rendered bars don't map one-to-one.
  • Contract test: every bar dashboard variation is rendered with every dataset (644 cases) and must navigate cleanly or be listed as unsupported with a reason. The bar dashboard gains a Keyboard navigation switch. It found and fixed a crash when every series is hidden; navigation now starts at the legend.

SonarCloud maintainability (S2)

  • Fixes: quadratic regex backtracking in a legend check; an object locale breaking accessible color names; scale/domain fields stringified as [object Object].
  • width narrowed to number | 'auto' | \${number}%``.
  • Deep copies unified on a new jsonClone (in @spectrum-charts/core-s2/utils) and structuredClone.
  • Reduced cognitive complexity and other no-behavior-change refactors. S1 is in maintenance, so its existing findings are suppressed with NOSONAR.

Docs and tooling

  • Accessibility guide section on experimental keyboard navigation, listing what's supported.
  • An s2-data-navigator agent skill.
  • lint:sonar honors NOSONAR and sonar.exclusions like SonarCloud.

Consolidates the review stack #1084–#1093 and supersedes #940.

Michael Jordan and others added 30 commits October 8, 2026 11:31
Replace the backtracking-prone [A-Z]{2,}(?=[A-Z][a-z]+\d*|\b) acronym
match with a single negative lookahead ([A-Z]+(?![a-z])), which decides
the split point without re-scanning candidate lengths. This removes the
O(n^2) worst case triggered by long uppercase runs (e.g. name props)
followed by a non-letter character.

SonarCloud: https://sonarcloud.io/project/issues?open=AZ89i0vkqenAEyA5_fKB&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/.*(Trendline|MetricRange).*highlightedData$/ has two unbounded
wildcards around an alternation, letting an adversarial data source
name (many repeated Trendline/MetricRange substrings) force O(n^2)
backtracking. The check is really just "contains one of these
substrings and ends with this suffix," which includes()/endsWith()
express directly and in guaranteed linear time. Applied to both the
s1 and s2 legendSpecBuilder, which had the identical regex.

SonarCloud: https://sonarcloud.io/project/issues?open=AZ89iz6oqenAEyA5_fJ_&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Prefer the Number-namespaced static method over the global function
per typescript:S7773. Fixes the flagged instance in
vega-spec-builder-s2/trendlineUtils.ts and the same pattern
everywhere else it appeared in the codebase (both trendlineUtils.ts
mirrors, both Chart.test.tsx stories files, and the sonar-fork
scripts).

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHaHID6Ha28pDOmS&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract the open- and close-popover branches of handleOpenChange into
their own nested functions (openPopoverView/closePopoverView), the
same pattern already used for the close branch's clearSelection.
Sonar scores nested function bodies separately from their enclosing
function, so this drops handleOpenChange's own complexity from 16 to
3 without changing behavior — the open branch's trailing
setSelectedSignals/run call, previously a second top-level `if (open)`
after the if/else, is folded into openPopoverView since it only ever
ran when open was true.

SonarCloud: https://sonarcloud.io/project/issues?open=AaDTnyTRvbuDdREVfOEV&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Split the function into the three independent steps it was doing:
applyWithinGroupNavigationRules (rebind same-column edges onto the
group-local axis), buildColumnsBySeries (map each column's series
values to their segment ids), and wireAdjacentColumnNavigation
(connect adjacent columns). This is a pure extraction — no behavior
change — and drops the parent function from complexity 21 to 2, with
each extracted function well under the 15 limit on its own.

SonarCloud: https://sonarcloud.io/project/issues?open=AaCnMno1EnEdGe7Q_SMX&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed by running eslint directly on the file with the blanket
disable removed: the only rule that actually fires is
@typescript-eslint/ban-ts-comment, triggered by the file's own
@ts-nocheck line. Naming it instead of disabling everything keeps
any other rule active for this file (it's checked into the repo even
though it's generated, since there's no in-repo generator script to
re-run).

SonarCloud: https://sonarcloud.io/project/issues?open=AaDTnyO8vbuDdREVfOEQ&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed by running eslint directly on the file with the blanket
disable removed: no rule fires here at all, so the comment was pure
dead weight rather than suppressing anything. Unlike the sibling
en-US.ts (which needs @typescript-eslint/ban-ts-comment for its own
@ts-nocheck line), this file has no such comment to guard against.

SonarCloud: https://sonarcloud.io/project/issues?open=AaDTnyPTvbuDdREVfOER&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
titleAt (in fieldLabels) and titleAtPosition (in navMetricTitleBySeries)
were byte-for-byte identical implementations, just with narrower vs
wider position parameter types. Extracted the shared logic into a
single module-level getAxisTitleAtPosition and had both useMemo
callbacks call it instead. Pure extraction, no behavior change.

SonarCloud: https://sonarcloud.io/project/issues?open=AaDTnyTRvbuDdREVfOES&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
showTooltipForFocusedNode and guardHoverParityAgainstMouseClear each
took 9 positional parameters, 6 of which (markName, dimension, color,
metric, fieldLabels, hasChartInspect) were the same "chart context"
shared between them. Bundled those into a FocusTooltipContext object
(mirroring the BarHoverParityOptions pattern already used by
applyHoverParitySignals in this file), dropping both functions to 4
params. guardHoverParityAgainstMouseClear now forwards its own
context straight through to showTooltipForFocusedNode instead of
re-listing the fields. Pure refactor, no behavior change.

SonarCloud:
https://sonarcloud.io/project/issues?open=AaCnMnrAEnEdGe7Q_SMY&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaCnMnrAEnEdGe7Q_SMZ&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- seriesOrder[seriesOrder.length - 1] -> seriesOrder.at(-1): simpler
  index access, same result (seriesOrder is already guarded non-empty
  above).
- locale prop can be a plain locale-code string OR a Vega Locale
  object ({ number?, time? }) — String(locale) would silently produce
  "[object Object]" for the object form instead of a usable BCP-47
  code. Navigator's own locale prop is string-only, so forward it
  only when it actually is a string and let Navigator's default apply
  otherwise.

SonarCloud:
https://sonarcloud.io/project/issues?open=AaDTnyTRvbuDdREVfOET&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaDTnyTRvbuDdREVfOEU&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces JSON.parse(JSON.stringify(...)) with structuredClone for
deep-cloning both the incoming chart data and the Vega spec before
Vega mutates them in place. structuredClone is faster and correctly
preserves types the JSON round-trip would degrade (e.g. Date fields
in time-series data becoming strings). Fixed in both the s2 and s1
VegaChart.tsx, which had the identical pattern at both call sites.

SonarCloud (s2 data clone, the linked issue):
https://sonarcloud.io/project/issues?open=AZx3iH2jID6Ha28pDOms&id=adobe_react-spectrum-charts

Same rule (typescript:S7784) also flagged on the sibling spec clone
in this file and on both call sites in the s1 mirror; fixed all four
together rather than one at a time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…l chain

- buildNodeLabel/getNodeIdForDatum treat dimensionValue as unknown
  (it comes off an arbitrary data row), so String(dimensionValue) or
  template interpolation could silently produce "[object Object]" if
  a caller's dimension field ever held an object. Bail out the same
  way the existing null check already does, since an object isn't a
  usable dimension value either way.
- metricSeriesLabel && key === metricSeriesLabel.metric -> key ===
  metricSeriesLabel?.metric: same short-circuit behavior (key is
  always a string, never undefined, so the equality can only be true
  when metricSeriesLabel is defined — TS narrows it correctly inside
  the block).

SonarCloud:
https://sonarcloud.io/project/issues?open=AaDTnyLFvbuDdREVfOEL&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaDTnyLFvbuDdREVfOEN&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaDTnyLFvbuDdREVfOEO&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaDTnyLFvbuDdREVfOEP&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AaCnMnr_EnEdGe7Q_SMa&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both catch blocks swallowed the underlying error entirely, rejecting
with a generic message and losing whatever fetch/blob failure
actually happened. Attach the original error as the new Error's
cause so it's still inspectable (e.g. in DevTools or error reporting)
without changing the rejection message callers already depend on.
Fixed in both s1 and s2, which had the identical pattern.

SonarCloud:
https://sonarcloud.io/project/issues?open=AZx3iH0PID6Ha28pDOmo&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iH0PID6Ha28pDOmp&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Width was number | 'auto' | string — the bare string made 'auto'
redundant (already a string) and let any string through the type
system even though useChartWidth only ever accepts a number, 'auto',
or a percentage string like '75%'. Narrowed it to number | 'auto' |
`${number}%`, mirroring how Height is already number | `${number}%`.
useChartWidth now imports Width instead of redeclaring its own looser
inline union, matching the pattern useChartHeight already follows.
Fixed in both s1 and s2 (identical Width definition and hook in both).

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHzRID6Ha28pDOmm&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same JSON.parse(JSON.stringify(...)) -> structuredClone swap as the
VegaChart fixes. Fixed in both s1 and s2, which had the identical
line.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHz2ID6Ha28pDOmn&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pointMark && pointMark.items.length === 1 -> pointMark?.items.length
=== 1: same short-circuit result (undefined === 1 is false), more
concise. Fixed in both s1 and s2, which had the identical line.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iH1-ID6Ha28pDOmr&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Array#push accepts multiple arguments; two back-to-back push() calls
on the same transform array collapse into one call with both items,
same insertion order. Fixed in both s1 and s2.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHa7ID6Ha28pDOmV&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same Array#push consolidation as barSpecBuilder. Fixed in both s1 and s2.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHeJID6Ha28pDOmc&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
buildSpec already defines safeClone (structuredClone with a
JSON.parse(JSON.stringify(...)) fallback for environments without it)
and calls it on the way out. The earlier "copy so we don't mutate the
original" step duplicated the same JSON round-trip logic instead of
calling safeClone — consolidated onto the one helper.

Left safeClone's own internal fallback branch alone: it's an
intentional feature-detection fallback for runtimes without
structuredClone, not a naive oversight, so replacing it would remove
that support rather than fix a lint nit.

SonarCloud (the mid-function clone this fixes):
https://sonarcloud.io/project/issues?open=AZx3iHdqID6Ha28pDOma&id=adobe_react-spectrum-charts

A second issue was reported on the safeClone fallback itself:
https://sonarcloud.io/project/issues?open=AZx3iHdqID6Ha28pDOmb&id=adobe_react-spectrum-charts
Left as-is per the reasoning above — happy to revisit if that
judgment call is wrong.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doubled backslashes in "\\u00a0\\u00a0" exist so the resulting
Vega expression string contains a literal   escape for Vega's
own expression parser to interpret at signal-evaluation time, not a
JS-level unicode escape. String.raw expresses that directly with
single backslashes instead of doubling them. Verified byte-identical
output and confirmed against the existing test asserting this exact
signal string. Fixed in both s1 and s2, which had the identical line.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHbiID6Ha28pDOmW&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BarAnnotationOptions.style has been marked @deprecated since before
the s1/s2 split, but style.width is still the only way to override an
annotation's computed width and remains part of the public
BarAnnotationProps type — no replacement field was ever added. Rather
than design a new public width prop as a side effect of a lint
cleanup, document why the deprecated field is read here at both call
sites, in both s1 and s2.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHakID6Ha28pDOmT&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
transforms.findIndex(...) === -1 is an existence test; .some() says
that directly instead of comparing an index to a magic -1. Fixed in
both s1 and s2, which had the identical line.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHfyID6Ha28pDOmi&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vega's ScaleField type is string | SignalRef, so scale.domain.fields
entries aren't guaranteed to be plain field names -- a SignalRef would
silently stringify to "[object Object]" via .toString(). Added an
isFieldName type guard and used it everywhere a domain field was
being coerced to a string (getFacets, getFacetsFromKeys,
scaleHasKey), skipping a scale's facet entirely when its field isn't
a plain name rather than producing a broken facet id.

This library's own scale construction (addDomainFields) only ever
pushes plain strings, so this is defensive rather than a fix for an
observed bug -- but the type doesn't guarantee it, and the existing
`as string` cast at two of these call sites was already papering over
exactly this gap. Fixed identically in both s1 and s2.

SonarCloud (the linked issue):
https://sonarcloud.io/project/issues?open=AZx3iHYpID6Ha28pDOmJ&id=adobe_react-spectrum-charts

Same rule (typescript:S6551) was also open on 6 other spots in this
file (lines 65, 90 x2, 95 x2, 110); fixed all of them together.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SpectrumColor | string, NumberFormat's preset union, and
ChartSymbolShape's union all had a bare `| string` appended after a
curated set of named literals, meant to give editor autocomplete for
the common values while still accepting any raw string (a hex color,
an Intl.NumberFormat-style string, etc). TypeScript silently collapses
`Literal | string` to plain `string` for completion purposes, so the
literals were dead weight — which is what Sonar's rule flags.

Replaced `| string` with `| (string & NonNullable<unknown>)`, the
standard "LiteralUnion" idiom: it keeps the literal members
distinguishable from a plain string so editors still suggest them,
while remaining assignable from/to any string at the type level. Used
NonNullable<unknown> rather than the more common `(string & {})`
because this repo's `@typescript-eslint/ban-types` rule bans bare `{}`
as a type and suggests that exact replacement for "any non-nullish
value" in its own error message. No runtime behavior change anywhere.

Fixed at all 19 sites across s1 and s2: SpectrumColor/SpectrumVizColor
color props (axisAnnotation, metricRange, scatterPath, trendline,
referenceLine (s1 only, no s2 equivalent), ColorFacet), plus
NumberFormat and ChartSymbolShape in specUtil.types.ts.

SonarCloud:
https://sonarcloud.io/project/issues?open=AZx3iHXFID6Ha28pDOl9&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHXFID6Ha28pDOl-&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHUMID6Ha28pDOl5&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHVGID6Ha28pDOl6&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHVsID6Ha28pDOl7&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmD&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmE&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmF&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmG&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmH&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHX2ID6Ha28pDOmI&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The comment "Remove undefined values" already stated the intent, but
Sonar can't tell that apart from a naive clone -- and structuredClone
would actually change behavior here: it keeps undefined-valued keys
(just with an undefined value) instead of dropping them entirely like
JSON.stringify does, which Vega's encode entries need. Documented why
the JSON round-trip is the correct choice at this call site rather
than swapping it. Fixed in both s1 and s2.

SonarCloud: https://sonarcloud.io/project/issues?open=AZx3iHZZID6Ha28pDOmR&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ication

Same underlying issue as the earlier legendFacetUtils fix: Vega's
ScaleField type is string | SignalRef, so scale.domain.field(s) isn't
guaranteed to be a plain string. getScaleField (scaleUtils.ts) and
getFacetsFromScales (specUtils.ts) both called .toString() on it
directly; now they only use it when it's actually a string, returning
undefined/skipping otherwise instead of risking "[object Object]".

Also narrowed getPathFromIcon's `Icon | string` parameter to `Icon |
(string & NonNullable<unknown>)`, the same literal-union-preserving
fix as the previous commit, so the named icon literals stay
autocomplete-visible instead of collapsing into plain string.

Fixed in both s1 and s2, which had identical code in all three spots.

SonarCloud:
https://sonarcloud.io/project/issues?open=AZx3iHfPID6Ha28pDOmg&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHfPID6Ha28pDOmh&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHexID6Ha28pDOmd&id=adobe_react-spectrum-charts
https://sonarcloud.io/project/issues?open=AZx3iHexID6Ha28pDOme&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same Array#push consolidation as the earlier barSpecBuilder/
bulletMarkUtils fixes. The s2 version already does this (and combines
even more sources into the one call) -- s1 had fallen behind.

SonarCloud: https://sonarcloud.io/project/issues?open=AZ9NqwwVHjdVvPVL6RiS&id=adobe_react-spectrum-charts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lone"

This reverts commit 4216f3d.

That commit swapped buildSpec's mid-function and final spec clones from
JSON.parse(JSON.stringify(...)) to structuredClone (via a safeClone
helper), to resolve a SonarCloud maintainability nit. It passed every
targeted test run at the time, but broke `S2Line.test.tsx`'s "lines
should be the correct color" test (stroke-width ends up missing
entirely) — visible only when running the full suite, never in
isolation or small groups, which is why it slipped through repeated
verification during that session.

Confirmed via git bisect against main (full-suite runs, not isolated
single-file runs — isolated runs pass at every commit, good or bad,
which is what made the original verification misleading): main passes
292/292 cleanily, and 4216f3d is the first commit where the full
suite drops to 291/292 with this exact failure. Tried using
structuredClone directly (skipping the safeClone wrapper) as an
alternative fix; that still failed, confirming the meaningful
difference is JSON.stringify's behavior itself (e.g. dropping
undefined-valued keys, which structuredClone preserves) rather than
anything about the wrapper function. Reverting restores 292/292.

The underlying SonarCloud finding this reintroduces is a minor
"prefer structuredClone" style nit
(https://sonarcloud.io/project/issues?open=AZx3iHdqID6Ha28pDOma&id=adobe_react-spectrum-charts),
not a functional requirement — not worth re-attempting without a much
narrower, verified-safe change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
S1 (react-spectrum-charts, vega-spec-builder) is in maintenance mode, so
functional changes made only to satisfy SonarCloud are not applied there.
Restores S1 to main and suppresses each existing S1 finding with a
line-level NOSONAR comment, guarded by prettier-ignore where formatting
would move the comment off the reported line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
c-lamoureux and others added 24 commits October 8, 2026 11:52
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rency regex

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix: SonarCloud follow-up for #937 (S1 NOSONAR, S2 fixes)
…s, and number/time dimensions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
fix: S2 bar navigation for multi-field series, dodged-and-stacked bars, and number/time dimensions
Covers entry/exit, drilling, arrows, Escape tooltip dismissal, popover and
onClick activation, click-to-focus, the bottom axis region, and focus across
host updates (kept on no-op/signal updates, reset on rebuilds; a data update
keeping focus is marked as a known failure).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts:
#	packages/react-spectrum-charts-s2/src/RscChart.tsx
#	packages/react-spectrum-charts-s2/src/VegaChart.tsx
#	packages/react-spectrum-charts-s2/src/hooks/useChartImperativeHandle.tsx
#	packages/vega-spec-builder-s2/src/bar/barFocusRingUtils.test.ts
#	packages/vega-spec-builder-s2/src/bar/barFocusRingUtils.ts
#	packages/vega-spec-builder-s2/src/bar/barSpecBuilder.ts
#	packages/vega-spec-builder-s2/src/chartSpecBuilder.ts
#	packages/vega-spec-builder-s2/src/specUtils.ts
#	packages/vega-spec-builder-s2/src/types/marks/supplemental/metricRangeSpec.types.ts
#	packages/vega-spec-builder-s2/src/types/marks/supplemental/trendlineSpec.types.ts
test(s2): pin accessible navigation behavior through Chart
…roller

- navigationSession (was dataNavigatorAdapter) mounts for one view and returns
  destroy/setActions/focusEntry instead of tracking listeners in module WeakMaps
- navigationController remounts only when the view or navigation structure
  changes, after the view's pending run; action changes update in place
- useDataNavigator replaces the Navigator component and RscChart's derivation
- a data update now resets focus to the entry button
- dataNavigator/ may not import React (eslint)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ategies

The session now owns only the shared work: DOM, focus, keys, entry/exit and teardown.
Chart content and the x-axis are standalone NavigationRegions; chart-type specifics
live behind ChartNavStrategy (bar today, line to follow).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The spec builder records navigable legends in usermeta; the session adds a
legend region whose items mirror chart content's leaves. Legend clicks and
hovers are dispatched through a per-view event registry, and the controller
restores focus to the same node across rebuilds (including hiddenSeries toggles).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d legend series

Dispatches a synthetic contextmenu at the focused mark's center through the
view's own listeners, so rightClick popovers and onContextMenu run unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ed accessibleNavigation module

Bar keeps its own rings but uses the shared ring stroke, offsets and navigation key helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…loud

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ity guide

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Non-bar marks, trellised bars, and charts with more than one mark now get no
focus rings, legend navigation, or navigation config, and a console warning
says why.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lopment

After building the navigation, development builds compare its leaves with the
bar mark's rendered items and warn about leaves with no bar and bars no leaf
reaches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion

Renders each bar dashboard variation with every dataset and accessibleNavigation
on, and requires it to navigate in step with the rendered bars, or to be listed
as unsupported with a reason. The dashboard gains a "Keyboard navigation" switch.

Fixes the crash this found: hiding every series left the chart content without
an entry point. Navigation now starts at the legend so series can be shown again.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@c-lamoureux c-lamoureux changed the title feat(s2): experimental keyboard navigation for bar charts feat(s2): legend navigation, keyboard right-click and hardening for the data navigator Nov 10, 2026
Wraps unionBounds in the reduce callback so it doesn't receive the index and
array, and drops a redundant `| string` from NumberFormat parameters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@c-lamoureux
c-lamoureux marked this pull request as ready for review November 10, 2026 20:28
Adopt the shared S2 message catalog from main: navigation strings (including
the legend descriptions) now live in intl/en-US.json, the navigator reads the
Provider locale, and the region, entry button and exit note are localized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

This branch has not been deployed

No deployments
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.

1 participant