@elabs-ai/components-editor 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/dist/{chunk-LBC5VJBD.js → chunk-C62O7IOQ.js} +110 -21
- package/dist/chunk-C62O7IOQ.js.map +1 -0
- package/dist/index.css +4 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -20
- package/dist/index.js.map +1 -1
- package/dist/markdown/index.css +4 -2
- package/dist/markdown/index.css.map +1 -1
- package/dist/markdown/index.d.ts +2 -2
- package/dist/markdown/index.js +9 -9
- package/dist/markdown/index.js.map +1 -1
- package/dist/{markdown-editor-DfBZibAn.d.ts → markdown-editor-CBp_4eDv.d.ts} +1 -1
- package/package.json +6 -6
- package/src/ai-objects/entity.tsx +1 -1
- package/src/ai-objects/knowledge-card.tsx +1 -1
- package/src/calc-block/calc-editor.css +1 -1
- package/src/code-editor/code-editor.stories.tsx +76 -1
- package/src/code-editor/code-editor.tsx +1 -1
- package/src/code-workspace/code-workspace.test.tsx +83 -0
- package/src/code-workspace/code-workspace.tsx +73 -20
- package/src/diff-editor/diff-editor.stories.tsx +9 -1
- package/src/lib/monaco-deep-imports.d.ts +59 -0
- package/src/lib/monaco-theme-bridge.test.ts +307 -0
- package/src/lib/monaco-theme-bridge.ts +154 -10
- package/src/markdown-academic/citations.tsx +2 -2
- package/src/markdown-academic/footnotes.tsx +2 -2
- package/src/markdown-academic/toc.tsx +1 -1
- package/src/markdown-editor/directive-views.tsx +2 -5
- package/src/markdown-editor/markdown-editor.css +26 -3
- package/src/markdown-editor/markdown-editor.focus.test.ts +49 -0
- package/src/markdown-editor/markdown-editor.stories.tsx +128 -7
- package/src/markdown-editor/markdown-editor.tsx +8 -5
- package/src/markdown-editor/milkdown-react/use-get-editor.timer-leak.test.ts +80 -0
- package/src/markdown-editor/milkdown-react/use-get-editor.ts +37 -1
- package/src/markdown-editor/paste-embed.ts +2 -1
- package/src/markdown-editor/slash/slash-menu.stories.tsx +8 -3
- package/src/markdown-editor/slash/slash-menu.test.tsx +27 -0
- package/src/markdown-editor/slash/slash-menu.tsx +13 -1
- package/src/markdown-editor/table-view.tsx +3 -2
- package/src/markdown-iteration/iteration-builder-dialog.stories.tsx +57 -19
- package/src/markdown-outline/document-outline.stories.tsx +1 -1
- package/src/markdown-outline/document-outline.tsx +1 -1
- package/src/markdown-preview/markdown-preview-transclusion.test.tsx +1 -1
- package/src/markdown-preview/markdown-preview.stories.tsx +24 -2
- package/src/markdown-preview/markdown-preview.test.tsx +20 -3
- package/src/markdown-toolbar/markdown-toolbar.stories.tsx +3 -0
- package/src/markdown-workspace/markdown-workspace.test.tsx +31 -1
- package/src/mermaid-diagram/mermaid-viewer.tsx +1 -1
- package/src/prose/prose.stories.tsx +3 -0
- package/src/prose/prose.test.ts +54 -0
- package/src/prose/prose.tsx +7 -0
- package/dist/chunk-LBC5VJBD.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
> Token-themed Monaco editor: CodeEditor, DiffEditor, CodeWorkspace.
|
|
7
7
|
|
|
8
8
|
Part of **brand-ui**, a source-owned, token-driven React component system.
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Published to the **public npm registry** under the `@elabs-ai` scope — it
|
|
10
|
+
installs like any other npm dependency, with no registry configuration and
|
|
11
|
+
no token required. See `docs/CONSUMING.md`.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
"@elabs-ai/components-editor": "workspace:*"
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @elabs-ai/components-tokens @elabs-ai/components-editor
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
## Set up styling (do not skip)
|
|
@@ -75,7 +74,7 @@ prompt for migrating an existing project: `docs/CONSUMING.md`.
|
|
|
75
74
|
|
|
76
75
|
## License
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
MIT
|
|
79
78
|
|
|
80
79
|
<!-- brand-ui:gen:readme:end -->
|
|
81
80
|
|
|
@@ -130,6 +130,7 @@ function withAlpha(hex, alpha) {
|
|
|
130
130
|
const base = hex.slice(0, 7);
|
|
131
131
|
return `${base}${byte(Math.round(clamp01(alpha) * 255))}`;
|
|
132
132
|
}
|
|
133
|
+
var LINE_HIGHLIGHT_ALPHA = 0.05;
|
|
133
134
|
function bare(hex) {
|
|
134
135
|
return hex.replace("#", "").slice(0, 6).padEnd(6, "0");
|
|
135
136
|
}
|
|
@@ -146,6 +147,13 @@ function contrast(a, b) {
|
|
|
146
147
|
const lb = luminance(b);
|
|
147
148
|
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
148
149
|
}
|
|
150
|
+
function flattenOver(overlayHexWithAlpha, groundHex) {
|
|
151
|
+
const overlayBase = overlayHexWithAlpha.slice(0, 7);
|
|
152
|
+
const alphaHex = overlayHexWithAlpha.length >= 9 ? overlayHexWithAlpha.slice(7, 9) : "ff";
|
|
153
|
+
const alpha = clamp01((parseInt(alphaHex, 16) || 0) / 255);
|
|
154
|
+
const blend = (i) => Math.round(channel(overlayBase, i) * alpha + channel(groundHex, i) * (1 - alpha));
|
|
155
|
+
return `#${byte(blend(0))}${byte(blend(1))}${byte(blend(2))}`;
|
|
156
|
+
}
|
|
149
157
|
function mixHex(hex, target, t) {
|
|
150
158
|
const lerp = (i) => Math.round(channel(hex, i) + (channel(target, i) - channel(hex, i)) * t);
|
|
151
159
|
return `#${byte(lerp(0))}${byte(lerp(1))}${byte(lerp(2))}`;
|
|
@@ -174,6 +182,8 @@ function buildBrandThemeData(rootEl) {
|
|
|
174
182
|
const border = read("--border", muted);
|
|
175
183
|
const primary = read("--primary", foreground);
|
|
176
184
|
const ring = read("--ring", primary);
|
|
185
|
+
const ringContour = read("--ring-contour", ring);
|
|
186
|
+
const focusBorder = contrast(ringContour, background) > contrast(ring, background) ? ringContour : ring;
|
|
177
187
|
const popover = read("--popover", background);
|
|
178
188
|
const popoverFg = read("--popover-foreground", foreground);
|
|
179
189
|
const input = read("--input", border);
|
|
@@ -183,7 +193,9 @@ function buildBrandThemeData(rootEl) {
|
|
|
183
193
|
const chart4 = read("--chart-4", primary);
|
|
184
194
|
const success = read("--success", chart2);
|
|
185
195
|
const destructive = read("--destructive", "#ff0000");
|
|
186
|
-
const
|
|
196
|
+
const lineHighlight = withAlpha(foreground, LINE_HIGHLIGHT_ALPHA);
|
|
197
|
+
const tokenGround = flattenOver(lineHighlight, background);
|
|
198
|
+
const calcResult = ensureReadable(read("--calc-result", primary), tokenGround, 4.5);
|
|
187
199
|
const colors = {
|
|
188
200
|
"editor.background": background,
|
|
189
201
|
"editor.foreground": foreground,
|
|
@@ -194,7 +206,7 @@ function buildBrandThemeData(rootEl) {
|
|
|
194
206
|
"editor.selectionBackground": withAlpha(primary, 0.28),
|
|
195
207
|
"editor.inactiveSelectionBackground": withAlpha(primary, 0.14),
|
|
196
208
|
"editor.selectionHighlightBackground": withAlpha(primary, 0.14),
|
|
197
|
-
"editor.lineHighlightBackground":
|
|
209
|
+
"editor.lineHighlightBackground": lineHighlight,
|
|
198
210
|
"editor.lineHighlightBorder": "#00000000",
|
|
199
211
|
"editorIndentGuide.background1": withAlpha(border, 0.6),
|
|
200
212
|
"editorIndentGuide.activeBackground1": mutedFg,
|
|
@@ -220,7 +232,7 @@ function buildBrandThemeData(rootEl) {
|
|
|
220
232
|
"input.background": input,
|
|
221
233
|
"input.foreground": foreground,
|
|
222
234
|
"input.border": border,
|
|
223
|
-
focusBorder
|
|
235
|
+
focusBorder,
|
|
224
236
|
"dropdown.background": popover,
|
|
225
237
|
"dropdown.foreground": popoverFg,
|
|
226
238
|
"dropdown.border": border,
|
|
@@ -261,7 +273,8 @@ function buildBrandThemeData(rootEl) {
|
|
|
261
273
|
"diffEditorOverview.removedForeground": withAlpha(destructive, 0.6),
|
|
262
274
|
"diffEditor.border": border
|
|
263
275
|
};
|
|
264
|
-
const
|
|
276
|
+
const AA_MARGIN = 0.15;
|
|
277
|
+
const ink = (hex, ratio = 4.5) => bare(ensureReadable(hex, tokenGround, ratio + AA_MARGIN));
|
|
265
278
|
const rules = [
|
|
266
279
|
{ token: "", foreground: bare(foreground), background: bare(background) },
|
|
267
280
|
{ token: "comment", foreground: ink(mutedFg, 3.2), fontStyle: "italic" },
|
|
@@ -286,7 +299,61 @@ function buildBrandThemeData(rootEl) {
|
|
|
286
299
|
{ token: "string.key", foreground: ink(chart1) },
|
|
287
300
|
{ token: "string.value", foreground: ink(chart2) },
|
|
288
301
|
{ token: "invalid", foreground: ink(destructive) },
|
|
289
|
-
{ token: "namespace", foreground: ink(success) }
|
|
302
|
+
{ token: "namespace", foreground: ink(success) },
|
|
303
|
+
// Monaco's built-in `vs`/`vs-dark` bases (inherited at `inherit: true`,
|
|
304
|
+
// below) ship LANGUAGE-SUFFIXED rules — `string.key.json`,
|
|
305
|
+
// `string.value.json`, `keyword.json`, `string.yaml`, `delimiter.html`, …
|
|
306
|
+
// — and Monaco's token-theme trie resolves the DEEPEST matching scope
|
|
307
|
+
// (`ThemeTrieElement.match`), with rules sorted lexicographically before
|
|
308
|
+
// insertion. A shorter brand scope (e.g. `string.key`, above) can
|
|
309
|
+
// therefore NEVER override a longer base scope: every scope a base theme
|
|
310
|
+
// specialises must be re-declared here, or that language renders in
|
|
311
|
+
// stock VS colours (#90). Keep this list in sync with the base themes —
|
|
312
|
+
// `IGNORED_BASE_SCOPES` + the drift guard in `monaco-theme-bridge.test.ts`
|
|
313
|
+
// fail CI if a future `monaco-editor` upgrade adds a new one.
|
|
314
|
+
{ token: "string.key.json", foreground: ink(chart1) },
|
|
315
|
+
// pairs with `key`
|
|
316
|
+
{ token: "string.value.json", foreground: ink(chart2) },
|
|
317
|
+
// pairs with `string`
|
|
318
|
+
{ token: "keyword.json", foreground: ink(primary) },
|
|
319
|
+
// pairs with `keyword`
|
|
320
|
+
// Closes the class, not just JSON — the same base-specialisation gap
|
|
321
|
+
// reaches YAML/HTML/SQL/XML/CSS/SCSS (#90 evidence #7).
|
|
322
|
+
{ token: "string.html", foreground: ink(chart2) },
|
|
323
|
+
{ token: "string.sql", foreground: ink(chart2) },
|
|
324
|
+
{ token: "string.yaml", foreground: ink(chart2) },
|
|
325
|
+
{ token: "delimiter.html", foreground: ink(mutedFg, 3.2) },
|
|
326
|
+
{ token: "delimiter.xml", foreground: ink(mutedFg, 3.2) },
|
|
327
|
+
{ token: "attribute.value.html", foreground: ink(chart2) },
|
|
328
|
+
{ token: "attribute.value.xml", foreground: ink(chart2) },
|
|
329
|
+
{ token: "attribute.value.number", foreground: ink(chart4) },
|
|
330
|
+
{ token: "attribute.value.unit", foreground: ink(chart4) },
|
|
331
|
+
{ token: "attribute.value.number.css", foreground: ink(chart4) },
|
|
332
|
+
{ token: "attribute.value.unit.css", foreground: ink(chart4) },
|
|
333
|
+
{ token: "attribute.value.hex.css", foreground: ink(chart4) },
|
|
334
|
+
{ token: "number.hex", foreground: ink(chart4) },
|
|
335
|
+
{ token: "keyword.flow", foreground: ink(primary) },
|
|
336
|
+
{ token: "keyword.flow.scss", foreground: ink(primary) },
|
|
337
|
+
{ token: "operator.scss", foreground: ink(primary) },
|
|
338
|
+
{ token: "operator.sql", foreground: ink(primary) },
|
|
339
|
+
{ token: "operator.swift", foreground: ink(primary) },
|
|
340
|
+
{ token: "predefined.sql", foreground: ink(chart3) },
|
|
341
|
+
{ token: "metatag", foreground: ink(chart3) },
|
|
342
|
+
{ token: "metatag.html", foreground: ink(chart3) },
|
|
343
|
+
{ token: "metatag.xml", foreground: ink(chart3) },
|
|
344
|
+
{ token: "metatag.content.html", foreground: ink(chart3) },
|
|
345
|
+
{ token: "meta.scss", foreground: ink(chart1) },
|
|
346
|
+
{ token: "meta.tag", foreground: ink(chart1) },
|
|
347
|
+
// `CodeEditorProps.language` is a plain, unrestricted `string` passed
|
|
348
|
+
// straight to `monaco.editor.setModelLanguage` (`code-editor.tsx`) — NOT
|
|
349
|
+
// limited to `EDITOR_LANGUAGES` — so a consumer really can reach these
|
|
350
|
+
// pug/handlebars scopes (PR #119 review thread 2). See
|
|
351
|
+
// `IGNORED_BASE_SCOPES` below for the one scope that stays un-overridden.
|
|
352
|
+
{ token: "tag.id.pug", foreground: ink(primary) },
|
|
353
|
+
// pairs with `tag`
|
|
354
|
+
{ token: "tag.class.pug", foreground: ink(primary) },
|
|
355
|
+
{ token: "variable.parameter", foreground: bare(foreground) }
|
|
356
|
+
// pairs with `variable`
|
|
290
357
|
];
|
|
291
358
|
return { base: "vs", inherit: true, colors, rules };
|
|
292
359
|
}
|
|
@@ -912,7 +979,7 @@ var DocumentOutline = forwardRef2(
|
|
|
912
979
|
className: cn2(
|
|
913
980
|
"-ms-px block w-full truncate border-s-2 py-1 pe-2 text-start text-caption",
|
|
914
981
|
"transition-colors duration-fast ease-standard motion-reduce:transition-none",
|
|
915
|
-
"focus-
|
|
982
|
+
"focus-ring-inset",
|
|
916
983
|
item.id === activeId ? "border-s-primary font-medium text-foreground" : "border-s-transparent text-muted-foreground hover:border-s-border-strong hover:text-foreground",
|
|
917
984
|
actions && "pe-8"
|
|
918
985
|
),
|
|
@@ -1725,7 +1792,23 @@ var SlashMenu = forwardRef4(function SlashMenu2({
|
|
|
1725
1792
|
className
|
|
1726
1793
|
),
|
|
1727
1794
|
...props,
|
|
1728
|
-
children: commands.length === 0 ?
|
|
1795
|
+
children: commands.length === 0 ? (
|
|
1796
|
+
// A `role="listbox"` may only own `option`/`group` children (WCAG 1.3.1,
|
|
1797
|
+
// #157): a bare `<div>` here is an unannounced, dangling message — an
|
|
1798
|
+
// AT user who types a query matching nothing gets silence. Shaping it
|
|
1799
|
+
// as a disabled, unselectable option keeps the listbox contract AND
|
|
1800
|
+
// makes the message reachable by an accessible-name query.
|
|
1801
|
+
/* @__PURE__ */ jsx5(
|
|
1802
|
+
"div",
|
|
1803
|
+
{
|
|
1804
|
+
role: "option",
|
|
1805
|
+
"aria-disabled": "true",
|
|
1806
|
+
"aria-selected": "false",
|
|
1807
|
+
className: "cursor-default select-none px-2 py-6 text-center text-caption text-muted-foreground",
|
|
1808
|
+
children: emptyLabel
|
|
1809
|
+
}
|
|
1810
|
+
)
|
|
1811
|
+
) : groups.map(({ group, commands: groupCommands }) => /* @__PURE__ */ jsxs4("div", { role: "group", "aria-label": group, className: "overflow-hidden p-1", children: [
|
|
1729
1812
|
/* @__PURE__ */ jsx5("div", { className: "px-2 py-1.5 text-meta font-medium text-muted-foreground", children: group }),
|
|
1730
1813
|
groupCommands.map((command) => {
|
|
1731
1814
|
const selected = command.id === activeId;
|
|
@@ -2414,6 +2497,7 @@ import { useMemo as useMemo2, useRef as useRef5, useState as useState3 } from "r
|
|
|
2414
2497
|
// src/markdown-editor/milkdown-react/use-get-editor.ts
|
|
2415
2498
|
import { createContext as createContext2, useContext, useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
2416
2499
|
var editorInfoContext = createContext2({});
|
|
2500
|
+
var pendingDestroys = /* @__PURE__ */ new Set();
|
|
2417
2501
|
function useGetEditor() {
|
|
2418
2502
|
const {
|
|
2419
2503
|
dom,
|
|
@@ -2436,7 +2520,11 @@ function useGetEditor() {
|
|
|
2436
2520
|
setLoading(false);
|
|
2437
2521
|
}).catch(console.error);
|
|
2438
2522
|
return () => {
|
|
2439
|
-
editor2.destroy().catch(console.error);
|
|
2523
|
+
const destroyPromise = editor2.destroy().catch(console.error);
|
|
2524
|
+
pendingDestroys.add(destroyPromise);
|
|
2525
|
+
void destroyPromise.finally(() => {
|
|
2526
|
+
pendingDestroys.delete(destroyPromise);
|
|
2527
|
+
});
|
|
2440
2528
|
};
|
|
2441
2529
|
}, [dom, editorRef, getEditor, setLoading]);
|
|
2442
2530
|
return domRef;
|
|
@@ -2549,10 +2637,7 @@ function InlineEdit({ value, onCommit, ariaLabel, placeholder, className }) {
|
|
|
2549
2637
|
spellCheck: false,
|
|
2550
2638
|
onBlur: commit,
|
|
2551
2639
|
onKeyDown,
|
|
2552
|
-
className: cn6(
|
|
2553
|
-
"brand-inline-edit rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2554
|
-
className
|
|
2555
|
-
)
|
|
2640
|
+
className: cn6("brand-inline-edit rounded-sm focus-ring", className)
|
|
2556
2641
|
}
|
|
2557
2642
|
);
|
|
2558
2643
|
}
|
|
@@ -2833,7 +2918,7 @@ function IterationDirectiveView() {
|
|
|
2833
2918
|
"data-directive-chrome": "",
|
|
2834
2919
|
"aria-label": "Iteration actions",
|
|
2835
2920
|
title: "Iteration actions\u2026",
|
|
2836
|
-
className: "ms-auto inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground focus-
|
|
2921
|
+
className: "ms-auto inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground focus-ring",
|
|
2837
2922
|
children: /* @__PURE__ */ jsx10(MoreHorizontal, { className: "size-4", "aria-hidden": "true" })
|
|
2838
2923
|
}
|
|
2839
2924
|
) }),
|
|
@@ -3045,7 +3130,7 @@ function makeErrorChip(message) {
|
|
|
3045
3130
|
chip.setAttribute("role", "alert");
|
|
3046
3131
|
chip.setAttribute("aria-live", "assertive");
|
|
3047
3132
|
chip.setAttribute("title", message);
|
|
3048
|
-
chip.className = "inline-flex items-center gap-1 rounded px-2 py-0.5 text-caption bg-destructive/10 text-destructive border border-destructive/30 select-none";
|
|
3133
|
+
chip.className = "inline-flex items-center gap-1 rounded px-2 py-0.5 text-caption bg-destructive/10 text-destructive-text border border-destructive/30 select-none";
|
|
3049
3134
|
const label = document.createElement("span");
|
|
3050
3135
|
label.textContent = `\u26A0 Upload failed`;
|
|
3051
3136
|
chip.appendChild(label);
|
|
@@ -3259,9 +3344,10 @@ function ToolbarButton({
|
|
|
3259
3344
|
className: cn7(
|
|
3260
3345
|
"inline-flex h-7 items-center gap-1 rounded px-2 text-caption font-medium",
|
|
3261
3346
|
"border border-border-strong",
|
|
3262
|
-
"focus-
|
|
3347
|
+
"focus-ring",
|
|
3263
3348
|
"transition-colors duration-fast ease-standard motion-reduce:transition-none",
|
|
3264
|
-
|
|
3349
|
+
// #124: the button's LABEL text, not a mark — ink rung.
|
|
3350
|
+
variant === "destructive" ? "bg-background text-destructive-text hover:bg-destructive/10" : "bg-background text-foreground hover:bg-surface-muted"
|
|
3265
3351
|
),
|
|
3266
3352
|
children
|
|
3267
3353
|
}
|
|
@@ -3591,10 +3677,13 @@ var MarkdownEditor = forwardRef6(
|
|
|
3591
3677
|
{
|
|
3592
3678
|
"data-testid": "markdown-editor",
|
|
3593
3679
|
className: cn8(
|
|
3594
|
-
//
|
|
3595
|
-
//
|
|
3596
|
-
//
|
|
3597
|
-
|
|
3680
|
+
// No focus-ring utility here (deliberately, #309): the editable
|
|
3681
|
+
// `.ProseMirror` element owns the compound focus indicator itself
|
|
3682
|
+
// (`markdown-editor.css`'s `:focus-visible` rule), so it renders
|
|
3683
|
+
// even when a consumer `className` overrides this wrapper's
|
|
3684
|
+
// classes (`MarkdownWorkspace` does exactly that via
|
|
3685
|
+
// `className="border-0"`) and never doubles up with a wrapper ring.
|
|
3686
|
+
"milkdown-host overflow-auto rounded-md border border-border bg-background text-foreground",
|
|
3598
3687
|
className
|
|
3599
3688
|
),
|
|
3600
3689
|
style: { ...markdownScaleVars(), ...style },
|
|
@@ -3745,4 +3834,4 @@ export {
|
|
|
3745
3834
|
CopyButton,
|
|
3746
3835
|
monacoContentAccess
|
|
3747
3836
|
};
|
|
3748
|
-
//# sourceMappingURL=chunk-
|
|
3837
|
+
//# sourceMappingURL=chunk-C62O7IOQ.js.map
|