Skip to content

Commit

Permalink
Merge a606c71 into fdda882
Browse files Browse the repository at this point in the history
  • Loading branch information
sallerli1 committed Jul 25, 2023
2 parents fdda882 + a606c71 commit d64a275
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 232 deletions.
2 changes: 1 addition & 1 deletion packages/pro/search/demo/Custom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
:searchFields="searchFields"
:onChange="onChange"
:onSearch="onSearch"
overlayContainer="demo-pro-search-custom"
overlayContainer=".demo-pro-search-custom"
>
<template #userForm="{ value, setValue, ok }">
<IxSpace class="demo-pro-search-custom-user-form" vertical>
Expand Down
161 changes: 14 additions & 147 deletions packages/pro/search/src/components/SearchItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,8 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import {
type Ref,
computed,
defineComponent,
inject,
normalizeClass,
onMounted,
onUnmounted,
provide,
ref,
watch,
} from 'vue'
import { computed, defineComponent, inject, normalizeClass, provide, ref, watch } from 'vue'

import { useResizeObserver } from '@idux/cdk/resize'
import { getScroll } from '@idux/cdk/scroll'
import { useEventListener } from '@idux/cdk/utils'
import { IxTooltip } from '@idux/components/tooltip'

import Segment from './segment/Segment'
Expand All @@ -29,7 +15,6 @@ import { useSegmentStates } from '../composables/useSegmentStates'
import { proSearchContext, searchItemContext } from '../token'
import { searchItemProps } from '../types'
import { renderIcon } from '../utils/RenderIcon'
import { getBoxSizingData } from '../utils/getBoxsizingData'

