@parallelworks/ui 0.12.1 → 0.14.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 (84) hide show
  1. package/dist/chunks/{BareModal-DBa84JZO.js → BareModal-BqroLbZ5.js} +1 -0
  2. package/dist/chunks/{CodePreviewEditor-hwjZof0M.js → CodePreviewEditor-67A_SKgE.js} +1 -1
  3. package/dist/chunks/{CreateModal-DLCC8t_x.js → CreateModal-DoA2WsXf.js} +28 -8
  4. package/dist/chunks/{Monaco-DCwRDrPm.js → Monaco-H56KyEdb.js} +22 -12
  5. package/dist/chunks/{PageHeader-dyoL44ue.js → PageHeader-CxxbidPp.js} +44 -10
  6. package/dist/chunks/{Provider-CfFV-vc5.js → Provider-CbFVFB7O.js} +8 -2
  7. package/dist/chunks/RequiredMark-DfLdYbYZ.js +11 -0
  8. package/dist/chunks/{StatusBadge-P6C2tv7Z.js → StatusBadge-RRUI78iS.js} +4 -4
  9. package/dist/chunks/{SwitchToggle-DcPCXkqL.js → SwitchToggle-DbW0Q__L.js} +49 -32
  10. package/dist/chunks/{Table-ByziK93x.js → Table-CutP1bRt.js} +22 -6
  11. package/dist/chunks/{Tooltip-WgbiC_nd.js → Tooltip-n9w7Tkt1.js} +28 -3
  12. package/dist/chunks/{editor-BH81U5-C.js → editor-BZ9Buwi4.js} +34 -23
  13. package/dist/chunks/lib-DyJvmbzj.js +256 -0
  14. package/dist/chunks/{list-CWVR3dfZ.js → list-CBZpAYP7.js} +35 -15
  15. package/dist/components/Breadcrumbs.d.ts +9 -0
  16. package/dist/components/CollapsiblePanel.d.ts +4 -4
  17. package/dist/components/CreateModal.d.ts +8 -8
  18. package/dist/components/Dropdown.d.ts +5 -3
  19. package/dist/components/Input.d.ts +1 -1
  20. package/dist/components/Listbox.d.ts +7 -4
  21. package/dist/components/Loader.d.ts +1 -1
  22. package/dist/components/MultiSelect.d.ts +8 -5
  23. package/dist/components/PageHeader.d.ts +4 -3
  24. package/dist/components/Pagination.d.ts +2 -2
  25. package/dist/components/Provider.d.ts +9 -3
  26. package/dist/components/RequiredMark.d.ts +1 -0
  27. package/dist/components/SettingRow.d.ts +1 -1
  28. package/dist/components/SwitchToggle.d.ts +3 -2
  29. package/dist/components/Table.d.ts +5 -3
  30. package/dist/components/Tooltip.d.ts +7 -1
  31. package/dist/components/dropdownUtils.d.ts +6 -6
  32. package/dist/editor/FormCodePanel.d.ts +3 -3
  33. package/dist/editor/Monaco.d.ts +2 -2
  34. package/dist/editor/index.js +1 -1
  35. package/dist/file-explorer/FileExplorer.d.ts +2 -2
  36. package/dist/file-explorer/helpers.d.ts +2 -2
  37. package/dist/file-explorer/index.js +46 -18
  38. package/dist/file-explorer/lib/fileExplorer.d.ts +1 -1
  39. package/dist/file-explorer/lib/types.d.ts +21 -21
  40. package/dist/form/FieldWrapper.d.ts +6 -1
  41. package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
  42. package/dist/form/Fields/StepField.d.ts +1 -1
  43. package/dist/form/Form.d.ts +28 -27
  44. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +16 -3
  45. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +11 -11
  46. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +12 -10
  47. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +1 -1
  48. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +2 -2
  49. package/dist/form/Wizard/WizardContainer.d.ts +1 -1
  50. package/dist/form/Wizard/WizardStepContent.d.ts +2 -2
  51. package/dist/form/Wizard/types.d.ts +21 -14
  52. package/dist/form/Wizard/utils.d.ts +1 -1
  53. package/dist/form/fieldContext.d.ts +19 -0
  54. package/dist/form/index.d.ts +2 -1
  55. package/dist/form/index.js +191 -106
  56. package/dist/form/lib.d.ts +3 -3
  57. package/dist/form/types/fieldComponentTypes.d.ts +2 -2
  58. package/dist/form/useParsedOpts.d.ts +1 -1
  59. package/dist/form/utils/getParentValue.d.ts +3 -1
  60. package/dist/form/utils/getValueUsingPath.d.ts +1 -1
  61. package/dist/form/utils/pwResolver.d.ts +4 -4
  62. package/dist/icons.d.ts +8 -8
  63. package/dist/icons.js +4 -4
  64. package/dist/index.d.ts +4 -3
  65. package/dist/index.js +51 -21
  66. package/dist/list/ActionToolbar.d.ts +4 -4
  67. package/dist/list/HoverCard.d.ts +3 -1
  68. package/dist/list/ListPage.d.ts +3 -3
  69. package/dist/list/ListTable.d.ts +1 -1
  70. package/dist/list/RowContextMenu.d.ts +20 -20
  71. package/dist/list/index.js +2 -2
  72. package/dist/logviewer/index.js +24 -7
  73. package/dist/styles/theme.css +6 -6
  74. package/dist/styles.css +1 -1
  75. package/dist/theme/index.js +4 -3
  76. package/dist/workflow/AnnotationBanner.d.ts +2 -1
  77. package/dist/workflow/DependencyGraph.d.ts +15 -3
  78. package/dist/workflow/JobSummary.d.ts +2 -25
  79. package/dist/workflow/MatrixGroup.d.ts +3 -2
  80. package/dist/workflow/TreeView.d.ts +1 -32
  81. package/dist/workflow/index.js +235 -138
  82. package/dist/workflow/types.d.ts +48 -0
  83. package/package.json +2 -6
  84. package/dist/chunks/lib-DTgL8klR.js +0 -191
