@nanahoshi/mona-ui 0.23.0 → 0.23.2
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/fesm2022/nanahoshi-mona-ui-dropdown-list.mjs +1 -1
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-footer-row.component--nF_Q7Bd.mjs → nanahoshi-mona-ui-grid-grid-footer-row.component-CIMXz60d.mjs} +2 -2
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-list.component-S2qxwP2M.mjs → nanahoshi-mona-ui-grid-grid-list.component-CpeheGXU.mjs} +4 -4
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-row.component-Dwy8hCm7.mjs → nanahoshi-mona-ui-grid-grid-row.component-B_5ay_Nz.mjs} +2 -2
- package/fesm2022/{nanahoshi-mona-ui-grid-grid-virtual-list.component-DArxdAah.mjs → nanahoshi-mona-ui-grid-grid-virtual-list.component-BzKywU5b.mjs} +4 -4
- package/fesm2022/{nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-bduDEm-x.mjs → nanahoshi-mona-ui-grid-nanahoshi-mona-ui-grid-IewqeT4m.mjs} +8 -8
- package/fesm2022/nanahoshi-mona-ui-grid.mjs +1 -1
- package/fesm2022/nanahoshi-mona-ui-i18n.mjs +150 -90
- package/fesm2022/nanahoshi-mona-ui-internal-filter-input.mjs +47 -7
- package/fesm2022/nanahoshi-mona-ui-internal-list.mjs +386 -180
- package/fesm2022/nanahoshi-mona-ui-internal-tree.mjs +266 -99
- package/fesm2022/nanahoshi-mona-ui-internal.mjs +35 -32
- package/fesm2022/nanahoshi-mona-ui-locales.mjs +13 -0
- package/fesm2022/nanahoshi-mona-ui-numeric-text-box.mjs +27 -1
- package/fesm2022/nanahoshi-mona-ui-popup-menu.mjs +1 -1
- package/fesm2022/nanahoshi-mona-ui-tree-view.mjs +1 -1
- package/package.json +1 -1
- package/types/nanahoshi-mona-ui-i18n.d.ts +21 -4
- package/types/nanahoshi-mona-ui-internal-filter-input.d.ts +35 -1
- package/types/nanahoshi-mona-ui-internal-list.d.ts +82 -16
- package/types/nanahoshi-mona-ui-internal-tree.d.ts +50 -3
- package/types/nanahoshi-mona-ui-internal.d.ts +18 -4
- package/types/nanahoshi-mona-ui-locales.d.ts +13 -0
- package/types/nanahoshi-mona-ui-numeric-text-box.d.ts +5 -0
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf } from '@angular/cdk/scrolling';
|
|
2
2
|
import { NgTemplateOutlet } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Directive, signal,
|
|
4
|
+
import { Directive, computed, signal, linkedSignal, Injectable, inject, input, ElementRef, Component, DestroyRef, viewChild, contentChild, TemplateRef, output, effect, untracked, afterNextRender } from '@angular/core';
|
|
5
5
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
6
|
import { MonaI18nService } from '@nanahoshi/mona-ui/i18n';
|
|
7
|
-
import { isTypeaheadKey, setupTypeahead } from '@nanahoshi/mona-ui/internal';
|
|
7
|
+
import { createElementControlId, isTypeaheadKey, setupTypeahead } from '@nanahoshi/mona-ui/internal';
|
|
8
8
|
import { FilterInputComponent } from '@nanahoshi/mona-ui/internal/filter-input';
|
|
9
9
|
import { PlaceholderComponent } from '@nanahoshi/mona-ui/placeholder';
|
|
10
10
|
import { Subject, ReplaySubject, fromEvent, asyncScheduler, filter, tap } from 'rxjs';
|
|
11
11
|
import { twMerge } from 'tailwind-merge';
|
|
12
|
-
import { ImmutableList,
|
|
12
|
+
import { ImmutableList, toImmutableSet, ImmutableSet, from } from '@mirei/ts-collections';
|
|
13
13
|
import { v4 } from 'uuid';
|
|
14
14
|
import { cva } from 'class-variance-authority';
|
|
15
|
-
import * as i1 from '@angular/forms';
|
|
16
|
-
import { FormsModule } from '@angular/forms';
|
|
17
15
|
import { CheckboxDirective } from '@nanahoshi/mona-ui/check-box';
|
|
18
16
|
|
|
19
17
|
const LIST_DEFAULT_MESSAGES = {
|
|
18
|
+
filterList: "Filter list",
|
|
20
19
|
noData: "No data"
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -27,8 +26,7 @@ class ListFooterTemplateDirective {
|
|
|
27
26
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListFooterTemplateDirective, decorators: [{
|
|
28
27
|
type: Directive,
|
|
29
28
|
args: [{
|
|
30
|
-
selector: "ng-template[monaListFooterTemplate]"
|
|
31
|
-
standalone: true
|
|
29
|
+
selector: "ng-template[monaListFooterTemplate]"
|
|
32
30
|
}]
|
|
33
31
|
}] });
|
|
34
32
|
|
|
@@ -39,8 +37,7 @@ class ListGroupHeaderTemplateDirective {
|
|
|
39
37
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListGroupHeaderTemplateDirective, decorators: [{
|
|
40
38
|
type: Directive,
|
|
41
39
|
args: [{
|
|
42
|
-
selector: "ng-template[monaListGroupHeaderTemplate]"
|
|
43
|
-
standalone: true
|
|
40
|
+
selector: "ng-template[monaListGroupHeaderTemplate]"
|
|
44
41
|
}]
|
|
45
42
|
}] });
|
|
46
43
|
|
|
@@ -51,8 +48,7 @@ class ListHeaderTemplateDirective {
|
|
|
51
48
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListHeaderTemplateDirective, decorators: [{
|
|
52
49
|
type: Directive,
|
|
53
50
|
args: [{
|
|
54
|
-
selector: "ng-template[monaListHeaderTemplate]"
|
|
55
|
-
standalone: true
|
|
51
|
+
selector: "ng-template[monaListHeaderTemplate]"
|
|
56
52
|
}]
|
|
57
53
|
}] });
|
|
58
54
|
|
|
@@ -63,17 +59,17 @@ class ListItemTemplateDirective {
|
|
|
63
59
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListItemTemplateDirective, decorators: [{
|
|
64
60
|
type: Directive,
|
|
65
61
|
args: [{
|
|
66
|
-
selector: "ng-template[monaListItemTemplate]"
|
|
67
|
-
standalone: true
|
|
62
|
+
selector: "ng-template[monaListItemTemplate]"
|
|
68
63
|
}]
|
|
69
64
|
}] });
|
|
70
65
|
|
|
71
66
|
class ListItem {
|
|
72
67
|
constructor(options) {
|
|
73
68
|
this.header = "";
|
|
74
|
-
this.uid = v4();
|
|
75
69
|
this.data = options.data;
|
|
70
|
+
this.uid = options.uid ?? v4();
|
|
76
71
|
this.header = options.header ?? this.header;
|
|
72
|
+
this.isHeader = options.isHeader ?? this.header !== "";
|
|
77
73
|
}
|
|
78
74
|
}
|
|
79
75
|
|
|
@@ -90,15 +86,110 @@ predicate) => {
|
|
|
90
86
|
|
|
91
87
|
class ListService {
|
|
92
88
|
constructor() {
|
|
89
|
+
this.#arrangedItems = computed(() => {
|
|
90
|
+
const listItems = this.#listItems();
|
|
91
|
+
const filterText = this.filterText();
|
|
92
|
+
const groupableOptions = this.groupableOptions();
|
|
93
|
+
let enumerable = listItems;
|
|
94
|
+
if (filterText) {
|
|
95
|
+
enumerable = enumerable.where(item => this.filterItem(item, filterText));
|
|
96
|
+
}
|
|
97
|
+
if (!groupableOptions.enabled) {
|
|
98
|
+
return enumerable.toImmutableSet();
|
|
99
|
+
}
|
|
100
|
+
if (groupableOptions.orderBy) {
|
|
101
|
+
if (groupableOptions.orderByDirection === "desc") {
|
|
102
|
+
enumerable = enumerable.orderByDescending(item => this.getOrderKey(item));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
enumerable = enumerable.orderBy(item => this.getOrderKey(item));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
let groupedEnumerable = enumerable.groupBy(item => this.getGroupField(item));
|
|
109
|
+
if (groupableOptions.headerOrder) {
|
|
110
|
+
if (groupableOptions.headerOrder === "desc") {
|
|
111
|
+
groupedEnumerable = groupedEnumerable.orderByDescending(g => g.key);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
groupedEnumerable = groupedEnumerable.orderBy(g => g.key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const previousHeaders = this.#groupHeaders;
|
|
118
|
+
const headers = new Map();
|
|
119
|
+
const items = groupedEnumerable
|
|
120
|
+
.selectMany(g => {
|
|
121
|
+
const header = previousHeaders.get(g.key) ??
|
|
122
|
+
new ListItem({ data: g.key, header: String(g.key ?? ""), isHeader: true });
|
|
123
|
+
headers.set(g.key, header);
|
|
124
|
+
return g.source.prepend(header);
|
|
125
|
+
})
|
|
126
|
+
.toImmutableSet();
|
|
127
|
+
this.#groupHeaders = headers;
|
|
128
|
+
return items;
|
|
129
|
+
}, /* @ts-ignore */
|
|
130
|
+
...(ngDevMode ? [{ debugName: "#arrangedItems" }] : /* istanbul ignore next */ []));
|
|
93
131
|
this.#data = signal(ImmutableList.create(), /* @ts-ignore */
|
|
94
132
|
...(ngDevMode ? [{ debugName: "#data" }] : /* istanbul ignore next */ []));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
/**
|
|
134
|
+
* First list item for each selection key, so key lookups are O(1) instead of a scan per key.
|
|
135
|
+
*/
|
|
136
|
+
this.#itemsByKey = computed(() => {
|
|
137
|
+
const itemsByKey = new Map();
|
|
138
|
+
for (const item of this.#listItems()) {
|
|
139
|
+
const key = this.getSelectionKey(item);
|
|
140
|
+
if (!itemsByKey.has(key)) {
|
|
141
|
+
itemsByKey.set(key, item);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return itemsByKey;
|
|
145
|
+
}, /* @ts-ignore */
|
|
146
|
+
...(ngDevMode ? [{ debugName: "#itemsByKey" }] : /* istanbul ignore next */ []));
|
|
147
|
+
this.#itemsByUid = computed(() => {
|
|
148
|
+
const itemsByUid = new Map();
|
|
149
|
+
for (const item of this.#listItems()) {
|
|
150
|
+
itemsByUid.set(item.uid, item);
|
|
151
|
+
}
|
|
152
|
+
return itemsByUid;
|
|
100
153
|
}, /* @ts-ignore */
|
|
101
|
-
...(ngDevMode ? [{ debugName: "#
|
|
154
|
+
...(ngDevMode ? [{ debugName: "#itemsByUid" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/**
|
|
156
|
+
* Wraps the data in list items while keeping item identity across data changes:
|
|
157
|
+
* an item whose selection key existed before keeps its `uid`, and keeps its instance when the data
|
|
158
|
+
* object is also unchanged. Highlight, `aria-activedescendant` ids and rendered rows therefore survive
|
|
159
|
+
* a refresh (for example server-side filtering) instead of pointing at items that no longer exist.
|
|
160
|
+
*/
|
|
161
|
+
this.#listItems = linkedSignal({ ...(ngDevMode ? { debugName: "#listItems" } : /* istanbul ignore next */ {}), source: () => ({ data: this.#data(), valueField: this.valueField() }),
|
|
162
|
+
computation: ({ data }, previous) => {
|
|
163
|
+
const previousByKey = new Map();
|
|
164
|
+
for (const item of previous?.value ?? []) {
|
|
165
|
+
const key = this.getSelectionKey(item);
|
|
166
|
+
if (!previousByKey.has(key)) {
|
|
167
|
+
previousByKey.set(key, item);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const usedUids = new Set();
|
|
171
|
+
const items = [];
|
|
172
|
+
for (const dataItem of data) {
|
|
173
|
+
const previousItem = previousByKey.get(this.getDataItemValue(dataItem));
|
|
174
|
+
let item;
|
|
175
|
+
if (previousItem && !usedUids.has(previousItem.uid)) {
|
|
176
|
+
item =
|
|
177
|
+
previousItem.data === dataItem
|
|
178
|
+
? previousItem
|
|
179
|
+
: new ListItem({ data: dataItem, uid: previousItem.uid });
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
item = new ListItem({ data: dataItem });
|
|
183
|
+
}
|
|
184
|
+
usedUids.add(item.uid);
|
|
185
|
+
items.push(item);
|
|
186
|
+
}
|
|
187
|
+
return toImmutableSet(items);
|
|
188
|
+
} });
|
|
189
|
+
/**
|
|
190
|
+
* Group header items from the last grouping pass, reused so header rows keep their uid between recomputations.
|
|
191
|
+
*/
|
|
192
|
+
this.#groupHeaders = new Map();
|
|
102
193
|
this.disabledBy = signal("", /* @ts-ignore */
|
|
103
194
|
...(ngDevMode ? [{ debugName: "disabledBy" }] : /* istanbul ignore next */ []));
|
|
104
195
|
this.filterChange$ = new Subject();
|
|
@@ -125,26 +216,68 @@ class ListService {
|
|
|
125
216
|
if (selected) {
|
|
126
217
|
return selected;
|
|
127
218
|
}
|
|
128
|
-
return this.viewItems().firstOrDefault(i => !i.
|
|
219
|
+
return this.viewItems().firstOrDefault(i => !i.isHeader && !this.isDisabled(i));
|
|
129
220
|
}, /* @ts-ignore */
|
|
130
221
|
...(ngDevMode ? [{ debugName: "focusableItem" }] : /* istanbul ignore next */ []));
|
|
131
222
|
this.groupBy = signal(null, /* @ts-ignore */
|
|
132
223
|
...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
|
|
224
|
+
/**
|
|
225
|
+
* Maps each option uid to the uid of the group header it is listed under.
|
|
226
|
+
*/
|
|
227
|
+
this.groupHeaderUids = computed(() => {
|
|
228
|
+
const headerUids = new Map();
|
|
229
|
+
let headerUid = null;
|
|
230
|
+
for (const item of this.#arrangedItems()) {
|
|
231
|
+
if (item.isHeader) {
|
|
232
|
+
headerUid = item.uid;
|
|
233
|
+
}
|
|
234
|
+
else if (headerUid != null) {
|
|
235
|
+
headerUids.set(item.uid, headerUid);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return headerUids;
|
|
239
|
+
}, /* @ts-ignore */
|
|
240
|
+
...(ngDevMode ? [{ debugName: "groupHeaderUids" }] : /* istanbul ignore next */ []));
|
|
133
241
|
this.groupableOptions = signal({
|
|
134
242
|
enabled: false,
|
|
135
243
|
headerOrder: "asc"
|
|
136
244
|
}, /* @ts-ignore */
|
|
137
245
|
...(ngDevMode ? [{ debugName: "groupableOptions" }] : /* istanbul ignore next */ []));
|
|
138
|
-
|
|
139
|
-
|
|
246
|
+
/**
|
|
247
|
+
* The highlighted (active) item. When the data changes, it is re-resolved to the item with the same `uid`,
|
|
248
|
+
* or cleared when that item is gone.
|
|
249
|
+
*/
|
|
250
|
+
this.highlightedItem = linkedSignal({ ...(ngDevMode ? { debugName: "highlightedItem" } : /* istanbul ignore next */ {}), source: this.#listItems,
|
|
251
|
+
computation: (_, previous) => {
|
|
252
|
+
const highlighted = previous?.value;
|
|
253
|
+
if (!highlighted || highlighted.isHeader) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return this.#itemsByUid().get(highlighted.uid) ?? null;
|
|
257
|
+
} });
|
|
140
258
|
this.itemFocusOut$ = new Subject();
|
|
141
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Position of each option among all filtered options (not only the current page), for `aria-posinset`
|
|
261
|
+
* and `aria-setsize`.
|
|
262
|
+
*/
|
|
263
|
+
this.itemPositions = computed(() => {
|
|
264
|
+
const options = this.#arrangedItems()
|
|
265
|
+
.where(i => !i.isHeader)
|
|
266
|
+
.toArray();
|
|
267
|
+
const positions = new Map();
|
|
268
|
+
options.forEach((item, index) => {
|
|
269
|
+
positions.set(item.uid, { position: index + 1, total: options.length });
|
|
270
|
+
});
|
|
271
|
+
return positions;
|
|
272
|
+
}, /* @ts-ignore */
|
|
273
|
+
...(ngDevMode ? [{ debugName: "itemPositions" }] : /* istanbul ignore next */ []));
|
|
142
274
|
this.navigableOptions = signal({
|
|
143
275
|
enabled: false,
|
|
144
276
|
mode: "select",
|
|
145
277
|
wrap: false
|
|
146
278
|
}, /* @ts-ignore */
|
|
147
279
|
...(ngDevMode ? [{ debugName: "navigableOptions" }] : /* istanbul ignore next */ []));
|
|
280
|
+
this.navigatedItem$ = new Subject();
|
|
148
281
|
this.pageState = signal({ page: 1, skip: 0, take: 10 }, /* @ts-ignore */
|
|
149
282
|
...(ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []));
|
|
150
283
|
this.pageableOptions = signal({
|
|
@@ -171,9 +304,10 @@ class ListService {
|
|
|
171
304
|
this.selectedKeysChange$ = new Subject();
|
|
172
305
|
this.selectedListItems = computed(() => {
|
|
173
306
|
const selectedKeys = this.selectedKeys();
|
|
307
|
+
const itemsByKey = this.#itemsByKey();
|
|
174
308
|
return selectedKeys
|
|
175
|
-
.select(key =>
|
|
176
|
-
.where(i => i != null)
|
|
309
|
+
.select(key => itemsByKey.get(key))
|
|
310
|
+
.where((i) => i != null)
|
|
177
311
|
.toImmutableSet();
|
|
178
312
|
}, /* @ts-ignore */
|
|
179
313
|
...(ngDevMode ? [{ debugName: "selectedListItems" }] : /* istanbul ignore next */ []));
|
|
@@ -182,53 +316,17 @@ class ListService {
|
|
|
182
316
|
...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
|
|
183
317
|
this.valueField = signal("", /* @ts-ignore */
|
|
184
318
|
...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
|
|
319
|
+
/**
|
|
320
|
+
* Visible items in display order: filtered, ordered and grouped (with header rows), but not paged.
|
|
321
|
+
*/
|
|
185
322
|
this.viewItems = computed(() => {
|
|
186
|
-
const
|
|
187
|
-
const filterText = this.filterText();
|
|
188
|
-
const groupableOptions = this.groupableOptions();
|
|
189
|
-
const virtualScrollOptions = this.virtualScrollOptions();
|
|
190
|
-
let enumerable = listItems;
|
|
191
|
-
if (filterText) {
|
|
192
|
-
enumerable = enumerable.where(item => this.filterItem(item, filterText));
|
|
193
|
-
}
|
|
194
|
-
let resultEnumerable;
|
|
195
|
-
if (groupableOptions.enabled) {
|
|
196
|
-
if (groupableOptions.orderBy) {
|
|
197
|
-
if (groupableOptions.orderByDirection === "desc") {
|
|
198
|
-
enumerable = enumerable.orderByDescending(item => this.getOrderKey(item));
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
enumerable = enumerable.orderBy(item => this.getOrderKey(item));
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
let groupedEnumerable = enumerable.groupBy(item => this.getGroupField(item));
|
|
205
|
-
if (groupableOptions.headerOrder) {
|
|
206
|
-
if (groupableOptions.headerOrder === "desc") {
|
|
207
|
-
groupedEnumerable = groupedEnumerable.orderByDescending(g => g.key);
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
groupedEnumerable = groupedEnumerable.orderBy(g => g.key);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
resultEnumerable = groupedEnumerable.selectMany(g => {
|
|
214
|
-
const groupHeaderItem = new ListItem({ data: g.key, header: String(g.key) });
|
|
215
|
-
return g.source.prepend(groupHeaderItem);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
resultEnumerable = enumerable;
|
|
220
|
-
}
|
|
323
|
+
const arrangedItems = this.#arrangedItems();
|
|
221
324
|
const pageableOptions = this.pageableOptions();
|
|
222
|
-
if (virtualScrollOptions.enabled) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
if (pageableOptions.enabled) {
|
|
226
|
-
const pageState = this.pageState();
|
|
227
|
-
const skip = pageState.skip;
|
|
228
|
-
const take = pageState.take;
|
|
229
|
-
return toImmutableSet(this.skipTakeWithGroups(resultEnumerable.toArray(), skip, take));
|
|
325
|
+
if (this.virtualScrollOptions().enabled || !pageableOptions.enabled) {
|
|
326
|
+
return arrangedItems;
|
|
230
327
|
}
|
|
231
|
-
|
|
328
|
+
const pageState = this.pageState();
|
|
329
|
+
return toImmutableSet(this.skipTakeWithGroups(arrangedItems.toArray(), pageState.skip, pageState.take));
|
|
232
330
|
}, /* @ts-ignore */
|
|
233
331
|
...(ngDevMode ? [{ debugName: "viewItems" }] : /* istanbul ignore next */ []));
|
|
234
332
|
this.virtualScrollOptions = signal({
|
|
@@ -236,41 +334,25 @@ class ListService {
|
|
|
236
334
|
height: 28
|
|
237
335
|
}, /* @ts-ignore */
|
|
238
336
|
...(ngDevMode ? [{ debugName: "virtualScrollOptions" }] : /* istanbul ignore next */ []));
|
|
239
|
-
this.itemPositions = computed(() => {
|
|
240
|
-
const viewItems = this.viewItems();
|
|
241
|
-
const nonHeaderItems = viewItems.where(i => !i.header).toArray();
|
|
242
|
-
const positions = new Map();
|
|
243
|
-
nonHeaderItems.forEach((item, index) => {
|
|
244
|
-
positions.set(item.uid, { position: index + 1, total: nonHeaderItems.length });
|
|
245
|
-
});
|
|
246
|
-
return positions;
|
|
247
|
-
}, /* @ts-ignore */
|
|
248
|
-
...(ngDevMode ? [{ debugName: "itemPositions" }] : /* istanbul ignore next */ []));
|
|
249
337
|
}
|
|
338
|
+
#arrangedItems;
|
|
250
339
|
#data;
|
|
340
|
+
/**
|
|
341
|
+
* First list item for each selection key, so key lookups are O(1) instead of a scan per key.
|
|
342
|
+
*/
|
|
343
|
+
#itemsByKey;
|
|
344
|
+
#itemsByUid;
|
|
345
|
+
/**
|
|
346
|
+
* Wraps the data in list items while keeping item identity across data changes:
|
|
347
|
+
* an item whose selection key existed before keeps its `uid`, and keeps its instance when the data
|
|
348
|
+
* object is also unchanged. Highlight, `aria-activedescendant` ids and rendered rows therefore survive
|
|
349
|
+
* a refresh (for example server-side filtering) instead of pointing at items that no longer exist.
|
|
350
|
+
*/
|
|
251
351
|
#listItems;
|
|
252
|
-
getItemPosition(item) {
|
|
253
|
-
return this.itemPositions().get(item.uid) ?? null;
|
|
254
|
-
}
|
|
255
352
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* When no `valueField` is configured, the data item itself is the value.
|
|
259
|
-
* When `valueField` is a string, the item property is read with a nullish fallback to the item.
|
|
260
|
-
* When `valueField` is a function, its result is used as-is.
|
|
261
|
-
*
|
|
262
|
-
* Falsy results such as `0`, `false`, and `""` are valid values and do not fall back to the item.
|
|
353
|
+
* Group header items from the last grouping pass, reused so header rows keep their uid between recomputations.
|
|
263
354
|
*/
|
|
264
|
-
|
|
265
|
-
const valueField = this.valueField();
|
|
266
|
-
if (!valueField) {
|
|
267
|
-
return dataItem;
|
|
268
|
-
}
|
|
269
|
-
if (typeof valueField === "string") {
|
|
270
|
-
return dataItem?.[valueField] ?? dataItem;
|
|
271
|
-
}
|
|
272
|
-
return valueField(dataItem);
|
|
273
|
-
}
|
|
355
|
+
#groupHeaders;
|
|
274
356
|
clearFilter() {
|
|
275
357
|
this.filterText.set("");
|
|
276
358
|
}
|
|
@@ -281,12 +363,18 @@ class ListService {
|
|
|
281
363
|
this.selectedKeys.update(set => set.clear());
|
|
282
364
|
this.highlightedItem.set(null);
|
|
283
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Finds the next item whose text starts with the typeahead buffer, starting after the active item.
|
|
368
|
+
* Group headers and disabled items are never matched.
|
|
369
|
+
*/
|
|
284
370
|
cycleThroughMatchedItems(buffer) {
|
|
285
371
|
const activeItem = this.highlightedItem() || this.selectedListItems().lastOrDefault();
|
|
286
|
-
const
|
|
287
|
-
return cycleThroughMatchedItems(viewItems, activeItem, item => {
|
|
288
|
-
|
|
289
|
-
|
|
372
|
+
const prefix = buffer.toLowerCase();
|
|
373
|
+
return cycleThroughMatchedItems(this.viewItems(), activeItem ?? null, item => {
|
|
374
|
+
if (item.isHeader || this.isDisabled(item)) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
return this.getItemText(item).toLowerCase().startsWith(prefix);
|
|
290
378
|
});
|
|
291
379
|
}
|
|
292
380
|
deselectItems(item) {
|
|
@@ -295,6 +383,25 @@ class ListService {
|
|
|
295
383
|
.toImmutableSet();
|
|
296
384
|
this.selectedKeys.update(set => set.removeAll(keys));
|
|
297
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Resolves the selection value of a data item through `valueField`.
|
|
388
|
+
*
|
|
389
|
+
* When no `valueField` is configured, the data item itself is the value.
|
|
390
|
+
* When `valueField` is a string, the item property is read with a nullish fallback to the item.
|
|
391
|
+
* When `valueField` is a function, its result is used as-is.
|
|
392
|
+
*
|
|
393
|
+
* Falsy results such as `0`, `false`, and `""` are valid values and do not fall back to the item.
|
|
394
|
+
*/
|
|
395
|
+
getDataItemValue(dataItem) {
|
|
396
|
+
const valueField = this.valueField();
|
|
397
|
+
if (!valueField) {
|
|
398
|
+
return dataItem;
|
|
399
|
+
}
|
|
400
|
+
if (typeof valueField === "string") {
|
|
401
|
+
return dataItem?.[valueField] ?? dataItem;
|
|
402
|
+
}
|
|
403
|
+
return valueField(dataItem);
|
|
404
|
+
}
|
|
298
405
|
getFilterDebounceDuration() {
|
|
299
406
|
const filterableOptions = this.filterableOptions();
|
|
300
407
|
return filterableOptions.enabled ? filterableOptions.debounce : 0;
|
|
@@ -309,33 +416,54 @@ class ListService {
|
|
|
309
416
|
}
|
|
310
417
|
return groupSelector(item.data);
|
|
311
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Returns the uid of the group header an item is rendered under, or `null` when the list is not grouped.
|
|
421
|
+
*/
|
|
422
|
+
getGroupHeaderUid(item) {
|
|
423
|
+
return this.groupHeaderUids().get(item.uid) ?? null;
|
|
424
|
+
}
|
|
425
|
+
getItemPosition(item) {
|
|
426
|
+
return this.itemPositions().get(item.uid) ?? null;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Resolves the display text of an item through `textField`.
|
|
430
|
+
* Non-string field values (numbers, dates, …) are converted with `String()`, and nullish values become `""`.
|
|
431
|
+
*/
|
|
312
432
|
getItemText(item) {
|
|
313
433
|
const textField = this.textField();
|
|
434
|
+
let text;
|
|
314
435
|
if (!textField) {
|
|
315
|
-
|
|
436
|
+
text = item.data;
|
|
316
437
|
}
|
|
317
|
-
if (typeof textField === "string") {
|
|
318
|
-
|
|
438
|
+
else if (typeof textField === "string") {
|
|
439
|
+
text = item.data?.[textField];
|
|
319
440
|
}
|
|
320
|
-
|
|
441
|
+
else {
|
|
442
|
+
text = textField(item.data);
|
|
443
|
+
}
|
|
444
|
+
return text == null ? "" : String(text);
|
|
321
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Finds the item that typed text should resolve to: an exact text match first (respecting `caseSensitive`),
|
|
448
|
+
* otherwise the first item accepted by the filter operator. Group headers and disabled items are skipped.
|
|
449
|
+
*/
|
|
322
450
|
getMatchingFilteredItem(text) {
|
|
323
451
|
const filterableOptions = this.filterableOptions();
|
|
324
452
|
const caseSensitive = filterableOptions.enabled ? filterableOptions.caseSensitive : false;
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return this.getItemText(i) === text;
|
|
330
|
-
}
|
|
331
|
-
return this.getItemText(i).toLowerCase() === text.toLowerCase();
|
|
453
|
+
const candidates = this.viewItems().where(i => !i.isHeader && !this.isDisabled(i));
|
|
454
|
+
const fullMatchingItem = candidates.firstOrDefault(i => {
|
|
455
|
+
const itemText = this.getItemText(i);
|
|
456
|
+
return caseSensitive ? itemText === text : itemText.toLowerCase() === text.toLowerCase();
|
|
332
457
|
});
|
|
333
458
|
if (fullMatchingItem) {
|
|
334
459
|
return fullMatchingItem;
|
|
335
460
|
}
|
|
336
|
-
return
|
|
461
|
+
return candidates.firstOrDefault(i => this.filterItem(i, text));
|
|
337
462
|
}
|
|
338
463
|
isDisabled(item) {
|
|
464
|
+
if (item.isHeader) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
339
467
|
const disabledBy = this.disabledBy();
|
|
340
468
|
if (typeof disabledBy === "string") {
|
|
341
469
|
return item.data?.[disabledBy] ?? false;
|
|
@@ -365,7 +493,7 @@ class ListService {
|
|
|
365
493
|
return null;
|
|
366
494
|
}
|
|
367
495
|
const viewItems = this.viewItems()
|
|
368
|
-
.where(i => !i.
|
|
496
|
+
.where(i => !i.isHeader && !this.isDisabled(i))
|
|
369
497
|
.toImmutableSet();
|
|
370
498
|
if (viewItems.isEmpty()) {
|
|
371
499
|
return null;
|
|
@@ -393,6 +521,26 @@ class ListService {
|
|
|
393
521
|
scrollToItem(item, focus) {
|
|
394
522
|
this.scrollToItem$.next({ item, focus });
|
|
395
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Selects every enabled item currently in view, keeping existing selections. Only applies in multiple mode.
|
|
526
|
+
* @returns whether the selection changed.
|
|
527
|
+
*/
|
|
528
|
+
selectAllItems() {
|
|
529
|
+
const options = this.selectableOptions();
|
|
530
|
+
if (!options.enabled || options.mode !== "multiple") {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const keys = this.viewItems()
|
|
534
|
+
.where(i => !i.isHeader && !this.isDisabled(i))
|
|
535
|
+
.select(i => this.getSelectionKey(i))
|
|
536
|
+
.toArray();
|
|
537
|
+
const selectedKeys = this.selectedKeys();
|
|
538
|
+
if (keys.every(key => selectedKeys.contains(key))) {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
this.selectedKeys.update(set => set.addAll(keys));
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
396
544
|
selectItem(item, scroll = false, focus = false) {
|
|
397
545
|
const key = this.getSelectionKey(item);
|
|
398
546
|
const options = this.selectableOptions();
|
|
@@ -496,6 +644,13 @@ class ListService {
|
|
|
496
644
|
return false;
|
|
497
645
|
}
|
|
498
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Returns the item when it is one of the navigable items, otherwise `null`.
|
|
649
|
+
* An anchor that was filtered out or removed must not be navigated from.
|
|
650
|
+
*/
|
|
651
|
+
getItemInView(viewItems, item) {
|
|
652
|
+
return item != null && viewItems.contains(item) ? item : null;
|
|
653
|
+
}
|
|
499
654
|
getOrderKey(item) {
|
|
500
655
|
const orderBy = this.groupableOptions().orderBy;
|
|
501
656
|
if (orderBy) {
|
|
@@ -524,16 +679,34 @@ class ListService {
|
|
|
524
679
|
getSelectionKey(item) {
|
|
525
680
|
return this.getDataItemValue(item.data);
|
|
526
681
|
}
|
|
682
|
+
navigateFirstForSingleSelection(viewItems, mode) {
|
|
683
|
+
const firstItem = viewItems.first();
|
|
684
|
+
if (mode === "select") {
|
|
685
|
+
if (!this.isSelected(firstItem)) {
|
|
686
|
+
this.selectItem(firstItem);
|
|
687
|
+
}
|
|
688
|
+
this.highlightedItem.set(null);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
this.highlightedItem.set(firstItem);
|
|
692
|
+
}
|
|
693
|
+
return firstItem;
|
|
694
|
+
}
|
|
527
695
|
navigateForMultipleSelection(viewItems, direction) {
|
|
528
696
|
const selectedKeys = this.selectedKeys();
|
|
529
697
|
const firstItem = viewItems.first();
|
|
530
|
-
if (
|
|
698
|
+
if (direction === "first" || direction === "last") {
|
|
699
|
+
const edgeItem = direction === "first" ? firstItem : viewItems.last();
|
|
700
|
+
this.highlightedItem.set(edgeItem);
|
|
701
|
+
return edgeItem;
|
|
702
|
+
}
|
|
703
|
+
const lastHighlightedItem = this.getItemInView(viewItems, this.highlightedItem());
|
|
704
|
+
if (selectedKeys.isEmpty() && !lastHighlightedItem) {
|
|
531
705
|
this.highlightedItem.set(firstItem);
|
|
532
706
|
return firstItem;
|
|
533
707
|
}
|
|
534
708
|
const firstSelectedItem = viewItems.firstOrDefault(i => selectedKeys.contains(this.getSelectionKey(i)));
|
|
535
709
|
const lastSelectedItem = viewItems.lastOrDefault(i => selectedKeys.contains(this.getSelectionKey(i)));
|
|
536
|
-
const lastHighlightedItem = this.highlightedItem();
|
|
537
710
|
if (direction === "next") {
|
|
538
711
|
const navigationItem = lastHighlightedItem ?? lastSelectedItem ?? firstItem;
|
|
539
712
|
let nextItem = viewItems
|
|
@@ -564,21 +737,14 @@ class ListService {
|
|
|
564
737
|
}
|
|
565
738
|
return null;
|
|
566
739
|
}
|
|
567
|
-
navigateFirstForSingleSelection(viewItems, mode) {
|
|
568
|
-
const firstItem = viewItems.first();
|
|
569
|
-
if (mode === "select") {
|
|
570
|
-
this.selectItem(firstItem);
|
|
571
|
-
this.highlightedItem.set(null);
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
this.highlightedItem.set(firstItem);
|
|
575
|
-
}
|
|
576
|
-
return firstItem;
|
|
577
|
-
}
|
|
578
740
|
navigateForSingleSelection(viewItems, direction, mode) {
|
|
579
741
|
const selectedKeys = this.selectedKeys();
|
|
580
742
|
const firstItem = viewItems.first();
|
|
581
|
-
|
|
743
|
+
const lastHighlightedItem = this.getItemInView(viewItems, this.highlightedItem());
|
|
744
|
+
if (selectedKeys.isEmpty() && !lastHighlightedItem) {
|
|
745
|
+
if (direction === "last") {
|
|
746
|
+
return this.navigateLastForSingleSelection(viewItems, mode);
|
|
747
|
+
}
|
|
582
748
|
if (mode === "select") {
|
|
583
749
|
this.selectItem(firstItem);
|
|
584
750
|
}
|
|
@@ -587,15 +753,13 @@ class ListService {
|
|
|
587
753
|
}
|
|
588
754
|
return firstItem;
|
|
589
755
|
}
|
|
590
|
-
else if (selectedKeys.isEmpty() &&
|
|
591
|
-
const highlightedItem = this.highlightedItem();
|
|
756
|
+
else if (selectedKeys.isEmpty() && lastHighlightedItem) {
|
|
592
757
|
if (mode === "select") {
|
|
593
|
-
this.selectItem(
|
|
594
|
-
return
|
|
758
|
+
this.selectItem(lastHighlightedItem);
|
|
759
|
+
return lastHighlightedItem;
|
|
595
760
|
}
|
|
596
761
|
}
|
|
597
762
|
const selectedItem = viewItems.lastOrDefault(i => selectedKeys.contains(this.getSelectionKey(i)));
|
|
598
|
-
const lastHighlightedItem = this.highlightedItem();
|
|
599
763
|
const navigationItem = lastHighlightedItem ?? selectedItem ?? firstItem;
|
|
600
764
|
if (direction === "next") {
|
|
601
765
|
return this.navigateNextForSingleSelection(viewItems, navigationItem, mode, firstItem);
|
|
@@ -614,7 +778,9 @@ class ListService {
|
|
|
614
778
|
navigateLastForSingleSelection(viewItems, mode) {
|
|
615
779
|
const lastItem = viewItems.last();
|
|
616
780
|
if (mode === "select") {
|
|
617
|
-
this.
|
|
781
|
+
if (!this.isSelected(lastItem)) {
|
|
782
|
+
this.selectItem(lastItem);
|
|
783
|
+
}
|
|
618
784
|
this.highlightedItem.set(null);
|
|
619
785
|
}
|
|
620
786
|
else {
|
|
@@ -675,7 +841,7 @@ class ListService {
|
|
|
675
841
|
let capturing = false;
|
|
676
842
|
const result = [];
|
|
677
843
|
for (const item of items) {
|
|
678
|
-
if (item.
|
|
844
|
+
if (item.isHeader) {
|
|
679
845
|
// Include group headers when we're capturing
|
|
680
846
|
if (capturing) {
|
|
681
847
|
result.push(item);
|
|
@@ -693,7 +859,7 @@ class ListService {
|
|
|
693
859
|
// Find the group header for this item
|
|
694
860
|
const itemIndex = items.indexOf(item);
|
|
695
861
|
for (let i = itemIndex - 1; i >= 0; i--) {
|
|
696
|
-
if (items[i].
|
|
862
|
+
if (items[i].isHeader) {
|
|
697
863
|
result.push(items[i]);
|
|
698
864
|
break;
|
|
699
865
|
}
|
|
@@ -710,7 +876,7 @@ class ListService {
|
|
|
710
876
|
}
|
|
711
877
|
// Remove trailing group headers if they are at the end
|
|
712
878
|
let lastIndex = result.length - 1;
|
|
713
|
-
while (lastIndex >= 0 && result[lastIndex].
|
|
879
|
+
while (lastIndex >= 0 && result[lastIndex].isHeader) {
|
|
714
880
|
result.pop();
|
|
715
881
|
lastIndex--;
|
|
716
882
|
}
|
|
@@ -780,6 +946,8 @@ class ListItemDirective {
|
|
|
780
946
|
this.#listService = inject((ListService));
|
|
781
947
|
this.disabled = computed(() => this.#listService.isDisabled(this.item()), /* @ts-ignore */
|
|
782
948
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
949
|
+
this.groupHeaderUid = computed(() => this.#listService.getGroupHeaderUid(this.item()), /* @ts-ignore */
|
|
950
|
+
...(ngDevMode ? [{ debugName: "groupHeaderUid" }] : /* istanbul ignore next */ []));
|
|
783
951
|
this.highlighted = computed(() => this.#listService.isHighlighted(this.item()), /* @ts-ignore */
|
|
784
952
|
...(ngDevMode ? [{ debugName: "highlighted" }] : /* istanbul ignore next */ []));
|
|
785
953
|
this.position = computed(() => {
|
|
@@ -819,15 +987,14 @@ class ListItemDirective {
|
|
|
819
987
|
.subscribe(() => this.#listService.itemFocusOut$.next(this.item()));
|
|
820
988
|
}
|
|
821
989
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
822
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: ListItemDirective, isStandalone: true, selector: "li[monaListItem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.aria-
|
|
990
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.6", type: ListItemDirective, isStandalone: true, selector: "li[monaListItem]", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.aria-describedby": "groupHeaderUid()", "attr.aria-disabled": "disabled()", "attr.aria-posinset": "position()", "attr.aria-selected": "selected()", "attr.aria-setsize": "setSize()", "attr.data-disabled": "disabled()", "attr.data-highlighted": "highlighted()", "attr.data-selected": "selected()", "attr.role": "'option'", "attr.tabindex": "rovingTabIndex()", "class": "textClasses()" } }, ngImport: i0 }); }
|
|
823
991
|
}
|
|
824
992
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListItemDirective, decorators: [{
|
|
825
993
|
type: Directive,
|
|
826
994
|
args: [{
|
|
827
995
|
selector: "li[monaListItem]",
|
|
828
|
-
standalone: true,
|
|
829
996
|
host: {
|
|
830
|
-
"[attr.aria-
|
|
997
|
+
"[attr.aria-describedby]": "groupHeaderUid()",
|
|
831
998
|
"[attr.aria-disabled]": "disabled()",
|
|
832
999
|
"[attr.aria-posinset]": "position()",
|
|
833
1000
|
"[attr.aria-selected]": "selected()",
|
|
@@ -849,8 +1016,7 @@ class ListNoDataTemplateDirective {
|
|
|
849
1016
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListNoDataTemplateDirective, decorators: [{
|
|
850
1017
|
type: Directive,
|
|
851
1018
|
args: [{
|
|
852
|
-
selector: "ng-template[monaListNoDataTemplate]"
|
|
853
|
-
standalone: true
|
|
1019
|
+
selector: "ng-template[monaListNoDataTemplate]"
|
|
854
1020
|
}]
|
|
855
1021
|
}] });
|
|
856
1022
|
|
|
@@ -898,13 +1064,13 @@ class ListItemComponent {
|
|
|
898
1064
|
if (item == null) {
|
|
899
1065
|
return "";
|
|
900
1066
|
}
|
|
901
|
-
if (item.
|
|
1067
|
+
if (item.isHeader) {
|
|
902
1068
|
return item.header;
|
|
903
1069
|
}
|
|
904
1070
|
return this.listService.getItemText(item);
|
|
905
1071
|
}, /* @ts-ignore */
|
|
906
1072
|
...(ngDevMode ? [{ debugName: "itemText" }] : /* istanbul ignore next */ []));
|
|
907
|
-
this.isHeader = computed(() => this.item()?.
|
|
1073
|
+
this.isHeader = computed(() => this.item()?.isHeader ?? false, /* @ts-ignore */
|
|
908
1074
|
...(ngDevMode ? [{ debugName: "isHeader" }] : /* istanbul ignore next */ []));
|
|
909
1075
|
this.listService = inject(ListService);
|
|
910
1076
|
this.textClasses = computed(() => {
|
|
@@ -917,42 +1083,53 @@ class ListItemComponent {
|
|
|
917
1083
|
return ``; // TODO
|
|
918
1084
|
}, /* @ts-ignore */
|
|
919
1085
|
...(ngDevMode ? [{ debugName: "textClasses" }] : /* istanbul ignore next */ []));
|
|
920
|
-
this.checked = output();
|
|
921
1086
|
this.item = input.required(/* @ts-ignore */
|
|
922
1087
|
...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
923
1088
|
this.template = input(/* @ts-ignore */
|
|
924
1089
|
...(ngDevMode ? [undefined, { debugName: "template" }] : /* istanbul ignore next */ []));
|
|
925
1090
|
}
|
|
926
|
-
onCheckboxChange(checked) {
|
|
927
|
-
this.checked.emit(checked);
|
|
928
|
-
}
|
|
929
1091
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
930
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: ListItemComponent, isStandalone: true, selector: "mona-list-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null } },
|
|
1092
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: ListItemComponent, isStandalone: true, selector: "mona-list-item", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "baseClass()" } }, ngImport: i0, template: "@if (checkboxes()) {\n <!-- Visual only: the option row owns selection, so the checkbox is inert (not focusable, clicks reach the row). -->\n <input type=\"checkbox\" [checked]=\"itemChecked()\" monaCheckbox class=\"aspect-square\" inert aria-hidden=\"true\" />\n}\n\n@if (template()) {\n <ng-container [ngTemplateOutlet]=\"template()\" [ngTemplateOutletContext]=\"{ $implicit: dataItem() }\"></ng-container>\n} @else {\n <span [class]=\"textClasses()\">{{ itemText() }}</span>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: CheckboxDirective, selector: "input[type='checkbox'][monaCheckbox]", inputs: ["invalid", "rounded", "touched", "class"] }] }); }
|
|
931
1093
|
}
|
|
932
1094
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListItemComponent, decorators: [{
|
|
933
1095
|
type: Component,
|
|
934
|
-
args: [{ selector: "mona-list-item", imports: [NgTemplateOutlet, CheckboxDirective
|
|
1096
|
+
args: [{ selector: "mona-list-item", imports: [NgTemplateOutlet, CheckboxDirective], host: {
|
|
935
1097
|
"[class]": "baseClass()"
|
|
936
|
-
}, template: "@if (checkboxes()) {\n <input
|
|
937
|
-
}], propDecorators: {
|
|
1098
|
+
}, template: "@if (checkboxes()) {\n <!-- Visual only: the option row owns selection, so the checkbox is inert (not focusable, clicks reach the row). -->\n <input type=\"checkbox\" [checked]=\"itemChecked()\" monaCheckbox class=\"aspect-square\" inert aria-hidden=\"true\" />\n}\n\n@if (template()) {\n <ng-container [ngTemplateOutlet]=\"template()\" [ngTemplateOutletContext]=\"{ $implicit: dataItem() }\"></ng-container>\n} @else {\n <span [class]=\"textClasses()\">{{ itemText() }}</span>\n}\n" }]
|
|
1099
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], template: [{ type: i0.Input, args: [{ isSignal: true, alias: "template", required: false }] }] } });
|
|
938
1100
|
|
|
939
1101
|
class ListComponent {
|
|
940
1102
|
#destroyRef;
|
|
1103
|
+
#generatedListId;
|
|
941
1104
|
#hostElementRef;
|
|
942
1105
|
#i18n;
|
|
943
1106
|
#typeaheadKey$;
|
|
944
1107
|
constructor() {
|
|
945
1108
|
this.#destroyRef = inject(DestroyRef);
|
|
1109
|
+
this.#generatedListId = createElementControlId();
|
|
946
1110
|
this.#hostElementRef = inject(ElementRef);
|
|
947
1111
|
this.#i18n = inject(MonaI18nService);
|
|
948
1112
|
this.#typeaheadKey$ = new Subject();
|
|
949
1113
|
this.filterInput = viewChild(FilterInputComponent, /* @ts-ignore */
|
|
950
1114
|
...(ngDevMode ? [{ debugName: "filterInput" }] : /* istanbul ignore next */ []));
|
|
1115
|
+
/**
|
|
1116
|
+
* Id of the highlighted option, used as `aria-activedescendant` by the filter input.
|
|
1117
|
+
*/
|
|
1118
|
+
this.activeDescendant = computed(() => {
|
|
1119
|
+
const item = this.listService.highlightedItem();
|
|
1120
|
+
return item && !item.isHeader ? item.uid : null;
|
|
1121
|
+
}, /* @ts-ignore */
|
|
1122
|
+
...(ngDevMode ? [{ debugName: "activeDescendant" }] : /* istanbul ignore next */ []));
|
|
951
1123
|
this.classes = computed(() => {
|
|
952
1124
|
const classes = listThemeVariants();
|
|
953
1125
|
return twMerge(classes);
|
|
954
1126
|
}, /* @ts-ignore */
|
|
955
1127
|
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
1128
|
+
/**
|
|
1129
|
+
* The `listId` input, or a generated id so the listbox can always be referenced by `aria-controls`.
|
|
1130
|
+
*/
|
|
1131
|
+
this.effectiveListId = computed(() => this.listId() || this.#generatedListId, /* @ts-ignore */
|
|
1132
|
+
...(ngDevMode ? [{ debugName: "effectiveListId" }] : /* istanbul ignore next */ []));
|
|
956
1133
|
this.footerTemplate = contentChild(ListFooterTemplateDirective, { ...(ngDevMode ? { debugName: "footerTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
|
|
957
1134
|
this.groupHeaderClasses = computed(() => {
|
|
958
1135
|
const listItemClass = this.listItemClass();
|
|
@@ -1027,7 +1204,7 @@ class ListComponent {
|
|
|
1027
1204
|
const items = this.listService.viewItems().size();
|
|
1028
1205
|
const headerItems = this.listService
|
|
1029
1206
|
.viewItems()
|
|
1030
|
-
.where(i =>
|
|
1207
|
+
.where(i => i.isHeader)
|
|
1031
1208
|
.count();
|
|
1032
1209
|
const itemHeight = this.listService.virtualScrollOptions().height ?? 28;
|
|
1033
1210
|
const height = items * itemHeight + headerItems * 2;
|
|
@@ -1098,22 +1275,20 @@ class ListComponent {
|
|
|
1098
1275
|
this.listService.setFilter(event.filter);
|
|
1099
1276
|
}
|
|
1100
1277
|
}
|
|
1101
|
-
onListItemCheck(item) {
|
|
1102
|
-
this.toggleItem(item);
|
|
1103
|
-
}
|
|
1104
1278
|
onListItemClick(item) {
|
|
1105
1279
|
this.toggleItem(item);
|
|
1106
1280
|
}
|
|
1281
|
+
trackByUid(_, item) {
|
|
1282
|
+
return item.uid;
|
|
1283
|
+
}
|
|
1107
1284
|
cycleThroughMatchedItems(buffer) {
|
|
1108
|
-
const nextItem =
|
|
1109
|
-
const text = this.listService.getItemText(item);
|
|
1110
|
-
return text.toLowerCase().startsWith(buffer.toLowerCase());
|
|
1111
|
-
});
|
|
1285
|
+
const nextItem = this.listService.cycleThroughMatchedItems(buffer);
|
|
1112
1286
|
if (nextItem) {
|
|
1113
1287
|
const navigationMode = this.listService.navigableOptions().mode;
|
|
1114
1288
|
this.listService.highlightedItem.set(nextItem);
|
|
1115
|
-
if (navigationMode === "select") {
|
|
1289
|
+
if (navigationMode === "select" && this.listService.selectableOptions().enabled) {
|
|
1116
1290
|
this.selectItem(nextItem);
|
|
1291
|
+
this.itemSelect.emit({ item: nextItem, source: { via: "keyboard", key: buffer } });
|
|
1117
1292
|
}
|
|
1118
1293
|
this.scrollToItem(nextItem, true, "instant");
|
|
1119
1294
|
}
|
|
@@ -1122,7 +1297,33 @@ class ListComponent {
|
|
|
1122
1297
|
if (this.listService.navigableOptions().enabled) {
|
|
1123
1298
|
this.listService.highlightedItem.set(item);
|
|
1124
1299
|
}
|
|
1125
|
-
this.scrollToItem(item,
|
|
1300
|
+
this.scrollToItem(item, this.filterInput() == null);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Handles multiple-selection shortcuts from the WAI-ARIA listbox pattern:
|
|
1304
|
+
* Space toggles the highlighted option and Ctrl/Cmd+A selects every enabled option.
|
|
1305
|
+
* @returns whether the event was handled.
|
|
1306
|
+
*/
|
|
1307
|
+
handleMultipleSelectionKeys(event) {
|
|
1308
|
+
const selectableOptions = this.listService.selectableOptions();
|
|
1309
|
+
if (!selectableOptions.enabled || selectableOptions.mode !== "multiple") {
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
if (event.key === " " && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
1313
|
+
const highlightedItem = this.listService.highlightedItem();
|
|
1314
|
+
if (highlightedItem && !this.listService.isDisabled(highlightedItem)) {
|
|
1315
|
+
this.selectItem(highlightedItem);
|
|
1316
|
+
this.itemSelect.emit({ item: highlightedItem, source: { via: "keyboard", key: event.key } });
|
|
1317
|
+
}
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
if ((event.ctrlKey || event.metaKey) && !event.altKey && event.key.toLowerCase() === "a") {
|
|
1321
|
+
if (this.listService.selectAllItems()) {
|
|
1322
|
+
this.listService.selectedKeysChange$.next(this.listService.selectedKeys().toArray());
|
|
1323
|
+
}
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
return false;
|
|
1126
1327
|
}
|
|
1127
1328
|
handleNavigation(key) {
|
|
1128
1329
|
const direction = getListNavigationDirection(key);
|
|
@@ -1136,7 +1337,9 @@ class ListComponent {
|
|
|
1136
1337
|
const navigableOptions = this.listService.navigableOptions();
|
|
1137
1338
|
const selectableOptions = this.listService.selectableOptions();
|
|
1138
1339
|
const previousSelectedItems = this.listService.selectedListItems();
|
|
1139
|
-
const
|
|
1340
|
+
const filterInput = this.filterInput();
|
|
1341
|
+
// With a filter input, focus stays in the input and the active option is conveyed by aria-activedescendant.
|
|
1342
|
+
const item = this.listService.navigate(direction, navigableOptions.mode, filterInput == null);
|
|
1140
1343
|
if (item && navigableOptions.mode === "select" && selectableOptions.enabled) {
|
|
1141
1344
|
this.itemSelect.emit({ item, source: { via: "keyboard", key } });
|
|
1142
1345
|
if (previousSelectedItems.contains(item)) {
|
|
@@ -1144,7 +1347,6 @@ class ListComponent {
|
|
|
1144
1347
|
}
|
|
1145
1348
|
this.listService.selectedKeysChange$.next(this.listService.selectedKeys().toArray());
|
|
1146
1349
|
}
|
|
1147
|
-
const filterInput = this.filterInput();
|
|
1148
1350
|
if (filterInput) {
|
|
1149
1351
|
filterInput.focus();
|
|
1150
1352
|
}
|
|
@@ -1181,9 +1383,10 @@ class ListComponent {
|
|
|
1181
1383
|
scrollToItem(item, focus, behavior = "auto") {
|
|
1182
1384
|
const element = this.#hostElementRef.nativeElement.querySelector(`[data-uid="${item.uid}"]`);
|
|
1183
1385
|
if (element) {
|
|
1184
|
-
|
|
1386
|
+
// "nearest" avoids scrolling the page when the option is already visible.
|
|
1387
|
+
element.scrollIntoView({ block: "nearest", behavior });
|
|
1185
1388
|
if (focus) {
|
|
1186
|
-
element.focus();
|
|
1389
|
+
element.focus({ preventScroll: true });
|
|
1187
1390
|
}
|
|
1188
1391
|
}
|
|
1189
1392
|
else if (this.listService.virtualScrollOptions().enabled) {
|
|
@@ -1195,7 +1398,7 @@ class ListComponent {
|
|
|
1195
1398
|
this.virtualScrollViewport()?.scrollToOffset(offset, behavior);
|
|
1196
1399
|
const scrolledElement = this.#hostElementRef.nativeElement.querySelector(`[data-uid="${item.uid}"]`);
|
|
1197
1400
|
if (scrolledElement && focus) {
|
|
1198
|
-
scrolledElement.focus();
|
|
1401
|
+
scrolledElement.focus({ preventScroll: true });
|
|
1199
1402
|
}
|
|
1200
1403
|
});
|
|
1201
1404
|
}
|
|
@@ -1226,7 +1429,7 @@ class ListComponent {
|
|
|
1226
1429
|
}
|
|
1227
1430
|
const firstItem = this.listService
|
|
1228
1431
|
.viewItems()
|
|
1229
|
-
.where(i => !i.
|
|
1432
|
+
.where(i => !i.isHeader && !this.listService.isDisabled(i))
|
|
1230
1433
|
.firstOrDefault();
|
|
1231
1434
|
if (firstItem) {
|
|
1232
1435
|
this.focusToItem(firstItem);
|
|
@@ -1234,7 +1437,7 @@ class ListComponent {
|
|
|
1234
1437
|
}
|
|
1235
1438
|
setKeyboardEvents() {
|
|
1236
1439
|
fromEvent(this.#hostElementRef.nativeElement, "keydown")
|
|
1237
|
-
.pipe(takeUntilDestroyed(this.#destroyRef), filter(event => event.key === "Enter"
|
|
1440
|
+
.pipe(takeUntilDestroyed(this.#destroyRef), filter(event => event.key === "Enter"), tap(event => event.preventDefault()))
|
|
1238
1441
|
.subscribe((event) => {
|
|
1239
1442
|
const highlightedItem = this.listService.highlightedItem();
|
|
1240
1443
|
const selectedItem = this.listService.selectedListItems().firstOrDefault();
|
|
@@ -1251,18 +1454,21 @@ class ListComponent {
|
|
|
1251
1454
|
}
|
|
1252
1455
|
setNavigationEvents() {
|
|
1253
1456
|
fromEvent(this.#hostElementRef.nativeElement, "keydown")
|
|
1254
|
-
.pipe(takeUntilDestroyed(this.#destroyRef), filter(
|
|
1255
|
-
|
|
1256
|
-
if (
|
|
1457
|
+
.pipe(takeUntilDestroyed(this.#destroyRef), filter(() => this.listService.navigableOptions().enabled))
|
|
1458
|
+
.subscribe(event => {
|
|
1459
|
+
if (this.handleMultipleSelectionKeys(event)) {
|
|
1257
1460
|
event.preventDefault();
|
|
1461
|
+
return;
|
|
1258
1462
|
}
|
|
1259
|
-
|
|
1260
|
-
.subscribe(event => {
|
|
1261
|
-
if (isTypeaheadKey(event.key)) {
|
|
1463
|
+
if (isTypeaheadKey(event.key, event)) {
|
|
1262
1464
|
event.preventDefault();
|
|
1263
1465
|
this.#typeaheadKey$.next(event.key);
|
|
1264
1466
|
return;
|
|
1265
1467
|
}
|
|
1468
|
+
if (getListNavigationDirection(event.key) == null) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
event.preventDefault();
|
|
1266
1472
|
this.#typeaheadKey$.next("");
|
|
1267
1473
|
this.handleNavigation(event.key);
|
|
1268
1474
|
});
|
|
@@ -1311,7 +1517,7 @@ class ListComponent {
|
|
|
1311
1517
|
this.itemSelect.emit({ item, source: { via: "mouse" } });
|
|
1312
1518
|
}
|
|
1313
1519
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1314
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: ListComponent, isStandalone: true, selector: "mona-list", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, focusOnMount: { classPropertyName: "focusOnMount", publicName: "focusOnMount", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, listClass: { classPropertyName: "listClass", publicName: "listClass", isSignal: true, isRequired: false, transformFunction: null }, listId: { classPropertyName: "listId", publicName: "listId", isSignal: true, isRequired: false, transformFunction: null }, listItemClass: { classPropertyName: "listItemClass", publicName: "listItemClass", isSignal: true, isRequired: false, transformFunction: null }, listItemStyle: { classPropertyName: "listItemStyle", publicName: "listItemStyle", isSignal: true, isRequired: false, transformFunction: null }, listStyle: { classPropertyName: "listStyle", publicName: "listStyle", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect" }, host: { properties: { "attr.tabindex": "hostTabIndex()", "class": "classes()", "style.height": "listHeight()", "style.max-height": "listMaxHeight()", "style.max-width": "listMaxWidth()", "style.width": "listWidth()" } }, queries: [{ propertyName: "footerTemplate", first: true, predicate: ListFooterTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "groupHeaderTemplate", first: true, predicate: ListGroupHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "headerTemplate", first: true, predicate: ListHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "itemTemplate", first: true, predicate: ListItemTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "noDataTemplate", first: true, predicate: ListNoDataTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "filterInput", first: true, predicate: FilterInputComponent, descendants: true, isSignal: true }, { propertyName: "virtualScrollViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "@if (headerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\n}\n@if (listService.filterableOptions().enabled && listService.filterInputVisible()) {\n <div class=\"p-2\">\n <mona-filter-input\n [filter]=\"listService.filterText()\"\n [debounce]=\"listService.filterableOptions().debounce\"\n [placeholder]=\"listService.filterPlaceholder()\"\n [size]=\"size()\"\n (filterChange)=\"onFilterChange($event)\"></mona-filter-input>\n </div>\n}\n@if (listService.virtualScrollOptions().enabled) {\n @if (listService.viewItems().length !== 0) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"listService.virtualScrollOptions().height\"\n [style.height]=\"viewportHeight()\">\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"
|
|
1520
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: ListComponent, isStandalone: true, selector: "mona-list", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, focusOnMount: { classPropertyName: "focusOnMount", publicName: "focusOnMount", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, listClass: { classPropertyName: "listClass", publicName: "listClass", isSignal: true, isRequired: false, transformFunction: null }, listId: { classPropertyName: "listId", publicName: "listId", isSignal: true, isRequired: false, transformFunction: null }, listItemClass: { classPropertyName: "listItemClass", publicName: "listItemClass", isSignal: true, isRequired: false, transformFunction: null }, listItemStyle: { classPropertyName: "listItemStyle", publicName: "listItemStyle", isSignal: true, isRequired: false, transformFunction: null }, listStyle: { classPropertyName: "listStyle", publicName: "listStyle", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect" }, host: { properties: { "attr.tabindex": "hostTabIndex()", "class": "classes()", "style.height": "listHeight()", "style.max-height": "listMaxHeight()", "style.max-width": "listMaxWidth()", "style.width": "listWidth()" } }, queries: [{ propertyName: "footerTemplate", first: true, predicate: ListFooterTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "groupHeaderTemplate", first: true, predicate: ListGroupHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "headerTemplate", first: true, predicate: ListHeaderTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "itemTemplate", first: true, predicate: ListItemTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "noDataTemplate", first: true, predicate: ListNoDataTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "filterInput", first: true, predicate: FilterInputComponent, descendants: true, isSignal: true }, { propertyName: "virtualScrollViewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true, isSignal: true }], ngImport: i0, template: "@if (headerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\n}\n@if (listService.filterableOptions().enabled && listService.filterInputVisible()) {\n <div class=\"p-2\">\n <mona-filter-input\n [filter]=\"listService.filterText()\"\n [debounce]=\"listService.filterableOptions().debounce\"\n [placeholder]=\"listService.filterPlaceholder()\"\n [ariaLabel]=\"messages().filterList\"\n [controls]=\"effectiveListId()\"\n [activeDescendant]=\"activeDescendant()\"\n [size]=\"size()\"\n (filterChange)=\"onFilterChange($event)\"></mona-filter-input>\n </div>\n}\n@if (listService.virtualScrollOptions().enabled) {\n @if (listService.viewItems().length !== 0) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"listService.virtualScrollOptions().height\"\n [style.height]=\"viewportHeight()\">\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"effectiveListId()\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-multiselectable]=\"listService.selectableOptions().mode === 'multiple' || null\">\n <ng-container *cdkVirtualFor=\"let item of listService.viewItems(); trackBy: trackByUid\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n </ng-container>\n </ul>\n </cdk-virtual-scroll-viewport>\n } @else if (noDataTemplate()) {\n <ng-container [ngTemplateOutlet]=\"noDataTemplate()\"></ng-container>\n } @else {\n <mona-placeholder\n [text]=\"messages().noData\"\n class=\"w-full h-full flex items-center justify-center\"></mona-placeholder>\n }\n} @else {\n @if (listService.viewItems().length !== 0) {\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"effectiveListId()\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-multiselectable]=\"listService.selectableOptions().mode === 'multiple' || null\">\n @for (item of listService.viewItems(); track item.uid) {\n <ng-container\n [ngTemplateOutlet]=\"listItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n }\n </ul>\n } @else if (noDataTemplate()) {\n <ng-container [ngTemplateOutlet]=\"noDataTemplate()\"></ng-container>\n } @else {\n <mona-placeholder\n [text]=\"messages().noData\"\n class=\"w-full h-full flex items-center justify-center\"></mona-placeholder>\n }\n}\n@if (footerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"footerTemplate()\"></ng-container>\n}\n\n<ng-template #listItemTemplate let-item>\n @if (item.isHeader) {\n <li [class]=\"groupHeaderClasses()\" [style]=\"listItemStyle()\" [id]=\"item.uid\" role=\"presentation\">\n <mona-list-item [item]=\"item\" [template]=\"groupHeaderTemplate()\"></mona-list-item>\n </li>\n } @else {\n <li\n [class]=\"listItemClass()\"\n [class.px-0.5]=\"true\"\n [style]=\"listItemStyle()\"\n [id]=\"item.uid\"\n [attr.data-uid]=\"item.uid\"\n (click)=\"onListItemClick(item)\"\n monaListItem\n [item]=\"item\">\n <mona-list-item [item]=\"item\" [template]=\"itemTemplate()\"></mona-list-item>\n </li>\n }\n</ng-template>\n", dependencies: [{ kind: "component", type: FilterInputComponent, selector: "mona-filter-input", inputs: ["activeDescendant", "ariaLabel", "controls", "debounce", "filter", "placeholder", "size"], outputs: ["filterChange", "inputBlur", "inputFocus"] }, { kind: "component", type: ListItemComponent, selector: "mona-list-item", inputs: ["item", "template"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ListItemDirective, selector: "li[monaListItem]", inputs: ["item"] }, { kind: "component", type: PlaceholderComponent, selector: "mona-placeholder", inputs: ["text", "class"] }, { kind: "component", type: CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "directive", type: CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }] }); }
|
|
1315
1521
|
}
|
|
1316
1522
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ListComponent, decorators: [{
|
|
1317
1523
|
type: Component,
|
|
@@ -1331,7 +1537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
|
|
|
1331
1537
|
"[style.max-height]": "listMaxHeight()",
|
|
1332
1538
|
"[style.max-width]": "listMaxWidth()",
|
|
1333
1539
|
"[style.width]": "listWidth()"
|
|
1334
|
-
}, template: "@if (headerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\n}\n@if (listService.filterableOptions().enabled && listService.filterInputVisible()) {\n <div class=\"p-2\">\n <mona-filter-input\n [filter]=\"listService.filterText()\"\n [debounce]=\"listService.filterableOptions().debounce\"\n [placeholder]=\"listService.filterPlaceholder()\"\n [size]=\"size()\"\n (filterChange)=\"onFilterChange($event)\"></mona-filter-input>\n </div>\n}\n@if (listService.virtualScrollOptions().enabled) {\n @if (listService.viewItems().length !== 0) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"listService.virtualScrollOptions().height\"\n [style.height]=\"viewportHeight()\">\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"
|
|
1540
|
+
}, template: "@if (headerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"headerTemplate()\"></ng-container>\n}\n@if (listService.filterableOptions().enabled && listService.filterInputVisible()) {\n <div class=\"p-2\">\n <mona-filter-input\n [filter]=\"listService.filterText()\"\n [debounce]=\"listService.filterableOptions().debounce\"\n [placeholder]=\"listService.filterPlaceholder()\"\n [ariaLabel]=\"messages().filterList\"\n [controls]=\"effectiveListId()\"\n [activeDescendant]=\"activeDescendant()\"\n [size]=\"size()\"\n (filterChange)=\"onFilterChange($event)\"></mona-filter-input>\n </div>\n}\n@if (listService.virtualScrollOptions().enabled) {\n @if (listService.viewItems().length !== 0) {\n <cdk-virtual-scroll-viewport\n [itemSize]=\"listService.virtualScrollOptions().height\"\n [style.height]=\"viewportHeight()\">\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"effectiveListId()\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-multiselectable]=\"listService.selectableOptions().mode === 'multiple' || null\">\n <ng-container *cdkVirtualFor=\"let item of listService.viewItems(); trackBy: trackByUid\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n </ng-container>\n </ul>\n </cdk-virtual-scroll-viewport>\n } @else if (noDataTemplate()) {\n <ng-container [ngTemplateOutlet]=\"noDataTemplate()\"></ng-container>\n } @else {\n <mona-placeholder\n [text]=\"messages().noData\"\n class=\"w-full h-full flex items-center justify-center\"></mona-placeholder>\n }\n} @else {\n @if (listService.viewItems().length !== 0) {\n <ul\n [class]=\"innerListClasses()\"\n [style]=\"listStyle()\"\n [tabIndex]=\"-1\"\n [id]=\"effectiveListId()\"\n role=\"listbox\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-multiselectable]=\"listService.selectableOptions().mode === 'multiple' || null\">\n @for (item of listService.viewItems(); track item.uid) {\n <ng-container\n [ngTemplateOutlet]=\"listItemTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"></ng-container>\n }\n </ul>\n } @else if (noDataTemplate()) {\n <ng-container [ngTemplateOutlet]=\"noDataTemplate()\"></ng-container>\n } @else {\n <mona-placeholder\n [text]=\"messages().noData\"\n class=\"w-full h-full flex items-center justify-center\"></mona-placeholder>\n }\n}\n@if (footerTemplate()) {\n <ng-container [ngTemplateOutlet]=\"footerTemplate()\"></ng-container>\n}\n\n<ng-template #listItemTemplate let-item>\n @if (item.isHeader) {\n <li [class]=\"groupHeaderClasses()\" [style]=\"listItemStyle()\" [id]=\"item.uid\" role=\"presentation\">\n <mona-list-item [item]=\"item\" [template]=\"groupHeaderTemplate()\"></mona-list-item>\n </li>\n } @else {\n <li\n [class]=\"listItemClass()\"\n [class.px-0.5]=\"true\"\n [style]=\"listItemStyle()\"\n [id]=\"item.uid\"\n [attr.data-uid]=\"item.uid\"\n (click)=\"onListItemClick(item)\"\n monaListItem\n [item]=\"item\">\n <mona-list-item [item]=\"item\" [template]=\"itemTemplate()\"></mona-list-item>\n </li>\n }\n</ng-template>\n" }]
|
|
1335
1541
|
}], ctorParameters: () => [], propDecorators: { filterInput: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FilterInputComponent), { isSignal: true }] }], footerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ListFooterTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], groupHeaderTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ListGroupHeaderTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], headerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ListHeaderTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], itemTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ListItemTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], noDataTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ListNoDataTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], virtualScrollViewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkVirtualScrollViewport), { isSignal: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], focusOnMount: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusOnMount", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], itemSelect: [{ type: i0.Output, args: ["itemSelect"] }], listClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "listClass", required: false }] }], listId: [{ type: i0.Input, args: [{ isSignal: true, alias: "listId", required: false }] }], listItemClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "listItemClass", required: false }] }], listItemStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "listItemStyle", required: false }] }], listStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "listStyle", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }] } });
|
|
1336
1542
|
|
|
1337
1543
|
/*
|