Resize handles too small / hard to grab #21

Open
opened 2026-04-20 15:31:56 +00:00 by mahmoud · 4 comments
Owner

The resize hit area at the widget corners is too small. easy to miss, as shown in the video.

The resize hit area at the widget corners is too small. easy to miss, as shown in the video.
Member

Implementation Spec for Issue #21

Objective

Make the transformer's resize anchors comfortable to grab without making them look chunky. Keep the visual anchor size modest; expand only the hit area.

Root cause

The transformer is configured with anchorSize: 8 in tools.js:46, smaller than Konva's own default of 10. On the attached video the user clearly mis-clicks an anchor; the 8 px hit box is too tight especially on high-DPI displays and with a trackpad. Both the visual and the pickable area are currently the same square.

Requirements

  • Increase the hit target around every transformer anchor (including the rotation handle) so clicks and trackpad taps within a few pixels of the anchor still register.
  • Keep the visible anchor size small enough that the outline doesn't look heavy or obscure content. A small bump is fine; a large anchor is not.
  • Do not change boundBoxFunc, rotateEnabled, keepRatio, colours, or any other transformer behaviour.
  • The fix must not cause adjacent anchors on small objects to overlap into each other's hit areas in a way that breaks the corner-versus-edge distinction.

Files to Modify

  • crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js — bump the visible anchorSize slightly and install a per-anchor hitFunc so every anchor has a padded hit box.

Implementation Plan

Step 1: Bump visible anchor size and install padded hitFunc

Files: crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js

  • Change anchorSize: 8 to anchorSize: 10 (Konva default). Visually almost identical, gives a hair more headroom.
  • After layer.add(transformer), iterate every anchor shape (transformer.find('._anchor')) and override hitFunc so each anchor draws a padded hit rectangle:
    var HIT_PAD = 6; // px of extra hit padding on every side (~22x22 pickable box)
    transformer.on('transformstart transformend', function() { /* no-op; just force hit-rect refresh if needed */ });
    function applyAnchorHitPadding() {
        transformer.find('._anchor').forEach(function(a) {
            var sz = typeof a.width === 'function' ? a.width() : transformer.anchorSize();
            a.hitFunc(function(ctx) {
                ctx.beginPath();
                ctx.rect(-HIT_PAD, -HIT_PAD, sz + HIT_PAD * 2, sz + HIT_PAD * 2);
                ctx.closePath();
                ctx.fillStrokeShape(a);
            });
        });
    }
    applyAnchorHitPadding();
    // Anchors are recreated whenever the transformer rebuilds (e.g. nodes change,
    // anchor set changes, or forceUpdate). Re-apply on each transform tick so the
    // padding survives those rebuilds.
    transformer.on('transform', applyAnchorHitPadding);
    
  • Place the helper inside init() so it shares closure with transformer. No new module-level state.

Dependencies: none

Acceptance Criteria

  • Clicking within 5-6 px of a corner or side anchor reliably starts a resize; no more mis-clicks selecting the underlying object.
  • Clicking within 5-6 px of the rotation handle reliably starts a rotate.
  • The anchor visual is still small (10 px filled square) — no chunky corner boxes obscuring content.
  • Adjacent anchors on a minimum-size object (40 x 30 per boundBoxFunc) do not overlap into a single indistinguishable hit zone; corner-vs-edge pick still works.
  • No regression in drag/resize/rotate behaviour on any object type (sticky, shape, document, kanban, calendar, mindmap, frame, webframe, image, text).
  • No regression on multi-select transform.

Notes

  • transformer.find('._anchor') is the documented Konva selector for the anchor shapes, including the rotation handle. Using it avoids hard-coding per-anchor names.
  • Re-applying the hitFunc on every transform event is cheap (eight shape walks + one hitFunc assignment each) and guarantees correctness when Konva rebuilds anchors — for example, when the selected node set changes via transformer.nodes([...]).
  • HIT_PAD: 6 was picked as a balance; if testing shows it still feels tight, bumping to 8 is safe — at anchorSize: 10 and HIT_PAD: 8 the hit box is 26 px, still smaller than the minimum 40 px object width so corner and edge anchors remain distinguishable.
