@wocart/theme 1.6.0 → 2.0.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.
package/dist/manifest.js CHANGED
@@ -35,9 +35,13 @@ export function themeManifest(theme) {
35
35
  label: copyData(surface.label),
36
36
  ...(surface.pageType === undefined ? {} : { pageType: surface.pageType }),
37
37
  ...(surface.document === undefined ? {} : { document: copyData(surface.document) }),
38
+ ...(surface.required === undefined ? {} : { required: copyData(surface.required) }),
39
+ ...(surface.max === undefined ? {} : { max: surface.max }),
38
40
  })),
41
+ areas: (theme.areas ?? []).map((area) => copyData(area)),
39
42
  locales: [...(theme.locales ?? [])],
40
43
  settings: (theme.settings ?? []).map((group) => copyData(group)),
44
+ ...(theme.style === undefined ? {} : { style: copyData(theme.style) }),
41
45
  content: copyData(theme.content ?? {}),
42
46
  preview: theme.preview === true,
43
47
  components: Object.fromEntries(Object.entries(theme.components).map(([key, definition]) => [
@@ -57,6 +61,9 @@ function componentSpec(definition) {
57
61
  ? {}
58
62
  : { capabilities: copyData(definition.capabilities) }),
59
63
  presets: (definition.presets ?? []).map((preset) => copyData(preset)),
64
+ ...(definition.placement === undefined ? {} : { placement: definition.placement }),
65
+ ...(definition.fixed === undefined ? {} : { fixed: copyData(definition.fixed) }),
66
+ ...(definition.role === undefined ? {} : { role: definition.role }),
60
67
  };
61
68
  }
62
69
  /**
@@ -75,6 +82,8 @@ const FieldCommonSchema = {
75
82
  label: LocalizedTextSchema,
76
83
  description: z.exactOptional(LocalizedTextSchema),
77
84
  required: z.exactOptional(z.boolean()),
85
+ // A condition's own rules are relational; `validateThemeDefinition` checks them.
86
+ visibleIf: z.exactOptional(z.custom((value) => isRecord(value))),
78
87
  };
79
88
  const TextFieldSchema = z.object({
80
89
  ...FieldCommonSchema,
@@ -91,6 +100,7 @@ const ScalarFieldSchemas = [
91
100
  min: z.exactOptional(z.number()),
92
101
  max: z.exactOptional(z.number()),
93
102
  step: z.exactOptional(z.number()),
103
+ display: z.exactOptional(z.enum(["input", "slider"])),
94
104
  }),
95
105
  z.object({
96
106
  ...FieldCommonSchema,
@@ -103,6 +113,7 @@ const ScalarFieldSchemas = [
103
113
  options: z.array(z.string()),
104
114
  optionLabels: z.exactOptional(z.record(z.string(), LocalizedTextSchema)),
105
115
  default: z.exactOptional(z.string()),
116
+ display: z.exactOptional(z.enum(["dropdown", "radio", "segmented"])),
106
117
  }),
107
118
  ...["url", "color", "menu", "collection", "product", "category"].map((kind) => z.object({
108
119
  ...FieldCommonSchema,
@@ -119,6 +130,26 @@ const ScalarFieldSchemas = [
119
130
  formats: z.exactOptional(z.array(z.custom((value) => typeof value === "string"))),
120
131
  // The document's own rules are relational; `validateThemeDefinition` checks them.
121
132
  default: z.exactOptional(z.custom((value) => typeof value === "object" && value !== null)),
133
+ inline: z.exactOptional(z.boolean()),
134
+ }),
135
+ ...["productList", "collectionList"].map((kind) => z.object({
136
+ ...FieldCommonSchema,
137
+ kind: z.literal(kind),
138
+ min: z.exactOptional(z.number()),
139
+ max: z.exactOptional(z.number()),
140
+ default: z.exactOptional(z.array(z.string())),
141
+ })),
142
+ z.object({
143
+ ...FieldCommonSchema,
144
+ kind: z.literal("style"),
145
+ panels: z.array(z.custom((value) => typeof value === "string")),
146
+ // Checked against the panels and the theme's sizes by `validateThemeDefinition`.
147
+ default: z.exactOptional(z.custom((value) => isRecord(value))),
148
+ }),
149
+ z.object({
150
+ ...FieldCommonSchema,
151
+ kind: z.literal("colorScheme"),
152
+ default: z.exactOptional(z.string()),
122
153
  }),
123
154
  ];
124
155
  const ItemFieldSchema = z.discriminatedUnion("kind", [...ScalarFieldSchemas]);
@@ -133,6 +164,11 @@ const FieldSchema = z.discriminatedUnion("kind", [
133
164
  default: z.exactOptional(z.array(z.object({ id: z.string(), props: z.exactOptional(z.record(z.string(), JsonValueSchema)) }))),
134
165
  itemLabel: z.exactOptional(z.string()),
135
166
  }),
167
+ z.object({
168
+ ...FieldCommonSchema,
169
+ kind: z.literal("colorSchemes"),
170
+ default: z.array(z.custom((value) => isRecord(value))),
171
+ }),
136
172
  ]);
137
173
  const PresetInstanceSchema = z.lazy(() => z.object({
138
174
  type: z.string(),
@@ -140,6 +176,11 @@ const PresetInstanceSchema = z.lazy(() => z.object({
140
176
  children: z.exactOptional(z.array(PresetInstanceSchema)),
141
177
  slots: z.exactOptional(z.record(z.string(), z.array(PresetInstanceSchema))),
142
178
  }));
179
+ const RequiredBlockSchema = z.object({
180
+ type: z.string(),
181
+ min: z.exactOptional(z.number()),
182
+ max: z.exactOptional(z.number()),
183
+ });
143
184
  const ComponentSpecSchema = z.object({
144
185
  type: z.string(),
145
186
  label: LocalizedTextSchema,
@@ -150,8 +191,13 @@ const ComponentSpecSchema = z.object({
150
191
  name: z.string(),
151
192
  multiple: z.exactOptional(z.boolean()),
152
193
  allow: z.exactOptional(z.array(z.string())),
194
+ max: z.exactOptional(z.number()),
195
+ required: z.exactOptional(z.array(RequiredBlockSchema)),
153
196
  }))
154
197
  .default([]),
198
+ placement: z.exactOptional(z.custom((value) => typeof value === "string")),
199
+ fixed: z.exactOptional(z.array(z.object({ name: z.string(), type: z.string(), label: z.exactOptional(LocalizedTextSchema) }))),
200
+ role: z.exactOptional(z.custom((value) => typeof value === "string")),
155
201
  capabilities: z.exactOptional(z.object({
156
202
  insertable: z.exactOptional(z.boolean()),
157
203
  surfaces: z.exactOptional(z.array(z.string())),
@@ -184,12 +230,27 @@ export const ThemeManifestSchema = z.object({
184
230
  pageType: z.exactOptional(z.custom((value) => typeof value === "string")),
185
231
  // A page document's own rules are checked by `validateThemeDefinition` on the probe.
186
232
  document: z.exactOptional(z.custom((value) => isRecord(value))),
233
+ required: z.exactOptional(z.array(RequiredBlockSchema)),
234
+ max: z.exactOptional(z.number()),
235
+ }))
236
+ .default([]),
237
+ areas: z
238
+ .array(z.object({
239
+ key: z.string(),
240
+ label: LocalizedTextSchema,
241
+ document: z.exactOptional(z.custom((value) => isRecord(value))),
242
+ required: z.exactOptional(z.array(RequiredBlockSchema)),
243
+ max: z.exactOptional(z.number()),
187
244
  }))
188
245
  .default([]),
189
246
  locales: z.array(z.string()).default([]),
190
247
  settings: z
191
248
  .array(z.object({ key: z.string(), label: LocalizedTextSchema, fields: z.array(FieldSchema) }))
192
249
  .default([]),
250
+ style: z.exactOptional(z.object({
251
+ spacing: z.exactOptional(z.record(z.string(), z.number())),
252
+ widths: z.exactOptional(z.record(z.string(), z.number())),
253
+ })),
193
254
  content: z.record(z.string(), z.record(z.string(), z.string())).default({}),
194
255
  preview: z.boolean().default(false),
195
256
  components: z.record(z.string(), ComponentSpecSchema),
@@ -247,7 +308,7 @@ function codeAt(path) {
247
308
  return "invalid-identity";
248
309
  if (path === "version")
249
310
  return "invalid-version";
250
- if (path === "surfaces" || path.startsWith("surfaces["))
311
+ if (path === "surfaces" || path.startsWith("surfaces[") || path === "areas" || path.startsWith("areas["))
251
312
  return "invalid-surface";
252
313
  if (path === "settings" || path.startsWith("settings["))
253
314
  return "invalid-settings";
@@ -278,8 +339,10 @@ export function manifestCatalog(manifest) {
278
339
  key: manifest.key,
279
340
  version: manifest.version,
280
341
  surfaces: manifest.surfaces,
342
+ areas: manifest.areas,
281
343
  locales: manifest.locales,
282
344
  settings: manifest.settings,
345
+ ...(manifest.style === undefined ? {} : { style: manifest.style }),
283
346
  content: manifest.content,
284
347
  preview: manifest.preview,
285
348
  components: Object.fromEntries(Object.entries(manifest.components).map(([key, spec]) => [
@@ -1,5 +1,5 @@
1
1
  import type { ComponentInstance, JsonValue, PageData, ThemeDefinition } from "./types.js";
2
- export type DocumentIssueCode = "invalid-document" | "invalid-instance" | "invalid-index" | "invalid-props" | "unknown-instance" | "unknown-type" | "unknown-slot" | "duplicate-id" | "not-insertable" | "not-available" | "not-allowed" | "slot-full" | "cycle";
2
+ export type DocumentIssueCode = "invalid-document" | "invalid-instance" | "invalid-index" | "invalid-props" | "unknown-instance" | "unknown-type" | "unknown-slot" | "duplicate-id" | "not-insertable" | "not-available" | "not-allowed" | "not-placeable" | "slot-full" | "fixed-block" | "required-block" | "limit-exceeded" | "cycle";
3
3
  /** Why a document or a change is refused; `path` points into the document. */
4
4
  export interface DocumentIssue {
5
5
  path: string;
@@ -20,12 +20,19 @@ export interface InstancePlacement {
20
20
  slot?: string;
21
21
  index?: number;
22
22
  }
23
- /** Where an instance is: its parent (`null` at the top level), its slot and its index there. */
23
+ /**
24
+ * Where an instance is: its parent (`null` at the top level) and its slot and
25
+ * index there — or, for a fixed block, its owner and `fixed`, the name the
26
+ * owner's definition gives it (`slot` is then `null`).
27
+ */
24
28
  export interface InstanceLocation {
25
29
  instance: ComponentInstance;
26
30
  parent: string | null;
27
31
  slot: string | null;
28
32
  index: number;
33
+ fixed?: string;
34
+ /** Levels below the top-level sections (a section is at 0). */
35
+ depth: number;
29
36
  }
30
37
  /**
31
38
  * A new instance id: `cmp_` and 16 random hex digits. Operations refuse an id
@@ -36,36 +43,55 @@ export declare function newInstanceId(): string;
36
43
  export declare function findInstance(page: PageData, id: string): InstanceLocation | null;
37
44
  /**
38
45
  * Insert a new instance — from `createInstance` or `instantiatePreset` — at a
39
- * placement. Refused when the component is not insertable or not available on
40
- * the document's surface, when the slot does not take it, or when any id is
41
- * already in the document.
46
+ * placement, completed with its fixed and required blocks. Refused when the
47
+ * component is not insertable, not available on the document's surface, not
48
+ * placeable there, when the list is full or already holds the most of its
49
+ * type, when the document would pass its caps, or when any id is already in
50
+ * the document.
42
51
  */
43
52
  export declare function insertInstance(theme: ThemeDefinition, page: PageData, instance: ComponentInstance, placement?: InstancePlacement): DocumentEdit;
44
- /** Remove an instance and everything inside it. */
53
+ /**
54
+ * Remove an instance and everything inside it. Refused for a fixed block and,
55
+ * given the theme, for the last required instance of its type in its list.
56
+ * The form without a theme is kept for writers that predate blocks: it still
57
+ * refuses a fixed block, but cannot know a slot's required types.
58
+ */
59
+ export declare function removeInstance(theme: ThemeDefinition, page: PageData, id: string): DocumentEdit;
60
+ /** @deprecated Pass the theme first, so required blocks are kept. */
45
61
  export declare function removeInstance(page: PageData, id: string): DocumentEdit;
46
62
  /**
47
63
  * Move an instance (with everything inside it) to a placement. `index` counts
48
64
  * positions in the destination after the instance has left its old place.
65
+ * A fixed block never moves; a required one moves only within its own list,
66
+ * unless enough of its type stay behind.
49
67
  */
50
68
  export declare function moveInstance(theme: ThemeDefinition, page: PageData, id: string, placement?: InstancePlacement): DocumentEdit;
51
69
  /**
52
70
  * Copy an instance and everything inside it right after the original, every
53
71
  * copied instance with an id from `newId`. List items keep their ids: they
54
- * are unique within their list, not the document.
72
+ * are unique within their list, not the document. A fixed block is never
73
+ * copied.
55
74
  */
56
75
  export declare function duplicateInstance(theme: ThemeDefinition, page: PageData, id: string, newId: () => string): DocumentEdit;
57
76
  /**
58
- * Change an instance's props: each value is checked by its field;
59
- * `undefined` removes the stored value, so the field falls back to its
60
- * content-pack text or default.
77
+ * Change an instance's props — a fixed block's too: each value is checked by
78
+ * its field; `undefined` removes the stored value, so the field falls back to
79
+ * its content-pack text or default.
61
80
  */
62
81
  export declare function setInstanceProps(theme: ThemeDefinition, page: PageData, id: string, changes: Readonly<Record<string, JsonValue | undefined>>): DocumentEdit;
63
- /** Switch an instance off (kept, not rendered) or back on. */
82
+ /**
83
+ * Switch an instance off (kept, not rendered) or back on. A fixed block is
84
+ * never hidden and, given the theme, neither is the last required instance of
85
+ * its type. The form without a theme refuses only a fixed block.
86
+ */
87
+ export declare function setInstanceHidden(theme: ThemeDefinition, page: PageData, id: string, hidden: boolean): DocumentEdit;
88
+ /** @deprecated Pass the theme first, so required blocks stay shown. */
64
89
  export declare function setInstanceHidden(page: PageData, id: string, hidden: boolean): DocumentEdit;
65
90
  /**
66
91
  * Everything a writer must fix before saving a document for this theme —
67
92
  * the strict counterpart of `validatePageData`: an envelope for one of the
68
- * theme's surfaces when it declares any, and every section a tree the
69
- * operations would have built. An empty list means the document may be saved.
93
+ * theme's surfaces when it declares any, the surface's required sections
94
+ * shown and its cap kept, and every section a tree the operations would have
95
+ * built. An empty list means the document may be saved.
70
96
  */
71
97
  export declare function checkPageData(theme: ThemeDefinition, input: unknown): DocumentIssue[];