@signalsafe/tree-spec-editor 0.2.0 → 0.2.2

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -59
  3. package/dist/index.d.ts +11 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +5 -1
  6. package/dist/lib/PanelHeaderCollapseCarets.d.ts +4 -7
  7. package/dist/lib/PanelHeaderCollapseCarets.d.ts.map +1 -1
  8. package/dist/lib/PanelHeaderCollapseCarets.js +2 -10
  9. package/dist/lib/buildDefaultToolbarSpec.d.ts +5 -155
  10. package/dist/lib/buildDefaultToolbarSpec.d.ts.map +1 -1
  11. package/dist/lib/buildDefaultToolbarSpec.js +17 -198
  12. package/dist/lib/panelHelpers.d.ts +1 -7
  13. package/dist/lib/panelHelpers.d.ts.map +1 -1
  14. package/dist/lib/panelHelpers.js +11 -19
  15. package/dist/lib/panelNodeLink.d.ts +13 -0
  16. package/dist/lib/panelNodeLink.d.ts.map +1 -0
  17. package/dist/lib/panelNodeLink.js +12 -0
  18. package/dist/lib/selectionStyles.d.ts +9 -0
  19. package/dist/lib/selectionStyles.d.ts.map +1 -0
  20. package/dist/lib/selectionStyles.js +8 -0
  21. package/dist/lib/toolbar/defaultToolbarTypes.d.ts +89 -0
  22. package/dist/lib/toolbar/defaultToolbarTypes.d.ts.map +1 -0
  23. package/dist/lib/toolbar/defaultToolbarTypes.js +1 -0
  24. package/dist/lib/toolbar/toolbarSectionBuilders.d.ts +10 -0
  25. package/dist/lib/toolbar/toolbarSectionBuilders.d.ts.map +1 -0
  26. package/dist/lib/toolbar/toolbarSectionBuilders.js +153 -0
  27. package/dist/modals/AuditLogModal.d.ts +1 -1
  28. package/dist/modals/AuditLogModal.d.ts.map +1 -1
  29. package/dist/modals/AuditLogModal.js +1 -1
  30. package/dist/modals/DraftHistoryModal.d.ts +1 -1
  31. package/dist/modals/DraftHistoryModal.d.ts.map +1 -1
  32. package/dist/modals/DraftHistoryModal.js +1 -1
  33. package/dist/modals/PublishReviewModal.d.ts +1 -1
  34. package/dist/modals/PublishReviewModal.d.ts.map +1 -1
  35. package/dist/panels/AdvancedJsonPanel.d.ts +1 -1
  36. package/dist/panels/AdvancedJsonPanel.d.ts.map +1 -1
  37. package/dist/panels/AppearancePanel.d.ts +26 -0
  38. package/dist/panels/AppearancePanel.d.ts.map +1 -0
  39. package/dist/panels/AppearancePanel.js +76 -0
  40. package/dist/panels/ChoiceEdgeAppearanceFields.d.ts +10 -0
  41. package/dist/panels/ChoiceEdgeAppearanceFields.d.ts.map +1 -0
  42. package/dist/panels/ChoiceEdgeAppearanceFields.js +13 -0
  43. package/dist/panels/ChoiceTemplatesPanel.d.ts +27 -0
  44. package/dist/panels/ChoiceTemplatesPanel.d.ts.map +1 -0
  45. package/dist/panels/ChoiceTemplatesPanel.js +9 -0
  46. package/dist/panels/GraphEditorInfoPanel.d.ts +5 -2
  47. package/dist/panels/GraphEditorInfoPanel.d.ts.map +1 -1
  48. package/dist/panels/GraphEditorInfoPanel.js +6 -6
  49. package/dist/panels/InspectorPanel.d.ts +3 -78
  50. package/dist/panels/InspectorPanel.d.ts.map +1 -1
  51. package/dist/panels/InspectorPanel.js +20 -60
  52. package/dist/panels/IssuesPanel.d.ts +6 -2
  53. package/dist/panels/IssuesPanel.d.ts.map +1 -1
  54. package/dist/panels/IssuesPanel.js +15 -8
  55. package/dist/panels/NodesPanel.d.ts +3 -1
  56. package/dist/panels/NodesPanel.d.ts.map +1 -1
  57. package/dist/panels/NodesPanel.js +12 -14
  58. package/dist/panels/SelectedEdgePanel.d.ts +10 -3
  59. package/dist/panels/SelectedEdgePanel.d.ts.map +1 -1
  60. package/dist/panels/SelectedEdgePanel.js +5 -6
  61. package/dist/panels/ToolbarPanel.d.ts +4 -6
  62. package/dist/panels/ToolbarPanel.d.ts.map +1 -1
  63. package/dist/panels/ToolbarPanel.js +2 -4
  64. package/dist/panels/inspector/ChoiceEditorCard.d.ts +28 -0
  65. package/dist/panels/inspector/ChoiceEditorCard.d.ts.map +1 -0
  66. package/dist/panels/inspector/ChoiceEditorCard.js +36 -0
  67. package/dist/panels/inspector/ChoiceEditorList.d.ts +25 -0
  68. package/dist/panels/inspector/ChoiceEditorList.d.ts.map +1 -0
  69. package/dist/panels/inspector/ChoiceEditorList.js +10 -0
  70. package/dist/panels/inspector/NodeTypeField.d.ts +8 -0
  71. package/dist/panels/inspector/NodeTypeField.d.ts.map +1 -0
  72. package/dist/panels/inspector/NodeTypeField.js +12 -0
  73. package/dist/panels/inspector/RequiredNodeCard.d.ts +14 -0
  74. package/dist/panels/inspector/RequiredNodeCard.d.ts.map +1 -0
  75. package/dist/panels/inspector/RequiredNodeCard.js +16 -0
  76. package/dist/panels/inspector/choiceActionIconProps.d.ts +12 -0
  77. package/dist/panels/inspector/choiceActionIconProps.d.ts.map +1 -0
  78. package/dist/panels/inspector/choiceActionIconProps.js +26 -0
  79. package/dist/panels/inspector/types.d.ts +77 -0
  80. package/dist/panels/inspector/types.d.ts.map +1 -0
  81. package/dist/panels/inspector/types.js +11 -0
  82. package/package.json +83 -65
@@ -1,63 +1,23 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { Form } from 'react-bootstrap';
4
- import { TERMINAL_OUTCOME } from '@signalsafe/tree-spec';
5
- import { END_NODE_ID, getTransition, TREE_SPEC_NODE_TYPE_PRESETS, } from '@signalsafe/tree-spec-editor-core';
6
- import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
7
2
  /**
8
- * Canonical outcome values from the `@signalsafe/tree-spec` wire contract.
9
- * Hosts can override via the `outcomeOptions` prop.
3
+ * Inspector panel for the TreeSpec graph editor.
4
+ *
5
+ * Renders a **Required** card (Type, Prompt, optional `renderExtraNodeFields`) and a **Choices**
6
+ * card (list-group rows with Label / Next / Outcome / delete). Hosts inject project-specific UI through optional render-prop slots:
7
+ *
8
+ * - `outcomeOptions`: override the END-transition outcome `<select>` options
9
+ * (default = the three canonical `TerminalOutcome` values from `@signalsafe/tree-spec`).
10
+ * - `hideOutcomeField`: hide the outcome `<select>` entirely when the host manages outcomes
11
+ * elsewhere.
12
+ * - `renderExtraNodeFields`: render additional UI below the prompt textarea, scoped to the
13
+ * currently-selected node.
14
+ * - `renderExtraChoiceFields`: render additional UI below each choice card.
15
+ * - `choiceTypes` / `onSetChoiceType`: host-defined choice type catalog (stable ids).
10
16
  */
