@gravity-ui/page-constructor 5.26.1 → 5.27.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/build/cjs/blocks/Banner/Banner.js +2 -2
- package/build/cjs/blocks/Slider/Slider.css +1 -1
- package/build/cjs/blocks/Slider/Slider.js +83 -29
- package/build/cjs/blocks/Slider/i18n/en.json +3 -1
- package/build/cjs/blocks/Slider/i18n/index.d.ts +1 -1
- package/build/cjs/blocks/Slider/i18n/ru.json +3 -1
- package/build/cjs/blocks/Slider/utils.d.ts +10 -0
- package/build/cjs/blocks/Slider/utils.js +85 -1
- package/build/cjs/blocks/SliderNew/Arrow/Arrow.d.ts +3 -1
- package/build/cjs/blocks/SliderNew/Arrow/Arrow.js +2 -2
- package/build/cjs/blocks/SliderNew/Slider.js +20 -8
- package/build/cjs/blocks/SliderNew/i18n/en.json +3 -1
- package/build/cjs/blocks/SliderNew/i18n/index.d.ts +1 -1
- package/build/cjs/blocks/SliderNew/i18n/ru.json +3 -1
- package/build/cjs/blocks/SliderNew/useSlider.d.ts +8 -6
- package/build/cjs/blocks/SliderNew/useSlider.js +4 -2
- package/build/cjs/blocks/SliderNew/useSliderPagination.d.ts +9 -0
- package/build/cjs/blocks/SliderNew/useSliderPagination.js +36 -0
- package/build/cjs/blocks/SliderNew/utils.d.ts +2 -0
- package/build/cjs/blocks/SliderNew/utils.js +13 -1
- package/build/cjs/containers/PageConstructor/PageConstructor.js +3 -1
- package/build/cjs/context/projectSettingsContext/ProjectSettingsContext.d.ts +1 -0
- package/build/cjs/models/constructor-items/sub-blocks.d.ts +2 -1
- package/build/cjs/sub-blocks/BannerCard/BannerCard.js +3 -3
- package/build/esm/blocks/Banner/Banner.js +2 -2
- package/build/esm/blocks/Slider/Slider.css +1 -1
- package/build/esm/blocks/Slider/Slider.js +84 -30
- package/build/esm/blocks/Slider/i18n/en.json +3 -1
- package/build/esm/blocks/Slider/i18n/index.d.ts +1 -1
- package/build/esm/blocks/Slider/i18n/ru.json +3 -1
- package/build/esm/blocks/Slider/utils.d.ts +10 -0
- package/build/esm/blocks/Slider/utils.js +82 -0
- package/build/esm/blocks/SliderNew/Arrow/Arrow.d.ts +3 -1
- package/build/esm/blocks/SliderNew/Arrow/Arrow.js +2 -2
- package/build/esm/blocks/SliderNew/Slider.js +20 -8
- package/build/esm/blocks/SliderNew/i18n/en.json +3 -1
- package/build/esm/blocks/SliderNew/i18n/index.d.ts +1 -1
- package/build/esm/blocks/SliderNew/i18n/ru.json +3 -1
- package/build/esm/blocks/SliderNew/useSlider.d.ts +8 -6
- package/build/esm/blocks/SliderNew/useSlider.js +6 -3
- package/build/esm/blocks/SliderNew/useSliderPagination.d.ts +9 -0
- package/build/esm/blocks/SliderNew/useSliderPagination.js +32 -0
- package/build/esm/blocks/SliderNew/utils.d.ts +2 -0
- package/build/esm/blocks/SliderNew/utils.js +10 -0
- package/build/esm/containers/PageConstructor/PageConstructor.js +4 -2
- package/build/esm/context/projectSettingsContext/ProjectSettingsContext.d.ts +1 -0
- package/build/esm/models/constructor-items/sub-blocks.d.ts +2 -1
- package/build/esm/sub-blocks/BannerCard/BannerCard.js +3 -3
- package/package.json +4 -1
- package/server/models/constructor-items/sub-blocks.d.ts +2 -1
- package/widget/index.js +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSliderResponsiveParams = exports.DEFAULT_SLIDE_BREAKPOINTS = void 0;
|
|
3
|
+
exports.setElementAtrributes = exports.useMemoized = exports.getSliderResponsiveParams = exports.DEFAULT_SLIDE_BREAKPOINTS = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const isEqual_1 = tslib_1.__importDefault(require("lodash/isEqual"));
|
|
5
7
|
const pickBy_1 = tslib_1.__importDefault(require("lodash/pickBy"));
|
|
6
8
|
const constants_1 = require("../../constants");
|
|
7
9
|
const models_1 = require("./models");
|
|
@@ -29,3 +31,13 @@ function getSliderResponsiveParams({ contentLength, slidesToShow, mobileFullscre
|
|
|
29
31
|
}, {});
|
|
30
32
|
}
|
|
31
33
|
exports.getSliderResponsiveParams = getSliderResponsiveParams;
|
|
34
|
+
const useMemoized = (value) => {
|
|
35
|
+
const [memoizedValue, setMemoizedValue] = (0, react_1.useState)(value);
|
|
36
|
+
(0, react_1.useEffect)(() => {
|
|
37
|
+
setMemoizedValue((memoized) => value && typeof value === 'object' && (0, isEqual_1.default)(memoized, value) ? memoized : value);
|
|
38
|
+
}, [value]);
|
|
39
|
+
return memoizedValue;
|
|
40
|
+
};
|
|
41
|
+
exports.useMemoized = useMemoized;
|
|
42
|
+
const setElementAtrributes = (element, attributes) => Object.entries(attributes).forEach(([attribute, value]) => element.setAttribute(attribute, String(value)));
|
|
43
|
+
exports.setElementAtrributes = setElementAtrributes;
|
|
@@ -9,6 +9,7 @@ const RootCn_1 = tslib_1.__importDefault(require("../../components/RootCn"));
|
|
|
9
9
|
const constructor_items_1 = require("../../constructor-items");
|
|
10
10
|
const animateContext_1 = require("../../context/animateContext");
|
|
11
11
|
const innerContext_1 = require("../../context/innerContext");
|
|
12
|
+
const projectSettingsContext_1 = require("../../context/projectSettingsContext");
|
|
12
13
|
const theme_1 = require("../../context/theme");
|
|
13
14
|
const grid_1 = require("../../grid");
|
|
14
15
|
const models_1 = require("../../models");
|
|
@@ -54,7 +55,8 @@ const Constructor = (props) => {
|
|
|
54
55
|
};
|
|
55
56
|
exports.Constructor = Constructor;
|
|
56
57
|
const PageConstructor = (props) => {
|
|
57
|
-
const {
|
|
58
|
+
const { isAnimationEnabled = true } = (0, react_1.useContext)(projectSettingsContext_1.ProjectSettingsContext);
|
|
59
|
+
const { content: { animated = isAnimationEnabled } = {} } = props, rest = tslib_1.__rest(props, ["content"]);
|
|
58
60
|
return (react_1.default.createElement(animateContext_1.AnimateContext.Provider, { value: { animated } },
|
|
59
61
|
react_1.default.createElement(exports.Constructor, Object.assign({ content: props.content }, rest))));
|
|
60
62
|
};
|
|
@@ -113,11 +113,12 @@ export interface BasicCardProps extends CardBaseProps, AnalyticsEventsBase, Card
|
|
|
113
113
|
export interface BannerCardProps {
|
|
114
114
|
title: string;
|
|
115
115
|
subtitle?: string;
|
|
116
|
+
className?: string;
|
|
116
117
|
image?: ThemeSupporting<string>;
|
|
117
118
|
disableCompress?: boolean;
|
|
118
119
|
color?: ThemeSupporting<string>;
|
|
119
120
|
theme?: TextTheme;
|
|
120
|
-
button
|
|
121
|
+
button?: Pick<ButtonProps, 'text' | 'url' | 'target' | 'theme'>;
|
|
121
122
|
mediaView?: MediaView;
|
|
122
123
|
}
|
|
123
124
|
export interface MediaCardProps extends MediaProps, AnalyticsEventsBase, CardBaseProps {
|
|
@@ -8,7 +8,7 @@ const theme_1 = require("../../context/theme");
|
|
|
8
8
|
const utils_1 = require("../../utils");
|
|
9
9
|
const b = (0, utils_1.block)('banner-card');
|
|
10
10
|
const BannerCard = (props) => {
|
|
11
|
-
const { title, subtitle, button: { url, text, target, theme: buttonTheme = 'raised' }, color, theme: textTheme = 'light', image, disableCompress, mediaView = 'full', } = props;
|
|
11
|
+
const { title, subtitle, button: { url, text, target, theme: buttonTheme = 'raised' } = {}, color, theme: textTheme = 'light', image, disableCompress, mediaView = 'full', } = props;
|
|
12
12
|
const theme = (0, theme_1.useTheme)();
|
|
13
13
|
const contentStyle = {};
|
|
14
14
|
if (color) {
|
|
@@ -21,8 +21,8 @@ const BannerCard = (props) => {
|
|
|
21
21
|
react_1.default.createElement("h2", { className: b('title') },
|
|
22
22
|
react_1.default.createElement(components_1.HTML, null, title)),
|
|
23
23
|
subtitle && (react_1.default.createElement(components_1.YFMWrapper, { className: b('subtitle'), content: subtitle, modifiers: { constructor: true } }))),
|
|
24
|
-
react_1.default.createElement(components_1.RouterLink, { href: url },
|
|
25
|
-
react_1.default.createElement(components_1.Button, { className: b('button'), theme: buttonTheme, size: "xl", text: text, url: url, target: target }))),
|
|
24
|
+
url && (react_1.default.createElement(components_1.RouterLink, { href: url },
|
|
25
|
+
react_1.default.createElement(components_1.Button, { className: b('button'), theme: buttonTheme, size: "xl", text: text !== null && text !== void 0 ? text : '', url: url, target: target })))),
|
|
26
26
|
react_1.default.createElement(components_1.BackgroundImage, { className: b('image'), src: (0, utils_1.getThemedValue)(image, theme), disableCompress: disableCompress }))));
|
|
27
27
|
};
|
|
28
28
|
exports.BannerCard = BannerCard;
|
|
@@ -6,8 +6,8 @@ import { block } from '../../utils';
|
|
|
6
6
|
import './Banner.css';
|
|
7
7
|
const b = block('banner-block');
|
|
8
8
|
export const BannerBlock = (props) => {
|
|
9
|
-
const { animated } = props, bannerProps = __rest(props, ["animated"]);
|
|
10
|
-
return (React.createElement(AnimateBlock, { className: b(), animate: animated },
|
|
9
|
+
const { animated, className } = props, bannerProps = __rest(props, ["animated", "className"]);
|
|
10
|
+
return (React.createElement(AnimateBlock, { className: b(null, className), animate: animated },
|
|
11
11
|
React.createElement(BannerCard, Object.assign({}, bannerProps))));
|
|
12
12
|
};
|
|
13
13
|
export default BannerBlock;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
import { useUniqId } from '@gravity-ui/uikit';
|
|
2
3
|
import debounce from 'lodash/debounce';
|
|
3
4
|
import get from 'lodash/get';
|
|
4
5
|
import noop from 'lodash/noop';
|
|
@@ -15,7 +16,8 @@ import useFocus from '../../hooks/useFocus';
|
|
|
15
16
|
import { SliderType, } from '../../models';
|
|
16
17
|
import { block } from '../../utils';
|
|
17
18
|
import Arrow from './Arrow/Arrow';
|
|
18
|
-
import {
|
|
19
|
+
import { i18n } from './i18n';
|
|
20
|
+
import { getSliderResponsiveParams, getSlidesCountByBreakpoint, getSlidesToShowCount, getSlidesToShowWithDefaults, isFocusable, useRovingTabIndex, } from './utils';
|
|
19
21
|
import './Slider.css';
|
|
20
22
|
const b = block('SliderBlock');
|
|
21
23
|
const slick = block('slick-origin');
|
|
@@ -23,12 +25,15 @@ const DOT_WIDTH = 8;
|
|
|
23
25
|
const DOT_GAP = 16;
|
|
24
26
|
export const SliderBlock = (props) => {
|
|
25
27
|
var _a;
|
|
26
|
-
const { animated, title, description, type, anchorId, arrows = true, adaptive, autoplay
|
|
28
|
+
const { animated, title, description, type, anchorId, arrows = true, adaptive, autoplay: autoplaySpeed, dots = true, dotsClassName, disclaimer, children, className, blockClassName, lazyLoad, arrowSize, onAfterChange: handleAfterChange, onBeforeChange: handleBeforeChange, } = props;
|
|
27
29
|
const { isServer } = useContext(SSRContext);
|
|
28
30
|
const isMobile = useContext(MobileContext);
|
|
29
31
|
const [breakpoint, setBreakpoint] = useState(BREAKPOINTS.xl);
|
|
30
|
-
const
|
|
32
|
+
const sliderId = useUniqId();
|
|
33
|
+
const disclosedChildren = useMemo(() => discloseAllNestedChildren(children, sliderId), [children, sliderId]);
|
|
31
34
|
const childrenCount = disclosedChildren.length;
|
|
35
|
+
const isAutoplayEnabled = autoplaySpeed !== undefined && autoplaySpeed > 0;
|
|
36
|
+
const isUserInteractionRef = useRef(false);
|
|
32
37
|
const [slidesToShow] = useState(getSlidesToShowWithDefaults({
|
|
33
38
|
contentLength: childrenCount,
|
|
34
39
|
breakpoints: props.slidesToShow,
|
|
@@ -39,8 +44,13 @@ export const SliderBlock = (props) => {
|
|
|
39
44
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
40
45
|
const [childStyles, setChildStyles] = useState({});
|
|
41
46
|
const [slider, setSlider] = useState();
|
|
47
|
+
const prevIndexRef = useRef(0);
|
|
42
48
|
const autoplayTimeId = useRef();
|
|
43
49
|
const { hasFocus, unsetFocus } = useFocus((_a = slider === null || slider === void 0 ? void 0 : slider.innerSlider) === null || _a === void 0 ? void 0 : _a.list);
|
|
50
|
+
const asUserInteraction = (fn) => (...args) => {
|
|
51
|
+
isUserInteractionRef.current = true;
|
|
52
|
+
return fn(...args);
|
|
53
|
+
};
|
|
44
54
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
55
|
const onResize = useCallback(debounce(() => {
|
|
46
56
|
if (!slider) {
|
|
@@ -55,7 +65,7 @@ export const SliderBlock = (props) => {
|
|
|
55
65
|
}, 100), [slider, breakpoint]);
|
|
56
66
|
const scrollLastSlide = useCallback((current) => {
|
|
57
67
|
const lastSlide = childrenCount - slidesToShowCount;
|
|
58
|
-
if (
|
|
68
|
+
if (isAutoplayEnabled && lastSlide === current) {
|
|
59
69
|
// Slick doesn't support autoplay with no infinity scroll
|
|
60
70
|
autoplayTimeId.current = setTimeout(() => {
|
|
61
71
|
if (slider) {
|
|
@@ -67,9 +77,9 @@ export const SliderBlock = (props) => {
|
|
|
67
77
|
slider.slickPlay();
|
|
68
78
|
}
|
|
69
79
|
}, 500);
|
|
70
|
-
},
|
|
80
|
+
}, autoplaySpeed);
|
|
71
81
|
}
|
|
72
|
-
}, [
|
|
82
|
+
}, [autoplaySpeed, childrenCount, isAutoplayEnabled, slider, slidesToShowCount]);
|
|
73
83
|
useEffect(() => {
|
|
74
84
|
if (hasFocus && autoplayTimeId.current) {
|
|
75
85
|
clearTimeout(autoplayTimeId.current);
|
|
@@ -83,7 +93,7 @@ export const SliderBlock = (props) => {
|
|
|
83
93
|
window.addEventListener('resize', onResize, { passive: true });
|
|
84
94
|
return () => window.removeEventListener('resize', onResize);
|
|
85
95
|
}, [onResize]);
|
|
86
|
-
const handleArrowClick =
|
|
96
|
+
const handleArrowClick = (direction) => {
|
|
87
97
|
let nextIndex;
|
|
88
98
|
if (direction === 'right') {
|
|
89
99
|
nextIndex =
|
|
@@ -96,11 +106,12 @@ export const SliderBlock = (props) => {
|
|
|
96
106
|
if (slider) {
|
|
97
107
|
slider.slickGoTo(nextIndex);
|
|
98
108
|
}
|
|
99
|
-
}
|
|
109
|
+
};
|
|
100
110
|
const onBeforeChange = useCallback((current, next) => {
|
|
101
111
|
if (handleBeforeChange) {
|
|
102
112
|
handleBeforeChange(current, next);
|
|
103
113
|
}
|
|
114
|
+
prevIndexRef.current = current;
|
|
104
115
|
setCurrentIndex(Math.ceil(next));
|
|
105
116
|
}, [handleBeforeChange]);
|
|
106
117
|
const onAfterChange = useCallback((current) => {
|
|
@@ -113,16 +124,33 @@ export const SliderBlock = (props) => {
|
|
|
113
124
|
if (!hasFocus) {
|
|
114
125
|
scrollLastSlide(current);
|
|
115
126
|
}
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
if (isUserInteractionRef.current) {
|
|
128
|
+
const focusIndex = prevIndexRef.current >= current
|
|
129
|
+
? current
|
|
130
|
+
: Math.max(current, prevIndexRef.current + slidesCountByBreakpoint);
|
|
131
|
+
const firstNewSlide = document.getElementById(getSlideId(sliderId, focusIndex));
|
|
132
|
+
if (firstNewSlide) {
|
|
133
|
+
const focusableChild = Array.from(firstNewSlide.querySelectorAll('*')).find(isFocusable);
|
|
134
|
+
focusableChild === null || focusableChild === void 0 ? void 0 : focusableChild.focus();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
isUserInteractionRef.current = false;
|
|
138
|
+
}, [handleAfterChange, hasFocus, scrollLastSlide, sliderId, slidesCountByBreakpoint]);
|
|
139
|
+
const handleDotClick = (index) => {
|
|
118
140
|
const nextIndex = index > currentIndex ? index + 1 - slidesCountByBreakpoint : index;
|
|
119
141
|
if (slider) {
|
|
120
142
|
slider.slickGoTo(nextIndex);
|
|
121
143
|
}
|
|
122
|
-
}
|
|
123
|
-
const barSlidesCount = childrenCount -
|
|
144
|
+
};
|
|
145
|
+
const barSlidesCount = childrenCount - slidesCountByBreakpoint + 1;
|
|
124
146
|
const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
|
|
125
147
|
const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
|
|
148
|
+
const { getRovingItemProps, rovingListProps } = useRovingTabIndex({
|
|
149
|
+
itemCount: barSlidesCount,
|
|
150
|
+
activeIndex: currentIndex + 1,
|
|
151
|
+
firstIndex: 1,
|
|
152
|
+
uniqId: sliderId,
|
|
153
|
+
});
|
|
126
154
|
const renderBar = () => {
|
|
127
155
|
return (slidesCountByBreakpoint > 1 && (React.createElement("li", { className: b('bar'), style: {
|
|
128
156
|
left: barPosition,
|
|
@@ -133,19 +161,22 @@ export const SliderBlock = (props) => {
|
|
|
133
161
|
const renderAccessibleBar = (index) => {
|
|
134
162
|
return (
|
|
135
163
|
// To have this key differ from keys used in renderDot function, added `-accessible-bar` part
|
|
136
|
-
React.createElement(Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 0 && (React.createElement("li", { className: b('accessible-bar'), "aria-
|
|
164
|
+
React.createElement(Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 0 && (React.createElement("li", Object.assign({ className: b('accessible-bar'), role: "menuitemradio", "aria-checked": true, "aria-label": i18n('dot-label', {
|
|
165
|
+
index: currentIndex + 1,
|
|
166
|
+
count: barSlidesCount,
|
|
167
|
+
}), style: {
|
|
137
168
|
left: barPosition,
|
|
138
169
|
width: barWidth,
|
|
139
|
-
} }))));
|
|
170
|
+
} }, getRovingItemProps(currentIndex + 1))))));
|
|
140
171
|
};
|
|
141
172
|
const getCurrentSlideNumber = (index) => {
|
|
142
173
|
const currentIndexDiff = index - currentIndex;
|
|
143
174
|
let currentSlideNumber;
|
|
144
|
-
if (0 <= currentIndexDiff && currentIndexDiff <
|
|
175
|
+
if (0 <= currentIndexDiff && currentIndexDiff < slidesCountByBreakpoint) {
|
|
145
176
|
currentSlideNumber = currentIndex + 1;
|
|
146
177
|
}
|
|
147
|
-
else if (currentIndexDiff >=
|
|
148
|
-
currentSlideNumber = index -
|
|
178
|
+
else if (currentIndexDiff >= slidesCountByBreakpoint) {
|
|
179
|
+
currentSlideNumber = index - slidesCountByBreakpoint + 2;
|
|
149
180
|
}
|
|
150
181
|
else {
|
|
151
182
|
currentSlideNumber = index + 1;
|
|
@@ -154,12 +185,24 @@ export const SliderBlock = (props) => {
|
|
|
154
185
|
};
|
|
155
186
|
const isVisibleSlide = (index) => {
|
|
156
187
|
const currentIndexDiff = index - currentIndex;
|
|
157
|
-
|
|
188
|
+
const result = slidesCountByBreakpoint > 0 &&
|
|
158
189
|
0 <= currentIndexDiff &&
|
|
159
|
-
currentIndexDiff <
|
|
190
|
+
currentIndexDiff < slidesCountByBreakpoint;
|
|
191
|
+
return result;
|
|
160
192
|
};
|
|
161
193
|
const renderDot = (index) => {
|
|
162
|
-
|
|
194
|
+
const isVisible = isVisibleSlide(index);
|
|
195
|
+
const currentSlideNumber = getCurrentSlideNumber(index);
|
|
196
|
+
const rovingItemProps = isVisible ? undefined : getRovingItemProps(currentSlideNumber);
|
|
197
|
+
return (React.createElement("li", Object.assign({ key: index, className: b('dot', { active: index === currentIndex }), onClick: asUserInteraction(() => handleDotClick(index)), onKeyDown: (e) => {
|
|
198
|
+
const key = e.key.toLowerCase();
|
|
199
|
+
if (key === 'space' || key === 'enter') {
|
|
200
|
+
e.currentTarget.click();
|
|
201
|
+
}
|
|
202
|
+
}, role: "menuitemradio", "aria-checked": false, tabIndex: -1, "aria-hidden": isVisible, "aria-label": i18n('dot-label', {
|
|
203
|
+
index: currentSlideNumber,
|
|
204
|
+
count: barSlidesCount,
|
|
205
|
+
}) }, rovingItemProps)));
|
|
163
206
|
};
|
|
164
207
|
const renderNavigation = () => {
|
|
165
208
|
if (childrenCount <= slidesCountByBreakpoint || !dots || childrenCount === 1) {
|
|
@@ -170,7 +213,7 @@ export const SliderBlock = (props) => {
|
|
|
170
213
|
.map((_item, index) => renderDot(index));
|
|
171
214
|
dotsList.splice(currentIndex, 0, renderAccessibleBar(currentIndex));
|
|
172
215
|
return (React.createElement("div", { className: b('dots', dotsClassName) },
|
|
173
|
-
React.createElement("ul", { className: b('dots-list') },
|
|
216
|
+
React.createElement("ul", Object.assign({ className: b('dots-list'), role: "menu", "aria-label": i18n('pagination-label') }, rovingListProps),
|
|
174
217
|
renderBar(),
|
|
175
218
|
dotsList)));
|
|
176
219
|
};
|
|
@@ -189,17 +232,18 @@ export const SliderBlock = (props) => {
|
|
|
189
232
|
infinite: false,
|
|
190
233
|
speed: 1000,
|
|
191
234
|
adaptiveHeight: adaptive,
|
|
192
|
-
autoplay:
|
|
193
|
-
autoplaySpeed
|
|
235
|
+
autoplay: isAutoplayEnabled,
|
|
236
|
+
autoplaySpeed,
|
|
194
237
|
slidesToShow: slidesToShowCount,
|
|
195
238
|
slidesToScroll: 1,
|
|
196
239
|
responsive: getSliderResponsiveParams(slidesToShow),
|
|
197
240
|
beforeChange: onBeforeChange,
|
|
198
241
|
afterChange: onAfterChange,
|
|
199
242
|
initialSlide: 0,
|
|
200
|
-
nextArrow: React.createElement(Arrow, { type: "right", handleClick: handleArrowClick, size: arrowSize }),
|
|
201
|
-
prevArrow: React.createElement(Arrow, { type: "left", handleClick: handleArrowClick, size: arrowSize }),
|
|
243
|
+
nextArrow: (React.createElement(Arrow, { type: "right", handleClick: asUserInteraction(handleArrowClick), size: arrowSize })),
|
|
244
|
+
prevArrow: (React.createElement(Arrow, { type: "left", handleClick: asUserInteraction(handleArrowClick), size: arrowSize })),
|
|
202
245
|
lazyLoad,
|
|
246
|
+
accessibility: false,
|
|
203
247
|
};
|
|
204
248
|
return (React.createElement(OutsideClick, { onOutsideClick: isMobile ? unsetFocus : noop },
|
|
205
249
|
React.createElement(SlickSlider, Object.assign({}, settings), disclosedChildren),
|
|
@@ -219,23 +263,33 @@ export const SliderBlock = (props) => {
|
|
|
219
263
|
React.createElement(Title, { title: title, subtitle: description, className: b('header', { 'no-description': !description }) }),
|
|
220
264
|
React.createElement(AnimateBlock, { className: b('animate-slides'), animate: animated }, renderSlider()))));
|
|
221
265
|
};
|
|
266
|
+
function getSlideId(sliderId, index) {
|
|
267
|
+
return `slider-${sliderId}-child-${index}`;
|
|
268
|
+
}
|
|
222
269
|
// TODO remove this and rework PriceDetailed CLOUDFRONT-12230
|
|
223
|
-
function discloseAllNestedChildren(children) {
|
|
270
|
+
function discloseAllNestedChildren(children, sliderId) {
|
|
224
271
|
if (!children) {
|
|
225
272
|
return [];
|
|
226
273
|
}
|
|
274
|
+
let childIndex = 0;
|
|
275
|
+
const wrapped = (child) => {
|
|
276
|
+
const id = getSlideId(sliderId, childIndex++);
|
|
277
|
+
return (React.createElement("div", { key: id, id: id }, child));
|
|
278
|
+
};
|
|
227
279
|
return React.Children.map(children, (child) => {
|
|
228
280
|
var _a;
|
|
229
281
|
if (child) {
|
|
230
282
|
// TODO: if child has 'items' then 'items' determinate like nested children for Slider.
|
|
231
283
|
const nestedChildren = (_a = child.props.data) === null || _a === void 0 ? void 0 : _a.items;
|
|
232
284
|
if (nestedChildren) {
|
|
233
|
-
return nestedChildren.map((nestedChild) =>
|
|
234
|
-
|
|
235
|
-
|
|
285
|
+
return nestedChildren.map((nestedChild) => {
|
|
286
|
+
return wrapped(React.cloneElement(child, {
|
|
287
|
+
data: Object.assign(Object.assign({}, child.props.data), { items: [nestedChild] }),
|
|
288
|
+
}));
|
|
289
|
+
});
|
|
236
290
|
}
|
|
237
291
|
}
|
|
238
|
-
return child;
|
|
292
|
+
return child && wrapped(child);
|
|
239
293
|
}).filter(Boolean);
|
|
240
294
|
}
|
|
241
295
|
export default SliderBlock;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const i18n: (key: "arrow-right" | "arrow-left", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
|
1
|
+
export declare const i18n: (key: "arrow-right" | "arrow-left" | "dot-label" | "pagination-label", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
|
@@ -10,6 +10,7 @@ export interface GetSlidesToShowParams {
|
|
|
10
10
|
breakpoints?: SlidesToShow;
|
|
11
11
|
mobileFullscreen?: boolean;
|
|
12
12
|
}
|
|
13
|
+
export declare const isFocusable: (element: Element) => boolean;
|
|
13
14
|
export declare function getSlidesToShowWithDefaults({ contentLength, breakpoints, mobileFullscreen, }: GetSlidesToShowParams): {
|
|
14
15
|
sm: number;
|
|
15
16
|
xl: number;
|
|
@@ -24,3 +25,12 @@ export declare function getSliderResponsiveParams(breakpoints: SliderBreakpointP
|
|
|
24
25
|
}[];
|
|
25
26
|
export declare function getSlidesCountByBreakpoint(breakpoint: number, breakpoints: SliderBreakpointParams): number;
|
|
26
27
|
export declare function getSlidesToShowCount(breakpoints: SliderBreakpointParams): number;
|
|
28
|
+
export declare function useRovingTabIndex(props: {
|
|
29
|
+
itemCount: number;
|
|
30
|
+
activeIndex: number;
|
|
31
|
+
firstIndex?: number;
|
|
32
|
+
uniqId: string;
|
|
33
|
+
}): {
|
|
34
|
+
getRovingItemProps: (index: number) => Pick<React.HTMLAttributes<HTMLElement>, 'id' | 'tabIndex' | 'onFocus'>;
|
|
35
|
+
rovingListProps: import("react").HTMLAttributes<HTMLElement>;
|
|
36
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
1
2
|
import pickBy from 'lodash/pickBy';
|
|
2
3
|
import { BREAKPOINTS } from '../../constants';
|
|
3
4
|
import { SliderBreakpointNames } from './models';
|
|
@@ -8,6 +9,37 @@ export const DEFAULT_SLIDE_BREAKPOINTS = {
|
|
|
8
9
|
[SliderBreakpointNames.Sm]: 1.15,
|
|
9
10
|
};
|
|
10
11
|
const BREAKPOINT_NAMES_BY_VALUES = Object.entries(BREAKPOINTS).reduce((acc, [key, value]) => (Object.assign(Object.assign({}, acc), { [value]: key })), {});
|
|
12
|
+
export const isFocusable = (element) => {
|
|
13
|
+
if (!(element instanceof HTMLElement)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const tabIndexAttr = element.getAttribute('tabindex');
|
|
17
|
+
const hasTabIndex = tabIndexAttr !== null;
|
|
18
|
+
const tabIndex = Number(tabIndexAttr);
|
|
19
|
+
if (element.ariaHidden === 'true' || (hasTabIndex && tabIndex < 0)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (hasTabIndex && tabIndex >= 0) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// without this jest fails here for some reason
|
|
26
|
+
let htmlElement;
|
|
27
|
+
switch (true) {
|
|
28
|
+
case element instanceof HTMLAnchorElement:
|
|
29
|
+
htmlElement = element;
|
|
30
|
+
return Boolean(htmlElement.href);
|
|
31
|
+
case element instanceof HTMLInputElement:
|
|
32
|
+
htmlElement = element;
|
|
33
|
+
return htmlElement.type !== 'hidden' && !htmlElement.disabled;
|
|
34
|
+
case element instanceof HTMLSelectElement:
|
|
35
|
+
case element instanceof HTMLTextAreaElement:
|
|
36
|
+
case element instanceof HTMLButtonElement:
|
|
37
|
+
htmlElement = element;
|
|
38
|
+
return !htmlElement.disabled;
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
11
43
|
export function getSlidesToShowWithDefaults({ contentLength, breakpoints, mobileFullscreen, }) {
|
|
12
44
|
let result;
|
|
13
45
|
if (typeof breakpoints === 'number') {
|
|
@@ -31,3 +63,53 @@ export function getSlidesCountByBreakpoint(breakpoint, breakpoints) {
|
|
|
31
63
|
export function getSlidesToShowCount(breakpoints) {
|
|
32
64
|
return Math.floor(Math.max(...Object.values(breakpoints)));
|
|
33
65
|
}
|
|
66
|
+
const getRovingListItemId = (uniqId, index) => `${uniqId}-roving-tabindex-item-${index}`;
|
|
67
|
+
export function useRovingTabIndex(props) {
|
|
68
|
+
const { itemCount, activeIndex, firstIndex = 0, uniqId } = props;
|
|
69
|
+
const [currentIndex, setCurrentIndex] = useState(firstIndex);
|
|
70
|
+
const hasFocusRef = useRef(false);
|
|
71
|
+
const lastIndex = itemCount + firstIndex - 1;
|
|
72
|
+
const getRovingItemProps = (index) => {
|
|
73
|
+
return {
|
|
74
|
+
id: getRovingListItemId(uniqId, index),
|
|
75
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
76
|
+
onFocus: () => {
|
|
77
|
+
setCurrentIndex(index);
|
|
78
|
+
hasFocusRef.current = true;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
var _a;
|
|
84
|
+
if (!hasFocusRef.current) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
(_a = document.getElementById(getRovingListItemId(uniqId, currentIndex))) === null || _a === void 0 ? void 0 : _a.focus();
|
|
88
|
+
}, [activeIndex, currentIndex, uniqId]);
|
|
89
|
+
const setNextIndex = () => setCurrentIndex((prev) => (prev >= lastIndex ? firstIndex : prev + 1));
|
|
90
|
+
const setPrevIndex = () => setCurrentIndex((prev) => (prev <= firstIndex ? lastIndex : prev - 1));
|
|
91
|
+
const onRovingListKeyDown = (e) => {
|
|
92
|
+
const key = e.key.toLowerCase();
|
|
93
|
+
if (key !== 'tab' && key !== 'enter') {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
}
|
|
96
|
+
switch (key) {
|
|
97
|
+
case 'arrowleft':
|
|
98
|
+
case 'arrowup':
|
|
99
|
+
setPrevIndex();
|
|
100
|
+
return;
|
|
101
|
+
case 'arrowright':
|
|
102
|
+
case 'arrowdown':
|
|
103
|
+
setNextIndex();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const onRovingListBlur = () => {
|
|
108
|
+
hasFocusRef.current = false;
|
|
109
|
+
};
|
|
110
|
+
const rovingListProps = {
|
|
111
|
+
onKeyDown: onRovingListKeyDown,
|
|
112
|
+
onBlur: onRovingListBlur,
|
|
113
|
+
};
|
|
114
|
+
return { getRovingItemProps, rovingListProps };
|
|
115
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { ClassNameProps } from '../../../models';
|
|
2
3
|
import './Arrow.css';
|
|
3
4
|
export type ArrowType = 'left' | 'right';
|
|
@@ -5,6 +6,7 @@ export interface ArrowProps {
|
|
|
5
6
|
type: ArrowType;
|
|
6
7
|
onClick?: () => void;
|
|
7
8
|
size?: number;
|
|
9
|
+
extraProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
|
|
8
10
|
}
|
|
9
|
-
declare const Arrow: ({ type, onClick, className, size }: ArrowProps & ClassNameProps) => JSX.Element;
|
|
11
|
+
declare const Arrow: ({ type, onClick, className, size, extraProps }: ArrowProps & ClassNameProps) => JSX.Element;
|
|
10
12
|
export default Arrow;
|
|
@@ -4,8 +4,8 @@ import { block } from '../../../utils';
|
|
|
4
4
|
import { i18n } from '../i18n';
|
|
5
5
|
import './Arrow.css';
|
|
6
6
|
const b = block('slider-new-block-arrow');
|
|
7
|
-
const Arrow = ({ type, onClick, className, size = 16 }) => (React.createElement("div", { className: b({ type }, className) },
|
|
8
|
-
React.createElement("button", { className: b('button'), onClick: onClick, "aria-label": i18n(`arrow-${type}`) },
|
|
7
|
+
const Arrow = ({ type, onClick, className, size = 16, extraProps }) => (React.createElement("div", { className: b({ type }, className) },
|
|
8
|
+
React.createElement("button", Object.assign({ className: b('button'), onClick: onClick, "aria-label": i18n(`arrow-${type}`) }, extraProps),
|
|
9
9
|
React.createElement("span", { className: b('icon-wrapper') },
|
|
10
10
|
React.createElement(ToggleArrow, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') })))));
|
|
11
11
|
export default Arrow;
|
|
@@ -6,18 +6,30 @@ import AnimateBlock from '../../components/AnimateBlock/AnimateBlock';
|
|
|
6
6
|
import Title from '../../components/Title/Title';
|
|
7
7
|
import { block } from '../../utils';
|
|
8
8
|
import Arrow from './Arrow/Arrow';
|
|
9
|
+
import { i18n } from './i18n';
|
|
9
10
|
import { useSlider } from './useSlider';
|
|
11
|
+
import { useSliderPagination } from './useSliderPagination';
|
|
10
12
|
import './Slider.css';
|
|
11
13
|
import 'swiper/swiper-bundle.css';
|
|
12
14
|
const b = block('SliderNewBlock');
|
|
13
15
|
SwiperCore.use([Autoplay, A11y, Pagination]);
|
|
14
16
|
export const SliderNewBlock = ({ animated, title, description, type, anchorId, arrows = true, adaptive, autoplay: autoplayMs, dots = true, className, dotsClassName, disclaimer, children, blockClassName, arrowSize, slidesToShow, onSlideChange, onSlideChangeTransitionStart, onSlideChangeTransitionEnd, onActiveIndexChange, onBreakpoint, }) => {
|
|
15
|
-
const { childrenCount, breakpoints,
|
|
17
|
+
const { autoplay, isLocked, childrenCount, breakpoints, onSwiper, onPrev, onNext, setIsLocked } = useSlider({
|
|
16
18
|
slidesToShow,
|
|
17
19
|
children,
|
|
18
20
|
type,
|
|
19
21
|
autoplayMs,
|
|
20
22
|
});
|
|
23
|
+
const isA11yControlHidden = Boolean(autoplay);
|
|
24
|
+
const controlTabIndex = isA11yControlHidden ? -1 : 0;
|
|
25
|
+
const paginationProps = useSliderPagination({
|
|
26
|
+
enabled: dots,
|
|
27
|
+
isA11yControlHidden,
|
|
28
|
+
controlTabIndex,
|
|
29
|
+
bulletClass: b('dot', dotsClassName),
|
|
30
|
+
bulletActiveClass: b('dot_active'),
|
|
31
|
+
paginationLabel: i18n('pagination-label'),
|
|
32
|
+
});
|
|
21
33
|
return (React.createElement("div", { className: b({
|
|
22
34
|
'one-slide': childrenCount === 1,
|
|
23
35
|
'only-arrows': !(title === null || title === void 0 ? void 0 : title.text) && !description && arrows,
|
|
@@ -27,14 +39,14 @@ export const SliderNewBlock = ({ animated, title, description, type, anchorId, a
|
|
|
27
39
|
anchorId && React.createElement(Anchor, { id: anchorId }),
|
|
28
40
|
React.createElement(Title, { title: title, subtitle: description, className: b('header', { 'no-description': !description }) }),
|
|
29
41
|
React.createElement(AnimateBlock, { className: b('animate-slides'), animate: animated },
|
|
30
|
-
React.createElement(Swiper, { className: b('slider', className), onSwiper: onSwiper,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}, speed: 1000, autoplay: autoplay, autoHeight: adaptive, initialSlide: 0, noSwiping: false, breakpoints: breakpoints, onSlideChange: onSlideChange, onSlideChangeTransitionStart: onSlideChangeTransitionStart, onSlideChangeTransitionEnd: onSlideChangeTransitionEnd, onActiveIndexChange: onActiveIndexChange, onBreakpoint: onBreakpoint, onLock: () => setIsLocked(true), onUnlock: () => setIsLocked(false), watchSlidesVisibility: true, watchOverflow: true }, React.Children.map(children, (elem, index) => (React.createElement(SwiperSlide, { className: b('slide'), key: index }, elem)))),
|
|
42
|
+
React.createElement(Swiper, Object.assign({ className: b('slider', className), onSwiper: onSwiper, speed: 1000, autoplay: autoplay, autoHeight: adaptive, initialSlide: 0, noSwiping: false, breakpoints: breakpoints, onSlideChange: onSlideChange, onSlideChangeTransitionStart: onSlideChangeTransitionStart, onSlideChangeTransitionEnd: onSlideChangeTransitionEnd, onActiveIndexChange: onActiveIndexChange, onBreakpoint: onBreakpoint, onLock: () => setIsLocked(true), onUnlock: () => setIsLocked(false), watchSlidesVisibility: true, watchOverflow: true, a11y: {
|
|
43
|
+
slideLabelMessage: '',
|
|
44
|
+
paginationBulletMessage: i18n('dot-label', { index: '{{index}}' }),
|
|
45
|
+
} }, paginationProps), React.Children.map(children, (elem, index) => (React.createElement(SwiperSlide, { className: b('slide'), key: index }, ({ isVisible }) => (React.createElement("div", { "aria-hidden": !isA11yControlHidden && !isVisible }, elem)))))),
|
|
35
46
|
arrows && !isLocked && (React.createElement(Fragment, null,
|
|
36
|
-
React.createElement(
|
|
37
|
-
|
|
47
|
+
React.createElement("div", { "aria-hidden": isA11yControlHidden },
|
|
48
|
+
React.createElement(Arrow, { className: b('arrow', { prev: true }), type: "left", onClick: onPrev, size: arrowSize, extraProps: { tabIndex: controlTabIndex } }),
|
|
49
|
+
React.createElement(Arrow, { className: b('arrow', { next: true }), type: "right", onClick: onNext, size: arrowSize, extraProps: { tabIndex: controlTabIndex } })))),
|
|
38
50
|
React.createElement("div", { className: b('footer') }, disclaimer ? (React.createElement("div", { className: b('disclaimer', { size: (disclaimer === null || disclaimer === void 0 ? void 0 : disclaimer.size) || 'm' }) }, disclaimer === null || disclaimer === void 0 ? void 0 : disclaimer.text)) : null))));
|
|
39
51
|
};
|
|
40
52
|
export default SliderNewBlock;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const i18n: (key: "arrow-right" | "arrow-left", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
|
1
|
+
export declare const i18n: (key: "arrow-right" | "arrow-left" | "dot-label" | "pagination-label", params?: import("@gravity-ui/i18n").Params | undefined) => string;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
2
|
import type { Swiper } from 'swiper';
|
|
3
3
|
import { SlidesToShow } from '../../models';
|
|
4
|
-
|
|
5
|
-
autoplayMs?: number
|
|
6
|
-
type?: string
|
|
7
|
-
slidesToShow?: SlidesToShow
|
|
8
|
-
}
|
|
4
|
+
type UseSliderProps = PropsWithChildren<{
|
|
5
|
+
autoplayMs?: number;
|
|
6
|
+
type?: string;
|
|
7
|
+
slidesToShow?: SlidesToShow;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const useSlider: ({ children, autoplayMs, type, ...props }: UseSliderProps) => {
|
|
9
10
|
slider: Swiper | undefined;
|
|
10
11
|
onSwiper: React.Dispatch<React.SetStateAction<Swiper | undefined>>;
|
|
11
12
|
onNext: () => void;
|
|
@@ -15,7 +16,8 @@ export declare const useSlider: ({ children, autoplayMs, type, slidesToShow, }:
|
|
|
15
16
|
isLocked: boolean;
|
|
16
17
|
setIsLocked: React.Dispatch<React.SetStateAction<boolean>>;
|
|
17
18
|
autoplay: false | {
|
|
18
|
-
delay: number
|
|
19
|
+
delay: number;
|
|
19
20
|
disableOnInteraction: boolean;
|
|
20
21
|
};
|
|
21
22
|
};
|
|
23
|
+
export {};
|