@masterteam/client-components 0.0.85 → 0.0.87

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.
@@ -7,7 +7,7 @@ import { TranslocoService, TranslocoModule, TranslocoPipe } from '@jsverse/trans
7
7
  import { Button } from '@masterteam/components/button';
8
8
  import { RuntimeActionRunner, resolveActionLabel } from '@masterteam/components/runtime-action';
9
9
  import { HttpClient } from '@angular/common/http';
10
- import { switchMap, of, map, Observable, share, take } from 'rxjs';
10
+ import { switchMap, map, of, Observable, share, take } from 'rxjs';
11
11
  import { resolveLocalizedLabel, serializeEntityRawValue, isSectionKey, EntitiesPreview } from '@masterteam/components/entities';
12
12
  import { Table, TableExportService, TableGroupingController, TableValueResolver, TableCaption } from '@masterteam/components/table';
13
13
  import * as i2 from 'primeng/skeleton';
@@ -23,22 +23,65 @@ import * as i3 from 'primeng/popover';
23
23
  import { PopoverModule } from 'primeng/popover';
24
24
  import { DashboardViewer } from '@masterteam/dashboard-builder';
25
25
 
26
- const DEFAULT_INCLUDE_STATE = ['escalation'];
26
+ const CLIENT_LIST_RECORD_STATE_KEY = '__clientListRecordState';
27
+ /** Record state every list request asks for. */
28
+ const CLIENT_LIST_DEFAULT_INCLUDE_STATE = ['escalation'];
29
+ /**
30
+ * `fetch/query` filter key that selects a single record by its own id. Reading
31
+ * one record back after a write filters on this, never on the list's scope
32
+ * filters alone — those match a whole page, not the row that just changed.
33
+ */
34
+ const CLIENT_LIST_RECORD_ID_FILTER_KEY = 'id';
35
+
36
+ const DEFAULT_INCLUDE_STATE = CLIENT_LIST_DEFAULT_INCLUDE_STATE;
27
37
  class ClientListApiService {
28
38
  http = inject(HttpClient);
29
39
  runtimeFetchBaseUrl = 'fetch/query';
30
40
  informativeBaseUrl = 'LevelData';
41
+ /**
42
+ * Fetches a table page. Resolves to the response **and** the `propertyKeys`
43
+ * the request carried, because a write that wants its record projected the
44
+ * same way has to repeat them and cannot recover them from the response.
45
+ */
31
46
  getRows(contextKey, query, filters = []) {
32
- return this.resolveTablePropertyKeys(contextKey, query).pipe(switchMap((propertyKeys) => this.queryRuntime({
47
+ return this.resolveTablePropertyKeys(contextKey, query).pipe(switchMap((resolvedKeys) => {
48
+ const propertyKeys = resolvedKeys?.length ? resolvedKeys : undefined;
49
+ return this.queryRuntime({
50
+ contextKey,
51
+ projection: 'Table',
52
+ surfaceKey: 'table',
53
+ includeState: [...DEFAULT_INCLUDE_STATE],
54
+ propertyKeys,
55
+ filters,
56
+ page: this.toPage(query.skip, query.take),
57
+ pageSize: query.take,
58
+ }).pipe(map((response) => ({ response, propertyKeys })));
59
+ }));
60
+ }
61
+ /**
62
+ * Reads a single record back with the list's own query settings — the path
63
+ * taken when a write reports `recordProjectionStatus: 'Unavailable'`. The
64
+ * write itself already succeeded; this only reads, and must never be served
65
+ * by re-submitting.
66
+ */
67
+ getRecord(contextKey, request, filters) {
68
+ return this.queryRuntime({
33
69
  contextKey,
34
- projection: 'Table',
35
- surfaceKey: 'table',
36
- includeState: [...DEFAULT_INCLUDE_STATE],
37
- propertyKeys: propertyKeys?.length ? propertyKeys : undefined,
70
+ projection: request.projection,
71
+ surfaceKey: request.surfaceKey,
72
+ includeState: request.includeState
73
+ ? [...request.includeState]
74
+ : [...DEFAULT_INCLUDE_STATE],
75
+ propertyKeys: request.propertyKeys?.length
76
+ ? request.propertyKeys
77
+ : undefined,
38
78
  filters,
39
- page: this.toPage(query.skip, query.take),
40
- pageSize: query.take,
41
- })));
79
+ page: 1,
80
+ pageSize: 1,
81
+ ...(request.projection === 'Card'
82
+ ? { display: { areas: ['card'] } }
83
+ : {}),
84
+ });
42
85
  }
43
86
  getCards(contextKey, filters = [], skip, take) {
44
87
  // `page` and `pageSize` must be sent together or not at all (fetch/query
@@ -108,8 +151,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
108
151
  args: [{ providedIn: 'root' }]
109
152
  }] });
