All files / composables useSplitAttributes.ts

100% Statements 19/19
100% Branches 3/3
100% Functions 6/6
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 7416x                                                           16x   2x               642x 818x 818x 312x 312x 315x     818x       642x 636x 1x     635x       642x   724x 724x     642x            
import { SetupContext, ComputedRef, StyleValue, computed } from 'vue';
 
/**
 * Splits attributes so some can be applied to the root element and others to a child element.
 *
 * For some components, attributes that are bound to the component shouldn't be bound to the root
 * element of the component (the default behavior), but rather should be bound to a child element
 * within the component. For example, you might want to be able to add a `placeholder` attribute on
 * a component which is then applied to an <input> element within that component.
 *
 * However, there are some attributes that should always be placed on the root element, namely
 * class and style.
 *
 * For these components, this composable can be used to take provided attributes and split them into
 * CSS classes for the root element (including any classes provided within the component, e.g.
 * dynamic or conditional classes), a style attribute for the root element, and all other
 * attributes (which can then be bound to the child element).
 *
 * This composable should be used with the `inheritAttrs: false` component option.
 *
 * See TextInput for sample usage.
 *
 * @param attrs Attributes from context
 * @param internalClasses A computed ref returning an object that includes any class names that will
 *                        be added to the root element of the component. Formatted according to
 *                        Vue's class binding object syntax, e.g. { 'cdx-button--primary': true }
 * @return rootClasses: all CSS classes that should be placed on the root element,
 *         rootStyle: style attribute to be placed on the root element,
 *         otherAttrs: all other attributes, which will be bound to another element
 */
export default function useSplitAttributes(
	attrs: SetupContext[ 'attrs' ],
	internalClasses: ComputedRef<Record<string, boolean>> = computed( () => ( {} ) )
): {
	rootClasses: ComputedRef<Record<string, boolean>>,
	rootStyle: ComputedRef<StyleValue|undefined>,
	otherAttrs: ComputedRef<SetupContext[ 'attrs' ]>
} {
	// Build an object of root classes including those set in the component (internal classes) and
	// those set on the component in the parent (provided classes).
	const rootClasses = computed( () => {
		const { ...classRecord } = internalClasses.value;
		if ( attrs.class ) {
			const providedClasses = ( attrs.class as string ).split( ' ' );
			providedClasses.forEach( ( className ) => {
				classRecord[ className ] = true;
			} );
		}
		return classRecord;
	} );
 
	// Get the style attribute set on the component in the parent.
	const rootStyle = computed( () => {
		if ( 'style' in attrs ) {
			return attrs.style as StyleValue;
		}
 
		return undefined;
	} );
 
	// Copy attrs and remove class and style, so all other attrs can be bound to another element.
	const otherAttrs = computed( () => {
		// eslint-disable-next-line @typescript-eslint/no-unused-vars
		const { class: _ignoredClass, style: _ignoredStyle, ...attrsCopy } = attrs;
		return attrsCopy;
	} );
 
	return {
		rootClasses,
		rootStyle,
		otherAttrs
	};
}