Handle z-index for stacked group notes

This commit is contained in:
Guilhem
2025-11-11 19:59:01 +01:00
parent 94be7911bb
commit e1b7efbc80
3 changed files with 78 additions and 14 deletions
@@ -524,7 +524,12 @@
(noteId: string, height: number) => {
noteTextHeights[noteId] = height
},
editMode
editMode,
Object.values(graph.nodes).map((n) => ({
id: n.id,
parentIds: n.parentIds,
offset: n.data.offset ?? 0
}))
)
]
edges = [
@@ -764,6 +769,7 @@
preventScrolling={scroll}
zoomOnDoubleClick={false}
elementsSelectable={false}
elevateNodesOnSelect={false}
{proOptions}
nodesDraggable={false}
--background-color={false}
@@ -156,8 +156,7 @@ export function buildNodeSpacingMap(
* Creates a stable hash of the noteTextHeights object for cache comparison
*/
export function hashNoteTextHeights(noteTextHeights: Record<string, number>): string {
const entries = Object.entries(noteTextHeights)
.sort(([a], [b]) => a.localeCompare(b)) // Sort for stable hash
const entries = Object.entries(noteTextHeights).sort(([a], [b]) => a.localeCompare(b)) // Sort for stable hash
return JSON.stringify(entries)
}
@@ -165,10 +164,13 @@ export function hashNoteTextHeights(noteTextHeights: Record<string, number>): st
/**
* Extracts note state signature for layout cache comparison
*/
export function getNoteStateSignature(groupNotes: FlowNote[], noteTextHeights: Record<string, number>) {
export function getNoteStateSignature(
groupNotes: FlowNote[],
noteTextHeights: Record<string, number>
) {
return {
notesCount: groupNotes.length,
noteIds: groupNotes.map(n => n.id).sort(),
noteIds: groupNotes.map((n) => n.id).sort(),
textHeightHash: hashNoteTextHeights(noteTextHeights)
}
}
@@ -180,11 +182,11 @@ export function getNoteStateSignature(groupNotes: FlowNote[], noteTextHeights: R
export function getLayoutSignature(notes: FlowNote[]) {
return {
notesCount: notes.length,
noteIds: notes.map(n => n.id).sort(),
noteIds: notes.map((n) => n.id).sort(),
// Group memberships affect layout spacing
groupMemberships: notes
.filter(note => note.type === 'group')
.map(note => ({
.filter((note) => note.type === 'group')
.map((note) => ({
id: note.id,
containedIds: note.contained_node_ids?.slice().sort() || []
}))
@@ -198,7 +200,7 @@ export function getLayoutSignature(notes: FlowNote[]) {
*/
export function getPropertySignature(notes: FlowNote[]) {
return notes
.map(note => ({
.map((note) => ({
id: note.id,
text: note.text,
color: note.color,
@@ -208,3 +210,43 @@ export function getPropertySignature(notes: FlowNote[]) {
}))
.sort((a, b) => a.id.localeCompare(b.id))
}
/**
* Calculates z-index values for all notes in a single graph traversal
* Group notes are ordered by their topmost node's hierarchy position
* Free notes get undefined z-index to use SvelteFlow's native behavior
*/
export function calculateAllNoteZIndexes(
notes: FlowNote[],
nodes: NodeDep[]
): Record<string, number | undefined> {
const zIndexMap: Record<string, number | undefined> = {}
// Create a mapping from node ID to its hierarchy position (topological order)
const nodeHierarchyMap: Record<string, number> = {}
nodes.forEach((node, index) => {
nodeHierarchyMap[node.id] = index
})
// Process each note
for (const note of notes) {
if (note.type === 'free') {
// Free notes use SvelteFlow's native z-index behavior (last selected on top)
zIndexMap[note.id] = undefined
} else if (note.type === 'group') {
// Group notes get z-index based on topmost contained node's hierarchy
const topmostNode = findTopmostNodeInGroup(note, nodes)
if (topmostNode) {
const hierarchyPosition = nodeHierarchyMap[topmostNode.id] ?? 0
// Higher hierarchy position = lower z-index (appears behind)
// Use negative values starting from -2000 to stay below other elements
zIndexMap[note.id] = hierarchyPosition - 2000
} else {
// Fallback for group notes without valid contained nodes
zIndexMap[note.id] = -2000
}
}
}
return zIndexMap
}
@@ -1,7 +1,11 @@
import type { FlowNote } from '$lib/gen'
import type { Node } from '@xyflow/svelte'
import { calculateNodesBounds } from './util'
import { getLayoutSignature, getPropertySignature } from './groupNoteUtils'
import {
getLayoutSignature,
getPropertySignature,
calculateAllNoteZIndexes
} from './groupNoteUtils'
import { deepEqual } from 'fast-equals'
export type NodePosition = {
@@ -191,7 +195,8 @@ export class NoteManager {
currentNodes: Node[],
textHeights: Record<string, number>,
onTextHeightChange: (noteId: string, height: number) => void,
editMode: boolean = false
editMode: boolean = false,
zIndex?: number
): Node {
const isGroupNote = note.type === 'group'
@@ -208,7 +213,7 @@ export class NoteManager {
style: `width: ${size.width}px; height: ${size.height}px;`,
width: size.width,
height: size.height,
zIndex: -2000,
zIndex: zIndex ?? -2000, // Use provided zIndex or fallback
draggable: isGroupNote ? false : editMode && !note.locked,
selectable: true
}
@@ -222,10 +227,21 @@ export class NoteManager {
currentNodes: Node[],
textHeights: Record<string, number>,
onTextHeightChange: (noteId: string, height: number) => void,
editMode: boolean = false
editMode: boolean = false,
flowNodes?: { id: string; parentIds?: string[]; offset?: number }[]
): Node[] {
// Calculate z-indexes for all notes in a single traversal
const zIndexMap = flowNodes ? calculateAllNoteZIndexes(notes, flowNodes) : {}
return notes.map((note) =>
this.convertNoteToNode(note, currentNodes, textHeights, onTextHeightChange, editMode)
this.convertNoteToNode(
note,
currentNodes,
textHeights,
onTextHeightChange,
editMode,
zIndexMap[note.id]
)
)
}