@watt-ui/react 0.1.4 → 0.2.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 (98) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +9 -9
  3. package/dist/chart/category-engine.d.ts +2 -0
  4. package/dist/chart/category-engine.d.ts.map +1 -0
  5. package/dist/chart/category-engine.js +9 -0
  6. package/dist/chart/category-engine.js.map +1 -0
  7. package/dist/chart/category-options.d.ts +23 -0
  8. package/dist/chart/category-options.d.ts.map +1 -0
  9. package/dist/chart/category-options.js +120 -0
  10. package/dist/chart/category-options.js.map +1 -0
  11. package/dist/components/asset-tree.d.ts.map +1 -1
  12. package/dist/components/asset-tree.js +16 -2
  13. package/dist/components/asset-tree.js.map +1 -1
  14. package/dist/components/battery-soc.d.ts.map +1 -1
  15. package/dist/components/battery-soc.js +5 -1
  16. package/dist/components/battery-soc.js.map +1 -1
  17. package/dist/components/category-chart.d.ts +37 -0
  18. package/dist/components/category-chart.d.ts.map +1 -0
  19. package/dist/components/category-chart.js +251 -0
  20. package/dist/components/category-chart.js.map +1 -0
  21. package/dist/components/energy-flow.d.ts +2 -1
  22. package/dist/components/energy-flow.d.ts.map +1 -1
  23. package/dist/components/energy-flow.js +2 -2
  24. package/dist/components/energy-flow.js.map +1 -1
  25. package/dist/components/equipment-card.d.ts +12 -0
  26. package/dist/components/equipment-card.d.ts.map +1 -0
  27. package/dist/components/equipment-card.js +18 -0
  28. package/dist/components/equipment-card.js.map +1 -0
  29. package/dist/components/equipment-heatmap.d.ts +33 -0
  30. package/dist/components/equipment-heatmap.d.ts.map +1 -0
  31. package/dist/components/equipment-heatmap.js +161 -0
  32. package/dist/components/equipment-heatmap.js.map +1 -0
  33. package/dist/components/equipment-table.d.ts +34 -0
  34. package/dist/components/equipment-table.d.ts.map +1 -0
  35. package/dist/components/equipment-table.js +141 -0
  36. package/dist/components/equipment-table.js.map +1 -0
  37. package/dist/components/metric-trend.d.ts +13 -0
  38. package/dist/components/metric-trend.d.ts.map +1 -0
  39. package/dist/components/metric-trend.js +30 -0
  40. package/dist/components/metric-trend.js.map +1 -0
  41. package/dist/components/operating-timeline.d.ts +31 -0
  42. package/dist/components/operating-timeline.d.ts.map +1 -0
  43. package/dist/components/operating-timeline.js +61 -0
  44. package/dist/components/operating-timeline.js.map +1 -0
  45. package/dist/components/plant-fleet-map.d.ts +32 -0
  46. package/dist/components/plant-fleet-map.d.ts.map +1 -0
  47. package/dist/components/plant-fleet-map.js +56 -0
  48. package/dist/components/plant-fleet-map.js.map +1 -0
  49. package/dist/components/threshold-gauge.d.ts +19 -0
  50. package/dist/components/threshold-gauge.d.ts.map +1 -0
  51. package/dist/components/threshold-gauge.js +54 -0
  52. package/dist/components/threshold-gauge.js.map +1 -0
  53. package/dist/components/time-range-selector.d.ts +16 -0
  54. package/dist/components/time-range-selector.d.ts.map +1 -0
  55. package/dist/components/time-range-selector.js +47 -0
  56. package/dist/components/time-range-selector.js.map +1 -0
  57. package/dist/icons/index.d.ts +17 -1
  58. package/dist/icons/index.d.ts.map +1 -1
  59. package/dist/icons/index.js +98 -1
  60. package/dist/icons/index.js.map +1 -1
  61. package/dist/index.d.ts +19 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +9 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/map/land.d.ts +2 -0
  66. package/dist/map/land.d.ts.map +1 -0
  67. package/dist/map/land.js +3 -0
  68. package/dist/map/land.js.map +1 -0
  69. package/dist/server.d.ts +5 -0
  70. package/dist/server.d.ts.map +1 -1
  71. package/dist/server.js +21 -1
  72. package/dist/server.js.map +1 -1
  73. package/dist/styles.css +133 -4
  74. package/dist/virtual-focus.d.ts +3 -0
  75. package/dist/virtual-focus.d.ts.map +1 -0
  76. package/dist/virtual-focus.js +23 -0
  77. package/dist/virtual-focus.js.map +1 -0
  78. package/package.json +37 -1
  79. package/src/chart/category-engine.ts +8 -0
  80. package/src/chart/category-options.ts +158 -0
  81. package/src/components/asset-tree.tsx +21 -5
  82. package/src/components/battery-soc.tsx +8 -0
  83. package/src/components/category-chart.tsx +452 -0
  84. package/src/components/energy-flow.tsx +3 -1
  85. package/src/components/equipment-card.tsx +46 -0
  86. package/src/components/equipment-heatmap.tsx +298 -0
  87. package/src/components/equipment-table.tsx +302 -0
  88. package/src/components/metric-trend.tsx +87 -0
  89. package/src/components/operating-timeline.tsx +182 -0
  90. package/src/components/plant-fleet-map.tsx +173 -0
  91. package/src/components/threshold-gauge.tsx +152 -0
  92. package/src/components/time-range-selector.tsx +126 -0
  93. package/src/icons/index.tsx +107 -0
  94. package/src/index.ts +44 -0
  95. package/src/map/land.ts +2 -0
  96. package/src/server.ts +28 -1
  97. package/src/styles.css +133 -4
  98. package/src/virtual-focus.ts +27 -0
