summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/grid/MkHeaderCell.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components/grid/MkHeaderCell.vue')
-rw-r--r--packages/frontend/src/components/grid/MkHeaderCell.vue216
1 files changed, 216 insertions, 0 deletions
diff --git a/packages/frontend/src/components/grid/MkHeaderCell.vue b/packages/frontend/src/components/grid/MkHeaderCell.vue
new file mode 100644
index 0000000000..605d27c6d6
--- /dev/null
+++ b/packages/frontend/src/components/grid/MkHeaderCell.vue
@@ -0,0 +1,216 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+ ref="rootEl"
+ class="mk_grid_th"
+ :class="$style.cell"
+ :style="[{ maxWidth: column.width, minWidth: column.width, width: column.width }]"
+ data-grid-cell
+ :data-grid-cell-row="-1"
+ :data-grid-cell-col="column.index"
+>
+ <div :class="$style.root">
+ <div :class="$style.left"/>
+ <div :class="$style.wrapper">
+ <div ref="contentEl" :class="$style.contentArea">
+ <span v-if="column.setting.icon" class="ti" :class="column.setting.icon" style="line-height: normal"/>
+ <span v-else>{{ text }}</span>
+ </div>
+ </div>
+ <div
+ :class="$style.right"
+ @mousedown="onHandleMouseDown"
+ @dblclick="onHandleDoubleClick"
+ />
+ </div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue';
+import { GridEventEmitter, Size } from '@/components/grid/grid.js';
+import { GridColumn } from '@/components/grid/column.js';
+
+const emit = defineEmits<{
+ (ev: 'operation:beginWidthChange', sender: GridColumn): void;
+ (ev: 'operation:endWidthChange', sender: GridColumn): void;
+ (ev: 'operation:widthLargest', sender: GridColumn): void;
+ (ev: 'change:width', sender: GridColumn, width: string): void;
+ (ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
+}>();
+const props = defineProps<{
+ column: GridColumn,
+ bus: GridEventEmitter,
+}>();
+
+const { column, bus } = toRefs(props);
+
+const rootEl = ref<InstanceType<typeof HTMLTableCellElement>>();
+const contentEl = ref<InstanceType<typeof HTMLDivElement>>();
+
+const resizing = ref<boolean>(false);
+
+const text = computed(() => {
+ const result = column.value.setting.title ?? column.value.setting.bindTo;
+ return result.length > 0 ? result : ' ';
+});
+
+watch(column, () => {
+ // 中身がセットされた直後はサイズが分からないので、次のタイミングで更新する
+ nextTick(emitContentSizeChanged);
+}, { immediate: true });
+
+function onHandleDoubleClick(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'dblclick': {
+ emit('operation:widthLargest', column.value);
+ break;
+ }
+ }
+}
+
+function onHandleMouseDown(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'mousedown': {
+ if (!resizing.value) {
+ registerHandleMouseUp();
+ registerHandleMouseMove();
+ resizing.value = true;
+ emit('operation:beginWidthChange', column.value);
+ }
+ break;
+ }
+ }
+}
+
+function onHandleMouseMove(ev: MouseEvent) {
+ if (!rootEl.value) {
+ // 型ガード
+ return;
+ }
+
+ switch (ev.type) {
+ case 'mousemove': {
+ if (resizing.value) {
+ const bounds = rootEl.value.getBoundingClientRect();
+ const clientWidth = rootEl.value.clientWidth;
+ const clientRight = bounds.left + clientWidth;
+ const nextWidth = clientWidth + (ev.clientX - clientRight);
+ emit('change:width', column.value, `${nextWidth}px`);
+ }
+ break;
+ }
+ }
+}
+
+function onHandleMouseUp(ev: MouseEvent) {
+ switch (ev.type) {
+ case 'mouseup': {
+ if (resizing.value) {
+ unregisterHandleMouseUp();
+ unregisterHandleMouseMove();
+ resizing.value = false;
+ emit('operation:endWidthChange', column.value);
+ }
+ break;
+ }
+ }
+}
+
+function onForceRefreshContentSize() {
+ emitContentSizeChanged();
+}
+
+function registerHandleMouseMove() {
+ unregisterHandleMouseMove();
+ addEventListener('mousemove', onHandleMouseMove);
+}
+
+function unregisterHandleMouseMove() {
+ removeEventListener('mousemove', onHandleMouseMove);
+}
+
+function registerHandleMouseUp() {
+ unregisterHandleMouseUp();
+ addEventListener('mouseup', onHandleMouseUp);
+}
+
+function unregisterHandleMouseUp() {
+ removeEventListener('mouseup', onHandleMouseUp);
+}
+
+function emitContentSizeChanged() {
+ const clientWidth = contentEl.value?.clientWidth ?? 0;
+ const clientHeight = contentEl.value?.clientHeight ?? 0;
+ emit('change:contentSize', column.value, {
+ // バーの横幅も考慮したいので、+3px
+ width: clientWidth + 3 + 3,
+ height: clientHeight,
+ });
+}
+
+onMounted(() => {
+ bus.value.on('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+onUnmounted(() => {
+ bus.value.off('forceRefreshContentSize', onForceRefreshContentSize);
+});
+
+</script>
+
+<style module lang="scss">
+$handleWidth: 5px;
+$cellHeight: 28px;
+
+.cell {
+ cursor: pointer;
+}
+
+.root {
+ display: flex;
+ flex-direction: row;
+ height: $cellHeight;
+ max-height: $cellHeight;
+ min-height: $cellHeight;
+
+ .wrapper {
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+ justify-content: center;
+ }
+
+ .contentArea {
+ display: flex;
+ padding: 6px 4px;
+ box-sizing: border-box;
+ overflow: hidden;
+ white-space: nowrap;
+ text-align: center;
+ }
+
+ .left {
+ // rightのぶんだけズレるのでそれを相殺するためのネガティブマージン
+ margin-left: -$handleWidth;
+ margin-right: auto;
+ width: $handleWidth;
+ min-width: $handleWidth;
+ }
+
+ .right {
+ margin-left: auto;
+ // 判定を罫線の上に重ねたいのでネガティブマージンを使う
+ margin-right: -$handleWidth;
+ width: $handleWidth;
+ min-width: $handleWidth;
+ cursor: w-resize;
+ z-index: 1;
+ }
+}
+</style>