Commit Graph
2818 Commits
Author SHA1 Message Date
Harrison BoothandClaude Sonnet 5.5 292b784f02 fix(calc): apply "Set precision as displayed" in calculation (#1366)
* ft-121.7.9: apply Set precision as displayed in calculation (#1343)

With the option on, a formula result (and each element of a spill) is
rounded to its cell's number format before it is stored and before other
formulas read it. Numbers typed, pasted or reformatted while it is on are
rounded at once, and turning it on through formulas.calculationOptions
{precisionAsDisplayed} rounds every number constant, as Excel does.

The rounding is NumberFormat::round_as_displayed in the numfmt crate,
which shares the renderer's digit logic (fixed, scientific, fraction).
General, date/time, text, digitless and conditional formats keep the full
value, as observed in Excel through COM. Nothing changes when the flag is
off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ft-121.7.9: round conditional formats by the sign's section under precision as displayed (#1343)

Excel does round formats with a condition when "Set precision as
displayed" is on; the PR kept them at full value. Observed through COM
with synthetic values (and in gridcraft-corpus
excel/precision-displayed/pad-on-conditional-sections.xlsx): conditions
are ignored and the sign picks the section, the first for positives and
the second for negatives. `[<1]0.00;0` shows 2+1/3 as 2 but stores 2.33,
and stores -10/3 as -3.

Two more observed details: with a condition and a single number section,
negatives keep the full value (`[<1]0.00`), unless an `@` section follows
(`[<1]0.00;@` rounds them by the first section); and a completely empty
section passes to the next one (`0.0;;0.000` stores -2.333 as -2.333,
`;0.0` stores 2.333 as 2.3), for formats with or without a condition.

Display is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* engine: recalculate spill anchors when a spill cell is reformatted under precision as displayed

round_edited_as_displayed only looked at stored-cell changes, but a spill
destination lives in Sheet::spill, so reformatting it (or its row or column) left
the anchor out of the changed set and the element unrounded. Add each spill anchor
whose range holds a changed cell or a changed row/column style to the changed set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* numfmt: drop a cast the fraction merge made redundant

* precision as displayed: keep locked constants on protected sheets, round new sheets, fix manual-mode comment

* precision as displayed: name the input sheet in the new-sheet histogram test

* precision as displayed: use six values in the new-sheet histogram test

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 22:24:49 +01:00
FredApps c402953828 Merge pull request #3425 from ZIDAN44/fix/2874-german-insert-cells
i18n: German context menus' Insert reads "Zellen einfügen", not a second "Einfügen" (#2874)
2026-11-05 15:03:10 +01:00
FredApps 0a58f94517 Merge remote-tracking branch 'origin/main' into pr 2026-11-05 14:49:58 +01:00
FredApps 6ea9f538a7 Merge pull request #2593 from helenkwok/web/host-page
Web: page host mode, so a page can host GridCraft in its own document (#2592)
2026-11-05 13:58:25 +01:00
Pranav 4e43ad8868 UI: RefEdit follow-ups: a dialog keeps the sheet it was opened on, a click on the selected cell picks, quoted sheet names in Solver (#3180)
* UI: a dialog's reference boxes belong to the sheet it was opened on, recorded when it opens rather than at the first pick

* UI: a click on the cell already selected writes it into the reference box being picked into

* Solver: references split into areas only at commas outside quoted sheet names, through one splitter shared with the reference boxes

* Solver: a constraint splits at its relation only outside quoted sheet names, in constraint text and in a model loaded from cells

* Solver: Save Model counts a constraint's areas outside quoted sheet names, and reads a number on the right side as one value whatever its separators

* UI: a Ctrl+click that follows a link is no pick of the cell already selected

The press on the grid tells the reference box being picked into that a
cell was selected only in the branches that select: Shift+click, Ctrl+click
that adds a cell, and a plain click. A Ctrl+click on a link's text follows
the link and selects nothing, so a link back to the selected cell no longer
replaces the box's text.

* UI: while a reference box picks, a second quick click on a cell picks it

A click on the same cell within the double-click interval started editing
the cell and skipped the pick, also when the earlier click came before the
box was clicked into. While a reference box is the one grid selections
fill, the double-click handling is skipped and the click picks.

* Docs: reference boxes take a second quick click as a pick; a followed link is none

* UI: while a reference box picks, a click on a link or checkbox picks its cell

A link was followed (Go To moved the selection into the box, and a web
link's question replaced the dialog) and a checkbox was toggled. While a
reference box is the one grid selections fill, a click only picks the cell
clicked, with or without Ctrl. The test of the Ctrl+click on a link back to
the selected cell becomes part of a test of links on the same sheet, a web
link and a checkbox.
2026-11-05 13:20:51 +01:00
Helen Kwok e06b2a7ead gridcraft-web: speak the host messages through craft-web-host
The transports, the message types and the origin checks come from the shared crate (storytold/craft-libs#420); GridCraft keeps the download hook, the open size limit, the workbook-saved rule and the author.
2026-11-05 17:51:00 +08:00
Helen Kwok bf6bee500a web host mode: refuse an oversized host file before copying it, cap the name, and document trust and limits 2026-11-05 17:03:44 +08:00
Helen Kwok d628abb7b0 Web: page host mode, so a page can host GridCraft in its own document (#2592)
data-host="page" on the canvas opts a page that loads the module into its own document into the host bridge. The ?host=parent messages travel as non-bubbling CustomEvents on the canvas; Bridge holds a Transport (Parent or Page) and shares the save, dirty and ready logic. Unframed ?host=parent still gets no bridge. Hosting guide documents both modes.
2026-11-05 17:03:44 +08:00
Zinadin ZidanandClaude Opus 5.5 cb05971659 i18n: German context menus' Insert reads "Zellen einfügen", not a second "Einfügen" (#2874)
German showed "Einfügen" for Paste and again for Insert in the cell menu
("Einfügen…") and the row and column header menus ("Einfügen"). The cell
menu's Insert… now reads "Zellen einfügen…", as Excel's German menu does.
The header menus' Insert gets its own key, "Header Menu|Insert", German
"Zellen einfügen"; the plain "Insert" stays as the Insert dialog's title
and button use it, and every other catalog gets the new key with the text
it shows today.

A test reads the three menus' items from menus.rs and checks that no two
read alike in German.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:04:32 +06:00
Francisco Herreraandalosslessdev 1fb191571b Charts: draw every doughnut series as a ring (#1827)
* Charts: draw every doughnut series as a ring (#1827)

* Charts: submit doughnut hole size and first slice angle when the drag ends (#1827)

---------

Co-authored-by: alosslessdev <alosslessdev@users.noreply.github.com>
2026-11-05 05:13:35 +01:00
FredAppsandClaude Opus 5.5 ede12f9da6 About: credits tables fit without side-scrolling and their columns resize (#3320)
* About: credits tables fit without side-scrolling and their columns resize

The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.

The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.

The About dialog opens 820 points wide, so every column fits at its natural width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Let every credits column be dragged, and refit changed tables

The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.

A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.

fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Refit credits tables on what they measure, not the fitted widths

Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.

It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.

The table now compares the natural widths and the clip rect's width,
which scrolling does not move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:33:46 +01:00
FredApps 642980b724 Merge pull request #481 from prxnvt/pr/form-controls
Form controls: buttons, sliders and spin buttons, with Assign Action, Format Control and XLSX round trip
2026-11-05 04:16:46 +01:00
FredApps 73fad54671 Merge origin/main into pr/form-controls 2026-11-05 04:05:43 +01:00
skylar taylor-barrick 919a6b58fe Merge pull request #2203 from Higgs32584/fix/2181-link-open-confirmation
link: require confirmation for programmatic link.open (#2181)
2026-11-05 00:30:08 +01:00
skylar taylor-barrick e8bdd0efd6 Merge pull request #2527 from RedBearAK/xml-node-cap
XLSX: the XML element cap counts only elements kept in the tree
2026-11-05 00:29:00 +01:00
Dylan M. Taylor 3162ac09f3 Pass the Linux signing key to the release workflow (#3380) 2026-11-04 17:45:51 -05:00
prxnvt b9595ccdec Merge origin/main into pr/form-controls
Conflicts:
- crates/xlsx/src/read.rs: main's names.unique() kept with this branch's
  (sheet, read_controls) result from read_sheet.
- crates/ui-egui/tests/it/keytips_generated.rs: regenerated with
  cargo xtask keytips from the merged keytips.json (413 sequences).
- docs/file-format-parity.md, docs/ui-parity.md: header and revision
  history keep main's entries and this branch's form-controls entries.
2026-11-04 13:01:54 -05:00
Dylan M. Taylor 632e4a1bc5 formula, calc: LET/LAMBDA names looked up through a map; ragged array literals refused before reserving (#3312) 2026-11-03 22:57:52 -05:00
Dylan M. Taylor e5f6fa8197 ui-egui: reference colours through a map; cap fill-character repeats per cell (#3311) 2026-11-03 22:57:23 -05:00
Dylan M. Taylor f77ed98cdb chart: a series without values takes no category-span entries (#3300) 2026-11-03 22:56:56 -05:00
Dylan M. Taylor 22c5dfe0df engine: protected sheets gate filter reapply, outline, line sizes and floating pictures; pivot destination resolved before the structure check; user-limited edit ranges stay locked (#3296) 2026-11-03 22:56:06 -05:00
Dylan M. Taylor cdd3a59dbc Treemap rows keep running aggregates; General-fit width is clamped (#3290) 2026-11-03 22:55:35 -05:00
Dylan M. Taylor bebe7de877 engine: cap header and footer sections at 64 KiB when fields expand (#3284)
The 10,000-character header spec was bounded, but each &A, &F or &Z
added the whole sheet name or path again, and a JSON workbook's sheet
name has no length limit. A field that would take a section past
64 KiB is now left out.
2026-11-03 22:55:07 -05:00
Dylan M. Taylor 4421bc6790 engine: weekday fill series moves by whole weeks (#3272)
edit.fillSeries with dateUnit "weekday" stepped one calendar day at a
time, so a large step cost work proportional to its size for every
cell. It now uses add_weekdays, which moves whole weeks arithmetically.
2026-11-03 22:54:17 -05:00
Dylan M. Taylor 017c80572c functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit (#3263)
* functions: match TEXTSPLIT/TEXTBEFORE/TEXTAFTER delimiters through an index, within a work limit

The delimiter matcher compared every delimiter, duplicates included, at
every position of the text. Delimiters are now deduplicated and grouped
by the character a match is anchored on, and a search that would
compare more than 100 million characters returns #VALUE!.

* functions: pass float literals in the delimiter test
2026-11-03 22:53:47 -05:00
Dylan M. Taylor a31178f519 functions: NUMBERVALUE strips trailing percent signs in one pass (#3262)
Each trailing % copied the rest of the text again, so a long run of
them made one call quadratic. The signs are now counted and cut off in
place.
2026-11-03 22:53:18 -05:00
Dylan M. Taylor 184c0ec84d ods: charge a cell's text when the cell is read, not when its row ends (#3257)
The importer kept every cell of a row and only charged their text to
the 64 MiB allowance when the row finished, so one row of long cells
could hold up to 16,384 MiB before being refused. Text is now charged
as each cell finishes; the row charges only its repeats.
2026-11-03 22:52:28 -05:00
Dylan M. Taylor 065f05dbec Security: MODE counts through an index, FREQUENCY bins by binary search (#3198)
* functions: MODE counts through an index, FREQUENCY bins by binary search

MODE/MODE.SNGL/MODE.MULT searched the list of distinct numbers seen so far
for every input, and FREQUENCY scanned the sorted bins for every data value,
so both were quadratic in computed arrays. MODE now keys counts by value
(-0 folded into 0, NaN never merged) and keeps first-occurrence order;
FREQUENCY finds the first bin at or above each value with partition_point
over the order it already sorts. Results are unchanged.

* address review: FREQUENCY keeps NaN data in the overflow bin and NaN bins empty
2026-11-03 22:51:58 -05:00
Dylan M. Taylor db05400e67 calc: list each dependent once when a change spreads (#3190)
* calc: list each dependent once when a change spreads

* calc: check the dependency closure against a walk of every cell

Random graphs of single-cell, narrow and wide range reads (some removed or unsettled), random seeds and changed areas: the closure with shared taken records and area lookups equals the one listing every dependent of every changed cell.
2026-11-03 22:51:29 -05:00
Dylan M. Taylor c0ea297615 calc: cap the text one & over arrays may build in all (#3189)
* calc: cap the text one & over arrays may build in all

* address review: concatenation over arrays stops at the byte budget without reserving the whole broadcast
2026-11-03 22:51:11 -05:00
Dylan M. Taylor 74111e5a93 check: charge relationship parts to the package's decompression budget (#3185)
* check: charge relationship parts to the package's decompression budget

* address review: charge bytes read before a package part decompression error
2026-11-03 22:50:40 -05:00
Dylan M. Taylor 014f030a15 engine: PivotTable filters test each record against a set of selected labels (#3279)
Every source record scanned the filter's whole selection, so a large
selection multiplied by many records was slow even when nothing passed.
Each filter's labels are lowercased into a set once per computation.
2026-11-03 18:10:48 -05:00
Dylan M. Taylor a305229287 PDF export: wrap text with running widths (#3208)
wrap_lines split an over-long word by re-measuring and copying the
rest of the word for every piece it cut, and only checked its line
limit once the word was done, so one long word in a narrow text box
cost word length x pieces. Each new word also re-measured the whole
line built so far.

Line and piece widths are now running sums of glyph widths (the same
sum text_width makes), so every character is measured once, and the
line limit is checked while a word is being split. The lines produced
are unchanged; a test compares them with the old wrapper.
2026-11-03 16:11:47 -05:00
Dylan M. Taylor df137b8ee8 xlsx: repair repeated sheet names through a set of assigned names (#3211)
Importing a workbook whose sheets share a name rescanned every sheet for
every suffix tried, so a file of repeated names opened in cubic time.
SheetNames keeps the assigned names and the next suffix per stem.
2026-11-03 10:53:20 -05:00
Higgs32584andClaude Opus 5.5 e12b462b90 Port the link.open confirmation check into the merged insert.rs (#2181)
Upstream folded cmd/insert/*.rs back into insert.rs, so the merge left
the policy check in a file that is no longer compiled. Restore that file
to upstream and apply the check in the live open_link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:45:50 -05:00
Brandon Thomas ed2347782d Merge PR #3252 (author's fix: an expanded formula bar leaves room for the worksheet)
# Conflicts:
#	crates/ui-egui/src/formula_bar.rs
2026-11-03 10:00:05 -05:00
Brandon ThomasandClaude Opus 5.5 865a789c73 Review fixes for #3223, #3253, #3254, #3252, #3239, #3244 and #3268
- #3223: sniff the first 64 KiB for a web page and skip JSON and files over the text-import cap, so
  every CSV/TSV is no longer decoded in full before the size check.
- #3253: SYLK relative offsets use checked arithmetic (no overflow on R[9223372036854775807]); at
  most 1024 column-width records are kept; regression test.
- #3254: a single selected merged cell searches the whole sheet, as one selected cell does.
- #3252: the formula bar's height cap is half the room below the ribbon, not half the window, so
  the worksheet stays visible in a short window (CodeRabbit Major).
- #3239, #3244, #3252, #3268: es/de/cs/zh/ko/ru/pt entries for the new interface strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:59:27 -05:00
Brandon Thomas 166234eff0 Merge PR #3268 2026-11-03 09:55:39 -05:00
Brandon Thomas 6c386f79ed Merge PR #3252 2026-11-03 09:55:39 -05:00
Brandon Thomas 5ae7ecfab9 Merge PR #3244 2026-11-03 09:55:39 -05:00
Brandon Thomas 0081e4f29e Merge PR #3239 2026-11-03 09:55:38 -05:00
Brandon Thomas ebf762a03a Merge PR #3254 2026-11-03 09:55:38 -05:00
Brandon Thomas 3bdfa6ed71 Merge PR #3253 2026-11-03 09:55:38 -05:00
Brandon Thomas d906bcbb6f Merge PR #3223 2026-11-03 09:55:38 -05:00
Brandon Thomas d7b53fcc20 Merge PR #3267 2026-11-03 09:55:37 -05:00
Brandon Thomas 31156f7949 Merge PR #3251 2026-11-03 09:55:37 -05:00
Brandon Thomas 502fac89fa Merge PR #3237 2026-11-03 09:55:37 -05:00
Brandon Thomas 56cc52eb39 Merge PR #3250 2026-11-03 09:55:37 -05:00
Brandon Thomas a6b7cf5c2a Merge PR #3243 2026-11-03 09:55:37 -05:00
Brandon Thomas 85f09f05d6 Merge PR #3240 2026-11-03 09:55:36 -05:00