@phis/ui 0.4.2 → 0.4.3

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 (241) hide show
  1. package/components/cms/phi-cms-layout-renderer.js +4 -3
  2. package/components/cms/phi-cms-page-render-scope.d.ts +4 -5
  3. package/components/cms/phi-cms-page-render-scope.js +43 -22
  4. package/components/cms/phi-cms-render-diagnostic-client.js +7 -3
  5. package/components/cms/phi-cms-render-error-boundary.js +2 -1
  6. package/components/controls/phi-bullet-list-control.js +31 -2
  7. package/components/controls/phi-ribbon-control.d.ts +29 -0
  8. package/components/controls/phi-ribbon-control.js +53 -0
  9. package/components/controls/phi-table-control.d.ts +3 -0
  10. package/components/controls/phi-table-control.js +13 -1
  11. package/components/controls/phi-tree-control.js +3 -3
  12. package/components/forms/shared-form-provider-registry.js +1 -4
  13. package/components/media/asset-collection-runtime.js +6 -16
  14. package/components/media/media-font-metrics.d.ts +3 -0
  15. package/components/media/media-font-metrics.js +43 -0
  16. package/components/media/phi-area-upload-widget.js +53 -97
  17. package/components/media/phi-image-preview-data.d.ts +13 -0
  18. package/components/media/phi-image-preview-data.js +184 -9
  19. package/components/media/phi-media-picker-binding.js +16 -13
  20. package/components/media/phi-media-upload.d.ts +15 -8
  21. package/components/media/phi-media-upload.js +18 -9
  22. package/components/media/phi-site-font-assets.js +7 -8
  23. package/components/overlays/phi-overlay-container-client.js +16 -1
  24. package/components/regions/presets/phi-settings-page-shell-tree.js +48 -1
  25. package/components/runtime/runtime-controller-materialization.js +12 -1
  26. package/components/state/scoped-state-store.js +20 -3
  27. package/components/tables/client/phi-table-binding.d.ts +2 -1
  28. package/components/tables/client/phi-table-binding.js +45 -21
  29. package/components/trees/client/phi-tree-binding.js +19 -11
  30. package/components/widgets/client/bullet-list-entries-editor.d.ts +26 -0
  31. package/components/widgets/client/bullet-list-entries-editor.js +103 -0
  32. package/components/widgets/config/choice-shared.js +12 -13
  33. package/components/widgets/config/command-buttons.d.ts +5 -1
  34. package/components/widgets/config/command-buttons.js +38 -81
  35. package/components/widgets/config/control-signal-config.d.ts +3 -1
  36. package/components/widgets/config/control-signal-config.js +19 -17
  37. package/components/widgets/config/image-source-parser.d.ts +1 -0
  38. package/components/widgets/config/image-source-parser.js +7 -8
  39. package/components/widgets/config/mask.d.ts +6 -1
  40. package/components/widgets/config/mask.js +24 -1
  41. package/components/widgets/config/parser-primitives.js +72 -0
  42. package/components/widgets/config/stack-tabs.d.ts +1 -1
  43. package/components/widgets/config/stack-tabs.js +2 -3
  44. package/components/widgets/config/strict-config-reader.d.ts +40 -0
  45. package/components/widgets/config/strict-config-reader.js +174 -0
  46. package/components/widgets/label-sets/effects.js +2 -0
  47. package/components/widgets/label-types/effects.d.ts +1 -0
  48. package/components/widgets/label-types/effects.js +1 -0
  49. package/components/widgets/navigation-descriptor-resolver.server.js +20 -8
  50. package/components/widgets/shared/bullet-list-vocabulary.d.ts +17 -1
  51. package/components/widgets/shared/bullet-list-vocabulary.js +19 -2
  52. package/components/widgets/shared/card-body-client.d.ts +16 -4
  53. package/components/widgets/shared/card-body-client.js +81 -24
  54. package/components/widgets/shared/card-vocabulary.d.ts +21 -4
  55. package/components/widgets/shared/card-vocabulary.js +28 -2
  56. package/components/widgets/signals/control-signal-capabilities.d.ts +0 -18
  57. package/components/widgets/signals/control-signal-capabilities.js +10 -22
  58. package/components/workspace/catalog-store.d.ts +4 -3
  59. package/components/workspace/catalog-store.js +6 -5
  60. package/gateway/dashboard-cards-route.js +1 -0
  61. package/gateway/internal-references.d.ts +6 -1
  62. package/gateway/internal-references.js +48 -23
  63. package/gateway/site-cms-write-validation.d.ts +9 -0
  64. package/gateway/site-cms-write-validation.js +78 -0
  65. package/gateway/site-proxy.js +19 -3
  66. package/helpers/renderable-block-serialization.js +1 -1
  67. package/next/config.d.mts +3 -0
  68. package/next/config.mjs +28 -1
  69. package/package.json +2 -2
  70. package/plugins/runtime/phi-slot-child-viewport-effects-observer.js +5 -1
  71. package/plugins/runtime-modules/accounting/presets.d.ts +9 -0
  72. package/plugins/runtime-modules/accounting/presets.js +37 -0
  73. package/plugins/runtime-modules/accounting/server.d.ts +1 -4
  74. package/plugins/runtime-modules/accounting/server.js +4 -7
  75. package/plugins/runtime-modules/app/presets.d.ts +18 -0
  76. package/plugins/runtime-modules/app/presets.js +67 -0
  77. package/plugins/runtime-modules/app/server.d.ts +1 -4
  78. package/plugins/runtime-modules/app/server.js +4 -7
  79. package/plugins/runtime-modules/asset/controller/runtime.js +2 -2
  80. package/plugins/runtime-modules/asset/services/collection.js +9 -1
  81. package/plugins/runtime-modules/asset/services/options.js +2 -2
  82. package/plugins/runtime-modules/builder/area-settings-answer.js +2 -1
  83. package/plugins/runtime-modules/builder/authoring-canvas.js +6 -6
  84. package/plugins/runtime-modules/builder/cards.js +3 -5
  85. package/plugins/runtime-modules/builder/clients/builder-region-overrides.js +3 -3
  86. package/plugins/runtime-modules/builder/clients/canvas-widget.js +15 -54
  87. package/plugins/runtime-modules/builder/clients/inspector-config-field.d.ts +4 -1
  88. package/plugins/runtime-modules/builder/clients/inspector-config-field.js +5 -34
  89. package/plugins/runtime-modules/builder/clients/inspector-link-target-field.js +1 -1
  90. package/plugins/runtime-modules/builder/clients/inspector-section-widget.js +8 -8
  91. package/plugins/runtime-modules/builder/clients/inspector-title.js +6 -6
  92. package/plugins/runtime-modules/builder/clients/layout-inspector.d.ts +1 -1
  93. package/plugins/runtime-modules/builder/clients/layout-inspector.js +11 -25
  94. package/plugins/runtime-modules/builder/clients/mode-switch.js +1 -1
  95. package/plugins/runtime-modules/builder/clients/pages-header.js +11 -11
  96. package/plugins/runtime-modules/builder/clients/pages-workspace.js +1 -1
  97. package/plugins/runtime-modules/builder/clients/region-visibility-fields.js +1 -1
  98. package/plugins/runtime-modules/builder/clients/runtime-module-authoring-boundary.js +1 -1
  99. package/plugins/runtime-modules/builder/clients/shells-workspace.js +15 -31
  100. package/plugins/runtime-modules/builder/clients/structure-canvas.js +1 -1
  101. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.d.ts +26 -0
  102. package/plugins/runtime-modules/builder/clients/use-region-draft-hydration.js +36 -0
  103. package/plugins/runtime-modules/builder/clients/widget-effects-editor.js +1 -1
  104. package/plugins/runtime-modules/builder/clients/widget-inspector.js +1 -0
  105. package/plugins/runtime-modules/builder/clients/workspace-scope-boundary.js +2 -2
  106. package/plugins/runtime-modules/builder/cms-instance-allocator.js +8 -28
  107. package/plugins/runtime-modules/builder/controller/client.d.ts +0 -15
  108. package/plugins/runtime-modules/builder/controller/definition.d.ts +0 -15
  109. package/plugins/runtime-modules/builder/controller/definition.js +0 -8
  110. package/plugins/runtime-modules/builder/controller/draft-command-controller.d.ts +1 -2
  111. package/plugins/runtime-modules/builder/controller/draft-command-controller.js +11 -30
  112. package/plugins/runtime-modules/builder/controller/draft-status-controller.js +15 -2
  113. package/plugins/runtime-modules/builder/controller/inspector-controller.d.ts +1 -2
  114. package/plugins/runtime-modules/builder/controller/inspector-controller.js +4 -7
  115. package/plugins/runtime-modules/builder/controller/page-controller.d.ts +1 -2
  116. package/plugins/runtime-modules/builder/controller/page-controller.js +16 -68
  117. package/plugins/runtime-modules/builder/controller/server-definition.d.ts +0 -15
  118. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.d.ts +2 -5
  119. package/plugins/runtime-modules/builder/controller/signal-wiring-controller.js +6 -50
  120. package/plugins/runtime-modules/builder/controller/workspace-controller.d.ts +2 -3
  121. package/plugins/runtime-modules/builder/controller/workspace-controller.js +89 -153
  122. package/plugins/runtime-modules/builder/data-providers.d.ts +1 -0
  123. package/plugins/runtime-modules/builder/data-providers.js +1 -0
  124. package/plugins/runtime-modules/builder/definition.d.ts +1 -0
  125. package/plugins/runtime-modules/builder/developer-workspace-store.d.ts +64 -30
  126. package/plugins/runtime-modules/builder/developer-workspace-store.js +255 -61
  127. package/plugins/runtime-modules/builder/edit-scaffold-drawer.js +1 -1
  128. package/plugins/runtime-modules/builder/history.d.ts +3 -1
  129. package/plugins/runtime-modules/builder/options-providers.js +3 -2
  130. package/plugins/runtime-modules/builder/page-catalog-client.js +19 -7
  131. package/plugins/runtime-modules/builder/page-meta-form.js +1 -0
  132. package/plugins/runtime-modules/builder/page-references-provider.js +1 -1
  133. package/plugins/runtime-modules/builder/persistence.js +33 -69
  134. package/plugins/runtime-modules/builder/public-route-collisions.d.ts +2 -2
  135. package/plugins/runtime-modules/builder/public-route-collisions.js +6 -19
  136. package/plugins/runtime-modules/builder/render-root-node-preview.server.js +20 -3
  137. package/plugins/runtime-modules/builder/render-root-node-scaffold.js +3 -3
  138. package/plugins/runtime-modules/builder/runtime-module-selection.d.ts +2 -12
  139. package/plugins/runtime-modules/builder/runtime-module-selection.js +16 -42
  140. package/plugins/runtime-modules/builder/services/navigation-table.js +9 -15
  141. package/plugins/runtime-modules/builder/services/page-source-tree.js +8 -8
  142. package/plugins/runtime-modules/builder/services/runtime-modules-table.js +10 -10
  143. package/plugins/runtime-modules/builder/services/signal-routes-table.js +3 -3
  144. package/plugins/runtime-modules/builder/signal-route-selection.d.ts +1 -2
  145. package/plugins/runtime-modules/builder/signal-route-selection.js +2 -2
  146. package/plugins/runtime-modules/builder/signal-wiring-options.js +3 -2
  147. package/plugins/runtime-modules/builder/structure-dnd.js +8 -61
  148. package/plugins/runtime-modules/builder/trees/phi-default-builder-area-preset-tree.js +13 -10
  149. package/plugins/runtime-modules/builder/widgets/structure-region/built-in.js +12 -18
  150. package/plugins/runtime-modules/builder/workspace-catalog-actions.d.ts +55 -0
  151. package/plugins/runtime-modules/builder/workspace-catalog-actions.js +142 -0
  152. package/plugins/runtime-modules/client-manifests/common.js +1 -1
  153. package/plugins/runtime-modules/{runtime-calendar-adapters.js → core/calendar-adapters.js} +2 -2
  154. package/plugins/runtime-modules/{client-calendar-adapters/core.js → core/client-calendar-adapters.js} +1 -1
  155. package/plugins/runtime-modules/core/content-entries/table.js +1 -1
  156. package/plugins/runtime-modules/core/data-providers.d.ts +3 -0
  157. package/plugins/runtime-modules/core/data-providers.js +2 -1
  158. package/plugins/runtime-modules/core/definition.d.ts +3 -0
  159. package/plugins/runtime-modules/core/definition.js +1 -1
  160. package/plugins/runtime-modules/core/messaging/data-providers.d.ts +1 -0
  161. package/plugins/runtime-modules/core/messaging/data-providers.js +1 -0
  162. package/plugins/runtime-modules/core/messaging/thread-inbox-table.js +1 -1
  163. package/plugins/runtime-modules/core/server.js +1 -1
  164. package/plugins/runtime-modules/core/themes.d.ts +44 -0
  165. package/plugins/runtime-modules/core/themes.js +12 -0
  166. package/plugins/runtime-modules/core/widgets/card/collection-view-binding.js +25 -9
  167. package/plugins/runtime-modules/core/widgets/card/config.d.ts +296 -254
  168. package/plugins/runtime-modules/core/widgets/card/config.js +52 -3
  169. package/plugins/runtime-modules/core/widgets/card/editor.js +30 -3
  170. package/plugins/runtime-modules/core/widgets/card/server.d.ts +3 -0
  171. package/plugins/runtime-modules/core/widgets/card/server.js +31 -3
  172. package/plugins/runtime-modules/core/widgets/collection-view/asset-binding.js +7 -6
  173. package/plugins/runtime-modules/core/widgets/collection-view/config.js +9 -2
  174. package/plugins/runtime-modules/core/widgets/description/config.d.ts +1 -25
  175. package/plugins/runtime-modules/core/widgets/description/editor.js +4 -61
  176. package/plugins/runtime-modules/core/widgets/entry-list/config.d.ts +1 -25
  177. package/plugins/runtime-modules/core/widgets/gallery/config.d.ts +1 -25
  178. package/plugins/runtime-modules/core/widgets/image/config.js +2 -2
  179. package/plugins/runtime-modules/core/widgets/markdown/editor.js +2 -3
  180. package/plugins/runtime-modules/core/widgets/result/config.d.ts +4 -25
  181. package/plugins/runtime-modules/core/widgets/result/config.js +2 -0
  182. package/plugins/runtime-modules/core/widgets/result/server.js +25 -19
  183. package/plugins/runtime-modules/core/widgets/simple-text/config.d.ts +1 -25
  184. package/plugins/runtime-modules/core/widgets/slot-upload/client.js +11 -2
  185. package/plugins/runtime-modules/core/widgets/table/client.js +8 -4
  186. package/plugins/runtime-modules/core/widgets/table/config.d.ts +3 -83
  187. package/plugins/runtime-modules/core/widgets/table/config.js +302 -452
  188. package/plugins/runtime-modules/core/widgets/tree/config.js +5 -2
  189. package/plugins/runtime-modules/dashboard/card-rows.d.ts +5 -0
  190. package/plugins/runtime-modules/dashboard/card-rows.js +59 -0
  191. package/plugins/runtime-modules/dashboard/clients/dashboard-card-view.js +4 -6
  192. package/plugins/runtime-modules/dashboard/services/collection.js +17 -7
  193. package/plugins/runtime-modules/groups/data-providers.d.ts +2 -0
  194. package/plugins/runtime-modules/groups/data-providers.js +3 -3
  195. package/plugins/runtime-modules/groups/definition.d.ts +2 -0
  196. package/plugins/runtime-modules/localization/data-providers.d.ts +3 -0
  197. package/plugins/runtime-modules/localization/data-providers.js +2 -0
  198. package/plugins/runtime-modules/localization/definition.d.ts +3 -0
  199. package/plugins/runtime-modules/localization/services/table.js +3 -7
  200. package/plugins/runtime-modules/observability/data-providers.d.ts +1 -0
  201. package/plugins/runtime-modules/observability/data-providers.js +1 -0
  202. package/plugins/runtime-modules/observability/definition.d.ts +1 -0
  203. package/plugins/runtime-modules/observability/module.d.ts +1 -0
  204. package/plugins/runtime-modules/observability/services/table.js +1 -1
  205. package/plugins/runtime-modules/phi-default-site-area-preset-tree.js +2 -105
  206. package/plugins/runtime-modules/public/presets.d.ts +115 -0
  207. package/plugins/runtime-modules/{area-base-presets.js → public/presets.js} +19 -119
  208. package/plugins/runtime-modules/public/server.d.ts +1 -4
  209. package/plugins/runtime-modules/public/server.js +4 -7
  210. package/plugins/runtime-modules/public/trees/phi-default-pub-error-page-tree.js +7 -8
  211. package/plugins/runtime-modules/public-base-page-references.d.ts +1 -1
  212. package/plugins/runtime-modules/public-base-page-references.js +1 -1
  213. package/plugins/runtime-modules/revisions/controller/client.js +11 -11
  214. package/plugins/runtime-modules/revisions/services/table.js +3 -3
  215. package/plugins/runtime-modules/theme/trees/phi-default-builder-theme-page-tree.js +22 -0
  216. package/plugins/runtime-modules/theme/widgets/brand-controls/client.d.ts +2 -1
  217. package/plugins/runtime-modules/theme/widgets/brand-controls/client.js +93 -58
  218. package/plugins/runtime-modules/theme/widgets/brand-controls/config.d.ts +2 -1
  219. package/plugins/runtime-modules/theme/widgets/brand-controls/config.js +23 -22
  220. package/plugins/runtime-modules/theme/widgets/brand-controls/plugin.js +1 -1
  221. package/plugins/runtime-modules/theme-descriptors.js +1 -1
  222. package/plugins/runtime-modules/user-management/data-providers.d.ts +2 -0
  223. package/plugins/runtime-modules/user-management/data-providers.js +1 -0
  224. package/plugins/runtime-modules/user-management/definition.d.ts +2 -0
  225. package/styles/control-shape.css +11 -11
  226. package/styles/controls.css +3 -3
  227. package/styles/layout.css +16 -7
  228. package/types/cms-config.js +142 -185
  229. package/types/cms-plugins.d.ts +4 -11
  230. package/types/core-widget-placements.d.ts +14 -3
  231. package/types/dashboard-cards.d.ts +9 -0
  232. package/types/media.d.ts +0 -3
  233. package/types/renderable-block.d.ts +6 -1
  234. package/types/table-widget.d.ts +9 -0
  235. package/types/table-widget.js +20 -0
  236. package/types.d.ts +1 -1
  237. package/widget-config.d.ts +2 -0
  238. package/widget-config.js +6 -0
  239. package/plugins/runtime-modules/area-base-presets.d.ts +0 -139
  240. /package/plugins/runtime-modules/{runtime-calendar-adapters.d.ts → core/calendar-adapters.d.ts} +0 -0
  241. /package/plugins/runtime-modules/{client-calendar-adapters/core.d.ts → core/client-calendar-adapters.d.ts} +0 -0
