@parallelworks/ui 0.14.0 → 0.15.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.
@@ -1,5 +1,5 @@
1
1
  import { t as useCssIsDark } from "./useCssIsDark-DHr05dG2.js";
2
- import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./editor-BZ9Buwi4.js";
2
+ import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./editor-Ko3zX22Y.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { useEffect, useRef } from "react";
5
5
  import * as monaco from "monaco-editor";
@@ -1,9 +1,9 @@
1
1
  import { AlertIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ClipboardIcon, CopyIcon, FavoriteIcon, FavoriteOutlineIcon, HiddenFalseIcon, HiddenTrueIcon, InfoIcon, TooltipIcon } from "../icons.js";
2
2
  import { c as useStrings, i as useNotify } from "./Provider-CbFVFB7O.js";
3
3
  import { n as TOOLTIP_ID } from "./Tooltip-n9w7Tkt1.js";
4
- import { i as filterOption, l as toggleSetMember } from "./StatusBadge-RRUI78iS.js";
4
+ import { i as filterOption, l as toggleSetMember } from "./StatusBadge-zvEtK16a.js";
5
5
  import { n as modalPanelClasses, t as BareModal } from "./BareModal-BqroLbZ5.js";
6
- import { w as AddMenu } from "./PageHeader-CxxbidPp.js";
6
+ import { w as AddMenu } from "./PageHeader-CoZWF0pJ.js";
7
7
  import cx from "classnames";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Suspense, lazy, useCallback, useEffect, useRef, useState } from "react";
