@ucd-lib/theme-elements 3.0.2 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -44,8 +44,10 @@ export function styles() {
|
|
|
44
44
|
.branding-bar-mobile-links {
|
|
45
45
|
display: block;
|
|
46
46
|
}
|
|
47
|
+
.fixed-mobile .off-canvas__container {
|
|
48
|
+
padding-bottom: 9rem;
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
|
-
|
|
49
51
|
@media (min-width: 992px) {
|
|
50
52
|
.fixed-desktop .l-navbar {
|
|
51
53
|
position: fixed;
|
|
@@ -101,21 +103,26 @@ export function styles() {
|
|
|
101
103
|
];
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
export function render() {
|
|
106
|
+
export function render() {
|
|
105
107
|
return html`
|
|
106
108
|
${this.isDemo ? html`
|
|
107
109
|
<style>
|
|
108
110
|
.l-navbar { top: auto !important}
|
|
109
111
|
</style>
|
|
110
112
|
` : html``}
|
|
113
|
+
${this._hasQuickLinks ? html`
|
|
114
|
+
<style>::slotted(ucd-theme-search-popup){ --ucd-theme-search-popup--margin-right:-1rem;}</style>
|
|
115
|
+
` : html`
|
|
116
|
+
<style>::slotted(ucd-theme-search-popup){--ucd-theme-search-popup--margin-right:0;}</style>
|
|
117
|
+
`}
|
|
111
118
|
<header class=${classMap(this._getHeaderClasses())}>
|
|
112
119
|
<div class="mobile-bar">
|
|
113
120
|
<div class="mobile-bar__nav-toggle">
|
|
114
|
-
<button
|
|
121
|
+
<button
|
|
115
122
|
@click=${this._onBtnClick}
|
|
116
|
-
class="nav-toggle ${this.opened ? 'nav-toggle--active' : ''}"
|
|
117
|
-
aria-controls="primary-nav"
|
|
118
|
-
aria-expanded="${this.opened ? 'true' : 'false'}"
|
|
123
|
+
class="nav-toggle ${this.opened ? 'nav-toggle--active' : ''}"
|
|
124
|
+
aria-controls="primary-nav"
|
|
125
|
+
aria-expanded="${this.opened ? 'true' : 'false'}"
|
|
119
126
|
aria-label="Toggle Main Menu">
|
|
120
127
|
<span class="nav-toggle__icon nav-toggle__icon--menu">Menu</span>
|
|
121
128
|
</button>
|
|
@@ -175,7 +182,7 @@ ${this.isDemo ? html`
|
|
|
175
182
|
<div class='branding-bar-mobile-links'>
|
|
176
183
|
<ul>
|
|
177
184
|
${this._brandingBarLinks.map(link => html`
|
|
178
|
-
<li><a
|
|
185
|
+
<li><a
|
|
179
186
|
href=${ifDefined(link.href ? link.href : null)}
|
|
180
187
|
target=${ifDefined(link.newTab ? "_blank": null)}
|
|
181
188
|
>${link.linkText}</a></li>
|
|
@@ -188,6 +195,4 @@ ${this.isDemo ? html`
|
|
|
188
195
|
</div>
|
|
189
196
|
</div>
|
|
190
197
|
</header>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`;}
|
|
198
|
+
`;}
|
|
@@ -8,6 +8,9 @@ export function styles() {
|
|
|
8
8
|
:host {
|
|
9
9
|
display: block;
|
|
10
10
|
}
|
|
11
|
+
.search-popup__open::after {
|
|
12
|
+
margin-right: var(--ucd-theme-search-popup--margin-right, -1rem)
|
|
13
|
+
}
|
|
11
14
|
`;
|
|
12
15
|
|
|
13
16
|
return [
|
|
@@ -16,10 +19,10 @@ export function styles() {
|
|
|
16
19
|
elementStyles];
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
export function render() {
|
|
22
|
+
export function render() {
|
|
20
23
|
return html`
|
|
21
|
-
<button
|
|
22
|
-
class="search-popup__open ${this.opened ? 'search-popup__open--close' : ''}"
|
|
24
|
+
<button
|
|
25
|
+
class="search-popup__open ${this.opened ? 'search-popup__open--close' : ''}"
|
|
23
26
|
@click=${this._onBtnClick}>
|
|
24
27
|
<span class="search-popup__open-icon">${this.buttonText}</span>
|
|
25
28
|
</button>
|
|
@@ -34,4 +37,4 @@ return html`
|
|
|
34
37
|
|
|
35
38
|
</div>
|
|
36
39
|
|
|
37
|
-
`;}
|
|
40
|
+
`;}
|
package/package.json
CHANGED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
|
|
3
|
+
import render from './ucdlib-range-slider.tpl.js';
|
|
4
|
+
|
|
5
|
+
import './ucdlib-range-slider.js';
|
|
6
|
+
|
|
7
|
+
export default class UcdlibRangeSlider extends LitElement {
|
|
8
|
+
static get properties() {
|
|
9
|
+
return {
|
|
10
|
+
data : { type: Array },
|
|
11
|
+
mergedData : { type: Array },
|
|
12
|
+
|
|
13
|
+
// slider/date input values
|
|
14
|
+
absMin: { type: Number },
|
|
15
|
+
absMax: { type: Number },
|
|
16
|
+
min: { type: Number },
|
|
17
|
+
max: { type: Number },
|
|
18
|
+
|
|
19
|
+
// labels for slide btns
|
|
20
|
+
minLabel: { type: String },
|
|
21
|
+
maxLabel: { type: String },
|
|
22
|
+
|
|
23
|
+
isMoving: { type: Boolean },
|
|
24
|
+
movingType: { type: String },
|
|
25
|
+
movingMin: { type: Boolean },
|
|
26
|
+
movingMax: { type: Boolean },
|
|
27
|
+
|
|
28
|
+
showUnknown: { type: Boolean },
|
|
29
|
+
|
|
30
|
+
// track width and height of widget so we don't have to ask the DOM
|
|
31
|
+
width: { type: Number },
|
|
32
|
+
height: { type: Number },
|
|
33
|
+
btnHeight: { type: Number },
|
|
34
|
+
|
|
35
|
+
hideHistogram: { type: Boolean, attribute: 'hide-histogram' },
|
|
36
|
+
hideSliderLabels: { type: Boolean, attribute: 'hide-slider-labels' },
|
|
37
|
+
|
|
38
|
+
// colors for histogram
|
|
39
|
+
lightColor: { type: String, attribute: 'light-color' },
|
|
40
|
+
mediumColor: { type: String, attribute: 'medium-color' },
|
|
41
|
+
darkColor: { type: String, attribute: 'dark-color' }
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
super();
|
|
47
|
+
this.render = render.bind(this);
|
|
48
|
+
this.active = true;
|
|
49
|
+
|
|
50
|
+
this.data = [];
|
|
51
|
+
this.mergedData = [];
|
|
52
|
+
|
|
53
|
+
this.absMin = 0;
|
|
54
|
+
this.absMax = 0;
|
|
55
|
+
this.min = this.absMin;
|
|
56
|
+
this.max = this.absMax;
|
|
57
|
+
this.showUnknown = false;
|
|
58
|
+
|
|
59
|
+
this.minLabel = '';
|
|
60
|
+
this.maxLabel = '';
|
|
61
|
+
this.width = 1;
|
|
62
|
+
this.height = 50;
|
|
63
|
+
this.btnHeight = 1;
|
|
64
|
+
this.movingType = '';
|
|
65
|
+
this.movingMin = false;
|
|
66
|
+
this.movingMax = false;
|
|
67
|
+
this.isMoving = false;
|
|
68
|
+
this.hasRendered = false;
|
|
69
|
+
this.hideHistogram = false;
|
|
70
|
+
this.hideSliderLabels = false;
|
|
71
|
+
this.lightColor = '#CCE0F3';
|
|
72
|
+
this.mediumColor = '#73ABDD';
|
|
73
|
+
this.darkColor = '#13639E';
|
|
74
|
+
|
|
75
|
+
// consts to build histogram
|
|
76
|
+
this.gapPx = 2;
|
|
77
|
+
this.minBinWidth = 6;
|
|
78
|
+
this.minBins = 5;
|
|
79
|
+
this.maxBins = 50;
|
|
80
|
+
|
|
81
|
+
this._windowResizeListener = this._onResize.bind(this);
|
|
82
|
+
this._windowMouseListener = this._onMoveStop.bind(this);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @method connectedCallback
|
|
87
|
+
* @description setup our window mouse listeners, fire first render
|
|
88
|
+
*/
|
|
89
|
+
connectedCallback() {
|
|
90
|
+
super.connectedCallback();
|
|
91
|
+
|
|
92
|
+
this.addEventListener('mousemove', (e) => this._onMove(e));
|
|
93
|
+
this.addEventListener('touchmove', (e) => this._onMove(e));
|
|
94
|
+
|
|
95
|
+
window.addEventListener('resize', this._windowResizeListener);
|
|
96
|
+
window.addEventListener('mouseup', this._windowMouseListener);
|
|
97
|
+
window.addEventListener('mouseout', this._windowMouseListener);
|
|
98
|
+
window.addEventListener('touchend', this._windowMouseListener);
|
|
99
|
+
window.addEventListener('touchcancel', this._windowMouseListener);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @method disconnectedCallback
|
|
104
|
+
* @description remove our window mouse listeners
|
|
105
|
+
*/
|
|
106
|
+
disconnectedCallback() {
|
|
107
|
+
super.disconnectedCallback();
|
|
108
|
+
|
|
109
|
+
window.removeEventListener('resize', this._windowResizeListener);
|
|
110
|
+
window.removeEventListener('mouseup', this._windowMouseListener);
|
|
111
|
+
window.removeEventListener('mouseout', this._windowMouseListener);
|
|
112
|
+
window.removeEventListener('touchend', this._windowMouseListener);
|
|
113
|
+
window.removeEventListener('touchcancel', this._windowMouseListener);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
willUpdate(e) {
|
|
117
|
+
if (!this.hasRendered) {
|
|
118
|
+
requestAnimationFrame(() => {
|
|
119
|
+
this._onResize(null, false);
|
|
120
|
+
this._renderAsync();
|
|
121
|
+
});
|
|
122
|
+
this.hasRendered = true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @method _onResize
|
|
128
|
+
* @description when the window resizes, re-render the histogram
|
|
129
|
+
*
|
|
130
|
+
* @param {Event} evt
|
|
131
|
+
* @param {Boolean} reMerge should we re-merge the data, typically when resizing window could cause difference
|
|
132
|
+
*/
|
|
133
|
+
_onResize(evt, reMerge=true) {
|
|
134
|
+
this.width = this.offsetWidth || 1;
|
|
135
|
+
this.height = this.offsetHeight;
|
|
136
|
+
this.left = this.offsetLeft;
|
|
137
|
+
let lowNumberBtn = this.shadowRoot.querySelector('#lowNumberBtn');
|
|
138
|
+
if (lowNumberBtn) {
|
|
139
|
+
this.height = 50;
|
|
140
|
+
this.btnHeight = 25;
|
|
141
|
+
this._render();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this._updateHistogram(reMerge);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @method _updateHistogram
|
|
149
|
+
* @description render histogram bins based on the min and max values from data received,
|
|
150
|
+
* and the width of the rendered svg. smallest bin possible is 6px with 2px gaps,
|
|
151
|
+
* and a max of 50 bins is possible.
|
|
152
|
+
* otherwise data points will be merged to fit within the max bins.
|
|
153
|
+
* if less than 5 bins, histogram will be hidden.
|
|
154
|
+
*
|
|
155
|
+
* @param {Boolean} reMerge should we re-merge the data, typically when resizing window could cause difference
|
|
156
|
+
*/
|
|
157
|
+
_updateHistogram(reMerge=false) {
|
|
158
|
+
if( this.hideHistogram ) return;
|
|
159
|
+
|
|
160
|
+
this.absMin = this.data?.[0]?.stat || 0;
|
|
161
|
+
this.absMax = this.data?.[this.data?.length - 1]?.stat || 0;
|
|
162
|
+
|
|
163
|
+
if( !this.merged ) {
|
|
164
|
+
this.min = this.absMin;
|
|
165
|
+
this.max = this.absMax;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if( this.data?.length < 5 ) return this.hideHistogram = true;
|
|
169
|
+
|
|
170
|
+
// get bound of svg
|
|
171
|
+
let svg = this.shadowRoot.getElementById('histogram');
|
|
172
|
+
let svgWidth = Math.floor(svg.getBoundingClientRect().width);
|
|
173
|
+
let svgHeight = svg.getBoundingClientRect().height;
|
|
174
|
+
if( !svgWidth || !svgHeight ) return;
|
|
175
|
+
|
|
176
|
+
let maxBarHeight = svgHeight - 20; // space for padding above histo
|
|
177
|
+
let binWidth = (svgWidth / (this.mergedData.length || this.data.length)) || 1;
|
|
178
|
+
svg.innerHTML = '';
|
|
179
|
+
|
|
180
|
+
// max 50 bins of 6px + 2px gap, so 50*8-2 = 398px min width
|
|
181
|
+
// if over 50 bins and svgWidth < 398px, need to merge bins
|
|
182
|
+
// start by merging 2 bins at a time, then 3, etc
|
|
183
|
+
// only merge initially on render, or when resizing window
|
|
184
|
+
if( !this.merged || reMerge ) {
|
|
185
|
+
// merge bins if necessary
|
|
186
|
+
let mergedData = JSON.parse(JSON.stringify(this.data));
|
|
187
|
+
let mergedBins = mergedData.length;
|
|
188
|
+
|
|
189
|
+
let mergedPerBin = 1;
|
|
190
|
+
|
|
191
|
+
while( binWidth < (this.minBinWidth + this.gapPx) || mergedBins > this.maxBins ) {
|
|
192
|
+
mergedBins = Math.ceil(mergedData.length / 2);
|
|
193
|
+
binWidth *= 2;
|
|
194
|
+
mergedData = [];
|
|
195
|
+
mergedPerBin *= 2;
|
|
196
|
+
|
|
197
|
+
for( let i = 0; i < mergedBins; i++ ) {
|
|
198
|
+
let start = i * mergedPerBin;
|
|
199
|
+
let end = start + mergedPerBin - 1;
|
|
200
|
+
|
|
201
|
+
if( end >= this.data.length ) {
|
|
202
|
+
mergedData.push(this.data[start]);
|
|
203
|
+
} else {
|
|
204
|
+
let mergedValue = this.data[start].value + this.data[end].value;
|
|
205
|
+
let mergedStat = this.data[start].stat + '-' + this.data[end].stat;
|
|
206
|
+
mergedData.push({ stat: mergedStat, value: mergedValue });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// recalc after potential merging above,
|
|
211
|
+
// to fix floating point precision issues when multiplying binWidth multiple times
|
|
212
|
+
binWidth = (svgWidth / (mergedData || []).length) || 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.mergedData = mergedData;
|
|
216
|
+
this.merged = true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// find the max value in the data
|
|
220
|
+
let max = Math.max(...this.mergedData.map(d => d.value));
|
|
221
|
+
|
|
222
|
+
// create bins
|
|
223
|
+
this.mergedData.forEach((d, i) => {
|
|
224
|
+
let barHeight = (d.value / max) * maxBarHeight;
|
|
225
|
+
let rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
226
|
+
rect.setAttribute('x', i * binWidth);
|
|
227
|
+
rect.setAttribute('y', svgHeight - barHeight);
|
|
228
|
+
rect.setAttribute('width', binWidth - this.gapPx);
|
|
229
|
+
rect.setAttribute('height', barHeight);
|
|
230
|
+
rect.setAttribute('class', 'bin');
|
|
231
|
+
rect.setAttribute('stat', d.stat);
|
|
232
|
+
svg.appendChild(rect);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
this.binWidth = binWidth;
|
|
236
|
+
this.numBins = (this.mergedData.length || this.data.length);
|
|
237
|
+
|
|
238
|
+
this._updateHistogramColors();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @method _updateHistogramColors
|
|
243
|
+
* @description update light/medium/dark bin colors in histogram
|
|
244
|
+
*/
|
|
245
|
+
_updateHistogramColors() {
|
|
246
|
+
if( this.hideHistogram ) return;
|
|
247
|
+
|
|
248
|
+
let histogram = this.shadowRoot.querySelector('#histogram');
|
|
249
|
+
if( !histogram ) return;
|
|
250
|
+
|
|
251
|
+
let bins = histogram.querySelectorAll('.bin');
|
|
252
|
+
bins.forEach(bin => {
|
|
253
|
+
let stat = bin.getAttribute('stat');
|
|
254
|
+
|
|
255
|
+
// when data stats are merged, there will be a string of stats separated by a hyphen
|
|
256
|
+
// ie '2010-2012' for years
|
|
257
|
+
// color partially selected bins with a medium color
|
|
258
|
+
let startStat = parseInt(stat.split('-')[0]);
|
|
259
|
+
let endStat = parseInt(stat.split('-').pop());
|
|
260
|
+
|
|
261
|
+
if( (startStat < this.min && endStat < this.min) || (startStat > this.max && endStat > this.max) ) {
|
|
262
|
+
bin.style.fill = this.lightColor;
|
|
263
|
+
} else if( startStat >= this.min && endStat <= this.max ) {
|
|
264
|
+
bin.style.fill = this.darkColor;
|
|
265
|
+
} else {
|
|
266
|
+
bin.style.fill = this.mediumColor;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @method _valueToPx
|
|
273
|
+
* @description given a number line value, return px location relative
|
|
274
|
+
* to the widget
|
|
275
|
+
*
|
|
276
|
+
* @param {Number} value number line value
|
|
277
|
+
* @param {Boolean} isMin is this the min value
|
|
278
|
+
*
|
|
279
|
+
* @returns {Number} px location
|
|
280
|
+
*/
|
|
281
|
+
_valueToPx(value, isMin=false) {
|
|
282
|
+
let px = 0;
|
|
283
|
+
let range = this.absMax - this.absMin + 1;
|
|
284
|
+
|
|
285
|
+
value -= this.absMin;
|
|
286
|
+
if( !isMin ) value += 1;
|
|
287
|
+
|
|
288
|
+
// no merging of bins
|
|
289
|
+
if( this.numBins === range ) {
|
|
290
|
+
px = Math.round(value * this.binWidth);
|
|
291
|
+
} else {
|
|
292
|
+
let rangeWidth = this.binWidth / (range / this.numBins);
|
|
293
|
+
px = Math.round(value * rangeWidth);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @method _pxToValue
|
|
301
|
+
* @description given a px location, return number line value
|
|
302
|
+
*
|
|
303
|
+
* @param {Number} px location
|
|
304
|
+
*
|
|
305
|
+
* @returns {Number} value
|
|
306
|
+
*/
|
|
307
|
+
_pxToValue(px) {
|
|
308
|
+
let range = this.absMax - this.absMin;
|
|
309
|
+
let valPerPx = range / this.width;
|
|
310
|
+
return Math.round(px * valPerPx) + this.absMin;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @method _renderAsync
|
|
315
|
+
* @description debounce render calls
|
|
316
|
+
*/
|
|
317
|
+
_renderAsync() {
|
|
318
|
+
if (this.renderTimer) {
|
|
319
|
+
clearTimeout(this.renderTimer);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.renderTimer = setTimeout(() => {
|
|
323
|
+
this.renderTimer = 0;
|
|
324
|
+
this._render();
|
|
325
|
+
}, 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @method _render
|
|
330
|
+
* @description set top/left px values for buttons/slider
|
|
331
|
+
*/
|
|
332
|
+
_render() {
|
|
333
|
+
let hh = this.height * 0.6;
|
|
334
|
+
|
|
335
|
+
// set line heights
|
|
336
|
+
this.shadowRoot.querySelector('#numberLine').style.top = hh + 'px';
|
|
337
|
+
this.shadowRoot.querySelector('#fillLine').style.top = hh + 'px';
|
|
338
|
+
|
|
339
|
+
// set btn heights
|
|
340
|
+
let hBtnHeight = this.btnHeight / 2;
|
|
341
|
+
this.shadowRoot.querySelector('#lowNumberBtn').style.top =
|
|
342
|
+
hh - hBtnHeight + 6 + 'px';
|
|
343
|
+
this.shadowRoot.querySelector('#highNumberBtn').style.top =
|
|
344
|
+
hh - hBtnHeight + 6 + 'px';
|
|
345
|
+
|
|
346
|
+
this.shadowRoot.querySelector('#lowNumberLabel').style.top =
|
|
347
|
+
hh - hBtnHeight - 22 + 'px';
|
|
348
|
+
this.shadowRoot.querySelector('#highNumberLabel').style.top =
|
|
349
|
+
hh - hBtnHeight - 22 + 'px';
|
|
350
|
+
|
|
351
|
+
// set btn left
|
|
352
|
+
let lv =
|
|
353
|
+
this.min < this.absMin ? this.absMin : this.min;
|
|
354
|
+
let uv =
|
|
355
|
+
this.max > this.absMax ? this.absMax : this.max;
|
|
356
|
+
|
|
357
|
+
let minPxValue = this._valueToPx(lv, true);
|
|
358
|
+
let maxPxValue = this._valueToPx(uv);
|
|
359
|
+
|
|
360
|
+
this.shadowRoot.querySelector('#lowNumberBtn').style.left =
|
|
361
|
+
minPxValue - hBtnHeight + 'px';
|
|
362
|
+
this.shadowRoot.querySelector('#highNumberBtn').style.left =
|
|
363
|
+
maxPxValue - hBtnHeight + 'px';
|
|
364
|
+
|
|
365
|
+
this.shadowRoot.querySelector('#lowNumberLabel').style.left =
|
|
366
|
+
minPxValue - hBtnHeight + 'px';
|
|
367
|
+
this.shadowRoot.querySelector('#highNumberLabel').style.left =
|
|
368
|
+
maxPxValue - hBtnHeight + 'px';
|
|
369
|
+
|
|
370
|
+
this.shadowRoot.querySelector('#fillLine').style.left = minPxValue + 'px';
|
|
371
|
+
this.shadowRoot.querySelector('#fillLine').style.width =
|
|
372
|
+
maxPxValue - minPxValue + 'px';
|
|
373
|
+
|
|
374
|
+
this.minLabel = this.min;
|
|
375
|
+
this.maxLabel = this.max;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @method _onRangeSliderChange
|
|
380
|
+
* @description moving of range slider has stopped
|
|
381
|
+
*/
|
|
382
|
+
_onRangeSliderChange(e) {
|
|
383
|
+
this._onRangeNullChange();
|
|
384
|
+
this._updateHistogramColors();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @method _onRangeNullChange
|
|
389
|
+
* @description bound to input checkbox
|
|
390
|
+
*/
|
|
391
|
+
_onRangeNullChange(evt) {
|
|
392
|
+
let value = {
|
|
393
|
+
gte: this.min,
|
|
394
|
+
lte: this.max,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
if( this.shadowRoot.querySelector('#unknown').checked ) {
|
|
398
|
+
value.includeNull = true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if( evt ) this._notifySelected();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @method _onInputChange
|
|
406
|
+
* @description bound to min/max number inputs
|
|
407
|
+
*/
|
|
408
|
+
_onInputChange(e) {
|
|
409
|
+
if( e.currentTarget.id === 'minInput' ) {
|
|
410
|
+
let min = Number(e.currentTarget.value);
|
|
411
|
+
if( min < this.absMin ) min = this.absMin;
|
|
412
|
+
if( min > this.absMax ) min = this.absMax;
|
|
413
|
+
if( min > this.max ) min = this.max;
|
|
414
|
+
this.min = min;
|
|
415
|
+
e.target.value = this.min;
|
|
416
|
+
} else if( e.currentTarget.id === 'maxInput' ) {
|
|
417
|
+
let max = Number(e.currentTarget.value);
|
|
418
|
+
if( max > this.absMax ) max = this.absMax;
|
|
419
|
+
if( max < this.absMin ) max = this.absMin;
|
|
420
|
+
if( max < this.min ) max = this.min;
|
|
421
|
+
this.max = max;
|
|
422
|
+
e.target.value = this.max;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this._render();
|
|
426
|
+
this._onRangeNullChange();
|
|
427
|
+
this._updateHistogramColors();
|
|
428
|
+
this._notifySelected();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @method _isFilterApplied
|
|
433
|
+
* @description is there currenlty a filter set
|
|
434
|
+
*
|
|
435
|
+
* @return {Boolean}
|
|
436
|
+
*/
|
|
437
|
+
_isFilterApplied() {
|
|
438
|
+
if (
|
|
439
|
+
this.min === this.absMin &&
|
|
440
|
+
this.max === this.absMax &&
|
|
441
|
+
this.shadowRoot.querySelector('#unknown').checked === true
|
|
442
|
+
) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* @method _notifySelected
|
|
450
|
+
* @description notify parent of selection change
|
|
451
|
+
*/
|
|
452
|
+
_notifySelected() {
|
|
453
|
+
this.dispatchEvent(
|
|
454
|
+
new CustomEvent('range-slider-change', {
|
|
455
|
+
detail: {
|
|
456
|
+
min: this.min,
|
|
457
|
+
max: this.max,
|
|
458
|
+
includeUnknown: this.shadowRoot.querySelector('#unknown').checked
|
|
459
|
+
},
|
|
460
|
+
})
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @method reset
|
|
466
|
+
* @description reset range filter
|
|
467
|
+
*/
|
|
468
|
+
reset() {
|
|
469
|
+
this.min = this.absMin;
|
|
470
|
+
this.max = this.absMax;
|
|
471
|
+
this.shadowRoot.querySelector('#unknown').checked = true;
|
|
472
|
+
|
|
473
|
+
this._onRangeNullChange();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @method _onMoveStart
|
|
478
|
+
* @description bound to btns and center line. Fired when the user mouses
|
|
479
|
+
* down on element indicating a move is starting
|
|
480
|
+
*
|
|
481
|
+
* @param {MouseEvent} e
|
|
482
|
+
*/
|
|
483
|
+
_onMoveStart(e) {
|
|
484
|
+
this.movingType = e.currentTarget.getAttribute('prop');
|
|
485
|
+
|
|
486
|
+
this.isMoving = true;
|
|
487
|
+
this.movingMin = this.movingType === 'max' ? false : true;
|
|
488
|
+
this.movingMax = this.movingType === 'min' ? false : true;
|
|
489
|
+
|
|
490
|
+
if( this.movingType === 'range' ) this._onMoveMiddle(e);
|
|
491
|
+
if( this.movingType === 'outside-range' ) this._onMoveOutsideRange(e);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* @method _onMove
|
|
496
|
+
* @description bound to mousemove event on this element. Update min/max
|
|
497
|
+
* values based on type of move that is happening ie min, max or range. Does
|
|
498
|
+
* nothing if we are not moving.
|
|
499
|
+
*
|
|
500
|
+
* @param {MouseEvent} e
|
|
501
|
+
*/
|
|
502
|
+
_onMove(e) {
|
|
503
|
+
if (!this.isMoving) return;
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
|
|
506
|
+
// handle both mouse and touch event
|
|
507
|
+
let left;
|
|
508
|
+
if (e.type === 'touchmove') {
|
|
509
|
+
if (!e.changedTouches.length) return;
|
|
510
|
+
left = e.changedTouches[0].pageX - this.left;
|
|
511
|
+
} else {
|
|
512
|
+
left = e.pageX - this.left;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if( this.movingType === 'min' ) {
|
|
516
|
+
this.min = this._pxToValue(left);
|
|
517
|
+
} else if( this.movingType === 'max' ) {
|
|
518
|
+
this.max = this._pxToValue(left);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (this.min < this.absMin) {
|
|
522
|
+
this.min = this.absMin;
|
|
523
|
+
}
|
|
524
|
+
if (this.max > this.absMax) {
|
|
525
|
+
this.max = this.absMax;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (this.min > this.max) {
|
|
529
|
+
if (this.movingType === 'min') this.min = this.max;
|
|
530
|
+
else this.max = this.min;
|
|
531
|
+
}
|
|
532
|
+
this.hasRendered = false;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @method _onMoveMiddle
|
|
537
|
+
* @description bound to mousemove event on this element. Update min/max
|
|
538
|
+
* values based closest min/max button (adjust selection that is closest to mouse position)
|
|
539
|
+
* @param {MouseEvent} e
|
|
540
|
+
*/
|
|
541
|
+
_onMoveMiddle(e) {
|
|
542
|
+
let fillLineWidth = e.currentTarget.offsetWidth;
|
|
543
|
+
let leftOffset = e.offsetX;
|
|
544
|
+
|
|
545
|
+
if( (fillLineWidth / 2) < leftOffset ) {
|
|
546
|
+
this.movingType = 'max';
|
|
547
|
+
} else {
|
|
548
|
+
this.movingType = 'min';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this._onMove(e);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @method _onMoveOutsideRange
|
|
556
|
+
* @description bound to mousemove event on this element. Update min/max
|
|
557
|
+
* values based on mouse position, but only if the mouse is outside the current
|
|
558
|
+
* min/max range
|
|
559
|
+
* @param {MouseEvent} e
|
|
560
|
+
*/
|
|
561
|
+
_onMoveOutsideRange(e) {
|
|
562
|
+
let fillLineWidth = e.currentTarget.offsetWidth;
|
|
563
|
+
let leftOffset = e.offsetX;
|
|
564
|
+
|
|
565
|
+
if( (fillLineWidth / 2) < leftOffset ) {
|
|
566
|
+
this.movingType = 'max';
|
|
567
|
+
} else {
|
|
568
|
+
this.movingType = 'min';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
this._onMove(e);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* @method _onMoveStop
|
|
576
|
+
* @description bound to mouseup/mouseout event on window. It's always best to bind
|
|
577
|
+
* this to the window as a catch all. Resets all moving flags
|
|
578
|
+
*/
|
|
579
|
+
_onMoveStop() {
|
|
580
|
+
if (!this.isMoving) return;
|
|
581
|
+
|
|
582
|
+
this.movingType = '';
|
|
583
|
+
this.movingMin = false;
|
|
584
|
+
this.movingMax = false;
|
|
585
|
+
this.isMoving = false;
|
|
586
|
+
|
|
587
|
+
this._onRangeSliderChange();
|
|
588
|
+
|
|
589
|
+
this.hasRendered = false;
|
|
590
|
+
this.requestUpdate();
|
|
591
|
+
this._notifySelected();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
customElements.define('ucdlib-range-slider', UcdlibRangeSlider);
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
|
|
3
|
+
export default function render() {
|
|
4
|
+
return html`
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
<style>
|
|
8
|
+
:host {
|
|
9
|
+
display: block;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[hidden] { display: none !important; }
|
|
13
|
+
|
|
14
|
+
.labels {
|
|
15
|
+
display: flex;
|
|
16
|
+
color: var(--gray-text, #666);
|
|
17
|
+
font-size: .92rem;
|
|
18
|
+
padding-top: 9px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.inputs {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: flex-start;
|
|
25
|
+
padding: 1rem 0 .5rem
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
input[type="number"] {
|
|
29
|
+
border: 0;
|
|
30
|
+
width: 6rem;
|
|
31
|
+
height: 61px;
|
|
32
|
+
padding: 0 1rem;
|
|
33
|
+
margin: 0 0.5rem;
|
|
34
|
+
font-size: var(--fs-sm);
|
|
35
|
+
background: var(--color-aggie-blue-30);
|
|
36
|
+
color: var(--color-aggie-blue-80, #13639E);
|
|
37
|
+
text-align: center;
|
|
38
|
+
|
|
39
|
+
/* remove default styles for chrome/safari/ff */
|
|
40
|
+
-webkit-appearance: none;
|
|
41
|
+
-moz-appearance: textfield;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* remove spinner button for chrome/safari */
|
|
45
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
46
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
47
|
+
-webkit-appearance: none;
|
|
48
|
+
margin: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.unknown {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
padding-top: .5rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
label {
|
|
58
|
+
padding-left: 5px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
range-slider {
|
|
62
|
+
--light-background-color: var(--medium-background-color);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#minInput {
|
|
66
|
+
margin-left: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
input[type="checkbox"] {
|
|
70
|
+
height: 1rem;
|
|
71
|
+
width: 1rem;
|
|
72
|
+
margin-left: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#histogram {
|
|
76
|
+
/* padding-top: 1.78rem; */
|
|
77
|
+
height: 100px;
|
|
78
|
+
width: 100%;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.bin {
|
|
82
|
+
fill: var(--color-aggie-blue-80);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.slider {
|
|
86
|
+
display: block;
|
|
87
|
+
position: relative;
|
|
88
|
+
height: 20px;
|
|
89
|
+
top: -22px;
|
|
90
|
+
/* margin: 0 13px; */
|
|
91
|
+
|
|
92
|
+
-webkit-touch-callout: none; /* iOS Safari */
|
|
93
|
+
-webkit-user-select: none; /* Safari */
|
|
94
|
+
-khtml-user-select: none; /* Konqueror HTML */
|
|
95
|
+
-moz-user-select: none; /* Firefox */
|
|
96
|
+
-ms-user-select: none; /* Internet Explorer/Edge */
|
|
97
|
+
user-select: none; /* Non-prefixed version, currently */
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#numberLine {
|
|
101
|
+
position: absolute;
|
|
102
|
+
left : 0;
|
|
103
|
+
right : 0;
|
|
104
|
+
height: 3px;
|
|
105
|
+
background-color: ${this.lightColor};
|
|
106
|
+
|
|
107
|
+
border-top: 5px solid white;
|
|
108
|
+
border-bottom: 5px solid white;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#fillLine {
|
|
112
|
+
position: absolute;
|
|
113
|
+
cursor: move;
|
|
114
|
+
background-color: ${this.darkColor};
|
|
115
|
+
height: 3px;
|
|
116
|
+
|
|
117
|
+
border-top: 5px solid white;
|
|
118
|
+
border-bottom: 5px solid white;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#numberLine {
|
|
122
|
+
cursor: move;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.btn {
|
|
126
|
+
position: absolute;
|
|
127
|
+
height: 25px;
|
|
128
|
+
width: 25px;
|
|
129
|
+
cursor: move;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.btn > div {
|
|
133
|
+
margin: 5px;
|
|
134
|
+
height: 15px;
|
|
135
|
+
width: 15px;
|
|
136
|
+
border-radius: 15px;
|
|
137
|
+
background-color: ${this.darkColor};
|
|
138
|
+
transition: all 150ms linear;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btn[moving] > div {
|
|
142
|
+
margin: 0px;
|
|
143
|
+
height: 25px;
|
|
144
|
+
width: 25px;
|
|
145
|
+
border-radius: 25px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.label {
|
|
149
|
+
width : 25px;
|
|
150
|
+
font-size: 12px;
|
|
151
|
+
position: absolute;
|
|
152
|
+
text-align: center;
|
|
153
|
+
transform: scale(0);
|
|
154
|
+
transition: transform 200ms linear;
|
|
155
|
+
color: var(--default-primary-color);
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
158
|
+
|
|
159
|
+
<svg ?hidden="${this.hideHistogram}" id="histogram"></svg>
|
|
160
|
+
|
|
161
|
+
<div class="slider">
|
|
162
|
+
<div id="numberLine"
|
|
163
|
+
prop="outside-range"
|
|
164
|
+
@mousedown="${this._onMoveStart}"
|
|
165
|
+
@touchstart="${this._onMoveStart}">
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div id="fillLine"
|
|
169
|
+
prop="range"
|
|
170
|
+
@mousedown="${this._onMoveStart}"
|
|
171
|
+
@touchstart="${this._onMoveStart}">
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div id="lowNumberLabel" class="label" ?moving="${this.isMoving}">${this.minLabel}</div>
|
|
175
|
+
<div id="highNumberLabel" class="label" ?moving="${this.isMoving}">${this.maxLabel}</div>
|
|
176
|
+
|
|
177
|
+
<div id="lowNumberBtn"
|
|
178
|
+
class="btn"
|
|
179
|
+
prop="min"
|
|
180
|
+
@mousedown="${this._onMoveStart}"
|
|
181
|
+
@touchstart="${this._onMoveStart}"
|
|
182
|
+
?moving="${this.movingMin}" >
|
|
183
|
+
<div></div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div id="highNumberBtn"
|
|
187
|
+
class="btn"
|
|
188
|
+
prop="max"
|
|
189
|
+
@mousedown="${this._onMoveStart}"
|
|
190
|
+
@touchstart="${this._onMoveStart}"
|
|
191
|
+
?moving="${this.movingMax}">
|
|
192
|
+
<div></div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div ?hidden="${this.hideSliderLabels}" class="labels">
|
|
197
|
+
<div style="flex:1">${this.absMin}</div>
|
|
198
|
+
<div>${this.absMax}</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div class="inputs">
|
|
202
|
+
<input id="minInput" .value="${this.min}" type="number" @change="${this._onInputChange}" min="${this.absMin}" max="${this.max}">
|
|
203
|
+
<span> - </span>
|
|
204
|
+
<input id="maxInput" .value="${this.max}" type="number" @change="${this._onInputChange}" min="${this.min}" max="${this.absMax}">
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="unknown" ?hidden="${this.showUnknown}">
|
|
208
|
+
<input type="checkbox" id="unknown" @click="${this._onRangeNullChange}" checked />
|
|
209
|
+
<label for="unknown">Include unknown/unspecified</label>
|
|
210
|
+
</div>
|
|
211
|
+
`;}
|