mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Backfill interactive-control accessibility against the council (APG)
This commit is contained in:
@@ -24,17 +24,18 @@ import { renderSpec, type RenderHandle } from '../services/chart-renderer';
|
||||
import { useAppStore } from '../stores/AppStore';
|
||||
import { usePreviewStore } from '../stores/PreviewStore';
|
||||
import { selectShownText, useSnippetStore } from '../stores/SnippetStore';
|
||||
import { SegmentedControl, type SegmentedOption } from './SegmentedControl';
|
||||
import styles from './LivePreview.module.css';
|
||||
|
||||
/** Render debounce (ms). Becomes the configurable `renderDebounce` setting in M5. */
|
||||
const RENDER_DEBOUNCE_MS = 300;
|
||||
|
||||
/** The four fit modes in display order (spec §04 → Fit / Sizing Modes). */
|
||||
const FIT_MODES: ReadonlyArray<{ mode: FitMode; label: string }> = [
|
||||
{ mode: 'default', label: 'Original' },
|
||||
{ mode: 'width', label: 'Width' },
|
||||
{ mode: 'height', label: 'Height' },
|
||||
{ mode: 'full', label: 'Full' },
|
||||
const FIT_OPTIONS: ReadonlyArray<SegmentedOption<FitMode>> = [
|
||||
{ value: 'default', label: 'Original' },
|
||||
{ value: 'width', label: 'Width' },
|
||||
{ value: 'height', label: 'Height' },
|
||||
{ value: 'full', label: 'Full' },
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -54,20 +55,17 @@ const FIT_CLASS: Record<FitMode, string> = {
|
||||
function FitControl() {
|
||||
const fitMode = useAppStore((s) => s.previewFitMode);
|
||||
const setFitMode = useAppStore((s) => s.setPreviewFitMode);
|
||||
// Single-select set → a radio group, not independent toggles (APG; doc §10.5).
|
||||
return (
|
||||
<div className={styles.fit} role="group" aria-label="Fit chart to pane">
|
||||
{FIT_MODES.map(({ mode, label }) => (
|
||||
<button
|
||||
key={mode}
|
||||
type="button"
|
||||
className={`${styles.fitOption} ${mode === fitMode ? styles.fitActive : ''}`}
|
||||
aria-pressed={mode === fitMode}
|
||||
onClick={() => setFitMode(mode)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<SegmentedControl
|
||||
label="Fit chart to pane"
|
||||
options={FIT_OPTIONS}
|
||||
value={fitMode}
|
||||
onChange={setFitMode}
|
||||
className={styles.fit}
|
||||
optionClassName={styles.fitOption}
|
||||
activeClassName={styles.fitActive}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,6 +79,10 @@ export function LivePreview() {
|
||||
const error = usePreviewStore((s) => s.error);
|
||||
const setError = usePreviewStore((s) => s.setError);
|
||||
|
||||
// TODO (council backfill, doc §10.2): a render that exceeds ~1s owes a
|
||||
// non-blocking busy indication (overlay + aria-busy), gated by a threshold so
|
||||
// sub-1s renders show nothing. Deferred — it pairs with the heavier M3 dataset
|
||||
// renders the budget flags; today's inline-data renders are effectively instant.
|
||||
useEffect(() => {
|
||||
const node = hostRef.current;
|
||||
if (!node) return;
|
||||
@@ -179,6 +181,9 @@ export function LivePreview() {
|
||||
<div className={`${styles.frame} ${FIT_CLASS[fitMode]}`} hidden={error !== null}>
|
||||
<div className={styles.host} ref={hostRef} />
|
||||
</div>
|
||||
{/* Visual only — no live region. The same error is announced once by the
|
||||
editor pane's role="alert" (one producer, two subscribers; doc §10.1),
|
||||
so adding one here would double-announce it. */}
|
||||
{error !== null && <pre className={styles.error}>{error}</pre>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user