@coreui/vue
Version:
UI Components Library for Vue.js
313 lines (310 loc) • 11.4 kB
JavaScript
import { defineComponent, ref, inject, computed, h } from 'vue';
import { Color } from '../../props.js';
import { chipSetContextKey } from '../chip-set/useChipSet.js';
const CChip = 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: Color,
/**
* Toggle the disabled state for the component.
*
* Defaults to `undefined` so a chip can override the value inherited from a `CChipSet` (including turning it off).
*/
disabled: {
type: Boolean,
default: undefined,
},
/**
* Turns the component into a filter chip. A filter chip is selectable and shows a leading check icon while selected.
*
* Defaults to `undefined` so a chip can override the value inherited from a `CChipSet`.
*/
filter: {
type: Boolean,
default: undefined,
},
/**
* Displays a remove button inside the component.
*
* Defaults to `undefined` so a chip can override the value inherited from a `CChipSet` (including turning it off).
*/
removable: {
type: Boolean,
default: undefined,
},
/**
* Replaces the default remove icon with a custom icon node.
*/
removeIcon: {
type: [String, Object],
default: undefined,
},
/**
* Enables selectable behavior and keyboard toggle support.
*
* Defaults to `undefined` so a chip can override the value inherited from a `CChipSet`.
*/
selectable: {
type: Boolean,
default: undefined,
},
/**
* Controls the selected state of a selectable component.
*/
selected: {
type: Boolean,
default: undefined,
},
/**
* Replaces the default selected icon shown by a filter chip with a custom icon node.
*/
selectedIcon: {
type: [String, Object],
default: undefined,
},
/**
* Size the component small or large.
*
* @values 'sm', 'lg'
*/
size: {
type: String,
validator: (value) => {
return ['sm', 'lg'].includes(value);
},
},
/**
* Sets the value associated with the component, used by `CChipSet` to track selection.
*/
value: {
type: String,
default: undefined,
},
/**
* 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 = ref();
const internalSelected = ref(false);
// A chip placed inside a CChipSet / CChipInput reads its coordinated state
// from the set context; standalone it manages its own.
const chipSet = inject(chipSetContextKey, null);
const config = (key) => chipSet?.config.value[key];
// A chip's own prop (when set) overrides the set-level config; otherwise it
// inherits it. `??` makes this work because the overridable props default to
// `undefined`, so an explicit `false` still wins over the set.
const isDisabled = computed(() => Boolean(props.disabled ?? config('disabled')));
const isFilter = computed(() => Boolean(props.filter ?? config('filter')));
const isRemovable = computed(() => Boolean(props.removable ?? config('removable')));
// A filter chip is selectable by definition.
const isSelectable = computed(() => Boolean((props.selectable ?? config('selectable')) || isFilter.value));
const removeIcon = computed(() => props.removeIcon ?? config('removeIcon'));
const selectedIcon = computed(() => props.selectedIcon ?? config('selectedIcon'));
const ariaRemoveLabel = computed(() => props.ariaRemoveLabel);
const coordinated = computed(() => chipSet !== null && props.value !== undefined);
const selectedState = computed(() => {
if (coordinated.value) {
return chipSet.isSelected(props.value);
}
return props.selected === undefined ? internalSelected.value : Boolean(props.selected);
});
const isFocusable = computed(() => Boolean(!isDisabled.value && (isSelectable.value || isRemovable.value)));
const setSelectableState = (nextSelected, event) => {
if (!isSelectable.value || isDisabled.value || nextSelected === selectedState.value) {
return;
}
if (coordinated.value) {
chipSet.toggleSelected(props.value, nextSelected, event);
}
else 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) => {
if (coordinated.value) {
chipSet.removeChip(props.value, event);
}
emit('remove', event);
};
const handleRemoveClick = (event) => {
event.stopPropagation();
handleRemove(event);
};
const handleClick = (event) => {
if (isDisabled.value) {
return;
}
if (event.target.closest('.chip-remove')) {
return;
}
if (isSelectable.value) {
toggleSelectedState(event);
}
emit('click', event);
};
const handleKeydown = (event) => {
if (isDisabled.value) {
emit('keydown', event);
return;
}
switch (event.key) {
case 'Enter':
case ' ':
case 'Spacebar': {
if (isSelectable.value) {
event.preventDefault();
toggleSelectedState(event);
}
break;
}
case 'Backspace':
case 'Delete': {
if (isRemovable.value) {
event.preventDefault();
handleRemove(event);
}
break;
}
// No default
}
emit('keydown', event);
};
expose({ chipRef });
return () => {
const removeIconVNode = slots.removeIcon?.() ||
removeIcon.value ||
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',
}, [h('line', { x1: 4, y1: 4, x2: 12, y2: 12 }), h('line', { x1: 12, y1: 4, x2: 4, y2: 12 })]);
const selectedIconVNode = slots.selectedIcon?.() ||
selectedIcon.value ||
h('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: 16,
height: 16,
viewBox: '0 0 512 512',
fill: 'currentColor',
}, h('path', {
d: 'M425.373 89.373 196 318.745 86.627 209.373l-45.254 45.254L196 409.255l274.627-274.628z',
}));
const children = [
isFilter.value &&
selectedState.value &&
h('span', { class: 'chip-check', 'aria-hidden': 'true' }, selectedIconVNode),
slots.default && slots.default(),
isRemovable.value &&
!isDisabled.value &&
h('button', {
type: 'button',
class: 'chip-remove',
'aria-label': ariaRemoveLabel.value,
onClick: handleRemoveClick,
tabindex: -1,
}, removeIconVNode),
].filter(Boolean);
return h(props.as, {
ref: chipRef,
class: [
'chip',
{
active: isSelectable.value ? selectedState.value : props.active,
disabled: isDisabled.value,
[`chip-${props.color}`]: props.color,
[`chip-${props.size}`]: props.size,
'chip-clickable': props.clickable || isSelectable.value || attrs.onClick,
'chip-outline': props.variant === 'outline',
},
attrs.class,
],
'data-coreui-chip-value': props.value,
...(isDisabled.value && { 'aria-disabled': true }),
...(isSelectable.value && { 'aria-selected': selectedState.value }),
...(isFocusable.value && !attrs.tabindex && { tabindex: 0 }),
onClick: handleClick,
onKeydown: handleKeydown,
...(props.as === 'button' && { disabled: isDisabled.value }),
}, children);
};
},
});
export { CChip };
//# sourceMappingURL=CChip.js.map