Skip to content

React parity: horizontal layout + seat colouring - #22

Merged
McSpace merged 25 commits into
mainfrom
dev
Jun 25, 2026
Merged

React parity: horizontal layout + seat colouring#22
McSpace merged 25 commits into
mainfrom
dev

Conversation

@McSpace

@McSpace McSpace commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings the Angular seatmap lib closer to the upstream React lib across two areas (the dev / React-parity phase). Unit 374/374, e2e green.

Horizontal layout parity (prior work on dev)

  • Nose/tail direction driven by rightToLeft; deck stacking order + nose/tail order flipped for horizontal LTR.
  • Tooltip kept on-screen & upright in horizontal (P1b).
  • Container reserves the full rotated footprint (incl. cabin labels, wings, nose/tail caps); re-measures on in-place horizontal toggle; demo stacks controls below the rotated map.
  • Nose/tail-to-fuselage join gap closed; galley bulks kept intact above staggered Business pods.

Seat-colour React parity (this phase)

  • Removed the Angular-only colorfulSeatsByScore gate and colorfulSeatsByClass lightness tint (+ seatClassTints, color-tint.ts).
  • Score colouring now driven solely by customSeatColorRanges (React parity).
  • Added a data-driven customSeatColorClasses palette (class → colour).
  • Priority: seatAvailableColor (force) > availability colour > customSeatColorRanges (score) > customSeatColorClasses (class) > API seat.color > theme default.
  • Demo expresses the old on/off toggles via these config options; README documents the priority.
  • Design spec + plan under docs/superpowers/.

Test plan

  • Unit: npx ng test seatmap-lib --watch=false → 374/374.
  • e2e: seat-colour priority (4 modes + availability override), customSeatColorRanges, exitIcons, horizontal layout → all green.

Notes

  • WCAG work ships as a separate PR (kept independent for reviewability).

🤖 Generated with Claude Code

McSpace and others added 24 commits June 19, 2026 16:19
`_resolveBulkOverlaps` measured a row's bbox via `getNativeRowHeight`,
which only inspects per-seat-type heights and ignores per-seat
`topOffset`. On staggered Business pods (LH470 row 4: seat 4D at
topOffset=-172, 4C/4H at +45) the real bottom edge of the row was
under-reported, the galley bulk just below it looked merely lightly
grazed, and the resolver shrunk its native height to ~22% — landing
just above the 20% safety floor and rendering as an invisible 11-px
strip while only the galley sticker stayed visible.

Add `getNativeRowBBox` to walk non-aisle seats with their `topOffset`
included and use it in `_resolveBulkOverlaps`. The LH470 case now
overlaps the bulk deeply enough to trip the safety floor, so the
partition keeps its original geometry — matching the React renderer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the React lib: in horizontal mode the nose and tail flip 180deg when
not rightToLeft, so the cabin points the same way as the React reference.
Adds horizontal/rightToLeft inputs to JetsNose/JetsTail and a transform
binding; covered by unit tests (RED->GREEN).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nose/tail order)

Mirror React PlaneBody/index.js for horizontal LTR: rotate the deck wrapper
180deg, render the tail first / nose last, and thread horizontal/rightToLeft
into the nose and tail so they flip too. Gated on horizontal && !rightToLeft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the resolved config flags into the plane body so the nose/tail/deck
flip logic activates. Verified live: rightToLeft now controls the nose
direction in horizontal mode (false=nose left, true=nose right), matching
the React reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (P1b)

Move the horizontal rotate(90deg) onto an inner .jets-seat-map__rotor wrapper
so the tooltip stays a sibling in the un-rotated container (mirrors the React
structure). The tooltip is then anchored by an explicit screen-space left/top
and a --horizontal CSS rule frees the right:8px constraint that was squeezing
it to a sliver. Previously the tooltip rendered far off-screen (x~2476).

_buildLayoutData now measures the rotor so the layoutUpdated payload is
unchanged. Vertical tooltip (arrow, positioning) is unaffected. Unit + smoke
e2e green; verified on-screen in the demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror React PlaneBody decks.reverse(): in horizontal && !rightToLeft, a
stacked multi-deck cabin reverses its deck order so the decks read correctly
after the rotor's 90deg rotation. Single-deck mode is unchanged (the active
deck is shown regardless of orientation). getDeckIndex still returns the
original content index, so data-deck-index / _buildLayoutData are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In horizontal mode the container kept its tall vertical height (e.g. 400x3139)
while the rotated rotor rendered 3139x359, leaving a huge gap below the cabin.
Measure the rotor's transform-immune offset size post-render and swap it onto
the container (width = rotor height, height = rotor width), mirroring React's
outer 'width: scaledTotalDecksHeight'. A --horizontal modifier aligns the rotor
to the start (it pivots top-left) and drops the vertical min-height.

Also simplifies mapTransform (the scaleType ternary had identical branches;
Angular bakes scale into deck rendering, so the offset is plain translateY(-100%)).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Behavioural guards for the horizontal React-parity work: nose points left in
LTR and right in RTL (rightToLeft drives the direction), and the built-in
tooltip stays fully within the viewport in horizontal mode (P1b regression).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rotor refactor (e3043bb) made _buildLayoutData measure the rotor instead
of the container, shifting widthInPx by the fuselage inset (~400->359) and
breaking the seatMapInited React-parity payload test. Measure the container
again; in horizontal it now carries the swapped dimensions, so the payload
stays correct without the rotor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nose & tail outline paths start 1.5 viewBox units inside the SVG border,
so they rendered slightly narrower than the fuselage and left a visible step
where they meet it (most noticeable on wide cabins like QT888). Scale each up
by 200/(200 - 2*effectiveInset), where effectiveInset = 1.5 - strokeWidth/2,
so the outline centre lines up with the fuselage border centre. Mirrors the
intent of React's Nose/Tail `distanceFromBorder` compensation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In horizontal mode the rotated cabin can be far wider than the controls
column, so the demo's flex-row layout squeezed the "Send Events" panel and
event log into a sliver pushed off-screen to the right (the e2e helper even
needs `force:true` to click through the overlap).

