@shoppexio/builder-contracts 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/builder-settings.d.ts +11 -666
  2. package/dist/builder-settings.d.ts.map +1 -1
  3. package/dist/builder-settings.js +2 -1
  4. package/dist/canonical-settings.d.ts +18 -0
  5. package/dist/canonical-settings.d.ts.map +1 -0
  6. package/dist/canonical-settings.js +106 -0
  7. package/dist/custom-pages.d.ts +15 -0
  8. package/dist/custom-pages.d.ts.map +1 -0
  9. package/dist/custom-pages.js +40 -0
  10. package/dist/dedicated-pages.d.ts +15 -0
  11. package/dist/dedicated-pages.d.ts.map +1 -0
  12. package/dist/dedicated-pages.js +142 -0
  13. package/dist/events.d.ts +35 -240
  14. package/dist/events.d.ts.map +1 -1
  15. package/dist/events.js +7 -0
  16. package/dist/fields.d.ts +229 -10
  17. package/dist/fields.d.ts.map +1 -1
  18. package/dist/fields.js +27 -0
  19. package/dist/index.d.ts +10 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +10 -0
  22. package/dist/legacy-manifest.d.ts +18 -0
  23. package/dist/legacy-manifest.d.ts.map +1 -1
  24. package/dist/legacy-manifest.js +137 -22
  25. package/dist/migrations.d.ts.map +1 -1
  26. package/dist/migrations.js +55 -6
  27. package/dist/persistence.d.ts +7 -0
  28. package/dist/persistence.d.ts.map +1 -0
  29. package/dist/persistence.js +58 -0
  30. package/dist/preview-boot.d.ts +68 -0
  31. package/dist/preview-boot.d.ts.map +1 -0
  32. package/dist/preview-boot.js +38 -0
  33. package/dist/preview-protocol.d.ts +227 -459
  34. package/dist/preview-protocol.d.ts.map +1 -1
  35. package/dist/preview-protocol.js +112 -0
  36. package/dist/preview-session-resolve.d.ts +115 -0
  37. package/dist/preview-session-resolve.d.ts.map +1 -0
  38. package/dist/preview-session-resolve.js +25 -0
  39. package/dist/preview-trusted-origins.d.ts +4 -0
  40. package/dist/preview-trusted-origins.d.ts.map +1 -0
  41. package/dist/preview-trusted-origins.js +28 -0
  42. package/dist/storefront-initial-data-html.d.ts +17 -0
  43. package/dist/storefront-initial-data-html.d.ts.map +1 -0
  44. package/dist/storefront-initial-data-html.js +83 -0
  45. package/dist/storefront-typography-fonts.d.ts +18 -0
  46. package/dist/storefront-typography-fonts.d.ts.map +1 -0
  47. package/dist/storefront-typography-fonts.js +89 -0
  48. package/dist/style-slots.d.ts +50 -152
  49. package/dist/style-slots.d.ts.map +1 -1
  50. package/dist/style-slots.js +80 -32
  51. package/dist/theme-manifest.d.ts +287 -456
  52. package/dist/theme-manifest.d.ts.map +1 -1
  53. package/dist/theme-manifest.js +92 -0
  54. package/dist/theme-schemes.d.ts +10 -0
  55. package/dist/theme-schemes.d.ts.map +1 -0
  56. package/dist/theme-schemes.js +25 -0
  57. package/dist/validation.d.ts +1 -1
  58. package/dist/validation.d.ts.map +1 -1
  59. package/dist/validation.js +23 -12
  60. package/package.json +43 -1
  61. package/src/builder-contracts.test.ts +416 -3
  62. package/src/builder-settings.ts +4 -1
  63. package/src/canonical-settings.ts +156 -0
  64. package/src/custom-pages.test.ts +74 -0
  65. package/src/custom-pages.ts +70 -0
  66. package/src/dedicated-pages.test.ts +88 -0
  67. package/src/dedicated-pages.ts +173 -0
  68. package/src/events.ts +8 -0
  69. package/src/fields.ts +30 -0
  70. package/src/index.ts +10 -0
  71. package/src/legacy-manifest.ts +147 -23
  72. package/src/migrations.ts +70 -6
  73. package/src/persistence.ts +77 -0
  74. package/src/preview-boot.test.ts +72 -0
  75. package/src/preview-boot.ts +49 -0
  76. package/src/preview-protocol.test.ts +132 -0
  77. package/src/preview-protocol.ts +122 -0
  78. package/src/preview-session-resolve.test.ts +37 -0
  79. package/src/preview-session-resolve.ts +34 -0
  80. package/src/preview-trusted-origins.test.ts +24 -0
  81. package/src/preview-trusted-origins.ts +35 -0
  82. package/src/storefront-initial-data-html.test.ts +73 -0
  83. package/src/storefront-initial-data-html.ts +112 -0
  84. package/src/storefront-typography-fonts.test.ts +48 -0
  85. package/src/storefront-typography-fonts.ts +108 -0
  86. package/src/style-slots.ts +102 -34
  87. package/src/theme-manifest.ts +118 -1
  88. package/src/theme-schemes.ts +34 -0
  89. package/src/validation.ts +32 -13
  90. package/dist/builder-contracts.test.d.ts +0 -2
  91. package/dist/builder-contracts.test.d.ts.map +0 -1
  92. package/dist/builder-contracts.test.js +0 -361
