@charcoal-ui/react 6.1.0 → 6.1.1-beta.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.
@@ -11,7 +11,7 @@
11
11
  * iOS Safari は非インタラクティブな要素へのペン入力で click を合成しないため、
12
12
  * click に依存する react-aria の外側判定では overlay を閉じられない。
13
13
  */
14
- import { fireEvent, render, screen } from '@testing-library/react'
14
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
15
15
  import { vi } from 'vitest'
16
16
  import DropdownSelector from '.'
17
17
  import DropdownMenuItem from './DropdownMenuItem'
@@ -60,7 +60,7 @@ describe.each([
60
60
  ['inertWorkaround が無効', false],
61
61
  ['inertWorkaround が有効', true],
62
62
  ])('%s なとき', (_, inertWorkaround) => {
63
- it('Apple Pencil で overlay の外側をタップすると閉じる', () => {
63
+ it('Apple Pencil で overlay の外側をタップすると閉じる', async () => {
64
64
  renderSelector(inertWorkaround)
65
65
 
66
66
  penTap(screen.getByRole('button'))
@@ -68,7 +68,9 @@ describe.each([
68
68
 
69
69
  penTap(hitTestOutsidePopover())
70
70
 
71
- expect(popover()).toBeNull()
71
+ await waitFor(() => {
72
+ expect(popover()).toBeNull()
73
+ })
72
74
  })
73
75
 
74
76
  // 選択肢のタップは onChange 経由で閉じるのが仕様なので、余白部分を叩く
@@ -85,6 +87,79 @@ describe.each([
85
87
  })
86
88
  })
87
89
 
90
+ it('inertWorkaround が有効なとき、開いた直後に underlay へ送られる互換 click では閉じない', () => {
91
+ renderSelector(true)
92
+
93
+ penTap(screen.getByRole('button'))
94
+ expect(popover()).not.toBeNull()
95
+
96
+ // Android Chrome はペンの pointerup 後、underlay 上の pointerdown を伴わない
97
+ // mouse / click イベントを新しく追加された underlay に送る。
98
+ const underlay = getUnderlay()
99
+ fireEvent.mouseDown(underlay)
100
+ fireEvent.mouseUp(underlay)
101
+ fireEvent.click(underlay)
102
+
103
+ expect(popover()).not.toBeNull()
104
+ })
105
+
106
+ it('ペンで外側をタップしたとき、下にあるクリック可能な要素をクリックせずに閉じる', async () => {
107
+ const handleBackgroundClick = vi.fn()
108
+ render(
109
+ <>
110
+ <DropdownSelector
111
+ label="Label"
112
+ value="1"
113
+ onChange={vi.fn()}
114
+ inertWorkaround
115
+ >
116
+ <DropdownMenuItem value="1">Option 1</DropdownMenuItem>
117
+ <DropdownMenuItem value="2">Option 2</DropdownMenuItem>
118
+ </DropdownSelector>
119
+ <button
120
+ type="button"
121
+ onClick={handleBackgroundClick}
122
+ style={{ position: 'fixed', right: 16, bottom: 16 }}
123
+ >
124
+ Background button
125
+ </button>
126
+ </>,
127
+ )
128
+
129
+ penTap(screen.getByRole('button', { name: 'Label' }))
130
+ expect(popover()).not.toBeNull()
131
+
132
+ const backgroundButton = screen.getByRole('button', {
133
+ name: 'Background button',
134
+ })
135
+ const rect = backgroundButton.getBoundingClientRect()
136
+ const point = {
137
+ x: Math.round(rect.left + rect.width / 2),
138
+ y: Math.round(rect.top + rect.height / 2),
139
+ }
140
+ const pointerTarget = document.elementFromPoint(point.x, point.y)
141
+ if (pointerTarget === null) throw new Error('pointer target not found')
142
+
143
+ fireEvent.pointerDown(pointerTarget, {
144
+ pointerType: 'pen',
145
+ button: 0,
146
+ composed: true,
147
+ })
148
+ // Android Chrome はこの touchstart がキャンセルされた場合、後続の互換
149
+ // mouse / click イベントを生成しない。
150
+ expect(fireEvent.touchStart(pointerTarget)).toBe(false)
151
+ fireEvent.pointerUp(pointerTarget, {
152
+ pointerType: 'pen',
153
+ button: 0,
154
+ composed: true,
155
+ })
156
+
157
+ await waitFor(() => {
158
+ expect(popover()).toBeNull()
159
+ })
160
+ expect(handleBackgroundClick).not.toHaveBeenCalled()
161
+ })
162
+
88
163
  it('underlay は inertWorkaround が無効なとき inert になる', () => {
89
164
  renderSelector(false)
90
165
  penTap(screen.getByRole('button'))
@@ -543,3 +543,40 @@ export const WithRef: StoryObj<typeof DropdownSelector> = {
543
543
  )
544
544
  },
545
545
  }
546
+
547
+ export const InertWorkaroundPenOutside: StoryObj<typeof DropdownSelector> = {
548
+ render: function Render() {
549
+ const [selected, setSelected] = useState('1')
550
+ const [backgroundClickCount, setBackgroundClickCount] = useState(0)
551
+
552
+ return (
553
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: 64 }}>
554
+ <div style={{ width: 288 }}>
555
+ <DropdownSelector
556
+ value={selected}
557
+ onChange={setSelected}
558
+ label="DropdownSelector"
559
+ inertWorkaround
560
+ >
561
+ <DropdownMenuItem value="1">Option 1</DropdownMenuItem>
562
+ <DropdownMenuItem value="2">Option 2</DropdownMenuItem>
563
+ <DropdownMenuItem value="3">Option 3</DropdownMenuItem>
564
+ </DropdownSelector>
565
+ </div>
566
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
567
+ <Button
568
+ data-testid="background-button"
569
+ onClick={() => {
570
+ setBackgroundClickCount((count) => count + 1)
571
+ }}
572
+ >
573
+ Background button
574
+ </Button>
575
+ <div data-testid="background-click-count">
576
+ Background click count: {backgroundClickCount}
577
+ </div>
578
+ </div>
579
+ </div>
580
+ )
581
+ },
582
+ }