@anglr/select 16.0.3 → 16.0.4-beta.20260511085948
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/angular-developer/SKILL.md +130 -0
- package/.claude/skills/angular-developer/references/angular-animations.md +160 -0
- package/.claude/skills/angular-developer/references/angular-aria.md +410 -0
- package/.claude/skills/angular-developer/references/cli.md +86 -0
- package/.claude/skills/angular-developer/references/component-harnesses.md +59 -0
- package/.claude/skills/angular-developer/references/component-styling.md +91 -0
- package/.claude/skills/angular-developer/references/components.md +117 -0
- package/.claude/skills/angular-developer/references/creating-services.md +97 -0
- package/.claude/skills/angular-developer/references/data-resolvers.md +69 -0
- package/.claude/skills/angular-developer/references/define-routes.md +67 -0
- package/.claude/skills/angular-developer/references/defining-providers.md +72 -0
- package/.claude/skills/angular-developer/references/di-fundamentals.md +120 -0
- package/.claude/skills/angular-developer/references/e2e-testing.md +66 -0
- package/.claude/skills/angular-developer/references/effects.md +83 -0
- package/.claude/skills/angular-developer/references/hierarchical-injectors.md +43 -0
- package/.claude/skills/angular-developer/references/host-elements.md +80 -0
- package/.claude/skills/angular-developer/references/injection-context.md +63 -0
- package/.claude/skills/angular-developer/references/inputs.md +101 -0
- package/.claude/skills/angular-developer/references/linked-signal.md +59 -0
- package/.claude/skills/angular-developer/references/loading-strategies.md +61 -0
- package/.claude/skills/angular-developer/references/mcp.md +106 -0
- package/.claude/skills/angular-developer/references/migrations.md +30 -0
- package/.claude/skills/angular-developer/references/navigate-to-routes.md +69 -0
- package/.claude/skills/angular-developer/references/outputs.md +86 -0
- package/.claude/skills/angular-developer/references/reactive-forms.md +122 -0
- package/.claude/skills/angular-developer/references/rendering-strategies.md +44 -0
- package/.claude/skills/angular-developer/references/resource.md +77 -0
- package/.claude/skills/angular-developer/references/route-animations.md +56 -0
- package/.claude/skills/angular-developer/references/route-guards.md +52 -0
- package/.claude/skills/angular-developer/references/router-lifecycle.md +45 -0
- package/.claude/skills/angular-developer/references/router-testing.md +87 -0
- package/.claude/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
- package/.claude/skills/angular-developer/references/signal-forms.md +897 -0
- package/.claude/skills/angular-developer/references/signals-overview.md +94 -0
- package/.claude/skills/angular-developer/references/tailwind-css.md +69 -0
- package/.claude/skills/angular-developer/references/template-driven-forms.md +114 -0
- package/.claude/skills/angular-developer/references/testing-fundamentals.md +66 -0
- package/.github/instructions/typescript/code-conventions.md +8 -0
- package/.github/instructions/typescript/comments.md +41 -0
- package/.github/instructions/typescript/formatting.md +42 -0
- package/.github/instructions/typescript/naming-conventions.md +7 -0
- package/.github/instructions/typescript.instructions.md +26 -0
- package/package.json +1 -1
- package/readme.md +1288 -99
- package/version.bak +1 -1
- package/.github/copilot-instructions.md +0 -1
package/readme.md
CHANGED
|
@@ -1,155 +1,1344 @@
|
|
|
1
1
|
[](https://badge.fury.io/js/%40anglr%2Fselect)
|
|
2
2
|
[](https://ci.appveyor.com/project/kukjevov/ng-select)
|
|
3
3
|
|
|
4
|
-
#
|
|
4
|
+
# @anglr/select
|
|
5
5
|
|
|
6
|
-
-
|
|
7
|
-
- [API Extensions](https://ressurectit.github.io/#/content/api/ng-select-extensions/select-extensions)
|
|
8
|
-
- [Samples](https://ressurectit.github.io/#/content/select#samples)
|
|
6
|
+
Angular component representing an HTML `<select>`. Fully plugin-based and signal-driven, with support for multi-select, live search, keyboard navigation, absolute positioning, and deep customisation via swappable plugins.
|
|
9
7
|
|
|
10
|
-
#
|
|
8
|
+
- [Resources](#resources)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Components](#components)
|
|
12
|
+
- [Select](#select)
|
|
13
|
+
- [Option](#option)
|
|
14
|
+
- [OptionGroup](#optiongroup)
|
|
15
|
+
- [Form Integration](#form-integration)
|
|
16
|
+
- [Reactive Forms (FormControl)](#reactive-forms-formcontrol)
|
|
17
|
+
- [Signal Forms (FormField)](#signal-forms-formfield)
|
|
18
|
+
- [Two-way Binding (withDirectAccess)](#two-way-binding-withdirectaccess)
|
|
19
|
+
- [Directives](#directives)
|
|
20
|
+
- [Plugins](#plugins)
|
|
21
|
+
- [Plugin Architecture](#plugin-architecture)
|
|
22
|
+
- [Available Plugins](#available-plugins)
|
|
23
|
+
- [Configuring Plugins](#configuring-plugins)
|
|
24
|
+
- [Implementing a Custom Plugin](#implementing-a-custom-plugin)
|
|
25
|
+
- [Extensions](#extensions)
|
|
26
|
+
- [Pipes](#pipes)
|
|
27
|
+
- [Styling](#styling)
|
|
28
|
+
- [Setup](#setup)
|
|
29
|
+
- [Themes](#themes)
|
|
30
|
+
- [CSS Custom Properties](#css-custom-properties)
|
|
31
|
+
- [Customizing a Theme](#customizing-a-theme)
|
|
32
|
+
- [Building a Custom Theme](#building-a-custom-theme)
|
|
33
|
+
- [Overriding Styles in Components](#overriding-styles-in-components)
|
|
34
|
+
- [Samples](#samples)
|
|
35
|
+
- [Basic Select](#basic-select)
|
|
36
|
+
- [Multiple Select](#multiple-select)
|
|
37
|
+
- [Edit / Type-ahead Mode](#edit--type-ahead-mode)
|
|
38
|
+
- [Live Search (Filter)](#live-search-filter)
|
|
39
|
+
- [Custom Templates](#custom-templates)
|
|
40
|
+
- [Lazy / Dynamic Options](#lazy--dynamic-options)
|
|
41
|
+
- [Dynamic Options with Remote Data](#dynamic-options-with-remote-data)
|
|
42
|
+
- [Dynamic Option Getter (Object Values)](#dynamic-option-getter-object-values)
|
|
43
|
+
- [Absolute Popup](#absolute-popup)
|
|
44
|
+
- [Popover Popup](#popover-popup)
|
|
45
|
+
- [Cancel Value](#cancel-value)
|
|
46
|
+
- [Hide Caret](#hide-caret)
|
|
47
|
+
- [Add New Option](#add-new-option)
|
|
48
|
+
- [Readonly](#readonly)
|
|
49
|
+
- [Custom Readonly State](#custom-readonly-state)
|
|
50
|
+
- [Styling Override](#styling-override)
|
|
11
51
|
|
|
12
|
-
|
|
52
|
+
## Resources
|
|
13
53
|
|
|
14
|
-
|
|
54
|
+
- **API reference**: https://ressurectit.github.io/#/content/api/ng-select/select
|
|
55
|
+
- **API extensions reference**: https://ressurectit.github.io/#/content/api/ng-select-extensions/select-extensions
|
|
56
|
+
- **Live samples**: https://ressurectit.github.io/#/content/select#samples
|
|
15
57
|
|
|
16
|
-
##
|
|
58
|
+
## Installation
|
|
17
59
|
|
|
60
|
+
```bash
|
|
61
|
+
npm install @anglr/select
|
|
18
62
|
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
63
|
+
|
|
64
|
+
**Peer dependencies**: `@angular/core`, `@angular/common`, `@angular/forms`, `@anglr/common`, `@jscrpt/common`, `@css-styles/common`, `rxjs`, `lodash-es`.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
Import the standalone components directly (no module required):
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
74
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
75
|
+
import {Select, Option, SelectFormControl} from '@anglr/select';
|
|
76
|
+
|
|
77
|
+
@Component(
|
|
78
|
+
{
|
|
79
|
+
template: `
|
|
80
|
+
<ng-select [formField]="myField">
|
|
81
|
+
<ng-option value="a" text="Option A"/>
|
|
82
|
+
<ng-option value="b" text="Option B"/>
|
|
83
|
+
<ng-option value="c" text="Option C"/>
|
|
84
|
+
</ng-select>
|
|
85
|
+
`,
|
|
86
|
+
imports:
|
|
87
|
+
[
|
|
88
|
+
Select,
|
|
89
|
+
Option,
|
|
90
|
+
FormField,
|
|
91
|
+
SelectFormControl,
|
|
92
|
+
],
|
|
93
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
94
|
+
})
|
|
95
|
+
export class MyComponent
|
|
96
|
+
{
|
|
97
|
+
protected myField = form(signal<string|null>(null));
|
|
98
|
+
}
|
|
28
99
|
```
|
|
29
100
|
|
|
30
101
|
---
|
|
31
102
|
|
|
32
103
|
## Components
|
|
33
104
|
|
|
34
|
-
|
|
105
|
+
### Select
|
|
106
|
+
|
|
107
|
+
**Selector**: `ng-select`
|
|
108
|
+
|
|
109
|
+
The root component. It manages the plugin lifecycle, template gathering, and options gathering.
|
|
110
|
+
|
|
111
|
+
| Input | Type | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `[selectOptions]` | `RecursivePartial<SelectOptions>` | Full configuration object (plugins, CSS classes, behaviour flags) |
|
|
114
|
+
| `[disabled]` | `boolean` | Signal-based disabled state |
|
|
115
|
+
| `[readonly]` | `boolean` | Signal-based readonly state |
|
|
116
|
+
|
|
117
|
+
| Property | Type | Description |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `initialized` | `Signal<boolean>` | Emits `true` once every plugin is ready |
|
|
120
|
+
| `events` | `SelectEvents` | Observable streams for `focus`, `click`, `blur` |
|
|
121
|
+
| `availableOptions` | `Signal<SelectOptionState[]>` | Current list of `<ng-option>` children |
|
|
122
|
+
|
|
123
|
+
| Method | Returns | Description |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `getPlugin(type)` | `SelectPlugin` | Retrieve a live plugin instance by `SelectPluginType` |
|
|
126
|
+
| `execute(...actions)` | `void` | Run one or more `SelectAction` lambdas |
|
|
127
|
+
| `executeAndReturn(fn)` | `TResult` | Run a `SelectFunction` and return its result |
|
|
128
|
+
|
|
129
|
+
### Option
|
|
130
|
+
|
|
131
|
+
**Selector**: `ng-option`
|
|
132
|
+
|
|
133
|
+
Declares a single selectable option inside `<ng-select>`.
|
|
134
|
+
|
|
135
|
+
| Input | Type | Description |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `[value]` | `TValue` | The value stored when selected |
|
|
138
|
+
| `[text]` | `string` | Display text |
|
|
139
|
+
|
|
140
|
+
### OptionGroup
|
|
141
|
+
|
|
142
|
+
**Selector**: `ng-option-group`
|
|
143
|
+
|
|
144
|
+
Groups `<ng-option>` children under a labelled header.
|
|
145
|
+
|
|
146
|
+
| Input | Type | Description |
|
|
35
147
|
|---|---|---|
|
|
36
|
-
| `
|
|
37
|
-
|
|
38
|
-
|
|
148
|
+
| `[text]` | `string` | **(required)** Group label text |
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<ng-select [formField]="field">
|
|
152
|
+
<ng-option-group text="Fruits">
|
|
153
|
+
<ng-option value="apple" text="Apple"/>
|
|
154
|
+
<ng-option value="banana" text="Banana"/>
|
|
155
|
+
</ng-option-group>
|
|
156
|
+
<ng-option-group text="Vegetables">
|
|
157
|
+
<ng-option value="carrot" text="Carrot"/>
|
|
158
|
+
<ng-option value="pea" text="Pea"/>
|
|
159
|
+
</ng-option-group>
|
|
160
|
+
</ng-select>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Form Integration
|
|
166
|
+
|
|
167
|
+
### Reactive Forms (FormControl)
|
|
168
|
+
|
|
169
|
+
Use the `SelectControlValueAccessor` directive. It activates automatically on `ng-select[formControl]`, `ng-select[formControlName]`, or `ng-select[ngModel]`.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import {Component, ChangeDetectionStrategy} from '@angular/core';
|
|
173
|
+
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
|
174
|
+
import {Select, Option, SelectControlValueAccessor} from '@anglr/select';
|
|
175
|
+
|
|
176
|
+
@Component(
|
|
177
|
+
{
|
|
178
|
+
template: `
|
|
179
|
+
<ng-select [formControl]="ctrl">
|
|
180
|
+
<ng-option value="a" text="A"/>
|
|
181
|
+
<ng-option value="b" text="B"/>
|
|
182
|
+
</ng-select>
|
|
183
|
+
`,
|
|
184
|
+
imports:
|
|
185
|
+
[
|
|
186
|
+
Select,
|
|
187
|
+
Option,
|
|
188
|
+
ReactiveFormsModule,
|
|
189
|
+
SelectControlValueAccessor,
|
|
190
|
+
],
|
|
191
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
192
|
+
})
|
|
193
|
+
export class MyComponent
|
|
194
|
+
{
|
|
195
|
+
protected ctrl: FormControl<string|null> = new FormControl<string|null>(null);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Signal Forms (FormField)
|
|
200
|
+
|
|
201
|
+
Use the `SelectFormControl` directive with Angular's signal-based `form()` / `FormField`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
205
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
206
|
+
import {Select, Option, SelectFormControl} from '@anglr/select';
|
|
207
|
+
|
|
208
|
+
@Component(
|
|
209
|
+
{
|
|
210
|
+
template: `
|
|
211
|
+
<ng-select [formField]="myField">
|
|
212
|
+
<ng-option value="x" text="X"/>
|
|
213
|
+
<ng-option value="y" text="Y"/>
|
|
214
|
+
</ng-select>
|
|
215
|
+
<div>Value: {{ myField().value() | json }}</div>
|
|
216
|
+
`,
|
|
217
|
+
imports:
|
|
218
|
+
[
|
|
219
|
+
Select,
|
|
220
|
+
Option,
|
|
221
|
+
FormField,
|
|
222
|
+
SelectFormControl,
|
|
223
|
+
],
|
|
224
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
225
|
+
})
|
|
226
|
+
export class MyComponent
|
|
227
|
+
{
|
|
228
|
+
protected myField = form(signal<string|null>(null));
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Two-way Binding (withDirectAccess)
|
|
233
|
+
|
|
234
|
+
Use the `withDirectAccess` directive for simple two-way model binding without a form layer:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
238
|
+
import {Select, Option} from '@anglr/select';
|
|
239
|
+
|
|
240
|
+
@Component(
|
|
241
|
+
{
|
|
242
|
+
template: `
|
|
243
|
+
<ng-select [(value)]="selectedValue" withDirectAccess>
|
|
244
|
+
<ng-option value="a" text="A"/>
|
|
245
|
+
</ng-select>
|
|
246
|
+
`,
|
|
247
|
+
imports:
|
|
248
|
+
[
|
|
249
|
+
Select,
|
|
250
|
+
Option,
|
|
251
|
+
],
|
|
252
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
253
|
+
})
|
|
254
|
+
export class MyComponent
|
|
255
|
+
{
|
|
256
|
+
protected selectedValue = signal<string|null>(null);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
39
259
|
|
|
40
260
|
---
|
|
41
261
|
|
|
42
262
|
## Directives
|
|
43
263
|
|
|
44
|
-
|
|
|
264
|
+
| Directive | Selector | Description |
|
|
45
265
|
|---|---|---|
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `ng-select[
|
|
51
|
-
| `ng-select[
|
|
52
|
-
| `ng-select[
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `ng-select[withDirectAccess]` |
|
|
266
|
+
| `SelectControlValueAccessor` | `ng-select[formControl]`, `ng-select[formControlName]`, `ng-select[ngModel]` | Bridges to Angular reactive/template-driven forms |
|
|
267
|
+
| `SelectFormControl` | `ng-select[formField]` | Bridges to Angular signal-based forms |
|
|
268
|
+
| `SelectEdit` | `ng-select[edit]` | Enables type-ahead / editable mode (swaps to `EditNormalState` + `EditLiveSearch`) |
|
|
269
|
+
| `SelectAbsolute` | `ng-select[absolute]` | Appends popup to `document.body` for overflow-safe positioning |
|
|
270
|
+
| `SelectMultipleKeepPopup` | `ng-select[multiple]` | Keeps popup open after each selection in multi-select mode |
|
|
271
|
+
| `SelectPlaceholder` | `ng-select[placeholder]` | Sets placeholder text reactively |
|
|
272
|
+
| `SelectNoCarret` | `ng-select[noCarret]` | Hides the dropdown caret |
|
|
273
|
+
| `NormalStateTemplate` | `[normalStateTemplate]` | Custom template for the closed/normal state display |
|
|
274
|
+
| `NormalStateTagTemplate` | `[normalStateTagTemplate]` | Custom template for individual tags in multi-select normal state |
|
|
275
|
+
| `OptionTemplate` | `[optionTemplate]` | Custom template for each option in the popup |
|
|
276
|
+
| `OptionGroupTemplate` | `[optionGroupTemplate]` | Custom template for option group headers |
|
|
277
|
+
| `WithDirectAccess` | `ng-select[withDirectAccess]` | Two-way value binding without a form control |
|
|
58
278
|
|
|
59
279
|
---
|
|
60
280
|
|
|
61
281
|
## Plugins
|
|
62
282
|
|
|
63
|
-
|
|
283
|
+
### Plugin Architecture
|
|
284
|
+
|
|
285
|
+
The select component is composed of **9 independent plugin slots**. Each slot has a default implementation that can be swapped for an alternative or custom one. Plugins communicate via a shared `SelectBus` — a centralized reactive bus with subjects/observables for events like option activation, popup show/hide, keyboard actions, etc.
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
┌─────────────────────────────────────────────┐
|
|
289
|
+
│ ng-select │
|
|
290
|
+
│ │
|
|
291
|
+
│ ┌──────────────┐ ┌────────────────────┐ │
|
|
292
|
+
│ │ LiveSearch │ │ Interactions │ │
|
|
293
|
+
│ └──────────────┘ └────────────────────┘ │
|
|
294
|
+
│ ┌──────────────┐ ┌────────────────────┐ │
|
|
295
|
+
│ │OptionsHandler│ │ KeyboardHandler │ │
|
|
296
|
+
│ └──────────────┘ └────────────────────┘ │
|
|
297
|
+
│ ┌──────────────┐ ┌────────────────────┐ │
|
|
298
|
+
│ │ Positioner │ │ ValueHandler │ │
|
|
299
|
+
│ └──────────────┘ └────────────────────┘ │
|
|
300
|
+
│ │
|
|
301
|
+
│ ┌──────────────────────────────────────┐ │
|
|
302
|
+
│ │ Visual Container │ │
|
|
303
|
+
│ │ ┌──────────┐ ┌───────┐ ┌───────┐ │ │
|
|
304
|
+
│ │ │ReadonlyS.│ │Normal │ │ Popup │ │ │
|
|
305
|
+
│ │ └──────────┘ │ State │ │ │ │ │
|
|
306
|
+
│ │ └───────┘ └───────┘ │ │
|
|
307
|
+
│ └──────────────────────────────────────┘ │
|
|
308
|
+
└─────────────────────────────────────────────┘
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Every plugin implements the `SelectPlugin` base interface and receives:
|
|
312
|
+
|
|
313
|
+
- `selectPlugins` — references to all sibling plugin instances
|
|
314
|
+
- `pluginElement` — its own host `ElementRef`
|
|
315
|
+
- `options` — merged configuration object
|
|
316
|
+
- `selectBus` — the shared reactive bus
|
|
317
|
+
|
|
318
|
+
### Available Plugins
|
|
319
|
+
|
|
320
|
+
| Slot | Default | Alternatives | Purpose |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| **Interactions** | `SimpleInteractions` | — | Click-to-toggle, option click, click-outside handling |
|
|
323
|
+
| **KeyboardHandler** | `SimpleKeyboardHandler` | — | Arrow keys, Enter, Backspace, alphanumeric search |
|
|
324
|
+
| **LiveSearch** | `NoLiveSearch` | `FilterLiveSearch`, `EditLiveSearch` | Text-based filtering of options |
|
|
325
|
+
| **NormalState** | `SimpleNormalState` | `EditNormalState` | Closed-state appearance (value, caret, cancel button) |
|
|
326
|
+
| **OptionsHandler** | `SimpleOptionsHandler` | `NoOptionsHandler` | Options list management, filtering, add-new-option |
|
|
327
|
+
| **Popup** | `SimplePopup` | — | Dropdown panel rendering |
|
|
328
|
+
| **Positioner** | `CommonPositioner` | `PopoverPositioner`, `NoPositioner` | Popup positioning (z-index, absolute, popover API) |
|
|
329
|
+
| **ReadonlyState** | *(same as NormalState)* | Custom component | Display when readonly |
|
|
330
|
+
| **ValueHandler** | `StaticValueHandler` | `DynamicValueHandler` | Selected value storage, single/multi-select logic |
|
|
331
|
+
|
|
332
|
+
### Configuring Plugins
|
|
333
|
+
|
|
334
|
+
Pass a `[selectOptions]` object to override plugin types and/or their options. Partial objects are deep-merged with defaults:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import {FilterLiveSearch, SelectOptions} from '@anglr/select';
|
|
338
|
+
import {RecursivePartial} from '@jscrpt/common';
|
|
339
|
+
|
|
340
|
+
const options: RecursivePartial<SelectOptions> =
|
|
341
|
+
{
|
|
342
|
+
plugins:
|
|
343
|
+
{
|
|
344
|
+
liveSearch:
|
|
345
|
+
{
|
|
346
|
+
type: FilterLiveSearch,
|
|
347
|
+
},
|
|
348
|
+
popup:
|
|
349
|
+
{
|
|
350
|
+
options:
|
|
351
|
+
{
|
|
352
|
+
liveSearchEnabled: true,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```html
|
|
360
|
+
<ng-select [selectOptions]="options" [formField]="field">
|
|
361
|
+
<ng-option value="a" text="Alpha"/>
|
|
362
|
+
</ng-select>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
You can also swap plugin types via dependency injection providers:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import {provideNormalStateType, EditNormalState} from '@anglr/select';
|
|
64
369
|
|
|
65
|
-
|
|
370
|
+
@Component(
|
|
371
|
+
{
|
|
372
|
+
providers:
|
|
373
|
+
[
|
|
374
|
+
provideNormalStateType(EditNormalState),
|
|
375
|
+
],
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Implementing a Custom Plugin
|
|
380
|
+
|
|
381
|
+
To create a custom plugin, implement the corresponding plugin interface and register it via `selectOptions` or a provider.
|
|
382
|
+
|
|
383
|
+
**Example: Custom ReadonlyState Plugin**
|
|
384
|
+
|
|
385
|
+
A custom readonly state that renders selected values differently.
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import {Component, ChangeDetectionStrategy, ElementRef} from '@angular/core';
|
|
389
|
+
import {ReadonlyState, ReadonlyStateOptions, SelectBus, SelectPluginInstances} from '@anglr/select';
|
|
390
|
+
|
|
391
|
+
@Component(
|
|
392
|
+
{
|
|
393
|
+
selector: 'custom-readonly-state',
|
|
394
|
+
template: `
|
|
395
|
+
<div class="custom-readonly">
|
|
396
|
+
@if (selectBus.selectedOptions(); as selected) {
|
|
397
|
+
@if (isArray(selected)) {
|
|
398
|
+
@for (opt of selected; track opt.index) {
|
|
399
|
+
<span class="tag">{{ opt.text() }}</span>
|
|
400
|
+
}
|
|
401
|
+
} @else {
|
|
402
|
+
<span>{{ selected.text() }}</span>
|
|
403
|
+
}
|
|
404
|
+
} @else {
|
|
405
|
+
<em>No value selected</em>
|
|
406
|
+
}
|
|
407
|
+
</div>
|
|
408
|
+
`,
|
|
409
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
410
|
+
})
|
|
411
|
+
export class CustomReadonlyStateComponent implements ReadonlyState<unknown, unknown>
|
|
412
|
+
{
|
|
413
|
+
public readonly selectPlugins!: SelectPluginInstances<unknown, unknown>;
|
|
414
|
+
public readonly pluginElement!: ElementRef<HTMLElement>;
|
|
415
|
+
public options!: ReadonlyStateOptions;
|
|
416
|
+
public readonly selectBus!: SelectBus<unknown, unknown>;
|
|
417
|
+
|
|
418
|
+
protected isArray = Array.isArray;
|
|
419
|
+
|
|
420
|
+
public focus(): void
|
|
421
|
+
{
|
|
422
|
+
// no-op for readonly
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Register the custom plugin:**
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
this.selectOptions =
|
|
431
|
+
{
|
|
432
|
+
plugins:
|
|
433
|
+
{
|
|
434
|
+
readonlyState:
|
|
435
|
+
{
|
|
436
|
+
type: CustomReadonlyStateComponent,
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```html
|
|
443
|
+
<ng-select [selectOptions]="selectOptions" [formField]="field">
|
|
444
|
+
<ng-option value="a" text="A"/>
|
|
445
|
+
</ng-select>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Example: Custom OptionsHandler Plugin**
|
|
449
|
+
|
|
450
|
+
You can extend `OptionsHandlerBase` to add custom filtering logic:
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import {Component} from '@angular/core';
|
|
454
|
+
import {OptionsHandlerBase} from '@anglr/select';
|
|
455
|
+
|
|
456
|
+
@Component(
|
|
457
|
+
{
|
|
458
|
+
selector: 'custom-options-handler',
|
|
459
|
+
template: '',
|
|
460
|
+
})
|
|
461
|
+
export class CustomOptionsHandlerComponent extends OptionsHandlerBase<string>
|
|
462
|
+
{
|
|
463
|
+
// Override filtering, sorting, or add-new-option logic
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Extensions
|
|
470
|
+
|
|
471
|
+
The `@anglr/select/extensions` entry point provides helper functions for programmatic interaction with the select via `execute()` / `executeAndReturn()`:
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import {getSearch, getValue, setReadonly, setValue} from '@anglr/select/extensions';
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
| Function | Type | Description |
|
|
66
478
|
|---|---|---|
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
479
|
+
| `getValue()` | `SelectFunction` | Returns the current value (reactive) |
|
|
480
|
+
| `setValue(value)` | `SelectAction` | Sets the select's value programmatically |
|
|
481
|
+
| `getSearch()` | `SelectFunction` | Returns the current live-search text (reactive) |
|
|
482
|
+
| `setReadonly(flag?)` | `SelectAction` | Sets the select to readonly mode |
|
|
483
|
+
|
|
484
|
+
**Usage:**
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
// Get the current value
|
|
488
|
+
const value = this.select().executeAndReturn(getValue());
|
|
489
|
+
|
|
490
|
+
// Set a value programmatically
|
|
491
|
+
this.select().execute(setValue('option-a'));
|
|
492
|
+
|
|
493
|
+
// Get current search text (in an effect for reactivity)
|
|
494
|
+
effect(() =>
|
|
495
|
+
{
|
|
496
|
+
const search = this.select().executeAndReturn(getSearch());
|
|
497
|
+
// fetch remote data based on search...
|
|
498
|
+
});
|
|
499
|
+
```
|
|
78
500
|
|
|
79
501
|
---
|
|
80
502
|
|
|
81
503
|
## Pipes
|
|
82
504
|
|
|
83
|
-
| Pipe
|
|
505
|
+
| Pipe | Pure | Description |
|
|
84
506
|
|---|---|---|
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `getPlugin` |
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `optionCssClasses` |
|
|
507
|
+
| `displayValue` | No | Transforms `SelectOption` or `SelectOption[]` into a display string |
|
|
508
|
+
| `hasValue` | Yes | Returns `true` when the option(s) represent a selected value |
|
|
509
|
+
| `getPlugin` | Yes | Retrieves a typed plugin instance from another plugin |
|
|
510
|
+
| `addNewOption` | Yes | Produces the "add new option" label for synthetic options |
|
|
511
|
+
| `groupedListOptions` | Yes | Converts a flat option list into grouped tuples for rendering |
|
|
512
|
+
| `optionCssClasses` | No | Returns CSS class array for a popup option (selected, active, grouped) |
|
|
91
513
|
|
|
92
514
|
---
|
|
93
515
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
Key contracts defined under `src/interfaces/`:
|
|
97
|
-
|
|
98
|
-
| Interface | Purpose |
|
|
99
|
-
|---|---|
|
|
100
|
-
| `SelectApi` | Public API of the select: `initialized`, `selectOptions`, `events`, `getPlugin()`, `execute()`, `executeAndReturn()`. |
|
|
101
|
-
| `SelectOptions` | Full configuration of the select (plugins, CSS classes, display/value/text functions, etc.). |
|
|
102
|
-
| `SelectPlugin` | Base for all plugins; grants access to the `selectPlugins` map. |
|
|
103
|
-
| `SelectEvents` | Signal-based event streams on the select. |
|
|
104
|
-
| `SelectOption` | Single option data: `value`, `text`, and `group` signals. |
|
|
105
|
-
| `SelectOptionState` | Extends `SelectOption` with `active`, `selected`, and `index`. |
|
|
106
|
-
| `SelectOptionStateSyntetic` | Extends `SelectOptionState` with a `created` flag (add-new-option UX). |
|
|
107
|
-
| `SelectOptionGroup` | Group metadata: `id`, `text`, `index`, optional `template`. |
|
|
108
|
-
| `NormalStateContext` | Template context for custom normal-state rendering. |
|
|
109
|
-
| `NormalStateTagContext` | Template context for individual-tag rendering in multi-select. |
|
|
110
|
-
| `PopupContext` | Template context for custom option rendering in the popup. |
|
|
111
|
-
| `PluginDescription` | Pairs a plugin `type` with optional `options`. |
|
|
516
|
+
## Styling
|
|
112
517
|
|
|
113
|
-
|
|
518
|
+
### Setup
|
|
519
|
+
|
|
520
|
+
The library ships pre-built SCSS that uses CSS custom properties for theming. Import styles in your global stylesheet:
|
|
521
|
+
|
|
522
|
+
```scss
|
|
523
|
+
// Import the light theme (includes core CSS + variables)
|
|
524
|
+
@use '@anglr/select/styles/themes/light' as selectLight;
|
|
525
|
+
|
|
526
|
+
// Apply the theme
|
|
527
|
+
@include selectLight.buildTheme();
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Or for the dark theme:
|
|
531
|
+
|
|
532
|
+
```scss
|
|
533
|
+
@use '@anglr/select/styles/themes/dark' as selectDark;
|
|
534
|
+
|
|
535
|
+
@include selectDark.buildTheme();
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Themes
|
|
539
|
+
|
|
540
|
+
Two pre-built themes are included:
|
|
541
|
+
|
|
542
|
+
| Theme | Import Path | Description |
|
|
543
|
+
|---|---|---|
|
|
544
|
+
| **Light** | `@anglr/select/styles/themes/light` | Light grays, blue focus, black text |
|
|
545
|
+
| **Dark** | `@anglr/select/styles/themes/dark` | Dark grays, blue focus, white text |
|
|
546
|
+
|
|
547
|
+
Each theme exposes:
|
|
548
|
+
- `defineTheme($fontSize, $theme, $customization)` — returns a theme map
|
|
549
|
+
- `buildTheme()` — applies the theme to the document
|
|
550
|
+
|
|
551
|
+
### CSS Custom Properties
|
|
552
|
+
|
|
553
|
+
All visual aspects are driven by CSS custom properties. Here are the key ones:
|
|
554
|
+
|
|
555
|
+
**Normal State**
|
|
556
|
+
|
|
557
|
+
| Property | Default (Light) | Description |
|
|
558
|
+
|---|---|---|
|
|
559
|
+
| `--select-normalState-borderColor` | `#666` | Border color |
|
|
560
|
+
| `--select-normalState-background` | `#eee` | Background color |
|
|
561
|
+
| `--select-normalState-foreground` | `#000` | Text color |
|
|
562
|
+
| `--select-normalState-borderRadius` | `4px` | Corner rounding |
|
|
563
|
+
| `--select-normalState-padding` | `1px 2px` | Inner padding |
|
|
564
|
+
| `--select-normalState-focus-borderColor` | `#0066ff` | Focus border |
|
|
565
|
+
| `--select-normalState-focus-outlineColor` | `#0066ff` | Focus outline |
|
|
566
|
+
| `--select-normalState-tag-background` | `#ccc` | Multi-select tag background |
|
|
567
|
+
| `--select-normalState-tag-foreground` | `#000` | Multi-select tag text |
|
|
568
|
+
| `--select-normalState-disabled-background` | *(derived)* | Disabled background |
|
|
569
|
+
|
|
570
|
+
**Popup**
|
|
571
|
+
|
|
572
|
+
| Property | Default (Light) | Description |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| `--select-popup-container-background` | `#F0F0F0` | Popup background |
|
|
575
|
+
| `--select-popup-container-foreground` | `#000` | Popup text color |
|
|
576
|
+
| `--select-popup-container-borderColor` | `#acacac` | Popup border |
|
|
577
|
+
| `--select-popup-option-active-background` | *(derived from container)* | Hovered option |
|
|
578
|
+
| `--select-popup-option-selected-background` | *(derived from container)* | Selected option |
|
|
579
|
+
|
|
580
|
+
**Live Search**
|
|
581
|
+
|
|
582
|
+
| Property | Default (Light) | Description |
|
|
583
|
+
|---|---|---|
|
|
584
|
+
| `--select-liveSearch-placeholder-foreground` | `#000` | Placeholder color |
|
|
585
|
+
|
|
586
|
+
### Customizing a Theme
|
|
587
|
+
|
|
588
|
+
Override specific values when calling `defineTheme`:
|
|
589
|
+
|
|
590
|
+
```scss
|
|
591
|
+
@use '@anglr/select/styles/themes/light' as selectLight;
|
|
592
|
+
@use '@anglr/select/styles/core/mixins' as selectMixins;
|
|
593
|
+
|
|
594
|
+
$myTheme: selectLight.defineTheme(
|
|
595
|
+
$fontSize: 16px,
|
|
596
|
+
$customization: (
|
|
597
|
+
select: (
|
|
598
|
+
normalState: (
|
|
599
|
+
borderColor: #999,
|
|
600
|
+
background: #fff,
|
|
601
|
+
foreground: #333,
|
|
602
|
+
borderRadius: 8px,
|
|
603
|
+
focus: (
|
|
604
|
+
borderColor: #ff6600,
|
|
605
|
+
outlineColor: #ff6600,
|
|
606
|
+
),
|
|
607
|
+
tag: (
|
|
608
|
+
background: #e0e0e0,
|
|
609
|
+
borderRadius: 12px,
|
|
610
|
+
),
|
|
611
|
+
),
|
|
612
|
+
popup: (
|
|
613
|
+
container: (
|
|
614
|
+
background: #fff,
|
|
615
|
+
borderColor: #ddd,
|
|
616
|
+
borderRadius: 8px,
|
|
617
|
+
),
|
|
618
|
+
),
|
|
619
|
+
),
|
|
620
|
+
),
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
@include selectMixins.buildTheme($myTheme);
|
|
624
|
+
```
|
|
114
625
|
|
|
115
|
-
|
|
626
|
+
### Building a Custom Theme
|
|
116
627
|
|
|
117
|
-
|
|
118
|
-
`SelectPluginType` — string enum naming all nine plugin slots.
|
|
628
|
+
Create a theme from scratch using the `defineTheme` function from the default theme:
|
|
119
629
|
|
|
120
|
-
|
|
121
|
-
|
|
630
|
+
```scss
|
|
631
|
+
@use '@anglr/select/styles/core/defaultTheme' as selectDefault;
|
|
632
|
+
@use '@anglr/select/styles/core/mixins' as selectMixins;
|
|
122
633
|
|
|
123
|
-
|
|
124
|
-
|
|
634
|
+
$customTheme: selectDefault.defineTheme(
|
|
635
|
+
$fontSize: 14px,
|
|
636
|
+
$theme: (
|
|
637
|
+
select: (
|
|
638
|
+
normalState: (
|
|
639
|
+
borderColor: #3498db,
|
|
640
|
+
background: #ecf0f1,
|
|
641
|
+
foreground: #2c3e50,
|
|
642
|
+
focus: (
|
|
643
|
+
borderColor: #2980b9,
|
|
644
|
+
outlineColor: #2980b9,
|
|
645
|
+
),
|
|
646
|
+
),
|
|
647
|
+
popup: (
|
|
648
|
+
container: (
|
|
649
|
+
background: #ecf0f1,
|
|
650
|
+
foreground: #2c3e50,
|
|
651
|
+
borderColor: #bdc3c7,
|
|
652
|
+
),
|
|
653
|
+
),
|
|
654
|
+
),
|
|
655
|
+
),
|
|
656
|
+
);
|
|
125
657
|
|
|
126
|
-
|
|
127
|
-
|
|
658
|
+
@include selectMixins.buildTheme($customTheme);
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Overriding Styles in Components
|
|
128
662
|
|
|
129
|
-
|
|
130
|
-
Internal helpers: `compareValueAndOption()`, `compareSelectOptions()`, `togglePopup()`, `selectOption()`.
|
|
663
|
+
For component-scoped overrides, wrap the select in a container and override CSS custom properties:
|
|
131
664
|
|
|
132
|
-
|
|
665
|
+
```html
|
|
666
|
+
<div class="select-override">
|
|
667
|
+
<ng-select [formField]="field">
|
|
668
|
+
<ng-option value="a" text="A"/>
|
|
669
|
+
</ng-select>
|
|
670
|
+
</div>
|
|
671
|
+
```
|
|
133
672
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
673
|
+
```scss
|
|
674
|
+
.select-override {
|
|
675
|
+
--select-normalState-borderColor: tomato;
|
|
676
|
+
--select-normalState-background: lightyellow;
|
|
677
|
+
--select-normalState-focus-borderColor: red;
|
|
678
|
+
--select-popup-container-background: lightyellow;
|
|
679
|
+
}
|
|
680
|
+
```
|
|
139
681
|
|
|
140
682
|
---
|
|
141
683
|
|
|
142
|
-
##
|
|
684
|
+
## Samples
|
|
685
|
+
|
|
686
|
+
### Basic Select
|
|
687
|
+
|
|
688
|
+
Minimal select with reactive forms:
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
// basic.component.ts
|
|
692
|
+
import {Component, ChangeDetectionStrategy} from '@angular/core';
|
|
693
|
+
import {JsonPipe} from '@angular/common';
|
|
694
|
+
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
|
695
|
+
import {Option, Select, SelectControlValueAccessor} from '@anglr/select';
|
|
696
|
+
|
|
697
|
+
@Component(
|
|
698
|
+
{
|
|
699
|
+
selector: 'basic-sample',
|
|
700
|
+
templateUrl: 'basic.component.html',
|
|
701
|
+
imports:
|
|
702
|
+
[
|
|
703
|
+
Select,
|
|
704
|
+
Option,
|
|
705
|
+
JsonPipe,
|
|
706
|
+
ReactiveFormsModule,
|
|
707
|
+
SelectControlValueAccessor,
|
|
708
|
+
],
|
|
709
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
710
|
+
})
|
|
711
|
+
export class BasicSampleComponent
|
|
712
|
+
{
|
|
713
|
+
protected selectControl: FormControl<string|null> = new FormControl<string|null>(null);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
```html
|
|
718
|
+
<!-- basic.component.html -->
|
|
719
|
+
<div>Value: {{ selectControl.value | json }}</div>
|
|
720
|
+
|
|
721
|
+
<ng-select [formControl]="selectControl">
|
|
722
|
+
<ng-option value="first" text="First value text"/>
|
|
723
|
+
<ng-option value="second" text="Second value text"/>
|
|
724
|
+
<ng-option value="third" text="Third value text"/>
|
|
725
|
+
<ng-option value="fourth" text="Fourth value text"/>
|
|
726
|
+
<ng-option value="fifth" text="Fifth value text"/>
|
|
727
|
+
</ng-select>
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Multiple Select
|
|
731
|
+
|
|
732
|
+
Multi-select with signal forms. The `multiple` attribute enables multi-selection, and `SelectMultipleKeepPopup` keeps the popup open after each pick:
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
// multiple.component.ts
|
|
736
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
737
|
+
import {JsonPipe} from '@angular/common';
|
|
738
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
739
|
+
import {Option, Select, SelectFormControl, SelectMultipleKeepPopup} from '@anglr/select';
|
|
740
|
+
|
|
741
|
+
@Component(
|
|
742
|
+
{
|
|
743
|
+
selector: 'multiple-sample',
|
|
744
|
+
templateUrl: 'multiple.component.html',
|
|
745
|
+
imports:
|
|
746
|
+
[
|
|
747
|
+
Select,
|
|
748
|
+
Option,
|
|
749
|
+
JsonPipe,
|
|
750
|
+
FormField,
|
|
751
|
+
SelectFormControl,
|
|
752
|
+
SelectMultipleKeepPopup,
|
|
753
|
+
],
|
|
754
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
755
|
+
})
|
|
756
|
+
export class MultipleSampleComponent
|
|
757
|
+
{
|
|
758
|
+
protected selectField = form(signal<string|null>(null));
|
|
759
|
+
}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
```html
|
|
763
|
+
<!-- multiple.component.html -->
|
|
764
|
+
<div>Value: {{ selectField().value() | json }}</div>
|
|
765
|
+
|
|
766
|
+
<ng-select multiple [formField]="selectField">
|
|
767
|
+
<ng-option value="first" text="First value text"/>
|
|
768
|
+
<ng-option value="second" text="Second value text"/>
|
|
769
|
+
<ng-option value="third" text="Third value text"/>
|
|
770
|
+
<ng-option value="fourth" text="Fourth value text"/>
|
|
771
|
+
<ng-option value="fifth" text="Fifth value text"/>
|
|
772
|
+
</ng-select>
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Edit / Type-ahead Mode
|
|
776
|
+
|
|
777
|
+
Add the `edit` directive for a type-ahead experience. Works for both single and multi-select:
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
// edit.component.ts
|
|
781
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
782
|
+
import {JsonPipe} from '@angular/common';
|
|
783
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
784
|
+
import {Option, Select, SelectEdit, SelectFormControl, SelectMultipleKeepPopup} from '@anglr/select';
|
|
785
|
+
|
|
786
|
+
@Component(
|
|
787
|
+
{
|
|
788
|
+
selector: 'edit-sample',
|
|
789
|
+
templateUrl: 'edit.component.html',
|
|
790
|
+
imports:
|
|
791
|
+
[
|
|
792
|
+
Select,
|
|
793
|
+
Option,
|
|
794
|
+
JsonPipe,
|
|
795
|
+
FormField,
|
|
796
|
+
SelectEdit,
|
|
797
|
+
SelectFormControl,
|
|
798
|
+
SelectMultipleKeepPopup,
|
|
799
|
+
],
|
|
800
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
801
|
+
})
|
|
802
|
+
export class EditSampleComponent
|
|
803
|
+
{
|
|
804
|
+
protected selectField = form(signal<string|null>(null));
|
|
805
|
+
protected selectMultipleField = form(signal<string[]|null>([]));
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
```html
|
|
810
|
+
<!-- edit.component.html -->
|
|
811
|
+
<!-- Single edit -->
|
|
812
|
+
<ng-select [formField]="selectField" edit>
|
|
813
|
+
<ng-option value="first" text="First value text"/>
|
|
814
|
+
<ng-option value="second" text="Second value text"/>
|
|
815
|
+
<ng-option value="third" text="Third value text"/>
|
|
816
|
+
</ng-select>
|
|
817
|
+
|
|
818
|
+
<!-- Multiple edit -->
|
|
819
|
+
<ng-select multiple [formField]="selectMultipleField" edit>
|
|
820
|
+
<ng-option value="first" text="First value text"/>
|
|
821
|
+
<ng-option value="second" text="Second value text"/>
|
|
822
|
+
<ng-option value="third" text="Third value text"/>
|
|
823
|
+
</ng-select>
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Live Search (Filter)
|
|
827
|
+
|
|
828
|
+
Enable a filter text input inside the popup using `FilterLiveSearch`:
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
// liveSearch.component.ts
|
|
832
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
833
|
+
import {JsonPipe} from '@angular/common';
|
|
834
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
835
|
+
import {Option, Select, SelectOptions, FilterLiveSearch, SelectFormControl} from '@anglr/select';
|
|
836
|
+
import {RecursivePartial} from '@jscrpt/common';
|
|
837
|
+
|
|
838
|
+
@Component(
|
|
839
|
+
{
|
|
840
|
+
selector: 'live-search-sample',
|
|
841
|
+
templateUrl: 'liveSearch.component.html',
|
|
842
|
+
imports:
|
|
843
|
+
[
|
|
844
|
+
Select,
|
|
845
|
+
Option,
|
|
846
|
+
JsonPipe,
|
|
847
|
+
FormField,
|
|
848
|
+
SelectFormControl,
|
|
849
|
+
],
|
|
850
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
851
|
+
})
|
|
852
|
+
export class LiveSearchSampleComponent
|
|
853
|
+
{
|
|
854
|
+
protected selectField = form(signal<string|null>(null));
|
|
143
855
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
856
|
+
protected selectOptions: RecursivePartial<SelectOptions> =
|
|
857
|
+
{
|
|
858
|
+
plugins:
|
|
859
|
+
{
|
|
860
|
+
popup:
|
|
861
|
+
{
|
|
862
|
+
options:
|
|
863
|
+
{
|
|
864
|
+
liveSearchEnabled: true,
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
liveSearch:
|
|
868
|
+
{
|
|
869
|
+
type: FilterLiveSearch,
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
```html
|
|
877
|
+
<!-- liveSearch.component.html -->
|
|
878
|
+
<div>Value: {{ selectField().value() | json }}</div>
|
|
879
|
+
|
|
880
|
+
<ng-select [selectOptions]="selectOptions" [formField]="selectField">
|
|
881
|
+
<ng-option value="first" text="First value text"/>
|
|
882
|
+
<ng-option value="second" text="Second value text"/>
|
|
883
|
+
<ng-option value="third" text="Third value text"/>
|
|
884
|
+
<ng-option value="fourth" text="Fourth value text"/>
|
|
885
|
+
<ng-option value="fifth" text="Fifth value text"/>
|
|
886
|
+
</ng-select>
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
### Custom Templates
|
|
890
|
+
|
|
891
|
+
Use `[normalStateTemplate]` and `[optionTemplate]` to completely customize rendering:
|
|
892
|
+
|
|
893
|
+
```typescript
|
|
894
|
+
// customTemplate.component.ts
|
|
895
|
+
import {Component, ChangeDetectionStrategy, signal} from '@angular/core';
|
|
896
|
+
import {JsonPipe} from '@angular/common';
|
|
897
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
898
|
+
import {DisplayValue, NormalStateTemplate, Option, OptionTemplate, Select, SelectFormControl} from '@anglr/select';
|
|
899
|
+
|
|
900
|
+
@Component(
|
|
901
|
+
{
|
|
902
|
+
selector: 'custom-template-sample',
|
|
903
|
+
templateUrl: 'customTemplate.component.html',
|
|
904
|
+
imports:
|
|
905
|
+
[
|
|
906
|
+
Select,
|
|
907
|
+
Option,
|
|
908
|
+
JsonPipe,
|
|
909
|
+
FormField,
|
|
910
|
+
DisplayValue,
|
|
911
|
+
OptionTemplate,
|
|
912
|
+
SelectFormControl,
|
|
913
|
+
NormalStateTemplate,
|
|
914
|
+
],
|
|
915
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
916
|
+
})
|
|
917
|
+
export class CustomTemplateSampleComponent
|
|
918
|
+
{
|
|
919
|
+
protected selectField = form(signal<string|null>(null));
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
```html
|
|
924
|
+
<!-- customTemplate.component.html -->
|
|
925
|
+
<div>Value: {{ selectField().value() | json }}</div>
|
|
926
|
+
|
|
927
|
+
<ng-select [formField]="selectField">
|
|
928
|
+
<ng-option value="first" text="First value text"/>
|
|
929
|
+
<ng-option value="second" text="Second value text"/>
|
|
930
|
+
<ng-option value="third" text="Third value text"/>
|
|
931
|
+
<ng-option value="fourth" text="Fourth value text"/>
|
|
932
|
+
<ng-option value="fifth" text="Fifth value text"/>
|
|
933
|
+
|
|
934
|
+
<!-- Custom normal state: shows arrows around the display value -->
|
|
935
|
+
<span *normalStateTemplate="let normalState">
|
|
936
|
+
=> {{ normalState.selectBus.selectedOptions() | displayValue }} <=
|
|
937
|
+
</span>
|
|
938
|
+
|
|
939
|
+
<!-- Custom option template: shows value and text -->
|
|
940
|
+
<span *optionTemplate="let option">
|
|
941
|
+
{{ option.value() }} - {{ option.text() }}
|
|
942
|
+
</span>
|
|
943
|
+
</ng-select>
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Lazy / Dynamic Options
|
|
947
|
+
|
|
948
|
+
Options can be loaded asynchronously. Use signals and `@for` to render them once loaded:
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
// basicLazy.component.ts
|
|
952
|
+
import {Component, ChangeDetectionStrategy, WritableSignal, signal} from '@angular/core';
|
|
953
|
+
import {JsonPipe} from '@angular/common';
|
|
954
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
955
|
+
import {Option, Select, SelectFormControl} from '@anglr/select';
|
|
956
|
+
|
|
957
|
+
interface KodPopisValue
|
|
958
|
+
{
|
|
959
|
+
kod: string;
|
|
960
|
+
popis: string;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
@Component(
|
|
964
|
+
{
|
|
965
|
+
selector: 'basic-lazy-sample',
|
|
966
|
+
templateUrl: 'basicLazy.component.html',
|
|
967
|
+
imports:
|
|
968
|
+
[
|
|
969
|
+
Select,
|
|
970
|
+
Option,
|
|
971
|
+
JsonPipe,
|
|
972
|
+
FormField,
|
|
973
|
+
SelectFormControl,
|
|
974
|
+
],
|
|
975
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
976
|
+
})
|
|
977
|
+
export class BasicLazySampleComponent
|
|
978
|
+
{
|
|
979
|
+
protected selectField = form(signal<string|null>(null));
|
|
980
|
+
protected lazyOptions: WritableSignal<KodPopisValue[]> = signal([]);
|
|
981
|
+
|
|
982
|
+
constructor()
|
|
983
|
+
{
|
|
984
|
+
// Simulate async loading
|
|
985
|
+
setTimeout(() =>
|
|
986
|
+
{
|
|
987
|
+
this.lazyOptions.set([
|
|
988
|
+
{kod: 'first', popis: 'First value text'},
|
|
989
|
+
{kod: 'second', popis: 'Second value text'},
|
|
990
|
+
{kod: 'third', popis: 'Third value text'},
|
|
991
|
+
]);
|
|
992
|
+
}, 2500);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
```html
|
|
998
|
+
<!-- basicLazy.component.html -->
|
|
999
|
+
<div>Value: {{ selectField().value() | json }}</div>
|
|
1000
|
+
|
|
1001
|
+
<ng-select [formField]="selectField">
|
|
1002
|
+
@for (option of lazyOptions(); track option) {
|
|
1003
|
+
<ng-option [value]="option.kod" [text]="option.popis"/>
|
|
1004
|
+
}
|
|
1005
|
+
</ng-select>
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### Dynamic Options with Remote Data
|
|
1009
|
+
|
|
1010
|
+
Use `CodeOptionsGatherer` + `DynamicValueHandler` + `FilterLiveSearch` to load options from a remote API:
|
|
1011
|
+
|
|
1012
|
+
```typescript
|
|
1013
|
+
// dynamic.component.ts
|
|
1014
|
+
import {Component, ChangeDetectionStrategy, effect, Signal, viewChild, signal} from '@angular/core';
|
|
1015
|
+
import {JsonPipe} from '@angular/common';
|
|
1016
|
+
import {form, FormField} from '@angular/forms/signals';
|
|
1017
|
+
import {CodeOptionsGatherer, DynamicValueHandler, DynamicValueHandlerOptions, FilterLiveSearch, Select, SelectFormControl, SelectOption, SelectOptions} from '@anglr/select';
|
|
1018
|
+
import {getSearch} from '@anglr/select/extensions';
|
|
1019
|
+
import {RecursivePartial} from '@jscrpt/common';
|
|
1020
|
+
|
|
1021
|
+
@Component(
|
|
1022
|
+
{
|
|
1023
|
+
selector: 'dynamic-sample',
|
|
1024
|
+
templateUrl: 'dynamic.component.html',
|
|
1025
|
+
imports:
|
|
1026
|
+
[
|
|
1027
|
+
Select,
|
|
1028
|
+
JsonPipe,
|
|
1029
|
+
FormField,
|
|
1030
|
+
SelectFormControl,
|
|
1031
|
+
],
|
|
1032
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
1033
|
+
})
|
|
1034
|
+
export class DynamicSampleComponent
|
|
1035
|
+
{
|
|
1036
|
+
protected optionsGatherer: CodeOptionsGatherer<string> = new CodeOptionsGatherer<string>();
|
|
1037
|
+
protected selectField = form(signal<string|null>(null));
|
|
1038
|
+
protected select: Signal<Select<string>> = viewChild.required<Select<string>>('select');
|
|
1039
|
+
|
|
1040
|
+
protected selectOptions: RecursivePartial<SelectOptions<string>> =
|
|
1041
|
+
{
|
|
1042
|
+
plugins:
|
|
1043
|
+
{
|
|
1044
|
+
liveSearch:
|
|
1045
|
+
{
|
|
1046
|
+
type: FilterLiveSearch,
|
|
1047
|
+
},
|
|
1048
|
+
valueHandler:
|
|
1049
|
+
{
|
|
1050
|
+
type: DynamicValueHandler,
|
|
1051
|
+
options: {} as DynamicValueHandlerOptions<string>,
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
optionsGatherer: this.optionsGatherer,
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
constructor(private dataSvc: DataService)
|
|
1058
|
+
{
|
|
1059
|
+
effect(async () =>
|
|
1060
|
+
{
|
|
1061
|
+
const search = this.select().executeAndReturn(getSearch());
|
|
1062
|
+
const result = await this.dataSvc.search(search);
|
|
1063
|
+
|
|
1064
|
+
this.optionsGatherer.setAvailableOptions(
|
|
1065
|
+
result.map(item =>
|
|
1066
|
+
({
|
|
1067
|
+
value: signal(item.id),
|
|
1068
|
+
text: signal(item.name),
|
|
1069
|
+
group: signal(null),
|
|
1070
|
+
})),
|
|
1071
|
+
);
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
```html
|
|
1078
|
+
<!-- dynamic.component.html -->
|
|
1079
|
+
<div>Value: {{ selectField().value() | json }}</div>
|
|
1080
|
+
|
|
1081
|
+
<ng-select #select [selectOptions]="selectOptions" [formField]="selectField"/>
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### Dynamic Option Getter (Object Values)
|
|
1085
|
+
|
|
1086
|
+
When values are objects and you need to resolve them back from a server (e.g., when setting an initial value), use `DynamicValueHandlerOptions.optionGetter`:
|
|
1087
|
+
|
|
1088
|
+
```typescript
|
|
1089
|
+
this.selectOptions =
|
|
1090
|
+
{
|
|
1091
|
+
valueExtractor: itm => itm.value()?.kod ?? '',
|
|
1092
|
+
plugins:
|
|
1093
|
+
{
|
|
1094
|
+
valueHandler:
|
|
1095
|
+
{
|
|
1096
|
+
type: DynamicValueHandler,
|
|
1097
|
+
options:
|
|
1098
|
+
{
|
|
1099
|
+
optionGetter: async (value) =>
|
|
1100
|
+
{
|
|
1101
|
+
const result = await lastValueFrom(dataSvc.getDetail(value));
|
|
1102
|
+
|
|
1103
|
+
return result
|
|
1104
|
+
? {
|
|
1105
|
+
group: signal(null),
|
|
1106
|
+
text: signal(result.name),
|
|
1107
|
+
value: signal({kod: result.kod, label: result.name}),
|
|
1108
|
+
}
|
|
1109
|
+
: null;
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
optionsGatherer: this.optionsGatherer,
|
|
1115
|
+
};
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
### Absolute Popup
|
|
1119
|
+
|
|
1120
|
+
Use the `absolute` directive to render the popup as a direct child of `<body>`, preventing clipping by `overflow: hidden` containers:
|
|
1121
|
+
|
|
1122
|
+
```html
|
|
1123
|
+
<ng-select [formField]="field" absolute>
|
|
1124
|
+
<ng-option value="a" text="A"/>
|
|
1125
|
+
<ng-option value="b" text="B"/>
|
|
1126
|
+
</ng-select>
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### Popover Popup
|
|
1130
|
+
|
|
1131
|
+
Use the native Popover API for popup positioning:
|
|
1132
|
+
|
|
1133
|
+
```typescript
|
|
1134
|
+
import {PopoverPositioner, PopoverPositionerOptions, SelectOptions} from '@anglr/select';
|
|
1135
|
+
import {RecursivePartial} from '@jscrpt/common';
|
|
1136
|
+
|
|
1137
|
+
const selectOptions: RecursivePartial<SelectOptions<string>> =
|
|
1138
|
+
{
|
|
1139
|
+
plugins:
|
|
1140
|
+
{
|
|
1141
|
+
interactions:
|
|
1142
|
+
{
|
|
1143
|
+
options:
|
|
1144
|
+
{
|
|
1145
|
+
handleClickOutside: false,
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
positioner:
|
|
1149
|
+
{
|
|
1150
|
+
type: PopoverPositioner,
|
|
1151
|
+
options:
|
|
1152
|
+
{
|
|
1153
|
+
popoverAuto: true,
|
|
1154
|
+
} as PopoverPositionerOptions,
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
};
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
```html
|
|
1161
|
+
<ng-select [formField]="field" [selectOptions]="selectOptions">
|
|
1162
|
+
<ng-option value="a" text="A"/>
|
|
1163
|
+
</ng-select>
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
### Cancel Value
|
|
1167
|
+
|
|
1168
|
+
Show a cancel/clear button to reset the selected value:
|
|
1169
|
+
|
|
1170
|
+
```typescript
|
|
1171
|
+
const selectOptions: RecursivePartial<SelectOptions<string>> =
|
|
1172
|
+
{
|
|
1173
|
+
plugins:
|
|
1174
|
+
{
|
|
1175
|
+
normalState:
|
|
1176
|
+
{
|
|
1177
|
+
options:
|
|
1178
|
+
{
|
|
1179
|
+
cancelValue: true,
|
|
1180
|
+
},
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
};
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
```html
|
|
1187
|
+
<ng-select [formField]="field" [selectOptions]="selectOptions">
|
|
1188
|
+
<ng-option value="a" text="A"/>
|
|
1189
|
+
<ng-option value="b" text="B"/>
|
|
1190
|
+
</ng-select>
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
### Hide Caret
|
|
1194
|
+
|
|
1195
|
+
In edit mode, hide the dropdown caret:
|
|
1196
|
+
|
|
1197
|
+
```typescript
|
|
1198
|
+
import {EditNormalStateOptions, SelectOptions} from '@anglr/select';
|
|
1199
|
+
|
|
1200
|
+
const selectOptions: RecursivePartial<SelectOptions<string>> =
|
|
1201
|
+
{
|
|
1202
|
+
plugins:
|
|
1203
|
+
{
|
|
1204
|
+
normalState:
|
|
1205
|
+
{
|
|
1206
|
+
options:
|
|
1207
|
+
{
|
|
1208
|
+
carret: false,
|
|
1209
|
+
} as EditNormalStateOptions,
|
|
1210
|
+
},
|
|
1211
|
+
},
|
|
1212
|
+
};
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
```html
|
|
1216
|
+
<ng-select [formField]="field" [selectOptions]="selectOptions" edit>
|
|
1217
|
+
<ng-option value="a" text="A"/>
|
|
1218
|
+
</ng-select>
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
Or use the shorthand directive:
|
|
1222
|
+
|
|
1223
|
+
```html
|
|
1224
|
+
<ng-select [formField]="field" noCarret>
|
|
1225
|
+
<ng-option value="a" text="A"/>
|
|
1226
|
+
</ng-select>
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
### Add New Option
|
|
1230
|
+
|
|
1231
|
+
Allow users to create new options by typing. Configure `newOptionGetter` on the OptionsHandler:
|
|
1232
|
+
|
|
1233
|
+
```typescript
|
|
1234
|
+
const selectOptions: RecursivePartial<SelectOptions<string>> =
|
|
1235
|
+
{
|
|
1236
|
+
plugins:
|
|
1237
|
+
{
|
|
1238
|
+
optionsHandler:
|
|
1239
|
+
{
|
|
1240
|
+
options:
|
|
1241
|
+
{
|
|
1242
|
+
newOptionGetter: (value: string) =>
|
|
1243
|
+
({
|
|
1244
|
+
group: signal(null),
|
|
1245
|
+
text: signal(value),
|
|
1246
|
+
value: signal(value),
|
|
1247
|
+
}),
|
|
1248
|
+
},
|
|
1249
|
+
},
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
```html
|
|
1255
|
+
<ng-select [formField]="field" [selectOptions]="selectOptions" edit>
|
|
1256
|
+
<ng-option value="first" text="First value text"/>
|
|
1257
|
+
<ng-option value="second" text="Second value text"/>
|
|
1258
|
+
</ng-select>
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
### Readonly
|
|
1262
|
+
|
|
1263
|
+
Use Angular signal forms' `readonly()` to toggle readonly state:
|
|
1264
|
+
|
|
1265
|
+
```typescript
|
|
1266
|
+
import {signal} from '@angular/core';
|
|
1267
|
+
import {form, FormField, readonly} from '@angular/forms/signals';
|
|
1268
|
+
|
|
1269
|
+
export class ReadonlySampleComponent
|
|
1270
|
+
{
|
|
1271
|
+
protected readonly = signal(false);
|
|
1272
|
+
protected selectField = form(
|
|
1273
|
+
signal<string|null>(null),
|
|
1274
|
+
path => readonly(path, () => this.readonly()),
|
|
1275
|
+
);
|
|
1276
|
+
}
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
```html
|
|
1280
|
+
<button (click)="readonly.set(!readonly())">
|
|
1281
|
+
{{ readonly() ? 'to normal' : 'to readonly' }}
|
|
1282
|
+
</button>
|
|
1283
|
+
|
|
1284
|
+
<ng-select [formField]="selectField">
|
|
1285
|
+
<ng-option value="a" text="A"/>
|
|
1286
|
+
<ng-option value="b" text="B"/>
|
|
1287
|
+
</ng-select>
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
Or use the `setReadonly()` extension:
|
|
1291
|
+
|
|
1292
|
+
```typescript
|
|
1293
|
+
import {setReadonly} from '@anglr/select/extensions';
|
|
1294
|
+
|
|
1295
|
+
this.select().execute(setReadonly(true));
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
### Custom Readonly State
|
|
1299
|
+
|
|
1300
|
+
Replace the readonly visual entirely with a custom plugin component:
|
|
1301
|
+
|
|
1302
|
+
```typescript
|
|
1303
|
+
this.selectOptions =
|
|
1304
|
+
{
|
|
1305
|
+
plugins:
|
|
1306
|
+
{
|
|
1307
|
+
readonlyState:
|
|
1308
|
+
{
|
|
1309
|
+
type: CustomReadonlyStateComponent,
|
|
1310
|
+
},
|
|
1311
|
+
},
|
|
1312
|
+
};
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
### Styling Override
|
|
1316
|
+
|
|
1317
|
+
Override CSS custom properties in a scoped container:
|
|
1318
|
+
|
|
1319
|
+
```html
|
|
1320
|
+
<div class="select-override">
|
|
1321
|
+
<ng-select [formField]="field">
|
|
1322
|
+
<ng-option value="first" text="First value text"/>
|
|
1323
|
+
<ng-option value="second" text="Second value text"/>
|
|
1324
|
+
</ng-select>
|
|
1325
|
+
</div>
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
```scss
|
|
1329
|
+
.select-override {
|
|
1330
|
+
--select-normalState-borderColor: tomato;
|
|
1331
|
+
--select-normalState-background: lightyellow;
|
|
1332
|
+
--select-normalState-foreground: #333;
|
|
1333
|
+
--select-normalState-focus-borderColor: red;
|
|
1334
|
+
--select-normalState-focus-outlineColor: red;
|
|
1335
|
+
--select-popup-container-background: lightyellow;
|
|
1336
|
+
--select-popup-container-borderColor: tomato;
|
|
1337
|
+
}
|
|
1338
|
+
```
|
|
147
1339
|
|
|
148
1340
|
---
|
|
149
1341
|
|
|
150
|
-
##
|
|
1342
|
+
## License
|
|
151
1343
|
|
|
152
|
-
|
|
153
|
-
|---|---|
|
|
154
|
-
| `SelectModule` | Classic `NgModule` that declares and exports `Select`, `Option`, and `OptionGroup` for apps not using standalone imports. |
|
|
155
|
-
| `SelectEditModule` | `NgModule` that additionally includes the `SelectEdit` directive and its required plugins. |
|
|
1344
|
+
MIT
|