Skip to content

fix(core): correct read only property#90

Open
coryrylan wants to merge 1 commit into
mainfrom
topic-readonly-fix
Open

fix(core): correct read only property#90
coryrylan wants to merge 1 commit into
mainfrom
topic-readonly-fix

Conversation

@coryrylan
Copy link
Copy Markdown
Collaborator

@coryrylan coryrylan commented May 18, 2026

  • fix readonly to readOnly to align with native input type readOnly property

Summary by CodeRabbit

  • New Features

    • Introduced a canonical readOnly property as the standard API for marking components read-only.
  • Deprecation

    • Marked the legacy readonly property as deprecated while preserving compatibility via an alias.
  • Documentation

    • Updated docs and examples to reference the new readOnly property.
  • Tests

    • Updated and added tests for readOnly behavior and attribute/property synchronization.
  • Chores

    • Added a lint rule and tests to encourage use of readOnly with attribute mapping.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

This PR renames the host property from readonly to the DOM-native readOnly across BaseButton, state and type controllers, tests, types, ESLint rule, and docs; readonly remains the HTML attribute and BaseButton exposes a deprecated readonly accessor.

Changes

Readonly Property Rename Across Components

Layer / File(s) Summary
BaseButton readOnly property and deprecated readonly accessor
projects/core/src/internal/base/button.ts, projects/core/src/internal/base/button.test.ts
BaseButton introduces readOnly as the primary reactive property reflecting to the readonly attribute and provides a deprecated readonly getter/setter for backward compatibility. Tests verify attribute↔property synchronization and event-listener behavior when readOnly changes.
State controller property rename
projects/core/src/internal/controllers/state-current.controller.ts, projects/core/src/internal/controllers/state-current.controller.test.ts, projects/core/src/internal/controllers/state-disabled.controller.ts, projects/core/src/internal/controllers/state-disabled.controller.test.ts, projects/core/src/internal/controllers/state-expanded.controller.ts, projects/core/src/internal/controllers/state-expanded.controller.test.ts, projects/core/src/internal/controllers/state-pressed.controller.ts, projects/core/src/internal/controllers/state-pressed.controller.test.ts, projects/core/src/internal/controllers/state-selected.controller.ts, projects/core/src/internal/controllers/state-selected.controller.test.ts
All state controllers update their host type constraints and runtime checks from readonlyreadOnly. Test helpers and unit tests are updated to use readOnly bound to the readonly attribute and to toggle/read that property.
Type controller property rename
projects/core/src/internal/controllers/type-anchor.controller.ts, projects/core/src/internal/controllers/type-anchor.controller.test.ts, projects/core/src/internal/controllers/type-button.controller.ts, projects/core/src/internal/controllers/type-button.controller.test.ts, projects/core/src/internal/controllers/type-command.controller.ts, projects/core/src/internal/controllers/type-command.controller.test.ts, projects/core/src/internal/controllers/type-interest.controller.ts, projects/core/src/internal/controllers/type-interest.controller.test.ts, projects/core/src/internal/controllers/type-submit.controller.ts, projects/core/src/internal/controllers/type-submit.controller.test.ts
All type controllers rename interface properties to readOnly, update controller checks to host.readOnly, and update test elements and tests (including event-listener assertions) to use the new property name and attribute mapping.
Public API update and validation enforcement
projects/core/src/internal/types/index.ts, projects/internals/eslint/src/local/reserved-property-names.js, projects/internals/eslint/src/local/reserved-property-names.test.js, projects/site/src/docs/elements/tag.md
NveElement adds readOnly? and deprecates readonly?. ESLint reserved-property-names adds a special-case error for decorated readonly properties requiring readOnly with attribute: 'readonly'. Tests and docs updated accordingly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • johnyanarella
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: renaming the property from 'readonly' to 'readOnly' to align with native DOM conventions across all affected files and controllers.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch topic-readonly-fix

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/core/src/internal/controllers/state-selected.controller.test.ts (1)

105-118: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a transition test for anchor-selected → readOnly=true.

Please add a case that first sets selected anchor state, waits stable, then toggles readOnly = true and asserts aria-current is removed. This guards the runtime transition path.

