feat(handles): allow 1-48 bottom connection points via slider
Replaces the 4-option dropdown with a 1..48 slider. Existing handle IDs
('bottom', 'bottom-2'..'bottom-4') are preserved and the hand-tuned pixel
positions for counts 1..4 are locked in (regression-tested) so saved
canvases render identically. Counts >=5 use uniform spacing; node card
auto-grows in width so handles stay clickable. Existing edge-remap on
shrink scales unchanged thanks to its index-based loop.
ha-relevant: yes
This commit is contained in:
@@ -2,20 +2,44 @@
|
||||
* Bottom handle configuration for multi-handle nodes.
|
||||
*
|
||||
* Handle IDs: index 0 = 'bottom' (always the default, backward-compatible)
|
||||
* index 1 = 'bottom-2', index 2 = 'bottom-3', index 3 = 'bottom-4'
|
||||
* index N≥1 = 'bottom-${N+1}' (so idx 1 = 'bottom-2', idx 47 = 'bottom-48')
|
||||
*
|
||||
* Invisible target handles follow the same pattern with a '-t' suffix:
|
||||
* 'bottom-t', 'bottom-2-t', 'bottom-3-t', 'bottom-4-t'
|
||||
* 'bottom-t', 'bottom-2-t', ..., 'bottom-48-t'
|
||||
*/
|
||||
|
||||
export const BOTTOM_HANDLE_IDS = ['bottom', 'bottom-2', 'bottom-3', 'bottom-4'] as const
|
||||
export const MIN_BOTTOM_HANDLES = 1
|
||||
export const MAX_BOTTOM_HANDLES = 48
|
||||
|
||||
/** Left % position for each handle slot, per count. */
|
||||
export const BOTTOM_HANDLE_POSITIONS: Record<number, number[]> = {
|
||||
1: [50],
|
||||
2: [25, 75],
|
||||
3: [20, 50, 80],
|
||||
4: [15, 38, 62, 85],
|
||||
/** Returns the source handle ID at a given slot index. */
|
||||
export function bottomHandleId(idx: number): string {
|
||||
return idx === 0 ? 'bottom' : `bottom-${idx + 1}`
|
||||
}
|
||||
|
||||
/** Clamp a raw count into the supported range. Non-finite or non-int → MIN. */
|
||||
export function clampBottomHandles(n: unknown): number {
|
||||
if (typeof n !== 'number' || !Number.isFinite(n)) return MIN_BOTTOM_HANDLES
|
||||
const i = Math.floor(n)
|
||||
if (i < MIN_BOTTOM_HANDLES) return MIN_BOTTOM_HANDLES
|
||||
if (i > MAX_BOTTOM_HANDLES) return MAX_BOTTOM_HANDLES
|
||||
return i
|
||||
}
|
||||
|
||||
/**
|
||||
* Left % positions for each handle slot.
|
||||
* Counts 1..4 keep their original hand-tuned values to preserve exact pixel
|
||||
* positions on canvases saved before the multi-handle expansion.
|
||||
* Counts ≥ 5 use uniform spacing.
|
||||
*/
|
||||
export function bottomHandlePositions(count: number): number[] {
|
||||
const c = clampBottomHandles(count)
|
||||
switch (c) {
|
||||
case 1: return [50]
|
||||
case 2: return [25, 75]
|
||||
case 3: return [20, 50, 80]
|
||||
case 4: return [15, 38, 62, 85]
|
||||
default: return Array.from({ length: c }, (_, i) => ((i + 1) * 100) / (c + 1))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +63,7 @@ export function normalizeHandle(h: string | null | undefined): string | null {
|
||||
export function removedBottomHandleIds(oldCount: number, newCount: number): Set<string> {
|
||||
const removed = new Set<string>()
|
||||
for (let i = newCount; i < oldCount; i++) {
|
||||
removed.add(BOTTOM_HANDLE_IDS[i])
|
||||
removed.add(bottomHandleId(i))
|
||||
}
|
||||
return removed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user