Skip to content

Commit

Permalink
Merge pull request #2733 from dimaanj/table-cells-merging-poc-3
Browse files Browse the repository at this point in the history
[TablePlugin] Table cells merging
  • Loading branch information
zbeyens authored Nov 23, 2023
2 parents 0389228 + 61157c1 commit 5cc5908
Show file tree
Hide file tree
Showing 49 changed files with 1,978 additions and 276 deletions.
6 changes: 6 additions & 0 deletions .changeset/sixty-bats-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@udecode/plate-serializer-html': major
---

- [Breaking] `serializeHtml`: replaced option `slateProps` by `plateProps`.
- Fix errors when the components were using Plate hooks.
5 changes: 5 additions & 0 deletions .changeset/table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@udecode/plate-table': minor
---

- Table plugin has now merging support. To enable it, use option `enableMerging: true`
4 changes: 4 additions & 0 deletions apps/www/src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ChevronsUpDown,
ClipboardCheck,
Code2,
Combine,
Copy,
DownloadCloud,
ExternalLink,
Expand Down Expand Up @@ -73,6 +74,7 @@ import {
Trash,
Twitter,
Underline,
Ungroup,
Unlink,
WrapText,
X,
Expand Down Expand Up @@ -281,6 +283,8 @@ export const Icons = {
codeblock: FileCode,
color: Baseline,
column: RectangleVertical,
combine: Combine,
ungroup: Ungroup,
comment: MessageSquare,
commentAdd: MessageSquarePlus,
conflict: Unlink,
Expand Down
2 changes: 1 addition & 1 deletion apps/www/src/lib/plate/demo/values/tableValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ export const tableValue: any = (
</hp>
{createSpanningTable()}
</fragment>
);
);
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ const TableCellElement = React.forwardRef<
rowSize,
borders,
isSelectingCell,
colSpan,
} = useTableCellElementState();
const { props: cellProps } = useTableCellElement({ element: props.element });
const resizableState = useTableCellElementResizableState({
colIndex,
rowIndex,
colSpan,
});

const { rightProps, bottomProps, leftProps, hiddenLeft } =
useTableCellElementResizable(resizableState);

