Skip to content

Commit c615196

Browse files
authored
1.0 update 🎉
Merge pull request #7 from myfrom/1.0
2 parents 24f936f + 55ba5a2 commit c615196

File tree

3 files changed

+240
-64
lines changed

3 files changed

+240
-64
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ before_script:
1414
- npm install -g polymer-cli bower
1515
- polymer install --variants
1616
script:
17-
- polymer lint
17+
- polymer lint --rules 'polymer-2-hybrid'
1818
- xvfb-run polymer test
1919
- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then polymer test -s 'default'; fi
2020
env:

demo/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,13 @@ <h3>Basic paper-pager demo</h3>
2424
</template>
2525
</demo-snippet>
2626
</div>
27+
<div class="vertical-section-container centered">
28+
<h3>paper-pager with accessibility features</h3>
29+
<demo-snippet>
30+
<template>
31+
<paper-pager dark accessible></paper-pager>
32+
</template>
33+
</demo-snippet>
34+
</div>
2735
</body>
2836
</html>

paper-pager.html

Lines changed: 231 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
and sets `selected` as user clicks on it. It doesn't require any variable but
99
it's higly recommended to provide `itemsCount` (as number) or `items` (as array).
1010
11+
**This element needs to be transpiled to run in older (ES6 incompatible) browsers**
12+
1113
Example:
1214
1315
<paper-pager items-count="3" selected="{{selected}}"></paper-pager>
@@ -20,6 +22,7 @@
2022
----------------|-------------|----------
2123
`--paper-pager-color` | Color of dots | `white`
2224
`--paper-pager-opacity` | Opacity of not selected dots | `0.7`
25+
`--paper-pager-dots-margin` | Margin of dots | `5px`
2326
If you quickly need to switch to dark theme you can use `dark` attribute.
2427
2528
@demo demo/index.html
@@ -31,6 +34,7 @@
3134
:host {
3235
margin: 5px;
3336
position: relative;
37+
display: inline-block;
3438
}
3539

3640
:host([dark]) {
@@ -44,8 +48,14 @@
4448
display: inline-flex;
4549
}
4650

