Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 2.4.0 — 2026-10-23

- Implement reflow-safe stepwise text scaling (`1.0`, `1.125`, `1.25`, `1.5`, `2.0`) via scoped CSS properties without CSS transforms (closes #41).
- Introduce `TextScaleController` component dedicated to WCAG SC 1.4.4 / 1.4.10 reflow-safe text enlargement.
- Enhance `FontSizeController` with `stepScale` and custom `steps` props while maintaining backward compatibility with continuous 10% steps.
- Export `TEXT_SCALE_STEPS`, `TextScaleStep`, `TEXT_SCALE_PROTECTED_SELECTOR`, and `isProtectedFromTextScale`.
- Protect form controls (`input`, `textarea`, `select`, `button`), labels, code blocks (`pre`, `code`, `kbd`, `samp`), vector icons (`svg`, `[data-icon]`), and widget internals from destructive font-size and line-height mutations.
- Measure all baselines before writing inline styles, preventing nested compounding across deep DOM trees.
- Wire `fontScale` and `textScale` into `featureSchema` builtins and `effects.ts`, providing full runtime support for accessibility profiles (e.g. Cognitive and Low Vision).
- Add unit test coverage in `tests/textScaling.test.tsx` and browser integration test in `tests/browser/widget.spec.ts`.

## 2.3.2 — 2026-10-23

- Harden GitHub Actions CI workflow (`ci.yml`) with job timeouts and comprehensive artifact capture for test results and Playwright HTML reports on failure (closes #40).
Expand Down
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,24 @@ export function App() {
Every controller is opt-in. Add only the controls that make sense for your
site, and choose their order in JSX.

| Controller | Effect |
| ------------------------- | --------------------------------------------------------------------------------- |
| `FontSizeController` | Scales eligible HTML text from 50% to 200%. |
| `LineHeightController` | Scales computed line height from 50% to 200%. |
| `LetterSpacingController` | Applies 0–10px letter spacing. |
| `ContrastController` | Turns contrast off, reduces it, increases it, or selects a named palette. |
| `SaturationController` | Reduces or increases saturation. |
| `InvertColorsController` | Inverts eligible page content. |
| `GrayscaleController` | Removes color while preserving the selected saturation value for later. |
| `LinkHighlightController` | Highlights links or selects a link palette. |
| `TextAlignController` | Restores the host alignment or forces left, center, right, or justified text. |
| `HideImagesController` | Hides images and CSS background images while retaining layout. |
| `HideVideosController` | Hides video elements and recognized YouTube/Vimeo embeds visually. |
| `BigCursorController` | Applies a larger high-contrast cursor where supported. |
| `ReadingMaskController` | Dims the page around a transparent reading window. |
| `ReadingGuideController` | Follows pointer/focus with a reading guide line. |
| `ResetController` | Resets display features and custom registered features without moving the widget. |
| Controller | Effect |
| ------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `TextScaleController` | Stepwise reflow-safe text enlargement (1.0x, 1.125x, 1.25x, 1.5x, 2.0x) protecting forms, code, and icons. |
| `FontSizeController` | Scales eligible HTML text from 50% to 200%; supports continuous 10% steps or `stepScale` mode. |
| `LineHeightController` | Scales computed line height from 50% to 200%. |
| `LetterSpacingController` | Applies 0–10px letter spacing. |
| `ContrastController` | Turns contrast off, reduces it, increases it, or selects a named palette. |
| `SaturationController` | Reduces or increases saturation. |
| `InvertColorsController` | Inverts eligible page content. |
| `GrayscaleController` | Removes color while preserving the selected saturation value for later. |
| `LinkHighlightController` | Highlights links or selects a link palette. |
| `TextAlignController` | Restores the host alignment or forces left, center, right, or justified text. |
| `HideImagesController` | Hides images and CSS background images while retaining layout. |
| `HideVideosController` | Hides video elements and recognized YouTube/Vimeo embeds visually. |
| `BigCursorController` | Applies a larger high-contrast cursor where supported. |
| `ReadingMaskController` | Dims the page around a transparent reading window. |
| `ReadingGuideController` | Follows pointer/focus with a reading guide line. |
| `ResetController` | Resets display features and custom registered features without moving the widget. |

Effects compose where appropriate. For example, contrast, saturation, invert,
and grayscale are applied in a deterministic order. Defaults leave host styles
Expand Down
31 changes: 17 additions & 14 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ All exports are named from `accessibility-plugin-react`. JavaScript consumers re

## Components

| Component | Props |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AccessibilityPlugin | `children: ReactNode` required; `position?: 'left' \| 'right' \| 'top' \| 'bottom'` default right; `persistenceKey?: string` default `react-accessibility-plugin:state`; `persist?: boolean` default true; `storage?: 'local' \| 'session' \| 'none'` default local; `locale?: string`; `statementUrl?: string`; `largeControls?: boolean` default false |
| AccessibilityButton | `draggable?: boolean` default true (pointer/Alt+arrow movement); `icon?: ReactNode` default inline accessibility SVG; `title?: string` accessible name, default Accessibility; `description?: string` supplementary accessible description; `className?: string`; `style?: CSSProperties`; `largeControls?: boolean` |
| AccessibilityList | `footer?: ReactNode` extra fixed actions; `children: ReactNode` required; `title?: string` visible heading and region name, default Accessibility settings; `description?: string` default Adjust your reading experience; `closeLabel?: string` default Close; `statementUrl?: string`; `largeControls?: boolean`; `locale?: string`; `className?: string`; `style?: CSSProperties` |
| FeatureCard | `title: ReactNode` required; `children: ReactNode` required; `featureKey?: string`; `icon?: ReactNode`; `description?: ReactNode`; `statusBadge?: string`; `status?: 'idle' \| 'active' \| 'error' \| 'unavailable' \| 'permission-required'`; `errorMessage?: string`; `unavailableMessage?: string`; `permissionMessage?: string`; `onRequestPermission?: () => void`; `largeControls?: boolean` |
| ToggleControl | `label: ReactNode` required; `checked: boolean` required; `onChange: (checked: boolean) => void` required; `disabled?: boolean`; `unavailable?: boolean`; `unavailableReason?: string`; `largeControls?: boolean` |
| ChoiceControl | `label: string` required; `value: T` required; `options?: ChoiceOption<T>[]`; `groups?: ChoiceGroup<T>[]`; `onChange: (value: T) => void` required; `disabled?: boolean`; `largeControls?: boolean` |
| StepperControl | `label: string` required; `value: number` required; `min: number`; `max: number`; `step: number`; `unit?: string` default '%'; `showSlider?: boolean`; `formatValue?: (val: number) => string`; `onChange: (value: number) => void` required; `disabled?: boolean`; `largeControls?: boolean` |
| ProfilePreviewCard | `profile: ProfileDefinition` required; `isActive: boolean` required; `onApply: (profile: ProfileDefinition) => void` required; `onRevert: () => void` required; `largeControls?: boolean` |
| SearchCount / EmptyState / ErrorState | Standalone accessible status and announcement primitives for search counts, zero-results fallbacks, and error recovery banners. |
| All fifteen controllers listed in FEATURES.md | No props. Pick/omit in JSX. For custom labels or behavior, compose a control with useAccessibility or the modular control primitives. |
| Component | Props |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AccessibilityPlugin | `children: ReactNode` required; `position?: 'left' \| 'right' \| 'top' \| 'bottom'` default right; `persistenceKey?: string` default `react-accessibility-plugin:state`; `persist?: boolean` default true; `storage?: 'local' \| 'session' \| 'none'` default local; `locale?: string`; `statementUrl?: string`; `largeControls?: boolean` default false |
| AccessibilityButton | `draggable?: boolean` default true (pointer/Alt+arrow movement); `icon?: ReactNode` default inline accessibility SVG; `title?: string` accessible name, default Accessibility; `description?: string` supplementary accessible description; `className?: string`; `style?: CSSProperties`; `largeControls?: boolean` |
| AccessibilityList | `footer?: ReactNode` extra fixed actions; `children: ReactNode` required; `title?: string` visible heading and region name, default Accessibility settings; `description?: string` default Adjust your reading experience; `closeLabel?: string` default Close; `statementUrl?: string`; `largeControls?: boolean`; `locale?: string`; `className?: string`; `style?: CSSProperties` |
| FeatureCard | `title: ReactNode` required; `children: ReactNode` required; `featureKey?: string`; `icon?: ReactNode`; `description?: ReactNode`; `statusBadge?: string`; `status?: 'idle' \| 'active' \| 'error' \| 'unavailable' \| 'permission-required'`; `errorMessage?: string`; `unavailableMessage?: string`; `permissionMessage?: string`; `onRequestPermission?: () => void`; `largeControls?: boolean` |
| ToggleControl | `label: ReactNode` required; `checked: boolean` required; `onChange: (checked: boolean) => void` required; `disabled?: boolean`; `unavailable?: boolean`; `unavailableReason?: string`; `largeControls?: boolean` |
| ChoiceControl | `label: string` required; `value: T` required; `options?: ChoiceOption<T>[]`; `groups?: ChoiceGroup<T>[]`; `onChange: (value: T) => void` required; `disabled?: boolean`; `largeControls?: boolean` |
| StepperControl | `label: string` required; `value: number` required; `min: number`; `max: number`; `step: number`; `unit?: string` default '%'; `showSlider?: boolean`; `formatValue?: (val: number) => string`; `onChange: (value: number) => void` required; `disabled?: boolean`; `largeControls?: boolean` |
| ProfilePreviewCard | `profile: ProfileDefinition` required; `isActive: boolean` required; `onApply: (profile: ProfileDefinition) => void` required; `onRevert: () => void` required; `largeControls?: boolean` |
| TextScaleController | `steps?: readonly number[]` default `[1.0, 1.125, 1.25, 1.5, 2.0]`; `label?: string` default `Bigger Text`. Stepped text scaling without transforms. |
| FontSizeController | Optional `stepScale?: boolean` (snaps to WCAG 1x–2x reflow steps); `steps?: readonly number[]` (custom steps); or omitted for continuous 10% steps. Protects forms, code, and icons. |
| All other controllers in FEATURES.md | Pick/omit in JSX. For custom labels or behavior, compose a control with useAccessibility or the modular control primitives. |

Use one button/list pair per provider and one provider per document. UI IDs use React `useId`; do not target the old fixed `arap-panel` ID. Plugin children can include ordinary app content, but mounting the widget as a sibling makes intent clearer. Persistence configuration is fixed at mount; use `<AccessibilityPlugin key={newKey} persistenceKey={newKey}>` when switching storage identity.

Expand Down Expand Up @@ -43,9 +44,11 @@ Definitions/defaults are immutable for the provider lifetime; the first register

`FeatureRegistration` is `FeatureOptions<unknown>` plus `defaultValue: unknown`. Use the generic hook instead of manually casting callbacks. Legacy `registerReset(key, fn)` / `unregisterReset(key)` remain available; callbacks run before global state is cleared. They are not the primary effect lifecycle API. Callback owners must unregister their callback on teardown.

Exported types: `AccessibilityPluginProps`, `AccessibilityButtonProps`, `AccessibilityListProps`, `AccessibilityState`, `AccessibilityContextValue`, `PluginPosition`, `WidgetPlacement`, `FeatureOptions<T>`, `FeatureRegistration`, `ToggleControlProps`, `ChoiceControlProps`, `ChoiceOption<T>`, `ChoiceGroup<T>`, `StepperControlProps`, `FeatureCardProps`, `FeatureStatus`, `SearchCountProps`, `EmptyStateProps`, `ErrorStateProps`, `ProfilePreviewCardProps`, `AccessibilityPreferences`, `ContrastMode`, `CursorSizePreference`, `FontFamilyPreference`, `ProfileDefinition`, `ProfilePresetId`, `ReaderMode`, `SaturationPreference`, `StorageMode`, `TextAlignPreference`, `LocaleStrings`.
Exported types: `AccessibilityPluginProps`, `AccessibilityButtonProps`, `AccessibilityListProps`, `AccessibilityState`, `AccessibilityContextValue`, `PluginPosition`, `WidgetPlacement`, `FeatureOptions<T>`, `FeatureRegistration`, `ToggleControlProps`, `ChoiceControlProps`, `ChoiceOption<T>`, `ChoiceGroup<T>`, `StepperControlProps`, `FeatureCardProps`, `FeatureStatus`, `SearchCountProps`, `EmptyStateProps`, `ErrorStateProps`, `ProfilePreviewCardProps`, `FontSizeControllerProps`, `TextScaleControllerProps`, `TextScaleStep`, `AccessibilityPreferences`, `ContrastMode`, `CursorSizePreference`, `FontFamilyPreference`, `ProfileDefinition`, `ProfilePresetId`, `ReaderMode`, `SaturationPreference`, `StorageMode`, `TextAlignPreference`, `LocaleStrings`.

Exported control components: `ToggleControl`, `ChoiceControl`, `StepperControl`, `FeatureCard`, `SearchCount`, `EmptyState`, `ErrorState`, `ProfilePreviewCard`.
Exported control components: `ToggleControl`, `ChoiceControl`, `StepperControl`, `FeatureCard`, `SearchCount`, `EmptyState`, `ErrorState`, `ProfilePreviewCard`, `FontSizeController`, `TextScaleController`.

Exported typography and reflow utilities: `TEXT_SCALE_STEPS`, `TEXT_SCALE_PROTECTED_SELECTOR`, `isProtectedFromTextScale`.

Exported preference and profile utilities: `DEFAULT_PREFERENCES`, `PROFILE_DEFINITIONS`, `normalizePreferences`, `normalizePreferenceValue`, `resolveEffectivePreferences`, `preferencesReducer`, `createInitialPreferencesState`.

Expand Down
Loading