From 50a7149a934e22fd1216f4359faea083dd7f8e09 Mon Sep 17 00:00:00 2001 From: zachend Date: Mon, 17 Feb 2025 18:37:41 -0500 Subject: [PATCH 1/6] ZE-0 add resizing for QTable Columns --- ui/src/components/table/QTable.js | 58 +++++++++++++++++++++++++++++ ui/src/components/table/QTable.sass | 11 ++++++ 2 files changed, 69 insertions(+) diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index c7e3601ca4e..733c3d4e7fc 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -43,6 +43,11 @@ export default createComponent({ type: [ String, Function ], default: 'id' }, + resizableCols: { + type: Boolean, + default: false + }, + columns: Array, loading: Boolean, @@ -116,6 +121,59 @@ export default createComponent({ ...useTableSortProps }, + + data() { + return { + colWidths: {}, + resizingCol: null, + startX: 0 + } + }, + mounted() { + // Initialize widths for each column + this.computedCols.forEach(col => { + this.$set(this.colWidths, col.name, 150) + }) + }, + methods: { + startResizing(colName, evt) { + this.resizingCol = colName + this.startX = evt.pageX + document.addEventListener('mousemove', this.handleResize) + document.addEventListener('mouseup', this.stopResizing) + }, + handleResize(evt) { + if (!this.resizingCol) return + const diff = evt.pageX - this.startX + this.colWidths[this.resizingCol] += diff + this.startX = evt.pageX + }, + stopResizing() { + document.removeEventListener('mousemove', this.handleResize) + document.removeEventListener('mouseup', this.stopResizing) + this.resizingCol = null + }, + renderHeaderCell(h, col) { + const hasHandle = this.resizableCols + const handle = hasHandle + ? h('span', { + class: 'q-table__resize-handle', + onMousedown: evt => this.startResizing(col.name, evt) + }) + : null + + return h('th', { + style: { width: this.colWidths[col.name] + 'px' } + }, [ + col.label, + handle + ]) + } + }, + + + + emits: [ 'request', 'virtualScroll', ...useFullscreenEmits, diff --git a/ui/src/components/table/QTable.sass b/ui/src/components/table/QTable.sass index e82234d61aa..4ccf7ad69bc 100644 --- a/ui/src/components/table/QTable.sass +++ b/ui/src/components/table/QTable.sass @@ -291,3 +291,14 @@ body.desktop .q-table > tbody > tr:not(.q-tr--no-hover):hover > td:not(.q-td--no &.q-table--vertical-separator, &.q-table--cell-separator .q-table__top border-color: $table-dark-border-color + + + +.q-table th .q-table__resize-handle { + cursor: col-resize; + display: inline-block; + width: 6px; + height: 100%; + background-color: #ccc; + margin-left: 4px; +} From 5c3ecb32d383c796142e9df7cbb96b57ae7367d1 Mon Sep 17 00:00:00 2001 From: zachend Date: Tue, 25 Feb 2025 08:17:01 -0500 Subject: [PATCH 2/6] address required fixes --- ui/src/components/table/QTable.js | 92 ++++++++++++----------------- ui/src/components/table/QTable.sass | 12 ++-- 2 files changed, 44 insertions(+), 60 deletions(-) diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index 733c3d4e7fc..e50717a6173 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -1,4 +1,4 @@ -import { h, ref, computed, watch, getCurrentInstance } from 'vue' +import { h, ref, computed, watch, getCurrentInstance, onMounted, reactive } from 'vue' import QTh from './QTh.js' @@ -43,15 +43,12 @@ export default createComponent({ type: [ String, Function ], default: 'id' }, - resizableCols: { - type: Boolean, - default: false - }, - columns: Array, loading: Boolean, + resizableCols: Boolean, + iconFirstPage: String, iconPrevPage: String, iconNextPage: String, @@ -121,58 +118,44 @@ export default createComponent({ ...useTableSortProps }, + const vm = getCurrentInstance() + const colWidths = reactive({}) + const resizingCol = ref(null) + const startX = ref(0) - data() { - return { - colWidths: {}, - resizingCol: null, - startX: 0 - } - }, - mounted() { - // Initialize widths for each column - this.computedCols.forEach(col => { - this.$set(this.colWidths, col.name, 150) - }) - }, - methods: { - startResizing(colName, evt) { - this.resizingCol = colName - this.startX = evt.pageX - document.addEventListener('mousemove', this.handleResize) - document.addEventListener('mouseup', this.stopResizing) - }, - handleResize(evt) { - if (!this.resizingCol) return - const diff = evt.pageX - this.startX - this.colWidths[this.resizingCol] += diff - this.startX = evt.pageX - }, - stopResizing() { - document.removeEventListener('mousemove', this.handleResize) - document.removeEventListener('mouseup', this.stopResizing) - this.resizingCol = null - }, - renderHeaderCell(h, col) { - const hasHandle = this.resizableCols - const handle = hasHandle - ? h('span', { - class: 'q-table__resize-handle', - onMousedown: evt => this.startResizing(col.name, evt) - }) - : null - - return h('th', { - style: { width: this.colWidths[col.name] + 'px' } - }, [ - col.label, - handle - ]) - } - }, + onMounted(() => { + vm.proxy.computedCols.forEach(col => { + colWidths[col.name] = 150 + }) + }) + + function startResizing(colName, evt) { + resizingCol.value = colName + startX.value = evt.pageX + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', stopResizing) + } + function handleResize(evt) { + if (!resizingCol.value) return + const diff = evt.pageX - startX.value + colWidths[resizingCol.value] += diff + startX.value = evt.pageX + } + function stopResizing() { + document.removeEventListener('mousemove', handleResize) + document.removeEventListener('mouseup', stopResizing) + resizingCol.value = null + } + return { + colWidths, + startResizing, + handleResize, + stopResizing + } + }, emits: [ 'request', 'virtualScroll', @@ -185,6 +168,7 @@ export default createComponent({ const vm = getCurrentInstance() const { proxy: { $q } } = vm + const isDark = useDark(props, $q) const { inFullscreen, toggleFullscreen } = useFullscreen() diff --git a/ui/src/components/table/QTable.sass b/ui/src/components/table/QTable.sass index 4ccf7ad69bc..d196ff535a1 100644 --- a/ui/src/components/table/QTable.sass +++ b/ui/src/components/table/QTable.sass @@ -295,10 +295,10 @@ body.desktop .q-table > tbody > tr:not(.q-tr--no-hover):hover > td:not(.q-td--no .q-table th .q-table__resize-handle { - cursor: col-resize; - display: inline-block; - width: 6px; - height: 100%; - background-color: #ccc; - margin-left: 4px; + cursor: col-resize + display: inline-block + width: 6px + height: 100% + background-color: #ccc + margin-left: 4px } From 78d6a65c4de6d76c88221fb339f87709a8d6f3a2 Mon Sep 17 00:00:00 2001 From: zachend Date: Thu, 27 Feb 2025 16:23:07 -0500 Subject: [PATCH 3/6] minor improvments --- ui/src/components/table/QTable.js | 112 +++++++++++++++------------- ui/src/components/table/QTable.sass | 4 +- 2 files changed, 64 insertions(+), 52 deletions(-) diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index e50717a6173..b8b2b75d81e 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -43,11 +43,11 @@ export default createComponent({ type: [ String, Function ], default: 'id' }, - + resizableCols: Boolean, columns: Array, loading: Boolean, - resizableCols: Boolean, + iconFirstPage: String, iconPrevPage: String, @@ -118,44 +118,7 @@ export default createComponent({ ...useTableSortProps }, - const vm = getCurrentInstance() - const colWidths = reactive({}) - const resizingCol = ref(null) - const startX = ref(0) - - onMounted(() => { - vm.proxy.computedCols.forEach(col => { - colWidths[col.name] = 150 - }) - }) - function startResizing(colName, evt) { - resizingCol.value = colName - startX.value = evt.pageX - document.addEventListener('mousemove', handleResize) - document.addEventListener('mouseup', stopResizing) - } - - function handleResize(evt) { - if (!resizingCol.value) return - const diff = evt.pageX - startX.value - colWidths[resizingCol.value] += diff - startX.value = evt.pageX - } - - function stopResizing() { - document.removeEventListener('mousemove', handleResize) - document.removeEventListener('mouseup', stopResizing) - resizingCol.value = null - } - - return { - colWidths, - startResizing, - handleResize, - stopResizing - } - }, emits: [ 'request', 'virtualScroll', @@ -169,6 +132,47 @@ export default createComponent({ const { proxy: { $q } } = vm + const colWidths = reactive({}) + const resizingCol = ref(null) + const startX = ref(0) + + onMounted(() => { + props.columns.forEach(col => { + colWidths[col.name] = 150 + }) + }) + + function startResizing(colName, evt) { + resizingCol.value = colName + startX.value = evt.pageX + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', stopResizing) + } + + function handleResize(evt) { + if (!resizingCol.value) return + const diff = evt.pageX - startX.value + colWidths[resizingCol.value] += diff + startX.value = evt.pageX + } + + function stopResizing() { + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', stopResizing); + resizingCol.value = null; + } + + return { + colWidths, + startResizing, + handleResize, + stopResizing, + }; + + + + + const isDark = useDark(props, $q) const { inFullscreen, toggleFullscreen } = useFullscreen() @@ -681,12 +685,18 @@ export default createComponent({ props = getHeaderScope({ col }) return slot !== void 0 - ? slot(props) - : h(QTh, { - key: col.name, - props - }, () => col.label) - }) + ? slot(props) + : h(QTh, { + key: col.name, + props + }, () => [ + col.label, + props.resizableCols ? h('div', { + class: 'q-table__resize-handle', + onMousedown: evt => startResizing(col.name, evt) + }) : null + ]) + }) if (singleSelection.value === true && props.grid !== true) { child.unshift( @@ -713,12 +723,12 @@ export default createComponent({ } return [ - h('tr', { - class: props.tableHeaderClass, - style: props.tableHeaderStyle - }, child) - ] - } + h('tr', { + class: props.tableHeaderClass, + style: props.tableHeaderStyle + }, child) + ] + } function getHeaderScope (data) { Object.assign(data, { diff --git a/ui/src/components/table/QTable.sass b/ui/src/components/table/QTable.sass index d196ff535a1..eed9f99353d 100644 --- a/ui/src/components/table/QTable.sass +++ b/ui/src/components/table/QTable.sass @@ -300,5 +300,7 @@ body.desktop .q-table > tbody > tr:not(.q-tr--no-hover):hover > td:not(.q-td--no width: 6px height: 100% background-color: #ccc - margin-left: 4px + position: absolute + right: 0 + top: 0 } From d2ca3a5029031c1d7c51284a166c6a8d4399ba56 Mon Sep 17 00:00:00 2001 From: zachend Date: Thu, 27 Feb 2025 16:44:27 -0500 Subject: [PATCH 4/6] - use column definitions to determine if a column is resizable or not - double clicking resizer brings column back to their default size --- ui/src/components/table/QTable.js | 171 +++++++++++++++--------------- 1 file changed, 84 insertions(+), 87 deletions(-) diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index b8b2b75d81e..28d784c4808 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -142,36 +142,36 @@ export default createComponent({ }) }) - function startResizing(colName, evt) { - resizingCol.value = colName - startX.value = evt.pageX - document.addEventListener('mousemove', handleResize) - document.addEventListener('mouseup', stopResizing) - } - - function handleResize(evt) { - if (!resizingCol.value) return - const diff = evt.pageX - startX.value - colWidths[resizingCol.value] += diff - startX.value = evt.pageX - } + function resetColumnWidth(colName) { + colWidths[colName] = 150; // Reset to default width or any desired full size + } - function stopResizing() { - document.removeEventListener('mousemove', handleResize); - document.removeEventListener('mouseup', stopResizing); - resizingCol.value = null; + function startResizing(colName, evt) { + resizingCol.value = colName + startX.value = evt.pageX + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', stopResizing) } - return { - colWidths, - startResizing, - handleResize, - stopResizing, - }; - - + function handleResize(evt) { + if (!resizingCol.value) return + const diff = evt.pageX - startX.value + colWidths[resizingCol.value] += diff + startX.value = evt.pageX + } + function stopResizing() { + document.removeEventListener('mousemove', handleResize); + document.removeEventListener('mouseup', stopResizing); + resizingCol.value = null; + } + return { + colWidths, + startResizing, + handleResize, + stopResizing, + }; const isDark = useDark(props, $q) const { inFullscreen, toggleFullscreen } = useFullscreen() @@ -667,68 +667,65 @@ export default createComponent({ return h('thead', child) } - function getTHeadTR () { - const - header = slots.header, - headerCell = slots[ 'header-cell' ] - - if (header !== void 0) { - return header( - getHeaderScope({ header: true }) - ).slice() - } - - const child = computedCols.value.map(col => { - const - headerCellCol = slots[ `header-cell-${ col.name }` ], - slot = headerCellCol !== void 0 ? headerCellCol : headerCell, - props = getHeaderScope({ col }) - - return slot !== void 0 - ? slot(props) - : h(QTh, { - key: col.name, - props - }, () => [ - col.label, - props.resizableCols ? h('div', { - class: 'q-table__resize-handle', - onMousedown: evt => startResizing(col.name, evt) - }) : null - ]) - }) - - if (singleSelection.value === true && props.grid !== true) { - child.unshift( - h('th', { class: 'q-table--col-auto-width' }, ' ') - ) - } - else if (multipleSelection.value === true) { - const slot = slots[ 'header-selection' ] - const content = slot !== void 0 - ? slot(getHeaderScope({})) - : [ - h(QCheckbox, { - color: props.color, - modelValue: headerSelectedValue.value, - dark: isDark.value, - dense: props.dense, - 'onUpdate:modelValue': onMultipleSelectionSet - }) - ] - - child.unshift( - h('th', { class: 'q-table--col-auto-width' }, content) - ) - } - - return [ - h('tr', { - class: props.tableHeaderClass, - style: props.tableHeaderStyle - }, child) - ] - } + function getTHeadTR() { + const header = slots.header; + const headerCell = slots['header-cell']; + + if (header !== void 0) { + return header( + getHeaderScope({ header: true }) + ).slice(); + } + + const child = computedCols.value.map(col => { + const headerCellCol = slots[`header-cell-${col.name}`]; + const slot = headerCellCol !== void 0 ? headerCellCol : headerCell; + const props = getHeaderScope({ col }); + + return slot !== void 0 + ? slot(props) + : h(QTh, { + key: col.name, + props + }, () => [ + col.label, + col.resizable ? h('div', { + class: 'q-table__resize-handle', + onMousedown: evt => startResizing(col.name, evt) + }) : null + ]); + }); + + if (singleSelection.value === true && props.grid !== true) { + child.unshift( + h('th', { class: 'q-table--col-auto-width' }, ' ') + ); + } else if (multipleSelection.value === true) { + const slot = slots['header-selection']; + const content = slot !== void 0 + ? slot(getHeaderScope({})) + : [ + h(QCheckbox, { + color: props.color, + modelValue: headerSelectedValue.value, + dark: isDark.value, + dense: props.dense, + 'onUpdate:modelValue': onMultipleSelectionSet + }) + ]; + + child.unshift( + h('th', { class: 'q-table--col-auto-width' }, content) + ); + } + + return [ + h('tr', { + class: props.tableHeaderClass, + style: props.tableHeaderStyle + }, child) + ]; + } function getHeaderScope (data) { Object.assign(data, { From 18ddcea529233765bfacf967c29d52cf13a78be4 Mon Sep 17 00:00:00 2001 From: zachend Date: Thu, 27 Feb 2025 18:23:39 -0500 Subject: [PATCH 5/6] -cleaned up code to match rest of project -documented new feature in QTable.json --- ui/src/components/table/QTable.js | 56 ++++++++++++++--------------- ui/src/components/table/QTable.json | 6 +++- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index 28d784c4808..8f40d441745 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -118,8 +118,6 @@ export default createComponent({ ...useTableSortProps }, - - emits: [ 'request', 'virtualScroll', ...useFullscreenEmits, @@ -131,7 +129,6 @@ export default createComponent({ const vm = getCurrentInstance() const { proxy: { $q } } = vm - const colWidths = reactive({}) const resizingCol = ref(null) const startX = ref(0) @@ -142,36 +139,37 @@ export default createComponent({ }) }) - function resetColumnWidth(colName) { - colWidths[colName] = 150; // Reset to default width or any desired full size - } + function resetColumnWidth (colName) { + colWidths[colName] = 150 // Reset to default width or any desired full size + } - function startResizing(colName, evt) { - resizingCol.value = colName - startX.value = evt.pageX - document.addEventListener('mousemove', handleResize) - document.addEventListener('mouseup', stopResizing) - } + function startResizing (colName, evt) { + resizingCol.value = colName + startX.value = evt.pageX + document.addEventListener('mousemove', handleResize) + document.addEventListener('mouseup', stopResizing) + } - function handleResize(evt) { - if (!resizingCol.value) return - const diff = evt.pageX - startX.value - colWidths[resizingCol.value] += diff - startX.value = evt.pageX - } + function handleResize (evt) { + if (!resizingCol.value) return + const diff = evt.pageX - startX.value + colWidths[resizingCol.value] += diff + startX.value = evt.pageX + } - function stopResizing() { - document.removeEventListener('mousemove', handleResize); - document.removeEventListener('mouseup', stopResizing); - resizingCol.value = null; - } + function stopResizing () { + document.removeEventListener('mousemove', handleResize) + document.removeEventListener('mouseup', stopResizing) + resizingCol.value = null + } - return { - colWidths, - startResizing, - handleResize, - stopResizing, - }; + return { + colWidths, + resetColumnWidth, + startResizing, + handleResize, + stopResizing, + }; const isDark = useDark(props, $q) const { inFullscreen, toggleFullscreen } = useFullscreen() diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index 6e3e6029c4f..b230aaf1dd2 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -13,7 +13,11 @@ "examples": [ "# :rows=\"myData\"" ], "category": "general" }, - + "resizable-cols": { + "type": "Boolean", + "desc": "Enable column resizing functionality", + "category": "behavior" + }, "row-key": { "type": [ "String", "Function" ], "desc": "Property of each row that defines the unique key of each row (the result must be a primitive, not Object, Array, etc); The value of property must be string or a function taking a row and returning the desired (nested) key in the row; If supplying a function then for best performance, reference it from your scope and do not define it inline", From 5ec286e8900e139ed651b2184c0f3fee5c275085 Mon Sep 17 00:00:00 2001 From: zachend Date: Thu, 27 Feb 2025 18:30:33 -0500 Subject: [PATCH 6/6] -improved specificity of QTable.json description for resizable-cols prop --- ui/src/components/table/QTable.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index b230aaf1dd2..e559afcb8e1 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -15,7 +15,7 @@ }, "resizable-cols": { "type": "Boolean", - "desc": "Enable column resizing functionality", + "desc": "Enabling allows for easy configuration of user resizable column widths by adding a customizable drag handle between each column specified in the 'columns' prop. By default the drag handle is a small vertical line between each column. The drag handle can be customized by using the 'column-resize-handle' slot. Double clicking the grabber sets the columns back to their default width", "category": "behavior" }, "row-key": {