From 8e6dca41bdb3806c55d58c9c4cfd81dbd58c2fe7 Mon Sep 17 00:00:00 2001 From: katashin Date: Thu, 29 Nov 2018 21:17:41 +0800 Subject: [PATCH] feat: go to previous style form by pressing shift+tab (#74) --- src/view/components/StyleDeclaration.vue | 10 ++++-- src/view/components/StyleInformation.vue | 33 +++++++++++++++++-- src/view/components/StyleValue.vue | 7 +++- tests/view/StyleInformation.spec.ts | 30 +++++++++++++++++ tests/view/StyleValue/basic.spec.ts | 31 +++++++++++++++-- .../StyleInformation.spec.ts.snap | 15 +++++++++ 6 files changed, 118 insertions(+), 8 deletions(-) diff --git a/src/view/components/StyleDeclaration.vue b/src/view/components/StyleDeclaration.vue index ef51b46..a9d43d0 100644 --- a/src/view/components/StyleDeclaration.vue +++ b/src/view/components/StyleDeclaration.vue @@ -62,7 +62,10 @@ export default Vue.extend({ this.$emit('update:value', value) }, - finishInputProp(rawProp: string, meta: { reason: string }): void { + finishInputProp( + rawProp: string, + meta: { reason: string; shiftKey: boolean } + ): void { this.$emit('input-end:prop', meta) const prop = rawProp.trim() @@ -73,7 +76,10 @@ export default Vue.extend({ } }, - finishInputValue(rawValue: string, meta: { reason: string }): void { + finishInputValue( + rawValue: string, + meta: { reason: string; shiftKey: boolean } + ): void { this.$emit('input-end:value', meta) const value = rawValue.trim() diff --git a/src/view/components/StyleInformation.vue b/src/view/components/StyleInformation.vue index fa1e527..9ecf7b4 100644 --- a/src/view/components/StyleInformation.vue +++ b/src/view/components/StyleInformation.vue @@ -156,11 +156,14 @@ export default Vue.extend({ rule: number, decl: number, type: string, - meta: { reason: string } + meta: { reason: string; shiftKey: boolean } ): void { // If the user end the input by pressing enter or tab key, // focus on the next form. - if (meta.reason === 'enter' || meta.reason === 'tab') { + // If it is shift+tab, focus on the prev form. + if (meta.reason === 'tab' && meta.shiftKey) { + this.focusOnPrevForm(rule, decl, type) + } else if (meta.reason === 'enter' || meta.reason === 'tab') { this.focusOnNextForm(rule, decl, type) } @@ -200,6 +203,32 @@ export default Vue.extend({ } }, + focusOnPrevForm(rule: number, decl: number, type: string): void { + if (type === 'value') { + this.autoFocusTarget = { + rule, + declaration: decl, + type: 'prop' + } + return + } + + if (type === 'prop') { + const nextDecl = decl - 1 + if (nextDecl >= 0) { + this.autoFocusTarget = { + rule, + declaration: nextDecl, + type: 'value' + } + return + } + + // Focus across another rule's declaration is not supported yet + this.autoFocusTarget = undefined + } + }, + shouldFocusFor(rule: number, decl: number): 'prop' | 'value' | null { const f = this.autoFocusTarget return f && f.rule === rule && f.declaration === decl ? f.type : null diff --git a/src/view/components/StyleValue.vue b/src/view/components/StyleValue.vue index 8581f14..1147f52 100644 --- a/src/view/components/StyleValue.vue +++ b/src/view/components/StyleValue.vue @@ -81,7 +81,12 @@ export default Vue.extend({ this.editing = false const el = event.currentTarget as HTMLDivElement - this.$emit('input-end', el.textContent, { reason }) + const anyEvent: any = event + + this.$emit('input-end', el.textContent, { + reason, + shiftKey: !!anyEvent.shiftKey + }) } }, diff --git a/tests/view/StyleInformation.spec.ts b/tests/view/StyleInformation.spec.ts index 049e679..df6a8ee 100644 --- a/tests/view/StyleInformation.spec.ts +++ b/tests/view/StyleInformation.spec.ts @@ -76,6 +76,16 @@ describe('StyleInformation', () => { expect(toDeclarationHtml(wrapper)).toMatchSnapshot() }) + it('moves from value to prop', () => { + const wrapper = create() + wrapper + .findAll(StyleDeclaration) + .at(0) + .vm.$emit('input-end:value', { reason: 'tab', shiftKey: true }) + + expect(toDeclarationHtml(wrapper)).toMatchSnapshot() + }) + it('moves from value to next prop value', () => { const wrapper = create() wrapper @@ -86,6 +96,26 @@ describe('StyleInformation', () => { expect(toDeclarationHtml(wrapper)).toMatchSnapshot() }) + it('moves from prop to prev declaration value', () => { + const wrapper = create() + wrapper + .findAll(StyleDeclaration) + .at(1) + .vm.$emit('input-end:prop', { reason: 'tab', shiftKey: true }) + + expect(toDeclarationHtml(wrapper)).toMatchSnapshot() + }) + + it('removes focus if it cannot go back', () => { + const wrapper = create() + wrapper + .findAll(StyleDeclaration) + .at(0) + .vm.$emit('input-end:prop', { reason: 'tab', shiftKey: true }) + + expect(toDeclarationHtml(wrapper)).toMatchSnapshot() + }) + it('adds a new declaration and moves focus to it', () => { const wrapper = create() wrapper diff --git a/tests/view/StyleValue/basic.spec.ts b/tests/view/StyleValue/basic.spec.ts index 4b4f897..85a4a54 100644 --- a/tests/view/StyleValue/basic.spec.ts +++ b/tests/view/StyleValue/basic.spec.ts @@ -74,7 +74,7 @@ describe('StyleValue basic', () => { expect(wrapper.attributes()!.contenteditable).not.toBe('true') expect(wrapper.emitted('input-end')[0]).toEqual([ '20px', - { reason: 'blur' } + { reason: 'blur', shiftKey: false } ]) }) @@ -95,7 +95,7 @@ describe('StyleValue basic', () => { expect(wrapper.attributes()!.contenteditable).not.toBe('true') expect(wrapper.emitted('input-end')[0]).toEqual([ '20px', - { reason: 'enter' } + { reason: 'enter', shiftKey: false } ]) }) @@ -114,7 +114,32 @@ describe('StyleValue basic', () => { key: 'Tab' }) expect(wrapper.attributes()!.contenteditable).not.toBe('true') - expect(wrapper.emitted('input-end')[0]).toEqual(['20px', { reason: 'tab' }]) + expect(wrapper.emitted('input-end')[0]).toEqual([ + '20px', + { reason: 'tab', shiftKey: false } + ]) + }) + + it('should include shift key state when end editing', async () => { + const wrapper = mount(StyleValue, { + propsData: { + value: '20px' + } + }) + wrapper.trigger('click') + expect(wrapper.attributes()!.contenteditable).toBe('true') + + await wrapper.vm.$nextTick() + + wrapper.trigger('keydown', { + key: 'Tab', + shiftKey: true + }) + expect(wrapper.attributes()!.contenteditable).not.toBe('true') + expect(wrapper.emitted('input-end')[0]).toEqual([ + '20px', + { reason: 'tab', shiftKey: true } + ]) }) it('should update editing content when prop is updated', async () => { diff --git a/tests/view/__snapshots__/StyleInformation.spec.ts.snap b/tests/view/__snapshots__/StyleInformation.spec.ts.snap index d1a4f5e..29d4589 100644 --- a/tests/view/__snapshots__/StyleInformation.spec.ts.snap +++ b/tests/view/__snapshots__/StyleInformation.spec.ts.snap @@ -29,6 +29,11 @@ exports[`StyleInformation moving focus does not move focus if editing is ended b
`; +exports[`StyleInformation moving focus moves from prop to prev declaration value 1`] = ` +
+
+`; + exports[`StyleInformation moving focus moves from prop to value 1`] = `
@@ -38,3 +43,13 @@ exports[`StyleInformation moving focus moves from value to next prop value 1`] =
`; + +exports[`StyleInformation moving focus moves from value to prop 1`] = ` +
+
+`; + +exports[`StyleInformation moving focus removes focus if it cannot go back 1`] = ` +
+
+`;