@gravity-ui/page-constructor 5.27.0 → 5.27.1-alpha.0

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 (88) hide show
  1. package/README.md +54 -0
  2. package/build/cjs/blocks/HeaderSlider/HeaderSlider.css +0 -10
  3. package/build/cjs/blocks/HeaderSlider/HeaderSlider.js +2 -2
  4. package/build/cjs/blocks/Slider/Slider.css +2 -0
  5. package/build/cjs/blocks/Slider/Slider.d.ts +1 -0
  6. package/build/cjs/blocks/Slider/Slider.js +85 -31
  7. package/build/cjs/blocks/Slider/i18n/en.json +3 -1
  8. package/build/cjs/blocks/Slider/i18n/index.d.ts +1 -1
  9. package/build/cjs/blocks/Slider/i18n/ru.json +3 -1
  10. package/build/cjs/blocks/Slider/utils.d.ts +10 -0
  11. package/build/cjs/blocks/Slider/utils.js +85 -1
  12. package/build/cjs/blocks/SliderNew/Arrow/Arrow.css +23 -19
  13. package/build/cjs/blocks/SliderNew/Arrow/Arrow.d.ts +4 -1
  14. package/build/cjs/blocks/SliderNew/Arrow/Arrow.js +5 -4
  15. package/build/cjs/blocks/SliderNew/Slider.css +103 -22
  16. package/build/cjs/blocks/SliderNew/Slider.d.ts +2 -1
  17. package/build/cjs/blocks/SliderNew/Slider.js +22 -9
  18. package/build/cjs/blocks/SliderNew/i18n/en.json +3 -1
  19. package/build/cjs/blocks/SliderNew/i18n/index.d.ts +1 -1
  20. package/build/cjs/blocks/SliderNew/i18n/ru.json +3 -1
  21. package/build/cjs/blocks/SliderNew/useSlider.d.ts +8 -6
  22. package/build/cjs/blocks/SliderNew/useSlider.js +4 -2
  23. package/build/cjs/blocks/SliderNew/useSliderPagination.d.ts +9 -0
  24. package/build/cjs/blocks/SliderNew/useSliderPagination.js +36 -0
  25. package/build/cjs/blocks/SliderNew/utils.d.ts +2 -0
  26. package/build/cjs/blocks/SliderNew/utils.js +13 -1
  27. package/build/cjs/components/FullscreenImage/FullscreenImage.css +48 -8
  28. package/build/cjs/components/FullscreenImage/FullscreenImage.d.ts +5 -0
  29. package/build/cjs/components/FullscreenImage/FullscreenImage.js +17 -3
  30. package/build/cjs/components/Media/Image/Image.d.ts +1 -0
  31. package/build/cjs/components/Media/Image/Image.js +7 -5
  32. package/build/cjs/components/Media/Media.css +4 -0
  33. package/build/cjs/components/Media/Media.d.ts +1 -0
  34. package/build/cjs/components/Media/Media.js +3 -2
  35. package/build/cjs/components/MediaBase/MediaBase.js +1 -1
  36. package/build/cjs/components/ReactPlayer/ReactPlayer.js +1 -1
  37. package/build/cjs/constructor-items.d.ts +1 -1
  38. package/build/cjs/models/constructor-items/blocks.d.ts +2 -1
  39. package/build/cjs/models/constructor-items/blocks.js +1 -0
  40. package/build/cjs/navigation/hooks/useShowBorder.js +3 -2
  41. package/build/cjs/sub-blocks/Content/Content.css +14 -4
  42. package/build/cjs/sub-blocks/ImageCard/ImageCard.css +8 -0
  43. package/build/esm/blocks/HeaderSlider/HeaderSlider.css +0 -10
  44. package/build/esm/blocks/HeaderSlider/HeaderSlider.js +1 -1
  45. package/build/esm/blocks/Slider/Slider.css +2 -0
  46. package/build/esm/blocks/Slider/Slider.d.ts +1 -0
  47. package/build/esm/blocks/Slider/Slider.js +86 -32
  48. package/build/esm/blocks/Slider/i18n/en.json +3 -1
  49. package/build/esm/blocks/Slider/i18n/index.d.ts +1 -1
  50. package/build/esm/blocks/Slider/i18n/ru.json +3 -1
  51. package/build/esm/blocks/Slider/utils.d.ts +10 -0
  52. package/build/esm/blocks/Slider/utils.js +82 -0
  53. package/build/esm/blocks/SliderNew/Arrow/Arrow.css +23 -19
  54. package/build/esm/blocks/SliderNew/Arrow/Arrow.d.ts +4 -1
  55. package/build/esm/blocks/SliderNew/Arrow/Arrow.js +5 -4
  56. package/build/esm/blocks/SliderNew/Slider.css +103 -22
  57. package/build/esm/blocks/SliderNew/Slider.d.ts +2 -1
  58. package/build/esm/blocks/SliderNew/Slider.js +22 -9
  59. package/build/esm/blocks/SliderNew/i18n/en.json +3 -1
  60. package/build/esm/blocks/SliderNew/i18n/index.d.ts +1 -1
  61. package/build/esm/blocks/SliderNew/i18n/ru.json +3 -1
  62. package/build/esm/blocks/SliderNew/useSlider.d.ts +8 -6
  63. package/build/esm/blocks/SliderNew/useSlider.js +6 -3
  64. package/build/esm/blocks/SliderNew/useSliderPagination.d.ts +9 -0
  65. package/build/esm/blocks/SliderNew/useSliderPagination.js +32 -0
  66. package/build/esm/blocks/SliderNew/utils.d.ts +2 -0
  67. package/build/esm/blocks/SliderNew/utils.js +10 -0
  68. package/build/esm/components/FullscreenImage/FullscreenImage.css +48 -8
  69. package/build/esm/components/FullscreenImage/FullscreenImage.d.ts +5 -0
  70. package/build/esm/components/FullscreenImage/FullscreenImage.js +18 -4
  71. package/build/esm/components/Media/Image/Image.d.ts +1 -0
  72. package/build/esm/components/Media/Image/Image.js +7 -5
  73. package/build/esm/components/Media/Media.css +4 -0
  74. package/build/esm/components/Media/Media.d.ts +1 -0
  75. package/build/esm/components/Media/Media.js +3 -2
  76. package/build/esm/components/MediaBase/MediaBase.js +1 -1
  77. package/build/esm/components/ReactPlayer/ReactPlayer.js +1 -1
  78. package/build/esm/constructor-items.d.ts +1 -1
  79. package/build/esm/models/constructor-items/blocks.d.ts +2 -1
  80. package/build/esm/models/constructor-items/blocks.js +1 -0
  81. package/build/esm/navigation/hooks/useShowBorder.js +1 -1
  82. package/build/esm/sub-blocks/Content/Content.css +14 -4
  83. package/build/esm/sub-blocks/ImageCard/ImageCard.css +8 -0
  84. package/package.json +2 -1
  85. package/server/models/constructor-items/blocks.d.ts +2 -1
  86. package/server/models/constructor-items/blocks.js +1 -0
  87. package/styles/mixins.scss +1 -1
  88. package/widget/index.js +1 -1
