UNPKG

@coreui/vue

Version:

UI Components Library for Vue.js

287 lines (283 loc) 9.97 kB
'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