UNPKG

@coreui/vue

Version:

UI Components Library for Vue.js

313 lines (310 loc) 11.4 kB
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