@teleporthq/teleport-plugin-next-data-source 0.43.75 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/_helpers/pagination-widget-fixture.ts +87 -4
- package/__tests__/booking-item-transform.test.ts +120 -0
- package/__tests__/browser-row-policy.test.ts +8 -2
- package/__tests__/fetchers.test.ts +1 -1
- package/__tests__/fixed-list-sort.test.ts +1 -1
- package/__tests__/infinite-scroll.test.ts +154 -5
- package/__tests__/list-sort-order.test.ts +61 -0
- package/__tests__/null-filter-where.test.ts +132 -0
- package/__tests__/pg-date-parser.test.ts +73 -0
- package/__tests__/plain-list-sort.test.ts +1 -1
- package/__tests__/product-price-sort.ts +11 -6
- package/__tests__/range-control.test.ts +159 -0
- package/__tests__/search-input-marker.test.ts +86 -0
- package/__tests__/search-url-sync.test.ts +34 -8
- package/__tests__/table-read-route-protected-tables.test.ts +33 -3
- 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/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/infinite-scroll.d.ts +25 -2
- package/dist/cjs/infinite-scroll.d.ts.map +1 -1
- package/dist/cjs/infinite-scroll.js +144 -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/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 +137 -101
- 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/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/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/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/infinite-scroll.d.ts +25 -2
- package/dist/esm/infinite-scroll.d.ts.map +1 -1
- package/dist/esm/infinite-scroll.js +142 -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/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 +138 -102
- 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/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/package.json +5 -5
- package/src/count-effect.ts +31 -8
- 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 +11 -8
- package/src/infinite-scroll.ts +240 -79
- package/src/list-sort-order.ts +34 -0
- package/src/pagination-controls.ts +26 -3
- package/src/pagination-plugin.ts +163 -135
- package/src/range-control.ts +207 -0
- package/src/row-order.ts +5 -4
- package/src/search-input.ts +190 -0
- package/src/transformations/booking-item.ts +71 -0
- package/src/transformations/index.ts +25 -2
package/src/pagination-plugin.ts
CHANGED
|
@@ -37,10 +37,15 @@ import {
|
|
|
37
37
|
import {
|
|
38
38
|
convertControlToButton,
|
|
39
39
|
dropInactivePaginationControls,
|
|
40
|
+
findAllPaginationControls,
|
|
40
41
|
findPaginationControl,
|
|
42
|
+
PAGINATION_FOR_ATTR,
|
|
43
|
+
readJSXAttributeString,
|
|
41
44
|
setJSXExpressionAttribute,
|
|
42
45
|
type PaginationControlKind,
|
|
43
46
|
} from './pagination-controls'
|
|
47
|
+
import { findRangeControls, wireRangeControl } from './range-control'
|
|
48
|
+
import { findAllSearchInputsInJSX, pairSearchInputs, wireSearchInput } from './search-input'
|
|
44
49
|
import { buildCountFetchEffect, buildCountSeqDeclaration } from './count-effect'
|
|
45
50
|
import {
|
|
46
51
|
findAllSearchClearButtonsInJSX,
|
|
@@ -53,6 +58,7 @@ import {
|
|
|
53
58
|
type NumberedPaginationVars,
|
|
54
59
|
} from './numbered-pagination'
|
|
55
60
|
import {
|
|
61
|
+
buildAccumulatingRequestMark,
|
|
56
62
|
buildAccumulatingResponseHandler,
|
|
57
63
|
buildInfiniteScrollDeclarations,
|
|
58
64
|
buildSentinelElement,
|
|
@@ -154,6 +160,10 @@ interface DataSourceUsage {
|
|
|
154
160
|
dataSourceIdentifier: string
|
|
155
161
|
// The cms-list-repeater renderPropIdentifier (e.g., "context_1i871")
|
|
156
162
|
arrayMapperRenderProp: string
|
|
163
|
+
// The array mapper's editor node id (`nodeId` on the cms-list-repeater), which
|
|
164
|
+
// a `range` control placed outside the pagination container names in its
|
|
165
|
+
// `data-tq-pagination-for` attribute. Absent from UIDL exported before it.
|
|
166
|
+
nodeId?: string
|
|
157
167
|
// Resource definition from UIDL
|
|
158
168
|
resourceDefinition: {
|
|
159
169
|
dataSourceId: string
|
|
@@ -603,6 +613,7 @@ function buildStateRegistry(uidlNode: any): StateRegistry {
|
|
|
603
613
|
index: index++,
|
|
604
614
|
dataSourceIdentifier: parentDataSource.identifier,
|
|
605
615
|
arrayMapperRenderProp,
|
|
616
|
+
nodeId: typeof content.nodeId === 'string' ? content.nodeId : undefined,
|
|
606
617
|
resourceDefinition: {
|
|
607
618
|
dataSourceId: parentDataSource.resourceDef.dataSourceId || '',
|
|
608
619
|
tableName: parentDataSource.resourceDef.tableName || '',
|
|
@@ -704,6 +715,9 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
704
715
|
setPageStateVar: string
|
|
705
716
|
maxPagesStateVar: string
|
|
706
717
|
setMaxPagesStateVar: string
|
|
718
|
+
/** The counted row total behind a `range` control; `null` until counted. */
|
|
719
|
+
totalStateVar: string
|
|
720
|
+
setTotalStateVar: string
|
|
707
721
|
searchQueryVar: string
|
|
708
722
|
setSearchQueryVar: string
|
|
709
723
|
debouncedSearchQueryVar: string
|
|
@@ -722,6 +736,8 @@ function getStateVarsForUsage(usage: DataSourceUsage): {
|
|
|
722
736
|
setPageStateVar: `setDs_${idx}_page`,
|
|
723
737
|
maxPagesStateVar: `ds_${idx}_maxPages`,
|
|
724
738
|
setMaxPagesStateVar: `setDs_${idx}_maxPages`,
|
|
739
|
+
totalStateVar: `ds_${idx}_total`,
|
|
740
|
+
setTotalStateVar: `setDs_${idx}_total`,
|
|
725
741
|
searchQueryVar: `ds_${idx}_searchQuery`,
|
|
726
742
|
setSearchQueryVar: `setDs_${idx}_searchQuery`,
|
|
727
743
|
debouncedSearchQueryVar: `ds_${idx}_debouncedQuery`,
|
|
@@ -829,7 +845,8 @@ function pushSearchUrlSyncEffects(
|
|
|
829
845
|
)
|
|
830
846
|
// read-back: `?<searchUrlParamKey>=` → the input AND the applied query at once
|
|
831
847
|
// (`ds_N_adoptUrlSearch`), so an address is not a typed change: it skips the
|
|
832
|
-
// debounce and keeps
|
|
848
|
+
// debounce and keeps a page the address carries — `?searchKeyword=x&page=3`
|
|
849
|
+
// lands on page 3.
|
|
833
850
|
effectStatements.push(
|
|
834
851
|
URLSearchParamSync.buildUrlReadBackEffect(
|
|
835
852
|
usage.searchUrlParamKey,
|
|
@@ -842,19 +859,26 @@ function pushSearchUrlSyncEffects(
|
|
|
842
859
|
|
|
843
860
|
// `const ds_N_adoptUrlSearch = (update) => { … }`: the setter the search's URL
|
|
844
861
|
// read-back calls with its `(prev) => next` updater. It moves the input and the
|
|
845
|
-
// applied (debounced) query together
|
|
846
|
-
// page
|
|
862
|
+
// applied (debounced) query together. A list whose page is in the address
|
|
863
|
+
// keeps its page — the page sync sets it from the same address. Any other
|
|
864
|
+
// list starts the new query on page 1, as a typed change does in the debounce
|
|
865
|
+
// effect: an appending list has usually advanced past page 1 by the time the
|
|
866
|
+
// router is ready, and the query's first answer would otherwise be its page 2
|
|
867
|
+
// — empty for a query only a few rows match.
|
|
847
868
|
function buildUrlSearchAdopter(
|
|
848
869
|
adopterVar: string,
|
|
849
870
|
usage: DataSourceUsage,
|
|
850
871
|
vars: ReturnType<typeof getStateVarsForUsage>
|
|
851
872
|
): types.VariableDeclaration {
|
|
852
873
|
const update = types.identifier('update')
|
|
874
|
+
const pageReset = usageHasPageUrlSync(usage)
|
|
875
|
+
? []
|
|
876
|
+
: [types.objectProperty(types.identifier('page'), types.numericLiteral(1))]
|
|
853
877
|
const applyToDebounced =
|
|
854
878
|
usage.category === 'paginated+search'
|
|
855
879
|
? // setDs_N_state((state) => {
|
|
856
880
|
// const next = update(state.debouncedQuery)
|
|
857
|
-
// return next === state.debouncedQuery ? state : { ...state, debouncedQuery: next }
|
|
881
|
+
// return next === state.debouncedQuery ? state : { ...state, page: 1, debouncedQuery: next }
|
|
858
882
|
// })
|
|
859
883
|
types.callExpression(types.identifier(vars.setCombinedStateVar), [
|
|
860
884
|
types.arrowFunctionExpression(
|
|
@@ -884,6 +908,7 @@ function buildUrlSearchAdopter(
|
|
|
884
908
|
types.identifier('state'),
|
|
885
909
|
types.objectExpression([
|
|
886
910
|
types.spreadElement(types.identifier('state')),
|
|
911
|
+
...pageReset,
|
|
887
912
|
types.objectProperty(
|
|
888
913
|
types.identifier('debouncedQuery'),
|
|
889
914
|
types.identifier('next')
|
|
@@ -1186,6 +1211,14 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1186
1211
|
}
|
|
1187
1212
|
const blockStatement = arrowFunction.body as types.BlockStatement
|
|
1188
1213
|
|
|
1214
|
+
// The "Showing 1–8 of 12" texts. Known up front because a component that
|
|
1215
|
+
// draws one needs the counted TOTAL in state — `ds_N_maxPages` alone cannot
|
|
1216
|
+
// say how many rows there are — and that state, with the live count that
|
|
1217
|
+
// fills it, is declared per usage below. A component without one emits
|
|
1218
|
+
// exactly what it always did.
|
|
1219
|
+
const rangeControls = findRangeControls(blockStatement)
|
|
1220
|
+
const hasRangeControl = rangeControls.length > 0
|
|
1221
|
+
|
|
1189
1222
|
// STEP 1: Build state registry from UIDL
|
|
1190
1223
|
const registry = buildStateRegistry(uidl.node)
|
|
1191
1224
|
|
|
@@ -1500,12 +1533,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1500
1533
|
pushPropIdsAsDeps(countEffectDeps, countEffectSeen, usage.filterPropIds)
|
|
1501
1534
|
// Same goes for URL-driven filters (already documented above).
|
|
1502
1535
|
pushUrlSearchParamMemoDeps(countEffectDeps, usage)
|
|
1536
|
+
if (hasRangeControl) {
|
|
1537
|
+
stateDeclarations.push(buildTotalDeclaration(vars))
|
|
1538
|
+
}
|
|
1503
1539
|
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1504
1540
|
effectStatements.push(
|
|
1505
1541
|
buildCountFetchEffect({
|
|
1506
1542
|
fileName,
|
|
1507
1543
|
perPage: usage.perPage,
|
|
1508
1544
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1545
|
+
setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
|
|
1509
1546
|
urlParams,
|
|
1510
1547
|
deps: countEffectDeps,
|
|
1511
1548
|
countSeqRefVar: vars.countSeqRefVar,
|
|
@@ -1572,8 +1609,10 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1572
1609
|
// "Next" button, but not to label a numbered strip or to know when an
|
|
1573
1610
|
// infinite list has reached its end, so those two modes fetch a live
|
|
1574
1611
|
// count as well.
|
|
1612
|
+
// A range text needs the live count too: the build-time count only
|
|
1613
|
+
// ever became a page count, never the total it shows.
|
|
1575
1614
|
const needsLiveCount =
|
|
1576
|
-
!isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll
|
|
1615
|
+
!isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll || hasRangeControl
|
|
1577
1616
|
if (needsLiveCount) {
|
|
1578
1617
|
const fileName = generateSafeFileName(
|
|
1579
1618
|
usage.resourceDefinition.dataSourceType,
|
|
@@ -1610,12 +1649,16 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1610
1649
|
pushPropIdsAsDeps(countDeps, countDepsSeen, usage.filterPropIds)
|
|
1611
1650
|
pushUrlSearchParamMemoDeps(countDeps, usage)
|
|
1612
1651
|
|
|
1652
|
+
if (hasRangeControl) {
|
|
1653
|
+
stateDeclarations.push(buildTotalDeclaration(vars))
|
|
1654
|
+
}
|
|
1613
1655
|
stateDeclarations.push(buildCountSeqDeclaration(vars.countSeqRefVar))
|
|
1614
1656
|
effectStatements.push(
|
|
1615
1657
|
buildCountFetchEffect({
|
|
1616
1658
|
fileName,
|
|
1617
1659
|
perPage: usage.perPage,
|
|
1618
1660
|
setMaxPagesVar: vars.setMaxPagesStateVar,
|
|
1661
|
+
setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
|
|
1619
1662
|
urlParams: countUrlParams,
|
|
1620
1663
|
deps: countDeps,
|
|
1621
1664
|
countSeqRefVar: vars.countSeqRefVar,
|
|
@@ -1868,10 +1911,8 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1868
1911
|
// An auto-loading list needs something at the end of the rows for its
|
|
1869
1912
|
// IntersectionObserver to watch.
|
|
1870
1913
|
if (usage.infiniteScroll && usage.paginated && !usage.infiniteScrollLoadMore) {
|
|
1871
|
-
insertSentinelAfterProvider(
|
|
1872
|
-
|
|
1873
|
-
dp,
|
|
1874
|
-
buildSentinelElement(getInfiniteScrollVars(usage.index))
|
|
1914
|
+
insertSentinelAfterProvider(blockStatement, dp, (parentTag) =>
|
|
1915
|
+
buildSentinelElement(getInfiniteScrollVars(usage.index), parentTag)
|
|
1875
1916
|
)
|
|
1876
1917
|
}
|
|
1877
1918
|
|
|
@@ -1940,25 +1981,17 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1940
1981
|
stabilizeDataProviderWithoutRepeater(dp)
|
|
1941
1982
|
})
|
|
1942
1983
|
|
|
1943
|
-
// STEP 4: Wire search inputs
|
|
1944
|
-
//
|
|
1984
|
+
// STEP 4: Wire search inputs — a platform input by its place among the
|
|
1985
|
+
// search-enabled mappers, a page's own marked input by the mapper it names
|
|
1986
|
+
// (`search-input.ts`).
|
|
1945
1987
|
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
|
|
1988
|
+
const usageBySearchInput = pairSearchInputs(searchInputs, registry.usages)
|
|
1989
|
+
for (const input of searchInputs) {
|
|
1990
|
+
const usage = usageBySearchInput.get(input.node)
|
|
1991
|
+
if (usage) {
|
|
1992
|
+
wireSearchInput(input, getStateVarsForUsage(usage))
|
|
1955
1993
|
}
|
|
1956
|
-
|
|
1957
|
-
const usage = searchEnabledUsages[idx]
|
|
1958
|
-
const vars = getStateVarsForUsage(usage)
|
|
1959
|
-
wireSearchInput(input.node, vars)
|
|
1960
|
-
usageBySearchInput.set(input.node, usage)
|
|
1961
|
-
})
|
|
1994
|
+
}
|
|
1962
1995
|
|
|
1963
1996
|
// STEP 4.5: Wire the clear buttons, each to the input it shares a parent with.
|
|
1964
1997
|
findAllSearchClearButtonsInJSX(blockStatement).forEach((button) => {
|
|
@@ -1997,6 +2030,25 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
1997
2030
|
}
|
|
1998
2031
|
)
|
|
1999
2032
|
|
|
2033
|
+
// STEP 5b: Wire the range texts that name their mapper.
|
|
2034
|
+
//
|
|
2035
|
+
// A `range` control carrying `data-tq-pagination-for` belongs to the mapper
|
|
2036
|
+
// whose editor node id it names, wherever it sits — a template's footer or
|
|
2037
|
+
// toolbar is laid out by the author, not inside a pagination container, so
|
|
2038
|
+
// the container-ownership rule above cannot reach it. One without the
|
|
2039
|
+
// attribute was wired by its container in STEP 5; one naming a mapper this
|
|
2040
|
+
// component does not render stays as drawn.
|
|
2041
|
+
rangeControls.forEach((control) => {
|
|
2042
|
+
const ownerNodeId = readJSXAttributeString(control, PAGINATION_FOR_ATTR)
|
|
2043
|
+
if (!ownerNodeId) {
|
|
2044
|
+
return
|
|
2045
|
+
}
|
|
2046
|
+
const owner = registry.usages.find((usage) => usage.nodeId === ownerNodeId)
|
|
2047
|
+
if (owner) {
|
|
2048
|
+
wireRangeControlFor(control, owner, getStateVarsForUsage(owner))
|
|
2049
|
+
}
|
|
2050
|
+
})
|
|
2051
|
+
|
|
2000
2052
|
// STEP 6: Update getStaticProps if this is a page
|
|
2001
2053
|
if (isPage) {
|
|
2002
2054
|
updateGetStaticProps(
|
|
@@ -2023,6 +2075,8 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
2023
2075
|
* It has to be a sibling: the provider's own child is a render-prop function,
|
|
2024
2076
|
* and anything placed inside it would be re-created on every fetch — the
|
|
2025
2077
|
* observer would then be watching an element that is no longer in the document.
|
|
2078
|
+
* The sentinel is built for the element it lands in (`buildSentinelElement`):
|
|
2079
|
+
* a fragment is transparent, a component's children may go anywhere.
|
|
2026
2080
|
*
|
|
2027
2081
|
* Returns false when the provider has no element parent to splice into (a
|
|
2028
2082
|
* provider returned directly from the component), leaving the list on its
|
|
@@ -2031,35 +2085,53 @@ export const createNextArrayMapperPaginationPlugin: ComponentPluginFactory<{}> =
|
|
|
2031
2085
|
function insertSentinelAfterProvider(
|
|
2032
2086
|
blockStatement: types.BlockStatement,
|
|
2033
2087
|
provider: any,
|
|
2034
|
-
|
|
2088
|
+
buildSentinel: (parentTag: string | null) => types.JSXElement
|
|
2035
2089
|
): boolean {
|
|
2036
2090
|
let inserted = false
|
|
2037
2091
|
|
|
2038
|
-
const
|
|
2092
|
+
const elementTagOf = (node: any, enclosing: string | null): string | null => {
|
|
2093
|
+
if (node?.type === 'JSXFragment') {
|
|
2094
|
+
return enclosing
|
|
2095
|
+
}
|
|
2096
|
+
if (node?.type !== 'JSXElement') {
|
|
2097
|
+
return enclosing
|
|
2098
|
+
}
|
|
2099
|
+
const name = node.openingElement?.name
|
|
2100
|
+
if (name?.type !== 'JSXIdentifier') {
|
|
2101
|
+
return null
|
|
2102
|
+
}
|
|
2103
|
+
if (name.name === 'Fragment') {
|
|
2104
|
+
return enclosing
|
|
2105
|
+
}
|
|
2106
|
+
return /^[a-z]/.test(name.name) ? name.name : null
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
const traverse = (node: any, enclosing: string | null): void => {
|
|
2039
2110
|
if (!node || inserted) {
|
|
2040
2111
|
return
|
|
2041
2112
|
}
|
|
2113
|
+
const tag = elementTagOf(node, enclosing)
|
|
2042
2114
|
if (Array.isArray(node.children)) {
|
|
2043
2115
|
const at = node.children.indexOf(provider)
|
|
2044
2116
|
if (at !== -1) {
|
|
2045
|
-
node.children.splice(at + 1, 0,
|
|
2117
|
+
node.children.splice(at + 1, 0, buildSentinel(tag))
|
|
2046
2118
|
inserted = true
|
|
2047
2119
|
return
|
|
2048
2120
|
}
|
|
2049
|
-
node.children.forEach(traverse)
|
|
2121
|
+
node.children.forEach((child: any) => traverse(child, tag))
|
|
2050
2122
|
}
|
|
2051
2123
|
if (node.body) {
|
|
2052
2124
|
if (Array.isArray(node.body)) {
|
|
2053
|
-
node.body.forEach(traverse)
|
|
2125
|
+
node.body.forEach((child: any) => traverse(child, tag))
|
|
2054
2126
|
} else {
|
|
2055
|
-
traverse(node.body)
|
|
2127
|
+
traverse(node.body, tag)
|
|
2056
2128
|
}
|
|
2057
2129
|
}
|
|
2058
2130
|
if (node.consequent) {
|
|
2059
|
-
traverse(node.consequent)
|
|
2131
|
+
traverse(node.consequent, tag)
|
|
2060
2132
|
}
|
|
2061
2133
|
if (node.alternate) {
|
|
2062
|
-
traverse(node.alternate)
|
|
2134
|
+
traverse(node.alternate, tag)
|
|
2063
2135
|
}
|
|
2064
2136
|
// `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
|
|
2065
2137
|
// panel, a gated section: the provider sits on the RIGHT of a logical
|
|
@@ -2067,23 +2139,23 @@ function insertSentinelAfterProvider(
|
|
|
2067
2139
|
// an argument. Left unvisited, the products list under the first tab of
|
|
2068
2140
|
// a shop was never paginated or seeded, and rendered nothing.
|
|
2069
2141
|
if (node.left) {
|
|
2070
|
-
traverse(node.left)
|
|
2142
|
+
traverse(node.left, tag)
|
|
2071
2143
|
}
|
|
2072
2144
|
if (node.right) {
|
|
2073
|
-
traverse(node.right)
|
|
2145
|
+
traverse(node.right, tag)
|
|
2074
2146
|
}
|
|
2075
2147
|
if (node.test) {
|
|
2076
|
-
traverse(node.test)
|
|
2148
|
+
traverse(node.test, tag)
|
|
2077
2149
|
}
|
|
2078
2150
|
if (node.expression) {
|
|
2079
|
-
traverse(node.expression)
|
|
2151
|
+
traverse(node.expression, tag)
|
|
2080
2152
|
}
|
|
2081
2153
|
if (node.argument) {
|
|
2082
|
-
traverse(node.argument)
|
|
2154
|
+
traverse(node.argument, tag)
|
|
2083
2155
|
}
|
|
2084
2156
|
}
|
|
2085
2157
|
|
|
2086
|
-
traverse(blockStatement)
|
|
2158
|
+
traverse(blockStatement, null)
|
|
2087
2159
|
return inserted
|
|
2088
2160
|
}
|
|
2089
2161
|
|
|
@@ -2199,68 +2271,6 @@ function findArrayMapperRenderPropInDataProvider(dataProvider: any): string | un
|
|
|
2199
2271
|
return renderItemAttr.value.expression.params[0].name
|
|
2200
2272
|
}
|
|
2201
2273
|
|
|
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
2274
|
/**
|
|
2265
2275
|
* Every pagination widget in the JSX, paired with the mapper it belongs to.
|
|
2266
2276
|
*
|
|
@@ -3057,11 +3067,21 @@ function createFetchDataAttribute(
|
|
|
3057
3067
|
[responseTail]
|
|
3058
3068
|
)
|
|
3059
3069
|
|
|
3070
|
+
// An appending list records the request it now stands on before making it
|
|
3071
|
+
// (see `buildAccumulatingRequestMark`).
|
|
3060
3072
|
const arrow = cache
|
|
3061
3073
|
? types.arrowFunctionExpression(
|
|
3062
3074
|
[types.identifier('params')],
|
|
3063
3075
|
buildCachedFetchBody(networkChain, cache)
|
|
3064
3076
|
)
|
|
3077
|
+
: accumulate
|
|
3078
|
+
? types.arrowFunctionExpression(
|
|
3079
|
+
[types.identifier('params')],
|
|
3080
|
+
types.blockStatement([
|
|
3081
|
+
buildAccumulatingRequestMark(getInfiniteScrollVars(accumulate.usage.index)),
|
|
3082
|
+
types.returnStatement(networkChain),
|
|
3083
|
+
])
|
|
3084
|
+
)
|
|
3065
3085
|
: types.arrowFunctionExpression([types.identifier('params')], networkChain)
|
|
3066
3086
|
|
|
3067
3087
|
return types.jsxAttribute(
|
|
@@ -3072,39 +3092,6 @@ function createFetchDataAttribute(
|
|
|
3072
3092
|
)
|
|
3073
3093
|
}
|
|
3074
3094
|
|
|
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
3095
|
/**
|
|
3109
3096
|
* The `fetchData` attribute for one paginated usage.
|
|
3110
3097
|
*
|
|
@@ -3120,6 +3107,41 @@ function buildFetchDataForUsage(fileName: string, usage: DataSourceUsage): types
|
|
|
3120
3107
|
return createFetchDataAttribute(fileName, usage.cache?.client ? usage.cache : undefined)
|
|
3121
3108
|
}
|
|
3122
3109
|
|
|
3110
|
+
/** `const [ds_N_total, setDs_N_total] = useState(null)` — declared only beside a range control. */
|
|
3111
|
+
function buildTotalDeclaration(vars: ReturnType<typeof getStateVarsForUsage>): types.Statement {
|
|
3112
|
+
return types.variableDeclaration('const', [
|
|
3113
|
+
types.variableDeclarator(
|
|
3114
|
+
types.arrayPattern([
|
|
3115
|
+
types.identifier(vars.totalStateVar),
|
|
3116
|
+
types.identifier(vars.setTotalStateVar),
|
|
3117
|
+
]),
|
|
3118
|
+
types.callExpression(types.identifier('useState'), [types.nullLiteral()])
|
|
3119
|
+
),
|
|
3120
|
+
])
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
/**
|
|
3124
|
+
* Fills one `range` text with the live row range of `usage` (see
|
|
3125
|
+
* `range-control.ts`). A mapper that does not page has no count to show, so
|
|
3126
|
+
* its range text is left as drawn.
|
|
3127
|
+
*/
|
|
3128
|
+
function wireRangeControlFor(
|
|
3129
|
+
control: any,
|
|
3130
|
+
usage: DataSourceUsage,
|
|
3131
|
+
vars: ReturnType<typeof getStateVarsForUsage>
|
|
3132
|
+
): void {
|
|
3133
|
+
if (!usage.paginated) {
|
|
3134
|
+
return
|
|
3135
|
+
}
|
|
3136
|
+
const { currentPageExpr } = getPageAccessors(usage, vars)
|
|
3137
|
+
wireRangeControl(control, {
|
|
3138
|
+
totalVar: vars.totalStateVar,
|
|
3139
|
+
currentPageExpr: currentPageExpr(),
|
|
3140
|
+
perPage: usage.perPage,
|
|
3141
|
+
infiniteScroll: usage.infiniteScroll,
|
|
3142
|
+
})
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3123
3145
|
/**
|
|
3124
3146
|
* Wires the authored Load More button of an appending list.
|
|
3125
3147
|
*
|
|
@@ -3185,6 +3207,12 @@ function wirePaginationWidget(
|
|
|
3185
3207
|
vars: ReturnType<typeof getStateVarsForUsage>,
|
|
3186
3208
|
hasLoadingState: boolean
|
|
3187
3209
|
): void {
|
|
3210
|
+
// A range text inside the container takes the container's owner, like the
|
|
3211
|
+
// buttons beside it; one naming its own mapper is wired in STEP 5b instead.
|
|
3212
|
+
findAllPaginationControls(paginationNode, 'range')
|
|
3213
|
+
.filter((control) => !readJSXAttributeString(control, PAGINATION_FOR_ATTR))
|
|
3214
|
+
.forEach((control) => wireRangeControlFor(control, usage, vars))
|
|
3215
|
+
|
|
3188
3216
|
if (usage.infiniteScroll) {
|
|
3189
3217
|
// Only the Load More button survives; page controls make no sense for a
|
|
3190
3218
|
// list the visitor never leaves.
|