package/dist/server.d.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import { type ChartConfig } from "./chart/options.js";
2
2
  import type { TelemetrySeries } from "./types.js";
3
+ import { type CategoryChartConfig } from "./chart/category-options.js";
3
4
  export interface ServerChartProps extends Omit<ChartConfig, "series"> {
4
5
  series: readonly TelemetrySeries[];
5
6
  width: number;
6
7
  height: number;
7
8
  }
9
+ export declare function renderCategorySVG({ width, height, ...config }: CategoryChartConfig & {
10
+ width: number;
11
+ height: number;
12
+ }): string;
8
13
  /** Render on the server and pass this trusted output to TelemetryChart.Canvas previewSvg. */
9
14
  export declare function renderTelemetrySVG({ width, height, series, ...config }: ServerChartProps): string;
10
15
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IACnE,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACV,EAAE,gBAAgB,GAAG,MAAM,CAwB3B"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAC;AACrC,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC;IACnE,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,MAAM,EACN,GAAG,MAAM,EACV,EAAE,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAiBlE;AACD,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACV,EAAE,gBAAgB,GAAG,MAAM,CAwB3B"}
package/dist/server.js CHANGED
@@ -1,9 +1,29 @@
1
1
  import { init, use } from "echarts/core";
2
- import { LineChart } from "echarts/charts";
2
+ import { LineChart, BarChart } from "echarts/charts";
3
3
  import { GridComponent, TooltipComponent, DataZoomComponent, } from "echarts/components";
4
4
  import { SVGRenderer } from "echarts/renderers";
5
5
  import { prepareSeries } from "./chart/data.js";
6
6
  import { chartOption } from "./chart/options.js";
7
+ import { categoryOption, validateCategories, } from "./chart/category-options.js";
8
+ export function renderCategorySVG({ width, height, ...config }) {
9
+ if (![width, height].every((value) => Number.isFinite(value) && value > 0))
10
+ throw new Error("SSR charts require positive width and height");
11
+ validateCategories(config);
12
+ use([BarChart, GridComponent, TooltipComponent, SVGRenderer]);
13
+ const chart = init(null, undefined, {
14
+ renderer: "svg",
15
+ ssr: true,
16
+ width,
17
+ height,
18
+ });
19
+ try {
20
+ chart.setOption(categoryOption(config));
21
+ return chart.renderToSVGString();
22
+ }
23
+ finally {
24
+ chart.dispose();
25
+ }
26
+ }
7
27
  /** Render on the server and pass this trusted output to TelemetryChart.Canvas previewSvg. */
