@teleporthq/teleport-plugin-next-data-source 0.43.74 → 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.
Files changed (231) hide show
  1. package/__tests__/_helpers/pagination-widget-fixture.ts +95 -5
  2. package/__tests__/blog-adjacent-arranged.test.ts +176 -0
  3. package/__tests__/blog-post-contents.test.ts +212 -0
  4. package/__tests__/blog-post-page-enrichment.test.ts +28 -5
  5. package/__tests__/blog-post-picture-stand-in.test.ts +48 -0
  6. package/__tests__/blog-post-reading-time.test.ts +55 -0
  7. package/__tests__/booking-item-transform.test.ts +120 -0
  8. package/__tests__/browser-row-policy.test.ts +13 -3
  9. package/__tests__/content-preset-wiring.test.ts +108 -0
  10. package/__tests__/fetchers.test.ts +1 -1
  11. package/__tests__/fixed-list-sort.test.ts +183 -0
  12. package/__tests__/infinite-scroll.test.ts +154 -5
  13. package/__tests__/list-sort-order.test.ts +61 -0
  14. package/__tests__/null-filter-where.test.ts +132 -0
  15. package/__tests__/pagination-filter-groups.test.ts +186 -0
  16. package/__tests__/pg-date-parser.test.ts +73 -0
  17. package/__tests__/plain-list-sort.test.ts +215 -0
  18. package/__tests__/product-price-sort.ts +58 -25
  19. package/__tests__/range-control.test.ts +159 -0
  20. package/__tests__/rest-api-key-header.test.ts +18 -0
  21. package/__tests__/search-input-marker.test.ts +86 -0
  22. package/__tests__/search-url-sync.test.ts +61 -16
  23. package/__tests__/shared-resource-prefetch.test.ts +65 -0
  24. package/__tests__/table-read-route-protected-tables.test.ts +33 -3
  25. package/dist/cjs/count-effect.d.ts +7 -1
  26. package/dist/cjs/count-effect.d.ts.map +1 -1
  27. package/dist/cjs/count-effect.js +15 -5
  28. package/dist/cjs/count-effect.js.map +1 -1
  29. package/dist/cjs/fetchers/pg-client-import.d.ts +12 -0
  30. package/dist/cjs/fetchers/pg-client-import.d.ts.map +1 -0
  31. package/dist/cjs/fetchers/pg-client-import.js +15 -0
  32. package/dist/cjs/fetchers/pg-client-import.js.map +1 -0
  33. package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
  34. package/dist/cjs/fetchers/postgresql.js +4 -1
  35. package/dist/cjs/fetchers/postgresql.js.map +1 -1
  36. package/dist/cjs/fetchers/raw-query.d.ts.map +1 -1
  37. package/dist/cjs/fetchers/raw-query.js +2 -1
  38. package/dist/cjs/fetchers/raw-query.js.map +1 -1
  39. package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
  40. package/dist/cjs/fetchers/rest-api.js +3 -1
  41. package/dist/cjs/fetchers/rest-api.js.map +1 -1
  42. package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
  43. package/dist/cjs/fetchers/teleport.js +5 -2
  44. package/dist/cjs/fetchers/teleport.js.map +1 -1
  45. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts +7 -6
  46. package/dist/cjs/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  47. package/dist/cjs/fetchers/utils/browser-row-policy.js +7 -33
  48. package/dist/cjs/fetchers/utils/browser-row-policy.js.map +1 -1
  49. package/dist/cjs/filter-utils.d.ts +19 -7
  50. package/dist/cjs/filter-utils.d.ts.map +1 -1
  51. package/dist/cjs/filter-utils.js +49 -13
  52. package/dist/cjs/filter-utils.js.map +1 -1
  53. package/dist/cjs/infinite-scroll.d.ts +25 -2
  54. package/dist/cjs/infinite-scroll.d.ts.map +1 -1
  55. package/dist/cjs/infinite-scroll.js +144 -33
  56. package/dist/cjs/infinite-scroll.js.map +1 -1
  57. package/dist/cjs/list-sort-order.d.ts +14 -0
  58. package/dist/cjs/list-sort-order.d.ts.map +1 -0
  59. package/dist/cjs/list-sort-order.js +36 -0
  60. package/dist/cjs/list-sort-order.js.map +1 -0
  61. package/dist/cjs/pagination-controls.d.ts +22 -3
  62. package/dist/cjs/pagination-controls.d.ts.map +1 -1
  63. package/dist/cjs/pagination-controls.js +29 -3
  64. package/dist/cjs/pagination-controls.js.map +1 -1
  65. package/dist/cjs/pagination-plugin.d.ts.map +1 -1
  66. package/dist/cjs/pagination-plugin.js +267 -212
  67. package/dist/cjs/pagination-plugin.js.map +1 -1
  68. package/dist/cjs/product-price-sort.d.ts +0 -9
  69. package/dist/cjs/product-price-sort.d.ts.map +1 -1
  70. package/dist/cjs/product-price-sort.js +1 -13
  71. package/dist/cjs/product-price-sort.js.map +1 -1
  72. package/dist/cjs/range-control.d.ts +45 -0
  73. package/dist/cjs/range-control.d.ts.map +1 -0
  74. package/dist/cjs/range-control.js +165 -0
  75. package/dist/cjs/range-control.js.map +1 -0
  76. package/dist/cjs/row-order.d.ts +20 -0
  77. package/dist/cjs/row-order.d.ts.map +1 -0
  78. package/dist/cjs/row-order.js +46 -0
  79. package/dist/cjs/row-order.js.map +1 -0
  80. package/dist/cjs/search-input.d.ts +42 -0
  81. package/dist/cjs/search-input.d.ts.map +1 -0
  82. package/dist/cjs/search-input.js +158 -0
  83. package/dist/cjs/search-input.js.map +1 -0
  84. package/dist/cjs/sort-utils.d.ts +10 -0
  85. package/dist/cjs/sort-utils.d.ts.map +1 -1
  86. package/dist/cjs/sort-utils.js +22 -1
  87. package/dist/cjs/sort-utils.js.map +1 -1
  88. package/dist/cjs/transformations/blog-adjacent-posts.d.ts +11 -1
  89. package/dist/cjs/transformations/blog-adjacent-posts.d.ts.map +1 -1
  90. package/dist/cjs/transformations/blog-adjacent-posts.js +14 -1
  91. package/dist/cjs/transformations/blog-adjacent-posts.js.map +1 -1
  92. package/dist/cjs/transformations/blog-comments.d.ts +1 -1
  93. package/dist/cjs/transformations/blog-comments.d.ts.map +1 -1
  94. package/dist/cjs/transformations/blog-comments.js +1 -1
  95. package/dist/cjs/transformations/blog-comments.js.map +1 -1
  96. package/dist/cjs/transformations/blog-post.d.ts +14 -0
  97. package/dist/cjs/transformations/blog-post.d.ts.map +1 -1
  98. package/dist/cjs/transformations/blog-post.js +4 -1
  99. package/dist/cjs/transformations/blog-post.js.map +1 -1
  100. package/dist/cjs/transformations/booking-item.d.ts +24 -0
  101. package/dist/cjs/transformations/booking-item.d.ts.map +1 -0
  102. package/dist/cjs/transformations/booking-item.js +27 -0
  103. package/dist/cjs/transformations/booking-item.js.map +1 -0
  104. package/dist/cjs/transformations/heading-anchors.d.ts +32 -8
  105. package/dist/cjs/transformations/heading-anchors.d.ts.map +1 -1
  106. package/dist/cjs/transformations/heading-anchors.js +34 -10
  107. package/dist/cjs/transformations/heading-anchors.js.map +1 -1
  108. package/dist/cjs/transformations/index.d.ts +27 -13
  109. package/dist/cjs/transformations/index.d.ts.map +1 -1
  110. package/dist/cjs/transformations/index.js +73 -16
  111. package/dist/cjs/transformations/index.js.map +1 -1
  112. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  113. package/dist/cjs/utils.d.ts.map +1 -1
  114. package/dist/cjs/utils.js +52 -3
  115. package/dist/cjs/utils.js.map +1 -1
  116. package/dist/esm/count-effect.d.ts +7 -1
  117. package/dist/esm/count-effect.d.ts.map +1 -1
  118. package/dist/esm/count-effect.js +15 -5
  119. package/dist/esm/count-effect.js.map +1 -1
  120. package/dist/esm/fetchers/pg-client-import.d.ts +12 -0
  121. package/dist/esm/fetchers/pg-client-import.d.ts.map +1 -0
  122. package/dist/esm/fetchers/pg-client-import.js +12 -0
  123. package/dist/esm/fetchers/pg-client-import.js.map +1 -0
  124. package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
  125. package/dist/esm/fetchers/postgresql.js +5 -2
  126. package/dist/esm/fetchers/postgresql.js.map +1 -1
  127. package/dist/esm/fetchers/raw-query.d.ts.map +1 -1
  128. package/dist/esm/fetchers/raw-query.js +2 -1
  129. package/dist/esm/fetchers/raw-query.js.map +1 -1
  130. package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
  131. package/dist/esm/fetchers/rest-api.js +3 -1
  132. package/dist/esm/fetchers/rest-api.js.map +1 -1
  133. package/dist/esm/fetchers/teleport.d.ts.map +1 -1
  134. package/dist/esm/fetchers/teleport.js +6 -3
  135. package/dist/esm/fetchers/teleport.js.map +1 -1
  136. package/dist/esm/fetchers/utils/browser-row-policy.d.ts +7 -6
  137. package/dist/esm/fetchers/utils/browser-row-policy.d.ts.map +1 -1
  138. package/dist/esm/fetchers/utils/browser-row-policy.js +5 -31
  139. package/dist/esm/fetchers/utils/browser-row-policy.js.map +1 -1
  140. package/dist/esm/filter-utils.d.ts +19 -7
  141. package/dist/esm/filter-utils.d.ts.map +1 -1
  142. package/dist/esm/filter-utils.js +46 -12
  143. package/dist/esm/filter-utils.js.map +1 -1
  144. package/dist/esm/infinite-scroll.d.ts +25 -2
  145. package/dist/esm/infinite-scroll.d.ts.map +1 -1
  146. package/dist/esm/infinite-scroll.js +142 -32
  147. package/dist/esm/infinite-scroll.js.map +1 -1
  148. package/dist/esm/list-sort-order.d.ts +14 -0
  149. package/dist/esm/list-sort-order.d.ts.map +1 -0
  150. package/dist/esm/list-sort-order.js +32 -0
  151. package/dist/esm/list-sort-order.js.map +1 -0
  152. package/dist/esm/pagination-controls.d.ts +22 -3
  153. package/dist/esm/pagination-controls.d.ts.map +1 -1
  154. package/dist/esm/pagination-controls.js +28 -2
  155. package/dist/esm/pagination-controls.js.map +1 -1
  156. package/dist/esm/pagination-plugin.d.ts.map +1 -1
  157. package/dist/esm/pagination-plugin.js +271 -216
  158. package/dist/esm/pagination-plugin.js.map +1 -1
  159. package/dist/esm/product-price-sort.d.ts +0 -9
  160. package/dist/esm/product-price-sort.d.ts.map +1 -1
  161. package/dist/esm/product-price-sort.js +0 -11
  162. package/dist/esm/product-price-sort.js.map +1 -1
  163. package/dist/esm/range-control.d.ts +45 -0
  164. package/dist/esm/range-control.d.ts.map +1 -0
  165. package/dist/esm/range-control.js +137 -0
  166. package/dist/esm/range-control.js.map +1 -0
  167. package/dist/esm/row-order.d.ts +20 -0
  168. package/dist/esm/row-order.d.ts.map +1 -0
  169. package/dist/esm/row-order.js +41 -0
  170. package/dist/esm/row-order.js.map +1 -0
  171. package/dist/esm/search-input.d.ts +42 -0
  172. package/dist/esm/search-input.d.ts.map +1 -0
  173. package/dist/esm/search-input.js +129 -0
  174. package/dist/esm/search-input.js.map +1 -0
  175. package/dist/esm/sort-utils.d.ts +10 -0
  176. package/dist/esm/sort-utils.d.ts.map +1 -1
  177. package/dist/esm/sort-utils.js +20 -0
  178. package/dist/esm/sort-utils.js.map +1 -1
  179. package/dist/esm/transformations/blog-adjacent-posts.d.ts +11 -1
  180. package/dist/esm/transformations/blog-adjacent-posts.d.ts.map +1 -1
  181. package/dist/esm/transformations/blog-adjacent-posts.js +14 -1
  182. package/dist/esm/transformations/blog-adjacent-posts.js.map +1 -1
  183. package/dist/esm/transformations/blog-comments.d.ts +1 -1
  184. package/dist/esm/transformations/blog-comments.d.ts.map +1 -1
  185. package/dist/esm/transformations/blog-comments.js +1 -1
  186. package/dist/esm/transformations/blog-comments.js.map +1 -1
  187. package/dist/esm/transformations/blog-post.d.ts +14 -0
  188. package/dist/esm/transformations/blog-post.d.ts.map +1 -1
  189. package/dist/esm/transformations/blog-post.js +4 -1
  190. package/dist/esm/transformations/blog-post.js.map +1 -1
  191. package/dist/esm/transformations/booking-item.d.ts +24 -0
  192. package/dist/esm/transformations/booking-item.d.ts.map +1 -0
  193. package/dist/esm/transformations/booking-item.js +23 -0
  194. package/dist/esm/transformations/booking-item.js.map +1 -0
  195. package/dist/esm/transformations/heading-anchors.d.ts +32 -8
  196. package/dist/esm/transformations/heading-anchors.d.ts.map +1 -1
  197. package/dist/esm/transformations/heading-anchors.js +33 -9
  198. package/dist/esm/transformations/heading-anchors.js.map +1 -1
  199. package/dist/esm/transformations/index.d.ts +27 -13
  200. package/dist/esm/transformations/index.d.ts.map +1 -1
  201. package/dist/esm/transformations/index.js +72 -16
  202. package/dist/esm/transformations/index.js.map +1 -1
  203. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  204. package/dist/esm/utils.d.ts.map +1 -1
  205. package/dist/esm/utils.js +52 -3
  206. package/dist/esm/utils.js.map +1 -1
  207. package/package.json +5 -5
  208. package/src/count-effect.ts +31 -8
  209. package/src/fetchers/pg-client-import.ts +16 -0
  210. package/src/fetchers/postgresql.ts +25 -23
  211. package/src/fetchers/raw-query.ts +2 -1
  212. package/src/fetchers/rest-api.ts +3 -1
  213. package/src/fetchers/teleport.ts +22 -23
  214. package/src/fetchers/utils/browser-row-policy.ts +10 -20
  215. package/src/filter-utils.ts +102 -28
  216. package/src/infinite-scroll.ts +240 -79
  217. package/src/list-sort-order.ts +34 -0
  218. package/src/pagination-controls.ts +26 -3
  219. package/src/pagination-plugin.ts +370 -306
  220. package/src/product-price-sort.ts +0 -11
  221. package/src/range-control.ts +207 -0
  222. package/src/row-order.ts +55 -0
  223. package/src/search-input.ts +190 -0
  224. package/src/sort-utils.ts +25 -0
  225. package/src/transformations/blog-adjacent-posts.ts +89 -3
  226. package/src/transformations/blog-comments.ts +5 -5
  227. package/src/transformations/blog-post.ts +64 -13
  228. package/src/transformations/booking-item.ts +71 -0
  229. package/src/transformations/heading-anchors.ts +79 -16
  230. package/src/transformations/index.ts +104 -14
  231. package/src/utils.ts +74 -3
