UNPKG

naive-ui

Version:

A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast

504 lines 16.7 kB
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue'; import { createIndexGetter } from 'treemate'; import { VirtualList } from 'vueuc'; import { depx, getPadding, happensIn } from 'seemly'; import { NEmpty } from "../../../empty/index.mjs"; import { NScrollbar } from "../../scrollbar/index.mjs"; import { resolveSlot, resolveWrappedSlot, useOnResize } from "../../../_utils/index.mjs"; import { createKey } from "../../../_utils/cssr/index.mjs"; import { useConfig, useRtl, useTheme, useThemeClass } from "../../../_mixins/index.mjs"; import NInternalLoading from "../../loading/index.mjs"; import NFocusDetector from "../../focus-detector/index.mjs"; import { internalSelectMenuLight } from "../styles/index.mjs"; import NSelectOption from "./SelectOption.mjs"; import NSelectGroupHeader from "./SelectGroupHeader.mjs"; import { internalSelectionMenuBodyInjectionKey, internalSelectionMenuInjectionKey } from "./interface.mjs"; import style from "./styles/index.cssr.mjs"; export default defineComponent({ name: 'InternalSelectMenu', props: Object.assign(Object.assign({}, useTheme.props), { clsPrefix: { type: String, required: true }, scrollable: { type: Boolean, default: true }, treeMate: { type: Object, required: true }, multiple: Boolean, size: { type: String, default: 'medium' }, value: { type: [String, Number, Array], default: null }, autoPending: Boolean, virtualScroll: { type: Boolean, default: true }, // show is used to toggle pending state initialization show: { type: Boolean, default: true }, labelField: { type: String, default: 'label' }, valueField: { type: String, default: 'value' }, loading: Boolean, focusable: Boolean, renderLabel: Function, renderOption: Function, nodeProps: Function, showCheckmark: { type: Boolean, default: true }, onMousedown: Function, onScroll: Function, onFocus: Function, onBlur: Function, onKeyup: Function, onKeydown: Function, onTabOut: Function, onMouseenter: Function, onMouseleave: Function, onResize: Function, resetMenuOnOptionsChange: { type: Boolean, default: true }, inlineThemeDisabled: Boolean, // deprecated onToggle: Function }), setup(props) { const { mergedClsPrefixRef, mergedRtlRef } = useConfig(props); const rtlEnabledRef = useRtl('InternalSelectMenu', mergedRtlRef, mergedClsPrefixRef); const themeRef = useTheme('InternalSelectMenu', '-internal-select-menu', style, internalSelectMenuLight, props, toRef(props, 'clsPrefix')); const selfRef = ref(null); const virtualListRef = ref(null); const scrollbarRef = ref(null); const flattenedNodesRef = computed(() => props.treeMate.getFlattenedNodes()); const fIndexGetterRef = computed(() => createIndexGetter(flattenedNodesRef.value)); const pendingNodeRef = ref(null); function initPendingNode() { const { treeMate } = props; let defaultPendingNode = null; const { value } = props; if (value === null) { defaultPendingNode = treeMate.getFirstAvailableNode(); } else { if (props.multiple) { defaultPendingNode = treeMate.getNode((value || [])[(value || []).length - 1]); } else { defaultPendingNode = treeMate.getNode(value); } if (!defaultPendingNode || defaultPendingNode.disabled) { defaultPendingNode = treeMate.getFirstAvailableNode(); } } if (defaultPendingNode) { setPendingTmNode(defaultPendingNode); } else { setPendingTmNode(null); } } function clearPendingNodeIfInvalid() { const { value: pendingNode } = pendingNodeRef; if (pendingNode && !props.treeMate.getNode(pendingNode.key)) { pendingNodeRef.value = null; } } let initPendingNodeWatchStopHandle; watch(() => props.show, show => { if (show) { initPendingNodeWatchStopHandle = watch(() => props.treeMate, () => { if (props.resetMenuOnOptionsChange) { if (props.autoPending) { initPendingNode(); } else { clearPendingNodeIfInvalid(); } void nextTick(scrollToPendingNode); } else { clearPendingNodeIfInvalid(); } }, { immediate: true }); } else { initPendingNodeWatchStopHandle === null || initPendingNodeWatchStopHandle === void 0 ? void 0 : initPendingNodeWatchStopHandle(); } }, { immediate: true }); onBeforeUnmount(() => { initPendingNodeWatchStopHandle === null || initPendingNodeWatchStopHandle === void 0 ? void 0 : initPendingNodeWatchStopHandle(); }); const itemSizeRef = computed(() => { return depx(themeRef.value.self[createKey('optionHeight', props.size)]); }); const paddingRef = computed(() => { return getPadding(themeRef.value.self[createKey('padding', props.size)]); }); const valueSetRef = computed(() => { if (props.multiple && Array.isArray(props.value)) { return new Set(props.value); } return new Set(); }); const emptyRef = computed(() => { const tmNodes = flattenedNodesRef.value; return tmNodes && tmNodes.length === 0; }); function doToggle(tmNode) { const { onToggle } = props; if (onToggle) onToggle(tmNode); } function doScroll(e) { const { onScroll } = props; if (onScroll) onScroll(e); } // required, scroller sync need to be triggered manually function handleVirtualListScroll(e) { var _a; (_a = scrollbarRef.value) === null || _a === void 0 ? void 0 : _a.sync(); doScroll(e); } function handleVirtualListResize() { var _a; (_a = scrollbarRef.value) === null || _a === void 0 ? void 0 : _a.sync(); } function getPendingTmNode() { const { value: pendingTmNode } = pendingNodeRef; if (pendingTmNode) return pendingTmNode; return null; } function handleOptionMouseEnter(e, tmNode) { if (tmNode.disabled) return; setPendingTmNode(tmNode, false); } function handleOptionClick(e, tmNode) { if (tmNode.disabled) return; doToggle(tmNode); } // keyboard related methods function handleKeyUp(e) { var _a; if (happensIn(e, 'action')) return; (_a = props.onKeyup) === null || _a === void 0 ? void 0 : _a.call(props, e); } function handleKeyDown(e) { var _a; if (happensIn(e, 'action')) return; (_a = props.onKeydown) === null || _a === void 0 ? void 0 : _a.call(props, e); } function handleMouseDown(e) { var _a; (_a = props.onMousedown) === null || _a === void 0 ? void 0 : _a.call(props, e); if (props.focusable) return; e.preventDefault(); } function next() { const { value: pendingTmNode } = pendingNodeRef; if (pendingTmNode) { setPendingTmNode(pendingTmNode.getNext({ loop: true }), true); } } function prev() { const { value: pendingTmNode } = pendingNodeRef; if (pendingTmNode) { setPendingTmNode(pendingTmNode.getPrev({ loop: true }), true); } } function setPendingTmNode(tmNode, doScroll = false) { pendingNodeRef.value = tmNode; if (doScroll) scrollToPendingNode(); } function scrollToPendingNode() { var _a, _b; const tmNode = pendingNodeRef.value; if (!tmNode) return; const fIndex = fIndexGetterRef.value(tmNode.key); if (fIndex === null) return; if (props.virtualScroll) { (_a = virtualListRef.value) === null || _a === void 0 ? void 0 : _a.scrollTo({ index: fIndex }); } else { (_b = scrollbarRef.value) === null || _b === void 0 ? void 0 : _b.scrollTo({ index: fIndex, elSize: itemSizeRef.value }); } } function handleFocusin(e) { var _a, _b; if ((_a = selfRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.target)) { (_b = props.onFocus) === null || _b === void 0 ? void 0 : _b.call(props, e); } } function handleFocusout(e) { var _a, _b; if (!((_a = selfRef.value) === null || _a === void 0 ? void 0 : _a.contains(e.relatedTarget))) { (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e); } } provide(internalSelectionMenuInjectionKey, { handleOptionMouseEnter, handleOptionClick, valueSetRef, pendingTmNodeRef: pendingNodeRef, nodePropsRef: toRef(props, 'nodeProps'), showCheckmarkRef: toRef(props, 'showCheckmark'), multipleRef: toRef(props, 'multiple'), valueRef: toRef(props, 'value'), renderLabelRef: toRef(props, 'renderLabel'), renderOptionRef: toRef(props, 'renderOption'), labelFieldRef: toRef(props, 'labelField'), valueFieldRef: toRef(props, 'valueField') }); provide(internalSelectionMenuBodyInjectionKey, selfRef); onMounted(() => { const { value } = scrollbarRef; if (value) value.sync(); }); const cssVarsRef = computed(() => { const { size } = props; const { common: { cubicBezierEaseInOut }, self: { height, borderRadius, color, groupHeaderTextColor, actionDividerColor, optionTextColorPressed, optionTextColor, optionTextColorDisabled, optionTextColorActive, optionOpacityDisabled, optionCheckColor, actionTextColor, optionColorPending, optionColorActive, loadingColor, loadingSize, optionColorActivePending, [createKey('optionFontSize', size)]: fontSize, [createKey('optionHeight', size)]: optionHeight, [createKey('optionPadding', size)]: optionPadding } } = themeRef.value; return { '--n-height': height, '--n-action-divider-color': actionDividerColor, '--n-action-text-color': actionTextColor, '--n-bezier': cubicBezierEaseInOut, '--n-border-radius': borderRadius, '--n-color': color, '--n-option-font-size': fontSize, '--n-group-header-text-color': groupHeaderTextColor, '--n-option-check-color': optionCheckColor, '--n-option-color-pending': optionColorPending, '--n-option-color-active': optionColorActive, '--n-option-color-active-pending': optionColorActivePending, '--n-option-height': optionHeight, '--n-option-opacity-disabled': optionOpacityDisabled, '--n-option-text-color': optionTextColor, '--n-option-text-color-active': optionTextColorActive, '--n-option-text-color-disabled': optionTextColorDisabled, '--n-option-text-color-pressed': optionTextColorPressed, '--n-option-padding': optionPadding, '--n-option-padding-left': getPadding(optionPadding, 'left'), '--n-option-padding-right': getPadding(optionPadding, 'right'), '--n-loading-color': loadingColor, '--n-loading-size': loadingSize }; }); const { inlineThemeDisabled } = props; const themeClassHandle = inlineThemeDisabled ? useThemeClass('internal-select-menu', computed(() => props.size[0]), cssVarsRef, props) : undefined; const exposedProps = { selfRef, next, prev, getPendingTmNode }; useOnResize(selfRef, props.onResize); return Object.assign({ mergedTheme: themeRef, mergedClsPrefix: mergedClsPrefixRef, rtlEnabled: rtlEnabledRef, virtualListRef, scrollbarRef, itemSize: itemSizeRef, padding: paddingRef, flattenedNodes: flattenedNodesRef, empty: emptyRef, virtualListContainer() { const { value } = virtualListRef; return value === null || value === void 0 ? void 0 : value.listElRef; }, virtualListContent() { const { value } = virtualListRef; return value === null || value === void 0 ? void 0 : value.itemsElRef; }, doScroll, handleFocusin, handleFocusout, handleKeyUp, handleKeyDown, handleMouseDown, handleVirtualListResize, handleVirtualListScroll, cssVars: inlineThemeDisabled ? undefined : cssVarsRef, themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass, onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender }, exposedProps); }, render() { const { $slots, virtualScroll, clsPrefix, mergedTheme, themeClass, onRender } = this; onRender === null || onRender === void 0 ? void 0 : onRender(); return h("div", { ref: "selfRef", tabindex: this.focusable ? 0 : -1, class: [`${clsPrefix}-base-select-menu`, this.rtlEnabled && `${clsPrefix}-base-select-menu--rtl`, themeClass, this.multiple && `${clsPrefix}-base-select-menu--multiple`], style: this.cssVars, onFocusin: this.handleFocusin, onFocusout: this.handleFocusout, onKeyup: this.handleKeyUp, onKeydown: this.handleKeyDown, onMousedown: this.handleMouseDown, onMouseenter: this.onMouseenter, onMouseleave: this.onMouseleave }, resolveWrappedSlot($slots.header, children => children && h("div", { class: `${clsPrefix}-base-select-menu__header`, "data-header": true, key: "header" }, children)), this.loading ? h("div", { class: `${clsPrefix}-base-select-menu__loading` }, h(NInternalLoading, { clsPrefix: clsPrefix, strokeWidth: 20 })) : !this.empty ? h(NScrollbar, { ref: "scrollbarRef", theme: mergedTheme.peers.Scrollbar, themeOverrides: mergedTheme.peerOverrides.Scrollbar, scrollable: this.scrollable, container: virtualScroll ? this.virtualListContainer : undefined, content: virtualScroll ? this.virtualListContent : undefined, onScroll: virtualScroll ? undefined : this.doScroll }, { default: () => { return virtualScroll ? h(VirtualList, { ref: "virtualListRef", class: `${clsPrefix}-virtual-list`, items: this.flattenedNodes, itemSize: this.itemSize, showScrollbar: false, paddingTop: this.padding.top, paddingBottom: this.padding.bottom, onResize: this.handleVirtualListResize, onScroll: this.handleVirtualListScroll, itemResizable: true }, { default: ({ item: tmNode }) => { return tmNode.isGroup ? h(NSelectGroupHeader, { key: tmNode.key, clsPrefix: clsPrefix, tmNode: tmNode }) : tmNode.ignored ? null : h(NSelectOption, { clsPrefix: clsPrefix, key: tmNode.key, tmNode: tmNode }); } }) : h("div", { class: `${clsPrefix}-base-select-menu-option-wrapper`, style: { paddingTop: this.padding.top, paddingBottom: this.padding.bottom } }, this.flattenedNodes.map(tmNode => tmNode.isGroup ? h(NSelectGroupHeader, { key: tmNode.key, clsPrefix: clsPrefix, tmNode: tmNode }) : h(NSelectOption, { clsPrefix: clsPrefix, key: tmNode.key, tmNode: tmNode }))); } }) : h("div", { class: `${clsPrefix}-base-select-menu__empty`, "data-empty": true }, resolveSlot($slots.empty, () => [h(NEmpty, { theme: mergedTheme.peers.Empty, themeOverrides: mergedTheme.peerOverrides.Empty, size: this.size })])), resolveWrappedSlot($slots.action, children => children && [h("div", { class: `${clsPrefix}-base-select-menu__action`, "data-action": true, key: "action" }, children), h(NFocusDetector, { onFocus: this.onTabOut, key: "focus-detector" })])); } });