@lalternative/editor 0.8.5 → 0.8.7

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/dist/index.d.ts CHANGED
@@ -23,6 +23,10 @@ interface PlaceToolbarInput {
23
23
  /** iOS renders its own callout above a selection, which covers a toolbar
24
24
  * placed there, so the caller asks for below on that platform. */
25
25
  preferBelow?: boolean;
26
+ /** Height of the system's own selection callout — Cut/Copy/Paste — which is
27
+ * drawn over the page and cannot be measured from it. Clearing it is what
28
+ * keeps the two from overlapping. Zero where there is no such callout. */
29
+ calloutHeight?: number;
26
30
  }
27
31
  interface ToolbarPlacement {
28
32
  left: number;
@@ -36,7 +40,7 @@ interface ToolbarPlacement {
36
40
  * ways — off-screen at the edges, hidden under a system callout — and the part
37
41
  * a test can pin down without a browser.
38
42
  */
39
- declare function placeToolbar({ selection, toolbar, viewport, preferBelow, }: PlaceToolbarInput): ToolbarPlacement;
43
+ declare function placeToolbar({ selection, toolbar, viewport, preferBelow, calloutHeight, }: PlaceToolbarInput): ToolbarPlacement;
40
44
 
41
45
  interface ToolbarAction {
42
46
  id: string;
@@ -73,11 +77,6 @@ interface SelectionInfo {
73
77
  *
74
78
  * Returns null whenever there is nothing to act on, so the caller renders the
75
79
  * toolbar only while it has a target.
76
- *
77
- * On a touch device the keyboard is dismissed as soon as a range is selected.
78
- * There is nothing to type while choosing a format, and the strip above the
79
- * keyboard belongs to the system — a toolbar left to fight for it is drawn
80
- * underneath. Out of the way, the toolbar sits by the text it acts on.
81
80
  */
82
81
  declare function useSelectionToolbar(editor: Editor | null): SelectionInfo | null;
83
82
 
package/dist/index.js CHANGED
@@ -7,16 +7,27 @@ import {
7
7
 
8
8
  // src/placement.ts
9
9
  var GAP = 8;
10
+ var IOS_CALLOUT_HEIGHT = 44;
10
11
  function placeToolbar({
11
12
  selection,
12
13
  toolbar,
13
14
  viewport,
14
- preferBelow = false
15
+ preferBelow = false,
16
+ calloutHeight = 0
15
17
  }) {
18
+ const clearance = GAP + calloutHeight;
16
19
  const fitsAbove = selection.top - GAP - toolbar.height >= 0;
17
- const fitsBelow = selection.bottom + GAP + toolbar.height <= viewport.height;
20
+ const fitsBelow = selection.bottom + clearance + toolbar.height <= viewport.height;
18
21
  const below = preferBelow ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
19
- const top = below ? selection.bottom + GAP : Math.max(0, selection.top - GAP - toolbar.height);
22
+ const top = below ? Math.min(
23
+ selection.bottom + clearance,
24
+ // Never past the bottom edge: a selection low on the page would put
25
+ // the toolbar off-screen once the callout's height is added.
26
+ Math.max(selection.top, viewport.height - toolbar.height - GAP)
27
+ ) : Math.max(
28
+ Math.min(GAP, selection.top),
29
+ selection.top - GAP - toolbar.height
30
+ );
20
31
  const centred = selection.x - toolbar.width / 2;
21
32
  const left = Math.max(0, Math.min(centred, viewport.width - toolbar.width));
22
33
  return { left, top, below };
@@ -122,7 +133,10 @@ function SelectionToolbar({
122
133
  selection,
123
134
  toolbar: size,
124
135
  viewport: { width: viewport.width, height: viewport.height },
125
- preferBelow
136
+ preferBelow,
137
+ // preferBelow is set by the same platform check that puts the callout
138
+ // there, so it is what says whether there is one to clear.
139
+ calloutHeight: preferBelow ? IOS_CALLOUT_HEIGHT : 0
126
140
  });
127
141
  return createPortal(
128
142
  /* @__PURE__ */ jsxs(
@@ -201,10 +215,6 @@ function SelectionToolbar({
201
215
 
202
216
  // src/useSelectionToolbar.ts
203
217
  import { useEffect as useEffect3, useState as useState3 } from "react";
204
- function keyboardIsInTheWay() {
205
- if (typeof window === "undefined" || !window.matchMedia) return false;
206
- return window.matchMedia("(hover: none) and (pointer: coarse)").matches;
207
- }
208
218
  function useSelectionToolbar(editor) {
209
219
  const [selection, setSelection] = useState3(null);
210
220
  useEffect3(() => {
@@ -212,7 +222,7 @@ function useSelectionToolbar(editor) {
212
222
  const read2 = () => {
213
223
  const { state } = editor;
214
224
  const { from, to, empty } = state.selection;
215
- if (empty) {
225
+ if (empty || !editor.isFocused) {
216
226
  setSelection(null);
217
227
  return;
218
228
  }
@@ -221,9 +231,6 @@ function useSelectionToolbar(editor) {
221
231
  setSelection(null);
222
232
  return;
223
233
  }
224
- if (keyboardIsInTheWay() && editor.isFocused) {
225
- editor.view.dom.blur();
226
- }
227
234
  const start = editor.view.coordsAtPos(from);
228
235
  const end = editor.view.coordsAtPos(to);
229
236
  setSelection({
@@ -260,41 +267,33 @@ var defaultFormatLabels = {
260
267
  function blockFormats(editor, labels = defaultFormatLabels) {
261
268
  if (!editor) return [];
262
269
  const mod = isApple() ? "\u2318" : "Ctrl";
263
- const act = (run) => {
264
- const focused = editor.isFocused;
265
- if (focused) return run();
266
- editor.commands.focus(void 0, { scrollIntoView: false });
267
- const applied = run();
268
- editor.view.dom.blur();
269
- return applied;
270
- };
271
270
  return [
272
271
  {
273
272
  id: "paragraph",
274
273
  label: labels.paragraph,
275
274
  isActive: editor.isActive("paragraph"),
276
- onSelect: () => act(() => editor.chain().setParagraph().run())
275
+ onSelect: () => editor.chain().focus().setParagraph().run()
277
276
  },
278
277
  ...[1, 2, 3].map((level) => ({
279
278
  id: `heading${level}`,
280
279
  label: labels[`heading${level}`],
281
280
  shortcut: `${mod}+Alt+${level}`,
282
281
  isActive: editor.isActive("heading", { level }),
283
- onSelect: () => act(() => editor.chain().toggleHeading({ level }).run())
282
+ onSelect: () => editor.chain().focus().toggleHeading({ level }).run()
284
283
  })),
285
284
  {
286
285
  id: "orderedList",
287
286
  label: labels.orderedList,
288
287
  shortcut: `${mod}+Shift+7`,
289
288
  isActive: editor.isActive("orderedList"),
290
- onSelect: () => act(() => editor.chain().toggleOrderedList().run())
289
+ onSelect: () => editor.chain().focus().toggleOrderedList().run()
291
290
  },
292
291
  {
293
292
  id: "bulletList",
294
293
  label: labels.bulletList,
295
294
  shortcut: `${mod}+Shift+8`,
296
295
  isActive: editor.isActive("bulletList"),
297
- onSelect: () => act(() => editor.chain().toggleBulletList().run())
296
+ onSelect: () => editor.chain().focus().toggleBulletList().run()
298
297
  }
299
298
  ];
300
299
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/placement.ts","../src/SelectionToolbar.tsx","../src/useViewport.ts","../src/viewport.ts","../src/useSelectionToolbar.ts","../src/formats.ts","../src/EditorScreen.tsx","../src/slash.ts","../src/SlashMenu.tsx","../src/SlashCommands.ts","../src/InlineSuggestions.ts","../src/passage.ts","../src/markdown.ts"],"sourcesContent":["export interface SelectionRect {\n /** Horizontal centre of the selection. */\n x: number;\n top: number;\n bottom: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface PlaceToolbarInput {\n selection: SelectionRect;\n toolbar: Size;\n viewport: Size;\n /** iOS renders its own callout above a selection, which covers a toolbar\n * placed there, so the caller asks for below on that platform. */\n preferBelow?: boolean;\n}\n\nexport interface ToolbarPlacement {\n left: number;\n top: number;\n below: boolean;\n}\n\nconst GAP = 8;\n\n/**\n * placeToolbar positions a floating toolbar against a selection.\n *\n * Kept apart from the component because it is the part that is wrong in subtle\n * ways — off-screen at the edges, hidden under a system callout — and the part\n * a test can pin down without a browser.\n */\nexport function placeToolbar({\n selection,\n toolbar,\n viewport,\n preferBelow = false,\n}: PlaceToolbarInput): ToolbarPlacement {\n const fitsAbove = selection.top - GAP - toolbar.height >= 0;\n const fitsBelow = selection.bottom + GAP + toolbar.height <= viewport.height;\n\n const below = preferBelow ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n\n const top = below\n ? selection.bottom + GAP\n : Math.max(0, selection.top - GAP - toolbar.height);\n\n const centred = selection.x - toolbar.width / 2;\n const left = Math.max(0, Math.min(centred, viewport.width - toolbar.width));\n\n return { left, top, below };\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { placeToolbar, type SelectionRect } from \"./placement\";\nimport { useViewport } from \"./useViewport\";\n\nexport interface ToolbarAction {\n id: string;\n label: string;\n icon?: React.ReactNode;\n onSelect: (selectedText: string) => void;\n}\n\nexport interface BlockFormat {\n id: string;\n label: string;\n shortcut?: string;\n isActive?: boolean;\n onSelect: () => void;\n}\n\nexport interface SelectionToolbarProps {\n selectedText: string;\n selection: SelectionRect;\n actions?: ToolbarAction[];\n /** Heading levels and list kinds, rendered behind a single trigger. */\n formats?: BlockFormat[];\n formatsLabel?: string;\n /** iOS draws its own callout above a selection; the host detects it. */\n preferBelow?: boolean;\n container?: HTMLElement | null;\n}\n\nconst FALLBACK = { width: 320, height: 40 };\n\n// Whether the width used to place the bar is the one it actually has. The\n// fallback is a guess, and placing on a guess puts the bar off-centre by\n// however far the guess is wrong — which is invisible while the bar happens to\n// be 320px wide and obvious as soon as a host adds another action to it.\ntype Measured = { width: number; height: number; real: boolean };\n\nexport function SelectionToolbar({\n selectedText,\n selection,\n actions = [],\n formats = [],\n formatsLabel = \"Format\",\n preferBelow = false,\n container,\n}: SelectionToolbarProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState<Measured>({ ...FALLBACK, real: false });\n const [formatsOpen, setFormatsOpen] = useState(false);\n const viewport = useViewport();\n\n // Measured after paint rather than assumed: the toolbar's width depends on\n // labels the host supplies, and guessing it puts the toolbar off-centre.\n //\n // Observed rather than recomputed from a dependency list: the width follows\n // the rendered labels, and a list naming the things that happen to change it\n // today misses the next one — a longer label, a font that loads late.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n const measure = () => {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0)\n setSize((prev) =>\n prev.real && prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height, real: true },\n );\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (!formatsOpen) return;\n const close = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setFormatsOpen(false);\n };\n document.addEventListener(\"mousedown\", close);\n return () => document.removeEventListener(\"mousedown\", close);\n }, [formatsOpen]);\n\n if (typeof document === \"undefined\") return null;\n\n const place = placeToolbar({\n selection,\n toolbar: size,\n viewport: { width: viewport.width, height: viewport.height },\n preferBelow,\n });\n\n return createPortal(\n <div\n ref={ref}\n role=\"toolbar\"\n className=\"lalt-toolbar\"\n // Custom properties rather than left/top directly: under the mobile\n // breakpoint the stylesheet docks the toolbar to the bottom of the\n // screen and ignores these, which inline left/top would override.\n //\n // The inset is what the docked bar sits on top of. `bottom: 0` means the\n // bottom of the layout viewport, which the on-screen keyboard covers\n // without shrinking — so the bar has to be lifted by the strip the\n // keyboard occupies to stay in sight.\n style={\n {\n \"--lalt-toolbar-left\": `${place.left}px`,\n \"--lalt-toolbar-top\": `${place.top}px`,\n \"--lalt-keyboard-inset\": `${viewport.bottomInset}px`,\n // Hidden for the one frame between mounting and being measured:\n // that first paint places the bar on the fallback width, and showing\n // it means showing the bar jump once the real width arrives.\n // Hidden rather than unmounted — it has to be in the document to\n // have a width at all.\n visibility: size.real ? undefined : \"hidden\",\n } as React.CSSProperties\n }\n >\n {actions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n className=\"lalt-toolbar__button\"\n // The label is what the narrow layout hides, so it has to stay\n // reachable to anyone not reading the icon.\n aria-label={action.icon ? action.label : undefined}\n data-has-icon={action.icon ? \"\" : undefined}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => action.onSelect(selectedText)}\n >\n {action.icon}\n <span className=\"lalt-toolbar__label\">{action.label}</span>\n </button>\n ))}\n\n {formats.length > 0 && (\n <div className=\"lalt-toolbar__formats\">\n <button\n type=\"button\"\n className=\"lalt-toolbar__button\"\n aria-haspopup=\"menu\"\n aria-expanded={formatsOpen}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => setFormatsOpen((open) => !open)}\n >\n {formatsLabel}\n </button>\n {formatsOpen && (\n <div role=\"menu\" className=\"lalt-toolbar__menu\">\n {formats.map((format) => (\n <button\n key={format.id}\n type=\"button\"\n role=\"menuitem\"\n className=\"lalt-toolbar__item\"\n aria-current={format.isActive}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n format.onSelect();\n setFormatsOpen(false);\n }}\n >\n <span>{format.label}</span>\n {format.shortcut && (\n <kbd className=\"lalt-toolbar__shortcut\">{format.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>,\n container ?? document.body,\n );\n}\n","import { useEffect, useState } from \"react\";\n\nimport { readViewport, type ViewportMetrics } from \"./viewport\";\n\nconst SERVER: ViewportMetrics = { width: 0, height: 0, offsetTop: 0, bottomInset: 0 };\n\nfunction read(): ViewportMetrics {\n if (typeof window === \"undefined\") return SERVER;\n return readViewport({\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n visual: window.visualViewport,\n });\n}\n\n/**\n * useViewport tracks the area a fixed element can occupy, keyboard included.\n *\n * The visual viewport's own events are the only ones a raised keyboard fires:\n * `window.resize` does not run on iOS when the keyboard opens, so a component\n * listening to it alone keeps the measurements it took before the keys\n * appeared.\n */\nexport function useViewport(): ViewportMetrics {\n const [metrics, setMetrics] = useState<ViewportMetrics>(read);\n\n useEffect(() => {\n const update = () => setMetrics(read());\n update();\n\n const visual = window.visualViewport;\n visual?.addEventListener(\"resize\", update);\n // iOS scrolls the visual viewport rather than the document to keep the\n // caret above the keyboard, and reports it here and nowhere else.\n visual?.addEventListener(\"scroll\", update);\n window.addEventListener(\"resize\", update);\n window.addEventListener(\"orientationchange\", update);\n\n return () => {\n visual?.removeEventListener(\"resize\", update);\n visual?.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n window.removeEventListener(\"orientationchange\", update);\n };\n }, []);\n\n return metrics;\n}\n","export interface VisualViewportLike {\n width: number;\n height: number;\n offsetTop: number;\n}\n\nexport interface ViewportMetrics {\n width: number;\n height: number;\n offsetTop: number;\n /** How much of the layout viewport's bottom is covered, the on-screen\n * keyboard included. Zero on a desktop browser and wherever the API is\n * missing. */\n bottomInset: number;\n}\n\nexport interface ReadViewportInput {\n innerWidth: number;\n innerHeight: number;\n visual?: VisualViewportLike | null;\n}\n\n/**\n * readViewport reports the area a fixed element can actually occupy.\n *\n * The distinction the on-screen keyboard turns on: `innerHeight` measures the\n * layout viewport, which the keyboard covers without shrinking, so an element\n * pinned to its bottom edge ends up underneath the keys. Only `visualViewport`\n * narrows, and the difference is the inset a docked toolbar has to clear.\n */\nexport function readViewport({\n innerWidth,\n innerHeight,\n visual,\n}: ReadViewportInput): ViewportMetrics {\n if (!visual) {\n return { width: innerWidth, height: innerHeight, offsetTop: 0, bottomInset: 0 };\n }\n\n // offsetTop is what iOS scrolls the visual viewport by; without it the\n // covered strip reads as smaller than it is once the page is scrolled under\n // a raised keyboard. Never negative: a viewport reported as taller than the\n // layout one — Android during a rotation, a browser mid-resize — would\n // otherwise push the toolbar down off the screen.\n const covered = innerHeight - visual.height - visual.offsetTop;\n\n return {\n width: visual.width,\n height: visual.height,\n offsetTop: visual.offsetTop,\n bottomInset: Math.max(0, Math.round(covered)),\n };\n}\n","import { useEffect, useState } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\n\nimport type { SelectionRect } from \"./placement\";\n\nexport interface SelectionInfo {\n text: string;\n rect: SelectionRect;\n}\n\n// Whether the on-screen keyboard is what stands between a selection and the\n// toolbar acting on it. Touch without a fine pointer: a laptop with a\n// touchscreen has a real keyboard and must keep its focus.\nfunction keyboardIsInTheWay(): boolean {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(\"(hover: none) and (pointer: coarse)\").matches;\n}\n\n/**\n * useSelectionToolbar reports the current non-empty selection of an editor.\n *\n * Returns null whenever there is nothing to act on, so the caller renders the\n * toolbar only while it has a target.\n *\n * On a touch device the keyboard is dismissed as soon as a range is selected.\n * There is nothing to type while choosing a format, and the strip above the\n * keyboard belongs to the system — a toolbar left to fight for it is drawn\n * underneath. Out of the way, the toolbar sits by the text it acts on.\n */\nexport function useSelectionToolbar(editor: Editor | null): SelectionInfo | null {\n const [selection, setSelection] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n if (!editor) return;\n\n const read = () => {\n const { state } = editor;\n const { from, to, empty } = state.selection;\n // Focus is not required while a range is held. On a phone the toolbar\n // only becomes usable once the keyboard is out of the way, and putting\n // it away blurs the editor — reading focus here would take the toolbar\n // down with the keyboard, which is the thing the author reached for.\n if (empty) {\n setSelection(null);\n return;\n }\n\n const text = state.doc.textBetween(from, to, \" \").trim();\n if (text === \"\") {\n setSelection(null);\n return;\n }\n\n // Blurred once per selection, not on every update: re-blurring while\n // the author drags a handle would fight iOS for the selection.\n if (keyboardIsInTheWay() && editor.isFocused) {\n (editor.view.dom as HTMLElement).blur();\n }\n\n const start = editor.view.coordsAtPos(from);\n const end = editor.view.coordsAtPos(to);\n setSelection({\n text,\n rect: {\n x: (start.left + end.right) / 2,\n top: Math.min(start.top, end.top),\n bottom: Math.max(start.bottom, end.bottom),\n },\n });\n };\n\n read();\n editor.on(\"selectionUpdate\", read);\n editor.on(\"blur\", read);\n editor.on(\"focus\", read);\n return () => {\n editor.off(\"selectionUpdate\", read);\n editor.off(\"blur\", read);\n editor.off(\"focus\", read);\n };\n }, [editor]);\n\n return selection;\n}\n","import type { Editor } from \"@tiptap/react\";\n\n// Type-only: these commands are contributed by StarterKit's extensions, and\n// without the reference TypeScript does not know they exist on a chain. No\n// runtime import, so the host stays in charge of which extensions it mounts —\n// blockFormats simply returns entries whose commands no-op if one is missing.\nimport type {} from \"@tiptap/starter-kit\";\n\nimport type { BlockFormat } from \"./SelectionToolbar\";\n\nexport interface FormatLabels {\n paragraph: string;\n heading1: string;\n heading2: string;\n heading3: string;\n orderedList: string;\n bulletList: string;\n}\n\nexport const defaultFormatLabels: FormatLabels = {\n paragraph: \"Texte\",\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n heading3: \"Titre 3\",\n orderedList: \"Liste numérotée\",\n bulletList: \"Liste à puces\",\n};\n\n/**\n * blockFormats is the set a writer reaches for on a selected passage.\n *\n * Labels are passed in rather than translated here: the package carries no\n * i18n runtime, so the host names things in whatever language it speaks.\n */\nexport function blockFormats(\n editor: Editor | null,\n labels: FormatLabels = defaultFormatLabels,\n): BlockFormat[] {\n if (!editor) return [];\n\n // Shortcuts are the ones TipTap's own extensions bind, read from their\n // source rather than invented: heading binds Mod-Alt-<level>, the list\n // extensions bind Mod-Shift-7/8, and setParagraph binds nothing at all.\n // A label promising a shortcut that does nothing is worse than no label.\n const mod = isApple() ? \"⌘\" : \"Ctrl\";\n\n // focus() is what makes a command apply to the selection rather than to\n // nothing — but on a phone the toolbar is only reachable because the\n // keyboard was dismissed, and focusing calls it straight back. The\n // scrollIntoView: false variant restores the range without the editor\n // asking for the keyboard again.\n const act = (run: () => boolean) => {\n const focused = editor.isFocused;\n if (focused) return run();\n editor.commands.focus(undefined, { scrollIntoView: false });\n const applied = run();\n (editor.view.dom as HTMLElement).blur();\n return applied;\n };\n\n return [\n {\n id: \"paragraph\",\n label: labels.paragraph,\n isActive: editor.isActive(\"paragraph\"),\n onSelect: () => act(() => editor.chain().setParagraph().run()),\n },\n ...([1, 2, 3] as const).map((level) => ({\n id: `heading${level}`,\n label: labels[`heading${level}` as keyof FormatLabels],\n shortcut: `${mod}+Alt+${level}`,\n isActive: editor.isActive(\"heading\", { level }),\n onSelect: () => act(() => editor.chain().toggleHeading({ level }).run()),\n })),\n {\n id: \"orderedList\",\n label: labels.orderedList,\n shortcut: `${mod}+Shift+7`,\n isActive: editor.isActive(\"orderedList\"),\n onSelect: () => act(() => editor.chain().toggleOrderedList().run()),\n },\n {\n id: \"bulletList\",\n label: labels.bulletList,\n shortcut: `${mod}+Shift+8`,\n isActive: editor.isActive(\"bulletList\"),\n onSelect: () => act(() => editor.chain().toggleBulletList().run()),\n },\n ];\n}\n\nfunction isApple(): boolean {\n if (typeof navigator === \"undefined\") return false;\n return /Mac|iPad|iPhone|iPod/.test(navigator.platform || navigator.userAgent);\n}\n","import { useViewport } from \"./useViewport\";\n\nexport type SaveState = \"idle\" | \"saving\" | \"saved\" | \"dirty\";\n\nexport interface EditorScreenProps {\n /** The writing surface. Mounted by the host, which owns the TipTap instance\n * and whichever extensions it needs. */\n children: React.ReactNode;\n title?: string;\n onTitleChange?: (title: string) => void;\n titlePlaceholder?: string;\n titleLabel?: string;\n saveState?: SaveState;\n saveLabels?: SaveLabels;\n /** Drawn at the far left of the bar; a back control belongs here. */\n lead?: React.ReactNode;\n /** Drawn at the far right. Keep it to what a thumb can reach on a phone —\n * one primary control and an overflow, not a row of five. */\n actions?: React.ReactNode;\n /** Sits between the save dot and the actions: a character count, a badge. */\n status?: React.ReactNode;\n /** Between the title and the writing surface — a dictation control, a hint. */\n aside?: React.ReactNode;\n /** Below the writing surface, inside the scrolling column. */\n footer?: React.ReactNode;\n /** Above the writing surface and below the bar, pinned while the page\n * scrolls: tabs over variants of one document belong here. */\n subnav?: React.ReactNode;\n /** Fills its container instead of the viewport, for a host whose shell\n * already owns the page height. */\n fill?: boolean;\n className?: string;\n}\n\nexport interface SaveLabels {\n saving: string;\n saved: string;\n dirty: string;\n}\n\nexport const defaultSaveLabels: SaveLabels = {\n saving: \"Enregistrement en cours\",\n saved: \"Enregistré\",\n dirty: \"Modifications non enregistrées\",\n};\n\n/**\n * EditorScreen is the page a writing surface sits in: a command bar, a title,\n * one column of prose, and slots for whatever the host puts around it.\n *\n * It renders and measures; it never fetches, saves or routes. `saveState` is\n * displayed, not computed — the host owns the debounce and the request, so the\n * same screen serves an autosaving draft and a form with an explicit button.\n *\n * Commands sit at the top rather than in a bar at the bottom. Below the fold\n * they are gone the moment the document runs past one screen, and pinned to\n * the bottom they eat a sixth of what the on-screen keyboard leaves.\n */\nexport function EditorScreen({\n children,\n title,\n onTitleChange,\n titlePlaceholder = \"Titre\",\n titleLabel = \"Titre\",\n saveState,\n saveLabels = defaultSaveLabels,\n lead,\n actions,\n status,\n aside,\n footer,\n subnav,\n fill = false,\n className,\n}: EditorScreenProps) {\n const { bottomInset } = useViewport();\n const withTitle = onTitleChange !== undefined;\n\n return (\n <div\n className={[\n \"lalt-screen\",\n fill ? \"lalt-screen--fill\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The keyboard covers the layout viewport without shrinking it, so the\n // column's last lines sit under the keys unless the strip is padded out.\n style={{ \"--lalt-keyboard\": `${bottomInset}px` } as React.CSSProperties}\n >\n <header className=\"lalt-screen__bar\">\n {lead}\n {saveState && (\n <SaveDot state={saveState} labels={saveLabels} />\n )}\n {status && <div className=\"lalt-screen__status\">{status}</div>}\n {actions && <div className=\"lalt-screen__actions\">{actions}</div>}\n </header>\n\n {subnav && <div className=\"lalt-screen__subnav\">{subnav}</div>}\n\n <div className=\"lalt-screen__scroll\">\n <div className=\"lalt-screen__column\">\n {withTitle && (\n <input\n className=\"lalt-screen__title\"\n value={title ?? \"\"}\n onChange={(event) => onTitleChange(event.target.value)}\n placeholder={titlePlaceholder}\n aria-label={titleLabel}\n />\n )}\n {aside && <div className=\"lalt-screen__aside\">{aside}</div>}\n {children}\n {footer && <div className=\"lalt-screen__footer\">{footer}</div>}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * A dot rather than a word: the state is worth a glance, never a read, and a\n * label that appears and vanishes leaves the author wondering what it meant\n * once it is gone. Screen readers get the sentence instead.\n */\nfunction SaveDot({ state, labels }: { state: SaveState; labels: SaveLabels }) {\n if (state === \"idle\") return <span className=\"lalt-screen__save\" />;\n\n const label =\n state === \"saving\" ? labels.saving : state === \"dirty\" ? labels.dirty : labels.saved;\n\n return (\n <span className=\"lalt-screen__save\" role=\"status\" aria-live=\"polite\">\n <span className={`lalt-screen__dot lalt-screen__dot--${state}`} aria-hidden />\n <span className=\"lalt-screen__sr\">{label}</span>\n </span>\n );\n}\n","import type { Editor } from \"@tiptap/react\";\nimport type { Range } from \"@tiptap/core\";\n\n// Type-only, as in formats.ts: StarterKit contributes these commands to a\n// chain, and without the reference TypeScript does not know they exist. The\n// host stays in charge of which extensions it actually mounts.\nimport type {} from \"@tiptap/starter-kit\";\n\n/**\n * SlashItem is one entry of the `/` menu.\n *\n * icon is a node rather than a component type: the package carries no icon\n * library, so the host renders whichever one it already ships.\n */\nexport interface SlashItem {\n id: string;\n title: string;\n description?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n command: (args: { editor: Editor; range: Range }) => void;\n}\n\nexport interface SlashLabels {\n heading1: string;\n heading2: string;\n bulletList: string;\n orderedList: string;\n blockquote: string;\n codeBlock: string;\n horizontalRule: string;\n}\n\nexport const defaultSlashLabels: SlashLabels = {\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n bulletList: \"Liste à puces\",\n orderedList: \"Liste numérotée\",\n blockquote: \"Citation\",\n codeBlock: \"Bloc de code\",\n horizontalRule: \"Séparateur\",\n};\n\nexport interface SlashDescriptions {\n heading1?: string;\n heading2?: string;\n bulletList?: string;\n orderedList?: string;\n blockquote?: string;\n codeBlock?: string;\n horizontalRule?: string;\n}\n\nexport interface DefaultSlashItemsOptions {\n labels?: SlashLabels;\n descriptions?: SlashDescriptions;\n icons?: Partial<Record<DefaultSlashId, React.ReactNode>>;\n}\n\nexport type DefaultSlashId =\n | \"heading1\"\n | \"heading2\"\n | \"bulletList\"\n | \"orderedList\"\n | \"blockquote\"\n | \"codeBlock\"\n | \"horizontalRule\";\n\n/**\n * defaultSlashItems is the block set every writing surface shares.\n *\n * Returned as data rather than rendered behind flags: a host adds, removes or\n * reorders entries by composing the array, so a surface that needs one more\n * command does not push another boolean into this signature.\n */\nexport function defaultSlashItems({\n labels = defaultSlashLabels,\n descriptions = {},\n icons = {},\n}: DefaultSlashItemsOptions = {}): SlashItem[] {\n const item = (\n id: DefaultSlashId,\n shortcut: string,\n run: (editor: Editor) => void,\n ): SlashItem => ({\n id,\n title: labels[id],\n description: descriptions[id],\n shortcut,\n icon: icons[id],\n command: ({ editor, range }) => {\n editor.chain().focus().deleteRange(range).run();\n run(editor);\n },\n });\n\n return [\n item(\"heading1\", \"#\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 1 }).run(),\n ),\n item(\"heading2\", \"##\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 2 }).run(),\n ),\n item(\"bulletList\", \"-\", (editor) =>\n editor.chain().focus().toggleBulletList().run(),\n ),\n item(\"orderedList\", \"1.\", (editor) =>\n editor.chain().focus().toggleOrderedList().run(),\n ),\n item(\"blockquote\", \">\", (editor) =>\n editor.chain().focus().toggleBlockquote().run(),\n ),\n item(\"codeBlock\", \"```\", (editor) =>\n editor.chain().focus().toggleCodeBlock().run(),\n ),\n item(\"horizontalRule\", \"---\", (editor) =>\n editor.chain().focus().setHorizontalRule().run(),\n ),\n ];\n}\n\n/** filterSlashItems matches the query against titles the way the menu does. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase();\n if (q === \"\") return items;\n return items.filter((item) => item.title.toLowerCase().includes(q));\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { SlashItem } from \"./slash\";\n\nexport interface SlashMenuHandle {\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n}\n\nexport interface SlashMenuProps {\n items: SlashItem[];\n command: (item: SlashItem) => void;\n emptyLabel?: string;\n}\n\nexport const SlashMenu = forwardRef<SlashMenuHandle, SlashMenuProps>(\n function SlashMenu({ items, command, emptyLabel = \"Aucune commande\" }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // The suggestion plugin rebuilds these props on every keystroke, so the\n // imperative handle reads refs rather than a captured render closure —\n // otherwise Enter runs the command the menu showed one keystroke ago.\n const itemsRef = useRef(items);\n itemsRef.current = items;\n const commandRef = useRef(command);\n commandRef.current = command;\n const selectedRef = useRef(selectedIndex);\n selectedRef.current = selectedIndex;\n\n const ids = items.map((item) => item.id).join(\" \");\n useEffect(() => {\n setSelectedIndex(0);\n selectedRef.current = 0;\n }, [ids]);\n\n const selectItem = useCallback((index: number) => {\n const item = itemsRef.current[index];\n if (item) commandRef.current(item);\n }, []);\n\n const move = useCallback((delta: number) => {\n const count = itemsRef.current.length;\n if (count === 0) return;\n const next = (selectedRef.current + delta + count) % count;\n selectedRef.current = next;\n setSelectedIndex(next);\n }, []);\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n if (event.key === \"ArrowUp\") {\n move(-1);\n return true;\n }\n if (event.key === \"ArrowDown\") {\n move(1);\n return true;\n }\n if (event.key === \"Enter\" || event.key === \"Tab\") {\n if (itemsRef.current.length === 0) return false;\n selectItem(selectedRef.current);\n return true;\n }\n return false;\n },\n }));\n\n if (items.length === 0) {\n return <div className=\"lalt-slash lalt-slash--empty\">{emptyLabel}</div>;\n }\n\n return (\n <div className=\"lalt-slash\" role=\"listbox\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={\n index === selectedIndex\n ? \"lalt-slash__item lalt-slash__item--active\"\n : \"lalt-slash__item\"\n }\n // Losing editor focus tears the suggestion plugin down before a\n // click lands, so the item would never run its command.\n onMouseDown={(event) => {\n event.preventDefault();\n selectItem(index);\n }}\n onMouseEnter={() => {\n selectedRef.current = index;\n setSelectedIndex(index);\n }}\n >\n {item.icon && (\n <span className=\"lalt-slash__icon\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"lalt-slash__text\">\n <span className=\"lalt-slash__title\">{item.title}</span>\n {item.description && (\n <span className=\"lalt-slash__description\">{item.description}</span>\n )}\n </span>\n {item.shortcut && (\n <kbd className=\"lalt-slash__shortcut\">{item.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n );\n },\n);\n","import { Extension } from \"@tiptap/core\";\nimport { ReactRenderer } from \"@tiptap/react\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport Suggestion, { type SuggestionOptions, type SuggestionProps } from \"@tiptap/suggestion\";\n\nimport { SlashMenu, type SlashMenuHandle, type SlashMenuProps } from \"./SlashMenu\";\nimport { filterSlashItems, type SlashItem } from \"./slash\";\n\nexport interface SlashCommandsOptions {\n /** Read on every keystroke, so a host can swap the set without remounting\n * TipTap — rebuilding the extensions array wipes the text being typed. */\n items: () => SlashItem[];\n char: string;\n emptyLabel?: string;\n /** Distinguishes concurrently mounted editors: two surfaces sharing one key\n * would answer each other's suggestions. */\n pluginKey: PluginKey;\n}\n\nconst OFFSET = 6;\n\nexport const SlashCommands = Extension.create<SlashCommandsOptions>({\n name: \"laSlashCommands\",\n\n addOptions() {\n return {\n items: () => [],\n char: \"/\",\n pluginKey: new PluginKey(\"laSlashCommands\"),\n };\n },\n\n addProseMirrorPlugins() {\n const { items, char, emptyLabel, pluginKey } = this.options;\n\n const suggestion: Omit<SuggestionOptions<SlashItem>, \"editor\"> = {\n char,\n pluginKey,\n startOfLine: false,\n items: ({ query }) => filterSlashItems(items(), query),\n render: () => {\n let component: ReactRenderer<SlashMenuHandle, SlashMenuProps> | null = null;\n let container: HTMLDivElement | null = null;\n\n const place = (clientRect: SuggestionProps<SlashItem>[\"clientRect\"]) => {\n if (!container || !clientRect) return;\n const rect = clientRect();\n if (!rect) return;\n container.style.top = `${rect.bottom + OFFSET}px`;\n container.style.left = `${rect.left}px`;\n };\n\n const close = () => {\n container?.remove();\n container = null;\n component?.destroy();\n component = null;\n };\n\n return {\n onStart: (props) => {\n component = new ReactRenderer(SlashMenu, {\n props: { ...props, emptyLabel },\n editor: props.editor,\n });\n container = document.createElement(\"div\");\n container.className = \"lalt-slash-anchor\";\n container.appendChild(component.element);\n document.body.appendChild(container);\n place(props.clientRect);\n },\n onUpdate: (props) => {\n component?.updateProps({ ...props, emptyLabel });\n place(props.clientRect);\n },\n onKeyDown: (props) => {\n if (props.event.key === \"Escape\") {\n close();\n return true;\n }\n return component?.ref?.onKeyDown(props) ?? false;\n },\n onExit: close,\n };\n },\n };\n\n return [\n Suggestion({\n editor: this.editor,\n ...suggestion,\n command: ({ editor, range, props }) => props.command({ editor, range }),\n }),\n ];\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\n\nexport interface InlineSuggestion {\n id: string;\n /** Document positions of the passage this replaces. */\n from: number;\n to: number;\n after: string;\n}\n\nexport interface SuggestionActions {\n onAccept: (id: string) => void;\n onReject: (id: string) => void;\n acceptLabel: string;\n rejectLabel: string;\n}\n\nexport const inlineSuggestionsKey = new PluginKey<DecorationSet>(\"lalt-inline-suggestions\");\n\n/**\n * InlineSuggestions renders proposed edits where they belong: over the passage\n * they replace, with the new text right after it.\n *\n * Decorations, never document changes. The editor's content stays exactly what\n * the author wrote until they accept, so the draft is saveable mid-review and\n * a rejected suggestion leaves nothing behind — which a document-level insert\n * could not promise.\n */\nexport const InlineSuggestions = Extension.create<{ actions: SuggestionActions | null }>({\n name: \"lalt-inline-suggestions\",\n\n addOptions() {\n return { actions: null };\n },\n\n addProseMirrorPlugins() {\n const options = this.options;\n\n return [\n new Plugin<DecorationSet>({\n key: inlineSuggestionsKey,\n\n state: {\n init: () => DecorationSet.empty,\n apply(tr, old) {\n const set = tr.getMeta(inlineSuggestionsKey) as InlineSuggestion[] | undefined;\n if (set) return build(tr.doc, set, options.actions);\n // Positions follow the document as the author keeps typing around\n // a pending suggestion.\n return old.map(tr.mapping, tr.doc);\n },\n },\n\n props: {\n decorations(state) {\n return inlineSuggestionsKey.getState(state) ?? DecorationSet.empty;\n },\n },\n }),\n ];\n },\n});\n\nfunction build(\n doc: ProseMirrorNode,\n suggestions: InlineSuggestion[],\n actions: SuggestionActions | null,\n): DecorationSet {\n const decorations: Decoration[] = [];\n\n for (const s of suggestions) {\n if (s.from < 0 || s.to > doc.content.size || s.from >= s.to) continue;\n\n decorations.push(\n Decoration.inline(s.from, s.to, { class: \"lalt-suggestion__old\" }),\n Decoration.widget(s.to, () => renderProposal(s, actions), {\n // The widget belongs to the suggestion that precedes it, so typing at\n // the boundary does not push it around.\n side: 1,\n ignoreSelection: true,\n }),\n );\n }\n return DecorationSet.create(doc, decorations);\n}\n\nfunction renderProposal(s: InlineSuggestion, actions: SuggestionActions | null): HTMLElement {\n const wrap = document.createElement(\"span\");\n wrap.className = \"lalt-suggestion\";\n wrap.setAttribute(\"contenteditable\", \"false\");\n\n const text = document.createElement(\"span\");\n text.className = \"lalt-suggestion__new\";\n text.textContent = s.after;\n wrap.appendChild(text);\n\n if (actions) {\n wrap.appendChild(button(\"reject\", actions.rejectLabel, \"✕\", () => actions.onReject(s.id)));\n wrap.appendChild(button(\"accept\", actions.acceptLabel, \"✓\", () => actions.onAccept(s.id)));\n }\n return wrap;\n}\n\nfunction button(kind: string, label: string, glyph: string, onClick: () => void): HTMLElement {\n const el = document.createElement(\"button\");\n el.type = \"button\";\n el.className = `lalt-suggestion__action lalt-suggestion__action--${kind}`;\n el.setAttribute(\"aria-label\", label);\n el.textContent = glyph;\n // mousedown would move the selection into the widget before the click lands.\n el.addEventListener(\"mousedown\", (e) => e.preventDefault());\n el.addEventListener(\"click\", (e) => {\n e.preventDefault();\n onClick();\n });\n return el;\n}\n","export interface TextRange {\n from: number;\n to: number;\n}\n\nexport interface RevisionPart {\n before: string;\n after: string;\n}\n\n// Letters, digits, and the marks that belong inside a French word.\nconst WORD = /[\\p{L}\\p{N}'’-]/u;\n\n/**\n * expandToWords grows a range out to whole words.\n *\n * A selection dragged across a line lands mid-word more often than not, and a\n * model handed \"ns de production\" answers for a fragment — which then reads as\n * a truncated diff the author has to decode.\n */\nexport function expandToWords(text: string, from: number, to: number): TextRange {\n if (from >= to) return { from, to };\n\n let start = from;\n while (start > 0 && WORD.test(text[start - 1]) && WORD.test(text[start])) start--;\n\n let end = to;\n while (end < text.length && WORD.test(text[end]) && WORD.test(text[end - 1])) end++;\n\n return { from: start, to: end };\n}\n\n/**\n * splitRevision turns one model answer into the decisions it really contains.\n *\n * A three-paragraph rewrite accepted or rejected as a whole forces the author\n * to keep a weak paragraph for a strong one, so each paragraph becomes its own\n * decision — but only when both sides line up. When the model merged or split\n * paragraphs, position no longer maps them, and pairing by index would show a\n * diff that never existed.\n */\nexport function splitRevision(before: string, after: string): RevisionPart[] {\n if (before.trim() === after.trim()) return [];\n\n const olds = paragraphs(before);\n const news = paragraphs(after);\n\n if (olds.length !== news.length || olds.length === 0) {\n return [{ before: before.trim(), after: after.trim() }];\n }\n\n const parts: RevisionPart[] = [];\n for (let i = 0; i < olds.length; i++) {\n if (olds[i] !== news[i]) parts.push({ before: olds[i], after: news[i] });\n }\n return parts;\n}\n\nfunction paragraphs(s: string): string[] {\n return s\n .split(/\\n\\s*\\n/)\n .map((p) => p.trim())\n .filter((p) => p !== \"\");\n}\n","import type { RichDoc, RichNode } from \"./document\";\n\ninterface Mark {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * docToMarkdown renders a TipTap document as the markdown a blog accepts.\n *\n * Written here rather than taken from a library: the editor only ever produces\n * the handful of nodes StarterKit gives it, and a general markdown serializer\n * brings a dependency and its escaping rules for output a human is about to\n * paste into their own site.\n */\nexport function docToMarkdown(doc: RichDoc): string {\n return blocks(doc.content ?? [])\n .filter((b) => b !== \"\")\n .join(\"\\n\\n\");\n}\n\nfunction blocks(nodes: RichNode[]): string[] {\n return nodes.map(block);\n}\n\nfunction block(node: RichNode): string {\n switch (node.type) {\n case \"heading\": {\n const level = Number(node.attrs?.level ?? 2);\n return `${\"#\".repeat(Math.min(6, Math.max(1, level)))} ${inline(node.content)}`;\n }\n case \"blockquote\":\n return blocks(node.content ?? [])\n .filter((b) => b !== \"\")\n .map((b) =>\n b\n .split(\"\\n\")\n .map((line) => `> ${line}`)\n .join(\"\\n\"),\n )\n .join(\"\\n>\\n\");\n case \"codeBlock\": {\n const language = String(node.attrs?.language ?? \"\");\n return `\\`\\`\\`${language}\\n${inline(node.content)}\\n\\`\\`\\``;\n }\n case \"bulletList\":\n return listItems(node).map((item) => `- ${item}`).join(\"\\n\");\n case \"orderedList\":\n return listItems(node)\n .map((item, i) => `${i + 1}. ${item}`)\n .join(\"\\n\");\n case \"horizontalRule\":\n return \"---\";\n default:\n return inline(node.content);\n }\n}\n\nfunction listItems(list: RichNode): string[] {\n return (list.content ?? []).map((item) =>\n blocks(item.content ?? [])\n .filter((b) => b !== \"\")\n // Continuation lines are indented so the item stays one item.\n .join(\"\\n\\n\")\n .split(\"\\n\")\n .join(\"\\n \"),\n );\n}\n\nfunction inline(nodes: RichNode[] | undefined): string {\n return (nodes ?? []).map(text).join(\"\");\n}\n\nfunction text(node: RichNode): string {\n if (node.type === \"hardBreak\") return \"\\n\";\n if (typeof node.text !== \"string\") return inline(node.content);\n\n let out = node.text;\n // Innermost first, so bold inside a link wraps the text and not the URL.\n for (const mark of ((node.marks as Mark[] | undefined) ?? []).slice().reverse()) {\n switch (mark.type) {\n case \"bold\":\n out = `**${out}**`;\n break;\n case \"italic\":\n out = `*${out}*`;\n break;\n case \"strike\":\n out = `~~${out}~~`;\n break;\n case \"code\":\n out = `\\`${out}\\``;\n break;\n case \"link\":\n out = `[${out}](${String(mark.attrs?.href ?? \"\")})`;\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useEffect","useState","read","text","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","SlashMenu","Extension","PluginKey","text","text"]}
1
+ {"version":3,"sources":["../src/placement.ts","../src/SelectionToolbar.tsx","../src/useViewport.ts","../src/viewport.ts","../src/useSelectionToolbar.ts","../src/formats.ts","../src/EditorScreen.tsx","../src/slash.ts","../src/SlashMenu.tsx","../src/SlashCommands.ts","../src/InlineSuggestions.ts","../src/passage.ts","../src/markdown.ts"],"sourcesContent":["export interface SelectionRect {\n /** Horizontal centre of the selection. */\n x: number;\n top: number;\n bottom: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface PlaceToolbarInput {\n selection: SelectionRect;\n toolbar: Size;\n viewport: Size;\n /** iOS renders its own callout above a selection, which covers a toolbar\n * placed there, so the caller asks for below on that platform. */\n preferBelow?: boolean;\n /** Height of the system's own selection callout — Cut/Copy/Paste — which is\n * drawn over the page and cannot be measured from it. Clearing it is what\n * keeps the two from overlapping. Zero where there is no such callout. */\n calloutHeight?: number;\n}\n\nexport interface ToolbarPlacement {\n left: number;\n top: number;\n below: boolean;\n}\n\nconst GAP = 8;\n\n/** iOS draws its Cut/Copy/Paste callout roughly this tall, below a selection\n * the page cannot see. Measured from the platform rather than derived: it is\n * a system surface, so nothing in the document reports its size. */\nexport const IOS_CALLOUT_HEIGHT = 44;\n\n/**\n * placeToolbar positions a floating toolbar against a selection.\n *\n * Kept apart from the component because it is the part that is wrong in subtle\n * ways — off-screen at the edges, hidden under a system callout — and the part\n * a test can pin down without a browser.\n */\nexport function placeToolbar({\n selection,\n toolbar,\n viewport,\n preferBelow = false,\n calloutHeight = 0,\n}: PlaceToolbarInput): ToolbarPlacement {\n const clearance = GAP + calloutHeight;\n const fitsAbove = selection.top - GAP - toolbar.height >= 0;\n const fitsBelow = selection.bottom + clearance + toolbar.height <= viewport.height;\n\n const below = preferBelow ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n\n // Both branches stay within reach of the selection. Clamping to the viewport\n // edge instead would park the toolbar at the top of the screen whenever a\n // raised keyboard shrinks the viewport under the selection's own\n // coordinates — far from the words it acts on, which is worse than a few\n // pixels of overlap.\n const top = below\n ? Math.min(\n selection.bottom + clearance,\n // Never past the bottom edge: a selection low on the page would put\n // the toolbar off-screen once the callout's height is added.\n Math.max(selection.top, viewport.height - toolbar.height - GAP),\n )\n : Math.max(\n Math.min(GAP, selection.top),\n selection.top - GAP - toolbar.height,\n );\n\n const centred = selection.x - toolbar.width / 2;\n const left = Math.max(0, Math.min(centred, viewport.width - toolbar.width));\n\n return { left, top, below };\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { placeToolbar, IOS_CALLOUT_HEIGHT, type SelectionRect } from \"./placement\";\nimport { useViewport } from \"./useViewport\";\n\nexport interface ToolbarAction {\n id: string;\n label: string;\n icon?: React.ReactNode;\n onSelect: (selectedText: string) => void;\n}\n\nexport interface BlockFormat {\n id: string;\n label: string;\n shortcut?: string;\n isActive?: boolean;\n onSelect: () => void;\n}\n\nexport interface SelectionToolbarProps {\n selectedText: string;\n selection: SelectionRect;\n actions?: ToolbarAction[];\n /** Heading levels and list kinds, rendered behind a single trigger. */\n formats?: BlockFormat[];\n formatsLabel?: string;\n /** iOS draws its own callout above a selection; the host detects it. */\n preferBelow?: boolean;\n container?: HTMLElement | null;\n}\n\nconst FALLBACK = { width: 320, height: 40 };\n\n// Whether the width used to place the bar is the one it actually has. The\n// fallback is a guess, and placing on a guess puts the bar off-centre by\n// however far the guess is wrong — which is invisible while the bar happens to\n// be 320px wide and obvious as soon as a host adds another action to it.\ntype Measured = { width: number; height: number; real: boolean };\n\nexport function SelectionToolbar({\n selectedText,\n selection,\n actions = [],\n formats = [],\n formatsLabel = \"Format\",\n preferBelow = false,\n container,\n}: SelectionToolbarProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState<Measured>({ ...FALLBACK, real: false });\n const [formatsOpen, setFormatsOpen] = useState(false);\n const viewport = useViewport();\n\n // Measured after paint rather than assumed: the toolbar's width depends on\n // labels the host supplies, and guessing it puts the toolbar off-centre.\n //\n // Observed rather than recomputed from a dependency list: the width follows\n // the rendered labels, and a list naming the things that happen to change it\n // today misses the next one — a longer label, a font that loads late.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n const measure = () => {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0)\n setSize((prev) =>\n prev.real && prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height, real: true },\n );\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (!formatsOpen) return;\n const close = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setFormatsOpen(false);\n };\n document.addEventListener(\"mousedown\", close);\n return () => document.removeEventListener(\"mousedown\", close);\n }, [formatsOpen]);\n\n if (typeof document === \"undefined\") return null;\n\n const place = placeToolbar({\n selection,\n toolbar: size,\n viewport: { width: viewport.width, height: viewport.height },\n preferBelow,\n // preferBelow is set by the same platform check that puts the callout\n // there, so it is what says whether there is one to clear.\n calloutHeight: preferBelow ? IOS_CALLOUT_HEIGHT : 0,\n });\n\n return createPortal(\n <div\n ref={ref}\n role=\"toolbar\"\n className=\"lalt-toolbar\"\n // Custom properties rather than left/top directly: under the mobile\n // breakpoint the stylesheet docks the toolbar to the bottom of the\n // screen and ignores these, which inline left/top would override.\n //\n // The inset is what the docked bar sits on top of. `bottom: 0` means the\n // bottom of the layout viewport, which the on-screen keyboard covers\n // without shrinking — so the bar has to be lifted by the strip the\n // keyboard occupies to stay in sight.\n style={\n {\n \"--lalt-toolbar-left\": `${place.left}px`,\n \"--lalt-toolbar-top\": `${place.top}px`,\n \"--lalt-keyboard-inset\": `${viewport.bottomInset}px`,\n // Hidden for the one frame between mounting and being measured:\n // that first paint places the bar on the fallback width, and showing\n // it means showing the bar jump once the real width arrives.\n // Hidden rather than unmounted — it has to be in the document to\n // have a width at all.\n visibility: size.real ? undefined : \"hidden\",\n } as React.CSSProperties\n }\n >\n {actions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n className=\"lalt-toolbar__button\"\n // The label is what the narrow layout hides, so it has to stay\n // reachable to anyone not reading the icon.\n aria-label={action.icon ? action.label : undefined}\n data-has-icon={action.icon ? \"\" : undefined}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => action.onSelect(selectedText)}\n >\n {action.icon}\n <span className=\"lalt-toolbar__label\">{action.label}</span>\n </button>\n ))}\n\n {formats.length > 0 && (\n <div className=\"lalt-toolbar__formats\">\n <button\n type=\"button\"\n className=\"lalt-toolbar__button\"\n aria-haspopup=\"menu\"\n aria-expanded={formatsOpen}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => setFormatsOpen((open) => !open)}\n >\n {formatsLabel}\n </button>\n {formatsOpen && (\n <div role=\"menu\" className=\"lalt-toolbar__menu\">\n {formats.map((format) => (\n <button\n key={format.id}\n type=\"button\"\n role=\"menuitem\"\n className=\"lalt-toolbar__item\"\n aria-current={format.isActive}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n format.onSelect();\n setFormatsOpen(false);\n }}\n >\n <span>{format.label}</span>\n {format.shortcut && (\n <kbd className=\"lalt-toolbar__shortcut\">{format.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>,\n container ?? document.body,\n );\n}\n","import { useEffect, useState } from \"react\";\n\nimport { readViewport, type ViewportMetrics } from \"./viewport\";\n\nconst SERVER: ViewportMetrics = { width: 0, height: 0, offsetTop: 0, bottomInset: 0 };\n\nfunction read(): ViewportMetrics {\n if (typeof window === \"undefined\") return SERVER;\n return readViewport({\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n visual: window.visualViewport,\n });\n}\n\n/**\n * useViewport tracks the area a fixed element can occupy, keyboard included.\n *\n * The visual viewport's own events are the only ones a raised keyboard fires:\n * `window.resize` does not run on iOS when the keyboard opens, so a component\n * listening to it alone keeps the measurements it took before the keys\n * appeared.\n */\nexport function useViewport(): ViewportMetrics {\n const [metrics, setMetrics] = useState<ViewportMetrics>(read);\n\n useEffect(() => {\n const update = () => setMetrics(read());\n update();\n\n const visual = window.visualViewport;\n visual?.addEventListener(\"resize\", update);\n // iOS scrolls the visual viewport rather than the document to keep the\n // caret above the keyboard, and reports it here and nowhere else.\n visual?.addEventListener(\"scroll\", update);\n window.addEventListener(\"resize\", update);\n window.addEventListener(\"orientationchange\", update);\n\n return () => {\n visual?.removeEventListener(\"resize\", update);\n visual?.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n window.removeEventListener(\"orientationchange\", update);\n };\n }, []);\n\n return metrics;\n}\n","export interface VisualViewportLike {\n width: number;\n height: number;\n offsetTop: number;\n}\n\nexport interface ViewportMetrics {\n width: number;\n height: number;\n offsetTop: number;\n /** How much of the layout viewport's bottom is covered, the on-screen\n * keyboard included. Zero on a desktop browser and wherever the API is\n * missing. */\n bottomInset: number;\n}\n\nexport interface ReadViewportInput {\n innerWidth: number;\n innerHeight: number;\n visual?: VisualViewportLike | null;\n}\n\n/**\n * readViewport reports the area a fixed element can actually occupy.\n *\n * The distinction the on-screen keyboard turns on: `innerHeight` measures the\n * layout viewport, which the keyboard covers without shrinking, so an element\n * pinned to its bottom edge ends up underneath the keys. Only `visualViewport`\n * narrows, and the difference is the inset a docked toolbar has to clear.\n */\nexport function readViewport({\n innerWidth,\n innerHeight,\n visual,\n}: ReadViewportInput): ViewportMetrics {\n if (!visual) {\n return { width: innerWidth, height: innerHeight, offsetTop: 0, bottomInset: 0 };\n }\n\n // offsetTop is what iOS scrolls the visual viewport by; without it the\n // covered strip reads as smaller than it is once the page is scrolled under\n // a raised keyboard. Never negative: a viewport reported as taller than the\n // layout one — Android during a rotation, a browser mid-resize — would\n // otherwise push the toolbar down off the screen.\n const covered = innerHeight - visual.height - visual.offsetTop;\n\n return {\n width: visual.width,\n height: visual.height,\n offsetTop: visual.offsetTop,\n bottomInset: Math.max(0, Math.round(covered)),\n };\n}\n","import { useEffect, useState } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\n\nimport type { SelectionRect } from \"./placement\";\n\nexport interface SelectionInfo {\n text: string;\n rect: SelectionRect;\n}\n\n/**\n * useSelectionToolbar reports the current non-empty selection of an editor.\n *\n * Returns null whenever there is nothing to act on, so the caller renders the\n * toolbar only while it has a target.\n */\nexport function useSelectionToolbar(editor: Editor | null): SelectionInfo | null {\n const [selection, setSelection] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n if (!editor) return;\n\n const read = () => {\n const { state } = editor;\n const { from, to, empty } = state.selection;\n if (empty || !editor.isFocused) {\n setSelection(null);\n return;\n }\n\n const text = state.doc.textBetween(from, to, \" \").trim();\n if (text === \"\") {\n setSelection(null);\n return;\n }\n\n const start = editor.view.coordsAtPos(from);\n const end = editor.view.coordsAtPos(to);\n setSelection({\n text,\n rect: {\n x: (start.left + end.right) / 2,\n top: Math.min(start.top, end.top),\n bottom: Math.max(start.bottom, end.bottom),\n },\n });\n };\n\n read();\n editor.on(\"selectionUpdate\", read);\n editor.on(\"blur\", read);\n editor.on(\"focus\", read);\n return () => {\n editor.off(\"selectionUpdate\", read);\n editor.off(\"blur\", read);\n editor.off(\"focus\", read);\n };\n }, [editor]);\n\n return selection;\n}\n","import type { Editor } from \"@tiptap/react\";\n\n// Type-only: these commands are contributed by StarterKit's extensions, and\n// without the reference TypeScript does not know they exist on a chain. No\n// runtime import, so the host stays in charge of which extensions it mounts —\n// blockFormats simply returns entries whose commands no-op if one is missing.\nimport type {} from \"@tiptap/starter-kit\";\n\nimport type { BlockFormat } from \"./SelectionToolbar\";\n\nexport interface FormatLabels {\n paragraph: string;\n heading1: string;\n heading2: string;\n heading3: string;\n orderedList: string;\n bulletList: string;\n}\n\nexport const defaultFormatLabels: FormatLabels = {\n paragraph: \"Texte\",\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n heading3: \"Titre 3\",\n orderedList: \"Liste numérotée\",\n bulletList: \"Liste à puces\",\n};\n\n/**\n * blockFormats is the set a writer reaches for on a selected passage.\n *\n * Labels are passed in rather than translated here: the package carries no\n * i18n runtime, so the host names things in whatever language it speaks.\n */\nexport function blockFormats(\n editor: Editor | null,\n labels: FormatLabels = defaultFormatLabels,\n): BlockFormat[] {\n if (!editor) return [];\n\n // Shortcuts are the ones TipTap's own extensions bind, read from their\n // source rather than invented: heading binds Mod-Alt-<level>, the list\n // extensions bind Mod-Shift-7/8, and setParagraph binds nothing at all.\n // A label promising a shortcut that does nothing is worse than no label.\n const mod = isApple() ? \"⌘\" : \"Ctrl\";\n\n return [\n {\n id: \"paragraph\",\n label: labels.paragraph,\n isActive: editor.isActive(\"paragraph\"),\n onSelect: () => editor.chain().focus().setParagraph().run(),\n },\n ...([1, 2, 3] as const).map((level) => ({\n id: `heading${level}`,\n label: labels[`heading${level}` as keyof FormatLabels],\n shortcut: `${mod}+Alt+${level}`,\n isActive: editor.isActive(\"heading\", { level }),\n onSelect: () => editor.chain().focus().toggleHeading({ level }).run(),\n })),\n {\n id: \"orderedList\",\n label: labels.orderedList,\n shortcut: `${mod}+Shift+7`,\n isActive: editor.isActive(\"orderedList\"),\n onSelect: () => editor.chain().focus().toggleOrderedList().run(),\n },\n {\n id: \"bulletList\",\n label: labels.bulletList,\n shortcut: `${mod}+Shift+8`,\n isActive: editor.isActive(\"bulletList\"),\n onSelect: () => editor.chain().focus().toggleBulletList().run(),\n },\n ];\n}\n\nfunction isApple(): boolean {\n if (typeof navigator === \"undefined\") return false;\n return /Mac|iPad|iPhone|iPod/.test(navigator.platform || navigator.userAgent);\n}\n","import { useViewport } from \"./useViewport\";\n\nexport type SaveState = \"idle\" | \"saving\" | \"saved\" | \"dirty\";\n\nexport interface EditorScreenProps {\n /** The writing surface. Mounted by the host, which owns the TipTap instance\n * and whichever extensions it needs. */\n children: React.ReactNode;\n title?: string;\n onTitleChange?: (title: string) => void;\n titlePlaceholder?: string;\n titleLabel?: string;\n saveState?: SaveState;\n saveLabels?: SaveLabels;\n /** Drawn at the far left of the bar; a back control belongs here. */\n lead?: React.ReactNode;\n /** Drawn at the far right. Keep it to what a thumb can reach on a phone —\n * one primary control and an overflow, not a row of five. */\n actions?: React.ReactNode;\n /** Sits between the save dot and the actions: a character count, a badge. */\n status?: React.ReactNode;\n /** Between the title and the writing surface — a dictation control, a hint. */\n aside?: React.ReactNode;\n /** Below the writing surface, inside the scrolling column. */\n footer?: React.ReactNode;\n /** Above the writing surface and below the bar, pinned while the page\n * scrolls: tabs over variants of one document belong here. */\n subnav?: React.ReactNode;\n /** Fills its container instead of the viewport, for a host whose shell\n * already owns the page height. */\n fill?: boolean;\n className?: string;\n}\n\nexport interface SaveLabels {\n saving: string;\n saved: string;\n dirty: string;\n}\n\nexport const defaultSaveLabels: SaveLabels = {\n saving: \"Enregistrement en cours\",\n saved: \"Enregistré\",\n dirty: \"Modifications non enregistrées\",\n};\n\n/**\n * EditorScreen is the page a writing surface sits in: a command bar, a title,\n * one column of prose, and slots for whatever the host puts around it.\n *\n * It renders and measures; it never fetches, saves or routes. `saveState` is\n * displayed, not computed — the host owns the debounce and the request, so the\n * same screen serves an autosaving draft and a form with an explicit button.\n *\n * Commands sit at the top rather than in a bar at the bottom. Below the fold\n * they are gone the moment the document runs past one screen, and pinned to\n * the bottom they eat a sixth of what the on-screen keyboard leaves.\n */\nexport function EditorScreen({\n children,\n title,\n onTitleChange,\n titlePlaceholder = \"Titre\",\n titleLabel = \"Titre\",\n saveState,\n saveLabels = defaultSaveLabels,\n lead,\n actions,\n status,\n aside,\n footer,\n subnav,\n fill = false,\n className,\n}: EditorScreenProps) {\n const { bottomInset } = useViewport();\n const withTitle = onTitleChange !== undefined;\n\n return (\n <div\n className={[\n \"lalt-screen\",\n fill ? \"lalt-screen--fill\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The keyboard covers the layout viewport without shrinking it, so the\n // column's last lines sit under the keys unless the strip is padded out.\n style={{ \"--lalt-keyboard\": `${bottomInset}px` } as React.CSSProperties}\n >\n <header className=\"lalt-screen__bar\">\n {lead}\n {saveState && (\n <SaveDot state={saveState} labels={saveLabels} />\n )}\n {status && <div className=\"lalt-screen__status\">{status}</div>}\n {actions && <div className=\"lalt-screen__actions\">{actions}</div>}\n </header>\n\n {subnav && <div className=\"lalt-screen__subnav\">{subnav}</div>}\n\n <div className=\"lalt-screen__scroll\">\n <div className=\"lalt-screen__column\">\n {withTitle && (\n <input\n className=\"lalt-screen__title\"\n value={title ?? \"\"}\n onChange={(event) => onTitleChange(event.target.value)}\n placeholder={titlePlaceholder}\n aria-label={titleLabel}\n />\n )}\n {aside && <div className=\"lalt-screen__aside\">{aside}</div>}\n {children}\n {footer && <div className=\"lalt-screen__footer\">{footer}</div>}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * A dot rather than a word: the state is worth a glance, never a read, and a\n * label that appears and vanishes leaves the author wondering what it meant\n * once it is gone. Screen readers get the sentence instead.\n */\nfunction SaveDot({ state, labels }: { state: SaveState; labels: SaveLabels }) {\n if (state === \"idle\") return <span className=\"lalt-screen__save\" />;\n\n const label =\n state === \"saving\" ? labels.saving : state === \"dirty\" ? labels.dirty : labels.saved;\n\n return (\n <span className=\"lalt-screen__save\" role=\"status\" aria-live=\"polite\">\n <span className={`lalt-screen__dot lalt-screen__dot--${state}`} aria-hidden />\n <span className=\"lalt-screen__sr\">{label}</span>\n </span>\n );\n}\n","import type { Editor } from \"@tiptap/react\";\nimport type { Range } from \"@tiptap/core\";\n\n// Type-only, as in formats.ts: StarterKit contributes these commands to a\n// chain, and without the reference TypeScript does not know they exist. The\n// host stays in charge of which extensions it actually mounts.\nimport type {} from \"@tiptap/starter-kit\";\n\n/**\n * SlashItem is one entry of the `/` menu.\n *\n * icon is a node rather than a component type: the package carries no icon\n * library, so the host renders whichever one it already ships.\n */\nexport interface SlashItem {\n id: string;\n title: string;\n description?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n command: (args: { editor: Editor; range: Range }) => void;\n}\n\nexport interface SlashLabels {\n heading1: string;\n heading2: string;\n bulletList: string;\n orderedList: string;\n blockquote: string;\n codeBlock: string;\n horizontalRule: string;\n}\n\nexport const defaultSlashLabels: SlashLabels = {\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n bulletList: \"Liste à puces\",\n orderedList: \"Liste numérotée\",\n blockquote: \"Citation\",\n codeBlock: \"Bloc de code\",\n horizontalRule: \"Séparateur\",\n};\n\nexport interface SlashDescriptions {\n heading1?: string;\n heading2?: string;\n bulletList?: string;\n orderedList?: string;\n blockquote?: string;\n codeBlock?: string;\n horizontalRule?: string;\n}\n\nexport interface DefaultSlashItemsOptions {\n labels?: SlashLabels;\n descriptions?: SlashDescriptions;\n icons?: Partial<Record<DefaultSlashId, React.ReactNode>>;\n}\n\nexport type DefaultSlashId =\n | \"heading1\"\n | \"heading2\"\n | \"bulletList\"\n | \"orderedList\"\n | \"blockquote\"\n | \"codeBlock\"\n | \"horizontalRule\";\n\n/**\n * defaultSlashItems is the block set every writing surface shares.\n *\n * Returned as data rather than rendered behind flags: a host adds, removes or\n * reorders entries by composing the array, so a surface that needs one more\n * command does not push another boolean into this signature.\n */\nexport function defaultSlashItems({\n labels = defaultSlashLabels,\n descriptions = {},\n icons = {},\n}: DefaultSlashItemsOptions = {}): SlashItem[] {\n const item = (\n id: DefaultSlashId,\n shortcut: string,\n run: (editor: Editor) => void,\n ): SlashItem => ({\n id,\n title: labels[id],\n description: descriptions[id],\n shortcut,\n icon: icons[id],\n command: ({ editor, range }) => {\n editor.chain().focus().deleteRange(range).run();\n run(editor);\n },\n });\n\n return [\n item(\"heading1\", \"#\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 1 }).run(),\n ),\n item(\"heading2\", \"##\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 2 }).run(),\n ),\n item(\"bulletList\", \"-\", (editor) =>\n editor.chain().focus().toggleBulletList().run(),\n ),\n item(\"orderedList\", \"1.\", (editor) =>\n editor.chain().focus().toggleOrderedList().run(),\n ),\n item(\"blockquote\", \">\", (editor) =>\n editor.chain().focus().toggleBlockquote().run(),\n ),\n item(\"codeBlock\", \"```\", (editor) =>\n editor.chain().focus().toggleCodeBlock().run(),\n ),\n item(\"horizontalRule\", \"---\", (editor) =>\n editor.chain().focus().setHorizontalRule().run(),\n ),\n ];\n}\n\n/** filterSlashItems matches the query against titles the way the menu does. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase();\n if (q === \"\") return items;\n return items.filter((item) => item.title.toLowerCase().includes(q));\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { SlashItem } from \"./slash\";\n\nexport interface SlashMenuHandle {\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n}\n\nexport interface SlashMenuProps {\n items: SlashItem[];\n command: (item: SlashItem) => void;\n emptyLabel?: string;\n}\n\nexport const SlashMenu = forwardRef<SlashMenuHandle, SlashMenuProps>(\n function SlashMenu({ items, command, emptyLabel = \"Aucune commande\" }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // The suggestion plugin rebuilds these props on every keystroke, so the\n // imperative handle reads refs rather than a captured render closure —\n // otherwise Enter runs the command the menu showed one keystroke ago.\n const itemsRef = useRef(items);\n itemsRef.current = items;\n const commandRef = useRef(command);\n commandRef.current = command;\n const selectedRef = useRef(selectedIndex);\n selectedRef.current = selectedIndex;\n\n const ids = items.map((item) => item.id).join(\" \");\n useEffect(() => {\n setSelectedIndex(0);\n selectedRef.current = 0;\n }, [ids]);\n\n const selectItem = useCallback((index: number) => {\n const item = itemsRef.current[index];\n if (item) commandRef.current(item);\n }, []);\n\n const move = useCallback((delta: number) => {\n const count = itemsRef.current.length;\n if (count === 0) return;\n const next = (selectedRef.current + delta + count) % count;\n selectedRef.current = next;\n setSelectedIndex(next);\n }, []);\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n if (event.key === \"ArrowUp\") {\n move(-1);\n return true;\n }\n if (event.key === \"ArrowDown\") {\n move(1);\n return true;\n }\n if (event.key === \"Enter\" || event.key === \"Tab\") {\n if (itemsRef.current.length === 0) return false;\n selectItem(selectedRef.current);\n return true;\n }\n return false;\n },\n }));\n\n if (items.length === 0) {\n return <div className=\"lalt-slash lalt-slash--empty\">{emptyLabel}</div>;\n }\n\n return (\n <div className=\"lalt-slash\" role=\"listbox\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={\n index === selectedIndex\n ? \"lalt-slash__item lalt-slash__item--active\"\n : \"lalt-slash__item\"\n }\n // Losing editor focus tears the suggestion plugin down before a\n // click lands, so the item would never run its command.\n onMouseDown={(event) => {\n event.preventDefault();\n selectItem(index);\n }}\n onMouseEnter={() => {\n selectedRef.current = index;\n setSelectedIndex(index);\n }}\n >\n {item.icon && (\n <span className=\"lalt-slash__icon\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"lalt-slash__text\">\n <span className=\"lalt-slash__title\">{item.title}</span>\n {item.description && (\n <span className=\"lalt-slash__description\">{item.description}</span>\n )}\n </span>\n {item.shortcut && (\n <kbd className=\"lalt-slash__shortcut\">{item.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n );\n },\n);\n","import { Extension } from \"@tiptap/core\";\nimport { ReactRenderer } from \"@tiptap/react\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport Suggestion, { type SuggestionOptions, type SuggestionProps } from \"@tiptap/suggestion\";\n\nimport { SlashMenu, type SlashMenuHandle, type SlashMenuProps } from \"./SlashMenu\";\nimport { filterSlashItems, type SlashItem } from \"./slash\";\n\nexport interface SlashCommandsOptions {\n /** Read on every keystroke, so a host can swap the set without remounting\n * TipTap — rebuilding the extensions array wipes the text being typed. */\n items: () => SlashItem[];\n char: string;\n emptyLabel?: string;\n /** Distinguishes concurrently mounted editors: two surfaces sharing one key\n * would answer each other's suggestions. */\n pluginKey: PluginKey;\n}\n\nconst OFFSET = 6;\n\nexport const SlashCommands = Extension.create<SlashCommandsOptions>({\n name: \"laSlashCommands\",\n\n addOptions() {\n return {\n items: () => [],\n char: \"/\",\n pluginKey: new PluginKey(\"laSlashCommands\"),\n };\n },\n\n addProseMirrorPlugins() {\n const { items, char, emptyLabel, pluginKey } = this.options;\n\n const suggestion: Omit<SuggestionOptions<SlashItem>, \"editor\"> = {\n char,\n pluginKey,\n startOfLine: false,\n items: ({ query }) => filterSlashItems(items(), query),\n render: () => {\n let component: ReactRenderer<SlashMenuHandle, SlashMenuProps> | null = null;\n let container: HTMLDivElement | null = null;\n\n const place = (clientRect: SuggestionProps<SlashItem>[\"clientRect\"]) => {\n if (!container || !clientRect) return;\n const rect = clientRect();\n if (!rect) return;\n container.style.top = `${rect.bottom + OFFSET}px`;\n container.style.left = `${rect.left}px`;\n };\n\n const close = () => {\n container?.remove();\n container = null;\n component?.destroy();\n component = null;\n };\n\n return {\n onStart: (props) => {\n component = new ReactRenderer(SlashMenu, {\n props: { ...props, emptyLabel },\n editor: props.editor,\n });\n container = document.createElement(\"div\");\n container.className = \"lalt-slash-anchor\";\n container.appendChild(component.element);\n document.body.appendChild(container);\n place(props.clientRect);\n },\n onUpdate: (props) => {\n component?.updateProps({ ...props, emptyLabel });\n place(props.clientRect);\n },\n onKeyDown: (props) => {\n if (props.event.key === \"Escape\") {\n close();\n return true;\n }\n return component?.ref?.onKeyDown(props) ?? false;\n },\n onExit: close,\n };\n },\n };\n\n return [\n Suggestion({\n editor: this.editor,\n ...suggestion,\n command: ({ editor, range, props }) => props.command({ editor, range }),\n }),\n ];\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\n\nexport interface InlineSuggestion {\n id: string;\n /** Document positions of the passage this replaces. */\n from: number;\n to: number;\n after: string;\n}\n\nexport interface SuggestionActions {\n onAccept: (id: string) => void;\n onReject: (id: string) => void;\n acceptLabel: string;\n rejectLabel: string;\n}\n\nexport const inlineSuggestionsKey = new PluginKey<DecorationSet>(\"lalt-inline-suggestions\");\n\n/**\n * InlineSuggestions renders proposed edits where they belong: over the passage\n * they replace, with the new text right after it.\n *\n * Decorations, never document changes. The editor's content stays exactly what\n * the author wrote until they accept, so the draft is saveable mid-review and\n * a rejected suggestion leaves nothing behind — which a document-level insert\n * could not promise.\n */\nexport const InlineSuggestions = Extension.create<{ actions: SuggestionActions | null }>({\n name: \"lalt-inline-suggestions\",\n\n addOptions() {\n return { actions: null };\n },\n\n addProseMirrorPlugins() {\n const options = this.options;\n\n return [\n new Plugin<DecorationSet>({\n key: inlineSuggestionsKey,\n\n state: {\n init: () => DecorationSet.empty,\n apply(tr, old) {\n const set = tr.getMeta(inlineSuggestionsKey) as InlineSuggestion[] | undefined;\n if (set) return build(tr.doc, set, options.actions);\n // Positions follow the document as the author keeps typing around\n // a pending suggestion.\n return old.map(tr.mapping, tr.doc);\n },\n },\n\n props: {\n decorations(state) {\n return inlineSuggestionsKey.getState(state) ?? DecorationSet.empty;\n },\n },\n }),\n ];\n },\n});\n\nfunction build(\n doc: ProseMirrorNode,\n suggestions: InlineSuggestion[],\n actions: SuggestionActions | null,\n): DecorationSet {\n const decorations: Decoration[] = [];\n\n for (const s of suggestions) {\n if (s.from < 0 || s.to > doc.content.size || s.from >= s.to) continue;\n\n decorations.push(\n Decoration.inline(s.from, s.to, { class: \"lalt-suggestion__old\" }),\n Decoration.widget(s.to, () => renderProposal(s, actions), {\n // The widget belongs to the suggestion that precedes it, so typing at\n // the boundary does not push it around.\n side: 1,\n ignoreSelection: true,\n }),\n );\n }\n return DecorationSet.create(doc, decorations);\n}\n\nfunction renderProposal(s: InlineSuggestion, actions: SuggestionActions | null): HTMLElement {\n const wrap = document.createElement(\"span\");\n wrap.className = \"lalt-suggestion\";\n wrap.setAttribute(\"contenteditable\", \"false\");\n\n const text = document.createElement(\"span\");\n text.className = \"lalt-suggestion__new\";\n text.textContent = s.after;\n wrap.appendChild(text);\n\n if (actions) {\n wrap.appendChild(button(\"reject\", actions.rejectLabel, \"✕\", () => actions.onReject(s.id)));\n wrap.appendChild(button(\"accept\", actions.acceptLabel, \"✓\", () => actions.onAccept(s.id)));\n }\n return wrap;\n}\n\nfunction button(kind: string, label: string, glyph: string, onClick: () => void): HTMLElement {\n const el = document.createElement(\"button\");\n el.type = \"button\";\n el.className = `lalt-suggestion__action lalt-suggestion__action--${kind}`;\n el.setAttribute(\"aria-label\", label);\n el.textContent = glyph;\n // mousedown would move the selection into the widget before the click lands.\n el.addEventListener(\"mousedown\", (e) => e.preventDefault());\n el.addEventListener(\"click\", (e) => {\n e.preventDefault();\n onClick();\n });\n return el;\n}\n","export interface TextRange {\n from: number;\n to: number;\n}\n\nexport interface RevisionPart {\n before: string;\n after: string;\n}\n\n// Letters, digits, and the marks that belong inside a French word.\nconst WORD = /[\\p{L}\\p{N}'’-]/u;\n\n/**\n * expandToWords grows a range out to whole words.\n *\n * A selection dragged across a line lands mid-word more often than not, and a\n * model handed \"ns de production\" answers for a fragment — which then reads as\n * a truncated diff the author has to decode.\n */\nexport function expandToWords(text: string, from: number, to: number): TextRange {\n if (from >= to) return { from, to };\n\n let start = from;\n while (start > 0 && WORD.test(text[start - 1]) && WORD.test(text[start])) start--;\n\n let end = to;\n while (end < text.length && WORD.test(text[end]) && WORD.test(text[end - 1])) end++;\n\n return { from: start, to: end };\n}\n\n/**\n * splitRevision turns one model answer into the decisions it really contains.\n *\n * A three-paragraph rewrite accepted or rejected as a whole forces the author\n * to keep a weak paragraph for a strong one, so each paragraph becomes its own\n * decision — but only when both sides line up. When the model merged or split\n * paragraphs, position no longer maps them, and pairing by index would show a\n * diff that never existed.\n */\nexport function splitRevision(before: string, after: string): RevisionPart[] {\n if (before.trim() === after.trim()) return [];\n\n const olds = paragraphs(before);\n const news = paragraphs(after);\n\n if (olds.length !== news.length || olds.length === 0) {\n return [{ before: before.trim(), after: after.trim() }];\n }\n\n const parts: RevisionPart[] = [];\n for (let i = 0; i < olds.length; i++) {\n if (olds[i] !== news[i]) parts.push({ before: olds[i], after: news[i] });\n }\n return parts;\n}\n\nfunction paragraphs(s: string): string[] {\n return s\n .split(/\\n\\s*\\n/)\n .map((p) => p.trim())\n .filter((p) => p !== \"\");\n}\n","import type { RichDoc, RichNode } from \"./document\";\n\ninterface Mark {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * docToMarkdown renders a TipTap document as the markdown a blog accepts.\n *\n * Written here rather than taken from a library: the editor only ever produces\n * the handful of nodes StarterKit gives it, and a general markdown serializer\n * brings a dependency and its escaping rules for output a human is about to\n * paste into their own site.\n */\nexport function docToMarkdown(doc: RichDoc): string {\n return blocks(doc.content ?? [])\n .filter((b) => b !== \"\")\n .join(\"\\n\\n\");\n}\n\nfunction blocks(nodes: RichNode[]): string[] {\n return nodes.map(block);\n}\n\nfunction block(node: RichNode): string {\n switch (node.type) {\n case \"heading\": {\n const level = Number(node.attrs?.level ?? 2);\n return `${\"#\".repeat(Math.min(6, Math.max(1, level)))} ${inline(node.content)}`;\n }\n case \"blockquote\":\n return blocks(node.content ?? [])\n .filter((b) => b !== \"\")\n .map((b) =>\n b\n .split(\"\\n\")\n .map((line) => `> ${line}`)\n .join(\"\\n\"),\n )\n .join(\"\\n>\\n\");\n case \"codeBlock\": {\n const language = String(node.attrs?.language ?? \"\");\n return `\\`\\`\\`${language}\\n${inline(node.content)}\\n\\`\\`\\``;\n }\n case \"bulletList\":\n return listItems(node).map((item) => `- ${item}`).join(\"\\n\");\n case \"orderedList\":\n return listItems(node)\n .map((item, i) => `${i + 1}. ${item}`)\n .join(\"\\n\");\n case \"horizontalRule\":\n return \"---\";\n default:\n return inline(node.content);\n }\n}\n\nfunction listItems(list: RichNode): string[] {\n return (list.content ?? []).map((item) =>\n blocks(item.content ?? [])\n .filter((b) => b !== \"\")\n // Continuation lines are indented so the item stays one item.\n .join(\"\\n\\n\")\n .split(\"\\n\")\n .join(\"\\n \"),\n );\n}\n\nfunction inline(nodes: RichNode[] | undefined): string {\n return (nodes ?? []).map(text).join(\"\");\n}\n\nfunction text(node: RichNode): string {\n if (node.type === \"hardBreak\") return \"\\n\";\n if (typeof node.text !== \"string\") return inline(node.content);\n\n let out = node.text;\n // Innermost first, so bold inside a link wraps the text and not the URL.\n for (const mark of ((node.marks as Mark[] | undefined) ?? []).slice().reverse()) {\n switch (mark.type) {\n case \"bold\":\n out = `**${out}**`;\n break;\n case \"italic\":\n out = `*${out}*`;\n break;\n case \"strike\":\n out = `~~${out}~~`;\n break;\n case \"code\":\n out = `\\`${out}\\``;\n break;\n case \"link\":\n out = `[${out}](${String(mark.attrs?.href ?? \"\")})`;\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useEffect","useState","read","text","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","SlashMenu","Extension","PluginKey","text","text"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalternative/editor",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Shared TipTap editor for L'Alternative apps: rich text, format menu, selection toolbar and inline AI revisions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",