People: Adjust menuProps and add focus trap to PConfirmDialog #5307

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-11-10 16:08:45 +01:00
parent 34635e852f
commit 50838fb83b
3 changed files with 62 additions and 74 deletions

View File

@@ -2,24 +2,28 @@
<v-dialog <v-dialog
ref="dialog" ref="dialog"
:model-value="visible" :model-value="visible"
:close-delay="0"
:open-delay="0"
persistent persistent
scrim
max-width="360" max-width="360"
class="p-dialog p-confirm-dialog" class="p-dialog p-confirm-dialog"
retain-focus @keyup.esc.exact="close"
@keydown.esc.exact.stop.prevent="close" @keyup.enter.exact="confirm"
@keydown.enter.exact.stop.prevent="confirm"
@after-enter="afterEnter" @after-enter="afterEnter"
@after-leave="afterLeave"
@focusout="onFocusOut"
> >
<v-card ref="content" tabindex="1"> <v-card ref="content" tabindex="0">
<v-card-title class="d-flex justify-start align-center ga-3"> <v-card-title class="d-flex justify-start align-center ga-3">
<v-icon :icon="icon" :size="iconSize" color="primary"></v-icon> <v-icon :icon="icon" :size="iconSize" color="primary"></v-icon>
<div class="text-subtitle-1">{{ text ? text : $gettext(`Are you sure?`) }}</div> <div class="text-subtitle-1">{{ text ? text : $gettext(`Are you sure?`) }}</div>
</v-card-title> </v-card-title>
<v-card-actions class="action-buttons"> <v-card-actions class="action-buttons">
<v-btn variant="flat" color="button" class="action-cancel action-close" @click.stop="close"> <v-btn variant="flat" tabindex="0" color="button" class="action-cancel action-close" @click.stop="close">
{{ $gettext(`Cancel`) }} {{ $gettext(`Cancel`) }}
</v-btn> </v-btn>
<v-btn color="highlight" variant="flat" class="action-confirm" @click.stop="confirm"> <v-btn color="highlight" tabindex="0" variant="flat" class="action-confirm" @click.stop="confirm">
{{ action ? action : $gettext(`Yes`) }} {{ action ? action : $gettext(`Yes`) }}
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -55,18 +59,31 @@ export default {
data() { data() {
return {}; return {};
}, },
watch: {
visible(show) {
if (show) {
this.$nextTick(() => this.$view.enter(this, this.$refs?.content, ".action-confirm"));
} else {
this.$view.leave(this);
}
},
},
methods: { methods: {
afterEnter() { afterEnter() {
this.$nextTick(() => this.$view.enter(this, this.$refs?.content, ".action-confirm")); this.$view.enter(this);
},
afterLeave() {
this.$view.leave(this);
},
onFocusOut(ev) {
if (!this.$view.isActive(this)) {
return;
}
const el = this.$refs.content?.$el;
if (!ev || !ev.target || !(ev.target instanceof HTMLElement) || !(el instanceof HTMLElement)) {
return;
}
const next = ev.relatedTarget;
const leavingDialog = !next || !(next instanceof Node) || !el.contains(next);
if (leavingDialog) {
el.focus();
ev.preventDefault();
}
}, },
close() { close() {
this.$emit("close"); this.$emit("close");

View File

@@ -79,10 +79,9 @@
item-title="Name" item-title="Name"
item-value="Name" item-value="Name"
:disabled="busy" :disabled="busy"
:menu-props="menuProps"
return-object return-object
hide-no-data hide-no-data
:menu-props="menuProps"
:menu="openMenuId === m.UID"
hide-details hide-details
single-line single-line
open-on-clear open-on-clear
@@ -90,15 +89,9 @@
prepend-inner-icon="mdi-account-plus" prepend-inner-icon="mdi-account-plus"
density="comfortable" density="comfortable"
class="input-name pa-0 ma-0 text-selectable" class="input-name pa-0 ma-0 text-selectable"
@blur="
() => {
onSetName(m, 'blur');
onUpdateMenu(m, false);
}
"
@update:menu="(val) => onUpdateMenu(m, val)"
@update:model-value="(person) => onSetPerson(m, person)" @update:model-value="(person) => onSetPerson(m, person)"
@keyup.enter="onSetName(m, 'enter')" @blur="(ev) => onSetName(m, ev)"
@keyup.enter="(ev) => onSetName(m, ev)"
> >
</v-combobox> </v-combobox>
</v-card-actions> </v-card-actions>
@@ -147,9 +140,15 @@ export default {
text: this.$gettext("Add person?"), text: this.$gettext("Add person?"),
}, },
menuProps: { menuProps: {
closeOnClick: false,
closeOnContentClick: true,
openOnClick: true, openOnClick: true,
openOnFocus: true,
closeOnBack: true,
closeOnContentClick: true,
persistent: false,
scrim: true,
openDelay: 0,
closeDelay: 0,
opacity: 0,
density: "compact", density: "compact",
maxHeight: 300, maxHeight: 300,
scrollStrategy: "reposition", scrollStrategy: "reposition",
@@ -161,7 +160,6 @@ export default {
return v.length <= this.$config.get("clip") || this.$gettext("Name too long"); return v.length <= this.$config.get("clip") || this.$gettext("Name too long");
}, },
openMenuId: "",
}; };
}, },
watch: { watch: {
@@ -331,7 +329,7 @@ export default {
return true; return true;
}, },
onSetName(model, trigger) { onSetName(model, ev) {
if (this.busy || !model) { if (this.busy || !model) {
return; return;
} }
@@ -366,7 +364,8 @@ export default {
model.Name = name; model.Name = name;
model.SubjUID = ""; model.SubjUID = "";
if (trigger === "enter") {
if (ev && ev.key === "Enter" && !ev.isComposing && !ev.repeat) {
this.setName(model); this.setName(model);
} else { } else {
this.confirm.visible = true; this.confirm.visible = true;
@@ -385,19 +384,6 @@ export default {
this.confirm.model.SubjUID = ""; this.confirm.model.SubjUID = "";
} }
this.confirm.visible = false; this.confirm.visible = false;
this.openMenuId = "";
},
getModelKey(model) {
return model?.UID || model?.ID || "";
},
onUpdateMenu(model, open) {
const key = this.getModelKey(model);
if (!key) return;
if (open) {
this.openMenuId = key;
} else if (this.openMenuId === key) {
this.openMenuId = "";
}
}, },
setName(model) { setName(model) {
if (this.busy || !model) { if (this.busy || !model) {

View File

@@ -85,8 +85,8 @@
single-line single-line
density="comfortable" density="comfortable"
class="input-name pa-0 ma-0" class="input-name pa-0 ma-0"
@blur="onSetName(m, 'blur')" @blur="(ev) => onSetName(m, ev)"
@keyup.enter="onSetName(m, 'enter')" @keyup.enter="(ev) => onSetName(m, ev)"
></v-text-field> ></v-text-field>
<v-combobox <v-combobox
v-else v-else
@@ -95,10 +95,9 @@
item-title="Name" item-title="Name"
item-value="Name" item-value="Name"
:readonly="readonly" :readonly="readonly"
:menu-props="menuProps"
return-object return-object
hide-no-data hide-no-data
:menu-props="menuProps"
:menu="openMenuId === m.ID"
hide-details hide-details
single-line single-line
open-on-clear open-on-clear
@@ -107,15 +106,9 @@
autocomplete="off" autocomplete="off"
density="comfortable" density="comfortable"
class="input-name pa-0 ma-0 text-selectable" class="input-name pa-0 ma-0 text-selectable"
@blur="
() => {
onSetName(m, 'blur');
onUpdateMenu(m, false);
}
"
@update:menu="(val) => onUpdateMenu(m, val)"
@update:model-value="(person) => onSetPerson(m, person)" @update:model-value="(person) => onSetPerson(m, person)"
@keyup.enter="onSetName(m, 'enter')" @blur="(ev) => onSetName(m, ev)"
@keyup.enter="(ev) => onSetName(m, ev)"
> >
</v-combobox> </v-combobox>
</v-card-actions> </v-card-actions>
@@ -201,9 +194,15 @@ export default {
text: this.$gettext("Add person?"), text: this.$gettext("Add person?"),
}, },
menuProps: { menuProps: {
closeOnClick: false,
closeOnContentClick: true,
openOnClick: true, openOnClick: true,
openOnFocus: true,
closeOnBack: true,
closeOnContentClick: true,
persistent: false,
scrim: true,
openDelay: 0,
closeDelay: 0,
opacity: 0,
density: "compact", density: "compact",
maxHeight: 300, maxHeight: 300,
scrollStrategy: "reposition", scrollStrategy: "reposition",
@@ -215,7 +214,6 @@ export default {
return v.length <= this.$config.get("clip") || this.$gettext("Text too long"); return v.length <= this.$config.get("clip") || this.$gettext("Text too long");
}, },
openMenuId: "",
}; };
}, },
computed: { computed: {
@@ -653,7 +651,7 @@ export default {
return true; return true;
}, },
onSetName(model, trigger) { onSetName(model, ev) {
if (this.busy || !model) { if (this.busy || !model) {
return; return;
} }
@@ -690,7 +688,7 @@ export default {
model.SubjUID = ""; model.SubjUID = "";
if (model.Name) { if (model.Name) {
if (trigger === "enter") { if (ev && ev.key === "Enter" && !ev.isComposing && !ev.repeat) {
this.setName(model, model.Name); this.setName(model, model.Name);
} else { } else {
this.confirm.visible = true; this.confirm.visible = true;
@@ -715,19 +713,6 @@ export default {
this.confirm.model.SubjUID = ""; this.confirm.model.SubjUID = "";
} }
this.confirm.visible = false; this.confirm.visible = false;
this.openMenuId = "";
},
getModelKey(model) {
return model?.ID || model?.UID || "";
},
onUpdateMenu(model, open) {
const key = this.getModelKey(model);
if (!key) return;
if (open) {
this.openMenuId = key;
} else if (this.openMenuId === key) {
this.openMenuId = "";
}
}, },
setName(model, newName) { setName(model, newName) {
if (this.busy || !model || !newName || newName.trim() === "") { if (this.busy || !model || !newName || newName.trim() === "") {