diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png index 604c23351..fadb02348 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-mobile-chromium-linux.png differ diff --git a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png index 1e2a1017c..350b01a28 100644 Binary files a/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png and b/browser_tests/tests/templates.spec.ts-snapshots/template-grid-tablet-chromium-linux.png differ diff --git a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png index b2fe8bb40..d50dc1135 100644 Binary files a/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png and b/browser_tests/tests/widget.spec.ts-snapshots/animated-image-preview-saved-webp-chromium-linux.png differ diff --git a/src/assets/css/style.css b/src/assets/css/style.css index ee6e697f0..8e4ecf6ae 100644 --- a/src/assets/css/style.css +++ b/src/assets/css/style.css @@ -7,65 +7,6 @@ @config '../../../tailwind.config.ts'; -@layer tailwind-utilities { - /* Set default values to prevent some styles from not working properly. */ - *, ::before, ::after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(66 153 225 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; - } - - @tailwind components; - @tailwind utilities; -} :root { --fg-color: #000; diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index c6eb86404..ee9bea3ba 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -96,7 +96,6 @@ import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vu import SideToolbar from '@/components/sidebar/SideToolbar.vue' import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue' import { useChainCallback } from '@/composables/functional/useChainCallback' -import { useNodeEventHandlers } from '@/composables/graph/useNodeEventHandlers' import { useViewportCulling } from '@/composables/graph/useViewportCulling' import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle' import { useNodeBadge } from '@/composables/node/useNodeBadge' @@ -116,6 +115,7 @@ import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys' import TransformPane from '@/renderer/core/layout/TransformPane.vue' import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue' import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue' +import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers' import { UnauthorizedError, api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' import { ChangeTracker } from '@/scripts/changeTracker' diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index bcd2a3636..a77f764f1 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -22,13 +22,14 @@ 'border-red-500 bg-red-50': error, 'will-change-transform': isDragging }, - lodCssClass + lodCssClass, + 'pointer-events-auto' ) " :style="[ { transform: `translate(${layoutPosition.x ?? position?.x ?? 0}px, ${(layoutPosition.y ?? position?.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`, - pointerEvents: 'auto' + zIndex: zIndex }, dragStyle ]" @@ -192,6 +193,7 @@ onErrorCaptured((error) => { // Use layout system for node position and dragging const { position: layoutPosition, + zIndex, startDrag, handleDrag: handleLayoutDrag, endDrag diff --git a/src/composables/graph/useNodeEventHandlers.ts b/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts similarity index 93% rename from src/composables/graph/useNodeEventHandlers.ts rename to src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts index e00f67b5b..5b354bf25 100644 --- a/src/composables/graph/useNodeEventHandlers.ts +++ b/src/renderer/extensions/vueNodes/composables/useNodeEventHandlers.ts @@ -11,8 +11,7 @@ import type { Ref } from 'vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' -import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' -import { LayoutSource } from '@/renderer/core/layout/types' +import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex' import { useCanvasStore } from '@/stores/graphStore' interface NodeManager { @@ -21,7 +20,7 @@ interface NodeManager { export function useNodeEventHandlers(nodeManager: Ref) { const canvasStore = useCanvasStore() - const layoutMutations = useLayoutMutations() + const { bringNodeToFront } = useNodeZIndex() /** * Handle node selection events @@ -51,8 +50,7 @@ export function useNodeEventHandlers(nodeManager: Ref) { // Bring node to front when clicked (similar to LiteGraph behavior) // Skip if node is pinned to avoid unwanted movement if (!node.flags?.pinned) { - layoutMutations.setSource(LayoutSource.Vue) - layoutMutations.bringNodeToFront(nodeData.id) + bringNodeToFront(nodeData.id) } // Update canvas selection tracking @@ -171,14 +169,13 @@ export function useNodeEventHandlers(nodeManager: Ref) { if (!canvasStore.canvas || !nodeManager.value) return if (!addToSelection) { - canvasStore.canvas.deselectAllNodes() + canvasStore.canvas.deselectAll() } nodeIds.forEach((nodeId) => { const node = nodeManager.value?.getNode(nodeId) if (node && canvasStore.canvas) { - canvasStore.canvas.selectNode(node) - node.selected = true + canvasStore.canvas.select(node) } }) diff --git a/src/renderer/extensions/vueNodes/composables/useNodeZIndex.ts b/src/renderer/extensions/vueNodes/composables/useNodeZIndex.ts new file mode 100644 index 000000000..3a6e71077 --- /dev/null +++ b/src/renderer/extensions/vueNodes/composables/useNodeZIndex.ts @@ -0,0 +1,36 @@ +/** + * Node Z-Index Management Composable + * + * Provides focused functionality for managing node layering through z-index. + * Integrates with the layout system to ensure proper visual ordering. + */ +import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' +import { LayoutSource } from '@/renderer/core/layout/types' +import type { NodeId } from '@/schemas/comfyWorkflowSchema' + +interface NodeZIndexOptions { + /** + * Layout source for z-index mutations + * @default LayoutSource.Vue + */ + layoutSource?: LayoutSource +} + +export function useNodeZIndex(options: NodeZIndexOptions = {}) { + const { layoutSource = LayoutSource.Vue } = options + const layoutMutations = useLayoutMutations() + + /** + * Bring node to front (highest z-index) + * @param nodeId - The node to bring to front + * @param source - Optional source override + */ + function bringNodeToFront(nodeId: NodeId, source?: LayoutSource) { + layoutMutations.setSource(source ?? layoutSource) + layoutMutations.bringNodeToFront(nodeId) + } + + return { + bringNodeToFront + } +} diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue new file mode 100644 index 000000000..45ce81aa1 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue new file mode 100644 index 000000000..6e91171b3 --- /dev/null +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.test.ts b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts similarity index 95% rename from src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.test.ts rename to src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts index 3e554023f..179c342d2 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.test.ts @@ -7,9 +7,9 @@ import { describe, expect, it } from 'vitest' import type { SimplifiedWidget } from '@/types/simplifiedWidget' -import WidgetSlider from './WidgetSlider.vue' +import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue' -describe('WidgetSlider Value Binding', () => { +describe('WidgetInputNumberSlider Value Binding', () => { const createMockWidget = ( value: number = 5, options: Partial = {}, @@ -27,7 +27,7 @@ describe('WidgetSlider Value Binding', () => { modelValue: number, readonly = false ) => { - return mount(WidgetSlider, { + return mount(WidgetInputNumberSlider, { global: { plugins: [PrimeVue], components: { InputText, Slider } diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue similarity index 98% rename from src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue rename to src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue index a303b4923..c9894f4b8 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberSlider.vue @@ -16,8 +16,6 @@ v-model="inputDisplayValue" :disabled="readonly" type="number" - :min="widget.options?.min" - :max="widget.options?.max" :step="stepValue" class="w-[4em] text-center text-xs px-0 !border-none !shadow-none !bg-transparent" size="small" diff --git a/src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts b/src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts index c8b0b0a49..b07c89e5b 100644 --- a/src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts +++ b/src/renderer/extensions/vueNodes/widgets/registry/widgetRegistry.ts @@ -9,12 +9,12 @@ import WidgetColorPicker from '../components/WidgetColorPicker.vue' import WidgetFileUpload from '../components/WidgetFileUpload.vue' import WidgetGalleria from '../components/WidgetGalleria.vue' import WidgetImageCompare from '../components/WidgetImageCompare.vue' +import WidgetInputNumber from '../components/WidgetInputNumber.vue' import WidgetInputText from '../components/WidgetInputText.vue' import WidgetMarkdown from '../components/WidgetMarkdown.vue' import WidgetMultiSelect from '../components/WidgetMultiSelect.vue' import WidgetSelect from '../components/WidgetSelect.vue' import WidgetSelectButton from '../components/WidgetSelectButton.vue' -import WidgetSlider from '../components/WidgetSlider.vue' import WidgetTextarea from '../components/WidgetTextarea.vue' import WidgetToggleSwitch from '../components/WidgetToggleSwitch.vue' import WidgetTreeSelect from '../components/WidgetTreeSelect.vue' @@ -38,11 +38,11 @@ const coreWidgetDefinitions: Array<[string, WidgetDefinition]> = [ essential: false } ], - ['int', { component: WidgetSlider, aliases: ['INT'], essential: true }], + ['int', { component: WidgetInputNumber, aliases: ['INT'], essential: true }], [ 'float', { - component: WidgetSlider, + component: WidgetInputNumber, aliases: ['FLOAT', 'number', 'slider'], essential: true } diff --git a/tests-ui/tests/composables/graph/useNodeEventHandlers.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts similarity index 96% rename from tests-ui/tests/composables/graph/useNodeEventHandlers.test.ts rename to tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts index d98a2134f..51a0ae235 100644 --- a/tests-ui/tests/composables/graph/useNodeEventHandlers.test.ts +++ b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeEventHandlers.test.ts @@ -1,13 +1,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { ref } from 'vue' -import type { - VueNodeData, - useGraphNodeManager -} from '@/composables/graph/useGraphNodeManager' -import { useNodeEventHandlers } from '@/composables/graph/useNodeEventHandlers' +import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' +import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' +import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers' import { useCanvasStore } from '@/stores/graphStore' vi.mock('@/stores/graphStore', () => ({ diff --git a/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeZIndex.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeZIndex.test.ts new file mode 100644 index 000000000..f9c669cd4 --- /dev/null +++ b/tests-ui/tests/renderer/extensions/vueNodes/composables/useNodeZIndex.test.ts @@ -0,0 +1,98 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' +import { LayoutSource } from '@/renderer/core/layout/types' +import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex' + +// Mock the layout mutations module +vi.mock('@/renderer/core/layout/operations/layoutMutations') + +const mockedUseLayoutMutations = vi.mocked(useLayoutMutations) + +describe('useNodeZIndex', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('bringNodeToFront', () => { + it('should bring node to front with default source', () => { + const mockSetSource = vi.fn() + const mockBringNodeToFront = vi.fn() + + mockedUseLayoutMutations.mockReturnValue({ + setSource: mockSetSource, + bringNodeToFront: mockBringNodeToFront + } as Partial> as ReturnType< + typeof useLayoutMutations + >) + + const { bringNodeToFront } = useNodeZIndex() + + bringNodeToFront('node1') + + expect(mockSetSource).toHaveBeenCalledWith(LayoutSource.Vue) + expect(mockBringNodeToFront).toHaveBeenCalledWith('node1') + }) + + it('should bring node to front with custom source', () => { + const mockSetSource = vi.fn() + const mockBringNodeToFront = vi.fn() + + mockedUseLayoutMutations.mockReturnValue({ + setSource: mockSetSource, + bringNodeToFront: mockBringNodeToFront + } as Partial> as ReturnType< + typeof useLayoutMutations + >) + + const { bringNodeToFront } = useNodeZIndex() + + bringNodeToFront('node2', LayoutSource.Canvas) + + expect(mockSetSource).toHaveBeenCalledWith(LayoutSource.Canvas) + expect(mockBringNodeToFront).toHaveBeenCalledWith('node2') + }) + + it('should use custom layout source from options', () => { + const mockSetSource = vi.fn() + const mockBringNodeToFront = vi.fn() + + mockedUseLayoutMutations.mockReturnValue({ + setSource: mockSetSource, + bringNodeToFront: mockBringNodeToFront + } as Partial> as ReturnType< + typeof useLayoutMutations + >) + + const { bringNodeToFront } = useNodeZIndex({ + layoutSource: LayoutSource.External + }) + + bringNodeToFront('node3') + + expect(mockSetSource).toHaveBeenCalledWith(LayoutSource.External) + expect(mockBringNodeToFront).toHaveBeenCalledWith('node3') + }) + + it('should override layout source with explicit source parameter', () => { + const mockSetSource = vi.fn() + const mockBringNodeToFront = vi.fn() + + mockedUseLayoutMutations.mockReturnValue({ + setSource: mockSetSource, + bringNodeToFront: mockBringNodeToFront + } as Partial> as ReturnType< + typeof useLayoutMutations + >) + + const { bringNodeToFront } = useNodeZIndex({ + layoutSource: LayoutSource.External + }) + + bringNodeToFront('node4', LayoutSource.Canvas) + + expect(mockSetSource).toHaveBeenCalledWith(LayoutSource.Canvas) + expect(mockBringNodeToFront).toHaveBeenCalledWith('node4') + }) + }) +}) diff --git a/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts index 4a363ebd1..64a8d614c 100644 --- a/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts +++ b/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts @@ -3,10 +3,10 @@ import { describe, expect, it } from 'vitest' import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue' import WidgetColorPicker from '@/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue' import WidgetFileUpload from '@/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue' +import WidgetInputNumber from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue' import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue' import WidgetMarkdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue' import WidgetSelect from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue' -import WidgetSlider from '@/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue' import WidgetTextarea from '@/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue' import WidgetToggleSwitch from '@/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue' import { @@ -20,15 +20,15 @@ describe('widgetRegistry', () => { // Test number type mappings describe('number types', () => { it('should map int types to slider widget', () => { - expect(getComponent('int')).toBe(WidgetSlider) - expect(getComponent('INT')).toBe(WidgetSlider) + expect(getComponent('int')).toBe(WidgetInputNumber) + expect(getComponent('INT')).toBe(WidgetInputNumber) }) it('should map float types to slider widget', () => { - expect(getComponent('float')).toBe(WidgetSlider) - expect(getComponent('FLOAT')).toBe(WidgetSlider) - expect(getComponent('number')).toBe(WidgetSlider) - expect(getComponent('slider')).toBe(WidgetSlider) + expect(getComponent('float')).toBe(WidgetInputNumber) + expect(getComponent('FLOAT')).toBe(WidgetInputNumber) + expect(getComponent('number')).toBe(WidgetInputNumber) + expect(getComponent('slider')).toBe(WidgetInputNumber) }) })