@thoughtspot/ts-chart-sdk 0.0.1-alpha.2 → 0.0.1-alpha.5

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 (83) hide show
  1. package/cjs/index.d.ts +1 -0
  2. package/cjs/index.d.ts.map +1 -1
  3. package/cjs/index.js +1 -0
  4. package/cjs/index.js.map +1 -1
  5. package/cjs/main/custom-chart-context.d.ts +1 -0
  6. package/cjs/main/custom-chart-context.d.ts.map +1 -1
  7. package/cjs/main/custom-chart-context.js +8 -0
  8. package/cjs/main/custom-chart-context.js.map +1 -1
  9. package/cjs/main/custom-chart-context.spec.js +69 -0
  10. package/cjs/main/custom-chart-context.spec.js.map +1 -1
  11. package/cjs/react/mocks/custom-chart-context-mock.d.ts +3 -0
  12. package/cjs/react/mocks/custom-chart-context-mock.d.ts.map +1 -0
  13. package/cjs/react/mocks/custom-chart-context-mock.js +100 -0
  14. package/cjs/react/mocks/custom-chart-context-mock.js.map +1 -0
  15. package/cjs/react/use-custom-chart-context.d.ts +4 -0
  16. package/cjs/react/use-custom-chart-context.d.ts.map +1 -0
  17. package/cjs/react/use-custom-chart-context.js +95 -0
  18. package/cjs/react/use-custom-chart-context.js.map +1 -0
  19. package/cjs/react/use-custom-chart-context.spec.d.ts +2 -0
  20. package/cjs/react/use-custom-chart-context.spec.d.ts.map +1 -0
  21. package/cjs/react/use-custom-chart-context.spec.js +391 -0
  22. package/cjs/react/use-custom-chart-context.spec.js.map +1 -0
  23. package/cjs/react/use-custom-chart-context.util.d.ts +6 -0
  24. package/cjs/react/use-custom-chart-context.util.d.ts.map +1 -0
  25. package/cjs/react/use-custom-chart-context.util.js +60 -0
  26. package/cjs/react/use-custom-chart-context.util.js.map +1 -0
  27. package/cjs/react/use-custom-chart-types.d.ts +23 -0
  28. package/cjs/react/use-custom-chart-types.d.ts.map +1 -0
  29. package/cjs/react/use-custom-chart-types.js +3 -0
  30. package/cjs/react/use-custom-chart-types.js.map +1 -0
  31. package/cjs/types/chart-to-ts-event.types.d.ts +12 -4
  32. package/cjs/types/chart-to-ts-event.types.d.ts.map +1 -1
  33. package/cjs/types/chart-to-ts-event.types.js +2 -0
  34. package/cjs/types/chart-to-ts-event.types.js.map +1 -1
  35. package/cjs/types/visual-prop.types.d.ts +12 -0
  36. package/cjs/types/visual-prop.types.d.ts.map +1 -1
  37. package/lib/index.d.ts +1 -0
  38. package/lib/index.d.ts.map +1 -1
  39. package/lib/index.js +1 -0
  40. package/lib/index.js.map +1 -1
  41. package/lib/main/custom-chart-context.d.ts +1 -0
  42. package/lib/main/custom-chart-context.d.ts.map +1 -1
  43. package/lib/main/custom-chart-context.js +8 -0
  44. package/lib/main/custom-chart-context.js.map +1 -1
  45. package/lib/main/custom-chart-context.spec.js +69 -0
  46. package/lib/main/custom-chart-context.spec.js.map +1 -1
  47. package/lib/react/mocks/custom-chart-context-mock.d.ts +3 -0
  48. package/lib/react/mocks/custom-chart-context-mock.d.ts.map +1 -0
  49. package/lib/react/mocks/custom-chart-context-mock.js +94 -0
  50. package/lib/react/mocks/custom-chart-context-mock.js.map +1 -0
  51. package/lib/react/use-custom-chart-context.d.ts +4 -0
  52. package/lib/react/use-custom-chart-context.d.ts.map +1 -0
  53. package/lib/react/use-custom-chart-context.js +68 -0
  54. package/lib/react/use-custom-chart-context.js.map +1 -0
  55. package/lib/react/use-custom-chart-context.spec.d.ts +2 -0
  56. package/lib/react/use-custom-chart-context.spec.d.ts.map +1 -0
  57. package/lib/react/use-custom-chart-context.spec.js +363 -0
  58. package/lib/react/use-custom-chart-context.spec.js.map +1 -0
  59. package/lib/react/use-custom-chart-context.util.d.ts +6 -0
  60. package/lib/react/use-custom-chart-context.util.d.ts.map +1 -0
  61. package/lib/react/use-custom-chart-context.util.js +51 -0
  62. package/lib/react/use-custom-chart-context.util.js.map +1 -0
  63. package/lib/react/use-custom-chart-types.d.ts +23 -0
  64. package/lib/react/use-custom-chart-types.d.ts.map +1 -0
  65. package/lib/react/use-custom-chart-types.js +2 -0
  66. package/lib/react/use-custom-chart-types.js.map +1 -0
  67. package/lib/types/chart-to-ts-event.types.d.ts +12 -4
  68. package/lib/types/chart-to-ts-event.types.d.ts.map +1 -1
  69. package/lib/types/chart-to-ts-event.types.js +2 -0
  70. package/lib/types/chart-to-ts-event.types.js.map +1 -1
  71. package/lib/types/visual-prop.types.d.ts +12 -0
  72. package/lib/types/visual-prop.types.d.ts.map +1 -1
  73. package/package.json +8 -2
  74. package/src/index.ts +1 -0
  75. package/src/main/custom-chart-context.spec.ts +116 -0
  76. package/src/main/custom-chart-context.ts +15 -0
  77. package/src/react/mocks/custom-chart-context-mock.ts +114 -0
  78. package/src/react/use-custom-chart-context.spec.tsx +464 -0
  79. package/src/react/use-custom-chart-context.tsx +180 -0
  80. package/src/react/use-custom-chart-context.util.ts +101 -0
  81. package/src/react/use-custom-chart-types.ts +70 -0
  82. package/src/types/chart-to-ts-event.types.ts +47 -2
  83. package/src/types/visual-prop.types.ts +68 -0
