@decocms/blocks 7.3.0 → 7.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.3.0",
3
+ "version": "7.3.1",
4
4
  "type": "module",
5
5
  "description": "Deco framework-agnostic core: CMS block resolution, section registry, matchers, portable SDK utilities",
6
6
  "repository": {
@@ -46,6 +46,7 @@
46
46
  "./sdk/cookie": "./src/sdk/cookie.ts",
47
47
  "./sdk/crypto": "./src/sdk/crypto.ts",
48
48
  "./sdk/csp": "./src/sdk/csp.ts",
49
+ "./sdk/detectDevice": "./src/sdk/detectDevice.ts",
49
50
  "./sdk/djb2": "./src/sdk/djb2.ts",
50
51
  "./sdk/encoding": "./src/sdk/encoding.ts",
51
52
  "./sdk/env": "./src/sdk/env.ts",
@@ -16,7 +16,7 @@
16
16
  * });
17
17
  * ```
18
18
  */
19
- import { detectDevice } from "../sdk/useDevice";
19
+ import { detectDevice } from "../sdk/detectDevice";
20
20
  import type { SectionLoaderFn } from "./sectionLoaders";
21
21
 
22
22
  /**
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type ComponentPropsWithoutRef,
3
+ type Context,
3
4
  createContext,
4
5
  forwardRef,
5
6
  type ReactNode,
@@ -12,13 +13,27 @@ import { type FitOptions, getOptimizedMediaUrl, getSrcSet } from "./Image";
12
13
  // Preload context — flows from <Picture preload> to child <Source> elements
13
14
  // so each source can inject its own <link rel="preload"> with the correct
14
15
  // media query for responsive art direction.
16
+ //
17
+ // Created LAZILY (first render), not at module scope: this file has no
18
+ // "use client" directive, so in a react-server graph (a Server Component
19
+ // page, or ANY Next.js route handler — which ignores "use client"
20
+ // entirely) the module evaluates against React's react-server build, where
21
+ // `createContext` does not exist. A module-scope call crashes the whole
22
+ // graph at import time ("createContext is not a function") even if
23
+ // Picture/Source never render. Deferring to render time is safe: renders
24
+ // only ever happen under a full React build (client or SSR), and both
25
+ // components resolve the same memoized context object.
15
26
  // -------------------------------------------------------------------------
16
27
 
17
28
  interface PreloadContextValue {
18
29
  preload: boolean;
19
30
  }
20
31
 
21
- const PreloadContext = createContext<PreloadContextValue>({ preload: false });
32
+ let _preloadContext: Context<PreloadContextValue> | null = null;
33
+ function getPreloadContext(): Context<PreloadContextValue> {
34
+ _preloadContext ??= createContext<PreloadContextValue>({ preload: false });
35
+ return _preloadContext;
36
+ }
22
37
 
23
38
  // -------------------------------------------------------------------------
24
39
  // Source — composable <source> with automatic srcSet optimization and
@@ -41,7 +56,7 @@ export const Source = forwardRef<HTMLSourceElement, SourceProps>(function Source
41
56
  { src, width, height, fetchPriority, fit = "cover", ...rest },
42
57
  ref,
43
58
  ) {
44
- const { preload } = useContext(PreloadContext);
59
+ const { preload } = useContext(getPreloadContext());
45
60
 
46
61
  const optimizedSrc = getOptimizedMediaUrl({
47
62
  originalSrc: src,
@@ -93,6 +108,7 @@ export const Picture = forwardRef<HTMLPictureElement, PictureProps>(function Pic
93
108
  ref,
94
109
  ) {
95
110
  const value = useMemo(() => ({ preload }), [preload]);
111
+ const PreloadContext = getPreloadContext();
96
112
 
97
113
  return (
98
114
  <PreloadContext.Provider value={value}>
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Pure User-Agent device detection — no React, no RequestContext, no
3
+ * side effects. Import from HERE (not `./useDevice`) in any module that can
4
+ * end up in a Next.js App Router **route handler** graph.
5
+ *
6
+ * Why this file exists: `useDevice.ts` re-exports the `"use client"`
7
+ * context half (`./useDeviceContext`, which calls `createContext` at module
8
+ * scope) for backward compatibility. That re-export is harmless in Server
9
+ * Components — Next honors the `"use client"` boundary there — but route
10
+ * handlers (App Router route.ts files) have NO client graph: the directive
11
+ * is ignored, the module executes against Next's vendored **RSC** React
12
+ * (`vendored/rsc/react.js`), and that build has no `createContext`, so the
13
+ * whole route module crashes at evaluation time
14
+ * ("...createContext is not a function"). `@decocms/nextjs`'s admin route
15
+ * handlers reach device detection via `cms/sectionMixins.ts`
16
+ * (`detectDevice`) and `sdk/requestContext.ts` (`isMobileUA`) — both now
17
+ * import this leaf so their graphs never touch the client context file.
18
+ */
19
+
20
+ export type Device = "mobile" | "tablet" | "desktop";
21
+
22
+ // Android phones include "Mobile" in their UA; Android tablets do not.
23
+ // Check TABLET_RE first so `android(?!.*mobile)` captures tablets before
24
+ // the MOBILE_RE `android.*mobile` branch matches phones.
25
+ export const MOBILE_RE = /mobile|android.*mobile|iphone|ipod|webos|blackberry|opera mini|iemobile/i;
26
+ export const TABLET_RE = /ipad|tablet|kindle|silk|playbook|android(?!.*mobile)/i;
27
+
28
+ /**
29
+ * Simple mobile-or-not check (mobile + tablet = true).
30
+ * Use this for cache key splitting or any context where you
31
+ * only need a mobile/desktop binary decision.
32
+ */
33
+ export function isMobileUA(userAgent: string): boolean {
34
+ return MOBILE_RE.test(userAgent) || TABLET_RE.test(userAgent);
35
+ }
36
+
37
+ /**
38
+ * Detect device type from a User-Agent string.
39
+ * Pure function — no side effects, works anywhere.
40
+ */
41
+ export function detectDevice(userAgent: string): Device {
42
+ if (TABLET_RE.test(userAgent)) return "tablet";
43
+ if (MOBILE_RE.test(userAgent)) return "mobile";
44
+ return "desktop";
45
+ }
@@ -87,7 +87,7 @@ export interface RequestContextData {
87
87
  // Storage
88
88
  // -------------------------------------------------------------------------
89
89
 
90
- import { isMobileUA } from "./useDevice";
90
+ import { isMobileUA } from "./detectDevice";
91
91
 
92
92
  const BOT_RE =
93
93
  /bot|crawl|spider|slurp|bingpreview|facebookexternalhit|linkedinbot|twitterbot|whatsapp|telegram|googlebot|yandex|baidu|duckduck/i;
@@ -21,52 +21,32 @@
21
21
  * ```
