Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TablePlugin] Table cells merging #2733

Merged
merged 18 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading