Releases: Samyssmile/notectl
Release list
v2.2.5
Fixed
- The documented
table,table-row, andtable-cellCSS Shadow Parts are now actually exposed (#201). The styling guide promisesnotectl-editor::part(table-cell) { padding: 12px; }, but no table part ever reached the live DOM, so::part()customization silently did nothing for tables. The part attributes were added (in #123) to the table NodeSpecs'toDOM(), yet the table plugin also registers NodeViews fortable,table_row, andtable_cell, and the renderer always prefers a registered NodeView overNodeSpec.toDOM(), so the annotated elements were never created. The NodeViews inTableNodeViews.tsnow set the parts themselves:part="table"on the scrollable.notectl-table-wrapper(matching the documented element),part="table-row"on each<tr>, andpart="table-cell"on each<td>. Blockquote and code-block parts were unaffected (blockquote has no NodeView, and the code-block NodeView already set its parts). The docs' Part Inventory also gains the previously undocumentedtoolbar-grouppart.
Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md
v2.2.4
Fixed
- The Angular wrapper's
getContentHTML()now forwards serialization options to core (#185). Core'sNotectlEditor.getContentHTML()accepts aContentHTMLOptionsargument (includeBlockIds,pretty,cssMode), most importantly{ includeBlockIds: false }to produce clean export HTML with nodata-block-idattributes for database storage or handoff to another system. The@notectl/angularNotectlEditorComponent.getContentHTML()was declared with no parameters and forwarded none, so an Angular consumer callingeditor.getContentHTML({ includeBlockIds: false })got a compile error and, if forced through, still received block ids. The component method now mirrors core's three overloads exactly and forwards the options to the underlying editor, soincludeBlockIds,pretty, andcssModeare all reachable from the wrapper. The option typesContentHTMLOptionsandContentCSSResultare re-exported from@notectl/angular, and theNotectlTestHarness.getContentHTML()testing helper mirrors the same overloads. The signal-forms[formField], classic[formControl], and value-sync paths are intentionally left on the default (block ids kept), because those round-trips rely ondata-block-idto preserve block identity and the caret across external sync.
Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md
v2.2.3
Fixed
- Selecting the whole document and deleting it now clears the editor back to the placeholder, even when the first block is a title or heading (#183). With a centered title as the first block, pressing Ctrl+A then Backspace (or Delete, or Cut) emptied the text but left the cursor inside an empty, still-centered title block, so the document was never considered empty and the "Start typing..." placeholder never returned.
deleteSelectionCommandnow detects a full-document selection (the same rangeselectAllproduces) and replaces the whole document with one fresh empty paragraph. Partial cross-block deletes still keep the first block's type (standard rich-text behavior), typing/paste over a full selection is unaffected, and the operation is a single undo step that restores the original blocks, types, alignment, and text.
Full changelog: https://github.com/Samyssmile/notectl/blob/main/CHANGELOG.md
v2.2.2
Added
- The formula plugin's structural palette now covers far more notation (#169). The on-screen math keyboard previously exposed only a thin slice of the bundled converter's capabilities (fractions, scripts, roots, a few operators, partial Greek, a handful of relations, arrows, and matrices). It now offers six new groups and several extended ones, all backed by snippets the converter already understood, so no engine change was needed. New groups: Accents (
\vec,\hat,\overline,\dot,\tilde), Functions (\sin,\cos,\tan,\log,\ln,\exp), Sets (\in,\notin,\subset,\subseteq,\cup,\cap,\emptyset, and the number sets ℝ ℕ ℤ ℚ ℂ via\mathbb), Logic (\forall,\exists,\neg,\land,\lor,\implies,\iff), Brackets (auto-sized\left … \rightparentheses, brackets, braces, absolute value, norm, floor, and ceiling), and Dots (\cdots,\ldots,\vdots,\ddots). Extended groups: Operators gains double and contour integrals (\iint,\oint) and the derivative symbols\partialand\nabla; Greek now spans the full lowercase and uppercase alphabet (adding ε, ζ, η, κ, ν, ξ, ρ, τ, χ, ψ and the capitals Γ Θ Λ Π Φ Ψ); Relations adds\sim,\cong, and\propto. Every new button carries an English accessible name, the palette keeps its ARIA toolbar and roving-tabindex keyboard model, and all six new group labels are translated across the nine bundled locales. Covered by a newMathPaletteDatatest that converts every palette snippet through the bundled LaTeX-to-MathML converter and asserts zero parse errors, plus the existing locale-completeness test extended to the new keys.
Fixed
-
Keyboard shortcuts now work on non-Latin keyboard layouts (#176). With a Russian (JCUKEN) layout, command shortcuts such as Ctrl+A, Ctrl+Z, and Ctrl+B did nothing, and Delete appeared broken too because nothing had been selected. The whole keyboard path keyed off
event.key, which is layout-dependent: on a Russian layout the physical A key reportsevent.key = 'ф', the Z key reports'я', sonormalizeKeyDescriptorproducedMod-Фinstead ofMod-Aand matched no keymap, and the separate hardcoded built-in fallback for undo, redo, and select-all compared'я'against'z'and never fired. The fix adds a physical-key fallback modeled on ProseMirror and CodeMirror: the layout-awareevent.keydescriptor is still tried first and always wins when it matches, then, only when a command modifier (Ctrl or Cmd) is held and the primary descriptor matched nothing, a second descriptor derived from the layout-independentevent.code(the physical US-QWERTY position, for exampleKeyAtoA) is tried. Normal typing, IME composition, and dead keys are never touched, and AltGr combinations (Ctrl+Alt) are explicitly excluded so layouts that compose characters with AltGr are not hijacked. Descriptor logic moved into a new DOM-free, fully unit-testedinput/KeyDescriptor.tsmodule, and the two duplicated keymap dispatch loops inKeyboardHandlerwere unified behind a single helper that applies the fallback in both normal and read-only mode. This is fundamentally an internationalization and accessibility fix for keyboard operability (WCAG 2.1.1). The physical fallback also covers shifted bindings: when Shift is held it tries both the US-QWERTY base glyph and its shifted glyph, so digit shortcuts such as the heading bindingsMod-Shift-1toMod-Shift-6(which a real browser reports askey = '!', not'1', and which therefore never matched on any layout before) and punctuation shortcuts such asMod-Shift->(blockquote) andMod-Shift-+/Mod-Shift-_(font size) now resolve on every layout without changing a single binding descriptor, so tooltips stay correct. Covered by newKeyDescriptorunit tests (Cyrillic mapping, the no-modifier, AltGr, and named-key gates, and the base-plus-shifted digit and punctuation expansion), newKeyboardHandlertests (plugin keymap and built-in resolution on Cyrillic input, the headingMod-Shift-1and blockquoteMod-Shift->and font-sizeMod-Shift-+shifted-glyph repairs, with Latin precedence preserved), and an e2e spec that simulates a Russian layout by dispatching synthetic events with Cyrillickeyplus US-QWERTYcodeand confirms Ctrl+A then Delete clears the document and Ctrl+Z restores it, Ctrl+B bolds, and Ctrl+Shift+. toggles blockquote. -
Buttons inside custom toolbar popups can now be activated with the keyboard (#171). A keyboard user could focus a button inside a custom popup, such as the formula plugin's structural math palette or its Insert and Cancel actions, by using Tab and the arrow keys, but pressing Enter or Space did nothing, so the on-screen math keyboard was unusable without a pointer (a WCAG 2.1.1 Keyboard gap). The root cause was a split of responsibility: the structural palette is a self-contained roving-tabindex toolbar, like the color grid, but unlike the color grid it did not handle Enter/Space itself and let its arrow keys bubble to the host popup, which then advanced focus a second time. The palette now fully owns its keyboard, matching the color grid: it activates the focused button on Enter/Space and stops every navigation and activation key it handles from bubbling, which also fixes a latent bug where Arrow Down/Up moved the roving focus by two instead of one. For the popup's standalone Insert and Cancel buttons, which run their action on
click(withmousedownonly guarding text-field focus), the toolbar's shared key handler now replays a complete pointer press (mousedown, thenmouseup, thenclick) instead of dispatchingmousedownalone, so it activates both theclickconvention and themousedownconvention used by the link and image popups, exactly once. Covered by newMathPaletteunit tests (Enter/Space activation, single-step navigation, and that handled keys do not bubble while Tab and Escape still do), newToolbarPopupControllerunit tests for the click-based and mousedown-based conventions, and a formula e2e test that walks the palette to the integral symbol using Tab and the arrow keys and inserts it with Enter, without a mouse. -
A checklist item's checked state is now exposed to screen readers (#168) — The checked state was written as
aria-checkedon the<li>, which carriesrole="listitem", a role that does not supportaria-checked, so assistive technology ignored it and never announced whether an item was checked (a WCAG 4.1.2 Name/Role/Value gap). The visible checkbox was drawn purely as a CSS::beforepseudo-element, so no real element carried the state either. The marker is now a realrole="checkbox"element witharia-checkedand an accessible label, rendered as the first child of the list item, so the state lives in the accessibility tree and is announced when the caret enters the item. The invalidaria-checkedis removed from the<li>(thedata-checkedattribute that drives the CSS glyph stays). To keep the marker out of the editable text flow it iscontenteditable="false"and flaggeddata-widget; the selection layer now treatsdata-widgetelements as zero width, so the marker never shifts a caret offset or becomes a caret position, and an empty checklist item still places the caret on its editable line rather than before the checkbox. Because the marker is a real element, the toggle click handler now matches it directly and the previous bounding-box geometry and RTL math are gone. Toggling by mouse or byMod+Enteralso announces the new state through the editor's live region, so the action is spoken even though the marker is not focused (WCAG 4.1.3), complementing the keyboard-operability fix in #167. Covered by new unit tests for the marker markup and the announcement, by SelectionSync tests proving adata-widgetmarker is zero width in offset space (caret placement at text start, mid-text, and in an empty item), and by checklist and cursor e2e: a new live-region assertion confirms a keyboard toggle narrates the checked state (and is not clobbered by the block-type announcer), while the existing cursor suite confirms click, drag, double-click, and arrow navigation offsets are unchanged in a real browser. -
Checklist items can now be toggled with the keyboard (#167) — A keyboard-only user could create a checklist item but had no way to toggle its checked state: the only runtime toggle was a
mousedownhandler on the marker click region, and thetoggleChecklistItemcommand was registered but bound to no key, so the checked state was unreachable without a pointer (a WCAG 2.1.1 Keyboard gap). The command is now bound toMod+Enter(Ctrl+Enteron Windows/Linux,Cmd+Enteron macOS) while the caret is inside a checklist item. The binding declines on non-checklist blocks, so it falls through cleanly to otherMod+Enterhandlers (such as the code-block insert-after shortcut). It is registered at navigation priority so it stays reachable in read-only mode wheninteractiveCheckboxesis enabled, giving keyboard users the same toggle access as a mouse click. The screen-reader announcement of the checked state (aria-checkedcurrently sits on therole="listitem"element, which does not expose it) is a separate concern, tracked apart from this keyboard-operability fix. Covered by new unit tests that drive the toggle through the registered key binding (not the command directly), assert the navigation-priority registration, confirm the read-only interactive path, and verify the binding is absent when the checklist type is disabled. -
Pasting a table into a table cell no longer creates a nested table (#166) — Pasting table HTML (for example a table copied from a spreadsheet or a web page) while the caret sat inside a table cell inserted a full table into that cell, producing a schema-invalid document, because
table_cell.content.allowdoes not listtable. The table-...
v2.2.1
Patch release with three bug fixes on top of 2.2.0.
Fixed
- Copying an inline and a display formula together no longer loses their LaTeX source (#154) — A copied or cut selection containing both an inline and a display formula lost both formulas' LaTeX on paste, leaving them rendered but uneditable. The paste pre-sanitization pass now preserves the schema-registered MathML
<semantics>/<annotation>markup, and the formula node specs strip the editor-internaldata-block-idfrom stored MathML. - Typing immediately after a link no longer extends the link onto the new text (#153) — Inline marks were inclusive at their right edge, so typing at the end of a linked span absorbed the new text into the link.
MarkSpecgains aninclusiveflag (defaulttrue); the link mark opts out, and a singleresolveCursorMarkshelper makes every collapsed-caret mark derivation respect right-boundary inclusivity. Bold, italic, and other marks are unchanged. - Inserting a block object on an empty line no longer leaves a stray empty paragraph above it (#152) — Inserting a table, horizontal rule, image, or display formula on an empty line left a blank paragraph above the object. All four insert commands now route through a shared, inline-aware
insertBlockObjectOnOwnLineprimitive that consumes the empty anchor line.
Full Changelog: v2.2.0...v2.2.1
v2.1.3
[2.1.3] - 2026-05-18
Added
-
Toolbar group wrapper exposed as
part="toolbar-group"with opt-in accessible labels (#125) — Each visual cluster of toolbar buttons is now wrapped in a real<div part="toolbar-group" class="notectl-toolbar-group">flex container in both render paths (renderItemsByLayoutandrenderItemsByGroup). Consumers can style groups via::part(toolbar-group)directly withpadding/background/border— nodisplay: contentsworkaround required, since the wrapper participates in layout (display: flex; align-items: center; gap: 2px).ToolbarLayoutConfig.groupsand the editor-levelToolbarConfig.groupsnow accept a backwards-compatible union per entry: either aReadonlyArray<Plugin>/ReadonlyArray<string>(existing tuple form, unchanged) or an object{ plugins, label? }. Whenlabelis set, the wrapper additionally receivesrole="group"andaria-label="<label>"so assistive technology announces the cluster by name; without a label the wrapper stays role-less, matching the W3C ARIA APG toolbar example for unlabeled sub-groups. Separators continue to be rendered between groups, never inside, and empty / fully-hidden groups produce no wrapper at all. Stale wrappers are removed on every re-render, so runtimeconfigurePlugintoggles cannot accumulate orphan DOM. NewToolbarGroupConfigtype exported from@notectl/core(packages/core/src/plugins/toolbar/index.ts) for downstream typing. -
Structured theming contract: component-scoped tokens + CSS Shadow Parts (#122, resolves #120) —
<notectl-editor>now exposes a three-tier theming cascade across every component instead of the previous mix of one global token plus an ad-hoc per-component opt-in. Every CSS rule that previously readvar(--notectl-<global>)directly is now layered asvar(--notectl-<component>-<prop>, var(--notectl-<global>, <hard-coded-fallback>)), so consumers can override at exactly the granularity they need: existing global tokens continue to cascade to every component unchanged, new component-scoped tokens (--notectl-table-border,--notectl-table-cell-bg,--notectl-table-header-bg,--notectl-blockquote-border,--notectl-blockquote-bg,--notectl-toolbar-button-bg/-fg/-hover-bg/-active-bg/-active-fg) override a single component without touching the rest, and a hard-coded fallback keeps each component usable when neither is set. The per-table inline--ntbl-border-colorset by the toolbar's "Border color" action retains highest precedence so user customizations survive theme switches. Documented public tokens (--notectl-bg,--notectl-fg,--notectl-fg-muted,--notectl-border,--notectl-border-focus,--notectl-primary,--notectl-primary-fg,--notectl-focus-ring) are declared with@propertyso DevTools surfaces them as first-class CSS variables and invalid values fall back to a typedinitial-valueinstead of silently breaking downstream rules. The editor additionally exposes a stable CSS Shadow Parts surface —editor,content,plugin-container(+plugin-container-top/plugin-container-bottommodifiers),toolbar,toolbar-button(+toolbar-button-active/toolbar-overflow-buttonmodifiers),toolbar-divider,table,table-row,table-cell,code-block,code-block-header,code-block-content,blockquote— so consumers can target structural elements via::part()without piercing the shadow DOM or forking the editor. Modifier parts (e.g.toolbar-button-active) are kept in sync with their ARIA counterparts (aria-pressed) and never drift. A new@media (forced-colors: active)block ineditor/styles/base.tsmaps editor borders, focus rings, and node-selection outlines onto Windows High Contrast / system-color palette so the editor stays legible under OS-level accessibility settings. The--ntbl-border-colormechanism remains supported as the highest-priority fallback, so the table's "Border color" toolbar action is unaffected. No exports removed, no global tokens renamed, no DOM structure changes outside additivepartattributes — fully backwards compatible. -
VS Code–style editing inside code blocks: auto-indent, multi-line indent, and bracket pairing —
CodeBlockPlugingains three coordinated editing behaviors that bring it in line with what users expect from a modern IDE. Auto-indent onEnterinherits the current line's leading whitespace, adds one extra indent unit after an open bracket / brace / paren, and expands the cursor-between-pair pattern{|}into the three-line block form (open brace, indented empty line with the cursor, dedented close brace) — typing a close character on a whitespace-only line dedents by one step, or collapses the gap to consume an auto-paired close if one is in flight. Multi-line indent rebindsTab/Shift-Tabso a selection spanning ≥ 2 lines indents every covered line (not just the column where the caret happens to sit) while preserving the selection direction; a range within a single line replaces the selection with one indent unit, matching VS Code's behaviour exactly. Bracket pairing introduces auto-pair on open, overtype on tracked auto-paired close characters (so the user's)is consumed by an existing close instead of producing))), wrap-selection ("foo"→("foo")when typing(over a selection), and pair-delete onBackspace. Quote handling follows VS Code's heuristics: a stray apostrophe inside an English contraction is not paired, and quotes inside an existing string or comment token are suppressed via the newSyntaxHighlighterService.getTokenAt(blockId, offset)lookup so the highlighter and the editor share a single source of truth about token boundaries. Behaviour is configurable per-plugin viaindent.{mode, useSpaces, spaceCount}andpairing.{brackets, quotes, overtype, deletePair, surround}; the legacy top-leveluseSpaces/spaceCountoptions are kept as a fallback. Three plugin-API additions land alongside the feature:TextInputInterceptorinmodel/(parallel toPasteInterceptor, wired throughPluginContext,MiddlewareChain, andInputHandler) lets plugins intercept and rewrite text input before it becomes a transaction;PluginContext.getCompositionState()exposes the existingCompositionStateso plugins can guard against IME suppression; andpluginHarnessgains anotifyStateChangeoption soonStateChange-driven flows (the pair-stack migration in particular) can be tested in isolation. The internal pair-stack usesTransaction.mappingwithassoc = +1to migrate tracked open/close positions through everyStepMapvariant introduced in #118, and a deferred-op queue ensures fresh push/take operations apply in post-transaction position space; the interceptor flushes the queue on entry to drop ops left behind by middleware-suppressed transactions. Accessibility: all new key bindings respect the existing roving-tabindex and screenreader announcement surface; no new focus traps. -
Generic position-mapping primitive (
StepMap+Mapping) — Steps now carry their own position-space mapping via agetMap(docBefore): StepMapentry in theStepHandlerregistry, covering all 14 step types as a single discriminated union of five categories:identity(mark / attribute / schema / block-type / block-insertion),shift([from, to) → newLenwithin a block — unifiesinsertText/deleteText/insertInlineNode/removeInlineNodeas one primitive),split,merge, andblockRemoval(walks descendants).Transaction.mapping: Mappingis now a required field, populated automatically as steps are appended inTransactionBuilder.advanceDoc, and composes per-step maps withmap(pos, assoc?),mapResult(pos, assoc?),mapRange(range, assocFrom?, assocTo?),appendMap, andappendMapping. Theassocargument (-1sticky-left,+1sticky-right) mirrors ProseMirror semantics.mapSelection(sel, mapping)exists for all threeEditorSelectionvariants (text / node / gap) and collapses-aware (a cursor at a split boundary stays collapsed rather than tearing across two blocks).HistoryManagertracks aninterveningMappingper group — extended on every new-grouppushand via a newrecordIntervening(mapping)API for out-of-band transactions — soundofolds the restored selection through interleaved edits instead of relying onvalidateSelection's blind numeric clamp. New public exports from@notectl/core:Mapping,StepMap,IdentityMap/ShiftMap/SplitMap/MergeMap/BlockRemovalMap,Assoc,MapResult,PositionRange,IDENTITY_MAP,mapPositionThroughStep,collectRemovedBlockIds,mapSelection,mapTextSelection,getStepMap. Unblocks comment / annotation anchors, suggestion-mode / track-changes, remote cursors, and selection restoration under collaborative editing; lays groundwork for OT / CRDT rebasing (Phase 3, separate effort). -
TypeScript / TSX language support (#114) —
CodeBlockPluginnow ships with built-in TypeScript syntax highlighting andSmartPastePluginwith TypeScript content detection, alongside the existing Java / JSON / XML support. The newTYPESCRIPT_LANGUAGEdefinition covers modern TS — comments, escape-aware strings and template literals, decorators, the full keyword set incl.satisfies/using/infer/keyof/asserts/out/override, numeric literals with_separators andBigIntnsuffix (hex/octal/binary/decimal/scientific), and modern operators (=>,??,??=,?.,...,**=,>>>=,&&=,||=). Word boundaries are enforced so identifiers likeinterfaceNameare not mis-tokenized as keywords. The newTypeScriptDetectorscores ES imports/exports,type/interface/enum/namespace/declare,const/letbindings, arrow functions, optional chaining, nullish coalescing, template literals, type annotations, `as...
v2.1.2
Fixed
- CI on
mainred — Bootstrap-modal-like dropdown-position spec failed (#68 regression) — The vanillajs playground commit98239d1addednotectl-editor { height: 100%; min-height: 0; --notectl-content-min-height: 0 }to make the editor fill a new 700px grid row. Those rules apply globally to the host element, including indropdown-position.spec.tswhich reparents#editor-containerinto a Bootstrap-modal-like dialog with no explicit height. There,height: 100%resolved to auto, the editor shrank to its content and the heading dropdown popup ended up mispositioned (gap: -148px) inside the transformed containing block. Thegrid-template-rows: 700px autorow alone keeps the actions bar pinned and the inspector height-bounded; the editor sizes to its intrinsic height (--notectl-content-min-height: 460px) inside the row, identical to v2.0.x behaviour.
Full Changelog: v2.1.1...v2.1.2
v2.1.1
Fixed
- Code-block input rule loses the
languageattribute (#113) — Typing```<lang>+ space converts a paragraph to acode_blockvia a single transaction containing bothdeleteTextandsetBlockType. TheTextDirectionAutoPlugin's auto-detect middleware appended its ownsetNodeAttrstep built from a pre-transactionblock.attrssnapshot;applySetNodeAttrhas full-replace semantics, so by application time the snapshot was stale and clobbered the{ language, backgroundColor }attrs written bysetBlockType. The result was a code block withlanguage: '', no language label and no syntax highlighting. Auto-detect now pre-scans the transaction for blocks already targeted bysetBlockTypeorsetNodeAttrand skips its emission for those blocks — symmetrical to the guards already present in thepreserve-dirandinherit-dirmiddlewares. The next text-only transaction re-detects direction normally. Generally fixes the same data-loss pattern for any future input rule or command that combines text edits with a block-type or attr change in a single atomic transaction.
Full Changelog: v2.1.0...v2.1.1
v2.1.0
Added
BidiIsolationPlugin(@notectl/core/plugins/bidi-isolation) — Inline<bdi>mark,toggleBidi*/removeBidi/toggleBidiIsolationcommands, inline toolbar dropdown,Mod-Shift-B. Works standalone; optionally consumesTextDirectionServicesotoggleBidiIsolationpicks the direction opposite to the surrounding block.TextDirectionAutoPlugin(@notectl/core/plugins/text-direction-auto) — Headless plugin that registers the three transaction middlewares (preserve / auto-detect / inherit) previously fused into the heavyTextDirectionPlugin. Hard-depends onTextDirectionPluginviadependencies = ['text-direction']. Each middleware can be disabled individually via config.TextDirectionService+TEXT_DIRECTION_SERVICE_KEY— Typed service exposingdirectableTypesandgetBlockDir, registered byTextDirectionPluginfor cross-plugin consumption.makeBlockStatetest helper intest/TestUtils.ts— Convenience wrapper aroundstateBuilder()for plugin tests that only need a static document.
Changed
TextDirectionPluginis now lean (@notectl/core/plugins/text-direction) — Owns only the block-leveldirattribute, toolbar dropdown,Mod-Shift-D, andShiftDirectionHandler. Inline bidi and middleware behaviour moved to the two new plugins above.FullPresetregisters all three so behaviour for preset users is unchanged.- Locale split —
TextDirectionLocalekeeps the 8 block-direction strings;BidiIsolationLocale(new) owns the 6 inline-bidi strings. All 9 language files split mechanically.
Removed (BREAKING)
TextDirectionAdvancedPluginand the@notectl/core/plugins/text-direction-advancedsubpath export — replace by registeringTextDirectionPlugin+BidiIsolationPlugin+TextDirectionAutoPluginexplicitly (or usecreateFullPreset).TextDirectionCorePlugin—TextDirectionPluginis now the lean class; importingTextDirectionCorePluginno longer resolves.- Inline bidi capabilities (
<bdi>mark,toggleBidi*commands,Mod-Shift-B, auto-detect / inherit / preserve middleware) when only importingTextDirectionPlugin— those moved to the two new plugin entries. Previously the docs claimed the lean and heavy classes were "the same" — they were not, and consumers callingexecuteCommand('toggleBidiLTR')on the lean variant silently receivedfalse. The new split makes the contract honest.
Migration
// before
import { TextDirectionAdvancedPlugin } from '@notectl/core/plugins/text-direction-advanced';
new TextDirectionAdvancedPlugin();
// after
import { TextDirectionPlugin } from '@notectl/core/plugins/text-direction';
import { BidiIsolationPlugin } from '@notectl/core/plugins/bidi-isolation';
import { TextDirectionAutoPlugin } from '@notectl/core/plugins/text-direction-auto';
[new TextDirectionPlugin(), new BidiIsolationPlugin(), new TextDirectionAutoPlugin()];If you build via createFullPreset(), no migration is needed — the preset now registers all three plugins, and you actually gain the inline-bidi toolbar and auto-detect middleware that the previous lean-only registration silently lacked.
Performance
TextDirectionAutoPlugin.handleInsertTextskips thegetBlockTextstring allocation in the early-return path that protects explicit non-auto block direction. Switches the guard togetBlockLength, which is O(n) over inline children with no string allocation. Side benefit: blocks whose only inline content is anInlineNodeare now correctly protected from re-detection.
Full Changelog: v2.0.10...v2.1.0
v2.0.9
Fixed
- Signal-form cursor reset (#103) — The caret no longer jumps to the start of the document when
setJSON(getJSON())round-trips unchanged content. This was breaking Angular signal-form bindings, where every keystroke triggers such a round-trip.setContentHTML()benefits from the same preservation since it routes through the same code path.
Changed
EditorView.replaceState()preserves the caret. The prior selection is carried over (validated against the new document) instead of adopting the selection from the incoming state. Undo/redo history is still cleared. This is a behavior change on the publicEditorViewAPI — any selection carried on the state passed toreplaceState()is now ignored.
Added
EditorState.withSelection(selection)— Returns a new state with the given selection validated and clamped against the current document. Useful for callers that need to adopt an external selection safely.
Full Changelog: v2.0.8...v2.0.9