225 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { computed, defineComponent, ref, createVNode as _createVNode } from "vue";
 | 
						|
import { addUnit, truthProp, numericProp, preventDefault, makeStringProp, makeNumberProp, makeNumericProp, createNamespace } from "../utils/index.mjs";
 | 
						|
import { useRect, useCustomFieldValue, useEventListener } from "@vant/use";
 | 
						|
import { useRefs } from "../composables/use-refs.mjs";
 | 
						|
import { useTouch } from "../composables/use-touch.mjs";
 | 
						|
import { Icon } from "../icon/index.mjs";
 | 
						|
const [name, bem] = createNamespace("rate");
 | 
						|
function getRateStatus(value, index, allowHalf, readonly) {
 | 
						|
  if (value >= index) {
 | 
						|
    return {
 | 
						|
      status: "full",
 | 
						|
      value: 1
 | 
						|
    };
 | 
						|
  }
 | 
						|
  if (value + 0.5 >= index && allowHalf && !readonly) {
 | 
						|
    return {
 | 
						|
      status: "half",
 | 
						|
      value: 0.5
 | 
						|
    };
 | 
						|
  }
 | 
						|
  if (value + 1 >= index && allowHalf && readonly) {
 | 
						|
    const cardinal = 10 ** 10;
 | 
						|
    return {
 | 
						|
      status: "half",
 | 
						|
      value: Math.round((value - index + 1) * cardinal) / cardinal
 | 
						|
    };
 | 
						|
  }
 | 
						|
  return {
 | 
						|
    status: "void",
 | 
						|
    value: 0
 | 
						|
  };
 | 
						|
}
 | 
						|
const rateProps = {
 | 
						|
  size: numericProp,
 | 
						|
  icon: makeStringProp("star"),
 | 
						|
  color: String,
 | 
						|
  count: makeNumericProp(5),
 | 
						|
  gutter: numericProp,
 | 
						|
  clearable: Boolean,
 | 
						|
  readonly: Boolean,
 | 
						|
  disabled: Boolean,
 | 
						|
  voidIcon: makeStringProp("star-o"),
 | 
						|
  allowHalf: Boolean,
 | 
						|
  voidColor: String,
 | 
						|
  touchable: truthProp,
 | 
						|
  iconPrefix: String,
 | 
						|
  modelValue: makeNumberProp(0),
 | 
						|
  disabledColor: String
 | 
						|
};
 | 
						|
