@vielzeug/craftit 2.0.1 → 2.1.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 (400) hide show
  1. package/README.md +71 -53
  2. package/dist/component.cjs +2 -0
  3. package/dist/component.cjs.map +1 -0
  4. package/dist/component.d.ts +39 -0
  5. package/dist/component.d.ts.map +1 -0
  6. package/dist/component.js +2 -0
  7. package/dist/component.js.map +1 -0
  8. package/dist/controls/a11y-control.cjs +2 -0
  9. package/dist/controls/a11y-control.cjs.map +1 -0
  10. package/dist/controls/a11y-control.d.ts +16 -0
  11. package/dist/controls/a11y-control.d.ts.map +1 -0
  12. package/dist/controls/a11y-control.js +2 -0
  13. package/dist/controls/a11y-control.js.map +1 -0
  14. package/dist/controls/checkable-control.cjs +2 -0
  15. package/dist/controls/checkable-control.cjs.map +1 -0
  16. package/dist/controls/checkable-control.d.ts +15 -0
  17. package/dist/controls/checkable-control.d.ts.map +1 -0
  18. package/dist/controls/checkable-control.js +2 -0
  19. package/dist/controls/checkable-control.js.map +1 -0
  20. package/dist/controls/field-control.cjs +2 -0
  21. package/dist/controls/field-control.cjs.map +1 -0
  22. package/dist/controls/field-control.d.ts +156 -0
  23. package/dist/controls/field-control.d.ts.map +1 -0
  24. package/dist/controls/field-control.js +2 -0
  25. package/dist/controls/field-control.js.map +1 -0
  26. package/dist/controls/index.d.ts +10 -0
  27. package/dist/controls/index.d.ts.map +1 -0
  28. package/dist/controls/internal/control-state.cjs +2 -0
  29. package/dist/controls/internal/control-state.cjs.map +1 -0
  30. package/dist/controls/internal/control-state.d.ts +19 -0
  31. package/dist/controls/internal/control-state.d.ts.map +1 -0
  32. package/dist/controls/internal/control-state.js +2 -0
  33. package/dist/controls/internal/control-state.js.map +1 -0
  34. package/dist/controls/internal/keyboard-utils.cjs +2 -0
  35. package/dist/controls/internal/keyboard-utils.cjs.map +1 -0
  36. package/dist/controls/internal/keyboard-utils.d.ts +7 -0
  37. package/dist/controls/internal/keyboard-utils.d.ts.map +1 -0
  38. package/dist/controls/internal/keyboard-utils.js +2 -0
  39. package/dist/controls/internal/keyboard-utils.js.map +1 -0
  40. package/dist/controls/internal/number-utils.cjs +2 -0
  41. package/dist/controls/internal/number-utils.cjs.map +1 -0
  42. package/dist/controls/internal/number-utils.d.ts +6 -0
  43. package/dist/controls/internal/number-utils.d.ts.map +1 -0
  44. package/dist/controls/internal/number-utils.js +2 -0
  45. package/dist/controls/internal/number-utils.js.map +1 -0
  46. package/dist/controls/internal/validation-utils.cjs +2 -0
  47. package/dist/controls/internal/validation-utils.cjs.map +1 -0
  48. package/dist/controls/internal/validation-utils.d.ts +13 -0
  49. package/dist/controls/internal/validation-utils.d.ts.map +1 -0
  50. package/dist/controls/internal/validation-utils.js +2 -0
  51. package/dist/controls/internal/validation-utils.js.map +1 -0
  52. package/dist/controls/list-control.cjs +2 -0
  53. package/dist/controls/list-control.cjs.map +1 -0
  54. package/dist/controls/list-control.d.ts +21 -0
  55. package/dist/controls/list-control.d.ts.map +1 -0
  56. package/dist/controls/list-control.js +2 -0
  57. package/dist/controls/list-control.js.map +1 -0
  58. package/dist/controls/list-key-control.cjs +2 -0
  59. package/dist/controls/list-key-control.cjs.map +1 -0
  60. package/dist/controls/list-key-control.d.ts +14 -0
  61. package/dist/controls/list-key-control.d.ts.map +1 -0
  62. package/dist/controls/list-key-control.js +2 -0
  63. package/dist/controls/list-key-control.js.map +1 -0
  64. package/dist/controls/overlay-control.cjs +2 -0
  65. package/dist/controls/overlay-control.cjs.map +1 -0
  66. package/dist/controls/overlay-control.d.ts +37 -0
  67. package/dist/controls/overlay-control.d.ts.map +1 -0
  68. package/dist/controls/overlay-control.js +2 -0
  69. package/dist/controls/overlay-control.js.map +1 -0
  70. package/dist/controls/press-control.cjs +2 -0
  71. package/dist/controls/press-control.cjs.map +1 -0
  72. package/dist/controls/press-control.d.ts +12 -0
  73. package/dist/controls/press-control.d.ts.map +1 -0
  74. package/dist/controls/press-control.js +2 -0
  75. package/dist/controls/press-control.js.map +1 -0
  76. package/dist/controls/slider-control.cjs +2 -0
  77. package/dist/controls/slider-control.cjs.map +1 -0
  78. package/dist/controls/slider-control.d.ts +19 -0
  79. package/dist/controls/slider-control.d.ts.map +1 -0
  80. package/dist/controls/slider-control.js +2 -0
  81. package/dist/controls/slider-control.js.map +1 -0
  82. package/dist/controls/spinner-control.cjs +2 -0
  83. package/dist/controls/spinner-control.cjs.map +1 -0
  84. package/dist/controls/spinner-control.d.ts +18 -0
  85. package/dist/controls/spinner-control.d.ts.map +1 -0
  86. package/dist/controls/spinner-control.js +2 -0
  87. package/dist/controls/spinner-control.js.map +1 -0
  88. package/dist/controls.cjs +1 -0
  89. package/dist/controls.js +1 -0
  90. package/dist/craftit.cjs +1 -1
  91. package/dist/craftit.cjs.map +1 -1
  92. package/dist/craftit.js +1 -1
  93. package/dist/craftit.js.map +1 -1
  94. package/dist/directives/attr.cjs +1 -1
  95. package/dist/directives/attr.cjs.map +1 -1
  96. package/dist/directives/attr.d.ts +4 -6
  97. package/dist/directives/attr.d.ts.map +1 -1
  98. package/dist/directives/attr.js +1 -1
  99. package/dist/directives/attr.js.map +1 -1
  100. package/dist/directives/bind.cjs +1 -1
  101. package/dist/directives/bind.cjs.map +1 -1
  102. package/dist/directives/bind.d.ts +20 -12
  103. package/dist/directives/bind.d.ts.map +1 -1
  104. package/dist/directives/bind.js +1 -1
  105. package/dist/directives/bind.js.map +1 -1
  106. package/dist/directives/choose.cjs +1 -1
  107. package/dist/directives/choose.cjs.map +1 -1
  108. package/dist/directives/choose.d.ts +17 -12
  109. package/dist/directives/choose.d.ts.map +1 -1
  110. package/dist/directives/choose.js +1 -1
  111. package/dist/directives/choose.js.map +1 -1
  112. package/dist/directives/each.cjs +1 -1
  113. package/dist/directives/each.cjs.map +1 -1
  114. package/dist/directives/each.d.ts +19 -31
  115. package/dist/directives/each.d.ts.map +1 -1
  116. package/dist/directives/each.js +1 -1
  117. package/dist/directives/each.js.map +1 -1
  118. package/dist/directives/index.d.ts +1 -2
  119. package/dist/directives/index.d.ts.map +1 -1
  120. package/dist/directives/memo.cjs +1 -1
  121. package/dist/directives/memo.cjs.map +1 -1
  122. package/dist/directives/memo.d.ts +8 -4
  123. package/dist/directives/memo.d.ts.map +1 -1
  124. package/dist/directives/memo.js +1 -1
  125. package/dist/directives/memo.js.map +1 -1
  126. package/dist/directives/on.cjs +1 -1
  127. package/dist/directives/on.cjs.map +1 -1
  128. package/dist/directives/on.d.ts +1 -1
  129. package/dist/directives/on.d.ts.map +1 -1
  130. package/dist/directives/on.js +1 -1
  131. package/dist/directives/on.js.map +1 -1
  132. package/dist/directives/raw.cjs +1 -1
  133. package/dist/directives/raw.cjs.map +1 -1
  134. package/dist/directives/raw.d.ts +1 -1
  135. package/dist/directives/raw.d.ts.map +1 -1
  136. package/dist/directives/raw.js +1 -1
  137. package/dist/directives/raw.js.map +1 -1
  138. package/dist/directives/spread.cjs +1 -1
  139. package/dist/directives/spread.cjs.map +1 -1
  140. package/dist/directives/spread.d.ts +1 -1
  141. package/dist/directives/spread.d.ts.map +1 -1
  142. package/dist/directives/spread.js +1 -1
  143. package/dist/directives/spread.js.map +1 -1
  144. package/dist/directives/style.cjs +1 -1
  145. package/dist/directives/style.cjs.map +1 -1
  146. package/dist/directives/style.js +1 -1
  147. package/dist/directives/style.js.map +1 -1
  148. package/dist/directives/until.cjs.map +1 -1
  149. package/dist/directives/until.d.ts +1 -1
  150. package/dist/directives/until.d.ts.map +1 -1
  151. package/dist/directives/until.js.map +1 -1
  152. package/dist/directives/when.cjs +1 -1
  153. package/dist/directives/when.cjs.map +1 -1
  154. package/dist/directives/when.d.ts +11 -5
  155. package/dist/directives/when.d.ts.map +1 -1
  156. package/dist/directives/when.js +1 -1
  157. package/dist/directives/when.js.map +1 -1
  158. package/dist/directives.cjs +1 -0
  159. package/dist/directives.js +1 -0
  160. package/dist/form.cjs +2 -0
  161. package/dist/form.cjs.map +1 -0
  162. package/dist/form.d.ts +29 -0
  163. package/dist/form.d.ts.map +1 -0
  164. package/dist/form.js +2 -0
  165. package/dist/form.js.map +1 -0
  166. package/dist/host.cjs +2 -0
  167. package/dist/host.cjs.map +1 -0
  168. package/dist/host.d.ts +75 -0
  169. package/dist/host.d.ts.map +1 -0
  170. package/dist/host.js +2 -0
  171. package/dist/host.js.map +1 -0
  172. package/dist/index.cjs +1 -1
  173. package/dist/index.d.ts +8 -9
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +1 -1
  176. package/dist/internal.cjs +2 -0
  177. package/dist/internal.cjs.map +1 -0
  178. package/dist/internal.d.ts +171 -0
  179. package/dist/internal.d.ts.map +1 -0
  180. package/dist/internal.js +2 -0
  181. package/dist/internal.js.map +1 -0
  182. package/dist/observers/index.d.ts +4 -0
  183. package/dist/observers/index.d.ts.map +1 -0
  184. package/dist/observers/intersection-observe.cjs +2 -0
  185. package/dist/observers/intersection-observe.cjs.map +1 -0
  186. package/dist/observers/intersection-observe.d.ts +9 -0
  187. package/dist/observers/intersection-observe.d.ts.map +1 -0
  188. package/dist/observers/intersection-observe.js +2 -0
  189. package/dist/observers/intersection-observe.js.map +1 -0
  190. package/dist/observers/media-observe.cjs +2 -0
  191. package/dist/observers/media-observe.cjs.map +1 -0
  192. package/dist/observers/media-observe.d.ts +8 -0
  193. package/dist/observers/media-observe.d.ts.map +1 -0
  194. package/dist/observers/media-observe.js +2 -0
  195. package/dist/observers/media-observe.js.map +1 -0
  196. package/dist/observers/resize-observe.cjs +2 -0
  197. package/dist/observers/resize-observe.cjs.map +1 -0
  198. package/dist/observers/resize-observe.d.ts +11 -0
  199. package/dist/observers/resize-observe.d.ts.map +1 -0
  200. package/dist/observers/resize-observe.js +2 -0
  201. package/dist/observers/resize-observe.js.map +1 -0
  202. package/dist/observers.cjs +1 -0
  203. package/dist/observers.js +1 -0
  204. package/dist/props.cjs +2 -0
  205. package/dist/props.cjs.map +1 -0
  206. package/dist/props.d.ts +52 -0
  207. package/dist/props.d.ts.map +1 -0
  208. package/dist/props.js +2 -0
  209. package/dist/props.js.map +1 -0
  210. package/dist/registration.cjs +2 -0
  211. package/dist/registration.cjs.map +1 -0
  212. package/dist/registration.d.ts +18 -0
  213. package/dist/registration.d.ts.map +1 -0
  214. package/dist/registration.js +2 -0
  215. package/dist/registration.js.map +1 -0
  216. package/dist/runtime-bindings.cjs +2 -0
  217. package/dist/runtime-bindings.cjs.map +1 -0
  218. package/dist/runtime-bindings.d.ts.map +1 -0
  219. package/dist/runtime-bindings.js +2 -0
  220. package/dist/runtime-bindings.js.map +1 -0
  221. package/dist/runtime-core.cjs +2 -0
  222. package/dist/runtime-core.cjs.map +1 -0
  223. package/dist/runtime-core.d.ts +21 -0
  224. package/dist/runtime-core.d.ts.map +1 -0
  225. package/dist/runtime-core.js +2 -0
  226. package/dist/runtime-core.js.map +1 -0
  227. package/dist/runtime-lifecycle.cjs +2 -0
  228. package/dist/runtime-lifecycle.cjs.map +1 -0
  229. package/dist/runtime-lifecycle.d.ts +24 -0
  230. package/dist/runtime-lifecycle.d.ts.map +1 -0
  231. package/dist/runtime-lifecycle.js +2 -0
  232. package/dist/runtime-lifecycle.js.map +1 -0
  233. package/dist/runtime.cjs +2 -0
  234. package/dist/runtime.cjs.map +1 -0
  235. package/dist/runtime.d.ts +21 -0
  236. package/dist/runtime.d.ts.map +1 -0
  237. package/dist/runtime.js +2 -0
  238. package/dist/runtime.js.map +1 -0
  239. package/dist/template-bindings.cjs +2 -0
  240. package/dist/template-bindings.cjs.map +1 -0
  241. package/dist/{core/template-bindings.d.ts → template-bindings.d.ts} +4 -1
  242. package/dist/template-bindings.d.ts.map +1 -0
  243. package/dist/template-bindings.js +2 -0
  244. package/dist/template-bindings.js.map +1 -0
  245. package/dist/template-compiler.cjs +2 -0
  246. package/dist/template-compiler.cjs.map +1 -0
  247. package/dist/{core/template-compiler.d.ts → template-compiler.d.ts} +1 -2
  248. package/dist/template-compiler.d.ts.map +1 -0
  249. package/dist/template-compiler.js +2 -0
  250. package/dist/template-compiler.js.map +1 -0
  251. package/dist/template-dom.cjs +2 -0
  252. package/dist/{core/template-dom.js.map → template-dom.cjs.map} +1 -1
  253. package/dist/template-dom.d.ts.map +1 -0
  254. package/dist/template-dom.js +2 -0
  255. package/dist/template-dom.js.map +1 -0
  256. package/dist/template-html.cjs +2 -0
  257. package/dist/template-html.cjs.map +1 -0
  258. package/dist/{core/template-html.d.ts → template-html.d.ts} +1 -4
  259. package/dist/template-html.d.ts.map +1 -0
  260. package/dist/template-html.js +2 -0
  261. package/dist/template-html.js.map +1 -0
  262. package/dist/template.cjs +2 -0
  263. package/dist/template.cjs.map +1 -0
  264. package/dist/{core/template.d.ts → template.d.ts} +2 -3
  265. package/dist/template.d.ts.map +1 -0
  266. package/dist/template.js +2 -0
  267. package/dist/template.js.map +1 -0
  268. package/dist/testing/index.d.ts +2 -0
  269. package/dist/testing/index.d.ts.map +1 -0
  270. package/dist/testing/testing.cjs +2 -0
  271. package/dist/testing/testing.cjs.map +1 -0
  272. package/dist/{test/test.d.ts → testing/testing.d.ts} +8 -8
  273. package/dist/testing/testing.d.ts.map +1 -0
  274. package/dist/testing/testing.js +2 -0
  275. package/dist/testing/testing.js.map +1 -0
  276. package/dist/testing.cjs +1 -0
  277. package/dist/testing.js +1 -0
  278. package/package.json +19 -14
  279. package/dist/core/component.cjs +0 -2
  280. package/dist/core/component.cjs.map +0 -1
  281. package/dist/core/component.d.ts +0 -172
  282. package/dist/core/component.d.ts.map +0 -1
  283. package/dist/core/component.js +0 -2
  284. package/dist/core/component.js.map +0 -1
  285. package/dist/core/host.cjs +0 -2
  286. package/dist/core/host.cjs.map +0 -1
  287. package/dist/core/host.d.ts +0 -77
  288. package/dist/core/host.d.ts.map +0 -1
  289. package/dist/core/host.js +0 -2
  290. package/dist/core/host.js.map +0 -1
  291. package/dist/core/internal.cjs +0 -2
  292. package/dist/core/internal.cjs.map +0 -1
  293. package/dist/core/internal.d.ts +0 -107
  294. package/dist/core/internal.d.ts.map +0 -1
  295. package/dist/core/internal.js +0 -2
  296. package/dist/core/internal.js.map +0 -1
  297. package/dist/core/runtime-bindings.cjs +0 -2
  298. package/dist/core/runtime-bindings.cjs.map +0 -1
  299. package/dist/core/runtime-bindings.d.ts.map +0 -1
  300. package/dist/core/runtime-bindings.js +0 -2
  301. package/dist/core/runtime-bindings.js.map +0 -1
  302. package/dist/core/runtime-lifecycle.cjs +0 -2
  303. package/dist/core/runtime-lifecycle.cjs.map +0 -1
  304. package/dist/core/runtime-lifecycle.d.ts +0 -116
  305. package/dist/core/runtime-lifecycle.d.ts.map +0 -1
  306. package/dist/core/runtime-lifecycle.js +0 -2
  307. package/dist/core/runtime-lifecycle.js.map +0 -1
  308. package/dist/core/runtime.cjs +0 -1
  309. package/dist/core/runtime.d.ts +0 -3
  310. package/dist/core/runtime.d.ts.map +0 -1
  311. package/dist/core/runtime.js +0 -1
  312. package/dist/core/template-bindings.cjs +0 -2
  313. package/dist/core/template-bindings.cjs.map +0 -1
  314. package/dist/core/template-bindings.d.ts.map +0 -1
  315. package/dist/core/template-bindings.js +0 -2
  316. package/dist/core/template-bindings.js.map +0 -1
  317. package/dist/core/template-compiler.cjs +0 -2
  318. package/dist/core/template-compiler.cjs.map +0 -1
  319. package/dist/core/template-compiler.d.ts.map +0 -1
  320. package/dist/core/template-compiler.js +0 -2
  321. package/dist/core/template-compiler.js.map +0 -1
  322. package/dist/core/template-dom.cjs +0 -2
  323. package/dist/core/template-dom.cjs.map +0 -1
  324. package/dist/core/template-dom.d.ts.map +0 -1
  325. package/dist/core/template-dom.js +0 -2
  326. package/dist/core/template-html.cjs +0 -2
  327. package/dist/core/template-html.cjs.map +0 -1
  328. package/dist/core/template-html.d.ts.map +0 -1
  329. package/dist/core/template-html.js +0 -2
  330. package/dist/core/template-html.js.map +0 -1
  331. package/dist/core/template.cjs +0 -2
  332. package/dist/core/template.cjs.map +0 -1
  333. package/dist/core/template.d.ts.map +0 -1
  334. package/dist/core/template.js +0 -2
  335. package/dist/core/template.js.map +0 -1
  336. package/dist/core/utilities.cjs +0 -2
  337. package/dist/core/utilities.cjs.map +0 -1
  338. package/dist/core/utilities.d.ts +0 -68
  339. package/dist/core/utilities.d.ts.map +0 -1
  340. package/dist/core/utilities.js +0 -2
  341. package/dist/core/utilities.js.map +0 -1
  342. package/dist/directives/index.cjs +0 -1
  343. package/dist/directives/index.js +0 -1
  344. package/dist/directives/match.cjs +0 -2
  345. package/dist/directives/match.cjs.map +0 -1
  346. package/dist/directives/match.d.ts +0 -31
  347. package/dist/directives/match.d.ts.map +0 -1
  348. package/dist/directives/match.js +0 -2
  349. package/dist/directives/match.js.map +0 -1
  350. package/dist/labs/a11y.cjs +0 -2
  351. package/dist/labs/a11y.cjs.map +0 -1
  352. package/dist/labs/a11y.d.ts +0 -61
  353. package/dist/labs/a11y.d.ts.map +0 -1
  354. package/dist/labs/a11y.js +0 -2
  355. package/dist/labs/a11y.js.map +0 -1
  356. package/dist/labs/index.d.ts +0 -8
  357. package/dist/labs/index.d.ts.map +0 -1
  358. package/dist/labs/list.cjs +0 -2
  359. package/dist/labs/list.cjs.map +0 -1
  360. package/dist/labs/list.d.ts +0 -26
  361. package/dist/labs/list.d.ts.map +0 -1
  362. package/dist/labs/list.js +0 -2
  363. package/dist/labs/list.js.map +0 -1
  364. package/dist/labs/observers.cjs +0 -2
  365. package/dist/labs/observers.cjs.map +0 -1
  366. package/dist/labs/observers.d.ts +0 -42
  367. package/dist/labs/observers.d.ts.map +0 -1
  368. package/dist/labs/observers.js +0 -2
  369. package/dist/labs/observers.js.map +0 -1
  370. package/dist/labs/overlay.cjs +0 -2
  371. package/dist/labs/overlay.cjs.map +0 -1
  372. package/dist/labs/overlay.d.ts +0 -35
  373. package/dist/labs/overlay.d.ts.map +0 -1
  374. package/dist/labs/overlay.js +0 -2
  375. package/dist/labs/overlay.js.map +0 -1
  376. package/dist/labs/selectable.cjs +0 -2
  377. package/dist/labs/selectable.cjs.map +0 -1
  378. package/dist/labs/selectable.d.ts +0 -70
  379. package/dist/labs/selectable.d.ts.map +0 -1
  380. package/dist/labs/selectable.js +0 -2
  381. package/dist/labs/selectable.js.map +0 -1
  382. package/dist/labs/selection.cjs +0 -2
  383. package/dist/labs/selection.cjs.map +0 -1
  384. package/dist/labs/selection.d.ts +0 -68
  385. package/dist/labs/selection.d.ts.map +0 -1
  386. package/dist/labs/selection.js +0 -2
  387. package/dist/labs/selection.js.map +0 -1
  388. package/dist/labs.cjs +0 -1
  389. package/dist/labs.js +0 -1
  390. package/dist/test/index.d.ts +0 -2
  391. package/dist/test/index.d.ts.map +0 -1
  392. package/dist/test/test.cjs +0 -2
  393. package/dist/test/test.cjs.map +0 -1
  394. package/dist/test/test.d.ts.map +0 -1
  395. package/dist/test/test.js +0 -2
  396. package/dist/test/test.js.map +0 -1
  397. package/dist/test.cjs +0 -1
  398. package/dist/test.js +0 -1
  399. /package/dist/{core/runtime-bindings.d.ts → runtime-bindings.d.ts} +0 -0
  400. /package/dist/{core/template-dom.d.ts → template-dom.d.ts} +0 -0
