@flanksource/clicky-ui 0.2.12 → 0.2.14

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 (173) hide show
  1. package/dist/components/Combobox.cjs +47 -33
  2. package/dist/components/Combobox.cjs.map +1 -1
  3. package/dist/components/Combobox.d.ts +12 -0
  4. package/dist/components/Combobox.d.ts.map +1 -1
  5. package/dist/components/Combobox.js +48 -34
  6. package/dist/components/Combobox.js.map +1 -1
  7. package/dist/components/IconButton.cjs +26 -0
  8. package/dist/components/IconButton.cjs.map +1 -0
  9. package/dist/components/IconButton.d.ts +25 -0
  10. package/dist/components/IconButton.d.ts.map +1 -0
  11. package/dist/components/IconButton.js +26 -0
  12. package/dist/components/IconButton.js.map +1 -0
  13. package/dist/components/SecretKeySelector.cjs +151 -0
  14. package/dist/components/SecretKeySelector.cjs.map +1 -0
  15. package/dist/components/SecretKeySelector.d.ts +51 -0
  16. package/dist/components/SecretKeySelector.d.ts.map +1 -0
  17. package/dist/components/SecretKeySelector.js +151 -0
  18. package/dist/components/SecretKeySelector.js.map +1 -0
  19. package/dist/components/WorkloadPicker.cjs +144 -0
  20. package/dist/components/WorkloadPicker.cjs.map +1 -0
  21. package/dist/components/WorkloadPicker.d.ts +52 -0
  22. package/dist/components/WorkloadPicker.d.ts.map +1 -0
  23. package/dist/components/WorkloadPicker.js +144 -0
  24. package/dist/components/WorkloadPicker.js.map +1 -0
  25. package/dist/components/icon-menu-picker.cjs +83 -112
  26. package/dist/components/icon-menu-picker.cjs.map +1 -1
  27. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  28. package/dist/components/icon-menu-picker.js +85 -114
  29. package/dist/components/icon-menu-picker.js.map +1 -1
  30. package/dist/components.cjs +10 -0
  31. package/dist/components.cjs.map +1 -1
  32. package/dist/components.d.ts +3 -0
  33. package/dist/components.d.ts.map +1 -1
  34. package/dist/components.js +11 -1
  35. package/dist/components.js.map +1 -1
  36. package/dist/data/Clicky.cjs +1 -1
  37. package/dist/data/Clicky.cjs.map +1 -1
  38. package/dist/data/Clicky.js +1 -1
  39. package/dist/data/Clicky.js.map +1 -1
  40. package/dist/data/TimeseriesCoreBars.cjs +8 -1
  41. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  42. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  43. package/dist/data/TimeseriesCoreBars.js +8 -1
  44. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  45. package/dist/data/Version.cjs +42 -0
  46. package/dist/data/Version.cjs.map +1 -0
  47. package/dist/data/Version.d.ts +36 -0
  48. package/dist/data/Version.d.ts.map +1 -0
  49. package/dist/data/Version.js +42 -0
  50. package/dist/data/Version.js.map +1 -0
  51. package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
  52. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
  53. package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
  54. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
  55. package/dist/data/test-runner/TestDetailPanel.js +165 -0
  56. package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
  57. package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
  58. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
  59. package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
  60. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
  61. package/dist/data/test-runner/TestFailureDetail.js +78 -0
  62. package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
  63. package/dist/data/test-runner/TestFilterBar.cjs +108 -0
  64. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
  65. package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
  66. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
  67. package/dist/data/test-runner/TestFilterBar.js +108 -0
  68. package/dist/data/test-runner/TestFilterBar.js.map +1 -0
  69. package/dist/data/test-runner/TestRunSummary.cjs +81 -0
  70. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
  71. package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
  72. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
  73. package/dist/data/test-runner/TestRunSummary.js +81 -0
  74. package/dist/data/test-runner/TestRunSummary.js.map +1 -0
  75. package/dist/data/test-runner/TestRunner.cjs +143 -0
  76. package/dist/data/test-runner/TestRunner.cjs.map +1 -0
  77. package/dist/data/test-runner/TestRunner.d.ts +42 -0
  78. package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
  79. package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
  80. package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
  81. package/dist/data/test-runner/TestRunner.js +143 -0
  82. package/dist/data/test-runner/TestRunner.js.map +1 -0
  83. package/dist/data/test-runner/TestTree.cjs +42 -0
  84. package/dist/data/test-runner/TestTree.cjs.map +1 -0
  85. package/dist/data/test-runner/TestTree.d.ts +9 -0
  86. package/dist/data/test-runner/TestTree.d.ts.map +1 -0
  87. package/dist/data/test-runner/TestTree.js +42 -0
  88. package/dist/data/test-runner/TestTree.js.map +1 -0
  89. package/dist/data/test-runner/TestTreeNode.cjs +99 -0
  90. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
  91. package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
  92. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
  93. package/dist/data/test-runner/TestTreeNode.js +99 -0
  94. package/dist/data/test-runner/TestTreeNode.js.map +1 -0
  95. package/dist/data/test-runner/adapter.cjs +57 -0
  96. package/dist/data/test-runner/adapter.cjs.map +1 -0
  97. package/dist/data/test-runner/adapter.d.ts +60 -0
  98. package/dist/data/test-runner/adapter.d.ts.map +1 -0
  99. package/dist/data/test-runner/adapter.js +57 -0
  100. package/dist/data/test-runner/adapter.js.map +1 -0
  101. package/dist/data/test-runner/context.cjs +15 -0
  102. package/dist/data/test-runner/context.cjs.map +1 -0
  103. package/dist/data/test-runner/context.d.ts +40 -0
  104. package/dist/data/test-runner/context.d.ts.map +1 -0
  105. package/dist/data/test-runner/context.js +15 -0
  106. package/dist/data/test-runner/context.js.map +1 -0
  107. package/dist/data/test-runner/filterState.cjs +47 -0
  108. package/dist/data/test-runner/filterState.cjs.map +1 -0
  109. package/dist/data/test-runner/filterState.d.ts +14 -0
  110. package/dist/data/test-runner/filterState.d.ts.map +1 -0
  111. package/dist/data/test-runner/filterState.js +47 -0
  112. package/dist/data/test-runner/filterState.js.map +1 -0
  113. package/dist/data/test-runner/frameworkIcon.cjs +27 -0
  114. package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
  115. package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
  116. package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
  117. package/dist/data/test-runner/frameworkIcon.js +27 -0
  118. package/dist/data/test-runner/frameworkIcon.js.map +1 -0
  119. package/dist/data/test-runner/index.d.ts +14 -0
  120. package/dist/data/test-runner/index.d.ts.map +1 -0
  121. package/dist/data/test-runner/status.cjs +234 -0
  122. package/dist/data/test-runner/status.cjs.map +1 -0
  123. package/dist/data/test-runner/status.d.ts +52 -0
  124. package/dist/data/test-runner/status.d.ts.map +1 -0
  125. package/dist/data/test-runner/status.js +234 -0
  126. package/dist/data/test-runner/status.js.map +1 -0
  127. package/dist/data/test-runner/types.d.ts +148 -0
  128. package/dist/data/test-runner/types.d.ts.map +1 -0
  129. package/dist/data/version-info.cjs +26 -0
  130. package/dist/data/version-info.cjs.map +1 -0
  131. package/dist/data/version-info.d.ts +11 -0
  132. package/dist/data/version-info.d.ts.map +1 -0
  133. package/dist/data/version-info.js +26 -0
  134. package/dist/data/version-info.js.map +1 -0
  135. package/dist/data.cjs +49 -0
  136. package/dist/data.cjs.map +1 -1
  137. package/dist/data.d.ts +3 -0
  138. package/dist/data.d.ts.map +1 -1
  139. package/dist/data.js +50 -1
  140. package/dist/data.js.map +1 -1
  141. package/dist/index.cjs +59 -0
  142. package/dist/index.cjs.map +1 -1
  143. package/dist/index.js +60 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/overlay/DropdownMenu.cjs +57 -30
  146. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  147. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  148. package/dist/overlay/DropdownMenu.js +57 -30
  149. package/dist/overlay/DropdownMenu.js.map +1 -1
  150. package/dist/overlay/Modal.cjs +62 -6
  151. package/dist/overlay/Modal.cjs.map +1 -1
  152. package/dist/overlay/Modal.d.ts +19 -1
  153. package/dist/overlay/Modal.d.ts.map +1 -1
  154. package/dist/overlay/Modal.js +62 -6
  155. package/dist/overlay/Modal.js.map +1 -1
  156. package/dist/rpc/CommandForm.cjs +2 -2
  157. package/dist/rpc/CommandForm.cjs.map +1 -1
  158. package/dist/rpc/CommandForm.d.ts.map +1 -1
  159. package/dist/rpc/CommandForm.js +3 -3
  160. package/dist/rpc/CommandForm.js.map +1 -1
  161. package/dist/rpc/formMetadata.cjs +5 -4
  162. package/dist/rpc/formMetadata.cjs.map +1 -1
  163. package/dist/rpc/formMetadata.d.ts.map +1 -1
  164. package/dist/rpc/formMetadata.js +6 -5
  165. package/dist/rpc/formMetadata.js.map +1 -1
  166. package/dist/rpc/types.cjs +4 -0
  167. package/dist/rpc/types.cjs.map +1 -1
  168. package/dist/rpc/types.d.ts +10 -0
  169. package/dist/rpc/types.d.ts.map +1 -1
  170. package/dist/rpc/types.js +5 -1
  171. package/dist/rpc/types.js.map +1 -1
  172. package/dist/styles.css +1 -1
  173. package/package.json +3 -1
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const utils = require("../lib/utils.cjs");
6
6
  const Icon = require("../data/Icon.cjs");
7
+ const button = require("../components/button.cjs");
7
8
  const UiFullscreen = require("../icons/components/UiFullscreen.cjs");
8
9
  const UiClose = require("../icons/components/UiClose.cjs");
