@teleporthq/teleport-plugin-next-data-source 0.43.75 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/_helpers/first-page-runtime.ts +184 -0
- package/__tests__/_helpers/pagination-widget-fixture.ts +154 -5
- package/__tests__/booking-item-transform.test.ts +120 -0
- package/__tests__/browser-row-policy.test.ts +210 -4
- package/__tests__/data-source-request-locale.test.ts +4 -3
- package/__tests__/fetchers.test.ts +7 -3
- package/__tests__/fixed-list-sort.test.ts +1 -1
- package/__tests__/infinite-scroll.test.ts +363 -9
- package/__tests__/list-sort-order.test.ts +61 -0
- package/__tests__/null-filter-where.test.ts +132 -0
- package/__tests__/page-data-visitor-read.test.ts +196 -0
- package/__tests__/pg-date-parser.test.ts +73 -0
- package/__tests__/plain-list-sort.test.ts +2 -2
- package/__tests__/plain-list-starting-sort.test.ts +323 -0
- package/__tests__/product-price-sort.ts +11 -6
- package/__tests__/range-control.test.ts +159 -0
- package/__tests__/refetch-loading-state.test.ts +158 -2
- package/__tests__/search-input-marker.test.ts +86 -0
- package/__tests__/search-url-sync.test.ts +34 -8
- package/__tests__/starting-sort-first-page.test.ts +285 -0
- package/__tests__/table-read-route-protected-tables.test.ts +33 -3
- package/dist/cjs/cache/ast.d.ts +2 -0
- package/dist/cjs/cache/ast.d.ts.map +1 -1
- package/dist/cjs/cache/ast.js +5 -2
- package/dist/cjs/cache/ast.js.map +1 -1
- package/dist/cjs/count-effect.d.ts +7 -1
- package/dist/cjs/count-effect.d.ts.map +1 -1
- package/dist/cjs/count-effect.js +15 -5
- package/dist/cjs/count-effect.js.map +1 -1
- package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
- package/dist/cjs/data-source-fetchers.js +10 -2
- package/dist/cjs/data-source-fetchers.js.map +1 -1
- package/dist/cjs/fetchers/pg-client-import.d.ts +12 -0
- package/dist/cjs/fetchers/pg-client-import.d.ts.map +1 -0
- package/dist/cjs/fetchers/pg-client-import.js +15 -0
- package/dist/cjs/fetchers/pg-client-import.js.map +1 -0
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +3 -1
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
- package/dist/cjs/fetchers/raw-query.js +2 -1
- package/dist/cjs/fetchers/raw-query.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +4 -2
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js +5 -1
- package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +14 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/infinite-scroll.d.ts +89 -4
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +243 -33
- package/dist/cjs/infinite-scroll.js.map +1 -1
- package/dist/cjs/list-sort-order.d.ts +14 -0
- package/dist/cjs/list-sort-order.d.ts.map +1 -0
- package/dist/cjs/list-sort-order.js +36 -0
- package/dist/cjs/list-sort-order.js.map +1 -0
- package/dist/cjs/loading-state.d.ts +23 -2
- package/dist/cjs/loading-state.d.ts.map +1 -1
- package/dist/cjs/loading-state.js +50 -15
- package/dist/cjs/loading-state.js.map +1 -1
- package/dist/cjs/pagination-controls.d.ts +22 -3
- package/dist/cjs/pagination-controls.d.ts.map +1 -1
- package/dist/cjs/pagination-controls.js +29 -3
- package/dist/cjs/pagination-controls.js.map +1 -1
- package/dist/cjs/pagination-plugin.d.ts.map +1 -1
- package/dist/cjs/pagination-plugin.js +336 -164
- package/dist/cjs/pagination-plugin.js.map +1 -1
- package/dist/cjs/range-control.d.ts +45 -0
- package/dist/cjs/range-control.d.ts.map +1 -0
- package/dist/cjs/range-control.js +165 -0
- package/dist/cjs/range-control.js.map +1 -0
- package/dist/cjs/row-order.d.ts +3 -2
- package/dist/cjs/row-order.d.ts.map +1 -1
- package/dist/cjs/row-order.js +5 -4
- package/dist/cjs/row-order.js.map +1 -1
- package/dist/cjs/search-input.d.ts +42 -0
- package/dist/cjs/search-input.d.ts.map +1 -0
- package/dist/cjs/search-input.js +158 -0
- package/dist/cjs/search-input.js.map +1 -0
- package/dist/cjs/sort-utils.d.ts +53 -1
- package/dist/cjs/sort-utils.d.ts.map +1 -1
- package/dist/cjs/sort-utils.js +105 -13
- package/dist/cjs/sort-utils.js.map +1 -1
- package/dist/cjs/transformations/booking-item.d.ts +24 -0
- package/dist/cjs/transformations/booking-item.d.ts.map +1 -0
- package/dist/cjs/transformations/booking-item.js +27 -0
- package/dist/cjs/transformations/booking-item.js.map +1 -0
- package/dist/cjs/transformations/index.d.ts +4 -2
- package/dist/cjs/transformations/index.d.ts.map +1 -1
- package/dist/cjs/transformations/index.js +14 -5
- package/dist/cjs/transformations/index.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts +3 -2
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +48 -18
- package/dist/cjs/utils.js.map +1 -1
- package/dist/esm/cache/ast.d.ts +2 -0
- package/dist/esm/cache/ast.d.ts.map +1 -1
- package/dist/esm/cache/ast.js +3 -1
- package/dist/esm/cache/ast.js.map +1 -1
- package/dist/esm/count-effect.d.ts +7 -1
- package/dist/esm/count-effect.d.ts.map +1 -1
- package/dist/esm/count-effect.js +15 -5
- package/dist/esm/count-effect.js.map +1 -1
- package/dist/esm/data-source-fetchers.d.ts.map +1 -1
- package/dist/esm/data-source-fetchers.js +10 -2
- package/dist/esm/data-source-fetchers.js.map +1 -1
- package/dist/esm/fetchers/pg-client-import.d.ts +12 -0
- package/dist/esm/fetchers/pg-client-import.d.ts.map +1 -0
- package/dist/esm/fetchers/pg-client-import.js +12 -0
- package/dist/esm/fetchers/pg-client-import.js.map +1 -0
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +3 -1
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
- package/dist/esm/fetchers/raw-query.js +2 -1
- package/dist/esm/fetchers/raw-query.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +4 -2
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
- package/dist/esm/fetchers/utils/browser-row-policy.js +6 -2
- package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +14 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/infinite-scroll.d.ts +89 -4
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +237 -32
- package/dist/esm/infinite-scroll.js.map +1 -1
- package/dist/esm/list-sort-order.d.ts +14 -0
- package/dist/esm/list-sort-order.d.ts.map +1 -0
- package/dist/esm/list-sort-order.js +32 -0
- package/dist/esm/list-sort-order.js.map +1 -0
- package/dist/esm/loading-state.d.ts +23 -2
- package/dist/esm/loading-state.d.ts.map +1 -1
- package/dist/esm/loading-state.js +50 -15
- package/dist/esm/loading-state.js.map +1 -1
- package/dist/esm/pagination-controls.d.ts +22 -3
- package/dist/esm/pagination-controls.d.ts.map +1 -1
- package/dist/esm/pagination-controls.js +28 -2
- package/dist/esm/pagination-controls.js.map +1 -1
- package/dist/esm/pagination-plugin.d.ts.map +1 -1
- package/dist/esm/pagination-plugin.js +340 -168
- package/dist/esm/pagination-plugin.js.map +1 -1
- package/dist/esm/range-control.d.ts +45 -0
- package/dist/esm/range-control.d.ts.map +1 -0
- package/dist/esm/range-control.js +137 -0
- package/dist/esm/range-control.js.map +1 -0
- package/dist/esm/row-order.d.ts +3 -2
- package/dist/esm/row-order.d.ts.map +1 -1
- package/dist/esm/row-order.js +5 -4
- package/dist/esm/row-order.js.map +1 -1
- package/dist/esm/search-input.d.ts +42 -0
- package/dist/esm/search-input.d.ts.map +1 -0
- package/dist/esm/search-input.js +129 -0
- package/dist/esm/search-input.js.map +1 -0
- package/dist/esm/sort-utils.d.ts +53 -1
- package/dist/esm/sort-utils.d.ts.map +1 -1
- package/dist/esm/sort-utils.js +100 -12
- package/dist/esm/sort-utils.js.map +1 -1
- package/dist/esm/transformations/booking-item.d.ts +24 -0
- package/dist/esm/transformations/booking-item.d.ts.map +1 -0
- package/dist/esm/transformations/booking-item.js +23 -0
- package/dist/esm/transformations/booking-item.js.map +1 -0
- package/dist/esm/transformations/index.d.ts +4 -2
- package/dist/esm/transformations/index.d.ts.map +1 -1
- package/dist/esm/transformations/index.js +14 -5
- package/dist/esm/transformations/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts +3 -2
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +49 -19
- package/dist/esm/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/cache/ast.ts +4 -1
- package/src/count-effect.ts +31 -8
- package/src/data-source-fetchers.ts +33 -14
- package/src/fetchers/pg-client-import.ts +16 -0
- package/src/fetchers/postgresql.ts +15 -9
- package/src/fetchers/raw-query.ts +2 -1
- package/src/fetchers/teleport.ts +19 -10
- package/src/fetchers/utils/browser-row-policy.ts +79 -5
- package/src/index.ts +8 -2
- package/src/infinite-scroll.ts +420 -81
- package/src/list-sort-order.ts +34 -0
- package/src/loading-state.ts +85 -13
- package/src/pagination-controls.ts +26 -3
- package/src/pagination-plugin.ts +532 -218
- package/src/range-control.ts +207 -0
- package/src/row-order.ts +5 -4
- package/src/search-input.ts +190 -0
- package/src/sort-utils.ts +164 -21
- package/src/transformations/booking-item.ts +71 -0
- package/src/transformations/index.ts +25 -2
- package/src/utils.ts +55 -18
package/src/pagination-plugin.ts
CHANGED
|
@@ -3,15 +3,28 @@ import {
|
|
|
3
3
|
ComponentPluginFactory,
|
|
4
4
|
ChunkType,
|
|
5
5
|
FileType,
|
|
6
|
+
UIDLStateDefinition,
|
|
6
7
|
} from '@teleporthq/teleport-types'
|
|
7
8
|
import * as types from '@babel/types'
|
|
8
9
|
import { parseExpression } from '@babel/parser'
|
|
9
10
|
import { ASTStatementOrder, GenericUtils, StringUtils } from '@teleporthq/teleport-shared'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ASTUtils,
|
|
13
|
+
RouteUtils,
|
|
14
|
+
URLQueryWriter,
|
|
15
|
+
URLSearchParamSync,
|
|
16
|
+
} from '@teleporthq/teleport-plugin-common'
|
|
11
17
|
import { collectFilterLeafConditions, generateSafeFileName, pruneFilterTree } from './utils'
|
|
12
18
|
import { generateDataSourceFetcherWithCore } from './data-source-fetchers'
|
|
13
19
|
import { buildProductTransformOptions, type EntityTransformOptions } from './transformations'
|
|
14
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
appendSortsParam,
|
|
22
|
+
buildSortAtStartGuard,
|
|
23
|
+
buildStartingSortsValue,
|
|
24
|
+
DynamicSortAST,
|
|
25
|
+
readRepeaterSort,
|
|
26
|
+
type SortStateDefault,
|
|
27
|
+
} from './sort-utils'
|
|
15
28
|
import {
|
|
16
29
|
appendFiltersParam,
|
|
17
30
|
buildStaticFiltersStringifyCall,
|
|
@@ -32,15 +45,21 @@ import {
|
|
|
32
45
|
buildCacheStoreThen,
|
|
33
46
|
buildCachedFetchBody,
|
|
34
47
|
buildCachedParamsDeclarations,
|
|
48
|
+
paramsVarFor,
|
|
35
49
|
registerCacheClientImports,
|
|
36
50
|
} from './cache/ast'
|
|
37
51
|
import {
|
|
38
52
|
convertControlToButton,
|
|
39
53
|
dropInactivePaginationControls,
|
|
54
|
+
findAllPaginationControls,
|
|
40
55
|
findPaginationControl,
|
|
56
|
+
PAGINATION_FOR_ATTR,
|
|
57
|
+
readJSXAttributeString,
|
|
41
58
|
setJSXExpressionAttribute,
|
|
42
59
|
type PaginationControlKind,
|
|
43
60
|
} from './pagination-controls'
|
|
61
|
+
import { findRangeControls, wireRangeControl } from './range-control'
|
|
62
|
+
import { findAllSearchInputsInJSX, pairSearchInputs, wireSearchInput } from './search-input'
|
|
44
63
|
import { buildCountFetchEffect, buildCountSeqDeclaration } from './count-effect'
|
|
45
64
|
import {
|
|
46
65
|
findAllSearchClearButtonsInJSX,
|
|
@@ -53,10 +72,15 @@ import {
|
|
|
53
72
|
type NumberedPaginationVars,
|
|
54
73
|
} from './numbered-pagination'
|
|
55
74
|
import {
|
|
75
|
+
buildAccumulatingRequestMark,
|
|
56
76
|
buildAccumulatingResponseHandler,
|
|
77
|
+
buildAppendingRequestCheck,
|
|
78
|
+
buildFirstPageDeclaration,
|
|
79
|
+
buildFirstPageSeedEffect,
|
|
57
80
|
buildInfiniteScrollDeclarations,
|
|
58
81
|
buildSentinelElement,
|
|
59
82
|
buildSentinelObserverEffect,
|
|
83
|
+
buildServerRowsExpression,
|
|
60
84
|
getInfiniteScrollVars,
|
|
61
85
|
} from './infinite-scroll'
|
|
62
86
|
import {
|
|
@@ -154,6 +178,10 @@ interface DataSourceUsage {
|
|
|
154
178
|
dataSourceIdentifier: string
|
|
155
179
|
// The cms-list-repeater renderPropIdentifier (e.g., "context_1i871")
|
|
156
180
|
arrayMapperRenderProp: string
|
|
181
|
+
// The array mapper's editor node id (`nodeId` on the cms-list-repeater), which
|
|
182
|
+
// a `range` control placed outside the pagination container names in its
|
|
183
|
+
// `data-tq-pagination-for` attribute. Absent from UIDL exported before it.
|
|
184
|
+
nodeId?: string
|
|
157
185
|
// Resource definition from UIDL
|
|
158
186
|
resourceDefinition: {
|
|
159
187
|
dataSourceId: string
|
|
@@ -187,6 +215,12 @@ interface DataSourceUsage {
|
|
|
187
215
|
// Dynamic single-column sort bound to component state (set when legacy sorts empty
|
|
188
216
|
// and cms-list-repeater declares sort/sortDirection fields)
|
|
189
217
|
dynamicSort?: DynamicSortAST
|
|
218
|
+
// With `dynamicSort`: the states it reads, at the values a visitor starts
|
|
219
|
+
// with (`readSortStateDefaults`). The build fetches the first page by that
|
|
220
|
+
// sort, and the list takes those rows while the sort is still there. Absent
|
|
221
|
+
// when the build cannot know the starting sort: the list then fetches its
|
|
222
|
+
// first page in the browser.
|
|
223
|
+
startingSort?: SortStateDefault[]
|
|
190
224
|
// Filters from resource params, as the TREE the inspector authored: condition
|
|
191
225
|
// leaves and `{ type: 'group', operator, children }` entries (see
|
|
192
226
|
// `readFilterTree`). Anything that needs the leaves walks the tree with
|
|
@@ -446,7 +480,10 @@ function buildFilterDestinationExpression(destination: unknown): types.Expressio
|
|
|
446
480
|
}
|
|
447
481
|
|
|
448
482
|
// Scan UIDL to find all data source usages and build a registry
|
|
449
|
-
function buildStateRegistry(
|
|
483
|
+
function buildStateRegistry(
|
|
484
|
+
uidlNode: any,
|
|
485
|
+
stateDefinitions?: Record<string, UIDLStateDefinition>
|
|
486
|
+
): StateRegistry {
|
|
450
487
|
const usages: DataSourceUsage[] = []
|
|
451
488
|
const byDataSourceId = new Map<string, DataSourceUsage[]>()
|
|
452
489
|
const byArrayMapperRenderProp = new Map<string, DataSourceUsage>()
|
|
@@ -515,15 +552,20 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
515
552
|
// is skipped for a dynamic sort) — the pre-rendered HTML carried no
|
|
516
553
|
// posts, and the browser fetched page 1 a second time. Only a sort
|
|
517
554
|
// bound to state (the admin panel's sortable columns) is dynamic.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
555
|
+
//
|
|
556
|
+
// A sort the visitor picks still starts somewhere — the store's sort
|
|
557
|
+
// dropdown at "Name A-Z". Known at build time, it orders the
|
|
558
|
+
// server-side first page, which the list then shows while the visitor
|
|
559
|
+
// leaves the sort alone. Read as a plain list's page fetch reads it.
|
|
560
|
+
const repeaterSort =
|
|
561
|
+
!sorts || sorts.length === 0
|
|
562
|
+
? readRepeaterSort(content.sort, content.sortDirection, stateDefinitions)
|
|
563
|
+
: {}
|
|
564
|
+
if (repeaterSort.fixed) {
|
|
565
|
+
sorts = [repeaterSort.fixed]
|
|
526
566
|
}
|
|
567
|
+
const dynamicSort = repeaterSort.dynamic
|
|
568
|
+
const startingSort = repeaterSort.starting
|
|
527
569
|
|
|
528
570
|
// Extract filters from parent's resource params, keeping the group tree
|
|
529
571
|
// the inspector authored (see `readFilterTree`).
|
|
@@ -603,6 +645,7 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
603
645
|
index: index++,
|
|
604
646
|
dataSourceIdentifier: parentDataSource.identifier,
|
|
605
647
|
arrayMapperRenderProp,
|
|
648
|
+
nodeId: typeof content.nodeId === 'string' ? content.nodeId : undefined,
|
|
606
649
|
resourceDefinition: {
|
|
607
650
|
dataSourceId: parentDataSource.resourceDef.dataSourceId || '',
|
|
608
651
|
tableName: parentDataSource.resourceDef.tableName || '',
|
|
@@ -617,6 +660,7 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
617
660
|
queryColumns,
|
|
618
661
|
sorts,
|
|
619
662
|
dynamicSort,
|
|
663
|
+
startingSort,
|
|
620
664
|
filters,
|
|
621
665
|
filterStateIds,
|
|
622
666
|
filterPropIds,
|
|
@@ -704,6 +748,9 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
704
748
|
setPageStateVar: string
|
|
705
749
|
maxPagesStateVar: string
|
|
706
750
|
setMaxPagesStateVar: string
|
|
751
|
+
/** The counted row total behind a `range` control; `null` until counted. */
|
|
752
|
+
totalStateVar: string
|
|
753
|
+
setTotalStateVar: string
|
|
707
754
|
searchQueryVar: string
|
|
708
755
|
setSearchQueryVar: string
|
|
709
756
|
debouncedSearchQueryVar: string
|
|
@@ -722,6 +769,8 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
722
769
|
setPageStateVar: `setDs_${idx}_page`,
|
|
723
770
|
maxPagesStateVar: `ds_${idx}_maxPages`,
|
|
724
771
|
setMaxPagesStateVar: `setDs_${idx}_maxPages`,
|
|
772
|
+
totalStateVar: `ds_${idx}_total`,
|
|
773
|
+
setTotalStateVar: `setDs_${idx}_total`,
|
|
725
774
|
searchQueryVar: `ds_${idx}_searchQuery`,
|
|
726
775
|
setSearchQueryVar: `setDs_${idx}_searchQuery`,
|
|
727
776
|
debouncedSearchQueryVar: `ds_${idx}_debouncedQuery`,
|
|
@@ -829,7 +878,8 @@ function pushSearchUrlSyncEffects(
|
|
|
829
878
|
)
|
|
830
879
|
// read-back: `?<searchUrlParamKey>=` → the input AND the applied query at once
|
|
831
880
|
// (`ds_N_adoptUrlSearch`), so an address is not a typed change: it skips the
|
|
832
|
-
// debounce and keeps
|
|
881
|
+
// debounce and keeps a page the address carries — `?searchKeyword=x&page=3`
|
|
882
|
+
// lands on page 3.
|
|
833
883
|
effectStatements.push(
|
|
834
884
|
URLSearchParamSync.buildUrlReadBackEffect(
|
|
835
885
|
usage.searchUrlParamKey,
|
|
@@ -842,19 +892,26 @@ function pushSearchUrlSyncEffects(
|
|
|
842
892
|
|
|
843
893
|
// `const ds_N_adoptUrlSearch = (update) => { … }`: the setter the search's URL
|
|
844
894
|
// read-back calls with its `(prev) => next` updater. It moves the input and the
|
|
845
|
-
// applied (debounced) query together
|
|
846
|
-
// page
|
|
895
|
+
// applied (debounced) query together. A list whose page is in the address
|
|
896
|
+
// keeps its page — the page sync sets it from the same address. Any other
|
|
897
|
+
// list starts the new query on page 1, as a typed change does in the debounce
|
|
898
|
+
// effect: an appending list has usually advanced past page 1 by the time the
|
|
899
|
+
// router is ready, and the query's first answer would otherwise be its page 2
|
|
900
|
+
// — empty for a query only a few rows match.
|
|
847
901
|
function buildUrlSearchAdopter(
|
|
848
902
|
adopterVar: string,
|
|
849
903
|
usage: DataSourceUsage,
|
|
850
904
|
vars: ReturnType<typeof getStateVarsForUsage>
|
|
851
905
|
): types.VariableDeclaration {
|
|
852
906
|
const update = types.identifier('update')
|
|
907
|
+
const pageReset = usageHasPageUrlSync(usage)
|
|
908
|
+
? []
|
|
909
|
+
: [types.objectProperty(types.identifier('page'), types.numericLiteral(1))]
|
|
853
910
|
const applyToDebounced =
|
|
854
911
|
usage.category === 'paginated+search'
|
|
855
912
|
? // setDs_N_state((state) => {
|
|
856
913
|
// const next = update(state.debouncedQuery)
|
|
857
|
-
// return next === state.debouncedQuery ? state : { ...state, debouncedQuery: next }
|
|
914
|
+
// return next === state.debouncedQuery ? state : { ...state, page: 1, debouncedQuery: next }
|
|
858
915
|
// })
|
|
859
916
|
types.callExpression(types.identifier(vars.setCombinedStateVar), [
|
|
860
917
|
types.arrowFunctionExpression(
|
|
@@ -884,6 +941,7 @@ function buildUrlSearchAdopter(
|
|
|
884
941
|
types.identifier('state'),
|
|
885
942
|
types.objectExpression([
|
|
886
943
|
types.spreadElement(types.identifier('state')),
|
|
944
|
+
...pageReset,
|
|
887
945
|
types.objectProperty(
|
|
888
946
|
types.identifier('debouncedQuery'),
|
|
889
947
|
types.identifier('next')
|
|
@@ -1186,8 +1244,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1186
1244
|
}
|
|
1187
1245
|
const blockStatement = arrowFunction.body as types.BlockStatement
|
|
1188
1246
|
|
|
1247
|
+
// The "Showing 1–8 of 12" texts. Known up front because a component that
|
|
1248
|
+
// draws one needs the counted TOTAL in state — `ds_N_maxPages` alone cannot
|
|
1249
|
+
// say how many rows there are — and that state, with the live count that
|
|
1250
|
+
// fills it, is declared per usage below. A component without one emits
|
|
1251
|
+
// exactly what it always did.
|
|
1252
|
+
const rangeControls = findRangeControls(blockStatement)
|
|
1253
|
+
const hasRangeControl = rangeControls.length > 0
|
|
1254
|
+
|
|
1189
1255
|
// STEP 1: Build state registry from UIDL
|
|
1190
|
-
const registry = buildStateRegistry(uidl.node)
|
|
1256
|
+
const registry = buildStateRegistry(uidl.node, uidl.stateDefinitions)
|
|
1191
1257
|
|
|
1192
1258
|
if (registry.usages.length === 0) {
|
|
1193
1259
|
return structure
|
|
@@ -1500,12 +1566,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1500
1566
|
pushPropIdsAsDeps(countEffectDeps, countEffectSeen, usage.filterPropIds)
|
|
1501
1567
|
// Same goes for URL-driven filters (already documented above).
|
|
1502
1568
|
pushUrlSearchParamMemoDeps(countEffectDeps, usage)
|
|
1569
|
+
if (hasRangeControl) {
|
|
1570
|
+
stateDeclarations.push(buildTotalDeclaration(vars))
|
|
1571
|
+
}
|
|
1503
1572
|
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1504
1573
|
effectStatements.push(
|
|
1505
1574
|
buildCountFetchEffect({
|
|
1506
1575
|
fileName,
|
|
1507
1576
|
perPage: usage.perPage,
|
|
1508
1577
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1578
|
+
setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
|
|
1509
1579
|
urlParams,
|
|
1510
1580
|
deps: countEffectDeps,
|
|
1511
1581
|
countSeqRefVar: vars.countSeqRefVar,
|
|
@@ -1572,8 +1642,10 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1572
1642
|
// "Next" button, but not to label a numbered strip or to know when an
|
|
1573
1643
|
// infinite list has reached its end, so those two modes fetch a live
|
|
1574
1644
|
// count as well.
|
|
1645
|
+
// A range text needs the live count too: the build-time count only
|
|
1646
|
+
// ever became a page count, never the total it shows.
|
|
1575
1647
|
const needsLiveCount =
|
|
1576
|
-
!isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll
|
|
1648
|
+
!isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll || hasRangeControl
|
|
1577
1649
|
if (needsLiveCount) {
|
|
1578
1650
|
const fileName = generateSafeFileName(
|
|
1579
1651
|
usage.resourceDefinition.dataSourceType,
|
|
@@ -1610,12 +1682,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1610
1682
|
pushPropIdsAsDeps(countDeps, countDepsSeen, usage.filterPropIds)
|
|
1611
1683
|
pushUrlSearchParamMemoDeps(countDeps, usage)
|
|
1612
1684
|
|
|
1685
|
+
if (hasRangeControl) {
|
|
1686
|
+
stateDeclarations.push(buildTotalDeclaration(vars))
|
|
1687
|
+
}
|
|
1613
1688
|
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1614
1689
|
effectStatements.push(
|
|
1615
1690
|
buildCountFetchEffect({
|
|
1616
1691
|
fileName,
|
|
1617
1692
|
perPage: usage.perPage,
|
|
1618
1693
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1694
|
+
setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
|
|
1619
1695
|
urlParams: countUrlParams,
|
|
1620
1696
|
deps: countDeps,
|
|
1621
1697
|
countSeqRefVar: vars.countSeqRefVar,
|
|
@@ -1804,6 +1880,9 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1804
1880
|
// widgets can be matched to their owner rather than counted off in order.
|
|
1805
1881
|
const usageByProviderNode = new Map<any, DataSourceUsage>()
|
|
1806
1882
|
const cacheDeclarations: types.Statement[] = []
|
|
1883
|
+
// Only a page has rows of its own to start an appending list from: its
|
|
1884
|
+
// `getStaticProps` fetched page 1 (`updateGetStaticProps`).
|
|
1885
|
+
const firstPageDeclarations: types.Statement[] | undefined = isPage ? [] : undefined
|
|
1807
1886
|
|
|
1808
1887
|
dataProvidersWithRepeaters.forEach((dp) => {
|
|
1809
1888
|
const nameAttr = dp.openingElement.attributes.find(
|
|
@@ -1842,10 +1921,19 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1842
1921
|
vars,
|
|
1843
1922
|
fileName,
|
|
1844
1923
|
cacheDeclarations,
|
|
1845
|
-
localized
|
|
1924
|
+
localized,
|
|
1925
|
+
firstPageDeclarations
|
|
1846
1926
|
)
|
|
1847
1927
|
} else if (usage.category === 'paginated-only') {
|
|
1848
|
-
updateDataProviderForPaginationOnly(
|
|
1928
|
+
updateDataProviderForPaginationOnly(
|
|
1929
|
+
dp,
|
|
1930
|
+
usage,
|
|
1931
|
+
vars,
|
|
1932
|
+
fileName,
|
|
1933
|
+
cacheDeclarations,
|
|
1934
|
+
localized,
|
|
1935
|
+
firstPageDeclarations
|
|
1936
|
+
)
|
|
1849
1937
|
} else if (usage.category === 'search-only') {
|
|
1850
1938
|
updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclarations, localized)
|
|
1851
1939
|
} else if (usage.category === 'plain') {
|
|
@@ -1856,9 +1944,17 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1856
1944
|
// change refetches, instead of leaving the previous rows on screen with
|
|
1857
1945
|
// no feedback. Runs last so it sees the `fetchData` and
|
|
1858
1946
|
// `persistDataDuringLoading` attributes the updaters above just wrote.
|
|
1947
|
+
// An appending list keeps its rows on screen while its next page loads;
|
|
1948
|
+
// only a list loading afresh falls through to the loading slot.
|
|
1859
1949
|
const loadingVars = getLoadingStateVars(usage.index)
|
|
1860
|
-
|
|
1861
|
-
|
|
1950
|
+
const appendingRequest =
|
|
1951
|
+
usage.infiniteScroll && usage.paginated
|
|
1952
|
+
? buildAppendingRequestCheck(getInfiniteScrollVars(usage.index))
|
|
1953
|
+
: undefined
|
|
1954
|
+
if (applyLoadingStateToDataProvider(dp, loadingVars, appendingRequest)) {
|
|
1955
|
+
loadingStateDeclarations.push(
|
|
1956
|
+
...buildLoadingStateDeclarations(loadingVars, !!appendingRequest)
|
|
1957
|
+
)
|
|
1862
1958
|
// Recorded because the loading state is only declared for a provider
|
|
1863
1959
|
// that HAS a loading slot; a Load More button wired later may only
|
|
1864
1960
|
// reference `ds_N_isFetching` if it actually exists.
|
|
@@ -1868,10 +1964,8 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1868
1964
|
// An auto-loading list needs something at the end of the rows for its
|
|
1869
1965
|
// IntersectionObserver to watch.
|
|
1870
1966
|
if (usage.infiniteScroll && usage.paginated && !usage.infiniteScrollLoadMore) {
|
|
1871
|
-
insertSentinelAfterProvider(
|
|
1872
|
-
|
|
1873
|
-
dp,
|
|
1874
|
-
buildSentinelElement(getInfiniteScrollVars(usage.index))
|
|
1967
|
+
insertSentinelAfterProvider(blockStatement, dp, (parentTag) =>
|
|
1968
|
+
buildSentinelElement(getInfiniteScrollVars(usage.index), parentTag)
|
|
1875
1969
|
)
|
|
1876
1970
|
}
|
|
1877
1971
|
|
|
@@ -1928,6 +2022,12 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1928
2022
|
registerCacheClientImports(dependencies, uidl.outputOptions?.folderPath)
|
|
1929
2023
|
}
|
|
1930
2024
|
|
|
2025
|
+
// After the cache declarations: a cached list's first page reads the
|
|
2026
|
+
// `ds_N_params` they hoisted.
|
|
2027
|
+
if (firstPageDeclarations?.length) {
|
|
2028
|
+
ASTStatementOrder.insertStatementsAfterDependencies(blockStatement, firstPageDeclarations)
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1931
2031
|
// STEP 3.5: Handle DataProviders WITHOUT repeaters (data-source-item type)
|
|
1932
2032
|
// These access single items like data[0].name and should not re-render on state changes
|
|
1933
2033
|
// We wrap their params in useMemo to prevent reference changes from triggering re-renders
|
|
@@ -1940,25 +2040,17 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1940
2040
|
stabilizeDataProviderWithoutRepeater(dp)
|
|
1941
2041
|
})
|
|
1942
2042
|
|
|
1943
|
-
// STEP 4: Wire search inputs
|
|
1944
|
-
//
|
|
2043
|
+
// STEP 4: Wire search inputs — a platform input by its place among the
|
|
2044
|
+
// search-enabled mappers, a page's own marked input by the mapper it names
|
|
2045
|
+
// (`search-input.ts`).
|
|
1945
2046
|
const searchInputs = findAllSearchInputsInJSX(blockStatement)
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
const usageBySearchInput = new Map<any, DataSourceUsage>()
|
|
1952
|
-
searchInputs.forEach((input, idx) => {
|
|
1953
|
-
if (idx >= searchEnabledUsages.length) {
|
|
1954
|
-
return
|
|
2047
|
+
const usageBySearchInput = pairSearchInputs(searchInputs, registry.usages)
|
|
2048
|
+
for (const input of searchInputs) {
|
|
2049
|
+
const usage = usageBySearchInput.get(input.node)
|
|
2050
|
+
if (usage) {
|
|
2051
|
+
wireSearchInput(input, getStateVarsForUsage(usage))
|
|
1955
2052
|
}
|
|
1956
|
-
|
|
1957
|
-
const usage = searchEnabledUsages[idx]
|
|
1958
|
-
const vars = getStateVarsForUsage(usage)
|
|
1959
|
-
wireSearchInput(input.node, vars)
|
|
1960
|
-
usageBySearchInput.set(input.node, usage)
|
|
1961
|
-
})
|
|
2053
|
+
}
|
|
1962
2054
|
|
|
1963
2055
|
// STEP 4.5: Wire the clear buttons, each to the input it shares a parent with.
|
|
1964
2056
|
findAllSearchClearButtonsInJSX(blockStatement).forEach((button) => {
|
|
@@ -1997,14 +2089,36 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1997
2089
|
}
|
|
1998
2090
|
)
|
|
1999
2091
|
|
|
2000
|
-
// STEP
|
|
2092
|
+
// STEP 5b: Wire the range texts that name their mapper.
|
|
2093
|
+
//
|
|
2094
|
+
// A `range` control carrying `data-tq-pagination-for` belongs to the mapper
|
|
2095
|
+
// whose editor node id it names, wherever it sits — a template's footer or
|
|
2096
|
+
// toolbar is laid out by the author, not inside a pagination container, so
|
|
2097
|
+
// the container-ownership rule above cannot reach it. One without the
|
|
2098
|
+
// attribute was wired by its container in STEP 5; one naming a mapper this
|
|
2099
|
+
// component does not render stays as drawn.
|
|
2100
|
+
rangeControls.forEach((control) => {
|
|
2101
|
+
const ownerNodeId = readJSXAttributeString(control, PAGINATION_FOR_ATTR)
|
|
2102
|
+
if (!ownerNodeId) {
|
|
2103
|
+
return
|
|
2104
|
+
}
|
|
2105
|
+
const owner = registry.usages.find((usage) => usage.nodeId === ownerNodeId)
|
|
2106
|
+
if (owner) {
|
|
2107
|
+
wireRangeControlFor(control, owner, getStateVarsForUsage(owner))
|
|
2108
|
+
}
|
|
2109
|
+
})
|
|
2110
|
+
|
|
2111
|
+
// STEP 6: Update getStaticProps if this is a page. Its rows are what the
|
|
2112
|
+
// page renders, so they are read as a visitor is served them — unless only
|
|
2113
|
+
// the store's trusted readers can open the page (`pageDataReadArguments`).
|
|
2001
2114
|
if (isPage) {
|
|
2002
2115
|
updateGetStaticProps(
|
|
2003
2116
|
chunks,
|
|
2004
2117
|
registry,
|
|
2005
2118
|
dependencies,
|
|
2006
2119
|
uidl.outputOptions?.folderPath,
|
|
2007
|
-
localized
|
|
2120
|
+
localized,
|
|
2121
|
+
() => RouteUtils.pageDataReadArguments(uidl, options.auth)
|
|
2008
2122
|
)
|
|
2009
2123
|
}
|
|
2010
2124
|
|
|
@@ -2023,6 +2137,8 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
2023
2137
|
* It has to be a sibling: the provider's own child is a render-prop function,
|
|
2024
2138
|
* and anything placed inside it would be re-created on every fetch — the
|
|
2025
2139
|
* observer would then be watching an element that is no longer in the document.
|
|
2140
|
+
* The sentinel is built for the element it lands in (`buildSentinelElement`):
|
|
2141
|
+
* a fragment is transparent, a component's children may go anywhere.
|
|
2026
2142
|
*
|
|
2027
2143
|
* Returns false when the provider has no element parent to splice into (a
|
|
2028
2144
|
* provider returned directly from the component), leaving the list on its
|
|
@@ -2031,35 +2147,53 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
2031
2147
|
function insertSentinelAfterProvider(
|
|
2032
2148
|
blockStatement: types.BlockStatement,
|
|
2033
2149
|
provider: any,
|
|
2034
|
-
|
|
2150
|
+
buildSentinel: (parentTag: string | null) => types.JSXElement
|
|
2035
2151
|
): boolean {
|
|
2036
2152
|
let inserted = false
|
|
2037
2153
|
|
|
2038
|
-
const
|
|
2154
|
+
const elementTagOf = (node: any, enclosing: string | null): string | null => {
|
|
2155
|
+
if (node?.type === 'JSXFragment') {
|
|
2156
|
+
return enclosing
|
|
2157
|
+
}
|
|
2158
|
+
if (node?.type !== 'JSXElement') {
|
|
2159
|
+
return enclosing
|
|
2160
|
+
}
|
|
2161
|
+
const name = node.openingElement?.name
|
|
2162
|
+
if (name?.type !== 'JSXIdentifier') {
|
|
2163
|
+
return null
|
|
2164
|
+
}
|
|
2165
|
+
if (name.name === 'Fragment') {
|
|
2166
|
+
return enclosing
|
|
2167
|
+
}
|
|
2168
|
+
return /^[a-z]/.test(name.name) ? name.name : null
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const traverse = (node: any, enclosing: string | null): void => {
|
|
2039
2172
|
if (!node || inserted) {
|
|
2040
2173
|
return
|
|
2041
2174
|
}
|
|
2175
|
+
const tag = elementTagOf(node, enclosing)
|
|
2042
2176
|
if (Array.isArray(node.children)) {
|
|
2043
2177
|
const at = node.children.indexOf(provider)
|
|
2044
2178
|
if (at !== -1) {
|
|
2045
|
-
node.children.splice(at + 1, 0,
|
|
2179
|
+
node.children.splice(at + 1, 0, buildSentinel(tag))
|
|
2046
2180
|
inserted = true
|
|
2047
2181
|
return
|
|
2048
2182
|
}
|
|
2049
|
-
node.children.forEach(traverse)
|
|
2183
|
+
node.children.forEach((child: any) => traverse(child, tag))
|
|
2050
2184
|
}
|
|
2051
2185
|
if (node.body) {
|
|
2052
2186
|
if (Array.isArray(node.body)) {
|
|
2053
|
-
node.body.forEach(traverse)
|
|
2187
|
+
node.body.forEach((child: any) => traverse(child, tag))
|
|
2054
2188
|
} else {
|
|
2055
|
-
traverse(node.body)
|
|
2189
|
+
traverse(node.body, tag)
|
|
2056
2190
|
}
|
|
2057
2191
|
}
|
|
2058
2192
|
if (node.consequent) {
|
|
2059
|
-
traverse(node.consequent)
|
|
2193
|
+
traverse(node.consequent, tag)
|
|
2060
2194
|
}
|
|
2061
2195
|
if (node.alternate) {
|
|
2062
|
-
traverse(node.alternate)
|
|
2196
|
+
traverse(node.alternate, tag)
|
|
2063
2197
|
}
|
|
2064
2198
|
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2065
2199
|
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
@@ -2067,23 +2201,23 @@ function insertSentinelAfterProvider(
|
|
|
2067
2201
|
// an argument. Left unvisited, the products list under the first tab of
|
|
2068
2202
|
// a shop was never paginated or seeded, and rendered nothing.
|
|
2069
2203
|
if (node.left) {
|
|
2070
|
-
traverse(node.left)
|
|
2204
|
+
traverse(node.left, tag)
|
|
2071
2205
|
}
|
|
2072
2206
|
if (node.right) {
|
|
2073
|
-
traverse(node.right)
|
|
2207
|
+
traverse(node.right, tag)
|
|
2074
2208
|
}
|
|
2075
2209
|
if (node.test) {
|
|
2076
|
-
traverse(node.test)
|
|
2210
|
+
traverse(node.test, tag)
|
|
2077
2211
|
}
|
|
2078
2212
|
if (node.expression) {
|
|
2079
|
-
traverse(node.expression)
|
|
2213
|
+
traverse(node.expression, tag)
|
|
2080
2214
|
}
|
|
2081
2215
|
if (node.argument) {
|
|
2082
|
-
traverse(node.argument)
|
|
2216
|
+
traverse(node.argument, tag)
|
|
2083
2217
|
}
|
|
2084
2218
|
}
|
|
2085
2219
|
|
|
2086
|
-
traverse(blockStatement)
|
|
2220
|
+
traverse(blockStatement, null)
|
|
2087
2221
|
return inserted
|
|
2088
2222
|
}
|
|
2089
2223
|
|
|
@@ -2199,68 +2333,6 @@ function findArrayMapperRenderPropInDataProvider(dataProvider: any): string | un
|
|
|
2199
2333
|
return renderItemAttr.value.expression.params[0].name
|
|
2200
2334
|
}
|
|
2201
2335
|
|
|
2202
|
-
function findAllSearchInputsInJSX(
|
|
2203
|
-
blockStatement: types.BlockStatement
|
|
2204
|
-
): Array<{ node: any; className: string }> {
|
|
2205
|
-
const results: Array<{ node: any; className: string }> = []
|
|
2206
|
-
|
|
2207
|
-
const traverse = (node: any): void => {
|
|
2208
|
-
if (!node) {
|
|
2209
|
-
return
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
if (node.type === 'JSXElement' && node.openingElement?.name?.name === 'input') {
|
|
2213
|
-
const classAttr = node.openingElement.attributes?.find(
|
|
2214
|
-
(attr: any) => attr.type === 'JSXAttribute' && attr.name.name === 'className'
|
|
2215
|
-
)
|
|
2216
|
-
const className = classAttr?.value?.value || classAttr?.value?.expression?.value || ''
|
|
2217
|
-
if (className.includes('search-input')) {
|
|
2218
|
-
results.push({ node, className })
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
if (node.children && Array.isArray(node.children)) {
|
|
2223
|
-
node.children.forEach((c: any) => traverse(c))
|
|
2224
|
-
}
|
|
2225
|
-
if (node.body) {
|
|
2226
|
-
if (Array.isArray(node.body)) {
|
|
2227
|
-
node.body.forEach((s: any) => traverse(s))
|
|
2228
|
-
} else {
|
|
2229
|
-
traverse(node.body)
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
if (node.consequent) {
|
|
2233
|
-
traverse(node.consequent)
|
|
2234
|
-
}
|
|
2235
|
-
if (node.alternate) {
|
|
2236
|
-
traverse(node.alternate)
|
|
2237
|
-
}
|
|
2238
|
-
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2239
|
-
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
2240
|
-
// expression (or in a ternary's `test`), which is not a child, a body or
|
|
2241
|
-
// an argument. Left unvisited, the products list under the first tab of
|
|
2242
|
-
// a shop was never paginated or seeded, and rendered nothing.
|
|
2243
|
-
if (node.left) {
|
|
2244
|
-
traverse(node.left)
|
|
2245
|
-
}
|
|
2246
|
-
if (node.right) {
|
|
2247
|
-
traverse(node.right)
|
|
2248
|
-
}
|
|
2249
|
-
if (node.test) {
|
|
2250
|
-
traverse(node.test)
|
|
2251
|
-
}
|
|
2252
|
-
if (node.expression) {
|
|
2253
|
-
traverse(node.expression)
|
|
2254
|
-
}
|
|
2255
|
-
if (node.argument) {
|
|
2256
|
-
traverse(node.argument)
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
traverse(blockStatement)
|
|
2261
|
-
return results
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
2336
|
/**
|
|
2265
2337
|
* Every pagination widget in the JSX, paired with the mapper it belongs to.
|
|
2266
2338
|
*
|
|
@@ -2343,10 +2415,12 @@ function findPaginationNodesWithOwners(
|
|
|
2343
2415
|
*
|
|
2344
2416
|
* An uncached mapper keeps the inline `params={useMemo(...)}` it has always had,
|
|
2345
2417
|
* byte for byte. A cached one hoists that memo into a component const so the
|
|
2346
|
-
* peek can depend on the same object identity instead of rebuilding it
|
|
2418
|
+
* peek can depend on the same object identity instead of rebuilding it; so does
|
|
2419
|
+
* an appending list that starts from its page's rows (`hoistInto`), whose first
|
|
2420
|
+
* page is filed in the accumulator under those params.
|
|
2347
2421
|
*
|
|
2348
|
-
* Returns the identifier holding the
|
|
2349
|
-
*
|
|
2422
|
+
* Returns the identifier holding the params when they were hoisted, and the one
|
|
2423
|
+
* holding the peeked rows when this mapper caches.
|
|
2350
2424
|
*/
|
|
2351
2425
|
function pushParamsAttribute(
|
|
2352
2426
|
// tslint:disable-next-line:no-any
|
|
@@ -2354,24 +2428,40 @@ function pushParamsAttribute(
|
|
|
2354
2428
|
usage: DataSourceUsage,
|
|
2355
2429
|
paramsProps: types.ObjectProperty[],
|
|
2356
2430
|
memoDeps: types.Expression[],
|
|
2357
|
-
cacheDeclarations: types.Statement[]
|
|
2358
|
-
|
|
2431
|
+
cacheDeclarations: types.Statement[],
|
|
2432
|
+
hoistInto?: types.Statement[]
|
|
2433
|
+
): { paramsIdentifier?: string; cachedIdentifier?: string } {
|
|
2359
2434
|
const paramsMemo = types.callExpression(types.identifier('useMemo'), [
|
|
2360
2435
|
types.arrowFunctionExpression([], types.objectExpression(paramsProps)),
|
|
2361
2436
|
types.arrayExpression(memoDeps),
|
|
2362
2437
|
])
|
|
2363
2438
|
|
|
2364
|
-
// A state-bound sort
|
|
2365
|
-
//
|
|
2366
|
-
//
|
|
2367
|
-
//
|
|
2439
|
+
// A state-bound sort gets no cache peek. The peek is `initialData` by another
|
|
2440
|
+
// name, and such a list takes the server's rows only while its sort is still
|
|
2441
|
+
// where the visitor starts (`buildStartingSortGuard`); the peek was never
|
|
2442
|
+
// extended to it.
|
|
2368
2443
|
const cache = usage.cache?.client && !usage.dynamicSort ? usage.cache : undefined
|
|
2369
2444
|
|
|
2370
2445
|
if (!cache) {
|
|
2446
|
+
if (!hoistInto) {
|
|
2447
|
+
dp.openingElement.attributes.push(
|
|
2448
|
+
types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(paramsMemo))
|
|
2449
|
+
)
|
|
2450
|
+
return {}
|
|
2451
|
+
}
|
|
2452
|
+
const paramsIdentifier = paramsVarFor(usage.index)
|
|
2453
|
+
hoistInto.push(
|
|
2454
|
+
types.variableDeclaration('const', [
|
|
2455
|
+
types.variableDeclarator(types.identifier(paramsIdentifier), paramsMemo),
|
|
2456
|
+
])
|
|
2457
|
+
)
|
|
2371
2458
|
dp.openingElement.attributes.push(
|
|
2372
|
-
types.jsxAttribute(
|
|
2459
|
+
types.jsxAttribute(
|
|
2460
|
+
types.jsxIdentifier('params'),
|
|
2461
|
+
types.jsxExpressionContainer(types.identifier(paramsIdentifier))
|
|
2462
|
+
)
|
|
2373
2463
|
)
|
|
2374
|
-
return
|
|
2464
|
+
return { paramsIdentifier }
|
|
2375
2465
|
}
|
|
2376
2466
|
|
|
2377
2467
|
const hoisted = buildCachedParamsDeclarations({ index: usage.index, paramsMemo, cache })
|
|
@@ -2384,7 +2474,97 @@ function pushParamsAttribute(
|
|
|
2384
2474
|
)
|
|
2385
2475
|
)
|
|
2386
2476
|
|
|
2387
|
-
return hoisted.cachedIdentifier
|
|
2477
|
+
return { paramsIdentifier: hoisted.paramsIdentifier, cachedIdentifier: hoisted.cachedIdentifier }
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// Whether the rows `getStaticProps` fetched can be this list's first page at
|
|
2481
|
+
// all. A sort bound to state can only be served them when the build knows the
|
|
2482
|
+
// sort the visitor starts with (`startingSort`) and fetched by it; otherwise
|
|
2483
|
+
// the fetch has no sort, and its rows are not the list's first page.
|
|
2484
|
+
function prefetchFollowsSort(usage: DataSourceUsage): boolean {
|
|
2485
|
+
return !usage.dynamicSort || !!usage.startingSort
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
// `sortBy === "name-asc"`: the visitor has not moved the sort from where it
|
|
2489
|
+
// starts, so the build-time rows are still its first page. `null` for a list
|
|
2490
|
+
// whose sort the visitor cannot pick, or one that reads no state.
|
|
2491
|
+
function buildStartingSortGuard(usage: DataSourceUsage): types.Expression | null {
|
|
2492
|
+
return usage.dynamicSort && usage.startingSort ? buildSortAtStartGuard(usage.startingSort) : null
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// `<condition> && <starting-sort guard>`, or the condition alone.
|
|
2496
|
+
function withStartingSortGuard(
|
|
2497
|
+
condition: types.Expression,
|
|
2498
|
+
usage: DataSourceUsage
|
|
2499
|
+
): types.Expression {
|
|
2500
|
+
const sortGuard = buildStartingSortGuard(usage)
|
|
2501
|
+
return sortGuard ? types.logicalExpression('&&', condition, sortGuard) : condition
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// An appending list on a page starts from the rows its `getStaticProps`
|
|
2505
|
+
// fetched, when they can be its first page (`prefetchFollowsSort`).
|
|
2506
|
+
function startsFromServerRows(usage: DataSourceUsage): boolean {
|
|
2507
|
+
return usage.infiniteScroll && usage.paginated && prefetchFollowsSort(usage)
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
/**
|
|
2511
|
+
* `initialData={ds_N_firstPage}` on an appending list, beside the frozen first
|
|
2512
|
+
* page and the effect that files it as page 1 of the accumulator (see
|
|
2513
|
+
* `infinite-scroll.ts`).
|
|
2514
|
+
*
|
|
2515
|
+
* The rows stand in for page 1 only while they answer the list's query: no
|
|
2516
|
+
* search (`queryGuard`), the sort where the visitor starts, no filter set (the
|
|
2517
|
+
* guards the paged lists use), and at least one row — an empty build-time
|
|
2518
|
+
* answer, which a failed fetch also gives, leaves the browser to fetch page 1
|
|
2519
|
+
* as before.
|
|
2520
|
+
*/
|
|
2521
|
+
function pushInfiniteScrollFirstPage(
|
|
2522
|
+
// tslint:disable-next-line:no-any
|
|
2523
|
+
dp: any,
|
|
2524
|
+
usage: DataSourceUsage,
|
|
2525
|
+
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2526
|
+
paramsIdentifier: string,
|
|
2527
|
+
declarations: types.Statement[],
|
|
2528
|
+
queryGuard: types.Expression | null,
|
|
2529
|
+
instanceDeps: types.Expression[]
|
|
2530
|
+
): void {
|
|
2531
|
+
const infiniteVars = getInfiniteScrollVars(usage.index)
|
|
2532
|
+
const serverPayload = (): types.Expression =>
|
|
2533
|
+
types.optionalMemberExpression(
|
|
2534
|
+
types.identifier('props'),
|
|
2535
|
+
types.identifier(vars.propsPrefix),
|
|
2536
|
+
false,
|
|
2537
|
+
true
|
|
2538
|
+
)
|
|
2539
|
+
const hasServerRows = types.binaryExpression(
|
|
2540
|
+
'>',
|
|
2541
|
+
types.optionalMemberExpression(
|
|
2542
|
+
buildServerRowsExpression(serverPayload(), usage.itemsPath),
|
|
2543
|
+
types.identifier('length'),
|
|
2544
|
+
false,
|
|
2545
|
+
true
|
|
2546
|
+
),
|
|
2547
|
+
types.numericLiteral(0)
|
|
2548
|
+
)
|
|
2549
|
+
const startsFromServer = [
|
|
2550
|
+
queryGuard,
|
|
2551
|
+
buildStartingSortGuard(usage),
|
|
2552
|
+
buildNoUrlFilterGuard(usage),
|
|
2553
|
+
hasServerRows,
|
|
2554
|
+
]
|
|
2555
|
+
.filter((guard): guard is types.Expression => guard !== null)
|
|
2556
|
+
.reduce((all, guard) => types.logicalExpression('&&', all, guard))
|
|
2557
|
+
|
|
2558
|
+
declarations.push(
|
|
2559
|
+
buildFirstPageDeclaration(infiniteVars, startsFromServer, serverPayload(), instanceDeps),
|
|
2560
|
+
buildFirstPageSeedEffect(infiniteVars, paramsIdentifier, usage.perPage, usage.itemsPath)
|
|
2561
|
+
)
|
|
2562
|
+
dp.openingElement.attributes.push(
|
|
2563
|
+
types.jsxAttribute(
|
|
2564
|
+
types.jsxIdentifier('initialData'),
|
|
2565
|
+
types.jsxExpressionContainer(types.identifier(infiniteVars.firstPageVar))
|
|
2566
|
+
)
|
|
2567
|
+
)
|
|
2388
2568
|
}
|
|
2389
2569
|
|
|
2390
2570
|
function updateDataProviderForPaginatedSearch(
|
|
@@ -2393,7 +2573,8 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2393
2573
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2394
2574
|
fileName: string,
|
|
2395
2575
|
cacheDeclarations: types.Statement[],
|
|
2396
|
-
localized: boolean
|
|
2576
|
+
localized: boolean,
|
|
2577
|
+
firstPageDeclarations?: types.Statement[]
|
|
2397
2578
|
): void {
|
|
2398
2579
|
const attrs = dp.openingElement.attributes
|
|
2399
2580
|
|
|
@@ -2452,36 +2633,67 @@ function updateDataProviderForPaginatedSearch(
|
|
|
2452
2633
|
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2453
2634
|
}
|
|
2454
2635
|
|
|
2455
|
-
const
|
|
2636
|
+
const seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined
|
|
2637
|
+
const { paramsIdentifier, cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2638
|
+
dp,
|
|
2639
|
+
usage,
|
|
2640
|
+
paramsProps,
|
|
2641
|
+
memoDeps,
|
|
2642
|
+
cacheDeclarations,
|
|
2643
|
+
seedInto
|
|
2644
|
+
)
|
|
2456
2645
|
|
|
2457
|
-
// Add initialData
|
|
2458
|
-
//
|
|
2459
|
-
//
|
|
2460
|
-
//
|
|
2461
|
-
//
|
|
2462
|
-
//
|
|
2646
|
+
// Add initialData: the server-prefetched page 1 while it is the page on show
|
|
2647
|
+
// — page 1, no search, no filter set, and a sort the visitor picks still where
|
|
2648
|
+
// they start (the prefetch ran by that sort). A list whose starting sort the
|
|
2649
|
+
// build cannot know never takes it: that prefetch ran without a sort, so
|
|
2650
|
+
// reusing it would mask the current sort AND make DataProvider skip its first
|
|
2651
|
+
// client fetch (its internal guard only skips when initialData is defined on
|
|
2652
|
+
// mount). Any other sort is fetched fresh, through the useMemo params.
|
|
2463
2653
|
//
|
|
2464
|
-
// An appending list
|
|
2465
|
-
//
|
|
2466
|
-
//
|
|
2467
|
-
//
|
|
2468
|
-
|
|
2654
|
+
// An appending list takes its own: `initialData` makes DataProvider skip its
|
|
2655
|
+
// first fetch, so the prefetched page 1 has to be filed in the accumulator
|
|
2656
|
+
// too, or appending page 2 would REPLACE the visible rows with page 2 alone.
|
|
2657
|
+
// The provider remounts per query (its `key`), so the first page is frozen
|
|
2658
|
+
// per query.
|
|
2659
|
+
if (seedInto && paramsIdentifier) {
|
|
2660
|
+
const appliedQuery = (): types.MemberExpression =>
|
|
2661
|
+
types.memberExpression(
|
|
2662
|
+
types.identifier(vars.combinedStateVar),
|
|
2663
|
+
types.identifier('debouncedQuery')
|
|
2664
|
+
)
|
|
2665
|
+
pushInfiniteScrollFirstPage(
|
|
2666
|
+
dp,
|
|
2667
|
+
usage,
|
|
2668
|
+
vars,
|
|
2669
|
+
paramsIdentifier,
|
|
2670
|
+
seedInto,
|
|
2671
|
+
types.unaryExpression('!', appliedQuery(), true),
|
|
2672
|
+
[appliedQuery()]
|
|
2673
|
+
)
|
|
2674
|
+
} else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
|
|
2469
2675
|
const initialDataCondition = wrapInitialDataWithUrlFilterGuard(
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
types.identifier(vars.combinedStateVar),
|
|
2481
|
-
types.identifier('debouncedQuery')
|
|
2676
|
+
withStartingSortGuard(
|
|
2677
|
+
types.logicalExpression(
|
|
2678
|
+
'&&',
|
|
2679
|
+
types.binaryExpression(
|
|
2680
|
+
'===',
|
|
2681
|
+
types.memberExpression(
|
|
2682
|
+
types.identifier(vars.combinedStateVar),
|
|
2683
|
+
types.identifier('page')
|
|
2684
|
+
),
|
|
2685
|
+
types.numericLiteral(1)
|
|
2482
2686
|
),
|
|
2483
|
-
|
|
2484
|
-
|
|
2687
|
+
types.unaryExpression(
|
|
2688
|
+
'!',
|
|
2689
|
+
types.memberExpression(
|
|
2690
|
+
types.identifier(vars.combinedStateVar),
|
|
2691
|
+
types.identifier('debouncedQuery')
|
|
2692
|
+
),
|
|
2693
|
+
true
|
|
2694
|
+
)
|
|
2695
|
+
),
|
|
2696
|
+
usage
|
|
2485
2697
|
),
|
|
2486
2698
|
usage
|
|
2487
2699
|
)
|
|
@@ -2575,7 +2787,8 @@ function updateDataProviderForPaginationOnly(
|
|
|
2575
2787
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
2576
2788
|
fileName: string,
|
|
2577
2789
|
cacheDeclarations: types.Statement[],
|
|
2578
|
-
localized: boolean
|
|
2790
|
+
localized: boolean,
|
|
2791
|
+
firstPageDeclarations?: types.Statement[]
|
|
2579
2792
|
): void {
|
|
2580
2793
|
const attrs = dp.openingElement.attributes
|
|
2581
2794
|
|
|
@@ -2613,22 +2826,36 @@ function updateDataProviderForPaginationOnly(
|
|
|
2613
2826
|
}
|
|
2614
2827
|
|
|
2615
2828
|
// Add params
|
|
2616
|
-
const
|
|
2829
|
+
const seedInto = startsFromServerRows(usage) ? firstPageDeclarations : undefined
|
|
2830
|
+
const { paramsIdentifier, cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2831
|
+
dp,
|
|
2832
|
+
usage,
|
|
2833
|
+
paramsProps,
|
|
2834
|
+
memoDeps,
|
|
2835
|
+
cacheDeclarations,
|
|
2836
|
+
seedInto
|
|
2837
|
+
)
|
|
2617
2838
|
|
|
2618
|
-
// Add initialData. See paginated+search updater for
|
|
2619
|
-
//
|
|
2620
|
-
//
|
|
2621
|
-
|
|
2839
|
+
// Add initialData. See paginated+search updater for when a sort the visitor
|
|
2840
|
+
// picks may reuse the prefetch, and how an appending list takes its own. This
|
|
2841
|
+
// provider never remounts (no `key`), so its first page is frozen for the
|
|
2842
|
+
// page's lifetime.
|
|
2843
|
+
if (seedInto && paramsIdentifier) {
|
|
2844
|
+
pushInfiniteScrollFirstPage(dp, usage, vars, paramsIdentifier, seedInto, null, [])
|
|
2845
|
+
} else if (prefetchFollowsSort(usage) && !usage.infiniteScroll) {
|
|
2622
2846
|
dp.openingElement.attributes.push(
|
|
2623
2847
|
types.jsxAttribute(
|
|
2624
2848
|
types.jsxIdentifier('initialData'),
|
|
2625
2849
|
types.jsxExpressionContainer(
|
|
2626
2850
|
types.conditionalExpression(
|
|
2627
2851
|
wrapInitialDataWithUrlFilterGuard(
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2852
|
+
withStartingSortGuard(
|
|
2853
|
+
types.binaryExpression(
|
|
2854
|
+
'===',
|
|
2855
|
+
types.identifier(vars.pageStateVar),
|
|
2856
|
+
types.numericLiteral(1)
|
|
2857
|
+
),
|
|
2858
|
+
usage
|
|
2632
2859
|
),
|
|
2633
2860
|
usage
|
|
2634
2861
|
),
|
|
@@ -2733,18 +2960,27 @@ function updateDataProviderForSearchOnly(
|
|
|
2733
2960
|
appendClientLocaleParam(paramsProps, memoDeps)
|
|
2734
2961
|
}
|
|
2735
2962
|
|
|
2736
|
-
const cachedInitialData = pushParamsAttribute(
|
|
2963
|
+
const { cachedIdentifier: cachedInitialData } = pushParamsAttribute(
|
|
2964
|
+
dp,
|
|
2965
|
+
usage,
|
|
2966
|
+
paramsProps,
|
|
2967
|
+
memoDeps,
|
|
2968
|
+
cacheDeclarations
|
|
2969
|
+
)
|
|
2737
2970
|
|
|
2738
|
-
// Add initialData. See paginated+search updater for
|
|
2739
|
-
//
|
|
2740
|
-
if (
|
|
2971
|
+
// Add initialData. See paginated+search updater for when a sort the visitor
|
|
2972
|
+
// picks may reuse the prefetch.
|
|
2973
|
+
if (prefetchFollowsSort(usage)) {
|
|
2741
2974
|
dp.openingElement.attributes.push(
|
|
2742
2975
|
types.jsxAttribute(
|
|
2743
2976
|
types.jsxIdentifier('initialData'),
|
|
2744
2977
|
types.jsxExpressionContainer(
|
|
2745
2978
|
types.conditionalExpression(
|
|
2746
2979
|
wrapInitialDataWithUrlFilterGuard(
|
|
2747
|
-
|
|
2980
|
+
withStartingSortGuard(
|
|
2981
|
+
types.unaryExpression('!', types.identifier(vars.debouncedSearchQueryVar), true),
|
|
2982
|
+
usage
|
|
2983
|
+
),
|
|
2748
2984
|
usage
|
|
2749
2985
|
),
|
|
2750
2986
|
types.optionalMemberExpression(
|
|
@@ -2789,20 +3025,58 @@ function updateDataProviderForSearchOnly(
|
|
|
2789
3025
|
)
|
|
2790
3026
|
}
|
|
2791
3027
|
|
|
3028
|
+
/**
|
|
3029
|
+
* A plain list on a page is seeded with the rows its `getStaticProps` fetched
|
|
3030
|
+
* (`extractDataSourceIntoGetStaticProps`), and a seeded provider skips its first
|
|
3031
|
+
* fetch. With a sort the visitor picks, those rows are the list's only while the
|
|
3032
|
+
* sort is where a visitor starts — the build fetched them by it:
|
|
3033
|
+
*
|
|
3034
|
+
* initialData={sortBy === "name-asc" ? props.items_products : undefined}
|
|
3035
|
+
*
|
|
3036
|
+
* — and any other sort is fetched fresh, through the params it changes. When the
|
|
3037
|
+
* build cannot know where the sort starts (`prefetchFollowsSort`), it fetched
|
|
3038
|
+
* the rows unsorted, and the list is never seeded with them. A plain list has no
|
|
3039
|
+
* search, and is only seeded without a filter a visitor sets.
|
|
3040
|
+
*/
|
|
3041
|
+
function seedPlainListWhileSortStarts(dp: any, usage: DataSourceUsage): void {
|
|
3042
|
+
if (!usage.dynamicSort) {
|
|
3043
|
+
return
|
|
3044
|
+
}
|
|
3045
|
+
const attrs = dp.openingElement.attributes
|
|
3046
|
+
const index = attrs.findIndex(
|
|
3047
|
+
(attr: any) => attr.type === 'JSXAttribute' && attr.name?.name === 'initialData'
|
|
3048
|
+
)
|
|
3049
|
+
if (index === -1) {
|
|
3050
|
+
return
|
|
3051
|
+
}
|
|
3052
|
+
if (!prefetchFollowsSort(usage)) {
|
|
3053
|
+
attrs.splice(index, 1)
|
|
3054
|
+
return
|
|
3055
|
+
}
|
|
3056
|
+
const sortGuard = buildStartingSortGuard(usage)
|
|
3057
|
+
const seeded = (attrs[index] as types.JSXAttribute).value
|
|
3058
|
+
if (!sortGuard || seeded?.type !== 'JSXExpressionContainer') {
|
|
3059
|
+
return
|
|
3060
|
+
}
|
|
3061
|
+
attrs[index] = types.jsxAttribute(
|
|
3062
|
+
types.jsxIdentifier('initialData'),
|
|
3063
|
+
types.jsxExpressionContainer(
|
|
3064
|
+
types.conditionalExpression(
|
|
3065
|
+
sortGuard,
|
|
3066
|
+
seeded.expression as types.Expression,
|
|
3067
|
+
types.identifier('undefined')
|
|
3068
|
+
)
|
|
3069
|
+
)
|
|
3070
|
+
)
|
|
3071
|
+
}
|
|
3072
|
+
|
|
2792
3073
|
function updateDataProviderForPlain(
|
|
2793
3074
|
dp: any,
|
|
2794
3075
|
fileName: string,
|
|
2795
3076
|
usage: DataSourceUsage,
|
|
2796
3077
|
localized: boolean
|
|
2797
3078
|
): void {
|
|
2798
|
-
|
|
2799
|
-
// pre-rendered in the table's order would stand in for it, since a seeded
|
|
2800
|
-
// provider skips its first fetch — the paginated lists drop them the same way.
|
|
2801
|
-
if (usage.dynamicSort) {
|
|
2802
|
-
dp.openingElement.attributes = dp.openingElement.attributes.filter(
|
|
2803
|
-
(attr: any) => !(attr.type === 'JSXAttribute' && attr.name?.name === 'initialData')
|
|
2804
|
-
)
|
|
2805
|
-
}
|
|
3079
|
+
seedPlainListWhileSortStarts(dp, usage)
|
|
2806
3080
|
|
|
2807
3081
|
const attrs = dp.openingElement.attributes
|
|
2808
3082
|
|
|
@@ -3057,11 +3331,21 @@ function createFetchDataAttribute(
|
|
|
3057
3331
|
[responseTail]
|
|
3058
3332
|
)
|
|
3059
3333
|
|
|
3334
|
+
// An appending list records the request it now stands on before making it
|
|
3335
|
+
// (see `buildAccumulatingRequestMark`).
|
|
3060
3336
|
const arrow = cache
|
|
3061
3337
|
? types.arrowFunctionExpression(
|
|
3062
3338
|
[types.identifier('params')],
|
|
3063
3339
|
buildCachedFetchBody(networkChain, cache)
|
|
3064
3340
|
)
|
|
3341
|
+
: accumulate
|
|
3342
|
+
? types.arrowFunctionExpression(
|
|
3343
|
+
[types.identifier('params')],
|
|
3344
|
+
types.blockStatement([
|
|
3345
|
+
buildAccumulatingRequestMark(getInfiniteScrollVars(accumulate.usage.index)),
|
|
3346
|
+
types.returnStatement(networkChain),
|
|
3347
|
+
])
|
|
3348
|
+
)
|
|
3065
3349
|
: types.arrowFunctionExpression([types.identifier('params')], networkChain)
|
|
3066
3350
|
|
|
3067
3351
|
return types.jsxAttribute(
|
|
@@ -3072,39 +3356,6 @@ function createFetchDataAttribute(
|
|
|
3072
3356
|
)
|
|
3073
3357
|
}
|
|
3074
3358
|
|
|
3075
|
-
function wireSearchInput(inputNode: any, vars: ReturnType<typeof getStateVarsForUsage>): void {
|
|
3076
|
-
// Remove existing onChange and value
|
|
3077
|
-
inputNode.openingElement.attributes = inputNode.openingElement.attributes.filter(
|
|
3078
|
-
(attr: any) => !['onChange', 'value'].includes(attr.name?.name)
|
|
3079
|
-
)
|
|
3080
|
-
|
|
3081
|
-
// Add onChange
|
|
3082
|
-
inputNode.openingElement.attributes.push(
|
|
3083
|
-
types.jsxAttribute(
|
|
3084
|
-
types.jsxIdentifier('onChange'),
|
|
3085
|
-
types.jsxExpressionContainer(
|
|
3086
|
-
types.arrowFunctionExpression(
|
|
3087
|
-
[types.identifier('e')],
|
|
3088
|
-
types.callExpression(types.identifier(vars.setSearchQueryVar), [
|
|
3089
|
-
types.memberExpression(
|
|
3090
|
-
types.memberExpression(types.identifier('e'), types.identifier('target')),
|
|
3091
|
-
types.identifier('value')
|
|
3092
|
-
),
|
|
3093
|
-
])
|
|
3094
|
-
)
|
|
3095
|
-
)
|
|
3096
|
-
)
|
|
3097
|
-
)
|
|
3098
|
-
|
|
3099
|
-
// Add value
|
|
3100
|
-
inputNode.openingElement.attributes.push(
|
|
3101
|
-
types.jsxAttribute(
|
|
3102
|
-
types.jsxIdentifier('value'),
|
|
3103
|
-
types.jsxExpressionContainer(types.identifier(vars.searchQueryVar))
|
|
3104
|
-
)
|
|
3105
|
-
)
|
|
3106
|
-
}
|
|
3107
|
-
|
|
3108
3359
|
/**
|
|
3109
3360
|
* The `fetchData` attribute for one paginated usage.
|
|
3110
3361
|
*
|
|
@@ -3120,6 +3371,41 @@ function buildFetchDataForUsage(fileName: string, usage: DataSourceUsage): types
|
|
|
3120
3371
|
return createFetchDataAttribute(fileName, usage.cache?.client ? usage.cache : undefined)
|
|
3121
3372
|
}
|
|
3122
3373
|
|
|
3374
|
+
/** `const [ds_N_total, setDs_N_total] = useState(null)` — declared only beside a range control. */
|
|
3375
|
+
function buildTotalDeclaration(vars: ReturnType<typeof getStateVarsForUsage>): types.Statement {
|
|
3376
|
+
return types.variableDeclaration('const', [
|
|
3377
|
+
types.variableDeclarator(
|
|
3378
|
+
types.arrayPattern([
|
|
3379
|
+
types.identifier(vars.totalStateVar),
|
|
3380
|
+
types.identifier(vars.setTotalStateVar),
|
|
3381
|
+
]),
|
|
3382
|
+
types.callExpression(types.identifier('useState'), [types.nullLiteral()])
|
|
3383
|
+
),
|
|
3384
|
+
])
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
/**
|
|
3388
|
+
* Fills one `range` text with the live row range of `usage` (see
|
|
3389
|
+
* `range-control.ts`). A mapper that does not page has no count to show, so
|
|
3390
|
+
* its range text is left as drawn.
|
|
3391
|
+
*/
|
|
3392
|
+
function wireRangeControlFor(
|
|
3393
|
+
control: any,
|
|
3394
|
+
usage: DataSourceUsage,
|
|
3395
|
+
vars: ReturnType<typeof getStateVarsForUsage>
|
|
3396
|
+
): void {
|
|
3397
|
+
if (!usage.paginated) {
|
|
3398
|
+
return
|
|
3399
|
+
}
|
|
3400
|
+
const { currentPageExpr } = getPageAccessors(usage, vars)
|
|
3401
|
+
wireRangeControl(control, {
|
|
3402
|
+
totalVar: vars.totalStateVar,
|
|
3403
|
+
currentPageExpr: currentPageExpr(),
|
|
3404
|
+
perPage: usage.perPage,
|
|
3405
|
+
infiniteScroll: usage.infiniteScroll,
|
|
3406
|
+
})
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3123
3409
|
/**
|
|
3124
3410
|
* Wires the authored Load More button of an appending list.
|
|
3125
3411
|
*
|
|
@@ -3185,6 +3471,12 @@ function wirePaginationWidget(
|
|
|
3185
3471
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
3186
3472
|
hasLoadingState: boolean
|
|
3187
3473
|
): void {
|
|
3474
|
+
// A range text inside the container takes the container's owner, like the
|
|
3475
|
+
// buttons beside it; one naming its own mapper is wired in STEP 5b instead.
|
|
3476
|
+
findAllPaginationControls(paginationNode, 'range')
|
|
3477
|
+
.filter((control) => !readJSXAttributeString(control, PAGINATION_FOR_ATTR))
|
|
3478
|
+
.forEach((control) => wireRangeControlFor(control, usage, vars))
|
|
3479
|
+
|
|
3188
3480
|
if (usage.infiniteScroll) {
|
|
3189
3481
|
// Only the Load More button survives; page controls make no sense for a
|
|
3190
3482
|
// list the visitor never leaves.
|
|
@@ -3599,12 +3891,24 @@ export default dataSourceModule.getCount
|
|
|
3599
3891
|
}
|
|
3600
3892
|
}
|
|
3601
3893
|
|
|
3894
|
+
// `fetchCount(<params>, <read arguments>)`: the params may be left out only
|
|
3895
|
+
// when nothing follows them.
|
|
3896
|
+
const countReadArguments = (
|
|
3897
|
+
countParams: types.ObjectProperty[],
|
|
3898
|
+
readArguments: types.Expression[]
|
|
3899
|
+
): types.Expression[] =>
|
|
3900
|
+
countParams.length > 0 || readArguments.length > 0
|
|
3901
|
+
? [types.objectExpression(countParams), ...readArguments]
|
|
3902
|
+
: []
|
|
3903
|
+
|
|
3602
3904
|
function updateGetStaticProps(
|
|
3603
3905
|
chunks: any[],
|
|
3604
3906
|
registry: StateRegistry,
|
|
3605
3907
|
dependencies: Record<string, any>,
|
|
3606
3908
|
folderPath: string[] | undefined,
|
|
3607
|
-
localized: boolean
|
|
3909
|
+
localized: boolean,
|
|
3910
|
+
// The arguments every read hands the module after its params (fresh nodes per call).
|
|
3911
|
+
readArguments: () => types.Expression[]
|
|
3608
3912
|
): void {
|
|
3609
3913
|
const getStaticPropsChunk = chunks.find((c) => c.name === 'getStaticProps')
|
|
3610
3914
|
if (!getStaticPropsChunk || getStaticPropsChunk.type !== ChunkType.AST) {
|
|
@@ -3736,6 +4040,16 @@ function updateGetStaticProps(
|
|
|
3736
4040
|
)
|
|
3737
4041
|
)
|
|
3738
4042
|
)
|
|
4043
|
+
} else if (usage.dynamicSort && usage.startingSort) {
|
|
4044
|
+
// A sort the visitor picks: page 1 by the sort they start with, sent
|
|
4045
|
+
// as the very string the browser sends for it, so page 2 continues
|
|
4046
|
+
// this page exactly (see `buildStartingSortsValue`).
|
|
4047
|
+
fetchParams.push(
|
|
4048
|
+
types.objectProperty(
|
|
4049
|
+
types.identifier('sorts'),
|
|
4050
|
+
buildStartingSortsValue(usage.dynamicSort, usage.startingSort)
|
|
4051
|
+
)
|
|
4052
|
+
)
|
|
3739
4053
|
}
|
|
3740
4054
|
|
|
3741
4055
|
// Add filters if present
|
|
@@ -3773,7 +4087,7 @@ function updateGetStaticProps(
|
|
|
3773
4087
|
types.identifier(fetcherImportName),
|
|
3774
4088
|
types.identifier('fetchData')
|
|
3775
4089
|
),
|
|
3776
|
-
[types.objectExpression(fetchParams)]
|
|
4090
|
+
[types.objectExpression(fetchParams), ...readArguments()]
|
|
3777
4091
|
),
|
|
3778
4092
|
types.identifier('catch')
|
|
3779
4093
|
),
|
|
@@ -3962,7 +4276,7 @@ function updateGetStaticProps(
|
|
|
3962
4276
|
types.identifier(fetcherImportName),
|
|
3963
4277
|
types.identifier('fetchCount')
|
|
3964
4278
|
),
|
|
3965
|
-
countParams
|
|
4279
|
+
countReadArguments(countParams, readArguments())
|
|
3966
4280
|
),
|
|
3967
4281
|
types.identifier('catch')
|
|
3968
4282
|
),
|