AND/OR/NOT, Fehler-Literale und Zeilen/Spalten einfügen/löschen
- AND(...)/OR(...)/NOT(...) als gewöhnliche Funktionen (kein Kurzschluss- Sonderfall wie IF/XLOOKUP nötig, werten wie in echtem Excel alle Argumente eager aus). Dabei die bisher dreifach duplizierte Wahrheits-Prüfung zu functions.ts#isTruthy() zusammengezogen, jetzt von FUNCTIONS.IF, evalIf() und dem Block-IF-Statement gemeinsam genutzt. Schließt "kein AND/OR für zusammengesetzte Bedingungen" aus den Bekannten Grenzen, auch nutzbar in "Mit Schleife"-IF-Bedingungen. - Neue Grammatik-Ergänzung: ErrorLiteralNode. Jeder der sieben Fehlercodes (#REF, #DIV/0!, #NAME?, #ERR, #CIRC, #VALUE!, #N/A) lässt sich jetzt direkt in eine Formel tippen (=IF(A1=0;#N/A;A1)) und wertet unbedingt zu genau diesem Fehler aus. - Zeilen/Spalten einfügen/löschen mitten im Gitter (Rechtsklick auf einen Zeilen-/Spaltenkopf), nicht nur am Rand wie bisher: Zellinhalte, Formate und Zeilenhöhen/Spaltenbreiten verschieben sich innerhalb der Tabelle, und jede Formel im gesamten Dokument, die die Tabelle referenziert (auch über Tabelle!Zelle/Blatt!Tabelle::Zelle-Qualifier aus anderen Tabellen), wird automatisch mitgezogen. Ein Bereich, der die Stelle überspannt, wächst/schrumpft entsprechend; eine Referenz, die exakt die gelöschte Zeile/Spalte trifft, wird zu einem echten #REF-Literal im Formeltext (nutzt die neue ErrorLiteralNode-Grammatik). $-Sperren bleiben dabei bewusst wirkungslos (anders als beim Kopieren), unveränderte Referenzen bleiben byte-identisch. Token-Level-Splice wie shiftFormula (Kopieren/ Ausschneiden), keine AST-Neuserialisierung. 406 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -952,13 +952,44 @@ function onKeydown(e: KeyboardEvent) {
|
||||
|
||||
// ---- context menu -----------------------------------------
|
||||
const menu = ref({ x: 0, y: 0, open: false });
|
||||
/** Which row/col header (if any) the menu was opened on -- drives whether
|
||||
* it shows the row/col insert-delete items (Excel-style, only from a
|
||||
* header right-click) or the ordinary cell menu. */
|
||||
const menuCtx = ref<{ row: number | null; col: number | null }>({ row: null, col: null });
|
||||
function onContextMenu(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement | null;
|
||||
const rowHeadEl = target?.closest?.('th[data-rowhead]') as HTMLElement | null;
|
||||
const colHeadEl = target?.closest?.('th[data-colhead]') as HTMLElement | null;
|
||||
menuCtx.value = {
|
||||
row: rowHeadEl ? Number(rowHeadEl.dataset.rowhead) : null,
|
||||
col: colHeadEl ? Number(colHeadEl.dataset.colhead) : null,
|
||||
};
|
||||
const hit = cellFromEvent(e);
|
||||
// keep a multi-selection if the click landed inside it; otherwise select the cell
|
||||
if (hit && !inSelection(hit.c, hit.r)) select(hit.c, hit.r);
|
||||
menu.value = { x: e.clientX, y: e.clientY, open: true };
|
||||
}
|
||||
const menuItems = computed(() => {
|
||||
const t = table.value;
|
||||
const ctx = menuCtx.value;
|
||||
if (t && ctx.row !== null) {
|
||||
const r = ctx.row;
|
||||
const items = [
|
||||
{ label: 'Zeile oberhalb einfügen', action: () => store.insertRow(props.tableId, r) },
|
||||
{ label: 'Zeile unterhalb einfügen', action: () => store.insertRow(props.tableId, r + 1) },
|
||||
];
|
||||
if (t.rows > 1) items.push({ label: 'Zeile löschen', action: () => store.deleteRow(props.tableId, r) });
|
||||
return items;
|
||||
}
|
||||
if (t && ctx.col !== null) {
|
||||
const c = ctx.col;
|
||||
const items = [
|
||||
{ label: 'Spalte links einfügen', action: () => store.insertColumn(props.tableId, c) },
|
||||
{ label: 'Spalte rechts einfügen', action: () => store.insertColumn(props.tableId, c + 1) },
|
||||
];
|
||||
if (t.cols > 1) items.push({ label: 'Spalte löschen', action: () => store.deleteColumn(props.tableId, c) });
|
||||
return items;
|
||||
}
|
||||
const items = [
|
||||
{ label: 'Kopieren', action: () => store.copySelection() },
|
||||
{ label: 'Ausschneiden', action: () => store.cutSelection() },
|
||||
@@ -1061,6 +1092,7 @@ defineExpose({
|
||||
v-for="c in table.cols"
|
||||
:key="'h' + (c - 1)"
|
||||
class="col-head"
|
||||
:data-colhead="c - 1"
|
||||
:class="{ hot: colHot(c - 1), 'freeze-edge-right': c - 1 === frozenCols - 1 }"
|
||||
:style="c - 1 < frozenCols ? { left: colLeft(c - 1) + 'px', zIndex: '7' } : {}"
|
||||
@click="selectColumn(c - 1, $event)"
|
||||
@@ -1074,6 +1106,7 @@ defineExpose({
|
||||
<tr v-for="r in table.rows" :key="'r' + (r - 1)" :style="{ height: rowH(r - 1) + 'px' }">
|
||||
<th
|
||||
class="row-head"
|
||||
:data-rowhead="r - 1"
|
||||
:class="{ hot: rowHot(r - 1), 'frozen-row': r - 1 < frozenRows, 'freeze-edge-bottom': r - 1 === frozenRows - 1 }"
|
||||
:style="rowHeadStyle(r - 1)"
|
||||
@click="selectRow(r - 1, $event)"
|
||||
|
||||
Reference in New Issue
Block a user