import { Adapter } from "../core/base/adapter"; import { GroupModel } from "../models/group-model"; import { OptionModel } from "../models/option-model"; import { GroupView } from "../views/group-view"; import { OptionView } from "../views/option-view"; import { MixedItem, VisibilityStats, } from "../types/core/base/mixed-adapter.type"; import { IEventCallback } from "../types/utils/ievents.type"; import { ImagePosition, LabelHalign, LabelValign, } from "../types/views/view.option.type"; import { Libs } from "../utils/libs"; import { LifecycleState } from "../types/core/base/lifecycle.type"; import { Lifecycle } from "../core/base/lifecycle"; import { SelectiveOptions } from "../types/utils/selective.type"; /** * Mixed (heterogeneous) adapter for rendering and interacting with a list that contains * both {@link GroupModel} and {@link OptionModel} items. * * ### Responsibility * - Flatten hierarchical data (groups → options) into `flatOptions` for: * - keyboard navigation (highlight + next/prev visible), * - visibility aggregation (visibleCount/totalCount), * - selection helpers (getSelectedItem(s), checkAll). * - Create the correct view implementation per item: * - {@link GroupView} for groups, * - {@link OptionView} for options (including options inside a group container). * - Bind DOM events and model hooks to keep View ↔ Model synchronized: * - click → selection changes, * - mouseenter → highlight changes, * - model visibility change → group visibility recalculation + debounced stats event. * * ### Lifecycle (Strict FSM, idempotency) * - `init()` registers a debounced visibility aggregation job and then calls `mount()`. * - View binding in `handleGroupView` / `handleOptionView` is guarded by `model.isInit` * to avoid double-wiring listeners (idempotent binding). * - `destroy()` clears scheduler jobs and destroys groups (cascades into their options/views), * then transitions to `DESTROYED`. Subsequent destroy calls are **no-ops**. * * ### Event / Hook flow (external vs internal) * - **External selection**: user click triggers `changingProp("select")` then changes `OptionModel.selected`, * and eventually emits adapter-level `changeProp("selected")` via `optionModel.onSelected`. * - **Internal selection**: `OptionModel.selectedNonTrigger` / `onInternalSelected` updates internal state * (e.g., cache `selectedItemSingle`) and emits adapter-level `changeProp("selected_internal")` * without implying user intent. * * ### Visibility / Highlight / Navigation * - Visibility is tracked per option model (`OptionModel.visible`), and groups can update their own * derived visibility via `GroupModel.updateVisibility()`. * - Highlight is tracked by flat index (`currentHighlightIndex`) and by model flag * (`OptionModel.highlighted`), enabling view-level styling / a11y hooks. * * ### DOM side effects / a11y notes * - Adds DOM listeners (`click`, `mouseenter`) on first bind only. * - Uses `Element.scrollIntoView()` when highlighting with scrolling enabled. * - When options are virtualized, falls back to `recyclerView.ensureRendered(i, { scrollIntoView: true })` * before attempting to scroll. * * @extends {Adapter} * @see {@link GroupModel} * @see {@link OptionModel} * @see {@link GroupView} * @see {@link OptionView} */ export class MixedAdapter extends Adapter { /** Whether the adapter operates in multi-selection mode. */ public isMultiple = false; /** * Parsed configuration (bound from the `