codemirror-interact icon indicating copy to clipboard operation
codemirror-interact copied to clipboard

Proposal: allow drag to continue outside of editor area

Open curran opened this issue 1 year ago • 2 comments

As a user editing a number with the interactive number dragger widget, I want to be able to continue dragging outside the code editor, so that I can get a greater range.

For example, if a number is positioned close to the left edge of the editor and I want to make it smaller, I don't have that much room to drag left. However in the UI I'm working on, there is a sidebar, so in theory it should be possible to intercept mousemove events from the top level document rather than from the code editor area to extend the range.

curran avatar Dec 31 '23 04:12 curran

This is the current "rule" I'm using

      // a rule for a number dragger
      {
        // the regexp matching the value
        regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
        // set cursor to "ew-resize" on hover
        cursor: 'ew-resize',
        // change number value based on mouse X movement on drag
        onDrag: (text, setText, e) => {
          if (onInteract) onInteract();
          const newVal = Number(text) + e.movementX;
          if (isNaN(newVal)) return;
          setText(newVal.toString());
        },
      },

curran avatar Dec 31 '23 04:12 curran

This code works, but has a bug when the number of digits change:

toomanynumbers


// a rule for a number dragger
{
  // the regexp matching the value
  regexp: /(?<!\#)-?\b\d+\.?\d*\b/g,
  // set cursor to "ew-resize" on hover
  cursor: 'ew-resize',
  // change number value based on mouse X movement on drag
  // This works fine with the 999 to 1000 transition
  // onDrag: (text, setText, e) => {
  //   if (onInteract) onInteract();
  //   const newVal = Number(text) + e.movementX;
  //   if (isNaN(newVal)) return;
  //   setText(newVal.toString());
  // },

  // This DOES NOT works fine with the 999 to 1000 transition
  // TODO figure out why
  onClick(text, setText, e) {
    let newVal = Number(text);

    const handleDrag = (e: MouseEvent) => {
      if (onInteract) onInteract();
      newVal += e.movementX;
      if (isNaN(newVal)) return;
      setText(newVal.toString());
    };
    // add an event listener to the document for mouse move
    document.addEventListener(
      'mousemove',
      handleDrag,
    );

    // when the mouse is released, remove the event listener
    document.addEventListener('mouseup', () => {
      document.removeEventListener(
        'mousemove',
        handleDrag,
      );
    });
  },
},

If it starts with, say 900, then is dragged past 1000, each new change adds a digit. Likewise if it decreases from say 100 to 99, subsequent characters get deleted. Any ideas why that might be happening?

I think it has to do with this logic:

  updateText(target) {
    return (text) => {
      view.dispatch({
        effects: setInteract.of({ ...target, text }),
        changes: {
          from: target.pos,
          to: target.pos + target.text.length, // The target length has changed, but how can we update it?
          insert: text,
        },
      });
    };
  },

If only there were a way to redefine setText to be this.updateText(this.target) on each drag event, but from the rule definition rather than from within the class itself. I think the issue is that it's working with a stale definition of this.target, which I guess changes every time you call setText.

curran avatar Dec 31 '23 06:12 curran