8
28
  export function renderTelemetrySVG({ width, height, series, ...config }) {
9
29
  if (![width, height].every((v) => Number.isFinite(v) && v > 0))
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAOnE,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACQ;IACjB,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,GAAG,CAAC;QACF,SAAS;QACT,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,WAAW;KACZ,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE;QAClC,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,KAAK;QACL,MAAM;KACP,CAAC,CAAC;IACH,IAAI,CAAC;QACH,KAAK,CAAC,SAAS,CACb,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CACjE,CAAC;QACF,OAAO,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACnC,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAoB,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EACL,cAAc,EACd,kBAAkB,GAEnB,MAAM,6BAA6B,CAAC;AAMrC,MAAM,UAAU,iBAAiB,CAAC,EAChC,KAAK,EACL,MAAM,EACN,GAAG,MAAM,EAC+C;IACxD,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QACxE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,GAAG,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE;QAClC,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,KAAK;QACL,MAAM;KACP,CAAC,CAAC;IACH,IAAI,CAAC;QACH,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;QACxC,OAAO,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACnC,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AACD,6FAA6F;AAC7F,MAAM,UAAU,kBAAkB,CAAC,EACjC,KAAK,EACL,MAAM,EACN,MAAM,EACN,GAAG,MAAM,EACQ;IACjB,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,GAAG,CAAC;QACF,SAAS;QACT,aAAa;QACb,gBAAgB;QAChB,iBAAiB;QACjB,WAAW;KACZ,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE;QAClC,QAAQ,EAAE,KAAK;QACf,GAAG,EAAE,IAAI;QACT,KAAK;QACL,MAAM;KACP,CAAC,CAAC;IACH,IAAI,CAAC;QACH,KAAK,CAAC,SAAS,CACb,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CACjE,CAAC;QACF,OAAO,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACnC,CAAC;YAAS,CAAC;QACT,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;AACH,CAAC"}
package/dist/styles.css CHANGED
@@ -52,6 +52,93 @@
52
52
  .watt-soc-fill {
53
53
  transition-property: var(--watt-motion-property, width);
54
54
  }
55
+ .watt-gauge-fill {
56
+ transition-property: var(--watt-motion-property, width);
57
+ }
58
+ .watt-category-marker {
59
+ transition-property: var(--watt-motion-property, background-color, scale);
60
+ }
61
+ .watt-chart-legend-toggle[aria-pressed="false"] .watt-category-marker {
62
+ scale: 0.75;
63
+ }
64
+ .watt-timeline-event {
65
+ transition-property: var(--watt-motion-property, left, width);
66
+ }
67
+ [data-slot="equipment-table-row"] {
68
+ transition-property: var(--watt-motion-property, background-color);
69
+ }
70
+ [data-slot="plant-fleet-map-site"][data-selected] {
71
+ background-color: color-mix(
72
+ in srgb,
73
+ var(--watt-accent) 12%,
74
+ var(--watt-surface)
75
+ );
76
+ }
77
+ [data-slot="equipment-table-row"]:has(button):focus-within {
78
+ outline: 2px solid var(--watt-accent);
79
+ outline-offset: -2px;
80
+ }
81
+ @media (hover: hover) {
82
+ [data-slot="equipment-table-row"][tabindex]:hover {
83
+ background-color: var(--watt-subtle);
84
+ }
85
+ [data-slot="operating-timeline-event"]:hover {
86
+ border-color: var(--watt-foreground);
87
+ }
88
+ }
89
+ .watt-heatmap-cell {
90
+ transition-property: var(
91
+ --watt-motion-property,
92
+ background-color,
93
+ border-color
94
+ );
95
+ }
96
+ .watt-heatmap-cell[data-selected] {
97
+ outline: 2px solid var(--watt-accent);
98
+ outline-offset: -2px;
99
+ }
100
+ .watt-heatmap-cell[data-quality="stale"],
101
+ .watt-heatmap-cell[data-quality="offline"] {
102
+ border-style: dashed;
103
+ }
104
+ .watt-heatmap-cell[data-quality="bad"],
105
+ .watt-heatmap-cell[data-status="fault"] {
106
+ border: 3px double var(--watt-critical);
107
+ }
108
+ .watt-heatmap-cell:disabled {
109
+ opacity: 0.5;
110
+ cursor: not-allowed;
111
+ }
112
+ @media (hover: hover) {
113
+ .watt-heatmap-cell:hover:not(:disabled) {
114
+ border-color: var(--watt-foreground);
115
+ }
116
+ }
117
+ [data-slot="threshold-gauge"][data-tone="positive"] .watt-gauge-fill {
118
+ background-color: var(--watt-positive);
119
+ }
120
+ [data-slot="threshold-gauge"][data-tone="warning"] .watt-gauge-fill {
121
+ background-color: var(--watt-warning);
122
+ }
123
+ [data-slot="threshold-gauge"][data-tone="critical"] .watt-gauge-fill {
124
+ background-color: var(--watt-critical);
125
+ }
126
+ .watt-choice {
127
+ transition-property: var(--watt-motion-property, background-color, color);
128
+ }
129
+ @media (hover: hover) {
130
+ .watt-choice:hover {
131
+ background-color: var(--watt-subtle);
132
+ }
133
+ }
134
+ .watt-choice:active {
135
+ background-color: var(--watt-border);
136
+ }
137
+ @media (pointer: coarse) {
138
+ .watt-choice {
139
+ min-height: 44px;
140
+ }
141
+ }
55
142
  [data-slot="battery-soc"][data-invalid] .watt-soc-fill {
56
143
  transition-duration: 0s;
57
144
  }
@@ -59,21 +146,33 @@
59
146
  transition-property: var(--watt-motion-property, background-color);
60
147
  }
