diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-28 20:07:37 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-11-28 20:07:37 +0900 |
| commit | e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6 (patch) | |
| tree | 4283a0a36f5cb03f0fb3a534142c06783f8ff725 /packages/client/src/components/form/range.vue | |
| parent | /antennas/notes API で日付による絞り込みができるようにする... (diff) | |
| download | misskey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.tar.gz misskey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.tar.bz2 misskey-e8005c8d3a6edf2c8cdce3fe098fb9acff8a57c6.zip | |
client: refine ui
Diffstat (limited to 'packages/client/src/components/form/range.vue')
| -rw-r--r-- | packages/client/src/components/form/range.vue | 272 |
1 files changed, 190 insertions, 82 deletions
diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue index dd771abfe2..79a83d6a93 100644 --- a/packages/client/src/components/form/range.vue +++ b/packages/client/src/components/form/range.vue @@ -1,29 +1,27 @@ <template> -<div class="timctyfi" :class="{ focused, disabled }"> - <div class="icon"><slot name="icon"></slot></div> - <span class="label"><slot name="label"></slot></span> - <input - ref="input" - v-model="v" - type="range" - :disabled="disabled" - :min="min" - :max="max" - :step="step" - :autofocus="autofocus" - @focus="focused = true" - @blur="focused = false" - @input="$emit('update:value', $event.target.value)" - /> +<div class="timctyfi" :class="{ disabled }"> + <div class="label"><slot name="label"></slot></div> + <div v-panel class="body"> + <div ref="containerEl" class="container"> + <div class="track"> + <div class="highlight" :style="{ width: (steppedValue * 100) + '%' }"></div> + </div> + <div v-if="steps" class="ticks"> + <div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div> + </div> + <div ref="thumbEl" v-tooltip="textConverter(finalValue)" class="thumb" :style="{ left: thumbPosition + 'px' }" @mousedown="onMousedown" @touchstart="onMousedown"></div> + </div> + </div> </div> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { computed, defineComponent, ref, watch } from 'vue'; +import * as os from '@/os'; export default defineComponent({ props: { - value: { + modelValue: { type: Number, required: false, default: 0 @@ -51,88 +49,198 @@ export default defineComponent({ autofocus: { type: Boolean, required: false - } + }, + textConverter: { + type: Function, + required: false, + default: (v) => v.toString(), + }, }, - data() { + + setup(props, context) { + const rawValue = ref((props.modelValue - props.min) / (props.max - props.min)); + const steppedValue = computed(() => { + if (props.step) { + const step = props.step / (props.max - props.min); + return (step * Math.round(rawValue.value / step)); + } else { + return rawValue.value; + } + }); + const finalValue = computed(() => { + return (steppedValue.value * (props.max - props.min)) + props.min; + }); + watch(finalValue, () => { + context.emit('update:modelValue', finalValue.value); + }); + + const thumbWidth = computed(() => { + if (thumbEl.value == null) return 0; + return thumbEl.value!.offsetWidth; + }); + const thumbPosition = computed(() => { + if (containerEl.value == null) return 0; + return (containerEl.value.offsetWidth - thumbWidth.value) * steppedValue.value; + }); + const steps = computed(() => { + if (props.step) { + return (props.max - props.min) / props.step; + } else { + return 0; + } + }); + const containerEl = ref<HTMLElement>(); + const thumbEl = ref<HTMLElement>(); + + const onMousedown = (ev: MouseEvent | TouchEvent) => { + ev.preventDefault(); + + const tooltipShowing = ref(true); + os.popup(import('@/components/ui/tooltip.vue'), { + showing: tooltipShowing, + text: computed(() => { + return props.textConverter(finalValue.value); + }), + source: thumbEl, + }, {}, 'closed'); + + const style = document.createElement('style'); + style.appendChild(document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }')); + document.head.appendChild(style); + + const onDrag = (ev: MouseEvent | TouchEvent) => { + ev.preventDefault(); + const containerRect = containerEl.value!.getBoundingClientRect(); + const pointerX = ev.touches && ev.touches.length > 0 ? ev.touches[0].clientX : ev.clientX; + const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth.value / 2)); + rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth.value))); + }; + + const onMouseup = () => { + document.head.removeChild(style); + tooltipShowing.value = false; + window.removeEventListener('mousemove', onDrag); + window.removeEventListener('touchmove', onDrag); + window.removeEventListener('mouseup', onMouseup); + window.removeEventListener('touchend', onMouseup); + }; + + window.addEventListener('mousemove', onDrag); + window.addEventListener('touchmove', onDrag); + window.addEventListener('mouseup', onMouseup, { once: true }); + window.addEventListener('touchend', onMouseup, { once: true }); + }; + return { - v: this.value, - focused: false + rawValue, + finalValue, + steppedValue, + onMousedown, + containerEl, + thumbEl, + thumbPosition, + steps, }; }, - watch: { - value(v) { - this.v = parseFloat(v); - } - }, - mounted() { - if (this.autofocus) { - this.$nextTick(() => { - this.$refs.input.focus(); - }); - } - } }); </script> <style lang="scss" scoped> +@use "sass:math"; + .timctyfi { position: relative; - margin: 8px; - > .icon { - display: inline-block; - width: 24px; - text-align: center; - } + > .label { + font-size: 0.85em; + padding: 0 0 8px 0; + user-select: none; - > .title { - pointer-events: none; - font-size: 16px; - color: var(--inputLabel); - overflow: hidden; + &:empty { + display: none; + } } - > input { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--X10); - height: 7px; - margin: 0 8px; - outline: 0; - border: 0; - border-radius: 7px; + > .caption { + font-size: 0.85em; + padding: 8px 0 0 0; + color: var(--fgTransparentWeak); - &.disabled { - opacity: 0.6; - cursor: not-allowed; + &:empty { + display: none; } + } - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); - box-sizing: content-box; - } + $thumbHeight: 20px; + $thumbWidth: 20px; + + > .body { + padding: 12px; + border-radius: 6px; + + > .container { + position: relative; + height: $thumbHeight; + + > .track { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: calc(100% - #{$thumbWidth}); + height: 3px; + background: rgba(0, 0, 0, 0.1); + border-radius: 999px; + overflow: clip; + + > .highlight { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: var(--accent); + opacity: 0.5; + transition: width 0.2s cubic-bezier(0,0,0,1); + } + } + + > .ticks { + $tickWidth: 3px; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + width: calc(100% - #{$thumbWidth}); + + > .tick { + position: absolute; + bottom: 0; + width: $tickWidth; + height: 3px; + margin-left: - math.div($tickWidth, 2); + background: var(--divider); + border-radius: 999px; + } + } + + > .thumb { + position: absolute; + width: $thumbWidth; + height: $thumbHeight; + cursor: grab; + background: var(--accent); + border-radius: 999px; + transition: left 0.2s cubic-bezier(0,0,0,1); - &::-moz-range-thumb { - -moz-appearance: none; - appearance: none; - cursor: pointer; - width: 20px; - height: 20px; - display: block; - border-radius: 50%; - border: none; - background: var(--accent); - box-shadow: 0 0 6px rgba(0, 0, 0, 0.3); + &:hover { + background: var(--accentLighten); + } + } } } } |