Expand All @@ -50,7 +53,7 @@ const TableCellElement = React.forwardRef<
asChild
ref={ref}
className={cn(
'relative overflow-visible border-none bg-background p-0',
'relative h-full overflow-visible border-none bg-background p-0',
hideBorder && 'before:border-none',
element.background ? 'bg-[--cellBackground]' : 'bg-background',
!hideBorder &&
Expand Down
91 changes: 66 additions & 25 deletions apps/www/src/registry/default/plate-ui/table-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import {
useRemoveNodeButton,
} from '@udecode/plate-common';
import {
mergeTableCells,
TTableElement,
unmergeTableCells,
useTableBordersDropdownMenuContentState,
useTableElement,
useTableElementState,
useTableMergeState,
} from '@udecode/plate-table';
import { useReadOnly, useSelected } from 'slate-react';

Expand Down Expand Up @@ -114,35 +117,73 @@ const TableFloatingToolbar = React.forwardRef<
const readOnly = useReadOnly();
const selected = useSelected();
const editor = useEditorState();
const open = !readOnly && selected && isCollapsed(editor.selection);

const collapsed = !readOnly && selected && isCollapsed(editor.selection);
const open = !readOnly && selected;

const { canMerge, canUnmerge } = useTableMergeState();

const mergeContent = canMerge && (
<Button
contentEditable={false}
variant="ghost"
isMenu
onClick={() => mergeTableCells(editor)}
>
<Icons.combine className="mr-2 h-4 w-4" />
Merge
</Button>
);

const unmergeButton = canUnmerge && (
<Button
contentEditable={false}
variant="ghost"
isMenu
onClick={() => unmergeTableCells(editor)}
>
<Icons.ungroup className="mr-2 h-4 w-4" />
Unmerge
</Button>
);

const bordersContent = collapsed && (
<>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" isMenu>
<Icons.borderAll className="mr-2 h-4 w-4" />
Borders
</Button>
</DropdownMenuTrigger>

<DropdownMenuPortal>
<TableBordersDropdownMenuContent />
</DropdownMenuPortal>
</DropdownMenu>

<Button contentEditable={false} variant="ghost" isMenu {...buttonProps}>
<Icons.delete className="mr-2 h-4 w-4" />
Delete
</Button>
</>
);

return (
<Popover open={open} modal={false}>
<PopoverAnchor asChild>{children}</PopoverAnchor>
<PopoverContent
ref={ref}
className={cn(popoverVariants(), 'flex w-[220px] flex-col gap-1 p-1')}
onOpenAutoFocus={(e) => e.preventDefault()}
{...props}
>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" isMenu>
<Icons.borderAll className="mr-2 h-4 w-4" />
Borders
</Button>
</DropdownMenuTrigger>

<DropdownMenuPortal>
<TableBordersDropdownMenuContent />
</DropdownMenuPortal>
</DropdownMenu>

<Button contentEditable={false} variant="ghost" isMenu {...buttonProps}>
<Icons.delete className="mr-2 h-4 w-4" />
Delete
</Button>
</PopoverContent>
{(canMerge || canUnmerge || collapsed) && (
<PopoverContent
ref={ref}
className={cn(popoverVariants(), 'flex w-[220px] flex-col gap-1 p-1')}
onOpenAutoFocus={(e) => e.preventDefault()}
{...props}
>
{unmergeButton}
{mergeContent}
{bordersContent}
</PopoverContent>
)}
</Popover>
);
});
Expand Down
9 changes: 8 additions & 1 deletion packages/resizable/src/components/ResizeHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const ResizeHandleProvider = ({

export type ResizeHandleOptions = {
direction?: ResizeDirection;
initialSize?: number;
onResize?: (event: ResizeEvent) => void;
onMouseDown?: MouseEventHandler;
onTouchStart?: TouchEventHandler;
Expand All @@ -60,6 +61,7 @@ export type ResizeHandleOptions = {

export const useResizeHandleState = ({
direction = 'left',
initialSize: _initialSize,
onResize,
onMouseDown,
onTouchStart,
Expand Down Expand Up @@ -88,7 +90,12 @@ export const useResizeHandleState = ({

const currentPosition = isHorizontal ? clientX : clientY;
const delta = currentPosition - initialPosition;
onResize?.({ initialSize, delta, finished, direction });
onResize?.({
initialSize: _initialSize || initialSize,
delta,
finished,
direction,
});
};

const handleMouseMove = (event: MouseEvent | TouchEvent) =>
Expand Down
10 changes: 5 additions & 5 deletions packages/serializer-html/src/elementToHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ComponentClass, FunctionComponent } from 'react';
import {
pipeInjectProps,
PlateEditor,
PlateProps,
PlateRenderElementProps,
pluginRenderElement,
SlateProps,
Value,
} from '@udecode/plate-common';
import { decode } from 'html-entities';
Expand All @@ -17,12 +17,12 @@ export const elementToHtml = <V extends Value>(
editor: PlateEditor<V>,
{
props,
slateProps,
plateProps,
preserveClassNames,
dndWrapper,
}: {
props: PlateRenderElementProps<V>;
slateProps?: Partial<SlateProps>;
plateProps?: Partial<PlateProps>;
preserveClassNames?: string[];
dndWrapper?: string | FunctionComponent | ComponentClass;
}
Expand Down Expand Up @@ -50,8 +50,8 @@ export const elementToHtml = <V extends Value>(
renderToStaticMarkup(
createElementWithSlate(
{
...slateProps,

editor: editor as any,
...plateProps,
children:
plugin.serializeHtml?.(props as any) ??
pluginRenderElement(editor, plugin)(props),
Expand Down
8 changes: 4 additions & 4 deletions packages/serializer-html/src/leafToHtml.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
pipeInjectProps,
PlateEditor,
PlateProps,
PlateRenderLeafProps,
pluginRenderLeaf,
SlateProps,
Value,
} from '@udecode/plate-common';
import { decode } from 'html-entities';
Expand All @@ -16,11 +16,11 @@ export const leafToHtml = <V extends Value>(
editor: PlateEditor<V>,
{
props,
slateProps,
plateProps,
preserveClassNames,
}: {
props: PlateRenderLeafProps<V>;
slateProps?: Partial<SlateProps>;
plateProps?: Partial<PlateProps>;
preserveClassNames?: string[];
}
) => {
Expand All @@ -43,7 +43,7 @@ export const leafToHtml = <V extends Value>(
let html = decode(
renderToStaticMarkup(
createElementWithSlate({
...slateProps,
...plateProps,
children: serialized,
})
)
Expand Down
12 changes: 6 additions & 6 deletions packages/serializer-html/src/serializeHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
EElement,
isText,
PlateEditor,
SlateProps,
PlateProps,
Value,
} from '@udecode/plate-common';
import { encode } from 'html-entities';
Expand All @@ -22,7 +22,7 @@ export const serializeHtml = <V extends Value>(
editor: PlateEditor<V>,
{
nodes,
slateProps,
plateProps,
stripDataAttributes = true,
preserveClassNames,
stripWhitespace = true,
Expand All @@ -45,9 +45,9 @@ export const serializeHtml = <V extends Value>(
preserveClassNames?: string[];

/**
* Slate props to provide if the rendering depends on slate hooks
* Slate props to provide if the rendering depends on plate/slate hooks
*/
slateProps?: Partial<SlateProps>;
plateProps?: Partial<PlateProps>;

/**
* Whether stripping whitespaces from serialized HTML
Expand Down Expand Up @@ -82,7 +82,7 @@ export const serializeHtml = <V extends Value>(
attributes: { 'data-slate-leaf': true },
editor,
},
slateProps,
plateProps,
preserveClassNames,
});
}
Expand All @@ -99,7 +99,7 @@ export const serializeHtml = <V extends Value>(
attributes: { 'data-slate-node': 'element', ref: null },
editor,
},
slateProps,
plateProps,
preserveClassNames,
dndWrapper,
});
Expand Down
42 changes: 14 additions & 28 deletions packages/serializer-html/src/utils/createElementWithSlate.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,35 @@
import React, { ComponentClass, FunctionComponent } from 'react';
import { createTEditor, SlateProps, withTReact } from '@udecode/plate-common';
import { Slate } from 'slate-react';
import { Plate, PlateProps } from '@udecode/plate-common';

/**
* Create a React element wrapped in a Slate provider.
* By default, it will use an empty editor.
* TODO: allow other providers
* Create a React element wrapped in a Plate provider.
*/
export const createElementWithSlate = (
slateProps?: Partial<SlateProps>,
plateProps?: Partial<PlateProps>,
dndWrapper?: string | FunctionComponent | ComponentClass
) => {
const {
editor = withTReact(createTEditor()),
editor,
value = [],
onChange = () => {},
children,
...props
} = slateProps || {};
} = plateProps || {};

if (dndWrapper) {
return React.createElement(
dndWrapper,
null,
React.createElement(
Slate,
{
editor,
initialValue: value,
onChange,
...props,
} as any,
children
)
);
}

return React.createElement(
Slate,
const plate = React.createElement(
Plate,
{
editor,
initialValue: value,
onChange,
...props,
} as any,
} as PlateProps,
children
);

if (dndWrapper) {
return React.createElement(dndWrapper, null, plate);
}

return plate;
};
Loading

0 comments on commit 5cc5908

Please sign in to comment.