diff --git a/bun.lockb b/bun.lockb index 01bc7b98..203c8bb4 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts b/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts index 8ef3d717..89f999a4 100644 --- a/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts +++ b/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts @@ -13,14 +13,14 @@ export const chunkCleanup = ( exclude?: string[]; include?: string[]; }; - } = {} + } = {}, ): Plugin => { const isJSOrLike = createFilter([ /\.vue$/, /\.mdx$/, /\.tsx?$/, /\.mjs$/, - /\.jsx?$/ + /\.jsx?$/, ]); return { @@ -50,13 +50,13 @@ export const chunkCleanup = ( const result = await transform(code, { filename: id, env: opt.env ?? { targets: "defaults" }, - minify: false + minify: false, }); return ( result.code && { code: result.code, - map: result.map || null + map: result.map || null, } ); }, @@ -65,11 +65,9 @@ export const chunkCleanup = ( return ( await transform(code, { minify: opt.minify ?? false, - plugins: [ - usePlugin({}) - ] + plugins: [usePlugin({})], }) ).code; - } + }, }; }; diff --git a/nodedevpkg/vue-vite-presets/src/mdx/index.tsx b/nodedevpkg/vue-vite-presets/src/mdx/index.tsx index 87d6e08b..fd03c213 100644 --- a/nodedevpkg/vue-vite-presets/src/mdx/index.tsx +++ b/nodedevpkg/vue-vite-presets/src/mdx/index.tsx @@ -43,31 +43,31 @@ export const mdx = (): PluginOption => { const rawCode = children[0].value; const exportName = `CodeBlock${getHash( - `${metadata["filename"] ?? pos}` + `${metadata["filename"] ?? pos}`, )}`; const id = vc.store(`${mdxFile}~${exportName}.tsx`, rawCode); additionalImports.set(mdxFile, { ...(additionalImports.get(mdxFile) ?? {}), - [id]: exportName + [id]: exportName, }); tree.children[pos] = h( "div", { - "data-example": "" + "data-example": "", }, [ h( "div", { - "data-example-container": "" + "data-example-container": "", }, - h(exportName) + h(exportName), ), - pre - ] + pre, + ], ); } } @@ -80,7 +80,7 @@ export const mdx = (): PluginOption => { include: [/\.mdx?$/], jsxRuntime: "automatic", jsxImportSource: "@innoai-tech/vuekit", - rehypePlugins: [rehypeRenderCodeBlock, rehypePrism] + rehypePlugins: [rehypeRenderCodeBlock, rehypePrism], }); return { @@ -114,36 +114,36 @@ export const mdx = (): PluginOption => { ...ret, code: ` ${Object.keys(codeBlockImports) - .map( - (importPath) => ` -import ${codeBlockImports[importPath]} from ${JSON.stringify(importPath)}` - ) - .join(";\n")} + .map( + (importPath) => ` +import ${codeBlockImports[importPath]} from ${JSON.stringify(importPath)}`, + ) + .join(";\n")} import { defineComponent, h } from "vue" ${ret.code.replace( - "export default function MDXContent(", - "function MDXContent(" - )} + "export default function MDXContent(", + "function MDXContent(", +)} export default defineComponent(() => { return () => h(MDXContent, { components: { ${Object.keys(codeBlockImports) - .map( - (importPath) => - `${codeBlockImports[importPath]?.toLowerCase()}: ${ - codeBlockImports[importPath] - }` - ) - .join(",\n")} + .map( + (importPath) => + `${codeBlockImports[importPath]?.toLowerCase()}: ${ + codeBlockImports[importPath] + }`, + ) + .join(",\n")} } }) }) -` +`, }; } - } + }, }; }; diff --git a/nodedevpkg/vue-vite-presets/src/viteVue.ts b/nodedevpkg/vue-vite-presets/src/viteVue.ts index b7c216b3..55d038cb 100644 --- a/nodedevpkg/vue-vite-presets/src/viteVue.ts +++ b/nodedevpkg/vue-vite-presets/src/viteVue.ts @@ -2,10 +2,14 @@ import vue from "@vitejs/plugin-vue"; import type { PluginOption } from "vite"; import vitePages, { type PageResolver, - type PageOptions + type PageOptions, } from "vite-plugin-pages"; import { mdx } from "./mdx"; -import { createPageMetaResolver, viteVueComponentCompleter, viteVueComponentHMR } from "./vue"; +import { + createPageMetaResolver, + viteVueComponentCompleter, + viteVueComponentHMR, +} from "./vue"; export interface ViteReactOptions { pagesDirs?: string | (string | PageOptions)[]; @@ -27,8 +31,8 @@ export const viteVue = (options: ViteReactOptions = {}): PluginOption[] => { onRoutesGenerated: r.onRoutesGenerated, resolver: { ...r.pagesResolver, - ...options.pagesResolver - } - }) as PluginOption + ...options.pagesResolver, + }, + }) as PluginOption, ]; }; diff --git a/nodedevpkg/vue-vite-presets/src/vue/componentCompleter.ts b/nodedevpkg/vue-vite-presets/src/vue/componentCompleter.ts index 0bd46d69..b9e1217f 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/componentCompleter.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/componentCompleter.ts @@ -2,18 +2,17 @@ import { type Plugin, createFilter } from "vite"; import { usePlugin } from "@innoai-tech/vuecomponentcompleter"; import { transform } from "@swc/core"; - export interface ComponentCompleterOptions { include?: string[]; exclude?: string[]; } export const viteVueComponentCompleter = ( - options: ComponentCompleterOptions = {} + options: ComponentCompleterOptions = {}, ): Plugin => { const filter = createFilter( options.include || [/\.tsx$/, /\.mdx?$/], - options.exclude + options.exclude, ); return { @@ -34,24 +33,24 @@ export const viteVueComponentCompleter = ( externalHelpers: false, parser: { syntax: "typescript", - tsx: true + tsx: true, }, experimental: { disableBuiltinTransformsForInternalTesting: true, - plugins: [usePlugin({})] - } - } + plugins: [usePlugin({})], + }, + }, }); return ( result.code && { code: result.code, - map: result.map || null + map: result.map || null, } ); } return null; - } + }, }; }; diff --git a/nodedevpkg/vue-vite-presets/src/vue/componentHMR.ts b/nodedevpkg/vue-vite-presets/src/vue/componentHMR.ts index 1772fce9..e9a5d0c0 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/componentHMR.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/componentHMR.ts @@ -18,12 +18,10 @@ export interface Module { exports: Map; } -export const viteVueComponentHMR = ( - options: VueJsxHmrOptions = {} -): Plugin => { +export const viteVueComponentHMR = (options: VueJsxHmrOptions = {}): Plugin => { const filter = createFilter( options.include || [/\.tsx$/, /\.mdx?$/], - options.exclude + options.exclude, ); let hmrEnabled = false; @@ -50,7 +48,7 @@ export const viteVueComponentHMR = ( } return null; - } + }, }; }; @@ -80,7 +78,6 @@ ${callbackBlock} return code; } - export const exportScanner = (id: string, filename = id) => { const re = /export (const (?\w+) =|default) (?(styled|component\$?)\()/; @@ -89,7 +86,7 @@ export const exportScanner = (id: string, filename = id) => { scan(code: string): Module { const ret = { code: "", - exports: new Map() + exports: new Map(), }; let src = code; let m: RegExpMatchArray | null = null; @@ -108,11 +105,13 @@ export const exportScanner = (id: string, filename = id) => { const local = exported !== "default" ? exported - : upperFirst(camelCase(`${basename(filename, extname(filename))}Default`)); + : upperFirst( + camelCase(`${basename(filename, extname(filename))}Default`), + ); const range = { start: m.index ?? 0, - length: m[0].length + length: m[0].length, }; ret.exports.set(local, { exported, id: getHash(`${id}#${exported}`) }); @@ -143,6 +142,6 @@ export { ${nonDefaultExports.join(", ")} } } return ret; - } + }, }; }; diff --git a/nodepkg/csstype/package.json b/nodepkg/csstype/package.json index e0ae17b3..d755e4e3 100644 --- a/nodepkg/csstype/package.json +++ b/nodepkg/csstype/package.json @@ -19,4 +19,4 @@ "sideEffects": false, "type": "module", "types": "index.d.ts" -} +} \ No newline at end of file diff --git a/nodepkg/gents/src/__tests__/client/example.ts b/nodepkg/gents/src/__tests__/client/example.ts index a0c540e5..07be5296 100644 --- a/nodepkg/gents/src/__tests__/client/example.ts +++ b/nodepkg/gents/src/__tests__/client/example.ts @@ -204,8 +204,8 @@ manifest = "manifest" export const displayKubepkgV1Alpha1DigestMetaType = (v: KubepkgV1Alpha1DigestMetaType) => { return ({ -blob: "Blob", -manifest: "Manifest" +"blob": "Blob", +"manifest": "Manifest" })[v] ?? v } diff --git a/nodepkg/jsoneditor/package.json b/nodepkg/jsoneditor/package.json new file mode 100644 index 00000000..3101ec15 --- /dev/null +++ b/nodepkg/jsoneditor/package.json @@ -0,0 +1,51 @@ +{ + "name": "@innoai-tech/jsoneditor", + "version": "0.1.4", + "monobundle": { + "build": { + "clean": true + }, + "exports": { + ".": "./src/index.ts" + } + }, + "dependencies": { + "@innoai-tech/vuekit": "workspace:^", + "@innoai-tech/vuemarkdown": "workspace:^", + "@innoai-tech/vuematerial": "workspace:^", + "@innoai-tech/vueuikit": "workspace:^", + "@mdi/js": "^7.4.47", + "copy-to-clipboard": "^3.3.3" + }, + "peerDependencies": {}, + "exports": { + ".": { + "bun": "./src/index.ts", + "import": { + "types": "./src/index.ts", + "default": "./dist/index.mjs" + } + } + }, + "files": [ + "dist/*", + "src/*", + "!/**/__tests__" + ], + "license": "MIT", + "publishConfig": { + "registry": "https://npm.pkg.github.com", + "access": "public" + }, + "repository": { + "type": "git", + "url": "ssh://git@github.com:innoai-tech/vuekit.git", + "directory": "nodepkg/jsoneditor" + }, + "scripts": { + "lint": "bunx --bun prettier --write . ", + "build": "bunx --bun monobundle", + "prepublishOnly": "bun run build" + }, + "type": "module" +} diff --git a/nodepkg/jsoneditor/src/JSONEditorView.tsx b/nodepkg/jsoneditor/src/JSONEditorView.tsx new file mode 100644 index 00000000..d7bf3bc9 --- /dev/null +++ b/nodepkg/jsoneditor/src/JSONEditorView.tsx @@ -0,0 +1,102 @@ +import { + type AnyType, + component$, + type Context, + EmptyContext, + rx, +} from "@innoai-tech/vuekit"; +import { JSONEditorProvider, JSONEditorSlotsProvider } from "./models"; +import { + ObjectInput, + ArrayInput, + RecordInput, + EnumInput, + NumberInput, + BooleanInput, + StringInput, + LayoutContextProvider, + Line, +} from "./views"; +import { styled } from "@innoai-tech/vueuikit"; +import { ref } from "vue"; + +export const JSONEditorView = component$(({}, { render }) => { + const editor$ = JSONEditorProvider.use(); + + const renderValues = (typedef: AnyType, value: any, ctx: Context) => { + if ( + typedef.type == "object" || + typedef.type == "intersection" || + typedef.type == "union" + ) { + return ; + } + + if (typedef.type == "record") { + return ; + } + + if (typedef.type == "array") { + return ; + } + + if (typedef.type == "enums") { + return ; + } + + if (typedef.type == "string") { + return ; + } + + if (typedef.type == "number" || typedef.type == "integer") { + return ; + } + + if (typedef.type == "boolean") { + return ; + } + + return null; + }; + + const $container = ref(null); + + return rx( + editor$, + render((root: any) => { + return ( + + +
+ {$container.value && ( + + {renderValues(editor$.typedef, root, EmptyContext)} + + )} + + + ); + }), + ); +}); + +const JSONEditorContainer = styled("div")({ + width: "100%", + height: "100%", + overflow: "auto", + + section: { + display: "flex", + flexDirection: "column", + minWidth: "max-content", + }, +}); diff --git a/nodepkg/jsoneditor/src/JSONPointer.ts b/nodepkg/jsoneditor/src/JSONPointer.ts new file mode 100644 index 00000000..2e7d0d57 --- /dev/null +++ b/nodepkg/jsoneditor/src/JSONPointer.ts @@ -0,0 +1,26 @@ +export class JSONPointer { + static parse(pointer: string): any[] { + if (pointer === "") { + return []; + } + if (pointer.charAt(0) !== "/") { + throw new Error("Invalid JSON pointer: " + pointer); + } + return pointer.substring(1).split(/\//).map(JSONPointer.unescape); + } + + static compile(keyPath: any[]) { + if (keyPath.length === 0) { + return ""; + } + return "/" + keyPath.map(JSONPointer.escape).join("/"); + } + + static unescape(str: string) { + return str.replace(/~1/g, "/").replace(/~0/g, "~"); + } + + static escape(str: string) { + return str.toString().replace(/~/g, "~0").replace(/\//g, "~1"); + } +} diff --git a/nodepkg/jsoneditor/src/index.ts b/nodepkg/jsoneditor/src/index.ts new file mode 100644 index 00000000..0aad583e --- /dev/null +++ b/nodepkg/jsoneditor/src/index.ts @@ -0,0 +1,3 @@ +export * from "./models"; +export * from "./JSONEditorView.tsx"; +export * from "./JSONPointer.ts"; diff --git a/nodepkg/jsoneditor/src/models/JSONEditor.tsx b/nodepkg/jsoneditor/src/models/JSONEditor.tsx new file mode 100644 index 00000000..512d6048 --- /dev/null +++ b/nodepkg/jsoneditor/src/models/JSONEditor.tsx @@ -0,0 +1,46 @@ +import { + type AnyType, + type Context, + createProvider, + ImmerBehaviorSubject, + type Infer, + t, + Type, +} from "@innoai-tech/vuekit"; +import { get, isPlainObject, isUndefined } from "@innoai-tech/lodash"; + +export class JSONEditor extends ImmerBehaviorSubject> { + static of(typedef: T, initials?: Partial>) { + return new JSONEditor( + typedef, + initials ?? (typedef.type == "array" ? [] : {}), + ); + } + + constructor( + public typedef: T, + protected initials: Infer, + ) { + super(initials); + } + + isDirty(value: any, path: any[]) { + if (!isPlainObject(value)) { + const v = get(this.initials, path); + + return isUndefined(v) || v !== value; + } + return false; + } +} + +export const JSONEditorProvider = createProvider( + () => new JSONEditor(t.object(), {}), +); + +export const JSONEditorSlotsProvider = createProvider(() => { + return { + render: (_t: AnyType, _value: any, _ctx: Context): JSX.Element | null => + null, + }; +}); diff --git a/nodepkg/jsoneditor/src/models/index.ts b/nodepkg/jsoneditor/src/models/index.ts new file mode 100644 index 00000000..8fbfc9bd --- /dev/null +++ b/nodepkg/jsoneditor/src/models/index.ts @@ -0,0 +1 @@ +export * from "./JSONEditor.tsx"; diff --git a/nodepkg/jsoneditor/src/views/Actions.tsx b/nodepkg/jsoneditor/src/views/Actions.tsx new file mode 100644 index 00000000..60d8ae19 --- /dev/null +++ b/nodepkg/jsoneditor/src/views/Actions.tsx @@ -0,0 +1,56 @@ +import { styled } from "@innoai-tech/vueuikit"; +import type { VNodeChild } from "@innoai-tech/vuekit"; + +const ActionToolbar = styled("span")({ + pos: "relative", + px: 8, + display: "flex", + alignItems: "center", + gap: 8 +}); + +export const Actions = styled< + { + $default?: VNodeChild; + }, + "span" +>("span", ({}, { slots }) => { + return (Root) => ( + + {slots.default?.()} + + ); +})({ + flex: 1, + lineHeight: 18, + + wordBreak: "keep-all", + whiteSpace: "nowrap", + display: "inline-flex", + alignItems: "center", + + [`& ${ActionToolbar}`]: { + visibility: "hidden" + }, + + _hover: { + [`& ${ActionToolbar}`]: { + visibility: "visible" + } + } +}); + +export const ActionBtn = styled("span")({ + display: "flex", + alignItems: "center", + justifyContent: "center", + height: 16, + width: 16, + opacity: 0.5, + cursor: "pointer", + textStyle: "sys.label-small", + + _hover: { + opacity: 0.8 + } +}); diff --git a/nodepkg/jsoneditor/src/views/ArrayInput.tsx b/nodepkg/jsoneditor/src/views/ArrayInput.tsx new file mode 100644 index 00000000..f20859f5 --- /dev/null +++ b/nodepkg/jsoneditor/src/views/ArrayInput.tsx @@ -0,0 +1,126 @@ +import { + type AnyType, + component$, + type Context, + rx, + type VNodeChild +} from "@innoai-tech/vuekit"; +import { get, set } from "@innoai-tech/lodash"; +import { Icon } from "@innoai-tech/vuematerial"; +import { mdiMinusBoxOutline, mdiPlusBoxOutline } from "@mdi/js"; +import { Block, Line, PropName } from "./TokenView.tsx"; +import { JSONEditorProvider, JSONEditorSlotsProvider } from "../models"; +import { ActionBtn, Actions } from "./Actions.tsx"; +import { CopyAsJSONIconBtn, InputFromJSONRawIconBtn } from "./JSONRaw.tsx"; +import { Tooltip } from "./Tooltip.tsx"; + + +export const ArrayInput = component$<{ + ctx: Context; + value: []; + typedef: AnyType; +}>((props, { render }) => { + const editor$ = JSONEditorProvider.use(); + const slots = JSONEditorSlotsProvider.use(); + + return rx( + props.value$, + render((obj) => { + return ( + + { + editor$.next((values: any) => { + const arr = get(values, props.ctx.path, []); + + set(values, props.ctx.path, [...arr, undefined]); + }); + }} + /> + + { + editor$.next((values: any) => { + if (props.ctx.path.length) { + set(values, props.ctx.path, updated); + } else { + Object.assign(values, updated); + } + }); + }} + /> + + } + > + {[...props.typedef.entries(obj, props.ctx)].map( + ([idx, itemValue, propSchema]) => { + const path = [...props.ctx.path, idx]; + + return ( + + { + editor$.next((values: any) => { + let arr = get(values, props.ctx.path, [] as any[]); + + set( + values, + props.ctx.path, + arr.filter((_: any, i: number) => i !== idx) + ); + }); + }} + /> + } + > + {String(idx)} + + {slots.render(propSchema, itemValue, { + ...props.ctx, + path: path, + branch: [...props.ctx.branch, itemValue] + })} + + ); + } + )} + + ); + }) + ); +}); + +const AddItemIconBtn = component$<{ + $default?: VNodeChild; + onAdd?: () => void; +}>(({}, { emit }) => { + return () => ( + + emit("add")}> + + + + ); +}); + +const RemoteItemIconBtn = component$<{ + $default?: VNodeChild; + onRemove?: () => void; +}>(({}, { emit }) => { + return () => ( + + emit("remove")}> + + + + ); +}); diff --git a/nodepkg/jsoneditor/src/views/BooleanInput.tsx b/nodepkg/jsoneditor/src/views/BooleanInput.tsx new file mode 100644 index 00000000..f333225a --- /dev/null +++ b/nodepkg/jsoneditor/src/views/BooleanInput.tsx @@ -0,0 +1,48 @@ +import { + type AnyType, + component$, + type Context, + rx, +} from "@innoai-tech/vuekit"; +import { JSONEditorProvider } from "../models"; +import { set } from "@innoai-tech/lodash"; +import { Menu, MenuItem, PopupStatus } from "./Menu.tsx"; +import { ValueView } from "./TokenView.tsx"; + +export const BooleanInput = component$<{ + ctx: Context; + value: any; + typedef: AnyType; +}>((props, { render }) => { + const editor$ = JSONEditorProvider.use(); + const open$ = new PopupStatus(false); + + return rx( + props.value$, + render((value) => { + const enumValues = [false, true]; + + return ( + { + editor$.next((values: any) => { + set(values, props.ctx.path, value === "true"); + }); + }} + $content={ + <> + {enumValues.map((v) => ( + +
{v}
+
+ ))} + + } + > + open$.show()} /> +
+ ); + }), + ); +}); diff --git a/nodepkg/jsoneditor/src/views/EnumInput.tsx b/nodepkg/jsoneditor/src/views/EnumInput.tsx new file mode 100644 index 00000000..a087dc20 --- /dev/null +++ b/nodepkg/jsoneditor/src/views/EnumInput.tsx @@ -0,0 +1,50 @@ +import { + type AnyType, + component$, + type Context, + rx, +} from "@innoai-tech/vuekit"; +import { JSONEditorProvider } from "../models"; +import { set } from "@innoai-tech/lodash"; +import { Menu, MenuItem, PopupStatus } from "./Menu.tsx"; +import { ValueView } from "./TokenView.tsx"; + +export const EnumInput = component$<{ + ctx: Context; + value: any; + typedef: AnyType; +}>((props, { render }) => { + const editor$ = JSONEditorProvider.use(); + const open$ = new PopupStatus(false); + + return rx( + props.value$, + render((value) => { + const enumValues = props.typedef.getSchema("enum") ?? []; + const enumLabels = props.typedef.getMeta("enumLabels") ?? []; + + return ( + { + editor$.next((values: any) => { + set(values, props.ctx.path, value); + }); + }} + $content={ + <> + {enumValues.map((v, i) => ( + +
{v}
+ {enumLabels[i] &&
{enumLabels[i]}
} +
+ ))} + + } + > + open$.show()} /> +
+ ); + }), + ); +}); diff --git a/nodepkg/jsoneditor/src/views/Form.tsx b/nodepkg/jsoneditor/src/views/Form.tsx new file mode 100644 index 00000000..7772c542 --- /dev/null +++ b/nodepkg/jsoneditor/src/views/Form.tsx @@ -0,0 +1,45 @@ +import { styled } from "@innoai-tech/vueuikit"; + +export const FormControls = styled("form")({ + display: "flex", + pos: "absolute", + right: 0, + bottom: 0, + px: 8, +}); + +export const FormContainer = styled("div")({ + display: "block", + pos: "relative", + + textarea: { + minW: "20vw", + outline: "none", + border: "none", + bg: "none", + py: 8, + px: 12, + }, +}); + +export const FormContainerAsRow = styled("div")({ + display: "flex", + alignItems: "center", + gap: 8, + pos: "relative", + px: 8, + + input: { + outline: "none", + border: "none", + bg: "none", + minWidth: "10vw", + py: 8, + px: 12, + }, + + [`& ${FormControls}`]: { + pos: "relative", + px: 0, + }, +}); diff --git a/nodepkg/jsoneditor/src/views/JSONRaw.tsx b/nodepkg/jsoneditor/src/views/JSONRaw.tsx new file mode 100644 index 00000000..6757a9a1 --- /dev/null +++ b/nodepkg/jsoneditor/src/views/JSONRaw.tsx @@ -0,0 +1,116 @@ +import { component$, ImmerBehaviorSubject, rx } from "@innoai-tech/vuekit"; +import { Icon, IconButton } from "@innoai-tech/vuematerial"; +import { ActionBtn } from "./Actions.tsx"; +import copyToClipboard from "copy-to-clipboard"; +import { mdiCancel, mdiCheck, mdiCodeJson, mdiContentCopy } from "@mdi/js"; +import { Popover, PopupStatus } from "./Menu.tsx"; +import { onMounted, ref } from "vue"; +import { FormContainer, FormControls } from "./Form.tsx"; +import { Tooltip } from "./Tooltip.tsx"; + +export const CopyAsJSONIconBtn = component$<{ + value?: any; +}>((props, {}) => { + return () => ( + + copyToClipboard(JSON.stringify(props.value, null, 2))}> + + + + ); +}); + +export const InputFromJSONRawIconBtn = component$<{ + onInput?: (prop: string) => void; +}>(({}, { emit }) => { + const open$ = new PopupStatus(false); + + return ( + { + if (v) { + emit("input", v); + } + open$.hide(); + }} + /> + } + > + + open$.show()}> + + + + + ); +}); + +const JSONRawForm = component$<{ + onSubmit: (value?: any) => void; +}>(({}, { emit, render }) => { + const input$ = new ImmerBehaviorSubject(""); + const $input = ref(null); + + const cancel = () => { + emit("submit", undefined); + }; + + const submit = () => { + try { + emit("submit", JSON.parse(input$.value!)); + } catch (e) { + } + }; + + const handleUserKeyPress = (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + submit(); + } + }; + + onMounted(() => { + $input.value?.focus(); + }); + + return rx( + input$, + render((input) => { + return ( + { + evt.preventDefault(); + submit(); + }} + > +