@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,515 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ test.describe('form-radiogroup 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-radiogroup.html')
10
+ await page.waitForSelector('form-radiogroup')
11
+ })
12
+
13
+ test('renders initial state correctly', async ({ page }) => {
14
+ const radiogroupComponent = page.locator('form-radiogroup').first()
15
+ const radios = radiogroupComponent.locator('input[type="radio"]')
16
+ const legend = radiogroupComponent.locator('legend')
17
+ const labels = radiogroupComponent.locator('label')
18
+
19
+ // Should have the correct number of radio buttons
20
+ await expect(radios).toHaveCount(3)
21
+ await expect(labels).toHaveCount(3)
22
+
23
+ // Should display correct legend text
24
+ await expect(legend).toHaveText('Gender')
25
+
26
+ // Should have the initial selection based on the value attribute
27
+ const checkedRadio = radiogroupComponent.locator('input[value="other"]')
28
+ await expect(checkedRadio).toBeChecked()
29
+
30
+ // Should have the selected class on the correct label
31
+ const selectedLabel = radiogroupComponent.locator('label.selected')
32
+ await expect(selectedLabel).toHaveCount(1)
33
+ await expect(selectedLabel.locator('span')).toHaveText('Other')
34
+ })
35
+
36
+ test('selects radio button when clicked', async ({ page }) => {
37
+ const radiogroupComponent = page.locator('form-radiogroup').first()
38
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
39
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
40
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
41
+
42
+ // Initially "other" is selected
43
+ await expect(otherRadio).toBeChecked()
44
+ await expect(femaleRadio).not.toBeChecked()
45
+ await expect(maleRadio).not.toBeChecked()
46
+
47
+ // Click female radio
48
+ await femaleRadio.click()
49
+ await expect(femaleRadio).toBeChecked()
50
+ await expect(otherRadio).not.toBeChecked()
51
+ await expect(maleRadio).not.toBeChecked()
52
+
53
+ // Click male radio
54
+ await maleRadio.click()
55
+ await expect(maleRadio).toBeChecked()
56
+ await expect(femaleRadio).not.toBeChecked()
57
+ await expect(otherRadio).not.toBeChecked()
58
+ })
59
+
60
+ test('syncs value property with radio selection', async ({ page }) => {
61
+ const radiogroupComponent = page.locator('form-radiogroup').first()
62
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
63
+
64
+ // Initially should have "other" value
65
+ let currentValue = await page.evaluate(() => {
66
+ const element = document.querySelector('form-radiogroup') as any
67
+ return element.value
68
+ })
69
+ expect(currentValue).toBe('other')
70
+
71
+ // Click female radio
72
+ await femaleRadio.click()
73
+
74
+ currentValue = await page.evaluate(() => {
75
+ const element = document.querySelector('form-radiogroup') as any
76
+ return element.value
77
+ })
78
+ expect(currentValue).toBe('female')
79
+ })
80
+
81
+ test('updates selected class when selection changes', async ({ page }) => {
82
+ const radiogroupComponent = page.locator('form-radiogroup').first()
83
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
84
+ const femaleLabel = radiogroupComponent
85
+ .locator('label')
86
+ .filter({ has: page.locator('input[value="female"]') })
87
+ const otherLabel = radiogroupComponent
88
+ .locator('label')
89
+ .filter({ has: page.locator('input[value="other"]') })
90
+
91
+ // Initially "other" label should have selected class
92
+ const initialSelectedLabel = radiogroupComponent.locator('label.selected')
93
+ await expect(initialSelectedLabel).toHaveCount(1)
94
+ await expect(femaleLabel).not.toHaveClass(/selected/)
95
+
96
+ // Click female radio
97
+ await femaleRadio.click()
98
+
99
+ // Female label should now have selected class, other should not
100
+ await expect(femaleLabel).toHaveClass(/selected/)
101
+ await expect(otherLabel).not.toHaveClass(/selected/)
102
+ })
103
+
104
+ test('handles keyboard navigation with arrow keys', async ({ page }) => {
105
+ const radiogroupComponent = page.locator('form-radiogroup').first()
106
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
107
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
108
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
109
+
110
+ // Start with focus on the initially checked radio (other is at index 2)
111
+ await otherRadio.focus()
112
+ await expect(otherRadio).toBeFocused()
113
+
114
+ // Arrow right should wrap around to index 0 (female)
115
+ await page.keyboard.press('ArrowRight')
116
+ await expect(femaleRadio).toBeFocused()
117
+
118
+ // Arrow right should move to index 1 (male)
119
+ await page.keyboard.press('ArrowRight')
120
+ await expect(maleRadio).toBeFocused()
121
+
122
+ // Arrow right should move to index 2 (other)
123
+ await page.keyboard.press('ArrowRight')
124
+ await expect(otherRadio).toBeFocused()
125
+
126
+ // Arrow left should move backwards to index 1 (male)
127
+ await page.keyboard.press('ArrowLeft')
128
+ await expect(maleRadio).toBeFocused()
129
+
130
+ // Arrow left should move to index 0 (female)
131
+ await page.keyboard.press('ArrowLeft')
132
+ await expect(femaleRadio).toBeFocused()
133
+
134
+ // Arrow left from first item should wrap around to last item (other)
135
+ await page.keyboard.press('ArrowLeft')
136
+ await expect(otherRadio).toBeFocused()
137
+ })
138
+
139
+ test('handles keyboard navigation with up/down arrows', async ({ page }) => {
140
+ const radiogroupComponent = page.locator('form-radiogroup').first()
141
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
142
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
143
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
144
+
145
+ // First select male radio to establish checked state (needed for focus management)
146
+ await maleRadio.click()
147
+ await expect(maleRadio).toBeChecked()
148
+ await expect(maleRadio).toBeFocused()
149
+
150
+ // Arrow down should move focus (same as arrow right) to next index (other)
151
+ await page.keyboard.press('ArrowDown')
152
+ await expect(otherRadio).toBeFocused()
153
+
154
+ // Arrow down should wrap around to first item (female)
155
+ await page.keyboard.press('ArrowDown')
156
+ await expect(femaleRadio).toBeFocused()
157
+
158
+ // Arrow up should move focus backwards (same as arrow left) to last item (other)
159
+ await page.keyboard.press('ArrowUp')
160
+ await expect(otherRadio).toBeFocused()
161
+ })
162
+
163
+ test('handles Home and End key navigation', async ({ page }) => {
164
+ const radiogroupComponent = page.locator('form-radiogroup').first()
165
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
166
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
167
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
168
+
169
+ // Focus somewhere in the middle
170
+ await maleRadio.focus()
171
+ await expect(maleRadio).toBeFocused()
172
+
173
+ // Home should move to first radio (female is first in DOM)
174
+ await page.keyboard.press('Home')
175
+ await expect(femaleRadio).toBeFocused()
176
+
177
+ // End should move to last radio (other is last in DOM)
178
+ await page.keyboard.press('End')
179
+ await expect(otherRadio).toBeFocused()
180
+ })
181
+
182
+ test('handles Enter key to select focused radio', async ({ page }) => {
183
+ const radiogroupComponent = page.locator('form-radiogroup').first()
184
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
185
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
186
+
187
+ // Initially "other" is checked
188
+ await expect(otherRadio).toBeChecked()
189
+ await expect(femaleRadio).not.toBeChecked()
190
+
191
+ // Focus the currently checked radio (other), then navigate to female
192
+ await otherRadio.focus()
193
+ await expect(otherRadio).toBeFocused()
194
+
195
+ // Navigate to female radio using keyboard
196
+ await page.keyboard.press('ArrowDown') // Move to next (female)
197
+ await expect(femaleRadio).toBeFocused()
198
+ await expect(femaleRadio).not.toBeChecked() // Still not selected
199
+
200
+ // Press Enter to select
201
+ await page.keyboard.press('Enter')
202
+ await expect(femaleRadio).toBeChecked()
203
+ await expect(otherRadio).not.toBeChecked()
204
+ })
205
+
206
+ test('manages tabindex correctly for roving tabindex pattern', async ({
207
+ page,
208
+ }) => {
209
+ const radiogroupComponent = page.locator('form-radiogroup').first()
210
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
211
+
212
+ // Wait for component to initialize and set tabindex
213
+
214
+ // Initially checked radio should have tabindex 0, others -1
215
+ // Check the DOM property, not the attribute
216
+ const initialTabIndexes = await page.evaluate(() => {
217
+ const radios = Array.from(
218
+ document.querySelectorAll('form-radiogroup input[type="radio"]'),
219
+ )
220
+ return radios.map((r: any) => ({
221
+ value: r.value,
222
+ tabIndex: r.tabIndex,
223
+ }))
224
+ })
225
+
226
+ expect(initialTabIndexes.find(r => r.value === 'other')?.tabIndex).toBe(0)
227
+ expect(initialTabIndexes.find(r => r.value === 'female')?.tabIndex).toBe(-1)
228
+ expect(initialTabIndexes.find(r => r.value === 'male')?.tabIndex).toBe(-1)
229
+
230
+ // After selecting female, it should get tabindex 0
231
+ await femaleRadio.click()
232
+
233
+ // Check tabindex after selection change
234
+ const updatedTabIndexes = await page.evaluate(() => {
235
+ const radios = Array.from(
236
+ document.querySelectorAll('form-radiogroup input[type="radio"]'),
237
+ )
238
+ return radios.map((r: any) => ({
239
+ value: r.value,
240
+ tabIndex: r.tabIndex,
241
+ }))
242
+ })
243
+
244
+ expect(updatedTabIndexes.find(r => r.value === 'female')?.tabIndex).toBe(0)
245
+ expect(updatedTabIndexes.find(r => r.value === 'other')?.tabIndex).toBe(-1)
246
+ expect(updatedTabIndexes.find(r => r.value === 'male')?.tabIndex).toBe(-1)
247
+ })
248
+
249
+ test('handles clicking on label to select radio', async ({ page }) => {
250
+ const radiogroupComponent = page.locator('form-radiogroup').first()
251
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
252
+ const femaleLabel = radiogroupComponent
253
+ .locator('label')
254
+ .filter({ has: page.locator('input[value="female"]') })
255
+
256
+ // Initially not checked
257
+ await expect(femaleRadio).not.toBeChecked()
258
+
259
+ // Click on label should select the radio
260
+ await femaleLabel.click()
261
+ await expect(femaleRadio).toBeChecked()
262
+ })
263
+
264
+ test('handles multiple radiogroup instances independently', async ({
265
+ page,
266
+ }) => {
267
+ const firstRadiogroup = page.locator('form-radiogroup').first()
268
+ const secondRadiogroup = page.locator('form-radiogroup.split-button')
269
+
270
+ // First radiogroup should have "other" selected
271
+ const firstOtherRadio = firstRadiogroup.locator('input[value="other"]')
272
+ await expect(firstOtherRadio).toBeChecked()
273
+
274
+ // Second radiogroup should have "all" selected
275
+ const secondAllRadio = secondRadiogroup.locator('input[value="all"]')
276
+ await expect(secondAllRadio).toBeChecked()
277
+
278
+ // Change selection in first radiogroup
279
+ const firstFemaleRadio = firstRadiogroup.locator('input[value="female"]')
280
+ await firstFemaleRadio.click()
281
+ await expect(firstFemaleRadio).toBeChecked()
282
+ await expect(firstOtherRadio).not.toBeChecked()
283
+
284
+ // Second radiogroup should be unaffected
285
+ await expect(secondAllRadio).toBeChecked()
286
+
287
+ // Change selection in second radiogroup (use label click for visually hidden inputs)
288
+ const secondActiveLabel = secondRadiogroup
289
+ .locator('label')
290
+ .filter({ has: page.locator('input[value="active"]') })
291
+ const secondActiveRadio = secondRadiogroup.locator('input[value="active"]')
292
+ await secondActiveLabel.click()
293
+ await expect(secondActiveRadio).toBeChecked()
294
+ await expect(secondAllRadio).not.toBeChecked()
295
+
296
+ // First radiogroup should still have female selected
297
+ await expect(firstFemaleRadio).toBeChecked()
298
+ })
299
+
300
+ test('handles split-button variant styling', async ({ page }) => {
301
+ const splitButtonRadiogroup = page.locator('form-radiogroup.split-button')
302
+ const labels = splitButtonRadiogroup.locator('label')
303
+ const selectedLabel = splitButtonRadiogroup.locator('label.selected')
304
+
305
+ // Should have split-button class
306
+ await expect(splitButtonRadiogroup).toHaveClass(/split-button/)
307
+
308
+ // Should have correct number of labels
309
+ await expect(labels).toHaveCount(3)
310
+
311
+ // Should have one selected label initially
312
+ await expect(selectedLabel).toHaveCount(1)
313
+ await expect(selectedLabel.locator('span')).toHaveText('All')
314
+
315
+ // Visually hidden inputs should be present
316
+ const hiddenInputs = splitButtonRadiogroup.locator('input.visually-hidden')
317
+ await expect(hiddenInputs).toHaveCount(3)
318
+ })
319
+
320
+ test('value property is readonly (sensor-based)', async ({ page }) => {
321
+ // Test that the value property reflects radio state but doesn't control it
322
+ const initialValue = await page.evaluate(() => {
323
+ const element = document.querySelector('form-radiogroup') as any
324
+ return element.value
325
+ })
326
+ expect(initialValue).toBe('other')
327
+
328
+ // Click a different radio
329
+ const femaleRadio = page
330
+ .locator('form-radiogroup input[value="female"]')
331
+ .first()
332
+ await femaleRadio.click()
333
+
334
+ // Property should now reflect the selected value
335
+ const valueAfterClick = await page.evaluate(() => {
336
+ const element = document.querySelector('form-radiogroup') as any
337
+ return element.value
338
+ })
339
+ expect(valueAfterClick).toBe('female')
340
+
341
+ // Verify that trying to set the value property doesn't change the selection
342
+ // (since it's a readonly sensor)
343
+ await page.evaluate(() => {
344
+ const element = document.querySelector('form-radiogroup') as any
345
+ try {
346
+ element.value = 'male'
347
+ } catch (_e) {
348
+ // Expected - property might be readonly
349
+ }
350
+ })
351
+
352
+ // Radio should still be female
353
+ await expect(femaleRadio).toBeChecked()
354
+
355
+ // The property should still reflect female because it reads from DOM
356
+ const valueAfterAttemptedChange = await page.evaluate(() => {
357
+ const element = document.querySelector('form-radiogroup') as any
358
+ return element.value
359
+ })
360
+ expect(valueAfterAttemptedChange).toBe('female')
361
+ })
362
+
363
+ test('sensor updates when radio state changes programmatically', async ({
364
+ page,
365
+ }) => {
366
+ const radiogroupComponent = page.locator('form-radiogroup').first()
367
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
368
+
369
+ // Change the radio state directly via DOM
370
+ await page.evaluate(() => {
371
+ const input = document.querySelector(
372
+ 'form-radiogroup input[value="male"]',
373
+ ) as HTMLInputElement
374
+ input.checked = true
375
+ input.dispatchEvent(new Event('change', { bubbles: true }))
376
+ })
377
+
378
+ // Component should reflect the change
379
+ await expect(maleRadio).toBeChecked()
380
+
381
+ const valueProperty = await page.evaluate(() => {
382
+ const element = document.querySelector('form-radiogroup') as any
383
+ return element.value
384
+ })
385
+ expect(valueProperty).toBe('male')
386
+ })
387
+
388
+ test('fires change events on radio interaction', async ({ page }) => {
389
+ // Set up event listener
390
+ await page.evaluate(() => {
391
+ ;(window as any).changeEventCount = 0
392
+ const radiogroup = document.querySelector('form-radiogroup')
393
+ radiogroup?.addEventListener('change', () => {
394
+ ;(window as any).changeEventCount++
395
+ })
396
+ })
397
+
398
+ const femaleRadio = page
399
+ .locator('form-radiogroup input[value="female"]')
400
+ .first()
401
+
402
+ // Click should fire change event
403
+ await femaleRadio.click()
404
+
405
+ let changeEventCount = await page.evaluate(
406
+ () => (window as any).changeEventCount,
407
+ )
408
+ expect(changeEventCount).toBe(1)
409
+
410
+ // Click different radio should fire another change event
411
+ const maleRadio = page
412
+ .locator('form-radiogroup input[value="male"]')
413
+ .first()
414
+ await maleRadio.click()
415
+
416
+ changeEventCount = await page.evaluate(
417
+ () => (window as any).changeEventCount,
418
+ )
419
+ expect(changeEventCount).toBe(2)
420
+ })
421
+
422
+ test('handles form integration', async ({ page }) => {
423
+ // Add a form wrapper and test form data
424
+ await page.evaluate(() => {
425
+ const form = document.createElement('form')
426
+ const radiogroup = document.querySelector('form-radiogroup')
427
+ if (radiogroup) {
428
+ radiogroup.parentNode?.insertBefore(form, radiogroup)
429
+ form.appendChild(radiogroup)
430
+ }
431
+ })
432
+
433
+ const femaleRadio = page
434
+ .locator('form-radiogroup input[value="female"]')
435
+ .first()
436
+ await femaleRadio.click()
437
+
438
+ // Test form data includes the selected radio value
439
+ const formData = await page.evaluate(() => {
440
+ const form = document.querySelector('form')
441
+ if (!form) return null
442
+ const data = new FormData(form)
443
+ return Object.fromEntries(data.entries())
444
+ })
445
+
446
+ expect(formData).toEqual({ gender: 'female' })
447
+ })
448
+
449
+ test('handles keyboard navigation without affecting page scroll', async ({
450
+ page,
451
+ }) => {
452
+ const radiogroupComponent = page.locator('form-radiogroup').first()
453
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
454
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
455
+
456
+ // Focus the initially checked radio
457
+ await otherRadio.focus()
458
+ await expect(otherRadio).toBeFocused()
459
+
460
+ // Get initial scroll position
461
+ const initialScrollY = await page.evaluate(() => window.scrollY)
462
+
463
+ // Navigate with arrow keys (these should not scroll the page)
464
+ await page.keyboard.press('ArrowRight')
465
+ await expect(femaleRadio).toBeFocused()
466
+
467
+ await page.keyboard.press('Home')
468
+ await expect(femaleRadio).toBeFocused() // Should stay at first
469
+
470
+ await page.keyboard.press('End')
471
+ await expect(otherRadio).toBeFocused() // Should move to last
472
+
473
+ // Verify page didn't scroll (meaning default behavior was prevented)
474
+ const finalScrollY = await page.evaluate(() => window.scrollY)
475
+ expect(finalScrollY).toBe(initialScrollY)
476
+ })
477
+
478
+ test('handles rapid selection changes', async ({ page }) => {
479
+ const radiogroupComponent = page.locator('form-radiogroup').first()
480
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
481
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
482
+ const otherRadio = radiogroupComponent.locator('input[value="other"]')
483
+
484
+ // Rapid clicks between different radios
485
+ await femaleRadio.click()
486
+ await maleRadio.click()
487
+ await otherRadio.click()
488
+ await femaleRadio.click()
489
+
490
+ // Should end up with female selected
491
+ await expect(femaleRadio).toBeChecked()
492
+ await expect(maleRadio).not.toBeChecked()
493
+ await expect(otherRadio).not.toBeChecked()
494
+
495
+ const finalValue = await page.evaluate(() => {
496
+ const element = document.querySelector('form-radiogroup') as any
497
+ return element.value
498
+ })
499
+ expect(finalValue).toBe('female')
500
+ })
501
+
502
+ test('maintains focus management after DOM changes', async ({ page }) => {
503
+ const radiogroupComponent = page.locator('form-radiogroup').first()
504
+ const femaleRadio = radiogroupComponent.locator('input[value="female"]')
505
+
506
+ // Focus and select female (index 0)
507
+ await femaleRadio.focus()
508
+ await femaleRadio.click()
509
+
510
+ // Verify focus management is still working - should move to next index
511
+ await page.keyboard.press('ArrowRight')
512
+ const maleRadio = radiogroupComponent.locator('input[value="male"]')
513
+ await expect(maleRadio).toBeFocused()
514
+ })
515
+ })
@@ -0,0 +1,58 @@
1
+ import {
2
+ type Collection,
3
+ type Component,
4
+ createSensor,
5
+ defineComponent,
6
+ read,
7
+ setProperty,
8
+ toggleClass,
9
+ } from '../..'
10
+ import { manageFocus } from '../_common/focus'
11
+
12
+ export type FormRadiogroupProps = {
13
+ readonly value: string
14
+ }
15
+
16
+ type FormRadiogroupUI = {
17
+ radios: Collection<HTMLInputElement>
18
+ labels: Collection<HTMLLabelElement>
19
+ }
20
+
21
+ declare global {
22
+ interface HTMLElementTagNameMap {
23
+ 'form-radiogroup': Component<FormRadiogroupProps>
24
+ }
25
+ }
26
+
27
+ const getIndex = (radios: Collection<HTMLInputElement>) =>
28
+ radios.get().findIndex(radio => radio.checked)
29
+
30
+ export default defineComponent<FormRadiogroupProps, FormRadiogroupUI>(
31
+ 'form-radiogroup',
32
+ {
33
+ value: createSensor(
34
+ read(({ radios }) => radios[getIndex(radios)]?.value, ''),
35
+ 'radios',
36
+ {
37
+ change: ({ target }) => target.value,
38
+ },
39
+ ),
40
+ },
41
+ ({ all }) => ({
42
+ radios: all(
43
+ 'input[type="radio"]',
44
+ 'Add at least two native radio buttons.',
45
+ ),
46
+ labels: all('label', 'Wrap radio buttons with labels.'),
47
+ }),
48
+ ({ host, radios }) => ({
49
+ host: manageFocus(radios, getIndex),
50
+ radios: setProperty('tabIndex', target =>
51
+ target.value === host.value ? 0 : -1,
52
+ ),
53
+ labels: toggleClass(
54
+ 'selected',
55
+ target => host.value === target.querySelector('input')?.value,
56
+ ),
57
+ }),
58
+ )
@@ -0,0 +1,95 @@
1
+ form-spinbutton {
2
+ display: inline-flex;
3
+ max-width: 7rem;
4
+
5
+ > input {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ border-width: 1px 0;
9
+ border-style: solid;
10
+ border-color: var(--color-border);
11
+ height: var(--input-height);
12
+ min-width: 3rem;
13
+ text-align: center;
14
+ font-size: var(--font-size-s);
15
+ padding: 0 var(--space-s);
16
+ line-height: 2;
17
+ flex-grow: 1;
18
+ appearance: textfield;
19
+ -moz-appearance: textfield;
20
+
21
+ &::-webkit-outer-spin-button,
22
+ &::-webkit-inner-spin-button {
23
+ -webkit-appearance: none;
24
+ margin: 0;
25
+ }
26
+
27
+ &:first-child {
28
+ border-width: 1px;
29
+ min-width: 2.5rem;
30
+ margin-right: 0.5rem;
31
+ }
32
+ }
33
+
34
+ > button {
35
+ flex-grow: 0;
36
+ box-sizing: border-box;
37
+ height: var(--input-height);
38
+ min-width: var(--input-height);
39
+ border: 1px solid var(--color-border);
40
+ background-color: var(--color-secondary);
41
+ color: var(--color-text);
42
+ padding: 0 var(--space-s);
43
+ font-size: var(--font-size-s);
44
+ line-height: var(--line-height-s);
45
+ white-space: nowrap;
46
+ cursor: pointer;
47
+ transition: all var(--transition-shorter) var(--easing-inout);
48
+
49
+ &:disabled {
50
+ opacity: var(--opacity-translucent);
51
+ }
52
+
53
+ &:not(:disabled) {
54
+ cursor: pointer;
55
+ opacity: var(--opacity-solid);
56
+
57
+ &:hover {
58
+ background-color: var(--color-secondary-hover);
59
+ }
60
+
61
+ &:active {
62
+ background-color: var(--color-secondary-active);
63
+ }
64
+ }
65
+
66
+ &:first-of-type {
67
+ border-radius: var(--space-xs) 0 0 var(--space-xs);
68
+ }
69
+
70
+ &:last-of-type {
71
+ border-radius: 0 var(--space-xs) var(--space-xs) 0;
72
+ }
73
+
74
+ &:not([hidden]) + button:last-of-type {
75
+ border-left-width: 0;
76
+ }
77
+ }
78
+
79
+ [hidden] + button {
80
+ flex-grow: 1;
81
+ min-width: 7rem;
82
+ border-radius: var(--space-xs);
83
+ color: var(--color-text-inverted);
84
+ background-color: var(--color-primary);
85
+ border-color: var(--color-primary-active);
86
+
87
+ &:hover {
88
+ background-color: var(--color-primary-hover);
89
+ }
90
+
91
+ &:active {
92
+ background-color: var(--color-primary-active);
93
+ }
94
+ }
95
+ }