@internetarchive/histogram-date-range 1.3.0 → 1.3.1-alpha-webdev7745.0

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.
@@ -2,11 +2,13 @@ import { __decorate } from "tslib";
2
2
  import '@internetarchive/ia-activity-indicator';
3
3
  import dayjs from 'dayjs/esm';
4
4
  import customParseFormat from 'dayjs/esm/plugin/customParseFormat';
5
+ import fixFirstCenturyYears from './plugins/fix-first-century-years';
5
6
  import { css, html, LitElement, nothing, svg, } from 'lit';
6
7
  import { customElement, property, state } from 'lit/decorators.js';
7
8
  import { live } from 'lit/directives/live.js';
8
9
  import { classMap } from 'lit/directives/class-map.js';
9
10
  dayjs.extend(customParseFormat);
11
+ dayjs.extend(fixFirstCenturyYears);
10
12
  // these values can be overridden via the component's HTML (camelCased) attributes
11
13
  const WIDTH = 180;
12
14
  const HEIGHT = 40;
@@ -178,11 +180,16 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
178
180
  * while dates past the 15th are rounded up.
179
181
  */
180
182
  snapToMonth(timestamp) {
181
- const d = new Date(timestamp);
182
- const [year, month, day] = [d.getFullYear(), d.getMonth(), d.getDate()];
183
- return day < 16 // Obviously only an approximation, but good enough for snapping
184
- ? new Date(year, month, 1).getTime()
185
- : new Date(year, month + 1, 1).getTime();
183
+ const d = dayjs(timestamp);
184
+ const monthsToAdd = d.date() < 16 ? 0 : 1;
185
+ const snapped = d
186
+ .add(monthsToAdd, 'month')
187
+ .date(1)
188
+ .hour(0)
189
+ .minute(0)
190
+ .second(0)
191
+ .millisecond(0); // First millisecond of the month
192
+ return snapped.valueOf();
186
193
  }
187
194
  /**
188
195
  * Rounds the given timestamp to the (approximate) nearest start of a year,
@@ -190,11 +197,17 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
190
197
  * July or later are rounded up.
191
198
  */
192
199
  snapToYear(timestamp) {
193
- const d = new Date(timestamp);
194
- const [year, month] = [d.getFullYear(), d.getMonth()];
195
- return month < 6 // NB: months are 0-indexed, so 6 = July
196
- ? new Date(year, 0, 1).getTime()
197
- : new Date(year + 1, 0, 1).getTime();
200
+ const d = dayjs(timestamp);
201
+ const yearsToAdd = d.month() < 6 ? 0 : 1;
202
+ const snapped = d
203
+ .add(yearsToAdd, 'year')
204
+ .month(0)
205
+ .date(1)
206
+ .hour(0)
207
+ .minute(0)
208
+ .second(0)
209
+ .millisecond(0); // First millisecond of the year
210
+ return snapped.valueOf();
198
211
  }