## Implementation Spec for Issue #21 ### Objective Make the transformer's resize anchors comfortable to grab without making them look chunky. Keep the visual anchor size modest; expand only the hit area. ### Root cause The transformer is configured with `anchorSize: 8` in `tools.js:46`, smaller than Konva's own default of 10. On the attached video the user clearly mis-clicks an anchor; the 8 px hit box is too tight especially on high-DPI displays and with a trackpad. Both the visual and the pickable area are currently the same square. ### Requirements - Increase the *hit* target around every transformer anchor (including the rotation handle) so clicks and trackpad taps within a few pixels of the anchor still register. - Keep the *visible* anchor size small enough that the outline doesn't look heavy or obscure content. A small bump is fine; a large anchor is not. - Do not change `boundBoxFunc`, `rotateEnabled`, `keepRatio`, colours, or any other transformer behaviour. - The fix must not cause adjacent anchors on small objects to overlap into each other's hit areas in a way that breaks the corner-versus-edge distinction. ### Files to Modify - `crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js` — bump the visible `anchorSize` slightly and install a per-anchor `hitFunc` so every anchor has a padded hit box. ### Implementation Plan #### Step 1: Bump visible anchor size and install padded hitFunc Files: `crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js` - Change `anchorSize: 8` to `anchorSize: 10` (Konva default). Visually almost identical, gives a hair more headroom. - After `layer.add(transformer)`, iterate every anchor shape (`transformer.find('._anchor')`) and override `hitFunc` so each anchor draws a padded hit rectangle: ```js var HIT_PAD = 6; // px of extra hit padding on every side (~22x22 pickable box) transformer.on('transformstart transformend', function() { /* no-op; just force hit-rect refresh if needed */ }); function applyAnchorHitPadding() { transformer.find('._anchor').forEach(function(a) { var sz = typeof a.width === 'function' ? a.width() : transformer.anchorSize(); a.hitFunc(function(ctx) { ctx.beginPath(); ctx.rect(-HIT_PAD, -HIT_PAD, sz + HIT_PAD * 2, sz + HIT_PAD * 2); ctx.closePath(); ctx.fillStrokeShape(a); }); }); } applyAnchorHitPadding(); // Anchors are recreated whenever the transformer rebuilds (e.g. nodes change, // anchor set changes, or forceUpdate). Re-apply on each transform tick so the // padding survives those rebuilds. transformer.on('transform', applyAnchorHitPadding); ``` - Place the helper inside `init()` so it shares closure with `transformer`. No new module-level state. Dependencies: none ### Acceptance Criteria - [ ] Clicking within 5-6 px of a corner or side anchor reliably starts a resize; no more mis-clicks selecting the underlying object. - [ ] Clicking within 5-6 px of the rotation handle reliably starts a rotate. - [ ] The anchor visual is still small (10 px filled square) — no chunky corner boxes obscuring content. - [ ] Adjacent anchors on a minimum-size object (40 x 30 per `boundBoxFunc`) do not overlap into a single indistinguishable hit zone; corner-vs-edge pick still works. - [ ] No regression in drag/resize/rotate behaviour on any object type (sticky, shape, document, kanban, calendar, mindmap, frame, webframe, image, text). - [ ] No regression on multi-select transform. ### Notes - `transformer.find('._anchor')` is the documented Konva selector for the anchor shapes, including the rotation handle. Using it avoids hard-coding per-anchor names. - Re-applying the hitFunc on every `transform` event is cheap (eight shape walks + one hitFunc assignment each) and guarantees correctness when Konva rebuilds anchors — for example, when the selected node set changes via `transformer.nodes([...])`. - `HIT_PAD: 6` was picked as a balance; if testing shows it still feels tight, bumping to 8 is safe — at `anchorSize: 10` and `HIT_PAD: 8` the hit box is 26 px, still smaller than the minimum 40 px object width so corner and edge anchors remain distinguishable.
Member

Test Results

  • cargo check --workspace: pass
  • cargo clippy --workspace -- -D warnings: pass
  • cargo fmt --check: pass

Changes are entirely in vanilla JS (tools.js); Rust workspace is unaffected. UI behavior needs manual verification in the browser.

## Test Results - `cargo check --workspace`: pass - `cargo clippy --workspace -- -D warnings`: pass - `cargo fmt --check`: pass Changes are entirely in vanilla JS (tools.js); Rust workspace is unaffected. UI behavior needs manual verification in the browser.
Member

Implementation Summary

Changes

crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js

  • Bumped visible anchorSize from 8 to 10 (Konva default).
  • Added an applyAnchorHitPadding() helper that walks every ._anchor shape on the transformer and installs a hitFunc drawing a padded rect (+6 px on every side, ~22x22 px total pickable area).
  • Re-applies padding on every transform event and monkey-patches transformer.nodes and transformer.forceUpdate so the hit padding survives anchor rebuilds (selection changes, resize end, etc.).

Test Results

  • cargo check --workspace: pass
  • cargo clippy --workspace -- -D warnings: pass
  • cargo fmt --check: pass

Vanilla-JS change; needs manual verification in the browser.

Manual verification checklist

  • Clicks within ~6 px of any anchor (including rotation handle) reliably start a resize/rotate.
  • Visible anchor size stays small and unobtrusive.
  • Adjacent corner/edge anchors on a minimum-size (40x30) object stay distinguishable.
  • No regression on single-select, multi-select, or rubber-band select transforms.
  • No regression in transform flows for any widget type (webframe, calendar, kanban, mindmap, document, sticky, shape, text, frame, image).
## Implementation Summary ### Changes **`crates/hero_whiteboard_ui/static/web/js/whiteboard/tools.js`** - Bumped visible `anchorSize` from 8 to 10 (Konva default). - Added an `applyAnchorHitPadding()` helper that walks every `._anchor` shape on the transformer and installs a `hitFunc` drawing a padded rect (+6 px on every side, ~22x22 px total pickable area). - Re-applies padding on every `transform` event and monkey-patches `transformer.nodes` and `transformer.forceUpdate` so the hit padding survives anchor rebuilds (selection changes, resize end, etc.). ### Test Results - `cargo check --workspace`: pass - `cargo clippy --workspace -- -D warnings`: pass - `cargo fmt --check`: pass Vanilla-JS change; needs manual verification in the browser. ### Manual verification checklist - [ ] Clicks within ~6 px of any anchor (including rotation handle) reliably start a resize/rotate. - [ ] Visible anchor size stays small and unobtrusive. - [ ] Adjacent corner/edge anchors on a minimum-size (40x30) object stay distinguishable. - [ ] No regression on single-select, multi-select, or rubber-band select transforms. - [ ] No regression in transform flows for any widget type (webframe, calendar, kanban, mindmap, document, sticky, shape, text, frame, image).
Member

Pull request opened: #27

This PR implements the changes discussed in this issue.

Pull request opened: https://forge.ourworld.tf/lhumina_code/hero_whiteboard/pulls/27 This PR implements the changes discussed in this issue.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#21
No description provided.