@zeix/le-truc 0.15.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 (406) hide show
  1. package/.ai-context.md +234 -0
  2. package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
  3. package/.editorconfig +12 -0
  4. package/.github/copilot-instructions.md +62 -0
  5. package/.github/workflows/codeql.yml +108 -0
  6. package/.github/workflows/static.yml +43 -0
  7. package/.prettierrc +17 -0
  8. package/CLAUDE.md +215 -0
  9. package/CODE_OF_CONDUCT.md +128 -0
  10. package/CONTRIBUTING.md +160 -0
  11. package/LICENSE +21 -0
  12. package/README.md +474 -0
  13. package/biome.json +295 -0
  14. package/bun.lock +239 -0
  15. package/docs/about.html +105 -0
  16. package/docs/assets/main.css +1 -0
  17. package/docs/assets/main.js +10 -0
  18. package/docs/assets/main.js.map +66 -0
  19. package/docs/components.html +293 -0
  20. package/docs/data-flow.html +308 -0
  21. package/docs/examples/basic-button.html +367 -0
  22. package/docs/examples/basic-counter.html +188 -0
  23. package/docs/examples/basic-hello.html +138 -0
  24. package/docs/examples/basic-number.html +271 -0
  25. package/docs/examples/basic-pluralize.html +214 -0
  26. package/docs/examples/card-callout.html +152 -0
  27. package/docs/examples/card-mediaqueries.html +138 -0
  28. package/docs/examples/context-media.html +198 -0
  29. package/docs/examples/empty.html +37 -0
  30. package/docs/examples/form-checkbox.html +233 -0
  31. package/docs/examples/form-combobox.html +420 -0
  32. package/docs/examples/form-listbox.html +434 -0
  33. package/docs/examples/form-radiogroup.html +296 -0
  34. package/docs/examples/form-spinbutton.html +402 -0
  35. package/docs/examples/form-textbox.html +361 -0
  36. package/docs/examples/layout.html +67 -0
  37. package/docs/examples/module-carousel.html +552 -0
  38. package/docs/examples/module-catalog.html +241 -0
  39. package/docs/examples/module-codeblock.html +270 -0
  40. package/docs/examples/module-dialog.html +343 -0
  41. package/docs/examples/module-lazyload.html +289 -0
  42. package/docs/examples/module-list.html +197 -0
  43. package/docs/examples/module-pagination.html +283 -0
  44. package/docs/examples/module-scrollarea.html +447 -0
  45. package/docs/examples/module-tabgroup.html +526 -0
  46. package/docs/examples/module-todo.html +367 -0
  47. package/docs/examples/module-with-type.html +63 -0
  48. package/docs/examples/nested-components.html +88 -0
  49. package/docs/examples/recursive.html +56 -0
  50. package/docs/examples/simple-text.html +39 -0
  51. package/docs/examples/snippet.html +93 -0
  52. package/docs/examples/with-styles.html +75 -0
  53. package/docs/getting-started.html +143 -0
  54. package/docs/index.html +112 -0
  55. package/docs/sitemap.xml +28 -0
  56. package/docs/styling.html +160 -0
  57. package/docs/sw.js +112 -0
  58. package/docs-src/api/README.md +478 -0
  59. package/docs-src/api/_media/LICENSE +21 -0
  60. package/docs-src/api/classes/CircularDependencyError.md +299 -0
  61. package/docs-src/api/classes/CircularMutationError.md +301 -0
  62. package/docs-src/api/classes/ContextRequestEvent.md +590 -0
  63. package/docs-src/api/classes/DependencyTimeoutError.md +301 -0
  64. package/docs-src/api/classes/InvalidCallbackError.md +303 -0
  65. package/docs-src/api/classes/InvalidComponentNameError.md +295 -0
  66. package/docs-src/api/classes/InvalidCustomElementError.md +301 -0
  67. package/docs-src/api/classes/InvalidEffectsError.md +301 -0
  68. package/docs-src/api/classes/InvalidPropertyNameError.md +307 -0
  69. package/docs-src/api/classes/InvalidReactivesError.md +307 -0
  70. package/docs-src/api/classes/InvalidSignalValueError.md +303 -0
  71. package/docs-src/api/classes/MissingElementError.md +307 -0
  72. package/docs-src/api/classes/NullishSignalValueError.md +299 -0
  73. package/docs-src/api/classes/StoreKeyExistsError.md +303 -0
  74. package/docs-src/api/classes/StoreKeyRangeError.md +299 -0
  75. package/docs-src/api/classes/StoreKeyReadonlyError.md +303 -0
  76. package/docs-src/api/functions/asBoolean.md +21 -0
  77. package/docs-src/api/functions/asEnum.md +31 -0
  78. package/docs-src/api/functions/asInteger.md +39 -0
  79. package/docs-src/api/functions/asJSON.md +49 -0
  80. package/docs-src/api/functions/asNumber.md +37 -0
  81. package/docs-src/api/functions/asString.md +37 -0
  82. package/docs-src/api/functions/createCollection.md +83 -0
  83. package/docs-src/api/functions/createSensor.md +71 -0
  84. package/docs-src/api/functions/dangerouslySetInnerHTML.md +48 -0
  85. package/docs-src/api/functions/defineComponent.md +65 -0
  86. package/docs-src/api/functions/isCollection.md +37 -0
  87. package/docs-src/api/functions/isParser.md +41 -0
  88. package/docs-src/api/functions/match.md +47 -0
  89. package/docs-src/api/functions/on.md +58 -0
  90. package/docs-src/api/functions/pass.md +53 -0
  91. package/docs-src/api/functions/provideContexts.md +47 -0
  92. package/docs-src/api/functions/read.md +47 -0
  93. package/docs-src/api/functions/requestContext.md +51 -0
  94. package/docs-src/api/functions/resolve.md +40 -0
  95. package/docs-src/api/functions/runEffects.md +51 -0
  96. package/docs-src/api/functions/runElementEffects.md +57 -0
  97. package/docs-src/api/functions/schedule.md +33 -0
  98. package/docs-src/api/functions/setAttribute.md +48 -0
  99. package/docs-src/api/functions/setProperty.md +52 -0
  100. package/docs-src/api/functions/setStyle.md +48 -0
  101. package/docs-src/api/functions/setText.md +42 -0
  102. package/docs-src/api/functions/show.md +42 -0
  103. package/docs-src/api/functions/toSignal.md +37 -0
  104. package/docs-src/api/functions/toggleAttribute.md +48 -0
  105. package/docs-src/api/functions/toggleClass.md +48 -0
  106. package/docs-src/api/functions/updateElement.md +53 -0
  107. package/docs-src/api/globals.md +131 -0
  108. package/docs-src/api/type-aliases/Cleanup.md +27 -0
  109. package/docs-src/api/type-aliases/Collection.md +91 -0
  110. package/docs-src/api/type-aliases/CollectionListener.md +27 -0
  111. package/docs-src/api/type-aliases/Component.md +17 -0
  112. package/docs-src/api/type-aliases/ComponentProp.md +11 -0
  113. package/docs-src/api/type-aliases/ComponentProps.md +11 -0
  114. package/docs-src/api/type-aliases/ComponentSetup.md +31 -0
  115. package/docs-src/api/type-aliases/ComponentUI.md +27 -0
  116. package/docs-src/api/type-aliases/Computed.md +49 -0
  117. package/docs-src/api/type-aliases/ComputedCallback.md +29 -0
  118. package/docs-src/api/type-aliases/Context.md +33 -0
  119. package/docs-src/api/type-aliases/ContextType.md +19 -0
  120. package/docs-src/api/type-aliases/DangerouslySetInnerHTMLOptions.md +27 -0
  121. package/docs-src/api/type-aliases/DiffResult.md +61 -0
  122. package/docs-src/api/type-aliases/Effect.md +35 -0
  123. package/docs-src/api/type-aliases/EffectCallback.md +23 -0
  124. package/docs-src/api/type-aliases/Effects.md +21 -0
  125. package/docs-src/api/type-aliases/ElementEffects.md +21 -0
  126. package/docs-src/api/type-aliases/ElementFromKey.md +21 -0
  127. package/docs-src/api/type-aliases/ElementQueries.md +27 -0
  128. package/docs-src/api/type-aliases/ElementUpdater.md +131 -0
  129. package/docs-src/api/type-aliases/EventHandler.md +31 -0
  130. package/docs-src/api/type-aliases/EventType.md +17 -0
  131. package/docs-src/api/type-aliases/Fallback.md +21 -0
  132. package/docs-src/api/type-aliases/Initializers.md +21 -0
  133. package/docs-src/api/type-aliases/LooseReader.md +31 -0
  134. package/docs-src/api/type-aliases/MatchHandlers.md +77 -0
  135. package/docs-src/api/type-aliases/MaybeCleanup.md +23 -0
  136. package/docs-src/api/type-aliases/MaybeSignal.md +17 -0
  137. package/docs-src/api/type-aliases/Parser.md +39 -0
  138. package/docs-src/api/type-aliases/ParserOrFallback.md +21 -0
  139. package/docs-src/api/type-aliases/PassedProp.md +25 -0
  140. package/docs-src/api/type-aliases/PassedProps.md +21 -0
  141. package/docs-src/api/type-aliases/Reactive.md +25 -0
  142. package/docs-src/api/type-aliases/Reader.md +31 -0
  143. package/docs-src/api/type-aliases/ReservedWords.md +11 -0
  144. package/docs-src/api/type-aliases/ResolveResult.md +29 -0
  145. package/docs-src/api/type-aliases/SensorEvents.md +25 -0
  146. package/docs-src/api/type-aliases/Signal.md +41 -0
  147. package/docs-src/api/type-aliases/State.md +85 -0
  148. package/docs-src/api/type-aliases/Store.md +29 -0
  149. package/docs-src/api/type-aliases/UI.md +11 -0
  150. package/docs-src/api/type-aliases/UnknownContext.md +13 -0
  151. package/docs-src/api/variables/CONTEXT_REQUEST.md +11 -0
  152. package/docs-src/api/variables/UNSET.md +23 -0
  153. package/docs-src/api/variables/batch.md +25 -0
  154. package/docs-src/api/variables/createComputed.md +41 -0
  155. package/docs-src/api/variables/createEffect.md +35 -0
  156. package/docs-src/api/variables/createState.md +37 -0
  157. package/docs-src/api/variables/createStore.md +42 -0
  158. package/docs-src/api/variables/diff.md +43 -0
  159. package/docs-src/api/variables/isAbortError.md +33 -0
  160. package/docs-src/api/variables/isAsyncFunction.md +39 -0
  161. package/docs-src/api/variables/isComputed.md +37 -0
  162. package/docs-src/api/variables/isEqual.md +49 -0
  163. package/docs-src/api/variables/isFunction.md +39 -0
  164. package/docs-src/api/variables/isMutableSignal.md +37 -0
  165. package/docs-src/api/variables/isNumber.md +33 -0
  166. package/docs-src/api/variables/isRecord.md +39 -0
  167. package/docs-src/api/variables/isRecordOrArray.md +39 -0
  168. package/docs-src/api/variables/isSignal.md +37 -0
  169. package/docs-src/api/variables/isState.md +37 -0
  170. package/docs-src/api/variables/isStore.md +37 -0
  171. package/docs-src/api/variables/isString.md +33 -0
  172. package/docs-src/api/variables/isSymbol.md +33 -0
  173. package/docs-src/api/variables/toError.md +33 -0
  174. package/docs-src/api/variables/valueString.md +33 -0
  175. package/docs-src/includes/menu.html +44 -0
  176. package/docs-src/pages/about.md +89 -0
  177. package/docs-src/pages/components.md +437 -0
  178. package/docs-src/pages/data-flow.md +449 -0
  179. package/docs-src/pages/getting-started.md +170 -0
  180. package/docs-src/pages/index.md +98 -0
  181. package/docs-src/pages/styling.md +165 -0
  182. package/eslint.config.js +64 -0
  183. package/examples/_common/clear.ts +49 -0
  184. package/examples/_common/fetch.ts +160 -0
  185. package/examples/_common/focus.ts +45 -0
  186. package/examples/_common/highlight.ts +5 -0
  187. package/examples/_global.css +463 -0
  188. package/examples/basic-button/basic-button.css +176 -0
  189. package/examples/basic-button/basic-button.html +46 -0
  190. package/examples/basic-button/basic-button.spec.ts +160 -0
  191. package/examples/basic-button/basic-button.ts +45 -0
  192. package/examples/basic-button/copyToClipboard.ts +37 -0
  193. package/examples/basic-counter/basic-counter.css +21 -0
  194. package/examples/basic-counter/basic-counter.html +24 -0
  195. package/examples/basic-counter/basic-counter.spec.ts +85 -0
  196. package/examples/basic-counter/basic-counter.ts +43 -0
  197. package/examples/basic-hello/basic-hello.html +34 -0
  198. package/examples/basic-hello/basic-hello.spec.ts +110 -0
  199. package/examples/basic-hello/basic-hello.ts +36 -0
  200. package/examples/basic-number/basic-number.html +79 -0
  201. package/examples/basic-number/basic-number.spec.ts +175 -0
  202. package/examples/basic-number/basic-number.ts +124 -0
  203. package/examples/basic-pluralize/basic-pluralize.html +64 -0
  204. package/examples/basic-pluralize/basic-pluralize.spec.ts +258 -0
  205. package/examples/basic-pluralize/basic-pluralize.ts +82 -0
  206. package/examples/card-callout/card-callout.css +79 -0
  207. package/examples/card-callout/card-callout.html +5 -0
  208. package/examples/card-mediaqueries/card-mediaqueries.html +29 -0
  209. package/examples/card-mediaqueries/card-mediaqueries.spec.ts +300 -0
  210. package/examples/card-mediaqueries/card-mediaqueries.ts +41 -0
  211. package/examples/context-media/context-media.html +3 -0
  212. package/examples/context-media/context-media.ts +127 -0
  213. package/examples/form-checkbox/form-checkbox.css +70 -0
  214. package/examples/form-checkbox/form-checkbox.html +13 -0
  215. package/examples/form-checkbox/form-checkbox.spec.ts +357 -0
  216. package/examples/form-checkbox/form-checkbox.ts +50 -0
  217. package/examples/form-checkbox/vanilla-checkbox.ts +101 -0
  218. package/examples/form-combobox/form-combobox.css +118 -0
  219. package/examples/form-combobox/form-combobox.html +74 -0
  220. package/examples/form-combobox/form-combobox.spec.ts +977 -0
  221. package/examples/form-combobox/form-combobox.ts +128 -0
  222. package/examples/form-listbox/form-listbox.css +71 -0
  223. package/examples/form-listbox/form-listbox.html +67 -0
  224. package/examples/form-listbox/form-listbox.spec.ts +1050 -0
  225. package/examples/form-listbox/form-listbox.ts +196 -0
  226. package/examples/form-listbox/mocks/timezones.json +495 -0
  227. package/examples/form-radiogroup/form-radiogroup.css +87 -0
  228. package/examples/form-radiogroup/form-radiogroup.html +51 -0
  229. package/examples/form-radiogroup/form-radiogroup.spec.ts +515 -0
  230. package/examples/form-radiogroup/form-radiogroup.ts +58 -0
  231. package/examples/form-spinbutton/form-spinbutton.css +95 -0
  232. package/examples/form-spinbutton/form-spinbutton.html +96 -0
  233. package/examples/form-spinbutton/form-spinbutton.spec.ts +688 -0
  234. package/examples/form-spinbutton/form-spinbutton.ts +111 -0
  235. package/examples/form-textbox/form-textbox.css +104 -0
  236. package/examples/form-textbox/form-textbox.html +53 -0
  237. package/examples/form-textbox/form-textbox.spec.ts +542 -0
  238. package/examples/form-textbox/form-textbox.ts +104 -0
  239. package/examples/main.css +22 -0
  240. package/examples/main.ts +23 -0
  241. package/examples/module-carousel/module-carousel.css +113 -0
  242. package/examples/module-carousel/module-carousel.html +208 -0
  243. package/examples/module-carousel/module-carousel.spec.ts +523 -0
  244. package/examples/module-carousel/module-carousel.ts +131 -0
  245. package/examples/module-catalog/module-catalog.css +22 -0
  246. package/examples/module-catalog/module-catalog.html +82 -0
  247. package/examples/module-catalog/module-catalog.spec.ts +396 -0
  248. package/examples/module-catalog/module-catalog.ts +37 -0
  249. package/examples/module-codeblock/module-codeblock.css +95 -0
  250. package/examples/module-codeblock/module-codeblock.html +28 -0
  251. package/examples/module-codeblock/module-codeblock.ts +47 -0
  252. package/examples/module-demo/module-demo.css +13 -0
  253. package/examples/module-dialog/module-dialog.css +96 -0
  254. package/examples/module-dialog/module-dialog.html +66 -0
  255. package/examples/module-dialog/module-dialog.spec.ts +557 -0
  256. package/examples/module-dialog/module-dialog.ts +81 -0
  257. package/examples/module-lazyload/mocks/empty.html +1 -0
  258. package/examples/module-lazyload/mocks/module-with-type.html +27 -0
  259. package/examples/module-lazyload/mocks/nested-components.html +52 -0
  260. package/examples/module-lazyload/mocks/recursive.html +20 -0
  261. package/examples/module-lazyload/mocks/simple-text.html +3 -0
  262. package/examples/module-lazyload/mocks/snippet.html +57 -0
  263. package/examples/module-lazyload/mocks/with-styles.html +39 -0
  264. package/examples/module-lazyload/module-lazyload.html +132 -0
  265. package/examples/module-lazyload/module-lazyload.spec.ts +734 -0
  266. package/examples/module-lazyload/module-lazyload.ts +89 -0
  267. package/examples/module-list/module-list.html +30 -0
  268. package/examples/module-list/module-list.spec.ts +592 -0
  269. package/examples/module-list/module-list.ts +99 -0
  270. package/examples/module-pagination/module-pagination.css +79 -0
  271. package/examples/module-pagination/module-pagination.html +16 -0
  272. package/examples/module-pagination/module-pagination.spec.ts +701 -0
  273. package/examples/module-pagination/module-pagination.ts +88 -0
  274. package/examples/module-scrollarea/module-scrollarea.css +77 -0
  275. package/examples/module-scrollarea/module-scrollarea.html +189 -0
  276. package/examples/module-scrollarea/module-scrollarea.spec.ts +445 -0
  277. package/examples/module-scrollarea/module-scrollarea.ts +81 -0
  278. package/examples/module-tabgroup/module-tabgroup.css +55 -0
  279. package/examples/module-tabgroup/module-tabgroup.html +269 -0
  280. package/examples/module-tabgroup/module-tabgroup.spec.ts +631 -0
  281. package/examples/module-tabgroup/module-tabgroup.ts +102 -0
  282. package/examples/module-toc/module-toc.css +34 -0
  283. package/examples/module-todo/module-todo.css +84 -0
  284. package/examples/module-todo/module-todo.html +92 -0
  285. package/examples/module-todo/module-todo.spec.ts +528 -0
  286. package/examples/module-todo/module-todo.ts +91 -0
  287. package/examples/section-hero/section-hero.css +37 -0
  288. package/examples/section-menu/section-menu.css +81 -0
  289. package/examples/server.ts +95 -0
  290. package/examples/test-setup.md +314 -0
  291. package/index.dev.js +1688 -0
  292. package/index.dev.ts +127 -0
  293. package/index.js +3 -0
  294. package/index.js.map +42 -0
  295. package/index.ts +127 -0
  296. package/package.json +64 -0
  297. package/playwright.config.ts +31 -0
  298. package/server/BUILD_SYSTEM.md +428 -0
  299. package/server/SERVER.md +286 -0
  300. package/server/build.ts +91 -0
  301. package/server/config.ts +130 -0
  302. package/server/effects/api.ts +28 -0
  303. package/server/effects/css.ts +31 -0
  304. package/server/effects/examples.ts +109 -0
  305. package/server/effects/js.ts +32 -0
  306. package/server/effects/menu.ts +34 -0
  307. package/server/effects/pages.ts +178 -0
  308. package/server/effects/service-worker.ts +57 -0
  309. package/server/effects/sitemap.ts +27 -0
  310. package/server/file-signals.ts +361 -0
  311. package/server/file-watcher.ts +77 -0
  312. package/server/io.ts +174 -0
  313. package/server/layout-engine.ts +470 -0
  314. package/server/layout-utils.ts +615 -0
  315. package/server/layouts/api.html +76 -0
  316. package/server/layouts/base.html +37 -0
  317. package/server/layouts/blog.html +115 -0
  318. package/server/layouts/example.html +104 -0
  319. package/server/layouts/overview.html +165 -0
  320. package/server/layouts/page.html +36 -0
  321. package/server/layouts/test.html +24 -0
  322. package/server/markdoc-helpers.ts +217 -0
  323. package/server/markdoc.config.ts +29 -0
  324. package/server/schema/callout.markdoc.ts +17 -0
  325. package/server/schema/carousel.markdoc.ts +118 -0
  326. package/server/schema/demo.markdoc.ts +74 -0
  327. package/server/schema/fence.markdoc.ts +84 -0
  328. package/server/schema/heading.markdoc.ts +23 -0
  329. package/server/schema/hero.markdoc.ts +59 -0
  330. package/server/schema/section.markdoc.ts +10 -0
  331. package/server/schema/slide.markdoc.ts +17 -0
  332. package/server/schema/source.markdoc.ts +53 -0
  333. package/server/schema/tabgroup.markdoc.ts +102 -0
  334. package/server/serve.ts +635 -0
  335. package/server/templates/README.md +352 -0
  336. package/server/templates/constants.ts +236 -0
  337. package/server/templates/fragments.ts +159 -0
  338. package/server/templates/hmr.ts +269 -0
  339. package/server/templates/menu.ts +33 -0
  340. package/server/templates/performance-hints.ts +94 -0
  341. package/server/templates/service-worker.ts +403 -0
  342. package/server/templates/sitemap.ts +57 -0
  343. package/server/templates/toc.ts +41 -0
  344. package/server/templates/utils.ts +378 -0
  345. package/src/component.ts +215 -0
  346. package/src/context.ts +156 -0
  347. package/src/effects/attribute.ts +82 -0
  348. package/src/effects/class.ts +28 -0
  349. package/src/effects/event.ts +67 -0
  350. package/src/effects/html.ts +60 -0
  351. package/src/effects/method.ts +57 -0
  352. package/src/effects/pass.ts +103 -0
  353. package/src/effects/property.ts +57 -0
  354. package/src/effects/style.ts +34 -0
  355. package/src/effects/text.ts +28 -0
  356. package/src/effects.ts +412 -0
  357. package/src/errors.ts +160 -0
  358. package/src/parsers/boolean.ts +14 -0
  359. package/src/parsers/json.ts +33 -0
  360. package/src/parsers/number.ts +55 -0
  361. package/src/parsers/string.ts +32 -0
  362. package/src/parsers.ts +90 -0
  363. package/src/scheduler.ts +47 -0
  364. package/src/signals/collection.ts +253 -0
  365. package/src/signals/sensor.ts +131 -0
  366. package/src/ui.ts +236 -0
  367. package/src/util.ts +187 -0
  368. package/tsconfig.json +34 -0
  369. package/types/examples/basic-button/basic-button.d.ts +16 -0
  370. package/types/examples/basic-hello/basic-hello.d.ts +18 -0
  371. package/types/index.d.ts +27 -0
  372. package/types/index.dev.d.ts +27 -0
  373. package/types/src/collection.d.ts +27 -0
  374. package/types/src/component.d.ts +32 -0
  375. package/types/src/context.d.ts +85 -0
  376. package/types/src/effects/attribute.d.ts +23 -0
  377. package/types/src/effects/callMethod.d.ts +23 -0
  378. package/types/src/effects/class.d.ts +13 -0
  379. package/types/src/effects/dangerouslySetInnerHTML.d.ts +18 -0
  380. package/types/src/effects/event.d.ts +18 -0
  381. package/types/src/effects/html.d.ts +17 -0
  382. package/types/src/effects/method.d.ts +22 -0
  383. package/types/src/effects/pass.d.ts +18 -0
  384. package/types/src/effects/property.d.ts +22 -0
  385. package/types/src/effects/setAttribute.d.ts +24 -0
  386. package/types/src/effects/setProperty.d.ts +23 -0
  387. package/types/src/effects/setStyle.d.ts +14 -0
  388. package/types/src/effects/setText.d.ts +13 -0
  389. package/types/src/effects/style.d.ts +13 -0
  390. package/types/src/effects/text.d.ts +12 -0
  391. package/types/src/effects/toggleClass.d.ts +14 -0
  392. package/types/src/effects.d.ts +153 -0
  393. package/types/src/errors.d.ts +99 -0
  394. package/types/src/events.d.ts +27 -0
  395. package/types/src/extractors.d.ts +23 -0
  396. package/types/src/parsers/boolean.d.ts +10 -0
  397. package/types/src/parsers/json.d.ts +13 -0
  398. package/types/src/parsers/number.d.ts +21 -0
  399. package/types/src/parsers/string.d.ts +19 -0
  400. package/types/src/parsers.d.ts +41 -0
  401. package/types/src/scheduler.d.ts +11 -0
  402. package/types/src/sensor.d.ts +27 -0
  403. package/types/src/signals/collection.d.ts +32 -0
  404. package/types/src/signals/sensor.d.ts +27 -0
  405. package/types/src/ui.d.ts +37 -0
  406. package/types/src/util.d.ts +65 -0
