@coreui/vue
Version:
UI Components Library for Vue.js
287 lines (283 loc) • 9.97 kB
JavaScript
'use strict';
var vue = require('vue');
var props = require('../../props.js');
const SELECTOR_FOCUSABLE_ITEMS = '.chip[tabindex="0"]:not(.disabled)';
const CChip = vue.defineComponent({
name: 'CChip',
props: {
/**
* Toggle the active state for the component.
*/
active: Boolean,
/**
* Provides an accessible label for the remove button.
*/
ariaRemoveLabel: {
type: String,
default: 'Remove',
},
/**
* Component used for the root node. Either a string to use a HTML element or a component.
*/
as: {
type: String,
default: 'span',
},
/**
* Enables interactive hover styling and pointer cursor.
*/
clickable: Boolean,
/**
* Sets the color context of the component to one of CoreUI's themed colors.
*
* @values 'primary', 'secondary', 'success', 'danger', 'warning', 'info', 'dark', 'light'
*/
color: props.Color,
/**
* Toggle the disabled state for the component.
*/
disabled: Boolean,
/**
* Displays a remove button inside the component.
*/
removable: Boolean,
/**
* Replaces the default remove icon with a custom icon node.
*/
removeIcon: {
type: [String, Object],
default: undefined,
},
/**
* Enables selectable behavior and keyboard toggle support.
*/
selectable: Boolean,
/**
* Controls the selected state of a selectable component.
*/
selected: Boolean,
/**
* Size the component small or large.
*
* @values 'sm', 'lg'
*/
size: {
type: String,
validator: (value) => {
return ['sm', 'lg'].includes(value);
},
},
/**
* Set the button variant to an outlined style.
*
* @values 'outline'
*/
variant: {
type: String,
validator: (value) => {
return value === 'outline';
},
},
},
emits: [
/**
* Event called when the user clicks on the component.
*/
'click',
/**
* Event called when the component becomes deselected.
*/
'deselect',
/**
* Event called when the user presses a key.
*/
'keydown',
/**
* Event called when the component requests removal.
*/
'remove',
/**
* Event called when the component becomes selected.
*/
'select',
/**
* Event called when the selected state changes.
*/
'selected-change',
],
setup(props, { attrs, emit, slots, expose }) {
const chipRef = vue.ref();
const internalSelected = vue.ref(false);
const selectedState = vue.computed(() => props.selected !== undefined ? Boolean(props.selected) : internalSelected.value);
const isFocusable = vue.computed(() => Boolean(!props.disabled && (props.selectable || props.removable)));
const getFocusableSibling = (shouldGetNext) => {
const currentElement = chipRef.value;
if (!currentElement?.parentElement) {
return null;
}
const chips = Array.from(currentElement.parentElement.querySelectorAll(SELECTOR_FOCUSABLE_ITEMS));
const index = chips.indexOf(currentElement);
if (index === -1 || chips.length <= 1) {
return null;
}
const targetIndex = shouldGetNext ? index + 1 : index - 1;
return chips[targetIndex] ?? null;
};
const navigateToEdge = (targetIndex) => {
const currentElement = chipRef.value;
if (!currentElement?.parentElement) {
return;
}
const chips = Array.from(currentElement.parentElement.querySelectorAll(SELECTOR_FOCUSABLE_ITEMS));
const edgeChip = targetIndex === -1 ? chips[chips.length - 1] : chips[0];
edgeChip?.focus();
};
const setSelectableState = (nextSelected, event) => {
if (!props.selectable || props.disabled || nextSelected === selectedState.value) {
return;
}
if (props.selected === undefined) {
internalSelected.value = nextSelected;
}
if (nextSelected) {
emit('select', event);
}
else {
emit('deselect', event);
}
emit('selected-change', nextSelected, event);
};
const toggleSelectedState = (event) => {
setSelectableState(!selectedState.value, event);
};
const handleRemove = (event) => {
emit('remove', event);
};
const handleRemoveClick = (event) => {
event.stopPropagation();
handleRemove(event);
};
const handleClick = (event) => {
if (props.disabled) {
return;
}
if (event.target.closest('.chip-remove')) {
return;
}
if (props.selectable) {
toggleSelectedState(event);
}
emit('click', event);
};
const handleKeydown = (event) => {
if (props.disabled) {
emit('keydown', event);
return;
}
switch (event.key) {
case 'Enter':
case ' ':
case 'Spacebar': {
if (props.selectable) {
event.preventDefault();
toggleSelectedState(event);
}
break;
}
case 'Backspace':
case 'Delete': {
if (props.removable) {
event.preventDefault();
const sibling = getFocusableSibling(false) || getFocusableSibling(true);
sibling?.focus();
handleRemove(event);
}
break;
}
case 'ArrowLeft': {
event.preventDefault();
const sibling = getFocusableSibling(false);
sibling?.focus();
if (selectedState.value && event.shiftKey) {
sibling?.dispatchEvent(new CustomEvent('coreui-chip-select'));
}
break;
}
case 'ArrowRight': {
event.preventDefault();
const sibling = getFocusableSibling(true);
sibling?.focus();
if (selectedState.value && event.shiftKey) {
sibling?.dispatchEvent(new CustomEvent('coreui-chip-select'));
}
break;
}
case 'Home': {
event.preventDefault();
navigateToEdge(0);
break;
}
case 'End': {
event.preventDefault();
navigateToEdge(-1);
break;
}
// No default
}
emit('keydown', event);
};
expose({ chipRef });
return () => {
const removeIconVNode = slots.removeIcon?.() ||
props.removeIcon ||
vue.h('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: 16,
height: 16,
viewBox: '0 0 16 16',
fill: 'none',
stroke: 'currentColor',
'stroke-width': 2,
'stroke-linecap': 'round',
}, [
vue.h('line', { x1: 4, y1: 4, x2: 12, y2: 12 }),
vue.h('line', { x1: 12, y1: 4, x2: 4, y2: 12 }),
]);
const children = [
slots.default && slots.default(),
props.removable &&
vue.h('button', {
type: 'button',
class: 'chip-remove',
'aria-label': props.ariaRemoveLabel,
onClick: handleRemoveClick,
tabindex: -1,
disabled: props.disabled,
}, removeIconVNode),
].filter(Boolean);
return vue.h(props.as, {
ref: chipRef,
class: [
'chip',
{
active: props.selectable ? selectedState.value : props.active,
disabled: props.disabled,
[`chip-${props.color}`]: props.color,
[`chip-${props.size}`]: props.size,
'chip-clickable': props.clickable || props.selectable || attrs.onClick,
'chip-outline': props.variant === 'outline',
},
attrs.class,
],
...(props.disabled && { 'aria-disabled': true }),
...(props.selectable && { 'aria-selected': selectedState.value }),
...(isFocusable.value && !attrs.tabindex && { tabindex: 0 }),
onClick: handleClick,
onKeydown: handleKeydown,
...(props.as === 'button' && { disabled: props.disabled }),
}, children);
};
},
});
exports.CChip = CChip;
//# sourceMappingURL=CChip.js.map