11
- export const DEFAULT_OUTCOME_OPTIONS = [
12
- { value: TERMINAL_OUTCOME.SAFE, label: TERMINAL_OUTCOME.SAFE },
13
- { value: TERMINAL_OUTCOME.AT_RISK, label: TERMINAL_OUTCOME.AT_RISK },
14
- { value: TERMINAL_OUTCOME.COMPROMISED, label: TERMINAL_OUTCOME.COMPROMISED },
15
- ];
16
- const DEFAULT_TYPE_HELPER_TEXT = 'Type is stored as a string in tree_spec. You can use presets or any custom value.';
17
- function NodeTypeField({ selectedNode, isPublished, onUpdateSelectedNode, typeHelperText, }) {
18
- const isPreset = TREE_SPEC_NODE_TYPE_PRESETS.includes(selectedNode.type);
19
- return (_jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Type" }), _jsxs(Form.Select, { value: isPreset ? selectedNode.type : '__custom__', onChange: (e) => {
20
- const nextType = e.target.value;
21
- if (nextType === '__custom__')
22
- return;
23
- onUpdateSelectedNode({ type: nextType });
24
- }, disabled: isPublished, children: [TREE_SPEC_NODE_TYPE_PRESETS.map((t) => (_jsx("option", { value: t, children: t }, t))), _jsx("option", { value: "__custom__", children: "custom\u2026" })] }), isPreset ? null : (_jsx(Form.Control, { className: "mt-2", value: selectedNode.type, onChange: (e) => onUpdateSelectedNode({ type: e.target.value }), placeholder: "Enter custom type\u2026", disabled: isPublished })), _jsx("div", { className: "text-muted mt-1 font-size-12", children: typeHelperText })] }));
25
- }
26
- function ChoiceEditorCard({ choice, tree, selectedNode, focusChoiceId, isPublished, onUpdateSelectedNode, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, }) {
27
- const transition = getTransition(tree, selectedNode.id, choice.id);
28
- const targetNodeId = transition?.toNodeId ?? '';
29
- const outcomeValue = transition?.outcome ?? outcomeOptions[0]?.value ?? '';
30
- const isFocused = focusChoiceId === choice.id;
31
- const showOutcome = !hideOutcomeField && targetNodeId === END_NODE_ID;
32
- return (_jsxs("div", { id: `choice-editor-${selectedNode.id}-${choice.id}`, className: `list-group-item py-3 px-3${isFocused ? ' list-group-item-primary' : ''}`, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-start gap-2 mb-2", children: [_jsx("h5", { className: `mb-0 text-uppercase fs-6 fw-semibold${isFocused ? '' : ' text-body-secondary'}`, children: choice.id }), _jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Delete choice", title: "Delete choice", onClick: () => onDeleteChoice(choice.id), children: _jsx("i", { className: "bi bi-trash text-danger action-icon cursor-pointer", "aria-hidden": true }) })] }), _jsx(Form.Label, { className: "mt-0", children: "Label" }), _jsx(Form.Control, { value: choice.label, onChange: (event) => {
33
- const updatedChoices = (selectedNode.choices ?? []).map((existingChoice) => existingChoice.id === choice.id
34
- ? { ...existingChoice, label: event.target.value }
35
- : existingChoice);
36
- onUpdateSelectedNode({ choices: updatedChoices });
37
- } }), _jsx(Form.Label, { className: "mt-2", children: "Next" }), _jsxs(Form.Select, { value: targetNodeId, onChange: (e) => onSetChoiceTarget(choice.id, e.target.value), children: [_jsx("option", { value: "", children: "(not connected)" }), Object.values(tree.nodes).map((n) => (_jsxs("option", { value: n.id, children: [n.id, " \u00B7 ", n.prompt?.slice(0, 40) || n.type] }, n.id))), _jsx("option", { value: END_NODE_ID, children: "END" })] }), showOutcome ? (_jsxs(_Fragment, { children: [_jsx(Form.Label, { className: "mt-2", children: "Outcome" }), _jsx(Form.Select, { value: outcomeValue, onChange: (e) => onSetChoiceOutcome(choice.id, e.target.value), children: outcomeOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] })) : null, renderExtraChoiceFields
38
- ? renderExtraChoiceFields({
39
- tree,
40
- node: selectedNode,
41
- choice,
42
- transition,
43
- isPublished,
44
- onUpdateNode: onUpdateSelectedNode,
45
- })
46
- : null] }));
47
- }
48
- function ChoiceEditorList({ tree, selectedNode, focusChoiceId, isPublished, onAddChoice, onUpdateSelectedNode, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, }) {
49
- const choices = selectedNode.choices ?? [];
50
- const [choicesExpanded, setChoicesExpanded] = useState(true);
51
- return (_jsxs("div", { className: "card mt-3", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: "Choices" }), _jsx(PanelHeaderCollapseCarets, { expanded: choicesExpanded, onToggle: () => setChoicesExpanded((v) => !v) })] }), _jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Add choice", title: "Add choice", onClick: onAddChoice, children: _jsx("i", { className: "bi bi-plus-lg text-primary action-icon cursor-pointer", "aria-hidden": true }) })] }), _jsx("div", { className: `card-body p-0${choicesExpanded ? '' : ' d-none'}`, "aria-hidden": !choicesExpanded, children: choices.length === 0 ? (_jsx("div", { className: "text-muted small py-3 px-3", children: _jsx("em", { children: "No choices" }) })) : (_jsx("div", { className: "list-group list-group-flush", children: choices.map((choice) => (_jsx(ChoiceEditorCard, { choice: choice, tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields }, choice.id))) })) })] }));
52
- }
53
- export default function InspectorPanel({ tree, selectedNode, focusChoiceId = null, isPublished = false, onUpdateSelectedNode, onAddChoice, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions = DEFAULT_OUTCOME_OPTIONS, hideOutcomeField = false, renderExtraNodeFields, renderExtraChoiceFields, title = 'Required', emptyStateText = 'Select a node to edit it.', typeHelperText = DEFAULT_TYPE_HELPER_TEXT, onDeleteSelectedNode, }) {
54
- const [requiredExpanded, setRequiredExpanded] = useState(true);
55
- return (_jsx("div", { children: selectedNode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "card", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: requiredExpanded, onToggle: () => setRequiredExpanded((v) => !v) })] }), onDeleteSelectedNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Delete node", title: "Delete node", disabled: isPublished, onClick: onDeleteSelectedNode, children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }), _jsxs("div", { className: `card-body${requiredExpanded ? '' : ' d-none'}`, "aria-hidden": !requiredExpanded, children: [_jsx(NodeTypeField, { selectedNode: selectedNode, isPublished: isPublished, onUpdateSelectedNode: onUpdateSelectedNode, typeHelperText: typeHelperText }), _jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Prompt" }), _jsx(Form.Control, { as: "textarea", rows: 4, value: selectedNode.prompt, onChange: (e) => onUpdateSelectedNode({ prompt: e.target.value }) })] }), renderExtraNodeFields
56
- ? renderExtraNodeFields({
57
- tree,
58
- node: selectedNode,
59
- isPublished,
60
- onUpdateNode: onUpdateSelectedNode,
61
- })
62
- : null] })] }), _jsx(ChoiceEditorList, { tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, onAddChoice: onAddChoice, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields })] })) : (_jsx("div", { className: "text-muted", children: _jsx("em", { children: emptyStateText }) })) }));
17
+ import ChoiceEditorList from './inspector/ChoiceEditorList';
18
+ import RequiredNodeCard from './inspector/RequiredNodeCard';
19
+ import { DEFAULT_OUTCOME_OPTIONS, DEFAULT_TYPE_HELPER_TEXT, } from './inspector/types';
20
+ export { DEFAULT_OUTCOME_OPTIONS, } from './inspector/types';
21
+ export default function InspectorPanel({ tree, selectedNode, focusChoiceId = null, isPublished = false, onUpdateSelectedNode, onAddChoice, onDeleteChoice, onMoveChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions = DEFAULT_OUTCOME_OPTIONS, hideOutcomeField = false, renderExtraNodeFields, renderExtraChoiceFields, choiceTypes, onSetChoiceType, title = 'Required', emptyStateText = 'Select a node to edit it.', typeHelperText = DEFAULT_TYPE_HELPER_TEXT, onDeleteSelectedNode, onFocusChoice, }) {
22
+ return (_jsx("div", { children: selectedNode ? (_jsxs(_Fragment, { children: [_jsx(RequiredNodeCard, { tree: tree, selectedNode: selectedNode, isPublished: isPublished, title: title, typeHelperText: typeHelperText, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteSelectedNode: onDeleteSelectedNode, renderExtraNodeFields: renderExtraNodeFields }), _jsx(ChoiceEditorList, { tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, onAddChoice: onAddChoice, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onMoveChoice: onMoveChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields, choiceTypes: choiceTypes, onSetChoiceType: onSetChoiceType, onFocusChoice: onFocusChoice })] })) : (_jsx("div", { className: "text-muted", children: _jsx("em", { children: emptyStateText }) })) }));
63
23
  }
