Skip to content

Commit 2146c82

Browse files
committed
Updates
1 parent 47c928c commit 2146c82

44 files changed

Lines changed: 2470 additions & 29 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

SKILLS/shiny-controls/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,10 +821,10 @@ Every control below is available on **both** MAUI and Blazor. The feature set (p
821821
The library contains:
822822
- **TableView**: A pure MAUI settings-style TableView with 14 cell types, cascading styles, sections, drag-sort reordering, and full MVVM/binding support
823823
- **DataGrid** (MAUI + Blazor): A feature-rich data grid modeled on MudBlazor. Blazor is a generic `DataGrid<TItem>` rendering an HTML `<table>` with child `PropertyColumn`/`TemplateColumn`; MAUI is a pure cross-platform composite (`shiny:DataGrid` + `DataGridColumn`/`DataGridTemplateColumn`, items as `object`) built on a `Grid` header + virtualized `CollectionView` (no native handlers). Sorting (single/multi), filtering (menu/row/toolbar), grouping + aggregates, single/multi selection w/ checkboxes, inline editing (cell/form), paging, virtualization, column resize/reorder, sticky header, `ServerData` delegate, density/striped/bordered/hover. See datagrid.md
824-
- **Spreadsheet** (MAUI + Blazor WASM): `SpreadsheetView` opens, renders and edits real `.xlsx` workbooks. A shared host-agnostic kernel (`Shiny.Controls.Office.Shared`) owns the OOXML package, a transactional undo stack and a formula engine (~80 functions, dependency-ordered recalculation, circular-reference detection); a shared SkiaSharp painter (`Shiny.Controls.Office.Skia`) draws the grid for both hosts, so MAUI and Blazor are literally the same renderer. Virtualized grid over 1,048,576 rows, frozen panes, merged cells, column/row resize, range selection, in-cell editing via a native `Entry`/`<input>`. Edits are surgical: an unmodified workbook saves byte-identical, and macros, tracked changes, pivot caches and custom XML survive untouched. MAUI requires `UseShinyOffice()` (SkiaSharp plus, on `net10.0-macos`, the AppKit canvas SkiaSharp does not ship); Blazor is WASM-only. Row/column insert-delete is deliberately not implemented. See spreadsheet.md
824+
- **Spreadsheet** (MAUI + Blazor WASM): `SpreadsheetView` opens, renders and edits real `.xlsx` workbooks. A shared host-agnostic kernel (`Shiny.Controls.Office.Shared`) owns the OOXML package, a transactional undo stack and a formula engine (~80 functions, dependency-ordered recalculation, circular-reference detection); a shared SkiaSharp painter (`Shiny.Controls.Office.Skia`) draws the grid for both hosts, so MAUI and Blazor are literally the same renderer. Virtualized grid over 1,048,576 rows, frozen panes, merged cells, column/row resize, range selection, in-cell editing via a native `Entry`/`<input>`. Edits are surgical: an unmodified workbook saves byte-identical, and macros, tracked changes, pivot caches and custom XML survive untouched. MAUI requires `UseShinyOffice()` (SkiaSharp plus, on `net10.0-macos`, the AppKit canvas SkiaSharp does not ship); Blazor is WASM-only. Row/column insert-delete is deliberately not implemented. **Find** is on the Home tab of all three Office toolbars — a box, a `3/12` readout and previous/next arrows over a shared `IFindController`; the spreadsheet searches cell text as the formula bar shows it (formula first, then literal), the active sheet only unless `SearchAllSheets` is set. See spreadsheet.md
825825
- **Document & Slide Viewers** (MAUI + Blazor WASM): `DocumentView` renders `.docx`, `SlideView` renders `.pptx`, both **read-only** and both in the same packages as the spreadsheet. Word **reflows** to the control's width rather than paginating — no pages, headers or footers by design — with the full style chain resolved (doc defaults → named style with its whole `basedOn` ancestry → direct formatting), lists numbered from `numbering.xml`, tables with spans/merges/shading, inline images, plus an outline for navigation. PowerPoint **scales** its fixed-size artboards rather than reflowing, with shapes resolved through slide → layout → master (a title placeholder usually carries text and nothing else), ~20 preset geometries, gradient fills, theme colours with lumMod/lumOff/shade/tint applied, per-level text styles, notes, and a thumbnail-grid mode. Both preserve the package untouched and report what they could not draw via `UnsupportedFeatureCollector`. See document-viewer.md
826-
- **Document Editor** (MAUI + Blazor WASM): two controls — `DocumentEditor` is the lone editing surface, `DocumentEditorView` wraps it in formatting chrome. Edits are surgical on the OOXML runs (runs are split only at the boundaries an edit needs and never rebuilt, so language, proofing state and revision marks survive), with a transactional undo stack, caret and selection over the shared text layout, and typing/Enter/Backspace/formatting/alignment. Both toolbars are built from the same core pickers — FontPickerButton, FontSizePickerButton and ColorPickerButton exist on both hosts; only the bar around them differs (MAUI lays out a row of primitives because it has no toolbar control, Blazor composes ShinyToolbar with the item row as its own flex container, which is what keeps the items off the text baseline and aligned). Blazor keyboard input is complete (via `beforeinput`, so IME and dictation work); **MAUI has no portable key-down event**, so physical keys route through `HandleKey`. **Spell check** uses the platform's own dictionary on MAUI — UITextChecker, NSSpellChecker, Android text services, Windows `ISpellChecker` — registered automatically, with a red wavy underline and a right-click/long-press menu offering corrections, Ignore and Add to dictionary (which writes to the user's real dictionary); the browser exposes no such API so Blazor defaults to none and takes an app-supplied `ISpellChecker`, overridable per control or globally via `SpellCheckers.Default` on either host. See document-editor.md
827-
- **Slide Editor** (MAUI + Blazor WASM): two controls — `SlideEditor` is the lone editing surface, `SlideEditorView` wraps it in an editing toolbar. Two gestures carry the whole design: a single click selects a shape and draws a dashed frame with eight resize handles (drag body to move, handle to resize); a double-click puts a caret inside that shape's text and the frame turns solid. Typing is deliberately dropped while a shape is merely selected — `Controller.IsEditingText` gates it. Only shapes the slide itself owns are selectable; layout/master shapes and group children are skipped, because dragging one would move it across every slide using that layout. Edits are surgical on the DrawingML runs, and `a:rPr` children are a *sequence* — out-of-order properties make PowerPoint call the file corrupt rather than repair it. The text layout inside a shape is shared with the painter so caret and glyphs cannot drift. `SetFontSize`/`CaretFormat.FontSize` are in points, not the model's pixels. Not implemented: soft line breaks, table-cell and grouped-shape editing, adding/reordering slides, rotation handles. See slide-editor.md
826+
- **Document Editor** (MAUI + Blazor WASM): two controls — `DocumentEditor` is the lone editing surface, `DocumentEditorView` wraps it in formatting chrome. Edits are surgical on the OOXML runs (runs are split only at the boundaries an edit needs and never rebuilt, so language, proofing state and revision marks survive), with a transactional undo stack, caret and selection over the shared text layout, and typing/Enter/Backspace/formatting/alignment. Both toolbars are built from the same core pickers — FontPickerButton, FontSizePickerButton and ColorPickerButton exist on both hosts; only the bar around them differs (MAUI lays out a row of primitives because it has no toolbar control, Blazor composes ShinyToolbar with the item row as its own flex container, which is what keeps the items off the text baseline and aligned). Blazor keyboard input is complete (via `beforeinput`, so IME and dictation work); **MAUI has no portable key-down event**, so physical keys route through `HandleKey`. **Spell check** uses the platform's own dictionary on MAUI — UITextChecker, NSSpellChecker, Android text services, Windows `ISpellChecker` — registered automatically, with a red wavy underline and a right-click/long-press menu offering corrections, Ignore and Add to dictionary (which writes to the user's real dictionary); the browser exposes no such API so Blazor defaults to none and takes an app-supplied `ISpellChecker`, overridable per control or globally via `SpellCheckers.Default` on either host. **Find** is on the Home tab — a box, a `3/12` readout and previous/next arrows (`OfficeFindBar` over `IFindController`, shared with the slide and spreadsheet editors). Setting `Find.Query` searches and steps onto the first hit at or after the caret and selects it; next/previous wrap; paragraphs only, because a caret position is a block and an offset and a table cell has neither. See document-editor.md
827+
- **Slide Editor** (MAUI + Blazor WASM): two controls — `SlideEditor` is the lone editing surface, `SlideEditorView` wraps it in an editing toolbar. Two gestures carry the whole design: a single click selects a shape and draws a dashed frame with eight resize handles (drag body to move, handle to resize); a double-click puts a caret inside that shape's text and the frame turns solid. Typing is deliberately dropped while a shape is merely selected — `Controller.IsEditingText` gates it. Only shapes the slide itself owns are selectable; layout/master shapes and group children are skipped, because dragging one would move it across every slide using that layout. Edits are surgical on the DrawingML runs, and `a:rPr` children are a *sequence* — out-of-order properties make PowerPoint call the file corrupt rather than repair it. The text layout inside a shape is shared with the painter so caret and glyphs cannot drift. `SetFontSize`/`CaretFormat.FontSize` are in points, not the model's pixels. **Find** is on the Home tab, over the same shared `IFindController`: a deck search spans every slide, opens the one it lands on, selects the shape and selects the matched word, and searches only `IsEditable` shapes — a hit in a layout or master shape would count the company name once per slide. Not implemented: soft line breaks, table-cell and grouped-shape editing, adding/reordering slides, rotation handles, replace. See slide-editor.md
828828
- **TreeView**: Hierarchical tree with lazy-loaded branches (`ChildrenLoader` for per-node async, `RootLoader` for async root), `ChildrenSelector` for sync data, `HasChildrenSelector`/`CanExpandSelector`/`CanSelectSelector` predicates, configurable `ExpandedIcon`/`CollapsedIcon`/`RetryIcon` (ImageSource on MAUI, RenderFragment slots on Blazor), single/multi selection with two-way `SelectedItem`/`SelectedItems` (multi-select renders a checkbox per row — `ShowSelectionCheckBoxes` to turn it off — and switching modes clears the selection), events + ICommand mirrors for `ItemSelected`/`ItemExpanded`/`ItemCollapsed`/`LoadFailed`/`ItemDropped`, indent + guide lines, drag/drop reorder with above/below/into drop positions and visual drop indicators (event-only — never mutates your data; native HTML5 drag via JS interop on Blazor for Safari/Firefox support, pan-gesture fallback on Catalyst/AppKit/GTK4), programmatic API (`ExpandAll`/`ExpandAllAsync` — both materialize every sync branch and cap at a `maxDepth` of 32 — plus `CollapseAll`/`Expand`/`Collapse`/`SelectAll`/`DeselectAll`/`SetBranchSelected`/`Refresh`/`ReloadAsync` with state preservation/`FindNode`), and keyboard navigation on Blazor
829829
- **FloatingPanel + OverlayHost**: A floating panel overlay system (MAUI only). Panels slide from bottom or top with configurable detents, header peek when closed, backdrop dimming, and feedback. Multiple panels coexist without blocking touches. Use with `OverlayHost` (manual Grid setup) or `ShinyContentPage` (convenience ContentPage with built-in overlay). Blazor uses `SheetView` with CSS-based overlays instead
830830
- **Expander & Accordion** (MAUI + Blazor): A disclosure panel — header plus content that animates in beneath or above it — and the accordion list that coordinates a stack of them. `Animation` is a flags enum (`Fade`/`Slide`/`Height`) so the effects combine; `SlideFrom` aims the slide at any of the four edges and `ExpandDirection` opens the panel down or up. Two-way `IsExpanded`, cancelable `Expanding`/`Collapsing`, `LoadContentOnDemand`, a rotating or swapping indicator on either edge, and full border/radius/fill/padding/separator control that falls back to the theme. `Accordion` adds `SelectionMode` Single/Multiple, `AllowCollapseAll`, a two-way `ExpandedIndex`, `ExpandAll`/`CollapseAll`, `ItemsSource` with header/content templates (MAUI) or a plain `@foreach` (Blazor), and motion/chrome defaults that reach every item that did not set them itself. On MAUI `Height` measures and animates a clipped panel; on Blazor it is a CSS `grid-template-rows` transition with no JS. See expander.md

