Merge pull request #58 from open-pencil/rename-dblclick
Double-click to rename layers, shared useInlineRename composable
This commit is contained in:
commit
324e1460d6
|
|
@ -3,6 +3,8 @@ import { nextTick, onUnmounted, ref, watch } from 'vue'
|
|||
import { useEventListener } from '@vueuse/core'
|
||||
import { TreeRoot, TreeItem, ContextMenuRoot, ContextMenuTrigger, ContextMenuPortal } from 'reka-ui'
|
||||
|
||||
import { useInlineRename } from '@/composables/use-inline-rename'
|
||||
|
||||
import IconCircle from '~icons/lucide/circle'
|
||||
import IconComponent from '~icons/lucide/diamond'
|
||||
import IconComponentSet from '~icons/lucide/component'
|
||||
|
|
@ -19,6 +21,7 @@ import { useEditorStore } from '@/stores/editor'
|
|||
import NodeContextMenuContent from './NodeContextMenuContent.vue'
|
||||
|
||||
const store = useEditorStore()
|
||||
const rename = useInlineRename((id, name) => store.renameNode(id, name))
|
||||
|
||||
interface LayerNode {
|
||||
id: string
|
||||
|
|
@ -277,7 +280,27 @@ function updateDropTarget(ev: PointerEvent) {
|
|||
:data-level="item.level"
|
||||
>
|
||||
<TreeItem v-slot="{ isExpanded }" v-bind="item.bind" as-child @select="onSelect">
|
||||
<div
|
||||
v-if="rename.editingId.value === item.value.id"
|
||||
class="flex w-full items-center gap-1 py-1"
|
||||
:style="{ paddingLeft: `${8 + (item.level - 1) * 16}px` }"
|
||||
>
|
||||
<span class="w-4 shrink-0" />
|
||||
<component
|
||||
:is="nodeIcons[item.value.type] ?? IconSquare"
|
||||
class="size-3 shrink-0 opacity-70"
|
||||
/>
|
||||
<input
|
||||
data-layer-edit
|
||||
data-test-id="layers-item-input"
|
||||
class="min-w-0 flex-1 rounded border border-accent bg-input px-1 py-0 text-xs text-surface outline-none"
|
||||
:value="item.value.name"
|
||||
@blur="rename.commit(item.value.id, $event.target as HTMLInputElement)"
|
||||
@keydown="rename.onKeydown"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
data-test-id="layers-item"
|
||||
class="group/row flex w-full cursor-pointer items-center gap-1 rounded border-none py-1 text-left text-xs"
|
||||
:class="[
|
||||
|
|
@ -290,6 +313,7 @@ function updateDropTarget(ev: PointerEvent) {
|
|||
]"
|
||||
:style="{ paddingLeft: `${8 + (item.level - 1) * 16}px` }"
|
||||
@pointerdown.prevent="onPointerDown($event, item.value.id)"
|
||||
@dblclick="rename.start(item.value.id, item.value.name, '[data-layer-edit]')"
|
||||
>
|
||||
<span
|
||||
v-if="item.hasChildren"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, nextTick } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useInlineRename } from '@/composables/use-inline-rename'
|
||||
import { useEditorStore } from '@/stores/editor'
|
||||
|
||||
const store = useEditorStore()
|
||||
|
|
@ -16,30 +17,10 @@ const pages = computed(() => {
|
|||
return store.graph.getPages()
|
||||
})
|
||||
|
||||
const editingPageId = ref<string | null>(null)
|
||||
const rename = useInlineRename((id, name) => store.renamePage(id, name))
|
||||
|
||||
function startRename(pageId: string) {
|
||||
editingPageId.value = pageId
|
||||
nextTick(() => {
|
||||
const input = document.querySelector<HTMLInputElement>('[data-page-edit]')
|
||||
input?.focus()
|
||||
input?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function commitRename(pageId: string, input: HTMLInputElement) {
|
||||
if (editingPageId.value !== pageId) return
|
||||
const value = input.value.trim()
|
||||
if (value && value !== store.graph.getNode(pageId)?.name) {
|
||||
store.renamePage(pageId, value)
|
||||
}
|
||||
editingPageId.value = null
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent, pageId: string) {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
;(e.target as HTMLInputElement).blur()
|
||||
}
|
||||
function startRename(pg: { id: string; name: string }) {
|
||||
rename.start(pg.id, pg.name, '[data-page-edit]')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -61,18 +42,18 @@ function onKeydown(e: KeyboardEvent, pageId: string) {
|
|||
<div class="overflow-x-hidden overflow-y-auto scrollbar-thin px-1 pb-1">
|
||||
<div v-for="pg in pages" :key="pg.id">
|
||||
<input
|
||||
v-if="editingPageId === pg.id"
|
||||
v-if="rename.editingId.value === pg.id"
|
||||
data-page-edit
|
||||
data-test-id="pages-item-input"
|
||||
class="w-full rounded border border-accent bg-input px-2 py-1 text-xs text-surface outline-none"
|
||||
:value="pg.name"
|
||||
@blur="commitRename(pg.id, $event.target as HTMLInputElement)"
|
||||
@keydown="onKeydown($event, pg.id)"
|
||||
@blur="rename.commit(pg.id, $event.target as HTMLInputElement)"
|
||||
@keydown="rename.onKeydown"
|
||||
/>
|
||||
<div
|
||||
v-else-if="isDivider(pg)"
|
||||
class="my-1 flex items-center px-2"
|
||||
@dblclick="startRename(pg.id)"
|
||||
@dblclick="startRename(pg)"
|
||||
>
|
||||
<div class="h-px flex-1 bg-border" />
|
||||
</div>
|
||||
|
|
@ -86,7 +67,7 @@ function onKeydown(e: KeyboardEvent, pageId: string) {
|
|||
: 'bg-transparent text-muted hover:bg-hover hover:text-surface'
|
||||
"
|
||||
@click="store.switchPage(pg.id)"
|
||||
@dblclick="startRename(pg.id)"
|
||||
@dblclick="startRename(pg)"
|
||||
>
|
||||
<icon-lucide-file class="size-3 shrink-0" />
|
||||
<span class="truncate">{{ pg.name }}</span>
|
||||
|
|
|
|||
33
src/composables/use-inline-rename.ts
Normal file
33
src/composables/use-inline-rename.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { nextTick, ref } from 'vue'
|
||||
|
||||
export function useInlineRename(onCommit: (id: string, newName: string) => void) {
|
||||
const editingId = ref<string | null>(null)
|
||||
let originalName = ''
|
||||
|
||||
function start(id: string, currentName: string, selector: string) {
|
||||
editingId.value = id
|
||||
originalName = currentName
|
||||
nextTick(() => {
|
||||
const input = document.querySelector<HTMLInputElement>(selector)
|
||||
input?.focus()
|
||||
input?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function commit(id: string, input: HTMLInputElement) {
|
||||
if (editingId.value !== id) return
|
||||
const value = input.value.trim()
|
||||
if (value && value !== originalName) {
|
||||
onCommit(id, value)
|
||||
}
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
;(e.target as HTMLInputElement).blur()
|
||||
}
|
||||
}
|
||||
|
||||
return { editingId, start, commit, onKeydown }
|
||||
}
|
||||
Loading…
Reference in a new issue