@torpor/ui 0.3.6 → 0.4.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 (89) hide show
  1. package/dist/Accordion/Accordion.torp +26 -25
  2. package/dist/Accordion/AccordionItem.torp +3 -3
  3. package/dist/Accordion/AccordionTypes.ts +5 -5
  4. package/dist/Accordion/index.d.ts +2 -2
  5. package/dist/Calendar/CalendarTypes.ts +1 -1
  6. package/dist/Charts/BarChart.torp +159 -0
  7. package/dist/Charts/DataSeries.ts +5 -0
  8. package/dist/Charts/GridLines.torp +65 -0
  9. package/dist/Charts/LineChart.torp +196 -0
  10. package/dist/Charts/PieChart.torp +145 -0
  11. package/dist/Charts/ScatterChart.torp +215 -0
  12. package/dist/Charts/SparkLine.torp +71 -0
  13. package/dist/Charts/XAxis.torp +66 -0
  14. package/dist/Charts/YAxis.torp +47 -0
  15. package/dist/Charts/index.d.ts +220 -0
  16. package/dist/Charts/index.js +34 -0
  17. package/dist/Charts/index.js.map +1 -0
  18. package/dist/Contextual/Contextual.torp +8 -3
  19. package/dist/Contextual/ContextualTypes.ts +1 -1
  20. package/dist/Dialog/Dialog.torp +12 -16
  21. package/dist/Dialog/DialogButton.torp +3 -0
  22. package/dist/Dialog/DialogTypes.ts +2 -2
  23. package/dist/Dialog/index.d.ts +2 -0
  24. package/dist/Disclosure/Disclosure.torp +3 -3
  25. package/dist/Disclosure/DisclosureTypes.ts +1 -1
  26. package/dist/Drawer/Drawer.torp +2 -2
  27. package/dist/Drawer/DrawerTypes.ts +1 -1
  28. package/dist/Form/CheckBox.torp +1 -2
  29. package/dist/Form/Input.torp +1 -1
  30. package/dist/Form/MessageSummary.torp +38 -0
  31. package/dist/Form/TextArea.torp +3 -0
  32. package/dist/Form/index.d.ts +25 -1
  33. package/dist/Form/index.js +4 -0
  34. package/dist/ListBox/ListBox.torp +271 -0
  35. package/dist/ListBox/ListBoxItem.torp +107 -0
  36. package/dist/ListBox/ListBoxSeparator.torp +26 -0
  37. package/dist/ListBox/ListBoxTypes.ts +23 -0
  38. package/dist/ListBox/index.d.ts +97 -0
  39. package/dist/ListBox/index.js +14 -0
  40. package/dist/ListBox/index.js.map +1 -0
  41. package/dist/Menu/Menu.torp +19 -14
  42. package/dist/Menu/MenuTypes.ts +5 -4
  43. package/dist/MenuBar/MenuBarItem.torp +5 -8
  44. package/dist/MenuBar/MenuBarTypes.ts +2 -2
  45. package/dist/Modal/Modal.torp +7 -2
  46. package/dist/Modal/ModalTypes.ts +1 -1
  47. package/dist/NavMenu/NavMenu.torp +7 -0
  48. package/dist/NavMenu/NavMenuButton.torp +5 -2
  49. package/dist/NavMenu/NavMenuPopout.torp +1 -1
  50. package/dist/NavMenu/{NavMenuPopoutButton.torp → NavMenuPopoutTrigger.torp} +4 -4
  51. package/dist/NavMenu/NavMenuSeparator.torp +2 -1
  52. package/dist/NavMenu/NavMenuTypes.ts +4 -3
  53. package/dist/NavMenu/index.d.ts +22 -18
  54. package/dist/NavMenu/index.js +4 -4
  55. package/dist/Pagination/Pagination.torp +145 -0
  56. package/dist/Pagination/PaginationItem.torp +100 -0
  57. package/dist/Pagination/PaginationTypes.ts +25 -0
  58. package/dist/Pagination/buildPages.ts +57 -0
  59. package/dist/Pagination/index.d.ts +53 -0
  60. package/dist/Pagination/index.js +40 -0
  61. package/dist/Pagination/index.js.map +1 -0
  62. package/dist/Popover/Popover.torp +8 -3
  63. package/dist/Popover/PopoverTypes.ts +1 -1
  64. package/dist/SelectBox/SelectBox.torp +136 -0
  65. package/dist/SelectBox/SelectBoxContent.torp +81 -0
  66. package/dist/SelectBox/SelectBoxInput.torp +95 -0
  67. package/dist/SelectBox/SelectBoxTrigger.torp +41 -0
  68. package/dist/SelectBox/SelectBoxTypes.ts +18 -0
  69. package/dist/SelectBox/index.d.ts +127 -0
  70. package/dist/SelectBox/index.js +18 -0
  71. package/dist/SelectBox/index.js.map +1 -0
  72. package/dist/TabGroup/TabGroup.torp +3 -3
  73. package/dist/TabGroup/TabGroupTypes.ts +2 -2
  74. package/dist/ToolBar/ToolBar.torp +198 -0
  75. package/dist/ToolBar/ToolBarButton.torp +57 -0
  76. package/dist/ToolBar/ToolBarElement.torp +55 -0
  77. package/dist/ToolBar/ToolBarPopout.torp +97 -0
  78. package/dist/ToolBar/ToolBarPopoutContent.torp +92 -0
  79. package/dist/ToolBar/ToolBarPopoutTrigger.torp +138 -0
  80. package/dist/ToolBar/ToolBarSeparator.torp +25 -0
  81. package/dist/ToolBar/ToolBarTypes.ts +35 -0
  82. package/dist/ToolBar/index.d.ts +212 -0
  83. package/dist/ToolBar/index.js +30 -0
  84. package/dist/ToolBar/index.js.map +1 -0
  85. package/dist/metafile-esm.json +1 -1
  86. package/dist/utils/PopoutTypes.ts +35 -0
  87. package/dist/utils/chartColors.ts +17 -0
  88. package/dist/utils/chartUtils.ts +71 -0
  89. package/package.json +22 -2
