@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.
Files changed (199) hide show
  1. package/__tests__/_helpers/first-page-runtime.ts +184 -0
  2. package/__tests__/_helpers/pagination-widget-fixture.ts +154 -5
  3. package/__tests__/booking-item-transform.test.ts +120 -0
  4. package/__tests__/browser-row-policy.test.ts +210 -4
  5. package/__tests__/data-source-request-locale.test.ts +4 -3
  6. package/__tests__/fetchers.test.ts +7 -3
  7. package/__tests__/fixed-list-sort.test.ts +1 -1
  8. package/__tests__/infinite-scroll.test.ts +363 -9
  9. package/__tests__/list-sort-order.test.ts +61 -0
  10. package/__tests__/null-filter-where.test.ts +132 -0
  11. package/__tests__/page-data-visitor-read.test.ts +196 -0
  12. package/__tests__/pg-date-parser.test.ts +73 -0
  13. package/__tests__/plain-list-sort.test.ts +2 -2
  14. package/__tests__/plain-list-starting-sort.test.ts +323 -0
  15. package/__tests__/product-price-sort.ts +11 -6
  16. package/__tests__/range-control.test.ts +159 -0
  17. package/__tests__/refetch-loading-state.test.ts +158 -2
  18. package/__tests__/search-input-marker.test.ts +86 -0
  19. package/__tests__/search-url-sync.test.ts +34 -8
  20. package/__tests__/starting-sort-first-page.test.ts +285 -0
  21. package/__tests__/table-read-route-protected-tables.test.ts +33 -3
  22. package/dist/cjs/cache/ast.d.ts +2 -0
  23. package/dist/cjs/cache/ast.d.ts.map +1 -1
  24. package/dist/cjs/cache/ast.js +5 -2
  25. package/dist/cjs/cache/ast.js.map +1 -1
  26. package/dist/cjs/count-effect.d.ts +7 -1
  27. package/dist/cjs/count-effect.d.ts.map +1 -1
  28. package/dist/cjs/count-effect.js +15 -5
  29. package/dist/cjs/count-effect.js.map +1 -1
  30. package/dist/cjs/data-source-fetchers.d.ts.map +1 -1
  31. package/dist/cjs/data-source-fetchers.js +10 -2
  32. package/dist/cjs/data-source-fetchers.js.map +1 -1
  33. package/dist/cjs/fetchers/pg-client-import.d.ts +12 -0
  34. package/dist/cjs/fetchers/pg-client-import.d.ts.map +1 -0
  35. package/dist/cjs/fetchers/pg-client-import.js +15 -0
  36. package/dist/cjs/fetchers/pg-client-import.js.map +1 -0
  37. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  38. package/dist/cjs/fetchers/postgresql.js +3 -1
  39. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  40. package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
  41. package/dist/cjs/fetchers/raw-query.js +2 -1
  42. package/dist/cjs/fetchers/raw-query.js.map +1 -1
  43. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  44. package/dist/cjs/fetchers/teleport.js +4 -2
  45. package/dist/cjs/fetchers/teleport.js.map +1 -1
  46. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  47. package/dist/cjs/fetchers/utils/browser-row-policy.js +5 -1
  48. package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
  49. package/dist/cjs/index.d.ts.map +1 -1
  50. package/dist/cjs/index.js +14 -2
  51. package/dist/cjs/index.js.map +1 -1
  52. package/dist/cjs/infinite-scroll.d.ts +89 -4
  53. package/dist/cjs/infinite-scroll.d.ts.map +1 -1
  54. package/dist/cjs/infinite-scroll.js +243 -33
  55. package/dist/cjs/infinite-scroll.js.map +1 -1
  56. package/dist/cjs/list-sort-order.d.ts +14 -0
  57. package/dist/cjs/list-sort-order.d.ts.map +1 -0
  58. package/dist/cjs/list-sort-order.js +36 -0
  59. package/dist/cjs/list-sort-order.js.map +1 -0
  60. package/dist/cjs/loading-state.d.ts +23 -2
  61. package/dist/cjs/loading-state.d.ts.map +1 -1
  62. package/dist/cjs/loading-state.js +50 -15
  63. package/dist/cjs/loading-state.js.map +1 -1
  64. package/dist/cjs/pagination-controls.d.ts +22 -3
  65. package/dist/cjs/pagination-controls.d.ts.map +1 -1
  66. package/dist/cjs/pagination-controls.js +29 -3
  67. package/dist/cjs/pagination-controls.js.map +1 -1
  68. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  69. package/dist/cjs/pagination-plugin.js +336 -164
  70. package/dist/cjs/pagination-plugin.js.map +1 -1
  71. package/dist/cjs/range-control.d.ts +45 -0
  72. package/dist/cjs/range-control.d.ts.map +1 -0
  73. package/dist/cjs/range-control.js +165 -0
  74. package/dist/cjs/range-control.js.map +1 -0
  75. package/dist/cjs/row-order.d.ts +3 -2
  76. package/dist/cjs/row-order.d.ts.map +1 -1
  77. package/dist/cjs/row-order.js +5 -4
  78. package/dist/cjs/row-order.js.map +1 -1
  79. package/dist/cjs/search-input.d.ts +42 -0
  80. package/dist/cjs/search-input.d.ts.map +1 -0
  81. package/dist/cjs/search-input.js +158 -0
  82. package/dist/cjs/search-input.js.map +1 -0
  83. package/dist/cjs/sort-utils.d.ts +53 -1
  84. package/dist/cjs/sort-utils.d.ts.map +1 -1
  85. package/dist/cjs/sort-utils.js +105 -13
  86. package/dist/cjs/sort-utils.js.map +1 -1
  87. package/dist/cjs/transformations/booking-item.d.ts +24 -0
  88. package/dist/cjs/transformations/booking-item.d.ts.map +1 -0
  89. package/dist/cjs/transformations/booking-item.js +27 -0
  90. package/dist/cjs/transformations/booking-item.js.map +1 -0
  91. package/dist/cjs/transformations/index.d.ts +4 -2
  92. package/dist/cjs/transformations/index.d.ts.map +1 -1
  93. package/dist/cjs/transformations/index.js +14 -5
  94. package/dist/cjs/transformations/index.js.map +1 -1
  95. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  96. package/dist/cjs/utils.d.ts +3 -2
  97. package/dist/cjs/utils.d.ts.map +1 -1
  98. package/dist/cjs/utils.js +48 -18
  99. package/dist/cjs/utils.js.map +1 -1
  100. package/dist/esm/cache/ast.d.ts +2 -0
  101. package/dist/esm/cache/ast.d.ts.map +1 -1
  102. package/dist/esm/cache/ast.js +3 -1
  103. package/dist/esm/cache/ast.js.map +1 -1
  104. package/dist/esm/count-effect.d.ts +7 -1
  105. package/dist/esm/count-effect.d.ts.map +1 -1
  106. package/dist/esm/count-effect.js +15 -5
  107. package/dist/esm/count-effect.js.map +1 -1
  108. package/dist/esm/data-source-fetchers.d.ts.map +1 -1
  109. package/dist/esm/data-source-fetchers.js +10 -2
  110. package/dist/esm/data-source-fetchers.js.map +1 -1
  111. package/dist/esm/fetchers/pg-client-import.d.ts +12 -0
  112. package/dist/esm/fetchers/pg-client-import.d.ts.map +1 -0
  113. package/dist/esm/fetchers/pg-client-import.js +12 -0
  114. package/dist/esm/fetchers/pg-client-import.js.map +1 -0
  115. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  116. package/dist/esm/fetchers/postgresql.js +3 -1
  117. package/dist/esm/fetchers/postgresql.js.map +1 -1
  118. package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
  119. package/dist/esm/fetchers/raw-query.js +2 -1
  120. package/dist/esm/fetchers/raw-query.js.map +1 -1
  121. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  122. package/dist/esm/fetchers/teleport.js +4 -2
  123. package/dist/esm/fetchers/teleport.js.map +1 -1
  124. package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  125. package/dist/esm/fetchers/utils/browser-row-policy.js +6 -2
  126. package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
  127. package/dist/esm/index.d.ts.map +1 -1
  128. package/dist/esm/index.js +14 -2
  129. package/dist/esm/index.js.map +1 -1
  130. package/dist/esm/infinite-scroll.d.ts +89 -4
  131. package/dist/esm/infinite-scroll.d.ts.map +1 -1
  132. package/dist/esm/infinite-scroll.js +237 -32
  133. package/dist/esm/infinite-scroll.js.map +1 -1
  134. package/dist/esm/list-sort-order.d.ts +14 -0
  135. package/dist/esm/list-sort-order.d.ts.map +1 -0
  136. package/dist/esm/list-sort-order.js +32 -0
  137. package/dist/esm/list-sort-order.js.map +1 -0
  138. package/dist/esm/loading-state.d.ts +23 -2
  139. package/dist/esm/loading-state.d.ts.map +1 -1
  140. package/dist/esm/loading-state.js +50 -15
  141. package/dist/esm/loading-state.js.map +1 -1
  142. package/dist/esm/pagination-controls.d.ts +22 -3
  143. package/dist/esm/pagination-controls.d.ts.map +1 -1
  144. package/dist/esm/pagination-controls.js +28 -2
  145. package/dist/esm/pagination-controls.js.map +1 -1
  146. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  147. package/dist/esm/pagination-plugin.js +340 -168
  148. package/dist/esm/pagination-plugin.js.map +1 -1
  149. package/dist/esm/range-control.d.ts +45 -0
  150. package/dist/esm/range-control.d.ts.map +1 -0
  151. package/dist/esm/range-control.js +137 -0
  152. package/dist/esm/range-control.js.map +1 -0
  153. package/dist/esm/row-order.d.ts +3 -2
  154. package/dist/esm/row-order.d.ts.map +1 -1
  155. package/dist/esm/row-order.js +5 -4
  156. package/dist/esm/row-order.js.map +1 -1
  157. package/dist/esm/search-input.d.ts +42 -0
  158. package/dist/esm/search-input.d.ts.map +1 -0
  159. package/dist/esm/search-input.js +129 -0
  160. package/dist/esm/search-input.js.map +1 -0
  161. package/dist/esm/sort-utils.d.ts +53 -1
  162. package/dist/esm/sort-utils.d.ts.map +1 -1
  163. package/dist/esm/sort-utils.js +100 -12
  164. package/dist/esm/sort-utils.js.map +1 -1
  165. package/dist/esm/transformations/booking-item.d.ts +24 -0
  166. package/dist/esm/transformations/booking-item.d.ts.map +1 -0
  167. package/dist/esm/transformations/booking-item.js +23 -0
  168. package/dist/esm/transformations/booking-item.js.map +1 -0
  169. package/dist/esm/transformations/index.d.ts +4 -2
  170. package/dist/esm/transformations/index.d.ts.map +1 -1
  171. package/dist/esm/transformations/index.js +14 -5
  172. package/dist/esm/transformations/index.js.map +1 -1
  173. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  174. package/dist/esm/utils.d.ts +3 -2
  175. package/dist/esm/utils.d.ts.map +1 -1
  176. package/dist/esm/utils.js +49 -19
  177. package/dist/esm/utils.js.map +1 -1
  178. package/package.json +5 -5
  179. package/src/cache/ast.ts +4 -1
  180. package/src/count-effect.ts +31 -8
  181. package/src/data-source-fetchers.ts +33 -14
  182. package/src/fetchers/pg-client-import.ts +16 -0
  183. package/src/fetchers/postgresql.ts +15 -9
  184. package/src/fetchers/raw-query.ts +2 -1
  185. package/src/fetchers/teleport.ts +19 -10
  186. package/src/fetchers/utils/browser-row-policy.ts +79 -5
  187. package/src/index.ts +8 -2
  188. package/src/infinite-scroll.ts +420 -81
  189. package/src/list-sort-order.ts +34 -0
  190. package/src/loading-state.ts +85 -13
  191. package/src/pagination-controls.ts +26 -3
  192. package/src/pagination-plugin.ts +532 -218
  193. package/src/range-control.ts +207 -0
  194. package/src/row-order.ts +5 -4
  195. package/src/search-input.ts +190 -0
  196. package/src/sort-utils.ts +164 -21
  197. package/src/transformations/booking-item.ts +71 -0
  198. package/src/transformations/index.ts +25 -2
  199. package/src/utils.ts +55 -18
