smt-select 0.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/fesm2022/smt-select.mjs +229 -0
- package/fesm2022/smt-select.mjs.map +1 -0
- package/package.json +45 -0
- package/types/smt-select.d.ts +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Samet Acar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# SmtSelect 🚀
|
|
2
|
+
|
|
3
|
+
A high-performance, lightweight, and customizable **Angular Select Component** with built-in Virtual Scroll and Search capabilities.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **🔍 Searchable**: Quickly filter through thousands of options.
|
|
8
|
+
- **⚡ Virtual Scroll**: Built-in support for high-performance rendering of large datasets.
|
|
9
|
+
- **✅ Multi-Select**: Support for multiple selection out of the box.
|
|
10
|
+
- **🎨 Custom Styling**: Fully customizable via SCSS tokens.
|
|
11
|
+
- **📱 Responsive**: Works seamlessly across mobile and desktop.
|
|
12
|
+
- **🛡️ Type Safe**: Developed with strict TypeScript.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚀 Installation
|
|
17
|
+
|
|
18
|
+
Install the package via npm:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install smt-select
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📦 Usage
|
|
27
|
+
|
|
28
|
+
### 1. Component Logic (app.component.ts)
|
|
29
|
+
```typescript
|
|
30
|
+
import { SmtSelectComponent, SmtSelectOption, SmtSelectConfig } from 'smt-select';
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
standalone: true,
|
|
34
|
+
imports: [SmtSelectComponent],
|
|
35
|
+
selector: 'app-root',
|
|
36
|
+
templateUrl: './app.component.html'
|
|
37
|
+
})
|
|
38
|
+
export class AppComponent {
|
|
39
|
+
myOptions: SmtSelectOption[] = [
|
|
40
|
+
{ value: 1, label: 'Option 1' },
|
|
41
|
+
{ value: 2, label: 'Option 2' },
|
|
42
|
+
{ value: 3, label: 'Option 3' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
selectConfig: SmtSelectConfig = {
|
|
46
|
+
placeholder: 'Choose an item...',
|
|
47
|
+
virtualScroll: true,
|
|
48
|
+
isMultiSelect: false
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
onSelection(value: any) {
|
|
52
|
+
console.log('Selected value:', value);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Template (app.component.html)
|
|
58
|
+
```html
|
|
59
|
+
<smt-select
|
|
60
|
+
[options]="myOptions"
|
|
61
|
+
[config]="selectConfig"
|
|
62
|
+
[(selectedValue)]="currentValue"
|
|
63
|
+
(selectionChange)="onSelection($event)">
|
|
64
|
+
</smt-select>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## ⚙️ API Reference
|
|
70
|
+
|
|
71
|
+
### Inputs
|
|
72
|
+
| Property | Type | Default | Description |
|
|
73
|
+
|----------|------|---------|-------------|
|
|
74
|
+
| `options` | `SmtSelectOption[]` | `[]` | Array of options to display. |
|
|
75
|
+
| `config` | `SmtSelectConfig` | `{}` | Configuration object for the component. |
|
|
76
|
+
| `selectedValue` | `any \| any[]` | `null` | The currently selected value(s). Supports two-way binding. |
|
|
77
|
+
| `errorMessage` | `string \| null` | `undefined` | Error message to display below the component. |
|
|
78
|
+
| `isInvalid` | `boolean` | `false` | Manually trigger error state if `errorMessage` is not provided. |
|
|
79
|
+
| `visibility` | `SmtVisibilityType` | `ENABLED` | Controls accessibility (`ENABLED`, `READONLY`, `HIDDEN`). |
|
|
80
|
+
|
|
81
|
+
### Outputs
|
|
82
|
+
| Event | Payload | Description |
|
|
83
|
+
|-------|---------|-------------|
|
|
84
|
+
| `selectionChange` | `any \| any[]` | Fired when the selected value changes. |
|
|
85
|
+
| `pocketOpen` | `boolean` | Fired when the dropdown is opened or closed. |
|
|
86
|
+
|
|
87
|
+
### Configuration (`SmtSelectConfig`)
|
|
88
|
+
| Property | Type | Description |
|
|
89
|
+
|----------|------|-------------|
|
|
90
|
+
| `fieldID` | `string \| number` | Unique ID for the wrapper element. |
|
|
91
|
+
| `placeholder` | `string` | Text to show when no value is selected. |
|
|
92
|
+
| `virtualScroll` | `boolean` | Enable/Disable CDK Virtual Scroll for large datasets. |
|
|
93
|
+
| `isMultiSelect` | `boolean` | Enable multiple item selection. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 📄 License
|
|
98
|
+
|
|
99
|
+
MIT © [Samet Acar](LICENSE)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Pipe, EventEmitter, ViewChild, Output, Input, Component } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i2 from '@angular/forms';
|
|
6
|
+
import { FormsModule } from '@angular/forms';
|
|
7
|
+
import * as i3 from '@angular/cdk/scrolling';
|
|
8
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
9
|
+
import { timer, take } from 'rxjs';
|
|
10
|
+
|
|
11
|
+
var SmtVisibilityType;
|
|
12
|
+
(function (SmtVisibilityType) {
|
|
13
|
+
SmtVisibilityType["HIDDEN"] = "HIDDEN";
|
|
14
|
+
SmtVisibilityType["READONLY"] = "READONLY";
|
|
15
|
+
SmtVisibilityType["ENABLED"] = "ENABLED";
|
|
16
|
+
})(SmtVisibilityType || (SmtVisibilityType = {}));
|
|
17
|
+
|
|
18
|
+
class SmtSelectSearchFilterPipe {
|
|
19
|
+
transform(options, searchText) {
|
|
20
|
+
if (!options)
|
|
21
|
+
return [];
|
|
22
|
+
if (!searchText)
|
|
23
|
+
return options;
|
|
24
|
+
searchText = searchText.toLowerCase();
|
|
25
|
+
return options.filter(option => {
|
|
26
|
+
return option.label.toLowerCase().includes(searchText) ||
|
|
27
|
+
(typeof option.value === 'string' && option.value.toLowerCase().includes(searchText));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SmtSelectSearchFilterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
31
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: SmtSelectSearchFilterPipe, isStandalone: true, name: "smtSelectSearchFilter" });
|
|
32
|
+
}
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SmtSelectSearchFilterPipe, decorators: [{
|
|
34
|
+
type: Pipe,
|
|
35
|
+
args: [{
|
|
36
|
+
name: 'smtSelectSearchFilter',
|
|
37
|
+
standalone: true,
|
|
38
|
+
pure: true
|
|
39
|
+
}]
|
|
40
|
+
}] });
|
|
41
|
+
|
|
42
|
+
class SmtSelectComponent {
|
|
43
|
+
el;
|
|
44
|
+
renderer;
|
|
45
|
+
options = [];
|
|
46
|
+
config = {};
|
|
47
|
+
selectedValue = null;
|
|
48
|
+
errorMessage;
|
|
49
|
+
isInvalid = false;
|
|
50
|
+
visibility = SmtVisibilityType.ENABLED;
|
|
51
|
+
selectionChange = new EventEmitter();
|
|
52
|
+
pocketOpen = new EventEmitter();
|
|
53
|
+
pocketElement;
|
|
54
|
+
inputElement;
|
|
55
|
+
isPocketOpen = false;
|
|
56
|
+
isOpenReverse = false;
|
|
57
|
+
searchText = '';
|
|
58
|
+
clickListener = null;
|
|
59
|
+
VisibilityType = SmtVisibilityType;
|
|
60
|
+
constructor(el, renderer) {
|
|
61
|
+
this.el = el;
|
|
62
|
+
this.renderer = renderer;
|
|
63
|
+
}
|
|
64
|
+
ngOnDestroy() {
|
|
65
|
+
this.removeOutsideClickListener();
|
|
66
|
+
}
|
|
67
|
+
get hasSelection() {
|
|
68
|
+
if (this.config.isMultiSelect) {
|
|
69
|
+
return Array.isArray(this.selectedValue) && this.selectedValue.length > 0;
|
|
70
|
+
}
|
|
71
|
+
return this.selectedValue !== null && this.selectedValue !== undefined && this.selectedValue !== '';
|
|
72
|
+
}
|
|
73
|
+
displayText() {
|
|
74
|
+
if (!this.hasSelection)
|
|
75
|
+
return this.config.placeholder || 'Select...';
|
|
76
|
+
if (this.config.isMultiSelect) {
|
|
77
|
+
const selectedOptions = this.options.filter(opt => this.selectedValue.includes(opt.value));
|
|
78
|
+
return selectedOptions.map(opt => opt.label).join(', ');
|
|
79
|
+
}
|
|
80
|
+
const selectedOption = this.options.find(opt => opt.value === this.selectedValue);
|
|
81
|
+
return selectedOption ? selectedOption.label : this.selectedValue;
|
|
82
|
+
}
|
|
83
|
+
togglePocket() {
|
|
84
|
+
if (this.visibility === SmtVisibilityType.READONLY) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.isPocketOpen) {
|
|
88
|
+
this.closePocket();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.openPocket();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
openPocket() {
|
|
95
|
+
this.calculateDirection();
|
|
96
|
+
this.isPocketOpen = true;
|
|
97
|
+
this.pocketOpen.emit(true);
|
|
98
|
+
this.searchText = '';
|
|
99
|
+
this.focusInput();
|
|
100
|
+
this.addOutsideClickListener();
|
|
101
|
+
}
|
|
102
|
+
closePocket() {
|
|
103
|
+
if (!this.isPocketOpen)
|
|
104
|
+
return;
|
|
105
|
+
this.isPocketOpen = false;
|
|
106
|
+
this.pocketOpen.emit(false);
|
|
107
|
+
this.removeOutsideClickListener();
|
|
108
|
+
}
|
|
109
|
+
addOutsideClickListener() {
|
|
110
|
+
if (this.clickListener)
|
|
111
|
+
return;
|
|
112
|
+
// Only clicks outside can reach the document, so we can close directly
|
|
113
|
+
this.clickListener = this.renderer.listen('document', 'mousedown', () => {
|
|
114
|
+
this.closePocket();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
removeOutsideClickListener() {
|
|
118
|
+
if (this.clickListener) {
|
|
119
|
+
this.clickListener();
|
|
120
|
+
this.clickListener = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
calculateDirection() {
|
|
124
|
+
const rect = this.el.nativeElement.getBoundingClientRect();
|
|
125
|
+
const windowHeight = window.innerHeight;
|
|
126
|
+
const pocketHeight = 200; // max-height
|
|
127
|
+
if (windowHeight - rect.bottom < pocketHeight && rect.top > pocketHeight) {
|
|
128
|
+
this.isOpenReverse = true;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
this.isOpenReverse = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
focusInput() {
|
|
135
|
+
timer(100).pipe(take(1)).subscribe(() => {
|
|
136
|
+
this.inputElement?.nativeElement.focus();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
onBlur() {
|
|
140
|
+
// Keyboard exit scenarios delay
|
|
141
|
+
timer(200).pipe(take(1)).subscribe(() => {
|
|
142
|
+
if (this.isPocketOpen) {
|
|
143
|
+
this.closePocket();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
isSelected(option) {
|
|
148
|
+
if (this.config.isMultiSelect) {
|
|
149
|
+
return Array.isArray(this.selectedValue) && this.selectedValue.includes(option.value);
|
|
150
|
+
}
|
|
151
|
+
return this.selectedValue === option.value;
|
|
152
|
+
}
|
|
153
|
+
onEnterPressed() {
|
|
154
|
+
if (!this.searchText)
|
|
155
|
+
return;
|
|
156
|
+
const filtered = this.options.filter(option => option.label.toLowerCase().includes(this.searchText.toLowerCase()) ||
|
|
157
|
+
(typeof option.value === 'string' && option.value.toLowerCase().includes(this.searchText.toLowerCase())));
|
|
158
|
+
if (filtered.length > 0) {
|
|
159
|
+
this.selectOption(filtered[0]);
|
|
160
|
+
this.closePocket();
|
|
161
|
+
if (this.config.isMultiSelect) {
|
|
162
|
+
this.searchText = '';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
selectOption(option, event) {
|
|
167
|
+
if (event) {
|
|
168
|
+
event.stopPropagation();
|
|
169
|
+
}
|
|
170
|
+
if (this.config.isMultiSelect) {
|
|
171
|
+
if (!Array.isArray(this.selectedValue)) {
|
|
172
|
+
this.selectedValue = [];
|
|
173
|
+
}
|
|
174
|
+
const index = this.selectedValue.indexOf(option.value);
|
|
175
|
+
if (index > -1) {
|
|
176
|
+
this.selectedValue.splice(index, 1);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.selectedValue.push(option.value);
|
|
180
|
+
}
|
|
181
|
+
// View update for spread or slice
|
|
182
|
+
this.selectedValue = [...this.selectedValue];
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
this.selectedValue = option.value;
|
|
186
|
+
}
|
|
187
|
+
this.closePocket();
|
|
188
|
+
this.selectionChange.emit(this.selectedValue);
|
|
189
|
+
}
|
|
190
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SmtSelectComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
|
|
191
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: SmtSelectComponent, isStandalone: true, selector: "smt-select", inputs: { options: "options", config: "config", selectedValue: "selectedValue", errorMessage: "errorMessage", isInvalid: "isInvalid", visibility: "visibility" }, outputs: { selectionChange: "selectionChange", pocketOpen: "pocketOpen" }, viewQueries: [{ propertyName: "pocketElement", first: true, predicate: ["pocket"], descendants: true }, { propertyName: "inputElement", first: true, predicate: ["input"], descendants: true }], ngImport: i0, template: "<div *ngIf=\"visibility !== VisibilityType.HIDDEN\" class=\"smt-select-wrapper\" [id]=\"config.fieldID || ''\"\r\n (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"smt-select-control\" (click)=\"togglePocket()\" [class.disabled]=\"visibility === VisibilityType.READONLY\"\r\n [class.error]=\"errorMessage ? true : (errorMessage === undefined ? isInvalid : false)\">\r\n\r\n <!-- Search Input -->\r\n <input #input *ngIf=\"isPocketOpen\" [(ngModel)]=\"searchText\" (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"onEnterPressed()\" (blur)=\"onBlur()\" class=\"smt-select-input\"\r\n [placeholder]=\"config.placeholder || 'Select...'\">\r\n\r\n <!-- Selected Value Display -->\r\n <ng-container *ngIf=\"!isPocketOpen\">\r\n <span class=\"select-value\" [class.placeholder]=\"!hasSelection\">\r\n {{ displayText() }}\r\n </span>\r\n </ng-container>\r\n\r\n <div class=\"arrow-icon\" [class.up]=\"isPocketOpen\"></div>\r\n </div>\r\n\r\n <!-- Dropdown (Pocket) -->\r\n <div #pocket *ngIf=\"isPocketOpen\" class=\"smt-select-pocket\" [class.open-reverse]=\"isOpenReverse\">\r\n\r\n <!-- Standard List -->\r\n <ul *ngIf=\"!config.virtualScroll\">\r\n <li *ngFor=\"let option of options | smtSelectSearchFilter: searchText\" (click)=\"selectOption(option, $event)\"\r\n class=\"smt-select-option\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </li>\r\n </ul>\r\n\r\n <!-- Virtual Scroll -->\r\n <cdk-virtual-scroll-viewport *ngIf=\"config.virtualScroll\" itemSize=\"34\" class=\"virtual-scroll-viewport\">\r\n <div *cdkVirtualFor=\"let option of options | smtSelectSearchFilter: searchText\" class=\"smt-select-option\"\r\n (click)=\"selectOption(option, $event)\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"error-message\">\r\n {{ errorMessage }}\r\n </div>\r\n</div>", styles: [".smt-select-wrapper{position:relative;width:100%;font-family:Arial,sans-serif;box-sizing:border-box}.smt-select-wrapper *{box-sizing:border-box}.smt-select-control{display:flex;align-items:center;min-height:34px;border:1px solid #ccc;padding:4px 8px;background:#fff;cursor:pointer;position:relative}.smt-select-control.disabled{background:#f5f5f5;cursor:not-allowed;opacity:.7}.smt-select-control.error{border-color:#d9534f}.smt-select-control.error:focus-within{box-shadow:0 0 0 2px #d9534f33}.smt-select-control .select-value{font-size:13px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.smt-select-control .select-value.placeholder{color:#999;font-style:italic}.smt-select-control .smt-select-input{border:none;outline:none;width:100%;font-size:13px;padding:0;background:transparent}.smt-select-control .smt-select-input::placeholder{color:#999;font-style:italic}.smt-select-control .arrow-icon{margin-left:auto;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #666;transition:transform .2s}.smt-select-control .arrow-icon.up{transform:rotate(180deg)}.smt-select-pocket{position:absolute;top:100%;left:0;width:100%;background:#fff;border:1px solid #ccc;border-top:none;z-index:1000;max-height:200px;box-shadow:0 4px 6px #0000001a}.smt-select-pocket:has(ul){overflow-y:auto}.smt-select-pocket.open-reverse{top:auto;bottom:100%;border-top:1px solid #ccc;border-bottom:none}.smt-select-pocket .smt-select-option{padding:8px 12px;font-size:13px;cursor:pointer;display:flex;align-items:center}.smt-select-pocket .smt-select-option:hover{background-color:#f0faff}.smt-select-pocket .smt-select-option.active{background-color:#005f87;color:#fff}.smt-select-pocket .smt-select-option .checkbox-container{margin-right:8px;display:flex;align-items:center}.smt-select-pocket ul{list-style:none;padding:0;margin:0}.virtual-scroll-viewport{height:200px;width:100%;outline:none}.error-message{color:#d9534f;font-size:11px;margin-top:4px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "pipe", type: SmtSelectSearchFilterPipe, name: "smtSelectSearchFilter" }] });
|
|
192
|
+
}
|
|
193
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SmtSelectComponent, decorators: [{
|
|
194
|
+
type: Component,
|
|
195
|
+
args: [{ selector: 'smt-select', standalone: true, imports: [CommonModule, FormsModule, ScrollingModule, SmtSelectSearchFilterPipe], template: "<div *ngIf=\"visibility !== VisibilityType.HIDDEN\" class=\"smt-select-wrapper\" [id]=\"config.fieldID || ''\"\r\n (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"smt-select-control\" (click)=\"togglePocket()\" [class.disabled]=\"visibility === VisibilityType.READONLY\"\r\n [class.error]=\"errorMessage ? true : (errorMessage === undefined ? isInvalid : false)\">\r\n\r\n <!-- Search Input -->\r\n <input #input *ngIf=\"isPocketOpen\" [(ngModel)]=\"searchText\" (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"onEnterPressed()\" (blur)=\"onBlur()\" class=\"smt-select-input\"\r\n [placeholder]=\"config.placeholder || 'Select...'\">\r\n\r\n <!-- Selected Value Display -->\r\n <ng-container *ngIf=\"!isPocketOpen\">\r\n <span class=\"select-value\" [class.placeholder]=\"!hasSelection\">\r\n {{ displayText() }}\r\n </span>\r\n </ng-container>\r\n\r\n <div class=\"arrow-icon\" [class.up]=\"isPocketOpen\"></div>\r\n </div>\r\n\r\n <!-- Dropdown (Pocket) -->\r\n <div #pocket *ngIf=\"isPocketOpen\" class=\"smt-select-pocket\" [class.open-reverse]=\"isOpenReverse\">\r\n\r\n <!-- Standard List -->\r\n <ul *ngIf=\"!config.virtualScroll\">\r\n <li *ngFor=\"let option of options | smtSelectSearchFilter: searchText\" (click)=\"selectOption(option, $event)\"\r\n class=\"smt-select-option\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </li>\r\n </ul>\r\n\r\n <!-- Virtual Scroll -->\r\n <cdk-virtual-scroll-viewport *ngIf=\"config.virtualScroll\" itemSize=\"34\" class=\"virtual-scroll-viewport\">\r\n <div *cdkVirtualFor=\"let option of options | smtSelectSearchFilter: searchText\" class=\"smt-select-option\"\r\n (click)=\"selectOption(option, $event)\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"error-message\">\r\n {{ errorMessage }}\r\n </div>\r\n</div>", styles: [".smt-select-wrapper{position:relative;width:100%;font-family:Arial,sans-serif;box-sizing:border-box}.smt-select-wrapper *{box-sizing:border-box}.smt-select-control{display:flex;align-items:center;min-height:34px;border:1px solid #ccc;padding:4px 8px;background:#fff;cursor:pointer;position:relative}.smt-select-control.disabled{background:#f5f5f5;cursor:not-allowed;opacity:.7}.smt-select-control.error{border-color:#d9534f}.smt-select-control.error:focus-within{box-shadow:0 0 0 2px #d9534f33}.smt-select-control .select-value{font-size:13px;color:#333;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.smt-select-control .select-value.placeholder{color:#999;font-style:italic}.smt-select-control .smt-select-input{border:none;outline:none;width:100%;font-size:13px;padding:0;background:transparent}.smt-select-control .smt-select-input::placeholder{color:#999;font-style:italic}.smt-select-control .arrow-icon{margin-left:auto;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #666;transition:transform .2s}.smt-select-control .arrow-icon.up{transform:rotate(180deg)}.smt-select-pocket{position:absolute;top:100%;left:0;width:100%;background:#fff;border:1px solid #ccc;border-top:none;z-index:1000;max-height:200px;box-shadow:0 4px 6px #0000001a}.smt-select-pocket:has(ul){overflow-y:auto}.smt-select-pocket.open-reverse{top:auto;bottom:100%;border-top:1px solid #ccc;border-bottom:none}.smt-select-pocket .smt-select-option{padding:8px 12px;font-size:13px;cursor:pointer;display:flex;align-items:center}.smt-select-pocket .smt-select-option:hover{background-color:#f0faff}.smt-select-pocket .smt-select-option.active{background-color:#005f87;color:#fff}.smt-select-pocket .smt-select-option .checkbox-container{margin-right:8px;display:flex;align-items:center}.smt-select-pocket ul{list-style:none;padding:0;margin:0}.virtual-scroll-viewport{height:200px;width:100%;outline:none}.error-message{color:#d9534f;font-size:11px;margin-top:4px}\n"] }]
|
|
196
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { options: [{
|
|
197
|
+
type: Input
|
|
198
|
+
}], config: [{
|
|
199
|
+
type: Input
|
|
200
|
+
}], selectedValue: [{
|
|
201
|
+
type: Input
|
|
202
|
+
}], errorMessage: [{
|
|
203
|
+
type: Input
|
|
204
|
+
}], isInvalid: [{
|
|
205
|
+
type: Input
|
|
206
|
+
}], visibility: [{
|
|
207
|
+
type: Input
|
|
208
|
+
}], selectionChange: [{
|
|
209
|
+
type: Output
|
|
210
|
+
}], pocketOpen: [{
|
|
211
|
+
type: Output
|
|
212
|
+
}], pocketElement: [{
|
|
213
|
+
type: ViewChild,
|
|
214
|
+
args: ['pocket']
|
|
215
|
+
}], inputElement: [{
|
|
216
|
+
type: ViewChild,
|
|
217
|
+
args: ['input']
|
|
218
|
+
}] } });
|
|
219
|
+
|
|
220
|
+
/*
|
|
221
|
+
* Public API Surface of smt-select
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Generated bundle index. Do not edit.
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
export { SmtSelectComponent, SmtVisibilityType };
|
|
229
|
+
//# sourceMappingURL=smt-select.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smt-select.mjs","sources":["../../../projects/smt-select/src/lib/models/index.ts","../../../projects/smt-select/src/lib/pipes/smt-select-search-filter.pipe.ts","../../../projects/smt-select/src/lib/components/smt-select/smt-select.component.ts","../../../projects/smt-select/src/lib/components/smt-select/smt-select.component.html","../../../projects/smt-select/src/public-api.ts","../../../projects/smt-select/src/smt-select.ts"],"sourcesContent":["export interface SmtSelectOption {\r\n value: any;\r\n label: string;\r\n [key: string]: any;\r\n}\r\n\r\nexport enum SmtVisibilityType {\r\n HIDDEN = 'HIDDEN',\r\n READONLY = 'READONLY',\r\n ENABLED = 'ENABLED'\r\n}\r\n\r\nexport interface SmtSelectConfig {\r\n fieldID?: string | number;\r\n placeholder?: string;\r\n virtualScroll?: boolean;\r\n isMultiSelect?: boolean;\r\n}\r\n","import { Pipe, PipeTransform } from '@angular/core';\r\nimport { SmtSelectOption } from '../models';\r\n\r\n@Pipe({\r\n name: 'smtSelectSearchFilter',\r\n standalone: true,\r\n pure: true\r\n})\r\nexport class SmtSelectSearchFilterPipe implements PipeTransform {\r\n transform(options: SmtSelectOption[], searchText: string): SmtSelectOption[] {\r\n if (!options) return [];\r\n if (!searchText) return options;\r\n\r\n searchText = searchText.toLowerCase();\r\n\r\n return options.filter(option => {\r\n return option.label.toLowerCase().includes(searchText) ||\r\n (typeof option.value === 'string' && option.value.toLowerCase().includes(searchText));\r\n });\r\n }\r\n}\r\n","import {\r\n Component, Input, Output, EventEmitter,\r\n ElementRef, Renderer2, ViewChild, OnDestroy\r\n} from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { FormsModule } from '@angular/forms';\r\nimport { ScrollingModule } from '@angular/cdk/scrolling';\r\nimport {\r\n SmtSelectOption, SmtSelectConfig,\r\n SmtVisibilityType\r\n} from '../../models';\r\nimport { SmtSelectSearchFilterPipe } from '../../pipes/smt-select-search-filter.pipe';\r\nimport { timer, take } from 'rxjs';\r\n\r\n@Component({\r\n selector: 'smt-select',\r\n standalone: true,\r\n imports: [CommonModule, FormsModule, ScrollingModule, SmtSelectSearchFilterPipe],\r\n templateUrl: './smt-select.component.html',\r\n styleUrls: ['./smt-select.component.scss']\r\n})\r\nexport class SmtSelectComponent implements OnDestroy {\r\n @Input() options: SmtSelectOption[] = [];\r\n @Input() config: SmtSelectConfig = {};\r\n @Input() selectedValue: any | any[] = null;\r\n @Input() errorMessage?: string | null;\r\n @Input() isInvalid = false;\r\n @Input() visibility: SmtVisibilityType = SmtVisibilityType.ENABLED;\r\n\r\n @Output() selectionChange = new EventEmitter<any | any[]>();\r\n @Output() pocketOpen = new EventEmitter<boolean>();\r\n\r\n @ViewChild('pocket') pocketElement!: ElementRef;\r\n @ViewChild('input') inputElement!: ElementRef;\r\n\r\n isPocketOpen = false;\r\n isOpenReverse = false;\r\n searchText = '';\r\n\r\n private clickListener: (() => void) | null = null;\r\n public VisibilityType = SmtVisibilityType;\r\n\r\n constructor(private el: ElementRef, private renderer: Renderer2) { }\r\n\r\n ngOnDestroy(): void {\r\n this.removeOutsideClickListener();\r\n }\r\n\r\n get hasSelection(): boolean {\r\n if (this.config.isMultiSelect) {\r\n return Array.isArray(this.selectedValue) && this.selectedValue.length > 0;\r\n }\r\n return this.selectedValue !== null && this.selectedValue !== undefined && this.selectedValue !== '';\r\n }\r\n\r\n displayText(): string {\r\n if (!this.hasSelection) return this.config.placeholder || 'Select...';\r\n\r\n if (this.config.isMultiSelect) {\r\n const selectedOptions = this.options.filter(opt =>\r\n (this.selectedValue as any[]).includes(opt.value)\r\n );\r\n return selectedOptions.map(opt => opt.label).join(', ');\r\n }\r\n\r\n const selectedOption = this.options.find(opt => opt.value === this.selectedValue);\r\n return selectedOption ? selectedOption.label : (this.selectedValue as string);\r\n }\r\n\r\n togglePocket(): void {\r\n if (this.visibility === SmtVisibilityType.READONLY) {\r\n return;\r\n }\r\n\r\n if (this.isPocketOpen) {\r\n this.closePocket();\r\n } else {\r\n this.openPocket();\r\n }\r\n }\r\n\r\n private openPocket(): void {\r\n this.calculateDirection();\r\n this.isPocketOpen = true;\r\n this.pocketOpen.emit(true);\r\n this.searchText = '';\r\n this.focusInput();\r\n this.addOutsideClickListener();\r\n }\r\n\r\n private closePocket(): void {\r\n if (!this.isPocketOpen) return;\r\n this.isPocketOpen = false;\r\n this.pocketOpen.emit(false);\r\n this.removeOutsideClickListener();\r\n }\r\n\r\n private addOutsideClickListener(): void {\r\n if (this.clickListener) return;\r\n\r\n // Only clicks outside can reach the document, so we can close directly\r\n this.clickListener = this.renderer.listen('document', 'mousedown', () => {\r\n this.closePocket();\r\n });\r\n }\r\n\r\n private removeOutsideClickListener(): void {\r\n if (this.clickListener) {\r\n this.clickListener();\r\n this.clickListener = null;\r\n }\r\n }\r\n\r\n private calculateDirection(): void {\r\n const rect = this.el.nativeElement.getBoundingClientRect();\r\n const windowHeight = window.innerHeight;\r\n const pocketHeight = 200; // max-height \r\n\r\n if (windowHeight - rect.bottom < pocketHeight && rect.top > pocketHeight) {\r\n this.isOpenReverse = true;\r\n } else {\r\n this.isOpenReverse = false;\r\n }\r\n }\r\n\r\n private focusInput(): void {\r\n timer(100).pipe(take(1)).subscribe(() => {\r\n this.inputElement?.nativeElement.focus();\r\n });\r\n }\r\n\r\n onBlur(): void {\r\n // Keyboard exit scenarios delay\r\n timer(200).pipe(take(1)).subscribe(() => {\r\n if (this.isPocketOpen) {\r\n this.closePocket();\r\n }\r\n });\r\n }\r\n\r\n isSelected(option: SmtSelectOption): boolean {\r\n if (this.config.isMultiSelect) {\r\n return Array.isArray(this.selectedValue) && this.selectedValue.includes(option.value);\r\n }\r\n return this.selectedValue === option.value;\r\n }\r\n\r\n onEnterPressed(): void {\r\n if (!this.searchText) return;\r\n\r\n const filtered = this.options.filter(option =>\r\n option.label.toLowerCase().includes(this.searchText.toLowerCase()) ||\r\n (typeof option.value === 'string' && option.value.toLowerCase().includes(this.searchText.toLowerCase()))\r\n );\r\n\r\n if (filtered.length > 0) {\r\n this.selectOption(filtered[0]);\r\n this.closePocket();\r\n if (this.config.isMultiSelect) {\r\n this.searchText = '';\r\n }\r\n }\r\n }\r\n\r\n selectOption(option: SmtSelectOption, event?: MouseEvent): void {\r\n if (event) {\r\n event.stopPropagation();\r\n }\r\n\r\n if (this.config.isMultiSelect) {\r\n if (!Array.isArray(this.selectedValue)) {\r\n this.selectedValue = [];\r\n }\r\n\r\n const index = this.selectedValue.indexOf(option.value);\r\n if (index > -1) {\r\n this.selectedValue.splice(index, 1);\r\n } else {\r\n this.selectedValue.push(option.value);\r\n }\r\n\r\n // View update for spread or slice \r\n this.selectedValue = [...this.selectedValue];\r\n } else {\r\n this.selectedValue = option.value;\r\n }\r\n\r\n this.closePocket();\r\n this.selectionChange.emit(this.selectedValue);\r\n }\r\n}\r\n","<div *ngIf=\"visibility !== VisibilityType.HIDDEN\" class=\"smt-select-wrapper\" [id]=\"config.fieldID || ''\"\r\n (mousedown)=\"$event.stopPropagation()\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"smt-select-control\" (click)=\"togglePocket()\" [class.disabled]=\"visibility === VisibilityType.READONLY\"\r\n [class.error]=\"errorMessage ? true : (errorMessage === undefined ? isInvalid : false)\">\r\n\r\n <!-- Search Input -->\r\n <input #input *ngIf=\"isPocketOpen\" [(ngModel)]=\"searchText\" (click)=\"$event.stopPropagation()\"\r\n (keydown.enter)=\"onEnterPressed()\" (blur)=\"onBlur()\" class=\"smt-select-input\"\r\n [placeholder]=\"config.placeholder || 'Select...'\">\r\n\r\n <!-- Selected Value Display -->\r\n <ng-container *ngIf=\"!isPocketOpen\">\r\n <span class=\"select-value\" [class.placeholder]=\"!hasSelection\">\r\n {{ displayText() }}\r\n </span>\r\n </ng-container>\r\n\r\n <div class=\"arrow-icon\" [class.up]=\"isPocketOpen\"></div>\r\n </div>\r\n\r\n <!-- Dropdown (Pocket) -->\r\n <div #pocket *ngIf=\"isPocketOpen\" class=\"smt-select-pocket\" [class.open-reverse]=\"isOpenReverse\">\r\n\r\n <!-- Standard List -->\r\n <ul *ngIf=\"!config.virtualScroll\">\r\n <li *ngFor=\"let option of options | smtSelectSearchFilter: searchText\" (click)=\"selectOption(option, $event)\"\r\n class=\"smt-select-option\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </li>\r\n </ul>\r\n\r\n <!-- Virtual Scroll -->\r\n <cdk-virtual-scroll-viewport *ngIf=\"config.virtualScroll\" itemSize=\"34\" class=\"virtual-scroll-viewport\">\r\n <div *cdkVirtualFor=\"let option of options | smtSelectSearchFilter: searchText\" class=\"smt-select-option\"\r\n (click)=\"selectOption(option, $event)\" [class.active]=\"isSelected(option)\">\r\n\r\n <div *ngIf=\"config.isMultiSelect\" class=\"checkbox-container\">\r\n <input type=\"checkbox\" [checked]=\"isSelected(option)\" (click)=\"$event.stopPropagation()\">\r\n </div>\r\n\r\n <span>{{ option.label }}</span>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n </div>\r\n\r\n <div *ngIf=\"errorMessage\" class=\"error-message\">\r\n {{ errorMessage }}\r\n </div>\r\n</div>","/*\r\n * Public API Surface of smt-select\r\n */\r\n\r\nexport * from './lib/models';\r\nexport * from './lib/components/smt-select/smt-select.component';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smt-select",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": "Samet Acar",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@angular/common": "^21.0.0",
|
|
8
|
+
"@angular/core": "^21.0.0"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"tslib": "^2.3.0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"angular-dropdown-select",
|
|
15
|
+
"Angular Dropdown Select",
|
|
16
|
+
"Angular Selectbox",
|
|
17
|
+
"Angular Select",
|
|
18
|
+
"angular select",
|
|
19
|
+
"Angular Dropdown",
|
|
20
|
+
"Dropdown Select",
|
|
21
|
+
"Virtual Scroll",
|
|
22
|
+
"Virtual Scroll Select",
|
|
23
|
+
"Dropdown",
|
|
24
|
+
"angular",
|
|
25
|
+
"ng",
|
|
26
|
+
"Angular",
|
|
27
|
+
"dropdown",
|
|
28
|
+
"select",
|
|
29
|
+
"multi-select",
|
|
30
|
+
"javascript",
|
|
31
|
+
"typescript"
|
|
32
|
+
],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"module": "fesm2022/smt-select.mjs",
|
|
35
|
+
"typings": "types/smt-select.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
"./package.json": {
|
|
38
|
+
"default": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./types/smt-select.d.ts",
|
|
42
|
+
"default": "./fesm2022/smt-select.mjs"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { OnDestroy, EventEmitter, ElementRef, Renderer2 } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
interface SmtSelectOption {
|
|
5
|
+
value: any;
|
|
6
|
+
label: string;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
declare enum SmtVisibilityType {
|
|
10
|
+
HIDDEN = "HIDDEN",
|
|
11
|
+
READONLY = "READONLY",
|
|
12
|
+
ENABLED = "ENABLED"
|
|
13
|
+
}
|
|
14
|
+
interface SmtSelectConfig {
|
|
15
|
+
fieldID?: string | number;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
virtualScroll?: boolean;
|
|
18
|
+
isMultiSelect?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare class SmtSelectComponent implements OnDestroy {
|
|
22
|
+
private el;
|
|
23
|
+
private renderer;
|
|
24
|
+
options: SmtSelectOption[];
|
|
25
|
+
config: SmtSelectConfig;
|
|
26
|
+
selectedValue: any | any[];
|
|
27
|
+
errorMessage?: string | null;
|
|
28
|
+
isInvalid: boolean;
|
|
29
|
+
visibility: SmtVisibilityType;
|
|
30
|
+
selectionChange: EventEmitter<any>;
|
|
31
|
+
pocketOpen: EventEmitter<boolean>;
|
|
32
|
+
pocketElement: ElementRef;
|
|
33
|
+
inputElement: ElementRef;
|
|
34
|
+
isPocketOpen: boolean;
|
|
35
|
+
isOpenReverse: boolean;
|
|
36
|
+
searchText: string;
|
|
37
|
+
private clickListener;
|
|
38
|
+
VisibilityType: typeof SmtVisibilityType;
|
|
39
|
+
constructor(el: ElementRef, renderer: Renderer2);
|
|
40
|
+
ngOnDestroy(): void;
|
|
41
|
+
get hasSelection(): boolean;
|
|
42
|
+
displayText(): string;
|
|
43
|
+
togglePocket(): void;
|
|
44
|
+
private openPocket;
|
|
45
|
+
private closePocket;
|
|
46
|
+
private addOutsideClickListener;
|
|
47
|
+
private removeOutsideClickListener;
|
|
48
|
+
private calculateDirection;
|
|
49
|
+
private focusInput;
|
|
50
|
+
onBlur(): void;
|
|
51
|
+
isSelected(option: SmtSelectOption): boolean;
|
|
52
|
+
onEnterPressed(): void;
|
|
53
|
+
selectOption(option: SmtSelectOption, event?: MouseEvent): void;
|
|
54
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SmtSelectComponent, never>;
|
|
55
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<SmtSelectComponent, "smt-select", never, { "options": { "alias": "options"; "required": false; }; "config": { "alias": "config"; "required": false; }; "selectedValue": { "alias": "selectedValue"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "isInvalid": { "alias": "isInvalid"; "required": false; }; "visibility": { "alias": "visibility"; "required": false; }; }, { "selectionChange": "selectionChange"; "pocketOpen": "pocketOpen"; }, never, never, true, never>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { SmtSelectComponent, SmtVisibilityType };
|
|
59
|
+
export type { SmtSelectConfig, SmtSelectOption };
|