@planet-matrix/mobius-model 0.5.0 → 0.9.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 (379) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +715 -4
  4. package/dist/index.js.map +981 -13
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +36 -18
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/ai/README.md +1 -0
  12. package/src/ai/ai.ts +107 -0
  13. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  14. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  15. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  16. package/src/ai/chat-completion-ai/index.ts +7 -0
  17. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  18. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  19. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  20. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  21. package/src/ai/embedding-ai/embedding.ts +50 -0
  22. package/src/ai/embedding-ai/index.ts +4 -0
  23. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  24. package/src/ai/index.ts +4 -0
  25. package/src/aio/README.md +100 -0
  26. package/src/aio/content.ts +141 -0
  27. package/src/aio/index.ts +3 -0
  28. package/src/aio/json.ts +127 -0
  29. package/src/aio/prompt.ts +246 -0
  30. package/src/basic/README.md +72 -116
  31. package/src/basic/error.ts +19 -5
  32. package/src/basic/function.ts +83 -64
  33. package/src/basic/index.ts +1 -0
  34. package/src/basic/is.ts +152 -71
  35. package/src/basic/promise.ts +29 -8
  36. package/src/basic/schedule.ts +111 -0
  37. package/src/basic/stream.ts +135 -25
  38. package/src/basic/string.ts +2 -33
  39. package/src/color/README.md +105 -0
  40. package/src/color/index.ts +3 -0
  41. package/src/color/internal.ts +42 -0
  42. package/src/color/rgb/analyze.ts +236 -0
  43. package/src/color/rgb/construct.ts +130 -0
  44. package/src/color/rgb/convert.ts +227 -0
  45. package/src/color/rgb/derive.ts +303 -0
  46. package/src/color/rgb/index.ts +6 -0
  47. package/src/color/rgb/internal.ts +208 -0
  48. package/src/color/rgb/parse.ts +302 -0
  49. package/src/color/rgb/serialize.ts +144 -0
  50. package/src/color/types.ts +57 -0
  51. package/src/color/xyz/analyze.ts +80 -0
  52. package/src/color/xyz/construct.ts +19 -0
  53. package/src/color/xyz/convert.ts +71 -0
  54. package/src/color/xyz/index.ts +3 -0
  55. package/src/color/xyz/internal.ts +23 -0
  56. package/src/credential/README.md +107 -0
  57. package/src/credential/api-key.ts +158 -0
  58. package/src/credential/bearer.ts +73 -0
  59. package/src/credential/index.ts +4 -0
  60. package/src/credential/json-web-token.ts +96 -0
  61. package/src/credential/password.ts +170 -0
  62. package/src/cron/README.md +86 -0
  63. package/src/cron/cron.ts +87 -0
  64. package/src/cron/index.ts +1 -0
  65. package/src/css/README.md +93 -0
  66. package/src/css/class.ts +559 -0
  67. package/src/css/index.ts +1 -0
  68. package/src/drizzle/README.md +1 -0
  69. package/src/drizzle/drizzle.ts +1 -0
  70. package/src/drizzle/helper.ts +47 -0
  71. package/src/drizzle/index.ts +5 -0
  72. package/src/drizzle/infer.ts +52 -0
  73. package/src/drizzle/kysely.ts +8 -0
  74. package/src/drizzle/pagination.ts +200 -0
  75. package/src/email/README.md +1 -0
  76. package/src/email/index.ts +1 -0
  77. package/src/email/resend.ts +25 -0
  78. package/src/encoding/README.md +66 -79
  79. package/src/encoding/base64.ts +13 -4
  80. package/src/environment/README.md +97 -0
  81. package/src/environment/basic.ts +26 -0
  82. package/src/environment/device.ts +311 -0
  83. package/src/environment/feature.ts +285 -0
  84. package/src/environment/geo.ts +337 -0
  85. package/src/environment/index.ts +7 -0
  86. package/src/environment/runtime.ts +400 -0
  87. package/src/environment/snapshot.ts +60 -0
  88. package/src/environment/variable.ts +239 -0
  89. package/src/event/README.md +90 -0
  90. package/src/event/class-event-proxy.ts +229 -0
  91. package/src/event/common.ts +29 -0
  92. package/src/event/event-manager.ts +203 -0
  93. package/src/event/index.ts +4 -0
  94. package/src/event/instance-event-proxy.ts +187 -0
  95. package/src/event/internal.ts +24 -0
  96. package/src/exception/README.md +96 -0
  97. package/src/exception/browser.ts +219 -0
  98. package/src/exception/index.ts +4 -0
  99. package/src/exception/nodejs.ts +169 -0
  100. package/src/exception/normalize.ts +106 -0
  101. package/src/exception/types.ts +99 -0
  102. package/src/form/README.md +25 -0
  103. package/src/form/index.ts +1 -0
  104. package/src/form/inputor-controller/base.ts +874 -0
  105. package/src/form/inputor-controller/boolean.ts +39 -0
  106. package/src/form/inputor-controller/file.ts +39 -0
  107. package/src/form/inputor-controller/form.ts +181 -0
  108. package/src/form/inputor-controller/helper.ts +117 -0
  109. package/src/form/inputor-controller/index.ts +17 -0
  110. package/src/form/inputor-controller/multi-select.ts +99 -0
  111. package/src/form/inputor-controller/number.ts +116 -0
  112. package/src/form/inputor-controller/select.ts +109 -0
  113. package/src/form/inputor-controller/text.ts +82 -0
  114. package/src/http/READMD.md +1 -0
  115. package/src/http/api/api-core.ts +84 -0
  116. package/src/http/api/api-handler.ts +79 -0
  117. package/src/http/api/api-host.ts +47 -0
  118. package/src/http/api/api-result.ts +56 -0
  119. package/src/http/api/api-schema.ts +154 -0
  120. package/src/http/api/api-server.ts +130 -0
  121. package/src/http/api/api-test.ts +142 -0
  122. package/src/http/api/api-type.ts +37 -0
  123. package/src/http/api/api.ts +81 -0
  124. package/src/http/api/index.ts +11 -0
  125. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  126. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  127. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  128. package/src/http/api-adapter/api-result-zod.ts +286 -0
  129. package/src/http/api-adapter/index.ts +5 -0
  130. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  131. package/src/http/bin/gen-api-list/index.ts +1 -0
  132. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  133. package/src/http/bin/gen-api-test/index.ts +1 -0
  134. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  135. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  136. package/src/http/bin/gen-api-type/index.ts +2 -0
  137. package/src/http/bin/index.ts +2 -0
  138. package/src/http/index.ts +3 -0
  139. package/src/huawei/README.md +1 -0
  140. package/src/huawei/index.ts +2 -0
  141. package/src/huawei/moderation/index.ts +1 -0
  142. package/src/huawei/moderation/moderation.ts +355 -0
  143. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  144. package/src/huawei/obs/index.ts +1 -0
  145. package/src/huawei/obs/obs.ts +42 -0
  146. package/src/identifier/README.md +92 -0
  147. package/src/identifier/id.ts +119 -0
  148. package/src/identifier/index.ts +2 -0
  149. package/src/identifier/uuid.ts +187 -0
  150. package/src/index.ts +33 -1
  151. package/src/json/README.md +92 -0
  152. package/src/json/index.ts +1 -0
  153. package/src/json/repair.ts +18 -0
  154. package/src/log/README.md +79 -0
  155. package/src/log/index.ts +5 -0
  156. package/src/log/log-emitter.ts +72 -0
  157. package/src/log/log-record.ts +10 -0
  158. package/src/log/log-scheduler.ts +74 -0
  159. package/src/log/log-type.ts +8 -0
  160. package/src/log/logger.ts +554 -0
  161. package/src/openai/README.md +1 -0
  162. package/src/openai/index.ts +1 -0
  163. package/src/openai/openai.ts +510 -0
  164. package/src/orchestration/README.md +91 -0
  165. package/src/orchestration/coordination/barrier.ts +214 -0
  166. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  167. package/src/orchestration/coordination/errors.ts +98 -0
  168. package/src/orchestration/coordination/index.ts +16 -0
  169. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  170. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  171. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  172. package/src/orchestration/coordination/mutex.ts +257 -0
  173. package/src/orchestration/coordination/permit.ts +127 -0
  174. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  175. package/src/orchestration/coordination/semaphore.ts +280 -0
  176. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  177. package/src/orchestration/dispatching/index.ts +2 -0
  178. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  179. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  180. package/src/orchestration/dispatching/selector/index.ts +2 -0
  181. package/src/orchestration/index.ts +3 -0
  182. package/src/orchestration/scheduling/index.ts +2 -0
  183. package/src/orchestration/scheduling/scheduler.ts +103 -0
  184. package/src/orchestration/scheduling/task.ts +32 -0
  185. package/src/random/README.md +56 -86
  186. package/src/random/base.ts +66 -0
  187. package/src/random/index.ts +5 -1
  188. package/src/random/random-boolean.ts +40 -0
  189. package/src/random/random-integer.ts +60 -0
  190. package/src/random/random-number.ts +72 -0
  191. package/src/random/random-string.ts +66 -0
  192. package/src/reactor/README.md +4 -0
  193. package/src/reactor/reactor-core/primitive.ts +9 -9
  194. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  195. package/src/request/README.md +108 -0
  196. package/src/request/fetch/base.ts +108 -0
  197. package/src/request/fetch/browser.ts +285 -0
  198. package/src/request/fetch/general.ts +20 -0
  199. package/src/request/fetch/index.ts +4 -0
  200. package/src/request/fetch/nodejs.ts +285 -0
  201. package/src/request/index.ts +2 -0
  202. package/src/request/request/base.ts +250 -0
  203. package/src/request/request/general.ts +64 -0
  204. package/src/request/request/index.ts +3 -0
  205. package/src/request/request/resource.ts +68 -0
  206. package/src/result/README.md +4 -0
  207. package/src/result/controller.ts +54 -0
  208. package/src/result/either.ts +193 -0
  209. package/src/result/index.ts +2 -0
  210. package/src/route/README.md +105 -0
  211. package/src/route/adapter/browser.ts +122 -0
  212. package/src/route/adapter/driver.ts +56 -0
  213. package/src/route/adapter/index.ts +2 -0
  214. package/src/route/index.ts +3 -0
  215. package/src/route/router/index.ts +2 -0
  216. package/src/route/router/route.ts +630 -0
  217. package/src/route/router/router.ts +1642 -0
  218. package/src/route/uri/hash.ts +308 -0
  219. package/src/route/uri/index.ts +7 -0
  220. package/src/route/uri/pathname.ts +376 -0
  221. package/src/route/uri/search.ts +413 -0
  222. package/src/singleton/README.md +79 -0
  223. package/src/singleton/factory.ts +55 -0
  224. package/src/singleton/index.ts +2 -0
  225. package/src/singleton/manager.ts +204 -0
  226. package/src/socket/README.md +105 -0
  227. package/src/socket/client/index.ts +2 -0
  228. package/src/socket/client/socket-unit.ts +660 -0
  229. package/src/socket/client/socket.ts +203 -0
  230. package/src/socket/common/index.ts +2 -0
  231. package/src/socket/common/socket-unit-common.ts +23 -0
  232. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  233. package/src/socket/index.ts +3 -0
  234. package/src/socket/server/index.ts +3 -0
  235. package/src/socket/server/server.ts +183 -0
  236. package/src/socket/server/socket-unit.ts +449 -0
  237. package/src/socket/server/socket.ts +264 -0
  238. package/src/storage/README.md +107 -0
  239. package/src/storage/index.ts +1 -0
  240. package/src/storage/table.ts +449 -0
  241. package/src/timer/README.md +86 -0
  242. package/src/timer/expiration/expiration-manager.ts +594 -0
  243. package/src/timer/expiration/index.ts +3 -0
  244. package/src/timer/expiration/min-heap.ts +208 -0
  245. package/src/timer/expiration/remaining-manager.ts +241 -0
  246. package/src/timer/index.ts +1 -0
  247. package/src/tube/README.md +99 -0
  248. package/src/tube/helper.ts +138 -0
  249. package/src/tube/index.ts +2 -0
  250. package/src/tube/tube.ts +880 -0
  251. package/src/type/README.md +54 -307
  252. package/src/type/class.ts +2 -2
  253. package/src/type/index.ts +14 -14
  254. package/src/type/is.ts +265 -2
  255. package/src/type/object.ts +37 -0
  256. package/src/type/string.ts +7 -2
  257. package/src/type/tuple.ts +6 -6
  258. package/src/type/union.ts +16 -0
  259. package/src/web/README.md +77 -0
  260. package/src/web/capture.ts +35 -0
  261. package/src/web/clipboard.ts +97 -0
  262. package/src/web/dom.ts +117 -0
  263. package/src/web/download.ts +16 -0
  264. package/src/web/event.ts +46 -0
  265. package/src/web/index.ts +10 -0
  266. package/src/web/local-storage.ts +113 -0
  267. package/src/web/location.ts +28 -0
  268. package/src/web/permission.ts +172 -0
  269. package/src/web/script-loader.ts +432 -0
  270. package/src/weixin/README.md +1 -0
  271. package/src/weixin/index.ts +2 -0
  272. package/src/weixin/official-account/authorization.ts +159 -0
  273. package/src/weixin/official-account/index.ts +2 -0
  274. package/src/weixin/official-account/js-api.ts +134 -0
  275. package/src/weixin/open/index.ts +1 -0
  276. package/src/weixin/open/oauth2.ts +133 -0
  277. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  278. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  279. package/tests/unit/ai/ai.spec.ts +85 -0
  280. package/tests/unit/aio/content.spec.ts +105 -0
  281. package/tests/unit/aio/json.spec.ts +147 -0
  282. package/tests/unit/aio/prompt.spec.ts +111 -0
  283. package/tests/unit/basic/array.spec.ts +1 -1
  284. package/tests/unit/basic/error.spec.ts +16 -4
  285. package/tests/unit/basic/schedule.spec.ts +74 -0
  286. package/tests/unit/basic/stream.spec.ts +91 -38
  287. package/tests/unit/basic/string.spec.ts +0 -9
  288. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  289. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  290. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  291. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  292. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  293. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  294. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  295. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  296. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  297. package/tests/unit/credential/api-key.spec.ts +37 -0
  298. package/tests/unit/credential/bearer.spec.ts +23 -0
  299. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  300. package/tests/unit/credential/password.spec.ts +41 -0
  301. package/tests/unit/cron/cron.spec.ts +84 -0
  302. package/tests/unit/css/class.spec.ts +157 -0
  303. package/tests/unit/environment/basic.spec.ts +20 -0
  304. package/tests/unit/environment/device.spec.ts +146 -0
  305. package/tests/unit/environment/feature.spec.ts +388 -0
  306. package/tests/unit/environment/geo.spec.ts +111 -0
  307. package/tests/unit/environment/runtime.spec.ts +364 -0
  308. package/tests/unit/environment/snapshot.spec.ts +4 -0
  309. package/tests/unit/environment/variable.spec.ts +190 -0
  310. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  311. package/tests/unit/event/event-manager.spec.ts +246 -0
  312. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  313. package/tests/unit/exception/browser.spec.ts +213 -0
  314. package/tests/unit/exception/nodejs.spec.ts +144 -0
  315. package/tests/unit/exception/normalize.spec.ts +57 -0
  316. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  317. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  318. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  319. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  320. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  321. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  322. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  323. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  324. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  325. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  326. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  327. package/tests/unit/http/api/api-server.spec.ts +363 -0
  328. package/tests/unit/http/api/api-test.spec.ts +117 -0
  329. package/tests/unit/http/api/api.spec.ts +121 -0
  330. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  331. package/tests/unit/identifier/id.spec.ts +71 -0
  332. package/tests/unit/identifier/uuid.spec.ts +85 -0
  333. package/tests/unit/json/repair.spec.ts +11 -0
  334. package/tests/unit/log/log-emitter.spec.ts +33 -0
  335. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  336. package/tests/unit/log/log-type.spec.ts +7 -0
  337. package/tests/unit/log/logger.spec.ts +237 -0
  338. package/tests/unit/openai/openai.spec.ts +64 -0
  339. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  340. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  341. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  342. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  343. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  344. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  345. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  346. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  347. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  348. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  349. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  350. package/tests/unit/random/base.spec.ts +58 -0
  351. package/tests/unit/random/random-boolean.spec.ts +25 -0
  352. package/tests/unit/random/random-integer.spec.ts +32 -0
  353. package/tests/unit/random/random-number.spec.ts +33 -0
  354. package/tests/unit/random/random-string.spec.ts +22 -0
  355. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  356. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  357. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  358. package/tests/unit/request/fetch/general.spec.ts +43 -0
  359. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  360. package/tests/unit/request/request/base.spec.ts +385 -0
  361. package/tests/unit/request/request/general.spec.ts +161 -0
  362. package/tests/unit/route/router/route.spec.ts +431 -0
  363. package/tests/unit/route/router/router.spec.ts +407 -0
  364. package/tests/unit/route/uri/hash.spec.ts +72 -0
  365. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  366. package/tests/unit/route/uri/search.spec.ts +107 -0
  367. package/tests/unit/singleton/singleton.spec.ts +49 -0
  368. package/tests/unit/socket/client.spec.ts +208 -0
  369. package/tests/unit/socket/server.spec.ts +135 -0
  370. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  371. package/tests/unit/storage/table.spec.ts +620 -0
  372. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  373. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  374. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  375. package/tests/unit/tube/helper.spec.ts +139 -0
  376. package/tests/unit/tube/tube.spec.ts +501 -0
  377. package/.oxlintrc.json +0 -5
  378. package/src/random/uuid.ts +0 -103
  379. package/tests/unit/random/uuid.spec.ts +0 -37
