Skip to content

Commit

Permalink
feat(comp:private/input): add input component (#657)
Browse files Browse the repository at this point in the history
this input is a pure style component

BREAKING CHANGE: IxInput and IxInputNumber rebuild with _private/input

fix #582

feat(comp:input,input-number): rebuild with _private/input
  • Loading branch information
danranVm committed Dec 23, 2021
1 parent e9f85e4 commit efbfda1
Show file tree
Hide file tree
Showing 39 changed files with 1,059 additions and 726 deletions.
7 changes: 7 additions & 0 deletions packages/cdk/a11y/docs/Index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export interface FocusMonitor {
* @param options 可用于配置焦点行为的参数
*/
focusVia(element: ElementType, origin: FocusOrigin, options?: FocusOptions): void

/**
* 让元素失去焦点.
*
* @param element 要失去焦点的元素.
*/
blurVia: (element: ElementType) => void
}

/**
Expand Down
29 changes: 28 additions & 1 deletion packages/cdk/a11y/src/focusMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ export interface FocusMonitor {
* @param options Options that can be used to configure the focus behavior.
*/
focusVia(element: ElementType, origin: FocusOrigin, options?: FocusOptions): void

/**
* Blur the element.
*
* @param element Element to blur.
*/
blurVia: (element: ElementType) => void
}

/** Monitors mouse and keyboard events to determine the cause of focus events. */
Expand Down Expand Up @@ -293,6 +300,26 @@ export function useFocusMonitor(options?: FocusMonitorOptions): FocusMonitor {
}
}

/**
* Blur the element.
*
* @param element Element to blur.
*/
function blurVia(element: ElementType): void {
const nativeElement = convertElement(element)
if (!nativeElement) {
return
}

const focusedElement = _getDocument().activeElement
// If the element is focused already, calling `focus` again won't trigger the event listener
// which means that the focus classes won't be updated. If that's the case, update the classes
// directly without waiting for an event.
if (nativeElement === focusedElement && typeof nativeElement.blur === 'function') {
nativeElement.blur()
}
}