61
148
  [data-slot="asset-tree-row"][data-selected] {
62
- background-color: color-mix(in srgb, var(--watt-accent) 8%, var(--watt-surface));
149
+ background-color: color-mix(
150
+ in srgb,
151
+ var(--watt-accent) 8%,
152
+ var(--watt-surface)
153
+ );
63
154
  }
64
155
  @media (hover: hover) {
65
156
  [data-slot="asset-tree-row"]:hover:not([data-selected]) {
66
157
  background-color: var(--watt-subtle);
67
158
  }
68
159
  [data-slot="asset-tree-row"][data-selected]:hover {
69
- background-color: color-mix(in srgb, var(--watt-accent) 12%, var(--watt-surface));
160
+ background-color: color-mix(
161
+ in srgb,
162
+ var(--watt-accent) 12%,
163
+ var(--watt-surface)
164
+ );
70
165
  }
71
166
  .watt-chart-legend-toggle:hover {
72
167
  background-color: var(--watt-subtle);
73
168
  }
74
169
  }
75
170
  [data-slot="asset-tree-row"]:active:not([data-selected]) {
76
- background-color: color-mix(in srgb, var(--watt-foreground) 7%, var(--watt-surface));
171
+ background-color: color-mix(
172
+ in srgb,
173
+ var(--watt-foreground) 7%,
174
+ var(--watt-surface)
175
+ );
77
176
  }
78
177
  .watt-chart-legend-toggle {
79
178
  min-height: 36px;
@@ -89,7 +188,9 @@
89
188
  background-color: var(--watt-border);
90
189
  }
91
190
  @media (pointer: coarse) {
92
- .watt-chart-legend-toggle { min-height: 44px; }
191
+ .watt-chart-legend-toggle {
192
+ min-height: 44px;
193
+ }
93
194
  }
