@gravity-ui/page-constructor 5.27.0 → 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.
Files changed (40) hide show
  1. package/build/cjs/blocks/Slider/Slider.css +1 -1
  2. package/build/cjs/blocks/Slider/Slider.js +83 -29
  3. package/build/cjs/blocks/Slider/i18n/en.json +3 -1
  4. package/build/cjs/blocks/Slider/i18n/index.d.ts +1 -1
  5. package/build/cjs/blocks/Slider/i18n/ru.json +3 -1
  6. package/build/cjs/blocks/Slider/utils.d.ts +10 -0
  7. package/build/cjs/blocks/Slider/utils.js +85 -1
  8. package/build/cjs/blocks/SliderNew/Arrow/Arrow.d.ts +3 -1
  9. package/build/cjs/blocks/SliderNew/Arrow/Arrow.js +2 -2
  10. package/build/cjs/blocks/SliderNew/Slider.js +20 -8
  11. package/build/cjs/blocks/SliderNew/i18n/en.json +3 -1
  12. package/build/cjs/blocks/SliderNew/i18n/index.d.ts +1 -1
  13. package/build/cjs/blocks/SliderNew/i18n/ru.json +3 -1
  14. package/build/cjs/blocks/SliderNew/useSlider.d.ts +8 -6
  15. package/build/cjs/blocks/SliderNew/useSlider.js +4 -2
  16. package/build/cjs/blocks/SliderNew/useSliderPagination.d.ts +9 -0
  17. package/build/cjs/blocks/SliderNew/useSliderPagination.js +36 -0
  18. package/build/cjs/blocks/SliderNew/utils.d.ts +2 -0
  19. package/build/cjs/blocks/SliderNew/utils.js +13 -1
  20. package/build/esm/blocks/Slider/Slider.css +1 -1
  21. package/build/esm/blocks/Slider/Slider.js +84 -30
  22. package/build/esm/blocks/Slider/i18n/en.json +3 -1
  23. package/build/esm/blocks/Slider/i18n/index.d.ts +1 -1
  24. package/build/esm/blocks/Slider/i18n/ru.json +3 -1
  25. package/build/esm/blocks/Slider/utils.d.ts +10 -0
  26. package/build/esm/blocks/Slider/utils.js +82 -0
  27. package/build/esm/blocks/SliderNew/Arrow/Arrow.d.ts +3 -1
  28. package/build/esm/blocks/SliderNew/Arrow/Arrow.js +2 -2
  29. package/build/esm/blocks/SliderNew/Slider.js +20 -8
  30. package/build/esm/blocks/SliderNew/i18n/en.json +3 -1
  31. package/build/esm/blocks/SliderNew/i18n/index.d.ts +1 -1
  32. package/build/esm/blocks/SliderNew/i18n/ru.json +3 -1
  33. package/build/esm/blocks/SliderNew/useSlider.d.ts +8 -6
  34. package/build/esm/blocks/SliderNew/useSlider.js +6 -3
  35. package/build/esm/blocks/SliderNew/useSliderPagination.d.ts +9 -0
  36. package/build/esm/blocks/SliderNew/useSliderPagination.js +32 -0
  37. package/build/esm/blocks/SliderNew/utils.d.ts +2 -0
  38. package/build/esm/blocks/SliderNew/utils.js +10 -0
  39. package/package.json +2 -1
  40. package/widget/index.js +1 -1
@@ -105,7 +105,7 @@ unpredictable css rules order in build */
105
105
  height: auto;
106
106
  }
107
107
  .pc-SliderBlock .slick-track .slick-slide > div {
108
- height: 100%;
108
+ display: flex;
109
109
  }
