@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.
@@ -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, defaultBooleanValueComparer, BooleanValueComparerAttribute, defaultValueExtractor, ValueExtractorAttribute, stringFilter, FilterPredicateAttribute, DataRequest } from '@bootkit/ng0/data';
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 { OverlayModule } from '@angular/cdk/overlay';
9
- import { CssClassAttribute, sequentialIdGenerator } from '@bootkit/ng0/common';
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 = signal(undefined, ...(ngDevMode ? [{ debugName: "_value" }] : []));
23
- _onChangeCallback;
24
- _onTouchedCallback;
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
- showIndicator = input(false, ...(ngDevMode ? [{ debugName: "showIndicator", transform: booleanAttribute }] : [{
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(defaultBooleanValueComparer, ...(ngDevMode ? [{ debugName: "compareBy", transform: BooleanValueComparerAttribute }] : [{
54
- transform: BooleanValueComparerAttribute
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(defaultValueFormatter, ...(ngDevMode ? [{ debugName: "formatBy", transform: ValueFormatterAttribute(this._ls.get()) }] : [{
61
- transform: ValueFormatterAttribute(this._ls.get())
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(defaultValueExtractor, ...(ngDevMode ? [{ debugName: "writeBy", transform: ValueExtractorAttribute }] : [{
67
- transform: ValueExtractorAttribute
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(stringFilter, ...(ngDevMode ? [{ debugName: "filterBy", transform: FilterPredicateAttribute }] : [{
75
- transform: FilterPredicateAttribute
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) => undefined, ...(ngDevMode ? [{ debugName: "itemClass", transform: CssClassAttribute }] : [{
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
- constructor(_el, _renderer, _destroyRef) {
87
- this._el = _el;
88
- this._renderer = _renderer;
89
- this._destroyRef = _destroyRef;
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
- ngOnInit() {
92
- this._loadItems();
93
- this._listenToDataSourceChanges();
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
- // this.scrollItemIntoView(this._activeOptionIndex(), 'nearest');
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
- * Filters the list items based on the provided criteria.
107
- * @param params The filter parameters to apply.
171
+ * Deselects an option by index
172
+ * @param index The index of the option to deselect.
108
173
  * @returns void
109
174
  */
110
- filter(...params) {
111
- let filterBy = this.filterBy();
112
- this._items().forEach(x => x.filtered = !filterBy(x.value, ...params));
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
- * Clears any applied filters and shows all items in the list.
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
- clearFilter() {
120
- this._items().forEach(x => x.filtered = false);
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
- scrollItemIntoView(index, position, behavior) {
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
- * Toggles the selection of an option by index
137
- */
138
- toggleSelection(index) {
139
- let optionsCount = this._items().length;
140
- if (optionsCount == 0 || index < 0 || index > optionsCount - 1) {
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
- else {
151
- if (item.selected) {
152
- return;
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
- let itemValue = writeValueBy(item.value);
155
- this._items().forEach(x => x.selected = false);
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(v)) {
165
- value = v;
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 = v;
277
+ findAndSelect(value);
176
278
  }
177
- this._value.set(value);
178
- // Update selection state of items
179
- let compareBy = this.compareBy();
180
- if (this.multiple()) {
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._onChangeCallback = fn;
286
+ this._changeCallback = fn;
189
287
  }
190
288
  registerOnTouched(fn) {
191
- this._onTouchedCallback = fn;
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 (this._activeOptionIndex() < optionsCount - 1) {
206
- this.active(this._activeOptionIndex() + 1);
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 (this._activeOptionIndex() > 0) {
212
- this.active(this._activeOptionIndex() - 1);
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
- this.toggleSelection(this._activeOptionIndex());
218
- e.preventDefault();
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
- _loadItems() {
231
- var r = new DataRequest();
232
- this.source().load(r).pipe(takeUntilDestroyed(this._destroyRef)).subscribe(res => {
233
- this._insertItems(0, ...res.data);
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: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.DestroyRef }], target: i0.ɵɵFactoryTarget.Component });
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 }, showIndicator: { classPropertyName: "showIndicator", publicName: "showIndicator", 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 } }, host: { listeners: { "keydown": "_onKeydown($event)" }, properties: { "class.ng0-list-loading": "source().isLoading()", "attr.aria-activedescendant": "_activeOptionIndex() > -1 ? (_items()[_activeOptionIndex()].id) : undefined", "attr.disabled": "_isDisabled()", "attr.tabindex": "_isDisabled() || focus() === \"none\" ? \"-1\" : \"0\"", "attr.aria-disabled": "_isDisabled()" } }, providers: [{
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 (i of _items(); track $index) {\r\n@if(!i.filtered) {\r\n<div class=\"ng0-list-item\"\r\n [ngClass]=\"itemClass()(i)\"\r\n [attr.id]=\"i.id\"\r\n [class.ng0-list-item-active]=\"$index == _activeOptionIndex()\"\r\n [class.ng0-list-item-selected]=\"i.selected\"\r\n (click)=\"toggleSelection($index);\">\r\n\r\n @if(_itemTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_itemTemplate; context: { $implicit: i}\" />\r\n } @else {\r\n\r\n @if(showIndicator()) {\r\n <input class=\"form-check-input ng0-list-item-indicator\"\r\n [checked]=\"i.selected\"\r\n [attr.type]=\"multiple() ? 'checkbox' : 'radio'\"\r\n [attr.name]=\"i.id\"\r\n [attr.id]=\"i.id\">\r\n }\r\n\r\n {{formatBy()(i.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}:host.disabled{background-color:var(--bs-secondary-bg)}.ng0-list-item{display:flex;padding-inline-start:.5rem}.ng0-list-item .ng0-list-item-indicator{margin-inline-end:.5rem}\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"] }, { kind: "ngmodule", type: OverlayModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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]': '_activeOptionIndex() > -1 ? (_items()[_activeOptionIndex()].id) : undefined',
432
+ '[attr.aria-activedescendant]': '_ariaActiveDescendant()',
295
433
  '[attr.disabled]': '_isDisabled()',
296
- '[attr.tabindex]': '_isDisabled() || focus() === "none" ? "-1" : "0"',
434
+ '[attr.tabindex]': '_isDisabled() || focus() === "none" ? undefined : "0"',
297
435
  '[attr.aria-disabled]': '_isDisabled()'
298
- }, template: "@for (i of _items(); track $index) {\r\n@if(!i.filtered) {\r\n<div class=\"ng0-list-item\"\r\n [ngClass]=\"itemClass()(i)\"\r\n [attr.id]=\"i.id\"\r\n [class.ng0-list-item-active]=\"$index == _activeOptionIndex()\"\r\n [class.ng0-list-item-selected]=\"i.selected\"\r\n (click)=\"toggleSelection($index);\">\r\n\r\n @if(_itemTemplate) {\r\n <ng-container *ngTemplateOutlet=\"_itemTemplate; context: { $implicit: i}\" />\r\n } @else {\r\n\r\n @if(showIndicator()) {\r\n <input class=\"form-check-input ng0-list-item-indicator\"\r\n [checked]=\"i.selected\"\r\n [attr.type]=\"multiple() ? 'checkbox' : 'radio'\"\r\n [attr.name]=\"i.id\"\r\n [attr.id]=\"i.id\">\r\n }\r\n\r\n {{formatBy()(i.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}:host.disabled{background-color:var(--bs-secondary-bg)}.ng0-list-item{display:flex;padding-inline-start:.5rem}.ng0-list-item .ng0-list-item-indicator{margin-inline-end:.5rem}\n"] }]
299
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.DestroyRef }], propDecorators: { _itemTemplate: [{
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']]