@akinon/pz-theme 2.0.6-rc.1 → 2.0.6

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,37 +1,15 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.6-rc.1
3
+ ## 2.0.6
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [51ea0688]
8
- - @akinon/next@2.0.6-rc.1
9
-
10
- ## 2.0.6-rc.0
11
-
12
- ### Patch Changes
13
-
14
- - Updated dependencies [0cf9ea23]
15
- - Updated dependencies [324f97d5]
16
- - Updated dependencies
17
- - Updated dependencies [b55acb768]
18
- - Updated dependencies [760258c1]
19
- - Updated dependencies [143be2b9]
20
- - Updated dependencies [7889b08f]
21
- - Updated dependencies [9f8cd3bc5]
22
- - Updated dependencies [bfafa3f4]
23
- - Updated dependencies [57d7eb30]
24
- - Updated dependencies [d99a6a7d]
25
- - Updated dependencies [9db81a71]
26
- - Updated dependencies [591e345e]
27
- - Updated dependencies [4de5303c5]
28
- - Updated dependencies [95b139dc]
29
- - Updated dependencies [1d00f2d0]
30
- - Updated dependencies [4ac7b2a1]
31
- - Updated dependencies [4998a963]
32
- - Updated dependencies [3909d322]
33
- - Updated dependencies [e18836b2]
34
- - @akinon/next@2.0.6-rc.0
7
+ - 8ae85c5a: ZERO-4382: Add lazy load and fetch priority support
8
+ - Updated dependencies [89deabe5]
9
+ - Updated dependencies [1a345c47]
10
+ - Updated dependencies [1f1ae44e]
11
+ - Updated dependencies [8d8fefbe]
12
+ - @akinon/next@2.0.6
35
13
 
36
14
  ## 2.0.5
37
15
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
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.6-rc.1",
4
+ "version": "2.0.6",
5
5
  "license": "MIT",
6
6
  "main": "src/index.ts",
7
7
  "peerDependencies": {
8
- "@akinon/next": "2.0.6-rc.1",
8
+ "@akinon/next": "2.0.6",
9
9
  "react": "^18.0.0 || ^19.0.0",
10
10
  "react-dom": "^18.0.0 || ^19.0.0"
11
11
  },
@@ -15,7 +15,7 @@
15
15
  "tailwind-merge": "^2.5.4"
16
16
  },
