UNPKG

ng-select2-component

Version:
556 lines (461 loc) 33.3 kB
[![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.