mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Per-chart export: copy/download spec and PNG/SVG from the preview header
This commit is contained in:
@@ -194,10 +194,13 @@ ephemeral request state, not durable data:
|
||||
|
||||
- **`useConfirmStore`** — the blocking confirm dialog (the `window.confirm` replacement).
|
||||
- **`useNotificationStore`** — non-blocking toasts (failed saves, etc.).
|
||||
- **`useSettingsPopoverStore`** — which per-pane settings disclosure is open (one
|
||||
at a time); its imperative `openSettingsPopover(id)` lets the Cmd/Ctrl+, shortcut
|
||||
open the editor cluster. The disclosure widget contract (gear + non-modal popover,
|
||||
not an ARIA menu; Esc/focus rules) is [10 · Interaction & Feedback](10-interaction-and-feedback.md) §5.
|
||||
- **`useSettingsPopoverStore`** — the single-open registry for **all** pane-header
|
||||
disclosures (the per-pane settings clusters and the per-chart Export control), keyed
|
||||
by id so at most one is open at once; its imperative `openSettingsPopover(id)` lets the
|
||||
Cmd/Ctrl+, shortcut open the editor cluster. Header disclosures share this registry rather
|
||||
than each carrying their own open-state. The disclosure widget contract (trigger +
|
||||
non-modal popover, not an ARIA menu; Esc/focus rules) is
|
||||
[10 · Interaction & Feedback](10-interaction-and-feedback.md) §5.
|
||||
|
||||
Each pairs its store with a thin **imperative trigger** exported alongside the hook —
|
||||
`confirm(opts): Promise<boolean>` and `notify(opts): string` — so orchestration/services can
|
||||
|
||||
@@ -101,6 +101,20 @@ async function rerender(node: HTMLElement, spec: TopLevelSpec, config: Config) {
|
||||
|
||||
- **Do** pass `actions: false`. Astrolabe owns its own export/copy affordances;
|
||||
the library's overlay menu does not belong on the preview.
|
||||
- **The per-chart export goes through the handle, not the raw view.** `RenderHandle`
|
||||
exposes `toImageURL('png' | 'svg', { scale, background })` so the preview's Export
|
||||
control can rasterize/serialize the live chart without any component importing
|
||||
`vega-embed` or touching the `View` directly — the embedding boundary holds. PNG goes
|
||||
via `view.toCanvas` → `blob:` URL (revoked after the download); SVG via `view.toSVG`
|
||||
→ `data:` URL. Renderer-agnostic: both work from the SVG-backed LivePreview view, since
|
||||
Vega draws to its own off-screen surface here. Two non-obvious details live in the
|
||||
handle, not the caller: **(1) dpr-aware scale** — the PNG is drawn at
|
||||
`scale × devicePixelRatio`, so a `1×` export is as crisp as the chart on a Retina
|
||||
screen (raw `toImageURL` scaleFactor ignores dpr, so a naive 1× looks half-resolution
|
||||
on a 2× display). **(2) background fill** — the chart config renders a transparent
|
||||
background (so the on-screen chart shows the pane colour), which would make a naive
|
||||
export transparent; an opaque colour is composited under the PNG canvas and added as a
|
||||
full-bleed `<rect>` to the SVG. The spec-text exports (copy / `.vl.json`) need no view.
|
||||
- **SVG is the default renderer, canvas is an opt-in for many-mark previews.** SVG is
|
||||
crisp/inspectable/copyable and stays the default for the editor's LivePreview. But an
|
||||
SVG chart renders one DOM node per mark, so a many-mark chart (e.g. the Chart Builder's
|
||||
|
||||
@@ -395,9 +395,15 @@ gear carries `aria-expanded` + `aria-controls`; Enter/Space toggle; **Esc closes
|
||||
focus to the gear**; an outside click closes; at most one is open at a time; focus moves to
|
||||
the first control on open (so `Cmd/Ctrl+,`, which opens the editor cluster, lands inside it).
|
||||
Non-modal — **no focus trap** (unlike the feature modal above). The panel is portaled to
|
||||
`<body>` and positioned `fixed` because the panes clip their content. _(Consulted via /council
|
||||
→ NN/g #4 consistency, #6 recognition-over-recall, #8 minimalist; WAI-ARIA APG disclosure +
|
||||
menu-and-menubar; Carbon popover/overflow-menu/text-toolbar. This bullet is the contract.)_
|
||||
`<body>` and positioned `fixed` because the panes clip their content. The same primitive and
|
||||
single-open registry serve any pane-header disclosure, not only settings: the per-chart
|
||||
**Export** control (preview header — _Import & Export → Per-chart export_) is a disclosure
|
||||
whose `group` holds a few **action buttons** (Copy / Download) plus option controls. A small
|
||||
set of action buttons in a disclosure stays a `group` — an ARIA menu is reserved for true
|
||||
`menuitem`/`menuitemcheckbox`/`menuitemradio` command lists, which this app does not use.
|
||||
_(Consulted via /council → NN/g #4 consistency, #6 recognition-over-recall, #8 minimalist;
|
||||
WAI-ARIA APG disclosure + menu-and-menubar; Carbon popover/overflow-menu/text-toolbar. This
|
||||
bullet is the contract.)_
|
||||
|
||||
**Resolved — an error names the right fix, not a boilerplate one.** Don't staple a generic
|
||||
remedy onto every failure. A missing dataset reference is **not** a JSON/spec syntax problem,
|
||||
|
||||
Reference in New Issue
Block a user