@c2-markdown-ui/react 0.4.3 → 0.4.4

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 (73) hide show
  1. package/README.md +6 -24
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +3733 -4365
  5. package/dist/lib/MarkdownUI.d.ts +10 -0
  6. package/dist/lib/MarkdownUI.d.ts.map +1 -0
  7. package/dist/lib/index.d.ts +4 -0
  8. package/dist/lib/index.d.ts.map +1 -0
  9. package/dist/widgets/Button.d.ts +15 -0
  10. package/dist/widgets/Button.d.ts.map +1 -0
  11. package/dist/widgets/ButtonGroup.d.ts +11 -0
  12. package/dist/widgets/ButtonGroup.d.ts.map +1 -0
  13. package/dist/widgets/Cascader.d.ts +34 -0
  14. package/dist/widgets/Cascader.d.ts.map +1 -0
  15. package/dist/widgets/Chart.d.ts +20 -0
  16. package/dist/widgets/Chart.d.ts.map +1 -0
  17. package/dist/widgets/DatePicker.d.ts +28 -0
  18. package/dist/widgets/DatePicker.d.ts.map +1 -0
  19. package/dist/widgets/Form.d.ts +47 -0
  20. package/dist/widgets/Form.d.ts.map +1 -0
  21. package/dist/widgets/Incomplete.d.ts +3 -0
  22. package/dist/widgets/Incomplete.d.ts.map +1 -0
  23. package/dist/widgets/MultipleChoiceQuestion.d.ts +15 -0
  24. package/dist/widgets/MultipleChoiceQuestion.d.ts.map +1 -0
  25. package/dist/widgets/NumberInput.d.ts +26 -0
  26. package/dist/widgets/NumberInput.d.ts.map +1 -0
  27. package/dist/widgets/Quiz.d.ts +37 -0
  28. package/dist/widgets/Quiz.d.ts.map +1 -0
  29. package/dist/widgets/Radio.d.ts +24 -0
  30. package/dist/widgets/Radio.d.ts.map +1 -0
  31. package/dist/widgets/Select.d.ts +15 -0
  32. package/dist/widgets/Select.d.ts.map +1 -0
  33. package/dist/widgets/SelectMulti.d.ts +20 -0
  34. package/dist/widgets/SelectMulti.d.ts.map +1 -0
  35. package/dist/widgets/ShortAnswerQuestion.d.ts +16 -0
  36. package/dist/widgets/ShortAnswerQuestion.d.ts.map +1 -0
  37. package/dist/widgets/Slider.d.ts +13 -0
  38. package/dist/widgets/Slider.d.ts.map +1 -0
  39. package/dist/widgets/Switch.d.ts +17 -0
  40. package/dist/widgets/Switch.d.ts.map +1 -0
  41. package/dist/widgets/TextInput.d.ts +11 -0
  42. package/dist/widgets/TextInput.d.ts.map +1 -0
  43. package/dist/widgets/Widget.d.ts +7 -0
  44. package/dist/widgets/Widget.d.ts.map +1 -0
  45. package/dist/widgets/chartRemoteData.d.ts +18 -0
  46. package/dist/widgets/chartRemoteData.d.ts.map +1 -0
  47. package/dist/widgets/choiceUtils.d.ts +24 -0
  48. package/dist/widgets/choiceUtils.d.ts.map +1 -0
  49. package/dist/widgets/index.d.ts +18 -0
  50. package/dist/widgets/index.d.ts.map +1 -0
  51. package/package.json +2 -2
  52. package/src/lib/MarkdownUI.tsx +9 -47
  53. package/src/lib/widgets.css +1372 -2188
  54. package/src/widgets/Button.tsx +39 -39
  55. package/src/widgets/ButtonGroup.tsx +47 -47
  56. package/src/widgets/Cascader.tsx +2 -3
  57. package/src/widgets/Chart.tsx +237 -237
  58. package/src/widgets/DatePicker.tsx +434 -451
  59. package/src/widgets/Form.tsx +372 -512
  60. package/src/widgets/Incomplete.tsx +3 -3
  61. package/src/widgets/MultipleChoiceQuestion.tsx +82 -82
  62. package/src/widgets/Quiz.tsx +320 -320
  63. package/src/widgets/Radio.tsx +26 -57
  64. package/src/widgets/Select.tsx +142 -178
  65. package/src/widgets/SelectMulti.tsx +72 -105
  66. package/src/widgets/ShortAnswerQuestion.tsx +101 -101
  67. package/src/widgets/Slider.tsx +56 -70
  68. package/src/widgets/Switch.tsx +2 -14
  69. package/src/widgets/TextInput.tsx +38 -76
  70. package/src/widgets/Widget.tsx +82 -130
  71. package/src/widgets/choiceUtils.ts +45 -90
  72. package/src/widgets/index.ts +16 -17
  73. package/src/widgets/TreeSelect.tsx +0 -649
