routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
import { Fragment as _Fragment, createElementVNode as _createElementVNode, mergeProps as _mergeProps, createVNode as _createVNode, normalizeClass as _normalizeClass, createTextVNode as _createTextVNode, normalizeStyle as _normalizeStyle } from "vue";
|
||||
// Styles
|
||||
import "./VRating.css";
|
||||
|
||||
// Components
|
||||
import { VBtn } from "../VBtn/index.js"; // Composables
|
||||
import { makeComponentProps } from "../../composables/component.js";
|
||||
import { makeDensityProps } from "../../composables/density.js";
|
||||
import { IconValue } from "../../composables/icons.js";
|
||||
import { useLocale } from "../../composables/locale.js";
|
||||
import { useProxiedModel } from "../../composables/proxiedModel.js";
|
||||
import { makeSizeProps } from "../../composables/size.js";
|
||||
import { makeTagProps } from "../../composables/tag.js";
|
||||
import { makeThemeProps, provideTheme } from "../../composables/theme.js"; // Utilities
|
||||
import { computed, nextTick, ref, shallowRef, useId } from 'vue';
|
||||
import { clamp, createRange, genericComponent, propsFactory, useRender } from "../../util/index.js"; // Types
|
||||
export const makeVRatingProps = propsFactory({
|
||||
name: String,
|
||||
itemAriaLabel: {
|
||||
type: String,
|
||||
default: '$vuetify.rating.ariaLabel.item'
|
||||
},
|
||||
activeColor: String,
|
||||
color: String,
|
||||
clearable: Boolean,
|
||||
disabled: Boolean,
|
||||
emptyIcon: {
|
||||
type: IconValue,
|
||||
default: '$ratingEmpty'
|
||||
},
|
||||
fullIcon: {
|
||||
type: IconValue,
|
||||
default: '$ratingFull'
|
||||
},
|
||||
halfIncrements: Boolean,
|
||||
hover: Boolean,
|
||||
length: {
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
},
|
||||
readonly: Boolean,
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
itemLabels: Array,
|
||||
itemLabelPosition: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: v => ['top', 'bottom'].includes(v)
|
||||
},
|
||||
ripple: Boolean,
|
||||
...makeComponentProps(),
|
||||
...makeDensityProps(),
|
||||
...makeSizeProps(),
|
||||
...makeTagProps(),
|
||||
...makeThemeProps()
|
||||
}, 'VRating');
|
||||
export const VRating = genericComponent()({
|
||||
name: 'VRating',
|
||||
props: makeVRatingProps(),
|
||||
emits: {
|
||||
'update:modelValue': value => true
|
||||
},
|
||||
setup(props, {
|
||||
slots
|
||||
}) {
|
||||
const {
|
||||
t
|
||||
} = useLocale();
|
||||
const {
|
||||
themeClasses
|
||||
} = provideTheme(props);
|
||||
const root = ref();
|
||||
const rating = useProxiedModel(props, 'modelValue');
|
||||
const normalizedValue = computed(() => clamp(parseFloat(rating.value), 0, Number(props.length)));
|
||||
const range = computed(() => createRange(Number(props.length), 1));
|
||||
const increments = computed(() => range.value.flatMap(v => props.halfIncrements ? [v - 0.5, v] : [v]));
|
||||
const hoverIndex = shallowRef(-1);
|
||||
const itemState = computed(() => increments.value.map(value => {
|
||||
const isHovering = props.hover && hoverIndex.value > -1;
|
||||
const isFilled = normalizedValue.value >= value;
|
||||
const isHovered = hoverIndex.value >= value;
|
||||
const isFullIcon = isHovering ? isHovered : isFilled;
|
||||
const icon = isFullIcon ? props.fullIcon : props.emptyIcon;
|
||||
const activeColor = props.activeColor ?? props.color;
|
||||
const color = isFilled || isHovered ? activeColor : props.color;
|
||||
return {
|
||||
isFilled,
|
||||
isHovered,
|
||||
icon,
|
||||
color
|
||||
};
|
||||
}));
|
||||
const eventState = computed(() => [0, ...increments.value].map(value => {
|
||||
function onMouseenter() {
|
||||
hoverIndex.value = value;
|
||||
}
|
||||
function onMouseleave() {
|
||||
hoverIndex.value = -1;
|
||||
}
|
||||
function onClick() {
|
||||
if (props.disabled || props.readonly) return;
|
||||
rating.value = normalizedValue.value === value && props.clearable ? 0 : value;
|
||||
}
|
||||
return {
|
||||
onMouseenter: props.hover ? onMouseenter : undefined,
|
||||
onMouseleave: props.hover ? onMouseleave : undefined,
|
||||
onClick
|
||||
};
|
||||
}));
|
||||
const currentItemIndex = computed(() => {
|
||||
return props.halfIncrements ? 1 + Math.floor(Math.max(0, Number(rating.value ?? 0) - 0.5)) * 2 : Math.floor(Math.max(0, Number(rating.value ?? 0) - 1));
|
||||
});
|
||||
function moveCurrentFocus() {
|
||||
const currentItem = root.value?.querySelector('[tabindex="0"]');
|
||||
currentItem?.focus();
|
||||
}
|
||||
function onItemKeydown(event) {
|
||||
if (props.disabled || props.readonly) return;
|
||||
if (event.ctrlKey || event.altKey) return;
|
||||
const step = props.halfIncrements ? 0.5 : 1;
|
||||
if (event.key === 'ArrowRight') {
|
||||
const newValue = Math.min(Number(props.length), Number(rating.value ?? 0) + step);
|
||||
rating.value = newValue;
|
||||
nextTick(() => moveCurrentFocus());
|
||||
}
|
||||
if (event.key === 'ArrowLeft') {
|
||||
const newValue = Math.max(0, Number(rating.value ?? 0) - step);
|
||||
rating.value = newValue;
|
||||
nextTick(() => moveCurrentFocus());
|
||||
}
|
||||
}
|
||||
const uid = useId();
|
||||
const name = computed(() => props.name ?? `v-rating-${uid}`);
|
||||
function VRatingItem({
|
||||
value,
|
||||
index,
|
||||
showStar = true
|
||||
}) {
|
||||
const {
|
||||
onMouseenter,
|
||||
onMouseleave,
|
||||
onClick
|
||||
} = eventState.value[index + 1];
|
||||
const id = `${name.value}-${String(value).replace('.', '-')}`;
|
||||
const isFocusable = index === currentItemIndex.value;
|
||||
const btnProps = {
|
||||
color: itemState.value[index]?.color,
|
||||
density: props.density,
|
||||
disabled: props.disabled,
|
||||
icon: itemState.value[index]?.icon,
|
||||
ripple: props.ripple,
|
||||
size: props.size,
|
||||
variant: 'plain',
|
||||
tabindex: isFocusable ? 0 : -1,
|
||||
onKeydown: onItemKeydown
|
||||
};
|
||||
return _createElementVNode(_Fragment, null, [_createElementVNode("label", {
|
||||
"for": id,
|
||||
"class": _normalizeClass({
|
||||
'v-rating__item--half': props.halfIncrements && value % 1 > 0,
|
||||
'v-rating__item--full': props.halfIncrements && value % 1 === 0
|
||||
}),
|
||||
"onMouseenter": onMouseenter,
|
||||
"onMouseleave": onMouseleave,
|
||||
"onClick": onClick
|
||||
}, [_createElementVNode("span", {
|
||||
"class": "v-rating__hidden"
|
||||
}, [t(props.itemAriaLabel, value, props.length)]), !showStar ? undefined : slots.item ? slots.item({
|
||||
...itemState.value[index],
|
||||
props: btnProps,
|
||||
value,
|
||||
index,
|
||||
rating: normalizedValue.value
|
||||
}) : _createVNode(VBtn, _mergeProps({
|
||||
"aria-label": t(props.itemAriaLabel, value, props.length)
|
||||
}, btnProps), null)]), _createElementVNode("input", {
|
||||
"class": "v-rating__hidden",
|
||||
"name": name.value,
|
||||
"id": id,
|
||||
"type": "radio",
|
||||
"value": value,
|
||||
"checked": normalizedValue.value === value,
|
||||
"tabindex": -1,
|
||||
"readonly": props.readonly,
|
||||
"disabled": props.disabled
|
||||
}, null)]);
|
||||
}
|
||||
function createLabel(labelProps) {
|
||||
if (slots['item-label']) return slots['item-label'](labelProps);
|
||||
if (labelProps.label) return _createElementVNode("span", null, [labelProps.label]);
|
||||
return _createElementVNode("span", null, [_createTextVNode("\xA0")]);
|
||||
}
|
||||
useRender(() => {
|
||||
const hasLabels = !!props.itemLabels?.length || slots['item-label'];
|
||||
return _createVNode(props.tag, {
|
||||
"class": _normalizeClass(['v-rating', {
|
||||
'v-rating--hover': props.hover,
|
||||
'v-rating--readonly': props.readonly
|
||||
}, themeClasses.value, props.class]),
|
||||
"style": _normalizeStyle(props.style),
|
||||
"ref": root
|
||||
}, {
|
||||
default: () => [_createVNode(VRatingItem, {
|
||||
"value": 0,
|
||||
"index": -1,
|
||||
"showStar": false
|
||||
}, null), range.value.map((value, i) => _createElementVNode("div", {
|
||||
"class": "v-rating__wrapper"
|
||||
}, [hasLabels && props.itemLabelPosition === 'top' ? createLabel({
|
||||
value,
|
||||
index: i,
|
||||
label: props.itemLabels?.[i]
|
||||
}) : undefined, _createElementVNode("div", {
|
||||
"class": "v-rating__item"
|
||||
}, [props.halfIncrements ? _createElementVNode(_Fragment, null, [_createVNode(VRatingItem, {
|
||||
"value": value - 0.5,
|
||||
"index": i * 2
|
||||
}, null), _createVNode(VRatingItem, {
|
||||
"value": value,
|
||||
"index": i * 2 + 1
|
||||
}, null)]) : _createVNode(VRatingItem, {
|
||||
"value": value,
|
||||
"index": i
|
||||
}, null)]), hasLabels && props.itemLabelPosition === 'bottom' ? createLabel({
|
||||
value,
|
||||
index: i,
|
||||
label: props.itemLabels?.[i]
|
||||
}) : undefined]))]
|
||||
});
|
||||
});
|
||||
return {};
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=VRating.js.map
|
||||
Reference in New Issue
Block a user