akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (388) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/base/symbols.ts +4 -0
  4. package/client/clientRuntime.ts +8 -0
  5. package/client/cn.ts +7 -0
  6. package/client/csrTypes.ts +2 -0
  7. package/client/frameDebug.ts +3 -2
  8. package/common/Logger.ts +4 -0
  9. package/common/deepObjectify.ts +21 -7
  10. package/common/index.ts +14 -0
  11. package/common/mcpExposure.ts +99 -0
  12. package/common/pathSet.ts +17 -5
  13. package/common/routeConvention.ts +30 -0
  14. package/constant/crystalize.ts +3 -1
  15. package/constant/fieldInfo.ts +6 -0
  16. package/constant/getDefault.ts +37 -9
  17. package/constant/immerify.ts +1 -1
  18. package/constant/index.ts +2 -0
  19. package/constant/labelOf.ts +17 -0
  20. package/constant/mask.ts +60 -0
  21. package/constant/purify.ts +17 -11
  22. package/constant/types.ts +14 -12
  23. package/dictionary/DictionaryLookup.ts +30 -0
  24. package/dictionary/agent.dictionary.ts +31 -0
  25. package/dictionary/agentTurn.dictionary.ts +29 -0
  26. package/dictionary/base.dictionary.ts +8 -0
  27. package/dictionary/dictInfo.ts +8 -0
  28. package/dictionary/dictionary.ts +9 -3
  29. package/dictionary/index.ts +1 -0
  30. package/document/database.ts +21 -0
  31. package/document/filterMeta.ts +7 -0
  32. package/document/index.ts +1 -0
  33. package/document/into.ts +7 -1
  34. package/document/noDocumentError.ts +12 -0
  35. package/fetch/agentTurn.ts +19 -0
  36. package/fetch/client/fetchClient.ts +71 -13
  37. package/fetch/client/wsClient.ts +17 -2
  38. package/fetch/fetchType/endpointFetch.type.ts +10 -4
  39. package/fetch/index.ts +1 -0
  40. package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
  41. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  42. package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
  43. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
  44. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  45. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
  46. package/package.json +11 -9
  47. package/server/akanApp.ts +30 -34
  48. package/server/akanOption.ts +65 -7
  49. package/server/akanServer.ts +172 -7
  50. package/server/animatedImage.ts +83 -0
  51. package/server/artifact/routeClientCache.ts +14 -0
  52. package/server/assetEncoding.ts +47 -0
  53. package/server/devtools/signalSerializer.ts +2 -2
  54. package/server/devtools/types.ts +2 -2
  55. package/server/di/diLifecycle.ts +31 -3
  56. package/server/di/predefinedAdaptor.ts +6 -0
  57. package/server/imageOptimizer.ts +33 -46
  58. package/server/index.ts +1 -2
  59. package/server/mcp/McpAuth.ts +177 -0
  60. package/server/mcp/McpDispatcher.ts +245 -0
  61. package/server/mcp/McpEventStream.ts +76 -0
  62. package/server/mcp/McpExecutionContext.ts +105 -0
  63. package/server/mcp/McpRouter.ts +591 -0
  64. package/server/mcp/index.ts +5 -0
  65. package/server/resolver/CascadeRunner.ts +1 -1
  66. package/server/resolver/database.resolver.ts +42 -5
  67. package/server/resolver/service.resolver.ts +29 -0
  68. package/server/resolver/signal.resolver.ts +16 -1
  69. package/server/routeTreeBuilder.ts +2 -44
  70. package/server/rscWorker.tsx +6 -2
  71. package/server/subRouteIndexDocument.tsx +61 -0
  72. package/server/systemPageDocument.tsx +2 -2
  73. package/server/systemPages.tsx +49 -16
  74. package/server/vendor/akanjs-fetch.ts +1 -0
  75. package/server/webRouter.ts +159 -35
  76. package/service/agent.service.ts +14 -0
  77. package/service/base.service.ts +2 -1
  78. package/service/index.ts +1 -0
  79. package/service/predefinedAdaptor/database.adaptor.ts +78 -24
  80. package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
  81. package/service/predefinedAdaptor/index.ts +3 -0
  82. package/service/predefinedAdaptor/insightQuery.ts +183 -0
  83. package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
  84. package/service/predefinedAdaptor/role.adaptor.ts +2 -0
  85. package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
  86. package/service/serviceModule.ts +17 -0
  87. package/service/types.ts +4 -0
  88. package/signal/agent/AgentCatalogue.ts +165 -0
  89. package/signal/agent/index.ts +1 -0
  90. package/signal/agent.signal.ts +42 -0
  91. package/signal/agentTurn.ts +8 -0
  92. package/signal/agentTurnStream.ts +48 -0
  93. package/signal/base.signal.ts +2 -1
  94. package/signal/endpointInfo.ts +38 -2
  95. package/signal/guard.ts +13 -1
  96. package/signal/guards.ts +52 -1
  97. package/signal/index.ts +6 -0
  98. package/signal/mcp/McpDocument.ts +286 -0
  99. package/signal/mcp/McpProgress.ts +106 -0
  100. package/signal/mcp/McpUriTemplate.ts +85 -0
  101. package/signal/mcp/Msg.ts +368 -0
  102. package/signal/mcp/index.ts +5 -0
  103. package/signal/mcp/mcpProtocol.ts +120 -0
  104. package/signal/middleware.ts +7 -3
  105. package/signal/openapi/openapi.ts +32 -171
  106. package/signal/schema/JsonSchemaBuilder.ts +211 -0
  107. package/signal/schema/index.ts +1 -0
  108. package/signal/signalContext.ts +85 -9
  109. package/signal/slice.ts +9 -9
  110. package/signal/types.ts +1 -1
  111. package/store/action.ts +11 -22
  112. package/store/actionTag.ts +33 -0
  113. package/store/agent/AgentBridge.ts +323 -0
  114. package/store/agent/AgentContext.ts +74 -0
  115. package/store/agent/AgentPrompts.ts +82 -0
  116. package/store/agent/AgentVisibility.ts +68 -0
  117. package/store/agent/ScreenReader.ts +208 -0
  118. package/store/agent/StoreCatalogue.ts +351 -0
  119. package/store/agent/StoreSurfaceSource.ts +122 -0
  120. package/store/agent/index.ts +9 -0
  121. package/store/agent/storeSurface.ts +28 -0
  122. package/store/agent/types.ts +50 -0
  123. package/store/agentic/StToolBuilder.ts +199 -0
  124. package/store/agentic/attachAgentic.ts +22 -0
  125. package/store/agentic/index.ts +5 -0
  126. package/store/agentic/readableValue.ts +17 -0
  127. package/store/agentic/useStExpose.ts +18 -0
  128. package/store/agentic/useStState.ts +35 -0
  129. package/store/baseSt.ts +7 -0
  130. package/store/databaseStateNames.ts +31 -0
  131. package/store/formSetterNames.ts +21 -0
  132. package/store/index.ts +8 -0
  133. package/store/rootStore.ts +3 -1
  134. package/store/sliceRole.ts +36 -0
  135. package/store/state.ts +2 -12
  136. package/store/store.ts +19 -1
  137. package/store/storeInstance.ts +165 -20
  138. package/store/storeRegistry.ts +16 -1
  139. package/store/types.ts +18 -0
  140. package/store/withSelector.ts +7 -8
  141. package/test/registerDom.ts +4 -0
  142. package/types/base/symbols.d.ts +4 -0
  143. package/types/client/cn.d.ts +5 -0
  144. package/types/client/csrTypes.d.ts +2 -0
  145. package/types/common/Logger.d.ts +2 -0
  146. package/types/common/deepObjectify.d.ts +4 -2
  147. package/types/common/index.d.ts +2 -1
  148. package/types/common/mcpExposure.d.ts +60 -0
  149. package/types/common/routeConvention.d.ts +8 -0
  150. package/types/constant/fieldInfo.d.ts +1 -0
  151. package/types/constant/getDefault.d.ts +5 -0
  152. package/types/constant/index.d.ts +2 -0
  153. package/types/constant/labelOf.d.ts +6 -0
  154. package/types/constant/mask.d.ts +34 -0
  155. package/types/constant/purify.d.ts +2 -2
  156. package/types/constant/types.d.ts +2 -2
  157. package/types/dictionary/DictionaryLookup.d.ts +14 -0
  158. package/types/dictionary/agent.dictionary.d.ts +1 -0
  159. package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
  160. package/types/dictionary/base.dictionary.d.ts +1 -1
  161. package/types/dictionary/dictInfo.d.ts +6 -0
  162. package/types/dictionary/dictionary.d.ts +11 -9
  163. package/types/dictionary/index.d.ts +1 -0
  164. package/types/document/database.d.ts +20 -0
  165. package/types/document/filterMeta.d.ts +1 -0
  166. package/types/document/index.d.ts +1 -0
  167. package/types/document/into.d.ts +6 -1
  168. package/types/document/noDocumentError.d.ts +12 -0
  169. package/types/fetch/agentTurn.d.ts +593 -0
  170. package/types/fetch/client/fetchClient.d.ts +10 -0
  171. package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
  172. package/types/fetch/index.d.ts +1 -0
  173. package/types/server/akanOption.d.ts +35 -7
  174. package/types/server/akanServer.d.ts +44 -1
  175. package/types/server/animatedImage.d.ts +7 -0
  176. package/types/server/artifact/routeClientCache.d.ts +6 -0
  177. package/types/server/assetEncoding.d.ts +7 -0
  178. package/types/server/devtools/types.d.ts +2 -2
  179. package/types/server/di/predefinedAdaptor.d.ts +4 -1
  180. package/types/server/index.d.ts +0 -2
  181. package/types/server/mcp/McpAuth.d.ts +62 -0
  182. package/types/server/mcp/McpDispatcher.d.ts +54 -0
  183. package/types/server/mcp/McpEventStream.d.ts +18 -0
  184. package/types/server/mcp/McpExecutionContext.d.ts +44 -0
  185. package/types/server/mcp/McpRouter.d.ts +60 -0
  186. package/types/server/mcp/index.d.ts +5 -0
  187. package/types/server/mcp.d.ts +1 -0
  188. package/types/server/subRouteIndexDocument.d.ts +8 -0
  189. package/types/server/systemPageDocument.d.ts +1 -0
  190. package/types/server/systemPages.d.ts +5 -0
  191. package/types/server/vendor/akanjs-fetch.d.ts +1 -0
  192. package/types/service/agent.service.d.ts +12 -0
  193. package/types/service/base.service.d.ts +4 -0
  194. package/types/service/index.d.ts +1 -0
  195. package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
  196. package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
  197. package/types/service/predefinedAdaptor/index.d.ts +3 -0
  198. package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
  199. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
  200. package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
  201. package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
  202. package/types/service/types.d.ts +4 -1
  203. package/types/signal/agent/AgentCatalogue.d.ts +100 -0
  204. package/types/signal/agent/index.d.ts +1 -0
  205. package/types/signal/agent.d.ts +1 -0
  206. package/types/signal/agent.signal.d.ts +29 -0
  207. package/types/signal/agentTurn.d.ts +9 -0
  208. package/types/signal/agentTurnStream.d.ts +16 -0
  209. package/types/signal/base.signal.d.ts +1 -1
  210. package/types/signal/endpointInfo.d.ts +21 -3
  211. package/types/signal/guard.d.ts +11 -0
  212. package/types/signal/guards.d.ts +22 -1
  213. package/types/signal/index.d.ts +6 -0
  214. package/types/signal/mcp/McpDocument.d.ts +65 -0
  215. package/types/signal/mcp/McpProgress.d.ts +40 -0
  216. package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
  217. package/types/signal/mcp/Msg.d.ts +143 -0
  218. package/types/signal/mcp/index.d.ts +5 -0
  219. package/types/signal/mcp/mcpProtocol.d.ts +107 -0
  220. package/types/signal/mcp.d.ts +1 -0
  221. package/types/signal/openapi/openapi.d.ts +3 -3
  222. package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
  223. package/types/signal/schema/index.d.ts +1 -0
  224. package/types/signal/schema.d.ts +1 -0
  225. package/types/signal/signalContext.d.ts +16 -1
  226. package/types/signal/slice.d.ts +8 -8
  227. package/types/signal/types.d.ts +1 -1
  228. package/types/store/actionTag.d.ts +21 -0
  229. package/types/store/agent/AgentBridge.d.ts +77 -0
  230. package/types/store/agent/AgentContext.d.ts +16 -0
  231. package/types/store/agent/AgentPrompts.d.ts +31 -0
  232. package/types/store/agent/AgentVisibility.d.ts +21 -0
  233. package/types/store/agent/ScreenReader.d.ts +11 -0
  234. package/types/store/agent/StoreCatalogue.d.ts +26 -0
  235. package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
  236. package/types/store/agent/index.d.ts +9 -0
  237. package/types/store/agent/storeSurface.d.ts +14 -0
  238. package/types/store/agent/types.d.ts +49 -0
  239. package/types/store/agent.d.ts +1 -0
  240. package/types/store/agentic/StToolBuilder.d.ts +41 -0
  241. package/types/store/agentic/attachAgentic.d.ts +14 -0
  242. package/types/store/agentic/index.d.ts +5 -0
  243. package/types/store/agentic/readableValue.d.ts +3 -0
  244. package/types/store/agentic/useStExpose.d.ts +8 -0
  245. package/types/store/agentic/useStState.d.ts +12 -0
  246. package/types/store/agentic.d.ts +1 -0
  247. package/types/store/baseSt.d.ts +29 -22
  248. package/types/store/databaseStateNames.d.ts +25 -0
  249. package/types/store/formSetterNames.d.ts +16 -0
  250. package/types/store/index.d.ts +7 -0
  251. package/types/store/rootStore.d.ts +5 -1
  252. package/types/store/sliceRole.d.ts +25 -0
  253. package/types/store/store.d.ts +7 -2
  254. package/types/store/storeInstance.d.ts +33 -1
  255. package/types/store/storeRegistry.d.ts +5 -0
  256. package/types/store/types.d.ts +15 -0
  257. package/types/store/withSelector.d.ts +7 -8
  258. package/types/test/registerDom.d.ts +1 -0
  259. package/types/ui/Agent/Approval.d.ts +7 -0
  260. package/types/ui/Agent/Bubble.d.ts +13 -0
  261. package/types/ui/Agent/Chat.d.ts +24 -0
  262. package/types/ui/Agent/Context.d.ts +9 -0
  263. package/types/ui/Agent/Dock.d.ts +16 -0
  264. package/types/ui/Agent/Guide.d.ts +9 -0
  265. package/types/ui/Agent/Section.d.ts +10 -0
  266. package/types/ui/Agent/StateKey.d.ts +16 -0
  267. package/types/ui/Agent/Tool.d.ts +15 -0
  268. package/types/ui/Agent/Transcript.d.ts +13 -0
  269. package/types/ui/Agent/Zone.d.ts +23 -0
  270. package/types/ui/Agent/fetchRunner.d.ts +12 -0
  271. package/types/ui/Agent/index.d.ts +17 -0
  272. package/types/ui/Agent/index_.d.ts +1 -0
  273. package/types/ui/Agent/sessionHistory.d.ts +12 -0
  274. package/types/ui/Agent.d.ts +1 -0
  275. package/types/ui/Badge.d.ts +1 -1
  276. package/types/ui/Button.d.ts +1 -1
  277. package/types/ui/CsrImage.d.ts +1 -1
  278. package/types/ui/Dropdown.d.ts +2 -0
  279. package/types/ui/Image.d.ts +3 -3
  280. package/types/ui/Layout/BottomInset.d.ts +2 -1
  281. package/types/ui/Layout/index.d.ts +1 -1
  282. package/types/ui/Signal/style.d.ts +4 -1
  283. package/types/ui/UiOverride/context.d.ts +14 -2
  284. package/types/ui/agentAttrs.d.ts +14 -0
  285. package/types/ui/index.d.ts +4 -1
  286. package/types/ui/overlayLayer.d.ts +24 -0
  287. package/types/ui/recipe/badgeRecipe.d.ts +7 -3
  288. package/types/ui/recipe/buttonRecipe.d.ts +7 -3
  289. package/types/ui/recipe/inputRecipe.d.ts +5 -1
  290. package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
  291. package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
  292. package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
  293. package/types/vendor/use-agentic/Agentic.d.ts +21 -0
  294. package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
  295. package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
  296. package/types/vendor/use-agentic/index.d.ts +13 -0
  297. package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
  298. package/types/vendor/use-agentic/types.d.ts +133 -0
  299. package/types/vendor/use-agentic/useAgent.d.ts +4 -0
  300. package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
  301. package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
  302. package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
  303. package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
  304. package/types/vendor/use-agentic.d.ts +1 -0
  305. package/types/webkit/index.d.ts +3 -0
  306. package/types/webkit/lazy.d.ts +12 -0
  307. package/types/webkit/useCsrValues.d.ts +3 -3
  308. package/types/webkit/useEscapeKey.d.ts +5 -0
  309. package/types/webkit/useFrameRuntime.d.ts +6 -1
  310. package/types/webkit/useScreenScope.d.ts +18 -0
  311. package/ui/Agent/Approval.tsx +29 -0
  312. package/ui/Agent/Bubble.tsx +74 -0
  313. package/ui/Agent/Chat.tsx +225 -0
  314. package/ui/Agent/Context.tsx +38 -0
  315. package/ui/Agent/Dock.tsx +72 -0
  316. package/ui/Agent/Guide.tsx +16 -0
  317. package/ui/Agent/Section.tsx +24 -0
  318. package/ui/Agent/StateKey.tsx +44 -0
  319. package/ui/Agent/Tool.tsx +66 -0
  320. package/ui/Agent/Transcript.tsx +33 -0
  321. package/ui/Agent/Zone.tsx +48 -0
  322. package/ui/Agent/fetchRunner.ts +30 -0
  323. package/ui/Agent/index.ts +12 -0
  324. package/ui/Agent/index_.tsx +4 -0
  325. package/ui/Agent/sessionHistory.ts +33 -0
  326. package/ui/Badge.tsx +3 -3
  327. package/ui/BottomSheet.tsx +5 -0
  328. package/ui/Button.tsx +6 -2
  329. package/ui/Constant/Doc.tsx +1 -1
  330. package/ui/CsrImage.tsx +1 -1
  331. package/ui/Data/ListContainer.tsx +1 -1
  332. package/ui/DatePicker.tsx +5 -4
  333. package/ui/Dialog/Modal.tsx +12 -15
  334. package/ui/DraggableList.tsx +3 -1
  335. package/ui/Dropdown.tsx +33 -11
  336. package/ui/Field.tsx +30 -16
  337. package/ui/Image.tsx +3 -3
  338. package/ui/Input.tsx +14 -5
  339. package/ui/Layout/BottomInset.tsx +6 -1
  340. package/ui/Load/Units.tsx +11 -2
  341. package/ui/Load/View.tsx +7 -2
  342. package/ui/Loading/ProgressBar.tsx +8 -1
  343. package/ui/Menu.tsx +7 -8
  344. package/ui/Model/EditModal.tsx +37 -2
  345. package/ui/Model/SureToRemove.tsx +2 -1
  346. package/ui/Model/index_.tsx +42 -15
  347. package/ui/ObjectId.tsx +3 -4
  348. package/ui/Pagination.tsx +3 -4
  349. package/ui/Popconfirm.tsx +8 -7
  350. package/ui/Select.tsx +6 -4
  351. package/ui/Signal/RestApi.tsx +43 -16
  352. package/ui/Signal/WebSocket.tsx +1 -1
  353. package/ui/Signal/style.ts +6 -1
  354. package/ui/Switch.tsx +2 -0
  355. package/ui/System/CSR.tsx +6 -2
  356. package/ui/System/ThemeToggle.tsx +55 -33
  357. package/ui/ToggleSelect.tsx +4 -3
  358. package/ui/Tooltip.tsx +2 -1
  359. package/ui/UiOverride/context.ts +14 -2
  360. package/ui/agentAttrs.ts +19 -0
  361. package/ui/index.ts +10 -1
  362. package/ui/overlayLayer.ts +39 -0
  363. package/ui/recipe/badgeRecipe.ts +22 -3
  364. package/ui/recipe/buttonRecipe.ts +51 -2
  365. package/ui/recipe/factory.ts +2 -2
  366. package/ui/recipe/inputRecipe.ts +18 -4
  367. package/vendor/use-agentic/AgentProvider.tsx +39 -0
  368. package/vendor/use-agentic/AgentScope.tsx +20 -0
  369. package/vendor/use-agentic/AgentSession.ts +292 -0
  370. package/vendor/use-agentic/Agentic.tsx +44 -0
  371. package/vendor/use-agentic/AgenticSurface.ts +294 -0
  372. package/vendor/use-agentic/WIRE.md +57 -0
  373. package/vendor/use-agentic/httpRunner.ts +115 -0
  374. package/vendor/use-agentic/index.ts +14 -0
  375. package/vendor/use-agentic/surfaceContext.ts +9 -0
  376. package/vendor/use-agentic/types.ts +144 -0
  377. package/vendor/use-agentic/useAgent.ts +17 -0
  378. package/vendor/use-agentic/useAgentGuide.ts +16 -0
  379. package/vendor/use-agentic/useAgentResource.ts +30 -0
  380. package/vendor/use-agentic/useAgentState.ts +61 -0
  381. package/vendor/use-agentic/useAgentTool.ts +55 -0
  382. package/webkit/bootCsr.tsx +2 -33
  383. package/webkit/index.ts +3 -0
  384. package/webkit/lazy.tsx +27 -3
  385. package/webkit/useCsrValues.ts +121 -4
  386. package/webkit/useEscapeKey.tsx +42 -0
  387. package/webkit/useFrameRuntime.ts +65 -32
  388. package/webkit/useScreenScope.tsx +49 -0