@@ -1,2 +1,2 @@
1
- require(`../core/internal.cjs`);const e=require(`./spread.cjs`);require(`@vielzeug/stateit`);function t(t){let n={};for(let[e,r]of Object.entries(t))n[`.${e}`]=r;return e.spread(n)}exports.attr=t;
1
+ require(`../internal.cjs`);const e=require(`./spread.cjs`);require(`@vielzeug/stateit`);function t(t){let n={};for(let[e,r]of Object.entries(t))n[`.${e}`]=r;return e.spread(n)}exports.attrs=t;
2
2
  //# sourceMappingURL=attr.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"attr.cjs","names":[],"sources":["../../src/directives/attr.ts"],"sourcesContent":["import { type ReadonlySignal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../core/internal';\nimport { spread } from './spread';\n\nexport type AttrValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<string | number | boolean | null | undefined>\n | (() => string | number | boolean | null | undefined);\n\n/**\n * Batch DOM property-binding helper.\n *\n * The public name stays `attr(...)` for ergonomics, but the mapped entries are\n * applied as `.property` bindings under the hood.\n *\n * @example\n * html`<input ${attr({ value, disabled, readOnly: readonly })} />`\n */\nexport function attr(map: Record<string, AttrValue>): Directive {\n const mapped: Record<string, AttrValue> = {};\n\n for (const [key, value] of Object.entries(map)) {\n mapped[`.${key}`] = value;\n }\n\n return spread(mapped);\n}\n"],"mappings":"6FAuBA,SAAgB,EAAK,EAA2C,CAC9D,IAAM,EAAoC,EAAE,CAE5C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAI,CAC5C,EAAO,IAAI,KAAS,EAGtB,OAAO,EAAA,OAAO,EAAO"}
1
+ {"version":3,"file":"attr.cjs","names":[],"sources":["../../src/directives/attr.ts"],"sourcesContent":["import { type ReadonlySignal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../internal';\nimport { spread } from './spread';\n\nexport type AttrValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<string | number | boolean | null | undefined>\n | (() => string | number | boolean | null | undefined);\n\n/**\n * Batch DOM property-binding helper.\n * Applied as `.property` bindings under the hood.\n *\n * @example\n * html`<input ${attrs({ value, disabled, readOnly: readonly })} />`\n */\nexport function attrs(map: Record<string, AttrValue>): Directive {\n const mapped: Record<string, AttrValue> = {};\n\n for (const [key, value] of Object.entries(map)) {\n mapped[`.${key}`] = value;\n }\n\n return spread(mapped);\n}\n"],"mappings":"wFAqBA,SAAgB,EAAM,EAA2C,CAC/D,IAAM,EAAoC,EAAE,CAE5C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAI,CAC5C,EAAO,IAAI,KAAS,EAGtB,OAAO,EAAA,OAAO,EAAO"}
@@ -1,14 +1,12 @@
1
1
  import { type ReadonlySignal } from '@vielzeug/stateit';
2
- import { type Directive } from '../core/internal';
2
+ import { type Directive } from '../internal';
3
3
  export type AttrValue = string | number | boolean | null | undefined | ReadonlySignal<string | number | boolean | null | undefined> | (() => string | number | boolean | null | undefined);
4
4
  /**
5
5
  * Batch DOM property-binding helper.
6
- *
7
- * The public name stays `attr(...)` for ergonomics, but the mapped entries are
8
- * applied as `.property` bindings under the hood.
6
+ * Applied as `.property` bindings under the hood.
9
7
  *
10
8
  * @example
11
- * html`<input ${attr({ value, disabled, readOnly: readonly })} />`
9
+ * html`<input ${attrs({ value, disabled, readOnly: readonly })} />`
12
10
  */
13
- export declare function attr(map: Record<string, AttrValue>): Directive;
11
+ export declare function attrs(map: Record<string, AttrValue>): Directive;
14
12
  //# sourceMappingURL=attr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attr.d.ts","sourceRoot":"","sources":["../../src/directives/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,cAAc,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,GAC5D,CAAC,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS,CAQ9D"}
1
+ {"version":3,"file":"attr.d.ts","sourceRoot":"","sources":["../../src/directives/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,GACT,cAAc,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,GAC5D,CAAC,MAAM,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,SAAS,CAQ/D"}
@@ -1,2 +1,2 @@
1
- import"../core/internal.js";import{spread as e}from"./spread.js";import"@vielzeug/stateit";function t(t){let n={};for(let[e,r]of Object.entries(t))n[`.${e}`]=r;return e(n)}export{t as attr};
1
+ import"../internal.js";import{spread as e}from"./spread.js";import"@vielzeug/stateit";function t(t){let n={};for(let[e,r]of Object.entries(t))n[`.${e}`]=r;return e(n)}export{t as attrs};
2
2
  //# sourceMappingURL=attr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"attr.js","names":[],"sources":["../../src/directives/attr.ts"],"sourcesContent":["import { type ReadonlySignal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../core/internal';\nimport { spread } from './spread';\n\nexport type AttrValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<string | number | boolean | null | undefined>\n | (() => string | number | boolean | null | undefined);\n\n/**\n * Batch DOM property-binding helper.\n *\n * The public name stays `attr(...)` for ergonomics, but the mapped entries are\n * applied as `.property` bindings under the hood.\n *\n * @example\n * html`<input ${attr({ value, disabled, readOnly: readonly })} />`\n */\nexport function attr(map: Record<string, AttrValue>): Directive {\n const mapped: Record<string, AttrValue> = {};\n\n for (const [key, value] of Object.entries(map)) {\n mapped[`.${key}`] = value;\n }\n\n return spread(mapped);\n}\n"],"mappings":"2FAuBA,SAAgB,EAAK,EAA2C,CAC9D,IAAM,EAAoC,EAAE,CAE5C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAI,CAC5C,EAAO,IAAI,KAAS,EAGtB,OAAO,EAAO,EAAO"}
1
+ {"version":3,"file":"attr.js","names":[],"sources":["../../src/directives/attr.ts"],"sourcesContent":["import { type ReadonlySignal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../internal';\nimport { spread } from './spread';\n\nexport type AttrValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlySignal<string | number | boolean | null | undefined>\n | (() => string | number | boolean | null | undefined);\n\n/**\n * Batch DOM property-binding helper.\n * Applied as `.property` bindings under the hood.\n *\n * @example\n * html`<input ${attrs({ value, disabled, readOnly: readonly })} />`\n */\nexport function attrs(map: Record<string, AttrValue>): Directive {\n const mapped: Record<string, AttrValue> = {};\n\n for (const [key, value] of Object.entries(map)) {\n mapped[`.${key}`] = value;\n }\n\n return spread(mapped);\n}\n"],"mappings":"sFAqBA,SAAgB,EAAM,EAA2C,CAC/D,IAAM,EAAoC,EAAE,CAE5C,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,EAAI,CAC5C,EAAO,IAAI,KAAS,EAGtB,OAAO,EAAO,EAAO"}
@@ -1,2 +1,2 @@
1
- require(`../core/internal.cjs`);const e=require(`./attr.cjs`);require(`@vielzeug/stateit`);function t(t){return{mount(n,r){let i=n;(i.type===`checkbox`||i.type===`radio`?e.attr({checked:t}):e.attr({value:t})).mount(n,r)}}}exports.bind=t;
1
+ const e=require(`../internal.cjs`),t=require(`./attr.cjs`);require(`@vielzeug/stateit`);var n=e=>{if(typeof e!=`object`||!e||!(`value`in e))return!1;let t=e.value;return typeof t==`object`&&!!t&&`value`in t},r=(e,t)=>t||(e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`)?`checked`:`value`),i=(e,t,n)=>n||(t===`checked`||e instanceof HTMLSelectElement?`change`:`input`),a=(e,t)=>{if(t===`checked`){if(!(e instanceof HTMLInputElement)||e.type!==`checkbox`&&e.type!==`radio`)throw Error(`[craftit:bind] mode "checked" requires <input type="checkbox|radio">.`);return}if(!(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement))throw Error(`[craftit:bind] value binding requires <input>, <textarea>, or <select>.`)},o=e=>{if(e.event!==void 0&&e.event.trim()===``)throw Error(`[craftit:bind] options.event must be a non-empty event name.`);if(e.parse!==void 0&&typeof e.parse!=`function`)throw Error(`[craftit:bind] options.parse must be a function when provided.`)},s=(e,t,n,r)=>{let i=t===`checked`?e.checked:e.value;return n.parse?n.parse(i,e,r):t===`checked`?i:typeof n.value.value==`number`?Number(i):i};function c(c){return{mount(l,u){let d=n(c)?c:{value:c};o(d);let f=r(l,d.as);a(l,f);let p=i(l,f,d.event);t.attrs(f===`checked`?{checked:d.value}:{value:d.value}).mount(l,u),u.registerCleanup(e.listen(l,p,e=>{let t=s(l,f,d,e);Object.is(d.value.value,t)||(d.value.value=t)}))}}}exports.bind=c;
2
2
  //# sourceMappingURL=bind.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bind.cjs","names":[],"sources":["../../src/directives/bind.ts"],"sourcesContent":["import { type Signal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../core/internal';\nimport { attr } from './attr';\n\n/**\n * Creates a two-way binding between a writable Signal and a form element.\n *\n * - **`Signal<string>`** (`<input>`, `<textarea>`): syncs `.value` via the `input` event.\n * - **`Signal<string>`** (`<select>`): syncs `.value` via the `change` event.\n * - **`Signal<boolean>`** (checkbox/radio): auto-detected from the element's `type` attribute;\n * syncs `.checked` via the `change` event.\n *\n * Internally this reuses craftit's shared property-binding path, so `.value`,\n * `.checked`, `attr(...)`, and `spread({ '.value': ... })` stay in sync.\n *\n * Use in spread position on any form element.\n *\n * @example\n * import { bind } from '@vielzeug/craftit/directives';\n *\n * const name = signal('');\n * html`<input ${bind(name)} />`\n *\n * const accepted = signal(false);\n * html`<input type=\"checkbox\" ${bind(accepted)} />`\n *\n * const size = signal('');\n * html`<select ${bind(size)}><option value=\"sm\">S</option></select>`\n */\nexport function bind(sig: Signal<boolean>): Directive;\nexport function bind(sig: Signal<string>): Directive;\nexport function bind(sig: Signal<boolean> | Signal<string>): Directive {\n return {\n mount(el, context) {\n const input = el as HTMLInputElement;\n const isToggle = input.type === 'checkbox' || input.type === 'radio';\n\n (isToggle ? attr({ checked: sig as Signal<boolean> }) : attr({ value: sig as Signal<string> })).mount!(\n el,\n context,\n );\n },\n };\n}\n"],"mappings":"2FAgCA,SAAgB,EAAK,EAAkD,CACrE,MAAO,CACL,MAAM,EAAI,EAAS,CACjB,IAAM,EAAQ,GACG,EAAM,OAAS,YAAc,EAAM,OAAS,QAEjD,EAAA,KAAK,CAAE,QAAS,EAAwB,CAAC,CAAG,EAAA,KAAK,CAAE,MAAO,EAAuB,CAAC,EAAE,MAC9F,EACA,EACD,EAEJ"}
