Lv_obj_remove_state LV_STATE_FOCUSED doesn't trigger LV_EVENT_DEFOCUSED

Description

I have a text area that, when focused, pops up a keypad (button matrix) to allow data entry. Upon LV_EVENT_DEFOCUSED of that text area, the keypad is hidden. The “enter key” of the keypad, when clicked, confirms the new value that was entered by triggering an LV_EVENT_READY event on the text area. In the LV_EVENT_READY handler for the text area, I store the new value and then attempt to defocus the text area by calling lv_obj_remove_state(ta, LV_STATE_FOCUSED). The text area does indeed get visually defocused (cursor disappears), but the LV_EVENT_DEFOCUSED event handler is never called.

What MCU/Processor/Board and compiler are you using?

ESP32-2432S028R (cheap yellow display)

What LVGL version are you using?

9.2.2

What do you want to achieve?

I expect LVGL to call the LV_EVENT_DEFOCUSED event handler when the text area is defocused by calling lv_obj_remove_state(ta, LV_STATE_FOCUSED).

What have you tried so far?

I can manually invoke my defocus handler just after the call to remove the focused state, but I wouldn’t expect to need to since the text area is, in fact, experiencing a defocusing event. Plus, the lb_event_t parameter isn’t accurate for the defocus handler (it is the LV_EVENT_READY event instead).

Code to reproduce

Global variable definitions omitted

void ta_Focused(lv_event_t* e) {
    Serial.println("ta_Focused");
    taSelected = (lv_obj_t*)lv_event_get_user_data(e);
    lv_obj_update_flag(btnmtxKeypad, LV_OBJ_FLAG_HIDDEN, false);
}

void ta_Defocused(lv_event_t* e) {
    Serial.println("ta_Defocused");
    taSelected = nullptr;
    lv_obj_update_flag(btnmtxKeypad, LV_OBJ_FLAG_HIDDEN, true);
}

void btnmtxKeypad_Click(lv_event_t * e)
{
    if (taSelected == nullptr) {
        return;  // Should never happen, but just to be safe
    }

    const char* txt = lv_buttonmatrix_get_button_text(btnmtxKeypad, lv_buttonmatrix_get_selected_button(btnmtxKeypad));

    if(lv_strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) lv_textarea_delete_char(taSelected);
    else if(lv_strcmp(txt, LV_SYMBOL_NEW_LINE) == 0) lv_obj_send_event(taSelected, LV_EVENT_READY, NULL);
    else lv_textarea_add_text(taSelected, txt);
}

void ta_Confirm(lv_event_t* e) {
    Serial.println("ta_Confirm");
    lv_obj_t* ta = (lv_obj_t*)lv_event_get_user_data(e);

    if (ta == ui_taCompressed) {
        Serial.println("Set compressed value");
    } else if (ta == ui_taExtended) {
        Serial.println("Set extended value");
    } else
        Serial.println("???");
    }

    lv_obj_remove_state(ta, LV_STATE_FOCUSED);  // <--- PROBLEM IS HERE
    // ta_Defocused(e);  <-- hack that achieves the outcome I expect
}

void scrWheel_Loaded(lv_event_t* e) {
    btnmtxKeypad = lv_buttonmatrix_create(lv_screen_active());
    lv_obj_update_flag(btnmtxKeypad, LV_OBJ_FLAG_HIDDEN, true);
    lv_obj_set_size(btnmtxKeypad, 200, 240);
    lv_obj_align(btnmtxKeypad, LV_ALIGN_LEFT_MID, 0, 0);
    lv_obj_add_event_cb(btnmtxKeypad, btnmtxKeypad_Click, LV_EVENT_VALUE_CHANGED, NULL);
    lv_obj_remove_flag(btnmtxKeypad, LV_OBJ_FLAG_CLICK_FOCUSABLE);  // Keep the text area focused on button clicks
    lv_buttonmatrix_set_map(btnmtxKeypad, BUTTON_MAP_NUMERIC);

    lv_obj_add_event_cb(ui_taCompressed, ta_Confirm, LV_EVENT_READY, ui_taCompressed);
    lv_obj_add_event_cb(ui_taCompressed, ta_Focused, LV_EVENT_FOCUSED, ui_taCompressed);
    lv_obj_add_event_cb(ui_taCompressed, ta_Defocused, LV_EVENT_DEFOCUSED, ui_taCompressed);

    lv_obj_add_event_cb(ui_taExtended, ta_Confirm, LV_EVENT_READY, ui_taExtended);
    lv_obj_add_event_cb(ui_taExtended, ta_Focused, LV_EVENT_FOCUSED, ui_taExtended);
    lv_obj_add_event_cb(ui_taExtended, ta_Defocused, LV_EVENT_DEFOCUSED, ui_taExtended);
}