@nyaruka/temba-components 0.128.0 → 0.129.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/demo/chart/horizontal-demo.html +81 -0
  3. package/demo/components/datepicker/example.html +63 -0
  4. package/demo/components/datepicker/range-picker-demo.html +161 -0
  5. package/demo/index.html +8 -0
  6. package/demo/static/css/prism.css +2 -0
  7. package/demo/static/js/prism-loader.js +12 -0
  8. package/demo/styles.css +71 -1
  9. package/dist/temba-components.js +172 -10
  10. package/dist/temba-components.js.map +1 -1
  11. package/out-tsc/src/chart/TembaChart.js +116 -59
  12. package/out-tsc/src/chart/TembaChart.js.map +1 -1
  13. package/out-tsc/src/datepicker/DatePicker.js +11 -1
  14. package/out-tsc/src/datepicker/DatePicker.js.map +1 -1
  15. package/out-tsc/src/datepicker/RangePicker.js +595 -0
  16. package/out-tsc/src/datepicker/RangePicker.js.map +1 -0
  17. package/out-tsc/src/interfaces.js +1 -0
  18. package/out-tsc/src/interfaces.js.map +1 -1
  19. package/out-tsc/temba-modules.js +3 -1
  20. package/out-tsc/temba-modules.js.map +1 -1
  21. package/out-tsc/test/temba-chart.test.js +36 -0
  22. package/out-tsc/test/temba-chart.test.js.map +1 -1
  23. package/out-tsc/test/temba-datepicker.test.js +1 -1
  24. package/out-tsc/test/temba-datepicker.test.js.map +1 -1
  25. package/out-tsc/test/temba-range-picker.test.js +123 -0
  26. package/out-tsc/test/temba-range-picker.test.js.map +1 -0
  27. package/out-tsc/test/temba-select.test.js +1 -1
  28. package/out-tsc/test/temba-select.test.js.map +1 -1
  29. package/out-tsc/test/temba-webchat.test.js +4 -0
  30. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  31. package/package.json +1 -1
  32. package/screenshots/truth/datepicker/range-picker-all.png +0 -0
  33. package/screenshots/truth/datepicker/range-picker-button-states.png +0 -0
  34. package/screenshots/truth/datepicker/range-picker-default.png +0 -0
  35. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  36. package/screenshots/truth/datepicker/range-picker-initial-values.png +0 -0
  37. package/screenshots/truth/datepicker/range-picker-min-max.png +0 -0
  38. package/screenshots/truth/datepicker/range-picker-week.png +0 -0
  39. package/screenshots/truth/datepicker/range-picker-year.png +0 -0
  40. package/screenshots/truth/webchat/connected-state.png +0 -0
  41. package/src/chart/TembaChart.ts +124 -63
  42. package/src/datepicker/DatePicker.ts +9 -1
  43. package/src/datepicker/RangePicker.ts +602 -0
  44. package/src/interfaces.ts +2 -1
  45. package/temba-modules.ts +3 -1
  46. package/test/temba-chart.test.ts +47 -0
  47. package/test/temba-datepicker.test.ts +1 -1
  48. package/test/temba-range-picker.test.ts +193 -0
  49. package/test/temba-select.test.ts +1 -1
  50. package/test/temba-webchat.test.ts +7 -0
  51. package/web-test-runner.config.mjs +2 -0
  52. package/demo/datepicker/example.html +0 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyaruka/temba-components",
3
- "version": "0.128.0",
3
+ "version": "0.129.0",
4
4
  "description": "Web components to support rapidpro and related projects",
5
5
  "author": "Nyaruka <code@nyaruka.coim>",
6
6
  "main": "dist/index.js",