199
212
  /**
200
213
  * Rounds the given timestamp according to the `binSnapping` property.
@@ -368,9 +381,9 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
368
381
  const formattedNumItems = Number(dataset.numItems).toLocaleString();
369
382
  this._tooltipOffset =
370
383
  x + (this._binWidth - this.sliderWidth - this.tooltipWidth) / 2;
371
- this._tooltipContent = html `
372
- ${formattedNumItems} ${itemsText}<br />
373
- ${dataset.tooltip}
384
+ this._tooltipContent = html `
385
+ ${formattedNumItems} ${itemsText}<br />
386
+ ${dataset.tooltip}
374
387
  `;
375
388
  this._tooltipVisible = true;
376
389
  }
@@ -566,25 +579,25 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
566
579
  // border-radius); used as part of a SVG quadratic curve. see
567
580
  // https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#curve_commands
568
581
  const cs = SLIDER_CORNER_SIZE;
569
- const sliderShape = `
570
- M${this.minSliderX},0
571
- h-${this.sliderWidth - cs}
572
- q-${cs},0 -${cs},${cs}
573
- v${this.height - cs * 2}
574
- q0,${cs} ${cs},${cs}
575
- h${this.sliderWidth - cs}
582
+ const sliderShape = `
583
+ M${this.minSliderX},0
584
+ h-${this.sliderWidth - cs}
585
+ q-${cs},0 -${cs},${cs}
586
+ v${this.height - cs * 2}
587
+ q0,${cs} ${cs},${cs}
588
+ h${this.sliderWidth - cs}
576
589
  `;
577
590
  return this.generateSliderSVG(this.minSliderX, 'slider-min', sliderShape);
578
591
  }
579
592
  get maxSliderTemplate() {
580
593
  const cs = SLIDER_CORNER_SIZE;
581
- const sliderShape = `
582
- M${this.maxSliderX},0
583
- h${this.sliderWidth - cs}
584
- q${cs},0 ${cs},${cs}
585
- v${this.height - cs * 2}
586
- q0,${cs} -${cs},${cs}
587
- h-${this.sliderWidth - cs}
594
+ const sliderShape = `
595
+ M${this.maxSliderX},0
596
+ h${this.sliderWidth - cs}
597
+ q${cs},0 ${cs},${cs}
598
+ v${this.height - cs * 2}
599
+ q0,${cs} -${cs},${cs}
600
+ h-${this.sliderWidth - cs}
588
601
  `;
589
602
  return this.generateSliderSVG(this.maxSliderX, 'slider-max', sliderShape);
590
603
  }
@@ -597,38 +610,38 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
597
610
  draggable: !this.disabled,
598
611
  dragging: this._isDragging,
599
612
  });
600
- return svg `
601
- <svg
602
- id=${id}
603
- class=${sliderClasses}
604
- @pointerdown=${this.drag}
605
- >
606
- <path d="${sliderShape} z" fill="${sliderColor}" />
607
- <rect
608
- x="${sliderPositionX - this.sliderWidth * k + this.sliderWidth * 0.4 * k}"
609
- y="${this.height / 3}"
610
- width="1"
611
- height="${this.height / 3}"
612
- fill="white"
613
- />
614
- <rect
615
- x="${sliderPositionX - this.sliderWidth * k + this.sliderWidth * 0.6 * k}"
616
- y="${this.height / 3}"
617
- width="1"
618
- height="${this.height / 3}"
619
- fill="white"
620
- />
621
- </svg>
613
+ return svg `
614
+ <svg
615
+ id=${id}
616
+ class=${sliderClasses}
617
+ @pointerdown=${this.drag}
618
+ >
619
+ <path d="${sliderShape} z" fill="${sliderColor}" />
620
+ <rect
621
+ x="${sliderPositionX - this.sliderWidth * k + this.sliderWidth * 0.4 * k}"
622
+ y="${this.height / 3}"
623
+ width="1"
624
+ height="${this.height / 3}"
625
+ fill="white"
626
+ />
627
+ <rect
628
+ x="${sliderPositionX - this.sliderWidth * k + this.sliderWidth * 0.6 * k}"
629
+ y="${this.height / 3}"
630
+ width="1"
631
+ height="${this.height / 3}"
632
+ fill="white"
633
+ />
634
+ </svg>
622
635
  `;
623
636
  }
624
637
  get selectedRangeTemplate() {
625
- return svg `
626
- <rect
627
- x="${this.minSliderX}"
628
- y="0"
629
- width="${this.maxSliderX - this.minSliderX}"
630
- height="${this.height}"
631
- fill="${selectedRangeColor}"
638
+ return svg `
639
+ <rect
640
+ x="${this.minSliderX}"
641
+ y="0"
642
+ width="${this.maxSliderX - this.minSliderX}"
643
+ height="${this.height}"
644
+ fill="${selectedRangeColor}"
632
645
  />`;
633
646
  }
634
647
  get histogramTemplate() {
@@ -646,22 +659,22 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
646
659
  // between adjacent bars (eg when viewing the tooltips or when trying to
647
660
  // extend the range by clicking on a bar)
648
661
  const barStyle = `stroke-dasharray: 0 ${barWidth} ${barHeight} ${barWidth} 0 ${barHeight}`;
649
- const bar = svg `
650
- <rect
651
- class="bar"
652
- style=${barStyle}
653
- x=${x}
654
- y=${this.height - barHeight}
655
- width=${barWidth}
656
- height=${barHeight}
657
- @pointerenter=${this.showTooltip}
658
- @pointerleave=${this.hideTooltip}
659
- @click=${this.handleBarClick}
660
- fill=${barFill}
661
- data-num-items=${data.value}
662
- data-bin-start=${data.binStart}
663
- data-bin-end=${data.binEnd}
664
- data-tooltip=${data.tooltip}
662
+ const bar = svg `
663
+ <rect
664
+ class="bar"
665
+ style=${barStyle}
666
+ x=${x}
667
+ y=${this.height - barHeight}
668
+ width=${barWidth}
669
+ height=${barHeight}
670
+ @pointerenter=${this.showTooltip}
671
+ @pointerleave=${this.hideTooltip}
672
+ @click=${this.handleBarClick}
673
+ fill=${barFill}
674
+ data-num-items=${data.value}
675
+ data-bin-start=${data.binStart}
676
+ data-bin-end=${data.binEnd}
677
+ data-tooltip=${data.tooltip}
665
678
  />`;
666
679
  x += xScale;
667
680
  return bar;
@@ -685,9 +698,12 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
685
698
  }
686
699
  const date = dayjs(dateMS);
687
700
  if (date.year() < 1000) {
688
- // years before 1000 don't play well with dayjs custom formatting, so fall
689
- // back to displaying only the year
690
- return String(date.year());
701
+ // years before 1000 don't play well with dayjs custom formatting, so work around dayjs
702
+ // by setting the year to a sentinel value and then replacing it instead.
703
+ // this is a bit hacky but it does the trick for essentially all reasonable cases
704
+ // until such time as we replace dayjs.
705
+ const tmpDate = date.year(199999);
706
+ return tmpDate.format(format).replace(/199999/g, date.year().toString());
691
707
  }
692
708
  return date.format(format);
693
709
  }
@@ -698,31 +714,31 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
698
714
  * https://lit.dev/docs/templates/directives/#live
699
715
  */