@@ -11,11 +11,11 @@ interface AccordionProps {
11
11
  /** Whether a single AccordionItem or multiple AccordionItems can be expanded at the same time */
12
12
  type?: "single" | "multiple",
13
13
  /** The value of the expanded AccordionItem, or an array of values if the type is "multiple" */
14
- value?: string | string[],
14
+ value?: any | any[],
15
15
  /** If set to true, the user cannot interact with any items in the Accordion */
16
16
  disabled?: boolean,
17
17
  /** Raised when the expanded item(s) have changed */
18
- onchange?: (value: string | string[]) => void,
18
+ onchange?: (value: any | any[]) => void,
19
19
  }
20
20
 
21
21
  /**
@@ -41,9 +41,15 @@ export default function Accordion($props: AccordionProps | undefined) {
41
41
  let type = $props?.type ?? "single";
42
42
 
43
43
  let $state = $watch({
44
- value: $props?.value ?? (type === "single" ? "" : [] as string[])
44
+ value: $props?.value ?? (type === "single" ? undefined : [])
45
45
  });
46
46
 
47
+ // Update $state on $props change
48
+ $run(() => {
49
+ $state.value = $props?.value ?? (type === "single" ? undefined : [])
50
+ });
51
+
52
+ // Update $props on $state change
47
53
  $run(() => {
48
54
  if ($props !== undefined) {
49
55
  $props.value = $state.value;
@@ -65,48 +71,43 @@ export default function Accordion($props: AccordionProps | undefined) {
65
71
  $context[AccordionContextName] = context;
66
72
 
67
73
  $run(() => {
68
- // HACK: This should probably be done with setters?
69
- let newValue = $state.value;
74
+ // Get the value to ensure that this effect will be subscribed
75
+ let value = $state.value;
70
76
 
71
- // Get the value
77
+ // And set the expanded item(s)
72
78
  switch (type) {
73
79
  case "single": {
74
- // Ensure the value passed in was a string (in case e.g. the user has set values to ints)
75
- newValue = newValue.toString();
76
80
  for (let item of itemStates) {
77
- item.expanded = item.value === newValue;
81
+ item.expanded = item.value === value;
78
82
  }
79
83
  break;
80
84
  }
81
85
  case "multiple": {
82
- // Ensure the value passed in is an array
83
- if (!Array.isArray(newValue)) {
84
- newValue = [newValue];
86
+ if (!Array.isArray(value)) {
87
+ value = [value];
85
88
  }
86
89
  for (let item of itemStates) {
87
- item.expanded = newValue.indexOf(item.value) !== -1;
90
+ item.expanded = value.indexOf(item.value) !== -1;
88
91
  }
89
92
  break;
90
93
  }
91
94
  }
92
-
93
- // Set the value
94
- $state.value = newValue;
95
95
  })
96
96
 
97
97
  $run(() => {
98
- itemStates.forEach((s) => s.parentDisabled = $props?.disabled === true);
98
+ let disabled = $props?.disabled;
99
+ itemStates.forEach((s) => s.parentDisabled = disabled === true);
99
100
  });
100
101
 
101
102
  // Dispatch the change event when the value changes
102
103
  let firstChange = true;
103
104
  $run(() => {
104
- const ev = $props?.onchange;
105
+ const onchange = $props?.onchange;
105
106
  const value = $state.value;
106
107
  if (firstChange) {
107
108
  firstChange = false;
108
- } else if (ev) {
109
- ev(value);
109
+ } else if (onchange) {
110
+ onchange(value);
110
111
  }
111
112
  });
112
113
 
@@ -118,11 +119,11 @@ export default function Accordion($props: AccordionProps | undefined) {
118
119
 
119
120
  // If a value wasn't set for the item, set it to the index
120
121
  if (!itemState.value) {
121
- itemState.value = itemState.index.toString();
122
+ itemState.value = itemState.index;
122
123
  }
123
124
 
124
125
  // If we have a value, set whether the item should be expanded
125
- if ($state.value) {
126
+ if ($state.value !== undefined && $state.value !== null) {
126
127
  switch (type) {
127
128
  case "single": {
128
129
  itemState.expanded = $state.value === itemState.value;
@@ -166,7 +167,7 @@ export default function Accordion($props: AccordionProps | undefined) {
166
167
  break;
167
168
  }
168
169
  case "multiple": {
169
- let newValue = [];
170
+ let value = [];
170
171
  for (let item of itemStates) {
171
172
  // The item is expanded if it has the supplied value (i.e. if it was clicked) or if it
172
173
  // was already expanded
@@ -174,10 +175,10 @@ export default function Accordion($props: AccordionProps | undefined) {
174
175
  (item.value === toggleValue && !item.expanded) ||
175
176
  (item.value !== toggleValue && item.expanded)
176
177
  ) {
177
- newValue.push(item.value);
178
+ value.push(item.value);
178
179
  }
179
180
  }
180
- $state.value = newValue.sort();
181
+ $state.value = value.sort();
181
182
  break;
182
183
  }
183
184
  }
@@ -71,12 +71,12 @@ export default function AccordionItem($props: AccordionItemProps | undefined) {
71
71
  // Dispatch the toggle event when the value of expanded changes
72
72
  let firstToggle = true;
73
73
  $run(() => {
74
- const ev = $props?.ontoggle;
74
+ const ontoggle = $props?.ontoggle;
75
75
  const expanded = $state.expanded;
76
76
  if (firstToggle) {
77
77
  firstToggle = false;
78
- } else if (ev) {
79
- ev(expanded);
78
+ } else if (ontoggle) {
79
+ ontoggle(expanded);
80
80
  }
81
81
  });
82
82
 
@@ -1,5 +1,5 @@
1
- export const AccordionContextName = "Accordion";
2
- export const AccordionItemContextName = "AccordionItem";
1
+ export const AccordionContextName: unique symbol = Symbol.for("torp.Accordion");
2
+ export const AccordionItemContextName: unique symbol = Symbol.for("torp.AccordionItem");
3
3
 
4
4
  export interface AccordionContext {
5
5
  /** Called from an AccordionItem when it is added */