@@ -4,6 +4,19 @@ import { Fragment as Fragment$1 } from "react";
4
4
  import { Tooltip } from "react-tooltip";
5
5
  //#region src/components/Tooltip.tsx
6
6
  var TOOLTIP_ID = "global-tooltip";
7
+ /** Anchor props that reveal `content` only while the text is ellipsized; `measure` picks the clipped descendant when it isn't the anchor. */
8
+ function truncationTooltipProps(content, measure) {
9
+ return {
10
+ role: "none",
11
+ "data-tooltip-id": TOOLTIP_ID,
12
+ onMouseEnter: (event) => {
13
+ const anchor = event.currentTarget;
14
+ const measured = measure ? measure(anchor) : anchor;
15
+ if (measured && measured.scrollWidth > measured.clientWidth) anchor.setAttribute("data-tooltip-content", content);
16
+ else anchor.removeAttribute("data-tooltip-content");
17
+ }
18
+ };
19
+ }
7
20
  var linkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
8
21
  function formatLine(line) {
9
22
  const nodes = [];
@@ -40,12 +53,14 @@ function KbdShortcut({ keys }) {
40
53
  })] }, `${part}-${idx}`))
41
54
  });
42
55
  }
56
+ var globalCloseEvents = { escape: true };
43
57
  /** Singleton rendered at layout root; anchors opt in via data-tooltip-id={TOOLTIP_ID}, data-tooltip-content, and data-tooltip-shortcut. */
