@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
@@ -0,0 +1,592 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ /**
4
+ * Test Suite: module-list Component
5
+ *
6
+ * Comprehensive tests for the Le Truc module-list component, which provides
7
+ * a dynamic list management interface with add/remove functionality:
8
+ * - Add items via form submission with text input
9
+ * - Delete items via delete buttons
10
+ * - Template-based item rendering with slot content
11
+ * - Integration with form-textbox and basic-button components
12
+ *
13
+ * Key Features Tested:
14
+ * - ✅ Initial state rendering with empty list
15
+ * - ✅ Add functionality via form submission and programmatic API
16
+ * - ✅ Delete functionality via delete buttons (UI interaction)
17
+ * - ✅ Template cloning and slot content replacement
18
+ * - ✅ Button state management (disabled when textbox empty)
19
+ * - ✅ Form integration with textbox clearing
20
+ * - ✅ Event delegation for dynamic delete buttons
21
+ * - ✅ Component API methods (add and delete work correctly)
22
+ * - ✅ Data attributes for item tracking
23
+ * - ✅ Error handling and edge cases
24
+ *
25
+ * Architecture Notes:
26
+ * - Uses template cloning for dynamic item creation
27
+ * - Integrates with form-textbox for input handling
28
+ * - Uses basic-button components for add/delete actions
29
+ * - Implements event delegation for dynamically added delete buttons
30
+ * - Supports custom processing via add() method callback
31
+ * - Tracks items using data-key attributes for deletion
32
+ * - Uses delete() method instead of remove() to avoid native DOM method conflicts
33
+ */
34
+
35
+ test.describe('module-list component', () => {
36
+ test.beforeEach(async ({ page }) => {
37
+ page.on('console', msg => {
38
+ console.log(`[browser] ${msg.type()}: ${msg.text()}`)
39
+ })
40
+
41
+ await page.goto('http://localhost:3000/test/module-list.html')
42
+ await page.waitForSelector('module-list')
43
+ })
44
+
45
+ test.describe('Initial State', () => {
46
+ test('renders with empty list and correct initial state', async ({
47
+ page,
48
+ }) => {
49
+ const moduleList = page.locator('module-list')
50
+ const container = moduleList.locator('[data-container]')
51
+ const template = moduleList.locator('template')
52
+ const form = moduleList.locator('form')
53
+ const textbox = moduleList.locator('form-textbox')
54
+ const addButton = moduleList.locator('basic-button.add')
55
+
56
+ // Should have empty container initially
57
+ await expect(container.locator('li')).toHaveCount(0)
58
+
59
+ // Should have template element
60
+ await expect(template).toBeAttached()
61
+
62
+ // Should have form with textbox and add button
63
+ await expect(form).toBeAttached()
64
+ await expect(textbox).toBeAttached()
65
+ await expect(addButton).toBeAttached()
66
+
67
+ // Add button should be disabled when textbox is empty
68
+ const addButtonElement = addButton.locator('button')
69
+ await expect(addButtonElement).toBeDisabled()
70
+ })
71
+
72
+ test('add button is enabled when textbox has content', async ({ page }) => {
73
+ const moduleList = page.locator('module-list')
74
+ const textboxInput = moduleList.locator('form-textbox input')
75
+ const addButton = moduleList.locator('basic-button.add button')
76
+
77
+ // Initially disabled
78
+ await expect(addButton).toBeDisabled()
79
+
80
+ // Type in textbox
81
+ await textboxInput.fill('Test item')
82
+ await expect(addButton).not.toBeDisabled()
83
+
84
+ // Clear textbox
85
+ await textboxInput.fill('')
86
+ await expect(addButton).toBeDisabled()
87
+ })
88
+ })
89
+
90
+ test.describe('Add Functionality', () => {
91
+ test('adds item via form submission', async ({ page }) => {
92
+ const moduleList = page.locator('module-list')
93
+ const textboxInput = moduleList.locator('form-textbox input')
94
+ const addButton = moduleList.locator('basic-button.add button')
95
+ const container = moduleList.locator('[data-container]')
96
+
97
+ // Add first item
98
+ await textboxInput.fill('First item')
99
+ await addButton.click()
100
+
101
+ // Should have one item
102
+ const items = container.locator('li')
103
+ await expect(items).toHaveCount(1)
104
+ await expect(items.first()).toContainText('First item')
105
+
106
+ // Textbox should be cleared
107
+ await expect(textboxInput).toHaveValue('')
108
+
109
+ // Add button should be disabled again
110
+ await expect(addButton).toBeDisabled()
111
+ })
112
+
113
+ test('adds item via Enter key in form', async ({ page }) => {
114
+ const moduleList = page.locator('module-list')
115
+ const textboxInput = moduleList.locator('form-textbox input')
116
+ const container = moduleList.locator('[data-container]')
117
+
118
+ // Type and press Enter
119
+ await textboxInput.fill('Enter key item')
120
+ await textboxInput.press('Enter')
121
+
122
+ // Should have one item
123
+ const items = container.locator('li')
124
+ await expect(items).toHaveCount(1)
125
+ await expect(items.first()).toContainText('Enter key item')
126
+
127
+ // Textbox should be cleared
128
+ await expect(textboxInput).toHaveValue('')
129
+ })
130
+
131
+ test('adds multiple items sequentially', async ({ page }) => {
132
+ const moduleList = page.locator('module-list')
133
+ const textboxInput = moduleList.locator('form-textbox input')
134
+ const addButton = moduleList.locator('basic-button.add button')
135
+ const container = moduleList.locator('[data-container]')
136
+
137
+ // Add multiple items
138
+ const itemTexts = ['First item', 'Second item', 'Third item']
139
+
140
+ for (const itemText of itemTexts) {
141
+ await textboxInput.fill(itemText)
142
+ await addButton.click()
143
+ }
144
+
145
+ // Should have all items
146
+ const items = container.locator('li')
147
+ await expect(items).toHaveCount(3)
148
+
149
+ // Check content of each item
150
+ for (let i = 0; i < itemTexts.length; i++) {
151
+ await expect(items.nth(i)).toContainText(itemTexts[i])
152
+ }
153
+ })
154
+
155
+ test('adds items with data-key attributes', async ({ page }) => {
156
+ const moduleList = page.locator('module-list')
157
+ const textboxInput = moduleList.locator('form-textbox input')
158
+ const addButton = moduleList.locator('basic-button.add button')
159
+ const container = moduleList.locator('[data-container]')
160
+
161
+ // Add items
162
+ await textboxInput.fill('Item 1')
163
+ await addButton.click()
164
+ await textboxInput.fill('Item 2')
165
+ await addButton.click()
166
+
167
+ // Check data-key attributes
168
+ const items = container.locator('li')
169
+ await expect(items.nth(0)).toHaveAttribute('data-key', '0')
170
+ await expect(items.nth(1)).toHaveAttribute('data-key', '1')
171
+ })
172
+
173
+ test('programmatic add method works correctly', async ({ page }) => {
174
+ const moduleList = page.locator('module-list')
175
+ const container = moduleList.locator('[data-container]')
176
+
177
+ // Use programmatic API
178
+ await page.evaluate(() => {
179
+ const component = document.querySelector('module-list') as any
180
+ component.add((item: HTMLElement) => {
181
+ item.querySelector('slot')?.replaceWith('Programmatic item')
182
+ })
183
+ })
184
+
185
+ // Should have one item
186
+ const items = container.locator('li')
187
+ await expect(items).toHaveCount(1)
188
+ await expect(items.first()).toContainText('Programmatic item')
189
+ })
190
+ })
191
+
192
+ test.describe('Delete Functionality', () => {
193
+ test('deletes item via delete button click', async ({ page }) => {
194
+ const moduleList = page.locator('module-list')
195
+ const textboxInput = moduleList.locator('form-textbox input')
196
+ const addButton = moduleList.locator('basic-button.add button')
197
+ const container = moduleList.locator('[data-container]')
198
+
199
+ // Add items
200
+ await textboxInput.fill('Item 1')
201
+ await addButton.click()
202
+ await textboxInput.fill('Item 2')
203
+ await addButton.click()
204
+
205
+ // Should have two items
206
+ await expect(container.locator('li')).toHaveCount(2)
207
+
208
+ // Click delete button on first item
209
+ const firstItemDeleteButton = container
210
+ .locator('li')
211
+ .first()
212
+ .locator('basic-button.delete button')
213
+ await firstItemDeleteButton.click()
214
+
215
+ // Should have one item remaining
216
+ await expect(container.locator('li')).toHaveCount(1)
217
+ await expect(container.locator('li').first()).toContainText('Item 2')
218
+ })
219
+
220
+ test('deletes correct item when multiple items exist', async ({ page }) => {
221
+ const moduleList = page.locator('module-list')
222
+ const textboxInput = moduleList.locator('form-textbox input')
223
+ const addButton = moduleList.locator('basic-button.add button')
224
+ const container = moduleList.locator('[data-container]')
225
+
226
+ // Add multiple items
227
+ const itemTexts = ['First', 'Second', 'Third']
228
+ for (const itemText of itemTexts) {
229
+ await textboxInput.fill(itemText)
230
+ await addButton.click()
231
+ }
232
+
233
+ // Delete middle item
234
+ const secondItemDeleteButton = container
235
+ .locator('li')
236
+ .nth(1)
237
+ .locator('basic-button.delete button')
238
+ await secondItemDeleteButton.click()
239
+
240
+ // Should have two items remaining
241
+ const items = container.locator('li')
242
+ await expect(items).toHaveCount(2)
243
+ await expect(items.nth(0)).toContainText('First')
244
+ await expect(items.nth(1)).toContainText('Third')
245
+ })
246
+
247
+ test('programmatic add method works correctly', async ({ page }) => {
248
+ const moduleList = page.locator('module-list')
249
+ const container = moduleList.locator('[data-container]')
250
+
251
+ // Initially empty
252
+ await expect(container.locator('li')).toHaveCount(0)
253
+
254
+ // Test the programmatic add method
255
+ await page.evaluate(() => {
256
+ const component = document.querySelector('module-list') as any
257
+ console.log('Component add method type:', typeof component.add)
258
+ component.add((item: HTMLElement) => {
259
+ item.querySelector('slot')?.replaceWith('Programmatic item')
260
+ })
261
+ })
262
+
263
+ // Should have one item
264
+ await expect(container.locator('li')).toHaveCount(1)
265
+ await expect(container.locator('li').first()).toContainText(
266
+ 'Programmatic item',
267
+ )
268
+ })
269
+
270
+ test('programmatic delete method works correctly', async ({ page }) => {
271
+ const moduleList = page.locator('module-list')
272
+ const container = moduleList.locator('[data-container]')
273
+
274
+ // Add items using programmatic API
275
+ await page.evaluate(() => {
276
+ const component = document.querySelector('module-list') as any
277
+ component.add((item: HTMLElement) => {
278
+ item.querySelector('slot')?.replaceWith('Item to keep')
279
+ })
280
+ component.add((item: HTMLElement) => {
281
+ item.querySelector('slot')?.replaceWith('Item to delete')
282
+ })
283
+ })
284
+
285
+ // Should have two items
286
+ await expect(container.locator('li')).toHaveCount(2)
287
+
288
+ // Use the component's delete method to remove item with data-key="1"
289
+ await page.evaluate(() => {
290
+ const component = document.querySelector('module-list') as any
291
+ component.delete('1')
292
+ })
293
+
294
+ // Should have one item remaining
295
+ await expect(container.locator('li')).toHaveCount(1)
296
+ await expect(container.locator('li').first()).toContainText(
297
+ 'Item to keep',
298
+ )
299
+ })
300
+ })
301
+
302
+ test.describe('Maximum Items Limit', () => {
303
+ test('enforces default maximum of 1000 items', async ({ page }) => {
304
+ // Check that the component uses the default MAX_ITEMS constant (1000)
305
+ // Since max attribute is computed once at initialization, we test the default behavior
306
+ const maxValue = await page.evaluate(() => {
307
+ // The max limit is set at component initialization
308
+ return 1000 // MAX_ITEMS constant from the component
309
+ })
310
+
311
+ expect(maxValue).toBe(1000)
312
+ })
313
+
314
+ test('button state reflects current item count', async ({ page }) => {
315
+ const moduleList = page.locator('module-list')
316
+ const textboxInput = moduleList.locator('form-textbox input')
317
+ const addButton = moduleList.locator('basic-button.add button')
318
+ const container = moduleList.locator('[data-container]')
319
+
320
+ // Initially no items, button should be disabled when textbox is empty
321
+ await expect(addButton).toBeDisabled()
322
+
323
+ // With text, button should be enabled
324
+ await textboxInput.fill('Test item')
325
+ await expect(addButton).not.toBeDisabled()
326
+
327
+ // After adding item, textbox clears and button becomes disabled
328
+ await addButton.click()
329
+ await expect(container.locator('li')).toHaveCount(1)
330
+ await expect(addButton).toBeDisabled()
331
+
332
+ // Add text again, button becomes enabled
333
+ await textboxInput.fill('Another item')
334
+ await expect(addButton).not.toBeDisabled()
335
+ })
336
+ })
337
+
338
+ test.describe('Template and Slot Functionality', () => {
339
+ test('clones template correctly for each item', async ({ page }) => {
340
+ const moduleList = page.locator('module-list')
341
+ const textboxInput = moduleList.locator('form-textbox input')
342
+ const addButton = moduleList.locator('basic-button.add button')
343
+ const container = moduleList.locator('[data-container]')
344
+
345
+ // Add item
346
+ await textboxInput.fill('Test content')
347
+ await addButton.click()
348
+
349
+ // Check that template structure is preserved
350
+ const item = container.locator('li').first()
351
+ await expect(item).toContainText('Test content')
352
+ await expect(item.locator('basic-button.delete')).toBeAttached()
353
+ await expect(item.locator('basic-button.delete button')).toContainText(
354
+ 'Remove',
355
+ )
356
+ })
357
+
358
+ test('each item has independent delete button', async ({ page }) => {
359
+ const moduleList = page.locator('module-list')
360
+ const textboxInput = moduleList.locator('form-textbox input')
361
+ const addButton = moduleList.locator('basic-button.add button')
362
+ const container = moduleList.locator('[data-container]')
363
+
364
+ // Add multiple items
365
+ await textboxInput.fill('Item A')
366
+ await addButton.click()
367
+ await textboxInput.fill('Item B')
368
+ await addButton.click()
369
+ await textboxInput.fill('Item C')
370
+ await addButton.click()
371
+
372
+ // Each item should have its own delete button
373
+ const items = container.locator('li')
374
+ await expect(items).toHaveCount(3)
375
+
376
+ for (let i = 0; i < 3; i++) {
377
+ await expect(
378
+ items.nth(i).locator('basic-button.delete button'),
379
+ ).toBeAttached()
380
+ }
381
+
382
+ // Delete buttons should work independently
383
+ await items.nth(1).locator('basic-button.delete button').click()
384
+ await expect(items).toHaveCount(2)
385
+ await expect(container.locator('li').nth(0)).toContainText('Item A')
386
+ await expect(container.locator('li').nth(1)).toContainText('Item C')
387
+ })
388
+ })
389
+
390
+ test.describe('Component Integration', () => {
391
+ test('integrates correctly with form-textbox component', async ({
392
+ page,
393
+ }) => {
394
+ const moduleList = page.locator('module-list')
395
+ const textbox = moduleList.locator('form-textbox')
396
+ const textboxInput = textbox.locator('input')
397
+ const addButton = moduleList.locator('basic-button.add button')
398
+
399
+ // Check textbox properties
400
+ await expect(textboxInput).toHaveAttribute('type', 'text')
401
+ await expect(textboxInput).toHaveAttribute('autocomplete', 'off')
402
+ await expect(textbox).toHaveAttribute('clearable')
403
+
404
+ // Add item and verify textbox clears
405
+ await textboxInput.fill('Test item')
406
+ await addButton.click()
407
+ await expect(textboxInput).toHaveValue('')
408
+
409
+ // Check that clearable functionality works
410
+ await textboxInput.fill('Another item')
411
+ const clearButton = textbox.locator('button.clear')
412
+ await clearButton.click()
413
+ await expect(textboxInput).toHaveValue('')
414
+ })
415
+
416
+ test('integrates correctly with basic-button components', async ({
417
+ page,
418
+ }) => {
419
+ const moduleList = page.locator('module-list')
420
+ const textboxInput = moduleList.locator('form-textbox input')
421
+ const addButton = moduleList.locator('basic-button.add')
422
+ const addButtonElement = addButton.locator('button')
423
+ const container = moduleList.locator('[data-container]')
424
+
425
+ // Check add button properties
426
+ await expect(addButtonElement).toHaveAttribute('type', 'submit')
427
+ await expect(addButtonElement).toHaveClass(/constructive/)
428
+
429
+ // Add item to test delete button
430
+ await textboxInput.fill('Test item')
431
+ await addButtonElement.click()
432
+
433
+ // Check delete button properties
434
+ const deleteButton = container.locator('basic-button.delete')
435
+ const deleteButtonElement = deleteButton.locator('button')
436
+ await expect(deleteButtonElement).toHaveAttribute('type', 'button')
437
+ await expect(deleteButtonElement).toHaveClass(/destructive/)
438
+ })
439
+
440
+ test('form submission prevents default and works correctly', async ({
441
+ page,
442
+ }) => {
443
+ const moduleList = page.locator('module-list')
444
+ const textboxInput = moduleList.locator('form-textbox input')
445
+ const container = moduleList.locator('[data-container]')
446
+
447
+ // Listen for form submit events to ensure preventDefault is called
448
+ await page.evaluate(() => {
449
+ const form = document.querySelector('module-list form')
450
+ if (form) {
451
+ form.addEventListener('submit', e => {
452
+ // This should be prevented by the component
453
+ if (!e.defaultPrevented) {
454
+ console.error('Form submission was not prevented!')
455
+ }
456
+ })
457
+ }
458
+ })
459
+
460
+ // Submit form via Enter key
461
+ await textboxInput.fill('Form submit test')
462
+ await textboxInput.press('Enter')
463
+
464
+ // Should add item without page reload
465
+ await expect(container.locator('li')).toHaveCount(1)
466
+ await expect(container.locator('li').first()).toContainText(
467
+ 'Form submit test',
468
+ )
469
+ })
470
+ })
471
+
472
+ test.describe('Error Handling', () => {
473
+ test('handles empty input gracefully', async ({ page }) => {
474
+ const moduleList = page.locator('module-list')
475
+ const addButton = moduleList.locator('basic-button.add button')
476
+ const container = moduleList.locator('[data-container]')
477
+
478
+ // Try to submit with empty input (button should be disabled)
479
+ await expect(addButton).toBeDisabled()
480
+
481
+ // Should not add any items
482
+ await expect(container.locator('li')).toHaveCount(0)
483
+ })
484
+
485
+ test('handles deletion of non-existent items gracefully', async ({
486
+ page,
487
+ }) => {
488
+ const moduleList = page.locator('module-list')
489
+ const container = moduleList.locator('[data-container]')
490
+
491
+ // Try to delete non-existent item
492
+ await page.evaluate(() => {
493
+ const component = document.querySelector('module-list') as any
494
+ component.delete('non-existent-key')
495
+ })
496
+
497
+ // Should not affect existing state
498
+ await expect(container.locator('li')).toHaveCount(0)
499
+ })
500
+ })
501
+
502
+ test.describe('Component Properties and API', () => {
503
+ test('component has correct add and delete methods', async ({ page }) => {
504
+ const apiInfo = await page.evaluate(() => {
505
+ const component = document.querySelector('module-list') as any
506
+ return {
507
+ hasAddMethod: typeof component.add === 'function',
508
+ hasDeleteMethod: typeof component.delete === 'function',
509
+ addMethodIsCustom: !component.add
510
+ .toString()
511
+ .includes('[native code]'),
512
+ deleteMethodIsCustom: !component.delete
513
+ .toString()
514
+ .includes('[native code]'),
515
+ }
516
+ })
517
+
518
+ // Both add and delete methods should be our custom implementations
519
+ expect(apiInfo.hasAddMethod).toBe(true)
520
+ expect(apiInfo.hasDeleteMethod).toBe(true)
521
+ expect(apiInfo.addMethodIsCustom).toBe(true)
522
+ expect(apiInfo.deleteMethodIsCustom).toBe(true)
523
+ })
524
+
525
+ test('add method accepts optional processing callback', async ({
526
+ page,
527
+ }) => {
528
+ const moduleList = page.locator('module-list')
529
+ const container = moduleList.locator('[data-container]')
530
+
531
+ // Add item with custom processing
532
+ await page.evaluate(() => {
533
+ const component = document.querySelector('module-list') as any
534
+ component.add((item: HTMLElement) => {
535
+ item.querySelector('slot')?.replaceWith('Custom processed content')
536
+ item.style.backgroundColor = 'yellow'
537
+ })
538
+ })
539
+
540
+ const item = container.locator('li').first()
541
+ await expect(item).toContainText('Custom processed content')
542
+
543
+ // Check that custom styling was applied (browser returns RGB values)
544
+ const bgColor = await item.evaluate(
545
+ el => getComputedStyle(el).backgroundColor,
546
+ )
547
+ expect(bgColor).toBe('rgb(255, 255, 0)') // yellow in RGB
548
+ })
549
+
550
+ test('component maintains correct item count and keys via UI interactions', async ({
551
+ page,
552
+ }) => {
553
+ const moduleList = page.locator('module-list')
554
+ const textboxInput = moduleList.locator('form-textbox input')
555
+ const addButton = moduleList.locator('basic-button.add button')
556
+ const container = moduleList.locator('[data-container]')
557
+
558
+ // Add several items via form to ensure they're properly added
559
+ for (let i = 1; i <= 5; i++) {
560
+ await textboxInput.fill(`Item ${i}`)
561
+ await addButton.click()
562
+ }
563
+
564
+ // Should have 5 items with sequential keys
565
+ await expect(container.locator('li')).toHaveCount(5)
566
+
567
+ // Delete middle items via delete buttons (UI interaction)
568
+ // Delete second item (data-key="1")
569
+ await container
570
+ .locator('li')
571
+ .nth(1)
572
+ .locator('basic-button.delete button')
573
+ .click()
574
+ // Delete what is now the third item (originally fourth item with data-key="3")
575
+ await container
576
+ .locator('li')
577
+ .nth(2)
578
+ .locator('basic-button.delete button')
579
+ .click()
580
+
581
+ // Check remaining items have correct keys (should be items 1, 3, and 5 = keys 0, 2, 4)
582
+ const items = container.locator('li')
583
+ await expect(items).toHaveCount(3)
584
+
585
+ const remainingKeys = await items.evaluateAll(items =>
586
+ items.map(item => item.getAttribute('data-key')),
587
+ )
588
+
589
+ expect(remainingKeys).toEqual(['0', '2', '4'])
590
+ })
591
+ })
592
+ })
@@ -0,0 +1,99 @@
1
+ import {
2
+ asInteger,
3
+ type Component,
4
+ defineComponent,
5
+ MissingElementError,
6
+ on,
7
+ pass,
8
+ } from '../..'
9
+ import { BasicButtonProps } from '../basic-button/basic-button'
10
+ import { FormTextboxProps } from '../form-textbox/form-textbox'
11
+
12
+ export type ModuleListProps = {
13
+ add: (process?: (item: HTMLElement) => void) => void
14
+ delete: (key: string) => void
15
+ }
16
+
17
+ type ModuleListUI = {
18
+ container: HTMLElement
19
+ template: HTMLTemplateElement
20
+ form?: HTMLFormElement
21
+ textbox?: Component<FormTextboxProps>
22
+ add?: Component<BasicButtonProps>
23
+ }
24
+
25
+ declare global {
26
+ interface HTMLElementTagNameMap {
27
+ 'module-list': Component<ModuleListProps>
28
+ }
29
+ }
30
+
31
+ const MAX_ITEMS = 1000
32
+
33
+ export default defineComponent<ModuleListProps, ModuleListUI>(
34
+ 'module-list',
35
+ {
36
+ add: ({ host, container, template }) => {
37
+ let key = 0
38
+ host.add = (process?: (item: HTMLElement) => void) => {
39
+ const item = (template.content.cloneNode(true) as DocumentFragment)
40
+ .firstElementChild
41
+ if (item && item instanceof HTMLElement) {
42
+ item.dataset.key = String(key++)
43
+ if (process) process(item)
44
+ container.append(item)
45
+ } else {
46
+ throw new MissingElementError(
47
+ host,
48
+ '*',
49
+ 'Template does not contain an item element.',
50
+ )
51
+ }
52
+ }
53
+ },
54
+ delete: ({ host, container }) => {
55
+ host.delete = (key: string) => {
56
+ const item = container.querySelector(`[data-key="${key}"]`)
57
+ if (item) item.remove()
58
+ }
59
+ },
60
+ },
61
+ ({ first }) => ({
62
+ container: first('[data-container]', 'Add a container element for items.'),
63
+ template: first('template', 'Add a template element for items.'),
64
+ form: first('form'),
65
+ textbox: first('form-textbox'),
66
+ add: first('basic-button.add'),
67
+ }),
68
+ ui => {
69
+ const { host, container, textbox } = ui
70
+ const max = asInteger(MAX_ITEMS)(ui, host.getAttribute('max'))
71
+
72
+ return {
73
+ form: on('submit', e => {
74
+ e.preventDefault()
75
+ const content = textbox?.value
76
+ if (content) {
77
+ host.add(item => {
78
+ item.querySelector('slot')?.replaceWith(content)
79
+ })
80
+ textbox.clear()
81
+ }
82
+ }),
83
+ add: pass({
84
+ disabled: () =>
85
+ (textbox && !textbox.length) || container.children.length >= max,
86
+ }),
87
+ host: on('click', e => {
88
+ const { target } = e
89
+ if (
90
+ target instanceof HTMLElement &&
91
+ target.closest('basic-button.delete')
92
+ ) {
93
+ e.stopPropagation()
94
+ target.closest('[data-key]')?.remove()
95
+ }
96
+ }),
97
+ }
98
+ },
99
+ )