document name input focus
This commit is contained in:
1 parent
92e9f1619f
commit
d59201100b
2 files changed
+44
-1
No files matched your search
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import { PhCheckerboard, PhFilmReel, PhPlus, PhX } from '@phosphor-icons/vue'
|
import { PhCheckerboard, PhFilmReel, PhPlus, PhX } from '@phosphor-icons/vue'
|
||||||
import { useSceneStore } from '@/stores/scene'
|
import { useSceneStore } from '@/stores/scene'
|
||||||
import { confirmDialog, promptDialog, dialogState } from '@/components/ui'
|
import { confirmDialog, promptDialog, dialogState } from '@/components/ui'
|
||||||
@@ -19,6 +19,7 @@ const { projectName, renameProject } = useProjectName()
|
|||||||
const { presets, addPreset, removePreset } = useCanvasPresets()
|
const { presets, addPreset, removePreset } = useCanvasPresets()
|
||||||
|
|
||||||
const rootEl = ref<HTMLElement | null>(null)
|
const rootEl = ref<HTMLElement | null>(null)
|
||||||
|
const nameInput = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
// The drawer is opened only by the topbar project-name button (no tab,
|
// The drawer is opened only by the topbar project-name button (no tab,
|
||||||
// no hover). An outside click or ESC dismisses it. A dialog on top
|
// no hover). An outside click or ESC dismisses it. A dialog on top
|
||||||
@@ -52,6 +53,14 @@ watch(open, (v) => {
|
|||||||
document.addEventListener('pointerdown', onDocPointerDown, true)
|
document.addEventListener('pointerdown', onDocPointerDown, true)
|
||||||
document.addEventListener('keydown', onDocKeyDown)
|
document.addEventListener('keydown', onDocKeyDown)
|
||||||
}, 0)
|
}, 0)
|
||||||
|
// Focus + select the project-name field on open — renaming is
|
||||||
|
// the main reason this drawer exists, so the cursor should land
|
||||||
|
// there without an extra click.
|
||||||
|
void nextTick().then(() => {
|
||||||
|
if (!open.value) return
|
||||||
|
nameInput.value?.focus()
|
||||||
|
nameInput.value?.select()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
document.removeEventListener('pointerdown', onDocPointerDown, true)
|
document.removeEventListener('pointerdown', onDocPointerDown, true)
|
||||||
document.removeEventListener('keydown', onDocKeyDown)
|
document.removeEventListener('keydown', onDocKeyDown)
|
||||||
@@ -327,6 +336,7 @@ async function onRemovePreset(p: CanvasPreset) {
|
|||||||
<label class="name-field">
|
<label class="name-field">
|
||||||
<span class="name-label">{{ t('documentDrawer.projectName') }}</span>
|
<span class="name-label">{{ t('documentDrawer.projectName') }}</span>
|
||||||
<input
|
<input
|
||||||
|
ref="nameInput"
|
||||||
class="name-input"
|
class="name-input"
|
||||||
type="text"
|
type="text"
|
||||||
:value="nameDraft"
|
:value="nameDraft"
|
||||||
|
|||||||
@@ -6074,6 +6074,39 @@ export function registerAllTests(): void {
|
|||||||
handle.unmount()
|
handle.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('opening the drawer moves focus into the project-name input', async () => {
|
||||||
|
// Renaming is the main reason to open this drawer — cursor
|
||||||
|
// should land on the name field so the user can just start
|
||||||
|
// typing. Focus is scheduled for the next tick so Vue has
|
||||||
|
// committed the open class + the input is laid out.
|
||||||
|
makeFreshStore()
|
||||||
|
const { useProjectName } = await import('@/composables/useProjectName')
|
||||||
|
useProjectName().projectName.value = 'Hero'
|
||||||
|
const handle = mountComponent(DocumentDrawer, {})
|
||||||
|
try {
|
||||||
|
await nextTick()
|
||||||
|
const { useDocumentDrawer } = await import(
|
||||||
|
'@/composables/useDocumentDrawer'
|
||||||
|
)
|
||||||
|
useDocumentDrawer().openDrawer()
|
||||||
|
// Wait two ticks: one for `open` to flush, one for the
|
||||||
|
// scheduled focus in nextTick inside the watch.
|
||||||
|
await nextTick()
|
||||||
|
await nextTick()
|
||||||
|
const input = handle.host.querySelector(
|
||||||
|
'.document-drawer .name-input',
|
||||||
|
) as HTMLInputElement
|
||||||
|
expect(input).toBeTruthy()
|
||||||
|
expect(document.activeElement).toBe(input)
|
||||||
|
// Contents pre-selected so the user can overwrite in one
|
||||||
|
// go (selectionStart/End span the full value).
|
||||||
|
expect(input.selectionStart).toBe(0)
|
||||||
|
expect(input.selectionEnd).toBe('Hero'.length)
|
||||||
|
} finally {
|
||||||
|
handle.unmount()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('panel exposes the canvas width / height fields bound to the store', async () => {
|
test('panel exposes the canvas width / height fields bound to the store', async () => {
|
||||||
const s = makeFreshStore()
|
const s = makeFreshStore()
|
||||||
const handle = mountComponent(DocumentDrawer, {})
|
const handle = mountComponent(DocumentDrawer, {})
|
||||||
|
|||||||
Reference in new issue
Block a user