@@ -1,5 +1,5 @@
1
1
  import type { TreeSpecIssue } from '@signalsafe/tree-spec';
2
- import { type EditorTree } from '@signalsafe/tree-spec-editor-core';
2
+ import { type EditorTree, type GraphSelection } from '@signalsafe/tree-spec-editor-core';
3
3
  export interface IssuesPanelProps {
4
4
  issues: TreeSpecIssue[];
5
5
  lastValidatedAt: string | null;
@@ -16,6 +16,10 @@ export interface IssuesPanelProps {
16
16
  * not have a tree in scope; type falls back to **—**.
17
17
  */
18
18
  tree?: EditorTree;
19
+ /** Current graph selection — used to highlight the active issue row. */
20
+ selection?: GraphSelection;
21
+ /** Focused choice id from the editor — pairs with `selection` for issue row highlight. */
22
+ focusChoiceId?: string | null;
19
23
  /** Optional override for the card header title. Defaults to "Issues". */
20
24
  title?: string;
21
25
  /** Optional placeholder for the search field. Defaults to "Search issues…". */
@@ -29,5 +33,5 @@ export interface IssuesPanelProps {
29
33
  */
30
34
  showValidationSummaryInHeader?: boolean;
31
35
  }
32
- export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, title, searchPlaceholder, formatTimestamp, showValidationSummaryInHeader, }: Readonly<IssuesPanelProps>): import("react/jsx-runtime").JSX.Element;
36
+ export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, selection, focusChoiceId, title, searchPlaceholder, formatTimestamp, showValidationSummaryInHeader, }: Readonly<IssuesPanelProps>): import("react").JSX.Element;
33
37
  //# sourceMappingURL=IssuesPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IssuesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/IssuesPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAKxF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAC3C;AA4BD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,IAAI,EACJ,KAAgB,EAChB,iBAAyC,EACzC,eAAwC,EACxC,6BAAoC,GACvC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,2CA0G5B"}
1
+ {"version":3,"file":"IssuesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/IssuesPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAGH,KAAK,UAAU,EACf,KAAK,cAAc,EACtB,MAAM,mCAAmC,CAAC;AAM3C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,wEAAwE;IACxE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAC3C;AAwCD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,IAAI,EACJ,SAAS,EACT,aAAoB,EACpB,KAAgB,EAChB,iBAAyC,EACzC,eAAwC,EACxC,6BAAoC,GACvC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,+BAoH5B"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from 'react';
3
3
  import { Form } from 'react-bootstrap';
4
- import { buildStableEntries } from '@signalsafe/tree-spec-editor-core';
4
+ import { buildStableEntries, GRAPH_SELECTION_KIND, } from '@signalsafe/tree-spec-editor-core';
5
5
  import { getIssueSeverityBadgeClass } from '../lib/panelHelpers';
6
6
  import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
7
+ import { LIST_SELECTION_CLASS, LIST_SELECTION_TEXT_CLASS } from '../lib/selectionStyles';
7
8
  function defaultFormatTimestamp(iso) {
8
9
  return new Date(iso).toLocaleString();
9
10
  }
@@ -28,7 +29,15 @@ function issueMatchesQuery(issue, qLower, tree) {
28
29
  ];
29
30
  return parts.some((p) => p.toLowerCase().includes(qLower));
30
31
  }
31
- export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, title = 'Issues', searchPlaceholder = 'Search issues\u2026', formatTimestamp = defaultFormatTimestamp, showValidationSummaryInHeader = true, }) {
32
+ function isIssueRowSelected(issue, selection, focusChoiceId) {
33
+ if (selection?.kind !== GRAPH_SELECTION_KIND.NODE || !issue.node_id) {
34
+ return false;
35
+ }
36
+ if (issue.node_id !== selection.id)
37
+ return false;
38
+ return (issue.choice_id ?? null) === (focusChoiceId ?? null);
39
+ }
40
+ export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, selection, focusChoiceId = null, title = 'Issues', searchPlaceholder = 'Search issues\u2026', formatTimestamp = defaultFormatTimestamp, showValidationSummaryInHeader = true, }) {
32
41
  const [expanded, setExpanded] = useState(true);
33
42
  const qLower = issueSearch.trim().toLowerCase();
34
43
  const filteredIssues = useMemo(() => issues.filter((issue) => issueMatchesQuery(issue, qLower, tree)), [issues, qLower, tree]);
@@ -36,11 +45,9 @@ export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, is
36
45
  return (_jsxs("div", { className: "card", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }), showValidationSummaryInHeader ? (_jsx("span", { className: "text-muted font-size-12 text-end flex-shrink-0", children: lastValidatedAt ? `Validated ${formatTimestamp(lastValidatedAt)}` : 'Not validated' })) : null] }), _jsx("div", { className: `card-body p-0${expanded ? '' : ' d-none'}`, "aria-hidden": !expanded, children: issues.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3 border-0", children: _jsx("em", { children: "No issues" }) }) })) : (_jsxs(_Fragment, { children: [_jsx(Form.Control, { className: "rounded-0 border-0 border-bottom shadow-none", placeholder: searchPlaceholder, value: issueSearch, onChange: (e) => onIssueSearchChange(e.target.value) }), _jsx("div", { className: "overflow-auto-max-h-320", children: issueEntries.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3 border-0", children: _jsx("em", { children: "No matching issues." }) }) })) : (_jsx("div", { className: "list-group list-group-flush", children: issueEntries.map(({ item: issue, key }) => {
37
46
  const typeLabel = nodeTypeLabel(tree, issue.node_id);
38
47
  const headerTitle = issueHeaderTitle(issue, typeLabel);
39
- return (_jsxs("div", { role: "button", tabIndex: 0, className: "list-group-item list-group-item-action px-3 py-2 text-start", onClick: () => onSelectIssue(issue), onKeyDown: (e) => {
40
- if (e.key === 'Enter' || e.key === ' ') {
41
- e.preventDefault();
42
- onSelectIssue(issue);
43
- }
44
- }, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-center gap-2 mb-1", children: [_jsxs("div", { className: "min-w-0 flex-grow-1 text-truncate text-start", title: headerTitle, children: [_jsx("span", { className: "fs-6 fw-semibold text-uppercase", children: issue.node_id ?? '—' }), _jsx("span", { className: "fs-6 text-uppercase text-body-secondary", children: `, ${typeLabel}` })] }), _jsx("span", { className: `badge flex-shrink-0 text-uppercase ${getIssueSeverityBadgeClass(issue.severity)}`, children: issue.severity })] }), _jsx("p", { className: "mb-1 small text-truncate font-monospace", style: { minWidth: 0 }, children: issue.message }), issue.choice_id ? (_jsx("small", { className: "text-body-secondary", children: issue.choice_id })) : null] }, key));
48
+ const isSelected = isIssueRowSelected(issue, selection, focusChoiceId);
49
+ return (_jsxs("button", { type: "button", className: `list-group-item px-3 py-2 text-start w-100 min-w-0${isSelected
50
+ ? ` ${LIST_SELECTION_CLASS} ${LIST_SELECTION_TEXT_CLASS}`
51
+ : ''}`, "aria-current": isSelected ? 'true' : undefined, onClick: () => onSelectIssue(issue), children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-start gap-2 mb-1 min-w-0", children: [_jsxs("div", { className: "min-w-0 flex-grow-1 text-break text-start", title: headerTitle, children: [_jsx("span", { className: "fs-6 fw-semibold text-uppercase", children: issue.node_id ?? '—' }), _jsx("span", { className: `fs-6 text-uppercase${isSelected ? '' : ' text-body-secondary'}`, children: `, ${typeLabel}` })] }), _jsx("span", { className: `badge flex-shrink-0 text-uppercase ${getIssueSeverityBadgeClass(issue.severity)}`, children: issue.severity })] }), _jsx("p", { className: `mb-1 small text-break font-monospace${isSelected ? '' : ' text-body-secondary'}`, children: issue.message }), issue.choice_id ? (_jsx("small", { className: `text-break${isSelected ? '' : ' text-body-secondary'}`, children: issue.choice_id })) : null] }, key));
45
52
  }) })) })] })) })] }));