9
10
  const sizeClass = {
@@ -13,9 +14,16 @@ const sizeClass = {
13
14
  xl: "max-w-4xl max-h-[90vh]",
14
15
  full: "max-w-[95vw] max-h-[95vh]"
15
16
  };
17
+ const DEFAULT_CONFIRM = {
18
+ title: "Discard changes?",
19
+ message: "You have unsaved changes. Closing now will discard them.",
20
+ confirmLabel: "Discard",
21
+ cancelLabel: "Keep editing"
22
+ };
16
23
  function Modal({
17
24
  open,
18
25
  onClose,
26
+ confirmClose = false,
19
27
  title,
20
28
  size = "md",
21
29
  closeOnBackdrop = true,
@@ -29,17 +37,30 @@ function Modal({
29
37
  }) {
30
38
  const dialogRef = react.useRef(null);
31
39
  const [expanded, setExpanded] = react.useState(false);
40
+ const [confirming, setConfirming] = react.useState(false);
41
+ const requestClose = () => {
42
+ if (confirmClose) setConfirming(true);
43
+ else onClose();
44
+ };
32
45
  react.useEffect(() => {
33
- if (!open) setExpanded(false);
46
+ if (!open) {
47
+ setExpanded(false);
48
+ setConfirming(false);
49
+ }
34
50
  }, [open]);
51
+ react.useEffect(() => {
52
+ if (!confirmClose) setConfirming(false);
53
+ }, [confirmClose]);
35
54
  react.useEffect(() => {
36
55
  if (!open || !closeOnEsc) return;
37
56
  const onKey = (e) => {
38
- if (e.key === "Escape") onClose();
57
+ if (e.key !== "Escape") return;
58
+ if (confirming) setConfirming(false);
59
+ else requestClose();
39
60
  };
40
61
  document.addEventListener("keydown", onKey);
41
62
  return () => document.removeEventListener("keydown", onKey);
42
- }, [open, closeOnEsc, onClose]);
63
+ }, [open, closeOnEsc, confirming, confirmClose, onClose]);
43
64
  react.useEffect(() => {
44
65
  var _a;
45
66
  if (!open) return;
@@ -55,7 +76,7 @@ function Modal({
55
76
  "div",
56
77
  {
57
78
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40",
58
- onClick: closeOnBackdrop ? onClose : void 0,
79
+ onClick: closeOnBackdrop ? requestClose : void 0,
59
80
  role: "presentation",
60
81
  children: /* @__PURE__ */ jsxRuntime.jsxs(
61
82
  "div",
@@ -89,7 +110,7 @@ function Modal({
89
110
  "button",
90
111
  {
91
112
  type: "button",
92
- onClick: onClose,
113
+ onClick: requestClose,
93
114
  "aria-label": "Close",
94
115
  className: "text-muted-foreground hover:text-foreground",
95
116
  children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClose.UiClose })
@@ -97,12 +118,47 @@ function Modal({
97
118
  )
98
119
  ] }),
99
120
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto px-density-4 py-density-3", children }),
100
- footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-density-4 py-density-3 border-t border-border", children: footer })
121
+ footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-density-4 py-density-3 border-t border-border", children: footer }),
122
+ confirming && /* @__PURE__ */ jsxRuntime.jsx(
123
+ ConfirmClosePrompt,
124
+ {
125
+ options: confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose },
126
+ onConfirm: () => {
127
+ setConfirming(false);
128
+ onClose();
129
+ },
130
+ onCancel: () => setConfirming(false)
131
+ }
132
+ )
101
133
  ]
102
134
  }
103
135
  )
104
136
  }
105
137
  );
106
138
  }
139
+ function ConfirmClosePrompt({
140
+ options,
141
+ onConfirm,
142
+ onCancel
143
+ }) {
144
+ return /* @__PURE__ */ jsxRuntime.jsx(
145
+ "div",
146
+ {
147
+ className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4",
148
+ role: "alertdialog",
149
+ "aria-modal": "true",
150
+ "aria-label": typeof options.title === "string" ? options.title : "Confirm close",
151
+ onClick: (e) => e.stopPropagation(),
152
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl", children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: options.title }),
154
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-density-2 text-sm text-muted-foreground", children: options.message }),
155
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-density-4 flex justify-end gap-density-2", children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "ghost", size: "sm", onClick: onCancel, children: options.cancelLabel }),
157
+ /* @__PURE__ */ jsxRuntime.jsx(button.Button, { variant: "destructive", size: "sm", onClick: onConfirm, children: options.confirmLabel })
158
+ ] })
159
+ ] })
160
+ }
161
+ );
162
+ }
107
163
  exports.Modal = Modal;
108
164
  //# sourceMappingURL=Modal.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.cjs","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nexport function Modal({\n open,\n onClose,\n title,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const [expanded, setExpanded] = useState(false);\n\n useEffect(() => {\n if (!open) setExpanded(false);\n }, [open]);\n\n useEffect(() => {\n if (!open || !closeOnEsc) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40\"\n onClick={closeOnBackdrop ? onClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || headerSlot || expandable || !hideClose) && (\n <div className=\"flex items-center gap-density-2 px-density-4 py-density-3 border-b border-border\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n </div>\n </div>\n );\n}\n"],"names":["useRef","useState","useEffect","_a","jsx","jsxs","cn","Icon","UiFullscreenFilled","UiFullscreen","UiClose"],"mappings":"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"}
