@atlaskit/editor-plugin-table 2.7.1 → 2.8.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 (31) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/plugins/table/nodeviews/TableResizer.js +25 -26
  3. package/dist/cjs/plugins/table/toolbar.js +13 -17
  4. package/dist/cjs/plugins/table/utils/analytics.js +146 -2
  5. package/dist/cjs/version.json +1 -1
  6. package/dist/es2019/plugins/table/nodeviews/TableResizer.js +27 -27
  7. package/dist/es2019/plugins/table/toolbar.js +11 -16
  8. package/dist/es2019/plugins/table/utils/analytics.js +130 -0
  9. package/dist/es2019/version.json +1 -1
  10. package/dist/esm/plugins/table/nodeviews/TableResizer.js +26 -27
  11. package/dist/esm/plugins/table/toolbar.js +13 -17
  12. package/dist/esm/plugins/table/utils/analytics.js +139 -0
  13. package/dist/esm/version.json +1 -1
  14. package/dist/types/plugins/table/types.d.ts +0 -1
  15. package/dist/types/plugins/table/utils/analytics.d.ts +28 -1
  16. package/dist/types-ts4.5/plugins/table/types.d.ts +0 -1
  17. package/dist/types-ts4.5/plugins/table/utils/analytics.d.ts +28 -1
  18. package/package.json +3 -2
  19. package/report.api.md +0 -2
  20. package/src/__tests__/integration/floating-toolbar.ts +0 -7
  21. package/src/__tests__/unit/nodeviews/TableContainer.tsx +57 -1
  22. package/src/__tests__/unit/utils/analytics.ts +98 -0
  23. package/src/__tests__/visual-regression/__image_snapshots__/cell-options-menu-ts-table-cell-options-menu-delete-column-menu-item-should-remove-the-table-column-on-click-1-snap.png +2 -2
  24. package/src/__tests__/visual-regression/__image_snapshots__/cell-options-menu-ts-table-cell-options-menu-delete-column-menu-item-visual-hints-should-be-added-to-the-table-column-on-hover-1-snap.png +2 -2
  25. package/src/__tests__/visual-regression/__image_snapshots__/cell-options-menu-ts-table-cell-options-menu-delete-row-menu-item-should-remove-the-table-row-on-click-1-snap.png +2 -2
  26. package/src/__tests__/visual-regression/__image_snapshots__/cell-options-menu-ts-table-cell-options-menu-delete-row-menu-item-visual-hints-should-be-added-to-the-table-row-on-hover-1-snap.png +2 -2
  27. package/src/plugins/table/nodeviews/TableResizer.tsx +35 -34
  28. package/src/plugins/table/toolbar.tsx +13 -32
  29. package/src/plugins/table/types.ts +0 -1
  30. package/src/plugins/table/utils/analytics.ts +168 -0
  31. package/tmp/api-report-tmp.d.ts +0 -2
