@parallelworks/ui 0.37.0 → 0.39.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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/ai/components/ChatInput.js +8 -23
  3. package/dist/ai/components/ChatSidebar.js +7 -4
  4. package/dist/ai/components/chatDraft.d.ts +7 -0
  5. package/dist/ai/components/chatDraft.js +66 -0
  6. package/dist/ai/index.d.ts +1 -1
  7. package/dist/ai/index.js +2 -2
  8. package/dist/ai/ui/sidebar.d.ts +6 -0
  9. package/dist/ai/ui/sidebar.js +15 -1
  10. package/dist/editing.d.ts +2 -0
  11. package/dist/editor/Monaco.js +1 -1
  12. package/dist/editor/index.js +1 -1
  13. package/dist/editor/lintContext.js +15 -3
  14. package/dist/form/FieldWrapper.js +1 -0
  15. package/dist/form/Form.js +64 -23
  16. package/dist/form/Wizard/WizardContainer.js +66 -6
  17. package/dist/form/Wizard/WizardStepContent.js +23 -10
  18. package/dist/form/Wizard/wizardPages.d.ts +12 -0
  19. package/dist/form/Wizard/wizardPages.js +5 -0
  20. package/dist/form/formEditing.d.ts +25 -0
  21. package/dist/form/formEditing.js +20 -0
  22. package/dist/graph/DependencyGraph.d.ts +8 -1
  23. package/dist/graph/DependencyGraph.js +433 -204
  24. package/dist/graph/GraphEditor.d.ts +27 -0
  25. package/dist/graph/GraphEditor.js +1543 -0
  26. package/dist/graph/GraphEditorDialogs.d.ts +87 -0
  27. package/dist/graph/GraphEditorDialogs.js +1808 -0
  28. package/dist/graph/InputDialog.js +1568 -54
  29. package/dist/graph/InputsEditor.d.ts +12 -0
  30. package/dist/graph/InputsEditor.js +1594 -0
  31. package/dist/graph/JobSummary.d.ts +3 -0
  32. package/dist/graph/JobSummary.js +34 -21
  33. package/dist/graph/MatrixGroup.d.ts +2 -1
  34. package/dist/graph/MatrixGroup.js +10 -3
  35. package/dist/graph/ShortcutsButton.js +56 -0
  36. package/dist/graph/SuggestionInput.js +29 -0
  37. package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
  38. package/dist/graph/WorkflowSettingsDialog.js +670 -0
  39. package/dist/graph/editorApi.js +8 -0
  40. package/dist/graph/editorChrome.js +75 -0
  41. package/dist/graph/editorFields.js +535 -0
  42. package/dist/graph/editorPrimitives.js +95 -4
  43. package/dist/graph/editorRows.d.ts +58 -0
  44. package/dist/graph/editorRows.js +460 -0
  45. package/dist/graph/editorStrings.d.ts +1 -0
  46. package/dist/graph/editorStrings.js +10 -1
  47. package/dist/graph/expressionRefs.js +39 -0
  48. package/dist/graph/fieldProblems.js +111 -0
  49. package/dist/graph/graphEditorState.d.ts +191 -0
  50. package/dist/graph/graphEditorState.js +74 -0
  51. package/dist/graph/index.d.ts +4 -0
  52. package/dist/graph/index.js +6 -2
  53. package/dist/graph/inputLayout.js +337 -0
  54. package/dist/graph/inputPages.d.ts +33 -0
  55. package/dist/graph/inputPages.js +186 -0
  56. package/dist/graph/inputRefs.js +388 -0
  57. package/dist/graph/inputRows.d.ts +43 -0
  58. package/dist/graph/inputRows.js +65 -0
  59. package/dist/graph/refusalText.js +13 -0
  60. package/dist/graph/settingsViews.js +62 -0
  61. package/dist/graph/stepWith.d.ts +38 -0
  62. package/dist/graph/stepWith.js +536 -0
  63. package/dist/styles.css +1 -1
  64. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ so an application ships only what it imports:
10
10
  | `./icons` | the icon barrel |
11
11
  | `./list` | list pages, tables, row menus and list controls |
12
12
  | `./form` | `DynamicForm` and its field registry |
13
- | `./graph` | job dependency graph and tree view |
13
+ | `./graph` | job dependency graph and tree view, and the visual workflow editor: the graph, the run form as an inputs editor, and their dialogs |
14
14
  | `./editor` | Monaco editor (`monaco-editor` is an optional peer); YAML validation, completion and hover run in a bundled worker |
15
15
  | `./logviewer` | log viewer with ANSI color and search |
16
16
  | `./file-explorer` | object storage explorer over a host-supplied provider |
@@ -6,6 +6,7 @@ import { useChat } from "../core/ChatProvider.js";
6
6
  import AttachmentUpload from "./AttachmentUpload.js";
7
7
  import useDragDrop from "../core/useDragDrop.js";
8
8
  import { ComposerContext, ComposerControls, ComposerSettings } from "./ComposerChrome.js";
9
+ import { clearDraft, readDraft, storeDraft, sweepDrafts } from "./chatDraft.js";
9
10
  import DragOverlay from "./DragOverlay.js";
10
11
  import { PasteFileCard } from "./PastedText.js";