46
53
  }
@@ -3,6 +3,8 @@ export interface NodesPanelProps {
3
3
  tree: EditorTree;
4
4
  nodeSearch: string;
5
5
  selection: GraphSelection;
6
+ /** Node to highlight when a choice or edge is focused (may differ from `selection`). */
7
+ focusNodeId?: string | null;
6
8
  showMiniMap: boolean;
7
9
  onNodeSearchChange: (value: string) => void;
8
10
  onNodeSelect: (nodeId: string) => void;
@@ -24,5 +26,5 @@ export interface NodesPanelProps {
24
26
  /** When true, node delete controls are disabled (read-only published revision). */
25
27
  isPublished?: boolean;
26
28
  }
27
- export default function NodesPanel({ tree, nodeSearch, selection, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder, tipText, onAddNode, onDeleteNode, isPublished, }: Readonly<NodesPanelProps>): import("react/jsx-runtime").JSX.Element;
29
+ export default function NodesPanel({ tree, nodeSearch, selection, focusNodeId, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder, tipText, onAddNode, onDeleteNode, isPublished, }: Readonly<NodesPanelProps>): import("react").JSX.Element;
28
30
  //# sourceMappingURL=NodesPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NodesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/NodesPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,EAIH,KAAK,UAAU,EACf,KAAK,cAAc,EACtB,MAAM,mCAAmC,CAAC;AAI3C,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mFAAmF;IACnF,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AA0BD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAC/B,IAAI,EACJ,UAAU,EACV,SAAS,EACT,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,iBAAwC,EACxC,OAA0B,EAC1B,SAAS,EACT,YAAY,EACZ,WAAmB,GACtB,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAyI3B"}
1
+ {"version":3,"file":"NodesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/NodesPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,EAIH,KAAK,UAAU,EACf,KAAK,cAAc,EACtB,MAAM,mCAAmC,CAAC;AAK3C,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mFAAmF;IACnF,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAmCD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAC/B,IAAI,EACJ,UAAU,EACV,SAAS,EACT,WAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,iBAAwC,EACxC,OAA0B,EAC1B,SAAS,EACT,YAAY,EACZ,WAAmB,GACtB,EAAE,QAAQ,CAAC,eAAe,CAAC,+BA8I3B"}
@@ -3,6 +3,7 @@ import { useState } from 'react';
3
3
  import { Form } from 'react-bootstrap';
4
4
  import { GRAPH_SELECTION_KIND, TREE_SPEC_NODE_TYPE_PRESETS, } from '@signalsafe/tree-spec-editor-core';
5
5
  import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
6
+ import { LIST_SELECTION_CLASS, LIST_SELECTION_TEXT_CLASS } from '../lib/selectionStyles';
6
7
  const DEFAULT_TIP_TEXT = 'Tip: connect choices by dragging the blue dots. Shortcuts: \u2318S save, \u2318\u21E7V validate, \u2318D duplicate, Del delete.';
7
8
  const TYPE_SORT_ORDER = new Map(TREE_SPEC_NODE_TYPE_PRESETS.map((t, i) => [t, i]));
8
9
  function choiceLine(n) {
@@ -24,7 +25,12 @@ function compareNodesByTypeThenId(a, b) {
24
25
  return ta.localeCompare(tb);
25
26
  return a.id.localeCompare(b.id);
26
27
  }
27
- export default function NodesPanel({ tree, nodeSearch, selection, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder = 'Search nodes\u2026', tipText = DEFAULT_TIP_TEXT, onAddNode, onDeleteNode, isPublished = false, }) {
28
+ function isNodeRowSelected(nodeId, selection, focusNodeId) {
29
+ if (focusNodeId === nodeId)
30
+ return true;
31
+ return selection.kind === GRAPH_SELECTION_KIND.NODE && selection.id === nodeId;
32
+ }
33
+ export default function NodesPanel({ tree, nodeSearch, selection, focusNodeId = null, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder = 'Search nodes\u2026', tipText = DEFAULT_TIP_TEXT, onAddNode, onDeleteNode, isPublished = false, }) {
28
34
  const [expanded, setExpanded] = useState(true);
29
35
  const filteredNodes = Object.values(tree.nodes)
30
36
  .filter((n) => {
@@ -39,18 +45,10 @@ export default function NodesPanel({ tree, nodeSearch, selection, showMiniMap, o
39
45
  })
40
46
  .sort(compareNodesByTypeThenId);
41
47
  return (_jsxs("div", { className: "card mt-3", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: "Nodes" }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }), onAddNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Add node", title: "Add node", onClick: onAddNode, children: _jsx("i", { className: "bi bi-plus-lg text-primary action-icon cursor-pointer", "aria-hidden": true }) })) : null] }), _jsxs("div", { className: expanded ? undefined : 'd-none', "aria-hidden": !expanded, children: [_jsx(Form.Control, { className: "rounded-0 border-0 border-bottom shadow-none", placeholder: searchPlaceholder, value: nodeSearch, onChange: (e) => onNodeSearchChange(e.target.value) }), _jsx("div", { className: "overflow-auto-max-h-320", children: filteredNodes.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3", children: "No matching nodes." }) })) : (_jsx("div", { className: "list-group list-group-flush", children: filteredNodes.map((n) => {
42
- const isSelected = selection.kind === GRAPH_SELECTION_KIND.NODE && selection.id === n.id;
48
+ const isSelected = isNodeRowSelected(n.id, selection, focusNodeId);
43
49
  const typeLabel = n.type || 'unknown';
44
- const mutedTop = isSelected ? '' : ' text-body-secondary';
45
- const mutedMeta = isSelected ? 'opacity-75' : 'text-body-secondary';
46
- return (_jsxs("div", { role: "button", tabIndex: 0, className: `list-group-item list-group-item-action text-start px-3 py-2${isSelected ? ' active' : ''}`, "aria-current": isSelected ? 'true' : undefined, onClick: () => onNodeSelect(n.id), onKeyDown: (e) => {
47
- if (e.key === 'Enter' || e.key === ' ') {
48
- e.preventDefault();
49
- onNodeSelect(n.id);
50
- }
51
- }, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-center gap-2 mb-1", children: [_jsxs("div", { className: "min-w-0 flex-grow-1 text-truncate text-start", title: `${n.id}, ${typeLabel}`, children: [_jsx("span", { className: `fs-6 fw-semibold text-uppercase${mutedTop}`, children: n.id }), _jsx("span", { className: `fs-6 text-uppercase ${isSelected ? 'opacity-75' : 'text-body-secondary'}`, children: `, ${typeLabel}` })] }), onDeleteNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": `Delete node ${n.id}`, title: "Delete node", disabled: isPublished, onClick: (e) => {
52
- e.stopPropagation();
53
- onDeleteNode(n.id);
54
- }, children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }), _jsx("p", { className: "mb-1 small text-truncate", style: { minWidth: 0 }, children: n.prompt || '(empty)' }), _jsx("small", { className: mutedMeta, children: choiceLine(n) })] }, n.id));
55
- }) })) }), _jsx("div", { className: "card-footer p-2 border-top", children: _jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsx("div", { className: "text-muted font-size-12", children: tipText }), _jsx(Form.Check, { type: "switch", id: "showMiniMapToggle", label: "Mini-map", checked: showMiniMap, onChange: (e) => onShowMiniMapChange(e.target.checked), className: "font-size-12" })] }) })] })] }));
50
+ return (_jsx("div", { className: `list-group-item px-3 py-2${isSelected
51
+ ? ` ${LIST_SELECTION_CLASS} ${LIST_SELECTION_TEXT_CLASS}`
52
+ : ''}`, "aria-current": isSelected ? 'true' : undefined, children: _jsxs("div", { className: "d-flex w-100 justify-content-between align-items-start gap-2 min-w-0", children: [_jsxs("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-grow-1 text-start min-w-0", onClick: () => onNodeSelect(n.id), children: [_jsx("div", { className: "d-flex w-100 justify-content-between align-items-center gap-2 mb-1 min-w-0", children: _jsxs("div", { className: "min-w-0 flex-grow-1 text-break text-start", title: `${n.id}, ${typeLabel}`, children: [_jsx("span", { className: `fs-6 fw-semibold text-uppercase${isSelected ? '' : ' text-body-secondary'}`, children: n.id }), _jsx("span", { className: `fs-6 text-uppercase${isSelected ? '' : ' text-body-secondary'}`, children: `, ${typeLabel}` })] }) }), _jsx("p", { className: `mb-1 small text-break${isSelected ? '' : ' text-body-secondary'}`, children: n.prompt || '(empty)' }), _jsx("small", { className: isSelected ? '' : ' text-body-secondary', children: choiceLine(n) })] }), onDeleteNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": `Delete node ${n.id}`, title: "Delete node", disabled: isPublished, onClick: () => onDeleteNode(n.id), children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }) }, n.id));
53
+ }) })) }), _jsx("div", { className: "card-footer p-2 border-top", children: _jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsx("div", { className: "text-muted font-size-12 text-break min-w-0 flex-grow-1 me-2", children: tipText }), _jsx(Form.Check, { type: "switch", id: "showMiniMapToggle", label: "Mini-map", checked: showMiniMap, onChange: (e) => onShowMiniMapChange(e.target.checked), className: "font-size-12" })] }) })] })] }));
56
54
  }
