@elabs-ai/components-editor 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +6 -7
  2. package/dist/{chunk-LBC5VJBD.js → chunk-FO5S3YZM.js} +349 -177
  3. package/dist/chunk-FO5S3YZM.js.map +1 -0
  4. package/dist/index.css +4 -2
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.d.ts +5 -4
  7. package/dist/index.js +112 -57
  8. package/dist/index.js.map +1 -1
  9. package/dist/markdown/index.css +4 -2
  10. package/dist/markdown/index.css.map +1 -1
  11. package/dist/markdown/index.d.ts +2 -2
  12. package/dist/markdown/index.js +268 -191
  13. package/dist/markdown/index.js.map +1 -1
  14. package/dist/{markdown-editor-DfBZibAn.d.ts → markdown-editor-Dn-0L_MM.d.ts} +9 -2
  15. package/dist/monaco.d.ts +2 -0
  16. package/dist/monaco.js +9 -0
  17. package/dist/monaco.js.map +1 -0
  18. package/package.json +10 -6
  19. package/src/ai-objects/decision-card.tsx +15 -9
  20. package/src/ai-objects/entity.tsx +1 -1
  21. package/src/ai-objects/knowledge-card.tsx +18 -9
  22. package/src/barrel-monaco-lazy.test.ts +50 -0
  23. package/src/calc-block/calc-block.tsx +10 -4
  24. package/src/calc-block/calc-editor.css +1 -1
  25. package/src/code-editor/code-editor.stories.tsx +76 -1
  26. package/src/code-editor/code-editor.test.tsx +141 -14
  27. package/src/code-editor/code-editor.tsx +167 -36
  28. package/src/code-workspace/code-workspace.test.tsx +113 -11
  29. package/src/code-workspace/code-workspace.tsx +73 -20
  30. package/src/copy-button/copy-button.tsx +6 -3
  31. package/src/diff-editor/diff-editor.stories.tsx +9 -1
  32. package/src/diff-editor/diff-editor.test.tsx +9 -3
  33. package/src/diff-editor/diff-editor.tsx +47 -23
  34. package/src/editor-toolbar/editor-toolbar.tsx +8 -2
  35. package/src/index.ts +4 -3
  36. package/src/lib/monaco-deep-imports.d.ts +59 -0
  37. package/src/lib/monaco-theme-bridge.test.ts +307 -0
  38. package/src/lib/monaco-theme-bridge.ts +154 -10
  39. package/src/markdown-academic/citations.tsx +14 -7
  40. package/src/markdown-academic/footnotes.tsx +2 -2
  41. package/src/markdown-academic/math.tsx +7 -4
  42. package/src/markdown-academic/toc.tsx +1 -1
  43. package/src/markdown-editor/completions/completions-menu.tsx +5 -2
  44. package/src/markdown-editor/directive-views.tsx +35 -24
  45. package/src/markdown-editor/markdown-editor.css +26 -3
  46. package/src/markdown-editor/markdown-editor.focus.test.ts +49 -0
  47. package/src/markdown-editor/markdown-editor.stories.tsx +128 -7
  48. package/src/markdown-editor/markdown-editor.tsx +8 -5
  49. package/src/markdown-editor/milkdown-react/use-get-editor.timer-leak.test.ts +80 -0
  50. package/src/markdown-editor/milkdown-react/use-get-editor.ts +37 -1
  51. package/src/markdown-editor/paste-embed.ts +2 -1
  52. package/src/markdown-editor/slash/slash-menu.stories.tsx +8 -3
  53. package/src/markdown-editor/slash/slash-menu.test.tsx +27 -0
  54. package/src/markdown-editor/slash/slash-menu.tsx +18 -3
  55. package/src/markdown-editor/table-view.tsx +31 -17
  56. package/src/markdown-iteration/iteration-builder-dialog.stories.tsx +57 -19
  57. package/src/markdown-iteration/iteration-builder-dialog.tsx +39 -18
  58. package/src/markdown-iteration/template-dialog.tsx +25 -7
  59. package/src/markdown-outline/document-outline.stories.tsx +1 -1
  60. package/src/markdown-outline/document-outline.tsx +7 -3
  61. package/src/markdown-preview/markdown-preview-academic.test.tsx +3 -3
  62. package/src/markdown-preview/markdown-preview-transclusion.test.tsx +1 -1
  63. package/src/markdown-preview/markdown-preview.stories.tsx +24 -2
  64. package/src/markdown-preview/markdown-preview.test.tsx +20 -3
  65. package/src/markdown-toolbar/markdown-toolbar.stories.tsx +3 -0
  66. package/src/markdown-toolbar/markdown-toolbar.tsx +42 -24
  67. package/src/markdown-workspace/markdown-workspace.test.tsx +53 -4
  68. package/src/markdown-workspace/markdown-workspace.tsx +6 -3
  69. package/src/mermaid-diagram/mermaid-diagram-fixes.test.tsx +130 -0
  70. package/src/mermaid-diagram/mermaid-diagram.test.tsx +2 -1
  71. package/src/mermaid-diagram/mermaid-diagram.tsx +89 -40
  72. package/src/mermaid-diagram/mermaid-viewer.tsx +22 -21
  73. package/src/monaco.ts +21 -0
  74. package/src/prose/prose.stories.tsx +3 -0
  75. package/src/prose/prose.test.ts +54 -0
  76. package/src/prose/prose.tsx +7 -0
  77. package/dist/chunk-LBC5VJBD.js.map +0 -1
package/dist/index.css CHANGED
@@ -11,8 +11,10 @@
11
11
  max-width: var(--md-measure, 48rem);
12
12
  margin-inline: auto;
13
13
  }
