Skip to content

Commit 16439a8

Browse files
feat(addon/components): adds Focusable glimmer component.
1 parent 1f50e35 commit 16439a8

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

addon/components/-focusable.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/**
2+
* @module ember-paper
3+
*/
4+
import Component from '@glimmer/component';
5+
import { tracked } from '@glimmer/tracking';
6+
import { action } from '@ember/object';
7+
8+
/**
9+
* @class Focusable
10+
* @extends @glimmer/component
11+
*
12+
* When extending from Focusable it is expected that md-focused be implemented
13+
* on the top level tag along with setting tabindex and disabled. This component
14+
* listens to a large number of events, therefore render listener register
15+
* functions have been created to ease usage. Clearly, this is non-optimal and
16+
* only the listeners that are required should be added using the `on` modifier.
17+
*
18+
* Use the following as a base:
19+
* ```hbs
20+
* <myTag class='{{if this.focused " md-focused"}}' disabled={{this.disabled}} tabindex={{if this.disabled "-1" "0"}} {{did-insert this.registerListeners}} {{will-destroy this.unregisterListeners}} ...attributes>
21+
* </myTag>
22+
* ```
23+
*/
24+
export default class Focusable extends Component {
25+
@tracked pressed = false;
26+
@tracked active = false;
27+
@tracked focused = false;
28+
@tracked hover = false;
29+
30+
// classNameBindings: ['focused:md-focused'],
31+
// attributeBindings: ['tabindex', 'disabledAttr:disabled'],
32+
33+
get disabled() {
34+
return this.args.disabled || false;
35+
}
36+
37+
toggle = false;
38+
39+
// Only render the "focused" state if the element gains focus due to
40+
// keyboard navigation.
41+
get focusOnlyOnKey() {
42+
return this.args.focusOnlyOnKey || false;
43+
}
44+
45+
@action registerListeners(element) {
46+
element.addEventListener('focusin', this.handleFocusIn);
47+
element.addEventListener('focusout', this.handleFocusOut);
48+
element.addEventListener('mousedown', this.handleMouseDown);
49+
element.addEventListener('mouseenter', this.handleMouseEnter);
50+
element.addEventListener('mouseleave', this.handleMouseLeave);
51+
element.addEventListener('mousemove', this.handleMouseMove);
52+
element.addEventListener('mouseup', this.handleMouseUp);
53+
element.addEventListener('pointermove', this.handlePointerMove);
54+
// Set all touch events as passive listeners to remove scroll jank on
55+
// mobile devices.
56+
// refer: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
57+
element.addEventListener('touchcancel', this.handleTouchCancel, {
58+
passive: true,
59+
});
60+
element.addEventListener('touchend', this.handleTouchEnd, {
61+
passive: true,
62+
});
63+
element.addEventListener('touchmove', this.handleTouchMove, {
64+
passive: true,
65+
});
66+
element.addEventListener('touchstart', this.handleTouchStart, {
67+
passive: true,
68+
});
69+
}
70+
71+
@action unregisterListeners(element) {
72+
element.removeEventListener('focusin', this.handleFocusIn);
73+
element.removeEventListener('focusout', this.handleFocusOut);
74+
element.removeEventListener('mousedown', this.handleMouseDown);
75+
element.removeEventListener('mouseenter', this.handleMouseEnter);
76+
element.removeEventListener('mouseleave', this.handleMouseLeave);
77+
element.removeEventListener('mousemove', this.handleMouseMove);
78+
element.removeEventListener('mouseup', this.handleMouseUp);
79+
element.removeEventListener('pointermove', this.handlePointerMove);
80+
element.removeEventListener('touchcancel', this.handleTouchCancel);
81+
element.removeEventListener('touchend', this.handleTouchEnd);
82+
element.removeEventListener('touchmove', this.handleTouchMove);
83+
element.removeEventListener('touchstart', this.handleTouchStart);
84+
}
85+
86+
/*
87+
* Listen to `focusIn` and `focusOut` events instead of `focus` and `blur`.
88+
* This way we don't need to explicitly bubble the events.
89+
* They bubble by default.
90+
*/
91+
@action handleFocusIn(e) {
92+
if ((!this.disabled && !this.focusOnlyOnKey) || !this.pressed) {
93+
this.focused = true;
94+
if (this.args.onFocusIn) {
95+
this.args.onFocusIn(e);
96+
}
97+
}
98+
}
99+
100+
@action handleFocusOut(e) {
101+
this.focused = false;
102+
if (this.args.onFocusOut) {
103+
this.args.onFocusOut(e);
104+
}
105+
}
106+
107+
@action handleMouseDown(e) {
108+
this.down(e);
109+
if (this.args.onMouseDown) {
110+
this.args.onMouseDown(e);
111+
}
112+
}
113+
114+
@action handleMouseEnter(e) {
115+
this.hover = true;
116+
if (this.args.onMouseEnter) {
117+
this.args.onMouseEnter(e);
118+
}
119+
}
120+
121+
@action handleMouseLeave(e) {
122+
this.hover = false;
123+
this.up(e);
124+
if (this.args.onMouseLeave) {
125+
this.args.onMouseLeave(e);
126+
}
127+
}
128+
129+
@action handleMouseMove(e) {
130+
return this.move(e);
131+
}
132+
133+
@action handleMouseUp(e) {
134+
return this.up(e);
135+
}
136+
137+
@action handlePointerMove(e) {
138+
return this.move(e);
139+
}
140+
141+
@action handleTouchCancel(e) {
142+
return this.up(e);
143+
}
144+
145+
@action handleTouchEnd(e) {
146+
return this.up(e);
147+
}
148+
149+
@action handleTouchMove(e) {
150+
return this.move(e);
151+
}
152+
153+
@action handleTouchStart(e) {
154+
return this.down(e);
155+
}
156+
157+
@action up() {
158+
this.pressed = false;
159+
if (!this.toggle) {
160+
this.active = false;
161+
}
162+
}
163+
164+
@action down() {
165+
this.pressed = true;
166+
if (this.toggle) {
167+
this.active = !this.active;
168+
} else {
169+
this.active = true;
170+
}
171+
}
172+
173+
move() {}
174+
}

0 commit comments

Comments
 (0)