@@ -1,18 +1,25 @@
1
1
  /**
2
2
  * Compact read-only display of the currently selected transition.
3
3
  * Returns `null` when no edge is selected, so the host can render it unconditionally.
4
+ *
5
+ * For editing edge/choice appearance, use {@link AppearancePanel} instead.
4
6
  */
5
7
  import type { ReactNode } from 'react';
6
8
  import type { EditorTransition } from '@signalsafe/tree-spec-editor-core';
9
+ import { type PanelNodeLinkContext } from '../lib/panelNodeLink';
10
+ export type SelectedEdgeDetailsContext = PanelNodeLinkContext;
11
+ export { PanelNodeLink } from '../lib/panelNodeLink';
7
12
  export interface SelectedEdgePanelProps {
8
13
  selectedEdge: EditorTransition | null;
14
+ /** Select a node when its id is clicked in the default details line. */
15
+ onNodeSelect?: (nodeId: string) => void;
9
16
  /** Override the card title (default "Selected Edge"). */
10
17
  title?: string;
11
18
  /**
12
19
  * Optional custom renderer for the edge details line. Defaults to
13
- * `from \u00b7 choice \u2192 to`.
20
+ * `from · choice → to` with clickable node ids when `onNodeSelect` is set.
14
21
  */
15
- renderDetails?: (edge: EditorTransition) => ReactNode;
22
+ renderDetails?: (edge: EditorTransition, ctx: SelectedEdgeDetailsContext) => ReactNode;
16
23
  }
17
- export default function SelectedEdgePanel({ selectedEdge, title, renderDetails, }: Readonly<SelectedEdgePanelProps>): import("react/jsx-runtime").JSX.Element | null;
24
+ export default function SelectedEdgePanel({ selectedEdge, onNodeSelect, title, renderDetails, }: Readonly<SelectedEdgePanelProps>): import("react").JSX.Element | null;
18
25
  //# sourceMappingURL=SelectedEdgePanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SelectedEdgePanel.d.ts","sourceRoot":"","sources":["../../src/panels/SelectedEdgePanel.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACnC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,SAAS,CAAC;CACzD;AAUD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACtC,YAAY,EACZ,KAAuB,EACvB,aAAoC,GACvC,EAAE,QAAQ,CAAC,sBAAsB,CAAC,kDAWlC"}