@@ -85,6 +85,8 @@ var loading_state_1 = require("./loading-state");
85
85
  var config_1 = require("./cache/config");
86
86
  var ast_1 = require("./cache/ast");
87
87
  var pagination_controls_1 = require("./pagination-controls");
88
+ var range_control_1 = require("./range-control");
89
+ var search_input_1 = require("./search-input");
88
90
  var count_effect_1 = require("./count-effect");
89
91
  var search_clear_1 = require("./search-clear");
90
92
  var numbered_pagination_1 = require("./numbered-pagination");
@@ -129,79 +131,37 @@ function searchDefaultValueInitAST(value) {
129
131
  // independent AST node — Babel does not support shared references.
130
132
  return types.cloneNode(value.ast, /* deep */ true);
131
133
  }
132
- // Builds the `useState(...)` initializer for the search query. When the
133
- // cms-list-repeater is bound to a URL param (`searchUrlParamKey`), the query is
134
- // seeded from `window.location.search` on the client so a deep link such as
135
- // `/products-list?searchKeyword=yoga` arrives pre-filtered on the very first
136
- // paint; the server render (where `window` is undefined) and any missing param
137
- // fall back to the `searchDefaultValue` seed (empty string by default). This
138
- // mirrors the `selectedCategory` / `sortBy` initializer emitted by
139
- // `createStateHookAST` for state-level `urlSearchParamBinding`, keeping the
140
- // three products-list controls consistent.
134
+ // Builds the `useState(...)` initializer for the search query: the
135
+ // `searchDefaultValue` seed (empty string by default), never the address. A
136
+ // statically generated page is rendered on the server without the visitor's
137
+ // `?searchKeyword=`, and the browser's first render must match it — a query
138
+ // seeded from `window.location.search` rendered a filled input and skipped the
139
+ // prefetched rows there, and React warned of a hydration mismatch. A URL-bound
140
+ // search adopts the address after mount instead (`pushSearchUrlSyncEffects`),
141
+ // as the page number does (`page-url-sync.ts`).
141
142
  //
