fix(sticky): fixed-size creation, shared presets module, live resize sync

Fixes three review findings:

1. Sticky creation no longer zoom-adapts — always creates at M preset
   (28px/260w) in world space. Consistent at any zoom level.

2. Preset definitions extracted to stickyPresets.ts (shared domain module).
   TextFormatToolbar and tools.ts both import from there — no more
   canvas→component dependency inversion.

3. TextEditor.onLiveResize callback syncs spatial index and transform
   box as sticky background grows during typing.
This commit is contained in:
Hiren Kangad
2026-03-13 00:31:44 +05:30
parent 706b00cf35
commit 2d05226ddd
6 changed files with 59 additions and 47 deletions
+5
View File
@@ -19,6 +19,7 @@ export class TextEditor {
private _viewport: Viewport | null = null;
private _container: HTMLElement | null = null;
private _onChange: (() => void) | null = null;
private _onLiveResize: ((item: SceneItem) => void) | null = null;
private _originalText: string = '';
/** True when a textarea is open. */
@@ -42,6 +43,7 @@ export class TextEditor {
viewport: Viewport,
container: HTMLElement,
onChange: () => void,
onLiveResize?: (item: SceneItem) => void,
): void {
// Accept text and sticky items
if (item.type !== 'text' && item.type !== 'sticky') return;
@@ -53,6 +55,7 @@ export class TextEditor {
this._viewport = viewport;
this._container = container;
this._onChange = onChange;
this._onLiveResize = onLiveResize || null;
const zoom = viewport.scale.x;
@@ -186,6 +189,7 @@ export class TextEditor {
data.text = ta.value;
item.displayObject.updateFromData(data);
data.h = item.displayObject.computedHeight;
this._onLiveResize?.(item);
}
};
@@ -221,6 +225,7 @@ export class TextEditor {
this._viewport = null;
this._container = null;
this._onChange = null;
this._onLiveResize = null;
this._originalText = '';
if (save) {
+38
View File
@@ -0,0 +1,38 @@
/**
* stickyPresets — sticky note size presets (shared domain module).
*
* Defines the S/M/L/XL/XXL presets for sticky text size and card width.
* Used by both canvas logic (tools, SceneManager) and UI (TextFormatToolbar).
* No React dependencies.
*/
export type StickyTextSize = 'S' | 'M' | 'L' | 'XL' | 'XXL';
export const STICKY_SIZES: StickyTextSize[] = ['S', 'M', 'L', 'XL', 'XXL'];
export const STICKY_FONT_MAP: Record<StickyTextSize, number> = {
S: 20, M: 28, L: 36, XL: 48, XXL: 60,
};
export const STICKY_WIDTH_MAP: Record<StickyTextSize, number> = {
S: 200, M: 260, L: 320, XL: 400, XXL: 480,
};
/** Default preset for new sticky creation. */
export const DEFAULT_STICKY_PRESET: StickyTextSize = 'M';
/** Map a fontSize to the nearest preset label. */
export function nearestStickySize(fontSize: number): StickyTextSize {
let best: StickyTextSize = 'M';
let bestDist = Infinity;
for (const size of STICKY_SIZES) {
const dist = Math.abs(fontSize - STICKY_FONT_MAP[size]);
if (dist < bestDist) { bestDist = dist; best = size; }
}
return best;
}
/** Get the card width for a given fontSize (snaps to nearest preset). */
export function getStickyWidthForSize(fontSize: number): number {
return STICKY_WIDTH_MAP[nearestStickySize(fontSize)];
}
+5 -10
View File
@@ -14,7 +14,7 @@ import { DrawingSprite } from './sprites/DrawingSprite';
import type { DrawingObject } from './scene-format';
import { TextEditor } from './TextEditor';
import { clampTextFontSize } from './textLimits';
import { snapToStickyPreset } from '../components/TextFormatToolbar';
import { DEFAULT_STICKY_PRESET, STICKY_FONT_MAP, STICKY_WIDTH_MAP } from './stickyPresets';
export enum ToolType {
SELECT = 'SELECT',
@@ -37,9 +37,6 @@ const defaultOptions: ToolOptions = {
// Creation defaults — zoom-aware via screenToWorld()
const DEFAULT_TEXT_SCREEN_FONT = 24;
const DEFAULT_STICKY_SCREEN_FONT = 28;
const DEFAULT_STICKY_SCREEN_WIDTH = 220;
const DEFAULT_STICKY_SCREEN_HEIGHT = 66;
type CleanupFn = (() => void) | null;
@@ -327,17 +324,15 @@ export function activateTool(
const rect = container.getBoundingClientRect();
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
const zoom = viewport.scale.x;
const preset = snapToStickyPreset(screenToWorld(DEFAULT_STICKY_SCREEN_FONT, zoom, 16, 72));
const cardW = preset.width;
const cardH = screenToWorld(DEFAULT_STICKY_SCREEN_HEIGHT, zoom, 40, 200);
const cardW = STICKY_WIDTH_MAP[DEFAULT_STICKY_PRESET];
const cardH = 60; // auto-computed by StickySprite
const stickyData = {
id: crypto.randomUUID(),
type: 'sticky' as const,
x: world.x - cardW / 2,
y: world.y - cardH / 2,
w: cardW,
h: cardH, // will be auto-computed by StickySprite
h: cardH,
sx: 1,
sy: 1,
angle: 0,
@@ -347,7 +342,7 @@ export function activateTool(
name: '',
visible: true,
text: '',
fontSize: preset.fontSize,
fontSize: STICKY_FONT_MAP[DEFAULT_STICKY_PRESET],
fontFamily: 'Inter, system-ui, sans-serif',
fill: '#ffd43b', // default yellow
textColor: '#1a1a1a',