@@ -0,0 +1,74 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import {
5
+ MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS,
6
+ mergeCustomPagesIntoManifest,
7
+ } from './custom-pages.ts';
8
+ import { CustomPageSchema } from './builder-settings.ts';
9
+ import { createEmptyBuilderSettings } from './migrations.ts';
10
+ import { convertLegacyThemeManifest } from './legacy-manifest.ts';
11
+ import { validateBuilderSettingsAgainstManifest } from './validation.ts';
12
+
13
+ function loadDefaultManifest() {
14
+ const manifestPath = join(import.meta.dir, '../../../themes/default/theme.manifest.json');
15
+ return convertLegacyThemeManifest(JSON.parse(readFileSync(manifestPath, 'utf8')) as unknown);
16
+ }
17
+
18
+ describe('custom pages manifest merge', () => {
19
+ test('keeps MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS aligned with config SSOT', () => {
20
+ const configPath = join(import.meta.dir, '../../../config/builder-shared-manifest-blocks.json');
21
+ const config = JSON.parse(readFileSync(configPath, 'utf8')) as {
22
+ customPageAllowedBlocks: string[];
23
+ };
24
+
25
+ expect([...MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS]).toEqual(config.customPageAllowedBlocks);
26
+ });
27
+
28
+ test('merges merchant custom pages into the effective manifest', () => {
29
+ const manifest = loadDefaultManifest();
30
+ const settings = createEmptyBuilderSettings(1);
31
+ settings.theme.pages = [
32
+ {
33
+ id: 'custom-about',
34
+ title: 'About Us',
35
+ slug: 'about-us',
36
+ visible: true,
37
+ layout: { blocks: [] },
38
+ },
39
+ ];
40
+ settings.theme.layout['custom-about'] = {
41
+ blocks: [
42
+ {
43
+ id: 'youtube-1',
44
+ type: 'youtube-embed',
45
+ visible: true,
46
+ settings: {
47
+ videoUrl: 'https://youtu.be/dQw4w9WgXcQ',
48
+ },
49
+ },
50
+ ],
51
+ };
52
+
53
+ const effective = mergeCustomPagesIntoManifest(manifest, settings);
54
+ expect(effective.pages['custom-about']).toEqual({
55
+ label: 'About Us',
56
+ previewPath: '/page/about-us',
57
+ allowedBlocks: expect.arrayContaining(['youtube-embed', 'custom-html']),
58
+ defaultBlocks: [],
59
+ });
60
+ expect(validateBuilderSettingsAgainstManifest(settings, manifest)).toEqual([]);
61
+ });
62
+
63
+ test('rejects multi-segment custom page slugs', () => {
64
+ const result = CustomPageSchema.safeParse({
65
+ id: 'custom-help-faq',
66
+ title: 'Help FAQ',
67
+ slug: 'help/faq',
68
+ visible: true,
69
+ layout: { blocks: [] },
70
+ });
71
+
72
+ expect(result.success).toBe(false);
73
+ });
74
+ });
@@ -0,0 +1,70 @@
1
+ import type { BuilderSettings } from './builder-settings.ts';
2
+ import type { ManifestPage, ThemeManifest } from './theme-manifest.ts';
3
+
4
+ /**
5
+ * Allowed embed/content blocks on merchant custom pages (`theme.pages[]`).
6
+ * Keep aligned with `customPageAllowedBlocks` in
7
+ * `config/builder-shared-manifest-blocks.json`.
8
+ */
9
+ export const MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS = [
10
+ 'custom-html',
11
+ 'youtube-embed',
12
+ 'text-block',
13
+ ] as const;
14
+
15
+ export type MerchantCustomPageAllowedBlock =
16
+ (typeof MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS)[number];
17
+
18
+ export function resolveMerchantCustomPageAllowedBlocks(
19
+ manifest: ThemeManifest,
20
+ ): string[] {
21
+ return MERCHANT_CUSTOM_PAGE_ALLOWED_BLOCKS.filter(
22
+ (blockType) => blockType in manifest.blocks,
23
+ );
24
+ }
25
+
26
+ export function buildMerchantCustomPageManifestEntry(
27
+ customPage: BuilderSettings['theme']['pages'][number],
28
+ manifest: ThemeManifest,
29
+ ): ManifestPage {
30
+ return {
31
+ label: customPage.title,
32
+ previewPath: `/page/${customPage.slug}`,
33
+ allowedBlocks: resolveMerchantCustomPageAllowedBlocks(manifest),
34
+ defaultBlocks: [],
35
+ };
36
+ }
37
+
38
+ export function mergeCustomPagesIntoManifest(
39
+ manifest: ThemeManifest,
40
+ settings: BuilderSettings,
41
+ ): ThemeManifest {
42
+ if (settings.theme.pages.length === 0) {
43
+ return manifest;
44
+ }
45
+
46
+ const pages = { ...manifest.pages };
47
+ for (const customPage of settings.theme.pages) {
48
+ if (pages[customPage.id]) {
49
+ continue;
50
+ }
51
+
52
+ pages[customPage.id] = buildMerchantCustomPageManifestEntry(customPage, manifest);
53
+ }
54
+
55
+ return { ...manifest, pages };
56
+ }
57
+
58
+ export function findCustomPageBySlug(
59
+ settings: BuilderSettings,
60
+ slug: string,
61
+ ): BuilderSettings['theme']['pages'][number] | undefined {
62
+ return settings.theme.pages.find((page) => page.slug === slug);
63
+ }
64
+
65
+ export function findCustomPageById(
66
+ settings: BuilderSettings,
67
+ pageId: string,
68
+ ): BuilderSettings['theme']['pages'][number] | undefined {
69
+ return settings.theme.pages.find((page) => page.id === pageId);
70
+ }
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import {
3
+ createDedicatedPageBlockType,
4
+ dedicatedPageContentKey,
5
+ dedicatedPagePreviewPath,
6
+ migrateDedicatedPageContent,
7
+ migrateDedicatedPageLayout,
8
+ normalizeDedicatedPageId,
9
+ } from './dedicated-pages.ts';
10
+
11
+ describe('dedicated-pages', () => {
12
+ test('normalizeDedicatedPageId maps legacy reviews id', () => {
13
+ expect(normalizeDedicatedPageId('reviews')).toBe('reviews-page');
14
+ expect(normalizeDedicatedPageId('faq-page')).toBe('faq-page');
15
+ });
16
+
17
+ test('createDedicatedPageBlockType follows page-{pageId} rule', () => {
18
+ expect(createDedicatedPageBlockType('faq-page')).toBe('page-faq-page');
19
+ expect(createDedicatedPageBlockType('reviews-page')).toBe('page-reviews-page');
20
+ expect(createDedicatedPageBlockType('reviews')).toBe('page-reviews');
21
+ });
22
+
23
+ test('dedicatedPagePreviewPath resolves known storefront paths', () => {
24
+ expect(dedicatedPagePreviewPath('contact-page')).toBe('/contact');
25
+ expect(dedicatedPagePreviewPath('reviews')).toBe('/reviews');
26
+ expect(dedicatedPagePreviewPath('home')).toBeNull();
27
+ });
28
+
29
+ test('dedicatedPageContentKey uses canonical page id', () => {
30
+ expect(dedicatedPageContentKey('reviews', 'title')).toBe('pages.reviews-page.title');
31
+ });
32
+
33
+ test('migrateDedicatedPageLayout renames legacy reviews page layout', () => {
34
+ const migrated = migrateDedicatedPageLayout({
35
+ home: { blocks: [] },
36
+ reviews: {
37
+ blocks: [{ id: 'reviews-1', type: 'page-reviews', visible: true, settings: {} }],
38
+ },
39
+ });
40
+
41
+ expect(migrated.reviews).toBeUndefined();
42
+ expect(migrated['reviews-page']).toMatchObject({
43
+ blocks: [{ id: 'reviews-1', type: 'page-reviews-page', visible: true, settings: {} }],
44
+ });
45
+ });
46
+
47
+ test('migrateDedicatedPageContent renames legacy content keys', () => {
48
+ const migrated = migrateDedicatedPageContent({
49
+ 'pages.reviews.title': 'Reviews',
50
+ 'tool.title': 'Free Tool',
51
+ 'hero.title': 'Home',
52
+ });
53
+
54
+ expect(migrated['pages.reviews.title']).toBeUndefined();
55
+ expect(migrated['pages.reviews-page.title']).toBe('Reviews');
56
+ expect(migrated['tool.title']).toBeUndefined();
57
+ expect(migrated['pages.tool-page.title']).toBe('Free Tool');
58
+ expect(migrated['hero.title']).toBe('Home');
59
+ });
60
+
61
+ test('migrateDedicatedPageLayout renames legacy vault tool page', () => {
62
+ const migrated = migrateDedicatedPageLayout({
63
+ tool: {
64
+ blocks: [{
65
+ id: 'tool-1',
66
+ type: 'tool',
67
+ visible: true,
68
+ settings: { 'tool.title': 'Free Tool' },
69
+ }],
70
+ },
71
+ });
72
+
73
+ expect(migrated.tool).toBeUndefined();
74
+ expect(migrated['tool-page']).toMatchObject({
75
+ blocks: [{
76
+ id: 'tool-1',
77
+ type: 'page-tool-page',
78
+ visible: true,
79
+ settings: { 'pages.tool-page.title': 'Free Tool' },
80
+ }],
81
+ });
82
+ });
83
+
84
+ test('dedicatedPagePreviewPath resolves tool-page', () => {
85
+ expect(dedicatedPagePreviewPath('tool-page')).toBe('/tool');
86
+ expect(dedicatedPagePreviewPath('tool')).toBe('/tool');
87
+ });
88
+ });
@@ -0,0 +1,173 @@
1
+ export const BUILDER_LAYOUT_PAGE_IDS = [
2
+ 'home',
3
+ 'navigation',
4
+ 'product',
5
+ 'footer',
6
+ 'terms',
7
+ ] as const;
8
+
9
+ export type BuilderLayoutPageId = (typeof BUILDER_LAYOUT_PAGE_IDS)[number];
10
+
11
+ export const BUILDER_DEDICATED_PAGE_IDS = [
12
+ 'contact-page',
13
+ 'faq-page',
14
+ 'reviews-page',
15
+ 'feedback-page',
16
+ 'guide-page',
17
+ 'status-page',
18
+ 'checkout-page',
19
+ 'tool-page',
20
+ ] as const;
21
+
22
+ export type BuilderDedicatedPageId = (typeof BUILDER_DEDICATED_PAGE_IDS)[number];
23
+
24
+ /** @deprecated Legacy manifest page ids migrated on settings load. */
25
+ export const DEDICATED_PAGE_ID_ALIASES: Readonly<Record<string, BuilderDedicatedPageId>> = {
26
+ reviews: 'reviews-page',
27
+ tool: 'tool-page',
28
+ };
29
+
30
+ const DEDICATED_PAGE_PREVIEW_PATHS: Readonly<Record<BuilderDedicatedPageId, string>> = {
31
+ 'contact-page': '/contact',
32
+ 'faq-page': '/faq',
33
+ 'reviews-page': '/reviews',
34
+ 'feedback-page': '/feedback',
35
+ 'guide-page': '/guide',
36
+ 'status-page': '/status',
37
+ 'checkout-page': '/checkout',
38
+ 'tool-page': '/tool',
39
+ };
40
+
41
+ export function normalizeDedicatedPageId(pageId: string): string {
42
+ return DEDICATED_PAGE_ID_ALIASES[pageId] ?? pageId;
43
+ }
44
+
45
+ export function isDedicatedPageId(pageId: string): pageId is BuilderDedicatedPageId {
46
+ return (BUILDER_DEDICATED_PAGE_IDS as readonly string[]).includes(pageId);
47
+ }
48
+
49
+ export function dedicatedPagePreviewPath(pageId: string): string | null {
50
+ const normalized = normalizeDedicatedPageId(pageId);
51
+ if (!isDedicatedPageId(normalized)) {
52
+ return null;
53
+ }
54
+ return DEDICATED_PAGE_PREVIEW_PATHS[normalized];
55
+ }
56
+
57
+ export function dedicatedPageContentKey(pageId: string, field: string): string {
58
+ return `pages.${normalizeDedicatedPageId(pageId)}.${field}`;
59
+ }
60
+
61
+ export function normalizeDedicatedPageBlockType(pageId: string): string {
62
+ return createDedicatedPageBlockType(normalizeDedicatedPageId(pageId));
63
+ }
64
+
65
+ export function createDedicatedPageBlockType(pageId: string): string {
66
+ const normalizedPageId = pageId
67
+ .toLowerCase()
68
+ .replace(/[^a-z0-9\-_.]+/g, '-')
69
+ .replace(/^[-_.]+/, '');
70
+ return `page-${normalizedPageId || 'custom'}`;
71
+ }
72
+
73
+ function migrateDedicatedPageBlockSettings(settings: unknown): Record<string, unknown> {
74
+ if (typeof settings !== 'object' || settings === null || Array.isArray(settings)) {
75
+ return {};
76
+ }
77
+
78
+ const next: Record<string, unknown> = {};
79
+ for (const [key, value] of Object.entries(settings)) {
80
+ if (key.startsWith('tool.')) {
81
+ next[`pages.tool-page.${key.slice('tool.'.length)}`] = value;
82
+ continue;
83
+ }
84
+ next[key] = value;
85
+ }
86
+ return next;
87
+ }
88
+
89
+ export function migrateDedicatedPageLayout(layout: Record<string, unknown>): Record<string, unknown> {
90
+ const next: Record<string, unknown> = { ...layout };
91
+
92
+ for (const [legacyId, canonicalId] of Object.entries(DEDICATED_PAGE_ID_ALIASES)) {
93
+ if (!Object.prototype.hasOwnProperty.call(next, legacyId)) {
94
+ continue;
95
+ }
96
+
97
+ const legacyLayout = next[legacyId];
98
+ delete next[legacyId];
99
+
100
+ if (Object.prototype.hasOwnProperty.call(next, canonicalId)) {
101
+ continue;
102
+ }
103
+
104
+ if (typeof legacyLayout !== 'object' || legacyLayout === null || Array.isArray(legacyLayout)) {
105
+ continue;
106
+ }
107
+
108
+ const blocks = (legacyLayout as { blocks?: unknown }).blocks;
109
+ if (!Array.isArray(blocks)) {
110
+ next[canonicalId] = legacyLayout;
111
+ continue;
112
+ }
113
+
114
+ const legacyBlockType = createDedicatedPageBlockType(legacyId);
115
+ const canonicalBlockType = createDedicatedPageBlockType(canonicalId);
116
+
117
+ next[canonicalId] = {
118
+ ...legacyLayout,
119
+ blocks: blocks.map((block) => {
120
+ if (typeof block !== 'object' || block === null || Array.isArray(block)) {
121
+ return block;
122
+ }
123
+ const typed = block as { type?: unknown };
124
+ const isLegacyBlockType = typed.type === legacyBlockType
125
+ || (legacyId === 'tool' && typed.type === 'tool');
126
+ if (!isLegacyBlockType) {
127
+ return block;
128
+ }
129
+ return {
130
+ ...typed,
131
+ type: canonicalBlockType,
132
+ settings: migrateDedicatedPageBlockSettings(
133
+ (typed as { settings?: unknown }).settings,
134
+ ),
135
+ };
136
+ }),
137
+ };
138
+ }
139
+
140
+ return next;
141
+ }
142
+
143
+ export function migrateDedicatedPageContent(content: Record<string, unknown>): Record<string, unknown> {
144
+ const next: Record<string, unknown> = { ...content };
145
+
146
+ for (const [key, value] of Object.entries(next)) {
147
+ if (key.startsWith('tool.') && !key.startsWith('tool-page.')) {
148
+ const canonicalKey = `pages.tool-page.${key.slice('tool.'.length)}`;
149
+ if (!Object.prototype.hasOwnProperty.call(next, canonicalKey)) {
150
+ next[canonicalKey] = value;
151
+ }
152
+ delete next[key];
153
+ }
154
+ }
155
+
156
+ for (const [legacyId, canonicalId] of Object.entries(DEDICATED_PAGE_ID_ALIASES)) {
157
+ const legacyPrefix = `pages.${legacyId}.`;
158
+ const canonicalPrefix = `pages.${canonicalId}.`;
159
+
160
+ for (const [key, value] of Object.entries(next)) {
161
+ if (!key.startsWith(legacyPrefix)) {
162
+ continue;
163
+ }
164
+ const canonicalKey = `${canonicalPrefix}${key.slice(legacyPrefix.length)}`;
165
+ if (!Object.prototype.hasOwnProperty.call(next, canonicalKey)) {
166
+ next[canonicalKey] = value;
167
+ }
168
+ delete next[key];
169
+ }
170
+ }
171
+
172
+ return next;
173
+ }
package/src/events.ts CHANGED
@@ -102,6 +102,13 @@ const TermsSetEventSchema = EventBaseSchema.extend({
102
102
  value: z.string(),
103
103
  }).strict();
