@akinon/pz-theme 2.0.112-beta.0 → 2.0.112

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,12 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.112-beta.0
3
+ ## 2.0.112
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [cbbbfd757]
8
- - @akinon/next@2.0.112-beta.0
9
- - @akinon/theme-contract@2.0.112-beta.0
7
+ - 4abc0fe: ZERO-5086: Read the video source type through the breakpoint and give a tablet the mobile video
8
+ - @akinon/next@2.0.112
9
+ - @akinon/theme-contract@2.0.112
10
10
 
11
11
  ## 2.0.111
12
12
 
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.112-beta.0",
4
+ "version": "2.0.112",
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.112-beta.0",
11
+ "@akinon/next": "2.0.112",
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.112-beta.0",
16
+ "@akinon/theme-contract": "2.0.112",
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.112-beta.0",
24
+ "@akinon/next": "2.0.112",
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",
@@ -316,6 +316,17 @@ describe('VideoBlock', () => {
316
316
  expect(container.querySelector('video')).toHaveAttribute('src', url);
317
317
  });
318
318
 
319
+ it('plays an extensionless URL when the editor said so per breakpoint', () => {
320
+ // What the editor ACTUALLY writes for that dropdown: a property stored
321
+ // per breakpoint, never a bare string. Compared raw, the escape hatch
322
+ // never fired for a single composition authored in the editor.
323
+ const url = 'https://cdn.example.com/asset/9f2a';
324
+ const { container } = renderBlock(url, {
325
+ sourceType: { desktop: 'file' }
326
+ });
327
+ expect(container.querySelector('video')).toHaveAttribute('src', url);
328
+ });
329
+
319
330
  it('refuses the same URL when they have not', () => {
320
331
  renderBlock('https://cdn.example.com/asset/9f2a');
321
332
  expect(screen.getByText('Invalid video URL')).toBeInTheDocument();
@@ -522,6 +533,40 @@ describe('VideoBlock', () => {
522
533
  ).toBe('https://cdn.example.com/mobile.mp4');
523
534
  });
524
535
 
536
+ it('plays the mobile file on a tablet too, the way the CSS already does', () => {
537
+ // The mobile media query is max-width: 1023px, so at 800px every style
538
+ // around this player is already the mobile one. Reading the source with
539
+ // `=== 'mobile'` left the tablet on the landscape clip inside a layout
540
+ // that had gone vertical.
541
+ const { container } = renderAt(
542
+ 'https://cdn.example.com/desktop.mp4',
543
+ {
544
+ useDifferentMobileVideo: true,
545
+ mobileUrl: 'https://cdn.example.com/mobile.mp4'
546
+ },
547
+ 'tablet'
548
+ );
549
+ expect((container.querySelector('video') as HTMLVideoElement).src).toBe(
550
+ 'https://cdn.example.com/mobile.mp4'
551
+ );
552
+ });
553
+
554
+ it('takes the mobile poster on a tablet for the same reason', () => {
555
+ const { container } = renderAt(
556
+ 'https://cdn.example.com/desktop.mp4',
557
+ {
558
+ useDifferentMobileVideo: true,
559
+ mobilePoster: 'https://cdn.example.com/mobile.jpg',
560
+ poster: 'https://cdn.example.com/desktop.jpg'
561
+ },
562
+ 'tablet'
563
+ );
564
+ expect(container.querySelector('video')).toHaveAttribute(
565
+ 'poster',
566
+ 'https://cdn.example.com/mobile.jpg'
567
+ );
568
+ });
569
+
525
570
  it('still plays the desktop file on desktop even when a mobile override is set', () => {
526
571
  const { container } = renderAt(
527
572
  'https://cdn.example.com/desktop.mp4',
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useRef, useState } from 'react';
4
- import { getResponsiveValue } from '../utils';
4
+ import { getResponsiveValue, isNarrowBreakpoint } from '../utils';
5
5
  import { BlockRendererProps } from './block-renderer-registry';
6
6
 
7
7
  /**
@@ -194,7 +194,25 @@ const VideoBlock = ({
194
194
  const desktopUrl = block.value as string;
195
195
  const properties = (block.properties || {}) as VideoProperties;
196
196
 
197
- const isMobile = currentBreakpoint === 'mobile';
197
+ /**
198
+ * "Narrower than desktop", not "is the phone".
199
+ *
200
+ * `=== 'mobile'` left the 768-1023 band on the DESKTOP video while every
201
+ * style around it was the mobile one — `MOBILE_MEDIA_QUERY` is
202
+ * `max-width: 1023px`, so a tablet reads the mobile rule unless a tablet rule
203
+ * narrows it, but `breakpointForWidth(800)` answers `'tablet'` and this
204
+ * comparison said no. A merchant who uploaded a portrait clip for phones got
205
+ * the landscape one on an iPad, inside a layout that had already gone
206
+ * vertical.
207
+ *
208
+ * `isNarrowBreakpoint` is the predicate written for exactly these call sites
209
+ * — the ones that asked `=== 'mobile'` back when mobile was the only other
210
+ * value — and it is what `hover-showcase-section.tsx` and
211
+ * `order-tracking-lookup-section.tsx` already use for their own `isMobile`.
212
+ * This also moves the custom play/pause button to its mobile corner on a
213
+ * tablet, which is the same inheritance every other mobile treatment has.
214
+ */
215
+ const isMobile = isNarrowBreakpoint(currentBreakpoint);
198
216
  const useDifferentMobileVideo = !!getResponsiveValue(
199
217
  properties.useDifferentMobileVideo,
200
218
  currentBreakpoint,
@@ -211,7 +229,24 @@ const VideoBlock = ({
211
229
  // `properties` handed over RAW, not resolved: `buildEmbedParams` reads
212
230
  // `autoplay`/`loop`/`muted`/`controls` the same way the native `<video>`
213
231
  // below deliberately still does — see the note there.
214
- const parsed = parseVideoSource(videoUrl, properties);
232
+ //
233
+ // `sourceType` is the one exception, and it is the same defect that note
234
+ // records for `controls` and `poster`: the editor stores `{ desktop: 'file' }`,
235
+ // so `properties?.sourceType === 'file'` inside `parseVideoSource` has been
236
+ // comparing an OBJECT to a string and the escape hatch has never once fired
237
+ // for anything authored in the editor. An uploaded clip whose address carries
238
+ // no extension — the ordinary shape of a signed CDN link — therefore had no
239
+ // remedy at all. `getResponsiveValue` passes a plain string straight through,
240
+ // so reading it here handles both shapes and repairs what is already
241
+ // published. Unlike autoplay/loop/muted, reading this one correctly can only
242
+ // turn a dead "Invalid video URL" into a playing video.
243
+ const sourceType = getResponsiveValue(
244
+ properties.sourceType,
245
+ currentBreakpoint,
246
+ 'auto'
247
+ ) as string;
248
+
249
+ const parsed = parseVideoSource(videoUrl, { ...properties, sourceType });
215
250
 
216
251
  /**
217
252
  * Read through the breakpoint, the way `aspect-ratio` already is.