mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-01-12 12:25:16 +08:00
refactor(select): ts
This commit is contained in:
parent
5faf1a8870
commit
e608c393df
@ -5,9 +5,11 @@ export { default as NBaseIcon } from './icon'
|
||||
export { default as NBaseFocusDetector } from './focus-detector'
|
||||
export { default as NBaseLoading } from './loading'
|
||||
export { default as NBaseSelectMenu } from './select-menu'
|
||||
export type { BaseSelectMenuRef } from './select-menu'
|
||||
export { default as NBaseWave } from './wave'
|
||||
export type { BaseWaveRef } from './wave'
|
||||
export { default as NBaseMenuMask } from './menu-mask'
|
||||
export { default as NBaseSelection } from './selection'
|
||||
export type { BaseSelectionRef } from './selection'
|
||||
export { default as NBaseSlotMachine } from './slot-machine'
|
||||
export { default as NBaseClear } from './clear'
|
||||
|
@ -1,2 +1,3 @@
|
||||
/* istanbul ignore file */
|
||||
export { default } from './src/SelectMenu'
|
||||
export type { BaseSelectMenuRef } from './src/SelectMenu'
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { h, defineComponent, PropType } from 'vue'
|
||||
import { TreeNode } from 'treemate'
|
||||
import type { GroupOption } from '../../../select'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NBaseSelectGroupHeader',
|
||||
props: {
|
||||
tmNode: {
|
||||
type: Object as PropType<TreeNode>,
|
||||
type: Object as PropType<TreeNode<GroupOption>>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
@ -12,12 +12,13 @@ import {
|
||||
provide,
|
||||
reactive
|
||||
} from 'vue'
|
||||
import { RawNode, TreeMate, TreeNode } from 'treemate'
|
||||
import { TreeMate, TreeNode } from 'treemate'
|
||||
import { VirtualList, VirtualListRef } from 'vueuc'
|
||||
import { depx, getPadding } from 'seemly'
|
||||
import { NEmpty } from '../../../empty'
|
||||
import { NScrollbar } from '../../../scrollbar'
|
||||
import type { ScrollbarRef } from '../../../scrollbar'
|
||||
import type { BaseOption, GroupOption, IgnoredOption } from '../../../select'
|
||||
import { formatLength } from '../../../_utils'
|
||||
import { createKey } from '../../../_utils/cssr'
|
||||
import { useTheme } from '../../../_mixins'
|
||||
@ -28,16 +29,18 @@ import style from './styles/index.cssr'
|
||||
import { baseSelectMenuLight, BaseSelectMenuTheme } from '../styles'
|
||||
|
||||
export interface BaseSelectMenuInjection {
|
||||
handleOptionMouseEnter: (e: MouseEvent, tmNode: TreeNode) => void
|
||||
handleOptionClick: (e: MouseEvent, tmNode: TreeNode) => void
|
||||
handleOptionMouseEnter: (e: MouseEvent, tmNode: TreeNode<BaseOption>) => void
|
||||
handleOptionClick: (e: MouseEvent, tmNode: TreeNode<BaseOption>) => void
|
||||
valueSet: Set<number | string>
|
||||
pendingTmNode: TreeNode | null
|
||||
pendingTmNode: TreeNode<BaseOption> | null
|
||||
multiple: boolean
|
||||
value: string | number | Array<string | number> | null
|
||||
}
|
||||
|
||||
export interface BaseSelectMenuRef {
|
||||
getPendingOption: () => TreeNode | null
|
||||
getPendingOption: () => BaseOption | null
|
||||
prev: () => void
|
||||
next: () => void
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@ -49,7 +52,9 @@ export default defineComponent({
|
||||
default: true
|
||||
},
|
||||
treeMate: {
|
||||
type: Object as PropType<TreeMate>,
|
||||
type: Object as PropType<
|
||||
TreeMate<BaseOption, GroupOption, IgnoredOption>
|
||||
>,
|
||||
required: true
|
||||
},
|
||||
multiple: {
|
||||
@ -137,8 +142,8 @@ export default defineComponent({
|
||||
const styleRef = computed(() => {
|
||||
return [{ width: formatLength(props.width) }, cssVarsRef.value]
|
||||
})
|
||||
const tmNodesRef = computed(() => {
|
||||
return props.treeMate.treeNodes
|
||||
const flattenedNodesRef = computed(() => {
|
||||
return props.treeMate.flattenedNodes
|
||||
})
|
||||
watch(toRef(props, 'treeMate'), () => {
|
||||
if (props.autoPending) {
|
||||
@ -148,7 +153,7 @@ export default defineComponent({
|
||||
setPendingTmNode(null)
|
||||
}
|
||||
})
|
||||
function doToggleOption (option: RawNode): void {
|
||||
function doToggleOption (option: BaseOption): void {
|
||||
const { onMenuToggleOption } = props
|
||||
if (onMenuToggleOption) onMenuToggleOption(option)
|
||||
}
|
||||
@ -164,16 +169,22 @@ export default defineComponent({
|
||||
function handleVirtualListResize (): void {
|
||||
scrollbarRef.value?.sync()
|
||||
}
|
||||
function getPendingOption (): RawNode | null {
|
||||
function getPendingOption (): BaseOption | null {
|
||||
const { value: pendingTmNode } = pendingNodeRef
|
||||
if (pendingTmNode) return pendingTmNode.rawNode
|
||||
return null
|
||||
}
|
||||
function handleOptionMouseEnter (e: MouseEvent, tmNode: TreeNode): void {
|
||||
function handleOptionMouseEnter (
|
||||
e: MouseEvent,
|
||||
tmNode: TreeNode<BaseOption>
|
||||
): void {
|
||||
if (tmNode.disabled) return
|
||||
setPendingTmNode(tmNode, false)
|
||||
}
|
||||
function handleOptionClick (e: MouseEvent, tmNode: TreeNode): void {
|
||||
function handleOptionClick (
|
||||
e: MouseEvent,
|
||||
tmNode: TreeNode<BaseOption>
|
||||
): void {
|
||||
if (tmNode.disabled) return
|
||||
doToggleOption(tmNode.rawNode)
|
||||
}
|
||||
@ -203,7 +214,10 @@ export default defineComponent({
|
||||
setPendingTmNode(pendingTmNode.getPrev({ loop: true }), true)
|
||||
}
|
||||
}
|
||||
function setPendingTmNode (tmNode: TreeNode | null, doScroll = false): void {
|
||||
function setPendingTmNode (
|
||||
tmNode: TreeNode<BaseOption> | null,
|
||||
doScroll = false
|
||||
): void {
|
||||
pendingNodeRef.value = tmNode
|
||||
if (doScroll && tmNode) {
|
||||
if (props.virtualScroll) {
|
||||
@ -279,7 +293,7 @@ export default defineComponent({
|
||||
defaultScrollIndex: pendingNodeRef.value?.fIndex,
|
||||
itemSize: itemSizeRef,
|
||||
padding: paddingRef,
|
||||
tmNodes: tmNodesRef,
|
||||
flattenedNodes: flattenedNodesRef,
|
||||
empty: emptyRef,
|
||||
next,
|
||||
prev,
|
||||
@ -327,7 +341,7 @@ export default defineComponent({
|
||||
<VirtualList
|
||||
ref="virtualListRef"
|
||||
class="n-virtual-list"
|
||||
items={this.tmNodes}
|
||||
items={this.flattenedNodes}
|
||||
itemSize={this.itemSize}
|
||||
showScrollbar={false}
|
||||
defaultScrollIndex={this.defaultScrollIndex}
|
||||
@ -337,14 +351,23 @@ export default defineComponent({
|
||||
onScroll={this.handleVirtualListScroll}
|
||||
>
|
||||
{{
|
||||
default: ({ item: tmNode }: { item: TreeNode }) => {
|
||||
return tmNode.rawNode.type === 'group' ? (
|
||||
default: ({
|
||||
item: tmNode
|
||||
}: {
|
||||
item: TreeNode<GroupOption | BaseOption | IgnoredOption>
|
||||
}) => {
|
||||
return tmNode.isGroup ? (
|
||||
<NSelectGroupHeader
|
||||
key={tmNode.key}
|
||||
tmNode={tmNode}
|
||||
tmNode={
|
||||
(tmNode as unknown) as TreeNode<GroupOption>
|
||||
}
|
||||
/>
|
||||
) : tmNode.ignored ? null : (
|
||||
<NSelectOption
|
||||
key={tmNode.key}
|
||||
tmNode={(tmNode as unknown) as TreeNode<BaseOption>}
|
||||
/>
|
||||
) : (
|
||||
<NSelectOption key={tmNode.key} tmNode={tmNode} />
|
||||
)
|
||||
}
|
||||
}}
|
||||
@ -357,11 +380,17 @@ export default defineComponent({
|
||||
paddingBottom: this.padding.bottom
|
||||
}}
|
||||
>
|
||||
{this.tmNodes.map((tmNode) =>
|
||||
{this.flattenedNodes.map((tmNode) =>
|
||||
tmNode.rawNode.type === 'group' ? (
|
||||
<NSelectGroupHeader key={tmNode.key} tmNode={tmNode} />
|
||||
<NSelectGroupHeader
|
||||
key={tmNode.key}
|
||||
tmNode={(tmNode as unknown) as TreeNode<GroupOption>}
|
||||
/>
|
||||
) : (
|
||||
<NSelectOption key={tmNode.key} tmNode={tmNode} />
|
||||
<NSelectOption
|
||||
key={tmNode.key}
|
||||
tmNode={(tmNode as unknown) as TreeNode<BaseOption>}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import { BaseSelectMenuInjection } from './SelectMenu'
|
||||
import { TreeNode } from 'treemate'
|
||||
import { useMemo } from 'vooks'
|
||||
import type { BaseOption } from '../../../select'
|
||||
import { CheckmarkIcon } from '../../icons'
|
||||
import NBaseIcon from '../../icon'
|
||||
|
||||
@ -34,7 +35,7 @@ export default defineComponent({
|
||||
name: 'NBaseSelectOption',
|
||||
props: {
|
||||
tmNode: {
|
||||
type: Object as PropType<TreeNode>,
|
||||
type: Object as PropType<TreeNode<BaseOption>>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
@ -1,2 +1,3 @@
|
||||
/* istanbul ignore file */
|
||||
export { default } from './src/Selection'
|
||||
export type { BaseSelectionRef } from './src/Selection'
|
||||
|
@ -19,10 +19,12 @@ import { baseSelectionLight } from '../styles'
|
||||
import type { BaseSelectionTheme } from '../styles'
|
||||
import Suffix from './Suffix'
|
||||
import style from './styles/index.cssr'
|
||||
import type { BaseOption } from '../../../select'
|
||||
|
||||
export interface SelectOption {
|
||||
value: string | number
|
||||
label: string
|
||||
export interface BaseSelectionRef {
|
||||
focusPatternInputWrapper: () => void
|
||||
focusPatternInput: () => void
|
||||
$el: HTMLElement
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@ -46,11 +48,11 @@ export default defineComponent({
|
||||
default: undefined
|
||||
},
|
||||
selectedOption: {
|
||||
type: Object as PropType<SelectOption | null>,
|
||||
type: Object as PropType<BaseOption | null>,
|
||||
default: null
|
||||
},
|
||||
selectedOptions: {
|
||||
type: Array as PropType<SelectOption[] | null>,
|
||||
type: Array as PropType<BaseOption[] | null>,
|
||||
default: null
|
||||
},
|
||||
multiple: {
|
||||
@ -184,7 +186,7 @@ export default defineComponent({
|
||||
const { onBlur } = props
|
||||
if (onBlur) onBlur(e)
|
||||
}
|
||||
function doDeleteOption (value: SelectOption): void {
|
||||
function doDeleteOption (value: BaseOption): void {
|
||||
const { onDeleteOption } = props
|
||||
if (onDeleteOption) onDeleteOption(value)
|
||||
}
|
||||
@ -250,7 +252,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleDeleteOption (option: SelectOption): void {
|
||||
function handleDeleteOption (option: BaseOption): void {
|
||||
doDeleteOption(option)
|
||||
}
|
||||
function handlePatternKeyDown (e: KeyboardEvent): void {
|
||||
@ -565,9 +567,11 @@ export default defineComponent({
|
||||
onBlur={this.handleBlur}
|
||||
>
|
||||
{this.label?.length ? (
|
||||
<div class="n-base-selection-label__input">{this.label}</div>
|
||||
<div class="n-base-selection-label__input" key="input">
|
||||
{this.label}
|
||||
</div>
|
||||
) : (
|
||||
<div class="n-base-selection-placeholder">
|
||||
<div class="n-base-selection-placeholder" key="placeholder">
|
||||
{this.placeholder}
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export { default as useFormItem } from './use-form-item'
|
||||
export { default as useTheme } from './use-theme'
|
||||
export { default as useTheme, createTheme } from './use-theme'
|
||||
export type {
|
||||
ThemeProps,
|
||||
MergedTheme,
|
||||
|
@ -19,6 +19,10 @@ export interface Theme<T = undefined, R = any> {
|
||||
self?: (vars: ThemeCommonVars) => T
|
||||
}
|
||||
|
||||
export function createTheme<T, R> (theme: Theme<T, R>): Theme<T, R> {
|
||||
return theme
|
||||
}
|
||||
|
||||
type UseThemeProps<T> = Readonly<{
|
||||
unstableTheme: Theme<T>
|
||||
unstableThemeOverrides: ThemeOverrides
|
||||
|
@ -140,12 +140,12 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
const handleKeyUp = (e: KeyboardEvent): void => {
|
||||
if (!props.keyboard) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
switch (e.code) {
|
||||
case 'Enter':
|
||||
if (!props.keyboard) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
enterPressedRef.value = false
|
||||
void nextTick(() => {
|
||||
if (!props.disabled) {
|
||||
@ -155,10 +155,10 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
const handleKeyDown = (e: KeyboardEvent): void => {
|
||||
e.preventDefault()
|
||||
if (!props.keyboard) return
|
||||
switch (e.code) {
|
||||
case 'Enter':
|
||||
if (!props.keyboard) return
|
||||
e.preventDefault()
|
||||
enterPressedRef.value = true
|
||||
}
|
||||
}
|
||||
|
@ -22,40 +22,28 @@ import style from './styles/index.cssr'
|
||||
|
||||
interface ScrollTo {
|
||||
(x: number, y: number): void
|
||||
(
|
||||
x: {
|
||||
left?: number
|
||||
top?: number
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
},
|
||||
y: number
|
||||
): void
|
||||
(
|
||||
x: {
|
||||
el: HTMLElement
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
},
|
||||
y: number
|
||||
): void
|
||||
(
|
||||
x: {
|
||||
index: number
|
||||
elSize: number
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
},
|
||||
y: number
|
||||
): void
|
||||
(
|
||||
x: {
|
||||
position: 'top' | 'bottom'
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
},
|
||||
y: number
|
||||
): void
|
||||
(options: {
|
||||
left?: number
|
||||
top?: number
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
}): void
|
||||
(options: {
|
||||
el: HTMLElement
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
}): void
|
||||
(options: {
|
||||
index: number
|
||||
elSize: number
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
}): void
|
||||
(options: {
|
||||
position: 'top' | 'bottom'
|
||||
behavior?: ScrollBehavior
|
||||
debounce?: boolean
|
||||
}): void
|
||||
}
|
||||
|
||||
export interface ScrollbarRef {
|
||||
|
@ -1,2 +0,0 @@
|
||||
/* istanbul ignore file */
|
||||
export { default as NSelect } from './src/Select.vue'
|
3
src/select/index.ts
Normal file
3
src/select/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
/* istanbul ignore file */
|
||||
export { default as NSelect } from './src/Select'
|
||||
export * from './src/interface'
|
731
src/select/src/Select.tsx
Normal file
731
src/select/src/Select.tsx
Normal file
@ -0,0 +1,731 @@
|
||||
import {
|
||||
h,
|
||||
ref,
|
||||
computed,
|
||||
toRef,
|
||||
defineComponent,
|
||||
PropType,
|
||||
nextTick,
|
||||
ComputedRef,
|
||||
watch,
|
||||
Transition,
|
||||
withDirectives
|
||||
} from 'vue'
|
||||
import { createTreeMate } from 'treemate'
|
||||
import { VBinder, VFollower, VTarget, FollowerRef } from 'vueuc'
|
||||
import { useIsMounted, useMergedState, useCompitable } from 'vooks'
|
||||
import { clickoutside } from 'vdirs'
|
||||
import { useTheme, useConfig, useLocale, useFormItem } from '../../_mixins'
|
||||
import type { ThemeProps } from '../../_mixins'
|
||||
import { warn, call, useAdjustedTo, MaybeArray } from '../../_utils'
|
||||
import { NBaseSelectMenu, NBaseSelection, BaseSelectMenuRef } from '../../_base'
|
||||
import type { BaseSelectionRef } from '../../_base'
|
||||
import { selectLight, SelectTheme } from '../styles'
|
||||
import {
|
||||
tmOptions,
|
||||
patternMatched,
|
||||
createValOptMap,
|
||||
filterOptions
|
||||
} from './utils'
|
||||
import style from './styles/index.cssr'
|
||||
|
||||
import type {
|
||||
Options,
|
||||
Option,
|
||||
BaseOption,
|
||||
GroupOption,
|
||||
IgnoredOption
|
||||
} from './interface'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Select',
|
||||
props: {
|
||||
...(useTheme.props as ThemeProps<SelectTheme>),
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Options>,
|
||||
required: true
|
||||
},
|
||||
defaultValue: {
|
||||
type: [String, Number, Array] as PropType<
|
||||
string | number | Array<string | number> | null
|
||||
>,
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array] as PropType<
|
||||
string | number | Array<string | number> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String as PropType<'small' | 'medium' | 'large'>,
|
||||
default: undefined
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filter: {
|
||||
type: Function as PropType<(pattern: string, option: Option) => boolean>,
|
||||
default: (pattern: string, option: Option) => {
|
||||
if (!option) return false
|
||||
if (option.label !== undefined) {
|
||||
return patternMatched(pattern, option.label)
|
||||
} else if (option.value !== undefined) {
|
||||
return patternMatched(pattern, String(option.value))
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-start'
|
||||
},
|
||||
widthMode: {
|
||||
type: String,
|
||||
default: 'trigger'
|
||||
},
|
||||
tag: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onCreate: {
|
||||
type: Function as PropType<(label: string) => BaseOption>,
|
||||
default: (label: string) => ({
|
||||
label: label,
|
||||
value: label
|
||||
})
|
||||
},
|
||||
fallbackOption: {
|
||||
type: [Function, Boolean] as PropType<
|
||||
(value: string | number) => BaseOption | false
|
||||
>,
|
||||
default: () => (value: string | number) => ({
|
||||
label: String(value),
|
||||
value
|
||||
})
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
'onUpdate:value': {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(value: string | number | null) => void> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
onBlur: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(e: FocusEvent) => void> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
onFocus: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(e: FocusEvent) => void> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
onScroll: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(e: Event) => void> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
onSearch: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(value: string) => void> | undefined
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
/** deprecated */
|
||||
onChange: {
|
||||
type: [Function, Array] as PropType<
|
||||
MaybeArray<(value: string | number | null) => void> | undefined
|
||||
>,
|
||||
validator: () => {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
'select',
|
||||
'`on-change` is deprecated, please use `on-update:value` instead.'
|
||||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<Options | undefined>,
|
||||
validator: () => {
|
||||
if (__DEV__) {
|
||||
warn('select', '`items` is deprecated, please use `options` instead.')
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const themeRef = useTheme('Select', 'Select', style, selectLight, props)
|
||||
const uncontrolledValueRef = ref(props.defaultValue)
|
||||
const controlledValueRef = toRef(props, 'value')
|
||||
const mergedValueRef = useMergedState(
|
||||
controlledValueRef,
|
||||
uncontrolledValueRef
|
||||
)
|
||||
const patternRef = ref('')
|
||||
const treeMateRef = computed(() =>
|
||||
createTreeMate<BaseOption, GroupOption, IgnoredOption>(
|
||||
filteredOptionsRef.value,
|
||||
tmOptions
|
||||
)
|
||||
)
|
||||
const valOptMapRef = computed(() => createValOptMap(props.options))
|
||||
const uncontrolledShowRef = ref(false)
|
||||
const mergedShowRef = useMergedState(
|
||||
toRef(props, 'show'),
|
||||
uncontrolledShowRef
|
||||
)
|
||||
const triggerRef = ref<BaseSelectionRef | null>(null)
|
||||
const followerRef = ref<FollowerRef | null>(null)
|
||||
const menuRef = ref<BaseSelectMenuRef | null>(null)
|
||||
const { locale } = useLocale('Select')
|
||||
const localizedPlaceholderRef = computed<string>(() => {
|
||||
return props.placeholder ?? locale.value.placeholder
|
||||
})
|
||||
const compitableOptionsRef = useCompitable(props, [
|
||||
'items',
|
||||
'options'
|
||||
]) as ComputedRef<Options>
|
||||
|
||||
const createdOptionsRef = ref<BaseOption[]>([])
|
||||
const beingCreatedOptionsRef = ref<BaseOption[]>([])
|
||||
const memoValOptMapRef = ref(new Map<string | number, BaseOption>())
|
||||
|
||||
const wrappedFallbackOptionRef = computed(() => {
|
||||
const { fallbackOption } = props
|
||||
if (!fallbackOption) return false
|
||||
return (value: string | number) => {
|
||||
return Object.assign(fallbackOption(value), { value }) as BaseOption
|
||||
}
|
||||
})
|
||||
const localOptionsRef = computed<Options>(() => {
|
||||
return (beingCreatedOptionsRef.value.concat(
|
||||
createdOptionsRef.value
|
||||
) as Options).concat(compitableOptionsRef.value)
|
||||
})
|
||||
const filteredOptionsRef = computed(() => {
|
||||
if (props.remote) {
|
||||
return compitableOptionsRef.value
|
||||
} else {
|
||||
const { value: localOptions } = localOptionsRef
|
||||
const { value: pattern } = patternRef
|
||||
if (!pattern.length || !props.filterable) {
|
||||
return localOptions
|
||||
} else {
|
||||
const { filter } = props
|
||||
return filterOptions(localOptions, filter, pattern)
|
||||
}
|
||||
}
|
||||
})
|
||||
const selectedOptionsRef = computed(() => {
|
||||
if (props.multiple) {
|
||||
const { value: values } = mergedValueRef
|
||||
if (!Array.isArray(values)) return []
|
||||
const remote = props.remote
|
||||
const { value: memoValOptMap } = memoValOptMapRef
|
||||
const { value: valOptMap } = valOptMapRef
|
||||
const { value: wrappedFallbackOption } = wrappedFallbackOptionRef
|
||||
const options: BaseOption[] = []
|
||||
values.forEach((value) => {
|
||||
if (valOptMap.has(value)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
options.push(valOptMap.get(value)!)
|
||||
} else if (remote && memoValOptMap.has(value)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
options.push(memoValOptMap.get(value)!)
|
||||
} else if (wrappedFallbackOption) {
|
||||
const option = wrappedFallbackOption(value)
|
||||
if (option) {
|
||||
options.push(option)
|
||||
}
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
return null
|
||||
})
|
||||
const selectedOptionRef = computed<BaseOption | null>(() => {
|
||||
const { value: mergedValue } = mergedValueRef
|
||||
if (!props.multiple && !Array.isArray(mergedValue)) {
|
||||
const { value: valOptMap } = valOptMapRef
|
||||
const { value: wrappedFallbackOption } = wrappedFallbackOptionRef
|
||||
if (mergedValue === null) return null
|
||||
let selectedOption = null
|
||||
if (valOptMap.has(mergedValue as any)) {
|
||||
selectedOption = valOptMap.get(mergedValue)
|
||||
} else if (props.remote) {
|
||||
selectedOption = memoValOptMapRef.value.get(mergedValue)
|
||||
}
|
||||
return (
|
||||
selectedOption ||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
(wrappedFallbackOption && wrappedFallbackOption(mergedValue)) ||
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const formItem = useFormItem(props)
|
||||
function doUpdateValue (
|
||||
value: string | number | Array<string | number> | null
|
||||
): void {
|
||||
const { onChange, 'onUpdate:value': onUpdateValue } = props
|
||||
const { nTriggerFormChange, nTriggerFormInput } = formItem
|
||||
if (onChange) call(onChange, value)
|
||||
if (onUpdateValue) call(onUpdateValue, value)
|
||||
uncontrolledValueRef.value = value
|
||||
nTriggerFormChange()
|
||||
nTriggerFormInput()
|
||||
}
|
||||
function doBlur (e: FocusEvent): void {
|
||||
const { onBlur } = props
|
||||
const { nTriggerFormBlur } = formItem
|
||||
if (onBlur) call(onBlur, e)
|
||||
nTriggerFormBlur()
|
||||
}
|
||||
function doFocus (e: FocusEvent): void {
|
||||
const { onFocus } = props
|
||||
const { nTriggerFormFocus } = formItem
|
||||
if (onFocus) call(onFocus, e)
|
||||
nTriggerFormFocus()
|
||||
}
|
||||
function doSearch (value: string): void {
|
||||
const { onSearch } = props
|
||||
if (onSearch) call(onSearch, value)
|
||||
}
|
||||
function doScroll (e: Event): void {
|
||||
const { onScroll } = props
|
||||
if (onScroll) call(onScroll, e)
|
||||
}
|
||||
// remote related methods
|
||||
function updateMemorizedOptions (): void {
|
||||
const { remote, multiple } = props
|
||||
if (remote) {
|
||||
const { value: memoValOptMap } = memoValOptMapRef
|
||||
if (multiple) {
|
||||
selectedOptionsRef.value?.forEach((option) => {
|
||||
memoValOptMap.set(option.value, option)
|
||||
})
|
||||
} else {
|
||||
const option = selectedOptionRef.value
|
||||
if (option) {
|
||||
memoValOptMap.set(option.value, option)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// menu related methods
|
||||
function openMenu (): void {
|
||||
if (!props.disabled) {
|
||||
patternRef.value = ''
|
||||
uncontrolledShowRef.value = true
|
||||
if (props.filterable) {
|
||||
triggerRef.value?.focusPatternInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
function closeMenu (): void {
|
||||
uncontrolledShowRef.value = false
|
||||
}
|
||||
function handleMenuLeave (): void {
|
||||
patternRef.value = ''
|
||||
}
|
||||
function handleTriggerClick (): void {
|
||||
if (props.disabled) return
|
||||
if (!mergedShowRef.value) {
|
||||
openMenu()
|
||||
} else {
|
||||
if (!props.filterable) {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleTriggerBlur (e: FocusEvent): void {
|
||||
doBlur(e)
|
||||
closeMenu()
|
||||
}
|
||||
function handleTriggerFocus (e: FocusEvent): void {
|
||||
doFocus(e)
|
||||
}
|
||||
function handleMenuClickOutside (e: MouseEvent): void {
|
||||
if (mergedShowRef.value) {
|
||||
if (!triggerRef.value?.$el.contains(e.target as Node)) {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
function createClearedMultipleSelectValue (
|
||||
value: string | number | Array<string | number> | null
|
||||
): Array<string | number> {
|
||||
if (!Array.isArray(value)) return []
|
||||
if (wrappedFallbackOptionRef.value) {
|
||||
// if option has a fallback, I can't help user to clear some unknown value
|
||||
return Array.from(value)
|
||||
} else {
|
||||
// if there's no option fallback, unappeared options are treated as invalid
|
||||
const { remote } = props
|
||||
const { value: valOptMap } = valOptMapRef
|
||||
if (remote) {
|
||||
const { value: memoValOptMap } = memoValOptMapRef
|
||||
return value.filter((v) => valOptMap.has(v) || memoValOptMap.has(v))
|
||||
} else {
|
||||
return value.filter((v) => valOptMap.has(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleToggleOption (option: BaseOption): void {
|
||||
if (props.disabled) return
|
||||
const { tag, remote } = props
|
||||
if (tag && !remote) {
|
||||
const { value: beingCreatedOptions } = beingCreatedOptionsRef
|
||||
const beingCreatedOption = beingCreatedOptions[0] || null
|
||||
if (beingCreatedOption) {
|
||||
createdOptionsRef.value.push(beingCreatedOption)
|
||||
beingCreatedOptionsRef.value = []
|
||||
}
|
||||
}
|
||||
if (remote) {
|
||||
memoValOptMapRef.value.set(option.value, option)
|
||||
}
|
||||
if (props.multiple) {
|
||||
const changedValue = createClearedMultipleSelectValue(
|
||||
mergedValueRef.value
|
||||
)
|
||||
const index = changedValue.findIndex((value) => value === option.value)
|
||||
if (~index) {
|
||||
changedValue.splice(index, 1)
|
||||
if (tag && !remote) {
|
||||
const createdOptionIndex = getCreatedOptionIndex(option.value)
|
||||
if (~createdOptionIndex) {
|
||||
createdOptionsRef.value.splice(createdOptionIndex, 1)
|
||||
patternRef.value = ''
|
||||
}
|
||||
}
|
||||
} else {
|
||||
changedValue.push(option.value)
|
||||
patternRef.value = ''
|
||||
}
|
||||
doUpdateValue(changedValue)
|
||||
} else {
|
||||
if (tag && !remote) {
|
||||
const createdOptionIndex = getCreatedOptionIndex(option.value)
|
||||
if (~createdOptionIndex) {
|
||||
createdOptionsRef.value = [
|
||||
createdOptionsRef.value[createdOptionIndex]
|
||||
]
|
||||
} else {
|
||||
createdOptionsRef.value = []
|
||||
}
|
||||
}
|
||||
if (props.filterable && !props.multiple) {
|
||||
returnFocusToWrapper()
|
||||
}
|
||||
closeMenu()
|
||||
doUpdateValue(option.value)
|
||||
}
|
||||
}
|
||||
function handleDeleteLastOption (): void {
|
||||
if (!patternRef.value.length) {
|
||||
const changedValue = createClearedMultipleSelectValue(
|
||||
mergedValueRef.value
|
||||
)
|
||||
if (Array.isArray(changedValue)) {
|
||||
const poppedValue = changedValue.pop()
|
||||
if (poppedValue === undefined) return
|
||||
const createdOptionIndex = getCreatedOptionIndex(poppedValue)
|
||||
~createdOptionIndex &&
|
||||
createdOptionsRef.value.splice(createdOptionIndex, 1)
|
||||
doUpdateValue(changedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
function getCreatedOptionIndex (optionValue: string | number): number {
|
||||
const createdOptions = createdOptionsRef.value
|
||||
return createdOptions.findIndex(
|
||||
(createdOption) => createdOption.value === optionValue
|
||||
)
|
||||
}
|
||||
function handlePatternInput (e: InputEvent): void {
|
||||
const { value } = (e.target as unknown) as HTMLInputElement
|
||||
patternRef.value = value
|
||||
const { tag, remote } = props
|
||||
doSearch(value)
|
||||
if (tag && !remote) {
|
||||
if (!value) {
|
||||
beingCreatedOptionsRef.value = []
|
||||
return
|
||||
}
|
||||
const optionBeingCreated = props.onCreate(value)
|
||||
if (
|
||||
compitableOptionsRef.value.some(
|
||||
(option) => option.value === optionBeingCreated.value
|
||||
) ||
|
||||
createdOptionsRef.value.some(
|
||||
(option) => option.value === optionBeingCreated.value
|
||||
)
|
||||
) {
|
||||
beingCreatedOptionsRef.value = []
|
||||
} else {
|
||||
beingCreatedOptionsRef.value = [optionBeingCreated]
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleClear (e: MouseEvent): void {
|
||||
e.stopPropagation()
|
||||
const { multiple } = props
|
||||
if (!multiple && props.filterable) {
|
||||
closeMenu()
|
||||
}
|
||||
if (multiple) {
|
||||
doUpdateValue([])
|
||||
} else {
|
||||
doUpdateValue(null)
|
||||
}
|
||||
}
|
||||
// scroll events on menu
|
||||
function handleMenuScroll (e: Event): void {
|
||||
doScroll(e)
|
||||
}
|
||||
// keyboard events
|
||||
function handleKeyUp (e: KeyboardEvent): void {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
if (props.filterable) break
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'Enter':
|
||||
if (mergedShowRef.value) {
|
||||
const menu = menuRef.value
|
||||
const pendingOptionData = menu?.getPendingOption()
|
||||
if (pendingOptionData) {
|
||||
handleToggleOption(pendingOptionData)
|
||||
} else {
|
||||
closeMenu()
|
||||
returnFocusToWrapper()
|
||||
}
|
||||
} else {
|
||||
openMenu()
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
case 'ArrowUp':
|
||||
if (props.loading) return
|
||||
if (mergedShowRef.value) {
|
||||
menuRef.value?.prev()
|
||||
}
|
||||
break
|
||||
case 'ArrowDown':
|
||||
if (props.loading) return
|
||||
if (mergedShowRef.value) {
|
||||
menuRef.value?.next()
|
||||
}
|
||||
break
|
||||
case 'Escape':
|
||||
closeMenu()
|
||||
triggerRef.value?.focusPatternInputWrapper()
|
||||
break
|
||||
}
|
||||
}
|
||||
function handleKeyDown (e: KeyboardEvent): void {
|
||||
switch (e.code) {
|
||||
case 'Space':
|
||||
if (!props.filterable) {
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
case 'ArrowUp':
|
||||
case 'ArrowDown':
|
||||
e.preventDefault()
|
||||
break
|
||||
}
|
||||
}
|
||||
function returnFocusToWrapper (): void {
|
||||
triggerRef.value?.focusPatternInputWrapper()
|
||||
}
|
||||
function syncPosition (): void {
|
||||
followerRef.value?.syncPosition()
|
||||
}
|
||||
updateMemorizedOptions()
|
||||
watch(toRef(props, 'options'), updateMemorizedOptions)
|
||||
watch(filteredOptionsRef, () => {
|
||||
if (!mergedShowRef.value) return
|
||||
void nextTick(syncPosition)
|
||||
})
|
||||
watch(mergedValueRef, () => {
|
||||
if (!mergedShowRef.value) return
|
||||
void nextTick(syncPosition)
|
||||
})
|
||||
return {
|
||||
...useConfig(props),
|
||||
treeMate: treeMateRef,
|
||||
isMounted: useIsMounted(),
|
||||
triggerRef,
|
||||
menuRef,
|
||||
pattern: patternRef,
|
||||
uncontrolledShow: uncontrolledShowRef,
|
||||
mergedShow: mergedShowRef,
|
||||
adjustedTo: useAdjustedTo(props),
|
||||
uncontrolledValue: uncontrolledValueRef,
|
||||
mergedValue: mergedValueRef,
|
||||
followerRef,
|
||||
localizedPlaceholder: localizedPlaceholderRef,
|
||||
selectedOption: selectedOptionRef,
|
||||
selectedOptions: selectedOptionsRef,
|
||||
mergedSize: formItem.mergedSize,
|
||||
handleTriggerClick,
|
||||
handleDeleteLastOption,
|
||||
handleToggleOption,
|
||||
handlePatternInput,
|
||||
handleClear,
|
||||
handleTriggerBlur,
|
||||
handleTriggerFocus,
|
||||
handleKeyDown,
|
||||
handleKeyUp,
|
||||
syncPosition,
|
||||
handleMenuLeave,
|
||||
handleMenuClickOutside,
|
||||
handleMenuScroll,
|
||||
mergedTheme: themeRef
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { $slots } = this
|
||||
return (
|
||||
<div class="n-select">
|
||||
<VBinder>
|
||||
{{
|
||||
default: () => [
|
||||
<VTarget>
|
||||
{{
|
||||
default: () => (
|
||||
<NBaseSelection
|
||||
ref="triggerRef"
|
||||
bordered={this.mergedBordered}
|
||||
active={this.mergedShow}
|
||||
pattern={this.pattern}
|
||||
placeholder={this.localizedPlaceholder}
|
||||
selectedOption={this.selectedOption}
|
||||
selectedOptions={this.selectedOptions}
|
||||
multiple={this.multiple}
|
||||
filterable={this.filterable}
|
||||
remote={this.remote}
|
||||
clearable={this.clearable}
|
||||
disabled={this.disabled}
|
||||
size={this.mergedSize}
|
||||
unstableTheme={this.mergedTheme.peers.BaseSelection}
|
||||
unstableThemeOverrides={
|
||||
this.mergedTheme.overrides.BaseSelection
|
||||
}
|
||||
loading={this.loading}
|
||||
autofocus={this.autofocus}
|
||||
onClick={this.handleTriggerClick}
|
||||
onDeleteLastOption={this.handleDeleteLastOption}
|
||||
onDeleteOption={this.handleToggleOption}
|
||||
onPatternInput={this.handlePatternInput}
|
||||
onClear={this.handleClear}
|
||||
onBlur={this.handleTriggerBlur}
|
||||
onFocus={this.handleTriggerFocus}
|
||||
onKeydown={this.handleKeyDown}
|
||||
onKeyup={this.handleKeyUp}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</VTarget>,
|
||||
<VFollower
|
||||
ref="followerRef"
|
||||
show={this.mergedShow}
|
||||
to={this.adjustedTo}
|
||||
containerClass="namespace"
|
||||
width="target"
|
||||
placement="bottom-start"
|
||||
>
|
||||
{{
|
||||
default: () => (
|
||||
<Transition
|
||||
name="n-fade-in-scale-up-transition"
|
||||
appear={this.isMounted}
|
||||
onLeave={this.handleMenuLeave}
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
this.mergedShow &&
|
||||
withDirectives(
|
||||
h(
|
||||
NBaseSelectMenu,
|
||||
{
|
||||
ref: 'menuRef',
|
||||
class: 'n-select-menu',
|
||||
autoPending: true,
|
||||
unstableTheme: this.mergedTheme.peers
|
||||
.BaseSelectMenu,
|
||||
unstableThemeOverrides: this.mergedTheme
|
||||
.overrides.BaseSelectMenu,
|
||||
pattern: this.pattern,
|
||||
treeMate: this.treeMate,
|
||||
multiple: this.multiple,
|
||||
size: 'medium',
|
||||
value: this.mergedValue,
|
||||
onMenuToggleOption: this.handleToggleOption,
|
||||
onScroll: this.handleMenuScroll
|
||||
},
|
||||
$slots
|
||||
),
|
||||
[[clickoutside, this.handleMenuClickOutside]]
|
||||
)
|
||||
}}
|
||||
</Transition>
|
||||
)
|
||||
}}
|
||||
</VFollower>
|
||||
]
|
||||
}}
|
||||
</VBinder>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
@ -1,717 +0,0 @@
|
||||
<template>
|
||||
<div class="n-select">
|
||||
<v-binder>
|
||||
<v-target>
|
||||
<n-base-selection
|
||||
ref="triggerRef"
|
||||
:bordered="mergedBordered"
|
||||
:active="mergedShow"
|
||||
:pattern="pattern"
|
||||
:placeholder="localizedPlaceholder"
|
||||
:selected-option="selectedOption"
|
||||
:selected-options="selectedOptions"
|
||||
:multiple="multiple"
|
||||
:filterable="filterable"
|
||||
:remote="remote"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="mergedSize"
|
||||
:unstable-theme="mergedTheme.peers.BaseSelection"
|
||||
:unstable-theme-overrides="mergedTheme.overrides.BaseSelection"
|
||||
:loading="loading"
|
||||
:autofocus="autofocus"
|
||||
@click="handleTriggerClick"
|
||||
@delete-last-option="handleDeleteLastOption"
|
||||
@delete-option="handleToggleOption"
|
||||
@pattern-input="handlePatternInput"
|
||||
@clear="handleClear"
|
||||
@blur="handleTriggerBlur"
|
||||
@focus="handleTriggerFocus"
|
||||
@keydown.up.prevent
|
||||
@keydown.down.prevent
|
||||
@keydown.space="handleKeyDownSpace"
|
||||
@keyup.up="handleKeyUpUp"
|
||||
@keyup.down="handleKeyUpDown"
|
||||
@keyup.enter="handleKeyUpEnter"
|
||||
@keyup.space="handleKeyUpSpace"
|
||||
@keyup.esc="handleKeyUpEsc"
|
||||
/>
|
||||
</v-target>
|
||||
<v-follower
|
||||
ref="followerRef"
|
||||
:show="mergedShow"
|
||||
:to="adjustedTo"
|
||||
:container-class="namespace"
|
||||
width="target"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<transition
|
||||
name="n-fade-in-scale-up-transition"
|
||||
:appear="isMounted"
|
||||
@leave="handleMenuLeave"
|
||||
>
|
||||
<n-base-select-menu
|
||||
v-if="mergedShow"
|
||||
ref="menuRef"
|
||||
v-clickoutside="handleMenuClickOutside"
|
||||
class="n-select-menu"
|
||||
auto-pending
|
||||
:unstable-theme="mergedTheme.peers.BaseSelectMenu"
|
||||
:unstable-theme-overrides="mergedTheme.overrides.BaseSelectMenu"
|
||||
:pattern="pattern"
|
||||
:tree-mate="treeMate"
|
||||
:multiple="multiple"
|
||||
size="medium"
|
||||
:value="mergedValue"
|
||||
@menu-toggle-option="handleToggleOption"
|
||||
@scroll="handleMenuScroll"
|
||||
>
|
||||
<template v-if="$slots.empty" #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
<template v-if="$slots.unmatch" #unmatch>
|
||||
<slot name="unmatch" />
|
||||
</template>
|
||||
<template v-if="$slots.action" #action>
|
||||
<slot name="action" />
|
||||
</template>
|
||||
</n-base-select-menu>
|
||||
</transition>
|
||||
</v-follower>
|
||||
</v-binder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, toRef, defineComponent } from 'vue'
|
||||
import { createTreeMate } from 'treemate'
|
||||
import { VBinder, VFollower, VTarget } from 'vueuc'
|
||||
import { useIsMounted, useMergedState, useCompitable } from 'vooks'
|
||||
import { clickoutside } from 'vdirs'
|
||||
import { useTheme, useConfig, useLocale, useFormItem } from '../../_mixins'
|
||||
import { warn, call, useAdjustedTo } from '../../_utils'
|
||||
import { NBaseSelectMenu, NBaseSelection } from '../../_base'
|
||||
import { selectLight } from '../styles'
|
||||
import style from './styles/index.cssr.js'
|
||||
|
||||
function patternMatched (pattern, value) {
|
||||
try {
|
||||
return (
|
||||
1 + value.toString().toLowerCase().indexOf(pattern.trim().toLowerCase())
|
||||
)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function filterOptions (originalOpts, filter, pattern) {
|
||||
if (!filter) return originalOpts
|
||||
function traverse (options) {
|
||||
if (!Array.isArray(options)) return []
|
||||
const filteredOptions = []
|
||||
for (const option of options) {
|
||||
if (option.type === 'group') {
|
||||
const children = traverse(option.children)
|
||||
if (children.length) {
|
||||
filteredOptions.push(
|
||||
Object.assign({}, option, {
|
||||
children
|
||||
})
|
||||
)
|
||||
}
|
||||
} else if (filter(pattern, option)) {
|
||||
filteredOptions.push(option)
|
||||
}
|
||||
}
|
||||
return filteredOptions
|
||||
}
|
||||
return traverse(originalOpts)
|
||||
}
|
||||
|
||||
function createValOptMap (options) {
|
||||
const valOptMap = new Map()
|
||||
options.forEach((option) => {
|
||||
if (option.type === 'group') {
|
||||
option.children.forEach((groupOption) => {
|
||||
valOptMap.set(groupOption.value, groupOption)
|
||||
})
|
||||
} else {
|
||||
valOptMap.set(option.value, option)
|
||||
}
|
||||
})
|
||||
return valOptMap
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Select',
|
||||
components: {
|
||||
NBaseSelectMenu,
|
||||
NBaseSelection,
|
||||
VBinder,
|
||||
VFollower,
|
||||
VTarget
|
||||
},
|
||||
directives: {
|
||||
clickoutside
|
||||
},
|
||||
provide () {
|
||||
return {
|
||||
NSelect: this
|
||||
}
|
||||
},
|
||||
props: {
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
defaultValue: {
|
||||
type: [String, Number, Array],
|
||||
default: null
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
default: undefined
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return ['small', 'medium', 'large'].includes(value)
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filter: {
|
||||
type: Function,
|
||||
default: (pattern, option) => {
|
||||
if (!option) return false
|
||||
if (option.label !== undefined) {
|
||||
return patternMatched(pattern, option.label)
|
||||
} else if (option.value !== undefined) {
|
||||
return patternMatched(pattern, option.value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-start'
|
||||
},
|
||||
widthMode: {
|
||||
type: String,
|
||||
default: 'trigger'
|
||||
},
|
||||
tag: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onCreate: {
|
||||
type: Function,
|
||||
default: (label) => ({
|
||||
label: label,
|
||||
value: label
|
||||
})
|
||||
},
|
||||
fallbackOption: {
|
||||
type: [Function, Boolean],
|
||||
default: () => (value) => ({
|
||||
label: '' + value,
|
||||
value
|
||||
})
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: undefined
|
||||
},
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
'onUpdate:value': {
|
||||
type: [Function, Array],
|
||||
default: undefined
|
||||
},
|
||||
onBlur: {
|
||||
type: [Function, Array],
|
||||
default: undefined
|
||||
},
|
||||
onFocus: {
|
||||
type: [Function, Array],
|
||||
default: undefined
|
||||
},
|
||||
onScroll: {
|
||||
type: [Function, Array],
|
||||
default: undefined
|
||||
},
|
||||
onSearch: {
|
||||
type: [Function, Array],
|
||||
default: undefined
|
||||
},
|
||||
/** deprecated */
|
||||
onChange: {
|
||||
validator () {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
'select',
|
||||
'`on-change` is deprecated, please use `on-update:value` instead.'
|
||||
)
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
items: {
|
||||
validator () {
|
||||
if (__DEV__) {
|
||||
warn('select', '`items` is deprecated, please use `options` instead.')
|
||||
}
|
||||
return true
|
||||
},
|
||||
default: undefined
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
const themeRef = useTheme('Select', 'Select', style, selectLight, props)
|
||||
const uncontrolledValueRef = ref(props.defaultValue)
|
||||
const controlledValueRef = toRef(props, 'value')
|
||||
const mergedValueRef = useMergedState(
|
||||
controlledValueRef,
|
||||
uncontrolledValueRef
|
||||
)
|
||||
const patternRef = ref('')
|
||||
const filteredOptionsRef = computed(() =>
|
||||
filterOptions(props.options, props.filter, patternRef.value)
|
||||
)
|
||||
const treeMateRef = computed(() =>
|
||||
createTreeMate(filteredOptionsRef.value, {
|
||||
getKey (node) {
|
||||
if (node.type === 'group') return node.name
|
||||
return node.value
|
||||
}
|
||||
})
|
||||
)
|
||||
const valOptMapRef = computed(() => createValOptMap(props.options))
|
||||
const uncontrolledShowRef = ref(false)
|
||||
const mergedShowRef = useMergedState(
|
||||
toRef(props, 'show'),
|
||||
uncontrolledShowRef
|
||||
)
|
||||
const followerRef = ref(null)
|
||||
return {
|
||||
...useFormItem(props),
|
||||
...useConfig(props),
|
||||
...useLocale('Select'),
|
||||
treeMate: treeMateRef,
|
||||
flattenedNodes: computed(() => {
|
||||
return treeMateRef.value.flattenedNodes
|
||||
}),
|
||||
valOptMap: valOptMapRef,
|
||||
isMounted: useIsMounted(),
|
||||
offsetContainerRef: ref(null),
|
||||
triggerRef: ref(null),
|
||||
trackingRef: ref(null),
|
||||
menuRef: ref(null),
|
||||
pattern: patternRef,
|
||||
uncontrolledShow: uncontrolledShowRef,
|
||||
mergedShow: mergedShowRef,
|
||||
compitableOptions: useCompitable(props, ['items', 'options']),
|
||||
adjustedTo: useAdjustedTo(props),
|
||||
createdOptions: ref([]),
|
||||
beingCreatedOptions: ref([]),
|
||||
memoValOptMap: ref(new Map()),
|
||||
uncontrolledValue: uncontrolledValueRef,
|
||||
mergedValue: mergedValueRef,
|
||||
followerRef,
|
||||
mergedTheme: themeRef
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
localizedPlaceholder () {
|
||||
return this.placeholder ?? this.locale.placeholder
|
||||
},
|
||||
localOptions () {
|
||||
return this.compitableOptions
|
||||
.concat(this.createdOptions)
|
||||
.concat(this.beingCreatedOptions)
|
||||
},
|
||||
filteredOptions () {
|
||||
if (this.remote) {
|
||||
return this.compitableOptions
|
||||
} else {
|
||||
const { localOptions, pattern } = this
|
||||
if (!pattern.length || !this.filterable) {
|
||||
return localOptions
|
||||
} else {
|
||||
const { filter } = this
|
||||
const mergedFilter = (option) => filter(pattern, option)
|
||||
return filterOptions(localOptions, mergedFilter)
|
||||
}
|
||||
}
|
||||
},
|
||||
selectedOptions () {
|
||||
if (this.multiple) {
|
||||
const { mergedValue: values } = this
|
||||
if (!Array.isArray(values)) return []
|
||||
const remote = this.remote
|
||||
const { valOptMap, memoValOptMap, wrappedFallbackOption } = this
|
||||
const options = []
|
||||
values.forEach((value) => {
|
||||
if (valOptMap.has(value)) {
|
||||
options.push(valOptMap.get(value))
|
||||
} else if (remote && memoValOptMap.has(value)) {
|
||||
options.push(memoValOptMap.get(value))
|
||||
} else if (wrappedFallbackOption) {
|
||||
const option = wrappedFallbackOption(value)
|
||||
if (option) {
|
||||
options.push(option)
|
||||
}
|
||||
}
|
||||
})
|
||||
return options
|
||||
}
|
||||
return null
|
||||
},
|
||||
selectedOption () {
|
||||
if (!this.multiple) {
|
||||
const { mergedValue, valOptMap, wrappedFallbackOption } = this
|
||||
if (mergedValue === null) return null
|
||||
let selectedOption = null
|
||||
if (valOptMap.has(mergedValue)) {
|
||||
selectedOption = valOptMap.get(mergedValue)
|
||||
} else if (this.remote) {
|
||||
selectedOption = this.memoValOptMap.get(mergedValue)
|
||||
}
|
||||
return (
|
||||
selectedOption ||
|
||||
(wrappedFallbackOption && wrappedFallbackOption(mergedValue)) ||
|
||||
null
|
||||
)
|
||||
}
|
||||
return null
|
||||
},
|
||||
wrappedFallbackOption () {
|
||||
const { fallbackOption } = this
|
||||
if (!fallbackOption) return false
|
||||
return (value) => {
|
||||
if (['string', 'number'].includes(typeof value)) {
|
||||
return Object.assign(fallbackOption(value), { value })
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options () {
|
||||
this.updateMemorizedOptions()
|
||||
},
|
||||
filteredOptions () {
|
||||
if (!this.mergedShow) return
|
||||
this.$nextTick(this.syncPosition)
|
||||
},
|
||||
mergedValue () {
|
||||
if (!this.mergedShow) return
|
||||
this.$nextTick(this.syncPosition)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.updateMemorizedOptions()
|
||||
},
|
||||
methods: {
|
||||
doUpdateValue (value) {
|
||||
const {
|
||||
onChange,
|
||||
'onUpdate:value': onUpdateValue,
|
||||
nTriggerFormChange,
|
||||
nTriggerFormInput
|
||||
} = this
|
||||
if (onChange) call(onChange, value)
|
||||
if (onUpdateValue) call(onUpdateValue, value)
|
||||
this.uncontrolledValue = value
|
||||
nTriggerFormChange()
|
||||
nTriggerFormInput()
|
||||
},
|
||||
doBlur (value) {
|
||||
const { onBlur, nTriggerFormBlur } = this
|
||||
if (onBlur) call(onBlur, value)
|
||||
nTriggerFormBlur()
|
||||
},
|
||||
doFocus (value) {
|
||||
const { onFocus, nTriggerFormFocus } = this
|
||||
if (onFocus) call(onFocus, value)
|
||||
nTriggerFormFocus()
|
||||
},
|
||||
doSearch (value) {
|
||||
const { onSearch } = this
|
||||
if (onSearch) call(onSearch, value)
|
||||
},
|
||||
doScroll (...args) {
|
||||
const { onScroll } = this
|
||||
if (onScroll) call(onScroll, ...args)
|
||||
},
|
||||
// remote related methods
|
||||
updateMemorizedOptions () {
|
||||
const { remote, multiple } = this
|
||||
if (remote) {
|
||||
const { memoValOptMap } = this
|
||||
if (multiple) {
|
||||
this.selectedOptions.forEach((option) => {
|
||||
memoValOptMap.set(option.value, option)
|
||||
})
|
||||
} else {
|
||||
const option = this.selectedOption
|
||||
if (option) {
|
||||
memoValOptMap.set(option.value, option)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// menu related methods
|
||||
openMenu () {
|
||||
if (!this.disabled) {
|
||||
this.pattern = ''
|
||||
this.uncontrolledShow = true
|
||||
if (this.filterable) {
|
||||
this.triggerRef.focusPatternInput()
|
||||
}
|
||||
}
|
||||
},
|
||||
closeMenu () {
|
||||
this.uncontrolledShow = false
|
||||
},
|
||||
handleMenuLeave () {
|
||||
this.pattern = ''
|
||||
},
|
||||
handleTriggerClick () {
|
||||
if (this.disabled) return
|
||||
if (!this.mergedShow) {
|
||||
this.openMenu()
|
||||
} else {
|
||||
if (!this.filterable) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTriggerBlur () {
|
||||
this.doBlur()
|
||||
this.closeMenu()
|
||||
},
|
||||
handleTriggerFocus () {
|
||||
this.doFocus()
|
||||
},
|
||||
handleMenuClickOutside (e) {
|
||||
if (this.mergedShow) {
|
||||
if (!this.triggerRef.$el.contains(e.target)) {
|
||||
this.closeMenu()
|
||||
}
|
||||
}
|
||||
},
|
||||
createClearedMultipleSelectValue (value) {
|
||||
if (!Array.isArray(value)) return []
|
||||
if (this.wrappedFallbackOption) {
|
||||
// if option has a fallback, I can't help user to clear some unknown value
|
||||
return Array.from(value)
|
||||
} else {
|
||||
// if there's no option fallback, unappeared options are treated as invalid
|
||||
const { remote, valOptMap } = this
|
||||
if (remote) {
|
||||
const { memoValOptMap } = this
|
||||
return value.filter((v) => valOptMap.has(v) || memoValOptMap.has(v))
|
||||
} else {
|
||||
return value.filter((v) => valOptMap.has(v))
|
||||
}
|
||||
}
|
||||
},
|
||||
handleToggleOption (option) {
|
||||
if (this.disabled) return
|
||||
const { tag, remote } = this
|
||||
if (tag && !remote) {
|
||||
const { beingCreatedOptions } = this
|
||||
const beingCreatedOption = beingCreatedOptions[0] || null
|
||||
if (beingCreatedOption) {
|
||||
this.createdOptions.push(beingCreatedOption)
|
||||
this.beingCreatedOptions = []
|
||||
}
|
||||
}
|
||||
if (remote) {
|
||||
this.memoValOptMap.set(option.value, option)
|
||||
}
|
||||
if (this.multiple) {
|
||||
const changedValue = this.createClearedMultipleSelectValue(
|
||||
this.mergedValue
|
||||
)
|
||||
const index = changedValue.findIndex((value) => value === option.value)
|
||||
if (~index) {
|
||||
changedValue.splice(index, 1)
|
||||
if (tag && !remote) {
|
||||
const createdOptionIndex = this.getCreatedOptionIndex(option.value)
|
||||
if (~createdOptionIndex) {
|
||||
this.createdOptions.splice(createdOptionIndex, 1)
|
||||
this.pattern = ''
|
||||
}
|
||||
}
|
||||
} else {
|
||||
changedValue.push(option.value)
|
||||
this.pattern = ''
|
||||
}
|
||||
this.doUpdateValue(changedValue)
|
||||
} else {
|
||||
if (tag && !remote) {
|
||||
const createdOptionIndex = this.getCreatedOptionIndex(option.value)
|
||||
if (~createdOptionIndex) {
|
||||
this.createdOptions = [this.createdOptions[createdOptionIndex]]
|
||||
} else {
|
||||
this.createdOptions = []
|
||||
}
|
||||
}
|
||||
if (this.filterable && !this.multiple) {
|
||||
this.returnFocusToWrapper()
|
||||
}
|
||||
this.closeMenu()
|
||||
this.doUpdateValue(option.value)
|
||||
}
|
||||
},
|
||||
handleDeleteLastOption (e) {
|
||||
if (!this.pattern.length) {
|
||||
const changedValue = this.createClearedMultipleSelectValue(
|
||||
this.mergedValue
|
||||
)
|
||||
if (Array.isArray(changedValue)) {
|
||||
const poppedValue = changedValue.pop()
|
||||
const createdOptionIndex = this.getCreatedOptionIndex(poppedValue)
|
||||
~createdOptionIndex &&
|
||||
this.createdOptions.splice(createdOptionIndex, 1)
|
||||
this.doUpdateValue(changedValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
getCreatedOptionIndex (optionValue) {
|
||||
const createdOptions = this.createdOptions
|
||||
return createdOptions.findIndex(
|
||||
(createdOption) => createdOption.value === optionValue
|
||||
)
|
||||
},
|
||||
handlePatternInput (e) {
|
||||
const { value } = e.target
|
||||
this.pattern = value
|
||||
const { onSearch, tag, remote } = this
|
||||
if (onSearch) {
|
||||
onSearch(value)
|
||||
this.doSearch(value)
|
||||
}
|
||||
if (tag && !remote) {
|
||||
if (!value) {
|
||||
this.beingCreatedOptions = []
|
||||
return
|
||||
}
|
||||
const optionBeingCreated = this.onCreate(value)
|
||||
if (
|
||||
this.compitableOptions.some(
|
||||
(option) => option.value === optionBeingCreated.value
|
||||
) ||
|
||||
this.createdOptions.some(
|
||||
(option) => option.value === optionBeingCreated.value
|
||||
)
|
||||
) {
|
||||
this.beingCreatedOptions = []
|
||||
} else {
|
||||
this.beingCreatedOptions = [optionBeingCreated]
|
||||
}
|
||||
}
|
||||
},
|
||||
handleClear (e) {
|
||||
e.stopPropagation()
|
||||
const { multiple, doUpdateValue } = this
|
||||
if (!multiple && this.filterable) {
|
||||
this.closeMenu()
|
||||
}
|
||||
if (multiple) {
|
||||
doUpdateValue([])
|
||||
} else {
|
||||
doUpdateValue(null)
|
||||
}
|
||||
},
|
||||
// scroll events on menu
|
||||
handleMenuScroll (e) {
|
||||
this.doScroll(e)
|
||||
},
|
||||
// keyboard events
|
||||
handleKeyUpEnter (e) {
|
||||
if (this.mergedShow) {
|
||||
const menu = this.menuRef
|
||||
const pendingOptionData = menu && menu.getPendingOption()
|
||||
if (pendingOptionData) {
|
||||
this.handleToggleOption(pendingOptionData)
|
||||
} else {
|
||||
this.closeMenu()
|
||||
this.returnFocusToWrapper()
|
||||
}
|
||||
} else {
|
||||
this.openMenu()
|
||||
}
|
||||
e.preventDefault()
|
||||
},
|
||||
handleKeyUpSpace (e) {
|
||||
if (!this.filterable) {
|
||||
this.handleKeyUpEnter(e)
|
||||
}
|
||||
},
|
||||
handleKeyUpUp () {
|
||||
if (this.loading) return
|
||||
if (this.mergedShow) {
|
||||
this.menuRef.prev()
|
||||
}
|
||||
},
|
||||
handleKeyUpDown () {
|
||||
if (this.loading) return
|
||||
if (this.mergedShow) {
|
||||
this.menuRef.next()
|
||||
}
|
||||
},
|
||||
handleKeyDownSpace (e) {
|
||||
if (!this.filterable) {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
handleKeyUpEsc (e) {
|
||||
this.closeMenu()
|
||||
this.triggerRef.focusPatternInputWrapper()
|
||||
},
|
||||
returnFocusToWrapper () {
|
||||
this.triggerRef.focusPatternInputWrapper()
|
||||
},
|
||||
syncPosition () {
|
||||
this.followerRef.syncPosition()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
23
src/select/src/interface.ts
Normal file
23
src/select/src/interface.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export type Options = Option[]
|
||||
export type Option = BaseOption | GroupOption | IgnoredOption
|
||||
|
||||
export interface BaseOption {
|
||||
value: string | number
|
||||
label: string
|
||||
disabled?: boolean
|
||||
[k: string]: any
|
||||
}
|
||||
|
||||
export interface GroupOption {
|
||||
label: string
|
||||
name: string
|
||||
type: 'group'
|
||||
children: BaseOption[]
|
||||
[k: string]: any
|
||||
}
|
||||
|
||||
export interface IgnoredOption {
|
||||
ignored: true
|
||||
value: string | number
|
||||
[k: string]: any
|
||||
}
|
85
src/select/src/utils.ts
Normal file
85
src/select/src/utils.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { TreeMateOptions } from 'treemate'
|
||||
import type {
|
||||
BaseOption,
|
||||
GroupOption,
|
||||
IgnoredOption,
|
||||
Option,
|
||||
Options
|
||||
} from './interface'
|
||||
|
||||
export function getKey (option: Option): string | number {
|
||||
if (getIsGroup(option)) return (option as GroupOption).name
|
||||
return option.value
|
||||
}
|
||||
|
||||
export function getIsGroup (option: Option): boolean {
|
||||
return option.type === 'group'
|
||||
}
|
||||
|
||||
export function getIgnored (option: Option): boolean {
|
||||
return !!option.ignored
|
||||
}
|
||||
|
||||
export const tmOptions: TreeMateOptions<
|
||||
BaseOption,
|
||||
GroupOption,
|
||||
IgnoredOption
|
||||
> = {
|
||||
getKey,
|
||||
getIsGroup,
|
||||
getIgnored
|
||||
}
|
||||
|
||||
export function patternMatched (pattern: string, value: string): boolean {
|
||||
try {
|
||||
return !!(
|
||||
1 + value.toString().toLowerCase().indexOf(pattern.trim().toLowerCase())
|
||||
)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function filterOptions (
|
||||
originalOpts: Options,
|
||||
filter: (pattern: string, option: Option) => boolean,
|
||||
pattern: string
|
||||
): Options {
|
||||
if (!filter) return originalOpts
|
||||
function traverse (options: Options): Options {
|
||||
if (!Array.isArray(options)) return []
|
||||
const filteredOptions = []
|
||||
for (const option of options) {
|
||||
if ('type' in option && option.type === 'group') {
|
||||
const children = traverse(option.children)
|
||||
if (children.length) {
|
||||
filteredOptions.push(
|
||||
Object.assign({}, option, {
|
||||
children
|
||||
})
|
||||
)
|
||||
}
|
||||
} else if (filter(pattern, option)) {
|
||||
filteredOptions.push(option)
|
||||
}
|
||||
}
|
||||
return filteredOptions
|
||||
}
|
||||
return traverse(originalOpts)
|
||||
}
|
||||
|
||||
export function createValOptMap (
|
||||
options: Options
|
||||
): Map<string | number, BaseOption> {
|
||||
const valOptMap = new Map()
|
||||
options.forEach((option) => {
|
||||
if (option.type === 'group') {
|
||||
option.children.forEach((groupOption: GroupOption) => {
|
||||
valOptMap.set(groupOption.value, groupOption)
|
||||
})
|
||||
} else {
|
||||
valOptMap.set(option.value, option)
|
||||
}
|
||||
})
|
||||
return valOptMap
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import { baseSelectionDark } from '../../_base/selection/styles'
|
||||
import { baseSelectMenuDark } from '../../_base/select-menu/styles'
|
||||
import { commonDark } from '../../_styles/new-common'
|
||||
import type { SelectTheme } from './light'
|
||||
|
||||
export default {
|
||||
const selectDark: SelectTheme = {
|
||||
name: 'Select',
|
||||
common: commonDark,
|
||||
peers: {
|
||||
@ -10,3 +11,5 @@ export default {
|
||||
BaseSelectMenu: baseSelectMenuDark
|
||||
}
|
||||
}
|
||||
|
||||
export default selectDark
|
@ -1,2 +0,0 @@
|
||||
export { default as selectDark } from './dark.js'
|
||||
export { default as selectLight } from './light.js'
|
3
src/select/styles/index.ts
Normal file
3
src/select/styles/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { default as selectDark } from './dark'
|
||||
export { default as selectLight } from './light'
|
||||
export type { SelectTheme } from './light'
|
@ -1,12 +1,16 @@
|
||||
import { baseSelectionLight } from '../../_base/selection/styles'
|
||||
import { baseSelectMenuLight } from '../../_base/select-menu/styles'
|
||||
import { commonLight } from '../../_styles/new-common'
|
||||
import { createTheme } from '../../_mixins'
|
||||
|
||||
export default {
|
||||
const selectLight = createTheme({
|
||||
name: 'Select',
|
||||
common: commonLight,
|
||||
peers: {
|
||||
BaseSelection: baseSelectionLight,
|
||||
BaseSelectMenu: baseSelectMenuLight
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default selectLight
|
||||
export type SelectTheme = typeof selectLight
|
@ -30,10 +30,10 @@ export default cB('text', `
|
||||
border-color .3s var(--bezier),
|
||||
background-color .3s var(--bezier);
|
||||
box-sizing: border-box;
|
||||
padding: .15em .45em 0 .45em;
|
||||
padding: .05em .35em 0 .35em;
|
||||
border-radius: var(--code-border-radius);
|
||||
font-size: .9em;
|
||||
color: var(code-text-color);
|
||||
color: var(--code-text-color);
|
||||
background-color: var(--code-color);
|
||||
border: var(--code-border);
|
||||
`)
|
||||
|
Loading…
Reference in New Issue
Block a user