@@ -0,0 +1,464 @@
1
+ import { render, waitFor } from '@testing-library/react';
2
+ import { renderHook } from '@testing-library/react-hooks';
3
+ import React from 'react';
4
+ import * as PostMessageEventBridge from '../main/post-message-event-bridge';
5
+ import { mockInitializeContextPayload } from '../test/test-utils';
6
+ import { ChartToTSEvent } from '../types/chart-to-ts-event.types';
7
+ import { TSToChartEvent } from '../types/ts-to-chart-event.types';
8
+ import { contextChartProps } from './mocks/custom-chart-context-mock';
9
+ import { useChartContext } from './use-custom-chart-context';
10
+
11
+ jest.mock('../main/post-message-event-bridge');
12
+
13
+ describe('useChartContext initialization', () => {
14
+ let eventProcessor: any;
15
+ let mockInitMessage;
16
+ let mockPostMessageToHost;
17
+ const mockedChartModel = { columns: [], config: {} };
18
+ beforeEach(() => {
19
+ mockInitMessage = jest.spyOn(
20
+ PostMessageEventBridge,
21
+ 'initMessageListener',
22
+ );
23
+ mockPostMessageToHost = jest.spyOn(
24
+ PostMessageEventBridge,
25
+ 'postMessageToHostApp',
26
+ );
27
+ mockInitMessage.mockImplementation((fn: any) => {
28
+ eventProcessor = fn;
29
+ });
30
+
31
+ mockPostMessageToHost.mockImplementation(() => {
32
+ return global.Promise.resolve();
33
+ });
34
+ });
35
+ afterEach(() => {
36
+ // Clear mock implementations after each test
37
+ jest.clearAllMocks();
38
+ });
39
+
40
+ test('should initialize the context only after intialize completes', async () => {
41
+ // Render the hook with the custom chart context props
42
+ const { result, waitFor } = renderHook(() =>
43
+ useChartContext(contextChartProps),
44
+ );
45
+
46
+ // Assert that the context is not initialized initially
47
+ expect(result.current.hasInitialized).toBe(false);
48
+ expect(result.current.chartModel).toBeUndefined();
49
+ const mockPostMessage = jest.fn();
50
+ eventProcessor({
51
+ data: {
52
+ payload: {
53
+ componentId: 'COMPONENT_ID',
54
+ hostUrl: 'https://some.chart.app',
55
+ chartModel: mockedChartModel,
56
+ },
57
+ eventType: TSToChartEvent.Initialize,
58
+ source: 'ts-host-app',
59
+ },
60
+ ports: [{ postMessage: mockPostMessage }],
61
+ });
62
+
63
+ eventProcessor({
64
+ data: {
65
+ payload: {},
66
+ eventType: TSToChartEvent.InitializeComplete,
67
+ source: 'ts-host-app',
68
+ },
69
+ ports: [{ postMessage: mockPostMessage }],
70
+ });
71
+ await waitFor(() => {
72
+ expect(result.current.hasInitialized).toBe(true);
73
+ expect(result.current.chartModel).toEqual(mockedChartModel);
74
+ });
75
+ result.current?.destroy();
76
+ });
77
+
78
+ test('should make sure hasInitialized to remain false when context initialization failed', async () => {
79
+ jest.mock('../main/custom-chart-context', () => ({
80
+ CustomChartContext: jest.fn().mockImplementation(() => ({
81
+ initialize: jest
82
+ .fn()
83
+ .mockRejectedValue(new Error('Initialization failed')),
84
+ emitEvent: jest.fn(),
85
+ on: jest.fn(),
86
+ getChartModel: jest.fn(),
87
+ renderChart: jest.fn(),
88
+ destroy: jest.fn(),
89
+ })),
90
+ }));
91
+
92
+ // Render the hook with the custom chart context props
93
+ const { result, waitFor } = renderHook(() =>
94
+ useChartContext({
95
+ ...contextChartProps,
96
+ }),
97
+ );
98
+ await waitFor(() => {
99
+ expect(result.current.hasInitialized).toBe(false);
100
+ expect(result.current.chartModel).toBeUndefined();
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('useChartContext emit', () => {
106
+ let eventProcessor: any;
107
+ let mockInitMessage;
108
+ let mockPostMessageToHost: any;
109
+ beforeEach(() => {
110
+ mockInitMessage = jest.spyOn(
111
+ PostMessageEventBridge,
112
+ 'initMessageListener',
113
+ );
114
+ mockPostMessageToHost = jest.spyOn(
115
+ PostMessageEventBridge,
116
+ 'postMessageToHostApp',
117
+ );
118
+ mockInitMessage.mockImplementation((fn: any) => {
119
+ eventProcessor = fn;
120
+ });
121
+
122
+ mockPostMessageToHost.mockImplementation(() => {
123
+ return global.Promise.resolve();
124
+ });
125
+ });
126
+ afterEach(() => {
127
+ // Clear mock implementations after each test
128
+ jest.clearAllMocks();
129
+ });
130
+
131
+ test('should trigger the emitter correctly when context is initialized', async () => {
132
+ // Render the hook with the custom chart context props
133
+ const { result, waitFor } = renderHook(() =>
134
+ useChartContext(contextChartProps),
135
+ );
136
+ const mockPostMessage = jest.fn();
137
+ eventProcessor({
138
+ data: {
139
+ payload: mockInitializeContextPayload,
140
+ eventType: TSToChartEvent.Initialize,
141
+ source: 'ts-host-app',
142
+ },
143
+ ports: [{ postMessage: mockPostMessage }],
144
+ });
145
+
146
+ eventProcessor({
147
+ data: {
148
+ payload: {},
149
+ eventType: TSToChartEvent.InitializeComplete,
150
+ source: 'ts-host-app',
151
+ },
152
+ ports: [{ postMessage: mockPostMessage }],
153
+ });
154
+ await waitFor(() => {
155
+ expect(result.current.hasInitialized).toBe(true);
156
+ });
157
+ const contextPayload = {
158
+ clickedPoint: { tuple: [{ columnId: '123', value: 12 }] },
159
+ event: {} as any,
160
+ };
161
+ result.current.emitOpenContextMenu(contextPayload);
162
+ expect(mockPostMessageToHost).toHaveBeenCalledWith(
163
+ mockInitializeContextPayload.componentId,
164
+ mockInitializeContextPayload.hostUrl,
165
+ contextPayload,
166
+ ChartToTSEvent.OpenContextMenu,
167
+ );
168
+ result.current?.destroy();
169
+ });
170
+
171
+ test('should not defined emitter when context is not initialized', async () => {
172
+ // Render the hook with the custom chart context props
173
+ const { result, waitFor } = renderHook(() =>
174
+ useChartContext(contextChartProps),
175
+ );
176
+ const mockPostMessage = jest.fn();
177
+ eventProcessor({
178
+ data: {
179
+ payload: mockInitializeContextPayload,
180
+ eventType: TSToChartEvent.Initialize,
181
+ source: 'ts-host-app',
182
+ },
183
+ ports: [{ postMessage: mockPostMessage }],
184
+ });
185
+ await waitFor(() => {
186
+ expect(result.current.hasInitialized).toBe(false);
187
+ });
188
+ const promise = result.current.emitRenderStart();
189
+ await expect(promise).rejects.toThrow('Context not initialized');
190
+ expect(mockPostMessageToHost).not.toHaveBeenCalled();
191
+ });
192
+ });
193
+
194
+ describe('useChartContext setOn listeners', () => {
195
+ let eventProcessor: any;
196
+ let mockInitMessage;
197
+ let mockPostMessageToHost;
198
+ const mockedChartModel = { columns: [], config: {} };
199
+ beforeEach(() => {
200
+ mockInitMessage = jest.spyOn(
201
+ PostMessageEventBridge,
202
+ 'initMessageListener',
203
+ );
204
+ mockPostMessageToHost = jest.spyOn(
205
+ PostMessageEventBridge,
206
+ 'postMessageToHostApp',
207
+ );
208
+ mockInitMessage.mockImplementation((fn: any) => {
209
+ eventProcessor = fn;
210
+ });
211
+
212
+ mockPostMessageToHost.mockImplementation(() => {
213
+ return global.Promise.resolve();
214
+ });
215
+ });
216
+ afterEach(() => {
217
+ // Clear mock implementations after each test
218
+ jest.clearAllMocks();
219
+ });
220
+
221
+ test('should trigger the setOnEvent correctly when context is initialized', async () => {
222
+ // Render the hook with the custom chart context props
223
+ const { result, waitFor } = renderHook(() =>
224
+ useChartContext(contextChartProps),
225
+ );
226
+
227
+ // Assert that the context is not initialized initially
228
+ expect(result.current.hasInitialized).toBe(false);
229
+ expect(result.current.chartModel).toBeUndefined();
230
+ const mockPostMessage = jest.fn();
231
+ eventProcessor({
232
+ data: {
233
+ payload: {
234
+ componentId: 'COMPONENT_ID',
235
+ hostUrl: 'https://some.chart.app',
236
+ chartModel: mockedChartModel,
237
+ },
238
+ eventType: TSToChartEvent.Initialize,
239
+ source: 'ts-host-app',
240
+ },
241
+ ports: [{ postMessage: mockPostMessage }],
242
+ });
243
+
244
+ eventProcessor({
245
+ data: {
246
+ payload: {},
247
+ eventType: TSToChartEvent.InitializeComplete,
248
+ source: 'ts-host-app',
249
+ },
250
+ ports: [{ postMessage: mockPostMessage }],
251
+ });
252
+ await waitFor(() => {
253
+ expect(result.current.hasInitialized).toBe(true);
254
+ expect(result.current.chartModel).toEqual(mockedChartModel);
255
+ });
256
+
257
+ eventProcessor({
258
+ data: {
259
+ payload: { chartModel: {} },
260
+ eventType: TSToChartEvent.ChartModelUpdate,
261
+ source: 'ts-host-app',
262
+ },
263
+ ports: [{ postMessage: mockPostMessage }],
264
+ });
265
+ await waitFor(() => {
266
+ expect(mockPostMessage).toHaveBeenCalledWith({
267
+ triggerRenderChart: true,
268
+ });
269
+ expect(result.current.chartModel).toEqual({});
270
+ });
271
+
272
+ eventProcessor({
273
+ data: {
274
+ payload: { visualProps: { color: 'red' } },
275
+ eventType: TSToChartEvent.VisualPropsUpdate,
276
+ source: 'ts-host-app',
277
+ },
278
+ ports: [{ postMessage: mockPostMessage }],
279
+ });
280
+ expect(mockPostMessage).toHaveBeenCalledWith({
281
+ triggerRenderChart: true,
282
+ });
283
+ await waitFor(() => {
284
+ expect(mockPostMessage).toHaveBeenCalledWith({
285
+ triggerRenderChart: true,
286
+ });
287
+ expect(result.current.chartModel?.visualProps).toEqual({
288
+ color: 'red',
289
+ });
290
+ });
291
+ result.current?.destroy();
292
+ });
293
+
294
+ test('should not defined setOnEvent when context is not initialized', async () => {
295
+ // Render the hook with the custom chart context props
296
+ const { result, waitFor } = renderHook(() =>
297
+ useChartContext(contextChartProps),
298
+ );
299
+ const mockPostMessage = jest.fn();
300
+ eventProcessor({
301
+ data: {
302
+ payload: mockInitializeContextPayload,
303
+ eventType: TSToChartEvent.Initialize,
304
+ source: 'ts-host-app',
305
+ },
306
+ ports: [{ postMessage: mockPostMessage }],
307
+ });
308
+ await waitFor(() => {
309
+ expect(result.current.hasInitialized).toBe(false);
310
+ });
311
+ const promise = result.current.setOnVisualPropsUpdate((payload) => {
312
+ //
313
+ });
314
+ await expect(promise).rejects.toThrow('Context not initialized');
315
+ const offPromise = result.current.setOffVisualPropsUpdate();
316
+ await expect(offPromise).rejects.toThrow('Context not initialized');
317
+ });
318
+ });
319
+
320
+ describe('useChartContext on React Wrapper component', () => {
321
+ let eventProcessor: any;
322
+ let mockInitMessage;
323
+ let mockPostMessageToHost;
324
+ const mockedChartModel = { columns: [], config: {} };
325
+ beforeEach(() => {
326
+ mockInitMessage = jest.spyOn(
327
+ PostMessageEventBridge,
328
+ 'initMessageListener',
329
+ );
330
+ mockPostMessageToHost = jest.spyOn(
331
+ PostMessageEventBridge,
332
+ 'postMessageToHostApp',
333
+ );
334
+ mockInitMessage.mockImplementation((fn: any) => {
335
+ eventProcessor = fn;
336
+ });
337
+
338
+ mockPostMessageToHost.mockImplementation(() => {
339
+ return global.Promise.resolve();
340
+ });
341
+ });
342
+ afterEach(() => {
343
+ // Clear mock implementations after each test
344
+ jest.clearAllMocks();
345
+ });
346
+ test('TSChartContext renders children and should not increase counter for useEffect on chartModel if visualProps is updated', async () => {
347
+ const CustomChartComponent = () => {
348
+ const [counter, setCounter] = React.useState(0);
349
+ const { TSChartContext, chartModel, hasInitialized } =
350
+ useChartContext(contextChartProps);
351
+ React.useEffect(() => {
352
+ setCounter((prevCount) => prevCount + 1);
353
+ }, [chartModel]);
354
+ return (
355
+ <TSChartContext>
356
+ <div data-testid="child-element">
357
+ Child Element counter: {counter}
358
+ {(chartModel?.visualProps as any)?.color ?? ''}
359
+ </div>
360
+ </TSChartContext>
361
+ );
362
+ };
363
+
364
+ const { getByTestId, rerender } = render(<CustomChartComponent />);
365
+
366
+ const mockPostMessage = jest.fn();
367
+ eventProcessor({
368
+ data: {
369
+ payload: {
370
+ componentId: 'COMPONENT_ID',
371
+ hostUrl: 'https://some.chart.app',
372
+ chartModel: mockedChartModel,
373
+ },
374
+ eventType: TSToChartEvent.Initialize,
375
+ source: 'ts-host-app',
376
+ },
377
+ ports: [{ postMessage: mockPostMessage }],
378
+ });
379
+ rerender(<CustomChartComponent />);
380
+
381
+ // Check if the child element is rendered
382
+ await waitFor(() => {
383
+ expect(getByTestId('child-element').textContent).toBe(
384
+ 'Child Element counter: 1',
385
+ );
386
+ });
387
+
388
+ eventProcessor({
389
+ data: {
390
+ payload: { visualProps: { color: 'red' } },
391
+ eventType: TSToChartEvent.VisualPropsUpdate,
392
+ source: 'ts-host-app',
393
+ },
394
+ ports: [{ postMessage: mockPostMessage }],
395
+ });
396
+
397
+ // Re-render the component with a new key
398
+ // Check if the child element is still in the document after re-render
399
+ const updatedChildElement = getByTestId('child-element');
400
+ await waitFor(() => {
401
+ expect(updatedChildElement.textContent).toBe(
402
+ 'Child Element counter: 1red',
403
+ );
404
+ });
405
+ });
406
+
407
+ test('TSChartContext renders children should increase counter for useEffect on chartModel.visualProps if visualProps is updated', async () => {
408
+ const CustomChartComponent = () => {
409
+ const [counter, setCounter] = React.useState(0);
410
+ const { TSChartContext, chartModel, hasInitialized } =
411
+ useChartContext(contextChartProps);
412
+ React.useEffect(() => {
413
+ setCounter((prevCount) => prevCount + 1);
414
+ }, [chartModel?.visualProps]);
415
+ return (
416
+ <TSChartContext>
417
+ <div data-testid="child-element">
418
+ Child Element counter: {counter}
419
+ {(chartModel?.visualProps as any)?.color ?? ''}
420
+ </div>
421
+ </TSChartContext>
422
+ );
423
+ };
424
+
425
+ const { getByTestId, rerender } = render(<CustomChartComponent />);
426
+
427
+ // Check if the child element is rendered
428
+ const childElement = getByTestId('child-element');
429
+ await waitFor(() => {
430
+ expect(childElement.textContent).toBe('Child Element counter: 1');
431
+ });
432
+
433
+ // Re-render the component with a new key
434
+ const mockPostMessage = jest.fn();
435
+ eventProcessor({
436
+ data: {
437
+ payload: {
438
+ componentId: 'COMPONENT_ID',
439
+ hostUrl: 'https://some.chart.app',
440
+ chartModel: mockedChartModel,
441
+ },
442
+ eventType: TSToChartEvent.Initialize,
443
+ source: 'ts-host-app',
444
+ },
445
+ ports: [{ postMessage: mockPostMessage }],
446
+ });
447
+ eventProcessor({
448
+ data: {
449
+ payload: { visualProps: { color: 'red' } },
450
+ eventType: TSToChartEvent.VisualPropsUpdate,
451
+ source: 'ts-host-app',
452
+ },
453
+ ports: [{ postMessage: mockPostMessage }],
454
+ });
455
+ rerender(<CustomChartComponent />);
456
+ // Check if the child element is still in the document after re-render
457
+ const updatedChildElement = getByTestId('child-element');
458
+ await waitFor(() => {
459
+ expect(updatedChildElement.textContent).toBe(
460
+ 'Child Element counter: 2red',
461
+ );
462
+ });
463
+ });
464
+ });
@@ -0,0 +1,180 @@
1
+ /**
2
+ * @file Custom Chart Context Hook for React App
3
+ * @fileoverview
4
+ * @author Divyam Lamiyan <divyam.lamiyan@thoughtspot.com>
5
+ *
6
+ * Copyright: ThoughtSpot Inc. 2023
7
+ */
8
+
9
+ import _ from 'lodash';
10
+ import React, { useCallback, useEffect, useState } from 'react';
11
+ import {
12
+ CustomChartContext,
13
+ CustomChartContextProps,
14
+ } from '../main/custom-chart-context';
15
+ import { ChartModel } from '../types/common.types';
16
+ import {
17
+ ChartModelUpdateEventPayload,
18
+ DataUpdateEventPayload,
19
+ VisualPropsUpdateEventPayload,
20
+ } from '../types/ts-to-chart-event.types';
21
+ import {
22
+ emitter,
23
+ eventListener,
24
+ eventOffListener,
25
+ } from './use-custom-chart-context.util';
26
+ import {
27
+ ChartContextProps,
28
+ TSChartContextProps,
29
+ } from './use-custom-chart-types';
30
+
31
+ /**
32
+ * A custom hook to manage the Chart Context state and provide necessary
33
+ * chart-related functionality.
34
+ * @param {CustomChartContextProps} props - The custom chart context properties
35
+ * to initialize the chart context.
36
+ * @returns {ChartContextProps} The chart context values, including initialized state,
37
+ * chart model, emitter, event listener, and TSChartContext.
38
+ */
39
+ export const useChartContext = (
40
+ props: Omit<CustomChartContextProps, 'renderChart'>,
41
+ ): ChartContextProps => {
42
+ /**
43
+ * State to keep track of whether the context has been initialized.
44
+ * @type {boolean}
45
+ */
46
+ const [hasInitialized, setHasInitialized] = useState(false);
47
+ /**
48
+ * State to manage the key used to re-render the chart.
49
+ * @type {number}
50
+ */
51
+ const [key, setKey] = useState(0);
52
+ /**
53
+ * State to hold the custom chart context.
54
+ * @type {CustomChartContext}
55
+ */
56
+ const [ctx, setContextState] = useState<CustomChartContext>(
57
+ {} as CustomChartContext,
58
+ );
59
+ /**
60
+ * State to manage the chart model.
61
+ * @type {ChartModel}
62
+ */
63
+ const [chartModel, setChartModel] = useState<ChartModel>();
64
+
65
+ /**
66
+ * Retrieves the chart context values.
67
+ * @param {CustomChartContext} ctx - The custom chart context.
68
+ * @returns {ChartContextProps} The chart context values,
69
+ * including initialized state, chart model, emitter,
70
+ * event listener, and TSChartContext.
71
+ */
72
+ const getChartContextValues = useCallback(
73
+ (ctx: CustomChartContext): ChartContextProps => {
74
+ return {
75
+ hasInitialized,
76
+ chartModel,
77
+ destroy: () => ctx?.destroy(),
78
+ ...emitter(ctx),
79
+ ...eventListener(ctx),
80
+ ...eventOffListener(ctx),
81
+ TSChartContext: ({ children }: TSChartContextProps) => {
82
+ return (
83
+ <React.Fragment key={key}>{children}</React.Fragment>
84
+ );
85
+ },
86
+ };
87
+ },
88
+ [chartModel, key, hasInitialized, ctx],
89
+ );
90
+
91
+ /**
92
+ * Register all external event listeners and update chart model once called
93
+ */
94
+ const setupEventListeners = useCallback((context: CustomChartContext) => {
95
+ const commonUpdateHandler = (
96
+ payload:
97
+ | ChartModelUpdateEventPayload
98
+ | VisualPropsUpdateEventPayload
99
+ | DataUpdateEventPayload,
100
+ ) => {
101
+ setChartModel(context.getChartModel());
102
+ return {
103
+ triggerRenderChart: true,
104
+ };
105
+ };
106
+
107
+ // Register all external event listeners here
108
+ getChartContextValues(context).setOnChartModelUpdate(
109
+ commonUpdateHandler,
110
+ );
111
+ getChartContextValues(context).setOnVisualPropsUpdate(
112
+ commonUpdateHandler,
113
+ );
114
+ getChartContextValues(context).setOnDataUpdate(commonUpdateHandler);
115
+ }, []);
116
+
117
+ /**
118
+ * Register all external event off listeners
119
+ */
120
+ const setupEventOffListeners = useCallback(
121
+ (context: CustomChartContext) => {
122
+ // Register all external event off listeners here
123
+ getChartContextValues(context).setOffChartModelUpdate();
124
+ getChartContextValues(context).setOffDataUpdate();
125
+ getChartContextValues(context).setOffVisualPropsUpdate();
126
+ },
127
+ [],
128
+ );
129
+
130
+ /**
131
+ * Initializes the chart context provider.
132
+ * @param {CustomChartContext} context - The custom chart context to be initialized.
133
+ * @returns {Promise<boolean>} A promise that resolves to
134
+ * `true` after successful initialization.
135
+ */
136
+ const initializeContext = async (context: CustomChartContext) => {
137
+ return context
138
+ .initialize()
139
+ .then(() => {
140
+ setHasInitialized(true);
141
+ setContextState(context);
142
+ setChartModel(context.getChartModel());
143
+ return true;
144
+ })
145
+ .catch((e) => {
146
+ console.log('Error in context initialization', e);
147
+ });
148
+ };
149
+
150
+ /**
151
+ * Renders the chart by updating the `key` state to force re-rendering.
152
+ * @param {CustomChartContext} ctx - The custom chart context.
153
+ * @returns {Promise<void>} A promise that resolves after the
154
+ * chart has been rendered.
155
+ */
156
+ const renderChart = useCallback(
157
+ (ctx: CustomChartContext) => {
158
+ setKey((prevKey: number) => prevKey + 1);
159
+ return Promise.resolve();
160
+ },
161
+ [key],
162
+ );
163
+
164
+ useEffect(() => {
165
+ // Create a new custom chart context with the provided props and
166
+ // renderChart function.
167
+ const context = new CustomChartContext({
168
+ ...props,
169
+ renderChart,
170
+ });
171
+ // Initialize the chart context provider.
172
+ initializeContext(context);
173
+ setupEventListeners(context);
174
+ return () => {
175
+ setupEventOffListeners(context);
176
+ };
177
+ }, []);
178
+
179
+ return getChartContextValues(ctx);
180
+ };