Add in-app help, formula reference outlines, underline and auto-closing
- Help dialog (?, F1, Datei > Hilfe): user topics, all shortcuts and the formula functions with one search over everything. Topics and shortcuts are plain data in data/help.ts; the function list comes from codeReference.ts so new built-ins appear by themselves. - While a formula is edited, the grid outlines each referenced cell/range in the colour of its position in the text, and the outline can be dragged to move or resize the reference, as in Excel. Point-mode picks (click, drag, arrow keys) are outlined too, so keyboard picking is no longer blind. - Underline as a cell format, plus Mod+Shift+B/I/U toggles that switch on unless every selected cell already has the format. - A formula committed with open parentheses or an open text literal gets the missing closers appended; anything that still doesn't parse is left alone so the usual error shows instead of a guess. - Cells and tables with a fill but no text colour get dark or light text by WCAG contrast instead of the theme colour (no black on black).
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ref } from 'vue';
|
||||
import { useDocument, type DocumentStore } from '../document';
|
||||
import { isError, toA1 } from '../../engine';
|
||||
import { serializeDocument, parseDocument } from '../../io/json';
|
||||
@@ -489,6 +490,16 @@ describe('cell formatting, number formats, row height', () => {
|
||||
expect(s.tableById(id)!.rowHeights).toBeUndefined();
|
||||
});
|
||||
|
||||
it('underline is a cell format like bold and survives a JSON round-trip', () => {
|
||||
const s = fresh();
|
||||
const id = tableId(s, 'Umsatz');
|
||||
s.setRangeFormat(id, { col: 0, row: 0 }, { col: 1, row: 1 }, { underline: true, italic: true });
|
||||
s.replaceDocument(parseDocument(serializeDocument(s.snapshot())));
|
||||
expect(s.cellFormat(id, 1, 1)).toEqual({ underline: true, italic: true });
|
||||
s.setRangeFormat(id, { col: 0, row: 0 }, { col: 1, row: 1 }, { underline: false });
|
||||
expect(s.cellFormat(id, 0, 0)).toEqual({ italic: true });
|
||||
});
|
||||
|
||||
it('formatting + row heights survive a JSON round-trip', () => {
|
||||
const s = fresh();
|
||||
const id = tableId(s, 'Umsatz');
|
||||
@@ -2397,6 +2408,27 @@ describe('formula "point mode" (click/drag a cell to insert a reference)', () =>
|
||||
expect(ed.text).toBe('=B2');
|
||||
});
|
||||
|
||||
it('pointRect outlines the picked cell/range until typing or closing the editor', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
const ed = mockEditor(id, '=');
|
||||
s.registerFormulaEditor(ed.handle);
|
||||
expect(s.pointRect.value).toBeNull();
|
||||
s.pointClick(id, 1, 2);
|
||||
expect(s.pointRect.value).toEqual({ tableId: id, c0: 1, r0: 2, c1: 1, r1: 2 });
|
||||
s.pointDrag(id, 2, 3, 0, 1); // dragged up-left: normalised
|
||||
expect(s.pointRect.value).toEqual({ tableId: id, c0: 0, r0: 1, c1: 2, r1: 3 });
|
||||
s.noteFormulaTyped();
|
||||
expect(s.pointRect.value).toBeNull();
|
||||
s.pointClick(id, 0, 0);
|
||||
s.registerFormulaEditor(null);
|
||||
expect(s.pointRect.value).toBeNull();
|
||||
// no formula in the buffer -> nothing picked, nothing outlined
|
||||
s.registerFormulaEditor(mockEditor(id, 'text').handle);
|
||||
s.pointClick(id, 0, 0);
|
||||
expect(s.pointRect.value).toBeNull();
|
||||
});
|
||||
|
||||
it('typing between two clicks makes the second an insert, not a replace', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
@@ -3016,3 +3048,69 @@ describe('whole-column / whole-row references', () => {
|
||||
expect(val(s, name, 'A1')).toBe(6);
|
||||
});
|
||||
});
|
||||
|
||||
describe('references of the formula being edited (coloured, draggable)', () => {
|
||||
afterEach(() => useDocument().registerFormulaEditor(null));
|
||||
|
||||
/** Like mockEditor(), but its text is a ref -- as in the real editors,
|
||||
* which is what keeps store.formulaRefs live. */
|
||||
function reactiveEditor(tableId: string, initialText: string) {
|
||||
const text = ref(initialText);
|
||||
return {
|
||||
handle: {
|
||||
tableId,
|
||||
getState: () => ({ text: text.value, start: text.value.length, end: text.value.length }),
|
||||
replace: (s: number, e: number, value: string) => {
|
||||
text.value = text.value.slice(0, s) + value + text.value.slice(e);
|
||||
},
|
||||
},
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
it('lists each reference with its table, rectangle and colour, following the text', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
const other = s.addTable(s.doc.activeSheetId);
|
||||
s.renameObject(other, 'Reisen');
|
||||
const ed = reactiveEditor(id, '=SUMME(B2:C3)+A1+Reisen!A2+B2:C3');
|
||||
s.registerFormulaEditor(ed.handle);
|
||||
const refs = s.formulaRefs.value;
|
||||
expect(refs.map((r) => [r.tableId, r.c0, r.r0, r.c1, r.r1])).toEqual([
|
||||
[id, 1, 1, 2, 2],
|
||||
[id, 0, 0, 0, 0],
|
||||
[other, 0, 1, 0, 1],
|
||||
[id, 1, 1, 2, 2],
|
||||
]);
|
||||
expect(refs[0].color).not.toBe(refs[1].color);
|
||||
expect(refs[3].color).toBe(refs[0].color); // same reference, same colour
|
||||
ed.text.value = '=D4';
|
||||
expect(s.formulaRefs.value.map((r) => [r.c0, r.r0])).toEqual([[3, 3]]);
|
||||
s.registerFormulaEditor(null);
|
||||
expect(s.formulaRefs.value).toEqual([]);
|
||||
});
|
||||
|
||||
it('whole columns are clamped to the table and marked as not draggable', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
s.registerFormulaEditor(reactiveEditor(id, '=SUMME(B:B)').handle);
|
||||
const [r] = s.formulaRefs.value;
|
||||
expect(r).toMatchObject({ c0: 1, r0: 0, c1: 1, r1: s.tableById(id)!.rows - 1, whole: true });
|
||||
expect(s.moveFormulaRef(0, 2, 0, 2, 3)).toBe(false);
|
||||
});
|
||||
|
||||
it('moveFormulaRef rewrites one reference, keeping qualifier and $ locks', () => {
|
||||
const s = fresh();
|
||||
const id = s.addTable(s.doc.activeSheetId);
|
||||
const other = s.addTable(s.doc.activeSheetId);
|
||||
s.renameObject(other, 'Reisen');
|
||||
const ed = reactiveEditor(id, '=SUMME($A$1:B2)*Reisen!C3');
|
||||
s.registerFormulaEditor(ed.handle);
|
||||
s.moveFormulaRef(0, 1, 1, 2, 3); // moved/resized
|
||||
expect(ed.text.value).toBe('=SUMME($B$2:C4)*Reisen!C3');
|
||||
s.moveFormulaRef(1, 0, 0, 1, 1); // a single cell grown into a range
|
||||
expect(ed.text.value).toBe('=SUMME($B$2:C4)*Reisen!A1:B2');
|
||||
s.moveFormulaRef(0, 3, 3, 3, 3); // shrunk to one cell
|
||||
expect(ed.text.value).toBe('=SUMME($D$4)*Reisen!A1:B2');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user