Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ export default function FillBetweenSlicesFlyout({ segmentIndex, maskFilter, onLo

const picker = useSliceAnchorPicker({
segmentIndex,
lastRequiresSegment: false,
// Unlike Copy-across-slices (whose destination legitimately doesn't
// need existing content), Fill needs a real segment on BOTH ends to
// interpolate between — a blank second slice has nothing to fill
// toward. Was `false` (copied from CopyAcrossSlicesFlyout without
// updating for this), which let step 2 accept any slice with no
// segment check at all.
lastRequiresSegment: true,
onError: (d) => { setPickError(d); onLog?.(d); },
});
const { phase, step, first, last } = picker;
Expand Down
4 changes: 2 additions & 2 deletions PanTS-Demo/src/components/segmentation/SegmentEffectPanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@

.seg-effect__number:focus {
outline: none;
border-color: #E76F51;
border-color: #0b17ff;
background: rgba(104, 172, 229, 0.08);
}

Expand Down Expand Up @@ -387,7 +387,7 @@

.seg-effect__icon-btn--primary {
border-color: rgba(15, 23, 42, 0.4);
color: #E76F51;
color: #0b17ff;
}

.seg-effect__icon-btn--primary:hover:not(:disabled) {
Expand Down
61 changes: 54 additions & 7 deletions PanTS-Demo/src/components/segmentation/SegmentsPopup.css
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@
}

.segpop__color-popover-swatch.is-active {
outline: 2px solid #E76F51;
outline: 2px solid #7cb7da;
outline-offset: 1px;
}

Expand All @@ -303,6 +303,53 @@
appearance: none;
}

/* Explicit commit footer for the color popover — live preview + hex value
+ a clear "Done" close button, so picking a color visibly registers
before the popover disappears instead of a swatch click silently
closing it with no confirmation of what was chosen. Doesn't itself save
the class (that's still the row's own Save/ApplyButton); this is purely
about the popover's own selection state being unambiguous. */
.segpop__color-popover-footer {
display: flex;
align-items: center;
gap: 8px;
margin-top: 2px;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.09);
}

.segpop__color-popover-preview {
width: 22px;
height: 22px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.2);
flex-shrink: 0;
}

.segpop__color-popover-hex {
font-size: 11px;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
color: rgba(255, 255, 255, 0.7);
flex: 1;
}

.segpop__color-popover-done {
appearance: none;
border: none;
border-radius: 6px;
padding: 5px 12px;
font-size: 11px;
font-weight: 600;
background: #7cb7da;
color: #16181d;
cursor: pointer;
}

.segpop__color-popover-done:hover {
background: #7cb7da;
}

/* Static color chip shown in the normal (non-editing) row — color is only
changed together with the name via the pen-icon editor, so this isn't an
<input type="color"> and doesn't open a picker on click. */
Expand All @@ -327,7 +374,7 @@
width: 18px;
height: 18px;
border-radius: 50%;
color: #E76F51;
color: #7cb7da;
background: rgba(104, 172, 229, 0.16);
}

Expand All @@ -339,8 +386,8 @@
}

.segpop__target-badge--btn:hover {
background: rgba(232, 93, 93, 0.22);
color: #E85D5D;
background: rgb(17, 0, 75);
color: #7cb7da;
transform: scale(1.08);
}

Expand Down Expand Up @@ -380,7 +427,7 @@

.segpop__edit-btn:hover,
.segpop__edit-btn.is-active {
color: #E76F51;
color: #7cb7da;
}