110
153
 
111
- const CLIENT_LIST_RECORD_STATE_KEY = '__clientListRecordState';
112
-
113
154
  const UNSORTABLE_ENTITY_VIEW_TYPES = new Set([
114
155
  'Attachment',
115
156
  'Checkbox',
@@ -306,6 +347,146 @@ class ClientListStateService {
306
347
  },
307
348
  });
308
349
  }
350
+ /**
351
+ * Splices a canonical record into the data the list already holds, so a
352
+ * create or update lands without re-fetching the page.
353
+ *
354
+ * Tables replay the whole page through {@link setRowsResult}: for a table
355
+ * the rendered page *is* the raw response, so replaying it keeps the catalog
356
+ * lookup, the column build and the host `transformResult` hook on exactly
357
+ * one code path — a second mapper for single rows would drift from the one
358
+ * that built the rest of the table. Cards cannot replay: with infinite
359
+ * scroll `rawData` holds only the last page while `cards` holds every page,
360
+ * so a single card is built and merged into the accumulated set instead.
361
+ *
362
+ * Returns `false` when there is nothing to splice into — an informative
363
+ * dashboard, or a list that never completed a load. The caller then falls
364
+ * back to a real fetch rather than inventing state.
365
+ */
366
+ upsertRecord(key, record, config) {
367
+ const target = this.itemsByKey()[key];
368
+ if (!target || target.type !== 'form')
369
+ return false;
370
+ const raw = target.rawData;
371
+ if (!raw)
372
+ return false;
373
+ return target.areaType === 'cards'
374
+ ? this.upsertCardRecord(key, target, raw, record)
375
+ : this.upsertRowRecord(key, target, raw, record, config);
376
+ }
377
+ /**
378
+ * Drops a record the backend has deleted, without re-fetching. Returns
379
+ * `false` when the record is not in the loaded set, so the caller can decide
380
+ * whether a reload is warranted.
381
+ */
382
+ removeRecord(key, recordId, config) {
383
+ const target = this.itemsByKey()[key];
384
+ if (!target || target.type !== 'form')
385
+ return false;
386
+ const raw = target.rawData;
387
+ if (!raw)
388
+ return false;
389
+ const records = (raw.records ?? []).filter((item) => item.id !== recordId);
390
+ if (target.areaType === 'cards') {
391
+ const cards = target.cards.filter((card) => card.id !== recordId);
392
+ if (cards.length === target.cards.length)
393
+ return false;
394
+ this.patchItem(key, {
395
+ ...target,
396
+ cards,
397
+ totalCount: Math.max(0, target.totalCount - 1),
398
+ error: null,
399
+ rawData: this.withRecords(raw, records, -1),
400
+ });
401
+ return true;
402
+ }
403
+ if (records.length === (raw.records ?? []).length)
404
+ return false;
405
+ this.setRowsResult(key, this.withRecords(raw, records, -1), config, target.skip, target.take);
406
+ return true;
407
+ }
408
+ upsertRowRecord(key, target, raw, record, config) {
409
+ // No columns means no completed table load to splice into.
410
+ if (!target.columns.length)
411
+ return false;
412
+ const records = [...(raw.records ?? [])];
413
+ const index = records.findIndex((item) => item.id === record.id);
414
+ if (index >= 0) {
415
+ records[index] = this.mergeRecord(records[index], record);
416
+ }
417
+ else {
418
+ // The backend owns the real ordering and does not say where the new
419
+ // record landed, so it goes to the top of the page in view: the user
420
+ // has to see what they just created. The next real load puts it in its
421
+ // true place.
422
+ records.unshift(record);
423
+ // Keep the page the size the paginator promises — the row pushed off
424
+ // the end now lives on the next page.
425
+ if (config.isPaginated &&
426
+ target.take > 0 &&
427
+ records.length > target.take) {
428
+ records.length = target.take;
429
+ }
430
+ }
431
+ this.setRowsResult(key, this.withRecords(raw, records, index >= 0 ? 0 : 1), config, target.skip, target.take);
432
+ return true;
433
+ }
434
+ upsertCardRecord(key, target, raw, record) {
435
+ if (!raw.projectionMeta)
436
+ return false;
437
+ const previous = (raw.records ?? []).find((item) => item.id === record.id);
438
+ const merged = previous ? this.mergeRecord(previous, record) : record;
439
+ const [card] = this.toCards({ ...raw, records: [merged] });
440
+ if (!card)
441
+ return false;
442
+ const index = target.cards.findIndex((item) => item.id === card.id);
443
+ const isInsert = index < 0;
444
+ const cards = isInsert
445
+ ? [card, ...target.cards]
446
+ : target.cards.map((item, position) => position === index ? card : item);
447
+ const records = [...(raw.records ?? [])];
448
+ const recordIndex = records.findIndex((item) => item.id === merged.id);
449
+ if (recordIndex >= 0)
450
+ records[recordIndex] = merged;
451
+ else
452
+ records.unshift(merged);
453
+ this.patchItem(key, {
454
+ ...target,
455
+ cards,
456
+ totalCount: isInsert ? target.totalCount + 1 : target.totalCount,
457
+ error: null,
458
+ rawData: this.withRecords(raw, records, isInsert ? 1 : 0),
459
+ });
460
+ return true;
461
+ }
462
+ /**
463
+ * Overlays the written record on the one being replaced, key by key. A
464
+ * cleared property comes back explicitly (`{ raw: null, value: null }`) and
465
+ * overwrites; only a key the backend left out entirely keeps its previous
466
+ * cell, which beats blanking a value that did not actually change.
467
+ */
468
+ mergeRecord(previous, next) {
469
+ return {
470
+ ...previous,
471
+ ...next,
472
+ values: { ...previous.values, ...next.values },
473
+ };
474
+ }
475
+ withRecords(raw, records, totalCountDelta) {
476
+ return {
477
+ ...raw,
478
+ records,
479
+ pagination: raw.pagination
480
+ ? {
481
+ ...raw.pagination,
482
+ totalCount: Math.max(0, raw.pagination.totalCount + totalCountDelta),
483
+ }
484
+ : raw.pagination,
485
+ };
486
+ }
487
+ patchItem(key, next) {
488
+ this.itemsByKey.set({ ...this.itemsByKey(), [key]: next });
489
+ }
309
490
  /**
310
491
  * Appends `next` to `current`, dropping any card whose id is already
311
492
  * present. Ids are the grid's `@for` track key, so duplicates are a runtime
@@ -378,8 +559,6 @@ class ClientListStateService {
378
559
  existing.areaType !== item.areaType) {
379
560
  return item;
380
561
  }
381
- const keepPaging = existing.config.isPaginated === item.config.isPaginated &&
382
- existing.take === item.take;
383
562
  return {
384
563
  ...item,
385
564
  loading: existing.loading,
@@ -390,7 +569,12 @@ class ClientListStateService {
390
569
  columns: existing.columns,
391
570
  rows: existing.rows,
392
571
  cards: existing.cards,
393
- skip: keepPaging ? existing.skip : item.skip,
572
+ // Paging is settled by the caller, which compared the incoming config
573
+ // against the previous one. Re-deciding it here — with only the two
574
+ // states and no view of what changed in the config — is what used to
575
+ // drop the user's page whenever their rows-per-page differed from the
576
+ // configured default.
577
+ skip: item.skip,
394
578
  take: item.take,
395
579
  expanded: existing.expanded,
396
580
  dashboardData: existing.dashboardData,
@@ -1760,6 +1944,40 @@ function toScalar(value) {
1760
1944
  item['displayName']);
1761
1945
  }
1762
1946
 
1947
+ /**
1948
+ * Whether the offset a list is currently sitting on still describes the same
1949
+ * result set, i.e. this reconfigure changed nothing about what gets fetched.
1950
+ *
1951
+ * Hosts rebuild the `configurations` array from a `computed()`, so *any*
1952
+ * unrelated state change — a permission refresh after a write, a language
1953
+ * switch, a re-rendered header template — hands the list a brand new array
1954
+ * with identical content. That must not move the user off their page, and
1955
+ * must not cost a request.
1956
+ *
1957
+ * The page size is compared **as configured**, never as currently applied. The
1958
+ * table owns its rows-per-page: the user picks it from the paginator and
1959
+ * `mt-table` restores a persisted value, and either of those legitimately
1960
+ * leaves the runtime `take` different from the configured one. Reading that
1961
+ * difference as drift to correct is what used to reset the paginator and
1962
+ * refetch page 1 on every reconfigure.
1963
+ */
1964
+ function preservesPaging(existing, areaType, next) {
1965
+ if (!existing ||
1966
+ existing.type !== 'form' ||
1967
+ existing.areaType !== areaType ||
1968
+ next.type !== 'form') {
1969
+ return false;
1970
+ }
1971
+ const current = existing.config;
1972
+ return (current.isPaginated === next.isPaginated &&
1973
+ current.take === next.take &&
1974
+ current.contextKey === next.contextKey &&
1975
+ serializeFilters(current.filters) === serializeFilters(next.filters));
1976
+ }
1977
+ function serializeFilters(filters) {
1978
+ return JSON.stringify(filters ?? []);
1979
+ }
1980
+
1763
1981
  const DEFAULT_AREA_TYPE = 'table';