@@ -268,9 +268,18 @@ export class TembaChart extends RapidElement {
268
268
  @property({ type: String })
269
269
  chartType: ChartType = 'bar';
270
270
 
271
+ @property({ type: Boolean })
272
+ horizontal: boolean = false;
273
+
271
274
  @property({ type: String })
272
275
  url: string;
273
276
 
277
+ @property({ type: String })
278
+ start: string;
279
+
280
+ @property({ type: String })
281
+ end: string;
282
+
274
283
  @property({ type: String })
275
284
  header: string = '';
276
285
 
@@ -441,11 +450,33 @@ export class TembaChart extends RapidElement {
441
450
  this.updateChart();
442
451
  }
443
452
 
444
- if (changes.has('url')) {
445
- const store = getStore();
446
- store.getUrl(this.url, { skipCache: true }).then((response) => {
447
- this.data = response.json.data;
448
- });
453
+ if (changes.has('horizontal')) {
454
+ this.updateChart();
455
+ }
456
+
457
+ if (changes.has('url') || changes.has('start') || changes.has('end')) {
458
+ if (this.url) {
459
+ const store = getStore();
460
+ let fullUrl = this.url;
461
+
462
+ // Add query parameters for date range if provided
463
+ const params = new URLSearchParams();
464
+ if (this.start) {
465
+ params.append('since', this.start);
466
+ }
467
+ if (this.end) {
468
+ params.append('until', this.end);
469
+ }
470
+
471
+ if (params.toString()) {
472
+ const separator = this.url.includes('?') ? '&' : '?';
473
+ fullUrl = `${this.url}${separator}${params.toString()}`;
474
+ }
475
+
476
+ store.getUrl(fullUrl, { skipCache: true }).then((response) => {
477
+ this.data = response.json.data;
478
+ });
479
+ }
449
480
  }
450
481
 
451
482
  if (changes.has('chartType')) {
@@ -455,11 +486,14 @@ export class TembaChart extends RapidElement {
455
486
  }
456
487
 
457
488
  if (changes.has('showPercent') && this.chart) {
458
- const yScale = (this.chart.options.scales as any).y;
459
- yScale.ticks.display = !this.showPercent;
460
- yScale.grid.display = !this.showPercent;
461
- yScale.border.display = !this.showPercent;
462
- yScale.max = this.showPercent ? this.getInflatedMax() : undefined;
489
+ // in horizontal charts, the value axis is X, in vertical charts it's Y
490
+ const valueScale = this.horizontal
491
+ ? (this.chart.options.scales as any).x
492
+ : (this.chart.options.scales as any).y;
493
+ valueScale.ticks.display = !this.showPercent;
494
+ valueScale.grid.display = !this.showPercent;
495
+ valueScale.border.display = !this.showPercent;
496
+ valueScale.max = this.showPercent ? this.getInflatedMax() : undefined;
463
497
  this.chart.update();
464
498
  }
465
499
  }
@@ -627,23 +661,17 @@ export class TembaChart extends RapidElement {
627
661
  }
628
662
  this.chart.options.plugins.datalabels = datalabels;
629
663
 
630
- this.chart.update();
631
- } else {
632
- let format = this.xFormat;
633
- if (this.xType === 'time' && this.xFormat === 'auto') {
634
- const firstDate = this.data.labels[0];
635
- const lastDate = this.data.labels[this.data.labels.length - 1];
636
-
637
- const first = Date.parse(firstDate);
638
- const last = Date.parse(lastDate);
639
-
640
- const dayDiff = Math.ceil((last - first) / (1000 * 60 * 60 * 24));
641
- format = 'MMM dd';
642
- if (dayDiff > 365) {
643
- format = 'MMM yyyy';
644
- }
664
+ // the scale config could have changed, so we need to update it
665
+ if (this.horizontal) {
666
+ this.chart.options.scales.x = this.getValueAxisConfig();
667
+ this.chart.options.scales.y = this.getCategoryAxisConfig() as any;
668
+ } else {
669
+ this.chart.options.scales.x = this.getCategoryAxisConfig() as any;
670
+ this.chart.options.scales.y = this.getValueAxisConfig();
645
671
  }
646
672
 
673
+ this.chart.update();
674
+ } else {
647
675
  const chartData = {
648
676
  type: this.chartType,
649
677
  data: {
@@ -651,6 +679,7 @@ export class TembaChart extends RapidElement {
651
679
  datasets: this.datasets
652
680
  },
653
681
  options: {
682
+ ...(this.horizontal && { indexAxis: 'y' }),
654
683
  maxBarThickness: 80,
655
684
  plugins: {
656
685
  legend: { display: this.legend },
@@ -658,7 +687,10 @@ export class TembaChart extends RapidElement {
658
687
  callbacks: {
659
688
  label: (context: any) => {
660
689
  const label = context.dataset.label || '';
661
- const value = context.parsed.y;
690
+ // in horizontal charts, the value is on parsed.x, in vertical charts on parsed.y
691
+ const value = this.horizontal
692
+ ? context.parsed.x
693
+ : context.parsed.y;
662
694
  if (this.yType === 'duration') {
663
695
  return `${label}: ${formatDurationFromSeconds(value)}`;
664
696
  }
@@ -690,44 +722,15 @@ export class TembaChart extends RapidElement {
690
722
  duration: 0
691
723
  }
692
724
  },
693
- scales: {
694
- y: {
695
- min: 0,
696
- ...(this.showPercent && { max: this.getInflatedMax() }),
697
- stacked: true,
698
- grid: {
699
- color: 'rgba(0,0,0,0.04)',
700
- display: !this.showPercent, // hide gridlines in percent mode
701
- drawBorder: !this.showPercent // hides axis line when false
702
- },
703
- border: {
704
- display: !this.showPercent // Chart.js >= 4
705
- },
706
- ticks: {
707
- display: !this.showPercent,
708
-
709
- ...(this.yType === 'duration' &&
710
- !this.showPercent && {
711
- callback: (value: any) => formatDurationFromSeconds(value)
712
- })
725
+ scales: this.horizontal
726
+ ? {
727
+ x: this.getValueAxisConfig(),
728
+ y: this.getCategoryAxisConfig()
729
+ }
730
+ : {
731
+ y: this.getValueAxisConfig(),
732
+ x: this.getCategoryAxisConfig()
713
733
  }
714
- },
715
- x: {
716
- type: this.xType,
717
- grid: { display: false },
718
- stacked: true,
719
- ticks: {
720
- maxTicksLimit: this.xMaxTicks
721
- },
722
- ...(this.xType === 'time' && {
723
- time: {
724
- unit: 'day',
725
- tooltipFormat: 'DDD',
726
- displayFormats: { day: format }
727
- }
728
- })
729
- }
730
- }
731
734
  }
732
735
  };
733
736
 
@@ -750,6 +753,64 @@ export class TembaChart extends RapidElement {
750
753
  }
751
754
  }
752
755
 
756
+ private getValueAxisConfig() {
757
+ return {
758
+ min: 0,
759
+ ...(this.showPercent && { max: this.getInflatedMax() }),
760
+ stacked: true,
761
+ grid: {
762
+ color: 'rgba(0,0,0,0.04)',
763
+ display: !this.showPercent,
764
+ drawBorder: !this.showPercent
765
+ },
766
+ border: {
767
+ display: !this.showPercent
768
+ },
769
+ ticks: {
770
+ display: !this.showPercent,
771
+ ...(this.yType === 'duration' &&
772
+ !this.showPercent && {
773
+ callback: (value: any) => formatDurationFromSeconds(value)
774
+ })
775
+ }
776
+ };
777
+ }
778
+
779
+ private getCategoryAxisConfig() {
780
+ let format = this.xFormat;
781
+ if (this.xType === 'time' && this.xFormat === 'auto') {
782
+ const firstDate = this.data.labels[0];
783
+ const lastDate = this.data.labels[this.data.labels.length - 1];
784
+
785
+ const first = Date.parse(firstDate);
786
+ const last = Date.parse(lastDate);
787
+
788
+ const dayDiff = Math.ceil((last - first) / (1000 * 60 * 60 * 24));
789
+ format = 'MMM dd';
790
+ if (dayDiff > 365) {
791
+ format = 'MMM yyyy';
792
+ }
793
+ }
794
+
795
+ return {
796
+ max: this.xType === 'time' ? this.end : this.xMaxTicks,
797
+ min: this.xType === 'time' ? this.start : 0,
798
+ type: this.xType,
799
+ grid: { display: false },
800
+ stacked: true,
801
+ ticks: {
802
+ maxTicksLimit: this.xMaxTicks
803
+ },
804
+ ...(this.xType === 'time' && {
805
+ time: {
806
+ unit: 'day',
807
+ tooltipFormat: 'DDD',
808
+ displayFormats: { day: format }
809
+ }
810
+ })
811
+ };
812
+ }
813
+
753
814
  protected render(): TemplateResult {
754
815
  return html`<div>
755
816
  ${this.header
@@ -4,7 +4,7 @@ import { FormElement } from '../FormElement';
4
4
  import { getClasses } from '../utils';
5
5
  import { DateTime } from 'luxon';
6
6
 
7
- export default class DatePicker extends FormElement {
7
+ export class DatePicker extends FormElement {
8
8
  static get styles() {
9
9
  return css`
10
10
  :host {
@@ -131,6 +131,12 @@ export default class DatePicker extends FormElement {
131
131
  @property({ type: Boolean })
132
132
  time = false;
133
133
 
134
+ @property({ type: String })
135
+ min = '';
136
+
137
+ @property({ type: String })
138
+ max = '';
139
+
134
140
  /** we just return the value since it should be a string */
135
141
  public serializeValue(value: any): string {
136
142
  return value;
@@ -229,6 +235,8 @@ export default class DatePicker extends FormElement {
229
235
  value=${dateWidgetValue}
230
236
  type="${this.time ? 'datetime-local' : 'date'}"
231
237
  @change=${this.handleChange}
238
+ min=${this.min || undefined}
239
+ max=${this.max || undefined}
232
240
  />
233
241
  </div>
234
242
  ${this.time