Skip to content

fix: restore camelCase support in setPrefixedStyle - #1139

Merged
redfish4ktc merged 3 commits into
mainfrom
fix/1046-set-prefixed-style-camel-case
Aug 11, 2026
Merged

fix: restore camelCase support in setPrefixedStyle#1139
redfish4ktc merged 3 commits into
mainfrom
fix/1046-set-prefixed-style-camel-case

Conversation

@redfish4ktc

@redfish4ktc redfish4ktc commented Aug 10, 2026

Copy link
Copy Markdown
Member

Problem

setPrefixedStyle writes CSS properties with CSSStyleDeclaration.setProperty, which only accepts kebab-case names. Any camelCase name is lowercased, matches no known property, and is silently discarded.

The reported symptom is the cell editor: CellEditorHandler calls setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px'), so the textarea never receives its transform origin and the editing overlay is visibly offset from the cell label at any zoom level other than 1x.

The vendor prefixed write was broken too, for every input spelling. The prefix is built by capitalizing the first character of the name, which produces webkit-cased names such as WebkitTransformOrigin. Those are CSSOM IDL attribute spellings, not CSS property names, so setProperty discards them as well. Feeding the function a kebab-case name does not help either: it yields the meaningless WebkitTransform-origin. Consequently the prefixed write in RubberBandHandler ('transition') has also been dead, even though its standard write happens to work, single word names surviving the lowercasing intact.

Root cause

Commit 61648e4, a large "Converting *Handlers into plugins. Keep resolving errors" refactor, changed exactly two lines of this function:

-  style[name] = value;
+  style.setProperty(name, value);
   if (prefix !== null && name.length > 0) {
     name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
-    style[name] = value;
+    style.setProperty(name, value);

style[name] = value does not compile here (TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'), so this was a type error being silenced during a mass refactor, not a deliberate API change. The kebab-case requirement was never chosen, and the JSDoc has kept documenting camelCase ever since.

Fix

Restore the property assignment used by mxGraph, with the cast that commit was avoiding:

const properties = style as unknown as Record<string, string>;
properties[name] = value;

if (!isNullish(prefix) && name.length > 0) {
  name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
  properties[name] = value;
}

Property assignment reaches the CSSOM camel-cased, dashed and webkit-cased attributes, so both writes work again. A comment records why the assignment is deliberate, so it does not get "fixed" back to setProperty.

Both call sites are left untouched: their existing camelCase arguments become correct again, rather than needing to change.

CSS custom properties are the one case that must keep using setProperty, so they get an early return before the prefix logic:

if (name.startsWith('--')) {
  style.setProperty(name, value);
  return;
}

Unlike standard properties, a custom property has no attribute on CSSStyleDeclaration, so assigning one would create a JavaScript property and no CSS declaration at all, and it is never vendor prefixed. This capability is not inherited from mxGraph, which used assignment and had the same limitation: it appeared as a side effect of 61648e4, the very commit that introduced the bug fixed here. It has been available for four years, so it is preserved rather than silently removed.

Vendor prefixing is deliberately kept rather than removed. It is very likely obsolete (transform-origin and transition have been unprefixed for over a decade, the browser detection relies on user agent sniffing, and the project targets modern browsers only), but the impact on external consumers calling this exported utility for other properties is unknown, so removing it belongs in its own change.

Documented limitation

The mangling only produces a valid name from camelCase input, which the JSDoc now states explicitly:

WARNING: name must be written in camelCase, as in the example above.

A kebab-case name such as transform-origin still sets the standard property, but silently skips the vendor prefixed one.

A CSS custom property such as --overlay-offset is set as is, and is never vendor prefixed.

Tests

The function had no test coverage. 14 cases added in packages/core/__tests__/util/styleUtils.test.ts, each written before the corresponding fix and confirmed to fail against the previous implementation (7 failures for the camelCase standard write plus every vendor prefixed write, then 3 more for the custom properties):

  • standard property set from camelCase, kebab-case and single word names
  • no vendor property written when no prefix applies
  • both properties written on Safari, Chrome and Firefox
  • single word names get prefixed too, covering the dead RubberBandHandler write
  • Webkit prefix wins over Moz when both flags are set
  • the name.length > 0 guard for an empty name
  • the kebab-case limitation, asserting the malformed WebkitTransform-origin, so the JSDoc warning has an executable counterpart
  • custom properties set with and without a vendor prefix active, and never given a prefixed variant, asserting cssText is exactly --overlay-offset: 10px; so a JavaScript property masquerading as a CSS declaration cannot pass

Two implementation notes. Client.IS_SF is true by default under jsdom, so each test sets the browser flags explicitly instead of inheriting the environment; those flags are global mutable state, captured before any test runs and restored after each one. And jsdom implements no vendor prefixed CSS property, so a prefixed assignment lands as a plain JavaScript property on the declaration, which a helper reads. That is the only way this path is observable in jsdom, and it works precisely because the fix uses assignment rather than setProperty.

Validation

  • npm test -w packages/core: 535 tests passed, 60 suites
  • npm run test-check -w packages/core and tsc --noEmit: clean
  • npm run lint: clean

✔️ Not covered by automated tests: the visual outcome. The tests prove transform-origin reaches the textarea style, not that the overlay aligns on screen. Manual check for a reviewer: open Storybook, zoom to a non-1x level, double-click a cell, and confirm the editor lines up with the label.

Fixes #1046

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes

    • Improved application of standard and vendor-prefixed styles across supported browsers.
    • Added reliable support for camelCase properties and CSS custom properties.
    • Improved prefix selection and handling of empty or missing property names.
    • Ensured style updates use the appropriate browser-compatible format.
  • Tests

    • Expanded coverage for browser-specific styling, prefix precedence, property naming formats, custom properties, and edge cases.
    • Added checks to ensure browser-state behavior remains consistent.

setPrefixedStyle used CSSStyleDeclaration.setProperty, which only accepts kebab-case names. Every camelCase call
site was therefore a silent no-op, including the transformOrigin write in CellEditorHandler that keeps the cell
editor overlay aligned with the label when the graph is zoomed.

The vendor prefixed write was dead for every input spelling: the prefix is built by capitalizing the first
character, producing webkit-cased names such as WebkitTransformOrigin, which setProperty always discards.

Restore the property assignment used by mxGraph, which accepts both spellings for the standard property and the
webkit-cased name for the prefixed one. It requires a cast to compile, which is what commit 61648e4 was avoiding
when it introduced the regression, so a comment now records why the assignment is deliberate. Both call sites are
left untouched, as their existing camelCase arguments become correct again.

Document in the JSDoc that camelCase is required to reach the vendor prefixed path, and cover the function with
tests, including the kebab-case limitation and the per-browser prefixes.

Fixes #1046
@redfish4ktc redfish4ktc added the bug Something isn't working label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

setPrefixedStyle now supports camelCase CSS properties through indexed style assignment. It handles custom properties and vendor prefixes. Tests cover browser flags, prefix precedence, property formats, edge cases, and global state restoration.

Changes

Prefixed style assignment

Layer / File(s) Summary
Style assignment implementation
packages/core/src/util/styleUtils.ts
setPrefixedStyle uses indexed CSSStyleDeclaration assignment for standard and vendor-prefixed properties, uses setProperty for CSS custom properties, checks nullish prefixes, returns void, and documents the camelCase requirement.
Style assignment validation
packages/core/__tests__/util/styleUtils.test.ts
Tests cover standard and custom properties, Safari, Chrome, and Firefox prefixes, prefix precedence, empty names, kebab-case behavior, and restoration of Client flags.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #1046 by restoring camelCase support and preserving CSS custom property handling with regression tests.
Out of Scope Changes check ✅ Passed The implementation, documentation, and tests are limited to the linked issue objectives and related regression coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title is concise, specific, and follows Conventional Commits while accurately describing the primary fix.
Description check ✅ Passed The description clearly covers the problem, root cause, fix, tests, validation, limitation, visual check, and linked issue.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 41cd3090-674b-462d-aae6-62b808fb5122

📥 Commits

Reviewing files that changed from the base of the PR and between 5c884e2 and 91e09b4.

📒 Files selected for processing (2)
  • packages/core/__tests__/util/styleUtils.test.ts
  • packages/core/src/util/styleUtils.ts

Comment thread packages/core/src/util/styleUtils.ts
redfish4ktc and others added 2 commits August 10, 2026 16:33
CSS custom properties have no attribute on CSSStyleDeclaration, unlike standard properties which expose camel-cased,
dashed and webkit-cased attributes. Assigning one only creates a JavaScript property and no CSS declaration, so
switching this function to property assignment would have dropped support for calls such as
setPrefixedStyle(style, '--overlay-offset', '10px').

That support does not come from mxGraph, which had the same limitation as the assignment. It appeared as a side
effect of the switch to setProperty and has been available for four years, so it is preserved rather than silently
removed.

Route names starting with -- through setProperty and return early, since custom properties are never vendor
prefixed.
@sonarqubecloud

Copy link
Copy Markdown

@redfish4ktc
redfish4ktc merged commit 76c4e44 into main Aug 11, 2026
14 checks passed
@redfish4ktc
redfish4ktc deleted the fix/1046-set-prefixed-style-camel-case branch August 11, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] setPrefixedStyle silently fails for camelCase CSS property

1 participant