From 7a437662b6aa6be1e6355a76c2e08b780c68d1d5 Mon Sep 17 00:00:00 2001 From: michgeek Date: Sat, 28 Sep 2024 15:09:10 +0200 Subject: [PATCH 1/2] Allow text selection within `input` tag using `Shift + Home` or `Shift + End` --- .../radix-vue/src/Combobox/ComboboxInput.vue | 8 +++- .../radix-vue/src/TagsInput/TagsInput.test.ts | 38 +++++++++++++++++++ .../radix-vue/src/TagsInput/TagsInputRoot.vue | 4 ++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/radix-vue/src/Combobox/ComboboxInput.vue b/packages/radix-vue/src/Combobox/ComboboxInput.vue index 8d3f4b68f..7300ce9d1 100644 --- a/packages/radix-vue/src/Combobox/ComboboxInput.vue +++ b/packages/radix-vue/src/Combobox/ComboboxInput.vue @@ -55,6 +55,12 @@ function handleKeyDown(ev: KeyboardEvent) { } function handleHomeEnd(ev: KeyboardEvent) { + // allow text selection while maintaining `shift` key + if(ev.shiftKey) + return + + ev.preventDefault() + if (!rootContext.open.value) return rootContext.onInputNavigation(ev.key === 'Home' ? 'home' : 'end') @@ -87,7 +93,7 @@ function handleInput(event: Event) { @input="handleInput" @keydown.down.up.prevent="handleKeyDown" @keydown.enter="rootContext.onInputEnter" - @keydown.home.end.prevent="handleHomeEnd" + @keydown.home.end="handleHomeEnd" > diff --git a/packages/radix-vue/src/TagsInput/TagsInput.test.ts b/packages/radix-vue/src/TagsInput/TagsInput.test.ts index 2f347e992..9926b36af 100644 --- a/packages/radix-vue/src/TagsInput/TagsInput.test.ts +++ b/packages/radix-vue/src/TagsInput/TagsInput.test.ts @@ -72,6 +72,15 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 2].attributes('data-state')).toBe('active') }) + it('should not select the previous tag when press Shift + ArrowLeft', async () => { + await input.trigger('keydown', { + key: 'ArrowLeft', + shiftKey: true, + }) + expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') + expect(tags[tags.length - 2].attributes('data-state')).not.toBe('active') + }) + it('should select the first item when press Home', async () => { await input.trigger('keydown', { key: 'Home', @@ -80,6 +89,15 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('inactive') }) + it('should not select the first item when press Shift + Home', async () => { + await input.trigger('keydown', { + key: 'Home', + shiftKey: true, + }) + expect(tags[0].attributes('data-state')).not.toBe('active'); + expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') + }) + it('should select the last item when press End', async () => { await input.trigger('keydown', { key: 'Home', @@ -91,6 +109,18 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('active') }) + it('should not select the last item when press Shift + End', async () => { + await input.trigger('keydown', { + key: 'Home', + }) + await input.trigger('keydown', { + key: 'End', + shiftKey: true, + }) + expect(tags[0].attributes('data-state')).not.toBe('inactive'); + expect(tags[tags.length - 1].attributes('data-state')).not.toBe('active'); + }) + it('should remove active state when press ArrowRight', async () => { await input.trigger('keydown', { key: 'ArrowRight', @@ -98,6 +128,14 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('inactive') }) + it('should not remove active state when press Shift + ArrowRight', async () => { + await input.trigger('keydown', { + key: 'ArrowRight', + shiftKey: true, + }) + expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') + }) + describe('after pressing on Backspace', () => { let prevTag: DOMWrapper beforeEach(async () => { diff --git a/packages/radix-vue/src/TagsInput/TagsInputRoot.vue b/packages/radix-vue/src/TagsInput/TagsInputRoot.vue index 48e545110..f91176150 100644 --- a/packages/radix-vue/src/TagsInput/TagsInputRoot.vue +++ b/packages/radix-vue/src/TagsInput/TagsInputRoot.vue @@ -172,6 +172,10 @@ provideTagsInputRootContext({ case 'End': case 'ArrowRight': case 'ArrowLeft': { + // allow text selection while maintaining `shift` key + if(event.shiftKey) + break + const isArrowRight = (event.key === 'ArrowRight' && dir.value === 'ltr') || (event.key === 'ArrowLeft' && dir.value === 'rtl') const isArrowLeft = !isArrowRight // only focus on tags when cursor is at the first position From a99e8e315806987c724ed1a3c4ba265789a27326 Mon Sep 17 00:00:00 2001 From: michgeek Date: Wed, 2 Oct 2024 21:50:44 +0200 Subject: [PATCH 2/2] Allow default input behavior when there's a value --- .../radix-vue/src/Combobox/ComboboxInput.vue | 4 +- .../radix-vue/src/TagsInput/TagsInput.test.ts | 38 ------------------- .../radix-vue/src/TagsInput/TagsInputRoot.vue | 10 ++--- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/packages/radix-vue/src/Combobox/ComboboxInput.vue b/packages/radix-vue/src/Combobox/ComboboxInput.vue index 7300ce9d1..a9e0b4f46 100644 --- a/packages/radix-vue/src/Combobox/ComboboxInput.vue +++ b/packages/radix-vue/src/Combobox/ComboboxInput.vue @@ -55,8 +55,8 @@ function handleKeyDown(ev: KeyboardEvent) { } function handleHomeEnd(ev: KeyboardEvent) { - // allow text selection while maintaining `shift` key - if(ev.shiftKey) + // allow default input behavior when element has a value + if(ev.target.value?.length > 0) return ev.preventDefault() diff --git a/packages/radix-vue/src/TagsInput/TagsInput.test.ts b/packages/radix-vue/src/TagsInput/TagsInput.test.ts index 9926b36af..2f347e992 100644 --- a/packages/radix-vue/src/TagsInput/TagsInput.test.ts +++ b/packages/radix-vue/src/TagsInput/TagsInput.test.ts @@ -72,15 +72,6 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 2].attributes('data-state')).toBe('active') }) - it('should not select the previous tag when press Shift + ArrowLeft', async () => { - await input.trigger('keydown', { - key: 'ArrowLeft', - shiftKey: true, - }) - expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') - expect(tags[tags.length - 2].attributes('data-state')).not.toBe('active') - }) - it('should select the first item when press Home', async () => { await input.trigger('keydown', { key: 'Home', @@ -89,15 +80,6 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('inactive') }) - it('should not select the first item when press Shift + Home', async () => { - await input.trigger('keydown', { - key: 'Home', - shiftKey: true, - }) - expect(tags[0].attributes('data-state')).not.toBe('active'); - expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') - }) - it('should select the last item when press End', async () => { await input.trigger('keydown', { key: 'Home', @@ -109,18 +91,6 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('active') }) - it('should not select the last item when press Shift + End', async () => { - await input.trigger('keydown', { - key: 'Home', - }) - await input.trigger('keydown', { - key: 'End', - shiftKey: true, - }) - expect(tags[0].attributes('data-state')).not.toBe('inactive'); - expect(tags[tags.length - 1].attributes('data-state')).not.toBe('active'); - }) - it('should remove active state when press ArrowRight', async () => { await input.trigger('keydown', { key: 'ArrowRight', @@ -128,14 +98,6 @@ describe('given default TagsInput', () => { expect(tags[tags.length - 1].attributes('data-state')).toBe('inactive') }) - it('should not remove active state when press Shift + ArrowRight', async () => { - await input.trigger('keydown', { - key: 'ArrowRight', - shiftKey: true, - }) - expect(tags[tags.length - 1].attributes('data-state')).not.toBe('inactive') - }) - describe('after pressing on Backspace', () => { let prevTag: DOMWrapper beforeEach(async () => { diff --git a/packages/radix-vue/src/TagsInput/TagsInputRoot.vue b/packages/radix-vue/src/TagsInput/TagsInputRoot.vue index f91176150..19eb70013 100644 --- a/packages/radix-vue/src/TagsInput/TagsInputRoot.vue +++ b/packages/radix-vue/src/TagsInput/TagsInputRoot.vue @@ -172,14 +172,12 @@ provideTagsInputRootContext({ case 'End': case 'ArrowRight': case 'ArrowLeft': { - // allow text selection while maintaining `shift` key - if(event.shiftKey) - break - const isArrowRight = (event.key === 'ArrowRight' && dir.value === 'ltr') || (event.key === 'ArrowLeft' && dir.value === 'rtl') const isArrowLeft = !isArrowRight - // only focus on tags when cursor is at the first position - if (target.selectionStart !== 0 || target.selectionEnd !== 0) + + // prevent tag focus when there's something in the input + // the user must clear the input if he needs to clear already added tags + if (target.value.length > 0) break // if you press ArrowLeft, then we last tag