@vkontakte/vkui 7.1.2 → 7.1.3

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 (129) hide show
  1. package/dist/components/Alert/Alert.js +5 -5
  2. package/dist/components/Alert/Alert.js.map +1 -1
  3. package/dist/components/CarouselBase/CarouselBase.d.ts.map +1 -1
  4. package/dist/components/CarouselBase/CarouselBase.js +1 -4
  5. package/dist/components/CarouselBase/CarouselBase.js.map +1 -1
  6. package/dist/components/FormItem/FormItem.d.ts.map +1 -1
  7. package/dist/components/FormItem/FormItem.js +1 -0
  8. package/dist/components/FormItem/FormItem.js.map +1 -1
  9. package/dist/components/HorizontalScroll/HorizontalScroll.d.ts +1 -1
  10. package/dist/components/HorizontalScroll/HorizontalScroll.d.ts.map +1 -1
  11. package/dist/components/HorizontalScroll/HorizontalScroll.js +17 -18
  12. package/dist/components/HorizontalScroll/HorizontalScroll.js.map +1 -1
  13. package/dist/components/ModalCard/ModalCardInternal.d.ts +1 -1
  14. package/dist/components/ModalCard/ModalCardInternal.d.ts.map +1 -1
  15. package/dist/components/ModalCard/ModalCardInternal.js +4 -3
  16. package/dist/components/ModalCard/ModalCardInternal.js.map +1 -1
  17. package/dist/components/ModalCard/types.d.ts +7 -0
  18. package/dist/components/ModalCard/types.d.ts.map +1 -1
  19. package/dist/components/ModalCard/types.js.map +1 -1
  20. package/dist/components/ModalPage/ModalPageInternal.d.ts +1 -1
  21. package/dist/components/ModalPage/ModalPageInternal.d.ts.map +1 -1
  22. package/dist/components/ModalPage/ModalPageInternal.js +4 -3
  23. package/dist/components/ModalPage/ModalPageInternal.js.map +1 -1
  24. package/dist/components/ModalPage/types.d.ts +7 -0
  25. package/dist/components/ModalPage/types.d.ts.map +1 -1
  26. package/dist/components/ModalPage/types.js.map +1 -1
  27. package/dist/components/OnboardingTooltip/OnboardingTooltip.d.ts +7 -2
  28. package/dist/components/OnboardingTooltip/OnboardingTooltip.d.ts.map +1 -1
  29. package/dist/components/OnboardingTooltip/OnboardingTooltip.js +32 -13
  30. package/dist/components/OnboardingTooltip/OnboardingTooltip.js.map +1 -1
  31. package/dist/components/Pagination/Pagination.d.ts +6 -0
  32. package/dist/components/Pagination/Pagination.d.ts.map +1 -1
  33. package/dist/components/Pagination/Pagination.js +5 -3
  34. package/dist/components/Pagination/Pagination.js.map +1 -1
  35. package/dist/components/Pagination/PaginationPage/PaginationPageButton.d.ts.map +1 -1
  36. package/dist/components/Pagination/PaginationPage/PaginationPageButton.js +4 -4
  37. package/dist/components/Pagination/PaginationPage/PaginationPageButton.js.map +1 -1
  38. package/dist/components/Removable/Removable.d.ts +5 -1
  39. package/dist/components/Removable/Removable.d.ts.map +1 -1
  40. package/dist/components/Removable/Removable.js +4 -3
  41. package/dist/components/Removable/Removable.js.map +1 -1
  42. package/dist/components/TabbarItem/TabbarItem.d.ts +1 -1
  43. package/dist/components/TabbarItem/TabbarItem.d.ts.map +1 -1
  44. package/dist/components/TabbarItem/TabbarItem.js +16 -4
  45. package/dist/components/TabbarItem/TabbarItem.js.map +1 -1
  46. package/dist/components/TooltipBase/TooltipBase.d.ts +6 -1
  47. package/dist/components/TooltipBase/TooltipBase.d.ts.map +1 -1
  48. package/dist/components/TooltipBase/TooltipBase.js +3 -1
  49. package/dist/components/TooltipBase/TooltipBase.js.map +1 -1
  50. package/dist/components.css +1 -1
  51. package/dist/components.css.map +1 -1
  52. package/dist/cssm/components/Alert/Alert.js +5 -5
  53. package/dist/cssm/components/Alert/Alert.js.map +1 -1
  54. package/dist/cssm/components/CarouselBase/CarouselBase.js +1 -4
  55. package/dist/cssm/components/CarouselBase/CarouselBase.js.map +1 -1
  56. package/dist/cssm/components/FormItem/FormItem.js +1 -0
  57. package/dist/cssm/components/FormItem/FormItem.js.map +1 -1
  58. package/dist/cssm/components/HorizontalScroll/HorizontalCellShowMore/HorizontalCellShowMore.module.css +1 -0
  59. package/dist/cssm/components/HorizontalScroll/HorizontalScroll.js +18 -14
  60. package/dist/cssm/components/HorizontalScroll/HorizontalScroll.js.map +1 -1
  61. package/dist/cssm/components/IconButton/IconButton.module.css +6 -0
  62. package/dist/cssm/components/ModalCard/ModalCardInternal.js +2 -2
  63. package/dist/cssm/components/ModalCard/ModalCardInternal.js.map +1 -1
  64. package/dist/cssm/components/ModalCard/types.js.map +1 -1
  65. package/dist/cssm/components/ModalPage/ModalPageInternal.js +2 -2
  66. package/dist/cssm/components/ModalPage/ModalPageInternal.js.map +1 -1
  67. package/dist/cssm/components/ModalPage/types.js.map +1 -1
  68. package/dist/cssm/components/OnboardingTooltip/OnboardingTooltip.js +21 -7
  69. package/dist/cssm/components/OnboardingTooltip/OnboardingTooltip.js.map +1 -1
  70. package/dist/cssm/components/OnboardingTooltip/OnboardingTooltip.module.css +15 -0
  71. package/dist/cssm/components/Pagination/Pagination.js +4 -2
  72. package/dist/cssm/components/Pagination/Pagination.js.map +1 -1
  73. package/dist/cssm/components/Pagination/PaginationPage/PaginationPageButton.js +4 -4
  74. package/dist/cssm/components/Pagination/PaginationPage/PaginationPageButton.js.map +1 -1
  75. package/dist/cssm/components/PanelHeaderButton/PanelHeaderButton.module.css +6 -0
  76. package/dist/cssm/components/Removable/Removable.js +2 -2
  77. package/dist/cssm/components/Removable/Removable.js.map +1 -1
  78. package/dist/cssm/components/Removable/Removable.module.css +3 -0
  79. package/dist/cssm/components/Search/Search.module.css +1 -0
  80. package/dist/cssm/components/SimpleCell/SimpleCell.module.css +2 -0
  81. package/dist/cssm/components/SubnavigationButton/SubnavigationButton.module.css +1 -0
  82. package/dist/cssm/components/TabbarItem/TabbarItem.js +13 -3
  83. package/dist/cssm/components/TabbarItem/TabbarItem.js.map +1 -1
  84. package/dist/cssm/components/TooltipBase/TooltipBase.js +2 -1
  85. package/dist/cssm/components/TooltipBase/TooltipBase.js.map +1 -1
  86. package/dist/cssm/components/TooltipBase/TooltipBase.module.css +1 -0
  87. package/dist/cssm/hooks/useFocusTrap.js +1 -1
  88. package/dist/cssm/hooks/useFocusTrap.js.map +1 -1
  89. package/dist/cssm/types.js.map +1 -1
  90. package/dist/hooks/useFocusTrap.js +1 -1
  91. package/dist/hooks/useFocusTrap.js.map +1 -1
  92. package/dist/types.d.ts +1 -1
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js.map +1 -1
  95. package/dist/vkui.css +1 -1
  96. package/dist/vkui.css.map +1 -1
  97. package/package.json +1 -1
  98. package/src/components/Alert/Alert.tsx +5 -5
  99. package/src/components/CarouselBase/CarouselBase.tsx +1 -6
  100. package/src/components/FormItem/FormItem.tsx +1 -0
  101. package/src/components/HorizontalScroll/HorizontalCellShowMore/HorizontalCellShowMore.module.css +1 -0
  102. package/src/components/HorizontalScroll/HorizontalScroll.tsx +21 -15
  103. package/src/components/IconButton/IconButton.module.css +5 -0
  104. package/src/components/ModalCard/ModalCardInternal.tsx +5 -1
  105. package/src/components/ModalCard/types.ts +7 -0
  106. package/src/components/ModalPage/ModalPageInternal.tsx +2 -1
  107. package/src/components/ModalPage/types.ts +7 -0
  108. package/src/components/OnboardingTooltip/OnboardingTooltip.module.css +13 -0
  109. package/src/components/OnboardingTooltip/OnboardingTooltip.tsx +36 -8
  110. package/src/components/Pagination/Pagination.tsx +19 -5
  111. package/src/components/Pagination/PaginationPage/PaginationPageButton.tsx +3 -3
  112. package/src/components/PanelHeaderButton/PanelHeaderButton.module.css +5 -0
  113. package/src/components/Removable/Removable.module.css +3 -0
  114. package/src/components/Removable/Removable.tsx +12 -1
  115. package/src/components/Search/Search.module.css +1 -0
  116. package/src/components/SimpleCell/SimpleCell.module.css +2 -0
  117. package/src/components/SubnavigationButton/SubnavigationButton.module.css +1 -0
  118. package/src/components/TabbarItem/TabbarItem.tsx +18 -1
  119. package/src/components/TooltipBase/TooltipBase.module.css +1 -0
  120. package/src/components/TooltipBase/TooltipBase.tsx +7 -1
  121. package/src/hooks/useFocusTrap.ts +1 -1
  122. package/src/types.ts +2 -1
  123. package/dist/components/Pagination/utils.d.ts +0 -2
  124. package/dist/components/Pagination/utils.d.ts.map +0 -1
  125. package/dist/components/Pagination/utils.js +0 -5
  126. package/dist/components/Pagination/utils.js.map +0 -1
  127. package/dist/cssm/components/Pagination/utils.js +0 -5
  128. package/dist/cssm/components/Pagination/utils.js.map +0 -1
  129. package/src/components/Pagination/utils.ts +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "type": "module",
