feat(10-09): plumb keyboard shortcut ref chain — DropZone→SearchBar→StorageBrowser→FileManagerView
- DropZone: defineExpose({ triggerInput }) to expose file picker trigger
- SearchBar: add inputEl ref + defineExpose({ focus }) for / shortcut
- StorageBrowser: add dropZoneRef + searchBarRef; expand defineExpose to include triggerUpload, focusSearch, clearSearch
- FileManagerView: add defineExpose delegating all four methods to browserRef via optional chaining
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div role="search">
|
<div role="search">
|
||||||
<input
|
<input
|
||||||
|
ref="inputEl"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
type="search"
|
type="search"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -25,4 +28,8 @@ defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const inputEl = ref(null)
|
||||||
|
|
||||||
|
defineExpose({ focus() { inputEl.value?.focus() } })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
@navigate="$emit('breadcrumb-navigate', $event)"
|
@navigate="$emit('breadcrumb-navigate', $event)"
|
||||||
/>
|
/>
|
||||||
<div class="ml-auto flex items-center gap-2 shrink-0">
|
<div class="ml-auto flex items-center gap-2 shrink-0">
|
||||||
<SearchBar v-if="showSearch" :model-value="searchQuery" @update:modelValue="$emit('search-change', $event)" />
|
<SearchBar v-if="showSearch" ref="searchBarRef" :model-value="searchQuery" @update:modelValue="$emit('search-change', $event)" />
|
||||||
<SortControls
|
<SortControls
|
||||||
v-if="showSearch"
|
v-if="showSearch"
|
||||||
:sort="sortField"
|
:sort="sortField"
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<!-- Upload zone -->
|
<!-- Upload zone -->
|
||||||
<div class="px-6 pt-5 pb-3">
|
<div class="px-6 pt-5 pb-3">
|
||||||
<DropZone @files-selected="$emit('upload', $event)" />
|
<DropZone ref="dropZoneRef" @files-selected="$emit('upload', $event)" />
|
||||||
<UploadProgress :items="uploadQueue" />
|
<UploadProgress :items="uploadQueue" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -321,6 +321,11 @@ function topicColor(name) {
|
|||||||
return props.topicColorFn(name)
|
return props.topicColorFn(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Child component refs ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const dropZoneRef = ref(null)
|
||||||
|
const searchBarRef = ref(null)
|
||||||
|
|
||||||
// ── New folder inline input ───────────────────────────────────────────────────
|
// ── New folder inline input ───────────────────────────────────────────────────
|
||||||
|
|
||||||
const showNewFolderInput = ref(false)
|
const showNewFolderInput = ref(false)
|
||||||
@@ -346,8 +351,12 @@ async function submitNewFolder() {
|
|||||||
emit('folder-create', { name, onError: (msg) => { newFolderError.value = msg }, onSuccess: cancelNewFolder })
|
emit('folder-create', { name, onError: (msg) => { newFolderError.value = msg }, onSuccess: cancelNewFolder })
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called by the parent view when the toolbar "New folder" button is clicked. */
|
defineExpose({
|
||||||
defineExpose({ startNewFolder })
|
startNewFolder,
|
||||||
|
triggerUpload: () => dropZoneRef.value?.triggerInput(),
|
||||||
|
focusSearch: () => searchBarRef.value?.focus(),
|
||||||
|
clearSearch: () => emit('search-change', ''),
|
||||||
|
})
|
||||||
|
|
||||||
// ── Rename ────────────────────────────────────────────────────────────────────
|
// ── Rename ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ function triggerInput() {
|
|||||||
inputRef.value?.click()
|
inputRef.value?.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({ triggerInput })
|
||||||
|
|
||||||
function onDrop(e) {
|
function onDrop(e) {
|
||||||
dragging.value = false
|
dragging.value = false
|
||||||
const files = Array.from(e.dataTransfer?.files || [])
|
const files = Array.from(e.dataTransfer?.files || [])
|
||||||
|
|||||||
@@ -180,4 +180,11 @@ async function doDeleteDoc(docId) {
|
|||||||
function topicColor(name) {
|
function topicColor(name) {
|
||||||
return topicsStore.topics.find(t => t.name === name)?.color ?? '#6366f1'
|
return topicsStore.topics.find(t => t.name === name)?.color ?? '#6366f1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focusSearch: () => browserRef.value?.focusSearch?.(),
|
||||||
|
triggerUpload: () => browserRef.value?.triggerUpload?.(),
|
||||||
|
startNewFolder: () => browserRef.value?.startNewFolder?.(),
|
||||||
|
clearSearch: () => browserRef.value?.clearSearch?.(),
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user