@cratis/scene.components 4.11.0 → 4.12.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 (42) hide show
  1. package/cratisComponentsDescriptors.ts +6 -4
  2. package/dist/cjs/cratisComponentsDescriptors.js +21 -5
  3. package/dist/cjs/cratisComponentsDescriptors.js.map +1 -1
  4. package/dist/cjs/forms/CommandFormRuntime.js +32 -20
  5. package/dist/cjs/forms/CommandFormRuntime.js.map +1 -1
  6. package/dist/cjs/forms/ExplicitCommandField.js +7 -3
  7. package/dist/cjs/forms/ExplicitCommandField.js.map +1 -1
  8. package/dist/cjs/forms/SceneCommandForm.js +4 -0
  9. package/dist/cjs/forms/SceneCommandForm.js.map +1 -1
  10. package/dist/cjs/forms/commandFormLayout.js +80 -0
  11. package/dist/cjs/forms/commandFormLayout.js.map +1 -0
  12. package/dist/cjs/forms/commandInputs.js +11 -2
  13. package/dist/cjs/forms/commandInputs.js.map +1 -1
  14. package/dist/esm/cratisComponentsDescriptors.d.ts.map +1 -1
  15. package/dist/esm/cratisComponentsDescriptors.js +21 -5
  16. package/dist/esm/cratisComponentsDescriptors.js.map +1 -1
  17. package/dist/esm/forms/CommandFormRuntime.d.ts +3 -1
  18. package/dist/esm/forms/CommandFormRuntime.d.ts.map +1 -1
  19. package/dist/esm/forms/CommandFormRuntime.js +32 -20
  20. package/dist/esm/forms/CommandFormRuntime.js.map +1 -1
  21. package/dist/esm/forms/ExplicitCommandField.d.ts +3 -1
  22. package/dist/esm/forms/ExplicitCommandField.d.ts.map +1 -1
  23. package/dist/esm/forms/ExplicitCommandField.js +7 -3
  24. package/dist/esm/forms/ExplicitCommandField.js.map +1 -1
  25. package/dist/esm/forms/SceneCommandForm.d.ts.map +1 -1
  26. package/dist/esm/forms/SceneCommandForm.js +4 -0
  27. package/dist/esm/forms/SceneCommandForm.js.map +1 -1
  28. package/dist/esm/forms/commandFormLayout.d.ts +3 -0
  29. package/dist/esm/forms/commandFormLayout.d.ts.map +1 -0
  30. package/dist/esm/forms/commandFormLayout.js +78 -0
  31. package/dist/esm/forms/commandFormLayout.js.map +1 -0
  32. package/dist/esm/forms/commandInputs.d.ts +2 -2
  33. package/dist/esm/forms/commandInputs.d.ts.map +1 -1
  34. package/dist/esm/forms/commandInputs.js +11 -2
  35. package/dist/esm/forms/commandInputs.js.map +1 -1
  36. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  37. package/forms/CommandFormRuntime.tsx +16 -7
  38. package/forms/ExplicitCommandField.tsx +10 -2
  39. package/forms/SceneCommandForm.tsx +2 -0
  40. package/forms/commandFormLayout.ts +61 -0
  41. package/forms/commandInputs.ts +6 -3
  42. package/package.json +7 -7
@@ -4,9 +4,11 @@
4
4
  import type { ComponentType, ReactNode } from 'react';
5
5
  import { AutoCommandForm } from '@cratis/components/CommandForm';
6
6
  import { CommandForm } from '@cratis/arc.react/commands';
7
+ import { formWidthToCss, normalizeCommandFormLayout, validateCommandFormLayout } from '@cratis/scene.engine';
7
8
  import { Guid } from '@cratis/fundamentals';
8
9
  import type { BoundConstructor } from '../bindings';
9
10
  import type { CommandInput } from './commandInputs';
11
+ import type { CommandFormLayout } from '@cratis/scene.model';
10
12
  import { CommandFormSubmit } from './CommandFormSubmit';
11
13
  import { ExplicitCommandField } from './ExplicitCommandField';
12
14
 
