@wocart/theme 3.1.0 → 4.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.
- package/CHANGELOG.md +52 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/operations.d.ts +3 -10
- package/dist/operations.js +14 -13
- package/dist/patterns.d.ts +1 -1
- package/dist/patterns.js +2 -0
- package/dist/theme-content.d.ts +19 -0
- package/dist/theme-content.js +19 -0
- package/dist/types.d.ts +3 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,58 @@ 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.1.0 — 2026-11-03
|
|
9
|
+
|
|
10
|
+
One content policy for every theme, and the theme key rule exported.
|
|
11
|
+
Additions only; works with `@wocart/storefront` 3.0.0 or newer 3.x.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `resolveThemeContent(theme, locale, overrides, options?)`: the theme's
|
|
16
|
+
words for one page — its pack for the language (else the default
|
|
17
|
+
language's: `options.defaultLocale`, else the first `locales` entry, else
|
|
18
|
+
the first pack), then the merchant's edits for that language, text only and
|
|
19
|
+
only for the pack's own keys. Takes `Pick<ThemeDefinition, "content" |
|
|
20
|
+
"locales">`, so a middleware can pass the packs without the component
|
|
21
|
+
catalog. Type `ResolveThemeContentOptions`.
|
|
22
|
+
- `THEME_KEY_PATTERN` from the root entry: the rule a theme's key follows
|
|
23
|
+
(lowercase kebab-case, at most 64 characters), the one `defineTheme` and
|
|
24
|
+
the platform apply, for a tool that checks a key before building a theme.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `ComponentRole`'s comment names both page types whose surfaces must
|
|
29
|
+
guarantee an `"order-form"` component: product and landing, as
|
|
30
|
+
`validateThemeDefinition` already checks.
|
|
31
|
+
|
|
32
|
+
### Theme migration
|
|
33
|
+
|
|
34
|
+
- Replace a theme's own merge of its packs and `contentOverrides` with
|
|
35
|
+
`resolveThemeContent({ content, locales }, locale, config.contentOverrides?.[locale])`.
|
|
36
|
+
An edit that is not text, or names a key the pack lacks, no longer reaches
|
|
37
|
+
a page.
|
|
38
|
+
|
|
39
|
+
## 4.0.0 — 2026-11-01
|
|
40
|
+
|
|
41
|
+
Search and contact are platform pages a theme composes, and the theme
|
|
42
|
+
operations take the theme they check. Requires `@wocart/storefront` 3.0.0.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `SurfacePageType` and `PAGE_TYPES` take `search` (`/search`) and `contact`
|
|
47
|
+
(`/contact`), the storefront's new page types: a theme declares a surface
|
|
48
|
+
for each, so a merchant composes them like any page.
|
|
49
|
+
|
|
50
|
+
### Removed
|
|
51
|
+
|
|
52
|
+
- `removeInstance(page, id)` and `setInstanceHidden(page, id, hidden)`, the
|
|
53
|
+
forms without the theme: pass the theme first, so a required block is
|
|
54
|
+
always kept and shown.
|
|
55
|
+
|
|
56
|
+
### Changed (breaking)
|
|
57
|
+
|
|
58
|
+
- The peer `@wocart/storefront` is `^3.0.0`.
|
|
59
|
+
|
|
8
60
|
## 3.1.0 — 2026-10-31
|
|
9
61
|
|
|
10
62
|
Landing pages are composed pages, each with its own document, and a page can
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,10 @@ export type { DocumentEdit, DocumentIssue, DocumentIssueCode, InstanceLocation,
|
|
|
27
27
|
export { THEME_SDK_VERSION } from "./version.js";
|
|
28
28
|
export type { RichTextHtmlOptions } from "./rich-text.js";
|
|
29
29
|
export { resolveThemeSettings, checkThemeSettings } from "./settings.js";
|
|
30
|
+
export { resolveThemeContent } from "./theme-content.js";
|
|
31
|
+
export type { ResolveThemeContentOptions } from "./theme-content.js";
|
|
30
32
|
export { localize } from "./locales.js";
|
|
33
|
+
export { THEME_KEY_PATTERN } from "./patterns.js";
|
|
31
34
|
export { PLURAL_CATEGORIES, formatMessage, messageForms, pluralFormIssues, pluralMessageKeys } from "./messages.js";
|
|
32
35
|
export type { MessageOptions, MessageValues, PluralCategory } from "./messages.js";
|
|
33
36
|
export { THEME_MANIFEST_CONTRACT_VERSION, THEME_MANIFEST_PATH, manifestCatalog, themeManifest, validateThemeManifest, } from "./manifest.js";
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,9 @@ export { COLOR_ROLES, COLOR_SCHEMES_MAX, STYLE_BREAKPOINTS, STYLE_PANELS, STYLE_
|
|
|
23
23
|
export { checkPageData, duplicateInstance, findInstance, insertInstance, moveInstance, newInstanceId, removeInstance, setAreaHidden, setInstanceHidden, setInstanceProps, } from "./operations.js";
|
|
24
24
|
export { THEME_SDK_VERSION } from "./version.js";
|
|
25
25
|
export { resolveThemeSettings, checkThemeSettings } from "./settings.js";
|
|
26
|
+
export { resolveThemeContent } from "./theme-content.js";
|
|
26
27
|
export { localize } from "./locales.js";
|
|
28
|
+
export { THEME_KEY_PATTERN } from "./patterns.js";
|
|
27
29
|
export { PLURAL_CATEGORIES, formatMessage, messageForms, pluralFormIssues, pluralMessageKeys } from "./messages.js";
|
|
28
30
|
export { THEME_MANIFEST_CONTRACT_VERSION, THEME_MANIFEST_PATH, manifestCatalog, themeManifest, validateThemeManifest, } from "./manifest.js";
|
|
29
31
|
export { diffThemeManifests } from "./manifest-diff.js";
|
package/dist/operations.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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`).
|
package/dist/operations.js
CHANGED
|
@@ -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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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)
|
package/dist/patterns.d.ts
CHANGED
|
@@ -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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The theme's words for one page: its own pack for the page's language, with
|
|
3
|
+
* the merchant's edits for that same language over it. One policy for every
|
|
4
|
+
* theme: an edit counts only when it is text and names a word the pack has,
|
|
5
|
+
* so a stale key, another theme's key or a value of the wrong kind never
|
|
6
|
+
* reaches a page, and another language's edits never mix in.
|
|
7
|
+
*/
|
|
8
|
+
import type { LocaleCode, ThemeDefinition } from "./types.js";
|
|
9
|
+
export interface ResolveThemeContentOptions {
|
|
10
|
+
/** The pack used for a language the theme has none for. Default: the theme's first `locales` entry, else its first pack. */
|
|
11
|
+
defaultLocale?: LocaleCode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The words a page renders in `locale`: the theme's pack for it (else the
|
|
15
|
+
* default language's), then `overrides` — the store's
|
|
16
|
+
* `contentOverrides[locale]` — for the pack's own keys, text only. Empty
|
|
17
|
+
* when the theme declares no content.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveThemeContent(theme: Pick<ThemeDefinition, "content" | "locales">, locale: LocaleCode, overrides: unknown, options?: ResolveThemeContentOptions): Record<string, string>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words a page renders in `locale`: the theme's pack for it (else the
|
|
3
|
+
* default language's), then `overrides` — the store's
|
|
4
|
+
* `contentOverrides[locale]` — for the pack's own keys, text only. Empty
|
|
5
|
+
* when the theme declares no content.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveThemeContent(theme, locale, overrides, options = {}) {
|
|
8
|
+
const packs = theme.content ?? {};
|
|
9
|
+
const fallback = options.defaultLocale ?? theme.locales?.[0] ?? Object.keys(packs)[0];
|
|
10
|
+
const pack = (Object.hasOwn(packs, locale) ? packs[locale] : fallback !== undefined && Object.hasOwn(packs, fallback) ? packs[fallback] : undefined) ?? {};
|
|
11
|
+
const words = { ...pack };
|
|
12
|
+
if (overrides === null || typeof overrides !== "object")
|
|
13
|
+
return words;
|
|
14
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
15
|
+
if (typeof value === "string" && Object.hasOwn(pack, key))
|
|
16
|
+
words[key] = value;
|
|
17
|
+
}
|
|
18
|
+
return words;
|
|
19
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -460,7 +460,8 @@ export interface FixedBlockDefinition {
|
|
|
460
460
|
}
|
|
461
461
|
/**
|
|
462
462
|
* What a component is to the platform. `"order-form"`: it places orders —
|
|
463
|
-
* every product surface must guarantee one
|
|
463
|
+
* every product and landing surface must guarantee one
|
|
464
|
+
* (`validateThemeDefinition`, `order-form-not-guaranteed`).
|
|
464
465
|
*/
|
|
465
466
|
export type ComponentRole = "order-form";
|
|
466
467
|
/**
|
|
@@ -756,7 +757,7 @@ export interface AreaSpec {
|
|
|
756
757
|
* The storefront's page types — what a URL means, with the paths the platform
|
|
757
758
|
* owns. The same names as `StorefrontPageType` in `@wocart/storefront`.
|
|
758
759
|
*/
|
|
759
|
-
export type SurfacePageType = "home" | "catalog" | "category" | "collection" | "product" | "page" | "landing" | "checkout" | "thankYou" | "trackOrder";
|
|
760
|
+
export type SurfacePageType = "home" | "catalog" | "category" | "collection" | "product" | "page" | "landing" | "checkout" | "thankYou" | "trackOrder" | "search" | "contact";
|
|
760
761
|
/**
|
|
761
762
|
* One component as data: a `ComponentDefinition` minus its runtime-only
|
|
762
763
|
* implementation. `component` (the live component reference) and `load` (the
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wocart/theme",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.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": "^
|
|
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": "^
|
|
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",
|