@parallelworks/ui 0.34.0 → 0.36.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 (86) hide show
  1. package/README.md +201 -0
  2. package/dist/ai/components/ChatEmptyState.js +71 -86
  3. package/dist/ai/components/ChatInput.d.ts +11 -3
  4. package/dist/ai/components/ChatInput.js +19 -11
  5. package/dist/ai/components/ChatSidebar.js +12 -130
  6. package/dist/ai/components/ChatStage.d.ts +11 -0
  7. package/dist/ai/components/ChatStage.js +26 -0
  8. package/dist/ai/components/ComposerChrome.d.ts +3 -0
  9. package/dist/ai/components/ComposerChrome.js +21 -2
  10. package/dist/ai/components/agent/AgentMessageParts.js +8 -2
  11. package/dist/ai/components/conversationActions.d.ts +23 -0
  12. package/dist/ai/components/conversationActions.js +108 -0
  13. package/dist/ai/index.d.ts +4 -3
  14. package/dist/ai/index.js +6 -5
  15. package/dist/ai/strings.d.ts +7 -0
  16. package/dist/ai/strings.js +8 -1
  17. package/dist/ai/types.d.ts +2 -0
  18. package/dist/ai/ui/ConversationSidebar.d.ts +3 -2
  19. package/dist/ai/ui/ConversationSidebar.js +2 -1
  20. package/dist/ai/ui/sidebar.d.ts +6 -0
  21. package/dist/ai/ui/sidebar.js +10 -1
  22. package/dist/components/Dropdown.js +3 -1
  23. package/dist/components/FilterPill.js +1 -0
  24. package/dist/components/Provider.d.ts +14 -0
  25. package/dist/components/Provider.js +14 -3
  26. package/dist/components/SegmentedControl.d.ts +24 -0
  27. package/dist/components/SegmentedControl.js +69 -0
  28. package/dist/editing.d.ts +32 -0
  29. package/dist/editor/FormCodePanel.js +1 -1
  30. package/dist/engine.d.ts +3 -0
  31. package/dist/form/FieldWrapper.js +1 -1
  32. package/dist/form/Fields/CheckboxGroupField.d.ts +3 -3
  33. package/dist/form/Fields/CheckboxGroupField.js +10 -4
  34. package/dist/form/Fields/RadioField.d.ts +5 -3
  35. package/dist/form/Fields/RadioField.js +25 -17
  36. package/dist/form/Fields/fieldOption.d.ts +12 -0
  37. package/dist/form/Fields/fieldOption.js +17 -0
  38. package/dist/form/Form.d.ts +4 -2
  39. package/dist/form/Form.js +170 -68
  40. package/dist/form/FormLayout.d.ts +7 -0
  41. package/dist/form/FormLayout.js +144 -0
  42. package/dist/form/SpecialTypes/FormikCustomDropdown.js +1 -1
  43. package/dist/form/SpecialTypes/FormikCustomInput.js +7 -0
  44. package/dist/form/Wizard/WizardContainer.d.ts +1 -1
  45. package/dist/form/Wizard/WizardContainer.js +244 -49
  46. package/dist/form/Wizard/WizardNavigation.js +4 -4
  47. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  48. package/dist/form/Wizard/WizardStepContent.js +19 -10
  49. package/dist/form/Wizard/WizardStepIndicator.js +35 -7
  50. package/dist/form/Wizard/types.d.ts +19 -6
  51. package/dist/form/Wizard/useWizardState.d.ts +1 -0
  52. package/dist/form/Wizard/useWizardState.js +5 -1
  53. package/dist/form/Wizard/utils.d.ts +7 -0
  54. package/dist/form/Wizard/utils.js +18 -1
  55. package/dist/form/index.d.ts +2 -0
  56. package/dist/form/index.js +4 -2
  57. package/dist/form/layout.d.ts +50 -0
  58. package/dist/form/layout.js +125 -0
  59. package/dist/form/layoutCSS.d.ts +10 -0
  60. package/dist/form/layoutCSS.js +234 -0
  61. package/dist/form/lib.d.ts +2 -0
  62. package/dist/form/lib.js +17 -2
  63. package/dist/form/types/fieldTypes.d.ts +8 -23
  64. package/dist/graph/DependencyGraph.d.ts +20 -1
  65. package/dist/graph/DependencyGraph.js +437 -142
  66. package/dist/graph/MatrixGroup.d.ts +3 -1
  67. package/dist/graph/MatrixGroup.js +31 -28
  68. package/dist/graph/TreeView.js +6 -6
  69. package/dist/graph/gridSpacing.d.ts +7 -0
  70. package/dist/graph/gridSpacing.js +10 -0
  71. package/dist/graph/util.d.ts +2 -0
  72. package/dist/graph/util.js +2 -2
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.js +2 -1
  75. package/dist/list/ListViewControls.d.ts +15 -0
  76. package/dist/list/ListViewControls.js +9 -3
  77. package/dist/list/index.d.ts +1 -1
  78. package/dist/list/index.js +2 -2
  79. package/dist/styles/base.css +3 -1
  80. package/dist/styles/theme.css +5 -0
  81. package/dist/styles.css +1 -1
  82. package/dist/theme/index.d.ts +2 -0
  83. package/dist/theme/index.js +3 -2
  84. package/package.json +4 -2
  85. package/dist/ai/components/ComposerFrame.d.ts +0 -16
  86. package/dist/ai/components/ComposerFrame.js +0 -33