14
- .milkdown-host .ProseMirror:focus {
15
- outline: none;
14
+ .milkdown-host .ProseMirror:focus-visible {
15
+ outline: 1px solid var(--ring-contour);
16
+ outline-offset: -3px;
17
+ box-shadow: inset 0 0 0 2px var(--ring);
16
18
  }
17
19
  .milkdown-host {
18
20
  display: flex;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/markdown-editor/markdown-editor.css","../src/calc-block/calc-editor.css"],"sourcesContent":["/**\n * Token-driven theme for the Milkdown (ProseMirror) WYSIWYG surface.\n *\n * Milkdown core ships ZERO styles (headless), so this is where brand-ui owns the\n * look. Every value is a semantic design token (var(--foreground), --border,\n * --radius, …) — NO raw colors — so the editor matches all three themes\n * (light/dark) for free. Every rule\n * is scoped under `.milkdown-host` so nothing leaks globally.\n */\n\n.milkdown-host .ProseMirror {\n outline: none;\n padding: 1rem 1.1rem;\n min-height: 9rem;\n color: var(--foreground);\n font-size: 0.875rem;\n line-height: 1.65;\n caret-color: var(--primary);\n word-break: break-word;\n /* Same reading measure as the preview (max-w-3xl). Driven by the shared scale\n (--md-measure, set via markdownScaleVars) so editor + preview match (#18). */\n max-width: var(--md-measure, 48rem);\n margin-inline: auto;\n}\n\n.milkdown-host .ProseMirror:focus {\n outline: none;\n}\n\n/* ----------------------------------------------------------------------------\n * Fill height — the editable grows to fill a definite-height parent.\n *\n * Milkdown's host box is otherwise content-sized (the `.ProseMirror` floor below\n * is `min-height: 9rem`), so inside a tall pane a short document leaves a large\n * dead area below the editable AND the caret-anchored `/` slash menu clips inside\n * the short box near the bottom edge. Making `.milkdown-host` a column flex that\n * fills its parent — and letting the intermediate Milkdown wrapper + the\n * ProseMirror surface grow inside it — means the editable fills the pane\n * (clickable everywhere) and the slash menu has room to open / flip.\n *\n * `min-height: 100%` only resolves against a parent with a DEFINITE height (the\n * workspace's `h-full` wysiwyg pane, or any sized container). In an\n * indefinite-height parent it resolves to `auto`, so the standalone / short\n * editor still sizes to its content above the 9rem floor — no regression for the\n * autodocs / inline cases. (elabs-components: editor-fill-height)\n * ------------------------------------------------------------------------- */\n.milkdown-host {\n display: flex;\n flex-direction: column;\n min-height: 100%;\n}\n.milkdown-host > div,\n.milkdown-host .milkdown {\n display: flex;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n}\n.milkdown-host .ProseMirror.editor {\n flex: 1 1 auto;\n}\n\n.milkdown-host .ProseMirror > * + * {\n margin-top: 0.7em;\n}\n\n/* Headings — sizes/weight/tracking come from the SHARED markdown scale\n (markdown-scale.ts → --md-* vars on .milkdown-host), so the editor matches the\n prose `Heading` exactly and can't drift on the next edit to either side (#18).\n Fallbacks equal the canonical scale for use without the vars (e.g. clipboard). */\n.milkdown-host .ProseMirror h1,\n.milkdown-host .ProseMirror h2,\n.milkdown-host .ProseMirror h3,\n.milkdown-host .ProseMirror h4,\n.milkdown-host .ProseMirror h5,\n.milkdown-host .ProseMirror h6 {\n font-weight: var(--md-heading-weight, 600);\n letter-spacing: var(--md-heading-tracking, -0.025em);\n line-height: 1.25;\n color: var(--foreground);\n margin-top: 1.1em;\n}\n.milkdown-host .ProseMirror h1 {\n font-size: var(--md-h1, 1.5rem);\n}\n.milkdown-host .ProseMirror h2 {\n font-size: var(--md-h2, 1.25rem);\n}\n.milkdown-host .ProseMirror h3 {\n font-size: var(--md-h3, 1.125rem);\n}\n.milkdown-host .ProseMirror h4 {\n font-size: var(--md-h4, 1rem);\n}\n.milkdown-host .ProseMirror h5 {\n font-size: var(--md-h5, 0.875rem);\n}\n.milkdown-host .ProseMirror h6 {\n font-size: var(--md-h6, 0.875rem);\n color: var(--muted-foreground);\n}\n\n/* Inline */\n.milkdown-host .ProseMirror a {\n color: var(--primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.milkdown-host .ProseMirror strong {\n font-weight: 650;\n}\n.milkdown-host .ProseMirror code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n background: var(--muted);\n color: var(--foreground);\n padding: 0.12em 0.36em;\n border-radius: calc(var(--radius) - 4px);\n}\n\n/* Blocks */\n.milkdown-host .ProseMirror blockquote {\n border-left: 2px solid var(--border);\n padding-left: 1rem;\n color: var(--muted-foreground);\n}\n.milkdown-host .ProseMirror pre {\n background: var(--surface-muted);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 0.85rem 1rem;\n overflow: auto;\n}\n.milkdown-host .ProseMirror pre code {\n background: transparent;\n padding: 0;\n font-size: 0.82rem;\n}\n.milkdown-host .ProseMirror hr {\n border: none;\n border-top: 1px solid var(--border);\n margin: 1.25em 0;\n}\n\n/* Lists */\n.milkdown-host .ProseMirror ul,\n.milkdown-host .ProseMirror ol {\n padding-left: 1.4rem;\n}\n.milkdown-host .ProseMirror ul {\n list-style: disc;\n}\n.milkdown-host .ProseMirror ol {\n list-style: decimal;\n}\n.milkdown-host .ProseMirror li > * + * {\n margin-top: 0.35em;\n}\n\n/* Tables (GFM) */\n.milkdown-host .ProseMirror table {\n border-collapse: collapse;\n width: 100%;\n font-size: 0.85rem;\n}\n.milkdown-host .ProseMirror th,\n.milkdown-host .ProseMirror td {\n border: 1px solid var(--border);\n padding: 0.4rem 0.6rem;\n text-align: left;\n}\n.milkdown-host .ProseMirror th {\n background: var(--surface-muted);\n font-weight: 600;\n}\n\n/* Task lists (GFM) */\n.milkdown-host .ProseMirror ul[data-item-type=\"task\"] {\n list-style: none;\n padding-left: 0.25rem;\n}\n\n/* Read-only mode dims the caret affordance */\n.milkdown-host .ProseMirror[contenteditable=\"false\"] {\n caret-color: transparent;\n}\n\n/* ---- Brand `:::` directives ----------------------------------------------------\n * These now render as LIVE @brand React components via @prosemirror-adapter/react\n * (see directive-views.tsx), so the box-model chrome (card/callout/metric borders,\n * fills, type accents) comes from the components themselves — NOT from here. This\n * section only owns what the editor adds on top: outer spacing, the editable-body\n * rhythm, the timeline frame (an editable list, not a derived <Timeline>), and the\n * inline-edit affordance for editable attributes (card/callout title, metric\n * label/value). The `toDOM` defs in directive-nodes.ts keep their own marker classes\n * for clipboard serialization but are not visually rendered while node-views are on.\n */\n.milkdown-host .brand-directive {\n margin: 0.85em 0;\n}\n.milkdown-host .brand-directive__body > * + * {\n margin-top: 0.6em;\n}\n/* The first/last editable block inside a card/callout shouldn't add stray margin\n * on top of the component's own padding. */\n.milkdown-host .brand-directive__body > :first-child {\n margin-top: 0;\n}\n\n/* timeline → keep the list editable inside a subtle frame */\n.milkdown-host .brand-directive--timeline {\n border-left: 2px solid var(--border);\n padding-left: 1rem;\n}\n\n/* Inline-editable directive attribute (seamless, caret-friendly). */\n.milkdown-host .brand-inline-edit {\n cursor: text;\n min-width: 1ch;\n /* inherit the surrounding component's type (CardTitle, MetricBlock label/value) */\n font: inherit;\n color: inherit;\n letter-spacing: inherit;\n}\n.milkdown-host .brand-inline-edit:empty::before {\n content: attr(data-placeholder);\n color: var(--muted-foreground);\n opacity: 0.65;\n pointer-events: none;\n}\n\n/* ------------------------------------------------------------------------- */\n/* Focus writing (Ulysses Phase A): under a `[data-focus-writing]` ancestor */\n/* (the workspace's wysiwyg pane), every top-level block dims except the one */\n/* owning the selection (`.wb-fw-active`, set by the workspace) — paragraph */\n/* focus. Hover un-dims for mouse repositioning. Token-driven, motion-safe. */\n/* ------------------------------------------------------------------------- */\n[data-focus-writing] .milkdown-host .ProseMirror > * {\n opacity: 0.35;\n transition: opacity var(--t-base, 200ms) var(--ease-standard, ease);\n}\n[data-focus-writing] .milkdown-host .ProseMirror > .wb-fw-active,\n[data-focus-writing] .milkdown-host .ProseMirror > *:hover {\n opacity: 1;\n}\n@media (prefers-reduced-motion: reduce) {\n [data-focus-writing] .milkdown-host .ProseMirror > * {\n transition: none;\n }\n}\n","/**\n * Calc authoring decorations (#220) — shared by the Monaco source surface\n * (decoration `inlineClassName`) and the Milkdown WYSIWYG surface (ProseMirror\n * inline decorations + result-inlay widgets).\n *\n * Colors come from the `--calc-*` syntax tokens — the SAME tokens `CalcBlock`\n * paints with (#221), already AA-verified against `--background` (the editor\n * surface) and `--card` in all three themes — so highlighting tracks the theme via\n * the cascade with no hardcoded hex. The compound `.brand-calc-tok.brand-calc-tok--x`\n * selector raises specificity above Monaco's own token color class (`.mtkN`) so\n * the calc color wins regardless of stylesheet load order (no `!important`).\n *\n * Hue is never the SOLE cue (var-def = weight, unresolved = dotted underline), so\n * roles stay distinct in the high-contrast theme where the calc colors collapse\n * toward the foreground — mirroring `CalcBlock.tokenClass`.\n */\n\n.brand-calc-tok.brand-calc-tok--number,\n.brand-calc-tok.brand-calc-tok--constant {\n color: var(--calc-number);\n}\n.brand-calc-tok.brand-calc-tok--unit {\n color: var(--calc-unit);\n}\n.brand-calc-tok.brand-calc-tok--currency {\n color: var(--calc-currency);\n}\n.brand-calc-tok.brand-calc-tok--operator {\n color: var(--calc-operator);\n}\n.brand-calc-tok.brand-calc-tok--function {\n color: var(--calc-function);\n}\n.brand-calc-tok.brand-calc-tok--var-ref {\n color: var(--calc-variable);\n}\n.brand-calc-tok.brand-calc-tok--var-def {\n color: var(--calc-variable);\n font-weight: 500;\n}\n.brand-calc-tok.brand-calc-tok--line-ref {\n color: var(--calc-reference);\n font-variant-numeric: tabular-nums;\n}\n.brand-calc-tok.brand-calc-tok--comment {\n color: var(--calc-comment);\n font-style: italic;\n}\n.brand-calc-tok.brand-calc-tok--unknown {\n color: var(--calc-warning);\n}\n\n/* Non-hue cue for unresolved identifiers / dangling refs — carries the role in\n high-contrast, where hue is flattened. */\n.brand-calc-tok.brand-calc-tok--unresolved {\n text-decoration: underline dotted;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n}\n\n/**\n * WYSIWYG result inlay — a ProseMirror widget after each calc line showing the\n * computed answer. The number IS the signal (shown as text), color is only an\n * enhancement, and it is non-editable / non-selectable so it never enters the\n * document. (The Monaco surface uses Monaco's own inlay hints, themed via the\n * theme bridge's `editorInlayHint.*` colors — not this class.)\n */\n.brand-calc-inlay {\n /* Full strength (no opacity de-emphasis): the result is the answer, and the\n `--calc-result` token is AA-verified at FULL opacity (#221) — reducing it\n would drop effective contrast below what the contrast gate models. */\n margin-inline-start: 1ch;\n color: var(--calc-result);\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: pre;\n}\n.brand-calc-inlay--error {\n color: var(--calc-warning);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/markdown-editor/markdown-editor.css","../src/calc-block/calc-editor.css"],"sourcesContent":["/**\n * Token-driven theme for the Milkdown (ProseMirror) WYSIWYG surface.\n *\n * Milkdown core ships ZERO styles (headless), so this is where brand-ui owns the\n * look. Every value is a semantic design token (var(--foreground), --border,\n * --radius, …) — NO raw colors — so the editor matches every theme\n * (light/dark) for free. Every rule\n * is scoped under `.milkdown-host` so nothing leaks globally.\n */\n\n.milkdown-host .ProseMirror {\n /* Suppresses the platform default outline — NOT a bare removal: the\n `:focus-visible` rule below is the replacement, drawn on this same\n element (#309). It owns the indicator unconditionally, so it can't be\n clipped by an ancestor and can't be deleted by a consumer `className`\n reaching `cn()` on the wrapper (`markdown-editor.tsx`'s root `div`\n intentionally carries no focus-ring utility of its own — see #67/#309). */\n outline: none;\n padding: 1rem 1.1rem;\n min-height: 9rem;\n color: var(--foreground);\n font-size: 0.875rem;\n line-height: 1.65;\n caret-color: var(--primary);\n word-break: break-word;\n /* Same reading measure as the preview (max-w-3xl). Driven by the shared scale\n (--md-measure, set via markdownScaleVars) so editor + preview match (#18). */\n max-width: var(--md-measure, 48rem);\n margin-inline: auto;\n}\n\n/* Focus indicator (#309) — a compound two-layer shape mirroring the\n `focus-ring-inset` Tailwind utility (`packages/tokens/src/themes.css`,\n ADR 0027), hand-written because this stylesheet is a plain side-effect CSS\n import resolved by the CONSUMER's bundler — not guaranteed to run through\n Tailwind, so `@apply`/utility classes aren't reachable here (verified:\n `@apply` appears zero times in any .css file in this repo). Drawn INSIDE\n the editable's own box (`outline-offset: -3px`, an inset `box-shadow`), so\n it can't be clipped by an ancestor and survives a consumer `className`\n deleting the wrapper's classes (`MarkdownWorkspace` already does this via\n `className=\"border-0\"`). Two layers of opposite value so at least one edge\n clears 3:1 against the ground whatever `--ring` resolves to in a given\n theme (`--ring-contour` is the dedicated contour token — `themes-contrast.\n test.ts`'s `INDICATOR_SURFACES` locks `max(ring, contour) >= 3:1` at the\n token level; `markdown-editor.stories.tsx`'s `FocusIndicator` play\n re-measures it on this rendered surface, both themes). */\n.milkdown-host .ProseMirror:focus-visible {\n outline: 1px solid var(--ring-contour);\n outline-offset: -3px;\n box-shadow: inset 0 0 0 2px var(--ring);\n}\n\n/* ----------------------------------------------------------------------------\n * Fill height — the editable grows to fill a definite-height parent.\n *\n * Milkdown's host box is otherwise content-sized (the `.ProseMirror` floor below\n * is `min-height: 9rem`), so inside a tall pane a short document leaves a large\n * dead area below the editable AND the caret-anchored `/` slash menu clips inside\n * the short box near the bottom edge. Making `.milkdown-host` a column flex that\n * fills its parent — and letting the intermediate Milkdown wrapper + the\n * ProseMirror surface grow inside it — means the editable fills the pane\n * (clickable everywhere) and the slash menu has room to open / flip.\n *\n * `min-height: 100%` only resolves against a parent with a DEFINITE height (the\n * workspace's `h-full` wysiwyg pane, or any sized container). In an\n * indefinite-height parent it resolves to `auto`, so the standalone / short\n * editor still sizes to its content above the 9rem floor — no regression for the\n * autodocs / inline cases. (elabs-components: editor-fill-height)\n * ------------------------------------------------------------------------- */\n.milkdown-host {\n display: flex;\n flex-direction: column;\n min-height: 100%;\n}\n.milkdown-host > div,\n.milkdown-host .milkdown {\n display: flex;\n min-height: 0;\n flex: 1 1 auto;\n flex-direction: column;\n}\n.milkdown-host .ProseMirror.editor {\n flex: 1 1 auto;\n}\n\n.milkdown-host .ProseMirror > * + * {\n margin-top: 0.7em;\n}\n\n/* Headings — sizes/weight/tracking come from the SHARED markdown scale\n (markdown-scale.ts → --md-* vars on .milkdown-host), so the editor matches the\n prose `Heading` exactly and can't drift on the next edit to either side (#18).\n Fallbacks equal the canonical scale for use without the vars (e.g. clipboard). */\n.milkdown-host .ProseMirror h1,\n.milkdown-host .ProseMirror h2,\n.milkdown-host .ProseMirror h3,\n.milkdown-host .ProseMirror h4,\n.milkdown-host .ProseMirror h5,\n.milkdown-host .ProseMirror h6 {\n font-weight: var(--md-heading-weight, 600);\n letter-spacing: var(--md-heading-tracking, -0.025em);\n line-height: 1.25;\n color: var(--foreground);\n margin-top: 1.1em;\n}\n.milkdown-host .ProseMirror h1 {\n font-size: var(--md-h1, 1.5rem);\n}\n.milkdown-host .ProseMirror h2 {\n font-size: var(--md-h2, 1.25rem);\n}\n.milkdown-host .ProseMirror h3 {\n font-size: var(--md-h3, 1.125rem);\n}\n.milkdown-host .ProseMirror h4 {\n font-size: var(--md-h4, 1rem);\n}\n.milkdown-host .ProseMirror h5 {\n font-size: var(--md-h5, 0.875rem);\n}\n.milkdown-host .ProseMirror h6 {\n font-size: var(--md-h6, 0.875rem);\n color: var(--muted-foreground);\n}\n\n/* Inline */\n.milkdown-host .ProseMirror a {\n color: var(--primary);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n.milkdown-host .ProseMirror strong {\n font-weight: 650;\n}\n.milkdown-host .ProseMirror code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n background: var(--muted);\n color: var(--foreground);\n padding: 0.12em 0.36em;\n border-radius: calc(var(--radius) - 4px);\n}\n\n/* Blocks */\n.milkdown-host .ProseMirror blockquote {\n border-left: 2px solid var(--border);\n padding-left: 1rem;\n color: var(--muted-foreground);\n}\n.milkdown-host .ProseMirror pre {\n background: var(--surface-muted);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 0.85rem 1rem;\n overflow: auto;\n}\n.milkdown-host .ProseMirror pre code {\n background: transparent;\n padding: 0;\n font-size: 0.82rem;\n}\n.milkdown-host .ProseMirror hr {\n border: none;\n border-top: 1px solid var(--border);\n margin: 1.25em 0;\n}\n\n/* Lists */\n.milkdown-host .ProseMirror ul,\n.milkdown-host .ProseMirror ol {\n padding-left: 1.4rem;\n}\n.milkdown-host .ProseMirror ul {\n list-style: disc;\n}\n.milkdown-host .ProseMirror ol {\n list-style: decimal;\n}\n.milkdown-host .ProseMirror li > * + * {\n margin-top: 0.35em;\n}\n\n/* Tables (GFM) */\n.milkdown-host .ProseMirror table {\n border-collapse: collapse;\n width: 100%;\n font-size: 0.85rem;\n}\n.milkdown-host .ProseMirror th,\n.milkdown-host .ProseMirror td {\n border: 1px solid var(--border);\n padding: 0.4rem 0.6rem;\n text-align: left;\n}\n.milkdown-host .ProseMirror th {\n background: var(--surface-muted);\n font-weight: 600;\n}\n\n/* Task lists (GFM) */\n.milkdown-host .ProseMirror ul[data-item-type=\"task\"] {\n list-style: none;\n padding-left: 0.25rem;\n}\n\n/* Read-only mode dims the caret affordance */\n.milkdown-host .ProseMirror[contenteditable=\"false\"] {\n caret-color: transparent;\n}\n\n/* ---- Brand `:::` directives ----------------------------------------------------\n * These now render as LIVE @brand React components via @prosemirror-adapter/react\n * (see directive-views.tsx), so the box-model chrome (card/callout/metric borders,\n * fills, type accents) comes from the components themselves — NOT from here. This\n * section only owns what the editor adds on top: outer spacing, the editable-body\n * rhythm, the timeline frame (an editable list, not a derived <Timeline>), and the\n * inline-edit affordance for editable attributes (card/callout title, metric\n * label/value). The `toDOM` defs in directive-nodes.ts keep their own marker classes\n * for clipboard serialization but are not visually rendered while node-views are on.\n */\n.milkdown-host .brand-directive {\n margin: 0.85em 0;\n}\n.milkdown-host .brand-directive__body > * + * {\n margin-top: 0.6em;\n}\n/* The first/last editable block inside a card/callout shouldn't add stray margin\n * on top of the component's own padding. */\n.milkdown-host .brand-directive__body > :first-child {\n margin-top: 0;\n}\n\n/* timeline → keep the list editable inside a subtle frame */\n.milkdown-host .brand-directive--timeline {\n border-left: 2px solid var(--border);\n padding-left: 1rem;\n}\n\n/* Inline-editable directive attribute (seamless, caret-friendly). */\n.milkdown-host .brand-inline-edit {\n cursor: text;\n min-width: 1ch;\n /* inherit the surrounding component's type (CardTitle, MetricBlock label/value) */\n font: inherit;\n color: inherit;\n letter-spacing: inherit;\n}\n.milkdown-host .brand-inline-edit:empty::before {\n content: attr(data-placeholder);\n color: var(--muted-foreground);\n opacity: 0.65;\n pointer-events: none;\n}\n\n/* ------------------------------------------------------------------------- */\n/* Focus writing (Ulysses Phase A): under a `[data-focus-writing]` ancestor */\n/* (the workspace's wysiwyg pane), every top-level block dims except the one */\n/* owning the selection (`.wb-fw-active`, set by the workspace) — paragraph */\n/* focus. Hover un-dims for mouse repositioning. Token-driven, motion-safe. */\n/* ------------------------------------------------------------------------- */\n[data-focus-writing] .milkdown-host .ProseMirror > * {\n opacity: 0.35;\n transition: opacity var(--t-base, 200ms) var(--ease-standard, ease);\n}\n[data-focus-writing] .milkdown-host .ProseMirror > .wb-fw-active,\n[data-focus-writing] .milkdown-host .ProseMirror > *:hover {\n opacity: 1;\n}\n@media (prefers-reduced-motion: reduce) {\n [data-focus-writing] .milkdown-host .ProseMirror > * {\n transition: none;\n }\n}\n","/**\n * Calc authoring decorations (#220) — shared by the Monaco source surface\n * (decoration `inlineClassName`) and the Milkdown WYSIWYG surface (ProseMirror\n * inline decorations + result-inlay widgets).\n *\n * Colors come from the `--calc-*` syntax tokens — the SAME tokens `CalcBlock`\n * paints with (#221), already AA-verified against `--background` (the editor\n * surface) and `--card` in every theme — so highlighting tracks the theme via\n * the cascade with no hardcoded hex. The compound `.brand-calc-tok.brand-calc-tok--x`\n * selector raises specificity above Monaco's own token color class (`.mtkN`) so\n * the calc color wins regardless of stylesheet load order (no `!important`).\n *\n * Hue is never the SOLE cue (var-def = weight, unresolved = dotted underline), so\n * roles stay distinct in the high-contrast theme where the calc colors collapse\n * toward the foreground — mirroring `CalcBlock.tokenClass`.\n */\n\n.brand-calc-tok.brand-calc-tok--number,\n.brand-calc-tok.brand-calc-tok--constant {\n color: var(--calc-number);\n}\n.brand-calc-tok.brand-calc-tok--unit {\n color: var(--calc-unit);\n}\n.brand-calc-tok.brand-calc-tok--currency {\n color: var(--calc-currency);\n}\n.brand-calc-tok.brand-calc-tok--operator {\n color: var(--calc-operator);\n}\n.brand-calc-tok.brand-calc-tok--function {\n color: var(--calc-function);\n}\n.brand-calc-tok.brand-calc-tok--var-ref {\n color: var(--calc-variable);\n}\n.brand-calc-tok.brand-calc-tok--var-def {\n color: var(--calc-variable);\n font-weight: 500;\n}\n.brand-calc-tok.brand-calc-tok--line-ref {\n color: var(--calc-reference);\n font-variant-numeric: tabular-nums;\n}\n.brand-calc-tok.brand-calc-tok--comment {\n color: var(--calc-comment);\n font-style: italic;\n}\n.brand-calc-tok.brand-calc-tok--unknown {\n color: var(--calc-warning);\n}\n\n/* Non-hue cue for unresolved identifiers / dangling refs — carries the role in\n high-contrast, where hue is flattened. */\n.brand-calc-tok.brand-calc-tok--unresolved {\n text-decoration: underline dotted;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n}\n\n/**\n * WYSIWYG result inlay — a ProseMirror widget after each calc line showing the\n * computed answer. The number IS the signal (shown as text), color is only an\n * enhancement, and it is non-editable / non-selectable so it never enters the\n * document. (The Monaco surface uses Monaco's own inlay hints, themed via the\n * theme bridge's `editorInlayHint.*` colors — not this class.)\n */\n.brand-calc-inlay {\n /* Full strength (no opacity de-emphasis): the result is the answer, and the\n `--calc-result` token is AA-verified at FULL opacity (#221) — reducing it\n would drop effective contrast below what the contrast gate models. */\n margin-inline-start: 1ch;\n color: var(--calc-result);\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: pre;\n}\n.brand-calc-inlay--error {\n color: var(--calc-warning);\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { E as EditorContentAccess, M as MonacoCodeEditor } from './markdown-editor-DfBZibAn.js';
2
- export { C as CodeEditor, a as CodeEditorProps, b as EditorAction, c as EditorSelection, d as EmbedAssetFn, e as MarkdownEditor, f as MarkdownEditorHandle, g as MarkdownEditorProps, m as monacoContentAccess } from './markdown-editor-DfBZibAn.js';
1
+ import { E as EditorContentAccess, M as MonacoCodeEditor } from './markdown-editor-Dn-0L_MM.js';
2
+ export { C as CodeEditor, a as CodeEditorProps, b as EditorAction, c as EditorSelection, d as EmbedAssetFn, e as MarkdownEditor, f as MarkdownEditorHandle, g as MarkdownEditorProps, m as monacoContentAccess } from './markdown-editor-Dn-0L_MM.js';
3
3
  import * as react from 'react';
4
4
  import { HTMLAttributes, ReactNode, ComponentProps } from 'react';
5
5
  import * as monaco from 'monaco-editor';
6
- export { monaco };
7
6
  import { Button } from '@elabs-ai/components-ui';
8
7
  import { ThemeName } from '@elabs-ai/components-tokens';
9
8
  import '@milkdown/kit/ctx';
@@ -11,6 +10,8 @@ import '@milkdown/kit/prose/model';
11
10
  import '@milkdown/kit/prose/state';
12
11
 
13
12
  type MonacoDiffEditor = monaco.editor.IStandaloneDiffEditor;
13
+ /** Same pattern as `code-editor.tsx`'s `MonacoNamespace`. */
14
+ type MonacoNamespace = typeof monaco;
14
15
  interface DiffEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultValue"> {
15
16
  /** Left/original document. */
16
17
  original: string;
@@ -27,7 +28,7 @@ interface DiffEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, "defaultV
27
28
  /** Passthrough Monaco diff options (merged over the defaults). */
28
29
  options?: monaco.editor.IStandaloneDiffEditorConstructionOptions;
29
30
  /** Called once the diff editor + monaco namespace are ready. */
30
- onMount?: (editor: MonacoDiffEditor, monacoApi: typeof monaco) => void;
31
+ onMount?: (editor: MonacoDiffEditor, monacoApi: MonacoNamespace) => void;
31
32
  }
32
33
  /**
33
34
  * A token-themed Monaco diff editor (original ↔ modified). Same theming bridge
package/dist/index.js CHANGED
@@ -10,10 +10,9 @@ import {
10
10
  buildBrandThemeData,
11
11
  monacoContentAccess,
12
12
  useDataTheme
13
- } from "./chunk-LBC5VJBD.js";
13
+ } from "./chunk-FO5S3YZM.js";
14
14
 
15
15
  // src/diff-editor/diff-editor.tsx
16
- import * as monaco from "monaco-editor";
17
16
  import { cn } from "@elabs-ai/components-ui/lib/cn";
18
17
  import {
19
18
  forwardRef,
@@ -46,59 +45,72 @@ var DiffEditor = forwardRef(function DiffEditor2({
46
45
  ...props
47
46
  }, ref) {
48
47
  const containerRef = useRef(null);
49
- const [editor2, setEditor] = useState(null);
48
+ const [editor, setEditor] = useState(null);
50
49
  const { theme, revision } = useDataTheme();
50
+ const monacoRef = useRef(null);
51
51
  const onMountRef = useRef(onMount);
52
52
  onMountRef.current = onMount;
53
- useImperativeHandle(ref, () => editor2, [
54
- editor2
53
+ useImperativeHandle(ref, () => editor, [
54
+ editor
55
55
  ]);
56
56
  useEffect(() => {
57
57
  const container = containerRef.current;
58
58
  if (!container) return;
59
- const instance = monaco.editor.createDiffEditor(container, {
60
- ...BASE_OPTIONS,
61
- readOnly,
62
- renderSideBySide,
63
- ...options
59
+ let cancelled = false;
60
+ let instance = null;
61
+ let originalModel = null;
62
+ let modifiedModel = null;
63
+ import("monaco-editor").then((monacoApi) => {
64
+ if (cancelled) return;
65
+ monacoRef.current = monacoApi;
66
+ instance = monacoApi.editor.createDiffEditor(container, {
67
+ ...BASE_OPTIONS,
68
+ readOnly,
69
+ renderSideBySide,
70
+ ...options
71
+ });
72
+ originalModel = monacoApi.editor.createModel(original, language);
73
+ modifiedModel = monacoApi.editor.createModel(modified, language);
74
+ instance.setModel({ original: originalModel, modified: modifiedModel });
75
+ setEditor(instance);
76
+ onMountRef.current?.(instance, monacoApi);
64
77
  });
65
- const originalModel = monaco.editor.createModel(original, language);
66
- const modifiedModel = monaco.editor.createModel(modified, language);
67
- instance.setModel({ original: originalModel, modified: modifiedModel });
68
- setEditor(instance);
69
- onMountRef.current?.(instance, monaco);
70
78
  return () => {
71
- instance.dispose();
72
- originalModel.dispose();
73
- modifiedModel.dispose();
79
+ cancelled = true;
80
+ instance?.dispose();
81
+ originalModel?.dispose();
82
+ modifiedModel?.dispose();
83
+ monacoRef.current = null;
74
84
  setEditor(null);
75
85
  };
76
86
  }, []);
77
87
  useEffect(() => {
78
- const original_ = editor2?.getModel()?.original;
88
+ const original_ = editor?.getModel()?.original;
79
89
  if (original_ && original_.getValue() !== original) original_.setValue(original);
80
- }, [editor2, original]);
90
+ }, [editor, original]);
81
91
  useEffect(() => {
82
- const modified_ = editor2?.getModel()?.modified;
92
+ const modified_ = editor?.getModel()?.modified;
83
93
  if (modified_ && modified_.getValue() !== modified) modified_.setValue(modified);
84
- }, [editor2, modified]);
94
+ }, [editor, modified]);
85
95
  useEffect(() => {
86
- const models = editor2?.getModel();
87
- if (!models) return;
88
- monaco.editor.setModelLanguage(models.original, language);
89
- monaco.editor.setModelLanguage(models.modified, language);
90
- }, [editor2, language]);
96
+ const monacoApi = monacoRef.current;
97
+ const models = editor?.getModel();
98
+ if (!models || !monacoApi) return;
99
+ monacoApi.editor.setModelLanguage(models.original, language);
100
+ monacoApi.editor.setModelLanguage(models.modified, language);
101
+ }, [editor, language]);
91
102
  useEffect(() => {
92
- editor2?.updateOptions({ readOnly, renderSideBySide });
93
- }, [editor2, readOnly, renderSideBySide]);
103
+ editor?.updateOptions({ readOnly, renderSideBySide });
104
+ }, [editor, readOnly, renderSideBySide]);
94
105
  useEffect(() => {
95
- if (!editor2) return;
106
+ const monacoApi = monacoRef.current;
107
+ if (!editor || !monacoApi) return;
96
108
  try {
97
- applyBrandTheme(monaco, theme);
109
+ applyBrandTheme(monacoApi, theme);
98
110
  } catch (err) {
99
111
  console.error("[@elabs-ai/components-editor] failed to apply brand theme", err);
100
112
  }
101
- }, [editor2, theme, revision]);
113
+ }, [editor, theme, revision]);
102
114
  const resolvedStyle = {
103
115
  height: typeof height === "number" ? `${height}px` : height,
104
116
  ...style
@@ -116,7 +128,7 @@ var DiffEditor = forwardRef(function DiffEditor2({
116
128
  });
117
129
 
118
130
  // src/code-workspace/code-workspace.tsx
119
- import { Tabs, TabsList, TabsTrigger } from "@elabs-ai/components-ui";
131
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@elabs-ai/components-ui";
120
132
  import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
121
133
  import {
122
134
  forwardRef as forwardRef2,
@@ -150,6 +162,13 @@ var EXT_LANGUAGE = {
150
162
  sh: "shell"
151
163
  };
152
164
  var basename = (path) => path.split("/").pop() || path;
165
+ function tabIdForFile(path) {
166
+ let out = "";
167
+ for (const char of path) {
168
+ out += /[A-Za-z0-9-]/.test(char) ? char : `_${char.codePointAt(0).toString(16)}_`;
169
+ }
170
+ return `tab-${out}`;
171
+ }
153
172
  function inferLanguage(file) {
154
173
  if (file.language) return file.language;
155
174
  const ext = file.path.split(".").pop()?.toLowerCase() ?? "";
@@ -181,6 +200,8 @@ var CodeWorkspace = forwardRef2(
181
200
  const requested = isControlled ? activePath : internalActive;
182
201
  const activeFile = files.find((f) => f.path === requested) ?? files[0];
183
202
  const active = activeFile?.path ?? "";
203
+ const tabs = files.map((file) => ({ file, id: tabIdForFile(file.path) }));
204
+ const activeTabId = tabs.find((t) => t.file.path === active)?.id ?? "";
184
205
  const rootRef = useRef2(null);
185
206
  const [activeEditor, setActiveEditor] = useState2(null);
186
207
  useEffect2(() => {
@@ -236,35 +257,63 @@ var CodeWorkspace = forwardRef2(
236
257
  children: /* @__PURE__ */ jsxs(
237
258
  Tabs,
238
259
  {
239
- value: active,
240
- onValueChange: selectTab,
260
+ value: activeTabId,
261
+ onValueChange: (id) => {
262
+ const found = tabs.find((t) => t.id === id);
263
+ if (found) selectTab(found.file.path);
264
+ },
241
265
  className: "flex min-h-0 flex-1 flex-col gap-0",
242
266
  children: [
243
267
  /* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-border bg-surface", children: [
244
- /* @__PURE__ */ jsx2(TabsList, { className: "h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0", children: files.map((file) => /* @__PURE__ */ jsx2(
268
+ /* @__PURE__ */ jsx2(TabsList, { className: "h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0", children: tabs.map(({ file, id }) => /* @__PURE__ */ jsx2(
245
269
  TabsTrigger,
246
270
  {
247
- value: file.path,
271
+ value: id,
248
272
  className: "h-9 rounded-none border-e border-border px-3 text-xs data-[state=active]:bg-background data-[state=active]:shadow-none",
249
273
  children: basename(file.path)
250
274
  },
251
- file.path
275
+ id
252
276
  )) }),
253
277
  activeFile ? /* @__PURE__ */ jsx2(CopyButton, { value: activeFile.value, className: "me-1.5" }) : null
254
278
  ] }),
255
- /* @__PURE__ */ jsx2("div", { className: "min-h-0 flex-1", children: activeFile ? /* @__PURE__ */ jsx2(
256
- CodeEditor,
257
- {
258
- path: activeFile.path,
259
- language: inferLanguage(activeFile),
260
- value: activeFile.value,
261
- readOnly,
262
- onChange: (next) => onFileChange?.(activeFile.path, next),
263
- onMount: (editor2) => setActiveEditor(editor2),
264
- height: "100%"
265
- },
266
- activeFile.path
267
- ) : null })
279
+ tabs.map(({ file, id }) => {
280
+ const isActive = file.path === activeFile?.path;
281
+ return (
282
+ // `forceMount` keeps every tab's content in the DOM so each
283
+ // trigger's `aria-controls` resolves to a real element, not only
284
+ // the active one (#154). Radix derives its own `hidden` from
285
+ // `forceMount || isSelected`, so under `forceMount` it hides
286
+ // NOTHING: without the explicit `hidden` below every inactive
287
+ // panel stays a visible `flex-1` child and the editor height is
288
+ // split across all of them. `tabIndex={-1}` keeps the empty
289
+ // panels out of the tab sequence for the same reason. Only the
290
+ // active file mounts a (single) Monaco instance.
291
+ /* @__PURE__ */ jsx2(
292
+ TabsContent,
293
+ {
294
+ value: id,
295
+ forceMount: true,
296
+ hidden: !isActive,
297
+ tabIndex: isActive ? 0 : -1,
298
+ className: "mt-0 min-h-0 flex-1",
299
+ children: isActive ? /* @__PURE__ */ jsx2(
300
+ CodeEditor,
301
+ {
302
+ path: file.path,
303
+ language: inferLanguage(file),
304
+ value: file.value,
305
+ readOnly,
306
+ onChange: (next) => onFileChange?.(file.path, next),
307
+ onMount: (editor) => setActiveEditor(editor),
308
+ height: "100%"
309
+ },
310
+ file.path
311
+ ) : null
312
+ },
313
+ id
314
+ )
315
+ );
316
+ })
268
317
  ]
269
318
  }
270
319
  )
@@ -279,7 +328,8 @@ import {
279
328
  SelectContent,
280
329
  SelectItem,
281
330
  SelectTrigger,
282
- SelectValue
331
+ SelectValue,
332
+ useLocale
283
333
  } from "@elabs-ai/components-ui";
284
334
  import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
285
335
  import { forwardRef as forwardRef3 } from "react";
@@ -316,6 +366,7 @@ var EditorToolbar = forwardRef3(function EditorToolbar2({
316
366
  className,
317
367
  ...props
318
368
  }, ref) {
369
+ const { t } = useLocale();
319
370
  return /* @__PURE__ */ jsxs2(
320
371
  "div",
321
372
  {
@@ -330,7 +381,15 @@ var EditorToolbar = forwardRef3(function EditorToolbar2({
330
381
  /* @__PURE__ */ jsxs2("div", { className: "ml-auto flex items-center gap-1.5", children: [
331
382
  actions,
332
383
  onLanguageChange ? /* @__PURE__ */ jsxs2(Select, { value: language, onValueChange: onLanguageChange, children: [
333
- /* @__PURE__ */ jsx3(SelectTrigger, { size: "sm", className: "h-7 w-[140px]", "aria-label": "Language", children: /* @__PURE__ */ jsx3(SelectValue, { placeholder: "Language" }) }),
384
+ /* @__PURE__ */ jsx3(
385
+ SelectTrigger,
386
+ {
387
+ size: "sm",
388
+ className: "h-7 w-[140px]",
389
+ "aria-label": t("editor.editorToolbar.language"),
390
+ children: /* @__PURE__ */ jsx3(SelectValue, { placeholder: t("editor.editorToolbar.language") })
391
+ }
392
+ ),
334
393
  /* @__PURE__ */ jsx3(SelectContent, { children: languages.map((l) => /* @__PURE__ */ jsx3(SelectItem, { value: l.id, children: l.label }, l.id)) })
335
394
  ] }) : null,
336
395
  copyValue != null ? /* @__PURE__ */ jsx3(CopyButton, { value: copyValue }) : null
@@ -339,9 +398,6 @@ var EditorToolbar = forwardRef3(function EditorToolbar2({
339
398
  }
340
399
  );
341
400
  });
342
-
343
- // src/index.ts
344
- import * as monaco2 from "monaco-editor";
345
401
  export {
346
402
  CodeEditor,
347
403
  CodeWorkspace,
@@ -355,7 +411,6 @@ export {
355
411
  brandThemeId,
356
412
  buildBrandThemeData,
357
413
  languageLabel,
358
- monaco2 as monaco,
359
414
  monacoContentAccess,
360
415
  useDataTheme
361
416
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/diff-editor/diff-editor.tsx","../src/code-workspace/code-workspace.tsx","../src/editor-toolbar/editor-toolbar.tsx","../src/lib/languages.ts","../src/index.ts"],"sourcesContent":["\"use client\";\n\nimport * as monaco from \"monaco-editor\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { applyBrandTheme } from \"../lib/monaco-theme-bridge\";\nimport { useDataTheme } from \"../lib/use-data-theme\";\n\nexport type MonacoDiffEditor = monaco.editor.IStandaloneDiffEditor;\n\nexport interface DiffEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\"> {\n /** Left/original document. */\n original: string;\n /** Right/modified document. */\n modified: string;\n /** Monaco language id applied to both sides. Defaults to \"typescript\". */\n language?: string;\n /** Read-only (diffs are read-only by default). */\n readOnly?: boolean;\n /** Side-by-side (true) vs inline (false) diff. Defaults to side-by-side. */\n renderSideBySide?: boolean;\n /** Editor height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n /** Passthrough Monaco diff options (merged over the defaults). */\n options?: monaco.editor.IStandaloneDiffEditorConstructionOptions;\n /** Called once the diff editor + monaco namespace are ready. */\n onMount?: (editor: MonacoDiffEditor, monacoApi: typeof monaco) => void;\n}\n\nconst BASE_OPTIONS: monaco.editor.IStandaloneDiffEditorConstructionOptions = {\n automaticLayout: true,\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 13,\n ignoreTrimWhitespace: false,\n padding: { top: 12, bottom: 12 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n};\n\n/**\n * A token-themed Monaco diff editor (original ↔ modified). Same theming bridge\n * and chrome story as {@link CodeEditor}; reads as a side-by-side or inline diff.\n */\nexport const DiffEditor = forwardRef<MonacoDiffEditor | null, DiffEditorProps>(function DiffEditor(\n {\n original,\n modified,\n language = \"typescript\",\n readOnly = true,\n renderSideBySide = true,\n height = \"100%\",\n options,\n onMount,\n className,\n style,\n ...props\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [editor, setEditor] = useState<MonacoDiffEditor | null>(null);\n const { theme, revision } = useDataTheme();\n\n const onMountRef = useRef(onMount);\n onMountRef.current = onMount;\n\n useImperativeHandle<MonacoDiffEditor | null, MonacoDiffEditor | null>(ref, () => editor, [\n editor,\n ]);\n\n // Mount once.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const instance = monaco.editor.createDiffEditor(container, {\n ...BASE_OPTIONS,\n readOnly,\n renderSideBySide,\n ...options,\n });\n const originalModel = monaco.editor.createModel(original, language);\n const modifiedModel = monaco.editor.createModel(modified, language);\n instance.setModel({ original: originalModel, modified: modifiedModel });\n // Theme is applied by the effect below once `setEditor` runs.\n setEditor(instance);\n onMountRef.current?.(instance, monaco);\n\n return () => {\n instance.dispose();\n originalModel.dispose();\n modifiedModel.dispose();\n setEditor(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const original_ = editor?.getModel()?.original;\n if (original_ && original_.getValue() !== original) original_.setValue(original);\n }, [editor, original]);\n\n useEffect(() => {\n const modified_ = editor?.getModel()?.modified;\n if (modified_ && modified_.getValue() !== modified) modified_.setValue(modified);\n }, [editor, modified]);\n\n useEffect(() => {\n const models = editor?.getModel();\n if (!models) return;\n monaco.editor.setModelLanguage(models.original, language);\n monaco.editor.setModelLanguage(models.modified, language);\n }, [editor, language]);\n\n useEffect(() => {\n editor?.updateOptions({ readOnly, renderSideBySide });\n }, [editor, readOnly, renderSideBySide]);\n\n useEffect(() => {\n if (!editor) return;\n try {\n applyBrandTheme(monaco, theme);\n } catch (err) {\n console.error(\"[@elabs-ai/components-editor] failed to apply brand theme\", err);\n }\n // `revision` re-applies once the data-theme attribute settles. See CodeEditor.\n }, [editor, theme, revision]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n data-testid=\"diff-editor\"\n className={cn(\"h-full w-full overflow-hidden bg-background text-foreground\", className)}\n style={resolvedStyle}\n {...props}\n />\n );\n});\n","\"use client\";\n\nimport { Tabs, TabsList, TabsTrigger } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { CodeEditor, type MonacoCodeEditor } from \"../code-editor\";\nimport { CopyButton } from \"../copy-button\";\nimport {\n monacoContentAccess,\n type EditorContentAccess,\n type EditorSelection,\n} from \"../lib/editor-content-access\";\n\nexport interface EditorFile {\n /** Unique path/name; shown (basename) on the tab and used as the model path. */\n path: string;\n /** Monaco language id. Inferred from the extension when omitted. */\n language?: string;\n /** File content. */\n value: string;\n}\n\nexport interface CodeWorkspaceProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Open files, one tab each. */\n files: EditorFile[];\n /** Controlled active file path. */\n activePath?: string;\n /** Initial active path for uncontrolled use. Defaults to the first file. */\n defaultActivePath?: string;\n /** Called when the active tab changes. */\n onActivePathChange?: (path: string) => void;\n /** Called on every edit with the file path + new content. */\n onFileChange?: (path: string, value: string) => void;\n /** Render all files read-only. */\n readOnly?: boolean;\n /** Workspace height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n}\n\nconst EXT_LANGUAGE: Record<string, string> = {\n ts: \"typescript\",\n tsx: \"typescript\",\n mts: \"typescript\",\n cts: \"typescript\",\n js: \"javascript\",\n jsx: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n json: \"json\",\n css: \"css\",\n scss: \"scss\",\n less: \"less\",\n html: \"html\",\n md: \"markdown\",\n py: \"python\",\n sql: \"sql\",\n yml: \"yaml\",\n yaml: \"yaml\",\n xml: \"xml\",\n sh: \"shell\",\n};\n\nconst basename = (path: string) => path.split(\"/\").pop() || path;\n\nfunction inferLanguage(file: EditorFile): string {\n if (file.language) return file.language;\n const ext = file.path.split(\".\").pop()?.toLowerCase() ?? \"\";\n return EXT_LANGUAGE[ext] ?? \"plaintext\";\n}\n\n/**\n * Imperative handle exposed via `CodeWorkspace`'s `ref`.\n *\n * **Migration note:** the forwarded `ref` type changed from `HTMLDivElement` to\n * `CodeWorkspaceHandle` (mirrors the #273 `MarkdownWorkspaceHandle` precedent).\n * Replace any `ref.current` / `ref.current.scrollIntoView()` DOM access with\n * `ref.current?.getElement()`. This is the only breaking change in this work.\n */\nexport interface CodeWorkspaceHandle extends EditorContentAccess {\n /**\n * The live Monaco editor for the currently active tab, or `null` while booting\n * or when no files are open. Consumers needing the full Monaco API can use this\n * and pass it to `monacoContentAccess(handle.getActiveEditor()!)`.\n */\n getActiveEditor(): MonacoCodeEditor | null;\n /**\n * The workspace root DOM element. Preserves the old `HTMLDivElement` ref access\n * that existed before the ref-type change (#273 `MarkdownWorkspace` playbook).\n */\n getElement(): HTMLDivElement | null;\n}\n\n/** No-op EditorContentAccess returned when no Monaco editor is mounted. */\nconst NULL_ACCESS: EditorContentAccess = {\n getText: () => \"\",\n getSelection: () => ({ text: \"\", empty: true }),\n replaceSelection: () => undefined,\n insertAtCursor: () => undefined,\n focus: () => undefined,\n onSelectionChange: () => () => undefined,\n};\n\n/**\n * Multi-file editor shell: brand-ui `Tabs` for the file bar above a single\n * {@link CodeEditor} that swaps to the active file. This is the \"reuse our\n * components for the chrome\" surface — Tabs/Button are ours; the editing surface\n * is Monaco. Edits bubble through `onFileChange`.\n *\n * The forwarded `ref` exposes a {@link CodeWorkspaceHandle} (not `HTMLDivElement`).\n * Use `ref.current?.getElement()` to access the root DOM node.\n */\nexport const CodeWorkspace = forwardRef<CodeWorkspaceHandle, CodeWorkspaceProps>(\n function CodeWorkspace(\n {\n files,\n activePath,\n defaultActivePath,\n onActivePathChange,\n onFileChange,\n readOnly = false,\n height = \"100%\",\n className,\n style,\n ...props\n },\n ref,\n ) {\n const [internalActive, setInternalActive] = useState(defaultActivePath ?? files[0]?.path ?? \"\");\n const isControlled = activePath !== undefined;\n const requested = isControlled ? activePath : internalActive;\n const activeFile = files.find((f) => f.path === requested) ?? files[0];\n const active = activeFile?.path ?? \"\";\n\n // The root div ref (getElement() preserves old HTMLDivElement access).\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n // The live Monaco editor for the active tab. Nulled when there is no active file.\n const [activeEditor, setActiveEditor] = useState<MonacoCodeEditor | null>(null);\n\n // Defensive: if the active file is removed (no `activeFile`), null the held instance\n // so getActiveEditor() and content-access methods don't act on a disposed editor.\n useEffect(() => {\n if (!activeFile) setActiveEditor(null);\n }, [activeFile]);\n\n // A STABLE listener set for onSelectionChange. The handle adds/removes here;\n // the binding effect below forwards the ACTIVE editor's selection changes into\n // it. This is what makes a subscribe-in-mount-effect survive the editor's async\n // mount (and tab swaps): the consumer subscribes once; binding re-attaches as\n // `activeEditor` changes. (Delegating onSelectionChange to a call-time snapshot\n // would bind to the not-yet-mounted editor and silently never fire.)\n const [selectionListeners] = useState(() => new Set<(sel: EditorSelection) => void>());\n useEffect(() => {\n if (!activeEditor) return;\n const unsub = monacoContentAccess(activeEditor).onSelectionChange((sel) =>\n selectionListeners.forEach((l) => l(sel)),\n );\n return unsub;\n }, [activeEditor, selectionListeners]);\n\n const selectTab = useCallback(\n (path: string) => {\n if (!isControlled) setInternalActive(path);\n onActivePathChange?.(path);\n },\n [isControlled, onActivePathChange],\n );\n\n useImperativeHandle(ref, () => {\n const access = activeEditor ? monacoContentAccess(activeEditor) : NULL_ACCESS;\n return {\n getActiveEditor: () => activeEditor,\n getElement: () => rootRef.current,\n getText: () => access.getText(),\n getSelection: () => access.getSelection(),\n replaceSelection: (text: string) => access.replaceSelection(text),\n insertAtCursor: (text: string) => access.insertAtCursor(text),\n focus: () => access.focus(),\n // Stable set (not the call-time `access`) so a subscription survives the\n // editor's async mount + tab swaps; the binding effect forwards events.\n onSelectionChange: (listener) => {\n selectionListeners.add(listener);\n return () => selectionListeners.delete(listener);\n },\n };\n }, [activeEditor, selectionListeners]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={rootRef}\n className={cn(\n \"flex flex-col overflow-hidden rounded-lg border border-border bg-surface\",\n className,\n )}\n style={resolvedStyle}\n {...props}\n >\n <Tabs\n value={active}\n onValueChange={selectTab}\n className=\"flex min-h-0 flex-1 flex-col gap-0\"\n >\n <div className=\"flex items-center border-b border-border bg-surface\">\n <TabsList className=\"h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0\">\n {files.map((file) => (\n <TabsTrigger\n key={file.path}\n value={file.path}\n className=\"h-9 rounded-none border-e border-border px-3 text-xs data-[state=active]:bg-background data-[state=active]:shadow-none\"\n >\n {basename(file.path)}\n </TabsTrigger>\n ))}\n </TabsList>\n {activeFile ? <CopyButton value={activeFile.value} className=\"me-1.5\" /> : null}\n </div>\n\n <div className=\"min-h-0 flex-1\">\n {activeFile ? (\n <CodeEditor\n key={activeFile.path}\n path={activeFile.path}\n language={inferLanguage(activeFile)}\n value={activeFile.value}\n readOnly={readOnly}\n onChange={(next) => onFileChange?.(activeFile.path, next)}\n onMount={(editor) => setActiveEditor(editor)}\n height=\"100%\"\n />\n ) : null}\n </div>\n </Tabs>\n </div>\n );\n },\n);\n","\"use client\";\n\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { CopyButton } from \"../copy-button\";\nimport { EDITOR_LANGUAGES, type EditorLanguage } from \"../lib/languages\";\n\nexport interface EditorToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /** Filename shown on the left (e.g. \"index.ts\"). */\n filename?: ReactNode;\n /** Active language id; shows a language picker when `onLanguageChange` is set. */\n language?: string;\n /** Called when the user picks a language. Omit to hide the picker. */\n onLanguageChange?: (language: string) => void;\n /** Languages offered in the picker. Defaults to {@link EDITOR_LANGUAGES}. */\n languages?: EditorLanguage[];\n /** Text copied by the copy button. Omit to hide the button. */\n copyValue?: string;\n /** Extra actions rendered before the copy button (brand-ui buttons, etc.). */\n actions?: ReactNode;\n}\n\n/**\n * Brand-ui chrome for the editors — the part that genuinely reuses our\n * components. Renders a filename label, an optional language `Select` and a copy\n * `Button`. The editing surface itself is Monaco; this is the frame around it.\n */\nexport const EditorToolbar = forwardRef<HTMLDivElement, EditorToolbarProps>(function EditorToolbar(\n {\n filename,\n language,\n onLanguageChange,\n languages = EDITOR_LANGUAGES,\n copyValue,\n actions,\n className,\n ...props\n },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n \"flex h-10 shrink-0 items-center gap-2 border-b border-border bg-surface px-3\",\n className,\n )}\n {...props}\n >\n {filename != null ? (\n <span className=\"truncate text-sm font-medium text-foreground\">{filename}</span>\n ) : null}\n\n <div className=\"ml-auto flex items-center gap-1.5\">\n {actions}\n\n {onLanguageChange ? (\n <Select value={language} onValueChange={onLanguageChange}>\n <SelectTrigger size=\"sm\" className=\"h-7 w-[140px]\" aria-label=\"Language\">\n <SelectValue placeholder=\"Language\" />\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.id} value={l.id}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : null}\n\n {copyValue != null ? <CopyButton value={copyValue} /> : null}\n </div>\n </div>\n );\n});\n","/**\n * Curated language list for the editor toolbar's language picker. Ids are\n * Monaco's built-in language ids (`monaco.languages.getLanguages()`), so they\n * map straight onto `setModelLanguage` / model creation.\n */\nexport interface EditorLanguage {\n /** Monaco language id (e.g. \"typescript\"). */\n id: string;\n /** Human-readable label for the picker. */\n label: string;\n}\n\nexport const EDITOR_LANGUAGES: EditorLanguage[] = [\n { id: \"typescript\", label: \"TypeScript\" },\n { id: \"javascript\", label: \"JavaScript\" },\n { id: \"json\", label: \"JSON\" },\n { id: \"html\", label: \"HTML\" },\n { id: \"css\", label: \"CSS\" },\n { id: \"scss\", label: \"SCSS\" },\n { id: \"markdown\", label: \"Markdown\" },\n { id: \"python\", label: \"Python\" },\n { id: \"sql\", label: \"SQL\" },\n { id: \"yaml\", label: \"YAML\" },\n { id: \"xml\", label: \"XML\" },\n { id: \"shell\", label: \"Shell\" },\n { id: \"plaintext\", label: \"Plain text\" },\n];\n\n/** Look up a language's label, falling back to the raw id. */\nexport function languageLabel(id: string): string {\n return EDITOR_LANGUAGES.find((l) => l.id === id)?.label ?? id;\n}\n","/**\n * @elabs-ai/components-editor — a token-themed Monaco (VS Code) editor wrapped as brand-ui\n * components.\n *\n * Monaco is a self-rendering editor engine; this package wraps it the way\n * @elabs-ai/components-flow wraps React Flow and @elabs-ai/components-data wraps TanStack. The editing\n * surface + widgets are Monaco's, recolored from the active brand theme via the\n * theming bridge; the chrome (toolbar, file tabs, copy button) is brand-ui.\n *\n * Worker setup (for completions/diagnostics) — import ONCE at the app entry:\n * import \"@elabs-ai/components-editor/monaco-environment\"; // Vite apps\n */\nexport {\n CodeEditor,\n type CodeEditorProps,\n type MonacoCodeEditor,\n type EditorAction,\n} from \"./code-editor\";\nexport { DiffEditor, type DiffEditorProps, type MonacoDiffEditor } from \"./diff-editor\";\nexport {\n MarkdownEditor,\n type MarkdownEditorProps,\n type MarkdownEditorHandle,\n type EmbedAssetFn,\n} from \"./markdown-editor\";\nexport {\n CodeWorkspace,\n type CodeWorkspaceHandle,\n type CodeWorkspaceProps,\n type EditorFile,\n} from \"./code-workspace\";\nexport {\n monacoContentAccess,\n type EditorContentAccess,\n type EditorSelection,\n} from \"./lib/editor-content-access\";\nexport { EditorToolbar, type EditorToolbarProps } from \"./editor-toolbar\";\nexport { EditorContextMenu, type EditorContextMenuProps } from \"./editor-context-menu\";\nexport { CopyButton, type CopyButtonProps } from \"./copy-button\";\n\n// Theming bridge + language helpers (advanced consumers / custom chrome).\nexport { applyBrandTheme, buildBrandThemeData, brandThemeId } from \"./lib/monaco-theme-bridge\";\nexport { useDataTheme, type DataThemeState } from \"./lib/use-data-theme\";\nexport { EDITOR_LANGUAGES, languageLabel, type EditorLanguage } from \"./lib/languages\";\n\n// Re-export the monaco namespace so consumers can build custom editors / wire\n// commands without adding a direct dependency.\nexport * as monaco from \"monaco-editor\";\n"],"mappings":"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","names":["DiffEditor","editor","cn","forwardRef","useEffect","useImperativeHandle","useRef","useState","jsx","forwardRef","CodeWorkspace","useState","useRef","useEffect","useImperativeHandle","cn","editor","cn","forwardRef","jsx","jsxs","forwardRef","EditorToolbar","cn","monaco"]}
1
+ {"version":3,"sources":["../src/diff-editor/diff-editor.tsx","../src/code-workspace/code-workspace.tsx","../src/editor-toolbar/editor-toolbar.tsx","../src/lib/languages.ts"],"sourcesContent":["\"use client\";\n\n// Type-only — see the matching note in `../code-editor/code-editor.tsx`. The\n// engine loads at RUNTIME via `import(\"monaco-editor\")` in the mount effect\n// below (`monacoRef`), so importing this module (e.g. transitively, via the\n// barrel) never evaluates Monaco.\nimport type * as monaco from \"monaco-editor\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { applyBrandTheme } from \"../lib/monaco-theme-bridge\";\nimport { useDataTheme } from \"../lib/use-data-theme\";\n\nexport type MonacoDiffEditor = monaco.editor.IStandaloneDiffEditor;\n\n/** Same pattern as `code-editor.tsx`'s `MonacoNamespace`. */\ntype MonacoNamespace = typeof monaco;\n\nexport interface DiffEditorProps extends Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\"> {\n /** Left/original document. */\n original: string;\n /** Right/modified document. */\n modified: string;\n /** Monaco language id applied to both sides. Defaults to \"typescript\". */\n language?: string;\n /** Read-only (diffs are read-only by default). */\n readOnly?: boolean;\n /** Side-by-side (true) vs inline (false) diff. Defaults to side-by-side. */\n renderSideBySide?: boolean;\n /** Editor height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n /** Passthrough Monaco diff options (merged over the defaults). */\n options?: monaco.editor.IStandaloneDiffEditorConstructionOptions;\n /** Called once the diff editor + monaco namespace are ready. */\n onMount?: (editor: MonacoDiffEditor, monacoApi: MonacoNamespace) => void;\n}\n\nconst BASE_OPTIONS: monaco.editor.IStandaloneDiffEditorConstructionOptions = {\n automaticLayout: true,\n minimap: { enabled: false },\n scrollBeyondLastLine: false,\n fontSize: 13,\n ignoreTrimWhitespace: false,\n padding: { top: 12, bottom: 12 },\n scrollbar: { verticalScrollbarSize: 10, horizontalScrollbarSize: 10 },\n};\n\n/**\n * A token-themed Monaco diff editor (original ↔ modified). Same theming bridge\n * and chrome story as {@link CodeEditor}; reads as a side-by-side or inline diff.\n */\nexport const DiffEditor = forwardRef<MonacoDiffEditor | null, DiffEditorProps>(function DiffEditor(\n {\n original,\n modified,\n language = \"typescript\",\n readOnly = true,\n renderSideBySide = true,\n height = \"100%\",\n options,\n onMount,\n className,\n style,\n ...props\n },\n ref,\n) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [editor, setEditor] = useState<MonacoDiffEditor | null>(null);\n const { theme, revision } = useDataTheme();\n // Populated once the dynamic `import(\"monaco-editor\")` below resolves.\n // `editor` (React state) is only ever set AFTER this ref, so every other\n // effect that reads both may assume: `editor` truthy implies this truthy.\n const monacoRef = useRef<MonacoNamespace | null>(null);\n\n const onMountRef = useRef(onMount);\n onMountRef.current = onMount;\n\n useImperativeHandle<MonacoDiffEditor | null, MonacoDiffEditor | null>(ref, () => editor, [\n editor,\n ]);\n\n // Mount once. Monaco itself loads lazily (see the top-of-file note) — merely\n // importing this module never evaluates the engine.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n let cancelled = false;\n let instance: MonacoDiffEditor | null = null;\n let originalModel: monaco.editor.ITextModel | null = null;\n let modifiedModel: monaco.editor.ITextModel | null = null;\n\n import(\"monaco-editor\").then((monacoApi) => {\n if (cancelled) return;\n monacoRef.current = monacoApi;\n instance = monacoApi.editor.createDiffEditor(container, {\n ...BASE_OPTIONS,\n readOnly,\n renderSideBySide,\n ...options,\n });\n originalModel = monacoApi.editor.createModel(original, language);\n modifiedModel = monacoApi.editor.createModel(modified, language);\n instance.setModel({ original: originalModel, modified: modifiedModel });\n // Theme is applied by the effect below once `setEditor` runs.\n setEditor(instance);\n onMountRef.current?.(instance, monacoApi);\n });\n\n return () => {\n cancelled = true;\n instance?.dispose();\n originalModel?.dispose();\n modifiedModel?.dispose();\n monacoRef.current = null;\n setEditor(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const original_ = editor?.getModel()?.original;\n if (original_ && original_.getValue() !== original) original_.setValue(original);\n }, [editor, original]);\n\n useEffect(() => {\n const modified_ = editor?.getModel()?.modified;\n if (modified_ && modified_.getValue() !== modified) modified_.setValue(modified);\n }, [editor, modified]);\n\n useEffect(() => {\n const monacoApi = monacoRef.current;\n const models = editor?.getModel();\n if (!models || !monacoApi) return;\n monacoApi.editor.setModelLanguage(models.original, language);\n monacoApi.editor.setModelLanguage(models.modified, language);\n }, [editor, language]);\n\n useEffect(() => {\n editor?.updateOptions({ readOnly, renderSideBySide });\n }, [editor, readOnly, renderSideBySide]);\n\n useEffect(() => {\n const monacoApi = monacoRef.current;\n if (!editor || !monacoApi) return;\n try {\n applyBrandTheme(monacoApi, theme);\n } catch (err) {\n console.error(\"[@elabs-ai/components-editor] failed to apply brand theme\", err);\n }\n // `revision` re-applies once the data-theme attribute settles. See CodeEditor.\n }, [editor, theme, revision]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n data-testid=\"diff-editor\"\n className={cn(\"h-full w-full overflow-hidden bg-background text-foreground\", className)}\n style={resolvedStyle}\n {...props}\n />\n );\n});\n","\"use client\";\n\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n} from \"react\";\nimport { CodeEditor, type MonacoCodeEditor } from \"../code-editor\";\nimport { CopyButton } from \"../copy-button\";\nimport {\n monacoContentAccess,\n type EditorContentAccess,\n type EditorSelection,\n} from \"../lib/editor-content-access\";\n\nexport interface EditorFile {\n /** Unique path/name; shown (basename) on the tab and used as the model path. */\n path: string;\n /** Monaco language id. Inferred from the extension when omitted. */\n language?: string;\n /** File content. */\n value: string;\n}\n\nexport interface CodeWorkspaceProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Open files, one tab each. */\n files: EditorFile[];\n /** Controlled active file path. */\n activePath?: string;\n /** Initial active path for uncontrolled use. Defaults to the first file. */\n defaultActivePath?: string;\n /** Called when the active tab changes. */\n onActivePathChange?: (path: string) => void;\n /** Called on every edit with the file path + new content. */\n onFileChange?: (path: string, value: string) => void;\n /** Render all files read-only. */\n readOnly?: boolean;\n /** Workspace height. Number → px. Defaults to \"100%\". */\n height?: number | string;\n}\n\nconst EXT_LANGUAGE: Record<string, string> = {\n ts: \"typescript\",\n tsx: \"typescript\",\n mts: \"typescript\",\n cts: \"typescript\",\n js: \"javascript\",\n jsx: \"javascript\",\n mjs: \"javascript\",\n cjs: \"javascript\",\n json: \"json\",\n css: \"css\",\n scss: \"scss\",\n less: \"less\",\n html: \"html\",\n md: \"markdown\",\n py: \"python\",\n sql: \"sql\",\n yml: \"yaml\",\n yaml: \"yaml\",\n xml: \"xml\",\n sh: \"shell\",\n};\n\nconst basename = (path: string) => path.split(\"/\").pop() || path;\n\n/**\n * Radix composes each `TabsContent`'s DOM id from the `Tabs`/`TabsTrigger`\n * `value` (see #154): a raw `file.path` leaks `/`, `.`, spaces and non-ASCII\n * characters straight into that id, which is what a screen reader gets back\n * via `aria-controls`. `path` stays the display label and the lookup key in\n * component state (`active`); this is only ever a DOM-id-safe Tabs value.\n *\n * The encoding is INJECTIVE and depends only on the path — never on the\n * file's position in `files`. Every character outside `[A-Za-z0-9-]` becomes\n * `_<hex code point>_`, and `_` itself is never emitted literally, so the\n * mapping is reversible and two distinct paths can never produce the same\n * value (\"a/b\" → `tab-a_2f_b`, \"a.b\" → `tab-a_2e_b`). An index-derived value\n * would have been collision-safe too, but it changes when the list is\n * reordered or a file is inserted, which re-keys the active `TabsContent` and\n * throws away Monaco's selection, scroll position and undo history on an\n * otherwise harmless `files` update.\n */\nfunction tabIdForFile(path: string): string {\n let out = \"\";\n for (const char of path) {\n out += /[A-Za-z0-9-]/.test(char) ? char : `_${char.codePointAt(0)!.toString(16)}_`;\n }\n return `tab-${out}`;\n}\n\nfunction inferLanguage(file: EditorFile): string {\n if (file.language) return file.language;\n const ext = file.path.split(\".\").pop()?.toLowerCase() ?? \"\";\n return EXT_LANGUAGE[ext] ?? \"plaintext\";\n}\n\n/**\n * Imperative handle exposed via `CodeWorkspace`'s `ref`.\n *\n * **Migration note:** the forwarded `ref` type changed from `HTMLDivElement` to\n * `CodeWorkspaceHandle` (mirrors the #273 `MarkdownWorkspaceHandle` precedent).\n * Replace any `ref.current` / `ref.current.scrollIntoView()` DOM access with\n * `ref.current?.getElement()`. This is the only breaking change in this work.\n */\nexport interface CodeWorkspaceHandle extends EditorContentAccess {\n /**\n * The live Monaco editor for the currently active tab, or `null` while booting\n * or when no files are open. Consumers needing the full Monaco API can use this\n * and pass it to `monacoContentAccess(handle.getActiveEditor()!)`.\n */\n getActiveEditor(): MonacoCodeEditor | null;\n /**\n * The workspace root DOM element. Preserves the old `HTMLDivElement` ref access\n * that existed before the ref-type change (#273 `MarkdownWorkspace` playbook).\n */\n getElement(): HTMLDivElement | null;\n}\n\n/** No-op EditorContentAccess returned when no Monaco editor is mounted. */\nconst NULL_ACCESS: EditorContentAccess = {\n getText: () => \"\",\n getSelection: () => ({ text: \"\", empty: true }),\n replaceSelection: () => undefined,\n insertAtCursor: () => undefined,\n focus: () => undefined,\n onSelectionChange: () => () => undefined,\n};\n\n/**\n * Multi-file editor shell: brand-ui `Tabs` for the file bar above a single\n * {@link CodeEditor} that swaps to the active file. This is the \"reuse our\n * components for the chrome\" surface — Tabs/Button are ours; the editing surface\n * is Monaco. Edits bubble through `onFileChange`.\n *\n * The forwarded `ref` exposes a {@link CodeWorkspaceHandle} (not `HTMLDivElement`).\n * Use `ref.current?.getElement()` to access the root DOM node.\n */\nexport const CodeWorkspace = forwardRef<CodeWorkspaceHandle, CodeWorkspaceProps>(\n function CodeWorkspace(\n {\n files,\n activePath,\n defaultActivePath,\n onActivePathChange,\n onFileChange,\n readOnly = false,\n height = \"100%\",\n className,\n style,\n ...props\n },\n ref,\n ) {\n const [internalActive, setInternalActive] = useState(defaultActivePath ?? files[0]?.path ?? \"\");\n const isControlled = activePath !== undefined;\n const requested = isControlled ? activePath : internalActive;\n const activeFile = files.find((f) => f.path === requested) ?? files[0];\n const active = activeFile?.path ?? \"\";\n\n // DOM-id-safe Tabs value per file (#154); `path` stays the lookup key.\n const tabs = files.map((file) => ({ file, id: tabIdForFile(file.path) }));\n const activeTabId = tabs.find((t) => t.file.path === active)?.id ?? \"\";\n\n // The root div ref (getElement() preserves old HTMLDivElement access).\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n // The live Monaco editor for the active tab. Nulled when there is no active file.\n const [activeEditor, setActiveEditor] = useState<MonacoCodeEditor | null>(null);\n\n // Defensive: if the active file is removed (no `activeFile`), null the held instance\n // so getActiveEditor() and content-access methods don't act on a disposed editor.\n useEffect(() => {\n if (!activeFile) setActiveEditor(null);\n }, [activeFile]);\n\n // A STABLE listener set for onSelectionChange. The handle adds/removes here;\n // the binding effect below forwards the ACTIVE editor's selection changes into\n // it. This is what makes a subscribe-in-mount-effect survive the editor's async\n // mount (and tab swaps): the consumer subscribes once; binding re-attaches as\n // `activeEditor` changes. (Delegating onSelectionChange to a call-time snapshot\n // would bind to the not-yet-mounted editor and silently never fire.)\n const [selectionListeners] = useState(() => new Set<(sel: EditorSelection) => void>());\n useEffect(() => {\n if (!activeEditor) return;\n const unsub = monacoContentAccess(activeEditor).onSelectionChange((sel) =>\n selectionListeners.forEach((l) => l(sel)),\n );\n return unsub;\n }, [activeEditor, selectionListeners]);\n\n const selectTab = useCallback(\n (path: string) => {\n if (!isControlled) setInternalActive(path);\n onActivePathChange?.(path);\n },\n [isControlled, onActivePathChange],\n );\n\n useImperativeHandle(ref, () => {\n const access = activeEditor ? monacoContentAccess(activeEditor) : NULL_ACCESS;\n return {\n getActiveEditor: () => activeEditor,\n getElement: () => rootRef.current,\n getText: () => access.getText(),\n getSelection: () => access.getSelection(),\n replaceSelection: (text: string) => access.replaceSelection(text),\n insertAtCursor: (text: string) => access.insertAtCursor(text),\n focus: () => access.focus(),\n // Stable set (not the call-time `access`) so a subscription survives the\n // editor's async mount + tab swaps; the binding effect forwards events.\n onSelectionChange: (listener) => {\n selectionListeners.add(listener);\n return () => selectionListeners.delete(listener);\n },\n };\n }, [activeEditor, selectionListeners]);\n\n const resolvedStyle: CSSProperties = {\n height: typeof height === \"number\" ? `${height}px` : height,\n ...style,\n };\n\n return (\n <div\n ref={rootRef}\n className={cn(\n \"flex flex-col overflow-hidden rounded-lg border border-border bg-surface\",\n className,\n )}\n style={resolvedStyle}\n {...props}\n >\n <Tabs\n value={activeTabId}\n onValueChange={(id) => {\n const found = tabs.find((t) => t.id === id);\n if (found) selectTab(found.file.path);\n }}\n className=\"flex min-h-0 flex-1 flex-col gap-0\"\n >\n <div className=\"flex items-center border-b border-border bg-surface\">\n <TabsList className=\"h-9 flex-1 justify-start gap-0 rounded-none bg-transparent p-0\">\n {tabs.map(({ file, id }) => (\n <TabsTrigger\n key={id}\n value={id}\n className=\"h-9 rounded-none border-e border-border px-3 text-xs data-[state=active]:bg-background data-[state=active]:shadow-none\"\n >\n {basename(file.path)}\n </TabsTrigger>\n ))}\n </TabsList>\n {activeFile ? <CopyButton value={activeFile.value} className=\"me-1.5\" /> : null}\n </div>\n\n {tabs.map(({ file, id }) => {\n const isActive = file.path === activeFile?.path;\n return (\n // `forceMount` keeps every tab's content in the DOM so each\n // trigger's `aria-controls` resolves to a real element, not only\n // the active one (#154). Radix derives its own `hidden` from\n // `forceMount || isSelected`, so under `forceMount` it hides\n // NOTHING: without the explicit `hidden` below every inactive\n // panel stays a visible `flex-1` child and the editor height is\n // split across all of them. `tabIndex={-1}` keeps the empty\n // panels out of the tab sequence for the same reason. Only the\n // active file mounts a (single) Monaco instance.\n <TabsContent\n key={id}\n value={id}\n forceMount\n hidden={!isActive}\n tabIndex={isActive ? 0 : -1}\n className=\"mt-0 min-h-0 flex-1\"\n >\n {isActive ? (\n <CodeEditor\n key={file.path}\n path={file.path}\n language={inferLanguage(file)}\n value={file.value}\n readOnly={readOnly}\n onChange={(next) => onFileChange?.(file.path, next)}\n onMount={(editor) => setActiveEditor(editor)}\n height=\"100%\"\n />\n ) : null}\n </TabsContent>\n );\n })}\n </Tabs>\n </div>\n );\n },\n);\n","\"use client\";\n\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { CopyButton } from \"../copy-button\";\nimport { EDITOR_LANGUAGES, type EditorLanguage } from \"../lib/languages\";\n\nexport interface EditorToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /** Filename shown on the left (e.g. \"index.ts\"). */\n filename?: ReactNode;\n /** Active language id; shows a language picker when `onLanguageChange` is set. */\n language?: string;\n /** Called when the user picks a language. Omit to hide the picker. */\n onLanguageChange?: (language: string) => void;\n /** Languages offered in the picker. Defaults to {@link EDITOR_LANGUAGES}. */\n languages?: EditorLanguage[];\n /** Text copied by the copy button. Omit to hide the button. */\n copyValue?: string;\n /** Extra actions rendered before the copy button (brand-ui buttons, etc.). */\n actions?: ReactNode;\n}\n\n/**\n * Brand-ui chrome for the editors — the part that genuinely reuses our\n * components. Renders a filename label, an optional language `Select` and a copy\n * `Button`. The editing surface itself is Monaco; this is the frame around it.\n */\nexport const EditorToolbar = forwardRef<HTMLDivElement, EditorToolbarProps>(function EditorToolbar(\n {\n filename,\n language,\n onLanguageChange,\n languages = EDITOR_LANGUAGES,\n copyValue,\n actions,\n className,\n ...props\n },\n ref,\n) {\n const { t } = useLocale();\n return (\n <div\n ref={ref}\n className={cn(\n \"flex h-10 shrink-0 items-center gap-2 border-b border-border bg-surface px-3\",\n className,\n )}\n {...props}\n >\n {filename != null ? (\n <span className=\"truncate text-sm font-medium text-foreground\">{filename}</span>\n ) : null}\n\n <div className=\"ml-auto flex items-center gap-1.5\">\n {actions}\n\n {onLanguageChange ? (\n <Select value={language} onValueChange={onLanguageChange}>\n <SelectTrigger\n size=\"sm\"\n className=\"h-7 w-[140px]\"\n aria-label={t(\"editor.editorToolbar.language\")}\n >\n <SelectValue placeholder={t(\"editor.editorToolbar.language\")} />\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.id} value={l.id}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : null}\n\n {copyValue != null ? <CopyButton value={copyValue} /> : null}\n </div>\n </div>\n );\n});\n","/**\n * Curated language list for the editor toolbar's language picker. Ids are\n * Monaco's built-in language ids (`monaco.languages.getLanguages()`), so they\n * map straight onto `setModelLanguage` / model creation.\n */\nexport interface EditorLanguage {\n /** Monaco language id (e.g. \"typescript\"). */\n id: string;\n /** Human-readable label for the picker. */\n label: string;\n}\n\nexport const EDITOR_LANGUAGES: EditorLanguage[] = [\n { id: \"typescript\", label: \"TypeScript\" },\n { id: \"javascript\", label: \"JavaScript\" },\n { id: \"json\", label: \"JSON\" },\n { id: \"html\", label: \"HTML\" },\n { id: \"css\", label: \"CSS\" },\n { id: \"scss\", label: \"SCSS\" },\n { id: \"markdown\", label: \"Markdown\" },\n { id: \"python\", label: \"Python\" },\n { id: \"sql\", label: \"SQL\" },\n { id: \"yaml\", label: \"YAML\" },\n { id: \"xml\", label: \"XML\" },\n { id: \"shell\", label: \"Shell\" },\n { id: \"plaintext\", label: \"Plain text\" },\n];\n\n/** Look up a language's label, falling back to the raw id. */\nexport function languageLabel(id: string): string {\n return EDITOR_LANGUAGES.find((l) => l.id === id)?.label ?? id;\n}\n"],"mappings":"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","names":["DiffEditor","cn","forwardRef","useEffect","useImperativeHandle","useRef","useState","jsx","forwardRef","CodeWorkspace","useState","useRef","useEffect","useImperativeHandle","cn","cn","forwardRef","jsx","jsxs","forwardRef","EditorToolbar","cn"]}
@@ -11,8 +11,10 @@
11
11
  max-width: var(--md-measure, 48rem);
12
12
  margin-inline: auto;
13
13
  }
14
- .milkdown-host .ProseMirror:focus {
15
- outline: none;
14
+ .milkdown-host .ProseMirror:focus-visible {
15
+ outline: 1px solid var(--ring-contour);
16
+ outline-offset: -3px;
17
+ box-shadow: inset 0 0 0 2px var(--ring);
16
18
  }
17
19
  .milkdown-host {
18
20
  display: flex;