@charcoal-ui/react 6.0.0-rc.5 → 6.0.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Carousel/index.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +24 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layered.css +24 -2
- package/dist/layered.css.map +1 -1
- package/package.json +5 -5
- package/src/components/Carousel/__snapshots__/index.css.snap +24 -2
- package/src/components/Carousel/index.browser.test.tsx +102 -0
- package/src/components/Carousel/index.css +23 -2
- package/src/components/Carousel/index.test.tsx +52 -0
- package/src/components/Carousel/index.tsx +7 -0
|
@@ -134,23 +134,44 @@
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/* hover
|
|
137
|
+
/* hover で表示。キーボード操作でも到達できるよう、キーボード由来のフォーカスが
|
|
138
|
+
カルーセル内にある間も表示する(:focus-within だとクリックで残ったフォーカスでも
|
|
139
|
+
表示され続けるため、react-aria が付与する data 属性で判定する)。 */
|
|
138
140
|
.charcoal-carousel:hover .charcoal-carousel__navigation,
|
|
139
|
-
.charcoal-carousel
|
|
141
|
+
.charcoal-carousel[data-focus-visible-within] .charcoal-carousel__navigation {
|
|
140
142
|
opacity: 1;
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
.charcoal-carousel__navigation__item {
|
|
144
146
|
pointer-events: auto;
|
|
145
147
|
|
|
148
|
+
/* ヒットエリアをグリッド端列(= mask のフェード帯と同じ 72px)全体へ広げる。
|
|
149
|
+
擬似要素上のクリックは元の button に届くため、帯のどこを押してもページ送りになる。
|
|
150
|
+
position: absolute の基準は positioned な .charcoal-carousel__navigation(inset: 0)。 */
|
|
151
|
+
&::after {
|
|
152
|
+
content: '';
|
|
153
|
+
position: absolute;
|
|
154
|
+
top: 0;
|
|
155
|
+
bottom: 0;
|
|
156
|
+
width: 72px;
|
|
157
|
+
}
|
|
158
|
+
|
|
146
159
|
&[data-direction='prev'] {
|
|
147
160
|
grid-column: 1;
|
|
148
161
|
justify-self: center;
|
|
162
|
+
|
|
163
|
+
&::after {
|
|
164
|
+
left: 0;
|
|
165
|
+
}
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
&[data-direction='next'] {
|
|
152
169
|
grid-column: 3;
|
|
153
170
|
justify-self: center;
|
|
171
|
+
|
|
172
|
+
&::after {
|
|
173
|
+
right: 0;
|
|
174
|
+
}
|
|
154
175
|
}
|
|
155
176
|
}
|
|
156
177
|
|
|
@@ -634,6 +634,58 @@ describe('Carousel', () => {
|
|
|
634
634
|
})
|
|
635
635
|
})
|
|
636
636
|
|
|
637
|
+
describe('navigation button visibility (focus modality)', () => {
|
|
638
|
+
// jsdom はジオメトリが全て 0 で Next ボタンが disabled(=フォーカス不可)の
|
|
639
|
+
// ままになるため、スクロール余地をモックして有効化してからフォーカスする。
|
|
640
|
+
function renderWithFocusableNext() {
|
|
641
|
+
render(<Carousel size="M">{slides}</Carousel>)
|
|
642
|
+
const scroller = getScroller()
|
|
643
|
+
mockScrollerGeometry(scroller, { scrollLeft: 0 })
|
|
644
|
+
scroller.scrollBy = vi.fn()
|
|
645
|
+
act(() => {
|
|
646
|
+
fireEvent.scroll(scroller)
|
|
647
|
+
})
|
|
648
|
+
const next = screen.getByRole('button', { name: 'Next' })
|
|
649
|
+
expect(next).not.toBeDisabled()
|
|
650
|
+
return next
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// react-aria は document 上の keydown / pointerdown でフォーカスの由来
|
|
654
|
+
// (keyboard / pointer)を判定するため、focus() の前にイベントを発火して
|
|
655
|
+
// モダリティを切り替える。
|
|
656
|
+
it('does not mark focus-visible when a button is focused by pointer', () => {
|
|
657
|
+
const next = renderWithFocusableNext()
|
|
658
|
+
fireEvent.pointerDown(next)
|
|
659
|
+
fireEvent.mouseDown(next)
|
|
660
|
+
act(() => next.focus())
|
|
661
|
+
fireEvent.click(next)
|
|
662
|
+
expect(document.activeElement).toBe(next)
|
|
663
|
+
expect(screen.getByRole('region')).not.toHaveAttribute(
|
|
664
|
+
'data-focus-visible-within',
|
|
665
|
+
)
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
it('marks focus-visible when a button is focused by keyboard', () => {
|
|
669
|
+
const next = renderWithFocusableNext()
|
|
670
|
+
fireEvent.keyDown(document.body, { key: 'Tab' })
|
|
671
|
+
act(() => next.focus())
|
|
672
|
+
expect(screen.getByRole('region')).toHaveAttribute(
|
|
673
|
+
'data-focus-visible-within',
|
|
674
|
+
'true',
|
|
675
|
+
)
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
it('clears focus-visible when focus leaves the carousel', () => {
|
|
679
|
+
const next = renderWithFocusableNext()
|
|
680
|
+
fireEvent.keyDown(document.body, { key: 'Tab' })
|
|
681
|
+
act(() => next.focus())
|
|
682
|
+
act(() => next.blur())
|
|
683
|
+
expect(screen.getByRole('region')).not.toHaveAttribute(
|
|
684
|
+
'data-focus-visible-within',
|
|
685
|
+
)
|
|
686
|
+
})
|
|
687
|
+
})
|
|
688
|
+
|
|
637
689
|
describe('SSR (server rendering)', () => {
|
|
638
690
|
// jsdom では window が定義されているため renderToString 時に
|
|
639
691
|
// 「useLayoutEffect does nothing on the server」警告が出る(実 SSR=window 無しでは
|
|
@@ -208,8 +208,14 @@ const Carousel = forwardRef<CarouselHandlerRef, CarouselProps>(function Render(
|
|
|
208
208
|
isFocusVisible: scrollerFocusVisible,
|
|
209
209
|
} = useFocusRing()
|
|
210
210
|
|
|
211
|
+
// ナビゲーションボタン表示用。クリックで残留したフォーカス(pointer 由来)では
|
|
212
|
+
// 表示し続けないよう、キーボード由来のフォーカスのみを検知する。
|
|
213
|
+
const { focusProps: rootFocusProps, isFocusVisible: rootFocusVisible } =
|
|
214
|
+
useFocusRing({ within: true })
|
|
215
|
+
|
|
211
216
|
return (
|
|
212
217
|
<div
|
|
218
|
+
{...rootFocusProps}
|
|
213
219
|
className={className}
|
|
214
220
|
data-size={size}
|
|
215
221
|
data-has-gradient={hasGradient}
|
|
@@ -219,6 +225,7 @@ const Carousel = forwardRef<CarouselHandlerRef, CarouselProps>(function Render(
|
|
|
219
225
|
data-scroll-snap-align={snapAlign}
|
|
220
226
|
data-can-prev={canPrev}
|
|
221
227
|
data-can-next={canNext}
|
|
228
|
+
data-focus-visible-within={rootFocusVisible || undefined}
|
|
222
229
|
role="region"
|
|
223
230
|
aria-roledescription="carousel"
|
|
224
231
|
aria-label="Carousel"
|