Skip to content

feat(editor): support for dynamic height - #41

Open
CatHood0 wants to merge 18 commits into
masterfrom
feat_dynamic_height
Open

feat(editor): support for dynamic height#41
CatHood0 wants to merge 18 commits into
masterfrom
feat_dynamic_height

Conversation

@CatHood0

@CatHood0 CatHood0 commented Aug 25, 2026

Copy link
Copy Markdown
Member

I'll add later the description of this feature

What I'm working on by now:

  • How to make the auto-scroll working (this feature removes the usage of any Scrollable, so, there's no way that the EditorState can handle auto-scroll service events automatically)
  • Why the tables are not reporting its sizes as expected.
  • Testing how improve it. At this point, the limit of the content is 8.000 words (I don't remember the amount of nodes).

Current State

Grabacion.de.pantalla.desde.2026-08-25.19-52-41.mp4

Knowed limitation

The perfomance is the issue that I couldn't find the solution. Since every block requires to update it's sizes, its really difficult to make the editor not being re-renderized everytime that keystroke event modifies node content.

As you see here, trying to use this "dynamic" mode, makes imposible using the editor (I don't do nothing, and it crashes. This video is running in a low end PC, with the release mode):

Grabacion.de.pantalla.desde.2026-08-25.19-58-01.mp4

CatHood0 added 14 commits July 21, 2026 03:13
- HeightCache: O(1) sparse index→height map with index-shift on insert/delete
- DynamicHeightConfig: minHeight, defaultBlockHeight, resizeDebounce
- DynamicHeightController: orchestrates cache, exposes currentHeight, receives mutations from EditorState.apply()
- BlockHeightReporter: mixin for block State classes; reports RenderBox height after layout
- DynamicHeightLayout: ConstrainedBox(minHeight) + Column(mainAxisSize.min); replaces PageBlockComponent when dynamicHeight active
- NovidentEditor: accepts dynamicHeightConfig / dynamicHeightController; skips ScrollServiceWidget
- EditorState.apply(): notifies NodesInserted/NodesRemoved/TextChanged to controller
- All 9 block components modified to include BlockHeightReporter mixin
- 90 tests covering cache, config, controller, reporter, layout, integration, and growth scenarios
…validation

- Overlay now wrapped in IntrinsicHeight when dynamicHeight active
- No more SizedBox fallback or defaultHeight estimates
- HeightCache.invalidateRange preserves old heights instead of reverting
  to defaultHeight, preventing one-frame overflow during growth
- reportHeight skips unchanged check when block is dirty (invalidated)
- 92 tests pass with zero overflow assertions
- DocumentContentStore: add multiEditDirectoryId ValueNotifier
- DirectoryTile: right-click → context menu with 'Modo múltiple'
- MultiEditorView: scrollable list of editors per file in directory
  (filename title + divider + NovidentEditor with dynamicHeight)
- DesktopView: ValueListenableBuilder switches to MultiEditorView
  when dirId is set
…torView

- ScrollServiceWidget no longer skipped — keeps Provider<EditorScrollController>
  intact for SelectionServiceWidget (follows original AppFlowy pattern)
- MyEditor accepts optional dynamicHeightConfig parameter
- MultiEditorView redesigned to match EditorPane exactly:
  white sheet (maxWidth 750, shadows, rounded), DocumentSession per file,
  status bar (close button only, no vim/word count/zen)
- _FileSheet uses same DocumentSession + MyEditor pattern as EditorPane
getVisibleNodes returned empty list when visibleRangeNotifier stayed
at (-1,-1) — the default state in dynamic height mode (no virtual
scroll list to update the range). DesktopSelectionService.getNodeInOffset
used this to locate tapped nodes, and with empty results selection
could never start (no cursor, no selection, no interaction).

Now returns all root children when the range is invalid, matching
the semantics of dynamic height where every block is always visible.

Added EditorState.getVisibleNodes test (5 cases) for regression.
IntrinsicHeight runs a full double-layout pass (O(N²)) which becomes
prohibitively slow with 5000+ words across multiple paragraphs.
Now IntrinsicHeight only runs on the very first frame (before any
block has been measured). Once the HeightCache has real measurements
(hasMeasuredBlocks=true), the editor switches to SizedBox with
the cached currentHeight — O(1) rebuilds on every subsequent frame.

The controller listener (_onDynamicHeightChanged) uses post-frame
callbacks to avoid setState-during-build from didChangeDependencies.
Frame 1 (no cache): Column + IntrinsicHeight → measures all blocks.
Frame 2+ (cache ready): ListView.builder(shrinkWrap:false, NeverScroll)
→ virtualizes blocks, only builds visible children. Combined with
SizedBox(currentHeight) optimization from _NovidentEditorState.

92/97 tests pass. 5 compilation errors to investigate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant