@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,175 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ test.describe('basic-number 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/basic-number.html')
10
+ await page.waitForSelector('basic-number')
11
+ })
12
+
13
+ test('renders default number formatting', async ({ page }) => {
14
+ // Test the first example: basic unit formatting
15
+ const firstNumber = page.locator('basic-number').first()
16
+ await expect(firstNumber).toHaveText('25,678.9 liters')
17
+ })
18
+
19
+ test('formats currency with locale-specific formatting', async ({ page }) => {
20
+ // Test German-Swiss currency formatting
21
+ const germanNumber = page.locator('#german-swiss')
22
+ await expect(germanNumber).toContainText('CHF') // Should contain Swiss Franc
23
+ await expect(germanNumber).toContainText('25') // Should contain the base number
24
+
25
+ // Test French-Swiss currency formatting
26
+ const frenchNumber = page.locator('#french-swiss')
27
+ await expect(frenchNumber).toContainText('CHF') // Should contain Swiss Franc
28
+ await expect(frenchNumber).toContainText('25') // Should contain the base number
29
+ })
30
+
31
+ test('handles different unit types and locales', async ({ page }) => {
32
+ // Test Arabic locale with speed unit - expect Arabic-Indic numerals
33
+ const arabicNumber = page.locator('#arabic-speed')
34
+ await expect(arabicNumber).toContainText('٢٥') // Should contain Arabic-Indic numerals for 25
35
+
36
+ // Test Chinese locale with time unit - should contain time unit and some form of the number
37
+ const chineseNumber = page.locator('#chinese-time')
38
+ await expect(chineseNumber).toContainText('秒') // Should contain Chinese word for "second"
39
+ })
40
+
41
+ test('updates when value property changes', async ({ page }) => {
42
+ const numberElement = page.locator('basic-number').first()
43
+
44
+ // Get initial text
45
+ const initialText = await numberElement.textContent()
46
+ expect(initialText).toContain('25,678.9')
47
+
48
+ // Change the value property
49
+ await numberElement.evaluate(node => {
50
+ ;(node as any).value = 12345.6
51
+ })
52
+
53
+ // Should update to show new formatted number
54
+ await expect(numberElement).toHaveText('12,345.6 liters')
55
+ })
56
+
57
+ test('handles invalid JSON options gracefully', async ({ page }) => {
58
+ // Test HTML markup error logging - should log error on page load
59
+ const htmlElement = page.locator('#invalid-json-html')
60
+ await expect(htmlElement).toHaveText('1,234.5') // Should fall back to default
61
+
62
+ // Test dynamic creation also logs errors
63
+ const consoleMessages: string[] = []
64
+ page.on('console', msg => {
65
+ if (msg.type() === 'error') {
66
+ consoleMessages.push(msg.text())
67
+ }
68
+ })
69
+
70
+ await page.evaluate(() => {
71
+ const element = document.createElement('basic-number') as any
72
+ element.value = 9999
73
+ element.setAttribute('options', '{ invalid json }')
74
+ element.setAttribute('id', 'invalid-json-dynamic')
75
+ document.body.appendChild(element)
76
+ })
77
+
78
+ const dynamicElement = page.locator('#invalid-json-dynamic')
79
+ await expect(dynamicElement).toHaveText('9,999') // Should fall back to default
80
+
81
+ // Verify console error was logged for dynamic creation
82
+ const hasJsonError = consoleMessages.some(
83
+ msg => msg.includes('Invalid JSON') || msg.includes('JSON'),
84
+ )
85
+ expect(hasJsonError).toBe(true)
86
+ })
87
+
88
+ test('handles missing required currency gracefully', async ({ page }) => {
89
+ // Test HTML markup error logging - should log error on page load
90
+ const htmlElement = page.locator('#missing-currency-html')
91
+ await expect(htmlElement).toHaveText('1,000') // Should fall back to default
92
+
93
+ // Test dynamic creation also logs errors
94
+ const consoleMessages: string[] = []
95
+ page.on('console', msg => {
96
+ if (msg.type() === 'error') {
97
+ consoleMessages.push(msg.text())
98
+ }
99
+ })
100
+
101
+ await page.evaluate(() => {
102
+ const element = document.createElement('basic-number') as any
103
+ element.value = 9999
104
+ element.setAttribute('options', '{"style":"currency"}')
105
+ element.setAttribute('id', 'missing-currency-dynamic')
106
+ document.body.appendChild(element)
107
+ })
108
+
109
+ const dynamicElement = page.locator('#missing-currency-dynamic')
110
+ await expect(dynamicElement).toHaveText('9,999') // Should fall back to default
111
+
112
+ // Verify console error was logged for dynamic creation
113
+ const hasCurrencyError = consoleMessages.some(
114
+ msg => msg.includes('currency') && msg.includes('CHF'),
115
+ )
116
+ expect(hasCurrencyError).toBe(true)
117
+ })
118
+
119
+ test('handles missing required unit gracefully', async ({ page }) => {
120
+ // Test HTML markup error logging - should log error on page load
121
+ const htmlElement = page.locator('#missing-unit-html')
122
+ await expect(htmlElement).toHaveText('500') // Should fall back to default
123
+
124
+ // Test dynamic creation also logs errors
125
+ const consoleMessages: string[] = []
126
+ page.on('console', msg => {
127
+ if (msg.type() === 'error') {
128
+ consoleMessages.push(msg.text())
129
+ }
130
+ })
131
+
132
+ await page.evaluate(() => {
133
+ const element = document.createElement('basic-number') as any
134
+ element.value = 9999
135
+ element.setAttribute('options', '{"style":"unit"}')
136
+ element.setAttribute('id', 'missing-unit-dynamic')
137
+ document.body.appendChild(element)
138
+ })
139
+
140
+ const dynamicElement = page.locator('#missing-unit-dynamic')
141
+ await expect(dynamicElement).toHaveText('9,999') // Should fall back to default
142
+
143
+ // Verify console error was logged for dynamic creation
144
+ const hasUnitError = consoleMessages.some(
145
+ msg =>
146
+ msg.includes('unit')
147
+ && (msg.includes('liter') || msg.includes('kilometer')),
148
+ )
149
+ expect(hasUnitError).toBe(true)
150
+ })
151
+
152
+ test('works with decimal style formatting', async ({ page }) => {
153
+ // Use the existing decimal test element from HTML
154
+ const decimalElement = page.locator('#decimal-test')
155
+ // Should respect the fraction digit limits
156
+ await expect(decimalElement).toHaveText('1,234.568')
157
+ })
158
+
159
+ test('inherits locale from closest lang attribute', async ({ page }) => {
160
+ // Use the existing inheritance test element from HTML
161
+ const inheritElement = page.locator('#inherit-test')
162
+ // Should use German formatting for currency
163
+ await expect(inheritElement).toContainText('€')
164
+ await expect(inheritElement).toContainText('1') // Should contain the number
165
+ })
166
+
167
+ test('handles zero and negative values correctly', async ({ page }) => {
168
+ // Use the existing elements from HTML
169
+ const zeroElement = page.locator('#zero-test')
170
+ const negativeElement = page.locator('#negative-test')
171
+
172
+ await expect(zeroElement).toHaveText('0')
173
+ await expect(negativeElement).toHaveText('-1,234.5')
174
+ })
175
+ })
@@ -0,0 +1,124 @@
1
+ import { asNumber, type Component, defineComponent, setText } from '../..'
2
+
3
+ export type BasicNumberProps = {
4
+ value: number
5
+ }
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ 'basic-number': Component<BasicNumberProps>
10
+ }
11
+ }
12
+
13
+ type Logger = {
14
+ onWarn: (message: string) => void
15
+ onError: (message: string) => void
16
+ }
17
+
18
+ const FALLBACK_LOCALE = 'en'
19
+
20
+ function getNumberFormatter(
21
+ locale: string,
22
+ rawOptions: string | null,
23
+ logger: Logger = {
24
+ onWarn: console.warn,
25
+ onError: console.error,
26
+ },
27
+ ) {
28
+ const useFallback = () => new Intl.NumberFormat(locale)
29
+ if (!rawOptions) return useFallback()
30
+ const { onWarn, onError } = logger
31
+
32
+ let o: Intl.NumberFormatOptions = {}
33
+ try {
34
+ o = JSON.parse(rawOptions)
35
+ } catch (error) {
36
+ onError?.(`Invalid JSON: ${error}`)
37
+ return useFallback()
38
+ }
39
+
40
+ const style = o.style ?? 'decimal'
41
+
42
+ const drops: string[] = []
43
+ if (style === 'currency') {
44
+ if (
45
+ !o.currency ||
46
+ typeof o.currency !== 'string' ||
47
+ o.currency.length !== 3
48
+ ) {
49
+ onError?.(
50
+ `style="currency" requires a 3-letter ISO currency (e.g. "CHF").`,
51
+ )
52
+ return useFallback()
53
+ }
54
+ } else {
55
+ drops.push('currency', 'currencyDisplay', 'currencySign')
56
+ }
57
+
58
+ if (style === 'unit') {
59
+ if (!o.unit || typeof o.unit !== 'string') {
60
+ onError?.(
61
+ `style="unit" requires a "unit" (e.g. "liter", "kilometer-per-hour").`,
62
+ )
63
+ return useFallback()
64
+ }
65
+ } else {
66
+ drops.push('unit', 'unitDisplay')
67
+ }
68
+
69
+ if (o.notation && o.notation !== 'compact') drops.push('compactDisplay')
70
+
71
+ const sanitized: Intl.NumberFormatOptions = {}
72
+ for (const [k, v] of Object.entries(o)) {
73
+ if (!drops.includes(k)) sanitized[k] = v
74
+ else onWarn?.(`Option "${k}" is ignored for style="${style}".`)
75
+ }
76
+
77
+ const { minimumFractionDigits: minFD, maximumFractionDigits: maxFD } =
78
+ sanitized
79
+ if (minFD != null && maxFD != null && minFD > maxFD) {
80
+ onWarn?.(
81
+ `minimumFractionDigits (${minFD}) > maximumFractionDigits (${maxFD}); swapping.`,
82
+ )
83
+ sanitized.minimumFractionDigits = maxFD
84
+ sanitized.maximumFractionDigits = minFD
85
+ }
86
+ const { minimumSignificantDigits: minSD, maximumSignificantDigits: maxSD } =
87
+ sanitized
88
+ if (minSD != null && maxSD != null && minSD > maxSD) {
89
+ onWarn?.(
90
+ `minimumSignificantDigits (${minSD}) > maximumSignificantDigits (${maxSD}); swapping.`,
91
+ )
92
+ sanitized.minimumSignificantDigits = maxSD
93
+ sanitized.maximumSignificantDigits = minSD
94
+ }
95
+
96
+ try {
97
+ const formatter = new Intl.NumberFormat(locale, sanitized)
98
+ if (formatter.resolvedOptions().locale !== locale)
99
+ onWarn(
100
+ `Fall back to locale ${formatter.resolvedOptions().locale} instead of ${locale}`,
101
+ )
102
+ return formatter
103
+ } catch (e) {
104
+ onError?.(
105
+ `Options rejected by Intl.NumberFormat: ${e instanceof Error ? e.message : String(e)}`,
106
+ )
107
+ return useFallback()
108
+ }
109
+ }
110
+
111
+ export default defineComponent<BasicNumberProps>(
112
+ 'basic-number',
113
+ { value: asNumber() },
114
+ undefined,
115
+ ({ host }) => {
116
+ const formatter = getNumberFormatter(
117
+ host.closest('[lang]')?.getAttribute('lang') || FALLBACK_LOCALE,
118
+ host.getAttribute('options'),
119
+ )
120
+ return {
121
+ host: setText(() => formatter.format(host.value)),
122
+ }
123
+ },
124
+ )
@@ -0,0 +1,64 @@
1
+ <!-- Default example -->
2
+ <basic-pluralize>
3
+ <p class="none">Well done, all done!</p>
4
+ <p class="some">
5
+ <span class="count"></span>
6
+ task<span class="other">s</span>
7
+ remaining
8
+ </p>
9
+ </basic-pluralize>
10
+
11
+ <!-- Test example with plural categories -->
12
+ <basic-pluralize id="plural-test" count="1">
13
+ <p class="none">Nobody</p>
14
+ <p class="some">
15
+ <span class="count"></span>
16
+ <span class="one">person</span>
17
+ <span class="other">people</span>
18
+ </p>
19
+ </basic-pluralize>
20
+
21
+ <!-- Welsh locale example - demonstrates complex pluralization rules
22
+ Welsh has 6 plural categories: zero, one, two, few, many, other
23
+ Example: "dog" in Welsh
24
+ 0 = cŵn (zero), 1 = ci (one), 2 = gi (two), 3 = chi (few), 6 = chi (many), 4/5/7+ = ci (other) -->
25
+ <div lang="cy">
26
+ <basic-pluralize id="welsh-test" count="0">
27
+ <p class="none">Dim anifeiliaid!</p>
28
+ <p class="some">
29
+ <span class="count"></span>
30
+ <span class="zero">cŵn</span>
31
+ <span class="one">ci</span>
32
+ <span class="two">gi</span>
33
+ <span class="few">chi</span>
34
+ <span class="many">chi</span>
35
+ <span class="other">ci</span>
36
+ </p>
37
+ </basic-pluralize>
38
+ </div>
39
+
40
+ <!-- Ordinal example -->
41
+ <basic-pluralize id="ordinal-test" count="1" ordinal>
42
+ <p class="none">None</p>
43
+ <p class="some">
44
+ <span class="count"></span><span class="one">st</span
45
+ ><span class="two">nd</span><span class="few">rd</span
46
+ ><span class="other">th</span>
47
+ </p>
48
+ </basic-pluralize>
49
+
50
+ <!-- Multiple instances example -->
51
+ <basic-pluralize id="pluralize-1" count="0">
52
+ <p class="none">No items</p>
53
+ <p class="some">
54
+ <span class="count"></span> item<span class="other">s</span>
55
+ </p>
56
+ </basic-pluralize>
57
+
58
+ <basic-pluralize id="pluralize-2" count="5">
59
+ <p class="none">No filters</p>
60
+ <p class="some">
61
+ <span class="count"></span> filter criteri<span class="one">on</span
62
+ ><span class="other">a</span>
63
+ </p>
64
+ </basic-pluralize>
@@ -0,0 +1,258 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ test.describe('basic-pluralize 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/basic-pluralize.html')
10
+ await page.waitForSelector('basic-pluralize')
11
+ })
12
+
13
+ test('renders correctly with count=0 (shows none, hides some)', async ({
14
+ page,
15
+ }) => {
16
+ // Set count to 0 on the default element
17
+ await page.evaluate(() => {
18
+ const element = document.querySelector('basic-pluralize') as any
19
+ element.setAttribute('count', '0')
20
+ })
21
+
22
+ // Should show .none and hide .some when count is 0
23
+ const defaultElement = page.locator('basic-pluralize').first()
24
+ const noneElement = defaultElement.locator('.none')
25
+ const someElement = defaultElement.locator('.some')
26
+
27
+ await expect(noneElement).toBeVisible()
28
+ await expect(someElement).toBeHidden()
29
+ })
30
+
31
+ test('renders correctly with count>0 (hides none, shows some)', async ({
32
+ page,
33
+ }) => {
34
+ // Set count to 5 on the default element
35
+ await page.evaluate(() => {
36
+ const element = document.querySelector('basic-pluralize') as any
37
+ element.setAttribute('count', '5')
38
+ })
39
+
40
+ const defaultElement = page.locator('basic-pluralize').first()
41
+ const noneElement = defaultElement.locator('.none')
42
+ const someElement = defaultElement.locator('.some')
43
+ const countSpan = defaultElement.locator('.count')
44
+
45
+ await expect(noneElement).toBeHidden()
46
+ await expect(someElement).toBeVisible()
47
+ await expect(countSpan).toHaveText('5')
48
+ })
49
+
50
+ test('updates count display when attribute changes', async ({ page }) => {
51
+ await page.evaluate(() => {
52
+ const element = document.querySelector('basic-pluralize') as any
53
+ element.setAttribute('count', '1')
54
+ })
55
+
56
+ const defaultElement = page.locator('basic-pluralize').first()
57
+ const countSpan = defaultElement.locator('.count')
58
+ await expect(countSpan).toHaveText('1')
59
+
60
+ // Change to different count
61
+ await page.evaluate(() => {
62
+ const element = document.querySelector('basic-pluralize') as any
63
+ element.setAttribute('count', '42')
64
+ })
65
+
66
+ await expect(countSpan).toHaveText('42')
67
+ })
68
+
69
+ test('handles plural categories correctly (English)', async ({ page }) => {
70
+ // Use the existing #plural-test element from HTML
71
+ const testElement = page.locator('#plural-test')
72
+ const oneElement = testElement.locator('.one')
73
+ const otherElement = testElement.locator('.other')
74
+
75
+ // Should start with count=1, showing 'one' category
76
+ await expect(oneElement).toBeVisible()
77
+ await expect(otherElement).toBeHidden()
78
+
79
+ // Test count = 2 (should show 'other' category)
80
+ await page.evaluate(() => {
81
+ const element = document.querySelector('#plural-test') as any
82
+ element.setAttribute('count', '2')
83
+ })
84
+
85
+ await expect(oneElement).toBeHidden()
86
+ await expect(otherElement).toBeVisible()
87
+ })
88
+
89
+ test('handles Welsh pluralization with all 6 categories (zero, one, two, few, many, other)', async ({
90
+ page,
91
+ }) => {
92
+ // Use the existing Welsh example from HTML
93
+ const welshElement = page.locator('#welsh-test')
94
+ const countSpan = welshElement.locator('.count')
95
+
96
+ // Should start with count=0, showing zero form "cŵn"
97
+ await expect(countSpan).toHaveText('0')
98
+ await expect(welshElement.locator('.none')).toBeVisible()
99
+ await expect(welshElement.locator('.some')).toBeHidden()
100
+
101
+ // Test count=1 (should show "ci" - one form)
102
+ await page.evaluate(() => {
103
+ const element = document.querySelector('#welsh-test') as any
104
+ element.setAttribute('count', '1')
105
+ })
106
+ await expect(welshElement.locator('.none')).toBeHidden()
107
+ await expect(welshElement.locator('.some')).toBeVisible()
108
+ await expect(welshElement.locator('.one')).toBeVisible()
109
+ await expect(welshElement.locator('.other')).toBeHidden()
110
+
111
+ // Test count=2 (should show "gi" - two form)
112
+ await page.evaluate(() => {
113
+ const element = document.querySelector('#welsh-test') as any
114
+ element.setAttribute('count', '2')
115
+ })
116
+ await expect(welshElement.locator('.one')).toBeHidden()
117
+ await expect(welshElement.locator('.two')).toBeVisible()
118
+ await expect(welshElement.locator('.other')).toBeHidden()
119
+
120
+ // Test count=3 (should show "chi" - few form)
121
+ await page.evaluate(() => {
122
+ const element = document.querySelector('#welsh-test') as any
123
+ element.setAttribute('count', '3')
124
+ })
125
+ await expect(welshElement.locator('.two')).toBeHidden()
126
+ await expect(welshElement.locator('.few')).toBeVisible()
127
+ await expect(welshElement.locator('.other')).toBeHidden()
128
+
129
+ // Test count=6 (should show "chi" - many form)
130
+ await page.evaluate(() => {
131
+ const element = document.querySelector('#welsh-test') as any
132
+ element.setAttribute('count', '6')
133
+ })
134
+ await expect(welshElement.locator('.few')).toBeHidden()
135
+ await expect(welshElement.locator('.many')).toBeVisible()
136
+ await expect(welshElement.locator('.other')).toBeHidden()
137
+
138
+ // Test count=4 (should show "ci" - other form)
139
+ await page.evaluate(() => {
140
+ const element = document.querySelector('#welsh-test') as any
141
+ element.setAttribute('count', '4')
142
+ })
143
+ await expect(welshElement.locator('.many')).toBeHidden()
144
+ await expect(welshElement.locator('.other')).toBeVisible()
145
+ await expect(welshElement.locator('.few')).toBeHidden()
146
+ })
147
+
148
+ test('handles ordinal attribute correctly', async ({ page }) => {
149
+ // Use the existing #ordinal-test element from HTML
150
+ const ordinalElement = page.locator('#ordinal-test')
151
+ const countSpan = ordinalElement.locator('.count')
152
+
153
+ // Should start with count=1, showing "st" (first)
154
+ await expect(countSpan).toHaveText('1')
155
+ await expect(ordinalElement.locator('.some')).toBeVisible()
156
+ await expect(ordinalElement.locator('.none')).toBeHidden()
157
+ await expect(ordinalElement.locator('.one')).toBeVisible()
158
+ await expect(ordinalElement.locator('.other')).toBeHidden()
159
+
160
+ // Test count=2 (should show "nd" - second)
161
+ await page.evaluate(() => {
162
+ const element = document.querySelector('#ordinal-test') as any
163
+ element.setAttribute('count', '2')
164
+ })
165
+ await expect(countSpan).toHaveText('2')
166
+ await expect(ordinalElement.locator('.one')).toBeHidden()
167
+ await expect(ordinalElement.locator('.two')).toBeVisible()
168
+ await expect(ordinalElement.locator('.other')).toBeHidden()
169
+
170
+ // Test count=3 (should show "rd" - third)
171
+ await page.evaluate(() => {
172
+ const element = document.querySelector('#ordinal-test') as any
173
+ element.setAttribute('count', '3')
174
+ })
175
+ await expect(countSpan).toHaveText('3')
176
+ await expect(ordinalElement.locator('.two')).toBeHidden()
177
+ await expect(ordinalElement.locator('.few')).toBeVisible()
178
+ await expect(ordinalElement.locator('.other')).toBeHidden()
179
+
180
+ // Test count=4 (should show "th" - other)
181
+ await page.evaluate(() => {
182
+ const element = document.querySelector('#ordinal-test') as any
183
+ element.setAttribute('count', '4')
184
+ })
185
+ await expect(countSpan).toHaveText('4')
186
+ await expect(ordinalElement.locator('.few')).toBeHidden()
187
+ await expect(ordinalElement.locator('.other')).toBeVisible()
188
+ })
189
+
190
+ test('handles multiple instances with different counts', async ({ page }) => {
191
+ // Use the existing elements from HTML
192
+ const firstElement = page.locator('#pluralize-1')
193
+ const secondElement = page.locator('#pluralize-2')
194
+
195
+ // First element (count=0) - should show "No items"
196
+ await expect(firstElement.locator('.none')).toBeVisible()
197
+ await expect(firstElement.locator('.some')).toBeHidden()
198
+
199
+ // Second element (count=5) - should show "5 filter criteria" (other form)
200
+ await expect(secondElement.locator('.none')).toBeHidden()
201
+ await expect(secondElement.locator('.some')).toBeVisible()
202
+ await expect(secondElement.locator('.count')).toHaveText('5')
203
+ await expect(secondElement.locator('.other')).toBeVisible()
204
+ await expect(secondElement.locator('.one')).toBeHidden()
205
+ })
206
+
207
+ test('handles large counts correctly', async ({ page }) => {
208
+ await page.evaluate(() => {
209
+ const element = document.querySelector('basic-pluralize') as any
210
+ element.setAttribute('count', '1000')
211
+ })
212
+
213
+ const defaultElement = page.locator('basic-pluralize').first()
214
+ const noneElement = defaultElement.locator('.none')
215
+ const someElement = defaultElement.locator('.some')
216
+ const countSpan = defaultElement.locator('.count')
217
+
218
+ // Large count should show .some and hide .none
219
+ await expect(noneElement).toBeHidden()
220
+ await expect(someElement).toBeVisible()
221
+ await expect(countSpan).toHaveText('1000')
222
+ })
223
+
224
+ test('handles negative counts by falling back to 0', async ({ page }) => {
225
+ await page.evaluate(() => {
226
+ const element = document.querySelector('basic-pluralize') as any
227
+ element.setAttribute('count', '-5')
228
+ })
229
+
230
+ const defaultElement = page.locator('basic-pluralize').first()
231
+ const noneElement = defaultElement.locator('.none')
232
+ const someElement = defaultElement.locator('.some')
233
+ const countSpan = defaultElement.locator('.count')
234
+
235
+ // Negative count should be treated as 0: .none shows, .some hides
236
+ await expect(noneElement).toBeVisible()
237
+ await expect(someElement).toBeHidden()
238
+ await expect(countSpan).toHaveText('0')
239
+ })
240
+
241
+ test('handles non-numeric count gracefully', async ({ page }) => {
242
+ // Test with invalid count - asPositiveInteger should default to 0
243
+ await page.evaluate(() => {
244
+ const element = document.querySelector('basic-pluralize') as any
245
+ element.setAttribute('count', 'invalid')
246
+ })
247
+
248
+ const defaultElement = page.locator('basic-pluralize').first()
249
+ const noneElement = defaultElement.locator('.none')
250
+ const someElement = defaultElement.locator('.some')
251
+ const countSpan = defaultElement.locator('.count')
252
+
253
+ // Should default to 0, showing .none
254
+ await expect(noneElement).toBeVisible()
255
+ await expect(someElement).toBeHidden()
256
+ await expect(countSpan).toHaveText('0')
257
+ })
258
+ })