@pactor-app/markdown 1.59.0 → 1.60.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 CHANGED
@@ -54,10 +54,12 @@ function Demo() {
54
54
  }
55
55
  ```
56
56
 
57
- 常用 props:`readOnly`(隐藏工具栏)、`toolbar={false}`、`placeholder`、`renderToolbar`(自定义工具栏内容)、`plugins`(追加 MDXEditor 插件)、`editorProps`(透传底层 props)。容器带基础 class `pactor-markdown-editor`。注意:编辑器体积较大且依赖浏览器 DOM API,SSR 场景请动态导入(如 `next/dynamic` 的 `ssr: false`)。
57
+ 常用 props:`readOnly`(隐藏工具栏)、`toolbar={false}`、`placeholder`、`theme`(`'light' | 'dark'`)、`renderToolbar`(自定义工具栏内容)、`plugins`(追加 MDXEditor 插件)、`editorProps`(透传底层 props)。容器带基础 class `pactor-markdown-editor`。注意:编辑器体积较大且依赖浏览器 DOM API,SSR 场景请动态导入(如 `next/dynamic` 的 `ssr: false`)。
58
58
 
59
59
  `value` 为受控语义:用户输入经 `onChange` 回流不重复导入(光标不跳动);外部变更(如 DSL state 回写)经 `setMarkdown` 同步进编辑器。
60
60
 
61
+ 明暗模式:`theme` 缺省跟随 `documentElement` 上的 `data-pactor-theme`(pactor 换肤约定)并监听其变化自动切换,也可显式传 `'light' | 'dark'` 固定。主题 class 会同时应用到编辑根节点与 portal 弹层的 `.mdxeditor-popup-container`(工具栏下拉、链接弹层),且原地切换、不重建弹层容器——打开中的下拉不会因换肤丢失;容器上还带 `data-theme` 属性便于宿主样式覆盖。
62
+
61
63
  ## DSL 使用
62
64
 
63
65
  ```tsx
@@ -92,7 +94,7 @@ events:
92
94
  draft: '${event}'