@@ -7,14 +7,14 @@ export interface AccordionContext {
7
7
  /** Called from an AccordionItem when it is removed */
8
8
  removeItem: (index: number) => void;
9
9
  /** Called from an AccordionItem when it is toggled */
10
- toggleItem: (value: string) => void;
10
+ toggleItem: (value: any) => void;
11
11
  /** Called from an AccordionHeader when it receives a keyboard event */
12
12
  handleHeaderKey: (index: number, e: KeyboardEvent) => void;
13
13
  }
14
14
 
15
15
  export interface AccordionItemContext {
16
16
  /** Raised when an AccordionItem is toggled */
17
- toggleItem: (value: string) => void;
17
+ toggleItem: (value: any) => void;
18
18
  /** Raised when an AccordionHeader receives a keyboard event */
19
19
  handleHeaderKey: (index: number, e: KeyboardEvent) => void;
20
20
  /** The reactive state for an AccordionItem */
@@ -25,7 +25,7 @@ export interface AccordionItemContext {
25
25
 
26
26
  export interface ItemState {
27
27
  index: number;
28
- value: string;
28
+ value: any;
29
29
  expanded: boolean;
30
30
  headerId: string;
31
31
  contentId: string;
@@ -11,11 +11,11 @@ interface AccordionProps {
11
11
  /** Whether a single AccordionItem or multiple AccordionItems can be expanded at the same time */
12
12
  type?: "single" | "multiple",
13
13
  /** The value of the expanded AccordionItem, or an array of values if the type is "multiple" */
14
- value?: string | string[],
14
+ value?: any | any[],
15
15
  /** If set to true, the user cannot interact with any items in the Accordion */
16
16
  disabled?: boolean,
17
17
  /** Raised when the expanded item(s) have changed */
18
- onchange?: (value: string | string[]) => void,
18
+ onchange?: (value: any | any[]) => void,
19
19
  }
20
20
 
21
21
  /**
@@ -1,4 +1,4 @@
1
- export const CalendarContextName = "Calendar";
1
+ export const CalendarContextName: unique symbol = Symbol.for("torp.Calendar");
2
2
 
3
3
  export interface CalendarContext {
4
4
  // The registerDay function is called from each CalendarDay to register itself with this
@@ -0,0 +1,159 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+ import XAxis from "./XAxis.torp";
3
+ import YAxis from "./YAxis.torp";
4
+ import GridLines from "./GridLines.torp";
5
+ import {
6
+ calculateMaxValue,
7
+ calculateStepValue,
8
+ calculateStepLabels,
9
+ calculateItemWidth,
10
+ calculateValueHeight,
11
+ calculateChartBottom,
12
+ calculateChartLeft
13
+ } from "../utils/chartUtils";
14
+ import { chartColors } from "../utils/chartColors";
15
+
16
+ import type DataSeries from "./DataSeries";
17
+
18
+ interface BarChartProps {
19
+ /** An ID for the root element */
20
+ id?: string;
21
+ /** Classes for the root element */
22
+ class?: ClassValue,
23
+ /** Styles for the root element */
24
+ style?: StyleValue,
25
+ series: DataSeries[],
26
+ height?: number,
27
+ width?: number,
28
+ xLabel?: string,
29
+ yLabel?: string,
30
+ maxValue?: number,
31
+ stepCount?: number,
32
+ stepValue?: number,
33
+ hideXAxis?: boolean,
34
+ hideYAxis?: boolean,
35
+ showHLines?: boolean,
36
+ showVLines?: boolean,
37
+ }
38
+
39
+ export default function BarChart($props: BarChartProps) {
40
+ const defaultHeight = 200;
41
+ const defaultWidth = 400;
42
+
43
+ let container: HTMLElement;
44
+ let measurer: SVGTextElement;
45
+
46
+ let $state = $watch({
47
+ textWidth: 0,
48
+ textHeight: 0,
49
+ get labels() {
50
+ return $props.series.map((s) => s.name);
51
+ },
52
+ get calculatedHeight() {
53
+ return $props.height || defaultHeight;
54
+ },
55
+ get calculatedWidth() {
56
+ return $props.width || container?.clientWidth || defaultWidth;
57
+ },
58
+ get maxValue () {
59
+ return $props.maxValue || calculateMaxValue($props.series);
60
+ },
61
+ get calculatedStepValue() {
62
+ return $props.stepValue || calculateStepValue($state.maxValue, $props.stepCount);
63
+ },
64
+ get stepLabels() {
65
+ return calculateStepLabels($props.stepCount || 2, $state.calculatedStepValue);
66
+ },
67
+ get itemWidth() {
68
+ return calculateItemWidth($state.calculatedWidth, $state.chartLeft, $state.labels);
69
+ },
70
+ get valueHeight() {
71
+ return calculateValueHeight($state.chartBottom, $state.textHeight, $state.calculatedStepValue, $props.stepCount || 2);
72
+ },
73
+ get chartBottom() {
74
+ return calculateChartBottom($props.xLabel ?? "", $state.calculatedHeight, $state.textHeight);
75
+ },
76
+ get chartLeft() {
77
+ return calculateChartLeft($state.stepLabels, $props.yLabel ?? "", $state.textHeight, $state.textWidth);
78
+ }
79
+ });
80
+
81
+ $mount(() => {
82
+ // HACK: Wait until the measurer is really available
83
+ setTimeout(() => {
84
+ const bbox = measurer.getBBox();
85
+ $state.textWidth = bbox.width;
86
+ $state.textHeight = bbox.height * 1.5;
87
+ measurer.remove();
88
+ }, 1);
89
+ });
90
+
91
+ @render {
92
+ <div
93
+ {$props?.id}
94
+ class={["torp-bar-chart", $props?.class]}
95
+ {$props?.style}
96
+ &ref={container}
97
+ >
98
+ <svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight} xmlns="http://www.w3.org/2000/svg">
99
+ <g>
100
+ <text &ref={measurer} x="-999" y="-999">8</text>
101
+
102
+ @if ($state.textWidth && $state.textHeight) {
103
+ <XAxis
104
+ showAxis={!$props.hideXAxis}
105
+ height={$state.calculatedHeight}
106
+ width={$state.calculatedWidth}
107
+ {$state.itemWidth}
108
+ {$state.textHeight}
109
+ xLabel={$props.xLabel ?? ""}
110
+ stepLabels={[]}
111
+ stepValue={0}
112
+ {$state.labels}
113
+ {$state.chartLeft}
114
+ {$state.chartBottom}
115
+ />
116
+ <YAxis
117
+ showAxis={!$props.hideYAxis}
118
+ {$state.valueHeight}
119
+ {$state.textHeight}
120
+ yLabel={$props.yLabel ?? ""}
121
+ {$state.stepLabels}
122
+ stepValue={$state.calculatedStepValue}
123
+ {$state.chartLeft}
124
+ {$state.chartBottom}
125
+ />
126
+ <GridLines
127
+ {$props.showHLines}
128
+ {$props.showVLines}
129
+ width={$state.calculatedWidth}
130
+ {$state.labels}
131
+ xStepValue={0}
132
+ xStepLabels={[]}
133
+ yStepValue={$state.calculatedStepValue}
134
+ yStepLabels={$state.stepLabels}
135
+ {$state.itemWidth}
136
+ {$state.valueHeight}
137
+ {$state.chartLeft}
138
+ {$state.chartBottom}
139
+ type="bar"
140
+ />
141
+ @for (let [i, series] of $props.series.entries()) {
142
+ <rect
143
+ x={$state.chartLeft +
144
+ i * $state.itemWidth +
145
+ $state.itemWidth / 4}
146
+ y={$state.chartBottom - Math.max(1, series.data[0] * $state.valueHeight)}
147
+ width={Math.max(1, $state.itemWidth / 2)}
148
+ height={Math.max(1, series.data[0] * $state.valueHeight)}
149
+ fill={series.color || chartColors[i % chartColors.length]}
150
+ >
151
+ <title>{`${[series.name, series.data[0]].filter(Boolean).join("\n")}`}</title>
152
+ </rect>
153
+ }
154
+ }
155
+ </g>
156
+ </svg>
157
+ </div>
158
+ }
159
+ }
@@ -0,0 +1,5 @@
1
+ export default interface DataSeries {
2
+ name: string;
3
+ color: string;
4
+ data: number[];
5
+ }
@@ -0,0 +1,65 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+
3
+ interface GridLinesProps {
4
+ /** An ID for the root element */
5
+ id?: string;
6
+ /** Classes for the root element */
7
+ class?: ClassValue,
8
+ /** Styles for the root element */
9
+ style?: StyleValue,
10
+ showHLines?: boolean,
11
+ showVLines?: boolean,
12
+ width: number;
13
+ labels: string[];
14
+ xStepValue: number;
15
+ xStepLabels: number[];
16
+ yStepValue: number;
17
+ yStepLabels: number[];
18
+ itemWidth: number;
19
+ valueHeight: number;
20
+ chartLeft: number;
21
+ chartBottom: number;
22
+ type: string;
23
+ }
24
+
25
+ export default function GridLines($props: GridLinesProps) {
26
+ @render {
27
+ <svg>
28
+ @if ($props.showHLines && $props.yStepLabels) {
29
+ @for (let [i, label] of $props.yStepLabels!.entries()) {
30
+ <line
31
+ x1={$props.chartLeft ?? 0}
32
+ y1={$props.chartBottom - i * $props.yStepValue * $props.valueHeight}
33
+ x2={$props.width ?? 0}
34
+ y2={$props.chartBottom - i * $props.yStepValue * $props.valueHeight}
35
+ stroke="currentcolor"
36
+ />
37
+ }
38
+ }
39
+
40
+ @if ($props.showVLines) {
41
+ @if ($props.xStepLabels.length && $props.xStepValue) {
42
+ @for (let [i, label] of $props.xStepLabels.entries()) {
43
+ <line
44
+ x1={$props.chartLeft + i * $props.xStepValue * $props.itemWidth}
45
+ y1={$props.chartBottom}
46
+ x2={$props.chartLeft + i * $props.xStepValue * $props.itemWidth}
47
+ y2={0}
48
+ stroke="currentcolor"
49
+ />
50
+ }
51
+ } else {
52
+ @for (let [i, label] of $props.labels.entries()) {
53
+ <line
54
+ x1={$props.chartLeft + i * $props.itemWidth + ($props.type === "bar" ? 0 : $props.itemWidth / 2)}
55
+ y1={$props.chartBottom}
56
+ x2={$props.chartLeft + i * $props.itemWidth + ($props.type === "bar" ? 0 : $props.itemWidth / 2)}
57
+ y2={0}
58
+ stroke="currentcolor"
59
+ />
60
+ }
61
+ }
62
+ }
63
+ </svg>
64
+ }
65
+ }
@@ -0,0 +1,196 @@
1
+ import type { ClassValue, StyleValue } from "@torpor/view";
2
+
3
+ import XAxis from "./XAxis.torp";
4
+ import YAxis from "./YAxis.torp";
5
+ import GridLines from "./GridLines.torp";
6
+ import {
7
+ calculateMaxValue,
8
+ calculateStepValue,
9
+ calculateStepLabels,
10
+ calculateItemWidth,
11
+ calculateValueHeight,
12
+ calculateChartBottom,
13
+ calculateChartLeft
14
+ } from "../utils/chartUtils";
15
+ import { chartColors } from "../utils/chartColors";
16
+
17
+ import type DataSeries from "./DataSeries";
18
+
19
+ interface LineChartProps {
20
+ /** An ID for the root element */
21
+ id?: string;
22
+ /** Classes for the root element */
23
+ class?: ClassValue,
24
+ /** Styles for the root element */
25
+ style?: StyleValue,
26
+ series: DataSeries[],
27
+ height?: number,
28
+ width?: number,
29
+ xLabel?: string,
30
+ yLabel?: string,
31
+ maxValue?: number,
32
+ stepCount?: number,
33
+ stepValue?: number,
34
+ showPoints?: boolean,
35
+ hideXAxis?: boolean,
36
+ hideYAxis?: boolean,
37
+ showHLines?: boolean,
38
+ showVLines?: boolean,
39
+ }
40
+
41
+ export default function LineChart($props: LineChartProps) {
42
+ const defaultHeight = 200;
43
+ const defaultWidth = 400;
44
+
45
+ let container: HTMLElement;
46
+ let measurer: SVGTextElement;
47
+
48
+ let $state = $watch({
49
+ textWidth: 0,
50
+ textHeight: 0,
51
+ get labels() {
52
+ return $props.series.map((s) => s.name);
53
+ },
54
+ get calculatedHeight() {
55
+ return $props.height || defaultHeight;
56
+ },
57
+ get calculatedWidth() {
58
+ return $props.width || container?.clientWidth || defaultWidth;
59
+ },
60
+ get maxValue() {
61
+ return $props.maxValue || calculateMaxValue($props.series);
62
+ },
63
+ get calculatedStepValue() {
64
+ return $props.stepValue || calculateStepValue($state.maxValue, $props.stepCount);
65
+ },
66
+ get stepLabels() {
67
+ return calculateStepLabels($props.stepCount || 2, $state.calculatedStepValue);
68
+ },
69
+ get itemWidth() {
70
+ return calculateItemWidth($state.calculatedWidth, $state.chartLeft, $state.labels);
71
+ },
72
+ get valueHeight() {
73
+ return calculateValueHeight($state.chartBottom, $state.textHeight, $state.calculatedStepValue, $props.stepCount || 2);
74
+ },
75
+ get chartBottom() {
76
+ return calculateChartBottom($props.xLabel ?? "", $state.calculatedHeight, $state.textHeight);
77
+ },
78
+ get chartLeft() {
79
+ return calculateChartLeft($state.stepLabels, $props.yLabel ?? "", $state.textHeight, $state.textWidth);
80
+ },
81
+ get pointSeries() {
82
+ return buildPointSeries(
83
+ $props.series,
84
+ $state.chartLeft,
85
+ $state.chartBottom,
86
+ $state.itemWidth,
87
+ $state.valueHeight
88
+ );
89
+ }
90
+ });
91
+
92
+ $mount(() => {
93
+ // HACK: Wait until the measurer is really available
94
+ setTimeout(() => {
95
+ const bbox = measurer.getBBox();
96
+ $state.textWidth = bbox.width;
97
+ $state.textHeight = bbox.height * 1.5;
98
+ measurer.remove();
99
+ }, 1);
100
+ });
101
+
102
+ function buildPointSeries(
103
+ theSeries: DataSeries[],
104
+ theChartLeft: number,
105
+ theChartBottom: number,
106
+ theItemWidth: number,
107
+ theValueHeight: number
108
+ ): { x: number; y: number }[][] {
109
+ return theSeries.map((series) => {
110
+ return series.data.map((value, i) => {
111
+ const x = +(theChartLeft + i * theItemWidth + theItemWidth / 2).toFixed(2);
112
+ const y = +(theChartBottom - value * theValueHeight).toFixed(2);
113
+ return { x, y };
114
+ });
115
+ });
116
+ }
117
+
118
+ @render {
119
+ <div
120
+ {$props?.id}
121
+ class={["torp-line-chart", $props?.class]}
122
+ {$props?.style}
123
+ &ref={container}
124
+ >
125
+ <svg version="1.1" width={$state.calculatedWidth} height={$state.calculatedHeight}>
126
+ <g>
127
+ <text &ref={measurer} x="-999" y="-999">8</text>
128
+
129
+ @if ($state.textWidth && $state.textHeight) {
130
+ <XAxis
131
+ showAxis={!$props.hideXAxis}
132
+ height={$state.calculatedHeight}
133
+ width={$state.calculatedWidth}
134
+ {$state.itemWidth}
135
+ {$state.textHeight}
136
+ xLabel={$props.xLabel ?? ""}
137
+ {$state.labels}
138
+ stepLabels={[]}
139
+ stepValue={0}
140
+ {$state.chartLeft}
141
+ {$state.chartBottom}
142
+ />
143
+ <YAxis
144
+ showAxis={!$props.hideYAxis}
145
+ {$state.valueHeight}
146
+ {$state.textHeight}
147
+ yLabel={$props.yLabel ?? ""}
148
+ {$state.stepLabels}
149
+ stepValue={$state.calculatedStepValue}
150
+ {$state.chartLeft}
151
+ {$state.chartBottom}
152
+ />
153
+ <GridLines
154
+ {$props.showHLines}
155
+ {$props.showVLines}
156
+ width={$state.calculatedWidth}
157
+ {$state.labels}
158
+ xStepValue={0}
159
+ xStepLabels={[]}
160
+ yStepValue={$state.calculatedStepValue}
161
+ yStepLabels={[]}
162
+ {$state.stepLabels}
163
+ {$state.itemWidth}
164
+ {$state.valueHeight}
165
+ {$state.chartLeft}
166
+ {$state.chartBottom}
167
+ type="line"
168
+ />
169
+ @for (let [i, series] of $props.series.entries()) {
170
+ <polyline
171
+ points={$state.pointSeries[i].map((p) => `${p.x},${p.y}`).join(" ")}
172
+ stroke={series.color || chartColors[i % chartColors.length]}
173
+ stroke-width="2"
174
+ fill="none"
175
+ />
176
+ @if ($props.showPoints) {
177
+ @for (let [j, point] of $state.pointSeries[i].entries()) {
178
+ <circle
179
+ cx={point.x}
180
+ cy={point.y}
181
+ r="4"
182
+ stroke={series.color || chartColors[i % chartColors.length]}
183
+ stroke-width="2"
184
+ fill="white"
185
+ >
186
+ <title>{`${[series.name, $state.labels[j], series.data[j]].filter(Boolean).join("\n")}`}</title>
187
+ </circle>
188
+ }
189
+ }
190
+ }
191
+ }
192
+ </g>
193
+ </svg>
194
+ </div>
195
+ }
196
+ }