@@ -194,6 +194,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
194
194
  modifiers: popperModifiers
195
195
  });
196
196
  const debounceRef = useRef(null);
197
+ const typedRef = useRef("");
197
198
  const buttonRef = useRef(null);
198
199
  const [expandedCategories, setExpandedCategories] = useState(/* @__PURE__ */ new Set());
199
200
  const selectedCategory = useMemo(() => options.find((o) => isCategory(o) && o.options.some((opt) => checkEquals(value.value, opt.value)))?.category, [options, value]);
@@ -213,6 +214,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
213
214
  const onQueryChange = (val) => {
214
215
  setQuery(val);
215
216
  if (allowCustomValue) {
217
+ typedRef.current = val;
216
218
  if (debounceRef.current) clearTimeout(debounceRef.current);
217
219
  debounceRef.current = setTimeout(() => {
218
220
  flushCustomValue(val);
@@ -224,7 +226,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
224
226
  if (allowCustomValue && debounceRef.current) {
225
227
  clearTimeout(debounceRef.current);
226
228
  debounceRef.current = null;
227
- flushCustomValue(query);
229
+ flushCustomValue(typedRef.current);
228
230
  }
229
231
  setQuery("");
230
232
  };
@@ -4,6 +4,7 @@ import { jsx } from "react/jsx-runtime";
4
4
  function FilterPill({ active, onClick, children }) {
5
5
  return /* @__PURE__ */ jsx("button", {
6
6
  type: "button",
7
+ "aria-pressed": active,
7
8
  onClick,
8
9
  className: cx("px-3 py-1.5 text-xs font-medium rounded-full border transition-all duration-200", active ? "bg-[var(--theme-panel-bg)] border-[var(--theme-border)] text-[var(--theme-panel)] shadow-sm" : "bg-transparent border-transparent text-[var(--theme-muted-text-color)] hover:text-[var(--theme-panel)] hover:bg-[var(--theme-hover)]"),
9
10
  children
@@ -100,6 +100,13 @@ export interface UIStrings {
100
100
  submitted: string;
101
101
  expandAll: string;
102
102
  collapseAll: string;
103
+ zoomIn: string;
104
+ zoomOut: string;
105
+ resetView: string;
106
+ previousGraph: string;
107
+ nextGraph: string;
108
+ matrixOf: (name: string) => string;
109
+ jobCount: (count: number) => string;
103
110
  loadingLogs: string;
104
111
  noLogFound: string;
105
112
  logLoadFailed: string;
@@ -321,6 +328,12 @@ export interface UIStrings {
321
328
  invalidDuration: string;
322
329
  durationOutOfRange: (min: string, max: string) => string;
323
330
  durationBelowMin: (min: string) => string;
331
+ /** A repeated wizard page's copy, numbered from 1. */
332
+ copyTitle: (title: string, number: number) => string;
333
+ addCopy: (title: string) => string;
334
+ removeCopy: (title: string) => string;
335
+ /** A wizard's row of step dots. */
336
+ steps: string;
324
337
  };
325
338
  time: {
326
339
  now: string;
@@ -419,6 +432,7 @@ export type UILinkComponent = React.ComponentType<{
419
432
  children: ReactNode;
420
433
  title?: string | undefined;
421
434
  'aria-label'?: string | undefined;
435
+ 'aria-current'?: 'page' | undefined;
422
436
  [dataAttr: `data-${string}`]: string | undefined;
423
437
  }>;
424
438
  /** The provider's link slot, with a plain anchor fallback. */
@@ -102,8 +102,15 @@ var DEFAULTS = {
102
102
  status: "Status",
103
103
  runtime: "Runtime",
104
104
  submitted: "Submitted",
105
- expandAll: "Expand all",
106
- collapseAll: "Collapse all",
105
+ expandAll: "Expand All",
106
+ collapseAll: "Collapse All",
107
+ zoomIn: "Zoom in",
108
+ zoomOut: "Zoom out",
109
+ resetView: "Reset View",
110
+ previousGraph: "Back to the previous graph",
111
+ nextGraph: "Forward to the next graph",
112
+ matrixOf: (name) => `Matrix: ${name}`,
113
+ jobCount: (count) => count === 1 ? "1 job" : `${count} jobs`,
107
114
  loadingLogs: "Loading logs",
108
115
  noLogFound: "No log found",
109
116
  logLoadFailed: "Log could not be loaded",
@@ -322,7 +329,11 @@ var DEFAULTS = {
322
329
  form: {
323
330
  invalidDuration: "Enter a duration as DD-HH:MM:SS or HH:MM:SS.",
324
331
  durationOutOfRange: (min, max) => `Must be between ${min} and ${max}.`,
325
- durationBelowMin: (min) => `Must be at least ${min}.`
332
+ durationBelowMin: (min) => `Must be at least ${min}.`,
333
+ copyTitle: (title, number) => `${title} ${number}`,
334
+ addCopy: (title) => `+ Add ${title}`,
335
+ removeCopy: (title) => `Remove ${title}`,
336
+ steps: "Steps"
326
337
  },
327
338
  time: {
328
339
  now: "now",
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface SegmentedOption<T extends string> {
3
+ value: T;
4
+ label: string;
5
+ icon?: ReactNode;
6
+ /** Beside the label, or in the corner when only the icon shows. */
7
+ badge?: ReactNode;
8
+ /** Navigates instead of calling `onChange`, for segments that are pages. */
9
+ to?: string;
10
+ testId?: string;
11
+ }
12
+ /** A few mutually exclusive choices side by side, one always current. With
13
+ * `iconOnly` the segments stack as icons with their labels in tooltips, for
14
+ * a collapsed rail. */
15
+ export declare function SegmentedControl<T extends string>({ label, options, value, onChange, fill, iconOnly, testId, }: {
16
+ label: string;
17
+ options: SegmentedOption<T>[];
18
+ value: T;
19
+ onChange?: (value: T) => void;
20
+ /** Segments share the container's width equally. */
21
+ fill?: boolean;
22
+ iconOnly?: boolean;
23
+ testId?: string;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { useLink } from "./Provider.js";
2
+ import { TOOLTIP_ID } from "./Tooltip.js";
3
+ import cx from "classnames";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/components/SegmentedControl.tsx
6
+ var itemBase = "relative flex min-w-0 items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-link)";
7
+ var itemCurrent = "bg-(--theme-panel-bg) theme-text shadow-sm ring-1 ring-(--theme-border)";
8
+ var itemIdle = "theme-muted-text hover:theme-text hover:bg-(--theme-hover)";
9
+ /** A few mutually exclusive choices side by side, one always current. With
10
+ * `iconOnly` the segments stack as icons with their labels in tooltips, for
11
+ * a collapsed rail. */
12
+ function SegmentedControl({ label, options, value, onChange, fill = false, iconOnly = false, testId }) {
13
+ const Link = useLink();
14
+ const links = options.some((o) => o.to);
15
+ const items = options.map((option) => {
16
+ const current = option.value === value;
17
+ const className = cx(itemBase, current ? itemCurrent : itemIdle, iconOnly ? "h-8 w-8" : cx("h-7 gap-1.5 text-[13px]", fill ? "px-1.5" : "px-2.5"));
18
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
19
+ option.icon,
20
+ iconOnly ? /* @__PURE__ */ jsx("span", {
21
+ className: "sr-only",
22
+ children: option.label
23
+ }) : /* @__PURE__ */ jsx("span", {
24
+ className: "truncate",
25
+ children: option.label
26
+ }),
27
+ option.badge && (iconOnly ? /* @__PURE__ */ jsx("span", {
28
+ className: "absolute top-1 right-1 inline-flex",
29
+ children: option.badge
30
+ }) : option.badge)
31
+ ] });
32
+ const tooltip = iconOnly ? {
33
+ "data-tooltip-id": TOOLTIP_ID,
34
+ "data-tooltip-content": option.label,
35
+ "data-tooltip-place": "right"
36
+ } : {};
37
+ return option.to ? /* @__PURE__ */ jsx(Link, {
38
+ to: option.to,
39
+ "aria-current": current ? "page" : void 0,
40
+ "data-testid": option.testId,
41
+ className,
42
+ ...tooltip,
43
+ children: content
44
+ }, option.value) : /* @__PURE__ */ jsx("button", {
45
+ type: "button",
46
+ "aria-pressed": current,
47
+ "data-testid": option.testId,
48
+ onClick: () => onChange?.(option.value),
49
+ className,
50
+ ...tooltip,
51
+ children: content
52
+ }, option.value);
53
+ });
54
+ const groupClassName = cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fill ? "grid auto-cols-fr grid-flow-col" : "inline-flex"));
55
+ return links ? /* @__PURE__ */ jsx("nav", {
56
+ "aria-label": label,
57
+ "data-testid": testId,
58
+ className: groupClassName,
59
+ children: items
60
+ }) : /* @__PURE__ */ jsx("div", {
61
+ role: "group",
62
+ "aria-label": label,
63
+ "data-testid": testId,
64
+ className: groupClassName,
65
+ children: items
66
+ });
67
+ }
68
+ //#endregion
69
+ export { SegmentedControl };
@@ -0,0 +1,32 @@
1
+ /** A job's slot on the dependency graph's column/row grid. */
2
+ export interface GraphPosition {
3
+ column: number;
4
+ row: number;
5
+ }
6
+ /** Job name to grid slot, as stored on a workflow. */
7
+ export type GraphLayout = Record<string, GraphPosition>;
8
+ /** One column's boxes, top to bottom, the row each box sits in, and the column's number. */
9
+ export interface LaidOutColumn {
10
+ boxes: string[][];
11
+ rows: number[];
12
+ column: number;
13
+ }
14
+ /**
15
+ * The workflow editing the visual editor defers to: reading and rewriting the workflow's YAML,
16
+ * its graph layout and its checks. A host built on a workflow package can pass that package's
17
+ * functions of the same names.
18
+ */
19
+ export interface WorkflowEditing {
20
+ /** The job a `needs` entry points at, without a matrix's wait-for-any suffix. */
21
+ needTarget: (need: string) => string;
22
+ /** Jobs whose matrix runs more than once as `<job>-<n>` members, the way a run lists them. */
23
+ expandMatrixJobs: <T>(jobs: Record<string, T>) => Record<string, T>;
24
+ hasLayout: (layout: GraphLayout | undefined) => boolean;
25
+ layoutPosition: (layout: GraphLayout | undefined, job: string) => GraphPosition | undefined;
26
+ mapLayout: (layout: GraphLayout, fn: (position: GraphPosition, job: string) => GraphPosition | undefined) => GraphLayout;
27
+ /** Boxes and rows from a stored layout; jobs without a slot go below, grouped when `together` says. */
28
+ boxesFromLayout: (jobNames: string[], deps: Record<string, string[]>, layout: GraphLayout, rules: {
29
+ together: (a: string, b: string) => boolean;
30
+ alone: (job: string) => boolean;
31
+ }) => LaidOutColumn[];
32
+ }
@@ -4,8 +4,8 @@ import { FiCopy } from "../icons/fi/FiCopy.js";
4
4
  import { LuEye } from "../icons/lu/LuEye.js";
5
5
  import { LuEyeOff } from "../icons/lu/LuEyeOff.js";
6
6
  import SwitchToggleSmall from "../components/SwitchToggleSmall.js";
7
- import Editor from "./Editor.js";
8
7
  import { flattenGroups, impureSetValueFromPath } from "../form/lib.js";
8
+ import Editor from "./Editor.js";
9
9
  import cx from "classnames";
10
10
  import { useId, useRef, useState } from "react";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
package/dist/engine.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { WorkflowEditing } from './editing.js';
1
2
  export type RunStatus = 'started' | 'running' | 'completed' | 'error' | 'failed' | 'canceled' | 'skipped' | 'faulted' | 'canceling' | 'skipped-failed' | '' | undefined;
2
3
  export interface MatrixGroup {
3
4
  originaljob: string;
@@ -95,4 +96,6 @@ export interface WorkflowEngine {
95
96
  segments: LogSegment[];
96
97
  }[], rawLines: string[]): ProcessedLogLine[];
97
98
  toYaml(value: unknown): string;
99
+ /** What the visual editor and stored graph layouts need; without it, graphs lay themselves out. */
100
+ editing?: WorkflowEditing | undefined;
98
101
  }
@@ -1,6 +1,6 @@
1
1
  import { RequiredMark } from "../components/RequiredMark.js";
2
- import { FieldProvider } from "./fieldContext.js";
3
2
  import { resolvedFlag } from "./lib.js";
3
+ import { FieldProvider } from "./fieldContext.js";
4
4
  import cx from "classnames";
5
5
  import { useId, useMemo } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -2,9 +2,9 @@ import type { FieldComponentProps } from '../types/fieldComponentTypes.js';
2
2
  import type { BaseField } from '../types/fieldTypes.js';
3
3
  export interface ICheckboxGroupField extends BaseField {
4
4
  type: 'checkbox-group';
5
- options?: Array<{
6
- label: string;
7
- value: string;
5
+ options?: Array<string | number | {
6
+ label?: string;
7
+ value: string | number;
8
8
  description?: string;
9
9
  }>;
10
10
  /**
@@ -1,4 +1,5 @@
1
1
  import { FieldWrapper } from "../FieldWrapper.js";
2
+ import { fieldOption } from "./fieldOption.js";
2
3
  import cx from "classnames";
3
4
  import { useState } from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -17,8 +18,12 @@ function CheckboxGroupField({ field, label, labelPosition, missing, disabled, on
17
18
  const [fieldState] = useField(fieldName);
18
19
  const { setFieldValue, setFieldTouched } = useFormikContext();
19
20
  const implies = field.implies;
21
+ const rawOf = new Map((field.options ?? []).map((choice) => {
22
+ const option = fieldOption(choice);
23
+ return [option.value, option.raw];
24
+ }));
20
25
  const [manual, setManual] = useState(() => {
21
- const saved = fieldState.value || [];
26
+ const saved = (fieldState.value || []).map(String);
22
27
  const impliedBySaved = impliedFrom(saved, implies);
23
28
  return saved.filter((v) => !impliedBySaved.has(v));
24
29
  });
@@ -26,7 +31,7 @@ function CheckboxGroupField({ field, label, labelPosition, missing, disabled, on
26
31
  const handleToggle = (value) => {
27
32
  if (disabled) return;
28
33
  const nextManual = manual.includes(value) ? manual.filter((v) => v !== value) : [...manual, value];
29
- const next = union(nextManual, impliedFrom(nextManual, implies));
34
+ const next = union(nextManual, impliedFrom(nextManual, implies)).map((v) => rawOf.get(v) ?? v);
30
35
  setManual(nextManual);
31
36
  setFieldTouched(fieldName, true);
32
37
  setFieldValue(fieldName, next);
@@ -43,7 +48,8 @@ function CheckboxGroupField({ field, label, labelPosition, missing, disabled, on
43
48
  "aria-labelledby": labelId,
44
49
  "aria-describedby": describedBy,
45
50
  className: "flex min-w-0 max-w-lg flex-col gap-1",
46
- children: field.options?.map((option) => {
51
+ children: field.options?.map((raw) => {
52
+ const option = fieldOption(raw);
47
53
  const locked = forced.has(option.value);
48
54
  const optionDisabled = disabled || locked;
49
55
  return /* @__PURE__ */ jsxs("label", {
@@ -53,7 +59,7 @@ function CheckboxGroupField({ field, label, labelPosition, missing, disabled, on
53
59
  checked: manual.includes(option.value) || locked,
54
60
  disabled: optionDisabled,
55
61
  onChange: () => handleToggle(option.value),
56
- className: cx("mt-px h-4 w-4 rounded accent-(--theme-link)", optionDisabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid")
62
+ className: cx("h-4 min-h-0 w-4 rounded accent-(--theme-link)", optionDisabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid")
57
63
  }), /* @__PURE__ */ jsxs("div", {
58
64
  className: "flex min-w-0 flex-col",
59
65
  children: [/* @__PURE__ */ jsx("span", {
@@ -1,9 +1,11 @@
1
- import type React from 'react';
2
1
  import type { FieldComponentProps } from '../types/fieldComponentTypes.js';
3
2
  import type { BaseField } from '../types/fieldTypes.js';
4
3
  export interface IRadioField extends BaseField {
5
4
  type: 'radio';
6
- options?: string[];
5
+ options?: Array<string | number | {
6
+ label?: string;
7
+ value: string | number;
8
+ }>;
7
9
  optionLabelPosition?: 'left' | 'top';
8
10
  }
9
- export default function RadioField(props: FieldComponentProps<IRadioField>): React.JSX.Element;
11
+ export default function RadioField(props: FieldComponentProps<IRadioField>): import("react").JSX.Element;
@@ -1,13 +1,15 @@
1
1
  import { FieldWrapper } from "../FieldWrapper.js";
2
+ import { fieldOption } from "./fieldOption.js";
2
3
  import cx from "classnames";
3
4
  import { useId } from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
- import { Field, useFormikContext } from "formik";
6
+ import { useField, useFormikContext } from "formik";
6
7
  //#region src/form/Fields/RadioField.tsx
7
8
  function RadioField(props) {
8
9
  const { field, label, labelPosition, missing, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
9
10
  const fieldName = field.name ?? "";
10
11
  const uid = useId();
12
+ const [fieldState] = useField(fieldName);
11
13
  const { setFieldValue, setFieldTouched } = useFormikContext();
12
14
  const handleChange = (val) => {
13
15
  if (disabled) return;
@@ -27,22 +29,28 @@ function RadioField(props) {
27
29
  "aria-labelledby": labelId,
28
30
  "aria-describedby": describedBy,
29
31
  "aria-required": field.optional ? void 0 : true,
30
- className: cx("flex", field.optionLabelPosition === "top" ? "flex-col" : "flex-row"),
31
- children: field.options?.map((option) => /* @__PURE__ */ jsxs("label", {
32
- htmlFor: `${uid}-${option}`,
33
- className: "capitalize",
34
- children: [/* @__PURE__ */ jsx(Field, {
35
- id: `${uid}-${option}`,
36
- className: cx("theme-input mr-1", disabled && "disabled", missing && "invalid"),
37
- name: field.name,
38
- type: "radio",
39
- value: option,
40
- disabled,
41
- onChange: (e) => {
42
- handleChange(e.target.value);
43
- }
44
- }), option]
45
- }, field.name + option))
32
+ className: cx("flex py-1.5", field.optionLabelPosition === "top" ? "flex-col gap-2" : "flex-row flex-wrap gap-x-5 gap-y-2"),
33
+ children: field.options?.map((raw) => {
34
+ const option = fieldOption(raw);
35
+ const unlabelled = typeof raw !== "object" || raw.label === void 0;
36
+ return /* @__PURE__ */ jsxs("label", {
37
+ htmlFor: `${uid}-${option.value}`,
38
+ className: cx("flex items-center gap-2", disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"),
39
+ children: [/* @__PURE__ */ jsx("input", {
40
+ id: `${uid}-${option.value}`,
41
+ className: cx("h-4 w-4", disabled ? "cursor-not-allowed" : "cursor-pointer", missing && "invalid"),
42
+ name: field.name,
43
+ type: "radio",
44
+ value: option.value,
45
+ checked: fieldState.value !== void 0 && fieldState.value !== null && String(fieldState.value) === option.value,
46
+ disabled,
47
+ onChange: () => handleChange(option.raw)
48
+ }), /* @__PURE__ */ jsx("span", {
49
+ className: cx("theme-text", unlabelled && "capitalize"),
50
+ children: option.label
51
+ })]
52
+ }, field.name + option.value);
53
+ })
46
54
  })
47
55
  });
48
56
  }
@@ -0,0 +1,12 @@
1
+ /** A choice as `{ label, value }`, its value as text so a numeric option or default still matches, and
2
+ * `raw` as written, so a picked number is stored as one. */
3
+ export declare function fieldOption(option: string | number | {
4
+ label?: string;
5
+ value: string | number;
6
+ description?: string;
7
+ }): {
8
+ label: string;
9
+ value: string;
10
+ raw: string | number;
11
+ description?: string | undefined;
12
+ };
@@ -0,0 +1,17 @@
1
+ //#region src/form/Fields/fieldOption.ts
2
+ /** A choice as `{ label, value }`, its value as text so a numeric option or default still matches, and
3
+ * `raw` as written, so a picked number is stored as one. */
4
+ function fieldOption(option) {
5
+ return typeof option === "object" && option !== null ? {
6
+ ...option,
7
+ label: option.label ?? String(option.value),
8
+ value: String(option.value),
9
+ raw: option.value
10
+ } : {
11
+ label: String(option),
12
+ value: String(option),
13
+ raw: option
14
+ };
15
+ }
16
+ //#endregion
17
+ export { fieldOption };
@@ -75,8 +75,10 @@ export declare function FieldsFromOptions({ options, values, setFormDirty, setFi
75
75
  spaceCompact?: boolean;
76
76
  /** Prevents deletion of hidden+ignored field values (workflow forms filter at submit time) */
77
77
  workflowForm?: boolean | undefined;
78
- }): React.JSX.Element[];
78
+ }): React.JSX.Element;
79
79
  export interface DynamicFormProps {
80
+ /** Enables validated layout-only CSS declarations on authored layout nodes. */
81
+ allowLayoutCSS?: boolean;
80
82
  formJSONs: Record<string, unknown>;
81
83
  reinitialize?: boolean;
82
84
  setFormDirty?: (dirty: boolean) => void;
@@ -104,5 +106,5 @@ export interface DynamicFormProps {
104
106
  /** Host-supplied field components, merged over the core registry by type. */
105
107
  fields?: Record<string, FieldComponent>;
106
108
  }
107
- export declare function DynamicForm({ formJSONs: options, reinitialize, initialValues, setValues, setFormDirty, formikRef, labelPosition, missingFields, spaceCompact, organizationVariables, remoteVars, needsOrganizationVariables, onSubmit, submitLabel, className, workflowForm, contextKey, skipValueParse, fields, }: DynamicFormProps): React.JSX.Element;
109
+ export declare function DynamicForm({ formJSONs: options, reinitialize, initialValues, setValues, setFormDirty, formikRef, labelPosition, missingFields, spaceCompact, organizationVariables, remoteVars, needsOrganizationVariables, onSubmit, submitLabel, className, workflowForm, contextKey, skipValueParse, fields, allowLayoutCSS, }: DynamicFormProps): React.JSX.Element;
108
110
  export {};