refactor(select): ts

This commit is contained in:
07akioni 2021-01-20 18:31:30 +08:00
parent 5faf1a8870
commit e608c393df
23 changed files with 963 additions and 801 deletions

View File

@ -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'

View File

@ -1,2 +1,3 @@
/* istanbul ignore file */
export { default } from './src/SelectMenu'
export type { BaseSelectMenuRef } from './src/SelectMenu'

View File

@ -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
}
},

View File

@ -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>

View File

@ -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
}
},

View File

@ -1,2 +1,3 @@
/* istanbul ignore file */
export { default } from './src/Selection'
export type { BaseSelectionRef } from './src/Selection'

View File

@ -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>
)}

View File

@ -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,

View File

@ -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

View File

@ -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
}
}

View File

@ -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 {

View File

@ -1,2 +0,0 @@
/* istanbul ignore file */
export { default as NSelect } from './src/Select.vue'

3
src/select/index.ts Normal file
View 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
View 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>
)
}
})

View File

@ -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>

View 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
View 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
}

View File

@ -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

View File

@ -1,2 +0,0 @@
export { default as selectDark } from './dark.js'
export { default as selectLight } from './light.js'

View File

@ -0,0 +1,3 @@
export { default as selectDark } from './dark'
export { default as selectLight } from './light'
export type { SelectTheme } from './light'

View File

@ -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

View File

@ -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);
`)