@@ -2,16 +2,14 @@ import { resolvePhiCmsWidgetPluginKey } from "../../../../../constants/cms-widge
2
2
  import { readPhiProviderResourceSource } from "../../../../../types/runtime-data-provider";
3
3
  import { PHI_TABLE_TAG_COLORS, PHI_TABLE_TAG_VARIANTS, readPhiTableQuery, } from "../../../../../types/table-widget";
4
4
  import { PHI_SIGNAL_VALUE_SCHEMAS, readPhiSignalRouteSet } from "../../../../../types/signals";
5
- import { readBoolean, readNumber, readNumberList, readString } from "../../../../../components/widgets/config/parser-primitives";
6
- import { PHI_CONTROL_SIZE_FIELD, parsePhiControlPresentationConfig, } from "../../../../../components/widgets/config/control-signal-config";
5
+ import { createPhiConfigReader } from "../../../../../components/widgets/config/strict-config-reader";
6
+ import { readPhiControlSize } from "../../../../../types/control";
7
+ import { PHI_CONTROL_SIZE_FIELD } from "../../../../../components/widgets/config/control-signal-config";
7
8
  import { PHI_CORE_RUNTIME_DATA_PROVIDER_KEYS } from "../../../../../constants/core-data-provider-keys";
8
9
  import { readPhiLengthValue } from "../../../../../types/length";
