some shitty initial implementation of emoji picker with popover

This commit is contained in:
Henry Jameson 2022-10-09 23:42:36 +03:00
parent 518fcf856a
commit 296a6fa4e3
6 changed files with 157 additions and 154 deletions

View File

@ -115,12 +115,12 @@ const EmojiInput = {
caret: 0, caret: 0,
focused: false, focused: false,
blurTimeout: null, blurTimeout: null,
showPicker: false,
temporarilyHideSuggestions: false, temporarilyHideSuggestions: false,
keepOpen: false, keepOpen: false,
disableClickOutside: false, disableClickOutside: false,
suggestions: [], suggestions: [],
overlayStyle: {} overlayStyle: {},
pickerShown: false
} }
}, },
components: { components: {
@ -142,7 +142,6 @@ const EmojiInput = {
return this.focused && return this.focused &&
this.suggestions && this.suggestions &&
this.suggestions.length > 0 && this.suggestions.length > 0 &&
!this.showPicker &&
!this.temporarilyHideSuggestions !this.temporarilyHideSuggestions
}, },
textAtCaret () { textAtCaret () {
@ -285,8 +284,8 @@ const EmojiInput = {
if (pickerInput) pickerInput.focus() if (pickerInput) pickerInput.focus()
}, },
triggerShowPicker () { triggerShowPicker () {
this.showPicker = true
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.picker.showPicker()
this.scrollIntoView() this.scrollIntoView()
this.focusPickerInput() this.focusPickerInput()
}) })
@ -299,12 +298,16 @@ const EmojiInput = {
}, 0) }, 0)
}, },
togglePicker () { togglePicker () {
console.log('piick')
this.input.focus() this.input.focus()
this.showPicker = !this.showPicker if (!this.pickerShown) {
if (this.showPicker) { console.log('pick')
this.scrollIntoView() this.scrollIntoView()
this.$refs.picker.showPicker()
this.$refs.picker.startEmojiLoad() this.$refs.picker.startEmojiLoad()
this.$nextTick(this.focusPickerInput) this.$nextTick(this.focusPickerInput)
} else {
this.$refs.picker.hidePicker()
} }
}, },
replace (replacement) { replace (replacement) {
@ -441,6 +444,12 @@ const EmojiInput = {
} }
}) })
}, },
onPickerShown () {
this.pickerShown = true
},
onPickerClosed () {
this.pickerShown = false
},
onBlur (e) { onBlur (e) {
// Clicking on any suggestion removes focus from autocomplete, // Clicking on any suggestion removes focus from autocomplete,
// preventing click handler ever executing. // preventing click handler ever executing.
@ -458,9 +467,6 @@ const EmojiInput = {
this.blurTimeout = null this.blurTimeout = null
} }
if (!this.keepOpen) {
this.showPicker = false
}
this.focused = true this.focused = true
this.setCaret(e) this.setCaret(e)
this.temporarilyHideSuggestions = false this.temporarilyHideSuggestions = false
@ -523,27 +529,15 @@ const EmojiInput = {
this.input.focus() this.input.focus()
} }
} }
this.showPicker = false
}, },
onInput (e) { onInput (e) {
this.showPicker = false
this.setCaret(e) this.setCaret(e)
this.$emit('update:modelValue', e.target.value) this.$emit('update:modelValue', e.target.value)
}, },
onClickInput (e) {
this.showPicker = false
},
onClickOutside (e) {
if (this.disableClickOutside) return
this.showPicker = false
},
onStickerUploaded (e) { onStickerUploaded (e) {
this.showPicker = false
this.$emit('sticker-uploaded', e) this.$emit('sticker-uploaded', e)
}, },
onStickerUploadFailed (e) { onStickerUploadFailed (e) {
this.showPicker = false
this.$emit('sticker-upload-Failed', e) this.$emit('sticker-upload-Failed', e)
}, },
setCaret ({ target: { selectionStart } }) { setCaret ({ target: { selectionStart } }) {

View File

@ -1,7 +1,6 @@
<template> <template>
<div <div
ref="root" ref="root"
v-click-outside="onClickOutside"
class="emoji-input" class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }" :class="{ 'with-picker': !hideEmojiButton }"
> >
@ -24,13 +23,13 @@
<EmojiPicker <EmojiPicker
v-if="enableEmojiPicker" v-if="enableEmojiPicker"
ref="picker" ref="picker"
:class="{ hide: !showPicker }"
:showing="showPicker"
:enable-sticker-picker="enableStickerPicker" :enable-sticker-picker="enableStickerPicker"
class="emoji-picker-panel" class="emoji-picker-panel"
@emoji="insert" @emoji="insert"
@sticker-uploaded="onStickerUploaded" @sticker-uploaded="onStickerUploaded"
@sticker-upload-failed="onStickerUploadFailed" @sticker-upload-failed="onStickerUploadFailed"
@show="onPickerShown"
@close="onPickerClosed"
/> />
</template> </template>
<Popover <Popover

View File

@ -1,5 +1,6 @@
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
import StillImage from '../still-image/still-image.vue' import StillImage from '../still-image/still-image.vue'
import { ensureFinalFallback } from '../../i18n/languages.js' import { ensureFinalFallback } from '../../i18n/languages.js'
import lozad from 'lozad' import lozad from 'lozad'
@ -87,10 +88,6 @@ const EmojiPicker = {
required: false, required: false,
type: Boolean, type: Boolean,
default: false default: false
},
showing: {
required: true,
type: Boolean
} }
}, },
data () { data () {
@ -111,15 +108,30 @@ const EmojiPicker = {
components: { components: {
StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')), StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
Checkbox, Checkbox,
StillImage StillImage,
Popover
}, },
methods: { methods: {
showPicker () {
console.log('pick')
this.$refs.popover.showPopover()
this.onShowing()
},
hidePicker () {
this.$refs.popover.hidePopover()
},
setGroupRef (name) { setGroupRef (name) {
return el => { this.groupRefs[name] = el } return el => { this.groupRefs[name] = el }
}, },
setEmojiRef (name) { setEmojiRef (name) {
return el => { this.emojiRefs[name] = el } return el => { this.emojiRefs[name] = el }
}, },
onPopoverShown () {
this.$emit('show')
},
onPopoverClosed () {
this.$emit('close')
},
onStickerUploaded (e) { onStickerUploaded (e) {
this.$emit('sticker-uploaded', e) this.$emit('sticker-uploaded', e)
}, },
@ -251,16 +263,6 @@ const EmojiPicker = {
allCustomGroups () { allCustomGroups () {
this.waitForDomAndInitializeLazyLoad() this.waitForDomAndInitializeLazyLoad()
this.filteredEmojiGroups = this.getFilteredEmojiGroups() this.filteredEmojiGroups = this.getFilteredEmojiGroups()
},
showing (val) {
if (val) {
this.onShowing()
}
}
},
mounted () {
if (this.showing) {
this.onShowing()
} }
}, },
destroyed () { destroyed () {

View File

@ -6,14 +6,10 @@ $emoji-picker-header-picture-height: 32px;
$emoji-picker-emoji-size: 32px; $emoji-picker-emoji-size: 32px;
.emoji-picker { .emoji-picker {
width: 25em;
max-width: 100vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: absolute;
right: 0;
left: 0;
margin: 0 !important;
// TODO: actually use popover in emoji picker
z-index: var(--ZI_popovers);
background-color: $fallback--bg; background-color: $fallback--bg;
background-color: var(--popover, $fallback--bg); background-color: var(--popover, $fallback--bg);
color: $fallback--link; color: $fallback--link;

View File

@ -1,129 +1,135 @@
<template> <template>
<div <Popover
class="emoji-picker panel panel-default panel-body" trigger="click"
popover-class="emoji-picker popover-default"
ref="popover"
@show="onPopoverShown"
@close="onPopoverClosed"
> >
<div class="heading"> <template #content>
<span <div class="heading">
ref="header"
class="emoji-tabs"
>
<span <span
v-for="group in filteredEmojiGroups" ref="header"
:ref="setGroupRef('group-header-' + group.id)" class="emoji-tabs"
:key="group.id"
class="emoji-tabs-item"
:class="{
active: activeGroupView === group.id
}"
:title="group.text"
@click.prevent="highlight(group.id)"
> >
<span <span
v-if="group.image"
class="emoji-picker-header-image"
>
<still-image
:alt="group.text"
:src="group.image"
/>
</span>
<FAIcon
v-else
:icon="group.icon"
fixed-width
/>
</span>
</span>
<span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="stickers-tab-icon additional-tabs-item"
:class="{active: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
<FAIcon
icon="sticky-note"
fixed-width
/>
</span>
</span>
</div>
<div
v-if="contentLoaded"
class="content"
>
<div
class="emoji-content"
:class="{hidden: showingStickers}"
>
<div class="emoji-search">
<input
v-model="keyword"
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<div
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
>
<div
v-for="group in filteredEmojiGroups" v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)"
:key="group.id" :key="group.id"
class="emoji-group" class="emoji-tabs-item"
:class="{
active: activeGroupView === group.id
}"
:title="group.text"
@click.prevent="highlight(group.id)"
> >
<h6
:ref="setGroupRef('group-' + group.id)"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span <span
v-for="emoji in group.emojis" v-if="group.image"
:key="group.id + emoji.displayText" class="emoji-picker-header-image"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
> >
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image <still-image
v-else :alt="group.text"
:ref="setEmojiRef(group.id + emoji.displayText)" :src="group.image"
class="emoji-picker-emoji -custom"
:data-src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/> />
</span> </span>
<span :ref="setGroupRef('group-end-' + group.id)" /> <FAIcon
</div> v-else
</div> :icon="group.icon"
<div class="keep-open"> fixed-width
<Checkbox v-model="keepOpen"> />
{{ $t('emoji.keep_open') }} </span>
</Checkbox> </span>
</div> <span
v-if="stickerPickerEnabled"
class="additional-tabs"
>
<span
class="stickers-tab-icon additional-tabs-item"
:class="{active: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
<FAIcon
icon="sticky-note"
fixed-width
/>
</span>
</span>
</div> </div>
<div <div
v-if="showingStickers" v-if="contentLoaded"
class="stickers-content" class="content"
> >
<sticker-picker <div
@uploaded="onStickerUploaded" class="emoji-content"
@upload-failed="onStickerUploadFailed" :class="{hidden: showingStickers}"
/> >
<div class="emoji-search">
<input
v-model="keyword"
type="text"
class="form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
<div
ref="emoji-groups"
class="emoji-groups"
:class="groupsScrolledClass"
@scroll="onScroll"
>
<div
v-for="group in filteredEmojiGroups"
:key="group.id"
class="emoji-group"
>
<h6
:ref="setGroupRef('group-' + group.id)"
class="emoji-group-title"
>
{{ group.text }}
</h6>
<span
v-for="emoji in group.emojis"
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
@click.stop.prevent="onEmoji(emoji)"
>
<span
v-if="!emoji.imageUrl"
class="emoji-picker-emoji -unicode"
>{{ emoji.replacement }}</span>
<still-image
v-else
:ref="setEmojiRef(group.id + emoji.displayText)"
class="emoji-picker-emoji -custom"
:data-src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
</span>
<span :ref="setGroupRef('group-end-' + group.id)" />
</div>
</div>
<div class="keep-open">
<Checkbox v-model="keepOpen">
{{ $t('emoji.keep_open') }}
</Checkbox>
</div>
</div>
<div
v-if="showingStickers"
class="stickers-content"
>
<sticker-picker
@uploaded="onStickerUploaded"
@upload-failed="onStickerUploadFailed"
/>
</div>
</div> </div>
</div> </template>
</div> </Popover>
</template> </template>
<script src="./emoji_picker.js"></script> <script src="./emoji_picker.js"></script>

View File

@ -63,6 +63,7 @@ const Popover = {
// used to avoid blinking if hovered onto popover // used to avoid blinking if hovered onto popover
graceTimeout: null, graceTimeout: null,
parentPopover: null, parentPopover: null,
disableClickOutside: false,
childrenShown: new Set() childrenShown: new Set()
} }
}, },
@ -234,6 +235,10 @@ const Popover = {
}, },
showPopover () { showPopover () {
if (this.disabled) return if (this.disabled) return
this.disableClickOutside = true
setTimeout(() => {
this.disableClickOutside = false
}, 0)
const wasHidden = this.hidden const wasHidden = this.hidden
this.hidden = false this.hidden = false
this.parentPopover && this.parentPopover.onChildPopoverState(this, true) this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
@ -294,6 +299,7 @@ const Popover = {
} }
}, },
onClickOutside (e) { onClickOutside (e) {
if (this.disableClickOutside) return
if (this.hidden) return if (this.hidden) return
if (this.$refs.content && this.$refs.content.contains(e.target)) return if (this.$refs.content && this.$refs.content.contains(e.target)) return
if (this.$el.contains(e.target)) return if (this.$el.contains(e.target)) return