@akinon/pz-theme 2.0.86-rc.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,38 +1,19 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.86-rc.0
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [86187b1c]
8
- - Updated dependencies [0cf9ea239]
9
- - Updated dependencies [324f97d55]
10
- - Updated dependencies [51ea06888]
11
- - Updated dependencies
12
- - Updated dependencies [2fea41430]
13
- - Updated dependencies [b55acb768]
14
- - Updated dependencies [760258c1c]
15
- - Updated dependencies [143be2b9d]
16
- - Updated dependencies [7889b08fe]
17
- - Updated dependencies [9f8cd3bc5]
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 [b1111d7b]
34
- - @akinon/next@2.0.86-rc.0
35
- - @akinon/theme-contract@2.0.86-rc.0
3
+ ## 2.0.86
4
+
5
+ ### Patch Changes
6
+
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
36
17
 
37
18
  ## 2.0.85
38
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-rc.0",
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-rc.0",
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-rc.0",
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-rc.0",
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
- dataLayer?: Array<Record<string, unknown>>;
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(window.dataLayer?.map((e) => e.event)).toEqual(['select_item']);
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(window.dataLayer?.map((e) => e.event)).toEqual(['wishlist_add']);
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();
@@ -40,7 +40,13 @@ export interface CommerceItem {
40
40
  attributes?: Record<string, string>;
41
41
  }
42
42
 
43
- export interface CommerceEventDetail {
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<CommerceEventDetail, 'action'> & {
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
- dataLayer?: Array<Record<string, unknown>>;
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
 
@@ -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
- const blockUsesScopedLineBreaks = (block: Block): boolean => {
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: Section[]
154
+ sections: LineBreakSection[]
139
155
  ): boolean =>
140
156
  sections.some((section) =>
141
157
  (section.blocks ?? []).some(blockUsesScopedLineBreaks)