@@ -0,0 +1,874 @@
1
+
2
+ const internalGenerateUuid = (): string => {
3
+ const tempURL = URL.createObjectURL(new Blob())
4
+ const uuidInUrl = tempURL
5
+ URL.revokeObjectURL(tempURL)
6
+ const uuid = uuidInUrl.slice(uuidInUrl.lastIndexOf("/") + 1)
7
+ return uuid
8
+ }
9
+
10
+ /**
11
+ * 这些属性是所有 Inputor 的共同属性。
12
+ */
13
+ export interface BaseInputorSchema<ExternalValue> {
14
+ inputorTypeName: string
15
+ id: string
16
+ name: string
17
+ placeholder: string
18
+ value: ExternalValue
19
+ isVoid: boolean
20
+ isFocused: boolean
21
+ /**
22
+ * 只有 isFocused 发生了更新才会被设置为 true。
23
+ */
24
+ isTouched: boolean
25
+ /**
26
+ * 只要 value 发生了更新就会被设置为 true。
27
+ */
28
+ isDirty: boolean
29
+ isDisabled: boolean
30
+ isDebugMode: boolean
31
+ }
32
+ /**
33
+ * Inputor 内部工作时的上下文,包含一些不对外暴露的属性和状态,扩展自 {@link BaseInputorSchema}。
34
+ */
35
+ export interface BaseInputorContext<ExternalValue, InternalValue> extends BaseInputorSchema<ExternalValue> {
36
+ internalValue: InternalValue
37
+ /**
38
+ * 是否可以开始进行交互。
39
+ */
40
+ isReady: boolean
41
+ }
42
+
43
+ export interface BaseInputorControllerOptions<ExternalValue, Schema extends BaseInputorSchema<ExternalValue>> {
44
+ id?: string | undefined
45
+ name?: string | undefined
46
+ placeholder?: string | undefined
47
+ /**
48
+ * 为了让所有的值(包括 undefined、null)都可以作为合法的 Inputor 的 Value,
49
+ * 所以将 voidValue 交给 Inputor 的具体实现者来定义,于是此处就变成了必选。
50
+ *
51
+ * 当然了,具体的 Inputor 为了使用方便,可以在处理好相关逻辑的基础上将其覆盖为可选。
52
+ */
53
+ value: ExternalValue
54
+ /**
55
+ * @default false
56
+ */
57
+ isFocused?: boolean | undefined
58
+ /**
59
+ * @default false
60
+ */
61
+ isTouched?: boolean | undefined
62
+ /**
63
+ * @default false
64
+ */
65
+ isDirty?: boolean | undefined
66
+ /**
67
+ * @default false
68
+ */
69
+ isDisabled?: boolean | undefined
70
+ /**
71
+ * @default false
72
+ */
73
+ isDebugMode?: boolean | undefined
74
+
75
+ /**
76
+ * 在原生 type 为 text 的 input 元素中,input 和 change 是两个独立的事件,
77
+ * 用户的所有修改操作都会实时触发 input 事件,但只有在失去焦点或确认(按回车等)
78
+ * 时才会触发 change 事件。
79
+ *
80
+ * 此参数用于控制是否在 input 事件发生后自动触发 change 事件,此行为受
81
+ * {@link inputDebounce} 影响。
82
+ *
83
+ * @default false
84
+ */
85
+ enableAutoChangeValueAfterInput?: boolean | undefined
86
+ /**
87
+ * 此参数用于控制 input 事件的触发频率,单位为毫秒,只有当
88
+ * {@link enableAutoChangeValueAfterInput} 为 true 时才会生效。
89
+ *
90
+ * @default 0
91
+ */
92
+ inputDebounce?: number | undefined
93
+
94
+ onInputChange?: InputChangeSubscriber<ExternalValue> | undefined
95
+ onValueChange?: ValueChangeSubscriber<ExternalValue> | undefined
96
+ onSchemaChange?: SchemaChangeSubscriber<ExternalValue, Schema> | undefined
97
+ }
98
+
99
+ export interface InputChangeContext<ExternalValue> {
100
+ newInput: ExternalValue
101
+ oldInput: ExternalValue
102
+ oldValue: ExternalValue
103
+ }
104
+ export type InputChangeSubscriber<ExternalValue>
105
+ = (context: InputChangeContext<ExternalValue>) => void | Promise<void>
106
+ export interface InputChangeSubscriberEntry<ExternalValue> {
107
+ subscriber: InputChangeSubscriber<ExternalValue>
108
+ unsubscribe: () => void
109
+ }
110
+ export type InputChangeSubscriberMap<ExternalValue>
111
+ = Map<InputChangeSubscriber<ExternalValue>, InputChangeSubscriberEntry<ExternalValue>>
112
+
113
+ export interface ValueChangeContext<ExternalValue> {
114
+ newValue: ExternalValue
115
+ oldValue: ExternalValue
116
+ }
117
+ export type ValueChangeSubscriber<ExternalValue> = (
118
+ context: ValueChangeContext<ExternalValue>
119
+ ) => void | Promise<void>
120
+ export interface ValueChangeSubscriberEntry<ExternalValue> {
121
+ subscriber: ValueChangeSubscriber<ExternalValue>
122
+ unsubscribe: () => void
123
+ }
124
+ export type ValueChangeSubscriberMap<ExternalValue> = Map<
125
+ ValueChangeSubscriber<ExternalValue>,
126
+ ValueChangeSubscriberEntry<ExternalValue>
127
+ >
128
+
129
+ export interface SchemaChangeContext<
130
+ ExternalValue,
131
+ Schema extends BaseInputorSchema<ExternalValue>
132
+ > {
133
+ newSchema: Schema
134
+ oldSchema: Schema
135
+ }
136
+ export type SchemaChangeSubscriber<
137
+ ExternalValue,
138
+ Schema extends BaseInputorSchema<ExternalValue>
139
+ > = (
140
+ context: SchemaChangeContext<ExternalValue, Schema>
141
+ ) => void | Promise<void>
142
+ export interface SchemaChangeSubscriberEntry<
143
+ ExternalValue,
144
+ Schema extends BaseInputorSchema<ExternalValue>,
145
+ > {
146
+ subscriber: SchemaChangeSubscriber<ExternalValue, Schema>
147
+ unsubscribe: () => void
148
+ }
149
+ export type SchemaChangeSubscriberMap<
150
+ ExternalValue,
151
+ Schema extends BaseInputorSchema<ExternalValue>,
152
+ > = Map<
153
+ SchemaChangeSubscriber<ExternalValue, Schema>,
154
+ SchemaChangeSubscriberEntry<ExternalValue, Schema>
155
+ >
156
+
157
+ export interface InputorControllerReadyState {
158
+ isReady: boolean
159
+ promise: Promise<void>
160
+ resolve: () => void
161
+ }
162
+ export interface InputorControllerInputState<ExternalValue> {
163
+ isInputing: boolean
164
+ previousInputValue: ExternalValue
165
+ currentInputValue: ExternalValue
166
+ timer: ReturnType<typeof setTimeout> | undefined
167
+ }
168
+ export interface InputorControllerInitializeHooks {
169
+ onStart?: () => void | Promise<void>
170
+ onSchemaReady?: () => void | Promise<void>
171
+ onOptionsReady?: () => void | Promise<void>
172
+ onInternalStatesReady?: () => void | Promise<void>
173
+ onAllReady?: () => void | Promise<void>
174
+ onFinish?: () => void | Promise<void>
175
+ }
176
+
177
+ export interface UpdateContext<ExternalValue, InternalValue> extends BaseInputorSchema<ExternalValue> {
178
+ internalValue: InternalValue
179
+ }
180
+ export type Update<ExternalValue, InternalValue> = (
181
+ context: UpdateContext<ExternalValue, InternalValue>
182
+ ) => UpdateContext<ExternalValue, InternalValue> | Promise<UpdateContext<ExternalValue, InternalValue>>
183
+
184
+ /**
185
+ * 假设一个 NumberTextInputor 由一个 `type="number"` 的 Input 元素实现,
186
+ * 那么其 InternalValue 就是 number,ExternalValue 就是 string。
187
+ *
188
+ * - ExternalValue - Inputor 对外界声明的值的类型。
189
+ * - InternalValue - Inputor 内部维护的值的类型。
190
+ *
191
+ * 备忘:
192
+ * - 不要试图将 InternalValue 纳入 Schema,因为 InternalValue 和 ExternalValue
193
+ * 之间有特定的转换关系,将二者都放在 Schema 中会提升使用难度和出错概率,换言之,
194
+ * InternalValue 叫 InternalValue 的原因之一就是因为我们决定将它隐藏在 Inputor 内部。
195
+ */
196
+ export abstract class BaseInputorController<
197
+ ExternalValue,
198
+ InternalValue,
199
+ Schema extends BaseInputorSchema<ExternalValue>,
200
+ Context extends BaseInputorContext<ExternalValue, InternalValue>,
201
+ > {
202
+ /**
203
+ * 方便进行类型计算。
204
+ */
205
+ abstract declare schemaType: Schema
206
+ /**
207
+ * 方便进行类型计算。
208
+ */
209
+ abstract declare optionsType: BaseInputorControllerOptions<ExternalValue, Schema>
210
+ /**
211
+ * 方便进行类型计算。
212
+ */
213
+ abstract declare constructorType: typeof BaseInputorController<ExternalValue, InternalValue, Schema, Context>
214
+ /**
215
+ * 方便进行类型计算。
216
+ */
217
+ declare instanceType: this
218
+
219
+ /**
220
+ * 每个 Inputor 都应该有一个类型名称,用于将它与其它的 Inputor 区分开来。
221
+ */
222
+ abstract inputorTypeName: string
223
+ protected abstract voidExternalValue: ExternalValue
224
+ protected abstract voidInternalValue: InternalValue
225
+
226
+ protected id!: string
227
+ protected name!: string
228
+ protected placeholder!: string
229
+ protected value!: ExternalValue
230
+ protected isVoid!: boolean
231
+ protected isFocused!: boolean
232
+ protected isTouched!: boolean
233
+ protected isDirty!: boolean
234
+ protected isDisabled!: boolean
235
+ protected isDebugMode!: boolean
236
+
237
+ protected enableAutoChangeValueAfterInput!: boolean
238
+ protected inputDebounce!: number
239
+ protected onInputChange?: InputChangeSubscriber<ExternalValue> | undefined
240
+ protected onValueChange?: ValueChangeSubscriber<ExternalValue> | undefined
241
+ protected onSchemaChange?: SchemaChangeSubscriber<ExternalValue, Schema> | undefined
242
+
243
+ protected inputChangeSubscriberMap: InputChangeSubscriberMap<ExternalValue>
244
+ protected valueChangeSubscriberMap: ValueChangeSubscriberMap<ExternalValue>
245
+ protected schemaChangeSubscriberMap: SchemaChangeSubscriberMap<ExternalValue, Schema>
246
+
247
+ protected readyState: InputorControllerReadyState
248
+ protected internalValue!: InternalValue
249
+ protected inputState!: InputorControllerInputState<ExternalValue>
250
+
251
+ constructor(options: BaseInputorControllerOptions<ExternalValue, Schema>) {
252
+ this.inputChangeSubscriberMap = new Map()
253
+ this.valueChangeSubscriberMap = new Map()
254
+ this.schemaChangeSubscriberMap = new Map()
255
+
256
+ let _resolve = (): void => {
257
+ // this is just a placeholder, the real resolve function will be assigned
258
+ // below when initializing the readyState
259
+ }
260
+ this.readyState = {
261
+ isReady: false,
262
+ promise: new Promise<void>((resolve) => {
263
+ _resolve = () => {
264
+ this.readyState.isReady = true
265
+ resolve()
266
+ }
267
+ }),
268
+ resolve: _resolve,
269
+ }
270
+ void this.initialize(options)
271
+ }
272
+
273
+ protected log(...args: unknown[]): void {
274
+ if (this.isDebugMode === true) {
275
+ console.log(...args)
276
+ }
277
+ }
278
+
279
+ protected async initialize(
280
+ options: BaseInputorControllerOptions<ExternalValue, Schema>,
281
+ hooks?: InputorControllerInitializeHooks,
282
+ ): Promise<void> {
283
+ const { onStart, onSchemaReady, onOptionsReady, onInternalStatesReady, onAllReady, onFinish } = hooks ?? {}
284
+
285
+ await onStart?.()
286
+
287
+ this.id = options.id ?? internalGenerateUuid()
288
+ this.name = options.name ?? `${this.inputorTypeName}-${this.id}`
289
+ this.placeholder = options.placeholder ?? ""
290
+ this.value = await this.constrainExternalValue(options.value)
291
+ this.isVoid = await this.isVoidExternalValue(this.value)
292
+ this.isFocused = options.isFocused ?? false
293
+ this.isTouched = options.isTouched ?? false
294
+ this.isDirty = options.isDirty ?? false
295
+ this.isDisabled = options.isDisabled ?? false
296
+ this.isDebugMode = options.isDebugMode ?? false
297
+
298
+ await onSchemaReady?.()
299
+
300
+ this.enableAutoChangeValueAfterInput = options.enableAutoChangeValueAfterInput ?? false
301
+ this.inputDebounce = options.inputDebounce ?? 0
302
+ this.onInputChange = options.onInputChange
303
+ this.onValueChange = options.onValueChange
304
+ this.onSchemaChange = options.onSchemaChange
305
+
306
+ if (this.onInputChange !== undefined) {
307
+ this.subscribeInputChange(this.onInputChange)
308
+ }
309
+ if (this.onValueChange !== undefined) {
310
+ this.subscribeValueChange(this.onValueChange)
311
+ }
312
+ if (this.onSchemaChange !== undefined) {
313
+ this.subscribeSchemaChange(this.onSchemaChange)
314
+ }
315
+
316
+ await onOptionsReady?.()
317
+
318
+ this.internalValue = await this.externalValueToInternalValue(this.value)
319
+ this.inputState = {
320
+ isInputing: false,
321
+ previousInputValue: this.value,
322
+ currentInputValue: this.value,
323
+ timer: undefined,
324
+ }
325
+
326
+ await onInternalStatesReady?.()
327
+
328
+ // update ready states before emitting changes
329
+ this.ready()
330
+
331
+ await onAllReady?.()
332
+
333
+ const currentValue = structuredClone(this.value)
334
+ this.emitValueChange({ newValue: currentValue, oldValue: currentValue })
335
+ const currentSchema = await this.getSchema()
336
+ this.emitSchemaChange({ newSchema: currentSchema, oldSchema: currentSchema })
337
+
338
+ await onFinish?.()
339
+ }
340
+
341
+ protected emitInputChange(context: InputChangeContext<ExternalValue>): void {
342
+ const { newInput, oldInput, oldValue } = context
343
+ this.log("[emitInputChange] emit start", "newInput", newInput, "oldInput", oldInput, "oldValue", oldValue)
344
+ this.log("[emitInputChange] count of subscribers", this.inputChangeSubscriberMap.size)
345
+ for (const state of this.inputChangeSubscriberMap.values()) {
346
+ void state.subscriber(context)
347
+ }
348
+ this.log("[emitInputChange] emit end")
349
+ }
350
+
351
+ subscribeInputChange(subscriber: InputChangeSubscriber<ExternalValue>): () => void {
352
+ this.log("[subscribeInputChange] subscribe start", "subscriber", subscriber)
353
+ const state: InputChangeSubscriberEntry<ExternalValue> = {
354
+ unsubscribe: () => {
355
+ this.log("[subscribeInputChange] unsubscribe start", "subscriber", subscriber)
356
+ this.inputChangeSubscriberMap.delete(subscriber)
357
+ this.log("[subscribeInputChange] unsubscribe end", "count of subscribers", this.inputChangeSubscriberMap.size)
358
+ },
359
+ subscriber,
360
+ }
361
+ this.inputChangeSubscriberMap.set(subscriber, state)
362
+ this.log("[subscribeInputChange] subscribe end", "count of subscribers", this.inputChangeSubscriberMap.size)
363
+ return state.unsubscribe
364
+ }
365
+
366
+ unsubscribeInputChange(subscriber: InputChangeSubscriber<ExternalValue>): void {
367
+ this.log("[unsubscribeInputChange] unsubscribe start", "subscriber", subscriber)
368
+ this.inputChangeSubscriberMap.delete(subscriber)
369
+ this.log("[unsubscribeInputChange] unsubscribe end", "count of subscribers", this.inputChangeSubscriberMap.size)
370
+ }
371
+
372
+ clearInputChangeSubscribers(): void {
373
+ this.log("[clearInputChangeSubscribers] clear start, count of subscribers", this.inputChangeSubscriberMap.size)
374
+ this.inputChangeSubscriberMap.clear()
375
+ this.log("[clearInputChangeSubscribers] clear end, count of subscribers", this.inputChangeSubscriberMap.size)
376
+ }
377
+
378
+ protected emitValueChange(context: ValueChangeContext<ExternalValue>): void {
379
+ const { newValue, oldValue } = context
380
+ this.log("[emitValueChange] emit start", "newValue", newValue, "oldValue", oldValue)
381
+ this.log("[emitValueChange] count of subscribers", this.valueChangeSubscriberMap.size)
382
+ for (const state of this.valueChangeSubscriberMap.values()) {
383
+ void state.subscriber(context)
384
+ }
385
+ this.log("[emitValueChange] emit end")
386
+ }
387
+
388
+ subscribeValueChange(subscriber: ValueChangeSubscriber<ExternalValue>): () => void {
389
+ this.log("[subscribeValueChange] subscribe start", "subscriber", subscriber)
390
+ const state: ValueChangeSubscriberEntry<ExternalValue> = {
391
+ unsubscribe: () => {
392
+ this.log("[subscribeValueChange] unsubscribe start", "subscriber", subscriber)
393
+ this.valueChangeSubscriberMap.delete(subscriber)
394
+ this.log("[subscribeValueChange] unsubscribe end", "count of subscribers", this.valueChangeSubscriberMap.size)
395
+ },
396
+ subscriber,
397
+ }
398
+ this.valueChangeSubscriberMap.set(subscriber, state)
399
+ this.log("[subscribeValueChange] subscribe end", "count of subscribers", this.valueChangeSubscriberMap.size)
400
+ return state.unsubscribe
401
+ }
402
+
403
+ unsubscribeValueChange(subscriber: ValueChangeSubscriber<ExternalValue>): void {
404
+ this.log("[unsubscribeValueChange] unsubscribe start", "subscriber", subscriber)
405
+ this.valueChangeSubscriberMap.delete(subscriber)
406
+ this.log("[unsubscribeValueChange] unsubscribe end", "count of subscribers", this.valueChangeSubscriberMap.size)
407
+ }
408
+
409
+ clearValueChangeSubscribers(): void {
410
+ this.log("[clearValueChangeSubscribers] clear start, count of subscribers", this.valueChangeSubscriberMap.size)
411
+ this.valueChangeSubscriberMap.clear()
412
+ this.log("[clearValueChangeSubscribers] clear end, count of subscribers", this.valueChangeSubscriberMap.size)
413
+ }
414
+
415
+ protected emitSchemaChange(context: SchemaChangeContext<ExternalValue, Schema>): void {
416
+ const { newSchema, oldSchema } = context
417
+ this.log("[emitSchemaChange] emit start", "newSchema", newSchema, "oldSchema", oldSchema)
418
+ this.log("[emitSchemaChange] count of subscribers", this.schemaChangeSubscriberMap.size)
419
+ for (const state of this.schemaChangeSubscriberMap.values()) {
420
+ void state.subscriber(context)
421
+ }
422
+ this.log("[emitSchemaChange] emit end")
423
+ }
424
+
425
+ subscribeSchemaChange(subscriber: SchemaChangeSubscriber<ExternalValue, Schema>): () => void {
426
+ this.log("[subscribeSchemaChange] subscribe start", "subscriber", subscriber)
427
+ const state: SchemaChangeSubscriberEntry<ExternalValue, Schema> = {
428
+ unsubscribe: () => {
429
+ this.log("[subscribeSchemaChange] unsubscribe start", "subscriber", subscriber)
430
+ this.schemaChangeSubscriberMap.delete(subscriber)
431
+ this.log("[subscribeSchemaChange] unsubscribe end", "count of subscribers", this.schemaChangeSubscriberMap.size)
432
+ },
433
+ subscriber,
434
+ }
435
+ this.schemaChangeSubscriberMap.set(subscriber, state)
436
+ this.log("[subscribeSchemaChange] subscribe end", "count of subscribers", this.schemaChangeSubscriberMap.size)
437
+ return state.unsubscribe
438
+ }
439
+
440
+ unsubscribeSchemaChange(subscriber: SchemaChangeSubscriber<ExternalValue, Schema>): void {
441
+ this.log("[unsubscribeSchemaChange] unsubscribe start", "subscriber", subscriber)
442
+ this.schemaChangeSubscriberMap.delete(subscriber)
443
+ this.log("[unsubscribeSchemaChange] unsubscribe end", "count of subscribers", this.schemaChangeSubscriberMap.size)
444
+ }
445
+
446
+ clearSchemaChangeSubscribers(): void {
447
+ this.log("[clearSchemaChangeSubscribers] clear start, count of subscribers", this.schemaChangeSubscriberMap.size)
448
+ this.schemaChangeSubscriberMap.clear()
449
+ this.log("[clearSchemaChangeSubscribers] clear end, count of subscribers", this.schemaChangeSubscriberMap.size)
450
+ }
451
+
452
+ protected ready(): void {
453
+ this.log("[ready] ready start")
454
+ this.readyState.resolve()
455
+ this.log("[ready] ready end")
456
+ }
457
+
458
+ isReady(): boolean {
459
+ return this.readyState.isReady
460
+ }
461
+
462
+ async waitForReady(): Promise<void> {
463
+ return await this.readyState.promise
464
+ }
465
+
466
+ getInternalValue(): InternalValue {
467
+ return structuredClone(this.internalValue)
468
+ }
469
+
470
+ async getSchema(): Promise<Schema> {
471
+ await this.waitForReady()
472
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
473
+ const schema: Schema = {
474
+ inputorTypeName: this.inputorTypeName,
475
+ id: this.id,
476
+ name: this.name,
477
+ placeholder: this.placeholder,
478
+ value: this.value,
479
+ isVoid: this.isVoid,
480
+ isFocused: this.isFocused,
481
+ isTouched: this.isTouched,
482
+ isDirty: this.isDirty,
483
+ isDisabled: this.isDisabled,
484
+ isDebugMode: this.isDebugMode,
485
+ } as unknown as Schema
486
+ return structuredClone(schema)
487
+ }
488
+
489
+ async getContext(): Promise<Context> {
490
+ const schema = await this.getSchema()
491
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
492
+ const context: Context = {
493
+ ...schema,
494
+ internalValue: this.getInternalValue(),
495
+ isReady: this.isReady(),
496
+ } as unknown as Context
497
+ return structuredClone(context)
498
+ }
499
+
500
+ protected async isVoidExternalValue(value: ExternalValue): Promise<boolean> {
501
+ const isSame = JSON.stringify(value) === JSON.stringify(this.voidExternalValue)
502
+ return await Promise.resolve(isSame)
503
+ }
504
+
505
+ protected async isVoidInternalValue(value: InternalValue): Promise<boolean> {
506
+ const isSame = JSON.stringify(value) === JSON.stringify(this.voidInternalValue)
507
+ return await Promise.resolve(isSame)
508
+ }
509
+
510
+ protected async isSameExternalValue(value1: ExternalValue, value2: ExternalValue): Promise<boolean> {
511
+ const isSame = JSON.stringify(value1) === JSON.stringify(value2)
512
+ return await Promise.resolve(isSame)
513
+ }
514
+
515
+ protected async isSameInternalValue(value1: InternalValue, value2: InternalValue): Promise<boolean> {
516
+ const isSame = JSON.stringify(value1) === JSON.stringify(value2)
517
+ return await Promise.resolve(isSame)
518
+ }
519
+
520
+ protected async isSameSchema(schema1: Schema, schema2: Schema): Promise<boolean> {
521
+ const isSame = JSON.stringify(schema1) === JSON.stringify(schema2)
522
+ return await Promise.resolve(isSame)
523
+ }
524
+
525
+ /**
526
+ * 给定任意可能是 Schema 的 Record,判断其是否与当前 Inputor 的 Schema 相同。
527
+ */
528
+ async isEqualSchema(schema: unknown): Promise<boolean> {
529
+ if (typeof schema !== "object" || schema === null) {
530
+ return false
531
+ }
532
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
533
+ const externalSchema = schema as Schema
534
+ const internalSchema = await this.getSchema()
535
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
536
+ const partialExternalSchema = {} as Schema
537
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
538
+ const partialInternalSchema = {} as Schema
539
+ Object.keys(externalSchema).forEach((key) => {
540
+ if (key in internalSchema) {
541
+ Object.assign(partialInternalSchema, {
542
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
543
+ [key]: (internalSchema as Record<string, unknown>)[key],
544
+ })
545
+ Object.assign(partialExternalSchema, {
546
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
547
+ [key]: (externalSchema as Record<string, unknown>)[key],
548
+ })
549
+ }
550
+ })
551
+ const isSame = this.isSameSchema(partialExternalSchema, partialInternalSchema)
552
+ return await Promise.resolve(isSame)
553
+ }
554
+
555
+ protected async constrainInternalValue(internalValue: InternalValue): Promise<InternalValue> {
556
+ return await Promise.resolve(internalValue)
557
+ }
558
+
559
+ protected async constrainExternalValue(externalValue: ExternalValue): Promise<ExternalValue> {
560
+ return await Promise.resolve(externalValue)
561
+ }
562
+
563
+ protected async internalValueToExternalValue(internalValue: InternalValue): Promise<ExternalValue> {
564
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
565
+ return await Promise.resolve(internalValue as unknown as ExternalValue)
566
+ }
567
+
568
+ protected async externalValueToInternalValue(externalValue: ExternalValue): Promise<InternalValue> {
569
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
570
+ return await Promise.resolve(externalValue as unknown as InternalValue)
571
+ }
572
+
573
+ protected async resetInputStates(): Promise<void> {
574
+ clearTimeout(this.inputState.timer)
575
+ this.inputState = {
576
+ isInputing: false,
577
+ previousInputValue: this.value,
578
+ currentInputValue: this.value,
579
+ timer: undefined,
580
+ }
581
+ return await Promise.resolve()
582
+ }
583
+
584
+ async applyInputValue(): Promise<void> {
585
+ await this.updateValue(this.inputState.currentInputValue)
586
+ }
587
+
588
+ async updateInputValue(inputValue: ExternalValue): Promise<void> {
589
+ const previousInputStates = structuredClone(this.inputState)
590
+ await this.resetInputStates()
591
+ this.inputState = {
592
+ isInputing: true,
593
+ previousInputValue: previousInputStates.currentInputValue,
594
+ currentInputValue: inputValue,
595
+ timer: setTimeout(() => {
596
+ this.emitInputChange({
597
+ newInput: this.inputState.currentInputValue,
598
+ oldInput: this.inputState.previousInputValue,
599
+ oldValue: this.value,
600
+ })
601
+ if (this.enableAutoChangeValueAfterInput === true) {
602
+ void this.applyInputValue()
603
+ }
604
+ }, this.inputDebounce),
605
+ }
606
+ }
607
+
608
+ protected async prepareUpdateContext(): Promise<UpdateContext<ExternalValue, InternalValue>> {
609
+ const schema = await this.getSchema()
610
+ return {
611
+ ...schema,
612
+ internalValue: this.internalValue,
613
+ }
614
+ }
615
+
616
+ /**
617
+ * Will produce a new context rather than modifying the original one.
618
+ */
619
+ protected async doUpdateToContext(
620
+ context: UpdateContext<ExternalValue, InternalValue>,
621
+ update: Update<ExternalValue, InternalValue>,
622
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
623
+ const newContext = update(structuredClone(context))
624
+ return await Promise.resolve(newContext)
625
+ }
626
+
627
+ protected async applyUpdateContext(context: UpdateContext<ExternalValue, InternalValue>): Promise<void> {
628
+ this.id = context.id
629
+ this.name = context.name
630
+ this.placeholder = context.placeholder
631
+ this.value = context.value
632
+ this.isVoid = context.isVoid
633
+ this.isFocused = context.isFocused
634
+ this.isTouched = context.isTouched
635
+ this.isDirty = context.isDirty
636
+ this.isDisabled = context.isDisabled
637
+ this.isDebugMode = context.isDebugMode
638
+ this.internalValue = context.internalValue
639
+ await Promise.resolve()
640
+ }
641
+
642
+ protected async doUpdate(updater: Update<ExternalValue, InternalValue>): Promise<void> {
643
+ this.log("[doUpdate] doUpdate start")
644
+
645
+ const prevContext = await this.prepareUpdateContext()
646
+ const nextContext = await this.doUpdateToContext(prevContext, updater)
647
+
648
+ await this.applyUpdateContext(nextContext)
649
+
650
+ // handle emits & states updates
651
+ const isSameValue = await this.isSameExternalValue(nextContext.value, prevContext.value)
652
+ if (isSameValue === false) {
653
+ this.log("[doUpdate] value changed", "prevValue", prevContext.value, "nextValue", nextContext.value)
654
+ await this.resetInputStates()
655
+ this.emitValueChange({
656
+ newValue: nextContext.value,
657
+ oldValue: prevContext.value,
658
+ })
659
+ }
660
+
661
+ const isSameSchema = await this.isSameSchema(
662
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
663
+ nextContext as unknown as Schema,
664
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
665
+ prevContext as unknown as Schema,
666
+ )
667
+ if (isSameSchema === false) {
668
+ this.log("[doUpdate] schema changed", "prevSchema", prevContext, "nextSchema", nextContext)
669
+ this.emitSchemaChange({
670
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
671
+ newSchema: nextContext as unknown as Schema,
672
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
673
+ oldSchema: prevContext as unknown as Schema,
674
+ })
675
+ }
676
+
677
+ this.log("[doUpdate] doUpdate end")
678
+ }
679
+
680
+ protected async updatePlaceholderToContext(
681
+ context: UpdateContext<ExternalValue, InternalValue>,
682
+ placeholder: string,
683
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
684
+ context.placeholder = placeholder
685
+ return await Promise.resolve(context)
686
+ }
687
+
688
+ async updatePlaceholder(placeholder: string): Promise<void> {
689
+ await this.doUpdate(async (context) => {
690
+ return await this.updatePlaceholderToContext(context, placeholder)
691
+ })
692
+ }
693
+
694
+ protected async updateValueToContext(
695
+ context: UpdateContext<ExternalValue, InternalValue>,
696
+ value: ExternalValue,
697
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
698
+ const clonedContext = structuredClone(context)
699
+ const oldValue = clonedContext.value
700
+ const newValue = await this.constrainExternalValue(value)
701
+ const isSameValue = await this.isSameExternalValue(newValue, oldValue)
702
+ if (isSameValue === true) {
703
+ return clonedContext
704
+ }
705
+
706
+ const isVoidValue = await this.isVoidExternalValue(newValue)
707
+ clonedContext.isVoid = isVoidValue
708
+ clonedContext.isDirty = true
709
+ clonedContext.value = newValue
710
+
711
+ const newInternalValue = await this.externalValueToInternalValue(newValue)
712
+ const updatedContext = await this.updateInternalValueToContext(clonedContext, newInternalValue)
713
+ return updatedContext
714
+ }
715
+
716
+ async updateValue(value: ExternalValue): Promise<void> {
717
+ await this.doUpdate(async (context) => {
718
+ return await this.updateValueToContext(context, value)
719
+ })
720
+ }
721
+
722
+ protected async updateIsVoidToContext(
723
+ context: UpdateContext<ExternalValue, InternalValue>,
724
+ isVoid: boolean,
725
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
726
+ const currentIsVoid = context.isVoid
727
+ const isSameIsVoid = isVoid === currentIsVoid
728
+ if (isSameIsVoid === true) {
729
+ return context
730
+ }
731
+
732
+ if (isVoid === true) {
733
+ const updatedContext = await this.updateValueToContext(context, this.voidExternalValue)
734
+ return updatedContext
735
+ }
736
+
737
+ return context
738
+ }
739
+
740
+ async updateIsVoid(isVoid: true): Promise<void> {
741
+ await this.doUpdate(async (context) => {
742
+ return await this.updateIsVoidToContext(context, isVoid)
743
+ })
744
+ }
745
+
746
+ protected async updateIsFocusedToContext(
747
+ context: UpdateContext<ExternalValue, InternalValue>,
748
+ isFocused: boolean,
749
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
750
+ const currentIsFocused = context.isFocused
751
+ const isSameIsFocused = isFocused === currentIsFocused
752
+ if (isSameIsFocused === true) {
753
+ return context
754
+ }
755
+
756
+ context.isFocused = isFocused
757
+ const updatedContext = await this.updateIsTouchedToContext(context, true)
758
+ return updatedContext
759
+ }
760
+
761
+ async updateIsFocused(isFocused: boolean): Promise<void> {
762
+ await this.doUpdate(async (context) => {
763
+ return await this.updateIsFocusedToContext(context, isFocused)
764
+ })
765
+ }
766
+
767
+ protected async updateIsTouchedToContext(
768
+ context: UpdateContext<ExternalValue, InternalValue>,
769
+ isTouched: boolean,
770
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
771
+ context.isTouched = isTouched
772
+ return await Promise.resolve(context)
773
+ }
774
+
775
+ async updateIsTouched(isTouched: boolean): Promise<void> {
776
+ await this.doUpdate(async (context) => {
777
+ return await this.updateIsTouchedToContext(context, isTouched)
778
+ })
779
+ }
780
+
781
+ protected async updateIsDirtyToContext(
782
+ context: UpdateContext<ExternalValue, InternalValue>,
783
+ isDirty: boolean,
784
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
785
+ context.isDirty = isDirty
786
+ return await Promise.resolve(context)
787
+ }
788
+
789
+ async updateIsDirty(isDirty: boolean): Promise<void> {
790
+ await this.doUpdate(async (context) => {
791
+ return await this.updateIsDirtyToContext(context, isDirty)
792
+ })
793
+ }
794
+
795
+ protected async updateIsDisabledToContext(
796
+ context: UpdateContext<ExternalValue, InternalValue>,
797
+ isDisabled: boolean,
798
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
799
+ context.isDisabled = isDisabled
800
+ return await Promise.resolve(context)
801
+ }
802
+
803
+ async updateIsDisabled(isDisabled: boolean): Promise<void> {
804
+ await this.doUpdate(async (context) => {
805
+ return await this.updateIsDisabledToContext(context, isDisabled)
806
+ })
807
+ }
808
+
809
+ protected async updateIsDebugModeToContext(
810
+ context: UpdateContext<ExternalValue, InternalValue>,
811
+ isDebugMode: boolean,
812
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
813
+ context.isDebugMode = isDebugMode
814
+ return await Promise.resolve(context)
815
+ }
816
+
817
+ async updateIsDebugMode(isDebugMode: boolean): Promise<void> {
818
+ await this.doUpdate(async (context) => {
819
+ return await this.updateIsDebugModeToContext(context, isDebugMode)
820
+ })
821
+ }
822
+
823
+ protected async updateInternalValueToContext(
824
+ context: UpdateContext<ExternalValue, InternalValue>,
825
+ internalValue: InternalValue,
826
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
827
+ const clonedContext = structuredClone(context)
828
+ const oldInternalValue = clonedContext.internalValue
829
+ const newInternalValue = await this.constrainInternalValue(internalValue)
830
+ const isSameInternalValue = await this.isSameInternalValue(newInternalValue, oldInternalValue)
831
+ if (isSameInternalValue === true) {
832
+ return clonedContext
833
+ }
834
+
835
+ clonedContext.internalValue = newInternalValue
836
+ const newValue = await this.internalValueToExternalValue(newInternalValue)
837
+ const updatedContext = await this.updateValueToContext(clonedContext, newValue)
838
+ return updatedContext
839
+ }
840
+
841
+ protected async updateInternalValue(internalValue: InternalValue): Promise<void> {
842
+ await this.doUpdate(async (context) => {
843
+ return await this.updateInternalValueToContext(context, internalValue)
844
+ })
845
+ }
846
+
847
+ protected async updateSchemaToContext(
848
+ context: UpdateContext<ExternalValue, InternalValue>,
849
+ schema: Schema,
850
+ ): Promise<UpdateContext<ExternalValue, InternalValue>> {
851
+ let updatedContext = structuredClone(context)
852
+ updatedContext = await this.updatePlaceholderToContext(updatedContext, schema.placeholder)
853
+ updatedContext = await this.updateValueToContext(updatedContext, schema.value)
854
+ updatedContext = await this.updateIsVoidToContext(updatedContext, schema.isVoid)
855
+ updatedContext = await this.updateIsFocusedToContext(updatedContext, schema.isFocused)
856
+ updatedContext = await this.updateIsTouchedToContext(updatedContext, schema.isTouched)
857
+ updatedContext = await this.updateIsDirtyToContext(updatedContext, schema.isDirty)
858
+ updatedContext = await this.updateIsDisabledToContext(updatedContext, schema.isDisabled)
859
+ updatedContext = await this.updateIsDebugModeToContext(updatedContext, schema.isDebugMode)
860
+ return await Promise.resolve(updatedContext)
861
+ }
862
+
863
+ async updateSchema(schema: Schema): Promise<void> {
864
+ await this.doUpdate(async (context) => {
865
+ return await this.updateSchemaToContext(context, schema)
866
+ })
867
+ }
868
+
869
+ destroy(): void {
870
+ this.clearInputChangeSubscribers()
871
+ this.clearValueChangeSubscribers()
872
+ this.clearSchemaChangeSubscribers()
873
+ }
874
+ }