Skip to content

Commit

Permalink
Merge pull request #3133 from PaulSinghDev/main
Browse files Browse the repository at this point in the history
Allow the <LinkFloatingToolbar /> component to be submitted with the 'enter' key when the <PlateEditor /> is contained within a <form></form>
  • Loading branch information
zbeyens committed Jun 9, 2024
2 parents 60ea69f + 381535b commit abc06bc
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-queens-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@udecode/plate-utils": patch
---

`useFormInputProps`: Generic form input props inside an editor
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react';

import { cn } from '@udecode/cn';
import { useFormInputProps } from '@udecode/plate-common';
import {
type UseVirtualFloatingOptions,
flip,
Expand Down Expand Up @@ -68,11 +69,14 @@ export function LinkFloatingToolbar({ state }: LinkFloatingToolbarProps) {
ref: editRef,
unlinkButtonProps,
} = useFloatingLinkEdit(editState);
const inputProps = useFormInputProps({
preventDefaultOnEnterKeydown: true,
});

if (hidden) return null;

const input = (
<div className="flex w-[330px] flex-col">
<div className="flex w-[330px] flex-col" {...inputProps}>
<div className="flex items-center">
<div className="flex items-center pl-3 text-muted-foreground">
<Icons.link className="size-4" />
Expand All @@ -83,9 +87,7 @@ export function LinkFloatingToolbar({ state }: LinkFloatingToolbarProps) {
placeholder="Paste link"
/>
</div>

<Separator />

<div className="flex items-center">
<div className="flex items-center pl-3 text-muted-foreground">
<Icons.text className="size-4" />
Expand Down
2 changes: 1 addition & 1 deletion packages/heading/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
*/

export * from './checkIn';
export * from './getHeadingList';
export * from './heightToTop';
export * from './isHeading';
export * from './getHeadingList';
1 change: 1 addition & 0 deletions packages/math/src/inline-equation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './types';
export * from './components/index';
export * from './hooks/index';
export * from './transforms/index';
export * from './utils/index';
5 changes: 5 additions & 0 deletions packages/math/src/inline-equation/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* @file Automatically generated by barrelsby.
*/

export * from './setSelectionInlineEquation';
1 change: 1 addition & 0 deletions packages/plate-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
* @file Automatically generated by barrelsby.
*/

export * from './useFormInputProps';
export * from './client/index';
export * from './shared/index';
2 changes: 1 addition & 1 deletion packages/plate-utils/src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

export * from './PlateElement';
export * from './PlateLeaf';
export * from './addSelectedRow';
export * from './createNodeHOC';
export * from './createNodesHOC';
export * from './defaultsDeepToNodes';
Expand All @@ -12,4 +13,3 @@ export * from './moveSelectionByOffset';
export * from './onKeyDownToggleElement';
export * from './onKeyDownToggleMark';
export * from './types/index';
export * from './addSelectedRow'
57 changes: 57 additions & 0 deletions packages/plate-utils/src/useFormInputProps.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useFormInputProps } from './useFormInputProps';

describe('useFormInputProps', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('will return an object with a props key regardless of whether the user provides a callback or sets preventDefaultOnEnterKeydown to true', () => {
const output = useFormInputProps();
expect(output.props).toBeDefined();
expect(Object.keys(output.props)).toHaveLength(0);
});

it('will return a callback for onKeyDownCapture when preventDefaultOnEnterKeydown is true', () => {
const output = useFormInputProps({ preventDefaultOnEnterKeydown: true });
expect(output.props).toBeDefined();
expect(Object.keys(output.props)).toHaveLength(1);
expect(output.props.onKeyDownCapture).toBeDefined();
expect(output.props.onKeyDownCapture instanceof Function).toBe(true);
});

it('will call event.preventDefault if the key is enter, and only if the key is enter', () => {
// Define mock for preventdefault
const preventDefaultMock = jest.fn();
// should trigger preventDefault
const eventWithEKeyEnter = {
key: 'Enter',
preventDefault: preventDefaultMock,
} as any;
// should trigger preventDefault
const eventWithKeyCode13 = {
keyCode: 13,
preventDefault: preventDefaultMock,
} as any;
// should not trigger preventDefault
const eventWithIrrelevantKey = {
keyCode: 30,
preventDefault: preventDefaultMock,
} as any;

const output = useFormInputProps({
preventDefaultOnEnterKeydown: true,
});

// call with enter key
output.props?.onKeyDownCapture?.(eventWithEKeyEnter);
expect(preventDefaultMock).toHaveBeenCalledTimes(1);

// call with keyCode 13
output.props?.onKeyDownCapture?.(eventWithKeyCode13);
expect(preventDefaultMock).toHaveBeenCalledTimes(2);

// call with irrelevant key
output.props?.onKeyDownCapture?.(eventWithIrrelevantKey);
expect(preventDefaultMock).toHaveBeenCalledTimes(2);
});
});
56 changes: 56 additions & 0 deletions packages/plate-utils/src/useFormInputProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
interface InputProps {
/**
* Should we activate the onKeyDownCapture handler to preventDefault when
* the user presses enter?
*/
preventDefaultOnEnterKeydown?: boolean;
}

/**
* Hook to allow the user to spread a set of predefined props to the Div wrapper
* of an Input element
*
* @param param0 an options object which can be expanded to add further functionality
* @returns a props object which can be spread onto the element
*/
export const useFormInputProps = (options?: InputProps) => {
// Nothing provided to just return an empty object which can still be spread.
// If we need to add more functionality later we will still be able to do so
if (!options) return { props: {} };

// Destructure our options so we can use them
const { preventDefaultOnEnterKeydown } = options;

/**
* Handle the keydown capture event and prevent the default behaviour when the
* user presses enter.
*
* In the event the user presses enter on a field such as a link, prior to
* filling in both label and url, the default behaviour is to submit the form.
* This, ultimately, results in no link being added as you need to fill both
* fields to pass validation.
*
* By calling preventDefault we short circuit the form's submission thus
* allowing the user to continue filling in the other fields
*
* @param e The original event which was provided by the VDOM
* implement their own behaviour on this event
*/
const handleEnterKeydownCapture = (
e: React.KeyboardEvent<HTMLDivElement>
) => {
// Prevent the form from submitting
if (e.key === 'Enter' || e.keyCode === 13) {
e.preventDefault();
}
};

return {
props: {
onKeyDownCapture: preventDefaultOnEnterKeydown
? (e: React.KeyboardEvent<HTMLDivElement>) =>
handleEnterKeydownCapture(e)
: undefined,
},
};
};

0 comments on commit abc06bc

Please sign in to comment.