Also applies to: 120-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/core/src/internal/controllers/state-selected.controller.test.ts`
around lines 105 - 118, Add a new test that exercises the runtime transition
from an anchor-selected state into readOnly: create an anchor element `a`,
append it to `element`, set `element.selected = true`, add the internal anchor
state via `element._internals.states.add('anchor')`, call
`element.requestUpdate()` and `await elementIsStable(element)` to stabilize,
then toggle `element.readOnly = true`, await stability again, and assert that
`a.getAttribute('aria-current')` is null (and that
`element._internals.ariaSelected` is null and
`element.matches(':state(selected)')` is false) to verify `aria-current` is
removed; add the same transition-style test for the other affected block noted
around the 120-131 region.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@projects/core/src/internal/controllers/state-selected.controller.ts`:
- Around line 30-33: The read-only branch exits without clearing any
anchor-specific aria state, so when this.host.readOnly is true you must also
clear any previously set anchor aria-current state: add code in the same block
(where this.host._internals!.ariaSelected is set to null and
this.host._internals!.states.delete('selected') is called) to clear the anchor
aria-current state—e.g. set this.host._internals!.ariaCurrent = null (or remove
the attribute on the stored anchor element, e.g.
this.host._internals!.anchor?.removeAttribute('aria-current')) so no stale
aria-current remains when switching to read-only.

---

Outside diff comments:
In `@projects/core/src/internal/controllers/state-selected.controller.test.ts`:
- Around line 105-118: Add a new test that exercises the runtime transition from
an anchor-selected state into readOnly: create an anchor element `a`, append it
to `element`, set `element.selected = true`, add the internal anchor state via
`element._internals.states.add('anchor')`, call `element.requestUpdate()` and
`await elementIsStable(element)` to stabilize, then toggle `element.readOnly =
true`, await stability again, and assert that `a.getAttribute('aria-current')`
is null (and that `element._internals.ariaSelected` is null and
`element.matches(':state(selected)')` is false) to verify `aria-current` is
removed; add the same transition-style test for the other affected block noted
around the 120-131 region.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b64ac6e0-5d5b-450a-ad84-3211122224f6

📥 Commits

Reviewing files that changed from the base of the PR and between b931ff5 and 45df4e3.

📒 Files selected for processing (26)
  • projects/core/src/internal/base/button.test.ts
  • projects/core/src/internal/base/button.ts
  • projects/core/src/internal/controllers/state-current.controller.test.ts
  • projects/core/src/internal/controllers/state-current.controller.ts
  • projects/core/src/internal/controllers/state-disabled.controller.test.ts
  • projects/core/src/internal/controllers/state-disabled.controller.ts
  • projects/core/src/internal/controllers/state-expanded.controller.test.ts
  • projects/core/src/internal/controllers/state-expanded.controller.ts
  • projects/core/src/internal/controllers/state-pressed.controller.test.ts
  • projects/core/src/internal/controllers/state-pressed.controller.ts
  • projects/core/src/internal/controllers/state-selected.controller.test.ts
  • projects/core/src/internal/controllers/state-selected.controller.ts
  • projects/core/src/internal/controllers/type-anchor.controller.test.ts
  • projects/core/src/internal/controllers/type-anchor.controller.ts
  • projects/core/src/internal/controllers/type-button.controller.test.ts
  • projects/core/src/internal/controllers/type-button.controller.ts
  • projects/core/src/internal/controllers/type-command.controller.test.ts
  • projects/core/src/internal/controllers/type-command.controller.ts
  • projects/core/src/internal/controllers/type-interest.controller.test.ts
  • projects/core/src/internal/controllers/type-interest.controller.ts
  • projects/core/src/internal/controllers/type-submit.controller.test.ts
  • projects/core/src/internal/controllers/type-submit.controller.ts
  • projects/core/src/internal/types/index.ts
  • projects/internals/eslint/src/local/reserved-property-names.js
  • projects/internals/eslint/src/local/reserved-property-names.test.js
  • projects/site/src/docs/elements/tag.md

Comment thread projects/core/src/internal/controllers/state-selected.controller.ts
@coryrylan coryrylan force-pushed the topic-readonly-fix branch from 45df4e3 to 4b0aef6 Compare May 18, 2026 00:31