1
+ {"version":3,"file":"Modal.cjs","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { Button } from \"../components/button\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Copy for the discard-confirmation prompt shown before a guarded close. */\nexport type ConfirmCloseOptions = {\n /** Heading of the confirmation prompt. */\n title?: ReactNode;\n /** Body explaining what is lost on close. */\n message?: ReactNode;\n /** Label of the button that proceeds with closing. */\n confirmLabel?: string;\n /** Label of the button that dismisses the prompt and keeps the modal open. */\n cancelLabel?: string;\n};\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /**\n * Guard every close path (close button, Escape, backdrop) behind a discard\n * confirmation. `true` uses the default copy; pass options to customise it.\n * `onClose` only fires once the user confirms. Set this while the modal holds\n * unsaved edits and clear it (or pass `false`) once there is nothing to lose.\n */\n confirmClose?: boolean | ConfirmCloseOptions;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nconst DEFAULT_CONFIRM: Required<ConfirmCloseOptions> = {\n title: \"Discard changes?\",\n message: \"You have unsaved changes. Closing now will discard them.\",\n confirmLabel: \"Discard\",\n cancelLabel: \"Keep editing\",\n};\n\nexport function Modal({\n open,\n onClose,\n confirmClose = false,\n title,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const [expanded, setExpanded] = useState(false);\n // When confirmClose is active, a close request opens this prompt instead of\n // closing outright; onClose only fires once the user confirms the discard.\n const [confirming, setConfirming] = useState(false);\n\n // requestClose is the single entry point for every close affordance. With the\n // guard on, it surfaces the confirmation; otherwise it closes immediately.\n const requestClose = () => {\n if (confirmClose) setConfirming(true);\n else onClose();\n };\n\n useEffect(() => {\n if (!open) {\n setExpanded(false);\n setConfirming(false);\n }\n }, [open]);\n\n // A close guard that disappears mid-prompt (changes saved/reverted) should\n // not leave a stale confirmation dialog up.\n useEffect(() => {\n if (!confirmClose) setConfirming(false);\n }, [confirmClose]);\n\n useEffect(() => {\n if (!open || !closeOnEsc) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n // While the prompt is up, Escape dismisses the prompt rather than the modal.\n if (confirming) setConfirming(false);\n else requestClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, confirming, confirmClose, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40\"\n onClick={closeOnBackdrop ? requestClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || headerSlot || expandable || !hideClose) && (\n <div className=\"flex items-center gap-density-2 px-density-4 py-density-3 border-b border-border\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={requestClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n {confirming && (\n <ConfirmClosePrompt\n options={confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose }}\n onConfirm={() => {\n setConfirming(false);\n onClose();\n }}\n onCancel={() => setConfirming(false)}\n />\n )}\n </div>\n </div>\n );\n}\n\nfunction ConfirmClosePrompt({\n options,\n onConfirm,\n onCancel,\n}: {\n options: Required<ConfirmCloseOptions>;\n onConfirm: () => void;\n onCancel: () => void;\n}) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label={typeof options.title === \"string\" ? options.title : \"Confirm close\"}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl\">\n <h3 className=\"text-sm font-semibold\">{options.title}</h3>\n <p className=\"mt-density-2 text-sm text-muted-foreground\">{options.message}</p>\n <div className=\"mt-density-4 flex justify-end gap-density-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {options.cancelLabel}\n </Button>\n <Button variant=\"destructive\" size=\"sm\" onClick={onConfirm}>\n {options.confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["useRef","useState","useEffect","_a","jsx","jsxs","cn","Icon","UiFullscreenFilled","UiFullscreen","UiClose","Button"],"mappings":"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"}
@@ -1,10 +1,28 @@
1
1
  import { ReactNode } from 'react';
2
2
  export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
3
+ /** Copy for the discard-confirmation prompt shown before a guarded close. */
4
+ export type ConfirmCloseOptions = {
5
+ /** Heading of the confirmation prompt. */
6
+ title?: ReactNode;
7
+ /** Body explaining what is lost on close. */
8
+ message?: ReactNode;
9
+ /** Label of the button that proceeds with closing. */
10
+ confirmLabel?: string;
11
+ /** Label of the button that dismisses the prompt and keeps the modal open. */
12
+ cancelLabel?: string;
13
+ };
3
14
  export type ModalProps = {
4
15
  /** Controls whether the modal is mounted. */
5
16
  open: boolean;
6
17
  /** Called when the modal requests to close. */
7
18
  onClose: () => void;
19
+ /**
20
+ * Guard every close path (close button, Escape, backdrop) behind a discard
21
+ * confirmation. `true` uses the default copy; pass options to customise it.
22
+ * `onClose` only fires once the user confirms. Set this while the modal holds
23
+ * unsaved edits and clear it (or pass `false`) once there is nothing to lose.
24
+ */
25
+ confirmClose?: boolean | ConfirmCloseOptions;
8
26
  /** Header title and accessible dialog label when it is a string. */
9
27
  title?: ReactNode;
10
28
  /** Width/height preset for the dialog. */
@@ -26,5 +44,5 @@ export type ModalProps = {
26
44
  /** Modal body content. */
27
45
  children: ReactNode;
28
46
  };
29
- export declare function Modal({ open, onClose, title, size, closeOnBackdrop, closeOnEsc, hideClose, expandable, className, headerSlot, footer, children, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
47
+ export declare function Modal({ open, onClose, confirmClose, title, size, closeOnBackdrop, closeOnEsc, hideClose, expandable, className, headerSlot, footer, children, }: ModalProps): import("react/jsx-runtime").JSX.Element | null;
30
48
  //# sourceMappingURL=Modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/overlay/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpE,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG;IACvB,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oCAAoC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,uDAAuD;IACvD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAUF,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,KAAK,EACL,IAAW,EACX,eAAsB,EACtB,UAAiB,EACjB,SAAiB,EACjB,UAAiB,EACjB,SAAS,EACT,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE,UAAU,kDAgFZ"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/overlay/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMpE,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAE3D,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG;IAChC,0CAA0C;IAC1C,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,6CAA6C;IAC7C,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAC;IAC7C,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oCAAoC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qCAAqC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,uDAAuD;IACvD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,0BAA0B;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAiBF,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,YAAoB,EACpB,KAAK,EACL,IAAW,EACX,eAAsB,EACtB,UAAiB,EACjB,SAAiB,EACjB,UAAiB,EACjB,SAAS,EACT,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE,UAAU,kDAgHZ"}
@@ -2,6 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState, useEffect } from "react";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { Icon } from "../data/Icon.js";
5
+ import { Button } from "../components/button.js";
5
6
  import { UiFullscreenFilled, UiFullscreen } from "../icons/components/UiFullscreen.js";
6
7
  import { UiClose } from "../icons/components/UiClose.js";
7
8
  const sizeClass = {
@@ -11,9 +12,16 @@ const sizeClass = {
11
12
  xl: "max-w-4xl max-h-[90vh]",
12
13
  full: "max-w-[95vw] max-h-[95vh]"
13
14
  };
15
+ const DEFAULT_CONFIRM = {
16
+ title: "Discard changes?",
17
+ message: "You have unsaved changes. Closing now will discard them.",
18
+ confirmLabel: "Discard",
19
+ cancelLabel: "Keep editing"
20
+ };
14
21
  function Modal({
15
22
  open,
16
23
  onClose,
24
+ confirmClose = false,
17
25
  title,
18
26
  size = "md",
19
27
  closeOnBackdrop = true,
@@ -27,17 +35,30 @@ function Modal({
27
35
  }) {
28
36
  const dialogRef = useRef(null);
29
37
  const [expanded, setExpanded] = useState(false);
38
+ const [confirming, setConfirming] = useState(false);
39
+ const requestClose = () => {
40
+ if (confirmClose) setConfirming(true);
41
+ else onClose();
42
+ };
30
43
  useEffect(() => {
31
- if (!open) setExpanded(false);
44
+ if (!open) {
45
+ setExpanded(false);
46
+ setConfirming(false);
47
+ }
32
48
  }, [open]);
49
+ useEffect(() => {
50
+ if (!confirmClose) setConfirming(false);
51
+ }, [confirmClose]);
33
52
  useEffect(() => {
34
53
  if (!open || !closeOnEsc) return;
35
54
  const onKey = (e) => {
36
- if (e.key === "Escape") onClose();
55
+ if (e.key !== "Escape") return;
56
+ if (confirming) setConfirming(false);
57
+ else requestClose();
37
58
  };
38
59
  document.addEventListener("keydown", onKey);
39
60
  return () => document.removeEventListener("keydown", onKey);
40
- }, [open, closeOnEsc, onClose]);
61
+ }, [open, closeOnEsc, confirming, confirmClose, onClose]);
41
62
  useEffect(() => {
42
63
  var _a;
43
64
  if (!open) return;
@@ -53,7 +74,7 @@ function Modal({
53
74
  "div",
54
75
  {
55
76
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40",
56
- onClick: closeOnBackdrop ? onClose : void 0,
77
+ onClick: closeOnBackdrop ? requestClose : void 0,
57
78
  role: "presentation",
58
79
  children: /* @__PURE__ */ jsxs(
59
80
  "div",
@@ -87,7 +108,7 @@ function Modal({
87
108
  "button",
88
109
  {
89
110
  type: "button",
90
- onClick: onClose,
111
+ onClick: requestClose,
91
112
  "aria-label": "Close",
92
113
  className: "text-muted-foreground hover:text-foreground",
93
114
  children: /* @__PURE__ */ jsx(Icon, { icon: UiClose })
@@ -95,13 +116,48 @@ function Modal({
95
116
  )
96
117
  ] }),
97
118
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto px-density-4 py-density-3", children }),
98
- footer && /* @__PURE__ */ jsx("div", { className: "px-density-4 py-density-3 border-t border-border", children: footer })
119
+ footer && /* @__PURE__ */ jsx("div", { className: "px-density-4 py-density-3 border-t border-border", children: footer }),
120
+ confirming && /* @__PURE__ */ jsx(
121
+ ConfirmClosePrompt,
122
+ {
123
+ options: confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose },
124
+ onConfirm: () => {
125
+ setConfirming(false);
126
+ onClose();
127
+ },
128
+ onCancel: () => setConfirming(false)
129
+ }
130
+ )
99
131
  ]
100
132
  }
101
133
  )
102
134
  }
103
135
  );
104
136
  }
137
+ function ConfirmClosePrompt({
138
+ options,
139
+ onConfirm,
140
+ onCancel
141
+ }) {
142
+ return /* @__PURE__ */ jsx(
143
+ "div",
144
+ {
145
+ className: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4",
146
+ role: "alertdialog",
147
+ "aria-modal": "true",
148
+ "aria-label": typeof options.title === "string" ? options.title : "Confirm close",
149
+ onClick: (e) => e.stopPropagation(),
150
+ children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl", children: [
151
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: options.title }),
152
+ /* @__PURE__ */ jsx("p", { className: "mt-density-2 text-sm text-muted-foreground", children: options.message }),
153
+ /* @__PURE__ */ jsxs("div", { className: "mt-density-4 flex justify-end gap-density-2", children: [
154
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onCancel, children: options.cancelLabel }),
155
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: onConfirm, children: options.confirmLabel })
156
+ ] })
157
+ ] })
158
+ }
159
+ );
160
+ }
105
161
  export {
106
162
  Modal
107
163
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nexport function Modal({\n open,\n onClose,\n title,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const [expanded, setExpanded] = useState(false);\n\n useEffect(() => {\n if (!open) setExpanded(false);\n }, [open]);\n\n useEffect(() => {\n if (!open || !closeOnEsc) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40\"\n onClick={closeOnBackdrop ? onClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || headerSlot || expandable || !hideClose) && (\n <div className=\"flex items-center gap-density-2 px-density-4 py-density-3 border-b border-border\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n </div>\n </div>\n );\n}\n"],"names":["_a"],"mappings":";;;;;;AAkCA,MAAM,YAAuC;AAAA,EAC3C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AACR;AAEO,SAAS,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAe;AACb,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,YAAU,MAAM;AACd,QAAI,CAAC,KAAM,aAAY,KAAK;AAAA,EAC9B,GAAG,CAAC,IAAI,CAAC;AAET,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ,CAAC,WAAY;AAC1B,UAAM,QAAQ,CAAC,MAAqB;AAClC,UAAI,EAAE,QAAQ,SAAU,SAAA;AAAA,IAC1B;AACA,aAAS,iBAAiB,WAAW,KAAK;AAC1C,WAAO,MAAM,SAAS,oBAAoB,WAAW,KAAK;AAAA,EAC5D,GAAG,CAAC,MAAM,YAAY,OAAO,CAAC;AAE9B,YAAU,MAAM;;AACd,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,SAAS;AACtB,oBAAU,YAAV,mBAAmB;AACnB,WAAO,MAAA;;AAAM,cAAAA,MAAA,6BAAM,UAAN,gBAAAA,IAAA;AAAA;AAAA,EACf,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,CAAC,KAAM,QAAO;AAElB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACV,SAAS,kBAAkB,UAAU;AAAA,MACrC,MAAK;AAAA,MAEL,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,UAAU;AAAA,UACV,MAAK;AAAA,UACL,cAAW;AAAA,UACX,cAAY,OAAO,UAAU,WAAW,QAAQ;AAAA,UAChD,WAAW;AAAA,YACT;AAAA,YACA,WAAW,UAAU,OAAO,UAAU,IAAI;AAAA,YAC1C;AAAA,UAAA;AAAA,UAEF,SAAS,CAAC,MAAM,EAAE,gBAAA;AAAA,UAEhB,UAAA;AAAA,aAAA,SAAS,cAAc,cAAc,CAAC,cACtC,qBAAC,OAAA,EAAI,WAAU,oFACZ,UAAA;AAAA,cAAA,QACC,oBAAC,QAAG,WAAU,gCAAgC,iBAAM,IAEpD,oBAAC,QAAA,EAAK,WAAU,SAAA,CAAS;AAAA,cAE1B;AAAA,cACA,cACC;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;AAAA,kBACpC,cAAY,WAAW,iBAAiB;AAAA,kBACxC,WAAU;AAAA,kBAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,WAAW,qBAAqB,aAAA,CAAc;AAAA,gBAAA;AAAA,cAAA;AAAA,cAG7D,CAAC,aACA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAS;AAAA,kBACT,cAAW;AAAA,kBACX,WAAU;AAAA,kBAEV,UAAA,oBAAC,MAAA,EAAK,MAAM,QAAA,CAAS;AAAA,gBAAA;AAAA,cAAA;AAAA,YACvB,GAEJ;AAAA,YAEF,oBAAC,OAAA,EAAI,WAAU,kDAAkD,SAAA,CAAS;AAAA,YACzE,UAAU,oBAAC,OAAA,EAAI,WAAU,oDAAoD,UAAA,OAAA,CAAO;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACvF;AAAA,EAAA;AAGN;"}
1
+ {"version":3,"file":"Modal.js","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { Button } from \"../components/button\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Copy for the discard-confirmation prompt shown before a guarded close. */\nexport type ConfirmCloseOptions = {\n /** Heading of the confirmation prompt. */\n title?: ReactNode;\n /** Body explaining what is lost on close. */\n message?: ReactNode;\n /** Label of the button that proceeds with closing. */\n confirmLabel?: string;\n /** Label of the button that dismisses the prompt and keeps the modal open. */\n cancelLabel?: string;\n};\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /**\n * Guard every close path (close button, Escape, backdrop) behind a discard\n * confirmation. `true` uses the default copy; pass options to customise it.\n * `onClose` only fires once the user confirms. Set this while the modal holds\n * unsaved edits and clear it (or pass `false`) once there is nothing to lose.\n */\n confirmClose?: boolean | ConfirmCloseOptions;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nconst DEFAULT_CONFIRM: Required<ConfirmCloseOptions> = {\n title: \"Discard changes?\",\n message: \"You have unsaved changes. Closing now will discard them.\",\n confirmLabel: \"Discard\",\n cancelLabel: \"Keep editing\",\n};\n\nexport function Modal({\n open,\n onClose,\n confirmClose = false,\n title,\n size = \"md\",\n closeOnBackdrop = true,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const [expanded, setExpanded] = useState(false);\n // When confirmClose is active, a close request opens this prompt instead of\n // closing outright; onClose only fires once the user confirms the discard.\n const [confirming, setConfirming] = useState(false);\n\n // requestClose is the single entry point for every close affordance. With the\n // guard on, it surfaces the confirmation; otherwise it closes immediately.\n const requestClose = () => {\n if (confirmClose) setConfirming(true);\n else onClose();\n };\n\n useEffect(() => {\n if (!open) {\n setExpanded(false);\n setConfirming(false);\n }\n }, [open]);\n\n // A close guard that disappears mid-prompt (changes saved/reverted) should\n // not leave a stale confirmation dialog up.\n useEffect(() => {\n if (!confirmClose) setConfirming(false);\n }, [confirmClose]);\n\n useEffect(() => {\n if (!open || !closeOnEsc) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n // While the prompt is up, Escape dismisses the prompt rather than the modal.\n if (confirming) setConfirming(false);\n else requestClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, confirming, confirmClose, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/40\"\n onClick={closeOnBackdrop ? requestClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || headerSlot || expandable || !hideClose) && (\n <div className=\"flex items-center gap-density-2 px-density-4 py-density-3 border-b border-border\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={requestClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n {confirming && (\n <ConfirmClosePrompt\n options={confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose }}\n onConfirm={() => {\n setConfirming(false);\n onClose();\n }}\n onCancel={() => setConfirming(false)}\n />\n )}\n </div>\n </div>\n );\n}\n\nfunction ConfirmClosePrompt({\n options,\n onConfirm,\n onCancel,\n}: {\n options: Required<ConfirmCloseOptions>;\n onConfirm: () => void;\n onCancel: () => void;\n}) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label={typeof options.title === \"string\" ? options.title : \"Confirm close\"}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl\">\n <h3 className=\"text-sm font-semibold\">{options.title}</h3>\n <p className=\"mt-density-2 text-sm text-muted-foreground\">{options.message}</p>\n <div className=\"mt-density-4 flex justify-end gap-density-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {options.cancelLabel}\n </Button>\n <Button variant=\"destructive\" size=\"sm\" onClick={onConfirm}>\n {options.confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
@@ -78,7 +78,7 @@ function ParameterField({
78
78
  {
79
79
  id: fieldId,
80
80
  value,
81
- placeholder: param.description || param.name,
81
+ placeholder: types.parameterPlaceholder(param) || param.name,
82
82
  onChange
83
83
  }
84
84
  ) });
@@ -159,7 +159,7 @@ function ParameterField({
159
159
  value,
160
160
  className: inputClassName,
161
161
  onChange: (event) => onChange(event.target.value),
162
- placeholder: (schema == null ? void 0 : schema.default) != null ? String(schema.default) : param.description || param.name
162
+ placeholder: (schema == null ? void 0 : schema.default) != null ? String(schema.default) : types.parameterPlaceholder(param) || param.name
163
163
  }
164
164
  ) });
165
165
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CommandForm.cjs","sources":["../../src/rpc/CommandForm.tsx"],"sourcesContent":["import { useReducer, type FormEvent, type KeyboardEvent, type ReactNode } from \"react\";\nimport { Button } from \"../components/button\";\nimport { Icon } from \"../data/Icon\";\nimport { UiClose } from \"../icons\";\nimport { isPositionalParam, type OpenAPIParameter } from \"./types\";\n\nexport type CommandFormProps = {\n parameters: OpenAPIParameter[];\n onExecute: (params: Record<string, string>, headers: Record<string, string>) => void;\n isPending: boolean;\n method: string;\n path: string;\n accept: string;\n initialValues?: Record<string, string>;\n};\n\ntype FormState = Record<string, string>;\ntype FormAction = { name: string; value: string };\n\nfunction formReducer(state: FormState, action: FormAction): FormState {\n return { ...state, [action.name]: action.value };\n}\n\nexport function CommandForm({\n parameters,\n onExecute,\n isPending,\n method: _method,\n path,\n accept,\n initialValues,\n}: CommandFormProps) {\n const [values, dispatch] = useReducer(\n formReducer,\n { parameters, path, initialValues },\n ({ parameters: params, path: formPath, initialValues: overrides }) =>\n buildInitialState(normalizeParameters(params, formPath), formPath, overrides),\n );\n\n const formParameters = normalizeParameters(parameters, path);\n const visibleParams = formParameters.filter((p) => !(p.in === \"path\" && initialValues?.[p.name]));\n const positionalNames = new Set(formParameters.filter(isPositionalParam).map((p) => p.name));\n const inlineLayout = visibleParams.length >= INLINE_LAYOUT_THRESHOLD;\n\n function handleSubmit(event: FormEvent) {\n event.preventDefault();\n const params: Record<string, string> = {};\n const args: string[] = [];\n for (const param of formParameters) {\n const value = submitValue(param, values[param.name]);\n if (value == null) continue;\n if (positionalNames.has(param.name)) {\n args.push(...splitArgsValue(value));\n } else {\n params[param.name] = value;\n }\n }\n if (args.length > 0) params.args = args.join(\",\");\n onExecute(params, { Accept: accept });\n }\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {visibleParams.length > 0 && (\n <div className={inlineLayout ? \"grid gap-2\" : \"grid gap-4\"}>\n {visibleParams.map((param) => (\n <ParameterField\n key={param.name}\n param={param}\n value={values[param.name] || \"\"}\n inline={inlineLayout}\n onChange={(value) => dispatch({ name: param.name, value })}\n />\n ))}\n </div>\n )}\n\n <div\n className={\n inlineLayout\n ? \"sticky bottom-0 z-10 -mx-2 flex justify-end border-t border-border bg-background/95 px-2 py-3 backdrop-blur-sm\"\n : \"flex justify-end\"\n }\n >\n <Button type=\"submit\" disabled={isPending}>\n {isPending ? \"Executing...\" : \"Execute\"}\n </Button>\n </div>\n </form>\n );\n}\n\nconst INLINE_LAYOUT_THRESHOLD = 6;\n\nfunction ParameterField({\n param,\n value,\n inline,\n onChange,\n}: {\n param: OpenAPIParameter;\n value: string;\n inline: boolean;\n onChange: (value: string) => void;\n}) {\n const schema = param.schema;\n const fieldId = `param-${param.name}`;\n\n if (isMultiValueParam(param)) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <TagInput\n id={fieldId}\n value={value}\n placeholder={param.description || param.name}\n onChange={onChange}\n />\n </FieldWrapper>\n );\n }\n\n if (schema?.enum) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <select\n id={fieldId}\n value={value}\n className={inputClassName}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">Select {param.name}</option>\n {schema.enum.map((v) => (\n <option key={String(v)} value={String(v)}>\n {String(v)}\n </option>\n ))}\n </select>\n </FieldWrapper>\n );\n }\n\n if (schema?.type === \"boolean\") {\n const checkbox = (\n <input\n id={fieldId}\n type=\"checkbox\"\n className=\"h-4 w-4 accent-primary\"\n checked={value === \"true\"}\n onChange={(event) => onChange(event.target.checked ? \"true\" : \"false\")}\n />\n );\n if (inline) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline>\n <div className=\"flex h-9 items-center\">{checkbox}</div>\n </FieldWrapper>\n );\n }\n return (\n <div className=\"flex items-center gap-2\">\n {checkbox}\n <label htmlFor={fieldId} className=\"text-sm font-medium\">\n {param.name}\n {param.required && <span className=\"text-destructive\"> *</span>}\n </label>\n {param.description && (\n <span className=\"text-xs text-muted-foreground\">{param.description}</span>\n )}\n </div>\n );\n }\n\n if (isDateParam(param)) {\n const dateTime = schema?.format === \"date-time\";\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <div className=\"flex gap-2\">\n <input\n id={fieldId}\n type={dateTime ? \"datetime-local\" : \"date\"}\n value={dateInputValue(value, dateTime)}\n className={inputClassName}\n onChange={(event) => onChange(dateOutputValue(event.target.value, dateTime))}\n />\n {value && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Clear ${param.name}`}\n onClick={() => onChange(\"\")}\n >\n <Icon icon={UiClose} />\n </Button>\n )}\n </div>\n </FieldWrapper>\n );\n }\n\n const inputType = schema?.type === \"integer\" || schema?.type === \"number\" ? \"number\" : \"text\";\n\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <input\n id={fieldId}\n type={inputType}\n value={value}\n className={inputClassName}\n onChange={(event) => onChange(event.target.value)}\n placeholder={\n schema?.default != null ? String(schema.default) : param.description || param.name\n }\n />\n </FieldWrapper>\n );\n}\n\nfunction TagInput({\n id,\n value,\n placeholder,\n onChange,\n}: {\n id: string;\n value: string;\n placeholder: string;\n onChange: (value: string) => void;\n}) {\n const tags = parseTags(value);\n\n function commit(raw: string, input: HTMLInputElement) {\n const next = raw\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n if (next.length === 0) return;\n onChange(serializeTags([...tags, ...next]));\n input.value = \"\";\n }\n\n function remove(index: number) {\n onChange(serializeTags(tags.filter((_, i) => i !== index)));\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n const input = event.currentTarget;\n if (event.key === \"Enter\" || event.key === \",\") {\n event.preventDefault();\n commit(input.value, input);\n return;\n }\n if (event.key === \"Backspace\" && input.value === \"\" && tags.length > 0) {\n remove(tags.length - 1);\n }\n }\n\n return (\n <div className=\"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-2 py-1 shadow-sm\">\n {tags.map((tag, index) => (\n <span\n key={`${tag}-${index}`}\n className=\"inline-flex h-6 max-w-full items-center gap-1 rounded-md bg-muted px-2 text-xs\"\n >\n <span className=\"truncate\">{tag}</span>\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-foreground\"\n aria-label={`Remove ${tag}`}\n onClick={() => remove(index)}\n >\n <Icon icon={UiClose} />\n </button>\n </span>\n ))}\n <input\n id={id}\n className=\"min-w-32 flex-1 bg-transparent px-1 py-1 text-sm outline-none\"\n placeholder={tags.length === 0 ? placeholder : \"\"}\n onKeyDown={handleKeyDown}\n onBlur={(event) => commit(event.currentTarget.value, event.currentTarget)}\n />\n </div>\n );\n}\n\nfunction FieldWrapper({\n param,\n fieldId,\n inline,\n children,\n}: {\n param: OpenAPIParameter;\n fieldId: string;\n inline?: boolean;\n children: ReactNode;\n}) {\n const label = (\n <label htmlFor={fieldId} className=\"text-sm font-medium\">\n {param.name}\n {param.required && <span className=\"text-destructive\"> *</span>}\n </label>\n );\n\n if (inline) {\n return (\n <div className=\"grid grid-cols-[10rem_1fr] items-start gap-x-3 gap-y-0.5\">\n <div className=\"flex h-9 items-center\">{label}</div>\n <div className=\"min-w-0\">{children}</div>\n {param.description && (\n <p className=\"col-start-2 text-xs text-muted-foreground\">{param.description}</p>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-1.5\">\n {label}\n {children}\n {param.description && <p className=\"text-xs text-muted-foreground\">{param.description}</p>}\n </div>\n );\n}\n\nexport function normalizeParameters(\n parameters: OpenAPIParameter[],\n path: string,\n): OpenAPIParameter[] {\n const pathParams = pathParamNames(path);\n const firstPathParam = pathParams[0];\n const seen = new Set(parameters.map((param) => param.name));\n const normalized = parameters.filter(\n (param) => !(param.name === \"args\" && firstPathParam != null && !seen.has(firstPathParam)),\n );\n for (const name of pathParams) {\n if (seen.has(name)) continue;\n normalized.unshift({\n name,\n in: \"path\",\n required: true,\n description: \"Path parameter\",\n schema: { type: \"string\" },\n });\n }\n return normalized;\n}\n\nexport function pathParamNames(path: string): string[] {\n return [...path.matchAll(/\\{([^}]+)\\}/g)]\n .map((match) => match[1])\n .filter((name): name is string => Boolean(name));\n}\n\nfunction buildInitialState(\n parameters: OpenAPIParameter[],\n path: string,\n overrides?: Record<string, string>,\n): FormState {\n const state: FormState = {};\n for (const p of parameters) {\n state[p.name] = initialParamValue(p, overrides?.[p.name]);\n }\n for (const name of pathParamNames(path)) {\n if (state[name] == null) {\n state[name] = overrides?.[name] ?? \"\";\n }\n }\n return state;\n}\n\nfunction initialParamValue(param: OpenAPIParameter, override: string | undefined): string {\n if (override != null) return sanitizeInitialValue(param, override);\n const value = param.schema?.default;\n if (value == null) return \"\";\n return sanitizeInitialValue(param, String(value));\n}\n\nfunction sanitizeInitialValue(param: OpenAPIParameter, value: string): string {\n const trimmed = value.trim();\n if (trimmed === \"[]\" || trimmed === \"null\") return \"\";\n if (isDateParam(param) && isZeroDate(trimmed)) return \"\";\n return value;\n}\n\nexport function submitValue(param: OpenAPIParameter, value: string | undefined): string | null {\n const trimmed = (value ?? \"\").trim();\n if (!trimmed || trimmed === \"[]\" || trimmed === \"null\") return null;\n if (isDateParam(param) && isZeroDate(trimmed)) return null;\n return trimmed;\n}\n\nfunction splitArgsValue(value: string): string[] {\n try {\n const parsed = JSON.parse(value);\n if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);\n } catch {\n // Use comma splitting below.\n }\n return value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction isMultiValueParam(param: OpenAPIParameter): boolean {\n const text = `${param.name} ${param.description ?? \"\"}`.toLowerCase();\n return (\n param.schema?.type === \"array\" ||\n String(param.schema?.default ?? \"\") === \"[]\" ||\n text.includes(\"repeatable\")\n );\n}\n\nfunction parseTags(value: string): string[] {\n const trimmed = value.trim();\n if (!trimmed || trimmed === \"[]\" || trimmed === \"null\") return [];\n try {\n const parsed = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);\n } catch {\n // Fall back to comma-delimited tags below.\n }\n return trimmed\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction serializeTags(tags: string[]): string {\n return tags.join(\",\");\n}\n\nfunction isDateParam(param: OpenAPIParameter): boolean {\n const format = param.schema?.format?.toLowerCase();\n const text = `${param.name} ${param.description ?? \"\"}`.toLowerCase();\n return format === \"date\" || format === \"date-time\" || text.includes(\"date\");\n}\n\nfunction isZeroDate(value: string): boolean {\n return value.startsWith(\"0001-01-01\") || value === \"0001-01-01\";\n}\n\nfunction dateInputValue(value: string, dateTime: boolean): string {\n if (!value || value.startsWith(\"0001-01-01\")) return \"\";\n if (!dateTime) return value.slice(0, 10);\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return value.slice(0, 16);\n const offset = parsed.getTimezoneOffset() * 60_000;\n return new Date(parsed.getTime() - offset).toISOString().slice(0, 16);\n}\n\nfunction dateOutputValue(value: string, dateTime: boolean): string {\n if (!value) return \"\";\n if (!dateTime) return value;\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return value;\n return parsed.toISOString();\n}\n\nconst inputClassName =\n \"h-9 w-full min-w-0 rounded-md border border-input bg-background px-2 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n"],"names":["useReducer","isPositionalParam","jsxs","jsx","Button","Icon","UiClose"],"mappings":"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"}
1
+ {"version":3,"file":"CommandForm.cjs","sources":["../../src/rpc/CommandForm.tsx"],"sourcesContent":["import { useReducer, type FormEvent, type KeyboardEvent, type ReactNode } from \"react\";\nimport { Button } from \"../components/button\";\nimport { Icon } from \"../data/Icon\";\nimport { UiClose } from \"../icons\";\nimport {\n isPositionalParam,\n parameterPlaceholder,\n type OpenAPIParameter,\n} from \"./types\";\n\nexport type CommandFormProps = {\n parameters: OpenAPIParameter[];\n onExecute: (params: Record<string, string>, headers: Record<string, string>) => void;\n isPending: boolean;\n method: string;\n path: string;\n accept: string;\n initialValues?: Record<string, string>;\n};\n\ntype FormState = Record<string, string>;\ntype FormAction = { name: string; value: string };\n\nfunction formReducer(state: FormState, action: FormAction): FormState {\n return { ...state, [action.name]: action.value };\n}\n\nexport function CommandForm({\n parameters,\n onExecute,\n isPending,\n method: _method,\n path,\n accept,\n initialValues,\n}: CommandFormProps) {\n const [values, dispatch] = useReducer(\n formReducer,\n { parameters, path, initialValues },\n ({ parameters: params, path: formPath, initialValues: overrides }) =>\n buildInitialState(normalizeParameters(params, formPath), formPath, overrides),\n );\n\n const formParameters = normalizeParameters(parameters, path);\n const visibleParams = formParameters.filter((p) => !(p.in === \"path\" && initialValues?.[p.name]));\n const positionalNames = new Set(formParameters.filter(isPositionalParam).map((p) => p.name));\n const inlineLayout = visibleParams.length >= INLINE_LAYOUT_THRESHOLD;\n\n function handleSubmit(event: FormEvent) {\n event.preventDefault();\n const params: Record<string, string> = {};\n const args: string[] = [];\n for (const param of formParameters) {\n const value = submitValue(param, values[param.name]);\n if (value == null) continue;\n if (positionalNames.has(param.name)) {\n args.push(...splitArgsValue(value));\n } else {\n params[param.name] = value;\n }\n }\n if (args.length > 0) params.args = args.join(\",\");\n onExecute(params, { Accept: accept });\n }\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {visibleParams.length > 0 && (\n <div className={inlineLayout ? \"grid gap-2\" : \"grid gap-4\"}>\n {visibleParams.map((param) => (\n <ParameterField\n key={param.name}\n param={param}\n value={values[param.name] || \"\"}\n inline={inlineLayout}\n onChange={(value) => dispatch({ name: param.name, value })}\n />\n ))}\n </div>\n )}\n\n <div\n className={\n inlineLayout\n ? \"sticky bottom-0 z-10 -mx-2 flex justify-end border-t border-border bg-background/95 px-2 py-3 backdrop-blur-sm\"\n : \"flex justify-end\"\n }\n >\n <Button type=\"submit\" disabled={isPending}>\n {isPending ? \"Executing...\" : \"Execute\"}\n </Button>\n </div>\n </form>\n );\n}\n\nconst INLINE_LAYOUT_THRESHOLD = 6;\n\nfunction ParameterField({\n param,\n value,\n inline,\n onChange,\n}: {\n param: OpenAPIParameter;\n value: string;\n inline: boolean;\n onChange: (value: string) => void;\n}) {\n const schema = param.schema;\n const fieldId = `param-${param.name}`;\n\n if (isMultiValueParam(param)) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <TagInput\n id={fieldId}\n value={value}\n placeholder={parameterPlaceholder(param) || param.name}\n onChange={onChange}\n />\n </FieldWrapper>\n );\n }\n\n if (schema?.enum) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <select\n id={fieldId}\n value={value}\n className={inputClassName}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">Select {param.name}</option>\n {schema.enum.map((v) => (\n <option key={String(v)} value={String(v)}>\n {String(v)}\n </option>\n ))}\n </select>\n </FieldWrapper>\n );\n }\n\n if (schema?.type === \"boolean\") {\n const checkbox = (\n <input\n id={fieldId}\n type=\"checkbox\"\n className=\"h-4 w-4 accent-primary\"\n checked={value === \"true\"}\n onChange={(event) => onChange(event.target.checked ? \"true\" : \"false\")}\n />\n );\n if (inline) {\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline>\n <div className=\"flex h-9 items-center\">{checkbox}</div>\n </FieldWrapper>\n );\n }\n return (\n <div className=\"flex items-center gap-2\">\n {checkbox}\n <label htmlFor={fieldId} className=\"text-sm font-medium\">\n {param.name}\n {param.required && <span className=\"text-destructive\"> *</span>}\n </label>\n {param.description && (\n <span className=\"text-xs text-muted-foreground\">{param.description}</span>\n )}\n </div>\n );\n }\n\n if (isDateParam(param)) {\n const dateTime = schema?.format === \"date-time\";\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <div className=\"flex gap-2\">\n <input\n id={fieldId}\n type={dateTime ? \"datetime-local\" : \"date\"}\n value={dateInputValue(value, dateTime)}\n className={inputClassName}\n onChange={(event) => onChange(dateOutputValue(event.target.value, dateTime))}\n />\n {value && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Clear ${param.name}`}\n onClick={() => onChange(\"\")}\n >\n <Icon icon={UiClose} />\n </Button>\n )}\n </div>\n </FieldWrapper>\n );\n }\n\n const inputType = schema?.type === \"integer\" || schema?.type === \"number\" ? \"number\" : \"text\";\n\n return (\n <FieldWrapper param={param} fieldId={fieldId} inline={inline}>\n <input\n id={fieldId}\n type={inputType}\n value={value}\n className={inputClassName}\n onChange={(event) => onChange(event.target.value)}\n placeholder={\n schema?.default != null\n ? String(schema.default)\n : parameterPlaceholder(param) || param.name\n }\n />\n </FieldWrapper>\n );\n}\n\nfunction TagInput({\n id,\n value,\n placeholder,\n onChange,\n}: {\n id: string;\n value: string;\n placeholder: string;\n onChange: (value: string) => void;\n}) {\n const tags = parseTags(value);\n\n function commit(raw: string, input: HTMLInputElement) {\n const next = raw\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n if (next.length === 0) return;\n onChange(serializeTags([...tags, ...next]));\n input.value = \"\";\n }\n\n function remove(index: number) {\n onChange(serializeTags(tags.filter((_, i) => i !== index)));\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {\n const input = event.currentTarget;\n if (event.key === \"Enter\" || event.key === \",\") {\n event.preventDefault();\n commit(input.value, input);\n return;\n }\n if (event.key === \"Backspace\" && input.value === \"\" && tags.length > 0) {\n remove(tags.length - 1);\n }\n }\n\n return (\n <div className=\"flex min-h-9 flex-wrap items-center gap-1.5 rounded-md border border-input bg-transparent px-2 py-1 shadow-sm\">\n {tags.map((tag, index) => (\n <span\n key={`${tag}-${index}`}\n className=\"inline-flex h-6 max-w-full items-center gap-1 rounded-md bg-muted px-2 text-xs\"\n >\n <span className=\"truncate\">{tag}</span>\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-foreground\"\n aria-label={`Remove ${tag}`}\n onClick={() => remove(index)}\n >\n <Icon icon={UiClose} />\n </button>\n </span>\n ))}\n <input\n id={id}\n className=\"min-w-32 flex-1 bg-transparent px-1 py-1 text-sm outline-none\"\n placeholder={tags.length === 0 ? placeholder : \"\"}\n onKeyDown={handleKeyDown}\n onBlur={(event) => commit(event.currentTarget.value, event.currentTarget)}\n />\n </div>\n );\n}\n\nfunction FieldWrapper({\n param,\n fieldId,\n inline,\n children,\n}: {\n param: OpenAPIParameter;\n fieldId: string;\n inline?: boolean;\n children: ReactNode;\n}) {\n const label = (\n <label htmlFor={fieldId} className=\"text-sm font-medium\">\n {param.name}\n {param.required && <span className=\"text-destructive\"> *</span>}\n </label>\n );\n\n if (inline) {\n return (\n <div className=\"grid grid-cols-[10rem_1fr] items-start gap-x-3 gap-y-0.5\">\n <div className=\"flex h-9 items-center\">{label}</div>\n <div className=\"min-w-0\">{children}</div>\n {param.description && (\n <p className=\"col-start-2 text-xs text-muted-foreground\">{param.description}</p>\n )}\n </div>\n );\n }\n\n return (\n <div className=\"space-y-1.5\">\n {label}\n {children}\n {param.description && <p className=\"text-xs text-muted-foreground\">{param.description}</p>}\n </div>\n );\n}\n\nexport function normalizeParameters(\n parameters: OpenAPIParameter[],\n path: string,\n): OpenAPIParameter[] {\n const pathParams = pathParamNames(path);\n const firstPathParam = pathParams[0];\n const seen = new Set(parameters.map((param) => param.name));\n const normalized = parameters.filter(\n (param) => !(param.name === \"args\" && firstPathParam != null && !seen.has(firstPathParam)),\n );\n for (const name of pathParams) {\n if (seen.has(name)) continue;\n normalized.unshift({\n name,\n in: \"path\",\n required: true,\n description: \"Path parameter\",\n schema: { type: \"string\" },\n });\n }\n return normalized;\n}\n\nexport function pathParamNames(path: string): string[] {\n return [...path.matchAll(/\\{([^}]+)\\}/g)]\n .map((match) => match[1])\n .filter((name): name is string => Boolean(name));\n}\n\nfunction buildInitialState(\n parameters: OpenAPIParameter[],\n path: string,\n overrides?: Record<string, string>,\n): FormState {\n const state: FormState = {};\n for (const p of parameters) {\n state[p.name] = initialParamValue(p, overrides?.[p.name]);\n }\n for (const name of pathParamNames(path)) {\n if (state[name] == null) {\n state[name] = overrides?.[name] ?? \"\";\n }\n }\n return state;\n}\n\nfunction initialParamValue(param: OpenAPIParameter, override: string | undefined): string {\n if (override != null) return sanitizeInitialValue(param, override);\n const value = param.schema?.default;\n if (value == null) return \"\";\n return sanitizeInitialValue(param, String(value));\n}\n\nfunction sanitizeInitialValue(param: OpenAPIParameter, value: string): string {\n const trimmed = value.trim();\n if (trimmed === \"[]\" || trimmed === \"null\") return \"\";\n if (isDateParam(param) && isZeroDate(trimmed)) return \"\";\n return value;\n}\n\nexport function submitValue(param: OpenAPIParameter, value: string | undefined): string | null {\n const trimmed = (value ?? \"\").trim();\n if (!trimmed || trimmed === \"[]\" || trimmed === \"null\") return null;\n if (isDateParam(param) && isZeroDate(trimmed)) return null;\n return trimmed;\n}\n\nfunction splitArgsValue(value: string): string[] {\n try {\n const parsed = JSON.parse(value);\n if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);\n } catch {\n // Use comma splitting below.\n }\n return value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction isMultiValueParam(param: OpenAPIParameter): boolean {\n const text = `${param.name} ${param.description ?? \"\"}`.toLowerCase();\n return (\n param.schema?.type === \"array\" ||\n String(param.schema?.default ?? \"\") === \"[]\" ||\n text.includes(\"repeatable\")\n );\n}\n\nfunction parseTags(value: string): string[] {\n const trimmed = value.trim();\n if (!trimmed || trimmed === \"[]\" || trimmed === \"null\") return [];\n try {\n const parsed = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);\n } catch {\n // Fall back to comma-delimited tags below.\n }\n return trimmed\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\nfunction serializeTags(tags: string[]): string {\n return tags.join(\",\");\n}\n\nfunction isDateParam(param: OpenAPIParameter): boolean {\n const format = param.schema?.format?.toLowerCase();\n const text = `${param.name} ${param.description ?? \"\"}`.toLowerCase();\n return format === \"date\" || format === \"date-time\" || text.includes(\"date\");\n}\n\nfunction isZeroDate(value: string): boolean {\n return value.startsWith(\"0001-01-01\") || value === \"0001-01-01\";\n}\n\nfunction dateInputValue(value: string, dateTime: boolean): string {\n if (!value || value.startsWith(\"0001-01-01\")) return \"\";\n if (!dateTime) return value.slice(0, 10);\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return value.slice(0, 16);\n const offset = parsed.getTimezoneOffset() * 60_000;\n return new Date(parsed.getTime() - offset).toISOString().slice(0, 16);\n}\n\nfunction dateOutputValue(value: string, dateTime: boolean): string {\n if (!value) return \"\";\n if (!dateTime) return value;\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) return value;\n return parsed.toISOString();\n}\n\nconst inputClassName =\n \"h-9 w-full min-w-0 rounded-md border border-input bg-background px-2 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n"],"names":["useReducer","isPositionalParam","jsxs","jsx","Button","parameterPlaceholder","Icon","UiClose"],"mappings":";;;;;;;;AAuBA,SAAS,YAAY,OAAkB,QAA+B;AACpE,SAAO,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,GAAG,OAAO,MAAA;AAC3C;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,QAAQ,QAAQ,IAAIA,MAAAA;AAAAA,IACzB;AAAA,IACA,EAAE,YAAY,MAAM,cAAA;AAAA,IACpB,CAAC,EAAE,YAAY,QAAQ,MAAM,UAAU,eAAe,gBACpD,kBAAkB,oBAAoB,QAAQ,QAAQ,GAAG,UAAU,SAAS;AAAA,EAAA;AAGhF,QAAM,iBAAiB,oBAAoB,YAAY,IAAI;AAC3D,QAAM,gBAAgB,eAAe,OAAO,CAAC,MAAM,EAAE,EAAE,OAAO,WAAU,+CAAgB,EAAE,OAAM;AAChG,QAAM,kBAAkB,IAAI,IAAI,eAAe,OAAOC,MAAAA,iBAAiB,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC3F,QAAM,eAAe,cAAc,UAAU;AAE7C,WAAS,aAAa,OAAkB;AACtC,UAAM,eAAA;AACN,UAAM,SAAiC,CAAA;AACvC,UAAM,OAAiB,CAAA;AACvB,eAAW,SAAS,gBAAgB;AAClC,YAAM,QAAQ,YAAY,OAAO,OAAO,MAAM,IAAI,CAAC;AACnD,UAAI,SAAS,KAAM;AACnB,UAAI,gBAAgB,IAAI,MAAM,IAAI,GAAG;AACnC,aAAK,KAAK,GAAG,eAAe,KAAK,CAAC;AAAA,MACpC,OAAO;AACL,eAAO,MAAM,IAAI,IAAI;AAAA,MACvB;AAAA,IACF;AACA,QAAI,KAAK,SAAS,UAAU,OAAO,KAAK,KAAK,GAAG;AAChD,cAAU,QAAQ,EAAE,QAAQ,OAAA,CAAQ;AAAA,EACtC;AAEA,SACEC,2BAAAA,KAAC,QAAA,EAAK,UAAU,cAAc,WAAU,aACrC,UAAA;AAAA,IAAA,cAAc,SAAS,KACtBC,2BAAAA,IAAC,OAAA,EAAI,WAAW,eAAe,eAAe,cAC3C,UAAA,cAAc,IAAI,CAAC,UAClBA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAEC;AAAA,QACA,OAAO,OAAO,MAAM,IAAI,KAAK;AAAA,QAC7B,QAAQ;AAAA,QACR,UAAU,CAAC,UAAU,SAAS,EAAE,MAAM,MAAM,MAAM,MAAA,CAAO;AAAA,MAAA;AAAA,MAJpD,MAAM;AAAA,IAAA,CAMd,GACH;AAAA,IAGFA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WACE,eACI,mHACA;AAAA,QAGN,UAAAA,2BAAAA,IAACC,iBAAO,MAAK,UAAS,UAAU,WAC7B,UAAA,YAAY,iBAAiB,UAAA,CAChC;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;AAEA,MAAM,0BAA0B;AAEhC,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,SAAS,MAAM;AACrB,QAAM,UAAU,SAAS,MAAM,IAAI;AAEnC,MAAI,kBAAkB,KAAK,GAAG;AAC5B,WACED,2BAAAA,IAAC,cAAA,EAAa,OAAc,SAAkB,QAC5C,UAAAA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAI;AAAA,QACJ;AAAA,QACA,aAAaE,MAAAA,qBAAqB,KAAK,KAAK,MAAM;AAAA,QAClD;AAAA,MAAA;AAAA,IAAA,GAEJ;AAAA,EAEJ;AAEA,MAAI,iCAAQ,MAAM;AAChB,WACEF,2BAAAA,IAAC,cAAA,EAAa,OAAc,SAAkB,QAC5C,UAAAD,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAI;AAAA,QACJ;AAAA,QACA,WAAW;AAAA,QACX,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,QAEhD,UAAA;AAAA,UAAAA,2BAAAA,KAAC,UAAA,EAAO,OAAM,IAAG,UAAA;AAAA,YAAA;AAAA,YAAQ,MAAM;AAAA,UAAA,GAAK;AAAA,UACnC,OAAO,KAAK,IAAI,CAAC,qCACf,UAAA,EAAuB,OAAO,OAAO,CAAC,GACpC,UAAA,OAAO,CAAC,KADE,OAAO,CAAC,CAErB,CACD;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAEL;AAAA,EAEJ;AAEA,OAAI,iCAAQ,UAAS,WAAW;AAC9B,UAAM,WACJC,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAI;AAAA,QACJ,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS,UAAU;AAAA,QACnB,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,UAAU,SAAS,OAAO;AAAA,MAAA;AAAA,IAAA;AAGzE,QAAI,QAAQ;AACV,aACEA,2BAAAA,IAAC,cAAA,EAAa,OAAc,SAAkB,QAAM,MAClD,UAAAA,2BAAAA,IAAC,OAAA,EAAI,WAAU,yBAAyB,UAAA,SAAA,CAAS,GACnD;AAAA,IAEJ;AACA,WACED,2BAAAA,KAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA;AAAA,MACDA,2BAAAA,KAAC,SAAA,EAAM,SAAS,SAAS,WAAU,uBAChC,UAAA;AAAA,QAAA,MAAM;AAAA,QACN,MAAM,YAAYC,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAmB,UAAA,KAAA,CAAE;AAAA,MAAA,GAC1D;AAAA,MACC,MAAM,eACLA,2BAAAA,IAAC,UAAK,WAAU,iCAAiC,gBAAM,YAAA,CAAY;AAAA,IAAA,GAEvE;AAAA,EAEJ;AAEA,MAAI,YAAY,KAAK,GAAG;AACtB,UAAM,YAAW,iCAAQ,YAAW;AACpC,WACEA,2BAAAA,IAAC,gBAAa,OAAc,SAAkB,QAC5C,UAAAD,2BAAAA,KAAC,OAAA,EAAI,WAAU,cACb,UAAA;AAAA,MAAAC,2BAAAA;AAAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,MAAM,WAAW,mBAAmB;AAAA,UACpC,OAAO,eAAe,OAAO,QAAQ;AAAA,UACrC,WAAW;AAAA,UACX,UAAU,CAAC,UAAU,SAAS,gBAAgB,MAAM,OAAO,OAAO,QAAQ,CAAC;AAAA,QAAA;AAAA,MAAA;AAAA,MAE5E,SACCA,2BAAAA;AAAAA,QAACC,OAAAA;AAAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,cAAY,SAAS,MAAM,IAAI;AAAA,UAC/B,SAAS,MAAM,SAAS,EAAE;AAAA,UAE1B,UAAAD,2BAAAA,IAACG,KAAAA,MAAA,EAAK,MAAMC,QAAAA,QAAA,CAAS;AAAA,QAAA;AAAA,MAAA;AAAA,IACvB,EAAA,CAEJ,EAAA,CACF;AAAA,EAEJ;AAEA,QAAM,aAAY,iCAAQ,UAAS,cAAa,iCAAQ,UAAS,WAAW,WAAW;AAEvF,SACEJ,2BAAAA,IAAC,cAAA,EAAa,OAAc,SAAkB,QAC5C,UAAAA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,WAAW;AAAA,MACX,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,MAChD,cACE,iCAAQ,YAAW,OACf,OAAO,OAAO,OAAO,IACrBE,MAAAA,qBAAqB,KAAK,KAAK,MAAM;AAAA,IAAA;AAAA,EAAA,GAG/C;AAEJ;AAEA,SAAS,SAAS;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,OAAO,UAAU,KAAK;AAE5B,WAAS,OAAO,KAAa,OAAyB;AACpD,UAAM,OAAO,IACV,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAA,CAAM,EACzB,OAAO,OAAO;AACjB,QAAI,KAAK,WAAW,EAAG;AACvB,aAAS,cAAc,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;AAC1C,UAAM,QAAQ;AAAA,EAChB;AAEA,WAAS,OAAO,OAAe;AAC7B,aAAS,cAAc,KAAK,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK,CAAC,CAAC;AAAA,EAC5D;AAEA,WAAS,cAAc,OAAwC;AAC7D,UAAM,QAAQ,MAAM;AACpB,QAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,YAAM,eAAA;AACN,aAAO,MAAM,OAAO,KAAK;AACzB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,eAAe,MAAM,UAAU,MAAM,KAAK,SAAS,GAAG;AACtE,aAAO,KAAK,SAAS,CAAC;AAAA,IACxB;AAAA,EACF;AAEA,SACEH,2BAAAA,KAAC,OAAA,EAAI,WAAU,iHACZ,UAAA;AAAA,IAAA,KAAK,IAAI,CAAC,KAAK,UACdA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QAEV,UAAA;AAAA,UAAAC,2BAAAA,IAAC,QAAA,EAAK,WAAU,YAAY,UAAA,KAAI;AAAA,UAChCA,2BAAAA;AAAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAY,UAAU,GAAG;AAAA,cACzB,SAAS,MAAM,OAAO,KAAK;AAAA,cAE3B,UAAAA,2BAAAA,IAACG,KAAAA,MAAA,EAAK,MAAMC,QAAAA,QAAA,CAAS;AAAA,YAAA;AAAA,UAAA;AAAA,QACvB;AAAA,MAAA;AAAA,MAXK,GAAG,GAAG,IAAI,KAAK;AAAA,IAAA,CAavB;AAAA,IACDJ,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACV,aAAa,KAAK,WAAW,IAAI,cAAc;AAAA,QAC/C,WAAW;AAAA,QACX,QAAQ,CAAC,UAAU,OAAO,MAAM,cAAc,OAAO,MAAM,aAAa;AAAA,MAAA;AAAA,IAAA;AAAA,EAC1E,GACF;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,QACJD,2BAAAA,KAAC,SAAA,EAAM,SAAS,SAAS,WAAU,uBAChC,UAAA;AAAA,IAAA,MAAM;AAAA,IACN,MAAM,YAAYC,2BAAAA,IAAC,QAAA,EAAK,WAAU,oBAAmB,UAAA,KAAA,CAAE;AAAA,EAAA,GAC1D;AAGF,MAAI,QAAQ;AACV,WACED,2BAAAA,KAAC,OAAA,EAAI,WAAU,4DACb,UAAA;AAAA,MAAAC,2BAAAA,IAAC,OAAA,EAAI,WAAU,yBAAyB,UAAA,OAAM;AAAA,MAC9CA,2BAAAA,IAAC,OAAA,EAAI,WAAU,WAAW,SAAA,CAAS;AAAA,MAClC,MAAM,eACLA,2BAAAA,IAAC,OAAE,WAAU,6CAA6C,gBAAM,YAAA,CAAY;AAAA,IAAA,GAEhF;AAAA,EAEJ;AAEA,SACED,2BAAAA,KAAC,OAAA,EAAI,WAAU,eACZ,UAAA;AAAA,IAAA;AAAA,IACA;AAAA,IACA,MAAM,eAAeC,2BAAAA,IAAC,OAAE,WAAU,iCAAiC,gBAAM,YAAA,CAAY;AAAA,EAAA,GACxF;AAEJ;AAEO,SAAS,oBACd,YACA,MACoB;AACpB,QAAM,aAAa,eAAe,IAAI;AACtC,QAAM,iBAAiB,WAAW,CAAC;AACnC,QAAM,OAAO,IAAI,IAAI,WAAW,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AAC1D,QAAM,aAAa,WAAW;AAAA,IAC5B,CAAC,UAAU,EAAE,MAAM,SAAS,UAAU,kBAAkB,QAAQ,CAAC,KAAK,IAAI,cAAc;AAAA,EAAA;AAE1F,aAAW,QAAQ,YAAY;AAC7B,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,eAAW,QAAQ;AAAA,MACjB;AAAA,MACA,IAAI;AAAA,MACJ,UAAU;AAAA,MACV,aAAa;AAAA,MACb,QAAQ,EAAE,MAAM,SAAA;AAAA,IAAS,CAC1B;AAAA,EACH;AACA,SAAO;AACT;AAEO,SAAS,eAAe,MAAwB;AACrD,SAAO,CAAC,GAAG,KAAK,SAAS,cAAc,CAAC,EACrC,IAAI,CAAC,UAAU,MAAM,CAAC,CAAC,EACvB,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC;AACnD;AAEA,SAAS,kBACP,YACA,MACA,WACW;AACX,QAAM,QAAmB,CAAA;AACzB,aAAW,KAAK,YAAY;AAC1B,UAAM,EAAE,IAAI,IAAI,kBAAkB,GAAG,uCAAY,EAAE,KAAK;AAAA,EAC1D;AACA,aAAW,QAAQ,eAAe,IAAI,GAAG;AACvC,QAAI,MAAM,IAAI,KAAK,MAAM;AACvB,YAAM,IAAI,KAAI,uCAAY,UAAS;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAyB,UAAsC;;AACxF,MAAI,YAAY,KAAM,QAAO,qBAAqB,OAAO,QAAQ;AACjE,QAAM,SAAQ,WAAM,WAAN,mBAAc;AAC5B,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,qBAAqB,OAAO,OAAO,KAAK,CAAC;AAClD;AAEA,SAAS,qBAAqB,OAAyB,OAAuB;AAC5E,QAAM,UAAU,MAAM,KAAA;AACtB,MAAI,YAAY,QAAQ,YAAY,OAAQ,QAAO;AACnD,MAAI,YAAY,KAAK,KAAK,WAAW,OAAO,EAAG,QAAO;AACtD,SAAO;AACT;AAEO,SAAS,YAAY,OAAyB,OAA0C;AAC7F,QAAM,WAAW,SAAS,IAAI,KAAA;AAC9B,MAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,OAAQ,QAAO;AAC/D,MAAI,YAAY,KAAK,KAAK,WAAW,OAAO,EAAG,QAAO;AACtD,SAAO;AACT;AAEA,SAAS,eAAe,OAAyB;AAC/C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAI,MAAM,QAAQ,MAAM,EAAG,QAAO,OAAO,IAAI,MAAM,EAAE,OAAO,OAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AACA,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAA,CAAM,EACzB,OAAO,OAAO;AACnB;AAEA,SAAS,kBAAkB,OAAkC;;AAC3D,QAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,eAAe,EAAE,GAAG,YAAA;AACxD,WACE,WAAM,WAAN,mBAAc,UAAS,WACvB,SAAO,WAAM,WAAN,mBAAc,YAAW,EAAE,MAAM,QACxC,KAAK,SAAS,YAAY;AAE9B;AAEA,SAAS,UAAU,OAAyB;AAC1C,QAAM,UAAU,MAAM,KAAA;AACtB,MAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,eAAe,CAAA;AAC/D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,QAAI,MAAM,QAAQ,MAAM,EAAG,QAAO,OAAO,IAAI,MAAM,EAAE,OAAO,OAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AACA,SAAO,QACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAA,CAAM,EACzB,OAAO,OAAO;AACnB;AAEA,SAAS,cAAc,MAAwB;AAC7C,SAAO,KAAK,KAAK,GAAG;AACtB;AAEA,SAAS,YAAY,OAAkC;;AACrD,QAAM,UAAS,iBAAM,WAAN,mBAAc,WAAd,mBAAsB;AACrC,QAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,eAAe,EAAE,GAAG,YAAA;AACxD,SAAO,WAAW,UAAU,WAAW,eAAe,KAAK,SAAS,MAAM;AAC5E;AAEA,SAAS,WAAW,OAAwB;AAC1C,SAAO,MAAM,WAAW,YAAY,KAAK,UAAU;AACrD;AAEA,SAAS,eAAe,OAAe,UAA2B;AAChE,MAAI,CAAC,SAAS,MAAM,WAAW,YAAY,EAAG,QAAO;AACrD,MAAI,CAAC,SAAU,QAAO,MAAM,MAAM,GAAG,EAAE;AACvC,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,MAAI,OAAO,MAAM,OAAO,QAAA,CAAS,EAAG,QAAO,MAAM,MAAM,GAAG,EAAE;AAC5D,QAAM,SAAS,OAAO,kBAAA,IAAsB;AAC5C,SAAO,IAAI,KAAK,OAAO,YAAY,MAAM,EAAE,cAAc,MAAM,GAAG,EAAE;AACtE;AAEA,SAAS,gBAAgB,OAAe,UAA2B;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,MAAI,OAAO,MAAM,OAAO,QAAA,CAAS,EAAG,QAAO;AAC3C,SAAO,OAAO,YAAA;AAChB;AAEA,MAAM,iBACJ;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommandForm.d.ts","sourceRoot":"","sources":["../../src/rpc/CommandForm.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACrF,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AASF,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,SAAS,EACT,SAAS,EACT,MAAM,EAAE,OAAO,EACf,IAAI,EACJ,MAAM,EACN,aAAa,GACd,EAAE,gBAAgB,2CA2DlB;AA2OD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,gBAAgB,EAAE,EAC9B,IAAI,EAAE,MAAM,GACX,gBAAgB,EAAE,CAkBpB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIrD;AAiCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK7F"}
1
+ {"version":3,"file":"CommandForm.d.ts","sourceRoot":"","sources":["../../src/rpc/CommandForm.tsx"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACrF,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC,CAAC;AASF,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,SAAS,EACT,SAAS,EACT,MAAM,EAAE,OAAO,EACf,IAAI,EACJ,MAAM,EACN,aAAa,GACd,EAAE,gBAAgB,2CA2DlB;AA6OD,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,gBAAgB,EAAE,EAC9B,IAAI,EAAE,MAAM,GACX,gBAAgB,EAAE,CAkBpB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAIrD;AAiCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK7F"}
@@ -2,7 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useReducer } from "react";
3
3
  import { Button } from "../components/button.js";
4
4
  import { Icon } from "../data/Icon.js";
5
- import { isPositionalParam } from "./types.js";
5
+ import { isPositionalParam, parameterPlaceholder } from "./types.js";
6
6
  import { UiClose } from "../icons/components/UiClose.js";
7
7
  function formReducer(state, action) {
8
8
  return { ...state, [action.name]: action.value };
@@ -76,7 +76,7 @@ function ParameterField({
76
76
  {
77
77
  id: fieldId,
78
78
  value,
79
- placeholder: param.description || param.name,
79
+ placeholder: parameterPlaceholder(param) || param.name,
80
80
  onChange
81
81
  }
82
82
  ) });
@@ -157,7 +157,7 @@ function ParameterField({
157
157
  value,
158
158
  className: inputClassName,
159
159
  onChange: (event) => onChange(event.target.value),
160
- placeholder: (schema == null ? void 0 : schema.default) != null ? String(schema.default) : param.description || param.name
160
+ placeholder: (schema == null ? void 0 : schema.default) != null ? String(schema.default) : parameterPlaceholder(param) || param.name
161
161
  }
162
162
  ) });
163
163
  }