SKILLS/shiny-controls/document-editor.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,59 @@ Editor.HandleKey(EditorKey.Undo, control: true);
328328
A desktop host adds its own platform hook (`NSEvent` on macOS, `KeyDown` on Windows) and calls that.
329329
Tapping, selection, typing and every toolbar command work without it.
330330

331+
## Find
332+
333+
**Home ▸ Find** on all three Office toolbars: a box, a `3/12` readout and previous/next arrows. One
334+
component per host — `OfficeFindBar` — bound to an `IFindController`, which the document, slide and
335+
spreadsheet finders all implement.
336+
337+
```csharp
338+
var find = c.Find; // DocumentFinder : IFindController
339+
340+
find.Options = new FindOptions { MatchCase = true, WholeWord = true };
341+
find.Query = "revenue"; // searches and steps onto the first hit
342+
343+
find.Count; // how many
344+
find.ActiveIndex; // zero-based, -1 when none
345+
find.Status; // "1/4"; "0/0" for no hits; "" when not searching
346+
find.Matches; // IReadOnlyList<DocumentFindMatch>
347+
348+
find.FindNext(); // wraps at the end
349+
find.FindPrevious(); // wraps at the start
350+
find.Clear(); // drops the query, leaves the selection alone
351+
find.Changed += (_, _) => { }; // query, options or match list changed
352+
```
353+
354+
Rules that hold on all three hosts and all three editors:
355+
356+
- Setting `Query` **searches and steps onto the first hit at or after the caret** — not the top of the
357+
content. Do not follow it with a `FindNext()`; that skips to the second hit.
358+
- Stepping **selects** the match rather than landing beside it, and scrolls it into view.
359+
- Next and previous **wrap**. `FindNext()` returns false only when there are no matches at all.
360+
- Editing invalidates the match list but **never moves the view**.
361+
- Finding changes nothing, so leave it enabled in a read-only editor.
362+
363+
Every hit is washed amber by the painter; the one the selection covers is drawn as the selection
364+
instead, so the current match is the one that looks different.
365+
366+
`MatchCase` and `WholeWord` live on the controller, not on the bar. Whole-word uses
367+
`WordBoundaries.IsWordChar`, the same rule double-click selection uses, so `don` does not match
368+
`don't`. `TextSearch.Matches(text, query, options)` is public if you need the same matcher elsewhere.
369+
370+
**Only paragraphs are searched.** Table cells are not: a `DocumentPosition` is a block and an offset,
371+
and a table has neither — counting hits the arrows could never reach would be worse than not counting
372+
them.
373+
374+
Wiring the bar by hand (it is what the three built-in toolbars host):
375+
376+
```xml
377+
<office:OfficeFindBar Find="{Binding Controller.Find}" />
378+
```
379+
380+
```razor
381+
<OfficeFindBar Find="@editor?.Controller?.Find" Moved="StateHasChanged" />
382+
```
383+
331384
## Spell check
332385