@@ -7,6 +7,7 @@ import { createContext, type ReactElement, type ReactNode, useContext, useRef }
7
7
  import { BiTrash } from "react-icons/bi";
8
8
  import { MdDragIndicator } from "react-icons/md";
9
9
  import { buttonRecipe } from "./Button";
10
+ import { useUiRecipe } from "./UiOverride";
10
11
 
11
12
  const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max);
12
13
 
@@ -169,6 +170,7 @@ const Item = ({
169
170
  removeClassName,
170
171
  }: ItemProps) => {
171
172
  const { onRemove } = useDragList();
173
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
172
174
  return (
173
175
  <div className={cn("flex w-full items-center gap-2", className)}>
174
176
  {cursor ? (
@@ -179,7 +181,7 @@ const Item = ({
179
181
  {children}
180
182
  {removable && (
181
183
  <button
182
- className={buttonRecipe({ variant: "outline", size: "xs" }, [
184
+ className={recipe({ variant: "outline", size: "xs" }, [
183
185
  "size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
184
186
  removeClassName,
185
187
  ])}
package/ui/Dropdown.tsx CHANGED
@@ -1,9 +1,15 @@
1
1
  "use client";
2
2
  import { cn } from "akanjs/client";
3
- import { type ReactNode, useEffect, useRef, useState } from "react";
3
+ import { type ReactNode, useEffect, useId, useRef, useState } from "react";
4
4
 
5
5
  import { buttonRecipe } from "./Button";
6
- import { createOverridable } from "./UiOverride";
6
+ import { isOwnOverlayClick, OverlayOwnerProvider, useOverlayScope } from "./overlayLayer";
7
+ import { createOverridable, useUiRecipe } from "./UiOverride";
8
+
9
+ /** Put this on a menu item that runs its own interaction (a switch, a copy button) to keep the menu open. */
10
+ export const DROPDOWN_KEEP_OPEN_ATTR = "data-dropdown-keep-open";
11
+
12
+ const keepOpenSelector = `[${DROPDOWN_KEEP_OPEN_ATTR}]`;
7
13
 
8
14
  export interface DropdownProps {
9
15
  /** Button/trigger content. */
@@ -20,39 +26,55 @@ export interface DropdownProps {
20
26
 
21
27
  export const DefaultDropdown = ({ value, content, className, buttonClassName, dropdownClassName }: DropdownProps) => {
22
28
  const [opened, setOpened] = useState(false);
29
+ const [mounted, setMounted] = useState(false);
23
30
  const ref = useRef<HTMLDivElement>(null);
31
+ const scope = useOverlayScope(useId());
32
+
33
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
24
34
  useEffect(() => {
25
35
  if (!opened) return;
26
- const onClick = (e: MouseEvent) => {
36
+ const onMouseDown = (e: MouseEvent) => {
37
+
38
+ if (isOwnOverlayClick(e.target, scope)) return;
27
39
  if (ref.current && !ref.current.contains(e.target as Node)) setOpened(false);
28
40
  };
29
- document.addEventListener("mousedown", onClick);
41
+ document.addEventListener("mousedown", onMouseDown);
30
42
  return () => {
31
- document.removeEventListener("mousedown", onClick);
43
+ document.removeEventListener("mousedown", onMouseDown);
32
44
  };
33
- }, [opened]);
45
+ }, [opened, scope]);
34
46
  return (
35
47
  <div ref={ref} className={cn("relative inline-block", className)}>
36
48
  <button
37
49
  type="button"
38
- className={buttonRecipe({ variant: "ghost" }, ["flex", buttonClassName])}
50
+ aria-haspopup="menu"
51
+ aria-expanded={opened}
52
+ className={recipe({ variant: "ghost" }, ["flex", buttonClassName])}
39
53
  onClick={() => {
54
+ setMounted(true);
40
55
  setOpened((o) => !o);
41
56
  }}
42
57
  >
43
58
  {value}
44
59
  </button>
45
- {opened ? (
60
+ {/* Hidden rather than unmounted once opened: unmounting takes any overlay a menu item opened down with it. */}
61
+ {mounted ? (
46
62
  <ul
47
- onClick={() => {
63
+ hidden={!opened}
64
+ onClick={(e) => {
65
+
66
+ if (isOwnOverlayClick(e.target, scope)) return;
67
+ if (e.target instanceof Element && e.target.closest(keepOpenSelector)) return;
48
68
  setOpened(false);
49
69
  }}
50
70
  className={cn(
51
- "absolute right-0 z-[100] mt-1 grid max-h-52 gap-2 overflow-auto whitespace-nowrap rounded-md bg-popover p-1 pr-3 text-popover-foreground shadow-md",
71
+ "absolute right-0 z-[100] mt-1 grid max-h-52 gap-0.5 overflow-auto whitespace-nowrap rounded-md bg-popover p-1 text-popover-foreground shadow-md",
52
72
  dropdownClassName,
73
+ !opened && "hidden",
53
74
  )}
54
75
  >
55
- {content}
76
+ {/* Reaches an overlay this menu opens even after it portals away, so it can claim it as its own. */}
77
+ <OverlayOwnerProvider value={scope}>{content}</OverlayOwnerProvider>
56
78
  </ul>
57
79
  ) : null}
58
80
  </div>
package/ui/Field.tsx CHANGED
@@ -9,14 +9,17 @@ import { AiOutlinePlus } from "react-icons/ai";
9
9
  import { BiHelpCircle, BiTrash, BiX } from "react-icons/bi";
10
10
  import { MdDragIndicator } from "react-icons/md";
11
11
 
12
+ import { agentAttrs } from "./agentAttrs";
12
13
  import { badgeRecipe } from "./Badge";
13
14
  import { buttonRecipe } from "./Button";
14
15
  import { DraggableList } from "./DraggableList";
15
16
  import { Input } from "./Input";
17
+ import { inputRecipe } from "./recipe";
16
18
  import { Select } from "./Select";
17
19
  import { Switch as UiSwitch } from "./Switch";
18
20
  import { ToggleSelect as UtilToggleSelect } from "./ToggleSelect";
19
21
  import { Tooltip } from "./Tooltip";
22
+ import { useUiRecipe } from "./UiOverride";
20
23
 
21
24
  interface LabelProps {
22
25
  className?: string;
@@ -101,6 +104,7 @@ const List = <Item,>({
101
104
  renderItem,
102
105
  }: ListProps<Item>) => {
103
106
  const { l } = usePage();
107
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
104
108
  return (
105
109
  <div className={cn("flex w-full flex-col", className)}>
106
110
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
@@ -111,7 +115,7 @@ const List = <Item,>({
111
115
  {renderItem(item, idx)}
112
116
  <div className="flex gap-2 border-border border-l pl-2">
113
117
  <button
114
- className={buttonRecipe(
118
+ className={recipe(
115
119
  { variant: "outline", size: "icon" },
116
120
  "size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
117
121
  )}
@@ -127,7 +131,7 @@ const List = <Item,>({
127
131
  </>
128
132
  ))}
129
133
  <button
130
- className={buttonRecipe({ variant: "outline" })}
134
+ className={recipe({ variant: "outline" })}
131
135
  onClick={() => {
132
136
  onAdd();
133
137
  }}
@@ -183,6 +187,7 @@ const Text = ({
183
187
  <div className={cn("flex flex-col", className)}>
184
188
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
185
189
  <Input
190
+ {...agentAttrs(onChange)}
186
191
  cacheKey={cache ? `${label}-${desc}-text` : undefined}
187
192
  inputStyleType={inputStyleType}
188
193
  value={value ?? ""}
@@ -247,6 +252,7 @@ const Price = ({
247
252
  <div className={cn("flex flex-col", className)}>
248
253
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
249
254
  <Input
255
+ {...agentAttrs(onChange)}
250
256
  inputStyleType={inputStyleType}
251
257
  value={value ?? ""}
252
258
  nullable={nullable}
@@ -313,6 +319,7 @@ const TextArea = ({
313
319
  <div className={cn("flex flex-col", className)}>
314
320
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
315
321
  <Input.TextArea
322
+ {...agentAttrs(onChange)}
316
323
  value={value ?? ""}
317
324
  cacheKey={cache ? `${label}-${desc}-textArea` : undefined}
318
325
  nullable={nullable}
@@ -361,7 +368,7 @@ const Switch = ({
361
368
  offDesc,
362
369
  }: SwitchProps) => {
363
370
  return (
364
- <div className={cn("flex flex-col", className)}>
371
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
365
372
  {label ? <Label className={labelClassName} nullable label={label} desc={desc} /> : null}
366
373
  <div className="flex items-center gap-2">
367
374
  <UiSwitch
@@ -411,7 +418,7 @@ const ToggleSelect = <I extends string | number | boolean | null>({
411
418
  const { l } = usePage();
412
419
  const isEnumValue = isEnum(items as EnumInstance<string, I>);
413
420
  return (
414
- <div className={cn("flex flex-col", className)}>
421
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
415
422
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
416
423
  <UtilToggleSelect
417
424
  className="mt-2"
@@ -468,7 +475,7 @@ const MultiToggleSelect = <I extends string | number | boolean>({
468
475
  const { l } = usePage();
469
476
  const isEnumValue = isEnum(items as EnumInstance<string, I>);
470
477
  return (
471
- <div className={cn("flex flex-col", className)}>
478
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
472
479
  {label ? <Label className={labelClassName} nullable={!!minlength} label={label} desc={desc} /> : null}
473
480
  <UtilToggleSelect.Multi
474
481
  nullable={!minlength}
@@ -533,8 +540,9 @@ const TextList = ({
533
540
  inputClassName,
534
541
  }: TextListProps) => {
535
542
  const { l } = usePage();
543
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
536
544
  return (
537
- <div className={cn("flex flex-col", className)}>
545
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
538
546
  {label ? <Label className={labelClassName} nullable={!minlength} label={label} desc={desc} /> : null}
539
547
  <div className="mb-5 h-full gap-2 rounded-md border border-border p-2">
540
548
  <DraggableList
@@ -570,7 +578,7 @@ const TextList = ({
570
578
  disabled={disabled}
571
579
  />
572
580
  <button
573
- className={buttonRecipe(
581
+ className={recipe(
574
582
  { variant: "outline", size: "icon" },
575
583
  "size-6 border-destructive p-0 text-destructive hover:bg-destructive hover:text-destructive-foreground",
576
584
  )}
@@ -588,7 +596,7 @@ const TextList = ({
588
596
  <div className="my-5 h-[0.5px] bg-foreground/20" />
589
597
  {value.length <= maxTextlength ? (
590
598
  <button
591
- className={buttonRecipe({ variant: "outline" }, "w-full")}
599
+ className={recipe({ variant: "outline" }, "w-full")}
592
600
  onClick={() => {
593
601
  onChange([...value, ""]);
594
602
  }}
@@ -638,6 +646,7 @@ const Tags = ({
638
646
  inputClassName,
639
647
  }: TagsProps) => {
640
648
  const { l } = usePage();
649
+ const badge = useUiRecipe("badge") ?? badgeRecipe;
641
650
  const [inputVisible, setInputVisible] = useState(false);
642
651
  const [tag, setTag] = useState("");
643
652
  const addTag = () => {
@@ -648,11 +657,11 @@ const Tags = ({
648
657
  };
649
658
 
650
659
  return (
651
- <div className={cn("flex flex-col", className)}>
660
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
652
661
  {label ? <Label className={labelClassName} nullable={!minlength} label={label} desc={desc} /> : null}
653
662
  <div className="flex w-full flex-wrap items-center gap-1 rounded-md border border-foreground/20 p-2">
654
663
  {value.map((val, idx) => (
655
- <span className={badgeRecipe({ variant: "outline" }, "items-center")} key={idx}>
664
+ <span className={badge({ variant: "outline" }, "items-center")} key={idx}>
656
665
  <div className="text-xs italic">#</div>
657
666
  {val}
658
667
  <BiX
@@ -731,15 +740,16 @@ const Date = <Nullable extends boolean>({
731
740
  dateClassName,
732
741
  }: DateProps<Nullable>) => {
733
742
  return (
734
- <div className={cn("flex flex-col", className)}>
743
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
735
744
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
736
- {/* //! daysi UI datetime-local 컴포넌트에 max 값 넣으면 오른쪽 끝 짤리는 버그 있음.*/}
745
+ {/* FIXME: daysi UI datetime-local 컴포넌트에 max 값 넣으면 오른쪽 끝 짤리는 버그 있음.*/}
737
746
  <input
738
747
  type={showTime ? "datetime-local" : "date"}
739
- className={cn(
740
- "input validator text-xs outline-none duration-200 focus-within:outline-none focus:outline-none",
748
+ className={inputRecipe({}, [
749
+
750
+ "user-invalid:border-destructive text-xs outline-none duration-200 focus-within:outline-none focus:outline-none",
741
751
  dateClassName,
742
- )}
752
+ ])}
743
753
  min={min ? (showTime ? dayjs(min).format("YYYY-MM-DDTHH:mm") : dayjs(min).format("YYYY-MM-DD")) : undefined}
744
754
  max={max ? (showTime ? dayjs(max).format("YYYY-MM-DDTHH:mm") : dayjs(max).format("YYYY-MM-DD")) : undefined}
745
755
  value={value ? (showTime ? dayjs(value).format("YYYY-MM-DDTHH:mm") : dayjs(value).format("YYYY-MM-DD")) : ""}
@@ -868,6 +878,7 @@ const Number = ({
868
878
  <div className={cn("flex flex-col", className)}>
869
879
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} unit={unit} /> : null}
870
880
  <Input.Number
881
+ {...agentAttrs(onChange)}
871
882
  min={min}
872
883
  max={max}
873
884
  cacheKey={cache ? `${label}-${desc}-number` : undefined}
@@ -935,7 +946,7 @@ const DoubleNumber = ({
935
946
  }: DoubleNumberProps) => {
936
947
  const { l } = usePage();
937
948
  return (
938
- <div className={cn("flex flex-col", className)}>
949
+ <div {...agentAttrs(onChange)} className={cn("flex flex-col", className)}>
939
950
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
940
951
  <div className="flex items-center gap-2">
941
952
  <Input.Number
@@ -1025,6 +1036,7 @@ const Email = ({
1025
1036
  <div className={cn("flex flex-col", className)}>
1026
1037
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
1027
1038
  <Input.Email
1039
+ {...agentAttrs(onChange)}
1028
1040
  value={value ?? ""}
1029
1041
  cacheKey={cache ? `${label}-${desc}-email` : undefined}
1030
1042
  nullable={nullable}
@@ -1089,6 +1101,7 @@ const Phone = ({
1089
1101
  <div className={cn("flex flex-col", className)}>
1090
1102
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
1091
1103
  <Input
1104
+ {...agentAttrs(onChange)}
1092
1105
  value={value ?? ""}
1093
1106
  cacheKey={cache ? `${label}-${desc}-phone` : undefined}
1094
1107
  nullable={nullable}
@@ -1159,6 +1172,7 @@ const Password = ({
1159
1172
  {label ? <Label className={labelClassName} nullable={nullable} label={label} desc={desc} /> : null}
1160
1173
  <div className="flex flex-col gap-2">
1161
1174
  <Input.Password
1175
+ {...agentAttrs(onChange)}
1162
1176
  cacheKey={cache ? `${label}-${desc}-password` : undefined}
1163
1177
  value={value ?? ""}
1164
1178
  nullable={nullable}
package/ui/Image.tsx CHANGED
@@ -30,7 +30,7 @@ type AkanImageProps = NativeImageProps & {
30
30
  /** Akan file object or file-like value with url and imageSize metadata. */
31
31
  file?: ImageLikeFile;
32
32
  /** Low-quality preview data. Overrides file.abstractData when provided. */
33
- abstractData?: string;
33
+ abstractData?: string | null;
34
34
  /** Accessible alt text. Defaults to "image" when omitted. */
35
35
  alt?: string;
36
36
  /** Image optimizer quality. Defaults to 75. */
@@ -59,12 +59,12 @@ export const Image = ({
59
59
  | {
60
60
  src?: string;
61
61
  file?: ProtoFile;
62
- abstractData?: string;
62
+ abstractData?: string | null;
63
63
  alt?: string;
64
64
  }
65
65
  | {
66
66
  src?: undefined;
67
- abstractData?: string;
67
+ abstractData?: string | null;
68
68
  file: { url: string; imageSize: [number, number]; abstractData?: string | null } | null;
69
69
  alt?: string;
70
70
  }
package/ui/Input.tsx CHANGED
@@ -13,11 +13,9 @@ import React, {
13
13
  } from "react";
14
14
  import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai";
15
15
 
16
+ import { agentAttrs } from "./agentAttrs";
16
17
  import { inputRecipe } from "./recipe";
17
- import { createOverridable } from "./UiOverride";
18
-
19
- const inputBase = inputRecipe();
20
- const textareaBase = inputRecipe({ kind: "area" });
18
+ import { createOverridable, useUiRecipe } from "./UiOverride";
21
19
 
22
20
  export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> & {
23
21
  /** Visual input style. */
@@ -64,6 +62,7 @@ const DefaultInput = ({
64
62
  const { l } = usePage();
65
63
  const [firstFocus, setFirstFocus] = useState(true);
66
64
  const validateResult = validate ? validate(value) : undefined;
65
+ const inputBase = (useUiRecipe("input") ?? inputRecipe)();
67
66
  const status: "error" | "warning" | "success" | null =
68
67
  !nullable && !value ? null : !value.length ? "warning" : validateResult === true ? "success" : "error";
69
68
  const invalidMessage =
@@ -110,6 +109,7 @@ const DefaultInput = ({
110
109
  {icon ? <div className={cn("flex items-center justify-center", iconClassName)}>{icon}</div> : null}
111
110
  <input
112
111
  {...rest}
112
+ {...agentAttrs(onChange)}
113
113
  ref={inputRef}
114
114
  value={value}
115
115
  onChange={(e) => {
@@ -169,6 +169,7 @@ const DefaultTextArea = ({
169
169
  const { l } = usePage();
170
170
  const inputRef = useRef<HTMLTextAreaElement>(null);
171
171
  const validateResult = validate(value);
172
+ const textareaBase = (useUiRecipe("input") ?? inputRecipe)({ kind: "area" });
172
173
  const [firstFocus, setFirstFocus] = useState(true);
173
174
  const status: "error" | "warning" | "success" =
174
175
  !nullable && !value.length ? "warning" : validateResult === true ? "success" : "error";
@@ -202,6 +203,7 @@ const DefaultTextArea = ({
202
203
  <div className={cn("relative mb-5", className)}>
203
204
  <textarea
204
205
  {...rest}
206
+ {...agentAttrs(onChange)}
205
207
  ref={inputRef}
206
208
  value={value}
207
209
  onChange={(e) => {
@@ -263,6 +265,7 @@ const DefaultPassword = ({
263
265
  const validateResult = validate(value);
264
266
  const [firstFocus, setFirstFocus] = useState(true);
265
267
  const [showPassword, setShowPassword] = useState(false);
268
+ const inputBase = (useUiRecipe("input") ?? inputRecipe)();
266
269
  const status: "error" | "warning" | "success" =
267
270
  !nullable && !value.length ? "warning" : validateResult === true ? "success" : "error";
268
271
  const invalidMessage =
@@ -301,6 +304,7 @@ const DefaultPassword = ({
301
304
  ) : null}
302
305
  <input
303
306
  {...rest}
307
+ {...agentAttrs(onChange)}
304
308
  type={showPassword ? "text" : "password"}
305
309
  ref={inputRef}
306
310
  value={value}
@@ -373,6 +377,7 @@ const DefaultEmail = ({
373
377
  const { l } = usePage();
374
378
  const inputRef = useRef<HTMLInputElement>(null);
375
379
  const isValidEmail = isEmail(value);
380
+ const inputBase = (useUiRecipe("input") ?? inputRecipe)();
376
381
  const [firstFocus, setFirstFocus] = useState(true);
377
382
  const validateResult = !isValidEmail ? l("base.emailInvalidError") : validate(value);
378
383
  const status: "error" | "warning" | "success" =
@@ -418,6 +423,7 @@ const DefaultEmail = ({
418
423
  ) : null}
419
424
  <input
420
425
  {...rest}
426
+ {...agentAttrs(onChange)}
421
427
  type="email"
422
428
  value={value}
423
429
  ref={inputRef}
@@ -484,6 +490,7 @@ const DefaultNumber = ({
484
490
  const { l } = usePage();
485
491
  const inputRef = useRef<HTMLInputElement>(null);
486
492
  const validateResult = validate ? validate(value) : undefined;
493
+ const inputBase = (useUiRecipe("input") ?? inputRecipe)();
487
494
  const generateFormat = () => {
488
495
  return isNaN(value ?? 0) ? "" : formatter ? formatter(value?.toString() ?? "") : (value?.toString() ?? "");
489
496
  };
@@ -584,6 +591,7 @@ const DefaultNumber = ({
584
591
  ) : null}
585
592
  <input
586
593
  {...rest}
594
+ {...agentAttrs(onChange)}
587
595
  ref={inputRef}
588
596
  value={formatValue}
589
597
  onKeyDown={handleKeyDown}
@@ -621,9 +629,10 @@ const DefaultCheckbox = ({ checked, onChange, className, ...rest }: CheckboxProp
621
629
  return (
622
630
  <input
623
631
  {...rest}
632
+ {...agentAttrs(onChange)}
624
633
  type="checkbox"
625
634
  checked={checked}
626
- className={cn("checkbox", className)}
635
+ className={cn("size-5 accent-primary", className)}
627
636
  onChange={(e) => {
628
637
  onChange(e.target.checked, e);
629
638
  }}
@@ -9,6 +9,7 @@ export interface BottomInsetProps {
9
9
  className?: string;
10
10
  children: ReactNode;
11
11
  keyboardSticky?: boolean;
12
+ contentAnchor?: "bottom";
12
13
  role?: "bottomChrome" | "keyboardAccessory";
13
14
  estimatedHeight?: number;
14
15
  frameScope?: FrameSlotRegistration["scope"];
@@ -20,6 +21,7 @@ export const BottomInset = ({
20
21
  className,
21
22
  children,
22
23
  keyboardSticky,
24
+ contentAnchor,
23
25
  role,
24
26
  estimatedHeight,
25
27
  frameScope = "page",
@@ -49,12 +51,13 @@ export const BottomInset = ({
49
51
  debugFrame("bottomInset.mount", {
50
52
  path,
51
53
  keyboardSticky,
54
+ contentAnchor,
52
55
  role: frameRole,
53
56
  frameScope,
54
57
  frameSource,
55
58
  });
56
59
  return () => debugFrame("bottomInset.unmount", { path, frameSource });
57
- }, [path, keyboardSticky, frameRole, frameScope, frameSource]);
60
+ }, [path, keyboardSticky, contentAnchor, frameRole, frameScope, frameSource]);
58
61
  useLayoutEffect(() => {
59
62
  if (!path) return;
60
63
  const height = useDeclaredHeight ? resolvedEstimatedHeight : (measuredHeight ?? readContentHeight());
@@ -63,6 +66,7 @@ export const BottomInset = ({
63
66
  role: frameRole,
64
67
  scope: frameScope,
65
68
  source: frameSource,
69
+ contentAnchor,
66
70
  estimatedHeight: resolvedEstimatedHeight,
67
71
  height,
68
72
  cache: frameCache,
@@ -72,6 +76,7 @@ export const BottomInset = ({
72
76
  frameRole,
73
77
  frameScope,
74
78
  frameSource,
79
+ contentAnchor,
75
80
  resolvedEstimatedHeight,
76
81
  measuredHeight,
77
82
  frameCache,
package/ui/Load/Units.tsx CHANGED
@@ -3,10 +3,10 @@ import { DataList } from "akanjs/base";
3
3
  import { cn } from "akanjs/client";
4
4
  import { capitalize, isQueryEqual, lowerlize } from "akanjs/common";
5
5
  import type { BaseInsight } from "akanjs/constant";
6
- import { ConstantRegistry } from "akanjs/constant";
6
+ import { ConstantRegistry, labelOf } from "akanjs/constant";
7
7
  import type { ClientInit, ServerInit } from "akanjs/fetch";
8
8
  import { st } from "akanjs/store";
9
- import { useFetch } from "akanjs/webkit";
9
+ import { useFetch, useScreenScope } from "akanjs/webkit";
10
10
  import { type ReactNode, type RefObject, useEffect, useMemo, useRef } from "react";
11
11
 
12
12
  import { Empty } from "../Empty";
@@ -183,6 +183,15 @@ function Render<RefName extends string, Light extends { id: string }>({
183
183
  };
184
184
 
185
185
  const modelDataList = !loaded.current ? modelInitList.filter(filter).sort(sort) : modelList.filter(filter).sort(sort);
186
+ useScreenScope({
187
+ id: sliceName,
188
+ kind: refName,
189
+ items: () =>
190
+ modelDataList.map((item) => {
191
+ const label = labelOf(cnst.full, item);
192
+ return { id: item.id, ...(label ? { label } : {}) };
193
+ }),
194
+ });
186
195
  const showLoading = loaded.current && modelListLoading;
187
196
  if (renderList)
188
197
  return (
package/ui/Load/View.tsx CHANGED
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { cn } from "akanjs/client";
3
- import { ConstantRegistry } from "akanjs/constant";
3
+ import { ConstantRegistry, labelOf } from "akanjs/constant";
4
4
  import type { ClientView, ServerView } from "akanjs/fetch";
5
5
  import { st } from "akanjs/store";
6
- import { useFetch } from "akanjs/webkit";
6
+ import { useFetch, useScreenScope } from "akanjs/webkit";
7
7
  import { type ReactNode, useEffect, useMemo, useRef } from "react";
8
8
 
9
9
  import { Empty } from "../Empty";
@@ -72,6 +72,11 @@ function Render<T extends string, Full extends { id: string }>({
72
72
  }, [modelViewAt, modelObj.id]);
73
73
 
74
74
  const renderModel = loadedId.current === modelObj.id ? model : modelInit;
75
+ useScreenScope({
76
+ id: `${refName}-view`,
77
+ kind: refName,
78
+ label: renderModel ? labelOf(cnst.full, renderModel) : undefined,
79
+ });
75
80
 
76
81
  return noDiv && renderModel ? (
77
82
  <>{renderView(renderModel)}</>
@@ -10,5 +10,12 @@ export interface ProgressBarProps {
10
10
  }
11
11
  export const ProgressBar = ({ className, value, max }: ProgressBarProps) => {
12
12
  const progress = useSpring({ value: 0, to: { value: value } });
13
- return <animated.progress className={cn("progress w-full", className)} value={progress.value} max={max} />;
13
+
14
+ return (
15
+ <animated.progress
16
+ className={cn("h-2 w-full rounded-box accent-primary", className)}
17
+ value={progress.value}
18
+ max={max}
19
+ />
20
+ );
14
21
  };
package/ui/Menu.tsx CHANGED
@@ -5,7 +5,7 @@ import { type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useSta
5
5
  import { AiFillCaretDown, AiOutlineEllipsis } from "react-icons/ai";
6
6
  import { buttonRecipe } from "./Button";
7
7
 
8
- import { createOverridable } from "./UiOverride";
8
+ import { createOverridable, useUiRecipe } from "./UiOverride";
9
9
 
10
10
  export interface MenuItem {
11
11
  label: ReactNode;
@@ -62,13 +62,11 @@ export const DefaultMenu = ({
62
62
  ? `fixed bottom-0 flex translate-y-[98%] flex-col rounded-xs border-0 bg-background shadow-lg hover:bg-background`
63
63
  : "flex flex-col gap-0 p-0 bg-primary/10 hover:bg-primary/10 overflow-hidden";
64
64
 
65
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
65
66
  const subMenuItemClassName =
66
67
  mode === "inline"
67
- ? buttonRecipe({ variant: "ghost" }, "m-0 h-full w-full px-2 font-normal text-primary hover:bg-primary/20")
68
- : buttonRecipe(
69
- { variant: "ghost" },
70
- "w-full whitespace-nowrap text-center font-normal text-primary duration-300",
71
- );
68
+ ? recipe({ variant: "ghost" }, "m-0 h-full w-full px-2 font-normal text-primary hover:bg-primary/20")
69
+ : recipe({ variant: "ghost" }, "w-full whitespace-nowrap text-center font-normal text-primary duration-300");
72
70
 
73
71
  const [overflowMenuItems, setOverflowMenuItems] = useState<MenuItem[]>([]);
74
72
 
@@ -222,6 +220,7 @@ interface OverflowMenuProps {
222
220
  const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
223
221
  const [isOpen, setIsOpen] = useState(false);
224
222
  const [expandedKey, setExpandedKey] = useState<string>();
223
+ const recipe = useUiRecipe("button") ?? buttonRecipe;
225
224
  const handleMouseEnter = () => {
226
225
  setIsOpen(true);
227
226
  };
@@ -239,7 +238,7 @@ const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
239
238
  <div
240
239
  key={item.key}
241
240
  onClick={() => onClick?.(item)}
242
- className={buttonRecipe({ variant: "ghost" }, "relative whitespace-nowrap font-normal text-primary")}
241
+ className={recipe({ variant: "ghost" }, "relative whitespace-nowrap font-normal text-primary")}
243
242
  onMouseEnter={() => {
244
243
  if (item.children && expandedKey !== item.key) setExpandedKey(item.key);
245
244
  }}
@@ -258,7 +257,7 @@ const OverflowMenu = ({ overflowItems, onClick }: OverflowMenuProps) => {
258
257
  setExpandedKey(undefined);
259
258
  }}
260
259
  key={child.key}
261
- className={buttonRecipe(
260
+ className={recipe(
262
261
  { variant: "ghost" },
263
262
  "flex items-center justify-center text-center font-normal text-primary",
264
263
  )}
@@ -5,7 +5,7 @@ import { ConstantRegistry, immerify } from "akanjs/constant";
5
5
  import type { ClientEdit, ServerEdit, SliceMeta } from "akanjs/fetch";
6
6
  import { type CreateOption, type Submit, st } from "akanjs/store";
7
7
  import { useDebounce } from "akanjs/webkit";
8
- import { type ReactNode, type Usable, use, useCallback, useEffect, useMemo } from "react";
8
+ import { type ReactNode, type Usable, use, useCallback, useEffect, useMemo, useRef } from "react";
9
9
  import { AiOutlinePlus, AiOutlineSave } from "react-icons/ai";
10
10
 
11
11
  import { Button } from "../Button";
@@ -13,6 +13,10 @@ import { Modal } from "../Modal";
13
13
 
14
14
  const EDIT_PAYLOAD_MAX_AGE_MS = 60_000;
15
15
 
16
+ const pendingModalResets = new Map<string, { cancel: () => void }>();
17
+ const modalResetKey = (modelName: string, modalName: string, modalId?: string) =>
18
+ `${modelName}|${modalName}|${modalId ?? ""}`;
19
+
16
20
  interface EditModelProps<Full> {
17
21
  /** Rendering mode for the edit shell. */
18
22
  type?: "modal" | "form" | "empty";
@@ -202,9 +206,40 @@ export default function EditModal<Full extends { id: string }>({
202
206
  const crystal = new modelRef().set(modelEdit as Full) as unknown as Full;
203
207
  void storeDo[names.newModel](crystal, { modal, setDefault: true, sliceName });
204
208
  }
209
+ }, [modelEdit, isEditPayloadStale]);
210
+
211
+ const ownershipRef = useRef({ wasOpen: false, modalName: modal ?? "edit", modalId });
212
+ useEffect(() => {
213
+ ownershipRef.current = {
214
+ wasOpen: ownershipRef.current.wasOpen || isModalOpen,
215
+ modalName: modal ?? "edit",
216
+ modalId,
217
+ };
218
+ }, [isModalOpen, modal, modalId]);
219
+ useEffect(() => {
220
+ pendingModalResets.get(modalResetKey(modelName, modal ?? "edit", modalId))?.cancel();
205
221
  return () => {
222
+
223
+ const { wasOpen, modalName, modalId } = ownershipRef.current;
224
+ if (!wasOpen) return;
225
+ const key = modalResetKey(modelName, modalName, modalId);
226
+ let cancelled = false;
227
+ pendingModalResets.set(key, {
228
+ cancel: () => {
229
+ cancelled = true;
230
+ },
231
+ });
232
+ queueMicrotask(() => {
233
+ pendingModalResets.delete(key);
234
+ if (cancelled) return;
235
+ const state = st.get() as unknown as { [key: string]: unknown };
236
+ if (state[names.modelModal] !== modalName) return;
237
+ const formId = (state[names.modelForm] as { id: string | null } | undefined)?.id ?? null;
238
+ if (formId !== (modalId ?? null)) return;
239
+ void storeDo[names.setModelModal](null);
240
+ });
206
241
  };
207
- }, [modelEdit, isEditPayloadStale]);
242
+ }, []);
208
243
 
209
244
  const handleCancel = useCallback(() => {
210
245
  const modelForm = (st.get() as any)[names.modelForm] as Full;