Skip to content

Mock Element Positioning System for Precise Cursor Handling #33

Closed

Description

@malibio

Implement character-level coordinate mapping system for precise cursor positioning during click-to-edit transitions, especially with different font sizes between display and edit modes.

Dependencies

None - This is a self-contained positioning system that can be implemented independently.

Related Issues

Self-Contained Scope

  • Complete mock element positioning system using hidden character mapping
  • Coordinate utilities for click-to-character position conversion
  • Integration with existing BaseNode textarea architecture
  • Mock data patterns for independent development and testing
  • Comprehensive testing with various font sizes and content types
  • Documentation and usage examples for positioning accuracy

Implementation Approach

  1. Mock Element Component: Hidden div that mirrors textarea with character-level spans
  2. Coordinate Mapping: Utilities using getBoundingClientRect for precise measurements
  3. BaseNode Integration: Replace current binary search with mock element positioning
  4. Testing Strategy: Accuracy tests across font sizes and content scenarios
  5. Demo/Documentation: Working examples showing positioning precision

Requirements

  • Character-Level Precision: Click coordinates map to exact character positions
  • Font Size Awareness: Accurate positioning across H1, H2, H3, normal text
  • Multi-line Support: Correct positioning for line breaks and wrapped content
  • Performance: Position calculation < 50ms for smooth user experience
  • Unicode Support: Handle emojis, accented characters, and grapheme clusters

Technical Implementation Notes

Step 1: Mock Element Component

<\!-- MockTextElement.svelte -->
<script>
  export let content = '';
  export let fontFamily = '';
  export let fontSize = '';
  export let fontWeight = '';
  export let lineHeight = '';
  export let width = '';

  $: characters = content.split('').map((char, idx) => ({ char, idx }));
</script>

<div 
  class="mock-text-element"
  style="
    font-family: {fontFamily};
    font-size: {fontSize};
    font-weight: {fontWeight};
    line-height: {lineHeight};
    width: {width}px;
    position: absolute;
    visibility: hidden;
    white-space: pre-wrap;
  "
>
  {#each characters as {char, idx}}
    <span id="mock-char-{idx}" data-position="{idx}">
      {char === '\n' ? '' : char}{#if char === '\n'}<br/>{/if}
    </span>
  {/each}
</div>

Step 2: Positioning Utilities

// CursorPositioning.js
export function findCharacterFromClick(mockElement, clickX, clickY, textareaRect) {
  const relativeX = clickX - textareaRect.left;
  const relativeY = clickY - textareaRect.top;
  
  let bestMatch = { index: 0, distance: Infinity };
  
  const allSpans = mockElement.querySelectorAll('[data-position]');
  for (const span of allSpans) {
    const rect = span.getBoundingClientRect();
    const mockRect = mockElement.getBoundingClientRect();
    
    const spanX = rect.left - mockRect.left;
    const spanY = rect.top - mockRect.top;
    
    const distance = Math.sqrt(
      Math.pow(spanX - relativeX, 2) + 
      Math.pow(spanY - relativeY, 2)
    );
    
    if (distance < bestMatch.distance) {
      bestMatch = { index: parseInt(span.dataset.position), distance };
    }
  }
  
  return bestMatch.index;
}

Step 3: BaseNode Integration

<\!-- BaseNode.svelte -->
<script>
  import MockTextElement from './MockTextElement.svelte';
  import { findCharacterFromClick } from './CursorPositioning.js';
  
  let mockElementRef;
  
  async function positionCursorFromClick(clickEvent) {
    if (\!textareaElement || \!mockElementRef) return;
    
    const textareaRect = textareaElement.getBoundingClientRect();
    const characterIndex = findCharacterFromClick(
      mockElementRef,
      clickEvent.clientX,
      clickEvent.clientY,
      textareaRect
    );
    
    textareaElement.setSelectionRange(characterIndex, characterIndex);
  }
</script>

<\!-- Add mock element alongside textarea -->
<MockTextElement 
  bind:this={mockElementRef}
  {content}
  fontFamily={getComputedStyle(textareaElement).fontFamily}
  fontSize={getComputedStyle(textareaElement).fontSize}
  fontWeight={getComputedStyle(textareaElement).fontWeight}
  lineHeight={getComputedStyle(textareaElement).lineHeight}
  width={textareaElement.offsetWidth}
/>

File Modifications Required:

  • Create: MockTextElement.svelte component
  • Create: CursorPositioning.js utilities
  • Modify: BaseNode.svelte to integrate mock element positioning
  • Replace: Current binary search logic with mock element approach

Patterns to Follow:

  • Follow existing BaseNode component structure and lifecycle
  • Use current content binding and event handling patterns
  • Maintain compatibility with existing TextNode integration
  • Follow project Svelte component conventions and TypeScript patterns

Acceptance Criteria

  • Click at start of text → cursor positions at character 0
  • Click between characters → cursor lands within 1-2 characters of click
  • Click at end of text → cursor positions at final character
  • Accurate positioning across different font sizes (H1=2rem, H2=1.5rem, normal)
  • Multi-line content positioning works correctly
  • Unicode characters and emojis handled properly
  • Performance < 50ms for position calculation
  • No memory leaks or cleanup issues
  • Integration with existing BaseNode API maintained
  • Cross-browser compatibility (Chrome, Firefox, Safari)

Resources & References

  • Logseq Implementation: Character mapping research from /Users/malibio/Zed Projects/logseq
  • Current Implementation: BaseNode positioning logic in /src/lib/design/components/BaseNode.svelte
  • Component Patterns: Existing Svelte components for structure and lifecycle
  • Testing Utilities: Current testing patterns in project

Definition of Done

  • MockTextElement component created and functional
  • CursorPositioning utilities implemented and tested
  • BaseNode integration completed with mock element positioning
  • All acceptance criteria met and verified through testing
  • Unit tests >90% coverage for positioning utilities
  • Manual testing across various content and font scenarios
  • Performance benchmarks meet requirements (< 50ms)
  • Code follows project standards (ESLint, Prettier, TypeScript)
  • Documentation includes usage examples and API reference
  • Ready for integration with hybrid markdown and decoration systems

Activity

  1. self-assigned this
    on Aug 10, 2025
  2. added a commit that references this issue on Aug 10, 2025
    e1fe303
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions