measuredco / puck

The visual editor for React
https://puckeditor.com
MIT License
5.01k stars 274 forks source link

Support for inline editing text and textareas #150

Open xaviemirmon opened 11 months ago

xaviemirmon commented 11 months ago

Another feature related to #149 a bit is supporting inline editing of text on components.

@chrisvxd I'm sure there are many considerations when working with beautiful-react-dnd, but I'm adding an issue so it's on the radar.

I'd love to have the ability to edit the text on my component inline from the main canvas, and if it was rich text have the ability to change the text style inline.

chrisvxd commented 11 months ago

I don't think this would be that difficult at this point, and the API would probably be similar to DropZones. We'd also probably use context

render: () => <p><InlineField name="title" /></p>

One question I would have is whether we retain the prop for the field. For example

fields: { title: { type: "text"} },
render: () => <p><InlineField name="title" /></p>

I guess it should probably be optional, but the inline field data should still be stored as a prop regardless.

BleedingDev commented 10 months ago

Would this work with other interactive components (for example custom contentEditable or some markdown editor)?

4leite commented 10 months ago

I might have a go at tackling this next as it's something I'm quite keen to see. I've implemented an auto-resizing inline text editing input before. Are we interested in supporting markdown? We could look to leverage something like: https://github.com/facebook/lexical

xaviemirmon commented 10 months ago

@4leite I was thinking of a plain text setup when I raised this. If we expand it to an RTE then we should probably use the same one as agreed upon for #149

chrisvxd commented 10 months ago

I'm a little undecided over whether we want this, or if it would just detract from the field API. Playing around with a demo might help, if anyone's happy to put one together.

BleedingDev commented 10 months ago

I would start with inline editing without MD or something, but would support just making the component clickable and interactive (so that you can plug in something like Lexical).

Advanced editors can be build on top of Custom Input for now.

4leite commented 10 months ago

I was thinking something like this https://craft.js.org/

xaviemirmon commented 9 months ago

100% @4leite, that was the example I had in mind too!

4leite commented 6 months ago

Took me a while to get back to this.

265 added the usePuck hook and I've got a surprisingly usable inline editor using lexical and a very brittle custom hook wrapping usePuck:

"use client";

import { usePuck } from "@measured/puck";
import { SerializedEditorState } from "lexical";
import { LexicalClient } from "./client";

const useSelected = (componentId: string) => {
  const {
    appState: {
      ui: { itemSelector },
      data,
    },
    dispatch,
  } = usePuck();

  if (!itemSelector) {
    return {
      isSelected: false,
      onChange: () => {},
    };
  }

  const item =
    itemSelector.zone && itemSelector.zone !== "default-zone"
      ? data.zones[itemSelector.zone][itemSelector.index]
      : data.content[itemSelector.index];

  if (item.props.id !== componentId) {
    return {
      isSelected: false,
      onChange: () => {},
    };
  }

  return {
    isSelected: true,
    onChange: (props: Partial<typeof item.props>) =>
      dispatch({
        type: "replace",
        destinationIndex: itemSelector.index,
        destinationZone: itemSelector.zone,
        data: {
          props: { ...item.props, ...props },
          type: item.type,
        },
      }),
  };
};

export function InlineEditor({
  id,
  state,
}: {
  id: string;
  state: SerializedEditorState;
}) {
  const { isSelected, onChange } = useSelected(id);

  return (
    <div
      style={{
        cursor: isSelected ? "default" : "grab",
        pointerEvents: "auto",
      }}
    >
      <LexicalClient
        id={id}
        state={state}
        onChange={onChange}
        editing={isSelected}
      />
    </div>
  );
}
4leite commented 6 months ago

This is brittle because we are effectively re-implementing the internal "getItem" code, which could change. @chrisvxd We could better support this in core by either: (a) exporting the above useSelected() hook, which would mean we could use the internal getItem functions and change it if we change the datastructure. (b) export the existing "selectedItem" object (which uses getItem internally) and expect users to check if their component is selected.

I'll link a working demo, with the lexical code included shortly, but the above should work with any contentEditable based or similar solution.

edit: https://inline-rich-text.vercel.app/ https://github.com/4leite/inline-rich-text

4leite commented 6 months ago

updated the above and raised a PR to support targeting the component toolbar in core: https://github.com/measuredco/puck/pull/360

GoMino commented 2 months ago

What could be done if we want to have different selectable/editable text inside the same react component/block?

How could we reuse what you did @4leite?

4leite commented 2 months ago

@GoMino If what you want is a more portable version of the lexical implementation I used, you are welcome to raise a feature request here: https://github.com/Tohuhono/Oberon/issues

However, at the moment I can't see a way to have multiple editable areas in the same puck component. I did have a go when I first did this PoC.

There were two issues, one was setting the individual fields from in the component - I believe this is solved now with newer version of puck.

The other issue was to do with how puck components interact with focus. I couldn't find a way to have the focus for each editable area work without janky or weird select / cursor behavior. You would have to find a way to solve that.

GoMino commented 2 months ago

I don't have a focus issue at the moment, but I don't know how to handle separate states for each editable text field in the same component, it seems like it should somehow be possible with lexical @4leite.

Or maybe using another editor like slate ?