@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.
@@ -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 isTableSelected =
378
- findTable(editorView.state?.selection)?.pos === getPos();
420
+ const handleTableSizeChangeOnKeypress = useCallback(
421
+ (step: number) => {
422
+ const newWidth = width + step;
379
423
 
380
- return (
381
- <ResizerNext
382
- enable={handles}
383
- width={width}
384
- handleAlignmentMethod="sticky"
385
- handleSize={handleSize}
386
- handleStyles={handleStyles}
387
- handleResizeStart={handleResizeStart}
388
- handleResize={scheduleResize}
389
- handleResizeStop={handleResizeStop}
390
- resizeRatio={2}
391
- minWidth={resizerMinWidth}
392
- maxWidth={maxWidth}
393
- snapGap={TABLE_SNAP_GAP}
394
- snap={guidelineSnaps}
395
- handlePositioning="adjacent"
396
- isHandleVisible={isTableSelected}
397
- appearance={
398
- isTableSelected && isWholeTableInDanger ? 'danger' : undefined
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
- handleHighlight="shadow"
401
- handleTooltipContent={formatMessage(messages.resizeTable)}
402
- >
403
- {children}
404
- </ResizerNext>
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: getBooleanFF('platform.editor.table-sticky-scrollbar')
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 getBooleanFF('platform.editor.table-sticky-scrollbar')
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