it('should remove aria-disabled if readonly', async () => {
element.readonly = true;
element.readOnly = true;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The native input form apis use readOnly for properties and readonly for attributes. This aligns our APIs to the platform read only APis. This updates all of our internal references to use readOnly but is backwards compatible with our pre-existing readonly property. The attribute behavior remains the same as all lowercase readonly. This is important to align on as we expand the form control capabilities.

}

set readonly(value: boolean) {
this.toggleAttribute('readonly', value);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor nit:

There are two subtle ways this aliasing approach changes the deprecated readonly property vs its previous behavior:

  • timing: synchronous reflection in the setter vs Lit's batched reflection in the in update() lifecycle method
  • changed property tracking: changedProperties won't include the deprecated property name in its set (with its oldValue).

The second is likely fine since we're supposed to be the only ones extending this internal base class, and would align any changedProperties optimizations around the new property name.

Did you run into an issue with just having the old property setter setting the new property (rather than toggling the attribute)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I didn't see any differences but I'll double check before merging. Good callout, didn't think about the shift there.

@coryrylan coryrylan force-pushed the topic-readonly-fix branch from 4b0aef6 to d2f7aa3 Compare May 18, 2026 19:26
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
projects/core/src/internal/controllers/state-selected.controller.ts (1)

30-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear anchor aria-current when switching to read-only.

When readOnly becomes true after the anchor branch (lines 36-40) has already set aria-current="page", the early return at line 33 leaves the stale aria-current attribute on the anchor element.

🧹 Proposed fix to clear stale aria-current
  if (this.host.readOnly) {
    this.host._internals!.ariaSelected = null;
    this.host._internals!.states.delete('selected');
+   this.host.querySelector('a')?.removeAttribute('aria-current');
    return;
  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/core/src/internal/controllers/state-selected.controller.ts` around
lines 30 - 33, When handling the readOnly early-return in
state-selected.controller (the block that uses this.host.readOnly and sets
this.host._internals!.ariaSelected = null and
this.host._internals!.states.delete('selected')), also clear any stale
aria-current on the anchor: call
this.host._internals!.anchor?.removeAttribute('aria-current') (or set it to
null) before returning so any previously set aria-current="page" is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@projects/core/src/internal/controllers/state-selected.controller.ts`:
- Around line 30-33: When handling the readOnly early-return in
state-selected.controller (the block that uses this.host.readOnly and sets
this.host._internals!.ariaSelected = null and
this.host._internals!.states.delete('selected')), also clear any stale
aria-current on the anchor: call
this.host._internals!.anchor?.removeAttribute('aria-current') (or set it to
null) before returning so any previously set aria-current="page" is removed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 5c347825-3cab-405c-a7f4-db5e9672134c

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0aef6 and d2f7aa3.

📒 Files selected for processing (26)
  • projects/core/src/internal/base/button.test.ts
  • projects/core/src/internal/base/button.ts
  • projects/core/src/internal/controllers/state-current.controller.test.ts
  • projects/core/src/internal/controllers/state-current.controller.ts
  • projects/core/src/internal/controllers/state-disabled.controller.test.ts
  • projects/core/src/internal/controllers/state-disabled.controller.ts
  • projects/core/src/internal/controllers/state-expanded.controller.test.ts
  • projects/core/src/internal/controllers/state-expanded.controller.ts
  • projects/core/src/internal/controllers/state-pressed.controller.test.ts
  • projects/core/src/internal/controllers/state-pressed.controller.ts
  • projects/core/src/internal/controllers/state-selected.controller.test.ts
  • projects/core/src/internal/controllers/state-selected.controller.ts
  • projects/core/src/internal/controllers/type-anchor.controller.test.ts
  • projects/core/src/internal/controllers/type-anchor.controller.ts
  • projects/core/src/internal/controllers/type-button.controller.test.ts
  • projects/core/src/internal/controllers/type-button.controller.ts
  • projects/core/src/internal/controllers/type-command.controller.test.ts
  • projects/core/src/internal/controllers/type-command.controller.ts
  • projects/core/src/internal/controllers/type-interest.controller.test.ts
  • projects/core/src/internal/controllers/type-interest.controller.ts
  • projects/core/src/internal/controllers/type-submit.controller.test.ts
  • projects/core/src/internal/controllers/type-submit.controller.ts
  • projects/core/src/internal/types/index.ts
  • projects/internals/eslint/src/local/reserved-property-names.js
  • projects/internals/eslint/src/local/reserved-property-names.test.js
  • projects/site/src/docs/elements/tag.md

- fix readonly to readOnly to align with native input type readOnly property

Signed-off-by: Cory Rylan <crylan@nvidia.com>
@coryrylan coryrylan force-pushed the topic-readonly-fix branch from d2f7aa3 to 3e2e571 Compare May 19, 2026 02:27
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
projects/core/src/internal/controllers/state-selected.controller.ts (1)

30-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale anchor aria-current when entering read-only mode.

At Line 30, the early return skips cleanup of any previously set anchor aria-current, so switching from selected-anchor state to read-only can leave stale aria-current="page".

Suggested fix
   if (this.host.readOnly) {
     this.host._internals!.ariaSelected = null;
     this.host._internals!.states.delete('selected');
+    this.host.querySelector('a')?.removeAttribute('aria-current');
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@projects/core/src/internal/controllers/state-selected.controller.ts` around
lines 30 - 33, The early return when this.host.readOnly skips clearing any
anchor aria-current, leaving stale aria-current="page"; update the read-only
branch in state-selected.controller by also clearing the anchor current
state—e.g., set this.host._internals!.ariaCurrent (or the internals property
that tracks aria-current) to null/undefined and remove any
'current'/'aria-current' state before returning, alongside the existing
this.host._internals!.ariaSelected = null and
this.host._internals!.states.delete('selected').
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@projects/core/src/internal/controllers/state-selected.controller.ts`:
- Around line 30-33: The early return when this.host.readOnly skips clearing any
anchor aria-current, leaving stale aria-current="page"; update the read-only
branch in state-selected.controller by also clearing the anchor current
state—e.g., set this.host._internals!.ariaCurrent (or the internals property
that tracks aria-current) to null/undefined and remove any
'current'/'aria-current' state before returning, alongside the existing
this.host._internals!.ariaSelected = null and
this.host._internals!.states.delete('selected').

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 80bfb338-fe80-4161-9c65-53ddbcf64268

📥 Commits

Reviewing files that changed from the base of the PR and between d2f7aa3 and 3e2e571.

📒 Files selected for processing (26)
  • projects/core/src/internal/base/button.test.ts
  • projects/core/src/internal/base/button.ts
  • projects/core/src/internal/controllers/state-current.controller.test.ts
  • projects/core/src/internal/controllers/state-current.controller.ts
  • projects/core/src/internal/controllers/state-disabled.controller.test.ts
  • projects/core/src/internal/controllers/state-disabled.controller.ts
  • projects/core/src/internal/controllers/state-expanded.controller.test.ts
  • projects/core/src/internal/controllers/state-expanded.controller.ts
  • projects/core/src/internal/controllers/state-pressed.controller.test.ts
  • projects/core/src/internal/controllers/state-pressed.controller.ts
  • projects/core/src/internal/controllers/state-selected.controller.test.ts
  • projects/core/src/internal/controllers/state-selected.controller.ts
  • projects/core/src/internal/controllers/type-anchor.controller.test.ts
  • projects/core/src/internal/controllers/type-anchor.controller.ts
  • projects/core/src/internal/controllers/type-button.controller.test.ts
  • projects/core/src/internal/controllers/type-button.controller.ts
  • projects/core/src/internal/controllers/type-command.controller.test.ts
  • projects/core/src/internal/controllers/type-command.controller.ts
  • projects/core/src/internal/controllers/type-interest.controller.test.ts
  • projects/core/src/internal/controllers/type-interest.controller.ts
  • projects/core/src/internal/controllers/type-submit.controller.test.ts
  • projects/core/src/internal/controllers/type-submit.controller.ts
  • projects/core/src/internal/types/index.ts
  • projects/internals/eslint/src/local/reserved-property-names.js
  • projects/internals/eslint/src/local/reserved-property-names.test.js
  • projects/site/src/docs/elements/tag.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants