@internetarchive/histogram-date-range 1.3.1 → 1.3.2

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.
@@ -12,9 +12,10 @@ import {
12
12
  SVGTemplateResult,
13
13
  TemplateResult,
14
14
  } from 'lit';
15
- import { customElement, property, state } from 'lit/decorators.js';
15
+ import { customElement, property, state, query } from 'lit/decorators.js';
16
16
  import { live } from 'lit/directives/live.js';
17
17
  import { classMap } from 'lit/directives/class-map.js';
18
+ import { styleMap } from 'lit/directives/style-map.js';
18
19
 
19
20
  dayjs.extend(customParseFormat);
20
21
  dayjs.extend(fixFirstCenturyYears);
@@ -100,13 +101,15 @@ export class HistogramDateRange extends LitElement {
100
101
  @property({ type: String }) binSnapping: BinSnappingInterval = 'none';
101
102
 
102
103
  // internal reactive properties not exposed as attributes
103
- @state() private _tooltipOffset = 0;
104
+ @state() private _tooltipOffsetX = 0;
105
+ @state() private _tooltipOffsetY = 0;
104
106
  @state() private _tooltipContent?: TemplateResult;
105
- @state() private _tooltipVisible = false;
106
107
  @state() private _tooltipDateFormat?: string;
107
108
  @state() private _isDragging = false;
108
109
  @state() private _isLoading = false;
109
110
 
111
+ @query('#tooltip') private _tooltip!: HTMLDivElement;
112
+
110
113
  // non-reactive properties (changes don't auto-trigger re-rendering)
111
114
  private _minSelectedDate = '';
112
115
  private _maxSelectedDate = '';
@@ -412,25 +415,39 @@ export class HistogramDateRange extends LitElement {
412
415
  if (this._isDragging || this.disabled) {
413
416
  return;
414
417
  }
418
+
415
419
  const target = e.currentTarget as SVGRectElement;
416
420
  const x = target.x.baseVal.value + this.sliderWidth / 2;
417
421
  const dataset = target.dataset as BarDataset;
418
422
  const itemsText = `item${dataset.numItems !== '1' ? 's' : ''}`;
419
423
  const formattedNumItems = Number(dataset.numItems).toLocaleString();
420
424
 
421
- this._tooltipOffset =
422
- x + (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2;
425
+ const tooltipPadding = 2;
426
+ const bufferHeight = 9;
427
+ const heightAboveHistogram = bufferHeight + this.tooltipHeight;
428
+ const histogramBounds = this.getBoundingClientRect();
429
+ const barX = histogramBounds.x + x;
430
+ const histogramY = histogramBounds.y;
431
+
432
+ // Center the tooltip horizontally along the bar
433
+ this._tooltipOffsetX =
434
+ barX -
435
+ tooltipPadding +
436
+ (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2 +
437
+ window.scrollX;
438
+ // Place the tooltip (with arrow) just above the top of the histogram bars
439
+ this._tooltipOffsetY = histogramY - heightAboveHistogram + window.scrollY;
423
440
 
424
441
  this._tooltipContent = html`
425
442
  ${formattedNumItems} ${itemsText}<br />
426
443
  ${dataset.tooltip}
427
444
  `;
428
- this._tooltipVisible = true;
445
+ this._tooltip.showPopover?.();
429
446
  }
430
447
 
431
448
  private hideTooltip(): void {
432
449
  this._tooltipContent = undefined;
433
- this._tooltipVisible = false;
450
+ this._tooltip.hidePopover?.();
434
451
  }
435
452
 
436
453
  // use arrow functions (rather than standard JS class instance methods) so
@@ -898,20 +915,15 @@ export class HistogramDateRange extends LitElement {
898
915
  }
899
916
 
900
917
  get tooltipTemplate(): TemplateResult {
918
+ const styles = styleMap({
919
+ width: `${this.tooltipWidth}px`,
920
+ height: `${this.tooltipHeight}px`,
921
+ top: `${this._tooltipOffsetY}px`,
922
+ left: `${this._tooltipOffsetX}px`,
923
+ });
924
+
901
925
  return html`
902
- <style>
903
- #tooltip {
904
- width: ${this.tooltipWidth}px;
905
- height: ${this.tooltipHeight}px;
906
- top: ${-9 - this.tooltipHeight}px;
907
- left: ${this._tooltipOffset}px;
908
- display: ${this._tooltipVisible ? 'block' : 'none'};
909
- }
910
- #tooltip:after {
911
- left: ${this.tooltipWidth / 2}px;
912
- }
913
- </style>
914
- <div id="tooltip">${this._tooltipContent}</div>
926
+ <div id="tooltip" style=${styles} popover>${this._tooltipContent}</div>
915
927
  `;
916
928
  }
917
929
 
@@ -982,6 +994,8 @@ export class HistogramDateRange extends LitElement {
982
994
  #tooltip {
983
995
  position: absolute;
984
996
  background: ${tooltipBackgroundColor};
997
+ margin: 0;
998
+ border: 0;
985
999
  color: ${tooltipTextColor};
986
1000
  text-align: center;
987
1001
  border-radius: 3px;
@@ -990,12 +1004,14 @@ export class HistogramDateRange extends LitElement {
990
1004
  font-family: ${tooltipFontFamily};
991
1005
  touch-action: none;
992
1006
  pointer-events: none;
1007
+ overflow: visible;
993
1008
  }
994
1009
  #tooltip:after {
995
1010
  content: '';
996
1011
  position: absolute;
997
1012
  margin-left: -5px;
998
1013
  top: 100%;
1014
+ left: 50%;
999
1015
  /* arrow */
1000
1016
  border: 5px solid ${tooltipTextColor};
1001
1017
  border-color: ${tooltipBackgroundColor} transparent transparent
@@ -1,52 +1,52 @@
1
- import type dayjs from 'dayjs/esm';
2
-
3
- /**
4
- * As with the Date(y, m, ...) constructor, dayjs interprets years 0-99 as offsets
5
- * from the year 1900 instead of the actual first-century years.
6
- * We don't want that weird legacy behavior; we want years parsed literally.
7
- *
8
- * The maintainer of dayjs apparently refuses to address this:
9
- * - https://github.com/iamkun/dayjs/pull/548#issuecomment-477660947
10
- * - https://github.com/iamkun/dayjs/issues/1237
11
- *
12
- * So this plugin tries to detect the anomalous cases where the date format
13
- * contains a YYYY block and the parsed date has a year in the 1900-1999 range,
14
- * by checking whether the parsed year actually occurred in the original string.
15
- * If not, then we assume it was parsed incorrectly as an offset, and adjust.
16
- *
17
- * In practice this assumption could fail if the input date is invalid in some
18
- * way (e.g. having overflow, like a YYYY-MM-DD of "1950-22-33", which might be
19
- * converted to 1951-11-02 and produce a false positive). Essentially, we trade away
20
- * leniency for overflow dates to ensure that we handle all valid ones correctly.
21
- * This seems a reasonable tradeoff for our present use cases. But realistically we
22
- * should probably explore moving to a date lib that handles these cases properly.
23
- */
24
- export default function fixFirstCenturyYears(
25
- _: unknown,
26
- dayjsClass: typeof dayjs.Dayjs
27
- ) {
28
- const proto = dayjsClass.prototype;
29
- const oldParse = proto.parse;
30
- proto.parse = function (cfg) {
31
- const inputDate = cfg.date;
32
- const format = cfg.args[1];
33
- oldParse.call(this, cfg);
34
-
35
- const year = this.year();
36
- const isProblemDateRange = year >= 1900 && year < 2000;
37
- const isProblemStringFormat =
38
- typeof format === 'string' && format.includes('YYYY');
39
- const isProblemArrayFormat =
40
- Array.isArray(format) &&
41
- typeof format[0] === 'string' &&
42
- format[0].includes('YYYY');
43
- const isProblemFormat = isProblemStringFormat || isProblemArrayFormat;
44
- const missingParsedYear =
45
- typeof inputDate === 'string' && !inputDate.includes(`${year}`);
46
-
47
- if (isProblemDateRange && isProblemFormat && missingParsedYear) {
48
- this.$d.setFullYear(year - 1900);
49
- this.init(); // Re-initialize with the new date
50
- }
51
- };
52
- }
1
+ import type dayjs from 'dayjs/esm';
2
+
3
+ /**
4
+ * As with the Date(y, m, ...) constructor, dayjs interprets years 0-99 as offsets
5
+ * from the year 1900 instead of the actual first-century years.
6
+ * We don't want that weird legacy behavior; we want years parsed literally.
7
+ *
8
+ * The maintainer of dayjs apparently refuses to address this:
9
+ * - https://github.com/iamkun/dayjs/pull/548#issuecomment-477660947
10
+ * - https://github.com/iamkun/dayjs/issues/1237
11
+ *
12
+ * So this plugin tries to detect the anomalous cases where the date format
13
+ * contains a YYYY block and the parsed date has a year in the 1900-1999 range,
14
+ * by checking whether the parsed year actually occurred in the original string.
15
+ * If not, then we assume it was parsed incorrectly as an offset, and adjust.
16
+ *
17
+ * In practice this assumption could fail if the input date is invalid in some
18
+ * way (e.g. having overflow, like a YYYY-MM-DD of "1950-22-33", which might be
19
+ * converted to 1951-11-02 and produce a false positive). Essentially, we trade away
20
+ * leniency for overflow dates to ensure that we handle all valid ones correctly.
21
+ * This seems a reasonable tradeoff for our present use cases. But realistically we
22
+ * should probably explore moving to a date lib that handles these cases properly.
23
+ */
24
+ export default function fixFirstCenturyYears(
25
+ _: unknown,
26
+ dayjsClass: typeof dayjs.Dayjs
27
+ ) {
28
+ const proto = dayjsClass.prototype;
29
+ const oldParse = proto.parse;
30
+ proto.parse = function (cfg) {
31
+ const inputDate = cfg.date;
32
+ const format = cfg.args[1];
33
+ oldParse.call(this, cfg);
34
+
35
+ const year = this.year();
36
+ const isProblemDateRange = year >= 1900 && year < 2000;
37
+ const isProblemStringFormat =
38
+ typeof format === 'string' && format.includes('YYYY');
39
+ const isProblemArrayFormat =
40
+ Array.isArray(format) &&
41
+ typeof format[0] === 'string' &&
42
+ format[0].includes('YYYY');
43
+ const isProblemFormat = isProblemStringFormat || isProblemArrayFormat;
44
+ const missingParsedYear =
45
+ typeof inputDate === 'string' && !inputDate.includes(`${year}`);
46
+
47
+ if (isProblemDateRange && isProblemFormat && missingParsedYear) {
48
+ this.$d.setFullYear(year - 1900);
49
+ this.init(); // Re-initialize with the new date
50
+ }
51
+ };
52
+ }
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
1
2
  import { html, fixture, expect, oneEvent, aTimeout } from '@open-wc/testing';
2
3
 
3
4
  import { HistogramDateRange } from '../src/histogram-date-range';
package/types/dayjs.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- export { default } from 'dayjs/esm';
2
-
3
- declare module 'dayjs/esm' {
4
- // Widening the Dayjs interface so that we can properly extend it via plugin
5
- interface Dayjs {
6
- $d: Date;
7
- parse(cfg: { date: unknown; args: unknown[] }): void;
8
- init(): void;
9
- }
10
- }
1
+ export { default } from 'dayjs/esm';
2
+
3
+ declare module 'dayjs/esm' {
4
+ // Widening the Dayjs interface so that we can properly extend it via plugin
5
+ interface Dayjs {
6
+ $d: Date;
7
+ parse(cfg: { date: unknown; args: unknown[] }): void;
8
+ init(): void;
9
+ }
10
+ }