@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.
- package/.github/workflows/ci.yml +2 -2
- package/demo/index.html +4 -4
- package/dist/src/dayjs/fix-first-century-years.d.ts +2 -0
- package/dist/src/dayjs/fix-first-century-years.js +25 -0
- package/dist/src/dayjs/fix-first-century-years.js.map +1 -0
- package/dist/src/dayjs/fix-two-digit-dates.d.ts +2 -0
- package/dist/src/dayjs/fix-two-digit-dates.js +25 -0
- package/dist/src/dayjs/fix-two-digit-dates.js.map +1 -0
- package/dist/src/histogram-date-range.d.ts +13 -2
- package/dist/src/histogram-date-range.js +71 -35
- package/dist/src/histogram-date-range.js.map +1 -1
- package/dist/src/plugins/fix-first-century-years.d.ts +23 -0
- package/dist/src/plugins/fix-first-century-years.js +43 -0
- package/dist/src/plugins/fix-first-century-years.js.map +1 -0
- package/dist/test/histogram-date-range.test.js +214 -5
- package/dist/test/histogram-date-range.test.js.map +1 -1
- package/docs/_snowpack/pkg/common/directive-d639fc45.js +8 -0
- package/docs/_snowpack/pkg/import-map.json +1 -0
- package/docs/_snowpack/pkg/lit/directives/class-map.js +10 -0
- package/docs/_snowpack/pkg/lit/directives/live.js +3 -9
- package/docs/demo/index.html +4 -4
- package/docs/dist/src/histogram-date-range.js +37 -25
- package/docs/dist/src/plugins/fix-first-century-years.js +19 -0
- package/package.json +1 -1
- package/src/histogram-date-range.ts +73 -36
- package/src/plugins/fix-first-century-years.ts +52 -0
- package/test/histogram-date-range.test.ts +255 -5
- package/types/dayjs.d.ts +10 -0
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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 =
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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
|
|
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
|
|
714
|
-
class
|
|
715
|
-
|
|
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
|
|
816
|
-
//
|
|
817
|
-
|
|
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
|
|
866
|
+
placeholder=${this.dateFormat}
|
|
833
867
|
type="text"
|
|
834
|
-
@focus
|
|
835
|
-
@blur
|
|
836
|
-
@keyup
|
|
837
|
-
.value
|
|
838
|
-
?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
|
|
881
|
+
placeholder=${this.dateFormat}
|
|
848
882
|
type="text"
|
|
849
|
-
@focus
|
|
850
|
-
@blur
|
|
851
|
-
@keyup
|
|
852
|
-
.value
|
|
853
|
-
?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(
|
|
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
|
|
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('
|
|
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(
|
|
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 }));
|
package/types/dayjs.d.ts
ADDED
|
@@ -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
|
+
}
|