@conduction/nextcloud-vue 2.96.0 → 2.98.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 (163) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +17 -0
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +383 -359
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +106 -2
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  12. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  13. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js +9 -0
  14. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue.js.map +1 -0
  15. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js +964 -0
  16. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue2.js.map +1 -0
  17. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js +282 -0
  18. package/dist/esm/components/CnChecklistRunWidget/CnChecklistRunWidget.vue3.js.map +1 -0
  19. package/dist/esm/components/CnChecklistRunWidget/index.js +28 -0
  20. package/dist/esm/components/CnChecklistRunWidget/index.js.map +1 -0
  21. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  22. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +17 -3
  23. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  24. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +61 -43
  25. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  26. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
  27. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +159 -18
  28. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +9 -7
  30. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +1 -0
  33. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  34. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +26 -8
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +188 -5
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  42. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +48 -3
  43. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +46 -12
  45. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  47. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +3 -3
  48. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  49. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  51. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +27 -0
  52. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +16 -2
  54. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  56. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +144 -0
  57. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +488 -399
  59. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  60. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  61. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +344 -21
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +29 -7
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  67. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +2 -2
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +2 -2
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/useNamedSource.js +1 -1
  72. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  73. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  74. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +13 -0
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +5 -1
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  78. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +9 -0
  79. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js.map +1 -0
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +220 -0
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -0
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +121 -0
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -0
  84. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue.js +1 -1
  85. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js +83 -9
  86. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue2.js.map +1 -1
  87. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js +80 -11
  88. package/dist/esm/components/CnUserSettingsDialog/CnUserSettingsDialog.vue3.js.map +1 -1
  89. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  90. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  91. package/dist/esm/composables/indexSources.js +420 -5
  92. package/dist/esm/composables/indexSources.js.map +1 -1
  93. package/dist/esm/composables/useOrganisationStore.js +115 -0
  94. package/dist/esm/composables/useOrganisationStore.js.map +1 -0
  95. package/dist/esm/index.js +2 -0
  96. package/dist/esm/index.js.map +1 -1
  97. package/dist/esm/l10n/en.json.js +21 -1
  98. package/dist/esm/l10n/en.json.js.map +1 -1
  99. package/dist/esm/l10n/nl.json.js +36 -1
  100. package/dist/esm/l10n/nl.json.js.map +1 -1
  101. package/dist/esm/library-style.js +1 -1
  102. package/dist/esm/nextcloud-vue.css +725 -383
  103. package/dist/esm/utils/builtInFormatters.js +34 -1
  104. package/dist/esm/utils/builtInFormatters.js.map +1 -1
  105. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  106. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  107. package/dist/esm/utils/matrixCells.js +158 -0
  108. package/dist/esm/utils/matrixCells.js.map +1 -0
  109. package/dist/esm/utils/nextcloudDirectory.js +121 -0
  110. package/dist/esm/utils/nextcloudDirectory.js.map +1 -0
  111. package/dist/esm/utils/queryPresets.js +13 -2
  112. package/dist/esm/utils/queryPresets.js.map +1 -1
  113. package/dist/esm/utils/scopeListLayout.js +37 -1
  114. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  115. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  116. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  117. package/dist/nextcloud-vue.cjs.js +14443 -10519
  118. package/dist/nextcloud-vue.cjs.js.map +2 -2
  119. package/dist/nextcloud-vue.css +725 -383
  120. package/l10n/en.json +21 -1
  121. package/l10n/nl.json +36 -1
  122. package/package.json +2 -2
  123. package/scripts/merge-l10n.js +196 -0
  124. package/src/components/CnAppRoot/CnAppRoot.md +1 -0
  125. package/src/components/CnAppRoot/CnAppRoot.vue +17 -0
  126. package/src/components/CnCellRenderer/CnCellRenderer.vue +57 -1
  127. package/src/components/CnChartWidget/CnChartWidget.md +2 -1
  128. package/src/components/CnChartWidget/CnChartWidget.vue +106 -2
  129. package/src/components/CnChecklistRunWidget/CnChecklistRunWidget.vue +1269 -0
  130. package/src/components/CnChecklistRunWidget/index.js +27 -0
  131. package/src/components/CnConversationThread/CnConversationThread.md +12 -0
  132. package/src/components/CnConversationThread/CnConversationThread.vue +30 -4
  133. package/src/components/CnConversationThread/CnConversationWidget.vue +167 -24
  134. package/src/components/CnDashboardPage/CnDashboardPage.vue +1 -0
  135. package/src/components/CnDataTable/CnDataTable.vue +26 -8
  136. package/src/components/CnDetailPage/CnDetailPage.md +48 -0
  137. package/src/components/CnDetailPage/CnDetailPage.vue +188 -5
  138. package/src/components/CnFactsWidget/CnFactsWidget.vue +114 -3
  139. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +3 -3
  140. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +38 -2
  141. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +508 -236
  142. package/src/components/CnFilesBrowser/filesBrowserColumns.js +8 -2
  143. package/src/components/CnIndexPage/CnIndexPage.md +28 -0
  144. package/src/components/CnIndexPage/CnIndexPage.vue +367 -27
  145. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +13 -4
  146. package/src/components/CnIndexPage/useNamedSource.js +1 -1
  147. package/src/components/CnObjectSidebar/CnFilesTab.vue +17 -0
  148. package/src/components/CnOrderedListField/CnOrderedListField.vue +386 -0
  149. package/src/components/CnOrderedListField/index.js +7 -0
  150. package/src/components/CnUserSettingsDialog/CnUserSettingsDialog.vue +139 -10
  151. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  152. package/src/components/index.js +2 -0
  153. package/src/composables/indexSources.js +419 -4
  154. package/src/composables/useOrganisationStore.js +111 -0
  155. package/src/index.js +2 -0
  156. package/src/schemas/app-manifest-v2.schema.json +42 -5
  157. package/src/utils/builtInFormatters.js +33 -0
  158. package/src/utils/libraryWidgetKeys.js +1 -0
  159. package/src/utils/matrixCells.js +154 -0
  160. package/src/utils/nextcloudDirectory.js +117 -0
  161. package/src/utils/queryPresets.js +13 -2
  162. package/src/utils/scopeListLayout.js +37 -1
  163. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"queryPresets.js","sources":["../../../src/utils/queryPresets.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Query presets of an index page (`config.queryPresets`): a menu entry that\n * deep-links to a list with a query (`{ caseType: '<uuid>' }`, the shape hydra\n * gate-68 asks for instead of a second page) can bring its own lenses,\n * columns and copy. The page names, per preset, the query it answers to and\n * the keys it replaces; the first preset whose every `match` pair is in the\n * current route query wins.\n *\n * @module utils/queryPresets\n * @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy\n */\n\n/**\n * The config keys a preset may replace. Anything else in a preset is ignored:\n * a preset changes what the list shows, never which register or schema it\n * reads or what it may write.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const QUERY_PRESET_KEYS = Object.freeze([\n\t'title',\n\t'quickFilters',\n\t'columns',\n\t'cardFields',\n\t'countText',\n\t'searchPlaceholder',\n\t'footerNote',\n\t'defaultSort',\n\t'viewSwitch',\n])\n\n/**\n * Whether one route query value satisfies one `match` value. A repeated query\n * parameter (an array) matches when it holds the value.\n *\n * @param {unknown} actual The route query value.\n * @param {string|number|boolean} expected The preset's value.\n * @return {boolean} True when they match.\n */\nfunction queryValueMatches(actual, expected) {\n\tconst want = String(expected)\n\tif (Array.isArray(actual)) {\n\t\treturn actual.some((v) => String(v) === want)\n\t}\n\treturn actual !== undefined && actual !== null && String(actual) === want\n}\n\n/**\n * The index of the first preset whose every `match` pair is in the query.\n * A preset without a non-empty `match` never matches.\n *\n * @param {Array<object>} presets The page's `queryPresets`.\n * @param {object} query The current route query.\n * @return {number} The preset index, or -1.\n */\nexport function matchQueryPreset(presets, query) {\n\tif (!Array.isArray(presets)) {\n\t\treturn -1\n\t}\n\tconst q = query && typeof query === 'object' ? query : {}\n\treturn presets.findIndex((preset) => {\n\t\tconst match = preset && typeof preset.match === 'object' && preset.match !== null ? preset.match : null\n\t\tif (!match || Object.keys(match).length === 0) {\n\t\t\treturn false\n\t\t}\n\t\treturn Object.entries(match).every(([key, value]) => queryValueMatches(q[key], value))\n\t})\n}\n\n/**\n * The page config with the preset's allowed keys laid over it, and without\n * `queryPresets` itself (the page component has no such prop).\n *\n * @param {object} config The page config.\n * @param {object|null} preset The matched preset, or null.\n * @return {object} The effective config.\n */\nexport function applyQueryPreset(config, preset) {\n\tconst { queryPresets, ...rest } = config || {}\n\tif (!preset || typeof preset !== 'object') {\n\t\treturn rest\n\t}\n\tconst out = { ...rest }\n\tfor (const key of QUERY_PRESET_KEYS) {\n\t\tif (key !== 'title' && preset[key] !== undefined) {\n\t\t\tout[key] = preset[key]\n\t\t}\n\t}\n\treturn out\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC;AAC/C,CAAC,OAAO;AACR,CAAC,cAAc;AACf,CAAC,SAAS;AACV,CAAC,YAAY;AACb,CAAC,WAAW;AACZ,CAAC,mBAAmB;AACpB,CAAC,YAAY;AACb,CAAC,aAAa;AACd,CAAC,YAAY;AACb,CAAC,EAAC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE;AAC7C,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAC;AAC9B,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;AAC5B,EAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;AAC/C,EAAE;AACF,CAAC,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI;AAC1E,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE;AACjD,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;AAC9B,EAAE,OAAO,CAAC,CAAC;AACX,EAAE;AACF,CAAC,MAAM,CAAC,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,GAAG,GAAE;AAC1D,CAAC,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,KAAK;AACtC,EAAE,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,GAAG,MAAM,CAAC,KAAK,GAAG,KAAI;AACzG,EAAE,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE;AACjD,GAAG,OAAO,KAAK;AACf,GAAG;AACH,EAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AACxF,EAAE,CAAC;AACH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE;AACjD,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,IAAI,GAAE;AAC/C,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,EAAE,GAAG,IAAI,GAAE;AACxB,CAAC,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE;AACtC,EAAE,IAAI,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE;AACpD,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,EAAC;AACzB,GAAG;AACH,EAAE;AACF,CAAC,OAAO,GAAG;AACX;;;;"}