700
716
  get minInputTemplate() {
701
- return html `
702
- <input
703
- id="date-min"
704
- placeholder=${this.dateFormat}
705
- type="text"
706
- @focus=${this.handleInputFocus}
707
- @blur=${this.handleMinDateInput}
708
- @keyup=${this.handleKeyUp}
709
- .value=${live(this.minSelectedDate)}
710
- ?disabled=${this.disabled}
711
- />
717
+ return html `
718
+ <input
719
+ id="date-min"
720
+ placeholder=${this.dateFormat}
721
+ type="text"
722
+ @focus=${this.handleInputFocus}
723
+ @blur=${this.handleMinDateInput}
724
+ @keyup=${this.handleKeyUp}
725
+ .value=${live(this.minSelectedDate)}
726
+ ?disabled=${this.disabled}
727
+ />
712
728
  `;
713
729
  }
714
730
  get maxInputTemplate() {
715
- return html `
716
- <input
717
- id="date-max"
718
- placeholder=${this.dateFormat}
719
- type="text"
720
- @focus=${this.handleInputFocus}
721
- @blur=${this.handleMaxDateInput}
722
- @keyup=${this.handleKeyUp}
723
- .value=${live(this.maxSelectedDate)}
724
- ?disabled=${this.disabled}
725
- />
731
+ return html `
732
+ <input
733
+ id="date-max"
734
+ placeholder=${this.dateFormat}
735
+ type="text"
736
+ @focus=${this.handleInputFocus}
737
+ @blur=${this.handleMaxDateInput}
738
+ @keyup=${this.handleKeyUp}
739
+ .value=${live(this.maxSelectedDate)}
740
+ ?disabled=${this.disabled}
741
+ />
726
742
  `;
727
743
  }
