@akinon/pz-theme 2.0.107-rc.0 → 2.0.108-beta.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 +15 -35
- package/package.json +4 -4
- package/readme.md +4 -0
- package/src/custom/README-sections.md +30 -2
- package/src/custom/README.md +21 -0
- package/src/custom/__tests__/custom-entry-exports.test.tsx +200 -0
- package/src/custom/index.ts +51 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,40 +1,20 @@
|
|
|
1
1
|
# @akinon/pz-theme
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies [
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Updated dependencies [bfafa3f49]
|
|
19
|
-
- Updated dependencies [57d7eb305]
|
|
20
|
-
- Updated dependencies [d99a6a7d5]
|
|
21
|
-
- Updated dependencies [9db81a714]
|
|
22
|
-
- Updated dependencies [591e345e1]
|
|
23
|
-
- Updated dependencies [4de5303c5]
|
|
24
|
-
- Updated dependencies [95b139dc1]
|
|
25
|
-
- Updated dependencies [1d00f2d06]
|
|
26
|
-
- Updated dependencies [4ac7b2a1e]
|
|
27
|
-
- Updated dependencies [e9598c71d]
|
|
28
|
-
- Updated dependencies [4998a9631]
|
|
29
|
-
- Updated dependencies [804d2bd6c]
|
|
30
|
-
- Updated dependencies [3909d3224]
|
|
31
|
-
- Updated dependencies [6a3d8a631]
|
|
32
|
-
- Updated dependencies [e18836b20]
|
|
33
|
-
- Updated dependencies [b1111d7b1]
|
|
34
|
-
- Updated dependencies [1595d458]
|
|
35
|
-
- Updated dependencies [dca12811]
|
|
36
|
-
- @akinon/next@2.0.107-rc.0
|
|
37
|
-
- @akinon/theme-contract@2.0.107-rc.0
|
|
3
|
+
## 2.0.108-beta.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [cbbbfd757]
|
|
8
|
+
- @akinon/next@2.0.108-beta.0
|
|
9
|
+
- @akinon/theme-contract@2.0.108-beta.0
|
|
10
|
+
|
|
11
|
+
## 2.0.107
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- da5a550: ZERO-5048: Export useCustomBlockContext, useOverlay and chrome mode helpers from the custom entry and add reference examples
|
|
16
|
+
- @akinon/next@2.0.107
|
|
17
|
+
- @akinon/theme-contract@2.0.107
|
|
38
18
|
|
|
39
19
|
## 2.0.106
|
|
40
20
|
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-theme",
|
|
3
3
|
"description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.108-beta.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@akinon/next": "2.0.
|
|
11
|
+
"@akinon/next": "2.0.108-beta.0",
|
|
12
12
|
"react": "^18.0.0 || ^19.0.0",
|
|
13
13
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@akinon/theme-contract": "2.0.
|
|
16
|
+
"@akinon/theme-contract": "2.0.108-beta.0",
|
|
17
17
|
"clsx": "2.1.1",
|
|
18
18
|
"lottie-web": "^5.13.0",
|
|
19
19
|
"react-multi-carousel": "2.8.4",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"zod": "4.4.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@akinon/next": "2.0.
|
|
24
|
+
"@akinon/next": "2.0.108-beta.0",
|
|
25
25
|
"@testing-library/jest-dom": "5.16.4",
|
|
26
26
|
"@testing-library/react": "16.3.2",
|
|
27
27
|
"@testing-library/user-event": "14.1.1",
|
package/readme.md
CHANGED
|
@@ -21,3 +21,7 @@ export default async function Page() {
|
|
|
21
21
|
return <ThemePlaceholder slug="homepage-body" />;
|
|
22
22
|
}
|
|
23
23
|
```
|
|
24
|
+
|
|
25
|
+
### Custom sections and blocks
|
|
26
|
+
|
|
27
|
+
Developer-authored sections and blocks are written against `@akinon/pz-theme/src/custom` — see [`src/custom/README.md`](./src/custom/README.md) and [`src/custom/README-sections.md`](./src/custom/README-sections.md). Reference examples live in `examples/header/` at the root of the pz-next repository; they are not shipped in the npm package.
|
|
@@ -9,6 +9,11 @@ Read [`README.md`](./README.md) first. Everything about `ThemeEditable`,
|
|
|
9
9
|
`useThemeSetting`, `ThemeSlot`, `partAttrs` and the limits works identically
|
|
10
10
|
inside a section; this file is only the difference.
|
|
11
11
|
|
|
12
|
+
For complete, commented sections to read alongside this file, see
|
|
13
|
+
`examples/header/` at the root of the pz-next repository: one header written at
|
|
14
|
+
six levels, from basic to advanced, each building on the previous one. They are
|
|
15
|
+
reference only — not shipped in the npm package and never imported by an app.
|
|
16
|
+
|
|
12
17
|
## When a section instead of a block
|
|
13
18
|
|
|
14
19
|
A block, because it is dropped into a section the merchant created, gets the
|
|
@@ -80,9 +85,17 @@ export default defineCustomSection(StoreHeader, {
|
|
|
80
85
|
Then, from the **client root only**:
|
|
81
86
|
|
|
82
87
|
```ts
|
|
88
|
+
import { registerCustomSections } from '@akinon/pz-theme/src/custom';
|
|
89
|
+
import StoreHeader from './store-header';
|
|
90
|
+
|
|
83
91
|
registerCustomSections('acme', { header: StoreHeader });
|
|
84
92
|
```
|
|
85
93
|
|
|
94
|
+
Register the **default export** — the `defineCustomSection` result — not the
|
|
95
|
+
plain `StoreHeader` function. A plain component is accepted, but it registers
|
|
96
|
+
with no options at all, so its regions, slots, placeholders, seeded styles and
|
|
97
|
+
`lockRootTransforms` are silently dropped.
|
|
98
|
+
|
|
86
99
|
## `ThemeRegion` — the one genuinely new piece
|
|
87
100
|
|
|
88
101
|
A region is a child you named, chose the type of, and placed. Declaring
|
|
@@ -310,11 +323,26 @@ and lose the second appearance.
|
|
|
310
323
|
```tsx
|
|
311
324
|
'use client';
|
|
312
325
|
|
|
313
|
-
|
|
326
|
+
import { usePathname } from 'next/navigation';
|
|
327
|
+
import { useEffect } from 'react';
|
|
328
|
+
import {
|
|
329
|
+
CHROME_MODE_DOCKED,
|
|
330
|
+
setChromeMode
|
|
331
|
+
} from '@akinon/pz-theme/src/custom';
|
|
332
|
+
|
|
333
|
+
function StoreHeader() {
|
|
334
|
+
const isHome = usePathname() === '/';
|
|
335
|
+
|
|
336
|
+
useEffect(() => setChromeMode(isHome ? null : CHROME_MODE_DOCKED), [isHome]);
|
|
314
337
|
|
|
315
|
-
|
|
338
|
+
// ...the header markup
|
|
339
|
+
}
|
|
316
340
|
```
|
|
317
341
|
|
|
342
|
+
Import both from `@akinon/pz-theme/src/custom`, like the rest of the section
|
|
343
|
+
API. The package root exports them too, but it pulls in `server-only`, so a
|
|
344
|
+
client header cannot import them from there.
|
|
345
|
+
|
|
318
346
|
`setChromeMode` returns a cleanup, which is why it is returned straight from the
|
|
319
347
|
effect. **Give the signal one owner.** The cleanup restores the value that call
|
|
320
348
|
displaced — a snapshot, not a counted stack — so it unwinds correctly as one
|
package/src/custom/README.md
CHANGED
|
@@ -16,6 +16,11 @@ and it names the children that go inside it rather than opening a hole. A custom
|
|
|
16
16
|
*block* is the right shape for one element the merchant drops into a band they
|
|
17
17
|
composed.
|
|
18
18
|
|
|
19
|
+
**Want a complete, commented example?** See `examples/header/` at the root of
|
|
20
|
+
the pz-next repository: one header written at six levels, from basic to
|
|
21
|
+
advanced, each building on the previous one. They are reference only — not
|
|
22
|
+
shipped in the npm package and never imported by an app.
|
|
23
|
+
|
|
19
24
|
## The shape
|
|
20
25
|
|
|
21
26
|
A "Complete the look" row for a product page — the merchant retypes its
|
|
@@ -205,6 +210,22 @@ region nothing could ever go in) and a `max` that is not a positive whole number
|
|
|
205
210
|
would otherwise empty a brand's entire palette, with the only explanation in a
|
|
206
211
|
repo the developer is not working in.
|
|
207
212
|
|
|
213
|
+
## Detecting the editor canvas
|
|
214
|
+
|
|
215
|
+
`useCustomBlockContext()` returns the context the block or section renders in,
|
|
216
|
+
or `null` outside one. Its `isDesigner` is what to branch on when the canvas
|
|
217
|
+
needs something the live site must not do — keep a menu open so the merchant
|
|
218
|
+
can style it, or stop a link from navigating on click.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const isDesigner = useCustomBlockContext()?.isDesigner ?? false;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`isDesigner` means "the editing affordances are on", not "the editor is
|
|
225
|
+
attached". The editor's **Use preview** toggle turns it off while the editor
|
|
226
|
+
stays open, so the canvas behaves like the live site: whatever you held open
|
|
227
|
+
closes and links navigate again.
|
|
228
|
+
|
|
208
229
|
## Styling parts
|
|
209
230
|
|
|
210
231
|
The `field-<key>` parts come free from `ThemeEditable`. Everything else a block
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
|
|
6
|
+
import * as chromeMode from '../../chrome/chrome-mode';
|
|
7
|
+
import * as blockContext from '../block-context';
|
|
8
|
+
import * as overlay from '../../hooks/use-overlay';
|
|
9
|
+
import { resetCustomCollection } from '../collect';
|
|
10
|
+
import { CustomSectionHost } from '../section-host';
|
|
11
|
+
import type { Section } from '../../theme-section';
|
|
12
|
+
|
|
13
|
+
// Through the barrel, the path a brand's custom header actually types. The
|
|
14
|
+
// package root exports the chrome mode helpers too, but it pulls in
|
|
15
|
+
// `server-only`, so a client module cannot reach them there.
|
|
16
|
+
import {
|
|
17
|
+
CHROME_MODE_DOCKED,
|
|
18
|
+
defineCustomSection,
|
|
19
|
+
setChromeMode,
|
|
20
|
+
useCustomBlockContext,
|
|
21
|
+
useOverlay
|
|
22
|
+
} from '../index';
|
|
23
|
+
import type {
|
|
24
|
+
ChromeMode,
|
|
25
|
+
CustomBlockContextValue,
|
|
26
|
+
OverlayHandle,
|
|
27
|
+
UseOverlayOptions
|
|
28
|
+
} from '../index';
|
|
29
|
+
|
|
30
|
+
const HEADER = 'custom:brand:store-header';
|
|
31
|
+
|
|
32
|
+
const section = (): Section =>
|
|
33
|
+
({
|
|
34
|
+
id: 'section-header',
|
|
35
|
+
type: HEADER,
|
|
36
|
+
label: 'Store header',
|
|
37
|
+
properties: {},
|
|
38
|
+
styles: {},
|
|
39
|
+
order: 0,
|
|
40
|
+
hidden: false,
|
|
41
|
+
blocks: []
|
|
42
|
+
} as unknown as Section);
|
|
43
|
+
|
|
44
|
+
/** Reports what a developer's own code would read from the hook. */
|
|
45
|
+
function EditorProbe() {
|
|
46
|
+
const context: CustomBlockContextValue | null = useCustomBlockContext();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-testid="probe"
|
|
51
|
+
data-inside={context ? 'yes' : 'no'}
|
|
52
|
+
data-designer={String(context?.isDesigner ?? 'none')}
|
|
53
|
+
data-scope={context?.scope ?? 'none'}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const StoreHeader = defineCustomSection(EditorProbe, {
|
|
59
|
+
label: 'Store header'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const renderHost = (isDesigner: boolean) =>
|
|
63
|
+
render(
|
|
64
|
+
<CustomSectionHost
|
|
65
|
+
type={HEADER}
|
|
66
|
+
label="Store header"
|
|
67
|
+
definition={StoreHeader}
|
|
68
|
+
section={section()}
|
|
69
|
+
placeholderId="header"
|
|
70
|
+
isDesigner={isDesigner}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const probe = () => screen.getByTestId('probe');
|
|
75
|
+
|
|
76
|
+
describe('the custom entry exports ZERO-5048 added', () => {
|
|
77
|
+
it('re-exports the same bindings rather than second copies', () => {
|
|
78
|
+
expect(useCustomBlockContext).toBe(blockContext.useCustomBlockContext);
|
|
79
|
+
expect(setChromeMode).toBe(chromeMode.setChromeMode);
|
|
80
|
+
expect(CHROME_MODE_DOCKED).toBe(chromeMode.CHROME_MODE_DOCKED);
|
|
81
|
+
expect(CHROME_MODE_DOCKED).toBe('docked');
|
|
82
|
+
expect(useOverlay).toBe(overlay.useOverlay);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('keeps use-overlay.ts client-safe: a client directive and react as its only import', () => {
|
|
86
|
+
const source = fs.readFileSync(
|
|
87
|
+
path.resolve(__dirname, '../../hooks/use-overlay.ts'),
|
|
88
|
+
'utf8'
|
|
89
|
+
);
|
|
90
|
+
const imports = source.match(/^import[\s\S]*?from\s+['"]([^'"]+)['"]/gm);
|
|
91
|
+
|
|
92
|
+
expect(source.trimStart()).toMatch(/^['"]use client['"]/);
|
|
93
|
+
expect(source).not.toMatch(/server-only/);
|
|
94
|
+
expect(imports).toHaveLength(1);
|
|
95
|
+
expect(imports?.[0]).toMatch(/from 'react'$/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('keeps chrome-mode.ts free of imports and directives, so the barrel stays client-safe', () => {
|
|
99
|
+
const source = fs.readFileSync(
|
|
100
|
+
path.resolve(__dirname, '../../chrome/chrome-mode.ts'),
|
|
101
|
+
'utf8'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(source).not.toMatch(/^import\s/m);
|
|
105
|
+
expect(source).not.toMatch(/server-only/);
|
|
106
|
+
expect(source.trimStart()).not.toMatch(/^['"]use (client|server)['"]/);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('useCustomBlockContext', () => {
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
resetCustomCollection();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('returns null outside a custom block or section instead of throwing', () => {
|
|
116
|
+
render(<EditorProbe />);
|
|
117
|
+
|
|
118
|
+
expect(probe().getAttribute('data-inside')).toBe('no');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('reports isDesigner true on the editing canvas', () => {
|
|
122
|
+
renderHost(true);
|
|
123
|
+
|
|
124
|
+
expect(probe().getAttribute('data-inside')).toBe('yes');
|
|
125
|
+
expect(probe().getAttribute('data-scope')).toBe('section');
|
|
126
|
+
expect(probe().getAttribute('data-designer')).toBe('true');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('reports isDesigner false when the section receives the preview flag', () => {
|
|
130
|
+
renderHost(false);
|
|
131
|
+
|
|
132
|
+
expect(probe().getAttribute('data-designer')).toBe('false');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('receives isDesigner with preview (browse mode) already folded in', () => {
|
|
136
|
+
// The documented promise: "Use preview" turns isDesigner off for the
|
|
137
|
+
// section. The host passes through what the placeholder hands it, and the
|
|
138
|
+
// placeholder hands sections `isDesigner && !isBrowseMode`, never the raw
|
|
139
|
+
// "editor attached" flag.
|
|
140
|
+
const wrapper = fs.readFileSync(
|
|
141
|
+
path.resolve(__dirname, '../../theme-placeholder-wrapper.tsx'),
|
|
142
|
+
'utf8'
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(wrapper).toMatch(/const isEditing = isDesigner && !isBrowseMode;/);
|
|
146
|
+
expect(wrapper).toMatch(/<ThemeSection[\s\S]*?isDesigner=\{isEditing\}/);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('setChromeMode from the custom entry', () => {
|
|
151
|
+
afterEach(() => {
|
|
152
|
+
document.body.removeAttribute('data-chrome-mode');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('stamps the docked mode on body and restores it on cleanup', () => {
|
|
156
|
+
const mode: ChromeMode = CHROME_MODE_DOCKED;
|
|
157
|
+
const cleanup = setChromeMode(mode);
|
|
158
|
+
|
|
159
|
+
expect(document.body.getAttribute('data-chrome-mode')).toBe('docked');
|
|
160
|
+
|
|
161
|
+
cleanup();
|
|
162
|
+
|
|
163
|
+
expect(document.body.hasAttribute('data-chrome-mode')).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('useOverlay from the custom entry', () => {
|
|
168
|
+
/** A panel written the way a custom block writes one. */
|
|
169
|
+
function Panel(props: Omit<UseOverlayOptions, 'modal'>) {
|
|
170
|
+
const handle: OverlayHandle<HTMLDivElement> = useOverlay<HTMLDivElement>({
|
|
171
|
+
...props,
|
|
172
|
+
modal: false
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return props.open ? (
|
|
176
|
+
<div {...handle.overlayProps} role="dialog" aria-label="Menu" />
|
|
177
|
+
) : null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
afterEach(() => {
|
|
181
|
+
document.body.removeAttribute('data-overlay-state');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('sets the overlay-open page state on body while open, and clears it on close', () => {
|
|
185
|
+
const { rerender } = render(<Panel open />);
|
|
186
|
+
|
|
187
|
+
expect(document.body.getAttribute('data-overlay-state')).toBe('open');
|
|
188
|
+
|
|
189
|
+
rerender(<Panel open={false} />);
|
|
190
|
+
|
|
191
|
+
expect(document.body.hasAttribute('data-overlay-state')).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('stays inert on the editor canvas', () => {
|
|
195
|
+
render(<Panel open isDesigner />);
|
|
196
|
+
|
|
197
|
+
expect(document.body.hasAttribute('data-overlay-state')).toBe(false);
|
|
198
|
+
expect(screen.getByRole('dialog').hasAttribute('aria-modal')).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
});
|
package/src/custom/index.ts
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
* export default defineCustomBlock(PromoStrip);
|
|
30
30
|
*
|
|
31
31
|
* // blocks/index.ts — imported from the CLIENT ROOT only
|
|
32
|
-
*
|
|
32
|
+
* import { registerCustomBlocks } from '@akinon/pz-theme/src/custom';
|
|
33
|
+
* import PromoStrip from './promo-strip';
|
|
34
|
+
* registerCustomBlocks('brand', { 'promo-strip': PromoStrip });
|
|
33
35
|
*
|
|
34
36
|
* The block NAME lives in that record, never in `Component.name`: every
|
|
35
37
|
* production build minifies the function name, so a block authored as
|
|
@@ -71,11 +73,11 @@ export { registerCustomBlocks, registerCustomSections } from './register';
|
|
|
71
73
|
*
|
|
72
74
|
* The shape a brand writes:
|
|
73
75
|
*
|
|
74
|
-
* // views/header/
|
|
76
|
+
* // views/header/store-header.tsx
|
|
75
77
|
* import { defineCustomSection, ThemeRegion, ThemeSlot, partAttrs }
|
|
76
78
|
* from '@akinon/pz-theme/src/custom';
|
|
77
79
|
*
|
|
78
|
-
* function
|
|
80
|
+
* function StoreHeader() {
|
|
79
81
|
* return (
|
|
80
82
|
* <header {...partAttrs('bar')}>
|
|
81
83
|
* <ThemeRegion name="logo" />
|
|
@@ -85,8 +87,8 @@ export { registerCustomBlocks, registerCustomSections } from './register';
|
|
|
85
87
|
* );
|
|
86
88
|
* }
|
|
87
89
|
*
|
|
88
|
-
* export default defineCustomSection(
|
|
89
|
-
* label: '
|
|
90
|
+
* export default defineCustomSection(StoreHeader, {
|
|
91
|
+
* label: 'Store header',
|
|
90
92
|
* regions: [
|
|
91
93
|
* { name: 'logo', label: 'Logo', block: 'image' },
|
|
92
94
|
* { name: 'basket', label: 'Basket', block: 'mini-basket' }
|
|
@@ -96,7 +98,9 @@ export { registerCustomBlocks, registerCustomSections } from './register';
|
|
|
96
98
|
* });
|
|
97
99
|
*
|
|
98
100
|
* // views/header/index.ts — imported from the CLIENT ROOT only
|
|
99
|
-
*
|
|
101
|
+
* import { registerCustomSections } from '@akinon/pz-theme/src/custom';
|
|
102
|
+
* import StoreHeader from './store-header';
|
|
103
|
+
* registerCustomSections('brand', { header: StoreHeader });
|
|
100
104
|
*
|
|
101
105
|
* A REGION AND A SLOT ARE DIFFERENT PROMISES. A region is a child the developer
|
|
102
106
|
* named, chose the type of and PLACED; the merchant styles it, configures it,
|
|
@@ -129,6 +133,36 @@ export type {
|
|
|
129
133
|
ThemeSettingOptions
|
|
130
134
|
} from './use-theme-setting';
|
|
131
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The context every custom unit renders inside, for the one question the
|
|
138
|
+
* components above cannot answer for a developer: is this the editor canvas?
|
|
139
|
+
*
|
|
140
|
+
* `isDesigner` is true only while the editing affordances are on. The editor's
|
|
141
|
+
* "Use preview" toggle (browse mode) turns it OFF for the section although the
|
|
142
|
+
* editor is still attached — `theme-placeholder-wrapper.tsx` hands sections
|
|
143
|
+
* `isDesigner && !isBrowseMode` — so anything kept open for styling closes and
|
|
144
|
+
* links navigate, exactly as on the live site.
|
|
145
|
+
*
|
|
146
|
+
* Returns null outside a custom block or section, never throws.
|
|
147
|
+
*/
|
|
148
|
+
export { useCustomBlockContext } from './block-context';
|
|
149
|
+
export type { CustomBlockContextValue } from './block-context';
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The overlay behaviour pz-theme's own drawers and menus use, for a custom
|
|
153
|
+
* block that opens a panel: Escape to close, an optional scroll lock and
|
|
154
|
+
* focus handling, and the `overlay-open` page state on `body` while it is
|
|
155
|
+
* open, which a header section can style with `:overlay-open/<property>`.
|
|
156
|
+
*
|
|
157
|
+
* Pass the block's `isDesigner`: on the editor canvas the hook is inert (no
|
|
158
|
+
* focus move, no scroll lock, no page state). `modal: false` leaves the page
|
|
159
|
+
* usable, so a trigger outside the panel can still close it.
|
|
160
|
+
* `hooks/use-overlay.ts` is 'use client' and imports only react, so it is
|
|
161
|
+
* safe to re-export here.
|
|
162
|
+
*/
|
|
163
|
+
export { useOverlay } from '../hooks/use-overlay';
|
|
164
|
+
export type { OverlayHandle, UseOverlayOptions } from '../hooks/use-overlay';
|
|
165
|
+
|
|
132
166
|
// What the editor bridge announces. Exported here rather than kept internal
|
|
133
167
|
// because a brand that mounts its own announce path needs the same payload the
|
|
134
168
|
// package would have sent, not a second construction of it.
|
|
@@ -185,3 +219,14 @@ export { findRegionChild, isRegionNode, regionNodeName } from './region-nodes';
|
|
|
185
219
|
* `data-part="…"`, which `sanitizePartName` then has no chance to normalize.
|
|
186
220
|
*/
|
|
187
221
|
export { partAttrs } from '../utils/part-styles';
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The chrome mode writer, re-exported so a custom header section can declare
|
|
225
|
+
* its docked appearance from this module. The package root exports the same
|
|
226
|
+
* symbols but pulls in `server-only`, which a client module cannot import.
|
|
227
|
+
* `chrome/chrome-mode.ts` has no imports and no directive, and `setChromeMode`
|
|
228
|
+
* is a no-op without a `document`, so it is safe on either graph. Call it from
|
|
229
|
+
* an effect and return the cleanup it hands back.
|
|
230
|
+
*/
|
|
231
|
+
export { CHROME_MODE_DOCKED, setChromeMode } from '../chrome/chrome-mode';
|
|
232
|
+
export type { ChromeMode } from '../chrome/chrome-mode';
|