Drive a `--horizontal` modifier from the active config's `horizontal` flag:
switch `.demo-content` to a vertical stack and let the wide map scroll inside
its own wrapper (`overflow-x:auto`) so the controls stay full-width and
on-screen below the map.

Also make the e2e playwright config honour `PW_BASE_URL` so the suite can
target an already-running dev server (e.g. the dev worktree on :4201) without
editing the file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggle

Toggling `horizontal` (or other layout-affecting flags) via a config change
rather than a full flight reload skipped `_updateHorizontalDims()`, so the
swapped container width/height stayed null from the initial vertical load. The
`.jets-seat-map` container then collapsed to the rotor's un-rotated layout
height (~3139px for a long cabin), leaving a large empty gap above the page
controls.

Re-run the swapped-dimension measurement after the view re-renders on a
settings-only config change so the container reserves the wide, short
horizontal footprint.

Covered by a new e2e that toggles horizontal on an already-loaded map and
asserts the reserved height stays smaller than the width.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s, wings, nose

The horizontal container only reserved the fuselage box (`offsetWidth`), so the
absolutely-positioned side cabin labels and wings (~20px past the fuselage on
each side) and the nose/tail caps were sheared off once the wrapper clipped
overflow: cabin titles were cut at the bottom and the nose ran off the
top-left.

Measure the union of every rotor descendant's painted rect to reserve the true
footprint, and nudge the rotor (a leading `translate` in the transform) so the
top-left overflow is pulled back inside the box. The offset compensates for the
already-applied shift so repeated measurements converge instead of oscillating.

Covered by a new e2e asserting the reserved height exceeds the fuselage strip
and that the nose and cabin labels stay within the container bounds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…alette

Remove the Angular-only colorfulSeatsByScore gate and colorfulSeatsByClass
lightness tint; add a data-driven customSeatColorClasses palette. Demo apps
implement the on/off toggles on top of customSeatColorRanges /
customSeatColorClasses / seatAvailableColor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task-by-task TDD plan: add customSeatColorClasses, drop the colorfulSeatsByScore
gate and colorfulSeatsByClass tint, clean up demo/e2e configs, document priority.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… dedup score unit test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pdate demo + e2e

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders each demo toggle combination via the new library config options
(seatAvailableColor / customSeatColorRanges / customSeatColorClasses) with all
seats available, capturing one screenshot per mode for visual review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@McSpace
McSpace requested a review from nexus June 24, 2026 14:17
} from '../constants';
import { BULK_SCALE_BY_ID, DEFAULT_BULK_SCALE } from './bulk-template.service';
import { getNativeRowHeight } from '../utils/cabin-utils';
import { getNativeRowBBox, getNativeRowHeight } from '../utils/cabin-utils';
@nexus nexus removed their assignment Jun 24, 2026
* Bump version after documentation fix

* Bump lib version;

* fix(security): resolve code-scanning alerts

CodeQL js/unused-local-variable (#1,5,6,35,36):
- drop unused imports IPassenger, IInitialLayoutData, FEATURE_ICONS
- drop unused destructured rest in demo updateTextareasForFlight

Dependencies (#29):
- npm audit fix (lockfile-only): clears 4 transitive advisories
  (hono, undici, qs, tar). Remaining @angular/* advisories are only
  patched in v22 (major) and ship as peerDependencies, so they are
  the consuming app's responsibility, not this library's artifact.

Scorecard Pinned-Dependencies (#40,#41):
- pin `npm install -g npm` to 11.5.1 instead of @latest in both
  publish workflows for reproducible builds.

Non-applicable Scorecard metrics (#24 Maintained, #25 Code-Review,
#27 CII-Best-Practices, #28 Fuzzing) dismissed via API as won't-fix.

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

---------

Co-authored-by: Iurii Perevertailo <the.one.nexus@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@McSpace
McSpace merged commit c4b1901 into main Jun 25, 2026
5 checks passed
McSpace added a commit that referenced this pull request Jun 26, 2026
Resolves PR #23 conflicts after #22 (React-parity seat colouring) landed
on main. #22 removed the colorfulSeatsByClass/colorfulSeatsByScore flags,
seatClassTints and color-tint util; this branch still carried them plus
the WCAG a11y work on top.

Conflict resolutions:
- nose/tail/tooltip components: keep WCAG a11y attributes (aria-hidden,
  role/aria-labelledby/aria-describedby, escape/keydown handlers).
- seat-map.component.ts: drop colorfulSeatsBy* (adopt #22) and keep WCAG's
  memoised _mergedThemeValue colourTheme.
- seat-map.component.html: keep WCAG template (a11y wrappers, list view,
  rotor); drop the deck's [colorfulSeatsByClass] binding (#22 removal).
- jets-seat.component.spec.ts: keep ARIA + grid-cell tests; drop the
  obsolete colorfulSeatsByClass describe block (#22 removed the feature).
- horizontalParity.spec.ts: keep WCAG's P1a arrow-key test.
- re-add IPassenger import to jets-tooltip.component.ts (used by WCAG's
  buildRestrictedPassengerTypeMessage; was unused on main).
- e2e baseline screenshots taken from main; regenerated separately.

Verified: build:lib OK, unit tests 587/587 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nexus
nexus deleted the dev branch June 26, 2026 08:59
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.

3 participants