@yuuvis/client-framework 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/breadcrumb/index.d.ts +2 -0
  2. package/breadcrumb/lib/breadcrumb/breadcrumb.component.d.ts +94 -0
  3. package/breadcrumb/lib/models/breadcrumb-item.model.d.ts +14 -0
  4. package/breadcrumb/lib/models/index.d.ts +1 -0
  5. package/common/lib/components/confirm/confirm.component.d.ts +1 -0
  6. package/common/lib/components/confirm/confirm.interface.d.ts +2 -0
  7. package/fesm2022/yuuvis-client-framework-breadcrumb.mjs +117 -0
  8. package/fesm2022/yuuvis-client-framework-breadcrumb.mjs.map +1 -0
  9. package/fesm2022/yuuvis-client-framework-common.mjs +24 -10
  10. package/fesm2022/yuuvis-client-framework-common.mjs.map +1 -1
  11. package/fesm2022/yuuvis-client-framework-forms.mjs +2 -2
  12. package/fesm2022/yuuvis-client-framework-forms.mjs.map +1 -1
  13. package/fesm2022/yuuvis-client-framework-list.mjs +365 -121
  14. package/fesm2022/yuuvis-client-framework-list.mjs.map +1 -1
  15. package/fesm2022/yuuvis-client-framework-object-details.mjs +28 -26
  16. package/fesm2022/yuuvis-client-framework-object-details.mjs.map +1 -1
  17. package/fesm2022/yuuvis-client-framework-object-form.mjs.map +1 -1
  18. package/fesm2022/yuuvis-client-framework-object-relationship.mjs +6 -5
  19. package/fesm2022/yuuvis-client-framework-object-relationship.mjs.map +1 -1
  20. package/fesm2022/yuuvis-client-framework-object-versions.mjs +1 -1
  21. package/fesm2022/yuuvis-client-framework-object-versions.mjs.map +1 -1
  22. package/fesm2022/yuuvis-client-framework-query-list.mjs +462 -127
  23. package/fesm2022/yuuvis-client-framework-query-list.mjs.map +1 -1
  24. package/fesm2022/yuuvis-client-framework-renderer.mjs +14 -16
  25. package/fesm2022/yuuvis-client-framework-renderer.mjs.map +1 -1
  26. package/fesm2022/yuuvis-client-framework-sort.mjs +26 -15
  27. package/fesm2022/yuuvis-client-framework-sort.mjs.map +1 -1
  28. package/fesm2022/yuuvis-client-framework-tile-list.mjs +709 -182
  29. package/fesm2022/yuuvis-client-framework-tile-list.mjs.map +1 -1
  30. package/lib/assets/i18n/ar.json +217 -0
  31. package/lib/assets/i18n/de.json +7 -3
  32. package/lib/assets/i18n/en.json +7 -3
  33. package/list/lib/list.component.d.ts +256 -44
  34. package/object-details/lib/object-details-header/object-details-header.component.d.ts +5 -2
  35. package/object-details/lib/object-details-shell/object-details-shell.component.d.ts +5 -2
  36. package/object-details/lib/object-details.component.d.ts +3 -1
  37. package/object-relationship/lib/object-relationship.component.d.ts +5 -2
  38. package/package.json +8 -4
  39. package/query-list/lib/query-list.component.d.ts +381 -86
  40. package/tile-list/lib/tile-list/tile-list.component.d.ts +527 -72
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, viewChild, contentChild, input, output, signal, computed, effect, untracked, HostBinding, Component, NgModule } from '@angular/core';
2
+ import { inject, signal, viewChild, contentChild, input, output, computed, untracked, effect, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
3
3
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
4
4
  import * as i1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
@@ -12,186 +12,441 @@ import { YuvListModule } from '@yuuvis/client-framework/list';
12
12
  import { DeviceService } from '@yuuvis/material';
13
13
 
14
14
  /**
15
- * Component to display a list of items based on a search query.
16
- * It will execute the query and render the results using the provided item template.
15
+ * Query-driven list component that executes a search query and renders the results
16
+ * using a consumer-provided item template.
17
17
  *
18
- * The component supports pagination, multi-selection, and drag selection.
18
+ * The component is a thin orchestration layer on top of `yuv-list`: it handles the
19
+ * full query lifecycle (executing, paginating, refreshing), exposes a typed result
20
+ * set via the optional `transformer` input, and delegates all keyboard navigation,
21
+ * selection, and accessibility concerns to the inner `ListComponent`.
19
22
  *
20
- * If you don't provide a transformer function, the raw SearchResultItem objects will be
21
- * used as items and passed to the template. The transformer function allows you to map
22
- * the SearchResultItem objects to a custom format before rendering.
23
+ * **Key Features:**
24
+ * - Accepts a `SearchQuery` object or a raw CMIS query string
25
+ * - Optional `transformer` function maps raw `SearchResultItem[]` to any custom type `T`
26
+ * - Built-in server-side pagination via Angular Material `mat-paginator`
27
+ * - Multi-selection with mouse drag (`DragSelectDirective`) and keyboard modifiers
28
+ * - "Drop-in" items prepended to the result set for optimistic UI updates
29
+ * - Busy state signal for loading indicators
30
+ * - Exposes a slim imperative API (`select`, `clear`, `refresh`, `goToPage`, …)
31
+ * so parents can drive the list programmatically
23
32
  *
24
- * Example usage:
33
+ * **Content Projection Slots:**
34
+ * - `#yuvQueryListItem` — required; `ng-template` used to render each result item.
35
+ * Receives the (optionally transformed) item as `$implicit`.
36
+ * - `#yuvQueryListEmpty` — optional; `ng-template` shown when the result set is empty.
37
+ *
38
+ * **Basic usage:**
25
39
  * ```html
26
- * <yuv-query-list [transformer]="transformResult" [query]="query" (itemSelect)="onItemSelect($event)">
27
-
28
- <!-- template used to render result item -->
29
- <ng-template #yuvQueryListItem let-item>
30
- <yuv-list-tile>
31
- <ng-template #titleSlot>{{ item.id }}</ng-template>
32
- <ng-template #descriptionSlot>{{ item.modified }}</ng-template>
33
- </yuv-list-tile>
34
- </ng-template>
35
-
36
- <!-- Content to display when the list is empty -->
37
- <ng-template #yuvQueryListEmpty>
38
- <div>No documents found.</div>
39
- </ng-template>
40
-
41
- </yuv-query-list>
40
+ * <yuv-query-list [query]="query" (itemSelect)="onSelect($event)">
41
+ * <ng-template #yuvQueryListItem let-item>
42
+ * <span>{{ item.title }}</span>
43
+ * </ng-template>
44
+ * </yuv-query-list>
42
45
  * ```
43
46
  *
44
- * ```ts
45
- * @Component({...})
46
- * export class TestQueryListComponent {
47
- * query = 'SELECT * FROM system:document';
47
+ * **With transformer and empty state:**
48
+ * ```html
49
+ * <yuv-query-list [query]="query" [transformer]="toViewModel" (itemSelect)="onSelect($event)">
50
+ * <ng-template #yuvQueryListItem let-item>
51
+ * <yuv-list-tile>
52
+ * <ng-template #titleSlot>{{ item.title }}</ng-template>
53
+ * <ng-template #descriptionSlot>{{ item.modified }}</ng-template>
54
+ * </yuv-list-tile>
55
+ * </ng-template>
56
+ * <ng-template #yuvQueryListEmpty>
57
+ * <p>No results found.</p>
58
+ * </ng-template>
59
+ * </yuv-query-list>
60
+ * ```
48
61
  *
49
- * transformResult = (items: SearchResultItem[]) =>
50
- * items.map((item) => ({
51
- * id: item.fields.get(BaseObjectTypeField.OBJECT_ID),
62
+ * ```ts
63
+ * toViewModel = (items: SearchResultItem[]) =>
64
+ * items.map(item => ({
65
+ * title: item.fields.get(BaseObjectTypeField.OBJECT_ID),
52
66
  * modified: item.fields.get(BaseObjectTypeField.MODIFICATION_DATE)
53
67
  * }));
54
- * } *
55
68
  * ```
69
+ *
70
+ * @typeParam T The shape of each item after the optional `transformer` is applied.
71
+ * Defaults to `any` when no transformer is provided.
56
72
  */
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
74
  class QueryListComponent {
75
+ //#region Dependencies
76
+ #device;
77
+ #searchService;
78
+ #items;
79
+ #dropInItems;
80
+ #listItemUpdates;
81
+ //#endregion
58
82
  constructor() {
83
+ //#region Dependencies
59
84
  this.#device = inject(DeviceService);
60
85
  this.#searchService = inject(SearchService);
86
+ //#endregion
87
+ //#region Angular stuff
88
+ /**
89
+ * Current pagination state. Set to a `Pagination` object when the last query
90
+ * result contained multiple pages; `undefined` when all results fit on one page
91
+ * or no query has been executed yet.
92
+ *
93
+ * Drives the `[class.pagination]` host binding so the template can conditionally
94
+ * render the `mat-paginator` footer.
95
+ */
96
+ this.pagination = signal(undefined);
97
+ /**
98
+ * Reference to the inner `ListComponent` instance.
99
+ *
100
+ * Used internally to delegate imperative operations (select, clear, focus, …).
101
+ * Can also be accessed from the parent via a `@ViewChild` on `yuv-query-list` if
102
+ * fine-grained control over the inner list is needed, though the public API methods
103
+ * on this component are preferred.
104
+ */
61
105
  this.list = viewChild.required('list');
106
+ /**
107
+ * Reference to the `#yuvQueryListItem` `ng-template` projected by the consumer.
108
+ *
109
+ * This template is used to render each individual item in the list. The template
110
+ * receives the (optionally transformed) item as the `$implicit` context variable:
111
+ * ```html
112
+ * <ng-template #yuvQueryListItem let-item>{{ item.title }}</ng-template>
113
+ * ```
114
+ */
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
116
  this.itemTemplate = contentChild('yuvQueryListItem');
117
+ /**
118
+ * Reference to the optional `#yuvQueryListEmpty` `ng-template` projected by the consumer.
119
+ *
120
+ * When provided, this template is rendered in place of the list whenever the query
121
+ * returns zero items. If omitted, nothing is shown for the empty state.
122
+ * ```html
123
+ * <ng-template #yuvQueryListEmpty>
124
+ * <p>No results found.</p>
125
+ * </ng-template>
126
+ * ```
127
+ */
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
129
  this.emptyTemplate = contentChild('yuvQueryListEmpty');
64
- this.isTouchDevice = this.#device.isTouchEnabled;
65
130
  /**
66
- * The query to execute (SearchQuery object or CMIS query string).
131
+ * The search query to execute.
132
+ *
133
+ * Accepts either a structured `SearchQuery` object (field filters, sort orders, etc.)
134
+ * or a raw CMIS query string. The query is re-executed reactively every time this
135
+ * input changes. Set to `null` or `undefined` to clear the list without triggering
136
+ * a new request.
67
137
  */
68
138
  this.query = input();
69
139
  /**
70
- * Optional property name to use as unique identifier for items.
71
- * If not provided, the index of the item in the result set will be used.
140
+ * Property name on the (transformed) item to use as a stable unique identifier.
141
+ *
142
+ * When provided, the template renderer uses this property for `@for` tracking instead
143
+ * of the item's position index, which preserves DOM nodes and component state when the
144
+ * list is refreshed with partially overlapping results.
145
+ *
146
+ * If omitted, the zero-based index is used as the tracking key.
147
+ *
148
+ * @default null
72
149
  */
73
150
  this.idProperty = input(null);
74
151
  /**
75
- * Optional transformer function to map SearchResultItem to a custom format.
152
+ * Optional mapping function applied to the raw `SearchResultItem[]` returned by the
153
+ * search service before the results are rendered.
154
+ *
155
+ * Use this to project only the fields your template needs, compute derived properties,
156
+ * or convert dates/enums to display-friendly values. The generic type parameter `T`
157
+ * of the component is inferred from the return type of this function.
158
+ *
159
+ * If omitted, raw `SearchResultItem` objects are passed to the item template as-is.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * transformer = (items: SearchResultItem[]) =>
164
+ * items.map(item => ({
165
+ * id: item.fields.get(BaseObjectTypeField.OBJECT_ID) as string,
166
+ * title: item.fields.get('cm:name') as string
167
+ * }));
168
+ * ```
76
169
  */
77
170
  this.transformer = input();
78
171
  /**
79
- * Function that returns `true` if selection changes should be prevented.
80
- * This can be used to temporarily block selection changes, e.g. while
81
- * there is a pending change inside another component that refers to the
82
- * current selection.
172
+ * Guard function that temporarily blocks all selection changes.
173
+ *
174
+ * Forwarded directly to the inner `ListComponent`. As long as the returned predicate
175
+ * evaluates to `true`, any attempt to change the selection — via click, keyboard, or
176
+ * the programmatic API — is silently ignored. Useful when the parent has unsaved
177
+ * changes tied to the current selection and needs to prevent the user from
178
+ * accidentally navigating away.
179
+ *
180
+ * @default () => false (never prevents)
83
181
  */
84
182
  this.preventChangeUntil = input(() => false);
85
183
  /**
86
- * If `true`, the list will select an item automatically on initialization.
87
- * First, list will search for an item item that has the "selected"-attribute
88
- * and is not disabled. If no such item exists, the first item will be selected.
184
+ * Automatically selects an item when the list is first rendered.
185
+ *
186
+ * Forwarded to the inner `ListComponent`. The selection logic runs once after the
187
+ * initial query result is displayed and follows this priority:
188
+ * 1. The first non-disabled item that already carries the `selected` attribute.
189
+ * 2. If no such item exists and `autoSelect` is `true`, the item at index 0.
190
+ *
191
+ * Accepts any truthy string in addition to a real boolean so it can be set as a
192
+ * plain HTML attribute: `<yuv-query-list autoSelect>`.
193
+ *
89
194
  * @default false
90
195
  */
91
- this.autoSelect = input(false, { transform: (value) => coerceBooleanProperty(value) });
196
+ this.autoSelect = input(false, {
197
+ transform: (value) => coerceBooleanProperty(value)
198
+ });
92
199
  /**
93
- * Event emitted when items are selected. Emits an array of selected item indices.
200
+ * Emits the current selection as an array of zero-based item indices whenever
201
+ * the selection changes — via click, drag, keyboard, or programmatic API calls.
202
+ *
203
+ * An empty array signals that the selection has been cleared.
94
204
  */
95
205
  this.itemSelect = output();
96
206
  /**
97
- * Event emitted during drag selection, providing the current selection of item indices.
207
+ * Emits the live selection indices while the user is actively dragging to select.
208
+ *
209
+ * Fires continuously during a drag operation (before the drag is released), allowing
210
+ * the parent to show a live preview of what will be selected. Once the drag ends,
211
+ * `itemSelect` fires with the final committed selection.
98
212
  */
99
213
  this.dragSelectChange = output();
100
214
  /**
101
- * Event emitted when an item is double-clicked, providing the index of the item.
215
+ * Emits the zero-based index of an item when it is double-clicked.
216
+ *
217
+ * The inner list operates with `selfHandleClick` mode when double-click is active,
218
+ * so single clicks do not immediately trigger `itemSelect`. The parent should listen
219
+ * to both `itemSelect` (for single-click selection) and this event (for double-click
220
+ * actions like opening a detail view).
102
221
  */
103
222
  this.itemDoubleClick = output();
104
223
  /**
105
- * Event emitted when the query result is available, providing total count of items.
224
+ * Emits once per query execution when the search result arrives, providing the total
225
+ * count of matching items and the raw result page.
226
+ *
227
+ * Use this to update external counters, breadcrumbs, or analytics — without having to
228
+ * execute a separate count query. Note that `totalCount` reflects the server-side total,
229
+ * not just the number of items on the current page.
106
230
  */
107
231
  this.queryResult = output();
108
- this.#items = signal([]);
109
- this.#dropInItems = signal([]);
110
- this.dropInSize = computed(() => this.#dropInItems().length);
111
- /**
112
- * The list of result items after applying the optional transformer.
113
- */
114
- this.resultItems = computed(() => {
115
- const items = this.#items();
116
- const updates = this.#listItemUpdates();
117
- const transformer = this.transformer();
118
- const transformedResult = transformer ? transformer(items) : items;
119
- const result = [...this.#dropInItems(), ...transformedResult];
120
- // apply updates to transformed result
121
- Object.keys(updates).forEach((index) => {
122
- result[Number(index)] = updates[Number(index)];
123
- });
124
- return result;
125
- });
126
- this.#listItemUpdates = signal({});
127
232
  /**
128
- * Number of items to fetch per page when executing the query.
233
+ * Number of items to request per page from the search service.
234
+ *
235
+ * Controls the `size` parameter of each search request. When the total result count
236
+ * exceeds this value, pagination controls are shown and the user can navigate between
237
+ * pages via `changePage()` / `goToPage()`.
238
+ *
129
239
  * @default SearchService.DEFAULT_QUERY_SIZE
130
240
  */
131
241
  this.pageSize = input(SearchService.DEFAULT_QUERY_SIZE);
132
242
  /**
133
- * Enables or disables drag selection of items. If `true`, users can select multiple items by dragging the mouse.
243
+ * Enables mouse drag-to-select on list items.
244
+ *
245
+ * When `true` and `multiselect` is also `true`, the user can click and drag across
246
+ * items to select a range without holding Shift. Powered by `DragSelectDirective`.
247
+ * Automatically disabled on touch devices regardless of this input value.
248
+ *
134
249
  * @default true
135
250
  */
136
251
  this.enableDragSelect = input(true);
137
252
  /**
138
- * Sets up the ability to select multiple tiles
253
+ * Enables multi-selection mode.
254
+ *
255
+ * When `true`, the user can hold **Shift** or **Ctrl** to extend or toggle the
256
+ * selection, and drag-to-select becomes available (if `enableDragSelect` is also
257
+ * `true`). Forwarded to the inner `ListComponent`.
258
+ *
139
259
  * @default false
140
260
  */
141
261
  this.multiselect = input(false);
142
262
  /**
143
- * If `true`, the component will handle selection itself. This means that
144
- * the parent component will be responsible for styling the selected and
145
- * focused items. If `false`, the component will take care of visualizing
146
- * the selection and focus states.
263
+ * Delegates visual selection and focus state rendering to the parent component.
264
+ *
265
+ * When `false` (default), `yuv-list` applies CSS classes for selected and active
266
+ * states automatically. When `true`, those signals are still updated but the host
267
+ * receives the `self-handle-selection` CSS class, allowing the parent to apply
268
+ * its own visual treatment. Forwarded to the inner `ListComponent`.
269
+ *
147
270
  * @default false
148
271
  */
149
272
  this.selfHandleSelection = input(false);
150
273
  /**
151
- * Indicator signal whether a query is currently being executed.
152
- * You could use this to show a loading indicator in the UI.
274
+ * Whether to include permission information in CMIS search results.
275
+ * When `true`, the third parameter of `searchCmis()` is set,
276
+ * causing the backend to return permission data alongside each item.
277
+ *
278
+ * @default false
279
+ */
280
+ this.includePermissions = input(false);
281
+ //#endregion
282
+ //#region Properties
283
+ /**
284
+ * Whether the current device has touch input enabled.
285
+ *
286
+ * Sourced from `DeviceService`. Used in the template to conditionally disable
287
+ * drag-to-select, which is not suitable for touch interactions.
288
+ */
289
+ this.isTouchDevice = this.#device.isTouchEnabled;
290
+ /**
291
+ * Number of drop-in items currently prepended to the result list.
292
+ *
293
+ * Derived from the internal `#dropInItems` signal. Exposed publicly so the template
294
+ * and parent components can easily check whether any temporary items are present
295
+ * (e.g. to show a visual indicator or adjust layout) without accessing internal state.
296
+ */
297
+ this.dropInSize = computed(() => this.#dropInItems().length);
298
+ /**
299
+ * The final list of items rendered by the template.
300
+ *
301
+ * Computed from three sources, merged in this order:
302
+ * 1. **Drop-in items** (`#dropInItems`) — temporary items prepended to the list,
303
+ * e.g. newly created objects that should appear before they match the query.
304
+ * 2. **Transformed query results** — raw `SearchResultItem[]` passed through the
305
+ * optional `transformer` function (or cast directly to `T[]` if none is provided).
306
+ * 3. **Optimistic updates** (`#listItemUpdates`) — per-index overrides applied on top
307
+ * of the merged list, allowing individual items to be patched without re-fetching.
308
+ *
309
+ * Re-evaluates automatically whenever any of the three sources change.
310
+ */
311
+ this.resultItems = computed(() => {
312
+ const items = this.#items();
313
+ const updates = this.#listItemUpdates();
314
+ const transformer = this.transformer();
315
+ const transformedResult = transformer ? transformer(items) : items;
316
+ const dropIns = this.#dropInItems();
317
+ let merged;
318
+ const idProp = this.idProperty();
319
+ if (dropIns.length > 0 && idProp) {
320
+ // Deduplicate: filter out server results that already appear as drop-in items
321
+ const dropInIds = new Set(dropIns.map((item) => item[idProp]));
322
+ const filtered = transformedResult.filter((item) => !dropInIds.has(item[idProp]));
323
+ merged = [...dropIns, ...filtered];
324
+ }
325
+ else {
326
+ merged = [...dropIns, ...transformedResult];
327
+ }
328
+ // apply updates to transformed result
329
+ Object.keys(updates).forEach((index) => {
330
+ merged[Number(index)] = updates[Number(index)];
331
+ });
332
+ return merged;
333
+ });
334
+ /**
335
+ * Indicates whether a search request is currently in flight.
336
+ *
337
+ * Set to `true` immediately before a query or page request is dispatched to the
338
+ * search service, and back to `false` once the response (or error) arrives.
339
+ * Bind this to a loading indicator or `BusyOverlayDirective` in the parent template:
340
+ * ```html
341
+ * <yuv-query-list #qList ...>...</yuv-query-list>
342
+ * <yuv-busy-overlay [active]="qList.busy()" />
343
+ * ```
153
344
  */
154
345
  this.busy = signal(false);
155
- this.#executeQueryEffect = effect(() => {
346
+ this.#items = signal([]);
347
+ this.#dropInItems = signal([]);
348
+ this.#listItemUpdates = signal({});
349
+ //#endregion
350
+ //#region Effects
351
+ this.#executeQueryEffect = () => {
156
352
  // execute the query each time it changes
157
353
  const query = this.query();
158
354
  if (query)
159
355
  untracked(() => {
160
356
  this.#executeQuery(query || null);
161
357
  });
162
- });
358
+ };
359
+ effect(this.#executeQueryEffect);
163
360
  }
164
- #device;
165
- #searchService;
166
- #items;
167
- #dropInItems;
168
- #listItemUpdates;
169
- #executeQueryEffect;
361
+ //#region UI Methods
362
+ /** Resolves the tracking key for a result item in the `@for` loop. */
363
+ trackItem(item, index) {
364
+ const prop = this.idProperty();
365
+ return prop ? item[prop] : index;
366
+ }
367
+ /**
368
+ * Handles a single click on a list item.
369
+ *
370
+ * Forwards the click to the inner `ListComponent` with the correct Shift/Ctrl modifier
371
+ * flags so range- and toggle-selection work correctly. Also transfers DOM focus to the
372
+ * list host so subsequent keyboard navigation works without an additional Tab press.
373
+ *
374
+ * Called from the template via `(click)` on each item wrapper. Not intended for
375
+ * external callers — use `select()` instead.
376
+ *
377
+ * @param idx Zero-based index of the clicked item.
378
+ * @param event The originating mouse event (provides `shiftKey` and `ctrlKey` flags).
379
+ */
170
380
  onItemClick(idx, event) {
171
381
  this.list().select(idx, event.shiftKey, event.ctrlKey);
172
382
  this.list().focus();
173
383
  }
384
+ /**
385
+ * Handles a double-click on a list item.
386
+ *
387
+ * Emits the `itemDoubleClick` output with the item's index. The parent can use this
388
+ * to trigger a secondary action (e.g. opening a detail panel or navigating to a route)
389
+ * that is distinct from the primary single-click selection.
390
+ *
391
+ * Called from the template via `ClickDoubleDirective`. Not intended for external callers.
392
+ *
393
+ * @param idx Zero-based index of the double-clicked item.
394
+ * @param event The originating mouse event (unused, kept for directive compatibility).
395
+ */
396
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
174
397
  onItemDoubleClick(idx, event) {
175
398
  this.itemDoubleClick.emit(idx);
176
399
  }
400
+ /**
401
+ * Handles intermediate drag-selection changes from `DragSelectDirective`.
402
+ *
403
+ * Fires continuously while the user is dragging across items. Updates the inner list's
404
+ * multi-selection state in real time so items are highlighted as the drag progresses,
405
+ * and also emits `dragSelectChange` so the parent can show a live preview.
406
+ *
407
+ * Called from the template via `(dragSelectChange)` on the drag-select host.
408
+ * Not intended for external callers.
409
+ *
410
+ * @param sel Current array of zero-based indices covered by the drag gesture.
411
+ */
177
412
  onDragSelectChange(sel) {
178
413
  this.list().multiSelect(sel);
179
414
  this.dragSelectChange.emit(sel);
180
415
  }
416
+ /**
417
+ * Handles the final committed drag-selection from `DragSelectDirective`.
418
+ *
419
+ * Called when the user releases the mouse after a drag-to-select gesture. Transfers
420
+ * DOM focus to the list (so keyboard navigation works immediately after) and emits
421
+ * the final selection via `itemSelect`.
422
+ *
423
+ * Called from the template via `(dragSelect)` on the drag-select host.
424
+ * Not intended for external callers.
425
+ *
426
+ * @param sel Final array of zero-based indices selected by the drag gesture.
427
+ */
181
428
  onDragSelect(sel) {
182
429
  this.list().focus();
183
430
  this.itemSelect.emit(sel);
184
431
  }
432
+ //#endregion
433
+ //#region Public methods
185
434
  /**
186
- * Updates an item in the list at the specified index with the provided value.
187
- * The value should match the type returned by the transformer function, if provided.
188
- * If you did not provide a transformer, the value should be of type SearchResultItem.
435
+ * Applies optimistic per-item overrides on top of the current query result.
436
+ *
437
+ * Each entry in the `updates` array patches the item at the given `index` with
438
+ * the provided `value`. The value type must match `T` — i.e. the shape produced
439
+ * by the `transformer` function, or `SearchResultItem` if no transformer is used.
440
+ *
441
+ * Overrides accumulate: calling this method multiple times merges the new entries
442
+ * with any previously applied ones. All overrides are automatically discarded when
443
+ * the user navigates to a different page (see `goToPage()`), since a fresh server
444
+ * response replaces the local data.
189
445
  *
190
- * Use this method for optimistic updates of list items. The updates be removed
191
- * when the user navigates to another search result page.
446
+ * **Use case:** apply instant visual feedback after a user action (rename, status
447
+ * change, etc.) without waiting for a full query refresh.
192
448
  *
193
- * @param index Index of the item to update.
194
- * @param value The new value for the item.
449
+ * @param updates Array of `{ index, value }` pairs describing which items to patch.
195
450
  */
196
451
  updateListItems(updates) {
197
452
  const updatesRecord = updates.reduce((acc, curr) => {
@@ -203,20 +458,25 @@ class QueryListComponent {
203
458
  this.#listItemUpdates.set({ ...this.#listItemUpdates(), ...updatesRecord });
204
459
  }
205
460
  /**
206
- * Optional array of items to be shown in addition to the query results.
207
- * These items will be prepended to the list of query results and visually
208
- * highlighted. They will also be affected by selection and drag-selection.
461
+ * Prepends a set of temporary items to the top of the result list.
209
462
  *
210
- * Use this input to "drop in" items, for example when creating features
211
- * like pasting items into a list where they would otherwise not appear due
212
- * to the current query/filter.
463
+ * Drop-in items appear before all query results and are visually distinguished
464
+ * by the template (e.g. a highlighted background). They participate in selection
465
+ * and drag-to-select exactly like regular items.
213
466
  *
214
- * Changing the page of the query will remove the drop-in items again, as
215
- * they are meant to be temporary.
467
+ * The existing selection indices are automatically shifted by `items.length` so that
468
+ * the currently selected query-result items remain selected after the prepend.
216
469
  *
217
- * @param items The items to drop into the list.
218
- * @param scrollTo If `true`, the list will scroll to the top after dropping
219
- * in the items. Default is `true`.
470
+ * **Typical use case:** when the user pastes or creates an object that does not yet
471
+ * appear in the current query (e.g. because the index has not been updated), drop it
472
+ * in temporarily so the user sees it immediately without a full refresh.
473
+ *
474
+ * Drop-in items are cleared automatically when the user navigates to a different page.
475
+ * Call `dropItems([])` to remove them programmatically.
476
+ *
477
+ * @param items Items to prepend. Must be of type `T` (same as query result items).
478
+ * @param scrollTo Whether to smooth-scroll the list back to the top after prepending.
479
+ * Defaults to `true` so the newly dropped items are immediately visible.
220
480
  */
221
481
  dropItems(items, scrollTo = true) {
222
482
  this.#dropInItems.set(items);
@@ -226,59 +486,124 @@ class QueryListComponent {
226
486
  }
227
487
  }
228
488
  /**
229
- * Selects multiple items in the list.
489
+ * Programmatically replaces the entire selection with the given indices.
490
+ *
491
+ * Only effective when `multiselect` is `true`. Out-of-range indices are silently
492
+ * discarded. The resulting selection is sorted ascending before being applied.
493
+ *
494
+ * Use this when the parent needs to restore a previously saved multi-selection
495
+ * state, e.g. after navigation or component re-initialization.
496
+ *
497
+ * @param index Array of zero-based item indices to select.
230
498
  */
231
499
  multiSelect(index) {
232
500
  this.list().multiSelect(index);
233
501
  }
502
+ /**
503
+ * Selects the item at the given zero-based index.
504
+ *
505
+ * Delegates to the inner `ListComponent`. The `preventChangeUntil` guard and
506
+ * `disableSelection` state are respected. Clamps the index to the valid range.
507
+ *
508
+ * @param index Zero-based index of the item to select.
509
+ */
234
510
  select(index) {
235
511
  this.list().select(index);
236
512
  }
513
+ /**
514
+ * Moves keyboard focus to the item at the given index and selects it.
515
+ *
516
+ * Combines focus management and selection in one call, and transfers DOM focus
517
+ * to the list host. Use this when the parent wants to programmatically drive
518
+ * both the visual focus indicator and the selection simultaneously.
519
+ *
520
+ * @param index Zero-based index of the item to activate.
521
+ */
237
522
  setActiveItem(index) {
238
523
  this.list().setActiveItem(index);
239
524
  }
240
525
  /**
241
- * Clear the current selection.
242
- * @param silent If `true`, the `itemSelect` event will not be emitted.
526
+ * Clears the current selection and resets the active keyboard-focus index.
527
+ *
528
+ * If the selection is already empty, the method is a no-op. The `preventChangeUntil`
529
+ * guard is respected — if the guard returns `true`, the clear is blocked.
530
+ *
531
+ * @param silent When `true`, skips emitting `itemSelect` after clearing. Use this
532
+ * when the parent needs to reset state programmatically without
533
+ * triggering downstream reactions.
243
534
  */
244
535
  clear(silent = false) {
245
536
  this.list().clear(silent);
246
537
  }
247
538
  /**
248
- * Refreshes the list by re-executing the current query/page.
539
+ * Re-executes the current query without changing the page or query parameters.
540
+ *
541
+ * If pagination is active, re-fetches the same page the user is currently on.
542
+ * If no pagination is active, re-runs the original query from scratch.
543
+ *
544
+ * Use this to reflect server-side changes (e.g. after a create/delete operation)
545
+ * without navigating away from the current view.
249
546
  */
250
547
  refresh() {
251
548
  const query = this.query();
252
549
  if (query) {
253
- if (this.pagination) {
254
- this.goToPage(this.pagination.page);
550
+ if (this.pagination()) {
551
+ this.goToPage(this.pagination().page);
255
552
  }
256
553
  else
257
554
  this.#executeQuery(query || null);
258
555
  }
259
556
  }
557
+ /**
558
+ * Forces the `transformer` function to be re-applied to the current result set.
559
+ *
560
+ * Normally the transformer runs automatically whenever the underlying `#items`
561
+ * signal changes. Use this method when the transformer itself has external
562
+ * dependencies that changed (e.g. a locale or display-format setting) but the
563
+ * raw search result did not — triggering a re-evaluation by creating a new array
564
+ * reference for `#items` without fetching from the server again.
565
+ */
260
566
  runTransformerAgain() {
261
567
  this.#items.set([...this.#items()]);
262
568
  }
263
- changePage(pe) {
264
- this.goToPage(pe.pageIndex + 1);
569
+ /**
570
+ * Handles a page-change event emitted by the `mat-paginator`.
571
+ *
572
+ * Converts the zero-based `pageEvent.pageIndex` to the one-based page number
573
+ * expected by `goToPage()` and delegates the request.
574
+ *
575
+ * Bound to `(page)` on the `mat-paginator` in the template.
576
+ *
577
+ * @param pageEvent The pagination event emitted by `MatPaginatorModule`.
578
+ */
579
+ changePage(pageEvent) {
580
+ this.#dropInItems.set([]);
581
+ this.goToPage(pageEvent.pageIndex + 1);
265
582
  }
583
+ /**
584
+ * Fetches and displays the given page of the current query result.
585
+ *
586
+ * Sets `busy` to `true` for the duration of the request. On success, updates
587
+ * the pagination state, clears all optimistic overrides (they are page-scoped),
588
+ * and replaces `#items` with the new page's results.
589
+ *
590
+ * Also used internally by `refresh()` to reload the currently active page.
591
+ *
592
+ * @param page One-based page number to navigate to.
593
+ */
266
594
  goToPage(page) {
267
595
  const query = this.query();
268
596
  if (!query)
269
597
  return;
270
598
  this.busy.set(true);
271
599
  this.#searchService
272
- .getPage(query, page, this.pageSize())
600
+ .getPage(query, page, this.pageSize(), this.includePermissions())
273
601
  .subscribe({
274
602
  next: (res) => {
275
603
  this.#setupPagination(res);
276
- // changing the page, reset any list item updates as they should only be used for
277
- // optimistic updates on the current page. Moving between pages would trigger a new
278
- // query anyway (will get the recent data).
279
604
  this.#listItemUpdates.set({});
280
- this.#dropInItems.set([]);
281
605
  this.#items.set(res.items);
606
+ this.queryResult.emit({ totalCount: res.totalNumItems, items: res.items });
282
607
  this.busy.set(false);
283
608
  },
284
609
  error: (err) => {
@@ -288,19 +613,23 @@ class QueryListComponent {
288
613
  }
289
614
  });
290
615
  }
616
+ //#endregion
617
+ //#region Utilities
291
618
  /**
292
619
  * Executes a search query.
293
- * @param query The search query to execute. This may be a SearchQuery object or a string. If it's a string, it is supposed to
620
+ * @param query The search query to execute.
621
+ * This may be a SearchQuery object or a string. If it's a string, it is supposed to
294
622
  * be a CMIS query statement.
295
623
  */
296
624
  #executeQuery(query) {
297
625
  if (query && !this.busy()) {
298
626
  this.busy.set(true);
299
627
  (typeof query === 'string'
300
- ? this.#searchService.searchCmis(query, this.pageSize())
628
+ ? this.#searchService.searchCmis(query, this.pageSize(), this.includePermissions())
301
629
  : this.#searchService.search(query)).subscribe({
302
630
  next: (res) => {
303
631
  this.#setupPagination(res);
632
+ this.#listItemUpdates.set({});
304
633
  this.#items.set(res.items);
305
634
  this.queryResult.emit({ totalCount: res.totalNumItems, items: res.items });
306
635
  this.busy.set(false);
@@ -313,25 +642,31 @@ class QueryListComponent {
313
642
  }
314
643
  }
315
644
  #setupPagination(searchResult) {
316
- this.pagination = undefined;
317
- this.pagination = searchResult.paging
645
+ this.pagination.set(searchResult.paging
318
646
  ? {
319
647
  total: searchResult.totalNumItems,
320
648
  pages: searchResult.paging.totalPages,
321
649
  page: searchResult.paging.page
322
650
  }
323
- : undefined;
651
+ : undefined);
324
652
  }
653
+ //#endregion
654
+ //#region Effects
655
+ #executeQueryEffect;
325
656
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: QueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
326
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: QueryListComponent, isStandalone: true, selector: "yuv-query-list", inputs: { query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, idProperty: { classPropertyName: "idProperty", publicName: "idProperty", isSignal: true, isRequired: false, transformFunction: null }, transformer: { classPropertyName: "transformer", publicName: "transformer", isSignal: true, isRequired: false, transformFunction: null }, preventChangeUntil: { classPropertyName: "preventChangeUntil", publicName: "preventChangeUntil", isSignal: true, isRequired: false, transformFunction: null }, autoSelect: { classPropertyName: "autoSelect", publicName: "autoSelect", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, enableDragSelect: { classPropertyName: "enableDragSelect", publicName: "enableDragSelect", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, selfHandleSelection: { classPropertyName: "selfHandleSelection", publicName: "selfHandleSelection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect", dragSelectChange: "dragSelectChange", itemDoubleClick: "itemDoubleClick", queryResult: "queryResult" }, host: { properties: { "class.pagination": "this.pagination" } }, queries: [{ propertyName: "itemTemplate", first: true, predicate: ["yuvQueryListItem"], descendants: true, isSignal: true }, { propertyName: "emptyTemplate", first: true, predicate: ["yuvQueryListEmpty"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }], ngImport: i0, template: "<yuv-list\n #list\n [multiselect]=\"multiselect()\"\n [autoSelect]=\"autoSelect()\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [selfHandleClick]=\"true\"\n [selfHandleSelection]=\"selfHandleSelection()\"\n [yuvDragSelect]=\"{ disabled: !enableDragSelect() || !multiselect() || isTouchDevice }\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n (dragSelect)=\"onDragSelect($event)\"\n (itemSelect)=\"itemSelect.emit($event)\"\n>\n @for (i of resultItems(); track idProperty() || $index) {\n <div yuvListItem yuvDragSelectItem [class.drop-in]=\"$index < dropInSize()\"\n (click.single)=\"onItemClick($index, $event)\"\n (click.double)=\"onItemDoubleClick($index, $event)\" >\n <ng-container *ngTemplateOutlet=\"itemTemplate() || null; context: { $implicit: i, index: $index }\"></ng-container>\n </div>\n } @empty {\n <ng-container *ngTemplateOutlet=\"emptyTemplate() || null\"></ng-container>\n }\n</yuv-list>\n@if (pagination) {\n <mat-paginator class=\"paginator\" [length]=\"pagination.total\" [pageSize]=\"pageSize()\" (page)=\"changePage($event)\" hidePageSize> </mat-paginator>\n}\n", styles: [":host{--paging-background: var(--ymt-surface);--drop-in-item-outline: 2px dashed rgb(from var(--ymt-primary) r g b / .9);--drop-in-item-outline-offset: -3px;display:flex;flex-direction:column;max-height:100%}:host [yuvListItem].drop-in{outline:var(--drop-in-item-outline);outline-offset:var(--drop-in-item-outline-offset);overflow:hidden}:host yuv-list{flex:1;overflow-y:auto}:host mat-paginator{flex:0 0 auto;font:inherit;border-block-start:1px solid var(--ymt-outline-variant);background-color:var(--paging-background)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i2.ListComponent, selector: "yuv-list", inputs: ["preventChangeUntil", "multiselect", "selfHandleSelection", "selfHandleClick", "autoSelect", "disableSelection"], outputs: ["itemSelect", "itemFocus"] }, { kind: "directive", type: i2.ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "directive", type: ClickDoubleDirective, selector: "[click.single],[click.double]", inputs: ["debounceTime"], outputs: ["click.double", "click.single"] }, { kind: "directive", type: DragSelectDirective, selector: "[yuvDragSelect]", inputs: ["yuvDragSelect"], outputs: ["dragSelectChange", "dragSelect"] }, { kind: "directive", type: DragSelectItemDirective, selector: "[yuvDragSelectItem]" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }] }); }
657
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: QueryListComponent, isStandalone: true, selector: "yuv-query-list", inputs: { query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, idProperty: { classPropertyName: "idProperty", publicName: "idProperty", isSignal: true, isRequired: false, transformFunction: null }, transformer: { classPropertyName: "transformer", publicName: "transformer", isSignal: true, isRequired: false, transformFunction: null }, preventChangeUntil: { classPropertyName: "preventChangeUntil", publicName: "preventChangeUntil", isSignal: true, isRequired: false, transformFunction: null }, autoSelect: { classPropertyName: "autoSelect", publicName: "autoSelect", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, enableDragSelect: { classPropertyName: "enableDragSelect", publicName: "enableDragSelect", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, selfHandleSelection: { classPropertyName: "selfHandleSelection", publicName: "selfHandleSelection", isSignal: true, isRequired: false, transformFunction: null }, includePermissions: { classPropertyName: "includePermissions", publicName: "includePermissions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect", dragSelectChange: "dragSelectChange", itemDoubleClick: "itemDoubleClick", queryResult: "queryResult" }, host: { properties: { "class.pagination": "!!pagination()" } }, queries: [{ propertyName: "itemTemplate", first: true, predicate: ["yuvQueryListItem"], descendants: true, isSignal: true }, { propertyName: "emptyTemplate", first: true, predicate: ["yuvQueryListEmpty"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }], ngImport: i0, template: "<yuv-list\n #list\n [multiselect]=\"multiselect()\"\n [autoSelect]=\"autoSelect()\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [selfHandleClick]=\"true\"\n [selfHandleSelection]=\"selfHandleSelection()\"\n [yuvDragSelect]=\"{ disabled: !enableDragSelect() || !multiselect() || isTouchDevice }\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n (dragSelect)=\"onDragSelect($event)\"\n (itemSelect)=\"itemSelect.emit($event)\"\n>\n @for (resultItem of resultItems(); track trackItem(resultItem, $index)) {\n <div\n yuvListItem\n yuvDragSelectItem\n [class.drop-in]=\"$index < dropInSize()\"\n (click.single)=\"onItemClick($index, $event)\"\n (click.double)=\"onItemDoubleClick($index, $event)\"\n >\n <ng-container *ngTemplateOutlet=\"itemTemplate() || null; context: { $implicit: resultItem, index: $index }\" />\n </div>\n } @empty {\n <ng-container *ngTemplateOutlet=\"emptyTemplate() || null\" />\n }\n</yuv-list>\n@if (pagination(); as pag) {\n <mat-paginator\n class=\"paginator\"\n [length]=\"pag.total\"\n [pageSize]=\"pageSize()\"\n (page)=\"changePage($event)\"\n hidePageSize\n />\n}\n", styles: [":host{--paging-background: var(--ymt-surface);--drop-in-item-outline: 2px dashed rgb(from var(--ymt-primary) r g b / .9);--drop-in-item-outline-offset: -3px;display:flex;flex-direction:column;max-height:100%}:host [yuvListItem].drop-in{outline:var(--drop-in-item-outline);outline-offset:var(--drop-in-item-outline-offset);overflow:hidden}:host yuv-list{flex:1;overflow-y:auto}:host mat-paginator{flex:0 0 auto;font:inherit;border-block-start:1px solid var(--ymt-outline-variant);background-color:var(--paging-background)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i2.ListComponent, selector: "yuv-list", inputs: ["preventChangeUntil", "multiselect", "selfHandleSelection", "selfHandleClick", "autoSelect", "disableSelection"], outputs: ["itemSelect", "itemFocus"] }, { kind: "directive", type: i2.ListItemDirective, selector: "[yuvListItem]", inputs: ["disabled", "active", "selected"] }, { kind: "directive", type: ClickDoubleDirective, selector: "[click.single],[click.double]", inputs: ["debounceTime"], outputs: ["click.double", "click.single"] }, { kind: "directive", type: DragSelectDirective, selector: "[yuvDragSelect]", inputs: ["yuvDragSelect"], outputs: ["dragSelectChange", "dragSelect"] }, { kind: "directive", type: DragSelectItemDirective, selector: "[yuvDragSelectItem]" }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
327
658
  }
328
659
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: QueryListComponent, decorators: [{
329
660
  type: Component,
330
- args: [{ selector: 'yuv-query-list', imports: [CommonModule, YuvListModule, ClickDoubleDirective, DragSelectDirective, DragSelectItemDirective, MatPaginatorModule], template: "<yuv-list\n #list\n [multiselect]=\"multiselect()\"\n [autoSelect]=\"autoSelect()\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [selfHandleClick]=\"true\"\n [selfHandleSelection]=\"selfHandleSelection()\"\n [yuvDragSelect]=\"{ disabled: !enableDragSelect() || !multiselect() || isTouchDevice }\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n (dragSelect)=\"onDragSelect($event)\"\n (itemSelect)=\"itemSelect.emit($event)\"\n>\n @for (i of resultItems(); track idProperty() || $index) {\n <div yuvListItem yuvDragSelectItem [class.drop-in]=\"$index < dropInSize()\"\n (click.single)=\"onItemClick($index, $event)\"\n (click.double)=\"onItemDoubleClick($index, $event)\" >\n <ng-container *ngTemplateOutlet=\"itemTemplate() || null; context: { $implicit: i, index: $index }\"></ng-container>\n </div>\n } @empty {\n <ng-container *ngTemplateOutlet=\"emptyTemplate() || null\"></ng-container>\n }\n</yuv-list>\n@if (pagination) {\n <mat-paginator class=\"paginator\" [length]=\"pagination.total\" [pageSize]=\"pageSize()\" (page)=\"changePage($event)\" hidePageSize> </mat-paginator>\n}\n", styles: [":host{--paging-background: var(--ymt-surface);--drop-in-item-outline: 2px dashed rgb(from var(--ymt-primary) r g b / .9);--drop-in-item-outline-offset: -3px;display:flex;flex-direction:column;max-height:100%}:host [yuvListItem].drop-in{outline:var(--drop-in-item-outline);outline-offset:var(--drop-in-item-outline-offset);overflow:hidden}:host yuv-list{flex:1;overflow-y:auto}:host mat-paginator{flex:0 0 auto;font:inherit;border-block-start:1px solid var(--ymt-outline-variant);background-color:var(--paging-background)}\n"] }]
331
- }], propDecorators: { pagination: [{
332
- type: HostBinding,
333
- args: ['class.pagination']
334
- }] } });
661
+ args: [{ selector: 'yuv-query-list', imports: [
662
+ CommonModule,
663
+ YuvListModule,
664
+ ClickDoubleDirective,
665
+ DragSelectDirective,
666
+ DragSelectItemDirective,
667
+ MatPaginatorModule
668
+ ], host: { '[class.pagination]': '!!pagination()' }, changeDetection: ChangeDetectionStrategy.OnPush, template: "<yuv-list\n #list\n [multiselect]=\"multiselect()\"\n [autoSelect]=\"autoSelect()\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [selfHandleClick]=\"true\"\n [selfHandleSelection]=\"selfHandleSelection()\"\n [yuvDragSelect]=\"{ disabled: !enableDragSelect() || !multiselect() || isTouchDevice }\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n (dragSelect)=\"onDragSelect($event)\"\n (itemSelect)=\"itemSelect.emit($event)\"\n>\n @for (resultItem of resultItems(); track trackItem(resultItem, $index)) {\n <div\n yuvListItem\n yuvDragSelectItem\n [class.drop-in]=\"$index < dropInSize()\"\n (click.single)=\"onItemClick($index, $event)\"\n (click.double)=\"onItemDoubleClick($index, $event)\"\n >\n <ng-container *ngTemplateOutlet=\"itemTemplate() || null; context: { $implicit: resultItem, index: $index }\" />\n </div>\n } @empty {\n <ng-container *ngTemplateOutlet=\"emptyTemplate() || null\" />\n }\n</yuv-list>\n@if (pagination(); as pag) {\n <mat-paginator\n class=\"paginator\"\n [length]=\"pag.total\"\n [pageSize]=\"pageSize()\"\n (page)=\"changePage($event)\"\n hidePageSize\n />\n}\n", styles: [":host{--paging-background: var(--ymt-surface);--drop-in-item-outline: 2px dashed rgb(from var(--ymt-primary) r g b / .9);--drop-in-item-outline-offset: -3px;display:flex;flex-direction:column;max-height:100%}:host [yuvListItem].drop-in{outline:var(--drop-in-item-outline);outline-offset:var(--drop-in-item-outline-offset);overflow:hidden}:host yuv-list{flex:1;overflow-y:auto}:host mat-paginator{flex:0 0 auto;font:inherit;border-block-start:1px solid var(--ymt-outline-variant);background-color:var(--paging-background)}\n"] }]
669
+ }], ctorParameters: () => [] });
335
670
 
336
671
  const cmp = [QueryListComponent];
337
672
  class YuvQueryListModule {