package/README.md CHANGED
@@ -109,6 +109,42 @@ interface NavigationLogo {
109
109
  }
110
110
  ```
111
111
 
112
+ ### Server utils
113
+
114
+ The package provides a set of server utilities for transforming your content.
115
+
116
+ ```ts
117
+ const {fullTransform} = require('@gravity-ui/page-constructor/server');
118
+
119
+ const {html} = fullTransform(content, {
120
+ lang,
121
+ extractTitle: true,
122
+ allowHTML: true,
123
+ path: __dirname,
124
+ plugins,
125
+ });
126
+ ```
127
+
128
+ Under the hood, a package is used to transform Yandex Flavored Markdown into HTML - `diplodoc/transfrom`, so it is also in peer dependencies
129
+
130
+ You can also use useful utilities in the places you need, for example in your custom components
131
+
132
+ ```ts
133
+ const {
134
+ typografToText,
135
+ typografToHTML,
136
+ yfmTransformer,
137
+ } = require('@gravity-ui/page-constructor/server');
138
+
139
+ const post = {
140
+ title: typografToText(title, lang),
141
+ content: typografToHTML(content, lang),
142
+ description: yfmTransformer(lang, description, {plugins}),
143
+ };
144
+ ```
145
+
146
+ You can find more utilities in this [section](https://github.com/gravity-ui/page-constructor/tree/main/src/text-transform)
147
+
112
148
  ### Custom blocks
113
149
 
114
150
  The page constructor lets you use blocks that are user-defined in their app. Blocks are regular React components.
@@ -316,6 +352,24 @@ npm ci
316
352
  npm run dev
317
353
  ```
