cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Merge main into rules

Dami committed 9a7705f
Rules compose with main's protected ranges: adding a checkbox or a
dropdown changes no cells, so it asks only on a protected sheet, and
picking from a dropdown asks as typing does. The note mark and the
dropdown's mark share a cell's last column, the note's first.
commit
9a7705faef068dd970cdc3e2401c5fb6a42d5842
parent
87ad1175b6575cdb184746ee099f880d2d7ba66e
parent
eb1d3bdf1b5bb54f712ff08bdc684b31784bf211
author
Dami <dami.etoile@gmail.com>
178 files changed, +5498 -799
MREADME.md3+-
MROADMAP.md15+-
Mdocs/architecture.md21++-
Mdocs/charts.md26+++-
Mdocs/data.md59+++++++-
Mdocs/extending.md2+-
Mdocs/files.md12+-
Mdocs/keys.md8+-
Me2e/charts_test.go43+++++
Me2e/import_test.go12+-
Ae2e/notes_test.go71+++++++++
Me2e/pivot_test.go24+++
Ae2e/screencharts_test.go35++++
Me2e/screenlist_test.go23+++-
Ae2e/screennotes_test.go73+++++++++
Me2e/screensetup_test.go12++
Ae2e/testdata/screens/chart-area-light.html30++++
Ae2e/testdata/screens/chart-area.html30++++
Ae2e/testdata/screens/chart-axes-light.html30++++
Ae2e/testdata/screens/chart-axes-narrow.html15++
Ae2e/testdata/screens/chart-axes.html30++++
Ae2e/testdata/screens/chart-bar-percent.html30++++
Me2e/testdata/screens/chart-bar.html18+-
Me2e/testdata/screens/chart-editor-light.html8+-
Me2e/testdata/screens/chart-editor.html8+-
Me2e/testdata/screens/chart-light.html6+-
Me2e/testdata/screens/chart-line-light.html10+-
Me2e/testdata/screens/chart-line.html10+-
Me2e/testdata/screens/chart-narrow.html9+-
Ae2e/testdata/screens/chart-scatter-light.html30++++
Ae2e/testdata/screens/chart-scatter.html30++++
Me2e/testdata/screens/chart-selected.html6+-
Ae2e/testdata/screens/chart-stacked-light.html30++++
Ae2e/testdata/screens/chart-stacked.html30++++
Me2e/testdata/screens/chart-wide.html6+-
Me2e/testdata/screens/chart.html6+-
Me2e/testdata/screens/context-menu.html10+-
Me2e/testdata/screens/help-light.html2+-
Me2e/testdata/screens/help-narrow.html2+-
Me2e/testdata/screens/help-wide.html38++--
Me2e/testdata/screens/help.html2+-
Me2e/testdata/screens/macro-menu-light.html28++--
Me2e/testdata/screens/macro-menu.html28++--
Ae2e/testdata/screens/note-hover-light.html30++++
Ae2e/testdata/screens/note-hover-narrow.html16++
Ae2e/testdata/screens/note-hover.html30++++
Ae2e/testdata/screens/note-light.html30++++
Ae2e/testdata/screens/note-typing.html30++++
Ae2e/testdata/screens/note.html30++++
Me2e/testdata/screens/palette-narrow.html2+-
Me2e/testdata/screens/palette-search-light.html2+-
Me2e/testdata/screens/palette-search.html2+-
Me2e/testdata/screens/palette-wide.html2+-
Me2e/testdata/screens/palette.html2+-
Me2e/testdata/screens/pivot-editor-light.html2+-
Me2e/testdata/screens/pivot-editor-narrow.html2+-
Me2e/testdata/screens/pivot-editor.html2+-
Ae2e/testdata/screens/pivot-rename-light.html30++++
Ae2e/testdata/screens/pivot-rename.html30++++
Ae2e/testdata/screens/pivot-subtotals-light.html30++++
Ae2e/testdata/screens/pivot-subtotals.html30++++
Ae2e/testdata/screens/protect-picker.html30++++
Ae2e/testdata/screens/protect-warning-light.html30++++
Ae2e/testdata/screens/protect-warning-narrow.html16++
Ae2e/testdata/screens/protect-warning.html30++++
Ainternal/chart/area.go161++++++++++++++++++++
Ainternal/chart/axis.go150+++++++++++++++++++
Minternal/chart/bar.go127++++++++--------
Minternal/chart/chart.go71+++------
Minternal/chart/chart_test.go78++++++++--
Minternal/chart/column.go222++++++++++++++++++----------
Minternal/chart/image.go64++++----
Ainternal/chart/legend.go114++++++++++++++
Minternal/chart/pie.go19++-
Ainternal/chart/scatter.go239++++++++++++++++++++++++++++++
Ainternal/chart/stack.go197+++++++++++++++++++++++++
Minternal/chart/stress_test.go2+
Ainternal/chart/testdata/area-negative.txt10+
Ainternal/chart/testdata/area-stacked.txt12++
Ainternal/chart/testdata/area.txt12++
Ainternal/chart/testdata/axis-log.txt12++
Ainternal/chart/testdata/axis-min-max.txt12++
Ainternal/chart/testdata/axis-min.txt12++
Ainternal/chart/testdata/bar-log.txt10+
Ainternal/chart/testdata/bar-percent.txt12++
Minternal/chart/testdata/bar-single.txt10+-
Ainternal/chart/testdata/bar-stacked.txt12++
Minternal/chart/testdata/bar.txt14+-
Minternal/chart/testdata/column-narrow.txt4+-
Minternal/chart/testdata/column-negative.txt6+-
Ainternal/chart/testdata/column-percent.txt12++
Minternal/chart/testdata/column-single.txt6+-
Ainternal/chart/testdata/column-stacked-negative.txt12++
Ainternal/chart/testdata/column-stacked.txt12++
Minternal/chart/testdata/column.txt6+-
Ainternal/chart/testdata/images.txt17++
Ainternal/chart/testdata/legend-none.txt12++
Ainternal/chart/testdata/legend-right.txt12++
Minternal/chart/testdata/line-single.txt8+-
Minternal/chart/testdata/line.txt6+-
Ainternal/chart/testdata/no-gridlines.txt10+
Ainternal/chart/testdata/pie-no-legend.txt10+
Ainternal/chart/testdata/scatter-single.txt10+
Ainternal/chart/testdata/scatter-trend.txt12++
Ainternal/chart/testdata/scatter.txt12++
Minternal/chart/types.go66++++++--
Ainternal/chart/types_test.go155+++++++++++++++++++
Minternal/fileio/snapshot.go11+
Minternal/fileio/xlsx.go15+-
Minternal/fileio/xlsx_diff_test.go6+
Minternal/fileio/xlsx_excelize_test.go61++++++--
Minternal/fileio/xlsxbook.go6+-
Minternal/fileio/xlsximport.go19++-
Ainternal/fileio/xlsxnotes.go134+++++++++++++++++
Ainternal/fileio/xlsxnotes_test.go62++++++++
Minternal/fileio/xlsxsheet.go2+
Minternal/fileio/xlsxview_test.go2+-
Minternal/fileio/xlsxviewread.go10+-
Minternal/fileio/xlsxwrite.go3+
Ainternal/sheet/cellfile.go92++++++++++++
Minternal/sheet/chart.go16+-
Minternal/sheet/chart_test.go9+
Ainternal/sheet/chartfile.go81++++++++++
Ainternal/sheet/chartopts.go106+++++++++++++
Minternal/sheet/clipfmt.go39+++++-
Minternal/sheet/file.go113+--------------
Minternal/sheet/insertbook.go50++++--
Minternal/sheet/insertbook_test.go18+-
Minternal/sheet/linecopy_test.go33++++-
Minternal/sheet/lines.go6+-
Ainternal/sheet/notes.go112++++++++++++++
Ainternal/sheet/notes_test.go104+++++++++++++
Minternal/sheet/ops.go17++-
Minternal/sheet/pivot.go3+-
Minternal/sheet/pivot_test.go4+-
Minternal/sheet/pivotcalc.go104+++----------
Ainternal/sheet/pivotcols.go126++++++++++++++++
Ainternal/sheet/pivotcols_test.go76++++++++++
Minternal/sheet/pivotlayout.go46+++---
Ainternal/sheet/protect.go164+++++++++++++++++++++
Ainternal/sheet/protect_test.go73+++++++++
Minternal/sheet/sheet.go33++++-
Minternal/sheet/view.go40++++--
Ainternal/ui/chartaxes.go97++++++++++++
Ainternal/ui/chartaxes_test.go133+++++++++++++++++
Minternal/ui/charteditor.go134+++++++++++++---
Minternal/ui/charts.go3+-
Minternal/ui/clipboard.go17++
Minternal/ui/commands.go9+
Minternal/ui/context.go4+-
Minternal/ui/context_test.go2+-
Minternal/ui/edit.go12+-
Minternal/ui/entry.go12++
Minternal/ui/fill.go9+
Minternal/ui/graphics.go1+
Minternal/ui/hosts.go10+
Minternal/ui/importplace.go28++-
Minternal/ui/importplace_test.go17+-
Minternal/ui/linecopy_test.go26+++
Minternal/ui/menubar.go4+-
Minternal/ui/model.go9+-
Ainternal/ui/notes.go139+++++++++++++++++
Ainternal/ui/notes_test.go62++++++++
Minternal/ui/overlay.go3+
Minternal/ui/panel.go3+
Minternal/ui/pivot.go17++
Minternal/ui/pivot_test.go39+++++-
Minternal/ui/pivotactions.go32++++-
Minternal/ui/pivoteditor.go12+-
Minternal/ui/prompt.go12+-
Ainternal/ui/protect.go226++++++++++++++++++++++++++++
Ainternal/ui/protect_test.go108++++++++++++++
Minternal/ui/rows.go9+-
Minternal/ui/rules.go10+-
Minternal/ui/sort.go3+
Minternal/ui/theme/scheme.go2+-
Minternal/ui/theme/theme.go6+
Minternal/ui/view.go2+
M README.md +2 -1
M ROADMAP.md +7 -8
@@ -110,10 +110,9 @@ work left open. Sizes: S (a day or two), M (about a week), L (weeks).
110110 | Precedent tracing explains when every precedent is on a hidden sheet. Done, for dependents too | A generic "no formula" hides why nothing shows | S |111111 | Settings > JEV API key checks the key with a test call. Done: one fixed question, "Key saved and checked" or why the check failed ([jev.md](docs/jev.md#setup)) | A mistyped key otherwise shows only as `#ERROR!` in cells | S |112112 | `012 serve`: open a file from the ssh command line, and save unsaved work to a recovery file on shutdown or idle timeout. Done: `ssh -t host file.012` (a one-word exec request taken as a file name inside the served directory, never run) and `.012-recovery/`, offered back on the next open ([ssh.md](docs/ssh.md#unsaved-work)) | Sessions reach a file in one step, and stopping the server or idling out keeps work | M |113-114-| Vim `p`/`P` after `yy` carries the rows' line formats, as a whole-row paste does | Only cell formats go with it | S |115-| Cutting a block clears the source cells to plain, as Sheets does | They keep showing their column or row format | S |116-| Replace current sheet removes charts whose range no longer fits the new data, or asks | They stay, pointing at empty cells, with a note | S |113+| Vim `p`/`P` after `yy` carries the rows' line formats, as a whole-row paste does. Done: `yy` and `dd` copy whole rows ([data.md](docs/data.md#copy-paste-and-fill)) | Only cell formats go with it | S |114+| Cutting a block clears the source cells to plain, as Sheets does. Done, over formatted columns, rows and the sheet ([data.md](docs/data.md#copy-paste-and-fill)) | They keep showing their column or row format | S |115+| Replace current sheet removes charts whose range no longer fits the new data, or asks. Done: removed, listed first in the note, and back on undo ([files.md](docs/files.md)) | They stay, pointing at empty cells, with a note | S |117116 118117 ### 2. Spreadsheet features Sheets users reach for119118
@@ -124,11 +123,11 @@ work left open. Sizes: S (a day or two), M (about a week), L (weeks).
124123 | Conditional formatting (color scales, rules on values and formulas), drawn in theme roles | Visual; must read under all 349 schemes | M |125124 | Data validation: dropdown lists (a picker in the cell), number and date rules, checkboxes | Pairs with the filter picker | M |126125 | Wrap text, row heights, borders, merged cells | Layout changes in the grid renderer | M to L |127-| Notes on cells (shown on hover and in the context line) | | S |126+| Notes on cells (shown on hover and in the context line). Done: Insert > Note (Shift+F2), a corner mark, the context line and a hover box, saved and in XLSX as Excel's notes; see [data.md](docs/data.md#notes) | | S |128127 | Locale: decimal comma, date order, list separator in formulas | Sheets' File > Settings > Locale | M |129-| Charts: scatter, area, stacked columns and bars, axis options, a legend position | Registry entries in `internal/chart` | M |130-| Pivot tables: column subtotals, renaming value columns, check the "(blank)" label against Sheets | Left open by pivots | S |131-| Protected ranges and sheets (warn on edit) | | S |128+| Charts: scatter, area, stacked columns and bars, axis options, a legend position. Done: area and scatter (with trend lines) types, stacked and 100% stacked columns, bars and areas, value axis minimum, maximum and log scale, gridlines on and off, the legend at the bottom, right or none; see [charts.md](docs/charts.md) | Registry entries in `internal/chart` | M |129+| Pivot tables: column subtotals, renaming value columns, check the "(blank)" label against Sheets. Done: a subtotal column per outer column group, R renames a value in the editor, and blank groups have a blank label as in Sheets ([data.md](docs/data.md#pivot-tables)) | Left open by pivots | S |130+| Protected ranges and sheets (warn on edit). Done: Data > Protect sheets and ranges, edits ask on the context line; an XLSX sheet's protection is noted on import; see [data.md](docs/data.md#protected-sheets-and-ranges) | | S |132131 133132 ### 3. Scale (see "Next: vast data" above and docs/limits.md)134133
M docs/architecture.md +14 -7
@@ -394,13 +394,20 @@ the user's editor (`AllowEditor`), since that starts a program.
394394 395395 `internal/chart` draws a chart in two layers that share one layout: text396396 for any terminal (block elements for bars, braille for lines, half blocks397-for pies) and, on terminals with kitty graphics, an image of the plot area398-with the axes and legend still terminal text. Each chart type is a layout399-in the `types` table, returning a plan that draws it both ways; the type400-constants and their names belong to `internal/sheet`, since charts are401-saved with sheets. Work is bounded by the chart's size, not its data: only402-the categories that fit are drawn, pie slices are found by binary search,403-and a pie image supersamples only pixels on a slice edge or the rim.397+for pies, eighths filled column by column for areas) and, on terminals398+with kitty graphics, an image of the plot area with the axes and legend399+still terminal text. Each chart type is a layout in the `types` table,400+returning a plan that draws it both ways, and the series its legend401+lists; `Draw` places the legend and gives the layout the room left. The402+type constants and their names, and the options of each chart403+(`sheet.ChartOptions`: stacking, trend lines, the value axis, gridlines,404+the legend), belong to `internal/sheet`, since charts are saved with405+sheets. Stacked bars are piles of spans, and a cell where two meet draws406+the lower as eighths over the upper as background. Work is bounded by the407+chart's size, not its data: only the categories that fit are drawn, areas408+are sampled once per column (per pixel column in images), pie slices are409+found by binary search, and a pie image supersamples only pixels on a410+slice edge or the rim.404411 405412 ## Serving over SSH406413
M docs/charts.md +22 -4
@@ -6,10 +6,28 @@ Insert > Chart charts the selection, or the table around the active cell,
66 guessing the header row, the label column and a title. The chart floats over77 the grid, pinned to a cell, and redraws when its data changes.88 9-The chart editor is a bar on the context line: Left/Right (or 1 to 4) picks10-column, bar, line or pie; S switches series between rows and columns; H and11-L toggle the header row and labels; R points at a new range; T edits the12-title. Enter keeps the changes, Esc undoes them.9+The chart editor is a bar on the context line: Left/Right (or 1 to 6) picks10+column, bar, line, pie, area or scatter; S switches series between rows and11+columns; H and L toggle the header row and labels; R points at a new range;12+T edits the title. Enter keeps the changes, Esc undoes them.13+14+| Type | Draws |15+|---|---|16+| Column, Bar | a bar per series in each category; K cycles Not stacked, Stacked and 100% stacked |17+| Line | a line per series |18+| Area | a line per series filled down to the axis, the smaller series in front; K stacks them as columns do |19+| Pie | the first series as slices, with each slice's share |20+| Scatter | the first series as X and every other one as Y against it, as in Sheets (with one series, the labels are X when they're numbers); E adds a linear trend line per series |21+22+A opens a second bar, as Sheets' Customize tab: N and X type the value23+axis' minimum and maximum (a number as a cell takes one, blank for24+automatic; values past a fixed end stop at it), L puts it on a log scale25+(values that aren't positive are left out, and its ends round out to26+powers of ten), G turns the gridlines off and on, and P moves the legend27+between the bottom, the right and none. A legend lists two series or more,28+and a pie's legend of slices always sits on its right. Enter or Esc goes29+back to the first bar, keeping the changes. On the value axis of a bar30+chart these options apply along the bottom; on a scatter, to Y.1331 1432 Click a chart to select it. Drag it to move it, drag its corner to resize it,1533 or use the arrows and Shift+arrows; Enter edits it, Del deletes it, and
M docs/data.md +53 -6
@@ -24,7 +24,10 @@ Shift+Space) copied and pasted at the top of a column, or the start of a
2424 row, take their column or row formats along, so a pasted column is2525 currency all the way down, not only where it had data; cut and pasted,2626 they move them, leaving the source columns plain. Pasted anywhere else,27-they paste as a block of the cells that hold something. Formulas reading27+they paste as a block of the cells that hold something. A block cut and28+pasted leaves the cells it came from plain, as Sheets does, even where29+a formatted column or row crosses them. Vim's `yy` and `dd` copy whole30+rows, so `p` and `P` bring their row formats along. Formulas reading2831 a column whose format changes, blank cells included, show the new format2932 at once (`=B5*2` shows currency when column B becomes currency). Copies also go to the3033 system clipboard as tab-separated text (OSC 52, so it works over SSH), and
@@ -84,15 +87,18 @@ opens docked at the right of the grid, with the results updating behind it:
8487 8588 - **Rows** group the data by a field's values, one row per value; with two8689 or more, the groups nest and each outer group gets a subtotal row.87-- **Columns** spread the groups across columns by a field's values.90+- **Columns** spread the groups across columns by a field's values; with91+ two or more, each outer group gets a subtotal column (`East Total`)92+ after its last column.8893 - **Values** summarize a field for each group: SUM, COUNTA, COUNT,8994 COUNTUNIQUE, AVERAGE, MAX or MIN (Sheets' "Summarize by"), shown as they9095 are or as a share of their row, column or grand total ("Show as").9196 A new value is summed when its field holds numbers, and counted92- otherwise.97+ otherwise. Its header is Sheets' `SUM of Units` until you rename it.9398 - **Filters** leave out rows by a field's values or a condition, in the9499 same picker as a filter's column.95-- **Grand total row** and **Grand total column** add Sheets' totals.100+- **Grand total row** and **Grand total column** add Sheets' totals, and101+ turn the subtotal rows and columns on and off with them.96102 97103 Up and Down pick a line. Space adds a field to the section it's on (pick98104 it from the fields of the data, type to narrow the list), opens a filter's
@@ -101,7 +107,9 @@ its sheet. On a row or column field, Left and Right order its groups A to
101107 Z, Z to A, or by a value's total, smallest or largest first, and102108 Shift+Up and Shift+Down move it before or after the others, which changes103109 how the groups nest. On a value, Left and Right change how it's104-summarized and S how it's shown. Del removes a field. Every change is an110+summarized, S how it's shown, and R (or F2) renames it on the context111+line, the name heading its columns; an empty name goes back to Sheets'.112+A value keeps its name when its summary changes. Del removes a field. Every change is an105113 undo step; Enter keeps them, Esc undoes them, and removes a pivot just106114 created. Data > Edit pivot table opens the editor again.107115
@@ -109,7 +117,10 @@ Groups follow Sheets: text ignoring case (`east` joins `East`), numbers
109117 and dates by value, with the source's format, so dates show as dates.110118 Numbers and text never share a group: `1` and the text `'1` are two.111119 Groups sort numbers first, then text, booleans and errors, and blank112-cells, shown as `(blank)`, last. Rows blank across the whole range are120+cells last; as in Sheets, the group of blank cells has a blank label,121+and its subtotal is just `Total`. Sorting an outer column field by a122+value's total orders whole outer groups, so their columns stay123+together. Rows blank across the whole range are113124 left out, so a range can reach past the data. SUM, AVERAGE, MIN and MAX114125 keep the column's number format; an error in a summed column shows as that115126 error, as SUM would.
@@ -147,3 +158,39 @@ searching formulas (Alt+=). The scope chip says where to search, as Sheets'
147158 "Search" choice: this sheet, all sheets, or the range selected when the bar148159 opened; Alt+S goes through them. Searching all sheets steps from sheet to149160 sheet in tab order, and replacing all across sheets is still one undo step.161+162+## Notes163+164+Insert > Note (Shift+F2, or Insert note on the right-click menu) adds a165+note to the active cell, as Sheets' notes: plain text typed on the context166+line, where Alt+Enter or Shift+Enter starts a new line (shown as ↵), and167+an empty note deletes it. A cell with a note shows ▝ in its top-right168+corner; while it is the active cell, the context line shows the note, and169+while the mouse is over it, a box beside it does. Delete notes (on the170+right-click menu) removes the notes of the selection.171+172+A note belongs to its cell: clearing the contents (Del) keeps it, it moves173+with the cell when rows or columns are inserted, deleted or sorted, and a174+copy or a cut takes it along, as in Sheets. Ctrl+Enter fills the entry175+into the selection and leaves each cell's note alone. Every change to a176+note is an undo step. Pivot table results can't take notes.177+178+## Protected sheets and ranges179+180+Data > Protect sheets and ranges works as Sheets' protection with "Show a181+warning when editing this range": it locks nothing, but an edit that182+touches a protected range, or anything on a protected sheet, asks first on183+the context line: Enter edits anyway, Esc cancels. The picker lists the184+sheet's protections; its first rows protect the selection or the whole185+sheet, with an optional description, Enter on a protection selects its186+range, and Ctrl+D removes it. Protect range and Remove protection on the187+right-click menu and in the palette do the same for the selection.188+189+What asks: typing into a cell, commands that change cells (clear, paste,190+fill, formats, sort, delete rows), pasting text from the terminal and the191+fill handle. Inserting rows or columns moves a protected range without192+asking, unless the whole sheet is protected. Macros that run don't ask, as193+Sheets' scripts don't. Protected ranges follow inserted and deleted rows194+and columns, save with the sheet, and adding or removing one is an undo195+step.196+
M docs/extending.md +1 -1
@@ -17,7 +17,7 @@ shows it is derived from the table rather than listing it again.
1717 | `functions.FuncDef` (one file per category in `internal/functions`) | a function: name, signature, description, arity, eval, result format, decimal twin | parsing, autocomplete, argument hints, help, [functions.md](functions.md) |1818 | `ui.command` | an action: id, title, description, run, enabled, checked, what it edits, and how macros treat it | key bindings (Sheets and vim), menu bar, context menus, palette, shortcuts help, the `:` command line and its completions, macro recording and `run()` in scripts |1919 | `fileio` formats (`formats.go`) | a format: name, extensions, labels, traits, importer, exporter | `Import`, `Export`, detection, import picker, File > Download, command line |20-| `chart.types`, with `sheet.ChartTypes` | a type: name and order (sheet, saved in files), a layout drawing text and image (chart) | `chart.Draw`, `chart.Image`, chart editor, Insert > Chart |20+| `chart.types`, with `sheet.ChartTypes` | a type: name and order (sheet, saved in files), a layout drawing text and image and the series its legend lists (chart) | `chart.Draw`, `chart.Image`, chart editor, Insert > Chart |2121 | theme roles (`theme.Theme`) | a role: dark and light styles on the 16 ANSI colors, with a contrast minimum for schemes (`minContrast`) | every style in the UI, drawn in the terminal's palette or any color scheme (`FromPalette`); `TestEveryThemeReadable` checks each role under all 349 schemes |2222 | `config.Options` | an option: name, type, default, environment variables, flag, live or not, description, check | parsing and warnings, flags, `012 config` and its default file, [config.md](config.md), Reload config |2323
M docs/files.md +9 -3
@@ -13,13 +13,13 @@ File > Import asks where the data goes, as Sheets' Import location does
1313 | Location | Does |1414 |---|---|1515 | Insert new sheet(s) | Adds the file's sheets after the sheet shown: every sheet of an `.xlsx`, named after the file for other formats. A name already taken gets a number (`Sales 2`) and the file's formulas follow it; named ranges come along unless their name is taken. One undo step, and the spreadsheet stays the file you're editing |16-| Replace current sheet | Puts the data in place of the sheet shown, keeping its name and position, so formulas and named ranges that read it read the new data. Its charts stay: a chart that drew a whole table is re-pointed to the table the file has at the same corner when it has as many columns (rows for a chart by row), so last month's chart draws this month's rows; any other chart keeps its range. The context line says which chart was re-pointed, which kept its range, and which range is empty now. One undo step. Not offered for `.xlsx`, which holds several sheets |16+| Replace current sheet | Puts the data in place of the sheet shown, keeping its name and position, so formulas and named ranges that read it read the new data. Its charts that fit the new data stay: a chart that drew a whole table is re-pointed to the table the file has at the same corner when it has as many columns (rows for a chart by row), so last month's chart draws this month's rows, and one of part of a table, or of whole columns or rows, keeps its range. A chart whose table now has other columns, or whose range is empty now, is removed. The context line lists the removed charts first, then which were re-pointed and which kept their range. One undo step, which brings the removed charts back. Not offered for `.xlsx`, which holds several sheets |1717 | Replace spreadsheet | Opens the file instead, as File > Open and the command line do, asking first when there are unsaved changes |1818 1919 | Format | Import | Download |2020 |---|---|---|2121 | CSV, TSV | Delimiter (`,` `;` tab `\|`), UTF-8 BOM, UTF-16 and Windows-1252 detected; entries become numbers, dates, currency and percentages as if typed; formulas stay text | Values as shown, as Sheets' Download does |22-| Excel `.xlsx` | Every sheet, opening on the one Excel showed: values, formulas (references between sheets too), workbook named ranges, number formats, bold, italic, underline, strikethrough, alignment, column widths, column and row styles, frozen panes, filters (see [Filters in XLSX](#filters-in-xlsx)), shared formulas, dates in the 1904 system; sheets Excel hid stay hidden (unless it's the one Excel showed). Formulas 012 can't read (unknown functions) keep their values; Excel pivot tables come in as the values they showed. Files past the reader's limits (a zip bomb, 1 GB in one part, 2 GB in all, cells past XFD1048576) are refused with a message saying which | Every sheet, the same, with formulas in Excel's syntax and their results cached, column and row formats as column and row styles, frozen rows and columns as frozen panes, and a filter as Excel's with the rows it hides hidden. A sheet whose name Excel can't take as is (spaces at its ends, or the same as another's but for them and case) gets one it can (without the spaces, with a number when two would clash, e.g. `Plan (2)`), and the formulas and named ranges naming it name that. JEV functions and `#AND#` save as values, and so do formulas naming a sheet that doesn't exist (their `#REF!`; Excel would refuse the reference), and pivot tables: Excel gets the results, not a pivot. The download's result counts the formulas saved as values, with an example |22+| Excel `.xlsx` | Every sheet, opening on the one Excel showed: values, formulas (references between sheets too), workbook named ranges, number formats, bold, italic, underline, strikethrough, alignment, column widths, column and row styles, frozen panes, filters (see [Filters in XLSX](#filters-in-xlsx)), notes (Excel's notes, its legacy comments; threaded comments aren't read), shared formulas, dates in the 1904 system; sheets Excel hid stay hidden (unless it's the one Excel showed). Formulas 012 can't read (unknown functions) keep their values; Excel pivot tables come in as the values they showed. A sheet Excel protects comes in unprotected, with a note saying so, since Excel's protection locks cells where 012's only warns. Files past the reader's limits (a zip bomb, 1 GB in one part, 2 GB in all, cells past XFD1048576) are refused with a message saying which | Every sheet, the same, with formulas in Excel's syntax and their results cached, column and row formats as column and row styles, frozen rows and columns as frozen panes, a filter as Excel's with the rows it hides hidden, and notes as Excel's notes (which Sheets reads as notes). Protected ranges aren't written. A sheet whose name Excel can't take as is (spaces at its ends, or the same as another's but for them and case) gets one it can (without the spaces, with a number when two would clash, e.g. `Plan (2)`), and the formulas and named ranges naming it name that. JEV functions and `#AND#` save as values, and so do formulas naming a sheet that doesn't exist (their `#REF!`; Excel would refuse the reference), and pivot tables: Excel gets the results, not a pivot. The download's result counts the formulas saved as values, with an example |2323 | SQLite | Pick a table or view, or type a query; a header row names the columns | The sheet shown or the selection as a table, first row as column names; a table of that name is replaced |2424 | Parquet | Every column, with dates and timestamps; lists joined with commas | |2525 | Lotus 1-2-3 `.wk1`, `.wks` | Numbers, labels with their alignment, formats, column widths, formulas translated (references, operators, `@SUM`, `@AVG`, `@IF`, `@ROUND`, `@PMT` and 60 more) or kept as values | |
@@ -70,7 +70,13 @@ small object. Version 3 adds named ranges, frozen panes and a filter, and is
7070 only written when a sheet uses one of them, so older builds of 012 can open7171 everything else (version 4 adds several sheets and version 5 pivot tables,7272 below). Charts are an optional `charts` field that older builds73-ignore. Saves are atomic: 012 writes a temporary file and renames it. Saving over73+ignore; a chart's options are fields left out at their defaults, which74+builds without them draw with the defaults, while builds that chart but75+lack a chart's type (area, scatter) can't open the file. A cell's `note`76+(the cell is then an object, e.g. `{"input":"1450","note":"Due on the 1st"}`)77+and a sheet's `protected` list of protected ranges78+(`{"range":"B2:C9","description":"Totals"}`, or `{"sheet":true}`) are79+optional too and raise no version. Saves are atomic: 012 writes a temporary file and renames it. Saving over7480 the open file when something else wrote it since it was opened or last7581 saved (another program, or another session of [012 serve](ssh.md)) asks7682 first: Enter overwrites, S saves under another name, Esc cancels. Save
M docs/keys.md +5 -3
@@ -26,7 +26,7 @@ the app uses, so it never drifts from what the keys do.
2626 | View > Freeze | Keep rows or columns on screen while the rest scrolls |2727 | Data > Sort sheet, Sort range | Sort by the active column A to Z or Z to A, or pick columns and order on a bar (Left/Right column, Space order, Alt+A add, Alt+H header row) |2828 | Alt+Down, click ▾ in a header | With a filter (Data > Create a filter): pick the column's values (Space checks, type to search) or a condition |29-| Data > Pivot table | Summarize the table on a new sheet; in the pivot editor Up/Down pick a line, Space adds a field (or opens a filter, flips a total, changes the data range), Left/Right change a field's order or summary, S its "show as", Shift+Up/Down move it, Del removes it, Enter keeps, Esc undoes. Data > Edit pivot table reopens it |29+| Data > Pivot table | Summarize the table on a new sheet; in the pivot editor Up/Down pick a line, Space adds a field (or opens a filter, flips a total, changes the data range), Left/Right change a field's order or summary, S its "show as", R or F2 renames a value, Shift+Up/Down move it, Del removes it, Enter keeps, Esc undoes. Data > Edit pivot table reopens it |3030 | Alt+Shift+F | Frequency table of the active column on a new sheet (Data > Frequency table), as VisiData's Shift+F |3131 | Ctrl+Alt+= / Ctrl+Alt+- | Insert rows above / delete the selected rows (columns when whole columns are selected) |3232 | Ctrl+Enter while typing | Enter the same entry in every selected cell |
@@ -34,6 +34,8 @@ the app uses, so it never drifts from what the keys do.
3434 | Typing a function, range or sheet name | Suggestions drop down: Up/Down pick, Tab or Enter insert (a sheet as `Summary!`, then arrows point into it), Esc hides them; inside a function's parentheses the context line shows its arguments with the current one marked |3535 | Alt+, / Alt+. | Trace precedents / dependents: highlight the cells a formula reads, or the formulas that read the cell, and jump to the first; press again for the next, Esc to go back (Excel's Ctrl+[ and Ctrl+], which terminals send as Esc) |3636 | Data > Named ranges | Name ranges for formulas (`=SUM(Sales)`): Enter goes to one, F2 renames or repoints it, Ctrl+D deletes it; Data > Define named range names the selection |37+| Shift+F2, Insert > Note | Add or edit the active cell's note on the context line (Alt+Enter or Shift+Enter starts a new line); see [data.md](data.md#notes) |38+| Data > Protect sheets and ranges | Protect a range or the sheet so edits to it ask first; Enter goes to a protection, Ctrl+D removes it; see [data.md](data.md#protected-sheets-and-ranges) |3739 | Ctrl+Shift+1 ... 6 | Number, time, date, currency, percent, scientific format |3840 | Ctrl+B, Ctrl+I, Ctrl+U, Alt+Shift+5 | Bold, italic, underline, strikethrough |3941 | Ctrl+Shift+L / E / R | Align left, center, right |
@@ -48,7 +50,7 @@ the app uses, so it never drifts from what the keys do.
4850 | Ctrl+K, Alt+/, Ctrl+Shift+P | Search the menus: find and run any command |4951 | Right-click, Shift+F10 | Cell, column or row menu |5052 | F1 or Ctrl+/ | Keyboard shortcuts |51-| Insert > Chart | Chart the selection (or the table around the active cell); the editor bar picks the type with Left/Right, S switches rows and columns, H and L toggle the header row and labels, R changes the range, T the title |53+| Insert > Chart | Chart the selection (or the table around the active cell); the editor bar picks the type with Left/Right or 1 to 6, S switches rows and columns, H and L toggle the header row and labels, K stacks columns, bars and areas, E adds a scatter's trend line, R changes the range, T the title, and A opens the axis and legend bar (N minimum, X maximum, L log scale, G gridlines, P legend position) |5254 | Click a chart, then Arrows / Shift+arrows / Del | Move, resize or delete it; drag the chart or its corner with the mouse; Enter edits it |5355 | Cmd- or Ctrl-click a URL | Open it: cells holding a URL, and `=HYPERLINK(url, [label])`, are terminal hyperlinks |5456 | Data > Macros | Record what you do as a macro (absolute or relative references), stop and save it with a name and shortcut, run or manage saved macros; see [macros.md](macros.md) |
@@ -83,7 +85,7 @@ are taken.
8385 | `o` `O` | Insert a row below or above and start typing in it |8486 | `x` | Clear the cell (with a count, that many to the right) |8587 | `dd` `yy` | Cut or copy the row (with a count, that many rows); `p` pastes them back |86-| `p` `P` | Paste rows cut or copied with `dd` or `yy` as new rows below or above; other copied cells paste at the active cell |88+| `p` `P` | Paste rows cut or copied with `dd` or `yy` as new rows below or above, with their row formats; other copied cells paste at the active cell |8789 | `u`, Ctrl+R | Undo, redo |8890 | `v` `V` | Select cells or whole rows (VISUAL); motions stretch the selection |8991 | `/`, `n` `N` | Find (Enter stays on the match), next and previous match |
M e2e/charts_test.go +43 -0
M e2e/import_test.go +11 -1
A e2e/notes_test.go +71 -0
@@ -0,0 +1,71 @@
1+package e2e2+3+import (4+ "strings"5+ "testing"6+7+ ghostty "go.mitchellh.com/libghostty"8+)9+10+func TestNotes(t *testing.T) {11+ dir := t.TempDir()12+ s := start(t, dir)13+ s.keys("Rent", "<tab>", "1450", "<enter>", "<up>", "<right>")14+ s.waitForName("B1")15+16+ // Insert > Note (Shift+F2): typed on the context line, Alt+Enter for17+ // a new line.18+ s.keys("<shift+f2>")19+ s.waitFor("Note on B1:")20+ s.keys("Due on the 1st", "<alt+enter>", "autopay", "<enter>")21+ s.waitFor("Note Due on the 1st ↵ autopay")22+ s.eventually("the note's mark", func() bool { return strings.Contains(s.line(gridRow1), "1450▝") })23+24+ // Hovering the cell from elsewhere shows the note in a box.25+ s.keys("<left>")26+ s.mouse(ghostty.MouseActionMotion, ghostty.MouseButtonUnknown, colX(1), gridRow1, 0)27+ s.waitFor("┌─ Note ─")28+ s.waitFor("│ autopay")29+30+ // It's saved with the sheet.31+ s.keys("<ctrl+s>", "notes", "<enter>")32+ s.eventually("saved", func() bool {33+ return strings.Contains(s.line(29), "notes.012") && !strings.Contains(s.line(29), "modified")34+ })35+ s2 := start(t, dir, "notes.012")36+ s2.keys("<right>")37+ s2.waitFor("Note Due on the 1st ↵ autopay")38+}39+40+func TestProtectedRange(t *testing.T) {41+ s := start(t, "")42+ s.keys("Price", "<enter>", "10", "<enter>", "<up>", "<shift+up>")43+ s.waitForName("A1:A2")44+45+ // Data > Protect sheets and ranges: protect the selection.46+ s.keys("<alt+d>")47+ s.waitFor("Protect sheets and ranges")48+ s.keys("<esc>", "<ctrl+k>", "protect range", "<enter>")49+ s.waitFor("Describe A1:A2 (optional):")50+ s.keys("Prices", "<enter>")51+ s.waitFor("A1:A2 is protected: edits to it ask first")52+53+ // Typing into it asks first; Esc backs out, Enter goes on.54+ s.keys("<esc>", "9")55+ s.waitFor("A1:A2 is protected.")56+ s.waitFor("Enter Edit anyway")57+ s.keys("<esc>")58+ s.waitForBar("A2", "10")59+ s.keys("9")60+ s.waitFor("A1:A2 is protected.")61+ s.keys("<enter>", "<enter>")62+ s.waitForBar("A3", "")63+ s.keys("<up>")64+ s.waitForBar("A2", "9")65+66+ // Undo brings back the value, and outside the range nothing asks.67+ s.keys("<ctrl+z>")68+ s.waitForBar("A2", "10")69+ s.keys("<right>", "5", "<enter>")70+ s.waitForBar("B3", "")71+}
M e2e/pivot_test.go +24 -0
A e2e/screencharts_test.go +35 -0
M e2e/screenlist_test.go +22 -1
@@ -433,6 +433,27 @@ var screens = []screen{
433433 s.keys("<enter>", "<down>", "<down>", "<right>", "<right>", "5")434434 s.waitFor("Pivot table results can't be edited")435435 }},436+ {name: "pivot-rename", setup: func(s *session) {437+ pivotEditor(s)438+ s.keys("r")439+ s.waitFor("Value name: AVERAGE of Price")440+ s.keys("Avg price")441+ }},442+ {name: "pivot-subtotals", setup: func(s *session) {443+ // Reorder, then Category, in Columns: a subtotal column after444+ // each Reorder value, and the average renamed.445+ pivotEditor(s)446+ s.keys("r")447+ s.waitFor("Value name: AVERAGE of Price")448+ s.keys("Avg price", "<enter>")449+ s.waitFor(" Avg price")450+ s.keys("<up>", "<up>", "<up>", "<up>", "<up>", "<delete>")451+ s.waitFor("Columns")452+ s.keys("<down>", "a", "categ", "<enter>")453+ s.waitFor(" Category")454+ s.keys("<enter>")455+ s.waitFor("no Total")456+ }},436457 {name: "pivot-filter", setup: func(s *session) {437458 pivotEditor(s)438459 s.keys("<down>", "<space>", "item", "<enter>", "<space>", "bolts")
@@ -456,7 +477,7 @@ var screens = []screen{
456477 // Key screens are also recorded on a light terminal, where the app picks457478 // its light theme from the reported background color.458479 func init() {459- for _, name := range []string{"budget", "point-range", "selection-stats", "menu", "palette-search", "context-menu-column", "functions", "quit-confirm", "help", "resizing-column", "copy-marker", "copy-marker-selected", "formats", "menu-format-number", "find", "replace", "jev", "import-picker", "import-progress", "import-xlsx", "frozen", "filter-picker", "filtered", "sort-bar", "fill-handle", "chart", "chart-editor", "chart-line", "chart-pie", "links-errors", "autocomplete", "signature", "named-ranges", "trace-precedents", "sheets", "sheets-point", "sheets-menu", "sheets-many", "vim-normal", "vim-visual", "vim-command", "pivot-editor", "pivot", "frequency", "far-corner"} {480+ for _, name := range []string{"budget", "point-range", "selection-stats", "menu", "palette-search", "context-menu-column", "functions", "quit-confirm", "help", "resizing-column", "copy-marker", "copy-marker-selected", "formats", "menu-format-number", "find", "replace", "jev", "import-picker", "import-progress", "import-xlsx", "frozen", "filter-picker", "filtered", "sort-bar", "fill-handle", "chart", "chart-editor", "chart-line", "chart-pie", "links-errors", "autocomplete", "signature", "named-ranges", "trace-precedents", "sheets", "sheets-point", "sheets-menu", "sheets-many", "vim-normal", "vim-visual", "vim-command", "pivot-editor", "pivot", "pivot-rename", "pivot-subtotals", "frequency", "far-corner"} {460481 for _, sc := range screens {461482 if sc.name == name {462483 sc.name += "-light"
A e2e/screennotes_test.go +73 -0
@@ -0,0 +1,73 @@
1+package e2e2+3+import ghostty "go.mitchellh.com/libghostty"4+5+// Golden screens for notes and protected ranges: a note on the active6+// cell and in the box shown on hover, the note being typed, the warning7+// before an edit to a protected range, and the list of protections.8+9+// notedBudget is the budget with a two-line note on Rent's amount.10+func notedBudget(s *session) {11+ budget(s)12+ s.keys("<up>", "<up>", "<up>", "<up>", "<shift+f2>")13+ s.waitFor("Note on B3:")14+ s.keys("Due on the 1st", "<alt+enter>", "Paid by autopay from checking", "<enter>")15+ s.waitFor("Note Due on the 1st")16+}17+18+// protectedBudget is the budget with its amounts protected.19+func protectedBudget(s *session) {20+ budget(s)21+ s.keys("<up>", "<up>", "<up>", "<up>", "<shift+down>", "<shift+down>", "<ctrl+k>", "protect range", "<enter>")22+ s.waitFor("Describe B3:B5 (optional):")23+ s.keys("Monthly amounts", "<enter>")24+ s.waitFor("B3:B5 is protected")25+}26+27+var noteScreens = []screen{28+ {name: "note", setup: notedBudget},29+ {name: "note-typing", setup: func(s *session) {30+ notedBudget(s)31+ s.keys("<shift+f2>")32+ s.waitFor("Note on B3:")33+ }},34+ {name: "note-hover", setup: func(s *session) {35+ notedBudget(s)36+ s.keys("<down>", "<down>")37+ s.mouse(ghostty.MouseActionMotion, ghostty.MouseButtonUnknown, 6+10+4, gridRow1+2, 0)38+ s.waitFor("┌─ Note ─")39+ }},40+ {name: "note-hover-narrow", opts: options{cols: 60, rows: 16}, setup: func(s *session) {41+ notedBudget(s)42+ s.keys("<left>")43+ s.mouse(ghostty.MouseActionMotion, ghostty.MouseButtonUnknown, 6+10+4, gridRow1+2, 0)44+ s.waitFor("┌─ Note ─")45+ }},46+ {name: "protect-warning", setup: func(s *session) {47+ protectedBudget(s)48+ s.keys("<esc>", "<down>", "100")49+ s.waitFor("Edit anyway")50+ }},51+ {name: "protect-warning-narrow", opts: options{cols: 60, rows: 16}, setup: func(s *session) {52+ protectedBudget(s)53+ s.keys("<esc>", "<down>", "100")54+ s.waitFor("Edit anyway")55+ }},56+ {name: "protect-picker", setup: func(s *session) {57+ protectedBudget(s)58+ s.keys("<esc>", "<ctrl+k>", "protect sheets", "<enter>")59+ s.waitFor("+ Protect the sheet")60+ }},61+}62+63+func init() {64+ light := map[string]bool{"note": true, "note-hover": true, "protect-warning": true}65+ for _, sc := range noteScreens {66+ screens = append(screens, sc)67+ if light[sc.name] {68+ sc.name += "-light"69+ sc.opts.light = true70+ screens = append(screens, sc)71+ }72+ }73+}
M e2e/screensetup_test.go +12 -0
A e2e/testdata/screens/chart-area-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇▇▇██████████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">████████████████████████████████</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-4)">▁▁▂</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-4)">▅▅▅▅▆▆▆▆▆▆▇▇▇▇▆▆▆▆▆▆▆▆▅▅▆▆▇▇▇</span><span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-area.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇▇▇██████████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-6)">▁▁▂</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-6)">▅▅▅▅▆▆▆▆▆▆▇▇▇▇▆▆▆▆▆▆▆▆▅▅▆▆▇▇▇</span><span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+ <span style="color:var(--vt-palette-0)">Min</span> <span style="color:var(--vt-palette-0)">auto</span> <span style="color:var(--vt-palette-0)">Max</span> <span style="color:var(--vt-palette-0)">auto</span> <span style="color:var(--vt-palette-0)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">right</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$750</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▁▁▁</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼─────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">N</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">min</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">X</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">max</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">L</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">log</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">grid</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">P</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">legend</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes-narrow.html +15 -0
@@ -0,0 +1,15 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+ <span style="color:var(--vt-palette-8)">Min</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Max</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">bottom</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+ <span style="color:var(--vt-palette-8)">Min</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Max</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">right</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$750</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▁▁▁</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼─────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">N</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">min</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">X</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">max</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">L</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">log</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">grid</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">P</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">legend</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-bar-percent.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▊</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▎</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▉</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▉</span><span style="color:var(--vt-palette-5)">███████████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">└──────┬──────┬──────┬──────┬──────┬</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">0%</span> <span style="color:var(--vt-palette-8)">20%</span> <span style="color:var(--vt-palette-8)">40%</span> <span style="color:var(--vt-palette-8)">60%</span> <span style="color:var(--vt-palette-8)">80%</span> <span style="color:var(--vt-palette-8)">100%</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-bar.html +9 -9
@@ -3,17 +3,17 @@
33 44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▍</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████▍</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span><span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▍</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span><span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████▍</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">████████████████▊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">└───────────┬───────────┬───────────┬</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-editor-light.html +4 -4
@@ -1,17 +1,17 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">Bar</span> <span style="color:var(--vt-palette-0)">Line</span> <span style="color:var(--vt-palette-0)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-0)">Not</span> <span style="color:var(--vt-palette-0)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
M e2e/testdata/screens/chart-editor.html +4 -4
@@ -1,17 +1,17 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">Bar</span> <span style="color:var(--vt-palette-8)">Line</span> <span style="color:var(--vt-palette-8)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-8)">Not</span> <span style="color:var(--vt-palette-8)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
M e2e/testdata/screens/chart-light.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-line-light.html +5 -5
@@ -4,15 +4,15 @@
44 <span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,600</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,600</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠔⠒⠒⠒⠒⠒⠊⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,400</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,400</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,200</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,200</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$800</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$800</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">⣀⣀⣀⠤⠤⠤⠤⠤⠤⠒⠒⠒⠒⠒⠢⠤⠤⠤⠤⠤⢄⣀⣀⣀⡠⠤⠤⠔⠒⠒⠊⠉⠉⠁</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$600</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-line.html +5 -5
@@ -4,15 +4,15 @@
44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,600</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,600</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠔⠒⠒⠒⠒⠒⠊⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,400</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,400</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,200</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,200</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$800</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$800</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">⣀⣀⣀⠤⠤⠤⠤⠤⠤⠒⠒⠒⠒⠒⠢⠤⠤⠤⠤⠤⢄⣀⣀⣀⡠⠤⠤⠔⠒⠒⠊⠉⠉⠁</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$600</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-narrow.html +4 -5
@@ -1,16 +1,15 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">Bar</span> <span style="color:var(--vt-palette-8)">Line</span> <span style="color:var(--vt-palette-8)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-8)">Not</span> <span style="color:var(--vt-palette-8)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1111 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>16- <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">type</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-scatter-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⣀</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$700</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⠉</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$680</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$660</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⣀⣀⠤⠤⠒</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⢠⡄</span> <span style="color:var(--vt-palette-4)">⢀⣀⡠⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$640</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⣀⣀⠤⠴⠒⠊⠉⠁</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⣀⣀⠤⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$620</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">⠤⠒⠒⠉⠉</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⠶</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⢠⡄</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$600</span> <span style="color:var(--vt-palette-8)">┼──────────┬──────────┬──────────┬</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,440</span> <span style="color:var(--vt-palette-0)">$1,460</span> <span style="color:var(--vt-palette-0)">$1,480</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-scatter.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⣀</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$700</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⠉</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$680</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$660</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⣀⣀⠤⠤⠒</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⢠⡄</span> <span style="color:var(--vt-palette-6)">⢀⣀⡠⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$640</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⣀⣀⠤⠴⠒⠊⠉⠁</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⣀⣀⠤⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$620</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">⠤⠒⠒⠉⠉</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⠶</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⢠⡄</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$600</span> <span style="color:var(--vt-palette-8)">┼──────────┬──────────┬──────────┬</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,440</span> <span style="color:var(--vt-palette-8)">$1,460</span> <span style="color:var(--vt-palette-8)">$1,480</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-selected.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
A e2e/testdata/screens/chart-stacked-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$2,500</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">▂▂▂▂▂▂</span> <span style="color:var(--vt-palette-5)">▃▃▃▃▃▃</span> <span style="color:var(--vt-palette-5)">▄▄▄▄▄▄</span> <span style="color:var(--vt-palette-5)">▆▆▆▆▆▆</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-stacked.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$2,500</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">▂▂▂▂▂▂</span> <span style="color:var(--vt-palette-5)">▃▃▃▃▃▃</span> <span style="color:var(--vt-palette-5)">▄▄▄▄▄▄</span> <span style="color:var(--vt-palette-5)">▆▆▆▆▆▆</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-wide.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/context-menu.html +5 -5
@@ -21,10 +21,10 @@
2121 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete column <span style="color:var(--vt-palette-8)">│</span>2222 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────┤</span>2323 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Clear <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────┘</span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────┤</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert note <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete notes <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Protect range <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────┘</span>2929 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>3030 Cut the selection, to move it where you paste <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help-light.html +1 -1
@@ -25,6 +25,6 @@
2525 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>2626 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+A</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>2727 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Space</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">1-25</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">88</span> <span style="color:var(--vt-palette-8)">─┘</span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">1-25</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">89</span> <span style="color:var(--vt-palette-8)">─┘</span>2929 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>3030 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Up/Down</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">scroll</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help-narrow.html +1 -1
@@ -11,6 +11,6 @@
1111 <span style="color:var(--vt-palette-8)">│</span> Last used cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+End</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="color:var(--vt-palette-8)">│</span> Go to <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+G</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="color:var(--vt-palette-8)">└────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-11</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">88</span> <span style="color:var(--vt-palette-8)">─┘</span>14+<span style="color:var(--vt-palette-8)">└────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-11</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">89</span> <span style="color:var(--vt-palette-8)">─┘</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help-wide.html +19 -19
@@ -22,24 +22,24 @@
2222 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend the selection <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>2323 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Extend to the edge of the data <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Insert</span> <span style="color:var(--vt-palette-8)">│</span>2424 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select by dragging or Shift+click <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Mouse</span><span style="background:var(--vt-palette-8)"> </span> Insert rows or columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Format</span> <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> Number <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select rows <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Space</span><span style="background:var(--vt-palette-8)"> </span> Percent <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Scientific <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>30-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">26</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Entering</span> <span style="font-weight:bold">data</span> Currency <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>31-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">27</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace the cell; = starts a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Type</span><span style="background:var(--vt-palette-8)"> </span> Date <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>32-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">28</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Accept and move down, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> Time <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>33-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">29</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Pick cells for a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> Bold <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+B</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>34-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">30</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Cancel the entry <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Italic <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+I</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>35-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">31</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Edit cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Underline <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+U</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>36-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">32</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Strikethrough <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>37-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">33</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Macros</span> Left <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+L</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>38-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">34</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Run the macro with that shortcut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+Shift+0-9</span><span style="background:var(--vt-palette-8)"> </span> Center <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+E</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>39-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">35</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Stop a macro while it runs <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>40-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">36</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Clear formatting <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+\</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>41-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">37</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Menus</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">search</span> <span style="color:var(--vt-palette-8)">│</span>42-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">38</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Open a menu by its underlined letter <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+letter</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Data</span> <span style="color:var(--vt-palette-8)">│</span>43-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">39</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-40</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">44</span> <span style="color:var(--vt-palette-8)">─┘</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Note <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Format</span> <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select rows <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Shift+Space</span><span style="background:var(--vt-palette-8)"> </span> Number <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+1</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Percent <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>30+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">26</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Entering</span> <span style="font-weight:bold">data</span> Scientific <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>31+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">27</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Replace the cell; = starts a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Type</span><span style="background:var(--vt-palette-8)"> </span> Currency <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+4</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>32+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">28</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Accept and move down, right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Tab</span><span style="background:var(--vt-palette-8)"> </span> Date <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+3</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>33+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">29</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Pick cells for a formula <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> Time <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>34+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">30</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Cancel the entry <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Bold <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+B</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>35+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">31</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Edit cell <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F2</span><span style="background:var(--vt-palette-8)"> </span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Italic <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+I</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>36+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">32</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Underline <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+U</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>37+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">33</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Macros</span> Strikethrough <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+Shift+5</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>38+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">34</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Run the macro with that shortcut <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+Shift+0-9</span><span style="background:var(--vt-palette-8)"> </span> Left <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+L</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>39+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">35</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Stop a macro while it runs <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Center <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+E</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>40+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">36</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Right <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Shift+R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>41+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">37</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">Menus</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">search</span> Clear formatting <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+\</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>42+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">38</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Open a menu by its underlined letter <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+letter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>43+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">39</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-40</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">45</span> <span style="color:var(--vt-palette-8)">─┘</span>4444 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">40</span><span style="background:var(--vt-palette-8)"> </span>4545 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/help.html +1 -1
@@ -25,6 +25,6 @@
2525 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Deselect <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>2626 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select all <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+A</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>2727 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Select columns <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Space</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-25</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">88</span> <span style="color:var(--vt-palette-8)">─┘</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">1-25</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">89</span> <span style="color:var(--vt-palette-8)">─┘</span>2929 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>3030 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">scroll</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/macro-menu-light.html +14 -14
@@ -13,18 +13,18 @@
1313 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Named ranges <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Define named range <span style="color:var(--vt-palette-8)">│</span>16-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace precedents <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+,</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>18-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace dependents <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+.</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>19-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────┌──────────────────────────────────────────┐</span>20-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>21-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>22-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>23-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>24-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>25-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>26-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>29-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Protect sheets and ranges <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace precedents <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+,</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace dependents <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Alt+.</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────┌──────────────────────────────────────────┐</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>3030 Record what you do as a macro that selects the same ce… <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Arrows</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">move</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">choose</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/macro-menu.html +14 -14
@@ -13,18 +13,18 @@
1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Named ranges <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Define named range <span style="color:var(--vt-palette-8)">│</span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace precedents <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+,</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>18-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace dependents <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+.</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>19-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────┌──────────────────────────────────────────┐</span>20-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>21-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>22-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>23-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>24-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>25-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>26-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>27-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>28-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>29-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Protect sheets and ranges <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├────────────────────────────────────────────────┤</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace precedents <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+,</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Trace dependents <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Alt+.</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────┌──────────────────────────────────────────┐</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Macros</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Record</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">macro</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├─────────────────────│</span> Record macro with relative references <span style="color:var(--vt-palette-8)">│</span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Stop</span> <span style="color:var(--vt-palette-8)">and</span> <span style="color:var(--vt-palette-8)">save</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└─────────────────────│</span> <span style="color:var(--vt-palette-8)">Discard</span> <span style="color:var(--vt-palette-8)">recording</span> <span style="color:var(--vt-palette-8)">│</span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">├──────────────────────────────────────────┤</span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Run</span> <span style="color:var(--vt-palette-8)">macro</span> <span style="color:var(--vt-palette-8)">│</span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Manage macros <span style="color:var(--vt-palette-8)">│</span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Write a macro <span style="color:var(--vt-palette-8)">│</span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────────┘</span>3030 Record what you do as a macro that selects the same ce… <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Arrows</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">choose</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
A e2e/testdata/screens/note-hover-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B5</span><span style="background:var(--vt-palette-7)"> </span> 963+4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Household budget 20266+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Rent 1450<span style="color:var(--vt-palette-5)">▝</span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Note</span> <span style="color:var(--vt-palette-8)">─────────────────────────┐</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">│</span> Due on the 1st <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">5</span><span style="background:var(--vt-palette-6)"> </span> Transit <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">96</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> Paid by autopay from checking <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span> <span style="color:var(--vt-palette-8)">└────────────────────────────────┘</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> Total 2158.412+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/note-hover-narrow.html +16 -0
@@ -0,0 +1,16 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A3</span><span style="background:var(--vt-palette-8)"> </span> Rent3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Rent</span><span style="background:var(--vt-palette-6)"> </span> 1450<span style="color:var(--vt-palette-3)">▝</span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Note</span> <span style="color:var(--vt-palette-8)">─────────────────────────┐</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">│</span> Due on the 1st <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 96 <span style="color:var(--vt-palette-8)">│</span> Paid by autopay from checking <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span> <span style="color:var(--vt-palette-8)">└────────────────────────────────┘</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span></pre>\ No newline at end of file
A e2e/testdata/screens/note-hover.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B5</span><span style="background:var(--vt-palette-8)"> </span> 963+4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 1450<span style="color:var(--vt-palette-3)">▝</span><span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Note</span> <span style="color:var(--vt-palette-8)">─────────────────────────┐</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">│</span> Due on the 1st <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">5</span><span style="background:var(--vt-palette-6)"> </span> Transit <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">96</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span> Paid by autopay from checking <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span> <span style="color:var(--vt-palette-8)">└────────────────────────────────┘</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/note-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B3</span><span style="background:var(--vt-palette-7)"> </span> 14503+<span style="font-weight:bold">Note</span> Due on the 1st <span style="color:var(--vt-palette-0)">↵</span> Paid by autopay from checking4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Household budget 20266+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span>7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> Rent <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">1450▝</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Groceries 612.49+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit 9610+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> Total 2158.412+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/note-typing.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">NOTE</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B3</span><span style="background:var(--vt-palette-8)"> </span> 14503+Note on B3: Due on the 1st↵Paid by autopay from checking4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> Rent <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">1450▝</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.49+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9610+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/note.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B3</span><span style="background:var(--vt-palette-8)"> </span> 14503+<span style="font-weight:bold">Note</span> Due on the 1st <span style="color:var(--vt-palette-8)">↵</span> Paid by autopay from checking4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> Rent <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">1450▝</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.49+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9610+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/palette-narrow.html +1 -1
@@ -12,5 +12,5 @@
1212 <span style="color:var(--vt-palette-8)">│</span> Sort <span style="color:var(--vt-palette-3);font-weight:bold">s</span>heet Z to <span style="color:var(--vt-palette-3);font-weight:bold">A</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Ask</span> <span style="color:var(--vt-palette-8)">JEV</span> <span style="color:var(--vt-palette-8)">again</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-3);font-weight:bold">S</span>ort r<span style="color:var(--vt-palette-3);font-weight:bold">a</span>nge <span style="color:var(--vt-palette-8)">│</span>15-<span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">30</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">126</span> <span style="color:var(--vt-palette-8)">─┘</span>15+<span style="color:var(--vt-palette-8)">└──────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">31</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">132</span> <span style="color:var(--vt-palette-8)">─┘</span>1616 Save the sheet u… <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Up/Down</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">move</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">run</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
M e2e/testdata/screens/palette-search-light.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-0)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Edit</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-4);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-0)">Insert</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">20</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">126</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">20</span> <span style="color:var(--vt-palette-0)">of</span> <span style="color:var(--vt-palette-0)">132</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>1919 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>2020 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>
M e2e/testdata/screens/palette-search.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns right <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Delete rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Edit</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+-</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Insert rows or <span style="color:var(--vt-palette-3);font-weight:bold">col</span>umns <span style="color:var(--vt-palette-8)">Insert</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+Alt+=</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">20</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">126</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">20</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">132</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M e2e/testdata/screens/palette-wide.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> In<span style="color:var(--vt-palette-3);font-weight:bold">se</span>rt rows be<span style="color:var(--vt-palette-3);font-weight:bold">l</span>ow <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Re<span style="color:var(--vt-palette-3);font-weight:bold">se</span>t co<span style="color:var(--vt-palette-3);font-weight:bold">l</span>umn width <span style="color:var(--vt-palette-8)">Format</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> In<span style="color:var(--vt-palette-3);font-weight:bold">se</span>rt co<span style="color:var(--vt-palette-3);font-weight:bold">l</span>umns right <span style="color:var(--vt-palette-8)">Insert</span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">22</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">126</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">22</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">132</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M e2e/testdata/screens/palette.html +1 -1
@@ -14,7 +14,7 @@
1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Decimal arithmetic <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Settings</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Vim keys <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Settings</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Theme <span style="color:var(--vt-palette-8)">File</span> <span style="color:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">Settings</span> <span style="color:var(--vt-palette-8)">│</span>17-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">126</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">126</span> <span style="color:var(--vt-palette-8)">─┘</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└───────────────────────────────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">132</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">132</span> <span style="color:var(--vt-palette-8)">─┘</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>2020 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>
M e2e/testdata/screens/pivot-editor-light.html +1 -1
@@ -27,4 +27,4 @@
2727 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>2828 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>2929 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30-Average the numbers <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">←/→</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">summarize</span> <span style="color:var(--vt-palette-0)">by</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">S</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">show</span> <span style="color:var(--vt-palette-0)">as</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Del</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">remove</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">done</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">cancel</span></pre>\ No newline at end of file30+Average the numbers <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">←/→</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">summary</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">S</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">show</span> <span style="color:var(--vt-palette-0)">as</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">R</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">name</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Del</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">remove</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">done</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">cancel</span></pre>\ No newline at end of file
M e2e/testdata/screens/pivot-editor-narrow.html +1 -1
@@ -13,4 +13,4 @@
1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> Qty <span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">‹</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">SUM</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">›</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Price</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">‹</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">AVERAGE</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">›</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└────────────────────────────</span> <span style="color:var(--vt-palette-8)">results</span> <span style="color:var(--vt-palette-8)">A1:G5</span> <span style="color:var(--vt-palette-8)">─┘</span>16- <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">summarize</span> <span style="color:var(--vt-palette-8)">by</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file16+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">summary</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">show</span> <span style="color:var(--vt-palette-8)">as</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file
M e2e/testdata/screens/pivot-editor.html +1 -1
@@ -27,4 +27,4 @@
2727 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>2828 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>2929 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30-Average the numbers <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">summarize</span> <span style="color:var(--vt-palette-8)">by</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">show</span> <span style="color:var(--vt-palette-8)">as</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">remove</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file30+Average the numbers <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">summary</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">S</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">show</span> <span style="color:var(--vt-palette-8)">as</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">R</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">name</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Del</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">remove</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file
A e2e/testdata/screens/pivot-rename-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Reorder3+Value name: Avg price <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">apply</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">cancel</span>4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Reorder</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">yes</span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">Category</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">AVERAGE</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">AVERAGE</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Fasteners 796 8.87941176470588 434 7.5375 12308+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Hardware 489 14.055 251 11.04 7409+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span> <span style="font-weight:bold">1285</span> <span style="font-weight:bold">10.7962962962963</span> <span style="font-weight:bold">685</span> <span style="font-weight:bold">8.88461538461539</span> <span style="font-weight:bold">1970</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+ Sheet1 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Pivot</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Table</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/pivot-rename.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">EDIT</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Reorder3+Value name: Avg price <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">apply</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Reorder</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">yes</span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Category</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">AVERAGE</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">AVERAGE</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Fasteners 796 8.87941176470588 434 7.5375 12308+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Hardware 489 14.055 251 11.04 7409+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span> <span style="font-weight:bold">1285</span> <span style="font-weight:bold">10.7962962962963</span> <span style="font-weight:bold">685</span> <span style="font-weight:bold">8.88461538461539</span> <span style="font-weight:bold">1970</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+ Sheet1 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Pivot</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Table</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/pivot-subtotals-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Reorder3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Reorder</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">Total</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">Category</span> <span style="font-weight:bold">Fasteners</span> <span style="font-weight:bold">Hardware</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span> <span style="font-weight:bold">796</span> <span style="font-weight:bold">8.879411765</span> <span style="font-weight:bold">489</span> <span style="font-weight:bold">14.055</span> <span style="font-weight:bold">1285</span> <span style="font-weight:bold">10.7962963</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+ Sheet1 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Pivot</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Table</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/pivot-subtotals.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Reorder3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Reorder</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">no</span> <span style="font-weight:bold">Total</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Category</span> <span style="font-weight:bold">Fasteners</span> <span style="font-weight:bold">Hardware</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span> <span style="font-weight:bold">SUM</span> <span style="font-weight:bold">of</span> <span style="font-weight:bold">Qty</span> <span style="font-weight:bold">Avg</span> <span style="font-weight:bold">price</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> <span style="font-weight:bold">Grand</span> <span style="font-weight:bold">Total</span> <span style="font-weight:bold">796</span> <span style="font-weight:bold">8.879411765</span> <span style="font-weight:bold">489</span> <span style="font-weight:bold">14.055</span> <span style="font-weight:bold">1285</span> <span style="font-weight:bold">10.7962963</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+ Sheet1 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Pivot</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Table</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/protect-picker.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B3</span><span style="background:var(--vt-palette-8)"> </span> 1450 <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Protected</span> <span style="font-weight:bold">sheets</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">ranges</span> <span style="color:var(--vt-palette-8)">──────────┐</span>3+ <span style="color:var(--vt-palette-8)">│</span> <span style="font-weight:bold">›</span> <span style="color:var(--vt-palette-8)">Type</span> <span style="color:var(--vt-palette-8)">to</span> <span style="color:var(--vt-palette-8)">search</span> <span style="color:var(--vt-palette-8)">│</span>4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-8)">├────────────────────────────────────────┤</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 2026 <span style="color:var(--vt-palette-8)">│</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">+</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Protect</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">a</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">range</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">B3</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> + Protect the sheet <span style="color:var(--vt-palette-8)">Sheet1</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">3</span><span style="background:var(--vt-palette-6)"> </span> Rent <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">1450</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">│</span> Monthly amounts <span style="color:var(--vt-palette-8)">B3:B5</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Groceries 612.4 <span style="color:var(--vt-palette-8)">└───────────────────────────────</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">of</span> <span style="color:var(--vt-palette-8)">3</span> <span style="color:var(--vt-palette-8)">─┘</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9610+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+Warn before edits to B3 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">add</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">close</span></pre>\ No newline at end of file
A e2e/testdata/screens/protect-warning-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B4</span><span style="background:var(--vt-palette-7)"> </span> 612.43+<span style="color:var(--vt-palette-3)">B3:B5</span> <span style="color:var(--vt-palette-3)">is</span> <span style="color:var(--vt-palette-3)">protected.</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Enter</span><span style="background:var(--vt-palette-7)"> </span> Edit anyway <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> Cancel4+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A</span><span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">1</span><span style="background:var(--vt-palette-7)"> </span> Household budget 20266+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Rent 14508+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span> Groceries<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">612.4</span><span style="background:var(--vt-palette-6)"> </span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Transit 9610+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> Total 2158.412+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+Monthly amounts: shouldn&#39;t be changed by accident</pre>\ No newline at end of file
A e2e/testdata/screens/protect-warning-narrow.html +16 -0
@@ -0,0 +1,16 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B4</span><span style="background:var(--vt-palette-8)"> </span> 612.43+<span style="color:var(--vt-palette-3)">B3:B5</span> <span style="color:var(--vt-palette-3)">is</span> <span style="color:var(--vt-palette-3)">protected.</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Edit anyway <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Cancel4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 14508+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span> Groceries<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">612.4</span><span style="background:var(--vt-palette-6)"> </span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9610+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+Monthly amounts: shouldn&#39;t be changed by accident</pre>\ No newline at end of file
A e2e/testdata/screens/protect-warning.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">MENU</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B4</span><span style="background:var(--vt-palette-8)"> </span> 612.43+<span style="color:var(--vt-palette-3)">B3:B5</span> <span style="color:var(--vt-palette-3)">is</span> <span style="color:var(--vt-palette-3)">protected.</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> Edit anyway <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> Cancel4+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A</span><span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">B</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">1</span><span style="background:var(--vt-palette-8)"> </span> Household budget 20266+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Rent 14508+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">4</span><span style="background:var(--vt-palette-6)"> </span> Groceries<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">612.4</span><span style="background:var(--vt-palette-6)"> </span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Transit 9610+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> Savings r <span style="color:var(--vt-palette-1);text-decoration:underline wavy var(--vt-palette-1)">#DIV/0!</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> Total 2158.412+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+Monthly amounts: shouldn&#39;t be changed by accident</pre>\ No newline at end of file
A internal/chart/area.go +161 -0
@@ -0,0 +1,161 @@
1+package chart2+3+import (4+ "math"5+ "slices"6+)7+8+// Area charts are line charts filled down to the axis. Each column of9+// the plot (each pixel column of the image) samples the series between10+// the two categories around it, so drawing costs the plot's width times11+// the series, whatever the data.12+13+// pointX is category i's point in cells from the plot's left edge: the14+// center of its braille dot, so fills meet the lines.15+func (p *columnPlan) pointX(i int) float64 { return (float64(p.dotX(i)) + 0.5) / 2 }16+17+// areaCursor finds the categories around points along the plot, left to18+// right.19+type areaCursor struct {20+ p *columnPlan21+ k int22+}23+24+// at returns category i and how far x (cells into the plot) lies from its25+// point toward the next one's, or false outside the first and last points.26+func (c *areaCursor) at(x float64) (int, float64, bool) {27+ p := c.p28+ if p.shown == 1 {29+ return 0, 0, math.Abs(x-p.pointX(0)) <= 0.530+ }31+ if x < p.pointX(0) || x > p.pointX(p.shown-1) {32+ return 0, 0, false33+ }34+ for c.k < p.shown-2 && p.pointX(c.k+1) < x {35+ c.k++36+ }37+ x0, x1 := p.pointX(c.k), p.pointX(c.k+1)38+ if x1 <= x0 {39+ return c.k, 0, true40+ }41+ return c.k, (x - x0) / (x1 - x0), true42+}43+44+// valueAt is series j between categories i and i+1, t of the way, or NaN45+// where either is missing.46+func (p *columnPlan) valueAt(j, i int, t float64) float64 {47+ s := p.d.Series[j]48+ a := value(s, i)49+ if t == 0 || i+1 >= p.shown {50+ return a51+ }52+ b := value(s, i+1)53+ return a + (b-a)*t54+}55+56+// areaSpans returns the area's spans at one point: stacked, the series'57+// piles; otherwise each series from the axis to its value, the smaller58+// in front, so each shows the part no smaller one covers.59+func (p *columnPlan) areaSpans(i int, t float64, vals []float64, out []span) []span {60+ vals = vals[:0]61+ for j := range p.d.Series {62+ vals = append(vals, p.valueAt(j, i, t))63+ }64+ if p.stacked {65+ return pileUp(len(vals), func(j int) float64 { return vals[j] }, out)66+ }67+ return overlap(vals, p.sc.base(), out)68+}69+70+// overlap turns values drawn from base, each in front of the larger ones,71+// into the visible spans: from base up through the positive values in72+// order, and down through the negative ones.73+func overlap(vals []float64, base float64, out []span) []span {74+ out = out[:0]75+ order := make([]int, 0, len(vals))76+ for j, v := range vals {77+ if !math.IsNaN(v) {78+ order = append(order, j)79+ }80+ }81+ slices.SortStableFunc(order, func(a, b int) int { return cmpDistance(vals[a]-base, vals[b]-base) })82+ up, down := base, base83+ for _, j := range order {84+ switch v := vals[j]; {85+ case v > up:86+ out = append(out, span{up, v, j})87+ up = v88+ case v < down:89+ out = append(out, span{v, down, j})90+ down = v91+ }92+ }93+ return out94+}95+96+// cmpDistance orders offsets by their distance from zero.97+func cmpDistance(a, b float64) int {98+ switch a, b = math.Abs(a), math.Abs(b); {99+ case a < b:100+ return -1101+ case a > b:102+ return 1103+ }104+ return 0105+}106+107+// drawArea fills each column of the plot under the series with eighths.108+func (p *columnPlan) drawArea(g *Grid) {109+ cur := areaCursor{p: p}110+ vals := make([]float64, 0, len(p.d.Series))111+ var spans, cells []span112+ for x := range p.plot.Dx() {113+ i, t, ok := cur.at(float64(x) + 0.5)114+ if !ok {115+ continue116+ }117+ spans = p.areaSpans(i, t, vals, spans)118+ cells = inCells(p.sc, spans, cells)119+ from, to := pileExtent(cells)120+ for r := from; r <= to; r++ {121+ if c, ok := pileCell(cells, r, lowerEighths, "▀"); ok {122+ if at := g.At(p.plot.Min.X+x, p.axisRow-1-r); at != nil {123+ *at = c124+ }125+ }126+ }127+ }128+}129+130+// areaImage fills the area a pixel column at a time: stacked piles131+// opaque, otherwise each series translucent over the ones before, then132+// strokes the lines on top.133+func (p *columnPlan) areaImage(cv *canvas, y func(float64) float64) {134+ cur := areaCursor{p: p}135+ vals := make([]float64, 0, len(p.d.Series))136+ var spans []span137+ for px := range cv.Rect.Dx() {138+ i, t, ok := cur.at((float64(px) + 0.5) / cv.cw)139+ if !ok {140+ continue141+ }142+ if p.stacked {143+ spans = p.areaSpans(i, t, vals, spans)144+ for _, s := range spans {145+ cv.rect(float64(px), y(s.hi), float64(px+1), y(s.lo), cv.pal.Series[s.j%Colors])146+ }147+ continue148+ }149+ base := y(p.sc.base())150+ for j := range p.d.Series {151+ if v := p.valueAt(j, i, t); !math.IsNaN(v) {152+ c := cv.pal.Series[j%Colors]153+ c.A = 150154+ cv.rect(float64(px), min(y(v), base), float64(px+1), max(y(v), base), c)155+ }156+ }157+ }158+ if !p.stacked {159+ p.lineImage(cv, y)160+ }161+}
A internal/chart/axis.go +150 -0
@@ -0,0 +1,150 @@
1+package chart2+3+import (4+ "math"5+6+ "github.com/FelineStateMachine/012/internal/sheet"7+)8+9+// newAxis fits dlo..dhi onto a value axis of length cells as newScale10+// does, with the chart's axis options: a fixed minimum or maximum, and a11+// log scale.12+func newAxis(dlo, dhi float64, length, minK, maxN int, f sheet.Format, o sheet.ChartOptions) scale {13+ if o.HasMin {14+ dlo = o.Min15+ }16+ if o.HasMax {17+ dhi = o.Max18+ }19+ if o.HasMin && !o.HasMax && dhi <= dlo {20+ dhi = dlo + max(math.Abs(dlo), 1)21+ }22+ if o.HasMax && !o.HasMin && dlo >= dhi {23+ dlo = dhi - max(math.Abs(dhi), 1)24+ }25+ if dlo > dhi {26+ dlo, dhi = dhi, dlo27+ }28+ switch {29+ case o.Log:30+ return newLogScale(dlo, dhi, length, minK, f)31+ case (o.HasMin || o.HasMax) && dlo < dhi:32+ return newFixedScale(dlo, dhi, o.HasMin, o.HasMax, length, minK, maxN, f)33+ }34+ return newScale(dlo, dhi, length, minK, maxN, f)35+}36+37+// newFixedScale is newScale keeping the ends that are fixed where they38+// are: the other end rounds out to a whole step, and with both fixed the39+// step divides the range, a round one where it can.40+func newFixedScale(dlo, dhi float64, fixLo, fixHi bool, length, minK, maxN int, f sheet.Format) scale {41+ best := scale{lo: dlo, hi: dhi, step: dhi - dlo, n: 1, k: max(length, 1)}42+ fallback := false43+ for target := min(maxN, max(length/minK, 1)); target >= 1; target-- {44+ s, ok := fixedTicks(dlo, dhi, fixLo, fixHi, target)45+ if !ok {46+ continue47+ }48+ s.k = length / s.n49+ if s.k < minK && (target > 1 || s.k < 1) {50+ continue51+ }52+ if fixLo && fixHi && niceStep(s.step) != s.step {53+ if !fallback { // the finest division, if no round step fits54+ best, fallback = s, true55+ }56+ continue57+ }58+ best = s59+ break60+ }61+ best.label = tickFormat(best, f)62+ return best63+}64+65+// fixedTicks divides dlo..dhi into about target steps, moving only the66+// ends that aren't fixed.67+func fixedTicks(dlo, dhi float64, fixLo, fixHi bool, target int) (scale, bool) {68+ if fixLo && fixHi {69+ return scale{lo: dlo, hi: dhi, step: (dhi - dlo) / float64(target), n: target}, true70+ }71+ step := niceStep((dhi - dlo) / float64(target))72+ n := int(math.Ceil((dhi-dlo)/step - 1e-9))73+ if n < 1 {74+ return scale{}, false75+ }76+ if fixLo {77+ return scale{lo: dlo, hi: dlo + float64(n)*step, step: step, n: n}, true78+ }79+ return scale{lo: dhi - float64(n)*step, hi: dhi, step: step, n: n}, true80+}81+82+// newLogScale is a log axis over positive dlo..dhi: its ends round out to83+// powers of ten, and ticks are one decade apart, or more when decades84+// would be closer than minK cells.85+func newLogScale(dlo, dhi float64, length, minK int, f sheet.Format) scale {86+ if dhi <= 0 {87+ dlo, dhi = 1, 1088+ }89+ if dlo <= 0 {90+ dlo = dhi / 1091+ }92+ lo, hi := math.Floor(math.Log10(dlo)+1e-9), math.Ceil(math.Log10(dhi)-1e-9)93+ if hi <= lo {94+ hi = lo + 195+ }96+ decades := int(hi - lo)97+ s := scale{lo: lo, log: true}98+ for _, step := range []int{1, 2, 3, 5, 10, 20, 50, 100} {99+ n := (decades + step - 1) / step100+ s.step, s.n, s.k = float64(step), n, length/n101+ if s.k >= minK || n == 1 {102+ break103+ }104+ }105+ s.k = max(s.k, 1)106+ s.hi = lo + float64(s.n)*s.step107+ s.label = func(v float64) string {108+ return tickFormat(scale{lo: v, hi: v, step: v}, f)(v)109+ }110+ return s111+}112+113+// base is where bars start: zero, or the end of the axis nearest it.114+func (s scale) base() float64 {115+ if s.log {116+ return s.tick(0)117+ }118+ return min(max(0, s.lo), s.hi)119+}120+121+// at is pos kept on the axis, for values beyond a fixed end.122+func (s scale) at(v float64) float64 {123+ p := s.pos(v)124+ if math.IsNaN(p) {125+ return 0126+ }127+ return min(max(p, 0), float64(s.cells()))128+}129+130+// crossesZero reports whether zero lies inside the axis, where images131+// draw a line.132+func (s scale) crossesZero() bool { return !s.log && s.lo < 0 && s.hi > 0 }133+134+// positive is d with every value that isn't positive left out, as a log135+// scale can't show them.136+func positive(d sheet.ChartData) sheet.ChartData {137+ out := d138+ out.Series = make([]sheet.ChartSeries, len(d.Series))139+ for j, s := range d.Series {140+ vals := make([]float64, len(s.Values))141+ for i, v := range s.Values {142+ if v <= 0 {143+ v = math.NaN()144+ }145+ vals[i] = v146+ }147+ out.Series[j] = sheet.ChartSeries{Name: s.Name, Values: vals}148+ }149+ return out150+}
M internal/chart/bar.go +66 -61
@@ -10,7 +10,8 @@ import (
1010 )1111 1212 // barPlan lays out horizontal bar charts: category labels on the left,13-// bars growing right, and the value axis along the bottom.13+// bars growing right, and the value axis along the bottom. Draw adds the14+// legend.1415 //1516 // Jan │██████████▌1617 // Feb │███████▎
@@ -18,32 +19,36 @@ import (
1819 // 0 1,000 2,0001920 type barPlan struct {2021 d sheet.ChartData22+ stacked bool23+ grid bool2124 msg string2225 sc scale2326 catW int // width of the category labels2427 axisX int2528 axisRow int2629 tickRow int27- legend int2830 plot image.Rectangle2931 shown int32+ slots int // bars per category: the series, or one stack3033 rows int // rows per category, including a gap row3134 }3235 33-func newBarPlan(d sheet.ChartData, w, h int) *barPlan {34- p := &barPlan{d: d, legend: -1}36+func newBarPlan(d sheet.ChartData, w, h int, o sheet.ChartOptions) *barPlan {37+ d = prepare(d, o, true)38+ p := &barPlan{d: d, stacked: o.Stack != sheet.StackNone, grid: !o.NoGrid, slots: len(d.Series)}3539 lo, hi, ok := finite(d.Series)40+ if p.stacked {41+ lo, hi, ok = stackedRange(d, len(d.Categories))42+ p.slots = 143+ }3644 if !ok || len(d.Categories) == 0 {3745 p.msg = "No numbers to chart"3846 return p3947 }40- lo, hi = min(lo, 0), max(hi, 0)41- p.tickRow = h - 142- ns := len(d.Series)43- if ns > 1 && h >= 7 {44- p.legend = h - 145- p.tickRow = h - 248+ if !o.Log {49+ lo, hi = min(lo, 0), max(hi, 0)4650 }51+ p.tickRow = h - 14752 p.axisRow = p.tickRow - 14853 if p.axisRow < 1 {4954 p.msg = "Too small to chart"
@@ -64,56 +69,69 @@ func newBarPlan(d sheet.ChartData, w, h int) *barPlan {
6469 p.msg = "Too small to chart"6570 return p6671 }67- p.sc = newScale(lo, hi, plotW, labelW+2, 6, d.Format)72+ p.sc = newAxis(lo, hi, plotW, labelW+2, 6, d.Format, o)6873 for j := 0; j <= p.sc.n; j++ {6974 labelW = max(labelW, ansi.StringWidth(p.sc.label(p.sc.tick(j))))7075 }7176 }7277 p.plot = image.Rect(x0, 0, x0+p.sc.cells(), p.axisRow)73- p.rows = ns + 178+ p.rows = p.slots + 17479 if len(d.Categories)*p.rows-1 > p.axisRow {75- p.rows = ns80+ p.rows = p.slots7681 }77- p.shown = max(min(len(d.Categories), (p.axisRow+p.rows-ns)/p.rows), 1)82+ p.shown = max(min(len(d.Categories), (p.axisRow+p.rows-p.slots)/p.rows), 1)7883 return p7984 }8085 81-// barRow returns the row of series j's bar in category i.82-func (p *barPlan) barRow(i, j int) int { return i*p.rows + j }86+// barRow returns the row of series j's bar in category i; stacked, all87+// series share one.88+func (p *barPlan) barRow(i, j int) int {89+ if p.stacked {90+ j = 091+ }92+ return i*p.rows + j93+}94+95+// piles returns category i's bars as spans, as columnPlan.piles does.96+func (p *barPlan) piles(i int, buf []span) []span {97+ if p.stacked {98+ return stack(p.d, i, buf)99+ }100+ buf = buf[:0]101+ base := p.sc.base()102+ for j, s := range p.d.Series {103+ if v := value(s, i); !math.IsNaN(v) {104+ buf = append(buf, span{min(v, base), max(v, base), j})105+ }106+ }107+ return buf108+}83109 84110 func (p *barPlan) draw(g *Grid, o Options) {85111 p.drawAxis(g)86- ns := len(p.d.Series)87112 for i := 0; i < p.shown; i++ {88113 label := ansi.Truncate(p.d.Categories[i], p.catW, "…")89- g.text(p.catW-ansi.StringWidth(label), p.barRow(i, (ns-1)/2), label, Label)90- }91- if p.legend >= 0 {92- g.legend(p.legend, seriesNames(p.d))114+ g.text(p.catW-ansi.StringWidth(label), p.barRow(i, (p.slots-1)/2), label, Label)93115 }94116 if o.Image {95117 return96118 }97- zero := p.sc.pos(0)98- for j, s := range p.d.Series {99- for i := 0; i < p.shown && i < len(s.Values); i++ {100- v := s.Values[i]101- if math.IsNaN(v) {102- continue103- }104- lo, hi := min(zero, p.sc.pos(v)), max(zero, p.sc.pos(v))105- for c := int(math.Floor(lo)); float64(c) < hi; c++ {106- // Left of the axis, bars fill from the right.107- if glyph := barGlyph(lo, hi, c, v >= 0, leftEighths, "▐"); glyph != "" {108- g.set(p.plot.Min.X+c, p.barRow(i, j), glyph, SeriesRole(j))109- }119+ if p.grid {120+ for j := 1; j <= p.sc.n; j++ {121+ for y := 0; y < p.axisRow; y++ {122+ g.set(p.plot.Min.X+j*p.sc.k-1, y, "┊", Gridline)110123 }111124 }112125 }126+ drawPiles(p.sc, p.shown, p.stacked, p.piles, leftEighths, "▐", func(i, j, c int, cell Cell) {127+ if at := g.At(p.plot.Min.X+c, p.barRow(i, j)); at != nil {128+ *at = cell129+ }130+ })113131 }114132 115133 // drawAxis draws the category axis on the left and the value axis along116-// the bottom, labeling as many ticks as fit without touching.134+// the bottom.117135 func (p *barPlan) drawAxis(g *Grid) {118136 for y := 0; y < p.axisRow; y++ {119137 g.set(p.axisX, y, "│", Axis)
@@ -122,39 +140,26 @@ func (p *barPlan) drawAxis(g *Grid) {
122140 for x := p.axisX + 1; x < p.plot.Max.X; x++ {123141 g.set(x, p.axisRow, "─", Axis)124142 }143+ drawXTicks(g, p.sc, p.axisX, p.plot.Min.X, p.axisRow, p.tickRow)144+}145+146+// drawXTicks marks the ticks of a horizontal axis on row axisRow, the147+// first at column axisX and the rest ending intervals from x0, and labels148+// as many on row tickRow as fit without touching.149+func drawXTicks(g *Grid, sc scale, axisX, x0, axisRow, tickRow int) {125150 next := 0 // first free column126- for j := 0; j <= p.sc.n; j++ {127- x := p.axisX151+ for j := 0; j <= sc.n; j++ {152+ x := axisX128153 if j > 0 {129- x = p.plot.Min.X + j*p.sc.k - 1130- g.set(x, p.axisRow, "┬", Axis)154+ x = x0 + j*sc.k - 1155+ g.set(x, axisRow, "┬", Axis)131156 }132- label := p.sc.label(p.sc.tick(j))157+ label := sc.label(sc.tick(j))133158 lw := ansi.StringWidth(label)134159 lx := min(max(x-(lw-1)/2, 0), g.W-lw)135160 if lx >= next {136- g.text(lx, p.tickRow, label, Label)161+ g.text(lx, tickRow, label, Label)137162 next = lx + lw + 1138163 }139164 }140165 }141-142-// barGlyph is the glyph of cell c of a bar from lo to hi, distances143-// along its axis in cells, or "" for none. A positive bar grows away144-// from the axis in eighths; a negative one has no eighths growing the145-// other way, so its partly covered cells are a full block or the half146-// block half.147-func barGlyph(lo, hi float64, c int, positive bool, eighths []string, half string) string {148- cover := min(hi, float64(c+1)) - max(lo, float64(c))149- switch {150- case positive:151- if g := eighths[int(math.Round(cover*8))]; g != " " {152- return g153- }154- case cover >= 0.75:155- return "█"156- case cover >= 0.25:157- return half158- }159- return ""160-}
M internal/chart/chart.go +26 -45
@@ -21,11 +21,12 @@ import (
2121 type Role uint82222 2323 const (24- None Role = iota // background25- Axis // axis lines and tick marks26- Label // tick values, category labels and legend text27- Muted // messages such as "No numbers to chart"28- Series // Series+i is the color of series (or slice) i24+ None Role = iota // background25+ Axis // axis lines and tick marks26+ Label // tick values, category labels and legend text27+ Muted // messages such as "No numbers to chart"28+ Gridline // gridlines29+ Series // Series+i is the color of series (or slice) i2930 )3031 3132 // Colors is how many series colors there are; later series reuse them.
@@ -57,6 +58,9 @@ type Options struct {
5758 // CellW and CellH are the size of a terminal cell in pixels; they set5859 // a pie's proportions. Zero means the usual 1:2.5960 CellW, CellH int61+ // Chart is the chart's own settings: stacking, the value axis, the62+ // gridlines and the legend.63+ Chart sheet.ChartOptions6064 }6165 6266 func (o Options) aspect() float64 {
@@ -128,13 +132,14 @@ func Draw(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) *Grid {
128132 if w < 8 || h < 3 {129133 return g130134 }131- p := planFor(t, d, w, h, o)135+ p, lg := planFor(t, d, w, h, o)132136 if msg := p.note(); msg != "" {133137 g.message(msg)134138 return g135139 }136140 g.Plot = p.plotArea()137141 p.draw(g, o)142+ lg.draw(g)138143 return g139144 }140145
@@ -159,10 +164,12 @@ func finite(series []sheet.ChartSeries) (lo, hi float64, ok bool) {
159164 }160165 161166 // scale is a value axis: lo..hi divided into n intervals of step, each k162-// cells long, so every tick lands on a cell boundary.167+// cells long, so every tick lands on a cell boundary. On a log scale lo,168+// hi and step are powers of ten (their logarithms), see axis.go.163169 type scale struct {164170 lo, hi, step float64165171 n, k int172+ log bool166173 label func(float64) string167174 }168175
@@ -171,11 +178,22 @@ func (s scale) cells() int { return s.n * s.k }
171178 172179 // pos maps a value to a distance along the axis in cells.173180 func (s scale) pos(v float64) float64 {181+ if s.log {182+ if v <= 0 {183+ return math.Inf(-1)184+ }185+ v = math.Log10(v)186+ }174187 return (v - s.lo) / (s.hi - s.lo) * float64(s.cells())175188 }176189 177190 // tick returns the value of tick j.178-func (s scale) tick(j int) float64 { return s.lo + float64(j)*s.step }191+func (s scale) tick(j int) float64 {192+ if s.log {193+ return math.Pow(10, s.lo+float64(j)*s.step)194+ }195+ return s.lo + float64(j)*s.step196+}179197 180198 // newScale fits dlo..dhi onto length cells, with ticks at least minK181199 // cells apart and at most maxN intervals.
@@ -274,43 +292,6 @@ func groupDigits(s string) string {
274292 return b.String()275293 }276294 277-// legend draws "■ Rent ■ Food" centered on row y, shortening names to278-// fit.279-func (g *Grid) legend(y int, names []string) {280- const gap = " "281- for limit := 24; limit >= 1; limit-- {282- items := make([]string, len(names))283- width := 0284- for i, n := range names {285- items[i] = "■ " + ansi.Truncate(n, limit, "…")286- width += ansi.StringWidth(items[i])287- }288- width += len(gap) * (len(items) - 1)289- if width > g.W && limit > 1 {290- continue291- }292- x := max((g.W-width)/2, 0)293- for i, it := range items {294- if x+ansi.StringWidth(it) > g.W {295- g.text(max(x, 0), y, "…", Label)296- return297- }298- g.set(x, y, "■", SeriesRole(i))299- g.text(x+2, y, it[len("■ "):], Label)300- x += ansi.StringWidth(it) + len(gap)301- }302- return303- }304-}305-306-func seriesNames(d sheet.ChartData) []string {307- names := make([]string, len(d.Series))308- for i, s := range d.Series {309- names[i] = s.Name310- }311- return names312-}313-314295 // eighths are the block elements filling 0/8 to 8/8 of a cell from the315296 // bottom, and from the left.316297 var (
M internal/chart/chart_test.go +62 -16
@@ -43,6 +43,32 @@ var mixed = sheet.ChartData{
4343 Format: sheet.Format{Kind: sheet.FmtPercent},4444 }4545 46+// signs has positive and negative values in the same category.47+var signs = sheet.ChartData{48+ Categories: []string{"Q1", "Q2", "Q3"},49+ Series: []sheet.ChartSeries{50+ {Name: "Sales", Values: []float64{30, 40, 20}},51+ {Name: "Costs", Values: []float64{-20, -10, -30}},52+ {Name: "Other", Values: []float64{10, -5, 15}},53+ },54+}55+56+// xy is a scatter: X in the first series, two series of Y against it.57+var xy = sheet.ChartData{58+ Categories: []string{"1", "2", "3", "4", "5", "6"},59+ Series: []sheet.ChartSeries{60+ {Name: "Hours", Values: []float64{1, 2, 3, 5, 6, 8}},61+ {Name: "Score", Values: []float64{52, 55, 61, 70, 71, 80}},62+ {Name: "Retake", Values: []float64{40, nan, 50, 58, 66, 69}},63+ },64+}65+66+// growth spans several powers of ten, for log scales.67+var growth = sheet.ChartData{68+ Categories: []string{"Y1", "Y2", "Y3", "Y4", "Y5"},69+ Series: []sheet.ChartSeries{{Name: "Users", Values: []float64{3, 40, 700, 9000, 0}}},70+}71+4672 // TestDrawGoldens renders every chart type as text; the goldens in4773 // testdata are the review surface for how charts look.4874 func TestDrawGoldens(t *testing.T) {
@@ -51,22 +77,42 @@ func TestDrawGoldens(t *testing.T) {
5177 t sheet.ChartType5278 d sheet.ChartData5379 w, h int80+ o sheet.ChartOptions5481 }{55- {"column", sheet.ChartColumn, budget, 44, 12},56- {"column-single", sheet.ChartColumn, single, 40, 10},57- {"column-negative", sheet.ChartColumn, mixed, 36, 10},58- {"column-narrow", sheet.ChartColumn, budget, 22, 8},59- {"bar", sheet.ChartBar, budget, 44, 12},60- {"bar-single", sheet.ChartBar, single, 40, 8},61- {"line", sheet.ChartLine, budget, 44, 12},62- {"line-single", sheet.ChartLine, single, 40, 10},63- {"pie", sheet.ChartPie, single, 40, 10},64- {"empty", sheet.ChartColumn, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{nan}}}}, 30, 8},65- {"pie-negative", sheet.ChartPie, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{-1}}}}, 30, 8},82+ {"column", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{}},83+ {"column-single", sheet.ChartColumn, single, 40, 10, sheet.ChartOptions{}},84+ {"column-negative", sheet.ChartColumn, mixed, 36, 10, sheet.ChartOptions{}},85+ {"column-narrow", sheet.ChartColumn, budget, 22, 8, sheet.ChartOptions{}},86+ {"bar", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{}},87+ {"bar-single", sheet.ChartBar, single, 40, 8, sheet.ChartOptions{}},88+ {"line", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{}},89+ {"line-single", sheet.ChartLine, single, 40, 10, sheet.ChartOptions{}},90+ {"pie", sheet.ChartPie, single, 40, 10, sheet.ChartOptions{}},91+ {"empty", sheet.ChartColumn, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{nan}}}}, 30, 8, sheet.ChartOptions{}},92+ {"pie-negative", sheet.ChartPie, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{-1}}}}, 30, 8, sheet.ChartOptions{}},93+ {"pie-no-legend", sheet.ChartPie, single, 40, 10, sheet.ChartOptions{Legend: sheet.LegendNone}},94+ {"area", sheet.ChartArea, budget, 44, 12, sheet.ChartOptions{}},95+ {"area-stacked", sheet.ChartArea, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},96+ {"area-negative", sheet.ChartArea, mixed, 36, 10, sheet.ChartOptions{}},97+ {"column-stacked", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},98+ {"column-percent", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackPercent}},99+ {"column-stacked-negative", sheet.ChartColumn, signs, 40, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},100+ {"bar-stacked", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},101+ {"bar-percent", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackPercent}},102+ {"scatter", sheet.ChartScatter, xy, 44, 12, sheet.ChartOptions{}},103+ {"scatter-trend", sheet.ChartScatter, xy, 44, 12, sheet.ChartOptions{Trend: true}},104+ {"scatter-single", sheet.ChartScatter, single, 40, 10, sheet.ChartOptions{}},105+ {"legend-right", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Legend: sheet.LegendRight}},106+ {"legend-none", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{Legend: sheet.LegendNone}},107+ {"no-gridlines", sheet.ChartColumn, single, 40, 10, sheet.ChartOptions{NoGrid: true}},108+ {"axis-min-max", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Min: 500, HasMin: true, Max: 1600, HasMax: true}},109+ {"axis-min", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{Min: 400, HasMin: true}},110+ {"axis-log", sheet.ChartColumn, growth, 40, 12, sheet.ChartOptions{Log: true}},111+ {"bar-log", sheet.ChartBar, growth, 40, 10, sheet.ChartOptions{Log: true}},66112 }67113 for _, tc := range cases {68114 t.Run(tc.name, func(t *testing.T) {69- got := Draw(tc.t, tc.d, tc.w, tc.h, Options{}).String() + "\n"115+ got := Draw(tc.t, tc.d, tc.w, tc.h, Options{Chart: tc.o}).String() + "\n"70116 path := filepath.Join("testdata", tc.name+".txt")71117 if *update {72118 os.MkdirAll("testdata", 0o755)
@@ -122,7 +168,7 @@ func TestScale(t *testing.T) {
122168 123169 func TestLayout(t *testing.T) {124170 // Columns: every bar lies inside the plot and bars don't overlap.125- p := newColumnPlan(budget, 44, 12, false)171+ p := newColumnPlan(budget, 44, 12, columnBars, sheet.ChartOptions{})126172 prev := 0127173 for i := range 4 {128174 for j := range 2 {
@@ -143,7 +189,7 @@ func TestLayout(t *testing.T) {
143189 many.Categories = append(many.Categories, "c")144190 many.Series[0].Values = append(many.Series[0].Values, float64(i))145191 }146- if p := newColumnPlan(many, 30, 10, false); p.shown != p.plot.Dx() {192+ if p := newColumnPlan(many, 30, 10, columnBars, sheet.ChartOptions{}); p.shown != p.plot.Dx() {147193 t.Errorf("shown %d categories in %d columns", p.shown, p.plot.Dx())148194 }149195 // The plot rectangle is what Image fills.
@@ -160,7 +206,7 @@ func TestLayout(t *testing.T) {
160206 // TestTypes checks every chart type the editor offers has a layout.161207 func TestTypes(t *testing.T) {162208 for _, ty := range sheet.ChartTypes {163- if types[ty] == nil {209+ if types[ty].layout == nil {164210 t.Errorf("%v has no layout", ty)165211 }166212 }
@@ -190,7 +236,7 @@ var testPalette = Palette{
190236 191237 func TestImagePixels(t *testing.T) {192238 o := Options{CellW: 8, CellH: 16}193- p := newColumnPlan(single, 40, 10, false)239+ p := newColumnPlan(single, 40, 10, columnBars, sheet.ChartOptions{})194240 img := Image(sheet.ChartColumn, single, 40, 10, o, testPalette)195241 x0, x1 := p.bar(0, 0)196242 mid := ((x0+x1)/2 - p.plot.Min.X) * 8
M internal/chart/column.go +143 -79
@@ -9,54 +9,65 @@ import (
99 "github.com/FelineStateMachine/012/internal/sheet"1010 )1111 12-// columnPlan lays out column and line charts: a value axis on the left13-// with labeled ticks, the plot, a category axis and labels below it, and a14-// legend when there are several series.12+// columnPlan lays out column, line and area charts: a value axis on the13+// left with labeled ticks, the plot, and a category axis and labels14+// below it. Draw adds the legend.1515 //1616 // 1,500 ┤ █▆1717 // 1,000 ┤ ██ ▆1818 // 500 ┤ ██ █1919 // 0 ┼─────────2020 // Jan Feb21-// ■ Rent ■ Food2221 type columnPlan struct {2322 d sheet.ChartData24- line bool23+ kind columnKind24+ stacked bool25+ grid bool2526 msg string2627 sc scale2728 labelW int // width of the tick labels2829 axisX int // column of the value axis2930 axisRow int // row of the category axis3031 catRow int31- legend int // row of the legend, -1 for none3232 plot image.Rectangle3333 shown int // categories drawn3434 // Columns: each category is a group gw cells wide, starting off cells35- // into the plot; each series' bar in it is bw wide.35+ // into the plot; each series' bar in it (the stack, when stacked) is36+ // bw wide.3637 gw, off, bw, inset int3738 }3839 39-func newColumnPlan(d sheet.ChartData, w, h int, line bool) *columnPlan {40- p := &columnPlan{d: d, line: line, legend: -1}40+// columnKind is what a columnPlan draws at each category.41+type columnKind uint842+43+const (44+ columnBars columnKind = iota45+ columnLine46+ columnArea47+)48+49+func newColumnPlan(d sheet.ChartData, w, h int, kind columnKind, o sheet.ChartOptions) *columnPlan {50+ stacked := o.Stack != sheet.StackNone && kind != columnLine51+ d = prepare(d, o, kind != columnLine)52+ p := &columnPlan{d: d, kind: kind, stacked: stacked, grid: !o.NoGrid}4153 lo, hi, ok := finite(d.Series)54+ if stacked {55+ lo, hi, ok = stackedRange(d, len(d.Categories))56+ }4257 if !ok || len(d.Categories) == 0 {4358 p.msg = "No numbers to chart"4459 return p4560 }46- if !line {61+ if kind != columnLine && !o.Log {4762 lo, hi = min(lo, 0), max(hi, 0)4863 }4964 p.catRow = h - 150- if len(d.Series) > 1 && h >= 7 {51- p.legend = h - 152- p.catRow = h - 253- }5465 p.axisRow = p.catRow - 15566 if p.axisRow < 2 {5667 p.msg = "Too small to chart"5768 return p5869 }59- p.sc = newScale(lo, hi, p.axisRow, 2, 6, d.Format)70+ p.sc = newAxis(lo, hi, p.axisRow, 2, 6, d.Format, o)6071 for j := 0; j <= p.sc.n; j++ {6172 p.labelW = max(p.labelW, ansi.StringWidth(p.sc.label(p.sc.tick(j))))6273 }
@@ -67,29 +78,53 @@ func newColumnPlan(d sheet.ChartData, w, h int, line bool) *columnPlan {
6778 p.msg = "Too small to chart"6879 return p6980 }70- ns := len(d.Series)71- if line {81+ if kind != columnBars {7282 p.shown = min(len(d.Categories), plotW*2)7383 return p7484 }75- p.shown = max(min(len(d.Categories), plotW/ns), 1)85+ slots := len(d.Series)86+ if stacked {87+ slots = 188+ }89+ p.shown = max(min(len(d.Categories), plotW/slots), 1)7690 p.gw = plotW / p.shown7791 gap := 078- if p.gw > ns {92+ if p.gw > slots {7993 gap = max(1, p.gw/4)8094 }81- p.bw = max((p.gw-gap)/ns, 1)82- p.inset = (p.gw - p.bw*ns) / 295+ p.bw = max((p.gw-gap)/slots, 1)96+ p.inset = (p.gw - p.bw*slots) / 28397 p.off = (plotW - p.shown*p.gw) / 28498 return p8599 }86100 87-// bar returns the columns of series j's bar in category i.101+// bar returns the columns of series j's bar in category i; stacked, all102+// series share the first.88103 func (p *columnPlan) bar(i, j int) (x0, x1 int) {104+ if p.stacked {105+ j = 0106+ }89107 x0 = p.plot.Min.X + p.off + i*p.gw + p.inset + j*p.bw90108 return x0, x0 + p.bw91109 }92110 111+// piles returns category i's bars as piles of spans in values, each with112+// the series whose slot it takes: one pile when stacked, one per series113+// otherwise.114+func (p *columnPlan) piles(i int, buf []span) []span {115+ if p.stacked {116+ return stack(p.d, i, buf)117+ }118+ buf = buf[:0]119+ base := p.sc.base()120+ for j, s := range p.d.Series {121+ if v := value(s, i); !math.IsNaN(v) {122+ buf = append(buf, span{min(v, base), max(v, base), j})123+ }124+ }125+ return buf126+}127+93128 // dotX returns the x of category i's point in braille dots (two per cell).94129 func (p *columnPlan) dotX(i int) int {95130 dots := p.plot.Dx() * 2
@@ -101,48 +136,67 @@ func (p *columnPlan) dotX(i int) int {
101136 102137 // catCenter is the column under which category i's label is centered.103138 func (p *columnPlan) catCenter(i int) int {104- if p.line {139+ if p.kind != columnBars {105140 return p.plot.Min.X + p.dotX(i)/2106141 }107142 return p.plot.Min.X + p.off + i*p.gw + p.gw/2108143 }109144 110145 func (p *columnPlan) draw(g *Grid, o Options) {111- // Value axis with a label at each tick, up to the top one.112- for y := p.axisRow - p.sc.cells(); y < p.axisRow; y++ {113- g.set(p.axisX, y, "│", Axis)146+ drawValueAxis(g, p.sc, p.labelW, p.axisX, p.axisRow, p.plot.Min.X, p.plot.Max.X+1)147+ p.drawCategories(g)148+ if o.Image {149+ return114150 }115- for x := p.plot.Min.X; x < g.W; x++ {116- g.set(x, p.axisRow, "─", Axis)151+ if p.grid {152+ hGridlines(g, p.sc, p.axisRow, p.plot)117153 }118- for j := 0; j <= p.sc.n; j++ {119- y := p.axisRow - j*p.sc.k120- label := p.sc.label(p.sc.tick(j))121- g.text(p.labelW-ansi.StringWidth(label), y, label, Label)154+ switch p.kind {155+ case columnLine:156+ p.drawLines(g)157+ case columnArea:158+ p.drawArea(g)159+ default:160+ p.drawBars(g)161+ }162+}163+164+// drawValueAxis draws a vertical value axis at column axisX with a label165+// at each tick, and the category axis along row axisRow from x0 to x1.166+func drawValueAxis(g *Grid, sc scale, labelW, axisX, axisRow, x0, x1 int) {167+ for y := axisRow - sc.cells(); y < axisRow; y++ {168+ g.set(axisX, y, "│", Axis)169+ }170+ for x := x0; x < min(x1, g.W); x++ {171+ g.set(x, axisRow, "─", Axis)172+ }173+ for j := 0; j <= sc.n; j++ {174+ y := axisRow - j*sc.k175+ label := sc.label(sc.tick(j))176+ g.text(labelW-ansi.StringWidth(label), y, label, Label)122177 mark := "┤"123178 if j == 0 {124179 mark = "┼"125180 }126- g.set(p.axisX, y, mark, Axis)127- }128- p.drawCategories(g)129- if p.legend >= 0 {130- g.legend(p.legend, seriesNames(p.d))131- }132- if o.Image {133- return181+ g.set(axisX, y, mark, Axis)134182 }135- if p.line {136- p.drawLines(g)137- } else {138- p.drawBars(g)183+}184+185+// hGridlines draws a dashed line across the plot at each tick above the186+// axis.187+func hGridlines(g *Grid, sc scale, axisRow int, plot image.Rectangle) {188+ for j := 1; j <= sc.n; j++ {189+ y := axisRow - j*sc.k190+ for x := plot.Min.X; x < plot.Max.X; x++ {191+ g.set(x, y, "┈", Gridline)192+ }139193 }140194 }141195 142196 // drawCategories labels as many categories as fit without touching.143197 func (p *columnPlan) drawCategories(g *Grid) {144198 room := p.gw - 1145- if p.line {199+ if p.kind != columnBars {146200 room = p.plot.Dx()/max(p.shown-1, 1) - 1147201 }148202 room = max(room, 3)
@@ -160,48 +214,28 @@ func (p *columnPlan) drawCategories(g *Grid) {
160214 }161215 162216 func (p *columnPlan) drawBars(g *Grid) {163- zero := p.sc.pos(0)164- for j, s := range p.d.Series {165- for i := 0; i < p.shown && i < len(s.Values); i++ {166- v := s.Values[i]167- if math.IsNaN(v) {168- continue169- }170- x0, x1 := p.bar(i, j)171- lo, hi := min(zero, p.sc.pos(v)), max(zero, p.sc.pos(v))172- for r := int(math.Floor(lo)); float64(r) < hi; r++ {173- // Below the axis, bars fill from the top.174- glyph := barGlyph(lo, hi, r, v >= 0, lowerEighths, "▀")175- for x := x0; glyph != "" && x < x1; x++ {176- g.set(x, p.axisRow-1-r, glyph, SeriesRole(j))177- }217+ drawPiles(p.sc, p.shown, p.stacked, p.piles, lowerEighths, "▀", func(i, j, r int, cell Cell) {218+ x0, x1 := p.bar(i, j)219+ for x := x0; x < x1; x++ {220+ if at := g.At(x, p.axisRow-1-r); at != nil {221+ *at = cell178222 }179223 }180- }224+ })181225 }182226 183227 // dotY maps a value to a braille dot row, four per cell, from the top.184228 func (p *columnPlan) dotY(v float64) int {185229 rows := p.axisRow * 4186230 full := p.sc.cells()*4 - 1187- y := rows - 1 - int(math.Round(p.sc.pos(v)/float64(p.sc.cells())*float64(full)))231+ y := rows - 1 - int(math.Round(p.sc.at(v)/float64(p.sc.cells())*float64(full)))188232 return min(max(y, 0), rows-1)189233 }190234 191235 // drawLines draws each series as a braille line; where lines cross a192236 // cell, the later series' color wins.193237 func (p *columnPlan) drawLines(g *Grid) {194- w, h := p.plot.Dx(), p.axisRow195- bits := make([]uint8, w*h)196- owner := make([]int, w*h)197- dot := func(x, y, series int) {198- cx, cy := x/2, y/4199- if cx < 0 || cx >= w || cy < 0 || cy >= h {200- return201- }202- bits[cy*w+cx] |= brailleBit[x%2][y%4]203- owner[cy*w+cx] = series204- }238+ b := newBraille(p.plot.Dx(), p.axisRow)205239 for j, s := range p.d.Series {206240 px, py, have := 0, 0, false207241 for i := 0; i < p.shown && i < len(s.Values); i++ {
@@ -212,17 +246,47 @@ func (p *columnPlan) drawLines(g *Grid) {
212246 }213247 x, y := p.dotX(i), p.dotY(v)214248 if have {215- bresenham(px, py, x, y, func(x, y int) { dot(x, y, j) })249+ b.line(px, py, x, y, j)216250 } else {217- dot(x, y, j)251+ b.dot(x, y, j)218252 }219253 px, py, have = x, y, true220254 }221255 }222- for cy := range h {223- for cx := range w {224- if b := bits[cy*w+cx]; b != 0 {225- g.set(p.plot.Min.X+cx, cy, string(rune(0x2800+int(b))), SeriesRole(owner[cy*w+cx]))256+ b.draw(g, p.plot.Min.X, 0)257+}258+259+// braille is a canvas of braille dots, two by four per cell, each cell260+// colored by the series that drew in it last.261+type braille struct {262+ w, h int263+ bits []uint8264+ owner []int265+}266+267+func newBraille(w, h int) *braille {268+ return &braille{w: w, h: h, bits: make([]uint8, w*h), owner: make([]int, w*h)}269+}270+271+func (b *braille) dot(x, y, series int) {272+ cx, cy := x/2, y/4273+ if x < 0 || y < 0 || cx >= b.w || cy >= b.h {274+ return275+ }276+ b.bits[cy*b.w+cx] |= brailleBit[x%2][y%4]277+ b.owner[cy*b.w+cx] = series278+}279+280+func (b *braille) line(x0, y0, x1, y1, series int) {281+ bresenham(x0, y0, x1, y1, func(x, y int) { b.dot(x, y, series) })282+}283+284+// draw puts the dots on g with the top-left cell at x, y.285+func (b *braille) draw(g *Grid, x, y int) {286+ for cy := range b.h {287+ for cx := range b.w {288+ if bits := b.bits[cy*b.w+cx]; bits != 0 {289+ g.set(x+cx, y+cy, string(rune(0x2800+int(bits))), SeriesRole(b.owner[cy*b.w+cx]))226290 }227291 }228292 }
M internal/chart/image.go +34 -30
@@ -28,7 +28,7 @@ func Image(t sheet.ChartType, d sheet.ChartData, w, h int, o Options, pal Palett
2828 if w < 8 || h < 3 {2929 return nil3030 }31- p := planFor(t, d, w, h, o)31+ p, _ := planFor(t, d, w, h, o)3232 plot := p.plotArea()3333 if p.note() != "" || plot.Empty() {3434 return nil
@@ -131,32 +131,36 @@ func segDist(px, py, ax, ay, bx, by float64) float64 {
131131 return math.Hypot(px-(ax+t*dx), py-(ay+t*dy))132132 }133133 134-// image draws columns or lines. Values map exactly, while gridlines sit135-// on the cell boundaries the text ticks label.134+// image draws columns, lines or areas. Values map exactly, while135+// gridlines sit on the cell boundaries the text ticks label.136136 func (p *columnPlan) image(cv *canvas) {137137 bottom := float64(p.plot.Dy()) * cv.ch138- y := func(v float64) float64 { return bottom - p.sc.pos(v)*cv.ch }139- for j := 1; j <= p.sc.n; j++ {140- cv.hline(int(math.Round(y(p.sc.tick(j)))), cv.pal.Grid)138+ y := func(v float64) float64 { return bottom - p.sc.at(v)*cv.ch }139+ if p.grid {140+ for j := 1; j <= p.sc.n; j++ {141+ cv.hline(int(math.Round(y(p.sc.tick(j)))), cv.pal.Grid)142+ }143+ }144+ if p.kind != columnLine && p.sc.crossesZero() {145+ cv.hline(int(math.Round(y(0))), cv.pal.Grid)141146 }142- if p.line {147+ switch p.kind {148+ case columnLine:143149 p.lineImage(cv, y)144150 return145- }146- if p.sc.lo < 0 {147- cv.hline(int(math.Round(y(0))), cv.pal.Grid)151+ case columnArea:152+ p.areaImage(cv, y)153+ return148154 }149155 inset := min(cv.cw/6, 2)150- for j, s := range p.d.Series {151- for i := 0; i < p.shown && i < len(s.Values); i++ {152- v := s.Values[i]153- if math.IsNaN(v) {154- continue155- }156- x0, x1 := p.bar(i, j)156+ var piles []span157+ for i := 0; i < p.shown; i++ {158+ piles = p.piles(i, piles)159+ for _, s := range piles {160+ x0, x1 := p.bar(i, s.j)157161 px0 := float64(x0-p.plot.Min.X)*cv.cw + inset158162 px1 := float64(x1-p.plot.Min.X)*cv.cw - inset159- cv.rect(px0, min(y(v), y(0)), px1, max(y(v), y(0)), cv.pal.Series[j%Colors])163+ cv.rect(px0, y(s.hi), px1, y(s.lo), cv.pal.Series[s.j%Colors])160164 }161165 }162166 }
@@ -187,22 +191,22 @@ func (p *columnPlan) lineImage(cv *canvas, y func(float64) float64) {
187191 188192 // image draws horizontal bars.189193 func (p *barPlan) image(cv *canvas) {190- x := func(v float64) float64 { return p.sc.pos(v) * cv.cw }191- for j := 1; j <= p.sc.n; j++ {192- cv.vline(int(math.Round(x(p.sc.tick(j))))-1, cv.pal.Grid)194+ x := func(v float64) float64 { return p.sc.at(v) * cv.cw }195+ if p.grid {196+ for j := 1; j <= p.sc.n; j++ {197+ cv.vline(int(math.Round(x(p.sc.tick(j))))-1, cv.pal.Grid)198+ }193199 }194- if p.sc.lo < 0 {200+ if p.sc.crossesZero() {195201 cv.vline(int(math.Round(x(0))), cv.pal.Grid)196202 }197203 inset := min(cv.ch/8, 2)198- for j, s := range p.d.Series {199- for i := 0; i < p.shown && i < len(s.Values); i++ {200- v := s.Values[i]201- if math.IsNaN(v) {202- continue203- }204- row := float64(p.barRow(i, j))205- cv.rect(min(x(v), x(0)), row*cv.ch+inset, max(x(v), x(0)), (row+1)*cv.ch-inset, cv.pal.Series[j%Colors])204+ var piles []span205+ for i := 0; i < p.shown; i++ {206+ piles = p.piles(i, piles)207+ for _, s := range piles {208+ row := float64(p.barRow(i, s.j))209+ cv.rect(x(s.lo), row*cv.ch+inset, x(s.hi), (row+1)*cv.ch-inset, cv.pal.Series[s.j%Colors])206210 }207211 }208212 }
A internal/chart/legend.go +114 -0
@@ -0,0 +1,114 @@
1+package chart2+3+import (4+ "strconv"5+6+ "github.com/charmbracelet/x/ansi"7+8+ "github.com/FelineStateMachine/012/internal/sheet"9+)10+11+// legendBox is where a chart's legend of series goes: a row under the12+// plot, a column right of it, or nowhere. Draw lays the chart out in the13+// room the legend leaves.14+type legendBox struct {15+ names []string16+ pos sheet.ChartLegend17+ row int // the bottom legend's row18+ x, w int // the right legend's first column and width19+}20+21+// Legend sizes.22+const (23+ legendNameW = 16 // longest name in a right legend24+ minRightPlot = 16 // narrowest chart a right legend leaves25+)26+27+// placeLegend makes room for a legend of names in a w by h chart and28+// returns the room left for the chart. A legend needs two series or more,29+// and space: a bottom one seven rows, a right one a chart still30+// minRightPlot wide beside it.31+func placeLegend(names []string, pos sheet.ChartLegend, w, h int) (legendBox, int, int) {32+ lg := legendBox{pos: sheet.LegendNone}33+ if len(names) < 2 {34+ return lg, w, h35+ }36+ switch pos {37+ case sheet.LegendBottom:38+ if h >= 7 {39+ return legendBox{names: names, pos: pos, row: h - 1}, w, h - 140+ }41+ case sheet.LegendRight:42+ nameW := 143+ for _, n := range names {44+ nameW = max(nameW, ansi.StringWidth(n))45+ }46+ lw := 2 + min(nameW, legendNameW)47+ if w-lw-1 >= minRightPlot {48+ return legendBox{names: names, pos: pos, x: w - lw, w: lw}, w - lw - 1, h49+ }50+ }51+ return lg, w, h52+}53+54+func (lg legendBox) draw(g *Grid) {55+ switch lg.pos {56+ case sheet.LegendBottom:57+ g.legend(lg.row, lg.names)58+ case sheet.LegendRight:59+ g.legendColumn(lg.x, lg.w, lg.names)60+ }61+}62+63+// legend draws "■ Rent ■ Food" centered on row y, shortening names to64+// fit.65+func (g *Grid) legend(y int, names []string) {66+ const gap = " "67+ for limit := 24; limit >= 1; limit-- {68+ items := make([]string, len(names))69+ width := 070+ for i, n := range names {71+ items[i] = "■ " + ansi.Truncate(n, limit, "…")72+ width += ansi.StringWidth(items[i])73+ }74+ width += len(gap) * (len(items) - 1)75+ if width > g.W && limit > 1 {76+ continue77+ }78+ x := max((g.W-width)/2, 0)79+ for i, it := range items {80+ if x+ansi.StringWidth(it) > g.W {81+ g.text(max(x, 0), y, "…", Label)82+ return83+ }84+ g.set(x, y, "■", SeriesRole(i))85+ g.text(x+2, y, it[len("■ "):], Label)86+ x += ansi.StringWidth(it) + len(gap)87+ }88+ return89+ }90+}91+92+// legendColumn draws the legend one series a row from column x, w wide,93+// centered down the chart, ending in "… 3 more" when they don't all fit.94+func (g *Grid) legendColumn(x, w int, names []string) {95+ n := min(len(names), g.H)96+ y := (g.H - n) / 297+ for i, name := range names {98+ if y == g.H-1 && i < len(names)-1 {99+ g.text(x, y, ansi.Truncate("… "+strconv.Itoa(len(names)-i)+" more", w, "…"), Label)100+ return101+ }102+ g.set(x, y, "■", SeriesRole(i))103+ g.text(x+2, y, ansi.Truncate(name, w-2, "…"), Label)104+ y++105+ }106+}107+108+func seriesNames(series []sheet.ChartSeries) []string {109+ names := make([]string, len(series))110+ for i, s := range series {111+ names[i] = s.Name112+ }113+ return names114+}
M internal/chart/pie.go +14 -5
@@ -12,7 +12,8 @@ import (
1212 )1313 1414 // piePlan lays out a pie of the first series, clockwise from twelve15-// o'clock as in Sheets, with a legend of labels and shares to its right.15+// o'clock as in Sheets, with a legend of labels and shares to its right16+// unless the legend is off: a pie's legend is always on the right.1617 // As text, the disc is half blocks: two square pixels per cell, each1718 // colored by its slice.1819 type piePlan struct {
@@ -21,7 +22,9 @@ type piePlan struct {
2122 slices []slice2223 disc image.Rectangle // in cells2324 legend image.Point // top-left of the legend24- nameW int25+ // noLegend leaves the legend out, for Legend: none.26+ noLegend bool27+ nameW int2528 }2629 2730 type slice struct {
@@ -59,15 +62,18 @@ func newPiePlan(d sheet.ChartData, w, h int, o Options) *piePlan {
5962 }6063 // "■ " name " " share: shares are at most "100%".6164 p.nameW = max(min(p.nameW, w/2-8), 3)62- legendW := 2 + p.nameW + 2 + 465+ legendW, gap := 2+p.nameW+2+4, 366+ if o.Chart.Legend == sheet.LegendNone {67+ legendW, gap, p.noLegend = 0, 0, true68+ }6369 a := o.aspect()64- rows := min(h, int(float64(w-legendW-3)/a))70+ rows := min(h, int(float64(w-legendW-gap)/a))6571 if rows < 2 {6672 p.msg = "Too small to chart"6773 return p6874 }6975 cols := int(math.Round(float64(rows) * a))70- x0 := max((w-(cols+3+legendW))/2, 0)76+ x0 := max((w-(cols+gap+legendW))/2, 0)7177 y0 := (h - rows) / 27278 p.disc = image.Rect(x0, y0, x0+cols, y0+rows)7379 n := min(len(p.slices), h)
@@ -124,6 +130,9 @@ func (p *piePlan) draw(g *Grid, o Options) {
124130 }125131 126132 func (p *piePlan) drawLegend(g *Grid) {133+ if p.noLegend {134+ return135+ }127136 x, y := p.legend.X, p.legend.Y128137 for i, s := range p.slices {129138 if y >= g.H {
A internal/chart/scatter.go +239 -0
@@ -0,0 +1,239 @@
1+package chart2+3+import (4+ "image"5+ "image/color"6+ "math"7+ "strconv"8+9+ "github.com/charmbracelet/x/ansi"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// scatterPlan lays out a scatter chart as Sheets does: the first series15+// is the X values and every other one a series of Y values plotted16+// against them, each point a dot, with an optional linear trend line per17+// series. The value axis on the left is Y, with the chart's axis options;18+// X runs along the bottom.19+//20+// 60 ┤ ⠛ ⠛⠛21+// 40 ┤ ⠛⠛ ⠛22+// 20 ┤⠛23+// 0 ┼──┬──┬──┬24+// 0 2 4 625+type scatterPlan struct {26+ xs []float6427+ ys []sheet.ChartSeries28+ trend bool29+ grid bool30+ msg string31+ xsc scale32+ ysc scale33+ labelW int34+ axisX int35+ axisRow int36+ tickRow int37+ plot image.Rectangle38+}39+40+// scatterSeries splits d into X values and Y series: the first series is41+// X when there are two or more, and otherwise the categories are, read as42+// numbers or else counted from 1.43+func scatterSeries(d sheet.ChartData) ([]float64, []sheet.ChartSeries) {44+ if len(d.Series) >= 2 {45+ return d.Series[0].Values, d.Series[1:]46+ }47+ xs := make([]float64, len(d.Categories))48+ for i, c := range d.Categories {49+ x, err := strconv.ParseFloat(c, 64)50+ if err != nil {51+ x = float64(i + 1)52+ }53+ xs[i] = x54+ }55+ return xs, d.Series56+}57+58+func newScatterPlan(d sheet.ChartData, w, h int, o sheet.ChartOptions) *scatterPlan {59+ d = prepare(d, o, false)60+ xs, ys := scatterSeries(d)61+ p := &scatterPlan{xs: xs, ys: ys, trend: o.Trend, grid: !o.NoGrid}62+ xlo, xhi, okX := finite([]sheet.ChartSeries{{Values: xs}})63+ ylo, yhi, okY := finite(ys)64+ if !okX || !okY {65+ p.msg = "No numbers to chart"66+ return p67+ }68+ p.tickRow = h - 169+ p.axisRow = h - 270+ if p.axisRow < 2 {71+ p.msg = "Too small to chart"72+ return p73+ }74+ p.ysc = newAxis(ylo, yhi, p.axisRow, 2, 6, d.Format, o)75+ for j := 0; j <= p.ysc.n; j++ {76+ p.labelW = max(p.labelW, ansi.StringWidth(p.ysc.label(p.ysc.tick(j))))77+ }78+ p.axisX = p.labelW + 179+ x0 := p.axisX + 180+ labelW := 181+ for range 2 {82+ plotW := w - x0 - (labelW+1)/283+ if plotW < 2 {84+ p.msg = "Too small to chart"85+ return p86+ }87+ p.xsc = newScale(xlo, xhi, plotW, labelW+2, 6, d.Format)88+ for j := 0; j <= p.xsc.n; j++ {89+ labelW = max(labelW, ansi.StringWidth(p.xsc.label(p.xsc.tick(j))))90+ }91+ }92+ p.plot = image.Rect(x0, 0, x0+p.xsc.cells(), p.axisRow)93+ return p94+}95+96+// points calls fn with each point of every series, in cells from the97+// plot's bottom left.98+func (p *scatterPlan) points(fn func(j int, x, y float64)) {99+ for j, s := range p.ys {100+ for i, x := range p.xs {101+ y := value(s, i)102+ if math.IsNaN(y) || math.IsNaN(x) || math.IsInf(x, 0) {103+ continue104+ }105+ fn(j, p.xsc.at(x), p.ysc.at(y))106+ }107+ }108+}109+110+// fit returns the least squares line y = a + b*x through series j's111+// points, and false with fewer than two distinct X values.112+func (p *scatterPlan) fit(j int) (a, b float64, ok bool) {113+ var n, sx, sy, sxx, sxy float64114+ for i, x := range p.xs {115+ y := value(p.ys[j], i)116+ if math.IsNaN(y) || math.IsNaN(x) || math.IsInf(x, 0) {117+ continue118+ }119+ n++120+ sx, sy, sxx, sxy = sx+x, sy+y, sxx+x*x, sxy+x*y121+ }122+ d := n*sxx - sx*sx123+ if n < 2 || math.Abs(d) < 1e-12*max(1, sxx*n) {124+ return 0, 0, false125+ }126+ b = (n*sxy - sx*sy) / d127+ return (sy - b*sx) / n, b, true128+}129+130+// trendPoints calls fn with points along series j's trend line, in cells131+// from the plot's bottom left, steps per cell across the plot; ok is132+// false where the line leaves the value axis.133+func (p *scatterPlan) trendPoints(j int, steps float64, fn func(x, y float64, ok bool)) {134+ a, b, ok := p.fit(j)135+ if !ok {136+ return137+ }138+ cells := float64(p.xsc.cells())139+ n := int(cells * steps)140+ for k := 0; k <= n; k++ {141+ x := cells * float64(k) / float64(max(n, 1))142+ v := a + b*(p.xsc.lo+x/cells*(p.xsc.hi-p.xsc.lo))143+ y := p.ysc.pos(v)144+ fn(x, y, !math.IsNaN(y) && y >= 0 && y <= float64(p.ysc.cells()))145+ }146+}147+148+func (p *scatterPlan) draw(g *Grid, o Options) {149+ drawValueAxis(g, p.ysc, p.labelW, p.axisX, p.axisRow, p.plot.Min.X, p.plot.Max.X)150+ drawXTicks(g, p.xsc, p.axisX, p.plot.Min.X, p.axisRow, p.tickRow)151+ if o.Image {152+ return153+ }154+ if p.grid {155+ hGridlines(g, p.ysc, p.axisRow, p.plot)156+ }157+ w, h := p.plot.Dx(), p.axisRow158+ b := newBraille(w, h)159+ // Dots: two per cell across, four down, from the top left.160+ dot := func(x, y float64) (int, int) {161+ dx := int(math.Round(x / float64(max(p.xsc.cells(), 1)) * float64(w*2-1)))162+ dy := h*4 - 1 - int(math.Round(y/float64(max(p.ysc.cells(), 1))*float64(p.ysc.cells()*4-1)))163+ return min(max(dx, 0), w*2-1), min(max(dy, 0), h*4-1)164+ }165+ if p.trend {166+ for j := range p.ys {167+ px, py, have := 0, 0, false168+ p.trendPoints(j, 2, func(x, y float64, ok bool) {169+ if !ok {170+ have = false171+ return172+ }173+ dx, dy := dot(x, y)174+ if have {175+ b.line(px, py, dx, dy, j)176+ }177+ px, py, have = dx, dy, true178+ })179+ }180+ }181+ p.points(func(j int, x, y float64) {182+ dx, dy := dot(x, y)183+ // A point is two dots by two, kept inside the plot.184+ dx, dy = min(dx, w*2-2), max(dy-1, 0)185+ for k := range 4 {186+ b.dot(dx+k%2, dy+k/2, j)187+ }188+ })189+ b.draw(g, p.plot.Min.X, 0)190+}191+192+// image draws the points as discs and the trend lines through them.193+func (p *scatterPlan) image(cv *canvas) {194+ bottom := float64(p.plot.Dy()) * cv.ch195+ px := func(x float64) float64 { return x * cv.cw }196+ py := func(y float64) float64 { return bottom - y*cv.ch }197+ if p.grid {198+ for j := 1; j <= p.ysc.n; j++ {199+ cv.hline(int(math.Round(py(float64(j*p.ysc.k)))), cv.pal.Grid)200+ }201+ for j := 1; j <= p.xsc.n; j++ {202+ cv.vline(int(math.Round(px(float64(j*p.xsc.k))))-1, cv.pal.Grid)203+ }204+ }205+ if p.trend {206+ width := max(cv.ch/12, 1.5)207+ for j := range p.ys {208+ var run [][2]float64209+ flush := func() {210+ if len(run) > 1 {211+ cv.polyline(run, width, cv.pal.Series[j%Colors])212+ }213+ run = run[:0]214+ }215+ p.trendPoints(j, 1, func(x, y float64, ok bool) {216+ if !ok {217+ flush()218+ return219+ }220+ run = append(run, [2]float64{px(x), py(y)})221+ })222+ flush()223+ }224+ }225+ r := max(min(cv.cw, cv.ch)*0.3, 2.5)226+ p.points(func(j int, x, y float64) {227+ cv.disc(px(x), py(y), r, cv.pal.Series[j%Colors])228+ })229+}230+231+// disc fills a circle of radius r centered at cx, cy, antialiased.232+func (cv *canvas) disc(cx, cy, r float64, c color.RGBA) {233+ for y := int(math.Floor(cy - r - 1)); y <= int(math.Ceil(cy+r+1)); y++ {234+ for x := int(math.Floor(cx - r - 1)); x <= int(math.Ceil(cx+r+1)); x++ {235+ d := math.Hypot(float64(x)+0.5-cx, float64(y)+0.5-cy)236+ cv.blend(x, y, c, r+0.5-d)237+ }238+ }239+}
A internal/chart/stack.go +197 -0
@@ -0,0 +1,197 @@
1+package chart2+3+import (4+ "math"5+6+ "github.com/FelineStateMachine/012/internal/sheet"7+)8+9+// A span is a piece of a bar, or of an area at one point, from lo to hi10+// along the value axis, in values, colored as series j.11+type span struct {12+ lo, hi float6413+ j int14+}15+16+// prepare readies d for a chart's options: values a log scale can't show17+// left out, and with 100% stacking each value as its share of its18+// category.19+func prepare(d sheet.ChartData, o sheet.ChartOptions, stackable bool) sheet.ChartData {20+ if o.Log {21+ d = positive(d)22+ }23+ if stackable && o.Stack == sheet.StackPercent {24+ d = shares(d)25+ }26+ return d27+}28+29+// shares is d with each value divided by the sum of its category's sizes,30+// formatted as percentages.31+func shares(d sheet.ChartData) sheet.ChartData {32+ out := sheet.ChartData{Categories: d.Categories, Format: sheet.Format{Kind: sheet.FmtPercent}}33+ totals := make([]float64, len(d.Categories))34+ for _, s := range d.Series {35+ for i, v := range s.Values {36+ if i < len(totals) && !math.IsNaN(v) && !math.IsInf(v, 0) {37+ totals[i] += math.Abs(v)38+ }39+ }40+ }41+ for _, s := range d.Series {42+ vals := make([]float64, len(s.Values))43+ for i, v := range s.Values {44+ vals[i] = math.NaN()45+ if i < len(totals) && totals[i] > 0 && !math.IsInf(v, 0) {46+ vals[i] = v / totals[i]47+ }48+ }49+ out.Series = append(out.Series, sheet.ChartSeries{Name: s.Name, Values: vals})50+ }51+ return out52+}53+54+// value returns series s's value i, NaN when missing or infinite.55+func value(s sheet.ChartSeries, i int) float64 {56+ if i >= len(s.Values) || math.IsInf(s.Values[i], 0) {57+ return math.NaN()58+ }59+ return s.Values[i]60+}61+62+// stackedRange is the range of the stacks' ends over the first n63+// categories: positive values pile up from zero and negative ones down.64+func stackedRange(d sheet.ChartData, n int) (lo, hi float64, ok bool) {65+ for i := range n {66+ up, down := 0.0, 0.067+ for _, s := range d.Series {68+ if v := value(s, i); v > 0 {69+ up += v70+ ok = true71+ } else if v <= 0 {72+ down += v73+ ok = true74+ }75+ }76+ lo, hi = min(lo, down), max(hi, up)77+ }78+ return lo, hi, ok79+}80+81+// stack returns category i's spans, one per series with a value, piled82+// up from zero (positive values) and down from it (negative ones).83+func stack(d sheet.ChartData, i int, out []span) []span {84+ return pileUp(len(d.Series), func(j int) float64 { return value(d.Series[j], i) }, out)85+}86+87+// pileUp stacks n values, v(j) for series j, into out.88+func pileUp(n int, v func(j int) float64, out []span) []span {89+ out = out[:0]90+ up, down := 0.0, 0.091+ for j := range n {92+ switch v := v(j); {93+ case v > 0:94+ out = append(out, span{up, up + v, j})95+ up += v96+ case v < 0:97+ out = append(out, span{down + v, down, j})98+ down += v99+ }100+ }101+ return out102+}103+104+// pileCell is cell c, counted in cells from the start of the axis, of a105+// pile of spans given in cells along it, drawn with eighths growing from106+// the start of the cell (lowerEighths or leftEighths) and half, the107+// glyph for its far half. The span covering the near edge of the cell108+// is the foreground, reaching as far as it goes; the one covering the far109+// edge is the background. A cell only its far part of which is covered110+// is a full block or the half block, as eighths can't grow the other111+// way. ok is false for an empty cell.112+func pileCell(spans []span, c int, eighths []string, half string) (Cell, bool) {113+ const edge = 1.0 / 16114+ near, far := -1, -1115+ for k, s := range spans {116+ if s.lo <= float64(c)+edge && s.hi > float64(c)+edge {117+ near = k118+ }119+ if s.lo < float64(c+1)-edge && s.hi >= float64(c+1)-edge {120+ far = k121+ }122+ }123+ switch {124+ case near >= 0 && near == far:125+ return Cell{Text: "█", Fg: SeriesRole(spans[near].j)}, true126+ case near >= 0:127+ n := int(math.Round((spans[near].hi - float64(c)) * 8))128+ if n >= 8 || far < 0 && n >= 1 {129+ bg := None130+ if far >= 0 {131+ bg = SeriesRole(spans[far].j)132+ }133+ return Cell{Text: eighths[min(n, 8)], Fg: SeriesRole(spans[near].j), Bg: bg}, true134+ }135+ if n >= 1 {136+ return Cell{Text: eighths[n], Fg: SeriesRole(spans[near].j), Bg: SeriesRole(spans[far].j)}, true137+ }138+ }139+ if far < 0 {140+ return Cell{}, false141+ }142+ switch cover := float64(c+1) - spans[far].lo; {143+ case cover >= 0.75:144+ return Cell{Text: "█", Fg: SeriesRole(spans[far].j)}, true145+ case cover >= 0.25:146+ return Cell{Text: half, Fg: SeriesRole(spans[far].j)}, true147+ }148+ return Cell{}, false149+}150+151+// drawPiles draws the bars of the first shown categories cell by cell:152+// piles gives category i's spans in values, one pile of them all when153+// stacked, and put draws cell c along the axis of series j's bar (the154+// stack's, when stacked) in category i.155+func drawPiles(sc scale, shown int, stacked bool, piles func(int, []span) []span, eighths []string, half string, put func(i, j, c int, cell Cell)) {156+ var vals, cells []span157+ for i := 0; i < shown; i++ {158+ vals = piles(i, vals)159+ for k := range vals {160+ pile := vals[k : k+1]161+ if stacked {162+ pile = vals163+ }164+ cells = inCells(sc, pile, cells)165+ from, to := pileExtent(cells)166+ for c := from; c <= to; c++ {167+ if cell, ok := pileCell(cells, c, eighths, half); ok {168+ put(i, vals[k].j, c, cell)169+ }170+ }171+ if stacked {172+ break173+ }174+ }175+ }176+}177+178+// inCells maps spans in values to spans in cells along sc, into out.179+func inCells(sc scale, spans []span, out []span) []span {180+ out = out[:0]181+ for _, s := range spans {182+ out = append(out, span{sc.at(s.lo), sc.at(s.hi), s.j})183+ }184+ return out185+}186+187+// pileExtent is the first and last cell a pile in cells touches.188+func pileExtent(spans []span) (from, to int) {189+ lo, hi := math.Inf(1), math.Inf(-1)190+ for _, s := range spans {191+ lo, hi = min(lo, s.lo), max(hi, s.hi)192+ }193+ if lo > hi {194+ return 0, -1195+ }196+ return int(math.Floor(lo)), int(math.Ceil(hi)) - 1197+}
M internal/chart/stress_test.go +2 -0
A internal/chart/testdata/area-negative.txt +10 -0
A internal/chart/testdata/area-stacked.txt +12 -0
A internal/chart/testdata/area.txt +12 -0
A internal/chart/testdata/axis-log.txt +12 -0
A internal/chart/testdata/axis-min-max.txt +12 -0
A internal/chart/testdata/axis-min.txt +12 -0
A internal/chart/testdata/bar-log.txt +10 -0
A internal/chart/testdata/bar-percent.txt +12 -0
M internal/chart/testdata/bar-single.txt +5 -5
A internal/chart/testdata/bar-stacked.txt +12 -0
M internal/chart/testdata/bar.txt +7 -7
M internal/chart/testdata/column-narrow.txt +2 -2
M internal/chart/testdata/column-negative.txt +3 -3
A internal/chart/testdata/column-percent.txt +12 -0
M internal/chart/testdata/column-single.txt +3 -3
A internal/chart/testdata/column-stacked-negative.txt +12 -0
A internal/chart/testdata/column-stacked.txt +12 -0
M internal/chart/testdata/column.txt +3 -3
A internal/chart/testdata/images.txt +17 -0
A internal/chart/testdata/legend-none.txt +12 -0
A internal/chart/testdata/legend-right.txt +12 -0
M internal/chart/testdata/line-single.txt +4 -4
M internal/chart/testdata/line.txt +3 -3
A internal/chart/testdata/no-gridlines.txt +10 -0
A internal/chart/testdata/pie-no-legend.txt +10 -0
A internal/chart/testdata/scatter-single.txt +10 -0
A internal/chart/testdata/scatter-trend.txt +12 -0
A internal/chart/testdata/scatter.txt +12 -0
M internal/chart/types.go +47 -19
@@ -20,33 +20,61 @@ type plan interface {
2020 image(cv *canvas)2121 }2222 23-// A layout lays out data as one type of chart.24-type layout func(d sheet.ChartData, w, h int, o Options) plan23+// A chartType lays out data as one type of chart. The legend of series24+// is Draw's: it gives the layout the room the legend leaves, and names25+// returns the series it lists, nil for a type that draws its own.26+type chartType struct {27+ layout func(d sheet.ChartData, w, h int, o Options) plan28+ names func(d sheet.ChartData) []string29+}2530 2631 // types are the chart types, each a layout whose plan draws it as text2732 // and as an image. The types themselves, their names and the order the2833 // chart editor offers them, are in the sheet package, since charts are2934 // saved with sheets; adding one is a constant there and a row here.30-var types = map[sheet.ChartType]layout{31- sheet.ChartColumn: func(d sheet.ChartData, w, h int, _ Options) plan { return newColumnPlan(d, w, h, false) },32- sheet.ChartBar: func(d sheet.ChartData, w, h int, _ Options) plan { return newBarPlan(d, w, h) },33- sheet.ChartLine: func(d sheet.ChartData, w, h int, _ Options) plan { return newColumnPlan(d, w, h, true) },34- sheet.ChartPie: func(d sheet.ChartData, w, h int, o Options) plan { return newPiePlan(d, w, h, o) },35+var types = map[sheet.ChartType]chartType{36+ sheet.ChartColumn: {columnLayout(columnBars), allSeries},37+ sheet.ChartBar: {func(d sheet.ChartData, w, h int, o Options) plan {38+ return newBarPlan(d, w, h, o.Chart)39+ }, allSeries},40+ sheet.ChartLine: {columnLayout(columnLine), allSeries},41+ sheet.ChartPie: {func(d sheet.ChartData, w, h int, o Options) plan {42+ return newPiePlan(d, w, h, o)43+ }, nil},44+ sheet.ChartArea: {columnLayout(columnArea), allSeries},45+ sheet.ChartScatter: {func(d sheet.ChartData, w, h int, o Options) plan {46+ return newScatterPlan(d, w, h, o.Chart)47+ }, func(d sheet.ChartData) []string {48+ _, ys := scatterSeries(d)49+ return seriesNames(ys)50+ }},51+}52+53+func columnLayout(kind columnKind) func(d sheet.ChartData, w, h int, o Options) plan {54+ return func(d sheet.ChartData, w, h int, o Options) plan { return newColumnPlan(d, w, h, kind, o.Chart) }3555 }3656 37-// planFor lays out d as a chart of type t; an unknown type is drawn as38-// columns.39-func planFor(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) plan {40- lay, ok := types[t]57+func allSeries(d sheet.ChartData) []string { return seriesNames(d.Series) }58+59+// planFor lays out d as a chart of type t, with its legend; an unknown60+// type is drawn as columns.61+func planFor(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) (plan, legendBox) {62+ ct, ok := types[t]4163 if !ok {42- lay = types[sheet.ChartColumn]64+ ct = types[sheet.ChartColumn]65+ }66+ lg := legendBox{pos: sheet.LegendNone}67+ if ct.names != nil {68+ lg, w, h = placeLegend(ct.names(d), o.Chart.Legend, w, h)4369 }44- return lay(d, w, h, o)70+ return ct.layout(d, w, h, o), lg4571 }4672 47-func (p *columnPlan) note() string { return p.msg }48-func (p *columnPlan) plotArea() image.Rectangle { return p.plot }49-func (p *barPlan) note() string { return p.msg }50-func (p *barPlan) plotArea() image.Rectangle { return p.plot }51-func (p *piePlan) note() string { return p.msg }52-func (p *piePlan) plotArea() image.Rectangle { return p.disc }73+func (p *columnPlan) note() string { return p.msg }74+func (p *columnPlan) plotArea() image.Rectangle { return p.plot }75+func (p *barPlan) note() string { return p.msg }76+func (p *barPlan) plotArea() image.Rectangle { return p.plot }77+func (p *piePlan) note() string { return p.msg }78+func (p *piePlan) plotArea() image.Rectangle { return p.disc }79+func (p *scatterPlan) note() string { return p.msg }80+func (p *scatterPlan) plotArea() image.Rectangle { return p.plot }
A internal/chart/types_test.go +155 -0
@@ -0,0 +1,155 @@
1+package chart2+3+import (4+ "crypto/sha256"5+ "fmt"6+ "os"7+ "path/filepath"8+ "strings"9+ "testing"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// imageCases are the images TestImageHashes pins: every type, and the15+// options that change how the plot is drawn.16+var imageCases = []struct {17+ name string18+ t sheet.ChartType19+ d sheet.ChartData20+ o sheet.ChartOptions21+}{22+ {"column", sheet.ChartColumn, budget, sheet.ChartOptions{}},23+ {"column-negative", sheet.ChartColumn, mixed, sheet.ChartOptions{}},24+ {"bar", sheet.ChartBar, budget, sheet.ChartOptions{}},25+ {"line", sheet.ChartLine, budget, sheet.ChartOptions{}},26+ {"pie", sheet.ChartPie, single, sheet.ChartOptions{}},27+ {"area", sheet.ChartArea, budget, sheet.ChartOptions{}},28+ {"area-stacked", sheet.ChartArea, budget, sheet.ChartOptions{Stack: sheet.StackNormal}},29+ {"column-stacked", sheet.ChartColumn, signs, sheet.ChartOptions{Stack: sheet.StackNormal}},30+ {"column-percent", sheet.ChartColumn, budget, sheet.ChartOptions{Stack: sheet.StackPercent}},31+ {"bar-stacked", sheet.ChartBar, signs, sheet.ChartOptions{Stack: sheet.StackNormal}},32+ {"bar-percent", sheet.ChartBar, budget, sheet.ChartOptions{Stack: sheet.StackPercent}},33+ {"scatter", sheet.ChartScatter, xy, sheet.ChartOptions{}},34+ {"scatter-trend", sheet.ChartScatter, xy, sheet.ChartOptions{Trend: true}},35+ {"no-gridlines", sheet.ChartColumn, budget, sheet.ChartOptions{NoGrid: true}},36+ {"axis-min-max", sheet.ChartLine, budget, sheet.ChartOptions{Min: 500, HasMin: true, Max: 1600, HasMax: true}},37+ {"axis-log", sheet.ChartColumn, growth, sheet.ChartOptions{Log: true}},38+ {"legend-right", sheet.ChartColumn, budget, sheet.ChartOptions{Legend: sheet.LegendRight}},39+}40+41+// TestImageHashes pins the image of each case by a hash of its pixels,42+// kept in testdata/images.txt (go test ./internal/chart -update rewrites43+// it). A changed hash means the image changed: look at it before44+// updating.45+func TestImageHashes(t *testing.T) {46+ var got strings.Builder47+ for _, tc := range imageCases {48+ img := Image(tc.t, tc.d, 40, 12, Options{CellW: 8, CellH: 16, Chart: tc.o}, testPalette)49+ if img == nil {50+ t.Errorf("%s: no image", tc.name)51+ continue52+ }53+ sum := sha256.Sum256(img.Pix)54+ fmt.Fprintf(&got, "%s %dx%d %x\n", tc.name, img.Bounds().Dx(), img.Bounds().Dy(), sum[:8])55+ }56+ path := filepath.Join("testdata", "images.txt")57+ if *update {58+ os.WriteFile(path, []byte(got.String()), 0o644)59+ return60+ }61+ want, err := os.ReadFile(path)62+ if err != nil {63+ t.Fatalf("missing hashes (go test ./internal/chart -update): %v", err)64+ }65+ if got.String() != string(want) {66+ t.Errorf("images changed:\n%s\nwant:\n%s", got.String(), want)67+ }68+}69+70+// TestNewTypesPixels checks a few pixels of the stacked, scatter and71+// area images directly.72+func TestNewTypesPixels(t *testing.T) {73+ o := Options{CellW: 8, CellH: 16}74+ // A stacked column: Rent at the bottom, Food above it.75+ o.Chart = sheet.ChartOptions{Stack: sheet.StackNormal}76+ p := newColumnPlan(budget, 44, 11, columnBars, o.Chart)77+ img := Image(sheet.ChartColumn, budget, 44, 12, o, testPalette)78+ x0, x1 := p.bar(0, 0)79+ mid := ((x0+x1)/2 - p.plot.Min.X) * 880+ bottom := img.Bounds().Dy()81+ rentTop := int(float64(bottom) - p.sc.pos(1450)*16)82+ if c := img.RGBAAt(mid, bottom-2); c != testPalette.Series[0] {83+ t.Errorf("stack bottom %v, want Rent's color", c)84+ }85+ if c := img.RGBAAt(mid, rentTop-4); c != testPalette.Series[1] {86+ t.Errorf("stack top %v, want Food's color", c)87+ }88+ if x0b, _ := p.bar(0, 1); x0b != x0 {89+ t.Errorf("stacked series at %d and %d, want one column", x0, x0b)90+ }91+ // A scatter point is its series' color at its center.92+ o.Chart = sheet.ChartOptions{}93+ sp := newScatterPlan(xy, 40, 11, o.Chart)94+ simg := Image(sheet.ChartScatter, xy, 40, 12, o, testPalette)95+ cx := int(sp.xsc.at(1) * 8)96+ cy := int(float64(simg.Bounds().Dy()) - sp.ysc.at(52)*16)97+ if c := simg.RGBAAt(cx, cy); c != testPalette.Series[0] {98+ t.Errorf("scatter point %v at %d,%d", c, cx, cy)99+ }100+ // The area under a line is filled, above it is clear.101+ aimg := Image(sheet.ChartArea, single, 40, 10, o, testPalette)102+ b := aimg.Bounds()103+ if c := aimg.RGBAAt(2, b.Dy()-3); c.A == 0 {104+ t.Errorf("under the area %v, want filled", c)105+ }106+ if c := aimg.RGBAAt(b.Dx()-2, 2); c.A != 0 {107+ t.Errorf("above the area %v, want clear", c)108+ }109+}110+111+func TestLogAndFixedScales(t *testing.T) {112+ s := newAxis(3, 9000, 10, 2, 6, sheet.Format{}, sheet.ChartOptions{Log: true})113+ if s.tick(0) != 1 || s.tick(s.n) != 10000 || s.n != 4 {114+ t.Errorf("log scale %v..%v in %d", s.tick(0), s.tick(s.n), s.n)115+ }116+ if got := s.label(1000); got != "1,000" {117+ t.Errorf("log label %q", got)118+ }119+ if p := s.pos(100); p != float64(2*s.k) {120+ t.Errorf("100 at %v cells", p)121+ }122+ f := newAxis(0, 1500, 9, 2, 6, sheet.Format{}, sheet.ChartOptions{Min: 200, HasMin: true})123+ if f.lo != 200 || f.hi < 1500 {124+ t.Errorf("fixed minimum: %v..%v", f.lo, f.hi)125+ }126+ f = newAxis(0, 1500, 8, 2, 6, sheet.Format{}, sheet.ChartOptions{Min: 0, HasMin: true, Max: 2000, HasMax: true})127+ if f.lo != 0 || f.hi != 2000 || niceStep(f.step) != f.step {128+ t.Errorf("fixed ends: %v..%v step %v", f.lo, f.hi, f.step)129+ }130+ // A value past a fixed end stops at the end of the axis.131+ if at := f.at(5000); at != float64(f.cells()) {132+ t.Errorf("clipped at %v", at)133+ }134+}135+136+// TestLegendPlacement checks the legend takes its room from the chart.137+func TestLegendPlacement(t *testing.T) {138+ names := []string{"Rent", "Food"}139+ if lg, w, h := placeLegend(names, sheet.LegendRight, 44, 12); lg.pos != sheet.LegendRight || w != 44-6-1 || h != 12 || lg.x != 38 {140+ t.Errorf("right: %+v in %dx%d", lg, w, h)141+ }142+ if lg, w, h := placeLegend(names, sheet.LegendBottom, 44, 12); lg.row != 11 || w != 44 || h != 11 {143+ t.Errorf("bottom: %+v in %dx%d", lg, w, h)144+ }145+ if lg, w, h := placeLegend(names, sheet.LegendNone, 44, 12); lg.pos != sheet.LegendNone || w != 44 || h != 12 {146+ t.Errorf("none: %+v in %dx%d", lg, w, h)147+ }148+ // One series, or too little room: no legend.149+ if lg, _, _ := placeLegend(names[:1], sheet.LegendBottom, 44, 12); lg.pos != sheet.LegendNone {150+ t.Errorf("one series: %+v", lg)151+ }152+ if lg, _, _ := placeLegend(names, sheet.LegendRight, 20, 12); lg.pos != sheet.LegendNone {153+ t.Errorf("narrow: %+v", lg)154+ }155+}
M internal/fileio/snapshot.go +11 -0
M internal/fileio/xlsx.go +11 -4
@@ -61,17 +61,24 @@ func exportXLSX(_ context.Context, name string, snap *Snapshot, _ ExportOptions)
6161 hidden[active] = false6262 err := writeFile(name, func(out io.Writer) error {6363 zw := zip.NewWriter(out)64+ var withNotes []int6465 for i, sn := range sheets {6566 rows, err := w.writeSheet(zw, i, names[i], sn, i == active)6667 if err != nil {6768 return err6869 }6970 res.Rows += rows71+ if len(sn.Notes) > 0 {72+ withNotes = append(withNotes, i+1)73+ if err := writeNotes(zw, i+1, sn.Notes); err != nil {74+ return err75+ }76+ }7077 }7178 if err := writePart(zw, "xl/styles.xml", w.styles.xml()); err != nil {7279 return err7380 }74- if err := writePackage(zw, names, hidden, active, w.definedNames(snap.Names), filterRanges(sheets, names)); err != nil {81+ if err := writePackage(zw, names, hidden, active, w.definedNames(snap.Names), filterRanges(sheets, names), notesTypes(withNotes)); err != nil {7582 return err7683 }7784 return zw.Close()
@@ -141,14 +148,14 @@ func filterRanges(sheets []*Snapshot, names []string) []string {
141148 // writePackage writes the parts around the worksheets and styles: the142149 // workbook with its sheets, active tab, names (with each filter's range,143150 // filters[i] for sheet i) and calculation settings, the relationships144-// and the content types.145-func writePackage(zw *zip.Writer, names []string, hidden []bool, active int, defined [][2]string, filters []string) error {151+// and the content types, with extra ones for the sheets' parts.152+func writePackage(zw *zip.Writer, names []string, hidden []bool, active int, defined [][2]string, filters []string, extraTypes string) error {146153 var types, rels, book strings.Builder147154 types.WriteString(xmlHead + `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">` +148155 `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>` +149156 `<Default Extension="xml" ContentType="application/xml"/>` +150157 `<Override PartName="/xl/workbook.xml" ContentType="` + mlType + `sheet.main+xml"/>` +151- `<Override PartName="/xl/styles.xml" ContentType="` + mlType + `styles+xml"/>`)158+ `<Override PartName="/xl/styles.xml" ContentType="` + mlType + `styles+xml"/>` + extraTypes)152159 rels.WriteString(xmlHead + `<Relationships xmlns="` + relsNS + `">`)153160 book.WriteString(xmlHead + `<workbook xmlns="` + sheetMain + `" xmlns:r="` + officeRel + `">` +154161 `<bookViews><workbookView activeTab="` + strconv.Itoa(active) + `"/></bookViews><sheets>`)
M internal/fileio/xlsx_diff_test.go +6 -0
M internal/fileio/xlsx_excelize_test.go +44 -17
@@ -68,14 +68,14 @@ func importXLSXExcelize(ctx context.Context, name string, opt Options) (*Result,
6868 if err != nil {6969 return nil, err7070 }71- excelizePanes(x, ws, next)7271 done += rows7372 }74- filters, err := referenceAutoFilters(name)73+ filters, protected, err := referenceAutoFilters(name)7574 if err != nil {7675 return nil, err7776 }7877 notes = append(notes, excelizeNames(x, book)...)78+ notes = append(notes, (&xlsxBook{protected: protected}).protectionNote()...)7979 active := book.Sheet(clamp(x.GetActiveSheetIndex(), 0, book.Len()-1))8080 book.SetActive(active)8181 for i, ws := range names {
@@ -89,15 +89,37 @@ func importXLSXExcelize(ctx context.Context, name string, opt Options) (*Result,
8989 return &Result{Sheet: s, Rows: done, Notes: append(notes, applyFilters(book, filters)...)}, nil9090 }9191 92-// excelizePanes freezes s as sheet ws is frozen.93-func excelizePanes(x *excelize.File, ws string, s *sheet.Sheet) {92+// excelizeView freezes s as sheet ws is frozen and loads its notes.93+func excelizeView(x *excelize.File, ws string, s *sheet.Sheet) error {9494 if p, err := x.GetPanes(ws); err == nil && p.Freeze {9595 s.LoadFrozen(p.YSplit, p.XSplit)9696 }97+ return excelizeNotes(x, ws, s)98+}99+100+// excelizeNotes loads sheet ws's notes (Excel's legacy comments) into s.101+func excelizeNotes(x *excelize.File, ws string, s *sheet.Sheet) error {102+ comments, err := x.GetComments(ws)103+ if err != nil {104+ return err105+ }106+ for _, c := range comments {107+ text := c.Text108+ for _, run := range c.Paragraph {109+ text += run.Text110+ }111+ if a, ok := sheet.ParseAddr(c.Cell); ok {112+ s.LoadNote(a, text)113+ }114+ }115+ return nil97116 }98117 99118 // refWorksheet is the part of a worksheet referenceAutoFilters reads.100119 type refWorksheet struct {120+ Protection *struct {121+ Sheet string `xml:"sheet,attr"`122+ } `xml:"sheetProtection"`101123 AutoFilter *struct {102124 Ref string `xml:"ref,attr"`103125 Cols []struct {
@@ -123,46 +145,51 @@ type refWorksheet struct {
123145 } `xml:"autoFilter"`124146 }125147 126-// referenceAutoFilters reads each sheet's autoFilter by decoding the127-// whole worksheet with encoding/xml, as excelize has no API for reading128-// one: the reference for the streaming reader's. Only the parts' names129-// come from 012's reader.130-func referenceAutoFilters(name string) ([]*xlsxAutoFilter, error) {148+// referenceAutoFilters reads each sheet's autoFilter, and the names of149+// the protected sheets, by decoding the whole worksheet with150+// encoding/xml, as excelize has no API for reading them: the reference151+// for the streaming reader's. Only the parts' names come from 012's152+// reader.153+func referenceAutoFilters(name string) ([]*xlsxAutoFilter, []string, error) {131154 f, err := os.Open(name)132155 if err != nil {133- return nil, err156+ return nil, nil, err134157 }135158 defer f.Close()136159 st, err := f.Stat()137160 if err != nil {138- return nil, err161+ return nil, nil, err139162 }140163 bk, err := openXLSX(f, st.Size(), defaultXLSXLimits)141164 if err != nil {142- return nil, err165+ return nil, nil, err143166 }144167 zr, err := zip.NewReader(f, st.Size())145168 if err != nil {146- return nil, err169+ return nil, nil, err147170 }148171 out := make([]*xlsxAutoFilter, len(bk.sheets))172+ var protected []string149173 for i, info := range bk.sheets {150174 if info.part == "" || info.kind != "worksheet" {151175 continue152176 }153177 rc, err := zr.Open(info.part)154178 if err != nil {155- return nil, err179+ return nil, nil, err156180 }157181 var ws refWorksheet158182 err = xml.NewDecoder(rc).Decode(&ws)159183 rc.Close()160184 if err != nil {161- return nil, err185+ return nil, nil, err162186 }163187 out[i] = ws.autoFilter()188+ if p := ws.Protection; p != nil && (p.Sheet == "1" || p.Sheet == "true") {189+ protected = append(protected, info.name)190+ }164191 }165- return out, nil192+ return out, protected, nil166193 }167194 168195 func (ws refWorksheet) autoFilter() *xlsxAutoFilter {
@@ -253,7 +280,7 @@ func excelizeSheet(ctx context.Context, x *excelize.File, b *builder, ws string,
253280 }254281 b.s.LoadColWidth(c, max(int(math.Round(w))+excelPadding, 1))255282 }256- return row, nil283+ return row, excelizeView(x, ws, b.s)257284 }258285 259286 // excelizeNames defines the workbook's named ranges that are a range on
M internal/fileio/xlsxbook.go +4 -2
M internal/fileio/xlsximport.go +18 -1
@@ -68,7 +68,7 @@ func (bk *xlsxBook) importBook(ctx context.Context, opt Options) (*Result, error
6868 }6969 done += rows7070 }71- notes := bk.importNames(book)71+ notes := append(bk.importNames(book), bk.protectionNote()...)7272 active := book.Sheet(clamp(bk.active, 0, book.Len()-1))7373 book.SetActive(active)7474 for i, info := range bk.sheets {
@@ -120,6 +120,12 @@ func (bk *xlsxBook) importSheet(ctx context.Context, b *builder, i int, af **xls
120120 return 0, fmt.Errorf("sheet %s: %w", bk.sheets[i].name, err)121121 }122122 bk.loadRules(b.s, r)123+ if r.protected {124+ bk.protected = append(bk.protected, bk.sheets[i].name)125+ }126+ if err := bk.readNotes(b.s, bk.sheets[i].part); err != nil {127+ return 0, fmt.Errorf("sheet %s: %w", bk.sheets[i].name, err)128+ }123129 loadColStyles(b, width, func(c int) (xlsxStyle, bool) {124130 st := bk.styles.style(r.lineStyle(c + 1))125131 return st, st != (xlsxStyle{})
@@ -268,6 +274,17 @@ func isoDate(s string) (float64, sheet.Format, bool) {
268274 return 0, sheet.Format{}, false269275 }270276 277+// protectionNote says which sheets were protected in the file: 012278+// leaves their protection out, since Excel's locks cells while 012's279+// only warns, and Data > Protect sheets and ranges adds that.280+func (bk *xlsxBook) protectionNote() []string {281+ if len(bk.protected) == 0 {282+ return nil283+ }284+ return []string{fmt.Sprintf("Protection of %s left out: Data > Protect sheets and ranges warns before edits",285+ strings.Join(bk.protected, ", "))}286+}287+271288 // importNames defines the workbook's named ranges that are a range on272289 // one sheet, such as Sales = Q3!$B$2:$B$20, and notes the others (sheet273290 // scoped names, constants, formulas).
A internal/fileio/xlsxnotes.go +134 -0
@@ -0,0 +1,134 @@
1+package fileio2+3+import (4+ "archive/zip"5+ "encoding/xml"6+ "fmt"7+ "io"8+ "maps"9+ "slices"10+ "strconv"11+ "strings"12+13+ "github.com/FelineStateMachine/012/internal/sheet"14+)15+16+// Cell notes travel as Excel's notes (legacy comments, which Sheets17+// imports as notes and writes its notes as): a comments part per sheet18+// with the text, and the VML drawing Excel needs to show each note's19+// box, both related to the worksheet, which names the drawing in20+// <legacyDrawing>.21+22+const (23+ commentsType = mlType + "comments+xml"24+ vmlType = "application/vnd.openxmlformats-officedocument.vmlDrawing"25+ notesRelID = "rIdNotes"26+ vmlRelID = "rIdVML"27+)28+29+// writeNotes writes worksheet part n's notes: its relationships, the30+// comments part and the VML drawing.31+func writeNotes(zw *zip.Writer, n int, notes map[sheet.Addr]string) error {32+ num := strconv.Itoa(n)33+ addrs := slices.SortedFunc(maps.Keys(notes), func(a, b sheet.Addr) int {34+ if a.Row != b.Row {35+ return a.Row - b.Row36+ }37+ return a.Col - b.Col38+ })39+ var comments, vml strings.Builder40+ comments.WriteString(xmlHead + `<comments xmlns="` + sheetMain + `"><authors><author></author></authors><commentList>`)41+ fmt.Fprintf(&vml, `<xml xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">`+42+ `<o:shapelayout v:ext="edit"><o:idmap v:ext="edit" data="%d"/></o:shapelayout>`+43+ `<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe">`+44+ `<v:stroke joinstyle="miter"/><v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>`, n)45+ for k, a := range addrs {46+ comments.WriteString(`<comment ref="` + a.String() + `" authorId="0"><text><t xml:space="preserve">`)47+ comments.Write(appendEscaped(nil, notes[a], true))48+ comments.WriteString(`</t></text></comment>`)49+ fmt.Fprintf(&vml, `<v:shape id="_x0000_s%d" type="#_x0000_t202" style="position:absolute;margin-left:60pt;margin-top:2pt;width:108pt;height:60pt;z-index:%d;visibility:hidden" fillcolor="#ffffe1" o:insetmode="auto">`+50+ `<v:fill color2="#ffffe1"/><v:shadow on="t" color="black" obscured="t"/><v:path o:connecttype="none"/>`+51+ `<v:textbox style="mso-direction-alt:auto"><div style="text-align:left"></div></v:textbox>`+52+ `<x:ClientData ObjectType="Note"><x:MoveWithCells/><x:SizeWithCells/><x:Anchor>%d, 15, %d, 2, %d, 15, %d, 4</x:Anchor>`+53+ `<x:AutoFill>False</x:AutoFill><x:Row>%d</x:Row><x:Column>%d</x:Column></x:ClientData></v:shape>`,54+ n*1024+k+1, k+1, a.Col+1, a.Row, a.Col+3, a.Row+4, a.Row, a.Col)55+ }56+ comments.WriteString(`</commentList></comments>`)57+ vml.WriteString(`</xml>`)58+ rels := xmlHead + `<Relationships xmlns="` + relsNS + `">` +59+ `<Relationship Id="` + notesRelID + `" Type="` + officeRel + `/comments" Target="../comments` + num + `.xml"/>` +60+ `<Relationship Id="` + vmlRelID + `" Type="` + officeRel + `/vmlDrawing" Target="../drawings/vmlDrawing` + num + `.vml"/>` +61+ `</Relationships>`62+ for _, p := range []struct{ name, body string }{63+ {"xl/worksheets/_rels/sheet" + num + ".xml.rels", rels},64+ {"xl/comments" + num + ".xml", comments.String()},65+ {"xl/drawings/vmlDrawing" + num + ".vml", vml.String()},66+ } {67+ if err := writePart(zw, p.name, p.body); err != nil {68+ return err69+ }70+ }71+ return nil72+}73+74+// notesTypes are the content types of the notes of the worksheet parts75+// numbered in withNotes.76+func notesTypes(withNotes []int) string {77+ if len(withNotes) == 0 {78+ return ""79+ }80+ var b strings.Builder81+ b.WriteString(`<Default Extension="vml" ContentType="` + vmlType + `"/>`)82+ for _, n := range withNotes {83+ fmt.Fprintf(&b, `<Override PartName="/xl/comments%d.xml" ContentType="%s"/>`, n, commentsType)84+ }85+ return b.String()86+}87+88+// readNotes reads the notes of worksheet part, from the comments part89+// its relationships name, into s.90+func (bk *xlsxBook) readNotes(s *sheet.Sheet, part string) error {91+ rels, err := bk.pkg.rels(part)92+ if err != nil {93+ return err94+ }95+ for _, rel := range rels {96+ if rel.typ == "comments" && bk.pkg.has(rel.target) {97+ if err := bk.readComments(s, rel.target); err != nil {98+ return fmt.Errorf("notes: %w", err)99+ }100+ }101+ }102+ return nil103+}104+105+// readComments reads a comments part: each <comment ref="B2"> with the106+// text of its runs.107+func (bk *xlsxBook) readComments(s *sheet.Sheet, part string) error {108+ x, err := bk.pkg.open(part)109+ if x == nil || err != nil {110+ return err111+ }112+ defer x.close()113+ var buf []byte114+ for {115+ t, err := x.next()116+ if err == io.EOF {117+ return nil118+ }119+ if err != nil {120+ return err121+ }122+ se, ok := t.(xml.StartElement)123+ if !ok || se.Name.Local != "comment" {124+ continue125+ }126+ ref, _ := attr(se, "ref")127+ if buf, err = inlineText(x, buf[:0]); err != nil {128+ return err129+ }130+ if a, ok := sheet.ParseAddr(ref); ok {131+ s.LoadNote(a, string(unescapeOOXML(buf)))132+ }133+ }134+}
A internal/fileio/xlsxnotes_test.go +62 -0
@@ -0,0 +1,62 @@
1+package fileio2+3+import (4+ "context"5+ "path/filepath"6+ "strings"7+ "testing"8+9+ "github.com/xuri/excelize/v2"10+)11+12+func TestXLSXNotes(t *testing.T) {13+ src := build(t, map[string]string{"A1": "Rent", "B1": "1450"})14+ src.SetNote(addr(t, "B1"), "Due on\nthe 1st")15+ src.SetNote(addr(t, "D9"), "Blank cell")16+ book := src.Book()17+ other, _ := book.AddSheet("Other", 1)18+ other.SetNote(addr(t, "A1"), "_x0041_ stays & <so> do these")19+ name := filepath.Join(t.TempDir(), "notes.xlsx")20+ if _, err := Export(context.Background(), name, XLSX, SnapBook(src), ExportOptions{}); err != nil {21+ t.Fatal(err)22+ }23+ // excelize reads them as Excel would.24+ x, err := excelize.OpenFile(name)25+ if err != nil {26+ t.Fatal(err)27+ }28+ cs, err := x.GetComments(x.GetSheetName(0))29+ if err != nil || len(cs) != 2 || cs[0].Cell != "B1" || cs[0].Text != "Due on\nthe 1st" {30+ t.Errorf("excelize comments %+v, %v", cs, err)31+ }32+ x.Close()33+ got, err := Import(context.Background(), name, Options{})34+ if err != nil {35+ t.Fatal(err)36+ }37+ s := got.Sheet38+ if s.Note(addr(t, "B1")) != "Due on\nthe 1st" || s.Note(addr(t, "D9")) != "Blank cell" {39+ t.Errorf("notes %q %q", s.Note(addr(t, "B1")), s.Note(addr(t, "D9")))40+ }41+ if n := s.Book().Lookup("Other").Note(addr(t, "A1")); n != "_x0041_ stays & <so> do these" {42+ t.Errorf("escaped note %q", n)43+ }44+}45+46+func TestXLSXProtectionNote(t *testing.T) {47+ x := excelize.NewFile()48+ x.SetCellValue("Sheet1", "A1", 1)49+ x.ProtectSheet("Sheet1", &excelize.SheetProtectionOptions{})50+ name := filepath.Join(t.TempDir(), "protected.xlsx")51+ if err := x.SaveAs(name); err != nil {52+ t.Fatal(err)53+ }54+ x.Close()55+ got, err := Import(context.Background(), name, Options{})56+ if err != nil {57+ t.Fatal(err)58+ }59+ if len(got.Sheet.Protections()) != 0 || !strings.Contains(strings.Join(got.Notes, "; "), "Protection of Sheet1 left out") {60+ t.Errorf("protections %+v, notes %q", got.Sheet.Protections(), got.Notes)61+ }62+}
M internal/fileio/xlsxsheet.go +2 -0
M internal/fileio/xlsxview_test.go +1 -1
M internal/fileio/xlsxviewread.go +7 -3
M internal/fileio/xlsxwrite.go +3 -0
A internal/sheet/cellfile.go +92 -0
@@ -0,0 +1,92 @@
1+package sheet2+3+import (4+ "encoding/json"5+ "fmt"6+)7+8+// fileCell is a cell with formatting or a note as the file writes it; a9+// cell with neither is just its input (see fileFormat).10+type fileCell struct {11+ Input string `json:"input,omitempty"`12+ Format string `json:"format,omitempty"`13+ Decimals *int `json:"decimals,omitempty"`14+ Pattern string `json:"pattern,omitempty"`15+ Bold bool `json:"bold,omitempty"`16+ Italic bool `json:"italic,omitempty"`17+ Underline bool `json:"underline,omitempty"`18+ Strikethrough bool `json:"strikethrough,omitempty"`19+ Align string `json:"align,omitempty"`20+ Own bool `json:"own,omitempty"` // not taking its column's or row's formatting21+ // Note needs no version bump: earlier builds ignore it and drop the22+ // note.23+ Note string `json:"note,omitempty"`24+}25+26+func encodeCell(c *Cell) (json.RawMessage, error) {27+ if c.Format.IsZero() && c.Style.IsZero() && c.Note == "" {28+ return json.Marshal(c.Input)29+ }30+ fc := fileCell{31+ Input: c.Input,32+ Pattern: c.Format.Pattern,33+ Bold: c.Style.Bold,34+ Italic: c.Style.Italic,35+ Underline: c.Style.Underline,36+ Strikethrough: c.Style.Strikethrough,37+ Align: c.Style.Align.String(),38+ Own: c.Style.own,39+ Note: c.Note,40+ }41+ if !c.Format.IsZero() {42+ fc.Format = c.Format.Kind.String()43+ }44+ if c.Format.Kind.HasDecimals() {45+ d := c.Format.Decimals46+ fc.Decimals = &d47+ }48+ return json.Marshal(fc)49+}50+51+// decodeCell reads a cell's entry and formatting, as line formats are52+// stored too.53+func decodeCell(raw json.RawMessage) (string, Format, Style, error) {54+ input, f, st, _, err := decodeNoted(raw)55+ return input, f, st, err56+}57+58+// decodeNoted is decodeCell with the cell's note.59+func decodeNoted(raw json.RawMessage) (string, Format, Style, string, error) {60+ var input string61+ if err := json.Unmarshal(raw, &input); err == nil {62+ return input, Format{}, Style{}, "", nil63+ }64+ var fc fileCell65+ if err := json.Unmarshal(raw, &fc); err != nil {66+ return "", Format{}, Style{}, "", err67+ }68+ input, f, st, err := decodeFormatted(fc)69+ return input, f, st, fc.Note, err70+}71+72+// decodeFormatted reads the entry and formatting of a cell written as an73+// object.74+func decodeFormatted(fc fileCell) (string, Format, Style, error) {75+ var f Format76+ if fc.Format != "" {77+ k, ok := ParseFormatKind(fc.Format)78+ if !ok {79+ return "", Format{}, Style{}, fmt.Errorf("unknown format %q", fc.Format)80+ }81+ f = Format{Kind: k, Pattern: fc.Pattern}82+ if fc.Decimals != nil {83+ f.Decimals = clampInt(*fc.Decimals, 0, MaxDecimals)84+ }85+ }86+ al, ok := ParseAlign(fc.Align)87+ if !ok {88+ return "", Format{}, Style{}, fmt.Errorf("unknown alignment %q", fc.Align)89+ }90+ st := Style{Bold: fc.Bold, Italic: fc.Italic, Underline: fc.Underline, Strikethrough: fc.Strikethrough, Align: al, own: fc.Own}91+ return fc.Input, f, st, nil92+}
M internal/sheet/chart.go +11 -5
M internal/sheet/chart_test.go +9 -0
A internal/sheet/chartfile.go +81 -0
@@ -0,0 +1,81 @@
1+package sheet2+3+import (4+ "encoding/json"5+ "fmt"6+)7+8+// fileChart is a chart, one per line after the cells:9+//10+// {"type": "column", "data": "A1:C7", "at": "E2", "width": 44, "height": 14, "header": true, "labels": true}11+//12+// Options at their defaults are left out, so they need no version bump:13+// older versions of 012 draw such a chart with the defaults, and older14+// versions still ignore the field and drop the charts. A type they don't15+// know makes them refuse the file.16+type fileChart struct {17+ Type string `json:"type"`18+ Data string `json:"data"`19+ At string `json:"at"`20+ Width int `json:"width"`21+ Height int `json:"height"`22+ ByRow bool `json:"byRow,omitempty"`23+ Header bool `json:"header,omitempty"`24+ Labels bool `json:"labels,omitempty"`25+ Title string `json:"title,omitempty"`26+ // Options: "stacked" or "percent"; a trend line; the ends of the value27+ // axis; a log scale; gridlines, only when off; "right" or "none".28+ Stack string `json:"stack,omitempty"`29+ Trend bool `json:"trend,omitempty"`30+ Min *float64 `json:"min,omitempty"`31+ Max *float64 `json:"max,omitempty"`32+ Log bool `json:"log,omitempty"`33+ Gridlines *bool `json:"gridlines,omitempty"`34+ Legend string `json:"legend,omitempty"`35+}36+37+func encodeChart(c Chart) ([]byte, error) {38+ fc := fileChart{39+ Type: c.Type.String(), Data: c.Data.String(), At: c.At.String(), Width: c.W, Height: c.H,40+ ByRow: c.ByRow, Header: c.Header, Labels: c.Labels, Title: c.Title,41+ Stack: c.Stack.String(), Trend: c.Trend, Log: c.Log,42+ Min: axisEnd(c.Min, c.HasMin), Max: axisEnd(c.Max, c.HasMax),43+ }44+ if c.NoGrid {45+ fc.Gridlines = new(bool)46+ }47+ if c.Legend != LegendBottom {48+ fc.Legend = c.Legend.String()49+ }50+ return json.Marshal(fc)51+}52+53+func decodeChart(fc fileChart) (Chart, error) {54+ t, ok := ParseChartType(fc.Type)55+ if !ok {56+ return Chart{}, fmt.Errorf("unknown chart type %q", fc.Type)57+ }58+ data, ok := ParseRange(fc.Data)59+ if !ok {60+ return Chart{}, fmt.Errorf("invalid chart range %q", fc.Data)61+ }62+ at, ok := ParseAddr(fc.At)63+ if !ok {64+ return Chart{}, fmt.Errorf("invalid chart position %q", fc.At)65+ }66+ c := Chart{Type: t, Data: data, At: at, W: fc.Width, H: fc.Height,67+ ByRow: fc.ByRow, Header: fc.Header, Labels: fc.Labels, Title: fc.Title}68+ // Options a later build writes and this one doesn't know are left at69+ // their defaults, as an older build would.70+ c.Stack, _ = ParseChartStack(fc.Stack)71+ c.Legend, _ = ParseChartLegend(fc.Legend)72+ c.Trend, c.Log = fc.Trend, fc.Log73+ c.NoGrid = fc.Gridlines != nil && !*fc.Gridlines74+ if fc.Min != nil {75+ c.Min, c.HasMin = *fc.Min, true76+ }77+ if fc.Max != nil {78+ c.Max, c.HasMax = *fc.Max, true79+ }80+ return c.clamped(), nil81+}
A internal/sheet/chartopts.go +106 -0
@@ -0,0 +1,106 @@
1+package sheet2+3+import (4+ "math"5+ "slices"6+ "strings"7+)8+9+// ChartOptions are a chart's settings beyond its type and data, as in10+// the Customize tab of Sheets' chart editor. The zero value is Sheets'11+// default: not stacked, no trend line, an automatic linear value axis12+// with gridlines, and the legend at the bottom. Every field is a plain13+// value, so charts stay comparable with ==.14+type ChartOptions struct {15+ // Stack stacks the series of column, bar and area charts.16+ Stack ChartStack17+ // Trend draws a linear trend line through each series of a scatter18+ // chart.19+ Trend bool20+ // Min and Max fix the ends of the value axis when HasMin and HasMax21+ // are set; otherwise the axis fits the data.22+ Min, Max float6423+ HasMin, HasMax bool24+ // Log puts the value axis on a logarithmic scale, leaving out values25+ // that aren't positive.26+ Log bool27+ // NoGrid hides the gridlines.28+ NoGrid bool29+ // Legend is where the legend goes.30+ Legend ChartLegend31+}32+33+// ChartStack is how the series of a chart pile up.34+type ChartStack int35+36+const (37+ StackNone ChartStack = iota // side by side38+ StackNormal // on top of each other39+ StackPercent // on top of each other, as shares of 100%40+)41+42+var chartStackNames = [...]string{StackNone: "", StackNormal: "stacked", StackPercent: "percent"}43+44+// String is the stacking as files store it, "" for none.45+func (s ChartStack) String() string {46+ if s < 0 || int(s) >= len(chartStackNames) {47+ return ""48+ }49+ return chartStackNames[s]50+}51+52+// Title is the stacking as the chart editor shows it.53+func (s ChartStack) Title() string {54+ switch s {55+ case StackNormal:56+ return "Stacked"57+ case StackPercent:58+ return "100% stacked"59+ }60+ return "Not stacked"61+}62+63+// ParseChartStack parses a stacking as String writes it.64+func ParseChartStack(s string) (ChartStack, bool) {65+ i := slices.Index(chartStackNames[:], strings.ToLower(s))66+ return ChartStack(max(i, 0)), i >= 067+}68+69+// Stackable reports whether charts of type t can stack their series.70+func (t ChartType) Stackable() bool {71+ return t == ChartColumn || t == ChartBar || t == ChartArea72+}73+74+// ChartLegend is where a chart's legend goes.75+type ChartLegend int76+77+const (78+ LegendBottom ChartLegend = iota // under the plot, centered79+ LegendRight // a column right of the plot80+ LegendNone // no legend81+)82+83+var chartLegendNames = [...]string{LegendBottom: "bottom", LegendRight: "right", LegendNone: "none"}84+85+// String is the position as files store it.86+func (l ChartLegend) String() string {87+ if l < 0 || int(l) >= len(chartLegendNames) {88+ return chartLegendNames[0]89+ }90+ return chartLegendNames[l]91+}92+93+// ParseChartLegend parses a position as String writes it.94+func ParseChartLegend(s string) (ChartLegend, bool) {95+ i := slices.Index(chartLegendNames[:], strings.ToLower(s))96+ return ChartLegend(max(i, 0)), i >= 097+}98+99+// axisEnd is an optional end of the value axis as files store it: nil100+// for automatic.101+func axisEnd(v float64, ok bool) *float64 {102+ if !ok || math.IsNaN(v) || math.IsInf(v, 0) {103+ return nil104+ }105+ return &v106+}
M internal/sheet/clipfmt.go +38 -1
@@ -12,7 +12,8 @@ import (
1212 // already show. Copying whole columns or rows and pasting them as whole1313 // lines (at the top of a column, the start of a row, or A1 for the whole1414 // sheet) carries the line formats themselves, and cutting them moves the15-// line formats, leaving the source lines plain.15+// line formats, leaving the source lines plain. Cutting a block leaves16+// its cells plain, over whatever lines cross it.1617 1718 // clipFormats is the formatting of a copied range, taken at copy time.1819 type clipFormats struct {
@@ -256,10 +257,46 @@ func (s *Sheet) moveFormats(from *Sheet, f *clipFormats, src, dst Rect) {
256257 for n := range f.rowFmt {257258 from.setLine(true, src.From.Row+n, lineFmt{})258259 }260+ case lineBlock:261+ from.plainSource(src, dst, from == s)259262 }260263 s.pasteFormats(f, p)261264 }262265 266+// plainSource leaves the cells a block was cut from showing plain, as267+// Sheets clears them: where a formatted row, column or the sheet crosses268+// src, its blank cells get plain formatting of their own, up to269+// materializeLimit cells looked at, except those in dst when the block270+// lands on the same sheet.271+func (s *Sheet) plainSource(src, dst Rect, same bool) {272+ var areas []Rect273+ if !s.lines.sheet.IsZero() {274+ areas = []Rect{src}275+ } else {276+ for n := range linesIn(s.lines.rows, src.From.Row, src.To.Row) {277+ row := src.From.Row + n278+ areas = append(areas, Rect{From: Addr{Col: src.From.Col, Row: row}, To: Addr{Col: src.To.Col, Row: row}})279+ }280+ for n := range linesIn(s.lines.cols, src.From.Col, src.To.Col) {281+ col := src.From.Col + n282+ areas = append(areas, Rect{From: Addr{Col: col, Row: src.From.Row}, To: Addr{Col: col, Row: src.To.Row}})283+ }284+ }285+ seen := 0286+ for _, r := range areas {287+ for row := r.From.Row; row <= r.To.Row; row++ {288+ for c := r.From.Col; c <= r.To.Col; c++ {289+ if seen++; seen > materializeLimit {290+ return291+ }292+ if a := (Addr{Col: c, Row: row}); !(same && dst.Contains(a)) && s.cells.get(a) == nil {293+ s.placeFormat(a, lineFmt{})294+ }295+ }296+ }297+ }298+}299+263300 // unionKeys are the keys of a and b, in order.264301 func unionKeys(a, b map[int]lineFmt) []int {265302 keys := slices.Collect(maps.Keys(a))
M internal/sheet/file.go +4 -109
@@ -72,113 +72,6 @@ type fileSheet struct {
7272 Validations []fileValidation `json:"validations,omitempty"`7373 }7474 75-// fileChart is a chart, one per line after the cells:76-//77-// {"type": "column", "data": "A1:C7", "at": "E2", "width": 44, "height": 14, "header": true, "labels": true}78-//79-// Older versions of 012 ignore the field and drop the charts.80-type fileChart struct {81- Type string `json:"type"`82- Data string `json:"data"`83- At string `json:"at"`84- Width int `json:"width"`85- Height int `json:"height"`86- ByRow bool `json:"byRow,omitempty"`87- Header bool `json:"header,omitempty"`88- Labels bool `json:"labels,omitempty"`89- Title string `json:"title,omitempty"`90-}91-92-func encodeChart(c Chart) ([]byte, error) {93- return json.Marshal(fileChart{94- Type: c.Type.String(), Data: c.Data.String(), At: c.At.String(), Width: c.W, Height: c.H,95- ByRow: c.ByRow, Header: c.Header, Labels: c.Labels, Title: c.Title,96- })97-}98-99-func decodeChart(fc fileChart) (Chart, error) {100- t, ok := ParseChartType(fc.Type)101- if !ok {102- return Chart{}, fmt.Errorf("unknown chart type %q", fc.Type)103- }104- data, ok := ParseRange(fc.Data)105- if !ok {106- return Chart{}, fmt.Errorf("invalid chart range %q", fc.Data)107- }108- at, ok := ParseAddr(fc.At)109- if !ok {110- return Chart{}, fmt.Errorf("invalid chart position %q", fc.At)111- }112- c := Chart{Type: t, Data: data, At: at, W: fc.Width, H: fc.Height,113- ByRow: fc.ByRow, Header: fc.Header, Labels: fc.Labels, Title: fc.Title}114- return c.clamped(), nil115-}116-117-type fileCell struct {118- Input string `json:"input,omitempty"`119- Format string `json:"format,omitempty"`120- Decimals *int `json:"decimals,omitempty"`121- Pattern string `json:"pattern,omitempty"`122- Bold bool `json:"bold,omitempty"`123- Italic bool `json:"italic,omitempty"`124- Underline bool `json:"underline,omitempty"`125- Strikethrough bool `json:"strikethrough,omitempty"`126- Align string `json:"align,omitempty"`127- Own bool `json:"own,omitempty"` // not taking its column's or row's formatting128-}129-130-func encodeCell(c *Cell) (json.RawMessage, error) {131- if c.Format.IsZero() && c.Style.IsZero() {132- return json.Marshal(c.Input)133- }134- fc := fileCell{135- Input: c.Input,136- Pattern: c.Format.Pattern,137- Bold: c.Style.Bold,138- Italic: c.Style.Italic,139- Underline: c.Style.Underline,140- Strikethrough: c.Style.Strikethrough,141- Align: c.Style.Align.String(),142- Own: c.Style.own,143- }144- if !c.Format.IsZero() {145- fc.Format = c.Format.Kind.String()146- }147- if c.Format.Kind.HasDecimals() {148- d := c.Format.Decimals149- fc.Decimals = &d150- }151- return json.Marshal(fc)152-}153-154-func decodeCell(raw json.RawMessage) (string, Format, Style, error) {155- var input string156- if err := json.Unmarshal(raw, &input); err == nil {157- return input, Format{}, Style{}, nil158- }159- var fc fileCell160- if err := json.Unmarshal(raw, &fc); err != nil {161- return "", Format{}, Style{}, err162- }163- var f Format164- if fc.Format != "" {165- k, ok := ParseFormatKind(fc.Format)166- if !ok {167- return "", Format{}, Style{}, fmt.Errorf("unknown format %q", fc.Format)168- }169- f = Format{Kind: k, Pattern: fc.Pattern}170- if fc.Decimals != nil {171- f.Decimals = clampInt(*fc.Decimals, 0, MaxDecimals)172- }173- }174- al, ok := ParseAlign(fc.Align)175- if !ok {176- return "", Format{}, Style{}, fmt.Errorf("unknown alignment %q", fc.Align)177- }178- st := Style{Bold: fc.Bold, Italic: fc.Italic, Underline: fc.Underline, Strikethrough: fc.Strikethrough, Align: al, own: fc.Own}179- return fc.Input, f, st, nil180-}181-18275 // Write saves the workbook the sheet belongs to; see Workbook.Write.18376 func (s *Sheet) Write(w io.Writer) error { return s.wb.Write(w) }18477
@@ -472,7 +365,7 @@ func (s *Sheet) read(f fileSheet, version int) error {
472365 if !ok {473366 return fmt.Errorf("invalid cell %q", name)474367 }475- input, fm, st, err := decodeCell(raw)368+ input, fm, st, note, err := decodeNoted(raw)476369 if err != nil {477370 return fmt.Errorf("%s: %w", name, err)478371 }
@@ -482,7 +375,9 @@ func (s *Sheet) read(f fileSheet, version int) error {
482375 if err != nil {483376 return fmt.Errorf("%s: %w", name, err)484377 }485- s.place(a, c)378+ if c = c.withNote(CleanNote(note)); c != nil {379+ s.place(a, c)380+ }486381 }487382 for i, fc := range f.Charts {488383 c, err := decodeChart(fc)
M internal/sheet/insertbook.go +31 -19
@@ -75,8 +75,9 @@ func (w *Workbook) InsertBook(src *Workbook, at int, label string) (ImportResult
7575 // ReplaceSheet puts s, the sheet of an imported workbook, in the place of7676 // dst, as one undo step labelled label: it takes dst's name and position,7777 // so formulas reading dst read it, and named ranges on dst move to it.78-// dst's charts stay, drawing s's data (see keepCharts), and it returns79-// what became of each. dst's cells and the rest go; undo brings them back.78+// dst's charts that fit s's data stay, drawing it, and the rest go (see79+// keepCharts); it returns what became of each. dst's cells and the rest80+// go; undo brings them back, charts too.8081 func (w *Workbook) ReplaceSheet(dst, s *Sheet, label string) ([]ChartFate, error) {8182 switch {8283 case !dst.live || dst.wb != w:
@@ -108,15 +109,20 @@ func (w *Workbook) ReplaceSheet(dst, s *Sheet, label string) ([]ChartFate, error
108109 type ChartFate struct {109110 Name string // the chart's title, or Chart 1, Chart 2 by position110111 Was, Now Rect // the range it drew, and draws111- Empty bool // the new data has nothing in Now112+ // Removed is set when the range no longer fits the new data: it113+ // holds nothing now, or it was a whole table and the new sheet's114+ // table there has other series.115+ Removed bool112116 }113117 114-// keepCharts gives s, replacing old, old's charts. A chart that drew a115-// whole block of old's data is re-pointed to the block s has at the same116-// corner when it has as many series (columns, or rows for a chart by117-// row), so a chart of last month's table draws this month's, however118-// many rows it has. Any other chart keeps its range, which may now hold119-// something else or nothing; ChartFate says which.118+// keepCharts gives s, replacing old, old's charts that fit its data. A119+// chart that drew a whole block of old's data is re-pointed to the block120+// s has at the same corner when it has as many series (columns, or rows121+// for a chart by row), so a chart of last month's table draws this122+// month's, however many rows it has; when s's block there has other123+// series the chart goes. A chart of whole columns or rows, or of part of124+// a block, keeps its range. A chart whose range holds nothing now goes125+// too. ChartFate says which.120126 func (s *Sheet) keepCharts(old *Sheet) []ChartFate {121127 fates := make([]ChartFate, 0, len(old.charts))122128 for i, c := range old.charts {
@@ -125,30 +131,36 @@ func (s *Sheet) keepCharts(old *Sheet) []ChartFate {
125131 name = fmt.Sprintf("Chart %d", i+1)126132 }127133 was := c.Data128- c.Data = s.repoint(old, was, c.ByRow)129- _, filled := s.FilledBounds(c.Data)130- fates = append(fates, ChartFate{Name: name, Was: was, Now: c.Data, Empty: !filled})131- s.charts = append(s.charts, c)134+ now, fits := s.repoint(old, was, c.ByRow)135+ _, filled := s.FilledBounds(now)136+ f := ChartFate{Name: name, Was: was, Now: now, Removed: !fits || !filled}137+ fates = append(fates, f)138+ if !f.Removed {139+ c.Data = now140+ s.charts = append(s.charts, c)141+ }132142 }133143 return fates134144 }135145 136146 // repoint is the range on s a chart drawing d on old draws: s's block of137147 // data at d's corner when d was old's whole block there and the two have138-// as many series, else d. Whole columns or rows read any data as is.139-func (s *Sheet) repoint(old *Sheet, d Rect, byRow bool) Rect {148+// as many series, else d. Whole columns or rows, and part of a block,149+// read any data as is. It reports false when d was a whole block and s150+// has none like it there.151+func (s *Sheet) repoint(old *Sheet, d Rect, byRow bool) (Rect, bool) {140152 if d.AllRows() || d.AllCols() || old.Region(d.From) != d {141- return d153+ return d, true142154 }143155 now := s.Region(d.From)144156 if now.From != d.From || s.cells.get(now.From).Blank() && now.From == now.To {145- return d157+ return d, false146158 }147159 if byRow && now.To.Row-now.From.Row != d.To.Row-d.From.Row ||148160 !byRow && now.To.Col-now.From.Col != d.To.Col-d.From.Col {149- return d161+ return d, false150162 }151- return now163+ return now, true152164 }153165 154166 // freeIn returns base, or base with a number added ("Sales 2"), so that
M internal/sheet/insertbook_test.go +11 -7
@@ -112,9 +112,11 @@ func namesOf(w *Workbook) string {
112112 // hidePlain hides s outside the history, as a loader would.113113 func (w *Workbook) hidePlain(s *Sheet) { s.tabHidden = true }114114 115-// Replacing a sheet keeps its charts: one that drew the whole table is116-// re-pointed to the new table when it has as many columns, and the rest117-// keep their ranges, whatever they now hold.115+// Replacing a sheet keeps the charts that fit the new data: one that116+// drew the whole table is re-pointed to the new table when it has as117+// many columns and removed when it hasn't; one of part of a table keeps118+// its range; one whose range is empty now is removed. Undo brings the119+// removed ones back.118120 func TestReplaceSheetKeepsCharts(t *testing.T) {119121 w := bookOf(t, page{"Sales", map[string]string{120122 "A1": "Month", "B1": "Total", "A2": "Jan", "B2": "1", "A3": "Feb", "B3": "2",
@@ -124,6 +126,7 @@ func TestReplaceSheetKeepsCharts(t *testing.T) {
124126 old.AddChart(Chart{Data: NewRect(at("A1"), at("B3")), Title: "Totals"})125127 old.AddChart(Chart{Data: NewRect(at("E1"), at("E2"))})126128 old.AddChart(Chart{Data: NewRect(at("H1"), at("H1"))})129+ old.AddChart(Chart{Data: NewRect(at("B2"), at("B3")), Title: "Part"})127130 w.ClearHistory()128131 src := bookOf(t, page{"sales", map[string]string{129132 "A1": "Month", "B1": "Total", "A2": "Jan", "B2": "4", "A3": "Feb", "B3": "5", "A4": "Mar", "B4": "6",
@@ -135,8 +138,9 @@ func TestReplaceSheetKeepsCharts(t *testing.T) {
135138 }136139 want := []ChartFate{137140 {Name: "Totals", Was: NewRect(at("A1"), at("B3")), Now: NewRect(at("A1"), at("B4"))},138- {Name: "Chart 2", Was: NewRect(at("E1"), at("E2")), Now: NewRect(at("E1"), at("E2"))}, // a column more: kept139- {Name: "Chart 3", Was: NewRect(at("H1"), at("H1")), Now: NewRect(at("H1"), at("H1")), Empty: true},141+ {Name: "Chart 2", Was: NewRect(at("E1"), at("E2")), Now: NewRect(at("E1"), at("E2")), Removed: true}, // a column more142+ {Name: "Chart 3", Was: NewRect(at("H1"), at("H1")), Now: NewRect(at("H1"), at("H1")), Removed: true}, // empty143+ {Name: "Part", Was: NewRect(at("B2"), at("B3")), Now: NewRect(at("B2"), at("B3"))},140144 }141145 if len(fates) != len(want) {142146 t.Fatalf("fates %+v", fates)
@@ -147,11 +151,11 @@ func TestReplaceSheetKeepsCharts(t *testing.T) {
147151 }148152 }149153 charts := w.Sheet(0).Charts()150- if len(charts) != 3 || charts[0].Data != want[0].Now || charts[0].Title != "Totals" {154+ if len(charts) != 2 || charts[0].Data != want[0].Now || charts[0].Title != "Totals" || charts[1].Title != "Part" {151155 t.Errorf("charts on the new sheet: %+v", charts)152156 }153157 w.Undo()154- if w.Sheet(0) != old || len(old.Charts()) != 3 || old.Charts()[0].Data != want[0].Was {158+ if w.Sheet(0) != old || len(old.Charts()) != 4 || old.Charts()[0].Data != want[0].Was {155159 t.Errorf("undo: charts %+v", old.Charts())156160 }157161 }
M internal/sheet/linecopy_test.go +30 -3
@@ -160,13 +160,40 @@ func TestMoveCarriesFormats(t *testing.T) {
160160 t.Errorf("Other!C1 shows %v %v; B1 %v", o.DisplayFormat(at("C1")).Kind, o.Value(at("C1")), s.DisplayFormat(at("B1")).Kind)161161 }162162 163- // A block keeps its formats; the source falls back on its lines.163+ // A block keeps its formats; the source is left plain, as Sheets164+ // clears cut cells, though its column stays currency.164165 o.Set(at("C2"), "7")165166 if _, err := o.MoveTo(s, Rect{From: at("C1"), To: at("C2")}, at("E4")); err != nil {166167 t.Fatal(err)167168 }168- if s.DisplayFormat(at("E5")).Kind != FmtCurrency || o.DisplayFormat(at("C1")).Kind != FmtCurrency {169- t.Errorf("E5 shows %v, Other!C1 %v", s.DisplayFormat(at("E5")).Kind, o.DisplayFormat(at("C1")).Kind)169+ if s.DisplayFormat(at("E5")).Kind != FmtCurrency || !o.DisplayFormat(at("C1")).IsZero() || o.DisplayFormat(at("C3")).Kind != FmtCurrency {170+ t.Errorf("E5 shows %v, Other!C1 %v, Other!C3 %v", s.DisplayFormat(at("E5")).Kind, o.DisplayFormat(at("C1")).Kind, o.DisplayFormat(at("C3")).Kind)171+ }172+}173+174+// Cutting a block on its sheet leaves the cells it left plain, over175+// formatted columns and rows, except where it lands; undo restores them.176+func TestMoveBlockLeavesSourcePlain(t *testing.T) {177+ s := New()178+ s.SetFormat(col(1), Preset(FmtCurrency))179+ s.SetStyle(rowRect(1, 1), bold) // row 2180+ s.Set(at("B1"), "5")181+ s.Set(at("B2"), "6")182+ s.Set(at("C3"), "7")183+ if _, err := s.Move(Rect{From: at("B1"), To: at("C3")}, at("C2")); err != nil {184+ t.Fatal(err)185+ }186+ for _, a := range []string{"B1", "B2", "B3", "C1"} {187+ if !s.DisplayFormat(at(a)).IsZero() || s.CellStyle(at(a)).Bold {188+ t.Errorf("%s shows %v, bold %v, want plain", a, s.DisplayFormat(at(a)).Kind, s.CellStyle(at(a)).Bold)189+ }190+ }191+ if s.DisplayFormat(at("C2")).Kind != FmtCurrency || s.DisplayFormat(at("B4")).Kind != FmtCurrency || !s.CellStyle(at("A2")).Bold {192+ t.Errorf("C2 %v (moved from B1), B4 %v, A2 bold %v", s.DisplayFormat(at("C2")).Kind, s.DisplayFormat(at("B4")).Kind, s.CellStyle(at("A2")).Bold)193+ }194+ s.Undo()195+ if s.DisplayFormat(at("B3")).Kind != FmtCurrency || !s.CellStyle(at("B2")).Bold || s.cells.get(at("B3")) != nil {196+ t.Errorf("undo: B3 %v, B2 bold %v", s.DisplayFormat(at("B3")).Kind, s.CellStyle(at("B2")).Bold)170197 }171198 }172199
M internal/sheet/lines.go +3 -3
A internal/sheet/notes.go +112 -0
@@ -0,0 +1,112 @@
1+package sheet2+3+import (4+ "strings"5+ "unicode"6+)7+8+// Notes are Sheets' notes (Insert > Note): plain text kept on a cell,9+// shown when the cell is active or hovered. A note is part of its cell10+// (Cell.Note), so it moves with the cell when rows are inserted, deleted11+// or sorted, copies with it, stays when the contents are cleared, and12+// every change to one is an undo step like any other change to a cell.13+14+// MaxNote caps a note's length in bytes, so a file or a paste can't15+// make one without bound.16+const MaxNote = 409617+18+// Note returns the note on the cell at a, or "".19+func (s *Sheet) Note(a Addr) string {20+ if c := s.cells.get(a); c != nil {21+ return c.Note22+ }23+ return ""24+}25+26+// CleanNote tidies a note as SetNote stores it: control characters27+// other than line breaks dropped, trailing space trimmed, at most28+// MaxNote bytes.29+func CleanNote(text string) string {30+ text = strings.ReplaceAll(text, "\r\n", "\n")31+ text = strings.Map(func(r rune) rune {32+ if r == '\n' {33+ return r34+ }35+ if unicode.IsControl(r) {36+ return -137+ }38+ return r39+ }, text)40+ text = strings.TrimRightFunc(text, unicode.IsSpace)41+ if len(text) > MaxNote {42+ text = strings.ToValidUTF8(text[:MaxNote], "")43+ }44+ return text45+}46+47+// SetNote sets the note on the cell at a as one undo step; an empty note48+// deletes it. A pivot table's results can't take notes: ErrPivotEdit.49+func (s *Sheet) SetNote(a Addr, text string) error {50+ if s.InPivot(Rect{From: a, To: a}) {51+ return ErrPivotEdit52+ }53+ text = CleanNote(text)54+ if s.Note(a) == text {55+ return nil56+ }57+ label := "note on " + a.String()58+ if text == "" {59+ label = "delete note on " + a.String()60+ }61+ s.change(label, Rect{From: a, To: a}, func() { s.putNote(a, text) })62+ return nil63+}64+65+// putNote stores a note without recording a step of its own.66+func (s *Sheet) putNote(a Addr, text string) {67+ old := s.cells.get(a)68+ c := old.clone()69+ if c == nil {70+ c = &Cell{}71+ }72+ c.Note = text73+ if c.Blank() && c.Format.IsZero() && c.Style.IsZero() && text == "" {74+ c = nil75+ }76+ s.place(a, c)77+}78+79+// LoadNote sets a note as a loader does: without recording undo.80+func (s *Sheet) LoadNote(a Addr, text string) {81+ if text = CleanNote(text); a.Valid() && text != "" {82+ s.putNote(a, text)83+ }84+}85+86+// NotesIn returns the cells in r that have notes, in row-major order.87+// It walks the cells the sheet holds, not r's addresses.88+func (s *Sheet) NotesIn(r Rect) []Addr {89+ var out []Addr90+ for a := range s.cells.anyInRange(r) {91+ if s.cells.get(a).Note != "" {92+ out = append(out, a)93+ }94+ }95+ sortAddrs(out)96+ return out97+}98+99+// ClearNotes deletes the notes in r as one undo step and returns how many100+// there were.101+func (s *Sheet) ClearNotes(r Rect) int {102+ notes := s.NotesIn(r)103+ if len(notes) == 0 {104+ return 0105+ }106+ s.change("delete notes in "+r.String(), r, func() {107+ for _, a := range notes {108+ s.putNote(a, "")109+ }110+ })111+ return len(notes)112+}
A internal/sheet/notes_test.go +104 -0
@@ -0,0 +1,104 @@
1+package sheet2+3+import (4+ "bytes"5+ "strings"6+ "testing"7+)8+9+func TestNotes(t *testing.T) {10+ s := New()11+ s.Set(at("B2"), "10")12+ if err := s.SetNote(at("B2"), "Checked\r\nwith \x1bthe bank "); err != nil {13+ t.Fatal(err)14+ }15+ if got := s.Note(at("B2")); got != "Checked\nwith the bank" {16+ t.Errorf("note %q", got)17+ }18+ // Typing over the cell and clearing it keep the note, as in Sheets.19+ s.Set(at("B2"), "12")20+ s.EraseRange(rng("A1:C3"))21+ if got := s.Note(at("B2")); got != "Checked\nwith the bank" {22+ t.Errorf("after clearing: note %q", got)23+ }24+ // A note on a blank cell is a cell of its own, without contents.25+ s.SetNote(at("D5"), "Later")26+ if c := s.Cell(at("D5")); c == nil || !c.Blank() || c.Note != "Later" {27+ t.Errorf("blank noted cell %+v", c)28+ }29+ // Notes move with their rows.30+ s.InsertRows(0, 2)31+ if s.Note(at("B4")) == "" || s.Note(at("D7")) != "Later" {32+ t.Errorf("after inserting rows: %q %q", s.Note(at("B4")), s.Note(at("D7")))33+ }34+ if got := s.NotesIn(rng("A1:Z99")); len(got) != 2 || got[0] != at("B4") {35+ t.Errorf("NotesIn %v", got)36+ }37+ // Undo steps back one change at a time.38+ s.Undo()39+ if s.Note(at("D5")) != "Later" {40+ t.Errorf("undo insert: %q", s.Note(at("D5")))41+ }42+ s.Undo()43+ if s.Cell(at("D5")) != nil {44+ t.Errorf("undo note: %+v", s.Cell(at("D5")))45+ }46+ // Deleting the last note of a blank, plain cell removes the cell.47+ if n := s.ClearNotes(rng("A1:C3")); n != 1 || s.Cell(at("B2")) != nil {48+ t.Errorf("ClearNotes = %d, cell %+v", n, s.Cell(at("B2")))49+ }50+}51+52+func TestNotesCopyAndFill(t *testing.T) {53+ s := New()54+ s.Set(at("A1"), "1")55+ s.SetNote(at("A1"), "Source")56+ s.SetNote(at("B1"), "Mine")57+ if _, err := s.Paste(s.Copy(rng("A1")), rng("A2"), false); err != nil {58+ t.Fatal(err)59+ }60+ if s.Note(at("A2")) != "Source" {61+ t.Errorf("pasted note %q", s.Note(at("A2")))62+ }63+ // Ctrl+Enter fills contents, each cell keeping its own note.64+ s.FillEntry(rng("A1:B1"), at("A1"), "7")65+ if s.Note(at("A1")) != "Source" || s.Note(at("B1")) != "Mine" || s.Value(at("B1")).Num != 7 {66+ t.Errorf("fill: %q %q %v", s.Note(at("A1")), s.Note(at("B1")), s.Value(at("B1")))67+ }68+}69+70+func TestNotesInPivotRefused(t *testing.T) {71+ s := New()72+ for a, v := range map[string]string{"A1": "Region", "B1": "Sales", "A2": "East", "B2": "5"} {73+ s.Set(at(a), v)74+ }75+ p, err := s.Book().CreatePivot(s, rng("A1:B2"), "", NewPivot(s, rng("A1:B2")))76+ if err != nil {77+ t.Fatal(err)78+ }79+ out, _ := p.PivotRange()80+ if err := p.SetNote(out.From, "x"); err != ErrPivotEdit {81+ t.Errorf("note on pivot results: %v", err)82+ }83+}84+85+func TestNotesFile(t *testing.T) {86+ s := New()87+ s.Set(at("A1"), "Rent")88+ s.SetNote(at("A1"), "Due on\nthe 1st")89+ s.SetNote(at("C3"), "Blank")90+ var b bytes.Buffer91+ if err := s.Write(&b); err != nil {92+ t.Fatal(err)93+ }94+ if !strings.Contains(b.String(), `"A1": {"input":"Rent","note":"Due on\nthe 1st"}`) {95+ t.Errorf("file:\n%s", b.String())96+ }97+ got, err := Read(&b)98+ if err != nil {99+ t.Fatal(err)100+ }101+ if got.Note(at("A1")) != "Due on\nthe 1st" || got.Note(at("C3")) != "Blank" || got.Value(at("A1")).Str != "Rent" {102+ t.Errorf("read back %q %q", got.Note(at("A1")), got.Note(at("C3")))103+ }104+}
M internal/sheet/ops.go +16 -1
M internal/sheet/pivot.go +2 -1
M internal/sheet/pivot_test.go +2 -2
M internal/sheet/pivotcalc.go +24 -80
@@ -154,27 +154,16 @@ type pivotNode struct {
154154 acc map[int][]accum155155 }156156 157-// colGroup is one combination of column fields' values.158-type colGroup struct {159- id int160- labels []Value161- formats []Format162-}163-164-// colNode finds column groups by each column field's value in turn.165-type colNode struct {166- kids map[groupKey]*colNode167- group *colGroup168-}169-170157 // pivotCalc is a pivot being computed.171158 type pivotCalc struct {172159 w *Workbook173160 p *Pivot174161 src *Sheet175162 root *pivotNode176- cols []*colGroup // sorted once gathered163+ cols []*colGroup // the column groups, sorted once gathered177164 colRoot colNode165+ nextCol int // the next column group's id166+ colIDs []int // colGroupsOf's answer, reused178167 unique []bool // by value: whether to gather distinct values179168 formats []Format // by value: the format results show in180169 records int // source rows summarized
@@ -215,12 +204,12 @@ func (c *pivotCalc) gather() {
215204 continue216205 }217206 c.records++218- col := c.colGroupOf(row)207+ ids := c.colGroupsOf(row)219208 n := c.root220- c.add(n, col, row)209+ c.add(n, ids, row)221210 for _, g := range c.p.Rows {222211 n = n.child(c.src, Addr{Col: g.Col, Row: row})223- c.add(n, col, row)212+ c.add(n, ids, row)224213 }225214 }226215 c.sortRows(c.root, 0)
@@ -273,58 +262,30 @@ func (n *pivotNode) child(src *Sheet, a Addr) *pivotNode {
273262 return kid274263 }275264 276-// colGroupOf returns the id of row's column group, or -1 without column277-// fields.278-func (c *pivotCalc) colGroupOf(row int) int {279- if len(c.p.Columns) == 0 || len(c.p.Values) == 0 {280- return -1281- }282- n := &c.colRoot283- for _, g := range c.p.Columns {284- k := keyOf(c.src.Value(Addr{Col: g.Col, Row: row}), true)285- kid := n.kids[k]286- if kid == nil {287- if n.kids == nil {288- n.kids = map[groupKey]*colNode{}289- }290- kid = &colNode{}291- n.kids[k] = kid292- }293- n = kid294- }295- if n.group == nil {296- cg := &colGroup{id: len(c.cols)}297- for _, g := range c.p.Columns {298- a := Addr{Col: g.Col, Row: row}299- cg.labels = append(cg.labels, c.src.Value(a))300- cg.formats = append(cg.formats, c.src.DisplayFormat(a))301- }302- n.group = cg303- c.cols = append(c.cols, cg)304- }305- return n.group.id306-}307-308-// add gathers row's values into n, for its column group and for all.309-func (c *pivotCalc) add(n *pivotNode, col, row int) {265+// add gathers row's values into n: for all column groups, and for each266+// of ids, the column groups the row counts in.267+func (c *pivotCalc) add(n *pivotNode, ids []int, row int) {310268 if len(c.p.Values) == 0 {311269 return312270 }313271 if n.acc == nil {314272 n.acc = map[int][]accum{}315273 }316- for _, id := range [2]int{-1, col} {317- accs := n.acc[id]318- if accs == nil {319- accs = make([]accum, len(c.p.Values))320- n.acc[id] = accs321- }322- for i, v := range c.p.Values {323- accs[i].add(c.src.Value(Addr{Col: v.Col, Row: row}), c.unique[i])324- }325- if col == -1 {326- return327- }274+ c.addTo(n, -1, row)275+ for _, id := range ids {276+ c.addTo(n, id, row)277+ }278+}279+280+// addTo gathers row's values into n's accumulators for column group id.281+func (c *pivotCalc) addTo(n *pivotNode, id, row int) {282+ accs := n.acc[id]283+ if accs == nil {284+ accs = make([]accum, len(c.p.Values))285+ n.acc[id] = accs286+ }287+ for i, v := range c.p.Values {288+ accs[i].add(c.src.Value(Addr{Col: v.Col, Row: row}), c.unique[i])328289 }329290 }330291
@@ -356,23 +317,6 @@ func (c *pivotCalc) sortRows(n *pivotNode, depth int) {
356317 }357318 }358319 359-// sortCols orders the column groups by each column field in turn.360-func (c *pivotCalc) sortCols() {361- slices.SortStableFunc(c.cols, func(a, b *colGroup) int {362- for k, g := range c.p.Columns {363- d := c.order(g, a.labels[k:k+1], b.labels[k:k+1], func(i int) (Value, Value) {364- va, _ := c.result(c.root, a.id, i)365- vb, _ := c.result(c.root, b.id, i)366- return va, vb367- })368- if d != 0 {369- return d370- }371- }372- return 0373- })374-}375-376320 // order compares two groups by a field's order: by label, as Sheets sorts377321 // (blanks last either way), or by a value's total, labels breaking ties.378322 func (c *pivotCalc) order(g PivotGroup, a, b []Value, totals func(i int) (Value, Value)) int {
A internal/sheet/pivotcols.go +126 -0
@@ -0,0 +1,126 @@
1+package sheet2+3+import "slices"4+5+// Column groups of a pivot: each combination of the column fields' values6+// is a group, and so is each combination of the first few fields' values,7+// an outer group. Outer groups order the columns when a field is sorted8+// by a value's total, and show as subtotal columns after their last9+// combination when column totals are on, as Sheets' totals of an outer10+// column field.11+12+// colGroup is a combination of the first len(labels) column fields'13+// values: all of them for a column of results, fewer for an outer group.14+type colGroup struct {15+ id int16+ labels []Value17+ formats []Format18+}19+20+// colNode finds column groups by each column field's value in turn.21+type colNode struct {22+ kids map[groupKey]*colNode23+ group *colGroup24+}25+26+// colGroupsOf returns the ids of the column groups row counts in, outer27+// groups first, or none without column fields.28+func (c *pivotCalc) colGroupsOf(row int) []int {29+ if len(c.p.Columns) == 0 || len(c.p.Values) == 0 {30+ return nil31+ }32+ c.colIDs = c.colIDs[:0]33+ n := &c.colRoot34+ for k, g := range c.p.Columns {35+ key := keyOf(c.src.Value(Addr{Col: g.Col, Row: row}), true)36+ kid := n.kids[key]37+ if kid == nil {38+ if n.kids == nil {39+ n.kids = map[groupKey]*colNode{}40+ }41+ kid = &colNode{group: c.newColGroup(row, k+1)}42+ n.kids[key] = kid43+ if k == len(c.p.Columns)-1 {44+ c.cols = append(c.cols, kid.group)45+ }46+ }47+ n = kid48+ c.colIDs = append(c.colIDs, n.group.id)49+ }50+ return c.colIDs51+}52+53+// newColGroup is the group of row's values in the first depth column54+// fields.55+func (c *pivotCalc) newColGroup(row, depth int) *colGroup {56+ cg := &colGroup{id: c.nextCol}57+ c.nextCol++58+ for _, g := range c.p.Columns[:depth] {59+ a := Addr{Col: g.Col, Row: row}60+ cg.labels = append(cg.labels, c.src.Value(a))61+ cg.formats = append(cg.formats, c.src.DisplayFormat(a))62+ }63+ return cg64+}65+66+// outer returns the group of the first len(labels) column fields' values.67+func (c *pivotCalc) outer(labels []Value) *colGroup {68+ n := &c.colRoot69+ for _, v := range labels {70+ n = n.kids[keyOf(v, true)]71+ }72+ return n.group73+}74+75+// sortCols orders the column groups by each column field in turn; a field76+// sorted by a value's total compares the totals of its outer groups, so77+// each outer group's columns stay together.78+func (c *pivotCalc) sortCols() {79+ slices.SortStableFunc(c.cols, func(a, b *colGroup) int {80+ for k, g := range c.p.Columns {81+ d := c.order(g, a.labels[k:k+1], b.labels[k:k+1], func(i int) (Value, Value) {82+ va, _ := c.result(c.root, c.outer(a.labels[:k+1]).id, i)83+ vb, _ := c.result(c.root, c.outer(b.labels[:k+1]).id, i)84+ return va, vb85+ })86+ if d != 0 {87+ return d88+ }89+ }90+ return 091+ })92+}93+94+// subtotals reports whether outer column groups show as subtotal columns:95+// with column totals on and several column fields.96+func (c *pivotCalc) subtotals() bool {97+ return c.p.ColumnTotals && len(c.p.Columns) > 198+}99+100+// columnOrder lists the column groups as the results show them: each101+// combination in order, after each outer group's last one its subtotal102+// when subtotals show, innermost first.103+func (c *pivotCalc) columnOrder() []*colGroup {104+ if !c.subtotals() {105+ return slices.Clone(c.cols)106+ }107+ var out []*colGroup108+ for i, cg := range c.cols {109+ out = append(out, cg)110+ for d := len(c.p.Columns) - 1; d >= 1; d-- {111+ if i+1 < len(c.cols) && sameLabels(cg.labels[:d], c.cols[i+1].labels[:d]) {112+ break113+ }114+ out = append(out, c.outer(cg.labels[:d]))115+ }116+ }117+ return out118+}119+120+// totalName labels the total of a group, "East Total", as Sheets does.121+func totalName(v Value, f Format) string {122+ if v.Kind == Empty {123+ return "Total"124+ }125+ return FormatText(v, f) + " Total"126+}
A internal/sheet/pivotcols_test.go +76 -0
@@ -0,0 +1,76 @@
1+package sheet2+3+import (4+ "bytes"5+ "testing"6+)7+8+func TestPivotColumnSubtotals(t *testing.T) {9+ w := sales(t)10+ s := newPivot(t, w, func(p *Pivot) {11+ p.Columns = []PivotGroup{{Col: 0}, {Col: 1}}12+ p.Values = []PivotValue{{Col: 3, Summarize: SumBy, Name: "Units sold"}}13+ })14+ // A subtotal column after each region's products, named in the15+ // Region row; the value's own name heads every column.16+ checkGrid(t, s,17+ "Region|East||East Total|North|North Total|West||West Total|Grand Total",18+ "Product|Ink|Pens||Pens||Ink|Pens",19+ "|Units sold|Units sold|Units sold|Units sold|Units sold|Units sold|Units sold|Units sold|Units sold",20+ "Grand Total|6|10|16|0|0|4|3|7|23")21+ if c := s.Cell(at("D1")); c == nil || !c.Style.Bold {22+ t.Error("the subtotal's header isn't bold")23+ }24+ // Without column totals, neither subtotals nor the grand total show.25+ p, _ := s.Pivot()26+ p.ColumnTotals = false27+ s.SetPivot(p, "")28+ checkGrid(t, s,29+ "Region|East||North|West",30+ "Product|Ink|Pens|Pens|Ink|Pens",31+ "|Units sold|Units sold|Units sold|Units sold|Units sold",32+ "Grand Total|6|10|0|4|3")33+ // Sorting the outer field by its total keeps each region together.34+ p.Columns[0] = PivotGroup{Col: 0, Desc: true, SortBy: 1}35+ s.SetPivot(p, "")36+ checkGrid(t, s,37+ "Region|East||West||North",38+ "Product|Ink|Pens|Ink|Pens|Pens",39+ "|Units sold|Units sold|Units sold|Units sold|Units sold",40+ "Grand Total|6|10|4|3|0")41+}42+43+func TestPivotBlankGroupsAndNamesRoundTrip(t *testing.T) {44+ w := sales(t)45+ w.Lookup("Sales").Set(at("A3"), "")46+ s := newPivot(t, w, func(p *Pivot) {47+ p.Rows = []PivotGroup{{Col: 0}, {Col: 1}}48+ p.Columns = []PivotGroup{{Col: 0}, {Col: 1}}49+ p.Values = []PivotValue{{Col: 3, Summarize: SumBy, Name: "Units"}}50+ })51+ // Blank cells group under a blank label, as in Sheets; their total52+ // is just "Total".53+ grid := pivotGrid(s)54+ found := false55+ for _, row := range grid {56+ if len(row) >= 6 && row[:6] == "Total|" {57+ found = true58+ }59+ }60+ if !found {61+ t.Errorf("no Total row for the blank group:\n%v", grid)62+ }63+ var b bytes.Buffer64+ if err := w.Write(&b); err != nil {65+ t.Fatal(err)66+ }67+ back, err := ReadBook(&b)68+ if err != nil {69+ t.Fatal(err)70+ }71+ p, _ := back.Lookup(s.Name()).Pivot()72+ if p.Values[0].Name != "Units" || !p.ColumnTotals {73+ t.Errorf("read back %+v", p)74+ }75+ checkGrid(t, back.Lookup(s.Name()), grid...)76+}
M internal/sheet/pivotlayout.go +26 -20
@@ -12,7 +12,8 @@ import (
1212 // fields, their labels shown where they change, with a subtotal row after1313 // each outer group and a Grand Total row when row totals are on. Columns1414 // are the row labels, then each column group's values, then the Grand15-// Total column's.15+// Total column's, with a subtotal column after each outer column group16+// when column totals are on and there are several column fields.1617 1718 // pivotCell is one cell of a pivot's results.1819 type pivotCell struct {
@@ -32,16 +33,13 @@ type pivotLayout struct {
3233 over bool // something fell off the sheet3334 }3435 35-// blankLabel stands for a group of blank cells, as in Excel.36-const blankLabel = "(blank)"37-3836 func (c *pivotCalc) layout() *pivotLayout {3937 l := &pivotLayout{c: c, lc: max(len(c.p.Rows), 1)}4038 if len(c.p.Values) > 0 {4139 if len(c.p.Columns) == 0 {4240 l.cols = []*colGroup{nil}4341 } else {44- l.cols = slices.Clone(c.cols)42+ l.cols = c.columnOrder()4543 if c.p.ColumnTotals {4644 l.cols = append(l.cols, nil)4745 }
@@ -64,10 +62,10 @@ func (l *pivotLayout) text(col, row int, s string, bold bool) {
6462 l.put(col, row, Value{Kind: Text, Str: s}, Format{}, bold)6563 }6664 67-// label shows a group's value in its own format, or (blank).65+// label shows a group's value in its own format; a group of blank cells66+// has a blank label, as in Sheets.6867 func (l *pivotLayout) label(col, row int, v Value, f Format, bold bool) {6968 if v.Kind == Empty {70- l.text(col, row, blankLabel, bold)7169 return7270 }7371 l.put(col, row, v, f, bold)
@@ -81,14 +79,7 @@ func (l *pivotLayout) header() {
8179 l.text(l.lc-1, l.y, c.w.FieldName(*c.p, g.Col), true)8280 var prev *colGroup8381 for j, cg := range l.cols {84- x := l.lc + j*nv85- switch {86- case cg == nil && k == 0:87- l.text(x, l.y, "Grand Total", true)88- case cg == nil:89- case prev == nil || !sameLabels(prev.labels[:k+1], cg.labels[:k+1]):90- l.label(x, l.y, cg.labels[k], cg.formats[k], true)91- }82+ l.colHeader(l.lc+j*nv, k, prev, cg)9283 prev = cg9384 }9485 l.y++
@@ -105,6 +96,25 @@ func (l *pivotLayout) header() {
10596 l.y++10697 }10798 99+// colHeader labels column group cg, after prev, in the header row of100+// column field k at column x: the field's value where it changes, a101+// subtotal's name in its outer field's row, Grand Total in the first.102+func (l *pivotLayout) colHeader(x, k int, prev, cg *colGroup) {103+ switch n := len(l.c.p.Columns); {104+ case cg == nil && k == 0:105+ l.text(x, l.y, "Grand Total", true)106+ case cg == nil:107+ case len(cg.labels) < n:108+ if k == len(cg.labels)-1 {109+ l.text(x, l.y, totalName(cg.labels[k], cg.formats[k]), true)110+ }111+ case prev == nil || len(prev.labels) <= k || !sameLabels(prev.labels[:k+1], cg.labels[:k+1]):112+ // The outer group's spelling, the first seen, as rows show.113+ og := l.c.outer(cg.labels[:k+1])114+ l.label(x, l.y, og.labels[k], og.formats[k], true)115+ }116+}117+108118 func sameLabels(a, b []Value) bool {109119 for i := range a {110120 if keyOf(a[i], true) != keyOf(b[i], true) {
@@ -141,11 +151,7 @@ func (l *pivotLayout) rows(n *pivotNode, depth int) {
141151 }142152 l.rows(kid, depth+1)143153 if l.totals() {144- name := blankLabel145- if kid.label.Kind != Empty {146- name = FormatText(kid.label, kid.format)147- }148- l.total(kid, name+" Total", depth)154+ l.total(kid, totalName(kid.label, kid.format), depth)149155 }150156 }151157 }
A internal/sheet/protect.go +164 -0
@@ -0,0 +1,164 @@
1+package sheet2+3+import (4+ "fmt"5+ "slices"6+7+ "github.com/FelineStateMachine/012/internal/formula"8+)9+10+// Protected ranges and sheets follow Sheets' Data > Protect sheets and11+// ranges in its "Show a warning when editing this range" mode: nothing is12+// locked, but an edit that touches one asks first. The engine only keeps13+// them (saved with the sheet, following inserted and deleted rows and14+// columns, every change an undo step); the UI asks.15+16+// Protection is a protected range, or the whole sheet.17+type Protection struct {18+ Range Rect // the whole grid when Sheet is set19+ Sheet bool // the whole sheet is protected20+ Desc string // what the user wrote to describe it, may be ""21+}22+23+// Label names the protection as the UI shows it: its description, or its24+// range, or "the sheet".25+func (p Protection) Label() string {26+ switch {27+ case p.Desc != "":28+ return p.Desc29+ case p.Sheet:30+ return "the sheet"31+ }32+ return p.Range.String()33+}34+35+// Protections returns the sheet's protected ranges, in the order added.36+func (s *Sheet) Protections() []Protection { return slices.Clone(s.view.protected) }37+38+// Protecting returns the first protection r overlaps.39+func (s *Sheet) Protecting(r Rect) (Protection, bool) {40+ for _, p := range s.view.protected {41+ if p.Sheet || overlaps(p.Range, r) {42+ return p, true43+ }44+ }45+ return Protection{}, false46+}47+48+// Protect adds a protected range, or protects the whole sheet, as one49+// undo step. Protecting the sheet again replaces its description.50+func (s *Sheet) Protect(p Protection) {51+ if p.Sheet {52+ p.Range = Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}}53+ }54+ next := slices.Clone(s.view.protected)55+ label := "protect " + p.Range.String()56+ if p.Sheet {57+ label = "protect sheet " + s.name58+ next = slices.DeleteFunc(next, func(q Protection) bool { return q.Sheet })59+ }60+ s.setProtected(label, p.Range, append(next, p))61+}62+63+// Unprotect removes protection i (in Protections' order).64+func (s *Sheet) Unprotect(i int) {65+ if i < 0 || i >= len(s.view.protected) {66+ return67+ }68+ p := s.view.protected[i]69+ next := slices.Delete(slices.Clone(s.view.protected), i, i+1)70+ s.setProtected("remove protection of "+p.Label(), p.Range, next)71+}72+73+// UnprotectRange removes every protection r overlaps, the sheet's74+// included, as one undo step, and returns how many there were.75+func (s *Sheet) UnprotectRange(r Rect) int {76+ next := slices.DeleteFunc(slices.Clone(s.view.protected), func(p Protection) bool {77+ return p.Sheet || overlaps(p.Range, r)78+ })79+ n := len(s.view.protected) - len(next)80+ if n > 0 {81+ s.setProtected("remove protection", r, next)82+ }83+ return n84+}85+86+func (s *Sheet) setProtected(label string, focus Rect, next []Protection) {87+ if len(next) == 0 {88+ next = nil89+ }90+ s.change(label, focus, func() {91+ s.recordView()92+ s.view.protected = next93+ })94+}95+96+// LoadProtection adds a protection as a loader does: without recording97+// undo.98+func (s *Sheet) LoadProtection(p Protection) {99+ if p.Sheet {100+ p.Range = Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}}101+ }102+ s.view.protected = append(slices.Clone(s.view.protected), p)103+}104+105+// shiftProtected moves protected ranges with inserted or deleted rows or106+// columns, as a range reference moves; one whose cells are all deleted107+// goes.108+func shiftProtected(ps []Protection, rows bool, sp formula.Span) []Protection {109+ if len(ps) == 0 {110+ return ps111+ }112+ _, rng := formula.AxisMaps(rows, sp)113+ var out []Protection114+ for _, p := range ps {115+ if !p.Sheet {116+ r, ok := rng(p.Range)117+ if !ok {118+ continue119+ }120+ p.Range = r121+ }122+ out = append(out, p)123+ }124+ return out125+}126+127+// fileProtection is a protection in the file: {"range": "B2:C9",128+// "description": "Totals"}, or {"sheet": true}. Earlier builds ignore129+// the field, so it needs no version bump; they simply don't warn.130+type fileProtection struct {131+ Range string `json:"range,omitempty"`132+ Sheet bool `json:"sheet,omitempty"`133+ Desc string `json:"description,omitempty"`134+}135+136+func encodeProtections(ps []Protection) []fileProtection {137+ var out []fileProtection138+ for _, p := range ps {139+ fp := fileProtection{Sheet: p.Sheet, Desc: p.Desc}140+ if !p.Sheet {141+ fp.Range = p.Range.String()142+ }143+ out = append(out, fp)144+ }145+ return out146+}147+148+func decodeProtections(fps []fileProtection) ([]Protection, error) {149+ var out []Protection150+ for _, fp := range fps {151+ p := Protection{Sheet: fp.Sheet, Desc: fp.Desc}152+ if p.Sheet {153+ p.Range = Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}}154+ } else {155+ r, ok := ParseRange(fp.Range)156+ if !ok {157+ return nil, fmt.Errorf("invalid protected range %q", fp.Range)158+ }159+ p.Range = r160+ }161+ out = append(out, p)162+ }163+ return out, nil164+}
A internal/sheet/protect_test.go +73 -0

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/sheet.go +29 -4

Not shown: this page already shows 6000 lines of diff.

M internal/sheet/view.go +28 -12

Not shown: this page already shows 6000 lines of diff.

A internal/ui/chartaxes.go +97 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/chartaxes_test.go +133 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/charteditor.go +107 -27

Not shown: this page already shows 6000 lines of diff.

M internal/ui/charts.go +2 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/clipboard.go +17 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/commands.go +9 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/context.go +3 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/context_test.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/edit.go +6 -6

Not shown: this page already shows 6000 lines of diff.

M internal/ui/entry.go +12 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/fill.go +9 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/graphics.go +1 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/hosts.go +10 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/importplace.go +17 -11

Not shown: this page already shows 6000 lines of diff.

M internal/ui/importplace_test.go +9 -8

Not shown: this page already shows 6000 lines of diff.

M internal/ui/linecopy_test.go +26 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/menubar.go +2 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/model.go +5 -4

Not shown: this page already shows 6000 lines of diff.

A internal/ui/notes.go +139 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/notes_test.go +62 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/overlay.go +3 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/panel.go +3 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/pivot.go +17 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/pivot_test.go +38 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/pivotactions.go +30 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/pivoteditor.go +10 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/prompt.go +10 -2

Not shown: this page already shows 6000 lines of diff.

A internal/ui/protect.go +226 -0

Not shown: this page already shows 6000 lines of diff.

A internal/ui/protect_test.go +108 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/rows.go +3 -6

Not shown: this page already shows 6000 lines of diff.

M internal/ui/rules.go +8 -2

Not shown: this page already shows 6000 lines of diff.

M internal/ui/sort.go +3 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/theme/scheme.go +1 -1

Not shown: this page already shows 6000 lines of diff.

M internal/ui/theme/theme.go +6 -0

Not shown: this page already shows 6000 lines of diff.

M internal/ui/view.go +2 -0

Not shown: this page already shows 6000 lines of diff.