@c2-markdown-ui/react 0.0.0-stage → 0.4.3
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 +111 -2
- package/dist/index.js +10085 -0
- package/package.json +44 -4
- package/src/index.ts +1 -0
- package/src/lib/MarkdownUI.tsx +111 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/widgets.css +2189 -0
- package/src/widgets/Button.tsx +39 -0
- package/src/widgets/ButtonGroup.tsx +47 -0
- package/src/widgets/Cascader.tsx +400 -0
- package/src/widgets/Chart.tsx +238 -0
- package/src/widgets/DatePicker.tsx +451 -0
- package/src/widgets/Form.tsx +512 -0
- package/src/widgets/Incomplete.tsx +55 -0
- package/src/widgets/MultipleChoiceQuestion.tsx +82 -0
- package/src/widgets/NumberInput.tsx +169 -0
- package/src/widgets/Quiz.tsx +320 -0
- package/src/widgets/Radio.tsx +177 -0
- package/src/widgets/Select.tsx +178 -0
- package/src/widgets/SelectMulti.tsx +105 -0
- package/src/widgets/ShortAnswerQuestion.tsx +101 -0
- package/src/widgets/Slider.tsx +71 -0
- package/src/widgets/Switch.tsx +68 -0
- package/src/widgets/TextInput.tsx +76 -0
- package/src/widgets/TreeSelect.tsx +649 -0
- package/src/widgets/Widget.tsx +131 -0
- package/src/widgets/chartRemoteData.ts +84 -0
- package/src/widgets/choiceUtils.ts +90 -0
- package/src/widgets/index.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c2-markdown-ui/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.3",
|
|
4
|
+
"description": "React renderer for markdown-ui",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./widgets.css": "./src/lib/widgets.css"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json && vite build",
|
|
21
|
+
"dev": "vite build --watch",
|
|
22
|
+
"demo": "node demo/server.js",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"react",
|
|
28
|
+
"markdown",
|
|
29
|
+
"widgets",
|
|
30
|
+
"ui"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
36
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/react": "^19.1.11",
|
|
40
|
+
"@types/react-dom": "^19.1.7"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {}
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useMemo } from 'react';
|
|
2
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
3
|
+
import { Widget } from '../widgets/Widget';
|
|
4
|
+
|
|
5
|
+
export interface MarkdownUIProps {
|
|
6
|
+
html: string;
|
|
7
|
+
/** 表单初始值:{ 字段id: 值 },作用于页面中所有 form widget(仅取各自 fields 声明的 id);优先级高于字段 default */
|
|
8
|
+
data?: Record<string, unknown>;
|
|
9
|
+
onWidgetEvent?: (event: CustomEvent<{id: string, value: unknown}>) => void;
|
|
10
|
+
/** 表单提交事件:form 校验通过并点击提交时触发(独立于 onWidgetEvent 的 change 流) */
|
|
11
|
+
onSubmit?: (event: CustomEvent<{id: string, value: unknown}>) => void;
|
|
12
|
+
/** 表单字段级统计事件:任一表单项被填写时触发,detail.stats 含 visibleCount / filledCount / values */
|
|
13
|
+
onFormChange?: (event: CustomEvent<{id: string, stats: {visibleCount: number, filledCount: number, values: Record<string, unknown>}}>) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 向 <markdown-ui-widget> 占位标签注入 data-widget-data 属性,把外部初始值带过 custom element 边界 */
|
|
17
|
+
function injectWidgetData(html: string, data?: Record<string, unknown>): string {
|
|
18
|
+
if (!data || Object.keys(data).length === 0) return html;
|
|
19
|
+
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
20
|
+
return html.replace(/<markdown-ui-widget\b/g, `<markdown-ui-widget data-widget-data="${encoded}"`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Custom element definition for markdown-ui-widget (browser only)
|
|
24
|
+
let MarkdownUIWidgetElement: typeof HTMLElement | undefined;
|
|
25
|
+
|
|
26
|
+
if (typeof HTMLElement !== 'undefined') {
|
|
27
|
+
MarkdownUIWidgetElement = class extends HTMLElement {
|
|
28
|
+
private reactRoot: Root | null = null;
|
|
29
|
+
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
const id = this.getAttribute('id') || '';
|
|
32
|
+
const content = this.getAttribute('content') || '';
|
|
33
|
+
const data = this.getAttribute('data-widget-data') || '';
|
|
34
|
+
|
|
35
|
+
console.log('🏗️ Custom element connected', { id, content });
|
|
36
|
+
|
|
37
|
+
// Create a React component instance
|
|
38
|
+
this.innerHTML = '';
|
|
39
|
+
const div = document.createElement('div');
|
|
40
|
+
this.appendChild(div);
|
|
41
|
+
|
|
42
|
+
// Use React 18+ createRoot API
|
|
43
|
+
this.reactRoot = createRoot(div);
|
|
44
|
+
this.reactRoot.render(React.createElement(Widget, { id, content, data }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
disconnectedCallback() {
|
|
48
|
+
if (this.reactRoot) {
|
|
49
|
+
this.reactRoot.unmount();
|
|
50
|
+
this.reactRoot = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Register the custom element if not already registered
|
|
56
|
+
if (typeof window !== 'undefined' && !customElements.get('markdown-ui-widget')) {
|
|
57
|
+
customElements.define('markdown-ui-widget', MarkdownUIWidgetElement);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const MarkdownUI: React.FC<MarkdownUIProps> = ({ html, data, onWidgetEvent, onSubmit, onFormChange }) => {
|
|
62
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
63
|
+
|
|
64
|
+
const processedHtml = useMemo(() => injectWidgetData(html, data), [html, data]);
|
|
65
|
+
|
|
66
|
+
// dangerouslySetInnerHTML 的 prop 对象必须引用稳定:React DOM 对该 prop 只做外层对象引用对比,
|
|
67
|
+
// 每次渲染新建 { __html } 会导致 innerHTML 全量重写 → custom element 重建 → 组件内部状态丢失
|
|
68
|
+
const innerHtmlProp = useMemo(() => ({ __html: processedHtml }), [processedHtml]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const handleWidgetEvent = (e: Event) => {
|
|
72
|
+
const customEvent = e as CustomEvent<{id: string, value: unknown}>;
|
|
73
|
+
if (onWidgetEvent) {
|
|
74
|
+
onWidgetEvent(customEvent);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleSubmit = (e: Event) => {
|
|
79
|
+
const customEvent = e as CustomEvent<{id: string, value: unknown}>;
|
|
80
|
+
if (onSubmit) {
|
|
81
|
+
onSubmit(customEvent);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleFormChange = (e: Event) => {
|
|
86
|
+
const customEvent = e as CustomEvent<{id: string, stats: {visibleCount: number, filledCount: number, values: Record<string, unknown>}}>;
|
|
87
|
+
if (onFormChange) {
|
|
88
|
+
onFormChange(customEvent);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const container = containerRef.current;
|
|
93
|
+
if (container) {
|
|
94
|
+
container.addEventListener('widget-event', handleWidgetEvent);
|
|
95
|
+
container.addEventListener('widget-submit', handleSubmit);
|
|
96
|
+
container.addEventListener('widget-form-change', handleFormChange);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
container.removeEventListener('widget-event', handleWidgetEvent);
|
|
100
|
+
container.removeEventListener('widget-submit', handleSubmit);
|
|
101
|
+
container.removeEventListener('widget-form-change', handleFormChange);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}, [onWidgetEvent, onSubmit, onFormChange]);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div ref={containerRef} className="widget-container">
|
|
108
|
+
<div dangerouslySetInnerHTML={innerHtmlProp} />
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
package/src/lib/index.ts
ADDED