333386
Turned on by default, and on MAUI the checker is the **platform's own**`UITextChecker` (iOS,
@@ -394,7 +447,7 @@ underlined.
394447
- **Floating (anchored) drawings.** They are read, and drawn in the text flow at the point they are
395448
anchored from rather than at their real position; the unsupported note says so. Nothing inserts one.
396449
- A shape's own text is drawn but has no caret — pass it at insert time.
397-
- Cut/copy/paste through the clipboard, find and replace.
450+
- Cut/copy/paste through the clipboard, and **replace** — find itself is implemented, see **Find**.
398451
- **Grammar** checking. Android reports grammar errors and they are deliberately ignored — only
399452
`LooksLikeTypo` is treated as an error, so the behaviour matches the other three platforms.
400453
- Inserting new paragraph styles.

SKILLS/shiny-controls/slide-editor.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,32 @@ Escape, Delete and Ctrl/Cmd+B/I/U/Z are wired.
177177
event. Route them with `editor.HandleKey(EditorKey.Left, shift: true)` from a platform hook;
178178
`EditorKey.Tab` carries the nesting.
179179

180+
## Find
181+
182+
**Home ▸ Find** — the same `OfficeFindBar` the document editor carries, over the same
183+
`IFindController`. See `document-editor.md`**Find** for the API and the rules it shares.
184+
185+
```csharp
186+
var find = c.Find; // SlideFinder : IFindController
187+
188+
find.Query = "roadmap"; // searches the whole deck and steps onto the first hit
189+
find.FindNext();
190+
find.Status; // "2/5"
191+
find.Matches; // IReadOnlyList<SlideFindMatch>
192+
```
193+
194+
Deck-specific behaviour:
195+
196+
- A search spans **every slide**, not the one being shown.
197+
- Stepping onto a match opens its slide, selects the shape, puts the caret in its text and selects the
198+
matched word. If the deck is in `SlideViewMode.Grid` it switches back to `Single` first — a
199+
thumbnail has no caret to move.
200+
- **Only `IsEditable` shapes are searched.** Layout and master shapes are template decoration shared
201+
by every slide using them; a hit inside one would count the company name once per slide and step the
202+
user into something they cannot select. Table cells and notes are out too — a `SlidePosition` is a
203+
shape, a paragraph and an offset, and neither has one.
204+
- `FindMatchRects()` returns highlights for the **showing slide only**, in viewport coordinates.
205+
180206
## Not implemented
181207