22
22
  */
23
23
 
24
+ import { detectDevice } from "./detectDevice";
24
25
  import { RequestContext } from "./requestContext";
25
26
 
26
- export type Device = "mobile" | "tablet" | "desktop";
27
+ // The pure UA-parsing half (`Device`, `MOBILE_RE`, `TABLET_RE`,
28
+ // `isMobileUA`, `detectDevice`) lives in `./detectDevice` — a leaf with no
29
+ // React and no RequestContext — and is re-exported below for backward
30
+ // compatibility. Modules that can land in a Next.js route-handler graph
31
+ // (`cms/sectionMixins.ts`, `sdk/requestContext.ts`) MUST import that leaf
32
+ // directly, not this file: this file also re-exports the `"use client"`
33
+ // context half, which crashes route handlers (see ./detectDevice's doc
34
+ // comment for the vendored-RSC-React mechanics).
35
+ export { type Device, detectDevice, isMobileUA, MOBILE_RE, TABLET_RE } from "./detectDevice";
27
36
 
28
37
  // `DeviceContext`, the `useDevice()` hook, and `DeviceProvider` live in
29
38
  // `./useDeviceContext` (a `"use client"` file) and are re-exported below for
30
39
  // full backward compatibility with this module's public API — see that
31
- // file's doc comment for why the split exists. Everything that stays in
32
- // *this* file is a plain, hook-free function, safe to import from
33
- // server-only code (e.g. `cms/sectionMixins.ts`'s `withDevice()`/
34
- // `withMobile()` loader mixins) without dragging a client-only hook/context
35
- // boundary into a Server Component's module graph.
40
+ // file's doc comment for why the split exists.
36
41
  export { DeviceContext, useDevice, DeviceProvider } from "./useDeviceContext";
37
42
 
38
- // Android phones include "Mobile" in their UA; Android tablets do not.
39
- // Check TABLET_RE first so `android(?!.*mobile)` captures tablets before
40
- // the MOBILE_RE `android.*mobile` branch matches phones.
41
- export const MOBILE_RE = /mobile|android.*mobile|iphone|ipod|webos|blackberry|opera mini|iemobile/i;
42
- export const TABLET_RE = /ipad|tablet|kindle|silk|playbook|android(?!.*mobile)/i;
43
-
44
- /**
45
- * Simple mobile-or-not check (mobile + tablet = true).
46
- * Use this for cache key splitting or any context where you
47
- * only need a mobile/desktop binary decision.
48
- */
49
- export function isMobileUA(userAgent: string): boolean {
50
- return MOBILE_RE.test(userAgent) || TABLET_RE.test(userAgent);
51
- }
52
-
53
- /**
54
- * Detect device type from a User-Agent string.
55
- * Pure function — no side effects, works anywhere.
56
- */
57
- export function detectDevice(userAgent: string): Device {
58
- if (TABLET_RE.test(userAgent)) return "tablet";
59
- if (MOBILE_RE.test(userAgent)) return "mobile";
60
- return "desktop";
61
- }
62
-
63
43
  /**
64
44
  * Resolve the current device from the ambient runtime (RequestContext on the
65
45
  * server, `navigator.userAgent` on the client) — no React hooks involved.
66
46
  * Used both by the plain `check*()` helpers below and by `useDevice()`/
67
47
  * `DeviceProvider` in `./useDeviceContext`.
68
48
  */
69
- export function resolveDeviceFromRuntime(): Device {
49
+ export function resolveDeviceFromRuntime(): ReturnType<typeof detectDevice> {
70
50
  // Server: use RequestContext UA header
71
51
  if (typeof document === "undefined") {
72
52
  const ctx = RequestContext.current;