@internetarchive/histogram-date-range 1.2.2-alpha-webdev7377.8 → 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.
@@ -1,6 +1,7 @@
1
1
  import '@internetarchive/ia-activity-indicator';
2
2
  import dayjs from 'dayjs/esm';
3
3
  import customParseFormat from 'dayjs/esm/plugin/customParseFormat';
4
+ import fixFirstCenturyYears from './plugins/fix-first-century-years';
4
5
  import {
5
6
  css,
6
7
  html,
@@ -13,8 +14,10 @@ import {
13
14
  } from 'lit';
14
15
  import { customElement, property, state } from 'lit/decorators.js';
15
16
  import { live } from 'lit/directives/live.js';
17
+ import { classMap } from 'lit/directives/class-map.js';
16
18
 
17
19
  dayjs.extend(customParseFormat);
20
+ dayjs.extend(fixFirstCenturyYears);
18
21
 
19
22
  // these values can be overridden via the component's HTML (camelCased) attributes
20
23
  const WIDTH = 180;
@@ -82,7 +85,18 @@ export class HistogramDateRange extends LitElement {
82
85
  @property({ type: Array }) bins: number[] = [];
83
86
  /** If true, update events will not be canceled by the date inputs receiving focus */
84
87
  @property({ type: Boolean }) updateWhileFocused = false;
85
- /** What interval bins should be rounded to for display */
88
+
89
+ /**
90
+ * What interval bins should be snapped to for determining their time ranges.
91
+ * - `none` (default): Bins should each represent an identical duration of time,
92
+ * without regard for the actual dates represented.
93
+ * - `month`: Bins should each represent one or more full, non-overlapping months.
94
+ * The bin ranges will be "snapped" to the nearest month boundaries, which can
95
+ * result in bins that represent different amounts of time, particularly if the
96
+ * provided bins do not evenly divide the provided date range, or if the months
97
+ * represented are of different lengths.
98
+ * - `year`: Same as `month`, but snapping to year boundaries instead of months.
99
+ */
86
100
  @property({ type: String }) binSnapping: BinSnappingInterval = 'none';
87
101
 
88
102
  // internal reactive properties not exposed as attributes
@@ -141,8 +155,10 @@ export class HistogramDateRange extends LitElement {
141
155
  return;
142
156
  }
143
157
  this._histWidth = this.width - this.sliderWidth * 2;
158
+
144
159
  this._minDateMS = this.snapTimestamp(this.getMSFromString(this.minDate));
145
- // NB: The max date string, converted as-is to ms, represents the _start_ of the final date interval; we want the _end_.
160
+ // NB: The max date string, converted as-is to ms, represents the *start* of the
161
+ // final date interval; we want the *end*, so we add any snap interval/offset.
146
162
  this._maxDateMS =
147
163
  this.snapTimestamp(
148
164
  this.getMSFromString(this.maxDate) + this.snapInterval
@@ -172,12 +188,16 @@ export class HistogramDateRange extends LitElement {
172
188
  * while dates past the 15th are rounded up.
173
189
  */
174
190
  private snapToMonth(timestamp: number): number {
175
- const d = new Date(timestamp);
176
- const [year, month, day] = [d.getFullYear(), d.getMonth(), d.getDate()];
177
-
178
- return day < 16 // Obviously only an approximation, but good enough for snapping
179
- ? new Date(year, month, 1).getTime()
180
- : new Date(year, month + 1, 1).getTime();
191
+ const d = dayjs(timestamp);
192
+ const monthsToAdd = d.date() < 16 ? 0 : 1;
193
+ const snapped = d
194
+ .add(monthsToAdd, 'month')
195
+ .date(1)
196
+ .hour(0)
197
+ .minute(0)
198
+ .second(0)
199
+ .millisecond(0); // First millisecond of the month
200
+ return snapped.valueOf();
181
201
  }
182
202
 
183
203
  /**
@@ -186,12 +206,17 @@ export class HistogramDateRange extends LitElement {
186
206
  * July or later are rounded up.
187
207
  */
188
208
  private snapToYear(timestamp: number): number {
189
- const d = new Date(timestamp);
190
- const [year, month] = [d.getFullYear(), d.getMonth()];
191
-
192
- return month < 6 // NB: months are 0-indexed, so 6 = July
193
- ? new Date(year, 0, 1).getTime()
194
- : new Date(year + 1, 0, 1).getTime();
209
+ const d = dayjs(timestamp);
210
+ const yearsToAdd = d.month() < 6 ? 0 : 1;
211
+ const snapped = d
212
+ .add(yearsToAdd, 'year')
213
+ .month(0)
214
+ .date(1)
215
+ .hour(0)
216
+ .minute(0)
217
+ .second(0)
218
+ .millisecond(0); // First millisecond of the year
219
+ return snapped.valueOf();
195
220
  }
196
221
 
197
222
  /**
@@ -206,6 +231,7 @@ export class HistogramDateRange extends LitElement {
206
231
  return this.snapToMonth(timestamp);
207
232
  case 'none':
208
233
  default:
234
+ // We still align it to second boundaries to resolve minor discrepancies
209
235
  return this.snapToNextSecond(timestamp);
210
236
  }
211
237
  }
@@ -286,7 +312,8 @@ export class HistogramDateRange extends LitElement {
286
312
  }
287
313
 
288
314
  /**
289
- * Offset added to the end of each bin to ensure disjoin intervals, if applicable.
315
+ * Offset added to the end of each bin to ensure disjoint intervals,
316
+ * depending on whether snapping is enabled and there are multiple bins.
290
317
  */
291
318
  private get snapEndOffset(): number {
292
319
  return this.binSnapping !== 'none' && this._numBins > 1 ? -1 : 0;
@@ -297,7 +324,7 @@ export class HistogramDateRange extends LitElement {
297
324
  * Falls back to `dateFormat` if not provided.
298
325
  */
299
326
  @property({ type: String }) get tooltipDateFormat(): string {
300
- return this._tooltipDateFormat ?? this.dateFormat ?? DATE_FORMAT;
327
+ return this._tooltipDateFormat ?? this.dateFormat;
301
328
  }
302
329
 
303
330
  set tooltipDateFormat(value: string) {
@@ -708,13 +735,17 @@ export class HistogramDateRange extends LitElement {
708
735
  // minimum, or facing towards the right (-1), ie maximum
709
736
  const k = id === 'slider-min' ? 1 : -1;
710
737
 
738
+ const sliderClasses = classMap({
739
+ slider: true,
740
+ draggable: !this.disabled,
741
+ dragging: this._isDragging,
742
+ });
743
+
711
744
  return svg`
712
745
  <svg
713
- id="${id}"
714
- class="
715
- ${this.disabled ? '' : 'draggable'}
716
- ${this._isDragging ? 'dragging' : ''}"
717
- @pointerdown="${this.drag}"
746
+ id=${id}
747
+ class=${sliderClasses}
748
+ @pointerdown=${this.drag}
718
749
  >
719
750
  <path d="${sliderShape} z" fill="${sliderColor}" />
720
751
  <rect
@@ -812,9 +843,12 @@ export class HistogramDateRange extends LitElement {
812
843
  }
813
844
  const date = dayjs(dateMS);
814
845
  if (date.year() < 1000) {
815
- // years before 1000 don't play well with dayjs custom formatting, so fall
816
- // back to displaying only the year
817
- return String(date.year());
846
+ // years before 1000 don't play well with dayjs custom formatting, so work around dayjs
847
+ // by setting the year to a sentinel value and then replacing it instead.
848
+ // this is a bit hacky but it does the trick for essentially all reasonable cases
849
+ // until such time as we replace dayjs.
850
+ const tmpDate = date.year(199999);
851
+ return tmpDate.format(format).replace(/199999/g, date.year().toString());
818
852
  }
819
853
  return date.format(format);
820
854
  }
@@ -829,13 +863,13 @@ export class HistogramDateRange extends LitElement {
829
863
  return html`
830
864
  <input
831
865
  id="date-min"
832
- placeholder="${this.dateFormat}"
866
+ placeholder=${this.dateFormat}
833
867
  type="text"
834
- @focus="${this.handleInputFocus}"
835
- @blur="${this.handleMinDateInput}"
836
- @keyup="${this.handleKeyUp}"
837
- .value="${live(this.minSelectedDate)}"
838
- ?disabled="${this.disabled}"
868
+ @focus=${this.handleInputFocus}
869
+ @blur=${this.handleMinDateInput}
870
+ @keyup=${this.handleKeyUp}
871
+ .value=${live(this.minSelectedDate)}
872
+ ?disabled=${this.disabled}
839
873
  />
840
874
  `;
841
875
  }
@@ -844,13 +878,13 @@ export class HistogramDateRange extends LitElement {
844
878
  return html`
845
879
  <input
846
880
  id="date-max"
847
- placeholder="${this.dateFormat}"
881
+ placeholder=${this.dateFormat}
848
882
  type="text"
849
- @focus="${this.handleInputFocus}"
850
- @blur="${this.handleMaxDateInput}"
851
- @keyup="${this.handleKeyUp}"
852
- .value="${live(this.maxSelectedDate)}"
853
- ?disabled="${this.disabled}"
883
+ @focus=${this.handleInputFocus}
884
+ @blur=${this.handleMaxDateInput}
885
+ @keyup=${this.handleKeyUp}
886
+ .value=${live(this.maxSelectedDate)}
887
+ ?disabled=${this.disabled}
854
888
  />
855
889
  `;
856
890
  }
@@ -968,6 +1002,9 @@ export class HistogramDateRange extends LitElement {
968
1002
  transparent;
969
1003
  }
970
1004
  /****** slider ********/
1005
+ .slider {
1006
+ shape-rendering: crispEdges; /* So the slider doesn't get blurry if dragged between pixels */
1007
+ }
971
1008
  .draggable:hover {
972
1009
  cursor: grab;
973
1010
  }
@@ -0,0 +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
+ }
@@ -248,7 +248,7 @@ describe('HistogramDateRange', () => {
248
248
 
249
249
  // initial state
250
250
  expect(minSlider.getBoundingClientRect().x).to.eq(108);
251
- expect(Array.from(minSlider.classList).join(' ')).to.eq('draggable');
251
+ expect(minSlider.classList.contains('draggable')).to.be.true;
252
252
 
253
253
  // pointer down
254
254
  minSlider.dispatchEvent(new PointerEvent('pointerdown'));
@@ -308,6 +308,55 @@ describe('HistogramDateRange', () => {
308
308
  window.dispatchEvent(new PointerEvent('pointermove', { clientX: 50 }));
309
309
  await el.updateComplete;
310
310
  expect(Math.round(maxSlider.getBoundingClientRect().x)).to.eq(268); // max slider didn't move
311
+
312
+ // try to slide max slider off the right edge
313
+ maxSlider.dispatchEvent(new PointerEvent('pointerdown', { clientX: 120 }));
314
+ window.dispatchEvent(new PointerEvent('pointermove', { clientX: 300 }));
315
+ await el.updateComplete;
316
+ expect(maxSlider.getBoundingClientRect().x).to.eq(298); // back to its initial position
317
+ expect(el.maxSelectedDate).to.equal('12/4/2020');
318
+ });
319
+
320
+ it('does not permit sliders to select dates outside the allowed range', async () => {
321
+ const el = await createCustomElementInHTMLContainer();
322
+ el.binSnapping = 'month';
323
+ el.dateFormat = 'YYYY-MM';
324
+ el.minDate = '2020-01';
325
+ el.maxDate = '2021-12';
326
+ el.minSelectedDate = '2020-01';
327
+ el.maxSelectedDate = '2021-12';
328
+ el.bins = [10, 20, 30, 40, 50, 60, 70, 80];
329
+ await el.updateComplete;
330
+
331
+ const minSlider = el.shadowRoot?.querySelector('#slider-min') as SVGElement;
332
+ const maxSlider = el.shadowRoot?.querySelector('#slider-max') as SVGElement;
333
+
334
+ const minDateInput = el.shadowRoot?.querySelector(
335
+ '#date-min'
336
+ ) as HTMLInputElement;
337
+ const maxDateInput = el.shadowRoot?.querySelector(
338
+ '#date-max'
339
+ ) as HTMLInputElement;
340
+
341
+ // initial state
342
+ expect(minSlider.getBoundingClientRect().x).to.eq(108, 'initial');
343
+ expect(maxSlider.getBoundingClientRect().x).to.eq(298, 'initial');
344
+ expect(minDateInput.value).to.eq('2020-01', 'initial');
345
+ expect(maxDateInput.value).to.eq('2021-12', 'initial');
346
+
347
+ // try dragging the min slider too far to the left
348
+ minSlider.dispatchEvent(new PointerEvent('pointerdown', { clientX: 0 }));
349
+ window.dispatchEvent(new PointerEvent('pointermove', { clientX: -50 }));
350
+ await el.updateComplete;
351
+ expect(minSlider.getBoundingClientRect().x).to.eq(108); // slider didn't move
352
+ expect(minDateInput.value).to.eq('2020-01'); // value unchanged
353
+
354
+ // try dragging the max slider too far to the right
355
+ maxSlider.dispatchEvent(new PointerEvent('pointerdown', { clientX: 195 }));
356
+ window.dispatchEvent(new PointerEvent('pointermove', { clientX: 250 }));
357
+ await el.updateComplete;
358
+ expect(maxSlider.getBoundingClientRect().x).to.eq(298); // slider didn't move
359
+ expect(maxDateInput.value).to.eq('2021-12'); // value unchanged
311
360
  });
312
361
 
313
362
  it("emits a custom event when the element's date range changes", async () => {
@@ -531,7 +580,7 @@ describe('HistogramDateRange', () => {
531
580
  expect(maxDateInput.value).to.eq('5000');
532
581
  });
533
582
 
534
- it('handles year values less than 1000 by overriding date format to just display year', async () => {
583
+ it('handles year values less than 1000 correctly', async () => {
535
584
  const el = await fixture<HistogramDateRange>(
536
585
  html`
537
586
  <histogram-date-range
@@ -548,12 +597,12 @@ describe('HistogramDateRange', () => {
548
597
  const minDateInput = el.shadowRoot?.querySelector(
549
598
  '#date-min'
550
599
  ) as HTMLInputElement;
551
- expect(minDateInput.value).to.eq('-500');
600
+ expect(minDateInput.value).to.eq('1/1/-500');
552
601
 
553
602
  const maxDateInput = el.shadowRoot?.querySelector(
554
603
  '#date-max'
555
604
  ) as HTMLInputElement;
556
- expect(maxDateInput.value).to.eq('500');
605
+ expect(maxDateInput.value).to.eq('1/1/500');
557
606
  });
558
607
 
559
608
  it('handles missing data', async () => {
@@ -602,6 +651,207 @@ describe('HistogramDateRange', () => {
602
651
  expect(heights).to.eql([37, 40, 38, 38, 37, 36]);
603
652
  });
604
653
 
654
+ it('correctly aligns bins to exact month boundaries when binSnapping=month', async () => {
655
+ const el = await fixture<HistogramDateRange>(
656
+ html`
657
+ <histogram-date-range
658
+ binSnapping="month"
659
+ dateFormat="YYYY-MM"
660
+ tooltipDateFormat="MMM YYYY"
661
+ minDate="2020-01"
662
+ maxDate="2021-12"
663
+ bins="[10,20,30,40,50,60,70,80]"
664
+ ></histogram-date-range>
665
+ `
666
+ );
667
+ const bars = el.shadowRoot?.querySelectorAll(
668
+ '.bar'
669
+ ) as unknown as SVGRectElement[];
670
+ const tooltips = Array.from(bars).map(b => b.dataset.tooltip);
671
+ expect(tooltips).to.eql([
672
+ 'Jan 2020 - Mar 2020',
673
+ 'Apr 2020 - Jun 2020',
674
+ 'Jul 2020 - Sep 2020',
675
+ 'Oct 2020 - Dec 2020',
676
+ 'Jan 2021 - Mar 2021',
677
+ 'Apr 2021 - Jun 2021',
678
+ 'Jul 2021 - Sep 2021',
679
+ 'Oct 2021 - Dec 2021',
680
+ ]);
681
+ });
682
+
683
+ it('correctly handles month snapping for years 0-99', async () => {
684
+ const el = await fixture<HistogramDateRange>(
685
+ html`
686
+ <histogram-date-range
687
+ binSnapping="month"
688
+ dateFormat="YYYY-MM"
689
+ tooltipDateFormat="MMM YYYY"
690
+ minDate="0050-01"
691
+ maxDate="0065-12"
692
+ bins="[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]"
693
+ ></histogram-date-range>
694
+ `
695
+ );
696
+
697
+ const bars = el.shadowRoot?.querySelectorAll(
698
+ '.bar'
699
+ ) as unknown as SVGRectElement[];
700
+ const tooltips = Array.from(bars).map(b => b.dataset.tooltip);
701
+ expect(tooltips).to.eql([
702
+ 'Jan 50 - Jun 50',
703
+ 'Jul 50 - Dec 50',
704
+ 'Jan 51 - Jun 51',
705
+ 'Jul 51 - Dec 51',
706
+ 'Jan 52 - Jun 52',
707
+ 'Jul 52 - Dec 52',
708
+ 'Jan 53 - Jun 53',
709
+ 'Jul 53 - Dec 53',
710
+ 'Jan 54 - Jun 54',
711
+ 'Jul 54 - Dec 54',
712
+ 'Jan 55 - Jun 55',
713
+ 'Jul 55 - Dec 55',
714
+ 'Jan 56 - Jun 56',
715
+ 'Jul 56 - Dec 56',
716
+ 'Jan 57 - Jun 57',
717
+ 'Jul 57 - Dec 57',
718
+ 'Jan 58 - Jun 58',
719
+ 'Jul 58 - Dec 58',
720
+ 'Jan 59 - Jun 59',
721
+ 'Jul 59 - Dec 59',
722
+ 'Jan 60 - Jun 60',
723
+ 'Jul 60 - Dec 60',
724
+ 'Jan 61 - Jun 61',
725
+ 'Jul 61 - Dec 61',
726
+ 'Jan 62 - Jun 62',
727
+ 'Jul 62 - Dec 62',
728
+ 'Jan 63 - Jun 63',
729
+ 'Jul 63 - Dec 63',
730
+ 'Jan 64 - Jun 64',
731
+ 'Jul 64 - Dec 64',
732
+ 'Jan 65 - Jun 65',
733
+ 'Jul 65 - Dec 65',
734
+ ]);
735
+ });
736
+
737
+ it('correctly aligns bins to exact year boundaries when binSnapping=year', async () => {
738
+ const el = await fixture<HistogramDateRange>(
739
+ html`
740
+ <histogram-date-range
741
+ binSnapping="year"
742
+ minDate="2000"
743
+ maxDate="2019"
744
+ bins="[10,20,30,40,50,60,70,80,90,100]"
745
+ ></histogram-date-range>
746
+ `
747
+ );
748
+ const bars = el.shadowRoot?.querySelectorAll(
749
+ '.bar'
750
+ ) as unknown as SVGRectElement[];
751
+ const tooltips = Array.from(bars).map(b => b.dataset.tooltip);
752
+ expect(tooltips).to.eql([
753
+ '2000 - 2001',
754
+ '2002 - 2003',
755
+ '2004 - 2005',
756
+ '2006 - 2007',
757
+ '2008 - 2009',
758
+ '2010 - 2011',
759
+ '2012 - 2013',
760
+ '2014 - 2015',
761
+ '2016 - 2017',
762
+ '2018 - 2019',
763
+ ]);
764
+ });
765
+
766
+ it('correctly handles year snapping for years 0-99', async () => {
767
+ const el = await fixture<HistogramDateRange>(
768
+ html`
769
+ <histogram-date-range
770
+ binSnapping="year"
771
+ dateFormat="YYYY"
772
+ minDate="0020"
773
+ maxDate="0025"
774
+ bins="[1,2,3,4,5,6]"
775
+ ></histogram-date-range>
776
+ `
777
+ );
778
+
779
+ const bars = el.shadowRoot?.querySelectorAll(
780
+ '.bar'
781
+ ) as unknown as SVGRectElement[];
782
+ const tooltips = Array.from(bars).map(b => b.dataset.tooltip);
783
+ expect(tooltips).to.eql(['20', '21', '22', '23', '24', '25']);
784
+ });
785
+
786
+ it('does not duplicate start/end date in tooltips when representing a single year', async () => {
787
+ const el = await fixture<HistogramDateRange>(
788
+ html`
789
+ <histogram-date-range
790
+ binSnapping="year"
791
+ dateFormat="YYYY"
792
+ minDate="2001"
793
+ maxDate="2005"
794
+ bins="[10,20,30,40,50]"
795
+ ></histogram-date-range>
796
+ `
797
+ );
798
+ const bars = el.shadowRoot?.querySelectorAll(
799
+ '.bar'
800
+ ) as unknown as SVGRectElement[];
801
+ const tooltips = Array.from(bars).map(b => b.dataset.tooltip);
802
+ expect(tooltips).to.eql(['2001', '2002', '2003', '2004', '2005']);
803
+ });
804
+
805
+ it('falls back to default date format for tooltips if no tooltip date format provided', async () => {
806
+ const el = await fixture<HistogramDateRange>(
807
+ html`
808
+ <histogram-date-range
809
+ binSnapping="year"
810
+ minDate="2001"
811
+ maxDate="2005"
812
+ bins="[10,20,30,40,50]"
813
+ ></histogram-date-range>
814
+ `
815
+ );
816
+
817
+ const bars = el.shadowRoot?.querySelectorAll(
818
+ '.bar'
819
+ ) as unknown as SVGRectElement[];
820
+ let tooltips = Array.from(bars).map(b => b.dataset.tooltip);
821
+ expect(tooltips).to.eql(['2001', '2002', '2003', '2004', '2005']); // default YYYY date format
822
+
823
+ el.dateFormat = 'YYYY/MM';
824
+ el.minDate = '2001/01';
825
+ el.maxDate = '2005/01';
826
+ await el.updateComplete;
827
+
828
+ // Should use dateFormat fallback for tooltips
829
+ tooltips = Array.from(bars).map(b => b.dataset.tooltip);
830
+ expect(tooltips).to.eql([
831
+ '2001/01 - 2001/12',
832
+ '2002/01 - 2002/12',
833
+ '2003/01 - 2003/12',
834
+ '2004/01 - 2004/12',
835
+ '2005/01 - 2005/12',
836
+ ]);
837
+
838
+ el.dateFormat = 'YYYY';
839
+ el.tooltipDateFormat = 'MMMM YYYY';
840
+ el.minDate = '2001';
841
+ el.maxDate = '2005';
842
+ await el.updateComplete;
843
+
844
+ // Should use defined tooltipDateFormat for tooltips
845
+ tooltips = Array.from(bars).map(b => b.dataset.tooltip);
846
+ expect(tooltips).to.eql([
847
+ 'January 2001 - December 2001',
848
+ 'January 2002 - December 2002',
849
+ 'January 2003 - December 2003',
850
+ 'January 2004 - December 2004',
851
+ 'January 2005 - December 2005',
852
+ ]);
853
+ });
854
+
605
855
  it('has a disabled state', async () => {
606
856
  const el = await fixture<HistogramDateRange>(
607
857
  html`
@@ -629,7 +879,7 @@ describe('HistogramDateRange', () => {
629
879
  await el.updateComplete;
630
880
 
631
881
  // cursor is not draggable if disabled
632
- expect(Array.from(minSlider.classList).join(' ')).to.eq('');
882
+ expect(minSlider.classList.contains('draggable')).to.be.false;
633
883
 
634
884
  // attempt to slide to right
635
885
  window.dispatchEvent(new PointerEvent('pointermove', { clientX: 70 }));
@@ -0,0 +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
+ }