728
744
  get minLabelTemplate() {
@@ -732,187 +748,187 @@ let HistogramDateRange = class HistogramDateRange extends LitElement {
732
748
  return html `<label for="date-max" class="sr-only">Maximum date:</label>`;
733
749
  }
734
750
  get tooltipTemplate() {
735
- return html `
736
- <style>
737
- #tooltip {
738
- width: ${this.tooltipWidth}px;
739
- height: ${this.tooltipHeight}px;
740
- top: ${-9 - this.tooltipHeight}px;
741
- left: ${this._tooltipOffset}px;
742
- display: ${this._tooltipVisible ? 'block' : 'none'};
743
- }
744
- #tooltip:after {
745
- left: ${this.tooltipWidth / 2}px;
746
- }
747
- </style>
748
- <div id="tooltip">${this._tooltipContent}</div>
751
+ return html `
752
+ <style>
753
+ #tooltip {
754
+ width: ${this.tooltipWidth}px;
755
+ height: ${this.tooltipHeight}px;
756
+ top: ${-9 - this.tooltipHeight}px;
757
+ left: ${this._tooltipOffset}px;
758
+ display: ${this._tooltipVisible ? 'block' : 'none'};
759
+ }
760
+ #tooltip:after {
761
+ left: ${this.tooltipWidth / 2}px;
762
+ }
763
+ </style>
764
+ <div id="tooltip">${this._tooltipContent}</div>
749
765
  `;
750
766
  }
751
767
  get noDataTemplate() {
752
- return html `
753
- <div class="missing-data-message">${this.missingDataMessage}</div>
768
+ return html `
769
+ <div class="missing-data-message">${this.missingDataMessage}</div>
754
770
  `;
755
771
  }
756
772
  get activityIndicatorTemplate() {
757
773
  if (!this.loading) {
758
774
  return nothing;
759
775
  }
760
- return html `
761
- <ia-activity-indicator mode="processing"> </ia-activity-indicator>
776
+ return html `
777
+ <ia-activity-indicator mode="processing"> </ia-activity-indicator>
762
778
  `;
763
779
  }
764
780
  render() {
765
781
  if (!this.hasBinData) {
766
782
  return this.noDataTemplate;
767
783
  }
768
- return html `
769
- <div
770
- id="container"
771
- class="
772
- noselect
773
- ${this._isDragging ? 'dragging' : ''}
774
- "
775
- style="width: ${this.width}px"
776
- >
777
- ${this.activityIndicatorTemplate} ${this.tooltipTemplate}
778
- <div
779
- class="inner-container
780
- ${this.disabled ? 'disabled' : ''}"
781
- >
782
- <svg
783
- width="${this.width}"
784
- height="${this.height}"
785
- @pointerleave="${this.drop}"
786
- >
787
- ${this.selectedRangeTemplate}
788
- <svg id="histogram">${this.histogramTemplate}</svg>
789
- ${this.minSliderTemplate} ${this.maxSliderTemplate}
790
- </svg>
791
- <div id="inputs">
792
- ${this.minLabelTemplate} ${this.minInputTemplate}
793
- <div class="dash">-</div>
794
- ${this.maxLabelTemplate} ${this.maxInputTemplate}
795
- <slot name="inputs-right-side"></slot>
796
- </div>
797
- </div>
798
- </div>
784
+ return html `
785
+ <div
786
+ id="container"
787
+ class="
788
+ noselect
789
+ ${this._isDragging ? 'dragging' : ''}
790
+ "
791
+ style="width: ${this.width}px"
792
+ >
793
+ ${this.activityIndicatorTemplate} ${this.tooltipTemplate}
794
+ <div
795
+ class="inner-container
796
+ ${this.disabled ? 'disabled' : ''}"
797
+ >
798
+ <svg
799
+ width="${this.width}"
800
+ height="${this.height}"
801
+ @pointerleave="${this.drop}"
802
+ >
803
+ ${this.selectedRangeTemplate}
804
+ <svg id="histogram">${this.histogramTemplate}</svg>
805
+ ${this.minSliderTemplate} ${this.maxSliderTemplate}
806
+ </svg>
807
+ <div id="inputs">
808
+ ${this.minLabelTemplate} ${this.minInputTemplate}
809
+ <div class="dash">-</div>
810
+ ${this.maxLabelTemplate} ${this.maxInputTemplate}
811
+ <slot name="inputs-right-side"></slot>
812
+ </div>
813
+ </div>
814
+ </div>
799
815
  `;
800
816
  }
801
817
  };
