@akinon/pz-theme 2.0.86-beta.0 → 2.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# @akinon/pz-theme
|
|
2
2
|
|
|
3
|
-
## 2.0.86
|
|
3
|
+
## 2.0.86
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
-
|
|
7
|
+
- 323443c8: ZERO-4911: Fix the four type errors pz-theme's own sources raise in a consumer build
|
|
8
|
+
|
|
9
|
+
The package ships its sources, so every consumer type-checks them, and four of its own declarations did not compile. A brand build failed on the first one it reached.
|
|
10
|
+
|
|
11
|
+
The hand-built favourite block in the slider quick view omitted the four fields `Block` requires. `CommerceEventDetail` carried an index signature, which makes `Omit` collapse every named field into it, so `currency` arrived as `unknown` where a `string` was wanted; the named facts now live in their own interface that the open one extends. The `Window.dataLayer` augmentation disagreed with the one `@akinon/next` declares, and the same block is duplicated in the sibling test, so both moved together. The line-break predicates were typed against `theme-section`'s `Section` while the CSS generator hands them its own structural copy, so they are now typed by shape.
|
|
12
|
+
|
|
13
|
+
None of this is a peer-version problem: `Block`, `Section` and `CommerceEventDetail` are pz-theme's own types, and `dataLayer` is declared identically in every published `@akinon/next`. Every fix is types-only and nothing changes at runtime.
|
|
14
|
+
|
|
15
|
+
- @akinon/next@2.0.86
|
|
16
|
+
- @akinon/theme-contract@2.0.86
|
|
10
17
|
|
|
11
18
|
## 2.0.85
|
|
12
19
|
|
package/package.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
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.86
|
|
4
|
+
"version": "2.0.86",
|
|
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.86
|
|
11
|
+
"@akinon/next": "2.0.86",
|
|
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.86
|
|
16
|
+
"@akinon/theme-contract": "2.0.86",
|
|
17
17
|
"clsx": "2.1.1",
|
|
18
18
|
"lottie-web": "^5.13.0",
|
|
19
19
|
"tailwind-merge": "^2.5.4",
|
|
20
20
|
"zod": "4.4.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@akinon/next": "2.0.86
|
|
23
|
+
"@akinon/next": "2.0.86",
|
|
24
24
|
"@testing-library/jest-dom": "5.16.4",
|
|
25
25
|
"@testing-library/react": "16.3.2",
|
|
26
26
|
"@testing-library/user-event": "14.1.1",
|
|
@@ -356,7 +356,18 @@ const SliderQuickView = ({
|
|
|
356
356
|
block={{
|
|
357
357
|
id: `quick-view-favorite-${String(row.pk)}`,
|
|
358
358
|
type: 'favorite-button',
|
|
359
|
-
properties: {}
|
|
359
|
+
properties: {},
|
|
360
|
+
/*
|
|
361
|
+
* Synthesised, not authored: this heart is never part of a
|
|
362
|
+
* composition, so it has no layer name, no styles of its
|
|
363
|
+
* own and no siblings. The section styles it through its
|
|
364
|
+
* own part instead. Written out because `Block` requires
|
|
365
|
+
* all four, and a consumer type-checks these sources.
|
|
366
|
+
*/
|
|
367
|
+
label: 'Quick view favourite',
|
|
368
|
+
styles: {},
|
|
369
|
+
order: 0,
|
|
370
|
+
hidden: false
|
|
360
371
|
}}
|
|
361
372
|
placeholderId=""
|
|
362
373
|
sectionId=""
|
|
@@ -8,10 +8,15 @@ import {
|
|
|
8
8
|
|
|
9
9
|
declare global {
|
|
10
10
|
interface Window {
|
|
11
|
-
|
|
11
|
+
// Must match the augmentation in ../analytics, and the core package's.
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
13
|
+
dataLayer?: Object[];
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
/** `Object[]` hides the element shape, so reads go through one typed view. */
|
|
18
|
+
const layer = () => (window.dataLayer ?? []) as Array<Record<string, unknown>>;
|
|
19
|
+
|
|
15
20
|
describe('neutral commerce events (ZERO-4869)', () => {
|
|
16
21
|
let received: Array<Record<string, unknown>>;
|
|
17
22
|
let listener: (event: Event) => void;
|
|
@@ -170,7 +175,7 @@ describe('default GA4 dataLayer bridge', () => {
|
|
|
170
175
|
items: [buildCommerceItem({ pk: 1, name: 'A' })]
|
|
171
176
|
});
|
|
172
177
|
|
|
173
|
-
expect(
|
|
178
|
+
expect(layer().map((e) => e.event)).toEqual(['select_item']);
|
|
174
179
|
|
|
175
180
|
detach();
|
|
176
181
|
});
|
|
@@ -223,7 +228,7 @@ describe('default GA4 dataLayer bridge', () => {
|
|
|
223
228
|
});
|
|
224
229
|
emitCommerceEvent('search', { searchTerm: 'polo' });
|
|
225
230
|
|
|
226
|
-
expect(
|
|
231
|
+
expect(layer().map((e) => e.event)).toEqual(['wishlist_add']);
|
|
227
232
|
expect(window.dataLayer?.[0]).toMatchObject({ wishlist_source: 'pdp' });
|
|
228
233
|
|
|
229
234
|
detach();
|
package/src/utils/analytics.ts
CHANGED
|
@@ -40,7 +40,13 @@ export interface CommerceItem {
|
|
|
40
40
|
attributes?: Record<string, string>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The named commerce facts, declared apart from the open index signature
|
|
45
|
+
* below. A `[key: string]: unknown` member makes `Omit` and `Pick` collapse
|
|
46
|
+
* every named field into that signature, so `Omit<CommerceEventDetail,
|
|
47
|
+
* 'action'>` hands back `currency: unknown` rather than `string`.
|
|
48
|
+
*/
|
|
49
|
+
export interface CommerceFacts {
|
|
44
50
|
action: string;
|
|
45
51
|
currency?: string;
|
|
46
52
|
value?: number;
|
|
@@ -48,6 +54,10 @@ export interface CommerceEventDetail {
|
|
|
48
54
|
searchTerm?: string;
|
|
49
55
|
method?: string;
|
|
50
56
|
transactionId?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What rides on the event: the named facts, plus whatever else a brand adds. */
|
|
60
|
+
export interface CommerceEventDetail extends CommerceFacts {
|
|
51
61
|
[key: string]: unknown;
|
|
52
62
|
}
|
|
53
63
|
|
|
@@ -168,8 +178,14 @@ export const readItemsCurrency = (
|
|
|
168
178
|
*/
|
|
169
179
|
export const emitCommerceEvent = (
|
|
170
180
|
action: string,
|
|
171
|
-
detail: Omit<
|
|
181
|
+
detail: Omit<CommerceFacts, 'action' | 'items'> & {
|
|
172
182
|
items?: Array<CommerceItem | null>;
|
|
183
|
+
/*
|
|
184
|
+
* Kept open on purpose. Callers pass arbitrary extra facts today and they
|
|
185
|
+
* still flow through into the event; narrowing here would break them for
|
|
186
|
+
* a type error that was never theirs.
|
|
187
|
+
*/
|
|
188
|
+
[key: string]: unknown;
|
|
173
189
|
} = {}
|
|
174
190
|
): void => {
|
|
175
191
|
if (typeof window === 'undefined') return;
|
|
@@ -220,7 +236,13 @@ export const emitCommerceEvent = (
|
|
|
220
236
|
|
|
221
237
|
declare global {
|
|
222
238
|
interface Window {
|
|
223
|
-
|
|
239
|
+
/*
|
|
240
|
+
* The shape @akinon/next declares, matched exactly. Two augmentations of
|
|
241
|
+
* one global have to agree or TypeScript refuses both, and the core
|
|
242
|
+
* package has declared it this way on every major line.
|
|
243
|
+
*/
|
|
244
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
245
|
+
dataLayer?: Object[];
|
|
224
246
|
}
|
|
225
247
|
}
|
|
226
248
|
|
package/src/utils/line-breaks.ts
CHANGED
|
@@ -3,8 +3,6 @@ import {
|
|
|
3
3
|
TABLET_MIN_WIDTH,
|
|
4
4
|
type BreakpointName
|
|
5
5
|
} from './breakpoints';
|
|
6
|
-
import type { Block } from '../theme-block';
|
|
7
|
-
import type { Section } from '../theme-section';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Line breaks that exist at one width and not another.
|
|
@@ -110,7 +108,25 @@ const stringsIn = (value: unknown): string[] => {
|
|
|
110
108
|
return [];
|
|
111
109
|
};
|
|
112
110
|
|
|
113
|
-
|
|
111
|
+
/*
|
|
112
|
+
* Typed by SHAPE rather than against `Block` and `Section`, because the CSS
|
|
113
|
+
* generator keeps its own structural copies of both and the two families are
|
|
114
|
+
* not assignable to one another - `generateThemeCSS` hands its own `Section[]`
|
|
115
|
+
* straight to the predicate below. The same reason `hasCustomSectionRenderer`
|
|
116
|
+
* is shape-typed.
|
|
117
|
+
*
|
|
118
|
+
* These are the three fields the walk actually reads and nothing else, so a
|
|
119
|
+
* scoped break is found in whichever copy it is kept.
|
|
120
|
+
*/
|
|
121
|
+
type LineBreakBlock = {
|
|
122
|
+
value?: unknown;
|
|
123
|
+
properties?: unknown;
|
|
124
|
+
blocks?: LineBreakBlock[];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
type LineBreakSection = { blocks?: LineBreakBlock[] };
|
|
128
|
+
|
|
129
|
+
const blockUsesScopedLineBreaks = (block: LineBreakBlock): boolean => {
|
|
114
130
|
// `value` holds the rich text (a locale map, or a bare string on a
|
|
115
131
|
// single-locale composition); `properties` holds the authored HTML of the
|
|
116
132
|
// blocks that keep their copy there instead.
|
|
@@ -135,7 +151,7 @@ const blockUsesScopedLineBreaks = (block: Block): boolean => {
|
|
|
135
151
|
* composition with no scoped break emits nothing new.
|
|
136
152
|
*/
|
|
137
153
|
export const compositionUsesScopedLineBreaks = (
|
|
138
|
-
sections:
|
|
154
|
+
sections: LineBreakSection[]
|
|
139
155
|
): boolean =>
|
|
140
156
|
sections.some((section) =>
|
|
141
157
|
(section.blocks ?? []).some(blockUsesScopedLineBreaks)
|