1
+ {"version":3,"file":"bind.cjs","names":[],"sources":["../../src/directives/bind.ts"],"sourcesContent":["import { type Signal } from '@vielzeug/stateit';\n\nimport { type Directive, listen } from '../internal';\nimport { attrs } from './attr';\n\nexport type BindMode = 'checked' | 'value';\n\nexport type BindOptions<T extends boolean | number | string> = {\n as?: BindMode;\n event?: string;\n parse?: (value: boolean | string, element: HTMLElement, event: Event) => T;\n value: Signal<T>;\n};\n\nconst isBindOptions = <T extends boolean | number | string>(\n input: Signal<T> | BindOptions<T>,\n): input is BindOptions<T> => {\n if (typeof input !== 'object' || input === null || !('value' in input)) return false;\n\n const candidate = (input as BindOptions<T>).value as unknown;\n\n return typeof candidate === 'object' && candidate !== null && 'value' in (candidate as object);\n};\n\nconst resolveMode = (element: HTMLElement, explicitMode?: BindMode): BindMode => {\n if (explicitMode) return explicitMode;\n\n if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {\n return 'checked';\n }\n\n return 'value';\n};\n\nconst resolveEventName = (element: HTMLElement, mode: BindMode, explicitEvent?: string): string => {\n if (explicitEvent) return explicitEvent;\n\n if (mode === 'checked' || element instanceof HTMLSelectElement) return 'change';\n\n return 'input';\n};\n\nconst assertBindableTarget = (element: HTMLElement, mode: BindMode): void => {\n if (mode === 'checked') {\n if (!(element instanceof HTMLInputElement) || (element.type !== 'checkbox' && element.type !== 'radio')) {\n throw new Error('[craftit:bind] mode \"checked\" requires <input type=\"checkbox|radio\">.');\n }\n\n return;\n }\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLTextAreaElement ||\n element instanceof HTMLSelectElement\n )\n ) {\n throw new Error('[craftit:bind] value binding requires <input>, <textarea>, or <select>.');\n }\n};\n\nconst validateBindOptions = <T extends boolean | number | string>(options: BindOptions<T>): void => {\n if (options.event !== undefined && options.event.trim() === '') {\n throw new Error('[craftit:bind] options.event must be a non-empty event name.');\n }\n\n if (options.parse !== undefined && typeof options.parse !== 'function') {\n throw new Error('[craftit:bind] options.parse must be a function when provided.');\n }\n};\n\nconst readBoundValue = <T extends boolean | number | string>(\n element: HTMLElement,\n mode: BindMode,\n options: BindOptions<T>,\n event: Event,\n): T => {\n const raw = mode === 'checked' ? (element as HTMLInputElement).checked : (element as HTMLInputElement).value;\n\n if (options.parse) return options.parse(raw, element, event);\n\n if (mode === 'checked') return raw as T;\n\n if (typeof options.value.value === 'number') return Number(raw) as T;\n\n return raw as T;\n};\n\n/**\n * Creates a two-way binding between a writable Signal and a form element.\n *\n * Defaults:\n * - Checkbox/radio elements bind `.checked` via `change`\n * - Other form elements bind `.value` via `input`\n * - Select elements bind `.value` via `change`\n * - Number signals default to `Number(el.value)` unless you provide `parse`\n *\n * Internally this reuses craftit's shared property-binding path, so `.value`,\n * `.checked`, `attrs(...)`, and `spread({ '.value': ... })` stay in sync.\n *\n * Use in spread position on any form element.\n *\n * @example\n * import { bind } from '@vielzeug/craftit/directives';\n *\n * const name = signal('');\n * html`<input ${bind({ value: name })} />`\n *\n * const accepted = signal(false);\n * html`<input type=\"checkbox\" ${bind({ value: accepted })} />`\n *\n * const amount = signal(1);\n * html`<input type=\"number\" ${bind({ value: amount })} />`\n */\nexport function bind<T extends boolean | number | string>(value: Signal<T>): Directive;\nexport function bind<T extends boolean | number | string>(options: BindOptions<T>): Directive;\nexport function bind<T extends boolean | number | string>(input: Signal<T> | BindOptions<T>): Directive {\n return {\n mount(el, context) {\n const options = isBindOptions(input) ? input : ({ value: input } as BindOptions<T>);\n\n validateBindOptions(options);\n\n const mode = resolveMode(el, options.as);\n\n assertBindableTarget(el, mode);\n\n const eventName = resolveEventName(el, mode, options.event);\n\n attrs(mode === 'checked' ? { checked: options.value } : { value: options.value }).mount!(el, context);\n\n context.registerCleanup(\n listen(el, eventName, (event: Event) => {\n const next = readBoundValue(el, mode, options, event);\n\n if (!Object.is(options.value.value, next)) options.value.value = next;\n }),\n );\n },\n };\n}\n"],"mappings":"wFAcA,IAAM,EACJ,GAC4B,CAC5B,GAAI,OAAO,GAAU,WAAY,GAAkB,EAAE,UAAW,GAAQ,MAAO,GAE/E,IAAM,EAAa,EAAyB,MAE5C,OAAO,OAAO,GAAc,YAAY,GAAsB,UAAY,GAGtE,GAAe,EAAsB,IACrC,IAEA,aAAmB,mBAAqB,EAAQ,OAAS,YAAc,EAAQ,OAAS,SACnF,UAGF,SAGH,GAAoB,EAAsB,EAAgB,IAC1D,IAEA,IAAS,WAAa,aAAmB,kBAA0B,SAEhE,SAGH,GAAwB,EAAsB,IAAyB,CAC3E,GAAI,IAAS,UAAW,CACtB,GAAI,EAAE,aAAmB,mBAAsB,EAAQ,OAAS,YAAc,EAAQ,OAAS,QAC7F,MAAU,MAAM,wEAAwE,CAG1F,OAGF,GACE,EACE,aAAmB,kBACnB,aAAmB,qBACnB,aAAmB,mBAGrB,MAAU,MAAM,0EAA0E,EAIxF,EAA4D,GAAkC,CAClG,GAAI,EAAQ,QAAU,IAAA,IAAa,EAAQ,MAAM,MAAM,GAAK,GAC1D,MAAU,MAAM,+DAA+D,CAGjF,GAAI,EAAQ,QAAU,IAAA,IAAa,OAAO,EAAQ,OAAU,WAC1D,MAAU,MAAM,iEAAiE,EAI/E,GACJ,EACA,EACA,EACA,IACM,CACN,IAAM,EAAM,IAAS,UAAa,EAA6B,QAAW,EAA6B,MAQvG,OANI,EAAQ,MAAc,EAAQ,MAAM,EAAK,EAAS,EAAM,CAExD,IAAS,UAAkB,EAE3B,OAAO,EAAQ,MAAM,OAAU,SAAiB,OAAO,EAAI,CAExD,GA+BT,SAAgB,EAA0C,EAA8C,CACtG,MAAO,CACL,MAAM,EAAI,EAAS,CACjB,IAAM,EAAU,EAAc,EAAM,CAAG,EAAS,CAAE,MAAO,EAAO,CAEhE,EAAoB,EAAQ,CAE5B,IAAM,EAAO,EAAY,EAAI,EAAQ,GAAG,CAExC,EAAqB,EAAI,EAAK,CAE9B,IAAM,EAAY,EAAiB,EAAI,EAAM,EAAQ,MAAM,CAE3D,EAAA,MAAM,IAAS,UAAY,CAAE,QAAS,EAAQ,MAAO,CAAG,CAAE,MAAO,EAAQ,MAAO,CAAC,CAAC,MAAO,EAAI,EAAQ,CAErG,EAAQ,gBACN,EAAA,OAAO,EAAI,EAAY,GAAiB,CACtC,IAAM,EAAO,EAAe,EAAI,EAAM,EAAS,EAAM,CAEhD,OAAO,GAAG,EAAQ,MAAM,MAAO,EAAK,GAAE,EAAQ,MAAM,MAAQ,IACjE,CACH,EAEJ"}
@@ -1,15 +1,23 @@
1
1
  import { type Signal } from '@vielzeug/stateit';
2
- import { type Directive } from '../core/internal';
2
+ import { type Directive } from '../internal';
3
+ export type BindMode = 'checked' | 'value';
4
+ export type BindOptions<T extends boolean | number | string> = {
5
+ as?: BindMode;
6
+ event?: string;
7
+ parse?: (value: boolean | string, element: HTMLElement, event: Event) => T;
8
+ value: Signal<T>;
9
+ };
3
10
  /**
4
11
  * Creates a two-way binding between a writable Signal and a form element.
5
12
  *
6
- * - **`Signal<string>`** (`<input>`, `<textarea>`): syncs `.value` via the `input` event.
7
- * - **`Signal<string>`** (`<select>`): syncs `.value` via the `change` event.
8
- * - **`Signal<boolean>`** (checkbox/radio): auto-detected from the element's `type` attribute;
9
- * syncs `.checked` via the `change` event.
13
+ * Defaults:
14
+ * - Checkbox/radio elements bind `.checked` via `change`
15
+ * - Other form elements bind `.value` via `input`
16
+ * - Select elements bind `.value` via `change`
17
+ * - Number signals default to `Number(el.value)` unless you provide `parse`
10
18
  *
11
19
  * Internally this reuses craftit's shared property-binding path, so `.value`,
12
- * `.checked`, `attr(...)`, and `spread({ '.value': ... })` stay in sync.
20
+ * `.checked`, `attrs(...)`, and `spread({ '.value': ... })` stay in sync.
13
21
  *
14
22
  * Use in spread position on any form element.
15
23
  *
@@ -17,14 +25,14 @@ import { type Directive } from '../core/internal';
17
25
  * import { bind } from '@vielzeug/craftit/directives';
18
26
  *
19
27
  * const name = signal('');
20
- * html`<input ${bind(name)} />`
28
+ * html`<input ${bind({ value: name })} />`
21
29
  *
22
30
  * const accepted = signal(false);
23
- * html`<input type="checkbox" ${bind(accepted)} />`
31
+ * html`<input type="checkbox" ${bind({ value: accepted })} />`
24
32
  *
25
- * const size = signal('');
26
- * html`<select ${bind(size)}><option value="sm">S</option></select>`
33
+ * const amount = signal(1);
34
+ * html`<input type="number" ${bind({ value: amount })} />`
27
35
  */
28
- export declare function bind(sig: Signal<boolean>): Directive;
29
- export declare function bind(sig: Signal<string>): Directive;
36
+ export declare function bind<T extends boolean | number | string>(value: Signal<T>): Directive;
37
+ export declare function bind<T extends boolean | number | string>(options: BindOptions<T>): Directive;
30
38
  //# sourceMappingURL=bind.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/directives/bind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;AACtD,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../src/directives/bind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,aAAa,CAAC;AAGrD,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAE3C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,MAAM,GAAG,MAAM,IAAI;IAC7D,EAAE,CAAC,EAAE,QAAQ,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,KAAK,CAAC,CAAC;IAC3E,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;CAClB,CAAC;AA6EF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AACvF,wBAAgB,IAAI,CAAC,CAAC,SAAS,OAAO,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC"}
@@ -1,2 +1,2 @@
1
- import"../core/internal.js";import{attr as e}from"./attr.js";import"@vielzeug/stateit";function t(t){return{mount(n,r){let i=n;(i.type===`checkbox`||i.type===`radio`?e({checked:t}):e({value:t})).mount(n,r)}}}export{t as bind};
1
+ import{listen as e}from"../internal.js";import{attrs as t}from"./attr.js";import"@vielzeug/stateit";var n=e=>{if(typeof e!=`object`||!e||!(`value`in e))return!1;let t=e.value;return typeof t==`object`&&!!t&&`value`in t},r=(e,t)=>t||(e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`)?`checked`:`value`),i=(e,t,n)=>n||(t===`checked`||e instanceof HTMLSelectElement?`change`:`input`),a=(e,t)=>{if(t===`checked`){if(!(e instanceof HTMLInputElement)||e.type!==`checkbox`&&e.type!==`radio`)throw Error(`[craftit:bind] mode "checked" requires <input type="checkbox|radio">.`);return}if(!(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement))throw Error(`[craftit:bind] value binding requires <input>, <textarea>, or <select>.`)},o=e=>{if(e.event!==void 0&&e.event.trim()===``)throw Error(`[craftit:bind] options.event must be a non-empty event name.`);if(e.parse!==void 0&&typeof e.parse!=`function`)throw Error(`[craftit:bind] options.parse must be a function when provided.`)},s=(e,t,n,r)=>{let i=t===`checked`?e.checked:e.value;return n.parse?n.parse(i,e,r):t===`checked`?i:typeof n.value.value==`number`?Number(i):i};function c(c){return{mount(l,u){let d=n(c)?c:{value:c};o(d);let f=r(l,d.as);a(l,f);let p=i(l,f,d.event);t(f===`checked`?{checked:d.value}:{value:d.value}).mount(l,u),u.registerCleanup(e(l,p,e=>{let t=s(l,f,d,e);Object.is(d.value.value,t)||(d.value.value=t)}))}}}export{c as bind};
2
2
  //# sourceMappingURL=bind.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bind.js","names":[],"sources":["../../src/directives/bind.ts"],"sourcesContent":["import { type Signal } from '@vielzeug/stateit';\n\nimport { type Directive } from '../core/internal';\nimport { attr } from './attr';\n\n/**\n * Creates a two-way binding between a writable Signal and a form element.\n *\n * - **`Signal<string>`** (`<input>`, `<textarea>`): syncs `.value` via the `input` event.\n * - **`Signal<string>`** (`<select>`): syncs `.value` via the `change` event.\n * - **`Signal<boolean>`** (checkbox/radio): auto-detected from the element's `type` attribute;\n * syncs `.checked` via the `change` event.\n *\n * Internally this reuses craftit's shared property-binding path, so `.value`,\n * `.checked`, `attr(...)`, and `spread({ '.value': ... })` stay in sync.\n *\n * Use in spread position on any form element.\n *\n * @example\n * import { bind } from '@vielzeug/craftit/directives';\n *\n * const name = signal('');\n * html`<input ${bind(name)} />`\n *\n * const accepted = signal(false);\n * html`<input type=\"checkbox\" ${bind(accepted)} />`\n *\n * const size = signal('');\n * html`<select ${bind(size)}><option value=\"sm\">S</option></select>`\n */\nexport function bind(sig: Signal<boolean>): Directive;\nexport function bind(sig: Signal<string>): Directive;\nexport function bind(sig: Signal<boolean> | Signal<string>): Directive {\n return {\n mount(el, context) {\n const input = el as HTMLInputElement;\n const isToggle = input.type === 'checkbox' || input.type === 'radio';\n\n (isToggle ? attr({ checked: sig as Signal<boolean> }) : attr({ value: sig as Signal<string> })).mount!(\n el,\n context,\n );\n },\n };\n}\n"],"mappings":"uFAgCA,SAAgB,EAAK,EAAkD,CACrE,MAAO,CACL,MAAM,EAAI,EAAS,CACjB,IAAM,EAAQ,GACG,EAAM,OAAS,YAAc,EAAM,OAAS,QAEjD,EAAK,CAAE,QAAS,EAAwB,CAAC,CAAG,EAAK,CAAE,MAAO,EAAuB,CAAC,EAAE,MAC9F,EACA,EACD,EAEJ"}
1
+ {"version":3,"file":"bind.js","names":[],"sources":["../../src/directives/bind.ts"],"sourcesContent":["import { type Signal } from '@vielzeug/stateit';\n\nimport { type Directive, listen } from '../internal';\nimport { attrs } from './attr';\n\nexport type BindMode = 'checked' | 'value';\n\nexport type BindOptions<T extends boolean | number | string> = {\n as?: BindMode;\n event?: string;\n parse?: (value: boolean | string, element: HTMLElement, event: Event) => T;\n value: Signal<T>;\n};\n\nconst isBindOptions = <T extends boolean | number | string>(\n input: Signal<T> | BindOptions<T>,\n): input is BindOptions<T> => {\n if (typeof input !== 'object' || input === null || !('value' in input)) return false;\n\n const candidate = (input as BindOptions<T>).value as unknown;\n\n return typeof candidate === 'object' && candidate !== null && 'value' in (candidate as object);\n};\n\nconst resolveMode = (element: HTMLElement, explicitMode?: BindMode): BindMode => {\n if (explicitMode) return explicitMode;\n\n if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {\n return 'checked';\n }\n\n return 'value';\n};\n\nconst resolveEventName = (element: HTMLElement, mode: BindMode, explicitEvent?: string): string => {\n if (explicitEvent) return explicitEvent;\n\n if (mode === 'checked' || element instanceof HTMLSelectElement) return 'change';\n\n return 'input';\n};\n\nconst assertBindableTarget = (element: HTMLElement, mode: BindMode): void => {\n if (mode === 'checked') {\n if (!(element instanceof HTMLInputElement) || (element.type !== 'checkbox' && element.type !== 'radio')) {\n throw new Error('[craftit:bind] mode \"checked\" requires <input type=\"checkbox|radio\">.');\n }\n\n return;\n }\n\n if (\n !(\n element instanceof HTMLInputElement ||\n element instanceof HTMLTextAreaElement ||\n element instanceof HTMLSelectElement\n )\n ) {\n throw new Error('[craftit:bind] value binding requires <input>, <textarea>, or <select>.');\n }\n};\n\nconst validateBindOptions = <T extends boolean | number | string>(options: BindOptions<T>): void => {\n if (options.event !== undefined && options.event.trim() === '') {\n throw new Error('[craftit:bind] options.event must be a non-empty event name.');\n }\n\n if (options.parse !== undefined && typeof options.parse !== 'function') {\n throw new Error('[craftit:bind] options.parse must be a function when provided.');\n }\n};\n\nconst readBoundValue = <T extends boolean | number | string>(\n element: HTMLElement,\n mode: BindMode,\n options: BindOptions<T>,\n event: Event,\n): T => {\n const raw = mode === 'checked' ? (element as HTMLInputElement).checked : (element as HTMLInputElement).value;\n\n if (options.parse) return options.parse(raw, element, event);\n\n if (mode === 'checked') return raw as T;\n\n if (typeof options.value.value === 'number') return Number(raw) as T;\n\n return raw as T;\n};\n\n/**\n * Creates a two-way binding between a writable Signal and a form element.\n *\n * Defaults:\n * - Checkbox/radio elements bind `.checked` via `change`\n * - Other form elements bind `.value` via `input`\n * - Select elements bind `.value` via `change`\n * - Number signals default to `Number(el.value)` unless you provide `parse`\n *\n * Internally this reuses craftit's shared property-binding path, so `.value`,\n * `.checked`, `attrs(...)`, and `spread({ '.value': ... })` stay in sync.\n *\n * Use in spread position on any form element.\n *\n * @example\n * import { bind } from '@vielzeug/craftit/directives';\n *\n * const name = signal('');\n * html`<input ${bind({ value: name })} />`\n *\n * const accepted = signal(false);\n * html`<input type=\"checkbox\" ${bind({ value: accepted })} />`\n *\n * const amount = signal(1);\n * html`<input type=\"number\" ${bind({ value: amount })} />`\n */\nexport function bind<T extends boolean | number | string>(value: Signal<T>): Directive;\nexport function bind<T extends boolean | number | string>(options: BindOptions<T>): Directive;\nexport function bind<T extends boolean | number | string>(input: Signal<T> | BindOptions<T>): Directive {\n return {\n mount(el, context) {\n const options = isBindOptions(input) ? input : ({ value: input } as BindOptions<T>);\n\n validateBindOptions(options);\n\n const mode = resolveMode(el, options.as);\n\n assertBindableTarget(el, mode);\n\n const eventName = resolveEventName(el, mode, options.event);\n\n attrs(mode === 'checked' ? { checked: options.value } : { value: options.value }).mount!(el, context);\n\n context.registerCleanup(\n listen(el, eventName, (event: Event) => {\n const next = readBoundValue(el, mode, options, event);\n\n if (!Object.is(options.value.value, next)) options.value.value = next;\n }),\n );\n },\n };\n}\n"],"mappings":"oGAcA,IAAM,EACJ,GAC4B,CAC5B,GAAI,OAAO,GAAU,WAAY,GAAkB,EAAE,UAAW,GAAQ,MAAO,GAE/E,IAAM,EAAa,EAAyB,MAE5C,OAAO,OAAO,GAAc,YAAY,GAAsB,UAAY,GAGtE,GAAe,EAAsB,IACrC,IAEA,aAAmB,mBAAqB,EAAQ,OAAS,YAAc,EAAQ,OAAS,SACnF,UAGF,SAGH,GAAoB,EAAsB,EAAgB,IAC1D,IAEA,IAAS,WAAa,aAAmB,kBAA0B,SAEhE,SAGH,GAAwB,EAAsB,IAAyB,CAC3E,GAAI,IAAS,UAAW,CACtB,GAAI,EAAE,aAAmB,mBAAsB,EAAQ,OAAS,YAAc,EAAQ,OAAS,QAC7F,MAAU,MAAM,wEAAwE,CAG1F,OAGF,GACE,EACE,aAAmB,kBACnB,aAAmB,qBACnB,aAAmB,mBAGrB,MAAU,MAAM,0EAA0E,EAIxF,EAA4D,GAAkC,CAClG,GAAI,EAAQ,QAAU,IAAA,IAAa,EAAQ,MAAM,MAAM,GAAK,GAC1D,MAAU,MAAM,+DAA+D,CAGjF,GAAI,EAAQ,QAAU,IAAA,IAAa,OAAO,EAAQ,OAAU,WAC1D,MAAU,MAAM,iEAAiE,EAI/E,GACJ,EACA,EACA,EACA,IACM,CACN,IAAM,EAAM,IAAS,UAAa,EAA6B,QAAW,EAA6B,MAQvG,OANI,EAAQ,MAAc,EAAQ,MAAM,EAAK,EAAS,EAAM,CAExD,IAAS,UAAkB,EAE3B,OAAO,EAAQ,MAAM,OAAU,SAAiB,OAAO,EAAI,CAExD,GA+BT,SAAgB,EAA0C,EAA8C,CACtG,MAAO,CACL,MAAM,EAAI,EAAS,CACjB,IAAM,EAAU,EAAc,EAAM,CAAG,EAAS,CAAE,MAAO,EAAO,CAEhE,EAAoB,EAAQ,CAE5B,IAAM,EAAO,EAAY,EAAI,EAAQ,GAAG,CAExC,EAAqB,EAAI,EAAK,CAE9B,IAAM,EAAY,EAAiB,EAAI,EAAM,EAAQ,MAAM,CAE3D,EAAM,IAAS,UAAY,CAAE,QAAS,EAAQ,MAAO,CAAG,CAAE,MAAO,EAAQ,MAAO,CAAC,CAAC,MAAO,EAAI,EAAQ,CAErG,EAAQ,gBACN,EAAO,EAAI,EAAY,GAAiB,CACtC,IAAM,EAAO,EAAe,EAAI,EAAM,EAAS,EAAM,CAEhD,OAAO,GAAG,EAAQ,MAAM,MAAO,EAAK,GAAE,EAAQ,MAAM,MAAQ,IACjE,CACH,EAEJ"}
@@ -1,2 +1,2 @@
1
- let e=require(`@vielzeug/stateit`);function t(t,n,r){let i=e=>{let t=n.find(([t])=>t===e);return t?t[1]():r?.()??``};return(0,e.isSignal)(t)?()=>i(t.value):typeof t==`function`?()=>i(t()):i(t)}exports.choose=t;
1
+ let e=require(`@vielzeug/stateit`);var t=e=>{if(!Array.isArray(e.cases))throw Error(`[craftit:choose] options.cases must be an array of [key, templateFn] entries.`);for(let t=0;t<e.cases.length;t++){let n=e.cases[t];if(!Array.isArray(n)||n.length!==2||typeof n[1]!=`function`)throw Error(`[craftit:choose] Invalid case at index ${t}. Expected [key, templateFn].`)}if(e.fallback!==void 0&&typeof e.fallback!=`function`)throw Error(`[craftit:choose] options.fallback must be a function when provided.`)};function n(n){t(n);let i=new Map(n.cases),a=e=>(i.get(e)??n.fallback??r)(),{value:o}=n;return(0,e.isSignal)(o)?()=>a(o.value):typeof o==`function`?()=>a(o()):a(o)}var r=()=>``;exports.choose=n;
2
2
  //# sourceMappingURL=choose.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"choose.cjs","names":[],"sources":["../../src/directives/choose.ts"],"sourcesContent":["import { isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../core/internal';\n\ntype TemplateFn = () => string | HTMLResult;\ntype CaseEntry<T> = readonly [T, TemplateFn];\n\n/**\n * Renders the template matching the current value — like a type-safe `switch`.\n * Cases are `[value, templateFn]` pairs evaluated with strict equality (`===`).\n *\n * The discriminant can be a static value, a reactive Signal, or a getter function.\n * When reactive, the rendered output updates automatically when the value changes.\n *\n * @example\n * import { choose } from '@vielzeug/craftit/directives';\n *\n * // Static\n * html`${choose(section, [\n * ['home', () => html`<h1>Home</h1>`],\n * ['about', () => html`<h1>About</h1>`],\n * ], () => html`<h1>Not found</h1>`)}`\n *\n * // Reactive signal — updates on change\n * const tab = signal('home');\n * html`${choose(tab, [\n * ['home', () => html`<home-panel>`],\n * ['about', () => html`<about-panel>`],\n * ])}`\n *\n * // Reactive getter\n * html`${choose(() => props.view.value, cases)}`\n */\nexport function choose<T extends PropertyKey>(\n value: ReadonlySignal<T> | (() => T),\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): TemplateFn;\nexport function choose<T extends PropertyKey>(\n value: T,\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): string | HTMLResult;\nexport function choose<T extends PropertyKey>(\n value: T | ReadonlySignal<T> | (() => T),\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): string | HTMLResult | TemplateFn {\n const pick = (v: T): string | HTMLResult => {\n const entry = cases.find(([key]) => key === v);\n\n return entry ? entry[1]() : (defaultFn?.() ?? '');\n };\n\n if (isSignal(value)) {\n return () => pick((value as ReadonlySignal<T>).value);\n }\n\n if (typeof value === 'function') {\n return () => pick((value as () => T)());\n }\n\n return pick(value as T);\n}\n"],"mappings":"mCA2CA,SAAgB,EACd,EACA,EACA,EACkC,CAClC,IAAM,EAAQ,GAA8B,CAC1C,IAAM,EAAQ,EAAM,MAAM,CAAC,KAAS,IAAQ,EAAE,CAE9C,OAAO,EAAQ,EAAM,IAAI,CAAI,KAAa,EAAI,IAWhD,OARA,EAAA,EAAA,UAAa,EAAM,KACJ,EAAM,EAA4B,MAAM,CAGnD,OAAO,GAAU,eACN,EAAM,GAAmB,CAAC,CAGlC,EAAK,EAAW"}
1
+ {"version":3,"file":"choose.cjs","names":[],"sources":["../../src/directives/choose.ts"],"sourcesContent":["import { isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../internal';\n\ntype TemplateFn = () => string | HTMLResult;\ntype CaseEntry<T> = readonly [T, TemplateFn];\n\nexport type ChooseOptions<T extends PropertyKey> = {\n cases: ReadonlyArray<CaseEntry<T>>;\n fallback?: TemplateFn;\n value: T | ReadonlySignal<T> | (() => T);\n};\n\nconst validateChooseOptions = <T extends PropertyKey>(options: ChooseOptions<T>): void => {\n if (!Array.isArray(options.cases)) {\n throw new Error('[craftit:choose] options.cases must be an array of [key, templateFn] entries.');\n }\n\n for (let i = 0; i < options.cases.length; i++) {\n const entry = options.cases[i] as unknown;\n\n if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[1] !== 'function') {\n throw new Error(`[craftit:choose] Invalid case at index ${i}. Expected [key, templateFn].`);\n }\n }\n\n if (options.fallback !== undefined && typeof options.fallback !== 'function') {\n throw new Error('[craftit:choose] options.fallback must be a function when provided.');\n }\n};\n\n/**\n * Renders the template matching the current value — like a type-safe `switch`.\n * Cases are `[value, templateFn]` pairs evaluated with strict equality (`===`).\n *\n * The discriminant can be a static value, a reactive Signal, or a getter function.\n * When reactive, the rendered output updates automatically when the value changes.\n *\n * @example\n * import { choose } from '@vielzeug/craftit/directives';\n *\n * // Static\n * html`${choose({\n * value: section,\n * cases: [\n * ['home', () => html`<h1>Home</h1>`],\n * ['about', () => html`<h1>About</h1>`],\n * ],\n * fallback: () => html`<h1>Not found</h1>`,\n * })}`\n *\n * // Reactive signal — updates on change\n * const tab = signal('home');\n * html`${choose({ value: tab, cases: [['home', () => html`<home-panel>`], ['about', () => html`<about-panel>`]] })}`\n *\n * // Reactive getter\n * html`${choose({ value: () => props.view.value, cases })}`\n */\nexport function choose<T extends PropertyKey>(options: ChooseOptions<T>): string | HTMLResult | TemplateFn {\n validateChooseOptions(options);\n\n const caseMap = new Map(options.cases);\n const pick = (value: T): string | HTMLResult => (caseMap.get(value) ?? options.fallback ?? emptyTemplate)();\n const { value } = options;\n\n if (isSignal(value)) {\n return () => pick((value as ReadonlySignal<T>).value);\n }\n\n if (typeof value === 'function') {\n return () => pick((value as () => T)());\n }\n\n return pick(value as T);\n}\n\nconst emptyTemplate = (): string => '';\n"],"mappings":"mCAaA,IAAM,EAAgD,GAAoC,CACxF,GAAI,CAAC,MAAM,QAAQ,EAAQ,MAAM,CAC/B,MAAU,MAAM,gFAAgF,CAGlG,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,MAAM,OAAQ,IAAK,CAC7C,IAAM,EAAQ,EAAQ,MAAM,GAE5B,GAAI,CAAC,MAAM,QAAQ,EAAM,EAAI,EAAM,SAAW,GAAK,OAAO,EAAM,IAAO,WACrE,MAAU,MAAM,0CAA0C,EAAE,+BAA+B,CAI/F,GAAI,EAAQ,WAAa,IAAA,IAAa,OAAO,EAAQ,UAAa,WAChE,MAAU,MAAM,sEAAsE,EA+B1F,SAAgB,EAA8B,EAA6D,CACzG,EAAsB,EAAQ,CAE9B,IAAM,EAAU,IAAI,IAAI,EAAQ,MAAM,CAChC,EAAQ,IAAmC,EAAQ,IAAI,EAAM,EAAI,EAAQ,UAAY,IAAgB,CACrG,CAAE,SAAU,EAUlB,OARA,EAAA,EAAA,UAAa,EAAM,KACJ,EAAM,EAA4B,MAAM,CAGnD,OAAO,GAAU,eACN,EAAM,GAAmB,CAAC,CAGlC,EAAK,EAAW,CAGzB,IAAM,MAA8B"}
@@ -1,7 +1,12 @@
1
1
  import { type ReadonlySignal } from '@vielzeug/stateit';
2
- import type { HTMLResult } from '../core/internal';
2
+ import type { HTMLResult } from '../internal';
3
3
  type TemplateFn = () => string | HTMLResult;
4
4
  type CaseEntry<T> = readonly [T, TemplateFn];
5
+ export type ChooseOptions<T extends PropertyKey> = {
6
+ cases: ReadonlyArray<CaseEntry<T>>;
7
+ fallback?: TemplateFn;
8
+ value: T | ReadonlySignal<T> | (() => T);
9
+ };
5
10
  /**
6
11
  * Renders the template matching the current value — like a type-safe `switch`.
7
12
  * Cases are `[value, templateFn]` pairs evaluated with strict equality (`===`).
@@ -13,22 +18,22 @@ type CaseEntry<T> = readonly [T, TemplateFn];
13
18
  * import { choose } from '@vielzeug/craftit/directives';
14
19
  *
15
20
  * // Static
16
- * html`${choose(section, [
17
- * ['home', () => html`<h1>Home</h1>`],
18
- * ['about', () => html`<h1>About</h1>`],
19
- * ], () => html`<h1>Not found</h1>`)}`
21
+ * html`${choose({
22
+ * value: section,
23
+ * cases: [
24
+ * ['home', () => html`<h1>Home</h1>`],
25
+ * ['about', () => html`<h1>About</h1>`],
26
+ * ],
27
+ * fallback: () => html`<h1>Not found</h1>`,
28
+ * })}`
20
29
  *
21
30
  * // Reactive signal — updates on change
22
31
  * const tab = signal('home');
23
- * html`${choose(tab, [
24
- * ['home', () => html`<home-panel>`],
25
- * ['about', () => html`<about-panel>`],
26
- * ])}`
32
+ * html`${choose({ value: tab, cases: [['home', () => html`<home-panel>`], ['about', () => html`<about-panel>`]] })}`
27
33
  *
28
34
  * // Reactive getter
29
- * html`${choose(() => props.view.value, cases)}`
35
+ * html`${choose({ value: () => props.view.value, cases })}`
30
36
  */
31
- export declare function choose<T extends PropertyKey>(value: ReadonlySignal<T> | (() => T), cases: ReadonlyArray<CaseEntry<T>>, defaultFn?: TemplateFn): TemplateFn;
32
- export declare function choose<T extends PropertyKey>(value: T, cases: ReadonlyArray<CaseEntry<T>>, defaultFn?: TemplateFn): string | HTMLResult;
37
+ export declare function choose<T extends PropertyKey>(options: ChooseOptions<T>): string | HTMLResult | TemplateFn;
33
38
  export {};
34
39
  //# sourceMappingURL=choose.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"choose.d.ts","sourceRoot":"","sources":["../../src/directives/choose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,UAAU,GAAG,MAAM,MAAM,GAAG,UAAU,CAAC;AAC5C,KAAK,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,WAAW,EAC1C,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EACpC,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAClC,SAAS,CAAC,EAAE,UAAU,GACrB,UAAU,CAAC;AACd,wBAAgB,MAAM,CAAC,CAAC,SAAS,WAAW,EAC1C,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAClC,SAAS,CAAC,EAAE,UAAU,GACrB,MAAM,GAAG,UAAU,CAAC"}
