style: move step count tab to right side of group bounding box

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Guilhem
2026-03-04 18:08:04 +00:00
parent 498af855db
commit c5f366e68a
7 changed files with 70 additions and 58 deletions
@@ -209,6 +209,9 @@
let outputPickerInner: OutputPickerInner | undefined = $state(undefined)
let historyOpen = $derived.by(() => outputPickerInner?.getHistoryOpen?.() ?? false)
const NODE_ACTION_CLASS =
'center-center p-1 text-secondary cursor-grab hover:bg-surface-hover hover:text-primary'
</script>
{#if deletable && id && editId}
@@ -532,7 +535,7 @@
{#if !isMultiSelected && id !== 'preprocessor' && moveManager && id}
<div
class="absolute -translate-y-[100%] top-2 right-5 h-7 p-1 min-w-7"
class="absolute -translate-y-[100%] top-1 right-5 h-7 p-1 min-w-7"
style="will-change: transform;"
>
<MoveHandleButton
@@ -548,7 +551,7 @@
{#if !isMultiSelected && menuItems && menuItems.length > 0}
<div
class="absolute -translate-y-[100%] top-2 -right-2 h-7 p-1 min-w-7"
class="absolute -translate-y-[100%] top-1 -right-1 h-7 p-1 min-w-7"
style="will-change: transform;"
>
<DropdownV2
@@ -561,9 +564,8 @@
{#snippet buttonReplacement()}
<button
class={twMerge(
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary',
NODE_ACTION_CLASS,
hover || selected || dropdownOpen ? 'block' : '!hidden',
'shadow-md rounded-md',
'group-hover:block'
)}
onpointerdown={stopPropagation(preventDefault(() => {}))}
@@ -34,7 +34,7 @@
</script>
<div
class="absolute -translate-y-[100%] top-2 right-0 h-7 p-1 min-w-7"
class="absolute -translate-y-[100%] top-1 right-0 h-7 p-1 min-w-7"
style="will-change: transform;"
>
<DropdownV2
@@ -47,9 +47,8 @@
{#snippet buttonReplacement()}
<button
class={twMerge(
'center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary',
visible || menuOpen ? 'block' : '!hidden',
'shadow-md rounded-md'
'center-center p-1 text-secondary duration-0 hover:bg-surface-hover hover:text-primary rounded-md',
visible || menuOpen ? 'block' : '!hidden'
)}
onpointerdown={stopPropagation(preventDefault(() => {}))}
title="Actions"
@@ -58,7 +57,9 @@
</button>
{/snippet}
{#snippet menu()}
<div class="bg-surface-tertiary dark:border w-56 origin-top-right rounded-lg shadow-lg focus:outline-none py-1">
<div
class="bg-surface-tertiary dark:border w-56 origin-top-right rounded-lg shadow-lg focus:outline-none py-1"
>
<!-- Color picker -->
<div class="px-4 py-2">
<div class="grid grid-cols-5 gap-1">
@@ -89,10 +90,15 @@
<!-- Add / Remove note -->
<button
class="px-4 py-2 text-primary font-normal hover:bg-surface-hover cursor-pointer text-xs w-full flex flex-row gap-2 items-center rounded-sm"
onclick={() => { note == null ? onAddNote() : onRemoveNote(); menuOpen = false }}
onclick={() => {
note == null ? onAddNote() : onRemoveNote()
menuOpen = false
}}
>
<StickyNote size={14} class="shrink-0" />
<p class="truncate grow min-w-0 whitespace-nowrap text-left">{note == null ? 'Add note' : 'Remove note'}</p>
<p class="truncate grow min-w-0 whitespace-nowrap text-left"
>{note == null ? 'Add note' : 'Remove note'}</p
>
</button>
{#if onDeleteGroup}
@@ -101,7 +107,10 @@
<!-- Ungroup -->
<button
class="px-4 py-2 font-normal hover:bg-red-500/10 cursor-pointer text-xs w-full flex flex-row gap-2 items-center rounded-sm text-red-600 dark:text-red-400"
onclick={() => { onDeleteGroup?.(); menuOpen = false }}
onclick={() => {
onDeleteGroup?.()
menuOpen = false
}}
>
<Ungroup size={14} class="shrink-0" />
<p class="truncate grow min-w-0 whitespace-nowrap text-left">Ungroup</p>
@@ -86,12 +86,11 @@
if (group.module_ids.length === 0) return null
const { minX, minY, maxX, maxY } = calculateNodesBoundsWithOffset(group.module_ids, allNodes)
const padding = 16
const topPadding = 28
return {
x: minX - padding,
y: minY - topPadding,
y: minY - padding,
width: maxX - minX + 2 * padding,
height: maxY - minY + topPadding + padding
height: maxY - minY + 2 * padding
}
}
@@ -167,7 +166,7 @@
// Include the header node itself in the bounds
const allIds = [node.id, ...innerIds]
const { minX, minY, maxX, maxY } = calculateNodesBoundsWithOffset(allIds, allNodes)
const padding = 16
const padding = 8
return {
x: minX - padding,
y: minY - padding,
@@ -198,11 +197,11 @@
<ViewportPortal target="front">
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute flex items-start justify-between"
class="absolute flex items-start justify-end"
style="pointer-events: auto; transform: translate({bounds.x}px, {bounds.y}px); width: {bounds.width}px;"
style:z-index="4"
>
<div class="relative" style="margin-left: 16px;">
<div class="relative" style="margin-right: 16px;">
<StepCountTab
stepCount={group.module_ids.length}
color={group.color}
@@ -210,42 +209,43 @@
short
onExpand={() => toggleCollapse(group.id)}
/>
{#if editingGroupId === group.id}
{@const textColorClass =
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
<input
bind:this={summaryInputEl}
bind:value={summaryInput}
class="absolute !text-3xs !font-medium !h-4 !bg-transparent !outline-none {textColorClass}"
style="top: 2px; left:-3px; width: 160px; padding: 2px 2px;"
onblur={() => saveSummary(group.id)}
onkeydown={(e) => handleSummaryKeydown(e, group.id)}
onclick={stopPropagation(preventDefault(() => {}))}
onpointerdown={stopPropagation(preventDefault(() => {}))}
spellcheck={false}
/>
{:else}
{@const textColorClass =
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span
class="absolute text-3xs font-medium truncate max-w-[150px] text-opacity-60 {textColorClass} {editMode
? 'cursor-text rounded px-0.5 -mx-0.5 hover:text-opacity-100'
: ''}"
style="top: 2px; left: 0px;"
onclick={editMode
? stopPropagation(
preventDefault(() => startEditingSummary(group.id, group.summary ?? ''))
)
: undefined}
onpointerdown={editMode ? stopPropagation(preventDefault(() => {})) : undefined}
>{group.summary || (editMode ? 'Group' : '')}</span
>
{/if}
</div>
{#if editingGroupId === group.id}
{@const textColorClass =
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
<input
bind:this={summaryInputEl}
bind:value={summaryInput}
class="absolute !text-3xs !font-medium !h-4 !bg-transparent !outline-none {textColorClass}"
style="top: -18px; left:-3px; width: 160px; padding: 2px 2px;"
onblur={() => saveSummary(group.id)}
onkeydown={(e) => handleSummaryKeydown(e, group.id)}
onclick={stopPropagation(preventDefault(() => {}))}
onpointerdown={stopPropagation(preventDefault(() => {}))}
spellcheck={false}
/>
{:else}
{@const textColorClass =
NOTE_COLORS[(group.color as NoteColor) ?? NoteColor.BLUE]?.text ?? ''}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span
class="absolute text-3xs font-medium truncate max-w-[150px] text-opacity-60 {textColorClass} {editMode
? 'cursor-text rounded px-0.5 -mx-0.5 hover:text-opacity-100'
: ''}"
style="top: -18px; left: 0px;"
onclick={editMode
? stopPropagation(
preventDefault(() => startEditingSummary(group.id, group.summary ?? ''))
)
: undefined}
onpointerdown={editMode ? stopPropagation(preventDefault(() => {})) : undefined}
>{group.summary || (editMode ? 'Group' : '')}</span
>
{/if}
{#if editMode}
<div class="relative" style="margin-top: -8px;">
<div class="absolute" style="top: 0px; right: 16px;">
<GroupActionBar
note={group.note}
color={group.color}
@@ -61,9 +61,9 @@
<button
class={twMerge(
'center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary cursor-grab',
'center-center p-1 text-secondary cursor-grab hover:bg-surface-hover hover:text-primary',
visible ? '' : '!hidden',
'shadow-md rounded-md',
'rounded-md',
'touch-none',
extraClass
)}
@@ -24,7 +24,7 @@
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute left-0 rounded-t-md px-2 text-3xs font-medium z-[-1] flex items-start gap-0.5 {colorConfig.background} {colorConfig.text} {onExpand
class="absolute right-[48px] rounded-t-md px-2 text-3xs font-medium z-[-1] flex items-start gap-0.5 {colorConfig.background} {colorConfig.text} {onExpand
? 'cursor-pointer hover:opacity-80'
: ''}"
style="top: -20px; height: {short ? 20 : 34}px;"
@@ -261,7 +261,7 @@ export function getGroupEditorContext(): GroupEditorContext | undefined {
/** Extra vertical space pushed above the topmost node of each group for the tab */
export const GROUP_HEADER_HEIGHT = 0
const GROUP_TOP_MARGIN = 28
const GROUP_TOP_MARGIN = 16
/**
* Compute adjusted node positions for collapsed groups whose note is visible.
@@ -53,7 +53,8 @@
if (data.isCollapsedContainer) return data.containerModules?.length ?? 0
const v = data.module.value
if (v.type === 'branchall') return v.branches.flatMap((b) => b.modules).length
if (v.type === 'branchone') return v.default.length + v.branches.flatMap((b) => b.modules).length
if (v.type === 'branchone')
return v.default.length + v.branches.flatMap((b) => b.modules).length
if (v.type === 'forloopflow' || v.type === 'whileloopflow') return v.modules.length
return 0
})
@@ -61,7 +62,7 @@
// Define context menu items
let noteDisabled = $derived(
!noteEditorContext?.noteEditor ||
(noteEditorContext?.noteEditor?.isNodeOnlyMemberOfGroupNote(data.id) ?? false)
(noteEditorContext?.noteEditor?.isNodeOnlyMemberOfGroupNote(data.id) ?? false)
)
let isPreprocessor = $derived(data.id === 'preprocessor')