ng-primitives 0.72.0 → 0.73.1
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/date-picker/date-range-picker/date-range-picker.d.ts +4 -1
- package/fesm2022/ng-primitives-a11y.mjs +1 -1
- package/fesm2022/ng-primitives-a11y.mjs.map +1 -1
- package/fesm2022/ng-primitives-date-picker.mjs +14 -4
- package/fesm2022/ng-primitives-date-picker.mjs.map +1 -1
- package/fesm2022/ng-primitives-select.mjs +690 -18
- package/fesm2022/ng-primitives-select.mjs.map +1 -1
- package/package.json +20 -20
- package/schematics/ng-generate/schema.d.ts +2 -1
- package/schematics/ng-generate/schema.json +1 -0
- package/schematics/ng-generate/templates/listbox/listbox.__fileSuffix@dasherize__.ts.template +24 -54
- package/schematics/ng-generate/templates/native-select/native-select.__fileSuffix@dasherize__.ts.template +47 -0
- package/schematics/ng-generate/templates/select/select.__fileSuffix@dasherize__.ts.template +198 -25
- package/select/config/select-config.d.ts +28 -0
- package/select/index.d.ts +5 -0
- package/select/native-select/native-select-state.d.ts +17 -0
- package/select/native-select/native-select.d.ts +22 -0
- package/select/select/select.d.ts +158 -8
- package/select/select-dropdown/select-dropdown.d.ts +17 -0
- package/select/select-option/select-option.d.ts +51 -0
- package/select/select-portal/select-portal.d.ts +37 -0
- package/toast/toast/toast.d.ts +1 -1
|
@@ -1,47 +1,49 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, booleanAttribute, Directive } from '@angular/core';
|
|
2
|
+
import { input, booleanAttribute, Directive, computed, HostListener, inject, ViewContainerRef, TemplateRef, Injector, signal, InjectionToken, output, afterRenderEffect } from '@angular/core';
|
|
3
3
|
import { setupFormControl } from 'ng-primitives/form-field';
|
|
4
|
-
import { setupInteractions } from 'ng-primitives/internal';
|
|
4
|
+
import { setupInteractions, observeResize, injectElementRef } from 'ng-primitives/internal';
|
|
5
5
|
import { uniqueId } from 'ng-primitives/utils';
|
|
6
6
|
import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
|
|
7
|
+
import { createOverlay } from 'ng-primitives/portal';
|
|
8
|
+
import { activeDescendantManager } from 'ng-primitives/a11y';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
* The state token
|
|
11
|
+
* The state token for the Select primitive.
|
|
10
12
|
*/
|
|
11
|
-
const
|
|
13
|
+
const NgpNativeSelectStateToken = createStateToken('Select');
|
|
12
14
|
/**
|
|
13
15
|
* Provides the Select state.
|
|
14
16
|
*/
|
|
15
|
-
const
|
|
17
|
+
const provideNativeSelectState = createStateProvider(NgpNativeSelectStateToken);
|
|
16
18
|
/**
|
|
17
19
|
* Injects the Select state.
|
|
18
20
|
*/
|
|
19
|
-
const
|
|
21
|
+
const injectNativeSelectState = createStateInjector(NgpNativeSelectStateToken);
|
|
20
22
|
/**
|
|
21
23
|
* The Select state registration function.
|
|
22
24
|
*/
|
|
23
|
-
const
|
|
25
|
+
const selectNativeSelectState = createState(NgpNativeSelectStateToken);
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
|
-
* Apply the `
|
|
28
|
+
* Apply the `ngpNativeSelect` directive to a select element that you want to enhance.
|
|
27
29
|
*/
|
|
28
|
-
class
|
|
30
|
+
class NgpNativeSelect {
|
|
29
31
|
constructor() {
|
|
30
32
|
/**
|
|
31
33
|
* The id of the select. If not provided, a unique id will be generated.
|
|
32
34
|
*/
|
|
33
|
-
this.id = input(uniqueId('ngp-select'));
|
|
35
|
+
this.id = input(uniqueId('ngp-native-select'));
|
|
34
36
|
/**
|
|
35
37
|
* Whether the select is disabled.
|
|
36
38
|
*/
|
|
37
39
|
this.disabled = input(false, {
|
|
38
|
-
alias: '
|
|
40
|
+
alias: 'ngpNativeSelectDisabled',
|
|
39
41
|
transform: booleanAttribute,
|
|
40
42
|
});
|
|
41
43
|
/**
|
|
42
44
|
* The select state.
|
|
43
45
|
*/
|
|
44
|
-
this.state =
|
|
46
|
+
this.state = selectNativeSelectState(this);
|
|
45
47
|
setupInteractions({
|
|
46
48
|
hover: true,
|
|
47
49
|
press: true,
|
|
@@ -51,25 +53,695 @@ class NgpSelect {
|
|
|
51
53
|
});
|
|
52
54
|
setupFormControl({ id: this.state.id, disabled: this.state.disabled });
|
|
53
55
|
}
|
|
56
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpNativeSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
57
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpNativeSelect, isStandalone: true, selector: "select[ngpNativeSelect]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpNativeSelectDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.disabled": "state.disabled() || null" } }, providers: [provideNativeSelectState()], exportAs: ["ngpNativeSelect"], ngImport: i0 }); }
|
|
58
|
+
}
|
|
59
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpNativeSelect, decorators: [{
|
|
60
|
+
type: Directive,
|
|
61
|
+
args: [{
|
|
62
|
+
selector: 'select[ngpNativeSelect]',
|
|
63
|
+
exportAs: 'ngpNativeSelect',
|
|
64
|
+
providers: [provideNativeSelectState()],
|
|
65
|
+
host: {
|
|
66
|
+
'[attr.disabled]': 'state.disabled() || null',
|
|
67
|
+
},
|
|
68
|
+
}]
|
|
69
|
+
}], ctorParameters: () => [] });
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The state token for the Select primitive.
|
|
73
|
+
*/
|
|
74
|
+
const NgpSelectStateToken = createStateToken('Select');
|
|
75
|
+
/**
|
|
76
|
+
* Provides the Select state.
|
|
77
|
+
*/
|
|
78
|
+
const provideSelectState = createStateProvider(NgpSelectStateToken);
|
|
79
|
+
/**
|
|
80
|
+
* Injects the Select state.
|
|
81
|
+
*/
|
|
82
|
+
const injectSelectState = createStateInjector(NgpSelectStateToken);
|
|
83
|
+
/**
|
|
84
|
+
* The Select state registration function.
|
|
85
|
+
*/
|
|
86
|
+
const selectState = createState(NgpSelectStateToken);
|
|
87
|
+
|
|
88
|
+
class NgpSelectDropdown {
|
|
89
|
+
constructor() {
|
|
90
|
+
/** Access the select state. */
|
|
91
|
+
this.state = injectSelectState();
|
|
92
|
+
/** The dimensions of the select. */
|
|
93
|
+
this.selectDimensions = observeResize(() => this.state().elementRef.nativeElement);
|
|
94
|
+
/**
|
|
95
|
+
* Access the element reference.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
this.elementRef = injectElementRef();
|
|
99
|
+
/** The id of the dropdown. */
|
|
100
|
+
this.id = input(uniqueId('ngp-select-dropdown'));
|
|
101
|
+
this.state().registerDropdown(this);
|
|
102
|
+
}
|
|
103
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectDropdown, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
104
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpSelectDropdown, isStandalone: true, selector: "[ngpSelectDropdown]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listbox" }, properties: { "id": "id()", "style.left.px": "state().overlay()?.position()?.x", "style.top.px": "state().overlay()?.position()?.y", "style.--ngp-select-transform-origin": "state().overlay()?.transformOrigin()", "style.--ngp-select-width.px": "selectDimensions().width" } }, exportAs: ["ngpSelectDropdown"], ngImport: i0 }); }
|
|
105
|
+
}
|
|
106
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectDropdown, decorators: [{
|
|
107
|
+
type: Directive,
|
|
108
|
+
args: [{
|
|
109
|
+
selector: '[ngpSelectDropdown]',
|
|
110
|
+
exportAs: 'ngpSelectDropdown',
|
|
111
|
+
host: {
|
|
112
|
+
role: 'listbox',
|
|
113
|
+
'[id]': 'id()',
|
|
114
|
+
'[style.left.px]': 'state().overlay()?.position()?.x',
|
|
115
|
+
'[style.top.px]': 'state().overlay()?.position()?.y',
|
|
116
|
+
'[style.--ngp-select-transform-origin]': 'state().overlay()?.transformOrigin()',
|
|
117
|
+
'[style.--ngp-select-width.px]': 'selectDimensions().width',
|
|
118
|
+
},
|
|
119
|
+
}]
|
|
120
|
+
}], ctorParameters: () => [] });
|
|
121
|
+
|
|
122
|
+
class NgpSelectOption {
|
|
123
|
+
constructor() {
|
|
124
|
+
/** Access the select state. */
|
|
125
|
+
this.state = injectSelectState();
|
|
126
|
+
/**
|
|
127
|
+
* The element reference of the option.
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
this.elementRef = injectElementRef();
|
|
131
|
+
/** The id of the option. */
|
|
132
|
+
this.id = input(uniqueId('ngp-select-option'));
|
|
133
|
+
/** @required The value of the option. */
|
|
134
|
+
this.value = input(undefined, {
|
|
135
|
+
alias: 'ngpSelectOptionValue',
|
|
136
|
+
});
|
|
137
|
+
/** The disabled state of the option. */
|
|
138
|
+
this.disabled = input(false, {
|
|
139
|
+
alias: 'ngpSelectOptionDisabled',
|
|
140
|
+
transform: booleanAttribute,
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Whether this option is the active descendant.
|
|
144
|
+
* @internal
|
|
145
|
+
*/
|
|
146
|
+
this.active = computed(() => this.state().activeDescendantManager.activeDescendant() === this.id());
|
|
147
|
+
/** Whether this option is selected. */
|
|
148
|
+
this.selected = computed(() => {
|
|
149
|
+
const value = this.value();
|
|
150
|
+
if (!value) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (this.state().multiple()) {
|
|
154
|
+
return (Array.isArray(value) && value.some(v => this.state().compareWith()(v, this.state().value())));
|
|
155
|
+
}
|
|
156
|
+
return this.state().compareWith()(value, this.state().value());
|
|
157
|
+
});
|
|
158
|
+
this.state().registerOption(this);
|
|
159
|
+
setupInteractions({
|
|
160
|
+
hover: true,
|
|
161
|
+
press: true,
|
|
162
|
+
disabled: this.disabled,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
ngOnInit() {
|
|
166
|
+
if (this.value() === undefined) {
|
|
167
|
+
throw new Error('ngpSelectOption: The value input is required. Please provide a value for the option.');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
ngOnDestroy() {
|
|
171
|
+
this.state().unregisterOption(this);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Select the option.
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
select() {
|
|
178
|
+
if (this.disabled()) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.state().toggleOption(this);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Scroll the option into view.
|
|
185
|
+
* @internal
|
|
186
|
+
*/
|
|
187
|
+
scrollIntoView() {
|
|
188
|
+
this.elementRef.nativeElement.scrollIntoView({ block: 'nearest' });
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whenever the pointer enters the option, activate it.
|
|
192
|
+
* @internal
|
|
193
|
+
*/
|
|
194
|
+
onPointerEnter() {
|
|
195
|
+
this.state().activeDescendantManager.activate(this);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Whenever the pointer leaves the option, deactivate it.
|
|
199
|
+
* @internal
|
|
200
|
+
*/
|
|
201
|
+
onPointerLeave() {
|
|
202
|
+
this.state().activeDescendantManager.activate(undefined);
|
|
203
|
+
}
|
|
204
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectOption, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
205
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpSelectOption, isStandalone: true, selector: "[ngpSelectOption]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "ngpSelectOptionValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpSelectOptionDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "click": "select()", "pointerenter": "onPointerEnter()", "pointerleave": "onPointerLeave()" }, properties: { "id": "id()", "attr.tabindex": "-1", "attr.aria-selected": "selected() ? \"true\" : undefined", "attr.data-selected": "selected() ? \"\" : undefined", "attr.data-active": "active() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined" } }, exportAs: ["ngpSelectOption"], ngImport: i0 }); }
|
|
206
|
+
}
|
|
207
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectOption, decorators: [{
|
|
208
|
+
type: Directive,
|
|
209
|
+
args: [{
|
|
210
|
+
selector: '[ngpSelectOption]',
|
|
211
|
+
exportAs: 'ngpSelectOption',
|
|
212
|
+
host: {
|
|
213
|
+
role: 'option',
|
|
214
|
+
'[id]': 'id()',
|
|
215
|
+
'[attr.tabindex]': '-1',
|
|
216
|
+
'[attr.aria-selected]': 'selected() ? "true" : undefined',
|
|
217
|
+
'[attr.data-selected]': 'selected() ? "" : undefined',
|
|
218
|
+
'[attr.data-active]': 'active() ? "" : undefined',
|
|
219
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
220
|
+
'(click)': 'select()',
|
|
221
|
+
},
|
|
222
|
+
}]
|
|
223
|
+
}], ctorParameters: () => [], propDecorators: { onPointerEnter: [{
|
|
224
|
+
type: HostListener,
|
|
225
|
+
args: ['pointerenter']
|
|
226
|
+
}], onPointerLeave: [{
|
|
227
|
+
type: HostListener,
|
|
228
|
+
args: ['pointerleave']
|
|
229
|
+
}] } });
|
|
230
|
+
|
|
231
|
+
class NgpSelectPortal {
|
|
232
|
+
constructor() {
|
|
233
|
+
/** Access the select state. */
|
|
234
|
+
this.state = injectSelectState();
|
|
235
|
+
/** Access the view container reference. */
|
|
236
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
237
|
+
/** Access the template reference. */
|
|
238
|
+
this.templateRef = inject(TemplateRef);
|
|
239
|
+
/** Access the injector. */
|
|
240
|
+
this.injector = inject(Injector);
|
|
241
|
+
/**
|
|
242
|
+
* The overlay that manages the popover
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
this.overlay = signal(null);
|
|
246
|
+
this.state().registerPortal(this);
|
|
247
|
+
}
|
|
248
|
+
/** Cleanup the portal. */
|
|
249
|
+
ngOnDestroy() {
|
|
250
|
+
this.overlay()?.destroy();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Attach the portal.
|
|
254
|
+
* @internal
|
|
255
|
+
*/
|
|
256
|
+
show() {
|
|
257
|
+
// Create the overlay if it doesn't exist yet
|
|
258
|
+
if (!this.overlay()) {
|
|
259
|
+
this.createOverlay();
|
|
260
|
+
}
|
|
261
|
+
// Show the overlay
|
|
262
|
+
return this.overlay().show();
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Detach the portal.
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
async detach() {
|
|
269
|
+
this.overlay()?.hide();
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Create the overlay that will contain the dropdown
|
|
273
|
+
*/
|
|
274
|
+
createOverlay() {
|
|
275
|
+
// Create config for the overlay
|
|
276
|
+
const config = {
|
|
277
|
+
content: this.templateRef,
|
|
278
|
+
viewContainerRef: this.viewContainerRef,
|
|
279
|
+
triggerElement: this.state().elementRef.nativeElement,
|
|
280
|
+
injector: this.injector,
|
|
281
|
+
placement: this.state().placement(),
|
|
282
|
+
closeOnOutsideClick: true,
|
|
283
|
+
closeOnEscape: true,
|
|
284
|
+
restoreFocus: false,
|
|
285
|
+
scrollBehaviour: 'reposition',
|
|
286
|
+
container: this.state().container(),
|
|
287
|
+
};
|
|
288
|
+
this.overlay.set(createOverlay(config));
|
|
289
|
+
}
|
|
290
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
291
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpSelectPortal, isStandalone: true, selector: "[ngpSelectPortal]", exportAs: ["ngpSelectPortal"], ngImport: i0 }); }
|
|
292
|
+
}
|
|
293
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelectPortal, decorators: [{
|
|
294
|
+
type: Directive,
|
|
295
|
+
args: [{
|
|
296
|
+
selector: '[ngpSelectPortal]',
|
|
297
|
+
exportAs: 'ngpSelectPortal',
|
|
298
|
+
}]
|
|
299
|
+
}], ctorParameters: () => [] });
|
|
300
|
+
|
|
301
|
+
const defaultSelectConfig = {
|
|
302
|
+
placement: 'bottom',
|
|
303
|
+
container: null,
|
|
304
|
+
};
|
|
305
|
+
const NgpSelectConfigToken = new InjectionToken('NgpSelectConfigToken');
|
|
306
|
+
/**
|
|
307
|
+
* Provide the default Select configuration
|
|
308
|
+
* @param config The Select configuration
|
|
309
|
+
* @returns The provider
|
|
310
|
+
*/
|
|
311
|
+
function provideSelectConfig(config) {
|
|
312
|
+
return [
|
|
313
|
+
{
|
|
314
|
+
provide: NgpSelectConfigToken,
|
|
315
|
+
useValue: { ...defaultSelectConfig, ...config },
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Inject the Select configuration
|
|
321
|
+
* @returns The global Select configuration
|
|
322
|
+
*/
|
|
323
|
+
function injectSelectConfig() {
|
|
324
|
+
return inject(NgpSelectConfigToken, { optional: true }) ?? defaultSelectConfig;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
class NgpSelect {
|
|
328
|
+
constructor() {
|
|
329
|
+
/** Access the select configuration. */
|
|
330
|
+
this.config = injectSelectConfig();
|
|
331
|
+
/** @internal Access the select element. */
|
|
332
|
+
this.elementRef = injectElementRef();
|
|
333
|
+
/** Access the injector. */
|
|
334
|
+
this.injector = inject(Injector);
|
|
335
|
+
/** The unique id of the select. */
|
|
336
|
+
this.id = input(uniqueId('ngp-select'));
|
|
337
|
+
/** The value of the select. */
|
|
338
|
+
this.value = input(undefined, {
|
|
339
|
+
alias: 'ngpSelectValue',
|
|
340
|
+
});
|
|
341
|
+
/** Event emitted when the value changes. */
|
|
342
|
+
this.valueChange = output({
|
|
343
|
+
alias: 'ngpSelectValueChange',
|
|
344
|
+
});
|
|
345
|
+
/** Whether the select is multiple selection. */
|
|
346
|
+
this.multiple = input(false, {
|
|
347
|
+
alias: 'ngpSelectMultiple',
|
|
348
|
+
transform: booleanAttribute,
|
|
349
|
+
});
|
|
350
|
+
/** Whether the select is disabled. */
|
|
351
|
+
this.disabled = input(false, {
|
|
352
|
+
alias: 'ngpSelectDisabled',
|
|
353
|
+
transform: booleanAttribute,
|
|
354
|
+
});
|
|
355
|
+
/** Emit when the dropdown open state changes. */
|
|
356
|
+
this.openChange = output({
|
|
357
|
+
alias: 'ngpSelectOpenChange',
|
|
358
|
+
});
|
|
359
|
+
/** The comparator function used to compare options. */
|
|
360
|
+
this.compareWith = input(Object.is, {
|
|
361
|
+
alias: 'ngpSelectCompareWith',
|
|
362
|
+
});
|
|
363
|
+
/** The position of the dropdown. */
|
|
364
|
+
this.placement = input(this.config.placement, {
|
|
365
|
+
alias: 'ngpSelectDropdownPlacement',
|
|
366
|
+
});
|
|
367
|
+
/** The container for the dropdown. */
|
|
368
|
+
this.container = input(this.config.container, {
|
|
369
|
+
alias: 'ngpSelectDropdownContainer',
|
|
370
|
+
});
|
|
371
|
+
/**
|
|
372
|
+
* Store the select portal.
|
|
373
|
+
* @internal
|
|
374
|
+
*/
|
|
375
|
+
this.portal = signal(undefined);
|
|
376
|
+
/**
|
|
377
|
+
* Store the select dropdown.
|
|
378
|
+
* @internal
|
|
379
|
+
*/
|
|
380
|
+
this.dropdown = signal(undefined);
|
|
381
|
+
/**
|
|
382
|
+
* Store the select options.
|
|
383
|
+
* @internal
|
|
384
|
+
*/
|
|
385
|
+
this.options = signal([]);
|
|
386
|
+
/**
|
|
387
|
+
* Access the overlay
|
|
388
|
+
* @internal
|
|
389
|
+
*/
|
|
390
|
+
this.overlay = computed(() => this.portal()?.overlay());
|
|
391
|
+
/**
|
|
392
|
+
* The open state of the select.
|
|
393
|
+
* @internal
|
|
394
|
+
*/
|
|
395
|
+
this.open = computed(() => this.overlay()?.isOpen() ?? false);
|
|
396
|
+
/**
|
|
397
|
+
* The active key descendant manager.
|
|
398
|
+
* @internal
|
|
399
|
+
*/
|
|
400
|
+
this.activeDescendantManager = activeDescendantManager({
|
|
401
|
+
// we must wrap the signal in a computed to ensure it is not used before it is defined
|
|
402
|
+
disabled: computed(() => this.state.disabled()),
|
|
403
|
+
items: this.options,
|
|
404
|
+
});
|
|
405
|
+
/** The state of the select. */
|
|
406
|
+
this.state = selectState(this);
|
|
407
|
+
setupInteractions({
|
|
408
|
+
focus: true,
|
|
409
|
+
focusWithin: true,
|
|
410
|
+
hover: true,
|
|
411
|
+
press: true,
|
|
412
|
+
disabled: this.state.disabled,
|
|
413
|
+
});
|
|
414
|
+
setupFormControl({ id: this.state.id, disabled: this.state.disabled });
|
|
415
|
+
// any time the active descendant changes, ensure we scroll it into view
|
|
416
|
+
// perform after next render to ensure the DOM is updated
|
|
417
|
+
// e.g. the dropdown is open before the option is scrolled into view
|
|
418
|
+
afterRenderEffect({
|
|
419
|
+
write: () => {
|
|
420
|
+
const isPositioned = this.portal()?.overlay()?.isPositioned() ?? false;
|
|
421
|
+
const activeItem = this.activeDescendantManager.activeItem();
|
|
422
|
+
if (!isPositioned || !activeItem) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.activeDescendantManager.activeItem()?.scrollIntoView?.();
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Open the dropdown.
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
async openDropdown() {
|
|
434
|
+
if (this.state.disabled() || this.open()) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
this.openChange.emit(true);
|
|
438
|
+
await this.portal()?.show();
|
|
439
|
+
// if there is a selected option(s), set the active descendant to the first selected option
|
|
440
|
+
const selectedOption = this.options().find(option => this.isOptionSelected(option));
|
|
441
|
+
// if there is no selected option, set the active descendant to the first option
|
|
442
|
+
const targetOption = selectedOption ?? this.options()[0];
|
|
443
|
+
// if there is no target option, do nothing
|
|
444
|
+
if (!targetOption) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// activate the selected option or the first option
|
|
448
|
+
this.activeDescendantManager.activate(targetOption);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Close the dropdown.
|
|
452
|
+
* @internal
|
|
453
|
+
*/
|
|
454
|
+
closeDropdown() {
|
|
455
|
+
if (!this.open()) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.openChange.emit(false);
|
|
459
|
+
this.portal()?.detach();
|
|
460
|
+
// clear the active descendant
|
|
461
|
+
this.activeDescendantManager.reset();
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Toggle the dropdown.
|
|
465
|
+
* @internal
|
|
466
|
+
*/
|
|
467
|
+
async toggleDropdown() {
|
|
468
|
+
if (this.open()) {
|
|
469
|
+
this.closeDropdown();
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
await this.openDropdown();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Select an option.
|
|
477
|
+
* @param option The option to select.
|
|
478
|
+
* @internal
|
|
479
|
+
*/
|
|
480
|
+
selectOption(option) {
|
|
481
|
+
if (this.state.disabled()) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (!option) {
|
|
485
|
+
this.state.value.set(undefined);
|
|
486
|
+
this.closeDropdown();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (this.state.multiple()) {
|
|
490
|
+
// if the option is already selected, do nothing
|
|
491
|
+
if (this.isOptionSelected(option)) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const value = [...(this.state.value() ?? []), option.value()];
|
|
495
|
+
// add the option to the value
|
|
496
|
+
this.state.value.set(value);
|
|
497
|
+
this.valueChange.emit(value);
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
this.state.value.set(option.value());
|
|
501
|
+
this.valueChange.emit(option.value());
|
|
502
|
+
// close the dropdown on single selection
|
|
503
|
+
this.closeDropdown();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Deselect an option.
|
|
508
|
+
* @param option The option to deselect.
|
|
509
|
+
* @internal
|
|
510
|
+
*/
|
|
511
|
+
deselectOption(option) {
|
|
512
|
+
// if the select is disabled or the option is not selected, do nothing
|
|
513
|
+
// if the select is single selection, we don't allow deselecting
|
|
514
|
+
if (this.state.disabled() || !this.isOptionSelected(option) || !this.state.multiple()) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const values = this.state.value() ?? [];
|
|
518
|
+
const newValue = values.filter(v => !this.state.compareWith()(v, option.value()));
|
|
519
|
+
// remove the option from the value
|
|
520
|
+
this.state.value.set(newValue);
|
|
521
|
+
this.valueChange.emit(newValue);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Toggle the selection of an option.
|
|
525
|
+
* @param option The option to toggle.
|
|
526
|
+
* @internal
|
|
527
|
+
*/
|
|
528
|
+
toggleOption(option) {
|
|
529
|
+
if (this.state.disabled()) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// if the state is single selection, we don't allow toggling
|
|
533
|
+
if (!this.state.multiple()) {
|
|
534
|
+
// always select the option in single selection mode even if it is already selected so that we update the input
|
|
535
|
+
this.selectOption(option);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
// otherwise toggle the option
|
|
539
|
+
if (this.isOptionSelected(option)) {
|
|
540
|
+
this.deselectOption(option);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
this.selectOption(option);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Determine if an option is selected.
|
|
548
|
+
* @param option The option to check.
|
|
549
|
+
* @internal
|
|
550
|
+
*/
|
|
551
|
+
isOptionSelected(option) {
|
|
552
|
+
if (this.state.disabled()) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
const value = this.state.value();
|
|
556
|
+
if (!value) {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (this.state.multiple()) {
|
|
560
|
+
return value && value.some(v => this.state.compareWith()(option.value(), v));
|
|
561
|
+
}
|
|
562
|
+
return this.state.compareWith()(option.value(), value);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Activate the next option in the list if there is one.
|
|
566
|
+
* If there is no option currently active, activate the selected option or the first option.
|
|
567
|
+
* @internal
|
|
568
|
+
*/
|
|
569
|
+
activateNextOption() {
|
|
570
|
+
if (this.state.disabled()) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const options = this.options();
|
|
574
|
+
// if there are no options, do nothing
|
|
575
|
+
if (options.length === 0) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// if there is no active option, activate the first option
|
|
579
|
+
if (!this.activeDescendantManager.activeItem()) {
|
|
580
|
+
const selectedOption = options.find(option => this.isOptionSelected(option));
|
|
581
|
+
// if there is a selected option(s), set the active descendant to the first selected option
|
|
582
|
+
const targetOption = selectedOption ?? options[0];
|
|
583
|
+
this.activeDescendantManager.activate(targetOption);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// otherwise activate the next option
|
|
587
|
+
this.activeDescendantManager.next();
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Activate the previous option in the list if there is one.
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
activatePreviousOption() {
|
|
594
|
+
if (this.state.disabled()) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const options = this.options();
|
|
598
|
+
// if there are no options, do nothing
|
|
599
|
+
if (options.length === 0) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// if there is no active option, activate the last option
|
|
603
|
+
if (!this.activeDescendantManager.activeItem()) {
|
|
604
|
+
const selectedOption = options.find(option => this.isOptionSelected(option));
|
|
605
|
+
// if there is a selected option(s), set the active descendant to the first selected option
|
|
606
|
+
const targetOption = selectedOption ?? options[options.length - 1];
|
|
607
|
+
this.activeDescendantManager.activate(targetOption);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
// otherwise activate the previous option
|
|
611
|
+
this.activeDescendantManager.previous();
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Register the dropdown portal with the select.
|
|
615
|
+
* @param portal The dropdown portal.
|
|
616
|
+
* @internal
|
|
617
|
+
*/
|
|
618
|
+
registerPortal(portal) {
|
|
619
|
+
this.portal.set(portal);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Register the dropdown with the select.
|
|
623
|
+
* @param dropdown The dropdown to register.
|
|
624
|
+
* @internal
|
|
625
|
+
*/
|
|
626
|
+
registerDropdown(dropdown) {
|
|
627
|
+
this.dropdown.set(dropdown);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Register an option with the select.
|
|
631
|
+
* @param option The option to register.
|
|
632
|
+
* @internal
|
|
633
|
+
*/
|
|
634
|
+
registerOption(option) {
|
|
635
|
+
this.options.update(options => [...options, option]);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Unregister an option from the select.
|
|
639
|
+
* @param option The option to unregister.
|
|
640
|
+
* @internal
|
|
641
|
+
*/
|
|
642
|
+
unregisterOption(option) {
|
|
643
|
+
this.options.update(options => options.filter(o => o !== option));
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Focus the select.
|
|
647
|
+
* @internal
|
|
648
|
+
*/
|
|
649
|
+
focus() {
|
|
650
|
+
this.elementRef.nativeElement.focus();
|
|
651
|
+
}
|
|
652
|
+
/** Handle keydown events for accessibility. */
|
|
653
|
+
handleKeydown(event) {
|
|
654
|
+
switch (event.key) {
|
|
655
|
+
case 'ArrowDown':
|
|
656
|
+
if (this.open()) {
|
|
657
|
+
this.activateNextOption();
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
this.openDropdown();
|
|
661
|
+
}
|
|
662
|
+
event.preventDefault();
|
|
663
|
+
break;
|
|
664
|
+
case 'ArrowUp':
|
|
665
|
+
if (this.open()) {
|
|
666
|
+
this.activatePreviousOption();
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
this.openDropdown();
|
|
670
|
+
this.activeDescendantManager.last();
|
|
671
|
+
}
|
|
672
|
+
event.preventDefault();
|
|
673
|
+
break;
|
|
674
|
+
case 'Home':
|
|
675
|
+
if (this.open()) {
|
|
676
|
+
this.activeDescendantManager.first();
|
|
677
|
+
}
|
|
678
|
+
event.preventDefault();
|
|
679
|
+
break;
|
|
680
|
+
case 'End':
|
|
681
|
+
if (this.open()) {
|
|
682
|
+
this.activeDescendantManager.last();
|
|
683
|
+
}
|
|
684
|
+
event.preventDefault();
|
|
685
|
+
break;
|
|
686
|
+
case 'Enter':
|
|
687
|
+
if (this.open()) {
|
|
688
|
+
this.selectOption(this.activeDescendantManager.activeItem());
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
this.openDropdown();
|
|
692
|
+
}
|
|
693
|
+
event.preventDefault();
|
|
694
|
+
break;
|
|
695
|
+
case ' ':
|
|
696
|
+
this.toggleDropdown();
|
|
697
|
+
event.preventDefault();
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
onBlur(event) {
|
|
702
|
+
const relatedTarget = event.relatedTarget;
|
|
703
|
+
// if the blur was caused by focus moving to the dropdown, don't close
|
|
704
|
+
if (relatedTarget && this.dropdown()?.elementRef.nativeElement.contains(relatedTarget)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this.closeDropdown();
|
|
708
|
+
event.preventDefault();
|
|
709
|
+
}
|
|
54
710
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelect, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
55
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpSelect, isStandalone: true, selector: "
|
|
711
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpSelect, isStandalone: true, selector: "[ngpSelect]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "ngpSelectValue", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "ngpSelectMultiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpSelectDisabled", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "ngpSelectCompareWith", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpSelectDropdownPlacement", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "ngpSelectDropdownContainer", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "ngpSelectValueChange", openChange: "ngpSelectOpenChange" }, host: { attributes: { "role": "combobox" }, listeners: { "click": "toggleDropdown()", "keydown": "handleKeydown($event)", "blur": "onBlur($event)" }, properties: { "id": "state.id()", "attr.aria-expanded": "state.open()", "attr.aria-controls": "state.open() ? state.dropdown()?.id() : undefined", "attr.aria-activedescendant": "state.open() ? activeDescendantManager.activeDescendant() : undefined", "attr.tabindex": "state.disabled() ? -1 : 0", "attr.data-open": "state.open() ? \"\" : undefined", "attr.data-disabled": "state.disabled() ? \"\" : undefined", "attr.data-multiple": "state.multiple() ? \"\" : undefined" } }, providers: [provideSelectState()], exportAs: ["ngpSelect"], ngImport: i0 }); }
|
|
56
712
|
}
|
|
57
713
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSelect, decorators: [{
|
|
58
714
|
type: Directive,
|
|
59
715
|
args: [{
|
|
60
|
-
selector: '
|
|
716
|
+
selector: '[ngpSelect]',
|
|
61
717
|
exportAs: 'ngpSelect',
|
|
62
718
|
providers: [provideSelectState()],
|
|
63
719
|
host: {
|
|
64
|
-
|
|
65
|
-
'[
|
|
720
|
+
role: 'combobox',
|
|
721
|
+
'[id]': 'state.id()',
|
|
722
|
+
'[attr.aria-expanded]': 'state.open()',
|
|
723
|
+
'[attr.aria-controls]': 'state.open() ? state.dropdown()?.id() : undefined',
|
|
724
|
+
'[attr.aria-activedescendant]': 'state.open() ? activeDescendantManager.activeDescendant() : undefined',
|
|
725
|
+
'[attr.tabindex]': 'state.disabled() ? -1 : 0',
|
|
726
|
+
'[attr.data-open]': 'state.open() ? "" : undefined',
|
|
727
|
+
'[attr.data-disabled]': 'state.disabled() ? "" : undefined',
|
|
728
|
+
'[attr.data-multiple]': 'state.multiple() ? "" : undefined',
|
|
66
729
|
},
|
|
67
730
|
}]
|
|
68
|
-
}], ctorParameters: () => []
|
|
731
|
+
}], ctorParameters: () => [], propDecorators: { toggleDropdown: [{
|
|
732
|
+
type: HostListener,
|
|
733
|
+
args: ['click']
|
|
734
|
+
}], handleKeydown: [{
|
|
735
|
+
type: HostListener,
|
|
736
|
+
args: ['keydown', ['$event']]
|
|
737
|
+
}], onBlur: [{
|
|
738
|
+
type: HostListener,
|
|
739
|
+
args: ['blur', ['$event']]
|
|
740
|
+
}] } });
|
|
69
741
|
|
|
70
742
|
/**
|
|
71
743
|
* Generated bundle index. Do not edit.
|
|
72
744
|
*/
|
|
73
745
|
|
|
74
|
-
export { NgpSelect, injectSelectState, provideSelectState };
|
|
746
|
+
export { NgpNativeSelect, NgpSelect, NgpSelectDropdown, NgpSelectOption, NgpSelectPortal, injectNativeSelectState, injectSelectState, provideNativeSelectState, provideSelectState };
|
|
75
747
|
//# sourceMappingURL=ng-primitives-select.mjs.map
|