@atlaskit/editor-plugin-table 5.4.25 → 5.5.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.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/plugins/table/nodeviews/TableResizer.js +150 -10
- package/dist/es2019/plugins/table/nodeviews/TableResizer.js +143 -5
- package/dist/esm/plugins/table/nodeviews/TableResizer.js +145 -5
- package/package.json +4 -1
- package/src/__tests__/unit/nodeviews/TableContainer.tsx +85 -0
- package/src/plugins/table/nodeviews/TableResizer.tsx +232 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-table
|
|
2
2
|
|
|
3
|
+
## 5.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#56823](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/56823) [`d2fbdf3b6822`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/d2fbdf3b6822) - [ux] ECA11Y-111: Keyboard accessibility of table resizer
|
|
8
|
+
|
|
3
9
|
## 5.4.25
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -9,17 +9,20 @@ exports.TableResizer = void 0;
|
|
|
9
9
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
10
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
11
|
var _react = _interopRequireWildcard(require("react"));
|
|
12
|
+
var _debounce = _interopRequireDefault(require("lodash/debounce"));
|
|
12
13
|
var _rafSchd = _interopRequireDefault(require("raf-schd"));
|
|
13
14
|
var _reactIntlNext = require("react-intl-next");
|
|
14
15
|
var _analytics = require("@atlaskit/editor-common/analytics");
|
|
15
16
|
var _guideline = require("@atlaskit/editor-common/guideline");
|
|
17
|
+
var _keymaps = require("@atlaskit/editor-common/keymaps");
|
|
16
18
|
var _messages = require("@atlaskit/editor-common/messages");
|
|
17
19
|
var _resizer = require("@atlaskit/editor-common/resizer");
|
|
18
|
-
var _utils = require("@atlaskit/editor-
|
|
20
|
+
var _utils = require("@atlaskit/editor-common/utils");
|
|
21
|
+
var _utils2 = require("@atlaskit/editor-tables/utils");
|
|
19
22
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
20
23
|
var _pluginFactory = require("../pm-plugins/plugin-factory");
|
|
21
24
|
var _tableAnalytics = require("../pm-plugins/table-analytics");
|
|
22
|
-
var
|
|
25
|
+
var _utils3 = require("../pm-plugins/table-resizing/utils");
|
|
23
26
|
var _tableWidth = require("../pm-plugins/table-width");
|
|
24
27
|
var _consts = require("../ui/consts");
|
|
25
28
|
var _analytics2 = require("../utils/analytics");
|
|
@@ -29,6 +32,8 @@ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "functio
|
|
|
29
32
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
30
33
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
31
34
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
35
|
+
var DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER = 1000;
|
|
36
|
+
var RESIZE_STEP_VALUE = 10;
|
|
32
37
|
var handles = {
|
|
33
38
|
right: true
|
|
34
39
|
};
|
|
@@ -59,8 +64,8 @@ var getResizerHandleHeight = function getResizerHandleHeight(tableRef) {
|
|
|
59
64
|
return 'small';
|
|
60
65
|
};
|
|
61
66
|
var getResizerMinWidth = function getResizerMinWidth(node) {
|
|
62
|
-
var currentColumnCount = (0,
|
|
63
|
-
var minColumnWidth = currentColumnCount <= 3 ? currentColumnCount *
|
|
67
|
+
var currentColumnCount = (0, _utils3.getColgroupChildrenLength)(node);
|
|
68
|
+
var minColumnWidth = currentColumnCount <= 3 ? currentColumnCount * _utils3.COLUMN_MIN_WIDTH : 3 * _utils3.COLUMN_MIN_WIDTH;
|
|
64
69
|
// add an extra pixel as the scale table logic will scale columns to be tableContainerWidth - 1
|
|
65
70
|
// the table can't scale past its min-width, so instead restrict table container min width to avoid that situation
|
|
66
71
|
return minColumnWidth + 1;
|
|
@@ -96,12 +101,36 @@ var TableResizer = exports.TableResizer = function TableResizer(_ref) {
|
|
|
96
101
|
var currentGap = (0, _react.useRef)(0);
|
|
97
102
|
// track resizing state - use ref over state to avoid re-render
|
|
98
103
|
var isResizing = (0, _react.useRef)(false);
|
|
104
|
+
var areResizeMetaKeysPressed = (0, _react.useRef)(false);
|
|
105
|
+
var resizerRef = (0, _react.useRef)(null);
|
|
106
|
+
|
|
107
|
+
// used to reposition tooltip when table is resizing via keyboard
|
|
108
|
+
var updateTooltip = _react.default.useRef();
|
|
99
109
|
var _useState = (0, _react.useState)(false),
|
|
100
110
|
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
101
111
|
snappingEnabled = _useState2[0],
|
|
102
112
|
setSnappingEnabled = _useState2[1];
|
|
113
|
+
|
|
114
|
+
// we don't want to update aria-live region on each width change, it might provide bad experience for screen reader users
|
|
115
|
+
var _useState3 = (0, _react.useState)({
|
|
116
|
+
type: 'none',
|
|
117
|
+
width: width
|
|
118
|
+
}),
|
|
119
|
+
_useState4 = (0, _slicedToArray2.default)(_useState3, 2),
|
|
120
|
+
screenReaderResizeInformation = _useState4[0],
|
|
121
|
+
setScreenReaderResizeInformation = _useState4[1];
|
|
103
122
|
var _useIntl = (0, _reactIntlNext.useIntl)(),
|
|
104
123
|
formatMessage = _useIntl.formatMessage;
|
|
124
|
+
var screenReaderResizeAnnouncerMessages = {
|
|
125
|
+
increase: formatMessage(_messages.tableMessages.tableSizeIncreaseScreenReaderInformation, {
|
|
126
|
+
newWidth: screenReaderResizeInformation.width
|
|
127
|
+
}),
|
|
128
|
+
decrease: formatMessage(_messages.tableMessages.tableSizeDecreaseScreenReaderInformation, {
|
|
129
|
+
newWidth: screenReaderResizeInformation.width
|
|
130
|
+
}),
|
|
131
|
+
none: ''
|
|
132
|
+
};
|
|
133
|
+
var isTableSelected = ((_findTable = (0, _utils2.findTable)((_editorView$state = editorView.state) === null || _editorView$state === void 0 ? void 0 : _editorView$state.selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) === getPos();
|
|
105
134
|
var resizerMinWidth = getResizerMinWidth(node);
|
|
106
135
|
var handleSize = getResizerHandleHeight(tableRef);
|
|
107
136
|
var _getPluginState = (0, _pluginFactory.getPluginState)(editorView.state),
|
|
@@ -171,7 +200,7 @@ var TableResizer = exports.TableResizer = function TableResizer(_ref) {
|
|
|
171
200
|
if (typeof pos !== 'number') {
|
|
172
201
|
return;
|
|
173
202
|
}
|
|
174
|
-
(0,
|
|
203
|
+
(0, _utils3.previewScaleTable)(tableRef, {
|
|
175
204
|
node: node,
|
|
176
205
|
prevNode: node,
|
|
177
206
|
start: pos + 1,
|
|
@@ -211,7 +240,7 @@ var TableResizer = exports.TableResizer = function TableResizer(_ref) {
|
|
|
211
240
|
width: newWidth
|
|
212
241
|
}));
|
|
213
242
|
var newNode = tr.doc.nodeAt(pos);
|
|
214
|
-
tr = (0,
|
|
243
|
+
tr = (0, _utils3.scaleTable)(tableRef, {
|
|
215
244
|
node: newNode,
|
|
216
245
|
prevNode: node,
|
|
217
246
|
start: pos + 1,
|
|
@@ -235,8 +264,109 @@ var TableResizer = exports.TableResizer = function TableResizer(_ref) {
|
|
|
235
264
|
}
|
|
236
265
|
return newWidth;
|
|
237
266
|
}, [displayGapCursor, updateWidth, editorView, getPos, node, tableRef, scheduleResize, displayGuideline, attachAnalyticsEvent, endMeasure, onResizeStop]);
|
|
238
|
-
var
|
|
239
|
-
|
|
267
|
+
var handleTableSizeChangeOnKeypress = (0, _react.useCallback)(function (step) {
|
|
268
|
+
var newWidth = width + step;
|
|
269
|
+
if (newWidth > maxWidth || newWidth < resizerMinWidth) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
handleResizeStop({
|
|
273
|
+
width: width,
|
|
274
|
+
x: 0,
|
|
275
|
+
y: 0,
|
|
276
|
+
height: 0
|
|
277
|
+
}, {
|
|
278
|
+
width: step,
|
|
279
|
+
height: 0
|
|
280
|
+
});
|
|
281
|
+
}, [width, handleResizeStop, maxWidth, resizerMinWidth]);
|
|
282
|
+
var handleEscape = (0, _react.useCallback)(function () {
|
|
283
|
+
editorView === null || editorView === void 0 || editorView.focus();
|
|
284
|
+
}, [editorView]);
|
|
285
|
+
var handleKeyDown = (0, _react.useCallback)(function (event) {
|
|
286
|
+
var isBracketKey = event.code === 'BracketRight' || event.code === 'BracketLeft';
|
|
287
|
+
var metaKey = _utils.browser.mac ? event.metaKey : event.ctrlKey;
|
|
288
|
+
if (event.altKey || metaKey || event.shiftKey) {
|
|
289
|
+
areResizeMetaKeysPressed.current = true;
|
|
290
|
+
}
|
|
291
|
+
if (event.altKey && metaKey) {
|
|
292
|
+
if (isBracketKey) {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
handleTableSizeChangeOnKeypress(event.code === 'BracketRight' ? RESIZE_STEP_VALUE : -RESIZE_STEP_VALUE);
|
|
295
|
+
}
|
|
296
|
+
} else if (!areResizeMetaKeysPressed.current) {
|
|
297
|
+
handleEscape();
|
|
298
|
+
}
|
|
299
|
+
}, [handleEscape, handleTableSizeChangeOnKeypress]);
|
|
300
|
+
var handleKeyUp = (0, _react.useCallback)(function (event) {
|
|
301
|
+
if (event.altKey || event.metaKey) {
|
|
302
|
+
areResizeMetaKeysPressed.current = false;
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}, [areResizeMetaKeysPressed]);
|
|
306
|
+
(0, _react.useLayoutEffect)(function () {
|
|
307
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.a11y-table-resizing_uapcv')) {
|
|
308
|
+
if (!resizerRef.current) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
var resizeHandleThumbEl = resizerRef.current.getResizerThumbEl();
|
|
312
|
+
var globalKeyDownHandler = function globalKeyDownHandler(event) {
|
|
313
|
+
var metaKey = _utils.browser.mac ? event.metaKey : event.ctrlKey;
|
|
314
|
+
if (!isTableSelected) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (event.altKey && event.shiftKey && metaKey && event.code === 'KeyR') {
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
if (!resizeHandleThumbEl) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
resizeHandleThumbEl.focus();
|
|
323
|
+
resizeHandleThumbEl.scrollIntoView({
|
|
324
|
+
behavior: 'smooth',
|
|
325
|
+
block: 'center',
|
|
326
|
+
inline: 'nearest'
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var editorViewDom = editorView === null || editorView === void 0 ? void 0 : editorView.dom;
|
|
331
|
+
editorViewDom === null || editorViewDom === void 0 || editorViewDom.addEventListener('keydown', globalKeyDownHandler);
|
|
332
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.addEventListener('keydown', handleKeyDown);
|
|
333
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.addEventListener('keyup', handleKeyUp);
|
|
334
|
+
return function () {
|
|
335
|
+
editorViewDom === null || editorViewDom === void 0 || editorViewDom.removeEventListener('keydown', globalKeyDownHandler);
|
|
336
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.removeEventListener('keydown', handleKeyDown);
|
|
337
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.removeEventListener('keyup', handleKeyUp);
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}, [resizerRef, editorView, handleResizeStop, isTableSelected, handleKeyDown, handleKeyUp]);
|
|
341
|
+
(0, _react.useLayoutEffect)(function () {
|
|
342
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.a11y-table-resizing_uapcv')) {
|
|
343
|
+
var _updateTooltip$curren;
|
|
344
|
+
(_updateTooltip$curren = updateTooltip.current) === null || _updateTooltip$curren === void 0 || _updateTooltip$curren.call(updateTooltip);
|
|
345
|
+
}
|
|
346
|
+
}, [width]);
|
|
347
|
+
(0, _react.useEffect)(function () {
|
|
348
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.editor.a11y-table-resizing_uapcv')) {
|
|
349
|
+
var debouncedSetWidth = (0, _debounce.default)(setScreenReaderResizeInformation, DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER);
|
|
350
|
+
debouncedSetWidth(function (prevState) {
|
|
351
|
+
var type = 'none';
|
|
352
|
+
if (prevState.width > width) {
|
|
353
|
+
type = 'decrease';
|
|
354
|
+
}
|
|
355
|
+
if (prevState.width < width) {
|
|
356
|
+
type = 'increase';
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
type: type,
|
|
360
|
+
width: width
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
return function () {
|
|
364
|
+
debouncedSetWidth.cancel();
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}, [width]);
|
|
368
|
+
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_resizer.ResizerNext, {
|
|
369
|
+
ref: resizerRef,
|
|
240
370
|
enable: handles,
|
|
241
371
|
width: width,
|
|
242
372
|
handleAlignmentMethod: "sticky",
|
|
@@ -254,6 +384,16 @@ var TableResizer = exports.TableResizer = function TableResizer(_ref) {
|
|
|
254
384
|
isHandleVisible: isTableSelected,
|
|
255
385
|
appearance: isTableSelected && isWholeTableInDanger ? 'danger' : undefined,
|
|
256
386
|
handleHighlight: "shadow",
|
|
257
|
-
handleTooltipContent:
|
|
258
|
-
|
|
387
|
+
handleTooltipContent: (0, _platformFeatureFlags.getBooleanFF)('platform.editor.a11y-table-resizing_uapcv') ? function (_ref3) {
|
|
388
|
+
var update = _ref3.update;
|
|
389
|
+
updateTooltip.current = update;
|
|
390
|
+
return /*#__PURE__*/_react.default.createElement(_keymaps.ToolTipContent, {
|
|
391
|
+
description: formatMessage(_messages.tableMessages.resizeTable),
|
|
392
|
+
keymap: _keymaps.focusTableResizer
|
|
393
|
+
});
|
|
394
|
+
} : formatMessage(_messages.tableMessages.resizeTable)
|
|
395
|
+
}, children), (0, _platformFeatureFlags.getBooleanFF)('platform.editor.a11y-table-resizing_uapcv') && /*#__PURE__*/_react.default.createElement("div", {
|
|
396
|
+
className: "assistive",
|
|
397
|
+
role: "status"
|
|
398
|
+
}, screenReaderResizeAnnouncerMessages[screenReaderResizeInformation.type]));
|
|
259
399
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import debounce from 'lodash/debounce';
|
|
2
3
|
import rafSchd from 'raf-schd';
|
|
3
4
|
import { useIntl } from 'react-intl-next';
|
|
4
5
|
import { TABLE_OVERFLOW_CHANGE_TRIGGER } from '@atlaskit/editor-common/analytics';
|
|
5
6
|
import { getGuidelinesWithHighlights } from '@atlaskit/editor-common/guideline';
|
|
7
|
+
import { focusTableResizer, ToolTipContent } from '@atlaskit/editor-common/keymaps';
|
|
6
8
|
import { tableMessages as messages } from '@atlaskit/editor-common/messages';
|
|
7
9
|
import { ResizerNext } from '@atlaskit/editor-common/resizer';
|
|
10
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
8
11
|
import { findTable } from '@atlaskit/editor-tables/utils';
|
|
9
12
|
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
10
13
|
import { getPluginState } from '../pm-plugins/plugin-factory';
|
|
@@ -15,6 +18,8 @@ import { TABLE_HIGHLIGHT_GAP, TABLE_HIGHLIGHT_TOLERANCE, TABLE_SNAP_GAP } from '
|
|
|
15
18
|
import { generateResizedPayload, generateResizeFrameRatePayloads, useMeasureFramerate } from '../utils/analytics';
|
|
16
19
|
import { defaultGuidelines } from '../utils/guidelines';
|
|
17
20
|
import { defaultSnappingWidths, findClosestSnap } from '../utils/snapping';
|
|
21
|
+
const DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER = 1000;
|
|
22
|
+
const RESIZE_STEP_VALUE = 10;
|
|
18
23
|
const handles = {
|
|
19
24
|
right: true
|
|
20
25
|
};
|
|
@@ -83,10 +88,31 @@ export const TableResizer = ({
|
|
|
83
88
|
const currentGap = useRef(0);
|
|
84
89
|
// track resizing state - use ref over state to avoid re-render
|
|
85
90
|
const isResizing = useRef(false);
|
|
91
|
+
const areResizeMetaKeysPressed = useRef(false);
|
|
92
|
+
const resizerRef = useRef(null);
|
|
93
|
+
|
|
94
|
+
// used to reposition tooltip when table is resizing via keyboard
|
|
95
|
+
const updateTooltip = React.useRef();
|
|
86
96
|
const [snappingEnabled, setSnappingEnabled] = useState(false);
|
|
97
|
+
|
|
98
|
+
// we don't want to update aria-live region on each width change, it might provide bad experience for screen reader users
|
|
99
|
+
const [screenReaderResizeInformation, setScreenReaderResizeInformation] = useState({
|
|
100
|
+
type: 'none',
|
|
101
|
+
width
|
|
102
|
+
});
|
|
87
103
|
const {
|
|
88
104
|
formatMessage
|
|
89
105
|
} = useIntl();
|
|
106
|
+
const screenReaderResizeAnnouncerMessages = {
|
|
107
|
+
increase: formatMessage(messages.tableSizeIncreaseScreenReaderInformation, {
|
|
108
|
+
newWidth: screenReaderResizeInformation.width
|
|
109
|
+
}),
|
|
110
|
+
decrease: formatMessage(messages.tableSizeDecreaseScreenReaderInformation, {
|
|
111
|
+
newWidth: screenReaderResizeInformation.width
|
|
112
|
+
}),
|
|
113
|
+
none: ''
|
|
114
|
+
};
|
|
115
|
+
const isTableSelected = ((_findTable = findTable((_editorView$state = editorView.state) === null || _editorView$state === void 0 ? void 0 : _editorView$state.selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) === getPos();
|
|
90
116
|
const resizerMinWidth = getResizerMinWidth(node);
|
|
91
117
|
const handleSize = getResizerHandleHeight(tableRef);
|
|
92
118
|
const {
|
|
@@ -230,8 +256,109 @@ export const TableResizer = ({
|
|
|
230
256
|
}
|
|
231
257
|
return newWidth;
|
|
232
258
|
}, [displayGapCursor, updateWidth, editorView, getPos, node, tableRef, scheduleResize, displayGuideline, attachAnalyticsEvent, endMeasure, onResizeStop]);
|
|
233
|
-
const
|
|
234
|
-
|
|
259
|
+
const handleTableSizeChangeOnKeypress = useCallback(step => {
|
|
260
|
+
const newWidth = width + step;
|
|
261
|
+
if (newWidth > maxWidth || newWidth < resizerMinWidth) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
handleResizeStop({
|
|
265
|
+
width: width,
|
|
266
|
+
x: 0,
|
|
267
|
+
y: 0,
|
|
268
|
+
height: 0
|
|
269
|
+
}, {
|
|
270
|
+
width: step,
|
|
271
|
+
height: 0
|
|
272
|
+
});
|
|
273
|
+
}, [width, handleResizeStop, maxWidth, resizerMinWidth]);
|
|
274
|
+
const handleEscape = useCallback(() => {
|
|
275
|
+
editorView === null || editorView === void 0 ? void 0 : editorView.focus();
|
|
276
|
+
}, [editorView]);
|
|
277
|
+
const handleKeyDown = useCallback(event => {
|
|
278
|
+
const isBracketKey = event.code === 'BracketRight' || event.code === 'BracketLeft';
|
|
279
|
+
const metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
280
|
+
if (event.altKey || metaKey || event.shiftKey) {
|
|
281
|
+
areResizeMetaKeysPressed.current = true;
|
|
282
|
+
}
|
|
283
|
+
if (event.altKey && metaKey) {
|
|
284
|
+
if (isBracketKey) {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
handleTableSizeChangeOnKeypress(event.code === 'BracketRight' ? RESIZE_STEP_VALUE : -RESIZE_STEP_VALUE);
|
|
287
|
+
}
|
|
288
|
+
} else if (!areResizeMetaKeysPressed.current) {
|
|
289
|
+
handleEscape();
|
|
290
|
+
}
|
|
291
|
+
}, [handleEscape, handleTableSizeChangeOnKeypress]);
|
|
292
|
+
const handleKeyUp = useCallback(event => {
|
|
293
|
+
if (event.altKey || event.metaKey) {
|
|
294
|
+
areResizeMetaKeysPressed.current = false;
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}, [areResizeMetaKeysPressed]);
|
|
298
|
+
useLayoutEffect(() => {
|
|
299
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
300
|
+
if (!resizerRef.current) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const resizeHandleThumbEl = resizerRef.current.getResizerThumbEl();
|
|
304
|
+
const globalKeyDownHandler = event => {
|
|
305
|
+
const metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
306
|
+
if (!isTableSelected) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (event.altKey && event.shiftKey && metaKey && event.code === 'KeyR') {
|
|
310
|
+
event.preventDefault();
|
|
311
|
+
if (!resizeHandleThumbEl) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
resizeHandleThumbEl.focus();
|
|
315
|
+
resizeHandleThumbEl.scrollIntoView({
|
|
316
|
+
behavior: 'smooth',
|
|
317
|
+
block: 'center',
|
|
318
|
+
inline: 'nearest'
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const editorViewDom = editorView === null || editorView === void 0 ? void 0 : editorView.dom;
|
|
323
|
+
editorViewDom === null || editorViewDom === void 0 ? void 0 : editorViewDom.addEventListener('keydown', globalKeyDownHandler);
|
|
324
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 ? void 0 : resizeHandleThumbEl.addEventListener('keydown', handleKeyDown);
|
|
325
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 ? void 0 : resizeHandleThumbEl.addEventListener('keyup', handleKeyUp);
|
|
326
|
+
return () => {
|
|
327
|
+
editorViewDom === null || editorViewDom === void 0 ? void 0 : editorViewDom.removeEventListener('keydown', globalKeyDownHandler);
|
|
328
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 ? void 0 : resizeHandleThumbEl.removeEventListener('keydown', handleKeyDown);
|
|
329
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 ? void 0 : resizeHandleThumbEl.removeEventListener('keyup', handleKeyUp);
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}, [resizerRef, editorView, handleResizeStop, isTableSelected, handleKeyDown, handleKeyUp]);
|
|
333
|
+
useLayoutEffect(() => {
|
|
334
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
335
|
+
var _updateTooltip$curren;
|
|
336
|
+
(_updateTooltip$curren = updateTooltip.current) === null || _updateTooltip$curren === void 0 ? void 0 : _updateTooltip$curren.call(updateTooltip);
|
|
337
|
+
}
|
|
338
|
+
}, [width]);
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
341
|
+
const debouncedSetWidth = debounce(setScreenReaderResizeInformation, DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER);
|
|
342
|
+
debouncedSetWidth(prevState => {
|
|
343
|
+
let type = 'none';
|
|
344
|
+
if (prevState.width > width) {
|
|
345
|
+
type = 'decrease';
|
|
346
|
+
}
|
|
347
|
+
if (prevState.width < width) {
|
|
348
|
+
type = 'increase';
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
type,
|
|
352
|
+
width
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
return () => {
|
|
356
|
+
debouncedSetWidth.cancel();
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}, [width]);
|
|
360
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ResizerNext, {
|
|
361
|
+
ref: resizerRef,
|
|
235
362
|
enable: handles,
|
|
236
363
|
width: width,
|
|
237
364
|
handleAlignmentMethod: "sticky",
|
|
@@ -249,6 +376,17 @@ export const TableResizer = ({
|
|
|
249
376
|
isHandleVisible: isTableSelected,
|
|
250
377
|
appearance: isTableSelected && isWholeTableInDanger ? 'danger' : undefined,
|
|
251
378
|
handleHighlight: "shadow",
|
|
252
|
-
handleTooltipContent:
|
|
253
|
-
|
|
379
|
+
handleTooltipContent: getBooleanFF('platform.editor.a11y-table-resizing_uapcv') ? ({
|
|
380
|
+
update
|
|
381
|
+
}) => {
|
|
382
|
+
updateTooltip.current = update;
|
|
383
|
+
return /*#__PURE__*/React.createElement(ToolTipContent, {
|
|
384
|
+
description: formatMessage(messages.resizeTable),
|
|
385
|
+
keymap: focusTableResizer
|
|
386
|
+
});
|
|
387
|
+
} : formatMessage(messages.resizeTable)
|
|
388
|
+
}, children), getBooleanFF('platform.editor.a11y-table-resizing_uapcv') && /*#__PURE__*/React.createElement("div", {
|
|
389
|
+
className: "assistive",
|
|
390
|
+
role: "status"
|
|
391
|
+
}, screenReaderResizeAnnouncerMessages[screenReaderResizeInformation.type]));
|
|
254
392
|
};
|
|
@@ -2,13 +2,16 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
3
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
4
4
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
5
|
-
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import debounce from 'lodash/debounce';
|
|
6
7
|
import rafSchd from 'raf-schd';
|
|
7
8
|
import { useIntl } from 'react-intl-next';
|
|
8
9
|
import { TABLE_OVERFLOW_CHANGE_TRIGGER } from '@atlaskit/editor-common/analytics';
|
|
9
10
|
import { getGuidelinesWithHighlights } from '@atlaskit/editor-common/guideline';
|
|
11
|
+
import { focusTableResizer, ToolTipContent } from '@atlaskit/editor-common/keymaps';
|
|
10
12
|
import { tableMessages as messages } from '@atlaskit/editor-common/messages';
|
|
11
13
|
import { ResizerNext } from '@atlaskit/editor-common/resizer';
|
|
14
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
12
15
|
import { findTable } from '@atlaskit/editor-tables/utils';
|
|
13
16
|
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
14
17
|
import { getPluginState } from '../pm-plugins/plugin-factory';
|
|
@@ -19,6 +22,8 @@ import { TABLE_HIGHLIGHT_GAP, TABLE_HIGHLIGHT_TOLERANCE, TABLE_SNAP_GAP } from '
|
|
|
19
22
|
import { generateResizedPayload, generateResizeFrameRatePayloads, useMeasureFramerate } from '../utils/analytics';
|
|
20
23
|
import { defaultGuidelines } from '../utils/guidelines';
|
|
21
24
|
import { defaultSnappingWidths, findClosestSnap } from '../utils/snapping';
|
|
25
|
+
var DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER = 1000;
|
|
26
|
+
var RESIZE_STEP_VALUE = 10;
|
|
22
27
|
var handles = {
|
|
23
28
|
right: true
|
|
24
29
|
};
|
|
@@ -86,12 +91,36 @@ export var TableResizer = function TableResizer(_ref) {
|
|
|
86
91
|
var currentGap = useRef(0);
|
|
87
92
|
// track resizing state - use ref over state to avoid re-render
|
|
88
93
|
var isResizing = useRef(false);
|
|
94
|
+
var areResizeMetaKeysPressed = useRef(false);
|
|
95
|
+
var resizerRef = useRef(null);
|
|
96
|
+
|
|
97
|
+
// used to reposition tooltip when table is resizing via keyboard
|
|
98
|
+
var updateTooltip = React.useRef();
|
|
89
99
|
var _useState = useState(false),
|
|
90
100
|
_useState2 = _slicedToArray(_useState, 2),
|
|
91
101
|
snappingEnabled = _useState2[0],
|
|
92
102
|
setSnappingEnabled = _useState2[1];
|
|
103
|
+
|
|
104
|
+
// we don't want to update aria-live region on each width change, it might provide bad experience for screen reader users
|
|
105
|
+
var _useState3 = useState({
|
|
106
|
+
type: 'none',
|
|
107
|
+
width: width
|
|
108
|
+
}),
|
|
109
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
110
|
+
screenReaderResizeInformation = _useState4[0],
|
|
111
|
+
setScreenReaderResizeInformation = _useState4[1];
|
|
93
112
|
var _useIntl = useIntl(),
|
|
94
113
|
formatMessage = _useIntl.formatMessage;
|
|
114
|
+
var screenReaderResizeAnnouncerMessages = {
|
|
115
|
+
increase: formatMessage(messages.tableSizeIncreaseScreenReaderInformation, {
|
|
116
|
+
newWidth: screenReaderResizeInformation.width
|
|
117
|
+
}),
|
|
118
|
+
decrease: formatMessage(messages.tableSizeDecreaseScreenReaderInformation, {
|
|
119
|
+
newWidth: screenReaderResizeInformation.width
|
|
120
|
+
}),
|
|
121
|
+
none: ''
|
|
122
|
+
};
|
|
123
|
+
var isTableSelected = ((_findTable = findTable((_editorView$state = editorView.state) === null || _editorView$state === void 0 ? void 0 : _editorView$state.selection)) === null || _findTable === void 0 ? void 0 : _findTable.pos) === getPos();
|
|
95
124
|
var resizerMinWidth = getResizerMinWidth(node);
|
|
96
125
|
var handleSize = getResizerHandleHeight(tableRef);
|
|
97
126
|
var _getPluginState = getPluginState(editorView.state),
|
|
@@ -225,8 +254,109 @@ export var TableResizer = function TableResizer(_ref) {
|
|
|
225
254
|
}
|
|
226
255
|
return newWidth;
|
|
227
256
|
}, [displayGapCursor, updateWidth, editorView, getPos, node, tableRef, scheduleResize, displayGuideline, attachAnalyticsEvent, endMeasure, onResizeStop]);
|
|
228
|
-
var
|
|
229
|
-
|
|
257
|
+
var handleTableSizeChangeOnKeypress = useCallback(function (step) {
|
|
258
|
+
var newWidth = width + step;
|
|
259
|
+
if (newWidth > maxWidth || newWidth < resizerMinWidth) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
handleResizeStop({
|
|
263
|
+
width: width,
|
|
264
|
+
x: 0,
|
|
265
|
+
y: 0,
|
|
266
|
+
height: 0
|
|
267
|
+
}, {
|
|
268
|
+
width: step,
|
|
269
|
+
height: 0
|
|
270
|
+
});
|
|
271
|
+
}, [width, handleResizeStop, maxWidth, resizerMinWidth]);
|
|
272
|
+
var handleEscape = useCallback(function () {
|
|
273
|
+
editorView === null || editorView === void 0 || editorView.focus();
|
|
274
|
+
}, [editorView]);
|
|
275
|
+
var handleKeyDown = useCallback(function (event) {
|
|
276
|
+
var isBracketKey = event.code === 'BracketRight' || event.code === 'BracketLeft';
|
|
277
|
+
var metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
278
|
+
if (event.altKey || metaKey || event.shiftKey) {
|
|
279
|
+
areResizeMetaKeysPressed.current = true;
|
|
280
|
+
}
|
|
281
|
+
if (event.altKey && metaKey) {
|
|
282
|
+
if (isBracketKey) {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
handleTableSizeChangeOnKeypress(event.code === 'BracketRight' ? RESIZE_STEP_VALUE : -RESIZE_STEP_VALUE);
|
|
285
|
+
}
|
|
286
|
+
} else if (!areResizeMetaKeysPressed.current) {
|
|
287
|
+
handleEscape();
|
|
288
|
+
}
|
|
289
|
+
}, [handleEscape, handleTableSizeChangeOnKeypress]);
|
|
290
|
+
var handleKeyUp = useCallback(function (event) {
|
|
291
|
+
if (event.altKey || event.metaKey) {
|
|
292
|
+
areResizeMetaKeysPressed.current = false;
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}, [areResizeMetaKeysPressed]);
|
|
296
|
+
useLayoutEffect(function () {
|
|
297
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
298
|
+
if (!resizerRef.current) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
var resizeHandleThumbEl = resizerRef.current.getResizerThumbEl();
|
|
302
|
+
var globalKeyDownHandler = function globalKeyDownHandler(event) {
|
|
303
|
+
var metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
304
|
+
if (!isTableSelected) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (event.altKey && event.shiftKey && metaKey && event.code === 'KeyR') {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
if (!resizeHandleThumbEl) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
resizeHandleThumbEl.focus();
|
|
313
|
+
resizeHandleThumbEl.scrollIntoView({
|
|
314
|
+
behavior: 'smooth',
|
|
315
|
+
block: 'center',
|
|
316
|
+
inline: 'nearest'
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
var editorViewDom = editorView === null || editorView === void 0 ? void 0 : editorView.dom;
|
|
321
|
+
editorViewDom === null || editorViewDom === void 0 || editorViewDom.addEventListener('keydown', globalKeyDownHandler);
|
|
322
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.addEventListener('keydown', handleKeyDown);
|
|
323
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.addEventListener('keyup', handleKeyUp);
|
|
324
|
+
return function () {
|
|
325
|
+
editorViewDom === null || editorViewDom === void 0 || editorViewDom.removeEventListener('keydown', globalKeyDownHandler);
|
|
326
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.removeEventListener('keydown', handleKeyDown);
|
|
327
|
+
resizeHandleThumbEl === null || resizeHandleThumbEl === void 0 || resizeHandleThumbEl.removeEventListener('keyup', handleKeyUp);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}, [resizerRef, editorView, handleResizeStop, isTableSelected, handleKeyDown, handleKeyUp]);
|
|
331
|
+
useLayoutEffect(function () {
|
|
332
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
333
|
+
var _updateTooltip$curren;
|
|
334
|
+
(_updateTooltip$curren = updateTooltip.current) === null || _updateTooltip$curren === void 0 || _updateTooltip$curren.call(updateTooltip);
|
|
335
|
+
}
|
|
336
|
+
}, [width]);
|
|
337
|
+
useEffect(function () {
|
|
338
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
339
|
+
var debouncedSetWidth = debounce(setScreenReaderResizeInformation, DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER);
|
|
340
|
+
debouncedSetWidth(function (prevState) {
|
|
341
|
+
var type = 'none';
|
|
342
|
+
if (prevState.width > width) {
|
|
343
|
+
type = 'decrease';
|
|
344
|
+
}
|
|
345
|
+
if (prevState.width < width) {
|
|
346
|
+
type = 'increase';
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
type: type,
|
|
350
|
+
width: width
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
return function () {
|
|
354
|
+
debouncedSetWidth.cancel();
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
}, [width]);
|
|
358
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ResizerNext, {
|
|
359
|
+
ref: resizerRef,
|
|
230
360
|
enable: handles,
|
|
231
361
|
width: width,
|
|
232
362
|
handleAlignmentMethod: "sticky",
|
|
@@ -244,6 +374,16 @@ export var TableResizer = function TableResizer(_ref) {
|
|
|
244
374
|
isHandleVisible: isTableSelected,
|
|
245
375
|
appearance: isTableSelected && isWholeTableInDanger ? 'danger' : undefined,
|
|
246
376
|
handleHighlight: "shadow",
|
|
247
|
-
handleTooltipContent:
|
|
248
|
-
|
|
377
|
+
handleTooltipContent: getBooleanFF('platform.editor.a11y-table-resizing_uapcv') ? function (_ref3) {
|
|
378
|
+
var update = _ref3.update;
|
|
379
|
+
updateTooltip.current = update;
|
|
380
|
+
return /*#__PURE__*/React.createElement(ToolTipContent, {
|
|
381
|
+
description: formatMessage(messages.resizeTable),
|
|
382
|
+
keymap: focusTableResizer
|
|
383
|
+
});
|
|
384
|
+
} : formatMessage(messages.resizeTable)
|
|
385
|
+
}, children), getBooleanFF('platform.editor.a11y-table-resizing_uapcv') && /*#__PURE__*/React.createElement("div", {
|
|
386
|
+
className: "assistive",
|
|
387
|
+
role: "status"
|
|
388
|
+
}, screenReaderResizeAnnouncerMessages[screenReaderResizeInformation.type]));
|
|
249
389
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-table",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "Table plugin for the @atlaskit/editor",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -141,6 +141,9 @@
|
|
|
141
141
|
},
|
|
142
142
|
"platform.editor.table.analytics-plugin-moved-event": {
|
|
143
143
|
"type": "boolean"
|
|
144
|
+
},
|
|
145
|
+
"platform.editor.a11y-table-resizing_uapcv": {
|
|
146
|
+
"type": "boolean"
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TABLE_ACTION,
|
|
10
10
|
} from '@atlaskit/editor-common/analytics';
|
|
11
11
|
import type { DocBuilder } from '@atlaskit/editor-common/types';
|
|
12
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
12
13
|
import { akEditorWideLayoutWidth } from '@atlaskit/editor-shared-styles';
|
|
13
14
|
import { findTable } from '@atlaskit/editor-tables/utils';
|
|
14
15
|
// eslint-disable-next-line import/no-extraneous-dependencies -- Removed import for fixing circular dependencies
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
tdEmpty,
|
|
23
24
|
tr,
|
|
24
25
|
} from '@atlaskit/editor-test-helpers/doc-builder';
|
|
26
|
+
import { ffTest } from '@atlassian/feature-flags-test-utils';
|
|
25
27
|
|
|
26
28
|
import tablePlugin from '../../../plugins/table-plugin';
|
|
27
29
|
import {
|
|
@@ -128,6 +130,89 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
128
130
|
});
|
|
129
131
|
});
|
|
130
132
|
|
|
133
|
+
describe('should focus resize handle thumb after Cmd + Opt + R key comibnation is pressed', () => {
|
|
134
|
+
let originalScrollBy: typeof window.scrollBy;
|
|
135
|
+
let originalScrollIntoView: typeof window.HTMLElement.prototype.scrollIntoView;
|
|
136
|
+
|
|
137
|
+
beforeEach(() => {
|
|
138
|
+
originalScrollBy = window.scrollBy;
|
|
139
|
+
originalScrollIntoView = window.HTMLElement.prototype.scrollIntoView;
|
|
140
|
+
|
|
141
|
+
window.scrollBy = jest.fn();
|
|
142
|
+
window.HTMLElement.prototype.scrollIntoView = jest.fn();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
afterEach(() => {
|
|
146
|
+
window.scrollBy = originalScrollBy;
|
|
147
|
+
window.HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const buildContainer = (
|
|
151
|
+
isTableResizingEnabled: boolean,
|
|
152
|
+
isBreakoutEnabled: boolean = true,
|
|
153
|
+
) => {
|
|
154
|
+
const { table, editorView } = createNode();
|
|
155
|
+
|
|
156
|
+
const { container } = render(
|
|
157
|
+
<TableContainer
|
|
158
|
+
containerWidth={{
|
|
159
|
+
width: 1800,
|
|
160
|
+
lineLength: 720,
|
|
161
|
+
}}
|
|
162
|
+
node={table}
|
|
163
|
+
isTableResizingEnabled={isTableResizingEnabled}
|
|
164
|
+
isBreakoutEnabled={isBreakoutEnabled}
|
|
165
|
+
className={''}
|
|
166
|
+
editorView={editorView}
|
|
167
|
+
getPos={() => 1}
|
|
168
|
+
tableRef={{} as any}
|
|
169
|
+
isNested={false}
|
|
170
|
+
/>,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return container;
|
|
174
|
+
};
|
|
175
|
+
ffTest(
|
|
176
|
+
'platform.editor.a11y-table-resizing_uapcv',
|
|
177
|
+
() => {
|
|
178
|
+
const { editorView } = createNode();
|
|
179
|
+
const container = buildContainer(true);
|
|
180
|
+
const resizerThumbHandle = container.querySelector(
|
|
181
|
+
'[data-testid="resizer-handle-right-thumb"',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
fireEvent.keyDown(editorView.dom, {
|
|
185
|
+
key: 'R',
|
|
186
|
+
code: 'KeyR',
|
|
187
|
+
altKey: true,
|
|
188
|
+
metaKey: browser.mac,
|
|
189
|
+
shiftKey: true,
|
|
190
|
+
ctrlKey: !browser.mac,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(resizerThumbHandle).toStrictEqual(document.activeElement);
|
|
194
|
+
},
|
|
195
|
+
() => {
|
|
196
|
+
const { editorView } = createNode();
|
|
197
|
+
const container = buildContainer(true);
|
|
198
|
+
const resizerThumbHandle = container.querySelector(
|
|
199
|
+
'[data-testid="resizer-handle-right-thumb"',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
fireEvent.keyDown(editorView.dom, {
|
|
203
|
+
key: 'R',
|
|
204
|
+
code: 'KeyR',
|
|
205
|
+
altKey: true,
|
|
206
|
+
metaKey: browser.mac,
|
|
207
|
+
shiftKey: true,
|
|
208
|
+
ctrlKey: !browser.mac,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(resizerThumbHandle).not.toStrictEqual(document.activeElement);
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
131
216
|
describe('when table is nested', () => {
|
|
132
217
|
const buildContainer = (
|
|
133
218
|
isTableResizingEnabled: boolean,
|
|
@@ -2,11 +2,13 @@ import type { PropsWithChildren } from 'react';
|
|
|
2
2
|
import React, {
|
|
3
3
|
useCallback,
|
|
4
4
|
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
5
6
|
useMemo,
|
|
6
7
|
useRef,
|
|
7
8
|
useState,
|
|
8
9
|
} from 'react';
|
|
9
10
|
|
|
11
|
+
import debounce from 'lodash/debounce';
|
|
10
12
|
import rafSchd from 'raf-schd';
|
|
11
13
|
import { useIntl } from 'react-intl-next';
|
|
12
14
|
|
|
@@ -14,9 +16,14 @@ import type { TableEventPayload } from '@atlaskit/editor-common/analytics';
|
|
|
14
16
|
import { TABLE_OVERFLOW_CHANGE_TRIGGER } from '@atlaskit/editor-common/analytics';
|
|
15
17
|
import { getGuidelinesWithHighlights } from '@atlaskit/editor-common/guideline';
|
|
16
18
|
import type { GuidelineConfig } from '@atlaskit/editor-common/guideline';
|
|
19
|
+
import {
|
|
20
|
+
focusTableResizer,
|
|
21
|
+
ToolTipContent,
|
|
22
|
+
} from '@atlaskit/editor-common/keymaps';
|
|
17
23
|
import { tableMessages as messages } from '@atlaskit/editor-common/messages';
|
|
18
24
|
import type { HandleResize, HandleSize } from '@atlaskit/editor-common/resizer';
|
|
19
25
|
import { ResizerNext } from '@atlaskit/editor-common/resizer';
|
|
26
|
+
import { browser } from '@atlaskit/editor-common/utils';
|
|
20
27
|
import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
21
28
|
import type { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
22
29
|
import type { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
@@ -67,6 +74,13 @@ export interface TableResizerImprovementProps extends TableResizerProps {
|
|
|
67
74
|
onResizeStart?: () => void;
|
|
68
75
|
}
|
|
69
76
|
|
|
77
|
+
type ResizerNextHandler = React.ElementRef<typeof ResizerNext>;
|
|
78
|
+
|
|
79
|
+
type ResizeAction = 'increase' | 'decrease' | 'none';
|
|
80
|
+
|
|
81
|
+
const DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER = 1000;
|
|
82
|
+
const RESIZE_STEP_VALUE = 10;
|
|
83
|
+
|
|
70
84
|
const handles = { right: true };
|
|
71
85
|
const handleStyles = {
|
|
72
86
|
right: {
|
|
@@ -150,8 +164,37 @@ export const TableResizer = ({
|
|
|
150
164
|
const currentGap = useRef(0);
|
|
151
165
|
// track resizing state - use ref over state to avoid re-render
|
|
152
166
|
const isResizing = useRef(false);
|
|
167
|
+
const areResizeMetaKeysPressed = useRef(false);
|
|
168
|
+
|
|
169
|
+
const resizerRef = useRef<ResizerNextHandler>(null);
|
|
170
|
+
|
|
171
|
+
// used to reposition tooltip when table is resizing via keyboard
|
|
172
|
+
const updateTooltip = React.useRef<() => void>();
|
|
153
173
|
const [snappingEnabled, setSnappingEnabled] = useState(false);
|
|
174
|
+
|
|
175
|
+
// we don't want to update aria-live region on each width change, it might provide bad experience for screen reader users
|
|
176
|
+
const [screenReaderResizeInformation, setScreenReaderResizeInformation] =
|
|
177
|
+
useState<{
|
|
178
|
+
type: ResizeAction;
|
|
179
|
+
width: number;
|
|
180
|
+
}>({
|
|
181
|
+
type: 'none',
|
|
182
|
+
width,
|
|
183
|
+
});
|
|
184
|
+
|
|
154
185
|
const { formatMessage } = useIntl();
|
|
186
|
+
const screenReaderResizeAnnouncerMessages = {
|
|
187
|
+
increase: formatMessage(messages.tableSizeIncreaseScreenReaderInformation, {
|
|
188
|
+
newWidth: screenReaderResizeInformation.width,
|
|
189
|
+
}),
|
|
190
|
+
decrease: formatMessage(messages.tableSizeDecreaseScreenReaderInformation, {
|
|
191
|
+
newWidth: screenReaderResizeInformation.width,
|
|
192
|
+
}),
|
|
193
|
+
none: '',
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const isTableSelected =
|
|
197
|
+
findTable(editorView.state?.selection)?.pos === getPos();
|
|
155
198
|
|
|
156
199
|
const resizerMinWidth = getResizerMinWidth(node);
|
|
157
200
|
const handleSize = getResizerHandleHeight(tableRef);
|
|
@@ -374,33 +417,196 @@ export const TableResizer = ({
|
|
|
374
417
|
],
|
|
375
418
|
);
|
|
376
419
|
|
|
377
|
-
const
|
|
378
|
-
|
|
420
|
+
const handleTableSizeChangeOnKeypress = useCallback(
|
|
421
|
+
(step: number) => {
|
|
422
|
+
const newWidth = width + step;
|
|
379
423
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
424
|
+
if (newWidth > maxWidth || newWidth < resizerMinWidth) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
handleResizeStop(
|
|
428
|
+
{ width: width, x: 0, y: 0, height: 0 },
|
|
429
|
+
{ width: step, height: 0 },
|
|
430
|
+
);
|
|
431
|
+
},
|
|
432
|
+
[width, handleResizeStop, maxWidth, resizerMinWidth],
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const handleEscape = useCallback((): void => {
|
|
436
|
+
editorView?.focus();
|
|
437
|
+
}, [editorView]);
|
|
438
|
+
|
|
439
|
+
const handleKeyDown = useCallback(
|
|
440
|
+
(event: KeyboardEvent): void => {
|
|
441
|
+
const isBracketKey =
|
|
442
|
+
event.code === 'BracketRight' || event.code === 'BracketLeft';
|
|
443
|
+
|
|
444
|
+
const metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
445
|
+
|
|
446
|
+
if (event.altKey || metaKey || event.shiftKey) {
|
|
447
|
+
areResizeMetaKeysPressed.current = true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (event.altKey && metaKey) {
|
|
451
|
+
if (isBracketKey) {
|
|
452
|
+
event.preventDefault();
|
|
453
|
+
handleTableSizeChangeOnKeypress(
|
|
454
|
+
event.code === 'BracketRight'
|
|
455
|
+
? RESIZE_STEP_VALUE
|
|
456
|
+
: -RESIZE_STEP_VALUE,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
} else if (!areResizeMetaKeysPressed.current) {
|
|
460
|
+
handleEscape();
|
|
399
461
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
462
|
+
},
|
|
463
|
+
[handleEscape, handleTableSizeChangeOnKeypress],
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const handleKeyUp = useCallback(
|
|
467
|
+
(event: KeyboardEvent): void => {
|
|
468
|
+
if (event.altKey || event.metaKey) {
|
|
469
|
+
areResizeMetaKeysPressed.current = false;
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
},
|
|
473
|
+
[areResizeMetaKeysPressed],
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
useLayoutEffect(() => {
|
|
477
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
478
|
+
if (!resizerRef.current) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const resizeHandleThumbEl = resizerRef.current.getResizerThumbEl();
|
|
482
|
+
|
|
483
|
+
const globalKeyDownHandler = (event: KeyboardEvent): void => {
|
|
484
|
+
const metaKey = browser.mac ? event.metaKey : event.ctrlKey;
|
|
485
|
+
|
|
486
|
+
if (!isTableSelected) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
event.altKey &&
|
|
491
|
+
event.shiftKey &&
|
|
492
|
+
metaKey &&
|
|
493
|
+
event.code === 'KeyR'
|
|
494
|
+
) {
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
|
|
497
|
+
if (!resizeHandleThumbEl) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
resizeHandleThumbEl.focus();
|
|
501
|
+
resizeHandleThumbEl.scrollIntoView({
|
|
502
|
+
behavior: 'smooth',
|
|
503
|
+
block: 'center',
|
|
504
|
+
inline: 'nearest',
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const editorViewDom = editorView?.dom as HTMLElement | undefined;
|
|
510
|
+
editorViewDom?.addEventListener('keydown', globalKeyDownHandler);
|
|
511
|
+
resizeHandleThumbEl?.addEventListener('keydown', handleKeyDown);
|
|
512
|
+
resizeHandleThumbEl?.addEventListener('keyup', handleKeyUp);
|
|
513
|
+
return () => {
|
|
514
|
+
editorViewDom?.removeEventListener('keydown', globalKeyDownHandler);
|
|
515
|
+
resizeHandleThumbEl?.removeEventListener('keydown', handleKeyDown);
|
|
516
|
+
resizeHandleThumbEl?.removeEventListener('keyup', handleKeyUp);
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
}, [
|
|
520
|
+
resizerRef,
|
|
521
|
+
editorView,
|
|
522
|
+
handleResizeStop,
|
|
523
|
+
isTableSelected,
|
|
524
|
+
handleKeyDown,
|
|
525
|
+
handleKeyUp,
|
|
526
|
+
]);
|
|
527
|
+
|
|
528
|
+
useLayoutEffect(() => {
|
|
529
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
530
|
+
updateTooltip.current?.();
|
|
531
|
+
}
|
|
532
|
+
}, [width]);
|
|
533
|
+
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (getBooleanFF('platform.editor.a11y-table-resizing_uapcv')) {
|
|
536
|
+
const debouncedSetWidth = debounce(
|
|
537
|
+
setScreenReaderResizeInformation,
|
|
538
|
+
DEBOUNCE_TIME_FOR_SCREEN_READER_ANNOUNCER,
|
|
539
|
+
);
|
|
540
|
+
debouncedSetWidth((prevState) => {
|
|
541
|
+
let type: ResizeAction = 'none';
|
|
542
|
+
if (prevState.width > width) {
|
|
543
|
+
type = 'decrease';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (prevState.width < width) {
|
|
547
|
+
type = 'increase';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
type,
|
|
552
|
+
width,
|
|
553
|
+
};
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
return () => {
|
|
557
|
+
debouncedSetWidth.cancel();
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}, [width]);
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<>
|
|
564
|
+
<ResizerNext
|
|
565
|
+
ref={resizerRef}
|
|
566
|
+
enable={handles}
|
|
567
|
+
width={width}
|
|
568
|
+
handleAlignmentMethod="sticky"
|
|
569
|
+
handleSize={handleSize}
|
|
570
|
+
handleStyles={handleStyles}
|
|
571
|
+
handleResizeStart={handleResizeStart}
|
|
572
|
+
handleResize={scheduleResize}
|
|
573
|
+
handleResizeStop={handleResizeStop}
|
|
574
|
+
resizeRatio={2}
|
|
575
|
+
minWidth={resizerMinWidth}
|
|
576
|
+
maxWidth={maxWidth}
|
|
577
|
+
snapGap={TABLE_SNAP_GAP}
|
|
578
|
+
snap={guidelineSnaps}
|
|
579
|
+
handlePositioning="adjacent"
|
|
580
|
+
isHandleVisible={isTableSelected}
|
|
581
|
+
appearance={
|
|
582
|
+
isTableSelected && isWholeTableInDanger ? 'danger' : undefined
|
|
583
|
+
}
|
|
584
|
+
handleHighlight="shadow"
|
|
585
|
+
handleTooltipContent={
|
|
586
|
+
getBooleanFF('platform.editor.a11y-table-resizing_uapcv')
|
|
587
|
+
? ({ update }) => {
|
|
588
|
+
updateTooltip.current = update;
|
|
589
|
+
return (
|
|
590
|
+
<ToolTipContent
|
|
591
|
+
description={formatMessage(messages.resizeTable)}
|
|
592
|
+
keymap={focusTableResizer}
|
|
593
|
+
/>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
: formatMessage(messages.resizeTable)
|
|
597
|
+
}
|
|
598
|
+
>
|
|
599
|
+
{children}
|
|
600
|
+
</ResizerNext>
|
|
601
|
+
{getBooleanFF('platform.editor.a11y-table-resizing_uapcv') && (
|
|
602
|
+
<div className="assistive" role="status">
|
|
603
|
+
{
|
|
604
|
+
screenReaderResizeAnnouncerMessages[
|
|
605
|
+
screenReaderResizeInformation.type
|
|
606
|
+
]
|
|
607
|
+
}
|
|
608
|
+
</div>
|
|
609
|
+
)}
|
|
610
|
+
</>
|
|
405
611
|
);
|
|
406
612
|
};
|