mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-05 01:58:58 +00:00
Merge PR #3267
This commit is contained in:
commit
d7b53fcc20
5 files changed
+216
-7
No files matched your search
@@ -101,6 +101,10 @@ pub enum Drag {
|
||||
Select,
|
||||
Rows(u32),
|
||||
Cols(u32),
|
||||
/// Point mode over the row headers: the formula's reference runs from this row to the pointer.
|
||||
PointRows(u32),
|
||||
/// Point mode over the column headers: the formula's reference runs from this column to the pointer.
|
||||
PointCols(u32),
|
||||
/// Reordering one contiguous whole-column selection by its header.
|
||||
MoveCols {
|
||||
source: RangeRef,
|
||||
|
||||
@@ -48,7 +48,7 @@ fn drag_cursor(drag: &Drag, rtl: bool) -> Option<CursorIcon> {
|
||||
match drag {
|
||||
Drag::None => None,
|
||||
Drag::Select => Some(CursorIcon::Cell),
|
||||
Drag::Rows(_) | Drag::Cols(_) => Some(CursorIcon::Default),
|
||||
Drag::Rows(_) | Drag::Cols(_) | Drag::PointRows(_) | Drag::PointCols(_) => Some(CursorIcon::Default),
|
||||
Drag::Fill { .. } => Some(CursorIcon::Crosshair),
|
||||
Drag::Move { .. } | Drag::MoveCols { .. } | Drag::Object { handle: None, .. } => Some(CursorIcon::Move),
|
||||
Drag::ResizeCol { .. } => Some(CursorIcon::ResizeColumn),
|
||||
|
||||
@@ -318,6 +318,7 @@ fn handle_column_header_press(app: &mut SheetApp, input: &InteractionInput<'_>,
|
||||
return;
|
||||
}
|
||||
if point_header(app, wb, sh, RangeRef::cols(c, c), mods.shift, mods.command) {
|
||||
point_header_first_move(app, input);
|
||||
return;
|
||||
}
|
||||
// An armed Format Painter paints the columns dragged over instead of moving them.
|
||||
@@ -348,6 +349,7 @@ fn handle_row_header_press(app: &mut SheetApp, input: &InteractionInput<'_>, p:
|
||||
}
|
||||
let r = geo.row_at(sh, p.y);
|
||||
if point_header(app, wb, sh, RangeRef::rows(r, r), mods.shift, mods.command) {
|
||||
point_header_first_move(app, input);
|
||||
return;
|
||||
}
|
||||
let range = if mods.shift { RangeRef::rows(sel.anchor.row.min(r), sel.anchor.row.max(r)) } else { RangeRef::rows(r, r) };
|
||||
@@ -623,6 +625,10 @@ fn handle_dragging(app: &mut SheetApp, input: &InteractionInput<'_>) {
|
||||
app.session.run("selection.set", json!({"range": RangeRef::cols(anchor.min(c), anchor.max(c)).a1(), "active": sel.active.a1()}));
|
||||
scroll_by(app, vec2(auto.x, 0.0));
|
||||
}
|
||||
drag @ (Drag::PointRows(_) | Drag::PointCols(_)) => {
|
||||
point_header_to(app, geo, sh, wb, &drag, p);
|
||||
scroll_by(app, if matches!(drag, Drag::PointRows(_)) { vec2(0.0, auto.y) } else { vec2(auto.x, 0.0) });
|
||||
}
|
||||
Drag::MoveCols { source, .. } => {
|
||||
let col = geo.col_at(sh, p.x.clamp(geo.cells.left() + 1.0, geo.cells.right() - 1.0));
|
||||
let midpoint = geo.x(sh, col) + sh.col_width(col) * geo.z * 0.5;
|
||||
@@ -699,6 +705,14 @@ fn handle_release(app: &mut SheetApp, input: &InteractionInput<'_>) {
|
||||
}
|
||||
// Whole rows or columns clicked or dragged over by their headers.
|
||||
Drag::Rows(_) | Drag::Cols(_) => format_painter_apply(app),
|
||||
// The release may be the first frame to report the pointer's last position.
|
||||
drag @ (Drag::PointRows(_) | Drag::PointCols(_)) => {
|
||||
if resp.drag_stopped()
|
||||
&& let Some(p) = pos
|
||||
{
|
||||
point_header_to(app, geo, sh, wb, &drag, p);
|
||||
}
|
||||
}
|
||||
// Esc mid-drag lands here too (egui ends the drag on Esc): no menu, and the key is
|
||||
// spent on the drag so the grid's Esc doesn't also drop the copy.
|
||||
Drag::Fill { target: Some(t), menu: true } => {
|
||||
@@ -762,11 +776,13 @@ fn format_painter_apply(app: &mut SheetApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Point mode on a header: inserts a whole-row or whole-column reference instead of selecting.
|
||||
/// Point mode on a header: inserts a whole-row or whole-column reference instead of selecting,
|
||||
/// and dragging on from there extends it.
|
||||
fn point_header(app: &mut SheetApp, wb: &Workbook, sh: &Sheet, range: RangeRef, extend: bool, add: bool) -> bool {
|
||||
let Some(ed) = app.editor.as_mut().filter(|ed| ed.can_point()) else { return false };
|
||||
let edit_sheet = ed.sheet;
|
||||
let active_sheet = wb.active_sheet();
|
||||
let pressed = range;
|
||||
let range = if extend {
|
||||
ed.point
|
||||
.and_then(|point| crate::editor::formula_refs(&ed.text).into_iter().find(|(start, end, ..)| (*start, *end) == point))
|
||||
@@ -787,9 +803,7 @@ fn point_header(app: &mut SheetApp, wb: &Workbook, sh: &Sheet, range: RangeRef,
|
||||
} else {
|
||||
range
|
||||
};
|
||||
let reference = range.a1();
|
||||
let reference =
|
||||
if ed.sheet != wb.active_sheet() { format!("{}!{reference}", gridcraft_engine::formula::quote_sheet(&sh.name)) } else { reference };
|
||||
let reference = header_reference(ed, wb, sh, range);
|
||||
if add {
|
||||
ed.append_ref(&reference);
|
||||
} else {
|
||||
@@ -798,9 +812,53 @@ fn point_header(app: &mut SheetApp, wb: &Workbook, sh: &Sheet, range: RangeRef,
|
||||
ed.point_anchor = None;
|
||||
ed.point_cell = Some(range.start);
|
||||
ed.request_focus = true;
|
||||
// The drag keeps the end of the reference the press didn't land on.
|
||||
app.grid.drag = if range.is_full_cols() {
|
||||
Drag::PointCols(if range.start.col == pressed.start.col { range.end.col } else { range.start.col })
|
||||
} else {
|
||||
Drag::PointRows(if range.start.row == pressed.start.row { range.end.row } else { range.start.row })
|
||||
};
|
||||
true
|
||||
}
|
||||
|
||||
fn header_reference(ed: &EditState, wb: &Workbook, sh: &Sheet, range: RangeRef) -> String {
|
||||
let reference = range.a1();
|
||||
if ed.sheet != wb.active_sheet() { format!("{}!{reference}", gridcraft_engine::formula::quote_sheet(&sh.name)) } else { reference }
|
||||
}
|
||||
|
||||
/// The frame that starts a drag reports no `dragged()`, so its movement is applied here.
|
||||
fn point_header_first_move(app: &mut SheetApp, input: &InteractionInput<'_>) {
|
||||
if input.response.drag_started()
|
||||
&& let Some(p) = input.position
|
||||
{
|
||||
let drag = app.grid.drag.clone();
|
||||
point_header_to(app, input.geo, input.sheet, input.workbook, &drag, p);
|
||||
}
|
||||
}
|
||||
|
||||
/// Point mode dragged over the headers: the pointed reference runs from the anchor to the pointer.
|
||||
fn point_header_to(app: &mut SheetApp, geo: &Geo, sh: &Sheet, wb: &Workbook, drag: &Drag, p: Pos2) {
|
||||
// The pointer's end moves with Shift+arrow next; the opposite corner keeps it a whole column or row.
|
||||
let (range, pointer, opposite) = match *drag {
|
||||
Drag::PointCols(anchor) => {
|
||||
let col = geo.col_at(sh, p.x.clamp(geo.cells.left() + 1.0, geo.cells.right() - 1.0));
|
||||
let range = RangeRef::cols(anchor.min(col), anchor.max(col));
|
||||
(range, CellRef::new(range.start.row, col), CellRef::new(range.end.row, anchor))
|
||||
}
|
||||
Drag::PointRows(anchor) => {
|
||||
let row = geo.row_at(sh, p.y.clamp(geo.cells.top() + 1.0, geo.cells.bottom() - 1.0));
|
||||
let range = RangeRef::rows(anchor.min(row), anchor.max(row));
|
||||
(range, CellRef::new(row, range.start.col), CellRef::new(anchor, range.end.col))
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
let Some(ed) = app.editor.as_mut().filter(|ed| ed.point_at_caret()) else { return };
|
||||
let reference = header_reference(ed, wb, sh, range);
|
||||
ed.insert_ref(&reference);
|
||||
ed.point_cell = Some(pointer);
|
||||
ed.point_anchor = Some((wb.active_sheet(), opposite));
|
||||
}
|
||||
|
||||
fn select_to_pointer(app: &mut SheetApp, geo: &Geo, sh: &Sheet, wb: &Workbook, p: Pos2) {
|
||||
let cell = CellRef::new(
|
||||
geo.row_at(sh, p.y.clamp(geo.cells.top() + 1.0, geo.cells.bottom() - 1.0)),
|
||||
@@ -1049,6 +1107,7 @@ pub fn point_move(app: &mut SheetApp, ed: &mut EditState, dr: i64, dc: i64, exte
|
||||
to
|
||||
};
|
||||
let text = if extend { RangeRef::new(anchor, to).a1() } else { to.a1() };
|
||||
let text = if ed.sheet != d.wb.active_sheet() { format!("{}!{text}", gridcraft_engine::formula::quote_sheet(&sheet.name)) } else { text };
|
||||
let sheet = d.wb.active_sheet();
|
||||
ed.insert_ref(&text);
|
||||
ed.point_cell = Some(to);
|
||||
|
||||
@@ -173,3 +173,148 @@ fn shift_does_not_extend_a_row_reference_across_sheets() {
|
||||
[("6:6".into(), Some("Other sheet".into())),]
|
||||
);
|
||||
}
|
||||
|
||||
fn press_and_drag(h: &mut egui_kittest::Harness<'_, SheetApp>, from: Pos2, to: Pos2, modifiers: Modifiers) {
|
||||
h.input_mut().events.push(Event::ModifiersChanged(modifiers));
|
||||
h.input_mut()
|
||||
.events
|
||||
.extend([Event::PointerMoved(from), Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers }]);
|
||||
h.step();
|
||||
drag_to(h, to);
|
||||
}
|
||||
|
||||
fn drag_to(h: &mut egui_kittest::Harness<'_, SheetApp>, to: Pos2) {
|
||||
h.input_mut().events.push(Event::PointerMoved(to));
|
||||
h.step();
|
||||
}
|
||||
|
||||
fn release(h: &mut egui_kittest::Harness<'_, SheetApp>, at: Pos2) {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
|
||||
}
|
||||
|
||||
fn text<'a>(h: &'a egui_kittest::Harness<'_, SheetApp>) -> &'a str {
|
||||
h.state().editor.as_ref().map_or("", |ed| ed.text.as_str())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_over_column_headers_extends_the_reference() {
|
||||
let mut h = harness();
|
||||
let (c, e, a, d) = (header_center(&h, false, 2), header_center(&h, false, 4), header_center(&h, false, 0), header_center(&h, false, 3));
|
||||
press_and_drag(&mut h, c, e, Modifiers::NONE);
|
||||
assert_eq!(text(&h), "=SUM(C:E");
|
||||
// Back past the press: the reference follows the pointer instead of keeping E.
|
||||
drag_to(&mut h, a);
|
||||
assert_eq!(text(&h), "=SUM(A:C");
|
||||
// The release position counts even without a movement frame before it.
|
||||
release(&mut h, d);
|
||||
assert_eq!(text(&h), "=SUM(C:D");
|
||||
assert_eq!(h.state().session.active().unwrap().selection.current().a1(), "F8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_over_row_headers_extends_the_reference() {
|
||||
let mut h = harness();
|
||||
let (r3, r6, r2) = (header_center(&h, true, 2), header_center(&h, true, 5), header_center(&h, true, 1));
|
||||
press_and_drag(&mut h, r3, r6, Modifiers::NONE);
|
||||
assert_eq!(text(&h), "=SUM(3:6");
|
||||
release(&mut h, r2);
|
||||
assert_eq!(text(&h), "=SUM(2:3");
|
||||
assert_eq!(h.state().session.active().unwrap().selection.current().a1(), "F8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_drag_on_another_sheet_keeps_the_qualifier() {
|
||||
let mut h = harness();
|
||||
h.state_mut().session.execute("home.insertSheet", json!({"name": "Other sheet"})).unwrap();
|
||||
h.run_steps(2);
|
||||
let (b, d) = (header_center(&h, false, 1), header_center(&h, false, 3));
|
||||
press_and_drag(&mut h, b, d, Modifiers::NONE);
|
||||
assert_eq!(text(&h), "=SUM('Other sheet'!B:D");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_drag_with_command_keeps_the_earlier_argument() {
|
||||
let mut h = harness();
|
||||
let (a, e, f) = (header_center(&h, false, 0), header_center(&h, false, 4), header_center(&h, false, 5));
|
||||
click(&mut h, a);
|
||||
press_and_drag(&mut h, e, f, Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE });
|
||||
assert_eq!(text(&h), "=SUM(A:A,E:F");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_drag_with_shift_keeps_the_far_end_of_the_reference() {
|
||||
let mut h = harness();
|
||||
let (c, e, d) = (header_center(&h, false, 2), header_center(&h, false, 4), header_center(&h, false, 3));
|
||||
click(&mut h, c);
|
||||
press_and_drag(&mut h, e, d, Modifiers { shift: true, ..Modifiers::NONE });
|
||||
assert_eq!(text(&h), "=SUM(C:D");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clicks_on_headers_replace_the_reference_after_a_drag_too() {
|
||||
let mut h = harness();
|
||||
let (c, e, g) = (header_center(&h, false, 2), header_center(&h, false, 4), header_center(&h, false, 6));
|
||||
press_and_drag(&mut h, c, e, Modifiers::NONE);
|
||||
release(&mut h, e);
|
||||
assert_eq!(text(&h), "=SUM(C:E");
|
||||
click(&mut h, g);
|
||||
assert_eq!(text(&h), "=SUM(G:G");
|
||||
}
|
||||
|
||||
fn shift_arrow(h: &mut egui_kittest::Harness<'_, SheetApp>, key: egui::Key) {
|
||||
let modifiers = Modifiers { shift: true, ..Modifiers::NONE };
|
||||
h.input_mut().events.push(Event::ModifiersChanged(modifiers));
|
||||
h.step();
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::Key { key, physical_key: None, pressed, repeat: false, modifiers });
|
||||
h.step();
|
||||
}
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_arrow_after_a_header_drag_moves_the_dragged_end() {
|
||||
let mut h = harness();
|
||||
let (c, e) = (header_center(&h, false, 2), header_center(&h, false, 4));
|
||||
press_and_drag(&mut h, c, e, Modifiers::NONE);
|
||||
release(&mut h, e);
|
||||
assert_eq!(text(&h), "=SUM(C:E");
|
||||
shift_arrow(&mut h, egui::Key::ArrowRight);
|
||||
assert_eq!(text(&h), "=SUM(C:F");
|
||||
|
||||
// Dragged leftwards, the dragged end is the first column.
|
||||
let mut h = harness();
|
||||
let (e, c) = (header_center(&h, false, 4), header_center(&h, false, 2));
|
||||
press_and_drag(&mut h, e, c, Modifiers::NONE);
|
||||
release(&mut h, c);
|
||||
assert_eq!(text(&h), "=SUM(C:E");
|
||||
shift_arrow(&mut h, egui::Key::ArrowLeft);
|
||||
assert_eq!(text(&h), "=SUM(B:E");
|
||||
|
||||
let mut h = harness();
|
||||
let (r1, r3) = (header_center(&h, true, 0), header_center(&h, true, 2));
|
||||
press_and_drag(&mut h, r1, r3, Modifiers::NONE);
|
||||
release(&mut h, r3);
|
||||
assert_eq!(text(&h), "=SUM(1:3");
|
||||
shift_arrow(&mut h, egui::Key::ArrowDown);
|
||||
assert_eq!(text(&h), "=SUM(1:4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_arrow_pointing_on_another_sheet_keeps_the_qualifier() {
|
||||
let mut h = harness();
|
||||
h.state_mut().session.execute("home.insertSheet", json!({"name": "Other sheet"})).unwrap();
|
||||
h.run_steps(2);
|
||||
let (b, d) = (header_center(&h, false, 1), header_center(&h, false, 3));
|
||||
press_and_drag(&mut h, b, d, Modifiers::NONE);
|
||||
release(&mut h, d);
|
||||
assert_eq!(text(&h), "=SUM('Other sheet'!B:D");
|
||||
let app = h.state_mut();
|
||||
let mut ed = app.editor.take().unwrap();
|
||||
gridcraft_ui_egui::grid::point_move(app, &mut ed, 0, 1, true, false);
|
||||
app.editor = Some(ed);
|
||||
assert_eq!(text(&h), "=SUM('Other sheet'!B:E");
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (a picture on the system clipboard pastes as a floating picture at the active cell with Cmd/Ctrl+V or Paste (#699); earlier: frozen columns keep their text, gridlines and merged borders whole while the sheet scrolls (#2544); earlier: every Paste Special option has its access key; earlier: the Draw tab's live stroke preview uses the pen's colour and thickness; earlier: copy quotes only cells with a tab or line break, as Excel does; earlier: Ctrl+. Next Corner of Selection; earlier: dragging the selection's border onto data asks before replacing it; earlier: RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (dragging over row or column headers in point mode extends the reference; earlier: a picture on the system clipboard pastes as a floating picture at the active cell with Cmd/Ctrl+V or Paste (#699); earlier: frozen columns keep their text, gridlines and merged borders whole while the sheet scrolls (#2544); earlier: every Paste Special option has its access key; earlier: the Draw tab's live stroke preview uses the pen's colour and thickness; earlier: copy quotes only cells with a tab or line break, as Excel does; earlier: Ctrl+. Next Corner of Selection; earlier: dragging the selection's border onto data asks before replacing it; earlier: RefEdit reference boxes in dialogs: Goal Seek, Solver and Data Analysis use them, the arrow keys select while a dialog is collapsed and F4 cycles a reference's anchors; earlier: Python cells: Python mode in the formula bar; earlier: Insert › Equation and the equation editor; earlier: every floating object selects, resizes from eight handles and deletes with the keyboard; earlier: Formulas › Error Checking › Trace Error; trace arrows stay through a zoom (#3097); earlier: Page Setup sets the page order, Down, then over or Over, then down, which printing, the print preview, Page Break Preview and Page Layout view follow; earlier: Formulas › Function Library as labelled buttons with category menus; Recently Used and the Insert Function categories; Esc leaves a File-page text field before closing the view; earlier: checkbox cells print as boxes; Ctrl+arrow and Ctrl+Shift+arrow while pointing a formula reference; editing a hyperlink loads its existing fields; AutoSum over a selected row puts its total to the right; ribbon catalog count 276 / 292) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
|
||||
|
||||
Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md).
|
||||
**UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured
|
||||
@@ -57,7 +57,7 @@ this logic, with one undo step.
|
||||
| Fill handle: drag to fill series, double-click to fill down, right-drag menu | fill done (Ctrl+drag swaps copy and series; month patterns use the workbook's 1900 or 1904 date system); right-drag menu partial: Copy Cells, Fill Series, Fill Formatting Only, Fill Without Formatting, and from a date Fill Days, Fill Weekdays, Fill Months and Fill Years, then Series…; a left-drag leaves an Auto Fill Options button with the same fill choices (gone once the workbook changes or the selection moves); Linear/Growth Trend and Flash Fill missing |
|
||||
| Drag-move and drag-copy cells (border drag, Ctrl/Option, Shift to insert) | border drag moves (M3) and asks "There's already data here. Do you want to replace it?" before replacing cells that hold data (`range.move`, #3136); Ctrl/Option to copy and Shift to insert are not wired to the border drag yet (reorder whole columns by their headers instead) |
|
||||
| In-cell editing, Enter/Tab movement, Alt/Option+Enter line breaks | done |
|
||||
| Point mode (click/drag/arrow references), reference colouring, F4 | done; Command/Ctrl-click adds separate arguments with the edit's locale separator, and Shift-click extends the current reference from a fixed anchor on the same sheet. Pointer-event regressions cover repeated/reversed selections, manual separators, sheet switches and keyboard continuation. |
|
||||
| Point mode (click/drag/arrow references), reference colouring, F4 | done; dragging over row or column headers points at whole rows or columns (`A:C`, `2:5`); Command/Ctrl-click adds separate arguments with the edit's locale separator, and Shift-click extends the current reference from a fixed anchor on the same sheet. Pointer-event regressions cover repeated/reversed selections, manual separators, sheet switches and keyboard continuation. |
|
||||
| Number-format colors (`[Green]`, `[Color1]`–`[Color56]`) | Rendered in grid and PDF from shared RGB mappings (#1452) |
|
||||
| Point mode (click/drag/arrow references), reference colouring, F4 | done |
|
||||
| AutoComplete in columns, function autocomplete and argument tips | done; cell helpers clip to the worksheet and hide when the editor scrolls out (#200) |
|
||||
@@ -238,6 +238,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Point mode: dragging over row or column headers extends the whole-row or whole-column reference, as it does over cells |
|
||||
| 2026-11-03 | minor | Frozen panes: text in frozen columns overflows only into its own pane and keeps its whole width while the sheet scrolls; frozen gridlines and merged-cell borders reach the freeze line, and the freeze handle measures the last frozen column itself (#2544). Page Layout view gridlines stop at each page's last column |
|
||||
| 2026-11-03 | minor | Excel 2003 menu access keys: 43 legacy sequences (Insert, Edit, Format, Data, View, Tools and File ▸ Page Setup, e.g. Alt+I, R inserts rows, Alt+E, A, A clears all, Alt+D, S sorts, Alt+T, O opens Options) run the existing commands and dialogs; Alt+I, D, V and T paint no badges, as in Excel (#941, #2311) |
|
||||
| 2026-11-03 | minor | Paste Special: every option has Excel's access key (A, F, V, T, C, N, X, W, R, U; O, D, S, M, I; B, E; L pastes the link at once), not only F, V, T, C and E (#1482) |
|
||||
|
||||
Reference in new issue
Block a user