export default defineComponent({
props: searchItemProps,
Expand Down Expand Up @@ -85,31 +70,6 @@ export default defineComponent({
() => searchItemName.value + ' ' + segmentRenderDatas.value.map(data => data.input).join(' '),
)

const { wrapperScroll, segmentScrolls } = useSegmentsScroll(segmentsRef, segmentRenderDatas)

// when we move cursor within input elements,
// current cursor position will be scrolled into view,
// so if ther start segment input and the segments wrapper is scrolled to start,
// there is no ellipsis
//
// however the wrapper may not be scrolled to start because inputs may have padding and border,
// and cursor wont be moved outside content
// so padding and border may always be ouside the wrapper view area
const leftSideEllipsis = computed(() => {
const startEl = segmentScrolls.value[0]?.el
const startElBoxSizingData = startEl && getBoxSizingData(startEl)
const offset = startElBoxSizingData ? startElBoxSizingData.paddingLeft + startElBoxSizingData.borderLeft : 0

return wrapperScroll.value.left > offset + 1 || (segmentScrolls.value[0]?.left ?? 0) > 1
})
const rightSideEllipsis = computed(() => {
const endEl = segmentScrolls.value[1]?.el
const endElBoxSizingData = endEl && getBoxSizingData(endEl)
const offset = endElBoxSizingData ? endElBoxSizingData.paddingRight + endElBoxSizingData.borderRight : 0

return wrapperScroll.value.right > offset + 1 || (segmentScrolls.value[1]?.right ?? 0) > 1
})

const handleNameMouseDown = (evt: MouseEvent) => {
evt.stopPropagation()
evt.preventDefault()
Expand Down Expand Up @@ -141,27 +101,19 @@ export default defineComponent({
<span class={`${prefixCls}-name`} onMousedown={handleNameMouseDown}>
{searchItemName.value}
</span>
<span v-show={leftSideEllipsis.value} class={`${prefixCls}-ellipsis-left`} key="__ellisps_left__">
...
</span>
<span ref={segmentsRef} class={`${prefixCls}-segments`} key="__segments__">
{segmentRenderDatas.value.map(segment => (
<Segment
key={segment.name}
v-slots={slots}
v-show={segment.segmentVisible}
itemKey={props.searchItem!.key}
input={segment.input}
value={segment.value}
selectionStart={segment.selectionStart}
disabled={proSearchProps.disabled}
segment={segment}
/>
))}
</span>
<span v-show={rightSideEllipsis.value} class={`${prefixCls}-ellipsis-right`} key="__ellisps_right__">
...
</span>
{segmentRenderDatas.value.map(segment => (
<Segment
key={segment.name}
v-slots={slots}
v-show={segment.segmentVisible}
itemKey={props.searchItem!.key}
input={segment.input}
value={segment.value}
selectionStart={segment.selectionStart}
disabled={proSearchProps.disabled}
segment={segment}
/>
))}
{!proSearchProps.disabled && (
<span
class={`${prefixCls}-close-icon`}
Expand All @@ -177,88 +129,3 @@ export default defineComponent({
}
},
})

interface SegmentScroll {
el: HTMLElement | undefined
left: number
right: number
}
function useSegmentsScroll(
wrapperRef: Ref<HTMLElement | undefined>,
resetTrigger: Ref<unknown>,
): {
wrapperScroll: Ref<SegmentScroll>
segmentScrolls: Ref<SegmentScroll[]>
} {
const wrapperScroll = ref<SegmentScroll>({ left: 0, right: 0, el: wrapperRef.value })
const segmentScrolls = ref<SegmentScroll[]>([])

const getScrolls = (el: HTMLElement) => {
const { scrollLeft } = getScroll(el)
return {
el,
left: scrollLeft,
right: Math.max(el.scrollWidth - scrollLeft - el.offsetWidth, 0),
}
}

let clearListeners: (() => void) | null = null
let calcSize: (() => void) | null = null
const initScrollListeners = () => {
if (!wrapperRef.value) {
return
}

clearListeners?.()
segmentScrolls.value = []

const calcWrapperScroll = () => {
wrapperScroll.value = getScrolls(wrapperRef.value!)
}
const sizeCalculations = [calcWrapperScroll]

const listenerStopHandlers = [useEventListener(wrapperRef.value, 'scroll', calcWrapperScroll)]

const inputs = wrapperRef.value.querySelectorAll('input')
if (!inputs.length) {
return
}

// listen to the start and end input elements' scroll event
// and add its scroll calculation to the overall scroll calculation
;[inputs.item(0), inputs.item(inputs.length - 1)].forEach((inputEl, index) => {
const scrollCalculation = () => {
segmentScrolls.value[index] = getScrolls(inputEl)
}
sizeCalculations.push(scrollCalculation)
listenerStopHandlers.push(useEventListener(inputEl, 'scroll', scrollCalculation))
})

clearListeners = () => listenerStopHandlers.forEach(stop => stop())
calcSize = () => sizeCalculations.forEach(calc => calc())

calcSize()
}

let resizeStop: (() => void) | null = null
onMounted(() => {
// when reset is triggered by comsumer, we re-init the scroll calculations
watch(resetTrigger, initScrollListeners, { immediate: true })

// when wrapper is resized, calculate scrolls
resizeStop = useResizeObserver(wrapperRef, () => {
calcSize?.()
})
})
onUnmounted(() => {
clearListeners?.()
resizeStop?.()
clearListeners = null
calcSize = null
})

return {
wrapperScroll,
segmentScrolls,
}
}
1 change: 1 addition & 0 deletions packages/pro/search/src/components/segment/Segment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export default defineComponent({
class={inputClasses.value}
value={props.input ?? ''}
disabled={props.disabled}
ellipsis={true}
placeholder={props.segment.placeholder}
onInput={handleInput}
onFocus={handleFocus}
Expand Down
98 changes: 94 additions & 4 deletions packages/pro/search/src/components/segment/SegmentInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,22 @@
* found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE
*/

import { type CSSProperties, computed, defineComponent, inject, normalizeClass, onMounted, ref, watch } from 'vue'

import { callEmit, convertCssPixel, useState } from '@idux/cdk/utils'
import {
type CSSProperties,
type Ref,
computed,
defineComponent,
inject,
normalizeClass,
onMounted,
onUnmounted,
ref,
watch,
} from 'vue'

import { useResizeObserver } from '@idux/cdk/resize'
import { getScroll, setScroll } from '@idux/cdk/scroll'
import { callEmit, convertCssPixel, useEventListener, useState } from '@idux/cdk/utils'

import { proSearchContext } from '../../token'
import { type SegmentInputProps, segmentIputProps } from '../../types'
Expand All @@ -19,6 +32,7 @@ export default defineComponent({
setup(props: SegmentInputProps, { attrs, expose }) {
const { mergedPrefixCls } = inject(proSearchContext)!

const segmentWrapperRef = ref<HTMLElement>()
const segmentInputRef = ref<HTMLInputElement>()

const [inputWidth, setInputWidth] = useState(0)
Expand Down Expand Up @@ -60,13 +74,24 @@ export default defineComponent({
})

const { handleInput, handleCompositionStart, handleCompositionEnd } = useInputEvents(props)
const segmentScroll = useSegmentScroll(props, segmentInputRef)

const leftSideEllipsis = computed(() => segmentScroll.value.left > 1)
const rightSideEllipsis = computed(() => segmentScroll.value.right > 1)

return () => {
const prefixCls = `${mergedPrefixCls.value}-segment-input`
const { class: className, style, ...rest } = attrs

return (
<span class={normalizeClass([classes.value, className])}>
<span ref={segmentWrapperRef} class={normalizeClass([classes.value, className])}>
<span
v-show={props.ellipsis && leftSideEllipsis.value}
class={`${prefixCls}-ellipsis-left`}
key="__ellisps_left__"
>
...
</span>
<input
ref={segmentInputRef}
class={`${prefixCls}-inner`}
Expand All @@ -79,6 +104,13 @@ export default defineComponent({
onCompositionend={handleCompositionEnd}
{...rest}
></input>
<span
v-show={props.ellipsis && rightSideEllipsis.value}
class={`${prefixCls}-ellipsis-right`}
key="__ellisps_right__"
>
...
</span>
<MeasureElement onWidthChange={setInputWidth}>{props.value || props.placeholder || ''}</MeasureElement>
</span>
)
Expand Down Expand Up @@ -116,3 +148,61 @@ function useInputEvents(props: SegmentInputProps): InputEventHandlers {
handleCompositionEnd,
}
}

interface SegmentScroll {
left: number
right: number
}

function useSegmentScroll(props: SegmentInputProps, inputRef: Ref<HTMLInputElement | undefined>) {
const [segmentScroll, setSegmentScroll] = useState<SegmentScroll>({ left: 0, right: 0 }, true)
let inputWidth = 0
let oldSegmentScroll: SegmentScroll = { left: 0, right: 0 }

let stopScrollListen: (() => void) | undefined
let stopResizeListen: (() => void) | undefined

const getScrolls = (): SegmentScroll => {
const el = inputRef.value
if (!el) {
return { left: 0, right: 0 }
}

const { scrollLeft } = getScroll(el)
return {
left: scrollLeft,
right: Math.max(el.scrollWidth - scrollLeft - el.offsetWidth, 0),
}
}

const updateScroll = () => {
oldSegmentScroll = segmentScroll.value
setSegmentScroll(getScrolls())
}

onMounted(() => {
inputWidth = inputRef.value?.offsetWidth ?? 0

if (props.ellipsis) {
stopScrollListen = useEventListener(inputRef, 'scroll', updateScroll)
stopResizeListen = useResizeObserver(inputRef, () => {
const width = inputRef.value?.offsetWidth ?? 0
const widthOffset = inputWidth - width
inputWidth = width

if (widthOffset > 0 && segmentScroll.value.left > 0 && oldSegmentScroll.left <= 0) {
setScroll({ scrollLeft: segmentScroll.value.left + widthOffset }, inputRef.value)
} else {
updateScroll()
}
})
}
})

onUnmounted(() => {
stopScrollListen?.()
stopResizeListen?.()
})

return segmentScroll
}
1 change: 1 addition & 0 deletions packages/pro/search/src/components/segment/TempSegment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export default defineComponent({
class={`${mergedPrefixCls.value}-temp-segment-input`}
style={nameInputStyle.value}
value={input.value}
ellipsis={false}
onMousedown={handleMouseDown}
onInput={handleInput}
onKeydown={handleKeyDown}
Expand Down
1 change: 1 addition & 0 deletions packages/pro/search/src/types/segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export type SegmentProps = ExtractInnerPropTypes<typeof segmentProps>
export const segmentIputProps = {
value: String,
disabled: Boolean,
ellipsis: Boolean,
placeholder: String,
onInput: [Function, Array] as PropType<MaybeArray<(input: string) => void>>,
onWidthChange: [Function, Array] as PropType<MaybeArray<(width: number) => void>>,
Expand Down
Loading

0 comments on commit d64a275

Please sign in to comment.