94
195
  [data-slot="asset-tree-indicator"] {
95
196
  transition-property: var(--watt-motion-property, transform);
@@ -200,3 +301,31 @@
200
301
  display: none;
201
302
  }
202
303
  }
304
+
305
+ [data-slot="threshold-gauge"][data-density="compact"]
306
+ [data-slot="threshold-gauge-track"] {
307
+ height: 0.5rem;
308
+ }
309
+ [data-slot="time-range-selector"][data-density="compact"]
310
+ [data-slot="time-range-selector-option"] {
311
+ min-height: 2rem;
312
+ padding: 0.25rem 0.5rem;
313
+ font-size: 0.75rem;
314
+ }
315
+ [data-slot="category-chart"][data-density="compact"]
316
+ [data-slot="category-chart-legend"] {
317
+ gap: 0.5rem;
318
+ font-size: 0.75rem;
319
+ }
320
+ [data-slot="plant-fleet-map"][data-density="compact"]
321
+ [data-slot="plant-fleet-map-site"] {
322
+ min-width: 1.75rem;
323
+ min-height: 1.75rem;
324
+ }
325
+ @media (pointer: coarse) {
326
+ [data-slot="time-range-selector"] [data-slot="time-range-selector-option"],
327
+ [data-slot="plant-fleet-map"] [data-slot="plant-fleet-map-site"] {
328
+ min-width: 44px;
329
+ min-height: 44px;
330
+ }
331
+ }
@@ -0,0 +1,3 @@
1
+ import { type RefObject } from "react";
2
+ export declare function useVirtualFocus(host: RefObject<HTMLElement | null>, scroll: (index: number) => void): (index: number) => void;
3
+ //# sourceMappingURL=virtual-focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-focus.d.ts","sourceRoot":"","sources":["../src/virtual-focus.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1D,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACnC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,IAIvB,OAAO,MAAM,UAiBtB"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ export function useVirtualFocus(host, scroll) {
4
+ const frame = useRef(0);
5
+ useEffect(() => () => cancelAnimationFrame(frame.current), []);
6
+ return (index) => {
7
+ cancelAnimationFrame(frame.current);
8
+ scroll(index);
9
+ const started = performance.now();
10
+ const focus = () => {
11
+ const active = document.activeElement;
12
+ if (active && active !== document.body && !host.current?.contains(active))
13
+ return;
14
+ const element = host.current?.querySelector(`[data-focus-index="${index}"]`);
15
+ if (element)
16
+ element.focus({ preventScroll: true });
17
+ else if (performance.now() - started < 1000)
18
+ frame.current = requestAnimationFrame(focus);
19
+ };
20
+ frame.current = requestAnimationFrame(focus);
21
+ };
22
+ }
23
+ //# sourceMappingURL=virtual-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-focus.js","sourceRoot":"","sources":["../src/virtual-focus.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAE1D,MAAM,UAAU,eAAe,CAC7B,IAAmC,EACnC,MAA+B;IAE/B,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,KAAa,EAAE,EAAE;QACvB,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC;QACd,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC;YACtC,IAAI,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC;gBACvE,OAAO;YACT,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CACzC,sBAAsB,KAAK,IAAI,CAChC,CAAC;YACF,IAAI,OAAO;gBAAE,OAAO,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;iBAC/C,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,IAAI;gBACzC,KAAK,CAAC,OAAO,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACjD,CAAC,CAAC;QACF,KAAK,CAAC,OAAO,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@watt-ui/react",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "React components for energy and power dashboards",
5
5
  "homepage": "https://watt-ui.pages.dev",
6
6
  "license": "MIT",
