@magicborn/dialogue-forge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +233 -0
  2. package/bin/dialogue-forge.js +78 -0
  3. package/demo/app/layout.tsx +36 -0
  4. package/demo/app/page.tsx +440 -0
  5. package/demo/components/ThemeSwitcher.tsx +611 -0
  6. package/demo/next.config.mjs +7 -0
  7. package/demo/package.json +29 -0
  8. package/demo/postcss.config.mjs +7 -0
  9. package/demo/public/logo.svg +1 -0
  10. package/demo/styles/globals.css +19 -0
  11. package/demo/tailwind.config.ts +90 -0
  12. package/demo/tsconfig.json +42 -0
  13. package/dist/components/ChoiceEdgeV2.d.ts +3 -0
  14. package/dist/components/ChoiceEdgeV2.js +103 -0
  15. package/dist/components/CodeBlock.d.ts +8 -0
  16. package/dist/components/CodeBlock.js +24 -0
  17. package/dist/components/ConditionAutocomplete.d.ts +14 -0
  18. package/dist/components/ConditionAutocomplete.js +284 -0
  19. package/dist/components/ConditionalNodeV2.d.ts +16 -0
  20. package/dist/components/ConditionalNodeV2.js +147 -0
  21. package/dist/components/DialogueEditorV2.d.ts +22 -0
  22. package/dist/components/DialogueEditorV2.js +1170 -0
  23. package/dist/components/EdgeIcon.d.ts +8 -0
  24. package/dist/components/EdgeIcon.js +13 -0
  25. package/dist/components/ExampleLoader.d.ts +11 -0
  26. package/dist/components/ExampleLoader.js +52 -0
  27. package/dist/components/ExampleLoaderButton.d.ts +15 -0
  28. package/dist/components/ExampleLoaderButton.js +102 -0
  29. package/dist/components/FlagManager.d.ts +11 -0
  30. package/dist/components/FlagManager.js +282 -0
  31. package/dist/components/FlagSelector.d.ts +11 -0
  32. package/dist/components/FlagSelector.js +235 -0
  33. package/dist/components/GuidePanel.d.ts +7 -0
  34. package/dist/components/GuidePanel.js +1176 -0
  35. package/dist/components/Minimap.d.ts +16 -0
  36. package/dist/components/Minimap.js +93 -0
  37. package/dist/components/NPCEdgeV2.d.ts +3 -0
  38. package/dist/components/NPCEdgeV2.js +104 -0
  39. package/dist/components/NPCNodeV2.d.ts +26 -0
  40. package/dist/components/NPCNodeV2.js +86 -0
  41. package/dist/components/NodeEditor.d.ts +18 -0
  42. package/dist/components/NodeEditor.js +1025 -0
  43. package/dist/components/PlayView.d.ts +12 -0
  44. package/dist/components/PlayView.js +307 -0
  45. package/dist/components/PlayerNodeV2.d.ts +16 -0
  46. package/dist/components/PlayerNodeV2.js +139 -0
  47. package/dist/components/ReactFlowPOC.d.ts +61 -0
  48. package/dist/components/ReactFlowPOC.js +312 -0
  49. package/dist/components/ScenePlayer.d.ts +18 -0
  50. package/dist/components/ScenePlayer.js +196 -0
  51. package/dist/components/YarnView.d.ts +9 -0
  52. package/dist/components/YarnView.js +45 -0
  53. package/dist/components/ZoomControls.d.ts +11 -0
  54. package/dist/components/ZoomControls.js +34 -0
  55. package/dist/esm/components/ChoiceEdgeV2.d.ts +3 -0
  56. package/dist/esm/components/ChoiceEdgeV2.js +67 -0
  57. package/dist/esm/components/CodeBlock.d.ts +8 -0
  58. package/dist/esm/components/CodeBlock.js +18 -0
  59. package/dist/esm/components/ConditionAutocomplete.d.ts +14 -0
  60. package/dist/esm/components/ConditionAutocomplete.js +248 -0
  61. package/dist/esm/components/ConditionalNodeV2.d.ts +16 -0
  62. package/dist/esm/components/ConditionalNodeV2.js +111 -0
  63. package/dist/esm/components/DialogueEditorV2.d.ts +22 -0
  64. package/dist/esm/components/DialogueEditorV2.js +1134 -0
  65. package/dist/esm/components/EdgeIcon.d.ts +8 -0
  66. package/dist/esm/components/EdgeIcon.js +7 -0
  67. package/dist/esm/components/ExampleLoader.d.ts +11 -0
  68. package/dist/esm/components/ExampleLoader.js +46 -0
  69. package/dist/esm/components/ExampleLoaderButton.d.ts +15 -0
  70. package/dist/esm/components/ExampleLoaderButton.js +66 -0
  71. package/dist/esm/components/FlagManager.d.ts +11 -0
  72. package/dist/esm/components/FlagManager.js +246 -0
  73. package/dist/esm/components/FlagSelector.d.ts +11 -0
  74. package/dist/esm/components/FlagSelector.js +199 -0
  75. package/dist/esm/components/GuidePanel.d.ts +7 -0
  76. package/dist/esm/components/GuidePanel.js +1140 -0
  77. package/dist/esm/components/Minimap.d.ts +16 -0
  78. package/dist/esm/components/Minimap.js +57 -0
  79. package/dist/esm/components/NPCEdgeV2.d.ts +3 -0
  80. package/dist/esm/components/NPCEdgeV2.js +68 -0
  81. package/dist/esm/components/NPCNodeV2.d.ts +26 -0
  82. package/dist/esm/components/NPCNodeV2.js +80 -0
  83. package/dist/esm/components/NodeEditor.d.ts +18 -0
  84. package/dist/esm/components/NodeEditor.js +989 -0
  85. package/dist/esm/components/PlayView.d.ts +12 -0
  86. package/dist/esm/components/PlayView.js +271 -0
  87. package/dist/esm/components/PlayerNodeV2.d.ts +16 -0
  88. package/dist/esm/components/PlayerNodeV2.js +103 -0
  89. package/dist/esm/components/ReactFlowPOC.d.ts +61 -0
  90. package/dist/esm/components/ReactFlowPOC.js +275 -0
  91. package/dist/esm/components/ScenePlayer.d.ts +18 -0
  92. package/dist/esm/components/ScenePlayer.js +160 -0
  93. package/dist/esm/components/YarnView.d.ts +9 -0
  94. package/dist/esm/components/YarnView.js +39 -0
  95. package/dist/esm/components/ZoomControls.d.ts +11 -0
  96. package/dist/esm/components/ZoomControls.js +28 -0
  97. package/dist/esm/examples/example-loader.d.ts +29 -0
  98. package/dist/esm/examples/example-loader.js +103 -0
  99. package/dist/esm/examples/examples-registry.d.ts +38 -0
  100. package/dist/esm/examples/examples-registry.js +153 -0
  101. package/dist/esm/examples/index.d.ts +26 -0
  102. package/dist/esm/examples/index.js +50 -0
  103. package/dist/esm/examples/legacy-examples.d.ts +9 -0
  104. package/dist/esm/examples/legacy-examples.js +814 -0
  105. package/dist/esm/examples/yarn-examples.d.ts +35 -0
  106. package/dist/esm/examples/yarn-examples.js +181 -0
  107. package/dist/esm/index.d.ts +21 -0
  108. package/dist/esm/index.js +26 -0
  109. package/dist/esm/lib/flag-manager.d.ts +21 -0
  110. package/dist/esm/lib/flag-manager.js +93 -0
  111. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  112. package/dist/esm/lib/yarn-converter/__tests__/round-trip.test.js +169 -0
  113. package/dist/esm/lib/yarn-converter.d.ts +17 -0
  114. package/dist/esm/lib/yarn-converter.js +521 -0
  115. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  116. package/dist/esm/lib/yarn-runner/__tests__/condition-evaluator.test.js +171 -0
  117. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  118. package/dist/esm/lib/yarn-runner/__tests__/node-processor.test.js +237 -0
  119. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  120. package/dist/esm/lib/yarn-runner/__tests__/variable-manager.test.js +106 -0
  121. package/dist/esm/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  122. package/dist/esm/lib/yarn-runner/condition-evaluator.js +56 -0
  123. package/dist/esm/lib/yarn-runner/index.d.ts +12 -0
  124. package/dist/esm/lib/yarn-runner/index.js +11 -0
  125. package/dist/esm/lib/yarn-runner/node-processor.d.ts +18 -0
  126. package/dist/esm/lib/yarn-runner/node-processor.js +129 -0
  127. package/dist/esm/lib/yarn-runner/variable-manager.d.ts +51 -0
  128. package/dist/esm/lib/yarn-runner/variable-manager.js +120 -0
  129. package/dist/esm/lib/yarn-runner/variable-operations.d.ts +16 -0
  130. package/dist/esm/lib/yarn-runner/variable-operations.js +88 -0
  131. package/dist/esm/types/conditionals.d.ts +29 -0
  132. package/dist/esm/types/conditionals.js +1 -0
  133. package/dist/esm/types/constants.d.ts +59 -0
  134. package/dist/esm/types/constants.js +55 -0
  135. package/dist/esm/types/flags.d.ts +49 -0
  136. package/dist/esm/types/flags.js +49 -0
  137. package/dist/esm/types/game-state.d.ts +62 -0
  138. package/dist/esm/types/game-state.js +6 -0
  139. package/dist/esm/types/index.d.ts +77 -0
  140. package/dist/esm/types/index.js +1 -0
  141. package/dist/esm/utils/constants.d.ts +5 -0
  142. package/dist/esm/utils/constants.js +5 -0
  143. package/dist/esm/utils/feature-flags.d.ts +11 -0
  144. package/dist/esm/utils/feature-flags.js +11 -0
  145. package/dist/esm/utils/game-state-flattener.d.ts +41 -0
  146. package/dist/esm/utils/game-state-flattener.js +135 -0
  147. package/dist/esm/utils/layout/collision.d.ts +27 -0
  148. package/dist/esm/utils/layout/collision.js +74 -0
  149. package/dist/esm/utils/layout/index.d.ts +82 -0
  150. package/dist/esm/utils/layout/index.js +98 -0
  151. package/dist/esm/utils/layout/registry.d.ts +91 -0
  152. package/dist/esm/utils/layout/registry.js +148 -0
  153. package/dist/esm/utils/layout/strategies/dagre.d.ts +19 -0
  154. package/dist/esm/utils/layout/strategies/dagre.js +182 -0
  155. package/dist/esm/utils/layout/strategies/force.d.ts +21 -0
  156. package/dist/esm/utils/layout/strategies/force.js +178 -0
  157. package/dist/esm/utils/layout/strategies/grid.d.ts +17 -0
  158. package/dist/esm/utils/layout/strategies/grid.js +91 -0
  159. package/dist/esm/utils/layout/strategies/index.d.ts +8 -0
  160. package/dist/esm/utils/layout/strategies/index.js +8 -0
  161. package/dist/esm/utils/layout/types.d.ts +100 -0
  162. package/dist/esm/utils/layout/types.js +7 -0
  163. package/dist/esm/utils/layout.d.ts +9 -0
  164. package/dist/esm/utils/layout.js +17 -0
  165. package/dist/esm/utils/node-helpers.d.ts +7 -0
  166. package/dist/esm/utils/node-helpers.js +94 -0
  167. package/dist/esm/utils/reactflow-converter.d.ts +42 -0
  168. package/dist/esm/utils/reactflow-converter.js +217 -0
  169. package/dist/examples/example-loader.d.ts +29 -0
  170. package/dist/examples/example-loader.js +109 -0
  171. package/dist/examples/examples-registry.d.ts +38 -0
  172. package/dist/examples/examples-registry.js +160 -0
  173. package/dist/examples/index.d.ts +26 -0
  174. package/dist/examples/index.js +63 -0
  175. package/dist/examples/legacy-examples.d.ts +9 -0
  176. package/dist/examples/legacy-examples.js +817 -0
  177. package/dist/examples/yarn-examples.d.ts +35 -0
  178. package/dist/examples/yarn-examples.js +189 -0
  179. package/dist/index.d.ts +21 -0
  180. package/dist/index.js +66 -0
  181. package/dist/lib/flag-manager.d.ts +21 -0
  182. package/dist/lib/flag-manager.js +99 -0
  183. package/dist/lib/yarn-converter/__tests__/round-trip.test.d.ts +1 -0
  184. package/dist/lib/yarn-converter/__tests__/round-trip.test.js +171 -0
  185. package/dist/lib/yarn-converter.d.ts +17 -0
  186. package/dist/lib/yarn-converter.js +525 -0
  187. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.d.ts +1 -0
  188. package/dist/lib/yarn-runner/__tests__/condition-evaluator.test.js +173 -0
  189. package/dist/lib/yarn-runner/__tests__/node-processor.test.d.ts +1 -0
  190. package/dist/lib/yarn-runner/__tests__/node-processor.test.js +239 -0
  191. package/dist/lib/yarn-runner/__tests__/variable-manager.test.d.ts +1 -0
  192. package/dist/lib/yarn-runner/__tests__/variable-manager.test.js +108 -0
  193. package/dist/lib/yarn-runner/condition-evaluator.d.ts +12 -0
  194. package/dist/lib/yarn-runner/condition-evaluator.js +60 -0
  195. package/dist/lib/yarn-runner/index.d.ts +12 -0
  196. package/dist/lib/yarn-runner/index.js +21 -0
  197. package/dist/lib/yarn-runner/node-processor.d.ts +18 -0
  198. package/dist/lib/yarn-runner/node-processor.js +133 -0
  199. package/dist/lib/yarn-runner/variable-manager.d.ts +51 -0
  200. package/dist/lib/yarn-runner/variable-manager.js +124 -0
  201. package/dist/lib/yarn-runner/variable-operations.d.ts +16 -0
  202. package/dist/lib/yarn-runner/variable-operations.js +92 -0
  203. package/dist/types/conditionals.d.ts +29 -0
  204. package/dist/types/conditionals.js +2 -0
  205. package/dist/types/constants.d.ts +59 -0
  206. package/dist/types/constants.js +58 -0
  207. package/dist/types/flags.d.ts +49 -0
  208. package/dist/types/flags.js +52 -0
  209. package/dist/types/game-state.d.ts +62 -0
  210. package/dist/types/game-state.js +7 -0
  211. package/dist/types/index.d.ts +77 -0
  212. package/dist/types/index.js +2 -0
  213. package/dist/utils/constants.d.ts +5 -0
  214. package/dist/utils/constants.js +8 -0
  215. package/dist/utils/feature-flags.d.ts +11 -0
  216. package/dist/utils/feature-flags.js +14 -0
  217. package/dist/utils/game-state-flattener.d.ts +41 -0
  218. package/dist/utils/game-state-flattener.js +140 -0
  219. package/dist/utils/layout/collision.d.ts +27 -0
  220. package/dist/utils/layout/collision.js +77 -0
  221. package/dist/utils/layout/index.d.ts +82 -0
  222. package/dist/utils/layout/index.js +109 -0
  223. package/dist/utils/layout/registry.d.ts +91 -0
  224. package/dist/utils/layout/registry.js +151 -0
  225. package/dist/utils/layout/strategies/dagre.d.ts +19 -0
  226. package/dist/utils/layout/strategies/dagre.js +189 -0
  227. package/dist/utils/layout/strategies/force.d.ts +21 -0
  228. package/dist/utils/layout/strategies/force.js +182 -0
  229. package/dist/utils/layout/strategies/grid.d.ts +17 -0
  230. package/dist/utils/layout/strategies/grid.js +95 -0
  231. package/dist/utils/layout/strategies/index.d.ts +8 -0
  232. package/dist/utils/layout/strategies/index.js +14 -0
  233. package/dist/utils/layout/types.d.ts +100 -0
  234. package/dist/utils/layout/types.js +8 -0
  235. package/dist/utils/layout.d.ts +9 -0
  236. package/dist/utils/layout.js +25 -0
  237. package/dist/utils/node-helpers.d.ts +7 -0
  238. package/dist/utils/node-helpers.js +101 -0
  239. package/dist/utils/reactflow-converter.d.ts +42 -0
  240. package/dist/utils/reactflow-converter.js +223 -0
  241. package/package.json +70 -0
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface EdgeIconProps {
3
+ color?: string;
4
+ size?: number;
5
+ className?: string;
6
+ }
7
+ export declare function EdgeIcon({ color, size, className }: EdgeIconProps): React.JSX.Element;
8
+ export {};
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export function EdgeIcon({ color = 'currentColor', size = 24, className = '' }) {
3
+ return (React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className },
4
+ React.createElement("circle", { cx: "6", cy: "12", r: "2", fill: color }),
5
+ React.createElement("circle", { cx: "18", cy: "12", r: "2", fill: color }),
6
+ React.createElement("line", { x1: "8", y1: "12", x2: "16", y2: "12", stroke: color, strokeWidth: "2" })));
7
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { DialogueTree } from '../types';
3
+ import { FlagSchema } from '../types/flags';
4
+ interface ExampleLoaderProps {
5
+ onLoadDialogue: (dialogue: DialogueTree) => void;
6
+ onLoadFlags: (flags: FlagSchema) => void;
7
+ currentDialogue?: DialogueTree;
8
+ currentFlags?: FlagSchema;
9
+ }
10
+ export declare function ExampleLoader({ onLoadDialogue, onLoadFlags }: ExampleLoaderProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { listExamples, listDemoFlagSchemas, getExampleDialogue, getDemoFlagSchema } from '../examples';
3
+ export function ExampleLoader({ onLoadDialogue, onLoadFlags }) {
4
+ const handleDialogueChange = (e) => {
5
+ const name = e.target.value;
6
+ if (name) {
7
+ const dialogue = getExampleDialogue(name);
8
+ if (dialogue) {
9
+ onLoadDialogue(dialogue);
10
+ }
11
+ }
12
+ };
13
+ const handleFlagsChange = (e) => {
14
+ const name = e.target.value;
15
+ if (name) {
16
+ const flags = getDemoFlagSchema(name);
17
+ if (flags) {
18
+ onLoadFlags(flags);
19
+ }
20
+ }
21
+ };
22
+ return (React.createElement("div", { className: "flex items-center gap-3" },
23
+ React.createElement("select", { onChange: handleDialogueChange, defaultValue: "", className: "bg-[#12121a] border border-[#2a2a3e] text-white text-sm px-3 py-1.5 rounded hover:border-[#3a3a4e] focus:outline-none focus:border-[#e94560] cursor-pointer", title: "Load Dialogue Example" },
24
+ React.createElement("option", { value: "", disabled: true }, "Dialogue Example..."),
25
+ listExamples().map(name => {
26
+ const dialogue = getExampleDialogue(name);
27
+ if (!dialogue)
28
+ return null;
29
+ const nodeCount = Object.keys(dialogue.nodes).length;
30
+ return (React.createElement("option", { key: name, value: name },
31
+ dialogue.title,
32
+ " (",
33
+ nodeCount,
34
+ " nodes)"));
35
+ }).filter(Boolean)),
36
+ React.createElement("select", { onChange: handleFlagsChange, defaultValue: "", className: "bg-[#12121a] border border-[#2a2a3e] text-white text-sm px-3 py-1.5 rounded hover:border-[#3a3a4e] focus:outline-none focus:border-[#e94560] cursor-pointer", title: "Load Flag Schema" },
37
+ React.createElement("option", { value: "", disabled: true }, "Flag Schema..."),
38
+ listDemoFlagSchemas().map(name => {
39
+ const flags = getDemoFlagSchema(name);
40
+ return (React.createElement("option", { key: name, value: name },
41
+ name.charAt(0).toUpperCase() + name.slice(1),
42
+ " (",
43
+ flags?.flags.length || 0,
44
+ " flags)"));
45
+ }))));
46
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ExampleLoaderButton - Compact button for loading examples
3
+ *
4
+ * Debug tool for loading example dialogues and flag schemas.
5
+ * Only shown when ENABLE_DEBUG_TOOLS is true.
6
+ */
7
+ import React from 'react';
8
+ import { DialogueTree } from '../types';
9
+ import { FlagSchema } from '../types/flags';
10
+ interface ExampleLoaderButtonProps {
11
+ onLoadDialogue: (dialogue: DialogueTree) => void;
12
+ onLoadFlags: (flags: FlagSchema) => void;
13
+ }
14
+ export declare function ExampleLoaderButton({ onLoadDialogue, onLoadFlags }: ExampleLoaderButtonProps): React.JSX.Element;
15
+ export {};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ExampleLoaderButton - Compact button for loading examples
3
+ *
4
+ * Debug tool for loading example dialogues and flag schemas.
5
+ * Only shown when ENABLE_DEBUG_TOOLS is true.
6
+ */
7
+ import React, { useState } from 'react';
8
+ import { listExamples, listDemoFlagSchemas, getExampleDialogue, getDemoFlagSchema } from '../examples';
9
+ import { FileCode } from 'lucide-react';
10
+ export function ExampleLoaderButton({ onLoadDialogue, onLoadFlags }) {
11
+ const [showMenu, setShowMenu] = useState(false);
12
+ const handleDialogueChange = (name) => {
13
+ const dialogue = getExampleDialogue(name);
14
+ if (dialogue) {
15
+ onLoadDialogue(dialogue);
16
+ setShowMenu(false);
17
+ }
18
+ };
19
+ const handleFlagsChange = (name) => {
20
+ const flags = getDemoFlagSchema(name);
21
+ if (flags) {
22
+ onLoadFlags(flags);
23
+ setShowMenu(false);
24
+ }
25
+ };
26
+ return (React.createElement("div", { className: "relative" },
27
+ React.createElement("button", { onClick: () => setShowMenu(!showMenu), className: `p-1.5 rounded transition-colors ${showMenu
28
+ ? 'bg-[#e94560]/20 text-[#e94560] border border-[#e94560]/50'
29
+ : 'bg-[#12121a] border border-[#2a2a3e] text-gray-400 hover:text-white hover:border-[#3a3a4e]'}`, title: "Load Examples (Debug Tool)" },
30
+ React.createElement(FileCode, { size: 14 })),
31
+ showMenu && (React.createElement("div", { className: "absolute left-full ml-2 top-0 z-50 bg-[#0d0d14] border border-[#2a2a3e] rounded-lg shadow-xl p-1 min-w-[250px]" },
32
+ React.createElement("div", { className: "text-[10px] text-gray-500 uppercase tracking-wider px-2 py-1 border-b border-[#2a2a3e]" }, "Load Examples"),
33
+ React.createElement("div", { className: "px-2 py-1" },
34
+ React.createElement("div", { className: "text-xs text-gray-400 mb-1" }, "Dialogue Examples"),
35
+ React.createElement("select", { onChange: (e) => {
36
+ if (e.target.value)
37
+ handleDialogueChange(e.target.value);
38
+ }, defaultValue: "", className: "w-full bg-[#12121a] border border-[#2a2a3e] text-white text-xs px-2 py-1.5 rounded hover:border-[#3a3a4e] focus:outline-none focus:border-[#e94560] cursor-pointer" },
39
+ React.createElement("option", { value: "" }, "Select dialogue..."),
40
+ listExamples().map(name => {
41
+ const dialogue = getExampleDialogue(name);
42
+ if (!dialogue)
43
+ return null;
44
+ const nodeCount = Object.keys(dialogue.nodes).length;
45
+ return (React.createElement("option", { key: name, value: name },
46
+ dialogue.title,
47
+ " (",
48
+ nodeCount,
49
+ " nodes)"));
50
+ }).filter(Boolean))),
51
+ React.createElement("div", { className: "px-2 py-1 border-t border-[#2a2a3e]" },
52
+ React.createElement("div", { className: "text-xs text-gray-400 mb-1" }, "Flag Schemas"),
53
+ React.createElement("select", { onChange: (e) => {
54
+ if (e.target.value)
55
+ handleFlagsChange(e.target.value);
56
+ }, defaultValue: "", className: "w-full bg-[#12121a] border border-[#2a2a3e] text-white text-xs px-2 py-1.5 rounded hover:border-[#3a3a4e] focus:outline-none focus:border-[#e94560] cursor-pointer" },
57
+ React.createElement("option", { value: "" }, "Select schema..."),
58
+ listDemoFlagSchemas().map(name => {
59
+ const flags = getDemoFlagSchema(name);
60
+ return (React.createElement("option", { key: name, value: name },
61
+ name.charAt(0).toUpperCase() + name.slice(1),
62
+ " (",
63
+ flags?.flags.length || 0,
64
+ " flags)"));
65
+ })))))));
66
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { FlagSchema } from '../types/flags';
3
+ import { DialogueTree } from '../types';
4
+ interface FlagManagerProps {
5
+ flagSchema: FlagSchema;
6
+ dialogue?: DialogueTree;
7
+ onUpdate: (schema: FlagSchema) => void;
8
+ onClose: () => void;
9
+ }
10
+ export declare function FlagManager({ flagSchema, dialogue, onUpdate, onClose }: FlagManagerProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,246 @@
1
+ import React, { useState, useMemo } from 'react';
2
+ const flagTypeColors = {
3
+ dialogue: 'bg-gray-500/20 text-gray-400 border-gray-500/30',
4
+ quest: 'bg-blue-500/20 text-blue-400 border-blue-500/30',
5
+ achievement: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
6
+ item: 'bg-green-500/20 text-green-400 border-green-500/30',
7
+ stat: 'bg-purple-500/20 text-purple-400 border-purple-500/30',
8
+ title: 'bg-pink-500/20 text-pink-400 border-pink-500/30',
9
+ global: 'bg-orange-500/20 text-orange-400 border-orange-500/30',
10
+ };
11
+ const flagTypeLabels = {
12
+ dialogue: 'Dialogue',
13
+ quest: 'Quest',
14
+ achievement: 'Achievement',
15
+ item: 'Item',
16
+ stat: 'Stat',
17
+ title: 'Title',
18
+ global: 'Global',
19
+ };
20
+ const flagTypeIcons = {
21
+ dialogue: '💬',
22
+ quest: '📜',
23
+ achievement: '🏆',
24
+ item: '🎒',
25
+ stat: '📊',
26
+ title: '👑',
27
+ global: '🌐',
28
+ };
29
+ export function FlagManager({ flagSchema, dialogue, onUpdate, onClose }) {
30
+ const [editingFlag, setEditingFlag] = useState(null);
31
+ const [isCreating, setIsCreating] = useState(false);
32
+ const [selectedSection, setSelectedSection] = useState('all');
33
+ // Find all flags used in the dialogue tree
34
+ const usedFlags = useMemo(() => {
35
+ if (!dialogue)
36
+ return new Set();
37
+ const used = new Set();
38
+ Object.values(dialogue.nodes).forEach(node => {
39
+ if (node.setFlags) {
40
+ node.setFlags.forEach(flagId => used.add(flagId));
41
+ }
42
+ if (node.choices) {
43
+ node.choices.forEach(choice => {
44
+ if (choice.setFlags) {
45
+ choice.setFlags.forEach(flagId => used.add(flagId));
46
+ }
47
+ if (choice.conditions) {
48
+ choice.conditions.forEach(cond => used.add(cond.flag));
49
+ }
50
+ });
51
+ }
52
+ });
53
+ return used;
54
+ }, [dialogue]);
55
+ // Group flags by type
56
+ const flagsByType = useMemo(() => {
57
+ const grouped = {
58
+ all: flagSchema.flags,
59
+ };
60
+ Object.keys(flagTypeLabels).forEach(type => {
61
+ grouped[type] = flagSchema.flags.filter(f => f.type === type);
62
+ });
63
+ return grouped;
64
+ }, [flagSchema.flags]);
65
+ const currentFlags = flagsByType[selectedSection] || [];
66
+ const handleCreateFlag = (type) => {
67
+ const newFlag = {
68
+ id: 'new_flag',
69
+ name: 'New Flag',
70
+ type: type || 'dialogue',
71
+ };
72
+ setEditingFlag(newFlag);
73
+ setIsCreating(true);
74
+ };
75
+ const handleSaveFlag = (flag) => {
76
+ if (isCreating) {
77
+ onUpdate({
78
+ ...flagSchema,
79
+ flags: [...flagSchema.flags, flag]
80
+ });
81
+ setIsCreating(false);
82
+ }
83
+ else {
84
+ onUpdate({
85
+ ...flagSchema,
86
+ flags: flagSchema.flags.map(f => f.id === flag.id ? flag : f)
87
+ });
88
+ }
89
+ setEditingFlag(null);
90
+ };
91
+ const handleDeleteFlag = (flagId) => {
92
+ if (confirm(`Delete flag "${flagId}"?`)) {
93
+ onUpdate({
94
+ ...flagSchema,
95
+ flags: flagSchema.flags.filter(f => f.id !== flagId)
96
+ });
97
+ }
98
+ };
99
+ // Get counts for each section
100
+ const sectionCounts = useMemo(() => {
101
+ const counts = { all: flagSchema.flags.length };
102
+ Object.keys(flagTypeLabels).forEach(type => {
103
+ counts[type] = flagsByType[type].length;
104
+ });
105
+ return counts;
106
+ }, [flagSchema.flags, flagsByType]);
107
+ return (React.createElement("div", { className: "fixed inset-0 bg-black/70 z-50 flex items-center justify-center p-4", onClick: (e) => {
108
+ if (e.target === e.currentTarget) {
109
+ onClose();
110
+ }
111
+ } },
112
+ React.createElement("div", { className: "bg-[#0d0d14] border border-[#1a1a2e] rounded-xl max-w-6xl w-full max-h-[90vh] overflow-hidden flex flex-col", onClick: (e) => e.stopPropagation() },
113
+ React.createElement("div", { className: "p-4 border-b border-[#1a1a2e] flex items-center justify-between flex-shrink-0" },
114
+ React.createElement("div", null,
115
+ React.createElement("h2", { className: "text-lg font-semibold text-white" }, "Flag Manager"),
116
+ React.createElement("p", { className: "text-xs text-gray-500 mt-1" }, dialogue && (React.createElement("span", null,
117
+ React.createElement("span", { className: "text-[#e94560] font-semibold" }, usedFlags.size),
118
+ React.createElement("span", { className: "text-gray-500" }, " / "),
119
+ React.createElement("span", { className: "text-gray-400" }, flagSchema.flags.length),
120
+ React.createElement("span", { className: "text-gray-500" }, " flags used in dialogue"))))),
121
+ React.createElement("div", { className: "flex gap-2" },
122
+ React.createElement("button", { onClick: () => handleCreateFlag(selectedSection !== 'all' ? selectedSection : undefined), className: "px-3 py-1.5 bg-[#e94560] hover:bg-[#d63850] text-white text-sm rounded flex items-center gap-2" },
123
+ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
124
+ React.createElement("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
125
+ React.createElement("line", { x1: "5", y1: "12", x2: "19", y2: "12" })),
126
+ "New Flag"),
127
+ React.createElement("button", { onClick: (e) => {
128
+ e.stopPropagation();
129
+ onClose();
130
+ }, className: "p-1.5 text-gray-400 hover:text-white transition-colors", title: "Close" },
131
+ React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
132
+ React.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
133
+ React.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))),
134
+ React.createElement("div", { className: "flex-1 flex overflow-hidden" },
135
+ React.createElement("div", { className: "w-64 border-r border-[#1a1a2e] bg-[#12121a] flex flex-col flex-shrink-0" },
136
+ React.createElement("div", { className: "p-4 border-b border-[#1a1a2e]" },
137
+ React.createElement("div", { className: "text-xs text-gray-500 uppercase tracking-wider mb-3" }, "Sections"),
138
+ React.createElement("nav", { className: "space-y-1" },
139
+ React.createElement("button", { onClick: () => setSelectedSection('all'), className: `w-full text-left px-3 py-2 rounded text-sm transition-colors flex items-center justify-between ${selectedSection === 'all'
140
+ ? 'bg-[#e94560]/20 text-[#e94560] border border-[#e94560]/30'
141
+ : 'text-gray-400 hover:bg-[#1a1a2e] hover:text-white'}` },
142
+ React.createElement("span", null, "All Flags"),
143
+ React.createElement("span", { className: "text-xs text-gray-500" }, sectionCounts.all)),
144
+ Object.entries(flagTypeLabels).map(([type, label]) => (React.createElement("button", { key: type, onClick: () => setSelectedSection(type), className: `w-full text-left px-3 py-2 rounded text-sm transition-colors flex items-center justify-between ${selectedSection === type
145
+ ? `${flagTypeColors[type]} border`
146
+ : 'text-gray-400 hover:bg-[#1a1a2e] hover:text-white'}` },
147
+ React.createElement("span", { className: "flex items-center gap-2" },
148
+ React.createElement("span", null, flagTypeIcons[type]),
149
+ React.createElement("span", null, label)),
150
+ React.createElement("span", { className: "text-xs text-gray-500" }, sectionCounts[type] || 0)))))),
151
+ React.createElement("div", { className: "p-4 border-t border-[#1a1a2e] mt-auto" },
152
+ React.createElement("div", { className: "text-xs text-gray-500 uppercase tracking-wider mb-2" }, "Info"),
153
+ React.createElement("div", { className: "text-xs text-gray-400 space-y-1" },
154
+ React.createElement("p", null,
155
+ React.createElement("span", { className: "text-gray-500" }, "Dialogue flags"),
156
+ " (gray) are temporary and reset after dialogue ends."),
157
+ React.createElement("p", null,
158
+ React.createElement("span", { className: "text-white" }, "Game flags"),
159
+ " (colored) persist and affect the entire game.")))),
160
+ React.createElement("div", { className: "flex-1 overflow-y-auto" }, editingFlag ? (React.createElement("div", { className: "p-6" },
161
+ React.createElement(FlagEditor, { flag: editingFlag, categories: flagSchema.categories || [], onSave: handleSaveFlag, onCancel: () => { setEditingFlag(null); setIsCreating(false); } }))) : (React.createElement("div", { className: "p-6" }, currentFlags.length === 0 ? (React.createElement("div", { className: "text-center py-12 text-gray-500" },
162
+ React.createElement("p", { className: "mb-4" },
163
+ "No ",
164
+ selectedSection === 'all' ? '' : flagTypeLabels[selectedSection],
165
+ " flags defined yet."),
166
+ React.createElement("button", { onClick: () => handleCreateFlag(selectedSection !== 'all' ? selectedSection : undefined), className: "px-4 py-2 bg-[#e94560] hover:bg-[#d63850] text-white rounded" },
167
+ "Create ",
168
+ selectedSection === 'all' ? 'a' : flagTypeLabels[selectedSection],
169
+ " Flag"))) : (React.createElement("div", { className: "space-y-2" }, currentFlags.map(flag => {
170
+ const isUsed = usedFlags.has(flag.id);
171
+ return (React.createElement("div", { key: flag.id, className: `bg-[#12121a] border rounded-lg p-4 hover:bg-[#1a1a2e] transition-colors ${isUsed ? 'border-l-4 border-l-[#e94560]' : 'border-[#1a1a2e]'}` },
172
+ React.createElement("div", { className: "flex items-start justify-between gap-4" },
173
+ React.createElement("div", { className: "flex-1 min-w-0" },
174
+ React.createElement("div", { className: "flex items-center gap-2 mb-2" },
175
+ React.createElement("span", { className: `px-2 py-1 rounded text-xs font-medium border ${flagTypeColors[flag.type]}` }, flagTypeLabels[flag.type]),
176
+ React.createElement("span", { className: "font-mono text-sm text-white" }, flag.id),
177
+ isUsed && (React.createElement("span", { className: "text-[10px] px-1.5 py-0.5 bg-[#e94560]/20 text-[#e94560] rounded border border-[#e94560]/30" }, "Used"))),
178
+ flag.name !== flag.id && (React.createElement("div", { className: "text-sm text-gray-300 mb-1" }, flag.name)),
179
+ flag.description && (React.createElement("div", { className: "text-xs text-gray-500 mt-1" }, flag.description)),
180
+ React.createElement("div", { className: "flex items-center gap-3 mt-2" },
181
+ flag.category && (React.createElement("span", { className: "text-xs text-gray-500" },
182
+ "Category: ",
183
+ flag.category)),
184
+ flag.valueType && (React.createElement("span", { className: "text-xs text-gray-500" },
185
+ "Type: ",
186
+ flag.valueType)),
187
+ flag.defaultValue !== undefined && (React.createElement("span", { className: "text-xs text-gray-500" },
188
+ "Default: ",
189
+ String(flag.defaultValue))))),
190
+ React.createElement("div", { className: "flex gap-1 flex-shrink-0" },
191
+ React.createElement("button", { onClick: () => setEditingFlag(flag), className: "p-2 text-gray-400 hover:text-white hover:bg-[#2a2a3e] rounded transition-colors", title: "Edit" },
192
+ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
193
+ React.createElement("path", { d: "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }),
194
+ React.createElement("path", { d: "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" }))),
195
+ React.createElement("button", { onClick: () => handleDeleteFlag(flag.id), className: "p-2 text-gray-400 hover:text-red-400 hover:bg-red-500/10 rounded transition-colors", title: "Delete" },
196
+ React.createElement("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" },
197
+ React.createElement("polyline", { points: "3 6 5 6 21 6" }),
198
+ React.createElement("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })))))));
199
+ }))))))))));
200
+ }
201
+ function FlagEditor({ flag, categories, onSave, onCancel }) {
202
+ const [edited, setEdited] = useState(flag);
203
+ const flagTypes = ['dialogue', 'quest', 'achievement', 'item', 'stat', 'title', 'global'];
204
+ return (React.createElement("div", { className: "space-y-4 max-w-2xl" },
205
+ React.createElement("div", null,
206
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Flag ID"),
207
+ React.createElement("input", { type: "text", value: edited.id, onChange: (e) => setEdited({ ...edited, id: e.target.value }), className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 font-mono focus:border-[#e94560] outline-none", placeholder: "quest_dragon_slayer" }),
208
+ React.createElement("p", { className: "text-xs text-gray-500 mt-1" }, "Use prefixes: quest_, item_, stat_, etc.")),
209
+ React.createElement("div", null,
210
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Display Name"),
211
+ React.createElement("input", { type: "text", value: edited.name, onChange: (e) => setEdited({ ...edited, name: e.target.value }), className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 focus:border-[#e94560] outline-none", placeholder: "Dragon Slayer Quest" })),
212
+ React.createElement("div", null,
213
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Description"),
214
+ React.createElement("textarea", { value: edited.description || '', onChange: (e) => setEdited({ ...edited, description: e.target.value }), className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 focus:border-[#e94560] outline-none min-h-[60px] resize-y", placeholder: "Optional description..." })),
215
+ React.createElement("div", null,
216
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Flag Type"),
217
+ React.createElement("div", { className: "grid grid-cols-2 gap-2" }, flagTypes.map(type => (React.createElement("button", { key: type, onClick: () => setEdited({ ...edited, type }), className: `px-3 py-2 rounded text-sm border transition-colors ${edited.type === type
218
+ ? flagTypeColors[type] + ' border-current'
219
+ : 'bg-[#12121a] border-[#2a2a3e] text-gray-400 hover:border-[#3a3a4e]'}` }, flagTypeLabels[type])))),
220
+ React.createElement("p", { className: "text-xs text-gray-500 mt-2" }, edited.type === 'dialogue' ? (React.createElement("span", { className: "text-gray-400" }, "Temporary flag - resets after dialogue ends")) : (React.createElement("span", { className: "text-white" }, "Persistent flag - affects entire game")))),
221
+ React.createElement("div", null,
222
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Category"),
223
+ React.createElement("input", { type: "text", value: edited.category || '', onChange: (e) => setEdited({ ...edited, category: e.target.value }), className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 focus:border-[#e94560] outline-none", placeholder: "quests, items, stats, etc.", list: "categories" }),
224
+ React.createElement("datalist", { id: "categories" }, categories.map(cat => React.createElement("option", { key: cat, value: cat })))),
225
+ React.createElement("div", null,
226
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Value Type"),
227
+ React.createElement("select", { value: edited.valueType || '', onChange: (e) => setEdited({ ...edited, valueType: e.target.value }), className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 focus:border-[#e94560] outline-none" },
228
+ React.createElement("option", { value: "" }, "Boolean (true/false)"),
229
+ React.createElement("option", { value: "number" }, "Number"),
230
+ React.createElement("option", { value: "string" }, "String"))),
231
+ edited.valueType && (React.createElement("div", null,
232
+ React.createElement("label", { className: "text-xs text-gray-500 uppercase block mb-1" }, "Default Value"),
233
+ React.createElement("input", { type: edited.valueType === 'number' ? 'number' : 'text', value: edited.defaultValue?.toString() || '', onChange: (e) => {
234
+ let value = e.target.value;
235
+ if (edited.valueType === 'number') {
236
+ value = parseFloat(value) || 0;
237
+ }
238
+ else if (edited.valueType === 'boolean') {
239
+ value = value === 'true';
240
+ }
241
+ setEdited({ ...edited, defaultValue: value });
242
+ }, className: "w-full bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 text-sm text-gray-200 focus:border-[#e94560] outline-none", placeholder: edited.valueType === 'number' ? '0' : edited.valueType === 'string' ? '""' : 'false' }))),
243
+ React.createElement("div", { className: "flex gap-2 pt-4 border-t border-[#1a1a2e]" },
244
+ React.createElement("button", { onClick: () => onSave(edited), className: "flex-1 px-4 py-2 bg-[#e94560] hover:bg-[#d63850] text-white rounded" }, "Save Flag"),
245
+ React.createElement("button", { onClick: onCancel, className: "px-4 py-2 bg-[#12121a] hover:bg-[#1a1a2e] text-gray-300 rounded" }, "Cancel"))));
246
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { FlagSchema } from '../types/flags';
3
+ import 'react-tooltip/dist/react-tooltip.css';
4
+ interface FlagSelectorProps {
5
+ value: string[];
6
+ onChange: (flags: string[]) => void;
7
+ flagSchema?: FlagSchema;
8
+ placeholder?: string;
9
+ }
10
+ export declare function FlagSelector({ value, onChange, flagSchema, placeholder }: FlagSelectorProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,199 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Info, X, BookOpen, Trophy, Package, TrendingUp, Crown, Globe, MessageSquare } from 'lucide-react';
3
+ import { Tooltip } from 'react-tooltip';
4
+ import 'react-tooltip/dist/react-tooltip.css';
5
+ function getFlagColorClasses(flagType) {
6
+ switch (flagType) {
7
+ case 'quest':
8
+ return 'bg-blue-500/20 text-blue-400 border-blue-500/30';
9
+ case 'achievement':
10
+ return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30';
11
+ case 'item':
12
+ return 'bg-green-500/20 text-green-400 border-green-500/30';
13
+ case 'stat':
14
+ return 'bg-purple-500/20 text-purple-400 border-purple-500/30';
15
+ case 'title':
16
+ return 'bg-pink-500/20 text-pink-400 border-pink-500/30';
17
+ case 'global':
18
+ return 'bg-orange-500/20 text-orange-400 border-orange-500/30';
19
+ case 'dialogue':
20
+ default:
21
+ return 'bg-gray-500/20 text-gray-400 border-gray-500/30';
22
+ }
23
+ }
24
+ function getFlagIcon(flagType) {
25
+ switch (flagType) {
26
+ case 'quest':
27
+ return BookOpen;
28
+ case 'achievement':
29
+ return Trophy;
30
+ case 'item':
31
+ return Package;
32
+ case 'stat':
33
+ return TrendingUp;
34
+ case 'title':
35
+ return Crown;
36
+ case 'global':
37
+ return Globe;
38
+ case 'dialogue':
39
+ default:
40
+ return MessageSquare;
41
+ }
42
+ }
43
+ function getFlagTypeDescription(flagType) {
44
+ switch (flagType) {
45
+ case 'quest':
46
+ return 'Quest flags track mission and storyline progression. They persist across game sessions and control quest availability and completion states.';
47
+ case 'achievement':
48
+ return 'Achievement flags mark player accomplishments and milestones. They are permanent and unlock rewards or recognition.';
49
+ case 'item':
50
+ return 'Item flags represent inventory items, equipment, or collectibles. They track what the player has obtained.';
51
+ case 'stat':
52
+ return 'Stat flags store numeric values like reputation, health, or skill levels. They can be incremented, decremented, or compared in conditions.';
53
+ case 'title':
54
+ return 'Title flags represent player titles or ranks. They are typically set based on achievements or story progression.';
55
+ case 'global':
56
+ return 'Global flags are game-wide state variables that affect multiple systems. They persist across all game sessions.';
57
+ case 'dialogue':
58
+ default:
59
+ return 'Dialogue flags are temporary, conversation-scoped variables. They reset between dialogue sessions and are used for local conversation state.';
60
+ }
61
+ }
62
+ export function FlagSelector({ value, onChange, flagSchema, placeholder = "Add flags..." }) {
63
+ const [inputValue, setInputValue] = useState('');
64
+ const [showDropdown, setShowDropdown] = useState(false);
65
+ const [focusedIndex, setFocusedIndex] = useState(null);
66
+ const inputRef = useRef(null);
67
+ const containerRef = useRef(null);
68
+ // Get flag definitions for selected flags
69
+ const selectedFlags = value.map(flagId => {
70
+ const flag = flagSchema?.flags.find(f => f.id === flagId);
71
+ return flag ? { ...flag, id: flagId } : { id: flagId, type: 'dialogue', name: flagId };
72
+ });
73
+ // Filter available flags (not already selected)
74
+ const availableFlags = flagSchema?.flags.filter(flag => !value.includes(flag.id)) || [];
75
+ // Filter flags based on input
76
+ const filteredFlags = inputValue.trim()
77
+ ? availableFlags.filter(flag => flag.id.toLowerCase().includes(inputValue.toLowerCase()) ||
78
+ flag.name.toLowerCase().includes(inputValue.toLowerCase()))
79
+ : availableFlags;
80
+ const flagsByCategory = filteredFlags.reduce((acc, flag) => {
81
+ const cat = flag.category || 'other';
82
+ if (!acc[cat])
83
+ acc[cat] = [];
84
+ acc[cat].push(flag);
85
+ return acc;
86
+ }, {});
87
+ // Handle input change
88
+ const handleInputChange = (e) => {
89
+ const text = e.target.value;
90
+ setInputValue(text);
91
+ setShowDropdown(true);
92
+ };
93
+ // Handle adding a flag
94
+ const handleAddFlag = (flagId) => {
95
+ if (!value.includes(flagId)) {
96
+ onChange([...value, flagId]);
97
+ setInputValue('');
98
+ setShowDropdown(false);
99
+ inputRef.current?.focus();
100
+ }
101
+ };
102
+ // Handle removing a flag
103
+ const handleRemoveFlag = (flagId, e) => {
104
+ e?.stopPropagation();
105
+ onChange(value.filter(id => id !== flagId));
106
+ };
107
+ // Handle input keydown
108
+ const handleKeyDown = (e) => {
109
+ if (e.key === 'Enter' && inputValue.trim()) {
110
+ // Try to find exact match first
111
+ const exactMatch = availableFlags.find(flag => flag.id.toLowerCase() === inputValue.trim().toLowerCase());
112
+ if (exactMatch) {
113
+ handleAddFlag(exactMatch.id);
114
+ e.preventDefault();
115
+ }
116
+ else if (filteredFlags.length > 0) {
117
+ // Add first filtered flag
118
+ handleAddFlag(filteredFlags[0].id);
119
+ e.preventDefault();
120
+ }
121
+ }
122
+ else if (e.key === 'Backspace' && inputValue === '' && value.length > 0) {
123
+ // Remove last flag when backspace on empty input
124
+ handleRemoveFlag(value[value.length - 1]);
125
+ e.preventDefault();
126
+ }
127
+ else if (e.key === 'Escape') {
128
+ setShowDropdown(false);
129
+ inputRef.current?.blur();
130
+ }
131
+ };
132
+ // Handle click outside
133
+ useEffect(() => {
134
+ const handleClickOutside = (event) => {
135
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
136
+ setShowDropdown(false);
137
+ }
138
+ };
139
+ document.addEventListener('mousedown', handleClickOutside);
140
+ return () => document.removeEventListener('mousedown', handleClickOutside);
141
+ }, []);
142
+ return (React.createElement("div", { className: "relative", ref: containerRef },
143
+ React.createElement("div", { className: "w-full min-h-[30px] bg-[#12121a] border border-[#2a2a3e] rounded px-2 py-1.5 flex flex-wrap gap-1.5 items-center cursor-text", onClick: () => inputRef.current?.focus() },
144
+ selectedFlags.map((flag, idx) => {
145
+ const IconComponent = getFlagIcon(flag.type);
146
+ const tooltipId = `flag-tooltip-${flag.id}-${idx}`;
147
+ const description = getFlagTypeDescription(flag.type);
148
+ return (React.createElement("span", { key: flag.id, className: `inline-flex items-center gap-1 px-2 py-0.5 rounded border text-xs font-mono ${getFlagColorClasses(flag.type)}` },
149
+ React.createElement("div", { className: "relative group" },
150
+ React.createElement(IconComponent, { size: 12, className: "cursor-help", "data-tooltip-id": tooltipId }),
151
+ React.createElement(Tooltip, { id: tooltipId, place: "top", border: "2px solid rgba(255, 255, 255, 0.2)", style: {
152
+ backgroundColor: '#000000',
153
+ color: '#ffffff',
154
+ fontFamily: 'monospace',
155
+ fontSize: '11px',
156
+ padding: '10px',
157
+ borderRadius: '6px',
158
+ boxShadow: '0 8px 20px rgba(0, 0, 0, 0.5)',
159
+ maxWidth: '280px',
160
+ zIndex: 10000,
161
+ } },
162
+ React.createElement("div", { style: { fontFamily: 'monospace', fontWeight: 600, color: '#ffffff', marginBottom: '6px', fontSize: '12px', textTransform: 'uppercase' } },
163
+ flag.type,
164
+ " Flag"),
165
+ React.createElement("div", { style: { fontFamily: 'monospace', fontSize: '11px', color: '#ffffff', lineHeight: '1.5' } }, description))),
166
+ React.createElement("span", null, flag.id),
167
+ React.createElement("button", { type: "button", onClick: (e) => handleRemoveFlag(flag.id, e), className: "hover:bg-black/20 rounded p-0.5 transition-colors", title: "Remove flag" },
168
+ React.createElement(X, { size: 12 }))));
169
+ }),
170
+ React.createElement("input", { ref: inputRef, type: "text", value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onFocus: () => setShowDropdown(true), className: "flex-1 min-w-[120px] bg-transparent text-sm text-gray-200 outline-none font-mono placeholder:text-gray-500", placeholder: value.length === 0 ? placeholder : '' }),
171
+ React.createElement("div", { className: "flex-shrink-0" },
172
+ React.createElement(Info, { size: 14, className: "text-gray-500 hover:text-white transition-colors cursor-help", "data-tooltip-id": "flags-tooltip" }))),
173
+ React.createElement(Tooltip, { id: "flags-tooltip", place: "left", border: "2px solid rgba(255, 255, 255, 0.2)", style: {
174
+ backgroundColor: '#000000',
175
+ color: '#ffffff',
176
+ fontFamily: 'monospace',
177
+ fontSize: '12px',
178
+ padding: '12px',
179
+ borderRadius: '8px',
180
+ boxShadow: '0 10px 25px rgba(0, 0, 0, 0.5)',
181
+ maxWidth: '300px',
182
+ zIndex: 9999,
183
+ } },
184
+ React.createElement("div", { style: { display: 'flex', alignItems: 'flex-start', gap: '8px' } },
185
+ React.createElement(Info, { size: 16, style: { color: '#ffffff', flexShrink: 0, marginTop: '2px' } }),
186
+ React.createElement("div", { style: { flex: 1 } },
187
+ React.createElement("div", { style: { fontFamily: 'monospace', fontWeight: 600, color: '#ffffff', marginBottom: '8px', fontSize: '14px' } }, "FLAGS"),
188
+ React.createElement("div", { style: { fontFamily: 'monospace', fontSize: '12px', color: '#ffffff', lineHeight: '1.6' } },
189
+ React.createElement("div", null, "Game state variables that persist across dialogue sessions."),
190
+ React.createElement("div", { style: { marginTop: '4px' } }, "Use to track quest progress, achievements, items, stats, and more."),
191
+ React.createElement("div", { style: { marginTop: '4px' } }, "Flags can be checked in conditions to control dialogue flow."))))),
192
+ showDropdown && flagSchema && filteredFlags.length > 0 && (React.createElement("div", { className: "absolute z-50 mt-1 w-full bg-[#1a1a2e] border border-[#2a2a3e] rounded-lg shadow-xl max-h-64 overflow-y-auto" }, Object.entries(flagsByCategory).map(([category, flags]) => (React.createElement("div", { key: category },
193
+ React.createElement("div", { className: "px-3 py-1.5 text-[10px] text-gray-500 uppercase border-b border-[#2a2a3e] bg-[#12121a]" }, category),
194
+ flags.map(flag => (React.createElement("button", { key: flag.id, type: "button", onClick: () => handleAddFlag(flag.id), onMouseEnter: () => setFocusedIndex(flags.indexOf(flag)), className: `w-full text-left px-3 py-2 text-sm text-gray-300 hover:bg-[#2a2a3e] flex items-center justify-between ${focusedIndex === flags.indexOf(flag) ? 'bg-[#2a2a3e]' : ''}` },
195
+ React.createElement("div", null,
196
+ React.createElement("div", { className: "font-mono text-xs" }, flag.id),
197
+ flag.name !== flag.id && (React.createElement("div", { className: "text-xs text-gray-500" }, flag.name))),
198
+ React.createElement("span", { className: `text-[10px] px-1.5 py-0.5 rounded border ${getFlagColorClasses(flag.type)}` }, flag.type === 'dialogue' ? 'temp' : flag.type)))))))))));
199
+ }