@egose/shadcn-theme-ng-tw 0.0.28 → 0.2.0
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/README.md +130 -13
- package/accordion/README.md +376 -34
- package/accordion/fesm2022/accordion.mjs +20 -20
- package/alert/README.md +343 -34
- package/alert/fesm2022/alert.mjs +18 -18
- package/alert-dialog/README.md +471 -5
- package/alert-dialog/fesm2022/alert-dialog.mjs +48 -48
- package/alert-dialog/types/alert-dialog.d.ts +1 -1
- package/aspect-ratio/README.md +272 -5
- package/aspect-ratio/fesm2022/aspect-ratio.mjs +9 -9
- package/autocomplete/README.md +473 -34
- package/autocomplete/fesm2022/autocomplete.mjs +54 -54
- package/autocomplete/types/autocomplete.d.ts +1 -1
- package/avatar/README.md +357 -5
- package/avatar/fesm2022/avatar.mjs +32 -32
- package/badge/README.md +289 -34
- package/badge/fesm2022/badge.mjs +4 -4
- package/basic-alert/README.md +324 -34
- package/basic-alert/fesm2022/basic-alert.mjs +4 -4
- package/basic-alert/types/basic-alert.d.ts +1 -1
- package/breadcrumb/README.md +406 -5
- package/breadcrumb/fesm2022/breadcrumb.mjs +34 -34
- package/button/README.md +453 -34
- package/button/fesm2022/button.mjs +96 -118
- package/button/types/button.d.ts +7 -10
- package/button-group/README.md +318 -5
- package/button-group/fesm2022/button-group.mjs +20 -20
- package/calendar/README.md +328 -34
- package/calendar/fesm2022/calendar.mjs +154 -154
- package/card/README.md +331 -5
- package/card/fesm2022/card.mjs +33 -33
- package/carousel/README.md +333 -5
- package/carousel/fesm2022/carousel.mjs +47 -44
- package/carousel/types/carousel.d.ts +1 -1
- package/checkbox/README.md +291 -34
- package/checkbox/fesm2022/checkbox.mjs +22 -20
- package/checkbox/types/checkbox.d.ts +5 -4
- package/collapsible/README.md +332 -5
- package/collapsible/fesm2022/collapsible.mjs +15 -15
- package/combobox/README.md +507 -5
- package/combobox/fesm2022/combobox.mjs +95 -92
- package/combobox/types/combobox.d.ts +1 -1
- package/command/README.md +435 -5
- package/command/fesm2022/command.mjs +56 -56
- package/confirmation-dialog/README.md +272 -34
- package/confirmation-dialog/fesm2022/confirmation-dialog.mjs +13 -12
- package/confirmation-dialog/types/confirmation-dialog.d.ts +9 -5
- package/context-menu/README.md +366 -5
- package/context-menu/fesm2022/context-menu.mjs +9 -9
- package/date-picker/README.md +436 -34
- package/date-picker/fesm2022/date-picker.mjs +79 -68
- package/date-picker/types/date-picker.d.ts +8 -4
- package/dialog/README.md +419 -34
- package/dialog/fesm2022/dialog.mjs +48 -48
- package/drawer/README.md +395 -5
- package/drawer/fesm2022/drawer.mjs +45 -45
- package/dropdown-menu/README.md +417 -5
- package/dropdown-menu/fesm2022/dropdown-menu.mjs +85 -73
- package/dropdown-menu/types/dropdown-menu.d.ts +4 -0
- package/empty/README.md +329 -5
- package/empty/fesm2022/empty.mjs +31 -31
- package/field/README.md +385 -5
- package/field/fesm2022/field.mjs +59 -59
- package/field/types/field.d.ts +1 -1
- package/form-checkbox/README.md +283 -34
- package/form-checkbox/fesm2022/form-checkbox.mjs +37 -19
- package/form-checkbox/types/form-checkbox.d.ts +7 -1
- package/form-date-picker/README.md +293 -34
- package/form-date-picker/fesm2022/form-date-picker.mjs +59 -24
- package/form-date-picker/types/form-date-picker.d.ts +7 -1
- package/form-field/README.md +327 -34
- package/form-field/fesm2022/form-field.mjs +30 -16
- package/form-field/types/form-field.d.ts +28 -20
- package/form-field-simple/README.md +311 -34
- package/form-field-simple/fesm2022/form-field-simple.mjs +4 -4
- package/form-searchable-multiselect/README.md +332 -34
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +41 -21
- package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +8 -2
- package/form-select/README.md +321 -34
- package/form-select/fesm2022/form-select.mjs +62 -29
- package/form-select/types/form-select.d.ts +7 -1
- package/form-text-input/README.md +342 -34
- package/form-text-input/fesm2022/form-text-input.mjs +43 -55
- package/form-text-input/types/form-text-input.d.ts +9 -12
- package/form-textarea/README.md +318 -34
- package/form-textarea/fesm2022/form-textarea.mjs +42 -23
- package/form-textarea/types/form-textarea.d.ts +8 -1
- package/hover-card/README.md +256 -5
- package/hover-card/fesm2022/hover-card.mjs +19 -19
- package/icon/README.md +210 -34
- package/icon/fesm2022/icon.mjs +8 -8
- package/input/README.md +240 -34
- package/input/fesm2022/input.mjs +17 -12
- package/input/types/input.d.ts +2 -1
- package/input-group/README.md +335 -5
- package/input-group/fesm2022/input-group.mjs +37 -37
- package/input-group/types/input-group.d.ts +3 -3
- package/input-otp/README.md +375 -5
- package/input-otp/fesm2022/input-otp.mjs +27 -27
- package/item/README.md +385 -5
- package/item/fesm2022/item.mjs +54 -54
- package/kbd/README.md +291 -5
- package/kbd/fesm2022/kbd.mjs +13 -13
- package/label/README.md +243 -34
- package/label/fesm2022/label.mjs +9 -9
- package/label/types/label.d.ts +1 -1
- package/layout-simple/README.md +174 -44
- package/layout-simple/fesm2022/layout-simple.mjs +917 -398
- package/layout-simple/types/layout-simple.d.ts +180 -129
- package/menu/README.md +418 -0
- package/menu/fesm2022/menu.mjs +461 -0
- package/menu/package.json +24 -0
- package/menu/types/menu.d.ts +137 -0
- package/menubar/README.md +343 -5
- package/menubar/fesm2022/menubar.mjs +13 -13
- package/native-select/README.md +323 -5
- package/native-select/fesm2022/native-select.mjs +19 -19
- package/navigation-menu/README.md +369 -5
- package/navigation-menu/fesm2022/navigation-menu.mjs +37 -37
- package/package.json +183 -154
- package/pagination/README.md +388 -5
- package/pagination/fesm2022/pagination.mjs +63 -63
- package/popover/README.md +302 -34
- package/popover/fesm2022/popover.mjs +30 -30
- package/progress/README.md +311 -5
- package/progress/fesm2022/progress.mjs +13 -13
- package/radio-group/README.md +335 -34
- package/radio-group/fesm2022/radio-group.mjs +26 -19
- package/resizable/README.md +269 -5
- package/resizable/fesm2022/resizable.mjs +19 -19
- package/scroll-area/README.md +233 -5
- package/scroll-area/fesm2022/scroll-area.mjs +9 -9
- package/searchable-multiselect/README.md +294 -34
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +63 -16
- package/searchable-multiselect/types/searchable-multiselect.d.ts +8 -2
- package/select/README.md +408 -34
- package/select/fesm2022/select.mjs +80 -72
- package/select/types/select.d.ts +3 -1
- package/separator/README.md +193 -34
- package/separator/fesm2022/separator.mjs +9 -9
- package/separator/types/separator.d.ts +1 -1
- package/sheet/README.md +282 -34
- package/sheet/fesm2022/sheet.mjs +30 -30
- package/sidebar/README.md +457 -5
- package/sidebar/fesm2022/sidebar.mjs +136 -134
- package/sidebar/types/sidebar.d.ts +3 -3
- package/skeleton/README.md +217 -5
- package/skeleton/fesm2022/skeleton.mjs +9 -9
- package/slider/README.md +273 -5
- package/slider/fesm2022/slider.mjs +29 -25
- package/sonner/README.md +317 -34
- package/sonner/fesm2022/sonner.mjs +9 -9
- package/spinner/README.md +255 -34
- package/spinner/fesm2022/spinner.mjs +8 -8
- package/switch/README.md +281 -34
- package/switch/fesm2022/switch.mjs +12 -14
- package/table/README.md +423 -5
- package/table/fesm2022/table.mjs +41 -41
- package/tabs/README.md +382 -34
- package/tabs/fesm2022/tabs.mjs +48 -38
- package/textarea/README.md +282 -5
- package/textarea/fesm2022/textarea.mjs +9 -9
- package/toggle/README.md +270 -5
- package/toggle/fesm2022/toggle.mjs +14 -14
- package/toggle-group/README.md +340 -5
- package/toggle-group/fesm2022/toggle-group.mjs +13 -13
- package/toggle-group/types/toggle-group.d.ts +13 -7
- package/tooltip/README.md +240 -34
- package/tooltip/fesm2022/tooltip.mjs +14 -14
- package/typography/README.md +271 -5
- package/typography/fesm2022/typography.mjs +53 -53
- package/typography/types/typography.d.ts +12 -12
- package/utils/README.md +274 -34
- package/utils/fesm2022/utils.mjs +68 -44
- package/accordion/fesm2022/accordion.mjs.map +0 -1
- package/alert/fesm2022/alert.mjs.map +0 -1
- package/alert-dialog/fesm2022/alert-dialog.mjs.map +0 -1
- package/aspect-ratio/fesm2022/aspect-ratio.mjs.map +0 -1
- package/autocomplete/fesm2022/autocomplete.mjs.map +0 -1
- package/avatar/fesm2022/avatar.mjs.map +0 -1
- package/badge/fesm2022/badge.mjs.map +0 -1
- package/basic-alert/fesm2022/basic-alert.mjs.map +0 -1
- package/breadcrumb/fesm2022/breadcrumb.mjs.map +0 -1
- package/button/fesm2022/button.mjs.map +0 -1
- package/button-group/fesm2022/button-group.mjs.map +0 -1
- package/calendar/fesm2022/calendar.mjs.map +0 -1
- package/card/fesm2022/card.mjs.map +0 -1
- package/carousel/fesm2022/carousel.mjs.map +0 -1
- package/checkbox/fesm2022/checkbox.mjs.map +0 -1
- package/collapsible/fesm2022/collapsible.mjs.map +0 -1
- package/combobox/fesm2022/combobox.mjs.map +0 -1
- package/command/fesm2022/command.mjs.map +0 -1
- package/confirmation-dialog/fesm2022/confirmation-dialog.mjs.map +0 -1
- package/context-menu/fesm2022/context-menu.mjs.map +0 -1
- package/date-picker/fesm2022/date-picker.mjs.map +0 -1
- package/dialog/fesm2022/dialog.mjs.map +0 -1
- package/drawer/fesm2022/drawer.mjs.map +0 -1
- package/dropdown-menu/fesm2022/dropdown-menu.mjs.map +0 -1
- package/empty/fesm2022/empty.mjs.map +0 -1
- package/field/fesm2022/field.mjs.map +0 -1
- package/form-checkbox/fesm2022/form-checkbox.mjs.map +0 -1
- package/form-date-picker/fesm2022/form-date-picker.mjs.map +0 -1
- package/form-field/fesm2022/form-field.mjs.map +0 -1
- package/form-field-simple/fesm2022/form-field-simple.mjs.map +0 -1
- package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs.map +0 -1
- package/form-select/fesm2022/form-select.mjs.map +0 -1
- package/form-text-input/fesm2022/form-text-input.mjs.map +0 -1
- package/form-textarea/fesm2022/form-textarea.mjs.map +0 -1
- package/hover-card/fesm2022/hover-card.mjs.map +0 -1
- package/icon/fesm2022/icon.mjs.map +0 -1
- package/input/fesm2022/input.mjs.map +0 -1
- package/input-group/fesm2022/input-group.mjs.map +0 -1
- package/input-otp/fesm2022/input-otp.mjs.map +0 -1
- package/item/fesm2022/item.mjs.map +0 -1
- package/kbd/fesm2022/kbd.mjs.map +0 -1
- package/label/fesm2022/label.mjs.map +0 -1
- package/layout-simple/fesm2022/layout-simple.mjs.map +0 -1
- package/menubar/fesm2022/menubar.mjs.map +0 -1
- package/native-select/fesm2022/native-select.mjs.map +0 -1
- package/navigation-menu/fesm2022/navigation-menu.mjs.map +0 -1
- package/pagination/fesm2022/pagination.mjs.map +0 -1
- package/popover/fesm2022/popover.mjs.map +0 -1
- package/progress/fesm2022/progress.mjs.map +0 -1
- package/radio-group/fesm2022/radio-group.mjs.map +0 -1
- package/resizable/fesm2022/resizable.mjs.map +0 -1
- package/scroll-area/fesm2022/scroll-area.mjs.map +0 -1
- package/searchable-multiselect/fesm2022/searchable-multiselect.mjs.map +0 -1
- package/select/fesm2022/select.mjs.map +0 -1
- package/separator/fesm2022/separator.mjs.map +0 -1
- package/sheet/fesm2022/sheet.mjs.map +0 -1
- package/sidebar/fesm2022/sidebar.mjs.map +0 -1
- package/skeleton/fesm2022/skeleton.mjs.map +0 -1
- package/slider/fesm2022/slider.mjs.map +0 -1
- package/sonner/fesm2022/sonner.mjs.map +0 -1
- package/spinner/fesm2022/spinner.mjs.map +0 -1
- package/switch/fesm2022/switch.mjs.map +0 -1
- package/table/fesm2022/table.mjs.map +0 -1
- package/tabs/fesm2022/tabs.mjs.map +0 -1
- package/textarea/fesm2022/textarea.mjs.map +0 -1
- package/toggle/fesm2022/toggle.mjs.map +0 -1
- package/toggle-group/fesm2022/toggle-group.mjs.map +0 -1
- package/tooltip/fesm2022/tooltip.mjs.map +0 -1
- package/typography/fesm2022/typography.mjs.map +0 -1
- package/utils/fesm2022/utils.mjs.map +0 -1
|
@@ -1,64 +1,324 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Searchable Multiselect (`@egose/shadcn-theme-ng/searchable-multiselect`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A shadcn/ui-style **multi-select with chips + popover picker** — selected values render as removable chips, and a popover holds the checkbox option list. There is no exact single shadcn/ui counterpart; it composes the `Popover`, `Button`, and `Checkbox` patterns into one opinionated control.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It is a **standalone `ControlValueAccessor` component** (`EgSearchableMultiselect`), so it binds directly to Angular reactive forms (`formControlName`) and template-driven forms (`ngModel`) with a `string[]` value. Internally it reuses `HlmPopover`/`HlmCheckbox`/`HlmButton` — you do not import those yourself.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> **Ships as:** `@egose/shadcn-theme-ng/searchable-multiselect` and `@egose/shadcn-theme-ng-tw/searchable-multiselect` (the `tw:`-prefixed Tailwind variant — same API, class strings prefixed with `tw:`).
|
|
8
|
+
> See the [package README](../../README.md) for installation, peer dependencies, and Tailwind setup. Do not publish this project directory independently.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
|
-
|
|
13
|
+
# Plain Tailwind (no prefix)
|
|
14
|
+
npm install @egose/shadcn-theme-ng
|
|
15
|
+
|
|
16
|
+
# Or the tw:-prefixed variant
|
|
17
|
+
npm install @egose/shadcn-theme-ng-tw
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
22
|
+
// tw variant:
|
|
23
|
+
// import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng-tw/searchable-multiselect';
|
|
11
24
|
```
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
Peer dependencies (see [package README](../../README.md) for versions): `@angular/core`, `@angular/common`. (No `@spartan-ng/brain` peer — popover/checkbox/button come along as regular library dependencies.)
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
## Imports
|
|
29
|
+
|
|
30
|
+
Real exported symbols (from `src/public-api.ts`):
|
|
31
|
+
|
|
32
|
+
| Symbol | Kind | Description |
|
|
33
|
+
| ------------------------- | -------------------------------------------------- | ---------------------------------------------------------- |
|
|
34
|
+
| `EgSearchableMultiselect` | Standalone component (`eg-searchable-multiselect`) | The whole control; provides `NG_VALUE_ACCESSOR` for itself |
|
|
35
|
+
| `SelectOption` | Interface | `{ label: string; value: string }` |
|
|
36
|
+
|
|
37
|
+
> Note: unlike most subpaths, this one exposes **no `*Imports` array and no `*Module`**. Import `EgSearchableMultiselect` directly — it is standalone.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Component } from '@angular/core';
|
|
41
|
+
import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
42
|
+
|
|
43
|
+
@Component({
|
|
44
|
+
selector: 'app-demo',
|
|
45
|
+
standalone: true,
|
|
46
|
+
imports: [EgSearchableMultiselect],
|
|
47
|
+
template: ` <eg-searchable-multiselect [options]="options" [(value)]="selected" /> `,
|
|
48
|
+
})
|
|
49
|
+
export class DemoComponent {}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Anatomy / Structure
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<eg-searchable-multiselect [options]="options" placeholder="Pick frameworks…" [(value)]="selected" />
|
|
17
56
|
```
|
|
18
57
|
|
|
19
|
-
|
|
58
|
+
Renders (internally — you do not write this yourself):
|
|
20
59
|
|
|
21
|
-
|
|
60
|
+
```html
|
|
61
|
+
<eg-searchable-multiselect>
|
|
62
|
+
<!-- chip row: placeholder pill when empty, removable chips otherwise -->
|
|
63
|
+
<div>
|
|
64
|
+
<span><!-- {{ placeholder }} or chip {{ item.label }} + ✕ button --></span>
|
|
65
|
+
</div>
|
|
22
66
|
|
|
23
|
-
|
|
24
|
-
|
|
67
|
+
<!-- popover trigger + checkbox list -->
|
|
68
|
+
<hlm-popover>
|
|
69
|
+
<button hlmPopoverTrigger hlmButton>N selected</button>
|
|
70
|
+
<hlm-popover-content>
|
|
71
|
+
<label><!-- <hlm-checkbox> per option + label text --></label>
|
|
72
|
+
</hlm-popover-content>
|
|
73
|
+
</hlm-popover>
|
|
74
|
+
</eg-searchable-multiselect>
|
|
25
75
|
```
|
|
26
76
|
|
|
27
|
-
|
|
77
|
+
Real selector: `eg-searchable-multiselect` (element). The `✕` chip buttons and popover trigger honor the disabled state; the popover panel is `tw:w-64` with a `tw:max-h-60` scrolling option list.
|
|
28
78
|
|
|
29
|
-
|
|
79
|
+
## API reference
|
|
30
80
|
|
|
31
|
-
|
|
81
|
+
### EgSearchableMultiselect (component, `ControlValueAccessor`)
|
|
32
82
|
|
|
33
|
-
|
|
83
|
+
| Input | Type | Default | Description |
|
|
84
|
+
| --------------------- | --------------------- | ------------------------ | -------------------------------------------------------------------------------- |
|
|
85
|
+
| `options` | `SelectOption[]` | `[]` | Full option list (`{ label, value }`) |
|
|
86
|
+
| `value` | `string[]` | `[]` | Selected values (one-way in; pairs with `valueChange` for two-way `[(value)]`) |
|
|
87
|
+
| `placeholder` | `string` | `'Start typing to add…'` | Text of the pill shown when nothing is selected |
|
|
88
|
+
| `id` | `string` | `''` | `id` placed on the trigger button |
|
|
89
|
+
| `disabled` | `boolean` | `false` | Disables chips + trigger + checkboxes |
|
|
90
|
+
| `wrapperDisabled` | `boolean` | `false` | Second disable flag (e.g. set by wrapper form components); OR-ed with `disabled` |
|
|
91
|
+
| `ariaLabel` | `string \| undefined` | `undefined` | `aria-label` for the trigger button |
|
|
92
|
+
| `ariaDescribedby` | `string \| null` | `null` | `aria-describedby` for the trigger button |
|
|
93
|
+
| `class` (`userClass`) | `ClassValue` | `''` | Extra classes on the host |
|
|
34
94
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
95
|
+
| Output | Type | Description |
|
|
96
|
+
| ------------- | ---------- | ---------------------------------------------------- |
|
|
97
|
+
| `valueChange` | `string[]` | Emitted with the new value array on every add/remove |
|
|
38
98
|
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npm publish
|
|
42
|
-
```
|
|
99
|
+
`ControlValueAccessor` contract: `writeValue(values)`, `registerOnChange`, `registerOnTouched`, `setDisabledState` are implemented, so `formControl` / `formControlName` / `ngModel` all work. Effective disabled state = `disabled() \|\| wrapperDisabled() \|\| formDisabled()` (the last set by forms via `setDisabledState`).
|
|
43
100
|
|
|
44
|
-
|
|
101
|
+
API surprise worth knowing: despite the "searchable" name, the current template ships **no filter text field** — the popover shows the full checkbox list and empty state reads "No options". Treat `options` as the complete visible list (filter it yourself before passing it in if you need search). Also `value` is a plain `input`, not a `model`: form writes flow through `writeValue`, and user edits flow out through `valueChange` + the CVA `onChange` callback.
|
|
45
102
|
|
|
46
|
-
|
|
103
|
+
## Examples
|
|
47
104
|
|
|
48
|
-
|
|
49
|
-
|
|
105
|
+
### 1. Basic two-way binding
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Component, signal } from '@angular/core';
|
|
109
|
+
import { EgSearchableMultiselect, type SelectOption } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
110
|
+
|
|
111
|
+
@Component({
|
|
112
|
+
selector: 'app-basic-multi',
|
|
113
|
+
standalone: true,
|
|
114
|
+
imports: [EgSearchableMultiselect],
|
|
115
|
+
template: `
|
|
116
|
+
<eg-searchable-multiselect [options]="frameworks" placeholder="Pick frameworks…" [(value)]="selected" />
|
|
117
|
+
<p>Selected: {{ selected().join(', ') || 'none' }}</p>
|
|
118
|
+
`,
|
|
119
|
+
})
|
|
120
|
+
export class BasicMultiComponent {
|
|
121
|
+
readonly frameworks: SelectOption[] = [
|
|
122
|
+
{ label: 'Angular', value: 'angular' },
|
|
123
|
+
{ label: 'React', value: 'react' },
|
|
124
|
+
{ label: 'Vue', value: 'vue' },
|
|
125
|
+
{ label: 'Svelte', value: 'svelte' },
|
|
126
|
+
];
|
|
127
|
+
readonly selected = signal<string[]>(['angular']);
|
|
128
|
+
}
|
|
50
129
|
```
|
|
51
130
|
|
|
52
|
-
|
|
131
|
+
### 2. Reactive forms
|
|
53
132
|
|
|
54
|
-
|
|
133
|
+
```ts
|
|
134
|
+
import { Component } from '@angular/core';
|
|
135
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
136
|
+
import { EgSearchableMultiselect, type SelectOption } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
55
137
|
|
|
56
|
-
|
|
57
|
-
|
|
138
|
+
@Component({
|
|
139
|
+
selector: 'app-reactive-multi',
|
|
140
|
+
standalone: true,
|
|
141
|
+
imports: [EgSearchableMultiselect, ReactiveFormsModule],
|
|
142
|
+
template: `
|
|
143
|
+
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
144
|
+
<label for="skills">Skills (pick at least one)</label>
|
|
145
|
+
<eg-searchable-multiselect id="skills" [options]="skills" formControlName="skillIds" />
|
|
146
|
+
@if (form.controls.skillIds.invalid && form.controls.skillIds.touched) {
|
|
147
|
+
<p class="tw:text-destructive tw:text-sm">Choose at least one skill.</p>
|
|
148
|
+
}
|
|
149
|
+
<button type="submit">Save</button>
|
|
150
|
+
</form>
|
|
151
|
+
`,
|
|
152
|
+
})
|
|
153
|
+
export class ReactiveMultiComponent {
|
|
154
|
+
readonly skills: SelectOption[] = [
|
|
155
|
+
{ label: 'TypeScript', value: 'ts' },
|
|
156
|
+
{ label: 'CSS', value: 'css' },
|
|
157
|
+
{ label: 'Testing', value: 'testing' },
|
|
158
|
+
];
|
|
159
|
+
readonly form = new FormGroup({
|
|
160
|
+
skillIds: new FormControl<string[]>(['ts'], { validators: Validators.required }),
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
submit(): void {
|
|
164
|
+
this.form.markAllAsTouched();
|
|
165
|
+
console.log(this.form.value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
58
168
|
```
|
|
59
169
|
|
|
60
|
-
|
|
170
|
+
### 3. Template-driven forms (`ngModel`)
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { Component } from '@angular/core';
|
|
174
|
+
import { FormsModule } from '@angular/forms';
|
|
175
|
+
import { EgSearchableMultiselect, type SelectOption } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
176
|
+
|
|
177
|
+
@Component({
|
|
178
|
+
selector: 'app-ngmodel-multi',
|
|
179
|
+
standalone: true,
|
|
180
|
+
imports: [EgSearchableMultiselect, FormsModule],
|
|
181
|
+
template: `
|
|
182
|
+
<eg-searchable-multiselect name="tags" [options]="tags" [(ngModel)]="selected" #tagsCtrl="ngModel" />
|
|
183
|
+
@if (tagsCtrl.touched && !selected.length) {
|
|
184
|
+
<p class="tw:text-destructive tw:text-sm">Pick at least one tag.</p>
|
|
185
|
+
}
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
export class NgModelMultiComponent {
|
|
189
|
+
readonly tags: SelectOption[] = [
|
|
190
|
+
{ label: 'Bug', value: 'bug' },
|
|
191
|
+
{ label: 'Feature', value: 'feature' },
|
|
192
|
+
{ label: 'Docs', value: 'docs' },
|
|
193
|
+
];
|
|
194
|
+
selected: string[] = [];
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 4. Disabled / read-only states
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { Component } from '@angular/core';
|
|
202
|
+
import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
203
|
+
|
|
204
|
+
@Component({
|
|
205
|
+
selector: 'app-disabled-multi',
|
|
206
|
+
standalone: true,
|
|
207
|
+
imports: [EgSearchableMultiselect],
|
|
208
|
+
template: `
|
|
209
|
+
<!-- fully disabled: chips, ✕ buttons, trigger, checkboxes -->
|
|
210
|
+
<eg-searchable-multiselect [options]="options" [value]="['a']" disabled />
|
|
211
|
+
|
|
212
|
+
<!-- wrapper-driven disable (e.g. parent form section locked) -->
|
|
213
|
+
<eg-searchable-multiselect [options]="options" [value]="['a']" [wrapperDisabled]="locked" />
|
|
214
|
+
`,
|
|
215
|
+
})
|
|
216
|
+
export class DisabledMultiComponent {
|
|
217
|
+
readonly locked = true;
|
|
218
|
+
readonly options = [
|
|
219
|
+
{ label: 'Alpha', value: 'a' },
|
|
220
|
+
{ label: 'Beta', value: 'b' },
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Disabling via `formControl.disable()` works too — it flows through `setDisabledState`.
|
|
226
|
+
|
|
227
|
+
### 5. Client-side search (filter `options` yourself)
|
|
228
|
+
|
|
229
|
+
Since the popover lists exactly what you pass in `options`, implement search by filtering upstream:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { Component, computed, signal } from '@angular/core';
|
|
233
|
+
import { EgSearchableMultiselect } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
234
|
+
|
|
235
|
+
@Component({
|
|
236
|
+
selector: 'app-search-multi',
|
|
237
|
+
standalone: true,
|
|
238
|
+
imports: [EgSearchableMultiselect],
|
|
239
|
+
template: `
|
|
240
|
+
<input
|
|
241
|
+
type="search"
|
|
242
|
+
placeholder="Filter options…"
|
|
243
|
+
[value]="query()"
|
|
244
|
+
(input)="query.set($any($event.target).value)"
|
|
245
|
+
aria-label="Filter options"
|
|
246
|
+
/>
|
|
247
|
+
<eg-searchable-multiselect [options]="filtered()" [(value)]="selected" />
|
|
248
|
+
`,
|
|
249
|
+
})
|
|
250
|
+
export class SearchMultiComponent {
|
|
251
|
+
readonly query = signal('');
|
|
252
|
+
readonly selected = signal<string[]>([]);
|
|
253
|
+
private readonly all = [
|
|
254
|
+
{ label: 'Angular', value: 'angular' },
|
|
255
|
+
{ label: 'React', value: 'react' },
|
|
256
|
+
{ label: 'Vue', value: 'vue' },
|
|
257
|
+
{ label: 'Svelte', value: 'svelte' },
|
|
258
|
+
{ label: 'Solid', value: 'solid' },
|
|
259
|
+
];
|
|
260
|
+
readonly filtered = computed(() => {
|
|
261
|
+
const q = this.query().trim().toLowerCase();
|
|
262
|
+
return q ? this.all.filter((o) => o.label.toLowerCase().includes(q)) : this.all;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Note: selections whose option is currently filtered out stay selected internally (chips still show) but have no checkbox row until the filter matches again. Unknown values passed via `value`/`writeValue` that match no option are dropped from the chip row.
|
|
268
|
+
|
|
269
|
+
### 6. Async options + reacting to changes
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { Component, resource, signal } from '@angular/core';
|
|
273
|
+
import { EgSearchableMultiselect, type SelectOption } from '@egose/shadcn-theme-ng/searchable-multiselect';
|
|
274
|
+
|
|
275
|
+
@Component({
|
|
276
|
+
selector: 'app-async-multi',
|
|
277
|
+
standalone: true,
|
|
278
|
+
imports: [EgSearchableMultiselect],
|
|
279
|
+
template: `
|
|
280
|
+
@if (users.isLoading()) {
|
|
281
|
+
<p>Loading users…</p>
|
|
282
|
+
} @else {
|
|
283
|
+
<eg-searchable-multiselect
|
|
284
|
+
[options]="users.value() ?? []"
|
|
285
|
+
[(value)]="assignees"
|
|
286
|
+
(valueChange)="onChange($event)"
|
|
287
|
+
ariaLabel="Assignees"
|
|
288
|
+
/>
|
|
289
|
+
}
|
|
290
|
+
`,
|
|
291
|
+
})
|
|
292
|
+
export class AsyncMultiComponent {
|
|
293
|
+
readonly assignees = signal<string[]>([]);
|
|
294
|
+
readonly users = resource({
|
|
295
|
+
loader: async (): Promise<SelectOption[]> => {
|
|
296
|
+
const res = await fetch('/api/users');
|
|
297
|
+
const list = (await res.json()) as Array<{ id: string; name: string }>;
|
|
298
|
+
return list.map((u) => ({ label: u.name, value: u.id }));
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
onChange(values: string[]): void {
|
|
303
|
+
console.log('assignees now:', values);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Accessibility notes
|
|
309
|
+
|
|
310
|
+
- The trigger is a real `<button>` — give it an accessible name via `ariaLabel` (or a visible `<label>` paired with `id`) and descriptions via `ariaDescribedby`.
|
|
311
|
+
- Options render as native checkbox-backed `hlm-checkbox` rows inside `<label>` elements, so they are keyboard-operable and announced per option.
|
|
312
|
+
- Chip `✕` buttons are real buttons and disabled along with the control; keep chip text concise so screen readers announce removals cleanly.
|
|
313
|
+
- The empty state is a plain text pill (not focusable) — the popover trigger remains the single keyboard entry point, which keeps tab order simple.
|
|
314
|
+
|
|
315
|
+
## Theming / CSS variables
|
|
316
|
+
|
|
317
|
+
Class-driven (chips, popover panel, checkbox rows). Extend via the `class` input on the host; inner popover width (`tw:w-64`) and list height (`tw:max-h-60`) are fixed in the template.
|
|
61
318
|
|
|
62
|
-
##
|
|
319
|
+
## Related subpaths
|
|
63
320
|
|
|
64
|
-
|
|
321
|
+
- `@egose/shadcn-theme-ng/form-searchable-multiselect` — form-field wrapper (label/description/error) around this control
|
|
322
|
+
- `@egose/shadcn-theme-ng/select` — single-select dropdown counterpart
|
|
323
|
+
- `@egose/shadcn-theme-ng/popover` — the underlying popover primitive
|
|
324
|
+
- `@egose/shadcn-theme-ng/checkbox` — the underlying option-row primitive
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { input, output, signal, computed, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
|
-
import { HlmPopover, HlmPopoverTrigger, HlmPopoverContent } from '@egose/shadcn-theme-ng-tw/popover';
|
|
4
|
+
import { HlmPopover, HlmPopoverTrigger, HlmPopoverContent, HlmPopoverPortal } from '@egose/shadcn-theme-ng-tw/popover';
|
|
5
5
|
import { HlmButton } from '@egose/shadcn-theme-ng-tw/button';
|
|
6
6
|
import { HlmCheckbox } from '@egose/shadcn-theme-ng-tw/checkbox';
|
|
7
7
|
import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
|
|
@@ -12,6 +12,16 @@ class EgSearchableMultiselect {
|
|
|
12
12
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
13
13
|
placeholder = input('Start typing to add…', /* @ts-ignore */
|
|
14
14
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
15
|
+
id = input('', /* @ts-ignore */
|
|
16
|
+
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
17
|
+
disabled = input(false, /* @ts-ignore */
|
|
18
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
19
|
+
wrapperDisabled = input(false, /* @ts-ignore */
|
|
20
|
+
...(ngDevMode ? [{ debugName: "wrapperDisabled" }] : /* istanbul ignore next */ []));
|
|
21
|
+
ariaLabel = input(undefined, /* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
23
|
+
ariaDescribedby = input(null, /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "ariaDescribedby" }] : /* istanbul ignore next */ []));
|
|
15
25
|
userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
|
|
16
26
|
/** Value: array of selected values (string[]) */
|
|
17
27
|
value = input([], /* @ts-ignore */
|
|
@@ -19,7 +29,10 @@ class EgSearchableMultiselect {
|
|
|
19
29
|
valueChange = output();
|
|
20
30
|
selectedItems = signal([], /* @ts-ignore */
|
|
21
31
|
...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
|
|
22
|
-
|
|
32
|
+
formDisabled = signal(false, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
|
|
34
|
+
disabledState = computed(() => this.disabled() || this.wrapperDisabled() || this.formDisabled(), /* @ts-ignore */
|
|
35
|
+
...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
23
36
|
isFormBound = false;
|
|
24
37
|
_hostClass = computed(() => hlm(this.userClass()), /* @ts-ignore */
|
|
25
38
|
...(ngDevMode ? [{ debugName: "_hostClass" }] : /* istanbul ignore next */ []));
|
|
@@ -27,6 +40,8 @@ class EgSearchableMultiselect {
|
|
|
27
40
|
return this.selectedItems().some((s) => s.value === value);
|
|
28
41
|
}
|
|
29
42
|
toggle(value, checked) {
|
|
43
|
+
if (this.disabledState())
|
|
44
|
+
return;
|
|
30
45
|
if (checked) {
|
|
31
46
|
this.addItem(value);
|
|
32
47
|
}
|
|
@@ -44,6 +59,8 @@ class EgSearchableMultiselect {
|
|
|
44
59
|
}
|
|
45
60
|
}
|
|
46
61
|
removeItem(value) {
|
|
62
|
+
if (this.disabledState())
|
|
63
|
+
return;
|
|
47
64
|
const updated = this.selectedItems().filter((i) => i.value !== value);
|
|
48
65
|
this.updateValueFromSelected(updated);
|
|
49
66
|
}
|
|
@@ -75,10 +92,10 @@ class EgSearchableMultiselect {
|
|
|
75
92
|
this.onTouched = fn;
|
|
76
93
|
}
|
|
77
94
|
setDisabledState(isDisabled) {
|
|
78
|
-
this.isDisabled
|
|
95
|
+
this.formDisabled.set(isDisabled);
|
|
79
96
|
}
|
|
80
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.
|
|
81
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.
|
|
97
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgSearchableMultiselect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
98
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgSearchableMultiselect, isStandalone: true, selector: "eg-searchable-multiselect", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, wrapperDisabled: { classPropertyName: "wrapperDisabled", publicName: "wrapperDisabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "ariaDescribedby", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { properties: { "class": "_hostClass()" } }, providers: [
|
|
82
99
|
{
|
|
83
100
|
provide: NG_VALUE_ACCESSOR,
|
|
84
101
|
useExisting: forwardRef(() => EgSearchableMultiselect),
|
|
@@ -104,6 +121,7 @@ class EgSearchableMultiselect {
|
|
|
104
121
|
{{ item.label }}
|
|
105
122
|
<button
|
|
106
123
|
type="button"
|
|
124
|
+
[disabled]="disabledState()"
|
|
107
125
|
(click)="removeItem(item.value)"
|
|
108
126
|
class="tw:bg-transparent tw:border-0 tw:text-gray-600
|
|
109
127
|
tw:hover:text-red-500 tw:cursor-pointer"
|
|
@@ -117,17 +135,31 @@ class EgSearchableMultiselect {
|
|
|
117
135
|
|
|
118
136
|
<!-- Trigger -->
|
|
119
137
|
<hlm-popover>
|
|
120
|
-
<button
|
|
138
|
+
<button
|
|
139
|
+
hlmPopoverTrigger
|
|
140
|
+
hlmButton
|
|
141
|
+
variant="secondary"
|
|
142
|
+
appearance="outline"
|
|
143
|
+
type="button"
|
|
144
|
+
[id]="id()"
|
|
145
|
+
[disabled]="disabledState()"
|
|
146
|
+
[attr.aria-label]="ariaLabel()"
|
|
147
|
+
[attr.aria-describedby]="ariaDescribedby()"
|
|
148
|
+
>
|
|
121
149
|
{{ selectedItems().length }} selected
|
|
122
150
|
</button>
|
|
123
151
|
|
|
124
|
-
<hlm-popover-content class="tw:w-64 tw:p-2">
|
|
152
|
+
<hlm-popover-content class="tw:w-64 tw:p-2" *hlmPopoverPortal="let ctx">
|
|
125
153
|
<div class="tw:max-h-60 tw:overflow-auto tw:flex tw:flex-col tw:gap-1">
|
|
126
154
|
@for (option of options(); track option.value) {
|
|
127
155
|
<label
|
|
128
156
|
class="tw:flex tw:items-center tw:gap-2 tw:cursor-pointer tw:px-2 tw:py-1 tw:rounded-sm tw:hover:bg-secondary"
|
|
129
157
|
>
|
|
130
|
-
<hlm-checkbox
|
|
158
|
+
<hlm-checkbox
|
|
159
|
+
[checked]="isSelected(option.value)"
|
|
160
|
+
[disabled]="disabledState()"
|
|
161
|
+
(changed)="toggle(option.value, $event)"
|
|
162
|
+
/>
|
|
131
163
|
<span class="tw:text-sm">{{ option.label }}</span>
|
|
132
164
|
</label>
|
|
133
165
|
} @empty {
|
|
@@ -137,14 +169,14 @@ class EgSearchableMultiselect {
|
|
|
137
169
|
</hlm-popover-content>
|
|
138
170
|
</hlm-popover>
|
|
139
171
|
</div>
|
|
140
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: HlmPopoverTrigger, selector: "button[hlmPopoverTrigger],button[hlmPopoverTriggerFor]" }, { kind: "directive", type: HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "component", type: HlmCheckbox, selector: "hlm-checkbox", inputs: ["class", "id", "aria-label", "aria-labelledby", "aria-describedby", "checked", "name", "required", "disabled"], outputs: ["checkedChange", "changed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
172
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: HlmPopover, selector: "[hlmPopover],hlm-popover" }, { kind: "directive", type: HlmPopoverTrigger, selector: "button[hlmPopoverTrigger],button[hlmPopoverTriggerFor]" }, { kind: "directive", type: HlmPopoverContent, selector: "[hlmPopoverContent],hlm-popover-content" }, { kind: "directive", type: HlmPopoverPortal, selector: "[hlmPopoverPortal]" }, { kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "component", type: HlmCheckbox, selector: "hlm-checkbox", inputs: ["class", "id", "aria-label", "aria-labelledby", "aria-describedby", "checked", "name", "required", "disabled", "wrapperDisabled"], outputs: ["checkedChange", "changed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
141
173
|
}
|
|
142
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.
|
|
174
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgSearchableMultiselect, decorators: [{
|
|
143
175
|
type: Component,
|
|
144
176
|
args: [{
|
|
145
177
|
selector: 'eg-searchable-multiselect',
|
|
146
178
|
standalone: true,
|
|
147
|
-
imports: [HlmPopover, HlmPopoverTrigger, HlmPopoverContent, HlmButton, HlmCheckbox],
|
|
179
|
+
imports: [HlmPopover, HlmPopoverTrigger, HlmPopoverContent, HlmPopoverPortal, HlmButton, HlmCheckbox],
|
|
148
180
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
149
181
|
providers: [
|
|
150
182
|
{
|
|
@@ -176,6 +208,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
176
208
|
{{ item.label }}
|
|
177
209
|
<button
|
|
178
210
|
type="button"
|
|
211
|
+
[disabled]="disabledState()"
|
|
179
212
|
(click)="removeItem(item.value)"
|
|
180
213
|
class="tw:bg-transparent tw:border-0 tw:text-gray-600
|
|
181
214
|
tw:hover:text-red-500 tw:cursor-pointer"
|
|
@@ -189,17 +222,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
189
222
|
|
|
190
223
|
<!-- Trigger -->
|
|
191
224
|
<hlm-popover>
|
|
192
|
-
<button
|
|
225
|
+
<button
|
|
226
|
+
hlmPopoverTrigger
|
|
227
|
+
hlmButton
|
|
228
|
+
variant="secondary"
|
|
229
|
+
appearance="outline"
|
|
230
|
+
type="button"
|
|
231
|
+
[id]="id()"
|
|
232
|
+
[disabled]="disabledState()"
|
|
233
|
+
[attr.aria-label]="ariaLabel()"
|
|
234
|
+
[attr.aria-describedby]="ariaDescribedby()"
|
|
235
|
+
>
|
|
193
236
|
{{ selectedItems().length }} selected
|
|
194
237
|
</button>
|
|
195
238
|
|
|
196
|
-
<hlm-popover-content class="tw:w-64 tw:p-2">
|
|
239
|
+
<hlm-popover-content class="tw:w-64 tw:p-2" *hlmPopoverPortal="let ctx">
|
|
197
240
|
<div class="tw:max-h-60 tw:overflow-auto tw:flex tw:flex-col tw:gap-1">
|
|
198
241
|
@for (option of options(); track option.value) {
|
|
199
242
|
<label
|
|
200
243
|
class="tw:flex tw:items-center tw:gap-2 tw:cursor-pointer tw:px-2 tw:py-1 tw:rounded-sm tw:hover:bg-secondary"
|
|
201
244
|
>
|
|
202
|
-
<hlm-checkbox
|
|
245
|
+
<hlm-checkbox
|
|
246
|
+
[checked]="isSelected(option.value)"
|
|
247
|
+
[disabled]="disabledState()"
|
|
248
|
+
(changed)="toggle(option.value, $event)"
|
|
249
|
+
/>
|
|
203
250
|
<span class="tw:text-sm">{{ option.label }}</span>
|
|
204
251
|
</label>
|
|
205
252
|
} @empty {
|
|
@@ -211,7 +258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
211
258
|
</div>
|
|
212
259
|
`,
|
|
213
260
|
}]
|
|
214
|
-
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
261
|
+
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], wrapperDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapperDisabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedby", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
215
262
|
|
|
216
263
|
/*
|
|
217
264
|
* Public API Surface of searchable-multiselect
|
|
@@ -222,4 +269,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
222
269
|
*/
|
|
223
270
|
|
|
224
271
|
export { EgSearchableMultiselect };
|
|
225
|
-
|
|
272
|
+
|
|
@@ -10,12 +10,18 @@ declare class EgSearchableMultiselect implements ControlValueAccessor {
|
|
|
10
10
|
/** Full option list with labels/values */
|
|
11
11
|
options: _angular_core.InputSignal<SelectOption[]>;
|
|
12
12
|
placeholder: _angular_core.InputSignal<string>;
|
|
13
|
+
id: _angular_core.InputSignal<string>;
|
|
14
|
+
disabled: _angular_core.InputSignal<boolean>;
|
|
15
|
+
wrapperDisabled: _angular_core.InputSignal<boolean>;
|
|
16
|
+
ariaLabel: _angular_core.InputSignal<string | undefined>;
|
|
17
|
+
ariaDescribedby: _angular_core.InputSignal<string | null>;
|
|
13
18
|
userClass: _angular_core.InputSignal<ClassValue>;
|
|
14
19
|
/** Value: array of selected values (string[]) */
|
|
15
20
|
value: _angular_core.InputSignal<string[]>;
|
|
16
21
|
valueChange: _angular_core.OutputEmitterRef<string[]>;
|
|
17
22
|
protected readonly selectedItems: _angular_core.WritableSignal<SelectOption[]>;
|
|
18
|
-
protected
|
|
23
|
+
protected readonly formDisabled: _angular_core.WritableSignal<boolean>;
|
|
24
|
+
protected readonly disabledState: _angular_core.Signal<boolean>;
|
|
19
25
|
private isFormBound;
|
|
20
26
|
protected readonly _hostClass: _angular_core.Signal<string>;
|
|
21
27
|
protected isSelected(value: string): boolean;
|
|
@@ -32,7 +38,7 @@ declare class EgSearchableMultiselect implements ControlValueAccessor {
|
|
|
32
38
|
registerOnTouched(fn: () => void): void;
|
|
33
39
|
setDisabledState(isDisabled: boolean): void;
|
|
34
40
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgSearchableMultiselect, never>;
|
|
35
|
-
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgSearchableMultiselect, "eg-searchable-multiselect", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
|
|
41
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgSearchableMultiselect, "eg-searchable-multiselect", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "wrapperDisabled": { "alias": "wrapperDisabled"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "ariaDescribedby": { "alias": "ariaDescribedby"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
export { EgSearchableMultiselect };
|