93
95
  ```
94
96
 
95
- 支持 props:`value` / `placeholder` / `readOnly` / `toolbar` / `autoFocus` / `className` / `style`。注意:编辑器依赖浏览器 DOM API 与 `@mdxeditor/editor/style.css`,仅适合 Page DSL(浏览器页面),不要注册进 Chat 的 Safe Registry。
97
+ 支持 props:`value` / `placeholder` / `readOnly` / `toolbar` / `autoFocus` / `theme` / `className` / `style`。`theme` 与组件用法一致:缺省跟随 `data-pactor-theme`,也可在 YAML 中写 `theme: dark` 固定。注意:编辑器依赖浏览器 DOM API 与 `@mdxeditor/editor/style.css`,仅适合 Page DSL(浏览器页面),不要注册进 Chat 的 Safe Registry。
96
98
 
97
99
  ## 文档
98
100
 
package/dist/index.cjs CHANGED
@@ -240,6 +240,20 @@ function MarkdownDslComponent({
240
240
  var import_react3 = require("react");
241
241
  var import_editor = require("@mdxeditor/editor");
242
242
  var import_jsx_runtime4 = require("react/jsx-runtime");
243
+ function usePactorTheme() {
244
+ const read = () => typeof document !== "undefined" && document.documentElement.getAttribute("data-pactor-theme") === "dark" ? "dark" : "light";
245
+ const [theme, setTheme] = (0, import_react3.useState)(read);
246
+ (0, import_react3.useEffect)(() => {
247
+ if (typeof document === "undefined") return;
248
+ const observer = new MutationObserver(() => setTheme(read()));
249
+ observer.observe(document.documentElement, {
250
+ attributes: true,
251
+ attributeFilter: ["data-pactor-theme"]
252
+ });
253
+ return () => observer.disconnect();
254
+ }, []);
255
+ return theme;
256
+ }
243
257
  var defaultToolbarContents = /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
244
258
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_editor.UndoRedo, {}),
245
259
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_editor.Separator, {}),
@@ -265,6 +279,7 @@ var MarkdownEditor = (0, import_react3.forwardRef)(
265
279
  autoFocus,
266
280
  className,
267
281
  style,
282
+ theme,
268
283
  toolbar = true,
269
284
  renderToolbar,
270
285
  plugins,
@@ -272,6 +287,38 @@ var MarkdownEditor = (0, import_react3.forwardRef)(
272
287
  }, ref) {
273
288
  const showToolbar = toolbar && !readOnly;
274
289
  const containerClass = className ? `pactor-markdown-editor ${className}` : "pactor-markdown-editor";
290
+ const pactorTheme = usePactorTheme();
291
+ const resolvedTheme = theme ?? pactorTheme;
292
+ const instanceId = (0, import_react3.useId)().replace(/[^a-zA-Z0-9_-]/g, "");
293
+ const instanceClass = `pactor-markdown-editor-instance-${instanceId}`;
294
+ const mdxEditorClassName = [
295
+ "pactor-markdown-editor-root",
296
+ instanceClass,
297
+ editorProps?.className
298
+ ].filter(Boolean).join(" ");
299
+ (0, import_react3.useEffect)(() => {
300
+ if (typeof document === "undefined") return;
301
+ const selector = `.${instanceClass}`;
302
+ const applyTheme = () => {
303
+ document.querySelectorAll(selector).forEach((element) => {
304
+ element.classList.toggle("dark", resolvedTheme === "dark");
305
+ element.classList.toggle("light", resolvedTheme === "light");
306
+ });
307
+ };
308
+ applyTheme();
309
+ const observer = new MutationObserver((records) => {
310
+ for (const record of records) {
311
+ for (const node of record.addedNodes) {
312
+ if (node instanceof Element && (node.matches(selector) || node.querySelector(selector))) {
313
+ applyTheme();
314
+ return;
315
+ }
316
+ }
317
+ }
318
+ });
319
+ observer.observe(document.body, { childList: true, subtree: true });
320
+ return () => observer.disconnect();
321
+ }, [instanceClass, resolvedTheme]);
275
322
  const innerRef = (0, import_react3.useRef)(null);
276
323
  (0, import_react3.useImperativeHandle)(ref, () => innerRef.current, []);
277
324
  const lastEmittedRef = (0, import_react3.useRef)(value);
@@ -285,7 +332,7 @@ var MarkdownEditor = (0, import_react3.forwardRef)(
285
332
  lastEmittedRef.current = next;
286
333
  onChange?.(next);
287
334
  };
288
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: containerClass, style, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
335
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: containerClass, style, "data-theme": resolvedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
289
336
  import_editor.MDXEditor,
290
337
  {
291
338
  ref: innerRef,
@@ -331,7 +378,8 @@ var MarkdownEditor = (0, import_react3.forwardRef)(
331
378
  ] : [],
332
379
  ...plugins ?? []
333
380
  ],
334
- ...editorProps
381
+ ...editorProps,
382
+ className: mdxEditorClassName
335
383
  }
336
384
  ) });
337
385
  }
@@ -347,7 +395,8 @@ function MarkdownEditorDslComponent({
347
395
  autoFocus,
348
396
  onChange,
349
397
  className,
350
- style
398
+ style,
399
+ theme
351
400
  }) {
352
401
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
353
402
  MarkdownEditor,
@@ -359,7 +408,8 @@ function MarkdownEditorDslComponent({
359
408
  autoFocus,
360
409
  onChange,
361
410
  className,
362
- style
411
+ style,
412
+ theme
363
413
  }
364
414
  );
365
415
  }
package/dist/index.d.cts CHANGED
@@ -91,6 +91,8 @@ interface MarkdownEditorProps {
91
91
  className?: string;
92
92
  /** 通用:根节点内联样式 */
93
93
  style?: CSSProperties;
94
+ /** 主题:缺省跟随 documentElement 的 data-pactor-theme(与库换肤机制一致),可显式覆盖 */
95
+ theme?: 'light' | 'dark';
94
96
  /** 是否渲染工具栏,默认 true(readOnly 时强制隐藏) */
95
97
  toolbar?: boolean;
96
98
  /** 自定义工具栏内容:参数为默认工具栏节点 */
@@ -141,12 +143,14 @@ interface MarkdownEditorDslComponentProps {
141
143
  className?: string;
142
144
  /** 通用:根节点内联样式 */
143
145
  style?: CSSProperties;
146
+ /** 主题:缺省跟随 data-pactor-theme,可显式覆盖 */
147
+ theme?: 'light' | 'dark';
144
148
  }
145
149
  /**
146
150
  * DSL 组件适配:`{ type: 'MarkdownEditor', props: { value }, events: { onChange } }`。
147
151
  * 仅做 props 透传,编辑能力全部委托给 MarkdownEditor。
148
152
  * 典型用法:value 绑定 state 表达式,onChange action 把 `${event}` 写回同一 state。
149
153
  */
150
- declare function MarkdownEditorDslComponent({ value, placeholder, readOnly, toolbar, autoFocus, onChange, className, style, }: MarkdownEditorDslComponentProps): react.JSX.Element;
154
+ declare function MarkdownEditorDslComponent({ value, placeholder, readOnly, toolbar, autoFocus, onChange, className, style, theme, }: MarkdownEditorDslComponentProps): react.JSX.Element;
151
155
 
152
156
  export { CodeBlock, type CodeBlockProps, MARKDOWN_DSL_TYPE, MARKDOWN_EDITOR_DSL_TYPE, MarkdownDslComponent, type MarkdownDslComponentProps, MarkdownEditor, MarkdownEditorDslComponent, type MarkdownEditorDslComponentProps, type MarkdownEditorProps, type MarkdownEditorToolbarRender, MarkdownRenderer, type MarkdownRendererProps, isSafeUrl, registerCodeLanguages, registerMarkdownComponents };
package/dist/index.d.ts CHANGED
@@ -91,6 +91,8 @@ interface MarkdownEditorProps {
91
91
  className?: string;
92
92
  /** 通用:根节点内联样式 */
93
93
  style?: CSSProperties;
94
+ /** 主题:缺省跟随 documentElement 的 data-pactor-theme(与库换肤机制一致),可显式覆盖 */
95
+ theme?: 'light' | 'dark';
94
96
  /** 是否渲染工具栏,默认 true(readOnly 时强制隐藏) */
95
97
  toolbar?: boolean;
96
98
  /** 自定义工具栏内容:参数为默认工具栏节点 */
@@ -141,12 +143,14 @@ interface MarkdownEditorDslComponentProps {
141
143
  className?: string;
142
144
  /** 通用:根节点内联样式 */
143
145
  style?: CSSProperties;
146
+ /** 主题:缺省跟随 data-pactor-theme,可显式覆盖 */
147
+ theme?: 'light' | 'dark';
144
148
  }
145
149
  /**
146
150
  * DSL 组件适配:`{ type: 'MarkdownEditor', props: { value }, events: { onChange } }`。
147
151
  * 仅做 props 透传,编辑能力全部委托给 MarkdownEditor。
148
152
  * 典型用法:value 绑定 state 表达式,onChange action 把 `${event}` 写回同一 state。
149
153
  */
150
- declare function MarkdownEditorDslComponent({ value, placeholder, readOnly, toolbar, autoFocus, onChange, className, style, }: MarkdownEditorDslComponentProps): react.JSX.Element;
154
+ declare function MarkdownEditorDslComponent({ value, placeholder, readOnly, toolbar, autoFocus, onChange, className, style, theme, }: MarkdownEditorDslComponentProps): react.JSX.Element;
151
155
 
152
156
  export { CodeBlock, type CodeBlockProps, MARKDOWN_DSL_TYPE, MARKDOWN_EDITOR_DSL_TYPE, MarkdownDslComponent, type MarkdownDslComponentProps, MarkdownEditor, MarkdownEditorDslComponent, type MarkdownEditorDslComponentProps, type MarkdownEditorProps, type MarkdownEditorToolbarRender, MarkdownRenderer, type MarkdownRendererProps, isSafeUrl, registerCodeLanguages, registerMarkdownComponents };
package/dist/index.js CHANGED
@@ -192,7 +192,7 @@ function MarkdownDslComponent({
192
192
  }
193
193
 
194
194
  // src/editor.tsx
195
- import { forwardRef, useEffect as useEffect2, useImperativeHandle, useRef } from "react";
195
+ import { forwardRef, useEffect as useEffect2, useId, useImperativeHandle, useRef, useState as useState2 } from "react";
196
196
  import {
197
197
  BlockTypeSelect,
198
198
  BoldItalicUnderlineToggles,
@@ -218,6 +218,20 @@ import {
218
218
  UndoRedo
219
219
  } from "@mdxeditor/editor";
220
220
  import { Fragment as Fragment2, jsx as jsx5, jsxs } from "react/jsx-runtime";
221
+ function usePactorTheme() {
222
+ const read = () => typeof document !== "undefined" && document.documentElement.getAttribute("data-pactor-theme") === "dark" ? "dark" : "light";
223
+ const [theme, setTheme] = useState2(read);
224
+ useEffect2(() => {
225
+ if (typeof document === "undefined") return;
226
+ const observer = new MutationObserver(() => setTheme(read()));
227
+ observer.observe(document.documentElement, {
228
+ attributes: true,
229
+ attributeFilter: ["data-pactor-theme"]
230
+ });
231
+ return () => observer.disconnect();
232
+ }, []);
233
+ return theme;
234
+ }
221
235
  var defaultToolbarContents = /* @__PURE__ */ jsxs(Fragment2, { children: [
222
236
  /* @__PURE__ */ jsx5(UndoRedo, {}),
223
237
  /* @__PURE__ */ jsx5(Separator, {}),
@@ -243,6 +257,7 @@ var MarkdownEditor = forwardRef(
243
257
  autoFocus,
244
258
  className,
245
259
  style,
260
+ theme,
246
261
  toolbar = true,
247
262
  renderToolbar,
248
263
  plugins,
@@ -250,6 +265,38 @@ var MarkdownEditor = forwardRef(
250
265
  }, ref) {
251
266
  const showToolbar = toolbar && !readOnly;
252
267
  const containerClass = className ? `pactor-markdown-editor ${className}` : "pactor-markdown-editor";
268
+ const pactorTheme = usePactorTheme();
269
+ const resolvedTheme = theme ?? pactorTheme;
270
+ const instanceId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
271
+ const instanceClass = `pactor-markdown-editor-instance-${instanceId}`;
272
+ const mdxEditorClassName = [
273
+ "pactor-markdown-editor-root",
274
+ instanceClass,
275
+ editorProps?.className
276
+ ].filter(Boolean).join(" ");
277
+ useEffect2(() => {
278
+ if (typeof document === "undefined") return;
279
+ const selector = `.${instanceClass}`;
280
+ const applyTheme = () => {
281
+ document.querySelectorAll(selector).forEach((element) => {
282
+ element.classList.toggle("dark", resolvedTheme === "dark");
283
+ element.classList.toggle("light", resolvedTheme === "light");
284
+ });
285
+ };
286
+ applyTheme();
287
+ const observer = new MutationObserver((records) => {
288
+ for (const record of records) {
289
+ for (const node of record.addedNodes) {
290
+ if (node instanceof Element && (node.matches(selector) || node.querySelector(selector))) {
291
+ applyTheme();
292
+ return;
293
+ }
294
+ }
295
+ }
296
+ });
297
+ observer.observe(document.body, { childList: true, subtree: true });
298
+ return () => observer.disconnect();
299
+ }, [instanceClass, resolvedTheme]);
253
300
  const innerRef = useRef(null);
254
301
  useImperativeHandle(ref, () => innerRef.current, []);
255
302
  const lastEmittedRef = useRef(value);
@@ -263,7 +310,7 @@ var MarkdownEditor = forwardRef(
263
310
  lastEmittedRef.current = next;
264
311
  onChange?.(next);
265
312
  };
266
- return /* @__PURE__ */ jsx5("div", { className: containerClass, style, children: /* @__PURE__ */ jsx5(
313
+ return /* @__PURE__ */ jsx5("div", { className: containerClass, style, "data-theme": resolvedTheme, children: /* @__PURE__ */ jsx5(
267
314
  MDXEditor,
268
315
  {
269
316
  ref: innerRef,
@@ -309,7 +356,8 @@ var MarkdownEditor = forwardRef(
309
356
  ] : [],
310
357
  ...plugins ?? []
311
358
  ],
312
- ...editorProps
359
+ ...editorProps,
360
+ className: mdxEditorClassName
313
361
  }
314
362
  ) });
315
363
  }
@@ -325,7 +373,8 @@ function MarkdownEditorDslComponent({
325
373
  autoFocus,
326
374
  onChange,
327
375
  className,
328
- style
376
+ style,
377
+ theme
329
378
  }) {
330
379
  return /* @__PURE__ */ jsx6(
331
380
  MarkdownEditor,
@@ -337,7 +386,8 @@ function MarkdownEditorDslComponent({
337
386
  autoFocus,
338
387
  onChange,
339
388
  className,
340
- style
389
+ style,
390
+ theme
341
391
  }
342
392
  );
343
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactor-app/markdown",
3
- "version": "1.59.0",
3
+ "version": "1.60.0",
4
4
  "description": "Pactor Markdown module: safe Markdown renderer (react-markdown AST, no raw HTML) and DSL Markdown component adapter",
5
5
  "keywords": [
6
6
  "pactor",
@@ -55,7 +55,7 @@
55
55
  "rehype-katex": "^7.0.1",
56
56
  "remark-gfm": "^4.0.1",
57
57
  "remark-math": "^6.0.0",
58
- "@pactor-app/core": "1.59.0"
58
+ "@pactor-app/core": "1.60.0"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "react": ">=18"
@@ -69,7 +69,7 @@
69
69
  "jsdom": "^30.0.1",
70
70
  "react": "^19.2.8",
71
71
  "react-dom": "^19.2.8",
72
- "@pactor-app/runtime": "1.59.0"
72
+ "@pactor-app/runtime": "1.60.0"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "tsup",
package/styles.css CHANGED
@@ -290,3 +290,42 @@
290
290
  min-height: 8em;
291
291
  outline: none;
292
292
  }
293
+
294
+ /**
295
+ * 明暗主题:`pactor-markdown-editor-root` 由组件同时挂到 MDXEditor 的编辑
296
+ * 根节点与 portal 的 `.mdxeditor-popup-container`(下拉 / 链接弹层)。
297
+ * 组件按 data-pactor-theme(或显式 theme)追加 `.light` / `.dark`,
298
+ * 激活 MDXEditor 内置的 Radix 令牌组;这里再把其基础令牌接到宿主语义令牌,
299
+ * 使画布 / 工具栏 / 弹层与宿主主题同色(无宿主令牌时回退 Radix 色阶)。
300
+ * 选择器带元素名提升优先级,确保覆盖 MDXEditor 自身的 ._editorRoot_* 声明。
301
+ */
302
+ div.pactor-markdown-editor-root.mdxeditor,
303
+ div.pactor-markdown-editor-root.mdxeditor-popup-container {
304
+ --basePageBg: var(--card, var(--slate-1));
305
+ --baseBase: var(--card, var(--slate-1));
306
+ --baseBgSubtle: color-mix(in srgb, var(--foreground, var(--slate-12)) 5%, var(--card, var(--slate-1)));
307
+ --baseBg: color-mix(in srgb, var(--foreground, var(--slate-12)) 8%, var(--card, var(--slate-1)));
308
+ --baseBgHover: color-mix(in srgb, var(--foreground, var(--slate-12)) 11%, var(--card, var(--slate-1)));
309
+ --baseBgActive: color-mix(in srgb, var(--foreground, var(--slate-12)) 15%, var(--card, var(--slate-1)));
310
+ --baseLine: var(--border, var(--slate-6));
311
+ --baseBorder: var(--border, var(--slate-7));
312
+ --baseBorderHover: color-mix(in srgb, var(--foreground, var(--slate-12)) 24%, var(--border, var(--slate-7)));
313
+ --baseSolid: var(--muted-foreground, var(--slate-9));
314
+ --baseSolidHover: var(--foreground, var(--slate-10));
315
+ --baseText: var(--muted-foreground, var(--slate-11));
316
+ --baseTextContrast: var(--foreground, var(--slate-12));
317
+
318
+ /* 弹层脱离编辑根节点,需自带强调色令牌(与 MDXEditor 默认同一色阶) */
319
+ --accentBase: var(--blue-1);
320
+ --accentBgSubtle: var(--blue-2);
321
+ --accentBg: var(--blue-3);
322
+ --accentBgHover: var(--blue-4);
323
+ --accentBgActive: var(--blue-5);
324
+ --accentLine: var(--blue-6);
325
+ --accentBorder: var(--blue-7);
326
+ --accentBorderHover: var(--blue-8);
327
+ --accentSolid: var(--blue-9);
328
+ --accentSolidHover: var(--blue-10);
329
+ --accentText: var(--blue-11);
330
+ --accentTextContrast: var(--blue-12);
331
+ }