# Filter Dropdown Select

Level: molecule.

## Purpose and use

Choose one or several filter values, optionally finding them by text. Use the
single mode for immediate application and multiple mode for a draft with Apply.

## Anatomy, behavior, and accessibility

- Filter Chip anchors a Dropdown in ordinary single mode, or a custom search/
  multiselect overlay. Single mode prepends «Все», which emits null.
- Single selection emits the ID and closes. Multiple mode edits selected IDs in
  a draft; Apply emits the array, including an empty array when clearing an
  existing selection. Closing and reopening restores the applied selection.
- Search filters the supplied options by case-insensitive label substring after
  minSearchLength (default 3). It emits query changes but makes no request.
  Matching text is emphasized; hint/empty messages appear as status text.
- Selected options are grouped first; live multiselect also shows removable tags.
  maxSelected disables unselected options at the limit without disabling removal
  of enabled selected options. Disabled options cannot be toggled.
- Custom option rows support Enter/Space and checkbox/option selection semantics;
  decorative child checkboxes are hidden. Use unique IDs, meaningful labels and
  a search prompt matching the configured minimum.
- Searchable panels keep their outer overflow hidden and delegate vertical
  scrolling to `.wbl-filter-dropdown-select__list` and its `__options` region.
  The list maximum height is `16.75rem`; live multiple selection keeps a 268px
  list height. Scroll containers use `overflow-y: auto` and
  `overscroll-behavior: contain`.
- Lifecycle, open state, and validation state are controlled by the owning
  screen through the nested Filter Chip. `openRequest` asks the owner to set
  `opened`; the semantic Chip action is forwarded as `action` and asks the
  owner to clear, reset, or remove its filter. The wrapper closes its overlay
  after that action but never mutates the owner's applied selection itself.

## Open Questions

Custom overlays have no explicit Escape handler, arrow-key option navigation or
focus restoration. The component does not provide remote-loading/error states;
searchable means local filtering of options currently supplied by the parent.

## Local component contract

This is the active owned contract for `component.filter-dropdown-select`. The API below
is verified against the current local implementation, focused test and story.
The Source commit `0112cba0779a2d2a4b6c7a17243201694344d70a` remains
migration provenance, not the runtime owner.

### Classes and selectors

- `WblFilterDropdownSelectComponent` — `wbl-filter-dropdown-select` (src/app/design-system/patterns/filter-row/filter-dropdown-select.component.ts).

### Inputs

- `WblFilterDropdownSelectComponent.label = input('Фильтр')`
- `WblFilterDropdownSelectComponent.allLabel = input('Все')`
- `WblFilterDropdownSelectComponent.options = input<WblFilterDropdownSelectOption[]>([])`
- `WblFilterDropdownSelectComponent.selectedId = input<string | null>(null)`
- `WblFilterDropdownSelectComponent.selectedIds = input<string[]>([])`
- `WblFilterDropdownSelectComponent.lifecycle = input<WblFilterLifecycle>('pinned')`
- `WblFilterDropdownSelectComponent.opened = input(false)`
- `WblFilterDropdownSelectComponent.invalid = input(false)`
- `WblFilterDropdownSelectComponent.errorText = input('')`
- `WblFilterDropdownSelectComponent.disabled = input(false)`
- `WblFilterDropdownSelectComponent.multiple = input(false)`
- `WblFilterDropdownSelectComponent.maxSelected = input<number | null>(null)`
- `WblFilterDropdownSelectComponent.searchable = input(false)`
- `WblFilterDropdownSelectComponent.searchValue = input('')`
- `WblFilterDropdownSelectComponent.searchPlaceholder = input('Введите первые 3 символа УИН')`
- `WblFilterDropdownSelectComponent.searchAriaLabel = input('Поиск')`
- `WblFilterDropdownSelectComponent.minSearchLength = input(MIN_SEARCH_LENGTH)`
- `WblFilterDropdownSelectComponent.emptyResultsText = input('Ничего не найдено')`
- `WblFilterDropdownSelectComponent.clearLabel = input('Очистить')`
- `WblFilterDropdownSelectComponent.applyLabel = input('Применить')`
- `WblFilterDropdownSelectComponent.showSelectAll = input(false)`
- `WblFilterDropdownSelectComponent.selectAllLabel = input('Выбрать все')`

