[![npm version](https://badge.fury.io/js/ng-select2-component.svg)](https://badge.fury.io/js/ng-select2-component) [![Downloads](https://img.shields.io/npm/dm/ng-select2-component.svg)](https://www.npmjs.com/package/ng-select2-component) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Harvest-Dev/ng-select2/master/LICENSE.md)

# Select2

This Angular CLI module it's a fork of [select2-component](https://github.com/plantain-00/select2-component) without Vue & React. For Vue or React, please use the original component.

## Installation

```
npm i ng-select2-component --save
```

## Requirements

- peerDependencies:
    - `angular` `^22.0.0`
    - `angular/cdk` `^22.0.0`

### Notes

| Version   | For **Angular**  | Notes                                                       |
| --------- | ---------------- | ----------------------------------------------------------- |
| `19.0.0`  | 22               | Zoneless                                                    |
| `18.0.0`  | 22               | Ivy / Stand-alone, native scroll (no `ngx-infinite-scroll`) |
| `17.3.2`  | 18.1, 19, 20, 21 | Ivy / Stand-alone                                           |
| `17.1.0`  | 19               | Ivy / Stand-alone                                           |
| `16.0.0`  | 19               | Ivy / Module                                                |
| `15.4.0`  | 18               | Ivy                                                         |
| `14.0.1`  | 17               | Ivy                                                         |
| `13.0.12` | 16.1             | Ivy                                                         |
| `12.1.0`  | 16               | Ivy                                                         |
| `11.1.0`  | 15               | Ivy                                                         |
| `10.0.0`  | 14               | Ivy                                                         |
| `9.0.0`   | 13               | Ivy                                                         |
| `8.1.0`   | 10, 11 and 12    | View Engine                                                 |
| `7.3.1`   | 7, 8 and 9       | View Engine                                                 |

## Demo

[See a demo and code generator](https://harvest-dev.github.io/ng-select2/dist/ng-select2/browser).

## Features

- **Selection**
    - Single and multiple selection
    - Limit the number of selections (multiple)
    - Hide already selected items from the dropdown (multiple)
    - Select all / Remove all button (multiple)
    - Checkbox next to each option
    - Drag & drop reordering of selected items (multiple)
    - Resettable with configurable reset value
    - Auto-create items not present in the list
- **Data representation**
    - Options and option groups
    - Declarative with `<ng-option>` and `<ng-group>` tags
    - Infinite scroll
    - Maximum results limit
    - Grid layout (fixed column count or auto cell size)
- **Selection representation**
    - Selection override: replace the selection area with custom text or a function
    - Selection no-wrap: keep selected tags on a single line
- **Search**
    - Local filtering with configurable minimum character threshold
    - External / async search (`customSearchEnabled`)
    - Highlight matched text in options (ignore the diacritics for: Latin, Japanese, Cyrillic, Greek, Arabic & Hebrew)
- **Keyboard**
    - Standard keyboard navigation
    - Native HTML `<select>` navigation mode (`nativeKeyboard`)
- **Templates**
    - Custom rendering for options, groups and the selection area
    - Per-item template ids (`templateId`, `templateSelectionId`)
- **Style**
    - Four modes: default, material, borderless, no-style
    - Fully themeable via CSS custom properties
- **Dropdown**
    - Dropdown position: below, above, or auto
    - Angular CDK Overlay mode
- **Forms**
    - `ngModel` and `FormControl` binding
    - `required`, `disabled`, `readonly`, `tabIndex`, `placeholder`
- **Accessibility**
    - WAI-ARIA roles, live regions, `ariaLabelledby`, `ariaDescribedby`, hint slot
- **Angular**
    - signals-based API (`input()` / `output()`)
    - standalone component
    - zoneless compatible (no `zone.js` dependency)
- Web Component compatible naming (`<ng-select2>`)

## Usage

### example

#### TS

```ts
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { Select2, Select2Hint, Select2Label } from 'ng-select2-component';

@Component({
    selector: 'my-component',
    templateUrl: './my-component.component.html',
    styleUrls: ['./my-component.component.scss'],
    imports: [FormsModule, ReactiveFormsModule, Select2, Select2Hint, Select2Label],
})
class MyComponent {}
```

#### HTML template

```html
<select2 [data]="data" [value]="value" (update)="update($event)"></select2>
```

or

```html
<ng-select2 [data]="data" [value]="value" (update)="update($event)" />
```

or

```html
<ng-select2 [data]="data" [value]="value" (update)="update($event)">
    <ng-select2-label>label</ng-select2-label>
    <ng-select2-hint>hint</ng-select2-hint>
</ng-select2>
```

or with declarative `<ng-option>` / `<ng-group>` (no `[data]` binding needed):

```html
<ng-select2 [value]="value" (update)="update($event)">
    <ng-group label="Fruits">
        <ng-option value="apple">Apple</ng-option>
        <ng-option value="banana">Banana</ng-option>
    </ng-group>
    <ng-option value="other">Other</ng-option>
</ng-select2>
```

### properties and events of the component

| name                                                                      | type                                                                                                 | default              | description                                                                                                             | required                        |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `data` (required)                                                         | [`Select2Data`](#select2-data-structure)                                                             |                      | the data of the select2                                                                                                 |                                 |
| `value`                                                                   | [`Select2Value`](#select2-data-structure)                                                            |                      | initial value                                                                                                           |                                 |
| `minCharForSearch`                                                        | `number`                                                                                             | `0`                  | start the search when the number of characters is reached (`0` = unlimited)                                             |                                 |
| `minCountForSearch`                                                       | `number`                                                                                             | `6`                  | hide search box if `options.length < minCountForSearch`                                                                 |                                 |
| `displaySearchStatus`                                                     | `'default'`<br>`'hidden'`<br>`'always'`                                                              | `'default'`          | display the search box (`default` : is based on `minCountForSearch`)                                                    |                                 |
| `placeholder`                                                             | `string`                                                                                             |                      | the placeholder string if nothing selected                                                                              |                                 |
| `noResultMessage`                                                         | `string`                                                                                             |                      | the message string if no results when using the search field                                                            |                                 |
| `customSearchEnabled`                                                     | `boolean`                                                                                            | `false`              | will trigger `search` event, and disable inside filter                                                                  |                                 |
| `multiple`                                                                | `boolean`                                                                                            | `false`              | select multiple options                                                                                                 |                                 |
| `multipleDrag`                                                            | `boolean`                                                                                            | `false`              | drag'n drop list of items in selection                                                                                  | with `multiple`                 |
| `resettable`                                                              | `boolean`                                                                                            | `false`              | add a button to reset value                                                                                             |                                 |
| `resetSelectedValue`                                                      | `any`                                                                                                | `undefined`          | selected option when × is clicked                                                                                       |                                 |
| `autoCreate`                                                              | `boolean`                                                                                            | `false`              | gives the possibility to add elements not present in the list.                                                          |                                 |
| `limitSelection`                                                          | `number`                                                                                             | `0`                  | to limit multiple selection (`0` = unlimited)                                                                           |                                 |
| `hideSelectedItems`                                                       | `boolean`                                                                                            | `false`              | remove selected values                                                                                                  | with `multiple`                 |
| `resultMaxHeight`                                                         | `string`                                                                                             | `'200px'`            | change the height size of results                                                                                       |                                 |
| `maxResults`                                                              | `number`                                                                                             | `0`                  | maximum results limit (`0` = unlimited)                                                                                 |                                 |
| `maxResultsMessage`                                                       | `string`                                                                                             | `'Too much result…'` | message when maximum result                                                                                             |                                 |
| `grid`                                                                    | `number` or `string`                                                                                 |                      | option by line in grid layout (empty or `0` = no grid layout)<br>`number`: item by line<br>`string`: minimal item width |                                 |
| `listPosition`                                                            | `'below'`<br>`'above'`<br>`'auto'` ¹                                                                 | `'below'`            | the position for the dropdown list                                                                                      | ¹ `'auto'`: only with `overlay` |
| `infiniteScroll`                                                          | `boolean`                                                                                            | `false`              | enable infinite scroll on the dropdown list                                                                             |                                 |
| `infiniteScrollDistance`                                                  | `number`                                                                                             | `1.5`                | scroll threshold as a fraction of the list height (e.g. `1.5` = bottom 15%)                                             |                                 |
| `infiniteScrollThrottle`                                                  | `number`                                                                                             | `150`                | debounce time in ms between scroll event emissions                                                                      |                                 |
| `overlay`                                                                 | `boolean`                                                                                            | `false`              | active an overlay mode for dropdown list (with angular cdk). (See [Overlay](#overlay))                                  |                                 |
| `styleMode`                                                               | `'default'`<br>`'material'`<br>`'noStyle'`<br>`'borderless'`                                         | `'default'`          | change style for material style or remove border and background color                                                   |                                 |
| `templates`                                                               | [`Select2Template`](#select2-data-structure)<br>(see ”possible object” in [Templating](#templating)) |                      | use templates for formatting content (see [Templating](#templating))                                                    |                                 |
| `templateSelection`                                                       | `TemplateRef`                                                                                        |                      | use templates for formatting content (see [Templating](#templating))                                                    |                                 |
| `noLabelTemplate`                                                         | `boolean`                                                                                            | `false`              | do not use the template in the selection, stay in text mode                                                             |                                 |
| `selectionOverride`                                                       | [`Select2SelectionOverride`](#select2-data-structure)                                                |                      | Replace selection by a text<br>`string`: `%size%` = total selected options<br>`function`: juste show the string         |                                 |
| `selectionNoWrap`                                                         | `boolean`                                                                                            | `false`              | force selection on one line                                                                                             |                                 |
| `showSelectAll`                                                           | `boolean`                                                                                            | `false`              | Add an option to select all options                                                                                     | with `multiple`                 |
| `showOptionCheckbox`                                                      | `boolean`                                                                                            | `false`              | Show a checkbox next to each option in the dropdown                                                                     |                                 |
| `selectAllText`                                                           | `string`                                                                                             | `'Select all'`       | Text when all options as not selected                                                                                   | with `multiple`                 |
| `removeAllText`                                                           | `string`                                                                                             | `'Remove all'`       | text when all options as selected                                                                                       | with `multiple`                 |
| `editPattern`                                                             | `(str: string) => string`                                                                            |                      | use it for change the pattern of the filter search                                                                      |                                 |
| `nativeKeyboard`                                                          | `boolean`                                                                                            | `false`              | use the keyboard navigation like native HTML select component                                                           | not with `multiple`             |
| `highlightText`                                                           | `boolean`                                                                                            | `false`              | use to highlight search text in dropdown options (with template see code generator)                                     |                                 |
| `ngModel`<br>`id`<br>`required`<br>`disabled`<br>`readonly`<br>`tabIndex` |                                                                                                      |                      | just like a `select` control                                                                                            |                                 |
| `(update)`                                                                | `(event: `[`Select2UpdateEvent`](#select2-data-structure)`) => void`                                 |                      | triggered when user select an option                                                                                    |                                 |
| `(open)`                                                                  | `(event: Select2) => void`                                                                           |                      | triggered when user open the options                                                                                    |                                 |
| `(close)`                                                                 | `(event: Select2) => void`                                                                           |                      | triggered when user close the options                                                                                   |                                 |
| `(focus)`                                                                 | `(event: Select2) => void`                                                                           |                      | triggered when user enters the component                                                                                |                                 |
| `(blur)`                                                                  | `(event: Select2) => void`                                                                           |                      | triggered when user leaves the component                                                                                |                                 |
| `(search)`                                                                | `(event: `[`Select2SearchEvent`](#select2-data-structure)`) => void`                                 |                      | triggered when search text changed                                                                                      | with `customSearchEnabled`      |
| `(scroll)`                                                                | `(event: `[`Select2ScrollEvent`](#select2-data-structure)`) => void`                                 |                      | triggered when infinite scroll reaches the `up` or `down` threshold                                                     |                                 |
| `(removeOption)`                                                          | `(event: `[`Select2RemoveEvent`](#select2-data-structure)`) => void`                                 |                      | triggered when an option is removed from the list of selected options options list                                      | with `multiple`                 |
| `(autoCreateItem)`                                                        | `(event: `[`Select2AutoCreateEvent`](#select2-data-structure)`) => void`                             |                      | triggered when a new item has been added                                                                                | with `autoCreate`               |

### select2 data structure

```ts
export interface Select2Group {
    /** label of group */
    label: string;
    /** options list */
    options: Select2Option[];
    /** add classes  */
    classes?: string;
    /** template id dropdown & selection if no templateSelectionId */
    templateId?: string;
    /** template data  */
    data?: any;
    /** force left to right or right to left  */
    dir?: 'ltr' | 'rtl';
}

export interface Select2Option {
    /** value  */
    value: Select2Value;
    /** label of option */
    label: string;
    /** no selectable is disabled */
    disabled?: boolean;
    /** for identification */
    id?: string;
    /** add classes  */
    classes?: string;
    /** template id dropdown & selection if no templateSelectionId */
    templateId?: string;
    /** template id for selection */
    templateSelectionId?: string;
    /** template data  */
    data?: any;
    /** hide this option */
    hide?: boolean;
    /** force left to right or right to left  */
    dir?: 'ltr' | 'rtl';
}

export type Select2Value = string | number | boolean | object | null | undefined;

export type Select2UpdateValue = Select2Value | Select2Value[] | undefined | null;

export type Select2Data = (Select2Group | Select2Option)[];

export interface Select2UpdateEvent<U extends Select2UpdateValue = Select2Value> {
    /** component */
    readonly component: Select2;
    /** current selected value */
    readonly value: U | null;
    /** selected option */
    readonly options: Select2Option[] | null;
}

export interface Select2AutoCreateEvent<U extends Select2UpdateValue = Select2Value> {
    /** component */
    readonly component: Select2;
    /** current selected value */
    readonly value: U;
    /** selected option */
    readonly options: Select2Option[] | null;
}

export interface Select2SearchEvent<U extends Select2UpdateValue = Select2Value> {
    /** component */
    readonly component: Select2;
    /** current selected value */
    readonly value: U | null;
    /** search text */
    readonly search: string;
    /** current data source */
    readonly data: Select2Data;
    /** method to call to update the data */
    readonly filteredData: (data: Select2Data) => void;
}

export interface Select2RemoveEvent<U extends Select2UpdateValue = Select2Value> {
    /** component */
    readonly component: Select2;
    /** current selected value */
    readonly value: U;
    /** remove */
    readonly removedOption: Select2Option;
}

export interface Select2ScrollEvent {
    /** component */
    readonly component: Select2;
    /** scroll way */
    readonly way: 'up' | 'down';
    /** search text */
    readonly search: string;
    /** current data */
    readonly data: Select2Data;
}

export type Select2SelectionOverride = string | ((params: { size: number; options: Select2Option[] | null }) => string);

export type Select2Template = TemplateRef<any> | { [key: string]: TemplateRef<any> } | undefined;
```

### Templating

#### Unique template

```html
<ng-select2 [data]="data" [templates]="template">
    <ng-template #template let-data="data"><strong>{{data?.color}}</strong>: {{data?.name}}</ng-template>
</ng-select2>
```

```ts
const data: Select2Data = [
    {
        value: 'heliotrope',
        label: 'Heliotrope',
        data: { color: 'white', name: 'Heliotrope' },
    },
    {
        value: 'hibiscus',
        label: 'Hibiscus',
        data: { color: 'red', name: 'Hibiscus' },
    },
];
```

#### Template group & option

```html
<ng-select2 [data]="data" [templates]="{option : option, group: group}">
    <ng-template #option let-data="data">{{data?.name}}</ng-template>
    <ng-template #group let-label="label">Group: {{label}}</ng-template>
</ng-select2>
```

No difference in data structure.
The template is defined by its type, option or group, automatically.

#### Template by templateId

```html
<ng-elect2 [data]="data" [templates]="{template1 : template1, template2: template2}">
    <ng-template #template1 let-data="data">{{data?.name}}</ng-template>
    <ng-template #template2 let-label="label" let-data="data">{{label}} : {{data?.color}}</ng-template>
</ng-elect2>
```

```ts
const data: Select2Data = [
    {
        value: 'heliotrope',
        label: 'Heliotrope',
        data: { color: 'white', name: 'Heliotrope' },
        templateId: 'template1',
    },
    {
        value: 'hibiscus',
        label: 'Hibiscus',
        data: { color: 'red', name: 'Hibiscus' },
        templateId: 'template2',
    },
];
```

#### Template for change the selection

```html
<ng-select2 [data]="data" [templateSelection]="templateSelection">
    <ng-template #templateSelection let-data="data"><strong>{{ data?.color }}</strong> ({{ data?.name }})</ng-template>
</ng-select2>
```

#### Possible object

- `TemplateRef`
- `{template: TemplateRef}`
- `{option?: TemplateRef, group?: TemplateRef}`
- `{templateId1: TemplateRef, ...}`

In addition to the rendering templates of options and groups, in addition to going through the `templateSelection` attribute, it is possible to define that of the selection :

- `{templateSelection: TemplateRef}`
- `{optionSelection: TemplateRef}`

#### Priority order

For group or option:

- `'id'` (from item data `templateId`)
- `'group'` or `'option'`
- `'template'`
- `TemplateRef` (from html attribute `templates`)
- Default render

For the selection:

- `'id'` (from item data `templateSelectionId`)
- `'optionSelection'`
- `'templateSelection'`
- `TemplateRef` (from html attribute `templateSelection`)
- `'id'` (from item data `templateId`)
- `'option'`
- `'template'`
- `TemplateRef` (from html attribute `templates`)
- Default render

### Overlay

If the overlay mode is used / activated, add to the project root in CSS (with `ViewEncapsulation.None`)

```css
@import '~@angular/cdk/overlay-prebuilt.css';
```

## CSS variables

It's possible to change different colors (and more) with CSS variables without having to modify them with `::ng-deep` or other CSS rules :

```css
:root {
    /* size */
    --select2-single-height: 28px;
    --select2-multiple-height: 28px;

    /* label */
    --select2-label-text-color: #000;
    --select2-required-color: red;

    /* selection */
    --select2-selection-border-radius: 4px;
    --select2-selection-background: #fff;
    --select2-selection-disabled-background: #eee;
    --select2-selection-border-color: #aaa;
    --select2-selection-focus-border-color: #000;
    --select2-selection-text-color: #111;
    --select2-selection-line-height: 28px;
    --select2-selection-padding: 0 0 0 8px;

    /* selection (multiple) */
    --select2-selection-multiple-gap: 2px 5px;
    --select2-selection-multiple-padding: 2px 5px;

    /* selection: choice item (multiple) */
    --select2-selection-choice-background: #e4e4e4;
    --select2-selection-choice-text-color: #000;
    --select2-selection-choice-border-color: #aaa;
    --select2-selection-choice-close-color: #999;
    --select2-selection-choice-hover-close-color: #333;

    /* placeholder */
    --select2-placeholder-color: #999;
    --select2-placeholder-overflow: ellipsis;

    /* no result message */
    --select2-no-result-color: #888;
    --select2-no-result-font-style: italic;

    /* no result message */
    --select2-too-much-result-color: #888;
    --select2-too-much-result-style: italic;

    /* reset */
    --select2-reset-color: #999;

    /* arrow */
    --select2-arrow-color: #888;

    /* dropdown panel */
    --select2-dropdown-background: #fff;
    --select2-dropdown-border-color: #aaa;
    --select2-dropdown-above-border-bottom: none;
    --select2-dropdown-above-border-bottom-left-radius: 0;
    --select2-dropdown-above-border-bottom-right-radius: 0;
    --select2-dropdown-below-border-top: none;
    --select2-dropdown-below-border-top-left-radius: 0;
    --select2-dropdown-below-border-top-right-radius: 0;

    /* overlay */
    --select2-overlay-backdrop: transparent;

    /* search field */
    --select2-search-border-color: #aaa;
    --select2-search-background: #fff;
    --select2-search-border-radius: 0px;

    /* dropdown option */
    --select2-option-text-color: #000;
    --select2-option-disabled-text-color: #999;
    --select2-option-disabled-background: transparent;
    --select2-option-selected-text-color: #000;
    --select2-option-selected-background: #ddd;
    --select2-option-highlighted-text-color: #fff;
    --select2-option-highlighted-background: #5897fb;
    --select2-option-group-text-color: gray;
    --select2-option-group-background: transparent;
    --select2-option-padding: 6px;

    /* hint */
    --select2-hint-text-color: #888;

    /* highlight search text */
    --select2-highlight-text-background: transparent;
    --select2-highlight-text-color: inherit;
    --select2-highlight-font-weight: 800;
    --select2-highlighted-highlight-text-background: transparent;
    --select2-highlighted-highlight-text-color: inherit;
    --select2-highlighted-highlight-font-weight: 800;

    /* for Material ------------------------------------------*/
    --select2-material-underline: #ddd;
    --select2-material-underline-active: #5a419e;
    --select2-material-underline-disabled: linear-gradient(
        to right,
        rgba(0, 0, 0, 0.26) 0,
        rgba(0, 0, 0, 0.26) 33%,
        transparent 0
    );
    --select2-material-underline-invalid: red;
    --select2-material-placeholder-color: rgba(0, 0, 0, 0.38);
    --select2-material-selection-background: #ddd;
    --select2-material-option-selected-background: rgba(0, 0, 0, 0.04);
    --select2-material-option-highlighted-text-color: #000;
    --select2-material-option-selected-text-color: #ff5722;
}
```

## Publishing the library

```sh
npm run build:lib
npm run publish:lib
```

## Update Demo

```sh
npm run build:demo
```

## Tests

```sh
# run tests
npm run test
npm run test:vitest
```

```sh
# run tests with coverage
npm run test:vitest:coverage
```

```sh
# run tests with watch
npm run test:vitest:watch
```

## License

Like Angular, this module is released under the permissive MIT license. Your contributions are always welcome.
