quoin-editor 0.3.0 → 0.4.1
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 +42 -8
- package/dist/headless.cjs +21 -11
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +1 -1
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +21 -11
- package/dist/headless.js.map +1 -1
- package/dist/index.cjs +301 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -18
- package/dist/index.d.ts +35 -18
- package/dist/index.js +303 -174
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +23 -6
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ export function TransitionEditor() {
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Since 0.4.1, stores that clone the document on the way through, such as a Redux or Zustand round trip or a server-synced store, are supported too. The canvas recognises an array it emitted however late it comes back, and a structural copy of one as long as it is within the last 64 edits.
|
|
69
69
|
|
|
70
70
|
Debounce the side effect, not the state. `useAutosave` does exactly that: it tracks the values you give it, waits for the edits to settle, then calls your `save`, and it retries a failed save with exponential backoff.
|
|
71
71
|
|
|
@@ -96,7 +96,7 @@ export function AutosavingEditor({ initial }: { initial: QuoinBlock[] }) {
|
|
|
96
96
|
}
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
`save` may return anything, so a mutation, an axios call or a `fetch` that resolves to a body goes straight in with no wrapper to discard the value. A save fails by rejecting, or by resolving to an explicit `{ ok: false }`; everything else, `undefined` included, is a success. `status` moves `idle` to `saving` to `saved`, or to `retrying` while it backs off, and `retry()` tries again at once.
|
|
99
|
+
`save` may return anything, so a mutation, an axios call or a `fetch` that resolves to a body goes straight in with no wrapper to discard the value. A save fails by rejecting, or by resolving to an explicit `{ ok: false }`; everything else, `undefined` included, is a success. `status` moves `idle` to `saving` to `saved`, or to `retrying` while it backs off, and `retry()` tries again at once. Since 0.4.1 one save runs at a time, with the newest payload queued behind it, and a pending edit is saved at once when the component unmounts or the page is hidden (`flush()` does the same on demand). Retries stop after `maxRetries` (default 8) or when `isRetryable(error)` returns false; `status` is then `failed` and `error` holds the reason.
|
|
100
100
|
|
|
101
101
|
### Loading, undoing and discarding
|
|
102
102
|
|
|
@@ -104,7 +104,7 @@ You can hand the canvas a new `blocks` array at any time to load, undo or merge
|
|
|
104
104
|
|
|
105
105
|
When you echo `onChange` back into `blocks`, as every sample above does, the last array you supplied is the edited document, so `setBlocks(saved)` differs from it and resets the canvas with nothing more to do.
|
|
106
106
|
|
|
107
|
-
The case that needs care is a host that does not echo edits back. There, the last array you supplied is still the original, and an array structurally equal to it is ignored: a "discard changes" button that re-supplies the original document does nothing. To force a reset, remount the canvas with a React `key`. The sample below uses a `key` so it works either way.
|
|
107
|
+
The case that needs care is a host that does not echo edits back. There, the last array you supplied is still the original, and an array structurally equal to it is ignored: a "discard changes" button that re-supplies the original document does nothing. The same goes for restoring a copy of a state from the last 64 edits, which reads as a late echo of the canvas's own output. To force a reset, remount the canvas with a React `key`. The sample below uses a `key` so it works either way.
|
|
108
108
|
|
|
109
109
|
```tsx
|
|
110
110
|
import { useState } from 'react';
|
|
@@ -311,7 +311,7 @@ Each core block keeps its content under its own keys. A host that persists block
|
|
|
311
311
|
|
|
312
312
|
Two of these are worth saying out loud, because both have been guessed wrong:
|
|
313
313
|
|
|
314
|
-
- **The callout's prose is `body`, not `text`.** The label over the field reads "Callout text", which is what the author sees; `body` is what the block stores.
|
|
314
|
+
- **The callout's prose is `body`, not `text`.** The label over the field reads "Callout text", which is what the author sees; `body` is what the block stores. `tone` is one of `info`, `tip`, `warning`, `success` or `danger`, each drawn in its own colour; anything else falls back to the neutral border and fill.
|
|
315
315
|
- **Markdown blocks store markdown source, not rendered HTML.** Quoin edits and stores the source and renders it as plain text; supply a `preview` if you want it rendered.
|
|
316
316
|
|
|
317
317
|
The optional fields are written after a block's first save: an image's `caption` when the author types one, its `width` and `height` once the uploaded bytes have been measured, a file's `size` when the upload reports it. `src/blocks/data.test.ts` reads the keys back out of each editor's source and fails if one drifts from the table above.
|
|
@@ -477,19 +477,22 @@ Because it draws previews and never editors, a block type registered without a `
|
|
|
477
477
|
--quoin-background: oklch(1 0 0);
|
|
478
478
|
--quoin-foreground: oklch(0.145 0 0);
|
|
479
479
|
--quoin-muted: oklch(0.97 0 0);
|
|
480
|
-
--quoin-muted-foreground: oklch(0.
|
|
480
|
+
--quoin-muted-foreground: oklch(0.54 0 0);
|
|
481
481
|
--quoin-secondary: oklch(0.97 0 0);
|
|
482
482
|
--quoin-secondary-foreground: oklch(0.205 0 0);
|
|
483
483
|
--quoin-popover: oklch(1 0 0);
|
|
484
484
|
--quoin-popover-foreground: oklch(0.145 0 0);
|
|
485
485
|
--quoin-border: oklch(0.922 0 0);
|
|
486
486
|
--quoin-input: oklch(0.922 0 0);
|
|
487
|
-
--quoin-ring: oklch(0.
|
|
487
|
+
--quoin-ring: oklch(0.45 0 0);
|
|
488
488
|
--quoin-primary: oklch(0.205 0 0);
|
|
489
489
|
--quoin-primary-foreground: oklch(0.985 0 0);
|
|
490
490
|
--quoin-accent: oklch(0.97 0 0);
|
|
491
491
|
--quoin-accent-foreground: oklch(0.205 0 0);
|
|
492
|
-
--quoin-destructive: oklch(0.
|
|
492
|
+
--quoin-destructive: oklch(0.55 0.245 27.325);
|
|
493
|
+
--quoin-info: oklch(0.55 0.17 250);
|
|
494
|
+
--quoin-success: oklch(0.55 0.14 155);
|
|
495
|
+
--quoin-warning: oklch(0.65 0.16 65);
|
|
493
496
|
--quoin-radius: 0.625rem;
|
|
494
497
|
}
|
|
495
498
|
|
|
@@ -504,12 +507,15 @@ Because it draws previews and never editors, a block type registered without a `
|
|
|
504
507
|
--quoin-popover-foreground: oklch(0.985 0 0);
|
|
505
508
|
--quoin-border: oklch(1 0 0 / 10%);
|
|
506
509
|
--quoin-input: oklch(1 0 0 / 15%);
|
|
507
|
-
--quoin-ring: oklch(0.
|
|
510
|
+
--quoin-ring: oklch(0.708 0 0);
|
|
508
511
|
--quoin-primary: oklch(0.922 0 0);
|
|
509
512
|
--quoin-primary-foreground: oklch(0.205 0 0);
|
|
510
513
|
--quoin-accent: oklch(0.269 0 0);
|
|
511
514
|
--quoin-accent-foreground: oklch(0.985 0 0);
|
|
512
515
|
--quoin-destructive: oklch(0.704 0.191 22.216);
|
|
516
|
+
--quoin-info: oklch(0.72 0.14 250);
|
|
517
|
+
--quoin-success: oklch(0.74 0.15 155);
|
|
518
|
+
--quoin-warning: oklch(0.79 0.15 65);
|
|
513
519
|
--quoin-radius: 0.625rem;
|
|
514
520
|
}
|
|
515
521
|
```
|
|
@@ -548,10 +554,17 @@ Point the prefixed names at your own in one block on `.quoin`. The canvas root c
|
|
|
548
554
|
--quoin-accent: var(--accent);
|
|
549
555
|
--quoin-accent-foreground: var(--accent-foreground);
|
|
550
556
|
--quoin-destructive: var(--destructive);
|
|
557
|
+
--quoin-info: var(--info);
|
|
558
|
+
--quoin-success: var(--success);
|
|
559
|
+
--quoin-warning: var(--warning);
|
|
551
560
|
--quoin-radius: var(--radius);
|
|
552
561
|
}
|
|
553
562
|
```
|
|
554
563
|
|
|
564
|
+
`--quoin-info`, `--quoin-success` and `--quoin-warning` arrived in 0.4.0 and are read only by the callout block, for its rule and the wash behind it, never for type. If your own theme has no equivalent, leave those three out of the block and the defaults above stand; the rest of the editor does not touch them.
|
|
565
|
+
|
|
566
|
+
`--quoin-ring` colours the keyboard focus outline, which is solid and drawn at full strength, so it should reach 3:1 against both `--quoin-background` and `--quoin-popover`. A pale value, such as the grey many shadcn themes keep for `--ring`, fails that, so give `--quoin-ring` a value of its own that does.
|
|
567
|
+
|
|
555
568
|
That one block covers both modes: the tokens on the right already flip with your own dark class, and each mapping beats Quoin's `:root` and `.dark` defaults alike. Write it unlayered, or in a layer that sorts after `quoin`, so it outranks those defaults.
|
|
556
569
|
|
|
557
570
|
To change one token rather than adopt a whole theme, declare it on `:root`, and pair it with a `.dark` rule if you theme dark at all. Pinning a token on `:root` alone also beats Quoin's `.dark` value for it, which would freeze that one token in its light form.
|
|
@@ -712,6 +725,27 @@ From `quoin-editor/headless`: the transforms and the two hooks listed above, wit
|
|
|
712
725
|
|
|
713
726
|
From `quoin-editor/styles.css`: the stylesheet.
|
|
714
727
|
|
|
728
|
+
## Accessibility
|
|
729
|
+
|
|
730
|
+
What the test suite checks:
|
|
731
|
+
|
|
732
|
+
- axe runs over the canvas, the block cards, the inserter, the slash palette and the shortcuts reference.
|
|
733
|
+
- Controls carry accessible names, and the icons in the canvas, the block options menu, the inserter and the slash palette are hidden from assistive technology.
|
|
734
|
+
- Blocks reorder by keyboard, with announcements that name each block by label and position.
|
|
735
|
+
- Escape leaves an open block and returns focus to its Edit button.
|
|
736
|
+
- The default light and dark tokens keep the keyboard focus outline at 3:1 or more and highlighted-row text at 4.5:1 or more, and focusable controls draw the focus as an outline, which Windows forced colours mode keeps.
|
|
737
|
+
|
|
738
|
+
The checks are automated. A manual pass with NVDA and VoiceOver is not recorded yet.
|
|
739
|
+
|
|
740
|
+
Known gaps in 0.4:
|
|
741
|
+
|
|
742
|
+
- A markdown block is read as source. At rest it is plain text, so a screen reader reads the markdown characters as written.
|
|
743
|
+
- Keyboard drag with Space and the arrow keys can drop a block in place, and its announcement can name the wrong position. Use Move up and Move down in the block's options menu to reorder by keyboard.
|
|
744
|
+
|
|
745
|
+
## Stability
|
|
746
|
+
|
|
747
|
+
Quoin is 0.x. A minor version may contain breaking changes, and each one is listed in the [changelog](CHANGELOG.md). If you need stability, pin a minor with `~0.4.0`, which takes patch releases only.
|
|
748
|
+
|
|
715
749
|
## Licence
|
|
716
750
|
|
|
717
751
|
MIT. Copyright (c) 2026 Amine Sboui. See [LICENSE](LICENSE).
|
package/dist/headless.cjs
CHANGED
|
@@ -328,15 +328,18 @@ function shouldOpenPalette(value, selectionStart, selectionEnd) {
|
|
|
328
328
|
return previous === "\n" || previous === " " || previous === " ";
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
// src/markdown/platform.ts
|
|
332
|
+
function isMac() {
|
|
333
|
+
if (typeof navigator === "undefined") return false;
|
|
334
|
+
const { userAgentData, platform } = navigator;
|
|
335
|
+
return /mac|iphone|ipad/i.test(userAgentData?.platform || platform || "");
|
|
336
|
+
}
|
|
337
|
+
|
|
331
338
|
// src/markdown/shortcut-label.ts
|
|
332
339
|
var MAC_LABEL = { mod: "\u2318", alt: "\u2325", shift: "\u21E7" };
|
|
333
340
|
var OTHER_LABEL = { mod: "Ctrl", alt: "Alt", shift: "Shift" };
|
|
334
|
-
function onMac() {
|
|
335
|
-
if (typeof navigator === "undefined") return false;
|
|
336
|
-
return /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
337
|
-
}
|
|
338
341
|
function formatShortcut(hint) {
|
|
339
|
-
const mac =
|
|
342
|
+
const mac = isMac();
|
|
340
343
|
const labels = mac ? MAC_LABEL : OTHER_LABEL;
|
|
341
344
|
const parts = hint.split("+").map((part) => labels[part] ?? part.toUpperCase());
|
|
342
345
|
return parts.join(mac ? "" : "+");
|
|
@@ -400,9 +403,10 @@ function useMarkdownKeymap({
|
|
|
400
403
|
}) {
|
|
401
404
|
const onKeyDown = React.useCallback(
|
|
402
405
|
(event) => {
|
|
406
|
+
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
|
|
403
407
|
const el = event.currentTarget;
|
|
404
408
|
const { value, selectionStart, selectionEnd } = el;
|
|
405
|
-
const mod = event.metaKey
|
|
409
|
+
const mod = isMac() ? event.metaKey : event.ctrlKey;
|
|
406
410
|
const run = (edit) => {
|
|
407
411
|
if (!edit) return;
|
|
408
412
|
event.preventDefault();
|
|
@@ -462,11 +466,17 @@ function useMarkdownKeymap({
|
|
|
462
466
|
}
|
|
463
467
|
|
|
464
468
|
// src/hooks/useAutoGrow.ts
|
|
469
|
+
var React3 = __toESM(require("react"), 1);
|
|
470
|
+
|
|
471
|
+
// src/hooks/use-isomorphic-layout-effect.ts
|
|
465
472
|
var React2 = __toESM(require("react"), 1);
|
|
473
|
+
var useIsomorphicLayoutEffect = typeof document === "undefined" ? React2.useEffect : React2.useLayoutEffect;
|
|
474
|
+
|
|
475
|
+
// src/hooks/useAutoGrow.ts
|
|
466
476
|
function useAutoGrow(value) {
|
|
467
|
-
const ref =
|
|
468
|
-
const lastWidth =
|
|
469
|
-
const fit =
|
|
477
|
+
const ref = React3.useRef(null);
|
|
478
|
+
const lastWidth = React3.useRef(null);
|
|
479
|
+
const fit = React3.useCallback(() => {
|
|
470
480
|
const el = ref.current;
|
|
471
481
|
if (!el) return;
|
|
472
482
|
const scroller = document.scrollingElement ?? document.documentElement;
|
|
@@ -475,8 +485,8 @@ function useAutoGrow(value) {
|
|
|
475
485
|
if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;
|
|
476
486
|
if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
|
|
477
487
|
}, []);
|
|
478
|
-
|
|
479
|
-
|
|
488
|
+
useIsomorphicLayoutEffect(fit, [fit, value]);
|
|
489
|
+
React3.useEffect(() => {
|
|
480
490
|
const el = ref.current;
|
|
481
491
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
482
492
|
const observer = new ResizeObserver(([entry]) => {
|
package/dist/headless.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/headless.ts","../src/markdown/edit.ts","../src/markdown/line-at.ts","../src/markdown/line-toggles.ts","../src/markdown/list-continuation.ts","../src/markdown/indent-list.ts","../src/markdown/wrap-selection.ts","../src/markdown/insert-command.ts","../src/markdown/slash-trigger.ts","../src/markdown/shortcut-label.ts","../src/markdown/caret-coordinates.ts","../src/markdown/useMarkdownKeymap.ts","../src/hooks/useAutoGrow.ts"],"sourcesContent":["export { applyEdit, type Edit } from './markdown/edit';\nexport { lineAt } from './markdown/line-at';\nexport { toggleLinePrefix, type LinePrefix } from './markdown/line-toggles';\nexport { continueList } from './markdown/list-continuation';\nexport { indentList } from './markdown/indent-list';\nexport { wrapSelection, linkPaste, type WrapKind } from './markdown/wrap-selection';\nexport { insertCommand, type InsertableCommand } from './markdown/insert-command';\nexport { shouldOpenPalette } from './markdown/slash-trigger';\nexport { formatShortcut } from './markdown/shortcut-label';\nexport { caretRect, type CaretRect } from './markdown/caret-coordinates';\nexport { useMarkdownKeymap } from './markdown/useMarkdownKeymap';\nexport { useAutoGrow } from './hooks/useAutoGrow';\n","/** A text transform result: the next value and where the selection should land. */\nexport type Edit = { value: string; selectionStart: number; selectionEnd: number };\n\nfunction changedSpan(before: string, after: string) {\n let start = 0;\n const shortest = Math.min(before.length, after.length);\n while (start < shortest && before[start] === after[start]) start += 1;\n\n let endBefore = before.length;\n let endAfter = after.length;\n while (endBefore > start && endAfter > start && before[endBefore - 1] === after[endAfter - 1]) {\n endBefore -= 1;\n endAfter -= 1;\n }\n\n return { start, endBefore, replacement: after.slice(start, endAfter) };\n}\n\n// Writing through execCommand keeps the transform as one entry on the browser's\n// own undo stack. Either path commits el.value rather than the edit's intended\n// value: a browser is trusted to write what it was asked, and if it ever\n// wrote something else, committing our own intent instead would silently\n// diverge React's state from the DOM.\nfunction writeNatively(el: HTMLTextAreaElement, replacement: string): boolean {\n if (typeof document.execCommand !== 'function') return false;\n try {\n return replacement === ''\n ? document.execCommand('delete')\n : document.execCommand('insertText', false, replacement);\n } catch {\n return false;\n }\n}\n\n/** Applies an edit to a textarea through the native undo stack when the browser allows it, so Ctrl+Z still works. */\nexport function applyEdit(\n el: HTMLTextAreaElement,\n edit: Edit,\n commit: (value: string) => void,\n): void {\n if (el.value !== edit.value) {\n const { start, endBefore, replacement } = changedSpan(el.value, edit.value);\n el.setSelectionRange(start, endBefore);\n if (!writeNatively(el, replacement)) {\n el.value = edit.value;\n }\n commit(el.value);\n }\n el.setSelectionRange(edit.selectionStart, edit.selectionEnd);\n}\n","const FENCE = /^ {0,3}(```|~~~)/;\n\nexport type Line = { start: number; end: number; text: string };\n\n/** The line containing the offset, with its start and end offsets and its text (no trailing newline). */\nexport function lineAt(value: string, offset: number): Line {\n const start = value.lastIndexOf('\\n', offset - 1) + 1;\n const next = value.indexOf('\\n', offset);\n const end = next === -1 ? value.length : next;\n return { start, end, text: value.slice(start, end) };\n}\n\nexport function isFenceDelimiter(text: string): boolean {\n return FENCE.test(text);\n}\n\nexport function insideFence(value: string, offset: number): boolean {\n const lines = value.slice(0, offset).split('\\n');\n if (isFenceDelimiter(lines[lines.length - 1])) return true;\n\n let open = false;\n for (let i = 0; i < lines.length - 1; i += 1) {\n if (isFenceDelimiter(lines[i])) open = !open;\n }\n return open;\n}\n","import type { Edit } from './edit';\nimport { insideFence, isFenceDelimiter, lineAt } from './line-at';\n\n/** A line-level markdown prefix: a heading level, a bullet or a numbered item. */\nexport type LinePrefix = 'h1' | 'h2' | 'h3' | 'bullet' | 'ordered';\n\nconst EXISTING = /^(\\s*)(?:#{1,6} |[-*+] \\[[ xX]\\] |[-*+] |\\d+[.)] )?/;\n\nconst PREFIX: Record<LinePrefix, (index: number) => string> = {\n h1: () => '# ',\n h2: () => '## ',\n h3: () => '### ',\n bullet: () => '- ',\n ordered: (index) => `${index + 1}. `,\n};\n\nconst CARRIES: Record<LinePrefix, RegExp> = {\n h1: /^\\s*# /,\n h2: /^\\s*## /,\n h3: /^\\s*### /,\n bullet: /^\\s*[-*+] /,\n ordered: /^\\s*\\d+[.)] /,\n};\n\n/** Adds the prefix to every line in the selection, or removes it when every line already carries it; fenced code is left alone. */\nexport function toggleLinePrefix(\n kind: LinePrefix,\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): Edit {\n const first = lineAt(value, selectionStart);\n const last = lineAt(value, selectionEnd);\n const block = value.slice(first.start, last.end);\n const lines = block.split('\\n');\n\n let offset = first.start;\n const eligible = lines.map((text) => {\n const at = offset;\n offset += text.length + 1;\n return !insideFence(value, at) && !isFenceDelimiter(text);\n });\n\n const carried = lines.every((text, i) => !eligible[i] || CARRIES[kind].test(text));\n\n const rewritten = lines.map((text, i) => {\n if (!eligible[i]) return text;\n const match = EXISTING.exec(text);\n const indent = match?.[1] ?? '';\n const body = text.slice(match?.[0].length ?? 0);\n return carried ? indent + body : indent + PREFIX[kind](i) + body;\n });\n\n const joined = rewritten.join('\\n');\n const next = value.slice(0, first.start) + joined + value.slice(last.end);\n\n if (selectionStart !== selectionEnd) {\n return { value: next, selectionStart: first.start, selectionEnd: first.start + joined.length };\n }\n\n const caret = Math.max(first.start, selectionStart + (rewritten[0].length - lines[0].length));\n return { value: next, selectionStart: caret, selectionEnd: caret };\n}\n","import type { Edit } from './edit';\nimport { insideFence, lineAt, type Line } from './line-at';\n\nexport const LIST_MARKER = /^(\\s*)((?:[-*+] \\[[ xX]\\] )|(?:[-*+] )|(?:\\d+[.)] )|(?:> ))(.*)$/;\n\nconst INDENT = ' ';\n\nfunction nextMarker(marker: string): string {\n const ordered = /^(\\d+)([.)] )$/.exec(marker);\n if (ordered) return `${Number(ordered[1]) + 1}${ordered[2]}`;\n const task = /^([-*+]) \\[[ xX]\\] $/.exec(marker);\n if (task) return `${task[1]} [ ] `;\n return marker;\n}\n\n// Both the dead item on Enter and the caret at the content start on Backspace\n// mean the same thing: climb out one level, or drop the marker at the top.\nfunction climbOut(value: string, line: Line, indent: string, marker: string, content: string): Edit {\n const outdent = indent.length >= INDENT.length;\n\n if (outdent) {\n const text = indent.slice(INDENT.length) + marker + content;\n const at = line.start + indent.length - INDENT.length + marker.length;\n return {\n value: value.slice(0, line.start) + text + value.slice(line.end),\n selectionStart: at,\n selectionEnd: at,\n };\n }\n\n // A stripped marker with only a single newline above it reads to CommonMark\n // as a lazy continuation of the previous list item's paragraph, not a new\n // block, so a blank line is needed whenever that previous line is a list item.\n const leavesList = line.start > 0 && LIST_MARKER.test(lineAt(value, line.start - 1).text);\n const text = leavesList ? `\\n${content}` : content;\n const at = leavesList ? line.start + 1 : line.start;\n\n return {\n value: value.slice(0, line.start) + text + value.slice(line.end),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n\n/** Continues a list item on Enter, or climbs out of the list on an empty item or Backspace at the marker; returns null when the caret is not in a list item or is inside a code fence, so the default key behaviour runs. */\nexport function continueList(\n value: string,\n caret: number,\n key: 'Enter' | 'Backspace',\n): Edit | null {\n if (insideFence(value, caret)) return null;\n\n const line = lineAt(value, caret);\n const match = LIST_MARKER.exec(line.text);\n if (!match) return null;\n\n const [, indent, marker, content] = match;\n\n if (key === 'Backspace') {\n if (caret !== line.start + indent.length + marker.length) return null;\n return climbOut(value, line, indent, marker, content);\n }\n\n if (content.trim() === '') return climbOut(value, line, indent, marker, content);\n\n const inserted = `\\n${indent}${nextMarker(marker)}`;\n const at = caret + inserted.length;\n return {\n value: value.slice(0, caret) + inserted + value.slice(caret),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n","import type { Edit } from './edit';\nimport { insideFence, lineAt } from './line-at';\nimport { LIST_MARKER } from './list-continuation';\n\nconst INDENT = ' ';\n\nfunction indentOf(text: string): number {\n return /^\\s*/.exec(text)?.[0].length ?? 0;\n}\n\n// Indenting more than one level past the line above turns a list item into an\n// indented code block, so the first line of the range is capped against it.\nfunction shift(text: string, direction: 1 | -1, cap: number | undefined): string {\n const current = indentOf(text);\n if (direction === -1) return text.slice(Math.min(current, INDENT.length));\n if (cap !== undefined && current >= cap + INDENT.length) return text;\n return INDENT + text;\n}\n\n/** Indents or outdents the selected list items; returns null when the selection is not entirely list items, is inside a code fence, or cannot move further, so Tab keeps its default behaviour. */\nexport function indentList(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n direction: 1 | -1,\n): Edit | null {\n if (insideFence(value, selectionStart)) return null;\n\n const first = lineAt(value, selectionStart);\n const last = lineAt(value, selectionEnd);\n const block = value.slice(first.start, last.end);\n const lines = block.split('\\n');\n if (!lines.every((text) => LIST_MARKER.test(text))) return null;\n\n const cap = first.start === 0 ? 0 : indentOf(lineAt(value, first.start - 1).text);\n const shifted = lines.map((text, i) => shift(text, direction, i === 0 ? cap : undefined));\n if (shifted.every((text, i) => text === lines[i])) return null;\n\n const joined = shifted.join('\\n');\n return {\n value: value.slice(0, first.start) + joined + value.slice(last.end),\n selectionStart: selectionStart + (shifted[0].length - lines[0].length),\n selectionEnd: selectionEnd + (joined.length - block.length),\n };\n}\n","import type { Edit } from './edit';\n\n/** An inline markdown wrapper: bold, italic, code or a link. */\nexport type WrapKind = 'bold' | 'italic' | 'code' | 'link';\n\nconst DELIMITER: Record<Exclude<WrapKind, 'link'>, string> = {\n bold: '**',\n italic: '_',\n code: '`',\n};\n\nconst WHOLE_LINK = /^\\[([^\\]]*)\\]\\(([^)]*)\\)$/;\nconst BARE_URL = /^https?:\\/\\/[^\\s]+$/;\n\nfunction unwrap(value: string, start: number, end: number, delimiter: string): Edit | null {\n const selected = value.slice(start, end);\n const width = delimiter.length;\n\n if (selected.length >= 2 * width && selected.startsWith(delimiter) && selected.endsWith(delimiter)) {\n const inner = selected.slice(width, selected.length - width);\n return {\n value: value.slice(0, start) + inner + value.slice(end),\n selectionStart: start,\n selectionEnd: start + inner.length,\n };\n }\n\n const outside =\n start >= width &&\n value.slice(start - width, start) === delimiter &&\n value.slice(end, end + width) === delimiter;\n\n if (!outside) return null;\n\n return {\n value: value.slice(0, start - width) + selected + value.slice(end + width),\n selectionStart: start - width,\n selectionEnd: start - width + selected.length,\n };\n}\n\nfunction link(value: string, start: number, end: number): Edit {\n const selected = value.slice(start, end);\n const whole = WHOLE_LINK.exec(selected);\n\n if (whole) {\n const text = whole[1];\n return {\n value: value.slice(0, start) + text + value.slice(end),\n selectionStart: start,\n selectionEnd: start + text.length,\n };\n }\n\n const opened = `[${selected}](`;\n const at = selected === '' ? start + 1 : start + opened.length;\n return {\n value: value.slice(0, start) + opened + ')' + value.slice(end),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n\n/** Wraps the selection in the markers for the kind, or removes them when it is already wrapped. */\nexport function wrapSelection(\n kind: WrapKind,\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): Edit {\n if (kind === 'link') return link(value, selectionStart, selectionEnd);\n\n const delimiter = DELIMITER[kind];\n const stripped = unwrap(value, selectionStart, selectionEnd, delimiter);\n if (stripped) return stripped;\n\n const selected = value.slice(selectionStart, selectionEnd);\n\n if (kind === 'code' && selected.includes('\\n')) {\n const at = selectionStart + 4;\n return {\n value: value.slice(0, selectionStart) + '```\\n' + selected + '\\n```' + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at + selected.length,\n };\n }\n\n const at = selectionStart + delimiter.length;\n return {\n value: value.slice(0, selectionStart) + delimiter + selected + delimiter + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at + selected.length,\n };\n}\n\n/** Turns a pasted URL over a selection into a markdown link; returns null when nothing is selected or the paste is not a bare URL, so the paste goes through unchanged. */\nexport function linkPaste(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n pasted: string,\n): Edit | null {\n if (selectionStart === selectionEnd) return null;\n\n const url = pasted.trim();\n if (!BARE_URL.test(url)) return null;\n\n const inserted = `[${value.slice(selectionStart, selectionEnd)}](${url})`;\n const at = selectionStart + inserted.length;\n return {\n value: value.slice(0, selectionStart) + inserted + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n","import type { Edit } from './edit';\nimport { lineAt } from './line-at';\n\n/** The fields `insertCommand` needs. `TextCommand` satisfies this structurally. */\nexport type InsertableCommand = {\n insert: string;\n caretOffset: number;\n selectLength?: number;\n separate: boolean;\n};\n\n/** Inserts a command's markdown at the caret, starting a new line (or a blank line if it asks) when the caret is mid-line. */\nexport function insertCommand(command: InsertableCommand, value: string, caret: number): Edit {\n const line = lineAt(value, caret);\n const before = value.slice(line.start, caret).replace(/[ \\t]+$/, '');\n const cut = line.start + before.length;\n const prefix = before.trim() === '' ? '' : command.separate ? '\\n\\n' : '\\n';\n const at = cut + prefix.length + command.caretOffset;\n\n return {\n value: value.slice(0, cut) + prefix + command.insert + value.slice(caret),\n selectionStart: at,\n selectionEnd: at + (command.selectLength ?? 0),\n };\n}\n","import { insideFence } from './line-at';\n\n/** Whether typing \"/\" here should open the command palette: only at a word start, with no selection and outside a code fence. */\nexport function shouldOpenPalette(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): boolean {\n if (selectionStart !== selectionEnd) return false;\n if (insideFence(value, selectionStart)) return false;\n if (selectionStart === 0) return true;\n\n const previous = value[selectionStart - 1];\n return previous === '\\n' || previous === ' ' || previous === '\\t';\n}\n","const MAC_LABEL: Record<string, string> = { mod: '⌘', alt: '⌥', shift: '⇧' };\nconst OTHER_LABEL: Record<string, string> = { mod: 'Ctrl', alt: 'Alt', shift: 'Shift' };\n\nfunction onMac(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /Mac|iPhone|iPad/.test(navigator.userAgent);\n}\n\n/** Renders a \"mod+shift+b\" style hint as the platform's own label, such as ⌘⇧B on a Mac and Ctrl+Shift+B elsewhere. */\nexport function formatShortcut(hint: string): string {\n const mac = onMac();\n const labels = mac ? MAC_LABEL : OTHER_LABEL;\n const parts = hint.split('+').map((part) => labels[part] ?? part.toUpperCase());\n return parts.join(mac ? '' : '+');\n}\n","type CopiedProperty =\n | 'boxSizing'\n | 'fontFamily'\n | 'fontSize'\n | 'fontStyle'\n | 'fontWeight'\n | 'letterSpacing'\n | 'lineHeight'\n | 'paddingBottom'\n | 'paddingLeft'\n | 'paddingRight'\n | 'paddingTop'\n | 'textIndent'\n | 'textTransform'\n | 'wordBreak'\n | 'wordSpacing'\n | 'borderBottomWidth'\n | 'borderLeftWidth'\n | 'borderRightWidth'\n | 'borderTopWidth';\n\nconst COPIED: CopiedProperty[] = [\n 'boxSizing',\n 'fontFamily',\n 'fontSize',\n 'fontStyle',\n 'fontWeight',\n 'letterSpacing',\n 'lineHeight',\n 'paddingBottom',\n 'paddingLeft',\n 'paddingRight',\n 'paddingTop',\n 'textIndent',\n 'textTransform',\n 'wordBreak',\n 'wordSpacing',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'borderRightWidth',\n 'borderTopWidth',\n];\n\n/** The caret's position and line height, in pixels relative to the textarea's top left corner. */\nexport type CaretRect = { top: number; left: number; height: number };\n\n/** A textarea exposes no caret geometry, so the text before the caret is laid\n * out again in a hidden div that copies the box's own metrics, and a marker at\n * the end of it is measured. Returns null when the textarea is not laid out\n * (hidden or detached), because there is nothing to measure. */\nexport function caretRect(el: HTMLTextAreaElement): CaretRect | null {\n if (el.offsetWidth === 0) return null;\n\n const style = window.getComputedStyle(el);\n const mirror = document.createElement('div');\n for (const property of COPIED) mirror.style[property] = style[property];\n mirror.style.position = 'absolute';\n mirror.style.top = '0';\n mirror.style.left = '-9999px';\n mirror.style.visibility = 'hidden';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.overflowWrap = 'break-word';\n mirror.style.width = `${el.clientWidth}px`;\n mirror.textContent = el.value.slice(0, el.selectionStart);\n\n const marker = document.createElement('span');\n marker.textContent = '\\u200b';\n mirror.appendChild(marker);\n document.body.appendChild(mirror);\n\n const top = marker.offsetTop;\n const left = marker.offsetLeft;\n const height = marker.offsetHeight || Number.parseFloat(style.lineHeight) || 0;\n mirror.remove();\n\n if (height === 0) return null;\n return { top: top - el.scrollTop, left, height };\n}\n","import * as React from 'react';\nimport { applyEdit, type Edit } from './edit';\nimport { indentList } from './indent-list';\nimport { toggleLinePrefix, type LinePrefix } from './line-toggles';\nimport { continueList } from './list-continuation';\nimport { shouldOpenPalette } from './slash-trigger';\nimport { linkPaste, wrapSelection, type WrapKind } from './wrap-selection';\n\nconst WRAP: Record<string, WrapKind> = { b: 'bold', i: 'italic', e: 'code', k: 'link' };\n\n// Shift turns the 8 key into \"*\", so the physical code is the only stable match.\nconst HEADING: Record<string, LinePrefix> = { Digit1: 'h1', Digit2: 'h2', Digit3: 'h3' };\nconst LIST: Record<string, LinePrefix> = { Digit7: 'ordered', Digit8: 'bullet' };\n\n/** Keyboard and paste handlers that give a plain textarea markdown shortcuts, list continuation, Tab indent and the slash palette trigger. */\nexport function useMarkdownKeymap({\n commit,\n onOpenPalette,\n}: {\n commit: (markdown: string) => void;\n onOpenPalette: () => void;\n}) {\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n const el = event.currentTarget;\n const { value, selectionStart, selectionEnd } = el;\n const mod = event.metaKey || event.ctrlKey;\n\n const run = (edit: Edit | null) => {\n if (!edit) return;\n event.preventDefault();\n applyEdit(el, edit, commit);\n };\n\n if (mod && event.altKey) {\n const kind = HEADING[event.code];\n if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (mod && event.shiftKey) {\n const kind = LIST[event.code];\n if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (mod) {\n const kind = WRAP[event.key.toLowerCase()];\n if (kind) run(wrapSelection(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (event.key === '/') {\n if (!shouldOpenPalette(value, selectionStart, selectionEnd)) return;\n event.preventDefault();\n onOpenPalette();\n return;\n }\n\n if (event.key === 'Enter' && !event.shiftKey) {\n run(continueList(value, selectionStart, 'Enter'));\n return;\n }\n\n if (event.key === 'Backspace' && selectionStart === selectionEnd) {\n run(continueList(value, selectionStart, 'Backspace'));\n return;\n }\n\n if (event.key === 'Tab') {\n run(indentList(value, selectionStart, selectionEnd, event.shiftKey ? -1 : 1));\n }\n },\n [commit, onOpenPalette],\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const el = event.currentTarget;\n const edit = linkPaste(\n el.value,\n el.selectionStart,\n el.selectionEnd,\n event.clipboardData.getData('text/plain'),\n );\n if (!edit) return;\n event.preventDefault();\n applyEdit(el, edit, commit);\n },\n [commit],\n );\n\n return { onKeyDown, onPaste };\n}\n","import * as React from 'react';\n\n/** Height follows the content, and is recomputed when the box gets narrower or\n * wider, because a re-wrap changes the line count without changing the value.\n * Only a width change refits: reacting to the height this hook just set would\n * feed the observer its own output. */\nexport function useAutoGrow<T extends HTMLTextAreaElement>(value: string) {\n const ref = React.useRef<T>(null);\n const lastWidth = React.useRef<number | null>(null);\n\n // Measuring means collapsing to \"auto\" first, which shrinks the page enough that\n // the browser clamps the scroll position, and growing back does not undo that.\n // Both steps run before paint, so restoring the scroll here is not visible.\n const fit = React.useCallback(() => {\n const el = ref.current;\n if (!el) return;\n const scroller = document.scrollingElement ?? document.documentElement;\n const scrollTop = scroller.scrollTop;\n el.style.height = 'auto';\n if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;\n if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;\n }, []);\n\n React.useLayoutEffect(fit, [fit, value]);\n\n React.useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(([entry]) => {\n const width = entry.contentRect.width;\n if (width === lastWidth.current) return;\n lastWidth.current = width;\n fit();\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [fit]);\n\n return ref;\n}\n"],"mappings":"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","names":["text","at","INDENT","at","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/headless.ts","../src/markdown/edit.ts","../src/markdown/line-at.ts","../src/markdown/line-toggles.ts","../src/markdown/list-continuation.ts","../src/markdown/indent-list.ts","../src/markdown/wrap-selection.ts","../src/markdown/insert-command.ts","../src/markdown/slash-trigger.ts","../src/markdown/platform.ts","../src/markdown/shortcut-label.ts","../src/markdown/caret-coordinates.ts","../src/markdown/useMarkdownKeymap.ts","../src/hooks/useAutoGrow.ts","../src/hooks/use-isomorphic-layout-effect.ts"],"sourcesContent":["export { applyEdit, type Edit } from './markdown/edit';\nexport { lineAt } from './markdown/line-at';\nexport { toggleLinePrefix, type LinePrefix } from './markdown/line-toggles';\nexport { continueList } from './markdown/list-continuation';\nexport { indentList } from './markdown/indent-list';\nexport { wrapSelection, linkPaste, type WrapKind } from './markdown/wrap-selection';\nexport { insertCommand, type InsertableCommand } from './markdown/insert-command';\nexport { shouldOpenPalette } from './markdown/slash-trigger';\nexport { formatShortcut } from './markdown/shortcut-label';\nexport { caretRect, type CaretRect } from './markdown/caret-coordinates';\nexport { useMarkdownKeymap } from './markdown/useMarkdownKeymap';\nexport { useAutoGrow } from './hooks/useAutoGrow';\n","/** A text transform result: the next value and where the selection should land. */\nexport type Edit = { value: string; selectionStart: number; selectionEnd: number };\n\nfunction changedSpan(before: string, after: string) {\n let start = 0;\n const shortest = Math.min(before.length, after.length);\n while (start < shortest && before[start] === after[start]) start += 1;\n\n let endBefore = before.length;\n let endAfter = after.length;\n while (endBefore > start && endAfter > start && before[endBefore - 1] === after[endAfter - 1]) {\n endBefore -= 1;\n endAfter -= 1;\n }\n\n return { start, endBefore, replacement: after.slice(start, endAfter) };\n}\n\n// Writing through execCommand keeps the transform as one entry on the browser's\n// own undo stack. Either path commits el.value rather than the edit's intended\n// value: a browser is trusted to write what it was asked, and if it ever\n// wrote something else, committing our own intent instead would silently\n// diverge React's state from the DOM.\nfunction writeNatively(el: HTMLTextAreaElement, replacement: string): boolean {\n if (typeof document.execCommand !== 'function') return false;\n try {\n return replacement === ''\n ? document.execCommand('delete')\n : document.execCommand('insertText', false, replacement);\n } catch {\n return false;\n }\n}\n\n/** Applies an edit to a textarea through the native undo stack when the browser allows it, so Ctrl+Z still works. */\nexport function applyEdit(\n el: HTMLTextAreaElement,\n edit: Edit,\n commit: (value: string) => void,\n): void {\n if (el.value !== edit.value) {\n const { start, endBefore, replacement } = changedSpan(el.value, edit.value);\n el.setSelectionRange(start, endBefore);\n if (!writeNatively(el, replacement)) {\n el.value = edit.value;\n }\n commit(el.value);\n }\n el.setSelectionRange(edit.selectionStart, edit.selectionEnd);\n}\n","const FENCE = /^ {0,3}(```|~~~)/;\n\nexport type Line = { start: number; end: number; text: string };\n\n/** The line containing the offset, with its start and end offsets and its text (no trailing newline). */\nexport function lineAt(value: string, offset: number): Line {\n const start = value.lastIndexOf('\\n', offset - 1) + 1;\n const next = value.indexOf('\\n', offset);\n const end = next === -1 ? value.length : next;\n return { start, end, text: value.slice(start, end) };\n}\n\nexport function isFenceDelimiter(text: string): boolean {\n return FENCE.test(text);\n}\n\nexport function insideFence(value: string, offset: number): boolean {\n const lines = value.slice(0, offset).split('\\n');\n if (isFenceDelimiter(lines[lines.length - 1])) return true;\n\n let open = false;\n for (let i = 0; i < lines.length - 1; i += 1) {\n if (isFenceDelimiter(lines[i])) open = !open;\n }\n return open;\n}\n","import type { Edit } from './edit';\nimport { insideFence, isFenceDelimiter, lineAt } from './line-at';\n\n/** A line-level markdown prefix: a heading level, a bullet or a numbered item. */\nexport type LinePrefix = 'h1' | 'h2' | 'h3' | 'bullet' | 'ordered';\n\nconst EXISTING = /^(\\s*)(?:#{1,6} |[-*+] \\[[ xX]\\] |[-*+] |\\d+[.)] )?/;\n\nconst PREFIX: Record<LinePrefix, (index: number) => string> = {\n h1: () => '# ',\n h2: () => '## ',\n h3: () => '### ',\n bullet: () => '- ',\n ordered: (index) => `${index + 1}. `,\n};\n\nconst CARRIES: Record<LinePrefix, RegExp> = {\n h1: /^\\s*# /,\n h2: /^\\s*## /,\n h3: /^\\s*### /,\n bullet: /^\\s*[-*+] /,\n ordered: /^\\s*\\d+[.)] /,\n};\n\n/** Adds the prefix to every line in the selection, or removes it when every line already carries it; fenced code is left alone. */\nexport function toggleLinePrefix(\n kind: LinePrefix,\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): Edit {\n const first = lineAt(value, selectionStart);\n const last = lineAt(value, selectionEnd);\n const block = value.slice(first.start, last.end);\n const lines = block.split('\\n');\n\n let offset = first.start;\n const eligible = lines.map((text) => {\n const at = offset;\n offset += text.length + 1;\n return !insideFence(value, at) && !isFenceDelimiter(text);\n });\n\n const carried = lines.every((text, i) => !eligible[i] || CARRIES[kind].test(text));\n\n const rewritten = lines.map((text, i) => {\n if (!eligible[i]) return text;\n const match = EXISTING.exec(text);\n const indent = match?.[1] ?? '';\n const body = text.slice(match?.[0].length ?? 0);\n return carried ? indent + body : indent + PREFIX[kind](i) + body;\n });\n\n const joined = rewritten.join('\\n');\n const next = value.slice(0, first.start) + joined + value.slice(last.end);\n\n if (selectionStart !== selectionEnd) {\n return { value: next, selectionStart: first.start, selectionEnd: first.start + joined.length };\n }\n\n const caret = Math.max(first.start, selectionStart + (rewritten[0].length - lines[0].length));\n return { value: next, selectionStart: caret, selectionEnd: caret };\n}\n","import type { Edit } from './edit';\nimport { insideFence, lineAt, type Line } from './line-at';\n\nexport const LIST_MARKER = /^(\\s*)((?:[-*+] \\[[ xX]\\] )|(?:[-*+] )|(?:\\d+[.)] )|(?:> ))(.*)$/;\n\nconst INDENT = ' ';\n\nfunction nextMarker(marker: string): string {\n const ordered = /^(\\d+)([.)] )$/.exec(marker);\n if (ordered) return `${Number(ordered[1]) + 1}${ordered[2]}`;\n const task = /^([-*+]) \\[[ xX]\\] $/.exec(marker);\n if (task) return `${task[1]} [ ] `;\n return marker;\n}\n\n// Both the dead item on Enter and the caret at the content start on Backspace\n// mean the same thing: climb out one level, or drop the marker at the top.\nfunction climbOut(value: string, line: Line, indent: string, marker: string, content: string): Edit {\n const outdent = indent.length >= INDENT.length;\n\n if (outdent) {\n const text = indent.slice(INDENT.length) + marker + content;\n const at = line.start + indent.length - INDENT.length + marker.length;\n return {\n value: value.slice(0, line.start) + text + value.slice(line.end),\n selectionStart: at,\n selectionEnd: at,\n };\n }\n\n // A stripped marker with only a single newline above it reads to CommonMark\n // as a lazy continuation of the previous list item's paragraph, not a new\n // block, so a blank line is needed whenever that previous line is a list item.\n const leavesList = line.start > 0 && LIST_MARKER.test(lineAt(value, line.start - 1).text);\n const text = leavesList ? `\\n${content}` : content;\n const at = leavesList ? line.start + 1 : line.start;\n\n return {\n value: value.slice(0, line.start) + text + value.slice(line.end),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n\n/** Continues a list item on Enter, or climbs out of the list on an empty item or Backspace at the marker; returns null when the caret is not in a list item or is inside a code fence, so the default key behaviour runs. */\nexport function continueList(\n value: string,\n caret: number,\n key: 'Enter' | 'Backspace',\n): Edit | null {\n if (insideFence(value, caret)) return null;\n\n const line = lineAt(value, caret);\n const match = LIST_MARKER.exec(line.text);\n if (!match) return null;\n\n const [, indent, marker, content] = match;\n\n if (key === 'Backspace') {\n if (caret !== line.start + indent.length + marker.length) return null;\n return climbOut(value, line, indent, marker, content);\n }\n\n if (content.trim() === '') return climbOut(value, line, indent, marker, content);\n\n const inserted = `\\n${indent}${nextMarker(marker)}`;\n const at = caret + inserted.length;\n return {\n value: value.slice(0, caret) + inserted + value.slice(caret),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n","import type { Edit } from './edit';\nimport { insideFence, lineAt } from './line-at';\nimport { LIST_MARKER } from './list-continuation';\n\nconst INDENT = ' ';\n\nfunction indentOf(text: string): number {\n return /^\\s*/.exec(text)?.[0].length ?? 0;\n}\n\n// Indenting more than one level past the line above turns a list item into an\n// indented code block, so the first line of the range is capped against it.\nfunction shift(text: string, direction: 1 | -1, cap: number | undefined): string {\n const current = indentOf(text);\n if (direction === -1) return text.slice(Math.min(current, INDENT.length));\n if (cap !== undefined && current >= cap + INDENT.length) return text;\n return INDENT + text;\n}\n\n/** Indents or outdents the selected list items; returns null when the selection is not entirely list items, is inside a code fence, or cannot move further, so Tab keeps its default behaviour. */\nexport function indentList(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n direction: 1 | -1,\n): Edit | null {\n if (insideFence(value, selectionStart)) return null;\n\n const first = lineAt(value, selectionStart);\n const last = lineAt(value, selectionEnd);\n const block = value.slice(first.start, last.end);\n const lines = block.split('\\n');\n if (!lines.every((text) => LIST_MARKER.test(text))) return null;\n\n const cap = first.start === 0 ? 0 : indentOf(lineAt(value, first.start - 1).text);\n const shifted = lines.map((text, i) => shift(text, direction, i === 0 ? cap : undefined));\n if (shifted.every((text, i) => text === lines[i])) return null;\n\n const joined = shifted.join('\\n');\n return {\n value: value.slice(0, first.start) + joined + value.slice(last.end),\n selectionStart: selectionStart + (shifted[0].length - lines[0].length),\n selectionEnd: selectionEnd + (joined.length - block.length),\n };\n}\n","import type { Edit } from './edit';\n\n/** An inline markdown wrapper: bold, italic, code or a link. */\nexport type WrapKind = 'bold' | 'italic' | 'code' | 'link';\n\nconst DELIMITER: Record<Exclude<WrapKind, 'link'>, string> = {\n bold: '**',\n italic: '_',\n code: '`',\n};\n\nconst WHOLE_LINK = /^\\[([^\\]]*)\\]\\(([^)]*)\\)$/;\nconst BARE_URL = /^https?:\\/\\/[^\\s]+$/;\n\nfunction unwrap(value: string, start: number, end: number, delimiter: string): Edit | null {\n const selected = value.slice(start, end);\n const width = delimiter.length;\n\n if (selected.length >= 2 * width && selected.startsWith(delimiter) && selected.endsWith(delimiter)) {\n const inner = selected.slice(width, selected.length - width);\n return {\n value: value.slice(0, start) + inner + value.slice(end),\n selectionStart: start,\n selectionEnd: start + inner.length,\n };\n }\n\n const outside =\n start >= width &&\n value.slice(start - width, start) === delimiter &&\n value.slice(end, end + width) === delimiter;\n\n if (!outside) return null;\n\n return {\n value: value.slice(0, start - width) + selected + value.slice(end + width),\n selectionStart: start - width,\n selectionEnd: start - width + selected.length,\n };\n}\n\nfunction link(value: string, start: number, end: number): Edit {\n const selected = value.slice(start, end);\n const whole = WHOLE_LINK.exec(selected);\n\n if (whole) {\n const text = whole[1];\n return {\n value: value.slice(0, start) + text + value.slice(end),\n selectionStart: start,\n selectionEnd: start + text.length,\n };\n }\n\n const opened = `[${selected}](`;\n const at = selected === '' ? start + 1 : start + opened.length;\n return {\n value: value.slice(0, start) + opened + ')' + value.slice(end),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n\n/** Wraps the selection in the markers for the kind, or removes them when it is already wrapped. */\nexport function wrapSelection(\n kind: WrapKind,\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): Edit {\n if (kind === 'link') return link(value, selectionStart, selectionEnd);\n\n const delimiter = DELIMITER[kind];\n const stripped = unwrap(value, selectionStart, selectionEnd, delimiter);\n if (stripped) return stripped;\n\n const selected = value.slice(selectionStart, selectionEnd);\n\n if (kind === 'code' && selected.includes('\\n')) {\n const at = selectionStart + 4;\n return {\n value: value.slice(0, selectionStart) + '```\\n' + selected + '\\n```' + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at + selected.length,\n };\n }\n\n const at = selectionStart + delimiter.length;\n return {\n value: value.slice(0, selectionStart) + delimiter + selected + delimiter + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at + selected.length,\n };\n}\n\n/** Turns a pasted URL over a selection into a markdown link; returns null when nothing is selected or the paste is not a bare URL, so the paste goes through unchanged. */\nexport function linkPaste(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n pasted: string,\n): Edit | null {\n if (selectionStart === selectionEnd) return null;\n\n const url = pasted.trim();\n if (!BARE_URL.test(url)) return null;\n\n const inserted = `[${value.slice(selectionStart, selectionEnd)}](${url})`;\n const at = selectionStart + inserted.length;\n return {\n value: value.slice(0, selectionStart) + inserted + value.slice(selectionEnd),\n selectionStart: at,\n selectionEnd: at,\n };\n}\n","import type { Edit } from './edit';\nimport { lineAt } from './line-at';\n\n/** The fields `insertCommand` needs. `TextCommand` satisfies this structurally. */\nexport type InsertableCommand = {\n insert: string;\n caretOffset: number;\n selectLength?: number;\n separate: boolean;\n};\n\n/** Inserts a command's markdown at the caret, starting a new line (or a blank line if it asks) when the caret is mid-line. */\nexport function insertCommand(command: InsertableCommand, value: string, caret: number): Edit {\n const line = lineAt(value, caret);\n const before = value.slice(line.start, caret).replace(/[ \\t]+$/, '');\n const cut = line.start + before.length;\n const prefix = before.trim() === '' ? '' : command.separate ? '\\n\\n' : '\\n';\n const at = cut + prefix.length + command.caretOffset;\n\n return {\n value: value.slice(0, cut) + prefix + command.insert + value.slice(caret),\n selectionStart: at,\n selectionEnd: at + (command.selectLength ?? 0),\n };\n}\n","import { insideFence } from './line-at';\n\n/** Whether typing \"/\" here should open the command palette: only at a word start, with no selection and outside a code fence. */\nexport function shouldOpenPalette(\n value: string,\n selectionStart: number,\n selectionEnd: number,\n): boolean {\n if (selectionStart !== selectionEnd) return false;\n if (insideFence(value, selectionStart)) return false;\n if (selectionStart === 0) return true;\n\n const previous = value[selectionStart - 1];\n return previous === '\\n' || previous === ' ' || previous === '\\t';\n}\n","type NavigatorWithData = Navigator & { userAgentData?: { platform?: string } };\n\n/** True on macOS and iOS, where the shortcut modifier is Command rather than Ctrl. Read on each call so a stubbed navigator is honoured. */\nexport function isMac(): boolean {\n if (typeof navigator === 'undefined') return false;\n const { userAgentData, platform } = navigator as NavigatorWithData;\n return /mac|iphone|ipad/i.test(userAgentData?.platform || platform || '');\n}\n","import { isMac } from './platform';\n\nconst MAC_LABEL: Record<string, string> = { mod: '⌘', alt: '⌥', shift: '⇧' };\nconst OTHER_LABEL: Record<string, string> = { mod: 'Ctrl', alt: 'Alt', shift: 'Shift' };\n\n/** Renders a \"mod+shift+b\" style hint as the platform's own label, such as ⌘⇧B on a Mac and Ctrl+Shift+B elsewhere. */\nexport function formatShortcut(hint: string): string {\n const mac = isMac();\n const labels = mac ? MAC_LABEL : OTHER_LABEL;\n const parts = hint.split('+').map((part) => labels[part] ?? part.toUpperCase());\n return parts.join(mac ? '' : '+');\n}\n","type CopiedProperty =\n | 'boxSizing'\n | 'fontFamily'\n | 'fontSize'\n | 'fontStyle'\n | 'fontWeight'\n | 'letterSpacing'\n | 'lineHeight'\n | 'paddingBottom'\n | 'paddingLeft'\n | 'paddingRight'\n | 'paddingTop'\n | 'textIndent'\n | 'textTransform'\n | 'wordBreak'\n | 'wordSpacing'\n | 'borderBottomWidth'\n | 'borderLeftWidth'\n | 'borderRightWidth'\n | 'borderTopWidth';\n\nconst COPIED: CopiedProperty[] = [\n 'boxSizing',\n 'fontFamily',\n 'fontSize',\n 'fontStyle',\n 'fontWeight',\n 'letterSpacing',\n 'lineHeight',\n 'paddingBottom',\n 'paddingLeft',\n 'paddingRight',\n 'paddingTop',\n 'textIndent',\n 'textTransform',\n 'wordBreak',\n 'wordSpacing',\n 'borderBottomWidth',\n 'borderLeftWidth',\n 'borderRightWidth',\n 'borderTopWidth',\n];\n\n/** The caret's position and line height, in pixels relative to the textarea's top left corner. */\nexport type CaretRect = { top: number; left: number; height: number };\n\n/** A textarea exposes no caret geometry, so the text before the caret is laid\n * out again in a hidden div that copies the box's own metrics, and a marker at\n * the end of it is measured. Returns null when the textarea is not laid out\n * (hidden or detached), because there is nothing to measure. */\nexport function caretRect(el: HTMLTextAreaElement): CaretRect | null {\n if (el.offsetWidth === 0) return null;\n\n const style = window.getComputedStyle(el);\n const mirror = document.createElement('div');\n for (const property of COPIED) mirror.style[property] = style[property];\n mirror.style.position = 'absolute';\n mirror.style.top = '0';\n mirror.style.left = '-9999px';\n mirror.style.visibility = 'hidden';\n mirror.style.whiteSpace = 'pre-wrap';\n mirror.style.overflowWrap = 'break-word';\n mirror.style.width = `${el.clientWidth}px`;\n mirror.textContent = el.value.slice(0, el.selectionStart);\n\n const marker = document.createElement('span');\n marker.textContent = '\\u200b';\n mirror.appendChild(marker);\n document.body.appendChild(mirror);\n\n const top = marker.offsetTop;\n const left = marker.offsetLeft;\n const height = marker.offsetHeight || Number.parseFloat(style.lineHeight) || 0;\n mirror.remove();\n\n if (height === 0) return null;\n return { top: top - el.scrollTop, left, height };\n}\n","import * as React from 'react';\nimport { applyEdit, type Edit } from './edit';\nimport { indentList } from './indent-list';\nimport { toggleLinePrefix, type LinePrefix } from './line-toggles';\nimport { continueList } from './list-continuation';\nimport { isMac } from './platform';\nimport { shouldOpenPalette } from './slash-trigger';\nimport { linkPaste, wrapSelection, type WrapKind } from './wrap-selection';\n\nconst WRAP: Record<string, WrapKind> = { b: 'bold', i: 'italic', e: 'code', k: 'link' };\n\n// Shift turns the 8 key into \"*\", so the physical code is the only stable match.\nconst HEADING: Record<string, LinePrefix> = { Digit1: 'h1', Digit2: 'h2', Digit3: 'h3' };\nconst LIST: Record<string, LinePrefix> = { Digit7: 'ordered', Digit8: 'bullet' };\n\n/** Keyboard and paste handlers that give a plain textarea markdown shortcuts, list continuation, Tab indent and the slash palette trigger. */\nexport function useMarkdownKeymap({\n commit,\n onOpenPalette,\n}: {\n commit: (markdown: string) => void;\n onOpenPalette: () => void;\n}) {\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n // Safari reports keyCode 229 for the keydown that follows compositionend.\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n\n const el = event.currentTarget;\n const { value, selectionStart, selectionEnd } = el;\n const mod = isMac() ? event.metaKey : event.ctrlKey;\n\n const run = (edit: Edit | null) => {\n if (!edit) return;\n event.preventDefault();\n applyEdit(el, edit, commit);\n };\n\n if (mod && event.altKey) {\n const kind = HEADING[event.code];\n if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (mod && event.shiftKey) {\n const kind = LIST[event.code];\n if (kind) run(toggleLinePrefix(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (mod) {\n const kind = WRAP[event.key.toLowerCase()];\n if (kind) run(wrapSelection(kind, value, selectionStart, selectionEnd));\n return;\n }\n\n if (event.key === '/') {\n if (!shouldOpenPalette(value, selectionStart, selectionEnd)) return;\n event.preventDefault();\n onOpenPalette();\n return;\n }\n\n if (event.key === 'Enter' && !event.shiftKey) {\n run(continueList(value, selectionStart, 'Enter'));\n return;\n }\n\n if (event.key === 'Backspace' && selectionStart === selectionEnd) {\n run(continueList(value, selectionStart, 'Backspace'));\n return;\n }\n\n if (event.key === 'Tab') {\n run(indentList(value, selectionStart, selectionEnd, event.shiftKey ? -1 : 1));\n }\n },\n [commit, onOpenPalette],\n );\n\n const onPaste = React.useCallback(\n (event: React.ClipboardEvent<HTMLTextAreaElement>) => {\n const el = event.currentTarget;\n const edit = linkPaste(\n el.value,\n el.selectionStart,\n el.selectionEnd,\n event.clipboardData.getData('text/plain'),\n );\n if (!edit) return;\n event.preventDefault();\n applyEdit(el, edit, commit);\n },\n [commit],\n );\n\n return { onKeyDown, onPaste };\n}\n","import * as React from 'react';\nimport { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';\n\n/** Height follows the content, and is recomputed when the box gets narrower or\n * wider, because a re-wrap changes the line count without changing the value.\n * Only a width change refits: reacting to the height this hook just set would\n * feed the observer its own output. */\nexport function useAutoGrow<T extends HTMLTextAreaElement>(value: string): React.RefObject<T> {\n const ref = React.useRef<T>(null);\n const lastWidth = React.useRef<number | null>(null);\n\n // Measuring means collapsing to \"auto\" first, which shrinks the page enough that\n // the browser clamps the scroll position, and growing back does not undo that.\n // Both steps run before paint, so restoring the scroll here is not visible.\n const fit = React.useCallback(() => {\n const el = ref.current;\n if (!el) return;\n const scroller = document.scrollingElement ?? document.documentElement;\n const scrollTop = scroller.scrollTop;\n el.style.height = 'auto';\n if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;\n if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;\n }, []);\n\n useIsomorphicLayoutEffect(fit, [fit, value]);\n\n React.useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(([entry]) => {\n const width = entry.contentRect.width;\n if (width === lastWidth.current) return;\n lastWidth.current = width;\n fit();\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [fit]);\n\n // React 18's types refuse `RefObject<T | null>` on a `ref` prop, and `RefObject<T>` fits both majors.\n return ref as React.RefObject<T>;\n}\n","import * as React from 'react';\n\nexport const useIsomorphicLayoutEffect = typeof document === 'undefined' ? React.useEffect : React.useLayoutEffect;\n"],"mappings":"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","names":["text","at","INDENT","at","React","React"]}
|
package/dist/headless.d.cts
CHANGED
|
@@ -76,6 +76,6 @@ declare function useMarkdownKeymap({ commit, onOpenPalette, }: {
|
|
|
76
76
|
* wider, because a re-wrap changes the line count without changing the value.
|
|
77
77
|
* Only a width change refits: reacting to the height this hook just set would
|
|
78
78
|
* feed the observer its own output. */
|
|
79
|
-
declare function useAutoGrow<T extends HTMLTextAreaElement>(value: string): React.RefObject<T
|
|
79
|
+
declare function useAutoGrow<T extends HTMLTextAreaElement>(value: string): React.RefObject<T>;
|
|
80
80
|
|
|
81
81
|
export { type CaretRect, type Edit, type InsertableCommand, type LinePrefix, type WrapKind, applyEdit, caretRect, continueList, formatShortcut, indentList, insertCommand, lineAt, linkPaste, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useMarkdownKeymap, wrapSelection };
|
package/dist/headless.d.ts
CHANGED
|
@@ -76,6 +76,6 @@ declare function useMarkdownKeymap({ commit, onOpenPalette, }: {
|
|
|
76
76
|
* wider, because a re-wrap changes the line count without changing the value.
|
|
77
77
|
* Only a width change refits: reacting to the height this hook just set would
|
|
78
78
|
* feed the observer its own output. */
|
|
79
|
-
declare function useAutoGrow<T extends HTMLTextAreaElement>(value: string): React.RefObject<T
|
|
79
|
+
declare function useAutoGrow<T extends HTMLTextAreaElement>(value: string): React.RefObject<T>;
|
|
80
80
|
|
|
81
81
|
export { type CaretRect, type Edit, type InsertableCommand, type LinePrefix, type WrapKind, applyEdit, caretRect, continueList, formatShortcut, indentList, insertCommand, lineAt, linkPaste, shouldOpenPalette, toggleLinePrefix, useAutoGrow, useMarkdownKeymap, wrapSelection };
|
package/dist/headless.js
CHANGED
|
@@ -280,15 +280,18 @@ function shouldOpenPalette(value, selectionStart, selectionEnd) {
|
|
|
280
280
|
return previous === "\n" || previous === " " || previous === " ";
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
// src/markdown/platform.ts
|
|
284
|
+
function isMac() {
|
|
285
|
+
if (typeof navigator === "undefined") return false;
|
|
286
|
+
const { userAgentData, platform } = navigator;
|
|
287
|
+
return /mac|iphone|ipad/i.test(userAgentData?.platform || platform || "");
|
|
288
|
+
}
|
|
289
|
+
|
|
283
290
|
// src/markdown/shortcut-label.ts
|
|
284
291
|
var MAC_LABEL = { mod: "\u2318", alt: "\u2325", shift: "\u21E7" };
|
|
285
292
|
var OTHER_LABEL = { mod: "Ctrl", alt: "Alt", shift: "Shift" };
|
|
286
|
-
function onMac() {
|
|
287
|
-
if (typeof navigator === "undefined") return false;
|
|
288
|
-
return /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
289
|
-
}
|
|
290
293
|
function formatShortcut(hint) {
|
|
291
|
-
const mac =
|
|
294
|
+
const mac = isMac();
|
|
292
295
|
const labels = mac ? MAC_LABEL : OTHER_LABEL;
|
|
293
296
|
const parts = hint.split("+").map((part) => labels[part] ?? part.toUpperCase());
|
|
294
297
|
return parts.join(mac ? "" : "+");
|
|
@@ -352,9 +355,10 @@ function useMarkdownKeymap({
|
|
|
352
355
|
}) {
|
|
353
356
|
const onKeyDown = React.useCallback(
|
|
354
357
|
(event) => {
|
|
358
|
+
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
|
|
355
359
|
const el = event.currentTarget;
|
|
356
360
|
const { value, selectionStart, selectionEnd } = el;
|
|
357
|
-
const mod = event.metaKey
|
|
361
|
+
const mod = isMac() ? event.metaKey : event.ctrlKey;
|
|
358
362
|
const run = (edit) => {
|
|
359
363
|
if (!edit) return;
|
|
360
364
|
event.preventDefault();
|
|
@@ -414,11 +418,17 @@ function useMarkdownKeymap({
|
|
|
414
418
|
}
|
|
415
419
|
|
|
416
420
|
// src/hooks/useAutoGrow.ts
|
|
421
|
+
import * as React3 from "react";
|
|
422
|
+
|
|
423
|
+
// src/hooks/use-isomorphic-layout-effect.ts
|
|
417
424
|
import * as React2 from "react";
|
|
425
|
+
var useIsomorphicLayoutEffect = typeof document === "undefined" ? React2.useEffect : React2.useLayoutEffect;
|
|
426
|
+
|
|
427
|
+
// src/hooks/useAutoGrow.ts
|
|
418
428
|
function useAutoGrow(value) {
|
|
419
|
-
const ref =
|
|
420
|
-
const lastWidth =
|
|
421
|
-
const fit =
|
|
429
|
+
const ref = React3.useRef(null);
|
|
430
|
+
const lastWidth = React3.useRef(null);
|
|
431
|
+
const fit = React3.useCallback(() => {
|
|
422
432
|
const el = ref.current;
|
|
423
433
|
if (!el) return;
|
|
424
434
|
const scroller = document.scrollingElement ?? document.documentElement;
|
|
@@ -427,8 +437,8 @@ function useAutoGrow(value) {
|
|
|
427
437
|
if (el.scrollHeight > 0) el.style.height = `${el.scrollHeight}px`;
|
|
428
438
|
if (scroller.scrollTop !== scrollTop) scroller.scrollTop = scrollTop;
|
|
429
439
|
}, []);
|
|
430
|
-
|
|
431
|
-
|
|
440
|
+
useIsomorphicLayoutEffect(fit, [fit, value]);
|
|
441
|
+
React3.useEffect(() => {
|
|
432
442
|
const el = ref.current;
|
|
433
443
|
if (!el || typeof ResizeObserver === "undefined") return;
|
|
434
444
|
const observer = new ResizeObserver(([entry]) => {
|