1
+ {"version":3,"file":"choose.d.ts","sourceRoot":"","sources":["../../src/directives/choose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,UAAU,GAAG,MAAM,MAAM,GAAG,UAAU,CAAC;AAC5C,KAAK,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAE7C,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,WAAW,IAAI;IACjD,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,KAAK,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAC1C,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,WAAW,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAgBzG"}
@@ -1,2 +1,2 @@
1
- import{isSignal as e}from"@vielzeug/stateit";function t(t,n,r){let i=e=>{let t=n.find(([t])=>t===e);return t?t[1]():r?.()??``};return e(t)?()=>i(t.value):typeof t==`function`?()=>i(t()):i(t)}export{t as choose};
1
+ import{isSignal as e}from"@vielzeug/stateit";var t=e=>{if(!Array.isArray(e.cases))throw Error(`[craftit:choose] options.cases must be an array of [key, templateFn] entries.`);for(let t=0;t<e.cases.length;t++){let n=e.cases[t];if(!Array.isArray(n)||n.length!==2||typeof n[1]!=`function`)throw Error(`[craftit:choose] Invalid case at index ${t}. Expected [key, templateFn].`)}if(e.fallback!==void 0&&typeof e.fallback!=`function`)throw Error(`[craftit:choose] options.fallback must be a function when provided.`)};function n(n){t(n);let i=new Map(n.cases),a=e=>(i.get(e)??n.fallback??r)(),{value:o}=n;return e(o)?()=>a(o.value):typeof o==`function`?()=>a(o()):a(o)}var r=()=>``;export{n as choose};
2
2
  //# sourceMappingURL=choose.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"choose.js","names":[],"sources":["../../src/directives/choose.ts"],"sourcesContent":["import { isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../core/internal';\n\ntype TemplateFn = () => string | HTMLResult;\ntype CaseEntry<T> = readonly [T, TemplateFn];\n\n/**\n * Renders the template matching the current value — like a type-safe `switch`.\n * Cases are `[value, templateFn]` pairs evaluated with strict equality (`===`).\n *\n * The discriminant can be a static value, a reactive Signal, or a getter function.\n * When reactive, the rendered output updates automatically when the value changes.\n *\n * @example\n * import { choose } from '@vielzeug/craftit/directives';\n *\n * // Static\n * html`${choose(section, [\n * ['home', () => html`<h1>Home</h1>`],\n * ['about', () => html`<h1>About</h1>`],\n * ], () => html`<h1>Not found</h1>`)}`\n *\n * // Reactive signal — updates on change\n * const tab = signal('home');\n * html`${choose(tab, [\n * ['home', () => html`<home-panel>`],\n * ['about', () => html`<about-panel>`],\n * ])}`\n *\n * // Reactive getter\n * html`${choose(() => props.view.value, cases)}`\n */\nexport function choose<T extends PropertyKey>(\n value: ReadonlySignal<T> | (() => T),\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): TemplateFn;\nexport function choose<T extends PropertyKey>(\n value: T,\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): string | HTMLResult;\nexport function choose<T extends PropertyKey>(\n value: T | ReadonlySignal<T> | (() => T),\n cases: ReadonlyArray<CaseEntry<T>>,\n defaultFn?: TemplateFn,\n): string | HTMLResult | TemplateFn {\n const pick = (v: T): string | HTMLResult => {\n const entry = cases.find(([key]) => key === v);\n\n return entry ? entry[1]() : (defaultFn?.() ?? '');\n };\n\n if (isSignal(value)) {\n return () => pick((value as ReadonlySignal<T>).value);\n }\n\n if (typeof value === 'function') {\n return () => pick((value as () => T)());\n }\n\n return pick(value as T);\n}\n"],"mappings":"6CA2CA,SAAgB,EACd,EACA,EACA,EACkC,CAClC,IAAM,EAAQ,GAA8B,CAC1C,IAAM,EAAQ,EAAM,MAAM,CAAC,KAAS,IAAQ,EAAE,CAE9C,OAAO,EAAQ,EAAM,IAAI,CAAI,KAAa,EAAI,IAWhD,OARI,EAAS,EAAM,KACJ,EAAM,EAA4B,MAAM,CAGnD,OAAO,GAAU,eACN,EAAM,GAAmB,CAAC,CAGlC,EAAK,EAAW"}
1
+ {"version":3,"file":"choose.js","names":[],"sources":["../../src/directives/choose.ts"],"sourcesContent":["import { isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../internal';\n\ntype TemplateFn = () => string | HTMLResult;\ntype CaseEntry<T> = readonly [T, TemplateFn];\n\nexport type ChooseOptions<T extends PropertyKey> = {\n cases: ReadonlyArray<CaseEntry<T>>;\n fallback?: TemplateFn;\n value: T | ReadonlySignal<T> | (() => T);\n};\n\nconst validateChooseOptions = <T extends PropertyKey>(options: ChooseOptions<T>): void => {\n if (!Array.isArray(options.cases)) {\n throw new Error('[craftit:choose] options.cases must be an array of [key, templateFn] entries.');\n }\n\n for (let i = 0; i < options.cases.length; i++) {\n const entry = options.cases[i] as unknown;\n\n if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[1] !== 'function') {\n throw new Error(`[craftit:choose] Invalid case at index ${i}. Expected [key, templateFn].`);\n }\n }\n\n if (options.fallback !== undefined && typeof options.fallback !== 'function') {\n throw new Error('[craftit:choose] options.fallback must be a function when provided.');\n }\n};\n\n/**\n * Renders the template matching the current value — like a type-safe `switch`.\n * Cases are `[value, templateFn]` pairs evaluated with strict equality (`===`).\n *\n * The discriminant can be a static value, a reactive Signal, or a getter function.\n * When reactive, the rendered output updates automatically when the value changes.\n *\n * @example\n * import { choose } from '@vielzeug/craftit/directives';\n *\n * // Static\n * html`${choose({\n * value: section,\n * cases: [\n * ['home', () => html`<h1>Home</h1>`],\n * ['about', () => html`<h1>About</h1>`],\n * ],\n * fallback: () => html`<h1>Not found</h1>`,\n * })}`\n *\n * // Reactive signal — updates on change\n * const tab = signal('home');\n * html`${choose({ value: tab, cases: [['home', () => html`<home-panel>`], ['about', () => html`<about-panel>`]] })}`\n *\n * // Reactive getter\n * html`${choose({ value: () => props.view.value, cases })}`\n */\nexport function choose<T extends PropertyKey>(options: ChooseOptions<T>): string | HTMLResult | TemplateFn {\n validateChooseOptions(options);\n\n const caseMap = new Map(options.cases);\n const pick = (value: T): string | HTMLResult => (caseMap.get(value) ?? options.fallback ?? emptyTemplate)();\n const { value } = options;\n\n if (isSignal(value)) {\n return () => pick((value as ReadonlySignal<T>).value);\n }\n\n if (typeof value === 'function') {\n return () => pick((value as () => T)());\n }\n\n return pick(value as T);\n}\n\nconst emptyTemplate = (): string => '';\n"],"mappings":"6CAaA,IAAM,EAAgD,GAAoC,CACxF,GAAI,CAAC,MAAM,QAAQ,EAAQ,MAAM,CAC/B,MAAU,MAAM,gFAAgF,CAGlG,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,MAAM,OAAQ,IAAK,CAC7C,IAAM,EAAQ,EAAQ,MAAM,GAE5B,GAAI,CAAC,MAAM,QAAQ,EAAM,EAAI,EAAM,SAAW,GAAK,OAAO,EAAM,IAAO,WACrE,MAAU,MAAM,0CAA0C,EAAE,+BAA+B,CAI/F,GAAI,EAAQ,WAAa,IAAA,IAAa,OAAO,EAAQ,UAAa,WAChE,MAAU,MAAM,sEAAsE,EA+B1F,SAAgB,EAA8B,EAA6D,CACzG,EAAsB,EAAQ,CAE9B,IAAM,EAAU,IAAI,IAAI,EAAQ,MAAM,CAChC,EAAQ,IAAmC,EAAQ,IAAI,EAAM,EAAI,EAAQ,UAAY,IAAgB,CACrG,CAAE,SAAU,EAUlB,OARI,EAAS,EAAM,KACJ,EAAM,EAA4B,MAAM,CAGnD,OAAO,GAAU,eACN,EAAM,GAAmB,CAAC,CAGlC,EAAK,EAAW,CAGzB,IAAM,MAA8B"}
@@ -1,2 +1,2 @@
1
- const e=require(`../core/internal.cjs`),t=require(`../core/utilities.cjs`);let n=require(`@vielzeug/stateit`);var r=RegExp(`u="(\\d+)"`,`g`),i=e.htmlResult(``),a=[],o=(n,r)=>e.isHtmlResult(n)?c(n,r):{bindings:a,html:t.escapeHtml(n)},s=n=>e.isHtmlResult(n)?n:e.htmlResult(t.escapeHtml(n));function c(e,t){let n=new Map,i=[];for(let r of e.__bindings){let e=r.uid,a=n.get(e)??String(t.n++);n.has(e)||n.set(e,a),i.push({...r,uid:a})}return{bindings:i,html:e.__html.replace(r,(e,t)=>`u="${n.get(t)??t}"`).replace(/<!--(\d+)-->/g,(e,t)=>`<!--${n.get(t)??t}-->`)}}function l(e,t,n){let r=``,i=[],a=[],s=[],c={n:0};for(let l=0;l<e.length;l++){a.push(n(e[l],l));let u=o(t(e[l],l),c);r+=u.html,i.push(...u.bindings),s.push(u)}return{bindings:i,html:r,keys:a,rendered:s}}function u(e,t){let n=``,r=[],i={n:0};for(let a=0;a<e.length;a++){let s=o(t(e[a],a),i);n+=s.html,r.push(...s.bindings)}return{bindings:r,html:n}}function d(t,r,a,o){let c=typeof a==`function`,d=c?a:void 0,f=(c?o:o??a)??{},p=f.select;if(Array.isArray(t)){let n=p?t.filter(p):t;if(!n.length){if(d){let t=e.extractResult(s(d()));return e.htmlResult(t.html,t.bindings)}return i}let{bindings:a,html:o}=u(n,r);return e.htmlResult(o,a)}let m=f.key;if(!m)throw Error(`[craftit:each] Reactive each() requires options.key for stable reconciliation.`);let h=(0,n.isSignal)(t)?()=>t.value:t;return{[e.EACH_SIGNAL]:(0,n.computed)(()=>{let t=h(),n=p?t.filter(p):t;if(!n.length){let t=d?s(d()):void 0;return t?{...e.extractResult(t),items:[],keys:[]}:{bindings:[],html:``,items:[],keys:[]}}let{bindings:i,html:a,keys:o,rendered:c}=l(n,r,m);return{bindings:i,html:a,items:c,keys:o}})}}exports.each=d;
1
+ const e=require(`../internal.cjs`);let t=require(`@vielzeug/stateit`);var n=RegExp(`u="(\\d+)"`,`g`),r=e.htmlResult(``),i=[],a=(t,n)=>e.isHtmlResult(t)?s(t,n):{bindings:i,html:e.escapeHtml(t)},o=t=>e.isHtmlResult(t)?t:e.htmlResult(e.escapeHtml(t));function s(e,t){let r=new Map,i=[];for(let n of e.__bindings){let e=n.uid,a=r.get(e)??String(t.n++);r.has(e)||r.set(e,a),i.push({...n,uid:a})}return{bindings:i,html:e.__html.replace(n,(e,t)=>`u="${r.get(t)??t}"`).replace(/<!--(\d+)-->/g,(e,t)=>`<!--${r.get(t)??t}-->`)}}function c(e,t,n){let r=``,i=[],o=[],s=new Set,c=[],l={n:0};for(let u=0;u<e.length;u++){let d=n(e[u],u);if(s.has(d))throw Error(`[craftit:each] Duplicate key "${String(d)}" at index ${u}.`);s.add(d),o.push(d);let f=a(t(e[u],u),l);r+=f.html,i.push(...f.bindings),c.push(f)}return{bindings:i,html:r,keys:o,rendered:c}}function l(e,t){let n=``,r=[],i={n:0};for(let o=0;o<e.length;o++){let s=a(t(e[o],o),i);n+=s.html,r.push(...s.bindings)}return{bindings:r,html:n}}var u=e=>typeof e==`function`,d=e=>{if(!u(e.render))throw Error(`[craftit:each] options.render must be a function.`);if(e.key!==void 0&&!u(e.key))throw Error(`[craftit:each] options.key must be a function when provided.`);if(e.select!==void 0&&!u(e.select))throw Error(`[craftit:each] options.select must be a function when provided.`);if(e.fallback!==void 0&&!u(e.fallback))throw Error(`[craftit:each] options.fallback must be a function when provided.`)},f=e=>{if(Array.isArray(e))return e;throw Error(`[craftit:each] source must resolve to an array.`)};function p(n,i){d(i);let{fallback:a,key:s,render:u,select:p}=i;if(Array.isArray(n)){let t=p?n.filter(p):n;if(!t.length){if(a){let t=e.extractResult(o(a()));return e.htmlResult(t.html,t.bindings)}return r}let{bindings:i,html:s}=l(t,u);return e.htmlResult(s,i)}if(!s)throw Error(`[craftit:each] Reactive each() requires options.key for stable reconciliation.`);let m=(0,t.isSignal)(n)?()=>n.value:n;return{[e.EACH_SIGNAL]:(0,t.computed)(()=>{let t=f(m()),n=p?t.filter(p):t;if(!n.length){let t=a?o(a()):void 0;return t?{...e.extractResult(t),items:[],keys:[]}:{bindings:[],html:``,items:[],keys:[]}}let{bindings:r,html:i,keys:l,rendered:d}=c(n,u,s);return{bindings:r,html:i,items:d,keys:l}})}}exports.each=p;
2
2
  //# sourceMappingURL=each.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { computed, isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport {\n CF_ID_ATTR,\n EACH_SIGNAL,\n extractResult,\n htmlResult,\n isHtmlResult,\n type Binding,\n type Directive,\n type HTMLResult,\n} from '../core/internal';\nimport { escapeHtml } from '../core/utilities';\n\nconst ATTR_ID_RE = new RegExp(`${CF_ID_ATTR}=\"(\\\\d+)\"`, 'g');\n\n/* immutable — shared singleton for empty static lists */\nconst EMPTY = htmlResult('');\nconst NO_BINDINGS: Binding[] = [];\n\nconst toResultEntry = (value: string | HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } =>\n isHtmlResult(value) ? renumber(value, c) : { bindings: NO_BINDINGS, html: escapeHtml(value) };\n\nconst toHtmlResult = (value: string | HTMLResult): HTMLResult =>\n isHtmlResult(value) ? value : htmlResult(escapeHtml(value));\n\nfunction renumber(res: HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } {\n const map = new Map<string, string>();\n const nb: Binding[] = [];\n\n for (const b of res.__bindings) {\n const oldId = b.uid;\n const newId = map.get(oldId) ?? String(c.n++);\n\n if (!map.has(oldId)) map.set(oldId, newId);\n\n nb.push({ ...b, uid: newId });\n }\n\n return {\n bindings: nb,\n html: res.__html\n // Re-map element binding ids.\n .replace(ATTR_ID_RE, (_, id) => `${CF_ID_ATTR}=\"${map.get(id) ?? id}\"`)\n // Re-map numeric comment markers used by text/html placeholders.\n .replace(/<!--(\\d+)-->/g, (_, id) => `<!--${map.get(id) ?? id}-->`),\n };\n}\n\n/** Render loop used by the reactive path (keys + rendered metadata required for reconciliation). */\nfunction renderKeyed<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n keyFn: (item: T, index: number) => string | number,\n): {\n bindings: Binding[];\n html: string;\n keys: (string | number)[];\n rendered: Array<{ bindings: Binding[]; html: string }>;\n} {\n let html = '';\n const allBindings: Binding[] = [];\n const keys: (string | number)[] = [];\n const rendered: Array<{ bindings: Binding[]; html: string }> = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n keys.push(keyFn(items[i], i));\n\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n rendered.push(entry);\n }\n\n return { bindings: allBindings, html, keys, rendered };\n}\n\n/** Render loop used by the static path — no key/reconciliation metadata needed. */\nfunction renderStatic<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n): { bindings: Binding[]; html: string } {\n let html = '';\n const allBindings: Binding[] = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n }\n\n return { bindings: allBindings, html };\n}\n\n/**\n * Options accepted by `each()`.\n */\nexport interface EachOptions<T> {\n /**\n * Key extractor for stable DOM reconciliation.\n * Receives the item and its **filtered** array index (after `select` is applied).\n *\n * Required when `source` is reactive (Signal/getter).\n * Optional for static arrays (render-once path).\n */\n key?: (item: T, index: number) => string | number;\n /**\n * Filter predicate. Receives the item and its **original** array index.\n * Only items for which `select` returns `true` are rendered.\n *\n * @example\n * each(items, item => html`<li>${item.name}</li>`, undefined, { select: item => item.active })\n */\n select?: (item: T, index: number) => boolean;\n}\n\ntype ReactiveSource<T> = ReadonlySignal<T[]> | (() => T[]);\ntype EachSignalResult = Directive & {\n [EACH_SIGNAL]: ReadonlySignal<{\n bindings: Binding[];\n html: string;\n items?: Array<{ bindings: Binding[]; html: string }>;\n keys?: (string | number)[];\n }>;\n};\n\n/**\n * Renders a reactive list with keyed DOM reconciliation for efficient updates.\n * Use inside `html` tagged templates.\n *\n * For reactive sources (Signal/getter), you must provide a stable `key` function.\n *\n * For dynamic lists with click handlers, prefer event delegation on a parent node\n * (`@click` + `closest(...)`) over per-item handlers inside `each()`.\n *\n * @example\n * import { each } from '@vielzeug/craftit/directives';\n *\n * // Static array (key optional):\n * html`${each([1, 2, 3], item => html`<li>${item}</li>`)}`\n *\n * // Reactive source (key required):\n * html`${each(items, item => html`<li>${item.name}</li>`, undefined, { key: item => item.id })}`\n *\n * // Full example:\n * html`${each(items, item => html`<li>${item.name}</li>`, () => html`<p>No items</p>`, {\n * key: item => item.id,\n * select: item => item.active,\n * })}`\n */\nexport function each<T>(\n source: T[],\n template: (item: T, index: number) => string | HTMLResult,\n empty?: () => string | HTMLResult,\n options?: EachOptions<T>,\n): HTMLResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n empty: (() => string | HTMLResult) | undefined,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(\n source: T[] | ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n emptyOrOptions?: (() => string | HTMLResult) | EachOptions<T>,\n options?: EachOptions<T>,\n): HTMLResult | EachSignalResult {\n const hasEmptyFn = typeof emptyOrOptions === 'function';\n const empty = hasEmptyFn ? emptyOrOptions : undefined;\n const resolvedOptions = (hasEmptyFn ? options : (options ?? emptyOrOptions)) ?? {};\n const select = resolvedOptions.select;\n\n if (Array.isArray(source)) {\n const filtered = select ? source.filter(select) : source;\n\n if (!filtered.length) {\n if (empty) {\n const er = extractResult(toHtmlResult(empty()));\n\n return htmlResult(er.html, er.bindings);\n }\n\n return EMPTY;\n }\n\n const { bindings, html } = renderStatic(filtered, template);\n\n return htmlResult(html, bindings);\n }\n\n const keyFn = resolvedOptions.key;\n\n if (!keyFn) {\n throw new Error('[craftit:each] Reactive each() requires options.key for stable reconciliation.');\n }\n\n const getItems = isSignal(source) ? () => (source as ReadonlySignal<T[]>).value : (source as () => T[]);\n\n return {\n [EACH_SIGNAL]: computed(() => {\n const raw = getItems();\n const filtered = select ? raw.filter(select) : raw;\n\n if (!filtered.length) {\n const er = empty ? toHtmlResult(empty()) : undefined;\n\n return er ? { ...extractResult(er), items: [], keys: [] } : { bindings: [], html: '', items: [], keys: [] };\n }\n\n const { bindings, html, keys, rendered } = renderKeyed(filtered, template, keyFn);\n\n return { bindings, html, items: rendered, keys };\n }),\n };\n}\n"],"mappings":"8GAcA,IAAM,EAAiB,OAAO,aAA0B,IAAI,CAGtD,EAAQ,EAAA,WAAW,GAAG,CACtB,EAAyB,EAAE,CAE3B,GAAiB,EAA4B,IACjD,EAAA,aAAa,EAAM,CAAG,EAAS,EAAO,EAAE,CAAG,CAAE,SAAU,EAAa,KAAM,EAAA,WAAW,EAAM,CAAE,CAEzF,EAAgB,GACpB,EAAA,aAAa,EAAM,CAAG,EAAQ,EAAA,WAAW,EAAA,WAAW,EAAM,CAAC,CAE7D,SAAS,EAAS,EAAiB,EAAyD,CAC1F,IAAM,EAAM,IAAI,IACV,EAAgB,EAAE,CAExB,IAAK,IAAM,KAAK,EAAI,WAAY,CAC9B,IAAM,EAAQ,EAAE,IACV,EAAQ,EAAI,IAAI,EAAM,EAAI,OAAO,EAAE,IAAI,CAExC,EAAI,IAAI,EAAM,EAAE,EAAI,IAAI,EAAO,EAAM,CAE1C,EAAG,KAAK,CAAE,GAAG,EAAG,IAAK,EAAO,CAAC,CAG/B,MAAO,CACL,SAAU,EACV,KAAM,EAAI,OAEP,QAAQ,GAAa,EAAG,IAAO,MAAkB,EAAI,IAAI,EAAG,EAAI,EAAG,GAAG,CAEtE,QAAQ,iBAAkB,EAAG,IAAO,OAAO,EAAI,IAAI,EAAG,EAAI,EAAG,KAAK,CACtE,CAIH,SAAS,EACP,EACA,EACA,EAMA,CACA,IAAI,EAAO,GACL,EAAyB,EAAE,CAC3B,EAA4B,EAAE,CAC9B,EAAyD,EAAE,CAC3D,EAAI,CAAE,EAAG,EAAG,CAElB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,EAAK,KAAK,EAAM,EAAM,GAAI,EAAE,CAAC,CAE7B,IAAM,EAAQ,EAAc,EAAS,EAAM,GAAI,EAAE,CAAE,EAAE,CAErD,GAAQ,EAAM,KACd,EAAY,KAAK,GAAG,EAAM,SAAS,CACnC,EAAS,KAAK,EAAM,CAGtB,MAAO,CAAE,SAAU,EAAa,OAAM,OAAM,WAAU,CAIxD,SAAS,EACP,EACA,EACuC,CACvC,IAAI,EAAO,GACL,EAAyB,EAAE,CAC3B,EAAI,CAAE,EAAG,EAAG,CAElB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAQ,EAAc,EAAS,EAAM,GAAI,EAAE,CAAE,EAAE,CAErD,GAAQ,EAAM,KACd,EAAY,KAAK,GAAG,EAAM,SAAS,CAGrC,MAAO,CAAE,SAAU,EAAa,OAAM,CA4ExC,SAAgB,EACd,EACA,EACA,EACA,EAC+B,CAC/B,IAAM,EAAa,OAAO,GAAmB,WACvC,EAAQ,EAAa,EAAiB,IAAA,GACtC,GAAmB,EAAa,EAAW,GAAW,IAAoB,EAAE,CAC5E,EAAS,EAAgB,OAE/B,GAAI,MAAM,QAAQ,EAAO,CAAE,CACzB,IAAM,EAAW,EAAS,EAAO,OAAO,EAAO,CAAG,EAElD,GAAI,CAAC,EAAS,OAAQ,CACpB,GAAI,EAAO,CACT,IAAM,EAAK,EAAA,cAAc,EAAa,GAAO,CAAC,CAAC,CAE/C,OAAO,EAAA,WAAW,EAAG,KAAM,EAAG,SAAS,CAGzC,OAAO,EAGT,GAAM,CAAE,WAAU,QAAS,EAAa,EAAU,EAAS,CAE3D,OAAO,EAAA,WAAW,EAAM,EAAS,CAGnC,IAAM,EAAQ,EAAgB,IAE9B,GAAI,CAAC,EACH,MAAU,MAAM,iFAAiF,CAGnG,IAAM,GAAA,EAAA,EAAA,UAAoB,EAAO,KAAU,EAA+B,MAAS,EAEnF,MAAO,EACJ,EAAA,cAAA,EAAA,EAAA,cAA6B,CAC5B,IAAM,EAAM,GAAU,CAChB,EAAW,EAAS,EAAI,OAAO,EAAO,CAAG,EAE/C,GAAI,CAAC,EAAS,OAAQ,CACpB,IAAM,EAAK,EAAQ,EAAa,GAAO,CAAC,CAAG,IAAA,GAE3C,OAAO,EAAK,CAAE,GAAG,EAAA,cAAc,EAAG,CAAE,MAAO,EAAE,CAAE,KAAM,EAAE,CAAE,CAAG,CAAE,SAAU,EAAE,CAAE,KAAM,GAAI,MAAO,EAAE,CAAE,KAAM,EAAE,CAAE,CAG7G,GAAM,CAAE,WAAU,OAAM,OAAM,YAAa,EAAY,EAAU,EAAU,EAAM,CAEjF,MAAO,CAAE,WAAU,OAAM,MAAO,EAAU,OAAM,EAChD,CACH"}
1
+ {"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { computed, isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport {\n CF_ID_ATTR,\n EACH_SIGNAL,\n escapeHtml,\n extractResult,\n htmlResult,\n isHtmlResult,\n type Binding,\n type Directive,\n type HTMLResult,\n} from '../internal';\n\nconst ATTR_ID_RE = new RegExp(`${CF_ID_ATTR}=\"(\\\\d+)\"`, 'g');\n\n/* immutable — shared singleton for empty static lists */\nconst EMPTY = htmlResult('');\nconst NO_BINDINGS: Binding[] = [];\n\nconst toResultEntry = (value: string | HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } =>\n isHtmlResult(value) ? renumber(value, c) : { bindings: NO_BINDINGS, html: escapeHtml(value) };\n\nconst toHtmlResult = (value: string | HTMLResult): HTMLResult =>\n isHtmlResult(value) ? value : htmlResult(escapeHtml(value));\n\nfunction renumber(res: HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } {\n const map = new Map<string, string>();\n const nb: Binding[] = [];\n\n for (const b of res.__bindings) {\n const oldId = b.uid;\n const newId = map.get(oldId) ?? String(c.n++);\n\n if (!map.has(oldId)) map.set(oldId, newId);\n\n nb.push({ ...b, uid: newId });\n }\n\n return {\n bindings: nb,\n html: res.__html\n // Re-map element binding ids.\n .replace(ATTR_ID_RE, (_, id) => `${CF_ID_ATTR}=\"${map.get(id) ?? id}\"`)\n // Re-map numeric comment markers used by text/html placeholders.\n .replace(/<!--(\\d+)-->/g, (_, id) => `<!--${map.get(id) ?? id}-->`),\n };\n}\n\n/** Render loop used by the reactive path (keys + rendered metadata required for reconciliation). */\nfunction renderKeyed<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n keyFn: (item: T, index: number) => string | number,\n): {\n bindings: Binding[];\n html: string;\n keys: (string | number)[];\n rendered: Array<{ bindings: Binding[]; html: string }>;\n} {\n let html = '';\n const allBindings: Binding[] = [];\n const keys: (string | number)[] = [];\n const seenKeys = new Set<string | number>();\n const rendered: Array<{ bindings: Binding[]; html: string }> = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const nextKey = keyFn(items[i], i);\n\n if (seenKeys.has(nextKey)) {\n throw new Error(`[craftit:each] Duplicate key \"${String(nextKey)}\" at index ${i}.`);\n }\n\n seenKeys.add(nextKey);\n keys.push(nextKey);\n\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n rendered.push(entry);\n }\n\n return { bindings: allBindings, html, keys, rendered };\n}\n\n/** Render loop used by the static path — no key/reconciliation metadata needed. */\nfunction renderStatic<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n): { bindings: Binding[]; html: string } {\n let html = '';\n const allBindings: Binding[] = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n }\n\n return { bindings: allBindings, html };\n}\n\ntype ReactiveSource<T> = ReadonlySignal<T[]> | (() => T[]);\ntype EachSignalResult = Directive & {\n [EACH_SIGNAL]: ReadonlySignal<{\n bindings: Binding[];\n html: string;\n items?: Array<{ bindings: Binding[]; html: string }>;\n keys?: (string | number)[];\n }>;\n};\n\nexport interface EachOptions<T> {\n fallback?: () => string | HTMLResult;\n key?: (item: T, index: number) => string | number;\n render: (item: T, index: number) => string | HTMLResult;\n /**\n * Filter predicate. Receives the item and its original array index.\n * Only items for which `select` returns `true` are rendered.\n */\n select?: (item: T, index: number) => boolean;\n}\n\nconst isFunction = (value: unknown): value is (...args: any[]) => any => typeof value === 'function';\n\nconst validateEachOptions = <T>(options: EachOptions<T>): void => {\n if (!isFunction(options.render)) {\n throw new Error('[craftit:each] options.render must be a function.');\n }\n\n if (options.key !== undefined && !isFunction(options.key)) {\n throw new Error('[craftit:each] options.key must be a function when provided.');\n }\n\n if (options.select !== undefined && !isFunction(options.select)) {\n throw new Error('[craftit:each] options.select must be a function when provided.');\n }\n\n if (options.fallback !== undefined && !isFunction(options.fallback)) {\n throw new Error('[craftit:each] options.fallback must be a function when provided.');\n }\n};\n\nconst assertArrayResult = <T>(value: T[] | unknown): T[] => {\n if (Array.isArray(value)) return value as T[];\n\n throw new Error('[craftit:each] source must resolve to an array.');\n};\n\n/**\n * Renders a list with keyed DOM reconciliation for reactive sources.\n * Use inside `html` tagged templates.\n *\n * For reactive sources (Signal/getter), you must provide a stable `key` function.\n *\n * For dynamic lists with click handlers, prefer event delegation on a parent node\n * (`@click` + `closest(...)`) over per-item handlers inside `each()`.\n *\n * @example\n * import { each } from '@vielzeug/craftit/directives';\n *\n * // Static array (key optional):\n * html`${each([1, 2, 3], { render: (item) => html`<li>${item}</li>` })}`\n *\n * // Reactive source (key required):\n * html`${each(items, { key: item => item.id, render: (item) => html`<li>${item.name}</li>` })}`\n *\n * // Full example:\n * html`${each(items, {\n * fallback: () => html`<p>No items</p>`,\n * key: item => item.id,\n * render: (item) => html`<li>${item.name}</li>`,\n * select: item => item.active,\n * })}`\n */\nexport function each<T>(source: T[], options: EachOptions<T>): HTMLResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(source: T[] | ReactiveSource<T>, options: EachOptions<T>): HTMLResult | EachSignalResult {\n validateEachOptions(options);\n\n const { fallback, key, render, select } = options;\n\n if (Array.isArray(source)) {\n const filtered = select ? source.filter(select) : source;\n\n if (!filtered.length) {\n if (fallback) {\n const er = extractResult(toHtmlResult(fallback()));\n\n return htmlResult(er.html, er.bindings);\n }\n\n return EMPTY;\n }\n\n const { bindings, html } = renderStatic(filtered, render);\n\n return htmlResult(html, bindings);\n }\n\n if (!key) {\n throw new Error('[craftit:each] Reactive each() requires options.key for stable reconciliation.');\n }\n\n const getItems = isSignal(source) ? () => (source as ReadonlySignal<T[]>).value : (source as () => T[]);\n\n return {\n [EACH_SIGNAL]: computed(() => {\n const raw = assertArrayResult<T>(getItems());\n const filtered = select ? raw.filter(select) : raw;\n\n if (!filtered.length) {\n const er = fallback ? toHtmlResult(fallback()) : undefined;\n\n return er ? { ...extractResult(er), items: [], keys: [] } : { bindings: [], html: '', items: [], keys: [] };\n }\n\n const { bindings, html, keys, rendered } = renderKeyed(filtered, render, key);\n\n return { bindings, html, items: rendered, keys };\n }),\n };\n}\n"],"mappings":"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"}
@@ -1,26 +1,5 @@
1
1
  import { type ReadonlySignal } from '@vielzeug/stateit';
