@coreui/react 5.9.1 → 5.10.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.
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/cjs/components/chip/CChip.d.ts +76 -0
- package/dist/cjs/components/chip/CChip.js +178 -0
- package/dist/cjs/components/chip/CChip.js.map +1 -0
- package/dist/cjs/components/chip/index.d.ts +2 -0
- package/dist/cjs/components/dropdown/CDropdown.js +1 -1
- package/dist/cjs/components/dropdown/CDropdown.js.map +1 -1
- package/dist/cjs/components/form/CChipInput.d.ts +92 -0
- package/dist/cjs/components/form/CChipInput.js +253 -0
- package/dist/cjs/components/form/CChipInput.js.map +1 -0
- package/dist/cjs/components/form/CFormSelect.js +1 -0
- package/dist/cjs/components/form/CFormSelect.js.map +1 -1
- package/dist/cjs/components/form/index.d.ts +2 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/chip/CChip.d.ts +76 -0
- package/dist/esm/components/chip/CChip.js +176 -0
- package/dist/esm/components/chip/CChip.js.map +1 -0
- package/dist/esm/components/chip/index.d.ts +2 -0
- package/dist/esm/components/dropdown/CDropdown.js +1 -1
- package/dist/esm/components/dropdown/CDropdown.js.map +1 -1
- package/dist/esm/components/form/CChipInput.d.ts +92 -0
- package/dist/esm/components/form/CChipInput.js +251 -0
- package/dist/esm/components/form/CChipInput.js.map +1 -0
- package/dist/esm/components/form/CFormSelect.js +1 -0
- package/dist/esm/components/form/CFormSelect.js.map +1 -1
- package/dist/esm/components/form/index.d.ts +2 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +11 -11
- package/src/components/chip/CChip.tsx +372 -0
- package/src/components/chip/__tests__/CChip.spec.tsx +113 -0
- package/src/components/chip/__tests__/__snapshots__/CChip.spec.tsx.snap +65 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/dropdown/CDropdown.tsx +1 -1
- package/src/components/form/CChipInput.tsx +477 -0
- package/src/components/form/CFormSelect.tsx +2 -0
- package/src/components/form/__tests__/CChipInput.spec.tsx +62 -0
- package/src/components/form/__tests__/__snapshots__/CChipInput.spec.tsx.snap +91 -0
- package/src/components/form/index.ts +2 -0
- package/src/components/index.ts +1 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ElementType,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
KeyboardEvent,
|
|
5
|
+
MouseEvent,
|
|
6
|
+
ReactNode,
|
|
7
|
+
forwardRef,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import PropTypes from 'prop-types'
|
|
14
|
+
import classNames from 'classnames'
|
|
15
|
+
|
|
16
|
+
import { PolymorphicRefForwardingComponent } from '../../helpers'
|
|
17
|
+
import { useForkedRef } from '../../hooks'
|
|
18
|
+
import { colorPropType } from '../../props'
|
|
19
|
+
import type { Colors } from '../../types'
|
|
20
|
+
|
|
21
|
+
export interface CChipProps extends HTMLAttributes<HTMLSpanElement | HTMLButtonElement> {
|
|
22
|
+
/**
|
|
23
|
+
* Toggles the active state of the React Chip component for non-selectable usage.
|
|
24
|
+
*/
|
|
25
|
+
active?: boolean
|
|
26
|
+
/**
|
|
27
|
+
* Provides an accessible label for the remove button in the React Chip component.
|
|
28
|
+
*/
|
|
29
|
+
ariaRemoveLabel?: string
|
|
30
|
+
/**
|
|
31
|
+
* Specifies the root element or custom component used by the React Chip component.
|
|
32
|
+
*/
|
|
33
|
+
as?: ElementType
|
|
34
|
+
/**
|
|
35
|
+
* Adds custom classes to the React Chip root element.
|
|
36
|
+
*/
|
|
37
|
+
className?: string
|
|
38
|
+
/**
|
|
39
|
+
* Enables interactive hover styling and pointer cursor for the React Chip component.
|
|
40
|
+
*/
|
|
41
|
+
clickable?: boolean
|
|
42
|
+
/**
|
|
43
|
+
* Sets the contextual color of the React Chip component using CoreUI theme colors.
|
|
44
|
+
*
|
|
45
|
+
* @type 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light' | string
|
|
46
|
+
*/
|
|
47
|
+
color?: Colors
|
|
48
|
+
/**
|
|
49
|
+
* Disables the React Chip component and removes interactive behavior.
|
|
50
|
+
*/
|
|
51
|
+
disabled?: boolean
|
|
52
|
+
/**
|
|
53
|
+
* Callback fired when the React Chip component becomes deselected.
|
|
54
|
+
*/
|
|
55
|
+
onDeselect?: (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => void
|
|
56
|
+
/**
|
|
57
|
+
* Callback fired when the React Chip component requests removal by button click or keyboard action.
|
|
58
|
+
*/
|
|
59
|
+
onRemove?: (event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLElement>) => void
|
|
60
|
+
/**
|
|
61
|
+
* Callback fired when the React Chip component becomes selected.
|
|
62
|
+
*/
|
|
63
|
+
onSelect?: (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => void
|
|
64
|
+
/**
|
|
65
|
+
* Callback fired when the selected state of the React Chip component changes.
|
|
66
|
+
*/
|
|
67
|
+
onSelectedChange?: (
|
|
68
|
+
selected: boolean,
|
|
69
|
+
event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>
|
|
70
|
+
) => void
|
|
71
|
+
/**
|
|
72
|
+
* Displays a remove button inside the React Chip component.
|
|
73
|
+
*/
|
|
74
|
+
removable?: boolean
|
|
75
|
+
/**
|
|
76
|
+
* Replaces the default remove icon with a custom icon node in the React Chip component.
|
|
77
|
+
*/
|
|
78
|
+
removeIcon?: ReactNode
|
|
79
|
+
/**
|
|
80
|
+
* Enables selectable behavior and keyboard toggle support for the React Chip component.
|
|
81
|
+
*/
|
|
82
|
+
selectable?: boolean
|
|
83
|
+
/**
|
|
84
|
+
* Controls the selected state of a selectable React Chip component.
|
|
85
|
+
*/
|
|
86
|
+
selected?: boolean
|
|
87
|
+
/**
|
|
88
|
+
* Sets the size of the React Chip component to small or large.
|
|
89
|
+
*/
|
|
90
|
+
size?: 'sm' | 'lg'
|
|
91
|
+
/**
|
|
92
|
+
* Sets the visual variant of the React Chip component to outline style.
|
|
93
|
+
*/
|
|
94
|
+
variant?: 'outline'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const SELECTOR_FOCUSABLE_ITEMS = '[data-coreui-chip-focusable="true"]:not(.disabled)'
|
|
98
|
+
|
|
99
|
+
export const CChip: PolymorphicRefForwardingComponent<'span', CChipProps> = forwardRef<
|
|
100
|
+
HTMLSpanElement | HTMLButtonElement,
|
|
101
|
+
CChipProps
|
|
102
|
+
>(
|
|
103
|
+
(
|
|
104
|
+
{
|
|
105
|
+
active,
|
|
106
|
+
ariaRemoveLabel = 'Remove',
|
|
107
|
+
children,
|
|
108
|
+
as: Component = 'span',
|
|
109
|
+
className,
|
|
110
|
+
clickable,
|
|
111
|
+
color,
|
|
112
|
+
disabled,
|
|
113
|
+
onClick,
|
|
114
|
+
onDeselect,
|
|
115
|
+
onKeyDown,
|
|
116
|
+
onRemove,
|
|
117
|
+
onSelect,
|
|
118
|
+
onSelectedChange,
|
|
119
|
+
removable,
|
|
120
|
+
removeIcon,
|
|
121
|
+
selectable,
|
|
122
|
+
selected,
|
|
123
|
+
size,
|
|
124
|
+
tabIndex,
|
|
125
|
+
variant,
|
|
126
|
+
...rest
|
|
127
|
+
},
|
|
128
|
+
ref
|
|
129
|
+
) => {
|
|
130
|
+
const chipRef = useRef<HTMLSpanElement | HTMLButtonElement>(null)
|
|
131
|
+
const forkedRef = useForkedRef(ref, chipRef)
|
|
132
|
+
const isSelectedControlled = selected !== undefined
|
|
133
|
+
const [_selected, setSelected] = useState(Boolean(selected))
|
|
134
|
+
const selectedState = isSelectedControlled ? Boolean(selected) : _selected
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (isSelectedControlled) {
|
|
138
|
+
setSelected(Boolean(selected))
|
|
139
|
+
}
|
|
140
|
+
}, [isSelectedControlled, selected])
|
|
141
|
+
|
|
142
|
+
const isFocusable = useMemo(
|
|
143
|
+
() => Boolean(!disabled && (selectable || removable)),
|
|
144
|
+
[disabled, selectable, removable]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const getFocusableSibling = (shouldGetNext: boolean) => {
|
|
148
|
+
const currentElement = chipRef.current
|
|
149
|
+
if (!currentElement?.parentElement) {
|
|
150
|
+
return null
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const chips = Array.from(
|
|
154
|
+
currentElement.parentElement.querySelectorAll<HTMLElement>(SELECTOR_FOCUSABLE_ITEMS)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
const index = chips.indexOf(currentElement as unknown as HTMLElement)
|
|
158
|
+
if (index === -1 || chips.length <= 1) {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const targetIndex = shouldGetNext ? index + 1 : index - 1
|
|
163
|
+
return chips[targetIndex] ?? null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const navigateToEdge = (targetIndex: 0 | -1) => {
|
|
167
|
+
const currentElement = chipRef.current
|
|
168
|
+
if (!currentElement?.parentElement) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const chips = Array.from(
|
|
173
|
+
currentElement.parentElement.querySelectorAll<HTMLElement>(SELECTOR_FOCUSABLE_ITEMS)
|
|
174
|
+
)
|
|
175
|
+
const edgeChip = targetIndex === -1 ? chips[chips.length - 1] : chips[0]
|
|
176
|
+
edgeChip?.focus()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const setSelectableState = (
|
|
180
|
+
nextSelected: boolean,
|
|
181
|
+
event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>
|
|
182
|
+
) => {
|
|
183
|
+
if (!selectable || disabled || nextSelected === selectedState) {
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!isSelectedControlled) {
|
|
188
|
+
setSelected(nextSelected)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (nextSelected) {
|
|
192
|
+
onSelect?.(event)
|
|
193
|
+
} else {
|
|
194
|
+
onDeselect?.(event)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
onSelectedChange?.(nextSelected, event)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const toggleSelectedState = (event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>) => {
|
|
201
|
+
setSelectableState(!selectedState, event)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const handleRemove = (event: MouseEvent<HTMLButtonElement> | KeyboardEvent<HTMLElement>) => {
|
|
205
|
+
onRemove?.(event)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const handleRemoveClick = (event: MouseEvent<HTMLButtonElement>) => {
|
|
209
|
+
event.stopPropagation()
|
|
210
|
+
handleRemove(event)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
214
|
+
if (disabled) {
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if ((event.target as HTMLElement).closest('.chip-remove')) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (selectable) {
|
|
223
|
+
toggleSelectedState(event)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
onClick?.(event)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {
|
|
230
|
+
if (disabled) {
|
|
231
|
+
onKeyDown?.(event)
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
switch (event.key) {
|
|
236
|
+
case 'Enter':
|
|
237
|
+
case ' ':
|
|
238
|
+
case 'Spacebar': {
|
|
239
|
+
if (selectable) {
|
|
240
|
+
event.preventDefault()
|
|
241
|
+
toggleSelectedState(event)
|
|
242
|
+
}
|
|
243
|
+
break
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'Backspace':
|
|
247
|
+
case 'Delete': {
|
|
248
|
+
if (removable) {
|
|
249
|
+
event.preventDefault()
|
|
250
|
+
const sibling = getFocusableSibling(false) || getFocusableSibling(true)
|
|
251
|
+
sibling?.focus()
|
|
252
|
+
handleRemove(event)
|
|
253
|
+
}
|
|
254
|
+
break
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
case 'ArrowLeft': {
|
|
258
|
+
event.preventDefault()
|
|
259
|
+
const sibling = getFocusableSibling(false)
|
|
260
|
+
sibling?.focus()
|
|
261
|
+
if (selectedState && event.shiftKey) {
|
|
262
|
+
sibling?.dispatchEvent(new CustomEvent('coreui-chip-select'))
|
|
263
|
+
}
|
|
264
|
+
break
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'ArrowRight': {
|
|
268
|
+
event.preventDefault()
|
|
269
|
+
const sibling = getFocusableSibling(true)
|
|
270
|
+
sibling?.focus()
|
|
271
|
+
if (selectedState && event.shiftKey) {
|
|
272
|
+
sibling?.dispatchEvent(new CustomEvent('coreui-chip-select'))
|
|
273
|
+
}
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
case 'Home': {
|
|
278
|
+
event.preventDefault()
|
|
279
|
+
navigateToEdge(0)
|
|
280
|
+
break
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
case 'End': {
|
|
284
|
+
event.preventDefault()
|
|
285
|
+
navigateToEdge(-1)
|
|
286
|
+
break
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// No default
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
onKeyDown?.(event)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<Component
|
|
297
|
+
className={classNames(
|
|
298
|
+
'chip',
|
|
299
|
+
{
|
|
300
|
+
active: selectable ? selectedState : active,
|
|
301
|
+
disabled,
|
|
302
|
+
[`chip-${color}`]: color,
|
|
303
|
+
[`chip-${size}`]: size,
|
|
304
|
+
'chip-clickable': clickable || selectable || Boolean(onClick),
|
|
305
|
+
'chip-outline': variant === 'outline',
|
|
306
|
+
},
|
|
307
|
+
className
|
|
308
|
+
)}
|
|
309
|
+
data-coreui-chip-focusable={isFocusable || undefined}
|
|
310
|
+
{...(disabled && { 'aria-disabled': true })}
|
|
311
|
+
{...(selectable && { 'aria-selected': selectedState })}
|
|
312
|
+
{...(isFocusable && tabIndex === undefined && { tabIndex: 0 })}
|
|
313
|
+
onClick={handleClick}
|
|
314
|
+
onKeyDown={handleKeyDown}
|
|
315
|
+
{...(Component === 'button' && { disabled })}
|
|
316
|
+
{...rest}
|
|
317
|
+
ref={forkedRef}
|
|
318
|
+
>
|
|
319
|
+
{children}
|
|
320
|
+
{removable && (
|
|
321
|
+
<button
|
|
322
|
+
type="button"
|
|
323
|
+
className="chip-remove"
|
|
324
|
+
aria-label={ariaRemoveLabel}
|
|
325
|
+
onClick={handleRemoveClick}
|
|
326
|
+
tabIndex={-1}
|
|
327
|
+
disabled={disabled}
|
|
328
|
+
>
|
|
329
|
+
{removeIcon ?? (
|
|
330
|
+
<svg
|
|
331
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
332
|
+
width="16"
|
|
333
|
+
height="16"
|
|
334
|
+
viewBox="0 0 16 16"
|
|
335
|
+
fill="none"
|
|
336
|
+
stroke="currentColor"
|
|
337
|
+
strokeWidth="2"
|
|
338
|
+
strokeLinecap="round"
|
|
339
|
+
>
|
|
340
|
+
<line x1="4" y1="4" x2="12" y2="12" />
|
|
341
|
+
<line x1="12" y1="4" x2="4" y2="12" />
|
|
342
|
+
</svg>
|
|
343
|
+
)}
|
|
344
|
+
</button>
|
|
345
|
+
)}
|
|
346
|
+
</Component>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
CChip.propTypes = {
|
|
352
|
+
active: PropTypes.bool,
|
|
353
|
+
ariaRemoveLabel: PropTypes.string,
|
|
354
|
+
as: PropTypes.elementType,
|
|
355
|
+
children: PropTypes.node,
|
|
356
|
+
className: PropTypes.string,
|
|
357
|
+
clickable: PropTypes.bool,
|
|
358
|
+
color: colorPropType,
|
|
359
|
+
disabled: PropTypes.bool,
|
|
360
|
+
onDeselect: PropTypes.func,
|
|
361
|
+
onRemove: PropTypes.func,
|
|
362
|
+
onSelect: PropTypes.func,
|
|
363
|
+
onSelectedChange: PropTypes.func,
|
|
364
|
+
removable: PropTypes.bool,
|
|
365
|
+
removeIcon: PropTypes.node,
|
|
366
|
+
selectable: PropTypes.bool,
|
|
367
|
+
selected: PropTypes.bool,
|
|
368
|
+
size: PropTypes.oneOf(['sm', 'lg']),
|
|
369
|
+
variant: PropTypes.oneOf(['outline']),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
CChip.displayName = 'CChip'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react'
|
|
3
|
+
import '@testing-library/jest-dom'
|
|
4
|
+
import { CChip } from '../index'
|
|
5
|
+
|
|
6
|
+
test('loads and displays CChip component', async () => {
|
|
7
|
+
const { container } = render(<CChip>Test</CChip>)
|
|
8
|
+
expect(container).toMatchSnapshot()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('CChip customize', async () => {
|
|
12
|
+
const { container } = render(
|
|
13
|
+
<CChip
|
|
14
|
+
active={true}
|
|
15
|
+
className="bazinga"
|
|
16
|
+
clickable={true}
|
|
17
|
+
color="warning"
|
|
18
|
+
as="button"
|
|
19
|
+
disabled={true}
|
|
20
|
+
size="lg"
|
|
21
|
+
variant="outline"
|
|
22
|
+
>
|
|
23
|
+
Test
|
|
24
|
+
</CChip>
|
|
25
|
+
)
|
|
26
|
+
expect(container).toMatchSnapshot()
|
|
27
|
+
expect(container.firstChild).toHaveClass('bazinga')
|
|
28
|
+
expect(container.firstChild).toHaveClass('chip')
|
|
29
|
+
expect(container.firstChild).toHaveClass('active')
|
|
30
|
+
expect(container.firstChild).toHaveClass('chip-clickable')
|
|
31
|
+
expect(container.firstChild).toHaveClass('chip-warning')
|
|
32
|
+
expect(container.firstChild).toHaveClass('chip-lg')
|
|
33
|
+
expect(container.firstChild).toHaveClass('chip-outline')
|
|
34
|
+
expect(container.firstChild).toHaveClass('disabled')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('CChip removable', async () => {
|
|
38
|
+
const onRemove = jest.fn()
|
|
39
|
+
const { container } = render(
|
|
40
|
+
<CChip removable={true} ariaRemoveLabel="Remove test" onRemove={onRemove}>
|
|
41
|
+
Test
|
|
42
|
+
</CChip>
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const removeButton = container.querySelector('.chip-remove')
|
|
46
|
+
expect(container).toMatchSnapshot()
|
|
47
|
+
expect(removeButton).toBeInTheDocument()
|
|
48
|
+
removeButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
49
|
+
expect(onRemove).toHaveBeenCalled()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('CChip selectable', async () => {
|
|
53
|
+
const onSelect = jest.fn()
|
|
54
|
+
const onDeselect = jest.fn()
|
|
55
|
+
const { getByText } = render(
|
|
56
|
+
<CChip selectable={true} onSelect={onSelect} onDeselect={onDeselect}>
|
|
57
|
+
Selectable
|
|
58
|
+
</CChip>,
|
|
59
|
+
)
|
|
60
|
+
const chip = getByText('Selectable')
|
|
61
|
+
|
|
62
|
+
expect(chip).toHaveAttribute('aria-selected', 'false')
|
|
63
|
+
fireEvent.click(chip)
|
|
64
|
+
expect(chip).toHaveClass('active')
|
|
65
|
+
expect(chip).toHaveAttribute('aria-selected', 'true')
|
|
66
|
+
expect(onSelect).toHaveBeenCalledTimes(1)
|
|
67
|
+
|
|
68
|
+
fireEvent.keyDown(chip, { key: 'Enter' })
|
|
69
|
+
expect(chip).not.toHaveClass('active')
|
|
70
|
+
expect(chip).toHaveAttribute('aria-selected', 'false')
|
|
71
|
+
expect(onDeselect).toHaveBeenCalledTimes(1)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('CChip keyboard navigation', async () => {
|
|
75
|
+
const { getByText } = render(
|
|
76
|
+
<div>
|
|
77
|
+
<CChip selectable={true}>First</CChip>
|
|
78
|
+
<CChip selectable={true}>Second</CChip>
|
|
79
|
+
<CChip selectable={true}>Third</CChip>
|
|
80
|
+
</div>,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const first = getByText('First')
|
|
84
|
+
const second = getByText('Second')
|
|
85
|
+
const third = getByText('Third')
|
|
86
|
+
|
|
87
|
+
first.focus()
|
|
88
|
+
fireEvent.keyDown(first, { key: 'ArrowRight' })
|
|
89
|
+
expect(second).toHaveFocus()
|
|
90
|
+
|
|
91
|
+
fireEvent.keyDown(second, { key: 'End' })
|
|
92
|
+
expect(third).toHaveFocus()
|
|
93
|
+
|
|
94
|
+
fireEvent.keyDown(third, { key: 'Home' })
|
|
95
|
+
expect(first).toHaveFocus()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('CChip delete triggers remove callback', async () => {
|
|
99
|
+
const onRemove = jest.fn()
|
|
100
|
+
const { getByText } = render(
|
|
101
|
+
<div>
|
|
102
|
+
<CChip removable={true} onRemove={onRemove}>
|
|
103
|
+
First
|
|
104
|
+
</CChip>
|
|
105
|
+
<CChip removable={true}>Second</CChip>
|
|
106
|
+
</div>,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const first = getByText('First')
|
|
110
|
+
first.focus()
|
|
111
|
+
fireEvent.keyDown(first, { key: 'Delete' })
|
|
112
|
+
expect(onRemove).toHaveBeenCalledTimes(1)
|
|
113
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`CChip customize 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<button
|
|
6
|
+
aria-disabled="true"
|
|
7
|
+
class="chip active disabled chip-warning chip-lg chip-clickable chip-outline bazinga"
|
|
8
|
+
disabled=""
|
|
9
|
+
>
|
|
10
|
+
Test
|
|
11
|
+
</button>
|
|
12
|
+
</div>
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
exports[`CChip removable 1`] = `
|
|
16
|
+
<div>
|
|
17
|
+
<span
|
|
18
|
+
class="chip"
|
|
19
|
+
data-coreui-chip-focusable="true"
|
|
20
|
+
tabindex="0"
|
|
21
|
+
>
|
|
22
|
+
Test
|
|
23
|
+
<button
|
|
24
|
+
aria-label="Remove test"
|
|
25
|
+
class="chip-remove"
|
|
26
|
+
tabindex="-1"
|
|
27
|
+
type="button"
|
|
28
|
+
>
|
|
29
|
+
<svg
|
|
30
|
+
fill="none"
|
|
31
|
+
height="16"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
stroke-linecap="round"
|
|
34
|
+
stroke-width="2"
|
|
35
|
+
viewBox="0 0 16 16"
|
|
36
|
+
width="16"
|
|
37
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
+
>
|
|
39
|
+
<line
|
|
40
|
+
x1="4"
|
|
41
|
+
x2="12"
|
|
42
|
+
y1="4"
|
|
43
|
+
y2="12"
|
|
44
|
+
/>
|
|
45
|
+
<line
|
|
46
|
+
x1="12"
|
|
47
|
+
x2="4"
|
|
48
|
+
y1="4"
|
|
49
|
+
y2="12"
|
|
50
|
+
/>
|
|
51
|
+
</svg>
|
|
52
|
+
</button>
|
|
53
|
+
</span>
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
exports[`loads and displays CChip component 1`] = `
|
|
58
|
+
<div>
|
|
59
|
+
<span
|
|
60
|
+
class="chip"
|
|
61
|
+
>
|
|
62
|
+
Test
|
|
63
|
+
</span>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
@@ -368,7 +368,7 @@ export const CDropdown: PolymorphicRefForwardingComponent<'div', CDropdownProps>
|
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
const target = event.target as HTMLElement | null
|
|
371
|
-
const FORM_TAG_RE = /^(input|select|option|textarea|form
|
|
371
|
+
const FORM_TAG_RE = /^(input|select|option|textarea|form)$/i
|
|
372
372
|
|
|
373
373
|
if (isOnMenu && target && FORM_TAG_RE.test(target.tagName)) {
|
|
374
374
|
return
|