Skip to content

Commit e4c6796

Browse files
authored
Merge pull request #537 from gselderslaghs/feature-discovery-trigger
fix/accessibility/enhancement(featureDiscovery) initialization, accessibility and spec test
2 parents dd8d3f3 + db22a70 commit e4c6796

File tree

2 files changed

+154
-25
lines changed

2 files changed

+154
-25
lines changed

spec/tests/taptarget/taptargetSpec.js

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/* eslint-disable no-undef */
2+
3+
describe('TapTarget', () => {
4+
const fixture = `<div class="tap-target-container">
5+
<a id="tap-target-link" class="waves-effect waves-light btn btn-floating toggle-tap-target"><i class="material-icons">menu</i></a>
6+
<div class="tap-target" data-target="tap-target-link">
7+
<div class="tap-target-content">
8+
<div class="tap-target-inner-wrapper">
9+
<h5>Feature Discovery</h5>
10+
<p>Some incredible text here</p>
11+
</div>
12+
</div>
13+
</div>
14+
</div>`;
15+
16+
beforeEach(() => XloadHtml(fixture));
17+
afterEach(() => XunloadFixtures());
18+
19+
describe('tap target plugin', () => {
20+
afterEach(() => {
21+
if (M.TapTarget._taptargets.length) {
22+
M.TapTarget.getInstance(document.querySelector('.tap-target')).destroy();
23+
}
24+
});
25+
26+
it('should be able to initialize', (done) => {
27+
expect(M.TapTarget._taptargets.length).toEqual(0, 'no tap targets initialized');
28+
M.TapTarget.init(document.querySelectorAll('.tap-target'));
29+
expect(M.TapTarget._taptargets.length).toEqual(1, 'there should be 1 tap target initialization');
30+
done();
31+
});
32+
33+
it('should be hidden on initialization', (done) => {
34+
const tapTargetElem = document.querySelector('.tap-target');
35+
M.TapTarget.init(tapTargetElem);
36+
setTimeout(() => {
37+
expect(tapTargetElem).not.toBeVisible('taptarget was not hidden on initialization');
38+
done();
39+
}, 10);
40+
});
41+
42+
it('should open by click interaction on the data-target element', (done) => {
43+
const tapTargetElem = document.querySelector('.tap-target');
44+
M.TapTarget.init(tapTargetElem);
45+
click(document.querySelector('.toggle-tap-target'));
46+
setTimeout(() => {
47+
expect(tapTargetElem).toBeVisible('taptarget was not visible after click interaction');
48+
done();
49+
}, 10);
50+
});
51+
52+
it('should close by click interaction on the data-target element', (done) => {
53+
const tapTargetElem = document.querySelector('.tap-target');
54+
const toggleTapTargetElem = document.querySelector('.toggle-tap-target');
55+
M.TapTarget.init(tapTargetElem);
56+
click(toggleTapTargetElem);
57+
setTimeout(() => {
58+
click(toggleTapTargetElem);
59+
setTimeout(() => {
60+
expect(tapTargetElem).not.toBeVisible('taptarget was not hidden after click interaction');
61+
done();
62+
}, 400);
63+
}, 400);
64+
});
65+
66+
it('should have working callbacks', (done) => {
67+
const toggleTapTargetElem = document.querySelector('.toggle-tap-target');
68+
let opened = false;
69+
let closed = false;
70+
M.TapTarget.init(document.querySelector('.tap-target'), {
71+
onOpen: () => {
72+
opened = true;
73+
},
74+
onClose: () => {
75+
closed = true;
76+
},
77+
});
78+
click(toggleTapTargetElem);
79+
expect(opened).toEqual(true, 'opened variable should be true after method callback');
80+
setTimeout(() => {
81+
click(toggleTapTargetElem);
82+
expect(closed).toEqual(true, 'closed variable should be true after method callback');
83+
}, 400);
84+
done();
85+
});
86+
87+
it('should destroy correctly', function(done) {
88+
const tapTargetElem = document.querySelector('.tap-target');
89+
expect(M.TapTarget._taptargets.length).toEqual(0, 'no tap targets initialized');
90+
M.TapTarget.init(tapTargetElem);
91+
expect(M.TapTarget._taptargets.length).toEqual(1, 'there should be 1 tap target initialization');
92+
M.TapTarget.getInstance(tapTargetElem).destroy();
93+
setTimeout(() => {
94+
expect(M.TapTarget._taptargets.length).toEqual(0, 'no tap targets initialized');
95+
done();
96+
}, 10);
97+
});
98+
});
99+
});