11
12
  import { SlashMenu, useSlashMenu } from "./SlashMenu.js";
@@ -14,29 +15,6 @@ import cx from "classnames";
14
15
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
15
16
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
17
  //#region src/ai/components/ChatInput.tsx
17
- var DRAFT_STORAGE_PREFIX = "aiChatDraft:";
18
- var DRAFT_MAX_LENGTH = 1e4;
19
- function draftKey(conversationId) {
20
- return `${DRAFT_STORAGE_PREFIX}${conversationId ?? "new"}`;
21
- }
22
- function readDraft(conversationId) {
23
- try {
24
- return window.localStorage.getItem(draftKey(conversationId)) ?? "";
25
- } catch {
26
- return "";
27
- }
28
- }
29
- function storeDraft(conversationId, text) {
30
- try {
31
- if (!text || text.length > DRAFT_MAX_LENGTH) window.localStorage.removeItem(draftKey(conversationId));
32
- else window.localStorage.setItem(draftKey(conversationId), text);
33
- } catch {}
34
- }
35
- function clearDraft(conversationId) {
36
- try {
37
- window.localStorage.removeItem(draftKey(conversationId));
38
- } catch {}
39
- }
40
18
  /** An optional box that is still empty should not wear the filled look of
41
19
  * one waiting to send something. */
42
20
  function sendability({ hasText, canFlushQueue, allowEmpty, disabled, modelReady, pendingCount }) {
@@ -88,8 +66,15 @@ function streamControls(chat, turn) {
88
66
  queuedCount: chat.queuedMessages.length
89
67
  };
90
68
  }
