= (props, { slots }) => {
+ const { disabled, prefixCls, removable, value, onRemove } = props
+
+ const classes = prefixCls + (disabled ? ` ${prefixCls}-disabled` : '')
+
+ const handleClick = (evt: Event) => {
+ evt.stopPropagation()
+ callEmit(onRemove, value)
+ }
+
+ return (
+
+ {slots.default!()}
+ {removable && (
+
+
+
+ )}
+
+ )
+}
+
+export default Item
diff --git a/packages/components/selector/src/token.ts b/packages/components/selector/src/token.ts
new file mode 100644
index 000000000..a5f80edba
--- /dev/null
+++ b/packages/components/selector/src/token.ts
@@ -0,0 +1,28 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { SelectorProps } from './types'
+import type { ComputedRef, InjectionKey, Ref } from 'vue'
+
+export interface SelectorContext {
+ props: SelectorProps
+ mergedPrefixCls: ComputedRef
+ mergedSearchable: ComputedRef
+ mirrorRef: Ref
+ inputRef: Ref
+ inputValue: Ref
+ isComposing: Ref
+ mergedFocused: ComputedRef
+ handleCompositionStart: (evt: CompositionEvent) => void
+ handleCompositionEnd: (evt: CompositionEvent) => void
+ handleInput: (evt: Event) => void
+ handleEnterDown: (evt: KeyboardEvent) => void
+}
+
+export const selectorToken: InjectionKey = Symbol('selectorToken')
diff --git a/packages/components/selector/src/types.ts b/packages/components/selector/src/types.ts
new file mode 100644
index 000000000..8de726c7d
--- /dev/null
+++ b/packages/components/selector/src/types.ts
@@ -0,0 +1,74 @@
+/**
+ * @license
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
+ */
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import type { ValidateStatus } from '@idux/cdk/forms'
+import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils'
+import type { FormSize } from '@idux/components/form'
+import type { GetKeyFn } from '@idux/components/utils'
+import type { DefineComponent, HTMLAttributes, PropType, VNodeChild } from 'vue'
+
+export interface SelectorData {
+ disabled?: boolean
+ key?: K
+ label?: string | number
+ value?: any
+ rawData?: any
+ customLabel?: string | ((data: SelectorData) => VNodeChild)
+
+ [key: string]: any
+}
+
+export const selectorProps = {
+ input: String,
+ allowInput: { type: [Boolean, String] as PropType, default: undefined },
+ autocomplete: { type: String, default: undefined },
+ autofocus: { type: Boolean, default: undefined },
+ borderless: { type: Boolean, default: undefined },
+ clearable: { type: Boolean, default: undefined },
+ clearIcon: { type: String, default: 'close-circle' },
+ dataSource: Array as PropType,
+ disabled: { type: Boolean, default: undefined },
+ focused: { type: Boolean, default: undefined },
+ getKey: { type: [String, Function] as PropType, default: 'key' },
+ labelKey: { type: String, default: undefined },
+ maxLabel: { type: [Number, String] as PropType, default: undefined },
+ multiple: { type: Boolean, default: undefined },
+ monitorFocus: { type: Boolean, default: true },
+ opened: { type: Boolean, default: false },
+ placeholder: { type: String, default: undefined },
+ readonly: { type: Boolean, default: undefined },
+ size: { type: String as PropType, default: 'md' },
+ status: String as PropType,
+ suffix: { type: String, default: undefined },
+ suffixRotate: { type: [Boolean, Number, String] as PropType, default: undefined },
+
+ 'onUpdate:input': [Function, Array] as PropType void>>,
+ onBlur: [Function, Array] as PropType void>>,
+ onFocus: [Function, Array] as PropType void>>,
+ onClear: [Function, Array] as PropType void>>,
+ onCompositionStart: [Function, Array] as PropType void>>,
+ onCompositionEnd: [Function, Array] as PropType void>>,
+ onInput: [Function, Array] as PropType void>>,
+ onInputValueChange: [Function, Array] as PropType void>>,
+ onItemRemove: [Function, Array] as PropType void>>,
+} as const
+
+export type SelectorProps = ExtractInnerPropTypes
+export type SelectorPublicProps = ExtractPublicPropTypes
+export interface SelectorBindings {
+ blur: () => void
+ focus: (options?: FocusOptions) => void
+ clearInput: () => void
+ getBoundingClientRect: () => DOMRect | undefined
+}
+export type SelectorComponent = DefineComponent<
+ Omit & SelectorPublicProps,
+ SelectorBindings
+>
+export type SelectorInstance = InstanceType>
diff --git a/packages/components/selector/style/index.less b/packages/components/selector/style/index.less
new file mode 100644
index 000000000..ff38eca8b
--- /dev/null
+++ b/packages/components/selector/style/index.less
@@ -0,0 +1,63 @@
+@import '../../style/variable/index.less';
+@import '../../style/mixins/borderless.less';
+@import '../../style/mixins/ellipsis.less';
+@import '../../style/mixins/reset.less';
+@import './single.less';
+@import './multiple.less';
+
+.@{selector-prefix} {
+ .reset-component();
+
+ position: relative;
+ display: inline-block;
+ width: 100%;
+
+ &-content {
+ position: relative;
+ display: flex;
+ align-items: center;
+ transition: all var(--ix-motion-duration-medium) var(--ix-motion-ease-in-out);
+ cursor: pointer;
+ }
+
+ &-item {
+ .ellipsis();
+
+ user-select: none;
+
+ &-label {
+ .ellipsis();
+ }
+ }
+
+ &-input {
+ &-inner {
+ width: 100%;
+ min-width: 1px;
+ margin: 0;
+ padding: 0;
+ background: transparent;
+ border: none;
+ outline: none;
+ appearance: none;
+ cursor: pointer;
+ }
+ }
+
+ &-placeholder {
+ flex: 1;
+ overflow: hidden;
+ color: var(--ix-color-text-placeholder);
+ position: absolute;
+ .ellipsis();
+ }
+
+ &-searchable &-content,
+ &-allow-input &-content {
+ cursor: text;
+
+ .@{selector-prefix}-input-inner {
+ cursor: auto;
+ }
+ }
+}
diff --git a/packages/components/selector/style/index.ts b/packages/components/selector/style/index.ts
new file mode 100644
index 000000000..a66eb7949
--- /dev/null
+++ b/packages/components/selector/style/index.ts
@@ -0,0 +1,6 @@
+// style dependencies
+
+import '@idux/components/icon/style'
+import '@idux/components/input/style'
+
+import './index.less'
diff --git a/packages/components/selector/style/multiple.less b/packages/components/selector/style/multiple.less
new file mode 100644
index 000000000..07e16f9ce
--- /dev/null
+++ b/packages/components/selector/style/multiple.less
@@ -0,0 +1,122 @@
+.select-size(@select-height; @select-font-size; @horizontal-padding) {
+ @select-margin-half: calc((var(--ix-margin-size-xs) / 2));
+ @select-padding-vertical: ~'max(calc(var(--ix-margin-size-xs) - var(--ix-control-line-width) - @{select-margin-half}), 0px)';
+ @select-item-height: calc(@select-height - var(--ix-margin-size-xs) * 2);
+ @select-item-line-height: calc(@select-item-height - var(--ix-control-line-width) * 2);
+
+ &.@{selector-prefix} {
+ font-size: @select-font-size;
+ }
+ .@{selector-prefix} {
+ &-content {
+ padding: @select-padding-vertical var(--ix-margin-size-xs);
+
+ .@{overflow-prefix}-item {
+ max-width: calc(100% - var(--ix-font-size-icon));
+ }
+ }
+
+ &-item {
+ height: @select-item-height;
+ line-height: @select-item-line-height;
+ margin: @select-margin-half 0;
+ margin-inline-end: var(--ix-margin-size-xs);
+ }
+
+ &-input {
+ margin: @select-margin-half 0;
+ margin-inline-start: var(--ix-margin-size-xs);
+
+ &-inner,
+ &-mirror {
+ height: @select-item-height;
+ line-height: @select-item-line-height;
+ }
+ }
+
+ &-overflow {
+ line-height: @select-item-line-height;
+ }
+
+ &-placeholder {
+ right: @horizontal-padding;
+ left: @horizontal-padding;
+ }
+ }
+
+ &.@{selector-prefix}-searchable .@{selector-prefix}-overflow,
+ &.@{selector-prefix}-allow-input .@{selector-prefix}-overflow {
+ padding-right: calc(var(--ix-font-size-icon) + var(--ix-margin-size-xs));
+ }
+}
+
+.@{selector-prefix}-multiple {
+ .@{selector-prefix} {
+ &-content {
+ flex-wrap: wrap;
+ align-items: center;
+
+ .@{overflow-prefix}-item {
+ overflow: hidden;
+ }
+ }
+
+ &-item {
+ display: flex;
+ flex: none;
+ max-width: 100%;
+ padding: 0 var(--ix-padding-size-sm) 0 var(--ix-padding-size-sm);
+ background-color: var(--ix-color-emphasized-container-bg);
+ border: var(--ix-control-line-width) var(--ix-control-line-type) var(--ix-color-border-inverse);
+ border-radius: var(--ix-border-radius-sm);
+ cursor: default;
+
+ &-label {
+ display: inline-block;
+ .ellipsis();
+ }
+
+ &-remove {
+ margin: 0 calc(var(--ix-margin-size-xs) * -1) 0 var(--ix-margin-size-xs);
+ color: var(--ix-color-icon);
+ font-size: var(--ix-font-size-icon);
+ line-height: inherit;
+ cursor: pointer;
+
+ &:hover {
+ color: var(--ix-color-icon-hover);
+ }
+ }
+ }
+
+ &-input {
+ position: relative;
+ max-width: 100%;
+
+ &-mirror {
+ position: absolute;
+ top: 0;
+ left: 0;
+ white-space: pre;
+ visibility: hidden;
+ }
+ }
+ }
+ &.@{selector-prefix}-disabled .@{selector-prefix}-item-disabled {
+ color: var(--ix-color-text-disabled);
+ border-color: var(--ix-color-border);
+ cursor: not-allowed;
+ }
+
+ &.@{selector-prefix}-sm {
+ .select-size(var(--ix-control-height-sm), var(--ix-control-font-size-sm), var(--ix-control-padding-size-horizontal-sm));
+ }
+
+ &.@{selector-prefix}-md {
+ .select-size(var(--ix-control-height-md), var(--ix-control-font-size-md), var(--ix-control-padding-size-horizontal-md));
+ }
+
+ &.@{selector-prefix}-lg {
+ .select-size(var(--ix-control-height-lg), var(--ix-control-font-size-lg), var(--ix-control-padding-size-horizontal-lg));
+ }
+}
diff --git a/packages/components/selector/style/single.less b/packages/components/selector/style/single.less
new file mode 100644
index 000000000..58a7c86a0
--- /dev/null
+++ b/packages/components/selector/style/single.less
@@ -0,0 +1,22 @@
+.@{selector-prefix}-single {
+ .@{selector-prefix} {
+ &-content {
+ width: 100%;
+ display: flex;
+ }
+
+ &-input {
+ flex: auto;
+ width: 0;
+ margin-right: calc((var(--ix-font-size-icon) / 2) + var(--ix-margin-size-sm));
+ }
+
+ &-item {
+ position: absolute;
+ }
+ }
+
+ &.@{selector-prefix}-opened .@{selector-prefix}-item {
+ color: var(--ix-color-text-placeholder);
+ }
+}