802
- HistogramDateRange.styles = css `
803
- .missing-data-message {
804
- text-align: center;
805
- }
806
- #container {
807
- margin: 0;
808
- touch-action: none;
809
- position: relative;
810
- }
811
- .disabled {
812
- opacity: 0.3;
813
- }
814
- ia-activity-indicator {
815
- position: absolute;
816
- left: calc(50% - 10px);
817
- top: 10px;
818
- width: 20px;
819
- height: 20px;
820
- --activityIndicatorLoadingDotColor: rgba(0, 0, 0, 0);
821
- --activityIndicatorLoadingRingColor: ${activityIndicatorColor};
822
- }
823
-
824
- /* prevent selection from interfering with tooltip, especially on mobile */
825
- /* https://stackoverflow.com/a/4407335/1163042 */
826
- .noselect {
827
- -webkit-touch-callout: none; /* iOS Safari */
828
- -webkit-user-select: none; /* Safari */
829
- -moz-user-select: none; /* Old versions of Firefox */
830
- -ms-user-select: none; /* Internet Explorer/Edge */
831
- user-select: none; /* current Chrome, Edge, Opera and Firefox */
832
- }
833
- .bar {
834
- /* create a transparent border around the hist bars to prevent "gaps" and
835
- flickering when moving around between bars. this also helps with handling
836
- clicks on the bars, preventing users from being able to click in between
837
- bars */
838
- stroke: rgba(0, 0, 0, 0);
839
- /* ensure transparent stroke wide enough to cover gap between bars */
840
- stroke-width: 2px;
841
- }
842
- .bar:hover {
843
- /* highlight currently hovered bar */
844
- fill-opacity: 0.7;
845
- }
846
- .disabled .bar:hover {
847
- /* ensure no visual hover interaction when disabled */
848
- fill-opacity: 1;
849
- }
850
- /****** histogram ********/
851
- #tooltip {
852
- position: absolute;
853
- background: ${tooltipBackgroundColor};
854
- color: ${tooltipTextColor};
855
- text-align: center;
856
- border-radius: 3px;
857
- padding: 2px;
858
- font-size: ${tooltipFontSize};
859
- font-family: ${tooltipFontFamily};
860
- touch-action: none;
861
- pointer-events: none;
862
- }
863
- #tooltip:after {
864
- content: '';
865
- position: absolute;
866
- margin-left: -5px;
867
- top: 100%;
868
- /* arrow */
869
- border: 5px solid ${tooltipTextColor};
870
- border-color: ${tooltipBackgroundColor} transparent transparent
871
- transparent;
872
- }
873
- /****** slider ********/
874
- .slider {
875
- shape-rendering: crispEdges; /* So the slider doesn't get blurry if dragged between pixels */
876
- }
877
- .draggable:hover {
878
- cursor: grab;
879
- }
880
- .dragging {
881
- cursor: grabbing !important;
882
- }
883
- /****** inputs ********/
884
- #inputs {
885
- display: flex;
886
- justify-content: center;
887
- margin: ${inputRowMargin};
888
- }
889
- #inputs .dash {
890
- position: relative;
891
- bottom: -1px;
892
- align-self: center; /* Otherwise the dash sticks to the top while the inputs grow */
893
- }
894
- input {
895
- width: ${inputWidth};
896
- margin: 0 3px;
897
- border: ${inputBorder};
898
- border-radius: 2px !important;
899
- text-align: center;
900
- font-size: ${inputFontSize};
901
- font-family: ${inputFontFamily};
902
- }
903
- .sr-only {
904
- position: absolute !important;
905
- width: 1px !important;
906
- height: 1px !important;
907
- margin: 0 !important;
908
- padding: 0 !important;
909
- border: 0 !important;
910
- overflow: hidden !important;
911
- white-space: nowrap !important;
912
- clip: rect(1px, 1px, 1px, 1px) !important;
913
- -webkit-clip-path: inset(50%) !important;
914
- clip-path: inset(50%) !important;
915
- }
818
+ HistogramDateRange.styles = css `
819
+ .missing-data-message {
820
+ text-align: center;
821
+ }
822
+ #container {
823
+ margin: 0;
824
+ touch-action: none;
825
+ position: relative;
826
+ }
827
+ .disabled {
828
+ opacity: 0.3;
829
+ }
830
+ ia-activity-indicator {
831
+ position: absolute;
832
+ left: calc(50% - 10px);
833
+ top: 10px;
834
+ width: 20px;
835
+ height: 20px;
836
+ --activityIndicatorLoadingDotColor: rgba(0, 0, 0, 0);
837
+ --activityIndicatorLoadingRingColor: ${activityIndicatorColor};
838
+ }
839
+
840
+ /* prevent selection from interfering with tooltip, especially on mobile */
841
+ /* https://stackoverflow.com/a/4407335/1163042 */
842
+ .noselect {
843
+ -webkit-touch-callout: none; /* iOS Safari */
844
+ -webkit-user-select: none; /* Safari */
845
+ -moz-user-select: none; /* Old versions of Firefox */
846
+ -ms-user-select: none; /* Internet Explorer/Edge */
847
+ user-select: none; /* current Chrome, Edge, Opera and Firefox */
848
+ }
849
+ .bar {
850
+ /* create a transparent border around the hist bars to prevent "gaps" and
851
+ flickering when moving around between bars. this also helps with handling
852
+ clicks on the bars, preventing users from being able to click in between
853
+ bars */
854
+ stroke: rgba(0, 0, 0, 0);
855
+ /* ensure transparent stroke wide enough to cover gap between bars */
856
+ stroke-width: 2px;
857
+ }
858
+ .bar:hover {
859
+ /* highlight currently hovered bar */
860
+ fill-opacity: 0.7;
861
+ }
862
+ .disabled .bar:hover {
863
+ /* ensure no visual hover interaction when disabled */
864
+ fill-opacity: 1;
865
+ }
866
+ /****** histogram ********/
867
+ #tooltip {
868
+ position: absolute;
869
+ background: ${tooltipBackgroundColor};
870
+ color: ${tooltipTextColor};
871
+ text-align: center;
872
+ border-radius: 3px;
873
+ padding: 2px;
874
+ font-size: ${tooltipFontSize};
875
+ font-family: ${tooltipFontFamily};
876
+ touch-action: none;
877
+ pointer-events: none;
878
+ }
879
+ #tooltip:after {
880
+ content: '';
881
+ position: absolute;
882
+ margin-left: -5px;
883
+ top: 100%;
884
+ /* arrow */
885
+ border: 5px solid ${tooltipTextColor};
886
+ border-color: ${tooltipBackgroundColor} transparent transparent
887
+ transparent;
888
+ }
889
+ /****** slider ********/
890
+ .slider {
891
+ shape-rendering: crispEdges; /* So the slider doesn't get blurry if dragged between pixels */
892
+ }
893
+ .draggable:hover {
894
+ cursor: grab;
895
+ }
896
+ .dragging {
897
+ cursor: grabbing !important;
898
+ }
899
+ /****** inputs ********/
900
+ #inputs {
901
+ display: flex;
902
+ justify-content: center;
903
+ margin: ${inputRowMargin};
904
+ }
905
+ #inputs .dash {
906
+ position: relative;
907
+ bottom: -1px;
908
+ align-self: center; /* Otherwise the dash sticks to the top while the inputs grow */
909
+ }
910
+ input {
911
+ width: ${inputWidth};
912
+ margin: 0 3px;
913
+ border: ${inputBorder};
914
+ border-radius: 2px !important;
915
+ text-align: center;
916
+ font-size: ${inputFontSize};
917
+ font-family: ${inputFontFamily};
918
+ }
919
+ .sr-only {
920
+ position: absolute !important;
921
+ width: 1px !important;
922
+ height: 1px !important;
923
+ margin: 0 !important;
924
+ padding: 0 !important;
925
+ border: 0 !important;
926
+ overflow: hidden !important;
927
+ white-space: nowrap !important;
928
+ clip: rect(1px, 1px, 1px, 1px) !important;
929
+ -webkit-clip-path: inset(50%) !important;
930
+ clip-path: inset(50%) !important;
931
+ }
916
932
  `;
917
933
  __decorate([
918
934
  property({ type: Number })