enhance(client): improve modal menu for mobile
This commit is contained in:
parent
6d6162333e
commit
6ab2c7780c
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :front="true" @click="done(true)" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog'" :front="true" @click="done(true)" @closed="$emit('closed')">
|
||||||
<div class="mk-dialog">
|
<div class="mk-dialog">
|
||||||
<div v-if="icon" class="icon">
|
<div v-if="icon" class="icon">
|
||||||
<i :class="icon"></i>
|
<i :class="icon"></i>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<MkPopup ref="popup" v-slot="{ point, close }" :manual-showing="manualShowing" :src="src" :front="true" @click="close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')">
|
<MkModal ref="modal" v-slot="{ type, maxHeight }" :manual-showing="manualShowing" :src="src" :front="true" @click="$refs.modal.close()" @opening="opening" @close="$emit('close')" @closed="$emit('closed')">
|
||||||
<MkEmojiPicker ref="picker" class="ryghynhb _popup _shadow" :class="{ pointer: point === 'top' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" @chosen="chosen"/>
|
<MkEmojiPicker ref="picker" class="ryghynhb _popup _shadow" :class="{ drawer: type === 'drawer' }" :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" :as-drawer="type === 'drawer'" :max-height="maxHeight" @chosen="chosen"/>
|
||||||
</MkPopup>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, markRaw } from 'vue';
|
import { defineComponent, markRaw } from 'vue';
|
||||||
import MkPopup from '@/components/ui/popup.vue';
|
import MkModal from '@/components/ui/modal.vue';
|
||||||
import MkEmojiPicker from '@/components/emoji-picker.vue';
|
import MkEmojiPicker from '@/components/emoji-picker.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MkPopup,
|
MkModal,
|
||||||
MkEmojiPicker,
|
MkEmojiPicker,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
chosen(emoji: any) {
|
chosen(emoji: any) {
|
||||||
this.$emit('done', emoji);
|
this.$emit('done', emoji);
|
||||||
this.$refs.popup.close();
|
this.$refs.modal.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
opening() {
|
opening() {
|
||||||
|
@ -57,20 +57,10 @@ export default defineComponent({
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.ryghynhb {
|
.ryghynhb {
|
||||||
&.pointer {
|
&.drawer {
|
||||||
&:before {
|
border-radius: 24px;
|
||||||
--size: 8px;
|
border-bottom-right-radius: 0;
|
||||||
content: '';
|
border-bottom-left-radius: 0;
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(0px - (var(--size) * 2));
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 0;
|
|
||||||
margin: auto;
|
|
||||||
border: solid var(--size) transparent;
|
|
||||||
border-bottom-color: var(--popup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="omfetrab" :class="['w' + width, 'h' + height, { big }]">
|
<div class="omfetrab" :class="['w' + width, 'h' + height, { big, asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : null }">
|
||||||
<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
|
<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="$ts.search" @paste.stop="paste" @keyup.enter="done()">
|
||||||
<div ref="emojis" class="emojis">
|
<div ref="emojis" class="emojis">
|
||||||
<section class="result">
|
<section class="result">
|
||||||
|
@ -92,9 +92,17 @@ export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
showPinned: {
|
showPinned: {
|
||||||
required: false,
|
required: false,
|
||||||
default: true
|
default: true,
|
||||||
},
|
},
|
||||||
asReactionPicker: {
|
asReactionPicker: {
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
maxHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
asDrawer: {
|
||||||
|
type: Boolean,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -353,26 +361,53 @@ export default defineComponent({
|
||||||
|
|
||||||
&.w1 {
|
&.w1 {
|
||||||
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
|
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
|
||||||
|
--columns: 1fr 1fr 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.w2 {
|
&.w2 {
|
||||||
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||||
|
--columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.w3 {
|
&.w3 {
|
||||||
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
|
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
|
||||||
|
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.h1 {
|
&.h1 {
|
||||||
--height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
|
height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
&.h2 {
|
&.h2 {
|
||||||
--height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
&.h3 {
|
&.h3 {
|
||||||
--height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
|
height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.asDrawer {
|
||||||
|
width: 100% !important;
|
||||||
|
|
||||||
|
> .emojis {
|
||||||
|
::v-deep(section) {
|
||||||
|
> div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--columns);
|
||||||
|
|
||||||
|
> button {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .search {
|
> .search {
|
||||||
|
@ -409,7 +444,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
> .emojis {
|
> .emojis {
|
||||||
height: var(--height);
|
height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog'" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||||
<div class="szkkfdyq _popup">
|
<div class="szkkfdyq _popup">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<template v-for="item in items">
|
<template v-for="item in items">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" :position="'top'" @click="$refs.modal.close()" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog:top'" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||||
<MkPostForm v-bind="$attrs" @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()"/>
|
<MkPostForm v-bind="$attrs" @posted="$refs.modal.close()" @cancel="$refs.modal.close()" @esc="$refs.modal.close()"/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div ref="items" v-hotkey="keymap"
|
<div ref="items" v-hotkey="keymap"
|
||||||
class="rrevdjwt"
|
class="rrevdjwt"
|
||||||
:class="{ center: align === 'center' }"
|
:class="{ center: align === 'center', asDrawer }"
|
||||||
:style="{ width: width ? width + 'px' : null, maxHeight: maxHeight ? maxHeight + 'px' : null }"
|
:style="{ width: (width && !asDrawer) ? width + 'px' : null, maxHeight: maxHeight ? maxHeight + 'px' : null }"
|
||||||
@contextmenu.self="e => e.preventDefault()"
|
@contextmenu.self="e => e.preventDefault()"
|
||||||
>
|
>
|
||||||
<template v-for="(item, i) in items2">
|
<template v-for="(item, i) in items2">
|
||||||
|
@ -56,6 +56,10 @@ export default defineComponent({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
asDrawer: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
align: {
|
align: {
|
||||||
type: String,
|
type: String,
|
||||||
requried: false
|
requried: false
|
||||||
|
@ -279,5 +283,29 @@ export default defineComponent({
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: var(--divider);
|
background: var(--divider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.asDrawer {
|
||||||
|
padding: 12px 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> .item {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 12px 24px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> i {
|
||||||
|
margin-right: 14px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .divider {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
|
<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')">
|
||||||
<div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
|
<div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
|
<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<transition :name="$store.state.animation ? popup ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? popup ? 500 : 300 : 0" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
|
<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="$emit('closed')" @enter="$emit('opening')" @after-enter="childRendered">
|
||||||
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
||||||
<div class="bg _modalBg" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
<div class="bg _modalBg" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
|
||||||
<div ref="content" class="content" :class="{ popup, fixed, top: position === 'top' }" :style="{ zIndex }" @click.self="onBgClick">
|
<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
|
||||||
<slot></slot>
|
<slot :max-height="maxHeight" :type="type"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent, nextTick, onMounted, computed, PropType, ref, watch } from 'vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
|
import { isTouchUsing } from '@/scripts/touch';
|
||||||
|
|
||||||
function getFixedContainer(el: Element | null): Element | null {
|
function getFixedContainer(el: Element | null): Element | null {
|
||||||
if (el == null || el.tagName === 'BODY') return null;
|
if (el == null || el.tagName === 'BODY') return null;
|
||||||
|
@ -27,6 +28,7 @@ export default defineComponent({
|
||||||
provide: {
|
provide: {
|
||||||
modal: true
|
modal: true
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
manualShowing: {
|
manualShowing: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -38,61 +40,81 @@ export default defineComponent({
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
src: {
|
src: {
|
||||||
|
type: Object as PropType<HTMLElement>,
|
||||||
required: false,
|
required: false,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
position: {
|
preferType: {
|
||||||
required: false
|
required: false,
|
||||||
|
type: String,
|
||||||
|
default: 'auto',
|
||||||
},
|
},
|
||||||
front: {
|
front: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
noOverlap: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
emits: ['opening', 'click', 'esc', 'close', 'closed'],
|
emits: ['opening', 'click', 'esc', 'close', 'closed'],
|
||||||
data() {
|
|
||||||
return {
|
setup(props, context) {
|
||||||
zIndex: os.claimZIndex(this.front),
|
const maxHeight = ref<number>();
|
||||||
showing: true,
|
const fixed = ref(false);
|
||||||
fixed: false,
|
const transformOrigin = ref('center');
|
||||||
transformOrigin: 'center',
|
const showing = ref(true);
|
||||||
contentClicking: false,
|
const content = ref<HTMLElement>();
|
||||||
};
|
const zIndex = os.claimZIndex(props.front);
|
||||||
},
|
const type = computed(() => {
|
||||||
computed: {
|
if (props.preferType === 'auto') {
|
||||||
keymap(): any {
|
if (isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) {
|
||||||
return {
|
return 'drawer';
|
||||||
'esc': () => this.$emit('esc'),
|
} else {
|
||||||
};
|
return props.src != null ? 'popup' : 'dialog';
|
||||||
},
|
}
|
||||||
popup(): boolean {
|
} else {
|
||||||
return this.src != null;
|
return props.preferType;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$watch('src', () => {
|
|
||||||
this.fixed = getFixedContainer(this.src) != null;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.align();
|
|
||||||
});
|
});
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
let contentClicking = false;
|
||||||
const popover = this.$refs.content as any;
|
|
||||||
new ResizeObserver((entries, observer) => {
|
|
||||||
this.align();
|
|
||||||
}).observe(popover);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
align() {
|
|
||||||
if (!this.popup) return;
|
|
||||||
|
|
||||||
const popover = this.$refs.content as any;
|
const close = () => {
|
||||||
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
|
if (props.src) props.src.style.pointerEvents = 'auto';
|
||||||
|
showing.value = false;
|
||||||
|
context.emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBgClick = () => {
|
||||||
|
if (contentClicking) return;
|
||||||
|
context.emit('click');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type.value === 'drawer') {
|
||||||
|
maxHeight.value = 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keymap = {
|
||||||
|
'esc': () => context.emit('esc'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const MARGIN = 16;
|
||||||
|
|
||||||
|
const align = () => {
|
||||||
|
if (props.src == null) return;
|
||||||
|
if (type.value === 'drawer') return;
|
||||||
|
|
||||||
|
const popover = content.value!;
|
||||||
|
|
||||||
if (popover == null) return;
|
if (popover == null) return;
|
||||||
|
|
||||||
const rect = this.src.getBoundingClientRect();
|
const rect = props.src.getBoundingClientRect();
|
||||||
|
|
||||||
const width = popover.offsetWidth;
|
const width = popover.offsetWidth;
|
||||||
const height = popover.offsetHeight;
|
const height = popover.offsetHeight;
|
||||||
|
@ -100,102 +122,143 @@ export default defineComponent({
|
||||||
let left;
|
let left;
|
||||||
let top;
|
let top;
|
||||||
|
|
||||||
if (this.srcCenter) {
|
if (props.srcCenter) {
|
||||||
const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
|
const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2);
|
||||||
const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + (this.src.offsetHeight / 2);
|
const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + (props.src.offsetHeight / 2);
|
||||||
left = (x - (width / 2));
|
left = (x - (width / 2));
|
||||||
top = (y - (height / 2));
|
top = (y - (height / 2));
|
||||||
} else {
|
} else {
|
||||||
const x = rect.left + (this.fixed ? 0 : window.pageXOffset) + (this.src.offsetWidth / 2);
|
const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2);
|
||||||
const y = rect.top + (this.fixed ? 0 : window.pageYOffset) + this.src.offsetHeight;
|
const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + props.src.offsetHeight;
|
||||||
left = (x - (width / 2));
|
left = (x - (width / 2));
|
||||||
top = y;
|
top = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fixed) {
|
if (fixed.value) {
|
||||||
|
// 画面から横にはみ出る場合
|
||||||
if (left + width > window.innerWidth) {
|
if (left + width > window.innerWidth) {
|
||||||
left = window.innerWidth - width;
|
left = window.innerWidth - width;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top + height > window.innerHeight) {
|
// 画面から縦にはみ出る場合
|
||||||
top = window.innerHeight - height;
|
if (top + height > (window.innerHeight - MARGIN)) {
|
||||||
|
if (props.noOverlap) {
|
||||||
|
const underSpace = (window.innerHeight - MARGIN) - top;
|
||||||
|
const upperSpace = (rect.top - MARGIN);
|
||||||
|
if (underSpace >= (upperSpace / 3)) {
|
||||||
|
maxHeight.value = underSpace;
|
||||||
|
} else {
|
||||||
|
maxHeight.value = upperSpace;
|
||||||
|
top = (upperSpace + MARGIN) - height;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
top = (window.innerHeight - MARGIN) - height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 画面から横にはみ出る場合
|
||||||
if (left + width - window.pageXOffset > window.innerWidth) {
|
if (left + width - window.pageXOffset > window.innerWidth) {
|
||||||
left = window.innerWidth - width + window.pageXOffset - 1;
|
left = window.innerWidth - width + window.pageXOffset - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top + height - window.pageYOffset > window.innerHeight) {
|
// 画面から縦にはみ出る場合
|
||||||
top = window.innerHeight - height + window.pageYOffset - 1;
|
if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
|
||||||
|
if (props.noOverlap) {
|
||||||
|
const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset);
|
||||||
|
const upperSpace = (rect.top - MARGIN);
|
||||||
|
if (underSpace >= (upperSpace / 3)) {
|
||||||
|
maxHeight.value = underSpace;
|
||||||
|
} else {
|
||||||
|
maxHeight.value = upperSpace;
|
||||||
|
top = window.pageYOffset + ((upperSpace + MARGIN) - height);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top < 0) {
|
if (top < 0) {
|
||||||
top = 0;
|
top = MARGIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left < 0) {
|
if (left < 0) {
|
||||||
left = 0;
|
left = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) {
|
if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) {
|
||||||
this.transformOrigin = 'center top';
|
transformOrigin.value = 'center top';
|
||||||
|
} else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) {
|
||||||
|
transformOrigin.value = 'center bottom';
|
||||||
} else {
|
} else {
|
||||||
this.transformOrigin = 'center';
|
transformOrigin.value = 'center';
|
||||||
}
|
}
|
||||||
|
|
||||||
popover.style.left = left + 'px';
|
popover.style.left = left + 'px';
|
||||||
popover.style.top = top + 'px';
|
popover.style.top = top + 'px';
|
||||||
},
|
};
|
||||||
|
|
||||||
childRendered() {
|
const childRendered = () => {
|
||||||
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
|
||||||
const content = this.$refs.content.children[0];
|
const el = content.value!.children[0];
|
||||||
content.addEventListener('mousedown', e => {
|
el.addEventListener('mousedown', e => {
|
||||||
this.contentClicking = true;
|
contentClicking = true;
|
||||||
window.addEventListener('mouseup', e => {
|
window.addEventListener('mouseup', e => {
|
||||||
// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ
|
// click イベントより先に mouseup イベントが発生するかもしれないのでちょっと待つ
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.contentClicking = false;
|
contentClicking = false;
|
||||||
}, 100);
|
}, 100);
|
||||||
}, { passive: true, once: true });
|
}, { passive: true, once: true });
|
||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
},
|
};
|
||||||
|
|
||||||
close() {
|
onMounted(() => {
|
||||||
this.showing = false;
|
watch(() => props.src, async () => {
|
||||||
this.$emit('close');
|
if (props.src) {
|
||||||
},
|
// eslint-disable-next-line vue/no-mutating-props
|
||||||
|
props.src.style.pointerEvents = 'none';
|
||||||
onBgClick() {
|
|
||||||
if (this.contentClicking) return;
|
|
||||||
this.$emit('click');
|
|
||||||
},
|
|
||||||
|
|
||||||
onClosed() {
|
|
||||||
this.$emit('closed');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
align();
|
||||||
|
}, { immediate: true, });
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const popover = content.value;
|
||||||
|
new ResizeObserver((entries, observer) => {
|
||||||
|
align();
|
||||||
|
}).observe(popover!);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
showing,
|
||||||
|
type,
|
||||||
|
fixed,
|
||||||
|
content,
|
||||||
|
transformOrigin,
|
||||||
|
maxHeight,
|
||||||
|
close,
|
||||||
|
zIndex,
|
||||||
|
keymap,
|
||||||
|
onBgClick,
|
||||||
|
childRendered,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.modal-popup-enter-active, .modal-popup-leave-active,
|
|
||||||
.modal-enter-from, .modal-leave-to {
|
|
||||||
> .content {
|
|
||||||
transform-origin: var(--transformOrigin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.modal-enter-active, .modal-leave-active {
|
.modal-enter-active, .modal-leave-active {
|
||||||
> .bg {
|
> .bg {
|
||||||
transition: opacity 0.3s !important;
|
transition: opacity 0.2s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .content {
|
> .content {
|
||||||
transition: opacity 0.3s, transform 0.3s !important;
|
transform-origin: var(--transformOrigin);
|
||||||
|
transition: opacity 0.2s, transform 0.2s !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modal-enter-from, .modal-leave-to {
|
.modal-enter-from, .modal-leave-to {
|
||||||
|
@ -206,17 +269,19 @@ export default defineComponent({
|
||||||
> .content {
|
> .content {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform-origin: var(--transformOrigin);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-popup-enter-active, .modal-popup-leave-active {
|
.modal-popup-enter-active, .modal-popup-leave-active {
|
||||||
> .bg {
|
> .bg {
|
||||||
transition: opacity 0.3s !important;
|
transition: opacity 0.2s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .content {
|
> .content {
|
||||||
transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
transform-origin: var(--transformOrigin);
|
||||||
|
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.modal-popup-enter-from, .modal-popup-leave-to {
|
.modal-popup-enter-from, .modal-popup-leave-to {
|
||||||
|
@ -227,12 +292,43 @@ export default defineComponent({
|
||||||
> .content {
|
> .content {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform-origin: var(--transformOrigin);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-drawer-enter-active {
|
||||||
|
> .bg {
|
||||||
|
transition: opacity 0.2s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-drawer-leave-active {
|
||||||
|
> .bg {
|
||||||
|
transition: opacity 0.2s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-drawer-enter-from, .modal-drawer-leave-to {
|
||||||
|
> .bg {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.qzhlnise {
|
.qzhlnise {
|
||||||
> .content:not(.popup) {
|
&.dialog {
|
||||||
|
> .content {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -262,8 +358,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> .content.popup {
|
&.popup {
|
||||||
|
> .content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
&.fixed {
|
&.fixed {
|
||||||
|
@ -271,4 +369,27 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.drawer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: clip;
|
||||||
|
|
||||||
|
> .content {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
> ::v-deep(*) {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<MkPopup ref="popup" v-slot="{ maxHeight, close }" :src="src" @closed="$emit('closed')">
|
<MkModal ref="modal" v-slot="{ type, maxHeight }" :src="src" @click="$refs.modal.close()" @closed="$emit('closed')">
|
||||||
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" class="_popup _shadow" @close="close()"/>
|
<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" class="sfhdhdhq _popup _shadow" :class="{ drawer: type === 'drawer' }" @close="$refs.modal.close()"/>
|
||||||
</MkPopup>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import MkPopup from './popup.vue';
|
import MkModal from './modal.vue';
|
||||||
import MkMenu from './menu.vue';
|
import MkMenu from './menu.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MkPopup,
|
MkModal,
|
||||||
MkMenu,
|
MkMenu,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,3 +40,13 @@ export default defineComponent({
|
||||||
emits: ['close', 'closed'],
|
emits: ['close', 'closed'],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.sfhdhdhq {
|
||||||
|
&.drawer {
|
||||||
|
border-radius: 24px;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
<template>
|
|
||||||
<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="$emit('closed')" @enter="$emit('opening')">
|
|
||||||
<div v-show="manualShowing != null ? manualShowing : showing" ref="content" class="ccczpooj" :class="{ fixed, top: position === 'top' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
|
|
||||||
<slot :max-height="maxHeight" :close="close"></slot>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, nextTick, onMounted, onUnmounted, PropType, ref, watch } from 'vue';
|
|
||||||
import * as os from '@/os';
|
|
||||||
|
|
||||||
function getFixedContainer(el: Element | null | undefined): Element | null {
|
|
||||||
if (el == null || el.tagName === 'BODY') return null;
|
|
||||||
const position = window.getComputedStyle(el).getPropertyValue('position');
|
|
||||||
if (position === 'fixed') {
|
|
||||||
return el;
|
|
||||||
} else {
|
|
||||||
return getFixedContainer(el.parentElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
manualShowing: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
srcCenter: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
src: {
|
|
||||||
type: Object as PropType<HTMLElement>,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
position: {
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
front: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
noOverlap: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
emits: ['opening', 'click', 'esc', 'close', 'closed'],
|
|
||||||
|
|
||||||
setup(props, context) {
|
|
||||||
const maxHeight = ref<number>();
|
|
||||||
const fixed = ref(false);
|
|
||||||
const transformOrigin = ref('center');
|
|
||||||
const showing = ref(true);
|
|
||||||
const content = ref<HTMLElement>();
|
|
||||||
const zIndex = os.claimZIndex(props.front);
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
if (props.src) props.src.style.pointerEvents = 'auto';
|
|
||||||
showing.value = false;
|
|
||||||
context.emit('close');
|
|
||||||
};
|
|
||||||
|
|
||||||
const MARGIN = 16;
|
|
||||||
|
|
||||||
const align = () => {
|
|
||||||
if (props.src == null) return;
|
|
||||||
|
|
||||||
const popover = content.value!;
|
|
||||||
|
|
||||||
if (popover == null) return;
|
|
||||||
|
|
||||||
const rect = props.src.getBoundingClientRect();
|
|
||||||
|
|
||||||
const width = popover.offsetWidth;
|
|
||||||
const height = popover.offsetHeight;
|
|
||||||
|
|
||||||
let left;
|
|
||||||
let top;
|
|
||||||
|
|
||||||
if (props.srcCenter) {
|
|
||||||
const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2);
|
|
||||||
const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + (props.src.offsetHeight / 2);
|
|
||||||
left = (x - (width / 2));
|
|
||||||
top = (y - (height / 2));
|
|
||||||
} else {
|
|
||||||
const x = rect.left + (fixed.value ? 0 : window.pageXOffset) + (props.src.offsetWidth / 2);
|
|
||||||
const y = rect.top + (fixed.value ? 0 : window.pageYOffset) + props.src.offsetHeight;
|
|
||||||
left = (x - (width / 2));
|
|
||||||
top = y;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fixed.value) {
|
|
||||||
// 画面から横にはみ出る場合
|
|
||||||
if (left + width > window.innerWidth) {
|
|
||||||
left = window.innerWidth - width;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 画面から縦にはみ出る場合
|
|
||||||
if (top + height > (window.innerHeight - MARGIN)) {
|
|
||||||
if (props.noOverlap) {
|
|
||||||
const underSpace = (window.innerHeight - MARGIN) - top;
|
|
||||||
const upperSpace = (rect.top - MARGIN);
|
|
||||||
if (underSpace >= (upperSpace / 3)) {
|
|
||||||
maxHeight.value = underSpace;
|
|
||||||
} else {
|
|
||||||
maxHeight.value = upperSpace;
|
|
||||||
top = (upperSpace + MARGIN) - height;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
top = (window.innerHeight - MARGIN) - height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 画面から横にはみ出る場合
|
|
||||||
if (left + width - window.pageXOffset > window.innerWidth) {
|
|
||||||
left = window.innerWidth - width + window.pageXOffset - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 画面から縦にはみ出る場合
|
|
||||||
if (top + height - window.pageYOffset > (window.innerHeight - MARGIN)) {
|
|
||||||
if (props.noOverlap) {
|
|
||||||
const underSpace = (window.innerHeight - MARGIN) - (top - window.pageYOffset);
|
|
||||||
const upperSpace = (rect.top - MARGIN);
|
|
||||||
if (underSpace >= (upperSpace / 3)) {
|
|
||||||
maxHeight.value = underSpace;
|
|
||||||
} else {
|
|
||||||
maxHeight.value = upperSpace;
|
|
||||||
top = window.pageYOffset + ((upperSpace + MARGIN) - height);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
top = (window.innerHeight - MARGIN) - height + window.pageYOffset - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top < 0) {
|
|
||||||
top = MARGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left < 0) {
|
|
||||||
left = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top > rect.top + (fixed.value ? 0 : window.pageYOffset)) {
|
|
||||||
transformOrigin.value = 'center top';
|
|
||||||
} else if ((top + height) <= rect.top + (fixed.value ? 0 : window.pageYOffset)) {
|
|
||||||
transformOrigin.value = 'center bottom';
|
|
||||||
} else {
|
|
||||||
transformOrigin.value = 'center';
|
|
||||||
}
|
|
||||||
|
|
||||||
popover.style.left = left + 'px';
|
|
||||||
popover.style.top = top + 'px';
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDocumentClick = (ev: MouseEvent) => {
|
|
||||||
const flyoutElement = content.value;
|
|
||||||
let targetElement = ev.target;
|
|
||||||
do {
|
|
||||||
if (targetElement === flyoutElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
targetElement = targetElement.parentNode;
|
|
||||||
} while (targetElement);
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
watch(() => props.src, async () => {
|
|
||||||
if (props.src) {
|
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
|
||||||
props.src.style.pointerEvents = 'none';
|
|
||||||
}
|
|
||||||
fixed.value = getFixedContainer(props.src) != null;
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
align();
|
|
||||||
}, { immediate: true, });
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
const popover = content.value;
|
|
||||||
new ResizeObserver((entries, observer) => {
|
|
||||||
align();
|
|
||||||
}).observe(popover!);
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', onDocumentClick, { passive: true });
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('mousedown', onDocumentClick);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
showing,
|
|
||||||
fixed,
|
|
||||||
content,
|
|
||||||
transformOrigin,
|
|
||||||
maxHeight,
|
|
||||||
close,
|
|
||||||
zIndex,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.popup-menu-enter-active {
|
|
||||||
transform-origin: var(--transformOrigin);
|
|
||||||
transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
|
|
||||||
}
|
|
||||||
.popup-menu-leave-active {
|
|
||||||
transform-origin: var(--transformOrigin);
|
|
||||||
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), transform 0.2s cubic-bezier(0.4, 0, 1, 1) !important;
|
|
||||||
}
|
|
||||||
.popup-menu-enter-from, .popup-menu-leave-to {
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ccczpooj {
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
&.fixed {
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
Reference in New Issue