Skip to content

Editor Demo

Below you can see a working demo of the UI Editor.
You can drag components from the blueprints panel onto the canvas or reaarange them via drag and drop within the canvas itself.
Click components to see their properties in the component inspector.
The tree panel shows you an overview of components in the canvas where you can also hide or show individual components.

Double clicking any text will enter in place editing.


Show editor content
Show code
vue
<template>
  <CraftEditor
    :config="config"
    :inheritStyles="true"
    :useIframe="true"
    @iframe-load="onIframeLoad"
  >
    <CraftCanvas componentName="div" />
  </CraftEditor>
  <div class="editor-switch">
    <label for="editorEnabled">Preview Content: </label>
    <input
      id="editorEnabled"
      type="checkbox"
      name="editorEnabled"
      v-model="previewContent"
    />
    <div class="text-sm">(disables editor drag and drop)</div>
  </div>
</template>
<script lang="ts" setup>
import { CraftEditorConfig, useEditor } from "@versa-stack/v-craft";
import blueprintsLibrary from "./blueprints";
import { resolverMap } from "./resolvermap";
import { demoContent } from "./demo-content";
import { onBeforeMount, ref, watch } from "vue";

const editor = useEditor();

onBeforeMount(() => {
  if (!editor.hasNodes) {
    editor.setNodes(demoContent);
  }
});

const previewContent = ref(false);

if (!previewContent.value) {
  editor.enable();
}

watch(
  () => previewContent.value,
  (disabled) => {
    if (!disabled) {
      editor.enable();
    }

    if (disabled) {
      editor.disable();
    }
  }
);

const config: CraftEditorConfig = {
  blueprintsLibrary,
  resolverMap,
};

const onIframeLoad = (iframe: HTMLIFrameElement) => {
  const syncDarkMode = () => {
    const isDark = document.documentElement.classList.contains('dark');
    const iframeHtml = iframe.contentDocument?.documentElement;
    if (iframeHtml) {
      if (isDark) {
        iframeHtml.classList.add('dark');
      } else {
        iframeHtml.classList.remove('dark');
      }
      iframeHtml.style.colorScheme = isDark ? 'dark' : 'light';
    }
  };

  // Wait for iframe content to be fully loaded
  const checkAndSync = () => {
    if (iframe.contentDocument?.documentElement) {
      syncDarkMode();
    } else {
      setTimeout(checkAndSync, 10);
    }
  };
  checkAndSync();

  const observer = new MutationObserver(syncDarkMode);
  observer.observe(document.documentElement, {
    attributes: true,
    attributeFilter: ['class'],
  });
};
</script>
<style lang="scss" scoped>
.editor-switch {
  display: inline-block;
}
</style>

Resolvers and Blueprints

Resolvers and Blueprints are essential concepts in the v-craft editor, enabling component management and preset layouts. They enhance the editor's functionality by defining available components and providing pre-configured component structures.

Resolver Maps

Resolver maps inform the editor about available components and their properties.

Purpose

  • Define existing components for the editor
  • Specify component events and properties
ts
import {
  CraftNodeResolverMap,
  defaultResolvers,
} from "@versa-stack/v-craft";
import CraftContainerExample from "../../CraftContainerExample.vue";
import CraftContainerSingleSlot from "../../CraftContainerSingleSlot.vue";

const resolveHtmlElements = (elements: string[]) => {
  const mapped: Record<string, any> = {};

  elements.forEach((element) => {
    mapped[element] = {
      componentName: element,
      eventsSchema: {
        $el: "div",
        children: [
          {
            $formkit: "textarea",
            name: "click",
            label: "onClick",
          },
        ],
      },
      propsSchema: [
        {
          $formkit: "text",
          label: "CSS Class(es)",
          name: "class",
        },
      ],
    };
  });
  return mapped;
};

export const htmlResolvers = {
  ...resolveHtmlElements([
    "article",
    "aside",
    "details",
    "div",
    "figure",
    "footer",
    "header",
    "li",
    "main",
    "nav",
    "ol",
    "section",
    "ul",
  ]),
};

export const resolverMap: CraftNodeResolverMap = {
  ...defaultResolvers,
  ...htmlResolvers,
  
  CraftContainerExample: {
    componentName: "CraftContainerExample",
    slots: ["header", "body"],
  },
  CraftContainerSingleSlot: {
    componentName: "CraftContainerSingleSlot",
  },
  CraftCanvas: {
    componentName: "CraftCanvas",
  },
};

Blueprints

Blueprints describe preset component trees that can be added to the page layout via drag-and-drop.

Purpose

  • Create reusable component structures
  • Enable quick addition of complex layouts
ts
import {
  Blueprints,
  BlueprintsLibrary,
  CraftNodeResolverMap,
  defaultBlueprints,
} from "@versa-stack/v-craft";
import { htmlResolvers } from "./resolvermap";

const createHtmlElementBlueprints = () => {
  const resolverMap: CraftNodeResolverMap<any> = htmlResolvers;
  const blueprints: Blueprints<any> = {};

  Object.entries(resolverMap).forEach(([key, value]) => {
    blueprints[key] = {
      label: `HTML <${value.componentName}>`,
      componentName: "CraftCanvas",
      props: {
        ...value.defaultProps,
        componentName: value.componentName,
      },
    };
  });

  return blueprints;
};

export default {
  groups: [
    defaultBlueprints,
    {
      metadata: {
        name: "html-elements",
      },
      label: "HTML Elements",
      blueprints: createHtmlElementBlueprints(),
    },
    {
      metadata: {
        name: "examples",
      },
      label: "Examples",
      blueprints: {
        CraftContainerExample: {
          label: "Container (2 Slots)",
          componentName: "CraftCanvas",
          props: {
            componentName: "CraftContainerExample",
          },
        },
        CraftContainerSingleSlot: {
          label: "Container (1 Slot)",
          componentName: "CraftCanvas",
          props: {
            componentName: "CraftContainerSingleSlot",
          },
        },
      },
    },
  ],
} as BlueprintsLibrary;