208 lines
7.0 KiB
JavaScript
208 lines
7.0 KiB
JavaScript
|
import { withDirectives as _withDirectives, resolveDirective as _resolveDirective, Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
|
||
|
// Styles
|
||
|
import "./VSelectionControl.css";
|
||
|
|
||
|
// Components
|
||
|
import { VIcon } from "../VIcon/index.mjs";
|
||
|
import { VLabel } from "../VLabel/index.mjs";
|
||
|
import { makeSelectionControlGroupProps, VSelectionControlGroupSymbol } from "../VSelectionControlGroup/VSelectionControlGroup.mjs"; // Composables
|
||
|
import { useBackgroundColor, useTextColor } from "../../composables/color.mjs";
|
||
|
import { makeComponentProps } from "../../composables/component.mjs";
|
||
|
import { useDensity } from "../../composables/density.mjs";
|
||
|
import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Directives
|
||
|
import { Ripple } from "../../directives/ripple/index.mjs"; // Utilities
|
||
|
import { computed, inject, nextTick, ref, shallowRef } from 'vue';
|
||
|
import { filterInputAttrs, genericComponent, getUid, matchesSelector, propsFactory, useRender, wrapInArray } from "../../util/index.mjs"; // Types
|
||
|
export const makeVSelectionControlProps = propsFactory({
|
||
|
label: String,
|
||
|
baseColor: String,
|
||
|
trueValue: null,
|
||
|
falseValue: null,
|
||
|
value: null,
|
||
|
...makeComponentProps(),
|
||
|
...makeSelectionControlGroupProps()
|
||
|
}, 'VSelectionControl');
|
||
|
export function useSelectionControl(props) {
|
||
|
const group = inject(VSelectionControlGroupSymbol, undefined);
|
||
|
const {
|
||
|
densityClasses
|
||
|
} = useDensity(props);
|
||
|
const modelValue = useProxiedModel(props, 'modelValue');
|
||
|
const trueValue = computed(() => props.trueValue !== undefined ? props.trueValue : props.value !== undefined ? props.value : true);
|
||
|
const falseValue = computed(() => props.falseValue !== undefined ? props.falseValue : false);
|
||
|
const isMultiple = computed(() => !!props.multiple || props.multiple == null && Array.isArray(modelValue.value));
|
||
|
const model = computed({
|
||
|
get() {
|
||
|
const val = group ? group.modelValue.value : modelValue.value;
|
||
|
return isMultiple.value ? wrapInArray(val).some(v => props.valueComparator(v, trueValue.value)) : props.valueComparator(val, trueValue.value);
|
||
|
},
|
||
|
set(val) {
|
||
|
if (props.readonly) return;
|
||
|
const currentValue = val ? trueValue.value : falseValue.value;
|
||
|
let newVal = currentValue;
|
||
|
if (isMultiple.value) {
|
||
|
newVal = val ? [...wrapInArray(modelValue.value), currentValue] : wrapInArray(modelValue.value).filter(item => !props.valueComparator(item, trueValue.value));
|
||
|
}
|
||
|
if (group) {
|
||
|
group.modelValue.value = newVal;
|
||
|
} else {
|
||
|
modelValue.value = newVal;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
const {
|
||
|
textColorClasses,
|
||
|
textColorStyles
|
||
|
} = useTextColor(computed(() => {
|
||
|
if (props.error || props.disabled) return undefined;
|
||
|
return model.value ? props.color : props.baseColor;
|
||
|
}));
|
||
|
const {
|
||
|
backgroundColorClasses,
|
||
|
backgroundColorStyles
|
||
|
} = useBackgroundColor(computed(() => {
|
||
|
return model.value && !props.error && !props.disabled ? props.color : undefined;
|
||
|
}));
|
||
|
const icon = computed(() => model.value ? props.trueIcon : props.falseIcon);
|
||
|
return {
|
||
|
group,
|
||
|
densityClasses,
|
||
|
trueValue,
|
||
|
falseValue,
|
||
|
model,
|
||
|
textColorClasses,
|
||
|
textColorStyles,
|
||
|
backgroundColorClasses,
|
||
|
backgroundColorStyles,
|
||
|
icon
|
||
|
};
|
||
|
}
|
||
|
export const VSelectionControl = genericComponent()({
|
||
|
name: 'VSelectionControl',
|
||
|
directives: {
|
||
|
Ripple
|
||
|
},
|
||
|
inheritAttrs: false,
|
||
|
props: makeVSelectionControlProps(),
|
||
|
emits: {
|
||
|
'update:modelValue': value => true
|
||
|
},
|
||
|
setup(props, _ref) {
|
||
|
let {
|
||
|
attrs,
|
||
|
slots
|
||
|
} = _ref;
|
||
|
const {
|
||
|
group,
|
||
|
densityClasses,
|
||
|
icon,
|
||
|
model,
|
||
|
textColorClasses,
|
||
|
textColorStyles,
|
||
|
backgroundColorClasses,
|
||
|
backgroundColorStyles,
|
||
|
trueValue
|
||
|
} = useSelectionControl(props);
|
||
|
const uid = getUid();
|
||
|
const isFocused = shallowRef(false);
|
||
|
const isFocusVisible = shallowRef(false);
|
||
|
const input = ref();
|
||
|
const id = computed(() => props.id || `input-${uid}`);
|
||
|
const isInteractive = computed(() => !props.disabled && !props.readonly);
|
||
|
group?.onForceUpdate(() => {
|
||
|
if (input.value) {
|
||
|
input.value.checked = model.value;
|
||
|
}
|
||
|
});
|
||
|
function onFocus(e) {
|
||
|
if (!isInteractive.value) return;
|
||
|
isFocused.value = true;
|
||
|
if (matchesSelector(e.target, ':focus-visible') !== false) {
|
||
|
isFocusVisible.value = true;
|
||
|
}
|
||
|
}
|
||
|
function onBlur() {
|
||
|
isFocused.value = false;
|
||
|
isFocusVisible.value = false;
|
||
|
}
|
||
|
function onClickLabel(e) {
|
||
|
e.stopPropagation();
|
||
|
}
|
||
|
function onInput(e) {
|
||
|
if (!isInteractive.value) return;
|
||
|
if (props.readonly && group) {
|
||
|
nextTick(() => group.forceUpdate());
|
||
|
}
|
||
|
model.value = e.target.checked;
|
||
|
}
|
||
|
useRender(() => {
|
||
|
const label = slots.label ? slots.label({
|
||
|
label: props.label,
|
||
|
props: {
|
||
|
for: id.value
|
||
|
}
|
||
|
}) : props.label;
|
||
|
const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
|
||
|
const inputNode = _createVNode("input", _mergeProps({
|
||
|
"ref": input,
|
||
|
"checked": model.value,
|
||
|
"disabled": !!props.disabled,
|
||
|
"id": id.value,
|
||
|
"onBlur": onBlur,
|
||
|
"onFocus": onFocus,
|
||
|
"onInput": onInput,
|
||
|
"aria-disabled": !!props.disabled,
|
||
|
"type": props.type,
|
||
|
"value": trueValue.value,
|
||
|
"name": props.name,
|
||
|
"aria-checked": props.type === 'checkbox' ? model.value : undefined
|
||
|
}, inputAttrs), null);
|
||
|
return _createVNode("div", _mergeProps({
|
||
|
"class": ['v-selection-control', {
|
||
|
'v-selection-control--dirty': model.value,
|
||
|
'v-selection-control--disabled': props.disabled,
|
||
|
'v-selection-control--error': props.error,
|
||
|
'v-selection-control--focused': isFocused.value,
|
||
|
'v-selection-control--focus-visible': isFocusVisible.value,
|
||
|
'v-selection-control--inline': props.inline
|
||
|
}, densityClasses.value, props.class]
|
||
|
}, rootAttrs, {
|
||
|
"style": props.style
|
||
|
}), [_createVNode("div", {
|
||
|
"class": ['v-selection-control__wrapper', textColorClasses.value],
|
||
|
"style": textColorStyles.value
|
||
|
}, [slots.default?.({
|
||
|
backgroundColorClasses,
|
||
|
backgroundColorStyles
|
||
|
}), _withDirectives(_createVNode("div", {
|
||
|
"class": ['v-selection-control__input']
|
||
|
}, [slots.input?.({
|
||
|
model,
|
||
|
textColorClasses,
|
||
|
textColorStyles,
|
||
|
backgroundColorClasses,
|
||
|
backgroundColorStyles,
|
||
|
inputNode,
|
||
|
icon: icon.value,
|
||
|
props: {
|
||
|
onFocus,
|
||
|
onBlur,
|
||
|
id: id.value
|
||
|
}
|
||
|
}) ?? _createVNode(_Fragment, null, [icon.value && _createVNode(VIcon, {
|
||
|
"key": "icon",
|
||
|
"icon": icon.value
|
||
|
}, null), inputNode])]), [[_resolveDirective("ripple"), props.ripple && [!props.disabled && !props.readonly, null, ['center', 'circle']]]])]), label && _createVNode(VLabel, {
|
||
|
"for": id.value,
|
||
|
"onClick": onClickLabel
|
||
|
}, {
|
||
|
default: () => [label]
|
||
|
})]);
|
||
|
});
|
||
|
return {
|
||
|
isFocused,
|
||
|
input
|
||
|
};
|
||
|
}
|
||
|
});
|
||
|
//# sourceMappingURL=VSelectionControl.mjs.map
|