Skip to content

Commit

Permalink
feat(comp:*): all input components with overlay support focus and blu…
Browse files Browse the repository at this point in the history
…r event (#1714)
  • Loading branch information
sallerli1 committed Oct 30, 2023
1 parent 0b59f73 commit 7b739aa
Show file tree
Hide file tree
Showing 48 changed files with 605 additions and 224 deletions.
10 changes: 4 additions & 6 deletions packages/components/_private/selector/src/Selector.tsx
Expand Up @@ -39,7 +39,7 @@ export default defineComponent({
})
const mergedSize = useFormSize(props, props.config)
const mergedSuffix = computed(() => {
return props.suffix ?? (mergedSearchable.value && isFocused.value ? 'search' : props.config.suffix)
return props.suffix ?? (mergedSearchable.value && props.focused ? 'search' : props.config.suffix)
})
const showPlaceholder = computed(() => {
return props.value.length === 0 && !isComposing.value && !inputValue.value
Expand All @@ -50,7 +50,6 @@ export default defineComponent({
inputRef,
inputValue,
isComposing,
isFocused,
blur,
focus,
handleCompositionStart,
Expand All @@ -77,7 +76,7 @@ export default defineComponent({
[`${prefixCls}-borderless`]: borderless,
[`${prefixCls}-clearable`]: mergedClearable.value,
[`${prefixCls}-disabled`]: props.disabled,
[`${prefixCls}-focused`]: isFocused.value,
[`${prefixCls}-focused`]: props.focused,
[`${prefixCls}-multiple`]: multiple,
[`${prefixCls}-opened`]: props.opened,
[`${prefixCls}-readonly`]: props.readonly,
Expand All @@ -100,7 +99,7 @@ export default defineComponent({
}

const { disabled, readonly } = props
if (disabled || readonly || isFocused.value) {
if (disabled || readonly || props.focused) {
evt.preventDefault()
}
}
Expand Down Expand Up @@ -135,7 +134,6 @@ export default defineComponent({
inputRef,
inputValue,
isComposing,
isFocused,
handleCompositionStart,
handleCompositionEnd,
handleInput,
Expand Down Expand Up @@ -246,7 +244,7 @@ export default defineComponent({

return (
<div ref={elementRef} class={classes.value} onClick={handleClick}>
{isFocused.value && !opened && (
{props.focused && !opened && (
<span style={hiddenBoxStyle} aria-live="polite">
{value.join(', ')}
</span>
Expand Down
Expand Up @@ -19,7 +19,6 @@ export interface InputStateContext {
inputRef: Ref<HTMLInputElement | undefined>
inputValue: Ref<string>
isComposing: Ref<boolean>
isFocused: Ref<boolean>
focus: (options?: FocusOptions) => void
blur: () => void
handleCompositionStart: (evt: CompositionEvent) => void
Expand All @@ -33,15 +32,12 @@ export function useInputState(props: SelectorProps, mergedSearchable: ComputedRe
const mirrorRef = ref<HTMLSpanElement>()
const inputValue = ref('')
const isComposing = ref(false)
const isFocused = ref(false)

const handleFocus = (evt: FocusEvent) => {
isFocused.value = true
callEmit(props.onFocus, evt)
}

const handleBlur = (evt: FocusEvent) => {
isFocused.value = false
callEmit(props.onBlur, evt)
}

Expand Down Expand Up @@ -120,7 +116,6 @@ export function useInputState(props: SelectorProps, mergedSearchable: ComputedRe
mirrorRef,
inputValue,
isComposing,
isFocused,
handleCompositionStart,
handleCompositionEnd,
handleInput,
Expand Down
3 changes: 1 addition & 2 deletions packages/components/_private/selector/src/contents/Input.tsx
Expand Up @@ -18,15 +18,14 @@ export default defineComponent({
mirrorRef,
inputRef,
inputValue,
isFocused,
handleCompositionStart,
handleCompositionEnd,
handleInput,
handleEnterDown,
} = inject(selectorToken)!

const inputReadonly = computed(
() => props.readonly || !isFocused.value || !(props.allowInput || mergedSearchable.value),
() => props.readonly || !props.focused || !(props.allowInput || mergedSearchable.value),
)
const innerStyle = computed(() => {
return { opacity: inputReadonly.value ? 0 : undefined }
Expand Down
1 change: 0 additions & 1 deletion packages/components/_private/selector/src/token.ts
Expand Up @@ -18,7 +18,6 @@ export interface SelectorContext {
inputRef: Ref<HTMLInputElement | undefined>
inputValue: Ref<string>
isComposing: Ref<boolean>
isFocused: Ref<boolean>
handleCompositionStart: (evt: CompositionEvent) => void
handleCompositionEnd: (evt: CompositionEvent) => void
handleInput: (evt: Event) => void
Expand Down
1 change: 1 addition & 0 deletions packages/components/_private/selector/src/types.ts
Expand Up @@ -25,6 +25,7 @@ export const selectorProps = {
dataSource: { type: Array, required: true },
defaultLabelSlotName: { type: String, default: undefined },
disabled: { type: Boolean, required: true },
focused: { type: Boolean, required: true },
maxLabel: { type: [Number, String] as PropType<number | 'responsive'>, required: true },
multiple: { type: Boolean, required: true },
opened: { type: Boolean, required: true },
Expand Down
@@ -1,7 +1,7 @@
// Vitest Snapshot v1

exports[`Trigger > render work 1`] = `
"<div class=\\"ix-trigger\\">
"<div class=\\"ix-trigger\\" tabindex=\\"-1\\">
<!---->
<!---->
<!---->
Expand Down
19 changes: 18 additions & 1 deletion packages/components/_private/trigger/src/Trigger.tsx
Expand Up @@ -59,6 +59,16 @@ export default defineComponent({

callEmit(props.onClick, evt)
}
const handleMouseDown = (evt: MouseEvent) => {
if (evt.target instanceof HTMLInputElement) {
return
}

const { disabled, readonly } = props
if (disabled || readonly || props.focused) {
evt.preventDefault()
}
}

const handleKeyDown = (evt: KeyboardEvent) => {
if (props.disabled) {
Expand Down Expand Up @@ -100,7 +110,14 @@ export default defineComponent({
}

return () => (
<div ref={triggerRef} class={classes.value} onClick={handleClick} onKeydown={handleKeyDown}>
<div
ref={triggerRef}
class={classes.value}
tabindex={-1}
onClick={handleClick}
onMousedown={handleMouseDown}
onKeydown={handleKeyDown}
>
{slots.default?.()}
{renderSuffix()}
{renderClearIcon()}
Expand Down
14 changes: 13 additions & 1 deletion packages/components/cascader/demo/Basic.vue
@@ -1,6 +1,12 @@
<template>
<IxSpace vertical>
<IxCascader v-model:value="fullPathValue" :dataSource="dataSource" @change="onChange" />
<IxCascader
v-model:value="fullPathValue"
:dataSource="dataSource"
@focus="onFocus"
@blur="onBlur"
@change="onChange"
/>
<IxCascader v-model:value="singlePathValue" :dataSource="dataSource" :fullPath="false" @change="onChange" />
</IxSpace>
</template>
Expand All @@ -14,6 +20,12 @@ const fullPathValue = ref(['components', 'general', 'button'])
const singlePathValue = ref('button')
const onChange = console.log
const onFocus = (evt: FocusEvent) => {
console.log('focus', evt)
}
const onBlur = (evt: FocusEvent) => {
console.log('blur', evt)
}
const dataSource: CascaderData[] = [
{
Expand Down
32 changes: 25 additions & 7 deletions packages/components/cascader/src/Cascader.tsx
Expand Up @@ -5,7 +5,7 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { computed, defineComponent, normalizeClass, provide, ref, toRaw, toRef, watch } from 'vue'
import { computed, defineComponent, normalizeClass, onMounted, provide, ref, toRaw, toRef, watch } from 'vue'

import { useAccessorAndControl } from '@idux/cdk/forms'
import { type VKey, callEmit, useState } from '@idux/cdk/utils'
Expand All @@ -15,7 +15,7 @@ import { ɵSelector, type ɵSelectorInstance } from '@idux/components/_private/s
import { useGlobalConfig } from '@idux/components/config'
import { useFormItemRegister, useFormSize, useFormStatus } from '@idux/components/form'
import { ɵUseOverlayState } from '@idux/components/select'
import { useGetDisabled, useGetKey } from '@idux/components/utils'
import { useGetDisabled, useGetKey, useOverlayFocusMonitor } from '@idux/components/utils'

import { useDataSource } from './composables/useDataSource'
import { usePanelProps } from './composables/usePanelProps'
Expand Down Expand Up @@ -84,13 +84,25 @@ export default defineComponent({
clearInput()
})

const handleOverlayClick = () => {
const handleOverlayMousedown = () => {
if (props.searchable !== 'overlay') {
focus()
setTimeout(focus)
}
}

const handleBlur = () => accessor.markAsBlurred()
const onFocus = (evt: FocusEvent) => {
callEmit(props.onFocus, evt)
}
const onBlur = (evt: FocusEvent) => {
accessor.markAsBlurred()
setOverlayOpened(false)
callEmit(props.onBlur, evt)
}
const { focused, handleFocus, handleBlur, bindOverlayMonitor } = useOverlayFocusMonitor(onFocus, onBlur)
onMounted(() => {
bindOverlayMonitor(overlayRef, overlayOpened)
})

const handleItemRemove = (key: VKey) => {
focus()
selectedStateContext.handleSelect(key)
Expand Down Expand Up @@ -129,6 +141,7 @@ export default defineComponent({
config={config}
dataSource={selectedData.value}
disabled={accessor.disabled}
focused={focused.value}
maxLabel={props.maxLabel}
multiple={props.multiple}
opened={overlayOpened.value}
Expand All @@ -139,6 +152,7 @@ export default defineComponent({
status={mergedStatus.value}
suffix={props.suffix}
value={resolvedSelectedKeys.value}
onFocus={handleFocus}
onBlur={handleBlur}
onClear={handleClear}
onInputValueChange={setInputValue}
Expand Down Expand Up @@ -190,13 +204,17 @@ export default defineComponent({
)
}

return <div onClick={handleOverlayClick}>{overlayRender ? overlayRender(children) : children}</div>
return (
<div tabindex={-1} onMousedown={handleOverlayMousedown}>
{overlayRender ? overlayRender(children) : children}
</div>
)
}

return () => {
const overlayProps = {
class: overlayClasses.value,
clickOutside: true,
clickOutside: false,
container: props.overlayContainer ?? config.overlayContainer,
containerFallback: `.${mergedPrefixCls.value}-overlay-container`,
disabled: accessor.disabled || props.readonly,
Expand Down
2 changes: 2 additions & 0 deletions packages/components/cascader/src/types.ts
Expand Up @@ -120,6 +120,8 @@ export const cascaderProps = {
'onUpdate:open': [Function, Array] as PropType<MaybeArray<(opened: boolean) => void>>,
onChange: [Function, Array] as PropType<MaybeArray<(value: any, oldValue: any) => void>>,
onClear: [Function, Array] as PropType<MaybeArray<(evt: Event) => void>>,
onFocus: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>>,
onBlur: [Function, Array] as PropType<MaybeArray<(evt: FocusEvent) => void>>,
onExpand: [Function, Array] as PropType<MaybeArray<(expanded: boolean, data: CascaderData) => void>>,
onExpandedChange: [Function, Array] as PropType<MaybeArray<(expendedKeys: any[], data: CascaderData[]) => void>>,
onLoaded: [Function, Array] as PropType<MaybeArray<(loadedKeys: any[], data: CascaderData) => void>>,
Expand Down
@@ -1,7 +1,7 @@
// Vitest Snapshot v1

exports[`DatePicker > render work 1`] = `
"<div class=\\"ix-date-picker ix-trigger ix-trigger-md ix-trigger-readonly\\">
"<div class=\\"ix-date-picker ix-trigger ix-trigger-md\\" tabindex=\\"-1\\">
<div class=\\"ix-date-picker-input\\"><input class=\\"ix-date-picker-input-inner\\" autocomplete=\\"off\\" placeholder=\\"请选择日期\\" readonly=\\"\\" size=\\"12\\"></div>
<div class=\\"ix-trigger-suffix\\"><i class=\\"ix-icon ix-icon-calendar\\" role=\\"img\\" aria-label=\\"calendar\\"></i></div>
<!---->
Expand Down
@@ -1,7 +1,7 @@
// Vitest Snapshot v1

exports[`DateRangePicker > render work 1`] = `
"<div class=\\"ix-date-range-picker ix-trigger ix-trigger-md ix-trigger-readonly\\">
"<div class=\\"ix-date-range-picker ix-trigger ix-trigger-md\\" tabindex=\\"-1\\">
<div class=\\"ix-date-range-picker-input\\"><input class=\\"ix-date-range-picker-input-inner\\" autocomplete=\\"off\\" placeholder=\\"开始日期\\" readonly=\\"\\" size=\\"12\\"><span class=\\"ix-date-range-picker-input-separator\\">至</span><input class=\\"ix-date-range-picker-input-inner\\" autocomplete=\\"off\\" placeholder=\\"结束日期\\" readonly=\\"\\" size=\\"12\\"></div>
<div class=\\"ix-trigger-suffix\\"><i class=\\"ix-icon ix-icon-calendar\\" role=\\"img\\" aria-label=\\"calendar\\"></i></div>
<!---->
Expand Down
15 changes: 14 additions & 1 deletion packages/components/date-picker/demo/AllowInput.vue
Expand Up @@ -8,7 +8,13 @@
</IxRadioGroup>
</IxSpace>
<IxSpace vertical>
<IxDatePicker v-model:value="dateValue" :allow-input="allowInput" clearable></IxDatePicker>
<IxDatePicker
v-model:value="dateValue"
:allow-input="allowInput"
clearable
@focus="onFocus"
@blur="onBlur"
></IxDatePicker>
<IxDatePicker v-model:value="weekValue" :allow-input="allowInput" type="week" clearable></IxDatePicker>
<IxDatePicker v-model:value="monthValue" :allow-input="allowInput" type="month" clearable></IxDatePicker>
<IxDatePicker v-model:value="quarterValue" :allow-input="allowInput" type="quarter" clearable></IxDatePicker>
Expand All @@ -27,4 +33,11 @@ const quarterValue = ref('2022-Q1')
const yearValue = ref('2022')
const allowInput = ref(true)
const onFocus = (evt: FocusEvent) => {
console.log('focus', evt)
}
const onBlur = (evt: FocusEvent) => {
console.log('blur', evt)
}
</script>

0 comments on commit 7b739aa

Please sign in to comment.