9
10
  import { parsePhiControlOptionsProviderConfig } from "../../../../../components/controls/phi-control-options";
10
11
  import { readPhiRuntimeConditionExpression, resolvePhiRuntimeConditionControllerRequirements, } from "../../../../../types/runtime-condition";
11
12
  import { isPhiRecord } from "../../../../../helpers/is-record";
12
- function readConditionExpression(value) {
13
- return readPhiRuntimeConditionExpression(value) ?? undefined;
14
- }
15
13
  const PHI_TABLE_DISABLED_CONDITION_CONFIG_FIELDS = [
16
14
  { key: "source", type: "choice", label: "Source", options: [
17
15
  { value: "row", label: "Row" },
@@ -123,19 +121,32 @@ const PHI_TABLE_ACTION_CONFIG_FIELDS = [
123
121
  { key: "confirm.okText", type: "string", label: "Confirm OK Text" },
124
122
  { key: "confirm.cancelText", type: "string", label: "Confirm Cancel Text" },
125
123
  ];
124
+ const PHI_TABLE_COLUMN_RENDERERS = [
125
+ "text", "email", "date", "datetime", "badge", "tags", "link", "code", "json", "switch", "checkbox", "icon",
126
+ ];
127
+ const PHI_TABLE_COLUMN_EDITOR_CONTROLS = [
128
+ "switch", "checkbox", "select", "radio", "segmented", "multi-select", "checkbox-group", "icon-picker",
129
+ ];
130
+ const PHI_TABLE_ALIGNS = ["left", "center", "right"];
131
+ function readCondition(reader, key) {
132
+ return reader.value(key, "a condition", (value) => readPhiRuntimeConditionExpression(value) ?? undefined);
133
+ }
134
+ function readLength(reader, key) {
135
+ return reader.value(key, "a CSS length", (value) => readPhiLengthValue(value) ?? undefined);
136
+ }
137
+ /** A required text: absent is as wrong as the wrong type, because the entry means nothing without it. */
138
+ function requireString(reader, key) {
139
+ return reader.string(key) ?? reader.fail(key, "a text", reader.record[key]);
140
+ }
126
141
  function readTableTagColor(value) {
127
142
  if (typeof value === "string" && PHI_TABLE_TAG_COLORS.includes(value)) {
128
143
  return value;
129
144
  }
130
145
  if (!isPhiRecord(value) || value.kind !== "custom")
131
146
  return undefined;
132
- const customValue = readString(value.value);
133
- return customValue ? { kind: "custom", value: customValue } : undefined;
134
- }
135
- function readTableTagVariant(value) {
136
- return typeof value === "string" && PHI_TABLE_TAG_VARIANTS.includes(value)
137
- ? value
138
- : "outlined";
147
+ return typeof value.value === "string" && value.value.trim()
148
+ ? { kind: "custom", value: value.value }
149
+ : undefined;
139
150
  }
140
151
  const PHI_TABLE_CORE_SUMMARY_FIELD_KEYS = [
141
152
  "totalRows",
@@ -144,510 +155,349 @@ const PHI_TABLE_CORE_SUMMARY_FIELD_KEYS = [
144
155
  "page",
145
156
  "pageCount",
146
157
  ];
147
- function isPhiTableCoreSummaryFieldKey(value) {
148
- return PHI_TABLE_CORE_SUMMARY_FIELD_KEYS.includes(value);
158
+ function readTableSummaryValueSource(reader) {
159
+ const source = reader.oneOf("source", ["core", "provider"]) ?? reader.fail("source", "\"core\" or \"provider\"");
160
+ if (source === "core") {
161
+ return {
162
+ source,
163
+ fieldKey: reader.oneOf("fieldKey", PHI_TABLE_CORE_SUMMARY_FIELD_KEYS)
164
+ ?? reader.fail("fieldKey", `one of ${PHI_TABLE_CORE_SUMMARY_FIELD_KEYS.join(", ")}`),
165
+ };
166
+ }
167
+ return { source, fieldKey: requireString(reader, "fieldKey") };
149
168
  }
150
- function readTableSummaryItem(value) {
151
- if (!isPhiRecord(value))
152
- return null;
153
- const key = readString(value.key);
154
- const rawValue = isPhiRecord(value.value) ? value.value : {};
155
- const fieldKey = readString(rawValue.fieldKey);
156
- if (!key || !fieldKey)
157
- return null;
158
- const source = rawValue.source === "core" &&
159
- isPhiTableCoreSummaryFieldKey(fieldKey)
160
- ? { source: "core", fieldKey }
161
- : rawValue.source === "provider"
162
- ? { source: "provider", fieldKey }
163
- : null;
164
- if (!source)
165
- return null;
169
+ function readTableSummaryItem(reader) {
166
170
  return {
167
- key,
168
- value: source,
169
- label: readString(value.label),
170
- labelPlacement: value.labelPlacement === "after" ? "after" : "before",
171
+ key: requireString(reader, "key"),
172
+ value: reader.object("value", readTableSummaryValueSource) ?? reader.fail("value", "a value source"),
173
+ label: reader.string("label"),
174
+ labelPlacement: reader.oneOf("labelPlacement", ["before", "after"]) ?? "before",
171
175
  };
172
176
  }
173
- function readTableFooterValues(value) {
174
- return Array.isArray(value)
175
- ? value.flatMap((item) => {
176
- const parsed = readTableSummaryItem(item);
177
- return parsed ? [{ key: parsed.key, value: parsed.value }] : [];
178
- })
179
- : [];
177
+ function readTableFooterValues(reader) {
178
+ return (reader.list("values", (entry) => {
179
+ const item = readTableSummaryItem(entry);
180
+ return { key: item.key, value: item.value };
181
+ }) ?? []);
180
182
  }
181
- function readTableFooterVariant(value) {
182
- if (!isPhiRecord(value) || !isPhiRecord(value.when))
183
- return null;
183
+ function readTableFooterVariant(reader) {
184
184
  // An empty template is a statement here, not a missing one: the footer has nothing to say.
185
- const template = typeof value.template === "string" ? value.template : null;
186
- const when = readTableSummaryItem({ key: "when", value: value.when.value });
187
- const operator = value.when.operator;
188
- if (template == null || !when || (operator !== "truthy" && operator !== "falsy"))
189
- return null;
190
- return { when: { value: when.value, operator }, template, values: readTableFooterValues(value.values) };
185
+ const template = typeof reader.record.template === "string"
186
+ ? reader.record.template
187
+ : reader.fail("template", "a text");
188
+ const when = reader.object("when", (whenReader) => ({
189
+ value: whenReader.object("value", readTableSummaryValueSource) ?? whenReader.fail("value", "a value source"),
190
+ operator: whenReader.oneOf("operator", ["truthy", "falsy"])
191
+ ?? whenReader.fail("operator", "\"truthy\" or \"falsy\""),
192
+ })) ?? reader.fail("when", "a condition");
193
+ return { when, template, values: readTableFooterValues(reader) };
191
194
  }
192
- function readTableFooter(value) {
193
- if (!isPhiRecord(value) || !Array.isArray(value.values))
194
- return undefined;
195
- const template = readString(value.template);
196
- const values = readTableFooterValues(value.values);
197
- if (!template || values.length === 0)
198
- return undefined;
199
- const variants = Array.isArray(value.variants)
200
- ? value.variants.flatMap((item) => {
201
- const variant = readTableFooterVariant(item);
202
- return variant ? [variant] : [];
203
- })
204
- : [];
195
+ /*
196
+ * A footer may name no values: a sentence of its own is a footer too. The old reader dropped such a
197
+ * footer in silence, so one that only ever said a sentence -- with variants that change it -- never
198
+ * showed at all.
199
+ */
200
+ function readTableFooter(reader) {
201
+ const values = readTableFooterValues(reader);
202
+ const variants = reader.list("variants", readTableFooterVariant) ?? [];
205
203
  return {
206
- template,
204
+ template: requireString(reader, "template"),
207
205
  values,
208
- align: value.align === "center" || value.align === "end"
209
- ? value.align
210
- : "start",
206
+ align: reader.oneOf("align", ["start", "center", "end"]) ?? "start",
211
207
  ...(variants.length > 0 ? { variants } : {}),
212
208
  };
213
209
  }
214
- function readTableSummary(value) {
215
- if (!isPhiRecord(value) || !Array.isArray(value.rows))
216
- return undefined;
217
- const rows = value.rows.flatMap((rowValue) => {
218
- if (!isPhiRecord(rowValue) || !Array.isArray(rowValue.cells))
219
- return [];
220
- const key = readString(rowValue.key);
221
- if (!key)
222
- return [];
223
- const cells = rowValue.cells.flatMap((cellValue) => {
224
- if (!isPhiRecord(cellValue))
225
- return [];
226
- const cellKey = readString(cellValue.key);
227
- const columnKey = readString(cellValue.columnKey);
228
- const item = readTableSummaryItem(cellValue.item);
229
- if (!cellKey || !columnKey || !item)
230
- return [];
231
- return [{
232
- key: cellKey,
233
- columnKey,
234
- throughColumnKey: readString(cellValue.throughColumnKey),
235
- align: cellValue.align === "center" || cellValue.align === "right" ? cellValue.align : "left",
236
- item,
237
- }];
238
- });
239
- return cells.length > 0 ? [{ key, cells }] : [];
240
- });
241
- if (rows.length === 0)
242
- return undefined;
210
+ function readTableSummary(reader) {
211
+ const rows = reader.list("rows", (rowReader) => ({
212
+ key: requireString(rowReader, "key"),
213
+ cells: rowReader.list("cells", (cellReader) => ({
214
+ key: requireString(cellReader, "key"),
215
+ columnKey: requireString(cellReader, "columnKey"),
216
+ throughColumnKey: cellReader.string("throughColumnKey"),
217
+ align: cellReader.oneOf("align", PHI_TABLE_ALIGNS) ?? "left",
218
+ item: cellReader.object("item", readTableSummaryItem) ?? cellReader.fail("item", "a summary item"),
219
+ })) ?? rowReader.fail("cells", "a list of cells"),
220
+ })) ?? reader.fail("rows", "a list of rows");
243
221
  return {
244
- placement: value.placement === "sticky-top" || value.placement === "sticky-bottom"
245
- ? value.placement
246
- : "body-end",
222
+ placement: reader.oneOf("placement", ["body-end", "sticky-top", "sticky-bottom"]) ?? "body-end",
247
223
  rows,
248
224
  };
249
225
  }
250
- function readColumns(value) {
251
- if (!Array.isArray(value)) {
252
- return [];
253
- }
254
- return value.flatMap((item) => {
255
- if (!isPhiRecord(item)) {
256
- return [];
257
- }
258
- const key = readString(item.key);
259
- const title = readString(item.title);
260
- if (!key || !title) {
261
- return [];
262
- }
263
- const fieldKey = readString(item.fieldKey);
264
- if (!fieldKey) {
265
- return [];
266
- }
267
- const renderer = item.renderer === "email" || item.renderer === "date" || item.renderer === "datetime" ||
268
- item.renderer === "badge" || item.renderer === "tags" || item.renderer === "link" ||
269
- item.renderer === "code" || item.renderer === "json" || item.renderer === "switch" ||
270
- item.renderer === "checkbox" || item.renderer === "icon"
271
- ? item.renderer
272
- : "text";
273
- const align = item.align === "center" || item.align === "right" ? item.align : "left";
274
- const rawSizing = isPhiRecord(item.sizing) ? item.sizing : {};
275
- const sizing = readTableColumnSizing(rawSizing);
276
- const valueMap = isPhiRecord(item.valueMap)
277
- ? Object.fromEntries(Object.entries(item.valueMap).filter((entry) => typeof entry[1] === "string"))
278
- : undefined;
279
- const tagColorMap = isPhiRecord(item.tagColorMap)
280
- ? Object.fromEntries(Object.entries(item.tagColorMap).flatMap(([key, value]) => {
281
- const color = readTableTagColor(value);
282
- return color == null ? [] : [[key, color]];
283
- }))
284
- : undefined;
285
- const rawEditor = isPhiRecord(item.editor) ? item.editor : undefined;
286
- const editorControl = readTableColumnEditorControl(rawEditor?.control);
287
- return [{
288
- key,
289
- title,
290
- fieldKey,
291
- sortField: readString(item.sortField),
292
- iconFieldKey: readString(item.iconFieldKey),
293
- titleIcon: readString(item.titleIcon),
294
- titleDisplay: item.titleDisplay === "icon" ? "icon" : undefined,
295
- description: readString(item.description),
296
- renderer,
297
- editor: rawEditor && readBoolean(rawEditor.enabled) !== false
298
- ? {
299
- control: editorControl,
300
- disabledWhen: readConditionExpression(rawEditor.disabledWhen),
301
- ...(readString(rawEditor.prefix) ? { prefix: readString(rawEditor.prefix) } : {}),
302
- }
303
- : undefined,
304
- sizing,
305
- align,
306
- sticky: item.sticky === "left" || item.sticky === "right" ? item.sticky : undefined,
307
- sortable: readBoolean(item.sortable),
308
- hidden: readBoolean(item.hidden),
309
- ellipsis: readBoolean(item.ellipsis),
310
- valueMap,
311
- tagColorMap,
312
- tagVariant: readTableTagVariant(item.tagVariant),
313
- }];
314
- });
315
- }
316
- function readTableColumnEditorControl(value) {
317
- return value === "switch" || value === "checkbox" || value === "select" || value === "radio" ||
318
- value === "segmented" || value === "multi-select" || value === "checkbox-group" || value === "icon-picker"
319
- ? value
320
- : undefined;
321
- }
322
- function readTableColumnSizing(value) {
323
- const minWidth = readPhiLengthValue(value.minWidth) ?? undefined;
324
- const maxWidth = readPhiLengthValue(value.maxWidth) ?? undefined;
325
- if (value.mode === "content")
326
- return { mode: "content", minWidth, maxWidth };
327
- if (value.mode === "fixed") {
328
- const width = readPhiLengthValue(value.width);
329
- return width == null ? { mode: "content" } : { mode: "fixed", width };
226
+ function readTableColumnSizing(reader) {
227
+ const mode = reader.oneOf("mode", ["content", "fixed", "fill"]) ?? "content";
228
+ if (mode === "fixed") {
229
+ return { mode, width: readLength(reader, "width") ?? reader.fail("width", "a CSS length for a fixed column") };
330
230
  }
331
- if (value.mode === "fill")
332
- return { mode: "fill", minWidth, maxWidth };
333
- return { mode: "content", minWidth, maxWidth };
231
+ return { mode, minWidth: readLength(reader, "minWidth"), maxWidth: readLength(reader, "maxWidth") };
334
232
  }
335
- function readFilterOptions(value) {
336
- if (!Array.isArray(value)) {
337
- return [];
338
- }
339
- return value.flatMap((item) => {
340
- if (!isPhiRecord(item)) {
341
- return [];
342
- }
343
- const label = readString(item.label);
344
- const optionValue = readString(item.value);
345
- return label && optionValue ? [{ label, value: optionValue }] : [];
346
- });
233
+ function readColumn(reader) {
234
+ const valueMap = reader.object("valueMap", (mapReader) => Object.fromEntries(Object.keys(mapReader.record).map((key) => [key, mapReader.string(key) ?? ""])));
235
+ const tagColorMap = reader.object("tagColorMap", (mapReader) => Object.fromEntries(Object.keys(mapReader.record).map((key) => [
236
+ key,
237
+ mapReader.value(key, "a tag colour", readTableTagColor) ?? mapReader.fail(key, "a tag colour"),
238
+ ])));
239
+ const editor = reader.object("editor", (editorReader) => editorReader.boolean("enabled") === false
240
+ ? undefined
241
+ : {
242
+ control: editorReader.oneOf("control", PHI_TABLE_COLUMN_EDITOR_CONTROLS),
243
+ disabledWhen: readCondition(editorReader, "disabledWhen"),
244
+ ...(editorReader.string("prefix") ? { prefix: editorReader.string("prefix") } : {}),
245
+ });
246
+ return {
247
+ key: requireString(reader, "key"),
248
+ title: requireString(reader, "title"),
249
+ fieldKey: requireString(reader, "fieldKey"),
250
+ sortField: reader.string("sortField"),
251
+ iconFieldKey: reader.string("iconFieldKey"),
252
+ titleIcon: reader.string("titleIcon"),
253
+ titleDisplay: reader.oneOf("titleDisplay", ["icon"]),
254
+ description: reader.string("description"),
255
+ renderer: reader.oneOf("renderer", PHI_TABLE_COLUMN_RENDERERS) ?? "text",
256
+ editor,
257
+ sizing: reader.object("sizing", readTableColumnSizing) ?? readTableColumnSizing(createPhiConfigReader({})),
258
+ align: reader.oneOf("align", PHI_TABLE_ALIGNS) ?? "left",
259
+ sticky: reader.oneOf("sticky", ["left", "right"]),
260
+ sortable: reader.boolean("sortable"),
261
+ hidden: reader.boolean("hidden"),
262
+ ellipsis: reader.boolean("ellipsis"),
263
+ valueMap,
264
+ tagColorMap,
265
+ tagVariant: reader.oneOf("tagVariant", PHI_TABLE_TAG_VARIANTS) ?? "outlined",
266
+ };
347
267
  }
348
- function readFilters(value) {
349
- if (!Array.isArray(value)) {
350
- return [];
351
- }
352
- const result = [];
353
- for (const item of value) {
354
- if (!isPhiRecord(item)) {
355
- continue;
356
- }
357
- const key = readString(item.key);
358
- const label = readString(item.label);
359
- if (!key || !label) {
360
- continue;
361
- }
362
- const labelPlacement = item.labelPlacement === "inline" || item.labelPlacement === "none"
363
- ? item.labelPlacement
364
- : undefined;
365
- if (item.type === "select") {
366
- result.push({
367
- key,
368
- type: "select",
369
- label,
370
- labelPlacement,
371
- multiple: readBoolean(item.multiple),
372
- options: readFilterOptions(item.options),
373
- optionsProvider: parsePhiControlOptionsProviderConfig(item.optionsProvider),
374
- defaultValue: typeof item.defaultValue === "string" ||
375
- (Array.isArray(item.defaultValue) && item.defaultValue.every((entry) => typeof entry === "string"))
376
- ? item.defaultValue
377
- : undefined,
378
- });
379
- continue;
380
- }
381
- if (item.type === "boolean") {
382
- result.push({
383
- key,
384
- type: "boolean",
385
- control: item.control === "switch" ? "switch" : undefined,
386
- label,
387
- labelPlacement,
388
- defaultValue: readBoolean(item.defaultValue),
389
- });
390
- continue;
391
- }
392
- if (item.type === "dateRange") {
393
- const startKey = readString(item.startKey);
394
- const endKey = readString(item.endKey);
395
- if (!startKey || !endKey) {
396
- continue;
397
- }
398
- const defaultValue = isPhiRecord(item.defaultValue)
399
- ? { start: readString(item.defaultValue.start), end: readString(item.defaultValue.end) }
400
- : undefined;
401
- result.push({
402
- key,
403
- type: "dateRange",
404
- label,
405
- labelPlacement,
406
- startKey,
407
- endKey,
408
- startPlaceholder: readString(item.startPlaceholder),
409
- endPlaceholder: readString(item.endPlaceholder),
410
- defaultValue,
411
- });
412
- continue;
413
- }
414
- result.push({
268
+ function readFilter(reader) {
269
+ const key = requireString(reader, "key");
270
+ const label = requireString(reader, "label");
271
+ const labelPlacement = reader.oneOf("labelPlacement", ["inline", "none"]);
272
+ const type = reader.oneOf("type", ["text", "select", "boolean", "dateRange"]) ?? "text";
273
+ if (type === "select") {
274
+ return {
415
275
  key,
416
- type: "text",
276
+ type,
417
277
  label,
418
278
  labelPlacement,
419
- placeholder: readString(item.placeholder),
420
- defaultValue: readString(item.defaultValue),
421
- });
279
+ multiple: reader.boolean("multiple"),
280
+ options: reader.list("options", (optionReader) => ({
281
+ label: requireString(optionReader, "label"),
282
+ value: requireString(optionReader, "value"),
283
+ })) ?? [],
284
+ optionsProvider: reader.value("optionsProvider", "an options provider", (value) => parsePhiControlOptionsProviderConfig(value) ?? undefined),
285
+ defaultValue: reader.value("defaultValue", "a text or a list of texts", (value) => typeof value === "string" ||
286
+ (Array.isArray(value) && value.every((entry) => typeof entry === "string"))
287
+ ? value
288
+ : undefined),
289
+ };
422
290
  }
423
- return result;
424
- }
425
- function readActions(value) {
426
- if (!Array.isArray(value)) {
427
- return [];
291
+ if (type === "boolean") {
292
+ return {
293
+ key,
294
+ type,
295
+ control: reader.oneOf("control", ["switch"]),
296
+ label,
297
+ labelPlacement,
298
+ defaultValue: reader.boolean("defaultValue"),
299
+ };
428
300
  }
429
- const result = [];
430
- for (const item of value) {
431
- if (!isPhiRecord(item)) {
432
- continue;
433
- }
434
- const key = readString(item.key);
435
- const label = readString(item.label);
436
- const execution = item.execution === "provider" || item.execution === "signal" || item.execution === "link"
437
- ? item.execution
438
- : null;
439
- if (!key || !label || !execution) {
440
- continue;
441
- }
442
- result.push({
301
+ if (type === "dateRange") {
302
+ return {
443
303
  key,
304
+ type,
444
305
  label,
445
- execution,
446
- icon: readString(item.icon),
447
- display: item.display === "icon" || item.display === "label" || item.display === "icon-label"
448
- ? item.display
449
- : undefined,
450
- mode: item.mode === "primary" || item.mode === "danger" ? item.mode : "normal",
451
- href: readString(item.href),
452
- hrefPath: readString(item.hrefPath),
453
- newTab: readBoolean(item.newTab),
454
- visibleWhen: readConditionExpression(item.visibleWhen),
455
- disabledWhen: readConditionExpression(item.disabledWhen),
456
- confirm: isPhiRecord(item.confirm) && readString(item.confirm.title)
457
- ? (() => {
458
- const alertLevel = isPhiRecord(item.confirm.alert) &&
459
- (item.confirm.alert.level === "success" || item.confirm.alert.level === "info" ||
460
- item.confirm.alert.level === "warning" || item.confirm.alert.level === "error")
461
- ? item.confirm.alert.level
462
- : undefined;
463
- const alert = isPhiRecord(item.confirm.alert) && readString(item.confirm.alert.title) && alertLevel
464
- ? {
465
- level: alertLevel,
466
- title: readString(item.confirm.alert.title),
467
- description: readString(item.confirm.alert.description),
468
- }
469
- : undefined;
470
- return {
471
- title: readString(item.confirm.title),
472
- description: readString(item.confirm.description),
473
- okText: readString(item.confirm.okText),
474
- cancelText: readString(item.confirm.cancelText),
475
- alert,
476
- };
477
- })()
478
- : undefined,
479
- });
306
+ labelPlacement,
307
+ startKey: requireString(reader, "startKey"),
308
+ endKey: requireString(reader, "endKey"),
309
+ startPlaceholder: reader.string("startPlaceholder"),
310
+ endPlaceholder: reader.string("endPlaceholder"),
311
+ defaultValue: reader.object("defaultValue", (rangeReader) => ({
312
+ start: rangeReader.string("start"),
313
+ end: rangeReader.string("end"),
314
+ })),
315
+ };
480
316
  }
481
- return result;
317
+ return {
318
+ key,
319
+ type,
320
+ label,
321
+ labelPlacement,
322
+ placeholder: reader.string("placeholder"),
323
+ defaultValue: reader.string("defaultValue"),
324
+ };
325
+ }
326
+ function readAction(reader) {
327
+ return {
328
+ key: requireString(reader, "key"),
329
+ label: requireString(reader, "label"),
330
+ execution: reader.oneOf("execution", ["provider", "signal", "link"])
331
+ ?? reader.fail("execution", "\"provider\", \"signal\" or \"link\""),
332
+ icon: reader.string("icon"),
333
+ display: reader.oneOf("display", ["icon", "label", "icon-label"]),
334
+ mode: reader.oneOf("mode", ["normal", "primary", "danger"]) ?? "normal",
335
+ href: reader.string("href"),
336
+ hrefPath: reader.string("hrefPath"),
337
+ newTab: reader.boolean("newTab"),
338
+ visibleWhen: readCondition(reader, "visibleWhen"),
339
+ disabledWhen: readCondition(reader, "disabledWhen"),
340
+ confirm: reader.object("confirm", (confirmReader) => ({
341
+ title: requireString(confirmReader, "title"),
342
+ description: confirmReader.string("description"),
343
+ okText: confirmReader.string("okText"),
344
+ cancelText: confirmReader.string("cancelText"),
345
+ alert: confirmReader.object("alert", (alertReader) => ({
346
+ level: alertReader.oneOf("level", ["success", "info", "warning", "error"])
347
+ ?? alertReader.fail("level", "\"success\", \"info\", \"warning\" or \"error\""),
348
+ title: requireString(alertReader, "title"),
349
+ description: alertReader.string("description"),
350
+ })),
351
+ })),
352
+ };
353
+ }
354
+ function readBindingField(reader) {
355
+ const disabledWhen = reader.object("disabledWhen", (conditionReader) => ({
356
+ fieldKey: requireString(conditionReader, "fieldKey"),
357
+ equals: conditionReader.value("equals", "a text, a number or true/false", (value) => typeof value === "string" || typeof value === "boolean" ||
358
+ (typeof value === "number" && Number.isFinite(value))
359
+ ? value
360
+ : undefined) ?? conditionReader.fail("equals", "a text, a number or true/false"),
361
+ }));
362
+ const optionLabels = reader.list("optionLabels", (optionReader) => ({
363
+ value: optionReader.value("value", "a text or a number", (value) => typeof value === "string" || (typeof value === "number" && Number.isFinite(value)) ? value : undefined) ?? optionReader.fail("value", "a text or a number"),
364
+ label: requireString(optionReader, "label"),
365
+ })) ?? [];
366
+ const cascader = reader.object("cascader", (cascaderReader) => ({
367
+ allowRoot: cascaderReader.boolean("allowRoot"),
368
+ separator: cascaderReader.string("separator"),
369
+ rootValue: cascaderReader.string("rootValue"),
370
+ normalize: cascaderReader.oneOf("normalize", ["raw", "path"]) ?? "raw",
371
+ }));
372
+ const create = reader.object("create", (createReader) => ({
373
+ label: requireString(createReader, "label"),
374
+ description: createReader.string("description"),
375
+ icon: createReader.string("icon"),
376
+ display: createReader.oneOf("display", ["icon", "label", "icon-label"]) ?? "icon",
377
+ placeholder: createReader.string("placeholder"),
378
+ submitLabel: createReader.string("submitLabel"),
379
+ }));
380
+ return {
381
+ key: requireString(reader, "key"),
382
+ label: reader.string("label"),
383
+ placeholder: reader.string("placeholder"),
384
+ control: reader.oneOf("control", ["select", "autocomplete", "cascader"]) ?? "select",
385
+ ...(disabledWhen ? { disabledWhen } : {}),
386
+ ...(optionLabels.length > 0 ? { optionLabels } : {}),
387
+ ...(cascader ? { cascader } : {}),
388
+ create,
389
+ };
482
390
  }
483
391
  export function parsePhiTableWidgetConfig(config) {
484
- const presentation = isPhiRecord(config.presentation) ? config.presentation : {};
485
- const features = isPhiRecord(config.features) ? config.features : {};
486
- const search = isPhiRecord(features.search) ? features.search : {};
487
- const pagination = isPhiRecord(features.pagination) ? features.pagination : {};
488
- const sorting = isPhiRecord(features.sorting) ? features.sorting : {};
489
- const rowSelection = isPhiRecord(features.rowSelection) ? features.rowSelection : {};
490
- const rowReordering = isPhiRecord(features.rowReordering) ? features.rowReordering : {};
491
- const columnReordering = isPhiRecord(features.columnReordering) ? features.columnReordering : {};
492
- const editing = isPhiRecord(features.editing) ? features.editing : {};
493
- const tools = isPhiRecord(features.tools) ? features.tools : {};
494
- const structure = isPhiRecord(features.structure) ? features.structure : {};
495
- const actions = isPhiRecord(features.actions) ? features.actions : {};
496
- const emptyState = isPhiRecord(presentation.emptyState) ? presentation.emptyState : {};
497
- const layout = isPhiRecord(presentation.layout) ? presentation.layout : {};
498
- const row = isPhiRecord(presentation.row) ? presentation.row : {};
499
- const initialQuery = readPhiTableQuery(isPhiRecord(config.initialQuery) ? config.initialQuery : {}) ?? {};
500
- const defaultSorts = readPhiTableQuery({ sorts: sorting.defaultSorts })?.sorts ?? [];
392
+ const root = createPhiConfigReader(config);
393
+ const section = (reader, key) => reader.object(key, (nested) => nested) ?? createPhiConfigReader({}, reader.path ? `${reader.path}.${key}` : key);
394
+ const presentation = section(root, "presentation");
395
+ const features = section(root, "features");
396
+ const search = section(features, "search");
397
+ const pagination = section(features, "pagination");
398
+ const sorting = section(features, "sorting");
399
+ const rowSelection = section(features, "rowSelection");
400
+ const rowReordering = section(features, "rowReordering");
401
+ const columnReordering = section(features, "columnReordering");
402
+ const editing = section(features, "editing");
403
+ const tools = section(features, "tools");
404
+ const structure = section(features, "structure");
405
+ const actions = section(features, "actions");
406
+ const emptyState = section(presentation, "emptyState");
407
+ const layout = section(presentation, "layout");
408
+ const row = section(presentation, "row");
409
+ const readQuery = (reader, key, wrap, expected) => reader.value(key, expected, (value) => readPhiTableQuery(wrap(value)) ?? undefined);
410
+ const initialQuery = readQuery(root, "initialQuery", (value) => value, "a table query") ?? {};
411
+ const defaultSorts = readQuery(sorting, "defaultSorts", (value) => ({ sorts: value }), "a list of sorts")?.sorts ?? [];
501
412
  // Read with the same rules as any query's filters; a value no filter can carry is refused, not kept.
502
- const fixedFilters = config.fixedFilters === undefined
503
- ? undefined
504
- : readPhiTableQuery({ filters: config.fixedFilters })?.filters;
505
- if (config.fixedFilters !== undefined && fixedFilters === undefined) {
506
- throw new Error("Table fixedFilters must map filter keys to query values.");
507
- }
508
- const presentationControls = parsePhiControlPresentationConfig(presentation);
509
- const toolsMode = tools.mode === "external" ? "external" : "self-contained";
510
- const toolsReset = readBoolean(tools.reset);
511
- const treeStructure = structure.mode === "tree" && readString(structure.parentRowIdentityPath)
413
+ const fixedFilters = readQuery(root, "fixedFilters", (value) => ({ filters: value }), "filter keys mapped to query values")?.filters;
414
+ const controlSize = presentation.value("controlSize", "a control size", readPhiControlSize);
415
+ const toolsMode = tools.oneOf("mode", ["self-contained", "external"]) ?? "self-contained";
416
+ const toolsReset = tools.boolean("reset");
417
+ const structureMode = structure.oneOf("mode", ["flat", "tree"]) ?? "flat";
418
+ const treeStructure = structureMode === "tree"
512
419
  ? {
513
420
  mode: "tree",
514
- parentRowIdentityPath: readString(structure.parentRowIdentityPath),
515
- defaultExpandedRowIdentities: readPhiTableQuery({
516
- expandedRowIdentities: structure.defaultExpandedRowIdentities,
517
- })?.expandedRowIdentities,
518
- expandRowByClick: readBoolean(structure.expandRowByClick),
519
- indentSize: readNumber(structure.indentSize),
520
- expandColumnKey: readString(structure.expandColumnKey),
421
+ parentRowIdentityPath: requireString(structure, "parentRowIdentityPath"),
422
+ defaultExpandedRowIdentities: readQuery(structure, "defaultExpandedRowIdentities", (value) => ({ expandedRowIdentities: value }), "a list of row identities")?.expandedRowIdentities,
423
+ expandRowByClick: structure.boolean("expandRowByClick"),
424
+ indentSize: structure.number("indentSize"),
425
+ expandColumnKey: requireString(structure, "expandColumnKey"),
521
426
  }
522
427
  : { mode: "flat" };
523
428
  return {
524
429
  presentation: {
525
- ...presentationControls,
526
- controlSize: presentationControls.controlSize ?? (toolsMode === "self-contained" ? "small" : "medium"),
527
- title: readString(presentation.title),
528
- description: readString(presentation.description),
529
- columns: readColumns(presentation.columns),
430
+ controlSize: controlSize ?? (toolsMode === "self-contained" ? "small" : "medium"),
431
+ title: presentation.string("title"),
432
+ description: presentation.string("description"),
433
+ columns: presentation.list("columns", readColumn) ?? [],
530
434
  layout: {
531
- mode: layout.mode === "fixed" ? "fixed" : "auto",
532
- overflowX: layout.overflowX === "visible" ? "visible" : "auto",
435
+ mode: layout.oneOf("mode", ["auto", "fixed"]) ?? "auto",
436
+ overflowX: layout.oneOf("overflowX", ["auto", "visible"]) ?? "auto",
533
437
  },
534
438
  emptyState: {
535
- title: readString(emptyState.title),
536
- description: readString(emptyState.description),
439
+ title: emptyState.string("title"),
440
+ description: emptyState.string("description"),
537
441
  },
538
- borders: readBoolean(presentation.borders),
539
- header: readBoolean(presentation.header),
540
- stickyHeader: readBoolean(presentation.stickyHeader),
541
- footer: readTableFooter(presentation.footer),
542
- summary: readTableSummary(presentation.summary),
442
+ borders: presentation.boolean("borders"),
443
+ header: presentation.boolean("header"),
444
+ stickyHeader: presentation.boolean("stickyHeader"),
445
+ footer: presentation.object("footer", readTableFooter),
446
+ summary: presentation.object("summary", readTableSummary),
543
447
  row: {
544
- striped: readBoolean(row.striped),
545
- mutedWhen: readConditionExpression(row.mutedWhen),
448
+ striped: row.boolean("striped"),
449
+ mutedWhen: readCondition(row, "mutedWhen"),
546
450
  },
547
451
  },
548
452
  features: {
549
453
  search: {
550
- enabled: readBoolean(search.enabled),
551
- placeholder: readString(search.placeholder),
552
- debounceMs: readNumber(search.debounceMs),
454
+ enabled: search.boolean("enabled"),
455
+ placeholder: search.string("placeholder"),
456
+ debounceMs: search.number("debounceMs"),
553
457
  },
554
- filters: readFilters(features.filters),
458
+ filters: features.list("filters", readFilter) ?? [],
555
459
  pagination: {
556
- enabled: readBoolean(pagination.enabled),
557
- pageSize: readNumber(pagination.pageSize),
558
- pageSizes: readNumberList(pagination.pageSizes),
460
+ enabled: pagination.boolean("enabled"),
461
+ pageSize: pagination.number("pageSize"),
462
+ pageSizes: pagination.value("pageSizes", "a list of numbers", (value) => Array.isArray(value) && value.length > 0 &&
463
+ value.every((entry) => typeof entry === "number" && Number.isFinite(entry))
464
+ ? value
465
+ : undefined),
559
466
  },
560
467
  sorting: {
561
- mode: sorting.mode === "single" || sorting.mode === "multiple" ? sorting.mode : "none",
468
+ mode: sorting.oneOf("mode", ["none", "single", "multiple"]) ?? "none",
562
469
  defaultSorts,
563
470
  },
564
471
  rowSelection: {
565
- mode: rowSelection.mode === "single" || rowSelection.mode === "multiple" ? rowSelection.mode : "none",
566
- preserveSelectedRowIdentities: readBoolean(rowSelection.preserveSelectedRowIdentities),
567
- disabledWhen: readConditionExpression(rowSelection.disabledWhen),
568
- ...(Array.isArray(rowSelection.emitFields) ? {
569
- emitFields: rowSelection.emitFields.flatMap((field) => {
570
- const key = readString(field)?.trim();
571
- return key ? [key] : [];
572
- }),
573
- } : {}),
472
+ mode: rowSelection.oneOf("mode", ["none", "single", "multiple"]) ?? "none",
473
+ preserveSelectedRowIdentities: rowSelection.boolean("preserveSelectedRowIdentities"),
474
+ disabledWhen: readCondition(rowSelection, "disabledWhen"),
475
+ ...(rowSelection.has("emitFields")
476
+ ? { emitFields: (rowSelection.stringList("emitFields") ?? []).map((field) => field.trim()).filter(Boolean) }
477
+ : {}),
574
478
  },
575
479
  rowReordering: {
576
- enabled: readBoolean(rowReordering.enabled),
480
+ enabled: rowReordering.boolean("enabled"),
577
481
  },
578
482
  columnReordering: {
579
- enabled: readBoolean(columnReordering.enabled),
483
+ enabled: columnReordering.boolean("enabled"),
580
484
  },
581
485
  editing: {
582
- mode: editing.mode === "cell" || editing.mode === "row" ? editing.mode : "none",
583
- disabledWhen: readConditionExpression(editing.disabledWhen),
486
+ mode: editing.oneOf("mode", ["none", "cell", "row"]) ?? "none",
487
+ disabledWhen: readCondition(editing, "disabledWhen"),
584
488
  },
585
489
  tools: {
586
490
  mode: toolsMode,
587
- bindingFields: Array.isArray(tools.bindingFields)
588
- ? tools.bindingFields.flatMap((entry) => {
589
- if (!isPhiRecord(entry) || !readString(entry.key))
590
- return [];
591
- const rawCreate = isPhiRecord(entry.create) ? entry.create : null;
592
- const createLabel = readString(rawCreate?.label);
593
- const disabledWhen = isPhiRecord(entry.disabledWhen) && readString(entry.disabledWhen.fieldKey) &&
594
- (typeof entry.disabledWhen.equals === "string" ||
595
- typeof entry.disabledWhen.equals === "boolean" ||
596
- typeof entry.disabledWhen.equals === "number" && Number.isFinite(entry.disabledWhen.equals))
597
- ? {
598
- fieldKey: readString(entry.disabledWhen.fieldKey),
599
- equals: entry.disabledWhen.equals,
600
- }
601
- : undefined;
602
- const optionLabels = Array.isArray(entry.optionLabels)
603
- ? entry.optionLabels.flatMap((option) => {
604
- if (!isPhiRecord(option))
605
- return [];
606
- const value = typeof option.value === "number" && Number.isFinite(option.value)
607
- ? option.value
608
- : readString(option.value);
609
- const label = readString(option.label);
610
- return value !== undefined && label ? [{ value, label }] : [];
611
- })
612
- : [];
613
- const cascader = isPhiRecord(entry.cascader) ? {
614
- allowRoot: readBoolean(entry.cascader.allowRoot),
615
- separator: readString(entry.cascader.separator),
616
- rootValue: readString(entry.cascader.rootValue),
617
- normalize: entry.cascader.normalize === "path" ? "path" : "raw",
618
- } : undefined;
619
- return [{
620
- key: readString(entry.key),
621
- label: readString(entry.label),
622
- placeholder: readString(entry.placeholder),
623
- control: entry.control === "autocomplete" || entry.control === "cascader"
624
- ? entry.control
625
- : "select",
626
- ...(disabledWhen ? { disabledWhen } : {}),
627
- ...(optionLabels.length > 0 ? { optionLabels } : {}),
628
- ...(cascader ? { cascader } : {}),
629
- create: rawCreate && createLabel ? {
630
- label: createLabel,
631
- description: readString(rawCreate.description),
632
- icon: readString(rawCreate.icon),
633
- display: rawCreate.display === "label" || rawCreate.display === "icon-label"
634
- ? rawCreate.display
635
- : "icon",
636
- placeholder: readString(rawCreate.placeholder),
637
- submitLabel: readString(rawCreate.submitLabel),
638
- } : undefined,
639
- }];
640
- })
641
- : undefined,
491
+ bindingFields: tools.list("bindingFields", readBindingField),
642
492
  ...(toolsReset === undefined ? {} : { reset: toolsReset }),
643
- reload: readBoolean(tools.reload),
493
+ reload: tools.boolean("reload"),
644
494
  },
645
495
  structure: treeStructure,
646
496
  actions: {
647
- rowLayout: actions.rowLayout === "spaced" ? "spaced" : "compact",
648
- toolbar: readActions(actions.toolbar),
649
- row: readActions(actions.row),
650
- bulk: readActions(actions.bulk),
497
+ rowLayout: actions.oneOf("rowLayout", ["compact", "spaced"]) ?? "compact",
498
+ toolbar: actions.list("toolbar", readAction) ?? [],
499
+ row: actions.list("row", readAction) ?? [],
500
+ bulk: actions.list("bulk", readAction) ?? [],
651
501
  },
652
502
  },
653
503
  initialQuery: {
@@ -655,7 +505,7 @@ export function parsePhiTableWidgetConfig(config) {
655
505
  sorts: initialQuery.sorts,
656
506
  },
657
507
  ...(fixedFilters ? { fixedFilters } : {}),
658
- source: readPhiProviderResourceSource(config.source),
508
+ source: root.value("source", "a provider source", (value) => readPhiProviderResourceSource(value) ?? undefined) ?? null,
659
509
  signalRoutes: readPhiSignalRouteSet(config.signalRoutes),
660
510
  };
661
511
  }