coralite-plugin-aggregation 0.9.0 → 0.10.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.
@@ -10,95 +10,90 @@
10
10
  import { defineComponent } from 'coralite/plugins'
11
11
 
12
12
  export default defineComponent({
13
- properties: (context) => {
14
- const props = context.properties
15
-
16
- const ariaLabel = props['aria-label'] || 'Page navigation'
17
-
18
- const currentPage = parseInt(props['current-page'] || '1', 10)
19
- const totalPages = parseInt(props['total-pages'] || '1', 10)
20
- const maxVisible = parseInt(props['max-visible'] || '5', 10)
21
-
22
- const baseUrl = props['base-url'] || ''
23
- const urlPrefix = props['url-prefix'] || ''
24
- const segment = props['segment'] || 'page'
25
- const ellipsis = props['ellipsis'] || '...'
26
-
27
- // Compute pagination synchronously
28
- const getPageUrl = (page) => {
29
- if (page === 1) {
30
- return baseUrl
13
+ attributes: {
14
+ ariaLabel: { type: String, default: 'Page navigation' },
15
+ currentPage: { type: Number, default: 1 },
16
+ totalPages: { type: Number, default: 1 },
17
+ maxVisible: { type: Number, default: 5 },
18
+ baseUrl: { type: String, default: '' },
19
+ urlPrefix: { type: String, default: '' },
20
+ segment: { type: String, default: 'page' },
21
+ ellipsis: { type: String, default: '...' }
22
+ },
23
+ getters: {
24
+ paginationLinks: (state) => {
25
+ const getPageUrl = (page) => {
26
+ if (page === 1) {
27
+ return state.baseUrl;
28
+ }
29
+
30
+ const cleanPrefix = state.urlPrefix.endsWith('/') ? state.urlPrefix : `${state.urlPrefix}/`;
31
+ return `${cleanPrefix}${state.segment}/${page}.html`;
31
32
  }
32
-
33
- const cleanPrefix = urlPrefix.endsWith('/') ? urlPrefix : `${urlPrefix}/`
34
- return `${cleanPrefix}${segment}/${page}.html`
35
- }
36
33
 
37
- const createItem = (page, text, isActive, isDisabled) => {
38
- let className = 'page-item'
39
- if (isActive) className += ' active'
40
- if (isDisabled) className += ' disabled'
34
+ const createItem = (page, text, isActive, isDisabled) => {
35
+ let className = 'page-item';
36
+ if (isActive) className += ' active';
37
+ if (isDisabled) className += ' disabled';
41
38
 
42
- let attr = ''
43
- if (isActive) attr += ' aria-current="page"'
44
- if (isDisabled) attr += ' tabindex="-1" aria-disabled="true"'
39
+ let attr = '';
40
+ if (isActive) attr += ' aria-current="page"';
41
+ if (isDisabled) attr += ' tabindex="-1" aria-disabled="true"';
45
42
 
46
- const href = isDisabled ? '#' : getPageUrl(page)
43
+ const href = isDisabled ? '#' : getPageUrl(page);
47
44
 
48
- return `<li class="${className}"><a class="page-link" href="${href}"${attr}>${text}</a></li>`
49
- }
45
+ return `<li class="${className}"><a class="page-link" href="${href}"${attr}>${text}</a></li>`;
46
+ }
50
47
 
51
- let links = ''
48
+ let links = '';
52
49
 
53
- // Previous Link
54
- links += createItem(currentPage - 1, 'Previous', false, currentPage <= 1)
50
+ // Previous Link
51
+ links += createItem(state.currentPage - 1, 'Previous', false, state.currentPage <= 1);
55
52
 
56
- // Calculate Window (Start/End)
57
- const half = Math.floor(maxVisible / 2)
58
- let start = currentPage - half
59
- let end = currentPage + half
53
+ // Calculate Window (Start/End)
54
+ const half = Math.floor(state.maxVisible / 2);
55
+ let start = state.currentPage - half;
56
+ let end = state.currentPage + half;
60
57
 
61
- if (start < 1) {
62
- start = 1
63
- end = Math.min(totalPages, maxVisible)
64
- }
58
+ if (start < 1) {
59
+ start = 1;
60
+ end = Math.min(state.totalPages, state.maxVisible);
61
+ }
65
62
 
66
- if (end > totalPages) {
67
- end = totalPages
68
- start = Math.max(1, totalPages - maxVisible + 1)
69
- }
63
+ if (end > state.totalPages) {
64
+ end = state.totalPages;
65
+ start = Math.max(1, state.totalPages - state.maxVisible + 1);
66
+ }
70
67
 
71
- const showFirst = start > 1
72
- const showLast = end < totalPages
68
+ const showFirst = start > 1;
69
+ const showLast = end < state.totalPages;
73
70
 
74
- // First page + ellipsis
75
- if (showFirst) {
76
- links += createItem(1, '1', false, false)
77
- if (start > 2) {
78
- links += `<li class="page-item disabled"><span class="page-link">${ellipsis}</span></li>`
71
+ // First page + ellipsis
72
+ if (showFirst) {
73
+ links += createItem(1, '1', false, false);
74
+ if (start > 2) {
75
+ links += `<li class="page-item disabled"><span class="page-link">${state.ellipsis}</span></li>`;
76
+ }
79
77
  }
80
- }
81
78
 
82
- // Main window loop
83
- for (let i = start; i <= end; i++) {
84
- links += createItem(i, i, i === currentPage, false)
85
- }
79
+ // Main window loop
80
+ for (let i = start; i <= end; i++) {
81
+ links += createItem(i, i, i === state.currentPage, false);
82
+ }
86
83
 
87
- // Last page + ellipsis
88
- if (showLast) {
89
- if (end < totalPages - 1) {
90
- links += `<li class="page-item disabled"><span class="page-link">${ellipsis}</span></li>`
84
+ // Last page + ellipsis
85
+ if (showLast) {
86
+ if (end < state.totalPages - 1) {
87
+ links += `<li class="page-item disabled"><span class="page-link">${state.ellipsis}</span></li>`;
88
+ }
89
+ links += createItem(state.totalPages, state.totalPages, false, false);
91
90
  }
92
- links += createItem(totalPages, totalPages, false, false)
93
- }
94
91
 
95
- // Next Link
96
- links += createItem(currentPage + 1, 'Next', false, currentPage >= totalPages)
92
+ // Next Link
93
+ links += createItem(state.currentPage + 1, 'Next', false, state.currentPage >= state.totalPages);
97
94
 
98
- return {
99
- ariaLabel,
100
- paginationLinks: links
95
+ return links;
101
96
  }
102
97
  }
103
98
  })
104
- </script>
99
+ </script>
package/lib/index.js CHANGED
@@ -1,248 +1,244 @@
1
1
  import { definePlugin } from 'coralite'
2
2
  import path from 'node:path'
3
3
 
4
- /**
5
- * Aggregates content based on configuration
6
- * @param {import('../types/index.js').AggregationOptions} options
7
- * @param {Object} context
8
- * @returns {Promise<any[]>}
9
- */
10
- async function aggregationMethod (options, context) {
11
- const {
12
- path: paths = [],
13
- template,
14
- pagination,
15
- filter,
16
- sort,
17
- limit,
18
- offset = 0,
19
- recursive = false,
20
- transformProperties
21
- } = options
22
-
23
- const contextProperties = context.properties || {}
24
- const pagesRoot = this.options.pages
25
-
26
- // Collect pages
27
- let allPages = []
28
- const uniquePaths = new Set()
29
-
30
- for (const relativePath of paths) {
31
- const targetPath = path.join(pagesRoot, relativePath)
32
-
33
- if (!recursive) {
34
- const pagesInDir = this.pages.getListByPath(targetPath)
35
- if (pagesInDir) {
36
- for (const page of pagesInDir) {
37
- const pagePath = page.url ? page.url.pathname : page.path.pathname
38
- if (!uniquePaths.has(pagePath)) {
39
- uniquePaths.add(pagePath)
40
- allPages.push(page)
4
+ export const aggregation = definePlugin({
5
+ name: 'aggregation',
6
+
7
+ async exports(options) {
8
+ const {
9
+ path: paths = [],
10
+ component,
11
+ pagination,
12
+ filter,
13
+ sort,
14
+ limit,
15
+ offset = 0,
16
+ recursive = false,
17
+ transformState
18
+ } = options
19
+ const state = this.state || {}
20
+ const app = this.app
21
+ const pagesRoot = app.options.pages
22
+
23
+ // Collect pages
24
+ let allPages = []
25
+ const uniquePaths = new Set()
26
+
27
+ for (const relativePath of paths) {
28
+ const targetPath = path.join(pagesRoot, relativePath)
29
+
30
+ if (!recursive) {
31
+ const pagesInDir = app.pages.getListByPath(targetPath)
32
+
33
+ if (pagesInDir) {
34
+ for (const page of pagesInDir) {
35
+ const pagePath = page.path.pathname
36
+
37
+ if (!uniquePaths.has(pagePath)) {
38
+ uniquePaths.add(pagePath)
39
+ allPages.push(page)
40
+ }
41
41
  }
42
42
  }
43
- }
44
- } else {
45
- // Recursive search
46
- for (const page of this.pages.list) {
47
- const dirname = page.file ? page.file.dirname : page.path.dirname
48
- if (dirname === targetPath || dirname.startsWith(targetPath + path.sep)) {
49
- const pagePath = page.url ? page.url.pathname : page.path.pathname
50
- if (!uniquePaths.has(pagePath)) {
51
- uniquePaths.add(pagePath)
52
- allPages.push(page)
43
+ } else {
44
+ // Recursive search
45
+ for (const page of app.pages.list) {
46
+ const dirname = page.path.dirname
47
+
48
+ if (dirname === targetPath || dirname.startsWith(targetPath + path.sep)) {
49
+ const pagePath = page.path.pathname
50
+
51
+ if (!uniquePaths.has(pagePath)) {
52
+ uniquePaths.add(pagePath)
53
+ allPages.push(page)
54
+ }
53
55
  }
54
56
  }
55
57
  }
56
58
  }
57
- }
58
-
59
- // Filter
60
- if (typeof filter === 'function') {
61
- allPages = allPages.filter(page => {
62
- const pageProps = (page.result && page.result.properties) ? page.result.properties : page.properties
63
- return filter(pageProps)
64
- })
65
- }
66
-
67
- // Sort
68
- if (typeof sort === 'function') {
69
- allPages.sort((a, b) => {
70
- const propsA = (a.result && a.result.properties) ? a.result.properties : a.properties
71
- const propsB = (b.result && b.result.properties) ? b.result.properties : b.properties
72
- return sort(propsA, propsB)
73
- })
74
- }
75
-
76
- // Pagination
77
- let startIndex = offset
78
- let endIndex = allPages.length
79
-
80
- let currentPage = 1
81
- let totalPages = 1
82
-
83
- const currentRenderContext = context.renderContext
84
- const buildId = currentRenderContext && currentRenderContext.buildId
85
-
86
- if (limit) {
87
- if (pagination) {
88
- const segment = pagination.segment || 'page'
89
- const urlPathname = context.page.url.pathname
90
59
 
91
- const escapedSegment = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
92
- const segmentRegex = new RegExp(`/${escapedSegment}/(\\d+)`)
93
- const match = urlPathname.match(segmentRegex)
60
+ // Filter
61
+ if (typeof filter === 'function') {
62
+ allPages = allPages.filter(page => {
63
+ return filter(page.result.state)
64
+ })
65
+ }
66
+
67
+ // Sort
68
+ if (typeof sort === 'function') {
69
+ allPages.sort((a, b) => {
70
+ const propsA = (a.result && a.result.state) ? a.result.state : a.state
71
+ const propsB = (b.result && b.result.state) ? b.result.state : b.state
72
+ return sort(propsA, propsB)
73
+ })
74
+ }
94
75
 
95
- if (match) {
96
- currentPage = parseInt(match[1], 10)
97
- }
76
+ // Pagination
77
+ let startIndex = offset
78
+ let endIndex = allPages.length
98
79
 
99
- startIndex = offset + (currentPage - 1) * limit
100
- endIndex = startIndex + limit
101
- totalPages = Math.ceil(allPages.length / limit)
80
+ let currentPage = 1
81
+ let totalPages = 1
102
82
 
103
- if (!match && currentPage === 1 && totalPages > 1 && buildId) {
104
- const currentPathname = context.page.file.pathname
105
- const currentFilename = context.page.file.filename
106
- const currentDirname = context.page.file.dirname
83
+ const currentRenderContext = this.renderContext
84
+ const buildId = currentRenderContext && currentRenderContext.buildId
107
85
 
108
- let targetDir = currentDirname
109
- let urlPrefixBase = ''
86
+ if (limit) {
87
+ if (pagination) {
88
+ const segment = pagination.segment || 'page'
89
+ const urlPathname = this.page.url.pathname
110
90
 
111
- if (currentFilename === 'index.html') {
112
- targetDir = currentDirname
113
- urlPrefixBase = path.dirname(urlPathname)
114
- } else {
115
- const basename = path.basename(currentFilename, path.extname(currentFilename))
116
- targetDir = path.join(currentDirname, basename)
117
- urlPrefixBase = urlPathname.replace(path.extname(currentFilename), '')
91
+ const escapedSegment = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
92
+ const segmentRegex = new RegExp(`/${escapedSegment}/(\\d+)`)
93
+ const match = urlPathname.match(segmentRegex)
94
+
95
+ if (match) {
96
+ currentPage = parseInt(match[1], 10)
118
97
  }
119
98
 
120
- if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
99
+ startIndex = offset + (currentPage - 1) * limit
100
+ endIndex = startIndex + limit
101
+ totalPages = Math.ceil(allPages.length / limit)
102
+
103
+ if (!match && currentPage === 1 && totalPages > 1 && buildId) {
104
+ const currentPathname = this.page.file.pathname
105
+ const currentFilename = this.page.file.filename
106
+ const currentDirname = this.page.file.dirname
107
+
108
+ let targetDir = currentDirname
109
+ let urlPrefixBase = ''
110
+
111
+ if (currentFilename === 'index.html') {
112
+ targetDir = currentDirname
113
+ urlPrefixBase = path.dirname(urlPathname)
114
+ } else {
115
+ const basename = path.basename(currentFilename, path.extname(currentFilename))
116
+ targetDir = path.join(currentDirname, basename)
117
+ urlPrefixBase = urlPathname.replace(path.extname(currentFilename), '')
118
+ }
121
119
 
122
- if (currentFilename === 'index.html') {
123
- urlPrefixBase = path.dirname(urlPathname)
124
120
  if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
125
- }
126
121
 
127
- const currentItem = this.pages.getItem(currentPathname)
128
-
129
- for (let i = 2; i <= totalPages; i++) {
130
- const newPathname = path.join(targetDir, segment, `${i}.html`)
131
-
132
- const virtualItem = {
133
- content: currentItem ? currentItem.content : '',
134
- path: {
135
- pathname: newPathname,
136
- dirname: path.dirname(newPathname),
137
- filename: path.basename(newPathname)
138
- },
139
- properties: {
140
- paginationBaseUrl: urlPathname,
141
- paginationUrlPrefix: urlPrefixBase
142
- },
143
- type: 'page'
122
+ if (currentFilename === 'index.html') {
123
+ urlPrefixBase = path.dirname(urlPathname)
124
+ if (!urlPrefixBase.endsWith('/')) urlPrefixBase += '/'
144
125
  }
145
126
 
146
- await this.addRenderQueue(virtualItem, buildId)
127
+ const currentItem = app.pages.getItem(currentPathname)
128
+
129
+ for (let i = 2; i <= totalPages; i++) {
130
+ const newPathname = path.join(targetDir, segment, `${i}.html`)
131
+
132
+ const virtualItem = {
133
+ content: currentItem ? currentItem.content : '',
134
+ path: {
135
+ pathname: newPathname,
136
+ dirname: path.dirname(newPathname),
137
+ filename: path.basename(newPathname)
138
+ },
139
+ state: {
140
+ paginationBaseUrl: urlPathname,
141
+ paginationUrlPrefix: urlPrefixBase
142
+ },
143
+ type: 'page'
144
+ }
145
+
146
+ await app.addRenderQueue(virtualItem, buildId)
147
+ }
147
148
  }
149
+ } else {
150
+ endIndex = Math.min(startIndex + limit, allPages.length)
148
151
  }
149
- } else {
150
- endIndex = Math.min(startIndex + limit, allPages.length)
151
152
  }
152
- }
153
-
154
- const paginatedPages = allPages.slice(startIndex, endIndex)
155
- const resultNodes = []
156
-
157
- for (const page of paginatedPages) {
158
- const pageProps = (page.result && page.result.properties) ? page.result.properties : page.properties
159
- let itemProps = { ...pageProps }
160
-
161
- // Apply properties transformations
162
- if (transformProperties && typeof transformProperties === 'object') {
163
- for (const key in transformProperties) {
164
- if (Object.prototype.hasOwnProperty.call(transformProperties, key)) {
165
- const transform = transformProperties[key]
166
- if (typeof transform === 'string') {
167
- itemProps[key] = pageProps[transform]
168
- } else if (typeof transform === 'function') {
169
- itemProps[key] = transform(pageProps)
153
+
154
+ const paginatedPages = allPages.slice(startIndex, endIndex)
155
+ const resultNodes = []
156
+
157
+ for (const page of paginatedPages) {
158
+ const pageProps = (page.result && page.result.state) ? page.result.state : page.state
159
+ let itemProps = { ...pageProps }
160
+
161
+ // Apply properties transformations
162
+ if (transformState && typeof transformState === 'object') {
163
+ for (const key in transformState) {
164
+ if (Object.prototype.hasOwnProperty.call(transformState, key)) {
165
+ const transform = transformState[key]
166
+ if (typeof transform === 'string') {
167
+ itemProps[key] = pageProps[transform]
168
+ } else if (typeof transform === 'function') {
169
+ itemProps[key] = transform(pageProps)
170
+ }
170
171
  }
171
172
  }
172
173
  }
173
- }
174
174
 
175
- if (template) {
176
- const component = await this.createComponentElement({
177
- id: template,
178
- properties: itemProps,
179
- page: context.page,
180
- renderContext: currentRenderContext
181
- })
175
+ if (component) {
176
+ const componentElement = await app.createComponentElement({
177
+ id: component,
178
+ state: itemProps,
179
+ page: this.page,
180
+ renderContext: currentRenderContext
181
+ })
182
182
 
183
- if (component && component.children) {
184
- resultNodes.push(...component.children)
183
+ if (componentElement && componentElement.children) {
184
+ resultNodes.push(...componentElement.children)
185
+ }
185
186
  }
186
187
  }
187
- }
188
188
 
189
- if (pagination) {
190
- const paginationTemplateId = pagination.template || 'coralite-pagination'
191
- const urlPathname = context.page.url.pathname
192
-
193
- let baseUrl = urlPathname
194
- let urlPrefix = ''
189
+ if (pagination) {
190
+ const paginationComponentId = pagination.component || 'coralite-pagination'
191
+ const urlPathname = this.page.url.pathname
195
192
 
196
- if (contextProperties.paginationBaseUrl) {
197
- baseUrl = contextProperties.paginationBaseUrl
198
- }
193
+ let baseUrl = urlPathname
194
+ let urlPrefix = ''
199
195
 
200
- if (contextProperties.paginationUrlPrefix) {
201
- urlPrefix = contextProperties.paginationUrlPrefix
202
- } else {
203
- if (baseUrl.endsWith('/index.html') || baseUrl.endsWith('/')) {
204
- urlPrefix = path.dirname(baseUrl)
196
+ if (state.paginationBaseUrl) {
197
+ baseUrl = state.paginationBaseUrl
198
+ }
199
+
200
+ if (state.paginationUrlPrefix) {
201
+ urlPrefix = state.paginationUrlPrefix
205
202
  } else {
206
- const basename = path.basename(baseUrl, '.html')
207
- urlPrefix = path.join(path.dirname(baseUrl), basename)
203
+ if (baseUrl.endsWith('/index.html') || baseUrl.endsWith('/')) {
204
+ urlPrefix = path.dirname(baseUrl)
205
+ } else {
206
+ const basename = path.basename(baseUrl, '.html')
207
+ urlPrefix = path.join(path.dirname(baseUrl), basename)
208
+ }
208
209
  }
209
- }
210
210
 
211
- if (!urlPrefix.endsWith('/')) urlPrefix += '/'
212
-
213
- const paginationProps = {
214
- 'current-page': String(currentPage),
215
- 'total-pages': String(totalPages),
216
- 'base-url': baseUrl,
217
- 'url-prefix': urlPrefix,
218
- segment: pagination.segment || 'page',
219
- 'max-visible': String(pagination.maxVisible || 5),
220
- 'aria-label': pagination.ariaLabel || 'Pagination',
221
- ellipsis: pagination.ellipsis || '...'
222
- }
211
+ if (!urlPrefix.endsWith('/')) urlPrefix += '/'
212
+
213
+ const paginationProps = {
214
+ 'current-page': String(currentPage),
215
+ 'total-pages': String(totalPages),
216
+ 'base-url': baseUrl,
217
+ 'url-prefix': urlPrefix,
218
+ segment: pagination.segment || 'page',
219
+ 'max-visible': String(pagination.maxVisible || 5),
220
+ 'aria-label': pagination.ariaLabel || 'Pagination',
221
+ ellipsis: pagination.ellipsis || '...'
222
+ }
223
223
 
224
- const component = await this.createComponentElement({
225
- id: paginationTemplateId,
226
- properties: paginationProps,
227
- page: context.page,
228
- renderContext: currentRenderContext
229
- })
224
+ const componentElement = await app.createComponentElement({
225
+ id: paginationComponentId,
226
+ state: paginationProps,
227
+ page: this.page,
228
+ renderContext: currentRenderContext
229
+ })
230
230
 
231
- if (component && component.children) {
232
- resultNodes.push(...component.children)
231
+ if (componentElement && componentElement.children) {
232
+ resultNodes.push(...componentElement.children)
233
+ }
233
234
  }
234
- }
235
-
236
- return resultNodes
237
- }
238
235
 
239
- export const aggregation = definePlugin({
240
- name: 'aggregation',
241
- method: aggregationMethod,
236
+ return resultNodes
237
+ },
242
238
  components: [
243
239
  // @ts-ignore
244
240
  path.join(import.meta.dirname, 'components/coralite-pagination.html')
245
241
  ]
246
242
  })
247
243
 
248
- export default aggregation
244
+ export default aggregation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-plugin-aggregation",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "A Coralite plugin for dynamically collecting, filtering, sorting, and displaying content across multiple sources. Build database-free Coralite websites with automated content aggregation.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -43,7 +43,7 @@
43
43
  "devDependencies": {
44
44
  "@stylistic/eslint-plugin-js": "^4.2.0",
45
45
  "@stylistic/eslint-plugin-plus": "^4.2.0",
46
- "coralite": "^0.33.1"
46
+ "coralite": "^0.34.0"
47
47
  },
48
48
  "scripts": {
49
49
  "test": "node --test ./tests/index.spec.js"
package/types/index.js CHANGED
@@ -10,14 +10,14 @@
10
10
  /**
11
11
  * @typedef {Object} AggregationOptions
12
12
  * @property {string[]} [path=[]] - An array of relative paths to search for pages within `pagesRoot`.
13
- * @property {string} [template] - The component ID to use for rendering each item found.
13
+ * @property {string} [component] - The component ID to use for rendering each item found.
14
14
  * @property {AggregationPaginationOptions} [pagination] - Configuration for pagination logic and controls. If present, pagination logic is enabled.
15
15
  * @property {function(Object): boolean} [filter] - A callback function to filter pages. It receives the page values object and should return `true` to keep the item.
16
16
  * @property {function(Object, Object): number} [sort] - A comparison function for sorting pages. It receives two page value objects (a, b) and should return a number.
17
17
  * @property {number} [limit] - The maximum number of items to return (or items per page if pagination is used).
18
18
  * @property {number} [offset=0] - The starting index for fetching items.
19
19
  * @property {boolean} [recursive=false] - If true, searches subdirectories of the specified paths.
20
- * @property {Object.<string, (string|function(Object): *)>} [transformProperties] - A map of key transformations. Keys are the new property names. Values can be a string (source property name) or a function (receiving page values and returning the new value).
20
+ * @property {Object.<string, (string|function(Object): *)>} [transformState] - A map of key transformations. Keys are the new property names. Values can be a string (source property name) or a function (receiving page values and returning the new value).
21
21
  */
22
22
 
23
23
  export default {}