2
- import { EACH_SIGNAL, type Binding, type Directive, type HTMLResult } from '../core/internal';
3
- /**
4
- * Options accepted by `each()`.
5
- */
6
- export interface EachOptions<T> {
7
- /**
8
- * Key extractor for stable DOM reconciliation.
9
- * Receives the item and its **filtered** array index (after `select` is applied).
10
- *
11
- * Required when `source` is reactive (Signal/getter).
12
- * Optional for static arrays (render-once path).
13
- */
14
- key?: (item: T, index: number) => string | number;
15
- /**
16
- * Filter predicate. Receives the item and its **original** array index.
17
- * Only items for which `select` returns `true` are rendered.
18
- *
19
- * @example
20
- * each(items, item => html`<li>${item.name}</li>`, undefined, { select: item => item.active })
21
- */
22
- select?: (item: T, index: number) => boolean;
23
- }
2
+ import { EACH_SIGNAL, type Binding, type Directive, type HTMLResult } from '../internal';
24
3
  type ReactiveSource<T> = ReadonlySignal<T[]> | (() => T[]);
25
4
  type EachSignalResult = Directive & {
26
5
  [EACH_SIGNAL]: ReadonlySignal<{
@@ -33,8 +12,18 @@ type EachSignalResult = Directive & {
33
12
  keys?: (string | number)[];
34
13
  }>;
35
14
  };
15
+ export interface EachOptions<T> {
16
+ fallback?: () => string | HTMLResult;
17
+ key?: (item: T, index: number) => string | number;
18
+ render: (item: T, index: number) => string | HTMLResult;
19
+ /**
20
+ * Filter predicate. Receives the item and its original array index.
21
+ * Only items for which `select` returns `true` are rendered.
22
+ */
23
+ select?: (item: T, index: number) => boolean;
24
+ }
36
25
  /**
37
- * Renders a reactive list with keyed DOM reconciliation for efficient updates.
26
+ * Renders a list with keyed DOM reconciliation for reactive sources.
38
27
  * Use inside `html` tagged templates.
39
28
  *
40
29
  * For reactive sources (Signal/getter), you must provide a stable `key` function.
@@ -46,22 +35,21 @@ type EachSignalResult = Directive & {
46
35
  * import { each } from '@vielzeug/craftit/directives';
47
36
  *
48
37
  * // Static array (key optional):
49
- * html`${each([1, 2, 3], item => html`<li>${item}</li>`)}`
38
+ * html`${each([1, 2, 3], { render: (item) => html`<li>${item}</li>` })}`
50
39
  *
51
40
  * // Reactive source (key required):
52
- * html`${each(items, item => html`<li>${item.name}</li>`, undefined, { key: item => item.id })}`
41
+ * html`${each(items, { key: item => item.id, render: (item) => html`<li>${item.name}</li>` })}`
53
42
  *
54
43
  * // Full example:
55
- * html`${each(items, item => html`<li>${item.name}</li>`, () => html`<p>No items</p>`, {
44
+ * html`${each(items, {
45
+ * fallback: () => html`<p>No items</p>`,
56
46
  * key: item => item.id,
47
+ * render: (item) => html`<li>${item.name}</li>`,
57
48
  * select: item => item.active,
58
49
  * })}`
59
50
  */
60
- export declare function each<T>(source: T[], template: (item: T, index: number) => string | HTMLResult, empty?: () => string | HTMLResult, options?: EachOptions<T>): HTMLResult;
61
- export declare function each<T>(source: ReactiveSource<T>, template: (item: T, index: number) => string | HTMLResult, options: EachOptions<T> & {
62
- key: (item: T, index: number) => string | number;
63
- }): EachSignalResult;
64
- export declare function each<T>(source: ReactiveSource<T>, template: (item: T, index: number) => string | HTMLResult, empty: (() => string | HTMLResult) | undefined, options: EachOptions<T> & {
51
+ export declare function each<T>(source: T[], options: EachOptions<T>): HTMLResult;
52
+ export declare function each<T>(source: ReactiveSource<T>, options: EachOptions<T> & {
65
53
  key: (item: T, index: number) => string | number;
66
54
  }): EachSignalResult;
67
55
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAEL,WAAW,EAIX,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAuF1B;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IAClD;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CAC9C;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAC3D,KAAK,gBAAgB,GAAG,SAAS,GAAG;IAClC,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC;QAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACrD,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,MAAM,EAAE,CAAC,EAAE,EACX,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,UAAU,EACzD,KAAK,CAAC,EAAE,MAAM,MAAM,GAAG,UAAU,EACjC,OAAO,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,GACvB,UAAU,CAAC;AACd,wBAAgB,IAAI,CAAC,CAAC,EACpB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,UAAU,EACzD,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAAC;AACpB,wBAAgB,IAAI,CAAC,CAAC,EACpB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,UAAU,EACzD,KAAK,EAAE,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,SAAS,EAC9C,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAAC"}
1
+ {"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAEL,WAAW,EAKX,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AA8FrB,KAAK,cAAc,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAC3D,KAAK,gBAAgB,GAAG,SAAS,GAAG;IAClC,CAAC,WAAW,CAAC,EAAE,cAAc,CAAC;QAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACrD,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;KAC5B,CAAC,CAAC;CACJ,CAAC;AAEF,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,UAAU,CAAC;IACrC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAC;IAClD,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,UAAU,CAAC;IACxD;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CAC9C;AA4BD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC;AAC1E,wBAAgB,IAAI,CAAC,CAAC,EACpB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EACzB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;CAAE,GAC7E,gBAAgB,CAAC"}
@@ -1,2 +1,2 @@
1
- import{EACH_SIGNAL as e,extractResult as t,htmlResult as n,isHtmlResult as r}from"../core/internal.js";import{escapeHtml as i}from"../core/utilities.js";import{computed as a,isSignal as o}from"@vielzeug/stateit";var s=RegExp(`u="(\\d+)"`,`g`),c=n(``),l=[],u=(e,t)=>r(e)?f(e,t):{bindings:l,html:i(e)},d=e=>r(e)?e:n(i(e));function f(e,t){let n=new Map,r=[];for(let i of e.__bindings){let e=i.uid,a=n.get(e)??String(t.n++);n.has(e)||n.set(e,a),r.push({...i,uid:a})}return{bindings:r,html:e.__html.replace(s,(e,t)=>`u="${n.get(t)??t}"`).replace(/<!--(\d+)-->/g,(e,t)=>`<!--${n.get(t)??t}-->`)}}function p(e,t,n){let r=``,i=[],a=[],o=[],s={n:0};for(let c=0;c<e.length;c++){a.push(n(e[c],c));let l=u(t(e[c],c),s);r+=l.html,i.push(...l.bindings),o.push(l)}return{bindings:i,html:r,keys:a,rendered:o}}function m(e,t){let n=``,r=[],i={n:0};for(let a=0;a<e.length;a++){let o=u(t(e[a],a),i);n+=o.html,r.push(...o.bindings)}return{bindings:r,html:n}}function h(r,i,s,l){let u=typeof s==`function`,f=u?s:void 0,h=(u?l:l??s)??{},g=h.select;if(Array.isArray(r)){let e=g?r.filter(g):r;if(!e.length){if(f){let e=t(d(f()));return n(e.html,e.bindings)}return c}let{bindings:a,html:o}=m(e,i);return n(o,a)}let _=h.key;if(!_)throw Error(`[craftit:each] Reactive each() requires options.key for stable reconciliation.`);let v=o(r)?()=>r.value:r;return{[e]:a(()=>{let e=v(),n=g?e.filter(g):e;if(!n.length){let e=f?d(f()):void 0;return e?{...t(e),items:[],keys:[]}:{bindings:[],html:``,items:[],keys:[]}}let{bindings:r,html:a,keys:o,rendered:s}=p(n,i,_);return{bindings:r,html:a,items:s,keys:o}})}}export{h as each};
1
+ import{EACH_SIGNAL as e,escapeHtml as t,extractResult as n,htmlResult as r,isHtmlResult as i}from"../internal.js";import{computed as a,isSignal as o}from"@vielzeug/stateit";var s=RegExp(`u="(\\d+)"`,`g`),c=r(``),l=[],u=(e,n)=>i(e)?f(e,n):{bindings:l,html:t(e)},d=e=>i(e)?e:r(t(e));function f(e,t){let n=new Map,r=[];for(let i of e.__bindings){let e=i.uid,a=n.get(e)??String(t.n++);n.has(e)||n.set(e,a),r.push({...i,uid:a})}return{bindings:r,html:e.__html.replace(s,(e,t)=>`u="${n.get(t)??t}"`).replace(/<!--(\d+)-->/g,(e,t)=>`<!--${n.get(t)??t}-->`)}}function p(e,t,n){let r=``,i=[],a=[],o=new Set,s=[],c={n:0};for(let l=0;l<e.length;l++){let d=n(e[l],l);if(o.has(d))throw Error(`[craftit:each] Duplicate key "${String(d)}" at index ${l}.`);o.add(d),a.push(d);let f=u(t(e[l],l),c);r+=f.html,i.push(...f.bindings),s.push(f)}return{bindings:i,html:r,keys:a,rendered:s}}function m(e,t){let n=``,r=[],i={n:0};for(let a=0;a<e.length;a++){let o=u(t(e[a],a),i);n+=o.html,r.push(...o.bindings)}return{bindings:r,html:n}}var h=e=>typeof e==`function`,g=e=>{if(!h(e.render))throw Error(`[craftit:each] options.render must be a function.`);if(e.key!==void 0&&!h(e.key))throw Error(`[craftit:each] options.key must be a function when provided.`);if(e.select!==void 0&&!h(e.select))throw Error(`[craftit:each] options.select must be a function when provided.`);if(e.fallback!==void 0&&!h(e.fallback))throw Error(`[craftit:each] options.fallback must be a function when provided.`)},_=e=>{if(Array.isArray(e))return e;throw Error(`[craftit:each] source must resolve to an array.`)};function v(t,i){g(i);let{fallback:s,key:l,render:u,select:f}=i;if(Array.isArray(t)){let e=f?t.filter(f):t;if(!e.length){if(s){let e=n(d(s()));return r(e.html,e.bindings)}return c}let{bindings:i,html:a}=m(e,u);return r(a,i)}if(!l)throw Error(`[craftit:each] Reactive each() requires options.key for stable reconciliation.`);let h=o(t)?()=>t.value:t;return{[e]:a(()=>{let e=_(h()),t=f?e.filter(f):e;if(!t.length){let e=s?d(s()):void 0;return e?{...n(e),items:[],keys:[]}:{bindings:[],html:``,items:[],keys:[]}}let{bindings:r,html:i,keys:a,rendered:o}=p(t,u,l);return{bindings:r,html:i,items:o,keys:a}})}}export{v as each};
2
2
  //# sourceMappingURL=each.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { computed, isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport {\n CF_ID_ATTR,\n EACH_SIGNAL,\n extractResult,\n htmlResult,\n isHtmlResult,\n type Binding,\n type Directive,\n type HTMLResult,\n} from '../core/internal';\nimport { escapeHtml } from '../core/utilities';\n\nconst ATTR_ID_RE = new RegExp(`${CF_ID_ATTR}=\"(\\\\d+)\"`, 'g');\n\n/* immutable — shared singleton for empty static lists */\nconst EMPTY = htmlResult('');\nconst NO_BINDINGS: Binding[] = [];\n\nconst toResultEntry = (value: string | HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } =>\n isHtmlResult(value) ? renumber(value, c) : { bindings: NO_BINDINGS, html: escapeHtml(value) };\n\nconst toHtmlResult = (value: string | HTMLResult): HTMLResult =>\n isHtmlResult(value) ? value : htmlResult(escapeHtml(value));\n\nfunction renumber(res: HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } {\n const map = new Map<string, string>();\n const nb: Binding[] = [];\n\n for (const b of res.__bindings) {\n const oldId = b.uid;\n const newId = map.get(oldId) ?? String(c.n++);\n\n if (!map.has(oldId)) map.set(oldId, newId);\n\n nb.push({ ...b, uid: newId });\n }\n\n return {\n bindings: nb,\n html: res.__html\n // Re-map element binding ids.\n .replace(ATTR_ID_RE, (_, id) => `${CF_ID_ATTR}=\"${map.get(id) ?? id}\"`)\n // Re-map numeric comment markers used by text/html placeholders.\n .replace(/<!--(\\d+)-->/g, (_, id) => `<!--${map.get(id) ?? id}-->`),\n };\n}\n\n/** Render loop used by the reactive path (keys + rendered metadata required for reconciliation). */\nfunction renderKeyed<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n keyFn: (item: T, index: number) => string | number,\n): {\n bindings: Binding[];\n html: string;\n keys: (string | number)[];\n rendered: Array<{ bindings: Binding[]; html: string }>;\n} {\n let html = '';\n const allBindings: Binding[] = [];\n const keys: (string | number)[] = [];\n const rendered: Array<{ bindings: Binding[]; html: string }> = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n keys.push(keyFn(items[i], i));\n\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n rendered.push(entry);\n }\n\n return { bindings: allBindings, html, keys, rendered };\n}\n\n/** Render loop used by the static path — no key/reconciliation metadata needed. */\nfunction renderStatic<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n): { bindings: Binding[]; html: string } {\n let html = '';\n const allBindings: Binding[] = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n }\n\n return { bindings: allBindings, html };\n}\n\n/**\n * Options accepted by `each()`.\n */\nexport interface EachOptions<T> {\n /**\n * Key extractor for stable DOM reconciliation.\n * Receives the item and its **filtered** array index (after `select` is applied).\n *\n * Required when `source` is reactive (Signal/getter).\n * Optional for static arrays (render-once path).\n */\n key?: (item: T, index: number) => string | number;\n /**\n * Filter predicate. Receives the item and its **original** array index.\n * Only items for which `select` returns `true` are rendered.\n *\n * @example\n * each(items, item => html`<li>${item.name}</li>`, undefined, { select: item => item.active })\n */\n select?: (item: T, index: number) => boolean;\n}\n\ntype ReactiveSource<T> = ReadonlySignal<T[]> | (() => T[]);\ntype EachSignalResult = Directive & {\n [EACH_SIGNAL]: ReadonlySignal<{\n bindings: Binding[];\n html: string;\n items?: Array<{ bindings: Binding[]; html: string }>;\n keys?: (string | number)[];\n }>;\n};\n\n/**\n * Renders a reactive list with keyed DOM reconciliation for efficient updates.\n * Use inside `html` tagged templates.\n *\n * For reactive sources (Signal/getter), you must provide a stable `key` function.\n *\n * For dynamic lists with click handlers, prefer event delegation on a parent node\n * (`@click` + `closest(...)`) over per-item handlers inside `each()`.\n *\n * @example\n * import { each } from '@vielzeug/craftit/directives';\n *\n * // Static array (key optional):\n * html`${each([1, 2, 3], item => html`<li>${item}</li>`)}`\n *\n * // Reactive source (key required):\n * html`${each(items, item => html`<li>${item.name}</li>`, undefined, { key: item => item.id })}`\n *\n * // Full example:\n * html`${each(items, item => html`<li>${item.name}</li>`, () => html`<p>No items</p>`, {\n * key: item => item.id,\n * select: item => item.active,\n * })}`\n */\nexport function each<T>(\n source: T[],\n template: (item: T, index: number) => string | HTMLResult,\n empty?: () => string | HTMLResult,\n options?: EachOptions<T>,\n): HTMLResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n empty: (() => string | HTMLResult) | undefined,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(\n source: T[] | ReactiveSource<T>,\n template: (item: T, index: number) => string | HTMLResult,\n emptyOrOptions?: (() => string | HTMLResult) | EachOptions<T>,\n options?: EachOptions<T>,\n): HTMLResult | EachSignalResult {\n const hasEmptyFn = typeof emptyOrOptions === 'function';\n const empty = hasEmptyFn ? emptyOrOptions : undefined;\n const resolvedOptions = (hasEmptyFn ? options : (options ?? emptyOrOptions)) ?? {};\n const select = resolvedOptions.select;\n\n if (Array.isArray(source)) {\n const filtered = select ? source.filter(select) : source;\n\n if (!filtered.length) {\n if (empty) {\n const er = extractResult(toHtmlResult(empty()));\n\n return htmlResult(er.html, er.bindings);\n }\n\n return EMPTY;\n }\n\n const { bindings, html } = renderStatic(filtered, template);\n\n return htmlResult(html, bindings);\n }\n\n const keyFn = resolvedOptions.key;\n\n if (!keyFn) {\n throw new Error('[craftit:each] Reactive each() requires options.key for stable reconciliation.');\n }\n\n const getItems = isSignal(source) ? () => (source as ReadonlySignal<T[]>).value : (source as () => T[]);\n\n return {\n [EACH_SIGNAL]: computed(() => {\n const raw = getItems();\n const filtered = select ? raw.filter(select) : raw;\n\n if (!filtered.length) {\n const er = empty ? toHtmlResult(empty()) : undefined;\n\n return er ? { ...extractResult(er), items: [], keys: [] } : { bindings: [], html: '', items: [], keys: [] };\n }\n\n const { bindings, html, keys, rendered } = renderKeyed(filtered, template, keyFn);\n\n return { bindings, html, items: rendered, keys };\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { computed, isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport {\n CF_ID_ATTR,\n EACH_SIGNAL,\n escapeHtml,\n extractResult,\n htmlResult,\n isHtmlResult,\n type Binding,\n type Directive,\n type HTMLResult,\n} from '../internal';\n\nconst ATTR_ID_RE = new RegExp(`${CF_ID_ATTR}=\"(\\\\d+)\"`, 'g');\n\n/* immutable — shared singleton for empty static lists */\nconst EMPTY = htmlResult('');\nconst NO_BINDINGS: Binding[] = [];\n\nconst toResultEntry = (value: string | HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } =>\n isHtmlResult(value) ? renumber(value, c) : { bindings: NO_BINDINGS, html: escapeHtml(value) };\n\nconst toHtmlResult = (value: string | HTMLResult): HTMLResult =>\n isHtmlResult(value) ? value : htmlResult(escapeHtml(value));\n\nfunction renumber(res: HTMLResult, c: { n: number }): { bindings: Binding[]; html: string } {\n const map = new Map<string, string>();\n const nb: Binding[] = [];\n\n for (const b of res.__bindings) {\n const oldId = b.uid;\n const newId = map.get(oldId) ?? String(c.n++);\n\n if (!map.has(oldId)) map.set(oldId, newId);\n\n nb.push({ ...b, uid: newId });\n }\n\n return {\n bindings: nb,\n html: res.__html\n // Re-map element binding ids.\n .replace(ATTR_ID_RE, (_, id) => `${CF_ID_ATTR}=\"${map.get(id) ?? id}\"`)\n // Re-map numeric comment markers used by text/html placeholders.\n .replace(/<!--(\\d+)-->/g, (_, id) => `<!--${map.get(id) ?? id}-->`),\n };\n}\n\n/** Render loop used by the reactive path (keys + rendered metadata required for reconciliation). */\nfunction renderKeyed<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n keyFn: (item: T, index: number) => string | number,\n): {\n bindings: Binding[];\n html: string;\n keys: (string | number)[];\n rendered: Array<{ bindings: Binding[]; html: string }>;\n} {\n let html = '';\n const allBindings: Binding[] = [];\n const keys: (string | number)[] = [];\n const seenKeys = new Set<string | number>();\n const rendered: Array<{ bindings: Binding[]; html: string }> = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const nextKey = keyFn(items[i], i);\n\n if (seenKeys.has(nextKey)) {\n throw new Error(`[craftit:each] Duplicate key \"${String(nextKey)}\" at index ${i}.`);\n }\n\n seenKeys.add(nextKey);\n keys.push(nextKey);\n\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n rendered.push(entry);\n }\n\n return { bindings: allBindings, html, keys, rendered };\n}\n\n/** Render loop used by the static path — no key/reconciliation metadata needed. */\nfunction renderStatic<T>(\n items: T[],\n template: (item: T, index: number) => string | HTMLResult,\n): { bindings: Binding[]; html: string } {\n let html = '';\n const allBindings: Binding[] = [];\n const c = { n: 0 };\n\n for (let i = 0; i < items.length; i++) {\n const entry = toResultEntry(template(items[i], i), c);\n\n html += entry.html;\n allBindings.push(...entry.bindings);\n }\n\n return { bindings: allBindings, html };\n}\n\ntype ReactiveSource<T> = ReadonlySignal<T[]> | (() => T[]);\ntype EachSignalResult = Directive & {\n [EACH_SIGNAL]: ReadonlySignal<{\n bindings: Binding[];\n html: string;\n items?: Array<{ bindings: Binding[]; html: string }>;\n keys?: (string | number)[];\n }>;\n};\n\nexport interface EachOptions<T> {\n fallback?: () => string | HTMLResult;\n key?: (item: T, index: number) => string | number;\n render: (item: T, index: number) => string | HTMLResult;\n /**\n * Filter predicate. Receives the item and its original array index.\n * Only items for which `select` returns `true` are rendered.\n */\n select?: (item: T, index: number) => boolean;\n}\n\nconst isFunction = (value: unknown): value is (...args: any[]) => any => typeof value === 'function';\n\nconst validateEachOptions = <T>(options: EachOptions<T>): void => {\n if (!isFunction(options.render)) {\n throw new Error('[craftit:each] options.render must be a function.');\n }\n\n if (options.key !== undefined && !isFunction(options.key)) {\n throw new Error('[craftit:each] options.key must be a function when provided.');\n }\n\n if (options.select !== undefined && !isFunction(options.select)) {\n throw new Error('[craftit:each] options.select must be a function when provided.');\n }\n\n if (options.fallback !== undefined && !isFunction(options.fallback)) {\n throw new Error('[craftit:each] options.fallback must be a function when provided.');\n }\n};\n\nconst assertArrayResult = <T>(value: T[] | unknown): T[] => {\n if (Array.isArray(value)) return value as T[];\n\n throw new Error('[craftit:each] source must resolve to an array.');\n};\n\n/**\n * Renders a list with keyed DOM reconciliation for reactive sources.\n * Use inside `html` tagged templates.\n *\n * For reactive sources (Signal/getter), you must provide a stable `key` function.\n *\n * For dynamic lists with click handlers, prefer event delegation on a parent node\n * (`@click` + `closest(...)`) over per-item handlers inside `each()`.\n *\n * @example\n * import { each } from '@vielzeug/craftit/directives';\n *\n * // Static array (key optional):\n * html`${each([1, 2, 3], { render: (item) => html`<li>${item}</li>` })}`\n *\n * // Reactive source (key required):\n * html`${each(items, { key: item => item.id, render: (item) => html`<li>${item.name}</li>` })}`\n *\n * // Full example:\n * html`${each(items, {\n * fallback: () => html`<p>No items</p>`,\n * key: item => item.id,\n * render: (item) => html`<li>${item.name}</li>`,\n * select: item => item.active,\n * })}`\n */\nexport function each<T>(source: T[], options: EachOptions<T>): HTMLResult;\nexport function each<T>(\n source: ReactiveSource<T>,\n options: EachOptions<T> & { key: (item: T, index: number) => string | number },\n): EachSignalResult;\nexport function each<T>(source: T[] | ReactiveSource<T>, options: EachOptions<T>): HTMLResult | EachSignalResult {\n validateEachOptions(options);\n\n const { fallback, key, render, select } = options;\n\n if (Array.isArray(source)) {\n const filtered = select ? source.filter(select) : source;\n\n if (!filtered.length) {\n if (fallback) {\n const er = extractResult(toHtmlResult(fallback()));\n\n return htmlResult(er.html, er.bindings);\n }\n\n return EMPTY;\n }\n\n const { bindings, html } = renderStatic(filtered, render);\n\n return htmlResult(html, bindings);\n }\n\n if (!key) {\n throw new Error('[craftit:each] Reactive each() requires options.key for stable reconciliation.');\n }\n\n const getItems = isSignal(source) ? () => (source as ReadonlySignal<T[]>).value : (source as () => T[]);\n\n return {\n [EACH_SIGNAL]: computed(() => {\n const raw = assertArrayResult<T>(getItems());\n const filtered = select ? raw.filter(select) : raw;\n\n if (!filtered.length) {\n const er = fallback ? toHtmlResult(fallback()) : undefined;\n\n return er ? { ...extractResult(er), items: [], keys: [] } : { bindings: [], html: '', items: [], keys: [] };\n }\n\n const { bindings, html, keys, rendered } = renderKeyed(filtered, render, key);\n\n return { bindings, html, items: rendered, keys };\n }),\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,8 @@
1
- export { attr } from './attr';
1
+ export { attrs } from './attr';
2
2
  export { bind } from './bind';
3
3
  export { choose } from './choose';
4
4
  export { classes } from './classes';
5
5
  export { each } from './each';
6
- export { match } from './match';
7
6
  export { memo } from './memo';
8
7
  export { on } from './on';
9
8
  export { raw } from './raw';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
@@ -1,2 +1,2 @@
1
- let e=require(`@vielzeug/stateit`);function t(t,n){let r=``,i=[],a=!1;return()=>{let o=t.map(t=>(0,e.isSignal)(t)?t.value:t);return(!a||o.length!==i.length||o.some((e,t)=>!Object.is(e,i[t])))&&(r=n(),i=o,a=!0),r}}exports.memo=t;
1
+ let e=require(`@vielzeug/stateit`);function t(t){let n=(0,e.computed)(()=>{for(let n of t.deps??[])(0,e.isSignal)(n)&&Reflect.get(n,`value`);return(0,e.untrack)(t.render)});return()=>n.value}exports.memo=t;
2
2
  //# sourceMappingURL=memo.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"memo.cjs","names":[],"sources":["../../src/directives/memo.ts"],"sourcesContent":["import { isSignal, type ReadonlySignal } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../core/internal';\n\ntype Dep = unknown | ReadonlySignal<unknown>;\n\n/**\n * Memoizes a template fragment — `templateFn` is only re-called when at least\n * one entry in `deps` has changed (compared with `Object.is`).\n *\n * Signal values in `deps` are automatically unwrapped and tracked, so changing a\n * dep signal triggers a re-evaluation. This is useful for skipping expensive\n * sub-tree renders when only unrelated state changes.\n *\n * @example\n * import { memo } from '@vielzeug/craftit/directives';\n *\n * // Only re-renders the table when `rows` actually changes\n * html`${memo([rows], () => html`<big-table :data=${rows}></big-table>`)}`\n *\n * // Multiple deps — re-renders when either changes\n * html`${memo([locale, theme], () => html`<themed-chart :locale=${locale}></themed-chart>`)}`\n */\nexport function memo(deps: ReadonlyArray<Dep>, templateFn: () => string | HTMLResult): () => string | HTMLResult {\n let cached: string | HTMLResult = '';\n let lastDeps: unknown[] = [];\n let initialized = false;\n\n return (): string | HTMLResult => {\n const current = deps.map((d) => (isSignal(d) ? (d as ReadonlySignal<unknown>).value : d));\n const changed =\n !initialized || current.length !== lastDeps.length || current.some((v, i) => !Object.is(v, lastDeps[i]));\n\n if (changed) {\n cached = templateFn();\n lastDeps = current;\n initialized = true;\n }\n\n return cached;\n };\n}\n"],"mappings":"mCAuBA,SAAgB,EAAK,EAA0B,EAAkE,CAC/G,IAAI,EAA8B,GAC9B,EAAsB,EAAE,CACxB,EAAc,GAElB,UAAkC,CAChC,IAAM,EAAU,EAAK,IAAK,IAAA,EAAA,EAAA,UAAgB,EAAE,CAAI,EAA8B,MAAQ,EAAG,CAUzF,OARE,CAAC,GAAe,EAAQ,SAAW,EAAS,QAAU,EAAQ,MAAM,EAAG,IAAM,CAAC,OAAO,GAAG,EAAG,EAAS,GAAG,CAAC,IAGxG,EAAS,GAAY,CACrB,EAAW,EACX,EAAc,IAGT"}
1
+ {"version":3,"file":"memo.cjs","names":[],"sources":["../../src/directives/memo.ts"],"sourcesContent":["import { computed, isSignal, type ReadonlySignal, untrack } from '@vielzeug/stateit';\n\nimport type { HTMLResult } from '../internal';\n\ntype Dep = unknown | ReadonlySignal<unknown>;\n\nexport type MemoOptions = {\n deps?: ReadonlyArray<Dep>;\n render: () => string | HTMLResult;\n};\n\n/**\n * Memoizes a template fragment — `templateFn` is only re-called when at least\n * one entry in `deps` has changed (compared with `Object.is`).\n *\n * Signal values in `deps` are automatically unwrapped and tracked, so changing a\n * dep signal triggers a re-evaluation. This is useful for skipping expensive\n * sub-tree renders when only unrelated state changes.\n *\n * @example\n * import { memo } from '@vielzeug/craftit/directives';\n *\n * // Only re-renders the table when `rows` actually changes\n * html`${memo({ deps: [rows], render: () => html`<big-table :data=${rows}></big-table>` })}`\n *\n * // Multiple deps — re-renders when either changes\n * html`${memo({ deps: [locale, theme], render: () => html`<themed-chart :locale=${locale}></themed-chart>` })}`\n */\nexport function memo(options: MemoOptions): () => string | HTMLResult {\n const renderSignal = computed(() => {\n for (const dep of options.deps ?? []) {\n if (isSignal(dep)) Reflect.get(dep, 'value');\n }\n\n return untrack(options.render);\n });\n\n return () => renderSignal.value;\n}\n"],"mappings":"mCA4BA,SAAgB,EAAK,EAAiD,CACpE,IAAM,GAAA,EAAA,EAAA,cAA8B,CAClC,IAAK,IAAM,KAAO,EAAQ,MAAQ,EAAE,EAClC,EAAA,EAAA,UAAa,EAAI,EAAE,QAAQ,IAAI,EAAK,QAAQ,CAG9C,OAAA,EAAA,EAAA,SAAe,EAAQ,OAAO,EAC9B,CAEF,UAAa,EAAa"}