@bootkit/ng0 0.0.0-alpha.21 → 0.0.0-alpha.23
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/common/index.d.ts +125 -7
- package/components/dropdown/index.d.ts +87 -25
- package/components/list/index.d.ts +138 -45
- package/components/select/index.d.ts +32 -25
- package/data/index.d.ts +25 -122
- package/fesm2022/bootkit-ng0-common.mjs +145 -11
- package/fesm2022/bootkit-ng0-common.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-components-dropdown.mjs +208 -95
- package/fesm2022/bootkit-ng0-components-dropdown.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-components-list.mjs +287 -144
- package/fesm2022/bootkit-ng0-components-list.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-components-select.mjs +168 -236
- package/fesm2022/bootkit-ng0-components-select.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-data.mjs +49 -114
- package/fesm2022/bootkit-ng0-data.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-localization-locales.mjs +2 -16
- package/fesm2022/bootkit-ng0-localization-locales.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-localization.mjs +42 -44
- package/fesm2022/bootkit-ng0-localization.mjs.map +1 -1
- package/fesm2022/bootkit-ng0-routing.mjs +80 -0
- package/fesm2022/bootkit-ng0-routing.mjs.map +1 -0
- package/localization/index.d.ts +29 -23
- package/package.json +9 -5
- package/routing/index.d.ts +124 -0
|
@@ -1,31 +1,40 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DOCUMENT, ChangeDetectorRef, signal, input, booleanAttribute, TemplateRef, forwardRef, HostListener, ContentChild, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
2
|
+
import { inject, DOCUMENT, Renderer2, DestroyRef, ChangeDetectorRef, signal, computed, ElementRef, input, booleanAttribute, EventEmitter, effect, TemplateRef, forwardRef, HostListener, Output, ContentChild, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
-
import { dataSourceAttribute,
|
|
5
|
+
import { dataSourceAttribute, DataRequest } from '@bootkit/ng0/data';
|
|
6
6
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
7
7
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { LocalizationService, defaultValueFormatter, ValueFormatterAttribute } from '@bootkit/ng0/localization';
|
|
8
|
+
import { defaultEqualityComparer, equalityComparerAttribute, defaultValueWriter, valueWriterAttribute, noopFilter, filterPredicateAttribute, CssClassAttribute, sequentialIdGenerator } from '@bootkit/ng0/common';
|
|
9
|
+
import { LocalizationService, defaultObjectFormatter, objectFormatterAttribute } from '@bootkit/ng0/localization';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Select component that allows users to choose an option from a dropdown list.
|
|
14
13
|
*/
|
|
15
14
|
class ListComponent {
|
|
16
|
-
_el;
|
|
17
|
-
_renderer;
|
|
18
|
-
_destroyRef;
|
|
19
15
|
_document = inject(DOCUMENT);
|
|
20
16
|
_ls = inject(LocalizationService);
|
|
17
|
+
_renderer = inject(Renderer2);
|
|
18
|
+
_destroyRef = inject(DestroyRef);
|
|
21
19
|
_changeDetector = inject(ChangeDetectorRef);
|
|
22
|
-
_value =
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
_value = undefined;
|
|
21
|
+
_changeCallback;
|
|
22
|
+
_touchCallback;
|
|
23
|
+
_selectedIndices = new Set();
|
|
25
24
|
_items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
|
|
26
25
|
_isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_isDisabled" }] : []));
|
|
27
26
|
_activeOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeOptionIndex" }] : []));
|
|
28
27
|
_itemTemplate;
|
|
28
|
+
_ariaActiveDescendant = computed(() => {
|
|
29
|
+
if (this.focus() == 'activeDescendant' && this._activeOptionIndex() > -1 && this._items().length) {
|
|
30
|
+
return this._items()[this._activeOptionIndex()].id;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}, ...(ngDevMode ? [{ debugName: "_ariaActiveDescendant" }] : []));
|
|
34
|
+
/**
|
|
35
|
+
* Reference to the host element
|
|
36
|
+
*/
|
|
37
|
+
elementRef = inject((ElementRef));
|
|
29
38
|
/**
|
|
30
39
|
* The data source for the select component.
|
|
31
40
|
* This can be an array of data, a function that returns an observable of data,
|
|
@@ -44,80 +53,192 @@ class ListComponent {
|
|
|
44
53
|
* Indicates whether to show selection indicator (checkbox/radio) next to each item.
|
|
45
54
|
* Default is false.
|
|
46
55
|
*/
|
|
47
|
-
|
|
56
|
+
showSelectionIndicator = input(false, ...(ngDevMode ? [{ debugName: "showSelectionIndicator", transform: booleanAttribute }] : [{
|
|
48
57
|
transform: booleanAttribute
|
|
49
58
|
}]));
|
|
50
59
|
/**
|
|
51
60
|
* A custom comparer function or the name of a field for comparing two objects.
|
|
52
61
|
*/
|
|
53
|
-
compareBy = input(
|
|
54
|
-
transform:
|
|
62
|
+
compareBy = input(defaultEqualityComparer, ...(ngDevMode ? [{ debugName: "compareBy", transform: equalityComparerAttribute }] : [{
|
|
63
|
+
transform: equalityComparerAttribute
|
|
55
64
|
}]));
|
|
56
65
|
/**
|
|
57
66
|
* Custom format function to convert an item to a string for display.
|
|
58
67
|
* Default converts the item to a string using its toString method.
|
|
59
68
|
*/
|
|
60
|
-
formatBy = input(
|
|
61
|
-
transform:
|
|
69
|
+
formatBy = input(defaultObjectFormatter, ...(ngDevMode ? [{ debugName: "formatBy", transform: objectFormatterAttribute(this._ls.get()) }] : [{
|
|
70
|
+
transform: objectFormatterAttribute(this._ls.get())
|
|
62
71
|
}]));
|
|
63
72
|
/**
|
|
64
73
|
* Custom value extractor function to extract the value of any object while writing values.
|
|
65
74
|
*/
|
|
66
|
-
writeBy = input(
|
|
67
|
-
transform:
|
|
75
|
+
writeBy = input(defaultValueWriter, ...(ngDevMode ? [{ debugName: "writeBy", transform: valueWriterAttribute }] : [{
|
|
76
|
+
transform: valueWriterAttribute
|
|
68
77
|
}]));
|
|
69
78
|
/**
|
|
70
79
|
* A custom filter predicate function to filter items based on a search string.
|
|
71
80
|
* Default checks if the item's string representation contains the filter string (case-insensitive).
|
|
72
81
|
* The filter predicate can be a function or a string representing the property name to filter.
|
|
73
82
|
*/
|
|
74
|
-
filterBy = input(
|
|
75
|
-
transform:
|
|
83
|
+
filterBy = input(noopFilter, ...(ngDevMode ? [{ debugName: "filterBy", transform: filterPredicateAttribute }] : [{
|
|
84
|
+
transform: filterPredicateAttribute
|
|
76
85
|
}]));
|
|
77
86
|
/**
|
|
78
87
|
* CSS class or classes to apply to the list container.
|
|
79
88
|
* Default is undefined.
|
|
80
89
|
*/
|
|
81
|
-
itemClass = input((item) =>
|
|
90
|
+
itemClass = input((item) => ['ng0-list-item'], ...(ngDevMode ? [{ debugName: "itemClass", transform: CssClassAttribute }] : [{
|
|
82
91
|
transform: CssClassAttribute
|
|
83
92
|
}]));
|
|
93
|
+
/**
|
|
94
|
+
* Defines the focus behavior of the list component.
|
|
95
|
+
* - 'none': No keyboard interaction is possible. The list cannot be focused.
|
|
96
|
+
* - 'roving': Roving tabindex is enabled. The list can be focused and the active item is tabbable.
|
|
97
|
+
* - 'activeDescendant': The list can be focused, but no item is tabbable. The active item is indicated using aria-activedescendant.
|
|
98
|
+
* @default 'activeDescendant'.
|
|
99
|
+
*/
|
|
84
100
|
focus = input('activeDescendant', ...(ngDevMode ? [{ debugName: "focus" }] : []));
|
|
101
|
+
/**
|
|
102
|
+
* Custom id generator function to generate unique ids for each item.
|
|
103
|
+
* Default generates sequential ids with the prefix 'ng0-list-item-'.
|
|
104
|
+
* If set to undefined, no ids will be generated.
|
|
105
|
+
* @default sequentialIdGenerator('ng0-list-item-')
|
|
106
|
+
*/
|
|
85
107
|
idGenerator = input(sequentialIdGenerator('ng0-list-item-'), ...(ngDevMode ? [{ debugName: "idGenerator" }] : []));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Event emitted when the selection state of an item changes by user interaction.
|
|
110
|
+
*/
|
|
111
|
+
selectionChange = new EventEmitter();
|
|
112
|
+
constructor() {
|
|
113
|
+
effect(() => {
|
|
114
|
+
let source = this.source(); // track source
|
|
115
|
+
this._activeOptionIndex.set(-1);
|
|
116
|
+
this._selectedIndices.clear();
|
|
117
|
+
this._loadItems();
|
|
118
|
+
});
|
|
90
119
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Gets the items of the list component.
|
|
122
|
+
* @returns A readonly array of the items in the list.
|
|
123
|
+
*/
|
|
124
|
+
items() {
|
|
125
|
+
return [...this._items()];
|
|
94
126
|
}
|
|
95
127
|
/**
|
|
96
128
|
* Sets an option as active
|
|
129
|
+
* @param index The index of the option to set as active.
|
|
130
|
+
* @param scrollIntoView Whether to scroll the active option into view. Default is true.
|
|
131
|
+
* @returns void
|
|
97
132
|
*/
|
|
98
|
-
active(index) {
|
|
99
|
-
if (index < 0) {
|
|
100
|
-
throw Error();
|
|
133
|
+
active(index, scrollIntoView = true) {
|
|
134
|
+
if (index < 0 || index >= this._items().length) {
|
|
135
|
+
throw Error('Index out of range');
|
|
101
136
|
}
|
|
102
137
|
this._activeOptionIndex.set(index);
|
|
103
|
-
|
|
138
|
+
if (scrollIntoView) {
|
|
139
|
+
this.scrollIntoView(this._activeOptionIndex(), 'nearest');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Selects an option by index
|
|
144
|
+
* @param index The index of the option to select.
|
|
145
|
+
* @returns void
|
|
146
|
+
*/
|
|
147
|
+
select(index) {
|
|
148
|
+
this._verifyIndexRange(index);
|
|
149
|
+
if (this._selectedIndices.has(index)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
if (!this.multiple()) {
|
|
154
|
+
this._selectedIndices.clear();
|
|
155
|
+
}
|
|
156
|
+
this._selectedIndices.add(index);
|
|
157
|
+
}
|
|
158
|
+
if (this.multiple()) {
|
|
159
|
+
this._value = [];
|
|
160
|
+
for (const idx of this._selectedIndices) {
|
|
161
|
+
this._value.push(this.writeBy()(this._items()[idx].value));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this._value = this.writeBy()(this._items()[index].value);
|
|
166
|
+
}
|
|
167
|
+
this._changeCallback?.(this._value);
|
|
168
|
+
this._changeDetector.markForCheck();
|
|
104
169
|
}
|
|
105
170
|
/**
|
|
106
|
-
*
|
|
107
|
-
* @param
|
|
171
|
+
* Deselects an option by index
|
|
172
|
+
* @param index The index of the option to deselect.
|
|
108
173
|
* @returns void
|
|
109
174
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.
|
|
175
|
+
deselect(index) {
|
|
176
|
+
this._verifyIndexRange(index);
|
|
177
|
+
if (this._selectedIndices.has(index)) {
|
|
178
|
+
this._selectedIndices.delete(index);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (this.multiple()) {
|
|
184
|
+
this._value = [];
|
|
185
|
+
for (const idx of this._selectedIndices) {
|
|
186
|
+
this._value.push(this.writeBy()(this._items()[idx].value));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this._value = undefined;
|
|
191
|
+
}
|
|
192
|
+
this._changeCallback?.(this._value);
|
|
113
193
|
this._changeDetector.markForCheck();
|
|
114
194
|
}
|
|
115
195
|
/**
|
|
116
|
-
*
|
|
196
|
+
* Toggles the selection state of an option by index
|
|
197
|
+
* @param index The index of the option to toggle.
|
|
117
198
|
* @returns void
|
|
118
199
|
*/
|
|
119
|
-
|
|
120
|
-
this.
|
|
200
|
+
toggle(index) {
|
|
201
|
+
if (this.isSelected(index)) {
|
|
202
|
+
this.deselect(index);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.select(index);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Checks if an option is selected.
|
|
210
|
+
* @param index The index of the option to check.
|
|
211
|
+
* @returns True if the option is selected, false otherwise.
|
|
212
|
+
*/
|
|
213
|
+
isSelected(index) {
|
|
214
|
+
return this._selectedIndices.has(index);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Checks if an option is active.
|
|
218
|
+
* @param index The index of the option to check.
|
|
219
|
+
* @returns True if the option is active, false otherwise.
|
|
220
|
+
*/
|
|
221
|
+
isActive(index) {
|
|
222
|
+
return this._activeOptionIndex() === index;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sets the value of the list component.
|
|
226
|
+
* @param value The value to set. Can be a single value or an array of values in multiple selection mode.
|
|
227
|
+
*/
|
|
228
|
+
set(value) {
|
|
229
|
+
this._setValue(value, true);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Gets the currently selected indices.
|
|
233
|
+
* @returns An array of the currently selected indices.
|
|
234
|
+
* @description
|
|
235
|
+
* - In single selection mode, the array will contain at most one item.
|
|
236
|
+
* - In multiple selection mode, the array can contain multiple items.
|
|
237
|
+
* - Changing the selection should be done using select(), deselect(), or toggle() methods to ensure proper event emission and state management.
|
|
238
|
+
* - Direct manipulation of the returned array will not affect the component's state.
|
|
239
|
+
*/
|
|
240
|
+
selectedIndices() {
|
|
241
|
+
return Array.from(this._selectedIndices);
|
|
121
242
|
}
|
|
122
243
|
/**
|
|
123
244
|
* Scrolls the item at the specified index into view within the dropdown list.
|
|
@@ -127,72 +248,121 @@ class ListComponent {
|
|
|
127
248
|
* Default is 'nearest'.
|
|
128
249
|
* @param behavior The scrolling behavior.
|
|
129
250
|
*/
|
|
130
|
-
|
|
251
|
+
scrollIntoView(index, position, behavior) {
|
|
131
252
|
let item = this._items()[index];
|
|
132
253
|
let elm = this._document.getElementById(item.id);
|
|
133
254
|
elm.scrollIntoView({ block: position, behavior: behavior });
|
|
134
255
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
throw new Error('Index out of range');
|
|
142
|
-
}
|
|
143
|
-
let item = this._items()[index];
|
|
144
|
-
let writeValueBy = this.writeBy();
|
|
145
|
-
if (this.multiple()) {
|
|
146
|
-
item.selected = !item.selected;
|
|
147
|
-
let selectedValues = this._items().filter(x => x.selected).map(x => (x.value));
|
|
148
|
-
this._value.set(selectedValues);
|
|
256
|
+
writeValue(value) {
|
|
257
|
+
this._setValue(value, false);
|
|
258
|
+
}
|
|
259
|
+
_setValue(value, fireCallback) {
|
|
260
|
+
if (this.multiple() && value !== null && value !== undefined && !Array.isArray(value)) {
|
|
261
|
+
throw Error('invalid value. Expected an array in multiple selection mode.');
|
|
149
262
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
263
|
+
let compareBy = this.compareBy();
|
|
264
|
+
let findAndSelect = (v) => {
|
|
265
|
+
let index = this._items().findIndex(i => compareBy(i.value, v));
|
|
266
|
+
if (index > -1) {
|
|
267
|
+
this._selectedIndices.add(index);
|
|
153
268
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
item.selected = true;
|
|
157
|
-
this._value.set(itemValue);
|
|
158
|
-
}
|
|
159
|
-
this._onChangeCallback(this._value());
|
|
160
|
-
}
|
|
161
|
-
writeValue(v) {
|
|
162
|
-
let value;
|
|
269
|
+
};
|
|
270
|
+
this._selectedIndices.clear();
|
|
163
271
|
if (this.multiple()) {
|
|
164
|
-
if (Array.isArray(
|
|
165
|
-
value
|
|
166
|
-
}
|
|
167
|
-
else if (v === null || v === undefined) {
|
|
168
|
-
value = [];
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
throw Error('Provide an array or null as the value ng0-list component');
|
|
272
|
+
if (Array.isArray(value)) {
|
|
273
|
+
value.forEach(i => findAndSelect(i));
|
|
172
274
|
}
|
|
173
275
|
}
|
|
174
276
|
else {
|
|
175
|
-
value
|
|
277
|
+
findAndSelect(value);
|
|
176
278
|
}
|
|
177
|
-
this._value
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this._items().forEach(x => x.selected = value.some(y => compareBy(x.value, y)));
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
this._items().forEach(x => x.selected = compareBy(x.value, value));
|
|
279
|
+
this._value = value;
|
|
280
|
+
this._changeDetector.markForCheck();
|
|
281
|
+
if (fireCallback) {
|
|
282
|
+
this._changeCallback?.(value);
|
|
185
283
|
}
|
|
186
284
|
}
|
|
187
285
|
registerOnChange(fn) {
|
|
188
|
-
this.
|
|
286
|
+
this._changeCallback = fn;
|
|
189
287
|
}
|
|
190
288
|
registerOnTouched(fn) {
|
|
191
|
-
this.
|
|
289
|
+
this._touchCallback = fn;
|
|
192
290
|
}
|
|
193
291
|
setDisabledState(isDisabled) {
|
|
194
292
|
this._isDisabled.set(isDisabled);
|
|
195
293
|
}
|
|
294
|
+
_getItemTabIndex(index) {
|
|
295
|
+
let focus = this.focus();
|
|
296
|
+
if (this._isDisabled() || focus == 'none' || focus == 'activeDescendant') {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// roving
|
|
301
|
+
return this._activeOptionIndex() === index ? 0 : -1;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
_handleUserSelection(index, item) {
|
|
305
|
+
let selected;
|
|
306
|
+
this.active(index);
|
|
307
|
+
if (this.multiple() && this.isSelected(index)) {
|
|
308
|
+
this.deselect(index);
|
|
309
|
+
selected = false;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.select(index);
|
|
313
|
+
selected = true;
|
|
314
|
+
}
|
|
315
|
+
this.selectionChange.emit({
|
|
316
|
+
value: item.value,
|
|
317
|
+
index: index,
|
|
318
|
+
selected: selected,
|
|
319
|
+
selectedIndices: this.selectedIndices(),
|
|
320
|
+
list: this,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
_loadItems() {
|
|
324
|
+
var r = new DataRequest();
|
|
325
|
+
this.source().load(r).pipe(takeUntilDestroyed(this._destroyRef)).subscribe(res => {
|
|
326
|
+
let items = this._createItems(res.data);
|
|
327
|
+
this._items().push(...items);
|
|
328
|
+
});
|
|
329
|
+
// listen to changes
|
|
330
|
+
this.source().change.subscribe(e => {
|
|
331
|
+
let items = this._items();
|
|
332
|
+
e.changes.forEach(change => {
|
|
333
|
+
switch (change.type) {
|
|
334
|
+
case 'push':
|
|
335
|
+
this._items().push(...this._createItems(change.items));
|
|
336
|
+
break;
|
|
337
|
+
// case 'replace':
|
|
338
|
+
// change.replacements.forEach(({ index, value }) => {
|
|
339
|
+
// items[index] = { id: this.idGenerator()(value), value };
|
|
340
|
+
// });
|
|
341
|
+
// break;
|
|
342
|
+
// case 'remove':
|
|
343
|
+
// this._activeOptionIndex.set(-1);
|
|
344
|
+
// change.indices.forEach(i => {
|
|
345
|
+
// this.deselect(i);
|
|
346
|
+
// items.splice(i, 1)
|
|
347
|
+
// });
|
|
348
|
+
// break;
|
|
349
|
+
}
|
|
350
|
+
this._changeDetector.markForCheck();
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
_createItems(items) {
|
|
355
|
+
let idGenerator = this.idGenerator();
|
|
356
|
+
return items.map(x => ({
|
|
357
|
+
id: idGenerator(x),
|
|
358
|
+
value: x,
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
_onHostClick() {
|
|
362
|
+
if (this.focus() != 'none') {
|
|
363
|
+
this.elementRef.nativeElement.focus();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
196
366
|
_onKeydown(e, firedByFilter = false) {
|
|
197
367
|
if (this._isDisabled())
|
|
198
368
|
return;
|
|
@@ -200,22 +370,31 @@ class ListComponent {
|
|
|
200
370
|
if (optionsCount == 0) {
|
|
201
371
|
return;
|
|
202
372
|
}
|
|
373
|
+
let index = this._activeOptionIndex();
|
|
203
374
|
switch (e.key) {
|
|
204
375
|
case 'ArrowDown':
|
|
205
|
-
if (
|
|
206
|
-
this.active(
|
|
376
|
+
if (index < optionsCount - 1) {
|
|
377
|
+
this.active(index + 1);
|
|
207
378
|
}
|
|
208
379
|
e.preventDefault();
|
|
209
380
|
break;
|
|
210
381
|
case 'ArrowUp':
|
|
211
|
-
if (
|
|
212
|
-
this.active(
|
|
382
|
+
if (index > 0) {
|
|
383
|
+
this.active(index - 1);
|
|
213
384
|
}
|
|
214
385
|
e.preventDefault();
|
|
215
386
|
break;
|
|
387
|
+
case 'Tab': // Go to next item if roving focus is enabled
|
|
388
|
+
// if (this.focus() === 'roving' && index < optionsCount - 1) {
|
|
389
|
+
// this.active(index + 1);
|
|
390
|
+
// e.preventDefault();
|
|
391
|
+
// }
|
|
392
|
+
break;
|
|
216
393
|
case 'Enter':
|
|
217
|
-
|
|
218
|
-
|
|
394
|
+
if (index > -1) {
|
|
395
|
+
this._handleUserSelection(index, this._items()[index]);
|
|
396
|
+
}
|
|
397
|
+
// e.preventDefault();
|
|
219
398
|
break;
|
|
220
399
|
case 'Home':
|
|
221
400
|
this.active(0);
|
|
@@ -227,78 +406,42 @@ class ListComponent {
|
|
|
227
406
|
break;
|
|
228
407
|
}
|
|
229
408
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
_listenToDataSourceChanges() {
|
|
237
|
-
this.source().change.subscribe(e => {
|
|
238
|
-
let options = this._items();
|
|
239
|
-
e.changes.forEach(change => {
|
|
240
|
-
switch (change.type) {
|
|
241
|
-
case 'insert':
|
|
242
|
-
this._insertItems(change.index, ...change.items);
|
|
243
|
-
break;
|
|
244
|
-
case 'replace':
|
|
245
|
-
options[change.index].value = change.value;
|
|
246
|
-
break;
|
|
247
|
-
case 'remove':
|
|
248
|
-
options.splice(change.index, change.count);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
// this._changeDetector.markForCheck();
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
_insertItems(index, ...items) {
|
|
255
|
-
// let filter = this.filterBy()()
|
|
256
|
-
let idGenerator = this.idGenerator();
|
|
257
|
-
let compareBy = this.compareBy();
|
|
258
|
-
let value = this._value();
|
|
259
|
-
let isItemSelected = this.multiple() && Array.isArray(value) ?
|
|
260
|
-
(item) => value.some(x => compareBy(x.value, item)) :
|
|
261
|
-
(item) => compareBy(value, item);
|
|
262
|
-
var options = items.map(x => ({
|
|
263
|
-
id: idGenerator ? idGenerator(x) : undefined,
|
|
264
|
-
value: x,
|
|
265
|
-
selected: isItemSelected(x),
|
|
266
|
-
filtered: false,
|
|
267
|
-
}));
|
|
268
|
-
if (Number.isInteger(index)) {
|
|
269
|
-
this._items().splice(index, 0, ...options);
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
this._items().push(...options);
|
|
409
|
+
_verifyIndexRange(index) {
|
|
410
|
+
let optionsCount = this._items().length;
|
|
411
|
+
if (optionsCount == 0 || index < 0 || index > optionsCount - 1) {
|
|
412
|
+
throw new Error('Index out of range');
|
|
273
413
|
}
|
|
274
|
-
this._changeDetector.markForCheck();
|
|
275
414
|
}
|
|
276
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ListComponent, deps: [
|
|
277
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: ListComponent, isStandalone: true, selector: "ng0-list", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null },
|
|
415
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
416
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.2.1", type: ListComponent, isStandalone: true, selector: "ng0-list", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: true, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, showSelectionIndicator: { classPropertyName: "showSelectionIndicator", publicName: "showSelectionIndicator", isSignal: true, isRequired: false, transformFunction: null }, compareBy: { classPropertyName: "compareBy", publicName: "compareBy", isSignal: true, isRequired: false, transformFunction: null }, formatBy: { classPropertyName: "formatBy", publicName: "formatBy", isSignal: true, isRequired: false, transformFunction: null }, writeBy: { classPropertyName: "writeBy", publicName: "writeBy", isSignal: true, isRequired: false, transformFunction: null }, filterBy: { classPropertyName: "filterBy", publicName: "filterBy", isSignal: true, isRequired: false, transformFunction: null }, itemClass: { classPropertyName: "itemClass", publicName: "itemClass", isSignal: true, isRequired: false, transformFunction: null }, focus: { classPropertyName: "focus", publicName: "focus", isSignal: true, isRequired: false, transformFunction: null }, idGenerator: { classPropertyName: "idGenerator", publicName: "idGenerator", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "click": "_onHostClick()", "keydown": "_onKeydown($event)" }, properties: { "class.ng0-list-loading": "source().isLoading()", "attr.aria-activedescendant": "_ariaActiveDescendant()", "attr.disabled": "_isDisabled()", "attr.tabindex": "_isDisabled() || focus() === \"none\" ? undefined : \"0\"", "attr.aria-disabled": "_isDisabled()" } }, providers: [{
|
|
278
417
|
provide: NG_VALUE_ACCESSOR,
|
|
279
418
|
useExisting: forwardRef(() => ListComponent),
|
|
280
419
|
multi: true
|
|
281
|
-
}], queries: [{ propertyName: "_itemTemplate", first: true, predicate: TemplateRef, descendants: true }], exportAs: ["ng0List"], ngImport: i0, template: "@for (
|
|
420
|
+
}], queries: [{ propertyName: "_itemTemplate", first: true, predicate: TemplateRef, descendants: true }], exportAs: ["ng0List"], ngImport: i0, template: "@let formatter = formatBy();\r\n@let filter = filterBy();\r\n\r\n\r\n@for (item of _items(); track item.id) {\r\n@let selected= isSelected($index);\r\n@let active= isActive($index);\r\n\r\n@if(filter(item)) {\r\n<div\r\n [ngClass]=\"itemClass()(item)\"\r\n [attr.id]=\"item.id\"\r\n [class.active]=\"active\"\r\n [class.selected]=\"selected\"\r\n [attr.tabIndex]=\"_getItemTabIndex($index)\"\r\n (click)=\"_handleUserSelection($index, item)\">\r\n\r\n @if(_itemTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_itemTemplate; context: { $implicit: {\r\n id: item.id,\r\n value: item.value,\r\n selected: selected, \r\n active: active,\r\n }}\" />\r\n } @else {\r\n\r\n @if(showSelectionIndicator()) {\r\n <input class=\"form-check-input ng0-list-selection-indicator\"\r\n tabindex=\"-1\"\r\n [checked]=\"selected\"\r\n [attr.type]=\"multiple() ? 'checkbox' : 'radio'\"\r\n [attr.name]=\"item.id\"\r\n [attr.id]=\"item.id\">\r\n }\r\n\r\n {{formatter(item.value)}}\r\n\r\n }\r\n</div>\r\n}\r\n}", styles: [":host{display:block;overflow-y:auto;overflow-x:hidden;-webkit-user-select:none;user-select:none;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}:host.disabled{background-color:var(--bs-secondary-bg)}:host .ng0-list-item{display:flex;padding:.5rem}:host .ng0-list-item .ng0-list-selection-indicator{margin-inline-end:.5rem}:host .ng0-list-item.selected{background-color:var(--bs-primary);color:var(--bs-light)}:host .ng0-list-item.active:not(.selected){background-color:var(--bs-gray-300)}:host .ng0-list-item:hover:not(.selected):not(.disabled):not(.active){background-color:var(--bs-gray-100)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
282
421
|
}
|
|
283
422
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.2.1", ngImport: i0, type: ListComponent, decorators: [{
|
|
284
423
|
type: Component,
|
|
285
424
|
args: [{ selector: 'ng0-list', exportAs: 'ng0List', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
286
425
|
CommonModule,
|
|
287
|
-
OverlayModule,
|
|
288
426
|
], providers: [{
|
|
289
427
|
provide: NG_VALUE_ACCESSOR,
|
|
290
428
|
useExisting: forwardRef(() => ListComponent),
|
|
291
429
|
multi: true
|
|
292
430
|
}], host: {
|
|
293
431
|
'[class.ng0-list-loading]': 'source().isLoading()',
|
|
294
|
-
'[attr.aria-activedescendant]': '
|
|
432
|
+
'[attr.aria-activedescendant]': '_ariaActiveDescendant()',
|
|
295
433
|
'[attr.disabled]': '_isDisabled()',
|
|
296
|
-
'[attr.tabindex]': '_isDisabled() || focus() === "none" ?
|
|
434
|
+
'[attr.tabindex]': '_isDisabled() || focus() === "none" ? undefined : "0"',
|
|
297
435
|
'[attr.aria-disabled]': '_isDisabled()'
|
|
298
|
-
}, template: "@for (
|
|
299
|
-
}], ctorParameters: () => [
|
|
436
|
+
}, template: "@let formatter = formatBy();\r\n@let filter = filterBy();\r\n\r\n\r\n@for (item of _items(); track item.id) {\r\n@let selected= isSelected($index);\r\n@let active= isActive($index);\r\n\r\n@if(filter(item)) {\r\n<div\r\n [ngClass]=\"itemClass()(item)\"\r\n [attr.id]=\"item.id\"\r\n [class.active]=\"active\"\r\n [class.selected]=\"selected\"\r\n [attr.tabIndex]=\"_getItemTabIndex($index)\"\r\n (click)=\"_handleUserSelection($index, item)\">\r\n\r\n @if(_itemTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_itemTemplate; context: { $implicit: {\r\n id: item.id,\r\n value: item.value,\r\n selected: selected, \r\n active: active,\r\n }}\" />\r\n } @else {\r\n\r\n @if(showSelectionIndicator()) {\r\n <input class=\"form-check-input ng0-list-selection-indicator\"\r\n tabindex=\"-1\"\r\n [checked]=\"selected\"\r\n [attr.type]=\"multiple() ? 'checkbox' : 'radio'\"\r\n [attr.name]=\"item.id\"\r\n [attr.id]=\"item.id\">\r\n }\r\n\r\n {{formatter(item.value)}}\r\n\r\n }\r\n</div>\r\n}\r\n}", styles: [":host{display:block;overflow-y:auto;overflow-x:hidden;-webkit-user-select:none;user-select:none;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}:host.disabled{background-color:var(--bs-secondary-bg)}:host .ng0-list-item{display:flex;padding:.5rem}:host .ng0-list-item .ng0-list-selection-indicator{margin-inline-end:.5rem}:host .ng0-list-item.selected{background-color:var(--bs-primary);color:var(--bs-light)}:host .ng0-list-item.active:not(.selected){background-color:var(--bs-gray-300)}:host .ng0-list-item:hover:not(.selected):not(.disabled):not(.active){background-color:var(--bs-gray-100)}\n"] }]
|
|
437
|
+
}], ctorParameters: () => [], propDecorators: { _itemTemplate: [{
|
|
300
438
|
type: ContentChild,
|
|
301
439
|
args: [TemplateRef]
|
|
440
|
+
}], selectionChange: [{
|
|
441
|
+
type: Output
|
|
442
|
+
}], _onHostClick: [{
|
|
443
|
+
type: HostListener,
|
|
444
|
+
args: ['click']
|
|
302
445
|
}], _onKeydown: [{
|
|
303
446
|
type: HostListener,
|
|
304
447
|
args: ['keydown', ['$event']]
|