142
143
  // Returns a fresh AST on every call (no shared node references) so the same
143
144
  // initializer can seed both the immediate `ds_N_searchQuery` state and the
144
145
  // debounced `ds_N_state.debouncedQuery` value.
145
146
  function buildSearchQueryInitAST(usage) {
146
- var fallback = searchDefaultValueInitAST(usage.searchDefaultValue);
147
- if (!usage.searchUrlParamKey) {
148
- return fallback;
149
- }
150
- // (typeof window !== 'undefined'
151
- // ? new URLSearchParams(window.location.search).get('<key>')
152
- // : null) ?? <fallback>
153
- return types.logicalExpression('??', types.conditionalExpression(types.binaryExpression('!==', types.unaryExpression('typeof', types.identifier('window')), types.stringLiteral('undefined')), types.callExpression(types.memberExpression(types.newExpression(types.identifier('URLSearchParams'), [
154
- types.memberExpression(types.memberExpression(types.identifier('window'), types.identifier('location')), types.identifier('search')),
155
- ]), types.identifier('get')), [types.stringLiteral(usage.searchUrlParamKey)]), types.nullLiteral()), fallback);
147
+ return searchDefaultValueInitAST(usage.searchDefaultValue);
156
148
  }
157
- // The UIDL's `filters.content` array can wrap one or more conditions inside
158
- // a `{ type: 'group', operator: 'and' | 'or', children: [...] }` entry — the
159
- // GUI builds this shape when the inspector adds a logical group. The data
160
- // source API endpoint (`processFilters` in the generated data-source module)
161
- // only knows how to consume a FLAT array of `{ source, destination, operand }`
162
- // conditions, so we walk groups recursively here and collect their leaf
163
- // conditions in order. Anything that isn't a group AND isn't a condition is
164
- // dropped — defensive against partially-built filter entries that would
165
- // otherwise emit `{ source: '', destination: '', operand: '' }` rows the
166
- // API ignores anyway.
167
- //
168
- // `or`-grouped conditions are flattened just like `and`-grouped ones — the
169
- // downstream SQL builder always joins flat filters with `AND`, so emitting
170
- // the conditions side-by-side approximates AND semantics. A future API
171
- // upgrade that respects group operators would key off the original tree
172
- // directly; until then, AND-flattening is the closest correct behaviour and
173
- // matches what the inspector preview shows for the common single-group case
174
- // the GUI emits today.
175
- function flattenFilterGroups(filters) {
149
+ // The UIDL's `filters.content` array is the tree the inspector authored:
150
+ // `{ type: 'group', operator: 'and' | 'or', children: [...] }` entries around
151
+ // `{ source, destination, operand }` conditions, nested as deep as the user
152
+ // grouped them. The tree is kept as it is — the generated fetchers read it with
153
+ // `normalizeFilterTree`, so an `or` group stays an `or`. Only junk is dropped:
154
+ // an entry that is neither a group nor a condition (a half-built inspector
155
+ // row) would otherwise reach the API as `{ source: '', destination: '' }`.
156
+ // Entries without an explicit `type` are the legacy flat form and are
157
+ // conditions.
158
+ function readFilterTree(filters) {
176
159
  if (!Array.isArray(filters)) {
177
160
  return [];
178
161
  }
179
- var out = [];
180
- var walk = function (entry) {
181
- if (!entry || typeof entry !== 'object') {
182
- return;
183
- }
184
- if (entry.type === 'group' && Array.isArray(entry.children)) {
185
- for (var _i = 0, _a = entry.children; _i < _a.length; _i++) {
186
- var child = _a[_i];
187
- walk(child);
188
- }
189
- return;
190
- }
191
- // Backwards-compatible: entries without an explicit `type` (legacy flat
192
- // form) and entries explicitly tagged `condition` are both treated as
193
- // condition leaves.
194
- if (entry.type === undefined || entry.type === 'condition') {
195
- out.push(entry);
196
- }
197
- };
198
- for (var _i = 0, filters_1 = filters; _i < filters_1.length; _i++) {
199
- var entry = filters_1[_i];
200
- walk(entry);
201
- }
202
- return out;
162
+ return (0, utils_1.pruneFilterTree)(filters, function (entry) { return entry.type === undefined || entry.type === 'condition'; });
203
163
  }
204
- // Walks a flat filter list to extract every `urlSearchParams` reference key
164
+ // Walks the filter tree to extract every `urlSearchParams` reference key
205
165
  // (e.g. `'categoryFilter'`). Used to (a) inject `const router = useRouter()`
206
166
  // once at the top of the component, (b) wire `router.query.<key>` into the
207
167
  // `useMemo` dependency array so the client-side fetch reruns whenever the
@@ -210,8 +170,8 @@ function flattenFilterGroups(filters) {
210
170
  function collectFilterUrlSearchParamKeys(filters) {
211
171
  var seen = new Set();
212
172
  var out = [];
213
- for (var _i = 0, filters_2 = filters; _i < filters_2.length; _i++) {
214
- var f = filters_2[_i];
173
+ for (var _i = 0, _a = (0, utils_1.collectFilterLeafConditions)(filters); _i < _a.length; _i++) {
174
+ var f = _a[_i];
215
175
  var dest = f === null || f === void 0 ? void 0 : f.destination;
216
176
  if (!teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(dest)) {
217
177
  continue;
@@ -228,6 +188,12 @@ function collectFilterUrlSearchParamKeys(filters) {
228
188
  }
229
189
  return out;
230
190
  }
191
+ // The filter tree with every dynamic destination pruned — what `getStaticProps`
192
+ // can resolve at build time. Groups keep their operator; a group left empty by
193
+ // the pruning disappears with it (`pruneFilterTree`).
194
+ function staticFiltersOf(usage) {
195
+ return (0, utils_1.pruneFilterTree)(usage.filters || [], function (f) { return !teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(f.destination); });
196
+ }
231
197
  // Returns true when any filter destination is a `urlSearchParams` dynamic
232
198
  // reference — used by the plugin to know it needs to import `useRouter` and
233
199
  // emit `const router = useRouter()` at the top of the component. Cheaper to
@@ -385,22 +351,28 @@ function buildStateRegistry(uidlNode) {
385
351
  if ((_k = (_j = parentDataSource.resourceParams) === null || _j === void 0 ? void 0 : _j.sorts) === null || _k === void 0 ? void 0 : _k.content) {
386
352
  sorts = parentDataSource.resourceParams.sorts.content;
387
353
  }
388
- // If legacy sorts aren't set, fall back to the new dynamic single-column
389
- // sort fields on the cms-list-repeater (used by admin-panel listing pages).
354
+ // If legacy sorts aren't set, fall back to the single-column sort fields
355
+ // on the cms-list-repeater. A FIXED one (a column chosen in the editor,
356
+ // e.g. a blog listing's newest first) is a static sort: read as dynamic,
357
+ // it made the list drop the server-side first page (`initialData` below
358
+ // is skipped for a dynamic sort) — the pre-rendered HTML carried no
359
+ // posts, and the browser fetched page 1 a second time. Only a sort
360
+ // bound to state (the admin panel's sortable columns) is dynamic.
390
361
  var dynamicSort = void 0;
391
362
  if ((!sorts || sorts.length === 0) && content.sort) {
392
- dynamicSort = (0, sort_utils_1.extractDynamicSort)(content.sort, content.sortDirection);
363
+ var fixedSort = (0, sort_utils_1.readFixedSort)(content.sort, content.sortDirection);
364
+ if (fixedSort) {
365
+ sorts = [fixedSort];
366
+ }
367
+ else {
368
+ dynamicSort = (0, sort_utils_1.extractDynamicSort)(content.sort, content.sortDirection);
369
+ }
393
370
  }
394
- // Extract filters from parent's resource params. The inspector wraps
395
- // every condition in a `{ type: 'group' }` envelope (single-group or
396
- // nested), so flatten to leaf conditions before the downstream emit
397
- // sites consume `.source` / `.destination` / `.operand` directly —
398
- // they expect a flat condition array. See `flattenFilterGroups`'s
399
- // header comment for why AND-flattening is the correct fallback for
400
- // the API endpoint's flat-condition contract.
371
+ // Extract filters from parent's resource params, keeping the group tree
372
+ // the inspector authored (see `readFilterTree`).
401
373
  var filters = [];
402
374
  if ((_m = (_l = parentDataSource.resourceParams) === null || _l === void 0 ? void 0 : _l.filters) === null || _m === void 0 ? void 0 : _m.content) {
403
- filters = flattenFilterGroups(parentDataSource.resourceParams.filters.content);
375
+ filters = readFilterTree(parentDataSource.resourceParams.filters.content);
404
376
  }
405
377
  // Split dynamic destination keys by reference type. `state`/`prop`
406
378
  // refs resolve to bare identifiers (so they're useMemo deps as-is);
@@ -408,8 +380,8 @@ function buildStateRegistry(uidlNode) {
408
380
  // `useRouter()` declaration injected separately.
409
381
  var filterStateIds = [];
410
382
  var filterPropIds = [];
411
- for (var _i = 0, filters_3 = filters; _i < filters_3.length; _i++) {
412
- var f = filters_3[_i];
383
+ for (var _i = 0, _u = (0, utils_1.collectFilterLeafConditions)(filters); _i < _u.length; _i++) {
384
+ var f = _u[_i];
413
385
  if (!teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(f.destination)) {
414
386
  continue;
415
387
  }
@@ -463,6 +435,7 @@ function buildStateRegistry(uidlNode) {
463
435
  index: index++,
464
436
  dataSourceIdentifier: parentDataSource.identifier,
465
437
  arrayMapperRenderProp: arrayMapperRenderProp,
438
+ nodeId: typeof content.nodeId === 'string' ? content.nodeId : undefined,
466
439
  resourceDefinition: {
467
440
  dataSourceId: parentDataSource.resourceDef.dataSourceId || '',
468
441
  tableName: parentDataSource.resourceDef.tableName || '',
@@ -515,8 +488,8 @@ function buildStateRegistry(uidlNode) {
515
488
  }
516
489
  // Recurse into children
517
490
  if (((_r = node.content) === null || _r === void 0 ? void 0 : _r.children) && Array.isArray(node.content.children)) {
518
- for (var _u = 0, _v = node.content.children; _u < _v.length; _u++) {
519
- var child = _v[_u];
491
+ for (var _v = 0, _w = node.content.children; _v < _w.length; _v++) {
492
+ var child = _w[_v];
520
493
  traverse(child, parentDataSource);
521
494
  }
522
495
  }
@@ -541,8 +514,8 @@ function buildStateRegistry(uidlNode) {
541
514
  }
542
515
  }
543
516
  if (Array.isArray(node.children)) {
544
- for (var _w = 0, _x = node.children; _w < _x.length; _w++) {
545
- var child = _x[_w];
517
+ for (var _x = 0, _y = node.children; _x < _y.length; _x++) {
518
+ var child = _y[_x];
546
519
  traverse(child, parentDataSource);
547
520
  }
548
521
  }
@@ -558,6 +531,8 @@ function getStateVarsForUsage(usage) {
558
531
  setPageStateVar: "setDs_".concat(idx, "_page"),
559
532
  maxPagesStateVar: "ds_".concat(idx, "_maxPages"),
560
533
  setMaxPagesStateVar: "setDs_".concat(idx, "_maxPages"),
534
+ totalStateVar: "ds_".concat(idx, "_total"),
535
+ setTotalStateVar: "setDs_".concat(idx, "_total"),
561
536
  searchQueryVar: "ds_".concat(idx, "_searchQuery"),
562
537
  setSearchQueryVar: "setDs_".concat(idx, "_searchQuery"),
563
538
  debouncedSearchQueryVar: "ds_".concat(idx, "_debouncedQuery"),
@@ -622,11 +597,64 @@ function pushSearchUrlSyncEffects(effectStatements, usage, vars) {
622
597
  ? types.memberExpression(types.identifier(vars.combinedStateVar), types.identifier('debouncedQuery'))
623
598
  : types.identifier(vars.debouncedSearchQueryVar);
624
599
  };
600
+ // The query starts at its default (see `buildSearchQueryInitAST`), so the
601
+ // pair must agree on who goes first: the read-back adopts the address, THEN
602
+ // the write-back may write — before that, its first run would delete the very
603
+ // `?<key>=` the read-back is about to adopt.
604
+ var adoptedRefVar = "ds_".concat(usage.index, "_searchUrlAdopted");
605
+ var adopterVar = "ds_".concat(usage.index, "_adoptUrlSearch");
606
+ effectStatements.push(types.variableDeclaration('const', [
607
+ types.variableDeclarator(types.identifier(adoptedRefVar), types.callExpression(types.identifier('useRef'), [types.booleanLiteral(false)])),
608
+ ]), buildUrlSearchAdopter(adopterVar, usage, vars));
625
609
  // write-back: debounced query → `?<searchUrlParamKey>=`
626
- effectStatements.push(teleport_plugin_common_1.URLSearchParamSync.buildUrlWriteBackEffect(usage.searchUrlParamKey, buildDebouncedValueExpr(), buildDebouncedValueExpr()));
627
- // read-back: `?<searchUrlParamKey>=` → input (`setDs_N_searchQuery`). The
628
- // debounce effect propagates the change into the debounced value + fetch.
629
- effectStatements.push(teleport_plugin_common_1.URLSearchParamSync.buildUrlReadBackEffect(usage.searchUrlParamKey, vars.setSearchQueryVar));
610
+ effectStatements.push(teleport_plugin_common_1.URLSearchParamSync.buildUrlWriteBackEffect(usage.searchUrlParamKey, buildDebouncedValueExpr(), buildDebouncedValueExpr(), undefined, adoptedRefVar));
611
+ // read-back: `?<searchUrlParamKey>=` → the input AND the applied query at once
612
+ // (`ds_N_adoptUrlSearch`), so an address is not a typed change: it skips the
613
+ // debounce and keeps a page the address carries — `?searchKeyword=x&page=3`
614
+ // lands on page 3.
615
+ effectStatements.push(teleport_plugin_common_1.URLSearchParamSync.buildUrlReadBackEffect(usage.searchUrlParamKey, adopterVar, undefined, adoptedRefVar));
616
+ }
617
+ // `const ds_N_adoptUrlSearch = (update) => { … }`: the setter the search's URL
618
+ // read-back calls with its `(prev) => next` updater. It moves the input and the
619
+ // applied (debounced) query together. A list whose page is in the address
620
+ // keeps its page — the page sync sets it from the same address. Any other
621
+ // list starts the new query on page 1, as a typed change does in the debounce
622
+ // effect: an appending list has usually advanced past page 1 by the time the
623
+ // router is ready, and the query's first answer would otherwise be its page 2
624
+ // — empty for a query only a few rows match.
625
+ function buildUrlSearchAdopter(adopterVar, usage, vars) {
626
+ var update = types.identifier('update');
627
+ var pageReset = usageHasPageUrlSync(usage)
628
+ ? []
629
+ : [types.objectProperty(types.identifier('page'), types.numericLiteral(1))];
630
+ var applyToDebounced = usage.category === 'paginated+search'
631
+ ? // setDs_N_state((state) => {
632
+ // const next = update(state.debouncedQuery)
633
+ // return next === state.debouncedQuery ? state : { ...state, page: 1, debouncedQuery: next }
634
+ // })
635
+ types.callExpression(types.identifier(vars.setCombinedStateVar), [
636
+ types.arrowFunctionExpression([types.identifier('state')], types.blockStatement([
637
+ types.variableDeclaration('const', [
638
+ types.variableDeclarator(types.identifier('next'), types.callExpression(types.identifier('update'), [
639
+ types.memberExpression(types.identifier('state'), types.identifier('debouncedQuery')),
640
+ ])),
641
+ ]),
642
+ types.returnStatement(types.conditionalExpression(types.binaryExpression('===', types.identifier('next'), types.memberExpression(types.identifier('state'), types.identifier('debouncedQuery'))), types.identifier('state'), types.objectExpression(__spreadArray(__spreadArray([
643
+ types.spreadElement(types.identifier('state'))
644
+ ], pageReset, true), [
645
+ types.objectProperty(types.identifier('debouncedQuery'), types.identifier('next')),
646
+ ], false)))),
647
+ ])),
648
+ ])
649
+ : types.callExpression(types.identifier(vars.setDebouncedSearchQueryVar), [update]);
650
+ return types.variableDeclaration('const', [
651
+ types.variableDeclarator(types.identifier(adopterVar), types.arrowFunctionExpression([types.identifier('update')], types.blockStatement([
652
+ types.expressionStatement(types.callExpression(types.identifier(vars.setSearchQueryVar), [
653
+ types.identifier('update'),
654
+ ])),
655
+ types.expressionStatement(applyToDebounced),
656
+ ]))),
657
+ ]);
630
658
  }
631
659
  // How one usage reads and writes its current page.
632
660
  //
@@ -778,7 +806,7 @@ function pushFilterSortPageResetEffect(stateDeclarations, effectStatements, usag
778
806
  // ==================== MAIN PLUGIN ====================
779
807
  var createNextArrayMapperPaginationPlugin = function () {
780
808
  var paginationPlugin = function (structure) { return __awaiter(void 0, void 0, void 0, function () {
781
- var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, registry, getStaticPropsChunk, isPage, localized, stateDeclarations, effectStatements, needsUseRouter, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, usagesWithLoadingState, usageByProviderNode, cacheDeclarations, sharedOptions, cachedScopes, dataProvidersWithoutRepeaters, searchInputs, searchEnabledUsages, usageBySearchInput;
809
+ var uidl, chunks, dependencies, options, componentChunk, variableDeclaration, declarator, arrowFunction, blockStatement, rangeControls, hasRangeControl, registry, getStaticPropsChunk, isPage, localized, stateDeclarations, effectStatements, needsUseRouter, returnIndex, insertIndex, dataProviders, dataProvidersWithRepeaters, usageIndexByDataSourceId, loadingStateDeclarations, usagesWithLoadingState, usageByProviderNode, cacheDeclarations, sharedOptions, cachedScopes, dataProvidersWithoutRepeaters, searchInputs, usageBySearchInput, _i, searchInputs_1, input, usage;
782
810
  var _a, _b;
783
811
  return __generator(this, function (_c) {
784
812
  uidl = structure.uidl, chunks = structure.chunks, dependencies = structure.dependencies, options = structure.options;
@@ -802,6 +830,8 @@ var createNextArrayMapperPaginationPlugin = function () {
802
830
  return [2 /*return*/, structure];
803
831
  }
804
832
  blockStatement = arrowFunction.body;
833
+ rangeControls = (0, range_control_1.findRangeControls)(blockStatement);
834
+ hasRangeControl = rangeControls.length > 0;
805
835
  registry = buildStateRegistry(uidl.node);
806
836
  if (registry.usages.length === 0) {
807
837
  return [2 /*return*/, structure];
@@ -888,13 +918,12 @@ var createNextArrayMapperPaginationPlugin = function () {
888
918
  ]),
889
919
  ])),
890
920
  ]));
891
- // Immediate search query state — seeded with `searchDefaultValue` (or
892
- // the `searchUrlParamKey` URL value when bound) so the input is
893
- // pre-filled on mount. The debounced value above is seeded identically
894
- // so a deep-linked `?searchKeyword=` fetches filtered results on the
895
- // first paint: the paginated+search `initialData` is gated on
896
- // `!ds_N_state.debouncedQuery`, so a non-empty seed forces the
897
- // DataProvider to fetch instead of reusing the unfiltered SSG prefetch.
921
+ // Immediate search query state — seeded with `searchDefaultValue`, like
922
+ // the debounced value above. A deep-linked `?searchKeyword=` is adopted
923
+ // after mount by the URL read-back, which sets both at once; the
924
+ // paginated+search `initialData` is gated on `!ds_N_state.debouncedQuery`,
925
+ // so the adopted query makes the DataProvider fetch filtered rows
926
+ // instead of reusing the unfiltered SSG prefetch the first render shows.
898
927
  stateDeclarations.push(types.variableDeclaration('const', [
899
928
  types.variableDeclarator(types.arrayPattern([
900
929
  types.identifier(vars.searchQueryVar),
@@ -971,11 +1000,15 @@ var createNextArrayMapperPaginationPlugin = function () {
971
1000
  (0, filter_utils_1.pushPropIdsAsDeps)(countEffectDeps, countEffectSeen, usage.filterPropIds);
972
1001
  // Same goes for URL-driven filters (already documented above).
973
1002
  pushUrlSearchParamMemoDeps(countEffectDeps, usage);
1003
+ if (hasRangeControl) {
1004
+ stateDeclarations.push(buildTotalDeclaration(vars));
1005
+ }
974
1006
  stateDeclarations.push((0, count_effect_1.buildCountSeqDeclaration)(vars.countSeqRefVar));
975
1007
  effectStatements.push((0, count_effect_1.buildCountFetchEffect)({
976
1008
  fileName: fileName,
977
1009
  perPage: usage.perPage,
978
1010
  setMaxPagesVar: vars.setMaxPagesStateVar,
1011
+ setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
979
1012
  urlParams: urlParams,
980
1013
  deps: countEffectDeps,
981
1014
  countSeqRefVar: vars.countSeqRefVar,
@@ -1017,7 +1050,9 @@ var createNextArrayMapperPaginationPlugin = function () {
1017
1050
  // "Next" button, but not to label a numbered strip or to know when an
1018
1051
  // infinite list has reached its end, so those two modes fetch a live
1019
1052
  // count as well.
1020
- var needsLiveCount = !isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll;
1053
+ // A range text needs the live count too: the build-time count only
1054
+ // ever became a page count, never the total it shows.
1055
+ var needsLiveCount = !isPage || usage.paginationMode === 'numbered' || usage.infiniteScroll || hasRangeControl;
1021
1056
  if (needsLiveCount) {
1022
1057
  var fileName = (0, utils_1.generateSafeFileName)(usage.resourceDefinition.dataSourceType, usage.resourceDefinition.tableName, usage.resourceDefinition.dataSourceId);
1023
1058
  // ⛔ This list has no search input, so no `query` param — but it may
@@ -1039,11 +1074,15 @@ var createNextArrayMapperPaginationPlugin = function () {
1039
1074
  (0, filter_utils_1.pushStateIdsAsDeps)(countDeps, countDepsSeen, usage.filterStateIds);
1040
1075
  (0, filter_utils_1.pushPropIdsAsDeps)(countDeps, countDepsSeen, usage.filterPropIds);
1041
1076
  pushUrlSearchParamMemoDeps(countDeps, usage);
1077
+ if (hasRangeControl) {
1078
+ stateDeclarations.push(buildTotalDeclaration(vars));
1079
+ }
1042
1080
  stateDeclarations.push((0, count_effect_1.buildCountSeqDeclaration)(vars.countSeqRefVar));
1043
1081
  effectStatements.push((0, count_effect_1.buildCountFetchEffect)({
1044
1082
  fileName: fileName,
1045
1083
  perPage: usage.perPage,
1046
1084
  setMaxPagesVar: vars.setMaxPagesStateVar,
1085
+ setTotalVar: hasRangeControl ? vars.setTotalStateVar : undefined,
1047
1086
  urlParams: countUrlParams,
1048
1087
  deps: countDeps,
1049
1088
  countSeqRefVar: vars.countSeqRefVar,
@@ -1174,7 +1213,9 @@ var createNextArrayMapperPaginationPlugin = function () {
1174
1213
  // An auto-loading list needs something at the end of the rows for its
1175
1214
  // IntersectionObserver to watch.
1176
1215
  if (usage.infiniteScroll && usage.paginated && !usage.infiniteScrollLoadMore) {
1177
- insertSentinelAfterProvider(blockStatement, dp, (0, infinite_scroll_1.buildSentinelElement)((0, infinite_scroll_1.getInfiniteScrollVars)(usage.index)));
1216
+ insertSentinelAfterProvider(blockStatement, dp, function (parentTag) {
1217
+ return (0, infinite_scroll_1.buildSentinelElement)((0, infinite_scroll_1.getInfiniteScrollVars)(usage.index), parentTag);
1218
+ });
1178
1219
  }
1179
1220
  // Create API route for all categories (including 'plain' for components)
1180
1221
  ensureAPIRouteExists(options.extractedResources, usage, options.dataSources, (0, transformations_1.buildProductTransformOptions)(options));
@@ -1215,18 +1256,15 @@ var createNextArrayMapperPaginationPlugin = function () {
1215
1256
  dataProvidersWithoutRepeaters.forEach(function (dp) {
1216
1257
  stabilizeDataProviderWithoutRepeater(dp);
1217
1258
  });
1218
- searchInputs = findAllSearchInputsInJSX(blockStatement);
1219
- searchEnabledUsages = registry.usages.filter(function (u) { return u.searchEnabled; });
1220
- usageBySearchInput = new Map();
1221
- searchInputs.forEach(function (input, idx) {
1222
- if (idx >= searchEnabledUsages.length) {
1223
- return;
1259
+ searchInputs = (0, search_input_1.findAllSearchInputsInJSX)(blockStatement);
1260
+ usageBySearchInput = (0, search_input_1.pairSearchInputs)(searchInputs, registry.usages);
1261
+ for (_i = 0, searchInputs_1 = searchInputs; _i < searchInputs_1.length; _i++) {
1262
+ input = searchInputs_1[_i];
1263
+ usage = usageBySearchInput.get(input.node);
1264
+ if (usage) {
1265
+ (0, search_input_1.wireSearchInput)(input, getStateVarsForUsage(usage));
1224
1266
  }
1225
- var usage = searchEnabledUsages[idx];
1226
- var vars = getStateVarsForUsage(usage);
1227
- wireSearchInput(input.node, vars);
1228
- usageBySearchInput.set(input.node, usage);
1229
- });
1267
+ }
1230
1268
  // STEP 4.5: Wire the clear buttons, each to the input it shares a parent with.
1231
1269
  (0, search_clear_1.findAllSearchClearButtonsInJSX)(blockStatement).forEach(function (button) {
1232
1270
  var input = (0, search_clear_1.findSiblingSearchInput)(button, searchInputs.map(function (entry) { return entry.node; }));
@@ -1253,6 +1291,24 @@ var createNextArrayMapperPaginationPlugin = function () {
1253
1291
  }
1254
1292
  wirePaginationWidget(node, usage, getStateVarsForUsage(usage), usagesWithLoadingState.has(usage.index));
1255
1293
  });
1294
+ // STEP 5b: Wire the range texts that name their mapper.
1295
+ //
1296
+ // A `range` control carrying `data-tq-pagination-for` belongs to the mapper
1297
+ // whose editor node id it names, wherever it sits — a template's footer or
1298
+ // toolbar is laid out by the author, not inside a pagination container, so
1299
+ // the container-ownership rule above cannot reach it. One without the
1300
+ // attribute was wired by its container in STEP 5; one naming a mapper this
1301
+ // component does not render stays as drawn.
1302
+ rangeControls.forEach(function (control) {
1303
+ var ownerNodeId = (0, pagination_controls_1.readJSXAttributeString)(control, pagination_controls_1.PAGINATION_FOR_ATTR);
1304
+ if (!ownerNodeId) {
1305
+ return;
1306
+ }
1307
+ var owner = registry.usages.find(function (usage) { return usage.nodeId === ownerNodeId; });
1308
+ if (owner) {
1309
+ wireRangeControlFor(control, owner, getStateVarsForUsage(owner));
1310
+ }
1311
+ });
1256
1312
  // STEP 6: Update getStaticProps if this is a page
1257
1313
  if (isPage) {
1258
1314
  updateGetStaticProps(chunks, registry, dependencies, (_b = uidl.outputOptions) === null || _b === void 0 ? void 0 : _b.folderPath, localized);
@@ -1271,39 +1327,59 @@ exports.createNextArrayMapperPaginationPlugin = createNextArrayMapperPaginationP
1271
1327
  * It has to be a sibling: the provider's own child is a render-prop function,
1272
1328
  * and anything placed inside it would be re-created on every fetch — the
1273
1329
  * observer would then be watching an element that is no longer in the document.
1330
+ * The sentinel is built for the element it lands in (`buildSentinelElement`):
1331
+ * a fragment is transparent, a component's children may go anywhere.
1274
1332
  *
1275
1333
  * Returns false when the provider has no element parent to splice into (a
1276
1334
  * provider returned directly from the component), leaving the list on its
1277
1335
  * count-driven `hasMore` rather than injecting a stray node.
1278
1336
  */
1279
- function insertSentinelAfterProvider(blockStatement, provider, sentinel) {
1337
+ function insertSentinelAfterProvider(blockStatement, provider, buildSentinel) {
1280
1338
  var inserted = false;
1281
- var traverse = function (node) {
1339
+ var elementTagOf = function (node, enclosing) {
1340
+ var _a;
1341
+ if ((node === null || node === void 0 ? void 0 : node.type) === 'JSXFragment') {
1342
+ return enclosing;
1343
+ }
1344
+ if ((node === null || node === void 0 ? void 0 : node.type) !== 'JSXElement') {
1345
+ return enclosing;
1346
+ }
1347
+ var name = (_a = node.openingElement) === null || _a === void 0 ? void 0 : _a.name;
1348
+ if ((name === null || name === void 0 ? void 0 : name.type) !== 'JSXIdentifier') {
1349
+ return null;
1350
+ }
1351
+ if (name.name === 'Fragment') {
1352
+ return enclosing;
1353
+ }
1354
+ return /^[a-z]/.test(name.name) ? name.name : null;
1355
+ };
1356
+ var traverse = function (node, enclosing) {
1282
1357
  if (!node || inserted) {
1283
1358
  return;
1284
1359
  }
1360
+ var tag = elementTagOf(node, enclosing);
1285
1361
  if (Array.isArray(node.children)) {
1286
1362
  var at = node.children.indexOf(provider);
1287
1363
  if (at !== -1) {
1288
- node.children.splice(at + 1, 0, sentinel);
1364
+ node.children.splice(at + 1, 0, buildSentinel(tag));
1289
1365
  inserted = true;
1290
1366
  return;
1291
1367
  }
1292
- node.children.forEach(traverse);
1368
+ node.children.forEach(function (child) { return traverse(child, tag); });
1293
1369
  }
1294
1370
  if (node.body) {
1295
1371
  if (Array.isArray(node.body)) {
1296
- node.body.forEach(traverse);
1372
+ node.body.forEach(function (child) { return traverse(child, tag); });
1297
1373
  }
1298
1374
  else {
1299
- traverse(node.body);
1375
+ traverse(node.body, tag);
1300
1376
  }
1301
1377
  }
1302
1378
  if (node.consequent) {
1303
- traverse(node.consequent);
1379
+ traverse(node.consequent, tag);
1304
1380
  }
1305
1381
  if (node.alternate) {
1306
- traverse(node.alternate);
1382
+ traverse(node.alternate, tag);
1307
1383
  }
1308
1384
  // `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
1309
1385
  // panel, a gated section: the provider sits on the RIGHT of a logical
@@ -1311,22 +1387,22 @@ function insertSentinelAfterProvider(blockStatement, provider, sentinel) {
1311
1387
  // an argument. Left unvisited, the products list under the first tab of
1312
1388
  // a shop was never paginated or seeded, and rendered nothing.
1313
1389
  if (node.left) {
1314
- traverse(node.left);
1390
+ traverse(node.left, tag);
1315
1391
  }
1316
1392
  if (node.right) {
1317
- traverse(node.right);
1393
+ traverse(node.right, tag);
1318
1394
  }
1319
1395
  if (node.test) {
1320
- traverse(node.test);
1396
+ traverse(node.test, tag);
1321
1397
  }
1322
1398
  if (node.expression) {
1323
- traverse(node.expression);
1399
+ traverse(node.expression, tag);
1324
1400
  }
1325
1401
  if (node.argument) {
1326
- traverse(node.argument);
1402
+ traverse(node.argument, tag);
1327
1403
  }
1328
1404
  };
1329
- traverse(blockStatement);
1405
+ traverse(blockStatement, null);
1330
1406
  return inserted;
1331
1407
  }
1332
1408
  function findAllDataProvidersInJSX(blockStatement) {
@@ -1421,61 +1497,6 @@ function findArrayMapperRenderPropInDataProvider(dataProvider) {
1421
1497
  }
1422
1498
  return renderItemAttr.value.expression.params[0].name;
1423
1499
  }
1424
- function findAllSearchInputsInJSX(blockStatement) {
1425
- var results = [];
1426
- var traverse = function (node) {
1427
- var _a, _b, _c, _d, _e, _f;
1428
- if (!node) {
1429
- return;
1430
- }
1431
- if (node.type === 'JSXElement' && ((_b = (_a = node.openingElement) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.name) === 'input') {
1432
- var classAttr = (_c = node.openingElement.attributes) === null || _c === void 0 ? void 0 : _c.find(function (attr) { return attr.type === 'JSXAttribute' && attr.name.name === 'className'; });
1433
- var className = ((_d = classAttr === null || classAttr === void 0 ? void 0 : classAttr.value) === null || _d === void 0 ? void 0 : _d.value) || ((_f = (_e = classAttr === null || classAttr === void 0 ? void 0 : classAttr.value) === null || _e === void 0 ? void 0 : _e.expression) === null || _f === void 0 ? void 0 : _f.value) || '';
1434
- if (className.includes('search-input')) {
1435
- results.push({ node: node, className: className });
1436
- }
1437
- }
1438
- if (node.children && Array.isArray(node.children)) {
1439
- node.children.forEach(function (c) { return traverse(c); });
1440
- }
1441
- if (node.body) {
1442
- if (Array.isArray(node.body)) {
1443
- node.body.forEach(function (s) { return traverse(s); });
1444
- }
1445
- else {
1446
- traverse(node.body);
1447
- }
1448
- }
1449
- if (node.consequent) {
1450
- traverse(node.consequent);
1451
- }
1452
- if (node.alternate) {
1453
- traverse(node.alternate);
1454
- }
1455
- // `{activeTabIndex === 0 && (<div>…<DataProvider/>…</div>)}` — a tab
1456
- // panel, a gated section: the provider sits on the RIGHT of a logical
1457
- // expression (or in a ternary's `test`), which is not a child, a body or
1458
- // an argument. Left unvisited, the products list under the first tab of
1459
- // a shop was never paginated or seeded, and rendered nothing.
1460
- if (node.left) {
1461
- traverse(node.left);
1462
- }
1463
- if (node.right) {
1464
- traverse(node.right);
1465
- }
1466
- if (node.test) {
1467
- traverse(node.test);
1468
- }
1469
- if (node.expression) {
1470
- traverse(node.expression);
1471
- }
1472
- if (node.argument) {
1473
- traverse(node.argument);
1474
- }
1475
- };
1476
- traverse(blockStatement);
1477
- return results;
1478
- }
1479
1500
  /**
1480
1501
  * Every pagination widget in the JSX, paired with the mapper it belongs to.
1481
1502
  *
@@ -1761,7 +1782,13 @@ function updateDataProviderForSearchOnly(dp, usage, vars, fileName, cacheDeclara
1761
1782
  dp.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('persistDataDuringLoading'), types.jsxExpressionContainer(types.booleanLiteral(true))));
1762
1783
  }
1763
1784
  function updateDataProviderForPlain(dp, fileName, usage, localized) {
1764
- var _a, _b, _c, _d;
1785
+ var _a, _b, _c, _d, _e, _f;
1786
+ // A sort bound to state is the visitor's, which the server cannot know: rows
1787
+ // pre-rendered in the table's order would stand in for it, since a seeded
1788
+ // provider skips its first fetch — the paginated lists drop them the same way.
1789
+ if (usage.dynamicSort) {
1790
+ dp.openingElement.attributes = dp.openingElement.attributes.filter(function (attr) { var _a; return !(attr.type === 'JSXAttribute' && ((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name) === 'initialData'); });
1791
+ }
1765
1792
  var attrs = dp.openingElement.attributes;
1766
1793
  var clientCache = ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined;
1767
1794
  // Check if fetchData already exists
@@ -1784,10 +1811,12 @@ function updateDataProviderForPlain(dp, fileName, usage, localized) {
1784
1811
  }
1785
1812
  // Find the params attribute. A plain list may have none at all — but the
1786
1813
  // fetch it was just given sends `params` regardless, and in a localized
1787
- // project those have to carry the locale, so the attribute is created.
1814
+ // project those have to carry the locale, and a sorted list its sort, so the
1815
+ // attribute is created.
1788
1816
  var paramsAttrIndex = attrs.findIndex(function (attr) { return attr.type === 'JSXAttribute' && attr.name.name === 'params'; });
1817
+ var isSorted = usage.sorts.length > 0 || !!usage.dynamicSort;
1789
1818
  if (paramsAttrIndex === -1) {
1790
- if (!localized) {
1819
+ if (!localized && !isSorted) {
1791
1820
  return;
1792
1821
  }
1793
1822
  attrs.push(types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(types.objectExpression([]))));
@@ -1808,10 +1837,17 @@ function updateDataProviderForPlain(dp, fileName, usage, localized) {
1808
1837
  if (!paramsExpression) {
1809
1838
  return;
1810
1839
  }
1811
- // Build useMemo dependencies including filter state IDs
1840
+ // The repeater's sort, which the paginated and searchable lists add to the
1841
+ // params they rebuild: kept as they come, a plain list's params never
1842
+ // carried it, and the rows arrived in the table's own order.
1843
+ if (paramsExpression.type === 'ObjectExpression' && !hasObjectKey(paramsExpression, 'sorts')) {
1844
+ (0, sort_utils_1.appendSortsParam)(paramsExpression.properties, usage.sorts, usage.dynamicSort);
1845
+ }
1846
+ // Build useMemo dependencies including filter state IDs and a dynamic sort's state
1812
1847
  var memoDeps = [];
1813
1848
  var memoSeen = new Set();
1814
1849
  (0, filter_utils_1.pushStateIdsAsDeps)(memoDeps, memoSeen, usage.filterStateIds);
1850
+ (0, filter_utils_1.pushStateIdsAsDeps)(memoDeps, memoSeen, (_f = (_e = usage.dynamicSort) === null || _e === void 0 ? void 0 : _e.depStateIds) !== null && _f !== void 0 ? _f : []);
1815
1851
  (0, filter_utils_1.pushPropIdsAsDeps)(memoDeps, memoSeen, usage.filterPropIds);
1816
1852
  pushUrlSearchParamMemoDeps(memoDeps, usage);
1817
1853
  if (localized && paramsExpression.type === 'ObjectExpression') {
@@ -1825,6 +1861,13 @@ function updateDataProviderForPlain(dp, fileName, usage, localized) {
1825
1861
  // Replace the params attribute
1826
1862
  attrs[paramsAttrIndex] = types.jsxAttribute(types.jsxIdentifier('params'), types.jsxExpressionContainer(memoizedParams));
1827
1863
  }
1864
+ var hasObjectKey = function (object, key) {
1865
+ return object.properties.some(function (property) {
1866
+ return property.type === 'ObjectProperty' &&
1867
+ ((property.key.type === 'Identifier' && property.key.name === key) ||
1868
+ (property.key.type === 'StringLiteral' && property.key.value === key));
1869
+ });
1870
+ };
1828
1871
  function stabilizeDataProviderWithoutRepeater(dp) {
1829
1872
  var _a, _b, _c;
1830
1873
  var attrs = dp.openingElement.attributes;
@@ -1898,21 +1941,18 @@ accumulate) {
1898
1941
  ? (0, ast_1.buildCacheStoreThen)(cache)
1899
1942
  : uncachedTail;
1900
1943
  var networkChain = types.callExpression(types.memberExpression(parsedJson, types.identifier('then')), [responseTail]);
1944
+ // An appending list records the request it now stands on before making it
1945
+ // (see `buildAccumulatingRequestMark`).
1901
1946
  var arrow = cache
1902
1947
  ? types.arrowFunctionExpression([types.identifier('params')], (0, ast_1.buildCachedFetchBody)(networkChain, cache))
1903
- : types.arrowFunctionExpression([types.identifier('params')], networkChain);
1948
+ : accumulate
1949
+ ? types.arrowFunctionExpression([types.identifier('params')], types.blockStatement([
1950
+ (0, infinite_scroll_1.buildAccumulatingRequestMark)((0, infinite_scroll_1.getInfiniteScrollVars)(accumulate.usage.index)),
1951
+ types.returnStatement(networkChain),
1952
+ ]))
1953
+ : types.arrowFunctionExpression([types.identifier('params')], networkChain);
1904
1954
  return types.jsxAttribute(types.jsxIdentifier('fetchData'), types.jsxExpressionContainer(types.callExpression(types.identifier('useCallback'), [arrow, types.arrayExpression([])])));
1905
1955
  }
1906
- function wireSearchInput(inputNode, vars) {
1907
- // Remove existing onChange and value
1908
- inputNode.openingElement.attributes = inputNode.openingElement.attributes.filter(function (attr) { var _a; return !['onChange', 'value'].includes((_a = attr.name) === null || _a === void 0 ? void 0 : _a.name); });
1909
- // Add onChange
1910
- inputNode.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('onChange'), types.jsxExpressionContainer(types.arrowFunctionExpression([types.identifier('e')], types.callExpression(types.identifier(vars.setSearchQueryVar), [
1911
- types.memberExpression(types.memberExpression(types.identifier('e'), types.identifier('target')), types.identifier('value')),
1912
- ])))));
1913
- // Add value
1914
- inputNode.openingElement.attributes.push(types.jsxAttribute(types.jsxIdentifier('value'), types.jsxExpressionContainer(types.identifier(vars.searchQueryVar))));
1915
- }
1916
1956
  /**
1917
1957
  * The `fetchData` attribute for one paginated usage.
1918
1958
  *
@@ -1928,6 +1968,32 @@ function buildFetchDataForUsage(fileName, usage) {
1928
1968
  }
1929
1969
  return createFetchDataAttribute(fileName, ((_a = usage.cache) === null || _a === void 0 ? void 0 : _a.client) ? usage.cache : undefined);
1930
1970
  }
1971
+ /** `const [ds_N_total, setDs_N_total] = useState(null)` — declared only beside a range control. */
1972
+ function buildTotalDeclaration(vars) {
1973
+ return types.variableDeclaration('const', [
1974
+ types.variableDeclarator(types.arrayPattern([
1975
+ types.identifier(vars.totalStateVar),
1976
+ types.identifier(vars.setTotalStateVar),
1977
+ ]), types.callExpression(types.identifier('useState'), [types.nullLiteral()])),
1978
+ ]);
1979
+ }
1980
+ /**
1981
+ * Fills one `range` text with the live row range of `usage` (see
1982
+ * `range-control.ts`). A mapper that does not page has no count to show, so
1983
+ * its range text is left as drawn.
1984
+ */
1985
+ function wireRangeControlFor(control, usage, vars) {
1986
+ if (!usage.paginated) {
1987
+ return;
1988
+ }
1989
+ var currentPageExpr = getPageAccessors(usage, vars).currentPageExpr;
1990
+ (0, range_control_1.wireRangeControl)(control, {
1991
+ totalVar: vars.totalStateVar,
1992
+ currentPageExpr: currentPageExpr(),
1993
+ perPage: usage.perPage,
1994
+ infiniteScroll: usage.infiniteScroll,
1995
+ });
1996
+ }
1931
1997
  /**
1932
1998
  * Wires the authored Load More button of an appending list.
1933
1999
  *
@@ -1967,6 +2033,11 @@ function wireLoadMoreButton(paginationNode, usage, vars, hasLoadingState) {
1967
2033
  * buttons beside its Load More.
1968
2034
  */
1969
2035
  function wirePaginationWidget(paginationNode, usage, vars, hasLoadingState) {
2036
+ // A range text inside the container takes the container's owner, like the
2037
+ // buttons beside it; one naming its own mapper is wired in STEP 5b instead.
2038
+ (0, pagination_controls_1.findAllPaginationControls)(paginationNode, 'range')
2039
+ .filter(function (control) { return !(0, pagination_controls_1.readJSXAttributeString)(control, pagination_controls_1.PAGINATION_FOR_ATTR); })
2040
+ .forEach(function (control) { return wireRangeControlFor(control, usage, vars); });
1970
2041
  if (usage.infiniteScroll) {
1971
2042
  // Only the Load More button survives; page controls make no sense for a
1972
2043
  // list the visitor never leaves.
@@ -2254,17 +2325,9 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2254
2325
  }
2255
2326
  // Add filters if present
2256
2327
  // Filter out dynamic destinations for getStaticProps (they can't be resolved at build time)
2257
- var staticFilters = (usage.filters || []).filter(function (f) { return !teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(f.destination); });
2328
+ var staticFilters = staticFiltersOf(usage);
2258
2329
  if (staticFilters.length > 0) {
2259
- fetchParams.push(types.objectProperty(types.identifier('filters'), types.callExpression(types.memberExpression(types.identifier('JSON'), types.identifier('stringify')), [
2260
- types.arrayExpression(staticFilters.map(function (filter) {
2261
- return types.objectExpression([
2262
- types.objectProperty(types.identifier('source'), types.stringLiteral(filter.source || '')),
2263
- types.objectProperty(types.identifier('destination'), types.stringLiteral(typeof filter.destination === 'string' ? filter.destination : '')),
2264
- types.objectProperty(types.identifier('operand'), types.stringLiteral(filter.operand || '')),
2265
- ]);
2266
- })),
2267
- ])));
2330
+ fetchParams.push(types.objectProperty(types.identifier('filters'), (0, filter_utils_1.buildStaticFiltersStringifyCall)(staticFilters)));
2268
2331
  }
2269
2332
  (0, items_path_1.appendItemsPathParam)(fetchParams, usage.itemsPath, 'itemsPath');
2270
2333
  // The prefetch is per locale — getStaticProps runs once per language.
@@ -2304,7 +2367,7 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2304
2367
  var maxPagesPropName_1 = "".concat(vars.propsPrefix, "_maxPages");
2305
2368
  // Use filter-specific count variable if usage has static filters
2306
2369
  // (dynamic filters can't be resolved at build time)
2307
- var staticFiltersForCount = (usage.filters || []).filter(function (f) { return !teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(f.destination); });
2370
+ var staticFiltersForCount = staticFiltersOf(usage);
2308
2371
  // A count scoped to an inner array is a different number from the
2309
2372
  // whole-payload one, so it gets its own variable just like a filtered one.
2310
2373
  var hasScopedCount = staticFiltersForCount.length > 0 || !!usage.itemsPath;
@@ -2355,7 +2418,7 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2355
2418
  usagesByFilters.forEach(function (usages, filtersKey) {
2356
2419
  var firstUsage = usages[0];
2357
2420
  // Filter out dynamic destinations for getStaticProps (they can't be resolved at build time)
2358
- var staticFiltersForCount = (firstUsage.filters || []).filter(function (f) { return !teleport_plugin_common_1.ASTUtils.isUIDLDynamicReference(f.destination); });
2421
+ var staticFiltersForCount = staticFiltersOf(firstUsage);
2359
2422
  var hasStaticFilters = staticFiltersForCount.length > 0;
2360
2423
  // Create unique count variable name based on filters (and inner-array
2361
2424
  // scope — must match the naming at the maxPages site above)
@@ -2375,15 +2438,7 @@ function updateGetStaticProps(chunks, registry, dependencies, folderPath, locali
2375
2438
  // Build count params if static filters exist
2376
2439
  var countParams = [];
2377
2440
  if (hasStaticFilters) {
2378
- countParams.push(types.objectProperty(types.identifier('filters'), types.callExpression(types.memberExpression(types.identifier('JSON'), types.identifier('stringify')), [
2379
- types.arrayExpression(staticFiltersForCount.map(function (f) {
2380
- return types.objectExpression([
2381
- types.objectProperty(types.identifier('source'), types.stringLiteral(f.source || '')),
2382
- types.objectProperty(types.identifier('destination'), types.stringLiteral(typeof f.destination === 'string' ? f.destination : '')),
2383
- types.objectProperty(types.identifier('operand'), types.stringLiteral(f.operand || '')),
2384
- ]);
2385
- })),
2386
- ])));
2441
+ countParams.push(types.objectProperty(types.identifier('filters'), (0, filter_utils_1.buildStaticFiltersStringifyCall)(staticFiltersForCount)));
2387
2442
  }
2388
2443
  (0, items_path_1.appendItemsPathParam)(countParams, firstUsage.itemsPath, 'collectionPath');
2389
2444
  fetchesArray.elements.push(types.callExpression(types.memberExpression(types.callExpression(types.memberExpression(types.identifier(fetcherImportName), types.identifier('fetchCount')), countParams.length > 0 ? [types.objectExpression(countParams)] : []), types.identifier('catch')), [