I can select range with visual feedback
This commit is contained in:
@@ -671,12 +671,8 @@
|
||||
const element = document.getElementById(candidate.elementId);
|
||||
const dynamicValues = func(event, element, candidate.node.combinationStr);
|
||||
if (dynamicValues && typeof dynamicValues === 'object') {
|
||||
// Suffix each key with _mousedown
|
||||
const suffixed = {};
|
||||
for (const [key, value] of Object.entries(dynamicValues)) {
|
||||
suffixed[key + '_mousedown'] = value;
|
||||
}
|
||||
mousedownJsValues[candidate.elementId] = suffixed;
|
||||
// Store raw values - _mousedown suffix added at mouseup time
|
||||
mousedownJsValues[candidate.elementId] = dynamicValues;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -699,12 +695,39 @@
|
||||
// Clear pending state
|
||||
MouseRegistry.mousedownPending = null;
|
||||
|
||||
// Remove mousemove listener (no longer needed)
|
||||
// Remove mousemove listener (threshold detection no longer needed)
|
||||
if (MouseRegistry.mousemoveHandler) {
|
||||
document.removeEventListener('mousemove', MouseRegistry.mousemoveHandler);
|
||||
MouseRegistry.mousemoveHandler = null;
|
||||
}
|
||||
|
||||
// Attach on_move handler if any candidate has 'on-move' config
|
||||
const onMoveCandidates = MouseRegistry.mousedownState.candidates.filter(
|
||||
c => c.node.config && c.node.config['on-move']
|
||||
);
|
||||
if (onMoveCandidates.length > 0) {
|
||||
let rafId = null;
|
||||
MouseRegistry.mousemoveHandler = (moveEvent) => {
|
||||
if (rafId) return;
|
||||
rafId = requestAnimationFrame(() => {
|
||||
for (const candidate of onMoveCandidates) {
|
||||
const funcName = candidate.node.config['on-move'];
|
||||
try {
|
||||
const func = window[funcName];
|
||||
if (typeof func === 'function') {
|
||||
const mousedownValues = mousedownJsValues[candidate.elementId] || null;
|
||||
func(moveEvent, candidate.node.combinationStr, mousedownValues);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error calling on_move function:', e);
|
||||
}
|
||||
}
|
||||
rafId = null;
|
||||
});
|
||||
};
|
||||
document.addEventListener('mousemove', MouseRegistry.mousemoveHandler);
|
||||
}
|
||||
|
||||
// For right button: prevent context menu during drag
|
||||
if (pendingData.button === 2) {
|
||||
const preventContextMenu = (e) => {
|
||||
@@ -907,9 +930,11 @@
|
||||
Object.assign(values, config['hx-vals']);
|
||||
}
|
||||
|
||||
// 2. Merge mousedown JS values (already suffixed with _mousedown)
|
||||
// 2. Merge mousedown JS values with _mousedown suffix
|
||||
if (match.mousedownJsValues) {
|
||||
Object.assign(values, match.mousedownJsValues);
|
||||
for (const [key, value] of Object.entries(match.mousedownJsValues)) {
|
||||
values[key + '_mousedown'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Call JS function at mouseup time, suffix with _mouseup
|
||||
|
||||
@@ -186,8 +186,10 @@ function bindTooltipsWithDelegation(elementId) {
|
||||
|
||||
// Add a single mouseenter and mouseleave listener to the parent element
|
||||
element.addEventListener("mouseenter", (event) => {
|
||||
// Early exit - check mf-no-tooltip FIRST (before any DOM work)
|
||||
if (element.hasAttribute("mf-no-tooltip")) {
|
||||
const target = event.target;
|
||||
|
||||
// Early exit - check mf-no-tooltip on the registered element OR any ancestor of the target
|
||||
if (element.hasAttribute("mf-no-tooltip") || target.closest("[mf-no-tooltip]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ function bindTooltipsWithDelegation(elementId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cell = event.target.closest("[data-tooltip]");
|
||||
const cell = target.closest("[data-tooltip]");
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
@@ -207,7 +209,7 @@ function bindTooltipsWithDelegation(elementId) {
|
||||
tooltipRafScheduled = false;
|
||||
|
||||
// Check again in case tooltip was disabled during RAF delay
|
||||
if (element.hasAttribute("mf-no-tooltip")) {
|
||||
if (element.hasAttribute("mf-no-tooltip") || target.closest("[mf-no-tooltip]")) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,16 @@
|
||||
background-color: var(--color-selection);
|
||||
}
|
||||
|
||||
.dt2-drag-preview {
|
||||
background-color: var(--color-selection);
|
||||
}
|
||||
|
||||
/* Selection border - outlines the entire selection rectangle */
|
||||
.dt2-selection-border-top { border-top: 2px solid var(--color-primary); }
|
||||
.dt2-selection-border-bottom { border-bottom: 2px solid var(--color-primary); }
|
||||
.dt2-selection-border-left { border-left: 2px solid var(--color-primary); }
|
||||
.dt2-selection-border-right { border-right: 2px solid var(--color-primary); }
|
||||
|
||||
|
||||
/* *********************************************** */
|
||||
/* ******** DataGrid Fixed Header/Footer ******** */
|
||||
|
||||
@@ -633,10 +633,17 @@ function updateDatagridSelection(datagridId) {
|
||||
const selectionManager = document.getElementById(`tsm_${datagridId}`);
|
||||
if (!selectionManager) return;
|
||||
|
||||
// Clear previous selections
|
||||
document.querySelectorAll('.dt2-selected-focus, .dt2-selected-cell, .dt2-selected-row, .dt2-selected-column').forEach((element) => {
|
||||
element.classList.remove('dt2-selected-focus', 'dt2-selected-cell', 'dt2-selected-row', 'dt2-selected-column');
|
||||
element.style.userSelect = 'none';
|
||||
// Re-enable tooltips after drag
|
||||
const wrapper = document.getElementById(`tw_${datagridId}`);
|
||||
if (wrapper) wrapper.removeAttribute('mf-no-tooltip');
|
||||
|
||||
// Clear browser text selection to prevent stale ranges from reappearing
|
||||
window.getSelection()?.removeAllRanges();
|
||||
|
||||
// Clear previous selections and drag preview
|
||||
document.querySelectorAll('.dt2-selected-focus, .dt2-selected-cell, .dt2-selected-row, .dt2-selected-column, .dt2-drag-preview, .dt2-selection-border-top, .dt2-selection-border-bottom, .dt2-selection-border-left, .dt2-selection-border-right').forEach((element) => {
|
||||
element.classList.remove('dt2-selected-focus', 'dt2-selected-cell', 'dt2-selected-row', 'dt2-selected-column', 'dt2-drag-preview', 'dt2-selection-border-top', 'dt2-selection-border-bottom', 'dt2-selection-border-left', 'dt2-selection-border-right');
|
||||
element.style.userSelect = '';
|
||||
});
|
||||
|
||||
// Loop through the children of the selection manager
|
||||
@@ -654,7 +661,6 @@ function updateDatagridSelection(datagridId) {
|
||||
const cellElement = document.getElementById(`${elementId}`);
|
||||
if (cellElement) {
|
||||
cellElement.classList.add('dt2-selected-cell');
|
||||
cellElement.style.userSelect = 'text';
|
||||
}
|
||||
} else if (selectionType === 'row') {
|
||||
const rowElement = document.getElementById(`${elementId}`);
|
||||
@@ -687,7 +693,10 @@ function updateDatagridSelection(datagridId) {
|
||||
const cell = document.getElementById(cellId);
|
||||
if (cell) {
|
||||
cell.classList.add('dt2-selected-cell');
|
||||
cell.style.userSelect = 'text';
|
||||
if (row === minRowNum) cell.classList.add('dt2-selection-border-top');
|
||||
if (row === maxRowNum) cell.classList.add('dt2-selection-border-bottom');
|
||||
if (col === minColNum) cell.classList.add('dt2-selection-border-left');
|
||||
if (col === maxColNum) cell.classList.add('dt2-selection-border-right');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -710,4 +719,72 @@ function getCellId(event) {
|
||||
return {cell_id: cell.id};
|
||||
}
|
||||
return {cell_id: null};
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlight the drag selection range in real time during a mousedown>mouseup drag.
|
||||
* Called by mouse.js on each animation frame while dragging.
|
||||
* Applies .dt2-drag-preview to all cells in the rectangle between the start and
|
||||
* current cell. The preview is cleared by updateDatagridSelection() when the server
|
||||
* responds with the final selection.
|
||||
*
|
||||
* @param {MouseEvent} event - The current mousemove event
|
||||
* @param {string} combination - The active mouse combination (e.g. "mousedown>mouseup")
|
||||
* @param {Object|null} mousedownResult - Result of getCellId() at mousedown, or null
|
||||
*/
|
||||
function highlightDatagridDragRange(event, combination, mousedownResult) {
|
||||
if (!mousedownResult || !mousedownResult.cell_id) return;
|
||||
|
||||
const currentCell = event.target.closest('.dt2-cell');
|
||||
if (!currentCell || !currentCell.id) return;
|
||||
|
||||
const startCellId = mousedownResult.cell_id;
|
||||
const endCellId = currentCell.id;
|
||||
|
||||
// Find the table from the start cell to scope the query
|
||||
const startCell = document.getElementById(startCellId);
|
||||
if (!startCell) return;
|
||||
const table = startCell.closest('.dt2-table');
|
||||
if (!table) return;
|
||||
|
||||
// Extract grid ID from table id: "t_{gridId}" -> "{gridId}"
|
||||
const gridId = table.id.substring(2);
|
||||
|
||||
// Disable tooltips during drag
|
||||
const wrapper = document.getElementById(`tw_${gridId}`);
|
||||
if (wrapper) wrapper.setAttribute('mf-no-tooltip', '');
|
||||
|
||||
// Parse col/row by splitting on "-" and taking the last two numeric parts
|
||||
const startParts = startCellId.split('-');
|
||||
const startCol = parseInt(startParts[startParts.length - 2]);
|
||||
const startRow = parseInt(startParts[startParts.length - 1]);
|
||||
|
||||
const endParts = endCellId.split('-');
|
||||
const endCol = parseInt(endParts[endParts.length - 2]);
|
||||
const endRow = parseInt(endParts[endParts.length - 1]);
|
||||
|
||||
if (isNaN(startCol) || isNaN(startRow) || isNaN(endCol) || isNaN(endRow)) return;
|
||||
|
||||
// Clear previous selection and drag preview within this table
|
||||
table.querySelectorAll('.dt2-drag-preview, .dt2-selected-cell, .dt2-selected-row, .dt2-selected-column, .dt2-selected-focus, .dt2-selection-border-top, .dt2-selection-border-bottom, .dt2-selection-border-left, .dt2-selection-border-right')
|
||||
.forEach(c => c.classList.remove('dt2-drag-preview', 'dt2-selected-cell', 'dt2-selected-row', 'dt2-selected-column', 'dt2-selected-focus', 'dt2-selection-border-top', 'dt2-selection-border-bottom', 'dt2-selection-border-left', 'dt2-selection-border-right'));
|
||||
|
||||
// Apply preview to all cells in the rectangular range
|
||||
const minCol = Math.min(startCol, endCol);
|
||||
const maxCol = Math.max(startCol, endCol);
|
||||
const minRow = Math.min(startRow, endRow);
|
||||
const maxRow = Math.max(startRow, endRow);
|
||||
|
||||
for (let col = minCol; col <= maxCol; col++) {
|
||||
for (let row = minRow; row <= maxRow; row++) {
|
||||
const cell = document.getElementById(`tcell_${gridId}-${col}-${row}`);
|
||||
if (cell) {
|
||||
cell.classList.add('dt2-drag-preview');
|
||||
if (row === minRow) cell.classList.add('dt2-selection-border-top');
|
||||
if (row === maxRow) cell.classList.add('dt2-selection-border-bottom');
|
||||
if (col === minCol) cell.classList.add('dt2-selection-border-left');
|
||||
if (col === maxCol) cell.classList.add('dt2-selection-border-right');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user