44
58
  function GlobalTooltip() {
45
59
  return /* @__PURE__ */ jsx(Tooltip, {
46
60
  id: TOOLTIP_ID,
47
61
  className: "max-w-lg break-words config-tips normal-case text-left z-[10000]",
48
62
  place: "top",
63
+ globalCloseEvents,
49
64
  render: ({ content, activeAnchor }) => {
50
65
  const body = formatTooltipContent(content);
51
66
  const shortcut = activeAnchor?.getAttribute("data-tooltip-shortcut");
@@ -58,14 +73,24 @@ function GlobalTooltip() {
58
73
  });
59
74
  }
60
75
  function TooltipInfo({ text, className = "", children, place = "top", tabIndex }) {
61
- return /* @__PURE__ */ jsx("div", {
76
+ const anchorProps = {
62
77
  "data-tooltip-id": TOOLTIP_ID,
63
78
  "data-tooltip-content": text,
64
79
  "data-tooltip-place": place,
65
- className,
80
+ className
81
+ };
82
+ if (children || !text) return /* @__PURE__ */ jsx("div", {
83
+ ...anchorProps,
66
84
  tabIndex,
67
85
  children: children || /* @__PURE__ */ jsx(TooltipIcon, { className: "tooltip" })
68
86
  });
87
+ return /* @__PURE__ */ jsx("div", {
88
+ ...anchorProps,
89
+ role: "img",
90
+ "aria-label": text,
91
+ tabIndex: tabIndex ?? 0,
92
+ children: /* @__PURE__ */ jsx(TooltipIcon, { className: "tooltip" })
93
+ });
69
94
  }
70
95
  //#endregion
71
- export { formatTooltipContent as i, TOOLTIP_ID as n, TooltipInfo as r, GlobalTooltip as t };
96
+ export { truncationTooltipProps as a, formatTooltipContent as i, TOOLTIP_ID as n, TooltipInfo as r, GlobalTooltip as t };
@@ -1,15 +1,15 @@
1
1
  import { CheckIcon, CopyIcon, EyeIcon, EyeOffIcon } from "../icons.js";
2
- import { c as useStrings, i as useNotify } from "./Provider-CfFV-vc5.js";
2
+ import { c as useStrings, i as useNotify } from "./Provider-CbFVFB7O.js";
3
3
  import { t as Loader } from "./Loader-BXoXTZGc.js";
4
4
  import { t as SwitchToggleSmall } from "./SwitchToggleSmall-FcsaYSTs.js";
5
- import { c as FieldWrapper, n as flattenGroups, r as impureSetValueFromPath } from "./lib-DTgL8klR.js";
5
+ import { c as FieldWrapper, n as flattenGroups, r as impureSetValueFromPath } from "./lib-DyJvmbzj.js";
6
6
  import cx from "classnames";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
- import { Suspense, lazy, useMemo, useRef, useState } from "react";
8
+ import { Suspense, lazy, useId, useMemo, useRef, useState } from "react";
9
9
  import { convertToDynamicForm, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
10
10
  import { useField, useFormikContext } from "formik";
11
11
  //#region src/editor/Editor.tsx
12
- var MonacoEditor = lazy(() => import("./Monaco-DCwRDrPm.js"));
12
+ var MonacoEditor = lazy(() => import("./Monaco-H56KyEdb.js"));
13
13
  function Editor({ ...props }) {
14
14
  return /* @__PURE__ */ jsx(Suspense, {
15
15
  fallback: /* @__PURE__ */ jsx(Loader, { text: "Loading editor..." }),
@@ -19,7 +19,7 @@ function Editor({ ...props }) {
19
19
  //#endregion
20
20
  //#region src/editor/EditorField.tsx
21
21
  function hasResetKeys(obj) {
22
- return obj.editorKeyPostfix !== void 0;
22
+ return typeof obj === "object" && obj !== null && "editorKeyPostfix" in obj && typeof obj.editorKeyPostfix === "string";
23
23
  }
24
24
  function EditorField(props) {
25
25
  const { field, label, labelPosition, disabled, onChange, tooltipComponent, spaceCompact, setFormDirty } = props;
@@ -51,9 +51,10 @@ function EditorField(props) {
51
51
  labelPosition,
52
52
  spaceCompact,
53
53
  children: /* @__PURE__ */ jsx(Editor, {
54
+ ariaLabel: label,
54
55
  height: "200px",
55
56
  path: field.name,
56
- value: touchedRef.current ? void 0 : value,
57
+ ...touchedRef.current ? {} : { value },
57
58
  className: cx("bg-(--theme-input-bg) w-11 border focus:border rounded text-[10px] leading-4 pl-[6px]", disabled && "opacity-75"),
58
59
  readOnly: disabled,
59
60
  onChange: handleChange,
@@ -63,53 +64,59 @@ function EditorField(props) {
63
64
  }
64
65
  //#endregion
65
66
  //#region src/editor/FormCodePanel.tsx
67
+ var isObject = (value) => typeof value === "object" && value !== null;
66
68
  var shouldIgnoreField = (fieldSchema, inputs, arrayIndex) => {
67
- if (!fieldSchema?.ignore || !fieldSchema?.hidden) return false;
69
+ if (!fieldSchema?.["ignore"] || !fieldSchema?.["hidden"]) return false;
68
70
  if (!parseStringsOfObjReady) return false;
69
71
  return parseStringsOfObj({
70
72
  inputs,
71
73
  obj: convertToDynamicForm(fieldSchema),
72
74
  index: arrayIndex ?? -1
73
- }).hidden === true;
75
+ })["hidden"] === true;
74
76
  };
75
77
  var filterFields = (formJSON, data, prefix, rootData, arrayIndex) => {
76
78
  const filteredFields = [];
77
79
  return {
78
80
  filteredFields,
79
81
  filtered: Object.keys(data).reduce((acc, key) => {
80
- if (data[key] !== null && Array.isArray(data[key])) {
81
- acc[key] = [];
82
- if (!formJSON[key]) return acc;
83
- for (let i = 0; i < data[key].length; i++) {
82
+ const value = data[key];
83
+ const fieldSchema = formJSON[key];
84
+ if (value !== null && Array.isArray(value)) {
85
+ const items = [];
86
+ acc[key] = items;
87
+ if (!fieldSchema) return acc;
88
+ const nestedSchema = isObject(fieldSchema) ? fieldSchema["options"] : void 0;
89
+ for (let i = 0; i < value.length; i++) {
84
90
  let filteredFieldsNested = [];
85
91
  let filteredNested = {};
86
- if (typeof data[key][i] === "object") {
87
- const result = filterFields(formJSON[key].options, data[key][i], key + `[${i}].`, rootData || data, i);
92
+ const item = value[i];
93
+ if (isObject(item)) {
94
+ const result = filterFields(isObject(nestedSchema) ? nestedSchema : {}, item, key + `[${i}].`, rootData || data, i);
88
95
  filteredNested = result.filtered;
89
96
  filteredFieldsNested = result.filteredFields;
90
- } else filteredNested = data[key][i];
97
+ } else filteredNested = item;
91
98
  filteredFields.push(...filteredFieldsNested);
92
- acc[key][i] = filteredNested;
99
+ items[i] = filteredNested;
93
100
  }
94
101
  return acc;
95
102
  }
96
- if (data[key] !== null && formJSON && formJSON[key] && typeof data[key] === "object") {
97
- const { filteredFields: filteredFieldsNested, filtered: filteredNested } = filterFields(formJSON[key], data[key], key + ".", rootData || data, arrayIndex);
103
+ if (fieldSchema && isObject(value)) {
104
+ const { filteredFields: filteredFieldsNested, filtered: filteredNested } = filterFields(isObject(fieldSchema) ? fieldSchema : {}, value, key + ".", rootData || data, arrayIndex);
98
105
  filteredFields.push(...filteredFieldsNested);
99
106
  acc[key] = filteredNested;
100
107
  return acc;
101
108
  }
102
- if (formJSON?.[key]?.sensitive) {
109
+ if (isObject(fieldSchema) && fieldSchema["sensitive"]) {
103
110
  const name = prefix ? prefix + key : key;
104
111
  filteredFields.push({
105
112
  key: name,
106
- value: data[key]
113
+ value
107
114
  });
108
115
  return acc;
109
116
  }
110
117
  const contextData = rootData || data;
111
- if (formJSON && shouldIgnoreField(formJSON[key], contextData, arrayIndex)) return acc;
112
- acc[key] = data[key];
118
+ if (isObject(fieldSchema) && shouldIgnoreField(fieldSchema, contextData, arrayIndex)) return acc;
119
+ acc[key] = value;
113
120
  return acc;
114
121
  }, {})
115
122
  };
@@ -123,6 +130,7 @@ var populateFiltered = (filteredFields, data) => {
123
130
  function FormCodePanel({ formJSON, data, mutate, height = "600px", disabled = false }) {
124
131
  const notify = useNotify();
125
132
  const { common: t } = useStrings();
133
+ const uid = useId();
126
134
  const [jsonHasErrors, setJsonHasErrors] = useState(false);
127
135
  const [showHidden, setShowHidden] = useState(false);
128
136
  const [copied, setCopied] = useState(false);
@@ -156,9 +164,11 @@ function FormCodePanel({ formJSON, data, mutate, height = "600px", disabled = fa
156
164
  children: [/* @__PURE__ */ jsxs("div", {
157
165
  className: "bg-(--theme-muted-panel-bg) text-(--theme-muted-panel-color) flex shrink-0 items-center gap-3 border-b border-(--theme-border) px-3 py-2",
158
166
  children: [hasHiddenFields && /* @__PURE__ */ jsxs("label", {
167
+ htmlFor: `${uid}-show-hidden`,
159
168
  title: t.showHiddenFieldsHint,
160
169
  className: "flex cursor-pointer select-none items-center gap-2 text-sm text-(--theme-app)",
161
170
  children: [/* @__PURE__ */ jsx(SwitchToggleSmall, {
171
+ id: `${uid}-show-hidden`,
162
172
  value: showHidden,
163
173
  "aria-label": t.showHiddenFields,
164
174
  onClick: () => setShowHidden((prev) => !prev)
@@ -199,6 +209,7 @@ function FormCodePanel({ formJSON, data, mutate, height = "600px", disabled = fa
199
209
  onChange: (e) => {
200
210
  try {
201
211
  const parsed = JSON.parse(e);
212
+ if (!isObject(parsed)) return;
202
213
  mutate(showHiddenRef.current ? parsed : populateFiltered(filteredFieldsRef.current, parsed));
203
214
  } catch {
204
215
  console.log("JSON.parse error in slurm editor");
@@ -240,7 +251,7 @@ function createEditorThemes() {
240
251
  };
241
252
  const light = {
242
253
  disabledBg: themeVar("--theme-input-disabled-bg", "#f3f4f6"),
243
- disabledFg: themeVar("--theme-muted-text-color", "#6c757d"),
254
+ disabledFg: themeVar("--theme-muted-text-color", "#5f6b76"),
244
255
  border: themeVar("--theme-border", "#d1d5db"),
245
256
  selection: themeVar("--theme-element", "#2f81f7"),
246
257
  selectionFg: themeVar("--theme-element-text", "#ffffff")
@@ -0,0 +1,256 @@
1
+ import { t as RequiredMark } from "./RequiredMark-DfLdYbYZ.js";
2
+ import cx from "classnames";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { createContext, useContext, useId, useMemo } from "react";
5
+ //#region src/form/fieldContext.tsx
6
+ var FieldContext = createContext({ required: false });
7
+ var FieldProvider = FieldContext.Provider;
8
+ /** True when the surrounding FieldWrapper is showing the required asterisk. */
9
+ function useFieldRequired() {
10
+ return useContext(FieldContext).required;
11
+ }
12
+ /** Ties the single focusable control of a field to its FieldWrapper label. */
13
+ function useFieldControlProps() {
14
+ const { id, descriptionId } = useContext(FieldContext);
15
+ return {
16
+ id,
17
+ "aria-describedby": descriptionId
18
+ };
19
+ }
20
+ /** For controls and groups that can only be named through aria-labelledby. */
21
+ function useFieldLabelledByProps() {
22
+ const { labelId, descriptionId } = useContext(FieldContext);
23
+ return {
24
+ "aria-labelledby": labelId,
25
+ "aria-describedby": descriptionId
26
+ };
27
+ }
28
+ //#endregion
29
+ //#region src/form/FieldWrapper.tsx
30
+ function FieldWrapper({ label, tooltipComponent, description, labelPosition = "left", spaceCompact = false, optional = false, children }) {
31
+ const uid = useId();
32
+ const id = `${uid}control`;
33
+ const labelId = `${uid}label`;
34
+ const descriptionId = description ? `${uid}description` : void 0;
35
+ const field = useMemo(() => ({
36
+ required: !optional,
37
+ id,
38
+ labelId,
39
+ descriptionId
40
+ }), [
41
+ optional,
42
+ id,
43
+ labelId,
44
+ descriptionId
45
+ ]);
46
+ return /* @__PURE__ */ jsxs("div", {
47
+ className: cx("flex w-full", labelPosition === "left" ? "items-start" : "items-left flex-col"),
48
+ children: [/* @__PURE__ */ jsxs("label", {
49
+ htmlFor: id,
50
+ id: labelId,
51
+ className: cx("relative", labelPosition === "left" ? "form-label" : "form-label-top", spaceCompact && "form-label-compact"),
52
+ children: [
53
+ label,
54
+ !optional && /* @__PURE__ */ jsx(RequiredMark, {}),
55
+ tooltipComponent
56
+ ]
57
+ }), /* @__PURE__ */ jsxs("div", {
58
+ className: "w-full",
59
+ children: [/* @__PURE__ */ jsx(FieldProvider, {
60
+ value: field,
61
+ children: typeof children === "function" ? children({
62
+ id,
63
+ labelId,
64
+ describedBy: descriptionId
65
+ }) : children
66
+ }), description && /* @__PURE__ */ jsx("div", {
67
+ id: descriptionId,
68
+ className: "text-xs theme-muted-text mt-1 leading-relaxed",
69
+ children: description
70
+ })]
71
+ })]
72
+ });
73
+ }
74
+ //#endregion
75
+ //#region src/form/utils/secondaryField.ts
76
+ function findSecondaryOption(options, value) {
77
+ for (const candidate of options) {
78
+ if (!candidate || typeof candidate !== "object") continue;
79
+ const option = candidate;
80
+ if (option.value === value) return option;
81
+ if (Array.isArray(option.options)) {
82
+ const nestedOption = findSecondaryOption(option.options, value);
83
+ if (nestedOption) return nestedOption;
84
+ }
85
+ }
86
+ }
87
+ function applySecondaryField(secondaryField, secondaryValue, setFieldValue) {
88
+ if (secondaryField === void 0 || secondaryValue === void 0) return;
89
+ const fields = Array.isArray(secondaryField) ? secondaryField : [secondaryField];
90
+ const values = Array.isArray(secondaryValue) ? secondaryValue : [secondaryValue];
91
+ fields.forEach((field, index) => {
92
+ setFieldValue(field, values[index]);
93
+ });
94
+ }
95
+ //#endregion
96
+ //#region src/form/lib.ts
97
+ var EMPTY_ENTRY = {};
98
+ function asRecord(value) {
99
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
100
+ }
101
+ /**
102
+ * Drops a schema flag the expression parser could not resolve. workflow.schema.json types
103
+ * `optional`/`hidden`/`ignore` as ["string", "boolean"], so a flag whose expression depends
104
+ * on something unavailable at form-render time (a list `[index]` outside a row, `needs.`,
105
+ * `env.`, a secret `var.`) arrives as the raw `${{…}}` string. That string is truthy, which
106
+ * inverts each flag's documented `false` default. Returning undefined lets the caller apply
107
+ * that default instead of reading an expression it never evaluated as "yes".
108
+ */
109
+ function resolvedFlag(value) {
110
+ return typeof value === "string" && value.includes("${{") ? void 0 : value;
111
+ }
112
+ function enforceOneMustBeTrue(items, options, promoteIndex) {
113
+ if (items.length === 0) return items;
114
+ const clampedIndex = Math.max(0, Math.min(promoteIndex, items.length - 1));
115
+ const oneMustBeTrueFields = Object.keys(options).filter((key) => typeof options[key] === "object" && options[key] !== null && "one_must_be_true" in options[key] && options[key].one_must_be_true);
116
+ for (const fieldKey of oneMustBeTrueFields) {
117
+ const fieldSchema = options[fieldKey];
118
+ if (typeof fieldSchema === "object" && fieldSchema && "options" in fieldSchema) {
119
+ const opts = fieldSchema.options;
120
+ const trueOption = typeof opts === "object" && opts && !Array.isArray(opts) && "onOption" in opts ? opts.onOption || true : true;
121
+ if (!items.some((item) => item[fieldKey] === trueOption)) items[clampedIndex][fieldKey] = trueOption;
122
+ }
123
+ }
124
+ return items;
125
+ }
126
+ function flattenGroups(schema) {
127
+ if (!schema || typeof schema !== "object") return {};
128
+ const fields = schema;
129
+ let flattenedFields = {};
130
+ Object.keys(fields).forEach((key) => {
131
+ const entry = fields[key];
132
+ if (entry?.type === "group") {
133
+ const flattenedGroup = flattenGroups(entry.options);
134
+ flattenedFields = {
135
+ ...flattenedFields,
136
+ ...flattenedGroup
137
+ };
138
+ } else if (entry?.type === "object") {
139
+ const flattenedGroup = flattenGroups(entry.options);
140
+ flattenedFields = {
141
+ ...flattenedFields,
142
+ [key]: flattenedGroup
143
+ };
144
+ } else flattenedFields[key] = entry;
145
+ });
146
+ return flattenedFields;
147
+ }
148
+ /**Not a pure function. Edits 'obj' passed into function */
149
+ function impureSetValueFromPath(obj, path, value) {
150
+ const pathArray = path.split(".");
151
+ if (pathArray.length === 1) {
152
+ obj[pathArray[0]] = value;
153
+ return;
154
+ }
155
+ let current = obj;
156
+ for (let i = 0; i < pathArray.length - 1; i++) {
157
+ const segment = pathArray[i];
158
+ const arrayMatch = segment.match(/^(.+)\[(\d+)\]$/);
159
+ if (arrayMatch) {
160
+ const key = arrayMatch[1];
161
+ const index = Number(arrayMatch[2]);
162
+ if (!current[key]) current[key] = [];
163
+ const list = current[key];
164
+ if (!list[index]) list[index] = {};
165
+ current = list[index];
166
+ } else {
167
+ if (!current[segment]) current[segment] = {};
168
+ current = current[segment];
169
+ }
170
+ }
171
+ current[pathArray[pathArray.length - 1]] = value;
172
+ }
173
+ function initializeValues(options, srcData = {}, ignoreDefaultStrings = false) {
174
+ if (!options || typeof options !== "object") return;
175
+ const schema = options;
176
+ const source = srcData;
177
+ const data = structuredClone(source);
178
+ return Object.keys(schema).reduce((acc, field) => {
179
+ if (field.startsWith("$")) return acc;
180
+ const fieldSchema = schema[field] ?? EMPTY_ENTRY;
181
+ if (fieldSchema.type === "group") {
182
+ const values = initializeValues(fieldSchema.options, data);
183
+ if (!values) return acc;
184
+ Object.keys(values).forEach((key) => {
185
+ acc[key] = data[key] ?? values[key];
186
+ });
187
+ return acc;
188
+ }
189
+ if (fieldSchema.type === "step") {
190
+ if (schema["$meta"]?.wizard?.flatten !== false) {
191
+ const values = initializeValues(fieldSchema.options, data);
192
+ if (!values) return acc;
193
+ Object.keys(values).forEach((key) => {
194
+ acc[key] = data[key] ?? values[key];
195
+ });
196
+ } else acc[field] = initializeValues(fieldSchema.options, asRecord(data[field]));
197
+ return acc;
198
+ }
199
+ if (fieldSchema.type === "object") {
200
+ acc[field] = initializeValues(fieldSchema.options, asRecord(data[field]));
201
+ return acc;
202
+ }
203
+ if (fieldSchema.type === "list") {
204
+ const listData = data[field];
205
+ if (fieldSchema.options === "string") {
206
+ acc[field] = Array.isArray(listData) ? [...listData] : [];
207
+ return acc;
208
+ }
209
+ const arr = [];
210
+ if (Array.isArray(listData)) for (let i = 0; i < listData.length; i++) arr.push(initializeValues(fieldSchema.options, asRecord(listData[i])));
211
+ acc[field] = arr;
212
+ return acc;
213
+ }
214
+ if (data[field] !== void 0) {
215
+ acc[field] = data[field];
216
+ if ((fieldSchema.type === "dropdown" || fieldSchema.type === "storage") && fieldSchema.secondaryField && fieldSchema.options && Array.isArray(fieldSchema.options)) {
217
+ const option = findSecondaryOption(fieldSchema.options, data[field]);
218
+ applySecondaryField(fieldSchema.secondaryField, option?.secondaryValue, (secondaryField, secondaryValue) => {
219
+ if (!(fieldSchema.type === "storage" && Object.prototype.hasOwnProperty.call(options, secondaryField) && source[secondaryField] !== void 0)) impureSetValueFromPath(acc, secondaryField, secondaryValue);
220
+ });
221
+ }
222
+ return acc;
223
+ }
224
+ if (fieldSchema.type === "label") return acc;
225
+ if (fieldSchema.optional && fieldSchema.default === void 0) return acc;
226
+ acc[field] = "";
227
+ if (fieldSchema.autoselect) acc[field] = void 0;
228
+ if (fieldSchema.type === "boolean") {
229
+ const boolOptions = asRecord(fieldSchema.options);
230
+ if (boolOptions["offOption"] && boolOptions["onOption"]) acc[field] = boolOptions["offOption"];
231
+ else acc[field] = false;
232
+ }
233
+ if (fieldSchema.type === "dropdown") {
234
+ if (fieldSchema.options && Array.isArray(fieldSchema.options) && fieldSchema.options.length > 0) acc[field] = fieldSchema.options[0].value;
235
+ if (fieldSchema.type === "dropdown" && fieldSchema.secondaryField && fieldSchema.options && Array.isArray(fieldSchema.options)) {
236
+ const option = fieldSchema.options.find((option) => option.value === data[field]);
237
+ const secondaryField = Array.isArray(fieldSchema.secondaryField) ? fieldSchema.secondaryField : [fieldSchema.secondaryField];
238
+ const secondaryValue = Array.isArray(option?.secondaryValue) ? option?.secondaryValue : [option?.secondaryValue];
239
+ secondaryField.forEach((field, index) => {
240
+ impureSetValueFromPath(data, field, secondaryValue[index]);
241
+ impureSetValueFromPath(acc, field, secondaryValue[index]);
242
+ });
243
+ }
244
+ }
245
+ if (fieldSchema.type === "multi-dropdown" || fieldSchema.type === "checkbox-group") acc[field] = [];
246
+ if (fieldSchema.default !== void 0) {
247
+ const dflt = fieldSchema.default;
248
+ const isPwResource = typeof dflt === "string" && dflt.startsWith("pw://");
249
+ acc[field] = fieldSchema.type === "string" || ignoreDefaultStrings && !isPwResource && (fieldSchema.type === "textarea" || fieldSchema.type === "compute-clusters" || fieldSchema.type === "compute-resources") ? "" : dflt;
250
+ }
251
+ if (fieldSchema.prefillDefault && fieldSchema.default !== void 0) acc[field] = fieldSchema.default;
252
+ return acc;
253
+ }, data);
254
+ }
255
+ //#endregion
256
+ export { resolvedFlag as a, FieldWrapper as c, useFieldLabelledByProps as d, useFieldRequired as f, initializeValues as i, FieldProvider as l, flattenGroups as n, applySecondaryField as o, impureSetValueFromPath as r, findSecondaryOption as s, enforceOneMustBeTrue as t, useFieldControlProps as u };
@@ -1,8 +1,8 @@
1
1
  import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, DisplayIcon, ErrorIcon, FilterIcon, MenuIcon, SearchIcon, XIcon } from "../icons.js";
2
- import { c as useStrings, n as useLink, r as useNavigation } from "./Provider-CfFV-vc5.js";
3
- import { n as TOOLTIP_ID, r as TooltipInfo } from "./Tooltip-WgbiC_nd.js";
4
- import { c as PageHeader, f as rowContextMenuProps, g as useRowMenuActive, l as MoreButton } from "./PageHeader-dyoL44ue.js";
5
- import { n as Table } from "./Table-ByziK93x.js";
2
+ import { c as useStrings, n as useLink, r as useNavigation } from "./Provider-CbFVFB7O.js";
3
+ import { n as TOOLTIP_ID, r as TooltipInfo } from "./Tooltip-n9w7Tkt1.js";
4
+ import { _ as useRowMenuActive, c as PageHeader, p as rowContextMenuProps, u as MoreButton } from "./PageHeader-CxxbidPp.js";
5
+ import { n as Table } from "./Table-CutP1bRt.js";
6
6
  import cx from "classnames";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Children, Fragment as Fragment$1, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -91,6 +91,7 @@ function OverflowMenu({ items }) {
91
91
  as: "div",
92
92
  className: "relative inline-block text-left",
93
93
  children: [/* @__PURE__ */ jsx(MenuButton, {
94
+ "aria-label": t.moreActions,
94
95
  className: "inline-flex items-center justify-center h-9 w-9 rounded-md border theme-border link hover:theme-hover transition-colors",
95
96
  "data-tooltip-id": TOOLTIP_ID,
96
97
  "data-tooltip-content": t.moreActions,
@@ -264,9 +265,13 @@ function ListError({ error, noun = "data" }) {
264
265
  });
265
266
  }
266
267
  function ListSkeleton({ rows = 6 }) {
267
- return /* @__PURE__ */ jsx("div", {
268
+ const { loading } = useStrings();
269
+ return /* @__PURE__ */ jsxs("div", {
270
+ role: "status",
271
+ "aria-label": loading,
268
272
  className: "divide-y border-b border-(--theme-border) divide-(--theme-border)",
269
- children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", {
273
+ children: [Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", {
274
+ "aria-hidden": "true",
270
275
  className: "flex items-center gap-3 px-4 py-2.5 animate-pulse",
271
276
  children: [
272
277
  /* @__PURE__ */ jsx("div", { className: "h-5 w-5 rounded bg-(--theme-muted-panel-bg)" }),
@@ -276,7 +281,10 @@ function ListSkeleton({ rows = 6 }) {
276
281
  }),
277
282
  /* @__PURE__ */ jsx("div", { className: "ml-auto h-3 w-20 rounded bg-(--theme-muted-panel-bg)" })
278
283
  ]
279
- }, i))
284
+ }, i)), /* @__PURE__ */ jsx("span", {
285
+ className: "sr-only",
286
+ children: loading
287
+ })]
280
288
  });
281
289
  }
282
290
  /** Prevents the name column from being crushed under table-auto layout during horizontal overflow. */
@@ -428,12 +436,13 @@ function ListRow({ href, items, goTo, openMenu, pinnedActions, selected, selecti
428
436
  "data-tooltip-id": "global-tooltip",
429
437
  "data-tooltip-content": action.label,
430
438
  "data-tooltip-place": "bottom",
431
- disabled: action.disabled,
439
+ "aria-disabled": action.disabled,
432
440
  onClick: (e) => {
433
441
  e.stopPropagation();
442
+ if (action.disabled) return;
434
443
  action.onSelect();
435
444
  },
436
- className: cx("rounded p-1 cursor-pointer text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) disabled:cursor-not-allowed disabled:opacity-20 [&>svg]:h-4 [&>svg]:w-4", alwaysShowActions || menuActive ? "opacity-100" : "opacity-0 group-hover:opacity-100 focus-visible:opacity-100"),
445
+ className: cx("rounded p-1 cursor-pointer text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) aria-disabled:cursor-not-allowed aria-disabled:opacity-20 aria-disabled:hover:bg-transparent aria-disabled:hover:text-(--theme-muted-text-color) [&>svg]:h-4 [&>svg]:w-4", alwaysShowActions || menuActive ? "opacity-100" : "opacity-0 group-hover:opacity-100 focus-visible:opacity-100"),
437
446
  children: action.icon
438
447
  }, action.key))
439
448
  }), /* @__PURE__ */ jsx(MoreButton, {
@@ -695,8 +704,8 @@ function AnchoredPopover({ icon, label, active, shortcut, panelClassName, childr
695
704
  const open = useCallback(() => {
696
705
  const r = btnRef.current?.getBoundingClientRect();
697
706
  if (r) setPos({
698
- right: Math.max(8, window.innerWidth - r.right),
699
- top: r.bottom + 4
707
+ right: Math.max(8, Math.round(window.innerWidth - r.right)),
708
+ top: Math.round(r.bottom) + 4
700
709
  });
701
710
  }, []);
702
711
  useEffect(() => {
@@ -740,6 +749,7 @@ function AnchoredPopover({ icon, label, active, shortcut, panelClassName, childr
740
749
  className: cx(listControlButtonClasses, active && "border-(--theme-link) text-(--theme-app)"),
741
750
  children: [icon, active && /* @__PURE__ */ jsx("span", { className: "absolute right-0 top-0 h-1.5 w-1.5 rounded-full bg-(--theme-link)" })]
742
751
  }), pos && createPortal(/* @__PURE__ */ jsx("div", {
752
+ role: "none",
743
753
  className: "fixed inset-0 z-50",
744
754
  onMouseDown: close,
745
755
  onContextMenu: (e) => {
@@ -994,10 +1004,17 @@ function FilterFacetRow({ view, facet, open, onEnter, onLeave }) {
994
1004
  const selected = view.filters[facet.key] ?? [];
995
1005
  return /* @__PURE__ */ jsxs("div", {
996
1006
  className: "relative",
1007
+ role: "none",
997
1008
  onMouseEnter: onEnter,
998
1009
  onMouseLeave: onLeave,
999
- children: [/* @__PURE__ */ jsxs("div", {
1000
- className: "flex cursor-default items-center justify-between gap-3 px-3 py-1.5 text-[13px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
1010
+ onFocus: onEnter,
1011
+ onBlur: onLeave,
1012
+ children: [/* @__PURE__ */ jsxs("button", {
1013
+ type: "button",
1014
+ "aria-expanded": open,
1015
+ "aria-haspopup": "menu",
1016
+ onClick: onEnter,
1017
+ className: "flex w-full cursor-default items-center justify-between gap-3 px-3 py-1.5 text-[13px] text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
1001
1018
  children: [/* @__PURE__ */ jsx("span", { children: facet.label }), /* @__PURE__ */ jsxs("span", {
1002
1019
  className: "flex items-center gap-1.5",
1003
1020
  children: [selected.length > 0 && /* @__PURE__ */ jsx("span", {
@@ -1242,7 +1259,7 @@ function useHoverCard() {
1242
1259
  keepOpen
1243
1260
  };
1244
1261
  }
1245
- function HoverCardSurface({ x, y, widthClassName, relayoutKey, onKeepOpen, onLeave, children }) {
1262
+ function HoverCardSurface({ x, y, widthClassName, padded = true, relayoutKey, onKeepOpen, onLeave, children }) {
1246
1263
  const ref = useRef(null);
1247
1264
  const [pos, setPos] = useState(null);
1248
1265
  useLayoutEffect(() => {
@@ -1260,9 +1277,12 @@ function HoverCardSurface({ x, y, widthClassName, relayoutKey, onKeepOpen, onLea
1260
1277
  ]);
1261
1278
  return createPortal(/* @__PURE__ */ jsx("div", {
1262
1279
  ref,
1280
+ role: "none",
1263
1281
  onMouseEnter: onKeepOpen,
1264
1282
  onMouseLeave: onLeave,
1265
- className: cx("fixed z-[9999] rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) p-3 shadow-xl backdrop-blur-xl", widthClassName),
1283
+ onFocus: onKeepOpen,
1284
+ onBlur: onLeave,
1285
+ className: cx("fixed z-[9999] overflow-hidden rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) shadow-xl backdrop-blur-xl", padded && "p-3", widthClassName),
1266
1286
  style: {
1267
1287
  left: pos?.left ?? x,
1268
1288
  top: pos?.top ?? y + 8,
@@ -16,12 +16,21 @@ interface BreadcrumbContextType {
16
16
  setTitleActionSlot: (el: HTMLElement | null) => void;
17
17
  rightActionSlot: HTMLElement | null;
18
18
  setRightActionSlot: (el: HTMLElement | null) => void;
19
+ /** Pathname whose breadcrumbs a page set itself, so layouts leave it alone. */
20
+ claimedPath: React.MutableRefObject<string | null>;
19
21
  }
20
22
  export declare const useBreadcrumb: () => BreadcrumbContextType;
21
23
  export declare function BreadcrumbProvider({ children, }: {
22
24
  children: React.ReactNode;
23
25
  }): React.JSX.Element;
24
26
  export declare function useSetBreadcrumbs(getBreadcrumbs: (() => IBreadcrumbItem[]) | IBreadcrumbItem[]): void;
27
+ /**
28
+ * For layout routes that derive breadcrumbs from the URL: applies them only
29
+ * while no page under the layout has set its own for the current path.
30
+ * Without this, a layout re-running its effect (for example when the
31
+ * organization's display name loads) overwrote the page's breadcrumbs.
32
+ */
33
+ export declare function useSetDefaultBreadcrumbs(getBreadcrumbs: (() => IBreadcrumbItem[]) | IBreadcrumbItem[]): void;
25
34
  /** For pages that render their own header instead of the breadcrumb strip. */
26
35
  export declare function useClearBreadcrumbs(): void;
27
36
  export declare function useHideBreadcrumbs(hidden: boolean): void;
@@ -4,16 +4,16 @@ interface IControlledCollapsiblePanelProps {
4
4
  children: React.ReactNode;
5
5
  open: boolean | undefined;
6
6
  setOpen?: React.Dispatch<React.SetStateAction<boolean>>;
7
- onClick?: () => void;
8
- wrapperElement?: React.ElementType;
7
+ onClick?: (() => void) | undefined;
8
+ wrapperElement?: React.ElementType | undefined;
9
9
  shown?: boolean;
10
10
  setShown?: (shown: boolean) => void;
11
11
  customizedButton?: React.ReactNode;
12
- className?: string;
12
+ className?: string | undefined;
13
13
  }
14
14
  export declare function ControlledCollapsiblePanel({ title, children, open, setOpen, onClick, wrapperElement, customizedButton, className, }: IControlledCollapsiblePanelProps): React.JSX.Element;
15
15
  export declare function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState, className, customizedButton, }: Omit<IControlledCollapsiblePanelProps, 'setOpen' | 'open'> & {
16
- initialState?: boolean;
16
+ initialState?: boolean | undefined;
17
17
  }): React.JSX.Element;
18
18
  type MaybeCollapsibleProps = {
19
19
  className?: string;