### Public types and allowed values

```ts
type WblFilterDropdownSelectOption = {
  id: string;
  label: string;
  disabled?: boolean;
};
```

`WblFilterLifecycle` and `WblFilterAction` are the shared Filter Chip types:
`'required' | 'pinned' | 'optional'` and `'clear' | 'reset' | 'remove'`.

### Models

- None.

### Outputs

- `WblFilterDropdownSelectComponent.selectedIdChange = output<string | null>()`
- `WblFilterDropdownSelectComponent.selectedIdsChange = output<string[]>()`
- `WblFilterDropdownSelectComponent.openRequest = output<boolean>()`
- `WblFilterDropdownSelectComponent.searchValueChange = output<string>()`
- `WblFilterDropdownSelectComponent.action = output<WblFilterAction>()`

### Slots and projection markers

- The exact template has no Angular content-projection slot.

### Local evidence

- Code: `src/app/design-system/patterns/filter-row/filter-dropdown-select.component.ts`.
- Focused test: `src/app/design-system/patterns/filter-row/filter-dropdown-select.component.spec.ts`.
- Public export: `export * from './patterns/filter-row/filter-dropdown-select.component';` in `src/app/design-system/index.ts`.
- Storybook: `storybook/stories/filter-row.stories.ts` (`Design System/Patterns/wbl-filter-row`). This is the shared active Storybook story for this family.

## Figma status

This runtime contract does not infer a Figma owner. Assembly evidence, when
verified, is maintained separately through the inventory-linked sidecar.

## Provenance

The archived source-document copy is historical evidence only. The active
resolver is local code/test/export and the exact Storybook story above.

## M-002 extension

M-002 added two optional inputs for the multiple-selection panel:

- `WblFilterDropdownSelectComponent.showSelectAll = input(false)`
- `WblFilterDropdownSelectComponent.selectAllLabel = input('Выбрать все')`

When `multiple=true` and `showSelectAll=true`, the panel displays a native
button with `selectAllLabel`. It copies every enabled option into the draft
selection, respects `maxSelected` when a limit exists, and does not emit the
applied value until the user chooses Apply. The action is disabled when there
are no enabled options or all enabled options are already selected. The default
`false` preserves the existing helper text and behavior.

The current Transport Requests `Тип заявки` filter enables `showSelectAll` for
its three available preliminary request types. The Angular implementation,
focused test, and shared Filter Row Storybook evidence are current.

## M-002 per-tab option catalogs

Transport Requests uses screen-owned request-type option sets: three values for
Preliminary, eleven for New, In progress and Archived, and Commercial only for
Awaiting payment. The Type control remains hidden on Awaiting payment. Selection
state is separate per tab and the screen provides option IDs and labels. Filter
Dropdown Select does not own a global request-type enum.

The Route control uses `multiple=true` and `searchable=true`; Status uses
`multiple=true`; vehicle, assignment, shipment and participant controls use
searchable single selection. Options come from the active tab's static fixtures,
and applied values stay in that tab's screen-owned state. Existing component
inputs express every mode, so the public API is unchanged.

## M-002 scrollbar treatment

The list/options scroll containers extend the scrollbar outside their content
with `margin-inline-end: calc(-1 * var(--space-100))` and matching
`padding-inline-end: var(--space-100)`. They use the same native 4px treatment
as Dropdown: tertiary thumb, transparent track/corner, pill radius,
`scrollbar-width: thin` fallback and `scrollbar-color` tertiary/transparent.
The thumb is visible whenever content overflows and is not hover-only. Searchable
panel overflow remains hidden so the list, rather than the whole panel, scrolls.
No public Filter Dropdown Select API is added.
