mirror of
https://github.com/FreeTubeApp/FreeTube.git
synced 2025-12-05 01:10:31 +00:00
* Update to Vue 3 * Fix toasts and removing videos from playlists * Fix duplicate app ID * Simplify aria-selected handling now that false doesn't remove attributes * Fix various errors * Fix toasts and hiding watched videos * Update vue-router to 4.6.3 --------- Co-authored-by: efb4f5ff-1298-471a-8973-3d47447115dc <73130443+efb4f5ff-1298-471a-8973-3d47447115dc@users.noreply.github.com>
206 lines
4.1 KiB
Vue
206 lines
4.1 KiB
Vue
<template>
|
|
<portal to="promptPortal">
|
|
<div
|
|
class="prompt"
|
|
tabindex="-1"
|
|
:inert="inert"
|
|
@click.self="hide"
|
|
@keydown.enter.self="hide"
|
|
@keydown.left.right.capture="handleArrowKeys"
|
|
>
|
|
<FtCard
|
|
ref="promptCard"
|
|
class="promptCard"
|
|
:class="{ autosize, [theme]: true }"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
:aria-labelledby="id"
|
|
>
|
|
<slot
|
|
name="label"
|
|
:label-id="id"
|
|
>
|
|
<h2
|
|
:id="id"
|
|
class="center"
|
|
>
|
|
{{ label }}
|
|
</h2>
|
|
</slot>
|
|
|
|
<slot>
|
|
<p
|
|
v-for="extraLabel in extraLabels"
|
|
:key="extraLabel"
|
|
class="center"
|
|
>
|
|
<strong>
|
|
{{ extraLabel }}
|
|
</strong>
|
|
</p>
|
|
<FtFlexBox>
|
|
<FtButton
|
|
v-for="(option, index) in optionNames"
|
|
:key="index"
|
|
:label="option"
|
|
:text-color="optionButtonTextColor(index)"
|
|
:background-color="optionButtonBackgroundColor(index)"
|
|
:icon="index === 0 && isFirstOptionDestructive ? ['fas', 'trash'] : null"
|
|
@click="click(optionValues[index])"
|
|
/>
|
|
</FtFlexBox>
|
|
</slot>
|
|
</FtCard>
|
|
</div>
|
|
</portal>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { nextTick, onBeforeUnmount, onMounted, ref, useId } from 'vue'
|
|
|
|
import store from '../../store/index'
|
|
|
|
import FtCard from '../ft-card/ft-card.vue'
|
|
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
|
import FtButton from '../FtButton/FtButton.vue'
|
|
|
|
const props = defineProps({
|
|
label: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
extraLabels: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
optionNames: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
optionValues: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
autosize: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
isFirstOptionDestructive: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
theme: {
|
|
type: String,
|
|
default: 'base'
|
|
},
|
|
inert: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['click'])
|
|
|
|
const id = useId()
|
|
|
|
const promptCard = ref(null)
|
|
|
|
let promptButtons = []
|
|
let lastActiveElement = null
|
|
|
|
onMounted(() => {
|
|
lastActiveElement = document.activeElement
|
|
document.addEventListener('keydown', handleEscape, true)
|
|
|
|
nextTick(() => {
|
|
promptButtons = Array.from(promptCard.value.$el.querySelectorAll('.btn.ripple, .iconButton'))
|
|
focusItem(0)
|
|
})
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener('keydown', handleEscape, true)
|
|
nextTick(() => lastActiveElement?.focus())
|
|
})
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
function optionButtonTextColor(index) {
|
|
if (index === 0 && props.isFirstOptionDestructive) {
|
|
return 'var(--destructive-text-color)'
|
|
} else if (index < props.optionNames.length - 1) {
|
|
return 'var(--text-with-accent-color)'
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
function optionButtonBackgroundColor(index) {
|
|
if (index === 0 && props.isFirstOptionDestructive) {
|
|
return 'var(--destructive-color)'
|
|
} else if (index < props.optionNames.length - 1) {
|
|
return 'var(--accent-color)'
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {any} value
|
|
*/
|
|
function click(value) {
|
|
emit('click', value)
|
|
}
|
|
|
|
function hide() {
|
|
click(null)
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
function focusItem(index) {
|
|
if (index < 0) {
|
|
index = promptButtons.length - 1
|
|
} else if (index >= promptButtons.length) {
|
|
index = 0
|
|
}
|
|
|
|
promptButtons[index].focus()
|
|
store.dispatch('showOutlines')
|
|
}
|
|
|
|
/**
|
|
* @param {KeyboardEvent} event
|
|
*/
|
|
function handleEscape(event) {
|
|
if (event.key === 'Escape' && !props.inert) {
|
|
event.preventDefault()
|
|
hide()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {KeyboardEvent} event
|
|
*/
|
|
function handleArrowKeys(event) {
|
|
const currentIndex = promptButtons.indexOf(event.target)
|
|
|
|
// Only react if a button was focused when the arrow key was pressed
|
|
if (currentIndex === -1) {
|
|
return
|
|
}
|
|
|
|
event.preventDefault()
|
|
|
|
const direction = (event.key === 'ArrowLeft') ? -1 : 1
|
|
focusItem(currentIndex + direction)
|
|
}
|
|
</script>
|
|
|
|
<style scoped src="./FtPrompt.css" />
|