Skip to content
This repository was archived by the owner on Jan 13, 2025. It is now read-only.

Commit 02a3def

Browse files
authored
fix(text-field): Restore icon tabindex according to its initial value (#2600)
BREAKING CHANGE: Adds getAttr adapter API to text field icon
1 parent 127375e commit 02a3def

File tree

6 files changed

+53
-9
lines changed

6 files changed

+53
-9
lines changed

packages/mdc-textfield/icon/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ This allows the parent `MDCTextField` component to access the public methods on
105105

106106
Method Signature | Description
107107
--- | ---
108+
`getAttr(attr: string) => string` | Gets the value of an attribute on the icon element
108109
`setAttr(attr: string, value: string) => void` | Sets an attribute with a given value on the icon element
109110
`removeAttr(attr: string) => void` | Removes an attribute from the icon element
110111
`registerInteractionHandler(evtType: string, handler: EventListener) => void` | Registers an event listener for a given event

packages/mdc-textfield/icon/adapter.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
* @record
2929
*/
3030
class MDCTextFieldIconAdapter {
31+
/**
32+
* Gets the value of an attribute on the icon element.
33+
* @param {string} attr
34+
* @return {string}
35+
*/
36+
getAttr(attr) {}
37+
3138
/**
3239
* Sets an attribute on the icon element.
3340
* @param {string} attr

packages/mdc-textfield/icon/foundation.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class MDCTextFieldIconFoundation extends MDCFoundation {
3737
*/
3838
static get defaultAdapter() {
3939
return /** @type {!MDCTextFieldIconAdapter} */ ({
40+
getAttr: () => {},
4041
setAttr: () => {},
4142
removeAttr: () => {},
4243
registerInteractionHandler: () => {},
@@ -51,11 +52,16 @@ class MDCTextFieldIconFoundation extends MDCFoundation {
5152
constructor(adapter) {
5253
super(Object.assign(MDCTextFieldIconFoundation.defaultAdapter, adapter));
5354

55+
/** @private {string?} */
56+
this.savedTabIndex_ = null;
57+
5458
/** @private {function(!Event): undefined} */
5559
this.interactionHandler_ = (evt) => this.handleInteraction(evt);
5660
}
5761

5862
init() {
63+
this.savedTabIndex_ = this.adapter_.getAttr('tabindex');
64+
5965
['click', 'keydown'].forEach((evtType) => {
6066
this.adapter_.registerInteractionHandler(evtType, this.interactionHandler_);
6167
});
@@ -72,11 +78,15 @@ class MDCTextFieldIconFoundation extends MDCFoundation {
7278
* @param {boolean} disabled
7379
*/
7480
setDisabled(disabled) {
81+
if (!this.savedTabIndex_) {
82+
return;
83+
}
84+
7585
if (disabled) {
7686
this.adapter_.setAttr('tabindex', '-1');
7787
this.adapter_.removeAttr('role');
7888
} else {
79-
this.adapter_.setAttr('tabindex', '0');
89+
this.adapter_.setAttr('tabindex', this.savedTabIndex_);
8090
this.adapter_.setAttr('role', strings.ICON_ROLE);
8191
}
8292
}

packages/mdc-textfield/icon/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class MDCTextFieldIcon extends MDCComponent {
4545
*/
4646
getDefaultFoundation() {
4747
return new MDCTextFieldIconFoundation(/** @type {!MDCTextFieldIconAdapter} */ (Object.assign({
48+
getAttr: (attr) => this.root_.getAttribute(attr),
4849
setAttr: (attr, value) => this.root_.setAttribute(attr, value),
4950
removeAttr: (attr) => this.root_.removeAttribute(attr),
5051
registerInteractionHandler: (evtType, handler) => this.root_.addEventListener(evtType, handler),

test/unit/mdc-textfield/mdc-text-field-icon-foundation.test.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ test('exports strings', () => {
3030

3131
test('defaultAdapter returns a complete adapter implementation', () => {
3232
verifyDefaultAdapter(MDCTextFieldIconFoundation, [
33-
'setAttr', 'removeAttr', 'registerInteractionHandler', 'deregisterInteractionHandler',
33+
'getAttr', 'setAttr', 'removeAttr', 'registerInteractionHandler', 'deregisterInteractionHandler',
3434
'notifyIconAction',
3535
]);
3636
});
@@ -53,28 +53,45 @@ test('#destroy removes event listeners', () => {
5353
td.verify(mockAdapter.deregisterInteractionHandler('keydown', td.matchers.isA(Function)));
5454
});
5555

56-
test('#setDisabled sets icon tabindex to -1 when set to true', () => {
56+
test('#setDisabled sets icon tabindex to -1 and removes role when set to true if icon initially had a tabindex', () => {
5757
const {foundation, mockAdapter} = setupTest();
58+
td.when(mockAdapter.getAttr('tabindex')).thenReturn('1');
59+
foundation.init();
60+
5861
foundation.setDisabled(true);
5962
td.verify(mockAdapter.setAttr('tabindex', '-1'));
63+
td.verify(mockAdapter.removeAttr('role'));
6064
});
6165

62-
test('#setDisabled removes icon role when set to true', () => {
66+
test('#setDisabled does not change icon tabindex or role when set to true if icon initially had no tabindex', () => {
6367
const {foundation, mockAdapter} = setupTest();
68+
td.when(mockAdapter.getAttr('tabindex')).thenReturn(null);
69+
foundation.init();
70+
6471
foundation.setDisabled(true);
65-
td.verify(mockAdapter.removeAttr('role'));
72+
td.verify(mockAdapter.setAttr('tabindex', td.matchers.isA(String)), {times: 0});
73+
td.verify(mockAdapter.removeAttr('role'), {times: 0});
6674
});
6775

68-
test('#setDisabled sets icon tabindex to 0 when set to false', () => {
76+
test('#setDisabled restores icon tabindex and role when set to false if icon initially had a tabindex', () => {
6977
const {foundation, mockAdapter} = setupTest();
78+
const expectedTabIndex = '1';
79+
td.when(mockAdapter.getAttr('tabindex')).thenReturn(expectedTabIndex);
80+
foundation.init();
81+
7082
foundation.setDisabled(false);
71-
td.verify(mockAdapter.setAttr('tabindex', '0'));
83+
td.verify(mockAdapter.setAttr('tabindex', expectedTabIndex));
84+
td.verify(mockAdapter.setAttr('role', strings.ICON_ROLE));
7285
});
7386

74-
test(`#setDisabled sets icon role to ${strings.ICON_ROLE} when set to false`, () => {
87+
test('#setDisabled does not change icon tabindex or role when set to false if icon initially had no tabindex', () => {
7588
const {foundation, mockAdapter} = setupTest();
89+
td.when(mockAdapter.getAttr('tabindex')).thenReturn(null);
90+
foundation.init();
91+
7692
foundation.setDisabled(false);
77-
td.verify(mockAdapter.setAttr('role', strings.ICON_ROLE));
93+
td.verify(mockAdapter.setAttr('tabindex', td.matchers.isA(String)), {times: 0});
94+
td.verify(mockAdapter.setAttr('role', td.matchers.isA(String)), {times: 0});
7895
});
7996

8097
test('on click notifies custom icon event', () => {

test/unit/mdc-textfield/mdc-text-field-icon.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ function setupTest() {
3737
return {root, component};
3838
}
3939

40+
test('#adapter.getAttr returns the value of a given attribute on the element', () => {
41+
const {root, component} = setupTest();
42+
const expectedAttr = 'tabindex';
43+
const expectedValue = '0';
44+
root.setAttribute(expectedAttr, expectedValue);
45+
assert.equal(component.getDefaultFoundation().adapter_.getAttr(expectedAttr), expectedValue);
46+
});
47+
4048
test('#adapter.setAttr adds a given attribute to the element', () => {
4149
const {root, component} = setupTest();
4250
component.getDefaultFoundation().adapter_.setAttr('aria-label', 'foo');

0 commit comments

Comments
 (0)