naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
295 lines • 9.81 kB
JavaScript
import { Transition, computed, defineComponent, h, inject, mergeProps, provide, ref } from 'vue';
import { VBinder, VFollower, VTarget } from 'vueuc';
import { useMemo } from 'vooks';
import { happensIn } from 'seemly';
import { ChevronRightIcon } from "../../_internal/icons/index.mjs";
import { render, useDeferredTrue } from "../../_utils/index.mjs";
import { NIcon } from "../../icon/index.mjs";
import { popoverBodyInjectionKey } from "../../popover/src/interface.mjs";
import NDropdownMenu from "./DropdownMenu.mjs";
import { dropdownInjectionKey, dropdownMenuInjectionKey, dropdownOptionInjectionKey } from "./context.mjs";
import { isSubmenuNode } from "./utils.mjs";
export default defineComponent({
name: 'DropdownOption',
props: {
clsPrefix: {
type: String,
required: true
},
tmNode: {
type: Object,
required: true
},
parentKey: {
type: [String, Number],
default: null
},
placement: {
type: String,
default: 'right-start'
},
props: Object,
scrollable: Boolean
},
setup(props) {
const NDropdown = inject(dropdownInjectionKey);
const {
hoverKeyRef,
keyboardKeyRef,
lastToggledSubmenuKeyRef,
pendingKeyPathRef,
activeKeyPathRef,
animatedRef,
mergedShowRef,
renderLabelRef,
renderIconRef,
labelFieldRef,
childrenFieldRef,
renderOptionRef,
nodePropsRef,
menuPropsRef
} = NDropdown;
const NDropdownOption = inject(dropdownOptionInjectionKey, null);
const NDropdownMenu = inject(dropdownMenuInjectionKey);
const NPopoverBody = inject(popoverBodyInjectionKey);
const rawNodeRef = computed(() => props.tmNode.rawNode);
const hasSubmenuRef = computed(() => {
const {
value: childrenField
} = childrenFieldRef;
return isSubmenuNode(props.tmNode.rawNode, childrenField);
});
const mergedDisabledRef = computed(() => {
const {
disabled
} = props.tmNode;
return disabled;
});
const showSubmenuRef = computed(() => {
if (!hasSubmenuRef.value) return false;
const {
key,
disabled
} = props.tmNode;
if (disabled) return false;
const {
value: hoverKey
} = hoverKeyRef;
const {
value: keyboardKey
} = keyboardKeyRef;
const {
value: lastToggledSubmenuKey
} = lastToggledSubmenuKeyRef;
const {
value: pendingKeyPath
} = pendingKeyPathRef;
if (hoverKey !== null) return pendingKeyPath.includes(key);
if (keyboardKey !== null) {
return pendingKeyPath.includes(key) && pendingKeyPath[pendingKeyPath.length - 1] !== key;
}
if (lastToggledSubmenuKey !== null) return pendingKeyPath.includes(key);
return false;
});
const shouldDelayRef = computed(() => {
return keyboardKeyRef.value === null && !animatedRef.value;
});
const deferredShowSubmenuRef = useDeferredTrue(showSubmenuRef, 300, shouldDelayRef);
const parentEnteringSubmenuRef = computed(() => {
return !!(NDropdownOption === null || NDropdownOption === void 0 ? void 0 : NDropdownOption.enteringSubmenuRef.value);
});
const enteringSubmenuRef = ref(false);
provide(dropdownOptionInjectionKey, {
enteringSubmenuRef
});
// methods
function handleSubmenuBeforeEnter() {
enteringSubmenuRef.value = true;
}
function handleSubmenuAfterEnter() {
enteringSubmenuRef.value = false;
}
function handleMouseEnter() {
const {
parentKey,
tmNode
} = props;
if (tmNode.disabled) return;
if (!mergedShowRef.value) return;
lastToggledSubmenuKeyRef.value = parentKey;
keyboardKeyRef.value = null;
hoverKeyRef.value = tmNode.key;
}
function handleMouseMove() {
const {
tmNode
} = props;
if (tmNode.disabled) return;
if (!mergedShowRef.value) return;
if (hoverKeyRef.value === tmNode.key) return;
handleMouseEnter();
}
function handleMouseLeave(e) {
if (props.tmNode.disabled) return;
if (!mergedShowRef.value) return;
const {
relatedTarget
} = e;
if (relatedTarget && !happensIn({
target: relatedTarget
}, 'dropdownOption') && !happensIn({
target: relatedTarget
}, 'scrollbarRail')) {
hoverKeyRef.value = null;
}
}
function handleClick() {
const {
value: hasSubmenu
} = hasSubmenuRef;
const {
tmNode
} = props;
if (!mergedShowRef.value) return;
if (!hasSubmenu && !tmNode.disabled) {
NDropdown.doSelect(tmNode.key, tmNode.rawNode);
NDropdown.doUpdateShow(false);
}
}
return {
labelField: labelFieldRef,
renderLabel: renderLabelRef,
renderIcon: renderIconRef,
siblingHasIcon: NDropdownMenu.showIconRef,
siblingHasSubmenu: NDropdownMenu.hasSubmenuRef,
menuProps: menuPropsRef,
popoverBody: NPopoverBody,
animated: animatedRef,
mergedShowSubmenu: computed(() => {
return deferredShowSubmenuRef.value && !parentEnteringSubmenuRef.value;
}),
rawNode: rawNodeRef,
hasSubmenu: hasSubmenuRef,
pending: useMemo(() => {
const {
value: pendingKeyPath
} = pendingKeyPathRef;
const {
key
} = props.tmNode;
return pendingKeyPath.includes(key);
}),
childActive: useMemo(() => {
const {
value: activeKeyPath
} = activeKeyPathRef;
const {
key
} = props.tmNode;
const index = activeKeyPath.findIndex(k => key === k);
if (index === -1) return false;
return index < activeKeyPath.length - 1;
}),
active: useMemo(() => {
const {
value: activeKeyPath
} = activeKeyPathRef;
const {
key
} = props.tmNode;
const index = activeKeyPath.findIndex(k => key === k);
if (index === -1) return false;
return index === activeKeyPath.length - 1;
}),
mergedDisabled: mergedDisabledRef,
renderOption: renderOptionRef,
nodeProps: nodePropsRef,
handleClick,
handleMouseMove,
handleMouseEnter,
handleMouseLeave,
handleSubmenuBeforeEnter,
handleSubmenuAfterEnter
};
},
render() {
var _a, _b;
const {
animated,
rawNode,
mergedShowSubmenu,
clsPrefix,
siblingHasIcon,
siblingHasSubmenu,
renderLabel,
renderIcon,
renderOption,
nodeProps,
props,
scrollable
} = this;
let submenuVNode = null;
if (mergedShowSubmenu) {
const submenuNodeProps = (_a = this.menuProps) === null || _a === void 0 ? void 0 : _a.call(this, rawNode, rawNode.children);
submenuVNode = h(NDropdownMenu, Object.assign({}, submenuNodeProps, {
clsPrefix: clsPrefix,
scrollable: this.scrollable,
tmNodes: this.tmNode.children,
parentKey: this.tmNode.key
}));
}
const builtinProps = {
class: [`${clsPrefix}-dropdown-option-body`, this.pending && `${clsPrefix}-dropdown-option-body--pending`, this.active && `${clsPrefix}-dropdown-option-body--active`, this.childActive && `${clsPrefix}-dropdown-option-body--child-active`, this.mergedDisabled && `${clsPrefix}-dropdown-option-body--disabled`],
onMousemove: this.handleMouseMove,
onMouseenter: this.handleMouseEnter,
onMouseleave: this.handleMouseLeave,
onClick: this.handleClick
};
const optionNodeProps = nodeProps === null || nodeProps === void 0 ? void 0 : nodeProps(rawNode);
const node = h("div", Object.assign({
class: [`${clsPrefix}-dropdown-option`, optionNodeProps === null || optionNodeProps === void 0 ? void 0 : optionNodeProps.class],
"data-dropdown-option": true
}, optionNodeProps), h('div', mergeProps(builtinProps, props), [h("div", {
class: [`${clsPrefix}-dropdown-option-body__prefix`, siblingHasIcon && `${clsPrefix}-dropdown-option-body__prefix--show-icon`]
}, [renderIcon ? renderIcon(rawNode) : render(rawNode.icon)]), h("div", {
"data-dropdown-option": true,
class: `${clsPrefix}-dropdown-option-body__label`
}, renderLabel ? renderLabel(rawNode) : render((_b = rawNode[this.labelField]) !== null && _b !== void 0 ? _b : rawNode.title)), h("div", {
"data-dropdown-option": true,
class: [`${clsPrefix}-dropdown-option-body__suffix`, siblingHasSubmenu && `${clsPrefix}-dropdown-option-body__suffix--has-submenu`]
}, this.hasSubmenu ? h(NIcon, null, {
default: () => h(ChevronRightIcon, null)
}) : null)]), this.hasSubmenu ? h(VBinder, null, {
default: () => [h(VTarget, null, {
default: () => h("div", {
class: `${clsPrefix}-dropdown-offset-container`
}, h(VFollower, {
show: this.mergedShowSubmenu,
placement: this.placement,
to: scrollable ? this.popoverBody || undefined : undefined,
teleportDisabled: !scrollable
}, {
default: () => {
return h("div", {
class: `${clsPrefix}-dropdown-menu-wrapper`
}, animated ? h(Transition, {
onBeforeEnter: this.handleSubmenuBeforeEnter,
onAfterEnter: this.handleSubmenuAfterEnter,
name: "fade-in-scale-up-transition",
appear: true
}, {
default: () => submenuVNode
}) : submenuVNode);
}
}))
})]
}) : null);
if (renderOption) {
return renderOption({
node,
option: rawNode
});
}
return node;
}
});