ng-virtual-list 19.7.13 → 19.7.15

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.
@@ -161,6 +161,9 @@ class NgVirtualListService {
161
161
  set methodOfSelecting(v) {
162
162
  this._$methodOfSelecting.next(v);
163
163
  }
164
+ _$focusedId = new BehaviorSubject(null);
165
+ $focusedId = this._$focusedId.asObservable();
166
+ get focusedId() { return this._$focusedId.getValue(); }
164
167
  selectByClick = DEFAULT_SELECT_BY_CLICK;
165
168
  collapseByClick = DEFAULT_COLLAPSE_BY_CLICK;
166
169
  _trackBox;
@@ -311,6 +314,9 @@ class NgVirtualListService {
311
314
  this.itemToFocus?.(element, pos);
312
315
  }
313
316
  }
317
+ areaFocus(id) {
318
+ this._$focusedId.next(id);
319
+ }
314
320
  initialize(trackBox) {
315
321
  this._trackBox = trackBox;
316
322
  }
@@ -328,6 +334,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
328
334
  }]
329
335
  }], ctorParameters: () => [] });
330
336
 
337
+ const isUndefinable = (value) => {
338
+ return value === undefined;
339
+ };
340
+ const isNullable = (value) => {
341
+ return value === null;
342
+ };
343
+ /**
344
+ * Int validator
345
+ * @param value Int
346
+ */
347
+ const validateInt = (value, undefinable = false) => {
348
+ return (undefinable && isUndefinable(value)) || !Number.isNaN(Number.parseInt(`${value}`));
349
+ };
350
+ /**
351
+ * Float validator
352
+ * @param value Float
353
+ */
354
+ const validateFloat = (value, undefinable = false) => {
355
+ return (undefinable && isUndefinable(value)) || !Number.isNaN(Number.parseFloat(`${value}`));
356
+ };
357
+ /**
358
+ * String validator
359
+ * @param value String
360
+ */
361
+ const validateString = (value, undefinable = false, nullable = false) => {
362
+ return (undefinable && isUndefinable(value)) || (nullable && isNullable(value)) || typeof value === 'string';
363
+ };
364
+ /**
365
+ * Boolean validator
366
+ * @param value Boolean
367
+ */
368
+ const validateBoolean = (value, undefinable = false) => {
369
+ return (undefinable && isUndefinable(value)) || typeof value === 'boolean';
370
+ };
371
+ /**
372
+ * Array validator
373
+ * @param value Array
374
+ */
375
+ const validateArray = (value, undefinable = false, nullable = false) => {
376
+ return (undefinable && isUndefinable(value)) || (nullable && isNullable(value)) || Array.isArray(value);
377
+ };
378
+ /**
379
+ * Object validator
380
+ * @param value Object
381
+ */
382
+ const validateObject = (value, undefinable = false, nullable = false) => {
383
+ return (undefinable && isUndefinable(value)) || (nullable && isNullable(value)) || typeof value === 'object';
384
+ };
385
+ /**
386
+ * Function validator
387
+ * @param value Function
388
+ */
389
+ const validateFunction = (value, undefinable = false, nullable = false) => {
390
+ return (undefinable && isUndefinable(value)) || (nullable && isNullable(value)) || typeof value === 'function';
391
+ };
392
+
331
393
  const ATTR_AREA_SELECTED = 'area-selected', TABINDEX = 'ng-vl-index', KEY_SPACE = " ", KEY_ARR_LEFT = "ArrowLeft", KEY_ARR_UP = "ArrowUp", KEY_ARR_RIGHT = "ArrowRight", KEY_ARR_DOWN = "ArrowDown", EVENT_FOCUS_IN = 'focusin', EVENT_FOCUS_OUT = 'focusout', EVENT_KEY_DOWN = 'keydown';
