@charcuterie/ui 3.3.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/Field/Field.d.ts.map +1 -1
  2. package/dist/Field/Field.js +19 -1
  3. package/dist/Field/Field.js.map +1 -1
  4. package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -1
  5. package/dist/MarkdownEditor/MarkdownEditor.js +21 -8
  6. package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -1
  7. package/dist/MarkdownEditor/markdownCommands.d.ts +32 -0
  8. package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -1
  9. package/dist/MarkdownEditor/markdownCommands.js +47 -0
  10. package/dist/MarkdownEditor/markdownCommands.js.map +1 -1
  11. package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -1
  12. package/dist/MarkdownEditor/markdownSpans.js +30 -0
  13. package/dist/MarkdownEditor/markdownSpans.js.map +1 -1
  14. package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.d.ts +93 -0
  15. package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.d.ts.map +1 -0
  16. package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.js +545 -0
  17. package/dist/markdownEditorCodeMirror/MarkdownEditorCodeMirror.js.map +1 -0
  18. package/dist/markdownEditorCodeMirror/index.d.ts +16 -0
  19. package/dist/markdownEditorCodeMirror/index.d.ts.map +1 -0
  20. package/dist/markdownEditorCodeMirror/index.js +14 -0
  21. package/dist/markdownEditorCodeMirror/index.js.map +1 -0
  22. package/dist/markdownEditorCodeMirror/livePreview.d.ts +40 -0
  23. package/dist/markdownEditorCodeMirror/livePreview.d.ts.map +1 -0
  24. package/dist/markdownEditorCodeMirror/livePreview.js +428 -0
  25. package/dist/markdownEditorCodeMirror/livePreview.js.map +1 -0
  26. package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts +128 -0
  27. package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts.map +1 -0
  28. package/dist/markdownEditorCodeMirror/livePreviewRanges.js +345 -0
  29. package/dist/markdownEditorCodeMirror/livePreviewRanges.js.map +1 -0
  30. package/package.json +36 -1
  31. package/src/Field/Field.test.tsx +35 -0
  32. package/src/Field/Field.tsx +18 -0
  33. package/src/MarkdownEditor/MarkdownEditor.tsx +33 -7
  34. package/src/MarkdownEditor/markdownCommands.test.ts +55 -0
  35. package/src/MarkdownEditor/markdownCommands.ts +60 -0
  36. package/src/MarkdownEditor/markdownSpans.test.ts +65 -0
  37. package/src/MarkdownEditor/markdownSpans.ts +35 -0
  38. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +144 -0
  39. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.stories.tsx +147 -0
  40. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.tsx +866 -0
  41. package/src/markdownEditorCodeMirror/index.ts +26 -0
  42. package/src/markdownEditorCodeMirror/livePreview.ts +535 -0
  43. package/src/markdownEditorCodeMirror/livePreviewRanges.test.ts +431 -0
  44. package/src/markdownEditorCodeMirror/livePreviewRanges.ts +581 -0
  45. package/src/sourceRules.test.ts +20 -0