1
+ {"version":3,"file":"SelectedEdgePanel.d.ts","sourceRoot":"","sources":["../../src/panels/SelectedEdgePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAA+B,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,MAAM,MAAM,0BAA0B,GAAG,oBAAoB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,WAAW,sBAAsB;IACnC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,0BAA0B,KAAK,SAAS,CAAC;CAC1F;AAED,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACtC,YAAY,EACZ,YAAY,EACZ,KAAuB,EACvB,aAA2C,GAC9C,EAAE,QAAQ,CAAC,sBAAsB,CAAC,sCAalC"}
@@ -1,9 +1,8 @@
1
- import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- function defaultRenderDetails(edge) {
3
- return (_jsxs(_Fragment, { children: [edge.fromNodeId, " \u00B7 ", edge.fromChoiceId, " \u2192 ", edge.toNodeId] }));
4
- }
5
- export default function SelectedEdgePanel({ selectedEdge, title = 'Selected Edge', renderDetails = defaultRenderDetails, }) {
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { renderDefaultEdgeBreadcrumb } from '../lib/panelNodeLink';
3
+ export { PanelNodeLink } from '../lib/panelNodeLink';
4
+ export default function SelectedEdgePanel({ selectedEdge, onNodeSelect, title = 'Selected Edge', renderDetails = renderDefaultEdgeBreadcrumb, }) {
6
5
  if (!selectedEdge)
7
6
  return null;
8
- return (_jsx("div", { className: "card mt-3", children: _jsxs("div", { className: "card-body", children: [_jsx("div", { className: "fw-bold", children: title }), _jsx("div", { className: "text-muted font-size-12", children: renderDetails(selectedEdge) })] }) }));
7
+ return (_jsx("div", { className: "card mt-3", children: _jsxs("div", { className: "card-body", children: [_jsx("div", { className: "fw-bold", children: title }), _jsx("div", { className: "text-muted font-size-12", children: renderDetails(selectedEdge, { onNodeSelect }) })] }) }));
9
8
  }
@@ -11,8 +11,6 @@
11
11
  * the package stays free of `react-router` (or any other) routing concerns.
12
12
  */
13
13
  import { type ReactNode } from 'react';
14
- /** Bootstrap variant string (e.g. "outline-secondary", "primary", "success"). */
15
- export type ToolbarVariant = string;
16
14
  /**
17
15
  * Discriminator values used by the `kind` field on every {@link ToolbarItem}.
18
16
  * Use these constants instead of bare string literals (e.g.
@@ -34,7 +32,7 @@ export interface ToolbarButtonItem {
34
32
  label: ReactNode;
35
33
  onClick: () => void;
36
34
  /** Bootstrap variant. Default: `"outline-secondary"`. */
37
- variant?: ToolbarVariant;
35
+ variant?: string;
38
36
  disabled?: boolean;
39
37
  title?: string;
40
38
  className?: string;
@@ -55,7 +53,7 @@ export interface ToolbarDropdownItem {
55
53
  label: ReactNode;
56
54
  entries: ToolbarDropdownEntry[];
57
55
  /** Bootstrap variant for the toggle. Default: `"outline-secondary"`. */
58
- variant?: ToolbarVariant;
56
+ variant?: string;
59
57
  disabled?: boolean;
60
58
  className?: string;
61
59
  }
@@ -64,7 +62,7 @@ export interface ToolbarBadgeItem {
64
62
  id?: string;
65
63
  label: ReactNode;
66
64
  /** Bootstrap badge variant (e.g. `"success"`, `"secondary"`). Default: `"secondary"`. */
67
- variant?: ToolbarVariant;
65
+ variant?: string;
68
66
  className?: string;
69
67
  }
70
68
  export interface ToolbarTextItem {
@@ -86,5 +84,5 @@ export interface ToolbarPanelProps {
86
84
  /** Override the outer wrapper className. Default: `"d-flex gap-2 align-items-center flex-wrap"`. */
87
85
  className?: string;
88
86
  }
89
- export default function ToolbarPanel({ items, className, }: Readonly<ToolbarPanelProps>): import("react/jsx-runtime").JSX.Element;
87
+ export default function ToolbarPanel({ items, className, }: Readonly<ToolbarPanelProps>): import("react").JSX.Element;
90
88
  //# sourceMappingURL=ToolbarPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ToolbarPanel.d.ts","sourceRoot":"","sources":["../../src/panels/ToolbarPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC;AAEX,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAEzF,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,MAAM,CAAC;IACtC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yDAAyD;IACzD,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACjC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,QAAQ,CAAC;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,wEAAwE;IACxE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,yFAAyF;IACzF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,SAAS,CAAC;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,MAAM,CAAC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,oFAAoF;IACpF,MAAM,EAAE,MAAM,SAAS,CAAC;CAC3B;AAED,MAAM,MAAM,WAAW,GACjB,iBAAiB,GACjB,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,CAAC;AAExB,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,oGAAoG;IACpG,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAyED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACjC,KAAK,EACL,SAAiC,GACpC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,2CAQ7B"}
1
+ {"version":3,"file":"ToolbarPanel.d.ts","sourceRoot":"","sources":["../../src/panels/ToolbarPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;;;;;;CAMpB,CAAC;AAEX,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAEzF,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,MAAM,CAAC;IACtC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACjC,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,OAAO,iBAAiB,CAAC,QAAQ,CAAC;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,OAAO,iBAAiB,CAAC,IAAI,CAAC;IACpC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,SAAS,CAAC;IACnB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,OAAO,iBAAiB,CAAC,MAAM,CAAC;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,oFAAoF;IACpF,MAAM,EAAE,MAAM,SAAS,CAAC;CAC3B;AAED,MAAM,MAAM,WAAW,GACjB,iBAAiB,GACjB,mBAAmB,GACnB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,CAAC;AAExB,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,oGAAoG;IACpG,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAuED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACjC,KAAK,EACL,SAAiC,GACpC,EAAE,QAAQ,CAAC,iBAAiB,CAAC,+BAQ7B"}
@@ -48,10 +48,8 @@ function renderItem(item) {
48
48
  return (_jsx("span", { className: item.className ?? DEFAULT_TEXT_CLASS, children: item.content }));
49
49
  case TOOLBAR_ITEM_KIND.CUSTOM:
50
50
  return item.render();
51
- default: {
52
- const exhaustive = item;
53
- return exhaustive;
54
- }
51
+ default:
52
+ return null;
55
53
  }
56
54
  }
57
55
  export default function ToolbarPanel({ items, className = DEFAULT_WRAPPER_CLASS, }) {
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type EditorChoice, type EditorNode, type EditorTree } from '@signalsafe/tree-spec-editor-core';
3
+ import type { ChoiceTypeOption, InspectorChoiceRenderContext } from './types';
4
+ export default function ChoiceEditorCard({ choice, tree, selectedNode, focusChoiceId, isPublished, canMoveUp, canMoveDown, usedChoiceTypeIds, onUpdateSelectedNode, onDeleteChoice, onMoveChoice, onSetChoiceTarget, onSetChoiceOutcome, onSetChoiceType, choiceTypes, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, onFocusChoice, }: Readonly<{
5
+ choice: EditorChoice;
6
+ tree: EditorTree;
7
+ selectedNode: EditorNode;
8
+ focusChoiceId: string | null;
9
+ isPublished: boolean;
10
+ canMoveUp: boolean;
11
+ canMoveDown: boolean;
12
+ usedChoiceTypeIds: Set<string>;
13
+ onUpdateSelectedNode: (patch: Partial<EditorNode>) => void;
14
+ onDeleteChoice: (choiceId: string) => void;
15
+ onMoveChoice?: (choiceId: string, direction: 'up' | 'down') => void;
16
+ onSetChoiceTarget: (choiceId: string, targetNodeId: string) => void;
17
+ onSetChoiceOutcome: (choiceId: string, outcome: string) => void;
18
+ onSetChoiceType?: (choiceId: string, typeId: string, defaultLabel?: string) => void;
19
+ choiceTypes?: ReadonlyArray<ChoiceTypeOption>;
20
+ outcomeOptions: ReadonlyArray<{
21
+ value: string;
22
+ label: string;
23
+ }>;
24
+ hideOutcomeField: boolean;
25
+ renderExtraChoiceFields?: (ctx: InspectorChoiceRenderContext) => ReactNode;
26
+ onFocusChoice?: (choiceId: string) => void;
27
+ }>): import("react").JSX.Element;
28
+ //# sourceMappingURL=ChoiceEditorCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChoiceEditorCard.d.ts","sourceRoot":"","sources":["../../../src/panels/inspector/ChoiceEditorCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAC;AAGpD,OAAO,EAGH,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EAClB,MAAM,mCAAmC,CAAC;AAI3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAE9E,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACrC,MAAM,EACN,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,GAChB,EAAE,QAAQ,CAAC;IACR,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC/B,oBAAoB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACpE,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpF,WAAW,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC9C,cAAc,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,GAAG,EAAE,4BAA4B,KAAK,SAAS,CAAC;IAC3E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC,+BAsJD"}
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Form } from 'react-bootstrap';
3
+ import { END_NODE_ID, getTransition, } from '@signalsafe/tree-spec-editor-core';
4
+ import { LIST_SELECTION_CLASS, LIST_SELECTION_TEXT_CLASS } from '../../lib/selectionStyles';
5
+ import { choiceActionIconProps } from './choiceActionIconProps';
6
+ export default function ChoiceEditorCard({ choice, tree, selectedNode, focusChoiceId, isPublished, canMoveUp, canMoveDown, usedChoiceTypeIds, onUpdateSelectedNode, onDeleteChoice, onMoveChoice, onSetChoiceTarget, onSetChoiceOutcome, onSetChoiceType, choiceTypes, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, onFocusChoice, }) {
7
+ const transition = getTransition(tree, selectedNode.id, choice.id);
8
+ const targetNodeId = transition?.toNodeId ?? '';
9
+ const outcomeValue = transition?.outcome ?? outcomeOptions[0]?.value ?? '';
10
+ const isFocused = focusChoiceId === choice.id;
11
+ const showOutcome = !hideOutcomeField && targetNodeId === END_NODE_ID;
12
+ const knownTypeIds = new Set((choiceTypes ?? []).map((type) => type.id));
13
+ const showTypeSelect = Boolean(choiceTypes?.length && onSetChoiceType);
14
+ const isCustomType = showTypeSelect && !knownTypeIds.has(choice.id);
15
+ return (_jsxs("div", { id: `choice-editor-${selectedNode.id}-${choice.id}`, className: `list-group-item py-3 px-3${isFocused ? ` ${LIST_SELECTION_CLASS} ${LIST_SELECTION_TEXT_CLASS}` : ''}`, onFocusCapture: () => onFocusChoice?.(choice.id), children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-end gap-2 mb-2", children: [_jsxs("div", { className: "d-flex flex-grow-1 min-w-0 gap-2", children: [showTypeSelect ? (_jsxs(Form.Group, { className: "mb-0 flex-fill min-w-0", children: [_jsx(Form.Label, { className: "mb-1", children: "Type" }), _jsxs(Form.Select, { value: isCustomType ? '__custom__' : choice.id, disabled: isPublished, "aria-label": `Choice type for ${choice.id}`, onChange: (event) => {
16
+ const nextTypeId = event.target.value;
17
+ if (nextTypeId && nextTypeId !== '__custom__') {
18
+ const type = choiceTypes.find((item) => item.id === nextTypeId);
19
+ onSetChoiceType(choice.id, nextTypeId, type?.label);
20
+ }
21
+ }, children: [choiceTypes.map((type) => (_jsx("option", { value: type.id, disabled: type.id !== choice.id && usedChoiceTypeIds.has(type.id), title: type.label, children: type.id }, type.id))), isCustomType ? (_jsxs("option", { value: "__custom__", children: [choice.id, " (custom)"] })) : null] })] })) : (_jsxs(Form.Group, { className: "mb-0 flex-fill min-w-0", children: [_jsx(Form.Label, { className: "mb-1", children: "Type" }), _jsx(Form.Control, { plaintext: true, readOnly: true, className: "text-uppercase fw-semibold", value: choice.id })] })), hideOutcomeField ? null : (_jsxs(Form.Group, { className: "mb-0 flex-fill min-w-0", children: [_jsx(Form.Label, { className: "mb-1", children: "Outcome" }), showOutcome ? (_jsx(Form.Select, { value: outcomeValue, disabled: isPublished, "aria-label": `Outcome for ${choice.id}`, onChange: (e) => onSetChoiceOutcome(choice.id, e.target.value), children: outcomeOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })) : (_jsx(Form.Select, { disabled: true, "aria-label": `Outcome for ${choice.id}`, value: "", children: _jsx("option", { value: "", children: "When Next is END" }) }))] }))] }), _jsxs("div", { className: "d-flex align-items-center gap-2 flex-shrink-0", children: [onMoveChoice ? (_jsxs(_Fragment, { children: [_jsx("i", { ...choiceActionIconProps(() => onMoveChoice(choice.id, 'up'), 'Move choice up', 'bi bi-chevron-up text-secondary', isPublished || !canMoveUp) }), _jsx("i", { ...choiceActionIconProps(() => onMoveChoice(choice.id, 'down'), 'Move choice down', 'bi bi-chevron-down text-secondary', isPublished || !canMoveDown) })] })) : null, _jsx("i", { ...choiceActionIconProps(() => onDeleteChoice(choice.id), 'Delete choice', 'bi bi-trash text-danger') })] })] }), _jsx(Form.Label, { className: "mt-0", children: "Label" }), _jsx(Form.Control, { value: choice.label, onChange: (event) => {
22
+ const updatedChoices = (selectedNode.choices ?? []).map((existingChoice) => existingChoice.id === choice.id
23
+ ? { ...existingChoice, label: event.target.value }
24
+ : existingChoice);
25
+ onUpdateSelectedNode({ choices: updatedChoices });
26
+ } }), _jsx(Form.Label, { className: "mt-2", children: "Next" }), _jsxs(Form.Select, { value: targetNodeId, onChange: (e) => onSetChoiceTarget(choice.id, e.target.value), children: [_jsx("option", { value: "", children: "(not connected)" }), Object.values(tree.nodes).map((n) => (_jsxs("option", { value: n.id, children: [n.id, " \u00B7 ", n.prompt?.slice(0, 40) || n.type] }, n.id))), _jsx("option", { value: END_NODE_ID, children: "END" })] }), renderExtraChoiceFields
27
+ ? renderExtraChoiceFields({
28
+ tree,
29
+ node: selectedNode,
30
+ choice,
31
+ transition,
32
+ isPublished,
33
+ onUpdateNode: onUpdateSelectedNode,
34
+ })
35
+ : null] }));
36
+ }
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { EditorNode, EditorTree } from '@signalsafe/tree-spec-editor-core';
3
+ import type { ChoiceTypeOption, InspectorChoiceRenderContext } from './types';
4
+ export default function ChoiceEditorList({ tree, selectedNode, focusChoiceId, isPublished, onAddChoice, onUpdateSelectedNode, onDeleteChoice, onMoveChoice, onSetChoiceTarget, onSetChoiceOutcome, onSetChoiceType, choiceTypes, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, onFocusChoice, }: Readonly<{
5
+ tree: EditorTree;
6
+ selectedNode: EditorNode;
7
+ focusChoiceId: string | null;
8
+ isPublished: boolean;
9
+ onAddChoice: () => void;
10
+ onUpdateSelectedNode: (patch: Partial<EditorNode>) => void;
11
+ onDeleteChoice: (choiceId: string) => void;
12
+ onMoveChoice?: (choiceId: string, direction: 'up' | 'down') => void;
13
+ onSetChoiceTarget: (choiceId: string, targetNodeId: string) => void;
14
+ onSetChoiceOutcome: (choiceId: string, outcome: string) => void;
15
+ onSetChoiceType?: (choiceId: string, typeId: string, defaultLabel?: string) => void;
16
+ choiceTypes?: ReadonlyArray<ChoiceTypeOption>;
17
+ outcomeOptions: ReadonlyArray<{
18
+ value: string;
19
+ label: string;
20
+ }>;
21
+ hideOutcomeField: boolean;
22
+ renderExtraChoiceFields?: (ctx: InspectorChoiceRenderContext) => ReactNode;
23
+ onFocusChoice?: (choiceId: string) => void;
24
+ }>): import("react").JSX.Element;
25
+ //# sourceMappingURL=ChoiceEditorList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChoiceEditorList.d.ts","sourceRoot":"","sources":["../../../src/panels/inspector/ChoiceEditorList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAIhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,4BAA4B,EAAE,MAAM,SAAS,CAAC;AAE9E,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,aAAa,GAChB,EAAE,QAAQ,CAAC;IACR,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,UAAU,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,oBAAoB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,KAAK,IAAI,CAAC;IACpE,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpF,WAAW,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC9C,cAAc,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,GAAG,EAAE,4BAA4B,KAAK,SAAS,CAAC;IAC3E,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,CAAC,+BAqED"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import PanelHeaderCollapseCarets from '../../lib/PanelHeaderCollapseCarets';
4
+ import ChoiceEditorCard from './ChoiceEditorCard';
5
+ export default function ChoiceEditorList({ tree, selectedNode, focusChoiceId, isPublished, onAddChoice, onUpdateSelectedNode, onDeleteChoice, onMoveChoice, onSetChoiceTarget, onSetChoiceOutcome, onSetChoiceType, choiceTypes, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, onFocusChoice, }) {
6
+ const choices = selectedNode.choices ?? [];
7
+ const [choicesExpanded, setChoicesExpanded] = useState(true);
8
+ const usedChoiceTypeIds = new Set(choices.map((choice) => choice.id));
9
+ return (_jsxs("div", { className: "card mt-3", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: "Choices" }), _jsx(PanelHeaderCollapseCarets, { expanded: choicesExpanded, onToggle: () => setChoicesExpanded((v) => !v) })] }), _jsx("div", { className: "d-flex align-items-center gap-1 flex-shrink-0", children: _jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Add choice", title: "Add choice", disabled: isPublished, onClick: onAddChoice, children: _jsx("i", { className: `bi bi-plus-lg action-icon${isPublished ? ' text-secondary opacity-50' : ' text-primary cursor-pointer'}`, "aria-hidden": true }) }) })] }), _jsx("div", { className: `card-body p-0${choicesExpanded ? '' : ' d-none'}`, "aria-hidden": !choicesExpanded, children: choices.length === 0 ? (_jsx("div", { className: "text-muted small py-3 px-3", children: _jsx("em", { children: "No choices" }) })) : (_jsx("div", { className: "list-group list-group-flush overflow-auto-max-h-320", children: choices.map((choice, index) => (_jsx(ChoiceEditorCard, { choice: choice, tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, canMoveUp: index > 0, canMoveDown: index < choices.length - 1, usedChoiceTypeIds: usedChoiceTypeIds, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onMoveChoice: onMoveChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, onSetChoiceType: onSetChoiceType, choiceTypes: choiceTypes, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields, onFocusChoice: onFocusChoice }, choice.id))) })) })] }));
10
+ }
@@ -0,0 +1,8 @@
1
+ import { type EditorNode } from '@signalsafe/tree-spec-editor-core';
2
+ export default function NodeTypeField({ selectedNode, isPublished, onUpdateSelectedNode, typeHelperText, }: Readonly<{
3
+ selectedNode: EditorNode;
4
+ isPublished: boolean;
5
+ onUpdateSelectedNode: (patch: Partial<EditorNode>) => void;
6
+ typeHelperText: string;
7
+ }>): import("react").JSX.Element;
8
+ //# sourceMappingURL=NodeTypeField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeTypeField.d.ts","sourceRoot":"","sources":["../../../src/panels/inspector/NodeTypeField.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEH,KAAK,UAAU,EAClB,MAAM,mCAAmC,CAAC;AAE3C,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAClC,YAAY,EACZ,WAAW,EACX,oBAAoB,EACpB,cAAc,GACjB,EAAE,QAAQ,CAAC;IACR,YAAY,EAAE,UAAU,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,oBAAoB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC3D,cAAc,EAAE,MAAM,CAAC;CAC1B,CAAC,+BAkCD"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Form } from 'react-bootstrap';
3
+ import { TREE_SPEC_NODE_TYPE_PRESETS, } from '@signalsafe/tree-spec-editor-core';
4
+ export default function NodeTypeField({ selectedNode, isPublished, onUpdateSelectedNode, typeHelperText, }) {
5
+ const isPreset = TREE_SPEC_NODE_TYPE_PRESETS.includes(selectedNode.type);
6
+ return (_jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Type" }), _jsxs(Form.Select, { value: isPreset ? selectedNode.type : '__custom__', onChange: (e) => {
7
+ const nextType = e.target.value;
8
+ if (nextType === '__custom__')
9
+ return;
10
+ onUpdateSelectedNode({ type: nextType });
11
+ }, disabled: isPublished, children: [TREE_SPEC_NODE_TYPE_PRESETS.map((t) => (_jsx("option", { value: t, children: t }, t))), _jsx("option", { value: "__custom__", children: "custom\u2026" })] }), isPreset ? null : (_jsx(Form.Control, { className: "mt-2", value: selectedNode.type, onChange: (e) => onUpdateSelectedNode({ type: e.target.value }), placeholder: "Enter custom type\u2026", disabled: isPublished })), _jsx("div", { className: "text-muted mt-1 font-size-12", children: typeHelperText })] }));
12
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { EditorNode, EditorTree } from '@signalsafe/tree-spec-editor-core';
3
+ import type { InspectorNodeRenderContext } from './types';
4
+ export default function RequiredNodeCard({ tree, selectedNode, isPublished, title, typeHelperText, onUpdateSelectedNode, onDeleteSelectedNode, renderExtraNodeFields, }: Readonly<{
5
+ tree: EditorTree;
6
+ selectedNode: EditorNode;
7
+ isPublished: boolean;
8
+ title: string;
9
+ typeHelperText: string;
10
+ onUpdateSelectedNode: (patch: Partial<EditorNode>) => void;
11
+ onDeleteSelectedNode?: () => void;
12
+ renderExtraNodeFields?: (ctx: InspectorNodeRenderContext) => ReactNode;
13
+ }>): import("react").JSX.Element;
14
+ //# sourceMappingURL=RequiredNodeCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequiredNodeCard.d.ts","sourceRoot":"","sources":["../../../src/panels/inspector/RequiredNodeCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAC;AAIpD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAIhF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAE1D,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,WAAW,EACX,KAAK,EACL,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,GACxB,EAAE,QAAQ,CAAC;IACR,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,UAAU,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,SAAS,CAAC;CAC1E,CAAC,+BA4DD"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Form } from 'react-bootstrap';
4
+ import PanelHeaderCollapseCarets from '../../lib/PanelHeaderCollapseCarets';
5
+ import NodeTypeField from './NodeTypeField';
6
+ export default function RequiredNodeCard({ tree, selectedNode, isPublished, title, typeHelperText, onUpdateSelectedNode, onDeleteSelectedNode, renderExtraNodeFields, }) {
7
+ const [requiredExpanded, setRequiredExpanded] = useState(true);
8
+ return (_jsxs("div", { className: "card", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: requiredExpanded, onToggle: () => setRequiredExpanded((v) => !v) })] }), onDeleteSelectedNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Delete node", title: "Delete node", disabled: isPublished, onClick: onDeleteSelectedNode, children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }), _jsxs("div", { className: `card-body${requiredExpanded ? '' : ' d-none'}`, "aria-hidden": !requiredExpanded, children: [_jsx(NodeTypeField, { selectedNode: selectedNode, isPublished: isPublished, onUpdateSelectedNode: onUpdateSelectedNode, typeHelperText: typeHelperText }), _jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Prompt" }), _jsx(Form.Control, { as: "textarea", rows: 4, value: selectedNode.prompt, onChange: (e) => onUpdateSelectedNode({ prompt: e.target.value }) })] }), renderExtraNodeFields
9
+ ? renderExtraNodeFields({
10
+ tree,
11
+ node: selectedNode,
12
+ isPublished,
13
+ onUpdateNode: onUpdateSelectedNode,
14
+ })
15
+ : null] })] }));
16
+ }