182208
- **Soft line breaks** (`a:br`) — read and rendered, but they contribute no characters to the offset

SKILLS/shiny-controls/spreadsheet.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,32 @@ controller.ClearSelection();
240240
controller.Undo();
241241
```
242242

243+
## Find
244+
245+
**Home ▸ Find** — the same `OfficeFindBar` the document editor carries, over the same
246+
`IFindController`. See `document-editor.md`**Find** for the API and the rules it shares.
247+
248+
```csharp
249+
var find = controller.Find; // SpreadsheetFinder : IFindController
250+
251+
find.SearchAllSheets = true; // off by default: the active sheet only, as in Excel
252+
find.Query = "Q1"; // searches and steps onto the first hit at or after the active cell
253+
find.FindNext(); // switches sheets when the hit is on another one
254+
find.Matches; // IReadOnlyList<SpreadsheetFindMatch> (Sheet name, CellRef, Start, Length)
255+
256+
controller.FindMatchCells(); // cells to wash on the showing sheet — what the painter takes
257+
```
258+
259+
Workbook-specific behaviour:
260+
261+
- What is searched is the cell text **as the formula bar shows it** — the formula when there is one,
262+
otherwise the literal. Not the formatted value: `1234` would miss a cell showing `1,234.00`.
263+
- Matches are collected in **book order**, never active-sheet-first — ordering around the showing
264+
sheet re-orders the list every time "next" crosses a boundary, which walks two sheets forever.
265+
- **Hidden sheets are never searched**, even with `SearchAllSheets`.
266+
- Stepping calls `GoTo`, so the cell is selected and scrolled into view; the wash covers **whole
267+
cells**, because a cell is the smallest thing a selection can address.
268+
243269
## Saving
244270

245271
```csharp
@@ -288,6 +314,7 @@ Do not generate code that assumes these exist:
288314
- Editing charts, pivot tables or conditional formatting.
289315
- Multi-range ("Ctrl-click") selection.
290316
- Copy/paste and the fill handle's drag-to-fill behaviour (the handle is drawn but inert).
317+
- **Replace.** Find is implemented (see **Find**); replacing what it finds is not.
291318
- Physical-key navigation on MAUI — MAUI has no portable key-down event, so arrow keys work on
292319
Blazor only. On MAUI, call `Move`/`BeginEdit`/`ClearSelection` from your own platform key hook.
293320

0 commit comments

Comments
 (0)