@@ -0,0 +1,428 @@
1
+ /**
2
+ * The CodeMirror half of live preview: descriptors → decorations,
3
+ * two widgets, and a theme that reads Charcuterie tokens.
4
+ *
5
+ * `livePreviewRanges.ts` decided *what* to decorate and is pure.
6
+ * This file owns everything that needs `@codemirror/view`, which is
7
+ * also everything that cannot be tested in this package's Node-only
8
+ * Vitest project — so it is kept deliberately thin and mechanical.
9
+ *
10
+ * ### Why the theme uses `var(--color-…)` and not Tailwind classes
11
+ *
12
+ * Every other visual in this package is a Tailwind utility reading
13
+ * a token. CodeMirror is the exception, and for a structural
14
+ * reason: it generates its own scoped class names and injects its
15
+ * own stylesheet, so the DOM these decorations land on is not DOM
16
+ * Tailwind ever scans. Referencing the custom properties directly
17
+ * keeps a scheme flip a repaint — the same behaviour, one layer
18
+ * lower — and avoids a `safelist` that would rot the first time a
19
+ * class here changed.
20
+ *
21
+ * The properties below are the generated token names, not invented
22
+ * ones. That distinction has already cost this fleet a release:
23
+ * Docket shipped invisible priority bars from `--color-danger-9`, a
24
+ * scale that does not exist, and nothing errored. A custom property
25
+ * that resolves to nothing paints nothing, silently.
26
+ */
27
+ import { syntaxTree } from "@codemirror/language";
28
+ import { StateEffect, StateField } from "@codemirror/state";
29
+ import { Decoration, EditorView, ViewPlugin, WidgetType, } from "@codemirror/view";
30
+ import { toLivePreviewRanges } from "./livePreviewRanges.js";
31
+ /** Marks a rendered link so the click handler can find its target. */
32
+ const URL_ATTRIBUTE = "data-charcuterie-url";
33
+ /** Toggle the "edit Markdown" mode. */
34
+ export const setLivePreviewRawMode = StateEffect.define();
35
+ /**
36
+ * Raw mode as editor state rather than a React prop threaded down.
37
+ *
38
+ * It has to live here because the decoration plugin reads it on
39
+ * every update, and because a keybinding should be able to flip it
40
+ * without a round trip through React.
41
+ */
42
+ export const livePreviewRawModeField = StateField.define({
43
+ create: () => false,
44
+ update: (isRawMode, transaction) => {
45
+ let isNextRawMode = isRawMode;
46
+ for (const effect of transaction.effects) {
47
+ if (effect.is(setLivePreviewRawMode)) {
48
+ isNextRawMode = effect.value;
49
+ }
50
+ }
51
+ return isNextRawMode;
52
+ },
53
+ });
54
+ const LINE_CLASSES = {
55
+ blockquote: "cm-md-blockquote",
56
+ code: "cm-md-codeblock",
57
+ heading1: "cm-md-heading1",
58
+ heading2: "cm-md-heading2",
59
+ heading3: "cm-md-heading3",
60
+ heading4: "cm-md-heading4",
61
+ heading5: "cm-md-heading5",
62
+ heading6: "cm-md-heading6",
63
+ table: "cm-md-table",
64
+ };
65
+ const MARK_CLASSES = {
66
+ autolink: "cm-md-link",
67
+ code: "cm-md-code",
68
+ emphasis: "cm-md-emphasis",
69
+ linkText: "cm-md-link",
70
+ strikethrough: "cm-md-strikethrough",
71
+ strong: "cm-md-strong",
72
+ url: "cm-md-url",
73
+ };
74
+ /**
75
+ * An image, standing in for its own markup.
76
+ *
77
+ * `eq` compares by URL *and* alt so scrolling past an image reuses
78
+ * the same `<img>` instead of refetching it, while an edit to
79
+ * either still rebuilds. Without it every keystroke elsewhere in
80
+ * the document flashes every image on screen.
81
+ */
82
+ class ImageWidget extends WidgetType {
83
+ url;
84
+ alt;
85
+ constructor(url, alt) {
86
+ super();
87
+ this.url = url;
88
+ this.alt = alt;
89
+ }
90
+ eq(other) {
91
+ return other.url === this.url && other.alt === this.alt;
92
+ }
93
+ toDOM(view) {
94
+ const image = document.createElement("img");
95
+ image.setAttribute("src", this.url);
96
+ image.setAttribute("alt", this.alt);
97
+ image.className = "cm-md-image";
98
+ // The document's height is unknown until the image decodes, and
99
+ // CodeMirror caches line heights. Without a remeasure the lines
100
+ // after a tall image overlap it until something else forces a
101
+ // layout.
102
+ image.addEventListener("load", () => {
103
+ view.requestMeasure();
104
+ });
105
+ return image;
106
+ }
107
+ }
108
+ /**
109
+ * A task checkbox, standing in for `[ ]` / `[x]`.
110
+ *
111
+ * The position is read back from the DOM with `posAtDOM` rather
112
+ * than captured in the constructor: a widget outlives the document
113
+ * offsets it was built at, and a stale `from` writes the checkbox
114
+ * into whatever moved into that slot.
115
+ */
116
+ class TaskWidget extends WidgetType {
117
+ isChecked;
118
+ label;
119
+ constructor(isChecked, label) {
120
+ super();
121
+ this.isChecked = isChecked;
122
+ this.label = label;
123
+ }
124
+ eq(other) {
125
+ return (other.isChecked === this.isChecked &&
126
+ other.label === this.label);
127
+ }
128
+ toDOM(view) {
129
+ const checkbox = document.createElement("input");
130
+ checkbox.type = "checkbox";
131
+ checkbox.checked = this.isChecked;
132
+ checkbox.className = "cm-md-task";
133
+ /**
134
+ * The item's own text, as the checkbox's name.
135
+ *
136
+ * A bare `<input type="checkbox">` inside a `contenteditable`
137
+ * has no wrapping `<label>` to inherit from and no `for`
138
+ * pointing at it, so without this it announces as an unlabelled
139
+ * checkbox — and a task list is exactly where "checkbox,
140
+ * checked" with no name is useless. The text is right there on
141
+ * the line; using it means the widget says what the sighted
142
+ * reader sees.
143
+ */
144
+ checkbox.setAttribute("aria-label", this.label === "" ? "Task" : this.label);
145
+ checkbox.addEventListener("mousedown", (event) => {
146
+ // The checkbox owns the click; without this CodeMirror also
147
+ // moves the caret into the widget's slot on the way past.
148
+ event.stopPropagation();
149
+ });
150
+ checkbox.addEventListener("change", () => {
151
+ const from = view.posAtDOM(checkbox);
152
+ view.dispatch({
153
+ changes: {
154
+ from,
155
+ insert: this.isChecked ? "[ ]" : "[x]",
156
+ to: from + 3,
157
+ },
158
+ });
159
+ });
160
+ return checkbox;
161
+ }
162
+ ignoreEvent() {
163
+ return false;
164
+ }
165
+ }
166
+ const toDecorations = (view) => {
167
+ const isRawMode = view.state.field(livePreviewRawModeField, false);
168
+ const text = view.state.doc.toString();
169
+ /**
170
+ * An unfocused editor reveals nothing.
171
+ *
172
+ * Without this the caret's default resting place — offset 0 —
173
+ * counts as "inside" whatever construct starts the document, so a
174
+ * task whose description opens with a heading rendered its `#` on
175
+ * load and no other heading's. It looked like a concealment bug
176
+ * and was really a focus bug: there is no caret in the document
177
+ * until someone puts one there.
178
+ */
179
+ const selections = view.hasFocus
180
+ ? view.state.selection.ranges.map((range) => ({
181
+ from: range.from,
182
+ to: range.to,
183
+ }))
184
+ : [];
185
+ const decorations = [];
186
+ for (const visible of view.visibleRanges) {
187
+ for (const range of toLivePreviewRanges({
188
+ from: visible.from,
189
+ isRawMode: isRawMode ?? false,
190
+ selections,
191
+ text,
192
+ to: visible.to,
193
+ tree: syntaxTree(view.state),
194
+ })) {
195
+ switch (range.type) {
196
+ case "image": {
197
+ decorations.push(Decoration.replace({
198
+ widget: new ImageWidget(range.url, range.alt),
199
+ }).range(range.from, range.to));
200
+ break;
201
+ }
202
+ case "line": {
203
+ decorations.push(Decoration.line({
204
+ class: LINE_CLASSES[range.lineKind],
205
+ }).range(range.from));
206
+ break;
207
+ }
208
+ case "mark": {
209
+ decorations.push(Decoration.mark({
210
+ attributes: range.url
211
+ ? { [URL_ATTRIBUTE]: range.url }
212
+ : undefined,
213
+ class: MARK_CLASSES[range.markKind],
214
+ }).range(range.from, range.to));
215
+ break;
216
+ }
217
+ case "marker": {
218
+ decorations.push(range.isConcealed
219
+ ? Decoration.replace({}).range(range.from, range.to)
220
+ : Decoration.mark({
221
+ class: "cm-md-marker",
222
+ }).range(range.from, range.to));
223
+ break;
224
+ }
225
+ case "task": {
226
+ decorations.push(Decoration.replace({
227
+ widget: new TaskWidget(range.isChecked, range.label),
228
+ }).range(range.from, range.to));
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ }
234
+ // `true` sorts. The tree walk emits in tree order, which is not
235
+ // the same as decoration order once line decorations and inline
236
+ // replacements interleave, and an unsorted set throws.
237
+ return Decoration.set(decorations, true);
238
+ };
239
+ const livePreviewPlugin = ViewPlugin.fromClass(class {
240
+ decorations;
241
+ constructor(view) {
242
+ this.decorations = toDecorations(view);
243
+ }
244
+ update(update) {
245
+ // Selection is in the list because concealment depends on the
246
+ // caret: moving it with an arrow key changes what is painted
247
+ // even though the document did not change.
248
+ if (update.docChanged ||
249
+ update.selectionSet ||
250
+ update.viewportChanged ||
251
+ // Focus is a decoration input now — see `toDecorations`.
252
+ update.focusChanged ||
253
+ update.transactions.some((transaction) => transaction.effects.some((effect) => effect.is(setLivePreviewRawMode)))) {
254
+ this.decorations = toDecorations(update.view);
255
+ }
256
+ }
257
+ }, {
258
+ decorations: (plugin) => plugin.decorations,
259
+ });
260
+ /**
261
+ * Open the link under the pointer.
262
+ *
263
+ * Plain click, not Ctrl+click, and that is Obsidian's behaviour
264
+ * rather than VS Code's — in a surface where the markup is hidden,
265
+ * a link that looks like a link should act like one. The cost is
266
+ * that clicking link *text* cannot place the caret there; arrow
267
+ * into it from the edge, or use the raw-markdown toggle. That is
268
+ * the same trade Obsidian makes and it is documented in the mdx.
269
+ *
270
+ * `noopener` is not optional: `target="_blank"` without it hands
271
+ * the opened page a live `window.opener` back into this one.
272
+ */
273
+ const openLinkOnClick = EditorView.domEventHandlers({
274
+ mousedown: (event) => {
275
+ const target = event.target;
276
+ if (!(target instanceof HTMLElement)) {
277
+ return false;
278
+ }
279
+ const url = target
280
+ .closest(`[${URL_ATTRIBUTE}]`)
281
+ ?.getAttribute(URL_ATTRIBUTE);
282
+ if (!url) {
283
+ return false;
284
+ }
285
+ event.preventDefault();
286
+ window.open(url, "_blank", "noopener,noreferrer");
287
+ return true;
288
+ },
289
+ });
290
+ /**
291
+ * The paint.
292
+ *
293
+ * Headings scale here — which is the one thing the textarea
294
+ * version could never do, because a `<textarea>` and a layer behind
295
+ * it only stay registered while every glyph keeps its advance
296
+ * width. CodeMirror owns the caret, so size is free.
297
+ */
298
+ const livePreviewTheme = EditorView.theme({
299
+ /**
300
+ * Prose is proportional, and only code is monospaced.
301
+ *
302
+ * The textarea sibling has no choice here — its painted layer only
303
+ * stays registered with the caret while every glyph keeps its
304
+ * advance width, which in practice means one monospaced family for
305
+ * the whole document. This surface has no such constraint, and a
306
+ * description full of prose set in a code font is the single
307
+ * biggest thing that makes an editor *feel* like a code editor
308
+ * rather than like a document.
309
+ *
310
+ * It has to be `.cm-scroller`, not `&`. CodeMirror's own base
311
+ * theme sets `font-family: monospace` there, and a rule on the
312
+ * wrapper loses to it — silently, and it looks like the token
313
+ * failed to resolve rather than like a specificity problem.
314
+ */
315
+ ".cm-scroller": {
316
+ fontFamily: "var(--font-sans)",
317
+ },
318
+ /**
319
+ * …except in raw mode, where monospace is the honest choice:
320
+ * "Markdown source" is a *source* view, and source is code.
321
+ */
322
+ "&.cm-md-raw .cm-scroller": {
323
+ fontFamily: "var(--font-mono)",
324
+ },
325
+ ".cm-md-blockquote": {
326
+ borderInlineStart: "3px solid var(--color-intent-accent-border)",
327
+ color: "var(--color-content-secondary)",
328
+ paddingInlineStart: "0.75em",
329
+ },
330
+ ".cm-md-code": {
331
+ color: "var(--color-intent-info-content)",
332
+ fontFamily: "var(--font-mono)",
333
+ },
334
+ ".cm-md-codeblock": {
335
+ backgroundColor: "var(--color-surface-sunken)",
336
+ fontFamily: "var(--font-mono)",
337
+ },
338
+ ".cm-md-emphasis": {
339
+ fontStyle: "italic",
340
+ },
341
+ ".cm-md-heading1": {
342
+ fontSize: "1.75em",
343
+ fontWeight: "700",
344
+ lineHeight: "1.25",
345
+ },
346
+ ".cm-md-heading2": {
347
+ fontSize: "1.5em",
348
+ fontWeight: "700",
349
+ lineHeight: "1.3",
350
+ },
351
+ ".cm-md-heading3": {
352
+ fontSize: "1.25em",
353
+ fontWeight: "600",
354
+ lineHeight: "1.35",
355
+ },
356
+ ".cm-md-heading4": {
357
+ fontSize: "1.1em",
358
+ fontWeight: "600",
359
+ },
360
+ ".cm-md-heading5": {
361
+ fontWeight: "600",
362
+ },
363
+ ".cm-md-heading6": {
364
+ color: "var(--color-content-secondary)",
365
+ fontWeight: "600",
366
+ },
367
+ ".cm-md-image": {
368
+ borderRadius: "var(--radius-md)",
369
+ display: "inline-block",
370
+ maxWidth: "100%",
371
+ verticalAlign: "top",
372
+ },
373
+ ".cm-md-link": {
374
+ color: "var(--color-intent-accent-content)",
375
+ cursor: "pointer",
376
+ textDecoration: "underline",
377
+ },
378
+ /**
379
+ * The dimmed marker.
380
+ *
381
+ * A token role, never `opacity`. Fading text over a themed
382
+ * background composites to a colour nobody audited, and axe
383
+ * measures the composited value — `content-secondary` at 60% came
384
+ * out at 3.77:1 in the sibling component and failed AA.
385
+ */
386
+ ".cm-md-marker": {
387
+ color: "var(--color-content-muted)",
388
+ },
389
+ ".cm-md-strikethrough": {
390
+ color: "var(--color-content-secondary)",
391
+ textDecoration: "line-through",
392
+ },
393
+ ".cm-md-strong": {
394
+ fontWeight: "700",
395
+ },
396
+ ".cm-md-table": {
397
+ fontFamily: "var(--font-mono)",
398
+ },
399
+ ".cm-md-task": {
400
+ accentColor: "var(--color-intent-accent-solid)",
401
+ marginInlineEnd: "0.35em",
402
+ verticalAlign: "middle",
403
+ },
404
+ ".cm-md-url": {
405
+ color: "var(--color-content-muted)",
406
+ },
407
+ });
408
+ /**
409
+ * Everything the live-preview surface needs, as one extension.
410
+ *
411
+ * Order matters only in that the theme must be present for the
412
+ * decoration classes to mean anything; CodeMirror resolves the rest.
413
+ */
414
+ /**
415
+ * Raw mode, as a class on the editor, so the theme above can react
416
+ * to it in CSS instead of every rule becoming a decoration.
417
+ */
418
+ const rawModeClass = EditorView.editorAttributes.of((view) => view.state.field(livePreviewRawModeField, false)
419
+ ? { class: "cm-md-raw" }
420
+ : null);
421
+ export const livePreview = () => [
422
+ livePreviewRawModeField,
423
+ livePreviewPlugin,
424
+ livePreviewTheme,
425
+ openLinkOnClick,
426
+ rawModeClass,
427
+ ];
428
+ //# sourceMappingURL=livePreview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livePreview.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreview.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Markdown → the ranges a live-preview surface decorates, as pure
3
+ * tree-in / descriptors-out.
4
+ *
5
+ * This is the half of the CodeMirror editor that decides what a
6
+ * document *looks* like, and it is deliberately the half with no
7
+ * `@codemirror/view` import in it. Descriptors are plain
8
+ * `{ from, to, type }` objects; turning them into `Decoration`s is
9
+ * `livePreview.ts`'s job. The split buys two things:
10
+ *
11
+ * - **These tests run in Node.** `packages/ui`'s Vitest project is
12
+ * `.ts`-only and has no DOM, on purpose. A test here parses real
13
+ * markdown with the real GFM parser and asserts real offsets —
14
+ * no jsdom, no mount, no story.
15
+ * - **The runtime stays incremental.** Tests hand in
16
+ * `markdownLanguage.parser.parse(text)`; the view hands in
17
+ * `syntaxTree(state)`, which CodeMirror has already reparsed
18
+ * incrementally. Same function, same offsets, and the expensive
19
+ * path is never the one under test.
20
+ *
21
+ * ### What separates this from `markdownSpans.ts`
22
+ *
23
+ * The sibling `MarkdownEditor` paints *behind* a `<textarea>`, so
24
+ * every span it emits has to be metric-neutral — same glyph
25
+ * advances, or the caret shears away from the letters. That is the
26
+ * constraint this module does **not** have. CodeMirror owns the
27
+ * caret and reflows around decorations, so a heading can genuinely
28
+ * be larger, `**` can genuinely be hidden, and an image can
29
+ * genuinely replace its own markup.
30
+ *
31
+ * That is the entire reason this subpath exists, and it is also why
32
+ * it is a subpath: the capability costs ~176 KB gz of optional peer
33
+ * and only a consumer that opts in should pay it.
34
+ *
35
+ * ### The one invariant
36
+ *
37
+ * **Concealment never touches the document.** Every descriptor here
38
+ * describes a *view* over an unmodified markdown string. Hiding
39
+ * `**` does not delete it; replacing `![alt](url)` with an image
40
+ * does not rewrite it. The stored value stays byte-for-byte what
41
+ * the user typed, which is the same no-HTML-in-the-data guarantee
42
+ * the textarea version gets structurally — kept here by never
43
+ * having a serialiser at all.
44
+ */
45
+ import type { Tree } from "@lezer/common";
46
+ /**
47
+ * A block-level treatment applied to a whole line.
48
+ *
49
+ * Headings carry their level because size is the point: this is the
50
+ * "block-level type scaling" the textarea version had to give up.
51
+ */
52
+ export type LivePreviewLineKind = "blockquote" | "code" | "heading1" | "heading2" | "heading3" | "heading4" | "heading5" | "heading6" | "table";
53
+ /** An inline treatment applied to a character range. */
54
+ export type LivePreviewMarkKind = "autolink" | "code" | "emphasis" | "linkText" | "strikethrough" | "strong" | "url";
55
+ export type LivePreviewRange = {
56
+ from: number;
57
+ to: number;
58
+ } & ({
59
+ /** A rendered image, standing in for its own `![alt](url)`. */
60
+ alt: string;
61
+ type: "image";
62
+ url: string;
63
+ } | {
64
+ /** A rendered task checkbox, standing in for `[ ]` / `[x]`. */
65
+ isChecked: boolean;
66
+ /**
67
+ * The item's own text, which becomes the checkbox's
68
+ * accessible name — a checkbox inside a `contenteditable` has
69
+ * no `<label>` to inherit one from.
70
+ */
71
+ label: string;
72
+ type: "task";
73
+ } | {
74
+ /**
75
+ * Syntax the reader does not need: `**`, `#`, `[`, `](…)`.
76
+ *
77
+ * `isConcealed` is the caret's answer, not the parser's. When
78
+ * the selection is inside the construct — or the whole
79
+ * surface is in raw mode — the marker stays in the flow and
80
+ * is merely dimmed, which is what makes the syntax editable
81
+ * at all.
82
+ */
83
+ isConcealed: boolean;
84
+ type: "marker";
85
+ } | {
86
+ lineKind: LivePreviewLineKind;
87
+ type: "line";
88
+ } | {
89
+ markKind: LivePreviewMarkKind;
90
+ type: "mark";
91
+ /** Present on `linkText` and `autolink`; the click target. */
92
+ url?: string;
93
+ });
94
+ /** A selection range, in the two fields this module needs. */
95
+ export type LivePreviewSelection = {
96
+ from: number;
97
+ to: number;
98
+ };
99
+ export type ToLivePreviewRangesOptions = {
100
+ /**
101
+ * Limit the walk to a slice of the document — the view passes its
102
+ * visible ranges, so a long description costs what is on screen
103
+ * rather than what is stored. Omit both and the whole tree is
104
+ * walked, which is what the tests do.
105
+ */
106
+ from?: number;
107
+ /**
108
+ * Raw mode — the "edit Markdown" toggle.
109
+ *
110
+ * Nothing is concealed, nothing is replaced by a widget, and no
111
+ * line is rescaled. Syntax colouring stays, because the point of
112
+ * the toggle is to see the markup, not to lose the highlighting.
113
+ */
114
+ isRawMode?: boolean;
115
+ selections: readonly LivePreviewSelection[];
116
+ text: string;
117
+ to?: number;
118
+ tree: Tree;
119
+ };
120
+ /**
121
+ * Every decoration the document wants, in tree order.
122
+ *
123
+ * Order is not sorted here on purpose — `Decoration.set(ranges,
124
+ * true)` sorts, and doing it twice is the sort of thing that looks
125
+ * free until a document is long.
126
+ */
127
+ export declare const toLivePreviewRanges: ({ from, isRawMode, selections, text, to, tree, }: ToLivePreviewRangesOptions) => LivePreviewRange[];
128
+ //# sourceMappingURL=livePreviewRanges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"livePreviewRanges.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreviewRanges.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAc,IAAI,EAAE,MAAM,eAAe,CAAA;AAErD;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B,YAAY,GACZ,MAAM,GACN,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,GACV,OAAO,CAAA;AAEX,wDAAwD;AACxD,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,MAAM,GACN,UAAU,GACV,UAAU,GACV,eAAe,GACf,QAAQ,GACR,KAAK,CAAA;AAET,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX,GAAG,CACA;IACE,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,OAAO,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,GACD;IACE,+DAA+D;IAC/D,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE;;;;;;;;OAQG;IACH,WAAW,EAAE,OAAO,CAAA;IACpB,IAAI,EAAE,QAAQ,CAAA;CACf,GACD;IACE,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;CACb,GACD;IACE,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CACJ,CAAA;AAED,8DAA8D;AAC9D,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,IAAI,CAAA;CACX,CAAA;AAgID;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,kDAOjC,0BAA0B,KAAG,gBAAgB,EAoS/C,CAAA"}