@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,523 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ /**
4
+ * Test Suite: module-carousel Component
5
+ *
6
+ * Comprehensive tests for the Le Truc module-carousel component, which provides
7
+ * an accessible carousel/slideshow interface with multiple navigation methods:
8
+ * - Button navigation (prev/next)
9
+ * - Dot navigation (direct slide selection)
10
+ * - Keyboard navigation (arrow keys, Home/End)
11
+ * - Scroll-based navigation (intersection observer)
12
+ *
13
+ * Key Features Tested:
14
+ * - ✅ Initial state rendering and ARIA compliance
15
+ * - ✅ Navigation wrapping (first ↔ last slide)
16
+ * - ✅ Reactive index property (writable, not readonly sensor)
17
+ * - ✅ Smooth scroll animations and intersection observer updates
18
+ * - ✅ ARIA attributes synchronization (aria-current, aria-selected, tabindex)
19
+ * - ✅ Keyboard accessibility (roving tab focus pattern)
20
+ * - ✅ State consistency across different navigation methods
21
+ * - ✅ Edge case handling (sequential navigation, timing issues)
22
+ *
23
+ * Architecture Notes:
24
+ * - Uses `asInteger` parser with DOM reader function (not readonly sensor)
25
+ * - Smooth scroll animations require timing considerations in tests
26
+ * - IntersectionObserver updates index based on scroll position
27
+ * - Supports proper ARIA carousel patterns for accessibility
28
+ */
29
+
30
+ test.describe('module-carousel component', () => {
31
+ test.beforeEach(async ({ page }) => {
32
+ page.on('console', msg => {
33
+ console.log(`[browser] ${msg.type()}: ${msg.text()}`)
34
+ })
35
+
36
+ await page.goto('http://localhost:3000/test/module-carousel.html')
37
+ await page.waitForSelector('module-carousel')
38
+ })
39
+
40
+ test.describe('Initial State', () => {
41
+ test('renders carousel with correct initial state', async ({ page }) => {
42
+ const carousel = page.locator('module-carousel')
43
+ const slides = carousel.locator('[role="tabpanel"]')
44
+ const dots = carousel.locator('[role="tab"]')
45
+ const prevButton = carousel.locator('button.prev')
46
+ const nextButton = carousel.locator('button.next')
47
+
48
+ // Should have correct number of slides and dots
49
+ await expect(slides).toHaveCount(3)
50
+ await expect(dots).toHaveCount(3)
51
+
52
+ // Should have navigation buttons
53
+ await expect(prevButton).toBeVisible()
54
+ await expect(nextButton).toBeVisible()
55
+
56
+ // First slide should be current
57
+ const firstSlide = slides.first()
58
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
59
+
60
+ // First dot should be selected
61
+ const firstDot = dots.first()
62
+ await expect(firstDot).toHaveAttribute('aria-selected', 'true')
63
+ await expect(firstDot).toHaveAttribute('tabindex', '0')
64
+
65
+ // Other dots should not be selected
66
+ const secondDot = dots.nth(1)
67
+ const thirdDot = dots.nth(2)
68
+ await expect(secondDot).toHaveAttribute('aria-selected', 'false')
69
+ await expect(thirdDot).toHaveAttribute('aria-selected', 'false')
70
+ await expect(secondDot).toHaveAttribute('tabindex', '-1')
71
+ await expect(thirdDot).toHaveAttribute('tabindex', '-1')
72
+ })
73
+
74
+ test('reads initial index from aria-current attribute', async ({
75
+ page,
76
+ }) => {
77
+ // Get the initial index from the component
78
+ const initialIndex = await page.evaluate(() => {
79
+ const carousel = document.querySelector('module-carousel')
80
+ return carousel?.index
81
+ })
82
+
83
+ // Should start at index 0 (first slide has aria-current="true")
84
+ expect(initialIndex).toBe(0)
85
+ })
86
+ })
87
+
88
+ test.describe('Button Navigation', () => {
89
+ test('navigates forward with next button', async ({ page }) => {
90
+ const carousel = page.locator('module-carousel')
91
+ const nextButton = carousel.locator('button.next')
92
+ const slides = carousel.locator('[role="tabpanel"]')
93
+ const dots = carousel.locator('[role="tab"]')
94
+
95
+ // Click next button
96
+ await nextButton.click()
97
+
98
+ // Wait for navigation
99
+
100
+ // Second slide should be current
101
+ const secondSlide = slides.nth(1)
102
+ await expect(secondSlide).toHaveAttribute('aria-current', 'true')
103
+
104
+ // First slide should not be current
105
+ const firstSlide = slides.first()
106
+ await expect(firstSlide).toHaveAttribute('aria-current', 'false')
107
+
108
+ // Second dot should be selected
109
+ const secondDot = dots.nth(1)
110
+ await expect(secondDot).toHaveAttribute('aria-selected', 'true')
111
+ await expect(secondDot).toHaveAttribute('tabindex', '0')
112
+
113
+ // First dot should not be selected
114
+ const firstDot = dots.first()
115
+ await expect(firstDot).toHaveAttribute('aria-selected', 'false')
116
+ await expect(firstDot).toHaveAttribute('tabindex', '-1')
117
+ })
118
+
119
+ test('navigates backward with prev button', async ({ page }) => {
120
+ const carousel = page.locator('module-carousel')
121
+ const nextButton = carousel.locator('button.next')
122
+ const prevButton = carousel.locator('button.prev')
123
+ const slides = carousel.locator('[role="tabpanel"]')
124
+
125
+ // Go to second slide first
126
+ await nextButton.click()
127
+
128
+ // Click prev button
129
+ await prevButton.click()
130
+
131
+ // First slide should be current again
132
+ const firstSlide = slides.first()
133
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
134
+
135
+ // Component index should be 0
136
+ const currentIndex = await page.evaluate(() => {
137
+ const carousel = document.querySelector('module-carousel')
138
+ return carousel?.index
139
+ })
140
+ expect(currentIndex).toBe(0)
141
+ })
142
+
143
+ test('wraps around from last to first slide', async ({ page }) => {
144
+ const carousel = page.locator('module-carousel')
145
+ const nextButton = carousel.locator('button.next')
146
+ const slides = carousel.locator('[role="tabpanel"]')
147
+
148
+ // Navigate to last slide (index 2)
149
+ await nextButton.click()
150
+ await nextButton.click()
151
+
152
+ // Third slide should be current
153
+ const thirdSlide = slides.nth(2)
154
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
155
+
156
+ // Click next again to wrap around
157
+ await nextButton.click()
158
+
159
+ // Should wrap to first slide
160
+ const firstSlide = slides.first()
161
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
162
+
163
+ const currentIndex = await page.evaluate(() => {
164
+ const carousel = document.querySelector('module-carousel')
165
+ return carousel?.index
166
+ })
167
+ expect(currentIndex).toBe(0)
168
+ })
169
+
170
+ test('wraps around from first to last slide', async ({ page }) => {
171
+ const carousel = page.locator('module-carousel')
172
+ const prevButton = carousel.locator('button.prev')
173
+ const slides = carousel.locator('[role="tabpanel"]')
174
+
175
+ // Click prev from first slide to wrap around
176
+ await prevButton.click()
177
+
178
+ // Should wrap to last slide
179
+ const thirdSlide = slides.nth(2)
180
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
181
+
182
+ const currentIndex = await page.evaluate(() => {
183
+ const carousel = document.querySelector('module-carousel')
184
+ return carousel?.index
185
+ })
186
+ expect(currentIndex).toBe(2)
187
+ })
188
+ })
189
+
190
+ test.describe('Dot Navigation', () => {
191
+ test('navigates to specific slide when dot is clicked', async ({
192
+ page,
193
+ }) => {
194
+ const carousel = page.locator('module-carousel')
195
+ const dots = carousel.locator('[role="tab"]')
196
+ const slides = carousel.locator('[role="tabpanel"]')
197
+
198
+ // Click third dot (index 2)
199
+ const thirdDot = dots.nth(2)
200
+ await thirdDot.click()
201
+
202
+ // Third slide should be current
203
+ const thirdSlide = slides.nth(2)
204
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
205
+
206
+ // Third dot should be selected
207
+ await expect(thirdDot).toHaveAttribute('aria-selected', 'true')
208
+ await expect(thirdDot).toHaveAttribute('tabindex', '0')
209
+
210
+ // Other dots should not be selected
211
+ const firstDot = dots.first()
212
+ const secondDot = dots.nth(1)
213
+ await expect(firstDot).toHaveAttribute('aria-selected', 'false')
214
+ await expect(secondDot).toHaveAttribute('aria-selected', 'false')
215
+ await expect(firstDot).toHaveAttribute('tabindex', '-1')
216
+ await expect(secondDot).toHaveAttribute('tabindex', '-1')
217
+ })
218
+
219
+ test('updates component index when dot is clicked', async ({ page }) => {
220
+ const carousel = page.locator('module-carousel')
221
+ const dots = carousel.locator('[role="tab"]')
222
+
223
+ // Click second dot
224
+ const secondDot = dots.nth(1)
225
+ await secondDot.click()
226
+
227
+ const currentIndex = await page.evaluate(() => {
228
+ const carousel = document.querySelector('module-carousel')
229
+ return carousel?.index
230
+ })
231
+ expect(currentIndex).toBe(1)
232
+ })
233
+ })
234
+
235
+ test.describe('Keyboard Navigation', () => {
236
+ test('navigates with arrow keys', async ({ page }) => {
237
+ const carousel = page.locator('module-carousel')
238
+ const slides = carousel.locator('[role="tabpanel"]')
239
+
240
+ // Focus on navigation buttons area and use arrow keys
241
+ const nextButton = carousel.locator('button.next')
242
+ await nextButton.focus()
243
+
244
+ // Press right arrow
245
+ await page.keyboard.press('ArrowRight')
246
+
247
+ // Second slide should be current
248
+ const secondSlide = slides.nth(1)
249
+ await expect(secondSlide).toHaveAttribute('aria-current', 'true')
250
+
251
+ // Press left arrow
252
+ await page.keyboard.press('ArrowLeft')
253
+
254
+ // First slide should be current again
255
+ const firstSlide = slides.first()
256
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
257
+ })
258
+
259
+ test('navigates with Home and End keys', async ({ page }) => {
260
+ const carousel = page.locator('module-carousel')
261
+ const nextButton = carousel.locator('button.next')
262
+ const slides = carousel.locator('[role="tabpanel"]')
263
+
264
+ // Go to middle slide first
265
+ await nextButton.click()
266
+
267
+ // Focus and press End key
268
+ await nextButton.focus()
269
+ await page.keyboard.press('End')
270
+
271
+ // Last slide should be current
272
+ const thirdSlide = slides.nth(2)
273
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
274
+
275
+ // Press Home key
276
+ await page.keyboard.press('Home')
277
+
278
+ // First slide should be current
279
+ const firstSlide = slides.first()
280
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
281
+ })
282
+
283
+ test('wraps around with arrow key navigation', async ({ page }) => {
284
+ const carousel = page.locator('module-carousel')
285
+ const nextButton = carousel.locator('button.next')
286
+ const slides = carousel.locator('[role="tabpanel"]')
287
+
288
+ // Navigate to last slide
289
+ await nextButton.click()
290
+ await nextButton.click()
291
+
292
+ // Focus and press right arrow to wrap around
293
+ await nextButton.focus()
294
+ await page.keyboard.press('ArrowRight')
295
+
296
+ // Should wrap to first slide
297
+ const firstSlide = slides.first()
298
+ await expect(firstSlide).toHaveAttribute('aria-current', 'true')
299
+
300
+ // Press left arrow to wrap around backwards
301
+ await page.keyboard.press('ArrowLeft')
302
+
303
+ // Should wrap to last slide
304
+ const thirdSlide = slides.nth(2)
305
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
306
+ })
307
+ })
308
+
309
+ test.describe('Scroll-based Navigation', () => {
310
+ test('updates index when slide is scrolled into view', async ({ page }) => {
311
+ const carousel = page.locator('module-carousel')
312
+ const slides = carousel.locator('[role="tabpanel"]')
313
+
314
+ // Use button navigation to trigger scroll behavior
315
+ const nextButton = carousel.locator('button.next')
316
+ await nextButton.click()
317
+
318
+ // Component index should be updated
319
+ const currentIndex = await page.evaluate(() => {
320
+ const carousel = document.querySelector('module-carousel')
321
+ return carousel?.index
322
+ })
323
+ expect(currentIndex).toBe(1)
324
+
325
+ // Second slide should have aria-current="true"
326
+ const secondSlide = slides.nth(1)
327
+ await expect(secondSlide).toHaveAttribute('aria-current', 'true')
328
+ })
329
+ })
330
+
331
+ test.describe('Component Properties', () => {
332
+ test('index property is writable and reactive', async ({ page }) => {
333
+ const carousel = page.locator('module-carousel')
334
+ const slides = carousel.locator('[role="tabpanel"]')
335
+
336
+ // Get initial index
337
+ const initialIndex = await page.evaluate(() => {
338
+ const carousel = document.querySelector('module-carousel')
339
+ return carousel?.index
340
+ })
341
+ expect(initialIndex).toBe(0)
342
+
343
+ // Set index directly (should work - writable property)
344
+ await page.evaluate(() => {
345
+ const carousel = document.querySelector('module-carousel')
346
+ carousel!.index = 2
347
+ })
348
+
349
+ // Wait for reactive updates
350
+
351
+ // Index should be updated
352
+ const updatedIndex = await page.evaluate(() => {
353
+ const carousel = document.querySelector('module-carousel')
354
+ return carousel?.index
355
+ })
356
+ expect(updatedIndex).toBe(2)
357
+
358
+ // Third slide should be current
359
+ const thirdSlide = slides.nth(2)
360
+ await expect(thirdSlide).toHaveAttribute('aria-current', 'true')
361
+ })
362
+
363
+ test('index property reflects DOM state', async ({ page }) => {
364
+ const carousel = page.locator('module-carousel')
365
+ const dots = carousel.locator('[role="tab"]')
366
+
367
+ // Click different dots and verify index updates
368
+ for (let i = 0; i < 3; i++) {
369
+ await dots.nth(i).click()
370
+
371
+ const currentIndex = await page.evaluate(() => {
372
+ const carousel = document.querySelector('module-carousel')
373
+ return carousel?.index
374
+ })
375
+ expect(currentIndex).toBe(i)
376
+ }
377
+ })
378
+ })
379
+
380
+ test.describe('ARIA and Accessibility', () => {
381
+ test('maintains proper ARIA attributes', async ({ page }) => {
382
+ const carousel = page.locator('module-carousel')
383
+ const slides = carousel.locator('[role="tabpanel"]')
384
+ const dots = carousel.locator('[role="tab"]')
385
+ const nextButton = carousel.locator('button.next')
386
+
387
+ // Check initial ARIA states
388
+ await expect(slides.first()).toHaveAttribute('aria-current', 'true')
389
+ await expect(slides.nth(1)).toHaveAttribute('aria-current', 'false')
390
+ await expect(slides.nth(2)).toHaveAttribute('aria-current', 'false')
391
+
392
+ await expect(dots.first()).toHaveAttribute('aria-selected', 'true')
393
+ await expect(dots.nth(1)).toHaveAttribute('aria-selected', 'false')
394
+ await expect(dots.nth(2)).toHaveAttribute('aria-selected', 'false')
395
+
396
+ // Navigate and check ARIA states update
397
+ await nextButton.click()
398
+
399
+ await expect(slides.first()).toHaveAttribute('aria-current', 'false')
400
+ await expect(slides.nth(1)).toHaveAttribute('aria-current', 'true')
401
+ await expect(slides.nth(2)).toHaveAttribute('aria-current', 'false')
402
+
403
+ await expect(dots.first()).toHaveAttribute('aria-selected', 'false')
404
+ await expect(dots.nth(1)).toHaveAttribute('aria-selected', 'true')
405
+ await expect(dots.nth(2)).toHaveAttribute('aria-selected', 'false')
406
+ })
407
+
408
+ test('maintains proper tabindex for roving tab focus', async ({ page }) => {
409
+ const carousel = page.locator('module-carousel')
410
+ const dots = carousel.locator('[role="tab"]')
411
+ const nextButton = carousel.locator('button.next')
412
+
413
+ // Check initial tabindex values
414
+ await expect(dots.first()).toHaveAttribute('tabindex', '0')
415
+ await expect(dots.nth(1)).toHaveAttribute('tabindex', '-1')
416
+ await expect(dots.nth(2)).toHaveAttribute('tabindex', '-1')
417
+
418
+ // Navigate and check tabindex updates
419
+ await nextButton.click()
420
+
421
+ await expect(dots.first()).toHaveAttribute('tabindex', '-1')
422
+ await expect(dots.nth(1)).toHaveAttribute('tabindex', '0')
423
+ await expect(dots.nth(2)).toHaveAttribute('tabindex', '-1')
424
+ })
425
+
426
+ test('has proper ARIA labels and controls', async ({ page }) => {
427
+ const carousel = page.locator('module-carousel')
428
+ const dots = carousel.locator('[role="tab"]')
429
+ const prevButton = carousel.locator('button.prev')
430
+ const nextButton = carousel.locator('button.next')
431
+ const nav = carousel.locator('nav')
432
+
433
+ // Check navigation has aria-label
434
+ await expect(nav).toHaveAttribute('aria-label', 'Carousel Navigation')
435
+
436
+ // Check buttons have labels
437
+ await expect(prevButton).toHaveAttribute('aria-label', 'Previous')
438
+ await expect(nextButton).toHaveAttribute('aria-label', 'Next')
439
+
440
+ // Check dots have proper labels and controls
441
+ for (let i = 0; i < 3; i++) {
442
+ const dot = dots.nth(i)
443
+ await expect(dot).toHaveAttribute('aria-label', `Slide ${i + 1}`)
444
+ await expect(dot).toHaveAttribute('aria-controls', `slide${i + 1}`)
445
+ }
446
+ })
447
+ })
448
+
449
+ test.describe('Edge Cases', () => {
450
+ test('handles empty or single slide gracefully', async ({ page }) => {
451
+ // This test assumes the fixture always has 3 slides
452
+ // In a real implementation, we might test with different fixtures
453
+ const slides = page.locator('module-carousel [role="tabpanel"]')
454
+ const slideCount = await slides.count()
455
+
456
+ // With our current fixture, should have 3 slides
457
+ expect(slideCount).toBe(3)
458
+ })
459
+
460
+ test('handles sequential navigation correctly', async ({ page }) => {
461
+ const carousel = page.locator('module-carousel')
462
+ const nextButton = carousel.locator('button.next')
463
+
464
+ // Track index through sequential navigation
465
+ let expectedIndex = 0
466
+
467
+ // Navigate through all slides sequentially
468
+ for (let i = 0; i < 5; i++) {
469
+ await nextButton.click()
470
+
471
+ expectedIndex = (expectedIndex + 1) % 3 // Wrap around at 3
472
+
473
+ const currentIndex = await page.evaluate(() => {
474
+ const carousel = document.querySelector('module-carousel')
475
+ return carousel?.index
476
+ })
477
+ expect(currentIndex).toBe(expectedIndex)
478
+ }
479
+ })
480
+
481
+ test('maintains state consistency across different navigation methods', async ({
482
+ page,
483
+ }) => {
484
+ const carousel = page.locator('module-carousel')
485
+ const nextButton = carousel.locator('button.next')
486
+ const dots = carousel.locator('[role="tab"]')
487
+ const slides = carousel.locator('[role="tabpanel"]')
488
+
489
+ // Use button navigation
490
+ await nextButton.click()
491
+
492
+ let currentIndex = await page.evaluate(() => {
493
+ const carousel = document.querySelector('module-carousel')
494
+ return carousel?.index
495
+ })
496
+ expect(currentIndex).toBe(1)
497
+
498
+ // Use dot navigation
499
+ await dots.nth(2).click()
500
+
501
+ currentIndex = await page.evaluate(() => {
502
+ const carousel = document.querySelector('module-carousel')
503
+ return carousel?.index
504
+ })
505
+ expect(currentIndex).toBe(2)
506
+
507
+ // Use keyboard navigation
508
+ await nextButton.focus()
509
+ await page.keyboard.press('Home')
510
+
511
+ currentIndex = await page.evaluate(() => {
512
+ const carousel = document.querySelector('module-carousel')
513
+ return carousel?.index
514
+ })
515
+ expect(currentIndex).toBe(0)
516
+
517
+ // Verify all UI elements are in sync
518
+ await expect(slides.first()).toHaveAttribute('aria-current', 'true')
519
+ await expect(dots.first()).toHaveAttribute('aria-selected', 'true')
520
+ await expect(dots.first()).toHaveAttribute('tabindex', '0')
521
+ })
522
+ })
523
+ })
@@ -0,0 +1,131 @@
1
+ import {
2
+ asInteger,
3
+ type Collection,
4
+ type Component,
5
+ defineComponent,
6
+ on,
7
+ setProperty,
8
+ } from '../..'
9
+
10
+ export type ModuleCarouselProps = {
11
+ index: number
12
+ }
13
+
14
+ type ModuleCarouselUI = {
15
+ dots: Collection<HTMLElement>
16
+ slides: Collection<HTMLElement>
17
+ buttons: Collection<HTMLElement>
18
+ }
19
+
20
+ declare global {
21
+ interface HTMLElementTagNameMap {
22
+ 'module-carousel': Component<ModuleCarouselProps>
23
+ }
24
+ }
25
+
26
+ const wrapAround = (index: number, total: number) => (index + total) % total
27
+
28
+ export default defineComponent<ModuleCarouselProps, ModuleCarouselUI>(
29
+ 'module-carousel',
30
+ {
31
+ index: asInteger(ui =>
32
+ Math.max(
33
+ ui.slides.get().findIndex(slide => slide.ariaCurrent === 'true'),
34
+ 0,
35
+ ),
36
+ ),
37
+ },
38
+ ({ all }) => ({
39
+ dots: all('[role="tab"]'),
40
+ slides: all('[role="tabpanel"]'),
41
+ buttons: all('nav button'),
42
+ }),
43
+ ({ host, slides }) => {
44
+ const isCurrentDot = (target: HTMLElement) =>
45
+ target.dataset.index === String(host.index)
46
+ const scrollToCurrentSlide = () => {
47
+ slides[host.index].scrollIntoView({
48
+ behavior: 'smooth',
49
+ block: 'nearest',
50
+ })
51
+ }
52
+
53
+ return {
54
+ // Register IntersectionObserver to update index based on scroll position
55
+ component: [
56
+ () => {
57
+ const observer = new IntersectionObserver(
58
+ entries => {
59
+ for (const entry of entries) {
60
+ if (entry.isIntersecting) {
61
+ host.index = slides
62
+ .get()
63
+ .findIndex(slide => slide === entry.target)
64
+ break
65
+ }
66
+ }
67
+ },
68
+ {
69
+ root: host,
70
+ threshold: 0.5,
71
+ },
72
+ )
73
+ slides.get().forEach(slide => {
74
+ observer.observe(slide)
75
+ })
76
+ return () => {
77
+ observer.disconnect()
78
+ }
79
+ },
80
+ ],
81
+
82
+ // Handle navigation button click and keyup events
83
+ buttons: [
84
+ on('click', ({ target }) => {
85
+ if (!(target instanceof HTMLElement)) return
86
+ const total = slides.length
87
+ const nextIndex = target.classList.contains('prev')
88
+ ? host.index - 1
89
+ : target.classList.contains('next')
90
+ ? host.index + 1
91
+ : parseInt(target.dataset.index || '0')
92
+ host.index = Number.isInteger(nextIndex)
93
+ ? wrapAround(nextIndex, total)
94
+ : 0
95
+ scrollToCurrentSlide()
96
+ }),
97
+ on('keyup', e => {
98
+ const { key } = e
99
+ if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(key)) {
100
+ e.preventDefault()
101
+ e.stopPropagation()
102
+ const total = slides.length
103
+ const nextIndex =
104
+ key === 'Home'
105
+ ? 0
106
+ : key === 'End'
107
+ ? total - 1
108
+ : wrapAround(
109
+ host.index + (key === 'ArrowLeft' ? -1 : 1),
110
+ total,
111
+ )
112
+ slides[nextIndex].focus()
113
+ host.index = nextIndex
114
+ scrollToCurrentSlide()
115
+ }
116
+ }),
117
+ ],
118
+
119
+ // Set the active slide in the navigation
120
+ dots: [
121
+ setProperty('ariaSelected', target => String(isCurrentDot(target))),
122
+ setProperty('tabIndex', target => (isCurrentDot(target) ? 0 : -1)),
123
+ ],
124
+
125
+ // Set the active slide in the slides
126
+ slides: setProperty('ariaCurrent', target =>
127
+ String(target.id === slides[host.index].id),
128
+ ),
129
+ }
130
+ },
131
+ )
@@ -0,0 +1,22 @@
1
+ module-catalog {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-l);
5
+
6
+ > header,
7
+ p {
8
+ margin: 0;
9
+ }
10
+
11
+ & ul {
12
+ padding: 0;
13
+ margin: 0;
14
+ }
15
+
16
+ & header,
17
+ li {
18
+ display: flex;
19
+ gap: var(--space-m);
20
+ justify-content: space-between;
21
+ }
22
+ }