1764
1982
  const DEFAULT_MODE = 'auto';
1765
1983
  const DEFAULT_TYPE = 'form';
@@ -1791,6 +2009,14 @@ class ClientList {
1791
2009
  inFlightRequestSignatures = new Map();
1792
2010
  fulfilledRequestSignatures = new Map();
1793
2011
  runtimeFilters = new Map();
2012
+ /**
2013
+ * The `propertyKeys` the last table request carried, per item. `undefined`
2014
+ * means the request left the projection to the backend, which is a distinct
2015
+ * state from "not loaded yet" — hence the map rather than a nullable field.
2016
+ */
2017
+ requestPropertyKeys = new Map();
2018
+ /** Single-record read-backs, kept apart from the page loads they must not cancel. */
2019
+ recordSubscriptions = new Map();
1794
2020
  constructor() {
1795
2021
  effect(() => {
1796
2022
  const configs = this.configurations();
@@ -1860,6 +2086,93 @@ class ClientList {
1860
2086
  return;
1861
2087
  this.loadItem(item, true);
1862
2088
  }
2089
+ /**
2090
+ * The query settings this list renders with, shaped as the `returnRecord`
2091
+ * block of a `process-submit` payload. Send it with a create/update so the
2092
+ * backend projects the written record the way this list reads records, then
2093
+ * hand the response to {@link applyWriteResult} — that pair replaces the
2094
+ * full page re-fetch a write used to cost.
2095
+ *
2096
+ * Returns `null` for an informative dashboard (no records to project) or an
2097
+ * unknown key.
2098
+ */
2099
+ getReturnRecordRequest(itemKey) {
2100
+ const item = this.state.itemsByKey()[itemKey];
2101
+ if (!item || item.type !== 'form')
2102
+ return null;
2103
+ const includeState = [...CLIENT_LIST_DEFAULT_INCLUDE_STATE];
2104
+ const culture = this.transloco.getActiveLang();
2105
+ if (item.areaType === 'cards') {
2106
+ return { projection: 'Card', includeState, culture };
2107
+ }
2108
+ const propertyKeys = this.requestPropertyKeys.get(itemKey);
2109
+ return {
2110
+ projection: 'Table',
2111
+ surfaceKey: 'table',
2112
+ includeState,
2113
+ // Omitted, not empty, when the table let the backend pick the
2114
+ // projection — an empty array would read as "no columns".
2115
+ ...(propertyKeys?.length ? { propertyKeys } : {}),
2116
+ culture,
2117
+ };
2118
+ }
2119
+ /**
2120
+ * Applies a create/update to the list in place, without re-fetching the
2121
+ * page. Pass the `process-submit` response as-is.
2122
+ *
2123
+ * Three outcomes, in the order they are tried:
2124
+ * - the write carried a usable record projection → splice it in, no request;
2125
+ * - it did not (`recordProjectionStatus: 'Unavailable'`, or the list has
2126
+ * nothing to splice into) → read that one record back by id. The write
2127
+ * already succeeded; this only reads, and never re-submits;
2128
+ * - nothing identifies the record at all → reload the page, as before.
2129
+ *
2130
+ * An approval-queued submit is a no-op: nothing has been written yet.
2131
+ *
2132
+ * Note that the list cannot know where the backend would have sorted a new
2133
+ * record, so an insert lands at the top of the page in view.
2134
+ */
2135
+ applyWriteResult(itemKey, result) {
2136
+ const item = this.state.itemsByKey()[itemKey];
2137
+ if (!item || item.type !== 'form')
2138
+ return;
2139
+ if (this.isPendingApprovalWrite(result))
2140
+ return;
2141
+ const recordId = this.toFiniteNumber(result?.recordId);
2142
+ const record = this.toWriteRecord(result, recordId);
2143
+ if (record) {
2144
+ // A failed splice means the list holds no page to splice into, so
2145
+ // reading the record back would not help either.
2146
+ if (this.state.upsertRecord(itemKey, record, item.config)) {
2147
+ this.afterRecordApplied(itemKey);
2148
+ }
2149
+ else {
2150
+ this.reloadByKey(itemKey);
2151
+ }
2152
+ return;
2153
+ }
2154
+ if (recordId != null) {
2155
+ this.readRecordBack(item, recordId);
2156
+ return;
2157
+ }
2158
+ this.reloadByKey(itemKey);
2159
+ }
2160
+ /**
2161
+ * Drops a deleted record from the list in place. Falls back to a reload when
2162
+ * the record is not part of the loaded set, so a stale list still corrects
2163
+ * itself.
2164
+ */
2165
+ removeRecord(itemKey, recordId) {
2166
+ const item = this.state.itemsByKey()[itemKey];
2167
+ if (!item || item.type !== 'form')
2168
+ return;
2169
+ if (!this.state.removeRecord(itemKey, recordId, item.config)) {
2170
+ this.reloadByKey(itemKey);
2171
+ return;
2172
+ }
2173
+ this.runtimeActions.invalidateRow(itemKey, recordId);
2174
+ this.afterRecordApplied(itemKey, { invalidateRuntimeActions: false });
2175
+ }
1863
2176
  toggleExpanded(key) {
1864
2177
  this.state.toggleExpanded(key);
1865
2178
  }
@@ -2110,6 +2423,89 @@ class ClientList {
2110
2423
  config.onActionExecuted?.(action, ctx);
2111
2424
  }
2112
2425
  }
2426
+ /**
2427
+ * A submit that only queued an approval request has written nothing — the
2428
+ * record it describes does not exist yet, so the list must not move.
2429
+ */
2430
+ isPendingApprovalWrite(result) {
2431
+ return (String(result?.status ?? '')
2432
+ .trim()
2433
+ .toLowerCase() === 'pendingapproval');
2434
+ }
2435
+ /**
2436
+ * The canonical record from a write response, or `null` when there is none
2437
+ * to trust. A backend that has not shipped the canonical projection yet
2438
+ * answers with neither `recordProjectionStatus: 'Available'` nor `values`,
2439
+ * and falls through to the read-back path — which is why this is a plain
2440
+ * shape check and not a parse of alternative record shapes.
2441
+ */
2442
+ toWriteRecord(result, recordId) {
2443
+ if (result?.recordProjectionStatus !== 'Available')
2444
+ return null;
2445
+ const record = result.record;
2446
+ if (!record?.values)
2447
+ return null;
2448
+ const id = this.toFiniteNumber(record.id) ?? recordId;
2449
+ if (id == null)
2450
+ return null;
2451
+ return { ...record, id };
2452
+ }
2453
+ /**
2454
+ * Reads one record back through `fetch/query` with this list's own settings.
2455
+ * Taken when the write could not project the record itself — the write has
2456
+ * already happened, so this reads and never re-submits.
2457
+ */
2458
+ readRecordBack(item, recordId) {
2459
+ const config = item.config;
2460
+ const request = this.getReturnRecordRequest(item.key);
2461
+ if (!request || !this.hasValidIdentifiers(config)) {
2462
+ this.reloadByKey(item.key);
2463
+ return;
2464
+ }
2465
+ // The host's own scope filters plus the record id. Runtime filters (the
2466
+ // search box, a column filter) are deliberately left out: a record the
2467
+ // user just wrote should come back whether or not it matches whatever
2468
+ // they happen to have typed into the list.
2469
+ const filters = [
2470
+ ...config.filters,
2471
+ {
2472
+ key: CLIENT_LIST_RECORD_ID_FILTER_KEY,
2473
+ operator: 'Equals',
2474
+ value: recordId,
2475
+ },
2476
+ ];
2477
+ this.recordSubscriptions.get(item.key)?.unsubscribe();
2478
+ const sub = this.api
2479
+ .getRecord(config.contextKey, request, filters)
2480
+ .subscribe({
2481
+ next: (response) => {
2482
+ this.recordSubscriptions.delete(item.key);
2483
+ const record = response.data?.records?.[0];
2484
+ if (!record || !this.state.upsertRecord(item.key, record, config)) {
2485
+ this.reloadByKey(item.key);
2486
+ return;
2487
+ }
2488
+ this.afterRecordApplied(item.key);
2489
+ },
2490
+ error: () => {
2491
+ this.recordSubscriptions.delete(item.key);
2492
+ this.reloadByKey(item.key);
2493
+ },
2494
+ });
2495
+ this.recordSubscriptions.set(item.key, sub);
2496
+ }
2497
+ /**
2498
+ * Runs the tail of a load for a list that changed without one: the row
2499
+ * action cache is stale (a written record's available actions follow its new
2500
+ * state), and hosts hang work off `dataLoaded` / `loaded`.
2501
+ */
2502
+ afterRecordApplied(itemKey, options) {
2503
+ if (options?.invalidateRuntimeActions !== false) {
2504
+ this.runtimeActions.invalidateList(itemKey);
2505
+ }
2506
+ this.notifyDataLoaded(itemKey);
2507
+ this.loaded.emit(itemKey);
2508
+ }
2113
2509
  toRuntimeActionsContext(item, row, config) {
2114
2510
  const recordId = this.resolveRecordId(config, row);
2115
2511
  if (recordId == null)
@@ -2147,6 +2543,9 @@ class ClientList {
2147
2543
  ngOnDestroy() {
2148
2544
  this.subscriptions.forEach((sub) => sub.unsubscribe());
2149
2545
  this.subscriptions.clear();
2546
+ this.recordSubscriptions.forEach((sub) => sub.unsubscribe());
2547
+ this.recordSubscriptions.clear();
2548
+ this.requestPropertyKeys.clear();
2150
2549
  this.rowActionsLoadingFnCache.clear();
2151
2550
  this.runtimeActions.clearAll();
2152
2551
  }
@@ -2184,6 +2583,10 @@ class ClientList {
2184
2583
  const key = config.key ?? this.createItemKey(config, index);
2185
2584
  const existing = previousItems[key];
2186
2585
  const normalizedConfig = this.toNormalizedConfig(config, type, areaType, mode, isPaginated, take, showHeader, collapseEnabled, layout);
2586
+ // Paging is runtime state, not config state: it survives a reconfigure
2587
+ // that changed nothing about the fetch, and the *applied* page size
2588
+ // (the user's rows-per-page choice) is carried forward with it.
2589
+ const keepsPaging = preservesPaging(existing, areaType, normalizedConfig);
2187
2590
  if (isInformative) {
2188
2591
  return {
2189
2592
  key,
@@ -2219,12 +2622,13 @@ class ClientList {
2219
2622
  columns: [],
2220
2623
  rows: [],
2221
2624
  cards: [],
2222
- // Cards restart from the first page on every reconfigure: the
2223
- // loaded set above is wiped, and for infinite scroll `skip` is
2224
- // the next offset into that set — carrying it forward would ask
2225
- // the backend for page N with nothing before it on screen.
2226
- skip: 0,
2227
- take,
2625
+ // For infinite scroll `skip` is the next offset into the loaded
2626
+ // set, so it travels with that set: kept while the fetch is
2627
+ // unchanged (the cards themselves are kept too, by
2628
+ // `mergeItemState`), and restarted at 0 the moment the fetch
2629
+ // changes and the loaded set stops meaning anything.
2630
+ skip: keepsPaging ? existing.skip : 0,
2631
+ take: keepsPaging ? existing.take : take,
2228
2632
  expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
2229
2633
  dashboardData: null,
2230
2634
  rawData: existing?.rawData ?? null,
@@ -2243,12 +2647,8 @@ class ClientList {
2243
2647
  columns: [],
2244
2648
  rows: [],
2245
2649
  cards: [],
2246
- skip: existing?.areaType === 'table' &&
2247
- existing.config.isPaginated === isPaginated &&
2248
- existing.take === take
2249
- ? existing.skip
2250
- : 0,
2251
- take,
2650
+ skip: keepsPaging ? existing.skip : 0,
2651
+ take: keepsPaging ? existing.take : take,
2252
2652
  expanded: existing?.expanded ?? config.collapse?.expandedByDefault ?? true,
2253
2653
  dashboardData: null,
2254
2654
  rawData: existing?.rawData ?? null,
@@ -2290,9 +2690,10 @@ class ClientList {
2290
2690
  this.state.setLoading(key, true);
2291
2691
  this.state.setError(key, null);
2292
2692
  const sub = this.api.getRows(config.contextKey, query, filters).subscribe({
2293
- next: (response) => {
2693
+ next: ({ response, propertyKeys }) => {
2294
2694
  let hasLoadedData = false;
2295
2695
  if (response.data) {
2696
+ this.requestPropertyKeys.set(key, propertyKeys);
2296
2697
  this.state.setRowsResult(key, response.data, config, query.skip, query.take);
2297
2698
  this.fulfilledRequestSignatures.set(key, requestSignature);
2298
2699
  hasLoadedData = true;
@@ -2563,11 +2964,22 @@ class ClientList {
2563
2964
  this.fulfilledRequestSignatures.delete(key);
2564
2965
  }
2565
2966
  }
2967
+ for (const [key, sub] of this.recordSubscriptions.entries()) {
2968
+ if (!activeKeys.has(key)) {
2969
+ sub.unsubscribe();
2970
+ this.recordSubscriptions.delete(key);
2971
+ }
2972
+ }
2566
2973
  for (const key of this.runtimeFilters.keys()) {
2567
2974
  if (!activeKeys.has(key)) {
2568
2975
  this.runtimeFilters.delete(key);
2569
2976
  }
2570
2977
  }
2978
+ for (const key of this.requestPropertyKeys.keys()) {
2979
+ if (!activeKeys.has(key)) {
2980
+ this.requestPropertyKeys.delete(key);
2981
+ }
2982
+ }
2571
2983
  for (const key of this.rowActionsLoadingFnCache.keys()) {
2572
2984
  if (!activeKeys.has(key)) {
2573
2985
  this.rowActionsLoadingFnCache.delete(key);
@@ -2694,5 +3106,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2694
3106
  * Generated bundle index. Do not edit.
2695
3107
  */
2696
3108
 
2697
- export { CLIENT_LIST_RECORD_STATE_KEY, ClientList, ClientListApiService, ClientListRuntimeActionsService, ClientListStateService, ClientListToolbarService, defaultResolveRecordId };
3109
+ export { CLIENT_LIST_DEFAULT_INCLUDE_STATE, CLIENT_LIST_RECORD_ID_FILTER_KEY, CLIENT_LIST_RECORD_STATE_KEY, ClientList, ClientListApiService, ClientListRuntimeActionsService, ClientListStateService, ClientListToolbarService, defaultResolveRecordId };
2698
3110
  //# sourceMappingURL=masterteam-client-components-client-list.mjs.map