diff --git a/Makefile b/Makefile index b8ae7153..84925972 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ build.app: cp ./public/vuekit/index.html ./public/vuekit/404.html preview: - $(VITE) preview + $(VITE) preview --host=127.0.0.1 build.pkg: $(TURBO) run build diff --git a/biome.json b/biome.json deleted file mode 100644 index 7c6d2cfb..00000000 --- a/biome.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", - "vcs": { - "clientKind": "git", - "enabled": true, - "useIgnoreFile": true - }, - "files": { - "ignore": ["*.d.ts", "*.js", "*.mjs"] - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "off" - }, - "complexity": { - "useLiteralKeys": "off", - "noBannedTypes": "off" - }, - "a11y": { - "useKeyWithClickEvents": "off", - "noSvgWithoutTitle": "off" - } - } - } -} diff --git a/bun.lockb b/bun.lockb index 99b157ed..9883b5a2 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/nodedevpkg/vue-vite-presets/package.json b/nodedevpkg/vue-vite-presets/package.json index d79e7142..f8524032 100644 --- a/nodedevpkg/vue-vite-presets/package.json +++ b/nodedevpkg/vue-vite-presets/package.json @@ -1,6 +1,6 @@ { "name": "@innoai-tech/vue-vite-presets", - "version": "0.8.0", + "version": "0.8.1", "monobundle": { "pipeline": { "test": false @@ -11,13 +11,13 @@ }, "dependencies": { "@innoai-tech/lodash": "^0.2.1", - "@innoai-tech/purebundle": "^0.2.12", + "@innoai-tech/purebundle": "^0.3.0", "@mapbox/rehype-prism": "^0.9.0", "@mdx-js/rollup": "^3.0.1", "@vitejs/plugin-vue": "^5.0.4", "hastscript": "^9.0.0", "unist-util-visit": "^5.0.0", - "vite": "^5.1.3", + "vite": "^5.1.4", "vite-plugin-pages": "^0.32.0", "vite-tsconfig-paths": "^4.3.1" }, @@ -48,7 +48,7 @@ "directory": "nodedevpkg/vue-vite-presets" }, "scripts": { - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodedevpkg/vue-vite-presets/src/app.ts b/nodedevpkg/vue-vite-presets/src/app.ts index c46f025a..a796aa06 100644 --- a/nodedevpkg/vue-vite-presets/src/app.ts +++ b/nodedevpkg/vue-vite-presets/src/app.ts @@ -1,83 +1,86 @@ import { resolve } from "path"; import { - type PluginOption, - type UserConfig, - searchForWorkspaceRoot, + type PluginOption, + type UserConfig, + searchForWorkspaceRoot, } from "vite"; import tsconfigPaths from "vite-tsconfig-paths"; export interface AppConfig { - enableBaseHref?: boolean; - buildWithPlaceHolder?: boolean; + enableBaseHref?: boolean; + buildWithPlaceHolder?: boolean; } export const app = ( - appName: string, - appConfig: AppConfig = {}, + appName: string, + appConfig: AppConfig = {}, ): PluginOption[] => { - (process.env as any).APP_VERSION = "__VERSION__"; - - const viteConfigRoot = searchForWorkspaceRoot("."); - - let userConfig: UserConfig; - - return [ - { - name: "vite-presets/app", - config(c, { command }) { - userConfig = c; - - c.base = appConfig.enableBaseHref - ? appConfig.buildWithPlaceHolder && command === "build" - ? "/__APP_BASE_HREF__/" - : `/${appName}/` - : "/"; - - c.publicDir = appConfig.enableBaseHref ? c.base : false; - - c.root = resolve(viteConfigRoot, `./webapp/${appName}`); - - c.build = c.build ?? {}; - - c.build.outDir = resolve(viteConfigRoot, `./public/${appName}`); - c.build.emptyOutDir = true; - - c.build.rollupOptions = c.build.rollupOptions ?? {}; - c.build.rollupOptions.external = c.build.rollupOptions.external ?? [ - "csstype", - ]; - - c.build.assetsDir = c.build.assetsDir ?? "__built__"; - - // to avoid some filename starts with _ - c.build.rollupOptions.output = { - assetFileNames: `${c.build.assetsDir}/[name].[hash][extname]`, - entryFileNames: `${c.build.assetsDir}/[name].[hash].entry.js`, - chunkFileNames: `${c.build.assetsDir}/[name].[hash].chunk.js`, - }; - - c.resolve = c.resolve ?? {}; - c.resolve.alias = c.resolve.alias ?? ({} as Record); - - c.esbuild = { - jsxDev: c.mode !== "production", - }; - }, - - transformIndexHtml(html: string) { - return { - html: html, - tags: [ - { - tag: "base", - attrs: { - href: userConfig.base ?? "/", - }, - }, - ], - }; - }, - }, - tsconfigPaths({}) as PluginOption, - ]; + (process.env as any).APP_VERSION = "__VERSION__"; + + const viteConfigRoot = searchForWorkspaceRoot("."); + + let userConfig: UserConfig; + + return [ + { + name: "vite-presets/app", + config(c, { command }) { + userConfig = c; + + c.base = appConfig.enableBaseHref + ? appConfig.buildWithPlaceHolder && command === "build" + ? "/__APP_BASE_HREF__/" + : `/${appName}/` + : "/"; + + c.publicDir = appConfig.enableBaseHref ? c.base : false; + + c.root = resolve(viteConfigRoot, `./webapp/${appName}`); + + c.build = c.build ?? {}; + + c.build.outDir = resolve( + viteConfigRoot, + c.build.outDir ?? `./public/${appName}`, + ); + c.build.emptyOutDir = true; + + c.build.rollupOptions = c.build.rollupOptions ?? {}; + c.build.rollupOptions.external = c.build.rollupOptions.external ?? [ + "csstype", + ]; + + c.build.assetsDir = c.build.assetsDir ?? "__built__"; + + // to avoid some filename starts with _ + c.build.rollupOptions.output = { + assetFileNames: `${c.build.assetsDir}/[name].[hash][extname]`, + entryFileNames: `${c.build.assetsDir}/[name].[hash].entry.js`, + chunkFileNames: `${c.build.assetsDir}/[name].[hash].chunk.js`, + }; + + c.resolve = c.resolve ?? {}; + c.resolve.alias = c.resolve.alias ?? ({} as Record); + + c.esbuild = { + jsxDev: c.mode !== "production", + }; + }, + + transformIndexHtml(html: string) { + return { + html: html, + tags: [ + { + tag: "base", + attrs: { + href: userConfig.base ?? "/", + }, + }, + ], + }; + }, + }, + tsconfigPaths({}) as PluginOption, + ]; }; diff --git a/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts b/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts index 2f2251dc..fa869346 100644 --- a/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts +++ b/nodedevpkg/vue-vite-presets/src/chunkCleanup.ts @@ -2,71 +2,71 @@ import { transform } from "@innoai-tech/purebundle"; import { type Plugin, createFilter } from "vite"; export const chunkCleanup = ( - opt: { - annotatePure?: boolean; - minify?: boolean; - env?: { - targets?: string | { [K: string]: string }; - mode?: string; - coreJs?: string; - exclude?: string[]; - include?: string[]; - }; - } = {}, + opt: { + annotatePure?: boolean; + minify?: boolean; + env?: { + targets?: string | { [K: string]: string }; + mode?: string; + coreJs?: string; + exclude?: string[]; + include?: string[]; + }; + } = {}, ): Plugin => { - const isJSOrLike = createFilter([ - /\.vue$/, - /\.mdx$/, - /\.tsx?$/, - /\.mjs$/, - /\.jsx?$/, - ]); + const isJSOrLike = createFilter([ + /\.vue$/, + /\.mdx$/, + /\.tsx?$/, + /\.mjs$/, + /\.jsx?$/, + ]); - return { - name: "monobundle/chunk-cleanup", - enforce: "post", - apply: "build", + return { + name: "monobundle/chunk-cleanup", + enforce: "post", + apply: "build", - config(c) { - c.build = c.build ?? {}; + config(c) { + c.build = c.build ?? {}; - if (opt.minify) { - // when minify set, disable default esbuild minify - c.build.minify = false; - } - }, + if (opt.minify) { + // when minify set, disable default esbuild minify + c.build.minify = false; + } + }, - async transform(code, id) { - if (!isJSOrLike(id)) { - return null; - } + async transform(code, id) { + if (!isJSOrLike(id)) { + return null; + } - if (id.includes("/node_modules/core-js/")) { - return null; - } + if (id.includes("/node_modules/core-js/")) { + return null; + } - // only for build - const result = await transform(code, { - filename: id, - env: opt.env ?? { targets: "defaults" }, - minify: false, - }); + // only for build + const result = await transform(code, { + filename: id, + env: opt.env ?? { targets: "defaults" }, + minify: false, + }); - return ( - result.code && { - code: result.code, - map: result.map || null, - } - ); - }, + return ( + result.code && { + code: result.code, + map: result.map || null, + } + ); + }, - async renderChunk(code: string) { - return ( - await transform(code, { - minify: opt.minify ?? false, - annotatePure: opt.annotatePure ?? true, - }) - ).code; - }, - }; + async renderChunk(code: string) { + return ( + await transform(code, { + minify: opt.minify ?? false, + annotatePure: opt.annotatePure ?? true, + }) + ).code; + }, + }; }; diff --git a/nodedevpkg/vue-vite-presets/src/helpers/VirtualCache.ts b/nodedevpkg/vue-vite-presets/src/helpers/VirtualCache.ts index 527baea9..4805084d 100644 --- a/nodedevpkg/vue-vite-presets/src/helpers/VirtualCache.ts +++ b/nodedevpkg/vue-vite-presets/src/helpers/VirtualCache.ts @@ -1,38 +1,38 @@ import type { ViteDevServer } from "vite"; export class VirtualCache { - private files = new Map(); - - private server?: ViteDevServer; - - bindDevServer(server: ViteDevServer) { - this.server = server; - } - - store(id: string, code: string) { - const finalID = id; - - if (this.server) { - const mod = this.server.moduleGraph.getModuleById(finalID); - if (mod) { - this.server.moduleGraph.invalidateModule( - mod, - undefined, - undefined, - true, - ); - } - } - - this.files.set(finalID, code); - return finalID; - } - - has(id: string) { - return this.files.has(id); - } - - get(id: string) { - return this.files.get(id); - } + private files = new Map(); + + private server?: ViteDevServer; + + bindDevServer(server: ViteDevServer) { + this.server = server; + } + + store(id: string, code: string) { + const finalID = id; + + if (this.server) { + const mod = this.server.moduleGraph.getModuleById(finalID); + if (mod) { + this.server.moduleGraph.invalidateModule( + mod, + undefined, + undefined, + true, + ); + } + } + + this.files.set(finalID, code); + return finalID; + } + + has(id: string) { + return this.files.has(id); + } + + get(id: string) { + return this.files.get(id); + } } diff --git a/nodedevpkg/vue-vite-presets/src/helpers/hash.ts b/nodedevpkg/vue-vite-presets/src/helpers/hash.ts index fb2b5485..717fed84 100644 --- a/nodedevpkg/vue-vite-presets/src/helpers/hash.ts +++ b/nodedevpkg/vue-vite-presets/src/helpers/hash.ts @@ -1,5 +1,5 @@ import { createHash } from "crypto"; export const getHash = (text: string) => { - return createHash("sha256").update(text).digest("hex").substring(0, 8); + return createHash("sha256").update(text).digest("hex").substring(0, 8); }; diff --git a/nodedevpkg/vue-vite-presets/src/mdx/index.tsx b/nodedevpkg/vue-vite-presets/src/mdx/index.tsx index d3d04e1e..52c93d12 100644 --- a/nodedevpkg/vue-vite-presets/src/mdx/index.tsx +++ b/nodedevpkg/vue-vite-presets/src/mdx/index.tsx @@ -10,139 +10,139 @@ import { VirtualCache, getHash } from "../helpers"; const reKeyValue = /\b(?[-\w]+)(?:=(?:"([^"]*)"|'([^']*)'|([^"'\s]+)))?/g; const parseMetadata = (meta: string) => { - const data: Record = {}; + const data: Record = {}; - for (const m of meta.matchAll(reKeyValue)) { - data[m.groups?.["key"] ?? ""] = m[2] || m[3] || m[4] || ""; - } + for (const m of meta.matchAll(reKeyValue)) { + data[m.groups?.["key"] ?? ""] = m[2] || m[3] || m[4] || ""; + } - return data; + return data; }; export const mdx = (): PluginOption => { - const vc = new VirtualCache(); - - const additionalImports = new Map>(); - - const rehypeRenderCodeBlock = () => { - return (tree: any, vfile: { path: string }) => { - const mdxFile = vfile.path; - - visit(tree, { tagName: "pre" }, visitor); - - function visitor(pre: any, pos: any) { - if (pre.children && pre.children.length > 0) { - const { tagName, data, properties, children } = pre.children[0]; - - if (tagName === "code" && data && data.meta) { - if ( - properties.className.includes("language-tsx") && - data.meta.includes("preview") - ) { - const metadata = parseMetadata(data.meta); - const rawCode = children[0].value; - - const exportName = `CodeBlock${getHash( - `${metadata["filename"] ?? pos}`, - )}`; - - const id = vc.store(`${mdxFile}~${exportName}.tsx`, rawCode); - - additionalImports.set(mdxFile, { - ...(additionalImports.get(mdxFile) ?? {}), - [id]: exportName, - }); - - tree.children[pos] = h( - "div", - { - "data-example": "", - }, - [ - h( - "div", - { - "data-example-container": "", - }, - h(exportName), - ), - pre, - ], - ); - } - } - } - } - }; - }; - - const origin = mdxRollup({ - include: [/\.mdx?$/], - jsxRuntime: "automatic", - jsxImportSource: "@innoai-tech/vuekit", - rehypePlugins: [rehypeRenderCodeBlock, rehypePrism], - }); - - return { - name: "vite-plugin/mdx", - - configureServer(server) { - vc.bindDevServer(server); - }, - - resolveId(source: string) { - if (vc.has(source)) { - return source; - } - return null; - }, - - load(id: string) { - if (vc.has(id)) { - return vc.get(id); - } - return null; - }, - - async transform(code, id) { - const ret = await (origin["transform"] as any)(code, id); - if (ret) { - const codeBlockImports = additionalImports.get(id) ?? {}; - - return { - ...ret, - code: ` + const vc = new VirtualCache(); + + const additionalImports = new Map>(); + + const rehypeRenderCodeBlock = () => { + return (tree: any, vfile: { path: string }) => { + const mdxFile = vfile.path; + + visit(tree, { tagName: "pre" }, visitor); + + function visitor(pre: any, pos: any) { + if (pre.children && pre.children.length > 0) { + const { tagName, data, properties, children } = pre.children[0]; + + if (tagName === "code" && data && data.meta) { + if ( + properties.className.includes("language-tsx") && + data.meta.includes("preview") + ) { + const metadata = parseMetadata(data.meta); + const rawCode = children[0].value; + + const exportName = `CodeBlock${getHash( + `${metadata["filename"] ?? pos}`, + )}`; + + const id = vc.store(`${mdxFile}~${exportName}.tsx`, rawCode); + + additionalImports.set(mdxFile, { + ...(additionalImports.get(mdxFile) ?? {}), + [id]: exportName, + }); + + tree.children[pos] = h( + "div", + { + "data-example": "", + }, + [ + h( + "div", + { + "data-example-container": "", + }, + h(exportName), + ), + pre, + ], + ); + } + } + } + } + }; + }; + + const origin = mdxRollup({ + include: [/\.mdx?$/], + jsxRuntime: "automatic", + jsxImportSource: "@innoai-tech/vuekit", + rehypePlugins: [rehypeRenderCodeBlock, rehypePrism], + }); + + return { + name: "vite-plugin/mdx", + + configureServer(server) { + vc.bindDevServer(server); + }, + + resolveId(source: string) { + if (vc.has(source)) { + return source; + } + return null; + }, + + load(id: string) { + if (vc.has(id)) { + return vc.get(id); + } + return null; + }, + + async transform(code, id) { + const ret = await (origin["transform"] as any)(code, id); + if (ret) { + const codeBlockImports = additionalImports.get(id) ?? {}; + + return { + ...ret, + code: ` ${Object.keys(codeBlockImports) - .map( - (importPath) => ` + .map( + (importPath) => ` import ${codeBlockImports[importPath]} from ${JSON.stringify(importPath)}`, - ) - .join(";\n")} + ) + .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/viteChunkSplit.ts b/nodedevpkg/vue-vite-presets/src/viteChunkSplit.ts index 1cd0b9ac..ba5abccf 100644 --- a/nodedevpkg/vue-vite-presets/src/viteChunkSplit.ts +++ b/nodedevpkg/vue-vite-presets/src/viteChunkSplit.ts @@ -2,326 +2,326 @@ import { readFileSync } from "fs"; import { dirname, join, relative, resolve } from "path"; import { forEach, get } from "@innoai-tech/lodash"; import { - type ManualChunkMeta, - type OutputOptions, - type PreRenderedChunk, + type ManualChunkMeta, + type OutputOptions, + type PreRenderedChunk, } from "rollup"; import { - type FilterPattern, - type PluginOption, - createFilter, - searchForWorkspaceRoot, + type FilterPattern, + type PluginOption, + createFilter, + searchForWorkspaceRoot, } from "vite"; export interface ChunkSplitOptions { - lib?: FilterPattern; - handleModuleFederations?: ( - pkgRelations: Record, - ) => void; + lib?: FilterPattern; + handleModuleFederations?: ( + pkgRelations: Record, + ) => void; } export const viteChunkSplit = ( - options: ChunkSplitOptions = {}, + options: ChunkSplitOptions = {}, ): PluginOption => { - const viteRoot = searchForWorkspaceRoot("."); - const cs = new ChunkSplit(resolve(viteRoot), options); - - return { - name: "vite-presets/chunk-split", - apply: "build", - config(c) { - c.build = c.build ?? {}; - - const assetsDir = c.build.assetsDir ?? "assets"; - - c.build.rollupOptions = c.build.rollupOptions ?? {}; - - c.build.rollupOptions.output = - c.build.rollupOptions.output ?? ({} as OutputOptions); - const chunkFileNames = get( - c.build.rollupOptions.output, - ["chunkFileNames"], - `${assetsDir}/[name].[hash].chunk.js`, - ); - - (c.build.rollupOptions.output as any) = { - ...c.build.rollupOptions.output, - chunkFileNames: (chunkInfo: PreRenderedChunk) => { - if ( - chunkInfo.name.startsWith("lib-") || - chunkInfo.name.startsWith("webapp-") || - chunkInfo.name.startsWith("vendor-") - ) { - return chunkFileNames; - } - - const name = cs - .extractName(chunkInfo.moduleIds[0]!) - .replaceAll(/[\[\]]/g, "_") - .replaceAll("/", "-"); - return `${assetsDir}/${name}.[hash].chunk.js`; - }, - }; - }, - outputOptions(o) { - o.manualChunks = (id: string, meta: ManualChunkMeta) => { - return cs.chunkName(id, meta)?.replaceAll("/", "-").replaceAll("@", ""); - }; - }, - }; + const viteRoot = searchForWorkspaceRoot("."); + const cs = new ChunkSplit(resolve(viteRoot), options); + + return { + name: "vite-presets/chunk-split", + apply: "build", + config(c) { + c.build = c.build ?? {}; + + const assetsDir = c.build.assetsDir ?? "assets"; + + c.build.rollupOptions = c.build.rollupOptions ?? {}; + + c.build.rollupOptions.output = + c.build.rollupOptions.output ?? ({} as OutputOptions); + const chunkFileNames = get( + c.build.rollupOptions.output, + ["chunkFileNames"], + `${assetsDir}/[name].[hash].chunk.js`, + ); + + (c.build.rollupOptions.output as any) = { + ...c.build.rollupOptions.output, + chunkFileNames: (chunkInfo: PreRenderedChunk) => { + if ( + chunkInfo.name.startsWith("lib-") || + chunkInfo.name.startsWith("webapp-") || + chunkInfo.name.startsWith("vendor-") + ) { + return chunkFileNames; + } + + const name = cs + .extractName(chunkInfo.moduleIds[0]!) + .replaceAll(/[\[\]]/g, "_") + .replaceAll("/", "-"); + return `${assetsDir}/${name}.[hash].chunk.js`; + }, + }; + }, + outputOptions(o) { + o.manualChunks = (id: string, meta: ManualChunkMeta) => { + return cs.chunkName(id, meta)?.replaceAll("/", "-").replaceAll("@", ""); + }; + }, + }; }; class ChunkSplit { - private readonly isLib: (id: string) => boolean; - private readonly dependencies: Record; - - constructor( - private root: string, - private options: ChunkSplitOptions, - ) { - this.isLib = createFilter(options.lib ?? []); - this.dependencies = - JSON.parse(String(readFileSync(join(root, "package.json")))) - .dependencies ?? {}; - } - - private _pkgRelegation?: ReturnType; - - pkgRelegation(meta: ManualChunkMeta, pkgName: string) { - return (this._pkgRelegation = - this._pkgRelegation ?? this.resolvePkgRelations(meta))[pkgName]; - } - - chunkName(id: string, meta: ManualChunkMeta): string | undefined { - if (this.isLib(id)) { - return this.pkgRelegation(meta, this.extractPkgName(id))?.federation; - } - - if (id.includes("node_modules") || id.startsWith("\0")) { - const pkgName = this.extractPkgName(id); - - if (this.isDirectVendor(pkgName)) { - return this.pkgRelegation(meta, this.extractPkgName(id))?.federation; - } - } - - return; - } - - private resolvePkgRelations({ - getModuleInfo, - getModuleIds, - }: ManualChunkMeta) { - const directImports: Record = {}; - const moduleFederations: Record = {}; - - const moduleIds = [...getModuleIds()]; - - forEach(moduleIds, (modID) => { - const pkgName = this.extractPkgName(modID) || modID; - - const markImported = (dep: string) => { - const currentModuleFederation = (moduleFederations[pkgName] = - moduleFederations[pkgName] ?? new ModuleFederation(pkgName)); - const moduleFederation = (moduleFederations[dep] = - moduleFederations[dep] ?? new ModuleFederation(dep)); - - moduleFederation.importedBy(currentModuleFederation); - }; - - const m = getModuleInfo(modID)!; - - m.importedIds - .map((id) => this.extractPkgName(id)) - .filter((v) => v !== pkgName) - .forEach((dep) => { - directImports[dep] = true; - markImported(dep); - }); - - m.dynamicallyImportedIds - .map((id) => this.extractPkgName(id)) - .filter((v) => v !== pkgName) - .forEach((dep) => { - markImported(dep); - }); - }); - - for (const pkgName in directImports) { - if (pkgName.startsWith("@lib")) { - continue; - } - - if (!this.isDirectVendor(pkgName)) { - delete directImports[pkgName]; - } - } - - markPkgRelegation(moduleFederations, directImports); - - this.options.handleModuleFederations?.(moduleFederations); - - return moduleFederations; - } - - extractName(id: string): string { - if (id.startsWith(this.root)) { - return dirname(id.slice(this.root.length + 1)); - } - return this.extractPkgName(id); - } - - private extractPkgName(id: string): string { - if (this.isLib(id)) { - const base = relative(this.root, id); - if (base.startsWith("webapp/")) { - if (base.includes("/mod/")) { - return `@${base.split("/").slice(0, 4).join("-")}`; - } - return `@${base.split("/").slice(0, 3).join("-")}`; - } - return `@lib/${base.split("/").slice(0, 2).join("-")}`; - } - - const parts = id.split("/node_modules/"); - - if (parts.length === 1) { - if (id) { - // vite or rollup helpers - if (id[0] === "\0") { - if (/react/.test(id)) { - return "react"; - } - return "@internal"; - } - if (id[0] !== "/") { - return id.split("/")[0]!; - } - } - - if (id.startsWith(this.root + "/")) { - return `@${id.slice(this.root.length + 1).replaceAll(/[.\[\]]/g, "_")}`; - } - - return id; - } - - const dirPaths = parts[parts.length - 1]!.split("/"); - - if (dirPaths[0]![0] === "@") { - return `vendor-${dirPaths[0]}/${dirPaths[1]}`; - } - - return `vendor-${dirPaths[0]!}`; - } - - private isDirectVendor(pkgName: string) { - if (pkgName.startsWith("vendor-")) { - return this.dependencies[pkgName.slice("vendor-".length)]; - } - return this.dependencies[pkgName]; - } + private readonly isLib: (id: string) => boolean; + private readonly dependencies: Record; + + constructor( + private root: string, + private options: ChunkSplitOptions, + ) { + this.isLib = createFilter(options.lib ?? []); + this.dependencies = + JSON.parse(String(readFileSync(join(root, "package.json")))) + .dependencies ?? {}; + } + + private _pkgRelegation?: ReturnType; + + pkgRelegation(meta: ManualChunkMeta, pkgName: string) { + return (this._pkgRelegation = + this._pkgRelegation ?? this.resolvePkgRelations(meta))[pkgName]; + } + + chunkName(id: string, meta: ManualChunkMeta): string | undefined { + if (this.isLib(id)) { + return this.pkgRelegation(meta, this.extractPkgName(id))?.federation; + } + + if (id.includes("node_modules") || id.startsWith("\0")) { + const pkgName = this.extractPkgName(id); + + if (this.isDirectVendor(pkgName)) { + return this.pkgRelegation(meta, this.extractPkgName(id))?.federation; + } + } + + return; + } + + private resolvePkgRelations({ + getModuleInfo, + getModuleIds, + }: ManualChunkMeta) { + const directImports: Record = {}; + const moduleFederations: Record = {}; + + const moduleIds = [...getModuleIds()]; + + forEach(moduleIds, (modID) => { + const pkgName = this.extractPkgName(modID) || modID; + + const markImported = (dep: string) => { + const currentModuleFederation = (moduleFederations[pkgName] = + moduleFederations[pkgName] ?? new ModuleFederation(pkgName)); + const moduleFederation = (moduleFederations[dep] = + moduleFederations[dep] ?? new ModuleFederation(dep)); + + moduleFederation.importedBy(currentModuleFederation); + }; + + const m = getModuleInfo(modID)!; + + m.importedIds + .map((id) => this.extractPkgName(id)) + .filter((v) => v !== pkgName) + .forEach((dep) => { + directImports[dep] = true; + markImported(dep); + }); + + m.dynamicallyImportedIds + .map((id) => this.extractPkgName(id)) + .filter((v) => v !== pkgName) + .forEach((dep) => { + markImported(dep); + }); + }); + + for (const pkgName in directImports) { + if (pkgName.startsWith("@lib")) { + continue; + } + + if (!this.isDirectVendor(pkgName)) { + delete directImports[pkgName]; + } + } + + markPkgRelegation(moduleFederations, directImports); + + this.options.handleModuleFederations?.(moduleFederations); + + return moduleFederations; + } + + extractName(id: string): string { + if (id.startsWith(this.root)) { + return dirname(id.slice(this.root.length + 1)); + } + return this.extractPkgName(id); + } + + private extractPkgName(id: string): string { + if (this.isLib(id)) { + const base = relative(this.root, id); + if (base.startsWith("webapp/")) { + if (base.includes("/mod/")) { + return `@${base.split("/").slice(0, 4).join("-")}`; + } + return `@${base.split("/").slice(0, 3).join("-")}`; + } + return `@lib/${base.split("/").slice(0, 2).join("-")}`; + } + + const parts = id.split("/node_modules/"); + + if (parts.length === 1) { + if (id) { + // vite or rollup helpers + if (id[0] === "\0") { + if (/react/.test(id)) { + return "react"; + } + return "@internal"; + } + if (id[0] !== "/") { + return id.split("/")[0]!; + } + } + + if (id.startsWith(this.root + "/")) { + return `@${id.slice(this.root.length + 1).replaceAll(/[.\[\]]/g, "_")}`; + } + + return id; + } + + const dirPaths = parts[parts.length - 1]!.split("/"); + + if (dirPaths[0]![0] === "@") { + return `vendor-${dirPaths[0]}/${dirPaths[1]}`; + } + + return `vendor-${dirPaths[0]!}`; + } + + private isDirectVendor(pkgName: string) { + if (pkgName.startsWith("vendor-")) { + return this.dependencies[pkgName.slice("vendor-".length)]; + } + return this.dependencies[pkgName]; + } } export class ModuleFederation { - _federation?: string; + _federation?: string; - _rank = 0; - _imported = new Map(); + _rank = 0; + _imported = new Map(); - constructor(public name: string) {} + constructor(public name: string) {} - get federation() { - return this._federation ?? this.name; - } + get federation() { + return this._federation ?? this.name; + } - rank() { - return this._rank; - } + rank() { + return this._rank; + } - importedBy(moduleFederation: ModuleFederation) { - this._rank++; - this._imported.set(moduleFederation.name, moduleFederation); - } + importedBy(moduleFederation: ModuleFederation) { + this._rank++; + this._imported.set(moduleFederation.name, moduleFederation); + } - namesOfImported() { - return [...this._imported.keys()]; - } + namesOfImported() { + return [...this._imported.keys()]; + } - bindFederation(federation: string) { - this._federation = federation; - } + bindFederation(federation: string) { + this._federation = federation; + } } const markPkgRelegation = ( - moduleFederations: Record, - directs: Record, + moduleFederations: Record, + directs: Record, ) => { - const federations: Record = {}; - - for (const targetPkg in moduleFederations) { - let federation = targetPkg; - const visited: Record = {}; - - while (!visited[federation] && !directs[federation]) { - visited[federation] = true; - - const pkgRelation = moduleFederations[federation]!; - - if (pkgRelation) { - const names = pkgRelation.namesOfImported(); - - names.sort((a, b) => { - return (moduleFederations[a]?.rank() ?? 0) > - (moduleFederations[b]?.rank() ?? 0) - ? -1 - : 1; - }); - - federation = names[0]!; - continue; - } - break; - } - - moduleFederations[targetPkg]?.bindFederation(federation); - federations[federation] = true; - } - - for (const federation in federations) { - if (!moduleFederations[federation]) { - moduleFederations[federation] = new ModuleFederation(federation); - } - } + const federations: Record = {}; + + for (const targetPkg in moduleFederations) { + let federation = targetPkg; + const visited: Record = {}; + + while (!visited[federation] && !directs[federation]) { + visited[federation] = true; + + const pkgRelation = moduleFederations[federation]!; + + if (pkgRelation) { + const names = pkgRelation.namesOfImported(); + + names.sort((a, b) => { + return (moduleFederations[a]?.rank() ?? 0) > + (moduleFederations[b]?.rank() ?? 0) + ? -1 + : 1; + }); + + federation = names[0]!; + continue; + } + break; + } + + moduleFederations[targetPkg]?.bindFederation(federation); + federations[federation] = true; + } + + for (const federation in federations) { + if (!moduleFederations[federation]) { + moduleFederations[federation] = new ModuleFederation(federation); + } + } }; export const d2Graph = ( - moduleFederations: Record, + moduleFederations: Record, ) => { - let g = ""; - - const pkgId = (pkgName: string) => { - return pkgName.replaceAll("@", "").replaceAll("/", "-"); - }; - - const r = (pkgName: string) => { - if (moduleFederations[pkgName]) { - const federation = moduleFederations[pkgName]!.federation; - if (pkgName == federation) { - return pkgId(pkgName); - } - return `${pkgId(federation)}.${pkgId(pkgName)}`; - } - return `${pkgId(pkgName)}`; - }; - - for (const pkgName in moduleFederations) { - const pkgRelation = moduleFederations[pkgName]!; - for (const d of pkgRelation.namesOfImported()) { - g += `${r(pkgName)} -> ${r(d)} + let g = ""; + + const pkgId = (pkgName: string) => { + return pkgName.replaceAll("@", "").replaceAll("/", "-"); + }; + + const r = (pkgName: string) => { + if (moduleFederations[pkgName]) { + const federation = moduleFederations[pkgName]!.federation; + if (pkgName == federation) { + return pkgId(pkgName); + } + return `${pkgId(federation)}.${pkgId(pkgName)}`; + } + return `${pkgId(pkgName)}`; + }; + + for (const pkgName in moduleFederations) { + const pkgRelation = moduleFederations[pkgName]!; + for (const d of pkgRelation.namesOfImported()) { + g += `${r(pkgName)} -> ${r(d)} `; - } - } + } + } - return g; + return g; }; diff --git a/nodedevpkg/vue-vite-presets/src/viteVue.ts b/nodedevpkg/vue-vite-presets/src/viteVue.ts index f9fc0778..b3072af7 100644 --- a/nodedevpkg/vue-vite-presets/src/viteVue.ts +++ b/nodedevpkg/vue-vite-presets/src/viteVue.ts @@ -1,33 +1,33 @@ import vue from "@vitejs/plugin-vue"; import type { PluginOption } from "vite"; import vitePages, { - type PageResolver, - type PageOptions, + type PageResolver, + type PageOptions, } from "vite-plugin-pages"; import { mdx } from "./mdx"; import { createPageMetaResolver, viteVueComponentPatcher } from "./vue"; export interface ViteReactOptions { - pagesDirs?: string | (string | PageOptions)[]; - pagesResolver?: Partial; + pagesDirs?: string | (string | PageOptions)[]; + pagesResolver?: Partial; } export const viteVue = (options: ViteReactOptions = {}): PluginOption[] => { - const r = createPageMetaResolver(); + const r = createPageMetaResolver(); - return [ - r.plugin, - mdx(), - vue() as PluginOption, - viteVueComponentPatcher(), - vitePages({ - extensions: ["tsx", "mdx", "md", "vue"], - dirs: options.pagesDirs ?? "./page", // base from UserConfig.root - onRoutesGenerated: r.onRoutesGenerated, - resolver: { - ...r.pagesResolver, - ...options.pagesResolver, - }, - }) as PluginOption, - ]; + return [ + r.plugin, + mdx(), + vue() as PluginOption, + viteVueComponentPatcher(), + vitePages({ + extensions: ["tsx", "mdx", "md", "vue"], + dirs: options.pagesDirs ?? "./page", // base from UserConfig.root + onRoutesGenerated: r.onRoutesGenerated, + resolver: { + ...r.pagesResolver, + ...options.pagesResolver, + }, + }) as PluginOption, + ]; }; diff --git a/nodedevpkg/vue-vite-presets/src/vue/__tests__/componentPatcher.spec.ts b/nodedevpkg/vue-vite-presets/src/vue/__tests__/componentPatcher.spec.ts index e33df509..a5697cd5 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/__tests__/componentPatcher.spec.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/__tests__/componentPatcher.spec.ts @@ -2,23 +2,23 @@ import { describe, expect, test } from "bun:test"; import { exportScanner } from "../index"; describe("exportScanner", () => { - test("scan", () => { - const ret = exportScanner("./File.tsx").scan( - ` + test("scan", () => { + const ret = exportScanner("./File.tsx").scan( + ` export const X = styled("div")({}) export default styled("div")({}) `, - ); + ); - expect(ret).toEqual({ - code: ` + expect(ret).toEqual({ + code: ` const __X = styled("div")({}) const FileDefault = styled("div")({}) `, - exports: new Map([ - ["__X", { exported: "X", id: "d0e67569" }], - ["FileDefault", { exported: "default", id: "57d8e51a" }], - ]), - }); - }); + exports: new Map([ + ["__X", { exported: "X", id: "d0e67569" }], + ["FileDefault", { exported: "default", id: "57d8e51a" }], + ]), + }); + }); }); diff --git a/nodedevpkg/vue-vite-presets/src/vue/__tests__/index.spec.ts b/nodedevpkg/vue-vite-presets/src/vue/__tests__/index.spec.ts index 7ea495c0..0b6834be 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/__tests__/index.spec.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/__tests__/index.spec.ts @@ -6,28 +6,28 @@ import { extractRouteMeta } from "../index"; * @property {typeof import("@innoai-tech/vuematerial").mdiPlus} meta.icon */ describe("extractRouteMeta", () => { - test("should extract route meta", () => { - const meta = extractRouteMeta( - "", - ` + test("should extract route meta", () => { + const meta = extractRouteMeta( + "", + ` /** * @property {"名称"} meta.name * @property {typeof import("@innoai-tech/vuematerial").mdiPlus} meta.icon */ `, - ); + ); - expect(meta).toEqual({ - id: "", - meta: { - name: `"名称"`, - icon: "mdiPlus", - }, - imports: { - "@innoai-tech/vuematerial": { - mdiPlus: true, - }, - }, - }); - }); + expect(meta).toEqual({ + id: "", + meta: { + name: `"名称"`, + icon: "mdiPlus", + }, + imports: { + "@innoai-tech/vuematerial": { + mdiPlus: true, + }, + }, + }); + }); }); diff --git a/nodedevpkg/vue-vite-presets/src/vue/componentPatcher.ts b/nodedevpkg/vue-vite-presets/src/vue/componentPatcher.ts index 1e46f399..c51946eb 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/componentPatcher.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/componentPatcher.ts @@ -4,146 +4,146 @@ import { type Plugin, createFilter } from "vite"; import { getHash } from "../helpers"; export interface VueJsxHmrOptions { - include?: string[]; - exclude?: string[]; + include?: string[]; + exclude?: string[]; } export interface ModuleExport { - exported: string; - id: string; + exported: string; + id: string; } export interface Module { - code: string; - exports: Map; + code: string; + exports: Map; } export const exportScanner = (id: string, filename = id) => { - const re = - /export (const (?\w+) =|default) (?define[A-Z]|styled|component\$?)/; - - return { - scan(code: string): Module { - const ret = { - code: "", - exports: new Map(), - }; - let src = code; - let m: RegExpMatchArray | null = null; - while (true) { - m = src.match(re); - - if (m === null) { - break; - } - - const defn = m.groups?.["defn"] ?? ""; - - const exported = m.groups?.["name"] ?? "default"; - const local = - exported !== "default" - ? `__${exported}` - : upperFirst( - camelCase(`${basename(filename, extname(filename))}Default`), - ); - - const range = { - start: m.index ?? 0, - length: m[0].length, - }; - - ret.exports.set(local, { exported, id: getHash(`${id}#${exported}`) }); - - ret.code += `${src.slice(0, range.start)}const ${local} = ${defn}`; - - src = src.slice(range.start + range.length); - } - - ret.code += src; - - return ret; - }, - }; + const re = + /export (const (?\w+) =|default) (?define[A-Z]|styled|component\$?)/; + + return { + scan(code: string): Module { + const ret = { + code: "", + exports: new Map(), + }; + let src = code; + let m: RegExpMatchArray | null = null; + while (true) { + m = src.match(re); + + if (m === null) { + break; + } + + const defn = m.groups?.["defn"] ?? ""; + + const exported = m.groups?.["name"] ?? "default"; + const local = + exported !== "default" + ? `__${exported}` + : upperFirst( + camelCase(`${basename(filename, extname(filename))}Default`), + ); + + const range = { + start: m.index ?? 0, + length: m[0].length, + }; + + ret.exports.set(local, { exported, id: getHash(`${id}#${exported}`) }); + + ret.code += `${src.slice(0, range.start)}const ${local} = ${defn}`; + + src = src.slice(range.start + range.length); + } + + ret.code += src; + + return ret; + }, + }; }; export const viteVueComponentPatcher = ( - options: VueJsxHmrOptions = {}, + options: VueJsxHmrOptions = {}, ): Plugin => { - const filter = createFilter( - options.include || [/\.tsx$/, /\.mdx?$/], - options.exclude, - ); - - let hmrEnabled = false; - - return { - name: "vite-plugin/vue-component-patcher", - - config(_, env) { - hmrEnabled = env.command === "serve"; - }, - - async transform(code, id) { - const [filepath] = id.split("?"); - - if (filter(id) || filter(filepath)) { - const m = exportScanner(id, filepath).scan(code); - let patched = m.code; - patched += exportWithDisplayName(m); - if (hmrEnabled) { - patched += hmrPatch(m); - } - return patched; - } - - return null; - }, - }; + const filter = createFilter( + options.include || [/\.tsx$/, /\.mdx?$/], + options.exclude, + ); + + let hmrEnabled = false; + + return { + name: "vite-plugin/vue-component-patcher", + + config(_, env) { + hmrEnabled = env.command === "serve"; + }, + + async transform(code, id) { + const [filepath] = id.split("?"); + + if (filter(id) || filter(filepath)) { + const m = exportScanner(id, filepath).scan(code); + let patched = m.code; + patched += exportWithDisplayName(m); + if (hmrEnabled) { + patched += hmrPatch(m); + } + return patched; + } + + return null; + }, + }; }; function exportWithDisplayName(m: Module) { - let code = ""; + let code = ""; - m.exports.forEach(({ exported }, local) => { - if (exported === "default") { - code += ` + m.exports.forEach(({ exported }, local) => { + if (exported === "default") { + code += ` export default `; - } else { - code += ` + } else { + code += ` export const ${exported} = `; - } + } - code += `Object.assign(${local}, { displayName: "${ - exported === "default" ? local : exported - }" }); + code += `Object.assign(${local}, { displayName: "${ + exported === "default" ? local : exported + }" }); `; - }); + }); - return code; + return code; } function hmrPatch(m: Module) { - let code = ""; - let callbackBlock = ""; + let code = ""; + let callbackBlock = ""; - m.exports.forEach(({ exported, id }, local) => { - const ident = exported === "default" ? local : exported; + m.exports.forEach(({ exported, id }, local) => { + const ident = exported === "default" ? local : exported; - code += ` + code += ` ${ident}.__hmrId = "${id}" __VUE_HMR_RUNTIME__.createRecord(${ident}.__hmrId, ${ident}); `; - callbackBlock += `__VUE_HMR_RUNTIME__.reload(exports.${exported}.__hmrId, exports.${exported}); + callbackBlock += `__VUE_HMR_RUNTIME__.reload(exports.${exported}.__hmrId, exports.${exported}); `; - }); + }); - if (callbackBlock) { - code += ` + if (callbackBlock) { + code += ` import.meta.hot?.accept((exports) => { ${callbackBlock} }) `; - } + } - return code; + return code; } diff --git a/nodedevpkg/vue-vite-presets/src/vue/pageMetaResolver.ts b/nodedevpkg/vue-vite-presets/src/vue/pageMetaResolver.ts index 4dbc890e..4be4a4e4 100644 --- a/nodedevpkg/vue-vite-presets/src/vue/pageMetaResolver.ts +++ b/nodedevpkg/vue-vite-presets/src/vue/pageMetaResolver.ts @@ -7,187 +7,187 @@ const reJsDoc = /\/\*\*\s*\n([^*]|\*[^\/])*\*\//g; const reProp = /@property( +)\{(?[^}]+)}( +)(?[\w.]+)/g; type RouteMetadata = { - id: string; - imports: Record>; - meta: Record; + id: string; + imports: Record>; + meta: Record; }; export const extractRouteMeta = ( - id: string, - jsdoc: string, + id: string, + jsdoc: string, ): RouteMetadata | undefined => { - let r: - | { - imports: Record>; - meta: Record; - id: string; - } - | undefined; - - for (const m of jsdoc.matchAll(reProp)) { - const keyPath = m.groups?.["path"] ?? ""; - const type = m.groups?.["type"] ?? ""; - - // Support import("@innoai-tech/vuematerial").mdiPlus - const importType = type.match( - /(typeof +)?import\(['"](?[^)]+)['"]\)\.(?.+)/, - ); - - if (isUndefined(r)) { - r = { - imports: {} as Record>, - meta: {} as Record, - id, - }; - } - - if (importType) { - set( - r, - [ - "imports", - importType.groups?.["importPath"] ?? "", - importType.groups?.["name"] ?? "", - ], - true, - ); - set(r, keyPath, importType.groups?.["name"] ?? ""); - } else { - set(r, keyPath, type); - } - } - - return r; + let r: + | { + imports: Record>; + meta: Record; + id: string; + } + | undefined; + + for (const m of jsdoc.matchAll(reProp)) { + const keyPath = m.groups?.["path"] ?? ""; + const type = m.groups?.["type"] ?? ""; + + // Support import("@innoai-tech/vuematerial").mdiPlus + const importType = type.match( + /(typeof +)?import\(['"](?[^)]+)['"]\)\.(?.+)/, + ); + + if (isUndefined(r)) { + r = { + imports: {} as Record>, + meta: {} as Record, + id, + }; + } + + if (importType) { + set( + r, + [ + "imports", + importType.groups?.["importPath"] ?? "", + importType.groups?.["name"] ?? "", + ], + true, + ); + set(r, keyPath, importType.groups?.["name"] ?? ""); + } else { + set(r, keyPath, type); + } + } + + return r; }; export const createPageMetaResolver = () => { - const viteRoot = searchForWorkspaceRoot(process.cwd()); - - const pageMetaLocalIdSuffix = "page_meta__"; - const pageMetaSuffix = "id=~page-meta.ts"; - - const r = vueResolver(); - - const metaMap = new Map(); - const metaImports = new Map(); - - const isPageMeta = (id: string) => { - return id.endsWith(pageMetaSuffix); - }; - - let userConfig: UserConfig; - - return { - plugin: { - name: "vite-plugin/vite-pages-page-meta", - config(c) { - userConfig = c; - }, - resolveId(source: string) { - if (isPageMeta(source)) { - return source; - } - return null; - }, - load(id: string) { - if (isPageMeta(id)) { - const [filepath, search] = id.split("?"); - const m = metaMap.get(filepath ?? ""); - const q = new URLSearchParams(search ?? ""); - - if (q.has("exportAsDefault")) { - return `export { ${q.get( - "exportAsDefault", - )} as default } from "${filepath}";`; - } - - if (m) { - const nameOfImports: Record = {}; - - for (const importPath in m.imports) { - for (const importName in m.imports[importPath]) { - nameOfImports[importName] = importPath; - } - } - - return ` + const viteRoot = searchForWorkspaceRoot(process.cwd()); + + const pageMetaLocalIdSuffix = "page_meta__"; + const pageMetaSuffix = "id=~page-meta.ts"; + + const r = vueResolver(); + + const metaMap = new Map(); + const metaImports = new Map(); + + const isPageMeta = (id: string) => { + return id.endsWith(pageMetaSuffix); + }; + + let userConfig: UserConfig; + + return { + plugin: { + name: "vite-plugin/vite-pages-page-meta", + config(c) { + userConfig = c; + }, + resolveId(source: string) { + if (isPageMeta(source)) { + return source; + } + return null; + }, + load(id: string) { + if (isPageMeta(id)) { + const [filepath, search] = id.split("?"); + const m = metaMap.get(filepath ?? ""); + const q = new URLSearchParams(search ?? ""); + + if (q.has("exportAsDefault")) { + return `export { ${q.get( + "exportAsDefault", + )} as default } from "${filepath}";`; + } + + if (m) { + const nameOfImports: Record = {}; + + for (const importPath in m.imports) { + for (const importName in m.imports[importPath]) { + nameOfImports[importName] = importPath; + } + } + + return ` export default { meta: { ${Object.keys(m.meta) - .map((k) => { - const name = m.meta[k]; - if (nameOfImports[name]) { - return `${k}: () => import("${nameOfImports[name]}?exportAsDefault=${name}&id=~page-meta.ts")`; - } - return `${k}: ${name}`; - }) - .join(",\n")} + .map((k) => { + const name = m.meta[k]; + if (nameOfImports[name]) { + return `${k}: () => import("${nameOfImports[name]}?exportAsDefault=${name}&id=~page-meta.ts")`; + } + return `${k}: ${name}`; + }) + .join(",\n")} } }`; - } - - return "export default {}"; - } - return null; - }, - } as Plugin, - onRoutesGenerated: async (routes: VueRoute[]) => { - const imports = new Map(); - - const walk = async (routes: VueRoute[]) => { - for (const r of routes) { - const id = `__pages_import_${imports.size}__`; - imports.set(id, ""); - - let filepath = r.component; - - if (filepath.endsWith(".tsx")) { - if (!filepath.startsWith(viteRoot)) { - filepath = `${userConfig.root}${filepath}`; - } - - const src = String(await readFile(filepath)); - - for (const m of src.matchAll(reJsDoc)) { - const meta = extractRouteMeta(id, m[0]); - if (meta) { - metaImports.set(id, filepath); - metaMap.set(filepath, meta); - } - } - } - - if (r.children) { - await walk(r.children); - } - } - }; - - await walk(routes); - - return routes; - }, - pagesResolver: { - ...r, - stringify: { - final(code: string) { - const importDecls = [...metaImports].map(([id, importPath]) => { - return `import ${id}${pageMetaLocalIdSuffix} from "${importPath}?${pageMetaSuffix}"`; - }); - - return ` + } + + return "export default {}"; + } + return null; + }, + } as Plugin, + onRoutesGenerated: async (routes: VueRoute[]) => { + const imports = new Map(); + + const walk = async (routes: VueRoute[]) => { + for (const r of routes) { + const id = `__pages_import_${imports.size}__`; + imports.set(id, ""); + + let filepath = r.component; + + if (filepath.endsWith(".tsx")) { + if (!filepath.startsWith(viteRoot)) { + filepath = `${userConfig.root}${filepath}`; + } + + const src = String(await readFile(filepath)); + + for (const m of src.matchAll(reJsDoc)) { + const meta = extractRouteMeta(id, m[0]); + if (meta) { + metaImports.set(id, filepath); + metaMap.set(filepath, meta); + } + } + } + + if (r.children) { + await walk(r.children); + } + } + }; + + await walk(routes); + + return routes; + }, + pagesResolver: { + ...r, + stringify: { + final(code: string) { + const importDecls = [...metaImports].map(([id, importPath]) => { + return `import ${id}${pageMetaLocalIdSuffix} from "${importPath}?${pageMetaSuffix}"`; + }); + + return ` ${importDecls.join(";\n")} ${code} `; - }, - component(importName: string) { - if (metaImports.has(importName)) { - return `Object.assign(${importName}, ${importName}${pageMetaLocalIdSuffix})`; - } - return importName; - }, - }, - }, - }; + }, + component(importName: string) { + if (metaImports.has(importName)) { + return `Object.assign(${importName}, ${importName}${pageMetaLocalIdSuffix})`; + } + return importName; + }, + }, + }, + }; }; diff --git a/nodedevpkg/vue-vite-presets/tsconfig.monobundle.json b/nodedevpkg/vue-vite-presets/tsconfig.monobundle.json index 21023dd8..3170e721 100644 --- a/nodedevpkg/vue-vite-presets/tsconfig.monobundle.json +++ b/nodedevpkg/vue-vite-presets/tsconfig.monobundle.json @@ -1,6 +1,6 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - } + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + } } diff --git a/nodepkg/gents/package.json b/nodepkg/gents/package.json index 7c2c54cf..ea5b5ab9 100644 --- a/nodepkg/gents/package.json +++ b/nodepkg/gents/package.json @@ -10,7 +10,7 @@ } }, "dependencies": { - "@innoai-tech/config": "0.5.3", + "@innoai-tech/config": "0.5.4", "@innoai-tech/lodash": "0.2.1", "@innoai-tech/typedef": "workspace:^", "node-fetch": "^3.3.2" @@ -45,10 +45,9 @@ "directory": "nodepkg/gents" }, "scripts": { - "test": "bun test .", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build", - "lint": "bunx --bun @biomejs/biome check --apply ." + "lint": "bunx --bun prettier --write . " }, "type": "module" } diff --git a/nodepkg/gents/src/ClientGen.ts b/nodepkg/gents/src/ClientGen.ts index e5647b72..4c9a3ab8 100644 --- a/nodepkg/gents/src/ClientGen.ts +++ b/nodepkg/gents/src/ClientGen.ts @@ -1,243 +1,243 @@ import { - camelCase, - first, - get, - keys, - lowerFirst, - set, - values, + camelCase, + first, + get, + keys, + lowerFirst, + set, + values, } from "@innoai-tech/lodash"; import { - type AnyType, - JSONSchemaDecoder, - TypeScriptEncoder, - TypedefEncoder, - refName, - t, + type AnyType, + JSONSchemaDecoder, + TypeScriptEncoder, + TypedefEncoder, + refName, + t, } from "@innoai-tech/typedef"; import { Genfile, dumpObj } from "./Genfile"; export interface RequestCreator { - importPath: string; - expose: string; + importPath: string; + expose: string; } export class ClientGen extends Genfile { - constructor( - private clientID: string, - private openapi: any, - private requestCreator: RequestCreator, - ) { - super(); - } - - typedef = new JSONSchemaDecoder((ref) => { - return [ - get(this.openapi, (ref.split("#/")[1] ?? "").split("/")), - refName(ref), - ]; - }); - - td = new TypedefEncoder(); - ts = new TypeScriptEncoder(); - - override async sync(file: string) { - this.decl(this.ts.decls()); - this.decl(this.td.decls()); - return await super.sync(file); - } - - scan() { - this.import(this.requestCreator.importPath, this.requestCreator.expose, ""); - this.import("@innoai-tech/typedef", "t", ""); - - for (const path in this.openapi.paths) { - const ops = this.openapi.paths[path]; - - for (const method in ops) { - const op = ops[method]; - - if (op.operationId === "") { - continue; - } - - if (op.operationId === "OpenAPI") { - continue; - } - - this.scanOperation(method, path, op); - } - } - } - - scanOperation(method: string, path: string, op: any) { - const requestObject = { - method: method.toUpperCase(), - url: path, - }; - - const requestParameterSchema: Record = {}; - - const requestUsed = {}; - let hasParamInPath = false; - let isRequestTypeEmpty = true; - - if (op.parameters) { - for (const p of op.parameters) { - if (p.in === "header" && p.name === "Authorization") { - continue; - } - - if (p.in === "cookie") { - continue; - } - - isRequestTypeEmpty = false; - - if (p.in === "path") { - hasParamInPath = true; - requestObject.url = requestObject.url.replace( - `{${p.name}}`, - `\${${p.in}_${lowerCamelCase(p.name)}}`, - ); - } - - if (p.in === "header") { - set( - requestObject, - ["headers", p.name], - Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), - ); - } - - if (p.in === "query") { - set( - requestObject, - ["params", p.name], - Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), - ); - } - - set( - requestUsed, - p.name, - Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), - ); - - if (p.required) { - set(requestParameterSchema, p.name, this.typedef.decode(p.schema)); - } else { - set( - requestParameterSchema, - p.name, - this.typedef.decode(p.schema).optional(), - ); - } - } - } - - const bodyTypes: AnyType[] = []; - - if (op.requestBody) { - const contentTypes = keys(op.requestBody.content); - - for (const ct of contentTypes) { - isRequestTypeEmpty = false; - - let schema = op.requestBody.content[ct].schema; - - if (ct === "application/octet-stream") { - schema = { type: "string", format: "binary" }; - } - - set(requestObject, "body", Genfile.id("body")); - set(requestUsed, "body", Genfile.id("body")); - - if (contentTypes.length === 1) { - if (!get(requestObject, ["headers", "Content-Type"])) { - set(requestObject, ["headers", "Content-Type"], ct); - } - set(requestParameterSchema, "body", this.typedef.decode(schema)); - continue; - } - - set( - requestUsed, - "Content-Type", - Genfile.id(`${lowerCamelCase("Content-Type")}`), - ); - - set( - requestObject, - ["headers", "Content-Type"], - Genfile.id(`${lowerCamelCase("Content-Type")}`), - ); - - bodyTypes.push( - t.object({ - "Content-Type": t.literal(ct), - body: this.typedef.decode(schema), - }), - ); - } - } - - if (hasParamInPath) { - requestObject.url = Genfile.id(`\`${requestObject.url}\``) as any; - } - - const requestType = isRequestTypeEmpty - ? "void" - : this.decodeAsTypeScript( - bodyTypes.length > 0 - ? t.intersection( - t.object(requestParameterSchema), - t.union(...bodyTypes), - ) - : t.object(requestParameterSchema), - ); - - const responseType = this.decodeAsTypeScript( - this.typedef.decode(getRespBodySchema(op.responses)), - ); - - this.decl(` + constructor( + private clientID: string, + private openapi: any, + private requestCreator: RequestCreator, + ) { + super(); + } + + typedef = new JSONSchemaDecoder((ref) => { + return [ + get(this.openapi, (ref.split("#/")[1] ?? "").split("/")), + refName(ref), + ]; + }); + + td = new TypedefEncoder(); + ts = new TypeScriptEncoder(); + + override async sync(file: string) { + this.decl(this.ts.decls()); + this.decl(this.td.decls()); + return await super.sync(file); + } + + scan() { + this.import(this.requestCreator.importPath, this.requestCreator.expose, ""); + this.import("@innoai-tech/typedef", "t", ""); + + for (const path in this.openapi.paths) { + const ops = this.openapi.paths[path]; + + for (const method in ops) { + const op = ops[method]; + + if (op.operationId === "") { + continue; + } + + if (op.operationId === "OpenAPI") { + continue; + } + + this.scanOperation(method, path, op); + } + } + } + + scanOperation(method: string, path: string, op: any) { + const requestObject = { + method: method.toUpperCase(), + url: path, + }; + + const requestParameterSchema: Record = {}; + + const requestUsed = {}; + let hasParamInPath = false; + let isRequestTypeEmpty = true; + + if (op.parameters) { + for (const p of op.parameters) { + if (p.in === "header" && p.name === "Authorization") { + continue; + } + + if (p.in === "cookie") { + continue; + } + + isRequestTypeEmpty = false; + + if (p.in === "path") { + hasParamInPath = true; + requestObject.url = requestObject.url.replace( + `{${p.name}}`, + `\${${p.in}_${lowerCamelCase(p.name)}}`, + ); + } + + if (p.in === "header") { + set( + requestObject, + ["headers", p.name], + Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), + ); + } + + if (p.in === "query") { + set( + requestObject, + ["params", p.name], + Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), + ); + } + + set( + requestUsed, + p.name, + Genfile.id(`${p.in}_${lowerCamelCase(p.name)}`), + ); + + if (p.required) { + set(requestParameterSchema, p.name, this.typedef.decode(p.schema)); + } else { + set( + requestParameterSchema, + p.name, + this.typedef.decode(p.schema).optional(), + ); + } + } + } + + const bodyTypes: AnyType[] = []; + + if (op.requestBody) { + const contentTypes = keys(op.requestBody.content); + + for (const ct of contentTypes) { + isRequestTypeEmpty = false; + + let schema = op.requestBody.content[ct].schema; + + if (ct === "application/octet-stream") { + schema = { type: "string", format: "binary" }; + } + + set(requestObject, "body", Genfile.id("body")); + set(requestUsed, "body", Genfile.id("body")); + + if (contentTypes.length === 1) { + if (!get(requestObject, ["headers", "Content-Type"])) { + set(requestObject, ["headers", "Content-Type"], ct); + } + set(requestParameterSchema, "body", this.typedef.decode(schema)); + continue; + } + + set( + requestUsed, + "Content-Type", + Genfile.id(`${lowerCamelCase("Content-Type")}`), + ); + + set( + requestObject, + ["headers", "Content-Type"], + Genfile.id(`${lowerCamelCase("Content-Type")}`), + ); + + bodyTypes.push( + t.object({ + "Content-Type": t.literal(ct), + body: this.typedef.decode(schema), + }), + ); + } + } + + if (hasParamInPath) { + requestObject.url = Genfile.id(`\`${requestObject.url}\``) as any; + } + + const requestType = isRequestTypeEmpty + ? "void" + : this.decodeAsTypeScript( + bodyTypes.length > 0 + ? t.intersection( + t.object(requestParameterSchema), + t.union(...bodyTypes), + ) + : t.object(requestParameterSchema), + ); + + const responseType = this.decodeAsTypeScript( + this.typedef.decode(getRespBodySchema(op.responses)), + ); + + this.decl(` export const ${lowerCamelCase(op.operationId)} = /*#__PURE__*/${this.requestCreator.expose}<${requestType}, ${responseType}>( "${this.clientID}.${op.operationId}", (${isRequestTypeEmpty ? "" : dumpObj(requestUsed)}) => (${dumpObj( - requestObject, - )}), + requestObject, + )}), ) `); - } + } - private decodeAsTypeScript(t: AnyType) { - // just for collect - this.td.encode(t, false); + private decodeAsTypeScript(t: AnyType) { + // just for collect + this.td.encode(t, false); - return this.ts.encode(t, false); - } + return this.ts.encode(t, false); + } } const lowerCamelCase = (id: string) => lowerFirst(camelCase(id)); const getRespBodySchema = (responses: any) => { - let bodySchema = { type: "null" }; + let bodySchema = { type: "null" }; - for (const codeOrDefault in responses) { - const resp = responses[codeOrDefault]; + for (const codeOrDefault in responses) { + const resp = responses[codeOrDefault]; - const code = Number(codeOrDefault); + const code = Number(codeOrDefault); - if (code >= 200 && code < 300 && resp.content) { - const mediaType = first(values(resp.content)); - if (mediaType && !!mediaType.schema) { - bodySchema = mediaType.schema; - break; - } - } - } + if (code >= 200 && code < 300 && resp.content) { + const mediaType = first(values(resp.content)); + if (mediaType && !!mediaType.schema) { + bodySchema = mediaType.schema; + break; + } + } + } - return bodySchema; + return bodySchema; }; diff --git a/nodepkg/gents/src/Genfile.ts b/nodepkg/gents/src/Genfile.ts index 4785de1d..b56f7e29 100644 --- a/nodepkg/gents/src/Genfile.ts +++ b/nodepkg/gents/src/Genfile.ts @@ -2,91 +2,91 @@ import { isFunction, isObject, isString, map } from "@innoai-tech/lodash"; import { writeFile } from "fs/promises"; export class ID { - constructor(private id: string) {} + constructor(private id: string) {} - toString(): string { - return this.id; - } + toString(): string { + return this.id; + } } export const dumpValue = (value: any) => { - if (!value) { - return null; - } + if (!value) { + return null; + } - const v = isFunction(value) ? value() : value; - if (v instanceof ID) { - return `${v}`; - } - if (isObject(v)) { - return `${dumpObj(v)}`; - } - if (isString(v)) { - return `"${v}"`; - } - return `${v}`; + const v = isFunction(value) ? value() : value; + if (v instanceof ID) { + return `${v}`; + } + if (isObject(v)) { + return `${dumpObj(v)}`; + } + if (isString(v)) { + return `"${v}"`; + } + return `${v}`; }; export const dumpObj = (obj: { [k: string]: any }): string => { - return `{ + return `{ ${map(obj, (v, k) => { - if (!v) { - return null; - } + if (!v) { + return null; + } - let key = `"${k}"`; + let key = `"${k}"`; - switch (k[0]) { - case "?": { - key = `"${k.slice(1)}"?`; - break; - } - case "+": - key = `[${k.slice(1)}]`; - } + switch (k[0]) { + case "?": { + key = `"${k.slice(1)}"?`; + break; + } + case "+": + key = `[${k.slice(1)}]`; + } - return `${key}: ${dumpValue(v)},`; + return `${key}: ${dumpValue(v)},`; }) - .filter((v) => !!v) - .join("\n")} + .filter((v) => !!v) + .join("\n")} }`; }; export class Genfile { - static id(i: string): ID { - return new ID(i); - } + static id(i: string): ID { + return new ID(i); + } - private imports = new Map>(); - private decls: string[] = []; + private imports = new Map>(); + private decls: string[] = []; - import(path: string, expose: string, alias = "") { - const exposes = this.imports.get(path) ?? {}; - this.imports.set(path, { - ...exposes, - [expose]: alias, - }); - } + import(path: string, expose: string, alias = "") { + const exposes = this.imports.get(path) ?? {}; + this.imports.set(path, { + ...exposes, + [expose]: alias, + }); + } - decl(d: string) { - this.decls.push(d); - } + decl(d: string) { + this.decls.push(d); + } - async sync(file: string) { - await writeFile( - file, - Buffer.from( - [ - ...map([...this.imports.entries()], ([path, nameAndAlias]) => { - return `import { ${map(nameAndAlias, (alias, name) => - alias ? `${name} as ${alias}` : name, - ).join(", ")} } from "${path}";`; - }), - ...this.decls, - ].join("\n\n"), - ), - ); + async sync(file: string) { + await writeFile( + file, + Buffer.from( + [ + ...map([...this.imports.entries()], ([path, nameAndAlias]) => { + return `import { ${map(nameAndAlias, (alias, name) => + alias ? `${name} as ${alias}` : name, + ).join(", ")} } from "${path}";`; + }), + ...this.decls, + ].join("\n\n"), + ), + ); - return; - } + return; + } } diff --git a/nodepkg/gents/src/__tests__/client/client.ts b/nodepkg/gents/src/__tests__/client/client.ts index 7a5dd471..94d606d3 100644 --- a/nodepkg/gents/src/__tests__/client/client.ts +++ b/nodepkg/gents/src/__tests__/client/client.ts @@ -1,9 +1,9 @@ import type { RequestConfig, RequestConfigCreator } from "@innoai-tech/fetcher"; export const createRequest = ( - _: string, - createConfig: (input: TInputs) => Omit, "inputs">, + _: string, + createConfig: (input: TInputs) => Omit, "inputs">, ) => { - const create = (inputs: TInputs) => ({ ...createConfig(inputs), inputs }); - return create as RequestConfigCreator; + const create = (inputs: TInputs) => ({ ...createConfig(inputs), inputs }); + return create as RequestConfigCreator; }; diff --git a/nodepkg/gents/src/__tests__/example/openapi.json b/nodepkg/gents/src/__tests__/example/openapi.json index b1436f0d..a8304446 100644 --- a/nodepkg/gents/src/__tests__/example/openapi.json +++ b/nodepkg/gents/src/__tests__/example/openapi.json @@ -1,822 +1,822 @@ { - "openapi": "3.0.3", - "info": { - "title": "", - "version": "" - }, - "paths": { - "/api/kubepkg.innoai.tech": { - "GET": { - "tags": ["httprouter"], - "operationId": "OpenAPI", - "responses": { - "200": { - "description": "", - "content": { - "application/json": {} - } - } - } - } - }, - "/api/kubepkg.innoai.tech/v1/blobs": { - "PUT": { - "tags": ["blob"], - "summary": "上传 blob", - "operationId": "UploadBlob", - "parameters": [ - { - "name": "Content-Type", - "in": "header", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/octet-stream": { - "schema": {} - } - } - }, - "responses": { - "200": { - "description": "" - }, - "400": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"InvalidContentType\",msg=\"InvalidContentType\",code=400}" - ] - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"RegistryError\",msg=\"RegistryError\",code=500}" - ] - } - } - } - }, - "/api/kubepkg.innoai.tech/v1/blobs/{digest}/stat": { - "GET": { - "tags": ["blob"], - "summary": "查询 blob 状态", - "operationId": "StatBlob", - "parameters": [ - { - "name": "digest", - "in": "path", - "required": true, - "schema": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "enum": ["sha256"] - } - ] - } - } - ], - "responses": { - "200": { - "description": "" - }, - "400": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"InvalidDigest\",msg=\"InvalidDigest\",code=400}" - ] - }, - "404": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"DigestNotFound\",msg=\"DigestNotFound\",code=404}" - ] - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"RegistryError\",msg=\"RegistryError\",code=500}" - ] - } - } - } - }, - "/api/kubepkg.innoai.tech/v1/kubepkgs": { - "GET": { - "tags": ["kubepkg"], - "summary": "部署包列表", - "operationId": "ListKubePkg", - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" - } - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" - ] - } - } - }, - "PUT": { - "tags": ["kubepkg"], - "summary": "部署部署包", - "operationId": "ApplyKubePkg", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" - }, - { - "description": "通过 KubePkg.json 部署" - } - ] - } - }, - "application/octet-stream": { - "schema": { - "description": "通过 KubePkg.tgz 部署" - } - } - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" - } - } - } - }, - "400": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"ReadKubePkgTgzFailed\",msg=\"ReadKubePkgTgzFailed\",code=400}", - "StatusError{key=\"NoRequestBody\",msg=\"NoRequestBody\",code=400}" - ] - } - } - } - }, - "/api/kubepkg.innoai.tech/v1/kubepkgs/{name}": { - "DELETE": { - "tags": ["kubepkg"], - "summary": "删除部署包", - "operationId": "DelKubePkg", - "parameters": [ - { - "name": "name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "namespace", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" - ] - } - } - }, - "GET": { - "tags": ["kubepkg"], - "summary": "查询部署包", - "operationId": "GetKubePkg", - "parameters": [ - { - "name": "name", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "namespace", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" - } - } - } - }, - "500": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StatuserrorStatusErr" - } - } - }, - "x-status-returnErrors": [ - "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" - ] - } - } - } - } - }, - "components": { - "schemas": { - "KubepkgV1Alpha1DigestMeta": { - "type": "object", - "properties": { - "digest": { - "type": "string", - "x-go-field-name": "Digest" - }, - "name": { - "type": "string", - "x-go-field-name": "Name" - }, - "platform": { - "type": "string", - "x-go-field-name": "Platform" - }, - "size": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1FileSize" - }, - { - "x-go-field-name": "Size" - } - ] - }, - "tag": { - "type": "string", - "x-go-field-name": "Tag" - }, - "type": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1DigestMetaType" - }, - { - "x-go-field-name": "Type" - } - ] - } - }, - "required": ["type", "digest", "name", "size"], - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.DigestMeta" - }, - "KubepkgV1Alpha1DigestMetaType": { - "type": "string", - "enum": ["blob", "manifest"], - "x-enum-labels": ["Blob", "Manifest"], - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.DigestMetaType" - }, - "KubepkgV1Alpha1FileSize": { - "type": "integer", - "format": "int64", - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.FileSize" - }, - "KubepkgV1Alpha1KubePkg": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1TypeMeta" - }, - { - "type": "object", - "properties": { - "metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1ObjectMeta" - }, - { - "x-go-field-name": "ObjectMeta" - } - ] - }, - "spec": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkgSpec" - }, - { - "x-go-field-name": "Spec" - } - ] - }, - "status": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkgStatus" - }, - { - "x-go-field-name": "Status" - } - ] - } - } - } - ], - "description": "KubePkg", - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkg" - }, - "KubepkgV1Alpha1KubePkgSpec": { - "type": "object", - "properties": { - "images": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "x-go-field-name": "Images" - }, - "manifests": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1Manifests" - }, - { - "x-go-field-name": "Manifests" - } - ] - }, - "version": { - "type": "string", - "x-go-field-name": "Version" - } - }, - "required": ["version"], - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkgSpec" - }, - "KubepkgV1Alpha1KubePkgStatus": { - "type": "object", - "properties": { - "digests": { - "type": "array", - "items": { - "$ref": "#/components/schemas/KubepkgV1Alpha1DigestMeta" - }, - "x-go-field-name": "Digests" - }, - "statuses": { - "allOf": [ - { - "$ref": "#/components/schemas/KubepkgV1Alpha1Statuses" - }, - { - "x-go-field-name": "Statuses" - } - ] - } - }, - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkgStatus" - }, - "KubepkgV1Alpha1Manifests": { - "type": "object", - "additionalProperties": {}, - "propertyNames": { - "type": "string" - }, - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.Manifests" - }, - "KubepkgV1Alpha1Statuses": { - "type": "object", - "additionalProperties": {}, - "propertyNames": { - "type": "string" - }, - "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.Statuses" - }, - "MetaV1FieldsV1": { - "type": "object", - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1" - }, - "MetaV1ManagedFieldsEntry": { - "type": "object", - "properties": { - "apiVersion": { - "type": "string", - "x-go-field-name": "APIVersion" - }, - "fieldsType": { - "type": "string", - "x-go-field-name": "FieldsType" - }, - "fieldsV1": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1FieldsV1" - }, - { - "x-go-field-name": "FieldsV1" - } - ] - }, - "manager": { - "type": "string", - "x-go-field-name": "Manager" - }, - "operation": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1ManagedFieldsOperationType" - }, - { - "x-go-field-name": "Operation" - } - ] - }, - "subresource": { - "type": "string", - "x-go-field-name": "Subresource" - }, - "time": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1Time" - }, - { - "x-go-field-name": "Time" - } - ] - } - }, - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry" - }, - "MetaV1ManagedFieldsOperationType": { - "type": "string", - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsOperationType" - }, - "MetaV1ObjectMeta": { - "type": "object", - "properties": { - "annotations": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "x-go-field-name": "Annotations" - }, - "clusterName": { - "type": "string", - "x-go-field-name": "ZZZ_DeprecatedClusterName" - }, - "creationTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1Time" - }, - { - "x-go-field-name": "CreationTimestamp" - } - ] - }, - "deletionGracePeriodSeconds": { - "type": "integer", - "format": "int64", - "nullable": true, - "x-go-field-name": "DeletionGracePeriodSeconds", - "x-go-star-level": 1 - }, - "deletionTimestamp": { - "allOf": [ - { - "$ref": "#/components/schemas/MetaV1Time" - }, - { - "x-go-field-name": "DeletionTimestamp" - } - ] - }, - "finalizers": { - "type": "array", - "items": { - "type": "string" - }, - "x-go-field-name": "Finalizers" - }, - "generateName": { - "type": "string", - "x-go-field-name": "GenerateName" - }, - "generation": { - "type": "integer", - "format": "int64", - "x-go-field-name": "Generation" - }, - "labels": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "propertyNames": { - "type": "string" - }, - "x-go-field-name": "Labels" - }, - "managedFields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MetaV1ManagedFieldsEntry" - }, - "x-go-field-name": "ManagedFields" - }, - "name": { - "type": "string", - "x-go-field-name": "Name" - }, - "namespace": { - "type": "string", - "x-go-field-name": "Namespace" - }, - "ownerReferences": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MetaV1OwnerReference" - }, - "x-go-field-name": "OwnerReferences" - }, - "resourceVersion": { - "type": "string", - "x-go-field-name": "ResourceVersion" - }, - "selfLink": { - "type": "string", - "x-go-field-name": "SelfLink" - }, - "uid": { - "allOf": [ - { - "$ref": "#/components/schemas/TypesUid" - }, - { - "x-go-field-name": "UID" - } - ] - } - }, - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" - }, - "MetaV1OwnerReference": { - "type": "object", - "properties": { - "apiVersion": { - "type": "string", - "x-go-field-name": "APIVersion" - }, - "blockOwnerDeletion": { - "type": "boolean", - "nullable": true, - "x-go-field-name": "BlockOwnerDeletion", - "x-go-star-level": 1 - }, - "controller": { - "type": "boolean", - "nullable": true, - "x-go-field-name": "Controller", - "x-go-star-level": 1 - }, - "kind": { - "type": "string", - "x-go-field-name": "Kind" - }, - "name": { - "type": "string", - "x-go-field-name": "Name" - }, - "uid": { - "allOf": [ - { - "$ref": "#/components/schemas/TypesUid" - }, - { - "x-go-field-name": "UID" - } - ] - } - }, - "required": ["apiVersion", "kind", "name", "uid"], - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference" - }, - "MetaV1Time": { - "type": "string", - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.Time" - }, - "MetaV1TypeMeta": { - "type": "object", - "properties": { - "apiVersion": { - "type": "string", - "x-go-field-name": "APIVersion" - }, - "kind": { - "type": "string", - "x-go-field-name": "Kind" - } - }, - "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" - }, - "StatuserrorErrorField": { - "type": "object", - "properties": { - "field": { - "type": "string", - "description": "field path\nprop.slice[2].a", - "x-go-field-name": "Field" - }, - "in": { - "type": "string", - "description": "location\neq. body, query, header, path, formData", - "x-go-field-name": "In" - }, - "msg": { - "type": "string", - "description": "msg", - "x-go-field-name": "Msg" - } - }, - "required": ["field", "msg", "in"], - "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.ErrorField" - }, - "StatuserrorErrorFields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/StatuserrorErrorField" - }, - "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.ErrorFields" - }, - "StatuserrorStatusErr": { - "type": "object", - "properties": { - "canBeTalkError": { - "type": "boolean", - "description": "can be task error\nfor client to should error msg to end user", - "x-go-field-name": "CanBeTalkError" - }, - "code": { - "type": "integer", - "format": "int32", - "description": "http code", - "x-go-field-name": "Code" - }, - "desc": { - "type": "string", - "description": "desc of err", - "x-go-field-name": "Desc" - }, - "errorFields": { - "allOf": [ - { - "$ref": "#/components/schemas/StatuserrorErrorFields" - }, - { - "description": "error in where fields", - "x-go-field-name": "ErrorFields" - } - ] - }, - "key": { - "type": "string", - "description": "key of err", - "x-go-field-name": "Key" - }, - "msg": { - "type": "string", - "description": "msg of err", - "x-go-field-name": "Msg" - }, - "sources": { - "type": "array", - "items": { - "type": "string" - }, - "description": "error tracing", - "x-go-field-name": "Sources" - } - }, - "required": [ - "code", - "key", - "msg", - "desc", - "canBeTalkError", - "sources", - "errorFields" - ], - "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.StatusErr" - }, - "TypesUid": { - "type": "string", - "x-go-vendor-type": "k8s.io/apimachinery/pkg/types.UID" - } - } - } + "openapi": "3.0.3", + "info": { + "title": "", + "version": "" + }, + "paths": { + "/api/kubepkg.innoai.tech": { + "GET": { + "tags": ["httprouter"], + "operationId": "OpenAPI", + "responses": { + "200": { + "description": "", + "content": { + "application/json": {} + } + } + } + } + }, + "/api/kubepkg.innoai.tech/v1/blobs": { + "PUT": { + "tags": ["blob"], + "summary": "上传 blob", + "operationId": "UploadBlob", + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/octet-stream": { + "schema": {} + } + } + }, + "responses": { + "200": { + "description": "" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"InvalidContentType\",msg=\"InvalidContentType\",code=400}" + ] + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"RegistryError\",msg=\"RegistryError\",code=500}" + ] + } + } + } + }, + "/api/kubepkg.innoai.tech/v1/blobs/{digest}/stat": { + "GET": { + "tags": ["blob"], + "summary": "查询 blob 状态", + "operationId": "StatBlob", + "parameters": [ + { + "name": "digest", + "in": "path", + "required": true, + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "enum": ["sha256"] + } + ] + } + } + ], + "responses": { + "200": { + "description": "" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"InvalidDigest\",msg=\"InvalidDigest\",code=400}" + ] + }, + "404": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"DigestNotFound\",msg=\"DigestNotFound\",code=404}" + ] + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"RegistryError\",msg=\"RegistryError\",code=500}" + ] + } + } + } + }, + "/api/kubepkg.innoai.tech/v1/kubepkgs": { + "GET": { + "tags": ["kubepkg"], + "summary": "部署包列表", + "operationId": "ListKubePkg", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" + } + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" + ] + } + } + }, + "PUT": { + "tags": ["kubepkg"], + "summary": "部署部署包", + "operationId": "ApplyKubePkg", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" + }, + { + "description": "通过 KubePkg.json 部署" + } + ] + } + }, + "application/octet-stream": { + "schema": { + "description": "通过 KubePkg.tgz 部署" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"ReadKubePkgTgzFailed\",msg=\"ReadKubePkgTgzFailed\",code=400}", + "StatusError{key=\"NoRequestBody\",msg=\"NoRequestBody\",code=400}" + ] + } + } + } + }, + "/api/kubepkg.innoai.tech/v1/kubepkgs/{name}": { + "DELETE": { + "tags": ["kubepkg"], + "summary": "删除部署包", + "operationId": "DelKubePkg", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "namespace", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" + ] + } + } + }, + "GET": { + "tags": ["kubepkg"], + "summary": "查询部署包", + "operationId": "GetKubePkg", + "parameters": [ + { + "name": "name", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "namespace", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkg" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatuserrorStatusErr" + } + } + }, + "x-status-returnErrors": [ + "StatusError{key=\"RequestK8sFailed\",msg=\"RequestK8sFailed\",code=500}" + ] + } + } + } + } + }, + "components": { + "schemas": { + "KubepkgV1Alpha1DigestMeta": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "x-go-field-name": "Digest" + }, + "name": { + "type": "string", + "x-go-field-name": "Name" + }, + "platform": { + "type": "string", + "x-go-field-name": "Platform" + }, + "size": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1FileSize" + }, + { + "x-go-field-name": "Size" + } + ] + }, + "tag": { + "type": "string", + "x-go-field-name": "Tag" + }, + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1DigestMetaType" + }, + { + "x-go-field-name": "Type" + } + ] + } + }, + "required": ["type", "digest", "name", "size"], + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.DigestMeta" + }, + "KubepkgV1Alpha1DigestMetaType": { + "type": "string", + "enum": ["blob", "manifest"], + "x-enum-labels": ["Blob", "Manifest"], + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.DigestMetaType" + }, + "KubepkgV1Alpha1FileSize": { + "type": "integer", + "format": "int64", + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.FileSize" + }, + "KubepkgV1Alpha1KubePkg": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1TypeMeta" + }, + { + "type": "object", + "properties": { + "metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1ObjectMeta" + }, + { + "x-go-field-name": "ObjectMeta" + } + ] + }, + "spec": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkgSpec" + }, + { + "x-go-field-name": "Spec" + } + ] + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1KubePkgStatus" + }, + { + "x-go-field-name": "Status" + } + ] + } + } + } + ], + "description": "KubePkg", + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkg" + }, + "KubepkgV1Alpha1KubePkgSpec": { + "type": "object", + "properties": { + "images": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "x-go-field-name": "Images" + }, + "manifests": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1Manifests" + }, + { + "x-go-field-name": "Manifests" + } + ] + }, + "version": { + "type": "string", + "x-go-field-name": "Version" + } + }, + "required": ["version"], + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkgSpec" + }, + "KubepkgV1Alpha1KubePkgStatus": { + "type": "object", + "properties": { + "digests": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KubepkgV1Alpha1DigestMeta" + }, + "x-go-field-name": "Digests" + }, + "statuses": { + "allOf": [ + { + "$ref": "#/components/schemas/KubepkgV1Alpha1Statuses" + }, + { + "x-go-field-name": "Statuses" + } + ] + } + }, + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.KubePkgStatus" + }, + "KubepkgV1Alpha1Manifests": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.Manifests" + }, + "KubepkgV1Alpha1Statuses": { + "type": "object", + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "x-go-vendor-type": "github.com/octohelm/kubepkg/pkg/apis/kubepkg/v1alpha1.Statuses" + }, + "MetaV1FieldsV1": { + "type": "object", + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1" + }, + "MetaV1ManagedFieldsEntry": { + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "x-go-field-name": "APIVersion" + }, + "fieldsType": { + "type": "string", + "x-go-field-name": "FieldsType" + }, + "fieldsV1": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1FieldsV1" + }, + { + "x-go-field-name": "FieldsV1" + } + ] + }, + "manager": { + "type": "string", + "x-go-field-name": "Manager" + }, + "operation": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1ManagedFieldsOperationType" + }, + { + "x-go-field-name": "Operation" + } + ] + }, + "subresource": { + "type": "string", + "x-go-field-name": "Subresource" + }, + "time": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1Time" + }, + { + "x-go-field-name": "Time" + } + ] + } + }, + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry" + }, + "MetaV1ManagedFieldsOperationType": { + "type": "string", + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsOperationType" + }, + "MetaV1ObjectMeta": { + "type": "object", + "properties": { + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "x-go-field-name": "Annotations" + }, + "clusterName": { + "type": "string", + "x-go-field-name": "ZZZ_DeprecatedClusterName" + }, + "creationTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1Time" + }, + { + "x-go-field-name": "CreationTimestamp" + } + ] + }, + "deletionGracePeriodSeconds": { + "type": "integer", + "format": "int64", + "nullable": true, + "x-go-field-name": "DeletionGracePeriodSeconds", + "x-go-star-level": 1 + }, + "deletionTimestamp": { + "allOf": [ + { + "$ref": "#/components/schemas/MetaV1Time" + }, + { + "x-go-field-name": "DeletionTimestamp" + } + ] + }, + "finalizers": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-field-name": "Finalizers" + }, + "generateName": { + "type": "string", + "x-go-field-name": "GenerateName" + }, + "generation": { + "type": "integer", + "format": "int64", + "x-go-field-name": "Generation" + }, + "labels": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "x-go-field-name": "Labels" + }, + "managedFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MetaV1ManagedFieldsEntry" + }, + "x-go-field-name": "ManagedFields" + }, + "name": { + "type": "string", + "x-go-field-name": "Name" + }, + "namespace": { + "type": "string", + "x-go-field-name": "Namespace" + }, + "ownerReferences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MetaV1OwnerReference" + }, + "x-go-field-name": "OwnerReferences" + }, + "resourceVersion": { + "type": "string", + "x-go-field-name": "ResourceVersion" + }, + "selfLink": { + "type": "string", + "x-go-field-name": "SelfLink" + }, + "uid": { + "allOf": [ + { + "$ref": "#/components/schemas/TypesUid" + }, + { + "x-go-field-name": "UID" + } + ] + } + }, + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" + }, + "MetaV1OwnerReference": { + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "x-go-field-name": "APIVersion" + }, + "blockOwnerDeletion": { + "type": "boolean", + "nullable": true, + "x-go-field-name": "BlockOwnerDeletion", + "x-go-star-level": 1 + }, + "controller": { + "type": "boolean", + "nullable": true, + "x-go-field-name": "Controller", + "x-go-star-level": 1 + }, + "kind": { + "type": "string", + "x-go-field-name": "Kind" + }, + "name": { + "type": "string", + "x-go-field-name": "Name" + }, + "uid": { + "allOf": [ + { + "$ref": "#/components/schemas/TypesUid" + }, + { + "x-go-field-name": "UID" + } + ] + } + }, + "required": ["apiVersion", "kind", "name", "uid"], + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference" + }, + "MetaV1Time": { + "type": "string", + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.Time" + }, + "MetaV1TypeMeta": { + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "x-go-field-name": "APIVersion" + }, + "kind": { + "type": "string", + "x-go-field-name": "Kind" + } + }, + "x-go-vendor-type": "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" + }, + "StatuserrorErrorField": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "field path\nprop.slice[2].a", + "x-go-field-name": "Field" + }, + "in": { + "type": "string", + "description": "location\neq. body, query, header, path, formData", + "x-go-field-name": "In" + }, + "msg": { + "type": "string", + "description": "msg", + "x-go-field-name": "Msg" + } + }, + "required": ["field", "msg", "in"], + "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.ErrorField" + }, + "StatuserrorErrorFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/StatuserrorErrorField" + }, + "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.ErrorFields" + }, + "StatuserrorStatusErr": { + "type": "object", + "properties": { + "canBeTalkError": { + "type": "boolean", + "description": "can be task error\nfor client to should error msg to end user", + "x-go-field-name": "CanBeTalkError" + }, + "code": { + "type": "integer", + "format": "int32", + "description": "http code", + "x-go-field-name": "Code" + }, + "desc": { + "type": "string", + "description": "desc of err", + "x-go-field-name": "Desc" + }, + "errorFields": { + "allOf": [ + { + "$ref": "#/components/schemas/StatuserrorErrorFields" + }, + { + "description": "error in where fields", + "x-go-field-name": "ErrorFields" + } + ] + }, + "key": { + "type": "string", + "description": "key of err", + "x-go-field-name": "Key" + }, + "msg": { + "type": "string", + "description": "msg of err", + "x-go-field-name": "Msg" + }, + "sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "error tracing", + "x-go-field-name": "Sources" + } + }, + "required": [ + "code", + "key", + "msg", + "desc", + "canBeTalkError", + "sources", + "errorFields" + ], + "x-go-vendor-type": "github.com/octohelm/courier/pkg/statuserror.StatusErr" + }, + "TypesUid": { + "type": "string", + "x-go-vendor-type": "k8s.io/apimachinery/pkg/types.UID" + } + } + } } diff --git a/nodepkg/gents/src/__tests__/index.spec.ts b/nodepkg/gents/src/__tests__/index.spec.ts index 94661f7b..885f44b7 100644 --- a/nodepkg/gents/src/__tests__/index.spec.ts +++ b/nodepkg/gents/src/__tests__/index.spec.ts @@ -7,15 +7,15 @@ import { generateClient } from ".."; const __dirname = dirname(fileURLToPath(import.meta.url)); describe("#generateClient", () => { - it("gen", async () => { - await generateClient({ - id: "example", - uri: `files://${join(__dirname, "example/openapi.json")}`, - outDir: join(__dirname, "client"), - requestCreator: { - expose: "createRequest", - importPath: "./client", - }, - }); - }); + it("gen", async () => { + await generateClient({ + id: "example", + uri: `files://${join(__dirname, "example/openapi.json")}`, + outDir: join(__dirname, "client"), + requestCreator: { + expose: "createRequest", + importPath: "./client", + }, + }); + }); }); diff --git a/nodepkg/gents/src/index.ts b/nodepkg/gents/src/index.ts index 4525f4c6..ac3a6ee6 100644 --- a/nodepkg/gents/src/index.ts +++ b/nodepkg/gents/src/index.ts @@ -6,54 +6,54 @@ import fetch from "node-fetch"; import { ClientGen, type RequestCreator } from "./ClientGen"; export interface GenerateOptions { - requestCreator: RequestCreator; + requestCreator: RequestCreator; } export interface Options extends GenerateOptions { - id: string; - uri: string; - outDir: string; + id: string; + uri: string; + outDir: string; } const loadOpenAPI = async (uri: string): Promise => { - const u = new URL(uri); + const u = new URL(uri); - if (u.protocol === "files:") { - const b = await readFile(u.pathname); - return JSON.parse(`${b}`); - } + if (u.protocol === "files:") { + const b = await readFile(u.pathname); + return JSON.parse(`${b}`); + } - const res = await fetch(u.toString()); - return await res.json(); + const res = await fetch(u.toString()); + return await res.json(); }; export const generateClient = async (opt: Options) => { - const openapi = await loadOpenAPI(opt.uri); + const openapi = await loadOpenAPI(opt.uri); - const g = new ClientGen(opt.id, openapi, opt.requestCreator); - g.scan(); - return g.sync(join(opt.outDir, `${camelCase(opt.id)}.ts`)); + const g = new ClientGen(opt.id, openapi, opt.requestCreator); + g.scan(); + return g.sync(join(opt.outDir, `${camelCase(opt.id)}.ts`)); }; export const generateClients = async ( - outDir: string, - c: AppConfig & { metadata: Record }, - options: GenerateOptions, + outDir: string, + c: AppConfig & { metadata: Record }, + options: GenerateOptions, ) => { - for (const k in c.config) { - if (c.metadata[k]?.api) { - const { id, openapi } = c.metadata[k]?.api ?? {}; - - const openapiURI = `${c.config[k]}${openapi}`; - - console.log(`generate client ${id} from ${openapiURI}`); - - await generateClient({ - ...options, - id, - uri: openapiURI, - outDir, - }); - } - } + for (const k in c.config) { + if (c.metadata[k]?.api) { + const { id, openapi } = c.metadata[k]?.api ?? {}; + + const openapiURI = `${c.config[k]}${openapi}`; + + console.log(`generate client ${id} from ${openapiURI}`); + + await generateClient({ + ...options, + id, + uri: openapiURI, + outDir, + }); + } + } }; diff --git a/nodepkg/gents/tsconfig.monobundle.json b/nodepkg/gents/tsconfig.monobundle.json index 21023dd8..3170e721 100644 --- a/nodepkg/gents/tsconfig.monobundle.json +++ b/nodepkg/gents/tsconfig.monobundle.json @@ -1,6 +1,6 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - } + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + } } diff --git a/nodepkg/typedef/package.json b/nodepkg/typedef/package.json index bb6b3801..17d47fd6 100644 --- a/nodepkg/typedef/package.json +++ b/nodepkg/typedef/package.json @@ -36,8 +36,7 @@ "directory": "nodepkg/typedef" }, "scripts": { - "test": "bun test .", - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodepkg/typedef/src/core/Meta.ts b/nodepkg/typedef/src/core/Meta.ts index f080e3d5..dc0539ab 100644 --- a/nodepkg/typedef/src/core/Meta.ts +++ b/nodepkg/typedef/src/core/Meta.ts @@ -1,30 +1,30 @@ import { type AnyType, Type, TypeWrapper } from "./Type"; export type MetaBuilder> = { - [K in keyof M]-?: M[K] extends NonNullable - ? (p: M[K]) => MetaBuilder> - : (p?: M[K]) => MetaBuilder>; + [K in keyof M]-?: M[K] extends NonNullable + ? (p: M[K]) => MetaBuilder> + : (p?: M[K]) => MetaBuilder>; } & { - (t: T): T; + (t: T): T; }; export function createMetaBuilder>( - meta?: M, + meta?: M, ): MetaBuilder { - const metadata = meta ?? ({} as Record); + const metadata = meta ?? ({} as Record); - const fn = (t: Type) => { - return TypeWrapper.of(t, { $meta: metadata }); - }; + const fn = (t: Type) => { + return TypeWrapper.of(t, { $meta: metadata }); + }; - const b = new Proxy(fn, { - get(_, name: string) { - return (v: any): any => { - metadata[`${name}`] = v ?? true; - return b; - }; - }, - }); + const b = new Proxy(fn, { + get(_, name: string) { + return (v: any): any => { + metadata[`${name}`] = v ?? true; + return b; + }; + }, + }); - return b as any; + return b as any; } diff --git a/nodepkg/typedef/src/core/Type.ts b/nodepkg/typedef/src/core/Type.ts index 80edbb1f..45dc87aa 100644 --- a/nodepkg/typedef/src/core/Type.ts +++ b/nodepkg/typedef/src/core/Type.ts @@ -1,609 +1,670 @@ -import {get, isObject} from "@innoai-tech/lodash"; +import { get, isObject } from "@innoai-tech/lodash"; export interface UnaryFunction { - (source: T): R; + (source: T): R; } export type Context = { - branch: Array; - path: Array; - node?: TypeNode; - mask?: boolean; + branch: Array; + path: Array; + node?: TypeNode; + mask?: boolean; }; export const EmptyContext: Context = { - path: [], - branch: [], + path: [], + branch: [], }; export type Failure = { - value: any; - key: any; - type: string; - refinement: string | undefined; - message: string; - explanation?: string; + value: any; + key: any; + type: string; + refinement: string | undefined; + message: string; + explanation?: string; } & Context; export class TypedError extends TypeError { - value: any; - key!: any; - type!: string; - refinement!: string | undefined; - path!: Array; - branch!: Array; - failures: () => Array; + value: any; + key!: any; + type!: string; + refinement!: string | undefined; + path!: Array; + branch!: Array; + failures: () => Array; - [x: string]: any; + [x: string]: any; - constructor(failure: Failure, failures: () => Generator) { - let cached: Array | undefined; + constructor(failure: Failure, failures: () => Generator) { + let cached: Array | undefined; - const {message, explanation, ...rest} = failure; - const {path} = failure; - const msg = - path.length === 0 ? message : `At path: ${path.join(".")} -- ${message}`; + const { message, explanation, ...rest } = failure; + const { path } = failure; + const msg = + path.length === 0 ? message : `At path: ${path.join(".")} -- ${message}`; - super(explanation ?? msg); + super(explanation ?? msg); - if (explanation != null) { - this["cause"] = msg; - } + if (explanation != null) { + this["cause"] = msg; + } - Object.assign(this, rest); + Object.assign(this, rest); - this.name = this.constructor.name; + this.name = this.constructor.name; - this.failures = () => { - // biome-ignore lint/suspicious/noAssignInExpressions: - return (cached ??= [failure, ...failures()]); - }; - } + this.failures = () => { + // biome-ignore lint/suspicious/noAssignInExpressions: + return (cached ??= [failure, ...failures()]); + }; + } } export type Result = - | boolean - | string - | Partial - | Iterable>; + | boolean + | string + | Partial + | Iterable>; export type AnyType = Type; export class Type { - static define( - validator: (value: unknown, ctx: Context) => Result = () => true, - ) { - class CustomType extends Type { - override validator(value: unknown, ctx: Context): Result { - return validator(value, ctx); - } - } - - return new CustomType(null); + static define( + validator: (value: unknown, ctx: Context) => Result = () => true, + ) { + class CustomType extends Type { + override validator(value: unknown, ctx: Context): Result { + return validator(value, ctx); + } } - constructor(public readonly schema: Schema) { - } + return new CustomType(null); + } - readonly Type!: T; - readonly Schema!: Schema; + constructor(public readonly schema: Schema) {} - get type() { - return (this.schema || ({} as any)).type ?? "unknown"; - } + readonly Type!: T; + readonly Schema!: Schema; - * entries( - _value: unknown, - _context: Context = EmptyContext, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - } + get type() { + return (this.schema || ({} as any)).type ?? "unknown"; + } - refiner(_value: T, _context: Context): Result { - return []; - } + *entries( + _value: unknown, + _context: Context = EmptyContext, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> {} - validator(_value: unknown, _context: Context): Result { - return []; - } + refiner(_value: T, _context: Context): Result { + return []; + } - coercer(value: unknown, _context: Context): unknown { - return value; - } + validator(_value: unknown, _context: Context): Result { + return []; + } - public validate( - value: unknown, - options: { - coerce?: boolean; - message?: string; - } = {}, - ): [TypedError, undefined] | [undefined, T] { - return validate(value, this, options); - } + coercer(value: unknown, _context: Context): unknown { + return value; + } - public create(value: unknown): T { - const result = validate(value, this, {coerce: true}); - - if (result[0]) { - throw result[0]; - } - - return result[1]; - } - - public mask(value: unknown): T { - const result = validate(value, this, {coerce: true, mask: true}); - if (result[0]) { - throw result[0]; - } - return result[1]; - } - - default(v: T): DefaultedType> { - return DefaultedType.create(this, v); - } - - optional(): OptionalType> { - return OptionalType.create(this); - } - - use, A>(op1: UnaryFunction): A; - use, A>(op1: UnaryFunction): A; - use, A, B>(op1: UnaryFunction, op2: UnaryFunction): B; - use, A, B, C>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction): C; - use, A, B, C, D>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction): D; - use, A, B, C, D, E>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction): E; - use, A, B, C, D, E, F>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction, op6: UnaryFunction): F; - use, A, B, C, D, E, F, G>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction, op6: UnaryFunction, op7: UnaryFunction): G; - use, A, B, C, D, E, F, G, H>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction, op6: UnaryFunction, op7: UnaryFunction, op8: UnaryFunction): H; - use, A, B, C, D, E, F, G, H, I>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction, op6: UnaryFunction, op7: UnaryFunction, op8: UnaryFunction, op9: UnaryFunction): I; - use, A, B, C, D, E, F, G, H, I>(op1: UnaryFunction, op2: UnaryFunction, op3: UnaryFunction, op4: UnaryFunction, op5: UnaryFunction, op6: UnaryFunction, op7: UnaryFunction, op8: UnaryFunction, op9: UnaryFunction, ...operations: UnaryFunction[]): Type; - use(...modifiers: UnaryFunction[]): Type { - return modifiers.reduce( - (t, r) => (r as any)(t), - this as Type, - ) as any; - } - - get unwrap(): Type { - return this; - } - - get meta(): Record { - if (this.schema) { - return get(this.schema, ["$meta"]) ?? {}; - } - return {}; - } - - getMeta(key: string): T | undefined { - if (this.schema) { - return get(this.schema, ["$meta", key]); - } - return undefined; - } - - getSchema(key: string): T | undefined { - if (key && this.schema) { - return get(this.schema, [key]); - } - return undefined; - } - - get isOptional(): boolean { - return false; - } + public validate( + value: unknown, + options: { + coerce?: boolean; + message?: string; + } = {}, + ): [TypedError, undefined] | [undefined, T] { + return validate(value, this, options); + } + + public create(value: unknown): T { + const result = validate(value, this, { coerce: true }); + + if (result[0]) { + throw result[0]; + } + + return result[1]; + } + + public mask(value: unknown): T { + const result = validate(value, this, { coerce: true, mask: true }); + if (result[0]) { + throw result[0]; + } + return result[1]; + } + + default(v: T): DefaultedType> { + return DefaultedType.create(this, v); + } + + optional(): OptionalType> { + return OptionalType.create(this); + } + + use, A>(op1: UnaryFunction): A; + use, A>(op1: UnaryFunction): A; + use, A, B>( + op1: UnaryFunction, + op2: UnaryFunction, + ): B; + use, A, B, C>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + ): C; + use, A, B, C, D>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + ): D; + use, A, B, C, D, E>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + ): E; + use, A, B, C, D, E, F>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + ): F; + use, A, B, C, D, E, F, G>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + ): G; + use, A, B, C, D, E, F, G, H>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, + ): H; + use, A, B, C, D, E, F, G, H, I>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, + op9: UnaryFunction, + ): I; + use, A, B, C, D, E, F, G, H, I>( + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, + op9: UnaryFunction, + ...operations: UnaryFunction[] + ): Type; + use(...modifiers: UnaryFunction[]): Type { + return modifiers.reduce( + (t, r) => (r as any)(t), + this as Type, + ) as any; + } + + get unwrap(): Type { + return this; + } + + get meta(): Record { + if (this.schema) { + return get(this.schema, ["$meta"]) ?? {}; + } + return {}; + } + + getMeta(key: string): T | undefined { + if (this.schema) { + return get(this.schema, ["$meta", key]); + } + return undefined; + } + + getSchema(key: string): T | undefined { + if (key && this.schema) { + return get(this.schema, [key]); + } + return undefined; + } + + get isOptional(): boolean { + return false; + } } export class TypeWrapper< - T, - U extends AnyType, - Schema extends Record, + T, + U extends AnyType, + Schema extends Record, > extends Type U) }> { - static of>( - t: U, - extra: ExtraSchema, - ): Type, MergeSchema, ExtraSchema>> { - return new TypeWrapper, U, ExtraSchema>({ - ...extra, - $unwrap: t, - }); - } - - static refine>( - t: U, - refiner: (v: Infer, ctx: Context) => Result, - schema: S, - ): Type, MergeSchema, S>> { - class Refiner< - U extends AnyType, - S extends Record, - > extends TypeWrapper, U, S> { - override* refiner(value: Infer, ctx: Context): Result { - yield* this.unwrap.refiner(value, ctx); - const result = refiner(value, ctx); - const failures = toFailures(result, ctx, t, value); - - for (const failure of failures) { - yield {...failure, refinement: Object.keys(schema).join(",")}; - } - } + static of>( + t: U, + extra: ExtraSchema, + ): Type, MergeSchema, ExtraSchema>> { + return new TypeWrapper, U, ExtraSchema>({ + ...extra, + $unwrap: t, + }); + } + + static refine>( + t: U, + refiner: (v: Infer, ctx: Context) => Result, + schema: S, + ): Type, MergeSchema, S>> { + class Refiner< + U extends AnyType, + S extends Record, + > extends TypeWrapper, U, S> { + override *refiner(value: Infer, ctx: Context): Result { + yield* this.unwrap.refiner(value, ctx); + const result = refiner(value, ctx); + const failures = toFailures(result, ctx, t, value); + + for (const failure of failures) { + yield { ...failure, refinement: Object.keys(schema).join(",") }; } - - return new Refiner({ - ...schema, - $unwrap: t, - }); + } } - override get type() { - return this.unwrap.type; - } + return new Refiner({ + ...schema, + $unwrap: t, + }); + } - override get unwrap() { - return typeof this.schema.$unwrap === "function" - ? this.schema.$unwrap() - : this.schema.$unwrap; - } - - override get isOptional(): boolean { - return this.unwrap.isOptional; - } + override get type() { + return this.unwrap.type; + } - override get meta(): Record { - return { - ...this.unwrap.meta, - ...get(this.schema, ["$meta"], {}), - }; - } - - override getMeta(key: string): T | undefined { - if (this.schema) { - return get(this.schema, ["$meta", key]) ?? this.unwrap.getMeta(key); - } - return undefined; - } + override get unwrap() { + return typeof this.schema.$unwrap === "function" + ? this.schema.$unwrap() + : this.schema.$unwrap; + } - override getSchema(key?: string): T | undefined { - if (key) { - return get(this.schema, [key]) ?? this.unwrap.getSchema(key); - } - return undefined; - } + override get isOptional(): boolean { + return this.unwrap.isOptional; + } - override* entries( - value: unknown, - context: Context = EmptyContext, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - yield* this.unwrap.entries(value, { - ...context, - node: TypeNode.create(this, context.node), - }); - } + override get meta(): Record { + return { + ...this.unwrap.meta, + ...get(this.schema, ["$meta"], {}), + }; + } - override validator(value: unknown, context: Context): Result { - return toFailures( - this.unwrap.validator(value, context), - context, - this, - value, - ); + override getMeta(key: string): T | undefined { + if (this.schema) { + return get(this.schema, ["$meta", key]) ?? this.unwrap.getMeta(key); } + return undefined; + } - override refiner(value: T, context: Context): Result { - return toFailures( - this.unwrap.refiner(value, context), - context, - this, - value, - ); + override getSchema(key?: string): T | undefined { + if (key) { + return get(this.schema, [key]) ?? this.unwrap.getSchema(key); } + return undefined; + } - override coercer(value: unknown, context: Context) { - return this.unwrap.coercer(value, context); - } + override *entries( + value: unknown, + context: Context = EmptyContext, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { + yield* this.unwrap.entries(value, { + ...context, + node: TypeNode.create(this, context.node), + }); + } + + override validator(value: unknown, context: Context): Result { + return toFailures( + this.unwrap.validator(value, context), + context, + this, + value, + ); + } + + override refiner(value: T, context: Context): Result { + return toFailures( + this.unwrap.refiner(value, context), + context, + this, + value, + ); + } + + override coercer(value: unknown, context: Context) { + return this.unwrap.coercer(value, context); + } } // biome-ignore lint/complexity/noStaticOnlyClass: export class TypeNode extends TypeWrapper< - Infer, - U, - { $parent: P | null } + Infer, + U, + { $parent: P | null } > { - static create( - t: U, - p: P | undefined | null, - ) { - return new TypeNode({ - $unwrap: t, - $parent: p ? p : null, - }); - } + static create( + t: U, + p: P | undefined | null, + ) { + return new TypeNode({ + $unwrap: t, + $parent: p ? p : null, + }); + } } export class DefaultedType extends TypeWrapper< - Infer, - T, - { default: Infer } + Infer, + T, + { default: Infer } > { - static create(t: U, defaultValue: Infer) { - return new DefaultedType({ - $unwrap: t, - default: defaultValue, - }); - } - - override coercer(value: unknown, context: Context): unknown { - return typeof value === "undefined" - ? this.schema.default - : super.unwrap.coercer(value, context); - } + static create(t: U, defaultValue: Infer) { + return new DefaultedType({ + $unwrap: t, + default: defaultValue, + }); + } + + override coercer(value: unknown, context: Context): unknown { + return typeof value === "undefined" + ? this.schema.default + : super.unwrap.coercer(value, context); + } } export class OptionalType extends TypeWrapper< - Infer | undefined, - T, - {} + Infer | undefined, + T, + {} > { - static create(t: T) { - return new OptionalType({ - $unwrap: t, - }); - } - - override get isOptional(): boolean { - return true; - } - - override refiner(value: T | undefined, context: Context): Result { - return value === undefined || super.unwrap.refiner(value, context); - } - - override validator(value: unknown, context: Context): Result { - return value === undefined || super.unwrap.validator(value, context); - } + static create(t: T) { + return new OptionalType({ + $unwrap: t, + }); + } + + override get isOptional(): boolean { + return true; + } + + override refiner(value: T | undefined, context: Context): Result { + return value === undefined || super.unwrap.refiner(value, context); + } + + override validator(value: unknown, context: Context): Result { + return value === undefined || super.unwrap.validator(value, context); + } } export type Infer = T["Type"]; export type InferSchema = T["Schema"]; -export type MergeSchema = Omit & B +export type MergeSchema = Omit & B; export type InferTuple< - Tuple extends Type[], - Length extends number = Tuple["length"], + Tuple extends Type[], + Length extends number = Tuple["length"], > = Length extends Length - ? number extends Length - ? Tuple - : _InferTuple - : never; + ? number extends Length + ? Tuple + : _InferTuple + : never; type _InferTuple< - Tuple extends Type[], - Length extends number, - Accumulated extends unknown[], - Index extends number = Accumulated["length"], + Tuple extends Type[], + Length extends number, + Accumulated extends unknown[], + Index extends number = Accumulated["length"], > = Index extends Length - ? Accumulated - : _InferTuple]>; + ? Accumulated + : _InferTuple]>; export type Simplify = T extends any[] ? T : { [K in keyof T]: T[K] } & {}; export type OmitBy = Omit< - T, - { [K in keyof T]: V extends Extract ? K : never }[keyof T] + T, + { [K in keyof T]: V extends Extract ? K : never }[keyof T] >; export type PickBy = Pick< - T, - { [K in keyof T]: V extends Extract ? K : never }[keyof T] + T, + { [K in keyof T]: V extends Extract ? K : never }[keyof T] >; export type Optionalize = OmitBy & - Partial>; + Partial>; export type ObjectType> = Simplify< - Optionalize<{ [K in keyof S]: Infer }> + Optionalize<{ [K in keyof S]: Infer }> >; export function shiftIterator(input: Iterator): T | undefined { - const {done, value} = input.next(); - return done ? undefined : value; + const { done, value } = input.next(); + return done ? undefined : value; } function isIterable(x: unknown): x is Iterable { - return isObject(x) && typeof (x as any)[Symbol.iterator] === "function"; + return isObject(x) && typeof (x as any)[Symbol.iterator] === "function"; } export function toFailure( - ret: string | boolean | Partial, - context: Context, - t: Type, - value: any, + ret: string | boolean | Partial, + context: Context, + t: Type, + value: any, ): Failure | undefined { - if (ret === true) { - return; - } - - let result: { message?: string; refinement?: string } = {}; - - if (ret === false) { - result = {}; - } else if (typeof ret === "string") { - result = {message: ret}; - } else { - result = ret; - } - - const {path, branch, node} = context; - const {type} = t; - - const { - refinement, - message = `Expected a value of type \`${type}\`${ - refinement ? ` with refinement \`${refinement}\`` : "" - }, but received: \`${value}\``, - } = result; - - return { - value, - type, - refinement, - key: path[path.length - 1], - path, - branch, - node, - ...result, - message, - }; + if (ret === true) { + return; + } + + let result: { message?: string; refinement?: string } = {}; + + if (ret === false) { + result = {}; + } else if (typeof ret === "string") { + result = { message: ret }; + } else { + result = ret; + } + + const { path, branch, node } = context; + const { type } = t; + + const { + refinement, + message = `Expected a value of type \`${type}\`${ + refinement ? ` with refinement \`${refinement}\`` : "" + }, but received: \`${value}\``, + } = result; + + return { + value, + type, + refinement, + key: path[path.length - 1], + path, + branch, + node, + ...result, + message, + }; } export function* toFailures( - ret: Result, - context: Context, - t: Type, - value: any, + ret: Result, + context: Context, + t: Type, + value: any, ): IterableIterator { - let result = ret; + let result = ret; - if (!isIterable(result)) { - result = [result]; - } + if (!isIterable(result)) { + result = [result]; + } - for (const r of result) { - const failure = toFailure(r, context, t, value); + for (const r of result) { + const failure = toFailure(r, context, t, value); - if (failure) { - yield failure; - } + if (failure) { + yield failure; } + } } export function validate( - value: unknown, - typed: Type, - options: { - coerce?: boolean; - mask?: boolean; - message?: string; - } = {}, + value: unknown, + typed: Type, + options: { + coerce?: boolean; + mask?: boolean; + message?: string; + } = {}, ): [TypedError, undefined] | [undefined, T] { - const tuples = run(value, typed, options); - // biome-ignore lint/style/noNonNullAssertion: - const tuple = shiftIterator(tuples)!; - - if (tuple[0]) { - const error = new TypedError(tuple[0], function* () { - for (const t of tuples) { - if (t[0]) { - yield t[0]; - } - } - }); - return [error, undefined]; - } + const tuples = run(value, typed, options); + // biome-ignore lint/style/noNonNullAssertion: + const tuple = shiftIterator(tuples)!; + + if (tuple[0]) { + const error = new TypedError(tuple[0], function* () { + for (const t of tuples) { + if (t[0]) { + yield t[0]; + } + } + }); + return [error, undefined]; + } - const v = tuple[1]; - return [undefined, v]; + const v = tuple[1]; + return [undefined, v]; } export function* run( - v: any, - t: Type, - options: Partial & { - coerce?: boolean; - mask?: boolean; - message?: string; - } = {}, + v: any, + t: Type, + options: Partial & { + coerce?: boolean; + mask?: boolean; + message?: string; + } = {}, ): IterableIterator<[Failure, undefined] | [undefined, T]> { - const { - path = [], - branch = [v], - node = TypeNode.create(t, null), - coerce = false, - mask = false, - } = options; - - const ctx: Context = {mask, path, branch, node}; - - let value = v; - if (coerce) { - value = t.coercer(value, ctx); - } - - let status: Status = Status.valid; - - for (const failure of toFailures(t.validator(value, ctx), ctx, t, value)) { - failure.explanation = options.message; - status = Status.not_valid; - yield [failure, undefined]; - } - - for (let [k, v, st] of t.entries(value, ctx)) { - const ts = run(v, st as Type, { - path: k === undefined ? path : [...path, k], - branch: k === undefined ? branch : [...branch, v], - node: k === undefined ? node : TypeNode.create(st, node), - coerce, - mask, - message: options.message, - }); - - for (const t of ts) { - if (t[0]) { - status = - t[0].refinement != null ? Status.not_refined : Status.not_valid; - yield [t[0], undefined]; - } else if (coerce) { - v = t[1]; - - if (k === undefined) { - value = v; - } else if (value instanceof Map) { - value.set(k, v); - } else if (value instanceof Set) { - value.add(v); - } else if (isObject(value)) { - if (v !== undefined || k in value) { - (value as any)[k] = v; - } - } - } + const { + path = [], + branch = [v], + node = TypeNode.create(t, null), + coerce = false, + mask = false, + } = options; + + const ctx: Context = { mask, path, branch, node }; + + let value = v; + if (coerce) { + value = t.coercer(value, ctx); + } + + let status: Status = Status.valid; + + for (const failure of toFailures(t.validator(value, ctx), ctx, t, value)) { + failure.explanation = options.message; + status = Status.not_valid; + yield [failure, undefined]; + } + + for (let [k, v, st] of t.entries(value, ctx)) { + const ts = run(v, st as Type, { + path: k === undefined ? path : [...path, k], + branch: k === undefined ? branch : [...branch, v], + node: k === undefined ? node : TypeNode.create(st, node), + coerce, + mask, + message: options.message, + }); + + for (const t of ts) { + if (t[0]) { + status = + t[0].refinement != null ? Status.not_refined : Status.not_valid; + yield [t[0], undefined]; + } else if (coerce) { + v = t[1]; + + if (k === undefined) { + value = v; + } else if (value instanceof Map) { + value.set(k, v); + } else if (value instanceof Set) { + value.add(v); + } else if (isObject(value)) { + if (v !== undefined || k in value) { + (value as any)[k] = v; + } } - } - - if (status !== Status.not_valid) { - for (const failure of toFailures( - t.refiner(value as T, ctx), - ctx, - t, - value, - )) { - failure.explanation = options.message; - status = Status.not_refined; - yield [failure, undefined]; - } - } - - if (status === Status.valid) { - yield [undefined, value as T]; - } + } + } + } + + if (status !== Status.not_valid) { + for (const failure of toFailures( + t.refiner(value as T, ctx), + ctx, + t, + value, + )) { + failure.explanation = options.message; + status = Status.not_refined; + yield [failure, undefined]; + } + } + + if (status === Status.valid) { + yield [undefined, value as T]; + } } enum Status { - valid = 0, - not_refined = 1, - not_valid = 2, + valid = 0, + not_refined = 1, + not_valid = 2, } export class TypeNever extends Type { - static create() { - return new TypeNever(false); - } + static create() { + return new TypeNever(false); + } - override get type() { - return "never"; - } + override get type() { + return "never"; + } - override validator(_value: unknown, _context: Context): Result { - return false; - } -} \ No newline at end of file + override validator(_value: unknown, _context: Context): Result { + return false; + } +} diff --git a/nodepkg/typedef/src/core/TypeAny.ts b/nodepkg/typedef/src/core/TypeAny.ts index d8901d97..86c8140f 100644 --- a/nodepkg/typedef/src/core/TypeAny.ts +++ b/nodepkg/typedef/src/core/TypeAny.ts @@ -1,19 +1,19 @@ -import {Type} from "./Type.ts"; +import { Type } from "./Type.ts"; export class TypeAny extends Type { - static create() { - return new TypeAny(); - } + static create() { + return new TypeAny(); + } - constructor() { - super({}); - } + constructor() { + super({}); + } - override get type() { - return "any"; - } + override get type() { + return "any"; + } - override validator() { - return true; - } -} \ No newline at end of file + override validator() { + return true; + } +} diff --git a/nodepkg/typedef/src/core/TypeArray.ts b/nodepkg/typedef/src/core/TypeArray.ts index d03abd72..a826d2af 100644 --- a/nodepkg/typedef/src/core/TypeArray.ts +++ b/nodepkg/typedef/src/core/TypeArray.ts @@ -1,41 +1,41 @@ -import type {AnyType, Infer} from "./Type.ts"; -import {Type} from "./Type.ts"; +import type { AnyType, Infer } from "./Type.ts"; +import { Type } from "./Type.ts"; export class TypeArray extends Type< - T[], - { - type: "array"; - items: S; - } + T[], + { + type: "array"; + items: S; + } > { - static create(items: Items) { - return new TypeArray, Items>({ - type: "array", - items: items, - }); - } + static create(items: Items) { + return new TypeArray, Items>({ + type: "array", + items: items, + }); + } - override get type() { - return this.schema.type; - } + override get type() { + return this.schema.type; + } - override* entries( - value: unknown, - ): Iterable<[string | number, unknown, AnyType | Type]> { - if (this.schema.items.type !== "any") { - if (Array.isArray(value)) { - for (const [i, v] of value.entries()) { - yield [i, v, this.schema.items]; - } - } + override *entries( + value: unknown, + ): Iterable<[string | number, unknown, AnyType | Type]> { + if (this.schema.items.type !== "any") { + if (Array.isArray(value)) { + for (const [i, v] of value.entries()) { + yield [i, v, this.schema.items]; } + } } + } - override validator(value: unknown) { - return Array.isArray(value); - } + override validator(value: unknown) { + return Array.isArray(value); + } - override coercer(value: unknown) { - return Array.isArray(value) ? value.slice() : value; - } -} \ No newline at end of file + override coercer(value: unknown) { + return Array.isArray(value) ? value.slice() : value; + } +} diff --git a/nodepkg/typedef/src/core/TypeBinary.ts b/nodepkg/typedef/src/core/TypeBinary.ts index 9219b249..5638d23e 100644 --- a/nodepkg/typedef/src/core/TypeBinary.ts +++ b/nodepkg/typedef/src/core/TypeBinary.ts @@ -1,18 +1,18 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export class TypeBinary extends Type< - File | Blob, - { type: "string"; format: "binary" } + File | Blob, + { type: "string"; format: "binary" } > { - static create() { - return new TypeBinary({type: "string", format: "binary"}); - } + static create() { + return new TypeBinary({ type: "string", format: "binary" }); + } - override get type() { - return "binary"; - } + override get type() { + return "binary"; + } - override validator(value: unknown, _: Context) { - return value instanceof File || value instanceof Blob; - } + override validator(value: unknown, _: Context) { + return value instanceof File || value instanceof Blob; + } } diff --git a/nodepkg/typedef/src/core/TypeBoolean.ts b/nodepkg/typedef/src/core/TypeBoolean.ts index d110e3b8..b6c9de6a 100644 --- a/nodepkg/typedef/src/core/TypeBoolean.ts +++ b/nodepkg/typedef/src/core/TypeBoolean.ts @@ -1,15 +1,15 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export class TypeBoolean extends Type { - static create() { - return new TypeBoolean({type: "boolean"}); - } + static create() { + return new TypeBoolean({ type: "boolean" }); + } - override get type() { - return this.schema.type; - } + override get type() { + return this.schema.type; + } - override validator(value: unknown, _: Context) { - return typeof value === "boolean"; - } -} \ No newline at end of file + override validator(value: unknown, _: Context) { + return typeof value === "boolean"; + } +} diff --git a/nodepkg/typedef/src/core/TypeEnum.ts b/nodepkg/typedef/src/core/TypeEnum.ts index cd631cb6..d1123009 100644 --- a/nodepkg/typedef/src/core/TypeEnum.ts +++ b/nodepkg/typedef/src/core/TypeEnum.ts @@ -1,38 +1,38 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export type NativeEnumLike = { - [k: string]: string | number; - [nu: number]: string; + [k: string]: string | number; + [nu: number]: string; }; export class TypeEnum extends Type { - static create( - values: T, - ): TypeEnum; - static create( - values: T, - ): TypeEnum; - static create( - values: U[], - ): TypeEnum { - return new TypeEnum({enum: values}); - } + static create( + values: T, + ): TypeEnum; + static create( + values: T, + ): TypeEnum; + static create( + values: U[], + ): TypeEnum { + return new TypeEnum({ enum: values }); + } - static literal(constant: T) { - return new TypeEnum({enum: [constant]}); - } + static literal(constant: T) { + return new TypeEnum({ enum: [constant] }); + } - static nativeEnum(nativeEnum: U) { - return new TypeEnum({ - enum: Object.values(nativeEnum) as any[], - }); - } + static nativeEnum(nativeEnum: U) { + return new TypeEnum({ + enum: Object.values(nativeEnum) as any[], + }); + } - override get type() { - return "enums"; - } + override get type() { + return "enums"; + } - override validator(value: unknown, _: Context) { - return this.schema.enum.includes(value as any); - } + override validator(value: unknown, _: Context) { + return this.schema.enum.includes(value as any); + } } diff --git a/nodepkg/typedef/src/core/TypeInteger.ts b/nodepkg/typedef/src/core/TypeInteger.ts index a81f51be..5a89163e 100644 --- a/nodepkg/typedef/src/core/TypeInteger.ts +++ b/nodepkg/typedef/src/core/TypeInteger.ts @@ -1,19 +1,19 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export class TypeInteger extends Type { - static create() { - return new TypeInteger({type: "integer"}); - } + static create() { + return new TypeInteger({ type: "integer" }); + } - override get type() { - return this.schema.type; - } + override get type() { + return this.schema.type; + } - override validator(value: unknown, _: Context) { - return ( - typeof value === "number" && - !Number.isNaN(value) && - Number.isInteger(value) - ); - } -} \ No newline at end of file + override validator(value: unknown, _: Context) { + return ( + typeof value === "number" && + !Number.isNaN(value) && + Number.isInteger(value) + ); + } +} diff --git a/nodepkg/typedef/src/core/TypeIntersection.ts b/nodepkg/typedef/src/core/TypeIntersection.ts index d59edc6b..0b790a1c 100644 --- a/nodepkg/typedef/src/core/TypeIntersection.ts +++ b/nodepkg/typedef/src/core/TypeIntersection.ts @@ -1,48 +1,56 @@ -import {type AnyType, type Infer, type Context, EmptyContext, toFailures, Type, type Result} from "./Type.ts"; +import { + type AnyType, + type Infer, + type Context, + EmptyContext, + toFailures, + Type, + type Result, +} from "./Type.ts"; type IntersectionTypes = Types extends [ - infer T, - ...infer O, - ] - ? T extends AnyType - ? Infer & IntersectionTypes - : unknown - : unknown; + infer T, + ...infer O, +] + ? T extends AnyType + ? Infer & IntersectionTypes + : unknown + : unknown; export class TypeIntersection extends Type< - T, - { - allOf: S; - } + T, + { + allOf: S; + } > { - static create(...types: Types) { - return new TypeIntersection, Types>({ - allOf: types, - }); - } + static create(...types: Types) { + return new TypeIntersection, Types>({ + allOf: types, + }); + } - override get type() { - return "intersection"; - } + override get type() { + return "intersection"; + } - override* entries( - value: unknown, - ctx: Context = EmptyContext, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - for (const t of this.schema.allOf) { - yield* t.entries(value, ctx); - } + override *entries( + value: unknown, + ctx: Context = EmptyContext, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { + for (const t of this.schema.allOf) { + yield* t.entries(value, ctx); } + } - override* validator(value: unknown, ctx: Context): Result { - for (const t of this.schema.allOf) { - yield* toFailures(t.validator(value, ctx), ctx, this, value); - } + override *validator(value: unknown, ctx: Context): Result { + for (const t of this.schema.allOf) { + yield* toFailures(t.validator(value, ctx), ctx, this, value); } + } - override* refiner(value: unknown, ctx: Context): Result { - for (const t of this.schema.allOf) { - yield* toFailures(t.refiner(value, ctx), ctx, this, value); - } + override *refiner(value: unknown, ctx: Context): Result { + for (const t of this.schema.allOf) { + yield* toFailures(t.refiner(value, ctx), ctx, this, value); } + } } diff --git a/nodepkg/typedef/src/core/TypeNull.ts b/nodepkg/typedef/src/core/TypeNull.ts index 69e248eb..8f597447 100644 --- a/nodepkg/typedef/src/core/TypeNull.ts +++ b/nodepkg/typedef/src/core/TypeNull.ts @@ -1,15 +1,15 @@ -import {type Context, type Result, Type} from "./Type.ts"; +import { type Context, type Result, Type } from "./Type.ts"; export class TypeNull extends Type { - static create() { - return new TypeNull({type: "null"}); - } + static create() { + return new TypeNull({ type: "null" }); + } - override get type() { - return "null"; - } + override get type() { + return "null"; + } - override validator(value: unknown, _context: Context): Result { - return Object.is(value, null); - } -} \ No newline at end of file + override validator(value: unknown, _context: Context): Result { + return Object.is(value, null); + } +} diff --git a/nodepkg/typedef/src/core/TypeNumber.ts b/nodepkg/typedef/src/core/TypeNumber.ts index a1a68c77..fc6e04b6 100644 --- a/nodepkg/typedef/src/core/TypeNumber.ts +++ b/nodepkg/typedef/src/core/TypeNumber.ts @@ -1,15 +1,15 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export class TypeNumber extends Type { - static create() { - return new TypeNumber({type: "number"}); - } + static create() { + return new TypeNumber({ type: "number" }); + } - override get type() { - return this.schema.type; - } + override get type() { + return this.schema.type; + } - override validator(value: unknown, _: Context) { - return typeof value === "number" && !Number.isNaN(value); - } -} \ No newline at end of file + override validator(value: unknown, _: Context) { + return typeof value === "number" && !Number.isNaN(value); + } +} diff --git a/nodepkg/typedef/src/core/TypeObject.ts b/nodepkg/typedef/src/core/TypeObject.ts index d3145766..590205b0 100644 --- a/nodepkg/typedef/src/core/TypeObject.ts +++ b/nodepkg/typedef/src/core/TypeObject.ts @@ -1,99 +1,98 @@ -import type {AnyType, Context, Infer, ObjectType} from "./Type.ts"; -import {isObject} from "@innoai-tech/lodash"; -import {Type, TypeNever} from "./Type.ts"; - +import type { AnyType, Context, Infer, ObjectType } from "./Type.ts"; +import { isObject } from "@innoai-tech/lodash"; +import { Type, TypeNever } from "./Type.ts"; export class TypeObject< - T extends Record, - Props extends Record, + T extends Record, + Props extends Record, > extends Type< - ObjectType, - { - type: "object"; - properties?: Props; - required: Array; - additionalProperties: AnyType | Type; - } + ObjectType, + { + type: "object"; + properties?: Props; + required: Array; + additionalProperties: AnyType | Type; + } > { - static create(): TypeObject<{}, {}>; - static create>( - props: Props, - ): TypeObject<{ [K in keyof Props]: Infer }, Props>; - static create>(props?: Props) { - const required: string[] = []; + static create(): TypeObject<{}, {}>; + static create>( + props: Props, + ): TypeObject<{ [K in keyof Props]: Infer }, Props>; + static create>(props?: Props) { + const required: string[] = []; - if (props) { - for (const propName in props) { - const p = props[propName]; - if (!p?.isOptional) { - required.push(propName); - } - } + if (props) { + for (const propName in props) { + const p = props[propName]; + if (!p?.isOptional) { + required.push(propName); } - - return new TypeObject<{ [K in keyof Props]: Infer }, Props>({ - type: "object", - properties: props, - required: required, - additionalProperties: TypeNever.create(), - }); + } } - override get type() { - return this.schema.type; - } + return new TypeObject<{ [K in keyof Props]: Infer }, Props>({ + type: "object", + properties: props, + required: required, + additionalProperties: TypeNever.create(), + }); + } - override* entries( - value: unknown, - ctx: Context, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - if (isObject(value)) { - const propNames = new Set(Object.keys(value)); + override get type() { + return this.schema.type; + } - if (this.schema.properties) { - for (const key in this.schema.properties) { - propNames.delete(key); - yield [ - key, - (value as any)[key], - (this.schema.properties as any)[key], - ]; - } - } + override *entries( + value: unknown, + ctx: Context, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { + if (isObject(value)) { + const propNames = new Set(Object.keys(value)); - if (ctx.node?.type !== "intersection") { - for (const key of propNames) { - yield [key, (value as any)[key], this.schema.additionalProperties]; - } - } + if (this.schema.properties) { + for (const key in this.schema.properties) { + propNames.delete(key); + yield [ + key, + (value as any)[key], + (this.schema.properties as any)[key], + ]; } - } + } - override validator(value: unknown, _ctx: Context) { - return isObject(value); + if (ctx.node?.type !== "intersection") { + for (const key of propNames) { + yield [key, (value as any)[key], this.schema.additionalProperties]; + } + } } + } - override coercer(value: unknown, ctx: Context) { - if (isObject(value)) { - const v: { [k: string]: any } = {...value}; + override validator(value: unknown, _ctx: Context) { + return isObject(value); + } - if (ctx.mask) { - // FIXME: support object with additionalProperties + override coercer(value: unknown, ctx: Context) { + if (isObject(value)) { + const v: { [k: string]: any } = { ...value }; - const properties = this.schema.properties; + if (ctx.mask) { + // FIXME: support object with additionalProperties - if (properties) { - for (const key in v) { - if (properties[key] === undefined) { - delete v[key]; - } - } - } - } + const properties = this.schema.properties; - return v; + if (properties) { + for (const key in v) { + if (properties[key] === undefined) { + delete v[key]; + } + } } + } - return value; + return v; } + + return value; + } } diff --git a/nodepkg/typedef/src/core/TypeRecord.ts b/nodepkg/typedef/src/core/TypeRecord.ts index 70d47365..aaeb7889 100644 --- a/nodepkg/typedef/src/core/TypeRecord.ts +++ b/nodepkg/typedef/src/core/TypeRecord.ts @@ -1,54 +1,54 @@ -import {isObject} from "@innoai-tech/lodash"; -import {type Infer, type AnyType, Type} from "./Type.ts"; +import { isObject } from "@innoai-tech/lodash"; +import { type Infer, type AnyType, Type } from "./Type.ts"; export const SymbolRecordKey = Symbol("$_key"); export class TypeRecord< - K extends string, - V, - S extends { - propertyNames: Type; - additionalProperties: Type; - }, + K extends string, + V, + S extends { + propertyNames: Type; + additionalProperties: Type; + }, > extends Type< - Record, - { - type: "object"; - } & S + Record, + { + type: "object"; + } & S > { - static create(k: K, v: V) { - return new TypeRecord< - Infer, - Infer, - { - propertyNames: K; - additionalProperties: V; - } - >({ - propertyNames: k, - additionalProperties: v, - type: "object", - }); - } + static create(k: K, v: V) { + return new TypeRecord< + Infer, + Infer, + { + propertyNames: K; + additionalProperties: V; + } + >({ + propertyNames: k, + additionalProperties: v, + type: "object", + }); + } - override get type() { - return "record"; - } + override get type() { + return "record"; + } - override* entries( - value: unknown, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - if (isObject(value)) { - for (const k in value) { - const v = (value as any)[k]; + override *entries( + value: unknown, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { + if (isObject(value)) { + for (const k in value) { + const v = (value as any)[k]; - yield [SymbolRecordKey, k, this.schema.propertyNames]; - yield [k, v, this.schema.additionalProperties]; - } - } + yield [SymbolRecordKey, k, this.schema.propertyNames]; + yield [k, v, this.schema.additionalProperties]; + } } + } - override validator(value: unknown) { - return isObject(value); - } + override validator(value: unknown) { + return isObject(value); + } } diff --git a/nodepkg/typedef/src/core/TypeRef.ts b/nodepkg/typedef/src/core/TypeRef.ts index 1e213ecd..2f4f75f6 100644 --- a/nodepkg/typedef/src/core/TypeRef.ts +++ b/nodepkg/typedef/src/core/TypeRef.ts @@ -1,24 +1,18 @@ -import { - type AnyType, - type Infer, - TypeWrapper, -} from "./Type"; +import { type AnyType, type Infer, TypeWrapper } from "./Type"; export class TypeRef extends TypeWrapper< - Infer, - U, - { $ref: string } + Infer, + U, + { $ref: string } > { - static create(name: string, t: () => U) { - return new TypeRef({ - $unwrap: t, - $ref: name, - }); - } + static create(name: string, t: () => U) { + return new TypeRef({ + $unwrap: t, + $ref: name, + }); + } - override get isOptional(): boolean { - return false; - } + override get isOptional(): boolean { + return false; + } } - - diff --git a/nodepkg/typedef/src/core/TypeString.ts b/nodepkg/typedef/src/core/TypeString.ts index 1bfcb264..73266414 100644 --- a/nodepkg/typedef/src/core/TypeString.ts +++ b/nodepkg/typedef/src/core/TypeString.ts @@ -1,15 +1,15 @@ -import {type Context, Type} from "./Type.ts"; +import { type Context, Type } from "./Type.ts"; export class TypeString extends Type { - static create() { - return new TypeString({type: "string"}); - } + static create() { + return new TypeString({ type: "string" }); + } - override get type() { - return this.schema.type; - } + override get type() { + return this.schema.type; + } - override validator(value: unknown, _: Context) { - return typeof value === "string"; - } -} \ No newline at end of file + override validator(value: unknown, _: Context) { + return typeof value === "string"; + } +} diff --git a/nodepkg/typedef/src/core/TypeTuple.ts b/nodepkg/typedef/src/core/TypeTuple.ts index d8ae945a..b7a193ec 100644 --- a/nodepkg/typedef/src/core/TypeTuple.ts +++ b/nodepkg/typedef/src/core/TypeTuple.ts @@ -1,41 +1,47 @@ -import {type AnyType, type InferTuple, type Context, Type, TypeNever} from "./Type.ts"; +import { + type AnyType, + type InferTuple, + type Context, + Type, + TypeNever, +} from "./Type.ts"; export class TypeTuple extends Type< - T, - { - type: "array"; - items: S; - } + T, + { + type: "array"; + items: S; + } > { - static create(values: [...Values]) { - return new TypeTuple, Values>({ - type: "array", - items: values, - }); - } + static create(values: [...Values]) { + return new TypeTuple, Values>({ + type: "array", + items: values, + }); + } - override get type() { - return "tuple"; - } + override get type() { + return "tuple"; + } - override* entries( - value: unknown, - _context: Context, - ): Iterable<[string | number, unknown, AnyType | Type]> { - if (Array.isArray(value)) { - const length = Math.max(this.schema.items.length, value.length); + override *entries( + value: unknown, + _context: Context, + ): Iterable<[string | number, unknown, AnyType | Type]> { + if (Array.isArray(value)) { + const length = Math.max(this.schema.items.length, value.length); - for (let i = 0; i < length; i++) { - yield [i, value[i], this.schema.items[i] ?? TypeNever.create()]; - } - } + for (let i = 0; i < length; i++) { + yield [i, value[i], this.schema.items[i] ?? TypeNever.create()]; + } } + } - override validator(value: unknown) { - return Array.isArray(value); - } + override validator(value: unknown) { + return Array.isArray(value); + } - override coercer(value: unknown) { - return Array.isArray(value) ? value.slice() : value; - } -} \ No newline at end of file + override coercer(value: unknown) { + return Array.isArray(value) ? value.slice() : value; + } +} diff --git a/nodepkg/typedef/src/core/TypeUnion.ts b/nodepkg/typedef/src/core/TypeUnion.ts index a9a36c9f..1cb3f333 100644 --- a/nodepkg/typedef/src/core/TypeUnion.ts +++ b/nodepkg/typedef/src/core/TypeUnion.ts @@ -1,201 +1,201 @@ -import {isUndefined, mapValues, omit} from "@innoai-tech/lodash"; +import { isUndefined, mapValues, omit } from "@innoai-tech/lodash"; import { - type AnyType, - type Context, - type Infer, - type InferTuple, - type Simplify, - run, - EmptyContext, - Type, - TypeWrapper, - validate + type AnyType, + type Context, + type Infer, + type InferTuple, + type Simplify, + run, + EmptyContext, + Type, + TypeWrapper, + validate, } from "./Type.ts"; -import {TypeEnum} from "./TypeEnum.ts"; -import {TypeObject} from "./TypeObject.ts"; +import { TypeEnum } from "./TypeEnum.ts"; +import { TypeObject } from "./TypeObject.ts"; type DiscriminatedUnionType< - D extends string, - Mapping extends Record, + D extends string, + Mapping extends Record, > = ValueOf<{ - [K in keyof Mapping]: { [k in D]: K } & Infer; + [K in keyof Mapping]: { [k in D]: K } & Infer; }>; type ValueOf = T[keyof T]; export class TypeUnion extends Type< - T, - { - oneOf: S; - discriminator?: { - propertyName: string; - }; - } + T, + { + oneOf: S; + discriminator?: { + propertyName: string; + }; + } > { - static create(...types: Types) { - return new TypeUnion[number], Types>({ - oneOf: types, + static create(...types: Types) { + return new TypeUnion[number], Types>({ + oneOf: types, + }); + } + + static discriminatorMapping< + D extends string, + Mapping extends Record, + >(discriminatorPropertyName: D, mapping: Mapping) { + const normalizedMapping = mapValues(mapping, (def, discriminatorValue) => { + const schema: Record = { + [discriminatorPropertyName]: TypeEnum.literal(discriminatorValue), + }; + + for (const [prop, _, t] of def.entries({}, EmptyContext)) { + schema[String(prop)] = t; + } + + return TypeObject.create(schema); + }); + + return new TypeUnion< + Simplify>, + AnyType[] + >({ + oneOf: Object.values(normalizedMapping) as any, + discriminator: { + propertyName: discriminatorPropertyName, + }, + }); + } + + _discriminatorPropName?: AnyType; + + discriminatorPropType(ctx: Context) { + if (isUndefined(this._discriminatorPropName)) { + this._discriminatorPropName = (() => { + const discriminatorPropName = + this.schema.discriminator?.propertyName ?? ""; + + const values = this.schema.oneOf.reduce((ret, s) => { + return [ + // biome-ignore lint/performance/noAccumulatingSpread: + ...ret, + ...(s.unwrap.schema.properties[discriminatorPropName] as AnyType) + .unwrap.schema.enum, + ]; + }, [] as any[]); + + return TypeWrapper.of(TypeEnum.create(values), { + $meta: ctx.node?.meta ?? {}, }); + })(); } - static discriminatorMapping< - D extends string, - Mapping extends Record, - >(discriminatorPropertyName: D, mapping: Mapping) { - const normalizedMapping = mapValues(mapping, (def, discriminatorValue) => { - const schema: Record = { - [discriminatorPropertyName]: TypeEnum.literal(discriminatorValue), - }; + return this._discriminatorPropName; + } - for (const [prop, _, t] of def.entries({}, EmptyContext)) { - schema[String(prop)] = t; - } + _discriminatorMapping: { [K: string | number]: { [K: string]: AnyType } } = + {}; - return TypeObject.create(schema); - }); - - return new TypeUnion< - Simplify>, - AnyType[] - >({ - oneOf: Object.values(normalizedMapping) as any, - discriminator: { - propertyName: discriminatorPropertyName, - }, - }); - } - - _discriminatorPropName?: AnyType; - - discriminatorPropType(ctx: Context) { - if (isUndefined(this._discriminatorPropName)) { - this._discriminatorPropName = (() => { - const discriminatorPropName = - this.schema.discriminator?.propertyName ?? ""; - - const values = this.schema.oneOf.reduce((ret, s) => { - return [ - // biome-ignore lint/performance/noAccumulatingSpread: - ...ret, - ...(s.unwrap.schema.properties[discriminatorPropName] as AnyType) - .unwrap.schema.enum, - ]; - }, [] as any[]); - - return TypeWrapper.of(TypeEnum.create(values), { - $meta: ctx.node?.meta ?? {}, - }); - })(); - } + discriminatorMapping(discriminatorPropValue: any, ctx: Context) { + const discriminatorPropName = this.schema.discriminator?.propertyName ?? ""; - return this._discriminatorPropName; + if (this._discriminatorMapping[discriminatorPropValue]) { + return this._discriminatorMapping[discriminatorPropValue]; } - _discriminatorMapping: { [K: string | number]: { [K: string]: AnyType } } = - {}; + const enumValues = this.discriminatorPropType(ctx).unwrap.schema.enum; - discriminatorMapping(discriminatorPropValue: any, ctx: Context) { - const discriminatorPropName = this.schema.discriminator?.propertyName ?? ""; - - if (this._discriminatorMapping[discriminatorPropValue]) { - return this._discriminatorMapping[discriminatorPropValue]; + if (enumValues.includes(discriminatorPropValue)) { + const matched = this.schema.oneOf.find((s) => { + const t = s.unwrap.schema.properties[discriminatorPropName] as AnyType; + if (t.unwrap.schema.enum) { + const [err, _] = t.validate(discriminatorPropValue); + return !err; } - - const enumValues = this.discriminatorPropType(ctx).unwrap.schema.enum; - - if (enumValues.includes(discriminatorPropValue)) { - const matched = this.schema.oneOf.find((s) => { - const t = s.unwrap.schema.properties[discriminatorPropName] as AnyType; - if (t.unwrap.schema.enum) { - const [err, _] = t.validate(discriminatorPropValue); - return !err; - } - return false; - }); - - if (matched) { - if ( - isUndefined(this._discriminatorMapping[`${discriminatorPropValue}`]) - ) { - this._discriminatorMapping[`${discriminatorPropValue}`] = omit( - matched.unwrap.schema.properties, - [discriminatorPropName], - ); - } - return this._discriminatorMapping[`${discriminatorPropValue}`]; - } + return false; + }); + + if (matched) { + if ( + isUndefined(this._discriminatorMapping[`${discriminatorPropValue}`]) + ) { + this._discriminatorMapping[`${discriminatorPropValue}`] = omit( + matched.unwrap.schema.properties, + [discriminatorPropName], + ); } - - return {}; + return this._discriminatorMapping[`${discriminatorPropValue}`]; + } } - override* entries( - value: unknown, - context: Context, - ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { - if (this.schema.discriminator) { - const discriminatorPropName = this.schema.discriminator.propertyName; + return {}; + } - const discriminatorPropValue = (value as any)?.[discriminatorPropName]; + override *entries( + value: unknown, + context: Context, + ): Iterable<[string | number | symbol, unknown, AnyType | Type]> { + if (this.schema.discriminator) { + const discriminatorPropName = this.schema.discriminator.propertyName; - const base = TypeObject.create({ - [discriminatorPropName]: this.discriminatorPropType(context), - ...this.discriminatorMapping(discriminatorPropValue, context), - }); + const discriminatorPropValue = (value as any)?.[discriminatorPropName]; - yield* base.entries(value, context); - } - } + const base = TypeObject.create({ + [discriminatorPropName]: this.discriminatorPropType(context), + ...this.discriminatorMapping(discriminatorPropValue, context), + }); - override get type() { - return "union"; + yield* base.entries(value, context); } - - override coercer(value: unknown) { - for (const t of this.schema.oneOf) { - const [error, coerced] = validate(value, t, {coerce: true}); - if (!error) { - return coerced; - } - } - return value; + } + + override get type() { + return "union"; + } + + override coercer(value: unknown) { + for (const t of this.schema.oneOf) { + const [error, coerced] = validate(value, t, { coerce: true }); + if (!error) { + return coerced; + } } + return value; + } - override validator(value: unknown, context: Context) { - if (this.schema.discriminator) { - const discriminatorPropName = this.schema.discriminator.propertyName; - const discriminatorPropValue = (value as any)?.[discriminatorPropName]; + override validator(value: unknown, context: Context) { + if (this.schema.discriminator) { + const discriminatorPropName = this.schema.discriminator.propertyName; + const discriminatorPropValue = (value as any)?.[discriminatorPropName]; - const base = TypeObject.create({ - [discriminatorPropName]: this.discriminatorPropType(context), - ...this.discriminatorMapping(discriminatorPropValue, context), - }); + const base = TypeObject.create({ + [discriminatorPropName]: this.discriminatorPropType(context), + ...this.discriminatorMapping(discriminatorPropValue, context), + }); - return base.validator(value, context); - } + return base.validator(value, context); + } - const failures = []; + const failures = []; - for (const t of this.schema.oneOf) { - const [...tuples] = run(value, t, context); - const [first] = tuples; + for (const t of this.schema.oneOf) { + const [...tuples] = run(value, t, context); + const [first] = tuples; - if (first && !first[0]) { - return []; - } + if (first && !first[0]) { + return []; + } - for (const [failure] of tuples) { - if (failure) { - failures.push(failure); - } - } + for (const [failure] of tuples) { + if (failure) { + failures.push(failure); } - - return [ - `Expected the value to satisfy a union of \`${this.schema.oneOf - .map((t) => t.type) - .join(" | ")}\`, but received: ${value}`, - ...failures, - ]; + } } -} \ No newline at end of file + + return [ + `Expected the value to satisfy a union of \`${this.schema.oneOf + .map((t) => t.type) + .join(" | ")}\`, but received: ${value}`, + ...failures, + ]; + } +} diff --git a/nodepkg/typedef/src/core/__tests__/index.spec.ts b/nodepkg/typedef/src/core/__tests__/index.spec.ts index ed9ecd4b..d1fad72f 100644 --- a/nodepkg/typedef/src/core/__tests__/index.spec.ts +++ b/nodepkg/typedef/src/core/__tests__/index.spec.ts @@ -2,209 +2,209 @@ import { describe, expect, test } from "bun:test"; import { type Infer, t } from "../index"; enum InputType { - text = "text", - number = "number", + text = "text", + number = "number", } describe("Type", () => { - describe("DefaultedType", () => { - test("simple", () => { - const s = t.string().default("default"); - expect(s.create(undefined)).toBe("default"); - }); - - test("object", () => { - const s = t.object({ - str: t.string().default("default"), - }); - expect(s.create({})).toEqual({ - str: "default", - }); - }); - }); - - describe("OptionalType", () => { - test("optional", () => { - const s = t.string().optional(); - expect(s.create(undefined)).toBeUndefined(); - }); - }); - - describe("TypeObject", () => { - const s = t.object({ - a: t.string(), - o: t.number().optional(), - }); - - test("schema should collect required", () => { - expect(s.schema.required).toEqual(["a"]); - }); - - test("should validate incorrect type", () => { - const [err, _] = s.validate({ - a: 1, - }); - expect(err).not.toBeUndefined(); - }); - - test("should validate prop not allowed", () => { - const [err, _] = s.validate({ - a: "1", - x: 1, - }); - expect(err).not.toBeUndefined(); - }); - }); - - describe("Complex", () => { - const Bool = t.boolean(); - - const s = t.object({ - bool: t.ref("Bool", () => Bool), - const: t.literal("1" as const), - enum: t.enums(["a", "b"]), - nativeEnum: t.nativeEnum(InputType), - int: t.integer(), - null: t.nil(), - num: t.number(), - str: t.string(), - arr: t.array(t.string()), - tuple: t.tuple([t.string(), t.number()]), - record: t.record(t.string(), t.any()), - }); - - test("should validate", () => { - const [err, _] = s.validate({}); - expect(err).not.toBeUndefined(); - - for (const f of err?.failures() ?? []) { - expect(f.type).not.toBeUndefined(); - } - }); - - const expected: Infer = { - const: "1", - bool: false, - enum: "a", - nativeEnum: InputType.text, - int: 0, - null: null, - num: 0, - str: "", - arr: ["1"], - tuple: ["x", 1], - record: { - a: 1, - }, - }; - - test("should create", () => { - expect(s.create(expected)).toEqual(expected); - }); - - test("should mask", () => { - expect( - s.mask({ - ...expected, - fieldToDrop: "-", - }), - ).toEqual(expected); - }); - }); - - describe("TypeUnion", () => { - const stringOrNumber = t.union(t.string(), t.number()); - - test("should validate pass", () => { - const [err, _] = stringOrNumber.validate(""); - expect(err).toBeUndefined(); - }); - - test("should validate not pass", () => { - const [err, _] = stringOrNumber.validate({}); - expect(err).not.toBeUndefined(); - }); - - describe("discriminatorMapping", () => { - const taggedUnion = t.discriminatorMapping("type", { - text: t.object(), - select: t.object({ - options: t.array( - t.object({ - label: t.string(), - value: t.any(), - }), - ), - }), - }); - - const v1: Infer = { - type: "text", - }; - - const v2: Infer = { - type: "select", - options: [ - { - label: "1", - value: 1, - }, - ], - }; - - test("should validate pass branch 1", () => { - const [err, _] = taggedUnion.validate(v1); - expect(err).toBeUndefined(); - }); - - test("should validate pass branch 2", () => { - const [err, _] = taggedUnion.validate(v2); - expect(err).toBeUndefined(); - }); - - test("should validate not pass", () => { - const [err, _] = taggedUnion.validate({ - type: "text", - options: [], - }); - - expect(err).not.toBeUndefined(); - }); - - describe("with intersection", () => { - const combined = t.intersection( - t.object({ - x: t.string(), - }), - taggedUnion, - ); - - const v1: Infer = { - x: "x", - type: "text", - }; - - const v2: Infer = { - x: "x", - type: "select", - options: [ - { - label: "1", - value: 1, - }, - ], - }; - - test("should validate pass branch 1", () => { - const [err, _] = combined.validate(v1); - expect(err).toBeUndefined(); - }); - - test("should validate pass branch 2", () => { - const [err, _] = combined.validate(v2); - expect(err).toBeUndefined(); - }); - }); - }); - }); + describe("DefaultedType", () => { + test("simple", () => { + const s = t.string().default("default"); + expect(s.create(undefined)).toBe("default"); + }); + + test("object", () => { + const s = t.object({ + str: t.string().default("default"), + }); + expect(s.create({})).toEqual({ + str: "default", + }); + }); + }); + + describe("OptionalType", () => { + test("optional", () => { + const s = t.string().optional(); + expect(s.create(undefined)).toBeUndefined(); + }); + }); + + describe("TypeObject", () => { + const s = t.object({ + a: t.string(), + o: t.number().optional(), + }); + + test("schema should collect required", () => { + expect(s.schema.required).toEqual(["a"]); + }); + + test("should validate incorrect type", () => { + const [err, _] = s.validate({ + a: 1, + }); + expect(err).not.toBeUndefined(); + }); + + test("should validate prop not allowed", () => { + const [err, _] = s.validate({ + a: "1", + x: 1, + }); + expect(err).not.toBeUndefined(); + }); + }); + + describe("Complex", () => { + const Bool = t.boolean(); + + const s = t.object({ + bool: t.ref("Bool", () => Bool), + const: t.literal("1" as const), + enum: t.enums(["a", "b"]), + nativeEnum: t.nativeEnum(InputType), + int: t.integer(), + null: t.nil(), + num: t.number(), + str: t.string(), + arr: t.array(t.string()), + tuple: t.tuple([t.string(), t.number()]), + record: t.record(t.string(), t.any()), + }); + + test("should validate", () => { + const [err, _] = s.validate({}); + expect(err).not.toBeUndefined(); + + for (const f of err?.failures() ?? []) { + expect(f.type).not.toBeUndefined(); + } + }); + + const expected: Infer = { + const: "1", + bool: false, + enum: "a", + nativeEnum: InputType.text, + int: 0, + null: null, + num: 0, + str: "", + arr: ["1"], + tuple: ["x", 1], + record: { + a: 1, + }, + }; + + test("should create", () => { + expect(s.create(expected)).toEqual(expected); + }); + + test("should mask", () => { + expect( + s.mask({ + ...expected, + fieldToDrop: "-", + }), + ).toEqual(expected); + }); + }); + + describe("TypeUnion", () => { + const stringOrNumber = t.union(t.string(), t.number()); + + test("should validate pass", () => { + const [err, _] = stringOrNumber.validate(""); + expect(err).toBeUndefined(); + }); + + test("should validate not pass", () => { + const [err, _] = stringOrNumber.validate({}); + expect(err).not.toBeUndefined(); + }); + + describe("discriminatorMapping", () => { + const taggedUnion = t.discriminatorMapping("type", { + text: t.object(), + select: t.object({ + options: t.array( + t.object({ + label: t.string(), + value: t.any(), + }), + ), + }), + }); + + const v1: Infer = { + type: "text", + }; + + const v2: Infer = { + type: "select", + options: [ + { + label: "1", + value: 1, + }, + ], + }; + + test("should validate pass branch 1", () => { + const [err, _] = taggedUnion.validate(v1); + expect(err).toBeUndefined(); + }); + + test("should validate pass branch 2", () => { + const [err, _] = taggedUnion.validate(v2); + expect(err).toBeUndefined(); + }); + + test("should validate not pass", () => { + const [err, _] = taggedUnion.validate({ + type: "text", + options: [], + }); + + expect(err).not.toBeUndefined(); + }); + + describe("with intersection", () => { + const combined = t.intersection( + t.object({ + x: t.string(), + }), + taggedUnion, + ); + + const v1: Infer = { + x: "x", + type: "text", + }; + + const v2: Infer = { + x: "x", + type: "select", + options: [ + { + label: "1", + value: 1, + }, + ], + }; + + test("should validate pass branch 1", () => { + const [err, _] = combined.validate(v1); + expect(err).toBeUndefined(); + }); + + test("should validate pass branch 2", () => { + const [err, _] = combined.validate(v2); + expect(err).toBeUndefined(); + }); + }); + }); + }); }); diff --git a/nodepkg/typedef/src/core/__tests__/validate.spec.ts b/nodepkg/typedef/src/core/__tests__/validate.spec.ts index 685561c0..d0944895 100644 --- a/nodepkg/typedef/src/core/__tests__/validate.spec.ts +++ b/nodepkg/typedef/src/core/__tests__/validate.spec.ts @@ -1,181 +1,176 @@ -import {describe, expect, test} from "bun:test"; -import {t, Type, TypeWrapper} from "../index"; +import { describe, expect, test } from "bun:test"; +import { t, Type, TypeWrapper } from "../index"; describe("Meta", () => { - describe("iter desc", () => { - test("when as array", () => { - const fields: { [k: string]: string } = {}; - - for (const [k, _, s] of t.array(schema).entries([undefined, undefined])) { - if (s.type === "never") { - continue; - } - - fields[k] = ""; - } - - expect(fields).toEqual({ - "0": "", - "1": "", - }); - }); - - test("when value empty", () => { - const fields: { [k: string]: string } = {}; - - for (const [k, _, s] of schema.entries({})) { - if (s.type === "never") { - continue; - } - - fields[String(k)] = [ - s.getMeta("description") ?? "", - ...(s.getSchema("enum") ?? []), - ].join("|"); - } - - expect(fields).toEqual({ - name: "名称", - desc: "描述", - envType: "环境类型|DEV|ONLINE", - netType: "网络类型|AIRGAP|DIRECT", - }); - }); - - test("when tagged value matched", () => { - const fields: { [k: string]: string } = {}; - - for (const [k, _, s] of schema.entries({ - netType: "DIRECT", - })) { - if (s.type === "never") { - continue; - } - - fields[String(k)] = [ - s.getMeta("description") ?? "", - ...(s.getSchema("enum") ?? []), - ].join("|"); - } - - expect(fields).toEqual({ - name: "名称", - desc: "描述", - envType: "环境类型|DEV|ONLINE", - netType: "网络类型|AIRGAP|DIRECT", - endpoint: "访问地址", - }); - }); + describe("iter desc", () => { + test("when as array", () => { + const fields: { [k: string]: string } = {}; + + for (const [k, _, s] of t.array(schema).entries([undefined, undefined])) { + if (s.type === "never") { + continue; + } + + fields[k] = ""; + } + + expect(fields).toEqual({ + "0": "", + "1": "", + }); }); + + test("when value empty", () => { + const fields: { [k: string]: string } = {}; + + for (const [k, _, s] of schema.entries({})) { + if (s.type === "never") { + continue; + } + + fields[String(k)] = [ + s.getMeta("description") ?? "", + ...(s.getSchema("enum") ?? []), + ].join("|"); + } + + expect(fields).toEqual({ + name: "名称", + desc: "描述", + envType: "环境类型|DEV|ONLINE", + netType: "网络类型|AIRGAP|DIRECT", + }); + }); + + test("when tagged value matched", () => { + const fields: { [k: string]: string } = {}; + + for (const [k, _, s] of schema.entries({ + netType: "DIRECT", + })) { + if (s.type === "never") { + continue; + } + + fields[String(k)] = [ + s.getMeta("description") ?? "", + ...(s.getSchema("enum") ?? []), + ].join("|"); + } + + expect(fields).toEqual({ + name: "名称", + desc: "描述", + envType: "环境类型|DEV|ONLINE", + netType: "网络类型|AIRGAP|DIRECT", + endpoint: "访问地址", + }); + }); + }); }); describe("Validate", () => { - describe("simple object", () => { - test("should", () => { - const [err] = objectSchema.validate({ - name: "1", - }); - const errors = err?.failures(); - - expect(errors?.[0]).toHaveProperty("key", "name"); - expect(errors?.[0]).toHaveProperty( - "message", - "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", - ); - }); + describe("simple object", () => { + test("should", () => { + const [err] = objectSchema.validate({ + name: "1", + }); + const errors = err?.failures(); + + expect(errors?.[0]).toHaveProperty("key", "name"); + expect(errors?.[0]).toHaveProperty( + "message", + "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", + ); + }); + }); + + describe("discriminatorMapping", () => { + test("validate discriminator", () => { + const [err] = taggedUnion.validate({}); + const errors = err?.failures(); + + expect(errors?.[0]).toHaveProperty("key", "netType"); + expect(errors?.[0]).toHaveProperty( + "message", + "Expected a value of type `enums`, but received: `undefined`", + ); }); - describe("discriminatorMapping", () => { - test("validate discriminator", () => { - const [err] = taggedUnion.validate({}); - const errors = err?.failures(); - - expect(errors?.[0]).toHaveProperty("key", "netType"); - expect(errors?.[0]).toHaveProperty( - "message", - "Expected a value of type `enums`, but received: `undefined`", - ); - }); - - test("validate branch left", () => { - const [err] = taggedUnion.validate({ - netType: NetType.AIRGAP, - }); - const errors = err?.failures(); - expect(errors).toBeUndefined(); - }); - - test("validate branch right", () => { - const [err] = taggedUnion.validate({ - netType: NetType.DIRECT, - }); - const errors = err?.failures(); - - expect(errors?.[0]).toHaveProperty("key", "endpoint"); - expect(errors?.[0]).toHaveProperty( - "message", - "Expected a value of type `string`, but received: `undefined`", - ); - }); + test("validate branch left", () => { + const [err] = taggedUnion.validate({ + netType: NetType.AIRGAP, + }); + const errors = err?.failures(); + expect(errors).toBeUndefined(); }); + + test("validate branch right", () => { + const [err] = taggedUnion.validate({ + netType: NetType.DIRECT, + }); + const errors = err?.failures(); + + expect(errors?.[0]).toHaveProperty("key", "endpoint"); + expect(errors?.[0]).toHaveProperty( + "message", + "Expected a value of type `string`, but received: `undefined`", + ); + }); + }); }); enum EnvType { - DEV = "DEV", - ONLINE = "ONLINE", + DEV = "DEV", + ONLINE = "ONLINE", } enum NetType { - AIRGAP = "AIRGAP", - DIRECT = "DIRECT", + AIRGAP = "AIRGAP", + DIRECT = "DIRECT", } const objectSchema = t.object({ - name: t - .string() - .use( - desc("名称"), - t.pattern( - /[a-z][a-z0-9-]+/, - "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", - ), - ), - desc: t.string().use( - desc("描述"), - readOnly(), + name: t + .string() + .use( + desc("名称"), + t.pattern( + /[a-z][a-z0-9-]+/, + "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", + ), ), - envType: t.nativeEnum(EnvType).use(desc("环境类型"), t.optional()), + desc: t.string().use(desc("描述"), readOnly()), + envType: t.nativeEnum(EnvType).use(desc("环境类型"), t.optional()), }); -const taggedUnion = t.discriminatorMapping("netType", { +const taggedUnion = t + .discriminatorMapping("netType", { [NetType.AIRGAP]: t.object({}), [NetType.DIRECT]: t.object({ - endpoint: t.string().use( - desc("访问地址"), - ), + endpoint: t.string().use(desc("访问地址")), }), -}).use( - desc("网络类型"), -); + }) + .use(desc("网络类型")); export const schema = t.intersection(objectSchema, taggedUnion); function desc(description: string) { - return (t: Type) => { - return TypeWrapper.of(t, { - $meta: { - description: description, - } - }) - } + return (t: Type) => { + return TypeWrapper.of(t, { + $meta: { + description: description, + }, + }); + }; } function readOnly(readOnly?: boolean) { - return (t: Type) => { - return TypeWrapper.of(t, { - $meta: { - readOnly: readOnly, - } - }) - } -} \ No newline at end of file + return (t: Type) => { + return TypeWrapper.of(t, { + $meta: { + readOnly: readOnly, + }, + }); + }; +} diff --git a/nodepkg/typedef/src/core/index.ts b/nodepkg/typedef/src/core/index.ts index 582882b0..fd7f05b2 100644 --- a/nodepkg/typedef/src/core/index.ts +++ b/nodepkg/typedef/src/core/index.ts @@ -1,6 +1,5 @@ export * from "./Meta"; - export * from "./Type.ts"; export * from "./TypeRef.ts"; export * from "./TypeAny.ts"; @@ -19,4 +18,3 @@ export * from "./TypeIntersection.ts"; export * from "./TypeUnion.ts"; export * as t from "./t"; - diff --git a/nodepkg/typedef/src/core/t.ts b/nodepkg/typedef/src/core/t.ts index 2605ae1f..672d52f6 100644 --- a/nodepkg/typedef/src/core/t.ts +++ b/nodepkg/typedef/src/core/t.ts @@ -1,19 +1,25 @@ -import {TypeRef} from "./TypeRef.ts"; -import {TypeAny} from "./TypeAny.ts"; -import {DefaultedType, OptionalType, Type, TypeNever, TypeWrapper} from "./Type.ts"; -import {TypeNull} from "./TypeNull.ts"; -import {TypeString} from "./TypeString.ts"; -import {TypeNumber} from "./TypeNumber.ts"; -import {TypeInteger} from "./TypeInteger.ts"; -import {TypeBoolean} from "./TypeBoolean.ts"; -import {TypeBinary} from "./TypeBinary.ts"; -import {TypeEnum} from "./TypeEnum.ts"; -import {TypeObject} from "./TypeObject.ts"; -import {TypeRecord} from "./TypeRecord.ts"; -import {TypeArray} from "./TypeArray.ts"; -import {TypeTuple} from "./TypeTuple.ts"; -import {TypeIntersection} from "./TypeIntersection.ts"; -import {TypeUnion} from "./TypeUnion.ts"; +import { TypeRef } from "./TypeRef.ts"; +import { TypeAny } from "./TypeAny.ts"; +import { + DefaultedType, + OptionalType, + Type, + TypeNever, + TypeWrapper, +} from "./Type.ts"; +import { TypeNull } from "./TypeNull.ts"; +import { TypeString } from "./TypeString.ts"; +import { TypeNumber } from "./TypeNumber.ts"; +import { TypeInteger } from "./TypeInteger.ts"; +import { TypeBoolean } from "./TypeBoolean.ts"; +import { TypeBinary } from "./TypeBinary.ts"; +import { TypeEnum } from "./TypeEnum.ts"; +import { TypeObject } from "./TypeObject.ts"; +import { TypeRecord } from "./TypeRecord.ts"; +import { TypeArray } from "./TypeArray.ts"; +import { TypeTuple } from "./TypeTuple.ts"; +import { TypeIntersection } from "./TypeIntersection.ts"; +import { TypeUnion } from "./TypeUnion.ts"; export const ref = TypeRef.create; export const any = TypeAny.create; @@ -23,87 +29,125 @@ export const nil = TypeNull.create; export const string = TypeString.create; export function pattern(pattern: RegExp, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (pattern.test(value)) { - return true - } - return msg ?? `Expected a ${type.type} matching \`/${pattern.source}/\` but received "${value}"` - }, { - pattern, - }) - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (pattern.test(value)) { + return true; + } + return ( + msg ?? + `Expected a ${type.type} matching \`/${pattern.source}/\` but received "${value}"` + ); + }, + { + pattern, + }, + ); + }; } export const number = TypeNumber.create; export const integer = TypeInteger.create; export function minimum(min: M, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (value >= min) { - return true - } - return msg ?? `Expected value great than or equal ${min}, but received "${value}"` - }, { - minimum: min, - }) - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (value >= min) { + return true; + } + return ( + msg ?? + `Expected value great than or equal ${min}, but received "${value}"` + ); + }, + { + minimum: min, + }, + ); + }; } export function exclusiveMinimum(min: M, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (value > min) { - return true - } - return (msg ?? `Expected value great than ${min}, but received "${value}"`) - }, { - exclusiveMinimum: min, - }) - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (value > min) { + return true; + } + return ( + msg ?? `Expected value great than ${min}, but received "${value}"` + ); + }, + { + exclusiveMinimum: min, + }, + ); + }; } - export function maximum(max: number, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (value <= max) { - return true - } - return (msg ?? `Expected value less than or equal ${max}, but received "${value}"`) - }, { - maximum: max, - }) as any - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (value <= max) { + return true; + } + return ( + msg ?? + `Expected value less than or equal ${max}, but received "${value}"` + ); + }, + { + maximum: max, + }, + ) as any; + }; } - export function exclusiveMaximum(max: number, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (value < max) { - return true - } - return msg ?? `Expected value less than or equal ${max}, but received "${value}"` - }, { - exclusiveMaximum: max, - }) as any; - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (value < max) { + return true; + } + return ( + msg ?? + `Expected value less than or equal ${max}, but received "${value}"` + ); + }, + { + exclusiveMaximum: max, + }, + ) as any; + }; } - export function multipleOf(multipleOf: number, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (value % multipleOf == 0) { - return true - } - return msg ?? `Expected value multiple of ${multipleOf}, but received "${value}"` - }, { - multipleOf: multipleOf, - }); - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (value % multipleOf == 0) { + return true; + } + return ( + msg ?? + `Expected value multiple of ${multipleOf}, but received "${value}"` + ); + }, + { + multipleOf: multipleOf, + }, + ); + }; } export const boolean = TypeBoolean.create; @@ -120,29 +164,43 @@ export const tuple = TypeTuple.create; export const array = TypeArray.create; export function minItems(minItems: number, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (Array.isArray(value) && value.length >= minItems) { - return true - } - return msg ?? `Expected array value at least ${minItems}, but received "${value?.length}"` - }, { - minItems: minItems, - }) - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (Array.isArray(value) && value.length >= minItems) { + return true; + } + return ( + msg ?? + `Expected array value at least ${minItems}, but received "${value?.length}"` + ); + }, + { + minItems: minItems, + }, + ); + }; } export function maxItems(maxItems: number, msg?: string) { - return (type: Type) => { - return TypeWrapper.refine(type, (value) => { - if (Array.isArray(value) && value.length <= maxItems) { - return true - } - return msg ?? `Expected array value ${maxItems}, but received "${value?.length}"` - }, { - maxItems: maxItems, - }); - }; + return (type: Type) => { + return TypeWrapper.refine( + type, + (value) => { + if (Array.isArray(value) && value.length <= maxItems) { + return true; + } + return ( + msg ?? + `Expected array value ${maxItems}, but received "${value?.length}"` + ); + }, + { + maxItems: maxItems, + }, + ); + }; } export const intersection = TypeIntersection.create; @@ -153,19 +211,19 @@ export const custom = Type.define; export const refine = TypeWrapper.refine; export function defaults(v: T) { - return >(type: U): U => { - return DefaultedType.create(type, v) as unknown as U; - }; + return >(type: U): U => { + return DefaultedType.create(type, v) as unknown as U; + }; } export function optional() { - return (type: Type): Type => { - return OptionalType.create(type) as unknown as any; - }; + return (type: Type): Type => { + return OptionalType.create(type) as unknown as any; + }; } export function annotate>(meta: M) { - return (type: Type): Type => { - return TypeWrapper.of(type, {$meta: meta}) as unknown as Type; - }; + return (type: Type): Type => { + return TypeWrapper.of(type, { $meta: meta }) as unknown as Type; + }; } diff --git a/nodepkg/typedef/src/encoding/JSONSchemaDecoder.ts b/nodepkg/typedef/src/encoding/JSONSchemaDecoder.ts index a0f5920f..830e7207 100644 --- a/nodepkg/typedef/src/encoding/JSONSchemaDecoder.ts +++ b/nodepkg/typedef/src/encoding/JSONSchemaDecoder.ts @@ -1,354 +1,353 @@ import { - assign, - filter, - has, - isArray, - isBoolean, - isEmpty, - last, - map, - mapValues, - some, + assign, + filter, + has, + isArray, + isBoolean, + isEmpty, + last, + map, + mapValues, + some, } from "@innoai-tech/lodash"; -import {type AnyType, t} from "../core"; -import {literal} from "../core/t.ts"; -import type {JSONSchema} from "./JSONSchemaEncoder"; +import { type AnyType, t } from "../core"; +import { literal } from "../core/t.ts"; +import type { JSONSchema } from "./JSONSchemaEncoder"; export const refName = (ref: string) => { - return last(ref.split("/")) ?? ""; + return last(ref.split("/")) ?? ""; }; export class JSONSchemaDecoder { - static decode( - type: JSONSchema | false, - resolveRef: (ref: string) => [JSONSchema, string], - ): AnyType { - if (type === false) { - return t.never() as any; - } - return new JSONSchemaDecoder(resolveRef).decode(type); + static decode( + type: JSONSchema | false, + resolveRef: (ref: string) => [JSONSchema, string], + ): AnyType { + if (type === false) { + return t.never() as any; } + return new JSONSchemaDecoder(resolveRef).decode(type); + } - def = new Map(); + def = new Map(); - constructor(private resolveRef: (ref: string) => [JSONSchema, string]) { - } + constructor(private resolveRef: (ref: string) => [JSONSchema, string]) {} - decode(jsonSchema: JSONSchema): AnyType { - const tt = this._decode(jsonSchema); + decode(jsonSchema: JSONSchema): AnyType { + const tt = this._decode(jsonSchema); - if (jsonSchema?.["description"]) { - return tt.use( - t.annotate({ - // only pick the first line as description - description: jsonSchema["description"].split("\n")[0], - }) - ) - } + if (jsonSchema?.["description"]) { + return tt.use( + t.annotate({ + // only pick the first line as description + description: jsonSchema["description"].split("\n")[0], + }), + ); + } - return tt; + return tt; + } + + private ref = (refName: string): AnyType => { + const t = this.def.get(refName); + if (t) { + return t; } + throw new Error(`undefined type ${refName}`); + }; - private ref = (refName: string): AnyType => { - const t = this.def.get(refName); - if (t) { - return t; - } - throw new Error(`undefined type ${refName}`); - }; + private _decode(s: JSONSchema): AnyType { + const schema = normalizeSchema(s); - private _decode(s: JSONSchema): AnyType { - const schema = normalizeSchema(s); + if (schema["$ref"]) { + const [reffedSchema, refName] = this.resolveRef(schema["$ref"]); - if (schema["$ref"]) { - const [reffedSchema, refName] = this.resolveRef(schema["$ref"]); + if (!this.def.has(refName)) { + this.def.set(refName, t.any()); + this.def.set(refName, this.decode(reffedSchema)); + } - if (!this.def.has(refName)) { - this.def.set(refName, t.any()); - this.def.set(refName, this.decode(reffedSchema)); - } + return t.ref(refName, () => this.ref(refName)); + } - return t.ref(refName, () => this.ref(refName)); - } + if (schema["enum"]) { + const e = t.enums(schema["enum"]); + + if (schema["x-enum-labels"]) { + return e.use( + t.annotate({ + enumLabels: schema["x-enum-labels"], + }), + ); + } - if (schema["enum"]) { - const e = t.enums(schema["enum"]); + return e; + } - if (schema["x-enum-labels"]) { - return e.use( - t.annotate({ - enumLabels: schema["x-enum-labels"], - }) - ); + if (schema["discriminator"]) { + const discriminatorPropertyName = schema["discriminator"][ + "propertyName" + ] as string; + + if (discriminatorPropertyName) { + const mapping: Record = {}; + + if (schema["discriminator"]["mapping"]) { + const discriminatorMapping = schema["discriminator"][ + "mapping" + ] as Record; + + for (const k in discriminatorMapping) { + const tt = this.decode(discriminatorMapping[k]); + const objectSchema: Record = {}; + + for (const [propName, _, p] of tt.entries( + {}, + { path: [], branch: [] }, + )) { + if (p.type === "never") { + continue; + } + + if (propName === discriminatorPropertyName) { + objectSchema[propName] = literal(k); + continue; + } + + objectSchema[String(propName)] = p; } - return e; - } + mapping[k] = isEmpty(objectSchema) + ? t.object() + : t.object(objectSchema); + } + } else { + for (const o of schema["oneOf"]) { + const tt = this.decode(o); + + if (tt) { + const objectSchema: Record = {}; + const values: any[] = []; + + for (const [propName, _, p] of tt.entries( + {}, + { path: [], branch: [] }, + )) { + if (p.type === "never") { + continue; + } - if (schema["discriminator"]) { - const discriminatorPropertyName = schema["discriminator"][ - "propertyName" - ] as string; - - if (discriminatorPropertyName) { - const mapping: Record = {}; - - if (schema["discriminator"]["mapping"]) { - const discriminatorMapping = schema["discriminator"][ - "mapping" - ] as Record; - - for (const k in discriminatorMapping) { - const tt = this.decode(discriminatorMapping[k]); - const objectSchema: Record = {}; - - for (const [propName, _, p] of tt.entries( - {}, - {path: [], branch: []}, - )) { - if (p.type === "never") { - continue; - } - - if (propName === discriminatorPropertyName) { - objectSchema[propName] = literal(k); - continue; - } - - objectSchema[String(propName)] = p; - } - - mapping[k] = isEmpty(objectSchema) - ? t.object() - : t.object(objectSchema); - } - } else { - for (const o of schema["oneOf"]) { - const tt = this.decode(o); - - if (tt) { - const objectSchema: Record = {}; - const values: any[] = []; - - for (const [propName, _, p] of tt.entries( - {}, - {path: [], branch: []}, - )) { - if (p.type === "never") { - continue; - } - - if (propName === discriminatorPropertyName) { - switch (p.type) { - case "literal": - case "enums": { - values.push(...p.getSchema("enum")); - } - } - continue; - } - - objectSchema[String(propName)] = p; - } - - if (values.length) { - for (const value of values) { - mapping[value] = isEmpty(objectSchema) - ? t.object() - : t.object(objectSchema); - } - } - } + if (propName === discriminatorPropertyName) { + switch (p.type) { + case "literal": + case "enums": { + values.push(...p.getSchema("enum")); } + } + continue; } - return t.discriminatorMapping(discriminatorPropertyName, mapping); + objectSchema[String(propName)] = p; + } + + if (values.length) { + for (const value of values) { + mapping[value] = isEmpty(objectSchema) + ? t.object() + : t.object(objectSchema); + } + } } + } } - if (schema["oneOf"]) { - const oneOf = map(schema["oneOf"], (s) => this.decode(s)); + return t.discriminatorMapping(discriminatorPropertyName, mapping); + } + } - if (oneOf.length === 1) { - return oneOf[0] as AnyType; - } + if (schema["oneOf"]) { + const oneOf = map(schema["oneOf"], (s) => this.decode(s)); - return t.union(...oneOf); - } + if (oneOf.length === 1) { + return oneOf[0] as AnyType; + } - if (schema["allOf"]) { - const allOf = map(schema["allOf"], (s) => this.decode(s)); + return t.union(...oneOf); + } - if (allOf.length === 1) { - return allOf[0] as AnyType; - } + if (schema["allOf"]) { + const allOf = map(schema["allOf"], (s) => this.decode(s)); - return t.intersection(...allOf); - } + if (allOf.length === 1) { + return allOf[0] as AnyType; + } - if (isObjectType(schema)) { - if (schema["properties"]) { - const required = schema["required"] ?? []; - - return t.object( - mapValues(schema["properties"], (s, n) => { - const propType = this.decode(s); - if (required.includes(n) && !s["nullable"]) { - return propType; - } - return propType.optional(); - }), - ); - } + return t.intersection(...allOf); + } - const additionalProperties = schema["additionalProperties"] ?? {}; + if (isObjectType(schema)) { + if (schema["properties"]) { + const required = schema["required"] ?? []; - if (additionalProperties) { - return t.record( - this.decode(schema["propertyNames"] ?? {type: "string"}), - this.decode(additionalProperties), - ); + return t.object( + mapValues(schema["properties"], (s, n) => { + const propType = this.decode(s); + if (required.includes(n) && !s["nullable"]) { + return propType; } + return propType.optional(); + }), + ); + } - return t.object(); - } + const additionalProperties = schema["additionalProperties"] ?? {}; - if (isArrayType(schema)) { - if (isArray(schema["items"])) { - return t.tuple( - (schema["items"] as JSONSchema[]).map((s) => this.decode(s)) as any, - ); - } + if (additionalProperties) { + return t.record( + this.decode(schema["propertyNames"] ?? { type: "string" }), + this.decode(additionalProperties), + ); + } - return t.array(this.decode(schema["items"])); - } + return t.object(); + } - if (isStringType(schema)) { - if (schema["format"] === "binary") { - return t.binary(); - } - return t.string(); - } + if (isArrayType(schema)) { + if (isArray(schema["items"])) { + return t.tuple( + (schema["items"] as JSONSchema[]).map((s) => this.decode(s)) as any, + ); + } - if (isNumberType(schema)) { - if (schema.type === "integer") { - return t.integer(); - } - return t.number(); - } + return t.array(this.decode(schema["items"])); + } - if (isBooleanType(schema)) { - return t.boolean(); - } + if (isStringType(schema)) { + if (schema["format"] === "binary") { + return t.binary(); + } + return t.string(); + } - if (isNullType(schema)) { - return t.nil(); - } + if (isNumberType(schema)) { + if (schema.type === "integer") { + return t.integer(); + } + return t.number(); + } - return t.any(); + if (isBooleanType(schema)) { + return t.boolean(); } + + if (isNullType(schema)) { + return t.nil(); + } + + return t.any(); + } } const isObjectType = (schema: any): boolean => schema.type === "object"; const isNullType = (schema: any): boolean => schema.type === "null"; const isArrayType = (schema: any): boolean => schema.type === "array"; const isNumberType = (schema: any): boolean => - schema.type === "number" || schema.type === "integer"; + schema.type === "number" || schema.type === "integer"; const isStringType = (schema: any): boolean => schema.type === "string"; const isBooleanType = (schema: any): boolean => schema.type === "boolean"; const typeRelationKeywords: { [k: string]: string[] } = { - object: [ - "properties", - "additionalProperties", - "unevaluatedProperties", - "patternProperties", - "propertyNames", - "dependentSchemas", - - "maxProperties", - "minProperties", - // "required", - // "dependentRequired", - ], - array: [ - "contains", - "items", - "additionalItems", - "unevaluatedItems", - - "maxItems", - "minItems", - "uniqueItems", - "maxContains", - "minContains", - ], - string: [ - "pattern", - "contentMediaType", - "contentEncoding", - "contentSchema", - "maxLength", - "minLength", - ], - number: [ - "maximum", - "minimum", - "multipleOf", - "exclusiveMaximum", - "exclusiveMinimum", - ], + object: [ + "properties", + "additionalProperties", + "unevaluatedProperties", + "patternProperties", + "propertyNames", + "dependentSchemas", + + "maxProperties", + "minProperties", + // "required", + // "dependentRequired", + ], + array: [ + "contains", + "items", + "additionalItems", + "unevaluatedItems", + + "maxItems", + "minItems", + "uniqueItems", + "maxContains", + "minContains", + ], + string: [ + "pattern", + "contentMediaType", + "contentEncoding", + "contentSchema", + "maxLength", + "minLength", + ], + number: [ + "maximum", + "minimum", + "multipleOf", + "exclusiveMaximum", + "exclusiveMinimum", + ], }; const hasProps = (schema: T, props: Array): boolean => - some(props, (prop: string) => has(schema, prop)); + some(props, (prop: string) => has(schema, prop)); const isMetaType = (schema: any): any => { - return !hasProps(schema, ["type", "$ref", "$id", "oneOf", "anyOf", "allOf"]); + return !hasProps(schema, ["type", "$ref", "$id", "oneOf", "anyOf", "allOf"]); }; const normalizeSchema = (schema: any): any => { - if (isBoolean(schema)) { - return {}; + if (isBoolean(schema)) { + return {}; + } + + // auto complete schema type + if (!schema.type) { + for (const t in typeRelationKeywords) { + if (hasProps(schema, typeRelationKeywords[t] as any)) { + schema.type = t as any; + break; + } } - - // auto complete schema type - if (!schema.type) { - for (const t in typeRelationKeywords) { - if (hasProps(schema, typeRelationKeywords[t] as any)) { - schema.type = t as any; - break; - } + } + + if (schema.const) { + schema.enum = [schema.const]; + } + + const mayNormalizeMeta = (key: "allOf" | "anyOf" | "oneOf") => { + if (schema[key]) { + schema[key] = filter(schema[key], (s) => { + const ns = normalizeSchema(s); + if (isMetaType(ns)) { + if (key === "allOf") { + // only allOf will merge meta + assign(schema, ns); + } + return false; } - } + return true; + }); - if (schema.const) { - schema.enum = [schema.const]; + if (schema[key]?.length === 0) { + schema[key] = undefined; + } } + }; - const mayNormalizeMeta = (key: "allOf" | "anyOf" | "oneOf") => { - if (schema[key]) { - schema[key] = filter(schema[key], (s) => { - const ns = normalizeSchema(s); - if (isMetaType(ns)) { - if (key === "allOf") { - // only allOf will merge meta - assign(schema, ns); - } - return false; - } - return true; - }); - - if (schema[key]?.length === 0) { - schema[key] = undefined; - } - } - }; - - mayNormalizeMeta("allOf"); - mayNormalizeMeta("anyOf"); - mayNormalizeMeta("oneOf"); + mayNormalizeMeta("allOf"); + mayNormalizeMeta("anyOf"); + mayNormalizeMeta("oneOf"); - return schema; + return schema; }; diff --git a/nodepkg/typedef/src/encoding/JSONSchemaEncoder.ts b/nodepkg/typedef/src/encoding/JSONSchemaEncoder.ts index 6b453420..eae439c1 100644 --- a/nodepkg/typedef/src/encoding/JSONSchemaEncoder.ts +++ b/nodepkg/typedef/src/encoding/JSONSchemaEncoder.ts @@ -2,98 +2,98 @@ import { isArray, isPlainObject } from "@innoai-tech/lodash"; import { type AnyType, Type } from "../core"; export type JSONSchema = { - type?: string; - [x: string]: any; + type?: string; + [x: string]: any; }; export class JSONSchemaEncoder { - static encode(type: T): JSONSchema | false { - return new JSONSchemaEncoder().encode(type); - } - - def = new Map(); - - encode(type: T): JSONSchema | false { - const s = this._encode(type); - - const definitions: Record = {}; - - for (const [name, d] of this.def) { - definitions[name] = d; - } - - return Object.assign(s, { - definitions: definitions, - }); - } - - private _encode(type: T): JSONSchema | false { - const jsonSchema = this._encodeCore(type); - - if (type.meta["description"]) { - return Object.assign(jsonSchema, { - description: type.meta["description"], - }); - } - - return jsonSchema; - } - - private _encodeCore(type: T): JSONSchema | false { - if (type.schema?.["$unwrap"]) { - const refName = type.schema["$ref"]; - - if (refName) { - if (!this.def.has(refName)) { - // set to lock to avoid loop - this.def.set(refName, {}); - this.def.set(refName, this._encode(type.schema["$unwrap"]())); - } - - return { - $ref: `#/definitions/${refName}`, - }; - } - - return this._encode(type.schema["$unwrap"]); - } - - return this._encodeFromSchema(type.schema); - } - - private _encodeFromSchema(s: Record | null): JSONSchema | false { - if (!s) { - if (s === false) { - return false; - } - return {}; - } - - const schema: Record = {}; - - for (const n in s) { - if (n.startsWith("$")) { - continue; - } - - const p = s[n]; - - if (p instanceof Type) { - schema[n] = this._encode(p); - } else if (isArray(p)) { - schema[n] = p.map((item) => { - if (item instanceof Type) { - return this._encode(item); - } - return item; - }); - } else if (isPlainObject(p)) { - schema[n] = this._encodeFromSchema(p); - } else { - schema[n] = s[n]; - } - } - - return schema; - } + static encode(type: T): JSONSchema | false { + return new JSONSchemaEncoder().encode(type); + } + + def = new Map(); + + encode(type: T): JSONSchema | false { + const s = this._encode(type); + + const definitions: Record = {}; + + for (const [name, d] of this.def) { + definitions[name] = d; + } + + return Object.assign(s, { + definitions: definitions, + }); + } + + private _encode(type: T): JSONSchema | false { + const jsonSchema = this._encodeCore(type); + + if (type.meta["description"]) { + return Object.assign(jsonSchema, { + description: type.meta["description"], + }); + } + + return jsonSchema; + } + + private _encodeCore(type: T): JSONSchema | false { + if (type.schema?.["$unwrap"]) { + const refName = type.schema["$ref"]; + + if (refName) { + if (!this.def.has(refName)) { + // set to lock to avoid loop + this.def.set(refName, {}); + this.def.set(refName, this._encode(type.schema["$unwrap"]())); + } + + return { + $ref: `#/definitions/${refName}`, + }; + } + + return this._encode(type.schema["$unwrap"]); + } + + return this._encodeFromSchema(type.schema); + } + + private _encodeFromSchema(s: Record | null): JSONSchema | false { + if (!s) { + if (s === false) { + return false; + } + return {}; + } + + const schema: Record = {}; + + for (const n in s) { + if (n.startsWith("$")) { + continue; + } + + const p = s[n]; + + if (p instanceof Type) { + schema[n] = this._encode(p); + } else if (isArray(p)) { + schema[n] = p.map((item) => { + if (item instanceof Type) { + return this._encode(item); + } + return item; + }); + } else if (isPlainObject(p)) { + schema[n] = this._encodeFromSchema(p); + } else { + schema[n] = s[n]; + } + } + + return schema; + } } diff --git a/nodepkg/typedef/src/encoding/TypeScriptEncoder.ts b/nodepkg/typedef/src/encoding/TypeScriptEncoder.ts index bddbd9a1..91bb656f 100644 --- a/nodepkg/typedef/src/encoding/TypeScriptEncoder.ts +++ b/nodepkg/typedef/src/encoding/TypeScriptEncoder.ts @@ -1,159 +1,159 @@ import { type AnyType, Type, TypeRef } from "../core"; export class TypeScriptEncoder { - static encode(type: AnyType): string { - return new TypeScriptEncoder().encode(type); - } + static encode(type: AnyType): string { + return new TypeScriptEncoder().encode(type); + } - def = new Map(); + def = new Map(); - encode(type: AnyType, all = true): string { - const d = this._encode(type); - if (all) { - return (type instanceof TypeRef ? "" : d) + this.decls(); - } - return d; - } + encode(type: AnyType, all = true): string { + const d = this._encode(type); + if (all) { + return (type instanceof TypeRef ? "" : d) + this.decls(); + } + return d; + } - decls() { - let decls = ""; + decls() { + let decls = ""; - for (const [name, [t, decl]] of this.def) { - decls += ` + for (const [name, [t, decl]] of this.def) { + decls += ` export ${t} ${name}${t === "enum" ? " " : " = "}${decl}`; - } - - return decls; - } - - private _encode(rawType: AnyType, declName = ""): string { - let type = rawType; - - while (true) { - if (type instanceof TypeRef) { - break; - } - const unwrapped = type.unwrap; - if (unwrapped === type) { - break; - } - type = unwrapped as AnyType; - } - - if (type instanceof TypeRef) { - const refName = type.schema.$ref; - - if (!this.def.has(refName)) { - // set to lock to avoid loop - this.def.set(refName, ["type", "any"]); - - const decl = this._encode(type.unwrap, refName); - if (decl) { - this.def.set(refName, ["type", decl]); - } - } - - return refName; - } - - switch (type.type) { - case "intersection": { - return `(${type.schema.allOf - .map((t: AnyType) => this._encode(t)) - .join(" & ")})`; - } - - case "union": { - return `(${type.schema.oneOf - .map((t: AnyType) => this._encode(t)) - .join(" | ")})`; - } - - case "literal": { - return JSON.stringify(type.schema.enum[0]); - } - - case "enums": { - if (declName) { - this.def.set(declName, [ - "enum", - `{ + } + + return decls; + } + + private _encode(rawType: AnyType, declName = ""): string { + let type = rawType; + + while (true) { + if (type instanceof TypeRef) { + break; + } + const unwrapped = type.unwrap; + if (unwrapped === type) { + break; + } + type = unwrapped as AnyType; + } + + if (type instanceof TypeRef) { + const refName = type.schema.$ref; + + if (!this.def.has(refName)) { + // set to lock to avoid loop + this.def.set(refName, ["type", "any"]); + + const decl = this._encode(type.unwrap, refName); + if (decl) { + this.def.set(refName, ["type", decl]); + } + } + + return refName; + } + + switch (type.type) { + case "intersection": { + return `(${type.schema.allOf + .map((t: AnyType) => this._encode(t)) + .join(" & ")})`; + } + + case "union": { + return `(${type.schema.oneOf + .map((t: AnyType) => this._encode(t)) + .join(" | ")})`; + } + + case "literal": { + return JSON.stringify(type.schema.enum[0]); + } + + case "enums": { + if (declName) { + this.def.set(declName, [ + "enum", + `{ ${type.schema.enum.map((v: any) => `${v} = ${JSON.stringify(v)}`).join(",\n")} }`, - ]); + ]); - const enumLabels = rawType.getMeta("enumLabels") as any[]; + const enumLabels = rawType.getMeta("enumLabels") as any[]; - if (enumLabels) { - this.def.set(`display${declName}`, [ - "const", - `(v: ${declName}) => { + if (enumLabels) { + this.def.set(`display${declName}`, [ + "const", + `(v: ${declName}) => { return ({ ${type.schema.enum - .map((v: any, i: number) => `${v}: ${JSON.stringify(enumLabels[i])}`) - .join(",\n")} + .map((v: any, i: number) => `${v}: ${JSON.stringify(enumLabels[i])}`) + .join(",\n")} })[v] ?? v }`, - ]); - } + ]); + } - return ""; - } + return ""; + } - return type.schema.enum.map((v: any) => JSON.stringify(v)).join(" | "); - } + return type.schema.enum.map((v: any) => JSON.stringify(v)).join(" | "); + } - case "record": { - return `{ [k: ${this._encode( - type.schema.propertyNames, - )}]: ${this._encode(type.schema.additionalProperties)} }`; - } + case "record": { + return `{ [k: ${this._encode( + type.schema.propertyNames, + )}]: ${this._encode(type.schema.additionalProperties)} }`; + } - case "object": { - let ts = `{ + case "object": { + let ts = `{ `; - for (const p in type.schema.properties) { - const propSchema = type.schema.properties[p] as Type; + for (const p in type.schema.properties) { + const propSchema = type.schema.properties[p] as Type; - ts += ` ${JSON.stringify(p)}`; - if (propSchema.isOptional) { - ts += "?"; - } + ts += ` ${JSON.stringify(p)}`; + if (propSchema.isOptional) { + ts += "?"; + } - ts += `: ${this._encode(propSchema)}, + ts += `: ${this._encode(propSchema)}, `; - } - - ts += "}"; - - return ts; - } - case "tuple": - return `[${type.schema.items - .map((t: AnyType) => this._encode(t)) - .join(", ")}]`; - case "array": - return `Array<${this._encode(type.schema.items)}>`; - case "string": { - return "string"; - } - case "binary": { - return "File | Blob"; - } - case "number": - case "integer": { - return "number"; - } - case "boolean": { - return "boolean"; - } - case "nil": { - return "null"; - } - } - - return "any"; - } + } + + ts += "}"; + + return ts; + } + case "tuple": + return `[${type.schema.items + .map((t: AnyType) => this._encode(t)) + .join(", ")}]`; + case "array": + return `Array<${this._encode(type.schema.items)}>`; + case "string": { + return "string"; + } + case "binary": { + return "File | Blob"; + } + case "number": + case "integer": { + return "number"; + } + case "boolean": { + return "boolean"; + } + case "nil": { + return "null"; + } + } + + return "any"; + } } diff --git a/nodepkg/typedef/src/encoding/TypedefEncoder.ts b/nodepkg/typedef/src/encoding/TypedefEncoder.ts index 58a11a0d..4b138007 100644 --- a/nodepkg/typedef/src/encoding/TypedefEncoder.ts +++ b/nodepkg/typedef/src/encoding/TypedefEncoder.ts @@ -1,175 +1,175 @@ -import {get, isEmpty, omit} from "@innoai-tech/lodash"; -import {type AnyType, Type, TypeRef, t} from "../core"; +import { get, isEmpty, omit } from "@innoai-tech/lodash"; +import { type AnyType, Type, TypeRef, t } from "../core"; export class TypedefEncoder { - static encode(type: T): string { - return new TypedefEncoder().encode(type); - } + static encode(type: T): string { + return new TypedefEncoder().encode(type); + } - def = new Map(); + def = new Map(); - encode(type: AnyType, all = true): string { - const d = this._encode(type); - if (all) { - return (type instanceof TypeRef ? "" : d) + this.decls(); - } - return d; + encode(type: AnyType, all = true): string { + const d = this._encode(type); + if (all) { + return (type instanceof TypeRef ? "" : d) + this.decls(); } + return d; + } - decls() { - let decls = ""; - for (const [name, decl] of this.def) { - decls += ` + decls() { + let decls = ""; + for (const [name, decl] of this.def) { + decls += ` export const ${name}Schema = /*#__PURE__*/${decl}`; - } - - return decls; } - private _encode(type: AnyType, declName = ""): string { - return `${this._encodeCode(type, declName)}${ - type.meta["description"] - ? `.use(t.annotate({ description: ${JSON.stringify( - type.meta["description"], - )} }))` - : "" - }`; + return decls; + } + + private _encode(type: AnyType, declName = ""): string { + return `${this._encodeCode(type, declName)}${ + type.meta["description"] + ? `.use(t.annotate({ description: ${JSON.stringify( + type.meta["description"], + )} }))` + : "" + }`; + } + + private _encodeCode(typ: AnyType, declName = ""): string { + let type = typ; + + while (true) { + if (type instanceof TypeRef) { + break; + } + const unwrapped = type.unwrap; + if (unwrapped === type) { + break; + } + type = unwrapped as AnyType; } - private _encodeCode(typ: AnyType, declName = ""): string { - let type = typ; - - while (true) { - if (type instanceof TypeRef) { - break; - } - const unwrapped = type.unwrap; - if (unwrapped === type) { - break; - } - type = unwrapped as AnyType; - } - - if (type instanceof TypeRef) { - const refName = type.schema.$ref; + if (type instanceof TypeRef) { + const refName = type.schema.$ref; - if (!this.def.has(refName)) { - // set to lock to avoid loop - this.def.set(refName, ""); - this.def.set(refName, this._encode(type.unwrap, refName)); - } + if (!this.def.has(refName)) { + // set to lock to avoid loop + this.def.set(refName, ""); + this.def.set(refName, this._encode(type.unwrap, refName)); + } - return `t.ref("${refName}", () => ${refName}Schema)`; - } + return `t.ref("${refName}", () => ${refName}Schema)`; + } - switch (type.type) { - case "intersection": { - return `t.intersection(${type.schema.allOf - .map((t: AnyType) => this._encode(t)) - .join(", ")})`; + switch (type.type) { + case "intersection": { + return `t.intersection(${type.schema.allOf + .map((t: AnyType) => this._encode(t)) + .join(", ")})`; + } + + case "union": { + const discriminatorPropertyName = get(type.schema, [ + "discriminator", + "propertyName", + ]); + + if (discriminatorPropertyName) { + const mapping: Record = {}; + + for (const sub of type.schema.oneOf) { + const e = get(sub.schema.properties, discriminatorPropertyName); + + const props = omit( + sub.schema.properties, + discriminatorPropertyName, + ); + + if (e) { + if (e.type === "enums") { + for (const enumValue of e.schema.enum) { + mapping[`${enumValue}`] = this._encode(t.object(props)); + } + } } + } - case "union": { - const discriminatorPropertyName = get(type.schema, [ - "discriminator", - "propertyName", - ]); - - if (discriminatorPropertyName) { - const mapping: Record = {}; - - for (const sub of type.schema.oneOf) { - const e = get(sub.schema.properties, discriminatorPropertyName); - - const props = omit( - sub.schema.properties, - discriminatorPropertyName, - ); - - if (e) { - if (e.type === "enums") { - for (const enumValue of e.schema.enum) { - mapping[`${enumValue}`] = this._encode(t.object(props)); - } - } - } - } - - return `t.discriminatorMapping("${discriminatorPropertyName}", { + return `t.discriminatorMapping("${discriminatorPropertyName}", { ${Object.keys(mapping) - .map((k) => `${JSON.stringify(k)}: ${mapping[k]}`) - .join(",\n")} + .map((k) => `${JSON.stringify(k)}: ${mapping[k]}`) + .join(",\n")} })`; - } + } - return `t.union(${type.schema.oneOf - .map((t: AnyType) => this._encode(t)) - .join(", ")})`; - } + return `t.union(${type.schema.oneOf + .map((t: AnyType) => this._encode(t)) + .join(", ")})`; + } - case "enums": { - if (declName) { - return `t.nativeEnum(${declName})`; - } + case "enums": { + if (declName) { + return `t.nativeEnum(${declName})`; + } - return `t.enums([${type.schema.enum - .map((v: any) => JSON.stringify(v)) - .join(", ")}])`; - } + return `t.enums([${type.schema.enum + .map((v: any) => JSON.stringify(v)) + .join(", ")}])`; + } - case "record": { - return `t.record(${this._encode( - type.schema.propertyNames, - )}, ${this._encode(type.schema.additionalProperties)})`; - } + case "record": { + return `t.record(${this._encode( + type.schema.propertyNames, + )}, ${this._encode(type.schema.additionalProperties)})`; + } - case "object": { - if (isEmpty(type.schema.properties)) { - return "t.object()"; - } + case "object": { + if (isEmpty(type.schema.properties)) { + return "t.object()"; + } - let ts = `{ + let ts = `{ `; - for (const p in type.schema.properties) { - const propSchema = type.schema.properties[p] as Type; + for (const p in type.schema.properties) { + const propSchema = type.schema.properties[p] as Type; - ts += ` ${JSON.stringify(p)}`; - ts += `: ${this._encode(propSchema)}`; + ts += ` ${JSON.stringify(p)}`; + ts += `: ${this._encode(propSchema)}`; - if (propSchema.isOptional) { - ts += ".optional()"; - } + if (propSchema.isOptional) { + ts += ".optional()"; + } - ts += `, + ts += `, `; - } - - ts += "}"; - - return `t.object(${ts})`; - } - case "tuple": - return `t.tuple([${type.schema.items - .map((t: AnyType) => this._encode(t)) - .join(", ")}])`; - case "array": - return `t.array(${this._encode(type.schema.items)})`; - case "string": - return "t.string()"; - case "binary": - return "t.binary()"; - case "number": - return "t.number()"; - case "integer": - return "t.integer()"; - case "boolean": - return "t.boolean()"; - case "nil": - return "t.nil()"; } - return "t.any()"; + ts += "}"; + + return `t.object(${ts})`; + } + case "tuple": + return `t.tuple([${type.schema.items + .map((t: AnyType) => this._encode(t)) + .join(", ")}])`; + case "array": + return `t.array(${this._encode(type.schema.items)})`; + case "string": + return "t.string()"; + case "binary": + return "t.binary()"; + case "number": + return "t.number()"; + case "integer": + return "t.integer()"; + case "boolean": + return "t.boolean()"; + case "nil": + return "t.nil()"; } + + return "t.any()"; + } } diff --git a/nodepkg/typedef/src/encoding/__tests__/JSONSchemaDecoder.spec.ts b/nodepkg/typedef/src/encoding/__tests__/JSONSchemaDecoder.spec.ts index 0ba6d096..5804ce12 100644 --- a/nodepkg/typedef/src/encoding/__tests__/JSONSchemaDecoder.spec.ts +++ b/nodepkg/typedef/src/encoding/__tests__/JSONSchemaDecoder.spec.ts @@ -3,110 +3,110 @@ import { get } from "@innoai-tech/lodash"; import { JSONSchemaDecoder, JSONSchemaEncoder, refName } from ".."; describe("JSONSchemaDecoder", () => { - it("decode complex", () => { - const schema = { - definitions: { - A: { type: "string" }, - B: { type: "integer" }, - C: { type: "string", enum: ["X", "Y", "Z"] }, - ObjC: { - type: "object", - properties: { - c: { $ref: "#/definitions/C" }, - }, - }, - Obj: { - allOf: [ - { $ref: "#/definitions/ObjC" }, - { - type: "object", - properties: { - a: { $ref: "#/definitions/A" }, - nested: { $ref: "#/definitions/ObjC" }, - }, - required: ["a"], - }, - ], - }, - Arr: { - type: "array", - items: { type: "string" }, - }, - Map: { - type: "object", - additionalProperties: { - type: "string", - }, - }, - Union: { - type: "object", - discriminator: { - propertyName: "type", - }, - required: ["type"], - oneOf: [ - { - properties: { - type: { enum: ["A"] }, - a: { $ref: "#/definitions/A" }, - b: { $ref: "#/definitions/B" }, - }, - required: ["a"], - additionalProperties: false, - }, - { - properties: { - type: { enum: ["B"], type: "string" }, - b: { $ref: "#/definitions/B" }, - }, - required: ["b"], - additionalProperties: false, - }, - ], - }, - TaggedUnionWhenA: { - properties: { - type: { enum: ["A"] }, - a: { $ref: "#/definitions/A" }, - b: { $ref: "#/definitions/B" }, - }, - required: ["a"], - additionalProperties: false, - }, - TaggedUnion: { - type: "object", - discriminator: { - propertyName: "type", - mapping: { - A: { $ref: "#/definitions/TaggedUnionWhenA" }, - B: { - properties: { - type: { enum: ["B"], type: "string" }, - b: { $ref: "#/definitions/B" }, - }, - required: ["b"], - additionalProperties: false, - }, - }, - }, - }, - }, - type: "object", - additionalProperties: false, - properties: { - obj: { $ref: "#/definitions/Obj" }, - arr: { $ref: "#/definitions/Arr" }, - map: { $ref: "#/definitions/Map" }, - union: { $ref: "#/definitions/Union" }, - tagged: { $ref: "#/definitions/TaggedUnion" }, - }, - }; + it("decode complex", () => { + const schema = { + definitions: { + A: { type: "string" }, + B: { type: "integer" }, + C: { type: "string", enum: ["X", "Y", "Z"] }, + ObjC: { + type: "object", + properties: { + c: { $ref: "#/definitions/C" }, + }, + }, + Obj: { + allOf: [ + { $ref: "#/definitions/ObjC" }, + { + type: "object", + properties: { + a: { $ref: "#/definitions/A" }, + nested: { $ref: "#/definitions/ObjC" }, + }, + required: ["a"], + }, + ], + }, + Arr: { + type: "array", + items: { type: "string" }, + }, + Map: { + type: "object", + additionalProperties: { + type: "string", + }, + }, + Union: { + type: "object", + discriminator: { + propertyName: "type", + }, + required: ["type"], + oneOf: [ + { + properties: { + type: { enum: ["A"] }, + a: { $ref: "#/definitions/A" }, + b: { $ref: "#/definitions/B" }, + }, + required: ["a"], + additionalProperties: false, + }, + { + properties: { + type: { enum: ["B"], type: "string" }, + b: { $ref: "#/definitions/B" }, + }, + required: ["b"], + additionalProperties: false, + }, + ], + }, + TaggedUnionWhenA: { + properties: { + type: { enum: ["A"] }, + a: { $ref: "#/definitions/A" }, + b: { $ref: "#/definitions/B" }, + }, + required: ["a"], + additionalProperties: false, + }, + TaggedUnion: { + type: "object", + discriminator: { + propertyName: "type", + mapping: { + A: { $ref: "#/definitions/TaggedUnionWhenA" }, + B: { + properties: { + type: { enum: ["B"], type: "string" }, + b: { $ref: "#/definitions/B" }, + }, + required: ["b"], + additionalProperties: false, + }, + }, + }, + }, + }, + type: "object", + additionalProperties: false, + properties: { + obj: { $ref: "#/definitions/Obj" }, + arr: { $ref: "#/definitions/Arr" }, + map: { $ref: "#/definitions/Map" }, + union: { $ref: "#/definitions/Union" }, + tagged: { $ref: "#/definitions/TaggedUnion" }, + }, + }; - const t = JSONSchemaDecoder.decode(schema, (ref) => { - return [get(schema, ref.slice(2).split("/"), {}), refName(ref)]; - }); + const t = JSONSchemaDecoder.decode(schema, (ref) => { + return [get(schema, ref.slice(2).split("/"), {}), refName(ref)]; + }); - const schemaV2 = JSONSchemaEncoder.encode(t); - expect(schemaV2).toMatchSnapshot(); - }); + const schemaV2 = JSONSchemaEncoder.encode(t); + expect(schemaV2).toMatchSnapshot(); + }); }); diff --git a/nodepkg/typedef/src/encoding/__tests__/index.spec.ts b/nodepkg/typedef/src/encoding/__tests__/index.spec.ts index 495ae627..a4a19c3f 100644 --- a/nodepkg/typedef/src/encoding/__tests__/index.spec.ts +++ b/nodepkg/typedef/src/encoding/__tests__/index.spec.ts @@ -1,89 +1,89 @@ -import {describe, expect, test} from "bun:test"; -import {get} from "@innoai-tech/lodash"; +import { describe, expect, test } from "bun:test"; +import { get } from "@innoai-tech/lodash"; import { - JSONSchemaDecoder, - JSONSchemaEncoder, - TypeScriptEncoder, - TypedefEncoder, - refName, + JSONSchemaDecoder, + JSONSchemaEncoder, + TypeScriptEncoder, + TypedefEncoder, + refName, } from "../"; -import {t} from "../../core"; +import { t } from "../../core"; describe("Encoding", () => { - enum InputType { - text = "text", - number = "number", - select = "select", - } + enum InputType { + text = "text", + number = "number", + select = "select", + } - const schemaStrOrInt = t.union(t.string(), t.integer()); + const schemaStrOrInt = t.union(t.string(), t.integer()); - const schemaTaggedUnion = t.discriminatorMapping("type", { - [InputType.text]: t.object(), - [InputType.select]: t.ref("WithOptions", () => - t.object({ - options: t.array( - t.object({ - label: t.string(), - value: t.string(), - }), - ), - }), + const schemaTaggedUnion = t.discriminatorMapping("type", { + [InputType.text]: t.object(), + [InputType.select]: t.ref("WithOptions", () => + t.object({ + options: t.array( + t.object({ + label: t.string(), + value: t.string(), + }), ), - }); - - const schema = t.intersection( - t.object({ - strOrInt: t - .ref("StrOrInt", () => schemaStrOrInt) - .use( - t.annotate({ - description: "StrOrInt", - }) - ), - placement: t.enums(["leading", "trailing"]), - inputType: t - .ref("InputType", () => - t.nativeEnum(InputType).use( - t.annotate({ - enumLabels: ["文本", "数字", "选项"], - }) - ), - ) - .optional(), - keyValues: t.record(t.string(), t.any()).optional(), - array: t.array(t.boolean()), - point: t.tuple([t.number(), t.number()]), - }), - schemaTaggedUnion, - ); + }), + ), + }); - test("JSONSchema decode", () => { - const jsonSchema = JSONSchemaEncoder.encode(schema); + const schema = t.intersection( + t.object({ + strOrInt: t + .ref("StrOrInt", () => schemaStrOrInt) + .use( + t.annotate({ + description: "StrOrInt", + }), + ), + placement: t.enums(["leading", "trailing"]), + inputType: t + .ref("InputType", () => + t.nativeEnum(InputType).use( + t.annotate({ + enumLabels: ["文本", "数字", "选项"], + }), + ), + ) + .optional(), + keyValues: t.record(t.string(), t.any()).optional(), + array: t.array(t.boolean()), + point: t.tuple([t.number(), t.number()]), + }), + schemaTaggedUnion, + ); - const schema2 = JSONSchemaDecoder.decode(jsonSchema, (ref) => { - return [ - get(jsonSchema, ref.split("#/")[1]?.split("/") ?? ""), - refName(ref), - ]; - }); + test("JSONSchema decode", () => { + const jsonSchema = JSONSchemaEncoder.encode(schema); - const jsonSchema2 = JSONSchemaEncoder.encode(schema2); - expect(jsonSchema2).toEqual(jsonSchema); + const schema2 = JSONSchemaDecoder.decode(jsonSchema, (ref) => { + return [ + get(jsonSchema, ref.split("#/")[1]?.split("/") ?? ""), + refName(ref), + ]; }); - test("Typedef encode", () => { - const typeDefCode = TypedefEncoder.encode(t.ref("Type", () => schema)); - expect(typeDefCode).toMatchSnapshot(); - }); + const jsonSchema2 = JSONSchemaEncoder.encode(schema2); + expect(jsonSchema2).toEqual(jsonSchema); + }); - test("TypeScript encode", () => { - const tsCode = TypeScriptEncoder.encode(t.ref("Type", () => schema)); - expect(tsCode).toMatchSnapshot(); - }); + test("Typedef encode", () => { + const typeDefCode = TypedefEncoder.encode(t.ref("Type", () => schema)); + expect(typeDefCode).toMatchSnapshot(); + }); - test("JSONSchema encode", () => { - const jsonSchema = JSONSchemaEncoder.encode(schema); - expect(jsonSchema).toMatchSnapshot(); - }); + test("TypeScript encode", () => { + const tsCode = TypeScriptEncoder.encode(t.ref("Type", () => schema)); + expect(tsCode).toMatchSnapshot(); + }); + + test("JSONSchema encode", () => { + const jsonSchema = JSONSchemaEncoder.encode(schema); + expect(jsonSchema).toMatchSnapshot(); + }); }); diff --git a/nodepkg/typedef/tsconfig.monobundle.json b/nodepkg/typedef/tsconfig.monobundle.json index 21023dd8..3170e721 100644 --- a/nodepkg/typedef/tsconfig.monobundle.json +++ b/nodepkg/typedef/tsconfig.monobundle.json @@ -1,6 +1,6 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - } + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + } } diff --git a/nodepkg/vue-jsx-runtime/package.json b/nodepkg/vue-jsx-runtime/package.json index 8d8aa9d1..f41027e0 100644 --- a/nodepkg/vue-jsx-runtime/package.json +++ b/nodepkg/vue-jsx-runtime/package.json @@ -44,8 +44,7 @@ "directory": "nodepkg/vue-jsx-runtime" }, "scripts": { - "test": "bun test .", - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodepkg/vue-jsx-runtime/src/__tests__/jsx-runtime.spec.ts b/nodepkg/vue-jsx-runtime/src/__tests__/jsx-runtime.spec.ts index adfe8a67..c9e360d2 100644 --- a/nodepkg/vue-jsx-runtime/src/__tests__/jsx-runtime.spec.ts +++ b/nodepkg/vue-jsx-runtime/src/__tests__/jsx-runtime.spec.ts @@ -6,146 +6,146 @@ import { defineComponent, h, renderSlot } from "vue"; import { Fragment, jsx } from "../jsx-runtime"; const Layout = defineComponent((_, { slots }) => () => { - return h("div", {}, [ - renderSlot(slots, "title"), - renderSlot(slots, "default"), - ]); + return h("div", {}, [ + renderSlot(slots, "title"), + renderSlot(slots, "default"), + ]); }); describe("jsx-runtime", () => { - describe("Component", () => { - it("with empty children", () => { - expect( - jsx(Layout, { - id: "1", - }), - ).toEqual( - h(Layout, { - id: "1", - }), - ); - }); - - it("with single children", () => { - expect( - jsx(Layout, { - id: "1", - }), - ).toEqual( - h(Layout, { - id: "1", - }), - ); - }); - - it("with single raw value child", () => { - expect(mount(() => jsx(Layout, { children: "1" })).html()).toEqual( - mount(() => h(Layout, {}, () => ["1"])).html(), - ); - }); - - it("with single child node", () => { - expect( - mount(() => - jsx(Layout, { - children: jsx("div", {}), - }), - ).html(), - ).toEqual(mount(() => h(Layout, {}, () => [jsx("div", {})])).html()); - }); - - it("with children", () => { - expect( - mount(() => - jsx(Layout, { - children: ["1", jsx("div", {})], - }), - ).html(), - ).toEqual(mount(() => h(Layout, {}, () => [["1", h("div", {})]])).html()); - }); - - it("with children and slots", () => { - it("with children", () => { - expect( - mount(() => - jsx(Layout, { - $title: jsx("h1", {}), - children: ["1", jsx("div", {})], - }), - ).html(), - ).toEqual( - mount(() => - h( - Layout, - {}, - { - title: () => h("h1", {}), - default: () => [["1", h("div", {})]], - }, - ), - ).html(), - ); - }); - }); - }); - - describe("tag", () => { - it("with empty children", () => { - expect( - jsx("div", { - id: "1", - }), - ).toEqual( - h("div", { - id: "1", - }), - ); - }); - - it("with single raw value child", () => { - expect(jsx("div", { children: 0 })).toEqual(h("div", {}, "0")); - }); - - it("with single child node", () => { - expect( - jsx("div", { - children: jsx("div", {}), - }), - ).toEqual(h("div", {}, [jsx("div", {})])); - }); - - it("with children", () => { - expect( - jsx("div", { - children: ["1", jsx("div", {})], - }), - ).toEqual(h("div", {}, ["1", jsx("div", {})])); - }); - }); - - it("Fragment", () => { - expect( - jsx(Fragment, { - children: null, - }), - ).toEqual(h(Fragment, {}, [])); - - expect( - jsx(Fragment, { - children: undefined, - }), - ).toEqual(h(Fragment, {}, [])); - - expect( - jsx(Fragment, { - children: jsx("div", {}), - }), - ).toEqual(h(Fragment, {}, [jsx("div", {})])); - - expect( - jsx(Fragment, { - children: [], - }), - ).toEqual(h(Fragment, {}, [])); - }); + describe("Component", () => { + it("with empty children", () => { + expect( + jsx(Layout, { + id: "1", + }), + ).toEqual( + h(Layout, { + id: "1", + }), + ); + }); + + it("with single children", () => { + expect( + jsx(Layout, { + id: "1", + }), + ).toEqual( + h(Layout, { + id: "1", + }), + ); + }); + + it("with single raw value child", () => { + expect(mount(() => jsx(Layout, { children: "1" })).html()).toEqual( + mount(() => h(Layout, {}, () => ["1"])).html(), + ); + }); + + it("with single child node", () => { + expect( + mount(() => + jsx(Layout, { + children: jsx("div", {}), + }), + ).html(), + ).toEqual(mount(() => h(Layout, {}, () => [jsx("div", {})])).html()); + }); + + it("with children", () => { + expect( + mount(() => + jsx(Layout, { + children: ["1", jsx("div", {})], + }), + ).html(), + ).toEqual(mount(() => h(Layout, {}, () => [["1", h("div", {})]])).html()); + }); + + it("with children and slots", () => { + it("with children", () => { + expect( + mount(() => + jsx(Layout, { + $title: jsx("h1", {}), + children: ["1", jsx("div", {})], + }), + ).html(), + ).toEqual( + mount(() => + h( + Layout, + {}, + { + title: () => h("h1", {}), + default: () => [["1", h("div", {})]], + }, + ), + ).html(), + ); + }); + }); + }); + + describe("tag", () => { + it("with empty children", () => { + expect( + jsx("div", { + id: "1", + }), + ).toEqual( + h("div", { + id: "1", + }), + ); + }); + + it("with single raw value child", () => { + expect(jsx("div", { children: 0 })).toEqual(h("div", {}, "0")); + }); + + it("with single child node", () => { + expect( + jsx("div", { + children: jsx("div", {}), + }), + ).toEqual(h("div", {}, [jsx("div", {})])); + }); + + it("with children", () => { + expect( + jsx("div", { + children: ["1", jsx("div", {})], + }), + ).toEqual(h("div", {}, ["1", jsx("div", {})])); + }); + }); + + it("Fragment", () => { + expect( + jsx(Fragment, { + children: null, + }), + ).toEqual(h(Fragment, {}, [])); + + expect( + jsx(Fragment, { + children: undefined, + }), + ).toEqual(h(Fragment, {}, [])); + + expect( + jsx(Fragment, { + children: jsx("div", {}), + }), + ).toEqual(h(Fragment, {}, [jsx("div", {})])); + + expect( + jsx(Fragment, { + children: [], + }), + ).toEqual(h(Fragment, {}, [])); + }); }); diff --git a/nodepkg/vue-jsx-runtime/src/jsx-dev-runtime.ts b/nodepkg/vue-jsx-runtime/src/jsx-dev-runtime.ts index 9a0e3037..f2854be0 100644 --- a/nodepkg/vue-jsx-runtime/src/jsx-dev-runtime.ts +++ b/nodepkg/vue-jsx-runtime/src/jsx-dev-runtime.ts @@ -4,12 +4,12 @@ import { jsx } from "./jsx-runtime"; export { Fragment }; export function jsxDEV( - type: any, - props: any, - key: string | undefined, - _isStaticChildren: boolean, - _source: object, - _self: object, + type: any, + props: any, + key: string | undefined, + _isStaticChildren: boolean, + _source: object, + _self: object, ) { - return jsx(type, props ?? {}, key); + return jsx(type, props ?? {}, key); } diff --git a/nodepkg/vue-jsx-runtime/src/jsx-runtime.ts b/nodepkg/vue-jsx-runtime/src/jsx-runtime.ts index 9d5027cf..f8ca7aee 100644 --- a/nodepkg/vue-jsx-runtime/src/jsx-runtime.ts +++ b/nodepkg/vue-jsx-runtime/src/jsx-runtime.ts @@ -1,10 +1,10 @@ import { - Fragment, - type NativeElements, - type ReservedProps, - type VNode, - type VNodeChild, - h, + Fragment, + type NativeElements, + type ReservedProps, + type VNode, + type VNodeChild, + h, } from "vue"; export { Fragment }; @@ -12,139 +12,139 @@ export { Fragment }; const isFunction = (val: any) => typeof val === "function"; const isUndefined = (val: any) => typeof val === "undefined"; const isFragment = (val: any) => { - return val === Fragment; + return val === Fragment; }; const isTagOrInternal = (val: any) => { - if (isFragment(val)) { - return true; - } + if (isFragment(val)) { + return true; + } - if (typeof val === "string") { - return true; - } + if (typeof val === "string") { + return true; + } - if (typeof val === "object") { - if (val.__isTeleport) { - return true; - } - } + if (typeof val === "object") { + if (val.__isTeleport) { + return true; + } + } - return false; + return false; }; const isSlots = (children: any) => { - if (children && typeof children === "object") { - if (children.__vInternal) { - return true; - } - } - return false; + if (children && typeof children === "object") { + if (children.__vInternal) { + return true; + } + } + return false; }; const wrapSlot = (children: any) => { - if (isFunction(children)) { - return children; - } - if (Array.isArray(children)) { - return () => children; - } - return isUndefined(children) ? children : () => children; + if (isFunction(children)) { + return children; + } + if (Array.isArray(children)) { + return () => children; + } + return isUndefined(children) ? children : () => children; }; const pickPropsWithoutSlots = ( - rawProps: Record, - key?: string, + rawProps: Record, + key?: string, ): [any, any] => { - const { children, ...otherProps } = rawProps; - - // pass slots as children - if (isSlots(children)) { - return [key ? { ...otherProps, key } : otherProps, children]; - } - - const props: Record = {}; - const slots: Record = {}; - let hasAnySlot = false; - - for (const prop in otherProps) { - const v = otherProps[prop]; - if (prop.startsWith("$")) { - const slotName = prop.slice(1); - slots[slotName] = wrapSlot(v); - hasAnySlot = true; - continue; - } - props[prop] = v; - } - - const defaultSlot = wrapSlot(children); - if (defaultSlot) { - slots["default"] = defaultSlot; - hasAnySlot = true; - } - - return [key ? { ...props, key } : props, hasAnySlot ? slots : undefined]; + const { children, ...otherProps } = rawProps; + + // pass slots as children + if (isSlots(children)) { + return [key ? { ...otherProps, key } : otherProps, children]; + } + + const props: Record = {}; + const slots: Record = {}; + let hasAnySlot = false; + + for (const prop in otherProps) { + const v = otherProps[prop]; + if (prop.startsWith("$")) { + const slotName = prop.slice(1); + slots[slotName] = wrapSlot(v); + hasAnySlot = true; + continue; + } + props[prop] = v; + } + + const defaultSlot = wrapSlot(children); + if (defaultSlot) { + slots["default"] = defaultSlot; + hasAnySlot = true; + } + + return [key ? { ...props, key } : props, hasAnySlot ? slots : undefined]; }; export const jsxs = (type: any, rawProps: any, key?: string) => { - return jsx(type, rawProps, key); + return jsx(type, rawProps, key); }; export const jsx = (type: any, rawProps: any, key?: string) => { - const [props, slots] = pickPropsWithoutSlots(rawProps, key); - if (isTagOrInternal(type)) { - return h( - type, - props, - slots?.default?.() ?? (isFragment(type) ? [] : undefined), - ); - } - return h(type, props, slots); + const [props, slots] = pickPropsWithoutSlots(rawProps, key); + if (isTagOrInternal(type)) { + return h( + type, + props, + slots?.default?.() ?? (isFragment(type) ? [] : undefined), + ); + } + return h(type, props, slots); }; declare module "vue" { - // always contains default slots - interface HTMLAttributes { - $default?: VNodeChild; - } - - interface SVGAttributes { - $default?: VNodeChild; - } - - interface TransitionProps { - $default?: VNodeChild; - } - - interface TeleportProps { - $default?: VNodeChild; - } + // always contains default slots + interface HTMLAttributes { + $default?: VNodeChild; + } + + interface SVGAttributes { + $default?: VNodeChild; + } + + interface TransitionProps { + $default?: VNodeChild; + } + + interface TeleportProps { + $default?: VNodeChild; + } } declare global { - namespace JSX { - export interface Element extends VNode {} - - export interface ElementClass { - $props: {}; - } - - export interface ElementAttributesProperty { - $props: {}; - } - - export interface IntrinsicElements extends NativeElements { - // allow arbitrary elements - // @ts-ignore suppress ts:2374 = Duplicate string index signature. - [name: string]: any; - } - - export interface IntrinsicAttributes extends ReservedProps {} - - // infer children type - export interface ElementChildrenAttribute { - $default: {}; - } - } + namespace JSX { + export interface Element extends VNode {} + + export interface ElementClass { + $props: {}; + } + + export interface ElementAttributesProperty { + $props: {}; + } + + export interface IntrinsicElements extends NativeElements { + // allow arbitrary elements + // @ts-ignore suppress ts:2374 = Duplicate string index signature. + [name: string]: any; + } + + export interface IntrinsicAttributes extends ReservedProps {} + + // infer children type + export interface ElementChildrenAttribute { + $default: {}; + } + } } diff --git a/nodepkg/vue-jsx-runtime/tsconfig.monobundle.json b/nodepkg/vue-jsx-runtime/tsconfig.monobundle.json index 21023dd8..3170e721 100644 --- a/nodepkg/vue-jsx-runtime/tsconfig.monobundle.json +++ b/nodepkg/vue-jsx-runtime/tsconfig.monobundle.json @@ -1,6 +1,6 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - } + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + } } diff --git a/nodepkg/vueformdata/package.json b/nodepkg/vueformdata/package.json index 9ffd6f81..f2163b37 100644 --- a/nodepkg/vueformdata/package.json +++ b/nodepkg/vueformdata/package.json @@ -38,8 +38,7 @@ "directory": "nodepkg/vueformdata" }, "scripts": { - "test": "bun test .", - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodepkg/vueformdata/src/FormData.ts b/nodepkg/vueformdata/src/FormData.ts index c5d55e88..50408763 100644 --- a/nodepkg/vueformdata/src/FormData.ts +++ b/nodepkg/vueformdata/src/FormData.ts @@ -1,277 +1,277 @@ -import {get, isFunction, isUndefined, set} from "@innoai-tech/lodash"; +import { get, isFunction, isUndefined, set } from "@innoai-tech/lodash"; import { - type AnyType, - EmptyContext, - ImmerBehaviorSubject, - type Infer, - type MetaBuilder, - createMetaBuilder, - type Component, - rx, SymbolRecordKey, + type AnyType, + EmptyContext, + ImmerBehaviorSubject, + type Infer, + type MetaBuilder, + createMetaBuilder, + type Component, + rx, + SymbolRecordKey, } from "@innoai-tech/vuekit"; -import {Observable, Subject, distinctUntilChanged, map} from "rxjs"; +import { Observable, Subject, distinctUntilChanged, map } from "rxjs"; export class FormData extends Subject> { - static of( - schema: T, - initials: Partial> | (() => Partial>), - ) { - return new FormData(schema, () => - isFunction(initials) ? initials() : initials, - ); + static of( + schema: T, + initials: Partial> | (() => Partial>), + ) { + return new FormData(schema, () => + isFunction(initials) ? initials() : initials, + ); + } + + static label(label: string): MetaBuilder { + return createMetaBuilder({ label }); + } + + public readonly inputs$: ImmerBehaviorSubject>>; + + constructor( + public typedef: T, + initials: () => Partial>, + ) { + super(); + this.inputs$ = new ImmerBehaviorSubject>>( + initials() ?? {}, + ); + } + + get inputs() { + return this.inputs$.value; + } + + public readonly _fields = new Map(); + + *fields( + typedef: T, + value = this.inputs$.value, + path: any[] = [], + ): Iterable { + for (const [nameOrIdx, _, t] of typedef.entries(value, EmptyContext)) { + // skip symbol + if (nameOrIdx === SymbolRecordKey) { + continue; + } + + if (t.type === "never") { + continue; + } + + const p = [...path, nameOrIdx]; + + const name = Field.stringify(p); + + let f = this._fields.get(name); + if (!f) { + f = new Field(this, t, p); + this._fields.set(name, f); + } + + yield f; } + } - static label(label: string): MetaBuilder { - return createMetaBuilder({label}); - } - - public readonly inputs$: ImmerBehaviorSubject>>; + submit = () => { + const values = {}; - constructor( - public typedef: T, - initials: () => Partial>, - ) { - super(); - this.inputs$ = new ImmerBehaviorSubject>>( - initials() ?? {}, - ); - } - - get inputs() { - return this.inputs$.value; - } + let hasError = false; - public readonly _fields = new Map(); + for (const [name, f] of this._fields) { + f.blur(); - * fields( - typedef: T, - value = this.inputs$.value, - path: any[] = [], - ): Iterable { - for (const [nameOrIdx, _, t] of typedef.entries(value, EmptyContext)) { - // skip symbol - if (nameOrIdx === SymbolRecordKey) { - continue; - } + const value = f.input; - if (t.type === "never") { - continue; - } + const err = f.validate(value); + if (err) { + f.next((state) => { + state.error = err; + }); + } + if (f.value.error) { + hasError = true; + continue; + } - const p = [...path, nameOrIdx]; + if (!isUndefined(value)) { + set(values, name, value); + } + } - const name = Field.stringify(p); + if (hasError) { + return; + } - let f = this._fields.get(name); - if (!f) { - f = new Field(this, t, p); - this._fields.set(name, f); - } + this.next(values); + }; - yield f; - } + setErrors = (errors: Record) => { + for (const name in errors) { + for (const [_, f] of this._fields) { + f.next((state) => { + state.error = errors[name]; + }); + } } - - submit = () => { - const values = {}; - - let hasError = false; - - for (const [name, f] of this._fields) { - f.blur(); - - const value = f.input; - - const err = f.validate(value); - if (err) { - f.next((state) => { - state.error = err; - }); - } - - if (f.value.error) { - hasError = true; - continue; - } - - if (!isUndefined(value)) { - set(values, name, value); - } - } - - if (hasError) { - return; - } - - this.next(values); - }; - - setErrors = (errors: Record) => { - for (const name in errors) { - for (const [_, f] of this._fields) { - f.next((state) => { - state.error = errors[name]; - }); - } - } - }; + }; } export interface InputComponentProps { - name: string; - readOnly?: boolean; - focus?: boolean; - onBlur?: () => void; - onFocus?: () => void; - value?: T; - onValueChange?: (v: T) => void; + name: string; + readOnly?: boolean; + focus?: boolean; + onBlur?: () => void; + onFocus?: () => void; + value?: T; + onValueChange?: (v: T) => void; } export interface FieldMeta { - label?: string; - readOnlyWhenInitialExist?: boolean; - input?: Component>; - valueDisplay?: (props: InputComponentProps) => JSX.Element | string; + label?: string; + readOnlyWhenInitialExist?: boolean; + input?: Component>; + valueDisplay?: (props: InputComponentProps) => JSX.Element | string; } export interface FieldState { - initial?: any; - focus?: boolean; - visited?: boolean; - touched?: boolean; - dirty?: boolean; - error?: string[] | null; + initial?: any; + focus?: boolean; + visited?: boolean; + touched?: boolean; + dirty?: boolean; + error?: string[] | null; } export class Field extends ImmerBehaviorSubject { - static defaultValue = (def: AnyType) => { - try { - return def.create(undefined); - } catch (e) { - return; - } - }; - - constructor( - public readonly form$: FormData, - public readonly typedef: AnyType, - public readonly path: Array, - public readonly name = Field.stringify(path), - ) { - super({ - initial: get(form$.inputs$.value, name, Field.defaultValue(typedef)), - }); + static defaultValue = (def: AnyType) => { + try { + return def.create(undefined); + } catch (e) { + return; } - - get input() { - return get(this.form$.inputs$.value, this.name); + }; + + constructor( + public readonly form$: FormData, + public readonly typedef: AnyType, + public readonly path: Array, + public readonly name = Field.stringify(path), + ) { + super({ + initial: get(form$.inputs$.value, name, Field.defaultValue(typedef)), + }); + } + + get input() { + return get(this.form$.inputs$.value, this.name); + } + + get meta(): FieldMeta { + return this.typedef.meta; + } + + private _optional?: boolean; + + get optional() { + if (typeof this._optional === "undefined") { + this._optional = !this.validate(undefined); } - - get meta(): FieldMeta { - return this.typedef.meta; + return this._optional; + } + + get label() { + return this.meta?.label ?? this.name; + } + + private _input$?: Observable; + get input$(): Observable { + if (typeof this._input$ === "undefined") { + this._input$ = rx( + this.form$.inputs$, + map((v) => get(v, this.name)), + distinctUntilChanged(), + ); } - - private _optional?: boolean; - - get optional() { - if (typeof this._optional === "undefined") { - this._optional = !this.validate(undefined); - } - return this._optional; + return this._input$; + } + + focus = () => { + this.next((state) => { + state.focus = true; + state.visited = true; + }); + }; + + blur = () => { + this.next((state: FieldState) => { + state.focus = false; + state.touched = true; + }); + }; + + reset = () => { + this.form$.inputs$.next((inputs) => { + set(inputs, this.name, this.value.initial); + }); + this.next({ initial: this.value.initial }); + }; + + update = (v: any) => { + this.form$.inputs$.next((inputs) => { + set(inputs, this.name, v); + }); + + this.next((state) => { + state.dirty = v !== state.initial; + state.error = this.validate(v) ?? null; + }); + }; + + validate(value: any): string[] | undefined { + const v = + this.typedef.type === "array" && !isUndefined(value) + ? value.filter((v: any) => !isUndefined(v)) + : value; + + const [err] = this.typedef.validate(v); + + if (!err) { + return; } - get label() { - return this.meta?.label ?? this.name; - } + const failures = err.failures().filter((v) => v.type !== "never"); - private _input$?: Observable; - get input$(): Observable { - if (typeof this._input$ === "undefined") { - this._input$ = rx( - this.form$.inputs$, - map((v) => get(v, this.name)), - distinctUntilChanged(), - ); - } - return this._input$; + if (failures.length === 0) { + // FIXME + return; } - focus = () => { - this.next((state) => { - state.focus = true; - state.visited = true; - }); - }; - - blur = () => { - this.next((state: FieldState) => { - state.focus = false; - state.touched = true; - }); - }; - - reset = () => { - this.form$.inputs$.next((inputs) => { - set(inputs, this.name, this.value.initial); - }); - this.next({initial: this.value.initial}); - }; - - update = (v: any) => { - this.form$.inputs$.next((inputs) => { - set(inputs, this.name, v); - }); - - this.next((state) => { - state.dirty = v !== state.initial; - state.error = this.validate(v) ?? null; - }); - }; - - validate(value: any): string[] | undefined { - const v = - this.typedef.type === "array" && !isUndefined(value) - ? value.filter((v: any) => !isUndefined(v)) - : value; + return failures.map((f) => { + if (f.value === undefined) { + return "字段不能为空"; + } + return f.message; + }); + } - const [err] = this.typedef.validate(v); + static stringify(path: Array) { + let p = ""; - if (!err) { - return; - } + for (const v of path) { + if (typeof v === "number") { + p += `[${v}]`; + continue; + } - const failures = err.failures().filter((v) => v.type !== "never"); - - if (failures.length === 0) { - // FIXME - return; - } - - return failures.map((f) => { - if (f.value === undefined) { - return "字段不能为空"; - } - return f.message; - }); + p += p ? `.${v}` : v; } - static stringify(path: Array) { - let p = ""; - - for (const v of path) { - if (typeof v === "number") { - p += `[${v}]`; - continue; - } + return p; + } - p += p ? `.${v}` : v; - } - - return p; - } - - destroy() { - this.form$._fields.delete(this.name); - } + destroy() { + this.form$._fields.delete(this.name); + } } diff --git a/nodepkg/vueformdata/src/__tests__/index.spec.ts b/nodepkg/vueformdata/src/__tests__/index.spec.ts index 499b2a34..06380583 100644 --- a/nodepkg/vueformdata/src/__tests__/index.spec.ts +++ b/nodepkg/vueformdata/src/__tests__/index.spec.ts @@ -3,74 +3,74 @@ import { t } from "@innoai-tech/vuekit"; import { FormData } from "../FormData"; enum EnvType { - DEV = "DEV", - ONLINE = "ONLINE", + DEV = "DEV", + ONLINE = "ONLINE", } enum NetType { - AIRGAP = "AIRGAP", - DIRECT = "DIRECT", + AIRGAP = "AIRGAP", + DIRECT = "DIRECT", } const schema = t.intersection( - t.object({ - name: t - .string() - .use( - t.pattern( - /[a-z][a-z0-9-]+/, - "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", - ), - FormData.label("名称").readOnlyWhenInitialExist(), - ), - desc: t.string().optional().use(FormData.label("描述")), - envType: t.nativeEnum(EnvType).use(FormData.label("环境类型")), - }), - t - .discriminatorMapping("netType", { - [NetType.AIRGAP]: t.object({}), - [NetType.DIRECT]: t.object({ - endpoint: t.string().use(FormData.label("集群访问地址")), - }), - }) - .use(FormData.label("网络类型")), + t.object({ + name: t + .string() + .use( + t.pattern( + /[a-z][a-z0-9-]+/, + "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", + ), + FormData.label("名称").readOnlyWhenInitialExist(), + ), + desc: t.string().optional().use(FormData.label("描述")), + envType: t.nativeEnum(EnvType).use(FormData.label("环境类型")), + }), + t + .discriminatorMapping("netType", { + [NetType.AIRGAP]: t.object({}), + [NetType.DIRECT]: t.object({ + endpoint: t.string().use(FormData.label("集群访问地址")), + }), + }) + .use(FormData.label("网络类型")), ); describe("FormData", () => { - const fd = FormData.of(schema, {}); + const fd = FormData.of(schema, {}); - it("should initial with correct fields", () => { - const fields = [...fd.fields(fd.typedef)]; - expect(fields.length).toBe(4); - }); + it("should initial with correct fields", () => { + const fields = [...fd.fields(fd.typedef)]; + expect(fields.length).toBe(4); + }); - it("should update value when field value change", () => { - const fields = [...fd.fields(fd.typedef)]; + it("should update value when field value change", () => { + const fields = [...fd.fields(fd.typedef)]; - fields[0]?.update(undefined); - expect(fields[0]?.value.error).toEqual(["字段不能为空"]); + fields[0]?.update(undefined); + expect(fields[0]?.value.error).toEqual(["字段不能为空"]); - fields[0]?.update("1"); - expect(fields[0]?.value.error).toEqual([ - "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", - ]); + fields[0]?.update("1"); + expect(fields[0]?.value.error).toEqual([ + "只能包含小写字符,数字与短横 -, 且必须由小写字符开头", + ]); - fields[0]?.update("test"); + fields[0]?.update("test"); - expect(fd.inputs).toEqual({ name: "test" }); - }); + expect(fd.inputs).toEqual({ name: "test" }); + }); - it("should render condition field", () => { - for (const field of fd.fields(fd.typedef)) { - if (field.name === "netType") { - field.update(NetType.DIRECT); - } - } + it("should render condition field", () => { + for (const field of fd.fields(fd.typedef)) { + if (field.name === "netType") { + field.update(NetType.DIRECT); + } + } - const fields = [...fd.fields(fd.typedef)]; + const fields = [...fd.fields(fd.typedef)]; - console.log(fields.map((f) => `${f.name} ${f.meta?.label}`)); + console.log(fields.map((f) => `${f.name} ${f.meta?.label}`)); - expect(fields.length).toBe(5); - }); + expect(fields.length).toBe(5); + }); }); diff --git a/nodepkg/vueformdata/tsconfig.monobundle.json b/nodepkg/vueformdata/tsconfig.monobundle.json index 21023dd8..3170e721 100644 --- a/nodepkg/vueformdata/tsconfig.monobundle.json +++ b/nodepkg/vueformdata/tsconfig.monobundle.json @@ -1,6 +1,6 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - } + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + } } diff --git a/nodepkg/vuekit/package.json b/nodepkg/vuekit/package.json index e5fa6434..d4e25932 100644 --- a/nodepkg/vuekit/package.json +++ b/nodepkg/vuekit/package.json @@ -15,7 +15,7 @@ "immer": "^10.0.3", "rxjs": "^7.8.1", "vue": "v3.4.19", - "vue-router": "^4.2.5" + "vue-router": "^4.3.0" }, "peerDependencies": {}, "exports": { @@ -58,8 +58,7 @@ "directory": "nodepkg/vuekit" }, "scripts": { - "test": "bun test .", - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodepkg/vuekit/src/OverridableComponent.ts b/nodepkg/vuekit/src/OverridableComponent.ts index 82739c36..ac3c6911 100644 --- a/nodepkg/vuekit/src/OverridableComponent.ts +++ b/nodepkg/vuekit/src/OverridableComponent.ts @@ -1,40 +1,39 @@ import type { Component, VElementType } from "./vue"; export interface OverridableTypeMap { - props: {}; - defaultComponent: VElementType; + props: {}; + defaultComponent: VElementType; } type BaseProps = M["props"]; type DistributiveOmit = T extends any - ? Omit - : never; + ? Omit + : never; type OverrideProps< - M extends OverridableTypeMap, - C extends VElementType, + M extends OverridableTypeMap, + C extends VElementType, > = BaseProps & DistributiveOmit, keyof BaseProps>; type DefaultComponentProps = BaseProps & - DistributiveOmit, keyof BaseProps>; + DistributiveOmit, keyof BaseProps>; -export type ComponentProps = T extends Component< - infer P -> - ? P - : T extends keyof JSX.IntrinsicElements - ? JSX.IntrinsicElements[T] - : never; +export type ComponentProps = + T extends Component + ? P + : T extends keyof JSX.IntrinsicElements + ? JSX.IntrinsicElements[T] + : never; export interface OverridableComponent< - M extends OverridableTypeMap, - P extends Record = DefaultComponentProps, + M extends OverridableTypeMap, + P extends Record = DefaultComponentProps, > extends Component

{ - ( - props: { component: C } & OverrideProps, - ctx: any, - ): any; + ( + props: { component: C } & OverrideProps, + ctx: any, + ): any; - (props: P, ctx: any): any; + (props: P, ctx: any): any; } diff --git a/nodepkg/vuekit/src/Provider.tsx b/nodepkg/vuekit/src/Provider.tsx index fdc677aa..0114b0f8 100644 --- a/nodepkg/vuekit/src/Provider.tsx +++ b/nodepkg/vuekit/src/Provider.tsx @@ -3,123 +3,123 @@ import { type AnyType, t } from "@innoai-tech/typedef"; import { type ComponentOptions, component } from "./component"; import { ext } from "./ext"; import { - type Component, - type InternalPropsOf, - type PublicPropsOf, - type VNodeChild, - inject, - provide, + type Component, + type InternalPropsOf, + type PublicPropsOf, + type VNodeChild, + inject, + provide, } from "./vue.ts"; export type ProviderComponent = Component< - P & { - $default?: VNodeChild; - } + P & { + $default?: VNodeChild; + } > & { use: () => Context }; export type CreateFunction< - Context extends object, - PropTypes extends Record, + Context extends object, + PropTypes extends Record, > = (props: InternalPropsOf) => Context; export function createProvider( - create: CreateFunction, - options?: ComponentOptions, + create: CreateFunction, + options?: ComponentOptions, ): ProviderComponent; export function createProvider< - Context extends object, - PropTypes extends Record, + Context extends object, + PropTypes extends Record, >( - propTypes: PropTypes, - create: CreateFunction, - options?: ComponentOptions, + propTypes: PropTypes, + create: CreateFunction, + options?: ComponentOptions, ): ProviderComponent>; export function createProvider< - Context extends object, - PropTypes extends Record, + Context extends object, + PropTypes extends Record, >( - propTypesOrCreate: PropTypes | CreateFunction, - createOrOptions?: CreateFunction | ComponentOptions, - options?: ComponentOptions, + propTypesOrCreate: PropTypes | CreateFunction, + createOrOptions?: CreateFunction | ComponentOptions, + options?: ComponentOptions, ): ProviderComponent> { - const finalPropTypes = ( - isPlainObject(propTypesOrCreate) ? propTypesOrCreate : {} - ) as Record; - const finalCreate = ( - isFunction(propTypesOrCreate) ? propTypesOrCreate : createOrOptions - ) as CreateFunction; - const finalOptions = (options ?? createOrOptions ?? {}) as ComponentOptions; + const finalPropTypes = ( + isPlainObject(propTypesOrCreate) ? propTypesOrCreate : {} + ) as Record; + const finalCreate = ( + isFunction(propTypesOrCreate) ? propTypesOrCreate : createOrOptions + ) as CreateFunction; + const finalOptions = (options ?? createOrOptions ?? {}) as ComponentOptions; - const key = Symbol(finalOptions.name ?? ""); + const key = Symbol(finalOptions.name ?? ""); - if (isEmpty(finalPropTypes)) { - let _default: any; + if (isEmpty(finalPropTypes)) { + let _default: any; - const getDefaults = () => { - if (typeof _default === "undefined") { - _default = finalCreate({}); - } - return _default; - }; + const getDefaults = () => { + if (typeof _default === "undefined") { + _default = finalCreate({}); + } + return _default; + }; - const Provider = component( - { - value: t.custom().optional(), - $default: t.custom().optional(), - }, - (props, { slots }) => { - provide(key, props.value ?? getDefaults()); + const Provider = component( + { + value: t.custom().optional(), + $default: t.custom().optional(), + }, + (props, { slots }) => { + provide(key, props.value ?? getDefaults()); - return () => { - return slots.default?.(); - }; - }, - { - ...finalOptions, - name: `Provide(${finalOptions.name ?? ""})`, - }, - ); + return () => { + return slots.default?.(); + }; + }, + { + ...finalOptions, + name: `Provide(${finalOptions.name ?? ""})`, + }, + ); - return ext(Provider as any, { - use: () => { - return inject(key, getDefaults, true) as Context; - }, - }); - } + return ext(Provider as any, { + use: () => { + return inject(key, getDefaults, true) as Context; + }, + }); + } - const propsSchema = t.object(finalPropTypes); - const getDefaultProps = () => propsSchema.create({}); + const propsSchema = t.object(finalPropTypes); + const getDefaultProps = () => propsSchema.create({}); - let _default: any; + let _default: any; - const getDefaults = () => { - if (typeof _default === "undefined") { - _default = finalCreate(getDefaultProps() as any); - } - return _default; - }; + const getDefaults = () => { + if (typeof _default === "undefined") { + _default = finalCreate(getDefaultProps() as any); + } + return _default; + }; - const Provider = component( - { - ...finalPropTypes, - $default: t.custom().optional(), - }, - (props, { slots }) => { - provide(key, finalCreate(props)); + const Provider = component( + { + ...finalPropTypes, + $default: t.custom().optional(), + }, + (props, { slots }) => { + provide(key, finalCreate(props)); - return () => { - return slots.default?.(); - }; - }, - { - ...finalOptions, - name: `Provide(${finalOptions.name ?? ""})`, - }, - ); + return () => { + return slots.default?.(); + }; + }, + { + ...finalOptions, + name: `Provide(${finalOptions.name ?? ""})`, + }, + ); - return ext(Provider as any, { - use: () => { - return inject(key, getDefaults, true) as Context; - }, - }); + return ext(Provider as any, { + use: () => { + return inject(key, getDefaults, true) as Context; + }, + }); } diff --git a/nodepkg/vuekit/src/__tests__/Provider.spec.tsx b/nodepkg/vuekit/src/__tests__/Provider.spec.tsx index c3803d24..cabd8a3d 100644 --- a/nodepkg/vuekit/src/__tests__/Provider.spec.tsx +++ b/nodepkg/vuekit/src/__tests__/Provider.spec.tsx @@ -5,172 +5,172 @@ import { ref, watch } from "vue"; import { component, createProvider } from "../index"; describe("Provider", () => { - describe("static", () => { - const P = createProvider(() => ({ context: "default" })); - const C = component((_, _1) => { - const p = P.use(); - return () =>

{p.context}
; - }); - - test("could use default value", () => { - const wrapper = mount(C, {}); - expect(wrapper.text()).toContain("default"); - wrapper.unmount(); - }); - - test("could inject custom value", () => { - const With = component((_, _1) => { - return () => ( -

- -

- ); - }); - - const wrapper = mount(With, {}); - expect(wrapper.text()).toContain("injected"); - - wrapper.unmount(); - }); - }); - - describe("reactive", () => { - const P = createProvider(() => ref({ context: "default" })); - const C = component((_, _1) => { - const p = P.use(); - return () =>
{p.value.context}
; - }); - - test("could use default value", () => { - const wrapper = mount(C, {}); - expect(wrapper.text()).toContain("default"); - wrapper.unmount(); - }); - - test("could inject custom value", async () => { - const With = component( - { - context: t.string().optional().default("injected"), - }, - (props, _) => { - const r = ref({ context: props.context }); - - watch( - () => props.context, - (context) => { - r.value = { context: context }; - }, - ); - - return () => ( -

- -

- ); - }, - ); - - const wrapper = mount(With, {}); - expect(wrapper.text()).toContain("injected"); - await wrapper.setProps({ - context: "injected2", - }); - expect(wrapper.text()).toContain("injected2"); - - wrapper.unmount(); - }); - }); - - describe("factory", () => { - const P = createProvider( - { - input: t.string().default("default"), - }, - (props) => ({ context: `${props.input}` }), - ); - - const C = component((_, _1) => { - const p = P.use(); - return () =>
{p.context}
; - }); - - test("could use default value", () => { - const wrapper = mount(C, {}); - expect(wrapper.text()).toContain("default"); - wrapper.unmount(); - }); - - test("could inject custom value", async () => { - const With = component((_, _1) => { - return () => ( -

- -

- ); - }); - - const wrapper = mount(With, {}); - expect(wrapper.text()).toContain("injected"); - - wrapper.unmount(); - }); - }); - - describe("reactive factory", () => { - const P = createProvider( - { - input: t.string().default("default"), - }, - (props) => { - const r = ref(props.input); - - watch( - () => props.input, - (input) => { - r.value = input; - }, - ); - - return r; - }, - ); - - const C = component((_, _1) => { - const p = P.use(); - return () =>
{p.value}
; - }); - - test("could use default value", () => { - const wrapper = mount(C, {}); - expect(wrapper.text()).toContain("default"); - wrapper.unmount(); - }); - - test("could inject custom value", async () => { - const With = component( - { - input: t.string().optional().default("injected"), - }, - (props, _) => { - return () => ( -

- -

- ); - }, - ); - - const wrapper = mount(With, {}); - expect(wrapper.text()).toContain("injected"); - - for (let i = 0; i < 2; i++) { - await wrapper.setProps({ - input: "injected2", - }); - expect(wrapper.text()).toContain("injected2"); - } - - wrapper.unmount(); - }); - }); + describe("static", () => { + const P = createProvider(() => ({ context: "default" })); + const C = component((_, _1) => { + const p = P.use(); + return () =>
{p.context}
; + }); + + test("could use default value", () => { + const wrapper = mount(C, {}); + expect(wrapper.text()).toContain("default"); + wrapper.unmount(); + }); + + test("could inject custom value", () => { + const With = component((_, _1) => { + return () => ( +

+ +

+ ); + }); + + const wrapper = mount(With, {}); + expect(wrapper.text()).toContain("injected"); + + wrapper.unmount(); + }); + }); + + describe("reactive", () => { + const P = createProvider(() => ref({ context: "default" })); + const C = component((_, _1) => { + const p = P.use(); + return () =>
{p.value.context}
; + }); + + test("could use default value", () => { + const wrapper = mount(C, {}); + expect(wrapper.text()).toContain("default"); + wrapper.unmount(); + }); + + test("could inject custom value", async () => { + const With = component( + { + context: t.string().optional().default("injected"), + }, + (props, _) => { + const r = ref({ context: props.context }); + + watch( + () => props.context, + (context) => { + r.value = { context: context }; + }, + ); + + return () => ( +

+ +

+ ); + }, + ); + + const wrapper = mount(With, {}); + expect(wrapper.text()).toContain("injected"); + await wrapper.setProps({ + context: "injected2", + }); + expect(wrapper.text()).toContain("injected2"); + + wrapper.unmount(); + }); + }); + + describe("factory", () => { + const P = createProvider( + { + input: t.string().default("default"), + }, + (props) => ({ context: `${props.input}` }), + ); + + const C = component((_, _1) => { + const p = P.use(); + return () =>
{p.context}
; + }); + + test("could use default value", () => { + const wrapper = mount(C, {}); + expect(wrapper.text()).toContain("default"); + wrapper.unmount(); + }); + + test("could inject custom value", async () => { + const With = component((_, _1) => { + return () => ( +

+ +

+ ); + }); + + const wrapper = mount(With, {}); + expect(wrapper.text()).toContain("injected"); + + wrapper.unmount(); + }); + }); + + describe("reactive factory", () => { + const P = createProvider( + { + input: t.string().default("default"), + }, + (props) => { + const r = ref(props.input); + + watch( + () => props.input, + (input) => { + r.value = input; + }, + ); + + return r; + }, + ); + + const C = component((_, _1) => { + const p = P.use(); + return () =>
{p.value}
; + }); + + test("could use default value", () => { + const wrapper = mount(C, {}); + expect(wrapper.text()).toContain("default"); + wrapper.unmount(); + }); + + test("could inject custom value", async () => { + const With = component( + { + input: t.string().optional().default("injected"), + }, + (props, _) => { + return () => ( +

+ +

+ ); + }, + ); + + const wrapper = mount(With, {}); + expect(wrapper.text()).toContain("injected"); + + for (let i = 0; i < 2; i++) { + await wrapper.setProps({ + input: "injected2", + }); + expect(wrapper.text()).toContain("injected2"); + } + + wrapper.unmount(); + }); + }); }); diff --git a/nodepkg/vuekit/src/__tests__/Type.spec.tsx b/nodepkg/vuekit/src/__tests__/Type.spec.tsx index 5c48431c..91ad4b51 100644 --- a/nodepkg/vuekit/src/__tests__/Type.spec.tsx +++ b/nodepkg/vuekit/src/__tests__/Type.spec.tsx @@ -4,67 +4,67 @@ import { mount } from "@vue/test-utils"; import { type VNodeChild, component } from "../index"; describe("Type", () => { - test("render with optional props", () => { - enum InputType { - text = "text", - select = "text", - } + test("render with optional props", () => { + enum InputType { + text = "text", + select = "text", + } - const propTypes = { - input: t.number().optional(), - type: t.nativeEnum(InputType), - inputWithDefault: t.number().optional().default(1), - onDidSetup: t.custom<() => void>(), - onDidSetupWith: t.custom<(v: string) => void>(), - }; + const propTypes = { + input: t.number().optional(), + type: t.nativeEnum(InputType), + inputWithDefault: t.number().optional().default(1), + onDidSetup: t.custom<() => void>(), + onDidSetupWith: t.custom<(v: string) => void>(), + }; - const C = component(propTypes, (props, { emit }) => { - emit("did-setup"); - emit("did-setup-with", "1"); + const C = component(propTypes, (props, { emit }) => { + emit("did-setup"); + emit("did-setup-with", "1"); - return () => ( -
- input: {props.input ?? 1} - type: {props.type === InputType.select} - inputWithDefault: {props.inputWithDefault * 2} -
- ); - }); + return () => ( +
+ input: {props.input ?? 1} + type: {props.type === InputType.select} + inputWithDefault: {props.inputWithDefault * 2} +
+ ); + }); - const wrapper = mount(C, { - props: { - type: InputType.text, - }, - }); + const wrapper = mount(C, { + props: { + type: InputType.text, + }, + }); - expect(wrapper.text()).toContain("2"); + expect(wrapper.text()).toContain("2"); - wrapper.unmount(); - }); + wrapper.unmount(); + }); - test("render with slots", () => { - const propTypes = { - $default: t.custom<(v: number) => VNodeChild>(), - $optional: t.custom().optional(), - }; + test("render with slots", () => { + const propTypes = { + $default: t.custom<(v: number) => VNodeChild>(), + $optional: t.custom().optional(), + }; - const C = component(propTypes, (_, { slots }) => { - return () => ( -
- {slots.optional?.()} - {slots.default(1)} -
- ); - }); + const C = component(propTypes, (_, { slots }) => { + return () => ( +
+ {slots.optional?.()} + {slots.default(1)} +
+ ); + }); - const wrapper = mount(C, { - slots: { - default: () => "1", - }, - }); + const wrapper = mount(C, { + slots: { + default: () => "1", + }, + }); - expect(wrapper.text()).toContain("1"); + expect(wrapper.text()).toContain("1"); - wrapper.unmount(); - }); + wrapper.unmount(); + }); }); diff --git a/nodepkg/vuekit/src/component.ts b/nodepkg/vuekit/src/component.ts index 8f1f1a69..2ed1ac55 100644 --- a/nodepkg/vuekit/src/component.ts +++ b/nodepkg/vuekit/src/component.ts @@ -1,88 +1,87 @@ -import {isFunction, kebabCase, partition} from "@innoai-tech/lodash"; -import {Fragment as OriginFragment} from "vue"; +import { isFunction, kebabCase, partition } from "@innoai-tech/lodash"; +import { Fragment as OriginFragment } from "vue"; import type { - Component, - PublicPropsOf, - SetupFunction, - WithDefaultSlot, + Component, + PublicPropsOf, + SetupFunction, + WithDefaultSlot, } from "./vue"; -import {type AnyType} from "@innoai-tech/typedef"; +import { type AnyType } from "@innoai-tech/typedef"; export interface ComponentOptions { - name?: string; - inheritAttrs?: boolean; + name?: string; + inheritAttrs?: boolean; - [K: string]: any + [K: string]: any; } export const Fragment: Component = OriginFragment as any; export function component( - setup: SetupFunction<{}>, - options?: ComponentOptions, + setup: SetupFunction<{}>, + options?: ComponentOptions, ): Component<{}>; export function component>( - propTypes: PropTypes, - setup: SetupFunction, - options?: ComponentOptions, + propTypes: PropTypes, + setup: SetupFunction, + options?: ComponentOptions, ): Component>; export function component>( - propTypesOrSetup: PropTypes | SetupFunction, - setupOrOptions?: SetupFunction | ComponentOptions, - options: ComponentOptions = {}, + propTypesOrSetup: PropTypes | SetupFunction, + setupOrOptions?: SetupFunction | ComponentOptions, + options: ComponentOptions = {}, ): Component> { - const finalOptions = (options ?? setupOrOptions) as ComponentOptions; - const finalSetup = (setupOrOptions ?? propTypesOrSetup) as SetupFunction; - const finalPropTypes = ( - isFunction(propTypesOrSetup) ? {} : propTypesOrSetup - ) as Record; + const finalOptions = (options ?? setupOrOptions) as ComponentOptions; + const finalSetup = (setupOrOptions ?? propTypesOrSetup) as SetupFunction; + const finalPropTypes = ( + isFunction(propTypesOrSetup) ? {} : propTypesOrSetup + ) as Record; - const [emits, props] = partition(Object.keys(finalPropTypes), (v: string) => - /^on[A-Z]/.test(v), - ); + const [emits, props] = partition(Object.keys(finalPropTypes), (v: string) => + /^on[A-Z]/.test(v), + ); - const emitsAndProps = { - emits: emits.map((v) => kebabCase(v.slice("on".length))), - props: props - .filter((p) => !/^[$]/.test(p)) - .reduce((ret, prop) => { - // biome-ignore lint/style/noNonNullAssertion: - const d = finalPropTypes[prop]!; + const emitsAndProps = { + emits: emits.map((v) => kebabCase(v.slice("on".length))), + props: props + .filter((p) => !/^[$]/.test(p)) + .reduce((ret, prop) => { + // biome-ignore lint/style/noNonNullAssertion: + const d = finalPropTypes[prop]!; - return { - // biome-ignore lint/performance/noAccumulatingSpread: - ...ret, - [prop]: { - default: () => { - try { - return d.create(undefined); - } catch (e) { - } - return; - }, - validator: (value: any) => { - return d.validate(value); - }, - }, - }; - }, {}), - }; + return { + // biome-ignore lint/performance/noAccumulatingSpread: + ...ret, + [prop]: { + default: () => { + try { + return d.create(undefined); + } catch (e) {} + return; + }, + validator: (value: any) => { + return d.validate(value); + }, + }, + }; + }, {}), + }; - const {name, inheritAttrs, ...others} = finalOptions + const { name, inheritAttrs, ...others } = finalOptions; - return { - ...others, - get name() { - return this.displayName ?? name; - }, - set name(n: string) { - finalOptions.name = n; - }, - setup: (props: any, ctx: any) => finalSetup(props, ctx), - emits: emitsAndProps.emits, - props: emitsAndProps.props, - inheritAttrs: inheritAttrs, - propTypes: finalPropTypes, - } as any; + return { + ...others, + get name() { + return this.displayName ?? name; + }, + set name(n: string) { + finalOptions.name = n; + }, + setup: (props: any, ctx: any) => finalSetup(props, ctx), + emits: emitsAndProps.emits, + props: emitsAndProps.props, + inheritAttrs: inheritAttrs, + propTypes: finalPropTypes, + } as any; } diff --git a/nodepkg/vuekit/src/ext.ts b/nodepkg/vuekit/src/ext.ts index f84a2974..13e9af7b 100644 --- a/nodepkg/vuekit/src/ext.ts +++ b/nodepkg/vuekit/src/ext.ts @@ -1,7 +1,7 @@ export const ext = (target: T, ext: E) => { - return new Proxy(target, { - get(target: T, p: string | symbol): any { - return (ext as any)[p] ?? (target as any)[p]; - }, - }) as T & E; + return new Proxy(target, { + get(target: T, p: string | symbol): any { + return (ext as any)[p] ?? (target as any)[p]; + }, + }) as T & E; }; diff --git a/nodepkg/vuekit/src/reactive/Immer.ts b/nodepkg/vuekit/src/reactive/Immer.ts index 061a9809..2f171129 100644 --- a/nodepkg/vuekit/src/reactive/Immer.ts +++ b/nodepkg/vuekit/src/reactive/Immer.ts @@ -6,19 +6,19 @@ import { type Observable } from "rxjs"; export { produce }; export interface ImmerSubject extends Observable { - next(valueOrUpdater: T | ((v: T) => void)): void; + next(valueOrUpdater: T | ((v: T) => void)): void; } export class ImmerBehaviorSubject - extends BehaviorSubject - implements ImmerSubject + extends BehaviorSubject + implements ImmerSubject { - override next(valueOrUpdater: T | ((v: T) => void)) { - const v = isFunction(valueOrUpdater) - ? produce(this.value, valueOrUpdater) - : valueOrUpdater; - if (!Object.is(v, this.value)) { - super.next(v); - } - } + override next(valueOrUpdater: T | ((v: T) => void)) { + const v = isFunction(valueOrUpdater) + ? produce(this.value, valueOrUpdater) + : valueOrUpdater; + if (!Object.is(v, this.value)) { + super.next(v); + } + } } diff --git a/nodepkg/vuekit/src/reactive/RxSlot.tsx b/nodepkg/vuekit/src/reactive/RxSlot.tsx index 77f67d00..517670f4 100644 --- a/nodepkg/vuekit/src/reactive/RxSlot.tsx +++ b/nodepkg/vuekit/src/reactive/RxSlot.tsx @@ -6,15 +6,15 @@ import { rx } from "./rx"; import { subscribeUntilUnmount } from "./subscribe"; export function render(renderFunc: (value: T) => VNodeChild) { - return (input$: Observable): JSX.Element => { - return ( - ((v) => () => renderFunc(v)))} - > - {{}} - - ); - }; + return (input$: Observable): JSX.Element => { + return ( + ((v) => () => renderFunc(v)))} + > + {{}} + + ); + }; } /** @@ -22,26 +22,26 @@ export function render(renderFunc: (value: T) => VNodeChild) { * {{}} */ const RxSlot = component( - { - elem$: t.custom>(), - $default: t.custom<{}>(), - }, - (props, _) => { - const r = shallowRef(null); + { + elem$: t.custom>(), + $default: t.custom<{}>(), + }, + (props, _) => { + const r = shallowRef(null); - rx( - props.elem$, - tap((renderFunc) => { - r.value = renderFunc; - }), - subscribeUntilUnmount(), - ); + rx( + props.elem$, + tap((renderFunc) => { + r.value = renderFunc; + }), + subscribeUntilUnmount(), + ); - return () => { - return r.value?.(); - }; - }, - { - name: "RxSlot", - }, + return () => { + return r.value?.(); + }; + }, + { + name: "RxSlot", + }, ); diff --git a/nodepkg/vuekit/src/reactive/__tests__/index.spec.tsx b/nodepkg/vuekit/src/reactive/__tests__/index.spec.tsx index 8b4d5c86..973434e1 100644 --- a/nodepkg/vuekit/src/reactive/__tests__/index.spec.tsx +++ b/nodepkg/vuekit/src/reactive/__tests__/index.spec.tsx @@ -2,164 +2,164 @@ import { describe, expect, test } from "bun:test"; import { mount } from "@vue/test-utils"; import { filter, map, of } from "rxjs"; import { - component$, - observableRef, - rx, - subscribeUntilUnmount, - toComputed, + component$, + observableRef, + rx, + subscribeUntilUnmount, + toComputed, } from ".."; import { type VNode, component, t } from "../../index"; describe("vue reactive", () => { - test("when first render, should use the first ", () => { - const C = component(() => { - const v = rx(of(1), toComputed()); - return () =>
{v.value}
; - }); - - const wrapper = mount(C); - - expect(wrapper.text()).toContain("1"); - - wrapper.unmount(); - }); - - test("when state changed, should rerender", async () => { - const C = component(() => { - const input$ = observableRef({ - count: 1, - }); - - const ret = rx( - input$, - map(({ count }) => count * count), - toComputed(), - ); - - return () => ( -
{ - input$.next((i) => { - i.count++; - }); - }} - > - {ret.value} -
- ); - }); - - const wrapper = mount(C); - - expect(wrapper.text()).toContain("1"); - - await wrapper.find("[role=button]").trigger("click"); - - expect(wrapper.text()).toContain("4"); - - wrapper.unmount(); - }); - - test("when slots changed, should rerender", async () => { - const C = component$( - { - $input: t.custom(), - }, - (_, { slots, render }) => { - const input$ = observableRef(""); - - const inputEl = rx( - input$, - render(() =>
{slots.input?.()}
), - ); - - return rx( - input$, - render((input) => ( -
-
{input}
- {inputEl} -
- )), - ); - }, - ); - - const Wrap = component( - { - input: t.custom(), - }, - (props) => { - return () => ; - }, - ); - - const wrapper = mount(Wrap as any, { - props: { - input: 1, - }, - }); - - expect(wrapper.text()).toContain("1"); - - await wrapper.setProps({ - input: 2, - }); - - expect(wrapper.text()).toContain("2"); - }); - - test("when props changed, should rerender", async () => { - const C = component$( - { input: t.number() }, - ({ input$, input }, { render }) => { - const localInput$ = observableRef(input); - - rx(input$, subscribeUntilUnmount(localInput$.next)); - - const inputEl = rx( - localInput$, - render((v) =>
{v}
), - ); - - return rx( - input$, - filter((v) => v % 2 !== 0), - map((v) => v * v), - render((v) => ( -
-
{v}
- {inputEl} -
- )), - ); - }, - ); - - const wrapper = mount(C, { - props: { - input: 1, - }, - }); - - expect(wrapper.find("[data-role=input]").text()).toContain(`${1}`); - expect(wrapper.find("[data-role=result]").text()).toContain(`${1}`); - - for (let i = 2; i <= 4; i++) { - await wrapper.setProps({ - input: i, - }); - - expect(wrapper.find("[data-role=input]").text()).toContain(`${i}`); - - if (i % 2 !== 0) { - expect(wrapper.find("[data-role=result]").text()).toContain(`${i * i}`); - } else { - expect(wrapper.find("[data-role=result]").text()).toContain( - `${(i - 1) * (i - 1)}`, - ); - } - } - }); + test("when first render, should use the first ", () => { + const C = component(() => { + const v = rx(of(1), toComputed()); + return () =>
{v.value}
; + }); + + const wrapper = mount(C); + + expect(wrapper.text()).toContain("1"); + + wrapper.unmount(); + }); + + test("when state changed, should rerender", async () => { + const C = component(() => { + const input$ = observableRef({ + count: 1, + }); + + const ret = rx( + input$, + map(({ count }) => count * count), + toComputed(), + ); + + return () => ( +
{ + input$.next((i) => { + i.count++; + }); + }} + > + {ret.value} +
+ ); + }); + + const wrapper = mount(C); + + expect(wrapper.text()).toContain("1"); + + await wrapper.find("[role=button]").trigger("click"); + + expect(wrapper.text()).toContain("4"); + + wrapper.unmount(); + }); + + test("when slots changed, should rerender", async () => { + const C = component$( + { + $input: t.custom(), + }, + (_, { slots, render }) => { + const input$ = observableRef(""); + + const inputEl = rx( + input$, + render(() =>
{slots.input?.()}
), + ); + + return rx( + input$, + render((input) => ( +
+
{input}
+ {inputEl} +
+ )), + ); + }, + ); + + const Wrap = component( + { + input: t.custom(), + }, + (props) => { + return () => ; + }, + ); + + const wrapper = mount(Wrap as any, { + props: { + input: 1, + }, + }); + + expect(wrapper.text()).toContain("1"); + + await wrapper.setProps({ + input: 2, + }); + + expect(wrapper.text()).toContain("2"); + }); + + test("when props changed, should rerender", async () => { + const C = component$( + { input: t.number() }, + ({ input$, input }, { render }) => { + const localInput$ = observableRef(input); + + rx(input$, subscribeUntilUnmount(localInput$.next)); + + const inputEl = rx( + localInput$, + render((v) =>
{v}
), + ); + + return rx( + input$, + filter((v) => v % 2 !== 0), + map((v) => v * v), + render((v) => ( +
+
{v}
+ {inputEl} +
+ )), + ); + }, + ); + + const wrapper = mount(C, { + props: { + input: 1, + }, + }); + + expect(wrapper.find("[data-role=input]").text()).toContain(`${1}`); + expect(wrapper.find("[data-role=result]").text()).toContain(`${1}`); + + for (let i = 2; i <= 4; i++) { + await wrapper.setProps({ + input: i, + }); + + expect(wrapper.find("[data-role=input]").text()).toContain(`${i}`); + + if (i % 2 !== 0) { + expect(wrapper.find("[data-role=result]").text()).toContain(`${i * i}`); + } else { + expect(wrapper.find("[data-role=result]").text()).toContain( + `${(i - 1) * (i - 1)}`, + ); + } + } + }); }); diff --git a/nodepkg/vuekit/src/reactive/__tests__/rx.spec.ts b/nodepkg/vuekit/src/reactive/__tests__/rx.spec.ts index 3307160e..7fe88a5d 100644 --- a/nodepkg/vuekit/src/reactive/__tests__/rx.spec.ts +++ b/nodepkg/vuekit/src/reactive/__tests__/rx.spec.ts @@ -3,24 +3,24 @@ import { firstValueFrom, map, of } from "rxjs"; import { rx } from "../index"; describe("rx", () => { - it("with only observable operator", async () => { - const ret = await firstValueFrom( - rx( - of(2), - map((v) => v * v), - ), - ); + it("with only observable operator", async () => { + const ret = await firstValueFrom( + rx( + of(2), + map((v) => v * v), + ), + ); - expect(ret).toEqual(4); - }); + expect(ret).toEqual(4); + }); - it("with any operator", async () => { - const ret = await rx( - of(2), - map((v) => v * v), - firstValueFrom, - ); + it("with any operator", async () => { + const ret = await rx( + of(2), + map((v) => v * v), + firstValueFrom, + ); - expect(ret).toEqual(4); - }); + expect(ret).toEqual(4); + }); }); diff --git a/nodepkg/vuekit/src/reactive/component$.tsx b/nodepkg/vuekit/src/reactive/component$.tsx index 20074f2d..3c9bb3f3 100644 --- a/nodepkg/vuekit/src/reactive/component$.tsx +++ b/nodepkg/vuekit/src/reactive/component$.tsx @@ -3,12 +3,12 @@ import { type AnyType } from "@innoai-tech/typedef"; import type { RenderFunction } from "vue"; import { type ComponentOptions, component } from "../component"; import { - type Component, - type InternalEmitsOf, - type InternalPropsOf, - type InternalSlotsOf, - type PublicPropsOf, - type SetupContext, + type Component, + type InternalEmitsOf, + type InternalPropsOf, + type InternalSlotsOf, + type PublicPropsOf, + type SetupContext, } from "../vue"; import { render } from "./RxSlot"; import { type Observables, toObservables } from "./toObservable"; @@ -16,71 +16,71 @@ import { type Observables, toObservables } from "./toObservable"; export { render }; export type ObservablesAndProps> = - Observables & Omit>; + Observables & Omit>; export type ObservableSetupFunction> = - ( - P: ObservablesAndProps>, - ctx: SetupContext< - InternalEmitsOf, - InternalSlotsOf - > & { render: typeof render }, - ) => RenderFunction | JSX.Element | null; + ( + P: ObservablesAndProps>, + ctx: SetupContext< + InternalEmitsOf, + InternalSlotsOf + > & { render: typeof render }, + ) => RenderFunction | JSX.Element | null; export function component$( - setup: ObservableSetupFunction<{}>, - options?: ComponentOptions, + setup: ObservableSetupFunction<{}>, + options?: ComponentOptions, ): Component<{}>; export function component$>( - propTypes: PropTypes, - setup: ObservableSetupFunction, - options?: ComponentOptions, + propTypes: PropTypes, + setup: ObservableSetupFunction, + options?: ComponentOptions, ): Component>; export function component$>( - propTypesOrSetup: PropTypes | ObservableSetupFunction, - setupOrOptions?: ObservableSetupFunction | ComponentOptions, - options: ComponentOptions = {}, + propTypesOrSetup: PropTypes | ObservableSetupFunction, + setupOrOptions?: ObservableSetupFunction | ComponentOptions, + options: ComponentOptions = {}, ): Component> { - const finalOptions = (options ?? setupOrOptions) as ComponentOptions; - const finalSetup = (setupOrOptions ?? - propTypesOrSetup) as ObservableSetupFunction; + const finalOptions = (options ?? setupOrOptions) as ComponentOptions; + const finalSetup = (setupOrOptions ?? + propTypesOrSetup) as ObservableSetupFunction; - const finalPropTypes = (propTypesOrSetup ?? {}) as PropTypes; + const finalPropTypes = (propTypesOrSetup ?? {}) as PropTypes; - return component( - finalPropTypes, - (props, ctx): RenderFunction => { - const props$ = toObservables(props); + return component( + finalPropTypes, + (props, ctx): RenderFunction => { + const props$ = toObservables(props); - const p = new Proxy( - {}, - { - get(_, key: string) { - return (props as any)[key] ?? (props$ as any)[key]; - }, - }, - ) as any; + const p = new Proxy( + {}, + { + get(_, key: string) { + return (props as any)[key] ?? (props$ as any)[key]; + }, + }, + ) as any; - const c = new Proxy( - {}, - { - get(_, key: string) { - if (key === "render") { - return render; - } - return (ctx as any)[key]; - }, - }, - ) as any; + const c = new Proxy( + {}, + { + get(_, key: string) { + if (key === "render") { + return render; + } + return (ctx as any)[key]; + }, + }, + ) as any; - const renderFuncOrVNode = finalSetup(p, c); + const renderFuncOrVNode = finalSetup(p, c); - if (isFunction(renderFuncOrVNode)) { - return renderFuncOrVNode; - } + if (isFunction(renderFuncOrVNode)) { + return renderFuncOrVNode; + } - return () => renderFuncOrVNode; - }, - finalOptions, - ) as any; + return () => renderFuncOrVNode; + }, + finalOptions, + ) as any; } diff --git a/nodepkg/vuekit/src/reactive/observableRef.ts b/nodepkg/vuekit/src/reactive/observableRef.ts index af35ea3b..c83908cb 100644 --- a/nodepkg/vuekit/src/reactive/observableRef.ts +++ b/nodepkg/vuekit/src/reactive/observableRef.ts @@ -1,52 +1,52 @@ -import {isFunction} from "@innoai-tech/lodash"; -import {produce} from "immer"; -import {type Ref, customRef} from "vue"; -import {ImmerBehaviorSubject, type ImmerSubject} from "./Immer"; +import { isFunction } from "@innoai-tech/lodash"; +import { produce } from "immer"; +import { type Ref, customRef } from "vue"; +import { ImmerBehaviorSubject, type ImmerSubject } from "./Immer"; export type ObservableRef = Ref & ImmerSubject; export const observableRef = (value: T): ObservableRef => { - const store$ = new ImmerBehaviorSubject(value); + const store$ = new ImmerBehaviorSubject(value); - const ref = customRef((track, trigger) => { - return { - get() { - track(); - return store$.value; - }, - set(value: T) { - const newValue = (value as any)?.$$forwardRef ?? value; + const ref = customRef((track, trigger) => { + return { + get() { + track(); + return store$.value; + }, + set(value: T) { + const newValue = (value as any)?.$$forwardRef ?? value; - if (!Object.is(newValue, store$.value)) { - store$.next(newValue); - trigger(); - } - }, - }; - }); + if (!Object.is(newValue, store$.value)) { + store$.next(newValue); + trigger(); + } + }, + }; + }); - return new Proxy(store$, { - get(_: any, p: string | symbol): any { - if (p === "next") { - return (valueOrUpdater: ((v: T) => void) | T) => { - ref.value = isFunction(valueOrUpdater) - ? produce(store$.value, valueOrUpdater) - : valueOrUpdater; - }; - } - if (p === "value") { - return ref.value; - } - return (store$ as any)[p] ?? (ref as any)[p]; - }, + return new Proxy(store$, { + get(_: any, p: string | symbol): any { + if (p === "next") { + return (valueOrUpdater: ((v: T) => void) | T) => { + ref.value = isFunction(valueOrUpdater) + ? produce(store$.value, valueOrUpdater) + : valueOrUpdater; + }; + } + if (p === "value") { + return ref.value; + } + return (store$ as any)[p] ?? (ref as any)[p]; + }, - set(value$: any, p: string | symbol, newValue: any): boolean { - if (p === "value") { - ref.value = newValue; - return true; - } - value$[p] = newValue; - return true; - }, - }); + set(value$: any, p: string | symbol, newValue: any): boolean { + if (p === "value") { + ref.value = newValue; + return true; + } + value$[p] = newValue; + return true; + }, + }); }; diff --git a/nodepkg/vuekit/src/reactive/operators/tapEffect.ts b/nodepkg/vuekit/src/reactive/operators/tapEffect.ts index 080b946c..0b51411f 100644 --- a/nodepkg/vuekit/src/reactive/operators/tapEffect.ts +++ b/nodepkg/vuekit/src/reactive/operators/tapEffect.ts @@ -2,36 +2,36 @@ import { isArray } from "@innoai-tech/lodash"; import { tap } from "rxjs"; const equal = (a: any, b: any) => { - if (isArray(a) && isArray(b)) { - if (a.length !== b.length) { - return false; - } - for (const i in a) { - if (!Object.is(a[i], b[i])) { - return false; - } - } - return true; - } - return Object.is(a, b); + if (isArray(a) && isArray(b)) { + if (a.length !== b.length) { + return false; + } + for (const i in a) { + if (!Object.is(a[i], b[i])) { + return false; + } + } + return true; + } + return Object.is(a, b); }; export const tapEffect = ( - create: (input: T) => (() => void) | undefined, + create: (input: T) => (() => void) | undefined, ) => { - let cleanup: (() => void) | undefined = undefined; - let prevInput: T | null = null; + let cleanup: (() => void) | undefined = undefined; + let prevInput: T | null = null; - return tap({ - next: (input: T) => { - if (!equal(input, prevInput)) { - cleanup?.(); - cleanup = create(input); - prevInput = input; - } - }, - unsubscribe: () => { - cleanup?.(); - }, - }); + return tap({ + next: (input: T) => { + if (!equal(input, prevInput)) { + cleanup?.(); + cleanup = create(input); + prevInput = input; + } + }, + unsubscribe: () => { + cleanup?.(); + }, + }); }; diff --git a/nodepkg/vuekit/src/reactive/rx.ts b/nodepkg/vuekit/src/reactive/rx.ts index 50c99bb5..2a32ee04 100644 --- a/nodepkg/vuekit/src/reactive/rx.ts +++ b/nodepkg/vuekit/src/reactive/rx.ts @@ -1,99 +1,99 @@ import { - Observable, - type ObservableInput, - type UnaryFunction, - from, - pipe, + Observable, + type ObservableInput, + type UnaryFunction, + from, + pipe, } from "rxjs"; export function rx, A>( - source: S, - op1: UnaryFunction, + source: S, + op1: UnaryFunction, ): A; export function rx, A, B>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, ): B; export function rx, A, B, C>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, ): C; export function rx, A, B, C, D>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, ): D; export function rx, A, B, C, D, E>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, ): E; export function rx, A, B, C, D, E, F>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, - op6: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, ): F; export function rx, A, B, C, D, E, F, G>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, - op6: UnaryFunction, - op7: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, ): G; export function rx, A, B, C, D, E, F, G, H>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, - op6: UnaryFunction, - op7: UnaryFunction, - op8: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, ): H; export function rx, A, B, C, D, E, F, G, H, I>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, - op6: UnaryFunction, - op7: UnaryFunction, - op8: UnaryFunction, - op9: UnaryFunction, + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, + op9: UnaryFunction, ): I; export function rx, A, B, C, D, E, F, G, H, I>( - source: S, - op1: UnaryFunction, - op2: UnaryFunction, - op3: UnaryFunction, - op4: UnaryFunction, - op5: UnaryFunction, - op6: UnaryFunction, - op7: UnaryFunction, - op8: UnaryFunction, - op9: UnaryFunction, - ...operations: UnaryFunction[] + source: S, + op1: UnaryFunction, + op2: UnaryFunction, + op3: UnaryFunction, + op4: UnaryFunction, + op5: UnaryFunction, + op6: UnaryFunction, + op7: UnaryFunction, + op8: UnaryFunction, + op9: UnaryFunction, + ...operations: UnaryFunction[] ): Observable; export function rx>( - source: S, - ...operations: UnaryFunction[] + source: S, + ...operations: UnaryFunction[] ): unknown { - return (pipe as any)(...operations)(from(source)); + return (pipe as any)(...operations)(from(source)); } diff --git a/nodepkg/vuekit/src/reactive/subscribe.ts b/nodepkg/vuekit/src/reactive/subscribe.ts index 4b3291ef..1173312f 100644 --- a/nodepkg/vuekit/src/reactive/subscribe.ts +++ b/nodepkg/vuekit/src/reactive/subscribe.ts @@ -2,22 +2,22 @@ import { Observable, type Observer, Subscription } from "rxjs"; import { onBeforeUnmount, onMounted } from "vue"; export function subscribeUntilUnmount( - observerOrNext?: Partial> | ((value: T) => void), + observerOrNext?: Partial> | ((value: T) => void), ) { - return (ob$: Observable) => { - const sub = ob$.subscribe(observerOrNext); - onBeforeUnmount(() => sub.unsubscribe()); - }; + return (ob$: Observable) => { + const sub = ob$.subscribe(observerOrNext); + onBeforeUnmount(() => sub.unsubscribe()); + }; } export function subscribeOnMountedUntilUnmount( - observerOrNext?: Partial> | ((value: T) => void), + observerOrNext?: Partial> | ((value: T) => void), ) { - return (ob$: Observable) => { - let sub: Subscription; - onMounted(() => { - sub = ob$.subscribe(observerOrNext); - }); - onBeforeUnmount(() => sub?.unsubscribe()); - }; + return (ob$: Observable) => { + let sub: Subscription; + onMounted(() => { + sub = ob$.subscribe(observerOrNext); + }); + onBeforeUnmount(() => sub?.unsubscribe()); + }; } diff --git a/nodepkg/vuekit/src/reactive/toComputed.ts b/nodepkg/vuekit/src/reactive/toComputed.ts index 4b95706e..2e7a57ec 100644 --- a/nodepkg/vuekit/src/reactive/toComputed.ts +++ b/nodepkg/vuekit/src/reactive/toComputed.ts @@ -1,36 +1,36 @@ import { Observable } from "rxjs"; import { - type ComputedRef, - type DebuggerOptions, - computed, - shallowRef, + type ComputedRef, + type DebuggerOptions, + computed, + shallowRef, } from "vue"; import { rx } from "./rx"; import { subscribeUntilUnmount } from "./subscribe"; export interface ObservableWithValue extends Observable { - value: T; + value: T; } export function toComputed(debugOptions?: DebuggerOptions) { - function computedObservable(ob$: ObservableWithValue): ComputedRef; - function computedObservable( - ob$: Observable, - initialValue?: T, - ): ComputedRef; - function computedObservable( - ob$: Observable | ObservableWithValue, - initialValue?: T, - ): ComputedRef { - const ref = shallowRef((ob$ as any).value ?? initialValue); - rx( - ob$, - subscribeUntilUnmount((v) => { - ref.value = v; - }), - ); - return computed(() => ref.value, debugOptions); - } + function computedObservable(ob$: ObservableWithValue): ComputedRef; + function computedObservable( + ob$: Observable, + initialValue?: T, + ): ComputedRef; + function computedObservable( + ob$: Observable | ObservableWithValue, + initialValue?: T, + ): ComputedRef { + const ref = shallowRef((ob$ as any).value ?? initialValue); + rx( + ob$, + subscribeUntilUnmount((v) => { + ref.value = v; + }), + ); + return computed(() => ref.value, debugOptions); + } - return computedObservable; + return computedObservable; } diff --git a/nodepkg/vuekit/src/reactive/toObservable.ts b/nodepkg/vuekit/src/reactive/toObservable.ts index 3e0dc6d3..f25f5477 100644 --- a/nodepkg/vuekit/src/reactive/toObservable.ts +++ b/nodepkg/vuekit/src/reactive/toObservable.ts @@ -3,43 +3,43 @@ import { BehaviorSubject, Observable, isObservable } from "rxjs"; import { watch } from "vue"; export const toObservable = ( - obj: O, - key: K, + obj: O, + key: K, ): Observable => { - const value$ = new BehaviorSubject(obj[key]); - watch( - () => obj[key], - (v) => value$.next(v), - ); - return value$; + const value$ = new BehaviorSubject(obj[key]); + watch( + () => obj[key], + (v) => value$.next(v), + ); + return value$; }; export type Function = (...args: any[]) => any; export type Observables = { - [K in keyof O as K extends string - ? O[K] extends Function | Observable - ? never - : `${K}$` - : never]-?: Observable; + [K in keyof O as K extends string + ? O[K] extends Function | Observable + ? never + : `${K}$` + : never]-?: Observable; } & { - [K in keyof O as K extends string - ? O[K] extends Function | Observable - ? K - : never - : never]: O[K]; + [K in keyof O as K extends string + ? O[K] extends Function | Observable + ? K + : never + : never]: O[K]; }; export const toObservables = (obj: O): Observables => { - const refs: Record = {}; + const refs: Record = {}; - for (const k in obj) { - if (isFunction(obj[k]) || isObservable(obj[k])) { - refs[k] = obj[k]; - continue; - } - refs[`${k}$`] = toObservable(obj, k); - } + for (const k in obj) { + if (isFunction(obj[k]) || isObservable(obj[k])) { + refs[k] = obj[k]; + continue; + } + refs[`${k}$`] = toObservable(obj, k); + } - return refs as Observables; + return refs as Observables; }; diff --git a/nodepkg/vuekit/src/router.ts b/nodepkg/vuekit/src/router.ts index c0f0b2f2..c4620516 100644 --- a/nodepkg/vuekit/src/router.ts +++ b/nodepkg/vuekit/src/router.ts @@ -1,10 +1,10 @@ import type { Component, WithDefaultSlot } from "./vue"; import { - RouterLink as _RouterLink, - type RouterLinkProps as _RouterLinkProps, - RouterView as _RouterView, - type RouterViewProps as _RouterViewProps, + RouterLink as _RouterLink, + type RouterLinkProps as _RouterLinkProps, + RouterView as _RouterView, + type RouterViewProps as _RouterViewProps, } from "vue-router"; export type RouterLinkProps = _RouterLinkProps & WithDefaultSlot; @@ -14,9 +14,9 @@ export type RouterViewProps = _RouterViewProps & WithDefaultSlot; export const RouterView: Component = _RouterView as any; export { - useLink, - useRoute, - useRouter, - createRouter, - createWebHistory, + useLink, + useRoute, + useRouter, + createRouter, + createWebHistory, } from "vue-router"; diff --git a/nodepkg/vuekit/src/vue.ts b/nodepkg/vuekit/src/vue.ts index 0fc45466..2d7b35f5 100644 --- a/nodepkg/vuekit/src/vue.ts +++ b/nodepkg/vuekit/src/vue.ts @@ -1,246 +1,242 @@ import { - type AnyType, - type DefaultedType, - type Infer, - type Simplify, - t, + type AnyType, + type DefaultedType, + type Infer, + type Simplify, + t, } from "@innoai-tech/typedef"; import { - type ObjectEmitsOptions, - type RenderFunction, - type SlotsType, - type VNode, - type Ref, - type UnwrapRef, - customRef + type ObjectEmitsOptions, + type RenderFunction, + type SlotsType, + type VNode, + type Ref, + type UnwrapRef, + customRef, } from "vue"; export type VElementType = string | Component; export type VNodeChildAtom = - | VNode - | string - | number - | boolean - | null - | undefined; + | VNode + | string + | number + | boolean + | null + | undefined; -export {shallowRef, watch, inject, provide} from "vue"; +export { shallowRef, watch, inject, provide } from "vue"; export type VNodeArrayChildren = Array; export type VNodeChild = VNodeChildAtom | VNodeArrayChildren; -export {type RenderFunction, type VNode}; +export { type RenderFunction, type VNode }; export type Emits = Record any>; export type Component

= { - (props: P): JSX.Element | null; - slots?: SlotsType>>; - propTypes?: PropTypesOf

; + (props: P): JSX.Element | null; + slots?: SlotsType>>; + propTypes?: PropTypesOf

; }; type ToVueSlotsType> = { - [K in keyof O as K extends string ? SlotName : never]: O[K]; + [K in keyof O as K extends string ? SlotName : never]: O[K]; }; export type WithDefaultSlot = { - $default?: VNodeChild; + $default?: VNodeChild; }; export type SetupContext = { - emit: EmitFn; - attrs: Record; - slots: S; - expose: (exposed?: Record) => void; + emit: EmitFn; + attrs: Record; + slots: S; + expose: (exposed?: Record) => void; }; type EmitFn< - E extends Emits, - Event extends keyof E = keyof E, + E extends Emits, + Event extends keyof E = keyof E, > = UnionToIntersection< - { - [Key in Event]: E[Key] extends (...args: infer Args) => any - ? (event: Key, ...args: Args) => void - : (event: Key) => void; - }[Event] + { + [Key in Event]: E[Key] extends (...args: infer Args) => any + ? (event: Key, ...args: Args) => void + : (event: Key) => void; + }[Event] >; type UnionToIntersection = ( - U extends any - ? (arg: U) => any - : never - ) extends (arg: infer I) => void - ? I - : never; + U extends any ? (arg: U) => any : never +) extends (arg: infer I) => void + ? I + : never; type PickRequired> = { - [K in keyof T as K extends string - ? T[K] extends NonNullable - ? K - : never - : never]: T[K]; + [K in keyof T as K extends string + ? T[K] extends NonNullable + ? K + : never + : never]: T[K]; }; export type PropTypesOf< - Props extends Record, - RequiredProps = Pick>, - OptionalProps = Omit, + Props extends Record, + RequiredProps = Pick>, + OptionalProps = Omit, > = { - [K in keyof RequiredProps]: ReturnType>; + [K in keyof RequiredProps]: ReturnType>; } & { - [K in keyof OptionalProps]-?: ReturnType< - typeof t.custom | undefined> - >; + [K in keyof OptionalProps]-?: ReturnType< + typeof t.custom | undefined> + >; }; export type SetupFunction> = ( - props: InternalPropsOf, - ctx: SetupContext, InternalSlotsOf>, + props: InternalPropsOf, + ctx: SetupContext, InternalSlotsOf>, ) => RenderFunction; export type PublicPropsOf< - PropTypes extends Record, - P extends Record = TypeOfPublic, + PropTypes extends Record, + P extends Record = TypeOfPublic, > = Simplify & PickSlotProps

& Partial>>; export type InternalPropsOf< - PropTypes extends Record, - P extends Record = TypeOfInternal, + PropTypes extends Record, + P extends Record = TypeOfInternal, > = Simplify>; export type InternalSlotsOf< - PropTypes extends Record, - P extends Record = TypeOfInternal, + PropTypes extends Record, + P extends Record = TypeOfInternal, > = Simplify>>; export type InternalEmitsOf< - PropTypes extends Record, - P extends Record = TypeOfInternal, + PropTypes extends Record, + P extends Record = TypeOfInternal, > = ToInternalEmits>>; type TypeOfPublic> = Infer< - ReturnType> + ReturnType> >; // optional with default as InternalRequired type PickInternalNonOptional> = { - [K in keyof T as K extends string - ? Infer extends NonNullable> - ? K - : T[K] extends DefaultedType - ? K - : never - : never]: T[K]; + [K in keyof T as K extends string + ? Infer extends NonNullable> + ? K + : T[K] extends DefaultedType + ? K + : never + : never]: T[K]; }; -export type InferNonNullable = Infer extends NonNullable< - Infer - > - ? Infer - : NonNullable>; +export type InferNonNullable = + Infer extends NonNullable> ? Infer : NonNullable>; export type TypeOfInternal< - PropTypes extends Record, - RequiredProps extends Record = Pick< - PropTypes, - keyof PickInternalNonOptional - >, - OptionalProps extends Record = Omit< - PropTypes, - keyof RequiredProps - >, + PropTypes extends Record, + RequiredProps extends Record = Pick< + PropTypes, + keyof PickInternalNonOptional + >, + OptionalProps extends Record = Omit< + PropTypes, + keyof RequiredProps + >, > = { - [K in keyof RequiredProps]: InferNonNullable; + [K in keyof RequiredProps]: InferNonNullable; } & { - [K in keyof OptionalProps]?: Infer; + [K in keyof OptionalProps]?: Infer; }; export type PickProps> = { - [K in keyof O as K extends string ? NormalProp : never]: O[K]; + [K in keyof O as K extends string ? NormalProp : never]: O[K]; }; export type PickEmitProps> = Required<{ - [K in keyof O as K extends string - ? NonNullable extends (...args: any[]) => any - ? EmitProp - : never - : never]: NonNullable; + [K in keyof O as K extends string + ? NonNullable extends (...args: any[]) => any + ? EmitProp + : never + : never]: NonNullable; }>; type ToInternalEmits = { - [K in keyof O as K extends string ? EmitName : never]: O[K]; + [K in keyof O as K extends string ? EmitName : never]: O[K]; }; export type PickSlotProps> = { - [K in keyof O as K extends string ? SlotProp : never]: O[K]; + [K in keyof O as K extends string ? SlotProp : never]: O[K]; }; export type ToInternalSlots> = { - [K in keyof O as K extends string ? SlotName : never]: NonNullable< - O[K] - > extends (v: infer P) => any - ? (p: P) => VNode[] - : () => VNode[]; + [K in keyof O as K extends string ? SlotName : never]: NonNullable< + O[K] + > extends (v: infer P) => any + ? (p: P) => VNode[] + : () => VNode[]; }; -type NormalProp = Prop extends EmitProp +type NormalProp = + Prop extends EmitProp ? never : Prop extends SlotProp - ? never - : Prop; + ? never + : Prop; type EmitProp = Prop extends `on${infer Name}` - ? Name extends Capitalize - ? `on${Name}` - : never - : never; + ? Name extends Capitalize + ? `on${Name}` + : never + : never; type EmitName = Prop extends `on${infer Name}` - ? ToKebabCase - : never; + ? ToKebabCase + : never; type SlotName = Prop extends `$${infer Name}` - ? Name - : never; + ? Name + : never; type SlotProp = Prop extends `$${infer Name}` - ? `$${Name}` - : never; + ? `$${Name}` + : never; export type ToKebabCase = S extends `${infer C}${infer T}` - ? ToKebabCase extends infer U - ? U extends string - ? T extends Uncapitalize - ? `${Uncapitalize}${U}` - : `${Uncapitalize}-${U}` - : never - : never - : S; + ? ToKebabCase extends infer U + ? U extends string + ? T extends Uncapitalize + ? `${Uncapitalize}${U}` + : `${Uncapitalize}-${U}` + : never + : never + : S; export type ToCamelCase = S extends `${infer T}-${infer U}` + ? `${T}${Capitalize>}` + : S extends `${infer T}_${infer U}` ? `${T}${Capitalize>}` - : S extends `${infer T}_${infer U}` - ? `${T}${Capitalize>}` - : S; + : S; export function ref(value: T): Ref>; export function ref(): Ref { - let currentValue: T; - - return customRef((track, trigger) => { - return { - get() { - track(); - return currentValue - }, - set(value: T) { - const newValue = (value as any)?.$$forwardRef ?? value; - - if (newValue !== currentValue) { - currentValue = newValue; - trigger(); - } - }, - }; - }); + let currentValue: T; + + return customRef((track, trigger) => { + return { + get() { + track(); + return currentValue; + }, + set(value: T) { + const newValue = (value as any)?.$$forwardRef ?? value; + + if (newValue !== currentValue) { + currentValue = newValue; + trigger(); + } + }, + }; + }); } diff --git a/nodepkg/vuekit/tsconfig.monobundle.json b/nodepkg/vuekit/tsconfig.monobundle.json index 48a402a5..1fca63f2 100644 --- a/nodepkg/vuekit/tsconfig.monobundle.json +++ b/nodepkg/vuekit/tsconfig.monobundle.json @@ -1,8 +1,8 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "jsxImportSource": "@innoai-tech/vue-jsx-runtime" - }, - "exclude": ["./example"] + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "jsxImportSource": "@innoai-tech/vue-jsx-runtime" + }, + "exclude": ["./example"] } diff --git a/nodepkg/vuemarkdown/package.json b/nodepkg/vuemarkdown/package.json index 7485b3df..2a8699c6 100644 --- a/nodepkg/vuemarkdown/package.json +++ b/nodepkg/vuemarkdown/package.json @@ -43,10 +43,9 @@ "directory": "nodepkg/vuemarkdown" }, "scripts": { - "test": "bun test .", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build", - "lint": "bunx --bun @biomejs/biome check --apply ." + "lint": "bunx --bun prettier --write . " }, "type": "module" } diff --git a/nodepkg/vuemarkdown/src/Markdown.tsx b/nodepkg/vuemarkdown/src/Markdown.tsx index 03bccd67..ea975270 100644 --- a/nodepkg/vuemarkdown/src/Markdown.tsx +++ b/nodepkg/vuemarkdown/src/Markdown.tsx @@ -1,32 +1,32 @@ import { - type Component, - type VNode, - component$, - rx, - t, + type Component, + type VNode, + component$, + rx, + t, } from "@innoai-tech/vuekit"; import { combineLatest, from, map, switchMap } from "rxjs"; import { rehypeVue, remarkParse, remarkRehype, unified } from "./unified"; export const Markdown = component$( - { - text: t.string(), - components: t.record(t.string(), t.custom>()).optional(), - }, - (props, { render }) => { - const processor$ = props.components$.pipe( - map((components) => - unified() - .use(remarkParse as any) - .use(remarkRehype as any) - .use(rehypeVue, { components: components ?? {} }), - ), - ); + { + text: t.string(), + components: t.record(t.string(), t.custom>()).optional(), + }, + (props, { render }) => { + const processor$ = props.components$.pipe( + map((components) => + unified() + .use(remarkParse as any) + .use(remarkRehype as any) + .use(rehypeVue, { components: components ?? {} }), + ), + ); - return rx( - combineLatest([processor$, props.text$]), - switchMap(([processor, text]) => from(processor.process(text))), - render((vfile: any) => vfile.result as VNode), - ); - }, + return rx( + combineLatest([processor$, props.text$]), + switchMap(([processor, text]) => from(processor.process(text))), + render((vfile: any) => vfile.result as VNode), + ); + }, ); diff --git a/nodepkg/vuemarkdown/src/rehypeVue.tsx b/nodepkg/vuemarkdown/src/rehypeVue.tsx index d7c164f5..c4419a2a 100644 --- a/nodepkg/vuemarkdown/src/rehypeVue.tsx +++ b/nodepkg/vuemarkdown/src/rehypeVue.tsx @@ -3,19 +3,19 @@ import { Fragment, jsx, jsxs } from "@innoai-tech/vuekit/jsx-runtime"; import { toJsxRuntime } from "hast-util-to-jsx-runtime"; export function rehypeVue({ - components, + components, }: { - components: Record>; + components: Record>; }) { - // @ts-ignore - Object.assign(this, { Compiler: compiler }); + // @ts-ignore + Object.assign(this, { Compiler: compiler }); - function compiler(node: any) { - return toJsxRuntime(node, { - Fragment, - jsx, - jsxs, - components, - }); - } + function compiler(node: any) { + return toJsxRuntime(node, { + Fragment, + jsx, + jsxs, + components, + }); + } } diff --git a/nodepkg/vuemarkdown/tsconfig.monobundle.json b/nodepkg/vuemarkdown/tsconfig.monobundle.json index 47bb7a9f..3f7c3bea 100644 --- a/nodepkg/vuemarkdown/tsconfig.monobundle.json +++ b/nodepkg/vuemarkdown/tsconfig.monobundle.json @@ -1,7 +1,7 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - }, - "exclude": ["example"] + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "exclude": ["example"] } diff --git a/nodepkg/vuematerial/example/buttons.tsx b/nodepkg/vuematerial/example/buttons.tsx index df5c2d52..d02c3a9a 100644 --- a/nodepkg/vuematerial/example/buttons.tsx +++ b/nodepkg/vuematerial/example/buttons.tsx @@ -1,68 +1,68 @@ import { map } from "@innoai-tech/lodash"; import { Fragment, component } from "@innoai-tech/vuekit"; import { - ElevatedButton, - FilledButton, - Icon, - OutlinedButton, - TextButton, - TonalButton, + ElevatedButton, + FilledButton, + Icon, + OutlinedButton, + TextButton, + TonalButton, } from "@innoai-tech/vuematerial"; import { Box } from "@innoai-tech/vueuikit"; import { mdiPlus, mdiSend } from "@mdi/js"; import { Container } from "@webapp/vuekit/layout"; export default component(() => { - const stateProps = { - Enabled: {}, - Disabled: { disabled: true }, - Hovered: { hover: true }, - Focused: { focus: true }, - Pressed: { active: true }, - }; + const stateProps = { + Enabled: {}, + Disabled: { disabled: true }, + Hovered: { hover: true }, + Focused: { focus: true }, + Pressed: { active: true }, + }; - const buttons = { - ElevatedButton: ElevatedButton, - FilledButton: FilledButton, - TonalButton: TonalButton, - OutlinedButton: OutlinedButton, - TextButton: TextButton, - }; + const buttons = { + ElevatedButton: ElevatedButton, + FilledButton: FilledButton, + TonalButton: TonalButton, + OutlinedButton: OutlinedButton, + TextButton: TextButton, + }; - return () => ( - - - {map(buttons, (Button, name) => ( - - {[ - (v: string) => v, - (v: string) => ( - <> - - {v} - - ), - (v: string) => ( - <> - {v} - - - ), - ].map((render, i) => ( - - - {map(stateProps, (props, name) => ( - - ))} - - - ))} - - ))} - - - ); + return () => ( + + + {map(buttons, (Button, name) => ( + + {[ + (v: string) => v, + (v: string) => ( + <> + + {v} + + ), + (v: string) => ( + <> + {v} + + + ), + ].map((render, i) => ( + + + {map(stateProps, (props, name) => ( + + ))} + + + ))} + + ))} + + + ); }); diff --git a/nodepkg/vuematerial/example/overlays.tsx b/nodepkg/vuematerial/example/overlays.tsx index 82535757..6367af27 100644 --- a/nodepkg/vuematerial/example/overlays.tsx +++ b/nodepkg/vuematerial/example/overlays.tsx @@ -1,141 +1,141 @@ import { component } from "@innoai-tech/vuekit"; import { - Dialog, - DialogContainer, - Icon, - IconButton, - ListItem, - Menu, - TextButton, - Tooltip, - mdiClose, + Dialog, + DialogContainer, + Icon, + IconButton, + ListItem, + Menu, + TextButton, + Tooltip, + mdiClose, } from "@innoai-tech/vuematerial"; import { Box } from "@innoai-tech/vueuikit"; import { Container } from "@webapp/vuekit/layout"; import { ref } from "vue"; export default component(() => { - const dialogIsOpen = ref(false); - const dialogIsOpen1 = ref(false); - const dialogIsOpen2 = ref(false); + const dialogIsOpen = ref(false); + const dialogIsOpen1 = ref(false); + const dialogIsOpen2 = ref(false); - return () => ( - - - { - dialogIsOpen1.value = true; - }} - > - Open Dialog - -

{ - dialogIsOpen1.value = false; - }} - > - - - 我是对话框 - { - dialogIsOpen1.value = false; - }} - > - - - + return () => ( + + + { + dialogIsOpen1.value = true; + }} + > + Open Dialog + + { + dialogIsOpen1.value = false; + }} + > + + + 我是对话框 + { + dialogIsOpen1.value = false; + }} + > + + + - { - dialogIsOpen2.value = true; - }} - > - Open Dialog In Dialog - + { + dialogIsOpen2.value = true; + }} + > + Open Dialog In Dialog + - { - dialogIsOpen2.value = false; - }} - > - - - 我是对话框 2 - { - dialogIsOpen2.value = false; - }} - > - - - - - - - + { + dialogIsOpen2.value = false; + }} + > + + + 我是对话框 2 + { + dialogIsOpen2.value = false; + }} + > + + + + + + + - - Hover me - + + Hover me + - - - 选项一 + + + 选项一 - { - dialogIsOpen.value = true; - }} - > - 选项二 - - { - dialogIsOpen.value = false; - }} - > - - 我是对话框 - - - - 选项三.1 - 选项三.2 - - } - > - 选项三 - - - } - > - Menu - - - - - ); + { + dialogIsOpen.value = true; + }} + > + 选项二 + + { + dialogIsOpen.value = false; + }} + > + + 我是对话框 + + + + 选项三.1 + 选项三.2 + + } + > + 选项三 + + + } + > + Menu + + + + + ); }); diff --git a/nodepkg/vuematerial/example/textfields.tsx b/nodepkg/vuematerial/example/textfields.tsx index c4cb7b64..b879bed1 100644 --- a/nodepkg/vuematerial/example/textfields.tsx +++ b/nodepkg/vuematerial/example/textfields.tsx @@ -6,62 +6,62 @@ import { mdiAccount, mdiPlus } from "@mdi/js"; import { Container } from "@webapp/vuekit/layout"; export default component(() => { - const stateProps = { - Enabled: {}, - Focused: { focus: true }, - Invalid: { invalid: true }, - Disabled: { disabled: true }, - }; + const stateProps = { + Enabled: {}, + Focused: { focus: true }, + Invalid: { invalid: true }, + Disabled: { disabled: true }, + }; - const buttons = { - TextField: TextField, - }; + const buttons = { + TextField: TextField, + }; - return () => ( - - - {map(buttons, (TextField, name) => ( - - {[ - (props: any) => ( - - - - ), - (props: any) => ( - - - - ), - (props: any) => ( - }> - - - ), - (props: any) => ( - }> - - - ), - ].map((render, i) => ( - - - {map(stateProps, (props) => - render({ - ...props, - $label: "Name", - $supporting: "Desc", - }), - )} - - - ))} - - ))} - - - ); + return () => ( + + + {map(buttons, (TextField, name) => ( + + {[ + (props: any) => ( + + + + ), + (props: any) => ( + + + + ), + (props: any) => ( + }> + + + ), + (props: any) => ( + }> + + + ), + ].map((render, i) => ( + + + {map(stateProps, (props) => + render({ + ...props, + $label: "Name", + $supporting: "Desc", + }), + )} + + + ))} + + ))} + + + ); }); diff --git a/nodepkg/vuematerial/package.json b/nodepkg/vuematerial/package.json index d093a0e1..bfdbe7a8 100644 --- a/nodepkg/vuematerial/package.json +++ b/nodepkg/vuematerial/package.json @@ -40,8 +40,7 @@ "directory": "nodepkg/vuematerial" }, "scripts": { - "test": "bun test .", - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, diff --git a/nodepkg/vuematerial/src/Buttons/ButtonBase.tsx b/nodepkg/vuematerial/src/Buttons/ButtonBase.tsx index 7c4debd0..d413abcc 100644 --- a/nodepkg/vuematerial/src/Buttons/ButtonBase.tsx +++ b/nodepkg/vuematerial/src/Buttons/ButtonBase.tsx @@ -2,61 +2,61 @@ import { t } from "@innoai-tech/vuekit"; import { styled } from "@innoai-tech/vueuikit"; export const ButtonBase = styled("button", { - hover: t.boolean().optional(), - focus: t.boolean().optional(), - active: t.boolean().optional(), - disabled: t.boolean().optional(), + hover: t.boolean().optional(), + focus: t.boolean().optional(), + active: t.boolean().optional(), + disabled: t.boolean().optional(), })({ - textStyle: "sys.label-large", - display: "inline-flex", - alignItems: "center", - justifyContent: "center", - - textDecoration: "none", - outline: "none", - overflow: "hidden", - border: 0, - margin: 0, - userSelect: "none", - - cursor: "pointer", - _disabled: { - cursor: "not-allowed", - }, - - gap: 8, - h: 40, - px: 24, - rounded: 20, - - transitionDuration: "md4", - transitionTimingFunction: "standard-accelerate", - - bg: "none", - - pos: "relative", - zIndex: 1, - - _$before: { - content: `""`, - pos: "absolute", - top: 0, - right: 0, - left: 0, - bottom: 0, - zIndex: -1, - - transitionDuration: "md1", - transitionTimingFunction: "standard-accelerate", - }, - - $data_icon: { - boxSize: 18, - _data_placement__start: { - ml: -8, - }, - _data_placement__end: { - mr: -8, - }, - }, + textStyle: "sys.label-large", + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + + textDecoration: "none", + outline: "none", + overflow: "hidden", + border: 0, + margin: 0, + userSelect: "none", + + cursor: "pointer", + _disabled: { + cursor: "not-allowed", + }, + + gap: 8, + h: 40, + px: 24, + rounded: 20, + + transitionDuration: "md4", + transitionTimingFunction: "standard-accelerate", + + bg: "none", + + pos: "relative", + zIndex: 1, + + _$before: { + content: `""`, + pos: "absolute", + top: 0, + right: 0, + left: 0, + bottom: 0, + zIndex: -1, + + transitionDuration: "md1", + transitionTimingFunction: "standard-accelerate", + }, + + $data_icon: { + boxSize: 18, + _data_placement__start: { + ml: -8, + }, + _data_placement__end: { + mr: -8, + }, + }, }); diff --git a/nodepkg/vuematerial/src/Buttons/ElevatedButton.tsx b/nodepkg/vuematerial/src/Buttons/ElevatedButton.tsx index ad2475e6..4ae4e52e 100644 --- a/nodepkg/vuematerial/src/Buttons/ElevatedButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/ElevatedButton.tsx @@ -2,38 +2,38 @@ import { alpha, styled, variant } from "@innoai-tech/vueuikit"; import { ButtonBase } from "./ButtonBase"; export const ElevatedButton = styled(ButtonBase)({ - color: "sys.primary", - shadow: "1", - _$before: { - bgColor: "sys.surface-container-low", - }, + color: "sys.primary", + shadow: "1", + _$before: { + bgColor: "sys.surface-container-low", + }, - _hover: { - shadow: "2", - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.08)), - }, - }, + _hover: { + shadow: "2", + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.08)), + }, + }, - _focus: { - shadow: "2", - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, - }, + _focus: { + shadow: "2", + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, + }, - _active: { - shadow: "2", - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, - }, + _active: { + shadow: "2", + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, + }, - _disabled: { - shadow: "0", - color: variant("sys.on-surface", alpha(0.38)), - _$before: { - bgColor: variant("sys.on-surface" as const, alpha(0.12)), - }, - }, + _disabled: { + shadow: "0", + color: variant("sys.on-surface", alpha(0.38)), + _$before: { + bgColor: variant("sys.on-surface" as const, alpha(0.12)), + }, + }, }); diff --git a/nodepkg/vuematerial/src/Buttons/FilledButton.tsx b/nodepkg/vuematerial/src/Buttons/FilledButton.tsx index 72c94904..2fab0a89 100644 --- a/nodepkg/vuematerial/src/Buttons/FilledButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/FilledButton.tsx @@ -2,38 +2,38 @@ import { alpha, styled, variant } from "@innoai-tech/vueuikit"; import { ButtonBase } from "./ButtonBase"; export const FilledButton = styled(ButtonBase)({ - containerStyle: "sys.primary", + containerStyle: "sys.primary", - shadow: "1", + shadow: "1", - _hover: { - shadow: "2", - _$before: { - bgColor: variant("white", alpha(0.08)), - }, - }, + _hover: { + shadow: "2", + _$before: { + bgColor: variant("white", alpha(0.08)), + }, + }, - _focus: { - shadow: "2", - _$before: { - bgColor: variant("white", alpha(0.12)), - }, - }, + _focus: { + shadow: "2", + _$before: { + bgColor: variant("white", alpha(0.12)), + }, + }, - _active: { - shadow: "2", - _$before: { - bgColor: variant("white", alpha(0.12)), - }, - }, + _active: { + shadow: "2", + _$before: { + bgColor: variant("white", alpha(0.12)), + }, + }, - _disabled: { - shadow: "0", - color: variant("sys.on-surface", alpha(0.38)), - bgColor: "rgba(0,0,0,0)", + _disabled: { + shadow: "0", + color: variant("sys.on-surface", alpha(0.38)), + bgColor: "rgba(0,0,0,0)", - _$before: { - bgColor: variant("sys.on-surface", alpha(0.12)), - }, - }, + _$before: { + bgColor: variant("sys.on-surface", alpha(0.12)), + }, + }, }); diff --git a/nodepkg/vuematerial/src/Buttons/IconButton.tsx b/nodepkg/vuematerial/src/Buttons/IconButton.tsx index 8fd439a7..e91810e5 100644 --- a/nodepkg/vuematerial/src/Buttons/IconButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/IconButton.tsx @@ -2,9 +2,9 @@ import { styled } from "@innoai-tech/vueuikit"; import { TextButton } from "./TextButton"; export const IconButton = styled( - TextButton, - {}, + TextButton, + {}, )({ - boxSize: 40, - p: 0, + boxSize: 40, + p: 0, }); diff --git a/nodepkg/vuematerial/src/Buttons/OutlinedButton.tsx b/nodepkg/vuematerial/src/Buttons/OutlinedButton.tsx index 88314cf2..d53e61e9 100644 --- a/nodepkg/vuematerial/src/Buttons/OutlinedButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/OutlinedButton.tsx @@ -2,32 +2,32 @@ import { alpha, styled, variant } from "@innoai-tech/vueuikit"; import { ButtonBase } from "./ButtonBase"; export const OutlinedButton = styled(ButtonBase)({ - extends: [ - { - // ensure the order to define border before all - border: "1px solid", - }, - ], + extends: [ + { + // ensure the order to define border before all + border: "1px solid", + }, + ], - color: "sys.primary", - bgColor: "rgba(0,0,0,0)", - borderColor: "sys.outline", + color: "sys.primary", + bgColor: "rgba(0,0,0,0)", + borderColor: "sys.outline", - _hover: { - bgColor: variant("sys.primary" as const, alpha(0.08)), - }, + _hover: { + bgColor: variant("sys.primary" as const, alpha(0.08)), + }, - _active: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, + _active: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, - _focus: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, + _focus: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, - _disabled: { - color: variant("sys.on-surface", alpha(0.38)), - bgColor: "rgba(0,0,0,0)", - borderColor: variant("sys.on-surface", alpha(0.12)), - }, + _disabled: { + color: variant("sys.on-surface", alpha(0.38)), + bgColor: "rgba(0,0,0,0)", + borderColor: variant("sys.on-surface", alpha(0.12)), + }, }); diff --git a/nodepkg/vuematerial/src/Buttons/TextButton.tsx b/nodepkg/vuematerial/src/Buttons/TextButton.tsx index fe6baaf0..d5e62ad8 100644 --- a/nodepkg/vuematerial/src/Buttons/TextButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/TextButton.tsx @@ -2,51 +2,51 @@ import { alpha, styled, variant } from "@innoai-tech/vueuikit"; import { ButtonBase } from "./ButtonBase"; export const TextButton = styled(ButtonBase)({ - extends: [ - { - px: 16, - - $data_icon: { - _data_placement__start: { - ml: -4, - }, - _data_placement__end: { - mr: -4, - }, - }, - }, - ], - - color: "sys.primary", - - _$before: { - bgColor: "rgba(0,0,0,0)", - }, - - _hover: { - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.08)), - }, - }, - - _focus: { - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, - }, - - _active: { - _$before: { - bgColor: variant("sys.primary" as const, alpha(0.12)), - }, - }, - - _disabled: { - color: variant("sys.on-surface", alpha(0.38)), - bgColor: "rgba(0,0,0,0)", - - _$before: { - bgColor: "rgba(0,0,0,0)", - }, - }, + extends: [ + { + px: 16, + + $data_icon: { + _data_placement__start: { + ml: -4, + }, + _data_placement__end: { + mr: -4, + }, + }, + }, + ], + + color: "sys.primary", + + _$before: { + bgColor: "rgba(0,0,0,0)", + }, + + _hover: { + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.08)), + }, + }, + + _focus: { + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, + }, + + _active: { + _$before: { + bgColor: variant("sys.primary" as const, alpha(0.12)), + }, + }, + + _disabled: { + color: variant("sys.on-surface", alpha(0.38)), + bgColor: "rgba(0,0,0,0)", + + _$before: { + bgColor: "rgba(0,0,0,0)", + }, + }, }); diff --git a/nodepkg/vuematerial/src/Buttons/TonalButton.tsx b/nodepkg/vuematerial/src/Buttons/TonalButton.tsx index 5d945add..0c1f4d0c 100644 --- a/nodepkg/vuematerial/src/Buttons/TonalButton.tsx +++ b/nodepkg/vuematerial/src/Buttons/TonalButton.tsx @@ -2,36 +2,36 @@ import { alpha, styled, variant } from "@innoai-tech/vueuikit"; import { ButtonBase } from "./ButtonBase"; export const TonalButton = styled(ButtonBase)({ - containerStyle: "sys.secondary-container", + containerStyle: "sys.secondary-container", - color: "sys.primary", - shadow: "0", + color: "sys.primary", + shadow: "0", - _hover: { - shadow: "1", - _$before: { - bgColor: variant("sys.on-secondary-container" as const, alpha(0.08)), - }, - }, + _hover: { + shadow: "1", + _$before: { + bgColor: variant("sys.on-secondary-container" as const, alpha(0.08)), + }, + }, - _focus: { - _$before: { - bgColor: variant("sys.on-secondary-container" as const, alpha(0.12)), - }, - }, + _focus: { + _$before: { + bgColor: variant("sys.on-secondary-container" as const, alpha(0.12)), + }, + }, - _active: { - _$before: { - bgColor: variant("sys.on-secondary-container" as const, alpha(0.12)), - }, - }, + _active: { + _$before: { + bgColor: variant("sys.on-secondary-container" as const, alpha(0.12)), + }, + }, - _disabled: { - color: variant("sys.on-surface", alpha(0.38)), - bgColor: "rgba(0,0,0,0)", - shadow: "0", - _$before: { - bgColor: variant("sys.on-surface" as const, alpha(0.12)), - }, - }, + _disabled: { + color: variant("sys.on-surface", alpha(0.38)), + bgColor: "rgba(0,0,0,0)", + shadow: "0", + _$before: { + bgColor: variant("sys.on-surface" as const, alpha(0.12)), + }, + }, }); diff --git a/nodepkg/vuematerial/src/FormControls/TextField.tsx b/nodepkg/vuematerial/src/FormControls/TextField.tsx index fccb087e..de9808ec 100644 --- a/nodepkg/vuematerial/src/FormControls/TextField.tsx +++ b/nodepkg/vuematerial/src/FormControls/TextField.tsx @@ -4,216 +4,216 @@ import { styled } from "@innoai-tech/vueuikit"; import { cloneVNode } from "vue"; export const TextField = styled( - "label", - { - valued: t.boolean().optional(), - focus: t.boolean().optional(), - invalid: t.boolean().optional(), - disabled: t.boolean().optional(), - - $label: t.custom().optional(), - $supporting: t.custom().optional(), - $leading: t.custom().optional(), - $trailing: t.custom().optional(), - $default: t.custom(), - }, - (props, { slots }) => { - return (Wrap) => { - let valued = props.valued; - const invalid = props.invalid; - let disabled = props.disabled; - - const children = (slots.default?.() ?? []).map((c) => { - if (c.type === "input") { - valued = !!get( - c.props, - ["value"], - get(c.props, ["placeholder"], valued), - ); - - disabled = get(c.props, ["disabled"], disabled); - - return cloneVNode(c, { - disabled: disabled, - }); - } - return cloneVNode(c); - }); - - return ( - -
- {slots.leading && {slots.leading()}} -
{slots.label?.()}
- {children} - {slots.trailing && {slots.trailing()}} -
- {slots.supporting && ( -
{slots.supporting?.()}
- )} -
- ); - }; - }, + "label", + { + valued: t.boolean().optional(), + focus: t.boolean().optional(), + invalid: t.boolean().optional(), + disabled: t.boolean().optional(), + + $label: t.custom().optional(), + $supporting: t.custom().optional(), + $leading: t.custom().optional(), + $trailing: t.custom().optional(), + $default: t.custom(), + }, + (props, { slots }) => { + return (Wrap) => { + let valued = props.valued; + const invalid = props.invalid; + let disabled = props.disabled; + + const children = (slots.default?.() ?? []).map((c) => { + if (c.type === "input") { + valued = !!get( + c.props, + ["value"], + get(c.props, ["placeholder"], valued), + ); + + disabled = get(c.props, ["disabled"], disabled); + + return cloneVNode(c, { + disabled: disabled, + }); + } + return cloneVNode(c); + }); + + return ( + +
+ {slots.leading && {slots.leading()}} +
{slots.label?.()}
+ {children} + {slots.trailing && {slots.trailing()}} +
+ {slots.supporting && ( +
{slots.supporting?.()}
+ )} +
+ ); + }; + }, )({ - display: "block", - pos: "relative", - textStyle: "sys.body-large", - - $data_input_container: { - pos: "relative", - }, - - $data_input: { - outline: "none", - flex: 1, - w: "100%", - - m: 0, - - px: 16, - py: 4, - minHeight: 56, - - bg: "none", - border: "none", - - cursor: "text", - "&[readonly]": { - cursor: "pointer", - }, - - bgColor: "sys.surface-container-highest", - color: "sys.on-surface", - - roundedTop: "xs", - - borderBottom: "1px solid", - borderColor: "sys.outline-variant", - - textStyle: "sys.body-large", - - transitionDuration: "sm1", - transitionTimingFunction: "standard", - }, - - $data_input_label: { - pos: "absolute", - top: 4, - bottom: 4, - left: 16, - - color: "sys.on-surface-variant", - - display: "flex", - alignItems: "center", - transitionDuration: "sm2", - transitionTimingFunction: "standard-accelerate", - }, - - $data_input_supporting: { - textStyle: "sys.body-small", - px: 16, - pt: 4, - display: "flex", - gap: 16, - - color: "sys.on-surface-variant", - }, - - _has_leading: { - $data_input: { - pl: 16 + 20, - }, - $data_input_label: { - left: 16 + 20, - }, - }, - - _has_trailing: { - $data_input: { - pr: 12 + 20, - }, - }, - - _valued: { - $data_input: { - pt: 18, - }, - - $data_input_label: { - top: 8, - bottom: "auto", - textStyle: "sys.body-small", - }, - }, - - _focusWithin: { - $data_input: { - pt: 18, - borderBottom: "2px solid", - borderColor: "sys.primary", - }, - - $data_input_label: { - top: 8, - bottom: "auto", - textStyle: "sys.body-small", - color: "sys.primary", - }, - }, - - _invalid: { - $data_input: { - borderBottom: "2px solid", - borderColor: "sys.error", - }, - - $data_input_label: { - color: "sys.error", - }, - - $data_input_supporting: { - color: "sys.error", - }, - }, - - _disabled: { - opacity: 0.38, - cursor: "not-allowed", - }, + display: "block", + pos: "relative", + textStyle: "sys.body-large", + + $data_input_container: { + pos: "relative", + }, + + $data_input: { + outline: "none", + flex: 1, + w: "100%", + + m: 0, + + px: 16, + py: 4, + minHeight: 56, + + bg: "none", + border: "none", + + cursor: "text", + "&[readonly]": { + cursor: "pointer", + }, + + bgColor: "sys.surface-container-highest", + color: "sys.on-surface", + + roundedTop: "xs", + + borderBottom: "1px solid", + borderColor: "sys.outline-variant", + + textStyle: "sys.body-large", + + transitionDuration: "sm1", + transitionTimingFunction: "standard", + }, + + $data_input_label: { + pos: "absolute", + top: 4, + bottom: 4, + left: 16, + + color: "sys.on-surface-variant", + + display: "flex", + alignItems: "center", + transitionDuration: "sm2", + transitionTimingFunction: "standard-accelerate", + }, + + $data_input_supporting: { + textStyle: "sys.body-small", + px: 16, + pt: 4, + display: "flex", + gap: 16, + + color: "sys.on-surface-variant", + }, + + _has_leading: { + $data_input: { + pl: 16 + 20, + }, + $data_input_label: { + left: 16 + 20, + }, + }, + + _has_trailing: { + $data_input: { + pr: 12 + 20, + }, + }, + + _valued: { + $data_input: { + pt: 18, + }, + + $data_input_label: { + top: 8, + bottom: "auto", + textStyle: "sys.body-small", + }, + }, + + _focusWithin: { + $data_input: { + pt: 18, + borderBottom: "2px solid", + borderColor: "sys.primary", + }, + + $data_input_label: { + top: 8, + bottom: "auto", + textStyle: "sys.body-small", + color: "sys.primary", + }, + }, + + _invalid: { + $data_input: { + borderBottom: "2px solid", + borderColor: "sys.error", + }, + + $data_input_label: { + color: "sys.error", + }, + + $data_input_supporting: { + color: "sys.error", + }, + }, + + _disabled: { + opacity: 0.38, + cursor: "not-allowed", + }, }); const Maker = styled("div", { - role: t.enums(["leading", "trailing"]).optional().default("leading"), + role: t.enums(["leading", "trailing"]).optional().default("leading"), })({ - pos: "absolute", - top: 4, - bottom: 4, - display: "flex", - alignItems: "center", - color: "sys.on-surface-variant", - - _role__leading: { - left: 12, - - $data_icon: { - ml: -4, - }, - }, - - _role__trailing: { - right: 12, - - $data_icon: { - mr: -4, - }, - }, + pos: "absolute", + top: 4, + bottom: 4, + display: "flex", + alignItems: "center", + color: "sys.on-surface-variant", + + _role__leading: { + left: 12, + + $data_icon: { + ml: -4, + }, + }, + + _role__trailing: { + right: 12, + + $data_icon: { + mr: -4, + }, + }, }); diff --git a/nodepkg/vuematerial/src/Icons/Icon.tsx b/nodepkg/vuematerial/src/Icons/Icon.tsx index 1ed1d2e8..5a5fb713 100644 --- a/nodepkg/vuematerial/src/Icons/Icon.tsx +++ b/nodepkg/vuematerial/src/Icons/Icon.tsx @@ -2,18 +2,18 @@ import { t } from "@innoai-tech/vuekit"; import { styled } from "@innoai-tech/vueuikit"; export const Icon = styled( - "span", - { - placement: t.enums(["start", "end"]).optional(), - path: t.string(), - }, - (props, _) => (Wrapper) => ( - - - - - - ), + "span", + { + placement: t.enums(["start", "end"]).optional(), + path: t.string(), + }, + (props, _) => (Wrapper) => ( + + + + + + ), )({ - boxSize: "1.2em", + boxSize: "1.2em", }); diff --git a/nodepkg/vuematerial/src/Overlays/Dialog.tsx b/nodepkg/vuematerial/src/Overlays/Dialog.tsx index ae53ea8d..f7401f81 100644 --- a/nodepkg/vuematerial/src/Overlays/Dialog.tsx +++ b/nodepkg/vuematerial/src/Overlays/Dialog.tsx @@ -1,123 +1,123 @@ import { type VNodeChild, component, t } from "@innoai-tech/vuekit"; import { - Overlay, - alpha, - defineTransition, - styled, - transition, - variant, + Overlay, + alpha, + defineTransition, + styled, + transition, + variant, } from "@innoai-tech/vueuikit"; import { ref, watch } from "vue"; const Container = styled("div")({ - pos: "absolute", - top: 0, - left: 0, - h: "100vh", - w: "100vw", - zIndex: 100, - display: "flex", - alignItems: "center", - justifyContent: "center", + pos: "absolute", + top: 0, + left: 0, + h: "100vh", + w: "100vw", + zIndex: 100, + display: "flex", + alignItems: "center", + justifyContent: "center", }); export const DialogBackdrop = styled("div")({ - cursor: "pointer", - pos: "absolute", - top: 0, - left: 0, - h: "100vh", - w: "100vw", - zIndex: -1, - bgColor: variant("sys.scrim", alpha(0.38)), + cursor: "pointer", + pos: "absolute", + top: 0, + left: 0, + h: "100vh", + w: "100vw", + zIndex: -1, + bgColor: variant("sys.scrim", alpha(0.38)), }); export const DialogContainer = styled("div")({ - py: 12, - rounded: "sm", - shadow: "3", - minW: "50vw", - containerStyle: "sys.surface-container-high", + py: 12, + rounded: "sm", + shadow: "3", + minW: "50vw", + containerStyle: "sys.surface-container-high", }); const FadeInOutTransition = defineTransition( - { - from: { - opacity: 0, - }, - to: { - opacity: 1, - }, - duration: transition.duration.md1, - easing: transition.easing.standard.accelerate, - }, - { - from: { - opacity: 1, - }, - to: { - opacity: 0, - }, - duration: transition.duration.sm4, - easing: transition.easing.standard.accelerate, - }, + { + from: { + opacity: 0, + }, + to: { + opacity: 1, + }, + duration: transition.duration.md1, + easing: transition.easing.standard.accelerate, + }, + { + from: { + opacity: 1, + }, + to: { + opacity: 0, + }, + duration: transition.duration.sm4, + easing: transition.easing.standard.accelerate, + }, ); export const Dialog = component( - { - isOpen: Overlay.propTypes!.isOpen, - onClose: t.custom<() => void>(), - $default: t.custom().optional(), - }, - (props, { slots, emit }) => { - const mount = ref(props.isOpen ?? false); - const animateEnterOrLeave = ref(false); + { + isOpen: Overlay.propTypes!.isOpen, + onClose: t.custom<() => void>(), + $default: t.custom().optional(), + }, + (props, { slots, emit }) => { + const mount = ref(props.isOpen ?? false); + const animateEnterOrLeave = ref(false); - watch( - () => props.isOpen, - (open) => { - if (open === true) { - // mount first,then animate enter - mount.value = true; - } else if (open === false) { - // animate leave first,then unmount - animateEnterOrLeave.value = false; - } - }, - ); + watch( + () => props.isOpen, + (open) => { + if (open === true) { + // mount first,then animate enter + mount.value = true; + } else if (open === false) { + // animate leave first,then unmount + animateEnterOrLeave.value = false; + } + }, + ); - return () => { - return ( - { - animateEnterOrLeave.value = true; - }} - onEscKeydown={() => { - animateEnterOrLeave.value = false; - }} - > - - { - if (t === "leave") { - mount.value = false; - emit("close"); - } - }} - > - {animateEnterOrLeave.value ? ( - { - animateEnterOrLeave.value = false; - }} - /> - ) : null} - - {slots.default?.()} - - - ); - }; - }, + return () => { + return ( + { + animateEnterOrLeave.value = true; + }} + onEscKeydown={() => { + animateEnterOrLeave.value = false; + }} + > + + { + if (t === "leave") { + mount.value = false; + emit("close"); + } + }} + > + {animateEnterOrLeave.value ? ( + { + animateEnterOrLeave.value = false; + }} + /> + ) : null} + + {slots.default?.()} + + + ); + }; + }, ); diff --git a/nodepkg/vuematerial/src/Overlays/Menu.tsx b/nodepkg/vuematerial/src/Overlays/Menu.tsx index ca2361d7..9fe940f4 100644 --- a/nodepkg/vuematerial/src/Overlays/Menu.tsx +++ b/nodepkg/vuematerial/src/Overlays/Menu.tsx @@ -3,62 +3,62 @@ import { Popper, alpha, styled, variant } from "@innoai-tech/vueuikit"; import { type VNode, cloneVNode, ref } from "vue"; export const MenuContainer = styled("div")({ - py: 8, - rounded: "sm", - shadow: "2", - minW: 120, - containerStyle: "sys.surface-container", - pos: "relative", - zIndex: 100, + py: 8, + rounded: "sm", + shadow: "2", + minW: 120, + containerStyle: "sys.surface-container", + pos: "relative", + zIndex: 100, }); export const ListItem = styled("div")({ - "& + &": { - borderTop: "1px solid", - borderColor: "sys.outline-variant", - }, - py: 8, - px: 16, - textStyle: "sys.label-large", + "& + &": { + borderTop: "1px solid", + borderColor: "sys.outline-variant", + }, + py: 8, + px: 16, + textStyle: "sys.label-large", - containerStyle: "sys.surface-container", + containerStyle: "sys.surface-container", - _hover: { - cursor: "pointer", - bgColor: variant("sys.on-surface", alpha(0.08)), - }, + _hover: { + cursor: "pointer", + bgColor: variant("sys.on-surface", alpha(0.08)), + }, }); export const Menu = component( - { - placement: t.custom().optional(), - $menu: t.custom(), - $default: t.custom(), - }, - (props, { slots }) => { - const isOpen = ref(false); + { + placement: t.custom().optional(), + $menu: t.custom(), + $default: t.custom(), + }, + (props, { slots }) => { + const isOpen = ref(false); - return () => { - const trigger = slots.default ? slots.default()[0] : undefined; + return () => { + const trigger = slots.default ? slots.default()[0] : undefined; - return ( - { - isOpen.value = false; - }} - $content={{slots.menu?.()}} - > - {trigger - ? cloneVNode(trigger, { - onClick: () => { - isOpen.value = true; - }, - }) - : null} - - ); - }; - }, + return ( + { + isOpen.value = false; + }} + $content={{slots.menu?.()}} + > + {trigger + ? cloneVNode(trigger, { + onClick: () => { + isOpen.value = true; + }, + }) + : null} + + ); + }; + }, ); diff --git a/nodepkg/vuematerial/src/Overlays/Tooltip.tsx b/nodepkg/vuematerial/src/Overlays/Tooltip.tsx index da3bedbb..e20b05b6 100644 --- a/nodepkg/vuematerial/src/Overlays/Tooltip.tsx +++ b/nodepkg/vuematerial/src/Overlays/Tooltip.tsx @@ -1,76 +1,76 @@ import { component, t } from "@innoai-tech/vuekit"; import { - Popper, - defineTransition, - styled, - transition, + Popper, + defineTransition, + styled, + transition, } from "@innoai-tech/vueuikit"; import { type VNode, cloneVNode, ref } from "vue"; const FadeInOutTransition = defineTransition( - { - from: { - opacity: 0, - }, - to: { - opacity: 1, - }, - duration: transition.duration.md1, - easing: transition.easing.standard.accelerate, - }, - { - from: { - opacity: 1, - }, - to: { - opacity: 0, - }, - duration: transition.duration.sm4, - easing: transition.easing.standard.decelerate, - }, + { + from: { + opacity: 0, + }, + to: { + opacity: 1, + }, + duration: transition.duration.md1, + easing: transition.easing.standard.accelerate, + }, + { + from: { + opacity: 1, + }, + to: { + opacity: 0, + }, + duration: transition.duration.sm4, + easing: transition.easing.standard.decelerate, + }, ); const TooltipContainer = styled("div")({ - py: 4, - px: 12, - rounded: "sm", - shadow: "3", - containerStyle: "sys.on-surface", - pos: "relative", - zIndex: 100, + py: 4, + px: 12, + rounded: "sm", + shadow: "3", + containerStyle: "sys.on-surface", + pos: "relative", + zIndex: 100, }); export const Tooltip = component( - { - title: t.custom(), - $default: t.custom(), - }, - (props, { slots }) => { - const isOpen = ref(false); + { + title: t.custom(), + $default: t.custom(), + }, + (props, { slots }) => { + const isOpen = ref(false); - return () => { - const child = slots.default()[0]; + return () => { + const child = slots.default()[0]; - return ( - {props.title}} - $transition={({ content }) => ( - {content} - )} - > - {child - ? cloneVNode(child, { - onMouseover: () => { - isOpen.value = true; - }, - onMouseout: () => { - isOpen.value = false; - }, - }) - : null} - - ); - }; - }, + return ( + {props.title}} + $transition={({ content }) => ( + {content} + )} + > + {child + ? cloneVNode(child, { + onMouseover: () => { + isOpen.value = true; + }, + onMouseout: () => { + isOpen.value = false; + }, + }) + : null} + + ); + }; + }, ); diff --git a/nodepkg/vuematerial/tsconfig.monobundle.json b/nodepkg/vuematerial/tsconfig.monobundle.json index 47bb7a9f..3f7c3bea 100644 --- a/nodepkg/vuematerial/tsconfig.monobundle.json +++ b/nodepkg/vuematerial/tsconfig.monobundle.json @@ -1,7 +1,7 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - }, - "exclude": ["example"] + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "exclude": ["example"] } diff --git a/nodepkg/vueuikit/example/color-palette.tsx b/nodepkg/vueuikit/example/color-palette.tsx index a671e31e..ca6cce39 100644 --- a/nodepkg/vueuikit/example/color-palette.tsx +++ b/nodepkg/vueuikit/example/color-palette.tsx @@ -1,132 +1,130 @@ -import {map} from "@innoai-tech/lodash"; -import {component, ref} from "@innoai-tech/vuekit"; -import {Palette} from "@innoai-tech/vueuikit"; -import {Box} from "@innoai-tech/vueuikit"; -import {DynamicThemingProvider} from "@webapp/vuekit/layout"; +import { map } from "@innoai-tech/lodash"; +import { component, ref } from "@innoai-tech/vuekit"; +import { Palette } from "@innoai-tech/vueuikit"; +import { Box } from "@innoai-tech/vueuikit"; +import { DynamicThemingProvider } from "@webapp/vuekit/layout"; import copyToClipboard from "copy-to-clipboard"; export default component(() => { - const p = DynamicThemingProvider.use(); + const p = DynamicThemingProvider.use(); - const el = ref(null); + const el = ref(null); - return () => { - const pp = Palette.fromColors(p.value.seed); + return () => { + const pp = Palette.fromColors(p.value.seed); - console.log(el.value); + console.log(el.value); - return ( - - - - {map(p.value.seed, (color, name) => { - return ( - - {name} - { - p.next((x) => { - (x.seed as any)[name] = (evt.target as HTMLInputElement) - .value as string; - }); - }} - /> - - ); - })} - - - {map(p.value.rules, ([base, onDark, onLight], role) => { - return ( - - {role} - - 50 ? "black" : "white", - }} - data-color={Palette.toHEX(pp.seeds[base].tone(onDark))} - data-theme={"dark"} - onChange={(evt) => { - try { - const v = parseInt( - (evt.target as HTMLInputElement).value, - ); - p.next((x) => { - (x.rules as any)[role] = [base, v, onLight]; - }); - } catch (_) { - } - }} - /> - 50 ? "black" : "white", - }} - data-color={Palette.toHEX(pp.seeds[base].tone(onLight))} - data-theme={"light"} - onChange={(evt) => { - try { - const v = parseInt( - (evt.target as HTMLInputElement).value, - ); - - p.next((x) => { - (x.rules as any)[role] = [base, onDark, v]; - }); - } catch (_) { - } - }} - /> - - - ); - })} - + return ( + + + + {map(p.value.seed, (color, name) => { + return ( + + {name} + { + p.next((x) => { + (x.seed as any)[name] = (evt.target as HTMLInputElement) + .value as string; + }); + }} + /> + ); + })} + + + {map(p.value.rules, ([base, onDark, onLight], role) => { + return ( + + {role} + + 50 ? "black" : "white", + }} + data-color={Palette.toHEX(pp.seeds[base].tone(onDark))} + data-theme={"dark"} + onChange={(evt) => { + try { + const v = parseInt( + (evt.target as HTMLInputElement).value, + ); + p.next((x) => { + (x.rules as any)[role] = [base, v, onLight]; + }); + } catch (_) {} + }} + /> + 50 ? "black" : "white", + }} + data-color={Palette.toHEX(pp.seeds[base].tone(onLight))} + data-theme={"light"} + onChange={(evt) => { + try { + const v = parseInt( + (evt.target as HTMLInputElement).value, + ); - *": {flex: 1}}}> -
 copyToClipboard(JSON.stringify(p.value))}>
-						{JSON.stringify(p.value, null, 2)}
-					
+ p.next((x) => { + (x.rules as any)[role] = [base, onDark, v]; + }); + } catch (_) {} + }} + /> +
-
- ); - }; + ); + })} +
+
+ + *": { flex: 1 } }}> +
 copyToClipboard(JSON.stringify(p.value))}>
+            {JSON.stringify(p.value, null, 2)}
+          
+
+
+ ); + }; }); diff --git a/nodepkg/vueuikit/example/color-scheme.tsx b/nodepkg/vueuikit/example/color-scheme.tsx index a2232b96..dfcef76d 100644 --- a/nodepkg/vueuikit/example/color-scheme.tsx +++ b/nodepkg/vueuikit/example/color-scheme.tsx @@ -3,174 +3,174 @@ import { Fragment, component } from "@innoai-tech/vuekit"; import { Box } from "@innoai-tech/vueuikit"; const tones = { - "0": true, - "10": true, - "20": true, - "30": true, - "40": true, - "50": true, - "60": true, - "70": true, - "80": true, - "90": true, - "95": true, - "100": true, + "0": true, + "10": true, + "20": true, + "30": true, + "40": true, + "50": true, + "60": true, + "70": true, + "80": true, + "90": true, + "95": true, + "100": true, } as const; export default component(() => { - return () => ( - <> - {["light", "dark"].map((theme) => ( -
- - {upperFirst(theme)} Scheme - - - - {[ - "primary", - "secondary", - "tertiary", - "error", - "warning", - "success", - ].map((keyColor) => { - return ( - - - {["", "-container"].map((suffix) => ( - - - {`${keyColor}${suffix}`} - - - {`on-${keyColor}${suffix}`} - - - ))} - - - {map(tones, (_, k) => ( - 50 ? 0 : 100 - }` as any, - bgColor: `${keyColor}.${k}` as any, - w: 60, - p: 8, - textAlign: "right", - }} - > - .{k} - - ))} - - - ); - })} - + return () => ( + <> + {["light", "dark"].map((theme) => ( +
+ + {upperFirst(theme)} Scheme + + + + {[ + "primary", + "secondary", + "tertiary", + "error", + "warning", + "success", + ].map((keyColor) => { + return ( + + + {["", "-container"].map((suffix) => ( + + + {`${keyColor}${suffix}`} + + + {`on-${keyColor}${suffix}`} + + + ))} + + + {map(tones, (_, k) => ( + 50 ? 0 : 100 + }` as any, + bgColor: `${keyColor}.${k}` as any, + w: 60, + p: 8, + textAlign: "right", + }} + > + .{k} + + ))} + + + ); + })} + -
- *": { width: 1 / 3 } }}> - {["-dim", "", "-bright"].map((suffix) => ( - - {`surface${suffix}`} - - ))} - - *": { width: 1 / 4 } }}> - {["-lowest", "-low", "", "-high", "-highest"].map((suffix) => ( - - {`surface-container${suffix}`} - - ))} - - *": { width: 1 / 4 } }}> - {[ - "on-surface", - "on-surface-variant", - "outline", - "outline-variant", - ].map((color) => ( - - {color} - - ))} - - - {map(tones, (_, k) => ( - 50 ? 0 : 100}` as any, - bgColor: `neutral.${k}` as any, - flex: 1, - p: 8, - textAlign: "right", - }} - > - .{k} - - ))} - -
-
-
- ))} - - ); +
+ *": { width: 1 / 3 } }}> + {["-dim", "", "-bright"].map((suffix) => ( + + {`surface${suffix}`} + + ))} + + *": { width: 1 / 4 } }}> + {["-lowest", "-low", "", "-high", "-highest"].map((suffix) => ( + + {`surface-container${suffix}`} + + ))} + + *": { width: 1 / 4 } }}> + {[ + "on-surface", + "on-surface-variant", + "outline", + "outline-variant", + ].map((color) => ( + + {color} + + ))} + + + {map(tones, (_, k) => ( + 50 ? 0 : 100}` as any, + bgColor: `neutral.${k}` as any, + flex: 1, + p: 8, + textAlign: "right", + }} + > + .{k} + + ))} + +
+
+
+ ))} + + ); }); diff --git a/nodepkg/vueuikit/example/elevation.tsx b/nodepkg/vueuikit/example/elevation.tsx index 47ce5860..9c5b79d6 100644 --- a/nodepkg/vueuikit/example/elevation.tsx +++ b/nodepkg/vueuikit/example/elevation.tsx @@ -2,29 +2,29 @@ import { component } from "@innoai-tech/vuekit"; import { Box, ThemeProvider } from "@innoai-tech/vueuikit"; export default component(() => { - const theme = ThemeProvider.use(); + const theme = ThemeProvider.use(); - return () => ( - <> - {Object.keys(theme.token.elevation.tokens).map((elevation) => ( - ( + <> + {Object.keys(theme.token.elevation.tokens).map((elevation) => ( + -
Elevation {elevation}
-
- ))} - - ); + _hover: { + shadow: `${1 + parseInt(elevation)}` as any, + }, + }} + > +
Elevation {elevation}
+
+ ))} + + ); }); diff --git a/nodepkg/vueuikit/example/figma-tokens.tsx b/nodepkg/vueuikit/example/figma-tokens.tsx index beb7ac95..44a4c733 100644 --- a/nodepkg/vueuikit/example/figma-tokens.tsx +++ b/nodepkg/vueuikit/example/figma-tokens.tsx @@ -3,39 +3,39 @@ import { ThemeProvider } from "@innoai-tech/vueuikit"; import { Box } from "@innoai-tech/vueuikit"; export default component(() => { - const t = ThemeProvider.use(); + const t = ThemeProvider.use(); - return () => { - return ( - -
点击下载,通过 Figma Tokens 全量导入文件
-
 {
-						download(
-							JSON.stringify(t.toFigmaTokens(), null, 2),
-							`tokens.${new Date().getTime()}.json`,
-						);
-					}}
-				>
-					{JSON.stringify(t.toFigmaTokens(), null, 2)}
-				
-
- ); - }; + return () => { + return ( + +
点击下载,通过 Figma Tokens 全量导入文件
+
 {
+            download(
+              JSON.stringify(t.toFigmaTokens(), null, 2),
+              `tokens.${new Date().getTime()}.json`,
+            );
+          }}
+        >
+          {JSON.stringify(t.toFigmaTokens(), null, 2)}
+        
+
+ ); + }; }); function download(data: string, filename: string, type = "application/json") { - const a = document.createElement("a"); - const file = new Blob([data], { type: type }); - const url = URL.createObjectURL(file); + const a = document.createElement("a"); + const file = new Blob([data], { type: type }); + const url = URL.createObjectURL(file); - a.href = url; - a.download = filename; + a.href = url; + a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeout(() => { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); } diff --git a/nodepkg/vueuikit/example/typography.tsx b/nodepkg/vueuikit/example/typography.tsx index 1bbfefbf..1664a1ae 100644 --- a/nodepkg/vueuikit/example/typography.tsx +++ b/nodepkg/vueuikit/example/typography.tsx @@ -2,52 +2,52 @@ import { component } from "@innoai-tech/vuekit"; import { Box, ThemeProvider } from "@innoai-tech/vueuikit"; export default component(() => { - const theme = ThemeProvider.use(); + const theme = ThemeProvider.use(); - return () => ( - <> - - {theme.token.textStyle.tokens.map((textStyle) => ( - ( + <> + + {theme.token.textStyle.tokens.map((textStyle) => ( + - - {textStyle} - - - 中文测试 - - ))} - - - ); + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + }} + > + + {textStyle} + + + 中文测试 + + ))} + + + ); }); diff --git a/nodepkg/vueuikit/package.json b/nodepkg/vueuikit/package.json index 1e2b72aa..2be391c6 100644 --- a/nodepkg/vueuikit/package.json +++ b/nodepkg/vueuikit/package.json @@ -46,9 +46,9 @@ "directory": "nodepkg/vueuikit" }, "scripts": { - "lint": "bunx --bun @biomejs/biome check --apply .", + "lint": "bunx --bun prettier --write . ", "build": "bunx --bun monobundle", "prepublishOnly": "bun run build" }, "type": "module" -} \ No newline at end of file +} diff --git a/nodepkg/vueuikit/src/Box.tsx b/nodepkg/vueuikit/src/Box.tsx index 9468a63f..9ebf8426 100644 --- a/nodepkg/vueuikit/src/Box.tsx +++ b/nodepkg/vueuikit/src/Box.tsx @@ -1,56 +1,60 @@ import { - type OverridableComponent, - type VElementType, - component, - t, ref, + type OverridableComponent, + type VElementType, + component, + t, + ref, } from "@innoai-tech/vuekit"; -import {onMounted} from "vue"; -import {CacheProvider} from "./CacheProvider"; -import {ThemeProvider} from "./ThemeProvider"; -import {type SystemStyleObject} from "./theming"; -import {useInsertStyles} from "./useInsertStyles"; +import { onMounted } from "vue"; +import { CacheProvider } from "./CacheProvider"; +import { ThemeProvider } from "./ThemeProvider"; +import { type SystemStyleObject } from "./theming"; +import { useInsertStyles } from "./useInsertStyles"; export type SxProps = { - sx: SystemStyleObject; + sx: SystemStyleObject; }; export const Box: OverridableComponent<{ - props: SxProps; - defaultComponent: "div"; + props: SxProps; + defaultComponent: "div"; }> = component( - { - sx: t.custom(), - component: t.custom().optional().default("div"), - }, - (props, {slots, expose}) => { - const theme = ThemeProvider.use(); - const cache = CacheProvider.use(); - - const serialized = theme.unstable_css(cache, props.sx ?? {}); - - const className = () => { - return serialized.name !== "0" ? `${cache.key}-${serialized.name}` : ""; - }; - - const insertStyle = useInsertStyles(cache); - - onMounted(() => { - insertStyle({ - serialized: serialized, - isStringTag: true, - }); - }); - - - const forwardRef = ref(null); - - expose({ - $$forwardRef: forwardRef, - }); - - return () => { - const Component: any = props.component ?? "div"; - return {slots}; - }; - }, + { + sx: t.custom(), + component: t.custom().optional().default("div"), + }, + (props, { slots, expose }) => { + const theme = ThemeProvider.use(); + const cache = CacheProvider.use(); + + const serialized = theme.unstable_css(cache, props.sx ?? {}); + + const className = () => { + return serialized.name !== "0" ? `${cache.key}-${serialized.name}` : ""; + }; + + const insertStyle = useInsertStyles(cache); + + onMounted(() => { + insertStyle({ + serialized: serialized, + isStringTag: true, + }); + }); + + const forwardRef = ref(null); + + expose({ + $$forwardRef: forwardRef, + }); + + return () => { + const Component: any = props.component ?? "div"; + return ( + + {slots} + + ); + }; + }, ) as any; diff --git a/nodepkg/vueuikit/src/CSSReset.tsx b/nodepkg/vueuikit/src/CSSReset.tsx index 62f294f6..f5a63b78 100644 --- a/nodepkg/vueuikit/src/CSSReset.tsx +++ b/nodepkg/vueuikit/src/CSSReset.tsx @@ -3,33 +3,33 @@ import { GlobalStyle } from "./GlobalStyle"; import { ThemeProvider } from "./ThemeProvider"; export const CSSReset = component(() => { - const theme = ThemeProvider.use(); + const theme = ThemeProvider.use(); - const rootVars = theme.rootCSSVars; + const rootVars = theme.rootCSSVars; - return () => { - return ( - { + return ( + - ); - }; + body: { + textStyle: "sys.body-medium", + }, + }} + /> + ); + }; }); diff --git a/nodepkg/vueuikit/src/CacheProvider.tsx b/nodepkg/vueuikit/src/CacheProvider.tsx index c5c54dfd..d3fcf806 100644 --- a/nodepkg/vueuikit/src/CacheProvider.tsx +++ b/nodepkg/vueuikit/src/CacheProvider.tsx @@ -3,11 +3,11 @@ import { type EmotionCache } from "@emotion/utils"; import { createProvider } from "@innoai-tech/vuekit"; export const CacheProvider = createProvider( - () => - createCache({ - key: "css", - }), - { - name: "Cache", - }, + () => + createCache({ + key: "css", + }), + { + name: "Cache", + }, ); diff --git a/nodepkg/vueuikit/src/GlobalStyle.tsx b/nodepkg/vueuikit/src/GlobalStyle.tsx index e87a15ce..b6058b81 100644 --- a/nodepkg/vueuikit/src/GlobalStyle.tsx +++ b/nodepkg/vueuikit/src/GlobalStyle.tsx @@ -7,25 +7,25 @@ import { type SystemStyleObject } from "./theming"; import { useInsertStyles } from "./useInsertStyles"; export const GlobalStyle = component( - { styles: t.custom() }, - ({ styles }) => { - const theme = ThemeProvider.use(); - const cache = CacheProvider.use(); + { styles: t.custom() }, + ({ styles }) => { + const theme = ThemeProvider.use(); + const cache = CacheProvider.use(); - const insert = useInsertStyles(cache); + const insert = useInsertStyles(cache); - const serialized = theme.unstable_css( - cache, - isString(styles) ? ({ "&": styles } as any) : styles, - ); + const serialized = theme.unstable_css( + cache, + isString(styles) ? ({ "&": styles } as any) : styles, + ); - onBeforeMount(() => { - insert({ - serialized, - withoutScoping: true, - }); - }); + onBeforeMount(() => { + insert({ + serialized, + withoutScoping: true, + }); + }); - return () => null; - }, + return () => null; + }, ); diff --git a/nodepkg/vueuikit/src/Overlay.tsx b/nodepkg/vueuikit/src/Overlay.tsx index 56c6bd85..c52e5429 100644 --- a/nodepkg/vueuikit/src/Overlay.tsx +++ b/nodepkg/vueuikit/src/Overlay.tsx @@ -1,182 +1,182 @@ /// import { - component, - createProvider, - rx, - subscribeUntilUnmount, - t, - tapEffect, - toObservable, + component, + createProvider, + rx, + subscribeUntilUnmount, + t, + tapEffect, + toObservable, } from "@innoai-tech/vuekit"; import { - type CSSProperties, - type Ref, - Teleport, - type VNodeChild, - cloneVNode, - onBeforeUnmount, - ref, - unref, + type CSSProperties, + type Ref, + Teleport, + type VNodeChild, + cloneVNode, + onBeforeUnmount, + ref, + unref, } from "vue"; export const OverlaySettingProvider = createProvider( - () => { - return { - mountPoint: () => document.body, - }; - }, - { - name: "OverlaySetting", - }, + () => { + return { + mountPoint: () => document.body, + }; + }, + { + name: "OverlaySetting", + }, ); const OverlayProvider = createProvider( - () => { - return new OverlayContext(ref(null), ref(null), () => false); - }, - { - name: "Overlay", - }, + () => { + return new OverlayContext(ref(null), ref(null), () => false); + }, + { + name: "Overlay", + }, ); class OverlayContext { - private children: OverlayContext[] = []; - - constructor( - private triggerRef: Ref, - private contentRef: Ref, - private isOpen: () => boolean, - ) {} - - add = (p: OverlayContext) => { - this.children = [...this.children, p]; - - return () => { - this.children = this.children.filter((c) => c !== p); - }; - }; - - isClickInside = (event: Event) => { - for (const child of this.children) { - if (child.isClickInside(event)) { - return true; - } - } - - const triggerEl = unref(this.triggerRef); - const contentEl = unref(this.contentRef); - - return ( - (triggerEl && - (triggerEl === event.target || - event.composedPath().includes(triggerEl))) || - (contentEl && - (contentEl === event.target || - event.composedPath().includes(contentEl))) - ); - }; - - topmost() { - return this.children.filter((c) => c.isOpen()).length === 0; - } + private children: OverlayContext[] = []; + + constructor( + private triggerRef: Ref, + private contentRef: Ref, + private isOpen: () => boolean, + ) {} + + add = (p: OverlayContext) => { + this.children = [...this.children, p]; + + return () => { + this.children = this.children.filter((c) => c !== p); + }; + }; + + isClickInside = (event: Event) => { + for (const child of this.children) { + if (child.isClickInside(event)) { + return true; + } + } + + const triggerEl = unref(this.triggerRef); + const contentEl = unref(this.contentRef); + + return ( + (triggerEl && + (triggerEl === event.target || + event.composedPath().includes(triggerEl))) || + (contentEl && + (contentEl === event.target || + event.composedPath().includes(contentEl))) + ); + }; + + topmost() { + return this.children.filter((c) => c.isOpen()).length === 0; + } } export const Overlay = component( - { - isOpen: t.boolean().optional(), - style: t.custom().optional(), - contentRef: t.custom>().optional(), - triggerRef: t.custom>().optional(), - - onClickOutside: t.custom<(e: Event) => void>(), - onEscKeydown: t.custom<(e: Event) => void>(), - onContentBeforeMount: t.custom<() => void>(), - $transition: t - .custom<(ctx: { content: JSX.Element | null }) => VNodeChild>() - .optional(), - $default: t.custom().optional(), - }, - (props, { slots, attrs, emit }) => { - const contentRef = props.contentRef || ref(null); - - const popperContext = new OverlayContext( - props.triggerRef ?? ref(null), - contentRef, - () => !!props.isOpen, - ); - - const setting = OverlaySettingProvider.use(); - - const parent = OverlayProvider.use(); - onBeforeUnmount(parent.add(popperContext)); - - if (window) { - rx( - toObservable(contentRef, "value"), - tapEffect((contentEl) => { - if (!contentEl) { - return; - } - - const handleClickOutside = (event: Event) => { - if (popperContext.isClickInside(event)) { - return; - } - emit("click-outside", event); - }; - - window.addEventListener("pointerdown", handleClickOutside); - return () => { - window.removeEventListener("pointerdown", handleClickOutside); - }; - }), - - tapEffect((contentEl) => { - if (!contentEl) { - return; - } - - const handleEscKeydown = (event: KeyboardEvent) => { - if (event.key === "Escape" && popperContext.topmost()) { - emit("esc-keydown", event); - } - }; - - window.addEventListener("keydown", handleEscKeydown); - return () => { - window.removeEventListener("keydown", handleEscKeydown); - }; - }), - subscribeUntilUnmount(), - ); - } - - return () => { - const content = props.isOpen - ? cloneVNode( -
- - {slots.default?.()} - -
, - { - onVnodeBeforeMount: () => { - emit("content-before-mount"); - }, - }, - ) - : null; - - return ( - - {slots.transition ? slots.transition({ content }) : content} - - ); - }; - }, - { - inheritAttrs: false, - name: "Overlay", - }, + { + isOpen: t.boolean().optional(), + style: t.custom().optional(), + contentRef: t.custom>().optional(), + triggerRef: t.custom>().optional(), + + onClickOutside: t.custom<(e: Event) => void>(), + onEscKeydown: t.custom<(e: Event) => void>(), + onContentBeforeMount: t.custom<() => void>(), + $transition: t + .custom<(ctx: { content: JSX.Element | null }) => VNodeChild>() + .optional(), + $default: t.custom().optional(), + }, + (props, { slots, attrs, emit }) => { + const contentRef = props.contentRef || ref(null); + + const popperContext = new OverlayContext( + props.triggerRef ?? ref(null), + contentRef, + () => !!props.isOpen, + ); + + const setting = OverlaySettingProvider.use(); + + const parent = OverlayProvider.use(); + onBeforeUnmount(parent.add(popperContext)); + + if (window) { + rx( + toObservable(contentRef, "value"), + tapEffect((contentEl) => { + if (!contentEl) { + return; + } + + const handleClickOutside = (event: Event) => { + if (popperContext.isClickInside(event)) { + return; + } + emit("click-outside", event); + }; + + window.addEventListener("pointerdown", handleClickOutside); + return () => { + window.removeEventListener("pointerdown", handleClickOutside); + }; + }), + + tapEffect((contentEl) => { + if (!contentEl) { + return; + } + + const handleEscKeydown = (event: KeyboardEvent) => { + if (event.key === "Escape" && popperContext.topmost()) { + emit("esc-keydown", event); + } + }; + + window.addEventListener("keydown", handleEscKeydown); + return () => { + window.removeEventListener("keydown", handleEscKeydown); + }; + }), + subscribeUntilUnmount(), + ); + } + + return () => { + const content = props.isOpen + ? cloneVNode( +
+ + {slots.default?.()} + +
, + { + onVnodeBeforeMount: () => { + emit("content-before-mount"); + }, + }, + ) + : null; + + return ( + + {slots.transition ? slots.transition({ content }) : content} + + ); + }; + }, + { + inheritAttrs: false, + name: "Overlay", + }, ); diff --git a/nodepkg/vueuikit/src/Popper.tsx b/nodepkg/vueuikit/src/Popper.tsx index a667b800..5dfc7a72 100644 --- a/nodepkg/vueuikit/src/Popper.tsx +++ b/nodepkg/vueuikit/src/Popper.tsx @@ -1,98 +1,98 @@ import { type VNode, type VNodeChild, component, t } from "@innoai-tech/vuekit"; import { - type Modifier, - type Placement, - createPopperLite, - flip, + type Modifier, + type Placement, + createPopperLite, + flip, } from "@popperjs/core"; import type { ModifierArguments, State } from "@popperjs/core"; import { cloneVNode, ref, watch } from "vue"; import { Overlay } from "./Overlay"; export function createPopperModifier>( - fn: (o: ModifierArguments) => State | undefined, - options: Omit, "fn" | "enabled">, + fn: (o: ModifierArguments) => State | undefined, + options: Omit, "fn" | "enabled">, ): Modifier { - return { - fn, - enabled: true, - ...options, - }; + return { + fn, + enabled: true, + ...options, + }; } export const Popper = component( - { - isOpen: Overlay.propTypes!.isOpen, - onClickOutside: Overlay.propTypes!.onClickOutside, + { + isOpen: Overlay.propTypes!.isOpen, + onClickOutside: Overlay.propTypes!.onClickOutside, - placement: t.custom().optional(), - modifiers: t.custom>>().optional(), + placement: t.custom().optional(), + modifiers: t.custom>>().optional(), - $transition: Overlay.propTypes!.$transition, - $content: t.custom(), - $default: t.custom(), - }, - (props, { slots, emit, attrs }) => { - const triggerRef = ref(null); - const contentRef = ref(null); + $transition: Overlay.propTypes!.$transition, + $content: t.custom(), + $default: t.custom(), + }, + (props, { slots, emit, attrs }) => { + const triggerRef = ref(null); + const contentRef = ref(null); - watch( - () => contentRef.value, - (contentEl) => { - if (contentEl && triggerRef.value) { - createPopperLite(triggerRef.value, contentEl, { - placement: props.placement ?? "bottom", - modifiers: [...(props.modifiers ?? []), flip], - }); - } - }, - ); + watch( + () => contentRef.value, + (contentEl) => { + if (contentEl && triggerRef.value) { + createPopperLite(triggerRef.value, contentEl, { + placement: props.placement ?? "bottom", + modifiers: [...(props.modifiers ?? []), flip], + }); + } + }, + ); - return () => { - const trigger = slots.default?.()[0]; + return () => { + const trigger = slots.default?.()[0]; - if (!trigger) { - return null; - } + if (!trigger) { + return null; + } - return ( - <> - {cloneVNode(trigger, { - ...attrs, - onVnodeMounted: (n) => { - triggerRef.value = resolveElement(n.el); - }, - })} - emit("click-outside", event)} - style={{ zIndex: 100 }} - $transition={slots.transition} - > - {slots.content?.()} - - - ); - }; - }, - { - name: "Popper", - inheritAttrs: false, - }, + return ( + <> + {cloneVNode(trigger, { + ...attrs, + onVnodeMounted: (n) => { + triggerRef.value = resolveElement(n.el); + }, + })} + emit("click-outside", event)} + style={{ zIndex: 100 }} + $transition={slots.transition} + > + {slots.content?.()} + + + ); + }; + }, + { + name: "Popper", + inheritAttrs: false, + }, ); function resolveElement(el: VNode["el"]): HTMLElement | null { - if (el) { - if (el instanceof HTMLElement) { - return el; - } + if (el) { + if (el instanceof HTMLElement) { + return el; + } - // Fragment - if (el instanceof Text) { - return resolveElement((el as any).nextElementSibling); - } - } - return null; + // Fragment + if (el instanceof Text) { + return resolveElement((el as any).nextElementSibling); + } + } + return null; } diff --git a/nodepkg/vueuikit/src/ThemeProvider.tsx b/nodepkg/vueuikit/src/ThemeProvider.tsx index 3969c327..5fbb0d92 100644 --- a/nodepkg/vueuikit/src/ThemeProvider.tsx +++ b/nodepkg/vueuikit/src/ThemeProvider.tsx @@ -4,5 +4,5 @@ import { defaultTheming } from "./theming"; export type Theme = typeof defaultTheming; export const ThemeProvider = createProvider(() => defaultTheming, { - name: "Theme", + name: "Theme", }); diff --git a/nodepkg/vueuikit/src/Transition.tsx b/nodepkg/vueuikit/src/Transition.tsx index 8046ac8b..b31563fa 100644 --- a/nodepkg/vueuikit/src/Transition.tsx +++ b/nodepkg/vueuikit/src/Transition.tsx @@ -1,135 +1,135 @@ import { component, t } from "@innoai-tech/vuekit"; import { - type Easing, - animate, - cubicBezier as cubicBezierFunc, + type Easing, + animate, + cubicBezier as cubicBezierFunc, } from "popmotion"; import { Transition } from "vue"; const cubicBezier = (mX1: number, mY1: number, mX2: number, mY2: number) => { - return Object.assign(cubicBezierFunc(mX1, mY1, mX2, mY2), { - toString() { - return `cubic-bezier(${mX1}, ${mY1}, ${mX2}, ${mY2})`; - }, - }); + return Object.assign(cubicBezierFunc(mX1, mY1, mX2, mY2), { + toString() { + return `cubic-bezier(${mX1}, ${mY1}, ${mX2}, ${mY2})`; + }, + }); }; export const transition = { - duration: { - sm1: 50, - sm2: 100, - sm3: 150, - sm4: 200, - md1: 250, - md2: 300, - md3: 350, - md4: 400, - lg1: 450, - lg2: 500, - lg3: 550, - lg4: 600, - xl1: 700, - xl2: 800, - xl3: 900, - xl4: 1000, - }, - easing: { - linear: cubicBezier(0, 0, 1, 1), - standard: Object.assign(cubicBezier(0.2, 0, 0, 1), { - accelerate: cubicBezier(0.3, 0, 1, 1), - decelerate: cubicBezier(0, 0, 0, 1), - }), - emphasized: Object.assign(cubicBezier(0.2, 0, 0, 1), { - accelerate: cubicBezier(0.3, 0, 0.8, 0.15), - decelerate: cubicBezier(0.05, 0.7, 0.1, 1), - }), - legacy: Object.assign(cubicBezier(0.4, 0, 0.2, 1), { - decelerate: cubicBezier(0.0, 0, 0.2, 1), - accelerate: cubicBezier(0.4, 0, 1.0, 1), - }), - }, + duration: { + sm1: 50, + sm2: 100, + sm3: 150, + sm4: 200, + md1: 250, + md2: 300, + md3: 350, + md4: 400, + lg1: 450, + lg2: 500, + lg3: 550, + lg4: 600, + xl1: 700, + xl2: 800, + xl3: 900, + xl4: 1000, + }, + easing: { + linear: cubicBezier(0, 0, 1, 1), + standard: Object.assign(cubicBezier(0.2, 0, 0, 1), { + accelerate: cubicBezier(0.3, 0, 1, 1), + decelerate: cubicBezier(0, 0, 0, 1), + }), + emphasized: Object.assign(cubicBezier(0.2, 0, 0, 1), { + accelerate: cubicBezier(0.3, 0, 0.8, 0.15), + decelerate: cubicBezier(0.05, 0.7, 0.1, 1), + }), + legacy: Object.assign(cubicBezier(0.4, 0, 0.2, 1), { + decelerate: cubicBezier(0.0, 0, 0.2, 1), + accelerate: cubicBezier(0.4, 0, 1.0, 1), + }), + }, }; export const defineTransition = ( - enter: { - from: T; - to: T; - duration?: number; - easing?: Easing; - }, - leave?: { - from: T; - to: T; - duration?: number; - easing?: Easing; - }, + enter: { + from: T; + to: T; + duration?: number; + easing?: Easing; + }, + leave?: { + from: T; + to: T; + duration?: number; + easing?: Easing; + }, ) => { - const finalLeave = leave ?? { - ...enter, - from: { - ...enter.to, - }, - to: { - ...enter.from, - }, - }; + const finalLeave = leave ?? { + ...enter, + from: { + ...enter.to, + }, + to: { + ...enter.from, + }, + }; - return component( - { - onComplete: t.custom<(v: "enter" | "leave") => void>(), - $default: t.custom(), - }, - (_, { slots, emit }) => { - let animated: any; + return component( + { + onComplete: t.custom<(v: "enter" | "leave") => void>(), + $default: t.custom(), + }, + (_, { slots, emit }) => { + let animated: any; - const onEnter = (e: Element, done: () => void) => { - animated = animate({ - ...enter, - autoplay: true, - onComplete: () => { - done(); - emit("complete", "enter"); - }, - onUpdate: (style) => { - Object.assign((e as HTMLElement).style, style); - }, - }); - }; + const onEnter = (e: Element, done: () => void) => { + animated = animate({ + ...enter, + autoplay: true, + onComplete: () => { + done(); + emit("complete", "enter"); + }, + onUpdate: (style) => { + Object.assign((e as HTMLElement).style, style); + }, + }); + }; - const onLeave = (e: Element, done: () => void) => { - animated = animate({ - ...finalLeave, - autoplay: true, - onComplete: () => { - done(); - emit("complete", "leave"); - }, - onUpdate: (style) => { - Object.assign((e as HTMLElement).style, style); - }, - }); - }; + const onLeave = (e: Element, done: () => void) => { + animated = animate({ + ...finalLeave, + autoplay: true, + onComplete: () => { + done(); + emit("complete", "leave"); + }, + onUpdate: (style) => { + Object.assign((e as HTMLElement).style, style); + }, + }); + }; - const onCancelled = () => { - animated?.stop(); - }; + const onCancelled = () => { + animated?.stop(); + }; - return () => { - return ( - - {slots.default?.()} - - ); - }; - }, - { - name: "Transition", - }, - ); + return () => { + return ( + + {slots.default?.()} + + ); + }; + }, + { + name: "Transition", + }, + ); }; diff --git a/nodepkg/vueuikit/src/styled.tsx b/nodepkg/vueuikit/src/styled.tsx index 948a4de6..14c9c888 100644 --- a/nodepkg/vueuikit/src/styled.tsx +++ b/nodepkg/vueuikit/src/styled.tsx @@ -1,15 +1,15 @@ import { isFunction, isPlainObject } from "@innoai-tech/lodash"; import { - type AnyType, - type InternalEmitsOf, - type InternalPropsOf, - type InternalSlotsOf, - type OverridableComponent, - type PublicPropsOf, - type SetupContext, - type VElementType, - component, - t, + type AnyType, + type InternalEmitsOf, + type InternalPropsOf, + type InternalSlotsOf, + type OverridableComponent, + type PublicPropsOf, + type SetupContext, + type VElementType, + component, + t, } from "@innoai-tech/vuekit"; import type { VNode } from "vue"; import { cloneVNode, onBeforeMount, onMounted, ref } from "vue"; @@ -20,152 +20,152 @@ import { type SystemStyleObject } from "./theming"; import { useInsertStyles } from "./useInsertStyles"; const defaultSetup = (props: any, ctx: any) => (Wrap: VElementType) => { - const dataProps: Record = {}; + const dataProps: Record = {}; - for (const prop in props) { - if (prop === "component" || prop === "sx") { - continue; - } + for (const prop in props) { + if (prop === "component" || prop === "sx") { + continue; + } - if ((props as any)[prop]) { - dataProps[`data-${prop}`] = (props as any)[prop]; - } - } + if ((props as any)[prop]) { + dataProps[`data-${prop}`] = (props as any)[prop]; + } + } - return {ctx.slots}; + return {ctx.slots}; }; export type StyledSetupFunction< - DefaultComponent extends VElementType, - PropTypes extends Record, + DefaultComponent extends VElementType, + PropTypes extends Record, > = ( - props: InternalPropsOf, - ctx: SetupContext, InternalSlotsOf>, + props: InternalPropsOf, + ctx: SetupContext, InternalSlotsOf>, ) => (Wrap: DefaultComponent) => VNode | null; export function styled< - DefaultComponent extends VElementType, - PropTypes extends Record = {}, + DefaultComponent extends VElementType, + PropTypes extends Record = {}, >( - defaultComponent: DefaultComponent, - setup?: StyledSetupFunction, + defaultComponent: DefaultComponent, + setup?: StyledSetupFunction, ): (presetSx: SystemStyleObject) => OverridableComponent<{ - props: PublicPropsOf & Partial; - defaultComponent: DefaultComponent; + props: PublicPropsOf & Partial; + defaultComponent: DefaultComponent; }>; export function styled< - DefaultComponent extends VElementType, - PropTypes extends Record = {}, + DefaultComponent extends VElementType, + PropTypes extends Record = {}, >( - defaultComponent: DefaultComponent, - propTypes: PropTypes, - setup?: StyledSetupFunction, + defaultComponent: DefaultComponent, + propTypes: PropTypes, + setup?: StyledSetupFunction, ): (presetSx: SystemStyleObject) => OverridableComponent<{ - props: PublicPropsOf & Partial; - defaultComponent: DefaultComponent; + props: PublicPropsOf & Partial; + defaultComponent: DefaultComponent; }>; export function styled< - DefaultComponent extends VElementType, - PropTypes extends Record = {}, + DefaultComponent extends VElementType, + PropTypes extends Record = {}, >( - defaultComponent: DefaultComponent, - propTypesOrSetup?: - | PropTypes - | StyledSetupFunction, - setup?: StyledSetupFunction, + defaultComponent: DefaultComponent, + propTypesOrSetup?: + | PropTypes + | StyledSetupFunction, + setup?: StyledSetupFunction, ): (presetSx: SystemStyleObject) => OverridableComponent<{ - props: PublicPropsOf & Partial; - defaultComponent: DefaultComponent; + props: PublicPropsOf & Partial; + defaultComponent: DefaultComponent; }> { - const finalSetup = - (isFunction(propTypesOrSetup) ? propTypesOrSetup : setup) ?? defaultSetup; - const finalPropTypes = isPlainObject(propTypesOrSetup) - ? propTypesOrSetup - : {}; - - return (presetSx: SystemStyleObject) => { - const c = component( - { - ...finalPropTypes, - sx: t.custom().optional(), - component: t.custom().optional(), - }, - (props, ctx) => { - const theme = ThemeProvider.use(); - const cache = CacheProvider.use(); - const insertCSS = useInsertStyles(cache); - - (presetSx as any).label = c.name; - - const sxClassName = ref(""); - - const presetSxSerialized = theme.unstable_css(cache, presetSx); - - const className = () => - presetSxSerialized.name !== "0" - ? `${cache.key}-${presetSxSerialized.name}${sxClassName.value}` - : `${sxClassName.value}`; - - if ((defaultComponent as any).__styled) { - const serialized = theme.unstable_css(cache, props.sx ?? {}); - - if (serialized.name !== "0") { - sxClassName.value = ` ${cache.key}-${serialized.name}`; - } - - onMounted(() => { - insertCSS({ - serialized: presetSxSerialized, - isStringTag: true, - }); - - insertCSS({ - serialized, - isStringTag: true, - }); - }); - } else { - onBeforeMount(() => { - insertCSS({ - serialized: presetSxSerialized, - isStringTag: true, - }); - }); - } - - const render = finalSetup(props as any, ctx as any); - - return () => { - if ((defaultComponent as any).__styled) { - const ret = render(defaultComponent); - - if (ret) { - return cloneVNode(ret, { - component: (props as any).component, - class: className(), - }); - } - - return null; - } - - const ret = render(Box as any); - - if (ret) { - return cloneVNode(ret, { - component: (props as any).component || defaultComponent, - sx: (props as any).sx, - class: className(), - }); - } - - return null; - }; - }, - ) as any; - - c.__styled = true; - - return c; - }; + const finalSetup = + (isFunction(propTypesOrSetup) ? propTypesOrSetup : setup) ?? defaultSetup; + const finalPropTypes = isPlainObject(propTypesOrSetup) + ? propTypesOrSetup + : {}; + + return (presetSx: SystemStyleObject) => { + const c = component( + { + ...finalPropTypes, + sx: t.custom().optional(), + component: t.custom().optional(), + }, + (props, ctx) => { + const theme = ThemeProvider.use(); + const cache = CacheProvider.use(); + const insertCSS = useInsertStyles(cache); + + (presetSx as any).label = c.name; + + const sxClassName = ref(""); + + const presetSxSerialized = theme.unstable_css(cache, presetSx); + + const className = () => + presetSxSerialized.name !== "0" + ? `${cache.key}-${presetSxSerialized.name}${sxClassName.value}` + : `${sxClassName.value}`; + + if ((defaultComponent as any).__styled) { + const serialized = theme.unstable_css(cache, props.sx ?? {}); + + if (serialized.name !== "0") { + sxClassName.value = ` ${cache.key}-${serialized.name}`; + } + + onMounted(() => { + insertCSS({ + serialized: presetSxSerialized, + isStringTag: true, + }); + + insertCSS({ + serialized, + isStringTag: true, + }); + }); + } else { + onBeforeMount(() => { + insertCSS({ + serialized: presetSxSerialized, + isStringTag: true, + }); + }); + } + + const render = finalSetup(props as any, ctx as any); + + return () => { + if ((defaultComponent as any).__styled) { + const ret = render(defaultComponent); + + if (ret) { + return cloneVNode(ret, { + component: (props as any).component, + class: className(), + }); + } + + return null; + } + + const ret = render(Box as any); + + if (ret) { + return cloneVNode(ret, { + component: (props as any).component || defaultComponent, + sx: (props as any).sx, + class: className(), + }); + } + + return null; + }; + }, + ) as any; + + c.__styled = true; + + return c; + }; } diff --git a/nodepkg/vueuikit/src/theming/CSSProcessor.ts b/nodepkg/vueuikit/src/theming/CSSProcessor.ts index f7e66485..d66a0768 100644 --- a/nodepkg/vueuikit/src/theming/CSSProcessor.ts +++ b/nodepkg/vueuikit/src/theming/CSSProcessor.ts @@ -1,214 +1,214 @@ import { - get, - isEmpty, - isPlainObject, - kebabCase, - last, - set, + get, + isEmpty, + isPlainObject, + kebabCase, + last, + set, } from "@innoai-tech/lodash"; import { - aliases, - extensions, - getSupportedPseudoClasses, - pseudoSelectors, + aliases, + extensions, + getSupportedPseudoClasses, + pseudoSelectors, } from "./csstype"; export interface Mixin { - get(token: string): {} | undefined; + get(token: string): {} | undefined; } export class CSSProcessor { - static supportedPseudoClasses: Record = - getSupportedPseudoClasses(); - - static convertSelector = (sel: string) => { - if ((pseudoSelectors as any)[sel]) { - return (pseudoSelectors as any)[sel]; - } - - let selector = sel; - - if ( - selector.startsWith("$") || - selector.endsWith("$") || - selector.startsWith("_") - ) { - let prefix = ""; - let suffix = ""; - - if (selector.startsWith("_")) { - prefix = "&"; - selector = selector.slice(1); - } else if (selector.startsWith("$")) { - prefix = "& "; - selector = selector.slice(1); - } else { - suffix = " &"; - selector = selector.slice(0, selector.length - 1); - } - - if (selector.startsWith("data") || selector.startsWith("aria")) { - const [k, v] = selector.split("__"); - return v - ? `${prefix}[${kebabCase(k)}='${kebabCase(v)}']${suffix}` - : `${prefix}[${kebabCase(k)}]${suffix}`; - } - - if (prefix === "&") { - if (selector.startsWith("$")) { - return `${prefix}::${selector.slice(1)}`; - } - - if (CSSProcessor.supportedPseudoClasses[selector]) { - const pseudoClass = CSSProcessor.supportedPseudoClasses[selector]; - - return [ - `${prefix}:${pseudoClass}`, - `${prefix}[data-${pseudoClass}]:not([data-${pseudoClass}='false'])`, - `${prefix}.${pseudoClass}`, // fallback with class for overwrite - ].join(", "); - } - - const [k, v] = selector.split("__"); - const stateKey = kebabCase(k); - - return v - ? `${prefix}[data-${stateKey}='${kebabCase(v)}']` - : `${prefix}[data-${stateKey}]:not([data-${stateKey}='false'])`; - } - } - - return; - }; - - static walkStateValues = ( - values: Record, - cb: (v: any, path: string[], when: string[]) => void, - ctx: { - default: object; - selectorPath: string[]; - path: string[]; - } = { - default: {}, - selectorPath: [], - path: [], - }, - ) => { - const { $, ...others } = values; - - for (const k in others) { - const v = others[k] as any; - - const finalDefault = others["default"] ?? ctx["default"]; - const selectorForCurrentNode = - $ ?? get(finalDefault, [...ctx.path.slice(1), "$"]); - const selectorPath = selectorForCurrentNode - ? [...ctx.selectorPath, selectorForCurrentNode] - : ctx.selectorPath; - const path = [...ctx.path, k]; - - if (isPlainObject(v)) { - CSSProcessor.walkStateValues(v, cb, { - path, - selectorPath: selectorPath, - default: finalDefault, - }); - continue; - } - - cb(v, path, selectorPath); - } - }; - - constructor( - private opt: { - mixins: Record; - processValue: (p: string, v: any) => any; - varPrefix: string; - }, - ) {} - - processAll(src: Record, full = true) { - const ret: Array> = []; - - const { state, extends: ex, ...others } = src; - - if (ex) { - // extends should process before all - for (const sx of ex) { - ret.push(...this.processAll(sx, full)); - } - } - - if (state) { - const cssVars: Record = {}; - const finalSx: {} = {}; - - CSSProcessor.walkStateValues(state, (v, path, selectorPath) => { - const varName = `--${this.opt.varPrefix}-state-${path.join("-")}`; - const prop = last(path) ?? ""; - const sx: Record = {}; - this.process(sx, prop, v, false); - - cssVars[varName] = sx[prop]; - - set(finalSx, [...selectorPath, prop], `var(${varName})`); - }); - - ret.push(cssVars); - ret.push(...this.processAll(finalSx)); - } - - if (!isEmpty(others)) { - const sx = {}; - this.processTo(sx, others, full); - ret.push(sx); - } - - return ret; - } - - processTo(dest: Record, src: Record, full = true) { - for (const p in src) { - this.process(dest, p, src[p], full); - } - } - - process(dest: Record, prop: string, v: any, full = true) { - if (this.opt.mixins[prop]) { - const mixinObj = this.opt.mixins[prop]?.get(v); - if (mixinObj) { - for (const p in mixinObj) { - this.process(dest, p, (mixinObj as any)[p], full); - } - } - return; - } - - let p = prop; - - if (isPlainObject(v)) { - // resolve pseudoSelectors or std selector - p = CSSProcessor.convertSelector(p) ?? p; - - dest[p] = dest[p] ?? {}; - this.processTo(dest[p], v); - return; - } - - if (full) { - // resolve aliases - p = (aliases as any)[p] ?? p; - - // extensions should process value for each - if ((extensions as any)[p]) { - for (const p2 of (extensions as any)[p]) { - dest[p2] = this.opt.processValue(p2, v); - } - return; - } - } - - dest[p] = this.opt.processValue(p, v); - } + static supportedPseudoClasses: Record = + getSupportedPseudoClasses(); + + static convertSelector = (sel: string) => { + if ((pseudoSelectors as any)[sel]) { + return (pseudoSelectors as any)[sel]; + } + + let selector = sel; + + if ( + selector.startsWith("$") || + selector.endsWith("$") || + selector.startsWith("_") + ) { + let prefix = ""; + let suffix = ""; + + if (selector.startsWith("_")) { + prefix = "&"; + selector = selector.slice(1); + } else if (selector.startsWith("$")) { + prefix = "& "; + selector = selector.slice(1); + } else { + suffix = " &"; + selector = selector.slice(0, selector.length - 1); + } + + if (selector.startsWith("data") || selector.startsWith("aria")) { + const [k, v] = selector.split("__"); + return v + ? `${prefix}[${kebabCase(k)}='${kebabCase(v)}']${suffix}` + : `${prefix}[${kebabCase(k)}]${suffix}`; + } + + if (prefix === "&") { + if (selector.startsWith("$")) { + return `${prefix}::${selector.slice(1)}`; + } + + if (CSSProcessor.supportedPseudoClasses[selector]) { + const pseudoClass = CSSProcessor.supportedPseudoClasses[selector]; + + return [ + `${prefix}:${pseudoClass}`, + `${prefix}[data-${pseudoClass}]:not([data-${pseudoClass}='false'])`, + `${prefix}.${pseudoClass}`, // fallback with class for overwrite + ].join(", "); + } + + const [k, v] = selector.split("__"); + const stateKey = kebabCase(k); + + return v + ? `${prefix}[data-${stateKey}='${kebabCase(v)}']` + : `${prefix}[data-${stateKey}]:not([data-${stateKey}='false'])`; + } + } + + return; + }; + + static walkStateValues = ( + values: Record, + cb: (v: any, path: string[], when: string[]) => void, + ctx: { + default: object; + selectorPath: string[]; + path: string[]; + } = { + default: {}, + selectorPath: [], + path: [], + }, + ) => { + const { $, ...others } = values; + + for (const k in others) { + const v = others[k] as any; + + const finalDefault = others["default"] ?? ctx["default"]; + const selectorForCurrentNode = + $ ?? get(finalDefault, [...ctx.path.slice(1), "$"]); + const selectorPath = selectorForCurrentNode + ? [...ctx.selectorPath, selectorForCurrentNode] + : ctx.selectorPath; + const path = [...ctx.path, k]; + + if (isPlainObject(v)) { + CSSProcessor.walkStateValues(v, cb, { + path, + selectorPath: selectorPath, + default: finalDefault, + }); + continue; + } + + cb(v, path, selectorPath); + } + }; + + constructor( + private opt: { + mixins: Record; + processValue: (p: string, v: any) => any; + varPrefix: string; + }, + ) {} + + processAll(src: Record, full = true) { + const ret: Array> = []; + + const { state, extends: ex, ...others } = src; + + if (ex) { + // extends should process before all + for (const sx of ex) { + ret.push(...this.processAll(sx, full)); + } + } + + if (state) { + const cssVars: Record = {}; + const finalSx: {} = {}; + + CSSProcessor.walkStateValues(state, (v, path, selectorPath) => { + const varName = `--${this.opt.varPrefix}-state-${path.join("-")}`; + const prop = last(path) ?? ""; + const sx: Record = {}; + this.process(sx, prop, v, false); + + cssVars[varName] = sx[prop]; + + set(finalSx, [...selectorPath, prop], `var(${varName})`); + }); + + ret.push(cssVars); + ret.push(...this.processAll(finalSx)); + } + + if (!isEmpty(others)) { + const sx = {}; + this.processTo(sx, others, full); + ret.push(sx); + } + + return ret; + } + + processTo(dest: Record, src: Record, full = true) { + for (const p in src) { + this.process(dest, p, src[p], full); + } + } + + process(dest: Record, prop: string, v: any, full = true) { + if (this.opt.mixins[prop]) { + const mixinObj = this.opt.mixins[prop]?.get(v); + if (mixinObj) { + for (const p in mixinObj) { + this.process(dest, p, (mixinObj as any)[p], full); + } + } + return; + } + + let p = prop; + + if (isPlainObject(v)) { + // resolve pseudoSelectors or std selector + p = CSSProcessor.convertSelector(p) ?? p; + + dest[p] = dest[p] ?? {}; + this.processTo(dest[p], v); + return; + } + + if (full) { + // resolve aliases + p = (aliases as any)[p] ?? p; + + // extensions should process value for each + if ((extensions as any)[p]) { + for (const p2 of (extensions as any)[p]) { + dest[p2] = this.opt.processValue(p2, v); + } + return; + } + } + + dest[p] = this.opt.processValue(p, v); + } } diff --git a/nodepkg/vueuikit/src/theming/Theming.ts b/nodepkg/vueuikit/src/theming/Theming.ts index 2c028f9a..e2c3b478 100644 --- a/nodepkg/vueuikit/src/theming/Theming.ts +++ b/nodepkg/vueuikit/src/theming/Theming.ts @@ -1,363 +1,361 @@ -import {serializeStyles} from "@emotion/serialize"; -import type {EmotionCache} from "@emotion/utils"; +import { serializeStyles } from "@emotion/serialize"; +import type { EmotionCache } from "@emotion/utils"; import { - camelCase, - isNumber, - isObject, - isString, - isUndefined, - kebabCase, - mapValues, - set, + camelCase, + isNumber, + isObject, + isString, + isUndefined, + kebabCase, + mapValues, + set, } from "@innoai-tech/lodash"; -import {CSSProcessor} from "./CSSProcessor"; -import {type CSSAllProps, type FullCSSObject} from "./csstype"; +import { CSSProcessor } from "./CSSProcessor"; +import { type CSSAllProps, type FullCSSObject } from "./csstype"; import { - DesignToken, - type DesignTokenOptionAny, - DesignTokenType, - type DesignTokens, - type FigmaToken, - type FigmaTokenValues, - Mixin, - TokenSet, - isVariant, - setTo, groupSysColors, + DesignToken, + type DesignTokenOptionAny, + DesignTokenType, + type DesignTokens, + type FigmaToken, + type FigmaTokenValues, + Mixin, + TokenSet, + isVariant, + setTo, + groupSysColors, } from "./token"; export interface ThemingOptions { - mode: "light" | "dark"; - varPrefix: string; + mode: "light" | "dark"; + varPrefix: string; } export interface TokenGetter { - (token: Token): Value; + (token: Token): Value; - tokens: string[]; + tokens: string[]; } const toMap = (list: string[]): { [K: string]: true } => - list.reduce((ret, v) => { - return Object.assign(ret, { - [v]: true, - }); - }, {}) as any; + list.reduce((ret, v) => { + return Object.assign(ret, { + [v]: true, + }); + }, {}) as any; export class Theming> { - private static propsCanPercent = toMap([...DesignToken.boxSize({}).on]); - - private static propsCanBaseDp = toMap([ - ...DesignToken.boxSize({}).on, - ...DesignToken.space({}).on, - ...DesignToken.fontSize({}).on, - ...DesignToken.letterSpacing({}).on, - ...DesignToken.lineHeight({}).on, - ...DesignToken.rounded({}).on, - ]); - - static create>( - theme: T, - options: Partial, - ) { - return new Theming(theme, options); + private static propsCanPercent = toMap([...DesignToken.boxSize({}).on]); + + private static propsCanBaseDp = toMap([ + ...DesignToken.boxSize({}).on, + ...DesignToken.space({}).on, + ...DesignToken.fontSize({}).on, + ...DesignToken.letterSpacing({}).on, + ...DesignToken.lineHeight({}).on, + ...DesignToken.rounded({}).on, + ]); + + static create>( + theme: T, + options: Partial, + ) { + return new Theming(theme, options); + } + + private readonly mode: string; + private readonly varPrefix: string; + private readonly cssVars: { [K: string]: any } = {}; + private readonly tokens: { [K in keyof T]: TokenSet } = {} as any; + private readonly propValues: { [K in string]: TokenSet } = {}; + private readonly mixins: { [K: string]: Mixin } = {} as any; + + dp = (v: number) => + v === 0 ? 0 : `calc(${v} * var(${this.cssVar("space", "dp")}))`; + + private transformFallback = (p: string, v: any) => { + if (Theming.propsCanBaseDp[p] && isNumber(v)) { + if (Theming.propsCanPercent[p] && Math.abs(v) < 1) { + return `${v * 100}%`; + } + return this.dp(v); } + return v; + }; - private readonly mode: string; - private readonly varPrefix: string; - private readonly cssVars: { [K: string]: any } = {}; - private readonly tokens: { [K in keyof T]: TokenSet } = {} as any; - private readonly propValues: { [K in string]: TokenSet } = {}; - private readonly mixins: { [K: string]: Mixin } = {} as any; - - dp = (v: number) => - v === 0 ? 0 : `calc(${v} * var(${this.cssVar("space", "dp")}))`; - - private transformFallback = (p: string, v: any) => { - if (Theming.propsCanBaseDp[p] && isNumber(v)) { - if (Theming.propsCanPercent[p] && Math.abs(v) < 1) { - return `${v * 100}%`; - } - return this.dp(v); - } - return v; - }; + constructor( + public readonly theme: T, + options: Partial = {}, + ) { + this.varPrefix = options.varPrefix ?? "vk"; + this.mode = options.mode ?? "light"; - constructor( - public readonly theme: T, - options: Partial = {}, - ) { - this.varPrefix = options.varPrefix ?? "vk"; - this.mode = options.mode ?? "light"; + set(this.cssVars, [this.cssVar("space", "dp")], "0.1rem"); - set(this.cssVars, [this.cssVar("space", "dp")], "0.1rem"); + for (const scale in theme) { + const dt = theme[scale]; - for (const scale in theme) { - const dt = theme[scale]; + if (!dt) { + continue; + } - if (!dt) { - continue; - } + if (dt.type === DesignTokenType.var) { + const dtv = new TokenSet(dt, { + cssVar: (token: string) => this.cssVar(scale, token), + transformFallback: (v) => this.transformFallback(dt.on[0], v), + }); - if (dt.type === DesignTokenType.var) { - const dtv = new TokenSet(dt, { - cssVar: (token: string) => this.cssVar(scale, token), - transformFallback: (v) => this.transformFallback(dt.on[0], v), - }); - - this.tokens[scale] = dtv; - for (const prop of dt.on) { - this.propValues[prop] = dtv; - } - - for (const token of dtv.tokens) { - for (const mode of ["light", "dark"] as const) { - const modePseudo = mode === this.mode ? "_default" : `_${mode}`; - - const v = dtv.get(token, modePseudo, true); - if (!isUndefined(v)) { - if (modePseudo === "_default") { - set(this.cssVars, [this.cssVar(scale, token)], v); - } else { - set(this.cssVars, [modePseudo, this.cssVar(scale, token)], v); - } - } - } - } - - continue; - } + this.tokens[scale] = dtv; + for (const prop of dt.on) { + this.propValues[prop] = dtv; + } - if (dt.type === DesignTokenType.mixin) { - const m = new Mixin(dt); - for (const prop of dt.on) { - this.mixins[prop] = m; - } + for (const token of dtv.tokens) { + for (const mode of ["light", "dark"] as const) { + const modePseudo = mode === this.mode ? "_default" : `_${mode}`; + + const v = dtv.get(token, modePseudo, true); + if (!isUndefined(v)) { + if (modePseudo === "_default") { + set(this.cssVars, [this.cssVar(scale, token)], v); + } else { + set(this.cssVars, [modePseudo, this.cssVar(scale, token)], v); + } } + } } - } - private cssVar(scale: string, key: string) { - return `--${this.varPrefix}-${kebabCase(scale)}__${key - .replaceAll("/", "--") - .replaceAll(".", "__")}`; - } + continue; + } - get rootCSSVars(): DesignTokens & CSSAllProps { - return this.cssVars as any; + if (dt.type === DesignTokenType.mixin) { + const m = new Mixin(dt); + for (const prop of dt.on) { + this.mixins[prop] = m; + } + } } - - token: { - [K in keyof T]: TokenGetter< - TokenSet["__Tokens"], - T[K]["__ValueType"] - >; - } = new Proxy( - {}, - { - get: (_, prop) => { - if (this.tokens[prop as any]) { - return Object.assign( - (token: string) => - this.tokens[prop as any]?.get(token, `_${this.mode}`), - { - tokens: this.tokens[prop as any]?.tokens, - }, - ); - } - if (this.mixins[prop as any]) { - return Object.assign( - (token: string) => this.mixins[prop as any]?.get(token), - { - tokens: this.mixins[prop as any]?.tokens, - }, - ); - } - return; + } + + private cssVar(scale: string, key: string) { + return `--${this.varPrefix}-${kebabCase(scale)}__${key + .replaceAll("/", "--") + .replaceAll(".", "__")}`; + } + + get rootCSSVars(): DesignTokens & CSSAllProps { + return this.cssVars as any; + } + + token: { + [K in keyof T]: TokenGetter< + TokenSet["__Tokens"], + T[K]["__ValueType"] + >; + } = new Proxy( + {}, + { + get: (_, prop) => { + if (this.tokens[prop as any]) { + return Object.assign( + (token: string) => + this.tokens[prop as any]?.get(token, `_${this.mode}`), + { + tokens: this.tokens[prop as any]?.tokens, }, - }, - ) as any; - - private processValue = (p: string, v: any) => { - if (isVariant(v)) { - const cssVar = this.propValues[p]?.use(v.token, true); - return cssVar ? v(cssVar) : undefined; + ); } - return this.propValues[p]?.use(v) ?? this.transformFallback(p, v); - }; - - unstable_sx = ( - sx: FullCSSObject & CSSAllProps>, - ): Array> => { - return new CSSProcessor({ - mixins: this.mixins, - varPrefix: this.varPrefix, - processValue: this.processValue, - }).processAll(sx); + if (this.mixins[prop as any]) { + return Object.assign( + (token: string) => this.mixins[prop as any]?.get(token), + { + tokens: this.mixins[prop as any]?.tokens, + }, + ); + } + return; + }, + }, + ) as any; + + private processValue = (p: string, v: any) => { + if (isVariant(v)) { + const cssVar = this.propValues[p]?.use(v.token, true); + return cssVar ? v(cssVar) : undefined; + } + return this.propValues[p]?.use(v) ?? this.transformFallback(p, v); + }; + + unstable_sx = ( + sx: FullCSSObject & CSSAllProps>, + ): Array> => { + return new CSSProcessor({ + mixins: this.mixins, + varPrefix: this.varPrefix, + processValue: this.processValue, + }).processAll(sx); + }; + + unstable_css( + cache: EmotionCache, + sx: FullCSSObject & CSSAllProps>, + ) { + const inputs = (sx ?? {}) as any; + + // mutate for cache + inputs.__emotion_styles = + inputs.__emotion_styles ?? + serializeStyles(this.unstable_sx(sx), cache?.registered, {}); + + return inputs.__emotion_styles; + } + + toFigmaTokens() { + const seedTokens: FigmaTokenValues = { + space: { + dp: { + $type: "sizing", + $value: 1, + }, + }, }; - unstable_css( - cache: EmotionCache, - sx: FullCSSObject & CSSAllProps>, - ) { - const inputs = (sx ?? {}) as any; + const baseTokens: FigmaTokenValues = {}; + const darkTokens: FigmaTokenValues = {}; - // mutate for cache - inputs.__emotion_styles = - inputs.__emotion_styles ?? - serializeStyles(this.unstable_sx(sx), cache?.registered, {}); - - return inputs.__emotion_styles; - } - - toFigmaTokens() { - const seedTokens: FigmaTokenValues = { - space: { - dp: { - $type: "sizing", - $value: 1, - }, - }, + const toFigmaToken = (type: string, value: any): FigmaToken => { + if (isObject(value)) { + return { + $type: type, + $value: mapValues(value, (v) => { + return toFigmaToken(type, v).$value; + }), }; + } - const baseTokens: FigmaTokenValues = {}; - const darkTokens: FigmaTokenValues = {}; - - const toFigmaToken = ( - type: string, - value: any, - ): FigmaToken => { - if (isObject(value)) { - return { - $type: type, - $value: mapValues(value, (v) => { - return toFigmaToken(type, v).$value; - }), - }; - } - - if (isString(value)) { - return { - $type: type, - $value: value - .replace(/var\(([^)]+)\)/g, (v) => { - // var(--vk-space-dp) - const k = v.slice("var(".length, v.length - 1); - const parts = k.slice(`--${this.varPrefix}-`.length).split("--"); - - const keyPath = parts[0] - ?.split("__") - .map((p, i) => (i === 0 ? camelCase(p) : p)) - .join(".") ?? "" - - if (!keyPath.startsWith("sys.")) { - return `{seed.${keyPath}}`; - } - - return `{${keyPath}}`; - }) - .replace(/calc\(.+\)$/g, (v) => - v.slice("calc(".length, v.length - 1), - ), - }; - } - - return { - $type: type, - $value: value, - }; + if (isString(value)) { + return { + $type: type, + $value: value + .replace(/var\(([^)]+)\)/g, (v) => { + // var(--vk-space-dp) + const k = v.slice("var(".length, v.length - 1); + const parts = k.slice(`--${this.varPrefix}-`.length).split("--"); + + const keyPath = + parts[0] + ?.split("__") + .map((p, i) => (i === 0 ? camelCase(p) : p)) + .join(".") ?? ""; + + if (!keyPath.startsWith("sys.")) { + return `{seed.${keyPath}}`; + } + + return `{${keyPath}}`; + }) + .replace(/calc\(.+\)$/g, (v) => + v.slice("calc(".length, v.length - 1), + ), }; + } - for (const topic in this.tokens) { - const ts = this.tokens[topic]; - - const collect = (type: string) => { - for (const t of ts.tokens) { - if (t.includes("/")) { - continue; - } - - if (t.startsWith("sys.")) { - const defaultValue = ts.get(t, "_default"); - const darkValue = ts.get(t, "_dark"); - - setTo( - baseTokens, - [topic, ...t.split(".")], - toFigmaToken(type, defaultValue), - ); - if (defaultValue !== darkValue) { - setTo( - darkTokens, - [topic, ...t.split(".")], - toFigmaToken(type, darkValue), - ); - } - } else { - setTo( - seedTokens, - [topic, ...t.split(".")], - toFigmaToken(type, ts.get(t, "_default")), - ); - } - } - }; - - switch (topic) { - case "color": - collect("color"); - break; - case "rounded": - collect("borderRadius"); - break; - case "shadow": - collect("boxShadow"); - break; - case "font": - collect("fontFamily"); - break; - case "fontWeight": - collect("fontWeight"); - break; + return { + $type: type, + $value: value, + }; + }; + + for (const topic in this.tokens) { + const ts = this.tokens[topic]; + + const collect = (type: string) => { + for (const t of ts.tokens) { + if (t.includes("/")) { + continue; + } + + if (t.startsWith("sys.")) { + const defaultValue = ts.get(t, "_default"); + const darkValue = ts.get(t, "_dark"); + + setTo( + baseTokens, + [topic, ...t.split(".")], + toFigmaToken(type, defaultValue), + ); + if (defaultValue !== darkValue) { + setTo( + darkTokens, + [topic, ...t.split(".")], + toFigmaToken(type, darkValue), + ); } + } else { + setTo( + seedTokens, + [topic, ...t.split(".")], + toFigmaToken(type, ts.get(t, "_default")), + ); + } } + }; + + switch (topic) { + case "color": + collect("color"); + break; + case "rounded": + collect("borderRadius"); + break; + case "shadow": + collect("boxShadow"); + break; + case "font": + collect("fontFamily"); + break; + case "fontWeight": + collect("fontWeight"); + break; + } + } - for (const topic in this.mixins) { - const mixin = this.mixins[topic]; + for (const topic in this.mixins) { + const mixin = this.mixins[topic]; - if (!mixin) { - continue; - } + if (!mixin) { + continue; + } - const collect = (type: string) => { - for (const t of mixin.tokens) { - const v = mixin.get(t); + const collect = (type: string) => { + for (const t of mixin.tokens) { + const v = mixin.get(t); - if (!v) { - continue; - } + if (!v) { + continue; + } - const value = this.unstable_sx(v)[0]; + const value = this.unstable_sx(v)[0]; - setTo( - baseTokens, - [topic, ...t.split(".")], - toFigmaToken(type, value), - ); - } - }; - - switch (topic) { - case "textStyle": - collect("typography"); - break; - } + setTo( + baseTokens, + [topic, ...t.split(".")], + toFigmaToken(type, value), + ); } + }; - return { - seed: seedTokens, - base: groupSysColors(baseTokens), - dark: groupSysColors(darkTokens), - }; + switch (topic) { + case "textStyle": + collect("typography"); + break; + } } -} + return { + seed: seedTokens, + base: groupSysColors(baseTokens), + dark: groupSysColors(darkTokens), + }; + } +} diff --git a/nodepkg/vueuikit/src/theming/__tests__/figma-tokens.spec.ts b/nodepkg/vueuikit/src/theming/__tests__/figma-tokens.spec.ts index 8c8a084d..b8bc443c 100644 --- a/nodepkg/vueuikit/src/theming/__tests__/figma-tokens.spec.ts +++ b/nodepkg/vueuikit/src/theming/__tests__/figma-tokens.spec.ts @@ -1,29 +1,29 @@ -import {describe, expect, test} from "bun:test"; -import {Theming, defaultTheming, themeFromFigmaTokens} from "../index"; +import { describe, expect, test } from "bun:test"; +import { Theming, defaultTheming, themeFromFigmaTokens } from "../index"; describe("#FigmaTokens", () => { - const figmaTokens = defaultTheming.toFigmaTokens(); + const figmaTokens = defaultTheming.toFigmaTokens(); - test("from figmaTokens", () => { - const themeTokens = themeFromFigmaTokens( - [figmaTokens.seed, figmaTokens.base], - [figmaTokens.dark], - ); + test("from figmaTokens", () => { + const themeTokens = themeFromFigmaTokens( + [figmaTokens.seed, figmaTokens.base], + [figmaTokens.dark], + ); - const t = Theming.create( - { - ...defaultTheming.theme, - ...themeTokens, - }, - { - varPrefix: "vk", - }, - ); + const t = Theming.create( + { + ...defaultTheming.theme, + ...themeTokens, + }, + { + varPrefix: "vk", + }, + ); - const nextTokens = t.toFigmaTokens(); + const nextTokens = t.toFigmaTokens(); - for (const k in nextTokens) { - expect((nextTokens as any)[k]).toEqual((figmaTokens as any)[k]); - } - }); + for (const k in nextTokens) { + expect((nextTokens as any)[k]).toEqual((figmaTokens as any)[k]); + } + }); }); diff --git a/nodepkg/vueuikit/src/theming/__tests__/index.spec.ts b/nodepkg/vueuikit/src/theming/__tests__/index.spec.ts index af9649ad..3dba5ea4 100644 --- a/nodepkg/vueuikit/src/theming/__tests__/index.spec.ts +++ b/nodepkg/vueuikit/src/theming/__tests__/index.spec.ts @@ -3,67 +3,67 @@ import { CSSProcessor } from "../CSSProcessor"; import { alpha, defaultTheming, variant } from "../index"; describe("theming", () => { - test("selector rule", () => { - const selectors: Record = { - _aria_current__page: "&[aria-current='page']", - _hover: "&:hover, &[data-hover]:not([data-hover='false']), &.hover", - _$before: "&::before", - _data_role__heading: "&[data-role='heading']", - $dataRole__heading: "& [data-role='heading']", + test("selector rule", () => { + const selectors: Record = { + _aria_current__page: "&[aria-current='page']", + _hover: "&:hover, &[data-hover]:not([data-hover='false']), &.hover", + _$before: "&::before", + _data_role__heading: "&[data-role='heading']", + $dataRole__heading: "& [data-role='heading']", - $data_popper_arrow: "& [data-popper-arrow]", + $data_popper_arrow: "& [data-popper-arrow]", - data_popper_placement__right$: "[data-popper-placement='right'] &", - }; + data_popper_placement__right$: "[data-popper-placement='right'] &", + }; - for (const k in selectors) { - expect(CSSProcessor.convertSelector(k)).toBe(selectors[k]); - } - }); + for (const k in selectors) { + expect(CSSProcessor.convertSelector(k)).toBe(selectors[k]); + } + }); - test("css", () => { - const cs = defaultTheming.unstable_sx({ - extends: [ - { - px: 4, - py: 2, - w: 1 / 4, - rounded: 20, - }, - ], + test("css", () => { + const cs = defaultTheming.unstable_sx({ + extends: [ + { + px: 4, + py: 2, + w: 1 / 4, + rounded: 20, + }, + ], - color: "sys.on-primary-container", + color: "sys.on-primary-container", - _$before: { - bgColor: "sys.primary-container", - }, + _$before: { + bgColor: "sys.primary-container", + }, - _error: { - bgColor: variant("secondary.20", alpha(0.12)), - }, - }); + _error: { + bgColor: variant("secondary.20", alpha(0.12)), + }, + }); - console.log(cs); + console.log(cs); - expect(cs).toEqual([ - { - borderRadius: "calc(20 * var(--vk-space__dp))", - paddingBottom: "calc(2 * var(--vk-space__dp))", - paddingLeft: "calc(4 * var(--vk-space__dp))", - paddingRight: "calc(4 * var(--vk-space__dp))", - paddingTop: "calc(2 * var(--vk-space__dp))", - width: "25%", - }, - { - color: "var(--vk-color__sys__on-primary-container)", - fill: "var(--vk-color__sys__on-primary-container)", - "&::before": { - backgroundColor: "var(--vk-color__sys__primary-container)", - }, - "&[data-error]:not([data-error='false'])": { - backgroundColor: "rgba(var(--vk-color__secondary__20--rgb) / 0.12)", - }, - }, - ]); - }); + expect(cs).toEqual([ + { + borderRadius: "calc(20 * var(--vk-space__dp))", + paddingBottom: "calc(2 * var(--vk-space__dp))", + paddingLeft: "calc(4 * var(--vk-space__dp))", + paddingRight: "calc(4 * var(--vk-space__dp))", + paddingTop: "calc(2 * var(--vk-space__dp))", + width: "25%", + }, + { + color: "var(--vk-color__sys__on-primary-container)", + fill: "var(--vk-color__sys__on-primary-container)", + "&::before": { + backgroundColor: "var(--vk-color__sys__primary-container)", + }, + "&[data-error]:not([data-error='false'])": { + backgroundColor: "rgba(var(--vk-color__secondary__20--rgb) / 0.12)", + }, + }, + ]); + }); }); diff --git a/nodepkg/vueuikit/src/theming/csstype/Properties.ts b/nodepkg/vueuikit/src/theming/csstype/Properties.ts index 3e9f9c64..1cba9b07 100644 --- a/nodepkg/vueuikit/src/theming/csstype/Properties.ts +++ b/nodepkg/vueuikit/src/theming/csstype/Properties.ts @@ -2,130 +2,130 @@ import { type Properties } from "@innoai-tech/csstype"; import type { ValuesOf } from "../typeutil"; export type CSSProps = { - [K in keyof Properties]: Properties[K] | Fallback; + [K in keyof Properties]: Properties[K] | Fallback; }; function createNameGetter>(): { - [K in keyof T]: K; + [K in keyof T]: K; } { - return new Proxy( - {}, - { - get(_, p) { - return p; - }, - }, - ) as any; + return new Proxy( + {}, + { + get(_, p) { + return p; + }, + }, + ) as any; } export const CSSProperty = createNameGetter>(); export const extensions = { - paddingX: [CSSProperty.paddingLeft, CSSProperty.paddingRight], - paddingY: [CSSProperty.paddingTop, CSSProperty.paddingBottom], - - marginX: [CSSProperty.marginInlineStart, CSSProperty.marginInlineEnd], - marginY: [CSSProperty.marginTop, CSSProperty.marginBottom], - - borderX: [CSSProperty.borderLeft, CSSProperty.borderRight], - borderY: [CSSProperty.borderTop, CSSProperty.borderBottom], - color: [CSSProperty.color, CSSProperty.fill], - - borderTopRadius: [ - CSSProperty.borderTopLeftRadius, - CSSProperty.borderTopRightRadius, - ], - borderBottomRadius: [ - CSSProperty.borderBottomLeftRadius, - CSSProperty.borderBottomRightRadius, - ], - borderRightRadius: [ - CSSProperty.borderTopRightRadius, - CSSProperty.borderBottomRightRadius, - ], - borderLeftRadius: [ - CSSProperty.borderTopLeftRadius, - CSSProperty.borderBottomLeftRadius, - ], - - backgroundGradient: [CSSProperty.backgroundImage], - - boxSize: [CSSProperty.width, CSSProperty.height], + paddingX: [CSSProperty.paddingLeft, CSSProperty.paddingRight], + paddingY: [CSSProperty.paddingTop, CSSProperty.paddingBottom], + + marginX: [CSSProperty.marginInlineStart, CSSProperty.marginInlineEnd], + marginY: [CSSProperty.marginTop, CSSProperty.marginBottom], + + borderX: [CSSProperty.borderLeft, CSSProperty.borderRight], + borderY: [CSSProperty.borderTop, CSSProperty.borderBottom], + color: [CSSProperty.color, CSSProperty.fill], + + borderTopRadius: [ + CSSProperty.borderTopLeftRadius, + CSSProperty.borderTopRightRadius, + ], + borderBottomRadius: [ + CSSProperty.borderBottomLeftRadius, + CSSProperty.borderBottomRightRadius, + ], + borderRightRadius: [ + CSSProperty.borderTopRightRadius, + CSSProperty.borderBottomRightRadius, + ], + borderLeftRadius: [ + CSSProperty.borderTopLeftRadius, + CSSProperty.borderBottomLeftRadius, + ], + + backgroundGradient: [CSSProperty.backgroundImage], + + boxSize: [CSSProperty.width, CSSProperty.height], }; export type ElementOf = T extends Array ? E : never; export type CSSExtensions = { - [K in keyof Extends as K extends string ? K : never]: ElementOf< - Extends[K] - > extends keyof CSSProps - ? CSSProps[ElementOf] - : never; + [K in keyof Extends as K extends string ? K : never]: ElementOf< + Extends[K] + > extends keyof CSSProps + ? CSSProps[ElementOf] + : never; }; export type CSSExtendedProps = CSSProps & Partial; export const CSSExtendedProperty = - createNameGetter>(); + createNameGetter>(); export const aliases = { - font: CSSExtendedProperty.fontFamily, - - shadow: CSSExtendedProperty.boxShadow, - - rounded: CSSExtendedProperty.borderRadius, - roundedTop: CSSExtendedProperty.borderTopRadius, - roundedBottom: CSSExtendedProperty.borderBottomRadius, - roundedLeft: CSSExtendedProperty.borderLeftRadius, - roundedRight: CSSExtendedProperty.borderRightRadius, - - bg: CSSExtendedProperty.background, - bgImage: CSSExtendedProperty.backgroundImage, - bgSize: CSSExtendedProperty.backgroundSize, - bgPosition: CSSExtendedProperty.backgroundPosition, - bgRepeat: CSSExtendedProperty.backgroundRepeat, - bgAttachment: CSSExtendedProperty.backgroundAttachment, - bgColor: CSSExtendedProperty.backgroundColor, - bgGradient: CSSExtendedProperty.backgroundGradient, - bgClip: CSSExtendedProperty.backgroundClip, - - pos: CSSExtendedProperty.position, - - p: CSSExtendedProperty.padding, - pt: CSSExtendedProperty.paddingTop, - pr: CSSExtendedProperty.paddingRight, - pl: CSSExtendedProperty.paddingLeft, - pb: CSSExtendedProperty.paddingBottom, - ps: CSSExtendedProperty.paddingInlineStart, - pe: CSSExtendedProperty.paddingInlineEnd, - px: CSSExtendedProperty.paddingX, - py: CSSExtendedProperty.paddingY, - - m: CSSExtendedProperty.margin, - mt: CSSExtendedProperty.marginTop, - mr: CSSExtendedProperty.marginRight, - ml: CSSExtendedProperty.marginLeft, - mb: CSSExtendedProperty.marginBottom, - ms: CSSExtendedProperty.marginInlineStart, - me: CSSExtendedProperty.marginInlineEnd, - mx: CSSExtendedProperty.marginX, - my: CSSExtendedProperty.marginY, - - w: CSSExtendedProperty.width, - minW: CSSExtendedProperty.minWidth, - maxW: CSSExtendedProperty.maxWidth, - - h: CSSExtendedProperty.height, - minH: CSSExtendedProperty.minHeight, - maxH: CSSExtendedProperty.maxHeight, + font: CSSExtendedProperty.fontFamily, + + shadow: CSSExtendedProperty.boxShadow, + + rounded: CSSExtendedProperty.borderRadius, + roundedTop: CSSExtendedProperty.borderTopRadius, + roundedBottom: CSSExtendedProperty.borderBottomRadius, + roundedLeft: CSSExtendedProperty.borderLeftRadius, + roundedRight: CSSExtendedProperty.borderRightRadius, + + bg: CSSExtendedProperty.background, + bgImage: CSSExtendedProperty.backgroundImage, + bgSize: CSSExtendedProperty.backgroundSize, + bgPosition: CSSExtendedProperty.backgroundPosition, + bgRepeat: CSSExtendedProperty.backgroundRepeat, + bgAttachment: CSSExtendedProperty.backgroundAttachment, + bgColor: CSSExtendedProperty.backgroundColor, + bgGradient: CSSExtendedProperty.backgroundGradient, + bgClip: CSSExtendedProperty.backgroundClip, + + pos: CSSExtendedProperty.position, + + p: CSSExtendedProperty.padding, + pt: CSSExtendedProperty.paddingTop, + pr: CSSExtendedProperty.paddingRight, + pl: CSSExtendedProperty.paddingLeft, + pb: CSSExtendedProperty.paddingBottom, + ps: CSSExtendedProperty.paddingInlineStart, + pe: CSSExtendedProperty.paddingInlineEnd, + px: CSSExtendedProperty.paddingX, + py: CSSExtendedProperty.paddingY, + + m: CSSExtendedProperty.margin, + mt: CSSExtendedProperty.marginTop, + mr: CSSExtendedProperty.marginRight, + ml: CSSExtendedProperty.marginLeft, + mb: CSSExtendedProperty.marginBottom, + ms: CSSExtendedProperty.marginInlineStart, + me: CSSExtendedProperty.marginInlineEnd, + mx: CSSExtendedProperty.marginX, + my: CSSExtendedProperty.marginY, + + w: CSSExtendedProperty.width, + minW: CSSExtendedProperty.minWidth, + maxW: CSSExtendedProperty.maxWidth, + + h: CSSExtendedProperty.height, + minH: CSSExtendedProperty.minHeight, + maxH: CSSExtendedProperty.maxHeight, }; export type CSSAliases = { - [K in keyof Aliases as K extends string - ? K - : never]: Aliases[K] extends keyof CSSExtendedProps - ? CSSExtendedProps[Aliases[K]] - : never; + [K in keyof Aliases as K extends string + ? K + : never]: Aliases[K] extends keyof CSSExtendedProps + ? CSSExtendedProps[Aliases[K]] + : never; }; export type CSSAllProps = CSSExtendedProps & Partial; @@ -133,18 +133,18 @@ export type CSSAllProps = CSSExtendedProps & Partial; export const CSSAllProperty = createNameGetter>(); export type ExpandedAliases< - P extends keyof CSSAllProps, - Aliases = typeof aliases, + P extends keyof CSSAllProps, + Aliases = typeof aliases, > = P | ValuesOf

: never>; export const expandAliases =

( - ...props: P[] + ...props: P[] ): Array> => { - const final: string[] = [...props]; - for (const p of props) { - if ((aliases as any)[p]) { - final.push((aliases as any)[p]); - } - } - return final as any; + const final: string[] = [...props]; + for (const p of props) { + if ((aliases as any)[p]) { + final.push((aliases as any)[p]); + } + } + return final as any; }; diff --git a/nodepkg/vueuikit/src/theming/csstype/Pseudos.ts b/nodepkg/vueuikit/src/theming/csstype/Pseudos.ts index 4da630cb..5e2aba7a 100644 --- a/nodepkg/vueuikit/src/theming/csstype/Pseudos.ts +++ b/nodepkg/vueuikit/src/theming/csstype/Pseudos.ts @@ -1,113 +1,113 @@ import { type SimplePseudos } from "@innoai-tech/csstype"; export const pseudoSelectors = { - _rtl: "[dir=rtl] &, &[dir=rtl]", - _ltr: "[dir=ltr] &, &[dir=ltr]", - _dark: "&[data-theme=dark]", - _light: "&[data-theme=light]", + _rtl: "[dir=rtl] &, &[dir=rtl]", + _ltr: "[dir=ltr] &, &[dir=ltr]", + _dark: "&[data-theme=dark]", + _light: "&[data-theme=light]", }; export type Pseudos = typeof pseudoSelectors; type DistributePseudoElement = U extends `::-${ - | "moz" - | "ms" - | "khtml" - | "webkit"}-${string}` - ? never - : U extends `::${string}` - ? U - : never; + | "moz" + | "ms" + | "khtml" + | "webkit"}-${string}` + ? never + : U extends `::${string}` + ? U + : never; type DistributePseudoClass = U extends `::${string}` - ? never - : U extends `:-${"moz" | "ms" | "khtml" | "webkit"}-${string}` - ? never - : U extends `:${string}` - ? U - : never; + ? never + : U extends `:-${"moz" | "ms" | "khtml" | "webkit"}-${string}` + ? never + : U extends `:${string}` + ? U + : never; type PseudoElements = DistributePseudoElement; type PseudoClasses = DistributePseudoClass; type ToCamelCase = S extends `${infer T}-${infer U}` - ? `${T}${Capitalize>}` - : S; + ? `${T}${Capitalize>}` + : S; type DistributePseudoElementNames = U extends `::${infer N}` ? N : never; type DistributePseudoClassNames = U extends `:${infer N}` ? N : never; export type PseudoElementAliases = { - [K in DistributePseudoElementNames as K extends string - ? `_$${ToCamelCase}` - : never]: K; + [K in DistributePseudoElementNames as K extends string + ? `_$${ToCamelCase}` + : never]: K; }; export type PseudoClassAliases = { - [K in DistributePseudoClassNames as K extends string - ? `_${ToCamelCase}` - : never]: K; + [K in DistributePseudoClassNames as K extends string + ? `_${ToCamelCase}` + : never]: K; }; export function getSupportedPseudoClasses(): { - [K in DistributePseudoClassNames as K extends string - ? `${ToCamelCase}` - : never]: K; + [K in DistributePseudoClassNames as K extends string + ? `${ToCamelCase}` + : never]: K; } { - return { - active: "active", - after: "after", - anyLink: "any-link", - before: "before", - blank: "blank", - checked: "checked", - current: "current", - default: "default", - defined: "defined", - disabled: "disabled", - empty: "empty", - enabled: "enabled", - first: "first", - firstChild: "first-child", - firstLetter: "first-letter", - firstLine: "first-line", - firstOfType: "first-of-type", - focus: "focus", - focusVisible: "focus-visible", - focusWithin: "focus-within", - fullscreen: "fullscreen", - future: "future", - hover: "hover", - inRange: "in-range", - indeterminate: "indeterminate", - invalid: "invalid", - lastChild: "last-child", - lastOfType: "last-of-type", - left: "left", - link: "link", - localLink: "local-link", - nthCol: "nth-col", - nthLastCol: "nth-last-col", - onlyChild: "only-child", - onlyOfType: "only-of-type", - optional: "optional", - outOfRange: "out-of-range", - past: "past", - paused: "paused", - pictureInPicture: "picture-in-picture", - placeholderShown: "placeholder-shown", - playing: "playing", - readOnly: "read-only", - readWrite: "read-write", - required: "required", - right: "right", - root: "root", - scope: "scope", - target: "target", - targetWithin: "target-within", - userInvalid: "user-invalid", - userValid: "user-valid", - valid: "valid", - visited: "visited", - }; + return { + active: "active", + after: "after", + anyLink: "any-link", + before: "before", + blank: "blank", + checked: "checked", + current: "current", + default: "default", + defined: "defined", + disabled: "disabled", + empty: "empty", + enabled: "enabled", + first: "first", + firstChild: "first-child", + firstLetter: "first-letter", + firstLine: "first-line", + firstOfType: "first-of-type", + focus: "focus", + focusVisible: "focus-visible", + focusWithin: "focus-within", + fullscreen: "fullscreen", + future: "future", + hover: "hover", + inRange: "in-range", + indeterminate: "indeterminate", + invalid: "invalid", + lastChild: "last-child", + lastOfType: "last-of-type", + left: "left", + link: "link", + localLink: "local-link", + nthCol: "nth-col", + nthLastCol: "nth-last-col", + onlyChild: "only-child", + onlyOfType: "only-of-type", + optional: "optional", + outOfRange: "out-of-range", + past: "past", + paused: "paused", + pictureInPicture: "picture-in-picture", + placeholderShown: "placeholder-shown", + playing: "playing", + readOnly: "read-only", + readWrite: "read-write", + required: "required", + right: "right", + root: "root", + scope: "scope", + target: "target", + targetWithin: "target-within", + userInvalid: "user-invalid", + userValid: "user-valid", + valid: "valid", + visited: "visited", + }; } diff --git a/nodepkg/vueuikit/src/theming/csstype/index.ts b/nodepkg/vueuikit/src/theming/csstype/index.ts index abfb9096..86d1a8c8 100644 --- a/nodepkg/vueuikit/src/theming/csstype/index.ts +++ b/nodepkg/vueuikit/src/theming/csstype/index.ts @@ -4,36 +4,36 @@ export * from "./Pseudos"; export type { Globals } from "@innoai-tech/csstype"; import type { CSSAllProps } from "./Properties"; import type { - PseudoClassAliases, - PseudoElementAliases, - Pseudos, + PseudoClassAliases, + PseudoElementAliases, + Pseudos, } from "./Pseudos"; export type RecursivePseudo = { - [K in - | keyof PseudoElementAliases - | keyof PseudoClassAliases - | keyof Pseudos]?: D; + [K in + | keyof PseudoElementAliases + | keyof PseudoClassAliases + | keyof Pseudos]?: D; }; type CSSDefinition = D | RecursiveCSSSelector; interface RecursiveCSSSelector { - [selector: string]: CSSDefinition; + [selector: string]: CSSDefinition; } export type RecursiveCSSObject = D & - (D | RecursivePseudo | RecursiveCSSSelector); + (D | RecursivePseudo | RecursiveCSSSelector); export type CSSObjectWithState = D & { - state?: Record< - string, - CSSDefinition<{ $?: string } & Pick> - >; + state?: Record< + string, + CSSDefinition<{ $?: string } & Pick> + >; }; export type ExtendableCSSObject = { extends?: ExtendableCSSObject[] } & D; export type FullCSSObject = ExtendableCSSObject< - RecursiveCSSObject> + RecursiveCSSObject> >; diff --git a/nodepkg/vueuikit/src/theming/index.ts b/nodepkg/vueuikit/src/theming/index.ts index c879b042..4a5a4864 100644 --- a/nodepkg/vueuikit/src/theming/index.ts +++ b/nodepkg/vueuikit/src/theming/index.ts @@ -11,5 +11,5 @@ export const defaultTheming = Theming.create(defaultTheme, { varPrefix: "vk" }); // TODO added support custom export type SystemStyleObject = Parameters< - typeof defaultTheming.unstable_sx + typeof defaultTheming.unstable_sx >[0]; diff --git a/nodepkg/vueuikit/src/theming/m3/elevation.ts b/nodepkg/vueuikit/src/theming/m3/elevation.ts index 938346f0..5d2f8b9a 100644 --- a/nodepkg/vueuikit/src/theming/m3/elevation.ts +++ b/nodepkg/vueuikit/src/theming/m3/elevation.ts @@ -1,87 +1,87 @@ import { DesignToken } from "../token"; export const elevation = { - shadow: DesignToken.shadow({ - "0": { - _default: - "0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px 0px rgba(0, 0, 0, 0)", - _dark: "0px 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(0, 0, 0, 0)", - }, - "1": { - _default: - "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)", - _dark: - "0px 1px 3px 1px rgba(0, 0, 0, 0.15), 0px 1px 2px rgba(0, 0, 0, 0.3)", - }, - "2": { - _default: - "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15)", - _dark: - "0px 2px 6px 2px rgba(0, 0, 0, 0.15), 0px 1px 2px rgba(0, 0, 0, 0.3)", - }, - "3": { - _default: - "0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px rgba(0, 0, 0, 0.3)", - _dark: - "0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px rgba(0, 0, 0, 0.3)", - }, - "4": { - _default: - "0px 6px 10px 4px rgba(0, 0, 0, 0.15), 0px 2px 3px rgba(0, 0, 0, 0.3)", - _dark: - "0px 6px 10px 4px rgba(0, 0, 0, 0.15), 0px 2px 3px rgba(0, 0, 0, 0.3)", - }, - "5": { - _default: - "0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)", - _dark: - "0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)", - }, - }), - elevation: DesignToken.customMixin("elevation", { - "0": DesignToken.mixin({ - transitionDuration: "md4", - transitionTimingFunction: "standard", - shadow: "0", + shadow: DesignToken.shadow({ + "0": { + _default: + "0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px 0px rgba(0, 0, 0, 0)", + _dark: "0px 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(0, 0, 0, 0)", + }, + "1": { + _default: + "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)", + _dark: + "0px 1px 3px 1px rgba(0, 0, 0, 0.15), 0px 1px 2px rgba(0, 0, 0, 0.3)", + }, + "2": { + _default: + "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15)", + _dark: + "0px 2px 6px 2px rgba(0, 0, 0, 0.15), 0px 1px 2px rgba(0, 0, 0, 0.3)", + }, + "3": { + _default: + "0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px rgba(0, 0, 0, 0.3)", + _dark: + "0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px rgba(0, 0, 0, 0.3)", + }, + "4": { + _default: + "0px 6px 10px 4px rgba(0, 0, 0, 0.15), 0px 2px 3px rgba(0, 0, 0, 0.3)", + _dark: + "0px 6px 10px 4px rgba(0, 0, 0, 0.15), 0px 2px 3px rgba(0, 0, 0, 0.3)", + }, + "5": { + _default: + "0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)", + _dark: + "0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)", + }, + }), + elevation: DesignToken.customMixin("elevation", { + "0": DesignToken.mixin({ + transitionDuration: "md4", + transitionTimingFunction: "standard", + shadow: "0", - _hover: { - shadow: "1", - }, - }), - "1": DesignToken.mixin({ - transitionDuration: "md4", - transitionTimingFunction: "standard", - shadow: "1", + _hover: { + shadow: "1", + }, + }), + "1": DesignToken.mixin({ + transitionDuration: "md4", + transitionTimingFunction: "standard", + shadow: "1", - _hover: { - shadow: "2", - }, - }), - "2": DesignToken.mixin({ - transitionDuration: "md4", - transitionTimingFunction: "standard", - shadow: "2", + _hover: { + shadow: "2", + }, + }), + "2": DesignToken.mixin({ + transitionDuration: "md4", + transitionTimingFunction: "standard", + shadow: "2", - _hover: { - shadow: "3", - }, - }), - "3": DesignToken.mixin({ - transitionDuration: "md4", - transitionTimingFunction: "standard", - shadow: "3", + _hover: { + shadow: "3", + }, + }), + "3": DesignToken.mixin({ + transitionDuration: "md4", + transitionTimingFunction: "standard", + shadow: "3", - _hover: { - shadow: "4", - }, - }), - "4": DesignToken.mixin({ - transitionDuration: "md4", - transitionTimingFunction: "standard", - shadow: "4", - _hover: { - shadow: "5", - }, - }), - }), + _hover: { + shadow: "4", + }, + }), + "4": DesignToken.mixin({ + transitionDuration: "md4", + transitionTimingFunction: "standard", + shadow: "4", + _hover: { + shadow: "5", + }, + }), + }), }; diff --git a/nodepkg/vueuikit/src/theming/m3/index.ts b/nodepkg/vueuikit/src/theming/m3/index.ts index fb42d3c3..5b233257 100644 --- a/nodepkg/vueuikit/src/theming/m3/index.ts +++ b/nodepkg/vueuikit/src/theming/m3/index.ts @@ -5,23 +5,23 @@ import { rounded } from "./shape"; import { typography } from "./typography"; const seedColors = Palette.fromColors({ - primary: "#1270f5", - secondary: "#8a90a5", - tertiary: "#b58391", - neutral: "#5e5e5e", - error: "#d93f23", - warning: "#e69c00", - success: "#5ac220", + primary: "#1270f5", + secondary: "#8a90a5", + tertiary: "#b58391", + neutral: "#5e5e5e", + error: "#d93f23", + warning: "#e69c00", + success: "#5ac220", }); export const defaultTheme = { - ...typography, - ...motion, - ...elevation, - rounded: rounded, - ...seedColors.toDesignTokens({ - primary: ["primary", 80, 50], - }), + ...typography, + ...motion, + ...elevation, + rounded: rounded, + ...seedColors.toDesignTokens({ + primary: ["primary", 80, 50], + }), } as const; export * from "./palette"; diff --git a/nodepkg/vueuikit/src/theming/m3/motion.ts b/nodepkg/vueuikit/src/theming/m3/motion.ts index 935e0c36..512183c9 100644 --- a/nodepkg/vueuikit/src/theming/m3/motion.ts +++ b/nodepkg/vueuikit/src/theming/m3/motion.ts @@ -3,17 +3,17 @@ import { DesignToken } from "../token"; // https://m3.material.io/styles/motion/easing-and-duration/tokens-specs export const motion = { - transitionDuration: DesignToken.transitionDuration(transition.duration), - transitionTimingFunction: DesignToken.transitionTimingFunction({ - linear: transition.easing.linear.toString(), - standard: transition.easing.standard.toString(), - "standard-accelerate": transition.easing.standard.accelerate.toString(), - "standard-decelerate": transition.easing.standard.decelerate.toString(), - emphasized: transition.easing.emphasized.toString(), - "emphasized-decelerate": transition.easing.emphasized.decelerate.toString(), - "emphasized-accelerate": transition.easing.emphasized.accelerate.toString(), - legacy: transition.easing.legacy.toString(), - "legacy-decelerate": transition.easing.legacy.decelerate.toString(), - "legacy-accelerate": transition.easing.legacy.accelerate.toString(), - }), + transitionDuration: DesignToken.transitionDuration(transition.duration), + transitionTimingFunction: DesignToken.transitionTimingFunction({ + linear: transition.easing.linear.toString(), + standard: transition.easing.standard.toString(), + "standard-accelerate": transition.easing.standard.accelerate.toString(), + "standard-decelerate": transition.easing.standard.decelerate.toString(), + emphasized: transition.easing.emphasized.toString(), + "emphasized-decelerate": transition.easing.emphasized.decelerate.toString(), + "emphasized-accelerate": transition.easing.emphasized.accelerate.toString(), + legacy: transition.easing.legacy.toString(), + "legacy-decelerate": transition.easing.legacy.decelerate.toString(), + "legacy-accelerate": transition.easing.legacy.accelerate.toString(), + }), }; diff --git a/nodepkg/vueuikit/src/theming/m3/palette.ts b/nodepkg/vueuikit/src/theming/m3/palette.ts index 639477a5..96a6df68 100644 --- a/nodepkg/vueuikit/src/theming/m3/palette.ts +++ b/nodepkg/vueuikit/src/theming/m3/palette.ts @@ -1,337 +1,337 @@ import { - type Dictionary, - get, - isNumber, - mapValues, - padStart, + type Dictionary, + get, + isNumber, + mapValues, + padStart, } from "@innoai-tech/lodash"; import { - CorePalette, - TonalPalette, - argbFromHex, - rgbaFromArgb, + CorePalette, + TonalPalette, + argbFromHex, + rgbaFromArgb, } from "@material/material-color-utilities"; import { DesignToken, type WithMixin } from "../token"; const tones = { - "0": true, - "10": true, - "20": true, - "30": true, - "40": true, - "50": true, - "60": true, - "70": true, - "80": true, - "90": true, - "95": true, - "100": true, + "0": true, + "10": true, + "20": true, + "30": true, + "40": true, + "50": true, + "60": true, + "70": true, + "80": true, + "90": true, + "95": true, + "100": true, } as const; export type ColorTonalPalette = { - [k in keyof typeof tones]: Color; + [k in keyof typeof tones]: Color; }; export type ConditionColors = { - _default: [number, number, number]; - _dark?: [number, number, number]; + _default: [number, number, number]; + _dark?: [number, number, number]; }; export type AccentColorPalettes = { - "inverse-primary": ConditionColors; + "inverse-primary": ConditionColors; } & { - [K in keyof KeyColors as K extends string ? `${K}` : never]: ConditionColors; + [K in keyof KeyColors as K extends string ? `${K}` : never]: ConditionColors; } & { - [K in keyof KeyColors as K extends string - ? `on-${K}` - : never]: ConditionColors; + [K in keyof KeyColors as K extends string + ? `on-${K}` + : never]: ConditionColors; } & { - [K in keyof KeyColors as K extends string - ? `${K}-container` - : never]: ConditionColors; + [K in keyof KeyColors as K extends string + ? `${K}-container` + : never]: ConditionColors; } & { - [K in keyof KeyColors as K extends string - ? `on-${K}-container` - : never]: ConditionColors; + [K in keyof KeyColors as K extends string + ? `on-${K}-container` + : never]: ConditionColors; }; export type SurfacePalettes = { - "on-surface": ConditionColors; - "on-surface-variant": ConditionColors; - - "inverse-surface": ConditionColors; - "inverse-on-surface": ConditionColors; - - surface: ConditionColors; - "surface-dim": ConditionColors; - "surface-bright": ConditionColors; - "surface-container-lowest": ConditionColors; - "surface-container-low": ConditionColors; - "surface-container": ConditionColors; - "surface-container-high": ConditionColors; - "surface-container-highest": ConditionColors; - "surface-variant": ConditionColors; + "on-surface": ConditionColors; + "on-surface-variant": ConditionColors; + + "inverse-surface": ConditionColors; + "inverse-on-surface": ConditionColors; + + surface: ConditionColors; + "surface-dim": ConditionColors; + "surface-bright": ConditionColors; + "surface-container-lowest": ConditionColors; + "surface-container-low": ConditionColors; + "surface-container": ConditionColors; + "surface-container-high": ConditionColors; + "surface-container-highest": ConditionColors; + "surface-variant": ConditionColors; }; export type ColorPalettes = { - outline: ConditionColors; - "outline-variant": ConditionColors; - shadow: ConditionColors; - scrim: ConditionColors; + outline: ConditionColors; + "outline-variant": ConditionColors; + shadow: ConditionColors; + scrim: ConditionColors; } & AccentColorPalettes & - SurfacePalettes; + SurfacePalettes; export interface SeedColors { - primary: string; - secondary: string; - tertiary: string; - neutral: string; - neutralVariant: string; - - error: string; - warning: string; - success: string; + primary: string; + secondary: string; + tertiary: string; + neutral: string; + neutralVariant: string; + + error: string; + warning: string; + success: string; } export type RoleColorRules = { - [k in keyof ColorPalettes]: [keyof Seeds, number, number]; + [k in keyof ColorPalettes]: [keyof Seeds, number, number]; }; const rgbFromArgb = (argb: number): [number, number, number] => { - const rgba = rgbaFromArgb(argb); - return [rgba.r, rgba.g, rgba.b]; + const rgba = rgbaFromArgb(argb); + return [rgba.r, rgba.g, rgba.b]; }; const isKeyColor = (k: string) => { - return { - primary: true, - secondary: true, - tertiary: true, - error: true, - warning: true, - success: true, - }[k]; + return { + primary: true, + secondary: true, + tertiary: true, + error: true, + warning: true, + success: true, + }[k]; }; export class Palette { - static toHEX = (n: number) => { - return `#${rgbFromArgb(n) - .map((v) => padStart(v.toString(16), 2, "0")) - .join("")}`; - }; - - static fromColors = ( - colors: Partial & { primary: string }, - ) => { - const { - primary, - secondary, - tertiary, - neutral, - neutralVariant, - error, - ...otherColors - } = colors; - - const palette = CorePalette.contentFromColors({ - primary: argbFromHex(primary), - secondary: secondary ? argbFromHex(secondary) : undefined, - tertiary: tertiary ? argbFromHex(tertiary) : undefined, - error: error ? argbFromHex(error) : undefined, - }); - - if (neutral) { - palette.n1 = TonalPalette.fromHueAndChroma(argbFromHex(neutral), 4); - } - - if (neutralVariant) { - palette.n2 = TonalPalette.fromHueAndChroma( - argbFromHex(neutralVariant), - 8, - ); - } - - return new Palette({ - primary: palette.a1, - secondary: palette.a2, - tertiary: palette.a3, - neutral: palette.n1, - neutralVariant: palette.n2, - error: palette.error, - ...(mapValues(otherColors as Dictionary, (v) => - TonalPalette.fromInt(argbFromHex(v)), - ) as any), - }); - }; - - constructor(public seeds: { [K in keyof Colors]: TonalPalette }) {} - - normalizeRoleRules( - rules: Partial> = {}, - ): RoleColorRules { - const roleRules: { [K: string]: [keyof Colors, number, number] } = { - shadow: ["neutral", 0, 0], - scrim: ["neutral", 0, 0], - - outline: ["neutralVariant", 60, 50], - "outline-variant": ["neutralVariant", 30, 80], - - surface: ["neutral", 10, 99], - "on-surface": ["neutral", 90, 10], - - "surface-variant": ["neutralVariant", 30, 90], - "on-surface-variant": ["neutralVariant", 80, 30], - - "surface-dim": ["neutral", 6, 87], - "surface-bright": ["neutral", 24, 98], - - "surface-container-lowest": ["neutral", 4, 100], - "surface-container-low": ["neutral", 10, 96], - "surface-container": ["neutral", 12, 94], - "surface-container-high": ["neutral", 17, 92], - "surface-container-highest": ["neutral", 22, 90], - - "inverse-surface": ["neutral", 90, 20], - "inverse-on-surface": ["neutral", 20, 95], - "inverse-primary": ["primary", 40, 80], - }; - - for (const name in this.seeds) { - if (name.startsWith("neutral")) { - continue; - } - - // https://m3.material.io/styles/color/the-color-system/custom-colors - roleRules[name] = [name as keyof Colors, 80, 40]; - roleRules[`on-${name}`] = [name as keyof Colors, 20, 100]; - roleRules[`${name}-container`] = [name as keyof Colors, 30, 90]; - roleRules[`on-${name}-container`] = [name as keyof Colors, 90, 10]; - } - - return { - ...roleRules, - ...rules, - } as RoleColorRules; - } - - private getThemeRoleColors(roleRules: RoleColorRules) { - const themeColors: { [K: string]: number } = {}; - const darkThemeColors: { [K: string]: number } = {}; - - for (const role in roleRules) { - const [base, toneOnDark, toneOnLight] = get(roleRules, [role], []); - - if ((this.seeds as any)[base]) { - darkThemeColors[role] = (tones as any)[toneOnDark] - ? `${base}.${toneOnDark}` - : (this.seeds as any)[base]?.tone(toneOnDark); - themeColors[role] = (tones as any)[toneOnLight] - ? `${base}.${toneOnLight}` - : (this.seeds as any)[base]?.tone(toneOnLight); - } - } - - return [themeColors, darkThemeColors] as const; - } - - toDesignTokens(rules: Partial> = {}) { - const [themeColors, dartThemeColors] = this.getThemeRoleColors( - this.normalizeRoleRules(rules), - ); - - const sysColors: Record = {}; - const containerStyles: Record = - {}; - - for (const role in themeColors) { - sysColors[`${role}`] = { - _default: isNumber((themeColors as any)[role]) - ? rgbFromArgb((themeColors as any)[role]) - : (themeColors as any)[role], - _dark: isNumber((dartThemeColors as any)[role]) - ? rgbFromArgb((dartThemeColors as any)[role]) - : (dartThemeColors as any)[role], - }; - - if (isKeyColor(role)) { - containerStyles[`${role}`] = DesignToken.mixin({ - bgColor: `sys.${role}`, - color: `sys.on-${role}`, - }); - - containerStyles[`${role}-container`] = DesignToken.mixin({ - bgColor: `sys.${role}-container`, - color: `sys.on-${role}-container`, - }); - } - - if (role.startsWith("surface")) { - if (role.includes("container")) { - containerStyles[`${role}`] = DesignToken.mixin({ - bgColor: `sys.${role}`, - color: "sys.on-surface", - }); - continue; - } - - containerStyles[`${role}`] = DesignToken.mixin({ - bgColor: `sys.${role}`, - color: "sys.on-surface", - }); - - containerStyles[`on-${role}`] = DesignToken.mixin({ - bgColor: `sys.on-${role}`, - color: "sys.inverse-on-surface", - }); - } - } - - const toTonalPalette = (t: TonalPalette): ColorTonalPalette => { - return Object.keys(tones).reduce( - (ret, tone) => - Object.assign(ret, { - [tone]: rgbFromArgb(t.tone(parseInt(tone))), - }), - {}, - ) as any; - }; - - const color = DesignToken.color({ - ...mapValues(this.seeds as { [K in keyof Colors]: TonalPalette }, (tp) => - toTonalPalette(tp), - ), - white: [255, 255, 255], - black: [0, 0, 0], - sys: sysColors as unknown as ColorPalettes, - }); - - const containerStyle = DesignToken.customMixin("containerStyle", { - sys: containerStyles as ContainerStyles< - Omit - >, - }); - - return { - color, - containerStyle, - }; - } + static toHEX = (n: number) => { + return `#${rgbFromArgb(n) + .map((v) => padStart(v.toString(16), 2, "0")) + .join("")}`; + }; + + static fromColors = ( + colors: Partial & { primary: string }, + ) => { + const { + primary, + secondary, + tertiary, + neutral, + neutralVariant, + error, + ...otherColors + } = colors; + + const palette = CorePalette.contentFromColors({ + primary: argbFromHex(primary), + secondary: secondary ? argbFromHex(secondary) : undefined, + tertiary: tertiary ? argbFromHex(tertiary) : undefined, + error: error ? argbFromHex(error) : undefined, + }); + + if (neutral) { + palette.n1 = TonalPalette.fromHueAndChroma(argbFromHex(neutral), 4); + } + + if (neutralVariant) { + palette.n2 = TonalPalette.fromHueAndChroma( + argbFromHex(neutralVariant), + 8, + ); + } + + return new Palette({ + primary: palette.a1, + secondary: palette.a2, + tertiary: palette.a3, + neutral: palette.n1, + neutralVariant: palette.n2, + error: palette.error, + ...(mapValues(otherColors as Dictionary, (v) => + TonalPalette.fromInt(argbFromHex(v)), + ) as any), + }); + }; + + constructor(public seeds: { [K in keyof Colors]: TonalPalette }) {} + + normalizeRoleRules( + rules: Partial> = {}, + ): RoleColorRules { + const roleRules: { [K: string]: [keyof Colors, number, number] } = { + shadow: ["neutral", 0, 0], + scrim: ["neutral", 0, 0], + + outline: ["neutralVariant", 60, 50], + "outline-variant": ["neutralVariant", 30, 80], + + surface: ["neutral", 10, 99], + "on-surface": ["neutral", 90, 10], + + "surface-variant": ["neutralVariant", 30, 90], + "on-surface-variant": ["neutralVariant", 80, 30], + + "surface-dim": ["neutral", 6, 87], + "surface-bright": ["neutral", 24, 98], + + "surface-container-lowest": ["neutral", 4, 100], + "surface-container-low": ["neutral", 10, 96], + "surface-container": ["neutral", 12, 94], + "surface-container-high": ["neutral", 17, 92], + "surface-container-highest": ["neutral", 22, 90], + + "inverse-surface": ["neutral", 90, 20], + "inverse-on-surface": ["neutral", 20, 95], + "inverse-primary": ["primary", 40, 80], + }; + + for (const name in this.seeds) { + if (name.startsWith("neutral")) { + continue; + } + + // https://m3.material.io/styles/color/the-color-system/custom-colors + roleRules[name] = [name as keyof Colors, 80, 40]; + roleRules[`on-${name}`] = [name as keyof Colors, 20, 100]; + roleRules[`${name}-container`] = [name as keyof Colors, 30, 90]; + roleRules[`on-${name}-container`] = [name as keyof Colors, 90, 10]; + } + + return { + ...roleRules, + ...rules, + } as RoleColorRules; + } + + private getThemeRoleColors(roleRules: RoleColorRules) { + const themeColors: { [K: string]: number } = {}; + const darkThemeColors: { [K: string]: number } = {}; + + for (const role in roleRules) { + const [base, toneOnDark, toneOnLight] = get(roleRules, [role], []); + + if ((this.seeds as any)[base]) { + darkThemeColors[role] = (tones as any)[toneOnDark] + ? `${base}.${toneOnDark}` + : (this.seeds as any)[base]?.tone(toneOnDark); + themeColors[role] = (tones as any)[toneOnLight] + ? `${base}.${toneOnLight}` + : (this.seeds as any)[base]?.tone(toneOnLight); + } + } + + return [themeColors, darkThemeColors] as const; + } + + toDesignTokens(rules: Partial> = {}) { + const [themeColors, dartThemeColors] = this.getThemeRoleColors( + this.normalizeRoleRules(rules), + ); + + const sysColors: Record = {}; + const containerStyles: Record = + {}; + + for (const role in themeColors) { + sysColors[`${role}`] = { + _default: isNumber((themeColors as any)[role]) + ? rgbFromArgb((themeColors as any)[role]) + : (themeColors as any)[role], + _dark: isNumber((dartThemeColors as any)[role]) + ? rgbFromArgb((dartThemeColors as any)[role]) + : (dartThemeColors as any)[role], + }; + + if (isKeyColor(role)) { + containerStyles[`${role}`] = DesignToken.mixin({ + bgColor: `sys.${role}`, + color: `sys.on-${role}`, + }); + + containerStyles[`${role}-container`] = DesignToken.mixin({ + bgColor: `sys.${role}-container`, + color: `sys.on-${role}-container`, + }); + } + + if (role.startsWith("surface")) { + if (role.includes("container")) { + containerStyles[`${role}`] = DesignToken.mixin({ + bgColor: `sys.${role}`, + color: "sys.on-surface", + }); + continue; + } + + containerStyles[`${role}`] = DesignToken.mixin({ + bgColor: `sys.${role}`, + color: "sys.on-surface", + }); + + containerStyles[`on-${role}`] = DesignToken.mixin({ + bgColor: `sys.on-${role}`, + color: "sys.inverse-on-surface", + }); + } + } + + const toTonalPalette = (t: TonalPalette): ColorTonalPalette => { + return Object.keys(tones).reduce( + (ret, tone) => + Object.assign(ret, { + [tone]: rgbFromArgb(t.tone(parseInt(tone))), + }), + {}, + ) as any; + }; + + const color = DesignToken.color({ + ...mapValues(this.seeds as { [K in keyof Colors]: TonalPalette }, (tp) => + toTonalPalette(tp), + ), + white: [255, 255, 255], + black: [0, 0, 0], + sys: sysColors as unknown as ColorPalettes, + }); + + const containerStyle = DesignToken.customMixin("containerStyle", { + sys: containerStyles as ContainerStyles< + Omit + >, + }); + + return { + color, + containerStyle, + }; + } } export type ContainerStyles< - KeyColors extends {}, - Mixin = WithMixin<{ - color: string; - fill: string; - bgColor: string; - }>, + KeyColors extends {}, + Mixin = WithMixin<{ + color: string; + fill: string; + bgColor: string; + }>, > = { - [K in keyof KeyColors as K extends string ? `${K}` : never]: Mixin; + [K in keyof KeyColors as K extends string ? `${K}` : never]: Mixin; } & { - [K in keyof KeyColors as K extends string ? `${K}-container` : never]: Mixin; + [K in keyof KeyColors as K extends string ? `${K}-container` : never]: Mixin; } & { - [K in keyof Omit< - SurfacePalettes, - "inverse-surface" | "inverse-on-surface-variant" - >]: Mixin; + [K in keyof Omit< + SurfacePalettes, + "inverse-surface" | "inverse-on-surface-variant" + >]: Mixin; }; diff --git a/nodepkg/vueuikit/src/theming/m3/shape.ts b/nodepkg/vueuikit/src/theming/m3/shape.ts index 9cfbcd4a..819e19ed 100644 --- a/nodepkg/vueuikit/src/theming/m3/shape.ts +++ b/nodepkg/vueuikit/src/theming/m3/shape.ts @@ -1,9 +1,9 @@ import { DesignToken } from "../token"; export const rounded = DesignToken.rounded({ - xs: 4, - sm: 8, - md: 12, - lg: 16, - xl: 28, + xs: 4, + sm: 8, + md: 12, + lg: 16, + xl: 28, }); diff --git a/nodepkg/vueuikit/src/theming/m3/typography.ts b/nodepkg/vueuikit/src/theming/m3/typography.ts index 5f269f05..f17c6299 100644 --- a/nodepkg/vueuikit/src/theming/m3/typography.ts +++ b/nodepkg/vueuikit/src/theming/m3/typography.ts @@ -1,157 +1,157 @@ import { DesignToken } from "../token"; export const typography = { - font: DesignToken.font({ - brand: [ - "-apple-system", - "BlinkMacSystemFont", - '"Segoe UI"', - "Roboto", - '"Helvetica Neue"', - "Arial", - "sans-serif", - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"', - ].join(","), - plain: [ - "-apple-system", - "BlinkMacSystemFont", - '"Segoe UI"', - "Roboto", - '"Helvetica Neue"', - "Arial", - "sans-serif", - '"Apple Color Emoji"', - '"Segoe UI Emoji"', - '"Segoe UI Symbol"', - ].join(","), - code: [ - `'Lucida Console'`, - "Consolas", - "Monaco", - `'Andale Mono'`, - `'Ubuntu Mono'`, - "monospace", - ].join(","), - }), + font: DesignToken.font({ + brand: [ + "-apple-system", + "BlinkMacSystemFont", + '"Segoe UI"', + "Roboto", + '"Helvetica Neue"', + "Arial", + "sans-serif", + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(","), + plain: [ + "-apple-system", + "BlinkMacSystemFont", + '"Segoe UI"', + "Roboto", + '"Helvetica Neue"', + "Arial", + "sans-serif", + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(","), + code: [ + `'Lucida Console'`, + "Consolas", + "Monaco", + `'Andale Mono'`, + `'Ubuntu Mono'`, + "monospace", + ].join(","), + }), - fontWeight: DesignToken.fontWeight({ - regular: 400, - medium: 500, - bold: 700, - }), + fontWeight: DesignToken.fontWeight({ + regular: 400, + medium: 500, + bold: 700, + }), - textStyle: DesignToken.customMixin("textStyle", { - sys: { - "display-large": DesignToken.mixin({ - lineHeight: 64, - fontSize: 57, - letterSpacing: -0.25, - fontWeight: "regular", - font: "brand", - }), - "display-medium": DesignToken.mixin({ - lineHeight: 52, - fontSize: 45, - letterSpacing: 0, - fontWeight: "regular", - font: "brand", - }), - "display-small": DesignToken.mixin({ - lineHeight: 44, - fontSize: 36, - letterSpacing: 0, - fontWeight: "regular", - font: "brand", - }), + textStyle: DesignToken.customMixin("textStyle", { + sys: { + "display-large": DesignToken.mixin({ + lineHeight: 64, + fontSize: 57, + letterSpacing: -0.25, + fontWeight: "regular", + font: "brand", + }), + "display-medium": DesignToken.mixin({ + lineHeight: 52, + fontSize: 45, + letterSpacing: 0, + fontWeight: "regular", + font: "brand", + }), + "display-small": DesignToken.mixin({ + lineHeight: 44, + fontSize: 36, + letterSpacing: 0, + fontWeight: "regular", + font: "brand", + }), - "headline-large": DesignToken.mixin({ - lineHeight: 40, - fontSize: 32, - letterSpacing: 0, - fontWeight: "regular", - font: "brand", - }), - "headline-medium": DesignToken.mixin({ - lineHeight: 36, - fontSize: 28, - letterSpacing: 0, - fontWeight: "regular", - font: "brand", - }), - "headline-small": DesignToken.mixin({ - lineHeight: 32, - fontSize: 24, - letterSpacing: 0, - fontWeight: "regular", - font: "plain", - }), + "headline-large": DesignToken.mixin({ + lineHeight: 40, + fontSize: 32, + letterSpacing: 0, + fontWeight: "regular", + font: "brand", + }), + "headline-medium": DesignToken.mixin({ + lineHeight: 36, + fontSize: 28, + letterSpacing: 0, + fontWeight: "regular", + font: "brand", + }), + "headline-small": DesignToken.mixin({ + lineHeight: 32, + fontSize: 24, + letterSpacing: 0, + fontWeight: "regular", + font: "plain", + }), - "title-large": DesignToken.mixin({ - lineHeight: 28, - fontSize: 22, - letterSpacing: 0, - fontWeight: "regular", - font: "brand", - }), - "title-medium": DesignToken.mixin({ - lineHeight: 24, - fontSize: 16, - letterSpacing: 0.15, - fontWeight: "medium", - font: "plain", - }), - "title-small": DesignToken.mixin({ - lineHeight: 20, - fontSize: 14, - letterSpacing: 0.1, - fontWeight: "medium", - font: "plain", - }), - "label-large": DesignToken.mixin({ - lineHeight: 20, - fontSize: 14, - letterSpacing: 0.1, - fontWeight: "medium", - font: "plain", - }), - "label-medium": DesignToken.mixin({ - lineHeight: 16, - fontSize: 12, - letterSpacing: 0.5, - fontWeight: "medium", - font: "plain", - }), - "label-small": DesignToken.mixin({ - lineHeight: 16, - fontSize: 11, - letterSpacing: 0.5, - fontWeight: "medium", - font: "plain", - }), + "title-large": DesignToken.mixin({ + lineHeight: 28, + fontSize: 22, + letterSpacing: 0, + fontWeight: "regular", + font: "brand", + }), + "title-medium": DesignToken.mixin({ + lineHeight: 24, + fontSize: 16, + letterSpacing: 0.15, + fontWeight: "medium", + font: "plain", + }), + "title-small": DesignToken.mixin({ + lineHeight: 20, + fontSize: 14, + letterSpacing: 0.1, + fontWeight: "medium", + font: "plain", + }), + "label-large": DesignToken.mixin({ + lineHeight: 20, + fontSize: 14, + letterSpacing: 0.1, + fontWeight: "medium", + font: "plain", + }), + "label-medium": DesignToken.mixin({ + lineHeight: 16, + fontSize: 12, + letterSpacing: 0.5, + fontWeight: "medium", + font: "plain", + }), + "label-small": DesignToken.mixin({ + lineHeight: 16, + fontSize: 11, + letterSpacing: 0.5, + fontWeight: "medium", + font: "plain", + }), - "body-large": DesignToken.mixin({ - lineHeight: 24, - fontSize: 16, - letterSpacing: 0.5, - fontWeight: "regular", - font: "plain", - }), - "body-medium": DesignToken.mixin({ - lineHeight: 20, - fontSize: 14, - letterSpacing: 0.25, - fontWeight: "regular", - font: "plain", - }), - "body-small": DesignToken.mixin({ - lineHeight: 16, - fontSize: 12, - letterSpacing: 0.4, - fontWeight: "regular", - font: "plain", - }), - }, - }), + "body-large": DesignToken.mixin({ + lineHeight: 24, + fontSize: 16, + letterSpacing: 0.5, + fontWeight: "regular", + font: "plain", + }), + "body-medium": DesignToken.mixin({ + lineHeight: 20, + fontSize: 14, + letterSpacing: 0.25, + fontWeight: "regular", + font: "plain", + }), + "body-small": DesignToken.mixin({ + lineHeight: 16, + fontSize: 12, + letterSpacing: 0.4, + fontWeight: "regular", + font: "plain", + }), + }, + }), }; diff --git a/nodepkg/vueuikit/src/theming/token/DesignToken.ts b/nodepkg/vueuikit/src/theming/token/DesignToken.ts index f2229aed..de67940d 100644 --- a/nodepkg/vueuikit/src/theming/token/DesignToken.ts +++ b/nodepkg/vueuikit/src/theming/token/DesignToken.ts @@ -4,29 +4,29 @@ import { CSSAllProperty, CSSProperty, expandAliases } from "../csstype"; import type { UnionToIntersection, ValuesOf } from "../typeutil"; export type DesignTokenValues = { - [K: string]: T | DesignTokenValues; + [K: string]: T | DesignTokenValues; }; export type DesignTokenValue> = - Tokens extends DesignTokenValues ? V : never; + Tokens extends DesignTokenValues ? V : never; export type FlattenTokenNames< - T, - O extends Record, - K = keyof O, + T, + O extends Record, + K = keyof O, > = K extends string - ? O[K] extends Record - ? O[K]["__mixin"] extends boolean // mixin values - ? `${K}` - : O[K]["_default"] extends T // condition values - ? `${K}` - : `${K}.${FlattenTokenNames}` - : `${K}` - : never; + ? O[K] extends Record + ? O[K]["__mixin"] extends boolean // mixin values + ? `${K}` + : O[K]["_default"] extends T // condition values + ? `${K}` + : `${K}.${FlattenTokenNames}` + : `${K}` + : never; export enum DesignTokenType { - var = "var", - mixin = "mixin", + var = "var", + mixin = "mixin", } export type Primitive = string | number | symbol | bigint | boolean; @@ -34,39 +34,39 @@ export type Primitive = string | number | symbol | bigint | boolean; export type MustDefined = T extends Primitive ? T : never; export type DesignTokenTransform = ( - i: I, - cssVar: (token: string) => string, + i: I, + cssVar: (token: string) => string, ) => O | { default: O; [V: string]: O }; export interface DesignTokenOption< - Tokens extends DesignTokenValues, - CSSPropNames extends keyof CSSAllProps | string, - InputValueType = DesignTokenValue, - ValueType = InputValueType, - Fallback = unknown, + Tokens extends DesignTokenValues, + CSSPropNames extends keyof CSSAllProps | string, + InputValueType = DesignTokenValue, + ValueType = InputValueType, + Fallback = unknown, > { - type: DesignTokenType; - value: Tokens; - on: Array; - transform?: DesignTokenTransform; - - __Tokens: FlattenTokenNames; - __CSSTokens: { - [P in CSSPropNames]: - | FlattenTokenNames - | Globals - | MustDefined; - }; - __ValueType: ValueType; + type: DesignTokenType; + value: Tokens; + on: Array; + transform?: DesignTokenTransform; + + __Tokens: FlattenTokenNames; + __CSSTokens: { + [P in CSSPropNames]: + | FlattenTokenNames + | Globals + | MustDefined; + }; + __ValueType: ValueType; } export type DesignTokenOptionAny = DesignTokenOption; export type DesignTokens> = - Partial["__CSSTokens"]>>; + Partial["__CSSTokens"]>>; export type WithMixin = { - __mixin: true; + __mixin: true; } & T; type RGB = `rgb(${string})`; @@ -77,223 +77,223 @@ type Color = RGB | RGBA | HEX; // biome-ignore lint/complexity/noStaticOnlyClass: export class DesignToken { - static create< - Tokens extends DesignTokenValues, - CSSPropNames extends keyof CSSAllProps | string, - InputValueType = DesignTokenValue, - ValueType = InputValueType, - Fallback = unknown, - >( - type: DesignTokenType, - { - value, - on, - transform, - }: { - value: Tokens; - on: Array; - transform?: DesignTokenTransform; - fallback?: Fallback; - }, - ): DesignTokenOption< - Tokens, - CSSPropNames, - InputValueType, - ValueType, - Fallback - > { - return { - type, - value, - on: on, - transform: transform, - - __Tokens: undefined as any, - __ValueType: undefined as any, - __CSSTokens: undefined as any, - }; - } - - static color>( - value: T, - ) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases( - CSSAllProperty.color, - CSSAllProperty.bgColor, - CSSAllProperty.outlineColor, - CSSAllProperty.borderColor, - CSSAllProperty.accentColor, - - CSSAllProperty.fill, - CSSAllProperty.stroke, - ), - transform: ( - rgb: [number, number, number] | string, - cssVar: (token: string) => string, - ) => { - return isString(rgb) - ? { - default: `var(${cssVar(rgb)})`, - rgb: `var(${cssVar(`${rgb}/rgb`)})`, - } - : { - default: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`, - rgb: `${rgb[0]} ${rgb[1]} ${rgb[2]}`, - }; - }, - fallback: "" as Color, - }); - } - - static space>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases( - CSSAllProperty.gap, - CSSAllProperty.rowGap, - CSSAllProperty.columnGap, - - CSSAllProperty.top, - CSSAllProperty.right, - CSSAllProperty.bottom, - CSSAllProperty.left, - - CSSAllProperty.m, - CSSAllProperty.ms, - CSSAllProperty.me, - CSSAllProperty.mt, - CSSAllProperty.mr, - CSSAllProperty.mb, - CSSAllProperty.ml, - CSSAllProperty.mx, - CSSAllProperty.my, - - CSSAllProperty.p, - CSSAllProperty.ps, - CSSAllProperty.pe, - CSSAllProperty.pt, - CSSAllProperty.pr, - CSSAllProperty.pb, - CSSAllProperty.pl, - CSSAllProperty.px, - CSSAllProperty.py, - ), - }); - } - - static boxSize>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases( - CSSAllProperty.w, - CSSAllProperty.minW, - CSSAllProperty.maxW, - CSSAllProperty.h, - CSSAllProperty.minH, - CSSAllProperty.maxH, - CSSAllProperty.boxSize, - ), - fallback: 0 as number, - }); - } - - static fontSize>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.fontSize), - fallback: 0 as number, - }); - } - - static lineHeight>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.lineHeight), - fallback: 0 as number, - }); - } - - static rounded>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - fallback: 0, - on: expandAliases( - CSSAllProperty.rounded, - CSSAllProperty.roundedTop, - CSSAllProperty.roundedBottom, - CSSAllProperty.roundedLeft, - CSSAllProperty.roundedRight, - - CSSProperty.borderTopLeftRadius, - CSSProperty.borderTopRightRadius, - CSSProperty.borderBottomLeftRadius, - CSSProperty.borderBottomRightRadius, - CSSProperty.borderTopRightRadius, - CSSProperty.borderBottomRightRadius, - CSSProperty.borderTopLeftRadius, - CSSProperty.borderBottomLeftRadius, - ), - }); - } - - static transitionTimingFunction>( - value: T, - ) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.transitionTimingFunction), - }); - } - - static transitionDuration>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.transitionDuration), - transform: (v) => `${v}ms`, - }); - } - - static font>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.font), - }); - } - - static fontWeight>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.fontWeight), - }); - } - - static letterSpacing>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.letterSpacing), - }); - } - - static shadow>(value: T) { - return DesignToken.create(DesignTokenType.var, { - value, - on: expandAliases(CSSAllProperty.shadow), - }); - } - - static customMixin< - CustomProp extends string, - T extends DesignTokenValues>, - >(customProp: CustomProp, value: T) { - return DesignToken.create(DesignTokenType.mixin, { - value: value, - on: [customProp], - }); - } - - static mixin(sx: T): WithMixin { - return { ...sx, __mixin: true }; - } + static create< + Tokens extends DesignTokenValues, + CSSPropNames extends keyof CSSAllProps | string, + InputValueType = DesignTokenValue, + ValueType = InputValueType, + Fallback = unknown, + >( + type: DesignTokenType, + { + value, + on, + transform, + }: { + value: Tokens; + on: Array; + transform?: DesignTokenTransform; + fallback?: Fallback; + }, + ): DesignTokenOption< + Tokens, + CSSPropNames, + InputValueType, + ValueType, + Fallback + > { + return { + type, + value, + on: on, + transform: transform, + + __Tokens: undefined as any, + __ValueType: undefined as any, + __CSSTokens: undefined as any, + }; + } + + static color>( + value: T, + ) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases( + CSSAllProperty.color, + CSSAllProperty.bgColor, + CSSAllProperty.outlineColor, + CSSAllProperty.borderColor, + CSSAllProperty.accentColor, + + CSSAllProperty.fill, + CSSAllProperty.stroke, + ), + transform: ( + rgb: [number, number, number] | string, + cssVar: (token: string) => string, + ) => { + return isString(rgb) + ? { + default: `var(${cssVar(rgb)})`, + rgb: `var(${cssVar(`${rgb}/rgb`)})`, + } + : { + default: `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`, + rgb: `${rgb[0]} ${rgb[1]} ${rgb[2]}`, + }; + }, + fallback: "" as Color, + }); + } + + static space>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases( + CSSAllProperty.gap, + CSSAllProperty.rowGap, + CSSAllProperty.columnGap, + + CSSAllProperty.top, + CSSAllProperty.right, + CSSAllProperty.bottom, + CSSAllProperty.left, + + CSSAllProperty.m, + CSSAllProperty.ms, + CSSAllProperty.me, + CSSAllProperty.mt, + CSSAllProperty.mr, + CSSAllProperty.mb, + CSSAllProperty.ml, + CSSAllProperty.mx, + CSSAllProperty.my, + + CSSAllProperty.p, + CSSAllProperty.ps, + CSSAllProperty.pe, + CSSAllProperty.pt, + CSSAllProperty.pr, + CSSAllProperty.pb, + CSSAllProperty.pl, + CSSAllProperty.px, + CSSAllProperty.py, + ), + }); + } + + static boxSize>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases( + CSSAllProperty.w, + CSSAllProperty.minW, + CSSAllProperty.maxW, + CSSAllProperty.h, + CSSAllProperty.minH, + CSSAllProperty.maxH, + CSSAllProperty.boxSize, + ), + fallback: 0 as number, + }); + } + + static fontSize>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.fontSize), + fallback: 0 as number, + }); + } + + static lineHeight>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.lineHeight), + fallback: 0 as number, + }); + } + + static rounded>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + fallback: 0, + on: expandAliases( + CSSAllProperty.rounded, + CSSAllProperty.roundedTop, + CSSAllProperty.roundedBottom, + CSSAllProperty.roundedLeft, + CSSAllProperty.roundedRight, + + CSSProperty.borderTopLeftRadius, + CSSProperty.borderTopRightRadius, + CSSProperty.borderBottomLeftRadius, + CSSProperty.borderBottomRightRadius, + CSSProperty.borderTopRightRadius, + CSSProperty.borderBottomRightRadius, + CSSProperty.borderTopLeftRadius, + CSSProperty.borderBottomLeftRadius, + ), + }); + } + + static transitionTimingFunction>( + value: T, + ) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.transitionTimingFunction), + }); + } + + static transitionDuration>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.transitionDuration), + transform: (v) => `${v}ms`, + }); + } + + static font>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.font), + }); + } + + static fontWeight>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.fontWeight), + }); + } + + static letterSpacing>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.letterSpacing), + }); + } + + static shadow>(value: T) { + return DesignToken.create(DesignTokenType.var, { + value, + on: expandAliases(CSSAllProperty.shadow), + }); + } + + static customMixin< + CustomProp extends string, + T extends DesignTokenValues>, + >(customProp: CustomProp, value: T) { + return DesignToken.create(DesignTokenType.mixin, { + value: value, + on: [customProp], + }); + } + + static mixin(sx: T): WithMixin { + return { ...sx, __mixin: true }; + } } diff --git a/nodepkg/vueuikit/src/theming/token/FigmaToken.ts b/nodepkg/vueuikit/src/theming/token/FigmaToken.ts index 52aa74b2..f3f9f60a 100644 --- a/nodepkg/vueuikit/src/theming/token/FigmaToken.ts +++ b/nodepkg/vueuikit/src/theming/token/FigmaToken.ts @@ -1,170 +1,164 @@ -import {get, has, isPlainObject, isString, last, mapValues, pickBy, some} from "@innoai-tech/lodash"; -import {DesignToken} from "./DesignToken"; -import {setTo} from "./util.ts"; -import {parseToRgb as polishedParseToRgb} from "polished" +import { + get, + has, + isPlainObject, + isString, + last, + mapValues, + pickBy, + some, +} from "@innoai-tech/lodash"; +import { DesignToken } from "./DesignToken"; +import { setTo } from "./util.ts"; +import { parseToRgb as polishedParseToRgb } from "polished"; export type FigmaToken = { - $type: string; - $value: any; + $type: string; + $value: any; }; export type FigmaTokenValues = { - [K: string]: FigmaToken | FigmaTokenValues; + [K: string]: FigmaToken | FigmaTokenValues; }; export function themeFromFigmaTokens( - defaultTokens: FigmaTokenValues[], - darkTokens: FigmaTokenValues[], + defaultTokens: FigmaTokenValues[], + darkTokens: FigmaTokenValues[], ) { - const target: Record = {}; - - walkFigmaTokens(defaultTokens, (v: any, path: string[]) => { - if (path[0] === "color" && path[1] === "sys") { - setTo(target, ["color", "sys", last(path)!, "_default"], processValue(v)); - return; - } - setTo(target, path, processValue(v)); - }); + const target: Record = {}; - walkFigmaTokens(darkTokens, (v: any, path: string[]) => { - if (path[0] === "color" && path[1] === "sys") { - setTo(target, ["color", "sys", last(path)!, "_dark"], processValue(v)); - return; - } - setTo(target, path, processValue(v)); - }); + walkFigmaTokens(defaultTokens, (v: any, path: string[]) => { + if (path[0] === "color" && path[1] === "sys") { + setTo(target, ["color", "sys", last(path)!, "_default"], processValue(v)); + return; + } + setTo(target, path, processValue(v)); + }); - const tokens: Record = {}; - - for (const topic in target) { - switch (topic) { - case "fontWeight": - tokens[topic] = DesignToken.fontWeight(target[topic]); - break; - case "font": - tokens[topic] = DesignToken.font(target[topic]); - break; - case "shadow": - tokens[topic] = DesignToken.shadow(target[topic]); - break; - case "rounded": - tokens[topic] = DesignToken.rounded(target[topic]); - break; - case "color": - tokens[topic] = DesignToken.color(target[topic]); - break; - case "textStyle": - tokens[topic] = DesignToken.customMixin("textStyle", target[topic]); - break; - } + walkFigmaTokens(darkTokens, (v: any, path: string[]) => { + if (path[0] === "color" && path[1] === "sys") { + setTo(target, ["color", "sys", last(path)!, "_dark"], processValue(v)); + return; } + setTo(target, path, processValue(v)); + }); + + const tokens: Record = {}; + + for (const topic in target) { + switch (topic) { + case "fontWeight": + tokens[topic] = DesignToken.fontWeight(target[topic]); + break; + case "font": + tokens[topic] = DesignToken.font(target[topic]); + break; + case "shadow": + tokens[topic] = DesignToken.shadow(target[topic]); + break; + case "rounded": + tokens[topic] = DesignToken.rounded(target[topic]); + break; + case "color": + tokens[topic] = DesignToken.color(target[topic]); + break; + case "textStyle": + tokens[topic] = DesignToken.customMixin("textStyle", target[topic]); + break; + } + } - return tokens; + return tokens; } function processValue(v: any): any { - if (isPlainObject(v)) { - return DesignToken.mixin(mapValues(v, (subV) => processValue(subV))); - } + if (isPlainObject(v)) { + return DesignToken.mixin(mapValues(v, (subV) => processValue(subV))); + } - if (isString(v)) { - if (v.includes("* {seed.space.dp}")) { - return parseFloat(v.replaceAll("* {seed.space.dp}", "")); - } + if (isString(v)) { + if (v.includes("* {seed.space.dp}")) { + return parseFloat(v.replaceAll("* {seed.space.dp}", "")); + } - if (v.startsWith("rgb") || v.startsWith("#")) { - return parseToRgb(v); - } + if (v.startsWith("rgb") || v.startsWith("#")) { + return parseToRgb(v); + } - return v.replace(/\{([^}]+)}/g, (v) => { - let scopeAndKey = v.slice(1, v.length - 1); + return v.replace(/\{([^}]+)}/g, (v) => { + let scopeAndKey = v.slice(1, v.length - 1); - if (scopeAndKey.startsWith("seed.")) { - scopeAndKey = scopeAndKey.slice("seed.".length) - } + if (scopeAndKey.startsWith("seed.")) { + scopeAndKey = scopeAndKey.slice("seed.".length); + } - const [_, ...key] = scopeAndKey.split("."); + const [_, ...key] = scopeAndKey.split("."); - if (key.length) { - return key.join("."); - } - return v; - }); - } - return v; + if (key.length) { + return key.join("."); + } + return v; + }); + } + return v; } function parseToRgb(color: string) { - const rgb = polishedParseToRgb(color) - // biome-ignore lint/style/noNonNullAssertion: - return [rgb.red, rgb.green, rgb.blue]; + const rgb = polishedParseToRgb(color); + // biome-ignore lint/style/noNonNullAssertion: + return [rgb.red, rgb.green, rgb.blue]; } function walkFigmaTokens( - tokens: FigmaTokenValues[], - cb: (value: any, path: string[]) => void, + tokens: FigmaTokenValues[], + cb: (value: any, path: string[]) => void, ) { - for (const token of tokens) { - walkValue(token, cb); - } + for (const token of tokens) { + walkValue(token, cb); + } } function walkValue( - o: any, - cb: (v: any, path: any[]) => void, - parent: any[] = [], + o: any, + cb: (v: any, path: any[]) => void, + parent: any[] = [], ) { - for (const k in o) { - const v = o[k] as any; - - if (isPlainObject(v)) { - if (has(v, "$type")) { - cb(v["$value"], [...parent, k]); - continue; - } - walkValue(v, cb, [...parent, k]); - continue; - } - cb(v, [...parent, k]); + for (const k in o) { + const v = o[k] as any; + + if (isPlainObject(v)) { + if (has(v, "$type")) { + cb(v["$value"], [...parent, k]); + continue; + } + walkValue(v, cb, [...parent, k]); + continue; } + cb(v, [...parent, k]); + } } - const groups = { - "primary": [ - "primary" - ], - "secondary": [ - "secondary" - ], - "tertiary": [ - "tertiary" - ], - "error": [ - "error" - ], - "warning": [ - "warning" - ], - "success": [ - "success" - ], - "neutral": [ - "surface", - "outline", - ], -} + primary: ["primary"], + secondary: ["secondary"], + tertiary: ["tertiary"], + error: ["error"], + warning: ["warning"], + success: ["success"], + neutral: ["surface", "outline"], +}; export const groupSysColors = (figmaTokensValues: FigmaTokenValues) => { - const sysColors = get(figmaTokensValues, ["color", "sys"], {}) - - return { - ...figmaTokensValues, - color: { - sys: mapValues(groups, (matches) => { - return pickBy(sysColors, (_, token) => some(matches, (m) => token.indexOf(m) > -1)) - }) - } - }; -} - + const sysColors = get(figmaTokensValues, ["color", "sys"], {}); + + return { + ...figmaTokensValues, + color: { + sys: mapValues(groups, (matches) => { + return pickBy(sysColors, (_, token) => + some(matches, (m) => token.indexOf(m) > -1), + ); + }), + }, + }; +}; diff --git a/nodepkg/vueuikit/src/theming/token/Mixin.ts b/nodepkg/vueuikit/src/theming/token/Mixin.ts index 11ff567c..323eb518 100644 --- a/nodepkg/vueuikit/src/theming/token/Mixin.ts +++ b/nodepkg/vueuikit/src/theming/token/Mixin.ts @@ -3,39 +3,39 @@ import type { DesignTokenOption } from "./DesignToken"; import { isMap } from "./util"; export class Mixin { - static walkValue = ( - o: any, - cb: (v: any, path: any[]) => void, - parent: any[] = [], - ) => { - for (const k in o) { - const v = o[k] as any; - if (isMap(v)) { - if (has(v, "__mixin")) { - cb(v, [...parent, k]); - continue; - } - Mixin.walkValue(v, cb, [...parent, k]); - continue; - } - cb(v, [...parent, k]); - } - }; + static walkValue = ( + o: any, + cb: (v: any, path: any[]) => void, + parent: any[] = [], + ) => { + for (const k in o) { + const v = o[k] as any; + if (isMap(v)) { + if (has(v, "__mixin")) { + cb(v, [...parent, k]); + continue; + } + Mixin.walkValue(v, cb, [...parent, k]); + continue; + } + cb(v, [...parent, k]); + } + }; - private _values: Record = {}; + private _values: Record = {}; - constructor(dt: DesignTokenOption) { - Mixin.walkValue(dt.value, (v, p) => { - const token = p.join("."); - set(this._values, [token], omit(v, "__mixin")); - }); - } + constructor(dt: DesignTokenOption) { + Mixin.walkValue(dt.value, (v, p) => { + const token = p.join("."); + set(this._values, [token], omit(v, "__mixin")); + }); + } - get tokens() { - return keys(this._values); - } + get tokens() { + return keys(this._values); + } - get(token: string) { - return this._values[token]; - } + get(token: string) { + return this._values[token]; + } } diff --git a/nodepkg/vueuikit/src/theming/token/TokenSet.ts b/nodepkg/vueuikit/src/theming/token/TokenSet.ts index dceda74c..3bf85a82 100644 --- a/nodepkg/vueuikit/src/theming/token/TokenSet.ts +++ b/nodepkg/vueuikit/src/theming/token/TokenSet.ts @@ -3,110 +3,110 @@ import type { DesignTokenOptionAny } from "./DesignToken"; import { isMap } from "./util"; export class TokenSet { - static defaultMode = "_default"; + static defaultMode = "_default"; - static walkValues = ( - o: any, - cb: (v: any, path: any[]) => void, - parent: any[] = [], - ) => { - for (const k in o) { - const v = o[k] as any; - if (isMap(v)) { - if (has(v, "_default")) { - cb(v, [...parent, k]); - continue; - } - TokenSet.walkValues(v, cb, [...parent, k]); - continue; - } - cb(v, [...parent, k]); - } - }; + static walkValues = ( + o: any, + cb: (v: any, path: any[]) => void, + parent: any[] = [], + ) => { + for (const k in o) { + const v = o[k] as any; + if (isMap(v)) { + if (has(v, "_default")) { + cb(v, [...parent, k]); + continue; + } + TokenSet.walkValues(v, cb, [...parent, k]); + continue; + } + cb(v, [...parent, k]); + } + }; - __Tokens: T["__Tokens"] = undefined as any; + __Tokens: T["__Tokens"] = undefined as any; - private _values: Record> = {}; - private _cssVarRefs: Record = {}; + private _values: Record> = {}; + private _cssVarRefs: Record = {}; - constructor( - dt: T, - { - cssVar, - transformFallback, - }: { - cssVar: (token: string) => string; - transformFallback: (v: any) => any; - }, - ) { - TokenSet.walkValues(dt.value, (v, p) => { - const token = p.join("."); + constructor( + dt: T, + { + cssVar, + transformFallback, + }: { + cssVar: (token: string) => string; + transformFallback: (v: any) => any; + }, + ) { + TokenSet.walkValues(dt.value, (v, p) => { + const token = p.join("."); - const forEachTransformed = (v: any, each: (v: any, k: string) => any) => { - const ret = dt.transform - ? dt.transform(v, cssVar) - : transformFallback(v); + const forEachTransformed = (v: any, each: (v: any, k: string) => any) => { + const ret = dt.transform + ? dt.transform(v, cssVar) + : transformFallback(v); - if (isObject(ret)) { - for (const i in ret) { - each((ret as any)[i], i === "default" ? "" : i); - } - } else { - each(ret, ""); - } - }; + if (isObject(ret)) { + for (const i in ret) { + each((ret as any)[i], i === "default" ? "" : i); + } + } else { + each(ret, ""); + } + }; - // Base Token - set(this._cssVarRefs, [token], cssVar(token)); + // Base Token + set(this._cssVarRefs, [token], cssVar(token)); - if (isMap(v)) { - // mutimode + if (isMap(v)) { + // mutimode - for (const k in v) { - forEachTransformed(v[k], (value, variant) => { - set( - this._values, - [`${token}${variant ? `/${variant}` : ""}`, k], - value, - ); - }); - } - } else { - forEachTransformed(v, (value, variant) => { - set(this._values, [`${token}${variant ? `/${variant}` : ""}`], value); - }); - } - }); - } + for (const k in v) { + forEachTransformed(v[k], (value, variant) => { + set( + this._values, + [`${token}${variant ? `/${variant}` : ""}`, k], + value, + ); + }); + } + } else { + forEachTransformed(v, (value, variant) => { + set(this._values, [`${token}${variant ? `/${variant}` : ""}`], value); + }); + } + }); + } - get tokens() { - return keys(this._values); - } + get tokens() { + return keys(this._values); + } - get(token: string, modePseudo: string, strict?: boolean) { - const v = this._values[token]; - if (isObject(v)) { - if (strict) { - return v[modePseudo]; - } - return v[modePseudo] ?? v[TokenSet.defaultMode]; - } - if (strict) { - if (modePseudo === TokenSet.defaultMode) { - return v; - } - return undefined; - } - return v; - } + get(token: string, modePseudo: string, strict?: boolean) { + const v = this._values[token]; + if (isObject(v)) { + if (strict) { + return v[modePseudo]; + } + return v[modePseudo] ?? v[TokenSet.defaultMode]; + } + if (strict) { + if (modePseudo === TokenSet.defaultMode) { + return v; + } + return undefined; + } + return v; + } - use(token: string, variableOnly = false): string | undefined { - if (this._cssVarRefs[token]) { - if (variableOnly) { - return `${this._cssVarRefs[token]}`; - } - return `var(${this._cssVarRefs[token]})`; - } - return; - } + use(token: string, variableOnly = false): string | undefined { + if (this._cssVarRefs[token]) { + if (variableOnly) { + return `${this._cssVarRefs[token]}`; + } + return `var(${this._cssVarRefs[token]})`; + } + return; + } } diff --git a/nodepkg/vueuikit/src/theming/token/util.ts b/nodepkg/vueuikit/src/theming/token/util.ts index c2db688e..ba8ad377 100644 --- a/nodepkg/vueuikit/src/theming/token/util.ts +++ b/nodepkg/vueuikit/src/theming/token/util.ts @@ -4,18 +4,18 @@ export const isMap = isPlainObject; // set like lodash.set but without array export const setTo = (target: any, keyPath: string[], v: any) => { - let f = target; + let f = target; - for (let i = 0; i < keyPath.length; i++) { - // biome-ignore lint/style/noNonNullAssertion: - const key = keyPath[i]!; + for (let i = 0; i < keyPath.length; i++) { + // biome-ignore lint/style/noNonNullAssertion: + const key = keyPath[i]!; - if (i === keyPath.length - 1) { - f[key] = v; - continue; - } + if (i === keyPath.length - 1) { + f[key] = v; + continue; + } - f[key] = f[key] ?? {}; - f = f[key]; - } + f[key] = f[key] ?? {}; + f = f[key]; + } }; diff --git a/nodepkg/vueuikit/src/theming/token/variant.ts b/nodepkg/vueuikit/src/theming/token/variant.ts index 939fa751..763a8b54 100644 --- a/nodepkg/vueuikit/src/theming/token/variant.ts +++ b/nodepkg/vueuikit/src/theming/token/variant.ts @@ -1,33 +1,33 @@ import { has, isFunction } from "@innoai-tech/lodash"; export const alpha = (a: number) => { - return (cssVar: string): any => `rgba(var(${cssVar}--rgb) / ${a})`; + return (cssVar: string): any => `rgba(var(${cssVar}--rgb) / ${a})`; }; export interface Variant { - (cssVar: string): any; + (cssVar: string): any; - token: T; + token: T; - toString(): T; + toString(): T; } export const isVariant = (fn: any): fn is Variant => { - return isFunction(fn) && has(fn, "token"); + return isFunction(fn) && has(fn, "token"); }; export function variant( - token: T, - toVariant: (v: string) => any, + token: T, + toVariant: (v: string) => any, ): T { - // FIXME fake type to bump as Token - return Object.assign( - (cssVar: string): any => { - return toVariant(cssVar); - }, - { - toString: () => token, - token: token, - }, - ) as any; + // FIXME fake type to bump as Token + return Object.assign( + (cssVar: string): any => { + return toVariant(cssVar); + }, + { + toString: () => token, + token: token, + }, + ) as any; } diff --git a/nodepkg/vueuikit/src/theming/typeutil.ts b/nodepkg/vueuikit/src/theming/typeutil.ts index 49b3fe94..f4a43f9c 100644 --- a/nodepkg/vueuikit/src/theming/typeutil.ts +++ b/nodepkg/vueuikit/src/theming/typeutil.ts @@ -1,9 +1,7 @@ export type UnionToIntersection = ( - U extends any - ? (k: U) => void - : never + U extends any ? (k: U) => void : never ) extends (k: infer I) => void - ? I - : never; + ? I + : never; export type ValuesOf = T[keyof T]; diff --git a/nodepkg/vueuikit/src/useInsertStyles.tsx b/nodepkg/vueuikit/src/useInsertStyles.tsx index 17e8731f..c022cd4d 100644 --- a/nodepkg/vueuikit/src/useInsertStyles.tsx +++ b/nodepkg/vueuikit/src/useInsertStyles.tsx @@ -2,19 +2,19 @@ import { type SerializedStyles } from "@emotion/serialize"; import { type EmotionCache, insertStyles } from "@emotion/utils"; export const useInsertStyles = (cache: EmotionCache) => { - return (props: { - withoutScoping?: boolean; - isStringTag?: boolean; - serialized?: SerializedStyles; - }) => { - if (!props.serialized) { - return; - } + return (props: { + withoutScoping?: boolean; + isStringTag?: boolean; + serialized?: SerializedStyles; + }) => { + if (!props.serialized) { + return; + } - if (props.withoutScoping) { - cache.insert("", props.serialized, cache.sheet, true); - } else { - insertStyles(cache, props.serialized, props.isStringTag ?? true); - } - }; + if (props.withoutScoping) { + cache.insert("", props.serialized, cache.sheet, true); + } else { + insertStyles(cache, props.serialized, props.isStringTag ?? true); + } + }; }; diff --git a/nodepkg/vueuikit/tsconfig.monobundle.json b/nodepkg/vueuikit/tsconfig.monobundle.json index 47bb7a9f..3f7c3bea 100644 --- a/nodepkg/vueuikit/tsconfig.monobundle.json +++ b/nodepkg/vueuikit/tsconfig.monobundle.json @@ -1,7 +1,7 @@ { - "extends": "@innoai-tech/vuedevconfig/tsconfig.json", - "compilerOptions": { - "rootDir": "./src" - }, - "exclude": ["example"] + "extends": "@innoai-tech/vuedevconfig/tsconfig.json", + "compilerOptions": { + "rootDir": "./src" + }, + "exclude": ["example"] } diff --git a/package.json b/package.json index 2bc68028..c3910d02 100644 --- a/package.json +++ b/package.json @@ -21,23 +21,28 @@ "dependencies": { "core-js": "^3.36.0" }, + "prettier": {}, "devDependencies": { - "@happy-dom/global-registrator": "^13.3.8", + "prettier": "^3.2.5", + "@happy-dom/global-registrator": "^13.4.1", "@innoai-tech/lodash": "^0.2.1", "@innoai-tech/devconfig": "^0.5.0", - "@innoai-tech/monobundle": "^0.12.0", + "@innoai-tech/monobundle": "^0.12.1", "@innoai-tech/vue-vite-presets": "workspace:*", "@innoai-tech/vuedevconfig": "workspace:*", - "@biomejs/biome": "^1.5.3", "@mdi/js": "^7.4.47", "@vue/test-utils": "^2.4.4", - "bun-types": "^1.0.27", + "bun-types": "^1.0.29", "copy-to-clipboard": "^3.3.3", "normalize.css": "^8.0.1", "rxjs": "^7.8.1", "turbo": "^1.12.4", "typescript": "^5.3.3", - "vite": "^5.1.3", + "vite": "^5.1.4", "vue": "v3.4.19" + }, + "resolutions": { + "vite": "^5.1.4", + "vue": "^3.4.19" } }