@zalify/storefront-kit 0.1.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.
Files changed (213) hide show
  1. package/LICENSE.md +49 -0
  2. package/README.md +63 -0
  3. package/dist/commerce/countdown.d.ts +18 -0
  4. package/dist/commerce/countdown.js +28 -0
  5. package/dist/commerce/events.d.ts +29 -0
  6. package/dist/commerce/events.js +14 -0
  7. package/dist/commerce/facets.d.ts +27 -0
  8. package/dist/commerce/facets.js +71 -0
  9. package/dist/commerce/index.d.ts +11 -0
  10. package/dist/commerce/index.js +11 -0
  11. package/dist/commerce/money.d.ts +51 -0
  12. package/dist/commerce/money.js +83 -0
  13. package/dist/commerce/product.d.ts +102 -0
  14. package/dist/commerce/product.js +93 -0
  15. package/dist/editor/frame.d.ts +57 -0
  16. package/dist/editor/frame.js +241 -0
  17. package/dist/editor/host.d.ts +31 -0
  18. package/dist/editor/host.js +67 -0
  19. package/dist/editor/index.d.ts +9 -0
  20. package/dist/editor/index.js +9 -0
  21. package/dist/react/adapter.d.ts +49 -0
  22. package/dist/react/adapter.js +23 -0
  23. package/dist/react/blocks/_box.d.ts +10 -0
  24. package/dist/react/blocks/_box.js +17 -0
  25. package/dist/react/blocks/_slide.d.ts +24 -0
  26. package/dist/react/blocks/_slide.js +21 -0
  27. package/dist/react/blocks/_video-card.d.ts +11 -0
  28. package/dist/react/blocks/_video-card.js +39 -0
  29. package/dist/react/blocks/accordion.d.ts +12 -0
  30. package/dist/react/blocks/accordion.js +5 -0
  31. package/dist/react/blocks/button.d.ts +10 -0
  32. package/dist/react/blocks/button.js +15 -0
  33. package/dist/react/blocks/countdown.d.ts +9 -0
  34. package/dist/react/blocks/countdown.js +64 -0
  35. package/dist/react/blocks/custom-liquid.d.ts +11 -0
  36. package/dist/react/blocks/custom-liquid.js +4 -0
  37. package/dist/react/blocks/group.d.ts +9 -0
  38. package/dist/react/blocks/group.js +8 -0
  39. package/dist/react/blocks/image.d.ts +8 -0
  40. package/dist/react/blocks/image.js +7 -0
  41. package/dist/react/blocks/message.d.ts +14 -0
  42. package/dist/react/blocks/message.js +5 -0
  43. package/dist/react/blocks/quote.d.ts +9 -0
  44. package/dist/react/blocks/quote.js +5 -0
  45. package/dist/react/blocks/spacer.d.ts +8 -0
  46. package/dist/react/blocks/spacer.js +5 -0
  47. package/dist/react/blocks/story.d.ts +17 -0
  48. package/dist/react/blocks/story.js +7 -0
  49. package/dist/react/blocks/text.d.ts +9 -0
  50. package/dist/react/blocks/text.js +5 -0
  51. package/dist/react/blocks/video.d.ts +17 -0
  52. package/dist/react/blocks/video.js +56 -0
  53. package/dist/react/components/Facets.d.ts +21 -0
  54. package/dist/react/components/Facets.js +220 -0
  55. package/dist/react/components/Icon.d.ts +12 -0
  56. package/dist/react/components/Icon.js +66 -0
  57. package/dist/react/components/PaymentIcons.d.ts +5 -0
  58. package/dist/react/components/PaymentIcons.generated.d.ts +22 -0
  59. package/dist/react/components/PaymentIcons.generated.js +21 -0
  60. package/dist/react/components/PaymentIcons.js +18 -0
  61. package/dist/react/components/Price.d.ts +24 -0
  62. package/dist/react/components/Price.js +9 -0
  63. package/dist/react/components/ProductBadges.d.ts +24 -0
  64. package/dist/react/components/ProductBadges.js +57 -0
  65. package/dist/react/components/ProductCard.d.ts +17 -0
  66. package/dist/react/components/ProductCard.js +202 -0
  67. package/dist/react/components/Rating.d.ts +11 -0
  68. package/dist/react/components/Rating.js +35 -0
  69. package/dist/react/components/SwatchStyle.d.ts +23 -0
  70. package/dist/react/components/SwatchStyle.js +22 -0
  71. package/dist/react/components/VideoModal.d.ts +55 -0
  72. package/dist/react/components/VideoModal.js +102 -0
  73. package/dist/react/engine/CssVariables.d.ts +1 -0
  74. package/dist/react/engine/CssVariables.js +105 -0
  75. package/dist/react/engine/context.d.ts +43 -0
  76. package/dist/react/engine/context.js +45 -0
  77. package/dist/react/engine/fonts.d.ts +15 -0
  78. package/dist/react/engine/fonts.js +33 -0
  79. package/dist/react/engine/images.d.ts +24 -0
  80. package/dist/react/engine/images.js +40 -0
  81. package/dist/react/engine/render.d.ts +29 -0
  82. package/dist/react/engine/render.js +87 -0
  83. package/dist/react/engine/settings.d.ts +8 -0
  84. package/dist/react/engine/settings.js +68 -0
  85. package/dist/react/engine/store.d.ts +64 -0
  86. package/dist/react/engine/store.js +42 -0
  87. package/dist/react/engine/translate.d.ts +7 -0
  88. package/dist/react/engine/translate.js +33 -0
  89. package/dist/react/engine/types.d.ts +128 -0
  90. package/dist/react/engine/types.js +1 -0
  91. package/dist/react/index.d.ts +38 -0
  92. package/dist/react/index.js +39 -0
  93. package/dist/react/registries.d.ts +10 -0
  94. package/dist/react/registries.js +68 -0
  95. package/dist/react/registries.server.d.ts +2 -0
  96. package/dist/react/registries.server.js +98 -0
  97. package/dist/react/sections/404.d.ts +10 -0
  98. package/dist/react/sections/404.js +10 -0
  99. package/dist/react/sections/announcement-bar.d.ts +14 -0
  100. package/dist/react/sections/announcement-bar.js +84 -0
  101. package/dist/react/sections/article.d.ts +17 -0
  102. package/dist/react/sections/article.js +31 -0
  103. package/dist/react/sections/contact-form.d.ts +16 -0
  104. package/dist/react/sections/contact-form.js +8 -0
  105. package/dist/react/sections/custom-liquid.d.ts +14 -0
  106. package/dist/react/sections/custom-liquid.js +7 -0
  107. package/dist/react/sections/custom-section.d.ts +14 -0
  108. package/dist/react/sections/custom-section.js +9 -0
  109. package/dist/react/sections/featured-collection.d.ts +10 -0
  110. package/dist/react/sections/featured-collection.js +23 -0
  111. package/dist/react/sections/hero.d.ts +6 -0
  112. package/dist/react/sections/hero.js +35 -0
  113. package/dist/react/sections/image-with-text.d.ts +13 -0
  114. package/dist/react/sections/image-with-text.js +9 -0
  115. package/dist/react/sections/multicolumn.d.ts +15 -0
  116. package/dist/react/sections/multicolumn.js +8 -0
  117. package/dist/react/sections/page.d.ts +13 -0
  118. package/dist/react/sections/page.js +11 -0
  119. package/dist/react/sections/password.d.ts +15 -0
  120. package/dist/react/sections/password.js +9 -0
  121. package/dist/react/sections/rich-text.d.ts +12 -0
  122. package/dist/react/sections/rich-text.js +7 -0
  123. package/dist/react/sections/stories.d.ts +18 -0
  124. package/dist/react/sections/stories.js +10 -0
  125. package/dist/react/sections/video-bubble.d.ts +13 -0
  126. package/dist/react/sections/video-bubble.js +54 -0
  127. package/dist/react/sections/video-carousel.d.ts +19 -0
  128. package/dist/react/sections/video-carousel.js +9 -0
  129. package/dist/react/server.d.ts +7 -0
  130. package/dist/react/server.js +42 -0
  131. package/dist/schemas/bridge.d.ts +98 -0
  132. package/dist/schemas/bridge.js +50 -0
  133. package/dist/schemas/data.d.ts +54 -0
  134. package/dist/schemas/data.js +22 -0
  135. package/dist/schemas/index.d.ts +14 -0
  136. package/dist/schemas/index.js +14 -0
  137. package/dist/schemas/manifest.d.ts +63 -0
  138. package/dist/schemas/manifest.js +32 -0
  139. package/dist/schemas/schema.d.ts +159 -0
  140. package/dist/schemas/schema.js +129 -0
  141. package/dist/schemas/validate.d.ts +22 -0
  142. package/dist/schemas/validate.js +199 -0
  143. package/package.json +84 -0
  144. package/src/commerce/countdown.ts +38 -0
  145. package/src/commerce/events.ts +43 -0
  146. package/src/commerce/facets.ts +88 -0
  147. package/src/commerce/index.ts +11 -0
  148. package/src/commerce/money.ts +120 -0
  149. package/src/commerce/product.ts +177 -0
  150. package/src/editor/frame.ts +305 -0
  151. package/src/editor/host.ts +101 -0
  152. package/src/editor/index.ts +9 -0
  153. package/src/react/adapter.tsx +83 -0
  154. package/src/react/blocks/_box.tsx +37 -0
  155. package/src/react/blocks/_slide.tsx +96 -0
  156. package/src/react/blocks/_video-card.tsx +131 -0
  157. package/src/react/blocks/accordion.tsx +33 -0
  158. package/src/react/blocks/button.tsx +36 -0
  159. package/src/react/blocks/countdown.tsx +105 -0
  160. package/src/react/blocks/custom-liquid.tsx +21 -0
  161. package/src/react/blocks/group.tsx +32 -0
  162. package/src/react/blocks/image.tsx +29 -0
  163. package/src/react/blocks/message.tsx +28 -0
  164. package/src/react/blocks/quote.tsx +24 -0
  165. package/src/react/blocks/spacer.tsx +17 -0
  166. package/src/react/blocks/story.tsx +40 -0
  167. package/src/react/blocks/text.tsx +24 -0
  168. package/src/react/blocks/video.tsx +132 -0
  169. package/src/react/components/Facets.tsx +424 -0
  170. package/src/react/components/Icon.tsx +96 -0
  171. package/src/react/components/PaymentIcons.generated.ts +24 -0
  172. package/src/react/components/PaymentIcons.tsx +27 -0
  173. package/src/react/components/Price.tsx +72 -0
  174. package/src/react/components/ProductBadges.tsx +95 -0
  175. package/src/react/components/ProductCard.tsx +417 -0
  176. package/src/react/components/Rating.tsx +59 -0
  177. package/src/react/components/SwatchStyle.tsx +50 -0
  178. package/src/react/components/VideoModal.tsx +236 -0
  179. package/src/react/engine/CssVariables.tsx +142 -0
  180. package/src/react/engine/context.tsx +108 -0
  181. package/src/react/engine/fonts.ts +42 -0
  182. package/src/react/engine/images.tsx +58 -0
  183. package/src/react/engine/render.tsx +190 -0
  184. package/src/react/engine/settings.ts +89 -0
  185. package/src/react/engine/store.ts +103 -0
  186. package/src/react/engine/translate.ts +40 -0
  187. package/src/react/engine/types.ts +142 -0
  188. package/src/react/index.ts +78 -0
  189. package/src/react/registries.server.ts +110 -0
  190. package/src/react/registries.ts +80 -0
  191. package/src/react/sections/404.tsx +37 -0
  192. package/src/react/sections/announcement-bar.tsx +170 -0
  193. package/src/react/sections/article.tsx +97 -0
  194. package/src/react/sections/contact-form.tsx +104 -0
  195. package/src/react/sections/custom-liquid.tsx +39 -0
  196. package/src/react/sections/custom-section.tsx +44 -0
  197. package/src/react/sections/featured-collection.tsx +89 -0
  198. package/src/react/sections/hero.tsx +84 -0
  199. package/src/react/sections/image-with-text.tsx +61 -0
  200. package/src/react/sections/multicolumn.tsx +46 -0
  201. package/src/react/sections/page.tsx +43 -0
  202. package/src/react/sections/password.tsx +60 -0
  203. package/src/react/sections/rich-text.tsx +37 -0
  204. package/src/react/sections/stories.tsx +51 -0
  205. package/src/react/sections/video-bubble.tsx +145 -0
  206. package/src/react/sections/video-carousel.tsx +57 -0
  207. package/src/react/server.ts +60 -0
  208. package/src/schemas/bridge.ts +143 -0
  209. package/src/schemas/data.ts +72 -0
  210. package/src/schemas/index.ts +14 -0
  211. package/src/schemas/manifest.ts +85 -0
  212. package/src/schemas/schema.ts +287 -0
  213. package/src/schemas/validate.ts +302 -0
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Contract validators. Pure functions returning issue lists (never
3
+ * throwing): callers decide whether an issue is fatal. Structural
4
+ * invariants always run; schema-aware checks (block whitelists,
5
+ * max_blocks) run only when a manifest is provided, so templates can
6
+ * be validated before the component registry is known.
7
+ */
8
+ import type {BlockData, SectionGroupData, TemplateData} from './data.ts';
9
+ import type {ThemeEditorManifest} from './manifest.ts';
10
+ import {
11
+ DEFAULT_MAX_BLOCKS,
12
+ allowedChildTypes,
13
+ isInputSetting,
14
+ isLocalBlockDefinition,
15
+ type SectionBlockEntry,
16
+ type SettingDefinition,
17
+ type ThemeBlockSchema,
18
+ } from './schema.ts';
19
+
20
+ export interface ValidationIssue {
21
+ /** JSON-pointer-ish location, e.g. "sections.hero.blocks.slide". */
22
+ path: string;
23
+ code:
24
+ | 'order-unknown-key'
25
+ | 'order-duplicate-key'
26
+ | 'unknown-type'
27
+ | 'block-not-allowed'
28
+ | 'max-blocks-exceeded'
29
+ | 'setting-missing-id'
30
+ | 'setting-duplicate-id'
31
+ | 'manifest-missing-field';
32
+ message: string;
33
+ }
34
+
35
+ interface SchemaContext {
36
+ manifest: ThemeEditorManifest;
37
+ allBlockTypes: string[];
38
+ }
39
+
40
+ function checkOrder(
41
+ order: string[] | undefined,
42
+ keys: Record<string, unknown> | undefined,
43
+ path: string,
44
+ issues: ValidationIssue[],
45
+ ): void {
46
+ if (!order) return;
47
+ const seen = new Set<string>();
48
+ for (const key of order) {
49
+ if (seen.has(key)) {
50
+ issues.push({
51
+ path,
52
+ code: 'order-duplicate-key',
53
+ message: `"${key}" appears more than once in the order array`,
54
+ });
55
+ }
56
+ seen.add(key);
57
+ if (!keys || !(key in keys)) {
58
+ issues.push({
59
+ path,
60
+ code: 'order-unknown-key',
61
+ message: `order references "${key}" which has no entry`,
62
+ });
63
+ }
64
+ }
65
+ }
66
+
67
+ /** Local block definitions in a section's schema, keyed by type. */
68
+ function localDefinitions(
69
+ rules: SectionBlockEntry[] | undefined,
70
+ ): Record<string, ThemeBlockSchema> {
71
+ const defs: Record<string, ThemeBlockSchema> = {};
72
+ for (const rule of rules ?? []) {
73
+ if (isLocalBlockDefinition(rule)) {
74
+ defs[rule.type] = {name: rule.name, settings: rule.settings};
75
+ }
76
+ }
77
+ return defs;
78
+ }
79
+
80
+ function checkBlocks(
81
+ parentPath: string,
82
+ parentRules: SectionBlockEntry[] | undefined,
83
+ parentMaxBlocks: number | undefined,
84
+ blocks: Record<string, BlockData> | undefined,
85
+ blockOrder: string[] | undefined,
86
+ ctx: SchemaContext | null,
87
+ issues: ValidationIssue[],
88
+ ): void {
89
+ checkOrder(blockOrder, blocks, parentPath, issues);
90
+ if (!blocks) return;
91
+
92
+ if (ctx) {
93
+ const allowed = allowedChildTypes(parentRules, ctx.allBlockTypes);
94
+ const localDefs = localDefinitions(parentRules);
95
+ const count = blockOrder?.length ?? Object.keys(blocks).length;
96
+ const cap = parentRules?.length
97
+ ? (parentMaxBlocks ?? DEFAULT_MAX_BLOCKS)
98
+ : 0;
99
+ if (count > cap) {
100
+ issues.push({
101
+ path: parentPath,
102
+ code: 'max-blocks-exceeded',
103
+ message: `${count} blocks exceed the allowed maximum of ${cap}`,
104
+ });
105
+ }
106
+ for (const [key, block] of Object.entries(blocks)) {
107
+ const blockPath = `${parentPath}.blocks.${key}`;
108
+ const schema = ctx.manifest.blocks[block.type] ?? localDefs[block.type];
109
+ if (!schema) {
110
+ issues.push({
111
+ path: blockPath,
112
+ code: 'unknown-type',
113
+ message: `block type "${block.type}" is not in the manifest`,
114
+ });
115
+ } else if (!allowed.includes(block.type)) {
116
+ issues.push({
117
+ path: blockPath,
118
+ code: 'block-not-allowed',
119
+ message: `block type "${block.type}" is not allowed here`,
120
+ });
121
+ }
122
+ checkBlocks(
123
+ blockPath,
124
+ schema?.blocks,
125
+ undefined,
126
+ block.blocks,
127
+ block.block_order,
128
+ ctx,
129
+ issues,
130
+ );
131
+ }
132
+ } else {
133
+ for (const [key, block] of Object.entries(blocks)) {
134
+ checkBlocks(
135
+ `${parentPath}.blocks.${key}`,
136
+ undefined,
137
+ undefined,
138
+ block.blocks,
139
+ block.block_order,
140
+ null,
141
+ issues,
142
+ );
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Validate a template (or section group) against the L1 invariants,
149
+ * and — when a manifest is given — against the section/block schemas.
150
+ */
151
+ export function validateTemplate(
152
+ template: TemplateData | SectionGroupData,
153
+ manifest?: ThemeEditorManifest,
154
+ ): ValidationIssue[] {
155
+ const issues: ValidationIssue[] = [];
156
+ const ctx: SchemaContext | null = manifest
157
+ ? {manifest, allBlockTypes: Object.keys(manifest.blocks)}
158
+ : null;
159
+
160
+ checkOrder(template.order, template.sections, 'order', issues);
161
+ for (const [key, section] of Object.entries(template.sections ?? {})) {
162
+ const sectionPath = `sections.${key}`;
163
+ const schema = ctx?.manifest.sections[section.type];
164
+ if (ctx && !schema) {
165
+ issues.push({
166
+ path: sectionPath,
167
+ code: 'unknown-type',
168
+ message: `section type "${section.type}" is not in the manifest`,
169
+ });
170
+ }
171
+ checkBlocks(
172
+ sectionPath,
173
+ schema?.blocks,
174
+ schema?.max_blocks,
175
+ section.blocks,
176
+ section.block_order,
177
+ ctx,
178
+ issues,
179
+ );
180
+ }
181
+ return issues;
182
+ }
183
+
184
+ function checkSettings(
185
+ settings: SettingDefinition[] | undefined,
186
+ path: string,
187
+ issues: ValidationIssue[],
188
+ ): void {
189
+ if (!settings) return;
190
+ const ids = new Set<string>();
191
+ for (const [index, setting] of settings.entries()) {
192
+ if (!isInputSetting(setting)) continue;
193
+ if (!setting.id) {
194
+ issues.push({
195
+ path: `${path}[${index}]`,
196
+ code: 'setting-missing-id',
197
+ message: `input setting of type "${setting.type}" has no id`,
198
+ });
199
+ continue;
200
+ }
201
+ if (ids.has(setting.id)) {
202
+ issues.push({
203
+ path: `${path}[${index}]`,
204
+ code: 'setting-duplicate-id',
205
+ message: `duplicate setting id "${setting.id}"`,
206
+ });
207
+ }
208
+ ids.add(setting.id);
209
+ }
210
+ }
211
+
212
+ /** Validate a manifest's own consistency. */
213
+ export function validateManifest(
214
+ manifest: ThemeEditorManifest,
215
+ ): ValidationIssue[] {
216
+ const issues: ValidationIssue[] = [];
217
+ for (const field of [
218
+ 'contractVersion',
219
+ 'theme',
220
+ 'sections',
221
+ 'blocks',
222
+ 'settingsSchema',
223
+ 'templates',
224
+ 'sectionGroups',
225
+ 'locales',
226
+ 'hash',
227
+ ] as const) {
228
+ if (manifest[field] === undefined || manifest[field] === null) {
229
+ issues.push({
230
+ path: field,
231
+ code: 'manifest-missing-field',
232
+ message: `manifest is missing "${field}"`,
233
+ });
234
+ }
235
+ }
236
+
237
+ const allBlockTypes = Object.keys(manifest.blocks ?? {});
238
+ for (const [type, section] of Object.entries(manifest.sections ?? {})) {
239
+ checkSettings(section.settings, `sections.${type}.settings`, issues);
240
+ for (const [ruleIndex, rule] of (section.blocks ?? []).entries()) {
241
+ if (isLocalBlockDefinition(rule)) {
242
+ checkSettings(
243
+ rule.settings,
244
+ `sections.${type}.blocks[${ruleIndex}].settings`,
245
+ issues,
246
+ );
247
+ } else if (
248
+ rule.type !== '@theme' &&
249
+ rule.type !== '@app' &&
250
+ !allBlockTypes.includes(rule.type)
251
+ ) {
252
+ issues.push({
253
+ path: `sections.${type}.blocks`,
254
+ code: 'unknown-type',
255
+ message: `block rule references unknown block type "${rule.type}"`,
256
+ });
257
+ }
258
+ }
259
+ for (const [index, preset] of (section.presets ?? []).entries()) {
260
+ const allowed = allowedChildTypes(section.blocks, allBlockTypes);
261
+ const walk = (
262
+ blocks: Array<{type: string; blocks?: unknown}> | undefined,
263
+ rules: string[],
264
+ where: string,
265
+ ): void => {
266
+ for (const block of blocks ?? []) {
267
+ if (!rules.includes(block.type)) {
268
+ issues.push({
269
+ path: where,
270
+ code: 'block-not-allowed',
271
+ message: `preset uses block type "${block.type}" which the parent does not allow`,
272
+ });
273
+ }
274
+ const child = manifest.blocks[block.type];
275
+ walk(
276
+ block.blocks as Array<{type: string; blocks?: unknown}> | undefined,
277
+ allowedChildTypes(child?.blocks, allBlockTypes),
278
+ where,
279
+ );
280
+ }
281
+ };
282
+ walk(preset.blocks, allowed, `sections.${type}.presets[${index}]`);
283
+ }
284
+ }
285
+ for (const [type, block] of Object.entries(manifest.blocks ?? {})) {
286
+ checkSettings(block.settings, `blocks.${type}.settings`, issues);
287
+ for (const rule of block.blocks ?? []) {
288
+ if (
289
+ rule.type !== '@theme' &&
290
+ rule.type !== '@app' &&
291
+ !allBlockTypes.includes(rule.type)
292
+ ) {
293
+ issues.push({
294
+ path: `blocks.${type}.blocks`,
295
+ code: 'unknown-type',
296
+ message: `block rule references unknown block type "${rule.type}"`,
297
+ });
298
+ }
299
+ }
300
+ }
301
+ return issues;
302
+ }