104
104
 
105
+ const PresetApplyEventSchema = EventBaseSchema.extend({
106
+ type: z.literal('preset.apply'),
107
+ pageId: PageIdSchema,
108
+ presetId: z.string().min(1),
109
+ blockId: z.string().min(1).optional(),
110
+ }).strict();
111
+
105
112
  export const BuilderEventSchema = z.discriminatedUnion('type', [
106
113
  ContentSetEventSchema,
107
114
  ListSetEventSchema,
@@ -117,5 +124,6 @@ export const BuilderEventSchema = z.discriminatedUnion('type', [
117
124
  PageUpsertEventSchema,
118
125
  PageRemoveEventSchema,
119
126
  TermsSetEventSchema,
127
+ PresetApplyEventSchema,
120
128
  ]);
121
129
  export type BuilderEvent = z.infer<typeof BuilderEventSchema>;
package/src/fields.ts CHANGED
@@ -7,6 +7,11 @@ const FieldBaseSchema = z
7
7
  description: z.string().min(1).optional(),
8
8
  defaultValue: z.unknown().optional(),
9
9
  required: z.boolean().optional(),
10
+ // Groups settings within the Inspector. "content" (default) shows
11
+ // under the Block Settings collapsible; "profile" shows under Profile
12
+ // banner; "style" shows under Style; "search" shows under Search bar.
13
+ // Themes pick up the value via the existing block.settings.path lookup.
14
+ group: z.enum(['content', 'profile', 'style', 'search']).optional(),
10
15
  })
