Skip to content

Commit

Permalink
Add outerDecorations facet
Browse files Browse the repository at this point in the history
FEATURE: The new `EditorView.outerDecorations` facet can be used to
provide decorations that should always be at the bottom of the precedence
stack.
  • Loading branch information
marijnh committed Dec 28, 2023
1 parent 39411bc commit 55257cc
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 2 deletions.
11 changes: 10 additions & 1 deletion src/docview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Decoration, DecorationSet, WidgetType, addRange, MarkDecoration} from ".
import {getAttrs} from "./attributes"
import {clientRectsFor, isEquivalentPosition, maxOffset, Rect, scrollRectIntoView,
getSelection, hasSelection, textRange, DOMSelectionState} from "./dom"
import {ViewUpdate, decorations as decorationsFacet,
import {ViewUpdate, decorations as decorationsFacet, outerDecorations,
ChangedRange, ScrollTarget, getScrollMargins} from "./extension"
import {EditorView} from "./editorview"
import {Direction} from "./bidi"
Expand Down Expand Up @@ -503,6 +503,15 @@ export class DocView extends ContentView {
let dynamic = this.dynamicDecorationMap[i] = typeof d == "function"
return dynamic ? (d as (view: EditorView) => DecorationSet)(this.view) : d as DecorationSet
})
let dynamicOuter = false, outerDeco = this.view.state.facet(outerDecorations).map((d, i) => {
let dynamic = typeof d == "function"
if (dynamic) dynamicOuter = true
return dynamic ? (d as (view: EditorView) => DecorationSet)(this.view) : d as DecorationSet
})
if (outerDeco.length) {
this.dynamicDecorationMap[allDeco.length] = dynamicOuter
allDeco.push(RangeSet.join(outerDeco))
}
for (let i = allDeco.length; i < allDeco.length + 3; i++) this.dynamicDecorationMap[i] = false
return this.decorations = [
...allDeco,
Expand Down
11 changes: 10 additions & 1 deletion src/editorview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {ViewUpdate, styleModule,
contentAttributes, editorAttributes, AttrSource,
clickAddsSelectionRange, dragMovesSelection, mouseSelectionStyle,
exceptionSink, updateListener, logException,
viewPlugin, ViewPlugin, PluginValue, PluginInstance, decorations, atomicRanges,
viewPlugin, ViewPlugin, PluginValue, PluginInstance, decorations, outerDecorations, atomicRanges,
scrollMargins, MeasureRequest, editable, inputHandler, focusChangeEffect, perLineTextDirection,
scrollIntoView, UpdateFlag, ScrollTarget, bidiIsolatedRanges, getIsolatedRanges} from "./extension"
import {theme, darkTheme, buildTheme, baseThemeID, baseLightID, baseDarkID, lightDarkIDs, baseTheme} from "./theme"
Expand Down Expand Up @@ -996,6 +996,15 @@ export class EditorView {
/// [`EditorView.atomicRanges`](#view.EditorView^atomicRanges).
static decorations = decorations

/// Facet that works much like
/// [`decorations`](#view.EditorView^decorations), but puts its
/// inputs at the very bottom of the precedence stack, meaning mark
/// decorations provided here will only be split by other, partially
/// overlapping \`outerDecorations\` ranges, and wrap around all
/// regular decorations. Use this for mark elements that should, as
/// much as possible, remain in one piece.
static outerDecorations = outerDecorations

/// Used to provide ranges that should be treated as atoms as far as
/// cursor motion is concerned. This causes methods like
/// [`moveByChar`](#view.EditorView.moveByChar) and
Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ export const contentAttributes = Facet.define<AttrSource>()
// Provide decorations
export const decorations = Facet.define<DecorationSet | ((view: EditorView) => DecorationSet)>()

export const outerDecorations = Facet.define<DecorationSet | ((view: EditorView) => DecorationSet)>()

export const atomicRanges = Facet.define<(view: EditorView) => RangeSet<any>>()

export const bidiIsolatedRanges = Facet.define<DecorationSet | ((view: EditorView) => DecorationSet)>()
Expand Down
11 changes: 11 additions & 0 deletions test/webtest-draw-decoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ describe("EditorView decoration", () => {
ist(b[0].parentNode, a[0])
})

it("draws outer decorations around others", () => {
let cm = tempView("abcde", [
decos(Decoration.set([d(1, 2, {class: "a"}), d(3, 4, {class: "a"})])),
EditorView.outerDecorations.of(Decoration.set(Decoration.mark({tagName: "strong"}).range(1, 4))),
EditorView.outerDecorations.of(Decoration.set(Decoration.mark({tagName: "var"}).range(0, 5))),
EditorView.outerDecorations.of(Decoration.set(Decoration.mark({tagName: "em"}).range(2, 3)))
])
ist((cm.contentDOM.firstChild as HTMLElement).innerHTML,
`<var>a<strong><span class="a">b</span><em>c</em><span class="a">d</span></strong>e</var>`)
})

it("properly updates the viewport gap when changes fall inside it", () => {
let doc = "a\n".repeat(500)
let cm = decoEditor(doc, [d(600, 601, "x")])
Expand Down

0 comments on commit 55257cc

Please sign in to comment.