@doyourjob/gravity-ui-page-constructor 5.31.282 → 5.31.284

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.
Files changed (37) hide show
  1. package/build/cjs/blocks/Header/Header.css +18 -1
  2. package/build/cjs/blocks/Header/Header.js +21 -3
  3. package/build/cjs/blocks/Header/schema.d.ts +18 -0
  4. package/build/cjs/blocks/Header/schema.js +3 -0
  5. package/build/cjs/blocks/Header/useAdaptiveUnicornBackground.d.ts +13 -0
  6. package/build/cjs/blocks/Header/useAdaptiveUnicornBackground.js +206 -0
  7. package/build/cjs/blocks/HeaderSlider/schema.d.ts +9 -0
  8. package/build/cjs/components/Media/Media.js +3 -2
  9. package/build/cjs/components/TextNode/TextNode.css +2 -0
  10. package/build/cjs/components/TextNode/TextNode.d.ts +12 -0
  11. package/build/cjs/components/TextNode/TextNode.js +16 -0
  12. package/build/cjs/components/VideoBlock/VideoBlock.css +8 -0
  13. package/build/cjs/components/VideoBlock/VideoBlock.d.ts +1 -0
  14. package/build/cjs/components/VideoBlock/VideoBlock.js +5 -3
  15. package/build/cjs/models/constructor-items/blocks.d.ts +3 -0
  16. package/build/cjs/models/constructor-items/common.d.ts +1 -0
  17. package/build/esm/blocks/Header/Header.css +18 -1
  18. package/build/esm/blocks/Header/Header.js +21 -3
  19. package/build/esm/blocks/Header/schema.d.ts +18 -0
  20. package/build/esm/blocks/Header/schema.js +3 -0
  21. package/build/esm/blocks/Header/useAdaptiveUnicornBackground.d.ts +13 -0
  22. package/build/esm/blocks/Header/useAdaptiveUnicornBackground.js +202 -0
  23. package/build/esm/blocks/HeaderSlider/schema.d.ts +9 -0
  24. package/build/esm/components/Media/Media.js +3 -2
  25. package/build/esm/components/TextNode/TextNode.css +2 -0
  26. package/build/esm/components/TextNode/TextNode.d.ts +13 -0
  27. package/build/esm/components/TextNode/TextNode.js +14 -0
  28. package/build/esm/components/VideoBlock/VideoBlock.css +8 -0
  29. package/build/esm/components/VideoBlock/VideoBlock.d.ts +1 -0
  30. package/build/esm/components/VideoBlock/VideoBlock.js +5 -3
  31. package/build/esm/models/constructor-items/blocks.d.ts +3 -0
  32. package/build/esm/models/constructor-items/common.d.ts +1 -0
  33. package/package.json +1 -1
  34. package/schema/index.js +1 -1
  35. package/server/models/constructor-items/blocks.d.ts +3 -0
  36. package/server/models/constructor-items/common.d.ts +1 -0
  37. package/widget/index.js +1 -1
@@ -246,7 +246,7 @@ unpredictable css rules order in build */
246
246
  z-index: 5;
247
247
  }
248
248
  @media (max-width: 769px) {
249
- .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_unicorn, .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_media.pc-header-block__background_unicorn {
249
+ .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_unicorn:not(.pc-header-block__background_unicorn-fallback), .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_media.pc-header-block__background_unicorn:not(.pc-header-block__background_unicorn-fallback) {
250
250
  display: none;
251
251
  }
252
252
  }
@@ -263,6 +263,23 @@ unpredictable css rules order in build */
263
263
  width: 100%;
264
264
  background-color: var(--g-color-base-background);
265
265
  }