src/tapTarget.ts

+55-25
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
2525
*/
2626
isOpen: boolean;
2727

28+
static _taptargets: TapTarget[];
2829
private wrapper: HTMLElement;
29-
private _origin: HTMLElement;
30+
// private _origin: HTMLElement;
3031
private originEl: HTMLElement;
3132
private waveEl: HTMLElement & Element & Node;
3233
private contentEl: HTMLElement;
@@ -42,10 +43,14 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
4243

4344
this.isOpen = false;
4445
// setup
45-
this._origin = document.querySelector(`#${el.dataset.target}`);
46+
this.originEl = document.querySelector(`#${el.dataset.target}`);
47+
this.originEl.tabIndex = 0;
48+
4649
this._setup();
4750
this._calculatePositioning();
4851
this._setupEventHandlers();
52+
53+
TapTarget._taptargets.push(this);
4954
}
5055

5156
static get defaults(): TapTargetOptions {
@@ -80,48 +85,65 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
8085
destroy() {
8186
this._removeEventHandlers();
8287
(this.el as any).TapTarget = undefined;
88+
const index = TapTarget._taptargets.indexOf(this);
89+
if (index >= 0) {
90+
TapTarget._taptargets.splice(index, 1);
91+
}
8392
}
8493

8594
_setupEventHandlers() {
86-
this.el.addEventListener('click', this._handleTargetClick);
87-
this.originEl.addEventListener('click', this._handleOriginClick);
95+
this.originEl.addEventListener('click', this._handleTargetToggle);
96+
this.originEl.addEventListener('keypress', this._handleKeyboardInteraction, true);
97+
// this.originEl.addEventListener('click', this._handleOriginClick);
8898
// Resize
8999
window.addEventListener('resize', this._handleThrottledResize);
90100
}
91101

92102
_removeEventHandlers() {
93-
this.el.removeEventListener('click', this._handleTargetClick);
94-
this.originEl.removeEventListener('click', this._handleOriginClick);
103+
this.originEl.removeEventListener('click', this._handleTargetToggle);
104+
this.originEl.removeEventListener('keypress', this._handleKeyboardInteraction, true);
105+
// this.originEl.removeEventListener('click', this._handleOriginClick);
95106
window.removeEventListener('resize', this._handleThrottledResize);
96107
}
97108

98109
_handleThrottledResize: () => void = Utils.throttle(function(){ this._handleResize(); }, 200).bind(this);
99110

100-
_handleTargetClick = () => {
101-
this.open();
111+
_handleKeyboardInteraction = (e: KeyboardEvent) => {
112+
if (Utils.keys.ENTER.includes(e.key)) {
113+
this._handleTargetToggle();
114+
}
102115
}
103116

104-
_handleOriginClick = () => {
105-
this.close();
117+
_handleTargetToggle = () => {
118+
!this.isOpen ? this.open() : this.close();
106119
}
107120

121+
/*_handleOriginClick = () => {
122+
this.close();
123+
}*/
124+
108125
_handleResize = () => {
109126
this._calculatePositioning();
110127
}
111128

112-
_handleDocumentClick = (e: MouseEvent | TouchEvent) => {
113-
if (!(e.target as HTMLElement).closest('.tap-target-wrapper')) {
129+
_handleDocumentClick = (e: MouseEvent | TouchEvent | KeyboardEvent) => {
130+
if (
131+
(e.target as HTMLElement).closest(`#${this.el.dataset.target}`) !== this.originEl &&
132+
!(e.target as HTMLElement).closest('.tap-target-wrapper')
133+
) {
114134
this.close();
115-
e.preventDefault();
116-
e.stopPropagation();
135+
// e.preventDefault();
136+
// e.stopPropagation();
117137
}
118138
}
119139

120140
_setup() {
121141
// Creating tap target
122142
this.wrapper = this.el.parentElement;
123143
this.waveEl = this.wrapper.querySelector('.tap-target-wave');
124-
this.originEl = this.wrapper.querySelector('.tap-target-origin');
144+
this.el.parentElement.ariaExpanded = 'false';
145+
this.originEl.style.zIndex = '1002';
146+
// this.originEl = this.wrapper.querySelector('.tap-target-origin');
125147
this.contentEl = this.el.querySelector('.tap-target-content');
126148
// Creating wrapper
127149
if (!this.wrapper.classList.contains('.tap-target-wrapper')) {
@@ -141,13 +163,13 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
141163
this.waveEl = document.createElement('div');
142164
this.waveEl.classList.add('tap-target-wave');
143165
// Creating origin
144-
if (!this.originEl) {
166+
/*if (!this.originEl) {
145167
this.originEl = <HTMLElement>this._origin.cloneNode(true); // .clone(true, true);
146168
this.originEl.classList.add('tap-target-origin');
147169
this.originEl.removeAttribute('id');
148170
this.originEl.removeAttribute('style');
149171
this.waveEl.append(this.originEl);
150-
}
172+
}*/
151173
this.wrapper.append(this.waveEl);
152174
}
153175
}
@@ -163,10 +185,10 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
163185

164186
_calculatePositioning() {
165187
// Element or parent is fixed position?
166-
let isFixed = getComputedStyle(this._origin).position === 'fixed';
188+
let isFixed = getComputedStyle(this.originEl).position === 'fixed';
167189
if (!isFixed) {
168190

169-
let currentElem: any = this._origin;
191+
let currentElem: any = this.originEl;
170192
const parents = [];
171193
while ((currentElem = currentElem.parentNode) && currentElem !== document)
172194
parents.push(currentElem);
@@ -177,10 +199,10 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
177199
}
178200
}
179201
// Calculating origin
180-
const originWidth = this._origin.offsetWidth;
181-
const originHeight = this._origin.offsetHeight;
182-
const originTop = isFixed ? this._offset(this._origin).top - Utils.getDocumentScrollTop() : this._offset(this._origin).top;
183-
const originLeft = isFixed ? this._offset(this._origin).left - Utils.getDocumentScrollLeft() : this._offset(this._origin).left;
202+
const originWidth = this.originEl.offsetWidth;
203+
const originHeight = this.originEl.offsetHeight;
204+
const originTop = isFixed ? this._offset(this.originEl).top - Utils.getDocumentScrollTop() : this._offset(this.originEl).top;
205+
const originLeft = isFixed ? this._offset(this.originEl).left - Utils.getDocumentScrollLeft() : this._offset(this.originEl).left;
184206

185207
// Calculating screen
186208
const windowWidth = window.innerWidth;
@@ -248,11 +270,13 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
248270
if (this.isOpen) return;
249271
// onOpen callback
250272
if (typeof this.options.onOpen === 'function') {
251-
this.options.onOpen.call(this, this._origin);
273+
this.options.onOpen.call(this, this.originEl);
252274
}
253275
this.isOpen = true;
254276
this.wrapper.classList.add('open');
277+
this.wrapper.ariaExpanded = 'true'
255278
document.body.addEventListener('click', this._handleDocumentClick, true);
279+
document.body.addEventListener('keypress', this._handleDocumentClick, true);
256280
document.body.addEventListener('touchend', this._handleDocumentClick);
257281
};
258282

@@ -263,11 +287,17 @@ export class TapTarget extends Component<TapTargetOptions> implements Openable {
263287
if (!this.isOpen) return;
264288
// onClose callback
265289
if (typeof this.options.onClose === 'function') {
266-
this.options.onClose.call(this, this._origin);
290+
this.options.onClose.call(this, this.originEl);
267291
}
268292
this.isOpen = false;
269293
this.wrapper.classList.remove('open');
294+
this.wrapper.ariaExpanded = 'false'
270295
document.body.removeEventListener('click', this._handleDocumentClick, true);
296+
document.body.removeEventListener('keypress', this._handleDocumentClick, true);
271297
document.body.removeEventListener('touchend', this._handleDocumentClick);
272298
};
299+
300+
static {
301+
TapTarget._taptargets = [];
302+
}
273303
}

0 commit comments

Comments
 (0)