@@ -29,6 +29,23 @@ import {
29
29
  import { pluginKey } from '../../../plugins/table/pm-plugins/plugin-key';
30
30
  import { TablePluginState } from '../../../plugins/table/types';
31
31
 
32
+ const mockStartMeasure = jest.fn();
33
+ const mockEndMeasure = jest.fn(() => {
34
+ return [51, 52, 53, 54];
35
+ });
36
+ const mockCountFrames = jest.fn();
37
+
38
+ jest.mock('../../../plugins/table/utils/analytics', () => ({
39
+ ...jest.requireActual('../../../plugins/table/utils/analytics'),
40
+ useMeasureFramerate: () => {
41
+ return {
42
+ startMeasure: mockStartMeasure,
43
+ endMeasure: mockEndMeasure,
44
+ countFrames: mockCountFrames,
45
+ };
46
+ },
47
+ }));
48
+
32
49
  describe('table -> nodeviews -> TableContainer.tsx', () => {
33
50
  const createEditor = createEditorFactory<TablePluginState>();
34
51
  const editor = (
@@ -202,7 +219,7 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
202
219
  fireEvent.mouseMove(container.querySelector('.resizer-handle-right')!);
203
220
  fireEvent.mouseUp(container.querySelector('.resizer-handle-right')!);
204
221
 
205
- expect(analyticsMock).toHaveBeenLastCalledWith({
222
+ expect(analyticsMock).toHaveBeenCalledWith({
206
223
  action: TABLE_ACTION.RESIZED,
207
224
  actionSubject: ACTION_SUBJECT.TABLE,
208
225
  eventType: EVENT_TYPE.TRACK,
@@ -215,6 +232,45 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
215
232
  totalColumnCount: 3,
216
233
  },
217
234
  });
235
+
236
+ expect(analyticsMock).toHaveBeenCalledWith({
237
+ action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
238
+ actionSubject: ACTION_SUBJECT.TABLE,
239
+ eventType: EVENT_TYPE.OPERATIONAL,
240
+ attributes: {
241
+ docSize: 22,
242
+ frameRate: 51,
243
+ isInitialSample: true,
244
+ nodeSize: 20,
245
+ },
246
+ });
247
+
248
+ expect(analyticsMock).toHaveBeenCalledWith({
249
+ action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
250
+ actionSubject: ACTION_SUBJECT.TABLE,
251
+ eventType: EVENT_TYPE.OPERATIONAL,
252
+ attributes: {
253
+ docSize: 22,
254
+ frameRate: 53,
255
+ isInitialSample: false,
256
+ nodeSize: 20,
257
+ },
258
+ });
259
+
260
+ analyticsMock.mockReset();
261
+ });
262
+
263
+ test('calls useMeasureFramerate handlers', async () => {
264
+ const { container } = buildContainer({ layout: 'wide' });
265
+
266
+ fireEvent.mouseDown(container.querySelector('.resizer-handle-right')!);
267
+ fireEvent.mouseMove(container.querySelector('.resizer-handle-right')!, {
268
+ clientX: 100,
269
+ });
270
+ fireEvent.mouseUp(container.querySelector('.resizer-handle-right')!);
271
+
272
+ expect(mockStartMeasure).toHaveBeenCalled();
273
+ expect(mockEndMeasure).toHaveBeenCalled();
218
274
  });
219
275
  });
220
276
  });
