@charcuterie/ui 3.0.0 → 3.2.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.
- package/README.md +10 -0
- package/dist/Badge/Badge.d.ts +33 -3
- package/dist/Badge/Badge.d.ts.map +1 -1
- package/dist/Badge/Badge.js +20 -2
- package/dist/Badge/Badge.js.map +1 -1
- package/dist/Board/Board.d.ts +119 -0
- package/dist/Board/Board.d.ts.map +1 -0
- package/dist/Board/Board.js +165 -0
- package/dist/Board/Board.js.map +1 -0
- package/dist/Board/BoardCard.d.ts +120 -0
- package/dist/Board/BoardCard.d.ts.map +1 -0
- package/dist/Board/BoardCard.js +77 -0
- package/dist/Board/BoardCard.js.map +1 -0
- package/dist/Board/BoardLaneList.d.ts +103 -0
- package/dist/Board/BoardLaneList.d.ts.map +1 -0
- package/dist/Board/BoardLaneList.js +69 -0
- package/dist/Board/BoardLaneList.js.map +1 -0
- package/dist/Board/boardMove.d.ts +116 -0
- package/dist/Board/boardMove.d.ts.map +1 -0
- package/dist/Board/boardMove.js +116 -0
- package/dist/Board/boardMove.js.map +1 -0
- package/dist/Board/useBoardDrag.d.ts +44 -0
- package/dist/Board/useBoardDrag.d.ts.map +1 -0
- package/dist/Board/useBoardDrag.js +237 -0
- package/dist/Board/useBoardDrag.js.map +1 -0
- package/dist/Checkbox/Checkbox.d.ts.map +1 -1
- package/dist/Checkbox/Checkbox.js +15 -4
- package/dist/Checkbox/Checkbox.js.map +1 -1
- package/dist/DataTable/DataTable.d.ts +159 -0
- package/dist/DataTable/DataTable.d.ts.map +1 -0
- package/dist/DataTable/DataTable.js +197 -0
- package/dist/DataTable/DataTable.js.map +1 -0
- package/dist/DatePicker/DateGrid.d.ts +79 -0
- package/dist/DatePicker/DateGrid.d.ts.map +1 -0
- package/dist/DatePicker/DateGrid.js +220 -0
- package/dist/DatePicker/DateGrid.js.map +1 -0
- package/dist/DatePicker/DatePicker.d.ts +138 -0
- package/dist/DatePicker/DatePicker.d.ts.map +1 -0
- package/dist/DatePicker/DatePicker.js +384 -0
- package/dist/DatePicker/DatePicker.js.map +1 -0
- package/dist/DatePicker/datePresets.d.ts +29 -0
- package/dist/DatePicker/datePresets.d.ts.map +1 -0
- package/dist/DatePicker/datePresets.js +28 -0
- package/dist/DatePicker/datePresets.js.map +1 -0
- package/dist/DatePicker/parseDateInput.d.ts +106 -0
- package/dist/DatePicker/parseDateInput.d.ts.map +1 -0
- package/dist/DatePicker/parseDateInput.js +419 -0
- package/dist/DatePicker/parseDateInput.js.map +1 -0
- package/dist/DatePicker/plainDate.d.ts +167 -0
- package/dist/DatePicker/plainDate.d.ts.map +1 -0
- package/dist/DatePicker/plainDate.js +272 -0
- package/dist/DatePicker/plainDate.js.map +1 -0
- package/dist/Dialog/Dialog.d.ts +12 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -2
- package/dist/Dialog/Dialog.js.map +1 -1
- package/dist/MarkdownEditor/MarkdownEditor.d.ts +147 -0
- package/dist/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
- package/dist/MarkdownEditor/MarkdownEditor.js +492 -0
- package/dist/MarkdownEditor/MarkdownEditor.js.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts +202 -0
- package/dist/MarkdownEditor/markdownCommands.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownCommands.js +379 -0
- package/dist/MarkdownEditor/markdownCommands.js.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts +92 -0
- package/dist/MarkdownEditor/markdownSpans.d.ts.map +1 -0
- package/dist/MarkdownEditor/markdownSpans.js +303 -0
- package/dist/MarkdownEditor/markdownSpans.js.map +1 -0
- package/dist/Modal/Modal.d.ts +6 -2
- package/dist/Modal/Modal.d.ts.map +1 -1
- package/dist/Modal/Modal.js +1 -1
- package/dist/Modal/Modal.js.map +1 -1
- package/dist/Overlay/OverlayPanel.d.ts +13 -2
- package/dist/Overlay/OverlayPanel.d.ts.map +1 -1
- package/dist/Overlay/OverlayPanel.js +5 -1
- package/dist/Overlay/OverlayPanel.js.map +1 -1
- package/dist/categoricalStyles.d.ts +53 -0
- package/dist/categoricalStyles.d.ts.map +1 -0
- package/dist/categoricalStyles.js +195 -0
- package/dist/categoricalStyles.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Badge/Badge.mdx +46 -0
- package/src/Badge/Badge.stories.tsx +98 -4
- package/src/Badge/Badge.test.tsx +157 -2
- package/src/Badge/Badge.tsx +49 -3
- package/src/Board/Board.mdx +199 -0
- package/src/Board/Board.stories.tsx +707 -0
- package/src/Board/Board.test.tsx +310 -0
- package/src/Board/Board.tsx +333 -0
- package/src/Board/BoardCard.tsx +290 -0
- package/src/Board/BoardLaneList.tsx +275 -0
- package/src/Board/boardMove.test.ts +213 -0
- package/src/Board/boardMove.ts +225 -0
- package/src/Board/useBoardDrag.ts +413 -0
- package/src/Checkbox/Checkbox.tsx +34 -3
- package/src/DataTable/DataTable.mdx +123 -0
- package/src/DataTable/DataTable.stories.tsx +411 -0
- package/src/DataTable/DataTable.test.tsx +386 -0
- package/src/DataTable/DataTable.tsx +497 -0
- package/src/DatePicker/DateGrid.tsx +467 -0
- package/src/DatePicker/DatePicker.mdx +170 -0
- package/src/DatePicker/DatePicker.stories.tsx +268 -0
- package/src/DatePicker/DatePicker.test.tsx +455 -0
- package/src/DatePicker/DatePicker.tsx +852 -0
- package/src/DatePicker/datePresets.ts +34 -0
- package/src/DatePicker/parseDateInput.test.ts +187 -0
- package/src/DatePicker/parseDateInput.ts +629 -0
- package/src/DatePicker/plainDate.test.ts +375 -0
- package/src/DatePicker/plainDate.ts +465 -0
- package/src/Dialog/Dialog.stories.tsx +48 -0
- package/src/Dialog/Dialog.test.tsx +30 -0
- package/src/Dialog/Dialog.tsx +13 -1
- package/src/MarkdownEditor/MarkdownEditor.mdx +189 -0
- package/src/MarkdownEditor/MarkdownEditor.stories.tsx +267 -0
- package/src/MarkdownEditor/MarkdownEditor.test.tsx +627 -0
- package/src/MarkdownEditor/MarkdownEditor.tsx +862 -0
- package/src/MarkdownEditor/markdownCommands.test.ts +384 -0
- package/src/MarkdownEditor/markdownCommands.ts +566 -0
- package/src/MarkdownEditor/markdownSpans.test.ts +217 -0
- package/src/MarkdownEditor/markdownSpans.ts +458 -0
- package/src/Modal/Modal.tsx +7 -1
- package/src/Overlay/OverlayPanel.tsx +17 -1
- package/src/argTypes.storyHelpers.ts +39 -0
- package/src/categoricalStyles.ts +244 -0
- package/src/index.ts +89 -0
- package/src/sourceRules.test.ts +52 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SlotProps } from "../slotProps.ts";
|
|
3
|
+
export type MarkdownImageUpload = {
|
|
4
|
+
/** Alt text. Defaults to the file's name when omitted. */
|
|
5
|
+
alt?: string;
|
|
6
|
+
/** Where the image now lives. Inserted verbatim. */
|
|
7
|
+
url: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The toolbar's glyphs, by action.
|
|
11
|
+
*
|
|
12
|
+
* **No default, and none shipped** — this package ships no icons,
|
|
13
|
+
* and a `⋯`/`▨` default renders as nothing where the font lacks it
|
|
14
|
+
* ([decision](../../../../docs/decisions/2026-07-29-ship-no-icons-and-no-symbol-glyphs.md)).
|
|
15
|
+
* With no icons the toolbar is nine word buttons, which works
|
|
16
|
+
* everywhere and collapses into the overflow menu early; an app
|
|
17
|
+
* that brings lucide gets a compact bar and keeps the words as the
|
|
18
|
+
* accessible names.
|
|
19
|
+
*/
|
|
20
|
+
export type MarkdownEditorIcons = Partial<Record<"bold" | "bulletedList" | "code" | "heading" | "italic" | "link" | "numberedList" | "quote" | "taskList", ReactNode>>;
|
|
21
|
+
export type MarkdownEditorProps = SlotProps & {
|
|
22
|
+
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* **Initial** only. This control is uncontrolled, and the long
|
|
25
|
+
* reason is in the doc comment below — it is the whole design.
|
|
26
|
+
*/
|
|
27
|
+
defaultValue?: string;
|
|
28
|
+
/** See `MarkdownEditorIcons`. */
|
|
29
|
+
icons?: MarkdownEditorIcons;
|
|
30
|
+
isDisabled?: boolean;
|
|
31
|
+
isReadOnly?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The accessible name, as `aria-label`. Omit it inside a `Field`,
|
|
34
|
+
* which supplies a real `<label htmlFor>` — same contract as
|
|
35
|
+
* `Select`.
|
|
36
|
+
*/
|
|
37
|
+
label?: string;
|
|
38
|
+
onChange?: (value: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* A failed upload, after the placeholder has been taken back out
|
|
41
|
+
* of the text. Pair it with a `Toast`; this component does not
|
|
42
|
+
* own one.
|
|
43
|
+
*/
|
|
44
|
+
onUploadError?: (file: File, error: unknown) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Paste or drop an image and this runs. **The component does not
|
|
47
|
+
* own storage** — it hands you a `File`, you put it somewhere,
|
|
48
|
+
* and the URL you return is inserted at the caret as
|
|
49
|
+
* ``. Docket keeps its own blob storage precisely so
|
|
50
|
+
* the URL stays stable and theirs.
|
|
51
|
+
*
|
|
52
|
+
* Omit it and image paste is simply not offered: a `<textarea>`
|
|
53
|
+
* ignores a pasted image on its own, which is the correct
|
|
54
|
+
* fallback rather than a broken one.
|
|
55
|
+
*/
|
|
56
|
+
onUploadImage?: (file: File) => Promise<MarkdownImageUpload>;
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/** The editing surface's accessible name in the toolbar's label. */
|
|
59
|
+
toolbarLabel?: string;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A live hybrid markdown editor whose stored value is markdown and
|
|
63
|
+
* only ever markdown.
|
|
64
|
+
*
|
|
65
|
+
* Docket asked for this one and the requirement is unusually sharp,
|
|
66
|
+
* because the tracker it replaces destroyed data by storing HTML in
|
|
67
|
+
* a field it later tried to read back as text. So the constraint
|
|
68
|
+
* here is not "prefer markdown" — it is that **an HTML tag must not
|
|
69
|
+
* be able to reach the stored string**, and the design is chosen to
|
|
70
|
+
* make that mechanical rather than policed:
|
|
71
|
+
*
|
|
72
|
+
* - the editing surface is a `<textarea>`, which takes
|
|
73
|
+
* `text/plain` off the clipboard and nothing else. Copy a
|
|
74
|
+
* styled paragraph out of a browser and the `text/html` flavour
|
|
75
|
+
* is simply not read;
|
|
76
|
+
* - every command in `markdownCommands.ts` returns markdown, and
|
|
77
|
+
* there is no serialiser anywhere in the component;
|
|
78
|
+
* - the painted layer emits `{ kind, text }` spans that React
|
|
79
|
+
* renders as **text nodes**. No `innerHTML`, no sanitiser, and
|
|
80
|
+
* nothing to get wrong — a `<script>` in the source is four
|
|
81
|
+
* coloured characters on screen.
|
|
82
|
+
*
|
|
83
|
+
* `MarkdownEditor.test.tsx` round-trips a value full of the
|
|
84
|
+
* characters that tempt escaping and asserts no tag ever appears.
|
|
85
|
+
*
|
|
86
|
+
* ## Why a `<textarea>` and not an editor core
|
|
87
|
+
*
|
|
88
|
+
* Charcuterie is published, so a dependency here is a permanent
|
|
89
|
+
* cost to every consumer of the package — measured, for a markdown
|
|
90
|
+
* setup: CodeMirror 6 at 176 KB gzipped, Lexical at 148 KB, TipTap
|
|
91
|
+
* at 238 KB. This component adds **nothing**. The full argument,
|
|
92
|
+
* with the numbers and with what the choice gives up, is
|
|
93
|
+
* `docs/decisions/2026-08-19-the-markdown-editor-is-a-textarea-with-a-painted-layer.md`.
|
|
94
|
+
*
|
|
95
|
+
* The accessibility half of that argument is worth repeating here,
|
|
96
|
+
* because it is the half that is hard to buy back later. A
|
|
97
|
+
* `<textarea>` arrives with caret movement, word-wise selection,
|
|
98
|
+
* `Home`/`End`, IME composition, spellcheck, autocorrect, the
|
|
99
|
+
* platform's own undo stack, `role="textbox"` and
|
|
100
|
+
* `aria-multiline="true"` — all of it, in every browser, tested by
|
|
101
|
+
* the vendors. A `contenteditable` starts with none of those and
|
|
102
|
+
* re-earns each one, and the ones it never fully re-earns are IME
|
|
103
|
+
* and undo. This library's central claim is that an agent and a
|
|
104
|
+
* keyboard can drive every component; starting from the platform's
|
|
105
|
+
* own text control is the cheapest way to keep it.
|
|
106
|
+
*
|
|
107
|
+
* ## It is uncontrolled, and that is not a shortcut
|
|
108
|
+
*
|
|
109
|
+
* `defaultValue` in, `onChange` out — the same contract as `Select`
|
|
110
|
+
* ([decision](../../../../docs/decisions/2026-07-31-select-is-uncontrolled-because-the-platform-owns-the-value.md)),
|
|
111
|
+
* and here it protects something specific. A controlled React
|
|
112
|
+
* `<textarea>` round-trips every keystroke through render, and any
|
|
113
|
+
* programmatic `value` assignment **wipes the browser's undo
|
|
114
|
+
* stack**: Ctrl+Z after a toolbar click then skips the paragraph
|
|
115
|
+
* you typed before it, or does nothing at all.
|
|
116
|
+
*
|
|
117
|
+
* So the textarea is never handed a `value`. Its text is mirrored
|
|
118
|
+
* into state for *painting* only, and every programmatic edit — a
|
|
119
|
+
* toolbar action, a shortcut, an image insertion — is applied as a
|
|
120
|
+
* range replacement through `document.execCommand("insertText")`,
|
|
121
|
+
* which is deprecated, is the only API that appends to the native
|
|
122
|
+
* undo stack, and is exactly why `getMinimalEdit` exists.
|
|
123
|
+
*
|
|
124
|
+
* A consumer that must swap the whole document — Docket opening a
|
|
125
|
+
* different task — remounts with a `key`. That is one line, and it
|
|
126
|
+
* is honest about the fact that the old undo history belonged to
|
|
127
|
+
* the old document.
|
|
128
|
+
*
|
|
129
|
+
* ## Composition, and the one component that did not fit
|
|
130
|
+
*
|
|
131
|
+
* The toolbar is this package's `Toolbar`, so the row collapses
|
|
132
|
+
* into a menu **by measurement** rather than at a breakpoint —
|
|
133
|
+
* which is what the Narrow View actually needs, since a lane in a
|
|
134
|
+
* three-up board is narrow on a 4K display. `Field` wraps it
|
|
135
|
+
* through `SlotProps`, so a label, a description and an error wire
|
|
136
|
+
* themselves to the textarea.
|
|
137
|
+
*
|
|
138
|
+
* `FileDropZone` does **not** wrap it, and that is the interesting
|
|
139
|
+
* one. That component is a `<label>` around a file input covering
|
|
140
|
+
* its whole area — correct for "drop a disc image here", and wrong
|
|
141
|
+
* for a text surface, where the drop has to land *at a character
|
|
142
|
+
* offset* and the area has to keep taking clicks as caret
|
|
143
|
+
* placement. So the editor handles its own drop, and `FileDropZone`
|
|
144
|
+
* stays the right answer for the attachment list beside it.
|
|
145
|
+
*/
|
|
146
|
+
export declare const MarkdownEditor: ({ className, defaultValue, icons, isDisabled, isReadOnly, label, onChange, onUploadError, onUploadImage, placeholder, toolbarLabel, ...receivedSlotProps }: MarkdownEditorProps) => ReactNode;
|
|
147
|
+
//# sourceMappingURL=MarkdownEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownEditor.d.ts","sourceRoot":"","sources":["../../src/MarkdownEditor/MarkdownEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,SAAS,EACV,MAAM,OAAO,CAAA;AAGd,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAwBhD,MAAM,MAAM,mBAAmB,GAAG;IAChC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,MAAM,CACF,MAAM,GACN,cAAc,GACd,MAAM,GACN,SAAS,GACT,QAAQ,GACR,MAAM,GACN,cAAc,GACd,OAAO,GACP,UAAU,EACZ,SAAS,CACV,CACF,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iCAAiC;IACjC,KAAK,CAAC,EAAE,mBAAmB,CAAA;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACpD;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CACd,IAAI,EAAE,IAAI,KACP,OAAO,CAAC,mBAAmB,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AA+DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoFG;AACH,eAAO,MAAM,cAAc,GAAI,4JAa5B,mBAAmB,KAAG,SAilBxB,CAAA"}
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useUniqueId } from "@charcuterie/logic";
|
|
3
|
+
import { Fragment, useRef, useState } from "react";
|
|
4
|
+
import { mergeSlotProps } from "../slotProps.js";
|
|
5
|
+
import { Toolbar } from "../Toolbar/Toolbar.js";
|
|
6
|
+
import { toClassName } from "../toClassName.js";
|
|
7
|
+
import { continueList, getMinimalEdit, indentLines, insertLink, insertText, MARKDOWN_LINE_PREFIXES, outdentLines, toggleHeading, toggleInlineMarker, toggleLinePrefix, toMarkdownImage, } from "./markdownCommands.js";
|
|
8
|
+
import { toLineIndex, toMarkdownLines, } from "./markdownSpans.js";
|
|
9
|
+
/**
|
|
10
|
+
* The paint for each span kind.
|
|
11
|
+
*
|
|
12
|
+
* **Written out, never interpolated** — `` `text-${kind}` ``
|
|
13
|
+
* generates nothing at all and fails silently, which is the rule
|
|
14
|
+
* `intentStyles.ts` is 48 literals for.
|
|
15
|
+
*
|
|
16
|
+
* ### Every one of these is metric-neutral, and that is the design
|
|
17
|
+
*
|
|
18
|
+
* The editing surface is a real `<textarea>` with this painted
|
|
19
|
+
* layer behind it in the same grid cell. The textarea owns the
|
|
20
|
+
* caret; the layer owns the colour. The two only stay registered
|
|
21
|
+
* while they **lay out identically**, so anything that changes a
|
|
22
|
+
* glyph's advance width — `font-bold`, `text-lg`, a different
|
|
23
|
+
* family, `tracking-wide` — shears the caret away from the letters
|
|
24
|
+
* it is supposed to be sitting between. Not subtly: by a character
|
|
25
|
+
* or two per emphasis run, cumulative across the line.
|
|
26
|
+
*
|
|
27
|
+
* So bold is faux bold. `text-shadow` at ±0.02em paints the glyph
|
|
28
|
+
* twice, a hair apart, which thickens it and moves nothing.
|
|
29
|
+
* Italic is real, because the surface is monospaced and a
|
|
30
|
+
* monospaced italic keeps the advance width by definition. Headings
|
|
31
|
+
* get weight and colour rather than size, for the same reason.
|
|
32
|
+
*
|
|
33
|
+
* This is the honest limit of the textarea-plus-layer approach and
|
|
34
|
+
* it is written down here rather than discovered later:
|
|
35
|
+
* **markers are dimmed, not hidden.** Concealing `**` would change
|
|
36
|
+
* the line's width and there is no way to do it in this medium.
|
|
37
|
+
* The staging decision record has the rest.
|
|
38
|
+
*/
|
|
39
|
+
const SPAN_KIND_CLASS = {
|
|
40
|
+
code: "text-intent-info-content",
|
|
41
|
+
emphasis: "italic text-content-primary",
|
|
42
|
+
heading: "text-content-primary [text-shadow:0.02em_0_0_currentColor,-0.02em_0_0_currentColor]",
|
|
43
|
+
link: "text-intent-accent-content underline",
|
|
44
|
+
marker: "",
|
|
45
|
+
plain: "text-content-primary",
|
|
46
|
+
strikethrough: "text-content-secondary line-through",
|
|
47
|
+
strong: "text-content-primary [text-shadow:0.02em_0_0_currentColor,-0.02em_0_0_currentColor]",
|
|
48
|
+
url: "text-content-secondary underline",
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The one thing that changes with the caret.
|
|
52
|
+
*
|
|
53
|
+
* Obsidian's live preview reveals a line's raw syntax when the
|
|
54
|
+
* cursor enters it. Here the markers are always present — they have
|
|
55
|
+
* to be, see above — so "reveal" becomes a contrast change: muted
|
|
56
|
+
* everywhere, accent on the line you are editing. Both are real
|
|
57
|
+
* token roles that clear AA on their own, which an `opacity`
|
|
58
|
+
* treatment would not.
|
|
59
|
+
*/
|
|
60
|
+
const ACTIVE_MARKER_CLASS = "text-intent-accent-content";
|
|
61
|
+
const RESTING_MARKER_CLASS = "text-content-muted";
|
|
62
|
+
const isImageFile = (file) => file.type.startsWith("image/");
|
|
63
|
+
/**
|
|
64
|
+
* A live hybrid markdown editor whose stored value is markdown and
|
|
65
|
+
* only ever markdown.
|
|
66
|
+
*
|
|
67
|
+
* Docket asked for this one and the requirement is unusually sharp,
|
|
68
|
+
* because the tracker it replaces destroyed data by storing HTML in
|
|
69
|
+
* a field it later tried to read back as text. So the constraint
|
|
70
|
+
* here is not "prefer markdown" — it is that **an HTML tag must not
|
|
71
|
+
* be able to reach the stored string**, and the design is chosen to
|
|
72
|
+
* make that mechanical rather than policed:
|
|
73
|
+
*
|
|
74
|
+
* - the editing surface is a `<textarea>`, which takes
|
|
75
|
+
* `text/plain` off the clipboard and nothing else. Copy a
|
|
76
|
+
* styled paragraph out of a browser and the `text/html` flavour
|
|
77
|
+
* is simply not read;
|
|
78
|
+
* - every command in `markdownCommands.ts` returns markdown, and
|
|
79
|
+
* there is no serialiser anywhere in the component;
|
|
80
|
+
* - the painted layer emits `{ kind, text }` spans that React
|
|
81
|
+
* renders as **text nodes**. No `innerHTML`, no sanitiser, and
|
|
82
|
+
* nothing to get wrong — a `<script>` in the source is four
|
|
83
|
+
* coloured characters on screen.
|
|
84
|
+
*
|
|
85
|
+
* `MarkdownEditor.test.tsx` round-trips a value full of the
|
|
86
|
+
* characters that tempt escaping and asserts no tag ever appears.
|
|
87
|
+
*
|
|
88
|
+
* ## Why a `<textarea>` and not an editor core
|
|
89
|
+
*
|
|
90
|
+
* Charcuterie is published, so a dependency here is a permanent
|
|
91
|
+
* cost to every consumer of the package — measured, for a markdown
|
|
92
|
+
* setup: CodeMirror 6 at 176 KB gzipped, Lexical at 148 KB, TipTap
|
|
93
|
+
* at 238 KB. This component adds **nothing**. The full argument,
|
|
94
|
+
* with the numbers and with what the choice gives up, is
|
|
95
|
+
* `docs/decisions/2026-08-19-the-markdown-editor-is-a-textarea-with-a-painted-layer.md`.
|
|
96
|
+
*
|
|
97
|
+
* The accessibility half of that argument is worth repeating here,
|
|
98
|
+
* because it is the half that is hard to buy back later. A
|
|
99
|
+
* `<textarea>` arrives with caret movement, word-wise selection,
|
|
100
|
+
* `Home`/`End`, IME composition, spellcheck, autocorrect, the
|
|
101
|
+
* platform's own undo stack, `role="textbox"` and
|
|
102
|
+
* `aria-multiline="true"` — all of it, in every browser, tested by
|
|
103
|
+
* the vendors. A `contenteditable` starts with none of those and
|
|
104
|
+
* re-earns each one, and the ones it never fully re-earns are IME
|
|
105
|
+
* and undo. This library's central claim is that an agent and a
|
|
106
|
+
* keyboard can drive every component; starting from the platform's
|
|
107
|
+
* own text control is the cheapest way to keep it.
|
|
108
|
+
*
|
|
109
|
+
* ## It is uncontrolled, and that is not a shortcut
|
|
110
|
+
*
|
|
111
|
+
* `defaultValue` in, `onChange` out — the same contract as `Select`
|
|
112
|
+
* ([decision](../../../../docs/decisions/2026-07-31-select-is-uncontrolled-because-the-platform-owns-the-value.md)),
|
|
113
|
+
* and here it protects something specific. A controlled React
|
|
114
|
+
* `<textarea>` round-trips every keystroke through render, and any
|
|
115
|
+
* programmatic `value` assignment **wipes the browser's undo
|
|
116
|
+
* stack**: Ctrl+Z after a toolbar click then skips the paragraph
|
|
117
|
+
* you typed before it, or does nothing at all.
|
|
118
|
+
*
|
|
119
|
+
* So the textarea is never handed a `value`. Its text is mirrored
|
|
120
|
+
* into state for *painting* only, and every programmatic edit — a
|
|
121
|
+
* toolbar action, a shortcut, an image insertion — is applied as a
|
|
122
|
+
* range replacement through `document.execCommand("insertText")`,
|
|
123
|
+
* which is deprecated, is the only API that appends to the native
|
|
124
|
+
* undo stack, and is exactly why `getMinimalEdit` exists.
|
|
125
|
+
*
|
|
126
|
+
* A consumer that must swap the whole document — Docket opening a
|
|
127
|
+
* different task — remounts with a `key`. That is one line, and it
|
|
128
|
+
* is honest about the fact that the old undo history belonged to
|
|
129
|
+
* the old document.
|
|
130
|
+
*
|
|
131
|
+
* ## Composition, and the one component that did not fit
|
|
132
|
+
*
|
|
133
|
+
* The toolbar is this package's `Toolbar`, so the row collapses
|
|
134
|
+
* into a menu **by measurement** rather than at a breakpoint —
|
|
135
|
+
* which is what the Narrow View actually needs, since a lane in a
|
|
136
|
+
* three-up board is narrow on a 4K display. `Field` wraps it
|
|
137
|
+
* through `SlotProps`, so a label, a description and an error wire
|
|
138
|
+
* themselves to the textarea.
|
|
139
|
+
*
|
|
140
|
+
* `FileDropZone` does **not** wrap it, and that is the interesting
|
|
141
|
+
* one. That component is a `<label>` around a file input covering
|
|
142
|
+
* its whole area — correct for "drop a disc image here", and wrong
|
|
143
|
+
* for a text surface, where the drop has to land *at a character
|
|
144
|
+
* offset* and the area has to keep taking clicks as caret
|
|
145
|
+
* placement. So the editor handles its own drop, and `FileDropZone`
|
|
146
|
+
* stays the right answer for the attachment list beside it.
|
|
147
|
+
*/
|
|
148
|
+
export const MarkdownEditor = ({ className, defaultValue = "", icons, isDisabled = false, isReadOnly = false, label, onChange, onUploadError, onUploadImage, placeholder, toolbarLabel = "Markdown formatting", ...receivedSlotProps }) => {
|
|
149
|
+
const baseId = useUniqueId();
|
|
150
|
+
const hintId = `${baseId}-hint`;
|
|
151
|
+
const statusId = `${baseId}-status`;
|
|
152
|
+
const textareaRef = useRef(null);
|
|
153
|
+
const layerRef = useRef(null);
|
|
154
|
+
const uploadCount = useRef(0);
|
|
155
|
+
const [value, setValue] = useState(defaultValue);
|
|
156
|
+
const [caretOffset, setCaretOffset] = useState(0);
|
|
157
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
158
|
+
const [uploadMessage, setUploadMessage] = useState("");
|
|
159
|
+
const readSelection = () => {
|
|
160
|
+
const element = textareaRef.current;
|
|
161
|
+
return {
|
|
162
|
+
selectionEnd: element?.selectionEnd ?? 0,
|
|
163
|
+
selectionStart: element?.selectionStart ?? 0,
|
|
164
|
+
text: element?.value ?? "",
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
const syncFromElement = () => {
|
|
168
|
+
const element = textareaRef.current;
|
|
169
|
+
if (!element) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
setCaretOffset(element.selectionStart);
|
|
173
|
+
if (element.value === value) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
setValue(element.value);
|
|
177
|
+
onChange?.(element.value);
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Apply a command's result to the live control **without**
|
|
181
|
+
* assigning `value`.
|
|
182
|
+
*
|
|
183
|
+
* `execCommand` is deprecated and is the only route that adds to
|
|
184
|
+
* the browser's own undo stack, so it is tried first and the
|
|
185
|
+
* modern `setRangeText` is the fallback for whatever eventually
|
|
186
|
+
* removes it. Losing undo is a degradation; losing the edit would
|
|
187
|
+
* not be.
|
|
188
|
+
*/
|
|
189
|
+
const applyEdit = (next) => {
|
|
190
|
+
const element = textareaRef.current;
|
|
191
|
+
if (!element) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const edit = getMinimalEdit(element.value, next.text);
|
|
195
|
+
element.focus();
|
|
196
|
+
element.setSelectionRange(edit.start, edit.end);
|
|
197
|
+
const isApplied = (() => {
|
|
198
|
+
try {
|
|
199
|
+
return edit.text === ""
|
|
200
|
+
? document.execCommand("delete")
|
|
201
|
+
: document.execCommand("insertText", false, edit.text);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
})();
|
|
207
|
+
if (!isApplied) {
|
|
208
|
+
element.setRangeText(edit.text, edit.start, edit.end, "end");
|
|
209
|
+
}
|
|
210
|
+
element.setSelectionRange(next.selectionStart, next.selectionEnd);
|
|
211
|
+
syncFromElement();
|
|
212
|
+
};
|
|
213
|
+
const runCommand = (command) => {
|
|
214
|
+
if (isDisabled || isReadOnly) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
applyEdit(command(readSelection()));
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Upload one file, with a **markdown** placeholder holding the
|
|
221
|
+
* caret's place while the request is in flight.
|
|
222
|
+
*
|
|
223
|
+
* The placeholder is real text in the document rather than an
|
|
224
|
+
* overlay, because the user keeps typing during an upload and the
|
|
225
|
+
* insertion point has to move with what they type. It carries a
|
|
226
|
+
* counter so two files of the same name do not resolve into each
|
|
227
|
+
* other's slot, and it is markdown like everything else — a
|
|
228
|
+
* failed upload leaves the document in a state the user can read
|
|
229
|
+
* and fix, not a dangling widget.
|
|
230
|
+
*/
|
|
231
|
+
const uploadImage = async (file) => {
|
|
232
|
+
if (!onUploadImage) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
uploadCount.current += 1;
|
|
236
|
+
const placeholder = ``;
|
|
237
|
+
applyEdit(insertText(readSelection(), placeholder));
|
|
238
|
+
setUploadMessage(`Uploading ${file.name}`);
|
|
239
|
+
const replacePlaceholder = (insertion) => {
|
|
240
|
+
const element = textareaRef.current;
|
|
241
|
+
if (!element) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const start = element.value.indexOf(placeholder);
|
|
245
|
+
if (start === -1) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const end = start + placeholder.length;
|
|
249
|
+
applyEdit({
|
|
250
|
+
selectionEnd: start + insertion.length,
|
|
251
|
+
selectionStart: start + insertion.length,
|
|
252
|
+
text: element.value.slice(0, start) +
|
|
253
|
+
insertion +
|
|
254
|
+
element.value.slice(end),
|
|
255
|
+
});
|
|
256
|
+
};
|
|
257
|
+
try {
|
|
258
|
+
const uploaded = await onUploadImage(file);
|
|
259
|
+
replacePlaceholder(toMarkdownImage({
|
|
260
|
+
alt: uploaded.alt ?? file.name,
|
|
261
|
+
url: uploaded.url,
|
|
262
|
+
}));
|
|
263
|
+
setUploadMessage(`Inserted ${file.name}`);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
replacePlaceholder("");
|
|
267
|
+
setUploadMessage(`Could not upload ${file.name}`);
|
|
268
|
+
onUploadError?.(file, error);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
const uploadImages = (files) => {
|
|
272
|
+
for (const file of files.filter(isImageFile)) {
|
|
273
|
+
void uploadImage(file);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const handlePaste = (pasteEvent) => {
|
|
277
|
+
const files = Array.from(pasteEvent.clipboardData.files).filter(isImageFile);
|
|
278
|
+
if (files.length === 0 || !onUploadImage) {
|
|
279
|
+
// Left to the browser on purpose. A `<textarea>` pastes
|
|
280
|
+
// `text/plain` and nothing else, so the no-HTML guarantee
|
|
281
|
+
// needs no code here at all — intercepting the paste to
|
|
282
|
+
// "sanitise" it would be the version that can have a bug.
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
pasteEvent.preventDefault();
|
|
286
|
+
uploadImages(files);
|
|
287
|
+
};
|
|
288
|
+
const handleDrop = (dropEvent) => {
|
|
289
|
+
const files = Array.from(dropEvent.dataTransfer.files).filter(isImageFile);
|
|
290
|
+
if (files.length === 0 || !onUploadImage) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
dropEvent.preventDefault();
|
|
294
|
+
uploadImages(files);
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* `Tab` is never captured, at any time.
|
|
298
|
+
*
|
|
299
|
+
* Indenting a list with Tab is the convention in editors that own
|
|
300
|
+
* the whole page, and inside a form it is a keyboard trap: WCAG
|
|
301
|
+
* 2.1.2 wants focus to be able to leave every component with the
|
|
302
|
+
* standard gesture, and a multi-line field on a task form is
|
|
303
|
+
* exactly where somebody tabs onward. Indent and outdent are
|
|
304
|
+
* `Ctrl+]` and `Ctrl+[` instead, which is what browsers'
|
|
305
|
+
* own editors bind them to.
|
|
306
|
+
*/
|
|
307
|
+
const handleKeyDown = (keyEvent) => {
|
|
308
|
+
if (isReadOnly || isDisabled) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (keyEvent.key === "Enter" &&
|
|
312
|
+
!keyEvent.shiftKey &&
|
|
313
|
+
!keyEvent.altKey &&
|
|
314
|
+
!keyEvent.ctrlKey &&
|
|
315
|
+
!keyEvent.metaKey) {
|
|
316
|
+
const continued = continueList(readSelection());
|
|
317
|
+
if (continued) {
|
|
318
|
+
keyEvent.preventDefault();
|
|
319
|
+
applyEdit(continued);
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const isModified = keyEvent.ctrlKey || keyEvent.metaKey;
|
|
324
|
+
if (!isModified) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const command = (() => {
|
|
328
|
+
switch (keyEvent.key.toLowerCase()) {
|
|
329
|
+
case "[": {
|
|
330
|
+
return outdentLines;
|
|
331
|
+
}
|
|
332
|
+
case "]": {
|
|
333
|
+
return indentLines;
|
|
334
|
+
}
|
|
335
|
+
case "b": {
|
|
336
|
+
return (state) => toggleInlineMarker(state, "**");
|
|
337
|
+
}
|
|
338
|
+
case "e": {
|
|
339
|
+
return (state) => toggleInlineMarker(state, "`");
|
|
340
|
+
}
|
|
341
|
+
case "i": {
|
|
342
|
+
return (state) => toggleInlineMarker(state, "_");
|
|
343
|
+
}
|
|
344
|
+
case "k": {
|
|
345
|
+
return (state) => insertLink(state);
|
|
346
|
+
}
|
|
347
|
+
default: {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
if (!command) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
keyEvent.preventDefault();
|
|
356
|
+
runCommand(command);
|
|
357
|
+
};
|
|
358
|
+
const lines = toMarkdownLines(value);
|
|
359
|
+
const activeLineIndex = toLineIndex(value, caretOffset);
|
|
360
|
+
const surfaceClassName = "col-start-1 row-start-1 m-0 min-h-32 whitespace-pre-wrap break-words border-0 p-3 font-mono text-md leading-relaxed cq-md:min-h-48";
|
|
361
|
+
return (_jsxs("div", { className: toClassName(
|
|
362
|
+
// `@container` here and not on a wrapper: a container query
|
|
363
|
+
// matches the container's *descendants*, never the
|
|
364
|
+
// container itself, so nothing in this string may be a
|
|
365
|
+
// `cq-*:` variant.
|
|
366
|
+
"@container flex flex-col gap-2 rounded-md border border-border-default bg-surface-base text-content-primary focus-within:outline-solid focus-within:outline-(length:--focus-ring-width) focus-within:outline-offset-(--focus-ring-offset) focus-within:outline-focus-ring",
|
|
367
|
+
// A token pair, never `opacity-*`. Fading the whole box
|
|
368
|
+
// fades the hint under it too, and axe measures the
|
|
369
|
+
// *composited* colour: `content-secondary` at 60% came out
|
|
370
|
+
// at 3.77:1 and failed AA on a `<p>` that is not itself
|
|
371
|
+
// disabled and owes no exemption.
|
|
372
|
+
isDisabled &&
|
|
373
|
+
"border-border-subtle bg-surface-sunken", className), children: [_jsx("div", { className: "border-border-subtle border-b px-2 pt-2 pb-1", children: _jsx(Toolbar, { items: [
|
|
374
|
+
{
|
|
375
|
+
icon: icons?.bold,
|
|
376
|
+
isDisabled: isDisabled || isReadOnly,
|
|
377
|
+
key: "bold",
|
|
378
|
+
label: "Bold",
|
|
379
|
+
onSelect: () => {
|
|
380
|
+
runCommand((state) => toggleInlineMarker(state, "**"));
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
icon: icons?.italic,
|
|
385
|
+
isDisabled: isDisabled || isReadOnly,
|
|
386
|
+
key: "italic",
|
|
387
|
+
label: "Italic",
|
|
388
|
+
onSelect: () => {
|
|
389
|
+
runCommand((state) => toggleInlineMarker(state, "_"));
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
icon: icons?.heading,
|
|
394
|
+
isDisabled: isDisabled || isReadOnly,
|
|
395
|
+
key: "heading",
|
|
396
|
+
label: "Heading",
|
|
397
|
+
onSelect: () => {
|
|
398
|
+
runCommand((state) => toggleHeading(state, 2));
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
icon: icons?.bulletedList,
|
|
403
|
+
isDisabled: isDisabled || isReadOnly,
|
|
404
|
+
key: "bulleted-list",
|
|
405
|
+
label: "Bulleted list",
|
|
406
|
+
onSelect: () => {
|
|
407
|
+
runCommand((state) => toggleLinePrefix(state, MARKDOWN_LINE_PREFIXES.bulletList));
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
icon: icons?.taskList,
|
|
412
|
+
isDisabled: isDisabled || isReadOnly,
|
|
413
|
+
key: "task-list",
|
|
414
|
+
label: "Task list",
|
|
415
|
+
onSelect: () => {
|
|
416
|
+
runCommand((state) => toggleLinePrefix(state, MARKDOWN_LINE_PREFIXES.taskList));
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
icon: icons?.numberedList,
|
|
421
|
+
isDisabled: isDisabled || isReadOnly,
|
|
422
|
+
key: "numbered-list",
|
|
423
|
+
label: "Numbered list",
|
|
424
|
+
onSelect: () => {
|
|
425
|
+
runCommand((state) => toggleLinePrefix(state, MARKDOWN_LINE_PREFIXES.orderedList));
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
icon: icons?.quote,
|
|
430
|
+
isDisabled: isDisabled || isReadOnly,
|
|
431
|
+
key: "quote",
|
|
432
|
+
label: "Quote",
|
|
433
|
+
onSelect: () => {
|
|
434
|
+
runCommand((state) => toggleLinePrefix(state, MARKDOWN_LINE_PREFIXES.blockquote));
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
icon: icons?.code,
|
|
439
|
+
isDisabled: isDisabled || isReadOnly,
|
|
440
|
+
key: "code",
|
|
441
|
+
label: "Code",
|
|
442
|
+
onSelect: () => {
|
|
443
|
+
runCommand((state) => toggleInlineMarker(state, "`"));
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
icon: icons?.link,
|
|
448
|
+
isDisabled: isDisabled || isReadOnly,
|
|
449
|
+
key: "link",
|
|
450
|
+
label: "Link",
|
|
451
|
+
onSelect: () => {
|
|
452
|
+
runCommand((state) => insertLink(state));
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
], label: toolbarLabel, overflow: "menu" }) }), _jsxs("div", { className: "grid", children: [_jsx("div", { "aria-hidden": "true", className: toClassName(surfaceClassName, "pointer-events-none overflow-hidden"), ref: layerRef, children: lines.map((line, lineIndex) => (_jsxs(Fragment
|
|
456
|
+
// The index *is* the identity here: line 3 is line 3,
|
|
457
|
+
// and its content is what changes.
|
|
458
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: a line's position is its identity
|
|
459
|
+
, { children: [lineIndex === 0 ? null : "\n", line.spans.map((span, spanIndex) => (_jsx("span", { className: span.kind === "marker"
|
|
460
|
+
? isFocused &&
|
|
461
|
+
lineIndex === activeLineIndex
|
|
462
|
+
? ACTIVE_MARKER_CLASS
|
|
463
|
+
: RESTING_MARKER_CLASS
|
|
464
|
+
: SPAN_KIND_CLASS[span.kind], children: span.text }, spanIndex)))] }, lineIndex))) }), _jsx("textarea", { ...mergeSlotProps(receivedSlotProps, {
|
|
465
|
+
"aria-describedby": hintId,
|
|
466
|
+
}), "aria-label": label, className: toClassName(surfaceClassName,
|
|
467
|
+
// The text is transparent and the layer behind supplies
|
|
468
|
+
// every glyph. The caret is not: `caret-color` is its
|
|
469
|
+
// own property precisely so a transparent-text overlay
|
|
470
|
+
// like this one can keep a visible cursor.
|
|
471
|
+
"resize-y bg-transparent text-transparent caret-content-primary outline-none placeholder:text-content-muted"), defaultValue: defaultValue, disabled: isDisabled, onBlur: () => {
|
|
472
|
+
setIsFocused(false);
|
|
473
|
+
}, onClick: syncFromElement, onDragOver: (dragEvent) => {
|
|
474
|
+
if (onUploadImage) {
|
|
475
|
+
// The default action for `dragover` is to reject the
|
|
476
|
+
// drop, so without this `onDrop` never fires at all —
|
|
477
|
+
// the same footgun `FileDropZone` documents.
|
|
478
|
+
dragEvent.preventDefault();
|
|
479
|
+
}
|
|
480
|
+
}, onDrop: handleDrop, onFocus: () => {
|
|
481
|
+
setIsFocused(true);
|
|
482
|
+
syncFromElement();
|
|
483
|
+
}, onInput: syncFromElement, onKeyDown: handleKeyDown, onKeyUp: syncFromElement, onPaste: handlePaste, onScroll: () => {
|
|
484
|
+
const element = textareaRef.current;
|
|
485
|
+
if (element && layerRef.current) {
|
|
486
|
+
layerRef.current.scrollTop = element.scrollTop;
|
|
487
|
+
layerRef.current.scrollLeft =
|
|
488
|
+
element.scrollLeft;
|
|
489
|
+
}
|
|
490
|
+
}, onSelect: syncFromElement, placeholder: placeholder, readOnly: isReadOnly, ref: textareaRef, spellCheck: true })] }), _jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1 px-3 pb-2", children: [_jsx("p", { className: "sr-only text-content-secondary text-sm cq-sm:not-sr-only", id: hintId, children: "Markdown. Ctrl+B bold, Ctrl+I italic, Ctrl+K link, Ctrl+E code." }), _jsx("span", { "aria-label": "Image upload", className: "text-content-secondary text-sm", id: statusId, role: "status", children: uploadMessage })] })] }));
|
|
491
|
+
};
|
|
492
|
+
//# sourceMappingURL=MarkdownEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownEditor.js","sourceRoot":"","sources":["../../src/MarkdownEditor/MarkdownEditor.tsx"],"names":[],"mappings":"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"}
|