@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,688 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ test.describe('form-spinbutton component', () => {
4
+ test.beforeEach(async ({ page }) => {
5
+ page.on('console', msg => {
6
+ console.log(`[browser] ${msg.type()}: ${msg.text()}`)
7
+ })
8
+
9
+ await page.goto('http://localhost:3000/test/form-spinbutton.html')
10
+ await page.waitForSelector('form-spinbutton')
11
+ })
12
+
13
+ test('renders initial state correctly', async ({ page }) => {
14
+ const spinbutton = page.locator('form-spinbutton').first()
15
+ const incrementButton = spinbutton.locator('button.increment')
16
+ const decrementButton = spinbutton.locator('button.decrement')
17
+ const input = spinbutton.locator('input.value')
18
+ const zeroElement = spinbutton.locator('.zero')
19
+ const otherElement = spinbutton.locator('.other')
20
+
21
+ // Initial value should be 0
22
+ await expect(input).toHaveValue('0')
23
+ await expect(input).toBeHidden()
24
+
25
+ // Decrement button should be hidden when value is 0
26
+ await expect(decrementButton).toBeHidden()
27
+
28
+ // Increment button should be enabled and visible
29
+ await expect(incrementButton).toBeVisible()
30
+ await expect(incrementButton).not.toHaveAttribute('disabled')
31
+
32
+ // Zero element should be visible, other element hidden
33
+ await expect(zeroElement).toBeVisible()
34
+ await expect(zeroElement).toHaveText('Add to Cart')
35
+ await expect(otherElement).toBeHidden()
36
+ })
37
+
38
+ test('increments value when clicking increment button', async ({ page }) => {
39
+ const spinbutton = page.locator('form-spinbutton').first()
40
+ const incrementButton = spinbutton.locator('button.increment')
41
+ const input = spinbutton.locator('input.value')
42
+ const decrementButton = spinbutton.locator('button.decrement')
43
+ const zeroElement = spinbutton.locator('.zero')
44
+ const otherElement = spinbutton.locator('.other')
45
+
46
+ // Click increment button
47
+ await incrementButton.click()
48
+
49
+ // Value should be 1 and visible
50
+ await expect(input).toHaveValue('1')
51
+ await expect(input).toBeVisible()
52
+
53
+ // Decrement button should now be visible
54
+ await expect(decrementButton).toBeVisible()
55
+
56
+ // Zero element should be hidden, other element visible
57
+ await expect(zeroElement).toBeHidden()
58
+ await expect(otherElement).toBeVisible()
59
+
60
+ // Click increment again
61
+ await incrementButton.click()
62
+ await expect(input).toHaveValue('2')
63
+ })
64
+
65
+ test('decrements value when clicking decrement button', async ({ page }) => {
66
+ const spinbutton = page.locator('form-spinbutton').first()
67
+ const incrementButton = spinbutton.locator('button.increment')
68
+ const decrementButton = spinbutton.locator('button.decrement')
69
+ const input = spinbutton.locator('input.value')
70
+ const zeroElement = spinbutton.locator('.zero')
71
+ const otherElement = spinbutton.locator('.other')
72
+
73
+ // First increment to 2
74
+ await incrementButton.click()
75
+ await incrementButton.click()
76
+ await expect(input).toHaveValue('2')
77
+
78
+ // Then decrement
79
+ await decrementButton.click()
80
+ await expect(input).toHaveValue('1')
81
+ await expect(decrementButton).toBeVisible()
82
+ await expect(otherElement).toBeVisible()
83
+
84
+ // Decrement to 0
85
+ await decrementButton.click()
86
+ await expect(input).toHaveValue('0')
87
+ await expect(input).toBeHidden()
88
+ await expect(decrementButton).toBeHidden()
89
+ await expect(zeroElement).toBeVisible()
90
+ await expect(otherElement).toBeHidden()
91
+ })
92
+
93
+ test('respects max value constraint', async ({ page }) => {
94
+ const spinbutton = page.locator('form-spinbutton').first()
95
+ const incrementButton = spinbutton.locator('button.increment')
96
+ const input = spinbutton.locator('input.value')
97
+
98
+ // Click increment 10 times to reach max (10)
99
+ for (let i = 0; i < 10; i++) {
100
+ await incrementButton.click()
101
+ }
102
+
103
+ await expect(input).toHaveValue('10')
104
+ await expect(incrementButton).toHaveAttribute('disabled')
105
+
106
+ // Button should be disabled at max, value should stay at 10
107
+ await expect(input).toHaveValue('10')
108
+ await expect(incrementButton).toHaveAttribute('disabled')
109
+ })
110
+
111
+ test('handles keyboard interactions on buttons', async ({ page }) => {
112
+ const spinbutton = page.locator('form-spinbutton').first()
113
+ const incrementButton = spinbutton.locator('button.increment')
114
+ const input = spinbutton.locator('input.value')
115
+
116
+ // Focus a button (keyboard events are handled on controls collection)
117
+ await incrementButton.focus()
118
+
119
+ // Test ArrowUp
120
+ await page.keyboard.press('ArrowUp')
121
+ await expect(input).toHaveValue('1')
122
+ await expect(input).toBeVisible()
123
+
124
+ // Test ArrowUp again
125
+ await page.keyboard.press('ArrowUp')
126
+ await expect(input).toHaveValue('2')
127
+
128
+ // Test ArrowDown
129
+ await page.keyboard.press('ArrowDown')
130
+ await expect(input).toHaveValue('1')
131
+
132
+ // Test + key
133
+ await page.keyboard.press('+')
134
+ await expect(input).toHaveValue('2')
135
+
136
+ // Test - key
137
+ await page.keyboard.press('-')
138
+ await expect(input).toHaveValue('1')
139
+ })
140
+
141
+ test('handles keyboard interactions on input when enabled', async ({
142
+ page,
143
+ }) => {
144
+ // Use the interactive-input-test which has an input that's not disabled
145
+ const spinbutton = page.locator('#interactive-input-test')
146
+ const input = spinbutton.locator('input.value')
147
+ const incrementButton = spinbutton.locator('button.increment')
148
+
149
+ // First make input visible by incrementing
150
+ await incrementButton.click()
151
+ await expect(input).toBeVisible()
152
+
153
+ // Focus the input directly
154
+ await input.focus()
155
+
156
+ // Test ArrowUp
157
+ await page.keyboard.press('ArrowUp')
158
+ await expect(input).toHaveValue('2')
159
+
160
+ // Test ArrowDown
161
+ await page.keyboard.press('ArrowDown')
162
+ await expect(input).toHaveValue('1')
163
+
164
+ // Test + key
165
+ await page.keyboard.press('+')
166
+ await expect(input).toHaveValue('2')
167
+
168
+ // Test - key
169
+ await page.keyboard.press('-')
170
+ await expect(input).toHaveValue('1')
171
+ })
172
+
173
+ test('handles direct input value changes with validation', async ({
174
+ page,
175
+ }) => {
176
+ // Use the interactive-input-test which has an input that's not disabled
177
+ const spinbutton = page.locator('#interactive-input-test')
178
+ const input = spinbutton.locator('input.value')
179
+ const incrementButton = spinbutton.locator('button.increment')
180
+
181
+ // First make input visible by incrementing
182
+ await incrementButton.click()
183
+ await expect(input).toBeVisible()
184
+
185
+ // Clear and type a valid value
186
+ await input.fill('3')
187
+ await input.blur() // Trigger change event
188
+ await expect(input).toHaveValue('3')
189
+
190
+ // Try to input a value above max (should be clamped)
191
+ await input.fill('15')
192
+ await input.blur()
193
+ await expect(input).toHaveValue('12') // Should be clamped to max
194
+
195
+ // Try to input a negative value (should be clamped to 0, which hides input)
196
+ await input.fill('-5')
197
+ await input.blur()
198
+ await expect(input).toHaveValue('0')
199
+ await expect(input).toBeHidden()
200
+
201
+ // Make input visible again for next test
202
+ await incrementButton.click()
203
+ await incrementButton.click()
204
+ await expect(input).toBeVisible()
205
+ await expect(input).toHaveValue('2')
206
+
207
+ // Try to input a non-integer (should reset to previous valid value)
208
+ await input.fill('2.5')
209
+ await input.blur()
210
+ await expect(input).toHaveValue('2') // Should reset to previous valid value
211
+ })
212
+
213
+ test('keyboard interactions respect constraints', async ({ page }) => {
214
+ const spinbutton = page.locator('form-spinbutton').first()
215
+ const incrementButton = spinbutton.locator('button.increment')
216
+ const input = spinbutton.locator('input.value')
217
+
218
+ await incrementButton.focus()
219
+
220
+ // Go to max value using keyboard
221
+ for (let i = 0; i < 10; i++) {
222
+ await page.keyboard.press('ArrowUp')
223
+ }
224
+
225
+ await expect(input).toHaveValue('10')
226
+ await expect(incrementButton).toHaveAttribute('disabled')
227
+
228
+ // Try to go past max
229
+ await page.keyboard.press('ArrowUp')
230
+ await expect(input).toHaveValue('10')
231
+
232
+ // Switch to decrement button to go down
233
+ const decrementButton = spinbutton.locator('button.decrement')
234
+ await decrementButton.focus()
235
+
236
+ // Go down to 0 and try to go below
237
+ for (let i = 0; i < 10; i++) {
238
+ await page.keyboard.press('ArrowDown')
239
+ }
240
+
241
+ await expect(input).toHaveValue('0')
242
+ await expect(input).toBeHidden()
243
+
244
+ // Try to go below 0
245
+ await page.keyboard.press('ArrowDown')
246
+ await expect(input).toHaveValue('0')
247
+ })
248
+
249
+ test('keyboard events are prevented from propagating', async ({ page }) => {
250
+ // Set up event listener on document to detect if events bubble up
251
+ await page.evaluate(() => {
252
+ ;(window as any).documentKeydownCount = 0
253
+ document.addEventListener('keydown', e => {
254
+ if (['ArrowUp', 'ArrowDown', '+', '-'].includes(e.key)) {
255
+ ;(window as any).documentKeydownCount++
256
+ }
257
+ })
258
+ })
259
+
260
+ const spinbutton = page.locator('form-spinbutton').first()
261
+ const incrementButton = spinbutton.locator('button.increment')
262
+ await incrementButton.focus()
263
+
264
+ // Press handled keys
265
+ await page.keyboard.press('ArrowUp')
266
+ await page.keyboard.press('ArrowDown')
267
+ await page.keyboard.press('+')
268
+ await page.keyboard.press('-')
269
+
270
+ // Check that events were prevented from reaching document
271
+ const documentKeydownCount = await page.evaluate(
272
+ () => (window as any).documentKeydownCount,
273
+ )
274
+ expect(documentKeydownCount).toBe(0)
275
+
276
+ // Test that other keys still propagate
277
+ await page.keyboard.press('Escape')
278
+ await page.keyboard.press('Tab')
279
+
280
+ // These should have propagated (but we don't count them in our listener)
281
+ })
282
+
283
+ test('syncs value property with DOM interactions', async ({ page }) => {
284
+ const spinbutton = page.locator('form-spinbutton').first()
285
+ const incrementButton = spinbutton.locator('button.increment')
286
+
287
+ // Initial value should be 0
288
+ let valueProperty = await page.evaluate(() => {
289
+ const element = document.querySelector('form-spinbutton') as any
290
+ return element.value
291
+ })
292
+ expect(valueProperty).toBe(0)
293
+
294
+ // Click increment button
295
+ await incrementButton.click()
296
+
297
+ valueProperty = await page.evaluate(() => {
298
+ const element = document.querySelector('form-spinbutton') as any
299
+ return element.value
300
+ })
301
+ expect(valueProperty).toBe(1)
302
+
303
+ // Use keyboard to increment
304
+ await incrementButton.focus()
305
+ await page.keyboard.press('ArrowUp')
306
+
307
+ valueProperty = await page.evaluate(() => {
308
+ const element = document.querySelector('form-spinbutton') as any
309
+ return element.value
310
+ })
311
+ expect(valueProperty).toBe(2)
312
+ })
313
+
314
+ test('reads max value from input max attribute', async ({ page }) => {
315
+ // Check that max property reads from input.max
316
+ const maxProperty = await page.evaluate(() => {
317
+ const element = document.querySelector('form-spinbutton') as any
318
+ return element.max
319
+ })
320
+ expect(maxProperty).toBe(10)
321
+
322
+ // Test with another component that has different max
323
+ const max5Property = await page.evaluate(() => {
324
+ const element = document.querySelector('#max-5-test') as any
325
+ return element.max
326
+ })
327
+ expect(max5Property).toBe(5)
328
+ })
329
+
330
+ test('handles aria-label updates correctly', async ({ page }) => {
331
+ const spinbutton = page.locator('form-spinbutton').first()
332
+ const incrementButton = spinbutton.locator('button.increment')
333
+
334
+ // When value is 0, aria-label should use zero element text
335
+ let ariaLabel = await incrementButton.getAttribute('aria-label')
336
+ expect(ariaLabel).toBe('Add to Cart')
337
+
338
+ // When value is > 0, should use original aria-label
339
+ await incrementButton.click()
340
+ ariaLabel = await incrementButton.getAttribute('aria-label')
341
+ expect(ariaLabel).toBe('Increment')
342
+
343
+ // When back to 0, should use zero element text again
344
+ const decrementButton = spinbutton.locator('button.decrement')
345
+ await decrementButton.click()
346
+ ariaLabel = await incrementButton.getAttribute('aria-label')
347
+ expect(ariaLabel).toBe('Add to Cart')
348
+ })
349
+
350
+ test('value property is readonly (sensor-based)', async ({ page }) => {
351
+ // Test that the value property reflects DOM state but can't be controlled externally
352
+ const initialValue = await page.evaluate(() => {
353
+ const element = document.querySelector('form-spinbutton') as any
354
+ return element.value
355
+ })
356
+ expect(initialValue).toBe(0)
357
+
358
+ // Click the increment button
359
+ const incrementButton = page
360
+ .locator('form-spinbutton button.increment')
361
+ .first()
362
+ await incrementButton.click()
363
+
364
+ // Property should now reflect the updated state
365
+ const valueAfterClick = await page.evaluate(() => {
366
+ const element = document.querySelector('form-spinbutton') as any
367
+ return element.value
368
+ })
369
+ expect(valueAfterClick).toBe(1)
370
+
371
+ // Verify that trying to set the value property directly doesn't work
372
+ // (since it's a readonly sensor)
373
+ await page.evaluate(() => {
374
+ const element = document.querySelector('form-spinbutton') as any
375
+ try {
376
+ element.value = 5
377
+ } catch (_e) {
378
+ // Expected - property might be readonly
379
+ }
380
+ })
381
+
382
+ // Value should still be 1 (not changed to 5)
383
+ const input = page.locator('form-spinbutton input.value').first()
384
+ await expect(input).toHaveValue('1')
385
+
386
+ // The property should still reflect the DOM state
387
+ const valueAfterAttemptedChange = await page.evaluate(() => {
388
+ const element = document.querySelector('form-spinbutton') as any
389
+ return element.value
390
+ })
391
+ expect(valueAfterAttemptedChange).toBe(1)
392
+ })
393
+
394
+ test('handles rapid button clicks correctly', async ({ page }) => {
395
+ const spinbutton = page.locator('form-spinbutton').first()
396
+ const incrementButton = spinbutton.locator('button.increment')
397
+ const decrementButton = spinbutton.locator('button.decrement')
398
+ const input = spinbutton.locator('input.value')
399
+
400
+ // Rapid increment clicks
401
+ await incrementButton.click()
402
+ await incrementButton.click()
403
+ await incrementButton.click()
404
+ await expect(input).toHaveValue('3')
405
+
406
+ // Rapid decrement clicks
407
+ await decrementButton.click()
408
+ await decrementButton.click()
409
+ await expect(input).toHaveValue('1')
410
+
411
+ // Mix of rapid clicks
412
+ await incrementButton.click()
413
+ await decrementButton.click()
414
+ await incrementButton.click()
415
+ await expect(input).toHaveValue('2')
416
+ })
417
+
418
+ test('maintains UI state consistency during value changes', async ({
419
+ page,
420
+ }) => {
421
+ const spinbutton = page.locator('form-spinbutton').first()
422
+ const incrementButton = spinbutton.locator('button.increment')
423
+ const decrementButton = spinbutton.locator('button.decrement')
424
+ const input = spinbutton.locator('input.value')
425
+ const zeroElement = spinbutton.locator('.zero')
426
+ const otherElement = spinbutton.locator('.other')
427
+
428
+ // Test transition from 0 to 1
429
+ await incrementButton.click()
430
+ await expect(input).toBeVisible()
431
+ await expect(decrementButton).toBeVisible()
432
+ await expect(zeroElement).toBeHidden()
433
+ await expect(otherElement).toBeVisible()
434
+
435
+ // Test transition from 1 to 0
436
+ await decrementButton.click()
437
+ await expect(input).toBeHidden()
438
+ await expect(decrementButton).toBeHidden()
439
+ await expect(zeroElement).toBeVisible()
440
+ await expect(otherElement).toBeHidden()
441
+
442
+ // Test reaching max value
443
+ for (let i = 0; i < 10; i++) {
444
+ await incrementButton.click()
445
+ }
446
+ await expect(incrementButton).toHaveAttribute('disabled')
447
+ await expect(input).toHaveValue('10')
448
+ await expect(decrementButton).toBeVisible()
449
+ })
450
+
451
+ test('handles focus and keyboard navigation', async ({ page }) => {
452
+ const spinbutton = page.locator('form-spinbutton').first()
453
+ const incrementButton = spinbutton.locator('button.increment')
454
+ const input = spinbutton.locator('input.value')
455
+
456
+ // Button should be focusable
457
+ await incrementButton.focus()
458
+ await expect(incrementButton).toBeFocused()
459
+
460
+ // Test that we can use keyboard while focused
461
+ await page.keyboard.press('ArrowUp')
462
+ await expect(input).toHaveValue('1')
463
+
464
+ // Test refocusing the button and using keyboard again
465
+ await incrementButton.focus()
466
+ await expect(incrementButton).toBeFocused()
467
+
468
+ // Should still be able to use keyboard after refocus
469
+ await page.keyboard.press('ArrowUp')
470
+ await expect(input).toHaveValue('2')
471
+ })
472
+
473
+ test('reads initial value from DOM content', async ({ page }) => {
474
+ // Test component that has initial value set in DOM
475
+ const initialValueSpinbutton = page.locator('#initial-value-test')
476
+ const input = initialValueSpinbutton.locator('input.value')
477
+ const incrementButton = initialValueSpinbutton.locator('button.increment')
478
+ const decrementButton = initialValueSpinbutton.locator('button.decrement')
479
+ const otherElement = initialValueSpinbutton.locator('.other')
480
+
481
+ // Should read initial value from DOM
482
+ await expect(input).toHaveValue('3')
483
+ await expect(input).toBeVisible()
484
+
485
+ // UI should reflect non-zero state
486
+ await expect(decrementButton).toBeVisible()
487
+ await expect(otherElement).toBeVisible()
488
+
489
+ // Verify the component property matches
490
+ const valueProperty = await page.evaluate(() => {
491
+ const element = document.querySelector('#initial-value-test') as any
492
+ return element.value
493
+ })
494
+ expect(valueProperty).toBe(3)
495
+
496
+ // Should be able to increment from initial value
497
+ await incrementButton.click()
498
+ await expect(input).toHaveValue('4')
499
+ })
500
+
501
+ test('handles different max values correctly', async ({ page }) => {
502
+ const max5Spinbutton = page.locator('#max-5-test')
503
+ const incrementButton = max5Spinbutton.locator('button.increment')
504
+ const input = max5Spinbutton.locator('input.value')
505
+
506
+ // Increment to max (5)
507
+ for (let i = 0; i < 5; i++) {
508
+ await incrementButton.click()
509
+ }
510
+
511
+ await expect(input).toHaveValue('5')
512
+ await expect(incrementButton).toHaveAttribute('disabled')
513
+
514
+ // Should be disabled at max value
515
+ await expect(incrementButton).toHaveAttribute('disabled')
516
+ await expect(input).toHaveValue('5')
517
+ })
518
+
519
+ test('handles component without zero element', async ({ page }) => {
520
+ const noZeroSpinbutton = page.locator('#no-zero-test')
521
+ const incrementButton = noZeroSpinbutton.locator('button.increment')
522
+ const zeroElement = noZeroSpinbutton.locator('.zero')
523
+
524
+ // Should not have zero element
525
+ await expect(zeroElement).toHaveCount(0)
526
+
527
+ // Aria-label should fallback to original when no zero element exists
528
+ let ariaLabel = await incrementButton.getAttribute('aria-label')
529
+ expect(ariaLabel).toBe('Increment')
530
+
531
+ // After incrementing, should still use original aria-label
532
+ await incrementButton.click()
533
+ ariaLabel = await incrementButton.getAttribute('aria-label')
534
+ expect(ariaLabel).toBe('Increment')
535
+ })
536
+
537
+ test('handles multiple instances independently', async ({ page }) => {
538
+ const defaultSpinbutton = page.locator('form-spinbutton').first()
539
+ const max5Spinbutton = page.locator('#max-5-test')
540
+ const initialValueSpinbutton = page.locator('#initial-value-test')
541
+
542
+ const defaultIncrement = defaultSpinbutton.locator('button.increment')
543
+ const max5Increment = max5Spinbutton.locator('button.increment')
544
+ const initialIncrement = initialValueSpinbutton.locator('button.increment')
545
+
546
+ const defaultInput = defaultSpinbutton.locator('input.value')
547
+ const max5Input = max5Spinbutton.locator('input.value')
548
+ const initialInput = initialValueSpinbutton.locator('input.value')
549
+
550
+ // Verify initial states are different
551
+ await expect(defaultInput).toHaveValue('0')
552
+ await expect(max5Input).toHaveValue('0')
553
+ await expect(initialInput).toHaveValue('3')
554
+
555
+ // Interact with each independently
556
+ await defaultIncrement.click()
557
+ await expect(defaultInput).toHaveValue('1')
558
+ await expect(max5Input).toHaveValue('0')
559
+ await expect(initialInput).toHaveValue('3')
560
+
561
+ await max5Increment.click()
562
+ await max5Increment.click()
563
+ await expect(defaultInput).toHaveValue('1')
564
+ await expect(max5Input).toHaveValue('2')
565
+ await expect(initialInput).toHaveValue('3')
566
+
567
+ await initialIncrement.click()
568
+ await expect(defaultInput).toHaveValue('1')
569
+ await expect(max5Input).toHaveValue('2')
570
+ await expect(initialInput).toHaveValue('4')
571
+
572
+ // Test different max constraints
573
+ // Default max is 10, max5 is 5
574
+ for (let i = 0; i < 3; i++) {
575
+ await max5Increment.click()
576
+ }
577
+ // max5 should be at max (5) and disabled
578
+ await expect(max5Input).toHaveValue('5')
579
+ await expect(max5Increment).toHaveAttribute('disabled')
580
+
581
+ // Default should still be able to increment
582
+ await expect(defaultIncrement).not.toHaveAttribute('disabled')
583
+ await defaultIncrement.click()
584
+ await expect(defaultInput).toHaveValue('2')
585
+ })
586
+
587
+ test('keyboard navigation works across multiple instances', async ({
588
+ page,
589
+ }) => {
590
+ const defaultSpinbutton = page.locator('form-spinbutton').first()
591
+ const max5Spinbutton = page.locator('#max-5-test')
592
+
593
+ const defaultIncrement = defaultSpinbutton.locator('button.increment')
594
+ const max5Increment = max5Spinbutton.locator('button.increment')
595
+
596
+ const defaultInput = defaultSpinbutton.locator('input.value')
597
+ const max5Input = max5Spinbutton.locator('input.value')
598
+
599
+ // Focus first instance and use keyboard
600
+ await defaultIncrement.focus()
601
+ await page.keyboard.press('ArrowUp')
602
+ await expect(defaultInput).toHaveValue('1')
603
+ await expect(max5Input).toHaveValue('0')
604
+
605
+ // Focus second instance and use keyboard
606
+ await max5Increment.focus()
607
+ await page.keyboard.press('ArrowUp')
608
+ await page.keyboard.press('ArrowUp')
609
+ await expect(defaultInput).toHaveValue('1')
610
+ await expect(max5Input).toHaveValue('2')
611
+ })
612
+
613
+ test('form integration works correctly', async ({ page }) => {
614
+ // Create a form wrapper around the interactive input test component
615
+ await page.evaluate(() => {
616
+ const form = document.createElement('form')
617
+ form.id = 'test-form'
618
+ const spinbutton = document.querySelector('#interactive-input-test')
619
+ if (spinbutton?.parentNode) {
620
+ spinbutton.parentNode.insertBefore(form, spinbutton)
621
+ form.appendChild(spinbutton)
622
+ }
623
+ })
624
+
625
+ const incrementButton = page.locator(
626
+ '#interactive-input-test button.increment',
627
+ )
628
+
629
+ // Increment the value
630
+ await incrementButton.click()
631
+ await incrementButton.click()
632
+
633
+ // Test form data includes the input value
634
+ const formData = await page.evaluate(() => {
635
+ const form = document.querySelector('#test-form') as HTMLFormElement
636
+ if (!form) return null
637
+ const data = new FormData(form)
638
+ return Object.fromEntries(data.entries())
639
+ })
640
+
641
+ expect(formData).toEqual({ interactive: '2' })
642
+ })
643
+
644
+ test('input shows proper max attribute synchronization', async ({ page }) => {
645
+ const spinbutton = page.locator('form-spinbutton').first()
646
+ const input = spinbutton.locator('input.value')
647
+
648
+ // Check initial max attribute
649
+ await expect(input).toHaveAttribute('max', '10')
650
+
651
+ // Test different components have different max values
652
+ const max5Input = page.locator('#max-5-test input.value')
653
+ await expect(max5Input).toHaveAttribute('max', '5')
654
+
655
+ const initialTestInput = page.locator('#initial-value-test input.value')
656
+ await expect(initialTestInput).toHaveAttribute('max', '15')
657
+ })
658
+
659
+ test('handles enabled input field interactions', async ({ page }) => {
660
+ // Test with interactive-input-test which has an enabled input
661
+ const spinbutton = page.locator('#interactive-input-test')
662
+ const input = spinbutton.locator('input.value')
663
+ const incrementButton = spinbutton.locator('button.increment')
664
+
665
+ // Initially hidden because value is 0
666
+ await expect(input).toBeHidden()
667
+
668
+ // Click increment to make input visible
669
+ await incrementButton.click()
670
+ await expect(input).toBeVisible()
671
+ await expect(input).toHaveValue('1')
672
+
673
+ // Now we can interact with the input directly
674
+ await input.focus()
675
+ await input.fill('4')
676
+
677
+ // After blur, the value should be updated
678
+ await input.blur()
679
+ await expect(input).toHaveValue('4')
680
+
681
+ // Component property should reflect the change
682
+ const valueProperty = await page.evaluate(() => {
683
+ const element = document.querySelector('#interactive-input-test') as any
684
+ return element.value
685
+ })
686
+ expect(valueProperty).toBe(4)
687
+ })
688
+ })