@neo4j-ndl/react-charts 1.1.1 → 1.1.2

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 (185) hide show
  1. package/lib/cjs/charts/Chart.js +56 -462
  2. package/lib/cjs/charts/Chart.js.map +1 -1
  3. package/lib/cjs/charts/ChartContainer.js +71 -0
  4. package/lib/cjs/charts/ChartContainer.js.map +1 -0
  5. package/lib/cjs/charts/ChartEmpty.js.map +1 -1
  6. package/lib/cjs/charts/ChartRender.js +97 -0
  7. package/lib/cjs/charts/ChartRender.js.map +1 -0
  8. package/lib/cjs/charts/ChartTooltip.js.map +1 -1
  9. package/lib/cjs/charts/Legend.js +32 -246
  10. package/lib/cjs/charts/Legend.js.map +1 -1
  11. package/lib/cjs/charts/LegendItem.js +68 -0
  12. package/lib/cjs/charts/LegendItem.js.map +1 -0
  13. package/lib/cjs/charts/LegendLayout.js +363 -0
  14. package/lib/cjs/charts/LegendLayout.js.map +1 -0
  15. package/lib/cjs/charts/hooks/use-chart-instance.js +133 -0
  16. package/lib/cjs/charts/hooks/use-chart-instance.js.map +1 -0
  17. package/lib/cjs/charts/hooks/use-chart-option.js +79 -0
  18. package/lib/cjs/charts/hooks/use-chart-option.js.map +1 -0
  19. package/lib/cjs/charts/hooks/use-chart-refs.js +52 -0
  20. package/lib/cjs/charts/hooks/use-chart-refs.js.map +1 -0
  21. package/lib/cjs/charts/hooks/use-chart-zoom.js +117 -0
  22. package/lib/cjs/charts/hooks/use-chart-zoom.js.map +1 -0
  23. package/lib/cjs/charts/hooks/use-legend-interactions.js +149 -0
  24. package/lib/cjs/charts/hooks/use-legend-interactions.js.map +1 -0
  25. package/lib/cjs/charts/hooks/use-legend-series.js +181 -0
  26. package/lib/cjs/charts/hooks/use-legend-series.js.map +1 -0
  27. package/lib/cjs/charts/hooks/use-legend-visibility.js +91 -0
  28. package/lib/cjs/charts/hooks/use-legend-visibility.js.map +1 -0
  29. package/lib/cjs/charts/index.js +1 -1
  30. package/lib/cjs/charts/index.js.map +1 -1
  31. package/lib/cjs/charts/tests/chart-test-utils.js +56 -9
  32. package/lib/cjs/charts/tests/chart-test-utils.js.map +1 -1
  33. package/lib/cjs/charts/themes/ndl-echarts-theme.js.map +1 -1
  34. package/lib/cjs/charts/{aria-description.js → utils/aria-description.js} +4 -45
  35. package/lib/cjs/charts/utils/aria-description.js.map +1 -0
  36. package/lib/cjs/charts/utils/build-chart-option.js +74 -0
  37. package/lib/cjs/charts/utils/build-chart-option.js.map +1 -0
  38. package/lib/cjs/charts/utils/chart-tooltip-formatter.js +86 -0
  39. package/lib/cjs/charts/utils/chart-tooltip-formatter.js.map +1 -0
  40. package/lib/cjs/charts/utils/chart-types.js.map +1 -0
  41. package/lib/cjs/charts/utils/defaults.js.map +1 -0
  42. package/lib/cjs/charts/{utils.js → utils/format-utils.js} +3 -19
  43. package/lib/cjs/charts/utils/format-utils.js.map +1 -0
  44. package/lib/cjs/charts/utils/legend-layout.js +65 -0
  45. package/lib/cjs/charts/utils/legend-layout.js.map +1 -0
  46. package/lib/cjs/charts/{legend-utils.js → utils/legend-utils.js} +1 -78
  47. package/lib/cjs/charts/utils/legend-utils.js.map +1 -0
  48. package/lib/cjs/charts/utils/threshold.js +114 -0
  49. package/lib/cjs/charts/utils/threshold.js.map +1 -0
  50. package/lib/cjs/charts/{user-option-utils.js → utils/user-option-utils.js} +7 -16
  51. package/lib/cjs/charts/utils/user-option-utils.js.map +1 -0
  52. package/lib/esm/charts/Chart.js +50 -457
  53. package/lib/esm/charts/Chart.js.map +1 -1
  54. package/lib/esm/charts/ChartContainer.js +67 -0
  55. package/lib/esm/charts/ChartContainer.js.map +1 -0
  56. package/lib/esm/charts/ChartEmpty.js.map +1 -1
  57. package/lib/esm/charts/ChartRender.js +93 -0
  58. package/lib/esm/charts/ChartRender.js.map +1 -0
  59. package/lib/esm/charts/ChartTooltip.js.map +1 -1
  60. package/lib/esm/charts/Legend.js +32 -243
  61. package/lib/esm/charts/Legend.js.map +1 -1
  62. package/lib/esm/charts/LegendItem.js +61 -0
  63. package/lib/esm/charts/LegendItem.js.map +1 -0
  64. package/lib/esm/charts/LegendLayout.js +323 -0
  65. package/lib/esm/charts/LegendLayout.js.map +1 -0
  66. package/lib/esm/charts/hooks/use-chart-instance.js +128 -0
  67. package/lib/esm/charts/hooks/use-chart-instance.js.map +1 -0
  68. package/lib/esm/charts/hooks/use-chart-option.js +76 -0
  69. package/lib/esm/charts/hooks/use-chart-option.js.map +1 -0
  70. package/lib/esm/charts/hooks/use-chart-refs.js +48 -0
  71. package/lib/esm/charts/hooks/use-chart-refs.js.map +1 -0
  72. package/lib/esm/charts/hooks/use-chart-zoom.js +114 -0
  73. package/lib/esm/charts/hooks/use-chart-zoom.js.map +1 -0
  74. package/lib/esm/charts/hooks/use-legend-interactions.js +145 -0
  75. package/lib/esm/charts/hooks/use-legend-interactions.js.map +1 -0
  76. package/lib/esm/charts/hooks/use-legend-series.js +178 -0
  77. package/lib/esm/charts/hooks/use-legend-series.js.map +1 -0
  78. package/lib/esm/charts/hooks/use-legend-visibility.js +87 -0
  79. package/lib/esm/charts/hooks/use-legend-visibility.js.map +1 -0
  80. package/lib/esm/charts/index.js +1 -1
  81. package/lib/esm/charts/index.js.map +1 -1
  82. package/lib/esm/charts/tests/chart-test-utils.js +53 -8
  83. package/lib/esm/charts/tests/chart-test-utils.js.map +1 -1
  84. package/lib/esm/charts/themes/ndl-echarts-theme.js.map +1 -1
  85. package/lib/esm/charts/{aria-description.js → utils/aria-description.js} +4 -45
  86. package/lib/esm/charts/utils/aria-description.js.map +1 -0
  87. package/lib/esm/charts/utils/build-chart-option.js +69 -0
  88. package/lib/esm/charts/utils/build-chart-option.js.map +1 -0
  89. package/lib/esm/charts/utils/chart-tooltip-formatter.js +82 -0
  90. package/lib/esm/charts/utils/chart-tooltip-formatter.js.map +1 -0
  91. package/lib/esm/charts/utils/chart-types.js.map +1 -0
  92. package/lib/esm/charts/utils/defaults.js.map +1 -0
  93. package/lib/esm/charts/{utils.js → utils/format-utils.js} +2 -17
  94. package/lib/esm/charts/utils/format-utils.js.map +1 -0
  95. package/lib/esm/charts/utils/legend-layout.js +59 -0
  96. package/lib/esm/charts/utils/legend-layout.js.map +1 -0
  97. package/lib/esm/charts/{legend-utils.js → utils/legend-utils.js} +1 -75
  98. package/lib/esm/charts/utils/legend-utils.js.map +1 -0
  99. package/lib/esm/charts/utils/threshold.js +106 -0
  100. package/lib/esm/charts/utils/threshold.js.map +1 -0
  101. package/lib/esm/charts/{user-option-utils.js → utils/user-option-utils.js} +5 -14
  102. package/lib/esm/charts/utils/user-option-utils.js.map +1 -0
  103. package/lib/types/charts/Chart.d.ts +2 -2
  104. package/lib/types/charts/Chart.d.ts.map +1 -1
  105. package/lib/{esm/charts/types.js → types/charts/ChartContainer.d.ts} +14 -1
  106. package/lib/types/charts/ChartContainer.d.ts.map +1 -0
  107. package/lib/types/charts/ChartEmpty.d.ts +1 -1
  108. package/lib/types/charts/ChartEmpty.d.ts.map +1 -1
  109. package/lib/types/charts/ChartRender.d.ts +36 -0
  110. package/lib/types/charts/ChartRender.d.ts.map +1 -0
  111. package/lib/types/charts/ChartTooltip.d.ts +1 -1
  112. package/lib/types/charts/ChartTooltip.d.ts.map +1 -1
  113. package/lib/types/charts/Legend.d.ts +15 -3
  114. package/lib/types/charts/Legend.d.ts.map +1 -1
  115. package/lib/{cjs/charts/types.js → types/charts/LegendItem.d.ts} +10 -3
  116. package/lib/types/charts/LegendItem.d.ts.map +1 -0
  117. package/lib/types/charts/LegendLayout.d.ts +38 -0
  118. package/lib/types/charts/LegendLayout.d.ts.map +1 -0
  119. package/lib/types/charts/hooks/use-chart-instance.d.ts +62 -0
  120. package/lib/types/charts/hooks/use-chart-instance.d.ts.map +1 -0
  121. package/lib/types/charts/hooks/use-chart-option.d.ts +48 -0
  122. package/lib/types/charts/hooks/use-chart-option.d.ts.map +1 -0
  123. package/lib/types/charts/hooks/use-chart-refs.d.ts +38 -0
  124. package/lib/types/charts/hooks/use-chart-refs.d.ts.map +1 -0
  125. package/lib/types/charts/hooks/use-chart-zoom.d.ts +36 -0
  126. package/lib/types/charts/hooks/use-chart-zoom.d.ts.map +1 -0
  127. package/lib/types/charts/hooks/use-legend-interactions.d.ts +56 -0
  128. package/lib/types/charts/hooks/use-legend-interactions.d.ts.map +1 -0
  129. package/lib/types/charts/hooks/use-legend-series.d.ts +42 -0
  130. package/lib/types/charts/hooks/use-legend-series.d.ts.map +1 -0
  131. package/lib/types/charts/hooks/use-legend-visibility.d.ts +24 -0
  132. package/lib/types/charts/hooks/use-legend-visibility.d.ts.map +1 -0
  133. package/lib/types/charts/index.d.ts +2 -2
  134. package/lib/types/charts/index.d.ts.map +1 -1
  135. package/lib/types/charts/tests/chart-test-utils.d.ts +7 -1
  136. package/lib/types/charts/tests/chart-test-utils.d.ts.map +1 -1
  137. package/lib/types/charts/themes/ndl-echarts-theme.d.ts +1 -1
  138. package/lib/types/charts/themes/ndl-echarts-theme.d.ts.map +1 -1
  139. package/lib/types/charts/utils/aria-description.d.ts.map +1 -0
  140. package/lib/types/charts/utils/build-chart-option.d.ts +52 -0
  141. package/lib/types/charts/utils/build-chart-option.d.ts.map +1 -0
  142. package/lib/types/charts/utils/chart-tooltip-formatter.d.ts +37 -0
  143. package/lib/types/charts/utils/chart-tooltip-formatter.d.ts.map +1 -0
  144. package/lib/types/charts/{chart-types.d.ts → utils/chart-types.d.ts} +23 -22
  145. package/lib/types/charts/utils/chart-types.d.ts.map +1 -0
  146. package/lib/types/charts/utils/defaults.d.ts.map +1 -0
  147. package/lib/types/charts/{utils.d.ts → utils/format-utils.d.ts} +2 -4
  148. package/lib/types/charts/utils/format-utils.d.ts.map +1 -0
  149. package/lib/types/charts/utils/legend-layout.d.ts +37 -0
  150. package/lib/types/charts/utils/legend-layout.d.ts.map +1 -0
  151. package/lib/types/charts/{legend-utils.d.ts → utils/legend-utils.d.ts} +1 -11
  152. package/lib/types/charts/utils/legend-utils.d.ts.map +1 -0
  153. package/lib/types/charts/utils/threshold.d.ts +45 -0
  154. package/lib/types/charts/utils/threshold.d.ts.map +1 -0
  155. package/lib/types/charts/utils/user-option-utils.d.ts.map +1 -0
  156. package/package.json +3 -3
  157. package/lib/cjs/charts/aria-description.js.map +0 -1
  158. package/lib/cjs/charts/chart-types.js.map +0 -1
  159. package/lib/cjs/charts/defaults.js.map +0 -1
  160. package/lib/cjs/charts/legend-utils.js.map +0 -1
  161. package/lib/cjs/charts/types.js.map +0 -1
  162. package/lib/cjs/charts/user-option-utils.js.map +0 -1
  163. package/lib/cjs/charts/utils.js.map +0 -1
  164. package/lib/esm/charts/aria-description.js.map +0 -1
  165. package/lib/esm/charts/chart-types.js.map +0 -1
  166. package/lib/esm/charts/defaults.js.map +0 -1
  167. package/lib/esm/charts/legend-utils.js.map +0 -1
  168. package/lib/esm/charts/types.js.map +0 -1
  169. package/lib/esm/charts/user-option-utils.js.map +0 -1
  170. package/lib/esm/charts/utils.js.map +0 -1
  171. package/lib/types/charts/aria-description.d.ts.map +0 -1
  172. package/lib/types/charts/chart-types.d.ts.map +0 -1
  173. package/lib/types/charts/defaults.d.ts.map +0 -1
  174. package/lib/types/charts/legend-utils.d.ts.map +0 -1
  175. package/lib/types/charts/types.d.ts +0 -44
  176. package/lib/types/charts/types.d.ts.map +0 -1
  177. package/lib/types/charts/user-option-utils.d.ts.map +0 -1
  178. package/lib/types/charts/utils.d.ts.map +0 -1
  179. /package/lib/cjs/charts/{chart-types.js → utils/chart-types.js} +0 -0
  180. /package/lib/cjs/charts/{defaults.js → utils/defaults.js} +0 -0
  181. /package/lib/esm/charts/{chart-types.js → utils/chart-types.js} +0 -0
  182. /package/lib/esm/charts/{defaults.js → utils/defaults.js} +0 -0
  183. /package/lib/types/charts/{aria-description.d.ts → utils/aria-description.d.ts} +0 -0
  184. /package/lib/types/charts/{defaults.d.ts → utils/defaults.d.ts} +0 -0
  185. /package/lib/types/charts/{user-option-utils.d.ts → utils/user-option-utils.d.ts} +0 -0
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __rest = (this && this.__rest) || function (s, e) {
36
+ var t = {};
37
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
38
+ t[p] = s[p];
39
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
40
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
41
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
42
+ t[p[i]] = s[p[i]];
43
+ }
44
+ return t;
45
+ };
46
+ var __importDefault = (this && this.__importDefault) || function (mod) {
47
+ return (mod && mod.__esModule) ? mod : { "default": mod };
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.LegendLayout = void 0;
51
+ const jsx_runtime_1 = require("react/jsx-runtime");
52
+ /**
53
+ *
54
+ * Copyright (c) "Neo4j"
55
+ * Neo4j Sweden AB [http://neo4j.com]
56
+ *
57
+ * This file is part of Neo4j.
58
+ *
59
+ * Neo4j is free software: you can redistribute it and/or modify
60
+ * it under the terms of the GNU General Public License as published by
61
+ * the Free Software Foundation, either version 3 of the License, or
62
+ * (at your option) any later version.
63
+ *
64
+ * This program is distributed in the hope that it will be useful,
65
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
66
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67
+ * GNU General Public License for more details.
68
+ *
69
+ * You should have received a copy of the GNU General Public License
70
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
71
+ */
72
+ const react_1 = require("@neo4j-ndl/react");
73
+ const classnames_1 = __importDefault(require("classnames"));
74
+ const react_2 = __importStar(require("react"));
75
+ const legend_layout_1 = require("./utils/legend-layout");
76
+ const getLegendElementsContentHeight = (legendContent, elements) => {
77
+ const legendContentStyle = getComputedStyle(legendContent);
78
+ const paddingBottom = parseFloat(legendContentStyle.paddingBottom) || 0;
79
+ const elementsBottom = Math.max(...elements.map((element) => element.offsetTop + element.offsetHeight));
80
+ return elementsBottom + paddingBottom;
81
+ };
82
+ const getCollapsedLegendContentHeight = ({ availableRowWidth, elements, legendContent, }) => {
83
+ const legendContentStyle = getComputedStyle(legendContent);
84
+ const paddingTop = parseFloat(legendContentStyle.paddingTop) || 0;
85
+ const paddingBottom = parseFloat(legendContentStyle.paddingBottom) || 0;
86
+ const rowGap = parseFloat(legendContentStyle.rowGap) || 0;
87
+ let currentRowHeight = 0;
88
+ let currentRowWidth = 0;
89
+ const rowHeights = [];
90
+ elements.forEach((element) => {
91
+ const elementWidth = (0, legend_layout_1.getComputedElementWidth)(element);
92
+ const elementHeight = element.getBoundingClientRect().height;
93
+ const shouldWrap = currentRowWidth > 0 && currentRowWidth + elementWidth > availableRowWidth;
94
+ if (shouldWrap) {
95
+ rowHeights.push(currentRowHeight);
96
+ currentRowHeight = 0;
97
+ currentRowWidth = 0;
98
+ }
99
+ currentRowWidth += elementWidth;
100
+ currentRowHeight = Math.max(currentRowHeight, elementHeight);
101
+ });
102
+ if (currentRowHeight > 0) {
103
+ rowHeights.push(currentRowHeight);
104
+ }
105
+ const rowsHeight = rowHeights.reduce((height, rowHeight) => {
106
+ return height + rowHeight;
107
+ }, 0);
108
+ return (paddingTop +
109
+ rowsHeight +
110
+ rowGap * Math.max(rowHeights.length - 1, 0) +
111
+ paddingBottom);
112
+ };
113
+ /**
114
+ * Tracks legend container width for responsive collapsed-layout recalculation.
115
+ *
116
+ * The width is kept in state so `useCollapsibleLegendLayout` can re-measure item
117
+ * wrapping whenever the available legend width changes.
118
+ */
119
+ const useLegendContainerWidth = (legendContainerRef) => {
120
+ const [legendWidth, setLegendWidth] = (0, react_2.useState)(Infinity);
121
+ const updateLegendWidth = (0, react_2.useCallback)(() => {
122
+ var _a;
123
+ const nextWidth = (_a = legendContainerRef.current) === null || _a === void 0 ? void 0 : _a.clientWidth;
124
+ if (nextWidth === undefined) {
125
+ return;
126
+ }
127
+ setLegendWidth(nextWidth);
128
+ }, [legendContainerRef]);
129
+ (0, react_2.useEffect)(() => {
130
+ updateLegendWidth();
131
+ const legendContainer = legendContainerRef.current;
132
+ if (!legendContainer) {
133
+ return;
134
+ }
135
+ const resizeObserver = new ResizeObserver(updateLegendWidth);
136
+ resizeObserver.observe(legendContainer);
137
+ return () => {
138
+ resizeObserver.disconnect();
139
+ };
140
+ }, [legendContainerRef, updateLegendWidth]);
141
+ return legendWidth;
142
+ };
143
+ /**
144
+ * Owns the collapsed, measuring, and expanded layout state for wrapping legends.
145
+ *
146
+ * Collapsed legends only render the visible subset of items. When the item count
147
+ * or container width changes, the hook temporarily renders every item in an
148
+ * invisible measuring pass so it can calculate wrapping, overflow, and the next
149
+ * visible item count. The last collapsed height is preserved during that pass so
150
+ * the chart area does not shrink while the hidden full legend is being measured.
151
+ */
152
+ const useCollapsibleLegendLayout = ({ itemCount, legendContainerRef, legendContentRef, legendMeasurementRef, viewMoreButtonMeasurementRef, }) => {
153
+ const [isExpanded, setIsExpanded] = (0, react_2.useState)(false);
154
+ const [isMeasuring, setIsMeasuring] = (0, react_2.useState)(false);
155
+ const [layoutSnapshot, setLayoutSnapshot] = (0, react_2.useState)({
156
+ contentHeight: 0,
157
+ hasOverflow: false,
158
+ visibleItemCount: itemCount,
159
+ });
160
+ const skipNextCollapseMeasurementRef = (0, react_2.useRef)(false);
161
+ const legendWidth = useLegendContainerWidth(legendContainerRef);
162
+ const updateVisibleItems = (0, react_2.useCallback)(() => {
163
+ var _a;
164
+ const legendContent = legendMeasurementRef.current;
165
+ const viewMoreButton = viewMoreButtonMeasurementRef.current;
166
+ if (!legendContent || !viewMoreButton) {
167
+ setIsMeasuring(false);
168
+ return;
169
+ }
170
+ const legendItems = Array.from(legendContent.querySelectorAll('.ndl-chart-legend-item')).filter((element) => element.offsetParent !== null);
171
+ if (legendItems.length === 0) {
172
+ setLayoutSnapshot((snapshot) => (Object.assign(Object.assign({}, snapshot), { contentHeight: 0, hasOverflow: false, visibleItemCount: itemCount })));
173
+ setIsMeasuring(false);
174
+ return;
175
+ }
176
+ const rowTops = Array.from(new Set(legendItems.map((element) => element.offsetTop))).sort((a, b) => a - b);
177
+ const nextLegendContentHeight = legendContent.scrollHeight;
178
+ const nextLegendItemsContentHeight = getLegendElementsContentHeight(legendContent, legendItems);
179
+ if (rowTops.length <= legend_layout_1.COLLAPSED_LEGEND_ROWS) {
180
+ setLayoutSnapshot((snapshot) => (Object.assign(Object.assign({}, snapshot), { contentHeight: nextLegendItemsContentHeight, hasOverflow: false, visibleItemCount: itemCount })));
181
+ setIsMeasuring(false);
182
+ return;
183
+ }
184
+ const legendContainer = legendContainerRef.current;
185
+ const legendContentStyle = getComputedStyle(legendContent);
186
+ const availableRowWidth = ((_a = legendContainer === null || legendContainer === void 0 ? void 0 : legendContainer.clientWidth) !== null && _a !== void 0 ? _a : legendContent.clientWidth) -
187
+ (parseFloat(legendContentStyle.paddingLeft) || 0) -
188
+ (parseFloat(legendContentStyle.paddingRight) || 0);
189
+ const viewMoreButtonWidth = (0, legend_layout_1.getComputedElementWidth)(viewMoreButton);
190
+ const legendItemWidths = legendItems.map(legend_layout_1.getComputedElementWidth);
191
+ const nextVisibleItemCount = (0, legend_layout_1.getCollapsedVisibleItemCount)(legendItemWidths, availableRowWidth, viewMoreButtonWidth);
192
+ const nextCollapsedContentHeight = getCollapsedLegendContentHeight({
193
+ availableRowWidth,
194
+ elements: [...legendItems.slice(0, nextVisibleItemCount), viewMoreButton],
195
+ legendContent,
196
+ });
197
+ setLayoutSnapshot((snapshot) => (Object.assign(Object.assign({}, snapshot), { collapsedHeight: Math.min(nextCollapsedContentHeight, legend_layout_1.COLLAPSED_LEGEND_MAX_HEIGHT), contentHeight: nextLegendContentHeight, hasOverflow: nextVisibleItemCount < itemCount, visibleItemCount: nextVisibleItemCount })));
198
+ setIsMeasuring(false);
199
+ }, [
200
+ itemCount,
201
+ legendContainerRef,
202
+ legendMeasurementRef,
203
+ viewMoreButtonMeasurementRef,
204
+ ]);
205
+ (0, react_2.useEffect)(() => {
206
+ setIsExpanded(false);
207
+ }, [itemCount]);
208
+ (0, react_2.useEffect)(() => {
209
+ if (isExpanded) {
210
+ setIsMeasuring(false);
211
+ return;
212
+ }
213
+ if (skipNextCollapseMeasurementRef.current) {
214
+ skipNextCollapseMeasurementRef.current = false;
215
+ setIsMeasuring(false);
216
+ return;
217
+ }
218
+ setIsMeasuring(true);
219
+ }, [isExpanded, itemCount, legendWidth]);
220
+ (0, react_2.useEffect)(() => {
221
+ const isMeasuringCollapsed = !isExpanded && isMeasuring;
222
+ const animationFrame = requestAnimationFrame(() => {
223
+ if (isMeasuringCollapsed) {
224
+ updateVisibleItems();
225
+ }
226
+ else {
227
+ setLayoutSnapshot((snapshot) => {
228
+ var _a, _b;
229
+ return (Object.assign(Object.assign({}, snapshot), { contentHeight: (_b = (_a = legendContentRef.current) === null || _a === void 0 ? void 0 : _a.scrollHeight) !== null && _b !== void 0 ? _b : 0 }));
230
+ });
231
+ if (!isMeasuring && !isExpanded && layoutSnapshot.hasOverflow) {
232
+ setLayoutSnapshot((snapshot) => {
233
+ var _a;
234
+ return (Object.assign(Object.assign({}, snapshot), { collapsedHeight: (_a = legendContentRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().height }));
235
+ });
236
+ }
237
+ }
238
+ });
239
+ return () => {
240
+ cancelAnimationFrame(animationFrame);
241
+ };
242
+ }, [
243
+ isExpanded,
244
+ isMeasuring,
245
+ legendContentRef,
246
+ layoutSnapshot.hasOverflow,
247
+ updateVisibleItems,
248
+ ]);
249
+ const expandLegend = (0, react_2.useCallback)(() => {
250
+ setIsMeasuring(false);
251
+ setIsExpanded(true);
252
+ }, []);
253
+ const collapseLegend = (0, react_2.useCallback)(() => {
254
+ skipNextCollapseMeasurementRef.current = true;
255
+ setIsExpanded(false);
256
+ }, []);
257
+ return {
258
+ collapseLegend,
259
+ expandLegend,
260
+ isExpanded,
261
+ isMeasuring,
262
+ layoutSnapshot,
263
+ };
264
+ };
265
+ /**
266
+ * Renders the responsive legend layout shell.
267
+ *
268
+ * The component switches between a two-row collapsed view, an invisible
269
+ * measurement view, and an expanded scrollable view while keeping the
270
+ * "View more"/"View less" action wired to the measured overflow state.
271
+ */
272
+ const LegendLayout = (_a) => {
273
+ var { children, className, isLayoutReady = true, itemCount, onLayoutReady, htmlAttributes } = _a, restProps = __rest(_a, ["children", "className", "isLayoutReady", "itemCount", "onLayoutReady", "htmlAttributes"]);
274
+ const legendContainerRef = (0, react_2.useRef)(null);
275
+ const legendContentRef = (0, react_2.useRef)(null);
276
+ const legendMeasurementRef = (0, react_2.useRef)(null);
277
+ const pendingFocusLegendItemIndexRef = (0, react_2.useRef)(null);
278
+ const viewMoreButtonMeasurementRef = (0, react_2.useRef)(null);
279
+ const { collapseLegend, expandLegend, isExpanded, isMeasuring, layoutSnapshot, } = useCollapsibleLegendLayout({
280
+ itemCount,
281
+ legendContainerRef,
282
+ legendContentRef,
283
+ legendMeasurementRef,
284
+ viewMoreButtonMeasurementRef,
285
+ });
286
+ const childList = react_2.default.Children.toArray(children);
287
+ const childrenToRender = !isExpanded
288
+ ? childList.slice(0, layoutSnapshot.visibleItemCount)
289
+ : childList;
290
+ const legendHeight = !isMeasuring && !isExpanded && layoutSnapshot.hasOverflow
291
+ ? layoutSnapshot.collapsedHeight
292
+ : !isMeasuring &&
293
+ !layoutSnapshot.hasOverflow &&
294
+ layoutSnapshot.contentHeight
295
+ ? layoutSnapshot.contentHeight
296
+ : undefined;
297
+ (0, react_2.useEffect)(() => {
298
+ if (!isMeasuring && layoutSnapshot.contentHeight > 0) {
299
+ let secondFrame;
300
+ const firstFrame = requestAnimationFrame(() => {
301
+ secondFrame = requestAnimationFrame(() => {
302
+ onLayoutReady === null || onLayoutReady === void 0 ? void 0 : onLayoutReady();
303
+ });
304
+ });
305
+ return () => {
306
+ cancelAnimationFrame(firstFrame);
307
+ if (secondFrame !== undefined) {
308
+ cancelAnimationFrame(secondFrame);
309
+ }
310
+ };
311
+ }
312
+ }, [isMeasuring, layoutSnapshot.contentHeight, onLayoutReady]);
313
+ (0, react_2.useEffect)(() => {
314
+ var _a, _b;
315
+ const pendingFocusLegendItemIndex = pendingFocusLegendItemIndexRef.current;
316
+ if (!isExpanded || pendingFocusLegendItemIndex === null) {
317
+ return;
318
+ }
319
+ const legendItems = (_a = legendContentRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.ndl-chart-legend-item');
320
+ (_b = legendItems === null || legendItems === void 0 ? void 0 : legendItems[pendingFocusLegendItemIndex]) === null || _b === void 0 ? void 0 : _b.focus();
321
+ pendingFocusLegendItemIndexRef.current = null;
322
+ }, [isExpanded]);
323
+ const handleLegendActionClick = (0, react_2.useCallback)(() => {
324
+ if (isExpanded) {
325
+ collapseLegend();
326
+ return;
327
+ }
328
+ pendingFocusLegendItemIndexRef.current = layoutSnapshot.visibleItemCount;
329
+ expandLegend();
330
+ }, [
331
+ collapseLegend,
332
+ expandLegend,
333
+ isExpanded,
334
+ layoutSnapshot.visibleItemCount,
335
+ ]);
336
+ const classes = (0, classnames_1.default)('ndl-chart-legend', {
337
+ 'ndl-chart-legend-calculating': !isExpanded && isMeasuring,
338
+ 'ndl-chart-legend-collapsed': layoutSnapshot.hasOverflow && !isExpanded,
339
+ 'ndl-chart-legend-expanded': layoutSnapshot.hasOverflow && isExpanded,
340
+ 'ndl-chart-legend-hidden': !isLayoutReady,
341
+ 'ndl-chart-legend-scrollable': layoutSnapshot.hasOverflow &&
342
+ isExpanded &&
343
+ layoutSnapshot.contentHeight > legend_layout_1.COLLAPSED_LEGEND_MAX_HEIGHT,
344
+ 'ndl-chart-legend-wrapping': true,
345
+ }, className);
346
+ return ((0, jsx_runtime_1.jsx)("div", Object.assign({ ref: legendContainerRef, className: "ndl-chart-legend-container" }, restProps, htmlAttributes, { children: (0, jsx_runtime_1.jsxs)("div", { className: classes, ref: legendContentRef, style: {
347
+ height: legendHeight,
348
+ maxHeight: layoutSnapshot.hasOverflow && isExpanded
349
+ ? 'var(--ndl-chart-legend-expanded-max-height)'
350
+ : layoutSnapshot.hasOverflow
351
+ ? legend_layout_1.COLLAPSED_LEGEND_MAX_HEIGHT
352
+ : undefined,
353
+ }, children: [childrenToRender, layoutSnapshot.hasOverflow && ((0, jsx_runtime_1.jsx)(react_1.TextLink, { as: "button", className: "ndl-chart-legend-action", htmlAttributes: {
354
+ 'aria-expanded': isExpanded,
355
+ onClick: handleLegendActionClick,
356
+ type: 'button',
357
+ }, children: isExpanded ? 'View less' : 'View more' })), isMeasuring && ((0, jsx_runtime_1.jsxs)("div", { "aria-hidden": "true", className: "ndl-chart-legend-measurement", ref: legendMeasurementRef, children: [childList, (0, jsx_runtime_1.jsx)(react_1.TextLink, { as: "button", ref: viewMoreButtonMeasurementRef, className: "ndl-chart-legend-action", htmlAttributes: {
358
+ tabIndex: -1,
359
+ type: 'button',
360
+ }, children: "View more" })] }))] }) })));
361
+ };
362
+ exports.LegendLayout = LegendLayout;
363
+ //# sourceMappingURL=LegendLayout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LegendLayout.js","sourceRoot":"","sources":["../../../src/charts/LegendLayout.tsx"],"names":[],"mappings":"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","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { TextLink } from '@neo4j-ndl/react';\nimport classNames from 'classnames';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { type CommonProps } from './utils/chart-types';\nimport {\n COLLAPSED_LEGEND_MAX_HEIGHT,\n COLLAPSED_LEGEND_ROWS,\n getCollapsedVisibleItemCount,\n getComputedElementWidth,\n} from './utils/legend-layout';\n\ntype LegendLayoutProps = {\n children: React.ReactNode;\n isLayoutReady?: boolean;\n itemCount: number;\n onLayoutReady?: () => void;\n};\n\ntype LegendLayoutSnapshot = {\n collapsedHeight?: number;\n contentHeight: number;\n hasOverflow: boolean;\n visibleItemCount: number;\n};\n\nconst getLegendElementsContentHeight = (\n legendContent: HTMLElement,\n elements: HTMLElement[],\n) => {\n const legendContentStyle = getComputedStyle(legendContent);\n const paddingBottom = parseFloat(legendContentStyle.paddingBottom) || 0;\n const elementsBottom = Math.max(\n ...elements.map((element) => element.offsetTop + element.offsetHeight),\n );\n\n return elementsBottom + paddingBottom;\n};\n\nconst getCollapsedLegendContentHeight = ({\n availableRowWidth,\n elements,\n legendContent,\n}: {\n availableRowWidth: number;\n elements: HTMLElement[];\n legendContent: HTMLElement;\n}) => {\n const legendContentStyle = getComputedStyle(legendContent);\n const paddingTop = parseFloat(legendContentStyle.paddingTop) || 0;\n const paddingBottom = parseFloat(legendContentStyle.paddingBottom) || 0;\n const rowGap = parseFloat(legendContentStyle.rowGap) || 0;\n let currentRowHeight = 0;\n let currentRowWidth = 0;\n const rowHeights: number[] = [];\n\n elements.forEach((element) => {\n const elementWidth = getComputedElementWidth(element);\n const elementHeight = element.getBoundingClientRect().height;\n const shouldWrap =\n currentRowWidth > 0 && currentRowWidth + elementWidth > availableRowWidth;\n\n if (shouldWrap) {\n rowHeights.push(currentRowHeight);\n currentRowHeight = 0;\n currentRowWidth = 0;\n }\n\n currentRowWidth += elementWidth;\n currentRowHeight = Math.max(currentRowHeight, elementHeight);\n });\n\n if (currentRowHeight > 0) {\n rowHeights.push(currentRowHeight);\n }\n\n const rowsHeight = rowHeights.reduce((height, rowHeight) => {\n return height + rowHeight;\n }, 0);\n\n return (\n paddingTop +\n rowsHeight +\n rowGap * Math.max(rowHeights.length - 1, 0) +\n paddingBottom\n );\n};\n\n/**\n * Tracks legend container width for responsive collapsed-layout recalculation.\n *\n * The width is kept in state so `useCollapsibleLegendLayout` can re-measure item\n * wrapping whenever the available legend width changes.\n */\nconst useLegendContainerWidth = (\n legendContainerRef: React.RefObject<HTMLDivElement | null>,\n) => {\n const [legendWidth, setLegendWidth] = useState(Infinity);\n\n const updateLegendWidth = useCallback(() => {\n const nextWidth = legendContainerRef.current?.clientWidth;\n if (nextWidth === undefined) {\n return;\n }\n\n setLegendWidth(nextWidth);\n }, [legendContainerRef]);\n\n useEffect(() => {\n updateLegendWidth();\n\n const legendContainer = legendContainerRef.current;\n if (!legendContainer) {\n return;\n }\n\n const resizeObserver = new ResizeObserver(updateLegendWidth);\n resizeObserver.observe(legendContainer);\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [legendContainerRef, updateLegendWidth]);\n\n return legendWidth;\n};\n\n/**\n * Owns the collapsed, measuring, and expanded layout state for wrapping legends.\n *\n * Collapsed legends only render the visible subset of items. When the item count\n * or container width changes, the hook temporarily renders every item in an\n * invisible measuring pass so it can calculate wrapping, overflow, and the next\n * visible item count. The last collapsed height is preserved during that pass so\n * the chart area does not shrink while the hidden full legend is being measured.\n */\nconst useCollapsibleLegendLayout = ({\n itemCount,\n legendContainerRef,\n legendContentRef,\n legendMeasurementRef,\n viewMoreButtonMeasurementRef,\n}: {\n itemCount: number;\n legendContainerRef: React.RefObject<HTMLDivElement | null>;\n legendContentRef: React.RefObject<HTMLDivElement | null>;\n legendMeasurementRef: React.RefObject<HTMLDivElement | null>;\n viewMoreButtonMeasurementRef: React.RefObject<HTMLButtonElement | null>;\n}) => {\n const [isExpanded, setIsExpanded] = useState(false);\n const [isMeasuring, setIsMeasuring] = useState(false);\n const [layoutSnapshot, setLayoutSnapshot] = useState<LegendLayoutSnapshot>({\n contentHeight: 0,\n hasOverflow: false,\n visibleItemCount: itemCount,\n });\n const skipNextCollapseMeasurementRef = useRef(false);\n const legendWidth = useLegendContainerWidth(legendContainerRef);\n\n const updateVisibleItems = useCallback(() => {\n const legendContent = legendMeasurementRef.current;\n const viewMoreButton = viewMoreButtonMeasurementRef.current;\n if (!legendContent || !viewMoreButton) {\n setIsMeasuring(false);\n return;\n }\n\n const legendItems = Array.from(\n legendContent.querySelectorAll<HTMLElement>('.ndl-chart-legend-item'),\n ).filter((element) => element.offsetParent !== null);\n\n if (legendItems.length === 0) {\n setLayoutSnapshot((snapshot) => ({\n ...snapshot,\n contentHeight: 0,\n hasOverflow: false,\n visibleItemCount: itemCount,\n }));\n setIsMeasuring(false);\n return;\n }\n\n const rowTops = Array.from(\n new Set(legendItems.map((element) => element.offsetTop)),\n ).sort((a, b) => a - b);\n const nextLegendContentHeight = legendContent.scrollHeight;\n const nextLegendItemsContentHeight = getLegendElementsContentHeight(\n legendContent,\n legendItems,\n );\n\n if (rowTops.length <= COLLAPSED_LEGEND_ROWS) {\n setLayoutSnapshot((snapshot) => ({\n ...snapshot,\n contentHeight: nextLegendItemsContentHeight,\n hasOverflow: false,\n visibleItemCount: itemCount,\n }));\n setIsMeasuring(false);\n return;\n }\n\n const legendContainer = legendContainerRef.current;\n const legendContentStyle = getComputedStyle(legendContent);\n const availableRowWidth =\n (legendContainer?.clientWidth ?? legendContent.clientWidth) -\n (parseFloat(legendContentStyle.paddingLeft) || 0) -\n (parseFloat(legendContentStyle.paddingRight) || 0);\n const viewMoreButtonWidth = getComputedElementWidth(viewMoreButton);\n const legendItemWidths = legendItems.map(getComputedElementWidth);\n const nextVisibleItemCount = getCollapsedVisibleItemCount(\n legendItemWidths,\n availableRowWidth,\n viewMoreButtonWidth,\n );\n const nextCollapsedContentHeight = getCollapsedLegendContentHeight({\n availableRowWidth,\n elements: [...legendItems.slice(0, nextVisibleItemCount), viewMoreButton],\n legendContent,\n });\n\n setLayoutSnapshot((snapshot) => ({\n ...snapshot,\n collapsedHeight: Math.min(\n nextCollapsedContentHeight,\n COLLAPSED_LEGEND_MAX_HEIGHT,\n ),\n contentHeight: nextLegendContentHeight,\n hasOverflow: nextVisibleItemCount < itemCount,\n visibleItemCount: nextVisibleItemCount,\n }));\n setIsMeasuring(false);\n }, [\n itemCount,\n legendContainerRef,\n legendMeasurementRef,\n viewMoreButtonMeasurementRef,\n ]);\n\n useEffect(() => {\n setIsExpanded(false);\n }, [itemCount]);\n\n useEffect(() => {\n if (isExpanded) {\n setIsMeasuring(false);\n return;\n }\n\n if (skipNextCollapseMeasurementRef.current) {\n skipNextCollapseMeasurementRef.current = false;\n setIsMeasuring(false);\n return;\n }\n\n setIsMeasuring(true);\n }, [isExpanded, itemCount, legendWidth]);\n\n useEffect(() => {\n const isMeasuringCollapsed = !isExpanded && isMeasuring;\n\n const animationFrame = requestAnimationFrame(() => {\n if (isMeasuringCollapsed) {\n updateVisibleItems();\n } else {\n setLayoutSnapshot((snapshot) => ({\n ...snapshot,\n contentHeight: legendContentRef.current?.scrollHeight ?? 0,\n }));\n\n if (!isMeasuring && !isExpanded && layoutSnapshot.hasOverflow) {\n setLayoutSnapshot((snapshot) => ({\n ...snapshot,\n collapsedHeight:\n legendContentRef.current?.getBoundingClientRect().height,\n }));\n }\n }\n });\n\n return () => {\n cancelAnimationFrame(animationFrame);\n };\n }, [\n isExpanded,\n isMeasuring,\n legendContentRef,\n layoutSnapshot.hasOverflow,\n updateVisibleItems,\n ]);\n\n const expandLegend = useCallback(() => {\n setIsMeasuring(false);\n setIsExpanded(true);\n }, []);\n\n const collapseLegend = useCallback(() => {\n skipNextCollapseMeasurementRef.current = true;\n setIsExpanded(false);\n }, []);\n\n return {\n collapseLegend,\n expandLegend,\n isExpanded,\n isMeasuring,\n layoutSnapshot,\n };\n};\n\n/**\n * Renders the responsive legend layout shell.\n *\n * The component switches between a two-row collapsed view, an invisible\n * measurement view, and an expanded scrollable view while keeping the\n * \"View more\"/\"View less\" action wired to the measured overflow state.\n */\nexport const LegendLayout = ({\n children,\n className,\n isLayoutReady = true,\n itemCount,\n onLayoutReady,\n htmlAttributes,\n ...restProps\n}: Omit<CommonProps<'div', LegendLayoutProps>, 'ref'>) => {\n const legendContainerRef = useRef<HTMLDivElement>(null);\n const legendContentRef = useRef<HTMLDivElement>(null);\n const legendMeasurementRef = useRef<HTMLDivElement>(null);\n const pendingFocusLegendItemIndexRef = useRef<number | null>(null);\n const viewMoreButtonMeasurementRef = useRef<HTMLButtonElement>(null);\n const {\n collapseLegend,\n expandLegend,\n isExpanded,\n isMeasuring,\n layoutSnapshot,\n } = useCollapsibleLegendLayout({\n itemCount,\n legendContainerRef,\n legendContentRef,\n legendMeasurementRef,\n viewMoreButtonMeasurementRef,\n });\n\n const childList = React.Children.toArray(children);\n const childrenToRender = !isExpanded\n ? childList.slice(0, layoutSnapshot.visibleItemCount)\n : childList;\n const legendHeight =\n !isMeasuring && !isExpanded && layoutSnapshot.hasOverflow\n ? layoutSnapshot.collapsedHeight\n : !isMeasuring &&\n !layoutSnapshot.hasOverflow &&\n layoutSnapshot.contentHeight\n ? layoutSnapshot.contentHeight\n : undefined;\n\n useEffect(() => {\n if (!isMeasuring && layoutSnapshot.contentHeight > 0) {\n let secondFrame: number | undefined;\n const firstFrame = requestAnimationFrame(() => {\n secondFrame = requestAnimationFrame(() => {\n onLayoutReady?.();\n });\n });\n\n return () => {\n cancelAnimationFrame(firstFrame);\n if (secondFrame !== undefined) {\n cancelAnimationFrame(secondFrame);\n }\n };\n }\n }, [isMeasuring, layoutSnapshot.contentHeight, onLayoutReady]);\n\n useEffect(() => {\n const pendingFocusLegendItemIndex = pendingFocusLegendItemIndexRef.current;\n if (!isExpanded || pendingFocusLegendItemIndex === null) {\n return;\n }\n\n const legendItems = legendContentRef.current?.querySelectorAll<HTMLElement>(\n '.ndl-chart-legend-item',\n );\n legendItems?.[pendingFocusLegendItemIndex]?.focus();\n pendingFocusLegendItemIndexRef.current = null;\n }, [isExpanded]);\n\n const handleLegendActionClick = useCallback(() => {\n if (isExpanded) {\n collapseLegend();\n return;\n }\n\n pendingFocusLegendItemIndexRef.current = layoutSnapshot.visibleItemCount;\n expandLegend();\n }, [\n collapseLegend,\n expandLegend,\n isExpanded,\n layoutSnapshot.visibleItemCount,\n ]);\n\n const classes = classNames(\n 'ndl-chart-legend',\n {\n 'ndl-chart-legend-calculating': !isExpanded && isMeasuring,\n 'ndl-chart-legend-collapsed': layoutSnapshot.hasOverflow && !isExpanded,\n 'ndl-chart-legend-expanded': layoutSnapshot.hasOverflow && isExpanded,\n 'ndl-chart-legend-hidden': !isLayoutReady,\n 'ndl-chart-legend-scrollable':\n layoutSnapshot.hasOverflow &&\n isExpanded &&\n layoutSnapshot.contentHeight > COLLAPSED_LEGEND_MAX_HEIGHT,\n 'ndl-chart-legend-wrapping': true,\n },\n className,\n );\n return (\n <div\n ref={legendContainerRef}\n className=\"ndl-chart-legend-container\"\n {...restProps}\n {...htmlAttributes}\n >\n <div\n className={classes}\n ref={legendContentRef}\n style={{\n height: legendHeight,\n maxHeight:\n layoutSnapshot.hasOverflow && isExpanded\n ? 'var(--ndl-chart-legend-expanded-max-height)'\n : layoutSnapshot.hasOverflow\n ? COLLAPSED_LEGEND_MAX_HEIGHT\n : undefined,\n }}\n >\n {childrenToRender}\n {layoutSnapshot.hasOverflow && (\n <TextLink\n as=\"button\"\n className=\"ndl-chart-legend-action\"\n htmlAttributes={{\n 'aria-expanded': isExpanded,\n onClick: handleLegendActionClick,\n type: 'button',\n }}\n >\n {isExpanded ? 'View less' : 'View more'}\n </TextLink>\n )}\n {isMeasuring && (\n <div\n aria-hidden=\"true\"\n className=\"ndl-chart-legend-measurement\"\n ref={legendMeasurementRef}\n >\n {childList}\n <TextLink\n as=\"button\"\n ref={viewMoreButtonMeasurementRef}\n className=\"ndl-chart-legend-action\"\n htmlAttributes={{\n tabIndex: -1,\n type: 'button',\n }}\n >\n View more\n </TextLink>\n </div>\n )}\n </div>\n </div>\n );\n};\n"]}
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ /**
3
+ *
4
+ * Copyright (c) "Neo4j"
5
+ * Neo4j Sweden AB [http://neo4j.com]
6
+ *
7
+ * This file is part of Neo4j.
8
+ *
9
+ * Neo4j is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ * GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License
20
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.useChartLifecycle = useChartLifecycle;
24
+ exports.useChartLoading = useChartLoading;
25
+ exports.useChartResize = useChartResize;
26
+ const base_1 = require("@neo4j-ndl/base");
27
+ const echarts_1 = require("echarts");
28
+ const react_1 = require("react");
29
+ const ndl_echarts_theme_1 = require("../themes/ndl-echarts-theme");
30
+ const use_chart_refs_1 = require("./use-chart-refs");
31
+ /**
32
+ * Registers Needle ECharts themes and initializes the chart instance.
33
+ *
34
+ * Theme registration runs with the active palette so ECharts can resolve Needle
35
+ * colors before options are applied. The hook only calls `init` when the DOM
36
+ * element does not already have an ECharts instance, allowing later renders to
37
+ * update registered theme data without replacing the existing chart.
38
+ */
39
+ function useChartLifecycle({ palette, theme }) {
40
+ const { chartEchartRef } = (0, use_chart_refs_1.useChartRefsContext)();
41
+ (0, react_1.useEffect)(() => {
42
+ if (chartEchartRef.current === null) {
43
+ return;
44
+ }
45
+ (0, echarts_1.registerTheme)('ndl-light', (0, ndl_echarts_theme_1.ndlEchartsTheme)('light', palette));
46
+ (0, echarts_1.registerTheme)('ndl-dark', (0, ndl_echarts_theme_1.ndlEchartsTheme)('dark', palette));
47
+ const currentChart = (0, echarts_1.getInstanceByDom)(chartEchartRef.current);
48
+ if (currentChart) {
49
+ return;
50
+ }
51
+ const echartsTheme = theme === 'light' ? 'ndl-light' : 'ndl-dark';
52
+ (0, echarts_1.init)(chartEchartRef.current, echartsTheme, {
53
+ renderer: 'svg',
54
+ });
55
+ });
56
+ }
57
+ /**
58
+ * Mirrors React loading state into ECharts' loading overlay.
59
+ *
60
+ * The overlay also stays visible while the chart waits for its first resize,
61
+ * preventing users from seeing an unmeasured SVG before ECharts has been sized
62
+ * to its container. Loading colors come from the active Needle theme.
63
+ */
64
+ function useChartLoading({ isLoading, isWaitingForFirstResize, theme, }) {
65
+ const { chartEchartRef } = (0, use_chart_refs_1.useChartRefsContext)();
66
+ (0, react_1.useEffect)(() => {
67
+ if (chartEchartRef.current === null) {
68
+ return;
69
+ }
70
+ const chart = (0, echarts_1.getInstanceByDom)(chartEchartRef.current);
71
+ if (isLoading === true || isWaitingForFirstResize) {
72
+ chart === null || chart === void 0 ? void 0 : chart.showLoading({
73
+ color: base_1.tokens.theme[theme].color.primary.bg.status,
74
+ fontFamily: base_1.tokens.typography['label'].fontFamily,
75
+ fontSize: base_1.tokens.typography['label'].fontSize,
76
+ fontWeight: base_1.tokens.typography['label'].fontWeight,
77
+ maskColor: `rgb( from ${base_1.tokens.theme[theme].color.neutral.text.inverse} r g b / 0.8)`,
78
+ text: 'Loading',
79
+ textColor: base_1.tokens.theme[theme].color.neutral.text.default,
80
+ });
81
+ }
82
+ else {
83
+ chart === null || chart === void 0 ? void 0 : chart.hideLoading();
84
+ }
85
+ }, [chartEchartRef, isLoading, isWaitingForFirstResize, theme]);
86
+ }
87
+ /**
88
+ * Keeps the ECharts canvas sized to the DOM box assigned by the chart layout.
89
+ *
90
+ * The hook listens to both window resize events and direct element resize
91
+ * observations because the chart can change size through parent layout changes
92
+ * that do not emit a window resize. It exposes `isWaitingForFirstResize` so the
93
+ * rest of the chart can delay rendering dependent UI until ECharts has been
94
+ * given its initial dimensions.
95
+ */
96
+ function useChartResize() {
97
+ const { chartEchartRef } = (0, use_chart_refs_1.useChartRefsContext)();
98
+ const [isWaitingForFirstResize, setIsWaitingForFirstResize] = (0, react_1.useState)(true);
99
+ const resizeChart = (0, react_1.useCallback)(() => {
100
+ if (chartEchartRef.current === null) {
101
+ return;
102
+ }
103
+ const chart = (0, echarts_1.getInstanceByDom)(chartEchartRef.current);
104
+ const { clientHeight: height, clientWidth: width } = chartEchartRef.current;
105
+ chart === null || chart === void 0 ? void 0 : chart.resize({
106
+ height,
107
+ width,
108
+ });
109
+ }, [chartEchartRef]);
110
+ (0, react_1.useEffect)(() => {
111
+ window.addEventListener('resize', resizeChart);
112
+ const resizeObserver = new ResizeObserver(() => {
113
+ resizeChart();
114
+ });
115
+ if (chartEchartRef.current) {
116
+ resizeObserver.observe(chartEchartRef.current);
117
+ }
118
+ const animationFrame = requestAnimationFrame(() => {
119
+ resizeChart();
120
+ setIsWaitingForFirstResize(false);
121
+ });
122
+ return () => {
123
+ cancelAnimationFrame(animationFrame);
124
+ window.removeEventListener('resize', resizeChart);
125
+ resizeObserver.disconnect();
126
+ };
127
+ }, [chartEchartRef, resizeChart]);
128
+ return {
129
+ isWaitingForFirstResize,
130
+ resizeChart,
131
+ };
132
+ }
133
+ //# sourceMappingURL=use-chart-instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chart-instance.js","sourceRoot":"","sources":["../../../../src/charts/hooks/use-chart-instance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n *\n * Copyright (c) \"Neo4j\"\n * Neo4j Sweden AB [http://neo4j.com]\n *\n * This file is part of Neo4j.\n *\n * Neo4j is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\nimport { tokens } from '@neo4j-ndl/base';\nimport { getInstanceByDom, init, registerTheme } from 'echarts';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { ndlEchartsTheme } from '../themes/ndl-echarts-theme';\nimport type { HexColor } from '../utils/chart-types';\nimport { useChartRefsContext } from './use-chart-refs';\n\ntype UseChartLifecycleParams = {\n palette?: HexColor[];\n theme: 'light' | 'dark';\n};\n\ntype UseChartLoadingParams = {\n isLoading?: boolean;\n isWaitingForFirstResize: boolean;\n theme: 'light' | 'dark';\n};\n\n/**\n * Registers Needle ECharts themes and initializes the chart instance.\n *\n * Theme registration runs with the active palette so ECharts can resolve Needle\n * colors before options are applied. The hook only calls `init` when the DOM\n * element does not already have an ECharts instance, allowing later renders to\n * update registered theme data without replacing the existing chart.\n */\nexport function useChartLifecycle({ palette, theme }: UseChartLifecycleParams) {\n const { chartEchartRef } = useChartRefsContext();\n\n useEffect(() => {\n if (chartEchartRef.current === null) {\n return;\n }\n\n registerTheme('ndl-light', ndlEchartsTheme('light', palette));\n registerTheme('ndl-dark', ndlEchartsTheme('dark', palette));\n\n const currentChart = getInstanceByDom(chartEchartRef.current);\n if (currentChart) {\n return;\n }\n\n const echartsTheme = theme === 'light' ? 'ndl-light' : 'ndl-dark';\n init(chartEchartRef.current, echartsTheme, {\n renderer: 'svg',\n });\n });\n}\n\n/**\n * Mirrors React loading state into ECharts' loading overlay.\n *\n * The overlay also stays visible while the chart waits for its first resize,\n * preventing users from seeing an unmeasured SVG before ECharts has been sized\n * to its container. Loading colors come from the active Needle theme.\n */\nexport function useChartLoading({\n isLoading,\n isWaitingForFirstResize,\n theme,\n}: UseChartLoadingParams) {\n const { chartEchartRef } = useChartRefsContext();\n\n useEffect(() => {\n if (chartEchartRef.current === null) {\n return;\n }\n\n const chart = getInstanceByDom(chartEchartRef.current);\n\n if (isLoading === true || isWaitingForFirstResize) {\n chart?.showLoading({\n color: tokens.theme[theme].color.primary.bg.status,\n fontFamily: tokens.typography['label'].fontFamily,\n fontSize: tokens.typography['label'].fontSize,\n fontWeight: tokens.typography['label'].fontWeight,\n maskColor: `rgb( from ${tokens.theme[theme].color.neutral.text.inverse} r g b / 0.8)`,\n text: 'Loading',\n textColor: tokens.theme[theme].color.neutral.text.default,\n });\n } else {\n chart?.hideLoading();\n }\n }, [chartEchartRef, isLoading, isWaitingForFirstResize, theme]);\n}\n\n/**\n * Keeps the ECharts canvas sized to the DOM box assigned by the chart layout.\n *\n * The hook listens to both window resize events and direct element resize\n * observations because the chart can change size through parent layout changes\n * that do not emit a window resize. It exposes `isWaitingForFirstResize` so the\n * rest of the chart can delay rendering dependent UI until ECharts has been\n * given its initial dimensions.\n */\nexport function useChartResize() {\n const { chartEchartRef } = useChartRefsContext();\n const [isWaitingForFirstResize, setIsWaitingForFirstResize] = useState(true);\n\n const resizeChart = useCallback(() => {\n if (chartEchartRef.current === null) {\n return;\n }\n\n const chart = getInstanceByDom(chartEchartRef.current);\n const { clientHeight: height, clientWidth: width } = chartEchartRef.current;\n\n chart?.resize({\n height,\n width,\n });\n }, [chartEchartRef]);\n\n useEffect(() => {\n window.addEventListener('resize', resizeChart);\n const resizeObserver = new ResizeObserver(() => {\n resizeChart();\n });\n if (chartEchartRef.current) {\n resizeObserver.observe(chartEchartRef.current);\n }\n const animationFrame = requestAnimationFrame(() => {\n resizeChart();\n setIsWaitingForFirstResize(false);\n });\n\n return () => {\n cancelAnimationFrame(animationFrame);\n window.removeEventListener('resize', resizeChart);\n resizeObserver.disconnect();\n };\n }, [chartEchartRef, resizeChart]);\n\n return {\n isWaitingForFirstResize,\n resizeChart,\n };\n}\n"]}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ *
4
+ * Copyright (c) "Neo4j"
5
+ * Neo4j Sweden AB [http://neo4j.com]
6
+ *
7
+ * This file is part of Neo4j.
8
+ *
9
+ * Neo4j is free software: you can redistribute it and/or modify
10
+ * it under the terms of the GNU General Public License as published by
11
+ * the Free Software Foundation, either version 3 of the License, or
12
+ * (at your option) any later version.
13
+ *
14
+ * This program is distributed in the hope that it will be useful,
15
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ * GNU General Public License for more details.
18
+ *
19
+ * You should have received a copy of the GNU General Public License
20
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.useChartOption = useChartOption;
24
+ const echarts_1 = require("echarts");
25
+ const react_1 = require("react");
26
+ const build_chart_option_1 = require("../utils/build-chart-option");
27
+ const use_chart_refs_1 = require("./use-chart-refs");
28
+ /**
29
+ * Builds, applies, and exposes the ECharts option for the current React props.
30
+ *
31
+ * `buildChartOption` merges Needle defaults, normalized axes/series, threshold
32
+ * lines, zoom settings, toolbox options, user overrides, and the current legend
33
+ * selection. After `setOption`, the hook returns ECharts' normalized option so
34
+ * downstream code, especially the custom legend, can read the final series and
35
+ * dataset shape ECharts is actually using.
36
+ */
37
+ function useChartOption({ dataZoom, dataset, hasCategoryXAxis, hasSliderZoom, propsSeries, series, settings, thresholdLines, toolboxOptions, userOption, xAxis, yAxis, }) {
38
+ const { chartEchartRef, legendSelectedRef } = (0, use_chart_refs_1.useChartRefsContext)();
39
+ const [chartOption, setChartOption] = (0, react_1.useState)();
40
+ (0, react_1.useEffect)(() => {
41
+ if (chartEchartRef.current === null) {
42
+ return;
43
+ }
44
+ const chart = (0, echarts_1.getInstanceByDom)(chartEchartRef.current);
45
+ const option = (0, build_chart_option_1.buildChartOption)({
46
+ dataZoom,
47
+ dataset,
48
+ hasCategoryXAxis,
49
+ hasSliderZoom,
50
+ legendSelected: legendSelectedRef.current || {},
51
+ propsSeries,
52
+ series,
53
+ thresholdLines,
54
+ toolboxOptions,
55
+ userOption,
56
+ xAxis,
57
+ yAxis,
58
+ });
59
+ chart === null || chart === void 0 ? void 0 : chart.setOption(option, settings);
60
+ setChartOption(chart === null || chart === void 0 ? void 0 : chart.getOption());
61
+ }, [
62
+ chartEchartRef,
63
+ dataZoom,
64
+ dataset,
65
+ hasCategoryXAxis,
66
+ hasSliderZoom,
67
+ legendSelectedRef,
68
+ propsSeries,
69
+ series,
70
+ settings,
71
+ thresholdLines,
72
+ toolboxOptions,
73
+ userOption,
74
+ xAxis,
75
+ yAxis,
76
+ ]);
77
+ return chartOption;
78
+ }
79
+ //# sourceMappingURL=use-chart-option.js.map