fix: address review feedback for ParalinguisticInput

- Initialize lastSerializedRef to empty string so first-mount hydration
  always runs (fixes initial value not rendering)
- Guard arrow-key menu nav against empty filteredTags (avoids NaN index)
- Disable ARIA role/multiline and detach event handlers when disabled
- Add onBlur to close autocomplete dropdown when editor loses focus
- Chain exception with 'from e' in unload endpoint for better tracebacks
This commit is contained in:
James Pine
2026-03-13 05:52:06 -07:00
parent b420637957
commit 139fa38e3f
2 changed files with 21 additions and 9 deletions
@@ -136,7 +136,7 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
left: 0, left: 0,
}); });
const triggerRangeRef = useRef<Range | null>(null); const triggerRangeRef = useRef<Range | null>(null);
const lastSerializedRef = useRef<string>(value ?? ''); const lastSerializedRef = useRef<string>('');
const isComposingRef = useRef(false); const isComposingRef = useRef(false);
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
@@ -218,6 +218,13 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => { (e: React.KeyboardEvent) => {
if (showMenu) { if (showMenu) {
if (filteredTags.length === 0) {
if (e.key === 'Escape') {
e.preventDefault();
setShowMenu(false);
}
return;
}
if (e.key === 'ArrowDown') { if (e.key === 'ArrowDown') {
e.preventDefault(); e.preventDefault();
setMenuIndex((i) => (i + 1) % filteredTags.length); setMenuIndex((i) => (i + 1) % filteredTags.length);
@@ -334,10 +341,11 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
ref={editorRef} ref={editorRef}
contentEditable={!disabled} contentEditable={!disabled}
suppressContentEditableWarning suppressContentEditableWarning
role="textbox" role={disabled ? undefined : 'textbox'}
aria-multiline aria-multiline={disabled ? undefined : true}
aria-placeholder={placeholder} aria-placeholder={placeholder}
aria-disabled={disabled} aria-disabled={disabled}
tabIndex={disabled ? -1 : 0}
className={cn( className={cn(
'min-h-[32px] text-sm whitespace-pre-wrap break-words outline-none', 'min-h-[32px] text-sm whitespace-pre-wrap break-words outline-none',
'[&_.ptag-badge]:inline-flex [&_.ptag-badge]:items-center [&_.ptag-badge]:rounded-full', '[&_.ptag-badge]:inline-flex [&_.ptag-badge]:items-center [&_.ptag-badge]:rounded-full',
@@ -349,11 +357,15 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
className, className,
)} )}
style={style} style={style}
onInput={handleInput} onInput={!disabled ? handleInput : undefined}
onKeyDown={handleKeyDown} onKeyDown={!disabled ? handleKeyDown : undefined}
onPaste={handlePaste} onPaste={!disabled ? handlePaste : undefined}
onClick={onClick} onClick={!disabled ? onClick : undefined}
onFocus={onFocus} onFocus={!disabled ? onFocus : undefined}
onBlur={() => {
setShowMenu(false);
triggerRangeRef.current = null;
}}
onCompositionStart={() => { onCompositionStart={() => {
isComposingRef.current = true; isComposingRef.current = true;
}} }}
+1 -1
View File
@@ -1546,7 +1546,7 @@ async def unload_model_by_name(model_name: str):
return {"message": f"Model {model_name} unloaded successfully"} return {"message": f"Model {model_name} unloaded successfully"}
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e)) from e
@app.get("/models/progress/{model_name}") @app.get("/models/progress/{model_name}")