@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.
Files changed (46) hide show
  1. package/.claude/skills/angular-developer/SKILL.md +130 -0
  2. package/.claude/skills/angular-developer/references/angular-animations.md +160 -0
  3. package/.claude/skills/angular-developer/references/angular-aria.md +410 -0
  4. package/.claude/skills/angular-developer/references/cli.md +86 -0
  5. package/.claude/skills/angular-developer/references/component-harnesses.md +59 -0
  6. package/.claude/skills/angular-developer/references/component-styling.md +91 -0
  7. package/.claude/skills/angular-developer/references/components.md +117 -0
  8. package/.claude/skills/angular-developer/references/creating-services.md +97 -0
  9. package/.claude/skills/angular-developer/references/data-resolvers.md +69 -0
  10. package/.claude/skills/angular-developer/references/define-routes.md +67 -0
  11. package/.claude/skills/angular-developer/references/defining-providers.md +72 -0
  12. package/.claude/skills/angular-developer/references/di-fundamentals.md +120 -0
  13. package/.claude/skills/angular-developer/references/e2e-testing.md +66 -0
  14. package/.claude/skills/angular-developer/references/effects.md +83 -0
  15. package/.claude/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  16. package/.claude/skills/angular-developer/references/host-elements.md +80 -0
  17. package/.claude/skills/angular-developer/references/injection-context.md +63 -0
  18. package/.claude/skills/angular-developer/references/inputs.md +101 -0
  19. package/.claude/skills/angular-developer/references/linked-signal.md +59 -0
  20. package/.claude/skills/angular-developer/references/loading-strategies.md +61 -0
  21. package/.claude/skills/angular-developer/references/mcp.md +106 -0
  22. package/.claude/skills/angular-developer/references/migrations.md +30 -0
  23. package/.claude/skills/angular-developer/references/navigate-to-routes.md +69 -0
  24. package/.claude/skills/angular-developer/references/outputs.md +86 -0
  25. package/.claude/skills/angular-developer/references/reactive-forms.md +122 -0
  26. package/.claude/skills/angular-developer/references/rendering-strategies.md +44 -0
  27. package/.claude/skills/angular-developer/references/resource.md +77 -0
  28. package/.claude/skills/angular-developer/references/route-animations.md +56 -0
  29. package/.claude/skills/angular-developer/references/route-guards.md +52 -0
  30. package/.claude/skills/angular-developer/references/router-lifecycle.md +45 -0
  31. package/.claude/skills/angular-developer/references/router-testing.md +87 -0
  32. package/.claude/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  33. package/.claude/skills/angular-developer/references/signal-forms.md +897 -0
  34. package/.claude/skills/angular-developer/references/signals-overview.md +94 -0
  35. package/.claude/skills/angular-developer/references/tailwind-css.md +69 -0
  36. package/.claude/skills/angular-developer/references/template-driven-forms.md +114 -0
  37. package/.claude/skills/angular-developer/references/testing-fundamentals.md +66 -0
  38. package/.github/instructions/typescript/code-conventions.md +8 -0
  39. package/.github/instructions/typescript/comments.md +41 -0
  40. package/.github/instructions/typescript/formatting.md +42 -0
  41. package/.github/instructions/typescript/naming-conventions.md +7 -0
  42. package/.github/instructions/typescript.instructions.md +26 -0
  43. package/package.json +1 -1
  44. package/readme.md +1288 -99
  45. package/version.bak +1 -1
  46. package/.github/copilot-instructions.md +0 -1