@@ -0,0 +1,98 @@
1
+ import { renderHook } from '@testing-library/react-hooks';
2
+
3
+ import {
4
+ ACTION_SUBJECT,
5
+ EVENT_TYPE,
6
+ TABLE_ACTION,
7
+ } from '@atlaskit/editor-common/analytics';
8
+
9
+ import {
10
+ generateResizeFrameRatePayloads,
11
+ reduceResizeFrameRateSamples,
12
+ useMeasureFramerate,
13
+ } from '../../../plugins/table/utils/analytics';
14
+
15
+ describe('reduceResizeFrameRateSamples()', () => {
16
+ it('should return the same array if it has only one element', () => {
17
+ expect(reduceResizeFrameRateSamples([1])).toEqual([1]);
18
+ });
19
+
20
+ it('should return the first element and the average of the array if length > 1', () => {
21
+ expect(reduceResizeFrameRateSamples([3, 2, 4, 6])).toEqual([3, 4]);
22
+ });
23
+ });
24
+
25
+ describe('generateResizeFrameRatePayloads()', () => {
26
+ it('should return an empty array if the array is empty', () => {
27
+ expect(
28
+ generateResizeFrameRatePayloads({
29
+ docSize: 10,
30
+ frameRateSamples: [],
31
+ originalNode: { nodeSize: 5 } as any,
32
+ }),
33
+ ).toEqual([]);
34
+ });
35
+
36
+ it('should return an array of payloads with the correct attributes', () => {
37
+ expect(
38
+ generateResizeFrameRatePayloads({
39
+ docSize: 10,
40
+ frameRateSamples: [3, 2, 4, 6],
41
+ originalNode: { nodeSize: 5 } as any,
42
+ }),
43
+ ).toEqual([
44
+ {
45
+ action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
46
+ actionSubject: ACTION_SUBJECT.TABLE,
47
+ eventType: EVENT_TYPE.OPERATIONAL,
48
+ attributes: {
49
+ frameRate: 3,
50
+ nodeSize: 5,
51
+ docSize: 10,
52
+ isInitialSample: true,
53
+ },
54
+ },
55
+ {
56
+ action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
57
+ actionSubject: ACTION_SUBJECT.TABLE,
58
+ eventType: EVENT_TYPE.OPERATIONAL,
59
+ attributes: {
60
+ frameRate: 4,
61
+ nodeSize: 5,
62
+ docSize: 10,
63
+ isInitialSample: false,
64
+ },
65
+ },
66
+ ]);
67
+ });
68
+ });
69
+
70
+ describe('useMeasureFramerate()', () => {
71
+ jest.useFakeTimers('modern');
72
+
73
+ it('should return the correct handlers', () => {
74
+ const { result } = renderHook(() => useMeasureFramerate());
75
+ const { startMeasure, endMeasure, countFrames } = result.current;
76
+
77
+ expect(startMeasure).toBeInstanceOf(Function);
78
+ expect(endMeasure).toBeInstanceOf(Function);
79
+ expect(countFrames).toBeInstanceOf(Function);
80
+ });
81
+
82
+ it('should return the correct frame rate sample', async () => {
83
+ const { result } = renderHook(() =>
84
+ useMeasureFramerate({ minTimeMs: 0, minFrames: 0, sampleRateMs: 0 }),
85
+ );
86
+
87
+ const { startMeasure, endMeasure, countFrames } = result.current;
88
+ jest.advanceTimersByTime(100);
89
+ startMeasure();
90
+ jest.advanceTimersByTime(100);
91
+ countFrames();
92
+ const samples = endMeasure();
93
+
94
+ expect(samples).toEqual([10]);
95
+
96
+ jest.useRealTimers();
97
+ });
98
+ });
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9c8221121bc35d8d4d1dd7b3d30212732802458bbae60bbae450b9b88bdd3ed3
3
- size 14078
2
+ oid sha256:1b71f23d11823e24b9a7ce319b38e1c2625f9d88301ebd4ce03074214202caa2
3
+ size 15206
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7e1eedc6e6df9262101a20fae98396c94f8ea7086cb5a6424bcc01f9b5616810
3
- size 29828
2
+ oid sha256:eca71983bb60a79e2f607ec05ab3d99ff58bc8551b232219dcf24fd8e176fea3
3
+ size 32711
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:185a432916fe30487eec362617c510dd132f2a37a56bfceeca674da4c6512dc9
3
- size 13986
2
+ oid sha256:6f2033158fd0e520d5dff43ca6308b8312b2321e246ffb0604aee1de57368e6b
3
+ size 15136
@@ -1,3 +1,3 @@
1
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:164b040aa0254b6bdc19497a86b9d9a226cde6545661f2f475f87fe9e200f3b5
3
- size 29856
2
+ oid sha256:559ec334bb7930be94de71631ca380ff968ac8d58ce4de6a3dc20881a1e48ab9
3
+ size 32765
@@ -8,12 +8,7 @@ import React, {
8
8
 
9
9
  import rafSchd from 'raf-schd';
10
10
 
11
- import {
12
- ACTION_SUBJECT,
13
- EVENT_TYPE,
14
- TABLE_ACTION,
15
- TableEventPayload,
16
- } from '@atlaskit/editor-common/analytics';
11
+ import { TableEventPayload } from '@atlaskit/editor-common/analytics';
17
12
  import { getGuidelinesWithHighlights } from '@atlaskit/editor-common/guideline';
18
13
  import type { GuidelineConfig } from '@atlaskit/editor-common/guideline';
19
14
  import {
@@ -25,21 +20,24 @@ import { resizerHandleShadowClassName } from '@atlaskit/editor-common/styles';
25
20
  import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
26
21
  import { Transaction } from '@atlaskit/editor-prosemirror/state';
27
22
  import { EditorView } from '@atlaskit/editor-prosemirror/view';
28
- import { TableMap } from '@atlaskit/editor-tables';
29
23
  import { findTable } from '@atlaskit/editor-tables/utils';
30
24
 
31
25
  import {
32
26
  COLUMN_MIN_WIDTH,
33
27
  getColgroupChildrenLength,
34
- hasTableBeenResized,
35
28
  previewScaleTable,
36
29
  scaleTable,
37
30
  } from '../pm-plugins/table-resizing/utils';
38
31
  import { pluginKey as tableWidthPluginKey } from '../pm-plugins/table-width';
39
32
  import { TABLE_HIGHLIGHT_GAP, TABLE_SNAP_GAP } from '../ui/consts';
40
- import { getTableWidth } from '../utils';
33
+ import {
34
+ generateResizedPayload,
35
+ generateResizeFrameRatePayloads,
36
+ useMeasureFramerate,
37
+ } from '../utils/analytics';
41
38
  import { defaultGuidelines, defaultGuidelineWidths } from '../utils/guidelines';
42
39
  import { findClosestSnap } from '../utils/snapping';
40
+
43
41
  interface TableResizerProps {
44
42
  width: number;
45
43
  maxWidth: number;
@@ -57,29 +55,6 @@ interface TableResizerProps {
57
55
  const handles = { right: true };
58
56
  const tableHandleMarginTop = 12;
59
57
 
60
- const generateResizedPayload = (props: {
61
- originalNode: PMNode;
62
- resizedNode: PMNode;
63
- }): TableEventPayload => {
64
- const tableMap = TableMap.get(props.resizedNode);
65
-
66
- return {
67
- action: TABLE_ACTION.RESIZED,
68
- actionSubject: ACTION_SUBJECT.TABLE,
69
- eventType: EVENT_TYPE.TRACK,
70
- attributes: {
71
- newWidth: props.resizedNode.attrs.width,
72
- prevWidth: props.originalNode.attrs.width ?? null,
73
- nodeSize: props.resizedNode.nodeSize,
74
- totalTableWidth: hasTableBeenResized(props.resizedNode)
75
- ? getTableWidth(props.resizedNode)
76
- : null,
77
- totalRowCount: tableMap.height,
78
- totalColumnCount: tableMap.width,
79
- },
80
- };
81
- };
82
-
83
58
  const getResizerHandleHeight = (tableRef: HTMLTableElement) => {
84
59
  const tableHeight = tableRef?.clientHeight;
85
60
  let handleHeightSize: HandleHeightSizeType | undefined = 'small';
@@ -129,6 +104,8 @@ export const TableResizer = ({
129
104
  const resizerMinWidth = getResizerMinWidth(node);
130
105
  const handleHeightSize = getResizerHandleHeight(tableRef);
131
106
 
107
+ const { startMeasure, endMeasure, countFrames } = useMeasureFramerate();
108
+
132
109
  const updateActiveGuidelines = useCallback(
133
110
  ({ gap, keys }: { gap: number; keys: string[] }) => {
134
111
  if (gap !== currentGap.current) {
@@ -157,6 +134,8 @@ export const TableResizer = ({
157
134
  );
158
135
 
159
136
  const handleResizeStart = useCallback(() => {
137
+ startMeasure();
138
+
160
139
  const {
161
140
  dispatch,
162
141
  state: { tr },
@@ -165,7 +144,7 @@ export const TableResizer = ({
165
144
  dispatch(tr.setMeta(tableWidthPluginKey, { resizing: true }));
166
145
 
167
146
  setSnappingEnabled(displayGuideline(defaultGuidelines));
168
- }, [displayGuideline, editorView]);
147
+ }, [displayGuideline, editorView, startMeasure]);
169
148
 
170
149
  const handleResizeStop = useCallback<HandleResize>(
171
150
  (originalState, delta) => {
@@ -174,6 +153,18 @@ export const TableResizer = ({
174
153
  const pos = getPos();
175
154
 
176
155
  let tr = state.tr.setMeta(tableWidthPluginKey, { resizing: false });
156
+ const frameRateSamples = endMeasure();
157
+
158
+ if (frameRateSamples.length > 0) {
159
+ const resizeFrameRatePayloads = generateResizeFrameRatePayloads({
160
+ docSize: state.doc.nodeSize,
161
+ frameRateSamples,
162
+ originalNode: node,
163
+ });
164
+ resizeFrameRatePayloads.forEach((payload) => {
165
+ attachAnalyticsEvent(payload)?.(tr);
166
+ });
167
+ }
177
168
 
178
169
  if (typeof pos === 'number') {
179
170
  tr = tr.setNodeMarkup(pos, undefined, {
@@ -219,11 +210,13 @@ export const TableResizer = ({
219
210
  tableRef,
220
211
  displayGuideline,
221
212
  attachAnalyticsEvent,
213
+ endMeasure,
222
214
  ],
223
215
  );
224
216
 
225
217
  const handleResize = useCallback(
226
218
  (originalState, delta) => {
219
+ countFrames();
227
220
  const newWidth = originalState.width + delta.width;
228
221
  const pos = getPos();
229
222
  if (typeof pos !== 'number') {
@@ -254,7 +247,15 @@ export const TableResizer = ({
254
247
 
255
248
  return newWidth;
256
249
  },
257
- [editorView, getPos, node, tableRef, updateWidth, updateActiveGuidelines],
250
+ [
251
+ editorView,
252
+ getPos,
253
+ node,
254
+ tableRef,
255
+ updateWidth,
256
+ updateActiveGuidelines,
257
+ countFrames,
258
+ ],
258
259
  );
259
260
 
260
261
  const scheduleResize = useMemo(() => rafSchd(handleResize), [handleResize]);
@@ -452,8 +452,6 @@ export const getToolbarConfig =
452
452
  editorAnalyticsAPI,
453
453
  );
454
454
 
455
- const { tableCellOptionsInFloatingToolbar } =
456
- getEditorFeatureFlags() || {};
457
455
  const cellItems = getCellItems(
458
456
  config,
459
457
  state,
@@ -461,15 +459,8 @@ export const getToolbarConfig =
461
459
  intl,
462
460
  getEditorContainerWidth,
463
461
  editorAnalyticsAPI,
464
- tableCellOptionsInFloatingToolbar,
465
- );
466
- const colorPicker = getColorPicker(
467
- state,
468
- menu,
469
- intl,
470
- editorAnalyticsAPI,
471
- tableCellOptionsInFloatingToolbar,
472
462
  );
463
+ const colorPicker = getColorPicker(state, menu, intl, editorAnalyticsAPI);
473
464
 
474
465
  // Check if we need to show confirm dialog for delete button
475
466
  let confirmDialog;
@@ -590,24 +581,18 @@ const getCellItems = (
590
581
  { formatMessage }: ToolbarMenuContext,
591
582
  getEditorContainerWidth: GetEditorContainerWidth,
592
583
  editorAnalyticsAPI: EditorAnalyticsAPI | undefined | null,
593
- tableCellOptionsInFloatingToolbar?: boolean,
594
584
  ): Array<FloatingToolbarItem<Command>> => {
595
- if (
596
- pluginConfig.allowCellOptionsInFloatingToolbar ||
597
- tableCellOptionsInFloatingToolbar
598
- ) {
599
- const initialSelectionRect = getClosestSelectionRect(state);
600
- if (initialSelectionRect) {
601
- const cellOptions = getToolbarCellOptionsConfig(
602
- state,
603
- view,
604
- initialSelectionRect,
605
- { formatMessage },
606
- getEditorContainerWidth,
607
- editorAnalyticsAPI,
608
- );
609
- return [cellOptions, separator(cellOptions.hidden!)];
610
- }
585
+ const initialSelectionRect = getClosestSelectionRect(state);
586
+ if (initialSelectionRect) {
587
+ const cellOptions = getToolbarCellOptionsConfig(
588
+ state,
589
+ view,
590
+ initialSelectionRect,
591
+ { formatMessage },
592
+ getEditorContainerWidth,
593
+ editorAnalyticsAPI,
594
+ );
595
+ return [cellOptions, separator(cellOptions.hidden!)];
611
596
  }
612
597
  return [];
613
598
  };
@@ -617,13 +602,9 @@ const getColorPicker = (
617
602
  menu: FloatingToolbarItem<Command>,
618
603
  { formatMessage }: ToolbarMenuContext,
619
604
  editorAnalyticsAPI: EditorAnalyticsAPI | null | undefined,
620
- tableCellOptionsInFloatingToolbar?: boolean,
621
605
  ): Array<FloatingToolbarItem<Command>> => {
622
606
  const { targetCellPosition, pluginConfig } = getPluginState(state);
623
- if (
624
- !pluginConfig.allowBackgroundColor ||
625
- !tableCellOptionsInFloatingToolbar
626
- ) {
607
+ if (!pluginConfig.allowBackgroundColor) {
627
608
  return [];
628
609
  }
629
610
  const node = targetCellPosition
@@ -53,7 +53,6 @@ export interface PluginConfig {
53
53
  permittedLayouts?: PermittedLayoutsDescriptor;
54
54
  allowControls?: boolean;
55
55
  stickyHeaders?: boolean;
56
- allowCellOptionsInFloatingToolbar?: boolean;
57
56
  tableCellOptimization?: boolean;
58
57
  tableRenderOptimization?: boolean;
59
58
  stickyHeadersOptimization?: boolean;
@@ -1,13 +1,25 @@
1
+ import { useEffect, useRef } from 'react';
2
+
1
3
  import type {
2
4
  AnalyticsEventPayload,
3
5
  AnalyticsEventPayloadCallback,
4
6
  EditorAnalyticsAPI,
7
+ TableEventPayload,
8
+ } from '@atlaskit/editor-common/analytics';
9
+ import {
10
+ ACTION_SUBJECT,
11
+ EVENT_TYPE,
12
+ TABLE_ACTION,
5
13
  } from '@atlaskit/editor-common/analytics';
6
14
  import { HigherOrderCommand } from '@atlaskit/editor-common/types';
15
+ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
7
16
  import { Selection } from '@atlaskit/editor-prosemirror/state';
8
17
  import { TableMap } from '@atlaskit/editor-tables/table-map';
9
18
  import { findTable, getSelectionRect } from '@atlaskit/editor-tables/utils';
10
19
 
20
+ import { hasTableBeenResized } from '../pm-plugins/table-resizing/utils';
21
+ import { getTableWidth } from '../utils';
22
+
11
23
  export function getSelectedTableInfo(selection: Selection) {
12
24
  let map;
13
25
  let totalRowCount = 0;
@@ -78,3 +90,159 @@ export const withEditorAnalyticsAPI =
78
90
  view,
79
91
  );
80
92
  };
93
+
94
+ interface UseMeasureFramerateConfig {
95
+ maxSamples?: number;
96
+ minFrames?: number;
97
+ minTimeMs?: number;
98
+ sampleRateMs?: number;
99
+ timeoutMs?: number;
100
+ }
101
+
102
+ export const generateResizedPayload = (props: {
103
+ originalNode: PMNode;
104
+ resizedNode: PMNode;
105
+ }): TableEventPayload => {
106
+ const tableMap = TableMap.get(props.resizedNode);
107
+
108
+ return {
109
+ action: TABLE_ACTION.RESIZED,
110
+ actionSubject: ACTION_SUBJECT.TABLE,
111
+ eventType: EVENT_TYPE.TRACK,
112
+ attributes: {
113
+ newWidth: props.resizedNode.attrs.width,
114
+ prevWidth: props.originalNode.attrs.width ?? null,
115
+ nodeSize: props.resizedNode.nodeSize,
116
+ totalTableWidth: hasTableBeenResized(props.resizedNode)
117
+ ? getTableWidth(props.resizedNode)
118
+ : null,
119
+ totalRowCount: tableMap.height,
120
+ totalColumnCount: tableMap.width,
121
+ },
122
+ };
123
+ };
124
+
125
+ export const reduceResizeFrameRateSamples = (frameRateSamples: number[]) => {
126
+ if (frameRateSamples.length > 1) {
127
+ const frameRateSum = frameRateSamples.reduce((sum, frameRate, index) => {
128
+ if (index === 0) {
129
+ return sum;
130
+ } else {
131
+ return sum + frameRate;
132
+ }
133
+ }, 0);
134
+ const averageFrameRate = Math.round(
135
+ frameRateSum / (frameRateSamples.length - 1),
136
+ );
137
+ return [frameRateSamples[0], averageFrameRate];
138
+ } else {
139
+ return frameRateSamples;
140
+ }
141
+ };
142
+
143
+ export const generateResizeFrameRatePayloads = (props: {
144
+ docSize: number;
145
+ frameRateSamples: number[];
146
+ originalNode: PMNode;
147
+ }): TableEventPayload[] => {
148
+ const reducedResizeFrameRateSamples = reduceResizeFrameRateSamples(
149
+ props.frameRateSamples,
150
+ );
151
+ return reducedResizeFrameRateSamples.map((frameRateSample, index) => ({
152
+ action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
153
+ actionSubject: ACTION_SUBJECT.TABLE,
154
+ eventType: EVENT_TYPE.OPERATIONAL,
155
+ attributes: {
156
+ frameRate: frameRateSample,
157
+ nodeSize: props.originalNode.nodeSize,
158
+ docSize: props.docSize,
159
+ isInitialSample: index === 0,
160
+ },
161
+ }));
162
+ };
163
+
164
+ /**
165
+ * Measures the framerate of a component over a given time period.
166
+ */
167
+ export const useMeasureFramerate = (config: UseMeasureFramerateConfig = {}) => {
168
+ const {
169
+ maxSamples = 10,
170
+ minFrames = 5,
171
+ minTimeMs = 500,
172
+ sampleRateMs = 1000,
173
+ timeoutMs = 200,
174
+ } = config;
175
+
176
+ let frameCount = useRef(0);
177
+ let lastTime = useRef(0);
178
+ let timeoutId = useRef<NodeJS.Timeout | undefined>();
179
+ let frameRateSamples = useRef<number[]>([]);
180
+
181
+ useEffect(() => {
182
+ return () => {
183
+ if (timeoutId.current) {
184
+ clearTimeout(timeoutId.current);
185
+ }
186
+ };
187
+ }, []);
188
+
189
+ const startMeasure = () => {
190
+ frameCount.current = 0;
191
+ lastTime.current = performance.now();
192
+ };
193
+
194
+ /**
195
+ * Returns an array of frame rate samples as integers.
196
+ */
197
+ const endMeasure = () => {
198
+ const samples = frameRateSamples.current;
199
+ frameRateSamples.current = [];
200
+ return samples;
201
+ };
202
+
203
+ const sampleFrameRate = (delay = 0) => {
204
+ const currentTime = performance.now();
205
+ const deltaTime = currentTime - lastTime.current - delay;
206
+ const isValidSample =
207
+ deltaTime > minTimeMs && frameCount.current >= minFrames;
208
+ if (isValidSample) {
209
+ const frameRate = Math.round(frameCount.current / (deltaTime / 1000));
210
+ frameRateSamples.current.push(frameRate);
211
+ }
212
+ frameCount.current = 0;
213
+ lastTime.current = 0;
214
+ };
215
+
216
+ /**
217
+ * Counts the number of frames that occur within a given time period. Intended to be called
218
+ * inside a `requestAnimationFrame` callback.
219
+ */
220
+ const countFrames = () => {
221
+ if (frameRateSamples.current.length >= maxSamples && timeoutId.current) {
222
+ clearTimeout(timeoutId.current);
223
+ return;
224
+ }
225
+
226
+ /**
227
+ * Allows us to keep counting frames even if `startMeasure` is not called
228
+ */
229
+ if (lastTime.current === 0) {
230
+ lastTime.current = performance.now();
231
+ }
232
+ frameCount.current++;
233
+
234
+ if (timeoutId.current) {
235
+ clearTimeout(timeoutId.current);
236
+ }
237
+ if (performance.now() - lastTime.current > sampleRateMs) {
238
+ sampleFrameRate();
239
+ } else {
240
+ timeoutId.current = setTimeout(
241
+ () => sampleFrameRate(timeoutMs),
242
+ timeoutMs,
243
+ );
244
+ }
245
+ };
246
+
247
+ return { startMeasure, endMeasure, countFrames };
248
+ };
@@ -30,8 +30,6 @@ interface PluginConfig {
30
30
  // (undocumented)
31
31
  allowBackgroundColor?: boolean;
32
32
  // (undocumented)
33
- allowCellOptionsInFloatingToolbar?: boolean;
34
- // (undocumented)
35
33
  allowCollapse?: boolean;
36
34
  // (undocumented)
37
35
  allowColumnResizing?: boolean;