Skip to content

Commit e5b174e

Browse files
daKmoRMikhail Bashkirov
authored andcommitted
fix(core): add DisabledWithTabIndexMixin to manage disabled and tabindex
1 parent 0d64792 commit e5b174e

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

packages/core/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ export { DomHelpersMixin } from './src/DomHelpersMixin.js';
5252
export { LionSingleton } from './src/LionSingleton.js';
5353
export { SlotMixin } from './src/SlotMixin.js';
5454
export { DisabledMixin } from './src/DisabledMixin.js';
55+
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { dedupeMixin } from './dedupeMixin.js';
2+
import { DisabledMixin } from './DisabledMixin.js';
3+
4+
/**
5+
* #DisabledWithTabIndexMixin
6+
*
7+
* @polymerMixin
8+
* @mixinFunction
9+
*/
10+
export const DisabledWithTabIndexMixin = dedupeMixin(
11+
superclass =>
12+
// eslint-disable-next-line no-shadow
13+
class DisabledWithTabIndexMixin extends DisabledMixin(superclass) {
14+
static get properties() {
15+
return {
16+
// we use a property here as if we use the native tabIndex we can not set a default value
17+
// in the constructor as it synchronously sets the attribute which is not allowed in the
18+
// constructor phase
19+
tabIndex: {
20+
type: Number,
21+
reflect: true,
22+
attribute: 'tabindex',
23+
},
24+
};
25+
}
26+
27+
constructor() {
28+
super();
29+
this.__isUserSettingTabIndex = true;
30+
31+
this.__restoreTabIndexTo = 0;
32+
this.__internalSetTabIndex(0);
33+
}
34+
35+
makeRequestToBeDisabled() {
36+
super.makeRequestToBeDisabled();
37+
if (this.__requestedToBeDisabled === false) {
38+
this.__restoreTabIndexTo = this.tabIndex;
39+
}
40+
}
41+
42+
retractRequestToBeDisabled() {
43+
super.retractRequestToBeDisabled();
44+
if (this.__requestedToBeDisabled === true) {
45+
this.__internalSetTabIndex(this.__restoreTabIndexTo);
46+
}
47+
}
48+
49+
__internalSetTabIndex(value) {
50+
this.__isUserSettingTabIndex = false;
51+
this.tabIndex = value;
52+
this.__isUserSettingTabIndex = true;
53+
}
54+
55+
_requestUpdate(name, oldValue) {
56+
super._requestUpdate(name, oldValue);
57+
58+
if (name === 'disabled') {
59+
if (this.disabled) {
60+
this.__internalSetTabIndex(-1);
61+
} else {
62+
this.__internalSetTabIndex(this.__restoreTabIndexTo);
63+
}
64+
}
65+
66+
if (name === 'tabIndex') {
67+
if (this.__isUserSettingTabIndex) {
68+
this.__restoreTabIndexTo = this.tabIndex;
69+
}
70+
71+
if (this.tabIndex !== -1 && this.__requestedToBeDisabled === true) {
72+
this.__internalSetTabIndex(-1);
73+
}
74+
}
75+
}
76+
77+
firstUpdated(changedProperties) {
78+
super.firstUpdated(changedProperties);
79+
// for ShadyDom the timing is a little different and we need to make sure
80+
// the tabindex gets correctly updated here
81+
if (this.disabled) {
82+
this.__internalSetTabIndex(-1);
83+
}
84+
}
85+
},
86+
);
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { expect, fixture, html } from '@open-wc/testing';
2+
3+
import { LitElement } from '../index.js';
4+
import { DisabledWithTabIndexMixin } from '../src/DisabledWithTabIndexMixin.js';
5+
6+
describe('DisabledWithTabIndexMixin', () => {
7+
before(() => {
8+
class WithTabIndex extends DisabledWithTabIndexMixin(LitElement) {}
9+
customElements.define('can-be-disabled-with-tab-index', WithTabIndex);
10+
});
11+
12+
it('has an initial tabIndex of 0', async () => {
13+
const el = await fixture(html`
14+
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
15+
`);
16+
expect(el.tabIndex).to.equal(0);
17+
expect(el.getAttribute('tabindex')).to.equal('0');
18+
});
19+
20+
it('sets tabIndex to -1 if disabled', async () => {
21+
const el = await fixture(html`
22+
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
23+
`);
24+
el.disabled = true;
25+
expect(el.tabIndex).to.equal(-1);
26+
await el.updateComplete;
27+
expect(el.getAttribute('tabindex')).to.equal('-1');
28+
});
29+
30+
it('disabled does not override user provided tabindex', async () => {
31+
const el = await fixture(html`
32+
<can-be-disabled-with-tab-index tabindex="5" disabled></can-be-disabled-with-tab-index>
33+
`);
34+
expect(el.getAttribute('tabindex')).to.equal('-1');
35+
el.disabled = false;
36+
await el.updateComplete;
37+
expect(el.getAttribute('tabindex')).to.equal('5');
38+
});
39+
40+
it('can be disabled imperatively', async () => {
41+
const el = await fixture(html`
42+
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
43+
`);
44+
expect(el.getAttribute('tabindex')).to.equal('-1');
45+
46+
el.disabled = false;
47+
await el.updateComplete;
48+
expect(el.getAttribute('tabindex')).to.equal('0');
49+
expect(el.hasAttribute('disabled')).to.equal(false);
50+
51+
el.disabled = true;
52+
await el.updateComplete;
53+
expect(el.getAttribute('tabindex')).to.equal('-1');
54+
expect(el.hasAttribute('disabled')).to.equal(true);
55+
});
56+
57+
it('will not allow to change tabIndex after makeRequestToBeDisabled()', async () => {
58+
const el = await fixture(html`
59+
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
60+
`);
61+
el.makeRequestToBeDisabled();
62+
63+
el.tabIndex = 5;
64+
expect(el.tabIndex).to.equal(-1);
65+
await el.updateComplete;
66+
expect(el.getAttribute('tabindex')).to.equal('-1');
67+
});
68+
69+
it('will restore last tabIndex after retractRequestToBeDisabled()', async () => {
70+
const el = await fixture(html`
71+
<can-be-disabled-with-tab-index tabindex="5"></can-be-disabled-with-tab-index>
72+
`);
73+
el.makeRequestToBeDisabled();
74+
expect(el.tabIndex).to.equal(-1);
75+
await el.updateComplete;
76+
expect(el.getAttribute('tabindex')).to.equal('-1');
77+
el.retractRequestToBeDisabled();
78+
expect(el.tabIndex).to.equal(5);
79+
await el.updateComplete;
80+
expect(el.getAttribute('tabindex')).to.equal('5');
81+
82+
el.makeRequestToBeDisabled();
83+
el.tabIndex = 12;
84+
el.retractRequestToBeDisabled();
85+
expect(el.tabIndex).to.equal(12);
86+
await el.updateComplete;
87+
expect(el.getAttribute('tabindex')).to.equal('12');
88+
89+
el.makeRequestToBeDisabled();
90+
el.tabIndex = 13;
91+
el.tabIndex = 14;
92+
el.retractRequestToBeDisabled();
93+
expect(el.tabIndex).to.equal(14);
94+
await el.updateComplete;
95+
expect(el.getAttribute('tabindex')).to.equal('14');
96+
});
97+
98+
it('may allow multiple calls to retractRequestToBeDisabled', async () => {
99+
const el = await fixture(html`
100+
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
101+
`);
102+
el.retractRequestToBeDisabled();
103+
el.retractRequestToBeDisabled();
104+
expect(el.disabled).to.be.true;
105+
expect(el.tabIndex).to.be.equal(-1);
106+
});
107+
});

0 commit comments

Comments
 (0)