package/readme.md CHANGED
@@ -1,155 +1,1344 @@
1
1
  [![npm version](https://badge.fury.io/js/%40anglr%2Fselect.svg)](https://badge.fury.io/js/%40anglr%2Fselect)
2
2
  [![Build status](https://ci.appveyor.com/api/projects/status/rib22utc0ap6vrxc?svg=true)](https://ci.appveyor.com/project/kukjevov/ng-select)
3
3
 
4
- # Angular Select
4
+ # @anglr/select
5
5
 
6
- - [API](https://ressurectit.github.io/#/content/api/ng-select/select)
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
- # `@anglr/select` — Source Overview
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
- 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.
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
- ## Directory Structure
58
+ ## Installation
17
59
 
60
+ ```bash
61
+ npm install @anglr/select
18
62
  ```
19
- src/
20
- ├── components/ # ng-select, ng-option, ng-option-group
21
- ├── decorators/ # MergeOptionsAsSignal property decorator
22
- ├── directives/ # Structural/behavioural directives
23
- ├── interfaces/ # TypeScript contracts for all public APIs
24
- ├── misc/ # Enums, types, tokens, providers, utils, classes
25
- ├── modules/ # NgModule wrappers
26
- ├── pipes/ # Template pipes
27
- └── plugins/ # Nine swappable plugin slots with implementations
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
- | Selector | Class | Purpose |
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
- | `ng-select` | `Select<TValue>` | Core select/dropdown component. Hosts all plugins, manages CSS classes and plugin lifecycle. |
37
- | `ng-option` | `Option<TValue>` | Declarative option element. Holds `value` and `text` input signals plus `active`/`selected` state. |
38
- | `ng-option-group` | `OptionGroup` | Declarative option group element. Holds a `text` input and an optional custom group-header template. |
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
- | Selector | Class | Purpose |
264
+ | Directive | Selector | Description |
45
265
  |---|---|---|
46
- | `[normalStateTemplate]` | `NormalStateTemplate` | Custom template for the select's closed/normal state. |
47
- | `[normalStateTagTemplate]` | `NormalStateTagTemplate` | Custom per-tag template in multi-select normal state. |
48
- | `[optionGroupTemplate]` | `OptionGroupTemplate` | Custom group-header template inside `ng-option-group`. |
49
- | `[optionTemplate]` | `OptionTemplate` | Custom template for each option in the popup dropdown. |
50
- | `ng-select[absolute]` | `SelectAbsolute` | Appends the popup to `document.body` instead of inline. |
51
- | `ng-select[formControlName\|formControl\|ngModel]` | `SelectControlValueAccessor` | Bridges `ng-select` to Angular's `ControlValueAccessor`. |
52
- | `ng-select[edit]` | `SelectEdit` | Switches to editable/type-ahead mode (`EditNormalState` + `EditLiveSearch`). |
53
- | `ng-select[formField]` | `SelectFormControl` | Integrates with signal-based forms (`FormValueControl`). |
54
- | `ng-select[multiple]` | `SelectMultipleKeepPopup` | Keeps popup open after each selection (`closeOnSelect = false`). |
55
- | `ng-select[noCarret]` | `SelectNoCarret` | Hides the dropdown caret arrow. |
56
- | `ng-select[placeholder]` | `SelectPlaceholder` | Reactively sets the placeholder text via a signal input. |
57
- | `ng-select[withDirectAccess]` | `WithDirectAccess` | Exposes a two-way `value` model signal for template binding without a form control. |
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
- The select behaviour is split into nine independently swappable plugin slots:
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
- | `SelectPluginType` | Default | Purpose |
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
- | `Interactions` | `SimpleInteractions` | User interaction logic (open/close, option click). |
68
- | `KeyboardHandler` | `SimpleKeyboardHandler` | Keyboard navigation and shortcut handling. |
69
- | `LiveSearch` | `NoLiveSearch` | Live filtering of options as the user types. |
70
- | `NormalState` | `SimpleNormalState` | Renders the select's closed/normal state. |
71
- | `OptionsHandler` | `SimpleOptionsHandler` | Manages the available options list and filtering. |
72
- | `Popup` | `SimplePopup` | Renders the dropdown popup. |
73
- | `Positioner` | `CommonPositioner` | Positions the popup relative to the host element. |
74
- | `ReadonlyState` | `SimpleNormalState` | Renders the select when `readonly = true`. |
75
- | `ValueHandler` | `StaticValueHandler` | Stores, sets, and reads the current selected value. |
76
-
77
- Swap a plugin by providing a different type via one of the provider factories (e.g. `provideNormalStateType(MyNormalState)`).
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 name | Class | Purpose |
505
+ | Pipe | Pure | Description |
84
506
  |---|---|---|
85
- | `addNewOption` | `AddNewOption` | Returns the "add new option" prefix text for a synthetic option. |
86
- | `displayValue` | `DisplayValue` | Transforms a selected option (or array) into a display string. |
87
- | `getPlugin` | `GetPlugin` | Retrieves a typed plugin instance from the `selectPlugins` map. |
88
- | `groupedListOptions` | `GroupedListOptions` | Converts a flat `SelectOptionState[]` into grouped tuples for rendering. |
89
- | `hasValue` | `HasValue` | Returns `true` when an option or array of options constitutes a selected value. |
90
- | `optionCssClasses` | `OptionCssClasses` | Returns the CSS class array for a popup option (selected, active, grouped). |
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
- ## Interfaces
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
- ## Misc
626
+ ### Building a Custom Theme
116
627
 
117
- ### `enums.ts`
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
- ### `types.ts`
121
- Function and action aliases used throughout the library, including `SelectAction`, `SelectFunction`, `DisplayTextFunc`, `ValueEqualityFunc`, `TextComparerFunc`, `OptionGetterFunc`, and `NewOptionGetterFunc`.
630
+ ```scss
631
+ @use '@anglr/select/styles/core/defaultTheme' as selectDefault;
632
+ @use '@anglr/select/styles/core/mixins' as selectMixins;
122
633
 
123
- ### `tokens.ts`
124
- One `InjectionToken` per plugin type and its options object, e.g. `SELECT_OPTIONS`, `NORMAL_STATE_TYPE`, `NORMAL_STATE_OPTIONS`.
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
- ### `providers.ts`
127
- Convenience provider factories that wrap each token: `provideSelectOptions()`, `provideNormalStateType()`, `providePopupType()`, etc.
658
+ @include selectMixins.buildTheme($customTheme);
659
+ ```
660
+
661
+ ### Overriding Styles in Components
128
662
 
129
- ### `utils.ts`
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
- ### `misc/classes/`
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
- | Class | Purpose |
135
- |---|---|
136
- | `SelectBus` | Central reactive bus (signals) shared between the select and all plugins. |
137
- | `SelectPluginInstances` | Live plugin references keyed by `SelectPluginType`. |
138
- | `CodeOptionsGatherer` | `OptionsGatherer` implementation for supplying options programmatically. |
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
- ## Decorators
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
- | Decorator | Purpose |
145
- |---|---|
146
- | `MergeOptionsAsSignal` | Property decorator that creates a `WritableSignal` and deep-merges partial options objects via `deepCopyWithArrayOverride`. Used on `Select.selectOptions` to make options reactive. |
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
+ =&gt; {{ normalState.selectBus.selectedOptions() | displayValue }} &lt;=
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
- ## Modules
1342
+ ## License
151
1343
 
152
- | Module | Purpose |
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