@wocart/theme 3.1.0 → 4.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/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to `@wocart/theme` are recorded here. The package follows
5
5
  major version, and an export is marked `@deprecated` for at least one minor
6
6
  release before a major removes it.
7
7
 
8
+ ## 4.0.0 — 2026-11-01
9
+
10
+ Search and contact are platform pages a theme composes, and the theme
11
+ operations take the theme they check. Requires `@wocart/storefront` 3.0.0.
12
+
13
+ ### Added
14
+
15
+ - `SurfacePageType` and `PAGE_TYPES` take `search` (`/search`) and `contact`
16
+ (`/contact`), the storefront's new page types: a theme declares a surface
17
+ for each, so a merchant composes them like any page.
18
+
19
+ ### Removed
20
+
21
+ - `removeInstance(page, id)` and `setInstanceHidden(page, id, hidden)`, the
22
+ forms without the theme: pass the theme first, so a required block is
23
+ always kept and shown.
24
+
25
+ ### Changed (breaking)
26
+
27
+ - The peer `@wocart/storefront` is `^3.0.0`.
28
+
8
29
  ## 3.1.0 — 2026-10-31
9
30
 
10
31
  Landing pages are composed pages, each with its own document, and a page can
@@ -51,14 +51,10 @@ export declare function findInstance(page: PageData, id: string): InstanceLocati
51
51
  */
52
52
  export declare function insertInstance(theme: ThemeDefinition, page: PageData, instance: ComponentInstance, placement?: InstancePlacement): DocumentEdit;
53
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.
54
+ * Remove an instance and everything inside it. Refused for a fixed block and
55
+ * for the last required instance of its type in its list.
58
56
  */
59
57
  export declare function removeInstance(theme: ThemeDefinition, page: PageData, id: string): DocumentEdit;
60
- /** @deprecated Pass the theme first, so required blocks are kept. */
61
- export declare function removeInstance(page: PageData, id: string): DocumentEdit;
62
58
  /**
63
59
  * Move an instance (with everything inside it) to a placement. `index` counts
64
60
  * positions in the destination after the instance has left its old place.
@@ -81,12 +77,9 @@ export declare function duplicateInstance(theme: ThemeDefinition, page: PageData
81
77
  export declare function setInstanceProps(theme: ThemeDefinition, page: PageData, id: string, changes: Readonly<Record<string, JsonValue | undefined>>): DocumentEdit;
82
78
  /**
83
79
  * 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.
80
+ * never hidden, and neither is the last required instance of its type.
86
81
  */
87
82
  export declare function setInstanceHidden(theme: ThemeDefinition, page: PageData, id: string, hidden: boolean): DocumentEdit;
