Skip to content

Commit b9e7588

Browse files
committed
fix(core): correct read only property
- fix readonly to readOnly to align with native input type readOnly property Signed-off-by: Cory Rylan <crylan@nvidia.com>
1 parent 3c83f7d commit b9e7588

26 files changed

Lines changed: 168 additions & 59 deletions

projects/core/src/internal/base/button.test.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe('base button', () => {
7979
});
8080

8181
it('should remove aria-disabled if readonly', async () => {
82-
element.readonly = true;
82+
element.readOnly = true;
8383
await elementIsStable(element);
8484
expect(element._internals.ariaDisabled).toBe(null);
8585
expect(element.matches(':state(disabled)')).toBe(false);
@@ -117,7 +117,7 @@ describe('base button', () => {
117117
expect(element._internals.ariaExpanded).toBe('true');
118118
expect(element.matches(':state(expanded)')).toBe(true);
119119

120-
element.readonly = true;
120+
element.readOnly = true;
121121
await elementIsStable(element);
122122
expect(element._internals.ariaExpanded).toBe(null);
123123
expect(element.matches(':state(expanded)')).toBe(false);
@@ -149,7 +149,7 @@ describe('base button', () => {
149149
expect(element._internals.ariaPressed).toBe('true');
150150
expect(element.matches(':state(pressed)')).toBe(true);
151151

152-
element.readonly = true;
152+
element.readOnly = true;
153153
await elementIsStable(element);
154154
expect(element._internals.ariaPressed).toBe(null);
155155
expect(element.matches(':state(pressed)')).toBe(false);
@@ -172,31 +172,54 @@ describe('base button', () => {
172172
});
173173

174174
it('should remove tabindex and role if readonly', async () => {
175-
element.readonly = true;
175+
element.readOnly = true;
176176
await elementIsStable(element);
177177
expect(element.tabIndex).toBe(-1);
178178
expect(element._internals.role).toBe('none');
179179
expect(element.getAttribute('role')).toBe(null);
180180
});
181181

182+
it('should map readonly attribute to readOnly property', async () => {
183+
element.setAttribute('readonly', '');
184+
await elementIsStable(element);
185+
expect(element.readOnly).toBe(true);
186+
});
187+
188+
it('should reflect readOnly property to readonly attribute', async () => {
189+
element.readOnly = true;
190+
await elementIsStable(element);
191+
expect(element.hasAttribute('readonly')).toBe(true);
192+
193+
element.readOnly = false;
194+
await elementIsStable(element);
195+
expect(element.hasAttribute('readonly')).toBe(false);
196+
});
197+
198+
it('should support deprecated readonly property alias', async () => {
199+
element.readonly = true;
200+
await elementIsStable(element);
201+
expect(element.readOnly).toBe(true);
202+
expect(element.hasAttribute('readonly')).toBe(true);
203+
});
204+
182205
it('should set the button type to submit if not defined within a form element', async () => {
183206
await elementIsStable(element);
184207
expect(element.type).toBe(undefined);
185208
expect(buttonInForm.type).toBe('button');
186209
expect(submitButtonInForm.type).toBe('submit');
187210
});
188211

189-
it('should add or remove button event listeners when readonly updates', async () => {
212+
it('should add or remove button event listeners when readOnly updates', async () => {
190213
await elementIsStable(submitButtonInForm);
191-
expect(submitButtonInForm.readonly).toBe(undefined);
214+
expect(submitButtonInForm.readOnly).toBe(false);
192215

193216
vi.spyOn(submitButtonInForm, 'removeEventListener');
194-
submitButtonInForm.readonly = true;
217+
submitButtonInForm.readOnly = true;
195218
await elementIsStable(submitButtonInForm);
196219
expect(submitButtonInForm.removeEventListener).toBeCalledTimes(3); // 2x button controller, 1x command controller
197220

198221
vi.spyOn(submitButtonInForm, 'addEventListener');
199-
submitButtonInForm.readonly = false;
222+
submitButtonInForm.readOnly = false;
200223
await elementIsStable(submitButtonInForm);
201224
expect(submitButtonInForm.addEventListener).toBeCalledTimes(3); // 2x button controller, 1x command controller
202225
});

projects/core/src/internal/base/button.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,18 @@ export class BaseButton extends LitElement {
5353
* Like input readonly, sets a button semantically as visual treatment only
5454
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly
5555
*/
56-
@property({ type: Boolean, reflect: true }) readonly: boolean;
56+
@property({ type: Boolean, attribute: 'readonly', reflect: true }) readOnly = false;
57+
58+
/**
59+
* @deprecated Use `readOnly`. The `readonly` attribute remains supported.
60+
*/
61+
get readonly(): boolean {
62+
return this.readOnly;
63+
}
64+
65+
set readonly(value: boolean) {
66+
this.readOnly = value; // eslint-disable-line local/stateless-property
67+
}
5768

5869
#form: string | HTMLFormElement | null = null;
5970

projects/core/src/internal/controllers/state-current.controller.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { stateCurrent } from '@nvidia-elements/core/internal';
1212
@customElement('state-current-controller-test-element')
1313
class StateCurrentControllerTestElement extends LitElement {
1414
@property({ type: String }) current: 'page' | 'step';
15-
@property({ type: Boolean }) readonly: boolean;
15+
@property({ type: Boolean, attribute: 'readonly' }) readOnly = false;
1616
declare _internals: ElementInternals;
1717

1818
render() {
@@ -70,7 +70,7 @@ describe('state-current.controller', () => {
7070
expect(element._internals.ariaCurrent).toBe('page');
7171
expect(element.matches(':state(current)')).toBe(true);
7272

73-
element.readonly = true;
73+
element.readOnly = true;
7474
await elementIsStable(element);
7575
expect(element._internals.ariaCurrent).toBe(null);
7676
expect(element.matches(':state(current)')).toBe(false);
@@ -107,7 +107,7 @@ describe('state-current.controller', () => {
107107
a.href = '#';
108108
element.appendChild(a);
109109
element.current = 'page';
110-
element.readonly = true;
110+
element.readOnly = true;
111111
element._internals.states.add('anchor');
112112
element.requestUpdate();
113113
await elementIsStable(element);
@@ -119,12 +119,12 @@ describe('state-current.controller', () => {
119119

120120
it('should restore current state when readonly is removed', async () => {
121121
element.current = 'page';
122-
element.readonly = true;
122+
element.readOnly = true;
123123
await elementIsStable(element);
124124
expect(element._internals.ariaCurrent).toBe(null);
125125
expect(element.matches(':state(current)')).toBe(false);
126126

127-
element.readonly = false;
127+
element.readOnly = false;
128128
await elementIsStable(element);
129129
expect(element._internals.ariaCurrent).toBe('page');
130130
expect(element.matches(':state(current)')).toBe(true);

projects/core/src/internal/controllers/state-current.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function stateCurrent<T extends Current>(): ClassDecorator {
1414
target.addInitializer!((instance: T) => new StateCurrentController(instance));
1515
}
1616

17-
type Current = ReactiveElement & { current: 'page' | 'step'; readonly?: boolean; _internals?: ElementInternals };
17+
type Current = ReactiveElement & { current: 'page' | 'step'; readOnly?: boolean; _internals?: ElementInternals };
1818

1919
export class StateCurrentController<T extends Current> implements ReactiveController {
2020
constructor(private host: T) {
@@ -26,7 +26,7 @@ export class StateCurrentController<T extends Current> implements ReactiveContro
2626
}
2727

2828
hostUpdated() {
29-
if (this.host.readonly) {
29+
if (this.host.readOnly) {
3030
this.host._internals!.ariaCurrent = null;
3131
this.host._internals!.states.delete('current');
3232
return;

projects/core/src/internal/controllers/state-disabled.controller.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { stateDisabled } from '@nvidia-elements/core/internal';
1212
@customElement('state-disabled-controller-test-element')
1313
class StateDisabledControllerTestElement extends LitElement {
1414
@property({ type: Boolean }) disabled = false;
15-
@property({ type: Boolean }) readonly = false;
15+
@property({ type: Boolean, attribute: 'readonly' }) readOnly = false;
1616
declare _internals: ElementInternals;
1717
}
1818

@@ -54,7 +54,7 @@ describe('state-disabled.controller', () => {
5454
});
5555

5656
it('should remove aria-disabled if readonly', async () => {
57-
element.readonly = true;
57+
element.readOnly = true;
5858
await elementIsStable(element);
5959
expect(element._internals.ariaDisabled).toBe(null);
6060
expect(element.matches(':state(disabled)')).toBe(false);

projects/core/src/internal/controllers/state-disabled.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function stateDisabled<T extends Disabled>(): ClassDecorator {
1515
target.addInitializer!((instance: T) => new StateDisabledController(instance));
1616
}
1717

18-
export type Disabled = ReactiveElement & { disabled: boolean; readonly?: boolean; _internals?: ElementInternals };
18+
export type Disabled = ReactiveElement & { disabled: boolean; readOnly?: boolean; _internals?: ElementInternals };
1919

2020
export class StateDisabledController<T extends Disabled> implements ReactiveController {
2121
constructor(private host: T) {
@@ -39,7 +39,7 @@ export class StateDisabledController<T extends Disabled> implements ReactiveCont
3939
this.host._internals!.states.delete('disabled');
4040
}
4141

42-
if (this.host.readonly) {
42+
if (this.host.readOnly) {
4343
this.host._internals!.ariaDisabled = null;
4444
}
4545
}

projects/core/src/internal/controllers/state-expanded.controller.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { stateExpanded } from '@nvidia-elements/core/internal';
1212
@customElement('state-expanded-controller-test-element')
1313
class StateExpandedControllerTestElement extends LitElement {
1414
@property({ type: Boolean }) expanded: boolean;
15-
@property({ type: Boolean }) readonly: boolean;
15+
@property({ type: Boolean, attribute: 'readonly' }) readOnly = false;
1616
declare _internals: ElementInternals;
1717
}
1818

@@ -66,7 +66,7 @@ describe('state-expanded.controller', () => {
6666
expect(element._internals.ariaExpanded).toBe('true');
6767
expect(element.matches(':state(expanded)')).toBe(true);
6868

69-
element.readonly = true;
69+
element.readOnly = true;
7070
await elementIsStable(element);
7171
expect(element._internals.ariaExpanded).toBe(null);
7272
expect(element.matches(':state(expanded)')).toBe(false);

projects/core/src/internal/controllers/state-expanded.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function stateExpanded<T extends Expanded>(): ClassDecorator {
1515
target.addInitializer!((instance: T) => new StateExpandedController(instance));
1616
}
1717

18-
export type Expanded = ReactiveElement & { expanded: boolean; readonly?: boolean; _internals?: ElementInternals };
18+
export type Expanded = ReactiveElement & { expanded: boolean; readOnly?: boolean; _internals?: ElementInternals };
1919

2020
export class StateExpandedController<T extends Expanded> implements ReactiveController {
2121
constructor(private host: T) {
@@ -37,7 +37,7 @@ export class StateExpandedController<T extends Expanded> implements ReactiveCont
3737
this.host._internals!.states.delete('expanded');
3838
}
3939

40-
if (this.host.readonly) {
40+
if (this.host.readOnly) {
4141
this.host._internals!.ariaExpanded = null;
4242
this.host._internals!.states.delete('expanded');
4343
}

projects/core/src/internal/controllers/state-pressed.controller.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { statePressed } from '@nvidia-elements/core/internal';
1212
@customElement('state-pressed-controller-test-element')
1313
class StatePressedControllerTestElement extends LitElement {
1414
@property({ type: Boolean }) pressed: boolean;
15-
@property({ type: Boolean }) readonly: boolean;
15+
@property({ type: Boolean, attribute: 'readonly' }) readOnly = false;
1616
declare _internals: ElementInternals;
1717
}
1818

@@ -60,7 +60,7 @@ describe('state-pressed.controller', () => {
6060
expect(element._internals.ariaPressed).toBe('true');
6161
expect(element.matches(':state(pressed)')).toBe(true);
6262

63-
element.readonly = true;
63+
element.readOnly = true;
6464
await elementIsStable(element);
6565
expect(element._internals.ariaPressed).toBe(null);
6666
expect(element.matches(':state(pressed)')).toBe(false);

projects/core/src/internal/controllers/state-pressed.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function statePressed<T extends Pressed>(): ClassDecorator {
1515
target.addInitializer!((instance: T) => new StatePressedController(instance));
1616
}
1717

18-
export type Pressed = ReactiveElement & { pressed: boolean; readonly?: boolean; _internals?: ElementInternals };
18+
export type Pressed = ReactiveElement & { pressed: boolean; readOnly?: boolean; _internals?: ElementInternals };
1919

2020
export class StatePressedController<T extends Pressed> implements ReactiveController {
2121
constructor(private host: T) {
@@ -37,7 +37,7 @@ export class StatePressedController<T extends Pressed> implements ReactiveContro
3737
this.host._internals!.states.delete('pressed');
3838
}
3939

40-
if (this.host.readonly) {
40+
if (this.host.readOnly) {
4141
this.host._internals!.ariaPressed = null;
4242
this.host._internals!.states.delete('pressed');
4343
}

0 commit comments

Comments
 (0)