@@ -1,131 +1,83 @@
1
- import React, { useEffect } from 'react';
2
- import * as widgets from './index';
3
-
4
- export interface WidgetProps {
5
- id: string;
6
- content: string;
7
- /** 外部传入的表单初始值(encodeURIComponent 后的 JSON 字符串),由 MarkdownUI 注入 */
8
- data?: string;
9
- }
10
-
11
- type WidgetType = keyof typeof widgets;
12
-
13
- // Map camelCase JSON types to PascalCase component names
14
- const typeMapping: Record<string, WidgetType> = {
15
- 'button': 'Button',
16
- 'button-group': 'ButtonGroup',
17
- 'cascade': 'Cascader',
18
- 'chart-line': 'Chart',
19
- 'chart-bar': 'Chart',
20
- 'chart-pie': 'Chart',
21
- 'chart-scatter': 'Chart',
22
- 'date-picker': 'DatePicker',
23
- 'form': 'Form',
24
- 'incomplete': 'Incomplete',
25
- 'multiple-choice-question': 'MultipleChoiceQuestion',
26
- 'number-input': 'NumberInput',
27
- 'quiz': 'Quiz',
28
- 'radio-group': 'RadioGroup',
29
- 'select': 'Select',
30
- 'select-multi': 'SelectMulti',
31
- 'tree-select': 'TreeSelect',
32
- 'short-answer-question': 'ShortAnswerQuestion',
33
- 'slider': 'Slider',
34
- 'switch': 'Switch',
35
- 'text-input': 'TextInput'
36
- };
37
-
38
- export const Widget: React.FC<WidgetProps> = ({ id, content, data }) => {
39
- const parsed = JSON.parse(decodeURIComponent(content));
40
- const componentType = typeMapping[parsed.type] || parsed.type;
41
- const WidgetComponent = widgets[componentType as WidgetType];
42
-
43
- // 解析外部注入的表单初始值(容错:非法 JSON 时忽略)
44
- let initialData: Record<string, unknown> | undefined;
45
- if (data) {
46
- try {
47
- initialData = JSON.parse(decodeURIComponent(data));
48
- } catch {
49
- initialData = undefined;
50
- }
51
- }
52
-
53
- const dispatch = (detail: any) => {
54
- console.log('🔥 Widget dispatch called', { id, detail });
55
-
56
- const event = new CustomEvent('widget-event', {
57
- detail: detail,
58
- bubbles: true,
59
- composed: true
60
- });
61
-
62
- // Find the custom element and bubble up to find the closest widget container
63
- const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`);
64
- console.log('🔍 Custom element found:', customElement);
65
-
66
- if (customElement) {
67
- const container = customElement.closest('.widget-container');
68
- console.log('📦 Container found:', container);
69
- if (container) {
70
- console.log('📤 Dispatching event on container');
71
- container.dispatchEvent(event);
72
- }
73
- } else {
74
- console.log('❌ No custom element found for id:', id);
75
- }
76
- };
77
-
78
- /** form 提交事件:独立于 widget-event,通过 widget-submit 派发 */
79
- const dispatchSubmit = (value: any) => {
80
- const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`);
81
- if (customElement) {
82
- const container = customElement.closest('.widget-container');
83
- if (container) {
84
- container.dispatchEvent(new CustomEvent('widget-submit', {
85
- detail: { id, value },
86
- bubbles: true,
87
- composed: true
88
- }));
89
- }
90
- }
91
- };
92
-
93
- /** form 字段级统计事件:任一表单项被填写时通过 widget-form-change 派发 */
94
- const dispatchFieldChange = (stats: any) => {
95
- const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`);
96
- if (customElement) {
97
- const container = customElement.closest('.widget-container');
98
- if (container) {
99
- container.dispatchEvent(new CustomEvent('widget-form-change', {
100
- detail: { id, stats },
101
- bubbles: true,
102
- composed: true
103
- }));
104
- }
105
- }
106
- };
107
-
108
- if (!WidgetComponent) {
109
- return (
110
- <div className="widget">
111
- <span style={{ color: 'red' }}>
112
- Unknown widget "{parsed.type}"
113
- </span>
114
- </div>
115
- );
116
- }
117
-
118
- return (
119
- <div className="widget">
120
- <WidgetComponent
121
- {...parsed}
122
- initialData={initialData}
123
- onchange={(value: any) => dispatch({ id, value })}
124
- {...(parsed.type === 'form' ? {
125
- onsubmit: (values: any) => dispatchSubmit(values),
126
- onfieldchange: (stats: any) => dispatchFieldChange(stats)
127
- } : {})}
128
- />
129
- </div>
130
- );
1
+ import React, { useEffect } from 'react';
2
+ import * as widgets from './index';
3
+
4
+ export interface WidgetProps {
5
+ id: string;
6
+ content: string;
7
+ }
8
+
9
+ type WidgetType = keyof typeof widgets;
10
+
11
+ // Map camelCase JSON types to PascalCase component names
12
+ const typeMapping: Record<string, WidgetType> = {
13
+ 'button': 'Button',
14
+ 'button-group': 'ButtonGroup',
15
+ 'cascade': 'Cascader',
16
+ 'chart-line': 'Chart',
17
+ 'chart-bar': 'Chart',
18
+ 'chart-pie': 'Chart',
19
+ 'chart-scatter': 'Chart',
20
+ 'date-picker': 'DatePicker',
21
+ 'form': 'Form',
22
+ 'incomplete': 'Incomplete',
23
+ 'multiple-choice-question': 'MultipleChoiceQuestion',
24
+ 'number-input': 'NumberInput',
25
+ 'quiz': 'Quiz',
26
+ 'radio-group': 'RadioGroup',
27
+ 'select': 'Select',
28
+ 'select-multi': 'SelectMulti',
29
+ 'short-answer-question': 'ShortAnswerQuestion',
30
+ 'slider': 'Slider',
31
+ 'switch': 'Switch',
32
+ 'text-input': 'TextInput'
33
+ };
34
+
35
+ export const Widget: React.FC<WidgetProps> = ({ id, content }) => {
36
+ const parsed = JSON.parse(decodeURIComponent(content));
37
+ const componentType = typeMapping[parsed.type] || parsed.type;
38
+ const WidgetComponent = widgets[componentType as WidgetType];
39
+
40
+ const dispatch = (detail: any) => {
41
+ console.log('🔥 Widget dispatch called', { id, detail });
42
+
43
+ const event = new CustomEvent('widget-event', {
44
+ detail: detail,
45
+ bubbles: true,
46
+ composed: true
47
+ });
48
+
49
+ // Find the custom element and bubble up to find the closest widget container
50
+ const customElement = document.querySelector(`markdown-ui-widget[id="${id}"]`);
51
+ console.log('🔍 Custom element found:', customElement);
52
+
53
+ if (customElement) {
54
+ const container = customElement.closest('.widget-container');
55
+ console.log('📦 Container found:', container);
56
+ if (container) {
57
+ console.log('📤 Dispatching event on container');
58
+ container.dispatchEvent(event);
59
+ }
60
+ } else {
61
+ console.log('❌ No custom element found for id:', id);
62
+ }
63
+ };
64
+
65
+ if (!WidgetComponent) {
66
+ return (
67
+ <div className="widget">
68
+ <span style={{ color: 'red' }}>
69
+ Unknown widget "{parsed.type}"
70
+ </span>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ return (
76
+ <div className="widget">
77
+ <WidgetComponent
78
+ {...parsed}
79
+ onchange={(value: any) => dispatch({ id, value })}
80
+ />
81
+ </div>
82
+ );
131
83
  };
@@ -1,90 +1,45 @@
1
- /** 选项条目:value 是表单实际取值(key),label 是展示文案 */
2
- export interface ChoiceItem {
3
- value: string;
4
- label: string;
5
- }
6
-
7
- /**
8
- * 解析本地 choices 条目的 key:label 格式:
9
- * - "bj:北京" → {value: "bj", label: "北京"}(value 是表单实际取值,label 是展示文案)
10
- * - "北京" → {value: "北京", label: "北京"}(无冒号或不符合判定规则时视为纯文本)
11
- * - "09:00" → {value: "09:00", label: "09:00"}(纯数字 key 视为时间/比例值,保持原样)
12
- * - "http://x" → 原样(label 以 // 开头视为 URL)
13
- *
14
- * 判定规则:按首个冒号分割,仅当 key 非空且不含空白、非纯数字,
15
- * 且 label 非空、不以 // 开头时才视为 key:label。
16
- */
17
- export function parseChoice(c: string): ChoiceItem {
18
- const s = String(c ?? '');
19
- const idx = s.indexOf(':');
20
- if (idx > 0) {
21
- const key = s.slice(0, idx);
22
- const label = s.slice(idx + 1);
23
- if (key && label && !/\s/.test(key) && !/^\d+$/.test(key) && !label.startsWith('//')) {
24
- return { value: key, label };
25
- }
26
- }
27
- return { value: s, label: s };
28
- }
29
-
30
- /** 把本地 choices(string[] 或对象数组)解析为 {value,label} 数组。
31
- * 对象条目:value > key 取值(表单实际取值),label 缺省回落到 value */
32
- export function parseChoices(choices: string[] | Array<{ value?: string; key?: string; label?: string }> | undefined | null): ChoiceItem[] {
33
- return (choices || []).map((c) => {
34
- if (c !== null && typeof c === 'object') {
35
- const value = (c as any).value ?? (c as any).key;
36
- if (value === undefined || value === null || value === '') return null;
37
- const valueStr = String(value);
38
- const label = (c as any).label !== undefined && (c as any).label !== null && (c as any).label !== ''
39
- ? String((c as any).label)
40
- : valueStr;
41
- return { value: valueStr, label };
42
- }
43
- return parseChoice(c as string);
44
- }).filter((c: ChoiceItem | null): c is ChoiceItem => c !== null);
45
- }
46
-
47
- /**
48
- * 兼容常见远端响应格式并归一化为 {value,label}:
49
- * - ["测试"] → value=label="测试"
50
- * - [{key:"test",label:"测试"}] → value="test"(表单取值),label="测试"(展示)
51
- * - [{value:"test",label:"测试"}] → value="test",label="测试"
52
- * - {data:[...]} / {options:[...]} / {choices:[...]} / {items:[...]} → 取内嵌数组
53
- *
54
- * 对象条目取值优先级:value > key > id > name;展示优先级:label > name > text > value。
55
- */
56
- export function normalizeRemoteChoices(json: any): ChoiceItem[] {
57
- let list = json;
58
- if (!Array.isArray(json)) {
59
- list = Array.isArray(json?.data) ? json.data
60
- : Array.isArray(json?.options) ? json.options
61
- : Array.isArray(json?.choices) ? json.choices
62
- : Array.isArray(json?.items) ? json.items
63
- : [];
64
- }
65
- return (list || [])
66
- .map((item: any): ChoiceItem | null => {
67
- if (typeof item === 'string' || typeof item === 'number') {
68
- const s = String(item);
69
- return s ? { value: s, label: s } : null;
70
- }
71
- if (item && typeof item === 'object') {
72
- const value = item.value ?? item.key ?? item.id ?? item.name;
73
- if (value === undefined || value === null || value === '') return null;
74
- const label = item.label ?? item.name ?? item.text ?? String(value);
75
- return { value: String(value), label: String(label) };
76
- }
77
- return null;
78
- })
79
- .filter((c: ChoiceItem | null): c is ChoiceItem => c !== null);
80
- }
81
-
82
- /**
83
- * 把本地 choices / default 值(可能写的是展示文案或 key:label 原文)映射到远端 value 体系:
84
- * 条目本身已是 value 优先,字符串则用 label 匹配远端条目回查 value。
85
- */
86
- export function resolveChoiceValue(c: string, remote: ChoiceItem[]): string {
87
- const parsed = parseChoice(c).value;
88
- const hit = remote.find(r => r.value === c || r.label === c || r.value === parsed);
89
- return hit ? hit.value : c;
90
- }
1
+ /** 选项条目:value 是表单实际取值(key),label 是展示文案 */
2
+ export interface ChoiceItem {
3
+ value: string;
4
+ label: string;
5
+ }
6
+
7
+ /**
8
+ * 解析本地 choices 条目的 key:label 格式:
9
+ * - "bj:北京" → {value: "bj", label: "北京"}(value 是表单实际取值,label 是展示文案)
10
+ * - "北京" → {value: "北京", label: "北京"}(无冒号或不符合判定规则时视为纯文本)
11
+ * - "09:00" → {value: "09:00", label: "09:00"}(纯数字 key 视为时间/比例值,保持原样)
12
+ * - "http://x" → 原样(label 以 // 开头视为 URL)
13
+ *
14
+ * 判定规则:按首个冒号分割,仅当 key 非空且不含空白、非纯数字,
15
+ * 且 label 非空、不以 // 开头时才视为 key:label。
16
+ */
17
+ export function parseChoice(c: string): ChoiceItem {
18
+ const s = String(c ?? '');
19
+ const idx = s.indexOf(':');
20
+ if (idx > 0) {
21
+ const key = s.slice(0, idx);
22
+ const label = s.slice(idx + 1);
23
+ if (key && label && !/\s/.test(key) && !/^\d+$/.test(key) && !label.startsWith('//')) {
24
+ return { value: key, label };
25
+ }
26
+ }
27
+ return { value: s, label: s };
28
+ }
29
+
30
+ /** 把本地 choices(string[] 或对象数组)解析为 {value,label} 数组。
31
+ * 对象条目:value > key 取值(表单实际取值),label 缺省回落到 value */
32
+ export function parseChoices(choices: string[] | Array<{ value?: string; key?: string; label?: string }> | undefined | null): ChoiceItem[] {
33
+ return (choices || []).map((c) => {
34
+ if (c !== null && typeof c === 'object') {
35
+ const value = (c as any).value ?? (c as any).key;
36
+ if (value === undefined || value === null || value === '') return null;
37
+ const valueStr = String(value);
38
+ const label = (c as any).label !== undefined && (c as any).label !== null && (c as any).label !== ''
39
+ ? String((c as any).label)
40
+ : valueStr;
41
+ return { value: valueStr, label };
42
+ }
43
+ return parseChoice(c as string);
44
+ }).filter((c: ChoiceItem | null): c is ChoiceItem => c !== null);
45
+ }
@@ -1,18 +1,17 @@
1
- export { Button } from './Button';
2
- export { ButtonGroup } from './ButtonGroup';
3
- export { Cascader } from './Cascader';
4
- export { Chart } from './Chart';
5
- export { DatePicker } from './DatePicker';
6
- export { Form } from './Form';
7
- export { Incomplete } from './Incomplete';
8
- export { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
9
- export { NumberInput } from './NumberInput';
10
- export { Quiz } from './Quiz';
11
- export { RadioGroup } from './Radio';
12
- export { Select } from './Select';
13
- export { SelectMulti } from './SelectMulti';
14
- export { TreeSelect } from './TreeSelect';
15
- export { ShortAnswerQuestion } from './ShortAnswerQuestion';
16
- export { Slider } from './Slider';
17
- export { Switch } from './Switch';
1
+ export { Button } from './Button';
2
+ export { ButtonGroup } from './ButtonGroup';
3
+ export { Cascader } from './Cascader';
4
+ export { Chart } from './Chart';
5
+ export { DatePicker } from './DatePicker';
6
+ export { Form } from './Form';
7
+ export { Incomplete } from './Incomplete';
8
+ export { MultipleChoiceQuestion } from './MultipleChoiceQuestion';
9
+ export { NumberInput } from './NumberInput';
10
+ export { Quiz } from './Quiz';
11
+ export { RadioGroup } from './Radio';
12
+ export { Select } from './Select';
13
+ export { SelectMulti } from './SelectMulti';
14
+ export { ShortAnswerQuestion } from './ShortAnswerQuestion';
15
+ export { Slider } from './Slider';
16
+ export { Switch } from './Switch';
18
17
  export { TextInput } from './TextInput';