hyper_render 1.2.1
hyper_render: ^1.2.1 copied to clipboard
Render HTML/Markdown/Delta at 60 FPS. The only Flutter renderer with CSS float layout, crash-free text selection, and CJK Ruby typography. Drop-in flutter_html alternative.
Changelog #
1.2.0 - 2026-03-30 #
✨ New Features #
-
Multi-tier Plugin API (
hyper_render_core): Third-party packages can now render arbitrary HTML tags as custom Flutter widgets viaHyperNodePlugin/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 inperformLayoutviagetMaxIntrinsicWidth / getMinIntrinsicHeight. - Register at startup:
HyperPluginRegistry()..register(MyPlugin())and pass toHyperViewer(pluginRegistry: ...).
- Block tier (
-
Dirty-flag incremental layout (
hyper_viewer.dart): Only re-layout sections whose content changed. EachDocumentNodechunk is fingerprinted withObject.hashAllover childtextContent; unchanged sections are reused on the next parse, andValueKey(hash)onRepaintBoundarylets Flutter skip re-layout and repaint entirely. ~90% layout rebuild reduction for live-updating feeds. -
Paged mode (
HyperRenderMode.paged):PageView.builder-based rendering, one document chunk per page. Suitable for e-book / epub / reader UIs.- Supply a
HyperPageControllerfor programmatic navigation (animateToPage,nextPage,previousPage,jumpToPage) andValueNotifier<int> currentPagefor reactive page indicators.
- Supply a
♿ Accessibility (WCAG 2.1 AA) #
- Image alt-text semantic nodes (
render_hyper_box_accessibility.dart):<img alt="…">elements now produce a discreteSemanticsNodeat 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-labelon links honored (render_hyper_box_accessibility.dart): If an<a>element carries anaria-labelattribute, that value is used as the link's semantic label instead of its text content (WCAG 4.1.2 Name, Role, Value).
🏗️ Refactor — Dead-code elimination #
- Removed 31 duplicate files from root
lib/src/that were identical or outdated copies of the canonical implementations inpackages/hyper_render_core. Rootlib/src/now contains only the 17 files that are genuinely unique to the root package (parsers, sanitizer,HyperViewer, virtualized selection,capture_extension). LazyImageQueuesingleton deduplication:lib/src/core/lazy_image_queue.dartwas a separate implementation that created a secondLazyImageQueue.instance— meaningLazyImageQueue.instance.cancel()called from outsideHyperViewerhit a different singleton than the oneHyperViewerused internally. Root now re-exportsLazyImageQueuedirectly fromhyper_render_core(single shared instance).- Added missing v1.2.0 symbols to root re-export:
HyperRenderConfig,LazyImageQueue,HyperNodePlugin,HyperPluginRegistry,HyperPluginBuildContext,LoadingSkeleton,HyperErrorWidget,FloatCarryoverare now all accessible frompackage:hyper_render. - Consolidated double export: The redundant second
export 'package:hyper_render_core' show HyperRenderConfig'line was folded into the main re-export block.
🐛 Bug Fixes #
-
Copy action produced empty clipboard (
virtualized_selection_overlay.dart,hyper_selection_overlay.dart): TheListener.onPointerDowncallback cleared the active selection before the Copy button'sonPressedcould fire, soClipboard.setDatareceived an empty string. Fixed by guardingclearSelection()behind a_showMenu/_showContextMenucheck (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 computedtopfor thePositionedmenu 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). -
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 aLongPressGestureRecognizerat the widget layer — this correctly competes with the parent scroll view'sVerticalDragGestureRecognizer, so a quick swipe scrolls while a 500 ms hold begins a text selection (matching iOS/Android native behaviour). -
Virtualized copy menu never appeared (
virtualized_selection_overlay.dart): Per-chunkRenderHyperBox._selectionwas set by the old pointer-event tracking, butVirtualizedSelectionController(cross-chunk selection) was never populated, sohasSelectionremainedfalseand the menu was never shown. Fixed by routing the long-press start throughVirtualizedSelectionController.startSelection(). -
Selection Escape key fix (
hyper_selection_overlay.dart):Escapekey failed to clear selection because the internalFocusNodewasn't reliably focused after selection was established. Fixed by calling_focusNode.requestFocus()insidestartSelectionAt. -
constlint fix (hyper_render_widget.dart):HyperPluginBuildContextinstantiation changed toconstto silenceprefer_const_constructors.
1.1.4 - 2026-03-28 #
🐛 Bug Fixes #
-
display:nonenot respected in renderer (render_hyper_box_layout.dart): Added early-return guard in_tokenizeNode— elements withdisplay:noneno longer produce any layout fragments and are correctly hidden. Previously, elements styled withdisplay:none(e.g. Wikipedia[edit]section links) were still rendered. -
<hr>rendered as line break (html_adapter.dart):<hr>now correctly returns a styledBlockNodewith a top border (borderColor: #CCCCCC, borderWidth: 1px), matching browser behavior. Previously it was incorrectly treated identically to<br>. -
Whitespace-only space nodes dropped between inline elements (
html_adapter.dart): Text nodes consisting only of horizontal spaces (e.g." "between<b>text</b> <i>more</i>) 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. -
TextPaintercache hash collision (render_hyper_box.dart): The_LruCache<int, TextPainter>key was computed withObject.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_TextPainterKeyclass using full value equality over all 9 style fields.
1.0.0 - 2026-03-01 #
First stable release. Core features, plugin architecture, and cross-platform support are production-ready.