1
+ {"version":3,"file":"queryPresets.js","sources":["../../../src/utils/queryPresets.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * Query presets of an index page (`config.queryPresets`): a menu entry that\n * deep-links to a list with a query (`{ caseType: '<uuid>' }`, the shape hydra\n * gate-68 asks for instead of a second page) can bring its own lenses,\n * columns and copy. The page names, per preset, the query it answers to and\n * the keys it replaces; the first preset whose every `match` pair is in the\n * current route query wins.\n *\n * @module utils/queryPresets\n * @spec openspec/changes/screens-index-query-presets/specs/index-query-presets/spec.md#requirement-a-query-preset-overlays-lenses-columns-and-copy\n */\n\n/**\n * The config keys a preset may replace. Anything else in a preset is ignored:\n * a preset changes what the list shows, never which register or schema it\n * reads or what it may write.\n *\n * @type {ReadonlyArray<string>}\n */\nexport const QUERY_PRESET_KEYS = Object.freeze([\n\t'title',\n\t'quickFilters',\n\t'columns',\n\t'cardFields',\n\t'countText',\n\t'searchPlaceholder',\n\t'footerNote',\n\t'defaultSort',\n\t'viewSwitch',\n\t'addLabel',\n\t'headerButtons',\n\t'breadcrumb',\n\t'headerMeta',\n\t'afterList',\n])\n\n/**\n * Whether one route query value satisfies one `match` value. A repeated query\n * parameter (an array) matches when it holds the value.\n *\n * @param {unknown} actual The route query value.\n * @param {string|number|boolean} expected The preset's value.\n * @return {boolean} True when they match.\n */\nfunction queryValueMatches(actual, expected) {\n\tconst want = String(expected)\n\tif (Array.isArray(actual)) {\n\t\treturn actual.some((v) => String(v) === want)\n\t}\n\treturn actual !== undefined && actual !== null && String(actual) === want\n}\n\n/**\n * The index of the first preset whose every `match` pair is in the query.\n * A preset without a non-empty `match` never matches.\n *\n * @param {Array<object>} presets The page's `queryPresets`.\n * @param {object} query The current route query.\n * @return {number} The preset index, or -1.\n */\nexport function matchQueryPreset(presets, query) {\n\tif (!Array.isArray(presets)) {\n\t\treturn -1\n\t}\n\tconst q = query && typeof query === 'object' ? query : {}\n\treturn presets.findIndex((preset) => {\n\t\tconst match = preset && typeof preset.match === 'object' && preset.match !== null ? preset.match : null\n\t\tif (!match || Object.keys(match).length === 0) {\n\t\t\treturn false\n\t\t}\n\t\treturn Object.entries(match).every(([key, value]) => queryValueMatches(q[key], value))\n\t})\n}\n\n/**\n * The page config with the preset's allowed keys laid over it, its `match`\n * as `fixedFilters`, and without `queryPresets` itself (the page component\n * has no such prop).\n *\n * @param {object} config The page config.\n * @param {object|null} preset The matched preset, or null.\n * @return {object} The effective config.\n */\nexport function applyQueryPreset(config, preset) {\n\tconst { queryPresets, ...rest } = config || {}\n\tif (!preset || typeof preset !== 'object') {\n\t\treturn rest\n\t}\n\tconst out = { ...rest }\n\tfor (const key of QUERY_PRESET_KEYS) {\n\t\tif (key !== 'title' && preset[key] !== undefined) {\n\t\t\tout[key] = preset[key]\n\t\t}\n\t}\n\t// The query pairs that chose the preset scope the list: the page shows\n\t// them as no removable filter chip.\n\tif (preset.match && typeof preset.match === 'object') {\n\t\tout.fixedFilters = { ...preset.match }\n\t}\n\treturn out\n}\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,20 @@
1
+ import { QUERY_PRESET_KEYS } from './queryPresets.js';
2
+
1
3
  /**
2
4
  * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
3
5
  * SPDX-License-Identifier: AGPL-3.0-or-later
4
6
  */
5
7
 
6
8
 
9
+ /**
10
+ * The presentation keys a scope may carry beside its list layout: the keys a
11
+ * query preset may set (`QUERY_PRESET_KEYS`), less the two the list layout
12
+ * resolves itself (`columns`, `defaultSort`).
13
+ *
14
+ * @type {ReadonlyArray<string>}
15
+ */
16
+ const SCOPE_PRESENTATION_KEYS = Object.freeze(QUERY_PRESET_KEYS.filter((key) => key !== 'columns' && key !== 'defaultSort'));
17
+
7
18
  /**
8
19
  * The key of a column entry, in either of the two shapes the manifest accepts:
9
20
  * a bare string (legacy shorthand) or a `{ key, label, ... }` object.
@@ -143,5 +154,30 @@ function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {})
143
154
  return { columns, sortKeys, searchFields }
144
155
  }
145
156
 
146
- export { columnKeyOf, resolveScopeLayout };
157
+ /**
158
+ * The presentation a scope asks for: each of `SCOPE_PRESENTATION_KEYS` the
159
+ * folder entry declares, else the one its schema row's `x-index` declares.
160
+ * Only declared keys are in the result, so the page's own value stands for
161
+ * every other key. These are the keys a query preset sets, under the same
162
+ * names, and a scope's value wins over a preset's because it is applied on
163
+ * top of the page config the preset produced.
164
+ *
165
+ * @param {object} options Resolution inputs.
166
+ * @param {(object|null)} [options.scope] The folder entry for the active scope.
167
+ * @param {(object|null)} [options.row] The schema row the folder derives from.
168
+ * @return {object} The declared presentation keys.
169
+ * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation
170
+ */
171
+ function resolveScopePresentation({ scope = null, row = null } = {}) {
172
+ const out = {};
173
+ for (const key of SCOPE_PRESENTATION_KEYS) {
174
+ const value = declaredOrRow(scope, row, key);
175
+ if (value !== undefined) {
176
+ out[key] = value;
177
+ }
178
+ }
179
+ return out
180
+ }
181
+
182
+ export { SCOPE_PRESENTATION_KEYS, columnKeyOf, resolveScopeLayout, resolveScopePresentation };
147
183
  //# sourceMappingURL=scopeListLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scopeListLayout.js","sources":["../../../src/utils/scopeListLayout.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\n/**\n * The three list-layout keys a `folderSidebar` scope may carry.\n *\n * @type {string[]}\n */\nconst SCOPE_LAYOUT_KEYS = ['columns', 'defaultSort', 'searchFields']\n\n/**\n * The key of a column entry, in either of the two shapes the manifest accepts:\n * a bare string (legacy shorthand) or a `{ key, label, ... }` object.\n *\n * @param {(string|object)} column A column entry.\n * @return {string} The column key, or '' when the entry carries none.\n */\nexport function columnKeyOf(column) {\n\tif (typeof column === 'string') {\n\t\treturn column\n\t}\n\tif (column && typeof column.key === 'string') {\n\t\treturn column.key\n\t}\n\treturn ''\n}\n\n/**\n * Normalise one sort entry to the `{ key, order }` shape the fetch uses.\n * Accepts `key` or `field` because the page prop spells it `field` and the\n * manifest spells it `key`; an entry naming neither is dropped rather than\n * sent as an empty sort key.\n *\n * @param {object} entry A sort entry.\n * @return {({key: string, order: string}|null)} The normalised entry, or null.\n */\nfunction normaliseSortEntry(entry) {\n\tif (!entry || typeof entry !== 'object') {\n\t\treturn null\n\t}\n\tconst key = typeof entry.key === 'string' && entry.key !== ''\n\t\t? entry.key\n\t\t: (typeof entry.field === 'string' ? entry.field : '')\n\tif (key === '') {\n\t\treturn null\n\t}\n\treturn { key, order: entry.order === 'desc' ? 'desc' : 'asc' }\n}\n\n/**\n * Normalise a `defaultSort` value, which may be a single entry or a list.\n *\n * @param {(object|object[]|undefined)} value The declared sort.\n * @return {Array<{key: string, order: string}>} The normalised sort keys.\n */\nfunction normaliseSort(value) {\n\tif (value === undefined || value === null) {\n\t\treturn []\n\t}\n\tconst list = Array.isArray(value) ? value : [value]\n\treturn list.map(normaliseSortEntry).filter(Boolean)\n}\n\n/**\n * Read one layout key from the scope entry, falling back to the schema row's\n * `x-index` block. A value declared on the folder entry WINS over the row's,\n * which is what makes the row a default rather than an override.\n *\n * @param {(object|null)} scope The folder entry for the active scope.\n * @param {(object|null)} row The schema row the folder derives from.\n * @param {string} key One of `columns`, `defaultSort`, `searchFields`.\n * @return {(Array|object|undefined)} The declared value, the row value, or undefined.\n */\nfunction declaredOrRow(scope, row, key) {\n\tif (scope && scope[key] !== undefined) {\n\t\treturn scope[key]\n\t}\n\tconst carried = row && row['x-index']\n\tif (carried && typeof carried === 'object' && carried[key] !== undefined) {\n\t\treturn carried[key]\n\t}\n\treturn undefined\n}\n\n/**\n * Resolve the list layout a `folderSidebar` scope asks for.\n *\n * Two declarations meet here and they do not decide the same thing:\n *\n * - The PAGE decides MEMBERSHIP. `config.columns` is the set of columns this\n * page has, and it carries each column's definition (label, formatter,\n * widget). A scope may reorder that set and narrow it; it cannot add to it.\n * A scope naming a column the page does not declare is DROPPED, so a scope\n * written before a column was removed cannot bring the column back.\n * - The SCOPE decides PRESENTATION: which of the page's columns are shown, in\n * what order, sorted by what, searched over what.\n *\n * The page declaring no columns at all is the one case where a scope's list is\n * taken as written: there is no declared set to measure it against, because the\n * columns are derived from the schema at runtime.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @param {Array} [options.pageColumns] The page's declared `config.columns`.\n * @return {{columns: (Array|null), sortKeys: Array<{key: string, order: string}>, searchFields: string[]}}\n * `columns` is null when neither source declares any, meaning the page's own\n * list stands.\n */\nexport function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {}) {\n\tconst declaredColumns = declaredOrRow(scope, row, 'columns')\n\tconst declaredSearch = declaredOrRow(scope, row, 'searchFields')\n\tconst sortKeys = normaliseSort(declaredOrRow(scope, row, 'defaultSort'))\n\n\tconst searchFields = Array.isArray(declaredSearch)\n\t\t? declaredSearch.filter((f) => typeof f === 'string' && f !== '')\n\t\t: []\n\n\tif (!Array.isArray(declaredColumns) || declaredColumns.length === 0) {\n\t\treturn { columns: null, sortKeys, searchFields }\n\t}\n\n\tconst page = Array.isArray(pageColumns) ? pageColumns : []\n\tif (page.length === 0) {\n\t\treturn { columns: [...declaredColumns], sortKeys, searchFields }\n\t}\n\n\tconst byKey = new Map()\n\tpage.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key !== '') {\n\t\t\tbyKey.set(key, col)\n\t\t}\n\t})\n\n\tconst columns = []\n\tdeclaredColumns.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key === '' || !byKey.has(key)) {\n\t\t\treturn\n\t\t}\n\t\t// The page's own entry is used, not the scope's, so the label and\n\t\t// formatter a column was declared with survive being selected.\n\t\tcolumns.push(byKey.get(key))\n\t})\n\n\treturn { columns, sortKeys, searchFields }\n}\n\nexport { SCOPE_LAYOUT_KEYS }\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,MAAM,EAAE;AACpC,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AACjC,EAAE,OAAO,MAAM;AACf,EAAE;AACF,CAAC,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE;AAC/C,EAAE,OAAO,MAAM,CAAC,GAAG;AACnB,EAAE;AACF,CAAC,OAAO,EAAE;AACV,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,kBAAkB,CAAC,KAAK,EAAE;AACnC,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC1C,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE;AAC9D,IAAI,KAAK,CAAC,GAAG;AACb,KAAK,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,KAAK,GAAG,EAAE,EAAC;AACxD,CAAC,IAAI,GAAG,KAAK,EAAE,EAAE;AACjB,EAAE,OAAO,IAAI;AACb,EAAE;AACF,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,KAAK,MAAM,GAAG,MAAM,GAAG,KAAK,EAAE;AAC/D,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,KAAK,EAAE;AAC9B,CAAC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE;AAC5C,EAAE,OAAO,EAAE;AACX,EAAE;AACF,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,EAAC;AACpD,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;AACpD,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE;AACxC,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE;AACxC,EAAE,OAAO,KAAK,CAAC,GAAG,CAAC;AACnB,EAAE;AACF,CAAC,MAAM,OAAO,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,EAAC;AACtC,CAAC,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE;AAC3E,EAAE,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,EAAE;AACF,CAAC,OAAO,SAAS;AACjB,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,kBAAkB,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE;AACxF,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAC;AAC7D,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,EAAC;AACjE,CAAC,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,aAAa,CAAC,EAAC;AACzE;AACA,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;AACnD,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;AACnE,IAAI,GAAE;AACN;AACA,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE;AACtE,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE;AAClD,EAAE;AACF;AACA,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,GAAE;AAC3D,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE;AACxB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,eAAe,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE;AAClE,EAAE;AACF;AACA,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,GAAE;AACxB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK;AACvB,EAAE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAC;AAC9B,EAAE,IAAI,GAAG,KAAK,EAAE,EAAE;AAClB,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAC;AACtB,GAAG;AACH,EAAE,EAAC;AACH;AACA,CAAC,MAAM,OAAO,GAAG,GAAE;AACnB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK;AAClC,EAAE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAC;AAC9B,EAAE,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AACrC,GAAG,MAAM;AACT,GAAG;AACH;AACA;AACA,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAC;AAC9B,EAAE,EAAC;AACH;AACA,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE;AAC3C;;;;"}
1
+ {"version":3,"file":"scopeListLayout.js","sources":["../../../src/utils/scopeListLayout.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n * SPDX-License-Identifier: AGPL-3.0-or-later\n */\n\nimport { QUERY_PRESET_KEYS } from './queryPresets.js'\n\n/**\n * The three list-layout keys a `folderSidebar` scope may carry.\n *\n * @type {string[]}\n */\nconst SCOPE_LAYOUT_KEYS = ['columns', 'defaultSort', 'searchFields']\n\n/**\n * The presentation keys a scope may carry beside its list layout: the keys a\n * query preset may set (`QUERY_PRESET_KEYS`), less the two the list layout\n * resolves itself (`columns`, `defaultSort`).\n *\n * @type {ReadonlyArray<string>}\n */\nconst SCOPE_PRESENTATION_KEYS = Object.freeze(QUERY_PRESET_KEYS.filter((key) => key !== 'columns' && key !== 'defaultSort'))\n\n/**\n * The key of a column entry, in either of the two shapes the manifest accepts:\n * a bare string (legacy shorthand) or a `{ key, label, ... }` object.\n *\n * @param {(string|object)} column A column entry.\n * @return {string} The column key, or '' when the entry carries none.\n */\nexport function columnKeyOf(column) {\n\tif (typeof column === 'string') {\n\t\treturn column\n\t}\n\tif (column && typeof column.key === 'string') {\n\t\treturn column.key\n\t}\n\treturn ''\n}\n\n/**\n * Normalise one sort entry to the `{ key, order }` shape the fetch uses.\n * Accepts `key` or `field` because the page prop spells it `field` and the\n * manifest spells it `key`; an entry naming neither is dropped rather than\n * sent as an empty sort key.\n *\n * @param {object} entry A sort entry.\n * @return {({key: string, order: string}|null)} The normalised entry, or null.\n */\nfunction normaliseSortEntry(entry) {\n\tif (!entry || typeof entry !== 'object') {\n\t\treturn null\n\t}\n\tconst key = typeof entry.key === 'string' && entry.key !== ''\n\t\t? entry.key\n\t\t: (typeof entry.field === 'string' ? entry.field : '')\n\tif (key === '') {\n\t\treturn null\n\t}\n\treturn { key, order: entry.order === 'desc' ? 'desc' : 'asc' }\n}\n\n/**\n * Normalise a `defaultSort` value, which may be a single entry or a list.\n *\n * @param {(object|object[]|undefined)} value The declared sort.\n * @return {Array<{key: string, order: string}>} The normalised sort keys.\n */\nfunction normaliseSort(value) {\n\tif (value === undefined || value === null) {\n\t\treturn []\n\t}\n\tconst list = Array.isArray(value) ? value : [value]\n\treturn list.map(normaliseSortEntry).filter(Boolean)\n}\n\n/**\n * Read one layout key from the scope entry, falling back to the schema row's\n * `x-index` block. A value declared on the folder entry WINS over the row's,\n * which is what makes the row a default rather than an override.\n *\n * @param {(object|null)} scope The folder entry for the active scope.\n * @param {(object|null)} row The schema row the folder derives from.\n * @param {string} key One of `columns`, `defaultSort`, `searchFields`.\n * @return {(Array|object|undefined)} The declared value, the row value, or undefined.\n */\nfunction declaredOrRow(scope, row, key) {\n\tif (scope && scope[key] !== undefined) {\n\t\treturn scope[key]\n\t}\n\tconst carried = row && row['x-index']\n\tif (carried && typeof carried === 'object' && carried[key] !== undefined) {\n\t\treturn carried[key]\n\t}\n\treturn undefined\n}\n\n/**\n * Resolve the list layout a `folderSidebar` scope asks for.\n *\n * Two declarations meet here and they do not decide the same thing:\n *\n * - The PAGE decides MEMBERSHIP. `config.columns` is the set of columns this\n * page has, and it carries each column's definition (label, formatter,\n * widget). A scope may reorder that set and narrow it; it cannot add to it.\n * A scope naming a column the page does not declare is DROPPED, so a scope\n * written before a column was removed cannot bring the column back.\n * - The SCOPE decides PRESENTATION: which of the page's columns are shown, in\n * what order, sorted by what, searched over what.\n *\n * The page declaring no columns at all is the one case where a scope's list is\n * taken as written: there is no declared set to measure it against, because the\n * columns are derived from the schema at runtime.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @param {Array} [options.pageColumns] The page's declared `config.columns`.\n * @return {{columns: (Array|null), sortKeys: Array<{key: string, order: string}>, searchFields: string[]}}\n * `columns` is null when neither source declares any, meaning the page's own\n * list stands.\n */\nexport function resolveScopeLayout({ scope = null, row = null, pageColumns = [] } = {}) {\n\tconst declaredColumns = declaredOrRow(scope, row, 'columns')\n\tconst declaredSearch = declaredOrRow(scope, row, 'searchFields')\n\tconst sortKeys = normaliseSort(declaredOrRow(scope, row, 'defaultSort'))\n\n\tconst searchFields = Array.isArray(declaredSearch)\n\t\t? declaredSearch.filter((f) => typeof f === 'string' && f !== '')\n\t\t: []\n\n\tif (!Array.isArray(declaredColumns) || declaredColumns.length === 0) {\n\t\treturn { columns: null, sortKeys, searchFields }\n\t}\n\n\tconst page = Array.isArray(pageColumns) ? pageColumns : []\n\tif (page.length === 0) {\n\t\treturn { columns: [...declaredColumns], sortKeys, searchFields }\n\t}\n\n\tconst byKey = new Map()\n\tpage.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key !== '') {\n\t\t\tbyKey.set(key, col)\n\t\t}\n\t})\n\n\tconst columns = []\n\tdeclaredColumns.forEach((col) => {\n\t\tconst key = columnKeyOf(col)\n\t\tif (key === '' || !byKey.has(key)) {\n\t\t\treturn\n\t\t}\n\t\t// The page's own entry is used, not the scope's, so the label and\n\t\t// formatter a column was declared with survive being selected.\n\t\tcolumns.push(byKey.get(key))\n\t})\n\n\treturn { columns, sortKeys, searchFields }\n}\n\n/**\n * The presentation a scope asks for: each of `SCOPE_PRESENTATION_KEYS` the\n * folder entry declares, else the one its schema row's `x-index` declares.\n * Only declared keys are in the result, so the page's own value stands for\n * every other key. These are the keys a query preset sets, under the same\n * names, and a scope's value wins over a preset's because it is applied on\n * top of the page config the preset produced.\n *\n * @param {object} options Resolution inputs.\n * @param {(object|null)} [options.scope] The folder entry for the active scope.\n * @param {(object|null)} [options.row] The schema row the folder derives from.\n * @return {object} The declared presentation keys.\n * @spec openspec/changes/index-scope-presentation/specs/index-page/spec.md#requirement-a-preset-or-a-scope-sets-the-list-s-presentation\n */\nexport function resolveScopePresentation({ scope = null, row = null } = {}) {\n\tconst out = {}\n\tfor (const key of SCOPE_PRESENTATION_KEYS) {\n\t\tconst value = declaredOrRow(scope, row, key)\n\t\tif (value !== undefined) {\n\t\t\tout[key] = value\n\t\t}\n\t}\n\treturn out\n}\n\nexport { SCOPE_LAYOUT_KEYS, SCOPE_PRESENTATION_KEYS }\n"],"names":[],"mappings":"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"}