How to prevent block splitting in custom nodes?

I’m working on implementing custom block nodes, and I’m encountering an issue with node splitting. Specifically, I want to prevent certain block nodes from being split when the user presses Enter at the middle of the node, but I also need to maintain the ability to exit the node at the end or remove it with backspace.

Important note: I specifically want to avoid using isolating: true because it prevents the desired behavior of exiting the node or removing it with backspace.

Example of the issue:

Currently, I have a custom node defined like this:

import { mergeAttributes, Node } from '@tiptap/core';
import { VueNodeViewRenderer } from '@tiptap/vue-2';
import NoActionGroupComponent from './GroupComponent.vue';

export default Node.create({
  name: 'group',
  group: 'block',
  content: 'block*',
  defining: true,

  addAttributes() {
    return {
      name: {
        default: '',
      },
    };
  },

  parseHTML() {
    return [
      {
        tag: 'group',
      },
    ];
  },

  renderHTML({ HTMLAttributes }) {
    return ['group', mergeAttributes(HTMLAttributes), 0];
  },

  addNodeView() {
    return VueNodeViewRenderer(NoActionGroupComponent);
  },
});

I’ve considered using addKeyboardShortcuts() to handle the Enter key, but I’m struggling to correctly detect when the cursor is at the end of the node vs. inside it, especially considering potential nested structures.

Has anyone successfully implemented this kind of behavior for custom nodes without using isolating: true ?

I’m looking for approaches that allow for the flexibility of exiting the node and removing it when needed, while still preventing unwanted splits. Any code examples or suggestions would be greatly appreciated. Thank you!

To anyone having the same senario, here is the solution:

import { mergeAttributes, Node } from '@tiptap/core';
import { VueNodeViewRenderer } from '@tiptap/vue-2';
import { TextSelection } from '@tiptap/pm/state';
import NoActionGroupComponent from './GroupComponent.vue';

export default Node.create({
  name: 'group',
  group: 'block',
  content: 'block*',
  defining: true,

  addAttributes() {
    return {
      name: { default: '' },
    };
  },

  parseHTML() {
    return [{ tag: 'group' }];
  },

  renderHTML({ HTMLAttributes }) {
    return ['group', mergeAttributes(HTMLAttributes), 0];
  },

  addKeyboardShortcuts() {
    return {
      Enter: ({ editor }) => {
        const { state, view } = editor;
        const { selection } = state;
        const { $anchor } = selection;

        const currentDepth = $anchor.depth - 1;
        const parentNode = $anchor.node(currentDepth);

        // Check if in a group level
        if (parentNode.type.name !== 'group') return false;

        const endPos = $anchor.end(currentDepth);
        const isAnchorAtEndOfGroup = $anchor.pos === endPos - 1;
        if (isAnchorAtEndOfGroup) return false;

        const isAnchorAtEndOfLine = selection.$from.parentOffset === selection.$from.parent.nodeSize - 2;
        if (!isAnchorAtEndOfLine) return false;

        // Insert a new paragraph node
        const paragraphNode = state.schema.nodes.paragraph.create();
        const transaction = state.tr.insert($anchor.pos, paragraphNode);

        // Set new selection
        const newSelection = TextSelection.create(transaction.doc, $anchor.pos + 1);
        transaction.setSelection(newSelection);
        view.dispatch(transaction);
        view.focus();

        return true;
      },
    };
  },

  addNodeView() {
    return VueNodeViewRenderer(NoActionGroupComponent);
  },
});