@@ -67,6 +67,42 @@
67
67
  "./asset-tree": {
68
68
  "types": "./dist/components/asset-tree.d.ts",
69
69
  "import": "./dist/components/asset-tree.js"
70
+ },
71
+ "./equipment-card": {
72
+ "types": "./dist/components/equipment-card.d.ts",
73
+ "import": "./dist/components/equipment-card.js"
74
+ },
75
+ "./metric-trend": {
76
+ "types": "./dist/components/metric-trend.d.ts",
77
+ "import": "./dist/components/metric-trend.js"
78
+ },
79
+ "./threshold-gauge": {
80
+ "types": "./dist/components/threshold-gauge.d.ts",
81
+ "import": "./dist/components/threshold-gauge.js"
82
+ },
83
+ "./time-range-selector": {
84
+ "types": "./dist/components/time-range-selector.d.ts",
85
+ "import": "./dist/components/time-range-selector.js"
86
+ },
87
+ "./equipment-heatmap": {
88
+ "types": "./dist/components/equipment-heatmap.d.ts",
89
+ "import": "./dist/components/equipment-heatmap.js"
90
+ },
91
+ "./equipment-table": {
92
+ "types": "./dist/components/equipment-table.d.ts",
93
+ "import": "./dist/components/equipment-table.js"
94
+ },
95
+ "./operating-timeline": {
96
+ "types": "./dist/components/operating-timeline.d.ts",
97
+ "import": "./dist/components/operating-timeline.js"
98
+ },
99
+ "./category-chart": {
100
+ "types": "./dist/components/category-chart.d.ts",
101
+ "import": "./dist/components/category-chart.js"
102
+ },
103
+ "./plant-fleet-map": {
104
+ "types": "./dist/components/plant-fleet-map.d.ts",
105
+ "import": "./dist/components/plant-fleet-map.js"
70
106
  }
71
107
  },
