자동완성 포커스 유지 및 디버그 로그 추가
This commit is contained in:
@@ -155,11 +155,21 @@ class _ApprovalApproverAutocompleteFieldState
|
||||
_applyManualEntry(textController.text);
|
||||
onFieldSubmitted();
|
||||
},
|
||||
onPressedOutside: (event) {
|
||||
// 드롭다운에서 항목을 고르기 전에 포커스를 잃지 않도록 한다.
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -184,9 +194,16 @@ class _ApprovalApproverAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -234,6 +251,7 @@ class _ApprovalApproverAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -198,6 +198,10 @@ class _InventoryEmployeeAutocompleteFieldState
|
||||
placeholder: Text(widget.placeholder),
|
||||
onChanged: (_) => widget.onChanged?.call(),
|
||||
onSubmitted: (_) => onFieldSubmitted(),
|
||||
onPressedOutside: (event) {
|
||||
// 드롭다운 선택 중 포커스를 유지해 리스트가 즉시 닫히는 문제를 방지한다.
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
if (!_isSearching) {
|
||||
return input;
|
||||
@@ -219,7 +223,14 @@ class _InventoryEmployeeAutocompleteFieldState
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
// 포커스가 사라지면 항목 선택 전에 닫히므로 즉시 복구한다.
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: Material(
|
||||
elevation: 6,
|
||||
@@ -235,9 +246,16 @@ class _InventoryEmployeeAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 240, maxWidth: 360),
|
||||
@@ -267,7 +285,9 @@ class _InventoryEmployeeAutocompleteFieldState
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'ID ${suggestion.id} · ${suggestion.employeeNo}',
|
||||
style: theme.textTheme.muted.copyWith(fontSize: 12),
|
||||
style: theme.textTheme.muted.copyWith(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -277,6 +297,7 @@ class _InventoryEmployeeAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -239,6 +239,10 @@ class _InventoryProductAutocompleteFieldState
|
||||
placeholder: const Text('제품명 검색'),
|
||||
onChanged: (_) => widget.onChanged?.call(),
|
||||
onSubmitted: (_) => onFieldSubmitted(),
|
||||
onPressedOutside: (event) {
|
||||
// 포커스를 유지해 항목 선택 전 오버레이가 닫히지 않도록 한다.
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
);
|
||||
if (!_isSearching) {
|
||||
return input;
|
||||
@@ -260,7 +264,13 @@ class _InventoryProductAutocompleteFieldState
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!widget.productFocusNode.hasPrimaryFocus) {
|
||||
widget.productFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -285,9 +295,16 @@ class _InventoryProductAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!widget.productFocusNode.hasPrimaryFocus) {
|
||||
widget.productFocusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
@@ -332,6 +349,7 @@ class _InventoryProductAutocompleteFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -395,6 +395,10 @@ class _InventoryWarehouseSelectFieldState
|
||||
enabled: widget.enabled,
|
||||
readOnly: !widget.enabled,
|
||||
placeholder: placeholder,
|
||||
onPressedOutside: (event) {
|
||||
// 포커스를 유지하지 않으면 드롭다운이 즉시 닫히므로 재요청한다.
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
if (_isSearching)
|
||||
const Padding(
|
||||
@@ -410,18 +414,34 @@ class _InventoryWarehouseSelectFieldState
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 260),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
minWidth: 260,
|
||||
),
|
||||
child: ShadCard(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (_) {
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
||||
@@ -433,7 +453,9 @@ class _InventoryWarehouseSelectFieldState
|
||||
itemBuilder: (context, index) {
|
||||
final option = options.elementAt(index);
|
||||
final isAll = option.id == -1;
|
||||
final label = isAll ? widget.allLabel : _displayLabel(option);
|
||||
final label = isAll
|
||||
? widget.allLabel
|
||||
: _displayLabel(option);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(option),
|
||||
child: Padding(
|
||||
@@ -448,6 +470,7 @@ class _InventoryWarehouseSelectFieldState
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -104,6 +104,7 @@ class ProductController extends ChangeNotifier {
|
||||
_isLoadingLookups = true;
|
||||
notifyListeners();
|
||||
try {
|
||||
debugPrint('[ProductController] 드롭다운 데이터 조회 시작');
|
||||
final vendors = await fetchAllPaginatedItems<Vendor>(
|
||||
request: (page, pageSize) =>
|
||||
_vendorRepository.list(page: page, pageSize: pageSize),
|
||||
@@ -114,9 +115,16 @@ class ProductController extends ChangeNotifier {
|
||||
);
|
||||
_vendorOptions = vendors;
|
||||
_uomOptions = uoms;
|
||||
debugPrint(
|
||||
'[ProductController] 드롭다운 데이터 조회 완료 '
|
||||
'(vendors=${vendors.length}, uoms=${uoms.length})',
|
||||
);
|
||||
} catch (error) {
|
||||
final failure = Failure.from(error);
|
||||
_errorMessage = failure.describe();
|
||||
debugPrint(
|
||||
'[ProductController] 드롭다운 데이터 조회 실패: ${_errorMessage ?? '알 수 없는 오류'}',
|
||||
);
|
||||
} finally {
|
||||
_isLoadingLookups = false;
|
||||
notifyListeners();
|
||||
|
||||
@@ -633,8 +633,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
||||
return ShadButton.ghost(
|
||||
onPressed: isSaving
|
||||
? null
|
||||
: () =>
|
||||
Navigator.of(context, rootNavigator: true).pop(false),
|
||||
: () => Navigator.of(context, rootNavigator: true).pop(false),
|
||||
child: const Text('취소'),
|
||||
);
|
||||
},
|
||||
@@ -739,6 +738,9 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
||||
enabled: !isSaving,
|
||||
placeholder: const Text('제조사를 선택하세요'),
|
||||
onSelected: (id) {
|
||||
debugPrint(
|
||||
'[ProductForm] 제조사 선택 -> id=$id',
|
||||
);
|
||||
vendorNotifier.value = id;
|
||||
vendorError.value = null;
|
||||
},
|
||||
@@ -780,6 +782,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
||||
enabled: !isSaving,
|
||||
placeholder: const Text('단위를 선택하세요'),
|
||||
onSelected: (id) {
|
||||
debugPrint('[ProductForm] 단위 선택 -> id=$id');
|
||||
uomNotifier.value = id;
|
||||
uomError.value = null;
|
||||
},
|
||||
@@ -867,8 +870,7 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
|
||||
title: '제품 삭제',
|
||||
description: '"${product.productName}" 제품을 삭제하시겠습니까?',
|
||||
primaryAction: ShadButton.destructive(
|
||||
onPressed: () =>
|
||||
Navigator.of(context, rootNavigator: true).pop(true),
|
||||
onPressed: () => Navigator.of(context, rootNavigator: true).pop(true),
|
||||
child: const Text('삭제'),
|
||||
),
|
||||
secondaryAction: ShadButton.ghost(
|
||||
|
||||
@@ -52,6 +52,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(_handleTextChanged);
|
||||
_focusNode.addListener(_handleFocusChange);
|
||||
_initializeOptions();
|
||||
}
|
||||
|
||||
@@ -75,7 +76,9 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
void dispose() {
|
||||
_controller.removeListener(_handleTextChanged);
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
_focusNode
|
||||
..removeListener(_handleFocusChange)
|
||||
..dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -96,6 +99,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
);
|
||||
_isApplyingText = false;
|
||||
}
|
||||
_log('초기 옵션 준비 완료 (base=${_baseOptions.length})');
|
||||
}
|
||||
|
||||
void _resetSuggestions() {
|
||||
@@ -104,6 +108,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
..clear()
|
||||
..addAll(_baseOptions);
|
||||
});
|
||||
_log('입력 초기화 -> 제안 목록 리셋 (count=${_suggestions.length})');
|
||||
}
|
||||
|
||||
void _handleTextChanged() {
|
||||
@@ -127,6 +132,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
void _scheduleSearch(String keyword) {
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
||||
_log('검색 예약: "$keyword"');
|
||||
}
|
||||
|
||||
Future<void> _search(String keyword) async {
|
||||
@@ -143,6 +149,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
_log('단위 검색 시작: "$keyword"');
|
||||
final result = await repository.list(
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
@@ -159,6 +166,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
..addAll(items);
|
||||
_isSearching = false;
|
||||
});
|
||||
_log('단위 검색 완료: ${items.length}건');
|
||||
} catch (error) {
|
||||
if (!mounted || request != _requestId) {
|
||||
return;
|
||||
@@ -169,6 +177,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
_suggestions.clear();
|
||||
_isSearching = false;
|
||||
});
|
||||
_log('단위 검색 실패: ${_error ?? '알 수 없는 오류'}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +217,11 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
}
|
||||
_applyControllerText(uom?.uomName ?? '');
|
||||
});
|
||||
if (uom == null) {
|
||||
_log('선택 해제됨');
|
||||
} else {
|
||||
_log('항목 선택: id=${uom.id}, label=${uom.uomName}');
|
||||
}
|
||||
if (notify) {
|
||||
widget.onSelected(uom?.id);
|
||||
}
|
||||
@@ -249,6 +263,13 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
enabled: widget.enabled,
|
||||
readOnly: !widget.enabled,
|
||||
placeholder: placeholder,
|
||||
onPressedOutside: (event) {
|
||||
_log(
|
||||
'입력 외부 포인터 감지 -> 포커스 유지 시도 '
|
||||
'pos=${event.localPosition} kind=${event.kind}',
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
if (_isSearching)
|
||||
const Padding(
|
||||
@@ -264,18 +285,45 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
_log(
|
||||
'오버레이 포인터 다운 (빈 목록) '
|
||||
'pos=${event.localPosition} kind=${event.kind}',
|
||||
);
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_log('포커스 복구 시도 (빈 목록)');
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 220),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
minWidth: 220,
|
||||
),
|
||||
child: ShadCard(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
_log('드롭다운 리스트 표시: ${options.length}건');
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
_log(
|
||||
'오버레이 포인터 다운 pos=${event.localPosition} '
|
||||
'kind=${event.kind}',
|
||||
);
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_log('포커스 복구 시도');
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 220),
|
||||
@@ -286,8 +334,16 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
itemCount: options.length,
|
||||
itemBuilder: (context, index) {
|
||||
final uom = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(uom),
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTapDown: (_) =>
|
||||
_log('항목 탭다운: id=${uom.id} index=$index'),
|
||||
onTapCancel: () =>
|
||||
_log('항목 탭 취소: id=${uom.id} index=$index'),
|
||||
onTap: () {
|
||||
_log('항목 탭 -> onSelected 호출: id=${uom.id}');
|
||||
onSelected(uom);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
@@ -300,6 +356,7 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -310,5 +367,18 @@ class _UomAutocompleteFieldState extends State<UomAutocompleteField> {
|
||||
_controller.text = value;
|
||||
_controller.selection = TextSelection.collapsed(offset: value.length);
|
||||
_isApplyingText = false;
|
||||
_log('입력 필드 텍스트 적용: "$value"');
|
||||
}
|
||||
|
||||
void _handleFocusChange() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_log('포커스 획득 -> 드롭다운 진입');
|
||||
} else {
|
||||
_log('포커스 해제 -> 드롭다운 종료');
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
debugPrint('[UomAutocomplete] $message');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(_handleTextChanged);
|
||||
_focusNode.addListener(_handleFocusChange);
|
||||
_initializeOptions();
|
||||
}
|
||||
|
||||
@@ -80,7 +81,9 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
void dispose() {
|
||||
_controller.removeListener(_handleTextChanged);
|
||||
_controller.dispose();
|
||||
_focusNode.dispose();
|
||||
_focusNode
|
||||
..removeListener(_handleFocusChange)
|
||||
..dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -104,6 +107,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
);
|
||||
_isApplyingText = false;
|
||||
}
|
||||
_log('초기 옵션 준비 완료 (base=${_baseOptions.length})');
|
||||
}
|
||||
|
||||
void _resetSuggestions() {
|
||||
@@ -112,6 +116,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
..clear()
|
||||
..addAll(_baseOptions);
|
||||
});
|
||||
_log('입력 초기화 -> 제안 목록 리셋 (count=${_suggestions.length})');
|
||||
}
|
||||
|
||||
void _handleTextChanged() {
|
||||
@@ -135,6 +140,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
void _scheduleSearch(String keyword) {
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(_debounceDuration, () => _search(keyword));
|
||||
_log('검색 예약: "$keyword"');
|
||||
}
|
||||
|
||||
Future<void> _search(String keyword) async {
|
||||
@@ -151,6 +157,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
_error = null;
|
||||
});
|
||||
try {
|
||||
_log('공급업체 검색 시작: "$keyword"');
|
||||
final result = await repository.list(
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
@@ -167,6 +174,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
..addAll(items);
|
||||
_isSearching = false;
|
||||
});
|
||||
_log('공급업체 검색 완료: ${items.length}건');
|
||||
} catch (error) {
|
||||
if (!mounted || request != _requestId) {
|
||||
return;
|
||||
@@ -177,6 +185,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
_suggestions.clear();
|
||||
_isSearching = false;
|
||||
});
|
||||
_log('공급업체 검색 실패: ${_error ?? '알 수 없는 오류'}');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +229,11 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
_applyControllerText(_displayLabel(vendor));
|
||||
}
|
||||
});
|
||||
if (vendor == null) {
|
||||
_log('선택 해제됨');
|
||||
} else {
|
||||
_log('항목 선택: id=${vendor.id}, label=${_displayLabel(vendor)}');
|
||||
}
|
||||
if (notify) {
|
||||
widget.onSelected(vendor?.id);
|
||||
}
|
||||
@@ -266,6 +280,13 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
enabled: widget.enabled,
|
||||
readOnly: !widget.enabled,
|
||||
placeholder: placeholder,
|
||||
onPressedOutside: (event) {
|
||||
_log(
|
||||
'입력 외부 포인터 감지 -> 포커스 유지 시도 '
|
||||
'pos=${event.localPosition} kind=${event.kind}',
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
},
|
||||
),
|
||||
if (_isSearching)
|
||||
const Padding(
|
||||
@@ -281,18 +302,45 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
},
|
||||
optionsViewBuilder: (context, onSelected, options) {
|
||||
if (options.isEmpty) {
|
||||
return Align(
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
_log(
|
||||
'오버레이 포인터 다운 (빈 목록) '
|
||||
'pos=${event.localPosition} kind=${event.kind}',
|
||||
);
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_log('포커스 복구 시도 (빈 목록)');
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200, minWidth: 260),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 200,
|
||||
minWidth: 260,
|
||||
),
|
||||
child: ShadCard(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: const Center(child: Text('검색 결과가 없습니다.')),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
_log('드롭다운 리스트 표시: ${options.length}건');
|
||||
return Listener(
|
||||
onPointerDown: (event) {
|
||||
_log(
|
||||
'오버레이 포인터 다운 pos=${event.localPosition} '
|
||||
'kind=${event.kind}',
|
||||
);
|
||||
if (!_focusNode.hasPrimaryFocus) {
|
||||
_log('포커스 복구 시도');
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 240, minWidth: 260),
|
||||
@@ -303,8 +351,16 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
itemCount: options.length,
|
||||
itemBuilder: (context, index) {
|
||||
final vendor = options.elementAt(index);
|
||||
return InkWell(
|
||||
onTap: () => onSelected(vendor),
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTapDown: (_) =>
|
||||
_log('항목 탭다운: id=${vendor.id} index=$index'),
|
||||
onTapCancel: () =>
|
||||
_log('항목 탭 취소: id=${vendor.id} index=$index'),
|
||||
onTap: () {
|
||||
_log('항목 탭 -> onSelected 호출: id=${vendor.id}');
|
||||
onSelected(vendor);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
@@ -317,6 +373,7 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -327,5 +384,18 @@ class _VendorAutocompleteFieldState extends State<VendorAutocompleteField> {
|
||||
_controller.text = value;
|
||||
_controller.selection = TextSelection.collapsed(offset: value.length);
|
||||
_isApplyingText = false;
|
||||
_log('입력 필드 텍스트 적용: "$value"');
|
||||
}
|
||||
|
||||
void _handleFocusChange() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_log('포커스 획득 -> 드롭다운 진입');
|
||||
} else {
|
||||
_log('포커스 해제 -> 드롭다운 종료');
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
debugPrint('[VendorAutocomplete] $message');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user