266
+ .pc-header-block__fallback {
267
+ position: absolute;
268
+ z-index: 1;
269
+ inset: 0;
270
+ pointer-events: none;
271
+ opacity: 0;
272
+ transition: opacity 0.3s ease;
273
+ }
274
+ .pc-header-block__fallback_visible {
275
+ opacity: 1;
276
+ }
277
+ .pc-header-block__fallback-image {
278
+ display: block;
279
+ width: 100%;
280
+ height: 100%;
281
+ object-fit: cover;
282
+ }
266
283
  .pc-header-block__overtitle {
267
284
  font-size: var(--g-text-body-3-font-size, var(--pc-text-body-3-font-size));
268
285
  line-height: var(--g-text-body-3-line-height, var(--pc-text-body-3-line-height));
@@ -20,6 +20,7 @@ const HeaderStock_1 = tslib_1.__importDefault(require("./HeaderStock/HeaderStock
20
20
  const HeaderTag_1 = tslib_1.__importDefault(require("./HeaderTag/HeaderTag"));
21
21
  const HeaderTags_1 = tslib_1.__importDefault(require("./HeaderTags/HeaderTags"));
22
22
  const SwitchingTtitle_1 = tslib_1.__importDefault(require("./SwitchingTitle/SwitchingTtitle"));
23
+ const useAdaptiveUnicornBackground_1 = require("./useAdaptiveUnicornBackground");
23
24
  const utils_3 = require("./utils");
24
25
  const b = (0, utils_2.block)('header-block');
25
26
  const Background = ({ background, isMobile }) => {
@@ -31,7 +32,7 @@ const Background = ({ background, isMobile }) => {
31
32
  const FullWidthBackground = ({ background }) => (react_1.default.createElement("div", { className: b('background', { ['full-width']: true }), style: { backgroundColor: background === null || background === void 0 ? void 0 : background.color } }));
32
33
  // eslint-disable-next-line complexity
33
34
  const HeaderBlock = (props) => {
34
- const { title, switchingTitle, topTags, bottomTags, overtitle, description, buttons, stock, stockPrice, stockShares, image, video, width = 's', imageSize, offset = 'default', background, theme: textTheme = 'light', verticalOffset = 'm', verticalOffsetTop, verticalOffsetBottom, className, breadcrumbs, status, renderTitle, children, mediaView = 'full', backgroundEffect, headerSpace, backLink, unicorn, unicornSdkUrl, } = props;
35
+ const { title, switchingTitle, topTags, bottomTags, overtitle, description, buttons, stock, stockPrice, stockShares, image, video, width = 's', imageSize, offset = 'default', background, theme: textTheme = 'light', verticalOffset = 'm', verticalOffsetTop, verticalOffsetBottom, className, breadcrumbs, status, renderTitle, children, mediaView = 'full', backgroundEffect, headerSpace, backLink, unicorn, unicornSdkUrl, unicornFallbackImage, unicornFallbackImageMobile, forceMobileImage = true, } = props;
35
36
  const isMobile = (0, react_1.useContext)(mobileContext_1.MobileContext);
36
37
  const { backButton, blockTag } = (0, react_1.useContext)(headerContext_1.HeaderContext);
37
38
  const theme = (0, theme_1.useTheme)();
@@ -43,6 +44,17 @@ const HeaderBlock = (props) => {
43
44
  const imageThemed = (0, react_1.useMemo)(() => image && (0, utils_2.getThemedValue)(image, theme), [image, theme]);
44
45
  const videoThemed = (0, react_1.useMemo)(() => video && (0, utils_2.getThemedValue)(video, theme), [theme, video]);
45
46
  const fullWidth = (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.fullWidth) || (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.fullWidthMedia);
47
+ const fallbackImage = isMobile
48
+ ? unicornFallbackImageMobile || unicornFallbackImage
49
+ : unicornFallbackImage || unicornFallbackImageMobile;
50
+ const forceFallbackOnMobile = isMobile && forceMobileImage && Boolean(fallbackImage);
51
+ const { backgroundRef, handleFallbackImageLoad, handleFallbackTransitionEnd, handleSceneLoad, isFallbackVisible, shouldMountScene, shouldPlayScene, showFallback, } = (0, useAdaptiveUnicornBackground_1.useAdaptiveUnicornBackground)({
52
+ enabled: Boolean(unicorn) && !forceFallbackOnMobile,
53
+ fallbackAvailable: Boolean(fallbackImage),
54
+ });
55
+ const showFallbackImage = forceFallbackOnMobile || showFallback;
56
+ const isFallbackImageVisible = forceFallbackOnMobile || isFallbackVisible;
57
+ const shouldRenderScene = !forceFallbackOnMobile && shouldMountScene;
46
58
  const titleId = (0, uikit_1.useUniqId)();
47
59
  const headerRef = (0, react_1.useRef)(null);
48
60
  const hasBackground = Boolean(backgroundThemed || backgroundEffect);
@@ -72,8 +84,14 @@ const HeaderBlock = (props) => {
72
84
  backgroundThemed && fullWidth && react_1.default.createElement(FullWidthBackground, { background: backgroundThemed }),
73
85
  backgroundThemed && react_1.default.createElement(Background, { background: backgroundThemed, isMobile: isMobile }),
74
86
  backgroundEffect && backgroundEffect.firstSrc && backgroundEffect.secondSrc && (react_1.default.createElement(components_1.BackgroundEffect, Object.assign({}, backgroundEffect, { attachRef: headerRef }))),
75
- unicorn && (react_1.default.createElement("div", { className: b('background', { unicorn: true }) },
76
- react_1.default.createElement(components_1.UnicornScene, { jsonFilePath: unicorn, sdkUrl: unicornSdkUrl }))),
87
+ unicorn && (react_1.default.createElement("div", { ref: backgroundRef, className: b('background', {
88
+ unicorn: true,
89
+ 'unicorn-fallback': showFallbackImage,
90
+ }) },
91
+ showFallbackImage && fallbackImage && (react_1.default.createElement("picture", { className: b('fallback', { visible: isFallbackImageVisible }), onTransitionEnd: handleFallbackTransitionEnd },
92
+ unicornFallbackImageMobile && (react_1.default.createElement("source", { media: "(max-width: 768px)", srcSet: unicornFallbackImageMobile })),
93
+ react_1.default.createElement("img", { alt: "", "aria-hidden": "true", className: b('fallback-image'), onLoad: handleFallbackImageLoad, src: fallbackImage }))),
94
+ shouldRenderScene && (react_1.default.createElement(components_1.UnicornScene, { jsonFilePath: unicorn, onLoad: handleSceneLoad, play: shouldPlayScene, sdkUrl: unicornSdkUrl })))),
77
95
  react_1.default.createElement(grid_1.Grid, { containerClass: b('container-fluid') },
78
96
  react_1.default.createElement(Breadcrumbs_1.default, { breadcrumbs: breadcrumbs, theme: textTheme }),
79
97
  react_1.default.createElement(BackButton_1.default, { backButton: backButtonItem, theme: textTheme }),
@@ -1078,6 +1078,15 @@ export declare const HeaderProperties: {
1078
1078
  unicornSdkUrl: {
1079
1079
  type: string;
1080
1080
  };
1081
+ unicornFallbackImage: {
1082
+ type: string;
1083
+ };
1084
+ unicornFallbackImageMobile: {
1085
+ type: string;
1086
+ };
1087
+ forceMobileImage: {
1088
+ type: string;
1089
+ };
1081
1090
  breadcrumbs: {
1082
1091
  type: string;
1083
1092
  additionalProperties: boolean;
@@ -1948,6 +1957,15 @@ export declare const HeaderBlock: {
1948
1957
  unicornSdkUrl: {
1949
1958
  type: string;
1950
1959
  };
1960
+ unicornFallbackImage: {
1961
+ type: string;
1962
+ };
1963
+ unicornFallbackImageMobile: {
1964
+ type: string;
1965
+ };
1966
+ forceMobileImage: {
1967
+ type: string;
1968
+ };
1951
1969
  breadcrumbs: {
1952
1970
  type: string;
1953
1971
  additionalProperties: boolean;
@@ -148,6 +148,9 @@ exports.HeaderProperties = {
148
148
  },
149
149
  unicorn: { type: 'string' },
150
150
  unicornSdkUrl: { type: 'string' },
151
+ unicornFallbackImage: { type: 'string' },
152
+ unicornFallbackImageMobile: { type: 'string' },
153
+ forceMobileImage: { type: 'boolean' },
151
154
  breadcrumbs: {
152
155
  type: 'object',
153
156
  additionalProperties: false,
@@ -0,0 +1,13 @@
1
+ export declare function useAdaptiveUnicornBackground({ enabled, fallbackAvailable, }: {
2
+ enabled: boolean;
3
+ fallbackAvailable: boolean;
4
+ }): {
5
+ backgroundRef: import("react").RefObject<HTMLDivElement>;
6
+ handleFallbackImageLoad: () => void;
7
+ handleFallbackTransitionEnd: () => void;
8
+ handleSceneLoad: () => void;
9
+ isFallbackVisible: boolean;
10
+ shouldMountScene: boolean;
11
+ shouldPlayScene: boolean;
12
+ showFallback: boolean;
13
+ };
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useAdaptiveUnicornBackground = void 0;
4
+ const react_1 = require("react");
5
+ const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)';
6
+ const LOW_DEVICE_MEMORY_GB = 4;
7
+ const LOW_HARDWARE_CONCURRENCY = 4;
8
+ const SLOW_EFFECTIVE_TYPES = new Set(['slow-2g', '2g']);
9
+ const FRAME_WARMUP_COUNT = 5;
10
+ const SLOW_FRAME_MS = 50;
11
+ const SLOW_FRAME_LIMIT = 8;
12
+ const LONG_TASK_MS = 100;
13
+ const LONG_TASK_LIMIT = 2;
14
+ const FALLBACK_FADE_TIMEOUT_MS = 500;
15
+ function prefersStaticBackground() {
16
+ var _a;
17
+ if (typeof window === 'undefined') {
18
+ return false;
19
+ }
20
+ const navigatorWithSignals = navigator;
21
+ const connection = navigatorWithSignals.connection;
22
+ return (((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, REDUCED_MOTION_QUERY).matches) ||
23
+ (connection === null || connection === void 0 ? void 0 : connection.saveData) === true ||
24
+ ((connection === null || connection === void 0 ? void 0 : connection.effectiveType) ? SLOW_EFFECTIVE_TYPES.has(connection.effectiveType) : false) ||
25
+ (navigatorWithSignals.deviceMemory
26
+ ? navigatorWithSignals.deviceMemory <= LOW_DEVICE_MEMORY_GB
27
+ : false) ||
28
+ (navigator.hardwareConcurrency
29
+ ? navigator.hardwareConcurrency <= LOW_HARDWARE_CONCURRENCY
30
+ : false));
31
+ }
32
+ function supportsLongTaskObserver() {
33
+ var _a;
34
+ return (typeof PerformanceObserver !== 'undefined' &&
35
+ ((_a = PerformanceObserver.supportedEntryTypes) === null || _a === void 0 ? void 0 : _a.includes('longtask')));
36
+ }
37
+ function isFallbackShortcut(event) {
38
+ return (event.altKey && event.shiftKey && (event.code === 'KeyU' || event.key.toLowerCase() === 'u'));
39
+ }
40
+ function useAdaptiveUnicornBackground({ enabled, fallbackAvailable, }) {
41
+ const backgroundRef = (0, react_1.useRef)(null);
42
+ const [mode, setMode] = (0, react_1.useState)('checking');
43
+ const [isNearViewport, setIsNearViewport] = (0, react_1.useState)(false);
44
+ const [fallbackFadeStarted, setFallbackFadeStarted] = (0, react_1.useState)(false);
45
+ const [fallbackImageLoaded, setFallbackImageLoaded] = (0, react_1.useState)(false);
46
+ const [sceneLoaded, setSceneLoaded] = (0, react_1.useState)(false);
47
+ const degradeBackground = (0, react_1.useCallback)(() => {
48
+ setMode((currentMode) => {
49
+ if (!fallbackAvailable) {
50
+ return 'paused';
51
+ }
52
+ return currentMode === 'animation' && isNearViewport ? 'fallback-fading' : 'fallback';
53
+ });
54
+ }, [fallbackAvailable, isNearViewport]);
55
+ const finishFallbackFade = (0, react_1.useCallback)(() => {
56
+ setMode((currentMode) => (currentMode === 'fallback-fading' ? 'fallback' : currentMode));
57
+ }, []);
58
+ const handleFallbackImageLoad = (0, react_1.useCallback)(() => {
59
+ if (mode === 'fallback-fading') {
60
+ setFallbackImageLoaded(true);
61
+ }
62
+ }, [mode]);
63
+ const handleSceneLoad = (0, react_1.useCallback)(() => {
64
+ setSceneLoaded(true);
65
+ }, []);
66
+ (0, react_1.useEffect)(() => {
67
+ setSceneLoaded(false);
68
+ setIsNearViewport(false);
69
+ if (!enabled) {
70
+ setMode('paused');
71
+ return;
72
+ }
73
+ if (prefersStaticBackground()) {
74
+ setMode(fallbackAvailable ? 'fallback' : 'paused');
75
+ return;
76
+ }
77
+ setMode('animation');
78
+ }, [enabled, fallbackAvailable]);
79
+ (0, react_1.useEffect)(() => {
80
+ if (!enabled) {
81
+ return undefined;
82
+ }
83
+ const handleKeyDown = (event) => {
84
+ if (isFallbackShortcut(event)) {
85
+ event.preventDefault();
86
+ degradeBackground();
87
+ }
88
+ };
89
+ document.addEventListener('keydown', handleKeyDown);
90
+ return () => document.removeEventListener('keydown', handleKeyDown);
91
+ }, [degradeBackground, enabled]);
92
+ (0, react_1.useEffect)(() => {
93
+ if (mode !== 'fallback-fading') {
94
+ setFallbackFadeStarted(false);
95
+ setFallbackImageLoaded(false);
96
+ }
97
+ return undefined;
98
+ }, [mode]);
99
+ (0, react_1.useEffect)(() => {
100
+ if (mode !== 'fallback-fading' || !fallbackImageLoaded) {
101
+ return undefined;
102
+ }
103
+ if (typeof window === 'undefined' || !window.requestAnimationFrame) {
104
+ setFallbackFadeStarted(true);
105
+ return undefined;
106
+ }
107
+ const frameId = window.requestAnimationFrame(() => setFallbackFadeStarted(true));
108
+ return () => { var _a; return (_a = window.cancelAnimationFrame) === null || _a === void 0 ? void 0 : _a.call(window, frameId); };
109
+ }, [fallbackImageLoaded, mode]);
110
+ (0, react_1.useEffect)(() => {
111
+ if (mode !== 'fallback-fading' || !fallbackFadeStarted) {
112
+ return undefined;
113
+ }
114
+ const fallbackTimer = setTimeout(finishFallbackFade, FALLBACK_FADE_TIMEOUT_MS);
115
+ return () => clearTimeout(fallbackTimer);
116
+ }, [fallbackFadeStarted, finishFallbackFade, mode]);
117
+ (0, react_1.useEffect)(() => {
118
+ if (!enabled || mode !== 'animation') {
119
+ return undefined;
120
+ }
121
+ const target = backgroundRef.current;
122
+ if (!target || typeof IntersectionObserver === 'undefined') {
123
+ setIsNearViewport(true);
124
+ return undefined;
125
+ }
126
+ const observer = new IntersectionObserver(([entry]) => {
127
+ setIsNearViewport(entry.isIntersecting);
128
+ if (!entry.isIntersecting) {
129
+ setSceneLoaded(false);
130
+ }
131
+ }, { rootMargin: '300px 0px', threshold: 0 });
132
+ observer.observe(target);
133
+ return () => observer.disconnect();
134
+ }, [enabled, mode]);
135
+ (0, react_1.useEffect)(() => {
136
+ if (mode !== 'animation' ||
137
+ !isNearViewport ||
138
+ !sceneLoaded ||
139
+ typeof window === 'undefined' ||
140
+ !window.requestAnimationFrame ||
141
+ !window.cancelAnimationFrame) {
142
+ return undefined;
143
+ }
144
+ let frameId = 0;
145
+ let previousTime;
146
+ let measuredFrames = 0;
147
+ let slowFrames = 0;
148
+ let longTasks = 0;
149
+ let stopped = false;
150
+ let observer;
151
+ const degradeOnce = () => {
152
+ if (stopped) {
153
+ return;
154
+ }
155
+ stopped = true;
156
+ degradeBackground();
157
+ };
158
+ const tick = (time) => {
159
+ if (stopped) {
160
+ return;
161
+ }
162
+ if (previousTime !== undefined) {
163
+ measuredFrames += 1;
164
+ if (measuredFrames > FRAME_WARMUP_COUNT && time - previousTime > SLOW_FRAME_MS) {
165
+ slowFrames += 1;
166
+ if (slowFrames >= SLOW_FRAME_LIMIT) {
167
+ degradeOnce();
168
+ return;
169
+ }
170
+ }
171
+ }
172
+ previousTime = time;
173
+ frameId = window.requestAnimationFrame(tick);
174
+ };
175
+ frameId = window.requestAnimationFrame(tick);
176
+ if (supportsLongTaskObserver()) {
177
+ observer = new PerformanceObserver((entries) => {
178
+ entries.getEntries().forEach((entry) => {
179
+ if (entry.duration >= LONG_TASK_MS) {
180
+ longTasks += 1;
181
+ }
182
+ });
183
+ if (longTasks >= LONG_TASK_LIMIT) {
184
+ degradeOnce();
185
+ }
186
+ });
187
+ observer.observe({ entryTypes: ['longtask'] });
188
+ }
189
+ return () => {
190
+ stopped = true;
191
+ window.cancelAnimationFrame(frameId);
192
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
193
+ };
194
+ }, [degradeBackground, isNearViewport, mode, sceneLoaded]);
195
+ return {
196
+ backgroundRef,
197
+ handleFallbackImageLoad,
198
+ handleFallbackTransitionEnd: finishFallbackFade,
199
+ handleSceneLoad,
200
+ isFallbackVisible: mode === 'fallback' || (mode === 'fallback-fading' && fallbackFadeStarted),
201
+ shouldMountScene: (mode === 'animation' && isNearViewport) || mode === 'fallback-fading',
202
+ shouldPlayScene: (mode === 'animation' && isNearViewport) || mode === 'fallback-fading',
203
+ showFallback: (mode === 'fallback-fading' || mode === 'fallback') && fallbackAvailable,
204
+ };
205
+ }
206
+ exports.useAdaptiveUnicornBackground = useAdaptiveUnicornBackground;
@@ -769,6 +769,15 @@ export declare const HeaderSliderBlock: {
769
769
  unicornSdkUrl: {
770
770
  type: string;
771
771
  };
772
+ unicornFallbackImage: {
773
+ type: string;
774
+ };
775
+ unicornFallbackImageMobile: {
776
+ type: string;
777
+ };
778
+ forceMobileImage: {
779
+ type: string;
780
+ };
772
781
  breadcrumbs: {
773
782
  type: string;
774
783
  additionalProperties: boolean;
@@ -14,7 +14,7 @@ const Image_1 = tslib_1.__importDefault(require("./Image/Image"));
14
14
  const Video_1 = tslib_1.__importDefault(require("./Video/Video"));
15
15
  const b = (0, utils_1.block)('Media');
16
16
  const Media = (props) => {
17
- const { animated, image, video, youtube, videoIframe, dataLens, color, height, previewImg, previewVideo, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, playButtonCorner, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, videoMicrodata, } = props;
17
+ const { animated, image, video, youtube, videoIframe, dataLens, color, height, previewImg, previewVideo, parallax = false, fullscreen, analyticsEvents, className, imageClassName, videoClassName, youtubeClassName, disableImageSliderForArrayInput, playVideo = true, isBackground, playButton, playButtonCorner, playButtonText, customBarControlsClassName, qa, ratio, autoplay, onImageLoad, iframe, margins, videoMicrodata, } = props;
18
18
  const [hasVideoFallback, setHasVideoFallback] = (0, react_1.useState)(false);
19
19
  const { microdata } = (0, react_1.useContext)(innerContext_1.InnerContext);
20
20
  const qaAttributes = (0, utils_1.getQaAttrubutes)(qa, 'video');
@@ -46,7 +46,7 @@ const Media = (props) => {
46
46
  }
47
47
  }
48
48
  if (youtube || videoIframe) {
49
- result = (react_1.default.createElement(VideoBlock_1.default, { className: b('youtube', youtubeClassName), record: youtube, videoIframe: videoIframe, attributes: { color: 'white', rel: '0' }, previewImg: previewImg, previewVideo: previewVideo, playButtonCorner: playButtonCorner, height: height, ratio: ratio, fullscreen: fullscreen, analyticsEvents: analyticsEvents, autoplay: autoplay, onImageLoad: onImageLoad }));
49
+ result = (react_1.default.createElement(VideoBlock_1.default, { className: b('youtube', youtubeClassName), record: youtube, videoIframe: videoIframe, attributes: { color: 'white', rel: '0' }, previewImg: previewImg, previewVideo: previewVideo, playButtonCorner: playButtonCorner, playButtonText: playButtonText, height: height, ratio: ratio, fullscreen: fullscreen, analyticsEvents: analyticsEvents, autoplay: autoplay, onImageLoad: onImageLoad }));
50
50
  }
51
51
  if (dataLens) {
52
52
  result = react_1.default.createElement(DataLens_1.default, { dataLens: dataLens });
@@ -83,6 +83,7 @@ const Media = (props) => {
83
83
  ratio,
84
84
  youtubeClassName,
85
85
  playButtonCorner,
86
+ playButtonText,
86
87
  autoplay,
87
88
  margins,
88
89
  ]);
@@ -0,0 +1,2 @@
1
+ /* use this for style redefinitions to awoid problems with
2
+ unpredictable css rules order in build */
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export interface TextNodeProps {
3
+ children?: string;
4
+ tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div';
5
+ }
6
+ declare const TextNode: ({ children, tag }: TextNodeProps) => React.DetailedReactHTMLElement<{
7
+ dangerouslySetInnerHTML: {
8
+ __html: string;
9
+ };
10
+ className: string;
11
+ }, HTMLElement> | null;
12
+ export default TextNode;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const react_1 = tslib_1.__importDefault(require("react"));
5
+ const utils_1 = require("../../utils");
6
+ const b = (0, utils_1.block)('tag');
7
+ const TextNode = ({ children, tag = 'div' }) => {
8
+ if (!children) {
9
+ return null;
10
+ }
11
+ return react_1.default.createElement(tag, {
12
+ dangerouslySetInnerHTML: { __html: children },
13
+ className: b(),
14
+ });
15
+ };
16
+ exports.default = TextNode;
@@ -57,6 +57,14 @@ unpredictable css rules order in build */
57
57
  width: 44px;
58
58
  height: 44px;
59
59
  }
60
+ .pc-VideoBlock__button_text {
61
+ height: 61px;
62
+ width: auto;
63
+ border-radius: 16px;
64
+ font-size: 24px;
65
+ line-height: 35px;
66
+ padding: 0 28px;
67
+ }
60
68
  .pc-VideoBlock__icon {
61
69
  margin-left: 1px;
62
70
  }
@@ -16,6 +16,7 @@ export interface VideoBlockProps extends AnalyticsEventsBase {
16
16
  previewVideo?: string;
17
17
  playButton?: React.ReactNode;
18
18
  playButtonCorner?: boolean;
19
+ playButtonText?: string;
19
20
  playButtonId?: string;
20
21
  height?: number;
21
22
  ratio?: number | 'auto';
@@ -49,7 +49,7 @@ function getHeight(width, ratio) {
49
49
  }
50
50
  exports.getHeight = getHeight;
51
51
  const VideoBlock = (props) => {
52
- const { stream, record, videoIframe, attributes, className, id, previewImg, previewVideo, playButton, playButtonCorner, playButtonId, height, ratio, fullscreen, analyticsEvents, autoplay, onImageLoad, } = props;
52
+ const { stream, record, videoIframe, attributes, className, id, previewImg, previewVideo, playButton, playButtonCorner, playButtonText, playButtonId, height, ratio, fullscreen, analyticsEvents, autoplay, onImageLoad, } = props;
53
53
  const handleAnalytics = (0, useAnalytics_1.useAnalytics)(common_1.DefaultEventNames.VideoPreview);
54
54
  const src = videoIframe ? videoIframe : getYoutubeVideoSrc(stream, record);
55
55
  const ref = (0, react_1.useRef)(null);
@@ -110,7 +110,9 @@ const VideoBlock = (props) => {
110
110
  iframeContent,
111
111
  previewImg && !hidePreview && !fullscreen && (react_1.default.createElement("div", { className: b('preview'), onClick: onPreviewClick, onKeyDown: onPreviewKeyDown, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, role: "button", tabIndex: 0, "aria-labelledby": playButton ? playButtonId : buttonId },
112
112
  isHovered && previewVideo ? (react_1.default.createElement("video", { src: previewVideo, className: b('video'), autoPlay: true, muted: true, loop: true, playsInline: true })) : (react_1.default.createElement(Image_1.default, { src: previewImg, className: b('image'), containerClassName: b('image-wrapper'), onLoad: onImageLoad })),
113
- playButton || (react_1.default.createElement("button", { title: "Play", id: buttonId, className: b('button', { corner: playButtonCorner }) },
114
- react_1.default.createElement(uikit_1.Icon, { className: b('icon'), data: icons_1.PlayFill, size: playButtonCorner ? 16 : 24 })))))));
113
+ playButton || (react_1.default.createElement("button", { title: "Play", id: buttonId, className: b('button', {
114
+ corner: playButtonCorner,
115
+ text: Boolean(playButtonText),
116
+ }) }, playButtonText ? (react_1.default.createElement("div", { className: b('button-text') }, playButtonText)) : (react_1.default.createElement(uikit_1.Icon, { className: b('icon'), data: icons_1.PlayFill, size: playButtonCorner ? 16 : 24 }))))))));
115
117
  };
116
118
  exports.default = VideoBlock;
@@ -228,6 +228,9 @@ export interface HeaderBlockProps {
228
228
  theme?: 'light' | 'dark';
229
229
  unicorn?: string;
230
230
  unicornSdkUrl?: string;
231
+ unicornFallbackImage?: string;
232
+ unicornFallbackImageMobile?: string;
233
+ forceMobileImage?: boolean;
231
234
  verticalOffset?: '0' | 's' | 'm' | 'l' | 'xl';
232
235
  verticalOffsetTop?: 's' | 'm' | 'l' | 'xl';
233
236
  verticalOffsetBottom?: 's' | 'm' | 'l' | 'xl';
@@ -196,6 +196,7 @@ export interface MediaComponentYoutubeProps {
196
196
  previewImg?: string;
197
197
  previewVideo?: string;
198
198
  playButtonCorner?: boolean;
199
+ playButtonText?: string;
199
200
  fullscreen?: boolean;
200
201
  }
201
202
  export interface MediaComponentImageProps {
@@ -246,7 +246,7 @@ unpredictable css rules order in build */
246
246
  z-index: 5;
247
247
  }
248
248
  @media (max-width: 769px) {
249
- .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_unicorn, .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_media.pc-header-block__background_unicorn {
249
+ .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_unicorn:not(.pc-header-block__background_unicorn-fallback), .pc-header-block_has-background .pc-header-block__background.pc-header-block__background_media.pc-header-block__background_unicorn:not(.pc-header-block__background_unicorn-fallback) {
250
250
  display: none;
251
251
  }
252
252
  }
@@ -263,6 +263,23 @@ unpredictable css rules order in build */
263
263
  width: 100%;
264
264
  background-color: var(--g-color-base-background);
265
265
  }
266
+ .pc-header-block__fallback {
267
+ position: absolute;
268
+ z-index: 1;
269
+ inset: 0;
270
+ pointer-events: none;
271
+ opacity: 0;
272
+ transition: opacity 0.3s ease;
273
+ }
274
+ .pc-header-block__fallback_visible {
275
+ opacity: 1;
276
+ }
277
+ .pc-header-block__fallback-image {
278
+ display: block;
279
+ width: 100%;
280
+ height: 100%;
281
+ object-fit: cover;
282
+ }
266
283
  .pc-header-block__overtitle {
267
284
  font-size: var(--g-text-body-3-font-size, var(--pc-text-body-3-font-size));
268
285
  line-height: var(--g-text-body-3-line-height, var(--pc-text-body-3-line-height));
@@ -17,6 +17,7 @@ import HeaderStock from './HeaderStock/HeaderStock';
17
17
  import HeaderTag from './HeaderTag/HeaderTag';
18
18
  import HeaderTags from './HeaderTags/HeaderTags';
19
19
  import SwitchingTitle from './SwitchingTitle/SwitchingTtitle';
20
+ import { useAdaptiveUnicornBackground } from './useAdaptiveUnicornBackground';
20
21
  import { getImageSize, getTitleSizes, titleWithImageSizes } from './utils';
21
22
  import './Header.css';
22
23
  const b = block('header-block');
@@ -29,7 +30,7 @@ const Background = ({ background, isMobile }) => {
29
30
  const FullWidthBackground = ({ background }) => (React.createElement("div", { className: b('background', { ['full-width']: true }), style: { backgroundColor: background === null || background === void 0 ? void 0 : background.color } }));
30
31
  // eslint-disable-next-line complexity
31
32
  export const HeaderBlock = (props) => {
32
- const { title, switchingTitle, topTags, bottomTags, overtitle, description, buttons, stock, stockPrice, stockShares, image, video, width = 's', imageSize, offset = 'default', background, theme: textTheme = 'light', verticalOffset = 'm', verticalOffsetTop, verticalOffsetBottom, className, breadcrumbs, status, renderTitle, children, mediaView = 'full', backgroundEffect, headerSpace, backLink, unicorn, unicornSdkUrl, } = props;
33
+ const { title, switchingTitle, topTags, bottomTags, overtitle, description, buttons, stock, stockPrice, stockShares, image, video, width = 's', imageSize, offset = 'default', background, theme: textTheme = 'light', verticalOffset = 'm', verticalOffsetTop, verticalOffsetBottom, className, breadcrumbs, status, renderTitle, children, mediaView = 'full', backgroundEffect, headerSpace, backLink, unicorn, unicornSdkUrl, unicornFallbackImage, unicornFallbackImageMobile, forceMobileImage = true, } = props;
33
34
  const isMobile = useContext(MobileContext);
34
35
  const { backButton, blockTag } = useContext(HeaderContext);
35
36
  const theme = useTheme();
@@ -41,6 +42,17 @@ export const HeaderBlock = (props) => {
41
42
  const imageThemed = useMemo(() => image && getThemedValue(image, theme), [image, theme]);
42
43
  const videoThemed = useMemo(() => video && getThemedValue(video, theme), [theme, video]);
43
44
  const fullWidth = (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.fullWidth) || (backgroundThemed === null || backgroundThemed === void 0 ? void 0 : backgroundThemed.fullWidthMedia);
45
+ const fallbackImage = isMobile
46
+ ? unicornFallbackImageMobile || unicornFallbackImage
47
+ : unicornFallbackImage || unicornFallbackImageMobile;
48
+ const forceFallbackOnMobile = isMobile && forceMobileImage && Boolean(fallbackImage);
49
+ const { backgroundRef, handleFallbackImageLoad, handleFallbackTransitionEnd, handleSceneLoad, isFallbackVisible, shouldMountScene, shouldPlayScene, showFallback, } = useAdaptiveUnicornBackground({
50
+ enabled: Boolean(unicorn) && !forceFallbackOnMobile,
51
+ fallbackAvailable: Boolean(fallbackImage),
52
+ });
53
+ const showFallbackImage = forceFallbackOnMobile || showFallback;
54
+ const isFallbackImageVisible = forceFallbackOnMobile || isFallbackVisible;
55
+ const shouldRenderScene = !forceFallbackOnMobile && shouldMountScene;
44
56
  const titleId = useUniqId();
45
57
  const headerRef = useRef(null);
46
58
  const hasBackground = Boolean(backgroundThemed || backgroundEffect);
@@ -70,8 +82,14 @@ export const HeaderBlock = (props) => {
70
82
  backgroundThemed && fullWidth && React.createElement(FullWidthBackground, { background: backgroundThemed }),
71
83
  backgroundThemed && React.createElement(Background, { background: backgroundThemed, isMobile: isMobile }),
72
84
  backgroundEffect && backgroundEffect.firstSrc && backgroundEffect.secondSrc && (React.createElement(BackgroundEffect, Object.assign({}, backgroundEffect, { attachRef: headerRef }))),
73
- unicorn && (React.createElement("div", { className: b('background', { unicorn: true }) },
74
- React.createElement(UnicornScene, { jsonFilePath: unicorn, sdkUrl: unicornSdkUrl }))),
85
+ unicorn && (React.createElement("div", { ref: backgroundRef, className: b('background', {
86
+ unicorn: true,
87
+ 'unicorn-fallback': showFallbackImage,
88
+ }) },
89
+ showFallbackImage && fallbackImage && (React.createElement("picture", { className: b('fallback', { visible: isFallbackImageVisible }), onTransitionEnd: handleFallbackTransitionEnd },
90
+ unicornFallbackImageMobile && (React.createElement("source", { media: "(max-width: 768px)", srcSet: unicornFallbackImageMobile })),
91
+ React.createElement("img", { alt: "", "aria-hidden": "true", className: b('fallback-image'), onLoad: handleFallbackImageLoad, src: fallbackImage }))),
92
+ shouldRenderScene && (React.createElement(UnicornScene, { jsonFilePath: unicorn, onLoad: handleSceneLoad, play: shouldPlayScene, sdkUrl: unicornSdkUrl })))),
75
93
  React.createElement(Grid, { containerClass: b('container-fluid') },
76
94
  React.createElement(Breadcrumbs, { breadcrumbs: breadcrumbs, theme: textTheme }),
77
95
  React.createElement(BackButton, { backButton: backButtonItem, theme: textTheme }),
@@ -1078,6 +1078,15 @@ export declare const HeaderProperties: {
1078
1078
  unicornSdkUrl: {
1079
1079
  type: string;
1080
1080
  };
1081
+ unicornFallbackImage: {
1082
+ type: string;
1083
+ };
1084
+ unicornFallbackImageMobile: {
1085
+ type: string;
1086
+ };
1087
+ forceMobileImage: {
1088
+ type: string;
1089
+ };
1081
1090
  breadcrumbs: {
1082
1091
  type: string;
1083
1092
  additionalProperties: boolean;
@@ -1948,6 +1957,15 @@ export declare const HeaderBlock: {
1948
1957
  unicornSdkUrl: {
1949
1958
  type: string;
1950
1959
  };
1960
+ unicornFallbackImage: {
1961
+ type: string;
1962
+ };
1963
+ unicornFallbackImageMobile: {
1964
+ type: string;
1965
+ };
1966
+ forceMobileImage: {
1967
+ type: string;
1968
+ };
1951
1969
  breadcrumbs: {
1952
1970
  type: string;
1953
1971
  additionalProperties: boolean;
@@ -145,6 +145,9 @@ export const HeaderProperties = {
145
145
  },
146
146
  unicorn: { type: 'string' },
147
147
  unicornSdkUrl: { type: 'string' },
148
+ unicornFallbackImage: { type: 'string' },
149
+ unicornFallbackImageMobile: { type: 'string' },
150
+ forceMobileImage: { type: 'boolean' },
148
151
  breadcrumbs: {
149
152
  type: 'object',
150
153
  additionalProperties: false,
@@ -0,0 +1,13 @@
1
+ export declare function useAdaptiveUnicornBackground({ enabled, fallbackAvailable, }: {
2
+ enabled: boolean;
3
+ fallbackAvailable: boolean;
4
+ }): {
5
+ backgroundRef: import("react").RefObject<HTMLDivElement>;
6
+ handleFallbackImageLoad: () => void;
7
+ handleFallbackTransitionEnd: () => void;
8
+ handleSceneLoad: () => void;
9
+ isFallbackVisible: boolean;
10
+ shouldMountScene: boolean;
11
+ shouldPlayScene: boolean;
12
+ showFallback: boolean;
13
+ };