72
108
  "scripts": {
@@ -0,0 +1,8 @@
1
+ import { init, use } from "echarts/core";
2
+ import { BarChart } from "echarts/charts";
3
+ import { GridComponent, TooltipComponent } from "echarts/components";
4
+ import { CanvasRenderer } from "echarts/renderers";
5
+ export function initCategoryCanvas(element: HTMLElement) {
6
+ use([BarChart, GridComponent, TooltipComponent, CanvasRenderer]);
7
+ return init(element, undefined, { renderer: "canvas" });
8
+ }
@@ -0,0 +1,158 @@
1
+ import type { EChartsCoreOption } from "echarts/core";
2
+ export interface CategorySeries {
3
+ id: string;
4
+ label: string;
5
+ unit: string;
6
+ values: readonly (number | null)[];
7
+ color?: string;
8
+ }
9
+ export interface CategoryChartConfig {
10
+ categories: readonly string[];
11
+ series: readonly CategorySeries[];
12
+ orientation?: "horizontal" | "vertical";
13
+ stacked?: boolean;
14
+ options?: EChartsCoreOption;
15
+ }
16
+ const record = (value: unknown): Record<string, unknown> =>
17
+ value && typeof value === "object" && !Array.isArray(value)
18
+ ? (value as Record<string, unknown>)
19
+ : {};
20
+ const at = (value: unknown) => record(Array.isArray(value) ? value[0] : value);
21
+ export function hasCategoryConflict(config: CategoryChartConfig) {
22
+ const options = config.options;
23
+ if (!options) return false;
24
+ const category = at(
25
+ config.orientation === "horizontal" ? options.yAxis : options.xAxis,
26
+ );
27
+ const value = at(
28
+ config.orientation === "horizontal" ? options.xAxis : options.yAxis,
29
+ );
30
+ return (
31
+ "data" in category ||
32
+ (category.type !== undefined && category.type !== "category") ||
33
+ (value.type !== undefined && value.type !== "value") ||
34
+ (value.name !== undefined && value.name !== config.series[0]?.unit) ||
35
+ at(options.legend).show === true ||
36
+ at(options.tooltip).showContent === true ||
37
+ (Array.isArray(options.series)
38
+ ? options.series
39
+ : options.series
40
+ ? [options.series]
41
+ : []
42
+ ).some((series) => {
43
+ const item = record(series);
44
+ return "data" in item || (item.type !== undefined && item.type !== "bar");
45
+ })
46
+ );
47
+ }
48
+ export function validateCategories(config: CategoryChartConfig) {
49
+ const ids = new Set<string>();
50
+ let unit: string | undefined;
51
+ for (const series of config.series) {
52
+ if (
53
+ !series.id ||
54
+ ids.has(series.id) ||
55
+ series.values.length !== config.categories.length
56
+ )
57
+ throw new Error(
58
+ "CategoryChart needs unique series IDs and one value per category",
59
+ );
60
+ if (unit !== undefined && unit !== series.unit)
61
+ throw new Error("CategoryChart series must share one unit");
62
+ ids.add(series.id);
63
+ unit = series.unit;
64
+ }
65
+ }
66
+ export function categoryOption(
67
+ config: CategoryChartConfig,
68
+ colors = { text: "#626b7b", border: "#e3e6ec", accent: "#355dff" },
69
+ ): EChartsCoreOption {
70
+ const horizontal = config.orientation === "horizontal";
71
+ const customCategory = at(
72
+ horizontal ? config.options?.yAxis : config.options?.xAxis,
73
+ );
74
+ const customValue = at(
75
+ horizontal ? config.options?.xAxis : config.options?.yAxis,
76
+ );
77
+ const category = {
78
+ ...customCategory,
79
+ type: "category",
80
+ data: config.categories,
81
+ axisLabel: {
82
+ color: colors.text,
83
+ hideOverlap: true,
84
+ ...record(customCategory.axisLabel),
85
+ },
86
+ axisLine: {
87
+ lineStyle: { color: colors.border },
88
+ ...record(customCategory.axisLine),
89
+ },
90
+ axisTick: { show: false, ...record(customCategory.axisTick) },
91
+ };
92
+ const value = {
93
+ ...customValue,
94
+ type: "value",
95
+ name: config.series[0]?.unit ?? "",
96
+ splitNumber: customValue.splitNumber ?? 4,
97
+ nameTextStyle: { color: colors.text, ...record(customValue.nameTextStyle) },
98
+ axisLabel: {
99
+ color: colors.text,
100
+ hideOverlap: true,
101
+ ...record(customValue.axisLabel),
102
+ },
103
+ splitLine: {
104
+ lineStyle: { color: colors.border },
105
+ ...record(customValue.splitLine),
106
+ },
107
+ };
108
+ const passthrough = { ...config.options };
109
+ delete passthrough.legend;
110
+ return {
111
+ ...passthrough,
112
+ animation: false,
113
+ grid: {
114
+ left: 12,
115
+ right: 20,
116
+ top: 30,
117
+ bottom: 40,
118
+ containLabel: true,
119
+ ...(config.options?.grid as object),
120
+ },
121
+ tooltip: {
122
+ ...at(config.options?.tooltip),
123
+ trigger: "axis",
124
+ showContent: false,
125
+ axisPointer: {
126
+ type: "shadow",
127
+ ...record(at(config.options?.tooltip).axisPointer),
128
+ },
129
+ },
130
+ xAxis: horizontal ? value : category,
131
+ yAxis: horizontal ? category : value,
132
+ series: config.series.map((series, index) => {
133
+ const custom = record(
134
+ Array.isArray(config.options?.series)
135
+ ? (config.options.series.find(
136
+ (item) => record(item).id === series.id,
137
+ ) ?? config.options.series[index])
138
+ : config.options?.series,
139
+ );
140
+ return {
141
+ ...custom,
142
+ id: series.id,
143
+ name: series.label,
144
+ type: "bar",
145
+ stack: config.stacked ? "total" : undefined,
146
+ data: series.values.map((value) =>
147
+ value !== null && Number.isFinite(value) ? value : null,
148
+ ),
149
+ itemStyle: {
150
+ ...record(custom.itemStyle),
151
+ color: series.color ?? colors.accent,
152
+ },
153
+ barMaxWidth: custom.barMaxWidth ?? 36,
154
+ emphasis: { focus: "series", ...record(custom.emphasis) },
155
+ };
156
+ }),
157
+ };
158
+ }
@@ -33,8 +33,7 @@ export type AssetTreeItem<T extends PlantAsset> =
33
33
  : AssetTreeItem<C>
34
34
  : never);
