Craft.js Documentation

repository·main·Indexed 27 days ago

https://github.com/prevwong/craft.js

A modular React framework for building extensible page editors. Craft.js provides core building blocks for drag-and-drop, component rendering, and state management, allowing developers to create custom user interfaces. It includes @craftjs/core for editor logic, @craftjs/layers for a layers panel, and @craftjs/slate for integrating Slate.js rich text editing capabilities.

Tokens
29.6K
Snippets
72
Records
172
Agent score
90%

What's inside Craft.js

  1. Overview of @craftjs/slate

    main
    @craftjs/slate is a package designed to help you create Rich Text Editor (RTE) User Components using Slate.js within the Craft.js ecosystem. It bridges the gap between Craft.js's component-based drag-and-drop architecture and Slate.js's specialized text editing capabilities.
  2. Understand User Elements and Nodes in Craft.js

    main

    In Craft.js, User Elements are the React elements (HTML tags or React Components) that users manipulate, drag, or drop within the editor.

    Craft.js manages these elements using an internal state of Nodes. Every User Element is represented by a corresponding Node that stores metadata such as element type, current props, the DOM element, and parent/child relationships.

  3. Configure Custom Properties on Nodes

    main

    You can attach custom data to nodes using the custom property. This is useful for storing metadata that needs to be persisted (e.g., in a database) but isn't part of the standard React props.

    You can define default custom values on a component using the craft.custom property.

  4. Make a component editable with useNode

    main

    Use the useNode hook to connect your React components to the Craft.js editor. To make a component interactive (e.g., for drag-and-drop or selection), use the connectors returned by useNode.

    To implement custom editing logic (like a property editor modal), you can pass a selector function to useNode to subscribe to specific editor states, such as state.events.selected. You can then use actions.setProp to update the component's props.

    import { useNode } from "@craftjs/core";
    
    const TextComponent = ({ text }) => {
      const {
        connectors: { connect, drag },
        isClicked,
        actions: { setProp },
      } = useNode((state) => ({
        isClicked: state.events.selected,
      }));
    
      return (
        <div ref={(dom) => connect(drag(dom))}>
          <h2>{text}</h2>
          {isClicked ? (
            <Modal>
              <input
                type="text"
                value={text}
                onChange={(e) => setProp(e.target.value)}
              />
            </Modal>
          ) : null}
        </div>
      );
    };
  5. Implement Related Components for Toolbars

    main

    To build a UI (like a Toolbar) that edits a selected component, use the related property in Text.craft to link a settings component to your user component.

    Inside the related component, useNode will access the same Node as the user component. In the main editor, you can retrieve the related component from the useEditor hook by accessing state.nodes[selectedNodeId].related[key].

  6. Serialize and restore editor state

    main

    Craft.js allows you to save the entire editor state as a JSON string, which can be stored in a database and later used to reconstruct the editor.

    • To Save: Call query.serialize() from the useEditor hook.
    • To Load: Pass the retrieved JSON string to the json prop of the <Frame /> component.
    // To Save
    const SaveButton = () => {
      const { query } = useEditor();
      return <a onClick={() => console.log(query.serialize()) }>Get JSON</a>
    }
    
    // To Load
    const App = () => {
      const jsonString = /* retrieve JSON from server */
      return (
        <Editor>
          <Frame json={jsonString}>
            ...
          </Frame>
        </Editor>
      )
    };
  7. Migrate EditorState events from 0.1.x to 0.2.x

    main

    In version 0.2.x, the events property in EditorState was changed from a single NodeId to a Set<NodeId> to support Multiselect. When accessing selected, hovered, or dragged via the useEditor hook, you must now use the .has() method instead of direct equality checks.

    // 0.1.x
    const { selected, hovered, dragged } = useEditor(state => ({
        selected: state.events.selected === 'some-node-id',
        hovered: state.events.hovered === 'some-node-id',
        dragged: state.events.dragged === 'some-node-id',
    }))
    
    // 0.2.x
    const { selected, hovered, dragged } = useEditor(state => ({
        selected: state.events.selected.has('some-node-id'),
        hovered: state.events.hovered.has('some-node-id'),
        dragged: state.events.dragged.has('some-node-id')
    }))
  8. Configure Nodes inside <Frame />

    main

    Because the <Frame /> component automatically creates a Node for all of its children, you can use <Element /> inside a <Frame /> to simply configure the values of those automatically created Nodes (e.g., making them droppable or setting custom properties).

    import { Craft, Frame, Element } from "@craftjs/core";
    
    const App = () => {
      return (
        <div>
          <h2>My App!</h2>
          <Craft resolver={{ MyComp }}>
            <h2>My Page Editor</h2>
            <Frame>
              {/* defines the Root Node, droppable */}
              <Element is="div" canvas>
                <h2>Drag me around</h2>
                <MyComp text="You can drag me around too" />
                {/* Canvas Node of type div, draggable and droppable */}
                <Element is="div" style={{ background: "#333" }} canvas>
                  <p>Same here</p>
                </Element>
              </Element>
            </Frame>
          </Craft>
        </div>
      );
    };
  9. Define Linked Nodes inside User Components

    main

    When using <Element /> inside a User Component, there is no Node in-place by default. You must use <Element /> to create a 'Linked Node'—a Node that is linked to the Node of the containing User Component via an arbitrary id.

    Note: <Element /> used inside a User Component MUST specify an id prop.

  10. Create droppable regions with Canvas

    main

    To allow users to drag and drop components into a specific area of a component (creating a container), wrap the target area in a <Canvas /> component. This turns that region into a valid drop zone within the editor.

    import {useNode} from "@craftjs/core";
    
    const Container = () => {
      const { connectors: {drag} } = useNode();
    
      return (
        <div ref={drag}>
          <Canvas id="drop_section">
             {/* Now users will be able to drag/drop components into this section */}
            <TextComponent />
          </Canvas>
        </div>
      )
    }
  11. Serialize and compress editor state

    main

    To save the editor state, use query.serialize() to obtain the JSON representation of the nodes. For efficient storage or transfer, it is recommended to compress this JSON using a library like lzutf8 and encode it to base64.

    To copy the compressed state to the clipboard, you can combine query.serialize(), lz.compress(), and lz.encodeBase64() with a clipboard utility like copy-to-clipboard.

    // Example: Copying compressed state to clipboard
    const json = query.serialize();
    copy(lz.encodeBase64(lz.compress(json)));