3
- "version": "7.1.2",
3
+ "version": "7.1.3",
4
4
  "name": "@vkontakte/vkui",
5
5
  "description": "VKUI library",
6
6
  "module": "./dist/index.js",
@@ -193,6 +193,11 @@ export const Alert = ({
193
193
  </IconButton>
194
194
  )}
195
195
  </div>
196
+ {isDismissButtonVisible && dismissButtonMode === 'outside' && (
197
+ <ModalDismissButton onClick={close} data-testid={dismissButtonTestId}>
198
+ {dismissLabel}
199
+ </ModalDismissButton>
200
+ )}
196
201
  <AlertActions
197
202
  actions={actions}
198
203
  actionsAlign={actionsAlign}
@@ -200,11 +205,6 @@ export const Alert = ({
200
205
  renderAction={renderAction}
201
206
  onItemClick={onItemClick}
202
207
  />
203
- {isDismissButtonVisible && dismissButtonMode === 'outside' && (
204
- <ModalDismissButton onClick={close} data-testid={dismissButtonTestId}>
205
- {dismissLabel}
206
- </ModalDismissButton>
207
- )}
208
208
  </FocusTrap>
209
209
  </PopoutWrapper>
210
210
  </AppRootPortal>
@@ -335,12 +335,7 @@ export const CarouselBase = ({
335
335
 
336
336
  const simpleSlideChangePerform = () => {
337
337
  const { snaps } = slidesManager.current;
338
- const startPoint = shiftXCurrentRef.current;
339
- const endPoint = snaps[slideIndex];
340
- const distance = endPoint - startPoint;
341
- addToAnimationQueue(
342
- getAnimateFunction((progress) => transformCssStyles(startPoint + distance * progress)),
343
- );
338
+ requestTransform(snaps[slideIndex], true);
344
339
  };
345
340
 
346
341
  useIsomorphicLayoutEffect(
@@ -145,6 +145,7 @@ export const FormItem: React.FC<FormItemProps> & {
145
145
  }}
146
146
  removePlaceholder={removePlaceholder}
147
147
  indent={removable === 'indent'}
148
+ noPadding={noPadding}
148
149
  >
149
150
  <div className={classNames(styles.removable, 'vkuiInternalFormItem__removable')}>
150
151
  {wrappedChildren}
@@ -12,6 +12,7 @@
12
12
  }
13
13
 
14
14
  .body {
15
+ box-sizing: content-box;
15
16
  display: flex;
16
17
  flex-direction: column;
17
18
  justify-content: center;
@@ -178,7 +178,6 @@ export const HorizontalScroll = ({
178
178
  scrollOnAnyWheel = false,
179
179
  prevButtonTestId,
180
180
  nextButtonTestId,
181
- getRootRef,
182
181
  ...restProps
183
182
  }: HorizontalScrollProps): React.ReactNode => {
184
183
  const [canScrollLeft, setCanScrollLeft] = React.useState(false);
@@ -192,8 +191,6 @@ export const HorizontalScroll = ({
192
191
 
193
192
  const scrollerRef = useExternRef(getRef, directionRef);
194
193
 
195
- const rootRef = useExternRef(getRootRef);
196
-
197
194
  const animationQueue = React.useRef<VoidFunction[]>([]);
198
195
 
199
196
  const hasPointer = useAdaptivityHasPointer();
@@ -250,31 +247,40 @@ export const HorizontalScroll = ({
250
247
 
251
248
  useIsomorphicLayoutEffect(
252
249
  function addWheelEventHandler() {
253
- if (!rootRef.current) {
250
+ const scrollEl = scrollerRef.current;
251
+ if (!scrollEl) {
254
252
  return noop;
255
253
  }
256
254
  /**
257
255
  * Прокрутка с помощью любого колеса мыши
258
256
  */
259
257
  const onWheel = (e: WheelEvent) => {
260
- const left = e.deltaX + (scrollOnAnyWheel ? e.deltaY : 0);
261
- scrollerRef.current!.scrollBy({ left, behavior: 'auto' });
262
- if (e.deltaY && scrollOnAnyWheel) {
263
- e.preventDefault();
264
- }
258
+ scrollerRef.current!.scrollBy({ left: e.deltaX + e.deltaY, behavior: 'auto' });
259
+ e.preventDefault();
265
260
  };
261
+
266
262
  const listenerOptions = { passive: false };
267
- rootRef.current?.addEventListener('wheel', onWheel, listenerOptions);
268
- // @ts-expect-error: TS2769 В интерфейсе EventListenerOptions для wheel нет passive свойства
269
- return () => rootRef.current?.removeEventListener('wheel', onWheel, listenerOptions);
263
+
264
+ if (scrollOnAnyWheel) {
265
+ scrollEl.addEventListener('wheel', onWheel, listenerOptions);
266
+ }
267
+ scrollEl.addEventListener('scroll', calculateArrowsVisibility, listenerOptions);
268
+
269
+ return () => {
270
+ if (scrollOnAnyWheel) {
271
+ // @ts-expect-error: TS2769 В интерфейсе EventListenerOptions для wheel нет passive свойства
272
+ scrollEl.removeEventListener('wheel', onWheel, listenerOptions);
273
+ }
274
+ // @ts-expect-error: TS2769 В интерфейсе EventListenerOptions для scroll нет passive свойства
275
+ scrollEl.removeEventListener('scroll', calculateArrowsVisibility, listenerOptions);
276
+ };
270
277
  },
271
- [rootRef, scrollOnAnyWheel, scrollerRef],
278
+ [scrollOnAnyWheel, calculateArrowsVisibility, scrollerRef],
272
279
  );
273
280
 
274
281
  return (
275
282
  <RootComponent
276
283
  {...restProps}
277
- getRootRef={rootRef}
278
284
  baseClassName={classNames(
279
285
  styles.host,
280
286
  'vkuiInternalHorizontalScroll',
@@ -306,7 +312,7 @@ export const HorizontalScroll = ({
306
312
  onClick={scrollToRight}
307
313
  />
308
314
  )}
309
- <div className={styles.in} ref={scrollerRef} onScroll={calculateArrowsVisibility}>
315
+ <div className={styles.in} ref={scrollerRef}>
310
316
  <div className={styles.inWrapper}>{children}</div>
311
317
  </div>
312
318
  </RootComponent>
@@ -18,6 +18,11 @@
18
18
  border-radius: 9999px;
19
19
  }
20
20
 
21
+ /* stylelint-disable-next-line selector-pseudo-class-disallowed-list */
22
+ .host :global(.vkuiIcon) {
23
+ box-sizing: content-box;
24
+ }
25
+
21
26
  .sizeYCompact {
22
27
  block-size: 44px;
23
28
  }
@@ -70,6 +70,7 @@ export const ModalCardInternal = ({
70
70
  onOpened,
71
71
  onClose = noop,
72
72
  onClosed,
73
+ disableFocusTrap,
73
74
  ...restProps
74
75
  }: ModalCardInternalProps): ReactNode => {
75
76
  const platform = usePlatform();
@@ -136,7 +137,10 @@ export const ModalCardInternal = ({
136
137
  );
137
138
 
138
139
  useScrollLock(!hidden);
139
- useFocusTrap(ref, { autoFocus: !noFocusToDialog, disabled: !opened || hidden });
140
+ useFocusTrap(ref, {
141
+ autoFocus: !noFocusToDialog,
142
+ disabled: !opened || hidden || disableFocusTrap,
143
+ });
140
144
 
141
145
  return (
142
146
  <ModalOutlet hidden={hidden} isDesktop={isDesktop} onKeyDown={handleEscKeyDown}>
@@ -1,4 +1,5 @@
1
1
  import type { UIEvent } from 'react';
2
+ import { type UseFocusTrapProps } from '../../hooks/useFocusTrap';
2
3
  import type { NavIdProps } from '../../lib/getNavId';
3
4
  import type { UseBottomSheetHandlers } from '../../lib/sheet';
4
5
  import type { ModalCardBaseProps } from '../ModalCardBase/ModalCardBase';
@@ -48,4 +49,10 @@ export interface ModalCardProps
48
49
  * Будет вызвано при окончательном закрытии модалки.
49
50
  */
50
51
  onClosed?: VoidFunction;
52
+ /**
53
+ * Позволяет отключить захват фокуса.
54
+ *
55
+ * Нужно использовать, когда поверх одной модалки открывается другая, чтобы два `FocusTrap` не конфликтовали
56
+ */
57
+ disableFocusTrap?: UseFocusTrapProps['disabled'];
51
58
  }
@@ -75,6 +75,7 @@ export const ModalPageInternal = ({
75
75
  onOpened,
76
76
  onClose = noop,
77
77
  onClosed,
78
+ disableFocusTrap,
78
79
  ...restProps
79
80
  }: ModalPageInternalProps) => {
80
81
  const { hasCustomPanelHeaderAfter } = useConfigProvider();
@@ -171,7 +172,7 @@ export const ModalPageInternal = ({
171
172
  autoFocus={!noFocusToDialog}
172
173
  role="dialog"
173
174
  aria-modal="true"
174
- disabled={!opened || hidden}
175
+ disabled={!opened || hidden || disableFocusTrap}
175
176
  className={classNames(
176
177
  className,
177
178
  styles.host,
@@ -1,4 +1,5 @@
1
1
  import type { CSSProperties, ReactNode, Ref, UIEvent } from 'react';
2
+ import { type UseFocusTrapProps } from '../../hooks/useFocusTrap';
2
3
  import type { NavIdProps } from '../../lib/getNavId';
3
4
  import type { HTMLAttributesWithRootRef } from '../../types';
4
5
 
@@ -109,4 +110,10 @@ export interface ModalPageProps
109
110
  * Будет вызвано при окончательном закрытии модалки.
110
111
  */
111
112
  onClosed?: VoidFunction;
113
+ /**
114
+ * Позволяет отключить захват фокуса.
115
+ *
116
+ * Нужно использовать, когда поверх одной модалки открывается другая, чтобы два `FocusTrap` не конфликтовали
117
+ */
118
+ disableFocusTrap?: UseFocusTrapProps['disabled'];
112
119
  }
@@ -4,4 +4,17 @@
4
4
  inset-block-start: 0;
5
5
  inline-size: 100%;
6
6
  block-size: 100%;
7
+ padding: 0;
8
+ margin: 0;
9
+ appearance: none;
10
+ -webkit-touch-callout: none;
11
+ -webkit-user-drag: none;
12
+ cursor: default;
13
+ background: transparent;
14
+ border: 0;
15
+ }
16
+
17
+ .overlay:focus,
18
+ .overlay:focus-visible {
19
+ outline: none;
7
20
  }
@@ -4,6 +4,7 @@ import * as React from 'react';
4
4
  import { hasReactNode } from '@vkontakte/vkjs';
5
5
  import { mergeStyle } from '../../helpers/mergeStyle';
6
6
  import { useExternRef } from '../../hooks/useExternRef';
7
+ import { type UseFocusTrapProps } from '../../hooks/useFocusTrap';
7
8
  import { usePatchChildren } from '../../hooks/usePatchChildren';
8
9
  import { createPortal } from '../../lib/createPortal';
9
10
  import {
@@ -18,6 +19,7 @@ import { useIsomorphicLayoutEffect } from '../../lib/useIsomorphicLayoutEffect';
18
19
  import { warnOnce } from '../../lib/warnOnce';
19
20
  import { DEFAULT_ARROW_HEIGHT, DEFAULT_ARROW_PADDING } from '../FloatingArrow/DefaultIcon';
20
21
  import type { FloatingArrowProps } from '../FloatingArrow/FloatingArrow';
22
+ import { FocusTrap } from '../FocusTrap/FocusTrap';
21
23
  import { useNavTransition } from '../NavTransitionContext/NavTransitionContext';
22
24
  import { TOOLTIP_MAX_WIDTH, TooltipBase, type TooltipBaseProps } from '../TooltipBase/TooltipBase';
23
25
  import { onboardingTooltipContainerAttr } from './OnboardingTooltipContainer';
@@ -58,7 +60,8 @@ type AllowedFloatingArrowProps = {
58
60
  export interface OnboardingTooltipProps
59
61
  extends AllowedFloatingComponentProps,
60
62
  AllowedTooltipBaseProps,
61
- AllowedFloatingArrowProps {
63
+ AllowedFloatingArrowProps,
64
+ Pick<UseFocusTrapProps, 'restoreFocus'> {
62
65
  /**
63
66
  * Скрывает стрелку, указывающую на якорный элемент.
64
67
  */
@@ -67,15 +70,19 @@ export interface OnboardingTooltipProps
67
70
  * Callback, который вызывается при клике по любому месту в пределах экрана.
68
71
  */
69
72
  onClose?: (this: void) => void;
73
+ /**
74
+ * [a11y] Метка для подложки-кнопки, для описания того, что произойдёт при клике.
75
+ */
76
+ overlayLabel?: string;
70
77
  }
71
78
 
72
79
  /**
73
80
  * @see https://vkcom.github.io/VKUI/#/Tooltip
74
81
  */
75
82
  export const OnboardingTooltip = ({
76
- id: idProp,
83
+ 'id': idProp,
77
84
  children,
78
- shown: shownProp = true,
85
+ 'shown': shownProp = true,
79
86
  arrowPadding = DEFAULT_ARROW_PADDING,
80
87
  arrowHeight = DEFAULT_ARROW_HEIGHT,
81
88
  offsetByMainAxis = 0,
@@ -83,13 +90,18 @@ export const OnboardingTooltip = ({
83
90
  arrowOffset = 0,
84
91
  isStaticArrowOffset = false,
85
92
  onClose,
86
- placement: placementProp = 'bottom-start',
93
+ 'placement': placementProp = 'bottom-start',
87
94
  maxWidth = TOOLTIP_MAX_WIDTH,
88
- style: styleProp,
95
+ 'style': styleProp,
89
96
  getRootRef,
90
97
  disableArrow = false,
91
98
  onPlacementChange,
92
99
  disableFlipMiddleware = false,
100
+ overlayLabel = 'Закрыть',
101
+ title,
102
+ 'aria-label': ariaLabel,
103
+ 'aria-labelledby': ariaLabelledBy,
104
+ restoreFocus,
93
105
  ...restProps
94
106
  }: OnboardingTooltipProps): React.ReactNode => {
95
107
  const generatedId = React.useId();
@@ -130,6 +142,13 @@ export const OnboardingTooltip = ({
130
142
 
131
143
  usePlacementChangeCallback(placementProp, resolvedPlacement, onPlacementChange);
132
144
 
145
+ const titleId = React.useId();
146
+ if (process.env.NODE_ENV === 'development' && !title && !ariaLabel && !ariaLabelledBy) {
147
+ warn(
148
+ 'Если "title" не используется, то необходимо задать либо "aria-label", либо "aria-labelledby" (см. правило axe aria-dialog-name)',
149
+ );
150
+ }
151
+
133
152
  let tooltip: React.ReactPortal | null = null;
134
153
  if (shown) {
135
154
  const floatingStyle = convertFloatingDataToReactCSSProperties(
@@ -139,10 +158,20 @@ export const OnboardingTooltip = ({
139
158
  );
140
159
 
141
160
  tooltip = createPortal(
142
- <>
161
+ <FocusTrap
162
+ role="dialog"
163
+ aria-modal="true"
164
+ aria-label={ariaLabel}
165
+ aria-labelledby={title ? titleId : ariaLabel ? undefined : ariaLabelledBy}
166
+ onClose={onClose}
167
+ restoreFocus={restoreFocus}
168
+ >
169
+ <button aria-label={overlayLabel} className={styles.overlay} onClickCapture={onClose} />
143
170
  <TooltipBase
144
171
  {...restProps}
145
172
  id={tooltipId}
173
+ title={title}
174
+ titleId={title ? titleId : undefined}
146
175
  getRootRef={tooltipRef}
147
176
  style={mergeStyle(floatingStyle, styleProp)}
148
177
  maxWidth={maxWidth}
@@ -158,8 +187,7 @@ export const OnboardingTooltip = ({
158
187
  }
159
188
  }
160
189
  />
161
- <div className={styles.overlay} onClickCapture={onClose} />
162
- </>,
190
+ </FocusTrap>,
163
191
  tooltipContainer,
164
192
  );
165
193
  }
@@ -17,7 +17,6 @@ import {
17
17
  PaginationPageButton,
18
18
  } from './PaginationPage/PaginationPageButton';
19
19
  import { PaginationPageEllipsis } from './PaginationPage/PaginationPageEllipsis';
20
- import { getPageLabelDefault } from './utils';
21
20
  import styles from './Pagination.module.css';
22
21
 
23
22
  export interface PaginationProps extends Omit<HTMLAttributesWithRootRef<HTMLElement>, 'onChange'> {
@@ -76,6 +75,12 @@ export interface PaginationProps extends Omit<HTMLAttributesWithRootRef<HTMLElem
76
75
  nextButtonLabel?: string;
77
76
  /**
78
77
  * [a11y] Функция для переопределения и/или локализации метки кнопки страницы.
78
+ *
79
+ * > Note: По возможности лучше не использовать,
80
+ * так как компонент и так проставляет номер страницы в разметку,
81
+ * что достаточно для пользователей скринридеров.
82
+ * Дополнительная информация скорее будет избыточна,
83
+ * так как будет зачитываться для каждой кнопки при перемещении по списку.
79
84
  */
80
85
  getPageLabel?: (isCurrent: boolean) => string;
81
86
  onChange?: (page: number, event: React.MouseEvent<HTMLElement>) => void;
@@ -117,8 +122,8 @@ export const Pagination = ({
117
122
  prevButtonCaption = 'Назад',
118
123
  nextButtonCaption = 'Вперёд',
119
124
  navigationButtonsStyle = 'icon',
120
- getPageLabel = getPageLabelDefault,
121
- navigationLabel = 'Навигация по страницам',
125
+ getPageLabel,
126
+ navigationLabel = 'Страницы',
122
127
  navigationLabelComponent = 'h2',
123
128
  prevButtonLabel = 'Перейти на предыдущую страницу',
124
129
  nextButtonLabel = 'Перейти на следующую страницу',
@@ -204,9 +209,18 @@ export const Pagination = ({
204
209
  [currentPage, disabled, getPageLabel, handleClick, renderPageButton, sizeY, pageButtonTestId],
205
210
  );
206
211
 
212
+ const navigationLabelId = React.useId();
213
+
207
214
  return (
208
- <RootComponent Component="nav" role="navigation" {...resetProps}>
209
- <VisuallyHidden Component={navigationLabelComponent}>{navigationLabel}</VisuallyHidden>
215
+ <RootComponent
216
+ Component="nav"
217
+ role="navigation"
218
+ aria-labelledby={navigationLabelId}
219
+ {...resetProps}
220
+ >
221
+ <VisuallyHidden id={navigationLabelId} Component={navigationLabelComponent}>
222
+ {navigationLabel}
223
+ </VisuallyHidden>
210
224
  <ul className={styles.list}>
211
225
  <li className={styles.prevButtonContainer}>
212
226
  <PaginationNavigationButton
@@ -5,7 +5,6 @@ import { Tappable, type TappableProps } from '../../Tappable/Tappable';
5
5
  import { Text } from '../../Typography/Text/Text';
6
6
  import { VisuallyHidden } from '../../VisuallyHidden/VisuallyHidden';
7
7
  import type { PaginationProps } from '../Pagination';
8
- import { getPageLabelDefault } from '../utils';
9
8
  import { getPaginationPageClassNames } from './usePaginationPageClasses';
10
9
  import styles from './PaginationPage.module.css';
11
10
 
@@ -26,7 +25,7 @@ const getTappablePropsFromPaginationPage = (
26
25
  ): TappableProps & { 'data-page': number } => {
27
26
  const {
28
27
  isCurrent = false,
29
- getPageLabel = getPageLabelDefault,
28
+ getPageLabel,
30
29
  children,
31
30
  className,
32
31
  disabled,
@@ -40,6 +39,7 @@ const getTappablePropsFromPaginationPage = (
40
39
  sizeY,
41
40
  });
42
41
 
42
+ const pageLabel = getPageLabel?.(isCurrent);
43
43
  return {
44
44
  'className': classNames(pageClassNames, className),
45
45
  'activeMode': styles.stateActive,
@@ -49,7 +49,7 @@ const getTappablePropsFromPaginationPage = (
49
49
  'disabled': disabled,
50
50
  'children': (
51
51
  <Text normalize={false}>
52
- <VisuallyHidden>{getPageLabel(isCurrent)} </VisuallyHidden>
52
+ {pageLabel && <VisuallyHidden>{pageLabel} </VisuallyHidden>}
53
53
  {children}
54
54
  </Text>
55
55
  ),
@@ -10,6 +10,11 @@
10
10
  position: relative;
11
11
  }
12
12
 
13
+ /* stylelint-disable-next-line selector-pseudo-class-disallowed-list */
14
+ .host :global(.vkuiIcon) {
15
+ box-sizing: content-box;
16
+ }
17
+
13
18
  .host[disabled] {
14
19
  opacity: 0.6;
15
20
  }
@@ -2,6 +2,9 @@
2
2
  position: relative;
3
3
  display: flex;
4
4
  align-items: center;
5
+ }
6
+
7
+ .withPadding {
5
8
  padding-inline-start: var(--vkui--size_base_padding_horizontal--regular);
6
9
  }
7
10
 
@@ -150,6 +150,10 @@ interface RemovableOwnProps
150
150
  * @since 5.4.0
151
151
  */
152
152
  indent?: boolean;
153
+ /**
154
+ * Убирает базовые отступы для базовой платформы
155
+ */
156
+ noPadding?: boolean;
153
157
  children?: React.ReactNode | ((renderProps: RemovableIosRenderProps) => React.ReactNode);
154
158
  }
155
159
 
@@ -165,6 +169,7 @@ export const Removable = ({
165
169
  toggleButtonTestId,
166
170
  removeButtonTestId,
167
171
  disabled,
172
+ noPadding,
168
173
  ...restProps
169
174
  }: RemovableOwnProps): React.ReactNode => {
170
175
  const platform = usePlatform();
@@ -186,7 +191,13 @@ export const Removable = ({
186
191
  )}
187
192
  >
188
193
  {platform !== 'ios' && (
189
- <div className={classNames(styles.content, 'vkuiInternalRemovable__content')}>
194
+ <div
195
+ className={classNames(
196
+ styles.content,
197
+ !noPadding && styles.withPadding,
198
+ 'vkuiInternalRemovable__content',
199
+ )}
200
+ >
190
201
  {typeof children === 'function' ? children({ isRemoving: false }) : children}
191
202
 
192
203
  <IconButton
@@ -1,4 +1,5 @@
1
1
  .host {
2
+ box-sizing: content-box;
2
3
  display: flex;
3
4
  overflow: hidden;
4
5
  -webkit-tap-highlight-color: transparent;
@@ -32,6 +32,7 @@
32
32
 
33
33
  /* stylelint-disable-next-line selector-pseudo-class-disallowed-list */
34
34
  .before > :global(.vkuiIcon) {
35
+ box-sizing: content-box;
35
36
  padding-inline-end: var(--vkui--spacing_size_xs);
36
37
  }
37
38
 
@@ -125,6 +126,7 @@
125
126
 
126
127
  /* stylelint-disable-next-line selector-pseudo-class-disallowed-list */
127
128
  .after > :global(.vkuiIcon) {
129
+ box-sizing: content-box;
128
130
  padding-inline-start: var(--vkui--spacing_size_m);
129
131
  }
130
132
 
@@ -80,6 +80,7 @@
80
80
  }
81
81
 
82
82
  .chevronIcon {
83
+ box-sizing: content-box;
83
84
  margin-inline-start: 8px;
84
85
  margin-block-start: 1px; /* Смещает иконку относительно текста */
85
86
  color: var(--vkui--color_icon_secondary);
@@ -2,7 +2,10 @@
2
2
 
3
3
  import * as React from 'react';
4
4
  import { classNames, hasReactNode, noop } from '@vkontakte/vkjs';
5
+ import { useFocusVisible } from '../../hooks/useFocusVisible';
6
+ import { useFocusVisibleClassName } from '../../hooks/useFocusVisibleClassName';
5
7
  import { usePlatform } from '../../hooks/usePlatform';
8
+ import { callMultiple } from '../../lib/callMultiple';
6
9
  import { COMMON_WARNINGS, warnOnce } from '../../lib/warnOnce';
7
10
  import type { HasComponent, HasRootRef } from '../../types';
8
11
  import { RootComponent } from '../RootComponent/RootComponent';
@@ -38,6 +41,8 @@ export const TabbarItem = ({
38
41
  href,
39
42
  Component = href ? 'a' : 'button',
40
43
  disabled,
44
+ onFocus: onFocusProp,
45
+ onBlur: onBlurProp,
41
46
  ...restProps
42
47
  }: TabbarItemProps): React.ReactNode => {
43
48
  const platform = usePlatform();
@@ -50,11 +55,22 @@ export const TabbarItem = ({
50
55
  }
51
56
  }
52
57
 
58
+ const {
59
+ focusVisible,
60
+ onFocus: handleFocusVisibleOnFocus,
61
+ onBlur: handleFocusVisibleOnBlur,
62
+ } = useFocusVisible();
63
+ const focusVisibleClassNames = useFocusVisibleClassName({
64
+ focusVisible,
65
+ });
66
+
53
67
  return (
54
68
  <RootComponent
55
69
  Component={Component}
56
70
  {...restProps}
57
71
  disabled={disabled}
72
+ onFocus={callMultiple(handleFocusVisibleOnFocus, onFocusProp)}
73
+ onBlur={callMultiple(handleFocusVisibleOnBlur, onBlurProp)}
58
74
  href={href}
59
75
  baseClassName={classNames(
60
76
  styles.host,
@@ -69,8 +85,9 @@ export const TabbarItem = ({
69
85
  activeMode={platform === 'ios' ? styles.tappableActive : 'background'}
70
86
  activeEffectDelay={platform === 'ios' ? 0 : 300}
71
87
  hasHover={false}
72
- className={styles.tappable}
88
+ className={classNames(styles.tappable, focusVisibleClassNames)}
73
89
  onClick={noop}
90
+ tabIndex={-1}
74
91
  />
75
92
  <div className={styles.in}>
76
93
  <div className={styles.icon}>
@@ -3,6 +3,7 @@
3
3
  }
4
4
 
5
5
  .content {
6
+ box-sizing: content-box;
6
7
  display: flex;
7
8
  align-items: center;
8
9
  justify-content: space-between;
@@ -35,6 +35,11 @@ export interface TooltipBaseProps
35
35
  * Заголовок тултипа.
36
36
  */
37
37
  title?: React.ReactNode;
38
+ /**
39
+ * [a11y] Id для заголовка тултипа.
40
+ * Можно использовать для связи элемента с `role="dialog"` и заголовка через `aria-labelledby`
41
+ */
42
+ titleId?: string;
38
43
  /**
39
44
  * Для показа указателя, требуется передать хотя бы `coords` и `placement`.
40
45
  */
@@ -85,6 +90,7 @@ export const TooltipBase = ({
85
90
  ArrowIcon = DefaultIcon,
86
91
  description,
87
92
  title,
93
+ titleId,
88
94
  maxWidth = TOOLTIP_MAX_WIDTH,
89
95
  closeIconLabel = 'Закрыть',
90
96
  onCloseIconClick,
@@ -111,7 +117,7 @@ export const TooltipBase = ({
111
117
  <div className={styles.content} style={maxWidth !== null ? { maxWidth } : undefined}>
112
118
  <div>
113
119
  {hasReactNode(title) && (
114
- <Subhead className={styles.title} weight="2">
120
+ <Subhead id={titleId} className={styles.title} weight="2">
115
121
  {title}
116
122
  </Subhead>
117
123
  )}
@@ -166,7 +166,7 @@ export const useFocusTrap = (
166
166
 
167
167
  recalculateFocusableNodesRef(parentNode);
168
168
 
169
- if (!autoFocus || arraysEquals(oldFocusableNodes, focusableNodesRef.current)) {
169
+ if (disabled || !autoFocus || arraysEquals(oldFocusableNodes, focusableNodesRef.current)) {
170
170
  return;
171
171
  }
172
172
 
package/src/types.ts CHANGED
@@ -51,7 +51,8 @@ export interface Version {
51
51
  export type AnchorHTMLAttributesOnly = Omit<
52
52
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
53
53
  keyof React.HTMLAttributes<HTMLAnchorElement>
54
- >;
54
+ > &
55
+ React.AriaAttributes;
55
56
 
56
57
  /**
57
58
  * Проверяет, является ли тип подтипом другого.
@@ -1,2 +0,0 @@
1
- export declare function getPageLabelDefault(isCurrent: boolean): string;
2
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/Pagination/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAE9D"}
@@ -1,5 +0,0 @@
1
- export function getPageLabelDefault(isCurrent) {
2
- return isCurrent ? `Страница` : `Перейти на страницу`;
3
- }
4
-
5
- //# sourceMappingURL=utils.js.map