11
16
  .strict();
12
17
 
@@ -30,6 +35,30 @@ const RichTextFieldSchema = FieldBaseSchema.extend({
30
35
  allowedMarks: z.array(z.enum(['bold', 'italic', 'link', 'code'])).optional(),
31
36
  }).strict();
32
37
 
38
+ // Plain-text source code field. Renders a Monaco editor in the
39
+ // Inspector and persists the raw string back into block.settings.
40
+ // Used by the `custom-html` block (and any future block that needs
41
+ // a code-shaped setting). Themes interpret the string however they
42
+ // see fit — sandboxed iframe srcDoc for HTML, etc.
43
+ const CodeFieldTemplateSchema = z
44
+ .object({
45
+ label: z.string().min(1),
46
+ snippet: z.string().min(1),
47
+ description: z.string().min(1).optional(),
48
+ })
49
+ .strict();
50
+ export type CodeFieldTemplate = z.infer<typeof CodeFieldTemplateSchema>;
51
+
52
+ const CodeFieldSchema = FieldBaseSchema.extend({
53
+ type: z.literal('code'),
54
+ language: z.enum(['html', 'css', 'javascript', 'json']).optional(),
55
+ placeholder: z.string().optional(),
56
+ maxLength: z.number().int().positive().optional(),
57
+ // Optional quick-insert chips rendered above the editor. Clicking a
58
+ // template replaces the field value with the snippet.
59
+ templates: z.array(CodeFieldTemplateSchema).optional(),
60
+ }).strict();
61
+
33
62
  const ImageFieldSchema = FieldBaseSchema.extend({
34
63
  type: z.literal('image'),
35
64
  aspectRatio: z.string().min(1).optional(),
@@ -117,6 +146,7 @@ const ListFieldSchema = FieldBaseSchema.extend({
117
146
  export const BuilderFieldSchema = z.discriminatedUnion('type', [
118
147
  TextFieldSchema,
119
148
  RichTextFieldSchema,
149
+ CodeFieldSchema,
120
150
  ImageFieldSchema,
121
151
  LinkFieldSchema,
122
152
  BooleanFieldSchema,
package/src/index.ts CHANGED
@@ -1,9 +1,19 @@
1
+ export * from './custom-pages.ts';
2
+ export * from './dedicated-pages.ts';
1
3
  export * from './builder-settings.ts';
4
+ export * from './canonical-settings.ts';
2
5
  export * from './events.ts';
3
6
  export * from './fields.ts';
4
7
  export * from './legacy-manifest.ts';
5
8
  export * from './migrations.ts';
9
+ export * from './persistence.ts';
10
+ export * from './preview-boot.ts';
6
11
  export * from './preview-protocol.ts';
12
+ export * from './preview-session-resolve.ts';
13
+ export * from './preview-trusted-origins.ts';
14
+ export * from './storefront-initial-data-html.ts';
15
+ export * from './storefront-typography-fonts.ts';
7
16
  export * from './style-slots.ts';
8
17
  export * from './theme-manifest.ts';
18
+ export * from './theme-schemes.ts';
9
19
  export * from './validation.ts';