@@ -514,7 +514,7 @@ function pushToggleRowOnce(nodes, toggledGroups, group, count, collapse) {
514
514
  function buildOptionNodes(options, query, onChange, collapse) {
515
515
  const trimmed = query.trim();
516
516
  const q = trimmed.toLowerCase().replace(/-/g, "");
517
- const filtered = q ? options.filter((o) => filterOption({
517
+ const filtered = q ? options.filter((o) => o.pinned || filterOption({
518
518
  label: o.label,
519
519
  value: o.value,
520
520
  secondaryValue: o.group
@@ -536,12 +536,28 @@ function buildOptionNodes(options, query, onChange, collapse) {
536
536
  pushToggleRowOnce(nodes, toggledGroups, group, collapsibleCounts.get(group) ?? 0, collapse);
537
537
  if (!collapse.expanded.has(group)) continue;
538
538
  }
539
- nodes.push(/* @__PURE__ */ jsx(MenuItem, { children: /* @__PURE__ */ jsxs("button", {
540
- type: "button",
541
- onClick: () => onChange(option.value),
542
- className: "flex w-full items-center gap-2 px-3 py-1.5 text-[13px] text-(--theme-app) data-[focus]:bg-(--theme-muted-panel-bg) cursor-pointer",
543
- children: [option.icon, option.label]
544
- }) }, option.value));
539
+ nodes.push(/* @__PURE__ */ jsx(MenuItem, {
540
+ disabled: option.disabled ?? false,
541
+ children: /* @__PURE__ */ jsxs("button", {
542
+ type: "button",
543
+ disabled: option.disabled,
544
+ "aria-disabled": option.disabled,
545
+ "aria-label": option.description ? option.label : void 0,
546
+ "aria-description": option.description,
547
+ onClick: () => onChange(option.value),
548
+ className: cx("flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] data-[focus]:bg-(--theme-muted-panel-bg)", option.disabled ? "cursor-not-allowed text-(--theme-muted-text-color)" : "cursor-pointer text-(--theme-app)", option.pinned && "mt-1 border-t border-(--theme-border) pt-2 font-medium"),
549
+ children: [option.icon, option.description ? /* @__PURE__ */ jsxs("span", {
550
+ className: "flex min-w-0 flex-col",
551
+ children: [/* @__PURE__ */ jsx("span", {
552
+ className: "truncate",
553
+ children: option.label
554
+ }), /* @__PURE__ */ jsx("span", {
555
+ className: "truncate text-[11px] font-normal text-(--theme-muted-text-color)",
556
+ children: option.description
557
+ })]
558
+ }) : option.label]
559
+ })
560
+ }, option.value));
545
561
  }
546
562
  return nodes;
547
563
  }
@@ -1,6 +1,6 @@
1
1
  import { l as useWorkflowJsonResolver } from "./Provider-CbFVFB7O.js";
2
2
  import { t as useCssIsDark } from "./useCssIsDark-DHr05dG2.js";
3
- import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./editor-BZ9Buwi4.js";
3
+ import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./editor-Ko3zX22Y.js";
4
4
  import cx from "classnames";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  import { useEffect, useRef } from "react";
@@ -128,7 +128,8 @@ function toRowMenuItem(action) {
128
128
  icon: action.icon,
129
129
  onSelect: action.onSelect ?? (() => {}),
130
130
  disabled: action.disabled,
131
- destructive: action.destructive
131
+ destructive: action.destructive,
132
+ tooltip: action.tooltip
132
133
  };
133
134
  }
134
135
  /** Bridges a toolbar action (icon component, `onClick`/`href`) onto the shared
@@ -143,7 +144,8 @@ function toolbarActionToRowAction(action) {
143
144
  onSelect: action.onClick,
144
145
  to: action.href,
145
146
  disabled: action.disabled,
146
- destructive: action.key === "delete" || action.key === "destroy"
147
+ destructive: action.key === "delete" || action.key === "destroy",
148
+ tooltip: action.disabledTooltip
147
149
  };
148
150
  }
149
151
  /** Assembles a row's context menu in the shared order: leading link(s) → middle
@@ -344,8 +346,14 @@ function MenuRow({ item, onClose, side }) {
344
346
  });
345
347
  return /* @__PURE__ */ jsxs("button", {
346
348
  type: "button",
347
- disabled: item.disabled,
349
+ disabled: item.disabled && !item.tooltip,
350
+ "aria-disabled": item.disabled,
351
+ ...item.disabled && item.tooltip ? {
352
+ "data-tooltip-id": TOOLTIP_ID,
353
+ "data-tooltip-content": item.tooltip
354
+ } : {},
348
355
  onClick: () => {
356
+ if (item.disabled) return;
349
357
  if (item.copy) {
350
358
  const { text, label } = item.copy;
351
359
  navigator.clipboard?.writeText(text).then(() => notify.success(t.copied(label)), () => notify.error(t.couldntCopy(label)));
@@ -440,7 +448,7 @@ var headerAddButtonClasses = "inline-flex h-[26px] shrink-0 items-center gap-1.5
440
448
  /** With menu, the plus opens a chooser; otherwise navigates to or calls the
441
449
  * single action. `word` is the visible verb; `label` stays the full action for
442
450
  * the tooltip and screen readers. */
443
- function HeaderAddButton({ to, onClick, label, word = "add", menu }) {
451
+ function HeaderAddButton({ to, onClick, label, word = "add", menu, disabled = false, disabledHint }) {
444
452
  const { common } = useStrings();
445
453
  const Link = useLink();
446
454
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AddIcon, { className: "w-3 h-3" }), /* @__PURE__ */ jsx("span", { children: word === "create" ? common.create : common.add })] });
@@ -457,12 +465,17 @@ function HeaderAddButton({ to, onClick, label, word = "add", menu }) {
457
465
  className: headerAddButtonClasses,
458
466
  children: content
459
467
  });
468
+ const hint = disabled ? disabledHint : void 0;
460
469
  return /* @__PURE__ */ jsx("button", {
461
470
  type: "button",
462
- onClick,
463
- "aria-label": label,
464
- title: label,
465
- className: headerAddButtonClasses,
471
+ onClick: disabled ? void 0 : onClick,
472
+ "aria-label": hint ? `${label}. ${hint}` : label,
473
+ "aria-disabled": disabled || void 0,
474
+ ...hint ? {
475
+ "data-tooltip-id": TOOLTIP_ID,
476
+ "data-tooltip-content": hint
477
+ } : { title: label },
478
+ className: cx(headerAddButtonClasses, "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:hover:bg-transparent aria-disabled:hover:text-(--theme-muted-text-color)"),
466
479
  children: content
467
480
  });
468
481
  }
@@ -67,7 +67,7 @@ function collapsedCategoryRows(category, filtered, collapsibleCount, expanded, c
67
67
  }
68
68
  function flattenDisplayOptions({ options, query, expandedCategories, collapsedLabel, selectedValue, allowCustomValue, customValueLabel }) {
69
69
  const flatOptions = options.flatMap((option) => {
70
- if (!isCategory(option)) return filterOption(option, query) ? [option] : [];
70
+ if (!isCategory(option)) return option.pinned || filterOption(option, query) ? [option] : [];
71
71
  const filtered = option.options.filter((opt) => filterOption(opt, query));
72
72
  if (filtered.length === 0) return [];
73
73
  const category = option.category || "";
@@ -1,7 +1,7 @@
1
1
  import { AngleDownIcon, AngleRightIcon, CaretDownIcon } from "../icons.js";
2
2
  import { c as useStrings } from "./Provider-CbFVFB7O.js";
3
3
  import { a as truncationTooltipProps, n as TOOLTIP_ID } from "./Tooltip-n9w7Tkt1.js";
4
- import { a as flattenDisplayOptions, c as isOption, i as filterOption, l as toggleSetMember, o as flattenOptions, r as DropdownCaret, s as isCategory } from "./StatusBadge-RRUI78iS.js";
4
+ import { a as flattenDisplayOptions, c as isOption, i as filterOption, l as toggleSetMember, o as flattenOptions, r as DropdownCaret, s as isCategory } from "./StatusBadge-zvEtK16a.js";
5
5
  import cx from "classnames";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -263,6 +263,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
263
263
  const optionDescription = typeof option !== "string" ? option.description : void 0;
264
264
  const isCategoryRow = typeof option !== "string" && Boolean(option.category);
265
265
  const toggleTarget = typeof option !== "string" ? option.toggle : void 0;
266
+ const isPinned = typeof option !== "string" && Boolean(option.pinned);
266
267
  if (toggleTarget !== void 0) return /* @__PURE__ */ jsx(ToggleOption, {
267
268
  option,
268
269
  category: toggleTarget,
@@ -272,7 +273,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
272
273
  return /* @__PURE__ */ jsx(ComboboxOption, {
273
274
  "data-testid": "combobox-option",
274
275
  value: option,
275
- className: cx("w-full group relative flex", optionDescription ? "min-h-10 py-2" : "h-10", isCategoryRow ? "text-[14px] font-bold pl-1 items-center" : "data-disabled:text-(--theme-muted-text-color) data-disabled:bg-(--theme-muted-panel-bg) data-disabled:cursor-not-allowed cursor-pointer items-center pr-9 pl-3 leading-5 data-focus:bg-(--theme-muted-panel-bg) data-focus:text-(--theme-app) data-focus:outline-hidden", "data-selected:border-l-2 data-selected:border-(--theme-link) data-selected:bg-(--theme-muted-panel-bg) data-selected:text-(--theme-app)"),
276
+ className: cx("w-full group relative flex", optionDescription ? "min-h-10 py-2" : "h-10", isCategoryRow ? "text-[14px] font-bold pl-1 items-center" : "data-disabled:text-(--theme-muted-text-color) data-disabled:bg-(--theme-muted-panel-bg) data-disabled:cursor-not-allowed cursor-pointer items-center pr-9 pl-3 leading-5 data-focus:bg-(--theme-muted-panel-bg) data-focus:text-(--theme-app) data-focus:outline-hidden", "data-selected:border-l-2 data-selected:border-(--theme-link) data-selected:bg-(--theme-muted-panel-bg) data-selected:text-(--theme-app)", isPinned && "border-t border-(--theme-border) font-medium"),
276
277
  children: /* @__PURE__ */ jsx(OptionContent, {
277
278
  label: optionLabel,
278
279
  description: optionDescription,
@@ -619,7 +620,7 @@ function MultiSelectDropdown({ options, value, onChange, id, "aria-labelledby":
619
620
  //#endregion
620
621
  //#region src/components/CollapsiblePanel.tsx
621
622
  var noop = () => {};
622
- function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onClick = noop, wrapperElement, customizedButton = null, className }) {
623
+ function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onClick = noop, wrapperElement, customizedButton = null, titleAction, className }) {
623
624
  const toggleOpen = useCallback(() => {
624
625
  if (onClick) onClick();
625
626
  setOpen?.((open) => !open);
@@ -633,18 +634,25 @@ function ControlledCollapsiblePanel({ title = "", children, open, setOpen, onCli
633
634
  }) })] }) : /* @__PURE__ */ jsxs("div", {
634
635
  className: cx("border border-solid rounded-2xl border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-panel) mb-1 w-full", className),
635
636
  children: [/* @__PURE__ */ jsxs("div", {
636
- className: cx(!open && "text-(--theme-muted-text-color)", "w-full flex items-center justify-between transform ease-in transition text-sm py-3 px-2 cursor-pointer"),
637
- children: [/* @__PURE__ */ jsxs("button", {
638
- type: "button",
639
- "aria-expanded": !!open,
640
- "aria-controls": contentId,
641
- className: "flex items-center w-full text-left cursor-pointer",
642
- onClick: toggleOpen,
643
- children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx(open && "transform rotate-90", "px-1 ml-1 text-[1.25rem] transition ease-in") }), /* @__PURE__ */ jsx("span", {
644
- className: "px-2 font-bold select-none",
645
- children: title?.toString()
646
- })]
647
- }), /* @__PURE__ */ jsx("div", { children: customizedButton })]
637
+ className: cx(!open && "text-(--theme-muted-text-color)", "w-full flex items-center transform ease-in transition text-sm py-3 px-2 cursor-pointer"),
638
+ children: [
639
+ /* @__PURE__ */ jsxs("button", {
640
+ type: "button",
641
+ "aria-expanded": !!open,
642
+ "aria-controls": contentId,
643
+ className: cx("flex items-center text-left cursor-pointer", !titleAction && "w-full"),
644
+ onClick: toggleOpen,
645
+ children: [/* @__PURE__ */ jsx(AngleRightIcon, { className: cx(open && "transform rotate-90", "px-1 ml-1 text-[1.25rem] transition ease-in") }), /* @__PURE__ */ jsx("span", {
646
+ className: "px-2 font-bold select-none",
647
+ children: title?.toString()
648
+ })]
649
+ }),
650
+ titleAction,
651
+ /* @__PURE__ */ jsx("div", {
652
+ className: "ml-auto",
653
+ children: customizedButton
654
+ })
655
+ ]
648
656
  }), /* @__PURE__ */ jsx(AnimateHeight, {
649
657
  id: contentId,
650
658
  duration: 300,
@@ -2,14 +2,14 @@ import { CheckIcon, CopyIcon, EyeIcon, EyeOffIcon } from "../icons.js";
2
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-DyJvmbzj.js";
5
+ import { c as FieldWrapper, n as flattenGroups, r as impureSetValueFromPath } from "./lib-CUTRDpa6.js";
6
6
  import cx from "classnames";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
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-H56KyEdb.js"));
12
+ var MonacoEditor = lazy(() => import("./Monaco-BXDH2jVj.js"));
13
13
  function Editor({ ...props }) {
14
14
  return /* @__PURE__ */ jsx(Suspense, {
15
15
  fallback: /* @__PURE__ */ jsx(Loader, { text: "Loading editor..." }),
@@ -115,11 +115,9 @@ function enforceOneMustBeTrue(items, options, promoteIndex) {
115
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
116
  for (const fieldKey of oneMustBeTrueFields) {
117
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
- }
118
+ const opts = typeof fieldSchema === "object" && fieldSchema && "options" in fieldSchema ? fieldSchema.options : void 0;
119
+ const trueOption = typeof opts === "object" && opts && !Array.isArray(opts) && "onOption" in opts ? opts.onOption || true : true;
120
+ if (!items.some((item) => item[fieldKey] === trueOption)) items[clampedIndex][fieldKey] = trueOption;
123
121
  }
124
122
  return items;
125
123
  }
@@ -1,7 +1,7 @@
1
1
  import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, DisplayIcon, ErrorIcon, FilterIcon, MenuIcon, SearchIcon, XIcon } from "../icons.js";
2
2
  import { c as useStrings, n as useLink, r as useNavigation } from "./Provider-CbFVFB7O.js";
3
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";
4
+ import { _ as useRowMenuActive, c as PageHeader, p as rowContextMenuProps, u as MoreButton } from "./PageHeader-CoZWF0pJ.js";
5
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";
@@ -203,13 +203,16 @@ var listTableFixedProps = {
203
203
  ...listTableProps,
204
204
  className: listTableProps.className.replace("table-auto", "table-fixed")
205
205
  };
206
- function NameCell({ icon, name, subtitle, href }) {
206
+ function NameCell({ icon, name, subtitle, href, badge }) {
207
207
  const Link = useLink();
208
208
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [icon, /* @__PURE__ */ jsxs("div", {
209
209
  className: "min-w-0",
210
- children: [/* @__PURE__ */ jsx("div", {
211
- className: NAME_TEXT,
212
- children: name
210
+ children: [/* @__PURE__ */ jsxs("div", {
211
+ className: "flex min-w-0 items-center gap-1.5",
212
+ children: [/* @__PURE__ */ jsx("div", {
213
+ className: cx(NAME_TEXT, href && "group-hover/namecell:text-(--theme-link)"),
214
+ children: name
215
+ }), badge]
213
216
  }), subtitle && /* @__PURE__ */ jsx("div", {
214
217
  className: SUBTITLE_TEXT,
215
218
  children: subtitle
@@ -219,7 +222,7 @@ function NameCell({ icon, name, subtitle, href }) {
219
222
  className: "py-2.5",
220
223
  children: href ? /* @__PURE__ */ jsx(Link, {
221
224
  to: href,
222
- className: "flex items-center gap-2.5 min-w-0 cursor-pointer",
225
+ className: "group/namecell flex items-center gap-2.5 min-w-0 cursor-pointer",
223
226
  children: inner
224
227
  }) : /* @__PURE__ */ jsx("div", {
225
228
  className: "flex items-center gap-2.5 min-w-0",
@@ -390,7 +393,7 @@ function ListPage({ section = "main", shellPadded, embedded = false, breadcrumbs
390
393
  actions,
391
394
  toolbar
392
395
  }), /* @__PURE__ */ jsx("div", {
393
- className: "@container min-h-0 flex-1 overflow-auto",
396
+ className: "relative @container min-h-0 flex-1 overflow-auto",
394
397
  children: /* @__PURE__ */ jsx(ListRowActionsProvider, {
395
398
  view,
396
399
  children
@@ -434,7 +437,7 @@ function ListRow({ href, items, goTo, openMenu, pinnedActions, selected, selecti
434
437
  type: "button",
435
438
  "aria-label": action.label,
436
439
  "data-tooltip-id": "global-tooltip",
437
- "data-tooltip-content": action.label,
440
+ "data-tooltip-content": action.disabled && action.tooltip ? action.tooltip : action.label,
438
441
  "data-tooltip-place": "bottom",
439
442
  "aria-disabled": action.disabled,
440
443
  onClick: (e) => {
@@ -9,9 +9,11 @@ interface IControlledCollapsiblePanelProps {
9
9
  shown?: boolean;
10
10
  setShown?: (shown: boolean) => void;
11
11
  customizedButton?: React.ReactNode;
12
+ /** Rendered right after the title, outside the toggle button. */
13
+ titleAction?: React.ReactNode;
12
14
  className?: string | undefined;
13
15
  }
14
- export declare function ControlledCollapsiblePanel({ title, children, open, setOpen, onClick, wrapperElement, customizedButton, className, }: IControlledCollapsiblePanelProps): React.JSX.Element;
16
+ export declare function ControlledCollapsiblePanel({ title, children, open, setOpen, onClick, wrapperElement, customizedButton, titleAction, className, }: IControlledCollapsiblePanelProps): React.JSX.Element;
15
17
  export declare function UncontrolledCollapsiblePanel({ title, children, onClick, wrapperElement, initialState, className, customizedButton, }: Omit<IControlledCollapsiblePanelProps, 'setOpen' | 'open'> & {
16
18
  initialState?: boolean | undefined;
17
19
  }): React.JSX.Element;
@@ -75,6 +75,12 @@ export interface CreateModalSelectOption {
75
75
  group?: string;
76
76
  /** Hidden behind the group's toggle row until the group is expanded. */
77
77
  collapsible?: boolean;
78
+ /** Always listed regardless of the typed filter (e.g. a trailing "Create new…" action). */
79
+ pinned?: boolean;
80
+ /** Muted second line under the label (e.g. the allocation a group bills to). */
81
+ description?: string;
82
+ /** Listed but not selectable (e.g. a group whose allocation is exhausted). */
83
+ disabled?: boolean;
78
84
  }
79
85
  /** Callers gating collapse on list size must use this same threshold so
80
86
  * collapsed rows always come with a filter to find them. */
@@ -14,12 +14,15 @@ export declare const headerActionButtonClasses = "flex items-center justify-cent
14
14
  /** With menu, the plus opens a chooser; otherwise navigates to or calls the
15
15
  * single action. `word` is the visible verb; `label` stays the full action for
16
16
  * the tooltip and screen readers. */
17
- export declare function HeaderAddButton({ to, onClick, label, word, menu, }: {
17
+ export declare function HeaderAddButton({ to, onClick, label, word, menu, disabled, disabledHint, }: {
18
18
  to?: string;
19
19
  onClick?: () => void;
20
20
  label: string;
21
21
  word?: 'add' | 'create';
22
22
  menu?: HeaderAddMenuItem[];
23
+ /** Button mode only. */
24
+ disabled?: boolean;
25
+ disabledHint?: string;
23
26
  }): import("react").JSX.Element;
24
27
  export declare function HeaderActionMenu({ items, label, }: {
25
28
  items: HeaderAddMenuItem[];
@@ -8,6 +8,8 @@ export interface IOptions<T = unknown> {
8
8
  description?: string | undefined;
9
9
  /** Hidden behind the category's toggle row until the category is expanded. */
10
10
  collapsible?: boolean | undefined;
11
+ /** Always listed regardless of the typed query (e.g. a trailing "Create new…" action). */
12
+ pinned?: boolean | undefined;
11
13
  }
12
14
  export interface ICategory<T = unknown> {
13
15
  category?: string | undefined;
@@ -1,2 +1,2 @@
1
- import { a as EditorField, i as FormCodePanel, n as defineEditorThemes, o as Editor, r as getThemeName, t as setupMonacoWorkers } from "../chunks/editor-BZ9Buwi4.js";
1
+ import { a as EditorField, i as FormCodePanel, n as defineEditorThemes, o as Editor, r as getThemeName, t as setupMonacoWorkers } from "../chunks/editor-Ko3zX22Y.js";
2
2
  export { Editor, EditorField, FormCodePanel, defineEditorThemes, getThemeName, setupMonacoWorkers };
@@ -1,5 +1,5 @@
1
1
  import type { IFileExplorerClient, IFileExplorerProvider } from './lib/fileExplorer.js';
2
- import type { TStorage } from './lib/types.js';
2
+ import type { TreeNode, TStorage } from './lib/types.js';
3
3
  interface IFileExplorerProps {
4
4
  storages: TStorage[];
5
5
  /** Resolves the storage's injected provider pair; the host owns caching. */
@@ -8,6 +8,10 @@ interface IFileExplorerProps {
8
8
  client: IFileExplorerClient | null;
9
9
  }>;
10
10
  onRefresh?: () => void;
11
+ /** Handles activating a file instead of previewing it. Lets a host embed the
12
+ * explorer as a file picker (the notebook editor opens .ipynb this way);
13
+ * return false to fall through to the built-in preview. */
14
+ onOpenFile?: ((node: TreeNode, storage: TStorage | undefined) => boolean) | undefined;
11
15
  showUserHierarchy?: boolean;
12
16
  groups?: {
13
17
  name: string;
@@ -16,6 +20,19 @@ interface IFileExplorerProps {
16
20
  groupsLoading?: boolean;
17
21
  selectedPath: string;
18
22
  onPathChange: (path: string) => void;
23
+ /** Renders only the tree, for a host that embeds it as a navigator and owns
24
+ * the pane beside it (the notebooks page puts its editor there). The host
25
+ * also owns the width, so the tree fills its container instead of carrying
26
+ * its own resize handle. */
27
+ treeOnly?: boolean;
28
+ /** Changes when the host has written into the selected folder outside the
29
+ * explorer's own upload flow, so the folder is re-listed and shows the file. */
30
+ refreshKey?: unknown;
31
+ /** Seeds the expanded folders on mount, so a host can restore the tree's open
32
+ * state across reloads. Read once; the explorer owns the set from then on. */
33
+ initialExpandedPaths?: readonly string[];
34
+ /** Fires whenever the expanded folders change, for the host to persist. */
35
+ onExpandedPathsChange?: (paths: string[]) => void;
19
36
  }
20
- export default function FileExplorer({ storages, onRefresh, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, getProviderAndClient, }: IFileExplorerProps): import("react").JSX.Element;
37
+ export default function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, getProviderAndClient, treeOnly, initialExpandedPaths, onExpandedPathsChange, }: IFileExplorerProps): import("react").JSX.Element;
21
38
  export {};
@@ -1,12 +1,12 @@
1
1
  import { AccessIcon, AlertIcon, AngleDownIcon, AngleRightIcon, AngleUpIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardIcon, CloseIcon, CloudUploadIcon, CollapseIcon, CustomizeIcon, DiskIcon, DocumentIcon, DownloadFileIcon, ExpandIcon, ExpandVerticalIcon, EyeIcon, FileIcon, FileTextIcon, FolderIcon, FolderOpenIcon, ImageIcon, LinkIcon, LockIcon, MinimizeIcon, MusicIcon, PdfIcon, RefreshIcon, ShareIcon, SpreadsheetIcon, SuccessIcon, TrashIcon, UploadIcon, UserIcon, VideoIcon, ZipIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
2
- import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-DoA2WsXf.js";
2
+ import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-B7smkHkv.js";
3
3
  import { a as useProvisionCorsRules, c as useStrings, i as useNotify, s as useSlots } from "../chunks/Provider-CbFVFB7O.js";
4
4
  import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-n9w7Tkt1.js";
5
- import { t as StatusBadge } from "../chunks/StatusBadge-RRUI78iS.js";
5
+ import { t as StatusBadge } from "../chunks/StatusBadge-zvEtK16a.js";
6
6
  import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
7
- import { _ as useRowMenuActive, d as assembleRowMenu, f as copySubmenu, g as useRowMenu, u as MoreButton } from "../chunks/PageHeader-CxxbidPp.js";
7
+ import { _ as useRowMenuActive, d as assembleRowMenu, f as copySubmenu, g as useRowMenu, u as MoreButton } from "../chunks/PageHeader-CoZWF0pJ.js";
8
8
  import { n as Table } from "../chunks/Table-CutP1bRt.js";
9
- import { C as ListSkeleton, E as RowSelectCheckbox, O as listTableFixedProps, k as listTableProps, x as ListActionsHeader } from "../chunks/list-CBZpAYP7.js";
9
+ import { C as ListSkeleton, E as RowSelectCheckbox, O as listTableFixedProps, k as listTableProps, x as ListActionsHeader } from "../chunks/list-CduoUsMW.js";
10
10
  import cx from "classnames";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { Suspense, createContext, lazy, memo, use, useCallback, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
@@ -2537,7 +2537,7 @@ function useObjectText(url, { enabled, maxBytes, maxLines, knownSize, cacheKey,
2537
2537
  }
2538
2538
  //#endregion
2539
2539
  //#region src/file-explorer/FilePreview/Viewer.tsx
2540
- var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-67A_SKgE.js"));
2540
+ var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-DuMZMA_k.js"));
2541
2541
  var PREVIEW_MARKDOWN_CONTROLS = {
2542
2542
  table: { fullscreen: false },
2543
2543
  mermaid: { fullscreen: false }
@@ -4313,7 +4313,37 @@ function getAncestorDirPaths(path) {
4313
4313
  for (let i = 1; i <= dirSegmentCount; i++) ancestors.push(segments.slice(0, i).join("/") + "/");
4314
4314
  return ancestors;
4315
4315
  }
4316
- function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient }) {
4316
+ /** The tree's width is the host's when the tree is embedded on its own, and this
4317
+ * component's when it is showing the details pane beside it. */
4318
+ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
4319
+ if (treeOnly) return /* @__PURE__ */ jsx("div", {
4320
+ className: "h-full w-full min-w-0",
4321
+ children
4322
+ });
4323
+ return /* @__PURE__ */ jsx(Resizable, {
4324
+ size: {
4325
+ width: sidebarWidth,
4326
+ height: "100%"
4327
+ },
4328
+ minWidth: 200,
4329
+ maxWidth: 800,
4330
+ enable: {
4331
+ top: false,
4332
+ right: true,
4333
+ bottom: false,
4334
+ left: false,
4335
+ topRight: false,
4336
+ bottomRight: false,
4337
+ bottomLeft: false,
4338
+ topLeft: false
4339
+ },
4340
+ onResizeStop: (_e, _direction, _ref, d) => {
4341
+ onWidthChange((prev) => prev + d.width);
4342
+ },
4343
+ children
4344
+ });
4345
+ }
4346
+ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange }) {
4317
4347
  const t = useStrings().fileExplorer;
4318
4348
  const notify = useNotify();
4319
4349
  const slots = useSlots();
@@ -4348,8 +4378,19 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
4348
4378
  return `${selectedPath}/`;
4349
4379
  }, [selectedPath, treeMap]);
4350
4380
  const setSelectedPath = onPathChange;
4351
- const [expandedPaths, setExpandedPaths] = useState(() => new Set([""]));
4381
+ const [expandedPaths, setExpandedPaths] = useState(() => new Set(["", ...initialExpandedPaths ?? []]));
4382
+ const onExpandedPathsChangeRef = useRef(onExpandedPathsChange);
4383
+ onExpandedPathsChangeRef.current = onExpandedPathsChange;
4384
+ useEffect(() => {
4385
+ onExpandedPathsChangeRef.current?.([...expandedPaths]);
4386
+ }, [expandedPaths]);
4352
4387
  const selectedNode = useMemo(() => normalizedSelectedPath ? treeMap[normalizedSelectedPath] ?? null : null, [normalizedSelectedPath, treeMap]);
4388
+ const seenRefreshKey = useRef(refreshKey);
4389
+ useEffect(() => {
4390
+ if (refreshKey === seenRefreshKey.current) return;
4391
+ seenRefreshKey.current = refreshKey;
4392
+ if (selectedNode?.type === "directory" && selectedNode.storageId) fetchNodeChildren(selectedNode, { replace: true });
4393
+ }, [refreshKey, selectedNode]);
4353
4394
  const [checkedItems, setCheckedItems] = useState(/* @__PURE__ */ new Set());
4354
4395
  const [checkedItemsPath, setCheckedItemsPath] = useState(selectedPath);
4355
4396
  if (checkedItemsPath !== selectedPath) {
@@ -4714,6 +4755,7 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
4714
4755
  const previewSiblings = useMemo(() => previewNode ? getNodeChildren(parentToChildrenMap, getParentPath(previewNode.path)).filter((n) => n.type === "file") : [], [previewNode, parentToChildrenMap]);
4715
4756
  const openPreview = (node) => {
4716
4757
  if (node.type !== "file") return;
4758
+ if (onOpenFile?.(node, storages.find((s) => s.id === node.storageId))) return;
4717
4759
  setPreviewNode(node);
4718
4760
  setPreviewOpen(true);
4719
4761
  };
@@ -5073,12 +5115,13 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
5073
5115
  }
5074
5116
  };
5075
5117
  const handleRefresh = async (opts) => {
5076
- if (selectedNode?.storageId) {
5077
- if (selectedNode.type === "directory") return fetchNodeChildren(selectedNode, {
5078
- replace: true,
5079
- silent: opts?.silent
5080
- });
5081
- } else if (onRefresh) onRefresh();
5118
+ const dirs = [...expandedPaths].map((path) => treeMap[path]).filter((node) => !!node && node.type === "directory" && !!node.storageId);
5119
+ if (selectedNode?.type === "directory" && selectedNode.storageId && !dirs.some((dir) => dir.path === selectedNode.path)) dirs.push(selectedNode);
5120
+ if (dirs.length > 0) return { corsError: (await Promise.all(dirs.map((dir) => fetchNodeChildren(dir, {
5121
+ replace: true,
5122
+ silent: opts?.silent
5123
+ })))).some((result) => result.corsError) };
5124
+ if (onRefresh) onRefresh();
5082
5125
  return { corsError: false };
5083
5126
  };
5084
5127
  return /* @__PURE__ */ jsxs("div", {
@@ -5086,26 +5129,10 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
5086
5129
  children: [
5087
5130
  /* @__PURE__ */ jsxs("div", {
5088
5131
  className: "w-full h-full flex",
5089
- children: [/* @__PURE__ */ jsx(Resizable, {
5090
- size: {
5091
- width: sidebarWidth,
5092
- height: "100%"
5093
- },
5094
- minWidth: 200,
5095
- maxWidth: 800,
5096
- enable: {
5097
- top: false,
5098
- right: true,
5099
- bottom: false,
5100
- left: false,
5101
- topRight: false,
5102
- bottomRight: false,
5103
- bottomLeft: false,
5104
- topLeft: false
5105
- },
5106
- onResizeStop: (e, direction, ref, d) => {
5107
- setSidebarWidth((prev) => prev + d.width);
5108
- },
5132
+ children: [/* @__PURE__ */ jsx(TreeFrame, {
5133
+ treeOnly,
5134
+ sidebarWidth,
5135
+ onWidthChange: setSidebarWidth,
5109
5136
  children: /* @__PURE__ */ jsxs("div", {
5110
5137
  className: "h-full flex flex-col border-r theme-border bg-(--theme-panel-bg)/60",
5111
5138
  children: [/* @__PURE__ */ jsx("div", {
@@ -5139,14 +5166,17 @@ function FileExplorer({ storages, onRefresh, showUserHierarchy = false, groups =
5139
5166
  selectedPath: normalizedSelectedPath,
5140
5167
  loadingPaths,
5141
5168
  onToggleExpand: handleToggleExpand,
5142
- onSelect: handleSelectTreeItem,
5169
+ onSelect: (path, node) => {
5170
+ handleSelectTreeItem(path, node);
5171
+ if (treeOnly && node.type === "file") openPreview(node);
5172
+ },
5143
5173
  onDropToFolder: onDropFiles,
5144
5174
  onContextMenu: openNodeContextMenu,
5145
5175
  onPreviewFile: openPreview,
5146
5176
  paging: treePaging
5147
5177
  })]
5148
5178
  })
5149
- }), /* @__PURE__ */ jsx("div", {
5179
+ }), !treeOnly && /* @__PURE__ */ jsx("div", {
5150
5180
  className: "h-full flex flex-col flex-1 min-w-0",
5151
5181
  children: !selectedNode && normalizedSelectedPath ? hydrationStuck ? /* @__PURE__ */ jsx(NotFoundPreview, {
5152
5182
  path: normalizedSelectedPath,
@@ -13,19 +13,24 @@ export declare function resolveMetaOverrides(options: {
13
13
  labelPosition: "left" | "top" | undefined;
14
14
  spaceCompact: boolean;
15
15
  };
16
+ /** Cheap signature of every list's length; gates the expensive collect walk. */
17
+ export declare function listLengthsSignature(options: Record<string, unknown>, values?: Record<string, unknown>, prefix?: string): string;
16
18
  /** Collects every field with a `default` for the form-level sync to track. */
17
19
  export declare function collectFieldsWithDefaults(options: Record<string, unknown>, values?: Record<string, unknown>, prefix?: string): Array<{
18
20
  name: string;
19
21
  defaultValue: unknown;
22
+ hasSecondary?: boolean;
20
23
  }>;
21
24
  export declare function defaultReferencesField(defaultValue: unknown, fieldPath: string): boolean;
22
25
  export declare function findSelfReferencingFieldNames(schema: Record<string, unknown>, values?: Record<string, unknown>): string[];
23
- /**
24
- * Re-syncs each field's value when its resolved default changes between
25
- * renders, unless `meta.touched` (this session) or a synth-touched marker
26
- * (cross-session, set on first-seen when saved ≠ default) is set. Must be
27
- * remounted via `key` when the run/config context changes.
28
- */
26
+ /** Resolves schema defaults into form values as dependencies change. Store-agnostic so the legacy
27
+ * form (Formik) and the cluster wizard (its own store) share one implementation. */
28
+ export declare function useDynamicDefaultsSync({ options, values, setValue, isTouched, }: {
29
+ options: Record<string, unknown>;
30
+ values: Record<string, unknown>;
31
+ setValue: (name: string, value: unknown) => void;
32
+ isTouched: (name: string) => boolean;
33
+ }): void;
29
34
  export declare function DynamicDefaultsSync({ options, }: {
30
35
  options: Record<string, unknown>;
31
36
  }): null;
@@ -104,8 +109,7 @@ export interface DynamicFormProps {
104
109
  onSubmit?: ((values: Record<string, unknown>) => void) | undefined;
105
110
  submitLabel?: string;
106
111
  className?: string;
107
- /** Prevents deletion of hidden+ignored field values (workflow forms filter at submit time).
108
- * Also gates the form-level DynamicDefaultsSync — it only runs for workflow forms. */
112
+ /** Prevents deletion of hidden+ignored field values (workflow forms filter at submit time). */
109
113
  workflowForm?: boolean;
110
114
  /** Identifier for the load context (e.g. rerun slug, configuration name). When this
111
115
  * changes, DynamicDefaultsSync remounts so it re-evaluates its prev-default tracking
@@ -1,4 +1,4 @@
1
- export { DynamicForm, DynamicDefaultsSync, FieldsFromOptions, GroupHeader, MultiSelectionDropdown, StatusBlock, collectFieldsWithDefaults, findSelfReferencingFieldNames, getInstanceTypesForForm, resolveMetaOverrides, type DynamicFormProps, type TSetFormDirty, } from './Form.js';
1
+ export { DynamicForm, DynamicDefaultsSync, FieldsFromOptions, GroupHeader, MultiSelectionDropdown, StatusBlock, collectFieldsWithDefaults, useDynamicDefaultsSync, findSelfReferencingFieldNames, getInstanceTypesForForm, resolveMetaOverrides, type DynamicFormProps, type TSetFormDirty, } from './Form.js';
2
2
  export { type FieldComponent, FieldRegistryContext, Registry, } from './fieldRegistry.js';
3
3
  export { FieldWrapper, type FieldLabelBinding } from './FieldWrapper.js';
4
4
  export { type FieldContextValue, FieldProvider, useFieldControlProps, useFieldLabelledByProps, useFieldRequired, } from './fieldContext.js';