318
354
 
355
+ #### Note about Vite
356
+
357
+ ```ts
358
+ import react from '@vitejs/plugin-react-swc';
359
+ import dynamicImport from 'vite-plugin-dynamic-import';
360
+
361
+ export default defineConfig({
362
+ plugins: [
363
+ react(),
364
+ dynamicImport({
365
+ filter: (id) => id.includes('/node_modules/@gravity-ui/page-constructor'),
366
+ }),
367
+ ],
368
+ });
369
+ ```
370
+
371
+ For Vite, you need to install the `vite-plugin-dynamic-import` plugin and configure the config so that dynamic imports work
372
+
319
373
  ## Release flow
320
374
 
321
375
  In usual cases we use two types of commits:
@@ -34,13 +34,6 @@ unpredictable css rules order in build */
34
34
  padding-right: 32px;
35
35
  }
36
36
  }
37
- @media (max-width: 769px) {
38
- .pc-header-slider-block.pc-SliderBlock {
39
- margin-left: -8px;
40
- padding-left: 0;
41
- width: calc(100% + 8px);
42
- }
43
- }
44
37
  @media (max-width: 577px) {
45
38
  .pc-header-slider-block__item-content .pc-header-block__content {
46
39
  padding-left: 0;
@@ -49,7 +42,4 @@ unpredictable css rules order in build */
49
42
  .pc-header-slider-block__item-content .pc-header-block__container-fluid {
50
43
  padding-left: 24px;
51
44
  }
52
- .pc-header-slider-block .slick-track .slick-slide {
53
- max-width: 100%;
54
- }
55
45
  }
@@ -7,14 +7,14 @@ const mobileContext_1 = require("../../context/mobileContext");
7
7
  const models_1 = require("../../models");
8
8
  const utils_1 = require("../../utils");
9
9
  const Header_1 = tslib_1.__importDefault(require("../Header/Header"));
10
- const index_1 = require("../index");
10
+ const unstable_1 = require("../unstable");
11
11
  const b = (0, utils_1.block)('header-slider-block');
12
12
  const HeaderSliderBlock = (_a) => {
13
13
  var { items, arrows } = _a, props = tslib_1.__rest(_a, ["items", "arrows"]);
14
14
  const isMobile = (0, react_1.useContext)(mobileContext_1.MobileContext);
15
15
  const showArrows = isMobile ? false : arrows;
16
16
  return (react_1.default.createElement("div", { className: b('wrapper'), "data-qa": "header-slider" },
17
- react_1.default.createElement(index_1.SliderBlock, Object.assign({}, props, { arrows: showArrows, slidesToShow: 1, type: models_1.SliderType.HeaderCard, animated: false, blockClassName: b(), arrowSize: 20 }), items.map((item, index) => (react_1.default.createElement("div", { key: index, className: b('item'), "data-qa": `header-slider-item-${index + 1}` },
17
+ react_1.default.createElement(unstable_1.SliderNewBlock, Object.assign({}, props, { arrows: showArrows, slidesToShow: 1, type: models_1.SliderType.HeaderCard, animated: false, blockClassName: b(), arrowSize: 20 }), items.map((item, index) => (react_1.default.createElement("div", { key: index, className: b('item'), "data-qa": `header-slider-item-${index + 1}` },
18
18
  react_1.default.createElement(Header_1.default, Object.assign({}, item, { className: b('item-content') }))))))));
19
19
  };
20
20
  exports.HeaderSliderBlock = HeaderSliderBlock;
@@ -105,6 +105,8 @@ unpredictable css rules order in build */
105
105
  height: auto;
106
106
  }
107
107
  .pc-SliderBlock .slick-track .slick-slide > div {
108
+ display: flex;
109
+ width: 100%;
108
110
  height: 100%;
109
111
  }
110
112
  .pc-SliderBlock .slick-arrow {
@@ -9,6 +9,7 @@ export interface SliderProps extends Omit<SliderParams, 'children'>, Refable<HTM
9
9
  dotsClassName?: string;
10
10
  blockClassName?: string;
11
11
  arrowSize?: number;
12
+ initialIndex?: number;
12
13
  }
13
14
  export declare const SliderBlock: (props: React.PropsWithChildren<SliderProps>) => JSX.Element;
14
15
  export default SliderBlock;
@@ -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, initialIndex = 0, } = 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,
@@ -39,11 +44,16 @@ const SliderBlock = (props) => {
39
44
  }));
40
45
  const slidesToShowCount = (0, utils_2.getSlidesToShowCount)(slidesToShow);
41
46
  const slidesCountByBreakpoint = (0, utils_2.getSlidesCountByBreakpoint)(breakpoint, slidesToShow);
42
- const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
47
+ const [currentIndex, setCurrentIndex] = (0, react_1.useState)(initialIndex);
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
- 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 }),
245
+ initialSlide: initialIndex,
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,33 +1,25 @@
1
- .pc-slider-new-block-arrow__button {
1
+ .pc-slider-new-block-arrow__inner {
2
2
  box-shadow: 0px 4px 24px var(--pc-color-sfx-shadow), 0px 2px 8px var(--pc-color-sfx-shadow);
3
3
  }
4
4
 
5
- .pc-slider-new-block-arrow__button:hover {
5
+ .pc-slider-new-block-arrow__inner:hover {
6
6
  box-shadow: 0px 4px 24px var(--g-color-sfx-shadow), 0px 2px 8px var(--g-color-sfx-shadow);
7
7
  cursor: pointer;
8
8
  }
9
9
 
10
10
  /* use this for style redefinitions to awoid problems with
11
11
  unpredictable css rules order in build */
12
- .pc-slider-new-block-arrow__icon-wrapper, .pc-slider-new-block-arrow__button, .pc-slider-new-block-arrow {
12
+ .pc-slider-new-block-arrow__icon-wrapper, .pc-slider-new-block-arrow__inner, .pc-slider-new-block-arrow {
13
13
  display: flex;
14
14
  align-items: center;
15
15
  justify-content: center;
16
16
  }
17
17
 
18
- .pc-slider-new-block-arrow {
19
- width: 42px;
20
- height: 42px;
21
- cursor: pointer;
22
- }
23
18
  @media (max-width: 576px) {
24
19
  .pc-slider-new-block-arrow {
25
20
  display: none;
26
21
  }
27
22
  }
28
- .pc-slider-new-block-arrow_type_left .pc-slider-new-block-arrow__icon-wrapper {
29
- transform: rotate(180deg);
30
- }
31
23
  .pc-slider-new-block-arrow__button {
32
24
  display: inline-block;
33
25
  margin: 0;
@@ -38,13 +30,6 @@ unpredictable css rules order in build */
38
30
  color: inherit;
39
31
  background: none;
40
32
  cursor: pointer;
41
- width: 42px;
42
- height: 42px;
43
- color: var(--g-color-text-secondary);
44
- border-radius: 100%;
45
- background-color: var(--g-color-base-background);
46
- box-shadow: 0 4px 24px var(--pc-color-sfx-shadow), 0 2px 8px var(--pc-color-sfx-shadow);
47
- transition: box-shadow 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), color 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
48
33
  }
49
34
  .pc-slider-new-block-arrow__button:focus {
50
35
  outline: 2px solid var(--g-color-line-focus);
@@ -53,10 +38,29 @@ unpredictable css rules order in build */
53
38
  .pc-slider-new-block-arrow__button:focus:not(:focus-visible) {
54
39
  outline: 0;
55
40
  }
56
- .pc-slider-new-block-arrow:hover .pc-slider-new-block-arrow__button {
41
+ .pc-slider-new-block-arrow__inner {
42
+ width: 42px;
43
+ height: 42px;
44
+ color: var(--g-color-text-secondary);
45
+ border-radius: 100%;
46
+ background-color: var(--g-color-base-background);
47
+ box-shadow: 0 4px 24px var(--pc-color-sfx-shadow), 0 2px 8px var(--pc-color-sfx-shadow);
48
+ transition: box-shadow 0.3s cubic-bezier(0.22, 0.61, 0.36, 1), color 1s cubic-bezier(0.22, 0.61, 0.36, 1);
49
+ }
50
+ .pc-slider-new-block-arrow__inner_type_left .pc-slider-new-block-arrow__icon-wrapper {
51
+ transform: rotate(180deg);
52
+ }
53
+ .pc-slider-new-block-arrow__inner:hover {
57
54
  color: var(--g-color-text-primary);
58
55
  box-shadow: 0 2px 12px var(--pc-color-sfx-shadow), 0 4px 24px var(--pc-color-sfx-shadow);
59
56
  }
57
+ .pc-slider-new-block-arrow__inner_transparent {
58
+ background-color: transparent;
59
+ box-shadow: none;
60
+ }
61
+ .pc-slider-new-block-arrow__inner_transparent:hover {
62
+ box-shadow: none;
63
+ }
60
64
  .pc-slider-new-block-arrow__icon {
61
65
  position: relative;
62
66
  }
@@ -1,9 +1,12 @@
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;
6
+ transparent?: boolean;
5
7
  onClick?: () => void;
6
8
  size?: number;
9
+ extraProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
7
10
  }
8
- declare const Arrow: ({ type, onClick, className, size }: ArrowProps & ClassNameProps) => JSX.Element;
11
+ declare const Arrow: ({ type, transparent, onClick, className, size, extraProps, }: ArrowProps & ClassNameProps) => JSX.Element;
9
12
  export default Arrow;
@@ -6,8 +6,9 @@ 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}`) },
11
- react_1.default.createElement("span", { className: b('icon-wrapper') },
12
- react_1.default.createElement(ToggleArrow_1.default, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') })))));
9
+ const Arrow = ({ type, transparent, onClick, className, size = 16, extraProps, }) => (react_1.default.createElement("div", { className: b(null, className) },
10
+ react_1.default.createElement("button", Object.assign({ className: b('button'), onClick: onClick, "aria-label": (0, i18n_1.i18n)(`arrow-${type}`) }, extraProps),
11
+ react_1.default.createElement("div", { className: b('inner', { type, transparent }) },
12
+ react_1.default.createElement("span", { className: b('icon-wrapper') },
13
+ react_1.default.createElement(ToggleArrow_1.default, { size: size, type: 'horizontal', iconType: "navigation", className: b('icon') }))))));
13
14
  exports.default = Arrow;