package/src/effects.ts ADDED
@@ -0,0 +1,412 @@
1
+ import {
2
+ type Cleanup,
3
+ createEffect,
4
+ isFunction,
5
+ isRecord,
6
+ isSignal,
7
+ isString,
8
+ type MaybeCleanup,
9
+ type Signal,
10
+ toError,
11
+ UNSET,
12
+ valueString,
13
+ } from '@zeix/cause-effect'
14
+ import type { Component, ComponentProps } from './component'
15
+ import { InvalidEffectsError } from './errors'
16
+ import { type Collection, isCollection } from './signals/collection'
17
+ import type { ElementFromKey, UI } from './ui'
18
+ import { DEV_MODE, elementName, LOG_ERROR, log } from './util'
19
+
20
+ /* === Types === */
21
+
22
+ type Effect<P extends ComponentProps, E extends Element> = (
23
+ host: Component<P>,
24
+ target: E,
25
+ ) => MaybeCleanup
26
+
27
+ type ElementEffects<P extends ComponentProps, E extends Element> =
28
+ | Effect<P, E>
29
+ | Effect<P, E>[]
30
+
31
+ type Effects<
32
+ P extends ComponentProps,
33
+ U extends UI & { host: Component<P> },
34
+ > = {
35
+ [K in keyof U]?: ElementEffects<P, ElementFromKey<U, K>>
36
+ }
37
+
38
+ type Reactive<T, P extends ComponentProps, E extends Element> =
39
+ | keyof P
40
+ | Signal<T & {}>
41
+ | ((target: E) => T | null | undefined)
42
+
43
+ type UpdateOperation = 'a' | 'c' | 'd' | 'h' | 'm' | 'p' | 's' | 't'
44
+
45
+ type ElementUpdater<E extends Element, T> = {
46
+ op: UpdateOperation
47
+ name?: string
48
+ read: (element: E) => T | null
49
+ update: (element: E, value: T) => void
50
+ delete?: (element: E) => void
51
+ resolve?: (element: E) => void
52
+ reject?: (error: unknown) => void
53
+ }
54
+
55
+ /* type ElementInserter<E extends Element> = {
56
+ position?: InsertPosition
57
+ create: (parent: E) => Element | null
58
+ resolve?: (parent: E) => void
59
+ reject?: (error: unknown) => void
60
+ } */
61
+
62
+ /* === Constants === */
63
+
64
+ // Special value explicitly marked as any so it can be used as signal value of any type
65
+ const RESET: any = Symbol('RESET')
66
+
67
+ /* === Internal Functions === */
68
+
69
+ const getUpdateDescription = (
70
+ op: UpdateOperation,
71
+ name: string = '',
72
+ ): string => {
73
+ const ops: Record<UpdateOperation, string> = {
74
+ a: 'attribute ',
75
+ c: 'class ',
76
+ d: 'dataset ',
77
+ h: 'inner HTML',
78
+ m: 'method call ',
79
+ p: 'property ',
80
+ s: 'style property ',
81
+ t: 'text content',
82
+ }
83
+ return ops[op] + name
84
+ }
85
+
86
+ /* === Exported Functions === */
87
+
88
+ /**
89
+ * Run element effects
90
+ *
91
+ * @since 0.15.0
92
+ * @param {U} host - Host component
93
+ * @param {E} target - Target element
94
+ * @param {ElementEffects<P, E>} effects - Effect functions to run
95
+ * @returns {MaybeCleanup} - Cleanup function that runs collected cleanup functions
96
+ * @throws {InvalidEffectsError} - If the effects are invalid
97
+ */
98
+ const runElementEffects = <P extends ComponentProps, E extends Element>(
99
+ host: Component<P>,
100
+ target: E,
101
+ effects: ElementEffects<P, E>,
102
+ ): MaybeCleanup => {
103
+ const cleanups: Cleanup[] = []
104
+
105
+ const run = (fn: Effect<P, E>) => {
106
+ const cleanup = fn(host, target)
107
+ if (cleanup) cleanups.push(cleanup)
108
+ }
109
+
110
+ if (Array.isArray(effects)) for (const fn of effects) run(fn)
111
+ else run(effects)
112
+
113
+ return () => {
114
+ cleanups.forEach(cleanup => cleanup())
115
+ cleanups.length = 0
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Run collection effects
121
+ *
122
+ * @since 0.15.0
123
+ * @param {Component<P>} host - Host component
124
+ * @param {Collection<E>} collection - Collection of elements
125
+ * @param {ElementEffects<P, E>} effects - Element effects
126
+ * @returns {Cleanup} - Cleanup function that runs collected cleanup functions
127
+ * @throws {InvalidEffectsError} - If the effects are invalid
128
+ */
129
+ const runCollectionEffects = <P extends ComponentProps, E extends Element>(
130
+ host: Component<P>,
131
+ collection: Collection<E>,
132
+ effects: ElementEffects<P, E>,
133
+ ): Cleanup => {
134
+ const cleanups: Map<E, Cleanup> = new Map()
135
+
136
+ const attach = (targets: readonly E[]) => {
137
+ for (const target of targets) {
138
+ const cleanup = runElementEffects(host, target, effects)
139
+ if (cleanup) cleanups.set(target, cleanup)
140
+ }
141
+ }
142
+ const detach = (targets: readonly E[]) => {
143
+ for (const target of targets) {
144
+ cleanups.get(target)?.()
145
+ cleanups.delete(target)
146
+ }
147
+ }
148
+
149
+ collection.on('add', attach)
150
+ collection.on('remove', detach)
151
+ attach(collection.get())
152
+ return () => {
153
+ for (const cleanup of cleanups.values()) cleanup()
154
+ cleanups.clear()
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Run component effects
160
+ *
161
+ * @since 0.15.0
162
+ * @param {ComponentUI<P, U>} ui - Component UI
163
+ * @param {Effects<P, U>} effects - Effect functions to run
164
+ * @returns {Cleanup} - Cleanup function that runs collected cleanup functions
165
+ * @throws {InvalidEffectsError} - If the effects are invalid
166
+ */
167
+ const runEffects = <
168
+ P extends ComponentProps,
169
+ U extends UI & { host: Component<P> },
170
+ >(
171
+ ui: U,
172
+ effects: Effects<P, U>,
173
+ ): Cleanup => {
174
+ if (!isRecord(effects)) throw new InvalidEffectsError(ui.host)
175
+
176
+ const cleanups: Cleanup[] = []
177
+ const keys = Object.keys(effects)
178
+ for (const key of keys) {
179
+ const k = key as keyof U
180
+ if (!effects[k]) continue
181
+
182
+ const elementEffects = Array.isArray(effects[k]) ? effects[k] : [effects[k]]
183
+ if (isCollection<ElementFromKey<U, typeof k>>(ui[k])) {
184
+ cleanups.push(runCollectionEffects(ui.host, ui[k], elementEffects))
185
+ } else if (ui[k]) {
186
+ const cleanup = runElementEffects(
187
+ ui.host,
188
+ ui[k] as ElementFromKey<U, typeof k>,
189
+ elementEffects,
190
+ )
191
+ if (cleanup) cleanups.push(cleanup)
192
+ }
193
+ }
194
+ return () => {
195
+ for (const cleanup of cleanups) cleanup()
196
+ cleanups.length = 0
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Resolve reactive property name, reader function or signal
202
+ *
203
+ * @param {Reactive<T, P, E>} reactive - Reactive property name, reader function or signal
204
+ * @param {Component<P, U>} host - Component host element
205
+ * @param {E} target - Element to resolve reactive value for
206
+ * @param {string} [context] - Context for error logging
207
+ * @returns {T} - Resolved reactive value
208
+ */
209
+ const resolveReactive = <
210
+ T extends {},
211
+ P extends ComponentProps,
212
+ E extends Element,
213
+ >(
214
+ reactive: Reactive<T, P, E>,
215
+ host: Component<P>,
216
+ target: E,
217
+ context?: string,
218
+ ): T => {
219
+ try {
220
+ return isString(reactive)
221
+ ? (host[reactive] as unknown as T)
222
+ : isSignal(reactive)
223
+ ? reactive.get()
224
+ : isFunction(reactive)
225
+ ? (reactive(target) as unknown as T)
226
+ : RESET
227
+ } catch (error) {
228
+ if (context) {
229
+ log(
230
+ error,
231
+ `Failed to resolve value of ${valueString(reactive)}${
232
+ context ? ` for ${context}` : ''
233
+ } in ${elementName(target)}${
234
+ (host as unknown as E) !== target ? ` in ${elementName(host)}` : ''
235
+ }`,
236
+ LOG_ERROR,
237
+ )
238
+ }
239
+ return RESET
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Core effect function for updating element properties based on reactive values.
245
+ * This function handles the lifecycle of reading, updating, and deleting element properties
246
+ * while providing proper error handling and debugging support.
247
+ *
248
+ * @since 0.9.0
249
+ * @param {Reactive<T, P, E>} reactive - The reactive value that drives the element updates
250
+ * @param {ElementUpdater<E, T>} updater - Configuration object defining how to read, update, and delete the element property
251
+ * @returns {Effect<P, E>} Effect function that manages the element property updates
252
+ */
253
+ const updateElement =
254
+ <T extends {}, P extends ComponentProps, E extends Element>(
255
+ reactive: Reactive<T, P, E>,
256
+ updater: ElementUpdater<E, T>,
257
+ ): Effect<P, E> =>
258
+ (host, target): Cleanup => {
259
+ const { op, name = '', read, update } = updater
260
+ const operationDesc = getUpdateDescription(op, name)
261
+
262
+ const ok = (verb: string) => () => {
263
+ if (DEV_MODE && host.debug) {
264
+ log(
265
+ target,
266
+ `${verb} ${operationDesc} of ${elementName(target)} in ${elementName(host)}`,
267
+ )
268
+ }
269
+ updater.resolve?.(target)
270
+ }
271
+
272
+ const err = (verb: string) => (error: unknown) => {
273
+ log(
274
+ error,
275
+ `Failed to ${verb} ${operationDesc} of ${elementName(target)} in ${elementName(host)}`,
276
+ LOG_ERROR,
277
+ )
278
+ updater.reject?.(error)
279
+ }
280
+
281
+ const fallback = read(target)
282
+
283
+ return createEffect(() => {
284
+ const value = resolveReactive(reactive, host, target, operationDesc)
285
+ const resolvedValue =
286
+ value === RESET
287
+ ? fallback
288
+ : value === UNSET
289
+ ? updater.delete
290
+ ? null
291
+ : fallback
292
+ : value
293
+
294
+ if (updater.delete && resolvedValue === null) {
295
+ try {
296
+ updater.delete!(target)
297
+ ok('delete')()
298
+ } catch (error) {
299
+ err('delete')(error)
300
+ }
301
+ } else if (resolvedValue != null) {
302
+ const current = read(target)
303
+ if (Object.is(resolvedValue, current)) return
304
+ try {
305
+ update(target, resolvedValue)
306
+ ok('update')()
307
+ } catch (error) {
308
+ err('update')(error)
309
+ }
310
+ }
311
+ })
312
+ }
313
+
314
+ /**
315
+ * Effect for dynamically inserting or removing elements based on a reactive numeric value.
316
+ * Positive values insert elements, negative values remove them.
317
+ *
318
+ * @since 0.12.1
319
+ * @param {Reactive<number, P, E>} reactive - Reactive value determining number of elements to insert (positive) or remove (negative)
320
+ * @param {ElementInserter<E>} inserter - Configuration object defining how to create and position elements
321
+ * @returns {Effect<P, E>} Effect function that manages element insertion and removal
322
+ * /
323
+ const insertOrRemoveElement =
324
+ <P extends ComponentProps, E extends Element = HTMLElement>(
325
+ reactive: Reactive<number, P, E>,
326
+ inserter?: ElementInserter<E>,
327
+ ): Effect<P, E> =>
328
+ (host, target) => {
329
+ const ok = (verb: string) => () => {
330
+ if (DEV_MODE && host.debug) {
331
+ log(
332
+ target,
333
+ `${verb} element in ${elementName(target)} in ${elementName(host)}`,
334
+ )
335
+ }
336
+ if (isFunction(inserter?.resolve)) {
337
+ inserter.resolve(target)
338
+ } else {
339
+ const signal = isSignal<number>(reactive) ? reactive : undefined
340
+ if (isState(signal)) signal.set(0)
341
+ }
342
+ }
343
+
344
+ const err = (verb: string) => (error: unknown) => {
345
+ log(
346
+ error,
347
+ `Failed to ${verb} element in ${elementName(target)} in ${elementName(host)}`,
348
+ LOG_ERROR,
349
+ )
350
+ inserter?.reject?.(error)
351
+ }
352
+
353
+ return createEffect(() => {
354
+ const diff = resolveReactive(
355
+ reactive,
356
+ host,
357
+ target,
358
+ 'insertion or deletion',
359
+ )
360
+ const resolvedDiff = diff === RESET ? 0 : diff
361
+
362
+ if (resolvedDiff > 0) {
363
+ // Positive diff => insert element
364
+ if (!inserter) throw new TypeError(`No inserter provided`)
365
+ try {
366
+ for (let i = 0; i < resolvedDiff; i++) {
367
+ const element = inserter.create(target)
368
+ if (!element) continue
369
+ target.insertAdjacentElement(
370
+ inserter.position ?? 'beforeend',
371
+ element,
372
+ )
373
+ }
374
+ ok('insert')()
375
+ } catch (error) {
376
+ err('insert')(error)
377
+ }
378
+ } else if (resolvedDiff < 0) {
379
+ try {
380
+ if (
381
+ inserter &&
382
+ (inserter.position === 'afterbegin' ||
383
+ inserter.position === 'beforeend')
384
+ ) {
385
+ for (let i = 0; i > resolvedDiff; i--) {
386
+ if (inserter.position === 'afterbegin')
387
+ target.firstElementChild?.remove()
388
+ else target.lastElementChild?.remove()
389
+ }
390
+ } else {
391
+ target.remove()
392
+ }
393
+ ok('remove')()
394
+ } catch (error) {
395
+ err('remove')(error)
396
+ }
397
+ }
398
+ })
399
+ } */
400
+
401
+ export {
402
+ type Effect,
403
+ type Effects,
404
+ type ElementEffects,
405
+ type ElementUpdater,
406
+ type Reactive,
407
+ runEffects,
408
+ runElementEffects,
409
+ resolveReactive,
410
+ updateElement,
411
+ RESET,
412
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,160 @@
1
+ import { valueString } from '@zeix/cause-effect'
2
+ import { elementName } from './util'
3
+
4
+ /* === Error Classes === */
5
+
6
+ /**
7
+ * Error thrown when a circular dependency is detected in a selection signal
8
+ *
9
+ * @since 0.14.0
10
+ */
11
+ class CircularMutationError extends Error {
12
+ /**
13
+ * @param {HTMLElement} host - Host component
14
+ * @param {string} selector - Selector used to find the elements
15
+ */
16
+ constructor(host: HTMLElement, selector: string) {
17
+ super(
18
+ `Circular dependency detected in selection signal for component ${elementName(host)} with selector "${selector}"`,
19
+ )
20
+ this.name = 'CircularMutationError'
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Error thrown when component name violates rules for custom element names
26
+ *
27
+ * @since 0.14.0
28
+ */
29
+ class InvalidComponentNameError extends TypeError {
30
+ /**
31
+ * @param {string} component - Component name
32
+ */
33
+ constructor(component: string) {
34
+ super(
35
+ `Invalid component name "${component}". Custom element names must contain a hyphen, start with a lowercase letter, and contain only lowercase letters, numbers, and hyphens.`,
36
+ )
37
+ this.name = 'InvalidComponentNameError'
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Error thrown when trying to assign a property name that conflicts with reserved words or inherited HTMLElement properties
43
+ *
44
+ * @since 0.14.0
45
+ */
46
+ class InvalidPropertyNameError extends TypeError {
47
+ /**
48
+ * @param {string} component - Component name
49
+ * @param {string} prop - Property name
50
+ * @param {string} reason - Explanation why the property is invalid
51
+ */
52
+ constructor(component: string, prop: string, reason: string) {
53
+ super(
54
+ `Invalid property name "${prop}" for component <${component}>. ${reason}`,
55
+ )
56
+ this.name = 'InvalidPropertyNameError'
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Error thrown when setup function does not return effects
62
+ *
63
+ * @since 0.14.0
64
+ */
65
+ class InvalidEffectsError extends TypeError {
66
+ /**
67
+ * @param {HTMLElement} host - Host component
68
+ * @param {Error} cause - Error that caused the invalid effects
69
+ */
70
+ constructor(host: HTMLElement, cause?: Error) {
71
+ super(
72
+ `Invalid effects in component ${elementName(host)}. Effects must be a record of effects for UI elements or the component, or a Promise that resolves to effects.`,
73
+ )
74
+ this.name = 'InvalidEffectsError'
75
+ if (cause) this.cause = cause
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Error thrown when a required desacendent element does not exist in a component's DOM subtree
81
+ *
82
+ * @since 0.14.0
83
+ */
84
+ class MissingElementError extends Error {
85
+ /**
86
+ * @param {HTMLElement} host - Host component
87
+ * @param {string} selector - Selector used to find the elements
88
+ * @param {string} required - Explanation why the element is required
89
+ */
90
+ constructor(host: HTMLElement, selector: string, required: string) {
91
+ super(
92
+ `Missing required element <${selector}> in component ${elementName(host)}. ${required}`,
93
+ )
94
+ this.name = 'MissingElementError'
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Error when a component's dependencies are not met within a specified timeout
100
+ *
101
+ * @since 0.14.0
102
+ */
103
+ class DependencyTimeoutError extends Error {
104
+ /**
105
+ * @param {HTMLElement} host - Host component
106
+ * @param {string[]} missing - List of missing dependencies
107
+ */
108
+ constructor(host: HTMLElement, missing: string[]) {
109
+ super(
110
+ `Timeout waiting for: [${missing.join(', ')}] in component ${elementName(host)}.`,
111
+ )
112
+ this.name = 'DependencyTimeoutError'
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Error thrown when reactives passed to a component are invalid
118
+ *
119
+ * @since 0.15.0
120
+ */
121
+ class InvalidReactivesError extends TypeError {
122
+ /**
123
+ * @param {HTMLElement} host - Host component
124
+ * @param {HTMLElement} target - Target component
125
+ * @param {unknown} reactives - Reactives passed to the component
126
+ */
127
+ constructor(host: HTMLElement, target: HTMLElement, reactives: unknown) {
128
+ super(
129
+ `Expected reactives passed from ${elementName(host)} to ${elementName(target)} to be a record of signals, reactive property names or functions. Got ${valueString(reactives)}.`,
130
+ )
131
+ this.name = 'InvalidReactivesError'
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Error thrown when target element is not a custom element as expected
137
+ *
138
+ * @since 0.15.0
139
+ */
140
+ class InvalidCustomElementError extends TypeError {
141
+ /**
142
+ * @param {HTMLElement} target - Target component
143
+ * @param {string} where - Location where the error occurred
144
+ */
145
+ constructor(target: HTMLElement, where: string) {
146
+ super(`Target ${elementName(target)} is not a custom element in ${where}.`)
147
+ this.name = 'InvalidCustomElementError'
148
+ }
149
+ }
150
+
151
+ export {
152
+ CircularMutationError,
153
+ DependencyTimeoutError,
154
+ InvalidComponentNameError,
155
+ InvalidCustomElementError,
156
+ InvalidPropertyNameError,
157
+ InvalidEffectsError,
158
+ InvalidReactivesError,
159
+ MissingElementError,
160
+ }
@@ -0,0 +1,14 @@
1
+ import type { Parser } from '../parsers'
2
+ import type { UI } from '../ui'
3
+
4
+ /**
5
+ * Parse a boolean attribute as an actual boolean value
6
+ *
7
+ * @since 0.13.1
8
+ * @returns {Parser<boolean, UI>}
9
+ */
10
+ const asBoolean =
11
+ (): Parser<boolean, UI> => (_: UI, value: string | null | undefined) =>
12
+ value != null && value !== 'false'
13
+
14
+ export { asBoolean }
@@ -0,0 +1,33 @@
1
+ import { type Fallback, getFallback, type Parser } from '../parsers'
2
+ import type { UI } from '../ui'
3
+
4
+ /**
5
+ * Parse a string as a JSON serialized object with a fallback
6
+ *
7
+ * @since 0.11.0
8
+ * @param {Fallback<T, U>} fallback - Fallback value or reader function
9
+ * @returns {Parser<T, U>} Parser function
10
+ * @throws {TypeError} If the value and fallback are both null or undefined
11
+ * @throws {SyntaxError} If value is not a valid JSON string
12
+ */
13
+ const asJSON =
14
+ <T extends {}, U extends UI>(fallback: Fallback<T, U>): Parser<T, U> =>
15
+ (ui: U, value: string | null | undefined) => {
16
+ if ((value ?? fallback) == null)
17
+ throw new TypeError(
18
+ 'asJSON: Value and fallback are both null or undefined',
19
+ )
20
+ if (value == null) return getFallback(ui, fallback)
21
+ if (value === '') throw new TypeError('Empty string is not valid JSON')
22
+ let result: T | undefined
23
+ try {
24
+ result = JSON.parse(value)
25
+ } catch (error) {
26
+ throw new SyntaxError(`Failed to parse JSON: ${String(error)}`, {
27
+ cause: error,
28
+ })
29
+ }
30
+ return result ?? getFallback(ui, fallback)
31
+ }
32
+
33
+ export { asJSON }
@@ -0,0 +1,55 @@
1
+ import { type Fallback, getFallback, type Parser } from '../parsers'
2
+ import type { UI } from '../ui'
3
+
4
+ /* === Internal Functions === */
5
+
6
+ const parseNumber = (
7
+ parseFn: (v: string) => number,
8
+ value: string | null | undefined,
9
+ ) => {
10
+ if (value == null) return
11
+ const parsed = parseFn(value)
12
+ return Number.isFinite(parsed) ? parsed : undefined
13
+ }
14
+
15
+ /* === Exported Functions === */
16
+
17
+ /**
18
+ * Parse a string as a number forced to integer with a fallback
19
+ *
20
+ * Supports hexadecimal and scientific notation
21
+ *
22
+ * @since 0.11.0
23
+ * @param {Fallback<number, U>} [fallback=0] - Fallback value or reader function
24
+ * @returns {Parser<number, U>} Parser function
25
+ */
26
+ const asInteger =
27
+ <U extends UI>(fallback: Fallback<number, U> = 0): Parser<number, U> =>
28
+ (ui: U, value: string | null | undefined) => {
29
+ if (value == null) return getFallback(ui, fallback)
30
+
31
+ // Handle hexadecimal notation
32
+ const trimmed = value.trim()
33
+ if (trimmed.toLowerCase().startsWith('0x'))
34
+ return (
35
+ parseNumber(v => parseInt(v, 16), trimmed) ?? getFallback(ui, fallback)
36
+ )
37
+
38
+ // Handle other formats (including scientific notation)
39
+ const parsed = parseNumber(parseFloat, value)
40
+ return parsed != null ? Math.trunc(parsed) : getFallback(ui, fallback)
41
+ }
42
+
43
+ /**
44
+ * Parse a string as a number with a fallback
45
+ *
46
+ * @since 0.11.0
47
+ * @param {Fallback<number, U>} [fallback=0] - Fallback value or reader function
48
+ * @returns {Parser<number, U>} Parser function
49
+ */
50
+ const asNumber =
51
+ <U extends UI>(fallback: Fallback<number, U> = 0): Parser<number, U> =>
52
+ (ui: U, value: string | null | undefined) =>
53
+ parseNumber(parseFloat, value) ?? getFallback(ui, fallback)
54
+
55
+ export { asInteger, asNumber }
@@ -0,0 +1,32 @@
1
+ import { type Fallback, getFallback, type Parser } from '../parsers'
2
+ import type { UI } from '../ui'
3
+
4
+ /**
5
+ * Pass through string with a fallback
6
+ *
7
+ * @since 0.11.0
8
+ * @param {Fallback<string, U>} [fallback=''] - Fallback value or reader function
9
+ * @returns {Parser<string, U>} Parser function
10
+ */
11
+ const asString =
12
+ <U extends UI>(fallback: Fallback<string, U> = ''): Parser<string, U> =>
13
+ (ui: U, value: string | null | undefined) =>
14
+ value ?? getFallback(ui, fallback)
15
+
16
+ /**
17
+ * Parse a string as a multi-state value (for example: ['true', 'false', 'mixed'], defaulting to the first valid option
18
+ *
19
+ * @since 0.9.0
20
+ * @param {[string, ...string[]]} valid - Array of valid values
21
+ * @returns {Parser<string, UI>} Parser function
22
+ */
23
+ const asEnum =
24
+ (valid: [string, ...string[]]): Parser<string, UI> =>
25
+ (_: UI, value: string | null | undefined) => {
26
+ if (value == null) return valid[0]
27
+ const lowerValue = value.toLowerCase()
28
+ const matchingValid = valid.find(v => v.toLowerCase() === lowerValue)
29
+ return matchingValid ? value : valid[0]
30
+ }
31
+
32
+ export { asString, asEnum }