/** Access injected document if available or fallback to global document reference */
function _getDocument(): Document {
return document
Expand Down Expand Up @@ -539,7 +566,7 @@ export function useFocusMonitor(options?: FocusMonitorOptions): FocusMonitor {

onScopeDispose(() => _elementInfo.forEach((_info, element) => stopMonitoring(element)))

return { monitor, stopMonitoring, focusVia }
return { monitor, stopMonitoring, focusVia, blurVia }
}

export const useSharedFocusMonitor = createSharedComposable(() => useFocusMonitor())
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Input render work 1`] = `"<input class=\\"ix-input ix-input-md\\">"`;
exports[`Input render work 2`] = `"<span class=\\"ix-input ix-input-md ix-input-with-addon-after\\"><!----><input class=\\"ix-input-inner\\"><span class=\\"ix-input-addon\\">addonAfter</span></span>"`;
exports[`Input render work 3`] = `
"<span class=\\"ix-input ix-input-md ix-input-with-suffix\\"><!----><input class=\\"ix-input-inner\\"><span class=\\"ix-input-suffix\\"><i class=\\"ix-icon ix-icon-setting\\" role=\\"img\\" aria-label=\\"setting\\"></i></span>
<!----></span>"
`;
exports[`Input render work 4`] = `
"<span class=\\"ix-input ix-input-md ix-input-with-addon-after ix-input-with-suffix\\"><!----><span class=\\"ix-input-wrapper\\"><!----><input class=\\"ix-input-inner\\"><span class=\\"ix-input-suffix\\"><i class=\\"ix-icon ix-icon-setting\\" role=\\"img\\" aria-label=\\"setting\\"></i></span>
<!----></span><span class=\\"ix-input-addon\\">addonAfter</span></span>"
`;
196 changes: 196 additions & 0 deletions packages/components/_private/input/__tests__/input.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { MountingOptions, mount } from '@vue/test-utils'

import { renderWork } from '@tests'

import Input from '../src/Input'
import { InputProps } from '../src/types'

describe('Input', () => {
const InputMount = (options?: MountingOptions<Partial<InputProps>>) => {
const { props, ...rest } = (options || {}) as MountingOptions<InputProps>
return mount(Input, { props: { size: 'md', ...props }, ...rest })
}

renderWork<InputProps>(Input, {
props: { size: 'md' },
})

renderWork<InputProps>(Input, {
props: { size: 'md', addonAfter: 'addonAfter' },
})

renderWork<InputProps>(Input, {
props: { size: 'md', suffix: 'setting' },
})

renderWork<InputProps>(Input, {
props: { size: 'md', addonAfter: 'addonAfter', suffix: 'setting' },
})

test('addonAfter and addonBefore work', async () => {
const wrapper = InputMount({ props: { addonAfter: 'addonAfter', addonBefore: 'addonBefore' } })

expect(wrapper.classes()).toContain('ix-input-with-addon-after')
expect(wrapper.classes()).toContain('ix-input-with-addon-before')

const addons = wrapper.findAll('.ix-input-addon')

expect(addons[0].text()).toBe('addonBefore')
expect(addons[1].text()).toBe('addonAfter')

await wrapper.setProps({ addonAfter: 'addonAfter change' })

expect(addons[1].text()).toBe('addonAfter change')

await wrapper.setProps({ addonBefore: 'addonBefore change' })

expect(addons[0].text()).toBe('addonBefore change')

await wrapper.setProps({ addonAfter: '' })

expect(wrapper.classes()).not.toContain('ix-input-with-addon-after')
expect(wrapper.findAll('.ix-input-addon').length).toBe(1)

await wrapper.setProps({ addonBefore: '' })

expect(wrapper.classes()).not.toContain('ix-input-with-addon-before')
expect(wrapper.findAll('.ix-input-addon').length).toBe(0)
})

test('addonAfter and addonBefore slots work', async () => {
const wrapper = InputMount({
props: { addonAfter: 'addonAfter', addonBefore: 'addonBefore' },
slots: { addonAfter: 'addonAfter slot', addonBefore: 'addonBefore slot' },
})

expect(wrapper.classes()).toContain('ix-input-with-addon-after')
expect(wrapper.classes()).toContain('ix-input-with-addon-before')

const addons = wrapper.findAll('.ix-input-addon')

expect(addons[0].text()).toBe('addonBefore slot')
expect(addons[1].text()).toBe('addonAfter slot')
})

test('borderless work', async () => {
const wrapper = InputMount({ props: { borderless: true } })

expect(wrapper.classes()).toContain('ix-input-borderless')

await wrapper.setProps({ borderless: false })

expect(wrapper.classes()).not.toContain('ix-input-borderless')
})

test('clearable work', async () => {
const onClear = jest.fn()
const wrapper = InputMount({ props: { clearIcon: 'close', clearable: true, onClear } })

expect(wrapper.find('.ix-input-clear').exists()).toBe(true)

await wrapper.setProps({ clearVisible: true })

expect(wrapper.find('.ix-input-clear').classes()).toContain('visible')

await wrapper.find('.ix-input-clear').trigger('click')

expect(onClear).toBeCalled()

await wrapper.setProps({ clearVisible: false })

expect(wrapper.find('.ix-input-clear').classes()).not.toContain('visible')

await wrapper.setProps({ clearable: false })

expect(wrapper.find('.ix-input-clear').exists()).toBe(false)
})

test('disabled work', async () => {
const onFocus = jest.fn()
const onBlur = jest.fn()

const wrapper = InputMount({ props: { disabled: true, onFocus, onBlur } })
await wrapper.find('input').trigger('focus')

expect(wrapper.classes()).toContain('ix-input-disabled')
expect(onFocus).not.toBeCalled()

await wrapper.find('input').trigger('blur')

expect(onBlur).not.toBeCalled()

await wrapper.setProps({ disabled: false })
await wrapper.find('input').trigger('focus')

expect(wrapper.classes()).not.toContain('ix-input-disabled')
expect(onFocus).toBeCalled()

await wrapper.find('input').trigger('blur')

expect(onBlur).toBeCalled()
})

test('focused work', async () => {
const wrapper = InputMount({ props: { focused: true } })

expect(wrapper.classes()).toContain('ix-input-focused')

await wrapper.setProps({ focused: false })

expect(wrapper.classes()).not.toContain('ix-input-focused')
})

test('suffix and prefix work', async () => {
const wrapper = InputMount({ props: { suffix: 'up', prefix: 'down' } })

const suffix = wrapper.find('.ix-input-suffix')
const prefix = wrapper.find('.ix-input-prefix')

expect(suffix.find('.ix-icon-up').exists()).toBe(true)
expect(prefix.find('.ix-icon-down').exists()).toBe(true)

await wrapper.setProps({ suffix: 'left' })

expect(suffix.find('.ix-icon-left').exists()).toBe(true)

await wrapper.setProps({ prefix: 'right' })

expect(prefix.find('.ix-icon-right').exists()).toBe(true)

await wrapper.setProps({ suffix: '' })

expect(wrapper.find('.ix-input-suffix').exists()).toBe(false)

await wrapper.setProps({ prefix: '' })

expect(wrapper.find('.ix-input-prefix').exists()).toBe(false)
})

test('suffix and prefix slots work', async () => {
const wrapper = InputMount({
props: { suffix: 'up', prefix: 'down' },
slots: { suffix: 'suffix slot', prefix: 'prefix slot' },
})

const suffix = wrapper.find('.ix-input-suffix')
const prefix = wrapper.find('.ix-input-prefix')

expect(suffix.find('.ix-icon-up').exists()).toBe(false)
expect(prefix.find('.ix-icon-down').exists()).toBe(false)

expect(suffix.text()).toBe('suffix slot')
expect(prefix.text()).toBe('prefix slot')
})

test('size work', async () => {
const wrapper = InputMount({ props: { size: 'lg' } })

expect(wrapper.classes()).toContain('ix-input-lg')

await wrapper.setProps({ size: 'sm' })
expect(wrapper.classes()).toContain('ix-input-sm')

await wrapper.setProps({ size: 'md' })
expect(wrapper.classes()).toContain('ix-input-md')
})
})
20 changes: 20 additions & 0 deletions packages/components/_private/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @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
*/

import type { InputComponent } from './src/types'

import Input from './src/Input'

const ɵInput = Input as unknown as InputComponent

export { ɵInput }

export type {
InputInstance as ɵInputInstance,
InputComponent as ɵInputComponent,
InputPublicProps as ɵInputProps,
} from './src/types'

0 comments on commit efbfda1

Please sign in to comment.