This commit is contained in:
lyswhut
2022-10-29 11:36:35 +08:00
parent 66b965d9f7
commit e45cee6596
715 changed files with 57096 additions and 31771 deletions
+12 -27
View File
@@ -1,9 +1,11 @@
<template>
<button
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
:disabled="disabled">
<slot />
</button>
<button
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
tabindex="0"
:disabled="disabled"
>
<slot />
</button>
</template>
<script>
@@ -34,13 +36,14 @@ export default {
border-radius: @form-radius;
cursor: pointer;
padding: 8px 15px;
color: @color-btn;
color: var(--color-button-font);
outline: none;
transition: background-color 0.2s ease;
background-color: @color-btn-background;
background-color: var(--color-button-background);
font-size: 14px;
&[disabled] {
opacity: .4;
cursor: default;
}
&.outline {
@@ -48,10 +51,10 @@ export default {
}
&:hover {
background-color: @color-btn-hover;
background-color: var(--color-button-background-hover);
}
&:active {
background-color: @color-btn-active;
background-color: var(--color-button-background-active);
}
}
@@ -60,22 +63,4 @@ export default {
font-size: 12px;
}
each(@themes, {
:global(#root.@{value}) {
.btn {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&.outline {
background-color: transparent;
}
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
&:active {
background-color: ~'@{color-@{value}-btn-active}';
}
}
}
})
</style>
+60 -96
View File
@@ -1,50 +1,60 @@
<template lang="pug">
div(:class="$style.checkbox")
input(:type="need ? 'radio' : 'checkbox'" :class="$style.input" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
label(:for="id" :class="$style.content")
div(v-if="indeterminate" :class="$style.container")
svg(v-show="indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-indeterminate')
svg(v-show="!indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
use(xlink:href='#icon-check-true')
div(v-else :class="$style.container")
svg(version='1.1' :class="$style.icon" xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-true')
span(v-if="label != null" v-html="label" :class="$style.label")
<template>
<div :class="$style.checkbox">
<input
:id="id" :type="need ? 'radio' : 'checkbox'" :aria-hidden="true" :checked="checked"
:class="$style.input" :disabled="disabled" :value="value" :name="name" @input="handleInput"
>
<label :for="id" :class="$style.content">
<div :class="$style.container" role="checkbox" tabindex="0" :aria-label="ariaLabel || label" :aria-checked="checked" :aria-disabled="disabled">
<svg version="1.1" :class="$style.icon" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" width="100%" viewBox="0 32 448 448" space="preserve">
<use xlink:href="#icon-check-true" />
</svg>
</div>
<span v-if="label != null" :class="$style.label" v-text="label" />
</label>
</div>
</template>
<script>
export default {
props: {
modelValue: {},
value: {},
modelValue: {
type: [String, Boolean, Number, Array],
required: true,
},
value: {
type: [String, Boolean, Number, Array],
default: undefined,
},
id: {
type: String,
required: true,
},
name: {
type: String,
default: undefined,
},
need: {
type: Boolean,
default: false,
},
ariaLabel: {
type: String,
default: undefined,
},
label: {
type: String,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
indeterminate: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'change'],
data() {
return {
bool: false,
checked: false,
}
},
watch: {
@@ -56,57 +66,38 @@ export default {
this.setValue(this.modelValue)
},
methods: {
change() {
let checked
handleInput(event) {
const checked = event.target.checked
let modelValue
if (Array.isArray(this.modelValue)) {
checked = [...this.modelValue]
const index = checked.indexOf(this.value)
if (index < 0) {
checked.push(this.value)
} else {
checked.splice(index, 1)
modelValue = [...this.modelValue]
if (checked) modelValue.push(this.value)
else {
const index = modelValue.indexOf(this.value)
if (index > -1) modelValue.splice(index, 1)
}
} else {
let bool = this.bool
switch (typeof this.modelValue) {
case 'boolean':
if (this.indeterminate) {
bool = true
this.$nextTick(() => {
this.bool = bool
})
}
checked = bool
break
case 'number':
checked = this.value
break
default:
checked = bool ? this.value : ''
break
}
if (typeof this.modelValue == 'boolean') {
modelValue = checked
} else modelValue = checked ? this.value : ''
}
this.$emit('update:modelValue', checked)
this.$emit('change', checked)
this.$emit('update:modelValue', modelValue)
this.$emit('change', modelValue)
},
setValue(value) {
let bool
let checked
if (Array.isArray(value)) {
bool = value.includes(this.value)
checked = value.includes(this.value)
} else {
switch (typeof value) {
case 'boolean':
bool = value
break
// case 'string':
// case 'number':
default:
bool = value === this.value
break
}
if (typeof this.modelValue == 'boolean') {
checked = this.modelValue
} else if (value == null) checked = this.modelValue != ''
else checked = this.modelValue == this.value
}
this.bool = this.need ? bool && this.value : bool
// console.log(this.need, this.value, checked)
// this.checked = this.need ? checked && this.value : checked
if (this.checked == checked) return
this.checked = checked
},
},
}
@@ -125,13 +116,16 @@ export default {
&[disabled] {
+ .content {
opacity: .5;
.container, .label {
cursor: default;
}
}
}
&:checked {
+ .content {
.container {
&:after {
border-color: @color-theme;
border-color: var(--color-primary-font);
}
}
.icon {
@@ -143,19 +137,16 @@ export default {
}
.content {
display: flex;
justify-content: center;
align-items: center;
}
.container {
flex: none;
position: relative;
display: inline-block;
width: 1em;
height: 1em;
cursor: pointer;
display: flex;
color: @color-theme;
margin-top: .25em;
color: var(--color-primary);
// border: 1px solid #ccc;
&:after {
position: absolute;
@@ -164,7 +155,7 @@ export default {
bottom: 0;
left: 0;
right: 0;
border: 1px solid @color-theme_2-font-label;
border: 1px solid var(--color-font-label);
transition: border-color 0.2s ease;
border-radius: 2px;
}
@@ -179,36 +170,9 @@ export default {
.label {
flex: auto;
line-height: 1;
margin-left: 5px;
line-height: 1.5;
cursor: pointer;
}
each(@themes, {
:global(#root.@{value}) {
.checkbox {
> input {
&:checked {
+ .content {
.container {
&:after {
border-color: ~'@{color-@{value}-theme}';
}
}
}
}
}
}
.content {
.container {
color: ~'@{color-@{value}-theme}';
// border: 1px solid #ccc;
&:after {
border-color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
}
})
</style>
+16 -30
View File
@@ -1,15 +1,16 @@
<template>
<input
:class="$style.input"
ref="dom_input"
:type="type"
:placeholder="placeholder"
:value="modelValue"
:disabled="disabled"
@input="handleInput"
@change="$emit('change', $event.target.value.trim())"
@keyup.enter="$emit('submit', $event.target.value.trim())"
/>
<input
ref="dom_input"
:class="$style.input"
:type="type"
:placeholder="placeholder"
:value="modelValue"
:disabled="disabled"
tabindex="0"
@input="handleInput"
@change="$emit('change', $event.target.value.trim())"
@keyup.enter="$emit('submit', $event.target.value.trim())"
>
</template>
<script>
@@ -55,10 +56,10 @@ export default {
border: none;
border-radius: @form-radius;
padding: 7px 8px;
color: @color-btn;
color: var(--color-button-font);
outline: none;
transition: background-color 0.2s ease;
background-color: @color-btn-background;
background-color: var(--color-primary-background);
font-size: 13.3px;
&::-webkit-outer-spin-button,
@@ -72,10 +73,10 @@ export default {
}
&:hover, &:focus {
background-color: @color-theme_2-hover;
background-color: var(--color-primary-background-hover);
}
&:active {
background-color: @color-theme_2-active;
background-color: var(--color-primary-background-active);
}
}
@@ -84,19 +85,4 @@ export default {
font-size: 12px;
}
each(@themes, {
:global(#root.@{value}) {
.input {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover, &:focus {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
})
</style>
+81 -147
View File
@@ -1,22 +1,42 @@
<template>
<ul :class="$style.list" :style="listStyles" ref="dom_list" role="toolbar" :aria-hidden="!show">
<li v-for="item in menus"
role="tab"
tabindex="0"
:aria-label="item[itemName]"
ignore-tip
:key="item.action"
@click="handleClick(item)"
v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)"
:disabled="item.disabled ? true : null">{{item[itemName]}}</li>
</ul>
<teleport to="#root">
<ul ref="dom_menu" :class="$style.list" :style="menuStyles" role="toolbar" :aria-hidden="!modelValue">
<li
v-for="item in menus"
v-show="!item.hide && (item.action == 'download' ? appSetting['download.enable'] : true)"
:key="item.action"
:class="$style.listItem"
role="tab"
tabindex="0"
:aria-label="item[itemName]"
ignore-tip
:disabled="item.disabled ? true : null"
@click="menuClick(item)"
>
{{ item[itemName] }}
</li>
</ul>
</teleport>
</template>
<script>
import { mapGetters } from 'vuex'
import { computed } from '@common/utils/vueTools'
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
import { appSetting } from '@renderer/store/setting'
export default {
name: 'MenuToolBar',
props: {
modelValue: {
type: Boolean,
required: true,
},
xy: {
type: Object,
required: true,
},
menus: {
type: Array,
default() {
@@ -27,103 +47,34 @@ export default {
type: String,
default: 'name',
},
isShow: {
type: Boolean,
default: false,
},
location: {
type: Object,
default() {
return { x: 0, y: 0 }
},
},
},
emits: ['menu-click'],
computed: {
...mapGetters(['setting']),
},
watch: {
isShow: {
handler(n) {
n ? this.handleShow() : this.handleHide()
},
immediate: true,
},
location: {
handler(n) {
this.listStyles.left = n.x + 2 + 'px'
this.listStyles.top = n.y + 'px'
if (this.show) {
if (this.listStyles.transitionProperty != this.transition2) this.listStyles.transitionProperty = this.transition2
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(n.x, n.y)})`
}
},
deep: true,
},
},
data() {
return {
show: false,
listStyles: {
left: 0,
top: 0,
opacity: 0,
transitionProperty: 'transform, opacity',
transform: 'scale(0) translate(0,0)',
},
transition1: 'transform, opacity',
transition2: 'transform, opacity, top, left',
transitionProperty: 'transform, opacity',
emits: ['update:modelValue', 'menu-click'],
setup(props, { emit }) {
const visible = computed(() => props.modelValue)
const location = computed(() => props.xy)
const onHide = () => {
emit('update:modelValue', false)
menuClick(null)
}
},
mounted() {
document.addEventListener('click', this.handleDocumentClick)
},
beforeUnmount() {
document.removeEventListener('click', this.handleDocumentClick)
},
methods: {
handleShow() {
this.show = true
this.listStyles.opacity = 1
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(this.location.x, this.location.y)})`
},
handleHide(e) {
this.listStyles.opacity = 0
this.listStyles.transform = 'scale(0) translate(0, 0)'
this.show = false
},
handleGetOffsetXY(left, top) {
const listWidth = this.$refs.dom_list.clientWidth
const listHeight = this.$refs.dom_list.clientHeight
const dom_container = this.$refs.dom_list.offsetParent
const containerWidth = dom_container.clientWidth
const containerHeight = dom_container.clientHeight
const offsetWidth = containerWidth - left - listWidth
const offsetHeight = containerHeight - top - listHeight
let x = 0
let y = 0
if (containerWidth > listWidth && offsetWidth < 17) {
x = offsetWidth - 17
}
if (containerHeight > listHeight && offsetHeight < 5) {
y = offsetHeight - 5
}
return `${x}px, ${y}px`
},
handleDocumentClick(event) {
if (!this.show) return
if (event.target == this.$refs.dom_list || this.$refs.dom_list.contains(event.target)) return
const { dom_menu, menuStyles } = useMenuLocation({
visible,
location,
onHide,
})
if (this.show && this.listStyles.transitionProperty != this.transition1) this.listStyles.transitionProperty = this.transition1
this.handleClick(null)
},
handleClick(item) {
const menuClick = (item) => {
if (item && item.disabled) return
emit('menu-click', item)
}
this.$emit('menu-click', item)
},
return {
dom_menu,
menuStyles,
menuClick,
appSetting,
}
},
}
</script>
@@ -138,60 +89,43 @@ export default {
opacity: 0;
transform: scale(0);
transform-origin: 0 0 0;
transition: .25s ease;
transition: .14s ease;
transition-property: transform, opacity;
border-radius: @radius-border;
background-color: @color-theme_2-background_2;
background-color: var(--color-content-background);
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
z-index: 10;
overflow: hidden;
// will-change: transform;
li {
cursor: pointer;
min-width: 96px;
line-height: 34px;
// color: @color-btn;
padding: 0 10px;
text-align: center;
outline: none;
transition: @transition-theme;
transition-property: background-color, opacity;
box-sizing: border-box;
.mixin-ellipsis-1;
}
.listItem {
cursor: pointer;
min-width: 96px;
line-height: 34px;
// color: var(--color-button-font);
padding: 0 10px;
text-align: center;
outline: none;
transition: @transition-normal;
transition-property: background-color, opacity;
box-sizing: border-box;
.mixin-ellipsis-1;
// background-color: var(--color-primary-light-600-alpha-800);
&:hover {
background-color: var(--color-primary-background-hover);
}
&:active {
background-color: var(--color-primary-background-active);
}
&[disabled] {
cursor: default;
opacity: .4;
&:hover {
background-color: @color-theme_2-hover;
}
&:active {
background-color: @color-theme_2-active;
}
&[disabled] {
cursor: default;
opacity: .4;
&:hover {
background: none !important;
}
background: none !important;
}
}
}
each(@themes, {
:global(#root.@{value}) {
.list {
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&:active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
}
}
})
</style>
+301
View File
@@ -0,0 +1,301 @@
<template>
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
<component :is="contentEl" ref="dom_list" :class="contentClass" :style="contentStyle">
<!-- <div v-for="item in views" :key="item.key" :style="item.style">
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
</div> -->
</component>
<slot name="footer" />
</component>
</template>
<script>
import {
computed,
ref,
nextTick,
watch,
onMounted,
onBeforeUnmount,
render,
// h,
} from 'vue'
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel = () => {}) => {
if (!element) return callback()
const start = element.scrollTop || element.scrollY || 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
} else if (to < start) {
if (to < 0) to = 0
} else return callback()
const change = to - start
const increment = 10
if (!change) return callback()
let currentTime = 0
let val
let cancelCallback
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(0, val)
} else {
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) {
cancelCallback()
onCancel()
return
}
setTimeout(animateScroll, increment)
} else {
callback()
}
}
animateScroll()
return (callback) => {
cancelCallback = callback
cancel = true
}
}
export default {
name: 'VirtualizedList',
props: {
containerEl: {
type: String,
default: 'div',
},
containerClass: {
type: String,
default: 'virtualized-list',
},
contentEl: {
type: String,
default: 'div',
},
contentClass: {
type: String,
default: 'virtualized-list-content',
},
itemHeight: {
type: Number,
required: true,
},
keyName: {
type: String,
required: true,
},
list: {
type: Array,
required: true,
},
},
emits: ['scroll'],
setup(props, { emit, slots }) {
const views = ref([])
const dom_scrollContainer = ref(null)
const dom_list = ref(null)
let startIndex = -1
let endIndex = -1
let scrollTop = -1
let cachedList = []
let cancelScroll = null
let isScrolling = false
let scrollToValue = 0
const createList = (startIndex, endIndex) => {
if (startIndex == endIndex) return []
console.log(startIndex, endIndex)
const cache = cachedList.slice(startIndex, endIndex)
const list = props.list.slice(startIndex, endIndex).map((item, i) => {
if (cache[i]) return cache[i]
const top = (startIndex + i) * props.itemHeight
const index = startIndex + i
return cachedList[index] = {
item,
top,
style: { position: 'absolute', left: 0, right: 0, top: top + 'px', height: props.itemHeight + 'px' },
index,
key: item[props.keyName],
}
})
return list
}
const renderListItem = (list, type) => {
if (!list.length) return
console.log(list)
// const dom = document.createDocumentFragment()
switch (type) {
case 'up':
break
case 'down':
break
default:
console.log(render(null, slots.default(list[0])[0]))
break
}
}
const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
// const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
const itemHeight = props.itemHeight
const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
const currentEndRenderIndex = currentEndIndex + 1
// console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
// debugger
if (!force && continuous) {
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
// console.log('update')
if (currentScrollTop > scrollTop) { // scroll down
console.log('scroll down')
renderListItem(createList(endIndex + 1, currentEndRenderIndex))
// // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
// // // if (this.views.length > 100) {
// // nextTick(() => {
// // views.value.splice(0, views.value.indexOf(list[0]))
// // })
// // }
} else if (currentScrollTop < scrollTop) { // scroll up
console.log('scroll up')
renderListItem(createList(currentStartRenderIndex, startIndex))
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
} else return
// if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
} else {
renderListItem(createList(currentStartRenderIndex, currentEndRenderIndex))
}
startIndex = currentStartIndex
endIndex = currentEndIndex
scrollTop = currentScrollTop
}
const onScroll = event => {
const currentScrollTop = dom_scrollContainer.value.scrollTop
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
updateView(false, currentScrollTop)
}
emit('scroll', event)
}
const scrollTo = (scrollTop, animate = false) => {
return new Promise(resolve => {
if (cancelScroll) {
cancelScroll(resolve)
} else {
resolve()
}
}).then(() => {
return new Promise((resolve, reject) => {
if (animate) {
isScrolling = true
scrollToValue = scrollTop
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
cancelScroll = null
isScrolling = false
resolve()
}, () => {
cancelScroll = null
isScrolling = false
reject('canceled')
})
} else {
dom_scrollContainer.value.scrollTop = scrollTop
}
})
})
}
const scrollToIndex = (index, offset = 0, animate = false) => {
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
}
const getScrollTop = () => {
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
}
const handleResize = () => {
setTimeout(updateView)
}
const contentStyle = computed(() => ({
display: 'block',
height: props.list.length * props.itemHeight + 'px',
}))
const handleReset = list => {
cachedList = Array(list.length)
startIndex = -1
endIndex = -1
nextTick(() => {
updateView(true)
})
}
watch(() => props.itemHeight, () => {
handleReset(props.list)
})
watch(() => props.list, (list) => {
handleReset(list)
}, {
deep: true,
})
onMounted(() => {
dom_scrollContainer.value.addEventListener('scroll', onScroll, false)
cachedList = Array(props.list.length)
startIndex = -1
endIndex = -1
updateView(true)
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
dom_scrollContainer.value.removeEventListener('scroll', onScroll)
window.removeEventListener('resize', handleResize)
if (cancelScroll) cancelScroll()
})
return {
views,
dom_scrollContainer,
dom_list,
contentStyle,
scrollTo,
scrollToIndex,
getScrollTop,
}
},
// render() {
// console.log('render')
// // const list = this.views.map((item) => {
// // return h('div', { style: item.style, key: item.key }, this.$slots.default(item))
// // })
// return h(this.containerEl, {
// ref: 'dom_scrollContainer',
// class: this.containerClass,
// style: 'outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;',
// }, [
// h(this.contentEl, { style: this.contentStyle, class: this.contentClass }, this.views.map((item) => {
// return h('div', { style: item.style, key: item.key }, 'this.$slots.default(item)')
// })),
// ])
// },
}
</script>
-167
View File
@@ -1,167 +0,0 @@
<template>
<div :class="[$style.select, {[$style.active]: show}]">
<div :class="$style.label" ref="dom_btn" @click="handleShow">{{modelValue ? itemName ? list.find(l => l.id == modelValue).name : modelValue : ''}}</div>
<ul :class="$style.list">
<li v-for="(item, index) in list" :key="index" @click="handleClick(itemKey ? item[itemKey] : item)">{{itemName ? item[itemName] : item}}</li>
</ul>
</div>
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
},
},
modelValue: {
type: [String, Number],
},
itemName: {
type: String,
},
itemKey: {
type: String,
},
},
data() {
return {
show: false,
}
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeUnmount() {
document.removeEventListener('click', this.handleHide)
},
methods: {
handleHide(e) {
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && e.target == this.$refs.dom_btn) return
setTimeout(() => {
this.show = false
}, 50)
},
handleClick(item) {
if (item === this.modelValue) return
this.$emit('update:modelValue', item)
this.$emit('change', item)
},
handleShow() {
this.show = !this.show
},
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.select {
font-size: 12px;
position: relative;
&.active {
.list {
transform: scaleY(1);
opacity: 1;
}
}
}
.label {
padding: 8px 15px;
// background-color: @color-btn-background;
transition: background-color @transition-theme;
border-top: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
box-sizing: border-box;
text-align: center;
border-top-left-radius: @radius-border;
color: @color-btn;
cursor: pointer;
.mixin-ellipsis-1;
&:hover {
background-color: @color-btn-hover;
}
&:active {
background-color: @color-btn-active;
}
}
.list {
position: absolute;
width: 100%;
top: 100%;
left: 0;
border-bottom: 2px solid @color-tab-border-bottom;
border-left: 2px solid @color-tab-border-bottom;
border-bottom-left-radius: @radius-border;
background-color: @color-theme_2-background_2;
opacity: 0;
transform: scaleY(0);
transform-origin: 0 0 0;
transition: .25s ease;
transition-property: transform, opacity;
z-index: 10;
li {
cursor: pointer;
padding: 8px 15px;
// color: @color-btn;
text-align: center;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
.mixin-ellipsis-1;
&:hover {
background-color: @color-btn-hover;
}
&:active {
background-color: @color-btn-active;
}
}
}
each(@themes, {
:global(#root.@{value}) {
.label {
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
color: ~'@{color-@{value}-btn}';
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
&:active {
background-color: ~'@{color-@{value}-btn-active}';
}
}
.list {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
&:active {
background-color: ~'@{color-@{value}-btn-active}';
}
}
}
}
})
</style>
+58 -79
View File
@@ -1,18 +1,22 @@
<template>
<div class="content" :class="[$style.select, show ? $style.active : '']">
<div class="label-content" :class="$style.label" ref="dom_btn" @click="handleShow">
<span class="label">{{label}}</span>
<div class="icon" :class="$style.icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
<use xlink:href="#icon-down"></use>
</svg>
<div class="content" :class="[$style.select, show ? $style.active : '']">
<div ref="dom_btn" class="label-content" :class="$style.label" @click="handleShow">
<span class="label">{{ label }}</span>
<div class="icon" :class="$style.icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
<use xlink:href="#icon-down" />
</svg>
</div>
</div>
<ul v-if="show" ref="dom_list" class="selection-list scroll" :class="$style.list" :style="listStyles">
<li
v-for="(item, index) in list" :key="index" :class="[$style.listItem, (itemKey ? item[itemKey] : item) == modelValue ? $style.active : null]"
:aria-label="itemName ? item[itemName] : item" @click="handleClick(item)"
>
{{ itemName ? item[itemName] : item }}
</li>
</ul>
</div>
<ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list">
<li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null"
@click="handleClick(item)" :aria-label="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li>
</ul>
</div>
</template>
<script>
@@ -27,14 +31,18 @@ export default {
},
modelValue: {
type: [String, Number],
required: true,
},
itemName: {
type: String,
default: '',
},
itemKey: {
type: String,
default: '',
},
},
emits: ['update:modelValue', 'change'],
data() {
return {
show: false,
@@ -43,12 +51,6 @@ export default {
},
}
},
mounted() {
document.addEventListener('click', this.handleHide)
},
beforeUnmount() {
document.removeEventListener('click', this.handleHide)
},
computed: {
label() {
if (this.modelValue == null) return ''
@@ -58,8 +60,15 @@ export default {
return item[this.itemName]
},
},
mounted() {
document.addEventListener('click', this.handleHide, true)
},
beforeUnmount() {
document.removeEventListener('click', this.handleHide, true)
},
methods: {
handleHide(e) {
if (!this.show) return
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
if (e && (e.target == this.$refs.dom_btn || this.$refs.dom_btn.contains(e.target))) return
this.listStyles.transform = 'scaleY(0) translateY(0)'
@@ -75,7 +84,9 @@ export default {
},
handleShow() {
this.show = true
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
this.$nextTick(() => {
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
})
},
handleGetOffset() {
const listHeight = this.$refs.dom_list.clientHeight
@@ -105,7 +116,7 @@ export default {
&.active {
.label {
background-color: @color-btn-background;
background-color: var(--color-button-background);
}
.list {
opacity: 1;
@@ -119,16 +130,17 @@ export default {
}
.label {
background-color: @color-btn-background;
background-color: var(--color-button-background);
padding: 0 10px;
transition: background-color @transition-theme;
transition: background-color @transition-normal;
height: @selection-height;
line-height: 27px;
// line-height: 27px;
box-sizing: border-box;
color: @color-btn;
color: var(--color-button-font);
border-radius: @form-radius;
cursor: pointer;
display: flex;
align-items: center;
span {
flex: auto;
@@ -146,10 +158,10 @@ export default {
}
&:hover {
background-color: @color-btn-hover;
background-color: var(--color-button-background-hover);
}
&:active {
background-color: @color-btn-active;
background-color: var(--color-button-background-active);
}
}
@@ -158,7 +170,7 @@ export default {
top: 0;
left: 0;
width: 100%;
background-color: @color-theme_2-background_2;
background-color: var(--color-content-background);
opacity: 0;
transform: scaleY(0) translateY(0);
transform-origin: 0 (@selection-height / 2) 0;
@@ -169,61 +181,28 @@ export default {
box-shadow: 0 0 4px rgba(0, 0, 0, .15);
overflow: auto;
max-height: 200px;
}
.listItem {
cursor: pointer;
padding: 0 10px;
line-height: @selection-height;
// color: var(--color-button-font);
outline: none;
transition: background-color @transition-normal;
background-color: var(--color-button-font)-background;
box-sizing: border-box;
.mixin-ellipsis-1;
li {
cursor: pointer;
padding: 0 10px;
line-height: @selection-height;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-btn-background;
box-sizing: border-box;
.mixin-ellipsis-1;
&:hover {
background-color: @color-btn-hover;
}
&:active {
background-color: @color-btn-active;
}
&.active {
color: @color-btn;
}
&:hover {
background-color: var(--color-button-background-hover);
}
&:active {
background-color: var(--color-button-background-active);
}
&.active {
color: var(--color-button-font);
}
}
each(@themes, {
:global(#root.@{value}) {
.label {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
&:active {
background-color: ~'@{color-@{value}-btn-active}';
}
}
.list {
background-color: ~'@{color-@{value}-theme_2-background_2}';
li {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
&:active {
background-color: ~'@{color-@{value}-btn-active}';
}
&.active {
color: ~'@{color-@{value}-btn}';
}
}
}
}
})
</style>
+87 -192
View File
@@ -1,20 +1,17 @@
<template lang="pug">
div.scroll(:class="$style.tab")
//- div(:class="$style.content")
ul
li.ignore-to-rem(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="modelValue === (itemKey ? item[itemKey] : item) ? $style.active : ''")
button(type="button"
@click="handleClick(itemKey ? item[itemKey] : item)") {{ itemName ? item[itemName] : item }}
//- div(:class="$style.control")
div(:class="$style.left")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-left')
div(:class="$style.right")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
use(xlink:href='#icon-right')
<template>
<ul :class="[$style.list, $style[align]]" role="tablist">
<li
v-for="item in list" :key="item[itemKey]" :class="[$style.listItem, {[$style.active]: modelValue == item[itemKey]}]" tabindex="-1"
role="tab" :aria-label="item[itemLabel]" ignore-tip :aria-selected="modelValue == item[itemKey]"
@click="handleToggle(item[itemKey])"
>
<span :class="$style.label">{{ item[itemLabel] }}</span>
</li>
</ul>
</template>
<script>
export default {
props: {
list: {
@@ -23,203 +20,101 @@ export default {
return []
},
},
modelValue: {
type: [String, Number],
},
itemName: {
align: {
type: String,
default: 'left',
},
itemKey: {
type: String,
default: 'id',
},
itemLabel: {
type: String,
default: 'label',
},
modelValue: {
type: [String, Number],
default: '',
},
},
methods: {
handleClick(item) {
if (item === this.modelValue) return
this.$emit('update:modelValue', item)
this.$emit('change', item)
},
emits: ['update:modelValue', 'change'],
setup(props, { emit }) {
const handleToggle = id => {
if (id == props.modelValue) return
emit('update:modelValue', id)
emit('change', id)
}
return {
handleToggle,
}
},
}
</script>
<style lang="less" module>
@import '@renderer/assets/styles/layout.less';
.tab {
// overflow: hidden;
// display: flex;
// flex-flow: row nowrap;
overflow-x: auto;
ul {
display: flex;
flex: none;
border-bottom: 2px solid @color-tab-border-bottom;
transition: border-bottom-color @transition-theme;
li {
position: relative;
flex: none;
border-top: 2px solid @color-tab-border-top;
border-left: 2px solid @color-tab-btn-background;
border-right: 2px solid @color-tab-btn-background;
// box-sizing: border-box;
transition: border-color @transition-theme;
.list {
display: flex;
flex-flow: row nowrap;
font-size: 12px;
gap: 25px;
padding: 0 15px;
&:global(.ignore-to-rem) {
margin-left: -2px;
margin-bottom: -2px;
&:after, &:before {
height: 2px;
}
}
&.left {
justify-content: flex-start;
}
&.center {
justify-content: center;
}
&.right {
justify-content: flex-end;
}
}
.listItem {
display: block;
// padding: 5px 15px;
cursor: pointer;
transition: color @transition-normal;
&:hover {
color: var(--color-primary);
}
&.active {
color: var(--color-primary);
cursor: default;
>.label {
&:after {
content: ' ';
display: block;
width: 50%;
position: absolute;
bottom: 0;
left: 0;
background-color: @color-tab-border-bottom;
transition: width @transition-theme;
}
&:before {
content: ' ';
display: block;
width: 50%;
position: absolute;
bottom: 0;
right: 0;
background-color: @color-tab-border-bottom;
transition: width @transition-theme;
}
&:first-child {
border-left: none;
margin-left: 0;
button {
border-top-left-radius: 3px;
// border-bottom-left-radius: @radius-border;
}
}
&:last-child {
border-right: 2px solid @color-tab-border-top;
border-top-right-radius: @radius-border;
button {
border-top-right-radius: 3px;
// border-bottom-right-radius: @radius-border;
}
}
button {
display: inline-block;
border: none;
cursor: pointer;
padding: 0 10px;
font-size: 12px;
min-width: 56px;
// color: @color-btn;
outline: none;
transition: background-color @transition-theme;
background-color: @color-tab-btn-background;
line-height: 28px;
}
&:hover {
// border-left-color: @color-theme_2-hover;
// border-right-color: @color-theme_2-hover;
button {
background-color: @color-tab-btn-background-hover;
}
}
&:active {
// border-left-color: @color-theme_2-active;
// border-right-color: @color-theme_2-active;
button {
background-color: @color-theme_2-active;
}
}
&.active {
border-bottom-color: @color-theme_2-background_1;
border-top-color: @color-tab-border-bottom;
border-left-color: @color-tab-border-bottom;
border-right-color: @color-tab-border-bottom;
&::after {
width: 0;
}
&::before {
width: 0;
}
button {
background-color: @color-theme_2-background_1;
}
// background-color: var(--color-primary);
opacity: 1;
transform: translateY(0);
}
}
}
}
// .control {
// flex: none;
// border-bottom: 2px solid @color-tab-border-bottom;
// width: 1em;
// svg {
// width: .85em;
// }
// }
// .left, .right {
// line-height: 0;
// }
// .content {
// flex: auto;
// overflow: hidden;
// }
each(@themes, {
:global(#root.@{value}) {
.tab {
ul {
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
li {
border-top-color: ~'@{color-@{value}-tab-border-top}';
border-left-color: ~'@{color-@{value}-tab-btn-background}';
border-right-color: ~'@{color-@{value}-tab-btn-background}';
&::after {
background-color: ~'@{color-@{value}-tab-border-bottom}';
}
&::before {
background-color: ~'@{color-@{value}-tab-border-bottom}';
}
&:last-child {
border-right-color: ~'@{color-@{value}-tab-border-top}';
}
button {
// color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-tab-btn-background}';
}
&:hover {
// border-left-color: ~'@{color-@{value}-theme_2-hover}';
// border-right-color: ~'@{color-@{value}-theme_2-hover}';
button {
background-color: ~'@{color-@{value}-tab-btn-background-hover}';
}
}
&:active {
// border-left-color: ~'@{color-@{value}-theme_2-active}';
// border-right-color: ~'@{color-@{value}-theme_2-active}';
button {
background-color: ~'@{color-@{value}-theme_2-active}';
}
}
&.active {
border-bottom-color: ~'@{color-@{value}-theme_2}';
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
button {
background-color: ~'@{color-@{value}-theme_2-background_1}';
}
}
}
}
}
.label {
display: block;
position: relative;
padding: 8px 0;
&:after {
.mixin-after;
left: 0;
bottom: 0;
width: 100%;
height: 2px;
border-radius: 20px;
background-color: transparent;
transform: translateY(-4px);
opacity: 0;
background-color: var(--color-primary-alpha-300);
transition: @transition-fast;
transition-property: transform, opacity;
}
})
}
</style>
@@ -1,5 +1,5 @@
<template>
<component :is="containerEl" :class="containerClass" tabindex="0" ref="dom_scrollContainer" style="outline: none; height: 100%; overflow: auto; position: relative; display: block; contain: strict;">
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
<component :is="contentEl" :class="contentClass" :style="contentStyle">
<div v-for="item in views" :key="item.key" :style="item.style">
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
@@ -57,7 +57,7 @@ const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel
onCancel()
return
}
setTimeout(animateScroll, increment)
window.setTimeout(animateScroll, increment)
} else {
callback()
}
@@ -88,21 +88,17 @@ export default {
type: String,
default: 'virtualized-list-content',
},
outsideNum: {
type: Number,
default: 1,
},
itemHeight: {
type: Number,
required: true,
},
keyName: {
type: String,
require: true,
required: true,
},
list: {
type: Array,
require: true,
required: true,
},
},
emits: ['scroll'],
@@ -141,12 +137,12 @@ export default {
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
const currentStartRenderIndex = Math.max(currentStartIndex - props.outsideNum, 0)
const currentEndRenderIndex = currentEndIndex + props.outsideNum + 1
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
const currentEndRenderIndex = currentEndIndex + 1
// console.log(continuous)
// debugger
if (continuous) {
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * this.outsideNum * 0.6) return
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
// console.log('update')
// if (currentScrollTop > scrollTop) { // scroll down
// // console.log('scroll down')
@@ -162,9 +158,13 @@ export default {
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// } else return
if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
requestAnimationFrame(() => {
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
})
} else {
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
requestAnimationFrame(() => {
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
})
}
startIndex = currentStartIndex
endIndex = currentEndIndex
@@ -173,42 +173,47 @@ export default {
const onScroll = event => {
const currentScrollTop = dom_scrollContainer.value.scrollTop
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * props.outsideNum * 0.6) {
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
updateView(currentScrollTop)
}
emit('scroll', event)
}
const scrollTo = (scrollTop, animate = false) => {
return new Promise(resolve => {
if (cancelScroll) {
cancelScroll(resolve)
} else {
resolve()
}
}).then(() => {
return new Promise((resolve, reject) => {
const scrollTo = (scrollTop, animate = false, onScrollEnd) => {
if (onScrollEnd) {
new Promise(resolve => {
if (cancelScroll) {
cancelScroll(resolve)
} else {
resolve()
}
}).then(() => {
if (animate) {
isScrolling = true
scrollToValue = scrollTop
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
cancelScroll = null
isScrolling = false
resolve()
onScrollEnd(true)
}, () => {
cancelScroll = null
isScrolling = false
reject('canceled')
onScrollEnd('canceled')
})
} else {
dom_scrollContainer.value.scrollTop = scrollTop
}
})
})
} else {
dom_scrollContainer.value.scrollTo({
top: scrollTop,
behavior: animate ? 'smooth' : 'instant',
})
}
}
const scrollToIndex = (index, offset = 0, animate = false) => {
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
const scrollToIndex = (index, offset = 0, animate = false, onScrollEnd) => {
scrollTo(Math.max(index * props.itemHeight + offset, 0), animate, onScrollEnd)
}
const getScrollTop = () => {
@@ -216,7 +221,7 @@ export default {
}
const handleResize = () => {
setTimeout(updateView)
window.setTimeout(updateView)
}
const contentStyle = computed(() => ({
@@ -0,0 +1,227 @@
// import {
// computed,
// ref,
// nextTick,
// watch,
// onMounted,
// onBeforeUnmount,
// } from 'vue'
// import { scrollTo } from '@common/utils/renderer'
// interface ListItem {
// item: LX.Music.MusicInfo
// top: number
// style: {
// position: string
// left: number
// right: number
// top: string
// height: string
// }
// index: number
// key: string
// }
// export default (props: { list: LX.Music.MusicInfo[], itemHeight: number }) => {
// const dom_scrollContainer = ref<HTMLElement | null>(null)
// const dom_list = ref<HTMLElement | null>(null)
// let startIndex = -1
// let endIndex = -1
// let scrollTop = -1
// let cachedList: ListItem[] = []
// let cancelScroll: null | (() => void) = null
// let isScrolling = false
// let scrollToValue = 0
// const createList = (startIndex: number, endIndex: number) => {
// if (startIndex == endIndex) return []
// console.log(startIndex, endIndex)
// const cache = cachedList.slice(startIndex, endIndex)
// const list = props.list.slice(startIndex, endIndex).map((item, i) => {
// if (cache[i]) return cache[i]
// const top = (startIndex + i) * props.itemHeight
// const index = startIndex + i
// return cachedList[index] = {
// item,
// top,
// style: { position: 'absolute', left: 0, right: 0, top: `${top}px`, height: `${props.itemHeight}px` },
// index,
// key: item.id,
// }
// })
// return list
// }
// // div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
// // :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
// // div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
// // div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :aria-label="item.name + (item.meta._qualitys.flac32bit ? ` - ${$t('tag__lossless_24bit')}` : (item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav) ? ` - ${$t('tag__lossless')}` : item.meta._qualitys['320k'] ? ` - ${$t('tag__high_quality')}` : '') + (sourceTag ? ` - ${item.source}` : '')")
// // span.select {{item.name}}
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-if="item.meta._qualitys.flac32bit") {{$t('tag__lossless_24bit')}}
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav") {{$t('tag__lossless')}}
// // span.badge.badge-theme-secondary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys['320k']") {{$t('tag__high_quality')}}
// // span.badge.badge-theme-tertiary(:class="[$style.labelQuality, $style.noSelect]" v-if="sourceTag") {{item.source}}
// // div.list-item-cell(:style="{ width: rowWidth.r3 }" :aria-label="item.singer")
// // span.select {{item.singer}}
// // div.list-item-cell(:style="{ width: rowWidth.r4 }" :aria-label="item.albumName")
// // span.select {{item.meta.albumName}}
// // div.list-item-cell(:style="{ width: rowWidth.r5 }")
// // span(:class="[$style.time, $style.noSelect]") {{item.meta.interval || '--/--'}}
// // div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
// // material-list-buttons(:index="index" :class="$style.btns"
// // :remove-btn="false" @btn-click="handleListBtnClick"
// // :download-btn="assertApiSupport(item.source)")
// const renderListItem = (list: ListItem) => {
// const dom_listItem = document.createElement('div')
// dom_listItem.className = 'list-item'
// }
// const renderList = (list: ListItem[], type?: 'up' | 'down') => {
// if (!list.length) return
// console.log(list)
// const dom = document.createDocumentFragment()
// for (const item of list) {
// dom.appendChild(renderListItem(item))
// }
// switch (type) {
// case 'up':
// break
// case 'down':
// break
// default:
// // console.log()
// break
// }
// }
// const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
// // const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
// const itemHeight = props.itemHeight
// const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
// const scrollContainerHeight = dom_scrollContainer.value.clientHeight
// const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
// const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
// const currentStartRenderIndex = Math.max(currentStartIndex, 0)
// const currentEndRenderIndex = currentEndIndex + 1
// // console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
// // debugger
// if (!force && continuous) {
// // if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
// // console.log('update')
// if (currentScrollTop > scrollTop) { // scroll down
// console.log('scroll down')
// renderList(createList(endIndex + 1, currentEndRenderIndex))
// // // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
// // // // if (this.views.length > 100) {
// // // nextTick(() => {
// // // views.value.splice(0, views.value.indexOf(list[0]))
// // // })
// // // }
// } else if (currentScrollTop < scrollTop) { // scroll up
// console.log('scroll up')
// renderList(createList(currentStartRenderIndex, startIndex))
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// } else return
// // if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
// } else {
// renderList(createList(currentStartRenderIndex, currentEndRenderIndex))
// }
// startIndex = currentStartIndex
// endIndex = currentEndIndex
// scrollTop = currentScrollTop
// }
// const onScroll = event => {
// const currentScrollTop = dom_scrollContainer.value.scrollTop
// if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
// updateView(false, currentScrollTop)
// }
// emit('scroll', event)
// }
// const scrollTo = async(scrollTop, animate = false) => {
// return await new Promise(resolve => {
// if (cancelScroll) {
// cancelScroll(resolve)
// } else {
// resolve()
// }
// }).then(async() => {
// return await new Promise((resolve, reject) => {
// if (animate) {
// isScrolling = true
// scrollToValue = scrollTop
// cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
// cancelScroll = null
// isScrolling = false
// resolve()
// }, () => {
// cancelScroll = null
// isScrolling = false
// reject('canceled')
// })
// } else {
// dom_scrollContainer.value.scrollTop = scrollTop
// }
// })
// })
// }
// const scrollToIndex = async(index, offset = 0, animate = false) => {
// return await scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
// }
// const getScrollTop = () => {
// return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
// }
// const handleResize = () => {
// setTimeout(updateView)
// }
// const contentStyle = computed(() => ({
// display: 'block',
// height: props.list.length * props.itemHeight + 'px',
// }))
// const handleReset = list => {
// cachedList = Array(list.length)
// startIndex = -1
// endIndex = -1
// void nextTick(() => {
// updateView(true)
// })
// }
// watch(() => props.itemHeight, () => {
// handleReset(props.list)
// })
// watch(() => props.list, (list) => {
// handleReset(list)
// }, {
// deep: true,
// })
// onMounted(() => {
// dom_scrollContainer.value!.addEventListener('scroll', onScroll, false)
// cachedList = Array(props.list.length)
// startIndex = -1
// endIndex = -1
// updateView(true)
// window.addEventListener('resize', handleResize)
// })
// onBeforeUnmount(() => {
// dom_scrollContainer.value!.removeEventListener('scroll', onScroll)
// window.removeEventListener('resize', handleResize)
// if (cancelScroll) cancelScroll()
// })
// return {
// dom_scrollContainer,
// dom_list,
// contentStyle,
// scrollTo,
// scrollToIndex,
// getScrollTop,
// }
// }