@shoppexio/builder-runtime 0.1.1 → 0.1.3
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/YouTubeEmbed.d.ts +13 -0
- package/dist/YouTubeEmbed.d.ts.map +1 -0
- package/dist/YouTubeEmbed.js +49 -0
- package/dist/YouTubeEmbedBuilderBlock.d.ts +7 -0
- package/dist/YouTubeEmbedBuilderBlock.d.ts.map +1 -0
- package/dist/YouTubeEmbedBuilderBlock.js +16 -0
- package/dist/block-style-settings.d.ts +5 -0
- package/dist/block-style-settings.d.ts.map +1 -0
- package/dist/block-style-settings.js +16 -0
- package/dist/builder-runtime.test.d.ts +2 -0
- package/dist/builder-runtime.test.d.ts.map +1 -0
- package/dist/builder-runtime.test.js +115 -0
- package/dist/content.d.ts +6 -0
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +31 -7
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/layout.d.ts +3 -8
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +2 -10
- package/dist/manifest-setting-paths.d.ts +5 -0
- package/dist/manifest-setting-paths.d.ts.map +1 -0
- package/dist/manifest-setting-paths.js +40 -0
- package/dist/merchant-custom-page.d.ts +57 -0
- package/dist/merchant-custom-page.d.ts.map +1 -0
- package/dist/merchant-custom-page.js +63 -0
- package/dist/preview-fixtures.d.ts +16 -0
- package/dist/preview-fixtures.d.ts.map +1 -0
- package/dist/preview-fixtures.js +40 -0
- package/dist/preview-mode.d.ts +2 -0
- package/dist/preview-mode.d.ts.map +1 -0
- package/dist/preview-mode.js +7 -0
- package/dist/product-page.d.ts +13 -0
- package/dist/product-page.d.ts.map +1 -0
- package/dist/product-page.js +18 -0
- package/dist/react-runtime.test.d.ts +2 -0
- package/dist/react-runtime.test.d.ts.map +1 -0
- package/dist/react-runtime.test.js +332 -0
- package/dist/react.d.ts +37 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +138 -46
- package/dist/search-bar-settings.d.ts +33 -0
- package/dist/search-bar-settings.d.ts.map +1 -0
- package/dist/search-bar-settings.js +99 -0
- package/dist/standard-product-blocks.d.ts +48 -0
- package/dist/standard-product-blocks.d.ts.map +1 -0
- package/dist/standard-product-blocks.js +45 -0
- package/dist/standard-product-page.d.ts +69 -0
- package/dist/standard-product-page.d.ts.map +1 -0
- package/dist/standard-product-page.js +89 -0
- package/dist/storefront-google-fonts.d.ts +2 -0
- package/dist/storefront-google-fonts.d.ts.map +1 -0
- package/dist/storefront-google-fonts.js +28 -0
- package/dist/youtube-embed-block.d.ts +10 -0
- package/dist/youtube-embed-block.d.ts.map +1 -0
- package/dist/youtube-embed-block.js +19 -0
- package/dist/youtube.d.ts +5 -0
- package/dist/youtube.d.ts.map +1 -0
- package/dist/youtube.js +52 -0
- package/package.json +3 -3
- package/src/YouTubeEmbed.tsx +105 -0
- package/src/YouTubeEmbedBuilderBlock.tsx +49 -0
- package/src/block-style-settings.ts +24 -0
- package/src/builder-runtime.test.ts +69 -0
- package/src/content.ts +44 -9
- package/src/index.ts +8 -0
- package/src/layout.ts +11 -21
- package/src/manifest-setting-paths.test.ts +23 -0
- package/src/manifest-setting-paths.ts +55 -0
- package/src/merchant-custom-page.tsx +161 -0
- package/src/preview-fixtures.ts +56 -0
- package/src/preview-mode.ts +8 -0
- package/src/product-page.test.ts +37 -0
- package/src/product-page.ts +32 -0
- package/src/react-runtime.test.tsx +42 -0
- package/src/react.tsx +243 -49
- package/src/search-bar-settings.test.ts +72 -0
- package/src/search-bar-settings.ts +176 -0
- package/src/standard-product-blocks.test.tsx +93 -0
- package/src/standard-product-blocks.tsx +121 -0
- package/src/standard-product-page.test.ts +171 -0
- package/src/standard-product-page.ts +169 -0
- package/src/storefront-google-fonts.test.ts +31 -0
- package/src/storefront-google-fonts.ts +43 -0
- package/src/youtube-embed-block.test.ts +76 -0
- package/src/youtube-embed-block.ts +28 -0
- package/src/youtube-embed-builder-block.test.tsx +166 -0
- package/src/youtube.test.ts +48 -0
- package/src/youtube.ts +66 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
resolveManifestSettingRecordValue,
|
|
4
|
+
toSnakeCaseManifestPath,
|
|
5
|
+
} from './manifest-setting-paths.js';
|
|
6
|
+
|
|
7
|
+
describe('manifest-setting-paths', () => {
|
|
8
|
+
it('converts dotted manifest paths to snake_case aliases', () => {
|
|
9
|
+
expect(toSnakeCaseManifestPath('howItWorks.beforeImage')).toBe('how_it_works.before_image');
|
|
10
|
+
expect(toSnakeCaseManifestPath('theme.accentColor')).toBe('theme.accent_color');
|
|
11
|
+
expect(toSnakeCaseManifestPath('hero.image')).toBe('hero.image');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('reads canonical and snake_cased block setting keys', () => {
|
|
15
|
+
const record = {
|
|
16
|
+
'howItWorks.beforeImage': '/media/before.png',
|
|
17
|
+
'how_it_works.after_image': '/media/after.png',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
expect(resolveManifestSettingRecordValue(record, 'howItWorks.beforeImage')).toBe('/media/before.png');
|
|
21
|
+
expect(resolveManifestSettingRecordValue(record, 'howItWorks.afterImage')).toBe('/media/after.png');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { JsonRecord } from './content.js';
|
|
2
|
+
|
|
3
|
+
export function toSnakeCaseManifestSegment(value: string): string {
|
|
4
|
+
return value
|
|
5
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
6
|
+
.replace(/[\s-]+/g, '_')
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function toSnakeCaseManifestPath(path: string): string {
|
|
11
|
+
return path
|
|
12
|
+
.split('.')
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map(toSnakeCaseManifestSegment)
|
|
15
|
+
.join('.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getByDottedPath(record: JsonRecord, path: string): unknown {
|
|
19
|
+
let current: unknown = record;
|
|
20
|
+
|
|
21
|
+
for (const segment of path.split('.')) {
|
|
22
|
+
if (!current || typeof current !== 'object') {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
current = (current as JsonRecord)[segment];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return current;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveManifestSettingRecordValue(
|
|
33
|
+
record: JsonRecord,
|
|
34
|
+
path: string,
|
|
35
|
+
): unknown {
|
|
36
|
+
if (Object.prototype.hasOwnProperty.call(record, path)) {
|
|
37
|
+
return record[path];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const snakePath = toSnakeCaseManifestPath(path);
|
|
41
|
+
if (snakePath !== path && Object.prototype.hasOwnProperty.call(record, snakePath)) {
|
|
42
|
+
return record[snakePath];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const nested = getByDottedPath(record, path);
|
|
46
|
+
if (nested !== undefined) {
|
|
47
|
+
return nested;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (snakePath !== path) {
|
|
51
|
+
return getByDottedPath(record, snakePath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { BlockInstance } from '@shoppex/builder-contracts';
|
|
4
|
+
import { findCustomPageBySlug } from '@shoppex/builder-contracts';
|
|
5
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { builderBlock } from './attributes.js';
|
|
8
|
+
import { readManifestStyleBlockProps } from './block-style-settings.js';
|
|
9
|
+
import { BuilderPage, useBuilderRuntime, useThemePageBlocks, type BuilderBlockRegistry } from './react.js';
|
|
10
|
+
import { YouTubeEmbedBuilderBlock } from './YouTubeEmbedBuilderBlock.js';
|
|
11
|
+
|
|
12
|
+
type CustomEmbedProps = {
|
|
13
|
+
embedHtml?: string;
|
|
14
|
+
height?: number;
|
|
15
|
+
autoResize?: boolean;
|
|
16
|
+
width?: 'full' | 'boxed' | 'embed';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type TextBlockProps = {
|
|
20
|
+
eyebrow?: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
body?: string;
|
|
23
|
+
alignment?: 'left' | 'center' | 'right';
|
|
24
|
+
ctaLabel?: string;
|
|
25
|
+
ctaHref?: string;
|
|
26
|
+
styleBackground?: string;
|
|
27
|
+
styleAccentColor?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type MerchantCustomPageRegistryOptions = {
|
|
31
|
+
pageId: string;
|
|
32
|
+
CustomEmbed: ComponentType<CustomEmbedProps>;
|
|
33
|
+
TextBlock?: ComponentType<TextBlockProps>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function readStringSetting(block: BlockInstance, key: string): string | undefined {
|
|
37
|
+
const value = block.settings[key];
|
|
38
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readNumberSetting(block: BlockInstance, key: string): number | undefined {
|
|
42
|
+
const value = block.settings[key];
|
|
43
|
+
return typeof value === 'number' && value > 0 ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createMerchantCustomPageRegistry(
|
|
47
|
+
options: MerchantCustomPageRegistryOptions,
|
|
48
|
+
): BuilderBlockRegistry {
|
|
49
|
+
const { pageId, CustomEmbed, TextBlock } = options;
|
|
50
|
+
|
|
51
|
+
const registry: BuilderBlockRegistry = {
|
|
52
|
+
'youtube-embed': ({ block }) => (
|
|
53
|
+
<YouTubeEmbedBuilderBlock block={block} pageId={pageId} />
|
|
54
|
+
),
|
|
55
|
+
'custom-html': ({ block }) => {
|
|
56
|
+
const styleProps = readManifestStyleBlockProps(block.settings);
|
|
57
|
+
const width = block.settings.width;
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
key={block.id}
|
|
61
|
+
data-page-id={pageId}
|
|
62
|
+
{...builderBlock(block.id, block.type)}
|
|
63
|
+
style={Object.keys(styleProps).length > 0 ? styleProps : undefined}
|
|
64
|
+
>
|
|
65
|
+
<CustomEmbed
|
|
66
|
+
embedHtml={readStringSetting(block, 'embedHtml')}
|
|
67
|
+
height={readNumberSetting(block, 'height')}
|
|
68
|
+
autoResize={block.settings.autoResize === true}
|
|
69
|
+
width={
|
|
70
|
+
width === 'full' || width === 'boxed' || width === 'embed'
|
|
71
|
+
? width
|
|
72
|
+
: 'embed'
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (TextBlock) {
|
|
81
|
+
registry['text-block'] = ({ block }) => (
|
|
82
|
+
<div key={block.id} data-page-id={pageId} {...builderBlock(block.id, block.type)}>
|
|
83
|
+
<TextBlock
|
|
84
|
+
eyebrow={readStringSetting(block, 'eyebrow')}
|
|
85
|
+
title={readStringSetting(block, 'title')}
|
|
86
|
+
body={readStringSetting(block, 'body')}
|
|
87
|
+
alignment={
|
|
88
|
+
(readStringSetting(block, 'alignment') as TextBlockProps['alignment']) ??
|
|
89
|
+
undefined
|
|
90
|
+
}
|
|
91
|
+
ctaLabel={readStringSetting(block, 'ctaLabel')}
|
|
92
|
+
ctaHref={readStringSetting(block, 'ctaHref')}
|
|
93
|
+
styleBackground={readStringSetting(block, 'style.background')}
|
|
94
|
+
styleAccentColor={readStringSetting(block, 'style.accentColor')}
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return registry;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function MerchantCustomPageBuilderView({
|
|
104
|
+
pageId,
|
|
105
|
+
registry,
|
|
106
|
+
className,
|
|
107
|
+
children,
|
|
108
|
+
}: {
|
|
109
|
+
pageId: string;
|
|
110
|
+
registry: BuilderBlockRegistry;
|
|
111
|
+
className?: string;
|
|
112
|
+
children?: ReactNode;
|
|
113
|
+
}) {
|
|
114
|
+
const blocks = useThemePageBlocks(pageId, []);
|
|
115
|
+
|
|
116
|
+
if (blocks.length === 0) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className={className}>
|
|
122
|
+
{children}
|
|
123
|
+
<BuilderPage pageId={pageId} blocks={blocks} registry={registry} context={null} />
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function useMerchantCustomPageRegistry(
|
|
129
|
+
options: MerchantCustomPageRegistryOptions | null,
|
|
130
|
+
): BuilderBlockRegistry | null {
|
|
131
|
+
return useMemo(
|
|
132
|
+
() => (options ? createMerchantCustomPageRegistry(options) : null),
|
|
133
|
+
[options?.pageId, options?.CustomEmbed, options?.TextBlock],
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function useMerchantCustomPageView(
|
|
138
|
+
slug: string | undefined,
|
|
139
|
+
components: Pick<MerchantCustomPageRegistryOptions, 'CustomEmbed' | 'TextBlock'>,
|
|
140
|
+
) {
|
|
141
|
+
const { settings } = useBuilderRuntime();
|
|
142
|
+
const customPage = slug ? findCustomPageBySlug(settings, slug) : undefined;
|
|
143
|
+
const registry = useMerchantCustomPageRegistry(
|
|
144
|
+
customPage
|
|
145
|
+
? {
|
|
146
|
+
pageId: customPage.id,
|
|
147
|
+
CustomEmbed: components.CustomEmbed,
|
|
148
|
+
TextBlock: components.TextBlock,
|
|
149
|
+
}
|
|
150
|
+
: null,
|
|
151
|
+
);
|
|
152
|
+
const builderBlockCount = customPage
|
|
153
|
+
? (settings.theme.layout[customPage.id]?.blocks.length ?? 0)
|
|
154
|
+
: 0;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
customPage,
|
|
158
|
+
registry,
|
|
159
|
+
hasBuilderContent: builderBlockCount > 0,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type BuilderPreviewReview = {
|
|
2
|
+
id: string;
|
|
3
|
+
author: string | null;
|
|
4
|
+
comment: string | null;
|
|
5
|
+
rating: number | null;
|
|
6
|
+
created_at: string;
|
|
7
|
+
is_automated?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type BuilderPreviewFaqItem = {
|
|
11
|
+
question: string;
|
|
12
|
+
answer: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const BUILDER_PREVIEW_REVIEWS: BuilderPreviewReview[] = [
|
|
16
|
+
{
|
|
17
|
+
id: 'preview-review-1',
|
|
18
|
+
author: 'Alex M.',
|
|
19
|
+
comment: 'Instant delivery and clear instructions. Would buy again.',
|
|
20
|
+
rating: 5,
|
|
21
|
+
created_at: '2026-04-12T10:00:00.000Z',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'preview-review-2',
|
|
25
|
+
author: 'Jamie R.',
|
|
26
|
+
comment: 'Support answered quickly when I had a setup question.',
|
|
27
|
+
rating: 5,
|
|
28
|
+
created_at: '2026-04-03T14:30:00.000Z',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'preview-review-3',
|
|
32
|
+
author: 'Taylor S.',
|
|
33
|
+
comment: 'Smooth checkout experience and exactly what was advertised.',
|
|
34
|
+
rating: 4,
|
|
35
|
+
created_at: '2026-03-22T09:15:00.000Z',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export function getBuilderPreviewReviewFixtures<T = BuilderPreviewReview>(): T[] {
|
|
40
|
+
return BUILDER_PREVIEW_REVIEWS as T[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const BUILDER_PREVIEW_FAQ_ITEMS: BuilderPreviewFaqItem[] = [
|
|
44
|
+
{
|
|
45
|
+
question: 'How fast is delivery?',
|
|
46
|
+
answer: 'Most digital products are delivered instantly after payment confirmation.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
question: 'Which payment methods do you accept?',
|
|
50
|
+
answer: 'Available payment methods depend on your shop configuration and region.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
question: 'How do I get support?',
|
|
54
|
+
answer: 'Use the contact page or your customer portal for order-related help.',
|
|
55
|
+
},
|
|
56
|
+
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function isBuilderPreviewMode(location: Pick<Location, 'search'> = window.location): boolean {
|
|
2
|
+
if (typeof window === 'undefined') {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const mode = new URLSearchParams(location.search).get('shoppex-preview-mode');
|
|
7
|
+
return mode === 'theme' || mode === 'builder';
|
|
8
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { createBlockInstance } from '@shoppex/builder-contracts';
|
|
3
|
+
import {
|
|
4
|
+
getBuilderBlockSettingText,
|
|
5
|
+
getLayoutPageBlockAttributes,
|
|
6
|
+
getProductPageBlockAttributes,
|
|
7
|
+
} from './product-page.js';
|
|
8
|
+
|
|
9
|
+
describe('product-page helpers', () => {
|
|
10
|
+
test('getBuilderBlockSettingText trims non-empty strings', () => {
|
|
11
|
+
const block = createBlockInstance({
|
|
12
|
+
type: 'buy-box',
|
|
13
|
+
settings: {
|
|
14
|
+
variantLabel: ' License Type ',
|
|
15
|
+
addonsLabel: ' ',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(getBuilderBlockSettingText(block, 'variantLabel')).toBe('License Type');
|
|
20
|
+
expect(getBuilderBlockSettingText(block, 'addonsLabel')).toBeNull();
|
|
21
|
+
expect(getBuilderBlockSettingText(block, 'missing')).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('getLayoutPageBlockAttributes includes page id and builder block attrs', () => {
|
|
25
|
+
const block = createBlockInstance({ id: 'gallery-1', type: 'gallery' });
|
|
26
|
+
expect(getProductPageBlockAttributes(block)).toEqual({
|
|
27
|
+
'data-page-id': 'product',
|
|
28
|
+
'data-builder-block': 'gallery-1',
|
|
29
|
+
'data-builder-block-type': 'gallery',
|
|
30
|
+
});
|
|
31
|
+
expect(getLayoutPageBlockAttributes('reviews-page', block)).toEqual({
|
|
32
|
+
'data-page-id': 'reviews-page',
|
|
33
|
+
'data-builder-block': 'gallery-1',
|
|
34
|
+
'data-builder-block-type': 'gallery',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BlockInstance } from '@shoppex/builder-contracts';
|
|
2
|
+
import { builderBlock } from './attributes.js';
|
|
3
|
+
|
|
4
|
+
export function getBuilderBlockSettingText(
|
|
5
|
+
block: Pick<BlockInstance, 'settings'>,
|
|
6
|
+
key: string,
|
|
7
|
+
): string | null {
|
|
8
|
+
const value = block.settings[key];
|
|
9
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getLayoutPageBlockAttributes(
|
|
13
|
+
pageId: string,
|
|
14
|
+
block: Pick<BlockInstance, 'id' | 'type'>,
|
|
15
|
+
) {
|
|
16
|
+
return {
|
|
17
|
+
'data-page-id': pageId,
|
|
18
|
+
...builderBlock(block.id, block.type),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getProductPageBlockAttributes(
|
|
23
|
+
block: Pick<BlockInstance, 'id' | 'type'>,
|
|
24
|
+
) {
|
|
25
|
+
return getLayoutPageBlockAttributes('product', block);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @deprecated Use getBuilderBlockSettingText */
|
|
29
|
+
export const getProductBlockText = getBuilderBlockSettingText;
|
|
30
|
+
|
|
31
|
+
/** @deprecated Use getProductPageBlockAttributes */
|
|
32
|
+
export const getBuilderProductBlockAttributes = getProductPageBlockAttributes;
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useBuilderContent,
|
|
13
13
|
useBuilderContentRecord,
|
|
14
14
|
useThemePageBlocks,
|
|
15
|
+
useThemePageBlockAttributes,
|
|
15
16
|
} from './react.js';
|
|
16
17
|
|
|
17
18
|
function createSettings(revision: number, title: string): BuilderSettings {
|
|
@@ -113,6 +114,18 @@ function PageBlocksProbe({ pageId = 'home', defaultOrder = ['hero', 'products']
|
|
|
113
114
|
);
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
function PageBlockAttributesProbe({
|
|
118
|
+
pageId = 'contact-page',
|
|
119
|
+
defaultOrder = ['page-contact-page'],
|
|
120
|
+
}: {
|
|
121
|
+
pageId?: string;
|
|
122
|
+
defaultOrder?: string[];
|
|
123
|
+
}) {
|
|
124
|
+
const attrs = useThemePageBlockAttributes(pageId, defaultOrder);
|
|
125
|
+
|
|
126
|
+
return <section data-testid="page-block-attrs" {...attrs} />;
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
function HeroBlock({ block }: { block: BlockInstance }) {
|
|
117
130
|
const title = useBuilderContent('hero.title', '');
|
|
118
131
|
|
|
@@ -468,6 +481,35 @@ describe('BuilderRuntimePreviewProvider', () => {
|
|
|
468
481
|
expect(dom.window.document.querySelector('[data-testid="page-blocks"]')?.textContent).toBe('hero-1:hero');
|
|
469
482
|
});
|
|
470
483
|
|
|
484
|
+
test('useThemePageBlockAttributes exposes page id and first block attrs', async () => {
|
|
485
|
+
await act(async () => {
|
|
486
|
+
root.render(
|
|
487
|
+
<BuilderRuntimePreviewProvider
|
|
488
|
+
initialSettings={{
|
|
489
|
+
...createEmptyBuilderSettings(1),
|
|
490
|
+
theme: {
|
|
491
|
+
...createEmptyBuilderSettings(1).theme,
|
|
492
|
+
layout: {
|
|
493
|
+
'contact-page': {
|
|
494
|
+
blocks: [
|
|
495
|
+
{ id: 'contact-1', type: 'page-contact-page', visible: true, settings: {} },
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
}}
|
|
501
|
+
>
|
|
502
|
+
<PageBlockAttributesProbe />
|
|
503
|
+
</BuilderRuntimePreviewProvider>,
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const section = dom.window.document.querySelector('[data-testid="page-block-attrs"]');
|
|
508
|
+
expect(section?.getAttribute('data-page-id')).toBe('contact-page');
|
|
509
|
+
expect(section?.getAttribute('data-builder-block')).toBe('contact-1');
|
|
510
|
+
expect(section?.getAttribute('data-builder-block-type')).toBe('page-contact-page');
|
|
511
|
+
});
|
|
512
|
+
|
|
471
513
|
test('shows missing registry blocks inside trusted builder preview', async () => {
|
|
472
514
|
await act(async () => {
|
|
473
515
|
root.render(
|