@@ -52,6 +52,14 @@ import { findCachedFetchNetworkChain } from './cache/ast'
52
52
  * Nothing changes for a provider that is idle or mounting: `isFetching` starts
53
53
  * at `false`, so the server render and the first client render are identical
54
54
  * (no hydration mismatch) and the `initialData` fast path is untouched.
55
+ *
56
+ * An appending list (infinite scroll, Load more) is the exception to "show the
57
+ * loading slot while a request is in flight": its next page belongs BELOW the
58
+ * rows on show. Swapping those rows for the loading slot until the page arrived
59
+ * collapsed the list on every append — the page jumped up under the visitor's
60
+ * scroll. Such a list also tracks whether the request in flight only appends
61
+ * (`isAppendingVar`) and keeps its rows through it; the loading slot replaces
62
+ * them only when the list loads afresh (a filter, sort or search change).
55
63
  */
56
64
 
57
65
  export interface LoadingStateVars {
@@ -80,6 +88,13 @@ export interface LoadingStateVars {
80
88
  fetchSeqRefVar: string
81
89
  /** Ref holding the rows of the newest request that settled — what an overtaken one resolves with. */
82
90
  latestDataRefVar: string
91
+ /**
92
+ * Boolean state of an appending list: `true` while the request it made last
93
+ * adds a page to the rows on show, so they stay there through it.
94
+ */
95
+ isAppendingVar: string
96
+ /** Setter for `isAppendingVar`. */
97
+ setIsAppendingVar: string
83
98
  }
84
99
 
85
100
  export function getLoadingStateVars(index: number): LoadingStateVars {
@@ -89,6 +104,8 @@ export function getLoadingStateVars(index: number): LoadingStateVars {
89
104
  inFlightRefVar: `ds_${index}_fetchesInFlight`,
90
105
  fetchSeqRefVar: `ds_${index}_fetchSeq`,
91
106
  latestDataRefVar: `ds_${index}_latestData`,
107
+ isAppendingVar: `ds_${index}_isAppending`,
108
+ setIsAppendingVar: `setDs_${index}_isAppending`,
92
109
  }
93
110
  }
94
111
 
@@ -97,14 +114,18 @@ export function getLoadingStateVars(index: number): LoadingStateVars {
97
114
  * `const ds_N_fetchSeq = useRef(0)`
98
115
  * `const ds_N_latestData = useRef(undefined)`
99
116
  * `const [ds_N_isFetching, setDs_N_isFetching] = useState(false)`
117
+ * `const [ds_N_isAppending, setDs_N_isAppending] = useState(false)` (an appending list)
100
118
  *
101
119
  * All are stable across renders (ref objects and a `useState` setter), which
102
120
  * is what lets the wrapped `fetchData` keep its empty `useCallback` dependency
103
121
  * array — a changing `fetchData` identity would retrigger the provider's fetch
104
122
  * effect on every render.
105
123
  */
106
- export function buildLoadingStateDeclarations(vars: LoadingStateVars): types.Statement[] {
107
- return [
124
+ export function buildLoadingStateDeclarations(
125
+ vars: LoadingStateVars,
126
+ appends: boolean = false
127
+ ): types.Statement[] {
128
+ const declarations: types.Statement[] = [
108
129
  types.variableDeclaration('const', [
109
130
  types.variableDeclarator(
110
131
  types.identifier(vars.inFlightRefVar),
@@ -133,6 +154,20 @@ export function buildLoadingStateDeclarations(vars: LoadingStateVars): types.Sta
133
154
  ),
134
155
  ]),
135
156
  ]
157
+ if (appends) {
158
+ declarations.push(
159
+ types.variableDeclaration('const', [
160
+ types.variableDeclarator(
161
+ types.arrayPattern([
162
+ types.identifier(vars.isAppendingVar),
163
+ types.identifier(vars.setIsAppendingVar),
164
+ ]),
165
+ types.callExpression(types.identifier('useState'), [types.booleanLiteral(false)])
166
+ ),
167
+ ])
168
+ )
169
+ }
170
+ return declarations
136
171
  }
137
172
 
138
173
  /**
@@ -146,11 +181,17 @@ export function buildLoadingStateDeclarations(vars: LoadingStateVars): types.Sta
146
181
  * - `fetchData` missing, not memoized, or not an expression-bodied promise
147
182
  * chain — see `findMemoizedFetchDataArrow`.
148
183
  * - already wired — keeps a second pass over the same AST idempotent.
184
+ *
185
+ * `appendingRequest` makes it an appending list's: the expression telling,
186
+ * inside `fetchData`, whether the request about to be made only adds a page to
187
+ * the rows on show (see `isAppendingVar`). The caller declares the state with
188
+ * `buildLoadingStateDeclarations(vars, true)`.
149
189
  */
150
190
  export function applyLoadingStateToDataProvider(
151
191
  // tslint:disable-next-line:no-any
152
192
  dataProvider: any,
153
- vars: LoadingStateVars
193
+ vars: LoadingStateVars,
194
+ appendingRequest?: types.Expression
154
195
  ): boolean {
155
196
  const attributes = dataProvider?.openingElement?.attributes
156
197
  if (!Array.isArray(attributes)) {
@@ -172,16 +213,16 @@ export function applyLoadingStateToDataProvider(
172
213
  }
173
214
 
174
215
  if (fetchArrow.body.type === 'CallExpression') {
175
- fetchArrow.body = buildTrackedFetchBody(fetchArrow.body, vars)
216
+ fetchArrow.body = buildTrackedFetchBody(fetchArrow.body, vars, appendingRequest)
176
217
  } else if (fetchArrow.body.type === 'BlockStatement') {
177
- if (!injectTrackingIntoCachedBody(fetchArrow.body, vars)) {
218
+ if (!injectTrackingIntoCachedBody(fetchArrow.body, vars, appendingRequest)) {
178
219
  return false
179
220
  }
180
221
  } else {
181
222
  return false
182
223
  }
183
224
 
184
- setPersistDataDuringLoading(attributes, vars)
225
+ setPersistDataDuringLoading(attributes, vars, !!appendingRequest)
185
226
 
186
227
  return true
187
228
  }
@@ -199,7 +240,11 @@ export function applyLoadingStateToDataProvider(
199
240
  * Returns `false` when the body is not the shape this module knows how to wrap,
200
241
  * so the caller emits no state declarations rather than half-wiring it.
201
242
  */
202
- function injectTrackingIntoCachedBody(body: types.BlockStatement, vars: LoadingStateVars): boolean {
243
+ function injectTrackingIntoCachedBody(
244
+ body: types.BlockStatement,
245
+ vars: LoadingStateVars,
246
+ appendingRequest?: types.Expression
247
+ ): boolean {
203
248
  // Idempotent: a second pass over the same AST must not double-count.
204
249
  if (containsIdentifier(body, vars.setIsFetchingVar)) {
205
250
  return false
@@ -210,7 +255,11 @@ function injectTrackingIntoCachedBody(body: types.BlockStatement, vars: LoadingS
210
255
  return false
211
256
  }
212
257
 
213
- const tracked = buildTrackedFetchBody(networkChain.statement.argument as types.Expression, vars)
258
+ const tracked = buildTrackedFetchBody(
259
+ networkChain.statement.argument as types.Expression,
260
+ vars,
261
+ appendingRequest
262
+ )
214
263
  body.body.splice(networkChain.index, 1, ...tracked.body)
215
264
  markCacheHitAsNewest(body, vars)
216
265
 
@@ -363,10 +412,14 @@ function findMemoizedFetchDataArrow(
363
412
  * `fetchExpression` is the `fetch(...).then(...).then(...)` chain the generator
364
413
  * emitted, so `.finally` is always available on it — the caller only reaches
365
414
  * here for a call-expression body.
415
+ *
416
+ * An appending list also records, as the flag goes up, whether this request
417
+ * only appends — `setDs_N_isAppending(<appendingRequest>)`, batched with it.
366
418
  */
367
419
  function buildTrackedFetchBody(
368
420
  fetchExpression: types.Expression,
369
- vars: LoadingStateVars
421
+ vars: LoadingStateVars,
422
+ appendingRequest?: types.Expression
370
423
  ): types.BlockStatement {
371
424
  const inFlightCount = types.memberExpression(
372
425
  types.identifier(vars.inFlightRefVar),
@@ -456,6 +509,15 @@ function buildTrackedFetchBody(
456
509
  types.numericLiteral(1)
457
510
  )
458
511
  ),
512
+ ...(appendingRequest
513
+ ? [
514
+ types.expressionStatement(
515
+ types.callExpression(types.identifier(vars.setIsAppendingVar), [
516
+ types.cloneNode(appendingRequest, true),
517
+ ])
518
+ ),
519
+ ]
520
+ : []),
459
521
  types.expressionStatement(
460
522
  types.callExpression(types.identifier(vars.setIsFetchingVar), [types.booleanLiteral(true)])
461
523
  ),
@@ -467,13 +529,23 @@ function buildTrackedFetchBody(
467
529
  ])
468
530
  }
469
531
 
470
- /** `persistDataDuringLoading={!ds_N_isFetching}`, replacing any existing value. */
471
- // tslint:disable-next-line:no-any
472
- function setPersistDataDuringLoading(attributes: any[], vars: LoadingStateVars): void {
532
+ /**
533
+ * `persistDataDuringLoading={!ds_N_isFetching}` — or, for an appending list,
534
+ * `{!ds_N_isFetching || ds_N_isAppending}` — replacing any existing value.
535
+ */
536
+ function setPersistDataDuringLoading(
537
+ // tslint:disable-next-line:no-any
538
+ attributes: any[],
539
+ vars: LoadingStateVars,
540
+ appends: boolean
541
+ ): void {
542
+ const notFetching = types.unaryExpression('!', types.identifier(vars.isFetchingVar), true)
473
543
  const attribute = types.jsxAttribute(
474
544
  types.jsxIdentifier('persistDataDuringLoading'),
475
545
  types.jsxExpressionContainer(
476
- types.unaryExpression('!', types.identifier(vars.isFetchingVar), true)
546
+ appends
547
+ ? types.logicalExpression('||', notFetching, types.identifier(vars.isAppendingVar))
548
+ : notFetching
477
549
  )
478
550
  )
479
551
 
@@ -17,7 +17,9 @@ export const PAGINATION_CONTROL_ATTR = 'data-tq-pagination-control'
17
17
  * `'pages'` is the container the page-number buttons are repeated into and
18
18
  * `'page'` the single template button inside it. `'ellipsis'` and `'sentinel'`
19
19
  * are reserved for an authored gap marker / scroll sentinel; until a builder
20
- * emits them both runtimes fall back to generating their own.
20
+ * emits them both runtimes fall back to generating their own. `'range'` is a
21
+ * TEXT element ("Showing 1–8 of 12") both runtimes fill with the live row
22
+ * range — see `range-control.ts`.
21
23
  */
22
24
  export type PaginationControlKind =
23
25
  | 'first'
@@ -29,6 +31,26 @@ export type PaginationControlKind =
29
31
  | 'last'
30
32
  | 'load-more'
31
33
  | 'sentinel'
34
+ | 'range'
35
+
36
+ /**
37
+ * Which mapper a `range` control belongs to, when it does not sit inside that
38
+ * mapper's pagination container: the array mapper's editor node id, which the
39
+ * UIDL carries as `nodeId` on the `cms-list-repeater`. A template's footer
40
+ * ("Showing 1–8 of 12") is laid out by the author, not inside a widget, so the
41
+ * container-ownership rule the buttons use cannot reach it.
42
+ *
43
+ * ⛔ Both attribute names must stay identical to the editor's
44
+ * (`array-mapper-pagination-settings.ts`) and the canvas renderer's
45
+ * (`array-mapper-pagination.ts`) copies.
46
+ */
47
+ export const PAGINATION_FOR_ATTR = 'data-tq-pagination-for'
48
+
49
+ /** The words of a `range` control, with `{from}`, `{to}` and `{total}` placeholders. */
50
+ export const RANGE_TEMPLATE_ATTR = 'data-tq-range-template'
51
+
52
+ /** What a `range` control reads when it carries no template. */
53
+ export const DEFAULT_RANGE_TEMPLATE = '{from}–{to} of {total}'
32
54
 
33
55
  // tslint:disable:no-any
34
56
 
@@ -152,13 +174,14 @@ export function removeJSXAttribute(node: any, attrName: string): void {
152
174
  *
153
175
  * Children carrying NO marker are always kept: they are the author's own
154
176
  * additions (a page counter, a spacer) and the generator has no business
155
- * deciding whether they belong.
177
+ * deciding whether they belong. A `range` text is kept in every mode too: the
178
+ * row range is meaningful whether the list pages or appends.
156
179
  */
157
180
  export function dropInactivePaginationControls(
158
181
  paginationNode: any,
159
182
  activeKinds: PaginationControlKind[]
160
183
  ): void {
161
- const active = new Set<string>(activeKinds)
184
+ const active = new Set<string>([...activeKinds, 'range'])
162
185
  const prune = (node: any): void => {
163
186
  if (!node || !Array.isArray(node.children)) {
164
187
  return