47-
iron-selector div {
48-
margin: 5px;
51+
iron-selector > div {
52+
width: calc(var(--paper-pager-dots-margin, 5px) * 2 + 10px);
53+
height: calc(var(--paper-pager-dots-margin, 5px) * 2 + 10px);
54+
position: relative;
55+
}
56+
57+
iron-selector .dot {
58+
margin: var(--paper-pager-dots-margin, 5px);
4959
border-radius: 5px;
5060
width: 10px;
5161
height: 10px;
@@ -54,27 +64,43 @@
5464
opacity: var(--paper-pager-opacity, 0.7);
5565
}
5666

57-
.dot {
58-
will-change: transform;
59-
display: none;
67+
iron-selector .iron-selected.ready .dot {
68+
opacity: 1;
69+
}
70+
71+
:host([accessible]) iron-selector > div:focus::after {
72+
content: '';
6073
position: absolute;
61-
border-radius: 5px;
74+
top: 0;
75+
bottom: 0;
76+
left: 0;
77+
right: 0;
78+
border-radius: 50%;
79+
opacity: 0.2;
6280
background-color: var(--paper-pager-color, white);
63-
transition: all 300ms cubic-bezier(0.4, 0.0, 0.2, 1);
6481
}
6582

66-
div {
67-
transition: background-color 150ms cubic-bezier(0.4, 0.0, 0.2, 1);
83+
iron-selector > div:focus {
84+
outline: none;
85+
}
86+
87+
#canvas {
88+
position: absolute;
89+
top: 0;
90+
bottom: 0;
91+
right: 0;
92+
left: 0;
93+
pointer-events: none;
6894
}
6995
</style>
7096
<iron-selector selected="[[selected]]">
71-
<dom-repeat items="[[items]]">
72-
<template is="dom-repeat" items="[[items]]">
73-
<div index="[[index]]" on-tap="_onTap"></div>
74-
</template>
75-
</dom-repeat>
97+
<template is="dom-repeat" items="[[items]]">
98+
<div index="[[index]]" on-tap="_onTap">
99+
<div class="dot"></div>
100+
</div>
101+
</template>
76102
</iron-selector>
77-
<div class="dot"></div>
103+
<canvas id="canvas"></canvas>
78104
</template>
79105
<script>
80106
Polymer({
@@ -99,7 +125,8 @@
99125
* leave itemsCount empty.
100126
*/
101127
items: {
102-
type: Array
128+
type: Array,
129+
observer: '_changeSize'
103130
},
104131

105132
/**
@@ -120,74 +147,215 @@
120147
type: Boolean,
121148
value: false,
122149
observer: '_updateStyles'
150+
},
151+
152+
/**
153+
* Time in ms for the animation between two dots
154+
*/
155+
transitionDuration: {
156+
type: Number,
157+
value: 200
158+
},
159+
160+
/**
161+
* Time in ms for the transition to pause (when two dots connect)
162+
*/
163+
pauseDuration: {
164+
type: Number,
165+
value: 200
166+
},
167+
168+
/**
169+
* Turn on accessibility features (keyboard navigation, focus ring);
170+
*/
171+
accessible: {
172+
type: Boolean,
173+
reflectToAttribute: true,
174+
observer: '_setupAccessibility'
123175
}
124176

125177
},
126178

179+
/*keyBindings: {
180+
'down' : '_previous',
181+
'up' : '_next',
182+
'left' : '_previous',
183+
'right' : '_next',
184+
'space' : '_enterSelected',
185+
'enter' : '_enterSelected',
186+
},*/
187+
127188
attached: function() {
128-
if (this.selected == 0) {
129-
this._selectedChanged(this.selected, 2);
130-
} else {
131-
this._selectedChanged(this.selected, 0);
132-
}
189+
this._draw = this.$.canvas.getContext('2d');
190+
Polymer.RenderStatus.afterNextRender(this, () => {
191+
this.$$('.iron-selected').classList.add('ready');
192+
});
133193
},
134194

135195
_onTap: function(e) {
136-
this.selected = e.target.index;
196+
this.selected = e.currentTarget.index;
137197
},
138198

139199
_computeItems: function(count) {
140200
this.items = new Array(count);
141201
},
142202

143-
_selectedChanged: function(selected, lastSelected) {
144-
if(selected === undefined || lastSelected === undefined) return;
145-
var dot = this.$$('.dot').style;
146-
if (this.items.length && selected != lastSelected) {
147-
dot.display = 'block';
148-
var selectedN = selected + 1;
149-
var lastSelectedN = lastSelected ? lastSelected + 1 : 1;
150-
var selectedItem = this.$$('div:nth-child(' + selectedN + ')');
151-
var lastSelectedItem = this.$$('div:nth-child(' + lastSelectedN + ')');
152-
if(!selectedItem || !lastSelectedItem) return;
153-
var lastSelRect = lastSelectedItem.getBoundingClientRect();
154-
var selectedRect = selectedItem.getBoundingClientRect();
155-
var elRect = this.getBoundingClientRect();
156-
selectedRect = this._processRelativeRect(selectedRect, elRect);
157-
lastSelRect = this._processRelativeRect(lastSelRect, elRect);
158-
dot.top = lastSelRect.top + 'px';
159-
dot.bottom = lastSelRect.bottom + 'px';
160-
dot.left = lastSelRect.left + 'px';
161-
dot.right = lastSelRect.right + 'px';
162-
if (lastSelected > selected) {
163-
dot.left = selectedRect.left + 'px';
164-
dot.right = lastSelRect.right + 'px';
165-
} else {
166-
dot.left = lastSelRect.left + 'px';
167-
dot.right = selectedRect.right + 'px';
168-
}
169-
setTimeout(function() {
170-
dot.left = selectedRect.left + 'px';
171-
dot.right = selectedRect.right + 'px';
172-
}, 400);
173-
}
203+
_changeSize: function(items) {
204+
const marginPx = this.getComputedStyleValue('--paper-pager-dots-margin');
205+
const margin = marginPx ? marginPx.match(/\d+/)[0] : 5;
206+
this.$.canvas.height = (10 + 2 * margin);
207+
this.$.canvas.width = items.length * (10 + 2 * margin);
174208
},
175209

176-
_processRelativeRect: function(element, parent) {
177-
var output = {
178-
height: element.height,
179-
width: element.width,
180-
top: element.top - parent.top,
181-
right: Math.abs(element.right - parent.right),
182-
bottom: Math.abs(element.bottom - parent.bottom),
183-
left: element.left - parent.left
210+
_selectedChanged: async function(selected, lastSelected) {
211+
if (!this._draw) return;
212+
if (this.accessible) {
213+
this._tabindex = this._tabindex.bind(this);
214+
setTimeout(this._tabindex);
215+
}
216+
if (this.$$('.ready')) this.$$('.ready').classList.remove('ready');
217+
this.$.canvas.style.pointerEvents = 'auto';
218+
const ctx = this._draw,
219+
color = this.getComputedStyleValue('--paper-pager-color') || 'white',
220+
marginPx = this.getComputedStyleValue('--paper-pager-dots-margin'),
221+
margin = marginPx ? marginPx.match(/\d+/)[0] : 5,
222+
y = margin + 5,
223+
width = this.$.canvas.width,
224+
height = this.$.canvas.height,
225+
start = (margin * 2 + 10) * (lastSelected + 1) - 10,
226+
end = (margin * 2 + 10) * (selected + 1) - 10,
227+
duration = this.transitionDuration,
228+
cycles = duration / 17,
229+
frameDistance = (start - end) / Math.round(cycles);
230+
let i = 0,
231+
pos = start;
232+
const draw = () => {
233+
i++;
234+
pos -= frameDistance;
235+
ctx.beginPath();
236+
ctx.clearRect(0, 0, width, height);
237+
ctx.moveTo(start, y);
238+
ctx.lineTo(pos, y);
239+
ctx.lineWidth = 10;
240+
ctx.strokeStyle = color;
241+
ctx.lineCap = 'round';
242+
ctx.stroke();
243+
if (i >= cycles) {
244+
clearInterval(interval);
245+
ctx.clearRect(0, 0, width, height);
246+
ctx.moveTo(start, y);
247+
ctx.lineTo(end, y);
248+
ctx.lineWidth = 10;
249+
ctx.strokeStyle = color;
250+
ctx.lineCap = 'round';
251+
ctx.stroke();
252+
}
184253
};
185-
186-
return output;
254+
const interval = setInterval(draw, 17);
255+
await this._wait(this.pauseDuration + duration);
256+
pos = start;
257+
i = 0;
258+
const drawReverse = () => {
259+
i++;
260+
pos -= frameDistance;
261+
ctx.beginPath();
262+
ctx.clearRect(0, 0, width, height);
263+
ctx.moveTo(end, y);
264+
ctx.lineTo(pos, y);
265+
ctx.lineWidth = 10;
266+
ctx.strokeStyle = color;
267+
ctx.lineCap = 'round';
268+
ctx.stroke();
269+
if (i >= cycles) {
270+
clearInterval(intervalReverse);
271+
ctx.clearRect(0, 0, width, height);
272+
ctx.moveTo(end, y);
273+
ctx.lineTo(end, y);
274+
ctx.lineWidth = 10;
275+
ctx.strokeStyle = color;
276+
ctx.lineCap = 'round';
277+
ctx.stroke();
278+
}
279+
};
280+
const intervalReverse = setInterval(drawReverse, 17);
281+
await this._wait(duration + 17);
282+
ctx.clearRect(0, 0, width, height);
283+
this.$.canvas.style.pointerEvents = 'none';
284+
this.$$('.iron-selected').classList.add('ready');
187285
},
188286

189287
_updateStyles: function() {
190288
this.updateStyles();
289+
},
290+
291+
_next: function(e) {
292+
e.detail.keyboardEvent.preventDefault();
293+
if (this._focused === this.items.length - 1) {
294+
this._focused = 0;
295+
} else {
296+
this._focused++;
297+
}
298+
Polymer.dom(this.root).querySelectorAll('iron-selector > div').forEach(item => {
299+
if (item.index === this._focused) {
300+
item.tabIndex = 0;
301+
item.focus();
302+
} else {
303+
item.tabIndex = -1;
304+
}
305+
});
306+
},
307+
308+
_previous: function(e) {
309+
e.detail.keyboardEvent.preventDefault();
310+
if (this._focused === 0) {
311+
this._focused = this.items.length - 1;
312+
} else {
313+
this._focused--;
314+
}
315+
Polymer.dom(this.root).querySelectorAll('iron-selector > div').forEach(item => {
316+
if (item.index === this._focused) {
317+
item.tabIndex = 0;
318+
item.focus();
319+
} else {
320+
item.tabIndex = -1;
321+
}
322+
});
323+
},
324+
325+
_enterSelected: function(e) {
326+
e.detail.keyboardEvent.preventDefault();
327+
this.selected = this._focused;
328+
},
329+
330+
_tabindex: function() {
331+
this._focused = this.selected;
332+
Polymer.dom(this.root).querySelectorAll('iron-selector > div').forEach(item => {
333+
item.tabIndex = item.index === this.selected ? 0 : -1;
334+
});
335+
},
336+
337+
_setupAccessibility: function(a11y) {
338+
if (a11y) {
339+
Polymer.RenderStatus.afterNextRender(this, () => {
340+
this.$$('iron-selector .iron-selected').tabIndex = 0;
341+
});
342+
this._focused = this.selected;
343+
this.addOwnKeyBinding('down', '_previous');
344+
this.addOwnKeyBinding('up', '_next');
345+
this.addOwnKeyBinding('left', '_previous');
346+
this.addOwnKeyBinding('right', '_next');
347+
this.addOwnKeyBinding('space', '_enterSelected');
348+
this.addOwnKeyBinding('enter', '_enterSelected');
349+
} else {
350+
Polymer.dom(this.root).querySelectorAll('iron-selector > div').forEach(item => {
351+
item.tabIndex = -1;
352+
});
353+
this.removeOwnKeyBindings();
354+
}
355+
},
356+
357+
_wait: function(ms) {
358+
return new Promise(r => setTimeout(r, ms));
191359
}
192360
});
193361
</script>

0 commit comments

Comments
 (0)