diff --git a/CHANGELOG.md b/CHANGELOG.md index 257537e..cbdc8f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,132 +1,60 @@ # Changelog -## [1.1.4] - 2026-03-28 +## [1.2.0] - 2026-03-30 -### 🐛 Bug Fixes +### ✨ New Features -- **`display:none` not respected in renderer** (`render_hyper_box_layout.dart`): Added early-return guard in `_tokenizeNode` — elements with `display:none` no longer produce any layout fragments and are correctly hidden. Previously, elements styled with `display:none` (e.g. Wikipedia `[edit]` section links) were still rendered. +- **Multi-tier Plugin API** (`hyper_render_core`): Third-party packages can now render arbitrary HTML tags as custom Flutter widgets via `HyperNodePlugin` / `HyperPluginRegistry`. + - **Block tier** (`isInline == false`, default): widget takes full available width with CSS margins. + - **Inline tier** (`isInline == true`): widget flows inside text lines; intrinsic size measured in `performLayout` via `getMaxIntrinsicWidth / getMinIntrinsicHeight`. + - Register at startup: `HyperPluginRegistry()..register(MyPlugin())` and pass to `HyperViewer(pluginRegistry: ...)`. -- **`
` rendered as line break** (`html_adapter.dart`): `
` now correctly returns a styled `BlockNode` with a top border (`borderColor: #CCCCCC, borderWidth: 1px`), matching browser behavior. Previously it was incorrectly treated identically to `
`. +- **Dirty-flag incremental layout** (`hyper_viewer.dart`): Only re-layout sections whose content changed. Each `DocumentNode` chunk is fingerprinted with `Object.hashAll` over child `textContent`; unchanged sections are reused on the next parse, and `ValueKey(hash)` on `RepaintBoundary` lets Flutter skip re-layout and repaint entirely. ~90% layout rebuild reduction for live-updating feeds. -- **Whitespace-only space nodes dropped between inline elements** (`html_adapter.dart`): Text nodes consisting only of horizontal spaces (e.g. `" "` between `text more`) were being silently dropped by `.trim().isEmpty`, causing missing word-separating spaces. Fixed to only drop nodes that contain newlines (structural indentation whitespace), not pure-space nodes. +- **Paged mode** (`HyperRenderMode.paged`): `PageView.builder`-based rendering, one document chunk per page. Suitable for e-book / epub / reader UIs. + - Supply a `HyperPageController` for programmatic navigation (`animateToPage`, `nextPage`, `previousPage`, `jumpToPage`) and `ValueNotifier currentPage` for reactive page indicators. -- **`TextPainter` cache hash collision** (`render_hyper_box.dart`): The `_LruCache` key was computed with `Object.hash()` which can collide for large documents with many distinct text styles, leading to wrong text metrics and subtle layout glitches. Replaced with a new `_TextPainterKey` class using full value equality over all 9 style fields. +### ♿ Accessibility (WCAG 2.1 AA) -- **`@override` analyzer warning** (`packages/hyper_render_html/lib/src/css_parser.dart`): Removed incorrect `@override` on `parseKeyframes()`. The `override_on_non_overriding_member` lint flagged this because the parent method has a concrete default body. Now `flutter analyze` reports 0 issues. +- **Image alt-text semantic nodes** (`render_hyper_box_accessibility.dart`): `…` elements now produce a discrete `SemanticsNode` at the image's layout rect. Screen-reader users can navigate to images element-by-element (WCAG 1.1.1 Non-text Content). Previously alt text only appeared in the flat document-level label. +- **`aria-label` on links honored** (`render_hyper_box_accessibility.dart`): If an `` element carries an `aria-label` attribute, that value is used as the link's semantic label instead of its text content (WCAG 4.1.2 Name, Role, Value). -### 📸 Assets & Documentation +### 🏗️ Refactor — Dead-code elimination -- **Added `assets/logo.svg`** — vector HyperRender logo now correctly displayed in README header and pub.dev listing. -- **README**: Fixed broken in-page navigation links (`Quick Start`, `Why Switch?`, `API`, `Packages`) — added emoji prefixes to section headings which generate the leading `-` in GitHub anchor IDs. Improved bottom section with star call-to-action, Discussions, API docs links. Updated version badge to `1.1.4`. +- **Removed 31 duplicate files from root `lib/src/`** that were identical or outdated copies of the canonical implementations in `packages/hyper_render_core`. Root `lib/src/` now contains only the 17 files that are genuinely unique to the root package (parsers, sanitizer, `HyperViewer`, virtualized selection, `capture_extension`). +- **`LazyImageQueue` singleton deduplication**: `lib/src/core/lazy_image_queue.dart` was a separate implementation that created a second `LazyImageQueue.instance` — meaning `LazyImageQueue.instance.cancel()` called from outside `HyperViewer` hit a different singleton than the one `HyperViewer` used internally. Root now re-exports `LazyImageQueue` directly from `hyper_render_core` (single shared instance). +- **Added missing v1.2.0 symbols to root re-export**: `HyperRenderConfig`, `LazyImageQueue`, `HyperNodePlugin`, `HyperPluginRegistry`, `HyperPluginBuildContext`, `LoadingSkeleton`, `HyperErrorWidget`, `FloatCarryover` are now all accessible from `package:hyper_render`. +- **Consolidated double export**: The redundant second `export 'package:hyper_render_core' show HyperRenderConfig'` line was folded into the main re-export block. -## [1.1.3] - 2026-03-25 +### 🐛 Bug Fixes -- Remove `publish_to: none` from all sub-package pubspec.yaml files so pub.dev can verify repository URLs (fixes pub points deduction). -- Commit DevTools extension web build to git so it is always available without a local rebuild step. -- Fix `.gitignore` to untrack all sub-package `pubspec.lock` files per Dart library conventions. -- Fix all unnecessary Flutter SDK import warnings (`rendering.dart`, `services.dart`, `gestures.dart` redundant with broader imports) across `lib/` and `packages/hyper_render_core/lib/` — resolves 6 static-analysis INFO issues and unblocks CI. +- **Copy action produced empty clipboard** (`virtualized_selection_overlay.dart`, `hyper_selection_overlay.dart`): The `Listener.onPointerDown` callback cleared the active selection before the Copy button's `onPressed` could fire, so `Clipboard.setData` received an empty string. Fixed by guarding `clearSelection()` behind a `_showMenu` / `_showContextMenu` check (matching the pattern already used in the non-virtualized overlay). +- **Context menu outside hit-testable bounds** (`hyper_selection_overlay.dart`, `virtualized_selection_overlay.dart`): When a selection was near the top of the widget the computed `top` for the `Positioned` menu went negative. `Stack(clipBehavior: Clip.none)` allows visual overflow but Flutter hit-testing is still bounded by the parent — the Copy button was unreachable. Fixed by clamping the top offset: `.clamp(0.0, double.infinity)`. -All notable changes to HyperRender will be documented in this file. +- **Scroll vs. text-selection conflict** (`render_hyper_box.dart`): `handleEvent(PointerMoveEvent)` bypassed the gesture arena and fired on every pointer move, creating accidental selections during scrolling. Removed raw-event selection tracking and moved selection initiation to a `LongPressGestureRecognizer` at the widget layer — this correctly competes with the parent scroll view's `VerticalDragGestureRecognizer`, so a quick swipe scrolls while a 500 ms hold begins a text selection (matching iOS/Android native behaviour). -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +- **Virtualized copy menu never appeared** (`virtualized_selection_overlay.dart`): Per-chunk `RenderHyperBox._selection` was set by the old pointer-event tracking, but `VirtualizedSelectionController` (cross-chunk selection) was never populated, so `hasSelection` remained `false` and the menu was never shown. Fixed by routing the long-press start through `VirtualizedSelectionController.startSelection()`. -## [1.1.2] - 2026-03-25 +- **Selection Escape key fix** (`hyper_selection_overlay.dart`): `Escape` key failed to clear selection because the internal `FocusNode` wasn't reliably focused after selection was established. Fixed by calling `_focusNode.requestFocus()` inside `startSelectionAt`. -### ✨ New Features -- **CSS @keyframes** (`DefaultCssParser.parseKeyframes`): `@keyframes` blocks are now parsed from `