17
17
  "devDependencies": {
18
- "@akinon/next": "2.0.6-rc.1",
18
+ "@akinon/next": "2.0.6",
19
19
  "@types/node": "^18.7.8",
20
20
  "@types/react": "^18.0.17",
21
21
  "@types/react-dom": "^18.0.6",
@@ -1,6 +1,13 @@
1
1
  import React, { useMemo } from 'react';
2
2
 
3
- import { getResponsiveValue } from '../utils';
3
+ import {
4
+ IMAGE_FETCH_PRIORITIES,
5
+ IMAGE_LOADING_STRATEGIES,
6
+ type ImageFetchPriority,
7
+ type ImageLoadingStrategy,
8
+ getResponsiveValue,
9
+ resolveOption
10
+ } from '../utils';
4
11
  import { BlockRendererProps } from './block-renderer-registry';
5
12
 
6
13
  const pickImageSource = (value: unknown, currentBreakpoint: string): string => {
@@ -72,6 +79,22 @@ const ImageBlock = ({
72
79
  }, [block.value, block.properties?.alt, currentBreakpoint]);
73
80
 
74
81
  const { url: src, alt } = imageValue;
82
+ const loadingStrategyValue =
83
+ block.properties?.loadingStrategy ?? block.styles?.loadingStrategy;
84
+ const priorityValue = block.properties?.priority ?? block.styles?.priority;
85
+
86
+ const loadingStrategy = resolveOption<ImageLoadingStrategy>(
87
+ loadingStrategyValue,
88
+ currentBreakpoint,
89
+ 'lazy',
90
+ IMAGE_LOADING_STRATEGIES
91
+ );
92
+ const priority = resolveOption<ImageFetchPriority>(
93
+ priorityValue,
94
+ currentBreakpoint,
95
+ 'auto',
96
+ IMAGE_FETCH_PRIORITIES
97
+ );
75
98
 
76
99
  const width = getResponsiveValue(
77
100
  block.styles?.width,
@@ -125,6 +148,8 @@ const ImageBlock = ({
125
148
  key={src}
126
149
  src={src}
127
150
  alt={alt}
151
+ loading={loadingStrategy}
152
+ fetchPriority={priority}
128
153
  style={{
129
154
  display: 'block',
130
155
  width: width ?? '100%',
@@ -7,6 +7,7 @@ import { twMerge } from 'tailwind-merge';
7
7
  import ThemeBlock from '../theme-block';
8
8
  import { useThemeSettingsContext } from '../theme-settings-context';
9
9
  import {
10
+ applyFirstSlideImageLoading,
10
11
  getCSSStyles,
11
12
  getResponsiveValue,
12
13
  resolveThemeCssVariables
@@ -481,37 +482,46 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
481
482
  !isDesigner && showArrows ? <CustomRightArrow /> : undefined
482
483
  }
483
484
  >
484
- {sortedBlocks.map((childBlock) => (
485
- <div
486
- key={childBlock.id}
487
- style={{
488
- paddingLeft: slidePadding,
489
- paddingRight: slidePadding
490
- }}
491
- >
492
- <ThemeBlock
493
- block={childBlock}
494
- placeholderId={placeholderId}
495
- sectionId={sectionId}
496
- isDesigner={isDesigner}
497
- isSelected={selectedBlockId === childBlock.id}
498
- selectedBlockId={selectedBlockId}
499
- currentBreakpoint={currentBreakpoint}
500
- onMoveUp={() => blockAction('MOVE_BLOCK_UP', childBlock.id)}
501
- onMoveDown={() => blockAction('MOVE_BLOCK_DOWN', childBlock.id)}
502
- onDuplicate={() =>
503
- blockAction('DUPLICATE_BLOCK', childBlock.id)
504
- }
505
- onToggleVisibility={() =>
506
- blockAction('TOGGLE_BLOCK_VISIBILITY', childBlock.id)
507
- }
508
- onDelete={() => blockAction('DELETE_BLOCK', childBlock.id)}
509
- onRename={(newLabel: string) =>
510
- blockAction('RENAME_BLOCK', childBlock.id, newLabel)
511
- }
512
- />
513
- </div>
514
- ))}
485
+ {sortedBlocks.map((childBlock, index) => {
486
+ const renderedChildBlock =
487
+ index === 0
488
+ ? applyFirstSlideImageLoading(childBlock)
489
+ : childBlock;
490
+
491
+ return (
492
+ <div
493
+ key={childBlock.id}
494
+ style={{
495
+ paddingLeft: slidePadding,
496
+ paddingRight: slidePadding
497
+ }}
498
+ >
499
+ <ThemeBlock
500
+ block={renderedChildBlock}
501
+ placeholderId={placeholderId}
502
+ sectionId={sectionId}
503
+ isDesigner={isDesigner}
504
+ isSelected={selectedBlockId === childBlock.id}
505
+ selectedBlockId={selectedBlockId}
506
+ currentBreakpoint={currentBreakpoint}
507
+ onMoveUp={() => blockAction('MOVE_BLOCK_UP', childBlock.id)}
508
+ onMoveDown={() =>
509
+ blockAction('MOVE_BLOCK_DOWN', childBlock.id)
510
+ }
511
+ onDuplicate={() =>
512
+ blockAction('DUPLICATE_BLOCK', childBlock.id)
513
+ }
514
+ onToggleVisibility={() =>
515
+ blockAction('TOGGLE_BLOCK_VISIBILITY', childBlock.id)
516
+ }
517
+ onDelete={() => blockAction('DELETE_BLOCK', childBlock.id)}
518
+ onRename={(newLabel: string) =>
519
+ blockAction('RENAME_BLOCK', childBlock.id, newLabel)
520
+ }
521
+ />
522
+ </div>
523
+ );
524
+ })}
515
525
  </Carousel>
516
526
 
517
527
  {isDesigner &&
@@ -6,7 +6,11 @@ import { twMerge } from 'tailwind-merge';
6
6
 
7
7
  import ThemeBlock from '../theme-block';
8
8
  import { useThemeSettingsContext } from '../theme-settings-context';
9
- import { getCSSStyles, getResponsiveValue } from '../utils';
9
+ import {
10
+ applyFirstSlideImageLoading,
11
+ getCSSStyles,
12
+ getResponsiveValue
13
+ } from '../utils';
10
14
 
11
15
  interface PostsSliderSectionProps {
12
16
  section: any;
@@ -424,9 +428,11 @@ const PostsSliderSection: React.FC<PostsSliderSectionProps> = ({
424
428
  containerClass="posts-slider-carousel"
425
429
  dotListClass="posts-slider-dots"
426
430
  >
427
- {expandedBlocks.map((block) => {
431
+ {expandedBlocks.map((block, index) => {
428
432
  const controlBlockId = block.__iteratorParentId || block.id;
429
433
  const isIteratorClone = Boolean(block.__fromIterator);
434
+ const renderedBlock =
435
+ index === 0 ? applyFirstSlideImageLoading(block) : block;
430
436
 
431
437
  return (
432
438
  <div
@@ -437,7 +443,7 @@ const PostsSliderSection: React.FC<PostsSliderSectionProps> = ({
437
443
  }}
438
444
  >
439
445
  <ThemeBlock
440
- block={block}
446
+ block={renderedBlock}
441
447
  placeholderId={placeholderId}
442
448
  sectionId={section.id}
443
449
  isDesigner={isDesigner && !isIteratorClone}
@@ -1,6 +1,33 @@
1
1
  import React from 'react';
2
+ import type { Block as ThemeBlockData } from '../theme-block';
3
+
2
4
  export * from './publish-window';
3
5
 
6
+ export type ImageLoadingStrategy = 'eager' | 'lazy';
7
+ export type ImageFetchPriority = 'high' | 'auto' | 'low';
8
+
9
+ export const IMAGE_LOADING_STRATEGIES: ImageLoadingStrategy[] = [
10
+ 'eager',
11
+ 'lazy'
12
+ ];
13
+ export const IMAGE_FETCH_PRIORITIES: ImageFetchPriority[] = [
14
+ 'high',
15
+ 'auto',
16
+ 'low'
17
+ ];
18
+
19
+ const FIRST_SLIDE_IMAGE_LOADING = {
20
+ loadingStrategy: 'eager',
21
+ priority: 'high'
22
+ } as const;
23
+
24
+ const hasOwnProperty = (value: unknown, key: string) =>
25
+ Boolean(
26
+ value &&
27
+ typeof value === 'object' &&
28
+ Object.prototype.hasOwnProperty.call(value, key)
29
+ );
30
+
4
31
  type ThemeSettings = Record<string, unknown> | null;
5
32
 
6
33
  const THEME_CSS_VARIABLE_MAP: Record<string, string> = {
@@ -160,6 +187,51 @@ export const getResponsiveValue = (
160
187
  return value;
161
188
  };
162
189
 
190
+ export const resolveOption = <T extends string>(
191
+ value: unknown,
192
+ currentBreakpoint: string,
193
+ fallback: T,
194
+ allowedValues: T[]
195
+ ): T => {
196
+ const candidate = getResponsiveValue(value, currentBreakpoint, fallback);
197
+
198
+ if (typeof candidate === 'string' && allowedValues.includes(candidate as T)) {
199
+ return candidate as T;
200
+ }
201
+
202
+ return fallback;
203
+ };
204
+
205
+ export const applyFirstSlideImageLoading = (
206
+ slideBlock: ThemeBlockData
207
+ ): ThemeBlockData => {
208
+ const applyToBlock = (candidate: ThemeBlockData): ThemeBlockData => {
209
+ const nextChildren = candidate.blocks?.map(applyToBlock);
210
+
211
+ if (candidate.type !== 'image') {
212
+ return nextChildren ? { ...candidate, blocks: nextChildren } : candidate;
213
+ }
214
+
215
+ const properties = candidate.properties || {};
216
+
217
+ return {
218
+ ...candidate,
219
+ properties: {
220
+ ...properties,
221
+ ...(!hasOwnProperty(properties, 'loadingStrategy')
222
+ ? { loadingStrategy: FIRST_SLIDE_IMAGE_LOADING.loadingStrategy }
223
+ : {}),
224
+ ...(!hasOwnProperty(properties, 'priority')
225
+ ? { priority: FIRST_SLIDE_IMAGE_LOADING.priority }
226
+ : {})
227
+ },
228
+ ...(nextChildren ? { blocks: nextChildren } : {})
229
+ };
230
+ };
231
+
232
+ return applyToBlock(slideBlock);
233
+ };
234
+
163
235
  const kebabToCamel = (str: string): string => {
164
236
  return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
165
237
  };
@@ -756,9 +828,7 @@ const collectBlockCSS = (
756
828
 
757
829
  blocks.forEach((block) => {
758
830
  if (block.styles && Object.keys(block.styles).length > 0) {
759
- const selector = `[data-block-id="${
760
- block.styleSourceId || block.id
761
- }"]`;
831
+ const selector = `[data-block-id="${block.styleSourceId || block.id}"]`;
762
832
  css += generateElementCSS(selector, block.styles, targetBreakpoint);
763
833
  }
764
834