88
- /** @deprecated Pass the theme first, so required blocks stay shown. */
89
- export declare function setInstanceHidden(page: PageData, id: string, hidden: boolean): DocumentEdit;
90
83
  /**
91
84
  * Show or leave out one of the theme's areas on this page — the header, the
92
85
  * footer — when its surface lets a page leave it out (`hideableAreas`).
@@ -29,7 +29,6 @@ export function newInstanceId() {
29
29
  return `cmp_${Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
30
30
  }
31
31
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
32
- const isTheme = (value) => isRecord(value) && isRecord(value.components);
33
32
  const definitionOf = (theme, type) => Object.hasOwn(theme.components, type) ? theme.components[type] : undefined;
34
33
  const refuse = (code, message, path = "") => ({
35
34
  ok: false,
@@ -347,8 +346,11 @@ export function insertInstance(theme, page, instance, placement = {}) {
347
346
  list.splice(placement.index ?? list.length, 0, tree);
348
347
  return { ok: true, page: next };
349
348
  }
350
- export function removeInstance(...args) {
351
- const [theme, page, id] = isTheme(args[0]) ? args : [null, args[0], args[1]];
349
+ /**
350
+ * Remove an instance and everything inside it. Refused for a fixed block and
351
+ * for the last required instance of its type in its list.
352
+ */
353
+ export function removeInstance(theme, page, id) {
352
354
  const next = structuredClone(page);
353
355
  const location = findInstance(next, id);
354
356
  if (location === null)
@@ -360,11 +362,9 @@ export function removeInstance(...args) {
360
362
  : targetList(next, { parent: location.parent, slot: location.slot ?? "default" });
361
363
  if (list === null)
362
364
  return refuse("unknown-instance", `no instance "${location.parent}" in the document`);
363
- if (theme !== null) {
364
- const short = leavesRequiredShort(listRules(theme, next, location.parent, location.slot), list, location.instance);
365
- if (short !== null)
366
- return refuse("required-block", `"${location.instance.type}" is required here (at least ${short.min ?? 1})`);
367
- }
365
+ const short = leavesRequiredShort(listRules(theme, next, location.parent, location.slot), list, location.instance);
366
+ if (short !== null)
367
+ return refuse("required-block", `"${location.instance.type}" is required here (at least ${short.min ?? 1})`);
368
368
  list.splice(list.findIndex((candidate) => candidate.id === id), 1);
369
369
  return { ok: true, page: next };
370
370
  }
@@ -496,17 +496,18 @@ export function setInstanceProps(theme, page, id, changes) {
496
496
  delete instance.props;
497
497
  return { ok: true, page: next };
498
498
  }
499
- export function setInstanceHidden(...args) {
500
- const [theme, page, id, hidden] = isTheme(args[0])
501
- ? args
502
- : [null, args[0], args[1], args[2]];
499
+ /**
500
+ * Switch an instance off (kept, not rendered) or back on. A fixed block is
501
+ * never hidden, and neither is the last required instance of its type.
502
+ */
503
+ export function setInstanceHidden(theme, page, id, hidden) {
503
504
  const next = structuredClone(page);
504
505
  const location = findInstance(next, id);
505
506
  if (location === null)
506
507
  return refuse("unknown-instance", `no instance "${id}" in the document`);
507
508
  if (hidden && location.fixed !== undefined)
508
509
  return refuse("fixed-block", `"${id}" is placed by its component's code and cannot be hidden`);
509
- if (hidden && theme !== null) {
510
+ if (hidden) {
510
511
  const list = location.parent === null ? next.sections : (slotLists(findInstance(next, location.parent).instance).find(([name]) => name === (location.slot ?? "default"))?.[1] ?? []);
511
512
  const short = leavesRequiredShort(listRules(theme, next, location.parent, location.slot), list, location.instance);
512
513
  if (short !== null)
@@ -17,7 +17,7 @@ export declare const TYPE_KEY_PATTERN: RegExp;
17
17
  */
18
18
  export declare const SURFACE_KEY_PATTERN: RegExp;
19
19
  /** The platform page types a surface can render (`SurfacePageType`), mirrored from the storefront's page types. */
20
- export declare const PAGE_TYPES: readonly ["home", "catalog", "category", "collection", "product", "page", "landing", "checkout", "thankYou", "trackOrder"];
20
+ export declare const PAGE_TYPES: readonly ["home", "catalog", "category", "collection", "product", "page", "landing", "checkout", "thankYou", "trackOrder", "search", "contact"];
21
21
  /** A `color` field's value: a hex color, `#rgb` or `#rrggbb`. */
22
22
  export declare const COLOR_PATTERN: RegExp;
23
23
  /**
package/dist/patterns.js CHANGED
@@ -28,6 +28,8 @@ export const PAGE_TYPES = [
28
28
  "checkout",
29
29
  "thankYou",
30
30
  "trackOrder",
31
+ "search",
32
+ "contact",
31
33
  ];
32
34
  /** A `color` field's value: a hex color, `#rgb` or `#rrggbb`. */
33
35
  export const COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
package/dist/types.d.ts CHANGED
@@ -756,7 +756,7 @@ export interface AreaSpec {
756
756
  * The storefront's page types — what a URL means, with the paths the platform
757
757
  * owns. The same names as `StorefrontPageType` in `@wocart/storefront`.
758
758
  */
759
- export type SurfacePageType = "home" | "catalog" | "category" | "collection" | "product" | "page" | "landing" | "checkout" | "thankYou" | "trackOrder";
759
+ export type SurfacePageType = "home" | "catalog" | "category" | "collection" | "product" | "page" | "landing" | "checkout" | "thankYou" | "trackOrder" | "search" | "contact";
760
760
  /**
761
761
  * One component as data: a `ComponentDefinition` minus its runtime-only
762
762
  * implementation. `component` (the live component reference) and `load` (the
package/dist/version.d.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * reads. Always equal to `package.json`'s version; the package check refuses
5
5
  * a release where they differ.
6
6
  */
7
- export declare const THEME_SDK_VERSION = "3.1.0";
7
+ export declare const THEME_SDK_VERSION = "4.0.0";
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  * reads. Always equal to `package.json`'s version; the package check refuses
5
5
  * a release where they differ.
6
6
  */
7
- export const THEME_SDK_VERSION = "3.1.0";
7
+ export const THEME_SDK_VERSION = "4.0.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wocart/theme",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "The Wocart theme contract: code-owned component definitions, JSON page documents, validation and resolution, and a reference Astro renderer.",
5
5
  "keywords": [
6
6
  "wocart",
@@ -82,7 +82,7 @@
82
82
  "zod": "^4.4.3"
83
83
  },
84
84
  "peerDependencies": {
85
- "@wocart/storefront": "^2.0.0",
85
+ "@wocart/storefront": "^3.0.0",
86
86
  "astro": "^7.0.0"
87
87
  },
88
88
  "peerDependenciesMeta": {
@@ -92,7 +92,7 @@
92
92
  },
93
93
  "devDependencies": {
94
94
  "@types/node": "^26.2.0",
95
- "@wocart/storefront": "^2.0.0",
95
+ "@wocart/storefront": "^3.0.0",
96
96
  "astro": "^7.3.2",
97
97
  "fast-check": "^4.6.0",
98
98
  "happy-dom": "^20.11.6",