@bettercms-ai/ui 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ // src/resolve.ts
4
+ function childArrays(block) {
5
+ const p = block.props ?? {};
6
+ switch (block.type) {
7
+ case "columns":
8
+ return Array.isArray(p.columns) ? p.columns : [];
9
+ case "section":
10
+ return Array.isArray(p.children) ? [p.children] : [];
11
+ case "slider":
12
+ return Array.isArray(p.slides) ? p.slides.map((s) => Array.isArray(s.children) ? s.children : []) : [];
13
+ case "tabs":
14
+ return Array.isArray(p.tabs) ? p.tabs.map((t) => Array.isArray(t.children) ? t.children : []) : [];
15
+ default:
16
+ return [];
17
+ }
18
+ }
19
+ function findBlockById(blocks, id) {
20
+ for (const b of blocks) {
21
+ if (b?.id === id) return b;
22
+ for (const child of childArrays(b)) {
23
+ const found = findBlockById(child, id);
24
+ if (found) return found;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ function setPath(obj, path, value) {
30
+ const keys = path.split(".");
31
+ let cur = obj;
32
+ for (let i = 0; i < keys.length - 1; i++) {
33
+ const k = keys[i];
34
+ if (typeof cur[k] !== "object" || cur[k] === null) cur[k] = {};
35
+ cur = cur[k];
36
+ }
37
+ cur[keys[keys.length - 1]] = value;
38
+ }
39
+ function applyOverrides(blockJson, propDefs, overrides) {
40
+ if (!Array.isArray(blockJson)) return [];
41
+ if (!propDefs.length || !overrides || Object.keys(overrides).length === 0) {
42
+ return blockJson;
43
+ }
44
+ const clone = structuredClone(blockJson);
45
+ for (const def of propDefs) {
46
+ if (!(def.key in overrides)) continue;
47
+ const target = findBlockById(clone, def.target.blockId);
48
+ if (target) setPath(target, def.target.path, overrides[def.key]);
49
+ }
50
+ return clone;
51
+ }
52
+ export {
53
+ applyOverrides,
54
+ childArrays,
55
+ findBlockById,
56
+ setPath
57
+ };
58
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve.ts"],"sourcesContent":["/**\n * Pure component-instance resolver — clone a component's blockJson and write each\n * declared override at its target (blockId + dotted path), honoring the props[]\n * allowlist. The single source of truth shared by the dashboard canvas preview, the\n * backend live HTML renderer, and @bettercms-ai/next's SSG output, so all three resolve\n * instances identically. React-free and exported on its own subpath (@bettercms-ai/ui/\n * resolve) so the Hono server can import it without pulling in the block components.\n */\n\n/** Minimal block shape the resolver needs — every real block type is structurally wider.\n * `props` is `unknown` (not `Record<string, unknown>`) so typed block unions like\n * @bettercms-ai/types' ContentBlock — whose `props` are specific interfaces without an\n * index signature — still satisfy the constraint. */\nexport type ResolverBlock = { type: string; id?: string; props?: unknown };\n\n/** Minimal prop-def shape — the real ComponentPropDef is structurally wider. */\nexport type ResolverPropDef = { key: string; target: { blockId: string; path: string } };\n\n/** The child block arrays a container holds, one entry per nested slot (all container types). */\nexport function childArrays<B extends ResolverBlock>(block: B): B[][] {\n const p = (block.props ?? {}) as Record<string, unknown>;\n switch (block.type) {\n case \"columns\":\n return Array.isArray(p.columns) ? (p.columns as B[][]) : [];\n case \"section\":\n return Array.isArray(p.children) ? [p.children as B[]] : [];\n case \"slider\":\n return Array.isArray(p.slides)\n ? (p.slides as { children?: B[] }[]).map((s) => (Array.isArray(s.children) ? s.children : []))\n : [];\n case \"tabs\":\n return Array.isArray(p.tabs)\n ? (p.tabs as { children?: B[] }[]).map((t) => (Array.isArray(t.children) ? t.children : []))\n : [];\n default:\n return [];\n }\n}\n\n/** Find a block by id anywhere in a tree (descends every container). */\nexport function findBlockById<B extends ResolverBlock>(blocks: B[], id: string): B | null {\n for (const b of blocks) {\n if (b?.id === id) return b;\n for (const child of childArrays(b)) {\n const found = findBlockById(child, id);\n if (found) return found;\n }\n }\n return null;\n}\n\n/** Write `value` at a dotted path (e.g. \"props.text\"), creating objects as needed. */\nexport function setPath(obj: Record<string, unknown>, path: string, value: unknown): void {\n const keys = path.split(\".\");\n let cur: Record<string, unknown> = obj;\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i]!;\n if (typeof cur[k] !== \"object\" || cur[k] === null) cur[k] = {};\n cur = cur[k] as Record<string, unknown>;\n }\n cur[keys[keys.length - 1]!] = value;\n}\n\n/**\n * Apply an instance's `overrides` onto a clone of a component's blockJson, honoring the\n * declared `props[]` allowlist (key → target block + path). Returns the original array\n * untouched (referentially stable) when there's nothing to override; returns [] for a\n * non-array input.\n */\nexport function applyOverrides<B extends ResolverBlock>(\n blockJson: unknown,\n propDefs: ResolverPropDef[],\n overrides: Record<string, unknown> | undefined,\n): B[] {\n if (!Array.isArray(blockJson)) return [];\n if (!propDefs.length || !overrides || Object.keys(overrides).length === 0) {\n return blockJson as B[];\n }\n const clone = structuredClone(blockJson) as B[];\n for (const def of propDefs) {\n if (!(def.key in overrides)) continue;\n const target = findBlockById(clone, def.target.blockId);\n if (target) setPath(target as unknown as Record<string, unknown>, def.target.path, overrides[def.key]);\n }\n return clone;\n}\n"],"mappings":";;;AAmBO,SAAS,YAAqC,OAAiB;AACpE,QAAM,IAAK,MAAM,SAAS,CAAC;AAC3B,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,MAAM,QAAQ,EAAE,OAAO,IAAK,EAAE,UAAoB,CAAC;AAAA,IAC5D,KAAK;AACH,aAAO,MAAM,QAAQ,EAAE,QAAQ,IAAI,CAAC,EAAE,QAAe,IAAI,CAAC;AAAA,IAC5D,KAAK;AACH,aAAO,MAAM,QAAQ,EAAE,MAAM,IACxB,EAAE,OAAgC,IAAI,CAAC,MAAO,MAAM,QAAQ,EAAE,QAAQ,IAAI,EAAE,WAAW,CAAC,CAAE,IAC3F,CAAC;AAAA,IACP,KAAK;AACH,aAAO,MAAM,QAAQ,EAAE,IAAI,IACtB,EAAE,KAA8B,IAAI,CAAC,MAAO,MAAM,QAAQ,EAAE,QAAQ,IAAI,EAAE,WAAW,CAAC,CAAE,IACzF,CAAC;AAAA,IACP;AACE,aAAO,CAAC;AAAA,EACZ;AACF;AAGO,SAAS,cAAuC,QAAa,IAAsB;AACxF,aAAW,KAAK,QAAQ;AACtB,QAAI,GAAG,OAAO,GAAI,QAAO;AACzB,eAAW,SAAS,YAAY,CAAC,GAAG;AAClC,YAAM,QAAQ,cAAc,OAAO,EAAE;AACrC,UAAI,MAAO,QAAO;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,QAAQ,KAA8B,MAAc,OAAsB;AACxF,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,MAA+B;AACnC,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACxC,UAAM,IAAI,KAAK,CAAC;AAChB,QAAI,OAAO,IAAI,CAAC,MAAM,YAAY,IAAI,CAAC,MAAM,KAAM,KAAI,CAAC,IAAI,CAAC;AAC7D,UAAM,IAAI,CAAC;AAAA,EACb;AACA,MAAI,KAAK,KAAK,SAAS,CAAC,CAAE,IAAI;AAChC;AAQO,SAAS,eACd,WACA,UACA,WACK;AACL,MAAI,CAAC,MAAM,QAAQ,SAAS,EAAG,QAAO,CAAC;AACvC,MAAI,CAAC,SAAS,UAAU,CAAC,aAAa,OAAO,KAAK,SAAS,EAAE,WAAW,GAAG;AACzE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,gBAAgB,SAAS;AACvC,aAAW,OAAO,UAAU;AAC1B,QAAI,EAAE,IAAI,OAAO,WAAY;AAC7B,UAAM,SAAS,cAAc,OAAO,IAAI,OAAO,OAAO;AACtD,QAAI,OAAQ,SAAQ,QAA8C,IAAI,OAAO,MAAM,UAAU,IAAI,GAAG,CAAC;AAAA,EACvG;AACA,SAAO;AACT;","names":[]}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@bettercms-ai/ui",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "description": "BetterCMS block components + registry/renderer for rendering page block_json in React (dashboard builder preview + headless sites).",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./blocks": {
20
+ "types": "./dist/blocks.d.ts",
21
+ "import": "./dist/blocks.js",
22
+ "default": "./dist/blocks.js"
23
+ },
24
+ "./registry": {
25
+ "types": "./dist/registry.d.ts",
26
+ "import": "./dist/registry.js",
27
+ "default": "./dist/registry.js"
28
+ },
29
+ "./renderer": {
30
+ "types": "./dist/renderer.d.ts",
31
+ "import": "./dist/renderer.js",
32
+ "default": "./dist/renderer.js"
33
+ },
34
+ "./resolve": {
35
+ "types": "./dist/resolve.d.ts",
36
+ "import": "./dist/resolve.js",
37
+ "default": "./dist/resolve.js"
38
+ }
39
+ },
40
+ "scripts": {
41
+ "typecheck": "tsc --noEmit",
42
+ "build": "tsup",
43
+ "prepack": "tsup",
44
+ "lint": "echo 'lint not yet configured'",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "add": "echo 'Use npx shadcn@latest add -p ../.. <component>'"
48
+ },
49
+ "dependencies": {
50
+ "@bettercms-ai/types": "^1.3.1",
51
+ "clsx": "^2.1.1",
52
+ "tailwind-merge": "^2.6.0"
53
+ },
54
+ "peerDependencies": {
55
+ "react": ">=18",
56
+ "react-dom": ">=18"
57
+ },
58
+ "devDependencies": {
59
+ "@swc/core": "^1.15.24",
60
+ "@types/react": "^19.0.0",
61
+ "@types/react-dom": "^19.0.0",
62
+ "@vitejs/plugin-react": "^6.0.1",
63
+ "@vitest/coverage-v8": "^4.1.4",
64
+ "autoprefixer": "^10.4.21",
65
+ "jsdom": "^26.1.0",
66
+ "postcss": "^8.5.4",
67
+ "react": "^19.2.0",
68
+ "react-dom": "^19.2.0",
69
+ "tailwindcss": "^3.4.17",
70
+ "tailwindcss-animate": "^1.0.7",
71
+ "tsup": "^8.5.1",
72
+ "typescript": "^5.8.3",
73
+ "vitest": "^4.1.4"
74
+ },
75
+ "publishConfig": {
76
+ "access": "public",
77
+ "registry": "https://registry.npmjs.org/"
78
+ }
79
+ }