/* Floating add/edit form (see FormFlyout in SegmentsPopup.tsx). Portaled to
Expand Down Expand Up @@ -515,7 +562,7 @@
}

.segpop__delete:hover:not(:disabled) {
color: #E76F51;
color: #7cb7da;
}

.segpop__err {
Expand Down Expand Up @@ -653,7 +700,7 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: #E76F51;
background: #7cb7da;
}

.segpop__tab.is-active .segpop__tab-dot {
Expand Down
44 changes: 43 additions & 1 deletion PanTS-Demo/src/components/segmentation/SegmentsPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ interface ColorPickerPopoverProps {
function ColorPickerPopover({ value, onChange, onClose, anchorRef }: ColorPickerPopoverProps) {
const [closing, setClosing] = useState(false);
const popRef = useRef<HTMLDivElement>(null);
const colorInputRef = useRef<HTMLInputElement>(null);
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);

const requestClose = () => {
Expand Down Expand Up @@ -146,6 +147,16 @@ function ColorPickerPopover({ value, onChange, onClose, anchorRef }: ColorPicker
const onDown = (e: MouseEvent) => {
if (popRef.current?.contains(e.target as Node)) return;
if (anchorRef.current?.contains(e.target as Node)) return;
// The native OS color picker (behind the "Custom…" <input
// type="color">) can fire a synthetic mousedown on `document`
// itself — outside both the input and this whole component tree —
// when the user drags/picks within the OS dialog, in some
// browsers. Without this guard that got misread as "clicked
// outside the popover" and closed it mid-pick, which is exactly
// why changing the color via the custom picker felt broken —
// every drag/selection risked closing before it registered. Skip
// closing while the color input itself still has focus.
if (document.activeElement === colorInputRef.current) return;
requestClose();
};
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") requestClose(); };
Expand All @@ -163,6 +174,7 @@ function ColorPickerPopover({ value, onChange, onClose, anchorRef }: ColorPicker
return createPortal(
<div
ref={popRef}
data-color-popover-portal
className={`segpop__color-popover segpop__color-popover--portaled ${closing ? "is-closing" : "is-open"}`}
style={{ position: "fixed", top: pos.top, left: pos.left }}
onClick={(e) => e.stopPropagation()}
Expand All @@ -175,14 +187,34 @@ function ColorPickerPopover({ value, onChange, onClose, anchorRef }: ColorPicker
className={`segpop__color-popover-swatch ${value.toLowerCase() === hex.toLowerCase() ? "is-active" : ""}`}
style={{ background: hex }}
aria-label={hex}
// Stages the color (updates the draft the caller holds) but does
// NOT close the popover — picking a swatch used to auto-close
// immediately, which looked like "nothing happened" since the
// actual save is a separate action on the row below. Leaving it
// open lets the person see the live preview/hex below update
// and confirm with an explicit "Done" instead.
onClick={() => onChange(hex)}
/>
))}
</div>
<label className="segpop__color-popover-custom">
<input type="color" value={value} onChange={(e) => onChange(e.target.value)} />
<input ref={colorInputRef} type="color" value={value} onChange={(e) => onChange(e.target.value)} />
<span>Custom…</span>
</label>
{/* Explicit commit step for the popover itself — a live preview swatch
plus the hex value, so it's visually obvious a selection has been
made and staged, then "Done" closes the popover. This does NOT save
the class — that's still the row's own Save/ApplyButton — it just
makes clear the color choice registered before the popover goes
away, instead of a swatch click silently vanishing the popover with
no confirmation of what got picked. */}
<div className="segpop__color-popover-footer">
<span className="segpop__color-popover-preview" style={{ background: value }} aria-hidden="true" />
<span className="segpop__color-popover-hex">{value.toUpperCase()}</span>
<button type="button" className="segpop__color-popover-done" onClick={requestClose}>
Done
</button>
</div>
</div>,
document.body
);
Expand Down Expand Up @@ -268,6 +300,16 @@ function FormFlyout({ anchorEl, onClose, children }: FormFlyoutProps) {
const onDown = (e: MouseEvent) => {
if (panelRef.current?.contains(e.target as Node)) return;
if (anchorEl?.contains(e.target as Node)) return;
// The color swatch popover portals to document.body on its OWN,
// separate from this FormFlyout's portal — so a click on a preset
// swatch or the native color input isn't a descendant of either
// panelRef or anchorEl, and without this check got misread as
// "clicked outside the name/color editor," closing the whole
// editor instead of just the small color popover. The color
// popover already closes itself independently on its own outside
// click; this just stops THIS flyout from also reacting to a
// click that was actually still inside it, conceptually.
if ((e.target as Element | null)?.closest?.("[data-color-popover-portal]")) return;
requestClose();
};
const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") requestClose(); };
Expand Down
10 changes: 5 additions & 5 deletions PanTS-Demo/src/components/viewer/AnnotationToolbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@

.atb-flyout__range:active::-webkit-slider-thumb {
background: #0F172A;
border-color: #E76F51;
border-color: #68ACE5;
}

.atb-flyout__range::-moz-range-track {
Expand Down Expand Up @@ -348,8 +348,8 @@
background: rgba(34, 34, 42, 0.62);
backdrop-filter: blur(20px) saturate(150%);
-webkit-backdrop-filter: blur(20px) saturate(150%);
border-left: 2px solid #E76F51;
border-top: 2px solid #E76F51;
border-left: 2px solid #68ACE5;
border-top: 2px solid #68ACE5;
border-radius: 4px 0 0 0;
transform: translateX(-50%) rotate(45deg);
transition: left 0.2s ease;
Expand Down Expand Up @@ -920,7 +920,7 @@
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--accent, #E76F51);
color: var(--jhu-blue-accent, #68ACE5);
margin: 0 0 8px;
}

Expand Down Expand Up @@ -979,7 +979,7 @@
padding: 10px 0;
border-radius: 999px;
background: var(--jhu-blue, #0F172A);
border: 1px solid var(--accent, #E76F51);
border: 1px solid var(--jhu-blue-accent, #68ACE5);
color: #ffffff;
font-size: 12.5px;
font-weight: 800;
Expand Down
Loading
Loading