Skip to content

Commit b81abb3

Browse files
authored
Merge pull request #141 from rackerlabs/refactor-hx-checkbox
fix compatibility support for checkbox control
2 parents ec803af + 56c0427 commit b81abb3

File tree

9 files changed

+266
-200
lines changed

9 files changed

+266
-200
lines changed

docs/components/checkboxes/checkbox-demo.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ if (document.getElementById('vue-checkboxDemo')) {
77
isIndeterminate: false,
88
isInvalid: false,
99
},
10+
methods: {
11+
onChange: function (evt) {
12+
// Use evt.currentTarget because evt.target is undefined in Firefox
13+
this.isChecked = evt.currentTarget.checked;
14+
},
15+
},
1016
computed: {
1117
snippet: function () {
1218
var raw = `<hx-checkbox

docs/components/checkboxes/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ <h3>Basic Checkbox</h2>
3535
:disabled="isDisabled"
3636
:indeterminate="isIndeterminate"
3737
:invalid="isInvalid"
38+
@change="onChange"
3839
></hx-checkbox>
3940
</div>
4041
{% code 'html' %}

docs/elements/hx-checkbox/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
<hx-dt>Permitted Children</hx-dt>
2222
<hx-dd>none (content will be removed upon element upgrade)</hx-dd>
2323
</hx-def>
24+
<hx-def>
25+
<hx-dt>Events</hx-dt>
26+
<hx-dd><code>change</code></hx-dd>
27+
</hx-def>
2428
</hx-dl>
2529
</section>
2630
{% endblock %}

src/helix-ui/elements/HXCheckboxElement.js

Lines changed: 35 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ const template = document.createElement('template');
77

88
template.innerHTML = `
99
<style>${shadowStyles}</style>
10-
<div id="container">
11-
<hx-icon type="checkmark" id="tick"></hx-icon>
12-
<hx-icon type="minus" id="minus"></hx-icon>
13-
</div>
10+
<label id="container">
11+
<input type="checkbox" id="nativeControl"/>
12+
<div id="customControl">
13+
<hx-icon type="checkmark" id="tick"></hx-icon>
14+
<hx-icon type="minus" id="minus"></hx-icon>
15+
</div>
16+
</label>
1417
`;
1518

1619
export class HXCheckboxElement extends HXElement {
@@ -19,57 +22,44 @@ export class HXCheckboxElement extends HXElement {
1922
}
2023

2124
static get observedAttributes () {
22-
return super.observedAttributes.concat([
25+
return [
2326
'checked',
27+
'disabled',
2428
'indeterminate',
25-
]);
29+
];
2630
}
2731

2832
constructor () {
2933
super(tagName, template);
34+
this._input = this.shadowRoot.getElementById('nativeControl');
35+
this._onChange = this._onChange.bind(this);
3036
}
3137

3238
connectedCallback () {
33-
super.connectedCallback();
34-
35-
this.$defaultAttribute('role', 'checkbox');
36-
if (!this.hasAttribute('tabindex') && !this.disabled) {
37-
this.setAttribute('tabindex', 0);
38-
}
39-
4039
this.$upgradeProperty('checked');
40+
this.$upgradeProperty('disabled');
4141
this.$upgradeProperty('indeterminate');
42-
43-
this.addEventListener('keydown', this.$preventScroll);
44-
this.addEventListener('keyup', this._onKeyUp);
45-
this.addEventListener('click', this._onClick);
42+
this._input.addEventListener('change', this._onChange);
4643
}
4744

4845
disconnectedCallback () {
49-
this.removeEventListener('keydown', this.$preventScroll);
50-
this.removeEventListener('keyup', this._onKeyUp);
51-
this.removeEventListener('click', this._onClick);
46+
this._input.removeEventListener('change', this._onChange);
5247
}
5348

5449
attributeChangedCallback (attr, oldVal, newVal) {
55-
super.attributeChangedCallback(attr, oldVal, newVal);
56-
5750
const hasValue = (newVal !== null);
5851
switch (attr) {
5952
case 'indeterminate':
60-
if (hasValue) {
61-
this.setAttribute('aria-checked', 'mixed');
62-
}
53+
this._input.indeterminate = hasValue;
6354
break;
64-
6555
case 'checked':
66-
if (!this.indeterminate) {
67-
this.setAttribute('aria-checked', hasValue);
68-
this.$emit('change', {
69-
checked: this.checked,
70-
});
56+
if (this._input.checked !== hasValue) {
57+
this._input.checked = hasValue;
7158
}
7259
break;
60+
case 'disabled':
61+
this._input.disabled = hasValue;
62+
break;
7363
}
7464
}//attributeChangedCallback()
7565

@@ -97,26 +87,24 @@ export class HXCheckboxElement extends HXElement {
9787
return this.hasAttribute('indeterminate');
9888
}
9989

100-
_onKeyUp (event) {
101-
if (event.altKey) {
102-
return;
103-
}
104-
105-
if (event.keyCode === KEYS.Space) {
106-
event.preventDefault();
107-
this._toggleChecked();
90+
set disabled (value) {
91+
if (value) {
92+
this.setAttribute('disabled', '');
93+
} else {
94+
this.removeAttribute('disabled');
10895
}
10996
}
11097

111-
_onClick () {
112-
this._toggleChecked();
98+
get disabled () {
99+
return this.hasAttribute('disabled');
113100
}
114101

115-
_toggleChecked () {
116-
if (this.disabled) {
117-
return;
118-
}
119-
this.indeterminate = false;
120-
this.checked = !this.checked;
102+
_onChange (evt) {
103+
this.checked = evt.target.checked;
104+
105+
// Chrome doesn't emit 'input' events for checkboxes and native 'change'
106+
// events do not bubble out of the ShadowDOM. Emit a custom event to
107+
// ensure a 'change' event makes it out of the ShadowDOM.
108+
this.$emit('change');
121109
}
122110
}//HXCheckboxElement

src/helix-ui/elements/HXElement.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ export class HXElement extends HTMLElement {
8585
}//$preventScroll()
8686

8787
$emit (evtName, details) {
88+
if (window.ShadyCSS && evtName === 'change') {
89+
// Let native 'change' events bubble naturally.
90+
return;
91+
}
92+
8893
let evt = new CustomEvent(evtName, {
8994
bubbles: true,
9095
details: details,

src/helix-ui/elements/_hx-checkbox.less

Lines changed: 106 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,37 @@
22
@import "core/hx-icon";
33

44
#container {
5+
display: flex;
6+
height: 100%;
7+
position: relative;
8+
width: 100%;
9+
}
10+
11+
#customControl {
512
align-content: center;
613
align-items: center;
14+
background-color: @gray-0;
15+
border: 1px solid currentColor;
16+
border-radius: 2px;
17+
color: @gray-500;
718
display: flex;
8-
font-size: 0.625em; /* ~10px */
19+
font-size: 0.625rem; /* ~10px */
920
height: 100%;
1021
justify-content: center;
22+
left: 0;
23+
position: absolute;
24+
top: 0;
25+
vertical-align: middle;
1126
width: 100%;
27+
z-index: 10;
28+
29+
&:hover {
30+
background-color: @cyan-50;
31+
color: @cyan-500;
32+
}
1233
}
1334

35+
/* icons */
1436
#minus,
1537
#tick {
1638
display: none;
@@ -19,12 +41,89 @@
1941
width: 1em;
2042
}
2143

22-
:host {
23-
&([checked]:not([indeterminate])) #tick {
24-
display: block;
44+
#nativeControl:checked:not(:indeterminate) ~ #customControl #tick {
45+
display: block;
46+
}
47+
48+
#nativeControl:indeterminate ~ #customControl #minus {
49+
display: block;
50+
}
51+
52+
#nativeControl {
53+
/* opacity 0 because Firefox and OS focus styles */
54+
opacity: 0;
55+
z-index: 0;
56+
57+
&:focus {
58+
border: none;
59+
outline: none;
60+
61+
~ #customControl {
62+
border-color: @cyan-700;
63+
box-shadow: @focus-glow;
64+
}
65+
}
66+
67+
/* default checked and indeterminate (checked or unchecked) */
68+
&:checked, &:indeterminate {
69+
~ #customControl {
70+
color: @cyan-900;
71+
72+
&:hover {
73+
background-color: @cyan-50;
74+
color: @cyan-500;
75+
}
76+
}
2577
}
2678

27-
&([indeterminate]) #minus {
28-
display: block;
79+
/* disabled unchecked */
80+
&:disabled ~ #customControl {
81+
background-color: @gray-100;
82+
color: @gray-500;
83+
cursor: not-allowed;
84+
85+
&:hover {
86+
background-color: @gray-100;
87+
color: @gray-500;
88+
}
89+
}//[disabled]
90+
}//input
91+
92+
/* invalid */
93+
:host([invalid]){
94+
#customControl {
95+
border-width: 2px;
96+
color: @red-900;
97+
98+
&:hover {
99+
background-color: @red-100;
100+
}
29101
}
30-
}
102+
103+
#nativeControl:focus ~ #customControl {
104+
border-color: @red-900;
105+
box-shadow: @focus-glow-invalid;
106+
}
107+
108+
/* below styles needed to override above, custom control styles */
109+
/* invalid and checked or indeterminate */
110+
#nativeControl:checked, #nativeControl:indeterminate {
111+
~ #customControl {
112+
color: @red-900;
113+
114+
&:hover {
115+
background-color: @red-100;
116+
}
117+
}
118+
}
119+
120+
/* invalid and disabled */
121+
#nativeControl:disabled ~ #customControl {
122+
border-width: 1px;
123+
color: @gray-500;
124+
125+
&:hover {
126+
background-color: @gray-100;
127+
}
128+
}
129+
}//[invalid]
Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,9 @@
11
hx-checkbox {
2-
background-color: @gray-0;
3-
border-color: currentColor;
4-
border-radius: 2px;
5-
border-style: solid;
6-
border-width: 1px;
7-
color: @gray-500;
82
display: inline-block;
93
height: 1rem;
10-
vertical-align: middle;
114
width: 1rem;
12-
5+
136
& + * {
147
margin-left: 0.5rem;
158
}
16-
17-
/* default unchecked */
18-
&:hover {
19-
background-color: @cyan-50;
20-
color: @cyan-500;
21-
}
22-
23-
/* default checked */
24-
&[checked] {
25-
color: @cyan-900;
26-
27-
&:hover {
28-
background-color: @cyan-50;
29-
color: @cyan-500;
30-
}
31-
}
32-
33-
/* default indeterminate (checked or unchecked) */
34-
&[indeterminate] {
35-
color: @cyan-900;
36-
37-
&:hover {
38-
color: @cyan-500;
39-
}
40-
}
41-
42-
/* invalid unchecked */
43-
&[invalid] {
44-
border-width: 2px;
45-
color: @red-900;
46-
47-
&:hover {
48-
background-color: @red-100;
49-
color: @red-900;
50-
}
51-
52-
/* invalid checked */
53-
// ???
54-
55-
/* invalid indeterminate (checked or unchecked) */
56-
// ???
57-
}//[invalid]
58-
59-
/* disabled unchecked */
60-
&[disabled] {
61-
background-color: @gray-100;
62-
color: @gray-500;
63-
cursor: not-allowed;
64-
65-
& + label {
66-
&:extend(.hxSubdued);
67-
}
68-
69-
&:hover {
70-
background-color: @gray-100;
71-
color: @gray-500;
72-
}
73-
74-
/* disabled checked */
75-
// ???
76-
77-
/* disabled indeterminate (checked or unchecked) */
78-
&[indeterminate] {
79-
color: @gray-500;
80-
}
81-
}//[disabled]
829
}

0 commit comments

Comments
 (0)