@sobree/block-tools 0.1.8 → 0.1.10
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.js +287 -336
- package/dist/index.js.map +1 -1
- package/dist/indicator.d.ts +1 -1
- package/dist/toolbar.d.ts +1 -1
- package/dist/tools/changeType.d.ts +1 -1
- package/dist/tools/perKind.d.ts +1 -1
- package/dist/tools/table.d.ts +1 -1
- package/dist/tools/text.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/blockKinds.ts","../src/indicator.ts","../src/toolbar.ts","../src/tools/icons.ts","../src/tools/changeType.ts","../src/tools/selectionState.ts","../src/tools/perKind.ts","../src/tools/table.ts","../src/tools/text.ts","../src/tools/pageSetup.ts","../src/blockTools.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Block-kind identification + per-kind metadata for the block indicator\n * and toolbar. Inline Lucide SVG (2px stroke) matches the design system;\n * icons are monochrome via `currentColor` so they inherit the indicator\n * state colour.\n */\n\nexport type BlockKind =\n | \"paragraph\"\n | \"heading\"\n | \"list\"\n | \"listOrdered\"\n | \"blockquote\"\n | \"table\"\n | \"image\"\n | \"header\"\n | \"footer\"\n | \"sectionBreak\";\n\nexport interface BlockKindInfo {\n kind: BlockKind;\n /** Short label shown in tooltips and the toolbar header. */\n label: string;\n /** Inline SVG body — `<svg>` wrapper is added by the indicator. */\n iconPath: string;\n}\n\nconst SVG_PARAGRAPH = `<path d=\"M13 4v16\"/><path d=\"M17 4v16\"/><path d=\"M19 4H9.5a4.5 4.5 0 0 0 0 9H13\"/>`;\nconst SVG_HEADING = `<path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"m17 12 3-2v8\"/>`;\nconst SVG_LIST = `<path d=\"M3 12h.01\"/><path d=\"M3 18h.01\"/><path d=\"M3 6h.01\"/><path d=\"M8 12h13\"/><path d=\"M8 18h13\"/><path d=\"M8 6h13\"/>`;\nconst SVG_LIST_ORDERED = `<path d=\"M10 12h11\"/><path d=\"M10 18h11\"/><path d=\"M10 6h11\"/><path d=\"M4 10h2\"/><path d=\"M4 6h1v4\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>`;\nconst SVG_QUOTE = `<path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/><path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/>`;\nconst SVG_TABLE = `<path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/>`;\nconst SVG_IMAGE = `<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>`;\nconst SVG_CHEVRON_UP = `<path d=\"m18 15-6-6-6 6\"/>`;\nconst SVG_CHEVRON_DOWN = `<path d=\"m6 9 6 6 6-6\"/>`;\n// Lucide \"separator-horizontal\".\nconst SVG_SECTION_BREAK = `<path d=\"M3 12h18\"/><path d=\"m8 8 4-4 4 4\"/><path d=\"m16 16-4 4-4-4\"/>`;\n\nexport const BLOCK_KINDS: Record<BlockKind, BlockKindInfo> = {\n paragraph: { kind: \"paragraph\", label: \"Paragraph\", iconPath: SVG_PARAGRAPH },\n heading: { kind: \"heading\", label: \"Heading\", iconPath: SVG_HEADING },\n list: { kind: \"list\", label: \"Bullet list\", iconPath: SVG_LIST },\n listOrdered: { kind: \"listOrdered\", label: \"Numbered list\", iconPath: SVG_LIST_ORDERED },\n blockquote: { kind: \"blockquote\", label: \"Quote\", iconPath: SVG_QUOTE },\n table: { kind: \"table\", label: \"Table\", iconPath: SVG_TABLE },\n image: { kind: \"image\", label: \"Image\", iconPath: SVG_IMAGE },\n header: { kind: \"header\", label: \"Header\", iconPath: SVG_CHEVRON_UP },\n footer: { kind: \"footer\", label: \"Footer\", iconPath: SVG_CHEVRON_DOWN },\n sectionBreak: { kind: \"sectionBreak\", label: \"Section break\", iconPath: SVG_SECTION_BREAK },\n};\n\nexport function iconSvg(info: BlockKindInfo): string {\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${info.iconPath}</svg>`;\n}\n\n/** A tracked block target for indicator / toolbar positioning. */\nexport interface BlockTarget {\n kind: BlockKind;\n /** DOM element representing the block — used for positioning. */\n element: HTMLElement;\n /** Enclosing `.paper` element. */\n paper: HTMLElement;\n /**\n * Stable block id (registry) if available — stamped by the renderer as\n * `data-block-id`. Lets the toolbar re-resolve the DOM element after\n * a commit rebuilds the body (header/footer zones have no id).\n */\n blockId?: string;\n}\n\n/**\n * Walk from a DOM node up to find its containing block inside the\n * paper stack. Returns `null` if the node is outside the stack.\n */\nexport function blockTargetFrom(node: Node, stackRoot: HTMLElement): BlockTarget | null {\n if (!stackRoot.contains(node)) return null;\n const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : (node as HTMLElement);\n if (!el) return null;\n\n const paper = el.closest(\".paper\") as HTMLElement | null;\n if (!paper) return null;\n\n const header = el.closest(\".paper-header\") as HTMLElement | null;\n if (header && paper.contains(header)) return { kind: \"header\", element: header, paper };\n\n const footer = el.closest(\".paper-footer\") as HTMLElement | null;\n if (footer && paper.contains(footer)) return { kind: \"footer\", element: footer, paper };\n\n const image = el.closest(\"img\") as HTMLElement | null;\n if (image && paper.contains(image)) {\n // Walk up to the enclosing block element to keep the indicator at\n // block-left rather than image-left.\n const host = image.closest(\"p, h1, h2, h3, h4, h5, h6, li, blockquote\") as HTMLElement | null;\n if (host) return withBlockId({ kind: \"image\", element: host, paper });\n }\n\n const sectionBreak = el.closest(\".sobree-section-break\") as HTMLElement | null;\n if (sectionBreak && paper.contains(sectionBreak)) {\n return withBlockId({ kind: \"sectionBreak\", element: sectionBreak, paper });\n }\n\n const table = el.closest(\"table\") as HTMLElement | null;\n if (table && paper.contains(table)) return withBlockId({ kind: \"table\", element: table, paper });\n\n const heading = el.closest(\"h1, h2, h3, h4, h5, h6\") as HTMLElement | null;\n if (heading && paper.contains(heading)) return withBlockId({ kind: \"heading\", element: heading, paper });\n\n const bq = el.closest(\"blockquote\") as HTMLElement | null;\n if (bq && paper.contains(bq)) return withBlockId({ kind: \"blockquote\", element: bq, paper });\n\n const li = el.closest(\"li\") as HTMLElement | null;\n if (li && paper.contains(li)) {\n const parent = li.parentElement;\n const kind: BlockKind = parent?.tagName.toLowerCase() === \"ol\" ? \"listOrdered\" : \"list\";\n return withBlockId({ kind, element: li, paper });\n }\n\n const p = el.closest(\"p\") as HTMLElement | null;\n if (p && paper.contains(p)) return withBlockId({ kind: \"paragraph\", element: p, paper });\n\n return null;\n}\n\nfunction withBlockId(t: BlockTarget): BlockTarget {\n const id = t.element.dataset.blockId;\n if (id) t.blockId = id;\n return t;\n}\n\n/** Same lookup by arbitrary `Node` (handles TEXT_NODE). */\nexport function blockTargetFromNode(\n node: Node | null,\n stackRoot: HTMLElement,\n): BlockTarget | null {\n if (!node) return null;\n return blockTargetFrom(node, stackRoot);\n}\n","import {\n BLOCK_KINDS,\n type BlockTarget,\n blockTargetFrom,\n blockTargetFromNode,\n iconSvg,\n} from \"./blockKinds\";\nimport type { Editor } from \"@sobree/core\";\n\nexport interface IndicatorOptions {\n stackRoot: HTMLElement;\n /** Editor instance — used for the `selection` event subscription so\n * the indicator doesn't have to listen to the global document event. */\n editor: Editor;\n /** Fires when the user clicks the indicator (Esc also triggers this). */\n onActivate: (target: BlockTarget) => void;\n}\n\n/**\n * Single floating indicator pinned to the left edge of the paper, at the\n * vertical offset of whatever block is currently being hovered or has\n * the selection / caret.\n *\n * Not one-per-block — one element that moves. Matches Sobree's \"quiet\n * gutter\" rule: the indicator shows the current block, nothing more.\n */\nexport class BlockIndicator {\n private readonly stackRoot: HTMLElement;\n private readonly onActivate: (target: BlockTarget) => void;\n private readonly root: HTMLButtonElement;\n private current: BlockTarget | null = null;\n private enabled = true;\n\n private readonly onHoverFn = (e: MouseEvent) => this.handleHover(e);\n private readonly onStackLeaveFn = () => this.maybeHide();\n private readonly onKeyFn = (e: KeyboardEvent) => this.handleKey(e);\n private readonly detachSelection: () => void;\n\n constructor(opts: IndicatorOptions) {\n this.stackRoot = opts.stackRoot;\n this.onActivate = opts.onActivate;\n\n this.root = document.createElement(\"button\");\n this.root.type = \"button\";\n this.root.className = \"sobree-block-indicator\";\n this.root.contentEditable = \"false\";\n this.root.setAttribute(\"aria-label\", \"Open block tools\");\n // The indicator opens the floating toolbar — a dialog-like surface\n // from an a11y standpoint (anchored, dismissible, contains controls).\n this.root.setAttribute(\"aria-haspopup\", \"dialog\");\n this.root.setAttribute(\"aria-expanded\", \"false\");\n\n // The stack needs to be a positioning context so the indicator follows\n // its paper-space coordinates through any viewport transforms.\n if (!this.stackRoot.style.position) this.stackRoot.style.position = \"relative\";\n this.stackRoot.appendChild(this.root);\n\n // Clicks on the indicator must not move the caret.\n this.root.addEventListener(\"mousedown\", (e) => e.preventDefault());\n this.root.addEventListener(\"click\", (e) => {\n e.preventDefault();\n if (this.current) this.onActivate(this.current);\n });\n\n this.stackRoot.addEventListener(\"mousemove\", this.onHoverFn);\n this.stackRoot.addEventListener(\"mouseleave\", this.onStackLeaveFn);\n this.detachSelection = opts.editor.on(\"selection\", () => this.handleSelectionChange());\n document.addEventListener(\"keydown\", this.onKeyFn);\n }\n\n destroy(): void {\n this.stackRoot.removeEventListener(\"mousemove\", this.onHoverFn);\n this.stackRoot.removeEventListener(\"mouseleave\", this.onStackLeaveFn);\n this.detachSelection();\n document.removeEventListener(\"keydown\", this.onKeyFn);\n this.root.remove();\n }\n\n /** Currently-tracked block, or `null` if the indicator is hidden. */\n getCurrent(): BlockTarget | null {\n return this.current;\n }\n\n /** Visually flag the indicator as \"active\" (toolbar is open for it). */\n setActive(active: boolean): void {\n this.root.classList.toggle(\"is-active\", active);\n this.root.setAttribute(\"aria-expanded\", String(active));\n }\n\n /**\n * Enable or disable the indicator entirely. Disabled = hidden and\n * inert (hover / selection / Esc do nothing). Used to suspend the\n * block UI while Sobree is in read mode.\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n if (!enabled) {\n this.current = null;\n this.root.classList.remove(\"is-visible\", \"is-active\");\n }\n }\n\n /** Recompute position. Call after pagination or viewport zoom changes. */\n refresh(): void {\n if (!this.current) return;\n // Re-resolve element if the body was re-rendered under us.\n if (this.current.blockId && !document.contains(this.current.element)) {\n const fresh = this.stackRoot.querySelector(\n `[data-block-id=\"${this.current.blockId}\"]`,\n ) as HTMLElement | null;\n if (fresh) {\n const paper = fresh.closest(\".paper\") as HTMLElement | null;\n if (paper) this.current = { ...this.current, element: fresh, paper };\n } else {\n // Block was deleted — hide.\n this.current = null;\n this.root.classList.remove(\"is-visible\", \"is-active\");\n return;\n }\n }\n this.position(this.current);\n }\n\n // ---- handlers ----\n\n private handleHover(e: MouseEvent): void {\n if (!this.enabled) return;\n const target = e.target as HTMLElement;\n if (this.root.contains(target)) return;\n const block = blockTargetFrom(target, this.stackRoot);\n if (block) this.setTarget(block);\n }\n\n private handleSelectionChange(): void {\n if (!this.enabled) return;\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const anchor = sel.anchorNode;\n if (!anchor) return;\n if (!this.stackRoot.contains(anchor)) return;\n const block = blockTargetFromNode(anchor, this.stackRoot);\n if (block) this.setTarget(block);\n }\n\n private handleKey(e: KeyboardEvent): void {\n if (!this.enabled) return;\n if (e.key !== \"Escape\") return;\n if (!this.current) return;\n // Esc on a visible indicator → treat as if the user clicked it.\n // The orchestrator's `onActivate` handler toggles the toolbar.\n this.onActivate(this.current);\n }\n\n private maybeHide(): void {\n // Only fade out when the mouse leaves the stack. Keep the indicator\n // visible when it's pinned by a selection.\n const sel = window.getSelection();\n const hasSelInStack =\n sel && sel.rangeCount > 0 && sel.anchorNode && this.stackRoot.contains(sel.anchorNode);\n if (hasSelInStack) return;\n this.current = null;\n this.root.classList.remove(\"is-visible\");\n }\n\n private setTarget(target: BlockTarget): void {\n const same =\n this.current?.element === target.element && this.current.kind === target.kind;\n if (same) return;\n this.current = target;\n this.root.dataset.kind = target.kind;\n this.root.title = BLOCK_KINDS[target.kind].label;\n this.root.innerHTML = iconSvg(BLOCK_KINDS[target.kind]);\n this.root.classList.add(\"is-visible\");\n this.position(target);\n }\n\n private position(target: BlockTarget): void {\n const scale = this.effectiveScale();\n const stackRect = this.stackRoot.getBoundingClientRect();\n const paperRect = target.paper.getBoundingClientRect();\n const blockRect = target.element.getBoundingClientRect();\n const left = (paperRect.left - stackRect.left) / scale;\n const top = (blockRect.top - stackRect.top) / scale;\n this.root.style.left = `${left}px`;\n this.root.style.top = `${top}px`;\n }\n\n /** Effective CSS-pixel scale applied by any viewport transform ancestor. */\n private effectiveScale(): number {\n if (this.stackRoot.offsetWidth === 0) return 1;\n return this.stackRoot.getBoundingClientRect().width / this.stackRoot.offsetWidth;\n }\n}\n","import type { BlockTarget } from \"./blockKinds\";\nimport type { Viewport } from \"@sobree/core\";\n\nexport interface FloatingToolbarOptions {\n /**\n * Stack root — used to re-resolve the target element by `data-block-id`\n * after a commit replaces the body DOM.\n */\n stackRoot: HTMLElement;\n /**\n * Rendering area — the element inside which the toolbar must fit.\n * Typically the demo viewport (`.demo-viewport`). Used for Case-A\n * stick-to-top and Case-C panning calculations.\n */\n renderingArea: HTMLElement;\n /** Optional viewport handle — used for the animated pan in Case C. */\n viewport?: Viewport | null;\n}\n\n/**\n * Floating toolbar shell. Positions itself above the active block\n * according to the three rules:\n * A. Block is taller than the visible area → toolbar sticks to the\n * top of the rendering area and floats over the block.\n * B. Block fits and there's room above → toolbar sits 8px above it.\n * C. Block fits but toolbar would clip outside the rendering area →\n * animate a viewport pan so the toolbar nestles between block-top\n * and rendering-area-top.\n *\n * B2 scope: shell only. Contents are set via `setContent(html)` from\n * whatever tool module owns the current block kind; wiring those up is\n * B3–B5.\n */\nexport class FloatingToolbar {\n readonly root: HTMLElement;\n private readonly stackRoot: HTMLElement;\n private readonly renderingArea: HTMLElement;\n private readonly viewport: Viewport | null;\n private target: BlockTarget | null = null;\n private readonly onScrollOrResizeFn = () => this.reposition();\n\n constructor(opts: FloatingToolbarOptions) {\n this.stackRoot = opts.stackRoot;\n this.renderingArea = opts.renderingArea;\n this.viewport = opts.viewport ?? null;\n\n this.root = document.createElement(\"div\");\n this.root.className = \"sobree-block-toolbar\";\n this.root.contentEditable = \"false\";\n this.root.setAttribute(\"role\", \"toolbar\");\n this.root.setAttribute(\"aria-label\", \"Block tools\");\n // Wraps onto a second line on narrow screens (CSS handles the wrap),\n // but the SR announcement still treats children as a single\n // horizontal cluster — that matches the visual mental model better\n // than `vertical` would after a wrap.\n this.root.setAttribute(\"aria-orientation\", \"horizontal\");\n // Appended to body (not stack) so it floats at viewport coordinates\n // and isn't affected by the stack's zoom transform.\n document.body.appendChild(this.root);\n\n // Mousedown inside the toolbar mustn't steal caret focus.\n this.root.addEventListener(\"mousedown\", (e) => {\n const target = e.target as HTMLElement;\n // Inputs and selects still need focus — let them get it.\n if (target.closest(\"input, select, textarea, [contenteditable=true]\")) return;\n e.preventDefault();\n });\n\n this.renderingArea.addEventListener(\"scroll\", this.onScrollOrResizeFn, {\n passive: true,\n });\n window.addEventListener(\"resize\", this.onScrollOrResizeFn);\n }\n\n destroy(): void {\n this.renderingArea.removeEventListener(\"scroll\", this.onScrollOrResizeFn);\n window.removeEventListener(\"resize\", this.onScrollOrResizeFn);\n this.root.remove();\n }\n\n /** Currently-targeted block, if the toolbar is open. */\n getTarget(): BlockTarget | null {\n return this.target;\n }\n\n /** Whether the toolbar is currently open. */\n isOpen(): boolean {\n return this.root.classList.contains(\"is-open\");\n }\n\n /** Replace the toolbar's inner HTML. Callers own the event wiring. */\n setContent(html: string): void {\n this.root.innerHTML = html;\n }\n\n /** Listen for clicks inside the toolbar (delegated). Returns an unsubscribe. */\n onClick(handler: (e: MouseEvent) => void): () => void {\n this.root.addEventListener(\"click\", handler);\n return () => this.root.removeEventListener(\"click\", handler);\n }\n\n /** Listen for input/change on form controls inside the toolbar. */\n onInput(handler: (e: Event) => void): () => void {\n this.root.addEventListener(\"input\", handler);\n this.root.addEventListener(\"change\", handler);\n return () => {\n this.root.removeEventListener(\"input\", handler);\n this.root.removeEventListener(\"change\", handler);\n };\n }\n\n /** Open the toolbar for `target`. Triggers positioning + panning as needed. */\n open(target: BlockTarget): void {\n this.target = target;\n this.root.dataset.kind = target.kind;\n this.root.classList.add(\"is-open\");\n // Pan-then-reposition so the final resting position is inside the\n // rendering area whenever possible.\n this.maybePanIntoView(target);\n this.reposition();\n }\n\n close(): void {\n this.target = null;\n this.root.classList.remove(\"is-open\");\n this.root.classList.remove(\"is-stuck\");\n this.root.removeAttribute(\"data-kind\");\n }\n\n toggle(target: BlockTarget): void {\n if (this.isOpen() && this.target?.element === target.element) this.close();\n else this.open(target);\n }\n\n /** Recompute positioning — call after scroll, zoom, or pagination. */\n reposition(): void {\n if (!this.target) return;\n // Re-resolve the target element if it was detached by a commit that\n // rebuilt the body DOM. `data-block-id` is stamped by the renderer.\n if (this.target.blockId && !document.contains(this.target.element)) {\n const fresh = this.stackRoot.querySelector(\n `[data-block-id=\"${this.target.blockId}\"]`,\n ) as HTMLElement | null;\n if (fresh) {\n const paper = fresh.closest(\".paper\") as HTMLElement | null;\n if (paper) this.target = { ...this.target, element: fresh, paper };\n } else {\n // Block was deleted — nothing to anchor to.\n this.close();\n return;\n }\n }\n const rendArea = this.renderingArea.getBoundingClientRect();\n // Cap width to the rendering area so the toolbar wraps to a second\n // line when its contents would otherwise overflow. Do this BEFORE\n // reading the block rect so the offsetHeight reflects the wrapped\n // (possibly two-line) layout.\n const margin = 8;\n const maxWidth = Math.max(240, rendArea.width - margin * 2);\n this.root.style.maxWidth = `${maxWidth}px`;\n const blockRect = this.target.element.getBoundingClientRect();\n const tbHeight = this.root.offsetHeight || 40;\n\n // Case A — block taller than the visible area: stick to the top of\n // the rendering area. Re-evaluated on every call so scrolling a\n // medium block into \"tall relative to what's showing\" flips it.\n const blockTallerThanView = blockRect.height > rendArea.height;\n // Also stick when the block's top has scrolled above the rendering\n // area — the user is inside a large block that spills upward.\n const blockTopAboveView = blockRect.top < rendArea.top;\n const stickToTop = blockTallerThanView || blockTopAboveView;\n\n let top: number;\n let left = Math.max(\n rendArea.left + margin,\n Math.min(blockRect.left, rendArea.right - this.root.offsetWidth - margin),\n );\n\n if (stickToTop) {\n top = rendArea.top + margin;\n } else {\n top = blockRect.top - tbHeight - margin;\n // Case B: natural fit. We already panned into view in `open`.\n // If after scrolling the toolbar ends up above the rendering\n // area, clamp.\n if (top < rendArea.top + margin) top = rendArea.top + margin;\n }\n\n // Clamp to window horizontally as a final safety net.\n left = Math.max(\n 0,\n Math.min(left, window.innerWidth - this.root.offsetWidth),\n );\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n this.root.classList.toggle(\"is-stuck\", stickToTop);\n }\n\n /**\n * Case C: if the block is small but the toolbar wouldn't fit above it\n * within the rendering area, animate the viewport so the block shifts\n * down by `toolbarHeight + margin`.\n */\n private maybePanIntoView(target: BlockTarget): void {\n if (!this.viewport) return;\n const rendArea = this.renderingArea.getBoundingClientRect();\n const blockRect = target.element.getBoundingClientRect();\n const tbHeight = this.root.offsetHeight || 40;\n const margin = 8;\n\n // If the block is too tall, we'll stick-to-top instead; no pan.\n if (blockRect.height > rendArea.height) return;\n // If there's already room above, nothing to do.\n const roomAbove = blockRect.top - rendArea.top;\n const needed = tbHeight + margin * 2;\n if (roomAbove >= needed) return;\n // Also skip if the block top is already above the rendering area —\n // we'll stick-to-top, not pan.\n if (blockRect.top < rendArea.top) return;\n\n const deficit = needed - roomAbove;\n this.viewport.panBy(0, -deficit, { animate: true });\n }\n}\n","/**\n * Inline Lucide SVG icons used by toolbar buttons. All 2px-stroke,\n * 24×24 viewBox, rendered at 16×16 via the wrapper. Icons inherit\n * `currentColor` so button state colours flow through.\n */\n\nconst BOLD = `<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>`;\nconst ITALIC = `<line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/>`;\nconst UNDERLINE = `<path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" y1=\"20\" x2=\"20\" y2=\"20\"/>`;\nconst STRIKE = `<path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/>`;\nconst SUPERSCRIPT = `<path d=\"m4 19 8-8\"/><path d=\"m12 19-8-8\"/><path d=\"M20 9c0-1.667-.667-2.5-2-2.5-.833 0-1.5.333-2 1 .5-1 1.5-1.5 2.5-1.5 1 0 2.5.5 2.5 2 0 1.667-1.667 2-2.5 3h2.5\"/>`;\nconst SUBSCRIPT = `<path d=\"m4 5 8 8\"/><path d=\"m12 5-8 8\"/><path d=\"M20 20c0-1.667-.667-2.5-2-2.5-.833 0-1.5.333-2 1 .5-1 1.5-1.5 2.5-1.5 1 0 2.5.5 2.5 2 0 1.667-1.667 2-2.5 3h2.5\"/>`;\nconst TYPE = `<polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/>`;\nconst PAINTBRUSH = `<path d=\"m14.622 17.897-10.68-2.913\"/><path d=\"M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z\"/><path d=\"M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15\"/>`;\nconst HIGHLIGHTER = `<path d=\"m9 11-6 6v3h9l3-3\"/><path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\"/>`;\nconst ERASER = `<path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\"/><path d=\"M22 21H7\"/><path d=\"m5 11 9 9\"/>`;\nconst CODE = `<polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/>`;\nconst ALIGN_LEFT = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"17\" y1=\"18\" x2=\"3\" y2=\"18\"/>`;\nconst ALIGN_CENTER = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"17\" y1=\"12\" x2=\"7\" y2=\"12\"/><line x1=\"19\" y1=\"18\" x2=\"5\" y2=\"18\"/>`;\nconst ALIGN_RIGHT = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/><line x1=\"21\" y1=\"18\" x2=\"7\" y2=\"18\"/>`;\nconst ALIGN_JUSTIFY = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"18\" x2=\"3\" y2=\"18\"/>`;\nconst INDENT_INCREASE = `<polyline points=\"3 8 7 12 3 16\"/><line x1=\"21\" y1=\"12\" x2=\"11\" y2=\"12\"/><line x1=\"21\" y1=\"6\" x2=\"11\" y2=\"6\"/><line x1=\"21\" y1=\"18\" x2=\"11\" y2=\"18\"/>`;\nconst INDENT_DECREASE = `<polyline points=\"7 8 3 12 7 16\"/><line x1=\"21\" y1=\"12\" x2=\"11\" y2=\"12\"/><line x1=\"21\" y1=\"6\" x2=\"11\" y2=\"6\"/><line x1=\"21\" y1=\"18\" x2=\"11\" y2=\"18\"/>`;\nconst LIST_BULLET = `<line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/>`;\nconst LIST_NUMBERED = `<line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>`;\nconst TRASH = `<polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>`;\nconst CHEVRON_DOWN_ICON = `<polyline points=\"6 9 12 15 18 9\"/>`;\n// Lucide \"sliders-horizontal\" — reads as \"settings / page setup\" without\n// the over-loaded gear iconography.\nconst SETTINGS = `<line x1=\"21\" y1=\"4\" x2=\"14\" y2=\"4\"/><line x1=\"10\" y1=\"4\" x2=\"3\" y2=\"4\"/><line x1=\"21\" y1=\"12\" x2=\"12\" y2=\"12\"/><line x1=\"8\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"20\" x2=\"16\" y2=\"20\"/><line x1=\"12\" y1=\"20\" x2=\"3\" y2=\"20\"/><line x1=\"14\" y1=\"2\" x2=\"14\" y2=\"6\"/><line x1=\"8\" y1=\"10\" x2=\"8\" y2=\"14\"/><line x1=\"16\" y1=\"18\" x2=\"16\" y2=\"22\"/>`;\n// Page outline with inner margin guides — reads unambiguously as\n// \"page setup\". Outer rectangle = paper edge; inner = type area.\nconst PAGE_SETUP = `<rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"/><rect x=\"7\" y=\"6\" width=\"10\" height=\"12\" rx=\"0.5\" stroke-dasharray=\"1.5 1.5\" opacity=\"0.7\"/>`;\n// Horizontal dashed divider with a short solid stroke each side —\n// reads as \"section break between two regions\".\nconst SECTION_BREAK = `<path d=\"M3 7h18\"/><path d=\"M3 17h18\"/><path d=\"M3 12h4\"/><path d=\"M11 12h2\"/><path d=\"M17 12h4\"/>`;\n// Pencil with a small underline-dot \"edit-mark\" — reads as \"track edits\".\n// The active (pressed) state of the button is what really tells the user\n// it's on; the glyph itself is the same in both states.\nconst TRACK_CHANGES = `<path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4z\"/><circle cx=\"5.5\" cy=\"18.5\" r=\"0.5\" fill=\"currentColor\"/>`;\n\nexport const ICONS: Record<string, string> = {\n bold: BOLD,\n italic: ITALIC,\n underline: UNDERLINE,\n strike: STRIKE,\n superscript: SUPERSCRIPT,\n subscript: SUBSCRIPT,\n type: TYPE,\n paintbrush: PAINTBRUSH,\n highlighter: HIGHLIGHTER,\n eraser: ERASER,\n code: CODE,\n \"align-left\": ALIGN_LEFT,\n \"align-center\": ALIGN_CENTER,\n \"align-right\": ALIGN_RIGHT,\n \"align-justify\": ALIGN_JUSTIFY,\n \"indent-increase\": INDENT_INCREASE,\n \"indent-decrease\": INDENT_DECREASE,\n \"list-bullet\": LIST_BULLET,\n \"list-numbered\": LIST_NUMBERED,\n trash: TRASH,\n \"chevron-down\": CHEVRON_DOWN_ICON,\n settings: SETTINGS,\n \"page-setup\": PAGE_SETUP,\n \"section-break\": SECTION_BREAK,\n \"track-changes\": TRACK_CHANGES,\n};\n\n/** Render an inline SVG for a named icon, sized 16×16. */\nexport function icon(name: keyof typeof ICONS): string {\n const path = ICONS[name] ?? \"\";\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${path}</svg>`;\n}\n","import type { BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\nimport type { BlockRef } from \"@sobree/core\";\nimport type {\n Block,\n NumberingDefinition,\n Paragraph,\n SobreeDocument,\n Table,\n} from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\n\nexport interface ChangeTypeContext {\n editor: Editor;\n target: BlockTarget;\n /** Blocks the operation should apply to (1 for single, N for multi). */\n refs: BlockRef[];\n}\n\n/**\n * Drill-down target kinds. Kept deliberately short — exotic targets\n * (images, nested tables) land as \"convert current to\" with a caveat\n * that the existing content is wrapped into the new structure.\n */\ntype TargetKind =\n | { kind: \"paragraph\" }\n | { kind: \"heading\"; level: 1 | 2 | 3 | 4 | 5 | 6 }\n | { kind: \"quote\" }\n | { kind: \"bullet\" }\n | { kind: \"ordered\" }\n | { kind: \"table\" }\n | { kind: \"section_break\" };\n\n/**\n * Build the \"Change block type\" trigger — rendered at the end of the\n * primary toolbar row. Label adapts to single vs multi-block.\n */\nexport function buildChangeTypeButton(refCount: number): string {\n const label = refCount > 1 ? `Convert ${refCount} blocks` : \"Change block\";\n return `\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"change-type\">\n <button type=\"button\" class=\"tb-change-btn\" data-action=\"open-change-type\"\n title=\"${label}\" aria-label=\"${label}\"\n aria-haspopup=\"menu\" aria-expanded=\"false\">\n ${icon(\"chevron-down\")} ${label}\n </button>\n </div>\n `;\n}\n\n/**\n * Open a popover next to the trigger button with target-kind options.\n * Uses the same `FloatingToolbar` element as an anchor reference so the\n * popover sits just below it.\n *\n * Returns a `close()` handle so callers can dismiss the popover when\n * the toolbar closes.\n */\nexport function openChangeTypePopover(\n trigger: HTMLElement,\n ctx: ChangeTypeContext,\n onClose: () => void,\n): () => void {\n const currentKey = currentTargetKey(ctx);\n const popover = document.createElement(\"div\");\n popover.className = \"sobree-change-popover\";\n popover.setAttribute(\"role\", \"menu\");\n popover.setAttribute(\"aria-label\", \"Change block type\");\n popover.tabIndex = -1; // accept focus so keyboard navigation works\n popover.innerHTML = buildPopoverHtml(ctx.refs.length, currentKey);\n document.body.appendChild(popover);\n\n // Position just below the trigger, aligned to its right edge so the\n // menu opens into the free space to the right of the button.\n const triggerRect = trigger.getBoundingClientRect();\n popover.style.top = `${triggerRect.bottom + 6}px`;\n popover.style.left = `${Math.max(8, triggerRect.left)}px`;\n const items = () =>\n Array.from(\n popover.querySelectorAll<HTMLButtonElement>(\n 'button[role=\"menuitem\"], button[role=\"menuitemradio\"]',\n ),\n );\n\n // Next tick: fade in + focus the first item so Down/Enter work right\n // away from a keyboard.\n requestAnimationFrame(() => {\n popover.classList.add(\"is-open\");\n items()[0]?.focus();\n });\n\n const close = () => {\n popover.classList.remove(\"is-open\");\n // Let the fade run before removing.\n window.setTimeout(() => popover.remove(), 180);\n document.removeEventListener(\"mousedown\", onDocDown, true);\n onClose();\n };\n\n const onDocDown = (e: MouseEvent) => {\n if (popover.contains(e.target as Node)) return;\n if (trigger.contains(e.target as Node)) return;\n close();\n };\n\n popover.addEventListener(\"click\", (e) => {\n const item = (e.target as HTMLElement).closest(\"[data-target-kind]\");\n if (!item) return;\n const raw = item.getAttribute(\"data-target-kind\");\n if (!raw) return;\n const target = parseTarget(raw);\n if (target) applyTarget(ctx, target);\n close();\n });\n\n // Standard menu keyboard model — ArrowDown/Up move between items,\n // Home/End jump to the ends, Enter activates, Esc closes.\n popover.addEventListener(\"keydown\", (e) => {\n const all = items();\n const idx = all.indexOf(document.activeElement as HTMLButtonElement);\n if (e.key === \"Escape\") {\n e.preventDefault();\n close();\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n all[(idx + 1) % all.length]?.focus();\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n all[(idx - 1 + all.length) % all.length]?.focus();\n return;\n }\n if (e.key === \"Home\") {\n e.preventDefault();\n all[0]?.focus();\n return;\n }\n if (e.key === \"End\") {\n e.preventDefault();\n all[all.length - 1]?.focus();\n return;\n }\n });\n\n document.addEventListener(\"mousedown\", onDocDown, true);\n\n return close;\n}\n\nfunction buildPopoverHtml(refCount: number, currentKey: string | null): string {\n const multi = refCount > 1;\n const item = (key: string, label: string): string => {\n const isCurrent = !multi && currentKey === key;\n const cls = isCurrent ? ' class=\"is-current\"' : \"\";\n const aria = isCurrent ? ' aria-checked=\"true\"' : ' aria-checked=\"false\"';\n return `<button type=\"button\" role=\"menuitemradio\"${aria}${cls} data-target-kind=\"${key}\">${label}</button>`;\n };\n const convertGroup = `\n <div class=\"popover-section\">\n <div class=\"popover-label\">Convert to</div>\n ${item(\"paragraph\", `${iconInline(\"type\")} Paragraph`)}\n ${item(\"heading:1\", \"H1 Heading 1\")}\n ${item(\"heading:2\", \"H2 Heading 2\")}\n ${item(\"heading:3\", \"H3 Heading 3\")}\n ${item(\"heading:4\", \"H4 Heading 4\")}\n ${item(\"quote\", `${iconInline(\"code\")} Quote`)}\n ${item(\"bullet\", `${iconInline(\"list-bullet\")} Bullet list`)}\n ${item(\"ordered\", `${iconInline(\"list-numbered\")} Numbered list`)}\n </div>\n `;\n // Structural conversions (wrap content into a new structure) only make\n // sense for single-block selections. These are not \"current\"-able —\n // section break is \"insert after\", table is destructive replace.\n const structuralGroup = multi\n ? \"\"\n : `\n <div class=\"popover-divider\"></div>\n <div class=\"popover-section\">\n <div class=\"popover-label\">Replace with</div>\n <button type=\"button\" role=\"menuitem\" data-target-kind=\"table\">Table (3×3)</button>\n </div>\n <div class=\"popover-section\">\n <div class=\"popover-label\">Insert after</div>\n <button type=\"button\" role=\"menuitem\" data-target-kind=\"section_break\" title=\"Ctrl/Cmd + Shift + Enter\">Section break</button>\n </div>\n `;\n return convertGroup + structuralGroup;\n}\n\n/**\n * Resolve the current block kind into one of the popover's `data-target-kind`\n * keys, so the matching item can be highlighted as \"current\". Returns null\n * if the current kind has no equivalent in the menu (table, section_break,\n * image, multi-block, …).\n */\nfunction currentTargetKey(ctx: ChangeTypeContext): string | null {\n if (ctx.refs.length !== 1) return null;\n const first = ctx.refs[0];\n if (!first) return null;\n // Defensive — minimal stubs in tests may not implement every method.\n if (\n typeof ctx.editor.getBlockById !== \"function\" ||\n typeof ctx.editor.getDocument !== \"function\"\n ) {\n return null;\n }\n const info = ctx.editor.getBlockById(first.id);\n if (!info || info.kind !== \"paragraph\") return null;\n const doc = ctx.editor.getDocument();\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return null;\n if (block.properties.numbering) {\n const numId = block.properties.numbering.numId;\n const def = doc.numbering.find((n) => n.numId === numId);\n const fmt = def?.abstractFormat.levels[0]?.format;\n if (fmt === \"bullet\") return \"bullet\";\n if (fmt === \"decimal\") return \"ordered\";\n return \"bullet\"; // fallback for unknown list formats\n }\n const styleId = block.properties.styleId;\n if (!styleId || styleId === \"Normal\") return \"paragraph\";\n if (styleId === \"Quote\") return \"quote\";\n const m = styleId.match(/^Heading([1-6])$/);\n if (m) return `heading:${m[1]}`;\n return null;\n}\n\nfunction iconInline(name: string): string {\n return icon(name as Parameters<typeof icon>[0]);\n}\n\nfunction parseTarget(raw: string): TargetKind | null {\n if (raw === \"paragraph\") return { kind: \"paragraph\" };\n if (raw === \"quote\") return { kind: \"quote\" };\n if (raw === \"bullet\") return { kind: \"bullet\" };\n if (raw === \"ordered\") return { kind: \"ordered\" };\n if (raw === \"table\") return { kind: \"table\" };\n if (raw === \"section_break\") return { kind: \"section_break\" };\n const m = raw.match(/^heading:([1-6])$/);\n if (m?.[1]) {\n const lv = Number(m[1]) as 1 | 2 | 3 | 4 | 5 | 6;\n return { kind: \"heading\", level: lv };\n }\n return null;\n}\n\nfunction applyTarget(ctx: ChangeTypeContext, target: TargetKind): void {\n if (target.kind === \"table\") {\n convertToTable(ctx);\n return;\n }\n if (target.kind === \"section_break\") {\n // Section break is \"insert after current block\" — same dispatch\n // path as Ctrl+Shift+Enter, so behaviour is consistent.\n ctx.editor.commands.execute(\"section.insertBreakAfter\");\n return;\n }\n // Convert-in-place: swap styleId + numbering as appropriate.\n for (const ref of ctx.refs) {\n applyConversion(ctx.editor, ref, target);\n }\n}\n\nfunction applyConversion(\n editor: Editor,\n ref: BlockRef,\n target: TargetKind,\n): void {\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n\n // Convert-from-table: collapse the table into a single paragraph\n // carrying the concatenated text of every cell. The block id and\n // index stay stable, so the subsequent property/numbering work\n // applies normally.\n if (info.kind === \"table\") {\n flattenTableToParagraph(editor, ref);\n // Re-resolve — replaceBlock bumped the version.\n const refreshed = editor.getBlockById(ref.id);\n if (!refreshed) return;\n ref = { id: refreshed.id, version: refreshed.version };\n } else if (info.kind !== \"paragraph\") {\n // section_break / image: nothing meaningful to convert. The\n // popover shouldn't have been reachable for these kinds anyway.\n return;\n }\n\n if (target.kind === \"paragraph\") {\n editor.applyBlockProperties([ref], { styleId: undefined, numbering: undefined });\n return;\n }\n if (target.kind === \"heading\") {\n editor.applyBlockProperties([ref], {\n styleId: `Heading${target.level}`,\n numbering: undefined,\n });\n return;\n }\n if (target.kind === \"quote\") {\n editor.applyBlockProperties([ref], { styleId: \"Quote\", numbering: undefined });\n return;\n }\n if (target.kind === \"bullet\" || target.kind === \"ordered\") {\n // Convert-to-list needs a numbering definition. Reuse an existing\n // one with the right format if present, else add one in a single\n // setDocument pass.\n convertBlockToList(editor, ref, target.kind === \"bullet\" ? \"bullet\" : \"decimal\");\n return;\n }\n}\n\n/**\n * Replace a `Table` block with a `Paragraph` carrying the concatenated\n * text of every cell, separated by spaces. Cells with formatting are\n * flattened to plain text — the round-trip story is \"convert is\n * destructive for tables\".\n */\nfunction flattenTableToParagraph(editor: Editor, ref: BlockRef): void {\n const doc = editor.getDocument();\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"table\") return;\n\n const parts: string[] = [];\n for (const row of block.rows) {\n for (const cell of row.cells) {\n for (const inner of cell.content) {\n if (inner.kind !== \"paragraph\") continue;\n for (const run of inner.runs) {\n if (run.kind === \"text\" && run.text) parts.push(run.text);\n }\n }\n }\n }\n const text = parts.join(\" \").replace(/\\s+/g, \" \").trim();\n const next: Paragraph = {\n kind: \"paragraph\",\n properties: {},\n runs: text ? [{ kind: \"text\", text, properties: {} }] : [],\n };\n editor.replaceBlock(ref, next);\n}\n\nfunction convertBlockToList(\n editor: Editor,\n ref: BlockRef,\n format: \"bullet\" | \"decimal\",\n): void {\n const doc = editor.getDocument();\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n\n const existing = doc.numbering.find(\n (n) => n.abstractFormat.levels[0]?.format === format,\n );\n let numId: number;\n let nextNumbering: NumberingDefinition[] = doc.numbering;\n if (existing) {\n numId = existing.numId;\n } else {\n numId = doc.numbering.reduce((n, d) => Math.max(n, d.numId), 0) + 1;\n nextNumbering = [\n ...doc.numbering,\n {\n numId,\n abstractFormat: {\n levels: [\n {\n level: 0,\n format,\n text: format === \"bullet\" ? \"\\u2022\" : \"%1.\",\n },\n ],\n },\n },\n ];\n }\n\n // Drop any heading / quote style when becoming a list item — omit the\n // key rather than assigning `undefined` so the shape satisfies\n // `exactOptionalPropertyTypes`.\n const { styleId: _omit, ...cleanProps } = block.properties;\n void _omit;\n const nextBlock: Paragraph = {\n ...block,\n properties: {\n ...cleanProps,\n numbering: { numId, level: 0 },\n },\n };\n const nextBody = doc.body.slice();\n nextBody[info.index] = nextBlock;\n const nextDoc: SobreeDocument = {\n ...doc,\n body: nextBody,\n numbering: nextNumbering,\n };\n editor.setDocument(nextDoc);\n}\n\n/**\n * Replace the current paragraph with a 3×3 table. If the paragraph has\n * text, seed the first cell with it; the other 8 cells are empty.\n */\nfunction convertToTable(ctx: ChangeTypeContext): void {\n const ref = ctx.refs[0];\n if (!ref) return;\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const doc = ctx.editor.getDocument();\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n\n const empty = () => ({\n content: [{ kind: \"paragraph\", properties: {}, runs: [] } as Paragraph] as Block[],\n });\n const withExistingRuns = {\n content: [{ kind: \"paragraph\", properties: {}, runs: block.runs } as Paragraph] as Block[],\n };\n\n const table: Table = {\n kind: \"table\",\n grid: [2400, 2400, 2400],\n rows: [\n { cells: [withExistingRuns, empty(), empty()] },\n { cells: [empty(), empty(), empty()] },\n { cells: [empty(), empty(), empty()] },\n ],\n properties: {},\n };\n ctx.editor.replaceBlock(ref, table);\n}\n","/**\n * Shared \"what does the current selection look like?\" reader.\n *\n * Tools wire `editor.on(\"selection\", …)` + `editor.on(\"change\", …)` and\n * call `readSelectionState(editor)` to get a snapshot they can use to\n * paint their pressed/active/value state. Everything goes through the\n * public Editor API — no internal access.\n */\n\nimport type {\n Block,\n Editor,\n ParagraphProperties,\n RunProperties,\n SobreeDocument,\n TextRun,\n InlineRun,\n Paragraph,\n} from \"@sobree/core\";\nimport { resolveStyleCascade } from \"@sobree/core\";\n\nexport interface SelectionState {\n /** Block kind under the caret (or `null` when focus is outside).\n * Derived from `Block[\"kind\"]` so new block kinds never silently\n * drift this type out of sync. */\n blockKind: Block[\"kind\"] | null;\n /** Paragraph properties of the active block (`null` for non-paragraphs). */\n paragraphProps: ParagraphProperties | null;\n /** Numbering format of the active block, when it's a list item. */\n listFormat: \"bullet\" | \"decimal\" | null;\n /**\n * Dominant run-level properties at the caret / range. For a range\n * selection: a property is included only if every text run in the\n * range agrees on it; otherwise it's omitted (=> \"mixed\", select\n * shows blank). For a caret: properties of the run on the left of\n * the caret (Word-style — the formatting that the next typed char\n * inherits).\n */\n runProps: RunProperties;\n}\n\nconst EMPTY_STATE: SelectionState = {\n blockKind: null,\n paragraphProps: null,\n listFormat: null,\n runProps: {},\n};\n\nexport function readSelectionState(editor: Editor): SelectionState {\n const caret = editor.selection.currentCaret();\n if (!caret) return EMPTY_STATE;\n const doc = editor.getDocument();\n const info = editor.getBlockById(caret.block.id);\n if (!info) return EMPTY_STATE;\n const block = doc.body[info.index];\n if (!block) return EMPTY_STATE;\n if (block.kind !== \"paragraph\") {\n return {\n ...EMPTY_STATE,\n blockKind: block.kind,\n };\n }\n const ownRunProps = resolveRunProps(editor, block);\n // Style cascade is resolved in @sobree/core and reused here so the\n // toolbar's \"what's the effective font?\" answer is identical to what\n // the renderer applies to the block element.\n const { runDefaults } = resolveStyleCascade(doc.styles, block.properties.styleId);\n return {\n blockKind: \"paragraph\",\n paragraphProps: block.properties,\n listFormat: resolveListFormat(doc, block),\n runProps: { ...runDefaults, ...ownRunProps },\n };\n}\n\nfunction resolveListFormat(doc: SobreeDocument, p: Paragraph): \"bullet\" | \"decimal\" | null {\n const numbering = p.properties.numbering;\n if (!numbering) return null;\n const def = doc.numbering.find((n) => n.numId === numbering.numId);\n const fmt = def?.abstractFormat.levels[0]?.format;\n if (fmt === \"bullet\" || fmt === \"decimal\") return fmt;\n return null;\n}\n\nfunction resolveRunProps(editor: Editor, p: Paragraph): RunProperties {\n const range = editor.selection.currentRange();\n if (range && range.from.block.id === range.to.block.id) {\n // Range inside this paragraph — collect every text run that touches\n // the range and intersect their props.\n const lo = Math.min(range.from.offset, range.to.offset);\n const hi = Math.max(range.from.offset, range.to.offset);\n const touched = textRunsInRange(p.runs, lo, hi);\n if (touched.length === 0) return {};\n return intersectProps(touched.map((r) => r.properties));\n }\n // Caret: the run immediately to the left of the offset wins (or, at\n // offset 0, the first run). Matches Word — the format that newly\n // typed characters will inherit.\n const caret = editor.selection.currentCaret();\n if (!caret) return {};\n return runPropsAtOffset(p.runs, caret.offset);\n}\n\n/** Walk a paragraph's runs and return every TextRun whose extent\n * overlaps `[lo, hi)`. Hyperlink children are flattened. */\nfunction textRunsInRange(\n runs: readonly InlineRun[],\n lo: number,\n hi: number,\n): TextRun[] {\n const out: TextRun[] = [];\n let cursor = 0;\n walk(runs);\n return out;\n\n function walk(list: readonly InlineRun[]): void {\n for (const r of list) {\n const start = cursor;\n const len = runLen(r);\n const end = start + len;\n cursor = end;\n if (end <= lo) continue;\n if (start >= hi) return;\n if (r.kind === \"text\") out.push(r);\n else if (r.kind === \"hyperlink\") {\n // Hyperlink children own their own textual extent — but `cursor`\n // already moved past the whole hyperlink span. Restore so the\n // recursion's offsets line up with the outer document order.\n cursor = start;\n walk(r.children);\n cursor = end;\n }\n }\n }\n}\n\nfunction runPropsAtOffset(\n runs: readonly InlineRun[],\n offset: number,\n): RunProperties {\n let cursor = 0;\n let lastTextProps: RunProperties = {};\n for (const r of runs) {\n const len = runLen(r);\n if (r.kind === \"text\") lastTextProps = r.properties;\n else if (r.kind === \"hyperlink\") {\n const inner = lastTextRunProps(r.children);\n if (inner) lastTextProps = inner;\n }\n if (offset > cursor && offset <= cursor + len) {\n return r.kind === \"text\" ? r.properties : lastTextProps;\n }\n cursor += len;\n }\n return lastTextProps;\n}\n\nfunction lastTextRunProps(runs: readonly InlineRun[]): RunProperties | null {\n let last: RunProperties | null = null;\n for (const r of runs) {\n if (r.kind === \"text\") last = r.properties;\n else if (r.kind === \"hyperlink\") {\n const inner = lastTextRunProps(r.children);\n if (inner) last = inner;\n }\n }\n return last;\n}\n\nfunction runLen(r: InlineRun): number {\n if (r.kind === \"text\") return r.text.length;\n if (r.kind === \"hyperlink\") {\n let n = 0;\n for (const inner of r.children) n += runLen(inner);\n return n;\n }\n // Atomic single-cell runs (break, tab, field, drawing).\n return 1;\n}\n\n/**\n * Return only the properties that EVERY input snapshot agrees on. A\n * key shared by all snapshots with the same value survives; anything\n * that differs (or is missing in some snapshots) is dropped.\n */\nfunction intersectProps(snapshots: ReadonlyArray<RunProperties>): RunProperties {\n if (snapshots.length === 0) return {};\n const first = snapshots[0]!;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(first)) {\n if (v === undefined) continue;\n let agree = true;\n for (let i = 1; i < snapshots.length; i++) {\n if ((snapshots[i] as Record<string, unknown>)[k] !== v) {\n agree = false;\n break;\n }\n }\n if (agree) out[k] = v;\n }\n return out as RunProperties;\n}\n","import type { BlockKind, BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\nimport type { BlockRef } from \"@sobree/core\";\nimport type { Paragraph, SobreeDocument } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport { readSelectionState } from \"./selectionState\";\n\nexport interface PerKindContext {\n editor: Editor;\n target: BlockTarget;\n}\n\n/**\n * Build the kind-specific HTML fragment that follows the shared text\n * tools. Returns an empty string for kinds without extras (e.g. no\n * special UI needed yet).\n *\n * Table tools live in B5 — handled in a dedicated module.\n */\nexport function buildPerKindHtml(kind: BlockKind): string {\n if (kind === \"table\") return \"\";\n if (kind === \"header\" || kind === \"footer\") return \"\";\n\n const alignment = buildAlignmentHtml();\n const extras: string[] = [alignment];\n\n if (kind === \"heading\") extras.push(buildHeadingLevelHtml());\n if (kind === \"list\" || kind === \"listOrdered\") extras.push(buildListHtml(kind));\n if (kind === \"image\") extras.push(buildImageHtml());\n\n return `<div class=\"tb-divider\"></div>${extras.join('<div class=\"tb-divider\"></div>')}`;\n}\n\n/**\n * Wire clicks + input events for the per-kind fragment. Returns a detach\n * function. Safe to call even if `kind` has no extras (noop detach).\n */\nexport function wirePerKindTools(\n root: HTMLElement,\n ctx: PerKindContext,\n): () => void {\n const handlers: Array<() => void> = [];\n\n const onAlign = (e: Event) => {\n const btn = (e.target as HTMLElement).closest('button[data-action=\"align\"]');\n if (!btn) return;\n const arg = btn.getAttribute(\"data-arg\");\n if (!arg) return;\n const alignment = arg === \"justify\" ? \"both\" : arg;\n if (alignment !== \"left\" && alignment !== \"center\" && alignment !== \"right\" && alignment !== \"both\") return;\n applyToTargetBlocks(ctx, (ref) => {\n const result = ctx.editor.applyBlockProperties([ref], { alignment });\n warnOnEditFailure(\"align\", result);\n });\n };\n root.addEventListener(\"click\", onAlign);\n handlers.push(() => root.removeEventListener(\"click\", onAlign));\n\n const onLineSpacing = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"line-spacing\") return;\n const raw = (el as HTMLSelectElement).value;\n (el as HTMLSelectElement).selectedIndex = 0;\n const mult = Number(raw);\n if (!Number.isFinite(mult) || mult <= 0) return;\n applyToTargetBlocks(ctx, (ref) =>\n ctx.editor.applyBlockProperties([ref], {\n spacing: { line: Math.round(240 * mult), lineRule: \"auto\" },\n }),\n );\n };\n root.addEventListener(\"change\", onLineSpacing);\n handlers.push(() => root.removeEventListener(\"change\", onLineSpacing));\n\n if (ctx.target.kind === \"heading\") {\n const onHeadingLevel = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"heading-level\") return;\n const level = Number((el as HTMLSelectElement).value);\n if (!Number.isFinite(level) || level < 1 || level > 6) return;\n const ref = refForTarget(ctx);\n if (!ref) return;\n ctx.editor.applyBlockProperties([ref], { styleId: `Heading${level}` });\n };\n root.addEventListener(\"change\", onHeadingLevel);\n handlers.push(() => root.removeEventListener(\"change\", onHeadingLevel));\n }\n\n if (ctx.target.kind === \"list\" || ctx.target.kind === \"listOrdered\") {\n const onListAction = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\");\n if (action === \"toggle-list-kind\") toggleListKind(ctx);\n else if (action === \"align-list\") {\n const arg = btn.getAttribute(\"data-arg\");\n const alignment = arg === \"justify\" ? \"both\" : arg;\n if (alignment !== \"left\" && alignment !== \"center\" && alignment !== \"right\" && alignment !== \"both\") return;\n applyAlignmentToWholeList(ctx, alignment);\n }\n };\n root.addEventListener(\"click\", onListAction);\n handlers.push(() => root.removeEventListener(\"click\", onListAction));\n }\n\n if (ctx.target.kind === \"image\") {\n const onImageInput = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (role !== \"image-alt\") return;\n const alt = (el as HTMLInputElement).value;\n updateImageAltAtTarget(ctx, alt);\n };\n root.addEventListener(\"input\", onImageInput);\n handlers.push(() => root.removeEventListener(\"input\", onImageInput));\n\n const onImageClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (btn?.getAttribute(\"data-action\") === \"delete-image\") {\n deleteImageAtTarget(ctx);\n }\n };\n root.addEventListener(\"click\", onImageClick);\n handlers.push(() => root.removeEventListener(\"click\", onImageClick));\n }\n\n // Sync visible state — alignment pressed-state, line-spacing select\n // value, heading level, list-toggle icon. Driven by editor `selection`\n // and `change` events so the toolbar mirrors what's under the caret.\n const syncAll = () => syncPerKindState(root, ctx);\n const detachSelection = ctx.editor.on(\"selection\", syncAll);\n const detachChange = ctx.editor.on(\"change\", syncAll);\n handlers.push(detachSelection, detachChange);\n // Initial paint.\n syncAll();\n\n return () => {\n for (const detach of handlers) detach();\n };\n}\n\n/**\n * Repaint pressed/active/value state on every per-kind control to\n * reflect the current selection. Call from selection / change events\n * and once on toolbar open.\n */\nfunction syncPerKindState(root: HTMLElement, ctx: PerKindContext): void {\n const state = readSelectionState(ctx.editor);\n\n // Alignment buttons — only one is \"pressed\" at a time.\n const align = state.paragraphProps?.alignment;\n const arg =\n align === \"both\" ? \"justify\" : align === undefined ? \"left\" : align;\n const alignBtns = root.querySelectorAll<HTMLButtonElement>(\n 'button[data-action=\"align\"]',\n );\n for (const btn of alignBtns) {\n const on = btn.getAttribute(\"data-arg\") === arg;\n btn.setAttribute(\"aria-pressed\", String(on));\n btn.classList.toggle(\"is-active\", on);\n }\n\n // Line-spacing select — show the current multiplier (or blank for\n // \"default\"). The select uses string options like \"1.5\".\n const lineSel = root.querySelector<HTMLSelectElement>(\n 'select[data-role=\"line-spacing\"]',\n );\n if (lineSel) {\n const sp = state.paragraphProps?.spacing;\n if (sp?.line && sp.lineRule === \"auto\") {\n const mult = sp.line / 240;\n const matched = optionByValue(lineSel, String(mult));\n lineSel.value = matched ?? \"\";\n } else {\n lineSel.value = \"\";\n }\n }\n\n // Heading level — show the current heading number (1..6) or blank.\n const headingSel = root.querySelector<HTMLSelectElement>(\n 'select[data-role=\"heading-level\"]',\n );\n if (headingSel) {\n const styleId = state.paragraphProps?.styleId;\n const m = styleId?.match(/^Heading([1-6])$/);\n headingSel.value = m?.[1] ?? \"\";\n }\n\n // List toggle — flip the icon to match the current numbering format.\n const listToggle = root.querySelector<HTMLButtonElement>(\n 'button[data-action=\"toggle-list-kind\"]',\n );\n if (listToggle) {\n const isOrdered = state.listFormat === \"decimal\";\n listToggle.innerHTML = icon(isOrdered ? \"list-numbered\" : \"list-bullet\");\n listToggle.title = isOrdered\n ? \"Switch to bullet list\"\n : \"Switch to numbered list\";\n }\n}\n\nfunction optionByValue(sel: HTMLSelectElement, value: string): string | null {\n for (const opt of Array.from(sel.options)) {\n if (opt.value === value) return value;\n }\n return null;\n}\n\n// === HTML builders ===\n\nfunction buildAlignmentHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"alignment\">\n <button type=\"button\" data-action=\"align\" data-arg=\"left\" title=\"Align left\">${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"center\" title=\"Align centre\">${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"right\" title=\"Align right\">${icon(\"align-right\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"justify\" title=\"Justify\">${icon(\"align-justify\")}</button>\n <select data-role=\"line-spacing\" aria-label=\"Line spacing\" title=\"Line spacing\">\n <option value=\"\">Line</option>\n <option value=\"1\">Single</option>\n <option value=\"1.15\">1.15</option>\n <option value=\"1.5\">1.5</option>\n <option value=\"2\">Double</option>\n </select>\n </div>\n `;\n}\n\nfunction buildHeadingLevelHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"heading\">\n <select data-role=\"heading-level\" aria-label=\"Heading level\" title=\"Heading level\">\n <option value=\"\">Level</option>\n <option value=\"1\">H1</option>\n <option value=\"2\">H2</option>\n <option value=\"3\">H3</option>\n <option value=\"4\">H4</option>\n <option value=\"5\">H5</option>\n <option value=\"6\">H6</option>\n </select>\n </div>\n `;\n}\n\nfunction buildListHtml(_kind: \"list\" | \"listOrdered\"): string {\n // The plain `align` action only affects the focused LI. These\n // `align-list` variants apply the alignment to *every* item in the\n // same list — a one-click way to reformat a whole list without\n // clicking each item.\n return `\n <div class=\"tb-group\" data-group=\"list\">\n <button type=\"button\" data-action=\"toggle-list-kind\" title=\"Toggle bullet / numbered\">${icon(\"list-numbered\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"left\" title=\"Align whole list left\">${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"center\" title=\"Align whole list centre\">${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"right\" title=\"Align whole list right\">${icon(\"align-right\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"justify\" title=\"Justify whole list\">${icon(\"align-justify\")}</button>\n </div>\n `;\n}\n\nfunction buildImageHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"image\">\n <input type=\"text\" data-role=\"image-alt\" placeholder=\"Alt text\" aria-label=\"Alt text\" title=\"Alt text (screen reader description)\" />\n <button type=\"button\" data-action=\"delete-image\" title=\"Delete image\">${icon(\"trash\")}</button>\n </div>\n `;\n}\n\n// === action helpers ===\n\nfunction applyToTargetBlocks(\n ctx: PerKindContext,\n fn: (ref: BlockRef) => void,\n): void {\n const ref = refForTarget(ctx);\n if (ref) fn(ref);\n}\n\n/**\n * Surface `EditResult` failures from toolbar actions in the console.\n * Editor mutations can fail silently (optimistic-lock conflicts, etc.)\n * if callers don't check the return value — and that's exactly what\n * happened here for months before we noticed: clicking \"justify\" did\n * nothing and there was no warning. Logging at least makes the next\n * silent failure visible at dev-time.\n */\nfunction warnOnEditFailure(action: string, result: { ok: boolean; error?: unknown }): void {\n if (!result.ok) {\n // eslint-disable-next-line no-console\n console.warn(`[block-tools] ${action} failed:`, result.error);\n }\n}\n\nfunction refForTarget(ctx: PerKindContext): BlockRef | null {\n // Prefer the explicit `target.blockId` — that's the block the toolbar\n // was *opened* on (captured by `blockTargetFrom` from the indicator\n // click). Reading `currentCaret()` here is unreliable because\n // clicking a toolbar button shifts focus, and the browser selection\n // can move off the editor before the action handler fires. In the\n // worst case the caret falls back to `getBlocks()[0]` (the title)\n // and the wrong block silently gets mutated — observed on user-\n // contract: toolbar `align: justify` clicked on a list item went\n // nowhere because `currentCaret()` returned null, then the\n // optimistic-lock guard on `applyBlockProperties` failed silently\n // because version 0 was sent against a current version of N.\n const explicitId = ctx.target.blockId;\n if (explicitId) {\n const info = ctx.editor.getBlockById(explicitId);\n if (info) return { id: info.id, version: info.version };\n }\n const caret = ctx.editor.selection.currentCaret();\n return caret?.block ?? ctx.editor.getBlocks()[0] ?? null;\n}\n\n/**\n * Apply `alignment` to every list item that belongs to the same logical\n * list as the toolbar target. \"Same list\" = a consecutive run of\n * paragraph blocks whose `numbering.numId` matches the target's — the\n * same grouping the renderer uses to fold paragraphs into one `<ol>` /\n * `<ul>`. Blocks outside that run (other lists with a different numId,\n * intervening paragraphs / headings) are left untouched.\n */\nfunction applyAlignmentToWholeList(\n ctx: PerKindContext,\n alignment: \"left\" | \"center\" | \"right\" | \"both\",\n): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const doc = ctx.editor.getDocument();\n const targetBlock = doc.body[info.index];\n if (!targetBlock || targetBlock.kind !== \"paragraph\" || !targetBlock.properties.numbering) return;\n const targetNumId = targetBlock.properties.numbering.numId;\n\n // Walk backward and forward from the target while the numId stays\n // the same. Stop at the first non-matching block on either side.\n let start = info.index;\n while (start > 0) {\n const prev = doc.body[start - 1];\n if (!prev || prev.kind !== \"paragraph\" || prev.properties.numbering?.numId !== targetNumId) break;\n start--;\n }\n let endExclusive = info.index + 1;\n while (endExclusive < doc.body.length) {\n const next = doc.body[endExclusive];\n if (!next || next.kind !== \"paragraph\" || next.properties.numbering?.numId !== targetNumId) break;\n endExclusive++;\n }\n\n // Collect fresh BlockRefs (id + current version) for every LI in the run.\n // Reading versions live avoids optimistic-lock failures when a recent\n // edit bumped a sibling.\n const refs: BlockRef[] = [];\n for (let i = start; i < endExclusive; i++) {\n const blk = doc.body[i];\n if (!blk) continue;\n const blkInfo = ctx.editor.getBlockById((blk as { id?: string }).id ?? \"\");\n // The doc body is keyed by index — to get the id, ask the editor.\n const liInfo = ctx.editor.getBlocks()[i];\n if (!liInfo) continue;\n refs.push({ id: liInfo.id, version: liInfo.version });\n void blkInfo;\n }\n\n const result = ctx.editor.applyBlockProperties(refs, { alignment });\n warnOnEditFailure(\"align-list\", result);\n}\n\n/**\n * Flip a list block between bullet and numbered by swapping its numId to\n * another numbering definition with the opposite format.\n *\n * Implemented as a whole-document mutation because list numbering\n * definitions live at the doc level.\n */\nfunction toggleListKind(ctx: PerKindContext): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\" || !block.properties.numbering) return;\n const currentDef = doc.numbering.find(\n (n) => n.numId === block.properties.numbering?.numId,\n );\n if (!currentDef) return;\n const currentFormat = currentDef.abstractFormat.levels[0]?.format ?? \"bullet\";\n const wantBullet = currentFormat !== \"bullet\";\n\n // Build the full next document in one step — both the numbering def\n // (if we need to add one) and the block's reference swap land together\n // in a single `setDocument`, so we don't have to re-resolve block refs\n // between operations.\n const existing = doc.numbering.find(\n (n) =>\n n.abstractFormat.levels[0]?.format === (wantBullet ? \"bullet\" : \"decimal\"),\n );\n let targetNumId: number;\n let nextNumbering = doc.numbering;\n if (existing) {\n targetNumId = existing.numId;\n } else {\n targetNumId = doc.numbering.reduce((n, d) => Math.max(n, d.numId), 0) + 1;\n nextNumbering = [\n ...doc.numbering,\n {\n numId: targetNumId,\n abstractFormat: {\n levels: [\n {\n level: 0,\n format: wantBullet ? \"bullet\" : \"decimal\",\n text: wantBullet ? \"\\u2022\" : \"%1.\",\n },\n ],\n },\n },\n ];\n }\n\n const nextBody = doc.body.slice();\n nextBody[info.index] = {\n ...block,\n properties: {\n ...block.properties,\n numbering: { numId: targetNumId, level: 0 },\n },\n };\n\n const nextDoc: SobreeDocument = { ...doc, numbering: nextNumbering, body: nextBody };\n ctx.editor.setDocument(nextDoc);\n}\n\nfunction updateImageAltAtTarget(ctx: PerKindContext, alt: string): void {\n // Walk the block's runs, find the first DrawingRun, update its altText.\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n const runs = block.runs.map((r) =>\n r.kind === \"drawing\" ? { ...r, altText: alt } : r,\n );\n ctx.editor.replaceBlock(ref, { ...block, runs });\n}\n\nfunction deleteImageAtTarget(ctx: PerKindContext): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index] as Paragraph | undefined;\n if (!block || block.kind !== \"paragraph\") return;\n const runs = block.runs.filter((r) => r.kind !== \"drawing\");\n ctx.editor.replaceBlock(ref, { ...block, runs });\n}\n","import type { BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\nimport type { BlockRef } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\n\nexport type TableMode = \"cell\" | \"table\";\n\nexport interface TableContext {\n editor: Editor;\n target: BlockTarget;\n /** Current-cell resolution — null if the caret isn't inside a cell. */\n cell: CellLocation | null;\n}\n\nexport interface CellLocation {\n row: number;\n col: number;\n element: HTMLTableCellElement;\n}\n\n/**\n * Build the table toolbar's HTML. Pill at the top toggles Cell/Table.\n * In Cell mode we show cell-level ops (alignment of the cell's content,\n * vertical alignment, merge / unmerge). In Table mode we show row /\n * column ops plus whole-table toggles.\n *\n * Text tools are NOT included here — the caller (`BlockTools`)\n * prepends the shared text-tools HTML so formatting always applies\n * to the cell content.\n */\nexport function buildTableToolsHtml(mode: TableMode, hasCell: boolean): string {\n const pill = `\n <div class=\"tb-divider\"></div>\n <div class=\"tb-pill\" role=\"tablist\" aria-label=\"Table scope\">\n <button type=\"button\" data-action=\"mode\" data-arg=\"cell\"\n title=\"Edit the current cell\"\n ${mode === \"cell\" ? 'class=\"is-active\"' : \"\"}\n ${!hasCell ? \"disabled\" : \"\"}\n aria-pressed=\"${mode === \"cell\"}\">Cell</button>\n <button type=\"button\" data-action=\"mode\" data-arg=\"table\"\n title=\"Edit the whole table\"\n ${mode === \"table\" ? 'class=\"is-active\"' : \"\"}\n aria-pressed=\"${mode === \"table\"}\">Table</button>\n </div>\n <div class=\"tb-divider\"></div>\n `;\n\n if (mode === \"cell\") return pill + buildCellOpsHtml(hasCell);\n return pill + buildTableOpsHtml();\n}\n\nexport function wireTableTools(\n root: HTMLElement,\n ctx: TableContext,\n onModeChange: (mode: TableMode) => void,\n): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn || btn.hasAttribute(\"disabled\")) return;\n const action = btn.getAttribute(\"data-action\");\n const arg = btn.getAttribute(\"data-arg\");\n if (action === \"mode\") {\n if (arg === \"cell\" || arg === \"table\") onModeChange(arg);\n return;\n }\n const tableRef = tableRefFor(ctx);\n if (!tableRef) return;\n handleAction(ctx, tableRef, action, arg);\n };\n\n const onChange = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (!role) return;\n const tableRef = tableRefFor(ctx);\n if (!tableRef) return;\n if (role === \"cell-valign\" && ctx.cell) {\n const v = (el as HTMLSelectElement).value;\n if (v === \"top\" || v === \"center\" || v === \"bottom\") {\n ctx.editor.table.setCellProperties(\n { table: tableRef, row: ctx.cell.row, col: ctx.cell.col },\n { verticalAlign: v },\n );\n }\n }\n };\n\n root.addEventListener(\"click\", onClick);\n root.addEventListener(\"change\", onChange);\n return () => {\n root.removeEventListener(\"click\", onClick);\n root.removeEventListener(\"change\", onChange);\n };\n}\n\n// === HTML ===\n\nfunction buildCellOpsHtml(hasCell: boolean): string {\n const disabled = hasCell ? \"\" : \"disabled\";\n return `\n <div class=\"tb-group\" data-group=\"cell-align\">\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"left\" title=\"Align left in cell\" ${disabled}>${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"center\" title=\"Align centre in cell\" ${disabled}>${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"right\" title=\"Align right in cell\" ${disabled}>${icon(\"align-right\")}</button>\n <select data-role=\"cell-valign\" aria-label=\"Vertical alignment\" title=\"Vertical align\" ${disabled}>\n <option value=\"\">V-align</option>\n <option value=\"top\">Top</option>\n <option value=\"center\">Middle</option>\n <option value=\"bottom\">Bottom</option>\n </select>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"cell-merge\">\n <button type=\"button\" data-action=\"merge-right\" title=\"Merge with cell to the right\" ${disabled}>Merge →</button>\n <button type=\"button\" data-action=\"merge-down\" title=\"Merge with cell below\" ${disabled}>Merge ↓</button>\n <button type=\"button\" data-action=\"unmerge\" title=\"Unmerge\" ${disabled}>Unmerge</button>\n </div>\n `;\n}\n\nfunction buildTableOpsHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"row-ops\">\n <button type=\"button\" data-action=\"row-above\" title=\"Insert row above\">+ Row ↑</button>\n <button type=\"button\" data-action=\"row-below\" title=\"Insert row below\">+ Row ↓</button>\n <button type=\"button\" data-action=\"delete-row\" title=\"Delete row\">${icon(\"trash\")} Row</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"col-ops\">\n <button type=\"button\" data-action=\"col-left\" title=\"Insert column left\">+ Col ←</button>\n <button type=\"button\" data-action=\"col-right\" title=\"Insert column right\">+ Col →</button>\n <button type=\"button\" data-action=\"delete-col\" title=\"Delete column\">${icon(\"trash\")} Col</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"table-ops\">\n <button type=\"button\" data-action=\"toggle-header-row\" title=\"Toggle first row as header\">Header row</button>\n <button type=\"button\" data-action=\"delete-table\" title=\"Delete table\">${icon(\"trash\")} Table</button>\n </div>\n `;\n}\n\n// === action dispatch ===\n\nfunction handleAction(\n ctx: TableContext,\n tableRef: BlockRef,\n action: string | null,\n arg: string | null,\n): void {\n if (!action) return;\n const ed = ctx.editor;\n const cell = ctx.cell;\n const row = cell?.row ?? 0;\n const col = cell?.col ?? 0;\n\n switch (action) {\n case \"cell-align\": {\n if (!cell) return;\n const a =\n arg === \"left\" || arg === \"center\" || arg === \"right\" ? arg : \"left\";\n // Set alignment on the FIRST paragraph in the cell via setCellContent.\n const info = ed.getBlockById(tableRef.id);\n if (!info) return;\n const doc = ed.getDocument();\n const table = doc.body[info.index];\n if (!table || table.kind !== \"table\") return;\n const rowObj = table.rows[cell.row];\n const cellObj = rowObj?.cells[cellIndexAt(table, cell.row, cell.col)];\n if (!cellObj) return;\n const newContent = cellObj.content.map((b) =>\n b.kind === \"paragraph\" ? { ...b, properties: { ...b.properties, alignment: a as \"left\" | \"center\" | \"right\" } } : b,\n );\n ed.table.setCellContent({ table: tableRef, row: cell.row, col: cell.col }, newContent);\n return;\n }\n case \"merge-right\":\n if (!cell) return;\n ed.table.mergeCells(tableRef, { row, col, colSpan: 2 });\n return;\n case \"merge-down\":\n if (!cell) return;\n ed.table.mergeCells(tableRef, { row, col, rowSpan: 2 });\n return;\n case \"unmerge\":\n if (!cell) return;\n ed.table.unmergeCell({ table: tableRef, row, col });\n return;\n case \"row-above\":\n ed.table.insertRow(tableRef, { at: \"before\", index: row });\n return;\n case \"row-below\":\n ed.table.insertRow(tableRef, { at: \"after\", index: row });\n return;\n case \"delete-row\":\n ed.table.deleteRow(tableRef, row);\n return;\n case \"col-left\":\n ed.table.insertColumn(tableRef, { at: \"before\", index: col });\n return;\n case \"col-right\":\n ed.table.insertColumn(tableRef, { at: \"after\", index: col });\n return;\n case \"delete-col\":\n ed.table.deleteColumn(tableRef, col);\n return;\n case \"toggle-header-row\":\n ed.table.toggleHeaderRow(tableRef, 0);\n return;\n case \"delete-table\":\n ed.deleteBlock(tableRef);\n return;\n }\n}\n\nfunction tableRefFor(ctx: TableContext): BlockRef | null {\n const caret = ctx.editor.selection.currentCaret();\n // The caret might be inside a cell — in which case its block ref is\n // the cell's containing paragraph (a child of the table cell). We\n // need the table block itself; walk `getBlocks()` looking for the\n // table that owns the target element.\n const doc = ctx.editor.getDocument();\n for (let i = 0; i < doc.body.length; i++) {\n if (doc.body[i]?.kind === \"table\") {\n const info = ctx.editor.getBlock(i);\n const blockEl = ctx.target.element;\n // When the caret is in a cell, `target` is the table element;\n // otherwise fall back to the first table in the body.\n if (!blockEl || blockEl.tagName.toLowerCase() !== \"table\") continue;\n return { id: info.id, version: info.version };\n }\n }\n // Fallback: first table in the doc.\n const tableBlock = ctx.editor.getBlocks().find((b) => b.kind === \"table\");\n void caret;\n return tableBlock ? { id: tableBlock.id, version: tableBlock.version } : null;\n}\n\n/** Index into `row.cells` for a given visual column. */\nfunction cellIndexAt(\n table: import(\"@sobree/core\").Table,\n row: number,\n col: number,\n): number {\n const r = table.rows[row];\n if (!r) return -1;\n let c = 0;\n for (let i = 0; i < r.cells.length; i++) {\n const span = r.cells[i]?.gridSpan ?? 1;\n if (col >= c && col < c + span) return i;\n c += span;\n }\n return -1;\n}\n\n/**\n * Given a `<table>` element and the current caret, return the\n * `{row, col, element}` of the cell under the caret, or `null` if the\n * caret isn't inside a cell in that table.\n */\nexport function locateCellFromSelection(\n tableEl: HTMLTableElement,\n): CellLocation | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchor = sel.anchorNode;\n if (!anchor) return null;\n const cell = (anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : (anchor as HTMLElement))?.closest(\n \"td, th\",\n ) as HTMLTableCellElement | null;\n if (!cell || !tableEl.contains(cell)) return null;\n const tr = cell.parentElement as HTMLTableRowElement | null;\n if (!tr) return null;\n // Row: position of its <tr> among all <tr>s in the table.\n const allRows = Array.from(tableEl.querySelectorAll(\"tr\"));\n const row = allRows.indexOf(tr);\n // Col: sum of preceding siblings' colspan.\n let col = 0;\n for (const sibling of Array.from(tr.children)) {\n if (sibling === cell) break;\n const cs = Number(sibling.getAttribute(\"colspan\") ?? 1);\n col += Number.isFinite(cs) && cs > 0 ? cs : 1;\n }\n return { row, col, element: cell };\n}\n","import type { BlockTarget } from \"../blockKinds\";\nimport { MARK_ON, MARK_PROP, toggleMark } from \"@sobree/core\";\nimport { icon } from \"./icons\";\nimport type { Range as ApiRange, BlockRef, RunProperties } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport { readSelectionState } from \"./selectionState\";\n\nexport interface ToolContext {\n editor: Editor;\n target: BlockTarget;\n}\n\n/**\n * Text-formatting tools shown on every block type. Returns an HTML\n * string for the toolbar shell to inject, plus a wiring function that\n * installs click/input listeners against it.\n */\nexport function buildTextToolsHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"text\">\n <select data-role=\"font-family\" aria-label=\"Font family\" title=\"Font family\">\n <option value=\"\">Font</option>\n <option>Arial</option>\n <option>Calibri</option>\n <option>Cambria</option>\n <option>Consolas</option>\n <option>Courier New</option>\n <option>Georgia</option>\n <option>Helvetica</option>\n <option>Times New Roman</option>\n <option>Verdana</option>\n </select>\n <select data-role=\"font-size\" aria-label=\"Font size\" title=\"Font size\">\n <option value=\"\">Size</option>\n ${[8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 72].map((n) => `<option>${n}</option>`).join(\"\")}\n </select>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"marks\" role=\"group\" aria-label=\"Text formatting\">\n <button type=\"button\" data-action=\"wrap\" data-tag=\"strong\" title=\"Bold (Ctrl+B)\" aria-label=\"Bold\" aria-pressed=\"false\">${icon(\"bold\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"em\" title=\"Italic (Ctrl+I)\" aria-label=\"Italic\" aria-pressed=\"false\">${icon(\"italic\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"u\" title=\"Underline (Ctrl+U)\" aria-label=\"Underline\" aria-pressed=\"false\">${icon(\"underline\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"s\" title=\"Strikethrough\" aria-label=\"Strikethrough\" aria-pressed=\"false\">${icon(\"strike\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"sup\" title=\"Superscript\" aria-label=\"Superscript\" aria-pressed=\"false\">${icon(\"superscript\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"sub\" title=\"Subscript\" aria-label=\"Subscript\" aria-pressed=\"false\">${icon(\"subscript\")}</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"colour\" role=\"group\" aria-label=\"Colour and highlight\">\n <label class=\"tb-colour\" title=\"Text colour\">\n ${icon(\"paintbrush\")}\n <input type=\"color\" data-role=\"color\" value=\"#c96f22\" aria-label=\"Text colour\" />\n </label>\n <label class=\"tb-colour\" title=\"Highlight\">\n ${icon(\"highlighter\")}\n <input type=\"color\" data-role=\"highlight\" value=\"#fff3a1\" aria-label=\"Highlight colour\" />\n </label>\n <button type=\"button\" data-action=\"clear-formatting\" title=\"Clear formatting\" aria-label=\"Clear formatting\">${icon(\"eraser\")}</button>\n </div>\n `;\n}\n\n/** Install click + input listeners for the text tool block. */\nexport function wireTextTools(\n root: HTMLElement,\n ctx: ToolContext,\n): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\");\n if (action === \"wrap\") {\n const tag = btn.getAttribute(\"data-tag\") as\n | \"strong\"\n | \"em\"\n | \"u\"\n | \"s\"\n | \"sup\"\n | \"sub\"\n | \"mark\"\n | null;\n if (!tag) return;\n const range = rangeForContext(ctx);\n toggleMark(ctx.editor, range, tag);\n // Sync visual state immediately — the editor's change event is\n // debounced, so wait for the next tick to re-check the AST.\n requestAnimationFrame(() => syncActive(root, ctx));\n return;\n }\n if (action === \"clear-formatting\") {\n ctx.editor.clearInlineFormattingAtSelection();\n requestAnimationFrame(() => syncActive(root, ctx));\n return;\n }\n };\n\n const onInput = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (!role) return;\n const range = rangeForContext(ctx);\n if (role === \"font-family\") {\n const v = (el as HTMLSelectElement).value;\n // No reset — the syncActive() pass on the next selection/change\n // event puts the select back in sync with the actual run state.\n if (v) ctx.editor.applyRunProperties(range, { fontFamily: v });\n return;\n }\n if (role === \"font-size\") {\n const v = Number((el as HTMLSelectElement).value);\n if (Number.isFinite(v) && v > 0) {\n ctx.editor.applyRunProperties(range, { fontSizePt: v });\n }\n return;\n }\n if (role === \"color\") {\n ctx.editor.applyRunProperties(range, {\n color: (el as HTMLInputElement).value,\n });\n return;\n }\n if (role === \"highlight\") {\n ctx.editor.applyRunProperties(range, {\n highlight: (el as HTMLInputElement).value,\n });\n return;\n }\n };\n\n root.addEventListener(\"click\", onClick);\n root.addEventListener(\"input\", onInput);\n root.addEventListener(\"change\", onInput);\n // Selection- and change-driven sync. Subscribe through the editor so\n // we share its single document-level listener (instead of every text\n // tools instance attaching its own `selectionchange` handler). Both\n // events: `selection` for moving the caret, `change` for edits that\n // alter the AST (e.g. apply bold from a keyboard shortcut).\n const sync = () => syncActive(root, ctx);\n const detachSelection = ctx.editor.on(\"selection\", sync);\n const detachChange = ctx.editor.on(\"change\", sync);\n\n // Initial sync — reflect the caret's current formatting when the\n // toolbar opens.\n sync();\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n root.removeEventListener(\"input\", onInput);\n root.removeEventListener(\"change\", onInput);\n detachSelection();\n detachChange();\n };\n}\n\n/**\n * Repaint mark pressed-state and font / colour input values to reflect\n * the current selection. Call from `selection` + `change` events.\n *\n * Mark buttons read from the cascade-resolved `state.runProps` so a\n * caret in an H1 (whose style defaults `bold: true`) shows Bold as\n * pressed, even if the run itself has no explicit `bold` override.\n */\nfunction syncActive(root: HTMLElement, ctx: ToolContext): void {\n const state = readSelectionState(ctx.editor);\n const btns = root.querySelectorAll<HTMLButtonElement>(\n 'button[data-action=\"wrap\"][data-tag]',\n );\n for (const btn of btns) {\n const tag = btn.getAttribute(\"data-tag\");\n if (!tag || tag === \"mark\") continue;\n const prop = MARK_PROP[tag];\n const expected = MARK_ON[tag];\n const on = prop !== undefined && (state.runProps as RunProperties)[prop] === expected;\n btn.setAttribute(\"aria-pressed\", String(on));\n btn.classList.toggle(\"is-active\", on);\n }\n\n // Font / colour inputs reflect the run-level state at the caret.\n // For mixed selections, dropdowns go to their placeholder (\"Font\" /\n // \"Size\") and colour inputs hold their last-set value (no native\n // way to render \"indeterminate\" on <input type=color>).\n const fontFamily = root.querySelector<HTMLSelectElement>(\n 'select[data-role=\"font-family\"]',\n );\n if (fontFamily) {\n const v = state.runProps.fontFamily ?? \"\";\n fontFamily.value = optionMatch(fontFamily, v) ? v : \"\";\n }\n const fontSize = root.querySelector<HTMLSelectElement>(\n 'select[data-role=\"font-size\"]',\n );\n if (fontSize) {\n const sz = state.runProps.fontSizePt;\n const v = sz === undefined ? \"\" : String(sz);\n fontSize.value = optionMatch(fontSize, v) ? v : \"\";\n }\n const color = root.querySelector<HTMLInputElement>(\n 'input[data-role=\"color\"]',\n );\n if (color && state.runProps.color) color.value = state.runProps.color;\n const highlight = root.querySelector<HTMLInputElement>(\n 'input[data-role=\"highlight\"]',\n );\n if (highlight && state.runProps.highlight) {\n highlight.value = state.runProps.highlight;\n }\n}\n\nfunction optionMatch(sel: HTMLSelectElement, value: string): boolean {\n for (const opt of Array.from(sel.options)) {\n if (opt.value === value) return true;\n }\n return false;\n}\n\n/**\n * Use the live DOM selection if one exists; otherwise fall back to a\n * range covering the whole target block. Matches the \"click Bold with\n * caret only → bold the whole paragraph\" UX.\n */\nfunction rangeForContext(ctx: ToolContext): ApiRange {\n const sel = ctx.editor.selection.currentRange();\n if (sel) return sel;\n return rangeForBlock(ctx.editor, ctx.target);\n}\n\nfunction rangeForBlock(editor: Editor, _target: BlockTarget): ApiRange {\n // Pragmatic: use the current selection's block ref as the anchor —\n // clicking the indicator has already placed the caret inside the\n // target block, so the caret's block ref is the right one. Fall back\n // to the first body block if selection is somehow missing.\n const caret = editor.selection.currentCaret();\n const block: BlockRef = caret?.block ?? editor.getBlocks()[0]!;\n const info = editor.getBlockById(block.id);\n const length = info?.length ?? 0;\n return {\n from: { block, offset: 0 },\n to: { block, offset: length },\n };\n}\n","import {\n PAGE_SIZES,\n type Editor,\n type PageSetup,\n type PageSizeKey,\n type VerticalAlign,\n} from \"@sobree/core\";\n\n/**\n * Section-aware Page & Section Setup popover.\n *\n * Two groups of controls:\n *\n * 1. Page properties — paper size, orientation, margins. These live\n * in `PageSetup.size / orientation / margins` and apply to the\n * selected section.\n * 2. Section properties — vertical alignment, different first /\n * last page header / footer flags, and an \"Insert section break\n * after current block\" affordance (dispatched through the\n * `section.insertBreakAfter` command registered by core).\n *\n * Edits apply live through `setSectionSetup` — the editor repaginates\n * and the paper visibly resizes per keystroke. Pre-selects the section\n * the caret currently sits in (computed by counting `section_break`\n * blocks before it).\n */\nexport interface PageSetupContext {\n editor: Editor;\n getSectionCount: () => number;\n getSectionSetup: (index: number) => PageSetup;\n setSectionSetup: (index: number, partial: Partial<PageSetup>) => void;\n}\n\nconst PAGE_SIZE_KEYS: PageSizeKey[] = [\n \"A3\",\n \"A4\",\n \"A5\",\n \"B5\",\n \"Letter\",\n \"Legal\",\n \"Tabloid\",\n];\n\nconst MARGIN_FIELDS = [\"top\", \"right\", \"bottom\", \"left\"] as const;\n\nconst VERTICAL_ALIGNS: VerticalAlign[] = [\"top\", \"center\", \"bottom\", \"both\"];\n\nexport function openPageSetupPopover(\n trigger: HTMLElement,\n ctx: PageSetupContext,\n onClose: () => void,\n): () => void {\n const sectionCount = Math.max(1, ctx.getSectionCount());\n let activeIndex = clampIndex(detectCurrentSection(ctx.editor), sectionCount);\n\n // Capture the editor's selection BEFORE we move focus into the\n // popover. Used by the \"Insert section break\" button to restore the\n // caret so the underlying command (which reads `currentCaret()`) has\n // somewhere to insert.\n const savedSelection = ctx.editor.selection.get();\n\n const popover = document.createElement(\"div\");\n popover.className = \"sobree-page-setup-popover sobree-change-popover\";\n popover.setAttribute(\"role\", \"dialog\");\n popover.setAttribute(\"aria-label\", \"Page and section setup\");\n popover.tabIndex = -1;\n document.body.appendChild(popover);\n\n // Section break can only be inserted at a real caret position. The\n // command is registered by core's always-on `attachSections` plugin.\n const canInsertBreak = ctx.editor.commands.has(\"section.insertBreakAfter\");\n\n const render = () => {\n const setup = ctx.getSectionSetup(activeIndex);\n popover.innerHTML = buildHtml(sectionCount, activeIndex, setup, canInsertBreak);\n wire(\n popover,\n ctx,\n () => activeIndex,\n (next) => {\n activeIndex = next;\n render(); // re-render so the form reflects the newly-selected section\n },\n () => close(),\n savedSelection,\n );\n };\n\n render();\n\n // Position below the trigger, clamped to the viewport.\n const triggerRect = trigger.getBoundingClientRect();\n const POPOVER_WIDTH = 320;\n const left = Math.max(\n 8,\n Math.min(window.innerWidth - POPOVER_WIDTH - 8, triggerRect.left),\n );\n popover.style.top = `${triggerRect.bottom + 6}px`;\n popover.style.left = `${left}px`;\n // Trigger the .is-open transition (mirrors sobree-change-popover) on\n // the next frame so the fade-in actually animates instead of being\n // skipped by the initial paint.\n requestAnimationFrame(() => popover.classList.add(\"is-open\"));\n\n // Click-away closes (but ignore clicks inside the popover or on the\n // trigger button — those are handled internally / open-toggling).\n const onDown = (e: MouseEvent) => {\n const t = e.target as Node;\n if (popover.contains(t)) return;\n if (trigger.contains(t)) return;\n close();\n };\n // Defer the listener install so the click that opened us doesn't\n // immediately close us.\n const installTimer = window.setTimeout(() => {\n document.addEventListener(\"mousedown\", onDown, true);\n }, 0);\n\n // Esc closes.\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n close();\n }\n };\n popover.addEventListener(\"keydown\", onKey);\n\n // Focus the section picker (or the first form field) for keyboard nav.\n queueMicrotask(() => {\n const first =\n popover.querySelector<HTMLElement>(\"select, input\") ?? popover;\n first.focus();\n });\n\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n window.clearTimeout(installTimer);\n document.removeEventListener(\"mousedown\", onDown, true);\n popover.removeEventListener(\"keydown\", onKey);\n popover.remove();\n onClose();\n };\n\n return close;\n}\n\n// === markup ===\n\nfunction buildHtml(\n sectionCount: number,\n activeIndex: number,\n setup: PageSetup,\n canInsertBreak: boolean,\n): string {\n const sectionPicker =\n sectionCount > 1\n ? `\n <label class=\"ps-row\">\n <span class=\"ps-label\">Section</span>\n <select data-field=\"section\">\n ${Array.from({ length: sectionCount }, (_, i) => {\n const sel = i === activeIndex ? \" selected\" : \"\";\n return `<option value=\"${i}\"${sel}>Section ${i + 1}</option>`;\n }).join(\"\")}\n </select>\n </label>`\n : \"\";\n\n // Page properties group — paper size, orientation, margins.\n const pageGroup = `\n <div class=\"ps-section-header\">Page</div>\n <div class=\"ps-grid\">\n <label class=\"ps-row\">\n <span class=\"ps-label\">Size</span>\n <select data-field=\"size\">\n ${PAGE_SIZE_KEYS.map((k) => {\n const sel = k === setup.size ? \" selected\" : \"\";\n const dim = PAGE_SIZES[k];\n return `<option value=\"${k}\"${sel}>${k} (${dim.width} × ${dim.height} mm)</option>`;\n }).join(\"\")}\n </select>\n </label>\n <label class=\"ps-row\">\n <span class=\"ps-label\">Orientation</span>\n <select data-field=\"orientation\">\n <option value=\"portrait\"${setup.orientation === \"portrait\" ? \" selected\" : \"\"}>Portrait</option>\n <option value=\"landscape\"${setup.orientation === \"landscape\" ? \" selected\" : \"\"}>Landscape</option>\n </select>\n </label>\n <div class=\"ps-row ps-margins\">\n <span class=\"ps-label\">Margins (mm)</span>\n <div class=\"ps-margin-grid\">\n ${MARGIN_FIELDS.map(\n (side) => `\n <label class=\"ps-margin\">\n <span>${side}</span>\n <input type=\"number\" min=\"0\" step=\"1\" data-field=\"margin-${side}\" value=\"${setup.margins[side]}\" />\n </label>`,\n ).join(\"\")}\n </div>\n </div>\n </div>`;\n\n // Section properties group — vAlign + page-variation flags + insert-break.\n const differentFirst = setup.header.differentFirst || setup.footer.differentFirst;\n const differentLast = setup.header.differentLast || setup.footer.differentLast;\n const sectionGroup = `\n <div class=\"ps-section-header\">Section</div>\n <div class=\"ps-grid\">\n <label class=\"ps-row\">\n <span class=\"ps-label\">Vertical alignment</span>\n <select data-field=\"vertical-align\">\n ${VERTICAL_ALIGNS.map((v) => {\n const sel = v === setup.verticalAlign ? \" selected\" : \"\";\n return `<option value=\"${v}\"${sel}>${capitalize(v)}</option>`;\n }).join(\"\")}\n </select>\n </label>\n <label class=\"ps-row ps-checkbox\">\n <input type=\"checkbox\" data-field=\"different-first\"${differentFirst ? \" checked\" : \"\"} />\n <span>Different header / footer for first page</span>\n </label>\n <label class=\"ps-row ps-checkbox\">\n <input type=\"checkbox\" data-field=\"different-last\"${differentLast ? \" checked\" : \"\"} />\n <span>Different header / footer for last page</span>\n </label>\n ${\n canInsertBreak\n ? `<button type=\"button\" class=\"ps-insert-break\" data-action=\"insert-section-break\">\n Insert section break after current block\n </button>`\n : \"\"\n }\n </div>`;\n\n return `\n <div class=\"ps-header\">Page & section setup</div>\n ${sectionPicker}\n ${pageGroup}\n ${sectionGroup}\n `;\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n// === wiring ===\n\nfunction wire(\n root: HTMLElement,\n ctx: PageSetupContext,\n getActive: () => number,\n onSectionChange: (index: number) => void,\n closePopover: () => void,\n savedSelection: ReturnType<Editor[\"selection\"][\"get\"]>,\n): void {\n // Section picker — switching sections re-renders the form.\n const sectionEl = root.querySelector<HTMLSelectElement>(\n 'select[data-field=\"section\"]',\n );\n if (sectionEl) {\n sectionEl.addEventListener(\"change\", () => {\n const next = Number(sectionEl.value);\n if (Number.isFinite(next)) onSectionChange(next);\n });\n }\n\n // Size + orientation — apply on change.\n const sizeEl = root.querySelector<HTMLSelectElement>(\n 'select[data-field=\"size\"]',\n );\n sizeEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), { size: sizeEl.value as PageSizeKey });\n });\n\n const orientationEl = root.querySelector<HTMLSelectElement>(\n 'select[data-field=\"orientation\"]',\n );\n orientationEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), {\n orientation: orientationEl.value as PageSetup[\"orientation\"],\n });\n });\n\n // Margins — debounced live updates so the paper resizes as the user\n // pauses typing, but each keystroke doesn't trigger a setDocument\n // round-trip (which steals focus back to the editor and would yank\n // the caret out of the input mid-typing). Also commits immediately\n // on `change` (blur / Enter / spinner click) so explicit commits feel\n // snappy.\n for (const side of MARGIN_FIELDS) {\n const input = root.querySelector<HTMLInputElement>(\n `input[data-field=\"margin-${side}\"]`,\n );\n if (!input) continue;\n const apply = () => {\n const value = Number(input.value);\n if (!Number.isFinite(value) || value < 0) return;\n const current = ctx.getSectionSetup(getActive());\n // Save caret state, then restore focus + caret AFTER setDocument\n // re-applies the editor selection (which steals focus on its\n // own microtask). Without this the input loses focus on every\n // commit while the user is still typing.\n const wasActive = document.activeElement === input;\n const caretStart = input.selectionStart;\n const caretEnd = input.selectionEnd;\n ctx.setSectionSetup(getActive(), {\n margins: { ...current.margins, [side]: value },\n });\n if (wasActive) {\n // Two rAFs: first clears the synchronous setDocument focus\n // restore; second handles any deferred work that runs in the\n // following microtask (e.g. paginator's selection re-apply).\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (document.activeElement !== input) input.focus();\n if (caretStart !== null && caretEnd !== null) {\n try {\n input.setSelectionRange(caretStart, caretEnd);\n } catch {\n /* number inputs may reject setSelectionRange in some\n browsers — silent fallback is fine, focus is what\n matters. */\n }\n }\n });\n });\n }\n };\n const debouncedApply = debounce(apply, 1000);\n input.addEventListener(\"input\", debouncedApply);\n // Explicit commit on blur / Enter / spinner so the user can short-\n // circuit the debounce by tabbing away or pressing Enter.\n input.addEventListener(\"change\", apply);\n }\n\n // Vertical alignment — apply on change.\n const vAlignEl = root.querySelector<HTMLSelectElement>(\n 'select[data-field=\"vertical-align\"]',\n );\n vAlignEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), {\n verticalAlign: vAlignEl.value as VerticalAlign,\n });\n });\n\n // Different first / last header & footer — apply on change. Mirror\n // the flag onto BOTH header and footer; that's how Word presents the\n // checkboxes to the user (per-section, not per-zone).\n const differentFirst = root.querySelector<HTMLInputElement>(\n 'input[data-field=\"different-first\"]',\n );\n differentFirst?.addEventListener(\"change\", () => {\n const current = ctx.getSectionSetup(getActive());\n const checked = differentFirst.checked;\n ctx.setSectionSetup(getActive(), {\n header: { ...current.header, differentFirst: checked },\n footer: { ...current.footer, differentFirst: checked },\n });\n });\n\n const differentLast = root.querySelector<HTMLInputElement>(\n 'input[data-field=\"different-last\"]',\n );\n differentLast?.addEventListener(\"change\", () => {\n const current = ctx.getSectionSetup(getActive());\n const checked = differentLast.checked;\n ctx.setSectionSetup(getActive(), {\n header: { ...current.header, differentLast: checked },\n footer: { ...current.footer, differentLast: checked },\n });\n });\n\n // Insert section break — runs the bus command, then closes the\n // popover so the user can see the result. New section appears as the\n // next entry in the section picker on the next open.\n const insertBreakBtn = root.querySelector<HTMLButtonElement>(\n 'button[data-action=\"insert-section-break\"]',\n );\n insertBreakBtn?.addEventListener(\"click\", (e) => {\n e.preventDefault();\n // Restore the editor selection that was live when the popover\n // opened — the command reads `editor.selection.currentCaret()` and\n // the popover stole focus on open. Without this the command silently\n // no-ops.\n if (savedSelection) ctx.editor.selection.set(savedSelection);\n ctx.editor.commands.execute(\"section.insertBreakAfter\");\n closePopover();\n });\n}\n\n// === section detection ===\n\n/**\n * Section the caret is currently in. We count `section_break` blocks\n * up to (and not including) the caret's block; each break advances the\n * section index by one.\n *\n * Falls back to section 0 when there's no caret (e.g. just-mounted\n * editor with no focus yet).\n */\nfunction detectCurrentSection(editor: Editor): number {\n const caret = editor.selection.currentBlock();\n if (!caret) return 0;\n const blocks = editor.getBlocks();\n let section = 0;\n for (const b of blocks) {\n if (b.id === caret.id) return section;\n if (b.kind === \"section_break\") section += 1;\n }\n return section;\n}\n\nfunction clampIndex(index: number, count: number): number {\n if (!Number.isFinite(index) || index < 0) return 0;\n if (index >= count) return Math.max(0, count - 1);\n return index;\n}\n\n/** Trailing-edge debounce — fn runs once, `ms` after the last call. */\nfunction debounce(fn: () => void, ms: number): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n return () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n fn();\n }, ms);\n };\n}\n","import \"./blockTools.css\";\nimport { type BlockTarget } from \"./blockKinds\";\nimport type { BlockRef } from \"@sobree/core\";\nimport { BlockIndicator } from \"./indicator\";\nimport { FloatingToolbar } from \"./toolbar\";\nimport {\n type ChangeTypeContext,\n buildChangeTypeButton,\n openChangeTypePopover,\n} from \"./tools/changeType\";\nimport { buildPerKindHtml, wirePerKindTools } from \"./tools/perKind\";\nimport {\n type CellLocation,\n type TableMode,\n buildTableToolsHtml,\n locateCellFromSelection,\n wireTableTools,\n} from \"./tools/table\";\nimport { buildTextToolsHtml, wireTextTools } from \"./tools/text\";\nimport { icon } from \"./tools/icons\";\nimport { openPageSetupPopover } from \"./tools/pageSetup\";\nimport { enterZoneEdit } from \"@sobree/core\";\nimport type { PageSetup } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { Viewport } from \"@sobree/core\";\n\nexport interface BlockToolsOptions {\n stackRoot: HTMLElement;\n editor: Editor;\n /** The scrollable area containing the stack (for toolbar positioning). */\n renderingArea: HTMLElement;\n /** Viewport handle — used for animated pans when the toolbar needs room. */\n viewport?: Viewport | null;\n getSetup: () => PageSetup;\n setSetup: (next: PageSetup) => void;\n /**\n * Multi-section setup (optional — defaults to single-section behaviour).\n * When all three are present, the Page setup popover renders a section\n * picker and edits the section the caret is in.\n */\n getSectionCount?: () => number;\n getSectionSetup?: (index: number) => PageSetup;\n setSectionSetup?: (index: number, partial: Partial<PageSetup>) => void;\n}\n\n/**\n * Floating block-tools orchestrator. Owns the left-gutter indicator and\n * the floating toolbar that opens above the active block.\n *\n * B1 landed the indicator + zone-edit hand-off. B2 adds the toolbar\n * shell with positioning + open/close animation. B3+ wire actual tools\n * into the shell per block kind.\n */\nexport class BlockTools {\n private readonly stackRoot: HTMLElement;\n private readonly editor: Editor;\n private readonly getSetup: () => PageSetup;\n private readonly setSetup: (s: PageSetup) => void;\n private readonly getSectionCount: () => number;\n private readonly getSectionSetup: (index: number) => PageSetup;\n private readonly setSectionSetup: (index: number, partial: Partial<PageSetup>) => void;\n private closePageSetupPopover: (() => void) | null = null;\n private readonly indicator: BlockIndicator;\n private readonly toolbar: FloatingToolbar;\n private zoneEditing = false;\n /** Currently-open zone-edit `finish()` (returned by `enterZoneEdit`),\n * or null when not editing. Lets a second indicator click commit. */\n private exitZoneEdit: (() => void) | null = null;\n private suspended = false;\n private detachTools: (() => void) | null = null;\n private closeChangePopover: (() => void) | null = null;\n /** Current table-toolbar mode (Cell vs Table). Reset when the toolbar closes. */\n private tableMode: TableMode = \"cell\";\n private readonly onDocumentDownFn = (e: MouseEvent) => this.onDocumentDown(e);\n\n constructor(opts: BlockToolsOptions) {\n this.stackRoot = opts.stackRoot;\n this.editor = opts.editor;\n this.getSetup = opts.getSetup;\n this.setSetup = opts.setSetup;\n // Section APIs default to a single-section view backed by getSetup /\n // setSetup. Embedders that pass the multi-section trio (createSobree\n // does) get section-aware editing for free.\n this.getSectionCount = opts.getSectionCount ?? (() => 1);\n this.getSectionSetup = opts.getSectionSetup ?? ((_) => opts.getSetup());\n this.setSectionSetup =\n opts.setSectionSetup ??\n ((index, partial) => {\n if (index !== 0) return;\n opts.setSetup({ ...opts.getSetup(), ...partial });\n });\n\n this.toolbar = new FloatingToolbar({\n stackRoot: this.stackRoot,\n renderingArea: opts.renderingArea,\n viewport: opts.viewport ?? null,\n });\n\n this.indicator = new BlockIndicator({\n stackRoot: this.stackRoot,\n editor: this.editor,\n onActivate: (target) => this.handleActivate(target),\n });\n\n // Click-away closes the toolbar.\n document.addEventListener(\"mousedown\", this.onDocumentDownFn, true);\n }\n\n destroy(): void {\n document.removeEventListener(\"mousedown\", this.onDocumentDownFn, true);\n this.toolbar.destroy();\n this.indicator.destroy();\n }\n\n /** Call after pagination / zoom changes so the indicator + toolbar re-align. */\n refresh(): void {\n this.indicator.refresh();\n if (this.toolbar.isOpen()) this.toolbar.reposition();\n }\n\n /**\n * Suspend all UI — indicator hidden, toolbar closed, interactions\n * ignored. Used when the host switches Sobree into read mode.\n */\n setSuspended(suspended: boolean): void {\n if (this.suspended === suspended) return;\n this.suspended = suspended;\n if (suspended) {\n this.closeToolbar();\n this.indicator.setEnabled(false);\n } else {\n this.indicator.setEnabled(true);\n }\n }\n\n private handleActivate(target: BlockTarget): void {\n if (this.suspended) return;\n if (this.zoneEditing) {\n // Second click on the indicator while editing the same zone → commit\n // and exit. Clicking the indicator on a *different* block does\n // nothing while editing — the click-outside handler in zoneEdit will\n // commit, then BlockTools is free to act on the new target.\n this.exitZoneEdit?.();\n return;\n }\n if (target.kind === \"header\" || target.kind === \"footer\") {\n this.enterZoneEdit(target.element, target.kind);\n return;\n }\n\n // Toggle: click the same indicator again → close.\n if (this.toolbar.isOpen() && this.toolbar.getTarget()?.element === target.element) {\n this.closeToolbar();\n return;\n }\n\n this.openToolbar(target);\n }\n\n private openToolbar(target: BlockTarget): void {\n this.detachTools?.();\n\n const changeCtx = this.buildChangeTypeContext(target);\n\n if (target.kind === \"table\") {\n // Table toolbar builds its own content (text tools + pill + ops).\n // Append the change-type trigger at the end.\n this.renderTableToolbar(target, changeCtx);\n } else {\n const isMulti = changeCtx.refs.length > 1;\n // Multi-block: text tools only (no per-kind primary tools).\n const perKindHtml = isMulti ? \"\" : buildPerKindHtml(target.kind);\n this.toolbar.setContent(\n buildTextToolsHtml() +\n perKindHtml +\n buildChangeTypeButton(changeCtx.refs.length) +\n this.buildTrailingToolsHtml(),\n );\n this.toolbar.open(target);\n const detachText = wireTextTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachPerKind = isMulti\n ? () => {}\n : wirePerKindTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachChange = this.wireChangeTypeTrigger(changeCtx);\n const detachTrailing = this.wireTrailingTools();\n this.detachTools = () => {\n detachText();\n detachPerKind();\n detachChange();\n detachTrailing();\n };\n }\n this.indicator.setActive(true);\n }\n\n /**\n * Resolve which blocks the \"change type\" drill-down should apply to.\n * Multi-block range selections return the full chain of block refs\n * between `from` and `to` (inclusive). Everything else returns the\n * single block under the caret / indicator.\n */\n private buildChangeTypeContext(target: BlockTarget): ChangeTypeContext {\n const range = this.editor.selection.currentRange();\n const refs: BlockRef[] = [];\n if (range && range.from.block.id !== range.to.block.id) {\n // Collect every block from the first to the last selected.\n const blocks = this.editor.getBlocks();\n const fromIdx = blocks.findIndex((b) => b.id === range.from.block.id);\n const toIdx = blocks.findIndex((b) => b.id === range.to.block.id);\n if (fromIdx >= 0 && toIdx >= 0) {\n const lo = Math.min(fromIdx, toIdx);\n const hi = Math.max(fromIdx, toIdx);\n for (let i = lo; i <= hi; i++) {\n const b = blocks[i];\n if (b) refs.push({ id: b.id, version: b.version });\n }\n }\n }\n if (refs.length === 0) {\n const caret = this.editor.selection.currentBlock();\n if (caret) refs.push(caret);\n else {\n const first = this.editor.getBlocks()[0];\n if (first) refs.push({ id: first.id, version: first.version });\n }\n }\n return { editor: this.editor, target, refs };\n }\n\n private wireChangeTypeTrigger(ctx: ChangeTypeContext): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\n 'button[data-action=\"open-change-type\"]',\n ) as HTMLButtonElement | null;\n if (!btn) return;\n e.preventDefault();\n // Toggle: if already open, close. Otherwise open. Mirror the\n // open/closed state via `aria-expanded` so screen readers know\n // whether activating the button will open or close the menu.\n if (this.closeChangePopover) {\n this.closeChangePopover();\n return;\n }\n btn.setAttribute(\"aria-expanded\", \"true\");\n this.closeChangePopover = openChangeTypePopover(btn, ctx, () => {\n btn.setAttribute(\"aria-expanded\", \"false\");\n this.closeChangePopover = null;\n });\n };\n this.toolbar.root.addEventListener(\"click\", onClick);\n return () => this.toolbar.root.removeEventListener(\"click\", onClick);\n }\n\n /**\n * Table-specific toolbar: text tools on the left, Cell/Table pill in\n * the middle, mode-specific ops on the right. Mode defaults to Cell\n * when the caret is in a cell, Table otherwise.\n */\n private renderTableToolbar(target: BlockTarget, changeCtx: ChangeTypeContext): void {\n const cell = this.resolveCell(target);\n this.tableMode = cell ? \"cell\" : \"table\";\n this.renderTableToolbarBody(target, cell, changeCtx);\n this.toolbar.open(target);\n }\n\n private renderTableToolbarBody(\n target: BlockTarget,\n cell: CellLocation | null,\n changeCtx: ChangeTypeContext,\n ): void {\n // Tear down any prior wiring first — otherwise each mode toggle\n // leaves another live listener on the root, and clicks start firing\n // multiple times.\n this.detachTools?.();\n this.toolbar.setContent(\n buildTextToolsHtml() +\n buildTableToolsHtml(this.tableMode, !!cell) +\n buildChangeTypeButton(changeCtx.refs.length) +\n this.buildTrailingToolsHtml(),\n );\n const detachText = wireTextTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachTable = wireTableTools(\n this.toolbar.root,\n { editor: this.editor, target, cell },\n (nextMode) => {\n this.tableMode = nextMode;\n this.renderTableToolbarBody(target, this.resolveCell(target), changeCtx);\n },\n );\n const detachChange = this.wireChangeTypeTrigger(changeCtx);\n const detachTrailing = this.wireTrailingTools();\n this.detachTools = () => {\n detachText();\n detachTable();\n detachChange();\n detachTrailing();\n };\n }\n\n /**\n * Tools that sit at the very right of every toolbar variant — global\n * affordances that aren't tied to the active block. Currently:\n * - Page setup — always present; opens an internal section-aware\n * popover. If the host has registered a `page-setup.open` command,\n * that wins (lets embedders ship their own modal).\n */\n private buildTrailingToolsHtml(): string {\n const useHostCommand = this.editor.commands.has(\"page-setup.open\");\n const action = useHostCommand\n ? `data-action=\"exec-command\" data-command=\"page-setup.open\"`\n : `data-action=\"open-page-setup\" aria-haspopup=\"dialog\" aria-expanded=\"false\"`;\n const pageSetupBtn = `<button type=\"button\" ${action} title=\"Page & section setup\" aria-label=\"Page and section setup\">${icon(\"page-setup\")}</button>`;\n // Track-changes pill — same place as page-setup so it's present in\n // every block-type toolbar (paragraph, heading, list, image, table,\n // multi-select). Toggles `editor.trackChanges.enabled` and mirrors\n // its visual state via the editor's `track-changes-change` event.\n // Author identity is *not* changed here: the toggle preserves whatever\n // author the embedder has set via `editor.setTrackChanges(...)`. If\n // none was set, authored revisions land with no author field\n // (Word's \"anonymous tracked change\").\n const tc = this.editor.getTrackChanges();\n const pressed = tc.enabled ? \"true\" : \"false\";\n const activeCls = tc.enabled ? \" is-active\" : \"\";\n const tcLabel = tc.enabled ? \"Track changes (on)\" : \"Track changes (off)\";\n const tcBtn = `<button type=\"button\" class=\"tb-action${activeCls}\" data-action=\"toggle-track-changes\" aria-pressed=\"${pressed}\" title=\"${tcLabel}\" aria-label=\"${tcLabel}\">${icon(\"track-changes\")}</button>`;\n // Author identity input — only rendered when track-changes is on,\n // so the toolbar isn't cluttered by an input the user doesn't need.\n // Writes through `editor.setTrackChanges` keeping `enabled: true`\n // so the pill stays lit. The placeholder (\"Anonymous\") matches\n // Word's behaviour for an unset author.\n const authorValue = tc.author !== undefined\n ? escapeHtmlAttr(tc.author)\n : \"\";\n const authorInput = tc.enabled\n ? `<input type=\"text\" class=\"tb-author-input\" data-role=\"track-changes-author\" value=\"${authorValue}\" placeholder=\"Anonymous\" title=\"Track changes author\" aria-label=\"Track changes author\" maxlength=\"60\" />`\n : \"\";\n return `<div class=\"tb-divider\"></div><div class=\"tb-group\" data-group=\"trailing\">${tcBtn}${authorInput}${pageSetupBtn}</div>`;\n }\n\n /**\n * Delegated click handler for trailing-toolbar buttons. Two flavours:\n * - `data-action=\"exec-command\"` dispatches through the command bus\n * (same path keyboard / MCP / agent would use).\n * - `data-action=\"open-page-setup\"` opens the built-in section-aware\n * popover, anchored to the clicked button.\n */\n private wireTrailingTools(): () => void {\n // Keep the pill's pressed state + author input in sync with editor\n // state. A toggle from anywhere (the API, another toolbar instance,\n // a keyboard plugin) flows through here. The author input is\n // dynamically inserted/removed by `buildTrailingToolsHtml` based on\n // the enabled flag — but since we don't re-run that during a live\n // toolbar session, we manage the input element imperatively here\n // so it appears/disappears on toggle without re-rendering.\n const syncTrackChangesBtn = () => {\n const state = this.editor.getTrackChanges();\n const pressed = state.enabled;\n // The pill needs to surface TWO things: the mode flag (on/off)\n // AND whether there are unresolved revisions in the doc. The\n // user can toggle the mode off and still have pending revisions\n // they need to act on — without this hint, the pill looks like\n // \"all done\" when it isn't. The numeric count is set as a data\n // attribute that CSS reads to render a small badge over the\n // pill icon; the full text is in the title for screen readers\n // and hover.\n const unresolved = this.editor.getRevisions().length;\n const modeStr = pressed ? \"on\" : \"off\";\n const label =\n unresolved > 0\n ? `Track changes (${modeStr} · ${unresolved} unresolved)`\n : `Track changes (${modeStr})`;\n for (const btn of Array.from(\n this.toolbar.root.querySelectorAll<HTMLButtonElement>(\n 'button[data-action=\"toggle-track-changes\"]',\n ),\n )) {\n btn.classList.toggle(\"is-active\", pressed);\n btn.classList.toggle(\"has-unresolved\", unresolved > 0);\n btn.setAttribute(\"aria-pressed\", String(pressed));\n if (unresolved > 0) {\n btn.dataset.unresolvedCount = String(unresolved);\n } else {\n delete btn.dataset.unresolvedCount;\n }\n btn.title = label;\n btn.setAttribute(\"aria-label\", label);\n // Show/hide the author input alongside the pill.\n const trailingGroup = btn.parentElement;\n if (!trailingGroup) continue;\n let input = trailingGroup.querySelector<HTMLInputElement>(\n 'input[data-role=\"track-changes-author\"]',\n );\n if (pressed && !input) {\n input = document.createElement(\"input\");\n input.type = \"text\";\n input.className = \"tb-author-input\";\n input.dataset.role = \"track-changes-author\";\n input.placeholder = \"Anonymous\";\n input.title = \"Track changes author\";\n input.setAttribute(\"aria-label\", \"Track changes author\");\n input.maxLength = 60;\n input.value = state.author ?? \"\";\n btn.insertAdjacentElement(\"afterend\", input);\n } else if (!pressed && input) {\n input.remove();\n } else if (input && input.value !== (state.author ?? \"\")) {\n // External state change (API call set a different author) —\n // reflect it, unless the input is currently focused (don't\n // stomp the user's in-progress typing).\n if (document.activeElement !== input) {\n input.value = state.author ?? \"\";\n }\n }\n }\n };\n const detachTrackChanges = this.editor.on(\n \"track-changes-change\",\n syncTrackChangesBtn,\n );\n // Also re-sync on every doc `change` so the unresolved-count\n // badge tracks revisions being authored, accepted, or rejected\n // from anywhere — not just from this toolbar's pill.\n const detachChange = this.editor.on(\"change\", syncTrackChangesBtn);\n // Initial paint covers the case where the toolbar just opened and\n // we haven't received an event since.\n syncTrackChangesBtn();\n\n // Author input — write through `editor.setTrackChanges` on every\n // input event (cheap; the editor de-dupes identical states).\n // Trim and treat the empty string as \"anonymous\" (no author field).\n const onAuthorInput = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"track-changes-author\") return;\n const raw = (el as HTMLInputElement).value.trim();\n const cur = this.editor.getTrackChanges();\n this.editor.setTrackChanges(\n raw === \"\"\n ? { enabled: cur.enabled }\n : { enabled: cur.enabled, author: raw },\n );\n };\n this.toolbar.root.addEventListener(\"input\", onAuthorInput);\n\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\n 'button[data-action=\"exec-command\"], button[data-action=\"open-page-setup\"], button[data-action=\"toggle-track-changes\"]',\n ) as HTMLButtonElement | null;\n if (!btn) return;\n e.preventDefault();\n const action = btn.getAttribute(\"data-action\");\n if (action === \"exec-command\") {\n const name = btn.getAttribute(\"data-command\");\n if (name) this.editor.commands.execute(name);\n return;\n }\n if (action === \"toggle-track-changes\") {\n const cur = this.editor.getTrackChanges();\n // Preserve author when flipping the enabled bit — see the\n // pill's docblock in buildTrailingToolsHtml for rationale.\n this.editor.setTrackChanges(\n cur.author === undefined\n ? { enabled: !cur.enabled }\n : { enabled: !cur.enabled, author: cur.author },\n );\n return;\n }\n // Built-in page-setup popover — toggle.\n if (this.closePageSetupPopover) {\n this.closePageSetupPopover();\n return;\n }\n btn.setAttribute(\"aria-expanded\", \"true\");\n this.closePageSetupPopover = openPageSetupPopover(\n btn,\n {\n editor: this.editor,\n getSectionCount: this.getSectionCount,\n getSectionSetup: this.getSectionSetup,\n setSectionSetup: this.setSectionSetup,\n },\n () => {\n btn.setAttribute(\"aria-expanded\", \"false\");\n this.closePageSetupPopover = null;\n },\n );\n };\n this.toolbar.root.addEventListener(\"click\", onClick);\n return () => {\n this.toolbar.root.removeEventListener(\"click\", onClick);\n this.toolbar.root.removeEventListener(\"input\", onAuthorInput);\n detachTrackChanges();\n detachChange();\n };\n }\n\n private resolveCell(target: BlockTarget): CellLocation | null {\n if (target.element.tagName.toLowerCase() !== \"table\") return null;\n return locateCellFromSelection(target.element as HTMLTableElement);\n }\n\n private closeToolbar(): void {\n this.closeChangePopover?.();\n this.closeChangePopover = null;\n this.closePageSetupPopover?.();\n this.closePageSetupPopover = null;\n this.detachTools?.();\n this.detachTools = null;\n this.toolbar.close();\n this.indicator.setActive(false);\n }\n\n private onDocumentDown(e: MouseEvent): void {\n if (!this.toolbar.isOpen()) return;\n const target = e.target as Node;\n if (this.toolbar.root.contains(target)) return;\n if (e.target instanceof HTMLElement) {\n // Don't close if the click lands on the indicator — that's a toggle.\n if (this.indicator.getCurrent() && e.target.closest(\".sobree-block-indicator\")) return;\n // Don't close if the click lands inside the open change-block popover\n // (or any toolbar-spawned popover that lives on document.body). The\n // popover removes `is-open` on close, which flips it back to\n // `pointer-events: none` — a premature close here would swallow\n // the user's click before its applyTarget handler runs.\n if (e.target.closest(\".sobree-change-popover\")) return;\n // Same for the page-setup popover (it reuses the change-popover\n // class for layout but has its own dialog role).\n if (e.target.closest(\".sobree-page-setup-popover\")) return;\n }\n this.closeToolbar();\n }\n\n private enterZoneEdit(zone: HTMLElement, kind: \"header\" | \"footer\"): void {\n this.zoneEditing = true;\n this.indicator.setActive(true);\n this.exitZoneEdit = enterZoneEdit({\n zone,\n kind,\n stackRoot: this.stackRoot,\n getSetup: this.getSetup,\n setSetup: this.setSetup,\n onExit: () => {\n this.zoneEditing = false;\n this.exitZoneEdit = null;\n this.indicator.setActive(false);\n },\n });\n }\n}\n\n/**\n * Escape a value for safe interpolation into an HTML attribute. Used\n * for user-controlled values (e.g. the track-changes author name) so\n * a malicious or accidental string can't break out of the attribute\n * and inject other attributes / script.\n */\nfunction escapeHtmlAttr(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n","/**\n * `SobreePlugin` factory wrapping the `BlockTools` class. Hand the\n * result to `createSobree({ plugins: [...] })`; setup constructs a\n * `BlockTools` against the plugin context and destroy tears it down.\n *\n * For embedders who skipped `createSobree()`, the `BlockTools` class\n * itself is exported from this package — instantiate directly with\n * full `BlockToolsOptions`.\n */\n\nimport type { SobreePlugin } from \"@sobree/core\";\nimport { BlockTools, type BlockToolsOptions } from \"./blockTools\";\n\n/**\n * User-overridable subset of `BlockToolsOptions`. The non-overridable\n * fields (`stackRoot`, `editor`, `renderingArea`, `viewport`,\n * `getSetup`, `setSetup`) come from the plugin context — the user\n * can't sensibly override them.\n */\nexport type BlockToolsPluginOptions = Partial<\n Omit<\n BlockToolsOptions,\n | \"stackRoot\"\n | \"editor\"\n | \"renderingArea\"\n | \"viewport\"\n | \"getSetup\"\n | \"setSetup\"\n | \"getSectionCount\"\n | \"getSectionSetup\"\n | \"setSectionSetup\"\n >\n>;\n\nexport function blockTools(opts: BlockToolsPluginOptions = {}): SobreePlugin {\n return {\n name: \"block-tools\",\n setup({ editor, sobree, viewport, host }) {\n const tools = new BlockTools({\n stackRoot: sobree.stackRoot,\n editor,\n renderingArea: host,\n viewport,\n getSetup: () => sobree.getPageSetup(),\n setSetup: (next) => sobree.setPageSetup(next),\n // Section-aware page setup — feeds the popover's section picker\n // and applies edits to the section the caret lives in.\n getSectionCount: () => sobree.getSectionCount(),\n getSectionSetup: (index) => sobree.getSectionSetup(index),\n setSectionSetup: (index, partial) => sobree.setSectionSetup(index, partial),\n ...opts,\n });\n return { destroy: () => tools.destroy() };\n },\n };\n}\n"],"names":["SVG_PARAGRAPH","SVG_HEADING","SVG_LIST","SVG_LIST_ORDERED","SVG_QUOTE","SVG_TABLE","SVG_IMAGE","SVG_CHEVRON_UP","SVG_CHEVRON_DOWN","SVG_SECTION_BREAK","BLOCK_KINDS","iconSvg","info","blockTargetFrom","node","stackRoot","el","paper","header","footer","image","host","withBlockId","sectionBreak","table","heading","bq","li","parent","kind","p","t","id","blockTargetFromNode","BlockIndicator","opts","__publicField","e","active","enabled","fresh","target","block","sel","anchor","_a","scale","stackRect","paperRect","blockRect","left","top","FloatingToolbar","html","handler","rendArea","margin","maxWidth","tbHeight","blockTallerThanView","blockTopAboveView","stickToTop","roomAbove","needed","deficit","BOLD","ITALIC","UNDERLINE","STRIKE","SUPERSCRIPT","SUBSCRIPT","TYPE","PAINTBRUSH","HIGHLIGHTER","ERASER","CODE","ALIGN_LEFT","ALIGN_CENTER","ALIGN_RIGHT","ALIGN_JUSTIFY","INDENT_INCREASE","INDENT_DECREASE","LIST_BULLET","LIST_NUMBERED","TRASH","CHEVRON_DOWN_ICON","SETTINGS","PAGE_SETUP","SECTION_BREAK","TRACK_CHANGES","ICONS","icon","name","buildChangeTypeButton","refCount","label","openChangeTypePopover","trigger","ctx","onClose","currentKey","currentTargetKey","popover","buildPopoverHtml","triggerRect","items","close","onDocDown","item","raw","parseTarget","applyTarget","all","idx","_b","_c","_d","multi","key","isCurrent","iconInline","first","doc","numId","def","n","fmt","styleId","m","convertToTable","ref","applyConversion","editor","flattenTableToParagraph","refreshed","convertBlockToList","parts","row","cell","inner","run","text","next","format","existing","nextNumbering","d","_omit","cleanProps","nextBlock","nextBody","nextDoc","empty","withExistingRuns","EMPTY_STATE","readSelectionState","caret","ownRunProps","resolveRunProps","runDefaults","resolveStyleCascade","resolveListFormat","numbering","range","lo","hi","touched","textRunsInRange","intersectProps","r","runPropsAtOffset","runs","out","cursor","walk","list","start","len","runLen","end","offset","lastTextProps","lastTextRunProps","last","snapshots","k","v","agree","i","buildPerKindHtml","extras","buildAlignmentHtml","buildHeadingLevelHtml","buildListHtml","buildImageHtml","wirePerKindTools","root","handlers","onAlign","btn","arg","alignment","applyToTargetBlocks","result","warnOnEditFailure","onLineSpacing","mult","onHeadingLevel","level","refForTarget","onListAction","action","toggleListKind","applyAlignmentToWholeList","onImageInput","alt","updateImageAltAtTarget","onImageClick","deleteImageAtTarget","syncAll","syncPerKindState","detachSelection","detachChange","detach","state","align","alignBtns","on","lineSel","sp","matched","optionByValue","headingSel","listToggle","isOrdered","value","opt","_kind","fn","explicitId","targetBlock","targetNumId","prev","endExclusive","refs","blk","liInfo","currentDef","wantBullet","buildTableToolsHtml","mode","hasCell","pill","buildCellOpsHtml","buildTableOpsHtml","wireTableTools","onModeChange","onClick","tableRef","tableRefFor","handleAction","onChange","role","disabled","ed","col","a","rowObj","cellObj","cellIndexAt","newContent","b","blockEl","tableBlock","c","span","locateCellFromSelection","tableEl","tr","sibling","cs","buildTextToolsHtml","wireTextTools","tag","rangeForContext","toggleMark","syncActive","onInput","sync","btns","prop","MARK_PROP","expected","MARK_ON","fontFamily","optionMatch","fontSize","sz","color","highlight","rangeForBlock","_target","length","PAGE_SIZE_KEYS","MARGIN_FIELDS","VERTICAL_ALIGNS","openPageSetupPopover","sectionCount","activeIndex","clampIndex","detectCurrentSection","savedSelection","canInsertBreak","render","setup","buildHtml","wire","onDown","installTimer","onKey","closed","sectionPicker","_","pageGroup","dim","PAGE_SIZES","side","differentFirst","differentLast","sectionGroup","capitalize","s","getActive","onSectionChange","closePopover","sectionEl","sizeEl","orientationEl","input","apply","current","wasActive","caretStart","caretEnd","debouncedApply","debounce","vAlignEl","checked","insertBreakBtn","blocks","section","index","count","ms","timer","BlockTools","partial","suspended","changeCtx","isMulti","perKindHtml","detachText","detachPerKind","detachTrailing","fromIdx","toIdx","detachTable","nextMode","pageSetupBtn","tc","pressed","activeCls","tcLabel","tcBtn","authorValue","escapeHtmlAttr","authorInput","syncTrackChangesBtn","unresolved","modeStr","trailingGroup","detachTrackChanges","onAuthorInput","cur","zone","enterZoneEdit","blockTools","sobree","viewport","tools"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/blockKinds.ts","../src/indicator.ts","../src/toolbar.ts","../src/tools/icons.ts","../src/tools/changeType.ts","../src/tools/pageSetup.ts","../src/tools/selectionState.ts","../src/tools/perKind.ts","../src/tools/table.ts","../src/tools/text.ts","../src/blockTools.ts","../src/plugin.ts"],"sourcesContent":["/**\n * Block-kind identification + per-kind metadata for the block indicator\n * and toolbar. Inline Lucide SVG (2px stroke) matches the design system;\n * icons are monochrome via `currentColor` so they inherit the indicator\n * state colour.\n */\n\nexport type BlockKind =\n | \"paragraph\"\n | \"heading\"\n | \"list\"\n | \"listOrdered\"\n | \"blockquote\"\n | \"table\"\n | \"image\"\n | \"header\"\n | \"footer\"\n | \"sectionBreak\";\n\nexport interface BlockKindInfo {\n kind: BlockKind;\n /** Short label shown in tooltips and the toolbar header. */\n label: string;\n /** Inline SVG body — `<svg>` wrapper is added by the indicator. */\n iconPath: string;\n}\n\nconst SVG_PARAGRAPH = `<path d=\"M13 4v16\"/><path d=\"M17 4v16\"/><path d=\"M19 4H9.5a4.5 4.5 0 0 0 0 9H13\"/>`;\nconst SVG_HEADING = `<path d=\"M4 12h8\"/><path d=\"M4 18V6\"/><path d=\"M12 18V6\"/><path d=\"m17 12 3-2v8\"/>`;\nconst SVG_LIST = `<path d=\"M3 12h.01\"/><path d=\"M3 18h.01\"/><path d=\"M3 6h.01\"/><path d=\"M8 12h13\"/><path d=\"M8 18h13\"/><path d=\"M8 6h13\"/>`;\nconst SVG_LIST_ORDERED = `<path d=\"M10 12h11\"/><path d=\"M10 18h11\"/><path d=\"M10 6h11\"/><path d=\"M4 10h2\"/><path d=\"M4 6h1v4\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>`;\nconst SVG_QUOTE = `<path d=\"M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/><path d=\"M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z\"/>`;\nconst SVG_TABLE = `<path d=\"M12 3v18\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/><path d=\"M3 15h18\"/>`;\nconst SVG_IMAGE = `<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/>`;\nconst SVG_CHEVRON_UP = `<path d=\"m18 15-6-6-6 6\"/>`;\nconst SVG_CHEVRON_DOWN = `<path d=\"m6 9 6 6 6-6\"/>`;\n// Lucide \"separator-horizontal\".\nconst SVG_SECTION_BREAK = `<path d=\"M3 12h18\"/><path d=\"m8 8 4-4 4 4\"/><path d=\"m16 16-4 4-4-4\"/>`;\n\nexport const BLOCK_KINDS: Record<BlockKind, BlockKindInfo> = {\n paragraph: { kind: \"paragraph\", label: \"Paragraph\", iconPath: SVG_PARAGRAPH },\n heading: { kind: \"heading\", label: \"Heading\", iconPath: SVG_HEADING },\n list: { kind: \"list\", label: \"Bullet list\", iconPath: SVG_LIST },\n listOrdered: { kind: \"listOrdered\", label: \"Numbered list\", iconPath: SVG_LIST_ORDERED },\n blockquote: { kind: \"blockquote\", label: \"Quote\", iconPath: SVG_QUOTE },\n table: { kind: \"table\", label: \"Table\", iconPath: SVG_TABLE },\n image: { kind: \"image\", label: \"Image\", iconPath: SVG_IMAGE },\n header: { kind: \"header\", label: \"Header\", iconPath: SVG_CHEVRON_UP },\n footer: { kind: \"footer\", label: \"Footer\", iconPath: SVG_CHEVRON_DOWN },\n sectionBreak: { kind: \"sectionBreak\", label: \"Section break\", iconPath: SVG_SECTION_BREAK },\n};\n\nexport function iconSvg(info: BlockKindInfo): string {\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${info.iconPath}</svg>`;\n}\n\n/** A tracked block target for indicator / toolbar positioning. */\nexport interface BlockTarget {\n kind: BlockKind;\n /** DOM element representing the block — used for positioning. */\n element: HTMLElement;\n /** Enclosing `.paper` element. */\n paper: HTMLElement;\n /**\n * Stable block id (registry) if available — stamped by the renderer as\n * `data-block-id`. Lets the toolbar re-resolve the DOM element after\n * a commit rebuilds the body (header/footer zones have no id).\n */\n blockId?: string;\n}\n\n/**\n * Walk from a DOM node up to find its containing block inside the\n * paper stack. Returns `null` if the node is outside the stack.\n */\nexport function blockTargetFrom(node: Node, stackRoot: HTMLElement): BlockTarget | null {\n if (!stackRoot.contains(node)) return null;\n const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : (node as HTMLElement);\n if (!el) return null;\n\n const paper = el.closest(\".paper\") as HTMLElement | null;\n if (!paper) return null;\n\n const header = el.closest(\".paper-header\") as HTMLElement | null;\n if (header && paper.contains(header)) return { kind: \"header\", element: header, paper };\n\n const footer = el.closest(\".paper-footer\") as HTMLElement | null;\n if (footer && paper.contains(footer)) return { kind: \"footer\", element: footer, paper };\n\n const image = el.closest(\"img\") as HTMLElement | null;\n if (image && paper.contains(image)) {\n // Walk up to the enclosing block element to keep the indicator at\n // block-left rather than image-left.\n const host = image.closest(\"p, h1, h2, h3, h4, h5, h6, li, blockquote\") as HTMLElement | null;\n if (host) return withBlockId({ kind: \"image\", element: host, paper });\n }\n\n const sectionBreak = el.closest(\".sobree-section-break\") as HTMLElement | null;\n if (sectionBreak && paper.contains(sectionBreak)) {\n return withBlockId({ kind: \"sectionBreak\", element: sectionBreak, paper });\n }\n\n const table = el.closest(\"table\") as HTMLElement | null;\n if (table && paper.contains(table)) return withBlockId({ kind: \"table\", element: table, paper });\n\n const heading = el.closest(\"h1, h2, h3, h4, h5, h6\") as HTMLElement | null;\n if (heading && paper.contains(heading))\n return withBlockId({ kind: \"heading\", element: heading, paper });\n\n const bq = el.closest(\"blockquote\") as HTMLElement | null;\n if (bq && paper.contains(bq)) return withBlockId({ kind: \"blockquote\", element: bq, paper });\n\n const li = el.closest(\"li\") as HTMLElement | null;\n if (li && paper.contains(li)) {\n const parent = li.parentElement;\n const kind: BlockKind = parent?.tagName.toLowerCase() === \"ol\" ? \"listOrdered\" : \"list\";\n return withBlockId({ kind, element: li, paper });\n }\n\n const p = el.closest(\"p\") as HTMLElement | null;\n if (p && paper.contains(p)) return withBlockId({ kind: \"paragraph\", element: p, paper });\n\n return null;\n}\n\nfunction withBlockId(t: BlockTarget): BlockTarget {\n const id = t.element.dataset.blockId;\n if (id) t.blockId = id;\n return t;\n}\n\n/** Same lookup by arbitrary `Node` (handles TEXT_NODE). */\nexport function blockTargetFromNode(node: Node | null, stackRoot: HTMLElement): BlockTarget | null {\n if (!node) return null;\n return blockTargetFrom(node, stackRoot);\n}\n","import type { Editor } from \"@sobree/core\";\nimport {\n BLOCK_KINDS,\n type BlockTarget,\n blockTargetFrom,\n blockTargetFromNode,\n iconSvg,\n} from \"./blockKinds\";\n\nexport interface IndicatorOptions {\n stackRoot: HTMLElement;\n /** Editor instance — used for the `selection` event subscription so\n * the indicator doesn't have to listen to the global document event. */\n editor: Editor;\n /** Fires when the user clicks the indicator (Esc also triggers this). */\n onActivate: (target: BlockTarget) => void;\n}\n\n/**\n * Single floating indicator pinned to the left edge of the paper, at the\n * vertical offset of whatever block is currently being hovered or has\n * the selection / caret.\n *\n * Not one-per-block — one element that moves. Matches Sobree's \"quiet\n * gutter\" rule: the indicator shows the current block, nothing more.\n */\nexport class BlockIndicator {\n private readonly stackRoot: HTMLElement;\n private readonly onActivate: (target: BlockTarget) => void;\n private readonly root: HTMLButtonElement;\n private current: BlockTarget | null = null;\n private enabled = true;\n\n private readonly onHoverFn = (e: MouseEvent) => this.handleHover(e);\n private readonly onStackLeaveFn = () => this.maybeHide();\n private readonly onKeyFn = (e: KeyboardEvent) => this.handleKey(e);\n private readonly detachSelection: () => void;\n\n constructor(opts: IndicatorOptions) {\n this.stackRoot = opts.stackRoot;\n this.onActivate = opts.onActivate;\n\n this.root = document.createElement(\"button\");\n this.root.type = \"button\";\n this.root.className = \"sobree-block-indicator\";\n this.root.contentEditable = \"false\";\n this.root.setAttribute(\"aria-label\", \"Open block tools\");\n // The indicator opens the floating toolbar — a dialog-like surface\n // from an a11y standpoint (anchored, dismissible, contains controls).\n this.root.setAttribute(\"aria-haspopup\", \"dialog\");\n this.root.setAttribute(\"aria-expanded\", \"false\");\n\n // The stack needs to be a positioning context so the indicator follows\n // its paper-space coordinates through any viewport transforms.\n if (!this.stackRoot.style.position) this.stackRoot.style.position = \"relative\";\n this.stackRoot.appendChild(this.root);\n\n // Clicks on the indicator must not move the caret.\n this.root.addEventListener(\"mousedown\", (e) => e.preventDefault());\n this.root.addEventListener(\"click\", (e) => {\n e.preventDefault();\n if (this.current) this.onActivate(this.current);\n });\n\n this.stackRoot.addEventListener(\"mousemove\", this.onHoverFn);\n this.stackRoot.addEventListener(\"mouseleave\", this.onStackLeaveFn);\n this.detachSelection = opts.editor.on(\"selection\", () => this.handleSelectionChange());\n document.addEventListener(\"keydown\", this.onKeyFn);\n }\n\n destroy(): void {\n this.stackRoot.removeEventListener(\"mousemove\", this.onHoverFn);\n this.stackRoot.removeEventListener(\"mouseleave\", this.onStackLeaveFn);\n this.detachSelection();\n document.removeEventListener(\"keydown\", this.onKeyFn);\n this.root.remove();\n }\n\n /** Currently-tracked block, or `null` if the indicator is hidden. */\n getCurrent(): BlockTarget | null {\n return this.current;\n }\n\n /** Visually flag the indicator as \"active\" (toolbar is open for it). */\n setActive(active: boolean): void {\n this.root.classList.toggle(\"is-active\", active);\n this.root.setAttribute(\"aria-expanded\", String(active));\n }\n\n /**\n * Enable or disable the indicator entirely. Disabled = hidden and\n * inert (hover / selection / Esc do nothing). Used to suspend the\n * block UI while Sobree is in read mode.\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n if (!enabled) {\n this.current = null;\n this.root.classList.remove(\"is-visible\", \"is-active\");\n }\n }\n\n /** Recompute position. Call after pagination or viewport zoom changes. */\n refresh(): void {\n if (!this.current) return;\n // Re-resolve element if the body was re-rendered under us.\n if (this.current.blockId && !document.contains(this.current.element)) {\n const fresh = this.stackRoot.querySelector(\n `[data-block-id=\"${this.current.blockId}\"]`,\n ) as HTMLElement | null;\n if (fresh) {\n const paper = fresh.closest(\".paper\") as HTMLElement | null;\n if (paper) this.current = { ...this.current, element: fresh, paper };\n } else {\n // Block was deleted — hide.\n this.current = null;\n this.root.classList.remove(\"is-visible\", \"is-active\");\n return;\n }\n }\n this.position(this.current);\n }\n\n // ---- handlers ----\n\n private handleHover(e: MouseEvent): void {\n if (!this.enabled) return;\n const target = e.target as HTMLElement;\n if (this.root.contains(target)) return;\n const block = blockTargetFrom(target, this.stackRoot);\n if (block) this.setTarget(block);\n }\n\n private handleSelectionChange(): void {\n if (!this.enabled) return;\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const anchor = sel.anchorNode;\n if (!anchor) return;\n if (!this.stackRoot.contains(anchor)) return;\n const block = blockTargetFromNode(anchor, this.stackRoot);\n if (block) this.setTarget(block);\n }\n\n private handleKey(e: KeyboardEvent): void {\n if (!this.enabled) return;\n if (e.key !== \"Escape\") return;\n if (!this.current) return;\n // Esc on a visible indicator → treat as if the user clicked it.\n // The orchestrator's `onActivate` handler toggles the toolbar.\n this.onActivate(this.current);\n }\n\n private maybeHide(): void {\n // Only fade out when the mouse leaves the stack. Keep the indicator\n // visible when it's pinned by a selection.\n const sel = window.getSelection();\n const hasSelInStack =\n sel && sel.rangeCount > 0 && sel.anchorNode && this.stackRoot.contains(sel.anchorNode);\n if (hasSelInStack) return;\n this.current = null;\n this.root.classList.remove(\"is-visible\");\n }\n\n private setTarget(target: BlockTarget): void {\n const same = this.current?.element === target.element && this.current.kind === target.kind;\n if (same) return;\n this.current = target;\n this.root.dataset.kind = target.kind;\n this.root.title = BLOCK_KINDS[target.kind].label;\n this.root.innerHTML = iconSvg(BLOCK_KINDS[target.kind]);\n this.root.classList.add(\"is-visible\");\n this.position(target);\n }\n\n private position(target: BlockTarget): void {\n const scale = this.effectiveScale();\n const stackRect = this.stackRoot.getBoundingClientRect();\n const paperRect = target.paper.getBoundingClientRect();\n const blockRect = target.element.getBoundingClientRect();\n const left = (paperRect.left - stackRect.left) / scale;\n const top = (blockRect.top - stackRect.top) / scale;\n this.root.style.left = `${left}px`;\n this.root.style.top = `${top}px`;\n }\n\n /** Effective CSS-pixel scale applied by any viewport transform ancestor. */\n private effectiveScale(): number {\n if (this.stackRoot.offsetWidth === 0) return 1;\n return this.stackRoot.getBoundingClientRect().width / this.stackRoot.offsetWidth;\n }\n}\n","import type { Viewport } from \"@sobree/core\";\nimport type { BlockTarget } from \"./blockKinds\";\n\nexport interface FloatingToolbarOptions {\n /**\n * Stack root — used to re-resolve the target element by `data-block-id`\n * after a commit replaces the body DOM.\n */\n stackRoot: HTMLElement;\n /**\n * Rendering area — the element inside which the toolbar must fit.\n * Typically the demo viewport (`.demo-viewport`). Used for Case-A\n * stick-to-top and Case-C panning calculations.\n */\n renderingArea: HTMLElement;\n /** Optional viewport handle — used for the animated pan in Case C. */\n viewport?: Viewport | null;\n}\n\n/**\n * Floating toolbar shell. Positions itself above the active block\n * according to the three rules:\n * A. Block is taller than the visible area → toolbar sticks to the\n * top of the rendering area and floats over the block.\n * B. Block fits and there's room above → toolbar sits 8px above it.\n * C. Block fits but toolbar would clip outside the rendering area →\n * animate a viewport pan so the toolbar nestles between block-top\n * and rendering-area-top.\n *\n * B2 scope: shell only. Contents are set via `setContent(html)` from\n * whatever tool module owns the current block kind; wiring those up is\n * B3–B5.\n */\nexport class FloatingToolbar {\n readonly root: HTMLElement;\n private readonly stackRoot: HTMLElement;\n private readonly renderingArea: HTMLElement;\n private readonly viewport: Viewport | null;\n private target: BlockTarget | null = null;\n private readonly onScrollOrResizeFn = () => this.reposition();\n\n constructor(opts: FloatingToolbarOptions) {\n this.stackRoot = opts.stackRoot;\n this.renderingArea = opts.renderingArea;\n this.viewport = opts.viewport ?? null;\n\n this.root = document.createElement(\"div\");\n this.root.className = \"sobree-block-toolbar\";\n this.root.contentEditable = \"false\";\n this.root.setAttribute(\"role\", \"toolbar\");\n this.root.setAttribute(\"aria-label\", \"Block tools\");\n // Wraps onto a second line on narrow screens (CSS handles the wrap),\n // but the SR announcement still treats children as a single\n // horizontal cluster — that matches the visual mental model better\n // than `vertical` would after a wrap.\n this.root.setAttribute(\"aria-orientation\", \"horizontal\");\n // Appended to body (not stack) so it floats at viewport coordinates\n // and isn't affected by the stack's zoom transform.\n document.body.appendChild(this.root);\n\n // Mousedown inside the toolbar mustn't steal caret focus.\n this.root.addEventListener(\"mousedown\", (e) => {\n const target = e.target as HTMLElement;\n // Inputs and selects still need focus — let them get it.\n if (target.closest(\"input, select, textarea, [contenteditable=true]\")) return;\n e.preventDefault();\n });\n\n this.renderingArea.addEventListener(\"scroll\", this.onScrollOrResizeFn, {\n passive: true,\n });\n window.addEventListener(\"resize\", this.onScrollOrResizeFn);\n }\n\n destroy(): void {\n this.renderingArea.removeEventListener(\"scroll\", this.onScrollOrResizeFn);\n window.removeEventListener(\"resize\", this.onScrollOrResizeFn);\n this.root.remove();\n }\n\n /** Currently-targeted block, if the toolbar is open. */\n getTarget(): BlockTarget | null {\n return this.target;\n }\n\n /** Whether the toolbar is currently open. */\n isOpen(): boolean {\n return this.root.classList.contains(\"is-open\");\n }\n\n /** Replace the toolbar's inner HTML. Callers own the event wiring. */\n setContent(html: string): void {\n this.root.innerHTML = html;\n }\n\n /** Listen for clicks inside the toolbar (delegated). Returns an unsubscribe. */\n onClick(handler: (e: MouseEvent) => void): () => void {\n this.root.addEventListener(\"click\", handler);\n return () => this.root.removeEventListener(\"click\", handler);\n }\n\n /** Listen for input/change on form controls inside the toolbar. */\n onInput(handler: (e: Event) => void): () => void {\n this.root.addEventListener(\"input\", handler);\n this.root.addEventListener(\"change\", handler);\n return () => {\n this.root.removeEventListener(\"input\", handler);\n this.root.removeEventListener(\"change\", handler);\n };\n }\n\n /** Open the toolbar for `target`. Triggers positioning + panning as needed. */\n open(target: BlockTarget): void {\n this.target = target;\n this.root.dataset.kind = target.kind;\n this.root.classList.add(\"is-open\");\n // Pan-then-reposition so the final resting position is inside the\n // rendering area whenever possible.\n this.maybePanIntoView(target);\n this.reposition();\n }\n\n close(): void {\n this.target = null;\n this.root.classList.remove(\"is-open\");\n this.root.classList.remove(\"is-stuck\");\n this.root.removeAttribute(\"data-kind\");\n }\n\n toggle(target: BlockTarget): void {\n if (this.isOpen() && this.target?.element === target.element) this.close();\n else this.open(target);\n }\n\n /** Recompute positioning — call after scroll, zoom, or pagination. */\n reposition(): void {\n if (!this.target) return;\n // Re-resolve the target element if it was detached by a commit that\n // rebuilt the body DOM. `data-block-id` is stamped by the renderer.\n if (this.target.blockId && !document.contains(this.target.element)) {\n const fresh = this.stackRoot.querySelector(\n `[data-block-id=\"${this.target.blockId}\"]`,\n ) as HTMLElement | null;\n if (fresh) {\n const paper = fresh.closest(\".paper\") as HTMLElement | null;\n if (paper) this.target = { ...this.target, element: fresh, paper };\n } else {\n // Block was deleted — nothing to anchor to.\n this.close();\n return;\n }\n }\n const rendArea = this.renderingArea.getBoundingClientRect();\n // Cap width to the rendering area so the toolbar wraps to a second\n // line when its contents would otherwise overflow. Do this BEFORE\n // reading the block rect so the offsetHeight reflects the wrapped\n // (possibly two-line) layout.\n const margin = 8;\n const maxWidth = Math.max(240, rendArea.width - margin * 2);\n this.root.style.maxWidth = `${maxWidth}px`;\n const blockRect = this.target.element.getBoundingClientRect();\n const tbHeight = this.root.offsetHeight || 40;\n\n // Case A — block taller than the visible area: stick to the top of\n // the rendering area. Re-evaluated on every call so scrolling a\n // medium block into \"tall relative to what's showing\" flips it.\n const blockTallerThanView = blockRect.height > rendArea.height;\n // Also stick when the block's top has scrolled above the rendering\n // area — the user is inside a large block that spills upward.\n const blockTopAboveView = blockRect.top < rendArea.top;\n const stickToTop = blockTallerThanView || blockTopAboveView;\n\n let top: number;\n let left = Math.max(\n rendArea.left + margin,\n Math.min(blockRect.left, rendArea.right - this.root.offsetWidth - margin),\n );\n\n if (stickToTop) {\n top = rendArea.top + margin;\n } else {\n top = blockRect.top - tbHeight - margin;\n // Case B: natural fit. We already panned into view in `open`.\n // If after scrolling the toolbar ends up above the rendering\n // area, clamp.\n if (top < rendArea.top + margin) top = rendArea.top + margin;\n }\n\n // Clamp to window horizontally as a final safety net.\n left = Math.max(0, Math.min(left, window.innerWidth - this.root.offsetWidth));\n\n this.root.style.top = `${top}px`;\n this.root.style.left = `${left}px`;\n this.root.classList.toggle(\"is-stuck\", stickToTop);\n }\n\n /**\n * Case C: if the block is small but the toolbar wouldn't fit above it\n * within the rendering area, animate the viewport so the block shifts\n * down by `toolbarHeight + margin`.\n */\n private maybePanIntoView(target: BlockTarget): void {\n if (!this.viewport) return;\n const rendArea = this.renderingArea.getBoundingClientRect();\n const blockRect = target.element.getBoundingClientRect();\n const tbHeight = this.root.offsetHeight || 40;\n const margin = 8;\n\n // If the block is too tall, we'll stick-to-top instead; no pan.\n if (blockRect.height > rendArea.height) return;\n // If there's already room above, nothing to do.\n const roomAbove = blockRect.top - rendArea.top;\n const needed = tbHeight + margin * 2;\n if (roomAbove >= needed) return;\n // Also skip if the block top is already above the rendering area —\n // we'll stick-to-top, not pan.\n if (blockRect.top < rendArea.top) return;\n\n const deficit = needed - roomAbove;\n this.viewport.panBy(0, -deficit, { animate: true });\n }\n}\n","/**\n * Inline Lucide SVG icons used by toolbar buttons. All 2px-stroke,\n * 24×24 viewBox, rendered at 16×16 via the wrapper. Icons inherit\n * `currentColor` so button state colours flow through.\n */\n\nconst BOLD = `<path d=\"M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/><path d=\"M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z\"/>`;\nconst ITALIC = `<line x1=\"19\" y1=\"4\" x2=\"10\" y2=\"4\"/><line x1=\"14\" y1=\"20\" x2=\"5\" y2=\"20\"/><line x1=\"15\" y1=\"4\" x2=\"9\" y2=\"20\"/>`;\nconst UNDERLINE = `<path d=\"M6 4v6a6 6 0 0 0 12 0V4\"/><line x1=\"4\" y1=\"20\" x2=\"20\" y2=\"20\"/>`;\nconst STRIKE = `<path d=\"M16 4H9a3 3 0 0 0-2.83 4\"/><path d=\"M14 12a4 4 0 0 1 0 8H6\"/><line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\"/>`;\nconst SUPERSCRIPT = `<path d=\"m4 19 8-8\"/><path d=\"m12 19-8-8\"/><path d=\"M20 9c0-1.667-.667-2.5-2-2.5-.833 0-1.5.333-2 1 .5-1 1.5-1.5 2.5-1.5 1 0 2.5.5 2.5 2 0 1.667-1.667 2-2.5 3h2.5\"/>`;\nconst SUBSCRIPT = `<path d=\"m4 5 8 8\"/><path d=\"m12 5-8 8\"/><path d=\"M20 20c0-1.667-.667-2.5-2-2.5-.833 0-1.5.333-2 1 .5-1 1.5-1.5 2.5-1.5 1 0 2.5.5 2.5 2 0 1.667-1.667 2-2.5 3h2.5\"/>`;\nconst TYPE = `<polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/>`;\nconst PAINTBRUSH = `<path d=\"m14.622 17.897-10.68-2.913\"/><path d=\"M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z\"/><path d=\"M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15\"/>`;\nconst HIGHLIGHTER = `<path d=\"m9 11-6 6v3h9l3-3\"/><path d=\"m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4\"/>`;\nconst ERASER = `<path d=\"m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21\"/><path d=\"M22 21H7\"/><path d=\"m5 11 9 9\"/>`;\nconst CODE = `<polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/>`;\nconst ALIGN_LEFT = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"17\" y1=\"18\" x2=\"3\" y2=\"18\"/>`;\nconst ALIGN_CENTER = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"17\" y1=\"12\" x2=\"7\" y2=\"12\"/><line x1=\"19\" y1=\"18\" x2=\"5\" y2=\"18\"/>`;\nconst ALIGN_RIGHT = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/><line x1=\"21\" y1=\"18\" x2=\"7\" y2=\"18\"/>`;\nconst ALIGN_JUSTIFY = `<line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"18\" x2=\"3\" y2=\"18\"/>`;\nconst INDENT_INCREASE = `<polyline points=\"3 8 7 12 3 16\"/><line x1=\"21\" y1=\"12\" x2=\"11\" y2=\"12\"/><line x1=\"21\" y1=\"6\" x2=\"11\" y2=\"6\"/><line x1=\"21\" y1=\"18\" x2=\"11\" y2=\"18\"/>`;\nconst INDENT_DECREASE = `<polyline points=\"7 8 3 12 7 16\"/><line x1=\"21\" y1=\"12\" x2=\"11\" y2=\"12\"/><line x1=\"21\" y1=\"6\" x2=\"11\" y2=\"6\"/><line x1=\"21\" y1=\"18\" x2=\"11\" y2=\"18\"/>`;\nconst LIST_BULLET = `<line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/>`;\nconst LIST_NUMBERED = `<line x1=\"10\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"10\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"10\" y1=\"18\" x2=\"21\" y2=\"18\"/><path d=\"M4 6h1v4\"/><path d=\"M4 10h2\"/><path d=\"M6 18H4c0-1 2-2 2-3s-1-1.5-2-1\"/>`;\nconst TRASH = `<polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>`;\nconst CHEVRON_DOWN_ICON = `<polyline points=\"6 9 12 15 18 9\"/>`;\n// Lucide \"sliders-horizontal\" — reads as \"settings / page setup\" without\n// the over-loaded gear iconography.\nconst SETTINGS = `<line x1=\"21\" y1=\"4\" x2=\"14\" y2=\"4\"/><line x1=\"10\" y1=\"4\" x2=\"3\" y2=\"4\"/><line x1=\"21\" y1=\"12\" x2=\"12\" y2=\"12\"/><line x1=\"8\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"20\" x2=\"16\" y2=\"20\"/><line x1=\"12\" y1=\"20\" x2=\"3\" y2=\"20\"/><line x1=\"14\" y1=\"2\" x2=\"14\" y2=\"6\"/><line x1=\"8\" y1=\"10\" x2=\"8\" y2=\"14\"/><line x1=\"16\" y1=\"18\" x2=\"16\" y2=\"22\"/>`;\n// Page outline with inner margin guides — reads unambiguously as\n// \"page setup\". Outer rectangle = paper edge; inner = type area.\nconst PAGE_SETUP = `<rect x=\"4\" y=\"3\" width=\"16\" height=\"18\" rx=\"1\"/><rect x=\"7\" y=\"6\" width=\"10\" height=\"12\" rx=\"0.5\" stroke-dasharray=\"1.5 1.5\" opacity=\"0.7\"/>`;\n// Horizontal dashed divider with a short solid stroke each side —\n// reads as \"section break between two regions\".\nconst SECTION_BREAK = `<path d=\"M3 7h18\"/><path d=\"M3 17h18\"/><path d=\"M3 12h4\"/><path d=\"M11 12h2\"/><path d=\"M17 12h4\"/>`;\n// Pencil with a small underline-dot \"edit-mark\" — reads as \"track edits\".\n// The active (pressed) state of the button is what really tells the user\n// it's on; the glyph itself is the same in both states.\nconst TRACK_CHANGES = `<path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4z\"/><circle cx=\"5.5\" cy=\"18.5\" r=\"0.5\" fill=\"currentColor\"/>`;\n\nexport const ICONS: Record<string, string> = {\n bold: BOLD,\n italic: ITALIC,\n underline: UNDERLINE,\n strike: STRIKE,\n superscript: SUPERSCRIPT,\n subscript: SUBSCRIPT,\n type: TYPE,\n paintbrush: PAINTBRUSH,\n highlighter: HIGHLIGHTER,\n eraser: ERASER,\n code: CODE,\n \"align-left\": ALIGN_LEFT,\n \"align-center\": ALIGN_CENTER,\n \"align-right\": ALIGN_RIGHT,\n \"align-justify\": ALIGN_JUSTIFY,\n \"indent-increase\": INDENT_INCREASE,\n \"indent-decrease\": INDENT_DECREASE,\n \"list-bullet\": LIST_BULLET,\n \"list-numbered\": LIST_NUMBERED,\n trash: TRASH,\n \"chevron-down\": CHEVRON_DOWN_ICON,\n settings: SETTINGS,\n \"page-setup\": PAGE_SETUP,\n \"section-break\": SECTION_BREAK,\n \"track-changes\": TRACK_CHANGES,\n};\n\n/** Render an inline SVG for a named icon, sized 16×16. */\nexport function icon(name: keyof typeof ICONS): string {\n const path = ICONS[name] ?? \"\";\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${path}</svg>`;\n}\n","import type { BlockRef } from \"@sobree/core\";\nimport type { Block, NumberingDefinition, Paragraph, SobreeDocument, Table } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\n\nexport interface ChangeTypeContext {\n editor: Editor;\n target: BlockTarget;\n /** Blocks the operation should apply to (1 for single, N for multi). */\n refs: BlockRef[];\n}\n\n/**\n * Drill-down target kinds. Kept deliberately short — exotic targets\n * (images, nested tables) land as \"convert current to\" with a caveat\n * that the existing content is wrapped into the new structure.\n */\ntype TargetKind =\n | { kind: \"paragraph\" }\n | { kind: \"heading\"; level: 1 | 2 | 3 | 4 | 5 | 6 }\n | { kind: \"quote\" }\n | { kind: \"bullet\" }\n | { kind: \"ordered\" }\n | { kind: \"table\" }\n | { kind: \"section_break\" };\n\n/**\n * Build the \"Change block type\" trigger — rendered at the end of the\n * primary toolbar row. Label adapts to single vs multi-block.\n */\nexport function buildChangeTypeButton(refCount: number): string {\n const label = refCount > 1 ? `Convert ${refCount} blocks` : \"Change block\";\n return `\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"change-type\">\n <button type=\"button\" class=\"tb-change-btn\" data-action=\"open-change-type\"\n title=\"${label}\" aria-label=\"${label}\"\n aria-haspopup=\"menu\" aria-expanded=\"false\">\n ${icon(\"chevron-down\")} ${label}\n </button>\n </div>\n `;\n}\n\n/**\n * Open a popover next to the trigger button with target-kind options.\n * Uses the same `FloatingToolbar` element as an anchor reference so the\n * popover sits just below it.\n *\n * Returns a `close()` handle so callers can dismiss the popover when\n * the toolbar closes.\n */\nexport function openChangeTypePopover(\n trigger: HTMLElement,\n ctx: ChangeTypeContext,\n onClose: () => void,\n): () => void {\n const currentKey = currentTargetKey(ctx);\n const popover = document.createElement(\"div\");\n popover.className = \"sobree-change-popover\";\n popover.setAttribute(\"role\", \"menu\");\n popover.setAttribute(\"aria-label\", \"Change block type\");\n popover.tabIndex = -1; // accept focus so keyboard navigation works\n popover.innerHTML = buildPopoverHtml(ctx.refs.length, currentKey);\n document.body.appendChild(popover);\n\n // Position just below the trigger, aligned to its right edge so the\n // menu opens into the free space to the right of the button.\n const triggerRect = trigger.getBoundingClientRect();\n popover.style.top = `${triggerRect.bottom + 6}px`;\n popover.style.left = `${Math.max(8, triggerRect.left)}px`;\n const items = () =>\n Array.from(\n popover.querySelectorAll<HTMLButtonElement>(\n 'button[role=\"menuitem\"], button[role=\"menuitemradio\"]',\n ),\n );\n\n // Next tick: fade in + focus the first item so Down/Enter work right\n // away from a keyboard.\n requestAnimationFrame(() => {\n popover.classList.add(\"is-open\");\n items()[0]?.focus();\n });\n\n const close = () => {\n popover.classList.remove(\"is-open\");\n // Let the fade run before removing.\n window.setTimeout(() => popover.remove(), 180);\n document.removeEventListener(\"mousedown\", onDocDown, true);\n onClose();\n };\n\n const onDocDown = (e: MouseEvent) => {\n if (popover.contains(e.target as Node)) return;\n if (trigger.contains(e.target as Node)) return;\n close();\n };\n\n popover.addEventListener(\"click\", (e) => {\n const item = (e.target as HTMLElement).closest(\"[data-target-kind]\");\n if (!item) return;\n const raw = item.getAttribute(\"data-target-kind\");\n if (!raw) return;\n const target = parseTarget(raw);\n if (target) applyTarget(ctx, target);\n close();\n });\n\n // Standard menu keyboard model — ArrowDown/Up move between items,\n // Home/End jump to the ends, Enter activates, Esc closes.\n popover.addEventListener(\"keydown\", (e) => {\n const all = items();\n const idx = all.indexOf(document.activeElement as HTMLButtonElement);\n if (e.key === \"Escape\") {\n e.preventDefault();\n close();\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n all[(idx + 1) % all.length]?.focus();\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n all[(idx - 1 + all.length) % all.length]?.focus();\n return;\n }\n if (e.key === \"Home\") {\n e.preventDefault();\n all[0]?.focus();\n return;\n }\n if (e.key === \"End\") {\n e.preventDefault();\n all[all.length - 1]?.focus();\n return;\n }\n });\n\n document.addEventListener(\"mousedown\", onDocDown, true);\n\n return close;\n}\n\nfunction buildPopoverHtml(refCount: number, currentKey: string | null): string {\n const multi = refCount > 1;\n const item = (key: string, label: string): string => {\n const isCurrent = !multi && currentKey === key;\n const cls = isCurrent ? ' class=\"is-current\"' : \"\";\n const aria = isCurrent ? ' aria-checked=\"true\"' : ' aria-checked=\"false\"';\n return `<button type=\"button\" role=\"menuitemradio\"${aria}${cls} data-target-kind=\"${key}\">${label}</button>`;\n };\n const convertGroup = `\n <div class=\"popover-section\">\n <div class=\"popover-label\">Convert to</div>\n ${item(\"paragraph\", `${iconInline(\"type\")} Paragraph`)}\n ${item(\"heading:1\", \"H1 Heading 1\")}\n ${item(\"heading:2\", \"H2 Heading 2\")}\n ${item(\"heading:3\", \"H3 Heading 3\")}\n ${item(\"heading:4\", \"H4 Heading 4\")}\n ${item(\"quote\", `${iconInline(\"code\")} Quote`)}\n ${item(\"bullet\", `${iconInline(\"list-bullet\")} Bullet list`)}\n ${item(\"ordered\", `${iconInline(\"list-numbered\")} Numbered list`)}\n </div>\n `;\n // Structural conversions (wrap content into a new structure) only make\n // sense for single-block selections. These are not \"current\"-able —\n // section break is \"insert after\", table is destructive replace.\n const structuralGroup = multi\n ? \"\"\n : `\n <div class=\"popover-divider\"></div>\n <div class=\"popover-section\">\n <div class=\"popover-label\">Replace with</div>\n <button type=\"button\" role=\"menuitem\" data-target-kind=\"table\">Table (3×3)</button>\n </div>\n <div class=\"popover-section\">\n <div class=\"popover-label\">Insert after</div>\n <button type=\"button\" role=\"menuitem\" data-target-kind=\"section_break\" title=\"Ctrl/Cmd + Shift + Enter\">Section break</button>\n </div>\n `;\n return convertGroup + structuralGroup;\n}\n\n/**\n * Resolve the current block kind into one of the popover's `data-target-kind`\n * keys, so the matching item can be highlighted as \"current\". Returns null\n * if the current kind has no equivalent in the menu (table, section_break,\n * image, multi-block, …).\n */\nfunction currentTargetKey(ctx: ChangeTypeContext): string | null {\n if (ctx.refs.length !== 1) return null;\n const first = ctx.refs[0];\n if (!first) return null;\n // Defensive — minimal stubs in tests may not implement every method.\n if (\n typeof ctx.editor.getBlockById !== \"function\" ||\n typeof ctx.editor.getDocument !== \"function\"\n ) {\n return null;\n }\n const info = ctx.editor.getBlockById(first.id);\n if (!info || info.kind !== \"paragraph\") return null;\n const doc = ctx.editor.getDocument();\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return null;\n if (block.properties.numbering) {\n const numId = block.properties.numbering.numId;\n const def = doc.numbering.find((n) => n.numId === numId);\n const fmt = def?.abstractFormat.levels[0]?.format;\n if (fmt === \"bullet\") return \"bullet\";\n if (fmt === \"decimal\") return \"ordered\";\n return \"bullet\"; // fallback for unknown list formats\n }\n const styleId = block.properties.styleId;\n if (!styleId || styleId === \"Normal\") return \"paragraph\";\n if (styleId === \"Quote\") return \"quote\";\n const m = styleId.match(/^Heading([1-6])$/);\n if (m) return `heading:${m[1]}`;\n return null;\n}\n\nfunction iconInline(name: string): string {\n return icon(name as Parameters<typeof icon>[0]);\n}\n\nfunction parseTarget(raw: string): TargetKind | null {\n if (raw === \"paragraph\") return { kind: \"paragraph\" };\n if (raw === \"quote\") return { kind: \"quote\" };\n if (raw === \"bullet\") return { kind: \"bullet\" };\n if (raw === \"ordered\") return { kind: \"ordered\" };\n if (raw === \"table\") return { kind: \"table\" };\n if (raw === \"section_break\") return { kind: \"section_break\" };\n const m = raw.match(/^heading:([1-6])$/);\n if (m?.[1]) {\n const lv = Number(m[1]) as 1 | 2 | 3 | 4 | 5 | 6;\n return { kind: \"heading\", level: lv };\n }\n return null;\n}\n\nfunction applyTarget(ctx: ChangeTypeContext, target: TargetKind): void {\n if (target.kind === \"table\") {\n convertToTable(ctx);\n return;\n }\n if (target.kind === \"section_break\") {\n // Section break is \"insert after current block\" — same dispatch\n // path as Ctrl+Shift+Enter, so behaviour is consistent.\n ctx.editor.commands.execute(\"section.insertBreakAfter\");\n return;\n }\n // Convert-in-place: swap styleId + numbering as appropriate.\n for (const ref of ctx.refs) {\n applyConversion(ctx.editor, ref, target);\n }\n}\n\nfunction applyConversion(editor: Editor, ref: BlockRef, target: TargetKind): void {\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n\n // Convert-from-table: collapse the table into a single paragraph\n // carrying the concatenated text of every cell. The block id and\n // index stay stable, so the subsequent property/numbering work\n // applies normally.\n if (info.kind === \"table\") {\n flattenTableToParagraph(editor, ref);\n // Re-resolve — replaceBlock bumped the version.\n const refreshed = editor.getBlockById(ref.id);\n if (!refreshed) return;\n ref = { id: refreshed.id, version: refreshed.version };\n } else if (info.kind !== \"paragraph\") {\n // section_break / image: nothing meaningful to convert. The\n // popover shouldn't have been reachable for these kinds anyway.\n return;\n }\n\n if (target.kind === \"paragraph\") {\n editor.applyBlockProperties([ref], { styleId: undefined, numbering: undefined });\n return;\n }\n if (target.kind === \"heading\") {\n editor.applyBlockProperties([ref], {\n styleId: `Heading${target.level}`,\n numbering: undefined,\n });\n return;\n }\n if (target.kind === \"quote\") {\n editor.applyBlockProperties([ref], { styleId: \"Quote\", numbering: undefined });\n return;\n }\n if (target.kind === \"bullet\" || target.kind === \"ordered\") {\n // Convert-to-list needs a numbering definition. Reuse an existing\n // one with the right format if present, else add one in a single\n // setDocument pass.\n convertBlockToList(editor, ref, target.kind === \"bullet\" ? \"bullet\" : \"decimal\");\n return;\n }\n}\n\n/**\n * Replace a `Table` block with a `Paragraph` carrying the concatenated\n * text of every cell, separated by spaces. Cells with formatting are\n * flattened to plain text — the round-trip story is \"convert is\n * destructive for tables\".\n */\nfunction flattenTableToParagraph(editor: Editor, ref: BlockRef): void {\n const doc = editor.getDocument();\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"table\") return;\n\n const parts: string[] = [];\n for (const row of block.rows) {\n for (const cell of row.cells) {\n for (const inner of cell.content) {\n if (inner.kind !== \"paragraph\") continue;\n for (const run of inner.runs) {\n if (run.kind === \"text\" && run.text) parts.push(run.text);\n }\n }\n }\n }\n const text = parts.join(\" \").replace(/\\s+/g, \" \").trim();\n const next: Paragraph = {\n kind: \"paragraph\",\n properties: {},\n runs: text ? [{ kind: \"text\", text, properties: {} }] : [],\n };\n editor.replaceBlock(ref, next);\n}\n\nfunction convertBlockToList(editor: Editor, ref: BlockRef, format: \"bullet\" | \"decimal\"): void {\n const doc = editor.getDocument();\n const info = editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n\n const existing = doc.numbering.find((n) => n.abstractFormat.levels[0]?.format === format);\n let numId: number;\n let nextNumbering: NumberingDefinition[] = doc.numbering;\n if (existing) {\n numId = existing.numId;\n } else {\n numId = doc.numbering.reduce((n, d) => Math.max(n, d.numId), 0) + 1;\n nextNumbering = [\n ...doc.numbering,\n {\n numId,\n abstractFormat: {\n levels: [\n {\n level: 0,\n format,\n text: format === \"bullet\" ? \"\\u2022\" : \"%1.\",\n },\n ],\n },\n },\n ];\n }\n\n // Drop any heading / quote style when becoming a list item — omit the\n // key rather than assigning `undefined` so the shape satisfies\n // `exactOptionalPropertyTypes`.\n const { styleId: _omit, ...cleanProps } = block.properties;\n void _omit;\n const nextBlock: Paragraph = {\n ...block,\n properties: {\n ...cleanProps,\n numbering: { numId, level: 0 },\n },\n };\n const nextBody = doc.body.slice();\n nextBody[info.index] = nextBlock;\n const nextDoc: SobreeDocument = {\n ...doc,\n body: nextBody,\n numbering: nextNumbering,\n };\n editor.setDocument(nextDoc);\n}\n\n/**\n * Replace the current paragraph with a 3×3 table. If the paragraph has\n * text, seed the first cell with it; the other 8 cells are empty.\n */\nfunction convertToTable(ctx: ChangeTypeContext): void {\n const ref = ctx.refs[0];\n if (!ref) return;\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const doc = ctx.editor.getDocument();\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n\n const empty = () => ({\n content: [{ kind: \"paragraph\", properties: {}, runs: [] } as Paragraph] as Block[],\n });\n const withExistingRuns = {\n content: [{ kind: \"paragraph\", properties: {}, runs: block.runs } as Paragraph] as Block[],\n };\n\n const table: Table = {\n kind: \"table\",\n grid: [2400, 2400, 2400],\n rows: [\n { cells: [withExistingRuns, empty(), empty()] },\n { cells: [empty(), empty(), empty()] },\n { cells: [empty(), empty(), empty()] },\n ],\n properties: {},\n };\n ctx.editor.replaceBlock(ref, table);\n}\n","import {\n type Editor,\n PAGE_SIZES,\n type PageSetup,\n type PageSizeKey,\n type VerticalAlign,\n} from \"@sobree/core\";\n\n/**\n * Section-aware Page & Section Setup popover.\n *\n * Two groups of controls:\n *\n * 1. Page properties — paper size, orientation, margins. These live\n * in `PageSetup.size / orientation / margins` and apply to the\n * selected section.\n * 2. Section properties — vertical alignment, different first /\n * last page header / footer flags, and an \"Insert section break\n * after current block\" affordance (dispatched through the\n * `section.insertBreakAfter` command registered by core).\n *\n * Edits apply live through `setSectionSetup` — the editor repaginates\n * and the paper visibly resizes per keystroke. Pre-selects the section\n * the caret currently sits in (computed by counting `section_break`\n * blocks before it).\n */\nexport interface PageSetupContext {\n editor: Editor;\n getSectionCount: () => number;\n getSectionSetup: (index: number) => PageSetup;\n setSectionSetup: (index: number, partial: Partial<PageSetup>) => void;\n}\n\nconst PAGE_SIZE_KEYS: PageSizeKey[] = [\"A3\", \"A4\", \"A5\", \"B5\", \"Letter\", \"Legal\", \"Tabloid\"];\n\nconst MARGIN_FIELDS = [\"top\", \"right\", \"bottom\", \"left\"] as const;\n\nconst VERTICAL_ALIGNS: VerticalAlign[] = [\"top\", \"center\", \"bottom\", \"both\"];\n\nexport function openPageSetupPopover(\n trigger: HTMLElement,\n ctx: PageSetupContext,\n onClose: () => void,\n): () => void {\n const sectionCount = Math.max(1, ctx.getSectionCount());\n let activeIndex = clampIndex(detectCurrentSection(ctx.editor), sectionCount);\n\n // Capture the editor's selection BEFORE we move focus into the\n // popover. Used by the \"Insert section break\" button to restore the\n // caret so the underlying command (which reads `currentCaret()`) has\n // somewhere to insert.\n const savedSelection = ctx.editor.selection.get();\n\n const popover = document.createElement(\"div\");\n popover.className = \"sobree-page-setup-popover sobree-change-popover\";\n popover.setAttribute(\"role\", \"dialog\");\n popover.setAttribute(\"aria-label\", \"Page and section setup\");\n popover.tabIndex = -1;\n document.body.appendChild(popover);\n\n // Section break can only be inserted at a real caret position. The\n // command is registered by core's always-on `attachSections` plugin.\n const canInsertBreak = ctx.editor.commands.has(\"section.insertBreakAfter\");\n\n const render = () => {\n const setup = ctx.getSectionSetup(activeIndex);\n popover.innerHTML = buildHtml(sectionCount, activeIndex, setup, canInsertBreak);\n wire(\n popover,\n ctx,\n () => activeIndex,\n (next) => {\n activeIndex = next;\n render(); // re-render so the form reflects the newly-selected section\n },\n () => close(),\n savedSelection,\n );\n };\n\n render();\n\n // Position below the trigger, clamped to the viewport.\n const triggerRect = trigger.getBoundingClientRect();\n const POPOVER_WIDTH = 320;\n const left = Math.max(8, Math.min(window.innerWidth - POPOVER_WIDTH - 8, triggerRect.left));\n popover.style.top = `${triggerRect.bottom + 6}px`;\n popover.style.left = `${left}px`;\n // Trigger the .is-open transition (mirrors sobree-change-popover) on\n // the next frame so the fade-in actually animates instead of being\n // skipped by the initial paint.\n requestAnimationFrame(() => popover.classList.add(\"is-open\"));\n\n // Click-away closes (but ignore clicks inside the popover or on the\n // trigger button — those are handled internally / open-toggling).\n const onDown = (e: MouseEvent) => {\n const t = e.target as Node;\n if (popover.contains(t)) return;\n if (trigger.contains(t)) return;\n close();\n };\n // Defer the listener install so the click that opened us doesn't\n // immediately close us.\n const installTimer = window.setTimeout(() => {\n document.addEventListener(\"mousedown\", onDown, true);\n }, 0);\n\n // Esc closes.\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n close();\n }\n };\n popover.addEventListener(\"keydown\", onKey);\n\n // Focus the section picker (or the first form field) for keyboard nav.\n queueMicrotask(() => {\n const first = popover.querySelector<HTMLElement>(\"select, input\") ?? popover;\n first.focus();\n });\n\n let closed = false;\n const close = () => {\n if (closed) return;\n closed = true;\n window.clearTimeout(installTimer);\n document.removeEventListener(\"mousedown\", onDown, true);\n popover.removeEventListener(\"keydown\", onKey);\n popover.remove();\n onClose();\n };\n\n return close;\n}\n\n// === markup ===\n\nfunction buildHtml(\n sectionCount: number,\n activeIndex: number,\n setup: PageSetup,\n canInsertBreak: boolean,\n): string {\n const sectionPicker =\n sectionCount > 1\n ? `\n <label class=\"ps-row\">\n <span class=\"ps-label\">Section</span>\n <select data-field=\"section\">\n ${Array.from({ length: sectionCount }, (_, i) => {\n const sel = i === activeIndex ? \" selected\" : \"\";\n return `<option value=\"${i}\"${sel}>Section ${i + 1}</option>`;\n }).join(\"\")}\n </select>\n </label>`\n : \"\";\n\n // Page properties group — paper size, orientation, margins.\n const pageGroup = `\n <div class=\"ps-section-header\">Page</div>\n <div class=\"ps-grid\">\n <label class=\"ps-row\">\n <span class=\"ps-label\">Size</span>\n <select data-field=\"size\">\n ${PAGE_SIZE_KEYS.map((k) => {\n const sel = k === setup.size ? \" selected\" : \"\";\n const dim = PAGE_SIZES[k];\n return `<option value=\"${k}\"${sel}>${k} (${dim.width} × ${dim.height} mm)</option>`;\n }).join(\"\")}\n </select>\n </label>\n <label class=\"ps-row\">\n <span class=\"ps-label\">Orientation</span>\n <select data-field=\"orientation\">\n <option value=\"portrait\"${setup.orientation === \"portrait\" ? \" selected\" : \"\"}>Portrait</option>\n <option value=\"landscape\"${setup.orientation === \"landscape\" ? \" selected\" : \"\"}>Landscape</option>\n </select>\n </label>\n <div class=\"ps-row ps-margins\">\n <span class=\"ps-label\">Margins (mm)</span>\n <div class=\"ps-margin-grid\">\n ${MARGIN_FIELDS.map(\n (side) => `\n <label class=\"ps-margin\">\n <span>${side}</span>\n <input type=\"number\" min=\"0\" step=\"1\" data-field=\"margin-${side}\" value=\"${setup.margins[side]}\" />\n </label>`,\n ).join(\"\")}\n </div>\n </div>\n </div>`;\n\n // Section properties group — vAlign + page-variation flags + insert-break.\n const differentFirst = setup.header.differentFirst || setup.footer.differentFirst;\n const differentLast = setup.header.differentLast || setup.footer.differentLast;\n const sectionGroup = `\n <div class=\"ps-section-header\">Section</div>\n <div class=\"ps-grid\">\n <label class=\"ps-row\">\n <span class=\"ps-label\">Vertical alignment</span>\n <select data-field=\"vertical-align\">\n ${VERTICAL_ALIGNS.map((v) => {\n const sel = v === setup.verticalAlign ? \" selected\" : \"\";\n return `<option value=\"${v}\"${sel}>${capitalize(v)}</option>`;\n }).join(\"\")}\n </select>\n </label>\n <label class=\"ps-row ps-checkbox\">\n <input type=\"checkbox\" data-field=\"different-first\"${differentFirst ? \" checked\" : \"\"} />\n <span>Different header / footer for first page</span>\n </label>\n <label class=\"ps-row ps-checkbox\">\n <input type=\"checkbox\" data-field=\"different-last\"${differentLast ? \" checked\" : \"\"} />\n <span>Different header / footer for last page</span>\n </label>\n ${\n canInsertBreak\n ? `<button type=\"button\" class=\"ps-insert-break\" data-action=\"insert-section-break\">\n Insert section break after current block\n </button>`\n : \"\"\n }\n </div>`;\n\n return `\n <div class=\"ps-header\">Page & section setup</div>\n ${sectionPicker}\n ${pageGroup}\n ${sectionGroup}\n `;\n}\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n// === wiring ===\n\nfunction wire(\n root: HTMLElement,\n ctx: PageSetupContext,\n getActive: () => number,\n onSectionChange: (index: number) => void,\n closePopover: () => void,\n savedSelection: ReturnType<Editor[\"selection\"][\"get\"]>,\n): void {\n // Section picker — switching sections re-renders the form.\n const sectionEl = root.querySelector<HTMLSelectElement>('select[data-field=\"section\"]');\n if (sectionEl) {\n sectionEl.addEventListener(\"change\", () => {\n const next = Number(sectionEl.value);\n if (Number.isFinite(next)) onSectionChange(next);\n });\n }\n\n // Size + orientation — apply on change.\n const sizeEl = root.querySelector<HTMLSelectElement>('select[data-field=\"size\"]');\n sizeEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), { size: sizeEl.value as PageSizeKey });\n });\n\n const orientationEl = root.querySelector<HTMLSelectElement>('select[data-field=\"orientation\"]');\n orientationEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), {\n orientation: orientationEl.value as PageSetup[\"orientation\"],\n });\n });\n\n // Margins — debounced live updates so the paper resizes as the user\n // pauses typing, but each keystroke doesn't trigger a setDocument\n // round-trip (which steals focus back to the editor and would yank\n // the caret out of the input mid-typing). Also commits immediately\n // on `change` (blur / Enter / spinner click) so explicit commits feel\n // snappy.\n for (const side of MARGIN_FIELDS) {\n const input = root.querySelector<HTMLInputElement>(`input[data-field=\"margin-${side}\"]`);\n if (!input) continue;\n const apply = () => {\n const value = Number(input.value);\n if (!Number.isFinite(value) || value < 0) return;\n const current = ctx.getSectionSetup(getActive());\n // Save caret state, then restore focus + caret AFTER setDocument\n // re-applies the editor selection (which steals focus on its\n // own microtask). Without this the input loses focus on every\n // commit while the user is still typing.\n const wasActive = document.activeElement === input;\n const caretStart = input.selectionStart;\n const caretEnd = input.selectionEnd;\n ctx.setSectionSetup(getActive(), {\n margins: { ...current.margins, [side]: value },\n });\n if (wasActive) {\n // Two rAFs: first clears the synchronous setDocument focus\n // restore; second handles any deferred work that runs in the\n // following microtask (e.g. paginator's selection re-apply).\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (document.activeElement !== input) input.focus();\n if (caretStart !== null && caretEnd !== null) {\n try {\n input.setSelectionRange(caretStart, caretEnd);\n } catch {\n /* number inputs may reject setSelectionRange in some\n browsers — silent fallback is fine, focus is what\n matters. */\n }\n }\n });\n });\n }\n };\n const debouncedApply = debounce(apply, 1000);\n input.addEventListener(\"input\", debouncedApply);\n // Explicit commit on blur / Enter / spinner so the user can short-\n // circuit the debounce by tabbing away or pressing Enter.\n input.addEventListener(\"change\", apply);\n }\n\n // Vertical alignment — apply on change.\n const vAlignEl = root.querySelector<HTMLSelectElement>('select[data-field=\"vertical-align\"]');\n vAlignEl?.addEventListener(\"change\", () => {\n ctx.setSectionSetup(getActive(), {\n verticalAlign: vAlignEl.value as VerticalAlign,\n });\n });\n\n // Different first / last header & footer — apply on change. Mirror\n // the flag onto BOTH header and footer; that's how Word presents the\n // checkboxes to the user (per-section, not per-zone).\n const differentFirst = root.querySelector<HTMLInputElement>(\n 'input[data-field=\"different-first\"]',\n );\n differentFirst?.addEventListener(\"change\", () => {\n const current = ctx.getSectionSetup(getActive());\n const checked = differentFirst.checked;\n ctx.setSectionSetup(getActive(), {\n header: { ...current.header, differentFirst: checked },\n footer: { ...current.footer, differentFirst: checked },\n });\n });\n\n const differentLast = root.querySelector<HTMLInputElement>('input[data-field=\"different-last\"]');\n differentLast?.addEventListener(\"change\", () => {\n const current = ctx.getSectionSetup(getActive());\n const checked = differentLast.checked;\n ctx.setSectionSetup(getActive(), {\n header: { ...current.header, differentLast: checked },\n footer: { ...current.footer, differentLast: checked },\n });\n });\n\n // Insert section break — runs the bus command, then closes the\n // popover so the user can see the result. New section appears as the\n // next entry in the section picker on the next open.\n const insertBreakBtn = root.querySelector<HTMLButtonElement>(\n 'button[data-action=\"insert-section-break\"]',\n );\n insertBreakBtn?.addEventListener(\"click\", (e) => {\n e.preventDefault();\n // Restore the editor selection that was live when the popover\n // opened — the command reads `editor.selection.currentCaret()` and\n // the popover stole focus on open. Without this the command silently\n // no-ops.\n if (savedSelection) ctx.editor.selection.set(savedSelection);\n ctx.editor.commands.execute(\"section.insertBreakAfter\");\n closePopover();\n });\n}\n\n// === section detection ===\n\n/**\n * Section the caret is currently in. We count `section_break` blocks\n * up to (and not including) the caret's block; each break advances the\n * section index by one.\n *\n * Falls back to section 0 when there's no caret (e.g. just-mounted\n * editor with no focus yet).\n */\nfunction detectCurrentSection(editor: Editor): number {\n const caret = editor.selection.currentBlock();\n if (!caret) return 0;\n const blocks = editor.getBlocks();\n let section = 0;\n for (const b of blocks) {\n if (b.id === caret.id) return section;\n if (b.kind === \"section_break\") section += 1;\n }\n return section;\n}\n\nfunction clampIndex(index: number, count: number): number {\n if (!Number.isFinite(index) || index < 0) return 0;\n if (index >= count) return Math.max(0, count - 1);\n return index;\n}\n\n/** Trailing-edge debounce — fn runs once, `ms` after the last call. */\nfunction debounce(fn: () => void, ms: number): () => void {\n let timer: ReturnType<typeof setTimeout> | null = null;\n return () => {\n if (timer !== null) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n fn();\n }, ms);\n };\n}\n","/**\n * Shared \"what does the current selection look like?\" reader.\n *\n * Tools wire `editor.on(\"selection\", …)` + `editor.on(\"change\", …)` and\n * call `readSelectionState(editor)` to get a snapshot they can use to\n * paint their pressed/active/value state. Everything goes through the\n * public Editor API — no internal access.\n */\n\nimport type {\n Block,\n Editor,\n InlineRun,\n Paragraph,\n ParagraphProperties,\n RunProperties,\n SobreeDocument,\n TextRun,\n} from \"@sobree/core\";\nimport { resolveStyleCascade } from \"@sobree/core\";\n\nexport interface SelectionState {\n /** Block kind under the caret (or `null` when focus is outside).\n * Derived from `Block[\"kind\"]` so new block kinds never silently\n * drift this type out of sync. */\n blockKind: Block[\"kind\"] | null;\n /** Paragraph properties of the active block (`null` for non-paragraphs). */\n paragraphProps: ParagraphProperties | null;\n /** Numbering format of the active block, when it's a list item. */\n listFormat: \"bullet\" | \"decimal\" | null;\n /**\n * Dominant run-level properties at the caret / range. For a range\n * selection: a property is included only if every text run in the\n * range agrees on it; otherwise it's omitted (=> \"mixed\", select\n * shows blank). For a caret: properties of the run on the left of\n * the caret (Word-style — the formatting that the next typed char\n * inherits).\n */\n runProps: RunProperties;\n}\n\nconst EMPTY_STATE: SelectionState = {\n blockKind: null,\n paragraphProps: null,\n listFormat: null,\n runProps: {},\n};\n\nexport function readSelectionState(editor: Editor): SelectionState {\n const caret = editor.selection.currentCaret();\n if (!caret) return EMPTY_STATE;\n const doc = editor.getDocument();\n const info = editor.getBlockById(caret.block.id);\n if (!info) return EMPTY_STATE;\n const block = doc.body[info.index];\n if (!block) return EMPTY_STATE;\n if (block.kind !== \"paragraph\") {\n return {\n ...EMPTY_STATE,\n blockKind: block.kind,\n };\n }\n const ownRunProps = resolveRunProps(editor, block);\n // Style cascade is resolved in @sobree/core and reused here so the\n // toolbar's \"what's the effective font?\" answer is identical to what\n // the renderer applies to the block element.\n const { runDefaults } = resolveStyleCascade(doc.styles, block.properties.styleId);\n return {\n blockKind: \"paragraph\",\n paragraphProps: block.properties,\n listFormat: resolveListFormat(doc, block),\n runProps: { ...runDefaults, ...ownRunProps },\n };\n}\n\nfunction resolveListFormat(doc: SobreeDocument, p: Paragraph): \"bullet\" | \"decimal\" | null {\n const numbering = p.properties.numbering;\n if (!numbering) return null;\n const def = doc.numbering.find((n) => n.numId === numbering.numId);\n const fmt = def?.abstractFormat.levels[0]?.format;\n if (fmt === \"bullet\" || fmt === \"decimal\") return fmt;\n return null;\n}\n\nfunction resolveRunProps(editor: Editor, p: Paragraph): RunProperties {\n const range = editor.selection.currentRange();\n if (range && range.from.block.id === range.to.block.id) {\n // Range inside this paragraph — collect every text run that touches\n // the range and intersect their props.\n const lo = Math.min(range.from.offset, range.to.offset);\n const hi = Math.max(range.from.offset, range.to.offset);\n const touched = textRunsInRange(p.runs, lo, hi);\n if (touched.length === 0) return {};\n return intersectProps(touched.map((r) => r.properties));\n }\n // Caret: the run immediately to the left of the offset wins (or, at\n // offset 0, the first run). Matches Word — the format that newly\n // typed characters will inherit.\n const caret = editor.selection.currentCaret();\n if (!caret) return {};\n return runPropsAtOffset(p.runs, caret.offset);\n}\n\n/** Walk a paragraph's runs and return every TextRun whose extent\n * overlaps `[lo, hi)`. Hyperlink children are flattened. */\nfunction textRunsInRange(runs: readonly InlineRun[], lo: number, hi: number): TextRun[] {\n const out: TextRun[] = [];\n let cursor = 0;\n walk(runs);\n return out;\n\n function walk(list: readonly InlineRun[]): void {\n for (const r of list) {\n const start = cursor;\n const len = runLen(r);\n const end = start + len;\n cursor = end;\n if (end <= lo) continue;\n if (start >= hi) return;\n if (r.kind === \"text\") out.push(r);\n else if (r.kind === \"hyperlink\") {\n // Hyperlink children own their own textual extent — but `cursor`\n // already moved past the whole hyperlink span. Restore so the\n // recursion's offsets line up with the outer document order.\n cursor = start;\n walk(r.children);\n cursor = end;\n }\n }\n }\n}\n\nfunction runPropsAtOffset(runs: readonly InlineRun[], offset: number): RunProperties {\n let cursor = 0;\n let lastTextProps: RunProperties = {};\n for (const r of runs) {\n const len = runLen(r);\n if (r.kind === \"text\") lastTextProps = r.properties;\n else if (r.kind === \"hyperlink\") {\n const inner = lastTextRunProps(r.children);\n if (inner) lastTextProps = inner;\n }\n if (offset > cursor && offset <= cursor + len) {\n return r.kind === \"text\" ? r.properties : lastTextProps;\n }\n cursor += len;\n }\n return lastTextProps;\n}\n\nfunction lastTextRunProps(runs: readonly InlineRun[]): RunProperties | null {\n let last: RunProperties | null = null;\n for (const r of runs) {\n if (r.kind === \"text\") last = r.properties;\n else if (r.kind === \"hyperlink\") {\n const inner = lastTextRunProps(r.children);\n if (inner) last = inner;\n }\n }\n return last;\n}\n\nfunction runLen(r: InlineRun): number {\n if (r.kind === \"text\") return r.text.length;\n if (r.kind === \"hyperlink\") {\n let n = 0;\n for (const inner of r.children) n += runLen(inner);\n return n;\n }\n // Atomic single-cell runs (break, tab, field, drawing).\n return 1;\n}\n\n/**\n * Return only the properties that EVERY input snapshot agrees on. A\n * key shared by all snapshots with the same value survives; anything\n * that differs (or is missing in some snapshots) is dropped.\n */\nfunction intersectProps(snapshots: ReadonlyArray<RunProperties>): RunProperties {\n if (snapshots.length === 0) return {};\n const first = snapshots[0]!;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(first)) {\n if (v === undefined) continue;\n let agree = true;\n for (let i = 1; i < snapshots.length; i++) {\n if ((snapshots[i] as Record<string, unknown>)[k] !== v) {\n agree = false;\n break;\n }\n }\n if (agree) out[k] = v;\n }\n return out as RunProperties;\n}\n","import type { BlockRef } from \"@sobree/core\";\nimport type { Paragraph, SobreeDocument } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { BlockKind, BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\nimport { readSelectionState } from \"./selectionState\";\n\nexport interface PerKindContext {\n editor: Editor;\n target: BlockTarget;\n}\n\n/**\n * Build the kind-specific HTML fragment that follows the shared text\n * tools. Returns an empty string for kinds without extras (e.g. no\n * special UI needed yet).\n *\n * Table tools live in B5 — handled in a dedicated module.\n */\nexport function buildPerKindHtml(kind: BlockKind): string {\n if (kind === \"table\") return \"\";\n if (kind === \"header\" || kind === \"footer\") return \"\";\n\n const alignment = buildAlignmentHtml();\n const extras: string[] = [alignment];\n\n if (kind === \"heading\") extras.push(buildHeadingLevelHtml());\n if (kind === \"list\" || kind === \"listOrdered\") extras.push(buildListHtml(kind));\n if (kind === \"image\") extras.push(buildImageHtml());\n\n return `<div class=\"tb-divider\"></div>${extras.join('<div class=\"tb-divider\"></div>')}`;\n}\n\n/**\n * Wire clicks + input events for the per-kind fragment. Returns a detach\n * function. Safe to call even if `kind` has no extras (noop detach).\n */\nexport function wirePerKindTools(root: HTMLElement, ctx: PerKindContext): () => void {\n const handlers: Array<() => void> = [];\n\n const onAlign = (e: Event) => {\n const btn = (e.target as HTMLElement).closest('button[data-action=\"align\"]');\n if (!btn) return;\n const arg = btn.getAttribute(\"data-arg\");\n if (!arg) return;\n const alignment = arg === \"justify\" ? \"both\" : arg;\n if (\n alignment !== \"left\" &&\n alignment !== \"center\" &&\n alignment !== \"right\" &&\n alignment !== \"both\"\n )\n return;\n applyToTargetBlocks(ctx, (ref) => {\n const result = ctx.editor.applyBlockProperties([ref], { alignment });\n warnOnEditFailure(\"align\", result);\n });\n };\n root.addEventListener(\"click\", onAlign);\n handlers.push(() => root.removeEventListener(\"click\", onAlign));\n\n const onLineSpacing = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"line-spacing\") return;\n const raw = (el as HTMLSelectElement).value;\n (el as HTMLSelectElement).selectedIndex = 0;\n const mult = Number(raw);\n if (!Number.isFinite(mult) || mult <= 0) return;\n applyToTargetBlocks(ctx, (ref) =>\n ctx.editor.applyBlockProperties([ref], {\n spacing: { line: Math.round(240 * mult), lineRule: \"auto\" },\n }),\n );\n };\n root.addEventListener(\"change\", onLineSpacing);\n handlers.push(() => root.removeEventListener(\"change\", onLineSpacing));\n\n if (ctx.target.kind === \"heading\") {\n const onHeadingLevel = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"heading-level\") return;\n const level = Number((el as HTMLSelectElement).value);\n if (!Number.isFinite(level) || level < 1 || level > 6) return;\n const ref = refForTarget(ctx);\n if (!ref) return;\n ctx.editor.applyBlockProperties([ref], { styleId: `Heading${level}` });\n };\n root.addEventListener(\"change\", onHeadingLevel);\n handlers.push(() => root.removeEventListener(\"change\", onHeadingLevel));\n }\n\n if (ctx.target.kind === \"list\" || ctx.target.kind === \"listOrdered\") {\n const onListAction = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\");\n if (action === \"toggle-list-kind\") toggleListKind(ctx);\n else if (action === \"align-list\") {\n const arg = btn.getAttribute(\"data-arg\");\n const alignment = arg === \"justify\" ? \"both\" : arg;\n if (\n alignment !== \"left\" &&\n alignment !== \"center\" &&\n alignment !== \"right\" &&\n alignment !== \"both\"\n )\n return;\n applyAlignmentToWholeList(ctx, alignment);\n }\n };\n root.addEventListener(\"click\", onListAction);\n handlers.push(() => root.removeEventListener(\"click\", onListAction));\n }\n\n if (ctx.target.kind === \"image\") {\n const onImageInput = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (role !== \"image-alt\") return;\n const alt = (el as HTMLInputElement).value;\n updateImageAltAtTarget(ctx, alt);\n };\n root.addEventListener(\"input\", onImageInput);\n handlers.push(() => root.removeEventListener(\"input\", onImageInput));\n\n const onImageClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (btn?.getAttribute(\"data-action\") === \"delete-image\") {\n deleteImageAtTarget(ctx);\n }\n };\n root.addEventListener(\"click\", onImageClick);\n handlers.push(() => root.removeEventListener(\"click\", onImageClick));\n }\n\n // Sync visible state — alignment pressed-state, line-spacing select\n // value, heading level, list-toggle icon. Driven by editor `selection`\n // and `change` events so the toolbar mirrors what's under the caret.\n const syncAll = () => syncPerKindState(root, ctx);\n const detachSelection = ctx.editor.on(\"selection\", syncAll);\n const detachChange = ctx.editor.on(\"change\", syncAll);\n handlers.push(detachSelection, detachChange);\n // Initial paint.\n syncAll();\n\n return () => {\n for (const detach of handlers) detach();\n };\n}\n\n/**\n * Repaint pressed/active/value state on every per-kind control to\n * reflect the current selection. Call from selection / change events\n * and once on toolbar open.\n */\nfunction syncPerKindState(root: HTMLElement, ctx: PerKindContext): void {\n const state = readSelectionState(ctx.editor);\n\n // Alignment buttons — only one is \"pressed\" at a time.\n const align = state.paragraphProps?.alignment;\n const arg = align === \"both\" ? \"justify\" : align === undefined ? \"left\" : align;\n const alignBtns = root.querySelectorAll<HTMLButtonElement>('button[data-action=\"align\"]');\n for (const btn of alignBtns) {\n const on = btn.getAttribute(\"data-arg\") === arg;\n btn.setAttribute(\"aria-pressed\", String(on));\n btn.classList.toggle(\"is-active\", on);\n }\n\n // Line-spacing select — show the current multiplier (or blank for\n // \"default\"). The select uses string options like \"1.5\".\n const lineSel = root.querySelector<HTMLSelectElement>('select[data-role=\"line-spacing\"]');\n if (lineSel) {\n const sp = state.paragraphProps?.spacing;\n if (sp?.line && sp.lineRule === \"auto\") {\n const mult = sp.line / 240;\n const matched = optionByValue(lineSel, String(mult));\n lineSel.value = matched ?? \"\";\n } else {\n lineSel.value = \"\";\n }\n }\n\n // Heading level — show the current heading number (1..6) or blank.\n const headingSel = root.querySelector<HTMLSelectElement>('select[data-role=\"heading-level\"]');\n if (headingSel) {\n const styleId = state.paragraphProps?.styleId;\n const m = styleId?.match(/^Heading([1-6])$/);\n headingSel.value = m?.[1] ?? \"\";\n }\n\n // List toggle — flip the icon to match the current numbering format.\n const listToggle = root.querySelector<HTMLButtonElement>(\n 'button[data-action=\"toggle-list-kind\"]',\n );\n if (listToggle) {\n const isOrdered = state.listFormat === \"decimal\";\n listToggle.innerHTML = icon(isOrdered ? \"list-numbered\" : \"list-bullet\");\n listToggle.title = isOrdered ? \"Switch to bullet list\" : \"Switch to numbered list\";\n }\n}\n\nfunction optionByValue(sel: HTMLSelectElement, value: string): string | null {\n for (const opt of Array.from(sel.options)) {\n if (opt.value === value) return value;\n }\n return null;\n}\n\n// === HTML builders ===\n\nfunction buildAlignmentHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"alignment\">\n <button type=\"button\" data-action=\"align\" data-arg=\"left\" title=\"Align left\">${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"center\" title=\"Align centre\">${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"right\" title=\"Align right\">${icon(\"align-right\")}</button>\n <button type=\"button\" data-action=\"align\" data-arg=\"justify\" title=\"Justify\">${icon(\"align-justify\")}</button>\n <select data-role=\"line-spacing\" aria-label=\"Line spacing\" title=\"Line spacing\">\n <option value=\"\">Line</option>\n <option value=\"1\">Single</option>\n <option value=\"1.15\">1.15</option>\n <option value=\"1.5\">1.5</option>\n <option value=\"2\">Double</option>\n </select>\n </div>\n `;\n}\n\nfunction buildHeadingLevelHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"heading\">\n <select data-role=\"heading-level\" aria-label=\"Heading level\" title=\"Heading level\">\n <option value=\"\">Level</option>\n <option value=\"1\">H1</option>\n <option value=\"2\">H2</option>\n <option value=\"3\">H3</option>\n <option value=\"4\">H4</option>\n <option value=\"5\">H5</option>\n <option value=\"6\">H6</option>\n </select>\n </div>\n `;\n}\n\nfunction buildListHtml(_kind: \"list\" | \"listOrdered\"): string {\n // The plain `align` action only affects the focused LI. These\n // `align-list` variants apply the alignment to *every* item in the\n // same list — a one-click way to reformat a whole list without\n // clicking each item.\n return `\n <div class=\"tb-group\" data-group=\"list\">\n <button type=\"button\" data-action=\"toggle-list-kind\" title=\"Toggle bullet / numbered\">${icon(\"list-numbered\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"left\" title=\"Align whole list left\">${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"center\" title=\"Align whole list centre\">${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"right\" title=\"Align whole list right\">${icon(\"align-right\")}</button>\n <button type=\"button\" data-action=\"align-list\" data-arg=\"justify\" title=\"Justify whole list\">${icon(\"align-justify\")}</button>\n </div>\n `;\n}\n\nfunction buildImageHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"image\">\n <input type=\"text\" data-role=\"image-alt\" placeholder=\"Alt text\" aria-label=\"Alt text\" title=\"Alt text (screen reader description)\" />\n <button type=\"button\" data-action=\"delete-image\" title=\"Delete image\">${icon(\"trash\")}</button>\n </div>\n `;\n}\n\n// === action helpers ===\n\nfunction applyToTargetBlocks(ctx: PerKindContext, fn: (ref: BlockRef) => void): void {\n const ref = refForTarget(ctx);\n if (ref) fn(ref);\n}\n\n/**\n * Surface `EditResult` failures from toolbar actions in the console.\n * Editor mutations can fail silently (optimistic-lock conflicts, etc.)\n * if callers don't check the return value — and that's exactly what\n * happened here for months before we noticed: clicking \"justify\" did\n * nothing and there was no warning. Logging at least makes the next\n * silent failure visible at dev-time.\n */\nfunction warnOnEditFailure(action: string, result: { ok: boolean; error?: unknown }): void {\n if (!result.ok) {\n // eslint-disable-next-line no-console\n console.warn(`[block-tools] ${action} failed:`, result.error);\n }\n}\n\nfunction refForTarget(ctx: PerKindContext): BlockRef | null {\n // Prefer the explicit `target.blockId` — that's the block the toolbar\n // was *opened* on (captured by `blockTargetFrom` from the indicator\n // click). Reading `currentCaret()` here is unreliable because\n // clicking a toolbar button shifts focus, and the browser selection\n // can move off the editor before the action handler fires. In the\n // worst case the caret falls back to `getBlocks()[0]` (the title)\n // and the wrong block silently gets mutated — observed on user-\n // contract: toolbar `align: justify` clicked on a list item went\n // nowhere because `currentCaret()` returned null, then the\n // optimistic-lock guard on `applyBlockProperties` failed silently\n // because version 0 was sent against a current version of N.\n const explicitId = ctx.target.blockId;\n if (explicitId) {\n const info = ctx.editor.getBlockById(explicitId);\n if (info) return { id: info.id, version: info.version };\n }\n const caret = ctx.editor.selection.currentCaret();\n return caret?.block ?? ctx.editor.getBlocks()[0] ?? null;\n}\n\n/**\n * Apply `alignment` to every list item that belongs to the same logical\n * list as the toolbar target. \"Same list\" = a consecutive run of\n * paragraph blocks whose `numbering.numId` matches the target's — the\n * same grouping the renderer uses to fold paragraphs into one `<ol>` /\n * `<ul>`. Blocks outside that run (other lists with a different numId,\n * intervening paragraphs / headings) are left untouched.\n */\nfunction applyAlignmentToWholeList(\n ctx: PerKindContext,\n alignment: \"left\" | \"center\" | \"right\" | \"both\",\n): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const doc = ctx.editor.getDocument();\n const targetBlock = doc.body[info.index];\n if (!targetBlock || targetBlock.kind !== \"paragraph\" || !targetBlock.properties.numbering) return;\n const targetNumId = targetBlock.properties.numbering.numId;\n\n // Walk backward and forward from the target while the numId stays\n // the same. Stop at the first non-matching block on either side.\n let start = info.index;\n while (start > 0) {\n const prev = doc.body[start - 1];\n if (!prev || prev.kind !== \"paragraph\" || prev.properties.numbering?.numId !== targetNumId)\n break;\n start--;\n }\n let endExclusive = info.index + 1;\n while (endExclusive < doc.body.length) {\n const next = doc.body[endExclusive];\n if (!next || next.kind !== \"paragraph\" || next.properties.numbering?.numId !== targetNumId)\n break;\n endExclusive++;\n }\n\n // Collect fresh BlockRefs (id + current version) for every LI in the run.\n // Reading versions live avoids optimistic-lock failures when a recent\n // edit bumped a sibling.\n const refs: BlockRef[] = [];\n for (let i = start; i < endExclusive; i++) {\n const blk = doc.body[i];\n if (!blk) continue;\n const blkInfo = ctx.editor.getBlockById((blk as { id?: string }).id ?? \"\");\n // The doc body is keyed by index — to get the id, ask the editor.\n const liInfo = ctx.editor.getBlocks()[i];\n if (!liInfo) continue;\n refs.push({ id: liInfo.id, version: liInfo.version });\n void blkInfo;\n }\n\n const result = ctx.editor.applyBlockProperties(refs, { alignment });\n warnOnEditFailure(\"align-list\", result);\n}\n\n/**\n * Flip a list block between bullet and numbered by swapping its numId to\n * another numbering definition with the opposite format.\n *\n * Implemented as a whole-document mutation because list numbering\n * definitions live at the doc level.\n */\nfunction toggleListKind(ctx: PerKindContext): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\" || !block.properties.numbering) return;\n const currentDef = doc.numbering.find((n) => n.numId === block.properties.numbering?.numId);\n if (!currentDef) return;\n const currentFormat = currentDef.abstractFormat.levels[0]?.format ?? \"bullet\";\n const wantBullet = currentFormat !== \"bullet\";\n\n // Build the full next document in one step — both the numbering def\n // (if we need to add one) and the block's reference swap land together\n // in a single `setDocument`, so we don't have to re-resolve block refs\n // between operations.\n const existing = doc.numbering.find(\n (n) => n.abstractFormat.levels[0]?.format === (wantBullet ? \"bullet\" : \"decimal\"),\n );\n let targetNumId: number;\n let nextNumbering = doc.numbering;\n if (existing) {\n targetNumId = existing.numId;\n } else {\n targetNumId = doc.numbering.reduce((n, d) => Math.max(n, d.numId), 0) + 1;\n nextNumbering = [\n ...doc.numbering,\n {\n numId: targetNumId,\n abstractFormat: {\n levels: [\n {\n level: 0,\n format: wantBullet ? \"bullet\" : \"decimal\",\n text: wantBullet ? \"\\u2022\" : \"%1.\",\n },\n ],\n },\n },\n ];\n }\n\n const nextBody = doc.body.slice();\n nextBody[info.index] = {\n ...block,\n properties: {\n ...block.properties,\n numbering: { numId: targetNumId, level: 0 },\n },\n };\n\n const nextDoc: SobreeDocument = { ...doc, numbering: nextNumbering, body: nextBody };\n ctx.editor.setDocument(nextDoc);\n}\n\nfunction updateImageAltAtTarget(ctx: PerKindContext, alt: string): void {\n // Walk the block's runs, find the first DrawingRun, update its altText.\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index];\n if (!block || block.kind !== \"paragraph\") return;\n const runs = block.runs.map((r) => (r.kind === \"drawing\" ? { ...r, altText: alt } : r));\n ctx.editor.replaceBlock(ref, { ...block, runs });\n}\n\nfunction deleteImageAtTarget(ctx: PerKindContext): void {\n const ref = refForTarget(ctx);\n if (!ref) return;\n const doc = ctx.editor.getDocument();\n const info = ctx.editor.getBlockById(ref.id);\n if (!info) return;\n const block = doc.body[info.index] as Paragraph | undefined;\n if (!block || block.kind !== \"paragraph\") return;\n const runs = block.runs.filter((r) => r.kind !== \"drawing\");\n ctx.editor.replaceBlock(ref, { ...block, runs });\n}\n","import type { BlockRef } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\n\nexport type TableMode = \"cell\" | \"table\";\n\nexport interface TableContext {\n editor: Editor;\n target: BlockTarget;\n /** Current-cell resolution — null if the caret isn't inside a cell. */\n cell: CellLocation | null;\n}\n\nexport interface CellLocation {\n row: number;\n col: number;\n element: HTMLTableCellElement;\n}\n\n/**\n * Build the table toolbar's HTML. Pill at the top toggles Cell/Table.\n * In Cell mode we show cell-level ops (alignment of the cell's content,\n * vertical alignment, merge / unmerge). In Table mode we show row /\n * column ops plus whole-table toggles.\n *\n * Text tools are NOT included here — the caller (`BlockTools`)\n * prepends the shared text-tools HTML so formatting always applies\n * to the cell content.\n */\nexport function buildTableToolsHtml(mode: TableMode, hasCell: boolean): string {\n const pill = `\n <div class=\"tb-divider\"></div>\n <div class=\"tb-pill\" role=\"tablist\" aria-label=\"Table scope\">\n <button type=\"button\" data-action=\"mode\" data-arg=\"cell\"\n title=\"Edit the current cell\"\n ${mode === \"cell\" ? 'class=\"is-active\"' : \"\"}\n ${!hasCell ? \"disabled\" : \"\"}\n aria-pressed=\"${mode === \"cell\"}\">Cell</button>\n <button type=\"button\" data-action=\"mode\" data-arg=\"table\"\n title=\"Edit the whole table\"\n ${mode === \"table\" ? 'class=\"is-active\"' : \"\"}\n aria-pressed=\"${mode === \"table\"}\">Table</button>\n </div>\n <div class=\"tb-divider\"></div>\n `;\n\n if (mode === \"cell\") return pill + buildCellOpsHtml(hasCell);\n return pill + buildTableOpsHtml();\n}\n\nexport function wireTableTools(\n root: HTMLElement,\n ctx: TableContext,\n onModeChange: (mode: TableMode) => void,\n): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn || btn.hasAttribute(\"disabled\")) return;\n const action = btn.getAttribute(\"data-action\");\n const arg = btn.getAttribute(\"data-arg\");\n if (action === \"mode\") {\n if (arg === \"cell\" || arg === \"table\") onModeChange(arg);\n return;\n }\n const tableRef = tableRefFor(ctx);\n if (!tableRef) return;\n handleAction(ctx, tableRef, action, arg);\n };\n\n const onChange = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (!role) return;\n const tableRef = tableRefFor(ctx);\n if (!tableRef) return;\n if (role === \"cell-valign\" && ctx.cell) {\n const v = (el as HTMLSelectElement).value;\n if (v === \"top\" || v === \"center\" || v === \"bottom\") {\n ctx.editor.table.setCellProperties(\n { table: tableRef, row: ctx.cell.row, col: ctx.cell.col },\n { verticalAlign: v },\n );\n }\n }\n };\n\n root.addEventListener(\"click\", onClick);\n root.addEventListener(\"change\", onChange);\n return () => {\n root.removeEventListener(\"click\", onClick);\n root.removeEventListener(\"change\", onChange);\n };\n}\n\n// === HTML ===\n\nfunction buildCellOpsHtml(hasCell: boolean): string {\n const disabled = hasCell ? \"\" : \"disabled\";\n return `\n <div class=\"tb-group\" data-group=\"cell-align\">\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"left\" title=\"Align left in cell\" ${disabled}>${icon(\"align-left\")}</button>\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"center\" title=\"Align centre in cell\" ${disabled}>${icon(\"align-center\")}</button>\n <button type=\"button\" data-action=\"cell-align\" data-arg=\"right\" title=\"Align right in cell\" ${disabled}>${icon(\"align-right\")}</button>\n <select data-role=\"cell-valign\" aria-label=\"Vertical alignment\" title=\"Vertical align\" ${disabled}>\n <option value=\"\">V-align</option>\n <option value=\"top\">Top</option>\n <option value=\"center\">Middle</option>\n <option value=\"bottom\">Bottom</option>\n </select>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"cell-merge\">\n <button type=\"button\" data-action=\"merge-right\" title=\"Merge with cell to the right\" ${disabled}>Merge →</button>\n <button type=\"button\" data-action=\"merge-down\" title=\"Merge with cell below\" ${disabled}>Merge ↓</button>\n <button type=\"button\" data-action=\"unmerge\" title=\"Unmerge\" ${disabled}>Unmerge</button>\n </div>\n `;\n}\n\nfunction buildTableOpsHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"row-ops\">\n <button type=\"button\" data-action=\"row-above\" title=\"Insert row above\">+ Row ↑</button>\n <button type=\"button\" data-action=\"row-below\" title=\"Insert row below\">+ Row ↓</button>\n <button type=\"button\" data-action=\"delete-row\" title=\"Delete row\">${icon(\"trash\")} Row</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"col-ops\">\n <button type=\"button\" data-action=\"col-left\" title=\"Insert column left\">+ Col ←</button>\n <button type=\"button\" data-action=\"col-right\" title=\"Insert column right\">+ Col →</button>\n <button type=\"button\" data-action=\"delete-col\" title=\"Delete column\">${icon(\"trash\")} Col</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"table-ops\">\n <button type=\"button\" data-action=\"toggle-header-row\" title=\"Toggle first row as header\">Header row</button>\n <button type=\"button\" data-action=\"delete-table\" title=\"Delete table\">${icon(\"trash\")} Table</button>\n </div>\n `;\n}\n\n// === action dispatch ===\n\nfunction handleAction(\n ctx: TableContext,\n tableRef: BlockRef,\n action: string | null,\n arg: string | null,\n): void {\n if (!action) return;\n const ed = ctx.editor;\n const cell = ctx.cell;\n const row = cell?.row ?? 0;\n const col = cell?.col ?? 0;\n\n switch (action) {\n case \"cell-align\": {\n if (!cell) return;\n const a = arg === \"left\" || arg === \"center\" || arg === \"right\" ? arg : \"left\";\n // Set alignment on the FIRST paragraph in the cell via setCellContent.\n const info = ed.getBlockById(tableRef.id);\n if (!info) return;\n const doc = ed.getDocument();\n const table = doc.body[info.index];\n if (!table || table.kind !== \"table\") return;\n const rowObj = table.rows[cell.row];\n const cellObj = rowObj?.cells[cellIndexAt(table, cell.row, cell.col)];\n if (!cellObj) return;\n const newContent = cellObj.content.map((b) =>\n b.kind === \"paragraph\"\n ? { ...b, properties: { ...b.properties, alignment: a as \"left\" | \"center\" | \"right\" } }\n : b,\n );\n ed.table.setCellContent({ table: tableRef, row: cell.row, col: cell.col }, newContent);\n return;\n }\n case \"merge-right\":\n if (!cell) return;\n ed.table.mergeCells(tableRef, { row, col, colSpan: 2 });\n return;\n case \"merge-down\":\n if (!cell) return;\n ed.table.mergeCells(tableRef, { row, col, rowSpan: 2 });\n return;\n case \"unmerge\":\n if (!cell) return;\n ed.table.unmergeCell({ table: tableRef, row, col });\n return;\n case \"row-above\":\n ed.table.insertRow(tableRef, { at: \"before\", index: row });\n return;\n case \"row-below\":\n ed.table.insertRow(tableRef, { at: \"after\", index: row });\n return;\n case \"delete-row\":\n ed.table.deleteRow(tableRef, row);\n return;\n case \"col-left\":\n ed.table.insertColumn(tableRef, { at: \"before\", index: col });\n return;\n case \"col-right\":\n ed.table.insertColumn(tableRef, { at: \"after\", index: col });\n return;\n case \"delete-col\":\n ed.table.deleteColumn(tableRef, col);\n return;\n case \"toggle-header-row\":\n ed.table.toggleHeaderRow(tableRef, 0);\n return;\n case \"delete-table\":\n ed.deleteBlock(tableRef);\n return;\n }\n}\n\nfunction tableRefFor(ctx: TableContext): BlockRef | null {\n const caret = ctx.editor.selection.currentCaret();\n // The caret might be inside a cell — in which case its block ref is\n // the cell's containing paragraph (a child of the table cell). We\n // need the table block itself; walk `getBlocks()` looking for the\n // table that owns the target element.\n const doc = ctx.editor.getDocument();\n for (let i = 0; i < doc.body.length; i++) {\n if (doc.body[i]?.kind === \"table\") {\n const info = ctx.editor.getBlock(i);\n const blockEl = ctx.target.element;\n // When the caret is in a cell, `target` is the table element;\n // otherwise fall back to the first table in the body.\n if (!blockEl || blockEl.tagName.toLowerCase() !== \"table\") continue;\n return { id: info.id, version: info.version };\n }\n }\n // Fallback: first table in the doc.\n const tableBlock = ctx.editor.getBlocks().find((b) => b.kind === \"table\");\n void caret;\n return tableBlock ? { id: tableBlock.id, version: tableBlock.version } : null;\n}\n\n/** Index into `row.cells` for a given visual column. */\nfunction cellIndexAt(table: import(\"@sobree/core\").Table, row: number, col: number): number {\n const r = table.rows[row];\n if (!r) return -1;\n let c = 0;\n for (let i = 0; i < r.cells.length; i++) {\n const span = r.cells[i]?.gridSpan ?? 1;\n if (col >= c && col < c + span) return i;\n c += span;\n }\n return -1;\n}\n\n/**\n * Given a `<table>` element and the current caret, return the\n * `{row, col, element}` of the cell under the caret, or `null` if the\n * caret isn't inside a cell in that table.\n */\nexport function locateCellFromSelection(tableEl: HTMLTableElement): CellLocation | null {\n const sel = window.getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchor = sel.anchorNode;\n if (!anchor) return null;\n const cell = (\n anchor.nodeType === Node.TEXT_NODE ? anchor.parentElement : (anchor as HTMLElement)\n )?.closest(\"td, th\") as HTMLTableCellElement | null;\n if (!cell || !tableEl.contains(cell)) return null;\n const tr = cell.parentElement as HTMLTableRowElement | null;\n if (!tr) return null;\n // Row: position of its <tr> among all <tr>s in the table.\n const allRows = Array.from(tableEl.querySelectorAll(\"tr\"));\n const row = allRows.indexOf(tr);\n // Col: sum of preceding siblings' colspan.\n let col = 0;\n for (const sibling of Array.from(tr.children)) {\n if (sibling === cell) break;\n const cs = Number(sibling.getAttribute(\"colspan\") ?? 1);\n col += Number.isFinite(cs) && cs > 0 ? cs : 1;\n }\n return { row, col, element: cell };\n}\n","import { MARK_ON, MARK_PROP, toggleMark } from \"@sobree/core\";\nimport type { Range as ApiRange, BlockRef, RunProperties } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { BlockTarget } from \"../blockKinds\";\nimport { icon } from \"./icons\";\nimport { readSelectionState } from \"./selectionState\";\n\nexport interface ToolContext {\n editor: Editor;\n target: BlockTarget;\n}\n\n/**\n * Text-formatting tools shown on every block type. Returns an HTML\n * string for the toolbar shell to inject, plus a wiring function that\n * installs click/input listeners against it.\n */\nexport function buildTextToolsHtml(): string {\n return `\n <div class=\"tb-group\" data-group=\"text\">\n <select data-role=\"font-family\" aria-label=\"Font family\" title=\"Font family\">\n <option value=\"\">Font</option>\n <option>Arial</option>\n <option>Calibri</option>\n <option>Cambria</option>\n <option>Consolas</option>\n <option>Courier New</option>\n <option>Georgia</option>\n <option>Helvetica</option>\n <option>Times New Roman</option>\n <option>Verdana</option>\n </select>\n <select data-role=\"font-size\" aria-label=\"Font size\" title=\"Font size\">\n <option value=\"\">Size</option>\n ${[8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 72].map((n) => `<option>${n}</option>`).join(\"\")}\n </select>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"marks\" role=\"group\" aria-label=\"Text formatting\">\n <button type=\"button\" data-action=\"wrap\" data-tag=\"strong\" title=\"Bold (Ctrl+B)\" aria-label=\"Bold\" aria-pressed=\"false\">${icon(\"bold\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"em\" title=\"Italic (Ctrl+I)\" aria-label=\"Italic\" aria-pressed=\"false\">${icon(\"italic\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"u\" title=\"Underline (Ctrl+U)\" aria-label=\"Underline\" aria-pressed=\"false\">${icon(\"underline\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"s\" title=\"Strikethrough\" aria-label=\"Strikethrough\" aria-pressed=\"false\">${icon(\"strike\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"sup\" title=\"Superscript\" aria-label=\"Superscript\" aria-pressed=\"false\">${icon(\"superscript\")}</button>\n <button type=\"button\" data-action=\"wrap\" data-tag=\"sub\" title=\"Subscript\" aria-label=\"Subscript\" aria-pressed=\"false\">${icon(\"subscript\")}</button>\n </div>\n <div class=\"tb-divider\"></div>\n <div class=\"tb-group\" data-group=\"colour\" role=\"group\" aria-label=\"Colour and highlight\">\n <label class=\"tb-colour\" title=\"Text colour\">\n ${icon(\"paintbrush\")}\n <input type=\"color\" data-role=\"color\" value=\"#c96f22\" aria-label=\"Text colour\" />\n </label>\n <label class=\"tb-colour\" title=\"Highlight\">\n ${icon(\"highlighter\")}\n <input type=\"color\" data-role=\"highlight\" value=\"#fff3a1\" aria-label=\"Highlight colour\" />\n </label>\n <button type=\"button\" data-action=\"clear-formatting\" title=\"Clear formatting\" aria-label=\"Clear formatting\">${icon(\"eraser\")}</button>\n </div>\n `;\n}\n\n/** Install click + input listeners for the text tool block. */\nexport function wireTextTools(root: HTMLElement, ctx: ToolContext): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\"button[data-action]\");\n if (!btn) return;\n const action = btn.getAttribute(\"data-action\");\n if (action === \"wrap\") {\n const tag = btn.getAttribute(\"data-tag\") as\n | \"strong\"\n | \"em\"\n | \"u\"\n | \"s\"\n | \"sup\"\n | \"sub\"\n | \"mark\"\n | null;\n if (!tag) return;\n const range = rangeForContext(ctx);\n toggleMark(ctx.editor, range, tag);\n // Sync visual state immediately — the editor's change event is\n // debounced, so wait for the next tick to re-check the AST.\n requestAnimationFrame(() => syncActive(root, ctx));\n return;\n }\n if (action === \"clear-formatting\") {\n ctx.editor.clearInlineFormattingAtSelection();\n requestAnimationFrame(() => syncActive(root, ctx));\n return;\n }\n };\n\n const onInput = (e: Event) => {\n const el = e.target as HTMLElement;\n const role = el.getAttribute(\"data-role\");\n if (!role) return;\n const range = rangeForContext(ctx);\n if (role === \"font-family\") {\n const v = (el as HTMLSelectElement).value;\n // No reset — the syncActive() pass on the next selection/change\n // event puts the select back in sync with the actual run state.\n if (v) ctx.editor.applyRunProperties(range, { fontFamily: v });\n return;\n }\n if (role === \"font-size\") {\n const v = Number((el as HTMLSelectElement).value);\n if (Number.isFinite(v) && v > 0) {\n ctx.editor.applyRunProperties(range, { fontSizePt: v });\n }\n return;\n }\n if (role === \"color\") {\n ctx.editor.applyRunProperties(range, {\n color: (el as HTMLInputElement).value,\n });\n return;\n }\n if (role === \"highlight\") {\n ctx.editor.applyRunProperties(range, {\n highlight: (el as HTMLInputElement).value,\n });\n return;\n }\n };\n\n root.addEventListener(\"click\", onClick);\n root.addEventListener(\"input\", onInput);\n root.addEventListener(\"change\", onInput);\n // Selection- and change-driven sync. Subscribe through the editor so\n // we share its single document-level listener (instead of every text\n // tools instance attaching its own `selectionchange` handler). Both\n // events: `selection` for moving the caret, `change` for edits that\n // alter the AST (e.g. apply bold from a keyboard shortcut).\n const sync = () => syncActive(root, ctx);\n const detachSelection = ctx.editor.on(\"selection\", sync);\n const detachChange = ctx.editor.on(\"change\", sync);\n\n // Initial sync — reflect the caret's current formatting when the\n // toolbar opens.\n sync();\n\n return () => {\n root.removeEventListener(\"click\", onClick);\n root.removeEventListener(\"input\", onInput);\n root.removeEventListener(\"change\", onInput);\n detachSelection();\n detachChange();\n };\n}\n\n/**\n * Repaint mark pressed-state and font / colour input values to reflect\n * the current selection. Call from `selection` + `change` events.\n *\n * Mark buttons read from the cascade-resolved `state.runProps` so a\n * caret in an H1 (whose style defaults `bold: true`) shows Bold as\n * pressed, even if the run itself has no explicit `bold` override.\n */\nfunction syncActive(root: HTMLElement, ctx: ToolContext): void {\n const state = readSelectionState(ctx.editor);\n const btns = root.querySelectorAll<HTMLButtonElement>('button[data-action=\"wrap\"][data-tag]');\n for (const btn of btns) {\n const tag = btn.getAttribute(\"data-tag\");\n if (!tag || tag === \"mark\") continue;\n const prop = MARK_PROP[tag];\n const expected = MARK_ON[tag];\n const on = prop !== undefined && (state.runProps as RunProperties)[prop] === expected;\n btn.setAttribute(\"aria-pressed\", String(on));\n btn.classList.toggle(\"is-active\", on);\n }\n\n // Font / colour inputs reflect the run-level state at the caret.\n // For mixed selections, dropdowns go to their placeholder (\"Font\" /\n // \"Size\") and colour inputs hold their last-set value (no native\n // way to render \"indeterminate\" on <input type=color>).\n const fontFamily = root.querySelector<HTMLSelectElement>('select[data-role=\"font-family\"]');\n if (fontFamily) {\n const v = state.runProps.fontFamily ?? \"\";\n fontFamily.value = optionMatch(fontFamily, v) ? v : \"\";\n }\n const fontSize = root.querySelector<HTMLSelectElement>('select[data-role=\"font-size\"]');\n if (fontSize) {\n const sz = state.runProps.fontSizePt;\n const v = sz === undefined ? \"\" : String(sz);\n fontSize.value = optionMatch(fontSize, v) ? v : \"\";\n }\n const color = root.querySelector<HTMLInputElement>('input[data-role=\"color\"]');\n if (color && state.runProps.color) color.value = state.runProps.color;\n const highlight = root.querySelector<HTMLInputElement>('input[data-role=\"highlight\"]');\n if (highlight && state.runProps.highlight) {\n highlight.value = state.runProps.highlight;\n }\n}\n\nfunction optionMatch(sel: HTMLSelectElement, value: string): boolean {\n for (const opt of Array.from(sel.options)) {\n if (opt.value === value) return true;\n }\n return false;\n}\n\n/**\n * Use the live DOM selection if one exists; otherwise fall back to a\n * range covering the whole target block. Matches the \"click Bold with\n * caret only → bold the whole paragraph\" UX.\n */\nfunction rangeForContext(ctx: ToolContext): ApiRange {\n const sel = ctx.editor.selection.currentRange();\n if (sel) return sel;\n return rangeForBlock(ctx.editor, ctx.target);\n}\n\nfunction rangeForBlock(editor: Editor, _target: BlockTarget): ApiRange {\n // Pragmatic: use the current selection's block ref as the anchor —\n // clicking the indicator has already placed the caret inside the\n // target block, so the caret's block ref is the right one. Fall back\n // to the first body block if selection is somehow missing.\n const caret = editor.selection.currentCaret();\n const block: BlockRef = caret?.block ?? editor.getBlocks()[0]!;\n const info = editor.getBlockById(block.id);\n const length = info?.length ?? 0;\n return {\n from: { block, offset: 0 },\n to: { block, offset: length },\n };\n}\n","import \"./blockTools.css\";\nimport type { BlockRef } from \"@sobree/core\";\nimport { enterZoneEdit } from \"@sobree/core\";\nimport type { PageSetup } from \"@sobree/core\";\nimport type { Editor } from \"@sobree/core\";\nimport type { Viewport } from \"@sobree/core\";\nimport type { BlockTarget } from \"./blockKinds\";\nimport { BlockIndicator } from \"./indicator\";\nimport { FloatingToolbar } from \"./toolbar\";\nimport {\n type ChangeTypeContext,\n buildChangeTypeButton,\n openChangeTypePopover,\n} from \"./tools/changeType\";\nimport { icon } from \"./tools/icons\";\nimport { openPageSetupPopover } from \"./tools/pageSetup\";\nimport { buildPerKindHtml, wirePerKindTools } from \"./tools/perKind\";\nimport {\n type CellLocation,\n type TableMode,\n buildTableToolsHtml,\n locateCellFromSelection,\n wireTableTools,\n} from \"./tools/table\";\nimport { buildTextToolsHtml, wireTextTools } from \"./tools/text\";\n\nexport interface BlockToolsOptions {\n stackRoot: HTMLElement;\n editor: Editor;\n /** The scrollable area containing the stack (for toolbar positioning). */\n renderingArea: HTMLElement;\n /** Viewport handle — used for animated pans when the toolbar needs room. */\n viewport?: Viewport | null;\n getSetup: () => PageSetup;\n setSetup: (next: PageSetup) => void;\n /**\n * Multi-section setup (optional — defaults to single-section behaviour).\n * When all three are present, the Page setup popover renders a section\n * picker and edits the section the caret is in.\n */\n getSectionCount?: () => number;\n getSectionSetup?: (index: number) => PageSetup;\n setSectionSetup?: (index: number, partial: Partial<PageSetup>) => void;\n}\n\n/**\n * Floating block-tools orchestrator. Owns the left-gutter indicator and\n * the floating toolbar that opens above the active block.\n *\n * B1 landed the indicator + zone-edit hand-off. B2 adds the toolbar\n * shell with positioning + open/close animation. B3+ wire actual tools\n * into the shell per block kind.\n */\nexport class BlockTools {\n private readonly stackRoot: HTMLElement;\n private readonly editor: Editor;\n private readonly getSetup: () => PageSetup;\n private readonly setSetup: (s: PageSetup) => void;\n private readonly getSectionCount: () => number;\n private readonly getSectionSetup: (index: number) => PageSetup;\n private readonly setSectionSetup: (index: number, partial: Partial<PageSetup>) => void;\n private closePageSetupPopover: (() => void) | null = null;\n private readonly indicator: BlockIndicator;\n private readonly toolbar: FloatingToolbar;\n private zoneEditing = false;\n /** Currently-open zone-edit `finish()` (returned by `enterZoneEdit`),\n * or null when not editing. Lets a second indicator click commit. */\n private exitZoneEdit: (() => void) | null = null;\n private suspended = false;\n private detachTools: (() => void) | null = null;\n private closeChangePopover: (() => void) | null = null;\n /** Current table-toolbar mode (Cell vs Table). Reset when the toolbar closes. */\n private tableMode: TableMode = \"cell\";\n private readonly onDocumentDownFn = (e: MouseEvent) => this.onDocumentDown(e);\n\n constructor(opts: BlockToolsOptions) {\n this.stackRoot = opts.stackRoot;\n this.editor = opts.editor;\n this.getSetup = opts.getSetup;\n this.setSetup = opts.setSetup;\n // Section APIs default to a single-section view backed by getSetup /\n // setSetup. Embedders that pass the multi-section trio (createSobree\n // does) get section-aware editing for free.\n this.getSectionCount = opts.getSectionCount ?? (() => 1);\n this.getSectionSetup = opts.getSectionSetup ?? ((_) => opts.getSetup());\n this.setSectionSetup =\n opts.setSectionSetup ??\n ((index, partial) => {\n if (index !== 0) return;\n opts.setSetup({ ...opts.getSetup(), ...partial });\n });\n\n this.toolbar = new FloatingToolbar({\n stackRoot: this.stackRoot,\n renderingArea: opts.renderingArea,\n viewport: opts.viewport ?? null,\n });\n\n this.indicator = new BlockIndicator({\n stackRoot: this.stackRoot,\n editor: this.editor,\n onActivate: (target) => this.handleActivate(target),\n });\n\n // Click-away closes the toolbar.\n document.addEventListener(\"mousedown\", this.onDocumentDownFn, true);\n }\n\n destroy(): void {\n document.removeEventListener(\"mousedown\", this.onDocumentDownFn, true);\n this.toolbar.destroy();\n this.indicator.destroy();\n }\n\n /** Call after pagination / zoom changes so the indicator + toolbar re-align. */\n refresh(): void {\n this.indicator.refresh();\n if (this.toolbar.isOpen()) this.toolbar.reposition();\n }\n\n /**\n * Suspend all UI — indicator hidden, toolbar closed, interactions\n * ignored. Used when the host switches Sobree into read mode.\n */\n setSuspended(suspended: boolean): void {\n if (this.suspended === suspended) return;\n this.suspended = suspended;\n if (suspended) {\n this.closeToolbar();\n this.indicator.setEnabled(false);\n } else {\n this.indicator.setEnabled(true);\n }\n }\n\n private handleActivate(target: BlockTarget): void {\n if (this.suspended) return;\n if (this.zoneEditing) {\n // Second click on the indicator while editing the same zone → commit\n // and exit. Clicking the indicator on a *different* block does\n // nothing while editing — the click-outside handler in zoneEdit will\n // commit, then BlockTools is free to act on the new target.\n this.exitZoneEdit?.();\n return;\n }\n if (target.kind === \"header\" || target.kind === \"footer\") {\n this.enterZoneEdit(target.element, target.kind);\n return;\n }\n\n // Toggle: click the same indicator again → close.\n if (this.toolbar.isOpen() && this.toolbar.getTarget()?.element === target.element) {\n this.closeToolbar();\n return;\n }\n\n this.openToolbar(target);\n }\n\n private openToolbar(target: BlockTarget): void {\n this.detachTools?.();\n\n const changeCtx = this.buildChangeTypeContext(target);\n\n if (target.kind === \"table\") {\n // Table toolbar builds its own content (text tools + pill + ops).\n // Append the change-type trigger at the end.\n this.renderTableToolbar(target, changeCtx);\n } else {\n const isMulti = changeCtx.refs.length > 1;\n // Multi-block: text tools only (no per-kind primary tools).\n const perKindHtml = isMulti ? \"\" : buildPerKindHtml(target.kind);\n this.toolbar.setContent(\n buildTextToolsHtml() +\n perKindHtml +\n buildChangeTypeButton(changeCtx.refs.length) +\n this.buildTrailingToolsHtml(),\n );\n this.toolbar.open(target);\n const detachText = wireTextTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachPerKind = isMulti\n ? () => {}\n : wirePerKindTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachChange = this.wireChangeTypeTrigger(changeCtx);\n const detachTrailing = this.wireTrailingTools();\n this.detachTools = () => {\n detachText();\n detachPerKind();\n detachChange();\n detachTrailing();\n };\n }\n this.indicator.setActive(true);\n }\n\n /**\n * Resolve which blocks the \"change type\" drill-down should apply to.\n * Multi-block range selections return the full chain of block refs\n * between `from` and `to` (inclusive). Everything else returns the\n * single block under the caret / indicator.\n */\n private buildChangeTypeContext(target: BlockTarget): ChangeTypeContext {\n const range = this.editor.selection.currentRange();\n const refs: BlockRef[] = [];\n if (range && range.from.block.id !== range.to.block.id) {\n // Collect every block from the first to the last selected.\n const blocks = this.editor.getBlocks();\n const fromIdx = blocks.findIndex((b) => b.id === range.from.block.id);\n const toIdx = blocks.findIndex((b) => b.id === range.to.block.id);\n if (fromIdx >= 0 && toIdx >= 0) {\n const lo = Math.min(fromIdx, toIdx);\n const hi = Math.max(fromIdx, toIdx);\n for (let i = lo; i <= hi; i++) {\n const b = blocks[i];\n if (b) refs.push({ id: b.id, version: b.version });\n }\n }\n }\n if (refs.length === 0) {\n const caret = this.editor.selection.currentBlock();\n if (caret) refs.push(caret);\n else {\n const first = this.editor.getBlocks()[0];\n if (first) refs.push({ id: first.id, version: first.version });\n }\n }\n return { editor: this.editor, target, refs };\n }\n\n private wireChangeTypeTrigger(ctx: ChangeTypeContext): () => void {\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\n 'button[data-action=\"open-change-type\"]',\n ) as HTMLButtonElement | null;\n if (!btn) return;\n e.preventDefault();\n // Toggle: if already open, close. Otherwise open. Mirror the\n // open/closed state via `aria-expanded` so screen readers know\n // whether activating the button will open or close the menu.\n if (this.closeChangePopover) {\n this.closeChangePopover();\n return;\n }\n btn.setAttribute(\"aria-expanded\", \"true\");\n this.closeChangePopover = openChangeTypePopover(btn, ctx, () => {\n btn.setAttribute(\"aria-expanded\", \"false\");\n this.closeChangePopover = null;\n });\n };\n this.toolbar.root.addEventListener(\"click\", onClick);\n return () => this.toolbar.root.removeEventListener(\"click\", onClick);\n }\n\n /**\n * Table-specific toolbar: text tools on the left, Cell/Table pill in\n * the middle, mode-specific ops on the right. Mode defaults to Cell\n * when the caret is in a cell, Table otherwise.\n */\n private renderTableToolbar(target: BlockTarget, changeCtx: ChangeTypeContext): void {\n const cell = this.resolveCell(target);\n this.tableMode = cell ? \"cell\" : \"table\";\n this.renderTableToolbarBody(target, cell, changeCtx);\n this.toolbar.open(target);\n }\n\n private renderTableToolbarBody(\n target: BlockTarget,\n cell: CellLocation | null,\n changeCtx: ChangeTypeContext,\n ): void {\n // Tear down any prior wiring first — otherwise each mode toggle\n // leaves another live listener on the root, and clicks start firing\n // multiple times.\n this.detachTools?.();\n this.toolbar.setContent(\n buildTextToolsHtml() +\n buildTableToolsHtml(this.tableMode, !!cell) +\n buildChangeTypeButton(changeCtx.refs.length) +\n this.buildTrailingToolsHtml(),\n );\n const detachText = wireTextTools(this.toolbar.root, {\n editor: this.editor,\n target,\n });\n const detachTable = wireTableTools(\n this.toolbar.root,\n { editor: this.editor, target, cell },\n (nextMode) => {\n this.tableMode = nextMode;\n this.renderTableToolbarBody(target, this.resolveCell(target), changeCtx);\n },\n );\n const detachChange = this.wireChangeTypeTrigger(changeCtx);\n const detachTrailing = this.wireTrailingTools();\n this.detachTools = () => {\n detachText();\n detachTable();\n detachChange();\n detachTrailing();\n };\n }\n\n /**\n * Tools that sit at the very right of every toolbar variant — global\n * affordances that aren't tied to the active block. Currently:\n * - Page setup — always present; opens an internal section-aware\n * popover. If the host has registered a `page-setup.open` command,\n * that wins (lets embedders ship their own modal).\n */\n private buildTrailingToolsHtml(): string {\n const useHostCommand = this.editor.commands.has(\"page-setup.open\");\n const action = useHostCommand\n ? `data-action=\"exec-command\" data-command=\"page-setup.open\"`\n : `data-action=\"open-page-setup\" aria-haspopup=\"dialog\" aria-expanded=\"false\"`;\n const pageSetupBtn = `<button type=\"button\" ${action} title=\"Page & section setup\" aria-label=\"Page and section setup\">${icon(\"page-setup\")}</button>`;\n // Track-changes pill — same place as page-setup so it's present in\n // every block-type toolbar (paragraph, heading, list, image, table,\n // multi-select). Toggles `editor.trackChanges.enabled` and mirrors\n // its visual state via the editor's `track-changes-change` event.\n // Author identity is *not* changed here: the toggle preserves whatever\n // author the embedder has set via `editor.setTrackChanges(...)`. If\n // none was set, authored revisions land with no author field\n // (Word's \"anonymous tracked change\").\n const tc = this.editor.getTrackChanges();\n const pressed = tc.enabled ? \"true\" : \"false\";\n const activeCls = tc.enabled ? \" is-active\" : \"\";\n const tcLabel = tc.enabled ? \"Track changes (on)\" : \"Track changes (off)\";\n const tcBtn = `<button type=\"button\" class=\"tb-action${activeCls}\" data-action=\"toggle-track-changes\" aria-pressed=\"${pressed}\" title=\"${tcLabel}\" aria-label=\"${tcLabel}\">${icon(\"track-changes\")}</button>`;\n // Author identity input — only rendered when track-changes is on,\n // so the toolbar isn't cluttered by an input the user doesn't need.\n // Writes through `editor.setTrackChanges` keeping `enabled: true`\n // so the pill stays lit. The placeholder (\"Anonymous\") matches\n // Word's behaviour for an unset author.\n const authorValue = tc.author !== undefined ? escapeHtmlAttr(tc.author) : \"\";\n const authorInput = tc.enabled\n ? `<input type=\"text\" class=\"tb-author-input\" data-role=\"track-changes-author\" value=\"${authorValue}\" placeholder=\"Anonymous\" title=\"Track changes author\" aria-label=\"Track changes author\" maxlength=\"60\" />`\n : \"\";\n return `<div class=\"tb-divider\"></div><div class=\"tb-group\" data-group=\"trailing\">${tcBtn}${authorInput}${pageSetupBtn}</div>`;\n }\n\n /**\n * Delegated click handler for trailing-toolbar buttons. Two flavours:\n * - `data-action=\"exec-command\"` dispatches through the command bus\n * (same path keyboard / MCP / agent would use).\n * - `data-action=\"open-page-setup\"` opens the built-in section-aware\n * popover, anchored to the clicked button.\n */\n private wireTrailingTools(): () => void {\n // Keep the pill's pressed state + author input in sync with editor\n // state. A toggle from anywhere (the API, another toolbar instance,\n // a keyboard plugin) flows through here. The author input is\n // dynamically inserted/removed by `buildTrailingToolsHtml` based on\n // the enabled flag — but since we don't re-run that during a live\n // toolbar session, we manage the input element imperatively here\n // so it appears/disappears on toggle without re-rendering.\n const syncTrackChangesBtn = () => {\n const state = this.editor.getTrackChanges();\n const pressed = state.enabled;\n // The pill needs to surface TWO things: the mode flag (on/off)\n // AND whether there are unresolved revisions in the doc. The\n // user can toggle the mode off and still have pending revisions\n // they need to act on — without this hint, the pill looks like\n // \"all done\" when it isn't. The numeric count is set as a data\n // attribute that CSS reads to render a small badge over the\n // pill icon; the full text is in the title for screen readers\n // and hover.\n const unresolved = this.editor.getRevisions().length;\n const modeStr = pressed ? \"on\" : \"off\";\n const label =\n unresolved > 0\n ? `Track changes (${modeStr} · ${unresolved} unresolved)`\n : `Track changes (${modeStr})`;\n for (const btn of Array.from(\n this.toolbar.root.querySelectorAll<HTMLButtonElement>(\n 'button[data-action=\"toggle-track-changes\"]',\n ),\n )) {\n btn.classList.toggle(\"is-active\", pressed);\n btn.classList.toggle(\"has-unresolved\", unresolved > 0);\n btn.setAttribute(\"aria-pressed\", String(pressed));\n if (unresolved > 0) {\n btn.dataset.unresolvedCount = String(unresolved);\n } else {\n delete btn.dataset.unresolvedCount;\n }\n btn.title = label;\n btn.setAttribute(\"aria-label\", label);\n // Show/hide the author input alongside the pill.\n const trailingGroup = btn.parentElement;\n if (!trailingGroup) continue;\n let input = trailingGroup.querySelector<HTMLInputElement>(\n 'input[data-role=\"track-changes-author\"]',\n );\n if (pressed && !input) {\n input = document.createElement(\"input\");\n input.type = \"text\";\n input.className = \"tb-author-input\";\n input.dataset.role = \"track-changes-author\";\n input.placeholder = \"Anonymous\";\n input.title = \"Track changes author\";\n input.setAttribute(\"aria-label\", \"Track changes author\");\n input.maxLength = 60;\n input.value = state.author ?? \"\";\n btn.insertAdjacentElement(\"afterend\", input);\n } else if (!pressed && input) {\n input.remove();\n } else if (input && input.value !== (state.author ?? \"\")) {\n // External state change (API call set a different author) —\n // reflect it, unless the input is currently focused (don't\n // stomp the user's in-progress typing).\n if (document.activeElement !== input) {\n input.value = state.author ?? \"\";\n }\n }\n }\n };\n const detachTrackChanges = this.editor.on(\"track-changes-change\", syncTrackChangesBtn);\n // Also re-sync on every doc `change` so the unresolved-count\n // badge tracks revisions being authored, accepted, or rejected\n // from anywhere — not just from this toolbar's pill.\n const detachChange = this.editor.on(\"change\", syncTrackChangesBtn);\n // Initial paint covers the case where the toolbar just opened and\n // we haven't received an event since.\n syncTrackChangesBtn();\n\n // Author input — write through `editor.setTrackChanges` on every\n // input event (cheap; the editor de-dupes identical states).\n // Trim and treat the empty string as \"anonymous\" (no author field).\n const onAuthorInput = (e: Event) => {\n const el = e.target as HTMLElement;\n if (el.getAttribute(\"data-role\") !== \"track-changes-author\") return;\n const raw = (el as HTMLInputElement).value.trim();\n const cur = this.editor.getTrackChanges();\n this.editor.setTrackChanges(\n raw === \"\" ? { enabled: cur.enabled } : { enabled: cur.enabled, author: raw },\n );\n };\n this.toolbar.root.addEventListener(\"input\", onAuthorInput);\n\n const onClick = (e: Event) => {\n const btn = (e.target as HTMLElement).closest(\n 'button[data-action=\"exec-command\"], button[data-action=\"open-page-setup\"], button[data-action=\"toggle-track-changes\"]',\n ) as HTMLButtonElement | null;\n if (!btn) return;\n e.preventDefault();\n const action = btn.getAttribute(\"data-action\");\n if (action === \"exec-command\") {\n const name = btn.getAttribute(\"data-command\");\n if (name) this.editor.commands.execute(name);\n return;\n }\n if (action === \"toggle-track-changes\") {\n const cur = this.editor.getTrackChanges();\n // Preserve author when flipping the enabled bit — see the\n // pill's docblock in buildTrailingToolsHtml for rationale.\n this.editor.setTrackChanges(\n cur.author === undefined\n ? { enabled: !cur.enabled }\n : { enabled: !cur.enabled, author: cur.author },\n );\n return;\n }\n // Built-in page-setup popover — toggle.\n if (this.closePageSetupPopover) {\n this.closePageSetupPopover();\n return;\n }\n btn.setAttribute(\"aria-expanded\", \"true\");\n this.closePageSetupPopover = openPageSetupPopover(\n btn,\n {\n editor: this.editor,\n getSectionCount: this.getSectionCount,\n getSectionSetup: this.getSectionSetup,\n setSectionSetup: this.setSectionSetup,\n },\n () => {\n btn.setAttribute(\"aria-expanded\", \"false\");\n this.closePageSetupPopover = null;\n },\n );\n };\n this.toolbar.root.addEventListener(\"click\", onClick);\n return () => {\n this.toolbar.root.removeEventListener(\"click\", onClick);\n this.toolbar.root.removeEventListener(\"input\", onAuthorInput);\n detachTrackChanges();\n detachChange();\n };\n }\n\n private resolveCell(target: BlockTarget): CellLocation | null {\n if (target.element.tagName.toLowerCase() !== \"table\") return null;\n return locateCellFromSelection(target.element as HTMLTableElement);\n }\n\n private closeToolbar(): void {\n this.closeChangePopover?.();\n this.closeChangePopover = null;\n this.closePageSetupPopover?.();\n this.closePageSetupPopover = null;\n this.detachTools?.();\n this.detachTools = null;\n this.toolbar.close();\n this.indicator.setActive(false);\n }\n\n private onDocumentDown(e: MouseEvent): void {\n if (!this.toolbar.isOpen()) return;\n const target = e.target as Node;\n if (this.toolbar.root.contains(target)) return;\n if (e.target instanceof HTMLElement) {\n // Don't close if the click lands on the indicator — that's a toggle.\n if (this.indicator.getCurrent() && e.target.closest(\".sobree-block-indicator\")) return;\n // Don't close if the click lands inside the open change-block popover\n // (or any toolbar-spawned popover that lives on document.body). The\n // popover removes `is-open` on close, which flips it back to\n // `pointer-events: none` — a premature close here would swallow\n // the user's click before its applyTarget handler runs.\n if (e.target.closest(\".sobree-change-popover\")) return;\n // Same for the page-setup popover (it reuses the change-popover\n // class for layout but has its own dialog role).\n if (e.target.closest(\".sobree-page-setup-popover\")) return;\n }\n this.closeToolbar();\n }\n\n private enterZoneEdit(zone: HTMLElement, kind: \"header\" | \"footer\"): void {\n this.zoneEditing = true;\n this.indicator.setActive(true);\n this.exitZoneEdit = enterZoneEdit({\n zone,\n kind,\n stackRoot: this.stackRoot,\n getSetup: this.getSetup,\n setSetup: this.setSetup,\n onExit: () => {\n this.zoneEditing = false;\n this.exitZoneEdit = null;\n this.indicator.setActive(false);\n },\n });\n }\n}\n\n/**\n * Escape a value for safe interpolation into an HTML attribute. Used\n * for user-controlled values (e.g. the track-changes author name) so\n * a malicious or accidental string can't break out of the attribute\n * and inject other attributes / script.\n */\nfunction escapeHtmlAttr(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n","/**\n * `SobreePlugin` factory wrapping the `BlockTools` class. Hand the\n * result to `createSobree({ plugins: [...] })`; setup constructs a\n * `BlockTools` against the plugin context and destroy tears it down.\n *\n * For embedders who skipped `createSobree()`, the `BlockTools` class\n * itself is exported from this package — instantiate directly with\n * full `BlockToolsOptions`.\n */\n\nimport type { SobreePlugin } from \"@sobree/core\";\nimport { BlockTools, type BlockToolsOptions } from \"./blockTools\";\n\n/**\n * User-overridable subset of `BlockToolsOptions`. The non-overridable\n * fields (`stackRoot`, `editor`, `renderingArea`, `viewport`,\n * `getSetup`, `setSetup`) come from the plugin context — the user\n * can't sensibly override them.\n */\nexport type BlockToolsPluginOptions = Partial<\n Omit<\n BlockToolsOptions,\n | \"stackRoot\"\n | \"editor\"\n | \"renderingArea\"\n | \"viewport\"\n | \"getSetup\"\n | \"setSetup\"\n | \"getSectionCount\"\n | \"getSectionSetup\"\n | \"setSectionSetup\"\n >\n>;\n\nexport function blockTools(opts: BlockToolsPluginOptions = {}): SobreePlugin {\n return {\n name: \"block-tools\",\n setup({ editor, sobree, viewport, host }) {\n const tools = new BlockTools({\n stackRoot: sobree.stackRoot,\n editor,\n renderingArea: host,\n viewport,\n getSetup: () => sobree.getPageSetup(),\n setSetup: (next) => sobree.setPageSetup(next),\n // Section-aware page setup — feeds the popover's section picker\n // and applies edits to the section the caret lives in.\n getSectionCount: () => sobree.getSectionCount(),\n getSectionSetup: (index) => sobree.getSectionSetup(index),\n setSectionSetup: (index, partial) => sobree.setSectionSetup(index, partial),\n ...opts,\n });\n return { destroy: () => tools.destroy() };\n },\n };\n}\n"],"names":["SVG_PARAGRAPH","SVG_HEADING","SVG_LIST","SVG_LIST_ORDERED","SVG_QUOTE","SVG_TABLE","SVG_IMAGE","SVG_CHEVRON_UP","SVG_CHEVRON_DOWN","SVG_SECTION_BREAK","BLOCK_KINDS","iconSvg","info","blockTargetFrom","node","stackRoot","el","paper","header","footer","image","host","withBlockId","sectionBreak","table","heading","bq","li","parent","kind","p","t","id","blockTargetFromNode","BlockIndicator","opts","__publicField","e","active","enabled","fresh","target","block","sel","anchor","_a","scale","stackRect","paperRect","blockRect","left","top","FloatingToolbar","html","handler","rendArea","margin","maxWidth","tbHeight","blockTallerThanView","blockTopAboveView","stickToTop","roomAbove","needed","deficit","BOLD","ITALIC","UNDERLINE","STRIKE","SUPERSCRIPT","SUBSCRIPT","TYPE","PAINTBRUSH","HIGHLIGHTER","ERASER","CODE","ALIGN_LEFT","ALIGN_CENTER","ALIGN_RIGHT","ALIGN_JUSTIFY","INDENT_INCREASE","INDENT_DECREASE","LIST_BULLET","LIST_NUMBERED","TRASH","CHEVRON_DOWN_ICON","SETTINGS","PAGE_SETUP","SECTION_BREAK","TRACK_CHANGES","ICONS","icon","name","buildChangeTypeButton","refCount","label","openChangeTypePopover","trigger","ctx","onClose","currentKey","currentTargetKey","popover","buildPopoverHtml","triggerRect","items","close","onDocDown","item","raw","parseTarget","applyTarget","all","idx","_b","_c","_d","multi","key","isCurrent","iconInline","first","doc","numId","def","n","fmt","styleId","m","convertToTable","ref","applyConversion","editor","flattenTableToParagraph","refreshed","convertBlockToList","parts","row","cell","inner","run","text","next","format","existing","nextNumbering","d","_omit","cleanProps","nextBlock","nextBody","nextDoc","empty","withExistingRuns","PAGE_SIZE_KEYS","MARGIN_FIELDS","VERTICAL_ALIGNS","openPageSetupPopover","sectionCount","activeIndex","clampIndex","detectCurrentSection","savedSelection","canInsertBreak","render","setup","buildHtml","wire","onDown","installTimer","onKey","closed","sectionPicker","_","i","pageGroup","k","dim","PAGE_SIZES","side","differentFirst","differentLast","sectionGroup","v","capitalize","s","root","getActive","onSectionChange","closePopover","sectionEl","sizeEl","orientationEl","input","apply","value","current","wasActive","caretStart","caretEnd","debouncedApply","debounce","vAlignEl","checked","insertBreakBtn","caret","blocks","section","b","index","count","fn","ms","timer","EMPTY_STATE","readSelectionState","ownRunProps","resolveRunProps","runDefaults","resolveStyleCascade","resolveListFormat","numbering","range","lo","hi","touched","textRunsInRange","intersectProps","r","runPropsAtOffset","runs","out","cursor","walk","list","start","len","runLen","end","offset","lastTextProps","lastTextRunProps","last","snapshots","agree","buildPerKindHtml","extras","buildAlignmentHtml","buildHeadingLevelHtml","buildListHtml","buildImageHtml","wirePerKindTools","handlers","onAlign","btn","arg","alignment","applyToTargetBlocks","result","warnOnEditFailure","onLineSpacing","mult","onHeadingLevel","level","refForTarget","onListAction","action","toggleListKind","applyAlignmentToWholeList","onImageInput","alt","updateImageAltAtTarget","onImageClick","deleteImageAtTarget","syncAll","syncPerKindState","detachSelection","detachChange","detach","state","align","alignBtns","on","lineSel","sp","matched","optionByValue","headingSel","listToggle","isOrdered","opt","_kind","explicitId","targetBlock","targetNumId","prev","endExclusive","refs","blk","liInfo","currentDef","wantBullet","buildTableToolsHtml","mode","hasCell","pill","buildCellOpsHtml","buildTableOpsHtml","wireTableTools","onModeChange","onClick","tableRef","tableRefFor","handleAction","onChange","role","disabled","ed","col","a","rowObj","cellObj","cellIndexAt","newContent","blockEl","tableBlock","c","span","locateCellFromSelection","tableEl","tr","sibling","cs","buildTextToolsHtml","wireTextTools","tag","rangeForContext","toggleMark","syncActive","onInput","sync","btns","prop","MARK_PROP","expected","MARK_ON","fontFamily","optionMatch","fontSize","sz","color","highlight","rangeForBlock","_target","length","BlockTools","partial","suspended","changeCtx","isMulti","perKindHtml","detachText","detachPerKind","detachTrailing","fromIdx","toIdx","detachTable","nextMode","pageSetupBtn","tc","pressed","activeCls","tcLabel","tcBtn","authorValue","escapeHtmlAttr","authorInput","syncTrackChangesBtn","unresolved","modeStr","trailingGroup","detachTrackChanges","onAuthorInput","cur","zone","enterZoneEdit","blockTools","sobree","viewport","tools"],"mappings":"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"}
|