@cisri/json-schema-editor 1.0.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 ADDED
@@ -0,0 +1,97 @@
1
+ # @cisri/json-schema-editor
2
+
3
+ A JSON Schema editor business component based on shadcn/ui.
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @cisri/json-schema-editor
9
+ ```
10
+
11
+ ## Peer Dependencies
12
+
13
+ 本组件依赖以下库,请确保你的项目已安装:
14
+
15
+ ```bash
16
+ npm install react react-dom @radix-ui/react-checkbox @radix-ui/react-select @radix-ui/react-slot class-variance-authority clsx lucide-react tailwind-merge
17
+ ```
18
+
19
+ ## 使用
20
+
21
+ ```tsx
22
+ import { useState } from 'react';
23
+ import { JsonSchemaEditor } from '@cisri/json-schema-editor';
24
+
25
+ export default function App() {
26
+ const [schema, setSchema] = useState({
27
+ type: 'object',
28
+ title: 'User',
29
+ properties: {
30
+ name: { type: 'string' },
31
+ age: { type: 'integer' },
32
+ },
33
+ required: ['name'],
34
+ });
35
+
36
+ return <JsonSchemaEditor value={schema} onChange={setSchema} />;
37
+ }
38
+ ```
39
+
40
+ ## Tailwind 配置
41
+
42
+ 确保 `tailwind.config.ts` 的 `content` 包含以下路径,否则组件的 Tailwind 类名不会被扫描到:
43
+
44
+ ```ts
45
+ content: [
46
+ './node_modules/@cisri/json-schema-editor/dist/**/*.{js,cjs}',
47
+ ];
48
+ ```
49
+
50
+ 如果同时使用多个 `@cisri/*` 组件,可以使用通配符:
51
+
52
+ ```ts
53
+ content: [
54
+ './node_modules/@cisri/*/dist/**/*.{js,cjs}',
55
+ ];
56
+ ```
57
+
58
+ ## CSS 变量
59
+
60
+ 本组件使用 shadcn/ui 标准 CSS 变量。你可以通过覆盖 `:root` 中的变量来自定义主题:
61
+
62
+ ```css
63
+ :root {
64
+ --background: 0 0% 100%;
65
+ --foreground: 222.2 84% 4.9%;
66
+ --card: 0 0% 100%;
67
+ --card-foreground: 222.2 84% 4.9%;
68
+ --popover: 0 0% 100%;
69
+ --popover-foreground: 222.2 84% 4.9%;
70
+ --primary: 222.2 47.4% 11.2%;
71
+ --primary-foreground: 210 40% 98%;
72
+ --secondary: 210 40% 96.1%;
73
+ --secondary-foreground: 222.2 47.4% 11.2%;
74
+ --muted: 210 40% 96.1%;
75
+ --muted-foreground: 215.4 16.3% 46.9%;
76
+ --accent: 210 40% 96.1%;
77
+ --accent-foreground: 222.2 47.4% 11.2%;
78
+ --destructive: 0 84.2% 60.2%;
79
+ --destructive-foreground: 210 40% 98%;
80
+ --border: 214.3 31.8% 91.4%;
81
+ --input: 214.3 31.8% 91.4%;
82
+ --ring: 222.2 84% 4.9%;
83
+ --radius: 0.5rem;
84
+ }
85
+ ```
86
+
87
+ ## 自定义样式
88
+
89
+ 你可以通过 `className` 覆盖默认外观:
90
+
91
+ ```tsx
92
+ <JsonSchemaEditor
93
+ value={schema}
94
+ onChange={setSchema}
95
+ className="w-full max-w-2xl border-primary"
96
+ />
97
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react/jsx-runtime"),c=require("react"),xe=require("@radix-ui/react-slot"),fe=require("class-variance-authority"),d=require("@cisri/core"),ge=require("@radix-ui/react-checkbox"),j=require("lucide-react"),ye=require("@radix-ui/react-select");function M(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const t in e)if(t!=="default"){const s=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,s.get?s:{enumerable:!0,get:()=>e[t]})}}return r.default=e,Object.freeze(r)}const h=M(c),_=M(ge),x=M(ye),je=fe.cva("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",{variants:{variant:{default:"bg-primary text-primary-foreground shadow hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",outline:"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2",sm:"h-8 rounded-md px-3 text-xs",lg:"h-10 rounded-md px-8",icon:"h-9 w-9"}},defaultVariants:{variant:"default",size:"default"}}),y=h.forwardRef(({className:e,variant:r,size:t,asChild:s=!1,...a},l)=>{const p=s?xe.Slot:"button";return n.jsx(p,{className:d.cn(je({variant:r,size:t,className:e})),ref:l,...a})});y.displayName="Button";const U=h.forwardRef(({className:e,...r},t)=>n.jsx(_.Root,{ref:t,className:d.cn("peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",e),...r,children:n.jsx(_.Indicator,{className:d.cn("flex items-center justify-center text-current"),children:n.jsx(j.Check,{className:"h-4 w-4"})})}));U.displayName=_.Root.displayName;const B=h.forwardRef(({className:e,type:r,...t},s)=>n.jsx("input",{type:r,className:d.cn("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",e),ref:s,...t}));B.displayName="Input";const we=x.Root,Ne=x.Value,K=h.forwardRef(({className:e,children:r,...t},s)=>n.jsxs(x.Trigger,{ref:s,className:d.cn("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span&gt;]:line-clamp-1",e),...t,children:[r,n.jsx(x.Icon,{asChild:!0,children:n.jsx(j.ChevronDown,{className:"h-4 w-4 opacity-50"})})]}));K.displayName=x.Trigger.displayName;const Y=h.forwardRef(({className:e,children:r,position:t="popper",...s},a)=>n.jsx(x.Portal,{children:n.jsx(x.Content,{ref:a,className:d.cn("relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",t==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",e),position:t,...s,children:n.jsx(x.Viewport,{className:d.cn("p-1",t==="popper"&&"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),children:r})})}));Y.displayName=x.Content.displayName;const G=h.forwardRef(({className:e,children:r,...t},s)=>n.jsxs(x.Item,{ref:s,className:d.cn("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",e),...t,children:[n.jsx("span",{className:"absolute right-2 flex h-3.5 w-3.5 items-center justify-center",children:n.jsx(x.ItemIndicator,{children:n.jsx(j.Check,{className:"h-4 w-4"})})}),n.jsx(x.ItemText,{children:r})]}));G.displayName=x.Item.displayName;const Q=h.forwardRef(({className:e,...r},t)=>n.jsx("div",{className:"relative w-full overflow-auto",children:n.jsx("table",{ref:t,className:d.cn("w-full caption-bottom text-sm",e),...r})}));Q.displayName="Table";const W=h.forwardRef(({className:e,...r},t)=>n.jsx("thead",{ref:t,className:d.cn("[&_tr]:border-b",e),...r}));W.displayName="TableHeader";const X=h.forwardRef(({className:e,...r},t)=>n.jsx("tbody",{ref:t,className:d.cn("[&_tr:last-child]:border-0",e),...r}));X.displayName="TableBody";const ve=h.forwardRef(({className:e,...r},t)=>n.jsx("tfoot",{ref:t,className:d.cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",e),...r}));ve.displayName="TableFooter";const V=h.forwardRef(({className:e,...r},t)=>n.jsx("tr",{ref:t,className:d.cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",e),...r}));V.displayName="TableRow";const k=h.forwardRef(({className:e,...r},t)=>n.jsx("th",{ref:t,className:d.cn("h-9 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",e),...r}));k.displayName="TableHead";const C=h.forwardRef(({className:e,...r},t)=>n.jsx("td",{ref:t,className:d.cn("p-2 align-middle [&:has([role=checkbox])]:pr-0",e),...r}));C.displayName="TableCell";const Se=h.forwardRef(({className:e,...r},t)=>n.jsx("caption",{ref:t,className:d.cn("mt-4 text-sm text-muted-foreground",e),...r}));Se.displayName="TableCaption";const D=h.forwardRef(({className:e,...r},t)=>n.jsx("textarea",{className:d.cn("flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",e),ref:t,...r}));D.displayName="Textarea";const ke=["object","array","string","number","integer","boolean"];let Ce=0;function Te(){return`sf-${++Ce}`}function Re(e){return e==="object"?[N("field1",{type:"string"},new Set)]:e==="array"?[N("ITEMS",{type:"string"},new Set,!0)]:[]}function N(e,r,t,s=!1){const a=r.type??"object",l=[];if(a==="object"&&r.properties){const p=new Set(r.required??[]);for(const[u,b]of Object.entries(r.properties))l.push(N(u,b,p))}else a==="array"&&r.items&&l.push(N("ITEMS",r.items,new Set,!0));return{id:Te(),name:e,type:a,required:t.has(e),description:r.description,schema:r,children:l,expanded:!0,isArrayItem:s}}function P(e){if((e.type??"object")!=="object"||!e.properties)return[];const t=new Set(e.required??[]);return Object.entries(e.properties).map(([s,a])=>N(s,a,t))}function Ie(){return N("",{type:"string"},new Set)}function q(e){return e.length>0?e:[Ie()]}function F(e){switch(e.type){case"string":return"string";case"number":case"integer":return 0;case"boolean":return!0;case"object":{const r={};if(e.properties)for(const[t,s]of Object.entries(e.properties))r[t]=F(s);return r}case"array":return e.items?[F(e.items)]:[];default:return null}}function T(e){const r={},t=[];for(const s of e)r[s.name]=Z(s),s.required&&t.push(s.name);return{type:"object",properties:r,required:t.length>0?t:void 0}}function Z(e){const r={...e.schema};if(e.type==="object"){const t=T(e.children);return{...r,type:e.type,description:e.description,properties:t.properties,required:t.required,items:void 0}}if(e.type==="array"){const t=e.children[0];return{...r,type:e.type,description:e.description,items:t?Z(t):{type:"string"},properties:void 0,required:void 0}}return{...r,type:e.type,description:e.description,properties:void 0,items:void 0,required:void 0}}function ee(e,r,t){return e.map(s=>{if(s.id===r){const a={...s,...t};return t.type&&t.type!==s.type&&(a.children=Re(t.type)),a}return s.children.length>0?{...s,children:ee(s.children,r,t)}:s})}function te(e,r){const t=e.findIndex(s=>s.id===r);if(t!==-1){const s=N(`field${e.length+1}`,{type:"string"},new Set),a=[...e];return a.splice(t+1,0,s),a}for(let s=0;s<e.length;s++)if(e[s].children.length>0){const a=te(e[s].children,r);if(a)return[...e.slice(0,s),{...e[s],children:a},...e.slice(s+1)]}return null}function re(e,r){return e.filter(t=>t.id!==r).map(t=>({...t,children:re(t.children,r)}))}function ne(e,r){return e.map(t=>t.id===r?{...t,expanded:!t.expanded}:t.children.length>0?{...t,children:ne(t.children,r)}:t)}function A(e,r){if(e===r)return!0;if(typeof e!="object"||typeof r!="object"||e==null||r==null)return!1;const t=Array.isArray(e),s=Array.isArray(r);if(t!==s)return!1;if(t){const b=e,f=r;return b.length!==f.length?!1:b.every((E,J)=>A(E,f[J]))}const a=e,l=r,p=Object.keys(a),u=Object.keys(l);return p.length!==u.length?!1:p.every(b=>A(a[b],l[b]))}function qe({level:e}){return n.jsx(n.Fragment,{children:Array.from({length:e}).map((r,t)=>n.jsx("span",{className:"inline-block w-5 shrink-0"},t))})}function Fe({value:e,"aria-label":r,disabled:t,onChange:s}){return n.jsxs(we,{value:e,onValueChange:a=>s(a),disabled:t,children:[n.jsx(K,{"aria-label":r,className:"h-7 w-28 text-xs",children:n.jsx(Ne,{})}),n.jsx(Y,{children:ke.map(a=>n.jsx(G,{value:a,className:"text-xs",children:a},a))})]})}function Ae({fieldName:e,showAdd:r,showDelete:t=!0,disableDelete:s,onAdd:a,onDelete:l}){return n.jsxs("div",{className:"flex items-center justify-end gap-1",children:[r&&n.jsx(y,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",onClick:a,"aria-label":`在字段 ${e||"未命名"} 后添加行`,children:n.jsx(j.Plus,{className:"h-4 w-4"})}),t&&n.jsx(y,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",disabled:s,onClick:l,"aria-label":`删除字段 ${e||"未命名"}`,children:n.jsx(j.Trash2,{className:"h-4 w-4 text-destructive"})})]})}function se({field:e,level:r,readOnly:t,disableDelete:s,dispatch:a}){const l=e.children.length>0,p=e.name||"未命名";return n.jsxs(n.Fragment,{children:[n.jsxs(V,{children:[n.jsx(C,{children:n.jsxs("div",{className:"flex items-center",children:[n.jsx(qe,{level:r}),l?n.jsx("button",{type:"button",onClick:()=>a({type:"toggleExpand",id:e.id}),className:"mr-1 inline-flex h-3.5 w-3.5 items-center justify-center text-muted-foreground","aria-label":e.expanded?"折叠":"展开",children:n.jsx(j.ChevronDown,{className:d.cn("h-3.5 w-3.5 transition-transform",!e.expanded&&"-rotate-90")})}):n.jsx(j.Minus,{className:"mr-1 h-3.5 w-3.5 text-muted-foreground/60"}),n.jsx(B,{id:`schema-name-${e.id}`,name:`schema-name-${e.id}`,"aria-label":`字段 ${p} 名称`,autoComplete:"off",disabled:t||e.isArrayItem,className:"h-7 border-transparent bg-transparent px-1 shadow-none focus-visible:bg-background disabled:opacity-100",value:e.name,onChange:u=>a({type:"update",id:e.id,patch:{name:u.target.value}})})]})}),n.jsx(C,{children:n.jsx(Fe,{value:e.type,"aria-label":`字段 ${p} 类型`,disabled:t,onChange:u=>a({type:"update",id:e.id,patch:{type:u}})})}),n.jsx(C,{children:e.isArrayItem?null:n.jsx(U,{id:`schema-required-${e.id}`,name:`schema-required-${e.id}`,"aria-label":`字段 ${p} 是否必填`,checked:e.required,disabled:t,onCheckedChange:u=>a({type:"update",id:e.id,patch:{required:u===!0}})})}),n.jsx(C,{children:n.jsx(B,{id:`schema-description-${e.id}`,name:`schema-description-${e.id}`,"aria-label":`字段 ${p} 描述`,autoComplete:"off",disabled:t,className:"h-7 border-transparent bg-transparent px-1 shadow-none focus-visible:bg-background disabled:opacity-100",value:e.description??"",onChange:u=>a({type:"update",id:e.id,patch:{description:u.target.value}})})}),n.jsx(C,{children:!t&&n.jsx(Ae,{fieldId:e.id,fieldName:e.name,showAdd:!e.isArrayItem,showDelete:!e.isArrayItem,disableDelete:s,onAdd:()=>a({type:"addSibling",id:e.id}),onDelete:()=>a({type:"delete",id:e.id})})})]}),e.expanded&&e.children.map(u=>n.jsx(se,{field:u,level:r+1,readOnly:t,disableDelete:e.type==="object"&&e.children.length<=1,dispatch:a},u.id))]})}function Ee({value:e,onChange:r,onSave:t,readOnly:s,className:a}){const[l,p]=c.useState(()=>({history:[q(P(e))],index:0})),u=l.history[l.index],b=c.useRef(l);c.useEffect(()=>{b.current=l},[l]);const[f,E]=c.useState("table"),[J,R]=c.useState(()=>JSON.stringify(e,null,2)),[H,v]=c.useState(null),[ae,z]=c.useState(null),O=c.useRef(!1),S=c.useRef(e),w=c.useMemo(()=>JSON.stringify(e,null,2),[e]);c.useEffect(()=>{if(!A(e,S.current)){const o=q(P(e));p({history:[o],index:0}),S.current=e}},[e]),c.useEffect(()=>{O.current||(R(w),v(null))},[w]),c.useEffect(()=>{z(null)},[f]);const I=c.useCallback(o=>{p(i=>A(i.history[i.index],o)?i:{history:[...i.history.slice(0,i.index+1),o],index:i.index+1})},[]),oe=c.useCallback(o=>{const i=b.current.history[b.current.index];let m;switch(o.type){case"update":m=ee(i,o.id,o.patch);break;case"addSibling":m=te(i,o.id)??i;break;case"delete":m=re(i,o.id);break;case"toggleExpand":m=ne(i,o.id);break;default:return}m=q(m);const g=T(m);S.current=g,I(m),r(g)},[r,I]),ie=l.index>0,ce=l.index<l.history.length-1,de=c.useCallback(()=>{const o=b.current;if(o.index<=0)return;const i=o.index-1,m=o.history[i],g=T(m);p({...o,index:i}),S.current=g,r(g)},[r]),le=c.useCallback(()=>{const o=b.current;if(o.index>=o.history.length-1)return;const i=o.index+1,m=o.history[i],g=T(m);p({...o,index:i}),S.current=g,r(g)},[r]),$=c.useCallback(o=>()=>{E(o),o==="json"&&(R(w),v(null))},[w]),ue=c.useCallback(o=>{const i=o.target.value;R(i);try{const m=JSON.parse(i);if(m&&typeof m=="object"&&!Array.isArray(m)){const g=q(P(m)),L=T(g);S.current=L,I(g),r(L),v(null)}else v("请输入有效的 JSON 对象。")}catch{v("JSON 解析失败,请检查格式。")}},[r,I]),me=c.useCallback(()=>{O.current=!0},[]),pe=c.useCallback(()=>{O.current=!1,R(w),v(null)},[w]),he=c.useCallback(async()=>{try{let o=w,i="已复制 json schema";f==="preview"&&(o=JSON.stringify(F(e),null,2),i="已复制 json 示例"),await navigator.clipboard.writeText(o),z(i),window.setTimeout(()=>z(null),1500)}catch{}},[w,e,f]),be=c.useCallback(()=>{t==null||t(e)},[t,e]);return n.jsxs("div",{className:d.cn("rounded-md border border-border bg-card text-card-foreground",a),children:[n.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2 border-b border-border p-2",children:[n.jsxs("div",{className:"inline-flex items-center overflow-hidden rounded-md border border-border",children:[n.jsx(y,{type:"button",variant:"ghost",size:"sm",onClick:$("table"),className:d.cn("h-7 rounded-none border-r border-border text-xs",f==="table"&&"bg-muted"),children:"表格视图"}),n.jsx(y,{type:"button",variant:"ghost",size:"sm",onClick:$("json"),className:d.cn("h-7 rounded-none border-r border-border text-xs",f==="json"&&"bg-muted"),children:"JSON 视图"}),n.jsx(y,{type:"button",variant:"ghost",size:"sm",onClick:$("preview"),className:d.cn("h-7 rounded-none text-xs",f==="preview"&&"bg-muted"),children:"预览"})]}),n.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[!s&&t&&n.jsx(y,{type:"button",size:"sm",onClick:be,className:"h-7 text-xs",children:"保存"}),f!=="table"&&n.jsx(y,{type:"button",variant:"outline",size:"sm",onClick:he,className:"h-7 text-xs",children:ae??"复制"}),!s&&n.jsxs(n.Fragment,{children:[n.jsx(y,{type:"button",variant:"ghost",size:"icon",onClick:de,disabled:!ie,className:"h-7 w-7","aria-label":"撤销",children:n.jsx(j.Undo2,{className:"h-4 w-4"})}),n.jsx(y,{type:"button",variant:"ghost",size:"icon",onClick:le,disabled:!ce,className:"h-7 w-7","aria-label":"恢复",children:n.jsx(j.Redo2,{className:"h-4 w-4"})})]})]})]}),f==="json"?n.jsxs("div",{className:"space-y-2 p-2",children:[n.jsx(D,{value:J,onChange:s?void 0:ue,onFocus:s?void 0:me,onBlur:s?void 0:pe,readOnly:s,placeholder:"在此编辑或粘贴 JSON Schema...",className:"min-h-[320px] font-mono text-xs"}),H&&n.jsx("p",{className:"text-xs text-destructive",children:H})]}):f==="preview"?n.jsx("div",{className:"p-2",children:n.jsx(D,{value:JSON.stringify(F(e),null,2),readOnly:!0,className:"min-h-[320px] font-mono text-xs"})}):n.jsxs(Q,{children:[n.jsx(W,{children:n.jsxs(V,{children:[n.jsx(k,{className:"w-[28%]",children:"名称"}),n.jsx(k,{className:"w-[18%]",children:"类型"}),n.jsx(k,{className:"w-[12%]",children:"必填"}),n.jsx(k,{className:"w-[30%]",children:"描述"}),n.jsx(k,{className:"w-[12%]"})]})}),n.jsx(X,{children:u.map(o=>n.jsx(se,{field:o,level:0,readOnly:s,disableDelete:u.length<=1,dispatch:oe},o.id))})]})]})}exports.JsonSchemaEditor=Ee;
@@ -0,0 +1,3 @@
1
+ export { JsonSchemaEditor } from './json-schema-editor';
2
+ export type { JsonSchema, JsonSchemaType, JsonSchemaEditorProps } from './json-schema-editor';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,813 @@
1
+ import { jsx as n, jsxs as p, Fragment as L } from "react/jsx-runtime";
2
+ import * as h from "react";
3
+ import { useState as C, useRef as M, useEffect as F, useMemo as Ne, useCallback as x } from "react";
4
+ import { Slot as ve } from "@radix-ui/react-slot";
5
+ import { cva as Se } from "class-variance-authority";
6
+ import { cn as d } from "@cisri/core";
7
+ import * as P from "@radix-ui/react-checkbox";
8
+ import { Check as G, ChevronDown as Q, Undo2 as je, Redo2 as Te, Minus as ke, Plus as Ce, Trash2 as Re } from "lucide-react";
9
+ import * as f from "@radix-ui/react-select";
10
+ const Ie = Se(
11
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
12
+ {
13
+ variants: {
14
+ variant: {
15
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
16
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
17
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline"
21
+ },
22
+ size: {
23
+ default: "h-9 px-4 py-2",
24
+ sm: "h-8 rounded-md px-3 text-xs",
25
+ lg: "h-10 rounded-md px-8",
26
+ icon: "h-9 w-9"
27
+ }
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default"
32
+ }
33
+ }
34
+ ), w = h.forwardRef(
35
+ ({ className: e, variant: r, size: t, asChild: a = !1, ...o }, c) => /* @__PURE__ */ n(
36
+ a ? ve : "button",
37
+ {
38
+ className: d(Ie({ variant: r, size: t, className: e })),
39
+ ref: c,
40
+ ...o
41
+ }
42
+ )
43
+ );
44
+ w.displayName = "Button";
45
+ const W = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
46
+ P.Root,
47
+ {
48
+ ref: t,
49
+ className: d(
50
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
51
+ e
52
+ ),
53
+ ...r,
54
+ children: /* @__PURE__ */ n(
55
+ P.Indicator,
56
+ {
57
+ className: d("flex items-center justify-center text-current"),
58
+ children: /* @__PURE__ */ n(G, { className: "h-4 w-4" })
59
+ }
60
+ )
61
+ }
62
+ ));
63
+ W.displayName = P.Root.displayName;
64
+ const H = h.forwardRef(
65
+ ({ className: e, type: r, ...t }, a) => /* @__PURE__ */ n(
66
+ "input",
67
+ {
68
+ type: r,
69
+ className: d(
70
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
71
+ e
72
+ ),
73
+ ref: a,
74
+ ...t
75
+ }
76
+ )
77
+ );
78
+ H.displayName = "Input";
79
+ const Ae = f.Root, Fe = f.Value, X = h.forwardRef(({ className: e, children: r, ...t }, a) => /* @__PURE__ */ p(
80
+ f.Trigger,
81
+ {
82
+ ref: a,
83
+ className: d(
84
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span&gt;]:line-clamp-1",
85
+ e
86
+ ),
87
+ ...t,
88
+ children: [
89
+ r,
90
+ /* @__PURE__ */ n(f.Icon, { asChild: !0, children: /* @__PURE__ */ n(Q, { className: "h-4 w-4 opacity-50" }) })
91
+ ]
92
+ }
93
+ ));
94
+ X.displayName = f.Trigger.displayName;
95
+ const Z = h.forwardRef(({ className: e, children: r, position: t = "popper", ...a }, o) => /* @__PURE__ */ n(f.Portal, { children: /* @__PURE__ */ n(
96
+ f.Content,
97
+ {
98
+ ref: o,
99
+ className: d(
100
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
101
+ t === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
102
+ e
103
+ ),
104
+ position: t,
105
+ ...a,
106
+ children: /* @__PURE__ */ n(
107
+ f.Viewport,
108
+ {
109
+ className: d(
110
+ "p-1",
111
+ t === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
112
+ ),
113
+ children: r
114
+ }
115
+ )
116
+ }
117
+ ) }));
118
+ Z.displayName = f.Content.displayName;
119
+ const ee = h.forwardRef(({ className: e, children: r, ...t }, a) => /* @__PURE__ */ p(
120
+ f.Item,
121
+ {
122
+ ref: a,
123
+ className: d(
124
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125
+ e
126
+ ),
127
+ ...t,
128
+ children: [
129
+ /* @__PURE__ */ n("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ n(f.ItemIndicator, { children: /* @__PURE__ */ n(G, { className: "h-4 w-4" }) }) }),
130
+ /* @__PURE__ */ n(f.ItemText, { children: r })
131
+ ]
132
+ }
133
+ ));
134
+ ee.displayName = f.Item.displayName;
135
+ const te = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ n(
136
+ "table",
137
+ {
138
+ ref: t,
139
+ className: d("w-full caption-bottom text-sm", e),
140
+ ...r
141
+ }
142
+ ) }));
143
+ te.displayName = "Table";
144
+ const re = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n("thead", { ref: t, className: d("[&_tr]:border-b", e), ...r }));
145
+ re.displayName = "TableHeader";
146
+ const ne = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
147
+ "tbody",
148
+ {
149
+ ref: t,
150
+ className: d("[&_tr:last-child]:border-0", e),
151
+ ...r
152
+ }
153
+ ));
154
+ ne.displayName = "TableBody";
155
+ const qe = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
156
+ "tfoot",
157
+ {
158
+ ref: t,
159
+ className: d(
160
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
161
+ e
162
+ ),
163
+ ...r
164
+ }
165
+ ));
166
+ qe.displayName = "TableFooter";
167
+ const U = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
168
+ "tr",
169
+ {
170
+ ref: t,
171
+ className: d(
172
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
173
+ e
174
+ ),
175
+ ...r
176
+ }
177
+ ));
178
+ U.displayName = "TableRow";
179
+ const T = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
180
+ "th",
181
+ {
182
+ ref: t,
183
+ className: d(
184
+ "h-9 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
185
+ e
186
+ ),
187
+ ...r
188
+ }
189
+ ));
190
+ T.displayName = "TableHead";
191
+ const k = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
192
+ "td",
193
+ {
194
+ ref: t,
195
+ className: d(
196
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0",
197
+ e
198
+ ),
199
+ ...r
200
+ }
201
+ ));
202
+ k.displayName = "TableCell";
203
+ const Je = h.forwardRef(({ className: e, ...r }, t) => /* @__PURE__ */ n(
204
+ "caption",
205
+ {
206
+ ref: t,
207
+ className: d("mt-4 text-sm text-muted-foreground", e),
208
+ ...r
209
+ }
210
+ ));
211
+ Je.displayName = "TableCaption";
212
+ const _ = h.forwardRef(
213
+ ({ className: e, ...r }, t) => /* @__PURE__ */ n(
214
+ "textarea",
215
+ {
216
+ className: d(
217
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
218
+ e
219
+ ),
220
+ ref: t,
221
+ ...r
222
+ }
223
+ )
224
+ );
225
+ _.displayName = "Textarea";
226
+ const Ee = [
227
+ "object",
228
+ "array",
229
+ "string",
230
+ "number",
231
+ "integer",
232
+ "boolean"
233
+ ];
234
+ let ze = 0;
235
+ function $e() {
236
+ return `sf-${++ze}`;
237
+ }
238
+ function Be(e) {
239
+ return e === "object" ? [v("field1", { type: "string" }, /* @__PURE__ */ new Set())] : e === "array" ? [v("ITEMS", { type: "string" }, /* @__PURE__ */ new Set(), !0)] : [];
240
+ }
241
+ function v(e, r, t, a = !1) {
242
+ const o = r.type ?? "object", c = [];
243
+ if (o === "object" && r.properties) {
244
+ const m = new Set(r.required ?? []);
245
+ for (const [l, b] of Object.entries(r.properties))
246
+ c.push(v(l, b, m));
247
+ } else o === "array" && r.items && c.push(v("ITEMS", r.items, /* @__PURE__ */ new Set(), !0));
248
+ return {
249
+ id: $e(),
250
+ name: e,
251
+ type: o,
252
+ required: t.has(e),
253
+ description: r.description,
254
+ schema: r,
255
+ children: c,
256
+ expanded: !0,
257
+ isArrayItem: a
258
+ };
259
+ }
260
+ function D(e) {
261
+ if ((e.type ?? "object") !== "object" || !e.properties)
262
+ return [];
263
+ const t = new Set(e.required ?? []);
264
+ return Object.entries(e.properties).map(
265
+ ([a, o]) => v(a, o, t)
266
+ );
267
+ }
268
+ function Oe() {
269
+ return v("", { type: "string" }, /* @__PURE__ */ new Set());
270
+ }
271
+ function q(e) {
272
+ return e.length > 0 ? e : [Oe()];
273
+ }
274
+ function J(e) {
275
+ switch (e.type) {
276
+ case "string":
277
+ return "string";
278
+ case "number":
279
+ case "integer":
280
+ return 0;
281
+ case "boolean":
282
+ return !0;
283
+ case "object": {
284
+ const r = {};
285
+ if (e.properties)
286
+ for (const [t, a] of Object.entries(e.properties))
287
+ r[t] = J(a);
288
+ return r;
289
+ }
290
+ case "array":
291
+ return e.items ? [J(e.items)] : [];
292
+ default:
293
+ return null;
294
+ }
295
+ }
296
+ function R(e) {
297
+ const r = {}, t = [];
298
+ for (const a of e)
299
+ r[a.name] = ae(a), a.required && t.push(a.name);
300
+ return {
301
+ type: "object",
302
+ properties: r,
303
+ required: t.length > 0 ? t : void 0
304
+ };
305
+ }
306
+ function ae(e) {
307
+ const r = { ...e.schema };
308
+ if (e.type === "object") {
309
+ const t = R(e.children);
310
+ return {
311
+ ...r,
312
+ type: e.type,
313
+ description: e.description,
314
+ properties: t.properties,
315
+ required: t.required,
316
+ items: void 0
317
+ };
318
+ }
319
+ if (e.type === "array") {
320
+ const t = e.children[0];
321
+ return {
322
+ ...r,
323
+ type: e.type,
324
+ description: e.description,
325
+ items: t ? ae(t) : { type: "string" },
326
+ properties: void 0,
327
+ required: void 0
328
+ };
329
+ }
330
+ return {
331
+ ...r,
332
+ type: e.type,
333
+ description: e.description,
334
+ properties: void 0,
335
+ items: void 0,
336
+ required: void 0
337
+ };
338
+ }
339
+ function oe(e, r, t) {
340
+ return e.map((a) => {
341
+ if (a.id === r) {
342
+ const o = { ...a, ...t };
343
+ return t.type && t.type !== a.type && (o.children = Be(t.type)), o;
344
+ }
345
+ return a.children.length > 0 ? {
346
+ ...a,
347
+ children: oe(a.children, r, t)
348
+ } : a;
349
+ });
350
+ }
351
+ function se(e, r) {
352
+ const t = e.findIndex((a) => a.id === r);
353
+ if (t !== -1) {
354
+ const a = v(
355
+ `field${e.length + 1}`,
356
+ { type: "string" },
357
+ /* @__PURE__ */ new Set()
358
+ ), o = [...e];
359
+ return o.splice(t + 1, 0, a), o;
360
+ }
361
+ for (let a = 0; a < e.length; a++)
362
+ if (e[a].children.length > 0) {
363
+ const o = se(e[a].children, r);
364
+ if (o)
365
+ return [
366
+ ...e.slice(0, a),
367
+ { ...e[a], children: o },
368
+ ...e.slice(a + 1)
369
+ ];
370
+ }
371
+ return null;
372
+ }
373
+ function ie(e, r) {
374
+ return e.filter((t) => t.id !== r).map((t) => ({
375
+ ...t,
376
+ children: ie(t.children, r)
377
+ }));
378
+ }
379
+ function de(e, r) {
380
+ return e.map((t) => t.id === r ? { ...t, expanded: !t.expanded } : t.children.length > 0 ? {
381
+ ...t,
382
+ children: de(t.children, r)
383
+ } : t);
384
+ }
385
+ function E(e, r) {
386
+ if (e === r) return !0;
387
+ if (typeof e != "object" || typeof r != "object" || e == null || r == null)
388
+ return !1;
389
+ const t = Array.isArray(e), a = Array.isArray(r);
390
+ if (t !== a) return !1;
391
+ if (t) {
392
+ const b = e, y = r;
393
+ return b.length !== y.length ? !1 : b.every((z, $) => E(z, y[$]));
394
+ }
395
+ const o = e, c = r, m = Object.keys(o), l = Object.keys(c);
396
+ return m.length !== l.length ? !1 : m.every((b) => E(o[b], c[b]));
397
+ }
398
+ function Ve({ level: e }) {
399
+ return /* @__PURE__ */ n(L, { children: Array.from({ length: e }).map((r, t) => /* @__PURE__ */ n("span", { className: "inline-block w-5 shrink-0" }, t)) });
400
+ }
401
+ function Me({
402
+ value: e,
403
+ "aria-label": r,
404
+ disabled: t,
405
+ onChange: a
406
+ }) {
407
+ return /* @__PURE__ */ p(Ae, { value: e, onValueChange: (o) => a(o), disabled: t, children: [
408
+ /* @__PURE__ */ n(X, { "aria-label": r, className: "h-7 w-28 text-xs", children: /* @__PURE__ */ n(Fe, {}) }),
409
+ /* @__PURE__ */ n(Z, { children: Ee.map((o) => /* @__PURE__ */ n(ee, { value: o, className: "text-xs", children: o }, o)) })
410
+ ] });
411
+ }
412
+ function De({
413
+ fieldName: e,
414
+ showAdd: r,
415
+ showDelete: t = !0,
416
+ disableDelete: a,
417
+ onAdd: o,
418
+ onDelete: c
419
+ }) {
420
+ return /* @__PURE__ */ p("div", { className: "flex items-center justify-end gap-1", children: [
421
+ r && /* @__PURE__ */ n(
422
+ w,
423
+ {
424
+ type: "button",
425
+ variant: "ghost",
426
+ size: "icon",
427
+ className: "h-7 w-7",
428
+ onClick: o,
429
+ "aria-label": `在字段 ${e || "未命名"} 后添加行`,
430
+ children: /* @__PURE__ */ n(Ce, { className: "h-4 w-4" })
431
+ }
432
+ ),
433
+ t && /* @__PURE__ */ n(
434
+ w,
435
+ {
436
+ type: "button",
437
+ variant: "ghost",
438
+ size: "icon",
439
+ className: "h-7 w-7",
440
+ disabled: a,
441
+ onClick: c,
442
+ "aria-label": `删除字段 ${e || "未命名"}`,
443
+ children: /* @__PURE__ */ n(Re, { className: "h-4 w-4 text-destructive" })
444
+ }
445
+ )
446
+ ] });
447
+ }
448
+ function ce({ field: e, level: r, readOnly: t, disableDelete: a, dispatch: o }) {
449
+ const c = e.children.length > 0, m = e.name || "未命名";
450
+ return /* @__PURE__ */ p(L, { children: [
451
+ /* @__PURE__ */ p(U, { children: [
452
+ /* @__PURE__ */ n(k, { children: /* @__PURE__ */ p("div", { className: "flex items-center", children: [
453
+ /* @__PURE__ */ n(Ve, { level: r }),
454
+ c ? /* @__PURE__ */ n(
455
+ "button",
456
+ {
457
+ type: "button",
458
+ onClick: () => o({ type: "toggleExpand", id: e.id }),
459
+ className: "mr-1 inline-flex h-3.5 w-3.5 items-center justify-center text-muted-foreground",
460
+ "aria-label": e.expanded ? "折叠" : "展开",
461
+ children: /* @__PURE__ */ n(
462
+ Q,
463
+ {
464
+ className: d(
465
+ "h-3.5 w-3.5 transition-transform",
466
+ !e.expanded && "-rotate-90"
467
+ )
468
+ }
469
+ )
470
+ }
471
+ ) : /* @__PURE__ */ n(ke, { className: "mr-1 h-3.5 w-3.5 text-muted-foreground/60" }),
472
+ /* @__PURE__ */ n(
473
+ H,
474
+ {
475
+ id: `schema-name-${e.id}`,
476
+ name: `schema-name-${e.id}`,
477
+ "aria-label": `字段 ${m} 名称`,
478
+ autoComplete: "off",
479
+ disabled: t || e.isArrayItem,
480
+ className: "h-7 border-transparent bg-transparent px-1 shadow-none focus-visible:bg-background disabled:opacity-100",
481
+ value: e.name,
482
+ onChange: (l) => o({
483
+ type: "update",
484
+ id: e.id,
485
+ patch: { name: l.target.value }
486
+ })
487
+ }
488
+ )
489
+ ] }) }),
490
+ /* @__PURE__ */ n(k, { children: /* @__PURE__ */ n(
491
+ Me,
492
+ {
493
+ value: e.type,
494
+ "aria-label": `字段 ${m} 类型`,
495
+ disabled: t,
496
+ onChange: (l) => o({ type: "update", id: e.id, patch: { type: l } })
497
+ }
498
+ ) }),
499
+ /* @__PURE__ */ n(k, { children: e.isArrayItem ? null : /* @__PURE__ */ n(
500
+ W,
501
+ {
502
+ id: `schema-required-${e.id}`,
503
+ name: `schema-required-${e.id}`,
504
+ "aria-label": `字段 ${m} 是否必填`,
505
+ checked: e.required,
506
+ disabled: t,
507
+ onCheckedChange: (l) => o({
508
+ type: "update",
509
+ id: e.id,
510
+ patch: { required: l === !0 }
511
+ })
512
+ }
513
+ ) }),
514
+ /* @__PURE__ */ n(k, { children: /* @__PURE__ */ n(
515
+ H,
516
+ {
517
+ id: `schema-description-${e.id}`,
518
+ name: `schema-description-${e.id}`,
519
+ "aria-label": `字段 ${m} 描述`,
520
+ autoComplete: "off",
521
+ disabled: t,
522
+ className: "h-7 border-transparent bg-transparent px-1 shadow-none focus-visible:bg-background disabled:opacity-100",
523
+ value: e.description ?? "",
524
+ onChange: (l) => o({
525
+ type: "update",
526
+ id: e.id,
527
+ patch: { description: l.target.value }
528
+ })
529
+ }
530
+ ) }),
531
+ /* @__PURE__ */ n(k, { children: !t && /* @__PURE__ */ n(
532
+ De,
533
+ {
534
+ fieldId: e.id,
535
+ fieldName: e.name,
536
+ showAdd: !e.isArrayItem,
537
+ showDelete: !e.isArrayItem,
538
+ disableDelete: a,
539
+ onAdd: () => o({
540
+ type: "addSibling",
541
+ id: e.id
542
+ }),
543
+ onDelete: () => o({ type: "delete", id: e.id })
544
+ }
545
+ ) })
546
+ ] }),
547
+ e.expanded && e.children.map((l) => /* @__PURE__ */ n(
548
+ ce,
549
+ {
550
+ field: l,
551
+ level: r + 1,
552
+ readOnly: t,
553
+ disableDelete: e.type === "object" && e.children.length <= 1,
554
+ dispatch: o
555
+ },
556
+ l.id
557
+ ))
558
+ ] });
559
+ }
560
+ function Ye({
561
+ value: e,
562
+ onChange: r,
563
+ onSave: t,
564
+ readOnly: a,
565
+ className: o
566
+ }) {
567
+ const [c, m] = C(() => ({
568
+ history: [q(D(e))],
569
+ index: 0
570
+ })), l = c.history[c.index], b = M(c);
571
+ F(() => {
572
+ b.current = c;
573
+ }, [c]);
574
+ const [y, z] = C("table"), [$, I] = C(
575
+ () => JSON.stringify(e, null, 2)
576
+ ), [K, S] = C(null), [le, B] = C(null), O = M(!1), j = M(e), N = Ne(() => JSON.stringify(e, null, 2), [e]);
577
+ F(() => {
578
+ if (!E(e, j.current)) {
579
+ const s = q(D(e));
580
+ m({ history: [s], index: 0 }), j.current = e;
581
+ }
582
+ }, [e]), F(() => {
583
+ O.current || (I(N), S(null));
584
+ }, [N]), F(() => {
585
+ B(null);
586
+ }, [y]);
587
+ const A = x((s) => {
588
+ m((i) => E(i.history[i.index], s) ? i : {
589
+ history: [...i.history.slice(0, i.index + 1), s],
590
+ index: i.index + 1
591
+ });
592
+ }, []), ue = x(
593
+ (s) => {
594
+ const i = b.current.history[b.current.index];
595
+ let u;
596
+ switch (s.type) {
597
+ case "update":
598
+ u = oe(i, s.id, s.patch);
599
+ break;
600
+ case "addSibling":
601
+ u = se(i, s.id) ?? i;
602
+ break;
603
+ case "delete":
604
+ u = ie(i, s.id);
605
+ break;
606
+ case "toggleExpand":
607
+ u = de(i, s.id);
608
+ break;
609
+ default:
610
+ return;
611
+ }
612
+ u = q(u);
613
+ const g = R(u);
614
+ j.current = g, A(u), r(g);
615
+ },
616
+ [r, A]
617
+ ), me = c.index > 0, pe = c.index < c.history.length - 1, he = x(() => {
618
+ const s = b.current;
619
+ if (s.index <= 0) return;
620
+ const i = s.index - 1, u = s.history[i], g = R(u);
621
+ m({ ...s, index: i }), j.current = g, r(g);
622
+ }, [r]), be = x(() => {
623
+ const s = b.current;
624
+ if (s.index >= s.history.length - 1) return;
625
+ const i = s.index + 1, u = s.history[i], g = R(u);
626
+ m({ ...s, index: i }), j.current = g, r(g);
627
+ }, [r]), V = x(
628
+ (s) => () => {
629
+ z(s), s === "json" && (I(N), S(null));
630
+ },
631
+ [N]
632
+ ), fe = x(
633
+ (s) => {
634
+ const i = s.target.value;
635
+ I(i);
636
+ try {
637
+ const u = JSON.parse(i);
638
+ if (u && typeof u == "object" && !Array.isArray(u)) {
639
+ const g = q(D(u)), Y = R(g);
640
+ j.current = Y, A(g), r(Y), S(null);
641
+ } else
642
+ S("请输入有效的 JSON 对象。");
643
+ } catch {
644
+ S("JSON 解析失败,请检查格式。");
645
+ }
646
+ },
647
+ [r, A]
648
+ ), ye = x(() => {
649
+ O.current = !0;
650
+ }, []), ge = x(() => {
651
+ O.current = !1, I(N), S(null);
652
+ }, [N]), xe = x(async () => {
653
+ try {
654
+ let s = N, i = "已复制 json schema";
655
+ y === "preview" && (s = JSON.stringify(J(e), null, 2), i = "已复制 json 示例"), await navigator.clipboard.writeText(s), B(i), window.setTimeout(() => B(null), 1500);
656
+ } catch {
657
+ }
658
+ }, [N, e, y]), we = x(() => {
659
+ t == null || t(e);
660
+ }, [t, e]);
661
+ return /* @__PURE__ */ p(
662
+ "div",
663
+ {
664
+ className: d(
665
+ "rounded-md border border-border bg-card text-card-foreground",
666
+ o
667
+ ),
668
+ children: [
669
+ /* @__PURE__ */ p("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-border p-2", children: [
670
+ /* @__PURE__ */ p("div", { className: "inline-flex items-center overflow-hidden rounded-md border border-border", children: [
671
+ /* @__PURE__ */ n(
672
+ w,
673
+ {
674
+ type: "button",
675
+ variant: "ghost",
676
+ size: "sm",
677
+ onClick: V("table"),
678
+ className: d(
679
+ "h-7 rounded-none border-r border-border text-xs",
680
+ y === "table" && "bg-muted"
681
+ ),
682
+ children: "表格视图"
683
+ }
684
+ ),
685
+ /* @__PURE__ */ n(
686
+ w,
687
+ {
688
+ type: "button",
689
+ variant: "ghost",
690
+ size: "sm",
691
+ onClick: V("json"),
692
+ className: d(
693
+ "h-7 rounded-none border-r border-border text-xs",
694
+ y === "json" && "bg-muted"
695
+ ),
696
+ children: "JSON 视图"
697
+ }
698
+ ),
699
+ /* @__PURE__ */ n(
700
+ w,
701
+ {
702
+ type: "button",
703
+ variant: "ghost",
704
+ size: "sm",
705
+ onClick: V("preview"),
706
+ className: d(
707
+ "h-7 rounded-none text-xs",
708
+ y === "preview" && "bg-muted"
709
+ ),
710
+ children: "预览"
711
+ }
712
+ )
713
+ ] }),
714
+ /* @__PURE__ */ p("div", { className: "flex flex-wrap items-center gap-2", children: [
715
+ !a && t && /* @__PURE__ */ n(
716
+ w,
717
+ {
718
+ type: "button",
719
+ size: "sm",
720
+ onClick: we,
721
+ className: "h-7 text-xs",
722
+ children: "保存"
723
+ }
724
+ ),
725
+ y !== "table" && /* @__PURE__ */ n(
726
+ w,
727
+ {
728
+ type: "button",
729
+ variant: "outline",
730
+ size: "sm",
731
+ onClick: xe,
732
+ className: "h-7 text-xs",
733
+ children: le ?? "复制"
734
+ }
735
+ ),
736
+ !a && /* @__PURE__ */ p(L, { children: [
737
+ /* @__PURE__ */ n(
738
+ w,
739
+ {
740
+ type: "button",
741
+ variant: "ghost",
742
+ size: "icon",
743
+ onClick: he,
744
+ disabled: !me,
745
+ className: "h-7 w-7",
746
+ "aria-label": "撤销",
747
+ children: /* @__PURE__ */ n(je, { className: "h-4 w-4" })
748
+ }
749
+ ),
750
+ /* @__PURE__ */ n(
751
+ w,
752
+ {
753
+ type: "button",
754
+ variant: "ghost",
755
+ size: "icon",
756
+ onClick: be,
757
+ disabled: !pe,
758
+ className: "h-7 w-7",
759
+ "aria-label": "恢复",
760
+ children: /* @__PURE__ */ n(Te, { className: "h-4 w-4" })
761
+ }
762
+ )
763
+ ] })
764
+ ] })
765
+ ] }),
766
+ y === "json" ? /* @__PURE__ */ p("div", { className: "space-y-2 p-2", children: [
767
+ /* @__PURE__ */ n(
768
+ _,
769
+ {
770
+ value: $,
771
+ onChange: a ? void 0 : fe,
772
+ onFocus: a ? void 0 : ye,
773
+ onBlur: a ? void 0 : ge,
774
+ readOnly: a,
775
+ placeholder: "在此编辑或粘贴 JSON Schema...",
776
+ className: "min-h-[320px] font-mono text-xs"
777
+ }
778
+ ),
779
+ K && /* @__PURE__ */ n("p", { className: "text-xs text-destructive", children: K })
780
+ ] }) : y === "preview" ? /* @__PURE__ */ n("div", { className: "p-2", children: /* @__PURE__ */ n(
781
+ _,
782
+ {
783
+ value: JSON.stringify(J(e), null, 2),
784
+ readOnly: !0,
785
+ className: "min-h-[320px] font-mono text-xs"
786
+ }
787
+ ) }) : /* @__PURE__ */ p(te, { children: [
788
+ /* @__PURE__ */ n(re, { children: /* @__PURE__ */ p(U, { children: [
789
+ /* @__PURE__ */ n(T, { className: "w-[28%]", children: "名称" }),
790
+ /* @__PURE__ */ n(T, { className: "w-[18%]", children: "类型" }),
791
+ /* @__PURE__ */ n(T, { className: "w-[12%]", children: "必填" }),
792
+ /* @__PURE__ */ n(T, { className: "w-[30%]", children: "描述" }),
793
+ /* @__PURE__ */ n(T, { className: "w-[12%]" })
794
+ ] }) }),
795
+ /* @__PURE__ */ n(ne, { children: l.map((s) => /* @__PURE__ */ n(
796
+ ce,
797
+ {
798
+ field: s,
799
+ level: 0,
800
+ readOnly: a,
801
+ disableDelete: l.length <= 1,
802
+ dispatch: ue
803
+ },
804
+ s.id
805
+ )) })
806
+ ] })
807
+ ]
808
+ }
809
+ );
810
+ }
811
+ export {
812
+ Ye as JsonSchemaEditor
813
+ };
@@ -0,0 +1,18 @@
1
+ export type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'integer';
2
+ export interface JsonSchema {
3
+ type?: JsonSchemaType;
4
+ title?: string;
5
+ description?: string;
6
+ properties?: Record<string, JsonSchema>;
7
+ required?: string[];
8
+ items?: JsonSchema;
9
+ }
10
+ export interface JsonSchemaEditorProps {
11
+ value: JsonSchema;
12
+ onChange: (value: JsonSchema) => void;
13
+ onSave?: (value: JsonSchema) => void;
14
+ readOnly?: boolean;
15
+ className?: string;
16
+ }
17
+ export declare function JsonSchemaEditor({ value, onChange, onSave, readOnly, className, }: JsonSchemaEditorProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=json-schema-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema-editor.d.ts","sourceRoot":"","sources":["../src/json-schema-editor.tsx"],"names":[],"mappings":"AAuBA,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA6fD,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,GACV,EAAE,qBAAqB,+BAkUvB"}
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import { type VariantProps } from 'class-variance-authority';
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | null | undefined;
5
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
+ asChild?: boolean;
9
+ }
10
+ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
11
+ export { Button, buttonVariants };
12
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGlE,QAAA,MAAM,cAAc;;;8EA2BnB,CAAC;AAEF,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,YAAY,CAAC,OAAO,cAAc,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,QAAA,MAAM,MAAM,uFAWX,CAAC;AAGF,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
3
+ declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
4
+ export { Checkbox };
5
+ //# sourceMappingURL=checkbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../src/ui/checkbox.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAC;AAI9D,QAAA,MAAM,QAAQ,iKAkBZ,CAAC;AAGH,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
3
+ }
4
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
5
+ export { Input };
6
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/ui/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC;CAAG;AAElF,QAAA,MAAM,KAAK,qFAcV,CAAC;AAGF,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import * as SelectPrimitive from '@radix-ui/react-select';
3
+ declare const Select: React.FC<SelectPrimitive.SelectProps>;
4
+ declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
5
+ declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
6
+ declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
7
+ declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
8
+ declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
9
+ export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem, };
10
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/ui/select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAC;AAI1D,QAAA,MAAM,MAAM,uCAAuB,CAAC;AACpC,QAAA,MAAM,WAAW,yGAAwB,CAAC;AAC1C,QAAA,MAAM,WAAW,0GAAwB,CAAC;AAE1C,QAAA,MAAM,aAAa,oKAiBjB,CAAC;AAGH,QAAA,MAAM,aAAa,8JA2BjB,CAAC;AAGH,QAAA,MAAM,UAAU,2JAmBd,CAAC;AAGH,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,UAAU,GACX,CAAC"}
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableElement> & React.RefAttributes<HTMLTableElement>>;
3
+ declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
4
+ declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
5
+ declare const TableFooter: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
6
+ declare const TableRow: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableRowElement> & React.RefAttributes<HTMLTableRowElement>>;
7
+ declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>;
8
+ declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>;
9
+ declare const TableCaption: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableCaptionElement> & React.RefAttributes<HTMLTableCaptionElement>>;
10
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, };
11
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/ui/table.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,QAAA,MAAM,KAAK,iHAWT,CAAC;AAGH,QAAA,MAAM,WAAW,+HAKf,CAAC;AAGH,QAAA,MAAM,SAAS,+HASb,CAAC;AAGH,QAAA,MAAM,WAAW,+HAYf,CAAC;AAGH,QAAA,MAAM,QAAQ,uHAYZ,CAAC;AAGH,QAAA,MAAM,SAAS,2HAYb,CAAC;AAGH,QAAA,MAAM,SAAS,2HAYb,CAAC;AAGH,QAAA,MAAM,YAAY,+HAShB,CAAC;AAGH,OAAO,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,SAAS,EACT,YAAY,GACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ import * as React from 'react';
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
+ }
4
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
5
+ export { Textarea };
6
+ //# sourceMappingURL=textarea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../src/ui/textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,aACf,SAAQ,KAAK,CAAC,sBAAsB,CAAC,mBAAmB,CAAC;CAAG;AAE9D,QAAA,MAAM,QAAQ,2FAab,CAAC;AAGF,OAAO,EAAE,QAAQ,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@cisri/json-schema-editor",
3
+ "version": "1.0.0",
4
+ "description": "JSON Schema editor business component based on shadcn/ui",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "dependencies": {
24
+ "@cisri/core": "1.0.0"
25
+ },
26
+ "peerDependencies": {
27
+ "react": "^18.0.0 || ^19.0.0",
28
+ "react-dom": "^18.0.0 || ^19.0.0",
29
+ "@radix-ui/react-checkbox": "^1.0.0",
30
+ "@radix-ui/react-select": "^2.0.0",
31
+ "@radix-ui/react-slot": "^1.0.0",
32
+ "class-variance-authority": "^0.7.0",
33
+ "clsx": "^2.0.0",
34
+ "lucide-react": "^0.300.0",
35
+ "tailwind-merge": "^2.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@radix-ui/react-checkbox": {
39
+ "optional": true
40
+ },
41
+ "@radix-ui/react-select": {
42
+ "optional": true
43
+ },
44
+ "@radix-ui/react-slot": {
45
+ "optional": true
46
+ },
47
+ "class-variance-authority": {
48
+ "optional": true
49
+ },
50
+ "clsx": {
51
+ "optional": true
52
+ },
53
+ "lucide-react": {
54
+ "optional": true
55
+ },
56
+ "tailwind-merge": {
57
+ "optional": true
58
+ }
59
+ },
60
+ "devDependencies": {
61
+ "@types/react": "^18.3.0",
62
+ "@types/react-dom": "^18.3.0",
63
+ "react": "^18.3.1",
64
+ "react-dom": "^18.3.1",
65
+ "typescript": "^5.4.5",
66
+ "vite": "^5.2.11"
67
+ },
68
+ "scripts": {
69
+ "build": "tsc && vite build"
70
+ }
71
+ }