@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ucd-lib/theme-elements",
3
- "version": "3.0.2",
3
+ "version": "3.0.5",
4
4
  "description": "Custom elements for the UCD brand theme",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
+ `;}