Snap-to-grid for moving/resizing canvas objects, plus a per-sheet grid size
Sheet.gridSize (4-200px, default 24, configurable in Settings -> Blatt) now doubles as both the visible dot-grid's spacing and the snap increment while dragging to move any canvas object, or free/viewport- resizing a chart/text/button/fixed-viewport table. An "Automatisch" table's resize is left unsnapped on top of this -- it already discretizes to row/col boundaries by construction. Also fixes the visible grid not actually lining up with real snap positions once zoomed: the dot pattern's background-size now scales with zoom like the objects themselves already did, instead of staying a fixed screen-pixel size regardless of zoom level. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -297,6 +297,21 @@ describe('per-sheet appearance, zoom & CSV default (saved in the file)', () => {
|
||||
void b;
|
||||
});
|
||||
|
||||
it('grid size (snap increment) is per-sheet, clamped and rounded', () => {
|
||||
const s = fresh();
|
||||
const a = s.doc.sheets[0].id;
|
||||
const b = s.doc.sheets[1].id;
|
||||
expect(s.doc.sheets[0].gridSize).toBeUndefined(); // unset = DEFAULT_GRID_SIZE at use-sites
|
||||
s.setSheetGridSize(a, 40.6);
|
||||
expect(s.doc.sheets[0].gridSize).toBe(41); // rounded
|
||||
expect(s.doc.sheets[1].gridSize).toBeUndefined(); // untouched
|
||||
s.setSheetGridSize(a, 1); // below MIN_GRID_SIZE
|
||||
expect(s.doc.sheets[0].gridSize).toBe(4);
|
||||
s.setSheetGridSize(a, 9999); // above MAX_GRID_SIZE
|
||||
expect(s.doc.sheets[0].gridSize).toBe(200);
|
||||
void b;
|
||||
});
|
||||
|
||||
it('zoom is stored per sheet; the active-sheet zoom follows the tab', () => {
|
||||
const s = fresh();
|
||||
expect(s.activeZoom.value).toBe(1);
|
||||
@@ -310,26 +325,30 @@ describe('per-sheet appearance, zoom & CSV default (saved in the file)', () => {
|
||||
expect(s.activeZoom.value).toBe(1.5); // sheet 1 kept its own
|
||||
});
|
||||
|
||||
it('appearance + zoom + csvDelimiter survive a JSON round-trip; bad values dropped', () => {
|
||||
it('appearance + zoom + gridSize + csvDelimiter survive a JSON round-trip; bad values dropped', () => {
|
||||
const s = fresh();
|
||||
const a = s.doc.sheets[0].id;
|
||||
s.setSheetBackground(a, '#abcdef');
|
||||
s.setSheetForeground(a, '#012');
|
||||
s.setSheetShowGrid(a, false);
|
||||
s.setSheetZoom(a, 1.25);
|
||||
s.setSheetGridSize(a, 32);
|
||||
s.setCsvDelimiter(';');
|
||||
const before = s.snapshot();
|
||||
s.replaceDocument(parseDocument(serializeDocument(before)));
|
||||
expect(s.snapshot()).toEqual(before);
|
||||
expect(s.doc.csvDelimiter).toBe(';');
|
||||
expect(s.doc.sheets[0].gridSize).toBe(32);
|
||||
|
||||
const doc = JSON.parse(serializeDocument(s.snapshot()));
|
||||
doc.sheets[0].background = 'not-a-color';
|
||||
doc.sheets[0].zoom = 99;
|
||||
doc.sheets[0].gridSize = 'huge';
|
||||
doc.csvDelimiter = 'x';
|
||||
s.replaceDocument(parseDocument(JSON.stringify(doc)));
|
||||
expect(s.doc.sheets[0].background).toBeUndefined();
|
||||
expect(s.doc.sheets[0].zoom).toBe(2); // clamped, not dropped
|
||||
expect(s.doc.sheets[0].gridSize).toBeUndefined(); // not a number -> dropped
|
||||
expect(s.doc.csvDelimiter).toBe(',');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,7 +75,7 @@ import type {
|
||||
TableView,
|
||||
TextObject,
|
||||
} from './types';
|
||||
import { defaultTableView, DEFAULT_ROW_H } from './types';
|
||||
import { defaultTableView, DEFAULT_ROW_H, MIN_GRID_SIZE, MAX_GRID_SIZE } from './types';
|
||||
import { createSeedDocument } from '../data/seed';
|
||||
import { validateDocument } from '../io/json';
|
||||
|
||||
@@ -855,6 +855,12 @@ function createStore() {
|
||||
s.showGrid = show;
|
||||
markDirty();
|
||||
}
|
||||
function setSheetGridSize(sheetId: string, size: number) {
|
||||
const s = sheetById(sheetId);
|
||||
if (!s) return;
|
||||
s.gridSize = Math.round(Math.min(MAX_GRID_SIZE, Math.max(MIN_GRID_SIZE, size)));
|
||||
markDirty();
|
||||
}
|
||||
function setSheetZoom(sheetId: string, z: number) {
|
||||
const s = sheetById(sheetId);
|
||||
if (!s) return;
|
||||
@@ -2874,6 +2880,7 @@ function createStore() {
|
||||
setSheetBackground,
|
||||
setSheetForeground,
|
||||
setSheetShowGrid,
|
||||
setSheetGridSize,
|
||||
setSheetZoom,
|
||||
activeZoom,
|
||||
setZoom,
|
||||
|
||||
@@ -161,10 +161,17 @@ export interface Sheet {
|
||||
foreground?: string;
|
||||
/** show the canvas dot-grid; unset / true = shown */
|
||||
showGrid?: boolean;
|
||||
/** grid spacing in px -- also the snap increment for moving/resizing
|
||||
* canvas objects on this sheet; unset = DEFAULT_GRID_SIZE */
|
||||
gridSize?: number;
|
||||
/** per-sheet canvas zoom (0.4 – 2); unset = 1 */
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_GRID_SIZE = 24;
|
||||
export const MIN_GRID_SIZE = 4;
|
||||
export const MAX_GRID_SIZE = 200;
|
||||
|
||||
export interface SelectionRange {
|
||||
anchor: CellCoord;
|
||||
focus: CellCoord;
|
||||
|
||||
Reference in New Issue
Block a user