69
+ var draftsSwept = false;
91
70
  var ChatInput = forwardRef(function ChatInput({ onSend, disabled = false, placeholder, conversationId, onEditLastMessage, allowEmpty = false, attachments: attachmentsWanted = true, requireModel = true, inputLabel, sendLabel, sendTestId, slash, notices, activity, context, reserveContext = false, settingsLeft, settingsRight = /* @__PURE__ */ jsx(ComposerControls, {}), hint, turn, pastes }, ref) {
92
71
  const [input, setInput] = useState(() => readDraft(conversationId));
72
+ useEffect(() => {
73
+ if (!draftsSwept) {
74
+ draftsSwept = true;
75
+ sweepDrafts();
76
+ }
77
+ }, []);
93
78
  const { cards, handleTextPaste, removeCard, takeCards, blocking: pastesBlocking } = usePasteCards(pastes, input, (value) => {
94
79
  setInput(value);
95
80
  storeDraft(conversationId, value);
@@ -2,18 +2,19 @@ import { FaRegFileImage } from "../../icons/fa/FaRegFileImage.js";
2
2
  import { FaSearch } from "../../icons/fa/FaSearch.js";
3
3
  import { FaUsers } from "../../icons/fa/FaUsers.js";
4
4
  import { TbEdit } from "../../icons/tb/TbEdit.js";
5
+ import { useRelativeTime } from "../../components/relativeTime.js";
5
6
  import { useChatConfig } from "../core/config.js";
6
7
  import { useChat } from "../core/ChatProvider.js";
7
8
  import { ManageProvidersLink } from "./ComposerChrome.js";
8
9
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "../core/events.js";
9
- import { SidebarPlaceholder, SidebarRow } from "../ui/sidebar.js";
10
+ import { SidebarPlaceholder, SidebarRow, SidebarRowTitle } from "../ui/sidebar.js";
10
11
  import { ConversationSidebar } from "../ui/ConversationSidebar.js";
11
12
  import { groupConversationsByDate, useConversationActions } from "./conversationActions.js";
12
13
  import cx from "classnames";
13
14
  import { useEffect, useMemo, useRef, useState } from "react";
14
15
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
15
16
  //#region src/ai/components/ChatSidebar.tsx
16
- function ConversationRow({ conv, isCurrent, menuItems, openMenu, onOpen }) {
17
+ function ConversationRow({ conv, isCurrent, updated, menuItems, openMenu, onOpen }) {
17
18
  const { LinkComponent, strings } = useChatConfig();
18
19
  const t = strings.sidebar;
19
20
  return /* @__PURE__ */ jsx(SidebarRow, {
@@ -30,8 +31,8 @@ function ConversationRow({ conv, isCurrent, menuItems, openMenu, onOpen }) {
30
31
  children: [conv.isOwner === false && /* @__PURE__ */ jsx(FaUsers, {
31
32
  className: "h-3 w-3 shrink-0 theme-muted-text",
32
33
  title: t.sharedWithYou
33
- }), /* @__PURE__ */ jsx("span", {
34
- className: "min-w-0 flex-1 truncate text-sm theme-text",
34
+ }), /* @__PURE__ */ jsx(SidebarRowTitle, {
35
+ time: updated,
35
36
  children: conv.title || t.untitled
36
37
  })]
37
38
  })
@@ -43,6 +44,7 @@ function ChatSidebar({ actions = true, top }) {
43
44
  const { adapter, navigation, activeConversationId, conversations, sidebar, sidebarPresentation, drawerOpen, setSidebar, setDrawerOpen, toggleSidebar, isLoading, loadConversations, clearCurrentConversation } = useChat();
44
45
  const attachmentsAvailable = !!adapter.attachments;
45
46
  const { menuItems, openMenu, overlays } = useConversationActions();
47
+ const { compact } = useRelativeTime();
46
48
  const drawer = sidebarPresentation === "drawer";
47
49
  const sidebarCollapsed = !drawer && sidebar !== "expanded";
48
50
  const closeDrawer = () => setDrawerOpen(false);
@@ -106,6 +108,7 @@ function ChatSidebar({ actions = true, top }) {
106
108
  renderRow: (conv) => /* @__PURE__ */ jsx(ConversationRow, {
107
109
  conv,
108
110
  isCurrent: activeConversationId === conv.id,
111
+ updated: compact(conv.updatedAt ?? conv.createdAt),
109
112
  menuItems: menuItems(conv),
110
113
  openMenu,
111
114
  onOpen: closeDrawer
@@ -0,0 +1,7 @@
1
+ export declare const DRAFT_MAX_LENGTH = 10000;
2
+ export declare const DRAFT_TTL_MS: number;
3
+ export declare function readDraft(conversationId?: string): string;
4
+ export declare function storeDraft(conversationId: string | undefined, text: string): void;
5
+ export declare function clearDraft(conversationId?: string): void;
6
+ /** Removes expired drafts, and starts the clock on ones stored without an age. */
7
+ export declare function sweepDrafts(now?: number): void;
@@ -0,0 +1,66 @@
1
+ //#region src/ai/components/chatDraft.ts
2
+ var DRAFT_STORAGE_PREFIX = "aiChatDraft:";
3
+ var DRAFT_MAX_LENGTH = 1e4;
4
+ var DRAFT_TTL_MS = 2592e6;
5
+ function draftKey(conversationId) {
6
+ return `${DRAFT_STORAGE_PREFIX}${conversationId ?? "new"}`;
7
+ }
8
+ /** Older versions stored the bare text, which has no age. */
9
+ function parseDraft(raw) {
10
+ try {
11
+ const parsed = JSON.parse(raw);
12
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.text === "string" && typeof parsed.savedAt === "number") return parsed;
13
+ } catch {}
14
+ return { text: raw };
15
+ }
16
+ var expired = (savedAt, now) => now - savedAt > DRAFT_TTL_MS;
17
+ function readDraft(conversationId) {
18
+ try {
19
+ const raw = window.localStorage.getItem(draftKey(conversationId));
20
+ if (raw === null) return "";
21
+ const draft = parseDraft(raw);
22
+ return draft.savedAt !== void 0 && expired(draft.savedAt, Date.now()) ? "" : draft.text;
23
+ } catch {
24
+ return "";
25
+ }
26
+ }
27
+ function storeDraft(conversationId, text) {
28
+ try {
29
+ if (!text || text.length > 1e4) window.localStorage.removeItem(draftKey(conversationId));
30
+ else {
31
+ const draft = {
32
+ text,
33
+ savedAt: Date.now()
34
+ };
35
+ window.localStorage.setItem(draftKey(conversationId), JSON.stringify(draft));
36
+ }
37
+ } catch {}
38
+ }
39
+ function clearDraft(conversationId) {
40
+ try {
41
+ window.localStorage.removeItem(draftKey(conversationId));
42
+ } catch {}
43
+ }
44
+ /** Removes expired drafts, and starts the clock on ones stored without an age. */
45
+ function sweepDrafts(now = Date.now()) {
46
+ try {
47
+ const storage = window.localStorage;
48
+ const keys = [];
49
+ for (let i = 0; i < storage.length; i++) {
50
+ const key = storage.key(i);
51
+ if (key?.startsWith(DRAFT_STORAGE_PREFIX)) keys.push(key);
52
+ }
53
+ for (const key of keys) {
54
+ const raw = storage.getItem(key);
55
+ if (raw === null) continue;
56
+ const draft = parseDraft(raw);
57
+ if (draft.savedAt === void 0) storage.setItem(key, JSON.stringify({
58
+ text: draft.text,
59
+ savedAt: now
60
+ }));
61
+ else if (expired(draft.savedAt, now)) storage.removeItem(key);
62
+ }
63
+ } catch {}
64
+ }
65
+ //#endregion
66
+ export { DRAFT_MAX_LENGTH, DRAFT_TTL_MS, clearDraft, readDraft, storeDraft, sweepDrafts };
@@ -43,5 +43,5 @@ export * from './types.js';
43
43
  export { ConversationSidebar, type ConversationSidebarGroup, useResizableSidebarWidth, } from './ui/ConversationSidebar.js';
44
44
  export { type DropdownOption, default as Dropdown } from './ui/Dropdown.js';
45
45
  export { NoticeBar, NoticeCard, type NoticeTone, OutputCard } from './ui/Notice.js';
46
- export { RenameDialog, type RowDialogStrings, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
46
+ export { RenameDialog, type RowDialogStrings, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs, } from './ui/sidebar.js';
47
47
  export { formatFileSize, isMac, isOrgModel } from './utils.js';
package/dist/ai/index.js CHANGED
@@ -37,7 +37,7 @@ import ChatEmptyState from "./components/ChatEmptyState.js";
37
37
  import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./core/events.js";
38
38
  import { useKeyboardShortcuts } from "./core/useKeyboardShortcuts.js";
39
39
  import { KeyboardShortcutsProvider } from "./core/KeyboardShortcutsProvider.js";
40
- import { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
40
+ import { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs } from "./ui/sidebar.js";
41
41
  import { ConversationSidebar, useResizableSidebarWidth } from "./ui/ConversationSidebar.js";
42
42
  import ShareDialog, { ShareButton } from "./components/ShareDialog.js";
43
43
  import { groupConversationsByDate, useConversationActions } from "./components/conversationActions.js";
@@ -46,4 +46,4 @@ import ChatLayout, { CHAT_COLUMN_SCOPE, ConversationLayout, DRAWER_BELOW_PX } fr
46
46
  import ChatMessage_default from "./components/ChatMessage.js";
47
47
  import ChatMessageList from "./components/ChatMessageList.js";
48
48
  import ChatThread from "./components/ChatThread.js";
49
- export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, CHAT_COLUMN_SCOPE, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatStage, ChatThread, ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ConversationLayout, ConversationSidebar, DEFAULT_PERMISSION_MODE, DRAWER_BELOW_PX, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ManageProvidersLink, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, groupConversationsByDate, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useConversationActions, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
49
+ export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, CHAT_COLUMN_SCOPE, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatStage, ChatThread, ComposerContext, ComposerControls, ComposerSettings, ComposerUsage, ConnectToolsLink, ConversationLayout, ConversationSidebar, DEFAULT_PERMISSION_MODE, DRAWER_BELOW_PX, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ManageProvidersLink, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, OutputCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, groupConversationsByDate, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useConversationActions, useDragDrop, useKeyboardShortcuts, useResizableSidebarWidth, useRowDialogs, wordEmphasisRanges };
@@ -43,6 +43,12 @@ export declare function SidebarPlaceholder({ children, testId, }: {
43
43
  children: ReactNode;
44
44
  testId?: string | undefined;
45
45
  }): import("react").JSX.Element;
46
+ /** A row's first line, the same in every list: what it is, and when it last
47
+ * moved, abbreviated, on the right. */
48
+ export declare function SidebarRowTitle({ children, time, }: {
49
+ children: ReactNode;
50
+ time?: string | undefined;
51
+ }): import("react").JSX.Element;
46
52
  /** One entry in the list: the row highlights while it is the open one or
47
53
  * while its menu is up, and the menu opens from its button or a right-click. */
48
54
  export declare function SidebarRow({ selected, menuItems, openMenu, className, children, }: {
@@ -85,6 +85,20 @@ function SidebarPlaceholder({ children, testId }) {
85
85
  children
86
86
  });
87
87
  }
88
+ /** A row's first line, the same in every list: what it is, and when it last
89
+ * moved, abbreviated, on the right. */
90
+ function SidebarRowTitle({ children, time }) {
91
+ return /* @__PURE__ */ jsxs("span", {
92
+ className: "flex min-w-0 flex-1 items-baseline gap-2",
93
+ children: [/* @__PURE__ */ jsx("span", {
94
+ className: "min-w-0 flex-1 truncate text-sm theme-text",
95
+ children
96
+ }), time && /* @__PURE__ */ jsx("span", {
97
+ className: "min-w-8 shrink-0 text-[11px] tabular-nums theme-muted-text",
98
+ children: time
99
+ })]
100
+ });
101
+ }
88
102
  /** One entry in the list: the row highlights while it is the open one or
89
103
  * while its menu is up, and the menu opens from its button or a right-click. */
90
104
  function SidebarRow({ selected, menuItems, openMenu, className, children }) {
@@ -191,4 +205,4 @@ function useRowDialogs({ strings, nameOf, onDelete, onRename }) {
191
205
  };
192
206
  }
193
207
  //#endregion
194
- export { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarToggle, useRowDialogs };
208
+ export { RenameDialog, SidebarDrawer, SidebarGroupHeading, SidebarPanel, SidebarPlaceholder, SidebarRailItem, SidebarRow, SidebarRowTitle, SidebarToggle, useRowDialogs };
package/dist/editing.d.ts CHANGED
@@ -406,6 +406,8 @@ export interface WorkflowEditing {
406
406
  inputChildrenKey: (type: unknown) => string | undefined;
407
407
  isValidInputName: (name: string) => boolean;
408
408
  newInputName: (existing: Iterable<string>) => string;
409
+ /** The name an `addJob` edit gives the job it adds. */
410
+ newJobName: (existing: Iterable<string>) => string;
409
411
  /** `base`, or `base_2`, `base_3`… when it's taken. */
410
412
  nextName: (taken: Iterable<string>, base: string) => string;
411
413
  /** The first of `candidate(1)`, `candidate(2)`… that `taken` doesn't hold. */
@@ -1,11 +1,11 @@
1
1
  import { useCssIsDark } from "../components/useCssIsDark.js";
2
2
  import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
3
+ import { lintContext, useLintEditing, useLintSources } from "./lintContext.js";
3
4
  import { publishNestedText } from "./nestedText.js";
4
5
  import { registerRevealer } from "./reveal.js";
5
6
  import { defineEditorThemes, getThemeName } from "./themes.js";
6
7
  import { setupMonacoWorkers } from "./workers.js";
7
8
  import { holdListeners, modelFor } from "./editorModel.js";
8
- import { lintContext, useLintEditing, useLintSources } from "./lintContext.js";
9
9
  import { ensureYamlLanguage } from "./yamlLanguage.js";
10
10
  import cx from "classnames";
11
11
  import { useEffect, useLayoutEffect, useRef } from "react";
@@ -1,8 +1,8 @@
1
+ import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
1
2
  import { INPUT_YAML_PATH, JOB_YAML_PATH, SETTINGS_YAML_PATH, STEP_YAML_PATH, settingsSchemas } from "./settingsYaml.js";
2
3
  import Editor from "./Editor.js";
3
4
  import EditorField from "./EditorField.js";
4
5
  import FormCodePanel from "./FormCodePanel.js";
5
- import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
6
6
  import { nestedWorkflowText } from "./nestedText.js";
7
7
  import { revealLines } from "./reveal.js";
8
8
  import { defineEditorThemes, getThemeName } from "./themes.js";
@@ -1,7 +1,7 @@
1
- import { useLoadedWorkflowEngine, useRepoSuggestions, useSecretVariablesResolver, useWorkflowActions, useWorkflowJsonResolver } from "../components/Provider.js";
1
+ import { useLoadedWorkflowEngine, useRepoSuggestions, useSecretVariablesResolver, useWorkflowActions, useWorkflowEditing, useWorkflowJsonResolver } from "../components/Provider.js";
2
2
  import { asRecord } from "../graph/records.js";
3
- import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
4
3
  import { DEFAULT_GITLAB_HOST, DEFAULT_REPO_YAML, loadUsesInputs } from "../graph/usesInputs.js";
4
+ import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
5
5
  import { useEffect, useMemo, useState } from "react";
6
6
  //#region src/editor/lintContext.ts
7
7
  var KEEP_MS = 6e4;
@@ -112,6 +112,18 @@ function useLintSources() {
112
112
  actions
113
113
  ]);
114
114
  }
115
+ /** lintContext for a component, which renders again when more of it arrives. */
116
+ function useLintContext(source) {
117
+ const editing = useWorkflowEditing();
118
+ const sources = useLintSources();
119
+ const [arrivals, setArrivals] = useState(0);
120
+ return useMemo(() => source === void 0 ? {} : lintContext(editing, source, sources, () => setArrivals((n) => n + 1)), [
121
+ editing,
122
+ source,
123
+ sources,
124
+ arrivals
125
+ ]);
126
+ }
115
127
  /**
116
128
  * The engine's editing once its checks have loaded, rendering again when they have; undefined
117
129
  * before then, without an engine that edits, or with `load` false.
@@ -133,4 +145,4 @@ function useLintEditing(load = true) {
133
145
  return load && ready ? engine?.editing : void 0;
134
146
  }
135
147
  //#endregion
136
- export { HOST_MARKER_OWNER, LINT_OWNER, lintContext, useLintEditing, useLintSources };
148
+ export { HOST_MARKER_OWNER, LINT_OWNER, lintContext, useLintContext, useLintEditing, useLintSources };
@@ -27,6 +27,7 @@ function FieldWrapper({ label, tooltipComponent, description, labelPosition = "l
27
27
  children: [/* @__PURE__ */ jsxs("label", {
28
28
  htmlFor: id,
29
29
  id: labelId,
30
+ "data-field-label": true,
30
31
  className: cx("relative", labelPosition === "left" ? "form-label" : "form-label-top", spaceCompact && "form-label-compact"),
31
32
  children: [
32
33
  label,
package/dist/form/Form.js CHANGED
@@ -14,6 +14,7 @@ import { FormLayout, LayoutCSSContext } from "./FormLayout.js";
14
14
  import { useFieldControlProps, useFieldRequired } from "./fieldContext.js";
15
15
  import { getValueUsingPath } from "./utils/getValueUsingPath.js";
16
16
  import { FieldRegistryContext, Registry } from "./fieldRegistry.js";
17
+ import { EditingScope, useFormEditing } from "./formEditing.js";
17
18
  import { useParsedOpts } from "./useParsedOpts.js";
18
19
  import { usePrevious } from "./usePrevious.js";
19
20
  import { getParentValue } from "./utils/getParentValue.js";
@@ -209,6 +210,7 @@ function GroupHeader({ children, open, setOpen, title }) {
209
210
  children: [/* @__PURE__ */ jsxs("button", {
210
211
  type: "button",
211
212
  "aria-expanded": open,
213
+ "data-field-label": true,
212
214
  className: "w-full flex items-center text-left transform ease-in transition cursor-pointer",
213
215
  onClick: () => setOpen((open) => !open),
214
216
  children: [/* @__PURE__ */ jsx(FaAngleRight, { className: cx(open && "transform rotate-90 ", "px-1 text-[1.25rem] transition ease-in ") }), /* @__PURE__ */ jsx("span", { children: title })]
@@ -393,6 +395,8 @@ function useFieldParse(optionsField, values, arrayIndex) {
393
395
  }
394
396
  var FormField = React.memo(function FormField({ optionsField, fieldName, values, setFormDirty, setFieldValue, setFieldTouched, parentInfo, labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false }) {
395
397
  const parsedField = useFieldParse(optionsField, values, parentInfo?.arrayIndex);
398
+ const editing = useFormEditing();
399
+ const path = editing ? [...editing.parent, fieldName] : null;
396
400
  const field = asSchemaField(parentInfo ? parsedField : optionsField);
397
401
  if (field === null) return null;
398
402
  const fieldNamePrefix = parentInfo?.fieldNamePrefix || "";
@@ -405,7 +409,11 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
405
409
  if (!fieldObj.name || fieldName === "v3") return null;
406
410
  if (field.hidden === true || !field.type) {
407
411
  if (resolvedFlag(field.ignore) && !workflowForm) deleteFieldByPath(values, fieldObj.name);
408
- return null;
412
+ return editing && path && fieldName !== "$meta" ? /* @__PURE__ */ jsx(editing.Row, {
413
+ path,
414
+ instance: fieldObj.name,
415
+ hidden: true
416
+ }) : null;
409
417
  }
410
418
  let onChange;
411
419
  if (field.one_must_be_true && parentInfo?.onChange) {
@@ -421,7 +429,11 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
421
429
  if (shouldShowField && field.show_if_not !== void 0) shouldShowField = !getParentValue(index, field.show_if_not, values, field);
422
430
  if (!shouldShowField) {
423
431
  if (!workflowForm) deleteFieldByPath(values, fieldObj.name);
424
- return null;
432
+ return editing && path ? /* @__PURE__ */ jsx(editing.Row, {
433
+ path,
434
+ instance: fieldObj.name,
435
+ hidden: true
436
+ }) : null;
425
437
  }
426
438
  }
427
439
  if (field.depends_on && parentInfo?.arrayIndex !== void 0) fieldObj.depends_on = field.depends_on.replace("[index]", `[${parentInfo.arrayIndex}]`);
@@ -436,7 +448,7 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
436
448
  const hideHeader = field.noCollapse;
437
449
  const HeaderElement = hideHeader ? React.Fragment : UncontrolledCollapsiblePanel;
438
450
  const meta = resolveMetaOverrides(field.options, labelPosition, spaceCompact);
439
- return /* @__PURE__ */ jsx("div", {
451
+ const group = /* @__PURE__ */ jsx("div", {
440
452
  className: "flex flex-col w-full",
441
453
  children: /* @__PURE__ */ jsx(HeaderElement, {
442
454
  ...!hideHeader && {
@@ -449,21 +461,30 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
449
461
  },
450
462
  children: /* @__PURE__ */ jsx("div", {
451
463
  className: "w-full",
452
- children: /* @__PURE__ */ jsx(FieldsFromOptions, {
453
- options: field.options ?? {},
454
- setFormDirty,
455
- setFieldValue,
456
- setFieldTouched,
457
- values,
458
- parentInfo,
459
- workflowForm,
460
- labelPosition: meta.labelPosition,
461
- spaceCompact: meta.spaceCompact,
462
- missingFields
464
+ children: /* @__PURE__ */ jsx(EditingScope, {
465
+ editing,
466
+ path,
467
+ children: /* @__PURE__ */ jsx(FieldsFromOptions, {
468
+ options: field.options ?? {},
469
+ setFormDirty,
470
+ setFieldValue,
471
+ setFieldTouched,
472
+ values,
473
+ parentInfo,
474
+ workflowForm,
475
+ labelPosition: meta.labelPosition,
476
+ spaceCompact: meta.spaceCompact,
477
+ missingFields
478
+ })
463
479
  })
464
480
  })
465
481
  })
466
482
  });
483
+ return editing && path ? /* @__PURE__ */ jsx(editing.Row, {
484
+ path,
485
+ instance: fieldObj.name,
486
+ children: group
487
+ }) : group;
467
488
  }
468
489
  if (typeof field.type === "object" && field.depends_on) {
469
490
  const parentValue = getValueUsingPath(values, field.depends_on);
@@ -489,7 +510,7 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
489
510
  if (Array.isArray(field.secondaryField)) fieldObj.secondaryField = field.secondaryField.map((secondaryField) => fieldNamePrefix + secondaryField);
490
511
  else fieldObj.secondaryField = fieldNamePrefix + field.secondaryField;
491
512
  }
492
- return /* @__PURE__ */ jsx("div", {
513
+ const input = /* @__PURE__ */ jsx("div", {
493
514
  className: cx("flex w-full", labelPosition === "left" ? "mb-[15px]" : "mb-[5px]"),
494
515
  children: /* @__PURE__ */ jsx(InputField, {
495
516
  field: fieldObj,
@@ -508,6 +529,17 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
508
529
  workflowForm
509
530
  })
510
531
  });
532
+ if (!editing || !path) return input;
533
+ const holds = fieldObj.type === "object" || fieldObj.type === "list";
534
+ return /* @__PURE__ */ jsx(editing.Row, {
535
+ path,
536
+ instance: fieldObj.name,
537
+ children: /* @__PURE__ */ jsx(EditingScope, {
538
+ editing,
539
+ path: holds ? path : null,
540
+ children: input
541
+ })
542
+ });
511
543
  }, (prev, next) => {
512
544
  if (!deepEqual(prev.optionsField, next.optionsField)) return false;
513
545
  if (prev.values !== next.values) return false;
@@ -519,6 +551,7 @@ var FormField = React.memo(function FormField({ optionsField, fieldName, values,
519
551
  return true;
520
552
  });
521
553
  function FieldsFromOptions({ options = {}, values, setFormDirty, setFieldValue, setFieldTouched, parentInfo, labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false }) {
554
+ const editing = useFormEditing();
522
555
  const chosen = useChosenLabelPosition(options);
523
556
  const wizard = parseWizardConfig(options);
524
557
  if (wizard) return /* @__PURE__ */ jsx(ChosenLabelPosition.Provider, {
@@ -556,22 +589,27 @@ function FieldsFromOptions({ options = {}, values, setFormDirty, setFieldValue,
556
589
  spaceCompact,
557
590
  workflowForm
558
591
  }, fieldName);
592
+ const add = editing ? /* @__PURE__ */ jsx(editing.Add, { parent: editing.parent }) : null;
559
593
  const layout = resolveFormLayout(asRecord(options["$meta"])["layout"], names.filter((name) => !name.startsWith("$") && asSchemaField(options[name])));
560
594
  if (layout.layout) return /* @__PURE__ */ jsx(ChosenLabelPosition.Provider, {
561
595
  value: chosen,
562
596
  children: /* @__PURE__ */ jsxs("div", {
563
597
  className: "flex min-w-0 flex-col gap-4",
564
- children: [/* @__PURE__ */ jsx(FormLayout, {
565
- layout: layout.layout,
566
- renderField: (name) => fieldOf(name, true)
567
- }), layout.remaining.map((name) => fieldOf(name, true))]
598
+ children: [
599
+ /* @__PURE__ */ jsx(FormLayout, {
600
+ layout: layout.layout,
601
+ renderField: (name) => fieldOf(name, true)
602
+ }),
603
+ layout.remaining.map((name) => fieldOf(name, true)),
604
+ add
605
+ ]
568
606
  })
569
607
  });
570
608
  return /* @__PURE__ */ jsx(ChosenLabelPosition.Provider, {
571
609
  value: chosen,
572
- children: flows ? /* @__PURE__ */ jsx("div", {
610
+ children: flows ? /* @__PURE__ */ jsxs("div", {
573
611
  className: "@container/inputs -mx-2 flex flex-wrap items-start",
574
- children: columns.map((column) => {
612
+ children: [columns.map((column) => {
575
613
  const width = inputWidth(asRecord(options[column.head])["width"]);
576
614
  return /* @__PURE__ */ jsx("div", {
577
615
  "data-input-cell": true,
@@ -587,8 +625,11 @@ function FieldsFromOptions({ options = {}, values, setFormDirty, setFieldValue,
587
625
  }, name);
588
626
  })
589
627
  }, column.head);
590
- })
591
- }) : names.map((name) => fieldOf(name))
628
+ }), add && /* @__PURE__ */ jsx("div", {
629
+ className: "order-2 w-full px-2",
630
+ children: add
631
+ })]
632
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [names.map((name) => fieldOf(name, void 0)), add] })
592
633
  });
593
634
  }
594
635
  var noop = () => {};
@@ -2,16 +2,21 @@ import { useStrings, useWorkflowEngine } from "../../components/Provider.js";
2
2
  import { copyBounds, stepText } from "./utils.js";
3
3
  import { initializeValues } from "../lib.js";
4
4
  import { getValueUsingPath } from "../utils/getValueUsingPath.js";
5
+ import { useFormEditing } from "../formEditing.js";
5
6
  import { useWizardState } from "./useWizardState.js";
6
7
  import { WizardNavigation } from "./WizardNavigation.js";
7
8
  import { WizardStepContent } from "./WizardStepContent.js";
8
9
  import { WizardStepIndicator } from "./WizardStepIndicator.js";
9
- import { useCallback, useEffect, useMemo, useRef } from "react";
10
+ import { WizardPagesContext } from "./wizardPages.js";
11
+ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
10
12
  import { jsx, jsxs } from "react/jsx-runtime";
11
13
  import { useFormikContext } from "formik";
12
14
  //#region src/form/Wizard/WizardContainer.tsx
13
15
  function WizardContainer({ wizardConfig, values, onChange, onSubmit, className = "", labelPosition = "left", missingFields = [], spaceCompact = false, workflowForm = false, nested = false, fieldNamePrefix = "", setFieldValue, setFieldTouched }) {
14
16
  const { validateForm, setTouched, touched, initialValues } = useFormikContext();
17
+ const editing = useFormEditing();
18
+ const pages = useContext(WizardPagesContext);
19
+ const [ownPage, setOwnPage] = useState(0);
15
20
  const { form: strings } = useStrings();
16
21
  const engine = useWorkflowEngine();
17
22
  const { config, steps, stepOrder } = wizardConfig;
@@ -197,19 +202,39 @@ function WizardContainer({ wizardConfig, values, onChange, onSubmit, className =
197
202
  touched,
198
203
  onSubmit
199
204
  ]);
205
+ const last = Math.max(shown.order.length - 1, 0);
206
+ const wizardKey = editing ? JSON.stringify(editing.parent) : "";
207
+ const editIndex = Math.min(Math.max(pages ? pages.page(wizardKey) : ownPage, 0), last);
208
+ const turn = (to) => {
209
+ const next = Math.max(to, 0);
210
+ if (pages) pages.setPage(wizardKey, next);
211
+ else setOwnPage(next);
212
+ };
213
+ const drawn = shown.order.length;
214
+ const setCount = pages?.setCount;
215
+ useEffect(() => {
216
+ setCount?.(wizardKey, drawn);
217
+ }, [
218
+ setCount,
219
+ wizardKey,
220
+ drawn
221
+ ]);
200
222
  const shownBefore = useRef(shown.order);
201
223
  useEffect(() => {
202
224
  shownBefore.current = shown.order;
203
225
  });
204
- const pageKey = shown.byKey[currentStep] ? currentStep : nearestPage(currentStep, shown.order, shownBefore.current);
226
+ const runKey = shown.byKey[currentStep] ? currentStep : nearestPage(currentStep, shown.order, shownBefore.current);
205
227
  useEffect(() => {
206
- if (pageKey && pageKey !== currentStep) showStep(pageKey);
228
+ if (!editing && runKey && runKey !== currentStep) showStep(runKey);
207
229
  }, [
208
- pageKey,
230
+ editing,
231
+ runKey,
209
232
  currentStep,
210
233
  showStep
211
234
  ]);
235
+ const pageKey = editing ? shown.order[editIndex] ?? "" : runKey;
212
236
  const page = shown.byKey[pageKey];
237
+ const show = (key, index) => editing ? turn(index) : showStep(key);
213
238
  const copiesOf = (step) => {
214
239
  const rows = getValueUsingPath(values, `${fieldNamePrefix}${step}`);
215
240
  return Array.isArray(rows) ? rows : [];
@@ -220,13 +245,15 @@ function WizardContainer({ wizardConfig, values, onChange, onSubmit, className =
220
245
  const kept = Array.from({ length: shownPage.count ?? rows.length }, (_, i) => rows[i] ?? initializeValues(options, {}) ?? {});
221
246
  const aside = setAside.current[`${fieldNamePrefix}${shownPage.step}`] ?? [];
222
247
  setFieldValue(`${fieldNamePrefix}${shownPage.step}`, [...kept, takeAside(aside, kept.length) ?? initializeValues(options, {}) ?? {}]);
223
- showStep(`${shownPage.step}[${kept.length}]`);
248
+ const at = shown.order.indexOf(pageKey) + (shownPage.copy === void 0 ? 0 : 1);
249
+ show(`${shownPage.step}[${kept.length}]`, at);
224
250
  };
225
251
  const removeCopy = (shownPage) => {
226
252
  const copy = shownPage.copy ?? 0;
227
253
  const rows = copiesOf(shownPage.step).filter((_, i) => i !== copy);
228
254
  setFieldValue(`${fieldNamePrefix}${shownPage.step}`, rows);
229
- showStep(rows.length > 0 ? `${shownPage.step}[${Math.max(copy - 1, 0)}]` : shownPage.step);
255
+ const at = shown.order.indexOf(pageKey) - (copy > 0 ? 1 : 0);
256
+ show(rows.length > 0 ? `${shownPage.step}[${Math.max(copy - 1, 0)}]` : shownPage.step, at);
230
257
  };
231
258
  const rawTitle = page ? steps[page.step]?.title : void 0;
232
259
  const titledPerCopy = Array.isArray(rawTitle) || typeof rawTitle === "string" && rawTitle.includes("${{");
@@ -264,6 +291,39 @@ function WizardContainer({ wizardConfig, values, onChange, onSubmit, className =
264
291
  setFieldTouched
265
292
  }), copies]
266
293
  }) : null;
294
+ if (editing) return /* @__PURE__ */ jsxs("div", {
295
+ className,
296
+ children: [
297
+ navigation.showSteps !== false && /* @__PURE__ */ jsx(WizardStepIndicator, {
298
+ stepOrder: shown.order,
299
+ currentStep: pageKey,
300
+ steps: configs,
301
+ visitedSteps: new Set(shown.order),
302
+ invalidSteps: /* @__PURE__ */ new Set(),
303
+ onStepClick: (key) => {
304
+ turn(shown.order.indexOf(key));
305
+ return true;
306
+ },
307
+ allowJump: true,
308
+ hideStepNumbers: navigation.hideStepNumbers
309
+ }),
310
+ content,
311
+ /* @__PURE__ */ jsx(WizardNavigation, {
312
+ isLastStep: editIndex === last,
313
+ canGoBack: editIndex > 0,
314
+ isCurrentStepValid: true,
315
+ nextLabel: page?.config.nextLabel,
316
+ prevLabel: page?.config.prevLabel,
317
+ submitLabel: config.submitLabel || "Execute",
318
+ onNext: async () => {
319
+ turn(editIndex + 1);
320
+ return true;
321
+ },
322
+ onPrevious: () => turn(editIndex - 1),
323
+ onSubmit: nested ? void 0 : async () => {}
324
+ })
325
+ ]
326
+ });
267
327
  return /* @__PURE__ */ jsxs("div", {
268
328
  className,
269
329
  children: [