332
394
  const getElementByIndex = (index) => {
333
395
  return `[${TABINDEX}="${index}"]`;
@@ -392,6 +454,11 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
392
454
  * @param selected - If the value is undefined, then the toggle method is executed, if false or true, then the selection/deselection is performed.
393
455
  */
394
456
  (selected = undefined) => {
457
+ const valid = validateBoolean(selected, true);
458
+ if (!valid) {
459
+ console.error('The "selected" parameter must be of type `boolean` or `undefined`.');
460
+ return;
461
+ }
395
462
  this._service.select(data, selected);
396
463
  };
397
464
  _collapseHandler = (data) =>
@@ -400,12 +467,21 @@ class NgVirtualListItemComponent extends BaseVirtualListItemComponent {
400
467
  * @param collapsed - If the value is undefined, then the toggle method is executed, if false or true, then the collapse/expand is performed.
401
468
  */
402
469
  (collapsed = undefined) => {
470
+ const valid = validateBoolean(collapsed, true);
471
+ if (!valid) {
472
+ console.error('The "collapsed" parameter must be of type `boolean` or `undefined`.');
473
+ return;
474
+ }
403
475
  this._service.collapse(data, collapsed);
404
476
  };
405
477
  constructor() {
406
478
  super();
407
479
  this._id = this._service.generateComponentId();
408
- const $data = toObservable(this.data);
480
+ this._elementRef.nativeElement.setAttribute('id', String(this._id));
481
+ const $data = toObservable(this.data), $focus = toObservable(this.focus);
482
+ $focus.pipe(takeUntilDestroyed(), tap(v => {
483
+ this._service.areaFocus(v ? this._id : this._service.focusedId === this._id ? null : this._service.focusedId);
484
+ })).subscribe();
409
485
  fromEvent(this.element, EVENT_FOCUS_IN).pipe(takeUntilDestroyed(), tap(e => {
410
486
  this.focus.set(true);
411
487
  this.updateConfig(this._data);
@@ -1977,9 +2053,52 @@ const isMethodForSelecting = (src, expected) => {
1977
2053
  return MULTI_SELECT_ALIASES.includes(src);
1978
2054
  };
1979
2055
 
2056
+ const objectAsReadonly = (source) => {
2057
+ if (!source) {
2058
+ return source;
2059
+ }
2060
+ const result = {};
2061
+ for (const prop in source) {
2062
+ const value = source[prop];
2063
+ Object.defineProperty(result, prop, {
2064
+ value,
2065
+ writable: false,
2066
+ enumerable: true,
2067
+ });
2068
+ }
2069
+ return result;
2070
+ };
2071
+ const copyValueAsReadonly = (source) => {
2072
+ if (!source) {
2073
+ return source;
2074
+ }
2075
+ if (Array.isArray(source)) {
2076
+ return Object.freeze([...source]);
2077
+ }
2078
+ if (typeof source === 'object') {
2079
+ return objectAsReadonly(source);
2080
+ }
2081
+ return source;
2082
+ };
2083
+
1980
2084
  const ROLE_LIST = 'list', ROLE_LIST_BOX = 'listbox';
1981
2085
  const validateScrollIteration = (value) => {
1982
2086
  return Number.isNaN(value) || (value < 0) ? 0 : value > MAX_SCROLL_TO_ITERATIONS ? MAX_SCROLL_TO_ITERATIONS : value;
2087
+ }, validateId = (id) => {
2088
+ const valid = validateString(id) || validateFloat(id);
2089
+ if (!valid) {
2090
+ throw Error('The "id" parameter must be of type `Id`.');
2091
+ }
2092
+ }, validateScrollBehavior = (behavior) => {
2093
+ const valid = validateString(behavior) && (behavior === 'auto' || behavior === 'instant' || behavior === 'smooth');
2094
+ if (!valid) {
2095
+ throw Error('The "behavior" parameter must have the value `auto`, `instant` or `smooth`.');
2096
+ }
2097
+ }, validateIteration = (iteration) => {
2098
+ const valid = validateInt(iteration, true);
2099
+ if (!valid) {
2100
+ throw Error('The "iteration" parameter must be of type `number`.');
2101
+ }
1983
2102
  };
1984
2103
  /**
1985
2104
  * Virtual list component.
@@ -2028,7 +2147,25 @@ class NgVirtualListComponent {
2028
2147
  onCollapse = output();
2029
2148
  _itemsOptions = {
2030
2149
  transform: (v) => {
2031
- // etc
2150
+ let valid = validateArray(v, true);
2151
+ if (valid) {
2152
+ if (v) {
2153
+ for (let i = 0, l = v.length; i < l; i++) {
2154
+ const item = v[i];
2155
+ valid = validateObject(item, true);
2156
+ if (valid) {
2157
+ if (item && !(validateFloat(item.id, true) || validateString(item.id, true))) {
2158
+ valid = false;
2159
+ break;
2160
+ }
2161
+ }
2162
+ }
2163
+ }
2164
+ }
2165
+ if (!valid) {
2166
+ console.error('The "items" parameter must be of type `IVirtualListCollection` or `undefined`.');
2167
+ return [];
2168
+ }
2032
2169
  return v;
2033
2170
  },
2034
2171
  };
@@ -2038,40 +2175,159 @@ class NgVirtualListComponent {
2038
2175
  items = input.required({
2039
2176
  ...this._itemsOptions,
2040
2177
  });
2178
+ _selectedIdsOptions = {
2179
+ transform: (v) => {
2180
+ let valid = validateArray(v, true) || validateString(v, true) || validateFloat(v, true);
2181
+ if (valid) {
2182
+ if (v && Array.isArray(v)) {
2183
+ for (let i = 0, l = v.length; i < l; i++) {
2184
+ const item = v[i];
2185
+ valid = validateString(item) || validateFloat(item);
2186
+ if (!valid) {
2187
+ break;
2188
+ }
2189
+ }
2190
+ }
2191
+ }
2192
+ if (!valid) {
2193
+ console.error('The "selectedIds" parameter must be of type `Array<Id> | Id` or `undefined`.');
2194
+ return this._isMultiSelecting ? [] : undefined;
2195
+ }
2196
+ return v;
2197
+ },
2198
+ };
2041
2199
  /**
2042
2200
  * Sets the selected items.
2043
2201
  */
2044
- selectedIds = input(undefined);
2202
+ selectedIds = input(undefined, { ...this._selectedIdsOptions });
2203
+ _collapsedIdsOptions = {
2204
+ transform: (v) => {
2205
+ let valid = validateArray(v, true) || validateString(v, true) || validateFloat(v, true);
2206
+ if (valid) {
2207
+ if (v && Array.isArray(v)) {
2208
+ for (let i = 0, l = v.length; i < l; i++) {
2209
+ const item = v[i];
2210
+ valid = validateString(item) || validateFloat(item);
2211
+ if (!valid) {
2212
+ break;
2213
+ }
2214
+ }
2215
+ }
2216
+ }
2217
+ if (!valid) {
2218
+ console.error('The "collapsedIds" parameter must be of type `Array<Id> | Id` or `undefined`.');
2219
+ return [];
2220
+ }
2221
+ return v;
2222
+ },
2223
+ };
2045
2224
  /**
2046
2225
  * Sets the collapsed items.
2047
2226
  */
2048
- collapsedIds = input([]);
2227
+ collapsedIds = input([], { ...this._collapsedIdsOptions });
2228
+ _selectByClickOptions = {
2229
+ transform: (v) => {
2230
+ const valid = validateBoolean(v);
2231
+ if (!valid) {
2232
+ console.error('The "selectByClick" parameter must be of type `boolean`.');
2233
+ return DEFAULT_SELECT_BY_CLICK;
2234
+ }
2235
+ return v;
2236
+ },
2237
+ };
2049
2238
  /**
2050
2239
  * If `false`, the element is selected using the config.select method passed to the template;
2051
2240
  * if `true`, the element is selected by clicking on it. The default value is `true`.
2052
2241
  */
2053
- selectByClick = input(DEFAULT_SELECT_BY_CLICK);
2242
+ selectByClick = input(DEFAULT_SELECT_BY_CLICK, { ...this._selectByClickOptions });
2243
+ _collapseByClickOptions = {
2244
+ transform: (v) => {
2245
+ const valid = validateBoolean(v);
2246
+ if (!valid) {
2247
+ console.error('The "collapseByClick" parameter must be of type `boolean`.');
2248
+ return DEFAULT_COLLAPSE_BY_CLICK;
2249
+ }
2250
+ return v;
2251
+ },
2252
+ };
2054
2253
  /**
2055
2254
  * If `false`, the element is collapsed using the config.collapse method passed to the template;
2056
2255
  * if `true`, the element is collapsed by clicking on it. The default value is `true`.
2057
2256
  */
2058
- collapseByClick = input(DEFAULT_COLLAPSE_BY_CLICK);
2257
+ collapseByClick = input(DEFAULT_COLLAPSE_BY_CLICK, { ...this._collapseByClickOptions });
2258
+ _snapOptions = {
2259
+ transform: (v) => {
2260
+ const valid = validateBoolean(v);
2261
+ if (!valid) {
2262
+ console.error('The "snap" parameter must be of type `boolean`.');
2263
+ return DEFAULT_SNAP;
2264
+ }
2265
+ return v;
2266
+ },
2267
+ };
2059
2268
  /**
2060
2269
  * Determines whether elements will snap. Default value is "true".
2061
2270
  */
2062
- snap = input(DEFAULT_SNAP);
2271
+ snap = input(DEFAULT_SNAP, { ...this._snapOptions });
2272
+ _enabledBufferOptimizationOptions = {
2273
+ transform: (v) => {
2274
+ const valid = validateBoolean(v);
2275
+ if (!valid) {
2276
+ console.error('The "enabledBufferOptimization" parameter must be of type `boolean`.');
2277
+ return DEFAULT_ENABLED_BUFFER_OPTIMIZATION;
2278
+ }
2279
+ return v;
2280
+ },
2281
+ };
2063
2282
  /**
2064
2283
  * Experimental!
2065
2284
  * Enables buffer optimization.
2066
2285
  * Can only be used if items in the collection are not added or updated. Otherwise, artifacts in the form of twitching of the scroll area are possible.
2067
2286
  * Works only if the property dynamic = true
2068
2287
  */
2069
- enabledBufferOptimization = input(DEFAULT_ENABLED_BUFFER_OPTIMIZATION);
2288
+ enabledBufferOptimization = input(DEFAULT_ENABLED_BUFFER_OPTIMIZATION, { ...this._enabledBufferOptimizationOptions });
2289
+ _itemRendererOptions = {
2290
+ transform: (v) => {
2291
+ const valid = validateObject(v);
2292
+ if (v) {
2293
+ Object.hasOwn(v, 'elementRef');
2294
+ Object.hasOwn(v, 'createEmbeddedView');
2295
+ }
2296
+ if (!valid) {
2297
+ throw Error('The "itemRenderer" parameter must be of type `TemplateRef`.');
2298
+ }
2299
+ return v;
2300
+ },
2301
+ };
2070
2302
  /**
2071
2303
  * Rendering element template.
2072
2304
  */
2073
- itemRenderer = input.required();
2305
+ itemRenderer = input.required({ ...this._itemRendererOptions });
2074
2306
  _itemRenderer = signal(undefined);
2307
+ _itemConfigMapOptions = {
2308
+ transform: (v) => {
2309
+ let valid = validateObject(v);
2310
+ if (valid) {
2311
+ if (v) {
2312
+ for (let id in v) {
2313
+ const item = v[id];
2314
+ if (!item ||
2315
+ !validateBoolean(item.collapsable, true) ||
2316
+ !validateBoolean(item.selectable, true) ||
2317
+ !(item.sticky === undefined || item.sticky === 0 || item.sticky === 1 || item.sticky === 2)) {
2318
+ valid = false;
2319
+ break;
2320
+ }
2321
+ }
2322
+ }
2323
+ }
2324
+ if (!valid) {
2325
+ console.error('The "itemConfigMap" parameter must be of type `IVirtualListItemConfigMap`.');
2326
+ return {};
2327
+ }
2328
+ return v;
2329
+ },
2330
+ };
2075
2331
  /**
2076
2332
  * Sets `sticky` position, `collapsable` and `selectable` for the list item element. If `sticky` position is greater than `0`, then `sticky` position is applied.
2077
2333
  * If the `sticky` value is greater than `0`, then the `sticky` position mode is enabled for the element. `1` - position start, `2` - position end. Default value is `0`.
@@ -2081,14 +2337,15 @@ class NgVirtualListComponent {
2081
2337
  * @author Evgenii Grebennikov
2082
2338
  * @email djonnyx@gmail.com
2083
2339
  */
2084
- itemConfigMap = input({});
2340
+ itemConfigMap = input({}, { ...this._itemConfigMapOptions });
2085
2341
  _itemSizeOptions = {
2086
2342
  transform: (v) => {
2087
- if (v === undefined) {
2343
+ const valid = validateFloat(v);
2344
+ if (!valid) {
2345
+ console.error('The "itemSize" parameter must be of type `number` or `undefined`.');
2088
2346
  return DEFAULT_ITEM_SIZE;
2089
2347
  }
2090
- const val = Number(v);
2091
- return Number.isNaN(val) || val <= 0 ? DEFAULT_ITEM_SIZE : val;
2348
+ return v;
2092
2349
  },
2093
2350
  };
2094
2351
  /**
@@ -2096,26 +2353,62 @@ class NgVirtualListComponent {
2096
2353
  * Ignored if the dynamicSize property is true.
2097
2354
  */
2098
2355
  itemSize = input(DEFAULT_ITEM_SIZE, { ...this._itemSizeOptions });
2356
+ _dynamicSizeOptions = {
2357
+ transform: (v) => {
2358
+ const valid = validateBoolean(v);
2359
+ if (!valid) {
2360
+ console.error('The "dynamicSize" parameter must be of type `boolean`.');
2361
+ return DEFAULT_DYNAMIC_SIZE;
2362
+ }
2363
+ return v;
2364
+ },
2365
+ };
2099
2366
  /**
2100
2367
  * If true then the items in the list can have different sizes and the itemSize property is ignored.
2101
2368
  * If false then the items in the list have a fixed size specified by the itemSize property. The default value is false.
2102
2369
  */
2103
- dynamicSize = input(DEFAULT_DYNAMIC_SIZE);
2370
+ dynamicSize = input(DEFAULT_DYNAMIC_SIZE, { ...this._dynamicSizeOptions });
2371
+ _directionOptions = {
2372
+ transform: (v) => {
2373
+ const valid = validateString(v) && (v === 'horizontal' || v === 'vertical');
2374
+ if (!valid) {
2375
+ console.error('The "direction" parameter must have the value `horizontal` or `vertical`.');
2376
+ return DEFAULT_DIRECTION;
2377
+ }
2378
+ return v;
2379
+ },
2380
+ };
2104
2381
  /**
2105
2382
  * Determines the direction in which elements are placed. Default value is "vertical".
2106
2383
  */
2107
- direction = input(DEFAULT_DIRECTION);
2384
+ direction = input(DEFAULT_DIRECTION, { ...this._directionOptions });
2385
+ _bufferSizeOptions = {
2386
+ transform: (v) => {
2387
+ const valid = validateInt(v);
2388
+ if (!valid) {
2389
+ console.error('The "bufferSize" parameter must be of type `number`.');
2390
+ return DEFAULT_BUFFER_SIZE;
2391
+ }
2392
+ return v;
2393
+ },
2394
+ };
2108
2395
  /**
2109
2396
  * Number of elements outside the scope of visibility. Default value is 2.
2110
2397
  */
2111
- bufferSize = input(DEFAULT_BUFFER_SIZE);
2398
+ bufferSize = input(DEFAULT_BUFFER_SIZE, { ...this._bufferSizeOptions });
2112
2399
  _maxBufferSizeTransform = {
2113
2400
  transform: (v) => {
2401
+ let val = v;
2402
+ const valid = validateInt(v, true);
2403
+ if (!valid) {
2404
+ console.error('The "maxBufferSize" parameter must be of type `number`.');
2405
+ val = DEFAULT_MAX_BUFFER_SIZE;
2406
+ }
2114
2407
  const bufferSize = this.bufferSize();
2115
- if (v === undefined || v <= bufferSize) {
2408
+ if (val === undefined || val <= bufferSize) {
2116
2409
  return bufferSize;
2117
2410
  }
2118
- return v;
2411
+ return val;
2119
2412
  }
2120
2413
  };
2121
2414
  /**
@@ -2124,19 +2417,53 @@ class NgVirtualListComponent {
2124
2417
  * The greater the scroll size, the more elements are allocated for rendering.
2125
2418
  */
2126
2419
  maxBufferSize = input(DEFAULT_MAX_BUFFER_SIZE, { ...this._maxBufferSizeTransform });
2420
+ _snappingMethodOptions = {
2421
+ transform: (v) => {
2422
+ const valid = validateString(v) && (v === 'normal' || v === 'advanced');
2423
+ if (!valid) {
2424
+ console.error('The "snappingMethod" parameter must have the value `normal` or `advanced`.');
2425
+ return DEFAULT_SNAPPING_METHOD;
2426
+ }
2427
+ return v;
2428
+ },
2429
+ };
2127
2430
  /**
2128
2431
  * Snapping method.
2129
2432
  * 'default' - Normal group rendering.
2130
2433
  * 'advanced' - The group is rendered on a transparent background. List items below the group are not rendered.
2131
2434
  */
2132
- snappingMethod = input(DEFAULT_SNAPPING_METHOD);
2435
+ snappingMethod = input(DEFAULT_SNAPPING_METHOD, { ...this._snappingMethodOptions });
2436
+ _methodForSelectingOptions = {
2437
+ transform: (v) => {
2438
+ const valid = validateString(v) && (v === 'none' || v === 'select' || 'multi-select');
2439
+ if (!valid) {
2440
+ console.error('The "methodForSelecting" parameter must have the value `none`, `select` or `multi-select`.');
2441
+ return DEFAULT_SELECT_METHOD;
2442
+ }
2443
+ return v;
2444
+ },
2445
+ };
2133
2446
  /**
2134
2447
  * Method for selecting list items. Default value is 'none'.
2135
2448
  * 'select' - List items are selected one by one.
2136
2449
  * 'multi-select' - Multiple selection of list items.
2137
2450
  * 'none' - List items are not selectable.
2138
2451
  */
2139
- methodForSelecting = input(DEFAULT_SELECT_METHOD);
2452
+ methodForSelecting = input(DEFAULT_SELECT_METHOD, { ...this._methodForSelectingOptions });
2453
+ _trackByOptions = {
2454
+ transform: (v) => {
2455
+ const valid = validateString(v);
2456
+ if (!valid) {
2457
+ console.error('The "trackBy" parameter must be of type `string`.');
2458
+ return TRACK_BY_PROPERTY_NAME;
2459
+ }
2460
+ return v;
2461
+ },
2462
+ };
2463
+ /**
2464
+ * The name of the property by which tracking is performed
2465
+ */
2466
+ trackBy = input(TRACK_BY_PROPERTY_NAME, { ...this._trackByOptions });
2140
2467
  _isNotSelecting = this.getIsNotSelecting();
2141
2468
  get isNotSelecting() { return this._isNotSelecting; }
2142
2469
  _isSingleSelecting = this.getIsSingleSelecting();
@@ -2149,6 +2476,7 @@ class NgVirtualListComponent {
2149
2476
  get orientation() {
2150
2477
  return this._isVertical ? Directions.VERTICAL : Directions.HORIZONTAL;
2151
2478
  }
2479
+ focusedElement = signal(undefined);
2152
2480
  _actualItems = signal([]);
2153
2481
  _collapsedItemIds = signal([]);
2154
2482
  _displayComponents = [];
@@ -2222,10 +2550,6 @@ class NgVirtualListComponent {
2222
2550
  _elementRef = inject((ElementRef));
2223
2551
  _initialized;
2224
2552
  $initialized;
2225
- /**
2226
- * The name of the property by which tracking is performed
2227
- */
2228
- trackBy = input(TRACK_BY_PROPERTY_NAME);
2229
2553
  /**
2230
2554
  * Base class of the element component
2231
2555
  */
@@ -2251,6 +2575,9 @@ class NgVirtualListComponent {
2251
2575
  this._initialized = signal(false);
2252
2576
  this.$initialized = toObservable(this._initialized);
2253
2577
  this._trackBox.displayComponents = this._displayComponents;
2578
+ this._service.$focusedId.pipe(takeUntilDestroyed(), tap(v => {
2579
+ this.focusedElement.set(v ?? undefined);
2580
+ })).subscribe();
2254
2581
  toObservable(this._list).pipe(takeUntilDestroyed(), filter(v => !!v), tap(v => {
2255
2582
  this._service.listElement = v.nativeElement;
2256
2583
  })).subscribe();
@@ -2264,7 +2591,7 @@ class NgVirtualListComponent {
2264
2591
  $trackBy.pipe(takeUntilDestroyed(), tap(v => {
2265
2592
  this._trackBox.trackingPropertyName = v;
2266
2593
  })).subscribe();
2267
- const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $bufferSize = toObservable(this.bufferSize).pipe(map(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $maxBufferSize = toObservable(this.maxBufferSize).pipe(map(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $itemConfigMap = toObservable(this.itemConfigMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $snappingMethod = toObservable(this.snappingMethod).pipe(map(v => this.getIsSnappingMethodAdvanced(v || DEFAULT_SNAPPING_METHOD))), $methodForSelecting = toObservable(this.methodForSelecting), $selectedIds = toObservable(this.selectedIds), $collapsedIds = toObservable(this.collapsedIds), $collapsedItemIds = toObservable(this._collapsedItemIds), $actualItems = toObservable(this._actualItems), $cacheVersion = toObservable(this._cacheVersion);
2594
+ const $bounds = toObservable(this._bounds).pipe(filter(b => !!b)), $items = toObservable(this.items).pipe(map(i => !i ? [] : i)), $scrollSize = toObservable(this._scrollSize), $itemSize = toObservable(this.itemSize).pipe(map(v => v <= 0 ? DEFAULT_ITEM_SIZE : v)), $bufferSize = toObservable(this.bufferSize).pipe(map(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $maxBufferSize = toObservable(this.maxBufferSize).pipe(map(v => v < 0 ? DEFAULT_BUFFER_SIZE : v)), $itemConfigMap = toObservable(this.itemConfigMap).pipe(map(v => !v ? {} : v)), $snap = toObservable(this.snap), $isVertical = toObservable(this.direction).pipe(map(v => this.getIsVertical(v || DEFAULT_DIRECTION))), $dynamicSize = toObservable(this.dynamicSize), $enabledBufferOptimization = toObservable(this.enabledBufferOptimization), $snappingMethod = toObservable(this.snappingMethod).pipe(map(v => this.getIsSnappingMethodAdvanced(v || DEFAULT_SNAPPING_METHOD))), $methodForSelecting = toObservable(this.methodForSelecting), $selectedIds = toObservable(this.selectedIds), $collapsedIds = toObservable(this.collapsedIds).pipe(map(v => Array.isArray(v) ? v : [])), $collapsedItemIds = toObservable(this._collapsedItemIds).pipe(map(v => Array.isArray(v) ? v : [])), $actualItems = toObservable(this._actualItems), $cacheVersion = toObservable(this._cacheVersion);
2268
2595
  combineLatest([$items, $itemSize]).pipe(takeUntilDestroyed(), map(([items, itemSize]) => ({ items, itemSize })), tap(({ items, itemSize }) => {
2269
2596
  this._trackBox.resetCollection(items, itemSize);
2270
2597
  })).subscribe();
@@ -2365,17 +2692,17 @@ class NgVirtualListComponent {
2365
2692
  this._itemRenderer.set(v);
2366
2693
  })).subscribe();
2367
2694
  $bounds.pipe(takeUntilDestroyed(), distinctUntilChanged(), tap(value => {
2368
- this.onViewportChange.emit(value);
2695
+ this.onViewportChange.emit(objectAsReadonly(value));
2369
2696
  })).subscribe();
2370
2697
  this._service.$itemClick.pipe(takeUntilDestroyed(), tap(v => {
2371
- this.onItemClick.emit(v);
2698
+ this.onItemClick.emit(objectAsReadonly(v));
2372
2699
  })).subscribe();
2373
2700
  let isSelectedIdsFirstEmit = 0;
2374
2701
  this._service.$selectedIds.pipe(takeUntilDestroyed(), distinctUntilChanged(), tap(v => {
2375
2702
  if (this.isSingleSelecting || (this.isMultiSelecting && isSelectedIdsFirstEmit >= 2)) {
2376
2703
  const curr = this.selectedIds();
2377
2704
  if ((this.isSingleSelecting && JSON.stringify(v) !== JSON.stringify(curr)) || (isSelectedIdsFirstEmit === 2 && JSON.stringify(v) !== JSON.stringify(curr)) || isSelectedIdsFirstEmit > 2) {
2378
- this.onSelect.emit(v);
2705
+ this.onSelect.emit(copyValueAsReadonly(v));
2379
2706
  }
2380
2707
  }
2381
2708
  if (isSelectedIdsFirstEmit < 3) {
@@ -2391,7 +2718,7 @@ class NgVirtualListComponent {
2391
2718
  if (isCollapsedIdsFirstEmit >= 2) {
2392
2719
  const curr = this.collapsedIds();
2393
2720
  if ((isCollapsedIdsFirstEmit === 2 && JSON.stringify(v) !== JSON.stringify(curr)) || isCollapsedIdsFirstEmit > 2) {
2394
- this.onCollapse.emit(v);
2721
+ this.onCollapse.emit(copyValueAsReadonly(v));
2395
2722
  }
2396
2723
  }
2397
2724
  if (isCollapsedIdsFirstEmit < 3) {
@@ -2502,6 +2829,7 @@ class NgVirtualListComponent {
2502
2829
  * Returns the bounds of an element with a given id
2503
2830
  */
2504
2831
  getItemBounds(id) {
2832
+ validateId(id);
2505
2833
  return this._trackBox.getItemBounds(id);
2506
2834
  }
2507
2835
  /**
@@ -2509,6 +2837,9 @@ class NgVirtualListComponent {
2509
2837
  * Behavior accepts the values ​​"auto", "instant" and "smooth".
2510
2838
  */
2511
2839
  scrollTo(id, behavior = BEHAVIOR_AUTO, iteration = 0) {
2840
+ validateId(id);
2841
+ validateScrollBehavior(behavior);
2842
+ validateIteration(iteration);
2512
2843
  this.scrollToExecutor(id, behavior, validateScrollIteration(iteration));
2513
2844
  }
2514
2845
  _scrollToRepeatExecutionTimeout;
@@ -2582,6 +2913,8 @@ class NgVirtualListComponent {
2582
2913
  * Scrolls the scroll area to the desired element with the specified ID.
2583
2914
  */
2584
2915
  scrollToEnd(behavior = BEHAVIOR_INSTANT, iteration = 0) {
2916
+ validateScrollBehavior(behavior);
2917
+ validateIteration(iteration);
2585
2918
  const items = this.items(), latItem = items[items.length > 0 ? items.length - 1 : 0];
2586
2919
  this.scrollTo(latItem.id, behavior, validateScrollIteration(iteration));
2587
2920
  }
@@ -2660,13 +2993,13 @@ class NgVirtualListComponent {
2660
2993
  }
2661
2994
  }
2662
2995
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2663
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, collapsedIds: { classPropertyName: "collapsedIds", publicName: "collapsedIds", isSignal: true, isRequired: false, transformFunction: null }, selectByClick: { classPropertyName: "selectByClick", publicName: "selectByClick", isSignal: true, isRequired: false, transformFunction: null }, collapseByClick: { classPropertyName: "collapseByClick", publicName: "collapseByClick", isSignal: true, isRequired: false, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, itemConfigMap: { classPropertyName: "itemConfigMap", publicName: "itemConfigMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, bufferSize: { classPropertyName: "bufferSize", publicName: "bufferSize", isSignal: true, isRequired: false, transformFunction: null }, maxBufferSize: { classPropertyName: "maxBufferSize", publicName: "maxBufferSize", isSignal: true, isRequired: false, transformFunction: null }, snappingMethod: { classPropertyName: "snappingMethod", publicName: "snappingMethod", isSignal: true, isRequired: false, transformFunction: null }, methodForSelecting: { classPropertyName: "methodForSelecting", publicName: "methodForSelecting", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd", onViewportChange: "onViewportChange", onItemClick: "onItemClick", onSelect: "onSelect", onCollapse: "onCollapse" }, host: { styleAttribute: "position: relative;" }, providers: [NgVirtualListService], viewQueries: [{ propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, isSignal: true }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (snap()) {\r\n <div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n </div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
2996
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgVirtualListComponent, isStandalone: true, selector: "ng-virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectedIds: { classPropertyName: "selectedIds", publicName: "selectedIds", isSignal: true, isRequired: false, transformFunction: null }, collapsedIds: { classPropertyName: "collapsedIds", publicName: "collapsedIds", isSignal: true, isRequired: false, transformFunction: null }, selectByClick: { classPropertyName: "selectByClick", publicName: "selectByClick", isSignal: true, isRequired: false, transformFunction: null }, collapseByClick: { classPropertyName: "collapseByClick", publicName: "collapseByClick", isSignal: true, isRequired: false, transformFunction: null }, snap: { classPropertyName: "snap", publicName: "snap", isSignal: true, isRequired: false, transformFunction: null }, enabledBufferOptimization: { classPropertyName: "enabledBufferOptimization", publicName: "enabledBufferOptimization", isSignal: true, isRequired: false, transformFunction: null }, itemRenderer: { classPropertyName: "itemRenderer", publicName: "itemRenderer", isSignal: true, isRequired: true, transformFunction: null }, itemConfigMap: { classPropertyName: "itemConfigMap", publicName: "itemConfigMap", isSignal: true, isRequired: false, transformFunction: null }, itemSize: { classPropertyName: "itemSize", publicName: "itemSize", isSignal: true, isRequired: false, transformFunction: null }, dynamicSize: { classPropertyName: "dynamicSize", publicName: "dynamicSize", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, bufferSize: { classPropertyName: "bufferSize", publicName: "bufferSize", isSignal: true, isRequired: false, transformFunction: null }, maxBufferSize: { classPropertyName: "maxBufferSize", publicName: "maxBufferSize", isSignal: true, isRequired: false, transformFunction: null }, snappingMethod: { classPropertyName: "snappingMethod", publicName: "snappingMethod", isSignal: true, isRequired: false, transformFunction: null }, methodForSelecting: { classPropertyName: "methodForSelecting", publicName: "methodForSelecting", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onScroll: "onScroll", onScrollEnd: "onScrollEnd", onViewportChange: "onViewportChange", onItemClick: "onItemClick", onSelect: "onSelect", onCollapse: "onCollapse" }, host: { styleAttribute: "position: relative;" }, providers: [NgVirtualListService], viewQueries: [{ propertyName: "_snappedContainer", first: true, predicate: ["snapped"], descendants: true, isSignal: true }, { propertyName: "_container", first: true, predicate: ["container"], descendants: true, isSignal: true }, { propertyName: "_list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "_listContainerRef", first: true, predicate: ["renderersContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "_snapContainerRef", first: true, predicate: ["snapRendererContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (snap()) {\r\n <div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n </div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"focusedElement()\" tabindex=\"0\" #list\r\n part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
2664
2997
  }
2665
2998
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgVirtualListComponent, decorators: [{
2666
2999
  type: Component,
2667
3000
  args: [{ selector: 'ng-virtual-list', imports: [CommonModule], host: {
2668
3001
  'style': 'position: relative;'
2669
- }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, providers: [NgVirtualListService], template: "@if (snap()) {\r\n <div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n </div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" #list part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"] }]
3002
+ }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, providers: [NgVirtualListService], template: "@if (snap()) {\r\n <div #snapped part=\"snapped-item\" class=\"ngvl__list-snapper\">\r\n <ng-container #snapRendererContainer></ng-container>\r\n </div>\r\n}\r\n<div #container part=\"scroller\" class=\"ngvl__scroller\">\r\n <div [attr.aria-orientation]=\"orientation\" [attr.aria-activedescendant]=\"focusedElement()\" tabindex=\"0\" #list\r\n part=\"list\" class=\"ngvl__list\">\r\n <ng-container #renderersContainer></ng-container>\r\n </div>\r\n</div>", styles: [":host{position:relative;display:block;width:400px;overflow:hidden}:host(.horizontal){height:48px}:host(.horizontal) .ngvl__list{display:inline-flex}:host(.horizontal) .ngvl__scroller{overflow:auto hidden}:host(.vertical) .ngvl__scroller{overflow:hidden auto}:host(.vertical){height:320px}.ngvl__scroller{overflow:auto;width:100%;height:100%}.ngvl__list-snapper{pointer-events:none;position:absolute;list-style:none;left:0;top:0;z-index:1}.ngvl__list{position:relative;list-style:none;padding:0;margin:0;width:100%;height:100%}\n"] }]
2670
3003
  }], ctorParameters: () => [], propDecorators: { _listContainerRef: [{
2671
3004
  type: ViewChild,
2672
3005
  args: ['renderersContainer', { read: ViewContainerRef }]