@wocart/theme 3.0.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 +46 -0
- package/dist/blocks.d.ts +20 -2
- package/dist/blocks.js +41 -7
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +4 -2
- package/dist/definitions.js +28 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.js +2 -0
- package/dist/operations.d.ts +8 -10
- package/dist/operations.js +40 -14
- package/dist/page-data.js +24 -3
- package/dist/patterns.d.ts +1 -1
- package/dist/patterns.js +2 -0
- package/dist/preview-target.d.ts +3 -0
- package/dist/preview-target.js +9 -4
- package/dist/types.d.ts +13 -1
- 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,52 @@ 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
|
+
|
|
29
|
+
## 3.1.0 — 2026-10-31
|
|
30
|
+
|
|
31
|
+
Landing pages are composed pages, each with its own document, and a page can
|
|
32
|
+
leave out the header or the footer.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `landingSurface(id)`: a landing page's own document is stored as
|
|
37
|
+
`lp:<id>`. `templateSurface(surface)` names the surface it follows: the
|
|
38
|
+
theme's `landing` surface gives every landing page its rules, its sections
|
|
39
|
+
and its starting document. `resolveSurfaceDocument`, `checkPageData`, the
|
|
40
|
+
document operations, `isInsertableOn` and `previewTarget` read an `lp:`
|
|
41
|
+
document by it; `LANDING_TEMPLATE` is its key.
|
|
42
|
+
- `SurfaceSpec.hideableAreas`: the areas a page of that surface may leave
|
|
43
|
+
out. `PageData.hiddenAreas` lists the ones it does; read it with
|
|
44
|
+
`isAreaShown(theme, page, key)`, change it with `setAreaHidden`, and list
|
|
45
|
+
the choices with `hideableAreas(theme, surface)`. The manifest carries it.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- A theme's `landing` surface renders the `landing` page type, a landing
|
|
50
|
+
page type is composed only by the `landing` surface, and, like a product
|
|
51
|
+
surface, it must always render an order form. A surface key cannot start
|
|
52
|
+
with `lp:` or `area:`.
|
|
53
|
+
|
|
8
54
|
## 3.0.0 — 2026-10-30
|
|
9
55
|
|
|
10
56
|
A store's images can live on any media host: the image service takes the
|
package/dist/blocks.d.ts
CHANGED
|
@@ -19,12 +19,30 @@ export declare const THEME_LIMITS: {
|
|
|
19
19
|
};
|
|
20
20
|
export declare const COMPONENT_PLACEMENTS: readonly ["section", "block", "both"];
|
|
21
21
|
export declare const COMPONENT_ROLES: readonly ["order-form"];
|
|
22
|
+
/** The surface every landing page's own document follows: its rules, its shipped document. */
|
|
23
|
+
export declare const LANDING_TEMPLATE = "landing";
|
|
22
24
|
/** The surface an area's document is stored under: `area:<key>`. */
|
|
23
25
|
export declare function areaSurface(key: string): string;
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The surface a landing page's own document is stored under: `lp:<id>`, by
|
|
28
|
+
* the page's id, which stays when its slug changes. A landing page is a
|
|
29
|
+
* campaign of its own, so each has its own document; the theme's `landing`
|
|
30
|
+
* surface gives every one of them its rules and its starting document.
|
|
31
|
+
*/
|
|
32
|
+
export declare function landingSurface(id: string): string;
|
|
33
|
+
/** The surface whose rules and shipped document a document follows: `landing` for a landing page's own `lp:<id>`, the surface itself otherwise. */
|
|
34
|
+
export declare function templateSurface(surface: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* The shipped document for a surface or an area, when the theme declares one.
|
|
37
|
+
* A landing page's own surface gets the `landing` document, addressed to it.
|
|
38
|
+
*/
|
|
25
39
|
export declare function shippedDocument(theme: ThemeDefinition, surface: string): PageData | undefined;
|
|
26
|
-
/** Whether a theme composes a surface: one it declares, or one of its areas. */
|
|
40
|
+
/** Whether a theme composes a surface: one it declares, a landing page's own when it declares `landing`, or one of its areas. */
|
|
27
41
|
export declare function composesSurface(theme: ThemeDefinition, surface: string): boolean;
|
|
42
|
+
/** The areas a page of this surface may leave out: the surface's `hideableAreas` the theme declares as areas. */
|
|
43
|
+
export declare function hideableAreas(theme: ThemeDefinition, surface: string): string[];
|
|
44
|
+
/** Whether a page shows one of the theme's areas: it does unless its document leaves it out and its surface allows that. */
|
|
45
|
+
export declare function isAreaShown(theme: ThemeDefinition, page: PageData, key: string): boolean;
|
|
28
46
|
/** The id a fixed block renders with while the document does not store it. */
|
|
29
47
|
export declare function fixedBlockId(ownerId: string, name: string): string;
|
|
30
48
|
/** Whether a component may be placed at the top level (`"section"`) or in a slot (`"block"`). */
|
package/dist/blocks.js
CHANGED
|
@@ -12,19 +12,52 @@ export const THEME_LIMITS = {
|
|
|
12
12
|
export const COMPONENT_PLACEMENTS = ["section", "block", "both"];
|
|
13
13
|
export const COMPONENT_ROLES = ["order-form"];
|
|
14
14
|
const AREA_PREFIX = "area:";
|
|
15
|
+
const LANDING_PREFIX = "lp:";
|
|
16
|
+
/** The surface every landing page's own document follows: its rules, its shipped document. */
|
|
17
|
+
export const LANDING_TEMPLATE = "landing";
|
|
15
18
|
/** The surface an area's document is stored under: `area:<key>`. */
|
|
16
19
|
export function areaSurface(key) {
|
|
17
20
|
return `${AREA_PREFIX}${key}`;
|
|
18
21
|
}
|
|
19
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The surface a landing page's own document is stored under: `lp:<id>`, by
|
|
24
|
+
* the page's id, which stays when its slug changes. A landing page is a
|
|
25
|
+
* campaign of its own, so each has its own document; the theme's `landing`
|
|
26
|
+
* surface gives every one of them its rules and its starting document.
|
|
27
|
+
*/
|
|
28
|
+
export function landingSurface(id) {
|
|
29
|
+
return `${LANDING_PREFIX}${id}`;
|
|
30
|
+
}
|
|
31
|
+
/** The surface whose rules and shipped document a document follows: `landing` for a landing page's own `lp:<id>`, the surface itself otherwise. */
|
|
32
|
+
export function templateSurface(surface) {
|
|
33
|
+
return surface.startsWith(LANDING_PREFIX) ? LANDING_TEMPLATE : surface;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The shipped document for a surface or an area, when the theme declares one.
|
|
37
|
+
* A landing page's own surface gets the `landing` document, addressed to it.
|
|
38
|
+
*/
|
|
20
39
|
export function shippedDocument(theme, surface) {
|
|
21
40
|
if (surface.startsWith(AREA_PREFIX))
|
|
22
41
|
return (theme.areas ?? []).find((area) => areaSurface(area.key) === surface)?.document;
|
|
23
|
-
|
|
42
|
+
const template = templateSurface(surface);
|
|
43
|
+
const document = (theme.surfaces ?? []).find((spec) => spec.key === template)?.document;
|
|
44
|
+
return document === undefined || template === surface ? document : { ...document, surface };
|
|
24
45
|
}
|
|
25
|
-
/** Whether a theme composes a surface: one it declares, or one of its areas. */
|
|
46
|
+
/** Whether a theme composes a surface: one it declares, a landing page's own when it declares `landing`, or one of its areas. */
|
|
26
47
|
export function composesSurface(theme, surface) {
|
|
27
|
-
|
|
48
|
+
const template = templateSurface(surface);
|
|
49
|
+
return (theme.surfaces ?? []).some((spec) => spec.key === template) || (theme.areas ?? []).some((area) => areaSurface(area.key) === surface);
|
|
50
|
+
}
|
|
51
|
+
/** The areas a page of this surface may leave out: the surface's `hideableAreas` the theme declares as areas. */
|
|
52
|
+
export function hideableAreas(theme, surface) {
|
|
53
|
+
const template = templateSurface(surface);
|
|
54
|
+
const spec = (theme.surfaces ?? []).find((candidate) => candidate.key === template);
|
|
55
|
+
const declared = new Set((theme.areas ?? []).map((area) => area.key));
|
|
56
|
+
return (spec?.hideableAreas ?? []).filter((key) => declared.has(key));
|
|
57
|
+
}
|
|
58
|
+
/** Whether a page shows one of the theme's areas: it does unless its document leaves it out and its surface allows that. */
|
|
59
|
+
export function isAreaShown(theme, page, key) {
|
|
60
|
+
return !(page.hiddenAreas ?? []).includes(key) || !hideableAreas(theme, page.surface).includes(key);
|
|
28
61
|
}
|
|
29
62
|
/** The id a fixed block renders with while the document does not store it. */
|
|
30
63
|
export function fixedBlockId(ownerId, name) {
|
|
@@ -46,9 +79,10 @@ export function surfaceRules(theme, surface) {
|
|
|
46
79
|
return { ...(area?.required === undefined ? {} : { required: area.required }), ...(area?.max === undefined ? {} : { max: area.max }) };
|
|
47
80
|
}
|
|
48
81
|
const surfaces = theme.surfaces ?? [];
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
82
|
+
const key = templateSurface(surface);
|
|
83
|
+
const own = surfaces.find((spec) => spec.key === key);
|
|
84
|
+
const dot = key.indexOf(".");
|
|
85
|
+
const base = dot > 0 ? surfaces.find((spec) => spec.key === key.slice(0, dot)) : undefined;
|
|
52
86
|
const required = own?.required ?? base?.required;
|
|
53
87
|
const max = own?.max ?? base?.max;
|
|
54
88
|
return { ...(required === undefined ? {} : { required }), ...(max === undefined ? {} : { max }) };
|
package/dist/composition.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ComponentDefinition, ComponentInstance, ComponentSpec, ThemeDefinition } from "./types.js";
|
|
2
|
-
/** Whether a surface key is `entry` itself or one of its alternates (`product.compact` for `product`)
|
|
2
|
+
/** Whether a surface key is `entry` itself or one of its alternates (`product.compact` for `product`); a landing page's own `lp:<id>` is covered by `landing`. */
|
|
3
3
|
export declare function surfaceCovers(entry: string, surface: string): boolean;
|
|
4
4
|
/**
|
|
5
5
|
* Whether an editor may insert this component on `surface`: it is not marked
|
package/dist/composition.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
* where a component may go, and how a preset becomes instances. Pure: no
|
|
4
4
|
* I/O, no randomness — ids come from the caller.
|
|
5
5
|
*/
|
|
6
|
+
import { templateSurface } from "./blocks.js";
|
|
6
7
|
import { createInstance } from "./instances.js";
|
|
7
|
-
/** Whether a surface key is `entry` itself or one of its alternates (`product.compact` for `product`)
|
|
8
|
+
/** Whether a surface key is `entry` itself or one of its alternates (`product.compact` for `product`); a landing page's own `lp:<id>` is covered by `landing`. */
|
|
8
9
|
export function surfaceCovers(entry, surface) {
|
|
9
|
-
|
|
10
|
+
const key = templateSurface(surface);
|
|
11
|
+
return key === entry || key.startsWith(`${entry}.`);
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Whether an editor may insert this component on `surface`: it is not marked
|
package/dist/definitions.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* data — the validator exists so a theme's test suite fails loudly on catalog
|
|
5
5
|
* bugs when the catalog is assembled.
|
|
6
6
|
*/
|
|
7
|
-
import { COMPONENT_PLACEMENTS, COMPONENT_ROLES, THEME_LIMITS, areaSurface, guaranteedRole, innerGuarantees, placementAllows, surfaceRules, } from "./blocks.js";
|
|
7
|
+
import { COMPONENT_PLACEMENTS, COMPONENT_ROLES, THEME_LIMITS, areaSurface, LANDING_TEMPLATE, guaranteedRole, innerGuarantees, placementAllows, surfaceRules, } from "./blocks.js";
|
|
8
8
|
import { instantiatePreset, surfaceCovers } from "./composition.js";
|
|
9
9
|
import { conditionProblem } from "./conditions.js";
|
|
10
10
|
import { COLOR_SCHEME_ID_PATTERN, STYLE_PANELS, STYLE_STEPS, STYLE_WIDTHS, colorSchemesProblem, styleSizeProblem, styleValueProblem, } from "./style.js";
|
|
@@ -117,7 +117,8 @@ export function validateThemeDefinition(theme, options = {}) {
|
|
|
117
117
|
addIssue(issues, "surfaces", "invalid-theme", "`surfaces` must be an array of surface declarations");
|
|
118
118
|
}
|
|
119
119
|
else {
|
|
120
|
-
|
|
120
|
+
const areaKeys = new Set((Array.isArray(theme.areas) ? theme.areas : []).map((area) => (isRecord(area) ? area.key : undefined)).filter((key) => typeof key === "string"));
|
|
121
|
+
inspectSurfaces(theme.surfaces, issues, locales, new Set(Object.keys(theme.components)), areaKeys);
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
if (theme.areas !== undefined)
|
|
@@ -324,7 +325,7 @@ function inspectAreas(areas, issues, locales, catalogKeys) {
|
|
|
324
325
|
inspectBlockBounds(area, path, issues, catalogKeys, THEME_LIMITS.sections, "invalid-theme");
|
|
325
326
|
});
|
|
326
327
|
}
|
|
327
|
-
function inspectSurfaces(surfaces, issues, locales, catalogKeys) {
|
|
328
|
+
function inspectSurfaces(surfaces, issues, locales, catalogKeys, areaKeys) {
|
|
328
329
|
const seenKeys = new Set();
|
|
329
330
|
surfaces.forEach((surface, index) => {
|
|
330
331
|
const surfacePath = `surfaces[${index}]`;
|
|
@@ -349,6 +350,28 @@ function inspectSurfaces(surfaces, issues, locales, catalogKeys) {
|
|
|
349
350
|
if (surface.pageType !== undefined && !PAGE_TYPES.includes(surface.pageType)) {
|
|
350
351
|
addIssue(issues, `${surfacePath}.pageType`, "invalid-theme", `\`pageType\` must be one of ${PAGE_TYPES.join(", ")}`);
|
|
351
352
|
}
|
|
353
|
+
if (typeof surface.key === "string" && (surface.key.startsWith("lp:") || surface.key.startsWith("area:"))) {
|
|
354
|
+
addIssue(issues, `${surfacePath}.key`, "invalid-theme", `"${surface.key}" uses a prefix the platform keeps for a landing page's or an area's own document`);
|
|
355
|
+
}
|
|
356
|
+
if (surface.key === LANDING_TEMPLATE && surface.pageType !== "landing") {
|
|
357
|
+
addIssue(issues, `${surfacePath}.pageType`, "invalid-theme", `the "${LANDING_TEMPLATE}" surface renders landing pages: its \`pageType\` is "landing"`);
|
|
358
|
+
}
|
|
359
|
+
if (surface.pageType === "landing" && typeof surface.key === "string" && surface.key !== LANDING_TEMPLATE) {
|
|
360
|
+
addIssue(issues, `${surfacePath}.key`, "invalid-theme", `landing pages are composed by the "${LANDING_TEMPLATE}" surface; each page's own document follows it`);
|
|
361
|
+
}
|
|
362
|
+
if (surface.hideableAreas !== undefined) {
|
|
363
|
+
const listed = surface.hideableAreas;
|
|
364
|
+
if (!Array.isArray(listed) || new Set(listed).size !== listed.length) {
|
|
365
|
+
addIssue(issues, `${surfacePath}.hideableAreas`, "invalid-theme", "`hideableAreas` must be a list of distinct area keys");
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
listed.forEach((key, position) => {
|
|
369
|
+
if (typeof key !== "string" || !areaKeys.has(key)) {
|
|
370
|
+
addIssue(issues, `${surfacePath}.hideableAreas[${position}]`, "invalid-theme", `${JSON.stringify(key)} is not an area this theme declares`);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
352
375
|
if (surface.document !== undefined && !isRecord(surface.document)) {
|
|
353
376
|
addIssue(issues, `${surfacePath}.document`, "invalid-document", "`document` must be a page document");
|
|
354
377
|
}
|
|
@@ -603,12 +626,12 @@ function inspectBlockRules(theme, issues) {
|
|
|
603
626
|
if (issues.length > 0)
|
|
604
627
|
return;
|
|
605
628
|
(theme.surfaces ?? []).forEach((surface, index) => {
|
|
606
|
-
if (surface.pageType !== "product")
|
|
629
|
+
if (surface.pageType !== "product" && surface.pageType !== "landing")
|
|
607
630
|
return;
|
|
608
631
|
const rules = surfaceRules(theme, surface.key);
|
|
609
632
|
const guaranteed = (rules.required ?? []).reduce((count, entry) => count + (entry.min ?? 1) * guaranteedRole(theme, entry.type, "order-form"), 0);
|
|
610
633
|
if (guaranteed === 0) {
|
|
611
|
-
addIssue(issues, `surfaces[${index}]`, "order-form-not-guaranteed",
|
|
634
|
+
addIssue(issues, `surfaces[${index}]`, "order-form-not-guaranteed", `${surface.pageType} surface "${surface.key}" must always render an order form: require a section that places a component with role "order-form" through fixed or required blocks`);
|
|
612
635
|
}
|
|
613
636
|
});
|
|
614
637
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,12 +17,12 @@ export { resolveInstance, createInstance, checkStoredValue as checkFieldValue }
|
|
|
17
17
|
export type { CheckedValue as FieldValueCheck } from "./instances.js";
|
|
18
18
|
export { RICH_TEXT_FORMATS, richTextToHtml, richTextToPlainText } from "./rich-text.js";
|
|
19
19
|
export { instantiatePreset, isInsertableOn } from "./composition.js";
|
|
20
|
-
export { COMPONENT_PLACEMENTS, COMPONENT_ROLES, THEME_LIMITS, areaSurface, fixedBlockId } from "./blocks.js";
|
|
20
|
+
export { COMPONENT_PLACEMENTS, COMPONENT_ROLES, LANDING_TEMPLATE, THEME_LIMITS, areaSurface, fixedBlockId, hideableAreas, isAreaShown, landingSurface, templateSurface, } from "./blocks.js";
|
|
21
21
|
export { completePage, resolveSurfaceDocument } from "./surface-document.js";
|
|
22
22
|
export { CONDITION_DEPTH, isFieldVisible } from "./conditions.js";
|
|
23
23
|
export { COLOR_ROLES, COLOR_SCHEMES_MAX, STYLE_BREAKPOINTS, STYLE_PANELS, STYLE_STEPS, STYLE_WIDTHS, THEME_STYLES_PATH, SCHEME_MARKER_CLASS, colorSchemeCss, contrastRatio, schemeClass, styleClasses, themeStyleSheet, } from "./style.js";
|
|
24
24
|
export type { SurfaceDocument, SurfaceDocumentReason } from "./surface-document.js";
|
|
25
|
-
export { checkPageData, duplicateInstance, findInstance, insertInstance, moveInstance, newInstanceId, removeInstance, setInstanceHidden, setInstanceProps, } from "./operations.js";
|
|
25
|
+
export { checkPageData, duplicateInstance, findInstance, insertInstance, moveInstance, newInstanceId, removeInstance, setAreaHidden, setInstanceHidden, setInstanceProps, } from "./operations.js";
|
|
26
26
|
export type { DocumentEdit, DocumentIssue, DocumentIssueCode, InstanceLocation, InstancePlacement, } from "./operations.js";
|
|
27
27
|
export { THEME_SDK_VERSION } from "./version.js";
|
|
28
28
|
export type { RichTextHtmlOptions } from "./rich-text.js";
|
package/dist/index.js
CHANGED
|
@@ -16,11 +16,11 @@ export { parsePageData, validatePageData } from "./page-data.js";
|
|
|
16
16
|
export { resolveInstance, createInstance, checkStoredValue as checkFieldValue } from "./instances.js";
|
|
17
17
|
export { RICH_TEXT_FORMATS, richTextToHtml, richTextToPlainText } from "./rich-text.js";
|
|
18
18
|
export { instantiatePreset, isInsertableOn } from "./composition.js";
|
|
19
|
-
export { COMPONENT_PLACEMENTS, COMPONENT_ROLES, THEME_LIMITS, areaSurface, fixedBlockId } from "./blocks.js";
|
|
19
|
+
export { COMPONENT_PLACEMENTS, COMPONENT_ROLES, LANDING_TEMPLATE, THEME_LIMITS, areaSurface, fixedBlockId, hideableAreas, isAreaShown, landingSurface, templateSurface, } from "./blocks.js";
|
|
20
20
|
export { completePage, resolveSurfaceDocument } from "./surface-document.js";
|
|
21
21
|
export { CONDITION_DEPTH, isFieldVisible } from "./conditions.js";
|
|
22
22
|
export { COLOR_ROLES, COLOR_SCHEMES_MAX, STYLE_BREAKPOINTS, STYLE_PANELS, STYLE_STEPS, STYLE_WIDTHS, THEME_STYLES_PATH, SCHEME_MARKER_CLASS, colorSchemeCss, contrastRatio, schemeClass, styleClasses, themeStyleSheet, } from "./style.js";
|
|
23
|
-
export { checkPageData, duplicateInstance, findInstance, insertInstance, moveInstance, newInstanceId, removeInstance, setInstanceHidden, setInstanceProps, } from "./operations.js";
|
|
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
26
|
export { localize } from "./locales.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export declare const ThemeManifestSchema: z.ZodObject<{
|
|
|
41
41
|
max: z.ZodExactOptional<z.ZodNumber>;
|
|
42
42
|
}, z.core.$strip>>>;
|
|
43
43
|
max: z.ZodExactOptional<z.ZodNumber>;
|
|
44
|
+
hideableAreas: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
|
|
44
45
|
}, z.core.$strip>>>;
|
|
45
46
|
areas: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
46
47
|
key: z.ZodString;
|
package/dist/manifest.js
CHANGED
|
@@ -37,6 +37,7 @@ export function themeManifest(theme) {
|
|
|
37
37
|
...(surface.document === undefined ? {} : { document: copyData(surface.document) }),
|
|
38
38
|
...(surface.required === undefined ? {} : { required: copyData(surface.required) }),
|
|
39
39
|
...(surface.max === undefined ? {} : { max: surface.max }),
|
|
40
|
+
...(surface.hideableAreas === undefined ? {} : { hideableAreas: [...surface.hideableAreas] }),
|
|
40
41
|
})),
|
|
41
42
|
areas: (theme.areas ?? []).map((area) => copyData(area)),
|
|
42
43
|
locales: [...(theme.locales ?? [])],
|
|
@@ -232,6 +233,7 @@ export const ThemeManifestSchema = z.object({
|
|
|
232
233
|
document: z.exactOptional(z.custom((value) => isRecord(value))),
|
|
233
234
|
required: z.exactOptional(z.array(RequiredBlockSchema)),
|
|
234
235
|
max: z.exactOptional(z.number()),
|
|
236
|
+
hideableAreas: z.exactOptional(z.array(z.string())),
|
|
235
237
|
}))
|
|
236
238
|
.default([]),
|
|
237
239
|
areas: z
|
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,14 @@ 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
|
-
/**
|
|
89
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Show or leave out one of the theme's areas on this page — the header, the
|
|
85
|
+
* footer — when its surface lets a page leave it out (`hideableAreas`).
|
|
86
|
+
*/
|
|
87
|
+
export declare function setAreaHidden(theme: ThemeDefinition, page: PageData, key: string, hidden: boolean): DocumentEdit;
|
|
90
88
|
/**
|
|
91
89
|
* Everything a writer must fix before saving a document for this theme —
|
|
92
90
|
* the strict counterpart of `validatePageData`: an envelope for one of the
|
package/dist/operations.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* nesting stay within their caps. Operations on a document assume it was
|
|
15
15
|
* completed (`completePage`) when it was opened.
|
|
16
16
|
*/
|
|
17
|
-
import { THEME_LIMITS, composesSurface, placementAllows, slotCap, surfaceRules } from "./blocks.js";
|
|
17
|
+
import { THEME_LIMITS, composesSurface, hideableAreas, placementAllows, slotCap, surfaceRules } from "./blocks.js";
|
|
18
18
|
import { surfaceCovers } from "./composition.js";
|
|
19
19
|
import { checkStoredValue, hasFallback, ownValue } from "./instances.js";
|
|
20
20
|
import { ComponentInstanceSchema } from "./schemas.js";
|
|
@@ -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)
|
|
@@ -518,6 +519,24 @@ export function setInstanceHidden(...args) {
|
|
|
518
519
|
delete location.instance.hidden;
|
|
519
520
|
return { ok: true, page: next };
|
|
520
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Show or leave out one of the theme's areas on this page — the header, the
|
|
524
|
+
* footer — when its surface lets a page leave it out (`hideableAreas`).
|
|
525
|
+
*/
|
|
526
|
+
export function setAreaHidden(theme, page, key, hidden) {
|
|
527
|
+
if (!hideableAreas(theme, page.surface).includes(key)) {
|
|
528
|
+
return refuse("not-allowed", `a page of "${page.surface}" cannot leave out the area "${key}"`);
|
|
529
|
+
}
|
|
530
|
+
const next = structuredClone(page);
|
|
531
|
+
const rest = (next.hiddenAreas ?? []).filter((candidate) => candidate !== key);
|
|
532
|
+
const order = (theme.areas ?? []).map((area) => area.key);
|
|
533
|
+
const hiddenAreas = hidden ? [...rest, key].sort((a, b) => order.indexOf(a) - order.indexOf(b)) : rest;
|
|
534
|
+
if (hiddenAreas.length > 0)
|
|
535
|
+
next.hiddenAreas = hiddenAreas;
|
|
536
|
+
else
|
|
537
|
+
delete next.hiddenAreas;
|
|
538
|
+
return { ok: true, page: next };
|
|
539
|
+
}
|
|
521
540
|
/**
|
|
522
541
|
* Everything a writer must fix before saving a document for this theme —
|
|
523
542
|
* the strict counterpart of `validatePageData`: an envelope for one of the
|
|
@@ -538,6 +557,13 @@ export function checkPageData(theme, input) {
|
|
|
538
557
|
if (input.theme !== undefined && !(isRecord(input.theme) && input.theme.key === theme.key && typeof input.theme.version === "string" && input.theme.version.length > 0)) {
|
|
539
558
|
issues.push({ path: "theme", code: "invalid-document", message: `\`theme\` must name this theme: { key: "${theme.key}", version }` });
|
|
540
559
|
}
|
|
560
|
+
if (input.hiddenAreas !== undefined) {
|
|
561
|
+
const hideable = hideableAreas(theme, surface);
|
|
562
|
+
const hidden = input.hiddenAreas;
|
|
563
|
+
if (!Array.isArray(hidden) || new Set(hidden).size !== hidden.length || !hidden.every((key) => typeof key === "string" && hideable.includes(key))) {
|
|
564
|
+
issues.push({ path: "hiddenAreas", code: "invalid-document", message: `\`hiddenAreas\` lists distinct areas this page may leave out: ${hideable.length > 0 ? hideable.join(", ") : "none"}` });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
541
567
|
const rules = surfaceRules(theme, surface);
|
|
542
568
|
const cap = slotCap(rules, THEME_LIMITS.sections);
|
|
543
569
|
if (input.sections.length > cap) {
|
package/dist/page-data.js
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* failed; a structurally invalid section is dropped with an issue; the
|
|
8
8
|
* document itself is not rewritten by `validatePageData`.
|
|
9
9
|
*/
|
|
10
|
-
import { THEME_LIMITS, completeRequired, slotCap, surfaceRules } from "./blocks.js";
|
|
10
|
+
import { THEME_LIMITS, completeRequired, hideableAreas, slotCap, surfaceRules } from "./blocks.js";
|
|
11
11
|
import { resolveInstance } from "./instances.js";
|
|
12
|
-
import { ComponentInstanceSchema, SURFACE_KEY_PATTERN, THEME_KEY_PATTERN } from "./schemas.js";
|
|
12
|
+
import { ComponentInstanceSchema, SURFACE_KEY_PATTERN, THEME_KEY_PATTERN, TYPE_KEY_PATTERN } from "./schemas.js";
|
|
13
13
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
/** At most this many areas a document leaves out: a theme declares a handful. */
|
|
15
|
+
const MAX_HIDDEN_AREAS = 8;
|
|
14
16
|
const envelopeFailure = (message) => ({
|
|
15
17
|
page: null,
|
|
16
18
|
issues: [{ path: "", code: "envelope-invalid", message }],
|
|
@@ -53,12 +55,25 @@ export function parsePageData(input) {
|
|
|
53
55
|
// name a prototype.
|
|
54
56
|
const page = { version: 1, surface, sections };
|
|
55
57
|
for (const key of Object.keys(input)) {
|
|
56
|
-
if (key === "version" || key === "surface" || key === "sections" || key === "theme")
|
|
58
|
+
if (key === "version" || key === "surface" || key === "sections" || key === "theme" || key === "hiddenAreas")
|
|
57
59
|
continue;
|
|
58
60
|
if (key === "__proto__")
|
|
59
61
|
continue;
|
|
60
62
|
page[key] = input[key];
|
|
61
63
|
}
|
|
64
|
+
if (input.hiddenAreas !== undefined) {
|
|
65
|
+
const hidden = input.hiddenAreas;
|
|
66
|
+
if (Array.isArray(hidden) && hidden.length <= MAX_HIDDEN_AREAS && hidden.every((key) => typeof key === "string" && TYPE_KEY_PATTERN.test(key)) && new Set(hidden).size === hidden.length) {
|
|
67
|
+
page.hiddenAreas = [...hidden];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
issues.push({
|
|
71
|
+
path: "hiddenAreas",
|
|
72
|
+
code: "invalid-prop",
|
|
73
|
+
message: `\`hiddenAreas\` must list at most ${MAX_HIDDEN_AREAS} distinct area keys; ignored, every area shows`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
62
77
|
if (input.theme !== undefined) {
|
|
63
78
|
const theme = input.theme;
|
|
64
79
|
if (isRecord(theme) &&
|
|
@@ -124,6 +139,12 @@ export function validatePageData(theme, input, scope = {}) {
|
|
|
124
139
|
walk(block, `${path}.fixed.${name}`);
|
|
125
140
|
}
|
|
126
141
|
};
|
|
142
|
+
const hideable = hideableAreas(theme, parsed.page.surface);
|
|
143
|
+
(parsed.page.hiddenAreas ?? []).forEach((key, index) => {
|
|
144
|
+
if (!hideable.includes(key)) {
|
|
145
|
+
issues.push({ path: `hiddenAreas[${index}]`, code: "invalid-prop", message: `"${key}" is not an area this page may leave out; it shows` });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
127
148
|
const spec = surfaceRules(theme, parsed.page.surface);
|
|
128
149
|
const cap = slotCap(spec, THEME_LIMITS.sections);
|
|
129
150
|
if (parsed.page.sections.length > cap) {
|
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
package/dist/preview-target.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ export interface PreviewTarget {
|
|
|
29
29
|
* when the theme composes that page, else on the home page.
|
|
30
30
|
* - The resource is chosen by the surface's `pageType`, never its key, so an
|
|
31
31
|
* alternate (`product.compact`) previews like its base.
|
|
32
|
+
* - A landing page's own document (`lp:<id>`) previews with the theme's
|
|
33
|
+
* `landing` surface on the page its `path` names (`/lp/<slug>`), and the
|
|
34
|
+
* target keeps its own surface, so the sections render that page's document.
|
|
32
35
|
*
|
|
33
36
|
* ```astro
|
|
34
37
|
* ---
|
package/dist/preview-target.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* document it renders.
|
|
5
5
|
*/
|
|
6
6
|
import { DEFAULT_COLLECTION_HANDLES, matchStorefrontPath } from "@wocart/storefront";
|
|
7
|
-
import { areaSurface } from "./blocks.js";
|
|
7
|
+
import { areaSurface, templateSurface } from "./blocks.js";
|
|
8
8
|
/** The surface a page type is shown with: the one keyed by the type itself, else the first declaring it. */
|
|
9
9
|
function surfaceFor(surfaces, pageType) {
|
|
10
10
|
return surfaces.find((surface) => surface.key === pageType) ?? surfaces.find((surface) => surface.pageType === pageType);
|
|
@@ -45,6 +45,9 @@ async function resourceParams(pageType, named, client) {
|
|
|
45
45
|
* when the theme composes that page, else on the home page.
|
|
46
46
|
* - The resource is chosen by the surface's `pageType`, never its key, so an
|
|
47
47
|
* alternate (`product.compact`) previews like its base.
|
|
48
|
+
* - A landing page's own document (`lp:<id>`) previews with the theme's
|
|
49
|
+
* `landing` surface on the page its `path` names (`/lp/<slug>`), and the
|
|
50
|
+
* target keeps its own surface, so the sections render that page's document.
|
|
48
51
|
*
|
|
49
52
|
* ```astro
|
|
50
53
|
* ---
|
|
@@ -58,14 +61,16 @@ export async function previewTarget(theme, draft, client) {
|
|
|
58
61
|
const surfaces = theme.surfaces ?? [];
|
|
59
62
|
const match = draft.path ? matchStorefrontPath(draft.path) : null;
|
|
60
63
|
const area = (theme.areas ?? []).some((candidate) => areaSurface(candidate.key) === draft.surface) ? draft.surface : null;
|
|
61
|
-
const
|
|
64
|
+
const template = templateSurface(draft.surface);
|
|
65
|
+
const shown = area === null ? surfaces.find((surface) => surface.key === template) : (match ? surfaceFor(surfaces, match.type) : undefined) ?? surfaceFor(surfaces, "home");
|
|
62
66
|
if (shown === undefined)
|
|
63
67
|
return null;
|
|
68
|
+
const surface = area === null ? draft.surface : shown.key;
|
|
64
69
|
const pageType = shown.pageType;
|
|
65
70
|
const named = match !== null && match.type === pageType ? { ...match.params } : {};
|
|
66
71
|
const [params, document] = await Promise.all([
|
|
67
72
|
resourceParams(pageType, named, client),
|
|
68
|
-
area === null && draft.document !== undefined ? draft.document : client.pageData.get(
|
|
73
|
+
area === null && draft.document !== undefined ? draft.document : client.pageData.get(surface),
|
|
69
74
|
]);
|
|
70
|
-
return { surface
|
|
75
|
+
return { surface, pageType, area, params, document };
|
|
71
76
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -728,6 +728,13 @@ export interface SurfaceSpec {
|
|
|
728
728
|
required?: readonly RequiredBlock[];
|
|
729
729
|
/** At most this many top-level sections (1–50). Absent: `THEME_LIMITS.sections`. */
|
|
730
730
|
max?: number;
|
|
731
|
+
/**
|
|
732
|
+
* The theme's areas a page of this surface may leave out (`["header",
|
|
733
|
+
* "footer"]`): its document lists the ones it hides in `hiddenAreas`. A
|
|
734
|
+
* landing page that sends an ad's visitor straight to its order form is
|
|
735
|
+
* the case. Absent: every page shows every area.
|
|
736
|
+
*/
|
|
737
|
+
hideableAreas?: readonly string[];
|
|
731
738
|
}
|
|
732
739
|
/**
|
|
733
740
|
* A part every page shares — a header, a footer — composed of sections like a
|
|
@@ -749,7 +756,7 @@ export interface AreaSpec {
|
|
|
749
756
|
* The storefront's page types — what a URL means, with the paths the platform
|
|
750
757
|
* owns. The same names as `StorefrontPageType` in `@wocart/storefront`.
|
|
751
758
|
*/
|
|
752
|
-
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";
|
|
753
760
|
/**
|
|
754
761
|
* One component as data: a `ComponentDefinition` minus its runtime-only
|
|
755
762
|
* implementation. `component` (the live component reference) and `load` (the
|
|
@@ -868,6 +875,11 @@ export interface PageData {
|
|
|
868
875
|
version: 1;
|
|
869
876
|
surface: string;
|
|
870
877
|
sections: ComponentInstance[];
|
|
878
|
+
/**
|
|
879
|
+
* The keys of the theme's areas this page leaves out. Only the areas its
|
|
880
|
+
* surface lists in `hideableAreas` are left out; read it with `isAreaShown`.
|
|
881
|
+
*/
|
|
882
|
+
hiddenAreas?: string[];
|
|
871
883
|
/**
|
|
872
884
|
* The theme and theme version that wrote the document. A document that
|
|
873
885
|
* names another theme is reported (`theme-mismatch`): its sections were
|
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.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": "^
|
|
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",
|