35
35
  export interface AssetTreeProps<T extends PlantAsset = PlantAsset>
36
- extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">,
37
- MotionProps {
36
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">, MotionProps {
38
37
  items: readonly T[];
39
38
  children: (row: AssetTreeRow<AssetTreeItem<T>>) => React.ReactNode;
40
39
  expandedIds?: string[];
@@ -188,7 +187,14 @@ function Root<T extends PlantAsset>(
188
187
  initialRect: { width: 0, height },
189
188
  getItemKey: (index) => visible[index]!.getId(),
190
189
  });
190
+ useEffect(() => {
191
+ virtualizer.measure();
192
+ }, [virtualizer, rowHeight]);
191
193
  virtualRef.current = virtualizer;
194
+ const virtualRows = virtualizer.getVirtualItems();
195
+ const focusIndex =
196
+ virtualRows.find((row) => visible[row.index]?.isFocused())?.index ??
197
+ virtualRows[0]?.index;
192
198
  const containerProps = tree.getContainerProps(props["aria-label"]);
193
199
  const hostRef = useComposedRefs(host, tree.registerElement, forwardedRef);
194
200
  const renderRow = (
@@ -200,6 +206,12 @@ function Root<T extends PlantAsset>(
200
206
  return (
201
207
  <div
202
208
  {...rowProps}
209
+ tabIndex={index === focusIndex ? 0 : -1}
210
+ onFocus={(event) => {
211
+ rowProps.onFocus?.(event);
212
+ if (event.target === event.currentTarget && !item.isFocused())
213
+ item.setFocused();
214
+ }}
203
215
  aria-label={undefined}
204
216
  key={item.getId()}
205
217
  data-slot="asset-tree-row"
@@ -229,13 +241,17 @@ function Root<T extends PlantAsset>(
229
241
  return (
230
242
  <MotionProvider motion={motion}>
231
243
  <div
232
- {...containerProps}
244
+ {...(items.length ? containerProps : { role: "group" })}
233
245
  {...props}
234
246
  ref={hostRef}
235
247
  data-slot="asset-tree"
236
248
  data-density={density}
237
249
  className={cx("watt-root overflow-auto text-sm", className)}
238
- style={{ height, ...style, ...motionStyle(motion) }}
250
+ style={{
251
+ height: items.length ? height : undefined,
252
+ ...style,
253
+ ...motionStyle(motion),
254
+ }}
239
255
  >
240
256
  {!items.length ? (
241
257
  empty
@@ -244,7 +260,7 @@ function Root<T extends PlantAsset>(
244
260
  role="presentation"
245
261
  style={{ height: virtualizer.getTotalSize(), position: "relative" }}
246
262
  >
247
- {virtualizer.getVirtualItems().map((row) => {
263
+ {virtualRows.map((row) => {
248
264
  const item = visible[row.index];
249
265
  return item && indexed.has(item.getId())
250
266
  ? renderRow(item, row.index, row.start)
@@ -62,6 +62,14 @@ const Track = forwardRef<HTMLElement, PartProps>(
62
62
  aria-valuemin={value === null ? undefined : 0}
63
63
  aria-valuemax={value === null ? undefined : 100}
64
64
  aria-valuenow={value ?? undefined}
65
+ aria-valuetext={value === null ? undefined : props["aria-valuetext"]}
66
+ aria-description={
67
+ value === null
68
+ ? (props["aria-valuetext"] ??
69
+ props["aria-description"] ??
70
+ "Unavailable")
71
+ : props["aria-description"]
72
+ }
65
73
  data-slot="battery-soc-track"
66
74
  className={cx(
67
75
  density === "compact" ? "h-2" : "h-4",