@atlaskit/editor-plugin-table 5.4.24 → 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 +12 -0
- package/dist/cjs/plugins/table/nodeviews/TableComponent.js +25 -12
- package/dist/cjs/plugins/table/nodeviews/TableResizer.js +150 -10
- package/dist/cjs/plugins/table/toolbar.js +3 -2
- package/dist/cjs/plugins/table/ui/common-styles.js +3 -3
- package/dist/es2019/plugins/table/nodeviews/TableComponent.js +29 -12
- package/dist/es2019/plugins/table/nodeviews/TableResizer.js +143 -5
- package/dist/es2019/plugins/table/toolbar.js +4 -2
- package/dist/es2019/plugins/table/ui/common-styles.js +3 -3
- package/dist/esm/plugins/table/nodeviews/TableComponent.js +25 -12
- package/dist/esm/plugins/table/nodeviews/TableResizer.js +145 -5
- package/dist/esm/plugins/table/toolbar.js +3 -2
- package/dist/esm/plugins/table/ui/common-styles.js +3 -3
- package/package.json +4 -4
- package/src/__tests__/unit/nodeviews/TableContainer.tsx +85 -0
- package/src/plugins/table/nodeviews/TableComponent.tsx +21 -7
- package/src/plugins/table/nodeviews/TableResizer.tsx +232 -26
- package/src/plugins/table/toolbar.tsx +4 -4
- package/src/plugins/table/ui/common-styles.ts +3 -3
|
@@ -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
|
};
|
|
@@ -49,7 +49,6 @@ import {
|
|
|
49
49
|
splitCell,
|
|
50
50
|
} from '@atlaskit/editor-tables/utils';
|
|
51
51
|
import RemoveIcon from '@atlaskit/icon/glyph/editor/remove';
|
|
52
|
-
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
53
52
|
|
|
54
53
|
import {
|
|
55
54
|
clearHoverSelection,
|
|
@@ -489,14 +488,15 @@ export const getToolbarConfig =
|
|
|
489
488
|
|
|
490
489
|
return element;
|
|
491
490
|
};
|
|
491
|
+
|
|
492
|
+
const { stickyScrollbar } = getEditorFeatureFlags();
|
|
493
|
+
|
|
492
494
|
return {
|
|
493
495
|
title: 'Table floating controls',
|
|
494
496
|
getDomRef,
|
|
495
497
|
nodeType,
|
|
496
498
|
offset: [0, 18],
|
|
497
|
-
absoluteOffset:
|
|
498
|
-
? { top: -6 }
|
|
499
|
-
: { top: 0 },
|
|
499
|
+
absoluteOffset: stickyScrollbar ? { top: -6 } : { top: 0 },
|
|
500
500
|
zIndex: akEditorFloatingPanelZIndex + 1, // Place the context menu slightly above the others
|
|
501
501
|
items: [
|
|
502
502
|
menu,
|
|
@@ -152,8 +152,8 @@ const stickyScrollbarContainerStyles = `.${ClassName.TABLE_CONTAINER} {
|
|
|
152
152
|
}
|
|
153
153
|
}`;
|
|
154
154
|
|
|
155
|
-
const stickyScrollbarStyles = () => {
|
|
156
|
-
return
|
|
155
|
+
const stickyScrollbarStyles = (featureFlags?: FeatureFlags) => {
|
|
156
|
+
return featureFlags?.stickyScrollbar
|
|
157
157
|
? `${stickyScrollbarContainerStyles} ${stickyScrollbarSentinelStyles}`
|
|
158
158
|
: '';
|
|
159
159
|
};
|
|
@@ -529,7 +529,7 @@ export const tableStyles = (
|
|
|
529
529
|
|
|
530
530
|
${sentinelStyles}
|
|
531
531
|
${OverflowShadow(props)}
|
|
532
|
-
${stickyScrollbarStyles()}
|
|
532
|
+
${stickyScrollbarStyles(props.featureFlags)}
|
|
533
533
|
|
|
534
534
|
.${ClassName.TABLE_STICKY} .${ClassName.TABLE_STICKY_SHADOW} {
|
|
535
535
|
height: 0; // stop overflow flash & set correct height in update-overflow-shadows.ts
|