@internetarchive/histogram-date-range 0.1.0 → 0.1.1-alpha

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.
@@ -19,6 +19,8 @@ import {
19
19
  import {property, state, customElement} from "../../_snowpack/pkg/lit/decorators.js";
20
20
  import {live} from "../../_snowpack/pkg/lit/directives/live.js";
21
21
  import dayjs from "../../_snowpack/pkg/dayjs/esm/index.js";
22
+ import customParseFormat from "../../_snowpack/pkg/dayjs/esm/plugin/customParseFormat.js";
23
+ dayjs.extend(customParseFormat);
22
24
  import "../../_snowpack/pkg/@internetarchive/ia-activity-indicator/ia-activity-indicator.js";
23
25
  const WIDTH = 180;
24
26
  const HEIGHT = 40;
@@ -90,11 +92,10 @@ export let HistogramDateRange = class extends LitElement {
90
92
  this.move = (e) => {
91
93
  const newX = e.offsetX - this._dragOffset;
92
94
  const slider = this._currentSlider;
93
- const date = this.translatePositionToDate(newX);
94
95
  if (slider.id === "slider-min") {
95
- this.minSelectedDate = date;
96
+ this.minSelectedDate = this.translatePositionToDate(this.validMinSliderX(newX));
96
97
  } else {
97
- this.maxSelectedDate = date;
98
+ this.maxSelectedDate = this.translatePositionToDate(this.validMaxSliderX(newX));
98
99
  }
99
100
  };
100
101
  }
@@ -112,8 +113,8 @@ export let HistogramDateRange = class extends LitElement {
112
113
  return;
113
114
  }
114
115
  this._histWidth = this.width - this.sliderWidth * 2;
115
- this._minDateMS = dayjs(this.minDate).valueOf();
116
- this._maxDateMS = dayjs(this.maxDate).valueOf();
116
+ this._minDateMS = this.getMSFromString(this.minDate);
117
+ this._maxDateMS = this.getMSFromString(this.maxDate);
117
118
  this._binWidth = this._histWidth / this._numBins;
118
119
  this._previousDateRange = this.currentDateRangeString;
119
120
  this._histData = this.calculateHistData();
@@ -124,7 +125,8 @@ export let HistogramDateRange = class extends LitElement {
124
125
  calculateHistData() {
125
126
  const minValue = Math.min(...this.bins);
126
127
  const maxValue = Math.max(...this.bins);
127
- const valueScale = this.height / Math.log1p(maxValue - minValue);
128
+ const valueRange = minValue === maxValue ? 1 : Math.log1p(maxValue - minValue);
129
+ const valueScale = this.height / valueRange;
128
130
  const dateScale = this.dateRangeMS / this._numBins;
129
131
  return this.bins.map((v, i) => {
130
132
  return {
@@ -152,38 +154,42 @@ export let HistogramDateRange = class extends LitElement {
152
154
  this._isLoading = value;
153
155
  }
154
156
  get minSelectedDate() {
155
- return this.formatDate(this._minSelectedDate);
157
+ return this.formatDate(this.getMSFromString(this._minSelectedDate));
156
158
  }
157
159
  set minSelectedDate(rawDate) {
158
160
  if (!this._minSelectedDate) {
159
161
  this._minSelectedDate = rawDate;
162
+ return;
160
163
  }
161
- const x = this.translateDateToPosition(rawDate);
162
- if (x) {
163
- const validX = this.validMinSliderX(x);
164
- this._minSelectedDate = this.translatePositionToDate(validX);
164
+ let ms = this.getMSFromString(rawDate);
165
+ if (!Number.isNaN(ms)) {
166
+ ms = Math.min(ms, this.getMSFromString(this._maxSelectedDate));
167
+ this._minSelectedDate = this.formatDate(ms);
165
168
  }
166
169
  this.requestUpdate();
167
170
  }
168
171
  get maxSelectedDate() {
169
- return this.formatDate(this._maxSelectedDate);
172
+ return this.formatDate(this.getMSFromString(this._maxSelectedDate));
170
173
  }
171
174
  set maxSelectedDate(rawDate) {
172
175
  if (!this._maxSelectedDate) {
173
176
  this._maxSelectedDate = rawDate;
177
+ return;
174
178
  }
175
- const x = this.translateDateToPosition(rawDate);
176
- if (x) {
177
- const validX = this.validMaxSliderX(x);
178
- this._maxSelectedDate = this.translatePositionToDate(validX);
179
+ let ms = this.getMSFromString(rawDate);
180
+ if (!Number.isNaN(ms)) {
181
+ ms = Math.max(this.getMSFromString(this._minSelectedDate), ms);
182
+ this._maxSelectedDate = this.formatDate(ms);
179
183
  }
180
184
  this.requestUpdate();
181
185
  }
182
186
  get minSliderX() {
183
- return this.translateDateToPosition(this.minSelectedDate) ?? this.sliderWidth;
187
+ const x = this.translateDateToPosition(this.minSelectedDate);
188
+ return this.validMinSliderX(x);
184
189
  }
185
190
  get maxSliderX() {
186
- return this.translateDateToPosition(this.maxSelectedDate) ?? this.width - this.sliderWidth;
191
+ const x = this.translateDateToPosition(this.maxSelectedDate);
192
+ return this.validMaxSliderX(x);
187
193
  }
188
194
  get dateRangeMS() {
189
195
  return this._maxDateMS - this._minDateMS;
@@ -196,9 +202,10 @@ export let HistogramDateRange = class extends LitElement {
196
202
  const x = target.x.baseVal.value + this.sliderWidth / 2;
197
203
  const dataset = target.dataset;
198
204
  const itemsText = `item${dataset.numItems !== "1" ? "s" : ""}`;
205
+ const formattedNumItems = Number(dataset.numItems).toLocaleString();
199
206
  this._tooltipOffset = x + (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2;
200
207
  this._tooltipContent = html`
201
- ${dataset.numItems} ${itemsText}<br />
208
+ ${formattedNumItems} ${itemsText}<br />
202
209
  ${dataset.binStart} - ${dataset.binEnd}
203
210
  `;
204
211
  this._tooltipVisible = true;
@@ -208,12 +215,16 @@ export let HistogramDateRange = class extends LitElement {
208
215
  this._tooltipVisible = false;
209
216
  }
210
217
  validMinSliderX(newX) {
211
- const validX = Math.max(newX, this.sliderWidth);
212
- return Math.min(validX, this.maxSliderX);
218
+ if (Number.isNaN(newX)) {
219
+ return this.sliderWidth;
220
+ }
221
+ return this.clamp(newX, this.sliderWidth, this.translateDateToPosition(this.maxSelectedDate));
213
222
  }
214
223
  validMaxSliderX(newX) {
215
- const validX = Math.max(newX, this.minSliderX);
216
- return Math.min(validX, this.width - this.sliderWidth);
224
+ if (Number.isNaN(newX)) {
225
+ return this.width - this.sliderWidth;
226
+ }
227
+ return this.clamp(newX, this.translateDateToPosition(this.minSelectedDate), this.width - this.sliderWidth);
217
228
  }
218
229
  addListeners() {
219
230
  window.addEventListener("pointermove", this.move);
@@ -263,9 +274,11 @@ export let HistogramDateRange = class extends LitElement {
263
274
  return this.formatDate(this._minDateMS + milliseconds);
264
275
  }
265
276
  translateDateToPosition(date) {
266
- const milliseconds = dayjs(date).valueOf();
267
- const xPosition = this.sliderWidth + (milliseconds - this._minDateMS) * this._histWidth / this.dateRangeMS;
268
- return isNaN(milliseconds) || isNaN(xPosition) ? null : xPosition;
277
+ const milliseconds = this.getMSFromString(date);
278
+ return this.sliderWidth + (milliseconds - this._minDateMS) * this._histWidth / this.dateRangeMS;
279
+ }
280
+ clamp(x, minValue, maxValue) {
281
+ return Math.min(Math.max(x, minValue), maxValue);
269
282
  }
270
283
  handleMinDateInput(e) {
271
284
  const target = e.currentTarget;
@@ -277,23 +290,31 @@ export let HistogramDateRange = class extends LitElement {
277
290
  this.maxSelectedDate = target.value;
278
291
  this.beginEmitUpdateProcess();
279
292
  }
293
+ handleKeyUp(e) {
294
+ if (e.key === "Enter") {
295
+ const target = e.currentTarget;
296
+ target.blur();
297
+ }
298
+ }
280
299
  get currentDateRangeString() {
281
300
  return `${this.minSelectedDate}:${this.maxSelectedDate}`;
282
301
  }
283
- get minSelectedDateMS() {
284
- return dayjs(this.minSelectedDate).valueOf();
285
- }
286
- get maxSelectedDateMS() {
287
- return dayjs(this.maxSelectedDate).valueOf();
302
+ getMSFromString(date) {
303
+ const digitGroupCount = (date.split(/(\d+)/).length - 1) / 2;
304
+ if (digitGroupCount === 1) {
305
+ const dateObj = new Date(0, 0);
306
+ dateObj.setFullYear(Number(date));
307
+ return dateObj.getTime();
308
+ }
309
+ return dayjs(date, [this.dateFormat, DATE_FORMAT]).valueOf();
288
310
  }
289
311
  handleBarClick(e) {
290
312
  const dataset = e.currentTarget.dataset;
291
- const binStartDateMS = dayjs(dataset.binStart).valueOf();
292
- if (binStartDateMS < this.minSelectedDateMS) {
313
+ const distanceFromMinSlider = this.getMSFromString(dataset.binStart) - this.getMSFromString(this.minSelectedDate);
314
+ const distanceFromMaxSlider = this.getMSFromString(this.maxSelectedDate) - this.getMSFromString(dataset.binEnd);
315
+ if (distanceFromMinSlider < distanceFromMaxSlider) {
293
316
  this.minSelectedDate = dataset.binStart;
294
- }
295
- const binEndDateMS = dayjs(dataset.binEnd).valueOf();
296
- if (binEndDateMS > this.maxSelectedDateMS) {
317
+ } else {
297
318
  this.maxSelectedDate = dataset.binEnd;
298
319
  }
299
320
  this.beginEmitUpdateProcess();
@@ -376,7 +397,7 @@ export let HistogramDateRange = class extends LitElement {
376
397
  @pointerenter="${this.showTooltip}"
377
398
  @pointerleave="${this.hideTooltip}"
378
399
  @click="${this.handleBarClick}"
379
- fill="${x >= this.minSliderX && x <= this.maxSliderX ? barIncludedFill : barExcludedFill}"
400
+ fill="${x + barWidth >= this.minSliderX && x <= this.maxSliderX ? barIncludedFill : barExcludedFill}"
380
401
  data-num-items="${data.value}"
381
402
  data-bin-start="${data.binStart}"
382
403
  data-bin-end="${data.binEnd}"
@@ -385,9 +406,15 @@ export let HistogramDateRange = class extends LitElement {
385
406
  return bar;
386
407
  });
387
408
  }
388
- formatDate(rawDate) {
389
- const date = dayjs(rawDate);
390
- return date.isValid() ? date.format(this.dateFormat) : "";
409
+ formatDate(dateMS) {
410
+ if (Number.isNaN(dateMS)) {
411
+ return "";
412
+ }
413
+ const date = dayjs(dateMS);
414
+ if (date.year() < 1e3) {
415
+ return String(date.year());
416
+ }
417
+ return date.format(this.dateFormat);
391
418
  }
392
419
  get minInputTemplate() {
393
420
  return html`
@@ -397,6 +424,7 @@ export let HistogramDateRange = class extends LitElement {
397
424
  type="text"
398
425
  @focus="${this.cancelPendingUpdateEvent}"
399
426
  @blur="${this.handleMinDateInput}"
427
+ @keyup="${this.handleKeyUp}"
400
428
  .value="${live(this.minSelectedDate)}"
401
429
  ?disabled="${this.disabled}"
402
430
  />
@@ -410,6 +438,7 @@ export let HistogramDateRange = class extends LitElement {
410
438
  type="text"
411
439
  @focus="${this.cancelPendingUpdateEvent}"
412
440
  @blur="${this.handleMaxDateInput}"
441
+ @keyup="${this.handleKeyUp}"
413
442
  .value="${live(this.maxSelectedDate)}"
414
443
  ?disabled="${this.disabled}"
415
444
  />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/histogram-date-range",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-alpha",
4
4
  "description": "Internet Archive histogram date range picker",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "dist/index.js",
@@ -16,12 +16,12 @@
16
16
  "prepare": "npm run build",
17
17
  "lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
18
18
  "format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
19
- "test": "tsc && wtr --coverage",
19
+ "test": "tsc && wtr",
20
20
  "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\""
21
21
  },
22
22
  "dependencies": {
23
23
  "dayjs": "^1.9.7",
24
- "lit": "^2.0.0-rc.2"
24
+ "lit": "^2.1.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@internetarchive/ia-activity-indicator": "^0.0.1",
@@ -11,6 +11,8 @@ import {
11
11
  import { property, state, customElement } from 'lit/decorators.js';
12
12
  import { live } from 'lit/directives/live.js';
13
13
  import dayjs from 'dayjs/esm/index.js';
14
+ import customParseFormat from 'dayjs/esm/plugin/customParseFormat';
15
+ dayjs.extend(customParseFormat);
14
16
  import '@internetarchive/ia-activity-indicator/ia-activity-indicator';
15
17
 
16
18
  // these values can be overridden via the component's HTML (camelCased) attributes
@@ -126,8 +128,8 @@ export class HistogramDateRange extends LitElement {
126
128
  return;
127
129
  }
128
130
  this._histWidth = this.width - this.sliderWidth * 2;
129
- this._minDateMS = dayjs(this.minDate).valueOf();
130
- this._maxDateMS = dayjs(this.maxDate).valueOf();
131
+ this._minDateMS = this.getMSFromString(this.minDate);
132
+ this._maxDateMS = this.getMSFromString(this.maxDate);
131
133
  this._binWidth = this._histWidth / this._numBins;
132
134
  this._previousDateRange = this.currentDateRangeString;
133
135
  this._histData = this.calculateHistData();
@@ -143,11 +145,17 @@ export class HistogramDateRange extends LitElement {
143
145
  private calculateHistData(): HistogramItem[] {
144
146
  const minValue = Math.min(...this.bins);
145
147
  const maxValue = Math.max(...this.bins);
146
- const valueScale = this.height / Math.log1p(maxValue - minValue);
148
+ // if there is no difference between the min and max values, use a range of
149
+ // 1 because log scaling will fail if the range is 0
150
+ const valueRange =
151
+ minValue === maxValue ? 1 : Math.log1p(maxValue - minValue);
152
+ const valueScale = this.height / valueRange;
147
153
  const dateScale = this.dateRangeMS / this._numBins;
148
154
  return this.bins.map((v: number, i: number) => {
149
155
  return {
150
156
  value: v,
157
+ // use log scaling for the height of the bar to prevent tall bars from
158
+ // making the smaller ones too small to see
151
159
  height: Math.floor(Math.log1p(v) * valueScale),
152
160
  binStart: `${this.formatDate(i * dateScale + this._minDateMS)}`,
153
161
  binEnd: `${this.formatDate((i + 1) * dateScale + this._minDateMS)}`,
@@ -178,56 +186,60 @@ export class HistogramDateRange extends LitElement {
178
186
 
179
187
  /** formatted minimum date of selected date range */
180
188
  @property() get minSelectedDate(): string {
181
- return this.formatDate(this._minSelectedDate);
189
+ return this.formatDate(this.getMSFromString(this._minSelectedDate));
182
190
  }
183
191
 
192
+ /** updates minSelectedDate if new date is valid */
184
193
  set minSelectedDate(rawDate: string) {
185
194
  if (!this._minSelectedDate) {
186
195
  // because the values needed to calculate valid max/min values are not
187
196
  // available during the lit init when it's populating properties from
188
197
  // attributes, fall back to just the raw date if nothing is already set
189
198
  this._minSelectedDate = rawDate;
199
+ return;
190
200
  }
191
- const x = this.translateDateToPosition(rawDate);
192
- if (x) {
193
- const validX = this.validMinSliderX(x);
194
- this._minSelectedDate = this.translatePositionToDate(validX);
201
+ let ms = this.getMSFromString(rawDate);
202
+ if (!Number.isNaN(ms)) {
203
+ ms = Math.min(ms, this.getMSFromString(this._maxSelectedDate));
204
+ this._minSelectedDate = this.formatDate(ms);
195
205
  }
196
206
  this.requestUpdate();
197
207
  }
198
208
 
199
209
  /** formatted maximum date of selected date range */
200
210
  @property() get maxSelectedDate(): string {
201
- return this.formatDate(this._maxSelectedDate);
211
+ return this.formatDate(this.getMSFromString(this._maxSelectedDate));
202
212
  }
203
213
 
214
+ /** updates maxSelectedDate if new date is valid */
204
215
  set maxSelectedDate(rawDate: string) {
205
216
  if (!this._maxSelectedDate) {
206
- // see comment above in the minSelectedDate setter
217
+ // because the values needed to calculate valid max/min values are not
218
+ // available during the lit init when it's populating properties from
219
+ // attributes, fall back to just the raw date if nothing is already set
207
220
  this._maxSelectedDate = rawDate;
221
+ return;
208
222
  }
209
- const x = this.translateDateToPosition(rawDate);
210
- if (x) {
211
- const validX = this.validMaxSliderX(x);
212
- this._maxSelectedDate = this.translatePositionToDate(validX);
223
+ let ms = this.getMSFromString(rawDate);
224
+ if (!Number.isNaN(ms)) {
225
+ ms = Math.max(this.getMSFromString(this._minSelectedDate), ms);
226
+ this._maxSelectedDate = this.formatDate(ms);
213
227
  }
214
228
  this.requestUpdate();
215
229
  }
230
+
216
231
  /** horizontal position of min date slider */
217
232
  get minSliderX(): number {
218
- return (
219
- // default to leftmost position if missing or invalid min position
220
- this.translateDateToPosition(this.minSelectedDate) ?? this.sliderWidth
221
- );
233
+ // default to leftmost position if missing or invalid min position
234
+ const x = this.translateDateToPosition(this.minSelectedDate);
235
+ return this.validMinSliderX(x);
222
236
  }
223
237
 
224
238
  /** horizontal position of max date slider */
225
239
  get maxSliderX(): number {
226
- return (
227
- // default to rightmost position if missing or invalid max position
228
- this.translateDateToPosition(this.maxSelectedDate) ??
229
- this.width - this.sliderWidth
230
- );
240
+ // default to rightmost position if missing or invalid max position
241
+ const x = this.translateDateToPosition(this.maxSelectedDate);
242
+ return this.validMaxSliderX(x);
231
243
  }
232
244
 
233
245
  private get dateRangeMS(): number {
@@ -242,12 +254,13 @@ export class HistogramDateRange extends LitElement {
242
254
  const x = target.x.baseVal.value + this.sliderWidth / 2;
243
255
  const dataset = target.dataset as BarDataset;
244
256
  const itemsText = `item${dataset.numItems !== '1' ? 's' : ''}`;
257
+ const formattedNumItems = Number(dataset.numItems).toLocaleString();
245
258
 
246
259
  this._tooltipOffset =
247
260
  x + (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2;
248
261
 
249
262
  this._tooltipContent = html`
250
- ${dataset.numItems} ${itemsText}<br />
263
+ ${formattedNumItems} ${itemsText}<br />
251
264
  ${dataset.binStart} - ${dataset.binEnd}
252
265
  `;
253
266
  this._tooltipVisible = true;
@@ -290,11 +303,14 @@ export class HistogramDateRange extends LitElement {
290
303
  private move = (e: PointerEvent): void => {
291
304
  const newX = e.offsetX - this._dragOffset;
292
305
  const slider = this._currentSlider as SVGRectElement;
293
- const date = this.translatePositionToDate(newX);
294
306
  if ((slider.id as SliderId) === 'slider-min') {
295
- this.minSelectedDate = date;
307
+ this.minSelectedDate = this.translatePositionToDate(
308
+ this.validMinSliderX(newX)
309
+ );
296
310
  } else {
297
- this.maxSelectedDate = date;
311
+ this.maxSelectedDate = this.translatePositionToDate(
312
+ this.validMaxSliderX(newX)
313
+ );
298
314
  }
299
315
  };
300
316
 
@@ -307,8 +323,14 @@ export class HistogramDateRange extends LitElement {
307
323
  * to the position of the max slider
308
324
  */
309
325
  private validMinSliderX(newX: number): number {
310
- const validX = Math.max(newX, this.sliderWidth);
311
- return Math.min(validX, this.maxSliderX);
326
+ if (Number.isNaN(newX)) {
327
+ return this.sliderWidth;
328
+ }
329
+ return this.clamp(
330
+ newX,
331
+ this.sliderWidth,
332
+ this.translateDateToPosition(this.maxSelectedDate)
333
+ );
312
334
  }
313
335
 
314
336
  /**
@@ -320,8 +342,14 @@ export class HistogramDateRange extends LitElement {
320
342
  * then set it to the position of the min slider
321
343
  */
322
344
  private validMaxSliderX(newX: number): number {
323
- const validX = Math.max(newX, this.minSliderX);
324
- return Math.min(validX, this.width - this.sliderWidth);
345
+ if (Number.isNaN(newX)) {
346
+ return this.width - this.sliderWidth;
347
+ }
348
+ return this.clamp(
349
+ newX,
350
+ this.translateDateToPosition(this.minSelectedDate),
351
+ this.width - this.sliderWidth
352
+ );
325
353
  }
326
354
 
327
355
  private addListeners(): void {
@@ -409,12 +437,17 @@ export class HistogramDateRange extends LitElement {
409
437
  * @param date
410
438
  * @returns x-position of slider
411
439
  */
412
- private translateDateToPosition(date: string): number | null {
413
- const milliseconds = dayjs(date).valueOf();
414
- const xPosition =
440
+ private translateDateToPosition(date: string): number {
441
+ const milliseconds = this.getMSFromString(date);
442
+ return (
415
443
  this.sliderWidth +
416
- ((milliseconds - this._minDateMS) * this._histWidth) / this.dateRangeMS;
417
- return isNaN(milliseconds) || isNaN(xPosition) ? null : xPosition;
444
+ ((milliseconds - this._minDateMS) * this._histWidth) / this.dateRangeMS
445
+ );
446
+ }
447
+
448
+ /** ensure that the returned value is between minValue and maxValue */
449
+ private clamp(x: number, minValue: number, maxValue: number): number {
450
+ return Math.min(Math.max(x, minValue), maxValue);
418
451
  }
419
452
 
420
453
  private handleMinDateInput(e: InputEvent): void {
@@ -429,28 +462,40 @@ export class HistogramDateRange extends LitElement {
429
462
  this.beginEmitUpdateProcess();
430
463
  }
431
464
 
432
- private get currentDateRangeString(): string {
433
- return `${this.minSelectedDate}:${this.maxSelectedDate}`;
465
+ private handleKeyUp(e: KeyboardEvent): void {
466
+ if (e.key === 'Enter') {
467
+ const target = e.currentTarget as HTMLInputElement;
468
+ target.blur();
469
+ }
434
470
  }
435
471
 
436
- /** minimum selected date in milliseconds */
437
- private get minSelectedDateMS(): number {
438
- return dayjs(this.minSelectedDate).valueOf();
472
+ private get currentDateRangeString(): string {
473
+ return `${this.minSelectedDate}:${this.maxSelectedDate}`;
439
474
  }
440
475
 
441
- /** maximum selected date in milliseconds */
442
- private get maxSelectedDateMS(): number {
443
- return dayjs(this.maxSelectedDate).valueOf();
476
+ private getMSFromString(date: string): number {
477
+ const digitGroupCount = (date.split(/(\d+)/).length - 1) / 2;
478
+ if (digitGroupCount === 1) {
479
+ // if there's just a single set of digits, assume it's a year
480
+ const dateObj = new Date(0, 0); // start at January 1, 1900
481
+ dateObj.setFullYear(Number(date)); // override year (=> 0099-01-01) = 99 CE
482
+ return dateObj.getTime(); // get time in milliseconds
483
+ }
484
+ return dayjs(date, [this.dateFormat, DATE_FORMAT]).valueOf();
444
485
  }
445
486
 
446
487
  private handleBarClick(e: InputEvent): void {
447
488
  const dataset = (e.currentTarget as SVGRectElement).dataset as BarDataset;
448
- const binStartDateMS = dayjs(dataset.binStart).valueOf();
449
- if (binStartDateMS < this.minSelectedDateMS) {
489
+ const distanceFromMinSlider =
490
+ this.getMSFromString(dataset.binStart) -
491
+ this.getMSFromString(this.minSelectedDate);
492
+ const distanceFromMaxSlider =
493
+ this.getMSFromString(this.maxSelectedDate) -
494
+ this.getMSFromString(dataset.binEnd);
495
+ // update the selection by moving the nearer slider
496
+ if (distanceFromMinSlider < distanceFromMaxSlider) {
450
497
  this.minSelectedDate = dataset.binStart;
451
- }
452
- const binEndDateMS = dayjs(dataset.binEnd).valueOf();
453
- if (binEndDateMS > this.maxSelectedDateMS) {
498
+ } else {
454
499
  this.maxSelectedDate = dataset.binEnd;
455
500
  }
456
501
  this.beginEmitUpdateProcess();
@@ -561,7 +606,7 @@ export class HistogramDateRange extends LitElement {
561
606
  @pointerleave="${this.hideTooltip}"
562
607
  @click="${this.handleBarClick}"
563
608
  fill="${
564
- x >= this.minSliderX && x <= this.maxSliderX
609
+ x + barWidth >= this.minSliderX && x <= this.maxSliderX
565
610
  ? barIncludedFill
566
611
  : barExcludedFill
567
612
  }"
@@ -574,9 +619,17 @@ export class HistogramDateRange extends LitElement {
574
619
  });
575
620
  }
576
621
 
577
- private formatDate(rawDate: string | number): string {
578
- const date = dayjs(rawDate);
579
- return date.isValid() ? date.format(this.dateFormat) : '';
622
+ private formatDate(dateMS: number): string {
623
+ if (Number.isNaN(dateMS)) {
624
+ return '';
625
+ }
626
+ const date = dayjs(dateMS);
627
+ if (date.year() < 1000) {
628
+ // years before 1000 don't play well with dayjs custom formatting, so fall
629
+ // back to displaying only the year
630
+ return String(date.year());
631
+ }
632
+ return date.format(this.dateFormat);
580
633
  }
581
634
 
582
635
  /**
@@ -593,6 +646,7 @@ export class HistogramDateRange extends LitElement {
593
646
  type="text"
594
647
  @focus="${this.cancelPendingUpdateEvent}"
595
648
  @blur="${this.handleMinDateInput}"
649
+ @keyup="${this.handleKeyUp}"
596
650
  .value="${live(this.minSelectedDate)}"
597
651
  ?disabled="${this.disabled}"
598
652
  />
@@ -607,6 +661,7 @@ export class HistogramDateRange extends LitElement {
607
661
  type="text"
608
662
  @focus="${this.cancelPendingUpdateEvent}"
609
663
  @blur="${this.handleMaxDateInput}"
664
+ @keyup="${this.handleKeyUp}"
610
665
  .value="${live(this.maxSelectedDate)}"
611
666
  ?disabled="${this.disabled}"
612
667
  />