Added TreeView and Panel

This commit is contained in:
2025-11-29 18:15:20 +01:00
parent ce5328fe34
commit 1d20fb8650
21 changed files with 2343 additions and 366 deletions

View File

@@ -465,4 +465,210 @@
.mf-dropdown.is-visible {
display: block;
opacity: 1;
}
}
/* *********************************************** */
/* ************** TreeView Component ************* */
/* *********************************************** */
/* TreeView Container */
.mf-treeview {
width: 100%;
user-select: none;
}
/* TreeNode Container */
.mf-treenode-container {
width: 100%;
}
/* TreeNode Element */
.mf-treenode {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 2px 0.5rem;
cursor: pointer;
transition: background-color 0.15s ease;
border-radius: 0.25rem;
}
/* Input for Editing */
.mf-treenode-input {
flex: 1;
padding: 2px 0.25rem;
border: 1px solid var(--color-primary);
border-radius: 0.25rem;
background-color: var(--color-base-100);
outline: none;
}
.mf-treenode:hover {
background-color: var(--color-base-200);
}
.mf-treenode.selected {
background-color: var(--color-primary);
color: var(--color-primary-content);
}
/* Toggle Icon */
.mf-treenode-toggle {
flex-shrink: 0;
width: 20px;
text-align: center;
font-size: 0.75rem;
}
/* Node Label */
.mf-treenode-label {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mf-treenode-input:focus {
box-shadow: 0 0 0 2px color-mix(in oklab, var(--color-primary) 25%, transparent);
}
/* Action Buttons - Hidden by default, shown on hover */
.mf-treenode-actions {
display: none;
gap: 0.1rem;
white-space: nowrap;
margin-left: 0.5rem;
}
.mf-treenode:hover .mf-treenode-actions {
display: flex;
}
/* *********************************************** */
/* ********** Generic Resizer Classes ************ */
/* *********************************************** */
/* Generic resizer - used by both Layout and Panel */
.mf-resizer {
position: absolute;
width: 4px;
cursor: col-resize;
background-color: transparent;
transition: background-color 0.2s ease;
z-index: 100;
top: 0;
bottom: 0;
}
.mf-resizer:hover {
background-color: rgba(59, 130, 246, 0.3);
}
/* Active state during resize */
.mf-resizing .mf-resizer {
background-color: rgba(59, 130, 246, 0.5);
}
/* Prevent text selection during resize */
.mf-resizing {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
/* Cursor override for entire body during resize */
.mf-resizing * {
cursor: col-resize !important;
}
/* Visual indicator for resizer on hover - subtle border */
.mf-resizer::before {
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 40px;
background-color: rgba(156, 163, 175, 0.4);
border-radius: 2px;
opacity: 0;
transition: opacity 0.2s ease;
}
.mf-resizer:hover::before,
.mf-resizing .mf-resizer::before {
opacity: 1;
}
/* Resizer positioning */
/* Left resizer is on the right side of the left panel */
.mf-resizer-left {
right: 0;
}
/* Right resizer is on the left side of the right panel */
.mf-resizer-right {
left: 0;
}
/* Position indicator for resizer */
.mf-resizer-left::before {
right: 1px;
}
.mf-resizer-right::before {
left: 1px;
}
/* Disable transitions during resize for smooth dragging */
.mf-item-resizing {
transition: none !important;
}
/* *********************************************** */
/* *************** Panel Component *************** */
/* *********************************************** */
/* Container principal du panel */
.mf-panel {
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
/* Panel gauche */
.mf-panel-left {
position: relative;
flex-shrink: 0;
width: 250px;
min-width: 150px;
max-width: 400px;
height: 100%;
overflow: auto;
border-right: 1px solid var(--color-border-primary);
}
/* Panel principal (centre) */
.mf-panel-main {
flex: 1;
height: 100%;
overflow: auto;
min-width: 0; /* Important pour permettre le shrink du flexbox */
}
/* Panel droit */
.mf-panel-right {
position: relative;
flex-shrink: 0;
width: 300px;
min-width: 150px;
max-width: 500px;
height: 100%;
overflow: auto;
border-left: 1px solid var(--color-border-primary);
}

View File

@@ -1,40 +1,43 @@
/**
* Layout Drawer Resizer
* Generic Resizer
*
* Handles resizing of left and right drawers with drag functionality.
* Handles resizing of elements with drag functionality.
* Communicates with server via HTMX to persist width changes.
* Works for both Layout drawers and Panel sides.
*/
/**
* Initialize drawer resizer functionality for a specific layout instance
* Initialize resizer functionality for a specific container
*
* @param {string} layoutId - The ID of the layout instance to initialize
* @param {string} containerId - The ID of the container instance to initialize
* @param {Object} options - Configuration options
* @param {number} options.minWidth - Minimum width in pixels (default: 150)
* @param {number} options.maxWidth - Maximum width in pixels (default: 600)
*/
function initLayoutResizer(layoutId) {
'use strict';
function initResizer(containerId, options = {}) {
const MIN_WIDTH = 150;
const MAX_WIDTH = 600;
const MIN_WIDTH = options.minWidth || 150;
const MAX_WIDTH = options.maxWidth || 600;
let isResizing = false;
let currentResizer = null;
let currentDrawer = null;
let currentItem = null;
let startX = 0;
let startWidth = 0;
let side = null;
const layoutElement = document.getElementById(layoutId);
const containerElement = document.getElementById(containerId);
if (!layoutElement) {
console.error(`Layout element with ID "${layoutId}" not found`);
if (!containerElement) {
console.error(`Container element with ID "${containerId}" not found`);
return;
}
/**
* Initialize resizer functionality for this layout instance
* Initialize resizer functionality for this container instance
*/
function initResizers() {
const resizers = layoutElement.querySelectorAll('.mf-layout-resizer');
const resizers = containerElement.querySelectorAll('.mf-resizer');
resizers.forEach(resizer => {
// Remove existing listener if any to avoid duplicates
@@ -51,24 +54,24 @@ function initLayoutResizer(layoutId) {
currentResizer = e.target;
side = currentResizer.dataset.side;
currentDrawer = currentResizer.closest('.mf-layout-drawer');
currentItem = currentResizer.parentElement;
if (!currentDrawer) {
console.error('Could not find drawer element');
if (!currentItem) {
console.error('Could not find item element');
return;
}
isResizing = true;
startX = e.clientX;
startWidth = currentDrawer.offsetWidth;
startWidth = currentItem.offsetWidth;
// Add event listeners for mouse move and up
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
// Add resizing class for visual feedback
document.body.classList.add('mf-layout-resizing');
currentDrawer.classList.add('mf-layout-drawer-resizing');
document.body.classList.add('mf-resizing');
currentItem.classList.add('mf-item-resizing');
}
/**
@@ -92,8 +95,8 @@ function initLayoutResizer(layoutId) {
// Constrain width between min and max
newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, newWidth));
// Update drawer width visually
currentDrawer.style.width = `${newWidth}px`;
// Update item width visually
currentItem.style.width = `${newWidth}px`;
}
/**
@@ -109,11 +112,11 @@ function initLayoutResizer(layoutId) {
document.removeEventListener('mouseup', handleMouseUp);
// Remove resizing classes
document.body.classList.remove('mf-layout-resizing');
currentDrawer.classList.remove('mf-layout-drawer-resizing');
document.body.classList.remove('mf-resizing');
currentItem.classList.remove('mf-item-resizing');
// Get final width
const finalWidth = currentDrawer.offsetWidth;
const finalWidth = currentItem.offsetWidth;
const commandId = currentResizer.dataset.commandId;
if (!commandId) {
@@ -122,24 +125,24 @@ function initLayoutResizer(layoutId) {
}
// Send width update to server
saveDrawerWidth(commandId, finalWidth);
saveWidth(commandId, finalWidth);
// Reset state
currentResizer = null;
currentDrawer = null;
currentItem = null;
side = null;
}
/**
* Save drawer width to server via HTMX
* Save width to server via HTMX
*/
function saveDrawerWidth(commandId, width) {
function saveWidth(commandId, width) {
htmx.ajax('POST', '/myfasthtml/commands', {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
swap: "outerHTML",
target: `#${currentDrawer.id}`,
target: `#${currentItem.id}`,
values: {
c_id: commandId,
width: width
@@ -150,8 +153,8 @@ function initLayoutResizer(layoutId) {
// Initialize resizers
initResizers();
// Re-initialize after HTMX swaps within this layout
layoutElement.addEventListener('htmx:afterSwap', function (event) {
// Re-initialize after HTMX swaps within this container
containerElement.addEventListener('htmx:afterSwap', function (event) {
initResizers();
});
}