var stdin_default = defineComponent({
 | 
						|
  name,
 | 
						|
  props: rateProps,
 | 
						|
  emits: ["change", "update:modelValue"],
 | 
						|
  setup(props, {
 | 
						|
    emit
 | 
						|
  }) {
 | 
						|
    const touch = useTouch();
 | 
						|
    const [itemRefs, setItemRefs] = useRefs();
 | 
						|
    const groupRef = ref();
 | 
						|
    const unselectable = computed(() => props.readonly || props.disabled);
 | 
						|
    const untouchable = computed(() => unselectable.value || !props.touchable);
 | 
						|
    const list = computed(() => Array(+props.count).fill("").map((_, i) => getRateStatus(props.modelValue, i + 1, props.allowHalf, props.readonly)));
 | 
						|
    let ranges;
 | 
						|
    let groupRefRect;
 | 
						|
    let minRectTop = Number.MAX_SAFE_INTEGER;
 | 
						|
    let maxRectTop = Number.MIN_SAFE_INTEGER;
 | 
						|
    const updateRanges = () => {
 | 
						|
      groupRefRect = useRect(groupRef);
 | 
						|
      const rects = itemRefs.value.map(useRect);
 | 
						|
      ranges = [];
 | 
						|
      rects.forEach((rect, index) => {
 | 
						|
        minRectTop = Math.min(rect.top, minRectTop);
 | 
						|
        maxRectTop = Math.max(rect.top, maxRectTop);
 | 
						|
        if (props.allowHalf) {
 | 
						|
          ranges.push({
 | 
						|
            score: index + 0.5,
 | 
						|
            left: rect.left,
 | 
						|
            top: rect.top,
 | 
						|
            height: rect.height
 | 
						|
          }, {
 | 
						|
            score: index + 1,
 | 
						|
            left: rect.left + rect.width / 2,
 | 
						|
            top: rect.top,
 | 
						|
            height: rect.height
 | 
						|
          });
 | 
						|
        } else {
 | 
						|
          ranges.push({
 | 
						|
            score: index + 1,
 | 
						|
            left: rect.left,
 | 
						|
            top: rect.top,
 | 
						|
            height: rect.height
 | 
						|
          });
 | 
						|
        }
 | 
						|
      });
 | 
						|
    };
 | 
						|
    const getScoreByPosition = (x, y) => {
 | 
						|
      for (let i = ranges.length - 1; i > 0; i--) {
 | 
						|
        if (y >= groupRefRect.top && y <= groupRefRect.bottom) {
 | 
						|
          if (x > ranges[i].left && y >= ranges[i].top && y <= ranges[i].top + ranges[i].height) {
 | 
						|
            return ranges[i].score;
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          const curTop = y < groupRefRect.top ? minRectTop : maxRectTop;
 | 
						|
          if (x > ranges[i].left && ranges[i].top === curTop) {
 | 
						|
            return ranges[i].score;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return props.allowHalf ? 0.5 : 1;
 | 
						|
    };
 | 
						|
    const select = (value) => {
 | 
						|
      if (unselectable.value || value === props.modelValue) return;
 | 
						|
      emit("update:modelValue", value);
 | 
						|
      emit("change", value);
 | 
						|
    };
 | 
						|
    const onTouchStart = (event) => {
 | 
						|
      if (untouchable.value) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      touch.start(event);
 | 
						|
      updateRanges();
 | 
						|
    };
 | 
						|
    const onTouchMove = (event) => {
 | 
						|
      if (untouchable.value) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      touch.move(event);
 | 
						|
      if (touch.isHorizontal() && !touch.isTap.value) {
 | 
						|
        const {
 | 
						|
          clientX,
 | 
						|
          clientY
 | 
						|
        } = event.touches[0];
 | 
						|
        preventDefault(event);
 | 
						|
        select(getScoreByPosition(clientX, clientY));
 | 
						|
      }
 | 
						|
    };
 | 
						|
    const renderStar = (item, index) => {
 | 
						|
      const {
 | 
						|
        icon,
 | 
						|
        size,
 | 
						|
        color,
 | 
						|
        count,
 | 
						|
        gutter,
 | 
						|
        voidIcon,
 | 
						|
        disabled,
 | 
						|
        voidColor,
 | 
						|
        allowHalf,
 | 
						|
        iconPrefix,
 | 
						|
        disabledColor
 | 
						|
      } = props;
 | 
						|
      const score = index + 1;
 | 
						|
      const isFull = item.status === "full";
 | 
						|
      const isVoid = item.status === "void";
 | 
						|
      const renderHalf = allowHalf && item.value > 0 && item.value < 1;
 | 
						|
      let style;
 | 
						|
      if (gutter && score !== +count) {
 | 
						|
        style = {
 | 
						|
          paddingRight: addUnit(gutter)
 | 
						|
        };
 | 
						|
      }
 | 
						|
      const onClickItem = (event) => {
 | 
						|
        updateRanges();
 | 
						|
        let value = allowHalf ? getScoreByPosition(event.clientX, event.clientY) : score;
 | 
						|
        if (props.clearable && touch.isTap.value && value === props.modelValue) {
 | 
						|
          value = 0;
 | 
						|
        }
 | 
						|
        select(value);
 | 
						|
      };
 | 
						|
      return _createVNode("div", {
 | 
						|
        "key": index,
 | 
						|
        "ref": setItemRefs(index),
 | 
						|
        "role": "radio",
 | 
						|
        "style": style,
 | 
						|
        "class": bem("item"),
 | 
						|
        "tabindex": disabled ? void 0 : 0,
 | 
						|
        "aria-setsize": count,
 | 
						|
        "aria-posinset": score,
 | 
						|
        "aria-checked": !isVoid,
 | 
						|
        "onClick": onClickItem
 | 
						|
      }, [_createVNode(Icon, {
 | 
						|
        "size": size,
 | 
						|
        "name": isFull ? icon : voidIcon,
 | 
						|
        "class": bem("icon", {
 | 
						|
          disabled,
 | 
						|
          full: isFull
 | 
						|
        }),
 | 
						|
        "color": disabled ? disabledColor : isFull ? color : voidColor,
 | 
						|
        "classPrefix": iconPrefix
 | 
						|
      }, null), renderHalf && _createVNode(Icon, {
 | 
						|
        "size": size,
 | 
						|
        "style": {
 | 
						|
          width: item.value + "em"
 | 
						|
        },
 | 
						|
        "name": isVoid ? voidIcon : icon,
 | 
						|
        "class": bem("icon", ["half", {
 | 
						|
          disabled,
 | 
						|
          full: !isVoid
 | 
						|
        }]),
 | 
						|
        "color": disabled ? disabledColor : isVoid ? voidColor : color,
 | 
						|
        "classPrefix": iconPrefix
 | 
						|
      }, null)]);
 | 
						|
    };
 | 
						|
    useCustomFieldValue(() => props.modelValue);
 | 
						|
    useEventListener("touchmove", onTouchMove, {
 | 
						|
      target: groupRef
 | 
						|
    });
 | 
						|
    return () => _createVNode("div", {
 | 
						|
      "ref": groupRef,
 | 
						|
      "role": "radiogroup",
 | 
						|
      "class": bem({
 | 
						|
        readonly: props.readonly,
 | 
						|
        disabled: props.disabled
 | 
						|
      }),
 | 
						|
      "tabindex": props.disabled ? void 0 : 0,
 | 
						|
      "aria-disabled": props.disabled,
 | 
						|
      "aria-readonly": props.readonly,
 | 
						|
      "onTouchstartPassive": onTouchStart
 | 
						|
    }, [list.value.map(renderStar)]);
 | 
						|
  }
 | 
						|
});
 | 
						|
export {
 | 
						|
  stdin_default as default,
 | 
						|
  rateProps
 | 
						|
};
 |