@@ -16,6 +18,7 @@ interface CommandFormRuntimeProps {
16
18
  exclude?: string[];
17
19
  submitLabel: string;
18
20
  columns?: number;
21
+ layout?: CommandFormLayout;
19
22
  }
20
23
 
21
24
  // Runtime bindings erase the proxy's keys; the optional footer is a Components >=4.13 contract.
@@ -60,15 +63,21 @@ function capability(command: BoundConstructor, inputs?: CommandInput[], exclude?
60
63
  }
61
64
 
62
65
  /** Private lazy entry point: importing the Scene registry must not import Arc. */
63
- export default function CommandFormRuntime({ command, inputs, exclude, submitLabel, columns = 1 }: CommandFormRuntimeProps) {
66
+ export default function CommandFormRuntime({ command, inputs, exclude, submitLabel, columns = 1, layout }: CommandFormRuntimeProps) {
64
67
  const error = capability(command, inputs, exclude);
65
68
  if (error) return <div role='alert'>{error}</div>;
66
69
  // The registry's never-argument constructor is erased; Arc creates this checked proxy with no args.
67
- if (inputs) return <CommandForm command={command as unknown as new () => object} showTitles={false}>
68
- <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`, gap: '1rem' }}>
69
- {inputs.map(input => <ExplicitCommandField key={input.property} input={input} />)}
70
- </div>
71
- <CommandFormSubmit label={submitLabel} />
72
- </CommandForm>;
70
+ if (inputs) {
71
+ const formLayout = normalizeCommandFormLayout(layout, inputs);
72
+ const diagnostics = validateCommandFormLayout(formLayout, inputs.map(input => input.property));
73
+ if (diagnostics.length) return <div role='alert'>{diagnostics[0].message}</div>;
74
+ const columnsCss = formLayout.columns.map(column => formWidthToCss(column.width) ?? 'minmax(0, 1fr)').join(' ') || `repeat(${Math.max(1, columns)}, minmax(0, 1fr))`;
75
+ return <CommandForm command={command as unknown as new () => object} showTitles={false}>
76
+ <div style={{ display: 'grid', gridTemplateColumns: columnsCss, columnGap: formWidthToCss(formLayout.columnGap) ?? '1rem', rowGap: formWidthToCss(formLayout.rowGap) ?? '1rem' }}>
77
+ {inputs.map(input => <ExplicitCommandField key={input.property} input={input} placement={formLayout.placements.find(placement => placement.field === input.property) ?? input.placement} />)}
78
+ </div>
79
+ <CommandFormSubmit label={submitLabel} />
80
+ </CommandForm>;
81
+ }
73
82
  return <NativeAutoCommandForm command={command} exclude={exclude} footer={<CommandFormSubmit label={submitLabel} />} />;
74
83
  }
@@ -2,6 +2,8 @@
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
4
  import { useId, useLayoutEffect, useRef, useState } from 'react';
5
+ import { formWidthToCss } from '@cratis/scene.engine';
6
+ import { FormFieldPlacement } from '@cratis/scene.model';
5
7
  import { asCommandFormField, useCommandFormContext } from '@cratis/arc.react/commands';
6
8
  import { Guid } from '@cratis/fundamentals';
7
9
  import type { CommandInput } from './commandInputs';
@@ -80,8 +82,14 @@ function TextControl({ value, onChange, onBlur, invalid, required, errors, label
80
82
  const NativeTextField = asCommandFormField<TextProps>(TextControl, { defaultValue: '' });
81
83
 
82
84
  /** Opaque child is self-bound by Arc's native runtime binding, with an exact fieldName. */
83
- export function ExplicitCommandField({ input }: { input: CommandInput }) {
84
- return <div style={{ gridColumn: input.column === undefined ? undefined : `${input.column}`, width: input.width }}>
85
+ export function ExplicitCommandField({ input, placement }: { input: CommandInput; placement?: FormFieldPlacement }) {
86
+ const effectivePlacement = placement ?? input.placement;
87
+ const columnSpan = effectivePlacement?.columnSpan ?? 1;
88
+ return <div style={{
89
+ gridColumn: effectivePlacement ? `${effectivePlacement.column} / span ${columnSpan}` : undefined,
90
+ gridRow: effectivePlacement ? `${effectivePlacement.row} / span ${effectivePlacement.rowSpan ?? 1}` : undefined,
91
+ width: formWidthToCss(effectivePlacement?.width),
92
+ }}>
85
93
  <NativeTextField fieldName={input.property} value={(command: Record<string, unknown>) => command[input.property]}
86
94
  property={input.property} guid={input.type === 'guid'} label={input.label} title={input.label} />
87
95
  </div>;
@@ -6,6 +6,7 @@ import { RegisteredComponentProps } from '@cratis/scene.react';
6
6
  import { ArcRuntimeBoundary, BindingKind, MissingBinding, resolveElementBinding } from '../bindings';
7
7
  import type { BoundConstructor } from '../bindings';
8
8
  import { numberProperty, stringArrayProperty, stringProperty, unionProperty } from '../properties';
9
+ import { commandFormLayout } from './commandFormLayout';
9
10
  import { commandInputs } from './commandInputs';
10
11
 
11
12
  const CommandFormRuntime = lazy(() => import('./CommandFormRuntime'));
@@ -44,6 +45,7 @@ export function SceneCommandForm({ element }: RegisteredComponentProps) {
44
45
  exclude={stringArrayProperty(element.properties, 'exclude')}
45
46
  submitLabel={stringProperty(element.properties, 'submitLabel') ?? 'Submit'}
46
47
  {...(numberProperty(element.properties, 'columns') === undefined ? {} : { columns: numberProperty(element.properties, 'columns') })}
48
+ {...(commandFormLayout(element.properties) === undefined ? {} : { layout: commandFormLayout(element.properties) })}
47
49
  />
48
50
  </ArcRuntimeBoundary>
49
51
  );
@@ -0,0 +1,61 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { CommandFormLayout, FormColumn, FormFieldPlacement, FormWidth, FormWidthUnit } from '@cratis/scene.model';
5
+ import { objectProperty } from '../properties';
6
+
7
+ /** Reads typed command-form layout metadata from an external component property bag. */
8
+ export function commandFormLayout(properties: Record<string, unknown>): CommandFormLayout | undefined {
9
+ const layout = objectProperty(properties, 'layout');
10
+ if (!layout) return undefined;
11
+ const columns = arrayOfObjects(layout.columns)?.map(readColumn).filter((column): column is FormColumn => column !== undefined);
12
+ const placements = arrayOfObjects(layout.placements)?.map(readPlacement).filter((placement): placement is FormFieldPlacement => placement !== undefined);
13
+ if (!columns || !placements) return undefined;
14
+ return {
15
+ columns,
16
+ placements,
17
+ ...(readWidth(layout.columnGap) ? { columnGap: readWidth(layout.columnGap) } : {}),
18
+ ...(readWidth(layout.rowGap) ? { rowGap: readWidth(layout.rowGap) } : {}),
19
+ };
20
+ }
21
+
22
+ function readColumn(value: Record<string, unknown>): FormColumn | undefined {
23
+ const index = numberValue(value.index);
24
+ if (index === undefined) return undefined;
25
+ return {
26
+ index,
27
+ ...(readWidth(value.width) ? { width: readWidth(value.width) } : {}),
28
+ ...(readWidth(value.minWidth) ? { minWidth: readWidth(value.minWidth) } : {}),
29
+ ...(readWidth(value.maxWidth) ? { maxWidth: readWidth(value.maxWidth) } : {}),
30
+ };
31
+ }
32
+
33
+ function readPlacement(value: Record<string, unknown>): FormFieldPlacement | undefined {
34
+ const field = typeof value.field === 'string' ? value.field : undefined;
35
+ const row = numberValue(value.row);
36
+ const column = numberValue(value.column);
37
+ if (!field || row === undefined || column === undefined) return undefined;
38
+ return {
39
+ field,
40
+ row,
41
+ column,
42
+ ...(numberValue(value.rowSpan) ? { rowSpan: numberValue(value.rowSpan) } : {}),
43
+ ...(numberValue(value.columnSpan) ? { columnSpan: numberValue(value.columnSpan) } : {}),
44
+ ...(readWidth(value.width) ? { width: readWidth(value.width) } : {}),
45
+ };
46
+ }
47
+
48
+ function readWidth(value: unknown): FormWidth | undefined {
49
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
50
+ const record = value as Record<string, unknown>;
51
+ if (!Object.values(FormWidthUnit).includes(record.unit as FormWidthUnit)) return undefined;
52
+ return { unit: record.unit as FormWidthUnit, ...(numberValue(record.value) !== undefined ? { value: numberValue(record.value) } : {}) };
53
+ }
54
+
55
+ function arrayOfObjects(value: unknown): Record<string, unknown>[] | undefined {
56
+ return Array.isArray(value) && value.every(item => typeof item === 'object' && item !== null && !Array.isArray(item)) ? value as Record<string, unknown>[] : undefined;
57
+ }
58
+
59
+ function numberValue(value: unknown): number | undefined {
60
+ return typeof value === 'number' && !Number.isNaN(value) ? value : undefined;
61
+ }
@@ -1,6 +1,8 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
+ import { FormFieldPlacement } from '@cratis/scene.model';
5
+ import { parseFormWidth } from '@cratis/scene.engine';
4
6
  import { arrayProperty, numberProperty, objectProperty, stringProperty } from '../properties';
5
7
 
6
8
  /** Exact, case-sensitive names from a generated command's propertyDescriptors. */
@@ -8,8 +10,7 @@ export interface CommandInput {
8
10
  property: string;
9
11
  type: 'string' | 'guid';
10
12
  label: string;
11
- column?: number;
12
- width?: string;
13
+ placement?: FormFieldPlacement;
13
14
  }
14
15
 
15
16
  /** An absent declaration selects legacy auto mode; a present but invalid declaration never does. */
@@ -27,7 +28,9 @@ export function commandInputs(properties: Record<string, unknown>): CommandInput
27
28
  seen.add(property);
28
29
  const column = numberProperty(input, 'column');
29
30
  const width = stringProperty(input, 'width');
30
- inputs.push({ property, type: input.type, label, column, width });
31
+ inputs.push({ property, type: input.type, label, ...(column === undefined && width === undefined ? {} : {
32
+ placement: { field: property, row: inputs.length + 1, column: column ?? 1, ...(parseFormWidth(width) ? { width: parseFormWidth(width) } : {}) },
33
+ }) });
31
34
  }
32
35
  return inputs;
33
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene.components",
3
- "version": "4.11.0",
3
+ "version": "4.12.0",
4
4
  "description": "The Cratis Components package: exposes @cratis/components' Arc-bound data, form and dialog composites as Scene components. Built on PrimeReact and Tailwind, and declares both as dependencies.",
5
5
  "keywords": [
6
6
  "cratis",
@@ -54,17 +54,17 @@
54
54
  "build-storybook": "storybook build"
55
55
  },
56
56
  "dependencies": {
57
- "@cratis/scene.engine": "4.11.0",
58
- "@cratis/scene.model": "4.11.0",
59
- "@cratis/scene.react": "4.11.0"
57
+ "@cratis/scene.engine": "4.12.0",
58
+ "@cratis/scene.model": "4.12.0",
59
+ "@cratis/scene.react": "4.12.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@cratis/arc": "22.50.5",
63
63
  "@cratis/arc.react": "22.50.5",
64
64
  "@cratis/components": "4.25.0",
65
- "@cratis/scene.engine": "4.11.0",
66
- "@cratis/scene.model": "4.11.0",
67
- "@cratis/scene.react": "4.11.0",
65
+ "@cratis/scene.engine": "4.12.0",
66
+ "@cratis/scene.model": "4.12.0",
67
+ "@cratis/scene.react": "4.12.0",
68
68
  "@storybook/addon-links": "^10.6.1",
69
69
  "@storybook/react": "^10.6.1",
70
70
  "@storybook/react-vite": "^10.6.1",