110
110
  .pc-SliderBlock .slick-arrow {
111
111
  position: absolute;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SliderBlock = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
+ const uikit_1 = require("@gravity-ui/uikit");
6
7
  const debounce_1 = tslib_1.__importDefault(require("lodash/debounce"));
7
8
  const get_1 = tslib_1.__importDefault(require("lodash/get"));
8
9
  const noop_1 = tslib_1.__importDefault(require("lodash/noop"));
@@ -19,6 +20,7 @@ const useFocus_1 = tslib_1.__importDefault(require("../../hooks/useFocus"));
19
20
  const models_1 = require("../../models");
20
21
  const utils_1 = require("../../utils");
21
22
  const Arrow_1 = tslib_1.__importDefault(require("./Arrow/Arrow"));
23
+ const i18n_1 = require("./i18n");
22
24
  const utils_2 = require("./utils");
23
25
  const b = (0, utils_1.block)('SliderBlock');
24
26
  const slick = (0, utils_1.block)('slick-origin');
@@ -26,12 +28,15 @@ const DOT_WIDTH = 8;
26
28
  const DOT_GAP = 16;
27
29
  const SliderBlock = (props) => {
28
30
  var _a;
29
- const { animated, title, description, type, anchorId, arrows = true, adaptive, autoplay = undefined, dots = true, dotsClassName, disclaimer, children, className, blockClassName, lazyLoad, arrowSize, onAfterChange: handleAfterChange, onBeforeChange: handleBeforeChange, } = props;
31
+ 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;
30
32
  const { isServer } = (0, react_1.useContext)(ssrContext_1.SSRContext);
31
33
  const isMobile = (0, react_1.useContext)(mobileContext_1.MobileContext);
32
34
  const [breakpoint, setBreakpoint] = (0, react_1.useState)(constants_1.BREAKPOINTS.xl);
33
- const disclosedChildren = (0, react_1.useMemo)(() => discloseAllNestedChildren(children), [children]);
35
+ const sliderId = (0, uikit_1.useUniqId)();
36
+ const disclosedChildren = (0, react_1.useMemo)(() => discloseAllNestedChildren(children, sliderId), [children, sliderId]);
34
37
  const childrenCount = disclosedChildren.length;
38
+ const isAutoplayEnabled = autoplaySpeed !== undefined && autoplaySpeed > 0;
39
+ const isUserInteractionRef = (0, react_1.useRef)(false);
35
40
  const [slidesToShow] = (0, react_1.useState)((0, utils_2.getSlidesToShowWithDefaults)({
36
41
  contentLength: childrenCount,
37
42
  breakpoints: props.slidesToShow,
@@ -42,8 +47,13 @@ const SliderBlock = (props) => {
42
47
  const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
43
48
  const [childStyles, setChildStyles] = (0, react_1.useState)({});
44
49
  const [slider, setSlider] = (0, react_1.useState)();
50
+ const prevIndexRef = (0, react_1.useRef)(0);
45
51
  const autoplayTimeId = (0, react_1.useRef)();
46
52
  const { hasFocus, unsetFocus } = (0, useFocus_1.default)((_a = slider === null || slider === void 0 ? void 0 : slider.innerSlider) === null || _a === void 0 ? void 0 : _a.list);
53
+ const asUserInteraction = (fn) => (...args) => {
54
+ isUserInteractionRef.current = true;
55
+ return fn(...args);
56
+ };
47
57
  // eslint-disable-next-line react-hooks/exhaustive-deps
48
58
  const onResize = (0, react_1.useCallback)((0, debounce_1.default)(() => {
49
59
  if (!slider) {
@@ -58,7 +68,7 @@ const SliderBlock = (props) => {
58
68
  }, 100), [slider, breakpoint]);
59
69
  const scrollLastSlide = (0, react_1.useCallback)((current) => {
60
70
  const lastSlide = childrenCount - slidesToShowCount;
61
- if (autoplay && lastSlide === current) {
71
+ if (isAutoplayEnabled && lastSlide === current) {
62
72
  // Slick doesn't support autoplay with no infinity scroll
63
73
  autoplayTimeId.current = setTimeout(() => {
64
74
  if (slider) {
@@ -70,9 +80,9 @@ const SliderBlock = (props) => {
70
80
  slider.slickPlay();
71
81
  }
72
82
  }, 500);
73
- }, autoplay);
83
+ }, autoplaySpeed);
74
84
  }
75
- }, [autoplay, childrenCount, slider, slidesToShowCount]);
85
+ }, [autoplaySpeed, childrenCount, isAutoplayEnabled, slider, slidesToShowCount]);
76
86
  (0, react_1.useEffect)(() => {
77
87
  if (hasFocus && autoplayTimeId.current) {
78
88
  clearTimeout(autoplayTimeId.current);
@@ -86,7 +96,7 @@ const SliderBlock = (props) => {
86
96
  window.addEventListener('resize', onResize, { passive: true });
87
97
  return () => window.removeEventListener('resize', onResize);
88
98
  }, [onResize]);
89
- const handleArrowClick = (0, react_1.useCallback)((direction) => {
99
+ const handleArrowClick = (direction) => {
90
100
  let nextIndex;
91
101
  if (direction === 'right') {
92
102
  nextIndex =
@@ -99,11 +109,12 @@ const SliderBlock = (props) => {
99
109
  if (slider) {
100
110
  slider.slickGoTo(nextIndex);
101
111
  }
102
- }, [childrenCount, currentIndex, slider, slidesCountByBreakpoint]);
112
+ };
103
113
  const onBeforeChange = (0, react_1.useCallback)((current, next) => {
104
114
  if (handleBeforeChange) {
105
115
  handleBeforeChange(current, next);
106
116
  }
117
+ prevIndexRef.current = current;
107
118
  setCurrentIndex(Math.ceil(next));
108
119
  }, [handleBeforeChange]);
109
120
  const onAfterChange = (0, react_1.useCallback)((current) => {
@@ -116,16 +127,33 @@ const SliderBlock = (props) => {
116
127
  if (!hasFocus) {
117
128
  scrollLastSlide(current);
118
129
  }
119
- }, [handleAfterChange, hasFocus, scrollLastSlide]);
120
- const handleDotClick = (0, react_1.useCallback)((index) => {
130
+ if (isUserInteractionRef.current) {
131
+ const focusIndex = prevIndexRef.current >= current
132
+ ? current
133
+ : Math.max(current, prevIndexRef.current + slidesCountByBreakpoint);
134
+ const firstNewSlide = document.getElementById(getSlideId(sliderId, focusIndex));
135
+ if (firstNewSlide) {
136
+ const focusableChild = Array.from(firstNewSlide.querySelectorAll('*')).find(utils_2.isFocusable);
137
+ focusableChild === null || focusableChild === void 0 ? void 0 : focusableChild.focus();
138
+ }
139
+ }
140
+ isUserInteractionRef.current = false;
141
+ }, [handleAfterChange, hasFocus, scrollLastSlide, sliderId, slidesCountByBreakpoint]);
142
+ const handleDotClick = (index) => {
121
143
  const nextIndex = index > currentIndex ? index + 1 - slidesCountByBreakpoint : index;
122
144
  if (slider) {
123
145
  slider.slickGoTo(nextIndex);
124
146
  }
125
- }, [slider, currentIndex, slidesCountByBreakpoint]);
126
- const barSlidesCount = childrenCount - slidesToShowCount + 1;
147
+ };
148
+ const barSlidesCount = childrenCount - slidesCountByBreakpoint + 1;
127
149
  const barPosition = (DOT_GAP + DOT_WIDTH) * currentIndex;
128
150
  const barWidth = DOT_WIDTH + (DOT_GAP + DOT_WIDTH) * (slidesCountByBreakpoint - 1);
151
+ const { getRovingItemProps, rovingListProps } = (0, utils_2.useRovingTabIndex)({
152
+ itemCount: barSlidesCount,
153
+ activeIndex: currentIndex + 1,
154
+ firstIndex: 1,
155
+ uniqId: sliderId,
156
+ });
129
157
  const renderBar = () => {
130
158
  return (slidesCountByBreakpoint > 1 && (react_1.default.createElement("li", { className: b('bar'), style: {
131
159
  left: barPosition,
@@ -136,19 +164,22 @@ const SliderBlock = (props) => {
136
164
  const renderAccessibleBar = (index) => {
137
165
  return (
138
166
  // To have this key differ from keys used in renderDot function, added `-accessible-bar` part
139
- react_1.default.createElement(react_1.Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 0 && (react_1.default.createElement("li", { className: b('accessible-bar'), "aria-current": true, "aria-label": `Slide ${currentIndex + 1} of ${barSlidesCount}`, style: {
167
+ react_1.default.createElement(react_1.Fragment, { key: `${index}-accessible-bar` }, slidesCountByBreakpoint > 0 && (react_1.default.createElement("li", Object.assign({ className: b('accessible-bar'), role: "menuitemradio", "aria-checked": true, "aria-label": (0, i18n_1.i18n)('dot-label', {
168
+ index: currentIndex + 1,
169
+ count: barSlidesCount,
170
+ }), style: {
140
171
  left: barPosition,
141
172
  width: barWidth,
142
- } }))));
173
+ } }, getRovingItemProps(currentIndex + 1))))));
143
174
  };
144
175
  const getCurrentSlideNumber = (index) => {
145
176
  const currentIndexDiff = index - currentIndex;
146
177
  let currentSlideNumber;
147
- if (0 <= currentIndexDiff && currentIndexDiff < slidesToShowCount) {
178
+ if (0 <= currentIndexDiff && currentIndexDiff < slidesCountByBreakpoint) {
148
179
  currentSlideNumber = currentIndex + 1;
149
180
  }
150
- else if (currentIndexDiff >= slidesToShowCount) {
151
- currentSlideNumber = index - slidesToShowCount + 2;
181
+ else if (currentIndexDiff >= slidesCountByBreakpoint) {
182
+ currentSlideNumber = index - slidesCountByBreakpoint + 2;
152
183
  }
153
184
  else {
154
185
  currentSlideNumber = index + 1;
@@ -157,12 +188,24 @@ const SliderBlock = (props) => {
157
188
  };
158
189
  const isVisibleSlide = (index) => {
159
190
  const currentIndexDiff = index - currentIndex;
160
- return (slidesCountByBreakpoint > 0 &&
191
+ const result = slidesCountByBreakpoint > 0 &&
161
192
  0 <= currentIndexDiff &&
162
- currentIndexDiff < slidesToShowCount);
193
+ currentIndexDiff < slidesCountByBreakpoint;
194
+ return result;
163
195
  };
164
196
  const renderDot = (index) => {
165
- return (react_1.default.createElement("li", { key: index, className: b('dot', { active: index === currentIndex }), onClick: () => handleDotClick(index), "aria-hidden": isVisibleSlide(index) ? true : undefined, "aria-label": `Slide ${getCurrentSlideNumber(index)} of ${barSlidesCount}` }));
197
+ const isVisible = isVisibleSlide(index);
198
+ const currentSlideNumber = getCurrentSlideNumber(index);
199
+ const rovingItemProps = isVisible ? undefined : getRovingItemProps(currentSlideNumber);
200
+ return (react_1.default.createElement("li", Object.assign({ key: index, className: b('dot', { active: index === currentIndex }), onClick: asUserInteraction(() => handleDotClick(index)), onKeyDown: (e) => {
201
+ const key = e.key.toLowerCase();
202
+ if (key === 'space' || key === 'enter') {
203
+ e.currentTarget.click();
204
+ }
205
+ }, role: "menuitemradio", "aria-checked": false, tabIndex: -1, "aria-hidden": isVisible, "aria-label": (0, i18n_1.i18n)('dot-label', {
206
+ index: currentSlideNumber,
207
+ count: barSlidesCount,
208
+ }) }, rovingItemProps)));
166
209
  };
167
210
  const renderNavigation = () => {
168
211
  if (childrenCount <= slidesCountByBreakpoint || !dots || childrenCount === 1) {
@@ -173,7 +216,7 @@ const SliderBlock = (props) => {
173
216
  .map((_item, index) => renderDot(index));
174
217
  dotsList.splice(currentIndex, 0, renderAccessibleBar(currentIndex));
175
218
  return (react_1.default.createElement("div", { className: b('dots', dotsClassName) },
176
- react_1.default.createElement("ul", { className: b('dots-list') },
219
+ react_1.default.createElement("ul", Object.assign({ className: b('dots-list'), role: "menu", "aria-label": (0, i18n_1.i18n)('pagination-label') }, rovingListProps),
177
220
  renderBar(),
178
221
  dotsList)));
179
222
  };
@@ -192,17 +235,18 @@ const SliderBlock = (props) => {
192
235
  infinite: false,
193
236
  speed: 1000,
194
237
  adaptiveHeight: adaptive,
195
- autoplay: Boolean(autoplay),
196
- autoplaySpeed: autoplay,
238
+ autoplay: isAutoplayEnabled,
239
+ autoplaySpeed,
197
240
  slidesToShow: slidesToShowCount,
198
241
  slidesToScroll: 1,
199
242
  responsive: (0, utils_2.getSliderResponsiveParams)(slidesToShow),
200
243
  beforeChange: onBeforeChange,
201
244
  afterChange: onAfterChange,
202
245
  initialSlide: 0,
203
- nextArrow: react_1.default.createElement(Arrow_1.default, { type: "right", handleClick: handleArrowClick, size: arrowSize }),
204
- prevArrow: react_1.default.createElement(Arrow_1.default, { type: "left", handleClick: handleArrowClick, size: arrowSize }),
246
+ nextArrow: (react_1.default.createElement(Arrow_1.default, { type: "right", handleClick: asUserInteraction(handleArrowClick), size: arrowSize })),
247
+ prevArrow: (react_1.default.createElement(Arrow_1.default, { type: "left", handleClick: asUserInteraction(handleArrowClick), size: arrowSize })),
205
248
  lazyLoad,
249
+ accessibility: false,
206
250
  };
207
251
  return (react_1.default.createElement(OutsideClick_1.default, { onOutsideClick: isMobile ? unsetFocus : noop_1.default },
208
252
  react_1.default.createElement(react_slick_1.default, Object.assign({}, settings), disclosedChildren),
@@ -223,23 +267,33 @@ const SliderBlock = (props) => {
223
267
  react_1.default.createElement(AnimateBlock_1.default, { className: b('animate-slides'), animate: animated }, renderSlider()))));
224
268
  };
225
269
  exports.SliderBlock = SliderBlock;
270
+ function getSlideId(sliderId, index) {
271
+ return `slider-${sliderId}-child-${index}`;
272
+ }
226
273
  // TODO remove this and rework PriceDetailed CLOUDFRONT-12230
227
- function discloseAllNestedChildren(children) {
274
+ function discloseAllNestedChildren(children, sliderId) {
228
275
  if (!children) {
229
276
  return [];
230
277
  }
278
+ let childIndex = 0;
279
+ const wrapped = (child) => {
280
+ const id = getSlideId(sliderId, childIndex++);
281
+ return (react_1.default.createElement("div", { key: id, id: id }, child));
282
+ };
231
283
  return react_1.default.Children.map(children, (child) => {
232
284
  var _a;
233
285
  if (child) {
234
286
  // TODO: if child has 'items' then 'items' determinate like nested children for Slider.
235
287
  const nestedChildren = (_a = child.props.data) === null || _a === void 0 ? void 0 : _a.items;
236
288
  if (nestedChildren) {
237
- return nestedChildren.map((nestedChild) => react_1.default.cloneElement(child, {
238
- data: Object.assign(Object.assign({}, child.props.data), { items: [nestedChild] }),
239
- }));
289
+ return nestedChildren.map((nestedChild) => {
290
+ return wrapped(react_1.default.cloneElement(child, {
291
+ data: Object.assign(Object.assign({}, child.props.data), { items: [nestedChild] }),
292
+ }));
293
+ });
240
294
  }
241
295
  }
242
- return child;
296
+ return child && wrapped(child);
243
297
  }).filter(Boolean);
244
298
  }
245
299
  exports.default = exports.SliderBlock;
@@ -1,4 +1,6 @@
1
1
  {
2
2
  "arrow-right": "Next",
3
- "arrow-left": "Previous"
3
+ "arrow-left": "Previous",
4
+ "dot-label": "Page {{index}} of {{count}}",
5
+ "pagination-label": "Pages"
4
6
  }
@@ -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,4 +1,6 @@
1
1
  {
2
2
  "arrow-right": "Дальше",
3
- "arrow-left": "Назад"
3
+ "arrow-left": "Назад",
4
+ "dot-label": "Страница {{index}} из {{count}}",
5
+ "pagination-label": "Страницы"
4
6
  }
@@ -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,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSlidesToShowCount = exports.getSlidesCountByBreakpoint = exports.getSliderResponsiveParams = exports.getSlidesToShowWithDefaults = exports.DEFAULT_SLIDE_BREAKPOINTS = void 0;
3
+ exports.useRovingTabIndex = exports.getSlidesToShowCount = exports.getSlidesCountByBreakpoint = exports.getSliderResponsiveParams = exports.getSlidesToShowWithDefaults = exports.isFocusable = exports.DEFAULT_SLIDE_BREAKPOINTS = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const react_1 = require("react");
5
6
  const pickBy_1 = tslib_1.__importDefault(require("lodash/pickBy"));
6
7
  const constants_1 = require("../../constants");
7
8
  const models_1 = require("./models");
@@ -12,6 +13,38 @@ exports.DEFAULT_SLIDE_BREAKPOINTS = {
12
13
  [models_1.SliderBreakpointNames.Sm]: 1.15,
13
14
  };
14
15
  const BREAKPOINT_NAMES_BY_VALUES = Object.entries(constants_1.BREAKPOINTS).reduce((acc, [key, value]) => (Object.assign(Object.assign({}, acc), { [value]: key })), {});
16
+ const isFocusable = (element) => {
17
+ if (!(element instanceof HTMLElement)) {
18
+ return false;
19
+ }
20
+ const tabIndexAttr = element.getAttribute('tabindex');
21
+ const hasTabIndex = tabIndexAttr !== null;
22
+ const tabIndex = Number(tabIndexAttr);
23
+ if (element.ariaHidden === 'true' || (hasTabIndex && tabIndex < 0)) {
24
+ return false;
25
+ }
26
+ if (hasTabIndex && tabIndex >= 0) {
27
+ return true;
28
+ }
29
+ // without this jest fails here for some reason
30
+ let htmlElement;
31
+ switch (true) {
32
+ case element instanceof HTMLAnchorElement:
33
+ htmlElement = element;
34
+ return Boolean(htmlElement.href);
35
+ case element instanceof HTMLInputElement:
36
+ htmlElement = element;
37
+ return htmlElement.type !== 'hidden' && !htmlElement.disabled;
38
+ case element instanceof HTMLSelectElement:
39
+ case element instanceof HTMLTextAreaElement:
40
+ case element instanceof HTMLButtonElement:
41
+ htmlElement = element;
42
+ return !htmlElement.disabled;
43
+ default:
44
+ return false;
45
+ }
46
+ };
47
+ exports.isFocusable = isFocusable;
15
48
  function getSlidesToShowWithDefaults({ contentLength, breakpoints, mobileFullscreen, }) {
16
49
  let result;
17
50
  if (typeof breakpoints === 'number') {
@@ -39,3 +72,54 @@ function getSlidesToShowCount(breakpoints) {
39
72
  return Math.floor(Math.max(...Object.values(breakpoints)));
40
73
  }
41
74
  exports.getSlidesToShowCount = getSlidesToShowCount;
75
+ const getRovingListItemId = (uniqId, index) => `${uniqId}-roving-tabindex-item-${index}`;
76
+ function useRovingTabIndex(props) {
77
+ const { itemCount, activeIndex, firstIndex = 0, uniqId } = props;
78
+ const [currentIndex, setCurrentIndex] = (0, react_1.useState)(firstIndex);
79
+ const hasFocusRef = (0, react_1.useRef)(false);
80
+ const lastIndex = itemCount + firstIndex - 1;
81
+ const getRovingItemProps = (index) => {
82
+ return {
83
+ id: getRovingListItemId(uniqId, index),
84
+ tabIndex: index === activeIndex ? 0 : -1,
85
+ onFocus: () => {
86
+ setCurrentIndex(index);
87
+ hasFocusRef.current = true;
88
+ },
89
+ };
90
+ };
91
+ (0, react_1.useEffect)(() => {
92
+ var _a;
93
+ if (!hasFocusRef.current) {
94
+ return;
95
+ }
96
+ (_a = document.getElementById(getRovingListItemId(uniqId, currentIndex))) === null || _a === void 0 ? void 0 : _a.focus();
97
+ }, [activeIndex, currentIndex, uniqId]);
98
+ const setNextIndex = () => setCurrentIndex((prev) => (prev >= lastIndex ? firstIndex : prev + 1));
99
+ const setPrevIndex = () => setCurrentIndex((prev) => (prev <= firstIndex ? lastIndex : prev - 1));
100
+ const onRovingListKeyDown = (e) => {
101
+ const key = e.key.toLowerCase();
102
+ if (key !== 'tab' && key !== 'enter') {
103
+ e.preventDefault();
104
+ }
105
+ switch (key) {
106
+ case 'arrowleft':
107
+ case 'arrowup':
108
+ setPrevIndex();
109
+ return;
110
+ case 'arrowright':
111
+ case 'arrowdown':
112
+ setNextIndex();
113
+ return;
114
+ }
115
+ };
116
+ const onRovingListBlur = () => {
117
+ hasFocusRef.current = false;
118
+ };
119
+ const rovingListProps = {
120
+ onKeyDown: onRovingListKeyDown,
121
+ onBlur: onRovingListBlur,
122
+ };
123
+ return { getRovingItemProps, rovingListProps };
124
+ }
125
+ exports.useRovingTabIndex = useRovingTabIndex;
@@ -1,9 +1,11 @@
1
+ import React from 'react';
1
2
  import { ClassNameProps } from '../../../models';
2
3
  export type ArrowType = 'left' | 'right';
3
4
  export interface ArrowProps {
4
5
  type: ArrowType;
5
6
  onClick?: () => void;
6
7
  size?: number;
8
+ extraProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
7
9
  }
8
- declare const Arrow: ({ type, onClick, className, size }: ArrowProps & ClassNameProps) => JSX.Element;
10
+ declare const Arrow: ({ type, onClick, className, size, extraProps }: ArrowProps & ClassNameProps) => JSX.Element;
9
11
  export default Arrow;
@@ -6,8 +6,8 @@ const ToggleArrow_1 = tslib_1.__importDefault(require("../../../components/Toggl
6
6
  const utils_1 = require("../../../utils");
7
7
  const i18n_1 = require("../i18n");
8
8
  const b = (0, utils_1.block)('slider-new-block-arrow');
9
- const Arrow = ({ type, onClick, className, size = 16 }) => (react_1.default.createElement("div", { className: b({ type }, className) },
10
- react_1.default.createElement("button", { className: b('button'), onClick: onClick, "aria-label": (0, i18n_1.i18n)(`arrow-${type}`) },
9
+ const Arrow = ({ type, onClick, className, size = 16, extraProps }) => (react_1.default.createElement("div", { className: b({ type }, className) },
10
+ react_1.default.createElement("button", Object.assign({ className: b('button'), onClick: onClick, "aria-label": (0, i18n_1.i18n)(`arrow-${type}`) }, extraProps),
11
11
  react_1.default.createElement("span", { className: b('icon-wrapper') },
12
12
  react_1.default.createElement(ToggleArrow_1.default, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') })))));
13
13
  exports.default = Arrow;
@@ -10,17 +10,29 @@ const AnimateBlock_1 = tslib_1.__importDefault(require("../../components/Animate
10
10
  const Title_1 = tslib_1.__importDefault(require("../../components/Title/Title"));
11
11
  const utils_1 = require("../../utils");
12
12
  const Arrow_1 = tslib_1.__importDefault(require("./Arrow/Arrow"));
13
+ const i18n_1 = require("./i18n");
13
14
  const useSlider_1 = require("./useSlider");
15
+ const useSliderPagination_1 = require("./useSliderPagination");
14
16
  require("swiper/swiper-bundle.css");
15
17
  const b = (0, utils_1.block)('SliderNewBlock');
16
18
  swiper_1.default.use([swiper_1.Autoplay, swiper_1.A11y, swiper_1.Pagination]);
17
19
  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, }) => {
18
- const { childrenCount, breakpoints, autoplay, onSwiper, onPrev, onNext, isLocked, setIsLocked } = (0, useSlider_1.useSlider)({
20
+ const { autoplay, isLocked, childrenCount, breakpoints, onSwiper, onPrev, onNext, setIsLocked } = (0, useSlider_1.useSlider)({
19
21
  slidesToShow,
20
22
  children,
21
23
  type,
22
24
  autoplayMs,
23
25
  });
26
+ const isA11yControlHidden = Boolean(autoplay);
27
+ const controlTabIndex = isA11yControlHidden ? -1 : 0;
28
+ const paginationProps = (0, useSliderPagination_1.useSliderPagination)({
29
+ enabled: dots,
30
+ isA11yControlHidden,
31
+ controlTabIndex,
32
+ bulletClass: b('dot', dotsClassName),
33
+ bulletActiveClass: b('dot_active'),
34
+ paginationLabel: (0, i18n_1.i18n)('pagination-label'),
35
+ });
24
36
  return (react_1.default.createElement("div", { className: b({
25
37
  'one-slide': childrenCount === 1,
26
38
  'only-arrows': !(title === null || title === void 0 ? void 0 : title.text) && !description && arrows,
@@ -30,14 +42,14 @@ const SliderNewBlock = ({ animated, title, description, type, anchorId, arrows =
30
42
  anchorId && react_1.default.createElement(Anchor_1.default, { id: anchorId }),
31
43
  react_1.default.createElement(Title_1.default, { title: title, subtitle: description, className: b('header', { 'no-description': !description }) }),
32
44
  react_1.default.createElement(AnimateBlock_1.default, { className: b('animate-slides'), animate: animated },
33
- react_1.default.createElement(react_2.Swiper, { className: b('slider', className), onSwiper: onSwiper, pagination: dots && {
34
- clickable: true,
35
- bulletClass: b('dot', dotsClassName),
36
- bulletActiveClass: b('dot_active'),
37
- }, 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_1.default.Children.map(children, (elem, index) => (react_1.default.createElement(react_2.SwiperSlide, { className: b('slide'), key: index }, elem)))),
45
+ react_1.default.createElement(react_2.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: {
46
+ slideLabelMessage: '',
47
+ paginationBulletMessage: (0, i18n_1.i18n)('dot-label', { index: '{{index}}' }),
48
+ } }, paginationProps), react_1.default.Children.map(children, (elem, index) => (react_1.default.createElement(react_2.SwiperSlide, { className: b('slide'), key: index }, ({ isVisible }) => (react_1.default.createElement("div", { "aria-hidden": !isA11yControlHidden && !isVisible }, elem)))))),
38
49
  arrows && !isLocked && (react_1.default.createElement(react_1.Fragment, null,
39
- react_1.default.createElement(Arrow_1.default, { className: b('arrow', { prev: true }), type: "left", onClick: onPrev, size: arrowSize }),
40
- react_1.default.createElement(Arrow_1.default, { className: b('arrow', { next: true }), type: "right", onClick: onNext, size: arrowSize }))),
50
+ react_1.default.createElement("div", { "aria-hidden": isA11yControlHidden },
51
+ react_1.default.createElement(Arrow_1.default, { className: b('arrow', { prev: true }), type: "left", onClick: onPrev, size: arrowSize, extraProps: { tabIndex: controlTabIndex } }),
52
+ react_1.default.createElement(Arrow_1.default, { className: b('arrow', { next: true }), type: "right", onClick: onNext, size: arrowSize, extraProps: { tabIndex: controlTabIndex } })))),
41
53
  react_1.default.createElement("div", { className: b('footer') }, disclaimer ? (react_1.default.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))));
42
54
  };
43
55
  exports.SliderNewBlock = SliderNewBlock;
@@ -1,4 +1,6 @@
1
1
  {
2
2
  "arrow-right": "Next",
3
- "arrow-left": "Previous"
3
+ "arrow-left": "Previous",
4
+ "dot-label": "Page {{index}}",
5
+ "pagination-label": "Pages"
4
6
  }
@@ -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,4 +1,6 @@
1
1
  {
2
2
  "arrow-right": "Дальше",
3
- "arrow-left": "Назад"
3
+ "arrow-left": "Назад",
4
+ "dot-label": "Страница {{index}}",
5
+ "pagination-label": "Страницы"
4
6
  }
@@ -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
- export declare const useSlider: ({ children, autoplayMs, type, slidesToShow, }: React.PropsWithChildren<{
5
- autoplayMs?: number | undefined;
6
- type?: string | undefined;
7
- slidesToShow?: SlidesToShow | undefined;
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 | undefined;
19
+ delay: number;
19
20
  disableOnInteraction: boolean;
20
21
  };
21
22
  };
23
+ export {};
@@ -5,11 +5,13 @@ const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
6
  const models_1 = require("../../models");
7
7
  const utils_1 = require("./utils");
8
- const useSlider = ({ children, autoplayMs, type, slidesToShow, }) => {
8
+ const useSlider = (_a) => {
9
+ var { children, autoplayMs, type } = _a, props = tslib_1.__rest(_a, ["children", "autoplayMs", "type"]);
9
10
  const [slider, setSlider] = (0, react_1.useState)();
10
11
  const [isLocked, setIsLocked] = (0, react_1.useState)(false);
12
+ const slidesToShow = (0, utils_1.useMemoized)(props.slidesToShow);
11
13
  const childrenCount = react_1.default.Children.count(children);
12
- const autoplayEnabled = (0, react_1.useMemo)(() => Boolean(autoplayMs), [autoplayMs]);
14
+ const autoplayEnabled = autoplayMs !== undefined && autoplayMs > 0;
13
15
  const breakpoints = (0, react_1.useMemo)(() => {
14
16
  return (0, utils_1.getSliderResponsiveParams)({
15
17
  contentLength: childrenCount,
@@ -0,0 +1,9 @@
1
+ import { Swiper as SwiperProps } from 'swiper/swiper-react';
2
+ export declare const useSliderPagination: (props: {
3
+ enabled: boolean;
4
+ isA11yControlHidden: boolean;
5
+ controlTabIndex: number;
6
+ bulletClass: string;
7
+ bulletActiveClass: string;
8
+ paginationLabel: string;
9
+ }) => Pick<SwiperProps, 'pagination' | 'onPaginationUpdate'> | undefined;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useSliderPagination = void 0;
4
+ const utils_1 = require("./utils");
5
+ const useSliderPagination = (props) => {
6
+ if (!props.enabled) {
7
+ return undefined;
8
+ }
9
+ const { isA11yControlHidden, controlTabIndex, bulletClass, bulletActiveClass, paginationLabel } = props;
10
+ return {
11
+ pagination: {
12
+ clickable: true,
13
+ bulletClass,
14
+ bulletActiveClass,
15
+ },
16
+ onPaginationUpdate: (slider) => {
17
+ const pagination = slider.pagination.el;
18
+ (0, utils_1.setElementAtrributes)(pagination, {
19
+ role: 'menu',
20
+ 'aria-hidden': isA11yControlHidden,
21
+ 'aria-label': paginationLabel,
22
+ });
23
+ const bullets = pagination.querySelectorAll(`.${bulletClass}`);
24
+ bullets.forEach((bullet) => {
25
+ const isActive = bullet.classList.contains(bulletActiveClass);
26
+ (0, utils_1.setElementAtrributes)(bullet, {
27
+ role: 'menuitemradio',
28
+ 'aria-hidden': isA11yControlHidden,
29
+ 'aria-checked': isActive,
30
+ tabindex: controlTabIndex,
31
+ });
32
+ });
33
+ },
34
+ };
35
+ };
36
+ exports.useSliderPagination = useSliderPagination;
@@ -12,3 +12,5 @@ export interface GetSlidesToShowParams {
12
12
  mobileFullscreen?: boolean;
13
13
  }
14
14
  export declare function getSliderResponsiveParams({ contentLength, slidesToShow, mobileFullscreen, }: GetSlidesToShowParams): Record<number, SwiperOptions>;
15
+ export declare const useMemoized: <T>(value: T) => T;
16
+ export declare const setElementAtrributes: (element: Element, attributes: Record<string, unknown>) => void;
@@ -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;